sonance-brand-mcp 1.1.4 → 1.1.7

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.
Files changed (71) hide show
  1. package/dist/assets/components/accordion.stories.tsx +310 -0
  2. package/dist/assets/components/accordion.tsx +56 -30
  3. package/dist/assets/components/alert.stories.tsx +199 -0
  4. package/dist/assets/components/autocomplete.stories.tsx +307 -0
  5. package/dist/assets/components/autocomplete.tsx +28 -4
  6. package/dist/assets/components/avatar.stories.tsx +175 -0
  7. package/dist/assets/components/badge.stories.tsx +258 -0
  8. package/dist/assets/components/breadcrumbs.stories.tsx +175 -0
  9. package/dist/assets/components/button.stories.tsx +362 -0
  10. package/dist/assets/components/button.tsx +48 -3
  11. package/dist/assets/components/calendar.stories.tsx +247 -0
  12. package/dist/assets/components/card.stories.tsx +275 -0
  13. package/dist/assets/components/card.tsx +26 -1
  14. package/dist/assets/components/checkbox-group.stories.tsx +281 -0
  15. package/dist/assets/components/checkbox.stories.tsx +160 -0
  16. package/dist/assets/components/checkbox.tsx +32 -4
  17. package/dist/assets/components/code.stories.tsx +265 -0
  18. package/dist/assets/components/date-input.stories.tsx +278 -0
  19. package/dist/assets/components/date-input.tsx +24 -2
  20. package/dist/assets/components/date-picker.stories.tsx +337 -0
  21. package/dist/assets/components/date-picker.tsx +28 -4
  22. package/dist/assets/components/date-range-picker.stories.tsx +340 -0
  23. package/dist/assets/components/dialog.stories.tsx +285 -0
  24. package/dist/assets/components/divider.stories.tsx +176 -0
  25. package/dist/assets/components/drawer.stories.tsx +216 -0
  26. package/dist/assets/components/dropdown.stories.tsx +342 -0
  27. package/dist/assets/components/dropdown.tsx +55 -10
  28. package/dist/assets/components/form.stories.tsx +372 -0
  29. package/dist/assets/components/image.stories.tsx +348 -0
  30. package/dist/assets/components/input-otp.stories.tsx +336 -0
  31. package/dist/assets/components/input-otp.tsx +24 -2
  32. package/dist/assets/components/input.stories.tsx +223 -0
  33. package/dist/assets/components/input.tsx +27 -2
  34. package/dist/assets/components/kbd.stories.tsx +272 -0
  35. package/dist/assets/components/link.stories.tsx +199 -0
  36. package/dist/assets/components/link.tsx +50 -1
  37. package/dist/assets/components/listbox.stories.tsx +287 -0
  38. package/dist/assets/components/listbox.tsx +30 -7
  39. package/dist/assets/components/navbar.stories.tsx +218 -0
  40. package/dist/assets/components/number-input.stories.tsx +295 -0
  41. package/dist/assets/components/number-input.tsx +30 -8
  42. package/dist/assets/components/pagination.stories.tsx +280 -0
  43. package/dist/assets/components/pagination.tsx +45 -21
  44. package/dist/assets/components/popover.stories.tsx +219 -0
  45. package/dist/assets/components/progress.stories.tsx +153 -0
  46. package/dist/assets/components/radio-group.stories.tsx +187 -0
  47. package/dist/assets/components/radio-group.tsx +30 -6
  48. package/dist/assets/components/range-calendar.stories.tsx +334 -0
  49. package/dist/assets/components/scroll-shadow.stories.tsx +335 -0
  50. package/dist/assets/components/select.stories.tsx +192 -0
  51. package/dist/assets/components/select.tsx +54 -7
  52. package/dist/assets/components/skeleton.stories.tsx +166 -0
  53. package/dist/assets/components/slider.stories.tsx +145 -0
  54. package/dist/assets/components/slider.tsx +43 -8
  55. package/dist/assets/components/spacer.stories.tsx +216 -0
  56. package/dist/assets/components/spinner.stories.tsx +149 -0
  57. package/dist/assets/components/switch.stories.tsx +170 -0
  58. package/dist/assets/components/switch.tsx +29 -4
  59. package/dist/assets/components/table.stories.tsx +322 -0
  60. package/dist/assets/components/tabs.stories.tsx +306 -0
  61. package/dist/assets/components/tabs.tsx +25 -4
  62. package/dist/assets/components/textarea.stories.tsx +103 -0
  63. package/dist/assets/components/textarea.tsx +27 -3
  64. package/dist/assets/components/theme-toggle.stories.tsx +248 -0
  65. package/dist/assets/components/time-input.stories.tsx +365 -0
  66. package/dist/assets/components/time-input.tsx +25 -3
  67. package/dist/assets/components/toast.stories.tsx +195 -0
  68. package/dist/assets/components/tooltip.stories.tsx +226 -0
  69. package/dist/assets/components/user.stories.tsx +274 -0
  70. package/dist/index.js +1649 -13
  71. package/package.json +1 -1
@@ -0,0 +1,362 @@
1
+ import type { Meta, StoryObj } from '@storybook/nextjs-vite';
2
+ import { Button, type ButtonState } from './button';
3
+
4
+ const meta: Meta<typeof Button> = {
5
+ title: 'Components/Forms/Button',
6
+ component: Button,
7
+ tags: ['autodocs'],
8
+ parameters: {
9
+ docs: {
10
+ description: {
11
+ component: 'A versatile button component with multiple variants and sizes. Follows Sonance brand guidelines with uppercase text and clean styling.',
12
+ },
13
+ },
14
+ },
15
+ argTypes: {
16
+ variant: {
17
+ control: 'select',
18
+ options: ['primary', 'secondary', 'ghost', 'inverted'],
19
+ description: 'Visual style variant',
20
+ table: {
21
+ defaultValue: { summary: 'primary' },
22
+ },
23
+ },
24
+ size: {
25
+ control: 'select',
26
+ options: ['sm', 'md', 'lg'],
27
+ description: 'Button size',
28
+ table: {
29
+ defaultValue: { summary: 'md' },
30
+ },
31
+ },
32
+ state: {
33
+ control: 'select',
34
+ options: ['default', 'hover', 'active', 'focus', 'disabled'],
35
+ description: 'Visual state for documentation',
36
+ table: {
37
+ defaultValue: { summary: 'default' },
38
+ },
39
+ },
40
+ disabled: {
41
+ control: 'boolean',
42
+ description: 'Disable the button',
43
+ },
44
+ children: {
45
+ control: 'text',
46
+ description: 'Button label',
47
+ },
48
+ },
49
+ };
50
+
51
+ export default meta;
52
+ type Story = StoryObj<typeof meta>;
53
+
54
+ // Default story
55
+ export const Default: Story = {
56
+ args: {
57
+ children: 'Button',
58
+ variant: 'primary',
59
+ size: 'md',
60
+ },
61
+ };
62
+
63
+ // Variants
64
+ export const Primary: Story = {
65
+ args: {
66
+ children: 'Primary Button',
67
+ variant: 'primary',
68
+ },
69
+ };
70
+
71
+ export const Secondary: Story = {
72
+ args: {
73
+ children: 'Secondary Button',
74
+ variant: 'secondary',
75
+ },
76
+ };
77
+
78
+ export const Ghost: Story = {
79
+ args: {
80
+ children: 'Ghost Button',
81
+ variant: 'ghost',
82
+ },
83
+ };
84
+
85
+ export const Inverted: Story = {
86
+ args: {
87
+ children: 'Inverted Button',
88
+ variant: 'inverted',
89
+ },
90
+ parameters: {
91
+ backgrounds: {
92
+ default: 'dark',
93
+ },
94
+ },
95
+ decorators: [
96
+ (Story) => (
97
+ <div className="bg-sonance-charcoal p-8 rounded">
98
+ <Story />
99
+ </div>
100
+ ),
101
+ ],
102
+ };
103
+
104
+ // Sizes
105
+ export const Small: Story = {
106
+ args: {
107
+ children: 'Small Button',
108
+ size: 'sm',
109
+ },
110
+ };
111
+
112
+ export const Medium: Story = {
113
+ args: {
114
+ children: 'Medium Button',
115
+ size: 'md',
116
+ },
117
+ };
118
+
119
+ export const Large: Story = {
120
+ args: {
121
+ children: 'Large Button',
122
+ size: 'lg',
123
+ },
124
+ };
125
+
126
+ // States
127
+ export const Disabled: Story = {
128
+ args: {
129
+ children: 'Disabled',
130
+ disabled: true,
131
+ },
132
+ };
133
+
134
+ // All Variants
135
+ export const AllVariants: Story = {
136
+ render: () => (
137
+ <div className="flex flex-wrap gap-4">
138
+ <Button variant="primary">Primary</Button>
139
+ <Button variant="secondary">Secondary</Button>
140
+ <Button variant="ghost">Ghost</Button>
141
+ <div className="bg-sonance-charcoal p-2 rounded">
142
+ <Button variant="inverted">Inverted</Button>
143
+ </div>
144
+ </div>
145
+ ),
146
+ };
147
+
148
+ // All Sizes
149
+ export const AllSizes: Story = {
150
+ render: () => (
151
+ <div className="flex items-center gap-4">
152
+ <Button size="sm">Small</Button>
153
+ <Button size="md">Medium</Button>
154
+ <Button size="lg">Large</Button>
155
+ </div>
156
+ ),
157
+ };
158
+
159
+ // Variant Matrix
160
+ export const VariantMatrix: Story = {
161
+ render: () => (
162
+ <div className="space-y-6">
163
+ <div>
164
+ <h3 className="text-sm font-medium text-foreground-muted mb-3">Primary</h3>
165
+ <div className="flex items-center gap-4">
166
+ <Button variant="primary" size="sm">Small</Button>
167
+ <Button variant="primary" size="md">Medium</Button>
168
+ <Button variant="primary" size="lg">Large</Button>
169
+ <Button variant="primary" size="md" disabled>Disabled</Button>
170
+ </div>
171
+ </div>
172
+ <div>
173
+ <h3 className="text-sm font-medium text-foreground-muted mb-3">Secondary</h3>
174
+ <div className="flex items-center gap-4">
175
+ <Button variant="secondary" size="sm">Small</Button>
176
+ <Button variant="secondary" size="md">Medium</Button>
177
+ <Button variant="secondary" size="lg">Large</Button>
178
+ <Button variant="secondary" size="md" disabled>Disabled</Button>
179
+ </div>
180
+ </div>
181
+ <div>
182
+ <h3 className="text-sm font-medium text-foreground-muted mb-3">Ghost</h3>
183
+ <div className="flex items-center gap-4">
184
+ <Button variant="ghost" size="sm">Small</Button>
185
+ <Button variant="ghost" size="md">Medium</Button>
186
+ <Button variant="ghost" size="lg">Large</Button>
187
+ <Button variant="ghost" size="md" disabled>Disabled</Button>
188
+ </div>
189
+ </div>
190
+ </div>
191
+ ),
192
+ };
193
+
194
+ // Interactive States - for Figma documentation
195
+ export const PrimaryStates: Story = {
196
+ render: () => (
197
+ <div className="space-y-4">
198
+ <h3 className="text-sm font-medium text-foreground-muted">Primary Button States</h3>
199
+ <div className="flex items-center gap-4">
200
+ <div className="text-center">
201
+ <Button variant="primary" state="default">Default</Button>
202
+ <p className="text-xs text-foreground-muted mt-2">Default</p>
203
+ </div>
204
+ <div className="text-center">
205
+ <Button variant="primary" state="hover">Hover</Button>
206
+ <p className="text-xs text-foreground-muted mt-2">Hover</p>
207
+ </div>
208
+ <div className="text-center">
209
+ <Button variant="primary" state="active">Active</Button>
210
+ <p className="text-xs text-foreground-muted mt-2">Active</p>
211
+ </div>
212
+ <div className="text-center">
213
+ <Button variant="primary" state="focus">Focus</Button>
214
+ <p className="text-xs text-foreground-muted mt-2">Focus</p>
215
+ </div>
216
+ <div className="text-center">
217
+ <Button variant="primary" state="disabled">Disabled</Button>
218
+ <p className="text-xs text-foreground-muted mt-2">Disabled</p>
219
+ </div>
220
+ </div>
221
+ </div>
222
+ ),
223
+ };
224
+
225
+ export const SecondaryStates: Story = {
226
+ render: () => (
227
+ <div className="space-y-4">
228
+ <h3 className="text-sm font-medium text-foreground-muted">Secondary Button States</h3>
229
+ <div className="flex items-center gap-4">
230
+ <div className="text-center">
231
+ <Button variant="secondary" state="default">Default</Button>
232
+ <p className="text-xs text-foreground-muted mt-2">Default</p>
233
+ </div>
234
+ <div className="text-center">
235
+ <Button variant="secondary" state="hover">Hover</Button>
236
+ <p className="text-xs text-foreground-muted mt-2">Hover</p>
237
+ </div>
238
+ <div className="text-center">
239
+ <Button variant="secondary" state="active">Active</Button>
240
+ <p className="text-xs text-foreground-muted mt-2">Active</p>
241
+ </div>
242
+ <div className="text-center">
243
+ <Button variant="secondary" state="focus">Focus</Button>
244
+ <p className="text-xs text-foreground-muted mt-2">Focus</p>
245
+ </div>
246
+ <div className="text-center">
247
+ <Button variant="secondary" state="disabled">Disabled</Button>
248
+ <p className="text-xs text-foreground-muted mt-2">Disabled</p>
249
+ </div>
250
+ </div>
251
+ </div>
252
+ ),
253
+ };
254
+
255
+ export const GhostStates: Story = {
256
+ render: () => (
257
+ <div className="space-y-4">
258
+ <h3 className="text-sm font-medium text-foreground-muted">Ghost Button States</h3>
259
+ <div className="flex items-center gap-4">
260
+ <div className="text-center">
261
+ <Button variant="ghost" state="default">Default</Button>
262
+ <p className="text-xs text-foreground-muted mt-2">Default</p>
263
+ </div>
264
+ <div className="text-center">
265
+ <Button variant="ghost" state="hover">Hover</Button>
266
+ <p className="text-xs text-foreground-muted mt-2">Hover</p>
267
+ </div>
268
+ <div className="text-center">
269
+ <Button variant="ghost" state="active">Active</Button>
270
+ <p className="text-xs text-foreground-muted mt-2">Active</p>
271
+ </div>
272
+ <div className="text-center">
273
+ <Button variant="ghost" state="focus">Focus</Button>
274
+ <p className="text-xs text-foreground-muted mt-2">Focus</p>
275
+ </div>
276
+ <div className="text-center">
277
+ <Button variant="ghost" state="disabled">Disabled</Button>
278
+ <p className="text-xs text-foreground-muted mt-2">Disabled</p>
279
+ </div>
280
+ </div>
281
+ </div>
282
+ ),
283
+ };
284
+
285
+ // Complete State Matrix - All variants × All states
286
+ export const StateMatrix: Story = {
287
+ render: () => {
288
+ const variants = ['primary', 'secondary', 'ghost'] as const;
289
+ const states: ButtonState[] = ['default', 'hover', 'active', 'focus', 'disabled'];
290
+
291
+ return (
292
+ <div className="space-y-8">
293
+ <div className="grid grid-cols-6 gap-4 text-center">
294
+ <div></div>
295
+ {states.map((state) => (
296
+ <div key={state} className="text-xs font-medium text-foreground-muted uppercase">
297
+ {state}
298
+ </div>
299
+ ))}
300
+ </div>
301
+ {variants.map((variant) => (
302
+ <div key={variant} className="grid grid-cols-6 gap-4 items-center">
303
+ <div className="text-xs font-medium text-foreground-muted uppercase">{variant}</div>
304
+ {states.map((state) => (
305
+ <Button key={`${variant}-${state}`} variant={variant} state={state}>
306
+ Button
307
+ </Button>
308
+ ))}
309
+ </div>
310
+ ))}
311
+ </div>
312
+ );
313
+ },
314
+ };
315
+
316
+ // Responsive Matrix - Mobile, Tablet, Desktop
317
+ export const ResponsiveMatrix: Story = {
318
+ render: () => (
319
+ <div className="space-y-8">
320
+ {/* Mobile */}
321
+ <div>
322
+ <h4 className="text-xs uppercase text-foreground-muted mb-2">Mobile (375px)</h4>
323
+ <div className="w-[375px] border border-dashed border-border p-4">
324
+ <div className="flex flex-col gap-2">
325
+ <Button variant="primary" className="w-full">Primary Button</Button>
326
+ <Button variant="secondary" className="w-full">Secondary Button</Button>
327
+ <div className="flex gap-2">
328
+ <Button variant="primary" size="sm">Small</Button>
329
+ <Button variant="secondary" size="sm">Small</Button>
330
+ </div>
331
+ </div>
332
+ </div>
333
+ </div>
334
+ {/* Tablet */}
335
+ <div>
336
+ <h4 className="text-xs uppercase text-foreground-muted mb-2">Tablet (768px)</h4>
337
+ <div className="w-[768px] border border-dashed border-border p-4">
338
+ <div className="flex gap-4">
339
+ <Button variant="primary">Primary Button</Button>
340
+ <Button variant="secondary">Secondary Button</Button>
341
+ <Button variant="ghost">Ghost Button</Button>
342
+ </div>
343
+ </div>
344
+ </div>
345
+ {/* Desktop */}
346
+ <div>
347
+ <h4 className="text-xs uppercase text-foreground-muted mb-2">Desktop (1280px)</h4>
348
+ <div className="w-[1280px] border border-dashed border-border p-4">
349
+ <div className="flex gap-4 items-center">
350
+ <Button variant="primary" size="lg">Large Primary</Button>
351
+ <Button variant="secondary" size="lg">Large Secondary</Button>
352
+ <Button variant="ghost" size="lg">Large Ghost</Button>
353
+ <Button variant="primary" size="md">Medium</Button>
354
+ <Button variant="secondary" size="md">Medium</Button>
355
+ <Button variant="primary" size="sm">Small</Button>
356
+ <Button variant="secondary" size="sm">Small</Button>
357
+ </div>
358
+ </div>
359
+ </div>
360
+ </div>
361
+ ),
362
+ };
@@ -2,6 +2,8 @@ import { cva, type VariantProps } from "class-variance-authority";
2
2
  import { cn } from "@/lib/utils";
3
3
  import { forwardRef } from "react";
4
4
 
5
+ export type ButtonState = "default" | "hover" | "active" | "focus" | "disabled";
6
+
5
7
  const buttonVariants = cva(
6
8
  "inline-flex items-center justify-center font-medium uppercase tracking-wide transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50",
7
9
  {
@@ -28,16 +30,59 @@ const buttonVariants = cva(
28
30
  }
29
31
  );
30
32
 
33
+ // State styles for Storybook/Figma visualization
34
+ const getStateStyles = (variant: string | null | undefined, state?: ButtonState) => {
35
+ if (!state || state === "default") return "";
36
+
37
+ const stateMap: Record<string, Record<string, string>> = {
38
+ primary: {
39
+ hover: "bg-primary-hover",
40
+ active: "bg-primary-active",
41
+ focus: "ring-2 ring-border-focus ring-offset-2 ring-offset-background",
42
+ disabled: "opacity-50 pointer-events-none",
43
+ },
44
+ secondary: {
45
+ hover: "bg-secondary-hover border-border-hover",
46
+ active: "bg-secondary-hover",
47
+ focus: "ring-2 ring-border-focus ring-offset-2 ring-offset-background",
48
+ disabled: "opacity-50 pointer-events-none",
49
+ },
50
+ ghost: {
51
+ hover: "bg-secondary-hover",
52
+ active: "bg-secondary-hover",
53
+ focus: "ring-2 ring-border-focus ring-offset-2 ring-offset-background",
54
+ disabled: "opacity-50 pointer-events-none",
55
+ },
56
+ inverted: {
57
+ hover: "opacity-90",
58
+ active: "opacity-80",
59
+ focus: "ring-2 ring-border-focus ring-offset-2 ring-offset-background",
60
+ disabled: "opacity-50 pointer-events-none",
61
+ },
62
+ };
63
+
64
+ return stateMap[variant || "primary"]?.[state] || "";
65
+ };
66
+
31
67
  interface ButtonProps
32
68
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
33
- VariantProps<typeof buttonVariants> {}
69
+ VariantProps<typeof buttonVariants> {
70
+ /** Visual state for Storybook/Figma documentation */
71
+ state?: ButtonState;
72
+ }
34
73
 
35
74
  export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
36
- ({ className, variant, size, ...props }, ref) => {
75
+ ({ className, variant, size, state, disabled, ...props }, ref) => {
76
+ const isDisabled = disabled || state === "disabled";
77
+
37
78
  return (
38
79
  <button
39
- className={cn(buttonVariants({ variant, size, className }))}
80
+ className={cn(
81
+ buttonVariants({ variant, size, className }),
82
+ getStateStyles(variant, state)
83
+ )}
40
84
  ref={ref}
85
+ disabled={isDisabled}
41
86
  {...props}
42
87
  />
43
88
  );
@@ -0,0 +1,247 @@
1
+ import type { Meta, StoryObj } from '@storybook/nextjs-vite';
2
+ import { useState } from 'react';
3
+ import { addDays, subDays } from 'date-fns';
4
+ import { Calendar, MiniCalendar } from './calendar';
5
+
6
+ const meta: Meta<typeof Calendar> = {
7
+ title: 'Components/Forms/Calendar',
8
+ component: Calendar,
9
+ tags: ['autodocs'],
10
+ parameters: {
11
+ docs: {
12
+ description: {
13
+ component: 'A date calendar component for selecting single dates. Supports min/max dates, disabled dates, and customizable styling.',
14
+ },
15
+ },
16
+ },
17
+ argTypes: {
18
+ selected: {
19
+ control: 'date',
20
+ description: 'The currently selected date',
21
+ },
22
+ minDate: {
23
+ control: 'date',
24
+ description: 'Minimum selectable date',
25
+ },
26
+ maxDate: {
27
+ control: 'date',
28
+ description: 'Maximum selectable date',
29
+ },
30
+ },
31
+ };
32
+
33
+ export default meta;
34
+ type Story = StoryObj<typeof meta>;
35
+
36
+ // Default
37
+ export const Default: Story = {
38
+ render: () => {
39
+ const [selected, setSelected] = useState<Date | undefined>(undefined);
40
+ return (
41
+ <Calendar
42
+ selected={selected}
43
+ onSelect={setSelected}
44
+ className="w-fit"
45
+ />
46
+ );
47
+ },
48
+ };
49
+
50
+ // With Selected Date
51
+ export const WithSelectedDate: Story = {
52
+ render: () => {
53
+ const [selected, setSelected] = useState<Date>(new Date());
54
+ return (
55
+ <Calendar
56
+ selected={selected}
57
+ onSelect={setSelected}
58
+ className="w-fit"
59
+ />
60
+ );
61
+ },
62
+ };
63
+
64
+ // With Min/Max Dates
65
+ export const WithMinMaxDates: Story = {
66
+ render: () => {
67
+ const [selected, setSelected] = useState<Date | undefined>(undefined);
68
+ const today = new Date();
69
+ return (
70
+ <div className="space-y-4">
71
+ <p className="text-sm text-foreground-muted">
72
+ Only dates within the next 30 days are selectable
73
+ </p>
74
+ <Calendar
75
+ selected={selected}
76
+ onSelect={setSelected}
77
+ minDate={today}
78
+ maxDate={addDays(today, 30)}
79
+ className="w-fit"
80
+ />
81
+ </div>
82
+ );
83
+ },
84
+ };
85
+
86
+ // With Disabled Dates (weekends)
87
+ export const DisabledWeekends: Story = {
88
+ render: () => {
89
+ const [selected, setSelected] = useState<Date | undefined>(undefined);
90
+ const isWeekend = (date: Date) => {
91
+ const day = date.getDay();
92
+ return day === 0 || day === 6;
93
+ };
94
+ return (
95
+ <div className="space-y-4">
96
+ <p className="text-sm text-foreground-muted">
97
+ Weekends are disabled
98
+ </p>
99
+ <Calendar
100
+ selected={selected}
101
+ onSelect={setSelected}
102
+ disabled={isWeekend}
103
+ className="w-fit"
104
+ />
105
+ </div>
106
+ );
107
+ },
108
+ };
109
+
110
+ // Past Dates Only
111
+ export const PastDatesOnly: Story = {
112
+ render: () => {
113
+ const [selected, setSelected] = useState<Date | undefined>(undefined);
114
+ const today = new Date();
115
+ return (
116
+ <div className="space-y-4">
117
+ <p className="text-sm text-foreground-muted">
118
+ Only past dates are selectable (useful for birthdays)
119
+ </p>
120
+ <Calendar
121
+ selected={selected}
122
+ onSelect={setSelected}
123
+ maxDate={subDays(today, 1)}
124
+ className="w-fit"
125
+ />
126
+ </div>
127
+ );
128
+ },
129
+ };
130
+
131
+ // Mini Calendar
132
+ export const Mini: Story = {
133
+ render: () => {
134
+ const [selected, setSelected] = useState<Date | undefined>(new Date());
135
+ return (
136
+ <div className="space-y-4">
137
+ <p className="text-sm text-foreground-muted">
138
+ Compact calendar for smaller spaces
139
+ </p>
140
+ <MiniCalendar
141
+ selected={selected}
142
+ onSelect={setSelected}
143
+ className="w-fit"
144
+ />
145
+ </div>
146
+ );
147
+ },
148
+ };
149
+
150
+ // Booking Example
151
+ export const BookingExample: Story = {
152
+ render: () => {
153
+ const [selected, setSelected] = useState<Date | undefined>(undefined);
154
+ const today = new Date();
155
+
156
+ // Simulate some booked dates
157
+ const bookedDates = [
158
+ addDays(today, 3),
159
+ addDays(today, 4),
160
+ addDays(today, 10),
161
+ addDays(today, 11),
162
+ addDays(today, 12),
163
+ ];
164
+
165
+ const isBooked = (date: Date) => {
166
+ return bookedDates.some(
167
+ bookedDate =>
168
+ bookedDate.getDate() === date.getDate() &&
169
+ bookedDate.getMonth() === date.getMonth() &&
170
+ bookedDate.getFullYear() === date.getFullYear()
171
+ );
172
+ };
173
+
174
+ return (
175
+ <div className="space-y-4">
176
+ <p className="text-sm text-foreground-muted">
177
+ Select an available appointment date
178
+ </p>
179
+ <Calendar
180
+ selected={selected}
181
+ onSelect={setSelected}
182
+ minDate={today}
183
+ maxDate={addDays(today, 60)}
184
+ disabled={isBooked}
185
+ className="w-fit"
186
+ />
187
+ {selected && (
188
+ <p className="text-sm text-foreground">
189
+ Selected: {selected.toLocaleDateString()}
190
+ </p>
191
+ )}
192
+ </div>
193
+ );
194
+ },
195
+ };
196
+
197
+ // Responsive Matrix - Mobile, Tablet, Desktop
198
+ export const ResponsiveMatrix: Story = {
199
+ render: () => {
200
+ const [mobile, setMobile] = useState<Date | undefined>(new Date());
201
+ const [tablet, setTablet] = useState<Date | undefined>(new Date());
202
+ const [desktop, setDesktop] = useState<Date | undefined>(new Date());
203
+
204
+ return (
205
+ <div className="space-y-8">
206
+ {/* Mobile */}
207
+ <div>
208
+ <h4 className="text-xs uppercase text-foreground-muted mb-2">Mobile (375px)</h4>
209
+ <div className="w-[375px] border border-dashed border-border p-4">
210
+ <MiniCalendar
211
+ selected={mobile}
212
+ onSelect={setMobile}
213
+ className="w-fit"
214
+ />
215
+ </div>
216
+ </div>
217
+ {/* Tablet */}
218
+ <div>
219
+ <h4 className="text-xs uppercase text-foreground-muted mb-2">Tablet (768px)</h4>
220
+ <div className="w-[768px] border border-dashed border-border p-4">
221
+ <Calendar
222
+ selected={tablet}
223
+ onSelect={setTablet}
224
+ className="w-fit"
225
+ />
226
+ </div>
227
+ </div>
228
+ {/* Desktop */}
229
+ <div>
230
+ <h4 className="text-xs uppercase text-foreground-muted mb-2">Desktop (1280px)</h4>
231
+ <div className="w-[1280px] border border-dashed border-border p-4">
232
+ <div className="flex gap-8">
233
+ <Calendar
234
+ selected={desktop}
235
+ onSelect={setDesktop}
236
+ className="w-fit"
237
+ />
238
+ <div className="text-sm text-foreground-muted">
239
+ {desktop && <p>Selected: {desktop.toLocaleDateString()}</p>}
240
+ </div>
241
+ </div>
242
+ </div>
243
+ </div>
244
+ </div>
245
+ );
246
+ },
247
+ };