@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,383 @@
1
+ import { useState, useEffect, ReactNode } from 'react';
2
+ import { ChevronUp, ChevronDown } from 'lucide-react';
3
+
4
+ export interface ExpandablePanelProps {
5
+ /** Content shown in the collapsed header bar */
6
+ collapsedContent: ReactNode;
7
+ /** Full content shown when expanded */
8
+ children: ReactNode;
9
+ /** Position of the panel */
10
+ position?: 'bottom' | 'top';
11
+ /**
12
+ * Positioning mode:
13
+ * - 'viewport': Fixed to viewport edges (default, for standalone use)
14
+ * - 'container': Sticky within parent container (for use inside Page/AppLayout)
15
+ */
16
+ mode?: 'viewport' | 'container';
17
+ /** Whether the panel is expanded (controlled) */
18
+ expanded?: boolean;
19
+ /** Default expanded state (uncontrolled) */
20
+ defaultExpanded?: boolean;
21
+ /** Callback when expanded state changes */
22
+ onExpandedChange?: (expanded: boolean) => void;
23
+ /** Height when expanded */
24
+ expandedHeight?: string | number;
25
+ /**
26
+ * Maximum width of the panel (e.g., '1400px', '80%', 1200)
27
+ * When set, the panel will be centered horizontally within its container/viewport
28
+ */
29
+ maxWidth?: string | number;
30
+ /** Whether to show the expand/collapse toggle button */
31
+ showToggle?: boolean;
32
+ /** Custom toggle button content */
33
+ toggleContent?: ReactNode;
34
+ /** Additional actions to show in the header (right side) */
35
+ headerActions?: ReactNode;
36
+ /** Close on Escape key */
37
+ closeOnEscape?: boolean;
38
+ /** Visual variant */
39
+ variant?: 'default' | 'elevated' | 'bordered';
40
+ /** Size variant affecting header height */
41
+ size?: 'sm' | 'md' | 'lg';
42
+ /** Additional CSS classes for the container */
43
+ className?: string;
44
+ /** Additional CSS classes for the header */
45
+ headerClassName?: string;
46
+ /** Additional CSS classes for the content */
47
+ contentClassName?: string;
48
+ /** Z-index for the panel (only applies in viewport mode) */
49
+ zIndex?: number;
50
+ }
51
+
52
+ const sizeClasses = {
53
+ sm: {
54
+ header: 'h-10 px-3',
55
+ text: 'text-sm',
56
+ icon: 'h-4 w-4',
57
+ },
58
+ md: {
59
+ header: 'h-12 px-4',
60
+ text: 'text-sm',
61
+ icon: 'h-5 w-5',
62
+ },
63
+ lg: {
64
+ header: 'h-14 px-5',
65
+ text: 'text-base',
66
+ icon: 'h-5 w-5',
67
+ },
68
+ };
69
+
70
+ const variantClasses = {
71
+ default: {
72
+ container: 'bg-white border-ink-200',
73
+ header: 'bg-paper-50',
74
+ },
75
+ elevated: {
76
+ container: 'bg-white shadow-lg border-ink-200',
77
+ header: 'bg-white',
78
+ },
79
+ bordered: {
80
+ container: 'bg-white border-2 border-ink-300',
81
+ header: 'bg-paper-100',
82
+ },
83
+ };
84
+
85
+ /**
86
+ * ExpandablePanel - A panel that sticks to the bottom (or top) and can expand/collapse
87
+ *
88
+ * For bottom position: expands UPWARD (content appears above header)
89
+ * For top position: expands DOWNWARD (content appears below header)
90
+ *
91
+ * Two modes of operation:
92
+ * - `viewport`: Fixed to the viewport (for standalone pages, covers StatusBar)
93
+ * - `container`: Sticky within its parent container (for use inside Page/AppLayout, respects StatusBar)
94
+ *
95
+ * @example Basic usage (viewport mode - full page)
96
+ * ```tsx
97
+ * <ExpandablePanel
98
+ * mode="viewport"
99
+ * collapsedContent={<Text>3 items selected</Text>}
100
+ * expandedHeight="300px"
101
+ * >
102
+ * {content}
103
+ * </ExpandablePanel>
104
+ * ```
105
+ *
106
+ * @example Inside Page/AppLayout (container mode - respects StatusBar)
107
+ * ```tsx
108
+ * <Page>
109
+ * <ExpandablePanelContainer>
110
+ * <div className="flex-1 overflow-auto">
111
+ * {pageContent}
112
+ * </div>
113
+ * <ExpandablePanel
114
+ * mode="container"
115
+ * collapsedContent={<Text>3 items selected</Text>}
116
+ * expandedHeight="300px"
117
+ * >
118
+ * {selectedItemsContent}
119
+ * </ExpandablePanel>
120
+ * </ExpandablePanelContainer>
121
+ * </Page>
122
+ * ```
123
+ *
124
+ * @example With maxWidth to match page content
125
+ * ```tsx
126
+ * <ExpandablePanel
127
+ * mode="container"
128
+ * maxWidth="1400px"
129
+ * collapsedContent={<Text>Generated SQL</Text>}
130
+ * expandedHeight="300px"
131
+ * >
132
+ * {sqlContent}
133
+ * </ExpandablePanel>
134
+ * ```
135
+ */
136
+ export default function ExpandablePanel({
137
+ collapsedContent,
138
+ children,
139
+ position = 'bottom',
140
+ mode = 'viewport',
141
+ expanded: controlledExpanded,
142
+ defaultExpanded = false,
143
+ onExpandedChange,
144
+ expandedHeight = '300px',
145
+ maxWidth,
146
+ showToggle = true,
147
+ toggleContent,
148
+ headerActions,
149
+ closeOnEscape = true,
150
+ variant = 'elevated',
151
+ size = 'md',
152
+ className = '',
153
+ headerClassName = '',
154
+ contentClassName = '',
155
+ zIndex = 40,
156
+ }: ExpandablePanelProps) {
157
+ const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);
158
+
159
+ // Determine if controlled or uncontrolled
160
+ const isControlled = controlledExpanded !== undefined;
161
+ const expanded = isControlled ? controlledExpanded : internalExpanded;
162
+
163
+ const setExpanded = (value: boolean) => {
164
+ if (!isControlled) {
165
+ setInternalExpanded(value);
166
+ }
167
+ onExpandedChange?.(value);
168
+ };
169
+
170
+ const toggleExpanded = () => {
171
+ setExpanded(!expanded);
172
+ };
173
+
174
+ // Close on Escape
175
+ useEffect(() => {
176
+ if (!closeOnEscape || !expanded) return;
177
+
178
+ const handleEscape = (e: KeyboardEvent) => {
179
+ if (e.key === 'Escape') {
180
+ setExpanded(false);
181
+ }
182
+ };
183
+
184
+ document.addEventListener('keydown', handleEscape);
185
+ return () => document.removeEventListener('keydown', handleEscape);
186
+ }, [closeOnEscape, expanded]);
187
+
188
+ const sizeStyle = sizeClasses[size];
189
+ const variantStyle = variantClasses[variant];
190
+
191
+ const heightValue = typeof expandedHeight === 'number' ? `${expandedHeight}px` : expandedHeight;
192
+ const maxWidthValue = maxWidth
193
+ ? (typeof maxWidth === 'number' ? `${maxWidth}px` : maxWidth)
194
+ : undefined;
195
+
196
+ // Position classes differ based on mode
197
+ const getPositionClasses = () => {
198
+ if (mode === 'viewport') {
199
+ // Fixed to viewport
200
+ return position === 'bottom'
201
+ ? 'fixed bottom-0 left-0 right-0'
202
+ : 'fixed top-0 left-0 right-0';
203
+ } else {
204
+ // Absolute positioning within container - snaps to bottom
205
+ return position === 'bottom'
206
+ ? 'absolute bottom-0 left-0 right-0'
207
+ : 'absolute top-0 left-0 right-0';
208
+ }
209
+ };
210
+
211
+ // For bottom panel, we want chevron up to expand (reveal content above)
212
+ // For top panel, we want chevron down to expand (reveal content below)
213
+ const ChevronIcon = position === 'bottom'
214
+ ? (expanded ? ChevronDown : ChevronUp)
215
+ : (expanded ? ChevronUp : ChevronDown);
216
+
217
+ // Header component
218
+ const header = (
219
+ <div
220
+ className={`
221
+ flex items-center justify-between
222
+ ${sizeStyle.header}
223
+ ${variantStyle.header}
224
+ border-ink-200
225
+ flex-shrink-0
226
+ ${headerClassName}
227
+ `}
228
+ >
229
+ {/* Left side: collapsed content */}
230
+ <div className={`flex-1 flex items-center ${sizeStyle.text}`}>
231
+ {collapsedContent}
232
+ </div>
233
+
234
+ {/* Right side: actions and toggle */}
235
+ <div className="flex items-center gap-2">
236
+ {headerActions}
237
+
238
+ {showToggle && (
239
+ <button
240
+ type="button"
241
+ onClick={toggleExpanded}
242
+ className={`
243
+ flex items-center justify-center
244
+ p-1.5 rounded-md
245
+ text-ink-500 hover:text-ink-700
246
+ hover:bg-ink-100
247
+ transition-colors
248
+ focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-1
249
+ `}
250
+ aria-expanded={expanded}
251
+ aria-label={expanded ? 'Collapse panel' : 'Expand panel'}
252
+ >
253
+ {toggleContent || <ChevronIcon className={sizeStyle.icon} />}
254
+ </button>
255
+ )}
256
+ </div>
257
+ </div>
258
+ );
259
+
260
+ // Content component
261
+ const content = (
262
+ <div
263
+ className={`
264
+ overflow-hidden
265
+ transition-all duration-300 ease-in-out
266
+ `}
267
+ style={{
268
+ maxHeight: expanded ? heightValue : '0px',
269
+ opacity: expanded ? 1 : 0,
270
+ }}
271
+ >
272
+ <div
273
+ className={`
274
+ overflow-y-auto p-4
275
+ ${contentClassName}
276
+ `}
277
+ style={{ maxHeight: heightValue }}
278
+ >
279
+ {children}
280
+ </div>
281
+ </div>
282
+ );
283
+
284
+ // Build container styles
285
+ const containerStyle: React.CSSProperties = {
286
+ ...(mode === 'viewport' ? { zIndex } : {}),
287
+ ...(maxWidthValue ? {
288
+ maxWidth: maxWidthValue,
289
+ marginLeft: 'auto',
290
+ marginRight: 'auto'
291
+ } : {}),
292
+ };
293
+
294
+ return (
295
+ <div
296
+ className={`
297
+ ${getPositionClasses()}
298
+ ${variantStyle.container}
299
+ border-t rounded-t-lg
300
+ transition-all duration-300 ease-in-out
301
+ flex flex-col
302
+ ${className}
303
+ `}
304
+ style={containerStyle}
305
+ >
306
+ {/* For bottom position: content ABOVE header (expands up) */}
307
+ {/* For top position: header ABOVE content (expands down) */}
308
+ {position === 'bottom' ? (
309
+ <>
310
+ {content}
311
+ {header}
312
+ </>
313
+ ) : (
314
+ <>
315
+ {header}
316
+ {content}
317
+ </>
318
+ )}
319
+ </div>
320
+ );
321
+ }
322
+
323
+ /**
324
+ * ExpandablePanelSpacer - Adds spacing to prevent content from being hidden behind the panel
325
+ * Only needed in viewport mode. In container mode, the panel is part of the flex layout.
326
+ *
327
+ * @example
328
+ * ```tsx
329
+ * <div>
330
+ * <MainContent />
331
+ * <ExpandablePanelSpacer size="md" />
332
+ * </div>
333
+ * <ExpandablePanel mode="viewport" position="bottom" size="md" {...props} />
334
+ * ```
335
+ */
336
+ export function ExpandablePanelSpacer({
337
+ size = 'md'
338
+ }: {
339
+ size?: 'sm' | 'md' | 'lg'
340
+ }) {
341
+ const heights = {
342
+ sm: 'h-10',
343
+ md: 'h-12',
344
+ lg: 'h-14',
345
+ };
346
+
347
+ return <div className={heights[size]} />;
348
+ }
349
+
350
+ /**
351
+ * ExpandablePanelContainer - Wrapper that sets up proper layout for container mode
352
+ * Use this to wrap your page content when using ExpandablePanel with mode="container"
353
+ *
354
+ * This creates a relative container with full height so the panel can position absolutely
355
+ * at the bottom while the content scrolls above it.
356
+ *
357
+ * @example
358
+ * ```tsx
359
+ * <Page>
360
+ * <ExpandablePanelContainer>
361
+ * <div className="flex-1 overflow-auto p-4">
362
+ * {pageContent}
363
+ * </div>
364
+ * <ExpandablePanel mode="container" {...props}>
365
+ * {panelContent}
366
+ * </ExpandablePanel>
367
+ * </ExpandablePanelContainer>
368
+ * </Page>
369
+ * ```
370
+ */
371
+ export function ExpandablePanelContainer({
372
+ children,
373
+ className = '',
374
+ }: {
375
+ children: ReactNode;
376
+ className?: string;
377
+ }) {
378
+ return (
379
+ <div className={`relative h-full overflow-hidden ${className}`}>
380
+ {children}
381
+ </div>
382
+ );
383
+ }
@@ -0,0 +1,197 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Plus, Camera, Upload, FileText, Edit, Share, Trash, MessageSquare } from 'lucide-react';
3
+ import FloatingActionButton from './FloatingActionButton';
4
+
5
+ const meta: Meta<typeof FloatingActionButton> = {
6
+ title: 'Mobile/FloatingActionButton',
7
+ component: FloatingActionButton,
8
+ parameters: {
9
+ layout: 'fullscreen',
10
+ viewport: {
11
+ defaultViewport: 'mobile1',
12
+ },
13
+ docs: {
14
+ description: {
15
+ component: 'Material Design style floating action button (FAB) for primary actions on mobile screens.',
16
+ },
17
+ },
18
+ },
19
+ decorators: [
20
+ (Story) => (
21
+ <div style={{ minHeight: '100vh', padding: '16px', background: '#f5f5f4' }}>
22
+ <h1 style={{ fontSize: '24px', fontWeight: 'bold', marginBottom: '16px' }}>Page Content</h1>
23
+ <p style={{ color: '#666', marginBottom: '16px' }}>The FAB floats above all content.</p>
24
+ {Array.from({ length: 15 }).map((_, i) => (
25
+ <div key={i} style={{ padding: '16px', margin: '8px 0', background: 'white', borderRadius: '8px' }}>
26
+ Item {i + 1}
27
+ </div>
28
+ ))}
29
+ <Story />
30
+ </div>
31
+ ),
32
+ ],
33
+ };
34
+
35
+ export default meta;
36
+ type Story = StoryObj<typeof FloatingActionButton>;
37
+
38
+ export const Default: Story = {
39
+ args: {
40
+ onClick: () => console.log('FAB clicked'),
41
+ label: 'Add item',
42
+ },
43
+ };
44
+
45
+ export const CustomIcon: Story = {
46
+ args: {
47
+ onClick: () => console.log('Edit clicked'),
48
+ icon: <Edit className="w-6 h-6" />,
49
+ label: 'Edit',
50
+ },
51
+ };
52
+
53
+ export const WithActionMenu: Story = {
54
+ args: {
55
+ actions: [
56
+ { id: 'camera', icon: <Camera className="w-5 h-5" />, label: 'Take Photo', onClick: () => console.log('Camera') },
57
+ { id: 'upload', icon: <Upload className="w-5 h-5" />, label: 'Upload File', onClick: () => console.log('Upload') },
58
+ { id: 'note', icon: <FileText className="w-5 h-5" />, label: 'Create Note', onClick: () => console.log('Note') },
59
+ ],
60
+ label: 'Create options',
61
+ },
62
+ };
63
+
64
+ export const ManyActions: Story = {
65
+ args: {
66
+ actions: [
67
+ { id: 'camera', icon: <Camera className="w-5 h-5" />, label: 'Camera', onClick: () => console.log('Camera') },
68
+ { id: 'upload', icon: <Upload className="w-5 h-5" />, label: 'Upload', onClick: () => console.log('Upload') },
69
+ { id: 'note', icon: <FileText className="w-5 h-5" />, label: 'Note', onClick: () => console.log('Note') },
70
+ { id: 'share', icon: <Share className="w-5 h-5" />, label: 'Share', onClick: () => console.log('Share') },
71
+ { id: 'message', icon: <MessageSquare className="w-5 h-5" />, label: 'Message', onClick: () => console.log('Message') },
72
+ ],
73
+ label: 'Actions',
74
+ },
75
+ };
76
+
77
+ export const WithDisabledAction: Story = {
78
+ args: {
79
+ actions: [
80
+ { id: 'camera', icon: <Camera className="w-5 h-5" />, label: 'Take Photo', onClick: () => console.log('Camera') },
81
+ { id: 'upload', icon: <Upload className="w-5 h-5" />, label: 'Upload (disabled)', onClick: () => console.log('Upload'), disabled: true },
82
+ { id: 'note', icon: <FileText className="w-5 h-5" />, label: 'Create Note', onClick: () => console.log('Note') },
83
+ ],
84
+ },
85
+ };
86
+
87
+ export const Extended: Story = {
88
+ args: {
89
+ extended: true,
90
+ extendedLabel: 'New Task',
91
+ onClick: () => console.log('Create task'),
92
+ icon: <Plus className="w-6 h-6" />,
93
+ },
94
+ };
95
+
96
+ export const ExtendedWithCustomIcon: Story = {
97
+ args: {
98
+ extended: true,
99
+ extendedLabel: 'Compose',
100
+ onClick: () => console.log('Compose'),
101
+ icon: <Edit className="w-5 h-5" />,
102
+ },
103
+ };
104
+
105
+ export const BottomLeft: Story = {
106
+ args: {
107
+ position: 'bottom-left',
108
+ onClick: () => console.log('FAB clicked'),
109
+ label: 'Add',
110
+ },
111
+ };
112
+
113
+ export const BottomCenter: Story = {
114
+ args: {
115
+ position: 'bottom-center',
116
+ onClick: () => console.log('FAB clicked'),
117
+ label: 'Add',
118
+ },
119
+ };
120
+
121
+ export const SecondaryVariant: Story = {
122
+ args: {
123
+ variant: 'secondary',
124
+ onClick: () => console.log('FAB clicked'),
125
+ icon: <Edit className="w-6 h-6" />,
126
+ label: 'Edit',
127
+ },
128
+ };
129
+
130
+ export const LargeSize: Story = {
131
+ args: {
132
+ size: 'lg',
133
+ onClick: () => console.log('FAB clicked'),
134
+ label: 'Add',
135
+ },
136
+ };
137
+
138
+ export const CustomOffset: Story = {
139
+ args: {
140
+ onClick: () => console.log('FAB clicked'),
141
+ offset: { x: 32, y: 100 },
142
+ label: 'Custom position',
143
+ },
144
+ };
145
+
146
+ export const Hidden: Story = {
147
+ args: {
148
+ onClick: () => console.log('FAB clicked'),
149
+ hidden: true,
150
+ label: 'Hidden FAB',
151
+ },
152
+ parameters: {
153
+ docs: {
154
+ description: {
155
+ story: 'FAB can be hidden (e.g., when scrolling down). Use `useFABScroll()` hook for scroll-based visibility.',
156
+ },
157
+ },
158
+ },
159
+ };
160
+
161
+ export const InteractiveScrollDemo: Story = {
162
+ render: () => {
163
+ // Note: In a real app, you would use useFABScroll() hook
164
+ return (
165
+ <div>
166
+ <div style={{ padding: '16px', background: '#fef3c7', borderRadius: '8px', marginBottom: '16px' }}>
167
+ <p style={{ fontWeight: 'bold' }}>Scroll Demo</p>
168
+ <p style={{ fontSize: '14px', color: '#666' }}>
169
+ In production, use the <code>useFABScroll()</code> hook to hide FAB when scrolling down.
170
+ </p>
171
+ </div>
172
+ <FloatingActionButton
173
+ actions={[
174
+ { id: 'camera', icon: <Camera className="w-5 h-5" />, label: 'Photo', onClick: () => {} },
175
+ { id: 'upload', icon: <Upload className="w-5 h-5" />, label: 'Upload', onClick: () => {} },
176
+ ]}
177
+ />
178
+ </div>
179
+ );
180
+ },
181
+ };
182
+
183
+ export const DeleteAction: Story = {
184
+ args: {
185
+ icon: <Trash className="w-6 h-6" />,
186
+ variant: 'secondary',
187
+ onClick: () => console.log('Delete clicked'),
188
+ label: 'Delete selected',
189
+ },
190
+ parameters: {
191
+ docs: {
192
+ description: {
193
+ story: 'FAB can be used for contextual actions like bulk delete.',
194
+ },
195
+ },
196
+ },
197
+ };