@titan-design/react-ui 0.1.0 → 0.1.1

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 (192) hide show
  1. package/package.json +10 -5
  2. package/src/components/custom/DateTime/DateTime.stories.tsx +180 -0
  3. package/src/components/custom/DateTime/DateTime.test.tsx +152 -0
  4. package/src/components/custom/DateTime/DateTime.tsx +235 -0
  5. package/src/components/custom/DateTime/index.ts +2 -0
  6. package/src/components/custom/EmptyState/EmptyState.stories.tsx +99 -0
  7. package/src/components/custom/EmptyState/EmptyState.test.tsx +106 -0
  8. package/src/components/custom/EmptyState/EmptyState.tsx +68 -0
  9. package/src/components/custom/EmptyState/index.ts +2 -0
  10. package/src/components/custom/Metric/Metric.stories.tsx +133 -0
  11. package/src/components/custom/Metric/Metric.test.tsx +120 -0
  12. package/src/components/custom/Metric/Metric.tsx +92 -0
  13. package/src/components/custom/Metric/index.ts +2 -0
  14. package/src/components/custom/Sidebar/Sidebar.stories.tsx +227 -0
  15. package/src/components/custom/Sidebar/Sidebar.test.tsx +185 -0
  16. package/src/components/custom/Sidebar/Sidebar.tsx +257 -0
  17. package/src/components/custom/Sidebar/index.ts +18 -0
  18. package/src/components/custom/Table/Table.stories.tsx +481 -0
  19. package/src/components/custom/Table/Table.test.tsx +531 -0
  20. package/src/components/custom/Table/Table.tsx +696 -0
  21. package/src/components/custom/Table/index.ts +22 -0
  22. package/src/components/custom/Typography/Typography.stories.tsx +153 -0
  23. package/src/components/custom/Typography/Typography.test.tsx +107 -0
  24. package/src/components/custom/Typography/Typography.tsx +218 -0
  25. package/src/components/custom/Typography/index.ts +19 -0
  26. package/src/components/custom/index.ts +8 -0
  27. package/src/components/custom/stepper/Stepper.stories.tsx +245 -0
  28. package/src/components/custom/stepper/Stepper.test.tsx +230 -0
  29. package/src/components/custom/stepper/Stepper.tsx +250 -0
  30. package/src/components/custom/stepper/index.ts +10 -0
  31. package/src/components/index.ts +5 -0
  32. package/src/components/ui/alert/Alert.stories.tsx +154 -0
  33. package/src/components/ui/alert/Alert.test.tsx +202 -0
  34. package/src/components/ui/alert/Alert.tsx +170 -0
  35. package/src/components/ui/alert/index.ts +8 -0
  36. package/src/components/ui/autocomplete/Autocomplete.stories.tsx +299 -0
  37. package/src/components/ui/autocomplete/Autocomplete.test.tsx +247 -0
  38. package/src/components/ui/autocomplete/Autocomplete.tsx +332 -0
  39. package/src/components/ui/autocomplete/index.ts +2 -0
  40. package/src/components/ui/avatar/Avatar.stories.tsx +105 -0
  41. package/src/components/ui/avatar/Avatar.test.tsx +135 -0
  42. package/src/components/ui/avatar/Avatar.tsx +168 -0
  43. package/src/components/ui/avatar/index.ts +2 -0
  44. package/src/components/ui/badge/Badge.stories.tsx +100 -0
  45. package/src/components/ui/badge/Badge.test.tsx +86 -0
  46. package/src/components/ui/badge/Badge.tsx +105 -0
  47. package/src/components/ui/badge/index.ts +2 -0
  48. package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +70 -0
  49. package/src/components/ui/breadcrumbs/Breadcrumbs.test.tsx +156 -0
  50. package/src/components/ui/breadcrumbs/Breadcrumbs.tsx +127 -0
  51. package/src/components/ui/breadcrumbs/index.ts +2 -0
  52. package/src/components/ui/button/Button.stories.tsx +216 -0
  53. package/src/components/ui/button/Button.test.tsx +132 -0
  54. package/src/components/ui/button/Button.tsx +242 -0
  55. package/src/components/ui/button/index.ts +10 -0
  56. package/src/components/ui/card/Card.stories.tsx +443 -0
  57. package/src/components/ui/card/Card.test.tsx +265 -0
  58. package/src/components/ui/card/Card.tsx +380 -0
  59. package/src/components/ui/card/index.ts +20 -0
  60. package/src/components/ui/checkbox/Checkbox.stories.tsx +126 -0
  61. package/src/components/ui/checkbox/Checkbox.test.tsx +152 -0
  62. package/src/components/ui/checkbox/Checkbox.tsx +163 -0
  63. package/src/components/ui/checkbox/index.ts +2 -0
  64. package/src/components/ui/chip/Chip.stories.tsx +112 -0
  65. package/src/components/ui/chip/Chip.test.tsx +119 -0
  66. package/src/components/ui/chip/Chip.tsx +151 -0
  67. package/src/components/ui/chip/index.ts +2 -0
  68. package/src/components/ui/collapse/Collapse.stories.tsx +313 -0
  69. package/src/components/ui/collapse/Collapse.test.tsx +298 -0
  70. package/src/components/ui/collapse/Collapse.tsx +275 -0
  71. package/src/components/ui/collapse/index.ts +18 -0
  72. package/src/components/ui/data-row/DataRow.stories.tsx +78 -0
  73. package/src/components/ui/data-row/DataRow.test.tsx +78 -0
  74. package/src/components/ui/data-row/DataRow.tsx +29 -0
  75. package/src/components/ui/data-row/index.ts +2 -0
  76. package/src/components/ui/divider/Divider.stories.tsx +71 -0
  77. package/src/components/ui/divider/Divider.test.tsx +55 -0
  78. package/src/components/ui/divider/Divider.tsx +37 -0
  79. package/src/components/ui/divider/index.ts +2 -0
  80. package/src/components/ui/drawer/Drawer.stories.tsx +228 -0
  81. package/src/components/ui/drawer/Drawer.test.tsx +142 -0
  82. package/src/components/ui/drawer/Drawer.tsx +228 -0
  83. package/src/components/ui/drawer/index.ts +9 -0
  84. package/src/components/ui/form-field/FormField.stories.tsx +260 -0
  85. package/src/components/ui/form-field/FormField.test.tsx +280 -0
  86. package/src/components/ui/form-field/FormField.tsx +308 -0
  87. package/src/components/ui/form-field/index.ts +2 -0
  88. package/src/components/ui/help-tip/HelpTip.stories.tsx +250 -0
  89. package/src/components/ui/help-tip/HelpTip.test.tsx +198 -0
  90. package/src/components/ui/help-tip/HelpTip.tsx +229 -0
  91. package/src/components/ui/help-tip/index.ts +2 -0
  92. package/src/components/ui/icon-box/IconBox.stories.tsx +139 -0
  93. package/src/components/ui/icon-box/IconBox.test.tsx +120 -0
  94. package/src/components/ui/icon-box/IconBox.tsx +57 -0
  95. package/src/components/ui/icon-box/index.ts +1 -0
  96. package/src/components/ui/index.ts +36 -0
  97. package/src/components/ui/input/Input.stories.tsx +192 -0
  98. package/src/components/ui/input/Input.test.tsx +86 -0
  99. package/src/components/ui/input/Input.tsx +273 -0
  100. package/src/components/ui/input/index.ts +2 -0
  101. package/src/components/ui/link/Link.stories.tsx +78 -0
  102. package/src/components/ui/link/Link.test.tsx +94 -0
  103. package/src/components/ui/link/Link.tsx +86 -0
  104. package/src/components/ui/link/index.ts +2 -0
  105. package/src/components/ui/list-item/ListItem.stories.tsx +146 -0
  106. package/src/components/ui/list-item/ListItem.test.tsx +128 -0
  107. package/src/components/ui/list-item/ListItem.tsx +89 -0
  108. package/src/components/ui/list-item/index.ts +14 -0
  109. package/src/components/ui/menu/Menu.stories.tsx +180 -0
  110. package/src/components/ui/menu/Menu.test.tsx +353 -0
  111. package/src/components/ui/menu/Menu.tsx +222 -0
  112. package/src/components/ui/menu/index.ts +2 -0
  113. package/src/components/ui/modal/Modal.stories.tsx +350 -0
  114. package/src/components/ui/modal/Modal.test.tsx +159 -0
  115. package/src/components/ui/modal/Modal.tsx +254 -0
  116. package/src/components/ui/modal/index.ts +19 -0
  117. package/src/components/ui/popover/Popover.stories.tsx +190 -0
  118. package/src/components/ui/popover/Popover.test.tsx +277 -0
  119. package/src/components/ui/popover/Popover.tsx +162 -0
  120. package/src/components/ui/popover/index.ts +2 -0
  121. package/src/components/ui/progress/Progress.stories.tsx +190 -0
  122. package/src/components/ui/progress/Progress.test.tsx +181 -0
  123. package/src/components/ui/progress/Progress.tsx +320 -0
  124. package/src/components/ui/progress/index.ts +9 -0
  125. package/src/components/ui/radio/Radio.stories.tsx +199 -0
  126. package/src/components/ui/radio/Radio.test.tsx +166 -0
  127. package/src/components/ui/radio/Radio.tsx +200 -0
  128. package/src/components/ui/radio/index.ts +2 -0
  129. package/src/components/ui/section/Section.stories.tsx +111 -0
  130. package/src/components/ui/section/Section.test.tsx +121 -0
  131. package/src/components/ui/section/Section.tsx +52 -0
  132. package/src/components/ui/section/index.ts +6 -0
  133. package/src/components/ui/select/Select.stories.tsx +286 -0
  134. package/src/components/ui/select/Select.test.tsx +180 -0
  135. package/src/components/ui/select/Select.tsx +236 -0
  136. package/src/components/ui/select/index.ts +2 -0
  137. package/src/components/ui/skeleton/Skeleton.stories.tsx +153 -0
  138. package/src/components/ui/skeleton/Skeleton.test.tsx +162 -0
  139. package/src/components/ui/skeleton/Skeleton.tsx +229 -0
  140. package/src/components/ui/skeleton/index.ts +16 -0
  141. package/src/components/ui/spinner/Spinner.stories.tsx +67 -0
  142. package/src/components/ui/spinner/Spinner.test.tsx +67 -0
  143. package/src/components/ui/spinner/Spinner.tsx +72 -0
  144. package/src/components/ui/spinner/index.ts +2 -0
  145. package/src/components/ui/stack/Stack.stories.tsx +127 -0
  146. package/src/components/ui/stack/Stack.test.tsx +184 -0
  147. package/src/components/ui/stack/Stack.tsx +76 -0
  148. package/src/components/ui/stack/index.ts +2 -0
  149. package/src/components/ui/surface/Surface.stories.tsx +91 -0
  150. package/src/components/ui/surface/Surface.test.tsx +63 -0
  151. package/src/components/ui/surface/Surface.tsx +51 -0
  152. package/src/components/ui/surface/index.ts +1 -0
  153. package/src/components/ui/switch/Switch.stories.tsx +111 -0
  154. package/src/components/ui/switch/Switch.test.tsx +112 -0
  155. package/src/components/ui/switch/Switch.tsx +103 -0
  156. package/src/components/ui/switch/index.ts +2 -0
  157. package/src/components/ui/tabs/Tabs.stories.tsx +213 -0
  158. package/src/components/ui/tabs/Tabs.test.tsx +192 -0
  159. package/src/components/ui/tabs/Tabs.tsx +253 -0
  160. package/src/components/ui/tabs/index.ts +16 -0
  161. package/src/components/ui/toast/Toast.stories.tsx +255 -0
  162. package/src/components/ui/toast/Toast.test.tsx +207 -0
  163. package/src/components/ui/toast/Toast.tsx +327 -0
  164. package/src/components/ui/toast/index.ts +8 -0
  165. package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +415 -0
  166. package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +224 -0
  167. package/src/components/ui/toolbar-button/ToolbarButton.tsx +307 -0
  168. package/src/components/ui/toolbar-button/index.ts +7 -0
  169. package/src/components/ui/tooltip/Tooltip.stories.tsx +126 -0
  170. package/src/components/ui/tooltip/Tooltip.test.tsx +179 -0
  171. package/src/components/ui/tooltip/Tooltip.tsx +143 -0
  172. package/src/components/ui/tooltip/index.ts +2 -0
  173. package/src/index.ts +10 -0
  174. package/src/test/setup.ts +19 -0
  175. package/src/theme/Colors.stories.tsx +258 -0
  176. package/src/theme/config.ts +193 -0
  177. package/src/theme/elevation.stories.tsx +346 -0
  178. package/src/theme/elevation.test.ts +27 -0
  179. package/src/theme/elevation.ts +567 -0
  180. package/src/theme/index.ts +39 -0
  181. package/src/theme/shadows.stories.tsx +523 -0
  182. package/src/theme/shadows.ts +439 -0
  183. package/src/theme/tokens/index.ts +2 -0
  184. package/src/theme/tokens/primitives.ts +354 -0
  185. package/src/theme/tokens/semantic.ts +381 -0
  186. package/src/types/jest-axe.d.ts +16 -0
  187. package/src/types/nativewind.d.ts +54 -0
  188. package/src/utils/cn.ts +14 -0
  189. package/src/utils/colors.ts +140 -0
  190. package/src/utils/form.ts +188 -0
  191. package/src/utils/index.ts +29 -0
  192. package/src/utils/useTheme.ts +72 -0
@@ -0,0 +1,481 @@
1
+ import React, { useState } from 'react'
2
+ import type { Meta, StoryObj } from '@storybook/react-vite'
3
+ import { View, Text } from 'react-native'
4
+ import { cn } from '../../../utils/cn'
5
+ import {
6
+ Table,
7
+ TableHeader,
8
+ TableBody,
9
+ TableRow,
10
+ TableHeaderCell,
11
+ TableCell,
12
+ TablePagination,
13
+ TableEmptyState,
14
+ TableSelectAllCell,
15
+ TableSelectCell,
16
+ useTable,
17
+ } from './Table'
18
+ import { Button, ButtonText } from '../../ui/button/Button'
19
+
20
+ interface User {
21
+ id: string
22
+ name: string
23
+ email: string
24
+ role: string
25
+ status: string
26
+ }
27
+
28
+ const sampleUsers: User[] = [
29
+ { id: '1', name: 'Alice Johnson', email: 'alice@example.com', role: 'Admin', status: 'Active' },
30
+ { id: '2', name: 'Bob Smith', email: 'bob@example.com', role: 'Editor', status: 'Active' },
31
+ { id: '3', name: 'Carol White', email: 'carol@example.com', role: 'Viewer', status: 'Inactive' },
32
+ { id: '4', name: 'David Brown', email: 'david@example.com', role: 'Editor', status: 'Active' },
33
+ { id: '5', name: 'Eve Davis', email: 'eve@example.com', role: 'Admin', status: 'Active' },
34
+ ]
35
+
36
+ const manyUsers: User[] = Array.from({ length: 50 }, (_, i) => ({
37
+ id: String(i + 1),
38
+ name: `User ${i + 1}`,
39
+ email: `user${i + 1}@example.com`,
40
+ role: ['Admin', 'Editor', 'Viewer'][i % 3],
41
+ status: i % 5 === 0 ? 'Inactive' : 'Active',
42
+ }))
43
+
44
+ const meta: Meta<typeof Table> = {
45
+ title: 'Custom/Table',
46
+ component: Table,
47
+ tags: ['autodocs'],
48
+ argTypes: {
49
+ isLoading: {
50
+ control: 'boolean',
51
+ description: 'Whether the table is in a loading state',
52
+ },
53
+ selectable: {
54
+ control: 'boolean',
55
+ description: 'Enable row selection checkboxes',
56
+ },
57
+ loadingRowCount: {
58
+ control: 'number',
59
+ description: 'Number of skeleton rows when loading',
60
+ },
61
+ },
62
+ }
63
+
64
+ export default meta
65
+ type Story = StoryObj<typeof Table>
66
+
67
+ export const Default: Story = {
68
+ render: () => (
69
+ <Table>
70
+ <TableHeader>
71
+ <TableRow isHoverable={false}>
72
+ <TableHeaderCell>Name</TableHeaderCell>
73
+ <TableHeaderCell>Email</TableHeaderCell>
74
+ <TableHeaderCell>Role</TableHeaderCell>
75
+ <TableHeaderCell>Status</TableHeaderCell>
76
+ </TableRow>
77
+ </TableHeader>
78
+ <TableBody>
79
+ {sampleUsers.map((user) => (
80
+ <TableRow key={user.id}>
81
+ <TableCell>
82
+ <Text className="text-sm text-text-primary font-medium">{user.name}</Text>
83
+ </TableCell>
84
+ <TableCell>{user.email}</TableCell>
85
+ <TableCell>{user.role}</TableCell>
86
+ <TableCell>
87
+ <View
88
+ className={cn(
89
+ 'px-2 py-0.5 rounded-full self-start',
90
+ user.status === 'Active' ? 'bg-status-success/20' : 'bg-status-warning/20'
91
+ )}
92
+ >
93
+ <Text
94
+ className={cn(
95
+ 'text-xs font-medium',
96
+ user.status === 'Active' ? 'text-status-success' : 'text-status-warning'
97
+ )}
98
+ >
99
+ {user.status}
100
+ </Text>
101
+ </View>
102
+ </TableCell>
103
+ </TableRow>
104
+ ))}
105
+ </TableBody>
106
+ </Table>
107
+ ),
108
+ }
109
+
110
+ export const WithSorting: Story = {
111
+ render: function Render() {
112
+ const [sortColumn, setSortColumn] = useState<string | undefined>(undefined)
113
+ const [sortDirection, setSortDirection] = useState<'asc' | 'desc' | null>(null)
114
+
115
+ const handleSort = (column: string) => {
116
+ if (sortColumn === column) {
117
+ setSortDirection((prev) =>
118
+ prev === 'asc' ? 'desc' : prev === 'desc' ? null : 'asc'
119
+ )
120
+ } else {
121
+ setSortColumn(column)
122
+ setSortDirection('asc')
123
+ }
124
+ }
125
+
126
+ const sorted = [...sampleUsers].sort((a, b) => {
127
+ if (!sortColumn || !sortDirection) return 0
128
+ const aVal = a[sortColumn as keyof User]
129
+ const bVal = b[sortColumn as keyof User]
130
+ const cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0
131
+ return sortDirection === 'asc' ? cmp : -cmp
132
+ })
133
+
134
+ return (
135
+ <Table sortColumn={sortColumn} sortDirection={sortDirection} onSort={handleSort}>
136
+ <TableHeader>
137
+ <TableRow isHoverable={false}>
138
+ <TableHeaderCell sortKey="name">Name</TableHeaderCell>
139
+ <TableHeaderCell sortKey="email">Email</TableHeaderCell>
140
+ <TableHeaderCell sortKey="role">Role</TableHeaderCell>
141
+ <TableHeaderCell sortKey="status">Status</TableHeaderCell>
142
+ </TableRow>
143
+ </TableHeader>
144
+ <TableBody>
145
+ {sorted.map((user) => (
146
+ <TableRow key={user.id}>
147
+ <TableCell>{user.name}</TableCell>
148
+ <TableCell>{user.email}</TableCell>
149
+ <TableCell>{user.role}</TableCell>
150
+ <TableCell>{user.status}</TableCell>
151
+ </TableRow>
152
+ ))}
153
+ </TableBody>
154
+ </Table>
155
+ )
156
+ },
157
+ }
158
+
159
+ export const WithRowSelection: Story = {
160
+ render: function Render() {
161
+ const [selectedRows, setSelectedRows] = useState<Set<string>>(new Set())
162
+ const rowIds = sampleUsers.map((u) => u.id)
163
+
164
+ const handleSelectRow = (id: string, selected: boolean) => {
165
+ setSelectedRows((prev) => {
166
+ const next = new Set(prev)
167
+ if (selected) {
168
+ next.add(id)
169
+ } else {
170
+ next.delete(id)
171
+ }
172
+ return next
173
+ })
174
+ }
175
+
176
+ const handleSelectAll = (selected: boolean) => {
177
+ setSelectedRows(selected ? new Set(rowIds) : new Set())
178
+ }
179
+
180
+ return (
181
+ <View style={{ gap: 8 }}>
182
+ <Text className="text-sm text-text-secondary">
183
+ Selected: {selectedRows.size} of {sampleUsers.length}
184
+ </Text>
185
+ <Table
186
+ selectable
187
+ selectedRows={selectedRows}
188
+ onSelectRow={handleSelectRow}
189
+ onSelectAll={handleSelectAll}
190
+ rowIds={rowIds}
191
+ >
192
+ <TableHeader>
193
+ <TableRow isHoverable={false}>
194
+ <TableSelectAllCell />
195
+ <TableHeaderCell>Name</TableHeaderCell>
196
+ <TableHeaderCell>Email</TableHeaderCell>
197
+ <TableHeaderCell>Role</TableHeaderCell>
198
+ </TableRow>
199
+ </TableHeader>
200
+ <TableBody>
201
+ {sampleUsers.map((user) => (
202
+ <TableRow key={user.id} isSelected={selectedRows.has(user.id)}>
203
+ <TableSelectCell rowId={user.id} />
204
+ <TableCell>{user.name}</TableCell>
205
+ <TableCell>{user.email}</TableCell>
206
+ <TableCell>{user.role}</TableCell>
207
+ </TableRow>
208
+ ))}
209
+ </TableBody>
210
+ </Table>
211
+ </View>
212
+ )
213
+ },
214
+ }
215
+
216
+ export const Loading: Story = {
217
+ render: () => (
218
+ <Table isLoading loadingRowCount={5}>
219
+ <TableHeader>
220
+ <TableRow isHoverable={false}>
221
+ <TableHeaderCell>Name</TableHeaderCell>
222
+ <TableHeaderCell>Email</TableHeaderCell>
223
+ <TableHeaderCell>Role</TableHeaderCell>
224
+ <TableHeaderCell>Status</TableHeaderCell>
225
+ </TableRow>
226
+ </TableHeader>
227
+ <TableBody>
228
+ <TableRow>
229
+ <TableCell>Placeholder</TableCell>
230
+ </TableRow>
231
+ </TableBody>
232
+ </Table>
233
+ ),
234
+ }
235
+
236
+ export const EmptyState: Story = {
237
+ render: () => (
238
+ <Table>
239
+ <TableHeader>
240
+ <TableRow isHoverable={false}>
241
+ <TableHeaderCell>Name</TableHeaderCell>
242
+ <TableHeaderCell>Email</TableHeaderCell>
243
+ <TableHeaderCell>Role</TableHeaderCell>
244
+ <TableHeaderCell>Status</TableHeaderCell>
245
+ </TableRow>
246
+ </TableHeader>
247
+ <TableEmptyState
248
+ title="No users found"
249
+ description="There are no users matching your current filters. Try adjusting your search criteria."
250
+ action={
251
+ <Button variant="outline" color="primary">
252
+ <ButtonText>Clear Filters</ButtonText>
253
+ </Button>
254
+ }
255
+ />
256
+ </Table>
257
+ ),
258
+ }
259
+
260
+ export const WithPagination: Story = {
261
+ render: function Render() {
262
+ const {
263
+ paginatedData,
264
+ page,
265
+ pageSize,
266
+ totalItems,
267
+ sortColumn,
268
+ sortDirection,
269
+ setPage,
270
+ setPageSize,
271
+ handleSort,
272
+ } = useTable({
273
+ data: manyUsers,
274
+ defaultPageSize: 10,
275
+ })
276
+
277
+ return (
278
+ <View>
279
+ <Table sortColumn={sortColumn} sortDirection={sortDirection} onSort={handleSort}>
280
+ <TableHeader>
281
+ <TableRow isHoverable={false}>
282
+ <TableHeaderCell sortKey="name">Name</TableHeaderCell>
283
+ <TableHeaderCell sortKey="email">Email</TableHeaderCell>
284
+ <TableHeaderCell sortKey="role">Role</TableHeaderCell>
285
+ <TableHeaderCell sortKey="status">Status</TableHeaderCell>
286
+ </TableRow>
287
+ </TableHeader>
288
+ <TableBody>
289
+ {paginatedData.map((user) => (
290
+ <TableRow key={user.id}>
291
+ <TableCell>{user.name}</TableCell>
292
+ <TableCell>{user.email}</TableCell>
293
+ <TableCell>{user.role}</TableCell>
294
+ <TableCell>{user.status}</TableCell>
295
+ </TableRow>
296
+ ))}
297
+ </TableBody>
298
+ </Table>
299
+ <TablePagination
300
+ page={page}
301
+ pageSize={pageSize}
302
+ totalItems={totalItems}
303
+ onPageChange={setPage}
304
+ onPageSizeChange={setPageSize}
305
+ />
306
+ </View>
307
+ )
308
+ },
309
+ }
310
+
311
+ export const WithColumnWidths: Story = {
312
+ render: () => (
313
+ <Table>
314
+ <TableHeader>
315
+ <TableRow isHoverable={false}>
316
+ <TableHeaderCell width={60} align="center">#</TableHeaderCell>
317
+ <TableHeaderCell width={200}>Name</TableHeaderCell>
318
+ <TableHeaderCell>Email</TableHeaderCell>
319
+ <TableHeaderCell width={120} align="center">Role</TableHeaderCell>
320
+ <TableHeaderCell width={100} align="right">Status</TableHeaderCell>
321
+ </TableRow>
322
+ </TableHeader>
323
+ <TableBody>
324
+ {sampleUsers.map((user, i) => (
325
+ <TableRow key={user.id}>
326
+ <TableCell width={60} align="center">
327
+ <Text className="text-sm text-text-secondary">{i + 1}</Text>
328
+ </TableCell>
329
+ <TableCell width={200}>
330
+ <Text className="text-sm text-text-primary font-medium">{user.name}</Text>
331
+ </TableCell>
332
+ <TableCell>{user.email}</TableCell>
333
+ <TableCell width={120} align="center">{user.role}</TableCell>
334
+ <TableCell width={100} align="right">{user.status}</TableCell>
335
+ </TableRow>
336
+ ))}
337
+ </TableBody>
338
+ </Table>
339
+ ),
340
+ }
341
+
342
+ export const UseTableHook: Story = {
343
+ render: function Render() {
344
+ const {
345
+ paginatedData,
346
+ page,
347
+ pageSize,
348
+ totalItems,
349
+ sortColumn,
350
+ sortDirection,
351
+ setPage,
352
+ setPageSize,
353
+ handleSort,
354
+ } = useTable({
355
+ data: manyUsers,
356
+ defaultPageSize: 10,
357
+ defaultSortColumn: 'name',
358
+ defaultSortDirection: 'asc',
359
+ })
360
+
361
+ return (
362
+ <View style={{ gap: 8 }}>
363
+ <Text className="text-sm text-text-secondary">
364
+ Showing {paginatedData.length} of {totalItems} users, sorted by{' '}
365
+ {sortColumn ?? 'none'} ({sortDirection ?? 'none'})
366
+ </Text>
367
+ <Table sortColumn={sortColumn} sortDirection={sortDirection} onSort={handleSort}>
368
+ <TableHeader>
369
+ <TableRow isHoverable={false}>
370
+ <TableHeaderCell sortKey="name">Name</TableHeaderCell>
371
+ <TableHeaderCell sortKey="email">Email</TableHeaderCell>
372
+ <TableHeaderCell sortKey="role">Role</TableHeaderCell>
373
+ <TableHeaderCell sortKey="status">Status</TableHeaderCell>
374
+ </TableRow>
375
+ </TableHeader>
376
+ <TableBody>
377
+ {paginatedData.map((user) => (
378
+ <TableRow key={user.id}>
379
+ <TableCell>{user.name}</TableCell>
380
+ <TableCell>{user.email}</TableCell>
381
+ <TableCell>{user.role}</TableCell>
382
+ <TableCell>{user.status}</TableCell>
383
+ </TableRow>
384
+ ))}
385
+ </TableBody>
386
+ </Table>
387
+ <TablePagination
388
+ page={page}
389
+ pageSize={pageSize}
390
+ totalItems={totalItems}
391
+ onPageChange={setPage}
392
+ onPageSizeChange={setPageSize}
393
+ />
394
+ </View>
395
+ )
396
+ },
397
+ }
398
+
399
+ export const SortingAndSelection: Story = {
400
+ render: function Render() {
401
+ const [selectedRows, setSelectedRows] = useState<Set<string>>(new Set())
402
+
403
+ const {
404
+ paginatedData,
405
+ page,
406
+ pageSize,
407
+ totalItems,
408
+ sortColumn,
409
+ sortDirection,
410
+ setPage,
411
+ setPageSize,
412
+ handleSort,
413
+ } = useTable({
414
+ data: manyUsers,
415
+ defaultPageSize: 10,
416
+ })
417
+
418
+ const handleSelectRow = (id: string, selected: boolean) => {
419
+ setSelectedRows((prev) => {
420
+ const next = new Set(prev)
421
+ if (selected) {
422
+ next.add(id)
423
+ } else {
424
+ next.delete(id)
425
+ }
426
+ return next
427
+ })
428
+ }
429
+
430
+ const handleSelectAll = (selected: boolean) => {
431
+ const currentPageIds = paginatedData.map((u) => u.id)
432
+ setSelectedRows(selected ? new Set(currentPageIds) : new Set())
433
+ }
434
+
435
+ return (
436
+ <View style={{ gap: 8 }}>
437
+ <Text className="text-sm text-text-secondary">
438
+ Selected: {selectedRows.size} rows
439
+ </Text>
440
+ <Table
441
+ sortColumn={sortColumn}
442
+ sortDirection={sortDirection}
443
+ onSort={handleSort}
444
+ selectable
445
+ selectedRows={selectedRows}
446
+ onSelectRow={handleSelectRow}
447
+ onSelectAll={handleSelectAll}
448
+ rowIds={paginatedData.map((u) => u.id)}
449
+ >
450
+ <TableHeader>
451
+ <TableRow isHoverable={false}>
452
+ <TableSelectAllCell />
453
+ <TableHeaderCell sortKey="name">Name</TableHeaderCell>
454
+ <TableHeaderCell sortKey="email">Email</TableHeaderCell>
455
+ <TableHeaderCell sortKey="role">Role</TableHeaderCell>
456
+ <TableHeaderCell sortKey="status">Status</TableHeaderCell>
457
+ </TableRow>
458
+ </TableHeader>
459
+ <TableBody>
460
+ {paginatedData.map((user) => (
461
+ <TableRow key={user.id} isSelected={selectedRows.has(user.id)}>
462
+ <TableSelectCell rowId={user.id} />
463
+ <TableCell>{user.name}</TableCell>
464
+ <TableCell>{user.email}</TableCell>
465
+ <TableCell>{user.role}</TableCell>
466
+ <TableCell>{user.status}</TableCell>
467
+ </TableRow>
468
+ ))}
469
+ </TableBody>
470
+ </Table>
471
+ <TablePagination
472
+ page={page}
473
+ pageSize={pageSize}
474
+ totalItems={totalItems}
475
+ onPageChange={setPage}
476
+ onPageSizeChange={setPageSize}
477
+ />
478
+ </View>
479
+ )
480
+ },
481
+ }