Reorganise components into atoms/molecules/organisms and fix Input icon colours
Moved all 17 components from ui/ into atomic design tiers: atoms (Button, IconButton, Input, Textarea, Select, Checkbox, Radio, Switch, Badge, Tag, Chip, Tooltip) and molecules (Alert, Accordion, Card, Dialog, Popover). Updated all Storybook titles and cross-component imports. Changed Input icons to primary-dark and replaced palette token references with semantic tokens. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
250
src/components/molecules/Dialog/Dialog.stories.tsx
Normal file
250
src/components/molecules/Dialog/Dialog.stories.tsx
Normal file
@@ -0,0 +1,250 @@
|
||||
import { useState } from 'react'
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite'
|
||||
import { AlertTriangle } from 'lucide-react'
|
||||
import { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogContent, DialogFooter } from './Dialog'
|
||||
import { Button } from '@/components/atoms/Button'
|
||||
import { Input } from '@/components/atoms/Input'
|
||||
|
||||
const meta: Meta<typeof Dialog> = {
|
||||
title: 'Molecules/Dialog',
|
||||
component: Dialog,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
size: {
|
||||
control: 'select',
|
||||
options: ['sm', 'default', 'lg', 'full'],
|
||||
},
|
||||
closeOnBackdrop: {
|
||||
control: 'boolean',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
// --- Default ---
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => {
|
||||
const [open, setOpen] = useState(false)
|
||||
return (
|
||||
<>
|
||||
<Button onClick={() => setOpen(true)}>Open dialog</Button>
|
||||
<Dialog open={open} onClose={() => setOpen(false)}>
|
||||
<DialogHeader onClose={() => setOpen(false)}>
|
||||
<DialogTitle>Dialog title</DialogTitle>
|
||||
<DialogDescription>A short description of the dialog purpose.</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogContent>
|
||||
<p className="text-body text-text">
|
||||
This is the dialog body. It can contain any content — text, forms, lists, or other
|
||||
components.
|
||||
</p>
|
||||
</DialogContent>
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" intent="neutral" onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={() => setOpen(false)}>Confirm</Button>
|
||||
</DialogFooter>
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
// --- Small ---
|
||||
|
||||
export const Small: Story = {
|
||||
render: () => {
|
||||
const [open, setOpen] = useState(false)
|
||||
return (
|
||||
<>
|
||||
<Button onClick={() => setOpen(true)}>Open small dialog</Button>
|
||||
<Dialog open={open} onClose={() => setOpen(false)} size="sm">
|
||||
<DialogHeader onClose={() => setOpen(false)}>
|
||||
<DialogTitle>Quick confirmation</DialogTitle>
|
||||
</DialogHeader>
|
||||
<DialogContent>
|
||||
<p className="text-body text-text">Are you sure you want to proceed?</p>
|
||||
</DialogContent>
|
||||
<DialogFooter>
|
||||
<Button variant="tertiary" intent="neutral" onClick={() => setOpen(false)}>
|
||||
No
|
||||
</Button>
|
||||
<Button onClick={() => setOpen(false)}>Yes</Button>
|
||||
</DialogFooter>
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
// --- Large ---
|
||||
|
||||
export const Large: Story = {
|
||||
render: () => {
|
||||
const [open, setOpen] = useState(false)
|
||||
return (
|
||||
<>
|
||||
<Button onClick={() => setOpen(true)}>Open large dialog</Button>
|
||||
<Dialog open={open} onClose={() => setOpen(false)} size="lg">
|
||||
<DialogHeader onClose={() => setOpen(false)}>
|
||||
<DialogTitle>Review submission details</DialogTitle>
|
||||
<DialogDescription>
|
||||
Please review the information below before submitting.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogContent>
|
||||
<div className="space-y-3">
|
||||
<div className="rounded-lg bg-bg px-4 py-3">
|
||||
<p className="text-small font-bold text-text">Participant count</p>
|
||||
<p className="text-body text-text-secondary">24 participants across 3 schools</p>
|
||||
</div>
|
||||
<div className="rounded-lg bg-bg px-4 py-3">
|
||||
<p className="text-small font-bold text-text">Data collection period</p>
|
||||
<p className="text-body text-text-secondary">March 2026 — June 2026</p>
|
||||
</div>
|
||||
<div className="rounded-lg bg-bg px-4 py-3">
|
||||
<p className="text-small font-bold text-text">Ethics approval</p>
|
||||
<p className="text-body text-text-secondary">SERAP 2026-0142 (approved)</p>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" intent="neutral" onClick={() => setOpen(false)}>
|
||||
Go back
|
||||
</Button>
|
||||
<Button onClick={() => setOpen(false)}>Submit</Button>
|
||||
</DialogFooter>
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
// --- Danger confirmation ---
|
||||
|
||||
export const DangerConfirmation: Story = {
|
||||
render: () => {
|
||||
const [open, setOpen] = useState(false)
|
||||
return (
|
||||
<>
|
||||
<Button intent="danger" onClick={() => setOpen(true)}>
|
||||
Delete project
|
||||
</Button>
|
||||
<Dialog open={open} onClose={() => setOpen(false)} size="sm">
|
||||
<DialogHeader onClose={() => setOpen(false)}>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex size-10 items-center justify-center rounded-full bg-error/10">
|
||||
<AlertTriangle className="size-5 text-error" />
|
||||
</div>
|
||||
<DialogTitle>Delete project?</DialogTitle>
|
||||
</div>
|
||||
</DialogHeader>
|
||||
<DialogContent>
|
||||
<p className="text-body text-text">
|
||||
This action cannot be undone. All data, themes, and participant responses associated
|
||||
with this project will be permanently deleted.
|
||||
</p>
|
||||
</DialogContent>
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" intent="neutral" onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button intent="danger" onClick={() => setOpen(false)}>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
// --- With form ---
|
||||
|
||||
export const WithForm: Story = {
|
||||
render: () => {
|
||||
const [open, setOpen] = useState(false)
|
||||
return (
|
||||
<>
|
||||
<Button onClick={() => setOpen(true)}>Create new theme</Button>
|
||||
<Dialog open={open} onClose={() => setOpen(false)}>
|
||||
<DialogHeader onClose={() => setOpen(false)}>
|
||||
<DialogTitle>New theme</DialogTitle>
|
||||
<DialogDescription>
|
||||
Give your theme a name and description to help organise your findings.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogContent>
|
||||
<div className="space-y-4">
|
||||
<Input label="Theme name" placeholder="e.g. Student engagement" />
|
||||
<Input label="Description" placeholder="Brief summary of this theme" hint="Optional — you can add this later" />
|
||||
</div>
|
||||
</DialogContent>
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" intent="neutral" onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={() => setOpen(false)}>Create theme</Button>
|
||||
</DialogFooter>
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
// --- No close button ---
|
||||
|
||||
export const NoCloseButton: Story = {
|
||||
render: () => {
|
||||
const [open, setOpen] = useState(false)
|
||||
return (
|
||||
<>
|
||||
<Button onClick={() => setOpen(true)}>Open without close button</Button>
|
||||
<Dialog open={open} onClose={() => setOpen(false)} closeOnBackdrop={false}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Terms of use</DialogTitle>
|
||||
</DialogHeader>
|
||||
<DialogContent>
|
||||
<p className="text-body text-text">
|
||||
You must accept the terms of use before continuing. This dialog cannot be dismissed
|
||||
by clicking the backdrop or pressing Escape — only through the action buttons.
|
||||
</p>
|
||||
</DialogContent>
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" intent="neutral" onClick={() => setOpen(false)}>
|
||||
Decline
|
||||
</Button>
|
||||
<Button onClick={() => setOpen(false)}>Accept</Button>
|
||||
</DialogFooter>
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
// --- Content only ---
|
||||
|
||||
export const ContentOnly: Story = {
|
||||
render: () => {
|
||||
const [open, setOpen] = useState(false)
|
||||
return (
|
||||
<>
|
||||
<Button onClick={() => setOpen(true)}>Open minimal dialog</Button>
|
||||
<Dialog open={open} onClose={() => setOpen(false)} size="sm">
|
||||
<DialogContent className="py-6">
|
||||
<p className="text-center text-body text-text">
|
||||
Your changes have been saved.
|
||||
</p>
|
||||
<div className="mt-4 flex justify-center">
|
||||
<Button onClick={() => setOpen(false)}>Done</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user