@dust-tt/sparkle 0.2.552 → 0.2.553-rc-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.
@@ -0,0 +1,592 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import React, { useState } from "react";
3
+
4
+ import { Button } from "@sparkle/components/Button";
5
+ import {
6
+ MultiPageDialog,
7
+ MultiPageDialogContent,
8
+ type MultiPageDialogPage,
9
+ MultiPageDialogTrigger,
10
+ } from "@sparkle/components/MultiPageDialog";
11
+ import { Cog6ToothIcon, DocumentTextIcon, UserIcon } from "@sparkle/icons/app";
12
+
13
+ const meta: Meta<typeof MultiPageDialogContent> = {
14
+ title: "Primitives/MultiPageDialog",
15
+ component: MultiPageDialogContent,
16
+ };
17
+
18
+ export default meta;
19
+ type Story = StoryObj<typeof meta>;
20
+
21
+ const samplePages: MultiPageDialogPage[] = [
22
+ {
23
+ id: "profile",
24
+ title: "User Profile",
25
+ description: "Manage your personal information",
26
+ icon: UserIcon,
27
+ content: (
28
+ <div className="s-space-y-4">
29
+ <div>
30
+ <h3 className="s-mb-2 s-text-lg s-font-semibold">
31
+ Personal Information
32
+ </h3>
33
+ <p className="s-text-sm s-text-muted-foreground">
34
+ Update your profile details and preferences.
35
+ </p>
36
+ </div>
37
+ <div className="s-space-y-3">
38
+ <div>
39
+ <label className="s-text-sm s-font-medium">Full Name</label>
40
+ <input
41
+ type="text"
42
+ className="s-mt-1 s-w-full s-rounded-md s-border s-px-3 s-py-2"
43
+ placeholder="John Doe"
44
+ />
45
+ </div>
46
+ <div>
47
+ <label className="s-text-sm s-font-medium">Email</label>
48
+ <input
49
+ type="email"
50
+ className="s-mt-1 s-w-full s-rounded-md s-border s-px-3 s-py-2"
51
+ placeholder="john@example.com"
52
+ />
53
+ </div>
54
+ </div>
55
+ </div>
56
+ ),
57
+ },
58
+ {
59
+ id: "documents",
60
+ title: "Documents",
61
+ description: "Manage your uploaded files",
62
+ icon: DocumentTextIcon,
63
+ content: (
64
+ <div className="s-space-y-4">
65
+ <div>
66
+ <h3 className="s-mb-2 s-text-lg s-font-semibold">File Management</h3>
67
+ <p className="s-text-sm s-text-muted-foreground">
68
+ Upload, organize, and manage your documents.
69
+ </p>
70
+ </div>
71
+ <div className="s-space-y-2">
72
+ <div className="s-flex s-items-center s-justify-between s-rounded-md s-border s-p-3">
73
+ <span className="s-text-sm">document1.pdf</span>
74
+ <Button label="Download" size="sm" variant="outline" />
75
+ </div>
76
+ <div className="s-flex s-items-center s-justify-between s-rounded-md s-border s-p-3">
77
+ <span className="s-text-sm">report.docx</span>
78
+ <Button label="Download" size="sm" variant="outline" />
79
+ </div>
80
+ </div>
81
+ </div>
82
+ ),
83
+ },
84
+ {
85
+ id: "settings",
86
+ title: "Settings",
87
+ description: "Configure your preferences",
88
+ icon: Cog6ToothIcon,
89
+ content: (
90
+ <div className="s-space-y-4">
91
+ <div>
92
+ <h3 className="s-mb-2 s-text-lg s-font-semibold">
93
+ Application Settings
94
+ </h3>
95
+ <p className="s-text-sm s-text-muted-foreground">
96
+ Customize your experience and notification preferences.
97
+ </p>
98
+ </div>
99
+ <div className="s-space-y-3">
100
+ <div className="s-flex s-items-center s-justify-between">
101
+ <span className="s-text-sm">Email notifications</span>
102
+ <input type="checkbox" className="s-rounded" defaultChecked />
103
+ </div>
104
+ <div className="s-flex s-items-center s-justify-between">
105
+ <span className="s-text-sm">Dark mode</span>
106
+ <input type="checkbox" className="s-rounded" />
107
+ </div>
108
+ <div className="s-flex s-items-center s-justify-between">
109
+ <span className="s-text-sm">Auto-save</span>
110
+ <input type="checkbox" className="s-rounded" defaultChecked />
111
+ </div>
112
+ </div>
113
+ </div>
114
+ ),
115
+ },
116
+ ];
117
+
118
+ const MultiPageDialogDemo = () => {
119
+ const [currentPageId, setCurrentPageId] = useState("profile");
120
+
121
+ const handleSave = () => {
122
+ alert("Changes saved!");
123
+ };
124
+
125
+ return (
126
+ <MultiPageDialog>
127
+ <MultiPageDialogTrigger asChild>
128
+ <Button label="Open Multi-Page Dialog" />
129
+ </MultiPageDialogTrigger>
130
+ <MultiPageDialogContent
131
+ pages={samplePages}
132
+ currentPageId={currentPageId}
133
+ onPageChange={setCurrentPageId}
134
+ size="xl"
135
+ onSave={handleSave}
136
+ />
137
+ </MultiPageDialog>
138
+ );
139
+ };
140
+
141
+ export const Default: Story = {
142
+ render: () => <MultiPageDialogDemo />,
143
+ };
144
+
145
+ export const InteractiveContent: Story = {
146
+ render: () => {
147
+ const [currentPageId, setCurrentPageId] = useState("step1");
148
+ const [formData, setFormData] = useState({
149
+ name: "",
150
+ email: "",
151
+ selectedFile: "",
152
+ notifications: false,
153
+ });
154
+
155
+ const handleSave = () => {
156
+ alert(`Setup completed! Data: ${JSON.stringify(formData, null, 2)}`);
157
+ };
158
+
159
+ const interactivePages: MultiPageDialogPage[] = [
160
+ {
161
+ id: "step1",
162
+ title: "Personal Info",
163
+ description: "Enter your basic information",
164
+ icon: UserIcon,
165
+ content: (
166
+ <div className="s-space-y-4">
167
+ <div>
168
+ <h3 className="s-mb-2 s-text-lg s-font-semibold">
169
+ Let's get started
170
+ </h3>
171
+ <p className="s-text-sm s-text-muted-foreground">
172
+ Fill in your details to continue to the next step.
173
+ </p>
174
+ </div>
175
+ <div className="s-space-y-3">
176
+ <div>
177
+ <label className="s-text-sm s-font-medium">Full Name *</label>
178
+ <input
179
+ type="text"
180
+ className="s-mt-1 s-w-full s-rounded-md s-border s-px-3 s-py-2"
181
+ placeholder="Enter your name"
182
+ value={formData.name}
183
+ onChange={(e) =>
184
+ setFormData({ ...formData, name: e.target.value })
185
+ }
186
+ />
187
+ </div>
188
+ <div>
189
+ <label className="s-text-sm s-font-medium">Email *</label>
190
+ <input
191
+ type="email"
192
+ className="s-mt-1 s-w-full s-rounded-md s-border s-px-3 s-py-2"
193
+ placeholder="Enter your email"
194
+ value={formData.email}
195
+ onChange={(e) =>
196
+ setFormData({ ...formData, email: e.target.value })
197
+ }
198
+ />
199
+ </div>
200
+ <div className="s-pt-2">
201
+ <Button
202
+ label="Continue to File Selection"
203
+ variant="primary"
204
+ size="md"
205
+ disabled={!formData.name || !formData.email}
206
+ onClick={() => setCurrentPageId("step2")}
207
+ />
208
+ </div>
209
+ </div>
210
+ </div>
211
+ ),
212
+ },
213
+ {
214
+ id: "step2",
215
+ title: "File Selection",
216
+ description: "Choose your files",
217
+ icon: DocumentTextIcon,
218
+ content: (
219
+ <div className="s-space-y-4">
220
+ <div>
221
+ <h3 className="s-mb-2 s-text-lg s-font-semibold">
222
+ Select a file to work with
223
+ </h3>
224
+ <p className="s-text-sm s-text-muted-foreground">
225
+ Choose from the available files below.
226
+ </p>
227
+ </div>
228
+ <div className="s-space-y-2">
229
+ {[
230
+ "project-proposal.pdf",
231
+ "budget-2024.xlsx",
232
+ "meeting-notes.docx",
233
+ ].map((file) => (
234
+ <div
235
+ key={file}
236
+ className={`s-flex s-cursor-pointer s-items-center s-justify-between s-rounded-md s-border s-p-3 s-transition-colors hover:s-bg-gray-50 ${
237
+ formData.selectedFile === file
238
+ ? "s-border-blue-300 s-bg-blue-50"
239
+ : ""
240
+ }`}
241
+ onClick={() =>
242
+ setFormData({ ...formData, selectedFile: file })
243
+ }
244
+ >
245
+ <span className="s-text-sm">{file}</span>
246
+ <div className="s-flex s-items-center s-gap-2">
247
+ <input
248
+ type="radio"
249
+ checked={formData.selectedFile === file}
250
+ readOnly
251
+ className="s-pointer-events-none"
252
+ />
253
+ </div>
254
+ </div>
255
+ ))}
256
+ </div>
257
+ {formData.selectedFile && (
258
+ <div className="s-pt-2">
259
+ <Button
260
+ label="Continue to Settings"
261
+ variant="primary"
262
+ size="md"
263
+ onClick={() => setCurrentPageId("step3")}
264
+ />
265
+ </div>
266
+ )}
267
+ </div>
268
+ ),
269
+ },
270
+ {
271
+ id: "step3",
272
+ title: "Final Settings",
273
+ description: "Configure your preferences",
274
+ icon: Cog6ToothIcon,
275
+ content: (
276
+ <div className="s-space-y-4">
277
+ <div>
278
+ <h3 className="s-mb-2 s-text-lg s-font-semibold">Almost done!</h3>
279
+ <p className="s-text-sm s-text-muted-foreground">
280
+ Configure your final preferences and complete the setup.
281
+ </p>
282
+ </div>
283
+ <div className="s-space-y-3">
284
+ <div className="s-flex s-items-center s-justify-between">
285
+ <span className="s-text-sm">Enable email notifications</span>
286
+ <input
287
+ type="checkbox"
288
+ className="s-rounded"
289
+ checked={formData.notifications}
290
+ onChange={(e) =>
291
+ setFormData({
292
+ ...formData,
293
+ notifications: e.target.checked,
294
+ })
295
+ }
296
+ />
297
+ </div>
298
+ <div className="s-rounded-md s-bg-gray-50 s-p-3">
299
+ <h4 className="s-mb-2 s-text-sm s-font-medium">Summary</h4>
300
+ <div className="s-space-y-1 s-text-xs s-text-gray-600">
301
+ <div>Name: {formData.name}</div>
302
+ <div>Email: {formData.email}</div>
303
+ <div>Selected File: {formData.selectedFile}</div>
304
+ <div>
305
+ Notifications:{" "}
306
+ {formData.notifications ? "Enabled" : "Disabled"}
307
+ </div>
308
+ </div>
309
+ </div>
310
+ </div>
311
+ </div>
312
+ ),
313
+ },
314
+ ];
315
+
316
+ return (
317
+ <MultiPageDialog>
318
+ <MultiPageDialogTrigger asChild>
319
+ <Button label="Open Interactive Setup" />
320
+ </MultiPageDialogTrigger>
321
+ <MultiPageDialogContent
322
+ pages={interactivePages}
323
+ currentPageId={currentPageId}
324
+ onPageChange={setCurrentPageId}
325
+ size="lg"
326
+ onSave={handleSave}
327
+ />
328
+ </MultiPageDialog>
329
+ );
330
+ },
331
+ };
332
+
333
+ export const WithConditionalNavigation: Story = {
334
+ render: () => {
335
+ const [currentPageId, setCurrentPageId] = useState("data-selection");
336
+ const [selectedItems, setSelectedItems] = useState<string[]>([]);
337
+ const [description, setDescription] = useState("");
338
+
339
+ const handleSave = () => {
340
+ alert(
341
+ `Configuration saved! Selected: ${selectedItems.join(", ")}, Description: ${description}`
342
+ );
343
+ };
344
+
345
+ const conditionalPages: MultiPageDialogPage[] = [
346
+ {
347
+ id: "data-selection",
348
+ title: "Select Data Sources",
349
+ description: "Choose which data sources to include",
350
+ icon: DocumentTextIcon,
351
+ content: (
352
+ <div className="s-space-y-4">
353
+ <div>
354
+ <h3 className="s-mb-2 s-text-lg s-font-semibold">
355
+ Available Data Sources
356
+ </h3>
357
+ <p className="s-text-sm s-text-muted-foreground">
358
+ Select at least one data source to proceed to the next step.
359
+ </p>
360
+ </div>
361
+ <div className="s-space-y-2">
362
+ {[
363
+ "Company Database",
364
+ "Customer Files",
365
+ "Analytics Data",
366
+ "Reports Archive",
367
+ ].map((item) => (
368
+ <div
369
+ key={item}
370
+ className={`s-flex s-cursor-pointer s-items-center s-justify-between s-rounded-md s-border s-p-3 s-transition-colors hover:s-bg-gray-50 ${
371
+ selectedItems.includes(item)
372
+ ? "s-border-blue-300 s-bg-blue-50"
373
+ : ""
374
+ }`}
375
+ onClick={() => {
376
+ if (selectedItems.includes(item)) {
377
+ setSelectedItems(selectedItems.filter((i) => i !== item));
378
+ } else {
379
+ setSelectedItems([...selectedItems, item]);
380
+ }
381
+ }}
382
+ >
383
+ <span className="s-text-sm">{item}</span>
384
+ <input
385
+ type="checkbox"
386
+ checked={selectedItems.includes(item)}
387
+ readOnly
388
+ className="s-pointer-events-none"
389
+ />
390
+ </div>
391
+ ))}
392
+ </div>
393
+ {selectedItems.length > 0 && (
394
+ <div className="s-rounded-md s-border s-bg-blue-50 s-p-3">
395
+ <p className="s-text-sm s-text-blue-700">
396
+ {selectedItems.length} data source
397
+ {selectedItems.length !== 1 ? "s" : ""} selected
398
+ </p>
399
+ </div>
400
+ )}
401
+ </div>
402
+ ),
403
+ },
404
+ {
405
+ id: "description",
406
+ title: "Add Description",
407
+ description: "Describe your configuration",
408
+ icon: Cog6ToothIcon,
409
+ content: (
410
+ <div className="s-space-y-4">
411
+ <div>
412
+ <h3 className="s-mb-2 s-text-lg s-font-semibold">
413
+ Configuration Details
414
+ </h3>
415
+ <p className="s-text-sm s-text-muted-foreground">
416
+ Add a description for your selected data sources.
417
+ </p>
418
+ </div>
419
+ <div className="s-rounded-md s-border s-bg-blue-50 s-p-3">
420
+ <p className="s-text-sm s-text-blue-700">
421
+ Selected: {selectedItems.join(", ")}
422
+ </p>
423
+ </div>
424
+ <div className="s-space-y-2">
425
+ <label className="s-text-sm s-font-medium">Description</label>
426
+ <textarea
427
+ className="s-mt-1 s-w-full s-rounded-md s-border s-px-3 s-py-2"
428
+ placeholder="Describe how these data sources will be used..."
429
+ value={description}
430
+ onChange={(e) => setDescription(e.target.value)}
431
+ rows={4}
432
+ />
433
+ <p className="s-text-xs s-text-muted-foreground">
434
+ This description helps explain the purpose of your
435
+ configuration.
436
+ </p>
437
+ </div>
438
+ </div>
439
+ ),
440
+ },
441
+ ];
442
+
443
+ return (
444
+ <MultiPageDialog>
445
+ <MultiPageDialogTrigger asChild>
446
+ <Button label="Open Configuration Wizard" />
447
+ </MultiPageDialogTrigger>
448
+ <MultiPageDialogContent
449
+ pages={conditionalPages}
450
+ currentPageId={currentPageId}
451
+ onPageChange={setCurrentPageId}
452
+ size="lg"
453
+ onSave={handleSave}
454
+ showNavigation={true}
455
+ disableNext={
456
+ currentPageId === "data-selection" && selectedItems.length === 0
457
+ }
458
+ disableSave={!description.trim()}
459
+ footerContent={
460
+ <div className="s-w-full s-border s-border-border-dark">
461
+ This is a footer content
462
+ </div>
463
+ }
464
+ />
465
+ </MultiPageDialog>
466
+ );
467
+ },
468
+ };
469
+
470
+ export const ScrollableContent: Story = {
471
+ render: () => {
472
+ const [currentPageId, setCurrentPageId] = useState("long-form");
473
+
474
+ const handleSave = () => {
475
+ alert("Long form submitted!");
476
+ };
477
+
478
+ const scrollablePages: MultiPageDialogPage[] = [
479
+ {
480
+ id: "long-form",
481
+ title: "Long Form Content",
482
+ description: "This page demonstrates scrollable content",
483
+ icon: DocumentTextIcon,
484
+ content: (
485
+ <div className="s-space-y-6">
486
+ <div>
487
+ <h3 className="s-mb-2 s-text-lg s-font-semibold">
488
+ Terms and Conditions
489
+ </h3>
490
+ <p className="s-text-sm s-text-muted-foreground">
491
+ This page contains a lot of content to demonstrate scrolling
492
+ functionality. The content should be scrollable within the
493
+ dialog area.
494
+ </p>
495
+ </div>
496
+
497
+ {Array.from({ length: 15 }, (_, i) => (
498
+ <div key={i} className="s-space-y-3">
499
+ <h4 className="s-text-md s-font-semibold">Section {i + 1}</h4>
500
+ <p className="s-text-sm s-text-muted-foreground">
501
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed
502
+ do eiusmod tempor incididunt ut labore et dolore magna aliqua.
503
+ Ut enim ad minim veniam, quis nostrud exercitation ullamco
504
+ laboris nisi ut aliquip ex ea commodo consequat. Duis aute
505
+ irure dolor in reprehenderit in voluptate velit esse cillum
506
+ dolore eu fugiat nulla pariatur.
507
+ </p>
508
+ <div className="s-space-y-2">
509
+ <div>
510
+ <label className="s-text-sm s-font-medium">
511
+ Field {i + 1}
512
+ </label>
513
+ <input
514
+ type="text"
515
+ className="s-mt-1 s-w-full s-rounded-md s-border s-px-3 s-py-2"
516
+ placeholder={`Enter value for field ${i + 1}`}
517
+ />
518
+ </div>
519
+ {i % 3 === 0 && (
520
+ <div>
521
+ <label className="s-text-sm s-font-medium">
522
+ Additional Notes
523
+ </label>
524
+ <textarea
525
+ className="s-mt-1 s-w-full s-rounded-md s-border s-px-3 s-py-2"
526
+ placeholder="Add any additional notes here..."
527
+ rows={3}
528
+ />
529
+ </div>
530
+ )}
531
+ </div>
532
+ </div>
533
+ ))}
534
+
535
+ <div className="s-rounded-md s-border s-bg-blue-50 s-p-4">
536
+ <h4 className="s-mb-2 s-text-sm s-font-semibold s-text-blue-900">
537
+ Scroll Test Complete
538
+ </h4>
539
+ <p className="s-text-xs s-text-blue-700">
540
+ If you can see this message, the scrolling functionality is
541
+ working correctly! The dialog maintains its fixed height while
542
+ allowing the content to scroll.
543
+ </p>
544
+ </div>
545
+ </div>
546
+ ),
547
+ },
548
+ {
549
+ id: "summary",
550
+ title: "Summary",
551
+ description: "Review your information",
552
+ icon: Cog6ToothIcon,
553
+ content: (
554
+ <div className="s-space-y-4">
555
+ <div>
556
+ <h3 className="s-mb-2 s-text-lg s-font-semibold">Form Summary</h3>
557
+ <p className="s-text-sm s-text-muted-foreground">
558
+ Thank you for testing the scrollable content functionality.
559
+ </p>
560
+ </div>
561
+ <div className="s-rounded-md s-border s-bg-green-50 s-p-3">
562
+ <p className="s-text-sm s-text-green-700">
563
+ ✓ Scrolling functionality verified
564
+ </p>
565
+ <p className="s-text-sm s-text-green-700">
566
+ ✓ Fixed dialog height maintained
567
+ </p>
568
+ <p className="s-text-sm s-text-green-700">
569
+ ✓ Content overflow handled properly
570
+ </p>
571
+ </div>
572
+ </div>
573
+ ),
574
+ },
575
+ ];
576
+
577
+ return (
578
+ <MultiPageDialog>
579
+ <MultiPageDialogTrigger asChild>
580
+ <Button label="Open Scrollable Content Dialog" />
581
+ </MultiPageDialogTrigger>
582
+ <MultiPageDialogContent
583
+ pages={scrollablePages}
584
+ currentPageId={currentPageId}
585
+ onPageChange={setCurrentPageId}
586
+ size="lg"
587
+ onSave={handleSave}
588
+ />
589
+ </MultiPageDialog>
590
+ );
591
+ },
592
+ };