aural-ui 4.0.1 → 4.2.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/README.md +8 -1
- package/dist/components/aspect-ratio/AspectRatio.stories.tsx +290 -1228
- package/dist/components/avatar/Avatar.stories.tsx +219 -235
- package/dist/components/badge/Badge.stories.tsx +379 -116
- package/dist/components/banner/Banner.stories.tsx +445 -391
- package/dist/components/breadcrumb/Breadcrumb.stories.tsx +453 -199
- package/dist/components/button/Button.stories.tsx +585 -230
- package/dist/components/card/Card.stories.tsx +619 -301
- package/dist/components/char-count/CharCount.stories.tsx +350 -248
- package/dist/components/checkbox/Checkbox.stories.tsx +309 -167
- package/dist/components/chip/Chip.stories.tsx +362 -168
- package/dist/components/circular-loader/CircularLoader.stories.tsx +221 -636
- package/dist/components/clamp-lines/ClampLines.stories.tsx +246 -117
- package/dist/components/collapsible/Collapsible.stories.tsx +391 -252
- package/dist/components/command/Command.stories.tsx +530 -867
- package/dist/components/dialog/Dialog.stories.tsx +501 -950
- package/dist/components/divider/Divider.stories.tsx +264 -527
- package/dist/components/dot-loader/DotLoader.stories.tsx +256 -257
- package/dist/components/drawer/Drawer.stories.tsx +659 -1023
- package/dist/components/dropdown/Dropdown.stories.tsx +643 -1028
- package/dist/components/form/Form.stories.tsx +560 -274
- package/dist/components/helper-text/HelperText.stories.tsx +199 -200
- package/dist/components/hover-card/HoverCard.stories.tsx +318 -1254
- package/dist/components/icon-button/IconButton.stories.tsx +837 -194
- package/dist/components/if-else/if-else.stories.tsx +370 -83
- package/dist/components/input/Input.stories.tsx +436 -368
- package/dist/components/label/Label.stories.tsx +156 -154
- package/dist/components/list/List.stories.tsx +484 -835
- package/dist/components/marquee/Marquee.stories.tsx +356 -712
- package/dist/components/otp-inputs/OtpInputs.stories.tsx +352 -422
- package/dist/components/overlay/Overlay.stories.tsx +452 -824
- package/dist/components/pagination/Pagination.stories.tsx +721 -210
- package/dist/components/popover/Popover.stories.tsx +481 -896
- package/dist/components/radio/Radio.stories.tsx +432 -124
- package/dist/components/resizable/Resizable.stories.tsx +495 -799
- package/dist/components/scroll-area/ScrollArea.stories.tsx +383 -1059
- package/dist/components/search/Search.stories.tsx +312 -595
- package/dist/components/select/Select.stories.tsx +684 -789
- package/dist/components/sheet/Sheet.stories.tsx +671 -950
- package/dist/components/skelton/Skelton.stories.tsx +230 -764
- package/dist/components/slider/Slider.stories.tsx +383 -760
- package/dist/components/stepper/Stepper.stories.tsx +371 -514
- package/dist/components/switch/Switch.stories.tsx +461 -208
- package/dist/components/switch-case/SwitchCase.stories.tsx +367 -188
- package/dist/components/table/Table.stories.tsx +770 -916
- package/dist/components/tabs/Tabs.stories.tsx +458 -1455
- package/dist/components/tag/Tag.stories.tsx +714 -542
- package/dist/components/textarea/TextArea.stories.tsx +621 -562
- package/dist/components/thumbnail-tags/ThumbnailTags.stories.tsx +228 -154
- package/dist/components/toast/Toast.stories.tsx +452 -1339
- package/dist/components/toggle/Toggle.stories.tsx +488 -931
- package/dist/components/tooltip/Tooltip.stories.tsx +344 -1388
- package/dist/components/typography/Typography.stories.tsx +406 -89
- package/dist/hooks/use-change-state/UseChangeState.stories.tsx +309 -606
- package/dist/hooks/use-previous/UsePrevious.stories.tsx +367 -917
- package/dist/hooks/use-standalone-pagination/UseStandalonePagination.stories.tsx +639 -867
- package/dist/icons/Icons.stories.tsx +0 -12
- package/dist/icons/ai-avatar-icon/AiAvatarIcon.stories.tsx +223 -1060
- package/dist/icons/alert-icon/AlertIcon.stories.tsx +106 -968
- package/dist/icons/all-icons.tsx +37 -16
- package/dist/icons/angle-down-icon/AngleDownIcon.stories.tsx +137 -1010
- package/dist/icons/apple-logo-icon/AppleLogoIcon.stories.tsx +145 -935
- package/dist/icons/arrow-box-left-icon/ArrowBoxLeftIcon.stories.tsx +132 -1046
- package/dist/icons/arrow-corner-up-left-icon/ArrowCornerUpLeftIcon.stories.tsx +134 -986
- package/dist/icons/arrow-corner-up-right-icon/ArrowCornerUpRightIcon.stories.tsx +135 -1028
- package/dist/icons/arrow-left-icon/ArrowLeftIcon.stories.tsx +133 -971
- package/dist/icons/arrow-right-icon/ArrowRightIcon.stories.tsx +145 -1123
- package/dist/icons/arrow-right-up-icon/ArrowRightUpIcon.stories.tsx +143 -1252
- package/dist/icons/art-board-icon/ArtBoardIcon.stories.tsx +123 -632
- package/dist/icons/audio-bar-icon/AudioBarIcon.stories.tsx +141 -1223
- package/dist/icons/backward-ten-seconds-icon/BackwardTenSecondsIcon.stories.tsx +164 -1018
- package/dist/icons/bubble-check-icon/BubbleCheckIcon.stories.tsx +121 -1236
- package/dist/icons/bubble-crossed-icon/BubbleCrossedIcon.stories.tsx +121 -1213
- package/dist/icons/bubble-sparkle-icon/BubbleSparkleIcon.stories.tsx +116 -893
- package/dist/icons/camera-icon/CameraIcon.stories.tsx +109 -1254
- package/dist/icons/capital-a-letter-icon/CapitalALetterIcon.stories.tsx +114 -975
- package/dist/icons/chevron-double-left-icon/ChevronDoubleLeftIcon.stories.tsx +157 -994
- package/dist/icons/chevron-double-right-icon/ChevronDoubleRightIcon.stories.tsx +160 -992
- package/dist/icons/chevron-down-icon/ChevronDownIcon.stories.tsx +140 -970
- package/dist/icons/chevron-left-icon/ChevronLeftIcon.stories.tsx +126 -993
- package/dist/icons/chevron-right-icon/ChevronRightIcon.stories.tsx +144 -987
- package/dist/icons/chevron-up-icon/ChevronUpIcon.stories.tsx +141 -1007
- package/dist/icons/circle-tick-icon/CircleTickIcon.stories.tsx +147 -1187
- package/dist/icons/circular-play-icon/CircularPlayIcon.stories.tsx +110 -476
- package/dist/icons/coin-icon/CoinIcon.stories.tsx +120 -1364
- package/dist/icons/coin-toons-icon/CoinToonsIcon.stories.tsx +113 -1360
- package/dist/icons/column-wide-add-icon/ColumnWideAddIcon.stories.tsx +111 -942
- package/dist/icons/command-icon/CommandIcon.stories.tsx +124 -1087
- package/dist/icons/copy-icon/CopyIcon.stories.tsx +119 -996
- package/dist/icons/cross-circle-icon/CrossCircleIcon.stories.tsx +144 -1046
- package/dist/icons/cross-icon/CrossIcon.stories.tsx +136 -999
- package/dist/icons/download-icon/DownloadIcon.stories.tsx +123 -857
- package/dist/icons/edit-big-icon/EditBigIcon.stories.tsx +121 -1080
- package/dist/icons/email-icon/EmailIcon.stories.tsx +112 -979
- package/dist/icons/expand-icon/ExpandIcon.stories.tsx +109 -1146
- package/dist/icons/eye-close-icon/EyeCloseIcon.stories.tsx +141 -1068
- package/dist/icons/eye-open-icon/EyeOpenIcon.stories.tsx +140 -1081
- package/dist/icons/feature-shine-icon/FeatureShineIcon.stories.tsx +124 -1050
- package/dist/icons/file-chart-icon/FileChartIcon.stories.tsx +123 -1091
- package/dist/icons/file-text-icon/FileTextIcon.stories.tsx +122 -633
- package/dist/icons/filter-bar-row-icon/FilterBarRowIcon.stories.tsx +116 -1087
- package/dist/icons/forward-ten-seconds-icon/ForwardTenSecondsIcon.stories.tsx +166 -1020
- package/dist/icons/git-branch-icon/GitBranchIcon.stories.tsx +112 -1182
- package/dist/icons/git-fork-icon/GitForkIcon.stories.tsx +112 -1155
- package/dist/icons/globe-icon/GlobeIcon.stories.tsx +127 -325
- package/dist/icons/google-logo-icon/GoogleLogoIcon.stories.tsx +142 -985
- package/dist/icons/grip-vertical-icon/GripVerticalIcon.stories.tsx +116 -1217
- package/dist/icons/head-icon/HeadIcon.stories.tsx +108 -953
- package/dist/icons/heart-icon/HeartIcon.stories.tsx +117 -1060
- package/dist/icons/image-avatar-sparkle-icon/ImageAvatarSparkleIcon.stories.tsx +116 -716
- package/dist/icons/image-icon/ImageIcon.stories.tsx +102 -1164
- package/dist/icons/import-folder-icon/ImportFolderIcon.stories.tsx +108 -1233
- package/dist/icons/import-left-arrow-folder-icon/ImportLeftArrowFolderIcon.stories.tsx +133 -1289
- package/dist/icons/indian-flag-icon/IndianFlagIcon.stories.tsx +155 -1012
- package/dist/icons/instagram-icon/InstagramIcon.stories.tsx +158 -1438
- package/dist/icons/layout-column-icon/LayoutColumnIcon.stories.tsx +121 -1011
- package/dist/icons/layout-left-icon/LayoutLeftIcon.stories.tsx +116 -981
- package/dist/icons/layout-right-icon/LayoutRightIcon.stories.tsx +116 -979
- package/dist/icons/light-bulb-simple-icon/LightBulbSimpleIcon.stories.tsx +105 -1252
- package/dist/icons/linked-in-icon/LinkedInIcon.stories.tsx +151 -1554
- package/dist/icons/magic-book-icon/MagicBookIcon.stories.tsx +107 -1227
- package/dist/icons/magic-edit-icon/MagicEditIcon.stories.tsx +116 -707
- package/dist/icons/maintenance-icon/MaintenanceIcon.stories.tsx +119 -1226
- package/dist/icons/message-icon/MessageIcon.stories.tsx +111 -557
- package/dist/icons/minimize-icon/MinimizeIcon.stories.tsx +112 -1198
- package/dist/icons/moon-icon/MoonIcon.stories.tsx +117 -557
- package/dist/icons/move-horizontal-icon/MoveHorizontalIcon.stories.tsx +106 -1235
- package/dist/icons/move-vertical-icon/MoveVerticalIcon.stories.tsx +112 -1185
- package/dist/icons/musical-note-icon/MusicalNoteIcon.stories.tsx +116 -1012
- package/dist/icons/notepad-icon/NotepadIcon.stories.tsx +108 -1137
- package/dist/icons/notes-icon/NotesIcon.stories.tsx +116 -1138
- package/dist/icons/page-search-icon/PageSearchIcon.stories.tsx +106 -1146
- package/dist/icons/page-text-icon/PageTextIcon.stories.tsx +119 -719
- package/dist/icons/paint-roll-icon/PaintRollIcon.stories.tsx +110 -999
- package/dist/icons/paper-plane-icon/PaperPlaneIcon.stories.tsx +109 -912
- package/dist/icons/pause-icon/PauseIcon.stories.tsx +110 -1041
- package/dist/icons/pencil-icon/PencilIcon.stories.tsx +112 -1109
- package/dist/icons/phone-icon/PhoneIcon.stories.tsx +112 -1023
- package/dist/icons/plus-icon/PlusIcon.stories.tsx +103 -1132
- package/dist/icons/pocket-studio-icon/PocketStudioIcon.stories.tsx +104 -870
- package/dist/icons/scroll-down-icon/ScrollDownIcon.stories.tsx +99 -476
- package/dist/icons/search-icon/SearchIcon.stories.tsx +108 -1161
- package/dist/icons/setting-icon/SettingIcon.stories.tsx +104 -1009
- package/dist/icons/share-icon/ShareIcon.stories.tsx +117 -1064
- package/dist/icons/shield-icon/ShieldIcon.stories.tsx +114 -974
- package/dist/icons/site-logo-icon/SiteLogoIcon.stories.tsx +134 -1160
- package/dist/icons/skip-backward-icon/SkipBackwardIcon.stories.tsx +169 -1017
- package/dist/icons/skip-forward-icon/SkipForwardIcon.stories.tsx +161 -1016
- package/dist/icons/sparkles-soft-icon/SparklesSoftIcon.stories.tsx +102 -1001
- package/dist/icons/spinner-gradient-icon/SpinnerGradientIcon.stories.tsx +155 -593
- package/dist/icons/spinner-solid-icon/SpinnerSolidIcon.stories.tsx +155 -608
- package/dist/icons/spinner-solid-neutral-icon/SpinnerSolidINeutralcon.stories.tsx +142 -712
- package/dist/icons/star-icon/StarIcon.stories.tsx +120 -946
- package/dist/icons/store-coin-icon/StoreCoinIcon.stories.tsx +109 -1013
- package/dist/icons/suggestion-icon/SuggestionIcon.stories.tsx +113 -891
- package/dist/icons/sun-icon/SunIcon.stories.tsx +117 -864
- package/dist/icons/text-color-icon/TextColorIcon.stories.tsx +113 -989
- package/dist/icons/text-indicator-icon/TextIndicatorIcon.stories.tsx +120 -1027
- package/dist/icons/threads-icon/ThreadsIcon.stories.tsx +153 -1476
- package/dist/icons/tick-circle-icon/TickCircleIcon.stories.tsx +143 -1187
- package/dist/icons/tick-icon/TickIcon.stories.tsx +142 -1322
- package/dist/icons/trash-icon/TrashIcon.stories.tsx +105 -970
- package/dist/icons/twitter-x-icon/TwitterXIcon.stories.tsx +154 -1457
- package/dist/icons/upload-icon/UploadIcon.stories.tsx +112 -930
- package/dist/icons/vertical-menu-icon/VerticalMenuIcon.stories.tsx +115 -1019
- package/dist/icons/video-play-list-icon/VideoPlaylistIcon.stories.tsx +122 -1092
- package/dist/icons/voice-playing-icon/VoicePlayingIcon.stories.tsx +120 -1401
- package/dist/icons/volume-full-icon/VolumeFullIcon.stories.tsx +107 -1212
- package/dist/icons/volume-half-icon/VolumeHalfIcon.stories.tsx +109 -1122
- package/dist/icons/volume-off-icon/VolumeOffIcon.stories.tsx +112 -1124
- package/dist/icons/warning-icon/WarningIcon.stories.tsx +119 -1083
- package/dist/icons/youtube-icon/YoutubeIcon.stories.tsx +158 -983
- package/dist/index.cjs +90 -90
- package/dist/index.js +90 -90
- package/package.json +8 -3
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import React from "react"
|
|
2
2
|
import { Badge } from "@components/badge"
|
|
3
|
-
import { Button } from "@components/button"
|
|
4
3
|
import { Checkbox } from "@components/checkbox"
|
|
5
4
|
import {
|
|
6
5
|
ChevronDownIcon,
|
|
7
6
|
ChevronUpIcon,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
VerticalMenuIcon,
|
|
7
|
+
HeartIcon,
|
|
8
|
+
MusicalNoteIcon,
|
|
9
|
+
PauseIcon,
|
|
12
10
|
} from "@icons/index"
|
|
13
11
|
import type { Meta, StoryObj } from "@storybook/react-vite"
|
|
14
12
|
|
|
13
|
+
import { AuralComponentDocsPage } from "src/ui/story-spec/components/component-story-docs-page"
|
|
14
|
+
|
|
15
15
|
import {
|
|
16
16
|
Table,
|
|
17
17
|
TableBody,
|
|
@@ -28,210 +28,36 @@ const meta: Meta<typeof Table> = {
|
|
|
28
28
|
component: Table,
|
|
29
29
|
parameters: {
|
|
30
30
|
layout: "fullscreen",
|
|
31
|
-
backgrounds: {
|
|
32
|
-
default: "dark",
|
|
33
|
-
values: [
|
|
34
|
-
{ name: "dark", value: "#0a0a0a" },
|
|
35
|
-
{ name: "light", value: "#ffffff" },
|
|
36
|
-
],
|
|
37
|
-
},
|
|
38
31
|
docs: {
|
|
39
32
|
description: {
|
|
40
|
-
component:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
A comprehensive table component system built with semantic HTML elements and design system integration.
|
|
44
|
-
|
|
45
|
-
## Components
|
|
46
|
-
|
|
47
|
-
### Core Components
|
|
48
|
-
- **Table**: Root table wrapper with overflow handling
|
|
49
|
-
- **TableHeader**: Table header (\`<thead>\`) element
|
|
50
|
-
- **TableBody**: Table body (\`<tbody>\`) element
|
|
51
|
-
- **TableFooter**: Table footer (\`<tfoot>\`) element
|
|
52
|
-
- **TableRow**: Table row (\`<tr>\`) with hover and selection states
|
|
53
|
-
- **TableHead**: Table header cell (\`<th>\`) with design system styling
|
|
54
|
-
- **TableCell**: Table data cell (\`<td>\`) with consistent typography
|
|
55
|
-
- **TableCaption**: Table caption with tertiary label styling
|
|
56
|
-
|
|
57
|
-
## Features
|
|
58
|
-
|
|
59
|
-
- **Responsive Design**: Horizontal scroll on smaller screens
|
|
60
|
-
- **Interactive Rows**: Hover effects and selection states
|
|
61
|
-
- **Checkbox Integration**: Built-in support for row selection
|
|
62
|
-
- **Typography System**: Consistent text styling with design tokens
|
|
63
|
-
- **Semantic HTML**: Proper table structure for accessibility
|
|
64
|
-
- **Design System**: Integrated with FM design tokens
|
|
65
|
-
- **Customizable**: All components accept className for customization
|
|
66
|
-
- **Data Slots**: Component identification with data-slot attributes
|
|
67
|
-
|
|
68
|
-
## Styling
|
|
69
|
-
|
|
70
|
-
### Table Structure
|
|
71
|
-
- **Background**: \`bg-fm-surface-primary\` - Primary surface background
|
|
72
|
-
- **Container**: Responsive overflow with \`overflow-x-auto\`
|
|
73
|
-
- **Layout**: Full width with \`w-full\`
|
|
74
|
-
|
|
75
|
-
### TableHead Styling
|
|
76
|
-
- **Typography**: Brand font with small leading
|
|
77
|
-
- **Color**: \`text-fm-tertiary\` - Tertiary text color
|
|
78
|
-
- **Text**: Uppercase, left-aligned, nowrap
|
|
79
|
-
- **Size**: \`--text-fm-sm\` design token
|
|
80
|
-
- **Padding**: \`p-4\` consistent spacing
|
|
81
|
-
|
|
82
|
-
### TableCell Styling
|
|
83
|
-
- **Typography**: Text font with medium leading
|
|
84
|
-
- **Color**: \`text-fm-primary\` - Primary text color
|
|
85
|
-
- **Text**: Left-aligned, nowrap
|
|
86
|
-
- **Size**: \`--text-fm-md\` design token
|
|
87
|
-
- **Padding**: \`p-4\` consistent spacing
|
|
88
|
-
|
|
89
|
-
### TableRow Interactions
|
|
90
|
-
- **Hover**: \`hover:bg-fm-surface-frosted/30\` - Subtle frosted effect
|
|
91
|
-
- **Selected**: \`data-[state=selected]:bg-fm-surface-frosted/50\` - Stronger selection state
|
|
92
|
-
- **Transition**: Smooth color transitions
|
|
93
|
-
|
|
94
|
-
### Checkbox Integration
|
|
95
|
-
- **Positioning**: Automatic translation for alignment
|
|
96
|
-
- **Padding**: Reduced right padding for checkbox columns
|
|
97
|
-
- **Accessibility**: Proper role attributes maintained
|
|
98
|
-
|
|
99
|
-
## Usage Examples
|
|
100
|
-
|
|
101
|
-
### Basic Table
|
|
102
|
-
\`\`\`tsx
|
|
103
|
-
<Table>
|
|
104
|
-
<TableHeader>
|
|
105
|
-
<TableRow>
|
|
106
|
-
<TableHead>Name</TableHead>
|
|
107
|
-
<TableHead>Email</TableHead>
|
|
108
|
-
<TableHead>Role</TableHead>
|
|
109
|
-
</TableRow>
|
|
110
|
-
</TableHeader>
|
|
111
|
-
<TableBody>
|
|
112
|
-
<TableRow>
|
|
113
|
-
<TableCell>John Doe</TableCell>
|
|
114
|
-
<TableCell>john@example.com</TableCell>
|
|
115
|
-
<TableCell>Admin</TableCell>
|
|
116
|
-
</TableRow>
|
|
117
|
-
</TableBody>
|
|
118
|
-
</Table>
|
|
119
|
-
\`\`\`
|
|
120
|
-
|
|
121
|
-
### With Selection
|
|
122
|
-
\`\`\`tsx
|
|
123
|
-
<Table>
|
|
124
|
-
<TableHeader>
|
|
125
|
-
<TableRow>
|
|
126
|
-
<TableHead>
|
|
127
|
-
<Checkbox />
|
|
128
|
-
</TableHead>
|
|
129
|
-
<TableHead>Name</TableHead>
|
|
130
|
-
<TableHead>Status</TableHead>
|
|
131
|
-
</TableRow>
|
|
132
|
-
</TableHeader>
|
|
133
|
-
<TableBody>
|
|
134
|
-
<TableRow data-state="selected">
|
|
135
|
-
<TableCell>
|
|
136
|
-
<Checkbox checked />
|
|
137
|
-
</TableCell>
|
|
138
|
-
<TableCell>Selected Row</TableCell>
|
|
139
|
-
<TableCell>Active</TableCell>
|
|
140
|
-
</TableRow>
|
|
141
|
-
</TableBody>
|
|
142
|
-
</Table>
|
|
143
|
-
\`\`\`
|
|
144
|
-
|
|
145
|
-
### With Actions
|
|
146
|
-
\`\`\`tsx
|
|
147
|
-
<Table>
|
|
148
|
-
<TableBody>
|
|
149
|
-
<TableRow>
|
|
150
|
-
<TableCell>Data</TableCell>
|
|
151
|
-
<TableCell>
|
|
152
|
-
<div className="flex gap-2">
|
|
153
|
-
<Button size="sm" variant="ghost">Edit</Button>
|
|
154
|
-
<Button size="sm" variant="ghost">Delete</Button>
|
|
155
|
-
</div>
|
|
156
|
-
</TableCell>
|
|
157
|
-
</TableRow>
|
|
158
|
-
</TableBody>
|
|
159
|
-
</Table>
|
|
160
|
-
\`\`\`
|
|
161
|
-
|
|
162
|
-
### With Caption and Footer
|
|
163
|
-
\`\`\`tsx
|
|
164
|
-
<Table>
|
|
165
|
-
<TableCaption>User management table</TableCaption>
|
|
166
|
-
<TableHeader>
|
|
167
|
-
<TableRow>
|
|
168
|
-
<TableHead>Name</TableHead>
|
|
169
|
-
<TableHead>Count</TableHead>
|
|
170
|
-
</TableRow>
|
|
171
|
-
</TableHeader>
|
|
172
|
-
<TableBody>
|
|
173
|
-
<TableRow>
|
|
174
|
-
<TableCell>Users</TableCell>
|
|
175
|
-
<TableCell>150</TableCell>
|
|
176
|
-
</TableRow>
|
|
177
|
-
</TableBody>
|
|
178
|
-
<TableFooter>
|
|
179
|
-
<TableRow>
|
|
180
|
-
<TableCell>Total</TableCell>
|
|
181
|
-
<TableCell>150</TableCell>
|
|
182
|
-
</TableRow>
|
|
183
|
-
</TableFooter>
|
|
184
|
-
</Table>
|
|
185
|
-
\`\`\`
|
|
186
|
-
|
|
187
|
-
## Accessibility
|
|
188
|
-
|
|
189
|
-
- **Semantic HTML**: Proper table structure with thead, tbody, tfoot
|
|
190
|
-
- **ARIA Support**: Compatible with screen readers
|
|
191
|
-
- **Keyboard Navigation**: Standard table navigation
|
|
192
|
-
- **Role Attributes**: Proper checkbox roles maintained
|
|
193
|
-
- **Focus Management**: Consistent focus indicators
|
|
194
|
-
- **Screen Reader**: Table structure announced properly
|
|
195
|
-
|
|
196
|
-
## Design Tokens
|
|
197
|
-
|
|
198
|
-
### Typography
|
|
199
|
-
- \`font-fm-brand\`: Header typography
|
|
200
|
-
- \`font-fm-text\`: Cell typography
|
|
201
|
-
- \`leading-fm-sm\`: Small line height for headers
|
|
202
|
-
- \`leading-fm-md\`: Medium line height for cells
|
|
203
|
-
- \`leading-fm-xs\`: Extra small line height for captions
|
|
204
|
-
|
|
205
|
-
### Colors
|
|
206
|
-
- \`text-fm-tertiary\`: Header text color
|
|
207
|
-
- \`text-fm-primary\`: Cell text color
|
|
208
|
-
- \`text-fm-label-tertiary\`: Caption text color
|
|
209
|
-
- \`bg-fm-surface-primary\`: Table background
|
|
210
|
-
- \`bg-fm-surface-frosted\`: Hover and selection states
|
|
211
|
-
|
|
212
|
-
### Sizing
|
|
213
|
-
- \`--text-fm-sm\`: Header font size
|
|
214
|
-
- \`--text-fm-md\`: Cell font size
|
|
215
|
-
- \`--text-fm-xs\`: Caption font size
|
|
216
|
-
|
|
217
|
-
## Best Practices
|
|
218
|
-
|
|
219
|
-
1. **Always use TableHeader for headers** - Semantic structure
|
|
220
|
-
2. **Include TableCaption for complex tables** - Accessibility
|
|
221
|
-
3. **Use data-state="selected" for selection** - Consistent styling
|
|
222
|
-
4. **Wrap interactive elements properly** - Button integration
|
|
223
|
-
5. **Consider responsive behavior** - Horizontal scroll handling
|
|
224
|
-
6. **Maintain consistent spacing** - Use default padding
|
|
225
|
-
7. **Follow typography hierarchy** - Headers vs cells distinction
|
|
226
|
-
`,
|
|
33
|
+
component:
|
|
34
|
+
"A semantic compound table for structured data. Composed of Table (root), TableHeader (thead), TableBody (tbody), TableFooter (tfoot), TableRow (tr), TableHead (th), TableCell (td), and TableCaption. Designed for audio app contexts: track listings, album catalogs, queue management, and analytics. Supports row selection states, sortable headers, dense layouts, and responsive horizontal scrolling.",
|
|
227
35
|
},
|
|
36
|
+
page: () => (
|
|
37
|
+
<AuralComponentDocsPage
|
|
38
|
+
features={[
|
|
39
|
+
{
|
|
40
|
+
title: "Semantic Parts",
|
|
41
|
+
description: "thead, tbody, tfoot",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
title: "Sortable Headers",
|
|
45
|
+
description: "Click to sort columns",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
title: "Row Selection",
|
|
49
|
+
description: "Checkbox select rows",
|
|
50
|
+
},
|
|
51
|
+
]}
|
|
52
|
+
/>
|
|
53
|
+
),
|
|
228
54
|
},
|
|
229
55
|
},
|
|
230
56
|
tags: ["autodocs"],
|
|
231
57
|
argTypes: {
|
|
232
58
|
className: {
|
|
233
59
|
control: "text",
|
|
234
|
-
description: "Additional CSS classes
|
|
60
|
+
description: "Additional CSS classes applied to the table root",
|
|
235
61
|
},
|
|
236
62
|
},
|
|
237
63
|
}
|
|
@@ -239,451 +65,358 @@ A comprehensive table component system built with semantic HTML elements and des
|
|
|
239
65
|
export default meta
|
|
240
66
|
type Story = StoryObj<typeof Table>
|
|
241
67
|
|
|
242
|
-
//
|
|
243
|
-
const sampleUsers = [
|
|
244
|
-
{
|
|
245
|
-
id: 1,
|
|
246
|
-
name: "John Doe",
|
|
247
|
-
email: "john.doe@example.com",
|
|
248
|
-
role: "Admin",
|
|
249
|
-
status: "Active",
|
|
250
|
-
lastSeen: "2 minutes ago",
|
|
251
|
-
avatar: "JD",
|
|
252
|
-
},
|
|
253
|
-
{
|
|
254
|
-
id: 2,
|
|
255
|
-
name: "Jane Smith",
|
|
256
|
-
email: "jane.smith@example.com",
|
|
257
|
-
role: "Editor",
|
|
258
|
-
status: "Active",
|
|
259
|
-
lastSeen: "1 hour ago",
|
|
260
|
-
avatar: "JS",
|
|
261
|
-
},
|
|
262
|
-
{
|
|
263
|
-
id: 3,
|
|
264
|
-
name: "Bob Johnson",
|
|
265
|
-
email: "bob.johnson@example.com",
|
|
266
|
-
role: "Viewer",
|
|
267
|
-
status: "Inactive",
|
|
268
|
-
lastSeen: "3 days ago",
|
|
269
|
-
avatar: "BJ",
|
|
270
|
-
},
|
|
271
|
-
{
|
|
272
|
-
id: 4,
|
|
273
|
-
name: "Alice Brown",
|
|
274
|
-
email: "alice.brown@example.com",
|
|
275
|
-
role: "Editor",
|
|
276
|
-
status: "Active",
|
|
277
|
-
lastSeen: "30 minutes ago",
|
|
278
|
-
avatar: "AB",
|
|
279
|
-
},
|
|
280
|
-
{
|
|
281
|
-
id: 5,
|
|
282
|
-
name: "Charlie Wilson",
|
|
283
|
-
email: "charlie.wilson@example.com",
|
|
284
|
-
role: "Admin",
|
|
285
|
-
status: "Active",
|
|
286
|
-
lastSeen: "Just now",
|
|
287
|
-
avatar: "CW",
|
|
288
|
-
},
|
|
289
|
-
]
|
|
290
|
-
|
|
291
|
-
const sampleProjects = [
|
|
292
|
-
{
|
|
293
|
-
id: 1,
|
|
294
|
-
name: "Website Redesign",
|
|
295
|
-
status: "In Progress",
|
|
296
|
-
priority: "High",
|
|
297
|
-
assignee: "John Doe",
|
|
298
|
-
dueDate: "Dec 15, 2024",
|
|
299
|
-
progress: 75,
|
|
300
|
-
},
|
|
301
|
-
{
|
|
302
|
-
id: 2,
|
|
303
|
-
name: "Mobile App",
|
|
304
|
-
status: "Planning",
|
|
305
|
-
priority: "Medium",
|
|
306
|
-
assignee: "Jane Smith",
|
|
307
|
-
dueDate: "Jan 20, 2025",
|
|
308
|
-
progress: 25,
|
|
309
|
-
},
|
|
310
|
-
{
|
|
311
|
-
id: 3,
|
|
312
|
-
name: "API Integration",
|
|
313
|
-
status: "Completed",
|
|
314
|
-
priority: "High",
|
|
315
|
-
assignee: "Bob Johnson",
|
|
316
|
-
dueDate: "Nov 30, 2024",
|
|
317
|
-
progress: 100,
|
|
318
|
-
},
|
|
319
|
-
{
|
|
320
|
-
id: 4,
|
|
321
|
-
name: "Documentation",
|
|
322
|
-
status: "In Progress",
|
|
323
|
-
priority: "Low",
|
|
324
|
-
assignee: "Alice Brown",
|
|
325
|
-
dueDate: "Dec 31, 2024",
|
|
326
|
-
progress: 50,
|
|
327
|
-
},
|
|
328
|
-
]
|
|
68
|
+
// ─── 1. Parts ─────────────────────────────────────────────────────────────────
|
|
329
69
|
|
|
330
|
-
|
|
331
|
-
export const Basic: Story = {
|
|
70
|
+
export const Parts: Story = {
|
|
332
71
|
render: () => (
|
|
333
|
-
<div className="p-8">
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
</TableHeader>
|
|
343
|
-
<TableBody>
|
|
344
|
-
{sampleUsers.slice(0, 3).map((user) => (
|
|
345
|
-
<TableRow key={user.id}>
|
|
346
|
-
<TableCell>{user.name}</TableCell>
|
|
347
|
-
<TableCell>{user.email}</TableCell>
|
|
348
|
-
<TableCell>{user.role}</TableCell>
|
|
72
|
+
<div className="space-y-10 p-8">
|
|
73
|
+
{/* Table */}
|
|
74
|
+
<div className="space-y-4">
|
|
75
|
+
<h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
|
|
76
|
+
Table — root container
|
|
77
|
+
</h4>
|
|
78
|
+
<Table>
|
|
79
|
+
<TableBody>
|
|
80
|
+
<TableRow>
|
|
349
81
|
<TableCell>
|
|
350
|
-
|
|
351
|
-
color={user.status === "Active" ? "positive" : "neutral"}
|
|
352
|
-
>
|
|
353
|
-
{user.status}
|
|
354
|
-
</Badge>
|
|
82
|
+
Root container wraps a scrollable div + semantic table element.
|
|
355
83
|
</TableCell>
|
|
356
84
|
</TableRow>
|
|
357
|
-
|
|
358
|
-
</
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
},
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// 2. With Selection
|
|
373
|
-
export const WithSelection: Story = {
|
|
374
|
-
render: () => {
|
|
375
|
-
const [selectedRows, setSelectedRows] = React.useState<number[]>([2])
|
|
376
|
-
const [selectAll, setSelectAll] = React.useState(false)
|
|
377
|
-
|
|
378
|
-
const handleSelectAll = () => {
|
|
379
|
-
if (selectAll) {
|
|
380
|
-
setSelectedRows([])
|
|
381
|
-
} else {
|
|
382
|
-
setSelectedRows(sampleUsers.map((user) => user.id))
|
|
383
|
-
}
|
|
384
|
-
setSelectAll(!selectAll)
|
|
385
|
-
}
|
|
85
|
+
</TableBody>
|
|
86
|
+
</Table>
|
|
87
|
+
<div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border p-4">
|
|
88
|
+
<p className="text-fm-secondary font-fm-text text-fm-md leading-fm-xl">
|
|
89
|
+
<strong className="text-fm-primary">Table</strong> renders a{" "}
|
|
90
|
+
<code>div[data-slot="table-container"]</code> with{" "}
|
|
91
|
+
<code>overflow-x-auto</code> for responsive scrolling, wrapping a
|
|
92
|
+
semantic <code><table></code> with{" "}
|
|
93
|
+
<code>bg-fm-surface-primary</code>.
|
|
94
|
+
</p>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
386
97
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
98
|
+
{/* TableHeader */}
|
|
99
|
+
<div className="space-y-4">
|
|
100
|
+
<h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
|
|
101
|
+
TableHeader — thead element
|
|
102
|
+
</h4>
|
|
103
|
+
<Table>
|
|
104
|
+
<TableHeader>
|
|
105
|
+
<TableRow>
|
|
106
|
+
<TableHead>#</TableHead>
|
|
107
|
+
<TableHead>Title</TableHead>
|
|
108
|
+
<TableHead>Artist</TableHead>
|
|
109
|
+
<TableHead>Duration</TableHead>
|
|
110
|
+
</TableRow>
|
|
111
|
+
</TableHeader>
|
|
112
|
+
<TableBody>
|
|
113
|
+
<TableRow>
|
|
114
|
+
<TableCell>1</TableCell>
|
|
115
|
+
<TableCell>Midnight Dreams</TableCell>
|
|
116
|
+
<TableCell>Aura Band</TableCell>
|
|
117
|
+
<TableCell>4:17</TableCell>
|
|
118
|
+
</TableRow>
|
|
119
|
+
</TableBody>
|
|
120
|
+
</Table>
|
|
121
|
+
<div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border p-4">
|
|
122
|
+
<p className="text-fm-secondary font-fm-text text-fm-md leading-fm-xl">
|
|
123
|
+
<strong className="text-fm-primary">TableHeader</strong> renders a{" "}
|
|
124
|
+
<code><thead></code> with a subtle frosted background (
|
|
125
|
+
<code>bg-fm-surface-frosted/10</code>). Always place{" "}
|
|
126
|
+
<code>TableHead</code> cells inside it for correct semantic
|
|
127
|
+
structure.
|
|
128
|
+
</p>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
392
131
|
|
|
393
|
-
|
|
394
|
-
<div className="
|
|
132
|
+
{/* TableHead */}
|
|
133
|
+
<div className="space-y-4">
|
|
134
|
+
<h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
|
|
135
|
+
TableHead — th cell
|
|
136
|
+
</h4>
|
|
395
137
|
<Table>
|
|
396
138
|
<TableHeader>
|
|
397
139
|
<TableRow>
|
|
398
|
-
<TableHead>
|
|
399
|
-
|
|
400
|
-
</TableHead>
|
|
401
|
-
<TableHead>
|
|
402
|
-
<TableHead>Email</TableHead>
|
|
403
|
-
<TableHead>Role</TableHead>
|
|
404
|
-
<TableHead>Status</TableHead>
|
|
405
|
-
<TableHead>Last Seen</TableHead>
|
|
140
|
+
<TableHead>Track</TableHead>
|
|
141
|
+
<TableHead>Album</TableHead>
|
|
142
|
+
<TableHead>Plays</TableHead>
|
|
143
|
+
<TableHead className="text-right">Actions</TableHead>
|
|
406
144
|
</TableRow>
|
|
407
145
|
</TableHeader>
|
|
408
146
|
<TableBody>
|
|
409
|
-
|
|
410
|
-
<
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
>
|
|
416
|
-
<TableCell>
|
|
417
|
-
<Checkbox
|
|
418
|
-
checked={selectedRows.includes(user.id)}
|
|
419
|
-
onChange={() => handleSelectRow(user.id)}
|
|
420
|
-
/>
|
|
421
|
-
</TableCell>
|
|
422
|
-
<TableCell>
|
|
423
|
-
<div className="flex items-center gap-3">
|
|
424
|
-
<div className="bg-fm-surface-secondary text-fm-primary flex h-8 w-8 items-center justify-center rounded-full text-sm font-medium">
|
|
425
|
-
{user.avatar}
|
|
426
|
-
</div>
|
|
427
|
-
<span>{user.name}</span>
|
|
428
|
-
</div>
|
|
429
|
-
</TableCell>
|
|
430
|
-
<TableCell className="text-fm-secondary">
|
|
431
|
-
{user.email}
|
|
432
|
-
</TableCell>
|
|
433
|
-
<TableCell>{user.role}</TableCell>
|
|
434
|
-
<TableCell>
|
|
435
|
-
<Badge
|
|
436
|
-
color={user.status === "Active" ? "positive" : "neutral"}
|
|
437
|
-
>
|
|
438
|
-
{user.status}
|
|
439
|
-
</Badge>
|
|
440
|
-
</TableCell>
|
|
441
|
-
<TableCell className="text-fm-tertiary">
|
|
442
|
-
{user.lastSeen}
|
|
443
|
-
</TableCell>
|
|
444
|
-
</TableRow>
|
|
445
|
-
))}
|
|
147
|
+
<TableRow>
|
|
148
|
+
<TableCell>Electric Sunrise</TableCell>
|
|
149
|
+
<TableCell>Wavelength EP</TableCell>
|
|
150
|
+
<TableCell>1.2M</TableCell>
|
|
151
|
+
<TableCell className="text-right">—</TableCell>
|
|
152
|
+
</TableRow>
|
|
446
153
|
</TableBody>
|
|
447
154
|
</Table>
|
|
155
|
+
<div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border p-4">
|
|
156
|
+
<p className="text-fm-secondary font-fm-text text-fm-md leading-fm-xl">
|
|
157
|
+
<strong className="text-fm-primary">TableHead</strong> renders a{" "}
|
|
158
|
+
<code><th></code> with <code>text-fm-tertiary</code>, brand
|
|
159
|
+
font, uppercase, and <code>text-fm-sm</code> sizing. Accepts{" "}
|
|
160
|
+
<code>className</code> for alignment overrides (e.g.{" "}
|
|
161
|
+
<code>text-right</code>).
|
|
162
|
+
</p>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
448
165
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
166
|
+
{/* TableBody */}
|
|
167
|
+
<div className="space-y-4">
|
|
168
|
+
<h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
|
|
169
|
+
TableBody — tbody element
|
|
170
|
+
</h4>
|
|
171
|
+
<Table>
|
|
172
|
+
<TableHeader>
|
|
173
|
+
<TableRow>
|
|
174
|
+
<TableHead>Title</TableHead>
|
|
175
|
+
<TableHead>Duration</TableHead>
|
|
176
|
+
</TableRow>
|
|
177
|
+
</TableHeader>
|
|
178
|
+
<TableBody>
|
|
179
|
+
<TableRow>
|
|
180
|
+
<TableCell>Echoes in Rain</TableCell>
|
|
181
|
+
<TableCell>3:42</TableCell>
|
|
182
|
+
</TableRow>
|
|
183
|
+
<TableRow>
|
|
184
|
+
<TableCell>Neon Horizon</TableCell>
|
|
185
|
+
<TableCell>5:01</TableCell>
|
|
186
|
+
</TableRow>
|
|
187
|
+
</TableBody>
|
|
188
|
+
</Table>
|
|
189
|
+
<div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border p-4">
|
|
190
|
+
<p className="text-fm-secondary font-fm-text text-fm-md leading-fm-xl">
|
|
191
|
+
<strong className="text-fm-primary">TableBody</strong> renders a{" "}
|
|
192
|
+
<code><tbody></code> that applies{" "}
|
|
193
|
+
<code>hover:bg-fm-surface-frosted/15</code> to every child{" "}
|
|
194
|
+
<code>TableRow</code> via a descendant selector.
|
|
195
|
+
</p>
|
|
456
196
|
</div>
|
|
457
197
|
</div>
|
|
458
|
-
)
|
|
459
|
-
},
|
|
460
|
-
parameters: {
|
|
461
|
-
docs: {
|
|
462
|
-
description: {
|
|
463
|
-
story:
|
|
464
|
-
"Table with row selection functionality. Shows selected state styling and checkbox integration.",
|
|
465
|
-
},
|
|
466
|
-
},
|
|
467
|
-
},
|
|
468
|
-
}
|
|
469
198
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
<
|
|
476
|
-
<
|
|
477
|
-
<
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
<
|
|
484
|
-
</TableRow>
|
|
485
|
-
</TableHeader>
|
|
486
|
-
<TableBody>
|
|
487
|
-
{sampleProjects.map((project) => (
|
|
488
|
-
<TableRow key={project.id}>
|
|
489
|
-
<TableCell className="font-medium">{project.name}</TableCell>
|
|
199
|
+
{/* TableRow */}
|
|
200
|
+
<div className="space-y-4">
|
|
201
|
+
<h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
|
|
202
|
+
TableRow — tr element
|
|
203
|
+
</h4>
|
|
204
|
+
<Table>
|
|
205
|
+
<TableHeader>
|
|
206
|
+
<TableRow>
|
|
207
|
+
<TableHead>State</TableHead>
|
|
208
|
+
<TableHead>Track</TableHead>
|
|
209
|
+
</TableRow>
|
|
210
|
+
</TableHeader>
|
|
211
|
+
<TableBody>
|
|
212
|
+
<TableRow>
|
|
490
213
|
<TableCell>
|
|
491
|
-
<Badge
|
|
492
|
-
color={
|
|
493
|
-
project.status === "Completed"
|
|
494
|
-
? "positive"
|
|
495
|
-
: project.status === "In Progress"
|
|
496
|
-
? "warning"
|
|
497
|
-
: "neutral"
|
|
498
|
-
}
|
|
499
|
-
>
|
|
500
|
-
{project.status}
|
|
501
|
-
</Badge>
|
|
214
|
+
<Badge color="neutral">Default</Badge>
|
|
502
215
|
</TableCell>
|
|
216
|
+
<TableCell>Midnight Dreams</TableCell>
|
|
217
|
+
</TableRow>
|
|
218
|
+
<TableRow data-state="selected">
|
|
503
219
|
<TableCell>
|
|
504
220
|
<Badge
|
|
505
|
-
color=
|
|
506
|
-
|
|
507
|
-
? "negative"
|
|
508
|
-
: project.priority === "Medium"
|
|
509
|
-
? "warning"
|
|
510
|
-
: "neutral"
|
|
511
|
-
}
|
|
221
|
+
color="info"
|
|
222
|
+
className="bg-fm-surface-info-sec text-fm-info-sec"
|
|
512
223
|
>
|
|
513
|
-
|
|
224
|
+
Selected
|
|
514
225
|
</Badge>
|
|
515
226
|
</TableCell>
|
|
516
|
-
<TableCell>
|
|
517
|
-
<TableCell className="text-fm-secondary">
|
|
518
|
-
{project.dueDate}
|
|
519
|
-
</TableCell>
|
|
520
|
-
<TableCell>
|
|
521
|
-
<div className="flex items-center gap-2">
|
|
522
|
-
<div className="bg-fm-surface-secondary h-2 w-20 rounded-full">
|
|
523
|
-
<div
|
|
524
|
-
className="bg-fm-primary-600 h-2 rounded-full"
|
|
525
|
-
style={{ width: `${project.progress}%` }}
|
|
526
|
-
/>
|
|
527
|
-
</div>
|
|
528
|
-
<span className="text-fm-tertiary text-sm">
|
|
529
|
-
{project.progress}%
|
|
530
|
-
</span>
|
|
531
|
-
</div>
|
|
532
|
-
</TableCell>
|
|
533
|
-
<TableCell>
|
|
534
|
-
<div className="flex items-center justify-end gap-2">
|
|
535
|
-
<Button size="sm" variant="text">
|
|
536
|
-
<EyeOpenIcon className="h-4 w-4" />
|
|
537
|
-
</Button>
|
|
538
|
-
<Button size="sm" variant="text">
|
|
539
|
-
<EditBigIcon className="h-4 w-4" />
|
|
540
|
-
</Button>
|
|
541
|
-
<Button size="sm" variant="text">
|
|
542
|
-
<TrashIcon className="h-4 w-4" />
|
|
543
|
-
</Button>
|
|
544
|
-
<Button size="sm" variant="text">
|
|
545
|
-
<VerticalMenuIcon className="h-4 w-4" />
|
|
546
|
-
</Button>
|
|
547
|
-
</div>
|
|
548
|
-
</TableCell>
|
|
227
|
+
<TableCell>Electric Sunrise</TableCell>
|
|
549
228
|
</TableRow>
|
|
550
|
-
|
|
551
|
-
</
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
},
|
|
562
|
-
},
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
// 4. With Footer and Caption
|
|
566
|
-
export const WithFooterAndCaption: Story = {
|
|
567
|
-
render: () => {
|
|
568
|
-
const totalUsers = sampleUsers.length
|
|
569
|
-
const activeUsers = sampleUsers.filter(
|
|
570
|
-
(user) => user.status === "Active"
|
|
571
|
-
).length
|
|
572
|
-
const inactiveUsers = totalUsers - activeUsers
|
|
229
|
+
</TableBody>
|
|
230
|
+
</Table>
|
|
231
|
+
<div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border p-4">
|
|
232
|
+
<p className="text-fm-secondary font-fm-text text-fm-md leading-fm-xl">
|
|
233
|
+
<strong className="text-fm-primary">TableRow</strong> renders a{" "}
|
|
234
|
+
<code><tr></code> with transition-colors. Apply{" "}
|
|
235
|
+
<code>data-state="selected"</code> to activate the selection
|
|
236
|
+
highlight (<code>bg-fm-secondary-50</code>).
|
|
237
|
+
</p>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
573
240
|
|
|
574
|
-
|
|
575
|
-
<div className="
|
|
241
|
+
{/* TableCell */}
|
|
242
|
+
<div className="space-y-4">
|
|
243
|
+
<h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
|
|
244
|
+
TableCell — td element
|
|
245
|
+
</h4>
|
|
576
246
|
<Table>
|
|
577
|
-
<TableCaption>User statistics and management overview</TableCaption>
|
|
578
247
|
<TableHeader>
|
|
579
248
|
<TableRow>
|
|
580
|
-
<TableHead>
|
|
581
|
-
<TableHead
|
|
582
|
-
<TableHead
|
|
249
|
+
<TableHead>Primary text</TableHead>
|
|
250
|
+
<TableHead>Secondary text</TableHead>
|
|
251
|
+
<TableHead>Tertiary text</TableHead>
|
|
583
252
|
</TableRow>
|
|
584
253
|
</TableHeader>
|
|
585
254
|
<TableBody>
|
|
586
255
|
<TableRow>
|
|
587
|
-
<TableCell>
|
|
588
|
-
<TableCell className="text-
|
|
589
|
-
|
|
256
|
+
<TableCell>text-fm-primary (default)</TableCell>
|
|
257
|
+
<TableCell className="text-fm-secondary">
|
|
258
|
+
text-fm-secondary
|
|
590
259
|
</TableCell>
|
|
591
|
-
<TableCell className="text-
|
|
592
|
-
|
|
260
|
+
<TableCell className="text-fm-tertiary">
|
|
261
|
+
text-fm-tertiary
|
|
593
262
|
</TableCell>
|
|
594
263
|
</TableRow>
|
|
264
|
+
</TableBody>
|
|
265
|
+
</Table>
|
|
266
|
+
<div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border p-4">
|
|
267
|
+
<p className="text-fm-secondary font-fm-text text-fm-md leading-fm-xl">
|
|
268
|
+
<strong className="text-fm-primary">TableCell</strong> renders a{" "}
|
|
269
|
+
<code><td></code> with <code>text-fm-primary</code>, text
|
|
270
|
+
font, <code>text-fm-md</code> sizing, and <code>p-4</code> padding.
|
|
271
|
+
Override <code>className</code> for secondary or tertiary text
|
|
272
|
+
hierarchy.
|
|
273
|
+
</p>
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
|
|
277
|
+
{/* TableFooter */}
|
|
278
|
+
<div className="space-y-4">
|
|
279
|
+
<h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
|
|
280
|
+
TableFooter — tfoot element
|
|
281
|
+
</h4>
|
|
282
|
+
<Table>
|
|
283
|
+
<TableHeader>
|
|
595
284
|
<TableRow>
|
|
596
|
-
<
|
|
597
|
-
<
|
|
598
|
-
{inactiveUsers}
|
|
599
|
-
</TableCell>
|
|
600
|
-
<TableCell className="text-right">
|
|
601
|
-
{Math.round((inactiveUsers / totalUsers) * 100)}%
|
|
602
|
-
</TableCell>
|
|
285
|
+
<TableHead>Track</TableHead>
|
|
286
|
+
<TableHead className="text-right">Plays</TableHead>
|
|
603
287
|
</TableRow>
|
|
288
|
+
</TableHeader>
|
|
289
|
+
<TableBody>
|
|
604
290
|
<TableRow>
|
|
605
|
-
<TableCell>
|
|
606
|
-
<TableCell className="text-right
|
|
607
|
-
{sampleUsers.filter((user) => user.role === "Admin").length}
|
|
608
|
-
</TableCell>
|
|
609
|
-
<TableCell className="text-right">
|
|
610
|
-
{Math.round(
|
|
611
|
-
(sampleUsers.filter((user) => user.role === "Admin").length /
|
|
612
|
-
totalUsers) *
|
|
613
|
-
100
|
|
614
|
-
)}
|
|
615
|
-
%
|
|
616
|
-
</TableCell>
|
|
291
|
+
<TableCell>Midnight Dreams</TableCell>
|
|
292
|
+
<TableCell className="text-right">1.2M</TableCell>
|
|
617
293
|
</TableRow>
|
|
618
294
|
<TableRow>
|
|
619
|
-
<TableCell>
|
|
620
|
-
<TableCell className="text-right
|
|
621
|
-
{sampleUsers.filter((user) => user.role === "Editor").length}
|
|
622
|
-
</TableCell>
|
|
623
|
-
<TableCell className="text-right">
|
|
624
|
-
{Math.round(
|
|
625
|
-
(sampleUsers.filter((user) => user.role === "Editor").length /
|
|
626
|
-
totalUsers) *
|
|
627
|
-
100
|
|
628
|
-
)}
|
|
629
|
-
%
|
|
630
|
-
</TableCell>
|
|
295
|
+
<TableCell>Electric Sunrise</TableCell>
|
|
296
|
+
<TableCell className="text-right">890K</TableCell>
|
|
631
297
|
</TableRow>
|
|
632
298
|
</TableBody>
|
|
633
299
|
<TableFooter>
|
|
634
300
|
<TableRow>
|
|
635
|
-
<TableCell className="font-
|
|
636
|
-
<TableCell className="text-right font-
|
|
637
|
-
{totalUsers}
|
|
638
|
-
</TableCell>
|
|
639
|
-
<TableCell className="text-right font-bold">100%</TableCell>
|
|
301
|
+
<TableCell className="font-semibold">Total</TableCell>
|
|
302
|
+
<TableCell className="text-right font-semibold">2.09M</TableCell>
|
|
640
303
|
</TableRow>
|
|
641
304
|
</TableFooter>
|
|
642
305
|
</Table>
|
|
306
|
+
<div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border p-4">
|
|
307
|
+
<p className="text-fm-secondary font-fm-text text-fm-md leading-fm-xl">
|
|
308
|
+
<strong className="text-fm-primary">TableFooter</strong> renders a{" "}
|
|
309
|
+
<code><tfoot></code> with the same frosted background as{" "}
|
|
310
|
+
<code>TableHeader</code>. Use it for totals, aggregates, or
|
|
311
|
+
pagination controls.
|
|
312
|
+
</p>
|
|
313
|
+
</div>
|
|
643
314
|
</div>
|
|
644
|
-
|
|
645
|
-
|
|
315
|
+
|
|
316
|
+
{/* TableCaption */}
|
|
317
|
+
<div className="space-y-4">
|
|
318
|
+
<h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
|
|
319
|
+
TableCaption — caption element
|
|
320
|
+
</h4>
|
|
321
|
+
<Table>
|
|
322
|
+
<TableCaption>Top tracks by play count — last 30 days</TableCaption>
|
|
323
|
+
<TableHeader>
|
|
324
|
+
<TableRow>
|
|
325
|
+
<TableHead>Track</TableHead>
|
|
326
|
+
<TableHead className="text-right">Plays</TableHead>
|
|
327
|
+
</TableRow>
|
|
328
|
+
</TableHeader>
|
|
329
|
+
<TableBody>
|
|
330
|
+
<TableRow>
|
|
331
|
+
<TableCell>Midnight Dreams</TableCell>
|
|
332
|
+
<TableCell className="text-right">1.2M</TableCell>
|
|
333
|
+
</TableRow>
|
|
334
|
+
</TableBody>
|
|
335
|
+
</Table>
|
|
336
|
+
<div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border p-4">
|
|
337
|
+
<p className="text-fm-secondary font-fm-text text-fm-md leading-fm-xl">
|
|
338
|
+
<strong className="text-fm-primary">TableCaption</strong> renders a{" "}
|
|
339
|
+
<code><caption></code> below the table using{" "}
|
|
340
|
+
<code>text-fm-label-tertiary</code>, brand font, and uppercase small
|
|
341
|
+
text. It is announced by screen readers for accessibility context.
|
|
342
|
+
</p>
|
|
343
|
+
</div>
|
|
344
|
+
</div>
|
|
345
|
+
</div>
|
|
346
|
+
),
|
|
646
347
|
parameters: {
|
|
348
|
+
layout: "fullscreen",
|
|
647
349
|
docs: {
|
|
648
350
|
description: {
|
|
649
351
|
story:
|
|
650
|
-
"Table with
|
|
352
|
+
"Each sub-component of the Table compound shown individually with a role explanation and live example. Use this as a reference when composing data table layouts.",
|
|
651
353
|
},
|
|
652
354
|
},
|
|
653
355
|
},
|
|
654
356
|
}
|
|
655
357
|
|
|
656
|
-
//
|
|
657
|
-
export const SortableTable: Story = {
|
|
658
|
-
render: () => {
|
|
659
|
-
const [sortField, setSortField] = React.useState<string | null>(null)
|
|
660
|
-
const [sortDirection, setSortDirection] = React.useState<"asc" | "desc">(
|
|
661
|
-
"asc"
|
|
662
|
-
)
|
|
663
|
-
|
|
664
|
-
const sortedUsers = React.useMemo(() => {
|
|
665
|
-
if (!sortField) return sampleUsers
|
|
358
|
+
// ─── 2. Configurations ────────────────────────────────────────────────────────
|
|
666
359
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
360
|
+
export const Configurations: Story = {
|
|
361
|
+
render: () => {
|
|
362
|
+
const [selectedRows, setSelectedRows] = React.useState<number[]>([2])
|
|
363
|
+
const [sortField, setSortField] = React.useState<string | null>("title")
|
|
364
|
+
const [sortDir, setSortDir] = React.useState<"asc" | "desc">("asc")
|
|
365
|
+
|
|
366
|
+
const tracks = [
|
|
367
|
+
{
|
|
368
|
+
id: 1,
|
|
369
|
+
title: "Midnight Dreams",
|
|
370
|
+
artist: "Aura Band",
|
|
371
|
+
album: "Dark Frequencies",
|
|
372
|
+
duration: "4:17",
|
|
373
|
+
plays: 1200000,
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
id: 2,
|
|
377
|
+
title: "Electric Sunrise",
|
|
378
|
+
artist: "Neon Collective",
|
|
379
|
+
album: "Wavelength EP",
|
|
380
|
+
duration: "5:01",
|
|
381
|
+
plays: 890000,
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
id: 3,
|
|
385
|
+
title: "Echoes in Rain",
|
|
386
|
+
artist: "The Drifters",
|
|
387
|
+
album: "Reflections",
|
|
388
|
+
duration: "3:42",
|
|
389
|
+
plays: 640000,
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
id: 4,
|
|
393
|
+
title: "Neon Horizon",
|
|
394
|
+
artist: "Synthwave FM",
|
|
395
|
+
album: "Future Bass",
|
|
396
|
+
duration: "4:58",
|
|
397
|
+
plays: 410000,
|
|
398
|
+
},
|
|
399
|
+
]
|
|
670
400
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
401
|
+
const sorted = [...tracks].sort((a, b) => {
|
|
402
|
+
if (!sortField) return 0
|
|
403
|
+
const av = a[sortField as keyof typeof a]
|
|
404
|
+
const bv = b[sortField as keyof typeof b]
|
|
405
|
+
if (av < bv) return sortDir === "asc" ? -1 : 1
|
|
406
|
+
if (av > bv) return sortDir === "asc" ? 1 : -1
|
|
407
|
+
return 0
|
|
408
|
+
})
|
|
676
409
|
|
|
677
410
|
const handleSort = (field: string) => {
|
|
678
411
|
if (sortField === field) {
|
|
679
|
-
|
|
412
|
+
setSortDir((d) => (d === "asc" ? "desc" : "asc"))
|
|
680
413
|
} else {
|
|
681
414
|
setSortField(field)
|
|
682
|
-
|
|
415
|
+
setSortDir("asc")
|
|
683
416
|
}
|
|
684
417
|
}
|
|
685
418
|
|
|
686
|
-
const
|
|
419
|
+
const SortHead = ({
|
|
687
420
|
field,
|
|
688
421
|
children,
|
|
689
422
|
}: {
|
|
@@ -692,228 +425,512 @@ export const SortableTable: Story = {
|
|
|
692
425
|
}) => (
|
|
693
426
|
<TableHead>
|
|
694
427
|
<button
|
|
695
|
-
className="hover:text-fm-primary flex items-center gap-
|
|
428
|
+
className="text-fm-tertiary hover:text-fm-primary flex items-center gap-1.5 transition-colors"
|
|
696
429
|
onClick={() => handleSort(field)}
|
|
697
430
|
>
|
|
698
431
|
{children}
|
|
699
|
-
{sortField === field
|
|
700
|
-
|
|
701
|
-
<ChevronUpIcon className="
|
|
432
|
+
{sortField === field ? (
|
|
433
|
+
sortDir === "asc" ? (
|
|
434
|
+
<ChevronUpIcon className="size-3.5" />
|
|
702
435
|
) : (
|
|
703
|
-
<ChevronDownIcon className="
|
|
704
|
-
)
|
|
436
|
+
<ChevronDownIcon className="size-3.5" />
|
|
437
|
+
)
|
|
438
|
+
) : (
|
|
439
|
+
<ChevronUpIcon className="text-fm-tertiary/40 size-3.5" />
|
|
440
|
+
)}
|
|
705
441
|
</button>
|
|
706
442
|
</TableHead>
|
|
707
443
|
)
|
|
708
444
|
|
|
445
|
+
const handleToggle = (id: number) =>
|
|
446
|
+
setSelectedRows((prev) =>
|
|
447
|
+
prev.includes(id) ? prev.filter((r) => r !== id) : [...prev, id]
|
|
448
|
+
)
|
|
449
|
+
|
|
709
450
|
return (
|
|
710
|
-
<div className="p-8">
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
<
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
</
|
|
732
|
-
<
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
451
|
+
<div className="space-y-8 p-8">
|
|
452
|
+
{/* Row selection */}
|
|
453
|
+
<div className="space-y-4">
|
|
454
|
+
<h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
|
|
455
|
+
Row selection
|
|
456
|
+
</h4>
|
|
457
|
+
<Table>
|
|
458
|
+
<TableHeader>
|
|
459
|
+
<TableRow>
|
|
460
|
+
<TableHead>
|
|
461
|
+
<Checkbox
|
|
462
|
+
checked={selectedRows.length === tracks.length}
|
|
463
|
+
onCheckedChange={() =>
|
|
464
|
+
setSelectedRows(
|
|
465
|
+
selectedRows.length === tracks.length
|
|
466
|
+
? []
|
|
467
|
+
: tracks.map((t) => t.id)
|
|
468
|
+
)
|
|
469
|
+
}
|
|
470
|
+
/>
|
|
471
|
+
</TableHead>
|
|
472
|
+
<TableHead>Title</TableHead>
|
|
473
|
+
<TableHead>Artist</TableHead>
|
|
474
|
+
<TableHead>Duration</TableHead>
|
|
475
|
+
</TableRow>
|
|
476
|
+
</TableHeader>
|
|
477
|
+
<TableBody>
|
|
478
|
+
{tracks.map((t) => (
|
|
479
|
+
<TableRow
|
|
480
|
+
key={t.id}
|
|
481
|
+
data-state={
|
|
482
|
+
selectedRows.includes(t.id) ? "selected" : undefined
|
|
483
|
+
}
|
|
484
|
+
>
|
|
485
|
+
<TableCell>
|
|
486
|
+
<Checkbox
|
|
487
|
+
checked={selectedRows.includes(t.id)}
|
|
488
|
+
onCheckedChange={() => handleToggle(t.id)}
|
|
489
|
+
/>
|
|
490
|
+
</TableCell>
|
|
491
|
+
<TableCell className="font-medium">{t.title}</TableCell>
|
|
492
|
+
<TableCell className="text-fm-secondary">
|
|
493
|
+
{t.artist}
|
|
494
|
+
</TableCell>
|
|
495
|
+
<TableCell className="text-fm-tertiary">
|
|
496
|
+
{t.duration}
|
|
497
|
+
</TableCell>
|
|
498
|
+
</TableRow>
|
|
499
|
+
))}
|
|
500
|
+
</TableBody>
|
|
501
|
+
<TableFooter>
|
|
502
|
+
<TableRow>
|
|
503
|
+
<TableCell colSpan={4}>
|
|
504
|
+
<span className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
|
|
505
|
+
{selectedRows.length} of {tracks.length} track
|
|
506
|
+
{tracks.length !== 1 ? "s" : ""} selected
|
|
507
|
+
</span>
|
|
745
508
|
</TableCell>
|
|
746
509
|
</TableRow>
|
|
747
|
-
|
|
748
|
-
</
|
|
749
|
-
</
|
|
510
|
+
</TableFooter>
|
|
511
|
+
</Table>
|
|
512
|
+
</div>
|
|
750
513
|
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
514
|
+
{/* Sortable columns */}
|
|
515
|
+
<div className="space-y-4">
|
|
516
|
+
<h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
|
|
517
|
+
Sortable columns
|
|
518
|
+
</h4>
|
|
519
|
+
<Table>
|
|
520
|
+
<TableHeader>
|
|
521
|
+
<TableRow>
|
|
522
|
+
<SortHead field="title">Title</SortHead>
|
|
523
|
+
<SortHead field="artist">Artist</SortHead>
|
|
524
|
+
<SortHead field="plays">Plays</SortHead>
|
|
525
|
+
<SortHead field="duration">Duration</SortHead>
|
|
526
|
+
</TableRow>
|
|
527
|
+
</TableHeader>
|
|
528
|
+
<TableBody>
|
|
529
|
+
{sorted.map((t) => (
|
|
530
|
+
<TableRow key={t.id}>
|
|
531
|
+
<TableCell className="font-medium">{t.title}</TableCell>
|
|
532
|
+
<TableCell className="text-fm-secondary">
|
|
533
|
+
{t.artist}
|
|
534
|
+
</TableCell>
|
|
535
|
+
<TableCell>{(t.plays / 1_000_000).toFixed(1)}M</TableCell>
|
|
536
|
+
<TableCell className="text-fm-tertiary">
|
|
537
|
+
{t.duration}
|
|
538
|
+
</TableCell>
|
|
539
|
+
</TableRow>
|
|
540
|
+
))}
|
|
541
|
+
</TableBody>
|
|
542
|
+
</Table>
|
|
543
|
+
</div>
|
|
544
|
+
|
|
545
|
+
{/* Dense layout */}
|
|
546
|
+
<div className="space-y-4">
|
|
547
|
+
<h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
|
|
548
|
+
Dense layout
|
|
549
|
+
</h4>
|
|
550
|
+
<Table>
|
|
551
|
+
<TableHeader>
|
|
552
|
+
<TableRow>
|
|
553
|
+
<TableHead className="py-2">#</TableHead>
|
|
554
|
+
<TableHead className="py-2">Title</TableHead>
|
|
555
|
+
<TableHead className="py-2">Artist</TableHead>
|
|
556
|
+
<TableHead className="py-2">Album</TableHead>
|
|
557
|
+
<TableHead className="py-2">Duration</TableHead>
|
|
558
|
+
</TableRow>
|
|
559
|
+
</TableHeader>
|
|
560
|
+
<TableBody>
|
|
561
|
+
{tracks.map((t) => (
|
|
562
|
+
<TableRow key={t.id}>
|
|
563
|
+
<TableCell className="text-fm-tertiary py-2">
|
|
564
|
+
{t.id}
|
|
565
|
+
</TableCell>
|
|
566
|
+
<TableCell className="py-2 font-medium">{t.title}</TableCell>
|
|
567
|
+
<TableCell className="text-fm-secondary py-2">
|
|
568
|
+
{t.artist}
|
|
569
|
+
</TableCell>
|
|
570
|
+
<TableCell className="text-fm-tertiary py-2">
|
|
571
|
+
{t.album}
|
|
572
|
+
</TableCell>
|
|
573
|
+
<TableCell className="text-fm-tertiary py-2">
|
|
574
|
+
{t.duration}
|
|
575
|
+
</TableCell>
|
|
576
|
+
</TableRow>
|
|
577
|
+
))}
|
|
578
|
+
</TableBody>
|
|
579
|
+
</Table>
|
|
758
580
|
</div>
|
|
759
581
|
</div>
|
|
760
582
|
)
|
|
761
583
|
},
|
|
762
584
|
parameters: {
|
|
585
|
+
layout: "fullscreen",
|
|
763
586
|
docs: {
|
|
764
587
|
description: {
|
|
765
588
|
story:
|
|
766
|
-
"
|
|
589
|
+
"Three configuration modes: row selection with checkboxes and footer count, sortable column headers with direction indicators, and a dense layout with reduced vertical padding.",
|
|
767
590
|
},
|
|
768
591
|
},
|
|
769
592
|
},
|
|
770
593
|
}
|
|
771
594
|
|
|
772
|
-
//
|
|
773
|
-
export const ResponsiveTable: Story = {
|
|
774
|
-
render: () => (
|
|
775
|
-
<div className="p-8">
|
|
776
|
-
<div className="mb-4">
|
|
777
|
-
<h3 className="text-fm-primary mb-2 text-lg font-medium">
|
|
778
|
-
Responsive Table
|
|
779
|
-
</h3>
|
|
780
|
-
<p className="text-fm-secondary text-sm">
|
|
781
|
-
Resize your browser or view on mobile to see horizontal scrolling
|
|
782
|
-
behavior
|
|
783
|
-
</p>
|
|
784
|
-
</div>
|
|
595
|
+
// ─── 3. UseCases ──────────────────────────────────────────────────────────────
|
|
785
596
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
<TableBody>
|
|
801
|
-
{sampleUsers.map((user) => (
|
|
802
|
-
<TableRow key={user.id}>
|
|
803
|
-
<TableCell className="font-mono">
|
|
804
|
-
#{user.id.toString().padStart(3, "0")}
|
|
805
|
-
</TableCell>
|
|
806
|
-
<TableCell className="font-medium">{user.name}</TableCell>
|
|
807
|
-
<TableCell className="text-fm-secondary">{user.email}</TableCell>
|
|
808
|
-
<TableCell>{user.role}</TableCell>
|
|
809
|
-
<TableCell>Engineering</TableCell>
|
|
810
|
-
<TableCell>
|
|
811
|
-
<Badge
|
|
812
|
-
color={user.status === "Active" ? "positive" : "neutral"}
|
|
813
|
-
>
|
|
814
|
-
{user.status}
|
|
815
|
-
</Badge>
|
|
816
|
-
</TableCell>
|
|
817
|
-
<TableCell>Nov 15, 2024</TableCell>
|
|
818
|
-
<TableCell className="text-fm-tertiary">
|
|
819
|
-
{user.lastSeen}
|
|
820
|
-
</TableCell>
|
|
821
|
-
<TableCell>
|
|
822
|
-
<div className="flex gap-1">
|
|
823
|
-
<Button size="sm" variant="text">
|
|
824
|
-
<EditBigIcon className="h-4 w-4" />
|
|
825
|
-
</Button>
|
|
826
|
-
<Button size="sm" variant="text">
|
|
827
|
-
<TrashIcon className="h-4 w-4" />
|
|
828
|
-
</Button>
|
|
829
|
-
</div>
|
|
830
|
-
</TableCell>
|
|
831
|
-
</TableRow>
|
|
832
|
-
))}
|
|
833
|
-
</TableBody>
|
|
834
|
-
</Table>
|
|
835
|
-
</div>
|
|
836
|
-
),
|
|
837
|
-
parameters: {
|
|
838
|
-
docs: {
|
|
839
|
-
description: {
|
|
840
|
-
story:
|
|
841
|
-
"Table with many columns demonstrating responsive horizontal scrolling behavior.",
|
|
597
|
+
export const UseCases: Story = {
|
|
598
|
+
render: () => {
|
|
599
|
+
const [nowPlaying, setNowPlaying] = React.useState<number | null>(1)
|
|
600
|
+
const [liked, setLiked] = React.useState<number[]>([1, 3])
|
|
601
|
+
|
|
602
|
+
const tracks = [
|
|
603
|
+
{
|
|
604
|
+
id: 1,
|
|
605
|
+
num: 1,
|
|
606
|
+
title: "Midnight Dreams",
|
|
607
|
+
artist: "Aura Band",
|
|
608
|
+
duration: "4:17",
|
|
609
|
+
plays: "1.2M",
|
|
610
|
+
explicit: false,
|
|
842
611
|
},
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
612
|
+
{
|
|
613
|
+
id: 2,
|
|
614
|
+
num: 2,
|
|
615
|
+
title: "Electric Sunrise",
|
|
616
|
+
artist: "Neon Collective",
|
|
617
|
+
duration: "5:01",
|
|
618
|
+
plays: "890K",
|
|
619
|
+
explicit: true,
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
id: 3,
|
|
623
|
+
num: 3,
|
|
624
|
+
title: "Echoes in Rain",
|
|
625
|
+
artist: "The Drifters",
|
|
626
|
+
duration: "3:42",
|
|
627
|
+
plays: "640K",
|
|
628
|
+
explicit: false,
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
id: 4,
|
|
632
|
+
num: 4,
|
|
633
|
+
title: "Neon Horizon",
|
|
634
|
+
artist: "Synthwave FM",
|
|
635
|
+
duration: "4:58",
|
|
636
|
+
plays: "410K",
|
|
637
|
+
explicit: false,
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
id: 5,
|
|
641
|
+
num: 5,
|
|
642
|
+
title: "Crystal Waves",
|
|
643
|
+
artist: "Luna Echo",
|
|
644
|
+
duration: "3:18",
|
|
645
|
+
plays: "310K",
|
|
646
|
+
explicit: false,
|
|
647
|
+
},
|
|
648
|
+
]
|
|
649
|
+
|
|
650
|
+
const albums = [
|
|
651
|
+
{
|
|
652
|
+
id: 1,
|
|
653
|
+
title: "Dark Frequencies",
|
|
654
|
+
artist: "Aura Band",
|
|
655
|
+
year: 2024,
|
|
656
|
+
tracks: 12,
|
|
657
|
+
genre: "Electronic",
|
|
658
|
+
status: "Released",
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
id: 2,
|
|
662
|
+
title: "Wavelength EP",
|
|
663
|
+
artist: "Neon Collective",
|
|
664
|
+
year: 2024,
|
|
665
|
+
tracks: 6,
|
|
666
|
+
genre: "Ambient",
|
|
667
|
+
status: "Released",
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
id: 3,
|
|
671
|
+
title: "Future Bass Vol. 3",
|
|
672
|
+
artist: "Synthwave FM",
|
|
673
|
+
year: 2025,
|
|
674
|
+
tracks: 9,
|
|
675
|
+
genre: "Bass",
|
|
676
|
+
status: "Pre-order",
|
|
677
|
+
},
|
|
678
|
+
{
|
|
679
|
+
id: 4,
|
|
680
|
+
title: "Reflections",
|
|
681
|
+
artist: "The Drifters",
|
|
682
|
+
year: 2023,
|
|
683
|
+
tracks: 14,
|
|
684
|
+
genre: "Indie",
|
|
685
|
+
status: "Released",
|
|
686
|
+
},
|
|
687
|
+
]
|
|
846
688
|
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
</
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
</
|
|
867
|
-
<
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
689
|
+
const toggleLike = (id: number) =>
|
|
690
|
+
setLiked((prev) =>
|
|
691
|
+
prev.includes(id) ? prev.filter((l) => l !== id) : [...prev, id]
|
|
692
|
+
)
|
|
693
|
+
|
|
694
|
+
return (
|
|
695
|
+
<div className="mx-auto max-w-3xl space-y-8 p-8">
|
|
696
|
+
{/* Track Listing */}
|
|
697
|
+
<div className="space-y-4">
|
|
698
|
+
<h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
|
|
699
|
+
Track listing
|
|
700
|
+
</h4>
|
|
701
|
+
<Table>
|
|
702
|
+
<TableCaption>
|
|
703
|
+
Late Night Sessions — 5 tracks · 21m 16s
|
|
704
|
+
</TableCaption>
|
|
705
|
+
<TableHeader>
|
|
706
|
+
<TableRow>
|
|
707
|
+
<TableHead className="w-10">#</TableHead>
|
|
708
|
+
<TableHead>Title</TableHead>
|
|
709
|
+
<TableHead>Plays</TableHead>
|
|
710
|
+
<TableHead className="text-right">Duration</TableHead>
|
|
711
|
+
<TableHead className="w-10" />
|
|
712
|
+
</TableRow>
|
|
713
|
+
</TableHeader>
|
|
714
|
+
<TableBody>
|
|
715
|
+
{tracks.map((t) => {
|
|
716
|
+
const isPlaying = nowPlaying === t.id
|
|
717
|
+
return (
|
|
718
|
+
<TableRow
|
|
719
|
+
key={t.id}
|
|
720
|
+
data-state={isPlaying ? "selected" : undefined}
|
|
721
|
+
>
|
|
722
|
+
<TableCell>
|
|
723
|
+
<button
|
|
724
|
+
className="text-fm-tertiary hover:text-fm-primary flex items-center justify-center transition-colors"
|
|
725
|
+
onClick={() => setNowPlaying(isPlaying ? null : t.id)}
|
|
726
|
+
aria-label={
|
|
727
|
+
isPlaying ? `Pause ${t.title}` : `Play ${t.title}`
|
|
728
|
+
}
|
|
729
|
+
>
|
|
730
|
+
{isPlaying ? (
|
|
731
|
+
<PauseIcon className="size-4" />
|
|
732
|
+
) : (
|
|
733
|
+
<span className="font-fm-text text-fm-sm leading-fm-sm">
|
|
734
|
+
{t.num}
|
|
735
|
+
</span>
|
|
736
|
+
)}
|
|
737
|
+
</button>
|
|
738
|
+
</TableCell>
|
|
739
|
+
<TableCell>
|
|
740
|
+
<div className="flex flex-col gap-0.5">
|
|
741
|
+
<span
|
|
742
|
+
className={
|
|
743
|
+
isPlaying
|
|
744
|
+
? "text-fm-label-primary font-medium"
|
|
745
|
+
: "font-medium"
|
|
746
|
+
}
|
|
747
|
+
>
|
|
748
|
+
{t.title}
|
|
749
|
+
</span>
|
|
750
|
+
<div className="flex items-center gap-2">
|
|
751
|
+
<span className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
|
|
752
|
+
{t.artist}
|
|
753
|
+
</span>
|
|
754
|
+
{t.explicit && (
|
|
755
|
+
<Badge
|
|
756
|
+
color="neutral"
|
|
757
|
+
className="text-fm-xs px-1 py-0"
|
|
758
|
+
>
|
|
759
|
+
E
|
|
760
|
+
</Badge>
|
|
761
|
+
)}
|
|
762
|
+
</div>
|
|
763
|
+
</div>
|
|
764
|
+
</TableCell>
|
|
765
|
+
<TableCell className="text-fm-tertiary font-fm-text text-fm-sm leading-fm-sm">
|
|
766
|
+
{t.plays}
|
|
767
|
+
</TableCell>
|
|
768
|
+
<TableCell className="text-fm-tertiary font-fm-text text-fm-sm leading-fm-sm text-right">
|
|
769
|
+
{t.duration}
|
|
770
|
+
</TableCell>
|
|
771
|
+
<TableCell>
|
|
772
|
+
<button
|
|
773
|
+
className={
|
|
774
|
+
liked.includes(t.id)
|
|
775
|
+
? "text-fm-label-primary"
|
|
776
|
+
: "text-fm-tertiary hover:text-fm-primary transition-colors"
|
|
777
|
+
}
|
|
778
|
+
onClick={() => toggleLike(t.id)}
|
|
779
|
+
aria-label={
|
|
780
|
+
liked.includes(t.id)
|
|
781
|
+
? `Unlike ${t.title}`
|
|
782
|
+
: `Like ${t.title}`
|
|
783
|
+
}
|
|
784
|
+
>
|
|
785
|
+
<HeartIcon className="size-4" />
|
|
786
|
+
</button>
|
|
787
|
+
</TableCell>
|
|
788
|
+
</TableRow>
|
|
789
|
+
)
|
|
790
|
+
})}
|
|
791
|
+
</TableBody>
|
|
792
|
+
</Table>
|
|
793
|
+
</div>
|
|
794
|
+
|
|
795
|
+
{/* Album catalog */}
|
|
796
|
+
<div className="space-y-4">
|
|
797
|
+
<h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
|
|
798
|
+
Album catalog
|
|
799
|
+
</h4>
|
|
800
|
+
<Table>
|
|
801
|
+
<TableHeader>
|
|
802
|
+
<TableRow>
|
|
803
|
+
<TableHead>Album</TableHead>
|
|
804
|
+
<TableHead>Artist</TableHead>
|
|
805
|
+
<TableHead>Year</TableHead>
|
|
806
|
+
<TableHead>Genre</TableHead>
|
|
807
|
+
<TableHead>Tracks</TableHead>
|
|
808
|
+
<TableHead>Status</TableHead>
|
|
809
|
+
</TableRow>
|
|
810
|
+
</TableHeader>
|
|
811
|
+
<TableBody>
|
|
812
|
+
{albums.map((a) => (
|
|
813
|
+
<TableRow key={a.id}>
|
|
814
|
+
<TableCell>
|
|
815
|
+
<div className="flex items-center gap-3">
|
|
816
|
+
<div className="bg-fm-surface-secondary flex size-9 shrink-0 items-center justify-center rounded">
|
|
817
|
+
<MusicalNoteIcon className="text-fm-tertiary size-4" />
|
|
818
|
+
</div>
|
|
819
|
+
<span className="font-medium">{a.title}</span>
|
|
820
|
+
</div>
|
|
821
|
+
</TableCell>
|
|
822
|
+
<TableCell className="text-fm-secondary">
|
|
823
|
+
{a.artist}
|
|
824
|
+
</TableCell>
|
|
825
|
+
<TableCell className="text-fm-tertiary">{a.year}</TableCell>
|
|
826
|
+
<TableCell>
|
|
827
|
+
<Badge color="neutral">{a.genre}</Badge>
|
|
828
|
+
</TableCell>
|
|
829
|
+
<TableCell className="text-fm-secondary">
|
|
830
|
+
{a.tracks}
|
|
831
|
+
</TableCell>
|
|
832
|
+
<TableCell>
|
|
833
|
+
<Badge
|
|
834
|
+
color={a.status === "Released" ? "positive" : "warning"}
|
|
835
|
+
className={
|
|
836
|
+
a.status === "Released"
|
|
837
|
+
? "bg-fm-surface-positive-sec text-fm-positive-sec"
|
|
838
|
+
: "bg-fm-yellow-200 text-fm-yellow-900"
|
|
839
|
+
}
|
|
840
|
+
>
|
|
841
|
+
{a.status}
|
|
842
|
+
</Badge>
|
|
843
|
+
</TableCell>
|
|
844
|
+
</TableRow>
|
|
845
|
+
))}
|
|
846
|
+
</TableBody>
|
|
847
|
+
<TableFooter>
|
|
848
|
+
<TableRow>
|
|
849
|
+
<TableCell colSpan={4} className="font-semibold">
|
|
850
|
+
4 albums
|
|
851
|
+
</TableCell>
|
|
852
|
+
<TableCell className="font-semibold">
|
|
853
|
+
{albums.reduce((s, a) => s + a.tracks, 0)}
|
|
854
|
+
</TableCell>
|
|
855
|
+
<TableCell />
|
|
856
|
+
</TableRow>
|
|
857
|
+
</TableFooter>
|
|
858
|
+
</Table>
|
|
859
|
+
</div>
|
|
860
|
+
</div>
|
|
861
|
+
)
|
|
862
|
+
},
|
|
881
863
|
parameters: {
|
|
864
|
+
layout: "fullscreen",
|
|
882
865
|
docs: {
|
|
883
866
|
description: {
|
|
884
867
|
story:
|
|
885
|
-
"
|
|
868
|
+
"Two realistic audio app scenarios: an interactive track listing with play/pause toggle and like buttons, and an album catalog with genre badges, release status, and a totals footer.",
|
|
886
869
|
},
|
|
887
870
|
},
|
|
888
871
|
},
|
|
889
872
|
}
|
|
890
873
|
|
|
891
|
-
//
|
|
892
|
-
|
|
893
|
-
|
|
874
|
+
// ─── 4. Playground ────────────────────────────────────────────────────────────
|
|
875
|
+
|
|
876
|
+
export const Playground: Story = {
|
|
877
|
+
args: {
|
|
878
|
+
className: "",
|
|
879
|
+
},
|
|
880
|
+
render: (args) => (
|
|
894
881
|
<div className="p-8">
|
|
895
|
-
<Table>
|
|
882
|
+
<Table {...args}>
|
|
883
|
+
<TableCaption>
|
|
884
|
+
Playground table — adjust className via controls
|
|
885
|
+
</TableCaption>
|
|
896
886
|
<TableHeader>
|
|
897
887
|
<TableRow>
|
|
898
|
-
<TableHead
|
|
899
|
-
<TableHead
|
|
900
|
-
<TableHead
|
|
901
|
-
<TableHead
|
|
888
|
+
<TableHead>#</TableHead>
|
|
889
|
+
<TableHead>Title</TableHead>
|
|
890
|
+
<TableHead>Artist</TableHead>
|
|
891
|
+
<TableHead>Duration</TableHead>
|
|
892
|
+
<TableHead>Status</TableHead>
|
|
902
893
|
</TableRow>
|
|
903
894
|
</TableHeader>
|
|
904
895
|
<TableBody>
|
|
905
|
-
{
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
896
|
+
{[
|
|
897
|
+
{
|
|
898
|
+
id: 1,
|
|
899
|
+
title: "Midnight Dreams",
|
|
900
|
+
artist: "Aura Band",
|
|
901
|
+
duration: "4:17",
|
|
902
|
+
status: "Playing",
|
|
903
|
+
},
|
|
904
|
+
{
|
|
905
|
+
id: 2,
|
|
906
|
+
title: "Electric Sunrise",
|
|
907
|
+
artist: "Neon Collective",
|
|
908
|
+
duration: "5:01",
|
|
909
|
+
status: "Queued",
|
|
910
|
+
},
|
|
911
|
+
{
|
|
912
|
+
id: 3,
|
|
913
|
+
title: "Echoes in Rain",
|
|
914
|
+
artist: "The Drifters",
|
|
915
|
+
duration: "3:42",
|
|
916
|
+
status: "Queued",
|
|
917
|
+
},
|
|
918
|
+
].map((t) => (
|
|
919
|
+
<TableRow key={t.id}>
|
|
920
|
+
<TableCell className="text-fm-tertiary">{t.id}</TableCell>
|
|
921
|
+
<TableCell className="font-medium">{t.title}</TableCell>
|
|
922
|
+
<TableCell className="text-fm-secondary">{t.artist}</TableCell>
|
|
923
|
+
<TableCell className="text-fm-tertiary">{t.duration}</TableCell>
|
|
924
|
+
<TableCell>
|
|
913
925
|
<Badge
|
|
914
|
-
color={
|
|
926
|
+
color={t.status === "Playing" ? "positive" : "neutral"}
|
|
927
|
+
className={
|
|
928
|
+
t.status === "Playing"
|
|
929
|
+
? "bg-fm-surface-positive-sec text-fm-positive-sec"
|
|
930
|
+
: undefined
|
|
931
|
+
}
|
|
915
932
|
>
|
|
916
|
-
{
|
|
933
|
+
{t.status}
|
|
917
934
|
</Badge>
|
|
918
935
|
</TableCell>
|
|
919
936
|
</TableRow>
|
|
@@ -922,175 +939,12 @@ export const DenseTable: Story = {
|
|
|
922
939
|
</Table>
|
|
923
940
|
</div>
|
|
924
941
|
),
|
|
925
|
-
parameters: {
|
|
926
|
-
docs: {
|
|
927
|
-
description: {
|
|
928
|
-
story: "Compact table with reduced padding for dense data display.",
|
|
929
|
-
},
|
|
930
|
-
},
|
|
931
|
-
},
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
// 9. Complete Showcase
|
|
935
|
-
export const CompleteShowcase: Story = {
|
|
936
|
-
render: () => {
|
|
937
|
-
const [selectedRows] = React.useState<number[]>([])
|
|
938
|
-
const [currentPage, setCurrentPage] = React.useState(1)
|
|
939
|
-
const itemsPerPage = 3
|
|
940
|
-
|
|
941
|
-
const paginatedData = sampleUsers.slice(
|
|
942
|
-
(currentPage - 1) * itemsPerPage,
|
|
943
|
-
currentPage * itemsPerPage
|
|
944
|
-
)
|
|
945
|
-
|
|
946
|
-
const totalPages = Math.ceil(sampleUsers.length / itemsPerPage)
|
|
947
|
-
|
|
948
|
-
return (
|
|
949
|
-
<div className="space-y-6 p-8">
|
|
950
|
-
<div className="flex items-center justify-between">
|
|
951
|
-
<div>
|
|
952
|
-
<h2 className="text-fm-primary text-xl font-bold">
|
|
953
|
-
User Management
|
|
954
|
-
</h2>
|
|
955
|
-
<p className="text-fm-secondary">
|
|
956
|
-
Manage your team members and their permissions
|
|
957
|
-
</p>
|
|
958
|
-
</div>
|
|
959
|
-
<Button>Add User</Button>
|
|
960
|
-
</div>
|
|
961
|
-
|
|
962
|
-
<Table>
|
|
963
|
-
<TableCaption>
|
|
964
|
-
A list of all users in your organization with their current status
|
|
965
|
-
and roles
|
|
966
|
-
</TableCaption>
|
|
967
|
-
<TableHeader>
|
|
968
|
-
<TableRow>
|
|
969
|
-
<TableHead>
|
|
970
|
-
<Checkbox />
|
|
971
|
-
</TableHead>
|
|
972
|
-
<TableHead>User</TableHead>
|
|
973
|
-
<TableHead>Role</TableHead>
|
|
974
|
-
<TableHead>Status</TableHead>
|
|
975
|
-
<TableHead>Last Activity</TableHead>
|
|
976
|
-
<TableHead className="text-right">Actions</TableHead>
|
|
977
|
-
</TableRow>
|
|
978
|
-
</TableHeader>
|
|
979
|
-
<TableBody>
|
|
980
|
-
{paginatedData.map((user) => (
|
|
981
|
-
<TableRow
|
|
982
|
-
key={user.id}
|
|
983
|
-
data-state={
|
|
984
|
-
selectedRows.includes(user.id) ? "selected" : undefined
|
|
985
|
-
}
|
|
986
|
-
>
|
|
987
|
-
<TableCell>
|
|
988
|
-
<Checkbox />
|
|
989
|
-
</TableCell>
|
|
990
|
-
<TableCell>
|
|
991
|
-
<div className="flex items-center gap-3">
|
|
992
|
-
<div className="bg-fm-surface-secondary text-fm-primary flex h-10 w-10 items-center justify-center rounded-full text-sm font-medium">
|
|
993
|
-
{user.avatar}
|
|
994
|
-
</div>
|
|
995
|
-
<div>
|
|
996
|
-
<div className="text-fm-primary font-medium">
|
|
997
|
-
{user.name}
|
|
998
|
-
</div>
|
|
999
|
-
<div className="text-fm-secondary text-sm">
|
|
1000
|
-
{user.email}
|
|
1001
|
-
</div>
|
|
1002
|
-
</div>
|
|
1003
|
-
</div>
|
|
1004
|
-
</TableCell>
|
|
1005
|
-
<TableCell>
|
|
1006
|
-
<Badge color="info">{user.role}</Badge>
|
|
1007
|
-
</TableCell>
|
|
1008
|
-
<TableCell>
|
|
1009
|
-
<div className="flex items-center gap-2">
|
|
1010
|
-
<div
|
|
1011
|
-
className={`h-2 w-2 rounded-full ${
|
|
1012
|
-
user.status === "Active"
|
|
1013
|
-
? "bg-fm-surface-positive"
|
|
1014
|
-
: "bg-fm-surface-tertiary"
|
|
1015
|
-
}`}
|
|
1016
|
-
/>
|
|
1017
|
-
<span
|
|
1018
|
-
className={
|
|
1019
|
-
user.status === "Active"
|
|
1020
|
-
? "text-fm-positive"
|
|
1021
|
-
: "text-fm-tertiary"
|
|
1022
|
-
}
|
|
1023
|
-
>
|
|
1024
|
-
{user.status}
|
|
1025
|
-
</span>
|
|
1026
|
-
</div>
|
|
1027
|
-
</TableCell>
|
|
1028
|
-
<TableCell className="text-fm-tertiary">
|
|
1029
|
-
{user.lastSeen}
|
|
1030
|
-
</TableCell>
|
|
1031
|
-
<TableCell>
|
|
1032
|
-
<div className="flex items-center justify-end gap-2">
|
|
1033
|
-
<Button size="sm" variant="text">
|
|
1034
|
-
<EyeOpenIcon className="h-4 w-4" />
|
|
1035
|
-
</Button>
|
|
1036
|
-
<Button size="sm" variant="text">
|
|
1037
|
-
<EditBigIcon className="h-4 w-4" />
|
|
1038
|
-
</Button>
|
|
1039
|
-
<Button
|
|
1040
|
-
size="sm"
|
|
1041
|
-
variant="text"
|
|
1042
|
-
className="text-fm-icon-negative hover:text-fm-icon-negative"
|
|
1043
|
-
>
|
|
1044
|
-
<TrashIcon className="h-4 w-4" />
|
|
1045
|
-
</Button>
|
|
1046
|
-
</div>
|
|
1047
|
-
</TableCell>
|
|
1048
|
-
</TableRow>
|
|
1049
|
-
))}
|
|
1050
|
-
</TableBody>
|
|
1051
|
-
<TableFooter>
|
|
1052
|
-
<TableRow>
|
|
1053
|
-
<TableCell colSpan={6}>
|
|
1054
|
-
<div className="flex items-center justify-between">
|
|
1055
|
-
<div className="text-fm-secondary text-sm">
|
|
1056
|
-
Showing {(currentPage - 1) * itemsPerPage + 1} to{" "}
|
|
1057
|
-
{Math.min(currentPage * itemsPerPage, sampleUsers.length)}{" "}
|
|
1058
|
-
of {sampleUsers.length} users
|
|
1059
|
-
</div>
|
|
1060
|
-
<div className="flex gap-2">
|
|
1061
|
-
<Button
|
|
1062
|
-
size="sm"
|
|
1063
|
-
variant="outline"
|
|
1064
|
-
disabled={currentPage === 1}
|
|
1065
|
-
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
|
|
1066
|
-
>
|
|
1067
|
-
Previous
|
|
1068
|
-
</Button>
|
|
1069
|
-
<Button
|
|
1070
|
-
size="sm"
|
|
1071
|
-
variant="outline"
|
|
1072
|
-
disabled={currentPage === totalPages}
|
|
1073
|
-
onClick={() =>
|
|
1074
|
-
setCurrentPage((p) => Math.min(totalPages, p + 1))
|
|
1075
|
-
}
|
|
1076
|
-
>
|
|
1077
|
-
Next
|
|
1078
|
-
</Button>
|
|
1079
|
-
</div>
|
|
1080
|
-
</div>
|
|
1081
|
-
</TableCell>
|
|
1082
|
-
</TableRow>
|
|
1083
|
-
</TableFooter>
|
|
1084
|
-
</Table>
|
|
1085
|
-
</div>
|
|
1086
|
-
)
|
|
1087
|
-
},
|
|
1088
942
|
parameters: {
|
|
1089
943
|
layout: "fullscreen",
|
|
1090
944
|
docs: {
|
|
1091
945
|
description: {
|
|
1092
946
|
story:
|
|
1093
|
-
"
|
|
947
|
+
"Controls-driven story. Use the Storybook sidebar to adjust the className prop and observe styling changes on the table root.",
|
|
1094
948
|
},
|
|
1095
949
|
},
|
|
1096
950
|
},
|