@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
|
@@ -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
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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={
|
|
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
|
+
};
|