@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
@@ -1,6 +1,7 @@
1
1
 
2
- import React, { useState } from 'react';
3
- import { ChevronDown, ChevronRight } from 'lucide-react';
2
+ import React, { useState, useEffect, useRef } from 'react';
3
+ import { ChevronDown, ChevronRight, X } from 'lucide-react';
4
+ import { createPortal } from 'react-dom';
4
5
 
5
6
  export interface SidebarItem {
6
7
  id: string;
@@ -22,6 +23,14 @@ export interface SidebarProps {
22
23
  header?: React.ReactNode; // Logo or header content
23
24
  footer?: React.ReactNode; // User profile or footer content
24
25
  currentPath?: string; // Current route for auto-active detection
26
+
27
+ // Mobile drawer props
28
+ /** Whether sidebar is open on mobile (drawer mode) */
29
+ mobileOpen?: boolean;
30
+ /** Callback when mobile drawer should close */
31
+ onMobileClose?: () => void;
32
+ /** Width of the sidebar (default: 256px / w-64) */
33
+ width?: string;
25
34
  }
26
35
 
27
36
  export interface SidebarGroupProps {
@@ -48,9 +57,9 @@ function SidebarNavItem({
48
57
  // Auto-detect if this item or any child is active based on currentPath
49
58
  const isItemActive = currentPath && item.href ? currentPath === item.href : item.active;
50
59
  const isChildActive = hasChildren && currentPath
51
- ? item.children?.some(child => currentPath === child.href || currentPath?.startsWith(child.href || ''))
60
+ ? item.children?.some(child => child.href && (currentPath === child.href || currentPath.startsWith(child.href)))
52
61
  : false;
53
- const shouldExpandByDefault = isChildActive || (hasChildren && currentPath?.startsWith(item.href || ''));
62
+ const shouldExpandByDefault = isChildActive || (hasChildren && item.href && currentPath?.startsWith(item.href));
54
63
 
55
64
  const [isExpanded, setIsExpanded] = useState(shouldExpandByDefault);
56
65
 
@@ -169,15 +178,147 @@ export function SidebarGroup({ title, items, onNavigate, defaultExpanded = true,
169
178
  );
170
179
  }
171
180
 
172
- export default function Sidebar({ items, onNavigate, className = '', header, footer, currentPath }: SidebarProps) {
173
- return (
174
- <div className={`flex flex-col h-full bg-white border-r border-paper-300 notebook-binding ${className}`}>
175
- {/* Header (Logo) */}
176
- {header && (
181
+ /**
182
+ * Sidebar - Navigation sidebar with mobile drawer support
183
+ *
184
+ * On desktop: Renders as a fixed-width sidebar
185
+ * On mobile: Renders as a drawer overlay when mobileOpen is true
186
+ *
187
+ * @example Desktop usage (no mobile props)
188
+ * ```tsx
189
+ * <Sidebar
190
+ * items={navItems}
191
+ * header={<Logo />}
192
+ * footer={<UserProfile />}
193
+ * currentPath={location.pathname}
194
+ * onNavigate={(href) => navigate(href)}
195
+ * />
196
+ * ```
197
+ *
198
+ * @example With mobile drawer support
199
+ * ```tsx
200
+ * const [mobileOpen, setMobileOpen] = useState(false);
201
+ *
202
+ * <Sidebar
203
+ * items={navItems}
204
+ * header={<Logo />}
205
+ * mobileOpen={mobileOpen}
206
+ * onMobileClose={() => setMobileOpen(false)}
207
+ * onNavigate={(href) => {
208
+ * navigate(href);
209
+ * setMobileOpen(false); // Close drawer on navigation
210
+ * }}
211
+ * />
212
+ * ```
213
+ */
214
+ export default function Sidebar({
215
+ items,
216
+ onNavigate,
217
+ className = '',
218
+ header,
219
+ footer,
220
+ currentPath,
221
+ mobileOpen,
222
+ onMobileClose,
223
+ width = 'w-64',
224
+ }: SidebarProps) {
225
+ const sidebarRef = useRef<HTMLDivElement>(null);
226
+ const [isAnimating, setIsAnimating] = useState(false);
227
+ const [shouldRender, setShouldRender] = useState(mobileOpen);
228
+
229
+ // Handle animation states for mobile drawer
230
+ useEffect(() => {
231
+ if (mobileOpen) {
232
+ setShouldRender(true);
233
+ // Small delay to trigger animation
234
+ requestAnimationFrame(() => {
235
+ setIsAnimating(true);
236
+ });
237
+ return; // No cleanup needed when opening
238
+ } else {
239
+ setIsAnimating(false);
240
+ // Wait for animation to complete before unmounting
241
+ const timer = setTimeout(() => {
242
+ setShouldRender(false);
243
+ }, 300);
244
+ return () => clearTimeout(timer);
245
+ }
246
+ }, [mobileOpen]);
247
+
248
+ // Handle escape key for mobile drawer
249
+ useEffect(() => {
250
+ if (!mobileOpen) return;
251
+
252
+ const handleEscape = (e: KeyboardEvent) => {
253
+ if (e.key === 'Escape') {
254
+ onMobileClose?.();
255
+ }
256
+ };
257
+
258
+ document.addEventListener('keydown', handleEscape);
259
+ return () => document.removeEventListener('keydown', handleEscape);
260
+ }, [mobileOpen, onMobileClose]);
261
+
262
+ // Lock body scroll when mobile drawer is open
263
+ useEffect(() => {
264
+ if (mobileOpen) {
265
+ document.body.style.overflow = 'hidden';
266
+ } else {
267
+ document.body.style.overflow = '';
268
+ }
269
+ return () => {
270
+ document.body.style.overflow = '';
271
+ };
272
+ }, [mobileOpen]);
273
+
274
+ // Handle navigation with auto-close on mobile
275
+ const handleNavigate = (href: string, external?: boolean) => {
276
+ onNavigate?.(href, external);
277
+ // Auto-close mobile drawer on navigation
278
+ if (mobileOpen) {
279
+ onMobileClose?.();
280
+ }
281
+ };
282
+
283
+ // Sidebar content (shared between desktop and mobile)
284
+ const sidebarContent = (
285
+ <div
286
+ ref={sidebarRef}
287
+ className={`flex flex-col h-full bg-white border-r border-paper-300 notebook-binding ${width} ${className}`}
288
+ >
289
+ {/* Mobile close button */}
290
+ {mobileOpen !== undefined && (
291
+ <div className="flex items-center justify-between px-4 pt-4 md:hidden">
292
+ <div className="flex-1">
293
+ {header}
294
+ </div>
295
+ <button
296
+ onClick={onMobileClose}
297
+ className="
298
+ flex items-center justify-center
299
+ w-10 h-10 -mr-2
300
+ text-ink-500 hover:text-ink-700
301
+ hover:bg-paper-100 rounded-full
302
+ transition-colors
303
+ "
304
+ aria-label="Close sidebar"
305
+ >
306
+ <X className="w-5 h-5" />
307
+ </button>
308
+ </div>
309
+ )}
310
+
311
+ {/* Header (Logo) - desktop only when mobile drawer has its own */}
312
+ {header && mobileOpen === undefined && (
177
313
  <div className="px-6 pt-6 pb-4">
178
314
  {header}
179
315
  </div>
180
316
  )}
317
+ {header && mobileOpen !== undefined && (
318
+ <div className="px-6 pt-2 pb-4 hidden md:block">
319
+ {header}
320
+ </div>
321
+ )}
181
322
 
182
323
  {/* Navigation */}
183
324
  <nav className="flex-1 px-3 py-2 space-y-1 overflow-y-auto">
@@ -192,7 +333,7 @@ export default function Sidebar({ items, onNavigate, className = '', header, foo
192
333
  <SidebarNavItem
193
334
  key={item.id}
194
335
  item={item}
195
- onNavigate={onNavigate}
336
+ onNavigate={handleNavigate}
196
337
  currentPath={currentPath}
197
338
  />
198
339
  );
@@ -207,4 +348,46 @@ export default function Sidebar({ items, onNavigate, className = '', header, foo
207
348
  )}
208
349
  </div>
209
350
  );
351
+
352
+ // If mobileOpen is not defined, render as regular sidebar (desktop mode)
353
+ if (mobileOpen === undefined) {
354
+ return sidebarContent;
355
+ }
356
+
357
+ // Mobile drawer mode
358
+ if (!shouldRender) {
359
+ return null;
360
+ }
361
+
362
+ return createPortal(
363
+ <div
364
+ className="fixed inset-0 z-50 md:hidden"
365
+ role="dialog"
366
+ aria-modal="true"
367
+ aria-label="Navigation menu"
368
+ >
369
+ {/* Backdrop */}
370
+ <div
371
+ className={`
372
+ absolute inset-0 bg-ink-900/50 backdrop-blur-sm
373
+ transition-opacity duration-300
374
+ ${isAnimating ? 'opacity-100' : 'opacity-0'}
375
+ `}
376
+ onClick={onMobileClose}
377
+ aria-hidden="true"
378
+ />
379
+
380
+ {/* Sidebar drawer */}
381
+ <div
382
+ className={`
383
+ absolute inset-y-0 left-0 flex max-w-full
384
+ transition-transform duration-300 ease-out
385
+ ${isAnimating ? 'translate-x-0' : '-translate-x-full'}
386
+ `}
387
+ >
388
+ {sidebarContent}
389
+ </div>
390
+ </div>,
391
+ document.body
392
+ );
210
393
  }
@@ -0,0 +1,327 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import SwipeActions from './SwipeActions';
3
+ import { Trash, Archive, Edit, Star, Share, Mail, MoreHorizontal } from 'lucide-react';
4
+ import Card, { CardContent } from './Card';
5
+ import Text from './Text';
6
+ import Badge from './Badge';
7
+ import Stack from './Stack';
8
+
9
+ const meta: Meta<typeof SwipeActions> = {
10
+ title: 'Mobile/SwipeActions',
11
+ component: SwipeActions,
12
+ parameters: {
13
+ layout: 'padded',
14
+ viewport: {
15
+ defaultViewport: 'mobileM',
16
+ },
17
+ docs: {
18
+ description: {
19
+ component: `
20
+ Touch-based swipe actions for mobile list items. Reveals action buttons when
21
+ swiping left or right, similar to iOS mail/messages.
22
+
23
+ Features:
24
+ - Left and right swipe directions
25
+ - Multiple actions per side
26
+ - Full swipe to trigger primary action
27
+ - Spring-back animation
28
+ - Touch and mouse support
29
+ - Customizable thresholds
30
+ `,
31
+ },
32
+ },
33
+ },
34
+ decorators: [
35
+ (Story) => (
36
+ <div className="max-w-md mx-auto">
37
+ <Story />
38
+ </div>
39
+ ),
40
+ ],
41
+ };
42
+
43
+ export default meta;
44
+ type Story = StoryObj<typeof SwipeActions>;
45
+
46
+ const SampleListItem = ({ title, subtitle, badge }: { title: string; subtitle: string; badge?: string }) => (
47
+ <div className="p-4 bg-white border-b border-paper-200">
48
+ <div className="flex items-start justify-between">
49
+ <div className="flex-1 min-w-0">
50
+ <Text weight="medium" className="truncate">{title}</Text>
51
+ <Text size="sm" color="muted" className="truncate mt-0.5">{subtitle}</Text>
52
+ </div>
53
+ {badge && (
54
+ <Badge variant="primary" size="sm">{badge}</Badge>
55
+ )}
56
+ </div>
57
+ </div>
58
+ );
59
+
60
+ /**
61
+ * Basic swipe left to reveal delete action
62
+ */
63
+ export const DeleteAction: Story = {
64
+ args: {
65
+ leftActions: [
66
+ {
67
+ id: 'delete',
68
+ label: 'Delete',
69
+ icon: <Trash className="h-5 w-5" />,
70
+ color: 'error',
71
+ onClick: () => alert('Delete clicked!'),
72
+ primary: true,
73
+ },
74
+ ],
75
+ children: <SampleListItem title="Swipe left to delete" subtitle="Try swiping this item to the left" />,
76
+ },
77
+ };
78
+
79
+ /**
80
+ * Multiple actions on the left side
81
+ */
82
+ export const MultipleLeftActions: Story = {
83
+ args: {
84
+ leftActions: [
85
+ {
86
+ id: 'delete',
87
+ label: 'Delete',
88
+ icon: <Trash className="h-5 w-5" />,
89
+ color: 'error',
90
+ onClick: () => alert('Delete clicked!'),
91
+ primary: true,
92
+ },
93
+ {
94
+ id: 'archive',
95
+ label: 'Archive',
96
+ icon: <Archive className="h-5 w-5" />,
97
+ color: 'warning',
98
+ onClick: () => alert('Archive clicked!'),
99
+ },
100
+ ],
101
+ children: <SampleListItem title="Multiple delete options" subtitle="Swipe left to see archive and delete" />,
102
+ },
103
+ };
104
+
105
+ /**
106
+ * Actions on both sides
107
+ */
108
+ export const BothSides: Story = {
109
+ args: {
110
+ leftActions: [
111
+ {
112
+ id: 'delete',
113
+ label: 'Delete',
114
+ icon: <Trash className="h-5 w-5" />,
115
+ color: 'error',
116
+ onClick: () => alert('Delete clicked!'),
117
+ primary: true,
118
+ },
119
+ ],
120
+ rightActions: [
121
+ {
122
+ id: 'edit',
123
+ label: 'Edit',
124
+ icon: <Edit className="h-5 w-5" />,
125
+ color: 'primary',
126
+ onClick: () => alert('Edit clicked!'),
127
+ primary: true,
128
+ },
129
+ {
130
+ id: 'star',
131
+ label: 'Star',
132
+ icon: <Star className="h-5 w-5" />,
133
+ color: 'warning',
134
+ onClick: () => alert('Star clicked!'),
135
+ },
136
+ ],
137
+ children: <SampleListItem title="Swipe both ways" subtitle="Left for delete, right for edit/star" />,
138
+ },
139
+ };
140
+
141
+ /**
142
+ * Full swipe to trigger action
143
+ */
144
+ export const FullSwipe: Story = {
145
+ args: {
146
+ leftActions: [
147
+ {
148
+ id: 'delete',
149
+ label: 'Delete',
150
+ icon: <Trash className="h-5 w-5" />,
151
+ color: 'error',
152
+ onClick: () => alert('Deleted via full swipe!'),
153
+ primary: true,
154
+ },
155
+ ],
156
+ fullSwipe: true,
157
+ fullSwipeThreshold: 0.4,
158
+ children: <SampleListItem title="Full swipe to delete" subtitle="Swipe all the way left to delete instantly" badge="Full swipe" />,
159
+ },
160
+ };
161
+
162
+ /**
163
+ * Email-style actions (like iOS Mail)
164
+ */
165
+ export const EmailStyle: Story = {
166
+ render: () => (
167
+ <Stack spacing="none">
168
+ <SwipeActions
169
+ leftActions={[
170
+ {
171
+ id: 'trash',
172
+ label: 'Trash',
173
+ icon: <Trash className="h-5 w-5" />,
174
+ color: 'error',
175
+ onClick: () => alert('Trash'),
176
+ primary: true,
177
+ },
178
+ ]}
179
+ rightActions={[
180
+ {
181
+ id: 'more',
182
+ label: 'More',
183
+ icon: <MoreHorizontal className="h-5 w-5" />,
184
+ color: 'default',
185
+ onClick: () => alert('More options'),
186
+ },
187
+ {
188
+ id: 'star',
189
+ label: 'Flag',
190
+ icon: <Star className="h-5 w-5" />,
191
+ color: 'warning',
192
+ onClick: () => alert('Flagged'),
193
+ },
194
+ ]}
195
+ fullSwipe
196
+ >
197
+ <SampleListItem
198
+ title="Meeting reminder"
199
+ subtitle="Don't forget the team sync at 3pm..."
200
+ badge="New"
201
+ />
202
+ </SwipeActions>
203
+
204
+ <SwipeActions
205
+ leftActions={[
206
+ {
207
+ id: 'trash',
208
+ label: 'Trash',
209
+ icon: <Trash className="h-5 w-5" />,
210
+ color: 'error',
211
+ onClick: () => alert('Trash'),
212
+ primary: true,
213
+ },
214
+ ]}
215
+ rightActions={[
216
+ {
217
+ id: 'more',
218
+ label: 'More',
219
+ icon: <MoreHorizontal className="h-5 w-5" />,
220
+ color: 'default',
221
+ onClick: () => alert('More options'),
222
+ },
223
+ {
224
+ id: 'reply',
225
+ label: 'Reply',
226
+ icon: <Mail className="h-5 w-5" />,
227
+ color: 'primary',
228
+ onClick: () => alert('Reply'),
229
+ },
230
+ ]}
231
+ fullSwipe
232
+ >
233
+ <SampleListItem
234
+ title="Weekly report"
235
+ subtitle="Here's the summary of this week's progress..."
236
+ />
237
+ </SwipeActions>
238
+
239
+ <SwipeActions
240
+ leftActions={[
241
+ {
242
+ id: 'archive',
243
+ label: 'Archive',
244
+ icon: <Archive className="h-5 w-5" />,
245
+ color: 'success',
246
+ onClick: () => alert('Archived'),
247
+ primary: true,
248
+ },
249
+ ]}
250
+ rightActions={[
251
+ {
252
+ id: 'share',
253
+ label: 'Share',
254
+ icon: <Share className="h-5 w-5" />,
255
+ color: 'primary',
256
+ onClick: () => alert('Share'),
257
+ },
258
+ ]}
259
+ fullSwipe
260
+ >
261
+ <SampleListItem
262
+ title="Project update"
263
+ subtitle="The new feature has been deployed to production..."
264
+ />
265
+ </SwipeActions>
266
+ </Stack>
267
+ ),
268
+ };
269
+
270
+ /**
271
+ * Disabled state
272
+ */
273
+ export const Disabled: Story = {
274
+ args: {
275
+ leftActions: [
276
+ {
277
+ id: 'delete',
278
+ label: 'Delete',
279
+ icon: <Trash className="h-5 w-5" />,
280
+ color: 'error',
281
+ onClick: () => alert('Delete clicked!'),
282
+ },
283
+ ],
284
+ disabled: true,
285
+ children: <SampleListItem title="Swipe disabled" subtitle="This item cannot be swiped" />,
286
+ },
287
+ };
288
+
289
+ /**
290
+ * In a card context
291
+ */
292
+ export const InCard: Story = {
293
+ render: () => (
294
+ <Card>
295
+ <CardContent className="p-0">
296
+ <SwipeActions
297
+ leftActions={[
298
+ {
299
+ id: 'delete',
300
+ label: 'Delete',
301
+ icon: <Trash className="h-5 w-5" />,
302
+ color: 'error',
303
+ onClick: () => alert('Delete'),
304
+ primary: true,
305
+ },
306
+ ]}
307
+ rightActions={[
308
+ {
309
+ id: 'edit',
310
+ label: 'Edit',
311
+ icon: <Edit className="h-5 w-5" />,
312
+ color: 'primary',
313
+ onClick: () => alert('Edit'),
314
+ },
315
+ ]}
316
+ >
317
+ <div className="p-4">
318
+ <Text weight="semibold">Card with swipe actions</Text>
319
+ <Text size="sm" color="muted" className="mt-1">
320
+ Swipe left to delete, right to edit
321
+ </Text>
322
+ </div>
323
+ </SwipeActions>
324
+ </CardContent>
325
+ </Card>
326
+ ),
327
+ };