@papernote/ui 1.3.1 → 1.6.0

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 (108) hide show
  1. package/dist/components/ActionBar.d.ts +112 -0
  2. package/dist/components/ActionBar.d.ts.map +1 -0
  3. package/dist/components/BottomNavigation.d.ts +98 -0
  4. package/dist/components/BottomNavigation.d.ts.map +1 -0
  5. package/dist/components/Checkbox.d.ts +2 -0
  6. package/dist/components/Checkbox.d.ts.map +1 -1
  7. package/dist/components/CheckboxList.d.ts +81 -0
  8. package/dist/components/CheckboxList.d.ts.map +1 -0
  9. package/dist/components/Chip.d.ts +92 -1
  10. package/dist/components/Chip.d.ts.map +1 -1
  11. package/dist/components/ConfirmDialog.d.ts +43 -1
  12. package/dist/components/ConfirmDialog.d.ts.map +1 -1
  13. package/dist/components/DataTable.d.ts +10 -1
  14. package/dist/components/DataTable.d.ts.map +1 -1
  15. package/dist/components/DataTableCardView.d.ts +99 -0
  16. package/dist/components/DataTableCardView.d.ts.map +1 -0
  17. package/dist/components/ExpandablePanel.d.ts +142 -0
  18. package/dist/components/ExpandablePanel.d.ts.map +1 -0
  19. package/dist/components/FloatingActionButton.d.ts +98 -0
  20. package/dist/components/FloatingActionButton.d.ts.map +1 -0
  21. package/dist/components/Input.d.ts +45 -1
  22. package/dist/components/Input.d.ts.map +1 -1
  23. package/dist/components/MobileHeader.d.ts +98 -0
  24. package/dist/components/MobileHeader.d.ts.map +1 -0
  25. package/dist/components/MobileLayout.d.ts +121 -0
  26. package/dist/components/MobileLayout.d.ts.map +1 -0
  27. package/dist/components/Modal.d.ts +78 -1
  28. package/dist/components/Modal.d.ts.map +1 -1
  29. package/dist/components/PageHeader.d.ts +86 -0
  30. package/dist/components/PageHeader.d.ts.map +1 -0
  31. package/dist/components/PullToRefresh.d.ts +87 -0
  32. package/dist/components/PullToRefresh.d.ts.map +1 -0
  33. package/dist/components/QueryTransparency.d.ts +1 -1
  34. package/dist/components/QueryTransparency.d.ts.map +1 -1
  35. package/dist/components/SearchableList.d.ts +83 -0
  36. package/dist/components/SearchableList.d.ts.map +1 -0
  37. package/dist/components/Select.d.ts +16 -2
  38. package/dist/components/Select.d.ts.map +1 -1
  39. package/dist/components/Sidebar.d.ts +40 -1
  40. package/dist/components/Sidebar.d.ts.map +1 -1
  41. package/dist/components/SwipeActions.d.ts +93 -0
  42. package/dist/components/SwipeActions.d.ts.map +1 -0
  43. package/dist/components/Switch.d.ts +1 -0
  44. package/dist/components/Switch.d.ts.map +1 -1
  45. package/dist/components/Textarea.d.ts +13 -0
  46. package/dist/components/Textarea.d.ts.map +1 -1
  47. package/dist/components/index.d.ts +31 -3
  48. package/dist/components/index.d.ts.map +1 -1
  49. package/dist/context/MobileContext.d.ts +168 -0
  50. package/dist/context/MobileContext.d.ts.map +1 -0
  51. package/dist/hooks/useResponsive.d.ts +158 -0
  52. package/dist/hooks/useResponsive.d.ts.map +1 -0
  53. package/dist/index.d.ts +1871 -51
  54. package/dist/index.esm.js +3025 -196
  55. package/dist/index.esm.js.map +1 -1
  56. package/dist/index.js +3063 -194
  57. package/dist/index.js.map +1 -1
  58. package/dist/styles.css +434 -1
  59. package/dist/types/index.d.ts +2 -0
  60. package/dist/types/index.d.ts.map +1 -1
  61. package/package.json +1 -1
  62. package/src/components/ActionBar.stories.tsx +246 -0
  63. package/src/components/ActionBar.tsx +242 -0
  64. package/src/components/BottomNavigation.stories.tsx +142 -0
  65. package/src/components/BottomNavigation.tsx +225 -0
  66. package/src/components/Checkbox.stories.tsx +162 -0
  67. package/src/components/Checkbox.tsx +22 -6
  68. package/src/components/CheckboxList.stories.tsx +311 -0
  69. package/src/components/CheckboxList.tsx +433 -0
  70. package/src/components/Chip.stories.tsx +389 -0
  71. package/src/components/Chip.tsx +182 -3
  72. package/src/components/ConfirmDialog.tsx +56 -4
  73. package/src/components/DataTable.tsx +60 -1
  74. package/src/components/DataTableCardView.stories.tsx +307 -0
  75. package/src/components/DataTableCardView.tsx +419 -0
  76. package/src/components/ExpandablePanel.stories.tsx +620 -0
  77. package/src/components/ExpandablePanel.tsx +383 -0
  78. package/src/components/FloatingActionButton.stories.tsx +197 -0
  79. package/src/components/FloatingActionButton.tsx +301 -0
  80. package/src/components/Grid.stories.tsx +16 -16
  81. package/src/components/Input.stories.tsx +214 -0
  82. package/src/components/Input.tsx +81 -4
  83. package/src/components/MobileHeader.stories.tsx +205 -0
  84. package/src/components/MobileHeader.tsx +233 -0
  85. package/src/components/MobileLayout.stories.tsx +338 -0
  86. package/src/components/MobileLayout.tsx +313 -0
  87. package/src/components/Modal.stories.tsx +388 -0
  88. package/src/components/Modal.tsx +122 -4
  89. package/src/components/PageHeader.stories.tsx +198 -0
  90. package/src/components/PageHeader.tsx +217 -0
  91. package/src/components/PullToRefresh.stories.tsx +321 -0
  92. package/src/components/PullToRefresh.tsx +294 -0
  93. package/src/components/QueryTransparency.tsx +1 -1
  94. package/src/components/SearchableList.stories.tsx +437 -0
  95. package/src/components/SearchableList.tsx +326 -0
  96. package/src/components/Select.stories.tsx +190 -0
  97. package/src/components/Select.tsx +353 -137
  98. package/src/components/Sidebar.tsx +193 -10
  99. package/src/components/SwipeActions.stories.tsx +327 -0
  100. package/src/components/SwipeActions.tsx +387 -0
  101. package/src/components/Switch.stories.tsx +158 -0
  102. package/src/components/Switch.tsx +12 -3
  103. package/src/components/Textarea.tsx +31 -1
  104. package/src/components/index.ts +69 -3
  105. package/src/context/MobileContext.tsx +296 -0
  106. package/src/hooks/useResponsive.ts +360 -0
  107. package/src/types/index.ts +4 -0
  108. package/tailwind.config.js +56 -1
@@ -0,0 +1,620 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { useState } from 'react';
3
+ import { ShoppingCart, Trash2, X, FileText, Check } from 'lucide-react';
4
+ import ExpandablePanel, { ExpandablePanelSpacer, ExpandablePanelContainer } from './ExpandablePanel';
5
+ import Stack from './Stack';
6
+ import Text from './Text';
7
+ import Button from './Button';
8
+ import Badge from './Badge';
9
+ import Card, { CardContent } from './Card';
10
+ import Checkbox from './Checkbox';
11
+
12
+ const meta: Meta<typeof ExpandablePanel> = {
13
+ title: 'Components/ExpandablePanel',
14
+ component: ExpandablePanel,
15
+ parameters: {
16
+ layout: 'fullscreen',
17
+ docs: {
18
+ description: {
19
+ component: `A panel that sticks to the bottom (or top) and can expand/collapse. Useful for selection summaries, shopping carts, batch actions, and persistent toolbars.
20
+
21
+ **Two modes of operation:**
22
+ - \`viewport\` (default): Fixed to the viewport edges. Use for standalone pages.
23
+ - \`container\`: Sticky within its parent container. Use inside Page/AppLayout to respect StatusBar and other layout elements.`,
24
+ },
25
+ },
26
+ },
27
+ tags: ['autodocs'],
28
+ decorators: [
29
+ (Story) => (
30
+ <div style={{ minHeight: '500px', padding: '20px', backgroundColor: '#f5f5f4' }}>
31
+ <Story />
32
+ </div>
33
+ ),
34
+ ],
35
+ };
36
+
37
+ export default meta;
38
+ type Story = StoryObj<typeof ExpandablePanel>;
39
+
40
+ /**
41
+ * Basic expandable panel at the bottom of the page (viewport mode).
42
+ */
43
+ export const Default: Story = {
44
+ render: () => (
45
+ <>
46
+ <div style={{ marginBottom: '100px' }}>
47
+ <Text size="lg" weight="semibold">Page Content</Text>
48
+ <Text color="muted">Scroll down to see more content. The panel is fixed at the bottom.</Text>
49
+ {Array.from({ length: 10 }).map((_, i) => (
50
+ <Card key={i} className="mt-4">
51
+ <CardContent>
52
+ <Text>Content block {i + 1}</Text>
53
+ </CardContent>
54
+ </Card>
55
+ ))}
56
+ </div>
57
+ <ExpandablePanelSpacer />
58
+ <ExpandablePanel
59
+ collapsedContent={<Text>Click to expand</Text>}
60
+ expandedHeight="200px"
61
+ >
62
+ <Text>This is the expanded content area. You can put any content here.</Text>
63
+ </ExpandablePanel>
64
+ </>
65
+ ),
66
+ };
67
+
68
+ /**
69
+ * Container mode - panel is sticky within its parent container.
70
+ * This mode is ideal for use inside Page/AppLayout where you want the panel
71
+ * to respect the StatusBar and only apply to the current page content.
72
+ */
73
+ export const ContainerMode: Story = {
74
+ decorators: [
75
+ (Story) => (
76
+ <div style={{ height: '500px', backgroundColor: '#f5f5f4' }}>
77
+ <Story />
78
+ </div>
79
+ ),
80
+ ],
81
+ render: function ContainerModeStory() {
82
+ const [selectedItems, setSelectedItems] = useState(['Item 1', 'Item 2']);
83
+
84
+ return (
85
+ <ExpandablePanelContainer className="h-full">
86
+ {/* Main scrollable content area */}
87
+ <div className="flex-1 overflow-auto p-4">
88
+ <Text size="lg" weight="semibold">Container Mode Example</Text>
89
+ <Text color="muted" className="mb-4">
90
+ The panel is sticky within this container, not fixed to the viewport.
91
+ This respects StatusBar and other layout elements.
92
+ </Text>
93
+
94
+ <Stack spacing="sm">
95
+ {['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5', 'Item 6', 'Item 7', 'Item 8'].map(item => (
96
+ <Card key={item}>
97
+ <CardContent>
98
+ <Checkbox
99
+ checked={selectedItems.includes(item)}
100
+ onChange={(checked) => {
101
+ if (checked) {
102
+ setSelectedItems(prev => [...prev, item]);
103
+ } else {
104
+ setSelectedItems(prev => prev.filter(i => i !== item));
105
+ }
106
+ }}
107
+ label={item}
108
+ />
109
+ </CardContent>
110
+ </Card>
111
+ ))}
112
+ </Stack>
113
+ </div>
114
+
115
+ {/* Panel sticky to bottom of container */}
116
+ {selectedItems.length > 0 && (
117
+ <ExpandablePanel
118
+ mode="container"
119
+ collapsedContent={
120
+ <Stack direction="horizontal" align="center" gap="sm">
121
+ <Badge variant="primary">{selectedItems.length}</Badge>
122
+ <Text>items selected</Text>
123
+ </Stack>
124
+ }
125
+ headerActions={
126
+ <Button size="sm" variant="ghost" onClick={() => setSelectedItems([])}>
127
+ Clear
128
+ </Button>
129
+ }
130
+ expandedHeight="200px"
131
+ >
132
+ <Stack spacing="sm">
133
+ <Text weight="semibold">Selected:</Text>
134
+ {selectedItems.map(item => (
135
+ <Text key={item} size="sm">{item}</Text>
136
+ ))}
137
+ </Stack>
138
+ </ExpandablePanel>
139
+ )}
140
+ </ExpandablePanelContainer>
141
+ );
142
+ },
143
+ };
144
+
145
+ /**
146
+ * Simulates how the panel would look inside a Page layout with a status bar.
147
+ * The container mode ensures the panel stays within the page content area.
148
+ */
149
+ export const InsidePageLayout: Story = {
150
+ decorators: [
151
+ (Story) => (
152
+ <div style={{ height: '600px', display: 'flex', flexDirection: 'column' }}>
153
+ <Story />
154
+ </div>
155
+ ),
156
+ ],
157
+ render: function InsidePageLayoutStory() {
158
+ return (
159
+ <>
160
+ {/* Simulated header */}
161
+ <div className="h-12 bg-white border-b border-ink-200 flex items-center px-4">
162
+ <Text weight="semibold">App Header</Text>
163
+ </div>
164
+
165
+ {/* Main content area (like Page component) */}
166
+ <div className="flex-1 flex flex-col overflow-hidden">
167
+ <ExpandablePanelContainer className="h-full">
168
+ <div className="flex-1 overflow-auto p-4 bg-paper-50">
169
+ <Text size="lg" weight="semibold">Page Content</Text>
170
+ <Text color="muted" className="mb-4">
171
+ This simulates a Page inside AppLayout. The expandable panel
172
+ respects the header above and status bar below.
173
+ </Text>
174
+
175
+ {Array.from({ length: 8 }).map((_, i) => (
176
+ <Card key={i} className="mt-4">
177
+ <CardContent>
178
+ <Text>Content row {i + 1}</Text>
179
+ </CardContent>
180
+ </Card>
181
+ ))}
182
+ </div>
183
+
184
+ <ExpandablePanel
185
+ mode="container"
186
+ collapsedContent={
187
+ <Stack direction="horizontal" align="center" gap="sm">
188
+ <Check className="h-4 w-4 text-success-600" />
189
+ <Text>3 items ready for export</Text>
190
+ </Stack>
191
+ }
192
+ headerActions={
193
+ <Button size="sm" variant="primary">
194
+ Export
195
+ </Button>
196
+ }
197
+ expandedHeight="180px"
198
+ >
199
+ <Stack spacing="sm">
200
+ <Text weight="semibold">Export Preview</Text>
201
+ <Text size="sm">Item A - Ready</Text>
202
+ <Text size="sm">Item B - Ready</Text>
203
+ <Text size="sm">Item C - Ready</Text>
204
+ </Stack>
205
+ </ExpandablePanel>
206
+ </ExpandablePanelContainer>
207
+ </div>
208
+
209
+ {/* Simulated status bar */}
210
+ <div className="h-8 bg-ink-800 text-white flex items-center px-4">
211
+ <Text size="sm" className="text-white">Status Bar - This stays visible</Text>
212
+ </div>
213
+ </>
214
+ );
215
+ },
216
+ };
217
+
218
+ /**
219
+ * Panel showing selected items count with actions.
220
+ */
221
+ export const SelectionSummary: Story = {
222
+ render: function SelectionSummaryStory() {
223
+ const [selectedItems, setSelectedItems] = useState(['Item 1', 'Item 2', 'Item 3']);
224
+ const [expanded, setExpanded] = useState(false);
225
+
226
+ const removeItem = (item: string) => {
227
+ setSelectedItems(prev => prev.filter(i => i !== item));
228
+ };
229
+
230
+ const clearAll = () => {
231
+ setSelectedItems([]);
232
+ };
233
+
234
+ if (selectedItems.length === 0) {
235
+ return (
236
+ <div style={{ padding: '20px' }}>
237
+ <Text color="muted">No items selected. The panel is hidden.</Text>
238
+ </div>
239
+ );
240
+ }
241
+
242
+ return (
243
+ <>
244
+ <div style={{ marginBottom: '80px' }}>
245
+ <Text size="lg" weight="semibold">Select Items</Text>
246
+ <Stack spacing="sm" className="mt-4">
247
+ {['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5'].map(item => (
248
+ <Card key={item}>
249
+ <CardContent>
250
+ <Checkbox
251
+ checked={selectedItems.includes(item)}
252
+ onChange={(checked) => {
253
+ if (checked) {
254
+ setSelectedItems(prev => [...prev, item]);
255
+ } else {
256
+ removeItem(item);
257
+ }
258
+ }}
259
+ label={item}
260
+ />
261
+ </CardContent>
262
+ </Card>
263
+ ))}
264
+ </Stack>
265
+ </div>
266
+ <ExpandablePanelSpacer />
267
+ <ExpandablePanel
268
+ expanded={expanded}
269
+ onExpandedChange={setExpanded}
270
+ collapsedContent={
271
+ <Stack direction="horizontal" align="center" gap="sm">
272
+ <Badge variant="primary">{selectedItems.length}</Badge>
273
+ <Text>items selected</Text>
274
+ </Stack>
275
+ }
276
+ headerActions={
277
+ <Button size="sm" variant="ghost" onClick={clearAll}>
278
+ Clear All
279
+ </Button>
280
+ }
281
+ expandedHeight="250px"
282
+ >
283
+ <Stack spacing="sm">
284
+ <Text weight="semibold">Selected Items:</Text>
285
+ {selectedItems.map(item => (
286
+ <Stack key={item} direction="horizontal" justify="between" align="center">
287
+ <Text>{item}</Text>
288
+ <Button
289
+ size="sm"
290
+ variant="ghost"
291
+ onClick={() => removeItem(item)}
292
+ >
293
+ <X className="h-4 w-4" />
294
+ </Button>
295
+ </Stack>
296
+ ))}
297
+ </Stack>
298
+ </ExpandablePanel>
299
+ </>
300
+ );
301
+ },
302
+ };
303
+
304
+ /**
305
+ * Shopping cart style panel.
306
+ */
307
+ export const ShoppingCartPanel: Story = {
308
+ render: function ShoppingCartStory() {
309
+ const [cartItems] = useState([
310
+ { id: 1, name: 'Product A', price: 29.99, qty: 2 },
311
+ { id: 2, name: 'Product B', price: 49.99, qty: 1 },
312
+ { id: 3, name: 'Product C', price: 19.99, qty: 3 },
313
+ ]);
314
+
315
+ const total = cartItems.reduce((sum, item) => sum + item.price * item.qty, 0);
316
+
317
+ return (
318
+ <>
319
+ <div style={{ marginBottom: '80px' }}>
320
+ <Text size="lg" weight="semibold">Product Catalog</Text>
321
+ <Text color="muted">Browse products below. Your cart is at the bottom.</Text>
322
+ </div>
323
+ <ExpandablePanelSpacer size="lg" />
324
+ <ExpandablePanel
325
+ collapsedContent={
326
+ <Stack direction="horizontal" align="center" gap="md">
327
+ <ShoppingCart className="h-5 w-5 text-ink-500" />
328
+ <Text weight="medium">{cartItems.length} items</Text>
329
+ <Text color="muted">|</Text>
330
+ <Text weight="semibold">${total.toFixed(2)}</Text>
331
+ </Stack>
332
+ }
333
+ headerActions={
334
+ <Button size="sm" variant="primary">
335
+ Checkout
336
+ </Button>
337
+ }
338
+ expandedHeight="300px"
339
+ size="lg"
340
+ variant="elevated"
341
+ >
342
+ <Stack spacing="md">
343
+ {cartItems.map(item => (
344
+ <Stack key={item.id} direction="horizontal" justify="between" align="center">
345
+ <Stack spacing="xs">
346
+ <Text weight="medium">{item.name}</Text>
347
+ <Text size="sm" color="muted">Qty: {item.qty}</Text>
348
+ </Stack>
349
+ <Text weight="semibold">${(item.price * item.qty).toFixed(2)}</Text>
350
+ </Stack>
351
+ ))}
352
+ <div className="border-t border-ink-200 pt-3 mt-2">
353
+ <Stack direction="horizontal" justify="between">
354
+ <Text weight="semibold">Total</Text>
355
+ <Text weight="bold" size="lg">${total.toFixed(2)}</Text>
356
+ </Stack>
357
+ </div>
358
+ </Stack>
359
+ </ExpandablePanel>
360
+ </>
361
+ );
362
+ },
363
+ };
364
+
365
+ /**
366
+ * Panel positioned at the top of the viewport.
367
+ */
368
+ export const TopPosition: Story = {
369
+ render: () => (
370
+ <>
371
+ <ExpandablePanel
372
+ position="top"
373
+ collapsedContent={
374
+ <Stack direction="horizontal" align="center" gap="sm">
375
+ <FileText className="h-4 w-4" />
376
+ <Text>Document info</Text>
377
+ </Stack>
378
+ }
379
+ expandedHeight="200px"
380
+ >
381
+ <Stack spacing="sm">
382
+ <Text weight="semibold">Document Details</Text>
383
+ <Text size="sm">Created: January 15, 2025</Text>
384
+ <Text size="sm">Modified: January 20, 2025</Text>
385
+ <Text size="sm">Author: John Doe</Text>
386
+ <Text size="sm">Status: Published</Text>
387
+ </Stack>
388
+ </ExpandablePanel>
389
+ <div style={{ marginTop: '80px' }}>
390
+ <Text size="lg" weight="semibold">Page Content</Text>
391
+ <Text color="muted">The panel is fixed at the top.</Text>
392
+ </div>
393
+ </>
394
+ ),
395
+ };
396
+
397
+ /**
398
+ * Small size variant.
399
+ */
400
+ export const SmallSize: Story = {
401
+ render: () => (
402
+ <>
403
+ <ExpandablePanelSpacer size="sm" />
404
+ <ExpandablePanel
405
+ size="sm"
406
+ collapsedContent={<Text size="sm">Small panel header</Text>}
407
+ expandedHeight="150px"
408
+ >
409
+ <Text size="sm">Compact content area for the small variant.</Text>
410
+ </ExpandablePanel>
411
+ </>
412
+ ),
413
+ };
414
+
415
+ /**
416
+ * Large size variant.
417
+ */
418
+ export const LargeSize: Story = {
419
+ render: () => (
420
+ <>
421
+ <ExpandablePanelSpacer size="lg" />
422
+ <ExpandablePanel
423
+ size="lg"
424
+ collapsedContent={<Text>Large panel with more header space</Text>}
425
+ expandedHeight="350px"
426
+ >
427
+ <Text>More spacious content area for the large variant.</Text>
428
+ </ExpandablePanel>
429
+ </>
430
+ ),
431
+ };
432
+
433
+ /**
434
+ * Different visual variants.
435
+ */
436
+ export const Variants: Story = {
437
+ render: function VariantsStory() {
438
+ const [variant, setVariant] = useState<'default' | 'elevated' | 'bordered'>('elevated');
439
+
440
+ return (
441
+ <>
442
+ <Stack spacing="md" style={{ marginBottom: '80px' }}>
443
+ <Text weight="semibold">Select Variant:</Text>
444
+ <Stack direction="horizontal" gap="sm">
445
+ <Button
446
+ size="sm"
447
+ variant={variant === 'default' ? 'primary' : 'secondary'}
448
+ onClick={() => setVariant('default')}
449
+ >
450
+ Default
451
+ </Button>
452
+ <Button
453
+ size="sm"
454
+ variant={variant === 'elevated' ? 'primary' : 'secondary'}
455
+ onClick={() => setVariant('elevated')}
456
+ >
457
+ Elevated
458
+ </Button>
459
+ <Button
460
+ size="sm"
461
+ variant={variant === 'bordered' ? 'primary' : 'secondary'}
462
+ onClick={() => setVariant('bordered')}
463
+ >
464
+ Bordered
465
+ </Button>
466
+ </Stack>
467
+ </Stack>
468
+ <ExpandablePanelSpacer />
469
+ <ExpandablePanel
470
+ variant={variant}
471
+ collapsedContent={<Text>Variant: {variant}</Text>}
472
+ expandedHeight="200px"
473
+ >
474
+ <Text>Content with {variant} variant styling.</Text>
475
+ </ExpandablePanel>
476
+ </>
477
+ );
478
+ },
479
+ };
480
+
481
+ /**
482
+ * Controlled expanded state.
483
+ */
484
+ export const Controlled: Story = {
485
+ render: function ControlledStory() {
486
+ const [expanded, setExpanded] = useState(false);
487
+
488
+ return (
489
+ <>
490
+ <Stack spacing="md" style={{ marginBottom: '80px' }}>
491
+ <Text weight="semibold">External Controls:</Text>
492
+ <Stack direction="horizontal" gap="sm">
493
+ <Button onClick={() => setExpanded(true)}>Expand</Button>
494
+ <Button onClick={() => setExpanded(false)}>Collapse</Button>
495
+ <Button onClick={() => setExpanded(e => !e)}>Toggle</Button>
496
+ </Stack>
497
+ <Text color="muted">Current state: {expanded ? 'Expanded' : 'Collapsed'}</Text>
498
+ </Stack>
499
+ <ExpandablePanelSpacer />
500
+ <ExpandablePanel
501
+ expanded={expanded}
502
+ onExpandedChange={setExpanded}
503
+ collapsedContent={<Text>Controlled panel</Text>}
504
+ expandedHeight="200px"
505
+ >
506
+ <Text>This panel's state is controlled externally.</Text>
507
+ </ExpandablePanel>
508
+ </>
509
+ );
510
+ },
511
+ };
512
+
513
+ /**
514
+ * Panel without the toggle button, only expandable via external controls.
515
+ */
516
+ export const NoToggle: Story = {
517
+ render: function NoToggleStory() {
518
+ const [expanded, setExpanded] = useState(false);
519
+
520
+ return (
521
+ <>
522
+ <Stack spacing="md" style={{ marginBottom: '80px' }}>
523
+ <Button onClick={() => setExpanded(e => !e)}>
524
+ {expanded ? 'Collapse' : 'Expand'} Panel
525
+ </Button>
526
+ </Stack>
527
+ <ExpandablePanelSpacer />
528
+ <ExpandablePanel
529
+ expanded={expanded}
530
+ onExpandedChange={setExpanded}
531
+ showToggle={false}
532
+ collapsedContent={<Text>Panel without built-in toggle</Text>}
533
+ headerActions={
534
+ <Button size="sm" variant="ghost" onClick={() => setExpanded(e => !e)}>
535
+ {expanded ? 'Hide' : 'Show'}
536
+ </Button>
537
+ }
538
+ expandedHeight="200px"
539
+ >
540
+ <Text>Toggle is hidden. Use external controls or header action.</Text>
541
+ </ExpandablePanel>
542
+ </>
543
+ );
544
+ },
545
+ };
546
+
547
+ /**
548
+ * Batch actions toolbar for a data table.
549
+ */
550
+ export const BatchActionsToolbar: Story = {
551
+ render: function BatchActionsStory() {
552
+ const [selectedCount] = useState(5);
553
+
554
+ return (
555
+ <>
556
+ <div style={{ marginBottom: '80px' }}>
557
+ <Text size="lg" weight="semibold">Data Table</Text>
558
+ <Text color="muted">5 rows selected. Batch actions available in the bottom panel.</Text>
559
+ </div>
560
+ <ExpandablePanelSpacer />
561
+ <ExpandablePanel
562
+ collapsedContent={
563
+ <Stack direction="horizontal" align="center" gap="md">
564
+ <Check className="h-5 w-5 text-success-600" />
565
+ <Text weight="medium">{selectedCount} rows selected</Text>
566
+ </Stack>
567
+ }
568
+ headerActions={
569
+ <Stack direction="horizontal" gap="sm">
570
+ <Button size="sm" variant="secondary" icon={<FileText className="h-4 w-4" />}>
571
+ Export
572
+ </Button>
573
+ <Button size="sm" variant="danger" icon={<Trash2 className="h-4 w-4" />}>
574
+ Delete
575
+ </Button>
576
+ </Stack>
577
+ }
578
+ expandedHeight="150px"
579
+ defaultExpanded={false}
580
+ >
581
+ <Stack spacing="sm">
582
+ <Text weight="semibold">Batch Actions</Text>
583
+ <Stack direction="horizontal" gap="sm" wrap>
584
+ <Button size="sm" variant="secondary">Change Status</Button>
585
+ <Button size="sm" variant="secondary">Assign Owner</Button>
586
+ <Button size="sm" variant="secondary">Add Tags</Button>
587
+ <Button size="sm" variant="secondary">Move to Folder</Button>
588
+ </Stack>
589
+ </Stack>
590
+ </ExpandablePanel>
591
+ </>
592
+ );
593
+ },
594
+ };
595
+
596
+ /**
597
+ * Default expanded state.
598
+ */
599
+ export const DefaultExpanded: Story = {
600
+ render: () => (
601
+ <>
602
+ <div style={{ marginBottom: '350px' }}>
603
+ <Text size="lg" weight="semibold">Page Content</Text>
604
+ <Text color="muted">Panel starts expanded by default.</Text>
605
+ </div>
606
+ <ExpandablePanelSpacer />
607
+ <ExpandablePanel
608
+ defaultExpanded={true}
609
+ collapsedContent={<Text>Panel (expanded by default)</Text>}
610
+ expandedHeight="250px"
611
+ >
612
+ <Stack spacing="sm">
613
+ <Text weight="semibold">Expanded Content</Text>
614
+ <Text>This panel starts in the expanded state.</Text>
615
+ <Text color="muted">Press Escape or click the toggle to collapse.</Text>
616
+ </Stack>
617
+ </ExpandablePanel>
618
+ </>
619
+ ),
620
+ };