@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
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Replaces window.confirm() with a styled, accessible modal dialog.
|
|
6
6
|
* Supports different variants (danger, warning, info) and customizable buttons.
|
|
7
|
+
*
|
|
8
|
+
* On mobile, automatically renders as a BottomSheet for better touch interaction.
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
11
|
import React from 'react';
|
|
10
|
-
import Modal, { ModalFooter } from './Modal';
|
|
12
|
+
import Modal, { ModalFooter, ModalProps } from './Modal';
|
|
11
13
|
import { AlertTriangle, Info, Trash2 } from 'lucide-react';
|
|
12
14
|
|
|
13
15
|
export interface ConfirmDialogProps {
|
|
@@ -21,6 +23,10 @@ export interface ConfirmDialogProps {
|
|
|
21
23
|
variant?: 'danger' | 'warning' | 'info';
|
|
22
24
|
icon?: React.ReactNode;
|
|
23
25
|
isLoading?: boolean;
|
|
26
|
+
/** Mobile display mode (inherited from Modal) */
|
|
27
|
+
mobileMode?: ModalProps['mobileMode'];
|
|
28
|
+
/** Height preset for BottomSheet on mobile */
|
|
29
|
+
mobileHeight?: ModalProps['mobileHeight'];
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
const variantStyles = {
|
|
@@ -44,6 +50,41 @@ const variantStyles = {
|
|
|
44
50
|
},
|
|
45
51
|
};
|
|
46
52
|
|
|
53
|
+
/**
|
|
54
|
+
* ConfirmDialog - Confirmation dialog with mobile support
|
|
55
|
+
*
|
|
56
|
+
* @example Basic usage
|
|
57
|
+
* ```tsx
|
|
58
|
+
* <ConfirmDialog
|
|
59
|
+
* isOpen={isOpen}
|
|
60
|
+
* onClose={handleClose}
|
|
61
|
+
* onConfirm={handleDelete}
|
|
62
|
+
* title="Delete Item"
|
|
63
|
+
* message="Are you sure you want to delete this item? This action cannot be undone."
|
|
64
|
+
* variant="danger"
|
|
65
|
+
* />
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* @example With useConfirmDialog hook
|
|
69
|
+
* ```tsx
|
|
70
|
+
* const confirmDialog = useConfirmDialog();
|
|
71
|
+
*
|
|
72
|
+
* const handleDelete = () => {
|
|
73
|
+
* confirmDialog.show({
|
|
74
|
+
* title: 'Delete Item',
|
|
75
|
+
* message: 'Are you sure?',
|
|
76
|
+
* onConfirm: async () => await deleteItem(),
|
|
77
|
+
* });
|
|
78
|
+
* };
|
|
79
|
+
*
|
|
80
|
+
* return (
|
|
81
|
+
* <>
|
|
82
|
+
* <button onClick={handleDelete}>Delete</button>
|
|
83
|
+
* <ConfirmDialog {...confirmDialog.props} />
|
|
84
|
+
* </>
|
|
85
|
+
* );
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
47
88
|
export default function ConfirmDialog({
|
|
48
89
|
isOpen,
|
|
49
90
|
onClose,
|
|
@@ -55,6 +96,8 @@ export default function ConfirmDialog({
|
|
|
55
96
|
variant = 'danger',
|
|
56
97
|
icon,
|
|
57
98
|
isLoading = false,
|
|
99
|
+
mobileMode = 'auto',
|
|
100
|
+
mobileHeight = 'sm',
|
|
58
101
|
}: ConfirmDialogProps) {
|
|
59
102
|
const variantStyle = variantStyles[variant];
|
|
60
103
|
const IconComponent = icon || variantStyle.icon;
|
|
@@ -65,7 +108,16 @@ export default function ConfirmDialog({
|
|
|
65
108
|
};
|
|
66
109
|
|
|
67
110
|
return (
|
|
68
|
-
<Modal
|
|
111
|
+
<Modal
|
|
112
|
+
isOpen={isOpen}
|
|
113
|
+
onClose={onClose}
|
|
114
|
+
title={typeof title === 'string' ? title : String(title)}
|
|
115
|
+
size="sm"
|
|
116
|
+
showCloseButton={false}
|
|
117
|
+
mobileMode={mobileMode}
|
|
118
|
+
mobileHeight={mobileHeight}
|
|
119
|
+
mobileShowHandle={false}
|
|
120
|
+
>
|
|
69
121
|
<div className="flex items-start gap-4">
|
|
70
122
|
{/* Icon */}
|
|
71
123
|
<div className={`flex-shrink-0 flex items-center justify-center w-12 h-12 rounded-full ${variantStyle.iconBg}`}>
|
|
@@ -89,14 +141,14 @@ export default function ConfirmDialog({
|
|
|
89
141
|
<button
|
|
90
142
|
onClick={onClose}
|
|
91
143
|
disabled={isLoading}
|
|
92
|
-
className="px-4 py-2 text-sm font-medium text-ink-700 bg-white border border-paper-300 rounded-lg hover:bg-paper-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-accent-400 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
144
|
+
className="px-4 py-2 text-sm font-medium text-ink-700 bg-white border border-paper-300 rounded-lg hover:bg-paper-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-accent-400 disabled:opacity-50 disabled:cursor-not-allowed transition-colors min-h-touch-sm"
|
|
93
145
|
>
|
|
94
146
|
{typeof cancelLabel === 'string' ? cancelLabel : String(cancelLabel)}
|
|
95
147
|
</button>
|
|
96
148
|
<button
|
|
97
149
|
onClick={handleConfirm}
|
|
98
150
|
disabled={isLoading}
|
|
99
|
-
className={`px-4 py-2 text-sm font-medium text-white rounded-lg focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors ${variantStyle.button}`}
|
|
151
|
+
className={`px-4 py-2 text-sm font-medium text-white rounded-lg focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors min-h-touch-sm ${variantStyle.button}`}
|
|
100
152
|
>
|
|
101
153
|
{isLoading ? (
|
|
102
154
|
<span className="flex items-center gap-2">
|
|
@@ -5,6 +5,8 @@ import { ChevronDown, ChevronRight, MoreVertical, Edit, Trash } from 'lucide-rea
|
|
|
5
5
|
import Menu, { MenuItem } from './Menu';
|
|
6
6
|
import Pagination from './Pagination';
|
|
7
7
|
import Select from './Select';
|
|
8
|
+
import DataTableCardView, { CardViewConfig } from './DataTableCardView';
|
|
9
|
+
import { useIsMobile } from '../hooks/useResponsive';
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* Base data item interface - all data items must have an id
|
|
@@ -247,6 +249,16 @@ interface DataTableProps<T extends BaseDataItem = BaseDataItem> {
|
|
|
247
249
|
onPageSizeChange?: (pageSize: number) => void;
|
|
248
250
|
/** Show page size selector (default: true when paginated) */
|
|
249
251
|
showPageSizeSelector?: boolean;
|
|
252
|
+
|
|
253
|
+
// Mobile view props
|
|
254
|
+
/** Mobile view mode: 'auto' (detect viewport), 'card' (always cards), 'table' (always table) */
|
|
255
|
+
mobileView?: 'auto' | 'card' | 'table';
|
|
256
|
+
/** Configuration for card view layout */
|
|
257
|
+
cardConfig?: CardViewConfig<T>;
|
|
258
|
+
/** Gap between cards in card view */
|
|
259
|
+
cardGap?: 'sm' | 'md' | 'lg';
|
|
260
|
+
/** Custom class name for cards */
|
|
261
|
+
cardClassName?: string;
|
|
250
262
|
}
|
|
251
263
|
|
|
252
264
|
/**
|
|
@@ -517,7 +529,14 @@ export default function DataTable<T extends BaseDataItem = BaseDataItem>({
|
|
|
517
529
|
pageSizeOptions = [10, 25, 50, 100],
|
|
518
530
|
onPageSizeChange,
|
|
519
531
|
showPageSizeSelector = true,
|
|
532
|
+
// Mobile view props
|
|
533
|
+
mobileView = 'auto',
|
|
534
|
+
cardConfig,
|
|
535
|
+
cardGap = 'md',
|
|
536
|
+
cardClassName,
|
|
520
537
|
}: DataTableProps<T>) {
|
|
538
|
+
// Mobile detection for auto mode
|
|
539
|
+
const isMobileViewport = useIsMobile();
|
|
521
540
|
// Column resizing state
|
|
522
541
|
const [columnWidths, setColumnWidths] = useState<Record<string, number>>({});
|
|
523
542
|
const [resizingColumn, setResizingColumn] = useState<string | null>(null);
|
|
@@ -1536,11 +1555,51 @@ export default function DataTable<T extends BaseDataItem = BaseDataItem>({
|
|
|
1536
1555
|
);
|
|
1537
1556
|
};
|
|
1538
1557
|
|
|
1558
|
+
// Determine if we should show card view
|
|
1559
|
+
const shouldShowCardView =
|
|
1560
|
+
mobileView === 'card' ||
|
|
1561
|
+
(mobileView === 'auto' && isMobileViewport);
|
|
1562
|
+
|
|
1563
|
+
// Card view content
|
|
1564
|
+
const cardViewContent = shouldShowCardView ? (
|
|
1565
|
+
<DataTableCardView
|
|
1566
|
+
data={data}
|
|
1567
|
+
columns={visibleColumns}
|
|
1568
|
+
cardConfig={cardConfig}
|
|
1569
|
+
loading={loading}
|
|
1570
|
+
loadingRows={loadingRows}
|
|
1571
|
+
emptyMessage={emptyMessage}
|
|
1572
|
+
onCardClick={onRowClick}
|
|
1573
|
+
onCardLongPress={(item, event) => {
|
|
1574
|
+
if (enableContextMenu && allActions.length > 0) {
|
|
1575
|
+
event.preventDefault();
|
|
1576
|
+
const clientX = 'touches' in event ? event.touches[0].clientX : (event as React.MouseEvent).clientX;
|
|
1577
|
+
const clientY = 'touches' in event ? event.touches[0].clientY : (event as React.MouseEvent).clientY;
|
|
1578
|
+
setContextMenuState({
|
|
1579
|
+
isOpen: true,
|
|
1580
|
+
position: { x: clientX, y: clientY },
|
|
1581
|
+
item,
|
|
1582
|
+
});
|
|
1583
|
+
}
|
|
1584
|
+
}}
|
|
1585
|
+
selectable={selectable}
|
|
1586
|
+
selectedRows={selectedRowsSet}
|
|
1587
|
+
onSelectionChange={onRowSelect ? (rows) => onRowSelect(rows) : undefined}
|
|
1588
|
+
keyExtractor={getRowKey}
|
|
1589
|
+
actions={allActions}
|
|
1590
|
+
onEdit={onEdit}
|
|
1591
|
+
onDelete={onDelete}
|
|
1592
|
+
className={className}
|
|
1593
|
+
cardClassName={cardClassName}
|
|
1594
|
+
gap={cardGap}
|
|
1595
|
+
/>
|
|
1596
|
+
) : null;
|
|
1597
|
+
|
|
1539
1598
|
// Render with context menu
|
|
1540
1599
|
return (
|
|
1541
1600
|
<>
|
|
1542
1601
|
{renderPaginationControls()}
|
|
1543
|
-
{finalContent}
|
|
1602
|
+
{shouldShowCardView ? cardViewContent : finalContent}
|
|
1544
1603
|
{contextMenuState.isOpen && contextMenuState.item && (
|
|
1545
1604
|
<Menu
|
|
1546
1605
|
items={convertActionsToMenuItems(contextMenuState.item)}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import DataTableCardView from './DataTableCardView';
|
|
4
|
+
import { DataTableColumn } from './DataTable';
|
|
5
|
+
import Badge from './Badge';
|
|
6
|
+
import { Edit, Trash, Eye, Mail } from 'lucide-react';
|
|
7
|
+
|
|
8
|
+
interface User {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
email: string;
|
|
12
|
+
department: string;
|
|
13
|
+
role: string;
|
|
14
|
+
status: 'active' | 'inactive' | 'pending';
|
|
15
|
+
avatar?: string;
|
|
16
|
+
joinDate: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const sampleUsers: User[] = [
|
|
20
|
+
{
|
|
21
|
+
id: '1',
|
|
22
|
+
name: 'John Smith',
|
|
23
|
+
email: 'john.smith@example.com',
|
|
24
|
+
department: 'Engineering',
|
|
25
|
+
role: 'Senior Developer',
|
|
26
|
+
status: 'active',
|
|
27
|
+
joinDate: '2023-01-15',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: '2',
|
|
31
|
+
name: 'Sarah Johnson',
|
|
32
|
+
email: 'sarah.j@example.com',
|
|
33
|
+
department: 'Design',
|
|
34
|
+
role: 'UX Designer',
|
|
35
|
+
status: 'active',
|
|
36
|
+
joinDate: '2023-03-22',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: '3',
|
|
40
|
+
name: 'Mike Wilson',
|
|
41
|
+
email: 'mike.w@example.com',
|
|
42
|
+
department: 'Marketing',
|
|
43
|
+
role: 'Marketing Manager',
|
|
44
|
+
status: 'pending',
|
|
45
|
+
joinDate: '2024-01-10',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: '4',
|
|
49
|
+
name: 'Emily Davis',
|
|
50
|
+
email: 'emily.d@example.com',
|
|
51
|
+
department: 'Engineering',
|
|
52
|
+
role: 'DevOps Engineer',
|
|
53
|
+
status: 'inactive',
|
|
54
|
+
joinDate: '2022-06-05',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: '5',
|
|
58
|
+
name: 'Alex Chen',
|
|
59
|
+
email: 'alex.c@example.com',
|
|
60
|
+
department: 'Product',
|
|
61
|
+
role: 'Product Manager',
|
|
62
|
+
status: 'active',
|
|
63
|
+
joinDate: '2023-09-01',
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
const columns: DataTableColumn<User>[] = [
|
|
68
|
+
{ key: 'name', header: 'Name' },
|
|
69
|
+
{ key: 'email', header: 'Email' },
|
|
70
|
+
{ key: 'department', header: 'Department' },
|
|
71
|
+
{ key: 'role', header: 'Role' },
|
|
72
|
+
{
|
|
73
|
+
key: 'status',
|
|
74
|
+
header: 'Status',
|
|
75
|
+
render: (item) => {
|
|
76
|
+
const variants = {
|
|
77
|
+
active: 'success',
|
|
78
|
+
inactive: 'default',
|
|
79
|
+
pending: 'warning',
|
|
80
|
+
} as const;
|
|
81
|
+
return (
|
|
82
|
+
<Badge variant={variants[item.status]} size="sm">
|
|
83
|
+
{item.status.charAt(0).toUpperCase() + item.status.slice(1)}
|
|
84
|
+
</Badge>
|
|
85
|
+
);
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
{ key: 'joinDate', header: 'Join Date' },
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
const meta: Meta<typeof DataTableCardView> = {
|
|
92
|
+
title: 'Data Display/DataTableCardView',
|
|
93
|
+
component: DataTableCardView,
|
|
94
|
+
parameters: {
|
|
95
|
+
layout: 'padded',
|
|
96
|
+
viewport: {
|
|
97
|
+
defaultViewport: 'mobileM',
|
|
98
|
+
},
|
|
99
|
+
docs: {
|
|
100
|
+
description: {
|
|
101
|
+
component: `
|
|
102
|
+
Mobile-friendly card view for data tables. Renders each row as a touchable card
|
|
103
|
+
with configurable title, subtitle, metadata fields, and status badge.
|
|
104
|
+
|
|
105
|
+
Features:
|
|
106
|
+
- Touch-optimized with active states
|
|
107
|
+
- Configurable card layout
|
|
108
|
+
- Selection mode support
|
|
109
|
+
- Loading skeletons
|
|
110
|
+
- Column render function reuse
|
|
111
|
+
- Long-press for context actions
|
|
112
|
+
`,
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export default meta;
|
|
119
|
+
type Story = StoryObj<typeof DataTableCardView<User>>;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Default card view with basic configuration
|
|
123
|
+
*/
|
|
124
|
+
export const Default: Story = {
|
|
125
|
+
args: {
|
|
126
|
+
data: sampleUsers,
|
|
127
|
+
columns,
|
|
128
|
+
cardConfig: {
|
|
129
|
+
titleKey: 'name',
|
|
130
|
+
subtitleKey: 'email',
|
|
131
|
+
metadataKeys: ['department', 'role'],
|
|
132
|
+
badgeKey: 'status',
|
|
133
|
+
},
|
|
134
|
+
onCardClick: (user) => alert(`Clicked: ${user.name}`),
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Card view with avatar/initials display
|
|
140
|
+
*/
|
|
141
|
+
export const WithAvatar: Story = {
|
|
142
|
+
args: {
|
|
143
|
+
data: sampleUsers,
|
|
144
|
+
columns,
|
|
145
|
+
cardConfig: {
|
|
146
|
+
titleKey: 'name',
|
|
147
|
+
subtitleKey: 'role',
|
|
148
|
+
metadataKeys: ['email', 'department'],
|
|
149
|
+
badgeKey: 'status',
|
|
150
|
+
avatarKey: 'name', // Will show initials since no avatar URL
|
|
151
|
+
},
|
|
152
|
+
onCardClick: (user) => alert(`Clicked: ${user.name}`),
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Card view with chevron indicator for navigation
|
|
158
|
+
*/
|
|
159
|
+
export const WithChevron: Story = {
|
|
160
|
+
args: {
|
|
161
|
+
data: sampleUsers,
|
|
162
|
+
columns,
|
|
163
|
+
cardConfig: {
|
|
164
|
+
titleKey: 'name',
|
|
165
|
+
subtitleKey: 'email',
|
|
166
|
+
metadataKeys: ['department'],
|
|
167
|
+
badgeKey: 'status',
|
|
168
|
+
showChevron: true,
|
|
169
|
+
},
|
|
170
|
+
onCardClick: (user) => alert(`Navigate to: ${user.name}`),
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Card view with selection enabled
|
|
176
|
+
*/
|
|
177
|
+
export const WithSelection: Story = {
|
|
178
|
+
render: () => {
|
|
179
|
+
const [selected, setSelected] = useState<Set<string>>(new Set(['1', '3']));
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<div className="space-y-4">
|
|
183
|
+
<div className="text-sm text-ink-600">
|
|
184
|
+
Selected: {selected.size} items
|
|
185
|
+
</div>
|
|
186
|
+
<DataTableCardView
|
|
187
|
+
data={sampleUsers}
|
|
188
|
+
columns={columns}
|
|
189
|
+
cardConfig={{
|
|
190
|
+
titleKey: 'name',
|
|
191
|
+
subtitleKey: 'email',
|
|
192
|
+
metadataKeys: ['department', 'role'],
|
|
193
|
+
badgeKey: 'status',
|
|
194
|
+
}}
|
|
195
|
+
selectable
|
|
196
|
+
selectedRows={selected}
|
|
197
|
+
onSelectionChange={(rows) => setSelected(new Set(rows))}
|
|
198
|
+
/>
|
|
199
|
+
</div>
|
|
200
|
+
);
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Card view with action menu
|
|
206
|
+
*/
|
|
207
|
+
export const WithActions: Story = {
|
|
208
|
+
args: {
|
|
209
|
+
data: sampleUsers,
|
|
210
|
+
columns,
|
|
211
|
+
cardConfig: {
|
|
212
|
+
titleKey: 'name',
|
|
213
|
+
subtitleKey: 'email',
|
|
214
|
+
metadataKeys: ['department'],
|
|
215
|
+
badgeKey: 'status',
|
|
216
|
+
},
|
|
217
|
+
onEdit: (user) => alert(`Edit: ${user.name}`),
|
|
218
|
+
onDelete: (user) => alert(`Delete: ${user.name}`),
|
|
219
|
+
actions: [
|
|
220
|
+
{
|
|
221
|
+
label: 'View Profile',
|
|
222
|
+
icon: <Eye className="h-4 w-4" />,
|
|
223
|
+
onClick: (user) => alert(`View: ${user.name}`),
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
label: 'Send Email',
|
|
227
|
+
icon: <Mail className="h-4 w-4" />,
|
|
228
|
+
onClick: (user) => alert(`Email: ${user.email}`),
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
onCardLongPress: (user, event) => {
|
|
232
|
+
// In a real app, this would open an action menu
|
|
233
|
+
console.log('Long press on:', user.name, event);
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Loading state with skeleton cards
|
|
240
|
+
*/
|
|
241
|
+
export const Loading: Story = {
|
|
242
|
+
args: {
|
|
243
|
+
data: [],
|
|
244
|
+
columns,
|
|
245
|
+
loading: true,
|
|
246
|
+
loadingRows: 5,
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Empty state
|
|
252
|
+
*/
|
|
253
|
+
export const Empty: Story = {
|
|
254
|
+
args: {
|
|
255
|
+
data: [],
|
|
256
|
+
columns,
|
|
257
|
+
emptyMessage: 'No users found. Try adjusting your filters.',
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Compact gap spacing
|
|
263
|
+
*/
|
|
264
|
+
export const CompactGap: Story = {
|
|
265
|
+
args: {
|
|
266
|
+
data: sampleUsers,
|
|
267
|
+
columns,
|
|
268
|
+
cardConfig: {
|
|
269
|
+
titleKey: 'name',
|
|
270
|
+
subtitleKey: 'email',
|
|
271
|
+
badgeKey: 'status',
|
|
272
|
+
},
|
|
273
|
+
gap: 'sm',
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Large gap spacing
|
|
279
|
+
*/
|
|
280
|
+
export const LargeGap: Story = {
|
|
281
|
+
args: {
|
|
282
|
+
data: sampleUsers,
|
|
283
|
+
columns,
|
|
284
|
+
cardConfig: {
|
|
285
|
+
titleKey: 'name',
|
|
286
|
+
subtitleKey: 'email',
|
|
287
|
+
metadataKeys: ['department', 'role', 'joinDate'],
|
|
288
|
+
badgeKey: 'status',
|
|
289
|
+
},
|
|
290
|
+
gap: 'lg',
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Minimal card (title only)
|
|
296
|
+
*/
|
|
297
|
+
export const MinimalCard: Story = {
|
|
298
|
+
args: {
|
|
299
|
+
data: sampleUsers,
|
|
300
|
+
columns,
|
|
301
|
+
cardConfig: {
|
|
302
|
+
titleKey: 'name',
|
|
303
|
+
showChevron: true,
|
|
304
|
+
},
|
|
305
|
+
onCardClick: (user) => alert(`Clicked: ${user.name}`),
|
|
306
|
+
},
|
|
307
|
+
};
|