sonance-brand-mcp 1.1.4 → 1.2.2
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/dist/assets/BRAND_GUIDELINES.md +0 -8
- package/dist/assets/components/accordion.stories.tsx +310 -0
- package/dist/assets/components/accordion.tsx +56 -30
- package/dist/assets/components/alert.stories.tsx +199 -0
- package/dist/assets/components/autocomplete.stories.tsx +307 -0
- package/dist/assets/components/autocomplete.tsx +28 -4
- package/dist/assets/components/avatar.stories.tsx +175 -0
- package/dist/assets/components/badge.stories.tsx +258 -0
- package/dist/assets/components/breadcrumbs.stories.tsx +175 -0
- package/dist/assets/components/button.stories.tsx +362 -0
- package/dist/assets/components/button.tsx +48 -3
- package/dist/assets/components/calendar.stories.tsx +247 -0
- package/dist/assets/components/card.stories.tsx +275 -0
- package/dist/assets/components/card.tsx +26 -1
- package/dist/assets/components/checkbox-group.stories.tsx +281 -0
- package/dist/assets/components/checkbox.stories.tsx +160 -0
- package/dist/assets/components/checkbox.tsx +32 -4
- package/dist/assets/components/code.stories.tsx +265 -0
- package/dist/assets/components/date-input.stories.tsx +278 -0
- package/dist/assets/components/date-input.tsx +24 -2
- package/dist/assets/components/date-picker.stories.tsx +337 -0
- package/dist/assets/components/date-picker.tsx +28 -4
- package/dist/assets/components/date-range-picker.stories.tsx +340 -0
- package/dist/assets/components/dialog.stories.tsx +285 -0
- package/dist/assets/components/divider.stories.tsx +176 -0
- package/dist/assets/components/drawer.stories.tsx +216 -0
- package/dist/assets/components/dropdown.stories.tsx +342 -0
- package/dist/assets/components/dropdown.tsx +55 -10
- package/dist/assets/components/form.stories.tsx +372 -0
- package/dist/assets/components/image.stories.tsx +348 -0
- package/dist/assets/components/input-otp.stories.tsx +336 -0
- package/dist/assets/components/input-otp.tsx +24 -2
- package/dist/assets/components/input.stories.tsx +223 -0
- package/dist/assets/components/input.tsx +27 -2
- package/dist/assets/components/kbd.stories.tsx +272 -0
- package/dist/assets/components/link.stories.tsx +199 -0
- package/dist/assets/components/link.tsx +50 -1
- package/dist/assets/components/listbox.stories.tsx +287 -0
- package/dist/assets/components/listbox.tsx +30 -7
- package/dist/assets/components/navbar.stories.tsx +218 -0
- package/dist/assets/components/number-input.stories.tsx +295 -0
- package/dist/assets/components/number-input.tsx +30 -8
- package/dist/assets/components/pagination.stories.tsx +280 -0
- package/dist/assets/components/pagination.tsx +45 -21
- package/dist/assets/components/popover.stories.tsx +219 -0
- package/dist/assets/components/progress.stories.tsx +153 -0
- package/dist/assets/components/radio-group.stories.tsx +187 -0
- package/dist/assets/components/radio-group.tsx +30 -6
- package/dist/assets/components/range-calendar.stories.tsx +334 -0
- package/dist/assets/components/scroll-shadow.stories.tsx +335 -0
- package/dist/assets/components/select.stories.tsx +192 -0
- package/dist/assets/components/select.tsx +54 -7
- package/dist/assets/components/skeleton.stories.tsx +166 -0
- package/dist/assets/components/slider.stories.tsx +145 -0
- package/dist/assets/components/slider.tsx +43 -8
- package/dist/assets/components/spacer.stories.tsx +216 -0
- package/dist/assets/components/spinner.stories.tsx +149 -0
- package/dist/assets/components/switch.stories.tsx +170 -0
- package/dist/assets/components/switch.tsx +29 -4
- package/dist/assets/components/table.stories.tsx +322 -0
- package/dist/assets/components/tabs.stories.tsx +306 -0
- package/dist/assets/components/tabs.tsx +25 -4
- package/dist/assets/components/textarea.stories.tsx +103 -0
- package/dist/assets/components/textarea.tsx +27 -3
- package/dist/assets/components/theme-toggle.stories.tsx +248 -0
- package/dist/assets/components/time-input.stories.tsx +365 -0
- package/dist/assets/components/time-input.tsx +25 -3
- package/dist/assets/components/toast.stories.tsx +195 -0
- package/dist/assets/components/tooltip.stories.tsx +226 -0
- package/dist/assets/components/user.stories.tsx +274 -0
- package/dist/assets/logo-manifest.json +0 -18
- package/dist/index.js +2142 -85
- package/package.json +1 -1
|
@@ -6,6 +6,23 @@ import { Calendar as CalendarIcon } from "lucide-react";
|
|
|
6
6
|
import { cn } from "@/lib/utils";
|
|
7
7
|
import { Calendar } from "./calendar";
|
|
8
8
|
|
|
9
|
+
export type DatePickerState = "default" | "hover" | "focus" | "open" | "error" | "disabled";
|
|
10
|
+
|
|
11
|
+
// State styles for Storybook/Figma visualization
|
|
12
|
+
const getStateStyles = (state?: DatePickerState) => {
|
|
13
|
+
if (!state || state === "default") return "";
|
|
14
|
+
|
|
15
|
+
const stateMap: Record<string, string> = {
|
|
16
|
+
hover: "border-border-hover",
|
|
17
|
+
focus: "border-input-focus",
|
|
18
|
+
open: "border-input-focus",
|
|
19
|
+
error: "border-error",
|
|
20
|
+
disabled: "opacity-50 cursor-not-allowed",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
return stateMap[state] || "";
|
|
24
|
+
};
|
|
25
|
+
|
|
9
26
|
interface DatePickerProps {
|
|
10
27
|
value?: Date;
|
|
11
28
|
defaultValue?: Date;
|
|
@@ -18,6 +35,8 @@ interface DatePickerProps {
|
|
|
18
35
|
minDate?: Date;
|
|
19
36
|
maxDate?: Date;
|
|
20
37
|
className?: string;
|
|
38
|
+
/** Visual state for Storybook/Figma documentation */
|
|
39
|
+
state?: DatePickerState;
|
|
21
40
|
}
|
|
22
41
|
|
|
23
42
|
export function DatePicker({
|
|
@@ -32,7 +51,11 @@ export function DatePicker({
|
|
|
32
51
|
minDate,
|
|
33
52
|
maxDate,
|
|
34
53
|
className,
|
|
54
|
+
state,
|
|
35
55
|
}: DatePickerProps) {
|
|
56
|
+
const isDisabled = disabled || state === "disabled";
|
|
57
|
+
const hasError = error || state === "error";
|
|
58
|
+
const isOpenState = state === "open";
|
|
36
59
|
const [internalValue, setInternalValue] = useState<Date | undefined>(defaultValue);
|
|
37
60
|
const [isOpen, setIsOpen] = useState(false);
|
|
38
61
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
@@ -78,15 +101,16 @@ export function DatePicker({
|
|
|
78
101
|
)}
|
|
79
102
|
<button
|
|
80
103
|
type="button"
|
|
81
|
-
onClick={() => !
|
|
82
|
-
disabled={
|
|
104
|
+
onClick={() => !isDisabled && setIsOpen(!isOpen)}
|
|
105
|
+
disabled={isDisabled}
|
|
83
106
|
className={cn(
|
|
84
107
|
"w-full flex items-center justify-between border border-input-border bg-input px-4 py-3",
|
|
85
108
|
"text-left transition-colors duration-200",
|
|
86
109
|
"focus:border-input-focus focus:outline-none",
|
|
87
110
|
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
88
|
-
|
|
89
|
-
isOpen && "border-input-focus"
|
|
111
|
+
hasError && "border-error",
|
|
112
|
+
(isOpen || isOpenState) && "border-input-focus",
|
|
113
|
+
getStateStyles(state)
|
|
90
114
|
)}
|
|
91
115
|
>
|
|
92
116
|
<span className={value ? "text-foreground" : "text-input-placeholder"}>
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/nextjs-vite';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { addDays, addMonths } from 'date-fns';
|
|
4
|
+
import { DateRangePicker } from './date-range-picker';
|
|
5
|
+
|
|
6
|
+
interface DateRange {
|
|
7
|
+
start: Date | undefined;
|
|
8
|
+
end: Date | undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const meta: Meta<typeof DateRangePicker> = {
|
|
12
|
+
title: 'Components/Forms/DateRangePicker',
|
|
13
|
+
component: DateRangePicker,
|
|
14
|
+
tags: ['autodocs'],
|
|
15
|
+
parameters: {
|
|
16
|
+
docs: {
|
|
17
|
+
description: {
|
|
18
|
+
component: 'A date range picker for selecting start and end dates. Opens a dual-month calendar for easy range selection. Supports min/max constraints.',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
argTypes: {
|
|
23
|
+
label: {
|
|
24
|
+
control: 'text',
|
|
25
|
+
description: 'Input label',
|
|
26
|
+
},
|
|
27
|
+
placeholder: {
|
|
28
|
+
control: 'text',
|
|
29
|
+
description: 'Placeholder text',
|
|
30
|
+
table: {
|
|
31
|
+
defaultValue: { summary: 'Select date range' },
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
dateFormat: {
|
|
35
|
+
control: 'text',
|
|
36
|
+
description: 'Display format for dates',
|
|
37
|
+
table: {
|
|
38
|
+
defaultValue: { summary: 'MMM d, yyyy' },
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
error: {
|
|
42
|
+
control: 'text',
|
|
43
|
+
description: 'Error message',
|
|
44
|
+
},
|
|
45
|
+
disabled: {
|
|
46
|
+
control: 'boolean',
|
|
47
|
+
description: 'Disabled state',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export default meta;
|
|
53
|
+
type Story = StoryObj<typeof meta>;
|
|
54
|
+
|
|
55
|
+
// Default
|
|
56
|
+
export const Default: Story = {
|
|
57
|
+
render: () => {
|
|
58
|
+
const [range, setRange] = useState<DateRange>({ start: undefined, end: undefined });
|
|
59
|
+
return (
|
|
60
|
+
<div className="w-80">
|
|
61
|
+
<DateRangePicker
|
|
62
|
+
value={range}
|
|
63
|
+
onValueChange={setRange}
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// With Label
|
|
71
|
+
export const WithLabel: Story = {
|
|
72
|
+
render: () => {
|
|
73
|
+
const [range, setRange] = useState<DateRange>({ start: undefined, end: undefined });
|
|
74
|
+
return (
|
|
75
|
+
<div className="w-80">
|
|
76
|
+
<DateRangePicker
|
|
77
|
+
label="Travel Dates"
|
|
78
|
+
value={range}
|
|
79
|
+
onValueChange={setRange}
|
|
80
|
+
/>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// With Selected Range
|
|
87
|
+
export const WithSelectedRange: Story = {
|
|
88
|
+
render: () => {
|
|
89
|
+
const today = new Date();
|
|
90
|
+
const [range, setRange] = useState<DateRange>({
|
|
91
|
+
start: today,
|
|
92
|
+
end: addDays(today, 7),
|
|
93
|
+
});
|
|
94
|
+
return (
|
|
95
|
+
<div className="w-80">
|
|
96
|
+
<DateRangePicker
|
|
97
|
+
label="Booking Period"
|
|
98
|
+
value={range}
|
|
99
|
+
onValueChange={setRange}
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// With Error
|
|
107
|
+
export const WithError: Story = {
|
|
108
|
+
render: () => {
|
|
109
|
+
const [range, setRange] = useState<DateRange>({ start: undefined, end: undefined });
|
|
110
|
+
return (
|
|
111
|
+
<div className="w-80">
|
|
112
|
+
<DateRangePicker
|
|
113
|
+
label="Required Range"
|
|
114
|
+
error="Please select a date range"
|
|
115
|
+
value={range}
|
|
116
|
+
onValueChange={setRange}
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
119
|
+
);
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// With Min/Max Dates
|
|
124
|
+
export const WithMinMaxDates: Story = {
|
|
125
|
+
render: () => {
|
|
126
|
+
const [range, setRange] = useState<DateRange>({ start: undefined, end: undefined });
|
|
127
|
+
const today = new Date();
|
|
128
|
+
return (
|
|
129
|
+
<div className="w-80 space-y-2">
|
|
130
|
+
<DateRangePicker
|
|
131
|
+
label="Event Period"
|
|
132
|
+
placeholder="Select dates within next 3 months"
|
|
133
|
+
value={range}
|
|
134
|
+
onValueChange={setRange}
|
|
135
|
+
minDate={today}
|
|
136
|
+
maxDate={addMonths(today, 3)}
|
|
137
|
+
/>
|
|
138
|
+
<p className="text-xs text-foreground-muted">
|
|
139
|
+
Available dates: Today through {addMonths(today, 3).toLocaleDateString()}
|
|
140
|
+
</p>
|
|
141
|
+
</div>
|
|
142
|
+
);
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// Disabled
|
|
147
|
+
export const Disabled: Story = {
|
|
148
|
+
render: () => {
|
|
149
|
+
const today = new Date();
|
|
150
|
+
return (
|
|
151
|
+
<div className="w-80">
|
|
152
|
+
<DateRangePicker
|
|
153
|
+
label="Locked Period"
|
|
154
|
+
defaultValue={{ start: today, end: addDays(today, 5) }}
|
|
155
|
+
disabled
|
|
156
|
+
/>
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// Custom Format
|
|
163
|
+
export const CustomFormat: Story = {
|
|
164
|
+
render: () => {
|
|
165
|
+
const today = new Date();
|
|
166
|
+
const [range, setRange] = useState<DateRange>({
|
|
167
|
+
start: today,
|
|
168
|
+
end: addDays(today, 7),
|
|
169
|
+
});
|
|
170
|
+
return (
|
|
171
|
+
<div className="w-80 space-y-4">
|
|
172
|
+
<DateRangePicker
|
|
173
|
+
label="Default Format"
|
|
174
|
+
dateFormat="MMM d, yyyy"
|
|
175
|
+
value={range}
|
|
176
|
+
onValueChange={setRange}
|
|
177
|
+
/>
|
|
178
|
+
<DateRangePicker
|
|
179
|
+
label="Short Format"
|
|
180
|
+
dateFormat="MM/dd/yy"
|
|
181
|
+
value={range}
|
|
182
|
+
onValueChange={setRange}
|
|
183
|
+
/>
|
|
184
|
+
<DateRangePicker
|
|
185
|
+
label="Verbose Format"
|
|
186
|
+
dateFormat="EEEE, MMMM d"
|
|
187
|
+
value={range}
|
|
188
|
+
onValueChange={setRange}
|
|
189
|
+
/>
|
|
190
|
+
</div>
|
|
191
|
+
);
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// Hotel Booking Example
|
|
196
|
+
export const HotelBookingExample: Story = {
|
|
197
|
+
render: () => {
|
|
198
|
+
const [range, setRange] = useState<DateRange>({ start: undefined, end: undefined });
|
|
199
|
+
const today = new Date();
|
|
200
|
+
|
|
201
|
+
const nights = range.start && range.end
|
|
202
|
+
? Math.ceil((range.end.getTime() - range.start.getTime()) / (1000 * 60 * 60 * 24))
|
|
203
|
+
: 0;
|
|
204
|
+
|
|
205
|
+
return (
|
|
206
|
+
<div className="w-80 space-y-4 p-6 border border-border rounded-sm">
|
|
207
|
+
<h3 className="font-medium">Reserve Your Room</h3>
|
|
208
|
+
<DateRangePicker
|
|
209
|
+
label="Stay Duration"
|
|
210
|
+
placeholder="Select check-in and check-out"
|
|
211
|
+
value={range}
|
|
212
|
+
onValueChange={setRange}
|
|
213
|
+
minDate={today}
|
|
214
|
+
maxDate={addMonths(today, 6)}
|
|
215
|
+
/>
|
|
216
|
+
{nights > 0 && (
|
|
217
|
+
<div className="space-y-2 pt-2 border-t border-border">
|
|
218
|
+
<div className="flex justify-between text-sm">
|
|
219
|
+
<span className="text-foreground-muted">Duration</span>
|
|
220
|
+
<span>{nights} night{nights > 1 ? 's' : ''}</span>
|
|
221
|
+
</div>
|
|
222
|
+
<div className="flex justify-between text-sm">
|
|
223
|
+
<span className="text-foreground-muted">Rate per night</span>
|
|
224
|
+
<span>$299</span>
|
|
225
|
+
</div>
|
|
226
|
+
<div className="flex justify-between font-medium pt-2 border-t border-border">
|
|
227
|
+
<span>Total</span>
|
|
228
|
+
<span>${(nights * 299).toLocaleString()}</span>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
)}
|
|
232
|
+
</div>
|
|
233
|
+
);
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// Report Filter Example
|
|
238
|
+
export const ReportFilterExample: Story = {
|
|
239
|
+
render: () => {
|
|
240
|
+
const [range, setRange] = useState<DateRange>({ start: undefined, end: undefined });
|
|
241
|
+
const today = new Date();
|
|
242
|
+
|
|
243
|
+
const presets = [
|
|
244
|
+
{ label: 'Last 7 days', start: addDays(today, -7), end: today },
|
|
245
|
+
{ label: 'Last 30 days', start: addDays(today, -30), end: today },
|
|
246
|
+
{ label: 'Last 90 days', start: addDays(today, -90), end: today },
|
|
247
|
+
{ label: 'This month', start: new Date(today.getFullYear(), today.getMonth(), 1), end: today },
|
|
248
|
+
];
|
|
249
|
+
|
|
250
|
+
return (
|
|
251
|
+
<div className="w-80 space-y-4">
|
|
252
|
+
<DateRangePicker
|
|
253
|
+
label="Report Period"
|
|
254
|
+
value={range}
|
|
255
|
+
onValueChange={setRange}
|
|
256
|
+
maxDate={today}
|
|
257
|
+
/>
|
|
258
|
+
<div className="flex flex-wrap gap-2">
|
|
259
|
+
{presets.map((preset) => (
|
|
260
|
+
<button
|
|
261
|
+
key={preset.label}
|
|
262
|
+
onClick={() => setRange({ start: preset.start, end: preset.end })}
|
|
263
|
+
className="text-xs px-2 py-1 border border-border rounded-sm hover:bg-secondary-hover transition-colors"
|
|
264
|
+
>
|
|
265
|
+
{preset.label}
|
|
266
|
+
</button>
|
|
267
|
+
))}
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
);
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
// All States
|
|
275
|
+
export const AllStates: Story = {
|
|
276
|
+
render: () => {
|
|
277
|
+
const today = new Date();
|
|
278
|
+
return (
|
|
279
|
+
<div className="space-y-6 w-80">
|
|
280
|
+
<DateRangePicker label="Default" placeholder="Select date range" />
|
|
281
|
+
<DateRangePicker
|
|
282
|
+
label="With Value"
|
|
283
|
+
defaultValue={{ start: today, end: addDays(today, 7) }}
|
|
284
|
+
/>
|
|
285
|
+
<DateRangePicker label="With Error" error="This field is required" />
|
|
286
|
+
<DateRangePicker
|
|
287
|
+
label="Disabled"
|
|
288
|
+
disabled
|
|
289
|
+
defaultValue={{ start: today, end: addDays(today, 3) }}
|
|
290
|
+
/>
|
|
291
|
+
</div>
|
|
292
|
+
);
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
// Responsive Matrix - Mobile, Tablet, Desktop
|
|
297
|
+
export const ResponsiveMatrix: Story = {
|
|
298
|
+
render: () => {
|
|
299
|
+
const today = new Date();
|
|
300
|
+
return (
|
|
301
|
+
<div className="space-y-8">
|
|
302
|
+
{/* Mobile */}
|
|
303
|
+
<div>
|
|
304
|
+
<h4 className="text-xs uppercase text-foreground-muted mb-2">Mobile (375px)</h4>
|
|
305
|
+
<div className="w-[375px] border border-dashed border-border p-4">
|
|
306
|
+
<DateRangePicker label="Travel Dates" placeholder="Select dates" />
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
{/* Tablet */}
|
|
310
|
+
<div>
|
|
311
|
+
<h4 className="text-xs uppercase text-foreground-muted mb-2">Tablet (768px)</h4>
|
|
312
|
+
<div className="w-[768px] border border-dashed border-border p-4">
|
|
313
|
+
<div className="grid grid-cols-2 gap-4">
|
|
314
|
+
<DateRangePicker label="Booking Period" placeholder="Select range" />
|
|
315
|
+
<DateRangePicker
|
|
316
|
+
label="With Value"
|
|
317
|
+
defaultValue={{ start: today, end: addDays(today, 7) }}
|
|
318
|
+
/>
|
|
319
|
+
</div>
|
|
320
|
+
</div>
|
|
321
|
+
</div>
|
|
322
|
+
{/* Desktop */}
|
|
323
|
+
<div>
|
|
324
|
+
<h4 className="text-xs uppercase text-foreground-muted mb-2">Desktop (1280px)</h4>
|
|
325
|
+
<div className="w-[1280px] border border-dashed border-border p-4">
|
|
326
|
+
<div className="grid grid-cols-3 gap-4">
|
|
327
|
+
<DateRangePicker label="Default" placeholder="Select range" />
|
|
328
|
+
<DateRangePicker label="With Error" error="Required field" />
|
|
329
|
+
<DateRangePicker
|
|
330
|
+
label="Disabled"
|
|
331
|
+
disabled
|
|
332
|
+
defaultValue={{ start: today, end: addDays(today, 5) }}
|
|
333
|
+
/>
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
337
|
+
</div>
|
|
338
|
+
);
|
|
339
|
+
},
|
|
340
|
+
};
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/nextjs-vite';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
Dialog,
|
|
5
|
+
DialogContent,
|
|
6
|
+
DialogHeader,
|
|
7
|
+
DialogTitle,
|
|
8
|
+
DialogDescription,
|
|
9
|
+
DialogBody,
|
|
10
|
+
DialogFooter,
|
|
11
|
+
} from './dialog';
|
|
12
|
+
import { Button } from './button';
|
|
13
|
+
import { Input } from './input';
|
|
14
|
+
|
|
15
|
+
const meta: Meta<typeof Dialog> = {
|
|
16
|
+
title: 'Components/Overlays/Dialog',
|
|
17
|
+
component: Dialog,
|
|
18
|
+
tags: ['autodocs'],
|
|
19
|
+
parameters: {
|
|
20
|
+
docs: {
|
|
21
|
+
description: {
|
|
22
|
+
component: 'A modal dialog component for focused interactions. Includes backdrop, keyboard support (Escape to close), and scroll lock.',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
layout: 'centered',
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default meta;
|
|
30
|
+
type Story = StoryObj<typeof meta>;
|
|
31
|
+
|
|
32
|
+
// Default Dialog
|
|
33
|
+
export const Default: Story = {
|
|
34
|
+
render: () => {
|
|
35
|
+
const [open, setOpen] = useState(false);
|
|
36
|
+
return (
|
|
37
|
+
<>
|
|
38
|
+
<Button onClick={() => setOpen(true)}>Open Dialog</Button>
|
|
39
|
+
<Dialog open={open} onClose={() => setOpen(false)}>
|
|
40
|
+
<DialogContent>
|
|
41
|
+
<DialogHeader>
|
|
42
|
+
<DialogTitle>Dialog Title</DialogTitle>
|
|
43
|
+
<DialogDescription>
|
|
44
|
+
This is a description of the dialog content.
|
|
45
|
+
</DialogDescription>
|
|
46
|
+
</DialogHeader>
|
|
47
|
+
<DialogBody>
|
|
48
|
+
<p className="text-foreground-secondary">
|
|
49
|
+
Dialog body content goes here. You can add any content you need.
|
|
50
|
+
</p>
|
|
51
|
+
</DialogBody>
|
|
52
|
+
<DialogFooter>
|
|
53
|
+
<Button variant="secondary" onClick={() => setOpen(false)}>
|
|
54
|
+
Cancel
|
|
55
|
+
</Button>
|
|
56
|
+
<Button onClick={() => setOpen(false)}>Confirm</Button>
|
|
57
|
+
</DialogFooter>
|
|
58
|
+
</DialogContent>
|
|
59
|
+
</Dialog>
|
|
60
|
+
</>
|
|
61
|
+
);
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Confirmation Dialog
|
|
66
|
+
export const Confirmation: Story = {
|
|
67
|
+
render: () => {
|
|
68
|
+
const [open, setOpen] = useState(false);
|
|
69
|
+
return (
|
|
70
|
+
<>
|
|
71
|
+
<Button variant="secondary" onClick={() => setOpen(true)}>
|
|
72
|
+
Delete Item
|
|
73
|
+
</Button>
|
|
74
|
+
<Dialog open={open} onClose={() => setOpen(false)}>
|
|
75
|
+
<DialogContent className="max-w-md">
|
|
76
|
+
<DialogHeader>
|
|
77
|
+
<DialogTitle>Delete Item?</DialogTitle>
|
|
78
|
+
<DialogDescription>
|
|
79
|
+
This action cannot be undone. This will permanently delete the item
|
|
80
|
+
from your account.
|
|
81
|
+
</DialogDescription>
|
|
82
|
+
</DialogHeader>
|
|
83
|
+
<DialogFooter>
|
|
84
|
+
<Button variant="secondary" onClick={() => setOpen(false)}>
|
|
85
|
+
Cancel
|
|
86
|
+
</Button>
|
|
87
|
+
<Button onClick={() => setOpen(false)}>Delete</Button>
|
|
88
|
+
</DialogFooter>
|
|
89
|
+
</DialogContent>
|
|
90
|
+
</Dialog>
|
|
91
|
+
</>
|
|
92
|
+
);
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Form Dialog
|
|
97
|
+
export const FormDialog: Story = {
|
|
98
|
+
render: () => {
|
|
99
|
+
const [open, setOpen] = useState(false);
|
|
100
|
+
return (
|
|
101
|
+
<>
|
|
102
|
+
<Button onClick={() => setOpen(true)}>Add New Item</Button>
|
|
103
|
+
<Dialog open={open} onClose={() => setOpen(false)}>
|
|
104
|
+
<DialogContent>
|
|
105
|
+
<DialogHeader>
|
|
106
|
+
<DialogTitle>Add New Item</DialogTitle>
|
|
107
|
+
<DialogDescription>
|
|
108
|
+
Fill out the form below to add a new item to your inventory.
|
|
109
|
+
</DialogDescription>
|
|
110
|
+
</DialogHeader>
|
|
111
|
+
<DialogBody>
|
|
112
|
+
<form className="space-y-4">
|
|
113
|
+
<Input label="Item Name" placeholder="Enter item name" />
|
|
114
|
+
<Input label="SKU" placeholder="Enter SKU" />
|
|
115
|
+
<Input label="Price" type="number" placeholder="0.00" />
|
|
116
|
+
</form>
|
|
117
|
+
</DialogBody>
|
|
118
|
+
<DialogFooter>
|
|
119
|
+
<Button variant="secondary" onClick={() => setOpen(false)}>
|
|
120
|
+
Cancel
|
|
121
|
+
</Button>
|
|
122
|
+
<Button onClick={() => setOpen(false)}>Add Item</Button>
|
|
123
|
+
</DialogFooter>
|
|
124
|
+
</DialogContent>
|
|
125
|
+
</Dialog>
|
|
126
|
+
</>
|
|
127
|
+
);
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// Without Close Button
|
|
132
|
+
export const WithoutCloseButton: Story = {
|
|
133
|
+
render: () => {
|
|
134
|
+
const [open, setOpen] = useState(false);
|
|
135
|
+
return (
|
|
136
|
+
<>
|
|
137
|
+
<Button onClick={() => setOpen(true)}>Open Dialog</Button>
|
|
138
|
+
<Dialog open={open} onClose={() => setOpen(false)}>
|
|
139
|
+
<DialogContent showClose={false}>
|
|
140
|
+
<DialogHeader>
|
|
141
|
+
<DialogTitle>Required Action</DialogTitle>
|
|
142
|
+
<DialogDescription>
|
|
143
|
+
Please complete the required action before closing.
|
|
144
|
+
</DialogDescription>
|
|
145
|
+
</DialogHeader>
|
|
146
|
+
<DialogBody>
|
|
147
|
+
<p className="text-foreground-secondary">
|
|
148
|
+
This dialog has no close button, requiring user to use the action buttons.
|
|
149
|
+
</p>
|
|
150
|
+
</DialogBody>
|
|
151
|
+
<DialogFooter>
|
|
152
|
+
<Button onClick={() => setOpen(false)}>Complete</Button>
|
|
153
|
+
</DialogFooter>
|
|
154
|
+
</DialogContent>
|
|
155
|
+
</Dialog>
|
|
156
|
+
</>
|
|
157
|
+
);
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// Long Content Dialog
|
|
162
|
+
export const LongContent: Story = {
|
|
163
|
+
render: () => {
|
|
164
|
+
const [open, setOpen] = useState(false);
|
|
165
|
+
return (
|
|
166
|
+
<>
|
|
167
|
+
<Button onClick={() => setOpen(true)}>Terms & Conditions</Button>
|
|
168
|
+
<Dialog open={open} onClose={() => setOpen(false)}>
|
|
169
|
+
<DialogContent className="max-w-2xl">
|
|
170
|
+
<DialogHeader>
|
|
171
|
+
<DialogTitle>Terms and Conditions</DialogTitle>
|
|
172
|
+
</DialogHeader>
|
|
173
|
+
<DialogBody className="max-h-[60vh] overflow-y-auto">
|
|
174
|
+
<div className="space-y-4 text-foreground-secondary text-sm">
|
|
175
|
+
{Array.from({ length: 10 }, (_, i) => (
|
|
176
|
+
<p key={i}>
|
|
177
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
|
|
178
|
+
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
|
|
179
|
+
enim ad minim veniam, quis nostrud exercitation ullamco laboris
|
|
180
|
+
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
|
|
181
|
+
in reprehenderit in voluptate velit esse cillum dolore eu fugiat
|
|
182
|
+
nulla pariatur.
|
|
183
|
+
</p>
|
|
184
|
+
))}
|
|
185
|
+
</div>
|
|
186
|
+
</DialogBody>
|
|
187
|
+
<DialogFooter>
|
|
188
|
+
<Button variant="secondary" onClick={() => setOpen(false)}>
|
|
189
|
+
Decline
|
|
190
|
+
</Button>
|
|
191
|
+
<Button onClick={() => setOpen(false)}>Accept</Button>
|
|
192
|
+
</DialogFooter>
|
|
193
|
+
</DialogContent>
|
|
194
|
+
</Dialog>
|
|
195
|
+
</>
|
|
196
|
+
);
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// Simple Alert Dialog
|
|
201
|
+
export const AlertDialog: Story = {
|
|
202
|
+
render: () => {
|
|
203
|
+
const [open, setOpen] = useState(false);
|
|
204
|
+
return (
|
|
205
|
+
<>
|
|
206
|
+
<Button onClick={() => setOpen(true)}>Show Alert</Button>
|
|
207
|
+
<Dialog open={open} onClose={() => setOpen(false)}>
|
|
208
|
+
<DialogContent className="max-w-sm">
|
|
209
|
+
<DialogHeader>
|
|
210
|
+
<DialogTitle>Alert</DialogTitle>
|
|
211
|
+
</DialogHeader>
|
|
212
|
+
<DialogBody>
|
|
213
|
+
<p className="text-foreground-secondary">
|
|
214
|
+
Your session will expire in 5 minutes.
|
|
215
|
+
</p>
|
|
216
|
+
</DialogBody>
|
|
217
|
+
<DialogFooter>
|
|
218
|
+
<Button onClick={() => setOpen(false)}>Okay</Button>
|
|
219
|
+
</DialogFooter>
|
|
220
|
+
</DialogContent>
|
|
221
|
+
</Dialog>
|
|
222
|
+
</>
|
|
223
|
+
);
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// Responsive Matrix - Mobile, Tablet, Desktop (static previews)
|
|
228
|
+
export const ResponsiveMatrix: Story = {
|
|
229
|
+
render: () => (
|
|
230
|
+
<div className="space-y-8">
|
|
231
|
+
{/* Mobile */}
|
|
232
|
+
<div>
|
|
233
|
+
<h4 className="text-xs uppercase text-foreground-muted mb-2">Mobile (375px)</h4>
|
|
234
|
+
<div className="w-[375px] border border-dashed border-border p-4 bg-black/50">
|
|
235
|
+
<div className="bg-background rounded-lg p-4 shadow-lg">
|
|
236
|
+
<div className="space-y-2">
|
|
237
|
+
<h3 className="text-lg font-semibold">Confirm Action</h3>
|
|
238
|
+
<p className="text-sm text-foreground-secondary">Are you sure you want to proceed?</p>
|
|
239
|
+
</div>
|
|
240
|
+
<div className="flex flex-col gap-2 mt-4">
|
|
241
|
+
<Button className="w-full">Confirm</Button>
|
|
242
|
+
<Button variant="secondary" className="w-full">Cancel</Button>
|
|
243
|
+
</div>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
</div>
|
|
247
|
+
{/* Tablet */}
|
|
248
|
+
<div>
|
|
249
|
+
<h4 className="text-xs uppercase text-foreground-muted mb-2">Tablet (768px)</h4>
|
|
250
|
+
<div className="w-[768px] border border-dashed border-border p-4 bg-black/50 flex items-center justify-center">
|
|
251
|
+
<div className="bg-background rounded-lg p-6 shadow-lg w-[400px]">
|
|
252
|
+
<div className="space-y-2">
|
|
253
|
+
<h3 className="text-lg font-semibold">Add New Item</h3>
|
|
254
|
+
<p className="text-sm text-foreground-secondary">Fill out the form below.</p>
|
|
255
|
+
</div>
|
|
256
|
+
<div className="space-y-4 mt-4">
|
|
257
|
+
<Input label="Item Name" placeholder="Enter name" />
|
|
258
|
+
<Input label="Price" type="number" placeholder="0.00" />
|
|
259
|
+
</div>
|
|
260
|
+
<div className="flex justify-end gap-2 mt-6">
|
|
261
|
+
<Button variant="secondary">Cancel</Button>
|
|
262
|
+
<Button>Add Item</Button>
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
{/* Desktop */}
|
|
268
|
+
<div>
|
|
269
|
+
<h4 className="text-xs uppercase text-foreground-muted mb-2">Desktop (1280px)</h4>
|
|
270
|
+
<div className="w-[1280px] border border-dashed border-border p-4 bg-black/50 flex items-center justify-center">
|
|
271
|
+
<div className="bg-background rounded-lg p-6 shadow-lg w-[500px]">
|
|
272
|
+
<div className="space-y-2">
|
|
273
|
+
<h3 className="text-lg font-semibold">Delete Item?</h3>
|
|
274
|
+
<p className="text-sm text-foreground-secondary">This action cannot be undone. This will permanently delete the item from your account.</p>
|
|
275
|
+
</div>
|
|
276
|
+
<div className="flex justify-end gap-2 mt-6">
|
|
277
|
+
<Button variant="secondary">Cancel</Button>
|
|
278
|
+
<Button>Delete</Button>
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
284
|
+
),
|
|
285
|
+
};
|