@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.
- package/dist/components/ActionBar.d.ts +112 -0
- package/dist/components/ActionBar.d.ts.map +1 -0
- package/dist/components/BottomNavigation.d.ts +98 -0
- package/dist/components/BottomNavigation.d.ts.map +1 -0
- package/dist/components/Checkbox.d.ts +2 -0
- package/dist/components/Checkbox.d.ts.map +1 -1
- package/dist/components/CheckboxList.d.ts +81 -0
- package/dist/components/CheckboxList.d.ts.map +1 -0
- package/dist/components/Chip.d.ts +92 -1
- package/dist/components/Chip.d.ts.map +1 -1
- package/dist/components/ConfirmDialog.d.ts +43 -1
- package/dist/components/ConfirmDialog.d.ts.map +1 -1
- package/dist/components/DataTable.d.ts +10 -1
- package/dist/components/DataTable.d.ts.map +1 -1
- package/dist/components/DataTableCardView.d.ts +99 -0
- package/dist/components/DataTableCardView.d.ts.map +1 -0
- package/dist/components/ExpandablePanel.d.ts +142 -0
- package/dist/components/ExpandablePanel.d.ts.map +1 -0
- package/dist/components/FloatingActionButton.d.ts +98 -0
- package/dist/components/FloatingActionButton.d.ts.map +1 -0
- package/dist/components/Input.d.ts +45 -1
- package/dist/components/Input.d.ts.map +1 -1
- package/dist/components/MobileHeader.d.ts +98 -0
- package/dist/components/MobileHeader.d.ts.map +1 -0
- package/dist/components/MobileLayout.d.ts +121 -0
- package/dist/components/MobileLayout.d.ts.map +1 -0
- package/dist/components/Modal.d.ts +78 -1
- package/dist/components/Modal.d.ts.map +1 -1
- package/dist/components/PageHeader.d.ts +86 -0
- package/dist/components/PageHeader.d.ts.map +1 -0
- package/dist/components/PullToRefresh.d.ts +87 -0
- package/dist/components/PullToRefresh.d.ts.map +1 -0
- package/dist/components/QueryTransparency.d.ts +1 -1
- package/dist/components/QueryTransparency.d.ts.map +1 -1
- package/dist/components/SearchableList.d.ts +83 -0
- package/dist/components/SearchableList.d.ts.map +1 -0
- package/dist/components/Select.d.ts +16 -2
- package/dist/components/Select.d.ts.map +1 -1
- package/dist/components/Sidebar.d.ts +40 -1
- package/dist/components/Sidebar.d.ts.map +1 -1
- package/dist/components/SwipeActions.d.ts +93 -0
- package/dist/components/SwipeActions.d.ts.map +1 -0
- package/dist/components/Switch.d.ts +1 -0
- package/dist/components/Switch.d.ts.map +1 -1
- package/dist/components/Textarea.d.ts +13 -0
- package/dist/components/Textarea.d.ts.map +1 -1
- package/dist/components/index.d.ts +31 -3
- package/dist/components/index.d.ts.map +1 -1
- package/dist/context/MobileContext.d.ts +168 -0
- package/dist/context/MobileContext.d.ts.map +1 -0
- package/dist/hooks/useResponsive.d.ts +158 -0
- package/dist/hooks/useResponsive.d.ts.map +1 -0
- package/dist/index.d.ts +1871 -51
- package/dist/index.esm.js +3025 -196
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +3063 -194
- package/dist/index.js.map +1 -1
- package/dist/styles.css +434 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/ActionBar.stories.tsx +246 -0
- package/src/components/ActionBar.tsx +242 -0
- package/src/components/BottomNavigation.stories.tsx +142 -0
- package/src/components/BottomNavigation.tsx +225 -0
- package/src/components/Checkbox.stories.tsx +162 -0
- package/src/components/Checkbox.tsx +22 -6
- package/src/components/CheckboxList.stories.tsx +311 -0
- package/src/components/CheckboxList.tsx +433 -0
- package/src/components/Chip.stories.tsx +389 -0
- package/src/components/Chip.tsx +182 -3
- package/src/components/ConfirmDialog.tsx +56 -4
- package/src/components/DataTable.tsx +60 -1
- package/src/components/DataTableCardView.stories.tsx +307 -0
- package/src/components/DataTableCardView.tsx +419 -0
- package/src/components/ExpandablePanel.stories.tsx +620 -0
- package/src/components/ExpandablePanel.tsx +383 -0
- package/src/components/FloatingActionButton.stories.tsx +197 -0
- package/src/components/FloatingActionButton.tsx +301 -0
- package/src/components/Grid.stories.tsx +16 -16
- package/src/components/Input.stories.tsx +214 -0
- package/src/components/Input.tsx +81 -4
- package/src/components/MobileHeader.stories.tsx +205 -0
- package/src/components/MobileHeader.tsx +233 -0
- package/src/components/MobileLayout.stories.tsx +338 -0
- package/src/components/MobileLayout.tsx +313 -0
- package/src/components/Modal.stories.tsx +388 -0
- package/src/components/Modal.tsx +122 -4
- package/src/components/PageHeader.stories.tsx +198 -0
- package/src/components/PageHeader.tsx +217 -0
- package/src/components/PullToRefresh.stories.tsx +321 -0
- package/src/components/PullToRefresh.tsx +294 -0
- package/src/components/QueryTransparency.tsx +1 -1
- package/src/components/SearchableList.stories.tsx +437 -0
- package/src/components/SearchableList.tsx +326 -0
- package/src/components/Select.stories.tsx +190 -0
- package/src/components/Select.tsx +353 -137
- package/src/components/Sidebar.tsx +193 -10
- package/src/components/SwipeActions.stories.tsx +327 -0
- package/src/components/SwipeActions.tsx +387 -0
- package/src/components/Switch.stories.tsx +158 -0
- package/src/components/Switch.tsx +12 -3
- package/src/components/Textarea.tsx +31 -1
- package/src/components/index.ts +69 -3
- package/src/context/MobileContext.tsx +296 -0
- package/src/hooks/useResponsive.ts +360 -0
- package/src/types/index.ts +4 -0
- 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
|
+
};
|