@yuno-payments/dashboard-design-system 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.storybook/main.ts +20 -0
- package/.storybook/preview.ts +18 -0
- package/.storybook/vitest.setup.ts +7 -0
- package/README.md +69 -0
- package/components.json +21 -0
- package/eslint.config.js +26 -0
- package/index.html +13 -0
- package/package.json +57 -0
- package/public/vite.svg +1 -0
- package/src/App.css +42 -0
- package/src/App.tsx +11 -0
- package/src/assets/react.svg +1 -0
- package/src/components/atoms/button/button.stories.tsx +222 -0
- package/src/components/atoms/button/button.test.tsx +78 -0
- package/src/components/atoms/button/index.tsx +80 -0
- package/src/components/atoms/checkbox/checkbox.stories.tsx +314 -0
- package/src/components/atoms/checkbox/checkbox.test.tsx +278 -0
- package/src/components/atoms/checkbox/index.tsx +103 -0
- package/src/components/atoms/chip/chip.stories.tsx +317 -0
- package/src/components/atoms/chip/chip.test.tsx +300 -0
- package/src/components/atoms/chip/index.tsx +114 -0
- package/src/components/atoms/input/index.tsx +27 -0
- package/src/components/atoms/link/index.tsx +79 -0
- package/src/components/atoms/link/link.stories.tsx +159 -0
- package/src/components/atoms/link/link.test.tsx +176 -0
- package/src/components/atoms/radiobutton/index.tsx +103 -0
- package/src/components/atoms/radiobutton/radiobutton.stories.tsx +314 -0
- package/src/components/atoms/radiobutton/radiobutton.test.tsx +245 -0
- package/src/components/atoms/tag/index.tsx +196 -0
- package/src/components/atoms/tag/tag.stories.tsx +281 -0
- package/src/components/atoms/tag/tag.test.tsx +282 -0
- package/src/components/atoms/typography/index.tsx +62 -0
- package/src/components/atoms/typography/typography.stories.tsx +214 -0
- package/src/components/atoms/typography/typography.test.tsx +187 -0
- package/src/components/index.tsx +17 -0
- package/src/components/molecules/announcement/announcement.stories.tsx +277 -0
- package/src/components/molecules/announcement/announcement.test.tsx +354 -0
- package/src/components/molecules/announcement/index.tsx +200 -0
- package/src/components/molecules/notification-alert/index.tsx +293 -0
- package/src/components/molecules/notification-alert/notification-alert.stories.tsx +418 -0
- package/src/components/molecules/notification-alert/notification-alert.test.tsx +454 -0
- package/src/components/molecules/popover/index.tsx +175 -0
- package/src/components/molecules/popover/popover.stories.tsx +241 -0
- package/src/components/molecules/popover/popover.test.tsx +191 -0
- package/src/components/molecules/textfield/index.tsx +154 -0
- package/src/components/molecules/textfield/textfield.stories.tsx +168 -0
- package/src/components/molecules/textfield/textfield.test.tsx +157 -0
- package/src/components/molecules/tooltip/index.tsx +263 -0
- package/src/components/molecules/tooltip/tooltip.stories.tsx +363 -0
- package/src/components/molecules/tooltip/tooltip.test.tsx +468 -0
- package/src/components/organisms/dialog/dialog.stories.tsx +522 -0
- package/src/components/organisms/dialog/dialog.test.tsx +525 -0
- package/src/components/organisms/dialog/index.tsx +233 -0
- package/src/components/organisms/dropdown/dropdown.stories.tsx +529 -0
- package/src/components/organisms/dropdown/dropdown.test.tsx +390 -0
- package/src/components/organisms/dropdown/index.tsx +624 -0
- package/src/index.css +184 -0
- package/src/lib/color-utils.ts +94 -0
- package/src/lib/utils.ts +6 -0
- package/src/main.tsx +10 -0
- package/src/stories/Colors.stories.tsx +107 -0
- package/src/stories/Shadows.stories.tsx +110 -0
- package/src/stories/Spacing.stories.tsx +121 -0
- package/src/stories/Typography.stories.tsx +197 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.app.json +33 -0
- package/tsconfig.json +13 -0
- package/tsconfig.node.json +25 -0
- package/vite.config.ts +43 -0
- package/vitest.config.ts +15 -0
- package/vitest.shims.d.ts +1 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
import { createRoot } from 'react-dom/client'
|
|
4
|
+
import { act } from 'react-dom/test-utils'
|
|
5
|
+
import { ArrowRight, ExternalLink } from 'lucide-react'
|
|
6
|
+
import { Link } from './index'
|
|
7
|
+
|
|
8
|
+
function render(ui: React.ReactElement) {
|
|
9
|
+
const container = document.createElement('div')
|
|
10
|
+
document.body.appendChild(container)
|
|
11
|
+
const root = createRoot(container)
|
|
12
|
+
act(() => {
|
|
13
|
+
root.render(ui)
|
|
14
|
+
})
|
|
15
|
+
return { container, root }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getByRole(container: HTMLElement, role: string) {
|
|
19
|
+
const el = Array.from(container.querySelectorAll('*')).find((n) => n.getAttribute('role') === role || n.tagName.toLowerCase() === role)
|
|
20
|
+
if (!el) throw new Error(`Element with role ${role} not found`)
|
|
21
|
+
return el as HTMLElement
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getByTestId(container: HTMLElement, testId: string) {
|
|
25
|
+
const el = container.querySelector(`[data-testid="${testId}"]`)
|
|
26
|
+
if (!el) throw new Error(`Element with testid ${testId} not found`)
|
|
27
|
+
return el as HTMLElement
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe('Link', () => {
|
|
31
|
+
it('renders children correctly', () => {
|
|
32
|
+
const { container } = render(<Link href="#">Test Link</Link>)
|
|
33
|
+
expect(container.textContent).toContain('Test Link')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('renders with default variant and size', () => {
|
|
37
|
+
const { container } = render(<Link href="#">Default Link</Link>)
|
|
38
|
+
const link = getByRole(container, 'a')
|
|
39
|
+
expect(link.className).toMatch(/text-primary/)
|
|
40
|
+
expect(link.className).toMatch(/text-\[var\(--font-size-tiny\)\]/)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('applies small size correctly', () => {
|
|
44
|
+
const { container } = render(<Link href="#" size="small">Small Link</Link>)
|
|
45
|
+
const link = getByRole(container, 'a')
|
|
46
|
+
expect(link.className).toMatch(/text-\[var\(--font-size-tiny\)\]/)
|
|
47
|
+
expect(link.className).toMatch(/leading-\[var\(--line-height-tiny\)\]/)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('applies medium size correctly', () => {
|
|
51
|
+
const { container } = render(<Link href="#" size="medium">Medium Link</Link>)
|
|
52
|
+
const link = getByRole(container, 'a')
|
|
53
|
+
expect(link.className).toMatch(/text-\[var\(--font-size-small\)\]/)
|
|
54
|
+
expect(link.className).toMatch(/leading-\[var\(--line-height-small\)\]/)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('applies inline variant correctly', () => {
|
|
58
|
+
const { container } = render(<Link href="#" inline>Inline Link</Link>)
|
|
59
|
+
const link = getByRole(container, 'a')
|
|
60
|
+
expect(link.className).toMatch(/underline/)
|
|
61
|
+
expect(link.className).toMatch(/underline-offset-4/)
|
|
62
|
+
expect(link.className).toMatch(/font-bold/)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('applies disabled state correctly', () => {
|
|
66
|
+
const { container } = render(<Link href="#" disabled>Disabled Link</Link>)
|
|
67
|
+
const link = getByRole(container, 'a')
|
|
68
|
+
expect(link.className).toMatch(/pointer-events-none/)
|
|
69
|
+
expect(link.className).toMatch(/opacity-50/)
|
|
70
|
+
expect(link.className).toMatch(/text-muted-foreground/)
|
|
71
|
+
expect(link.getAttribute('aria-disabled')).toBe('true')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('renders start icon correctly', () => {
|
|
75
|
+
const { container } = render(
|
|
76
|
+
<Link href="#" startIcon={<ExternalLink data-testid="start-icon" />}>
|
|
77
|
+
Link with Start Icon
|
|
78
|
+
</Link>
|
|
79
|
+
)
|
|
80
|
+
expect(getByTestId(container, 'start-icon')).toBeTruthy()
|
|
81
|
+
expect(container.textContent).toContain('Link with Start Icon')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('renders end icon correctly', () => {
|
|
85
|
+
const { container } = render(
|
|
86
|
+
<Link href="#" endIcon={<ArrowRight data-testid="end-icon" />}>
|
|
87
|
+
Link with End Icon
|
|
88
|
+
</Link>
|
|
89
|
+
)
|
|
90
|
+
expect(getByTestId(container, 'end-icon')).toBeTruthy()
|
|
91
|
+
expect(container.textContent).toContain('Link with End Icon')
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('renders both start and end icons correctly', () => {
|
|
95
|
+
const { container } = render(
|
|
96
|
+
<Link
|
|
97
|
+
href="#"
|
|
98
|
+
startIcon={<ExternalLink data-testid="start-icon" />}
|
|
99
|
+
endIcon={<ArrowRight data-testid="end-icon" />}
|
|
100
|
+
>
|
|
101
|
+
Link with Both Icons
|
|
102
|
+
</Link>
|
|
103
|
+
)
|
|
104
|
+
expect(getByTestId(container, 'start-icon')).toBeTruthy()
|
|
105
|
+
expect(getByTestId(container, 'end-icon')).toBeTruthy()
|
|
106
|
+
expect(container.textContent).toContain('Link with Both Icons')
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('applies custom className', () => {
|
|
110
|
+
const { container } = render(<Link href="#" className="custom-class">Custom Link</Link>)
|
|
111
|
+
const link = getByRole(container, 'a')
|
|
112
|
+
expect(link.className).toMatch(/custom-class/)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('forwards ref correctly', () => {
|
|
116
|
+
const ref = vi.fn()
|
|
117
|
+
render(<Link href="#" ref={ref}>Ref Link</Link>)
|
|
118
|
+
expect(ref).toHaveBeenCalled()
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('handles href attribute correctly', () => {
|
|
122
|
+
const { container } = render(<Link href="https://example.com">External Link</Link>)
|
|
123
|
+
const link = getByRole(container, 'a')
|
|
124
|
+
expect(link.getAttribute('href')).toBe('https://example.com')
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('handles target attribute correctly', () => {
|
|
128
|
+
const { container } = render(<Link href="#" target="_blank">Target Link</Link>)
|
|
129
|
+
const link = getByRole(container, 'a')
|
|
130
|
+
expect(link.getAttribute('target')).toBe('_blank')
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('combines inline and size variants correctly', () => {
|
|
134
|
+
const { container } = render(<Link href="#" inline size="medium">Medium Inline Link</Link>)
|
|
135
|
+
const link = getByRole(container, 'a')
|
|
136
|
+
expect(link.className).toMatch(/underline/)
|
|
137
|
+
expect(link.className).toMatch(/font-bold/)
|
|
138
|
+
expect(link.className).toMatch(/text-\[var\(--font-size-small\)\]/)
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('handles disabled inline links correctly', () => {
|
|
142
|
+
const { container } = render(<Link href="#" inline disabled>Disabled Inline Link</Link>)
|
|
143
|
+
const link = getByRole(container, 'a')
|
|
144
|
+
expect(link.className).toMatch(/underline/)
|
|
145
|
+
expect(link.className).toMatch(/pointer-events-none/)
|
|
146
|
+
expect(link.className).toMatch(/opacity-50/)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('applies all size variants correctly', () => {
|
|
150
|
+
const { container } = render(<Link href="#" size="small">Small</Link>)
|
|
151
|
+
let link = getByRole(container, 'a')
|
|
152
|
+
expect(link.className).toMatch(/text-\[var\(--font-size-tiny\)\]/)
|
|
153
|
+
|
|
154
|
+
const { container: container2 } = render(<Link href="#" size="medium">Medium</Link>)
|
|
155
|
+
link = getByRole(container2, 'a')
|
|
156
|
+
expect(link.className).toMatch(/text-\[var\(--font-size-small\)\]/)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('handles all HTML anchor attributes', () => {
|
|
160
|
+
const { container } = render(
|
|
161
|
+
<Link
|
|
162
|
+
href="https://example.com"
|
|
163
|
+
target="_blank"
|
|
164
|
+
rel="noopener noreferrer"
|
|
165
|
+
title="Example Link"
|
|
166
|
+
>
|
|
167
|
+
Full Link
|
|
168
|
+
</Link>
|
|
169
|
+
)
|
|
170
|
+
const link = getByRole(container, 'a')
|
|
171
|
+
expect(link.getAttribute('href')).toBe('https://example.com')
|
|
172
|
+
expect(link.getAttribute('target')).toBe('_blank')
|
|
173
|
+
expect(link.getAttribute('rel')).toBe('noopener noreferrer')
|
|
174
|
+
expect(link.getAttribute('title')).toBe('Example Link')
|
|
175
|
+
})
|
|
176
|
+
})
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { Circle, CircleDot } from "lucide-react"
|
|
4
|
+
import { cn } from "../../../lib/utils"
|
|
5
|
+
|
|
6
|
+
const radioButtonVariants = cva(
|
|
7
|
+
"peer relative inline-flex items-center justify-center rounded-full border-2 transition-all outline-none disabled:pointer-events-none disabled:opacity-50",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
size: {
|
|
11
|
+
sm: "size-3.5",
|
|
12
|
+
md: "size-4",
|
|
13
|
+
lg: "size-5",
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
defaultVariants: {
|
|
17
|
+
size: "md",
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
const radioIconVariants = cva(
|
|
23
|
+
"transition-all",
|
|
24
|
+
{
|
|
25
|
+
variants: {
|
|
26
|
+
size: {
|
|
27
|
+
sm: "size-3.5",
|
|
28
|
+
md: "size-4",
|
|
29
|
+
lg: "size-5",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
defaultVariants: {
|
|
33
|
+
size: "md",
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
export interface RadioButtonProps
|
|
39
|
+
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'>,
|
|
40
|
+
VariantProps<typeof radioButtonVariants> {
|
|
41
|
+
invalid?: boolean
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const RadioButton = React.forwardRef<HTMLInputElement, RadioButtonProps>(
|
|
45
|
+
(
|
|
46
|
+
{
|
|
47
|
+
className,
|
|
48
|
+
size,
|
|
49
|
+
invalid = false,
|
|
50
|
+
disabled = false,
|
|
51
|
+
checked,
|
|
52
|
+
...props
|
|
53
|
+
},
|
|
54
|
+
ref
|
|
55
|
+
) => {
|
|
56
|
+
return (
|
|
57
|
+
<div className="relative inline-flex items-center">
|
|
58
|
+
<input
|
|
59
|
+
ref={ref}
|
|
60
|
+
type="radio"
|
|
61
|
+
className="sr-only"
|
|
62
|
+
disabled={disabled}
|
|
63
|
+
checked={checked}
|
|
64
|
+
data-testid="radiobutton-input"
|
|
65
|
+
{...props}
|
|
66
|
+
/>
|
|
67
|
+
<div
|
|
68
|
+
className={cn(
|
|
69
|
+
radioButtonVariants({ size }),
|
|
70
|
+
"border-input bg-background",
|
|
71
|
+
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
|
72
|
+
checked && !invalid && "border-primary bg-primary text-primary-foreground",
|
|
73
|
+
invalid && "border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
|
|
74
|
+
invalid && checked && "border-destructive bg-destructive text-destructive-foreground",
|
|
75
|
+
disabled && "pointer-events-none opacity-50",
|
|
76
|
+
className
|
|
77
|
+
)}
|
|
78
|
+
data-testid="radiobutton"
|
|
79
|
+
>
|
|
80
|
+
{checked ? (
|
|
81
|
+
<CircleDot
|
|
82
|
+
className={cn(
|
|
83
|
+
radioIconVariants({ size }),
|
|
84
|
+
invalid ? "text-destructive-foreground" : "text-primary-foreground"
|
|
85
|
+
)}
|
|
86
|
+
/>
|
|
87
|
+
) : (
|
|
88
|
+
<Circle
|
|
89
|
+
className={cn(
|
|
90
|
+
radioIconVariants({ size }),
|
|
91
|
+
invalid ? "text-destructive" : "text-muted-foreground"
|
|
92
|
+
)}
|
|
93
|
+
/>
|
|
94
|
+
)}
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
RadioButton.displayName = "RadioButton"
|
|
102
|
+
|
|
103
|
+
export { RadioButton }
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/* eslint-disable storybook/no-renderer-packages */
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
3
|
+
import { RadioButton } from './index'
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof RadioButton> = {
|
|
6
|
+
title: 'Atoms/RadioButton',
|
|
7
|
+
component: RadioButton,
|
|
8
|
+
parameters: {
|
|
9
|
+
layout: 'centered',
|
|
10
|
+
},
|
|
11
|
+
tags: ['autodocs'],
|
|
12
|
+
argTypes: {
|
|
13
|
+
size: {
|
|
14
|
+
control: 'select',
|
|
15
|
+
options: ['sm', 'md', 'lg'],
|
|
16
|
+
description: 'The size of the radio button',
|
|
17
|
+
},
|
|
18
|
+
checked: {
|
|
19
|
+
control: 'boolean',
|
|
20
|
+
description: 'Whether the radio button is checked',
|
|
21
|
+
},
|
|
22
|
+
disabled: {
|
|
23
|
+
control: 'boolean',
|
|
24
|
+
description: 'Whether the radio button is disabled',
|
|
25
|
+
},
|
|
26
|
+
invalid: {
|
|
27
|
+
control: 'boolean',
|
|
28
|
+
description: 'Whether the radio button is in an invalid state',
|
|
29
|
+
},
|
|
30
|
+
name: {
|
|
31
|
+
control: 'text',
|
|
32
|
+
description: 'The name attribute for the radio button group',
|
|
33
|
+
},
|
|
34
|
+
value: {
|
|
35
|
+
control: 'text',
|
|
36
|
+
description: 'The value attribute for the radio button',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default meta
|
|
42
|
+
type Story = StoryObj<typeof meta>
|
|
43
|
+
|
|
44
|
+
export const Basic: Story = {
|
|
45
|
+
args: {
|
|
46
|
+
size: 'md',
|
|
47
|
+
checked: false,
|
|
48
|
+
disabled: false,
|
|
49
|
+
invalid: false,
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const Checked: Story = {
|
|
54
|
+
args: {
|
|
55
|
+
size: 'md',
|
|
56
|
+
checked: true,
|
|
57
|
+
disabled: false,
|
|
58
|
+
invalid: false,
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const Unchecked: Story = {
|
|
63
|
+
args: {
|
|
64
|
+
size: 'md',
|
|
65
|
+
checked: false,
|
|
66
|
+
disabled: false,
|
|
67
|
+
invalid: false,
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const Disabled: Story = {
|
|
72
|
+
args: {
|
|
73
|
+
size: 'md',
|
|
74
|
+
checked: false,
|
|
75
|
+
disabled: true,
|
|
76
|
+
invalid: false,
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const DisabledChecked: Story = {
|
|
81
|
+
args: {
|
|
82
|
+
size: 'md',
|
|
83
|
+
checked: true,
|
|
84
|
+
disabled: true,
|
|
85
|
+
invalid: false,
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const Invalid: Story = {
|
|
90
|
+
args: {
|
|
91
|
+
size: 'md',
|
|
92
|
+
checked: false,
|
|
93
|
+
disabled: false,
|
|
94
|
+
invalid: true,
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const InvalidChecked: Story = {
|
|
99
|
+
args: {
|
|
100
|
+
size: 'md',
|
|
101
|
+
checked: true,
|
|
102
|
+
disabled: false,
|
|
103
|
+
invalid: true,
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export const AllSizes: Story = {
|
|
108
|
+
render: () => (
|
|
109
|
+
<div className="flex items-center gap-4">
|
|
110
|
+
<div className="flex flex-col items-center gap-2">
|
|
111
|
+
<RadioButton size="sm" checked={false} />
|
|
112
|
+
<span className="text-xs text-muted-foreground">Small</span>
|
|
113
|
+
</div>
|
|
114
|
+
<div className="flex flex-col items-center gap-2">
|
|
115
|
+
<RadioButton size="md" checked={false} />
|
|
116
|
+
<span className="text-xs text-muted-foreground">Medium</span>
|
|
117
|
+
</div>
|
|
118
|
+
<div className="flex flex-col items-center gap-2">
|
|
119
|
+
<RadioButton size="lg" checked={false} />
|
|
120
|
+
<span className="text-xs text-muted-foreground">Large</span>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
),
|
|
124
|
+
parameters: {
|
|
125
|
+
docs: {
|
|
126
|
+
description: {
|
|
127
|
+
story: 'RadioButton component in different sizes: small (14px), medium (16px), and large (20px).',
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export const RadioGroup: Story = {
|
|
134
|
+
render: () => (
|
|
135
|
+
<div className="space-y-3">
|
|
136
|
+
<div className="flex items-center gap-2">
|
|
137
|
+
<RadioButton name="option" value="option1" checked={true} />
|
|
138
|
+
<label className="text-sm">Option 1 (Selected)</label>
|
|
139
|
+
</div>
|
|
140
|
+
<div className="flex items-center gap-2">
|
|
141
|
+
<RadioButton name="option" value="option2" checked={false} />
|
|
142
|
+
<label className="text-sm">Option 2</label>
|
|
143
|
+
</div>
|
|
144
|
+
<div className="flex items-center gap-2">
|
|
145
|
+
<RadioButton name="option" value="option3" checked={false} />
|
|
146
|
+
<label className="text-sm">Option 3</label>
|
|
147
|
+
</div>
|
|
148
|
+
<div className="flex items-center gap-2">
|
|
149
|
+
<RadioButton name="option" value="option4" checked={false} disabled />
|
|
150
|
+
<label className="text-sm text-muted-foreground">Option 4 (Disabled)</label>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
),
|
|
154
|
+
parameters: {
|
|
155
|
+
docs: {
|
|
156
|
+
description: {
|
|
157
|
+
story: 'Example of radio buttons used in a group with labels.',
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export const AllStates: Story = {
|
|
164
|
+
render: () => (
|
|
165
|
+
<div className="space-y-4">
|
|
166
|
+
<div>
|
|
167
|
+
<h3 className="text-sm font-medium mb-2">Normal States</h3>
|
|
168
|
+
<div className="flex items-center gap-4">
|
|
169
|
+
<div className="flex items-center gap-2">
|
|
170
|
+
<RadioButton checked={false} />
|
|
171
|
+
<span className="text-sm">Unchecked</span>
|
|
172
|
+
</div>
|
|
173
|
+
<div className="flex items-center gap-2">
|
|
174
|
+
<RadioButton checked={true} />
|
|
175
|
+
<span className="text-sm">Checked</span>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
<div>
|
|
181
|
+
<h3 className="text-sm font-medium mb-2">Disabled States</h3>
|
|
182
|
+
<div className="flex items-center gap-4">
|
|
183
|
+
<div className="flex items-center gap-2">
|
|
184
|
+
<RadioButton checked={false} disabled />
|
|
185
|
+
<span className="text-sm text-muted-foreground">Disabled Unchecked</span>
|
|
186
|
+
</div>
|
|
187
|
+
<div className="flex items-center gap-2">
|
|
188
|
+
<RadioButton checked={true} disabled />
|
|
189
|
+
<span className="text-sm text-muted-foreground">Disabled Checked</span>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
<div>
|
|
195
|
+
<h3 className="text-sm font-medium mb-2">Invalid States</h3>
|
|
196
|
+
<div className="flex items-center gap-4">
|
|
197
|
+
<div className="flex items-center gap-2">
|
|
198
|
+
<RadioButton checked={false} invalid />
|
|
199
|
+
<span className="text-sm">Invalid Unchecked</span>
|
|
200
|
+
</div>
|
|
201
|
+
<div className="flex items-center gap-2">
|
|
202
|
+
<RadioButton checked={true} invalid />
|
|
203
|
+
<span className="text-sm">Invalid Checked</span>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
),
|
|
209
|
+
parameters: {
|
|
210
|
+
docs: {
|
|
211
|
+
description: {
|
|
212
|
+
story: 'Comprehensive overview of all radio button states including normal, disabled, and invalid variations.',
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export const Docs: Story = {
|
|
219
|
+
render: () => (
|
|
220
|
+
<div className="space-y-6">
|
|
221
|
+
<div>
|
|
222
|
+
<h2 className="text-lg font-semibold mb-3">RadioButton Component</h2>
|
|
223
|
+
<p className="text-sm text-muted-foreground mb-4">
|
|
224
|
+
Radio buttons allow users to select one option from a set of mutually exclusive options.
|
|
225
|
+
The RadioButton component provides a custom-styled radio input that integrates seamlessly
|
|
226
|
+
with Shadcn design tokens and supports various sizes, states, and accessibility features.
|
|
227
|
+
</p>
|
|
228
|
+
</div>
|
|
229
|
+
|
|
230
|
+
<div>
|
|
231
|
+
<h3 className="text-base font-medium mb-2">Sizes</h3>
|
|
232
|
+
<div className="space-y-2">
|
|
233
|
+
<div className="flex items-center gap-2">
|
|
234
|
+
<RadioButton size="sm" checked={false} />
|
|
235
|
+
<span className="text-sm text-muted-foreground">Small (14px) - Compact layouts</span>
|
|
236
|
+
</div>
|
|
237
|
+
<div className="flex items-center gap-2">
|
|
238
|
+
<RadioButton size="md" checked={false} />
|
|
239
|
+
<span className="text-sm text-muted-foreground">Medium (16px) - Default size</span>
|
|
240
|
+
</div>
|
|
241
|
+
<div className="flex items-center gap-2">
|
|
242
|
+
<RadioButton size="lg" checked={false} />
|
|
243
|
+
<span className="text-sm text-muted-foreground">Large (20px) - Prominent selections</span>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
</div>
|
|
247
|
+
|
|
248
|
+
<div>
|
|
249
|
+
<h3 className="text-base font-medium mb-2">States</h3>
|
|
250
|
+
<div className="grid grid-cols-2 gap-2">
|
|
251
|
+
<div className="flex items-center gap-2">
|
|
252
|
+
<RadioButton checked={false} />
|
|
253
|
+
<span className="text-xs text-muted-foreground">Unchecked</span>
|
|
254
|
+
</div>
|
|
255
|
+
<div className="flex items-center gap-2">
|
|
256
|
+
<RadioButton checked={true} />
|
|
257
|
+
<span className="text-xs text-muted-foreground">Checked</span>
|
|
258
|
+
</div>
|
|
259
|
+
<div className="flex items-center gap-2">
|
|
260
|
+
<RadioButton checked={false} disabled />
|
|
261
|
+
<span className="text-xs text-muted-foreground">Disabled</span>
|
|
262
|
+
</div>
|
|
263
|
+
<div className="flex items-center gap-2">
|
|
264
|
+
<RadioButton checked={false} invalid />
|
|
265
|
+
<span className="text-xs text-muted-foreground">Invalid</span>
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
|
|
270
|
+
<div>
|
|
271
|
+
<h3 className="text-base font-medium mb-2">Usage</h3>
|
|
272
|
+
<div className="space-y-2">
|
|
273
|
+
<p className="text-sm text-muted-foreground">
|
|
274
|
+
• Use radio buttons when users need to select exactly one option from a list
|
|
275
|
+
</p>
|
|
276
|
+
<p className="text-sm text-muted-foreground">
|
|
277
|
+
• Group related radio buttons using the same `name` attribute
|
|
278
|
+
</p>
|
|
279
|
+
<p className="text-sm text-muted-foreground">
|
|
280
|
+
• Always provide clear labels for accessibility
|
|
281
|
+
</p>
|
|
282
|
+
<p className="text-sm text-muted-foreground">
|
|
283
|
+
• Use the `invalid` prop to indicate validation errors
|
|
284
|
+
</p>
|
|
285
|
+
</div>
|
|
286
|
+
</div>
|
|
287
|
+
|
|
288
|
+
<div>
|
|
289
|
+
<h3 className="text-base font-medium mb-2">Accessibility</h3>
|
|
290
|
+
<div className="space-y-2">
|
|
291
|
+
<p className="text-sm text-muted-foreground">
|
|
292
|
+
• Supports keyboard navigation (Tab, Space, Arrow keys)
|
|
293
|
+
</p>
|
|
294
|
+
<p className="text-sm text-muted-foreground">
|
|
295
|
+
• Proper ARIA attributes for screen readers
|
|
296
|
+
</p>
|
|
297
|
+
<p className="text-sm text-muted-foreground">
|
|
298
|
+
• Focus indicators for keyboard users
|
|
299
|
+
</p>
|
|
300
|
+
<p className="text-sm text-muted-foreground">
|
|
301
|
+
• Semantic HTML input element for form integration
|
|
302
|
+
</p>
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
</div>
|
|
306
|
+
),
|
|
307
|
+
parameters: {
|
|
308
|
+
docs: {
|
|
309
|
+
description: {
|
|
310
|
+
story: 'Complete documentation and usage guidelines for the RadioButton component.',
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
}
|