aural-ui 2.0.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/LICENSE +21 -0
- package/README.md +456 -0
- package/dist/components/aspect-ratio/AspectRatio.stories.tsx +1327 -0
- package/dist/components/aspect-ratio/index.tsx +10 -0
- package/dist/components/aspect-ratio/meta.ts +8 -0
- package/dist/components/avatar/Avatar.stories.tsx +645 -0
- package/dist/components/avatar/index.tsx +50 -0
- package/dist/components/avatar/meta.ts +8 -0
- package/dist/components/badge/Badge.stories.tsx +169 -0
- package/dist/components/badge/index.tsx +28 -0
- package/dist/components/badge/meta.ts +6 -0
- package/dist/components/banner/Banner.stories.tsx +475 -0
- package/dist/components/banner/index.tsx +256 -0
- package/dist/components/banner/meta.ts +36 -0
- package/dist/components/button/Button.stories.tsx +74 -0
- package/dist/components/button/index.tsx +158 -0
- package/dist/components/button/meta.ts +33 -0
- package/dist/components/card/Card.stories.tsx +377 -0
- package/dist/components/card/index.tsx +85 -0
- package/dist/components/card/meta.ts +14 -0
- package/dist/components/char-count/CharCount.stories.tsx +334 -0
- package/dist/components/char-count/index.tsx +51 -0
- package/dist/components/char-count/meta.ts +13 -0
- package/dist/components/checkbox/Checkbox.stories.tsx +209 -0
- package/dist/components/checkbox/index.tsx +34 -0
- package/dist/components/checkbox/meta.ts +19 -0
- package/dist/components/chip/Chip.stories.tsx +207 -0
- package/dist/components/chip/index.tsx +92 -0
- package/dist/components/chip/meta.ts +17 -0
- package/dist/components/circular-loader/CircularLoader.stories.tsx +741 -0
- package/dist/components/circular-loader/index.tsx +138 -0
- package/dist/components/circular-loader/meta.ts +11 -0
- package/dist/components/collapsible/Collapsible.stories.tsx +319 -0
- package/dist/components/collapsible/index.tsx +158 -0
- package/dist/components/collapsible/meta.ts +22 -0
- package/dist/components/command/Command.stories.tsx +996 -0
- package/dist/components/command/index.tsx +324 -0
- package/dist/components/command/meta.ts +18 -0
- package/dist/components/dialog/Dialog.stories.tsx +963 -0
- package/dist/components/dialog/index.tsx +250 -0
- package/dist/components/dialog/meta.ts +28 -0
- package/dist/components/divider/Divider.stories.tsx +633 -0
- package/dist/components/divider/index.tsx +181 -0
- package/dist/components/divider/meta.ts +12 -0
- package/dist/components/dot-loader/DotLoader.stories.tsx +352 -0
- package/dist/components/dot-loader/index.tsx +78 -0
- package/dist/components/dot-loader/meta.ts +14 -0
- package/dist/components/dropdown/Dropdown.stories.tsx +1210 -0
- package/dist/components/dropdown/index.tsx +479 -0
- package/dist/components/dropdown/meta.ts +21 -0
- package/dist/components/form/Form.stories.tsx +320 -0
- package/dist/components/form/index.tsx +183 -0
- package/dist/components/form/meta.ts +11 -0
- package/dist/components/helper-text/HelperText.stories.tsx +254 -0
- package/dist/components/helper-text/index.tsx +102 -0
- package/dist/components/helper-text/meta.ts +18 -0
- package/dist/components/hover-card/HoverCard.stories.tsx +1328 -0
- package/dist/components/hover-card/index.tsx +42 -0
- package/dist/components/hover-card/meta.ts +12 -0
- package/dist/components/icon-button/IconButton.stories.tsx +252 -0
- package/dist/components/icon-button/index.tsx +130 -0
- package/dist/components/icon-button/meta.ts +20 -0
- package/dist/components/if-else/if-else.stories.tsx +100 -0
- package/dist/components/if-else/index.tsx +56 -0
- package/dist/components/if-else/meta.ts +6 -0
- package/dist/components/index.ts +70 -0
- package/dist/components/input/Input.stories.tsx +431 -0
- package/dist/components/input/index.tsx +487 -0
- package/dist/components/input/meta.ts +28 -0
- package/dist/components/label/Label.stories.tsx +200 -0
- package/dist/components/label/index.tsx +43 -0
- package/dist/components/label/meta.ts +14 -0
- package/dist/components/list/List.stories.tsx +963 -0
- package/dist/components/list/index.tsx +567 -0
- package/dist/components/list/meta.ts +24 -0
- package/dist/components/marquee/Marquee.stories.tsx +819 -0
- package/dist/components/marquee/index.tsx +107 -0
- package/dist/components/marquee/meta.ts +6 -0
- package/dist/components/overlay/Overlay.stories.tsx +954 -0
- package/dist/components/overlay/index.tsx +58 -0
- package/dist/components/overlay/meta.ts +10 -0
- package/dist/components/pagination/Pagination.stories.tsx +354 -0
- package/dist/components/pagination/index.tsx +455 -0
- package/dist/components/pagination/meta.ts +29 -0
- package/dist/components/popover/Popover.stories.tsx +1037 -0
- package/dist/components/popover/index.tsx +67 -0
- package/dist/components/popover/meta.ts +12 -0
- package/dist/components/radio/Radio.stories.tsx +146 -0
- package/dist/components/radio/index.tsx +41 -0
- package/dist/components/radio/meta.ts +19 -0
- package/dist/components/resizable/Resizable.stories.tsx +866 -0
- package/dist/components/resizable/index.tsx +55 -0
- package/dist/components/resizable/meta.ts +12 -0
- package/dist/components/scroll-area/ScrollArea.stories.tsx +1104 -0
- package/dist/components/scroll-area/index.tsx +55 -0
- package/dist/components/scroll-area/meta.ts +8 -0
- package/dist/components/search/Search.stories.tsx +678 -0
- package/dist/components/search/index.tsx +141 -0
- package/dist/components/search/meta.ts +6 -0
- package/dist/components/select/Select.stories.tsx +962 -0
- package/dist/components/select/index.tsx +512 -0
- package/dist/components/select/meta.ts +40 -0
- package/dist/components/sheet/Sheet.stories.tsx +1060 -0
- package/dist/components/sheet/index.tsx +440 -0
- package/dist/components/sheet/meta.ts +38 -0
- package/dist/components/skelton/Skelton.stories.tsx +859 -0
- package/dist/components/skelton/index.tsx +17 -0
- package/dist/components/skelton/meta.ts +6 -0
- package/dist/components/slider/Slider.stories.tsx +876 -0
- package/dist/components/slider/index.tsx +156 -0
- package/dist/components/slider/meta.ts +29 -0
- package/dist/components/stepper/Stepper.stories.tsx +639 -0
- package/dist/components/stepper/index.tsx +650 -0
- package/dist/components/stepper/meta.ts +19 -0
- package/dist/components/switch/Switch.stories.tsx +85 -0
- package/dist/components/switch/index.tsx +37 -0
- package/dist/components/switch/meta.ts +24 -0
- package/dist/components/switch-case/SwitchCase.stories.tsx +209 -0
- package/dist/components/switch-case/index.tsx +89 -0
- package/dist/components/switch-case/meta.ts +6 -0
- package/dist/components/table/Table.stories.tsx +1095 -0
- package/dist/components/table/index.tsx +113 -0
- package/dist/components/table/meta.ts +20 -0
- package/dist/components/tabs/Tabs.stories.tsx +1379 -0
- package/dist/components/tabs/index.tsx +186 -0
- package/dist/components/tabs/meta.ts +25 -0
- package/dist/components/tag/Tag.stories.tsx +625 -0
- package/dist/components/tag/index.tsx +320 -0
- package/dist/components/tag/meta.ts +52 -0
- package/dist/components/textarea/TextArea.stories.tsx +723 -0
- package/dist/components/textarea/index.tsx +480 -0
- package/dist/components/textarea/meta.ts +23 -0
- package/dist/components/toast/Toast.stories.tsx +1427 -0
- package/dist/components/toast/index.tsx +26 -0
- package/dist/components/toast/meta.ts +19 -0
- package/dist/components/toggle/Toggle.stories.tsx +1093 -0
- package/dist/components/toggle/index.tsx +44 -0
- package/dist/components/toggle/meta.ts +19 -0
- package/dist/components/tooltip/Tooltip.stories.tsx +1548 -0
- package/dist/components/tooltip/index.tsx +304 -0
- package/dist/components/tooltip/meta.ts +21 -0
- package/dist/components/typography/Typography.stories.tsx +197 -0
- package/dist/components/typography/index.tsx +184 -0
- package/dist/components/typography/meta.ts +38 -0
- package/dist/fonts/LabGrotesque-Regular.ttf +0 -0
- package/dist/fonts/LabGrotesqueTRIAL-Bold.otf +0 -0
- package/dist/fonts/LabGrotesqueTRIAL-Light.otf +0 -0
- package/dist/fonts/LabGrotesqueTRIAL-Medium.otf +0 -0
- package/dist/fonts/LabGrotesqueTRIAL-Regular.otf +0 -0
- package/dist/fonts/PPSupplySans-Regular (1).otf +0 -0
- package/dist/fonts/PPSupplySans-Regular.otf +0 -0
- package/dist/fonts/PPSupplySans-Ultralight.otf +0 -0
- package/dist/hooks/index.ts +3 -0
- package/dist/hooks/use-previous/UsePrevious.stories.tsx +997 -0
- package/dist/hooks/use-previous/index.ts +15 -0
- package/dist/hooks/use-previous/meta.ts +6 -0
- package/dist/hooks/use-standalone-pagination/UseStandalonePagination.stories.tsx +983 -0
- package/dist/hooks/use-standalone-pagination/index.ts +146 -0
- package/dist/hooks/use-standalone-pagination/meta.ts +6 -0
- package/dist/icons/Icons.stories.tsx +29 -0
- package/dist/icons/alert-icon/AlertIcon.stories.tsx +991 -0
- package/dist/icons/alert-icon/index.tsx +48 -0
- package/dist/icons/alert-icon/meta.ts +8 -0
- package/dist/icons/all-icons.tsx +738 -0
- package/dist/icons/angle-down-icon/AngleDownIcon.stories.tsx +1031 -0
- package/dist/icons/angle-down-icon/index.tsx +25 -0
- package/dist/icons/angle-down-icon/meta.ts +8 -0
- package/dist/icons/arrow-box-left-icon/ArrowBoxLeftIcon.stories.tsx +1080 -0
- package/dist/icons/arrow-box-left-icon/index.tsx +24 -0
- package/dist/icons/arrow-box-left-icon/meta.ts +8 -0
- package/dist/icons/arrow-right-icon/ArrowRightIcon.stories.tsx +1151 -0
- package/dist/icons/arrow-right-icon/index.tsx +26 -0
- package/dist/icons/arrow-right-icon/meta.ts +8 -0
- package/dist/icons/arrow-right-up-icon/ArrowRightUpIcon.stories.tsx +1273 -0
- package/dist/icons/arrow-right-up-icon/index.tsx +24 -0
- package/dist/icons/arrow-right-up-icon/meta.ts +8 -0
- package/dist/icons/art-board-icon/ArtBoardIcon.stories.tsx +670 -0
- package/dist/icons/art-board-icon/index.tsx +24 -0
- package/dist/icons/art-board-icon/meta.ts +7 -0
- package/dist/icons/audio-bar-icon/AudioBarIcon.stories.tsx +1244 -0
- package/dist/icons/audio-bar-icon/index.tsx +19 -0
- package/dist/icons/audio-bar-icon/meta.ts +8 -0
- package/dist/icons/bubble-check-icon/BubbleCheckIcon.stories.tsx +1239 -0
- package/dist/icons/bubble-check-icon/index.tsx +24 -0
- package/dist/icons/bubble-check-icon/meta.ts +8 -0
- package/dist/icons/bubble-crossed-icon/BubbleCrossedIcon.stories.tsx +1228 -0
- package/dist/icons/bubble-crossed-icon/index.tsx +24 -0
- package/dist/icons/bubble-crossed-icon/meta.ts +8 -0
- package/dist/icons/bubble-sparkle-icon/BubbleSparkleIcon.stories.tsx +912 -0
- package/dist/icons/bubble-sparkle-icon/index.tsx +26 -0
- package/dist/icons/bubble-sparkle-icon/meta.ts +8 -0
- package/dist/icons/chevron-double-left-icon/ChevronDoubleLeftIcon.stories.tsx +1021 -0
- package/dist/icons/chevron-double-left-icon/index.tsx +34 -0
- package/dist/icons/chevron-double-left-icon/meta.ts +8 -0
- package/dist/icons/chevron-double-right-icon/ChevronDoubleRightIcon.stories.tsx +1021 -0
- package/dist/icons/chevron-double-right-icon/index.tsx +34 -0
- package/dist/icons/chevron-double-right-icon/meta.ts +8 -0
- package/dist/icons/chevron-down-icon/ChevronDownIcon.stories.tsx +1001 -0
- package/dist/icons/chevron-down-icon/index.tsx +27 -0
- package/dist/icons/chevron-down-icon/meta.ts +8 -0
- package/dist/icons/chevron-left-icon/ChevronLeftIcon.stories.tsx +1029 -0
- package/dist/icons/chevron-left-icon/index.tsx +27 -0
- package/dist/icons/chevron-left-icon/meta.ts +8 -0
- package/dist/icons/chevron-right-icon/ChevronRightIcon.stories.tsx +1021 -0
- package/dist/icons/chevron-right-icon/index.tsx +27 -0
- package/dist/icons/chevron-right-icon/meta.ts +8 -0
- package/dist/icons/chevron-up-icon/ChevronUpIcon.stories.tsx +1036 -0
- package/dist/icons/chevron-up-icon/index.tsx +27 -0
- package/dist/icons/chevron-up-icon/meta.ts +8 -0
- package/dist/icons/command-icon/CommandIcon.stories.tsx +1098 -0
- package/dist/icons/command-icon/index.tsx +24 -0
- package/dist/icons/command-icon/meta.ts +8 -0
- package/dist/icons/cross-circle-icon/CrossCircleIcon.stories.tsx +1061 -0
- package/dist/icons/cross-circle-icon/index.tsx +23 -0
- package/dist/icons/cross-circle-icon/meta.ts +8 -0
- package/dist/icons/cross-icon/CrossIcon.stories.tsx +1027 -0
- package/dist/icons/cross-icon/index.tsx +24 -0
- package/dist/icons/cross-icon/meta.ts +8 -0
- package/dist/icons/edit-big-icon/EditBigIcon.stories.tsx +1092 -0
- package/dist/icons/edit-big-icon/index.tsx +22 -0
- package/dist/icons/edit-big-icon/meta.ts +8 -0
- package/dist/icons/eye-close-icon/EyeCloseIcon.stories.tsx +1090 -0
- package/dist/icons/eye-close-icon/index.tsx +26 -0
- package/dist/icons/eye-close-icon/meta.ts +8 -0
- package/dist/icons/eye-open-icon/EyeOpenIcon.stories.tsx +1098 -0
- package/dist/icons/eye-open-icon/index.tsx +24 -0
- package/dist/icons/eye-open-icon/meta.ts +8 -0
- package/dist/icons/feature-shine-icon/FeatureShineIcon.stories.tsx +1071 -0
- package/dist/icons/feature-shine-icon/index.tsx +29 -0
- package/dist/icons/feature-shine-icon/meta.ts +8 -0
- package/dist/icons/file-chart-icon/FileChartIcon.stories.tsx +1115 -0
- package/dist/icons/file-chart-icon/index.tsx +24 -0
- package/dist/icons/file-chart-icon/meta.ts +8 -0
- package/dist/icons/file-text-icon/FileTextIcon.stories.tsx +668 -0
- package/dist/icons/file-text-icon/index.tsx +24 -0
- package/dist/icons/file-text-icon/meta.ts +8 -0
- package/dist/icons/grip-vertical-icon/GripVerticalIcon.stories.tsx +1239 -0
- package/dist/icons/grip-vertical-icon/index.tsx +28 -0
- package/dist/icons/grip-vertical-icon/meta.ts +8 -0
- package/dist/icons/image-icon/ImageIcon.stories.tsx +1181 -0
- package/dist/icons/image-icon/index.tsx +24 -0
- package/dist/icons/image-icon/meta.ts +8 -0
- package/dist/icons/import-folder-icon/ImportFolderIcon.stories.tsx +1248 -0
- package/dist/icons/import-folder-icon/index.tsx +22 -0
- package/dist/icons/import-folder-icon/meta.ts +8 -0
- package/dist/icons/index.ts +46 -0
- package/dist/icons/light-bulb-simple-icon/LightBulbSimpleIcon.stories.tsx +1272 -0
- package/dist/icons/light-bulb-simple-icon/index.tsx +24 -0
- package/dist/icons/light-bulb-simple-icon/meta.ts +8 -0
- package/dist/icons/magic-book-icon/MagicBookIcon.stories.tsx +1245 -0
- package/dist/icons/magic-book-icon/index.tsx +32 -0
- package/dist/icons/magic-book-icon/meta.ts +8 -0
- package/dist/icons/maintenance-icon/MaintenanceIcon.stories.tsx +1251 -0
- package/dist/icons/maintenance-icon/index.tsx +23 -0
- package/dist/icons/maintenance-icon/meta.ts +8 -0
- package/dist/icons/message-icon/MessageIcon.stories.tsx +595 -0
- package/dist/icons/message-icon/index.tsx +30 -0
- package/dist/icons/message-icon/meta.ts +8 -0
- package/dist/icons/move-horizontal-icon/MoveHorizontalIcon.stories.tsx +1245 -0
- package/dist/icons/move-horizontal-icon/index.tsx +23 -0
- package/dist/icons/move-horizontal-icon/meta.ts +8 -0
- package/dist/icons/move-vertical-icon/MoveVerticalIcon.stories.tsx +1196 -0
- package/dist/icons/move-vertical-icon/index.tsx +23 -0
- package/dist/icons/move-vertical-icon/meta.ts +8 -0
- package/dist/icons/page-search-icon/PageSearchIcon.stories.tsx +1167 -0
- package/dist/icons/page-search-icon/index.tsx +21 -0
- package/dist/icons/page-search-icon/meta.ts +8 -0
- package/dist/icons/pencil-icon/PencilIcon.stories.tsx +1131 -0
- package/dist/icons/pencil-icon/index.tsx +21 -0
- package/dist/icons/pencil-icon/meta.ts +8 -0
- package/dist/icons/plus-icon/PlusIcon.stories.tsx +1151 -0
- package/dist/icons/plus-icon/index.tsx +24 -0
- package/dist/icons/plus-icon/meta.ts +8 -0
- package/dist/icons/search-icon/SearchIcon.stories.tsx +1181 -0
- package/dist/icons/search-icon/index.tsx +24 -0
- package/dist/icons/search-icon/meta.ts +8 -0
- package/dist/icons/site-logo-icon/SiteLogoIcon.stories.tsx +1167 -0
- package/dist/icons/site-logo-icon/index.tsx +79 -0
- package/dist/icons/site-logo-icon/meta.ts +8 -0
- package/dist/icons/spinner-gradient-icon/SpinnerGradientIcon.stories.tsx +637 -0
- package/dist/icons/spinner-gradient-icon/index.tsx +53 -0
- package/dist/icons/spinner-gradient-icon/meta.ts +8 -0
- package/dist/icons/spinner-solid-icon/SpinnerSolidIcon.stories.tsx +644 -0
- package/dist/icons/spinner-solid-icon/index.tsx +59 -0
- package/dist/icons/spinner-solid-icon/meta.ts +8 -0
- package/dist/icons/spinner-solid-neutral-icon/SpinnerSolidINeutralcon.stories.tsx +736 -0
- package/dist/icons/spinner-solid-neutral-icon/index.tsx +53 -0
- package/dist/icons/spinner-solid-neutral-icon/meta.ts +8 -0
- package/dist/icons/tick-circle-icon/TickCircleIcon.stories.tsx +1204 -0
- package/dist/icons/tick-circle-icon/index.tsx +23 -0
- package/dist/icons/tick-circle-icon/meta.ts +8 -0
- package/dist/icons/tick-icon/TickIcon.stories.tsx +1340 -0
- package/dist/icons/tick-icon/index.tsx +24 -0
- package/dist/icons/tick-icon/meta.ts +8 -0
- package/dist/icons/trash-icon/TrashIcon.stories.tsx +996 -0
- package/dist/icons/trash-icon/index.tsx +24 -0
- package/dist/icons/trash-icon/meta.ts +8 -0
- package/dist/icons/upload-icon/UploadIcon.stories.tsx +947 -0
- package/dist/icons/upload-icon/index.tsx +24 -0
- package/dist/icons/upload-icon/meta.ts +8 -0
- package/dist/icons/vertical-menu-icon/VerticalMenuIcon.stories.tsx +1045 -0
- package/dist/icons/vertical-menu-icon/index.tsx +27 -0
- package/dist/icons/vertical-menu-icon/meta.ts +8 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +206 -0
- package/dist/lib/utils.ts +6 -0
- package/dist/styles/aural-theme.css +1008 -0
- package/package.json +142 -0
|
@@ -0,0 +1,1095 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { Badge } from "@components/badge"
|
|
3
|
+
import { Button } from "@components/button"
|
|
4
|
+
import { Checkbox } from "@components/checkbox"
|
|
5
|
+
import {
|
|
6
|
+
ChevronDownIcon,
|
|
7
|
+
ChevronUpIcon,
|
|
8
|
+
EditBigIcon,
|
|
9
|
+
EyeOpenIcon,
|
|
10
|
+
TrashIcon,
|
|
11
|
+
VerticalMenuIcon,
|
|
12
|
+
} from "@icons/index"
|
|
13
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
Table,
|
|
17
|
+
TableBody,
|
|
18
|
+
TableCaption,
|
|
19
|
+
TableCell,
|
|
20
|
+
TableFooter,
|
|
21
|
+
TableHead,
|
|
22
|
+
TableHeader,
|
|
23
|
+
TableRow,
|
|
24
|
+
} from "."
|
|
25
|
+
|
|
26
|
+
const meta: Meta<typeof Table> = {
|
|
27
|
+
title: "Components/UI/Table",
|
|
28
|
+
component: Table,
|
|
29
|
+
parameters: {
|
|
30
|
+
layout: "fullscreen",
|
|
31
|
+
backgrounds: {
|
|
32
|
+
default: "dark",
|
|
33
|
+
values: [
|
|
34
|
+
{ name: "dark", value: "#0a0a0a" },
|
|
35
|
+
{ name: "light", value: "#ffffff" },
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
docs: {
|
|
39
|
+
description: {
|
|
40
|
+
component: `
|
|
41
|
+
# Table Component
|
|
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
|
+
`,
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
tags: ["autodocs"],
|
|
231
|
+
argTypes: {
|
|
232
|
+
className: {
|
|
233
|
+
control: "text",
|
|
234
|
+
description: "Additional CSS classes for the table",
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export default meta
|
|
240
|
+
type Story = StoryObj<typeof Table>
|
|
241
|
+
|
|
242
|
+
// Sample data for stories
|
|
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
|
+
]
|
|
329
|
+
|
|
330
|
+
// 1. Basic Table
|
|
331
|
+
export const Basic: Story = {
|
|
332
|
+
render: () => (
|
|
333
|
+
<div className="p-8">
|
|
334
|
+
<Table>
|
|
335
|
+
<TableHeader>
|
|
336
|
+
<TableRow>
|
|
337
|
+
<TableHead>Name</TableHead>
|
|
338
|
+
<TableHead>Email</TableHead>
|
|
339
|
+
<TableHead>Role</TableHead>
|
|
340
|
+
<TableHead>Status</TableHead>
|
|
341
|
+
</TableRow>
|
|
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>
|
|
349
|
+
<TableCell>
|
|
350
|
+
<Badge
|
|
351
|
+
color={user.status === "Active" ? "positive" : "neutral"}
|
|
352
|
+
>
|
|
353
|
+
{user.status}
|
|
354
|
+
</Badge>
|
|
355
|
+
</TableCell>
|
|
356
|
+
</TableRow>
|
|
357
|
+
))}
|
|
358
|
+
</TableBody>
|
|
359
|
+
</Table>
|
|
360
|
+
</div>
|
|
361
|
+
),
|
|
362
|
+
parameters: {
|
|
363
|
+
docs: {
|
|
364
|
+
description: {
|
|
365
|
+
story:
|
|
366
|
+
"Basic table structure with headers and data cells. Shows proper typography hierarchy and spacing.",
|
|
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
|
+
}
|
|
386
|
+
|
|
387
|
+
const handleSelectRow = (id: number) => {
|
|
388
|
+
setSelectedRows((prev) =>
|
|
389
|
+
prev.includes(id) ? prev.filter((rowId) => rowId !== id) : [...prev, id]
|
|
390
|
+
)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return (
|
|
394
|
+
<div className="p-8">
|
|
395
|
+
<Table>
|
|
396
|
+
<TableHeader>
|
|
397
|
+
<TableRow>
|
|
398
|
+
<TableHead>
|
|
399
|
+
<Checkbox checked={selectAll} onChange={handleSelectAll} />
|
|
400
|
+
</TableHead>
|
|
401
|
+
<TableHead>User</TableHead>
|
|
402
|
+
<TableHead>Email</TableHead>
|
|
403
|
+
<TableHead>Role</TableHead>
|
|
404
|
+
<TableHead>Status</TableHead>
|
|
405
|
+
<TableHead>Last Seen</TableHead>
|
|
406
|
+
</TableRow>
|
|
407
|
+
</TableHeader>
|
|
408
|
+
<TableBody>
|
|
409
|
+
{sampleUsers.map((user) => (
|
|
410
|
+
<TableRow
|
|
411
|
+
key={user.id}
|
|
412
|
+
data-state={
|
|
413
|
+
selectedRows.includes(user.id) ? "selected" : undefined
|
|
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
|
+
))}
|
|
446
|
+
</TableBody>
|
|
447
|
+
</Table>
|
|
448
|
+
|
|
449
|
+
<div className="text-fm-secondary mt-4 text-sm">
|
|
450
|
+
{selectedRows.length > 0 && (
|
|
451
|
+
<p>
|
|
452
|
+
{selectedRows.length} row{selectedRows.length !== 1 ? "s" : ""}{" "}
|
|
453
|
+
selected
|
|
454
|
+
</p>
|
|
455
|
+
)}
|
|
456
|
+
</div>
|
|
457
|
+
</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
|
+
|
|
470
|
+
// 3. With Actions
|
|
471
|
+
export const WithActions: Story = {
|
|
472
|
+
render: () => (
|
|
473
|
+
<div className="p-8">
|
|
474
|
+
<Table>
|
|
475
|
+
<TableHeader>
|
|
476
|
+
<TableRow>
|
|
477
|
+
<TableHead>Project</TableHead>
|
|
478
|
+
<TableHead>Status</TableHead>
|
|
479
|
+
<TableHead>Priority</TableHead>
|
|
480
|
+
<TableHead>Assignee</TableHead>
|
|
481
|
+
<TableHead>Due Date</TableHead>
|
|
482
|
+
<TableHead>Progress</TableHead>
|
|
483
|
+
<TableHead className="text-right">Actions</TableHead>
|
|
484
|
+
</TableRow>
|
|
485
|
+
</TableHeader>
|
|
486
|
+
<TableBody>
|
|
487
|
+
{sampleProjects.map((project) => (
|
|
488
|
+
<TableRow key={project.id}>
|
|
489
|
+
<TableCell className="font-medium">{project.name}</TableCell>
|
|
490
|
+
<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>
|
|
502
|
+
</TableCell>
|
|
503
|
+
<TableCell>
|
|
504
|
+
<Badge
|
|
505
|
+
color={
|
|
506
|
+
project.priority === "High"
|
|
507
|
+
? "negative"
|
|
508
|
+
: project.priority === "Medium"
|
|
509
|
+
? "warning"
|
|
510
|
+
: "neutral"
|
|
511
|
+
}
|
|
512
|
+
>
|
|
513
|
+
{project.priority}
|
|
514
|
+
</Badge>
|
|
515
|
+
</TableCell>
|
|
516
|
+
<TableCell>{project.assignee}</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>
|
|
549
|
+
</TableRow>
|
|
550
|
+
))}
|
|
551
|
+
</TableBody>
|
|
552
|
+
</Table>
|
|
553
|
+
</div>
|
|
554
|
+
),
|
|
555
|
+
parameters: {
|
|
556
|
+
docs: {
|
|
557
|
+
description: {
|
|
558
|
+
story:
|
|
559
|
+
"Table with action buttons and rich content including progress bars, badges, and button groups.",
|
|
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
|
|
573
|
+
|
|
574
|
+
return (
|
|
575
|
+
<div className="p-8">
|
|
576
|
+
<Table>
|
|
577
|
+
<TableCaption>User statistics and management overview</TableCaption>
|
|
578
|
+
<TableHeader>
|
|
579
|
+
<TableRow>
|
|
580
|
+
<TableHead>Metric</TableHead>
|
|
581
|
+
<TableHead className="text-right">Count</TableHead>
|
|
582
|
+
<TableHead className="text-right">Percentage</TableHead>
|
|
583
|
+
</TableRow>
|
|
584
|
+
</TableHeader>
|
|
585
|
+
<TableBody>
|
|
586
|
+
<TableRow>
|
|
587
|
+
<TableCell>Active Users</TableCell>
|
|
588
|
+
<TableCell className="text-right font-medium">
|
|
589
|
+
{activeUsers}
|
|
590
|
+
</TableCell>
|
|
591
|
+
<TableCell className="text-right">
|
|
592
|
+
{Math.round((activeUsers / totalUsers) * 100)}%
|
|
593
|
+
</TableCell>
|
|
594
|
+
</TableRow>
|
|
595
|
+
<TableRow>
|
|
596
|
+
<TableCell>Inactive Users</TableCell>
|
|
597
|
+
<TableCell className="text-right font-medium">
|
|
598
|
+
{inactiveUsers}
|
|
599
|
+
</TableCell>
|
|
600
|
+
<TableCell className="text-right">
|
|
601
|
+
{Math.round((inactiveUsers / totalUsers) * 100)}%
|
|
602
|
+
</TableCell>
|
|
603
|
+
</TableRow>
|
|
604
|
+
<TableRow>
|
|
605
|
+
<TableCell>Admins</TableCell>
|
|
606
|
+
<TableCell className="text-right font-medium">
|
|
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>
|
|
617
|
+
</TableRow>
|
|
618
|
+
<TableRow>
|
|
619
|
+
<TableCell>Editors</TableCell>
|
|
620
|
+
<TableCell className="text-right font-medium">
|
|
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>
|
|
631
|
+
</TableRow>
|
|
632
|
+
</TableBody>
|
|
633
|
+
<TableFooter>
|
|
634
|
+
<TableRow>
|
|
635
|
+
<TableCell className="font-bold">Total Users</TableCell>
|
|
636
|
+
<TableCell className="text-right font-bold">
|
|
637
|
+
{totalUsers}
|
|
638
|
+
</TableCell>
|
|
639
|
+
<TableCell className="text-right font-bold">100%</TableCell>
|
|
640
|
+
</TableRow>
|
|
641
|
+
</TableFooter>
|
|
642
|
+
</Table>
|
|
643
|
+
</div>
|
|
644
|
+
)
|
|
645
|
+
},
|
|
646
|
+
parameters: {
|
|
647
|
+
docs: {
|
|
648
|
+
description: {
|
|
649
|
+
story:
|
|
650
|
+
"Table with caption and footer sections. Shows proper semantic structure for complex data tables.",
|
|
651
|
+
},
|
|
652
|
+
},
|
|
653
|
+
},
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// 5. Sortable Table
|
|
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
|
|
666
|
+
|
|
667
|
+
return [...sampleUsers].sort((a, b) => {
|
|
668
|
+
const aValue = a[sortField as keyof typeof a]
|
|
669
|
+
const bValue = b[sortField as keyof typeof b]
|
|
670
|
+
|
|
671
|
+
if (aValue < bValue) return sortDirection === "asc" ? -1 : 1
|
|
672
|
+
if (aValue > bValue) return sortDirection === "asc" ? 1 : -1
|
|
673
|
+
return 0
|
|
674
|
+
})
|
|
675
|
+
}, [sortField, sortDirection])
|
|
676
|
+
|
|
677
|
+
const handleSort = (field: string) => {
|
|
678
|
+
if (sortField === field) {
|
|
679
|
+
setSortDirection(sortDirection === "asc" ? "desc" : "asc")
|
|
680
|
+
} else {
|
|
681
|
+
setSortField(field)
|
|
682
|
+
setSortDirection("asc")
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const SortableHeader = ({
|
|
687
|
+
field,
|
|
688
|
+
children,
|
|
689
|
+
}: {
|
|
690
|
+
field: string
|
|
691
|
+
children: React.ReactNode
|
|
692
|
+
}) => (
|
|
693
|
+
<TableHead>
|
|
694
|
+
<button
|
|
695
|
+
className="hover:text-fm-primary flex items-center gap-2 transition-colors"
|
|
696
|
+
onClick={() => handleSort(field)}
|
|
697
|
+
>
|
|
698
|
+
{children}
|
|
699
|
+
{sortField === field &&
|
|
700
|
+
(sortDirection === "asc" ? (
|
|
701
|
+
<ChevronUpIcon className="h-4 w-4" />
|
|
702
|
+
) : (
|
|
703
|
+
<ChevronDownIcon className="h-4 w-4" />
|
|
704
|
+
))}
|
|
705
|
+
</button>
|
|
706
|
+
</TableHead>
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
return (
|
|
710
|
+
<div className="p-8">
|
|
711
|
+
<Table>
|
|
712
|
+
<TableHeader>
|
|
713
|
+
<TableRow>
|
|
714
|
+
<SortableHeader field="name">Name</SortableHeader>
|
|
715
|
+
<SortableHeader field="email">Email</SortableHeader>
|
|
716
|
+
<SortableHeader field="role">Role</SortableHeader>
|
|
717
|
+
<SortableHeader field="status">Status</SortableHeader>
|
|
718
|
+
<TableHead>Last Seen</TableHead>
|
|
719
|
+
</TableRow>
|
|
720
|
+
</TableHeader>
|
|
721
|
+
<TableBody>
|
|
722
|
+
{sortedUsers.map((user) => (
|
|
723
|
+
<TableRow key={user.id}>
|
|
724
|
+
<TableCell>
|
|
725
|
+
<div className="flex items-center gap-3">
|
|
726
|
+
<div className="bg-fm-surface-secondary text-fm-primary flex h-8 w-8 items-center justify-center rounded-full text-sm font-medium">
|
|
727
|
+
{user.avatar}
|
|
728
|
+
</div>
|
|
729
|
+
<span className="font-medium">{user.name}</span>
|
|
730
|
+
</div>
|
|
731
|
+
</TableCell>
|
|
732
|
+
<TableCell className="text-fm-secondary">
|
|
733
|
+
{user.email}
|
|
734
|
+
</TableCell>
|
|
735
|
+
<TableCell>{user.role}</TableCell>
|
|
736
|
+
<TableCell>
|
|
737
|
+
<Badge
|
|
738
|
+
color={user.status === "Active" ? "positive" : "neutral"}
|
|
739
|
+
>
|
|
740
|
+
{user.status}
|
|
741
|
+
</Badge>
|
|
742
|
+
</TableCell>
|
|
743
|
+
<TableCell className="text-fm-tertiary">
|
|
744
|
+
{user.lastSeen}
|
|
745
|
+
</TableCell>
|
|
746
|
+
</TableRow>
|
|
747
|
+
))}
|
|
748
|
+
</TableBody>
|
|
749
|
+
</Table>
|
|
750
|
+
|
|
751
|
+
<div className="text-fm-secondary mt-4 text-sm">
|
|
752
|
+
{sortField && (
|
|
753
|
+
<p>
|
|
754
|
+
Sorted by {sortField} (
|
|
755
|
+
{sortDirection === "asc" ? "ascending" : "descending"})
|
|
756
|
+
</p>
|
|
757
|
+
)}
|
|
758
|
+
</div>
|
|
759
|
+
</div>
|
|
760
|
+
)
|
|
761
|
+
},
|
|
762
|
+
parameters: {
|
|
763
|
+
docs: {
|
|
764
|
+
description: {
|
|
765
|
+
story:
|
|
766
|
+
"Interactive sortable table with clickable column headers and sort indicators.",
|
|
767
|
+
},
|
|
768
|
+
},
|
|
769
|
+
},
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// 6. Responsive Table
|
|
773
|
+
export const ResponsiveTable: Story = {
|
|
774
|
+
render: () => (
|
|
775
|
+
<div className="p-8">
|
|
776
|
+
<div className="mb-4">
|
|
777
|
+
<h3 className="mb-2 text-lg font-medium text-white">
|
|
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>
|
|
785
|
+
|
|
786
|
+
<Table>
|
|
787
|
+
<TableHeader>
|
|
788
|
+
<TableRow>
|
|
789
|
+
<TableHead>ID</TableHead>
|
|
790
|
+
<TableHead>Full Name</TableHead>
|
|
791
|
+
<TableHead>Email Address</TableHead>
|
|
792
|
+
<TableHead>Role</TableHead>
|
|
793
|
+
<TableHead>Department</TableHead>
|
|
794
|
+
<TableHead>Status</TableHead>
|
|
795
|
+
<TableHead>Created Date</TableHead>
|
|
796
|
+
<TableHead>Last Activity</TableHead>
|
|
797
|
+
<TableHead>Actions</TableHead>
|
|
798
|
+
</TableRow>
|
|
799
|
+
</TableHeader>
|
|
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.",
|
|
842
|
+
},
|
|
843
|
+
},
|
|
844
|
+
},
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// 7. Empty State
|
|
848
|
+
export const EmptyState: Story = {
|
|
849
|
+
render: () => (
|
|
850
|
+
<div className="p-8">
|
|
851
|
+
<Table>
|
|
852
|
+
<TableHeader>
|
|
853
|
+
<TableRow>
|
|
854
|
+
<TableHead>Name</TableHead>
|
|
855
|
+
<TableHead>Email</TableHead>
|
|
856
|
+
<TableHead>Role</TableHead>
|
|
857
|
+
<TableHead>Status</TableHead>
|
|
858
|
+
</TableRow>
|
|
859
|
+
</TableHeader>
|
|
860
|
+
<TableBody>
|
|
861
|
+
<TableRow>
|
|
862
|
+
<TableCell colSpan={4}>
|
|
863
|
+
<div className="flex flex-col items-center justify-center py-12 text-center">
|
|
864
|
+
<div className="bg-fm-surface-secondary mb-4 rounded-full p-3">
|
|
865
|
+
<EyeOpenIcon className="text-fm-tertiary h-6 w-6" />
|
|
866
|
+
</div>
|
|
867
|
+
<h3 className="text-fm-primary mb-2 text-lg font-medium">
|
|
868
|
+
No users found
|
|
869
|
+
</h3>
|
|
870
|
+
<p className="text-fm-secondary mb-4">
|
|
871
|
+
Get started by adding your first user to the system.
|
|
872
|
+
</p>
|
|
873
|
+
<Button>Add User</Button>
|
|
874
|
+
</div>
|
|
875
|
+
</TableCell>
|
|
876
|
+
</TableRow>
|
|
877
|
+
</TableBody>
|
|
878
|
+
</Table>
|
|
879
|
+
</div>
|
|
880
|
+
),
|
|
881
|
+
parameters: {
|
|
882
|
+
docs: {
|
|
883
|
+
description: {
|
|
884
|
+
story:
|
|
885
|
+
"Table with empty state design using colspan for centered content.",
|
|
886
|
+
},
|
|
887
|
+
},
|
|
888
|
+
},
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// 8. Dense Table
|
|
892
|
+
export const DenseTable: Story = {
|
|
893
|
+
render: () => (
|
|
894
|
+
<div className="p-8">
|
|
895
|
+
<Table>
|
|
896
|
+
<TableHeader>
|
|
897
|
+
<TableRow>
|
|
898
|
+
<TableHead className="py-2">Name</TableHead>
|
|
899
|
+
<TableHead className="py-2">Email</TableHead>
|
|
900
|
+
<TableHead className="py-2">Role</TableHead>
|
|
901
|
+
<TableHead className="py-2">Status</TableHead>
|
|
902
|
+
</TableRow>
|
|
903
|
+
</TableHeader>
|
|
904
|
+
<TableBody>
|
|
905
|
+
{sampleUsers.map((user) => (
|
|
906
|
+
<TableRow key={user.id}>
|
|
907
|
+
<TableCell className="py-2">{user.name}</TableCell>
|
|
908
|
+
<TableCell className="text-fm-secondary py-2">
|
|
909
|
+
{user.email}
|
|
910
|
+
</TableCell>
|
|
911
|
+
<TableCell className="py-2">{user.role}</TableCell>
|
|
912
|
+
<TableCell className="py-2">
|
|
913
|
+
<Badge
|
|
914
|
+
color={user.status === "Active" ? "positive" : "neutral"}
|
|
915
|
+
>
|
|
916
|
+
{user.status}
|
|
917
|
+
</Badge>
|
|
918
|
+
</TableCell>
|
|
919
|
+
</TableRow>
|
|
920
|
+
))}
|
|
921
|
+
</TableBody>
|
|
922
|
+
</Table>
|
|
923
|
+
</div>
|
|
924
|
+
),
|
|
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-xl font-bold text-white">User Management</h2>
|
|
953
|
+
<p className="text-fm-secondary">
|
|
954
|
+
Manage your team members and their permissions
|
|
955
|
+
</p>
|
|
956
|
+
</div>
|
|
957
|
+
<Button>Add User</Button>
|
|
958
|
+
</div>
|
|
959
|
+
|
|
960
|
+
<Table>
|
|
961
|
+
<TableCaption>
|
|
962
|
+
A list of all users in your organization with their current status
|
|
963
|
+
and roles
|
|
964
|
+
</TableCaption>
|
|
965
|
+
<TableHeader>
|
|
966
|
+
<TableRow>
|
|
967
|
+
<TableHead>
|
|
968
|
+
<Checkbox />
|
|
969
|
+
</TableHead>
|
|
970
|
+
<TableHead>User</TableHead>
|
|
971
|
+
<TableHead>Role</TableHead>
|
|
972
|
+
<TableHead>Status</TableHead>
|
|
973
|
+
<TableHead>Last Activity</TableHead>
|
|
974
|
+
<TableHead className="text-right">Actions</TableHead>
|
|
975
|
+
</TableRow>
|
|
976
|
+
</TableHeader>
|
|
977
|
+
<TableBody>
|
|
978
|
+
{paginatedData.map((user) => (
|
|
979
|
+
<TableRow
|
|
980
|
+
key={user.id}
|
|
981
|
+
data-state={
|
|
982
|
+
selectedRows.includes(user.id) ? "selected" : undefined
|
|
983
|
+
}
|
|
984
|
+
>
|
|
985
|
+
<TableCell>
|
|
986
|
+
<Checkbox />
|
|
987
|
+
</TableCell>
|
|
988
|
+
<TableCell>
|
|
989
|
+
<div className="flex items-center gap-3">
|
|
990
|
+
<div className="bg-fm-surface-secondary text-fm-primary flex h-10 w-10 items-center justify-center rounded-full text-sm font-medium">
|
|
991
|
+
{user.avatar}
|
|
992
|
+
</div>
|
|
993
|
+
<div>
|
|
994
|
+
<div className="text-fm-primary font-medium">
|
|
995
|
+
{user.name}
|
|
996
|
+
</div>
|
|
997
|
+
<div className="text-fm-secondary text-sm">
|
|
998
|
+
{user.email}
|
|
999
|
+
</div>
|
|
1000
|
+
</div>
|
|
1001
|
+
</div>
|
|
1002
|
+
</TableCell>
|
|
1003
|
+
<TableCell>
|
|
1004
|
+
<Badge color="info">{user.role}</Badge>
|
|
1005
|
+
</TableCell>
|
|
1006
|
+
<TableCell>
|
|
1007
|
+
<div className="flex items-center gap-2">
|
|
1008
|
+
<div
|
|
1009
|
+
className={`h-2 w-2 rounded-full ${
|
|
1010
|
+
user.status === "Active"
|
|
1011
|
+
? "bg-green-500"
|
|
1012
|
+
: "bg-gray-400"
|
|
1013
|
+
}`}
|
|
1014
|
+
/>
|
|
1015
|
+
<span
|
|
1016
|
+
className={
|
|
1017
|
+
user.status === "Active"
|
|
1018
|
+
? "text-green-400"
|
|
1019
|
+
: "text-fm-tertiary"
|
|
1020
|
+
}
|
|
1021
|
+
>
|
|
1022
|
+
{user.status}
|
|
1023
|
+
</span>
|
|
1024
|
+
</div>
|
|
1025
|
+
</TableCell>
|
|
1026
|
+
<TableCell className="text-fm-tertiary">
|
|
1027
|
+
{user.lastSeen}
|
|
1028
|
+
</TableCell>
|
|
1029
|
+
<TableCell>
|
|
1030
|
+
<div className="flex items-center justify-end gap-2">
|
|
1031
|
+
<Button size="sm" variant="text">
|
|
1032
|
+
<EyeOpenIcon className="h-4 w-4" />
|
|
1033
|
+
</Button>
|
|
1034
|
+
<Button size="sm" variant="text">
|
|
1035
|
+
<EditBigIcon className="h-4 w-4" />
|
|
1036
|
+
</Button>
|
|
1037
|
+
<Button
|
|
1038
|
+
size="sm"
|
|
1039
|
+
variant="text"
|
|
1040
|
+
className="text-red-400 hover:text-red-300"
|
|
1041
|
+
>
|
|
1042
|
+
<TrashIcon className="h-4 w-4" />
|
|
1043
|
+
</Button>
|
|
1044
|
+
</div>
|
|
1045
|
+
</TableCell>
|
|
1046
|
+
</TableRow>
|
|
1047
|
+
))}
|
|
1048
|
+
</TableBody>
|
|
1049
|
+
<TableFooter>
|
|
1050
|
+
<TableRow>
|
|
1051
|
+
<TableCell colSpan={6}>
|
|
1052
|
+
<div className="flex items-center justify-between">
|
|
1053
|
+
<div className="text-fm-secondary text-sm">
|
|
1054
|
+
Showing {(currentPage - 1) * itemsPerPage + 1} to{" "}
|
|
1055
|
+
{Math.min(currentPage * itemsPerPage, sampleUsers.length)}{" "}
|
|
1056
|
+
of {sampleUsers.length} users
|
|
1057
|
+
</div>
|
|
1058
|
+
<div className="flex gap-2">
|
|
1059
|
+
<Button
|
|
1060
|
+
size="sm"
|
|
1061
|
+
variant="outline"
|
|
1062
|
+
disabled={currentPage === 1}
|
|
1063
|
+
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
|
|
1064
|
+
>
|
|
1065
|
+
Previous
|
|
1066
|
+
</Button>
|
|
1067
|
+
<Button
|
|
1068
|
+
size="sm"
|
|
1069
|
+
variant="outline"
|
|
1070
|
+
disabled={currentPage === totalPages}
|
|
1071
|
+
onClick={() =>
|
|
1072
|
+
setCurrentPage((p) => Math.min(totalPages, p + 1))
|
|
1073
|
+
}
|
|
1074
|
+
>
|
|
1075
|
+
Next
|
|
1076
|
+
</Button>
|
|
1077
|
+
</div>
|
|
1078
|
+
</div>
|
|
1079
|
+
</TableCell>
|
|
1080
|
+
</TableRow>
|
|
1081
|
+
</TableFooter>
|
|
1082
|
+
</Table>
|
|
1083
|
+
</div>
|
|
1084
|
+
)
|
|
1085
|
+
},
|
|
1086
|
+
parameters: {
|
|
1087
|
+
layout: "fullscreen",
|
|
1088
|
+
docs: {
|
|
1089
|
+
description: {
|
|
1090
|
+
story:
|
|
1091
|
+
"Complete table implementation with all features: selection, actions, pagination, and proper semantic structure.",
|
|
1092
|
+
},
|
|
1093
|
+
},
|
|
1094
|
+
},
|
|
1095
|
+
}
|