@sovereignfs/ui 0.54.0 → 0.89.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +373 -661
- package/NOTICE +8 -0
- package/README.md +4 -6
- package/dist/Accordion.module.css +1 -1
- package/dist/Alert.module.css +1 -1
- package/dist/AppsLauncher.module.css +102 -0
- package/dist/Avatar.module.css +1 -1
- package/dist/Badge.module.css +30 -7
- package/dist/BalanceChip.module.css +1 -1
- package/dist/Button.module.css +23 -2
- package/dist/Card.module.css +7 -1
- package/dist/CardTile.module.css +187 -0
- package/dist/CheckableListRow.module.css +8 -0
- package/dist/Checkbox.module.css +8 -0
- package/dist/CodeTextarea.module.css +7 -1
- package/dist/Collapsible.module.css +1 -1
- package/dist/ColorPicker.module.css +138 -0
- package/dist/Combobox.module.css +8 -2
- package/dist/Command.module.css +7 -1
- package/dist/ConfirmDialog.module.css +8 -2
- package/dist/ConsentPrompt.module.css +21 -0
- package/dist/DatePicker.module.css +7 -1
- package/dist/Dialog.module.css +130 -21
- package/dist/DragHandleRow.module.css +6 -0
- package/dist/Drawer.module.css +36 -7
- package/dist/FileDropzone.module.css +25 -1
- package/dist/Header.module.css +68 -0
- package/dist/HeaderFooterLayout.module.css +21 -0
- package/dist/IconPicker.module.css +15 -5
- package/dist/Input.module.css +7 -1
- package/dist/Kbd.module.css +1 -1
- package/dist/LegalLinks.module.css +9 -2
- package/dist/Markdown.module.css +29 -0
- package/dist/Menu.module.css +1 -0
- package/dist/Message.module.css +10 -1
- package/dist/MessageScroller.module.css +12 -1
- package/dist/MobileAppsDrawer.module.css +19 -14
- package/dist/MobileFooter.module.css +7 -1
- package/dist/MobileHeader.module.css +14 -1
- package/dist/NavList.module.css +139 -0
- package/dist/NavTabs.module.css +8 -2
- package/dist/NotificationsPanel.module.css +268 -0
- package/dist/OverlayHeader.module.css +1 -1
- package/dist/PageContainer.module.css +62 -0
- package/dist/PageHeader.module.css +54 -0
- package/dist/PageLayout.module.css +51 -0
- package/dist/Popover.module.css +1 -1
- package/dist/QrCode.module.css +17 -0
- package/dist/QuantityStepper.module.css +5 -3
- package/dist/RadioGroup.module.css +6 -0
- package/dist/Resizable.module.css +3 -3
- package/dist/RootLayout.module.css +12 -0
- package/dist/SegmentedControl.module.css +7 -1
- package/dist/Select.module.css +7 -1
- package/dist/Sheet.module.css +20 -2
- package/dist/SplitPane.module.css +5 -5
- package/dist/StatusBadge.module.css +1 -1
- package/dist/StepStrip.module.css +55 -0
- package/dist/SuggestionInput.module.css +14 -3
- package/dist/SwipableMobileCarousel.module.css +12 -3
- package/dist/SwipableMobileCarouselDots.module.css +21 -0
- package/dist/SwipeStack.module.css +101 -0
- package/dist/SystemBanner.module.css +16 -0
- package/dist/Table.module.css +2 -2
- package/dist/Tabs.module.css +7 -1
- package/dist/TagInput.module.css +8 -2
- package/dist/Textarea.module.css +15 -1
- package/dist/ThreeColumnLayout.module.css +35 -0
- package/dist/Toast.module.css +67 -3
- package/dist/Toggle.module.css +7 -0
- package/dist/Tooltip.module.css +7 -1
- package/dist/UserMenu.module.css +95 -0
- package/dist/fonts/OFL.txt +93 -0
- package/dist/fonts/hanken-grotesk-cyrillic-ext-wght-normal.woff2 +0 -0
- package/dist/fonts/hanken-grotesk-latin-ext-wght-normal.woff2 +0 -0
- package/dist/fonts/hanken-grotesk-latin-wght-normal.woff2 +0 -0
- package/dist/fonts/hanken-grotesk-vietnamese-wght-normal.woff2 +0 -0
- package/dist/index.d.ts +1156 -76
- package/dist/index.js +4223 -1513
- package/dist/tokens/fonts.css +53 -0
- package/dist/tokens/primitives.css +23 -12
- package/dist/tokens/semantic.css +32 -0
- package/dist/tokens/theme-presets.css +33 -0
- package/dist/tokens.css +2 -1
- package/package.json +46 -19
- package/src/__tests__/motion.test.tsx +0 -105
- package/src/components/Accordion/Accordion.module.css +0 -3
- package/src/components/Accordion/Accordion.stories.tsx +0 -110
- package/src/components/Accordion/Accordion.tsx +0 -53
- package/src/components/Accordion/__tests__/Accordion.test.tsx +0 -61
- package/src/components/Alert/Alert.module.css +0 -62
- package/src/components/Alert/Alert.stories.tsx +0 -96
- package/src/components/Alert/Alert.tsx +0 -58
- package/src/components/Alert/__tests__/Alert.test.tsx +0 -75
- package/src/components/AspectRatio/AspectRatio.module.css +0 -15
- package/src/components/AspectRatio/AspectRatio.stories.tsx +0 -78
- package/src/components/AspectRatio/AspectRatio.tsx +0 -21
- package/src/components/AspectRatio/__tests__/AspectRatio.test.tsx +0 -27
- package/src/components/Avatar/Avatar.module.css +0 -38
- package/src/components/Avatar/Avatar.tsx +0 -42
- package/src/components/Badge/Badge.module.css +0 -154
- package/src/components/Badge/Badge.stories.tsx +0 -226
- package/src/components/Badge/Badge.tsx +0 -81
- package/src/components/Badge/__tests__/Badge.test.tsx +0 -88
- package/src/components/BalanceChip/BalanceChip.module.css +0 -32
- package/src/components/BalanceChip/BalanceChip.stories.tsx +0 -45
- package/src/components/BalanceChip/BalanceChip.tsx +0 -33
- package/src/components/Breadcrumb/Breadcrumb.module.css +0 -49
- package/src/components/Breadcrumb/Breadcrumb.stories.tsx +0 -47
- package/src/components/Breadcrumb/Breadcrumb.tsx +0 -57
- package/src/components/Breadcrumb/__tests__/Breadcrumb.test.tsx +0 -52
- package/src/components/Button/Button.module.css +0 -153
- package/src/components/Button/Button.stories.tsx +0 -83
- package/src/components/Button/Button.tsx +0 -52
- package/src/components/Button/__tests__/Button.test.tsx +0 -66
- package/src/components/ButtonGroup/ButtonGroup.module.css +0 -29
- package/src/components/ButtonGroup/ButtonGroup.stories.tsx +0 -57
- package/src/components/ButtonGroup/ButtonGroup.tsx +0 -27
- package/src/components/ButtonGroup/__tests__/ButtonGroup.test.tsx +0 -29
- package/src/components/Calendar/Calendar.module.css +0 -126
- package/src/components/Calendar/Calendar.tsx +0 -213
- package/src/components/Calendar/__tests__/Calendar.test.tsx +0 -98
- package/src/components/Calendar/dateUtils.ts +0 -97
- package/src/components/Card/Card.module.css +0 -42
- package/src/components/Card/Card.tsx +0 -34
- package/src/components/CheckableListRow/CheckableListRow.module.css +0 -95
- package/src/components/CheckableListRow/CheckableListRow.stories.tsx +0 -99
- package/src/components/CheckableListRow/CheckableListRow.tsx +0 -89
- package/src/components/Checkbox/Checkbox.module.css +0 -111
- package/src/components/Checkbox/Checkbox.tsx +0 -78
- package/src/components/CodeTextarea/CodeTextarea.module.css +0 -53
- package/src/components/CodeTextarea/CodeTextarea.stories.tsx +0 -77
- package/src/components/CodeTextarea/CodeTextarea.tsx +0 -36
- package/src/components/Collapsible/Collapsible.module.css +0 -70
- package/src/components/Collapsible/Collapsible.stories.tsx +0 -62
- package/src/components/Collapsible/Collapsible.tsx +0 -66
- package/src/components/Collapsible/__tests__/Collapsible.test.tsx +0 -71
- package/src/components/Combobox/Combobox.module.css +0 -122
- package/src/components/Combobox/Combobox.stories.tsx +0 -79
- package/src/components/Combobox/Combobox.tsx +0 -186
- package/src/components/Combobox/__tests__/Combobox.test.tsx +0 -158
- package/src/components/Command/Command.module.css +0 -80
- package/src/components/Command/Command.stories.tsx +0 -104
- package/src/components/Command/Command.tsx +0 -168
- package/src/components/Command/__tests__/Command.test.tsx +0 -162
- package/src/components/ConfirmDialog/ConfirmDialog.module.css +0 -113
- package/src/components/ConfirmDialog/ConfirmDialog.tsx +0 -124
- package/src/components/ConfirmDialog/__tests__/ConfirmDialog.test.tsx +0 -146
- package/src/components/ContextMenu/ContextMenu.stories.tsx +0 -81
- package/src/components/ContextMenu/ContextMenu.tsx +0 -100
- package/src/components/ContextMenu/__tests__/ContextMenu.test.tsx +0 -87
- package/src/components/CurrencyInput/CurrencyInput.stories.tsx +0 -50
- package/src/components/CurrencyInput/CurrencyInput.tsx +0 -60
- package/src/components/DataTable/DataTable.module.css +0 -28
- package/src/components/DataTable/DataTable.stories.tsx +0 -74
- package/src/components/DataTable/DataTable.tsx +0 -154
- package/src/components/DataTable/__tests__/DataTable.test.tsx +0 -123
- package/src/components/DatePicker/DatePicker.module.css +0 -61
- package/src/components/DatePicker/DatePicker.tsx +0 -103
- package/src/components/DatePicker/__tests__/DatePicker.test.tsx +0 -89
- package/src/components/DeviceOnlyGate/DeviceOnlyGate.tsx +0 -58
- package/src/components/DeviceOnlyGate/__tests__/DeviceOnlyGate.test.tsx +0 -45
- package/src/components/DeviceStorageKeyGate/DeviceStorageKeyGate.tsx +0 -100
- package/src/components/DeviceStorageKeyGate/__tests__/DeviceStorageKeyGate.test.tsx +0 -119
- package/src/components/Dialog/Dialog.module.css +0 -226
- package/src/components/Dialog/Dialog.stories.tsx +0 -147
- package/src/components/Dialog/Dialog.tsx +0 -160
- package/src/components/Dialog/__tests__/Dialog.test.tsx +0 -137
- package/src/components/DragHandleRow/DragHandleRow.module.css +0 -57
- package/src/components/DragHandleRow/DragHandleRow.tsx +0 -61
- package/src/components/Drawer/Drawer.module.css +0 -92
- package/src/components/Drawer/Drawer.stories.tsx +0 -132
- package/src/components/Drawer/Drawer.tsx +0 -176
- package/src/components/Drawer/__tests__/Drawer.test.tsx +0 -199
- package/src/components/EmptyState/EmptyState.module.css +0 -27
- package/src/components/EmptyState/EmptyState.tsx +0 -27
- package/src/components/FileDropzone/FileDropzone.module.css +0 -63
- package/src/components/FileDropzone/FileDropzone.tsx +0 -109
- package/src/components/FileDropzone/__tests__/FileDropzone.test.tsx +0 -67
- package/src/components/FormField/FormField.module.css +0 -33
- package/src/components/FormField/FormField.tsx +0 -78
- package/src/components/FormField/__tests__/FormField.test.tsx +0 -83
- package/src/components/HoverCard/HoverCard.module.css +0 -3
- package/src/components/HoverCard/HoverCard.stories.tsx +0 -66
- package/src/components/HoverCard/HoverCard.tsx +0 -111
- package/src/components/HoverCard/__tests__/HoverCard.test.tsx +0 -99
- package/src/components/Icon/Icon.module.css +0 -27
- package/src/components/Icon/Icon.stories.tsx +0 -95
- package/src/components/Icon/Icon.tsx +0 -43
- package/src/components/Icon/__tests__/Icon.test.tsx +0 -40
- package/src/components/Icon/icons/activity.tsx +0 -20
- package/src/components/Icon/icons/alert-triangle.tsx +0 -22
- package/src/components/Icon/icons/apple.tsx +0 -21
- package/src/components/Icon/icons/banana.tsx +0 -21
- package/src/components/Icon/icons/beef.tsx +0 -22
- package/src/components/Icon/icons/beer.tsx +0 -24
- package/src/components/Icon/icons/bell.tsx +0 -21
- package/src/components/Icon/icons/calendar.tsx +0 -23
- package/src/components/Icon/icons/candy.tsx +0 -24
- package/src/components/Icon/icons/carrot.tsx +0 -22
- package/src/components/Icon/icons/check.tsx +0 -20
- package/src/components/Icon/icons/chevron-down.tsx +0 -20
- package/src/components/Icon/icons/chevron-left.tsx +0 -20
- package/src/components/Icon/icons/chevron-right.tsx +0 -20
- package/src/components/Icon/icons/chevron-up.tsx +0 -20
- package/src/components/Icon/icons/circle-check.tsx +0 -21
- package/src/components/Icon/icons/circle-x.tsx +0 -22
- package/src/components/Icon/icons/coffee.tsx +0 -23
- package/src/components/Icon/icons/cookie.tsx +0 -25
- package/src/components/Icon/icons/croissant.tsx +0 -24
- package/src/components/Icon/icons/cup-soda.tsx +0 -23
- package/src/components/Icon/icons/drumstick.tsx +0 -21
- package/src/components/Icon/icons/egg.tsx +0 -20
- package/src/components/Icon/icons/ellipsis-vertical.tsx +0 -22
- package/src/components/Icon/icons/external-link.tsx +0 -22
- package/src/components/Icon/icons/eye-off.tsx +0 -23
- package/src/components/Icon/icons/eye.tsx +0 -21
- package/src/components/Icon/icons/file.tsx +0 -21
- package/src/components/Icon/icons/fish.tsx +0 -25
- package/src/components/Icon/icons/grid-2x2.tsx +0 -22
- package/src/components/Icon/icons/house.tsx +0 -21
- package/src/components/Icon/icons/index.ts +0 -234
- package/src/components/Icon/icons/info.tsx +0 -22
- package/src/components/Icon/icons/lock.tsx +0 -21
- package/src/components/Icon/icons/log-out.tsx +0 -22
- package/src/components/Icon/icons/mail.tsx +0 -21
- package/src/components/Icon/icons/menu.tsx +0 -22
- package/src/components/Icon/icons/milk.tsx +0 -22
- package/src/components/Icon/icons/package.tsx +0 -23
- package/src/components/Icon/icons/pencil.tsx +0 -21
- package/src/components/Icon/icons/pizza.tsx +0 -24
- package/src/components/Icon/icons/plus.tsx +0 -21
- package/src/components/Icon/icons/rotate-ccw.tsx +0 -21
- package/src/components/Icon/icons/salad.tsx +0 -24
- package/src/components/Icon/icons/search.tsx +0 -21
- package/src/components/Icon/icons/settings.tsx +0 -21
- package/src/components/Icon/icons/shield.tsx +0 -20
- package/src/components/Icon/icons/shopping-basket.tsx +0 -26
- package/src/components/Icon/icons/sliders-horizontal.tsx +0 -28
- package/src/components/Icon/icons/smartphone.tsx +0 -21
- package/src/components/Icon/icons/snowflake.tsx +0 -31
- package/src/components/Icon/icons/spray-can.tsx +0 -29
- package/src/components/Icon/icons/trash-2.tsx +0 -24
- package/src/components/Icon/icons/upload.tsx +0 -22
- package/src/components/Icon/icons/user.tsx +0 -21
- package/src/components/Icon/icons/wine.tsx +0 -23
- package/src/components/Icon/icons/x.tsx +0 -21
- package/src/components/IconPicker/IconPicker.module.css +0 -63
- package/src/components/IconPicker/IconPicker.stories.tsx +0 -84
- package/src/components/IconPicker/IconPicker.tsx +0 -90
- package/src/components/Input/Input.module.css +0 -52
- package/src/components/Input/Input.stories.tsx +0 -86
- package/src/components/Input/Input.tsx +0 -14
- package/src/components/Input/__tests__/Input.test.tsx +0 -22
- package/src/components/Item/Item.module.css +0 -70
- package/src/components/Item/Item.stories.tsx +0 -62
- package/src/components/Item/Item.tsx +0 -54
- package/src/components/Item/__tests__/Item.test.tsx +0 -39
- package/src/components/Kbd/Kbd.module.css +0 -14
- package/src/components/Kbd/Kbd.stories.tsx +0 -32
- package/src/components/Kbd/Kbd.tsx +0 -12
- package/src/components/Kbd/__tests__/Kbd.test.tsx +0 -14
- package/src/components/Label/Label.module.css +0 -11
- package/src/components/Label/Label.stories.tsx +0 -40
- package/src/components/Label/Label.tsx +0 -24
- package/src/components/Label/__tests__/Label.test.tsx +0 -32
- package/src/components/LegalLinks/LegalLinks.module.css +0 -24
- package/src/components/LegalLinks/LegalLinks.stories.tsx +0 -22
- package/src/components/LegalLinks/LegalLinks.tsx +0 -40
- package/src/components/Markdown/Markdown.module.css +0 -49
- package/src/components/Markdown/Markdown.stories.tsx +0 -30
- package/src/components/Markdown/Markdown.tsx +0 -168
- package/src/components/Marker/Marker.module.css +0 -33
- package/src/components/Marker/Marker.stories.tsx +0 -39
- package/src/components/Marker/Marker.tsx +0 -34
- package/src/components/Marker/__tests__/Marker.test.tsx +0 -23
- package/src/components/MemberMultiSelect/MemberMultiSelect.module.css +0 -27
- package/src/components/MemberMultiSelect/MemberMultiSelect.stories.tsx +0 -74
- package/src/components/MemberMultiSelect/MemberMultiSelect.tsx +0 -67
- package/src/components/Menu/Menu.module.css +0 -89
- package/src/components/Menu/Menu.tsx +0 -159
- package/src/components/Menu/__tests__/Menu.test.tsx +0 -197
- package/src/components/Menubar/Menubar.stories.tsx +0 -52
- package/src/components/Menubar/Menubar.tsx +0 -42
- package/src/components/Menubar/__tests__/Menubar.test.tsx +0 -45
- package/src/components/Message/Message.module.css +0 -99
- package/src/components/Message/Message.stories.tsx +0 -79
- package/src/components/Message/Message.tsx +0 -41
- package/src/components/Message/__tests__/Message.test.tsx +0 -46
- package/src/components/MessageScroller/MessageScroller.module.css +0 -54
- package/src/components/MessageScroller/MessageScroller.stories.tsx +0 -84
- package/src/components/MessageScroller/MessageScroller.tsx +0 -72
- package/src/components/MessageScroller/__tests__/MessageScroller.test.tsx +0 -105
- package/src/components/MobileAppsDrawer/MobileAppsDrawer.module.css +0 -81
- package/src/components/MobileAppsDrawer/MobileAppsDrawer.tsx +0 -78
- package/src/components/MobileAppsDrawer/__tests__/MobileAppsDrawer.test.tsx +0 -80
- package/src/components/MobileFooter/MobileFooter.module.css +0 -73
- package/src/components/MobileFooter/MobileFooter.stories.tsx +0 -47
- package/src/components/MobileFooter/MobileFooter.tsx +0 -109
- package/src/components/MobileFooter/__tests__/MobileFooter.test.tsx +0 -71
- package/src/components/MobileHeader/MobileHeader.module.css +0 -63
- package/src/components/MobileHeader/MobileHeader.stories.tsx +0 -57
- package/src/components/MobileHeader/MobileHeader.tsx +0 -77
- package/src/components/MobileHeader/__tests__/MobileHeader.test.tsx +0 -58
- package/src/components/NavTabs/NavTabs.module.css +0 -50
- package/src/components/NavTabs/NavTabs.tsx +0 -61
- package/src/components/NavigationMenu/NavigationMenu.module.css +0 -53
- package/src/components/NavigationMenu/NavigationMenu.stories.tsx +0 -58
- package/src/components/NavigationMenu/NavigationMenu.tsx +0 -125
- package/src/components/NavigationMenu/__tests__/NavigationMenu.test.tsx +0 -93
- package/src/components/OfflineGate/OfflineGate.tsx +0 -44
- package/src/components/OfflineGate/__tests__/OfflineGate.test.tsx +0 -66
- package/src/components/OverlayHeader/OverlayHeader.module.css +0 -98
- package/src/components/OverlayHeader/OverlayHeader.tsx +0 -62
- package/src/components/OverlayHeader/__tests__/OverlayHeader.test.tsx +0 -51
- package/src/components/PageContainer/PageContainer.module.css +0 -20
- package/src/components/PageContainer/PageContainer.tsx +0 -31
- package/src/components/PageContainer/__tests__/PageContainer.test.tsx +0 -37
- package/src/components/PageHeader/PageHeader.module.css +0 -32
- package/src/components/PageHeader/PageHeader.tsx +0 -35
- package/src/components/Pagination/Pagination.module.css +0 -98
- package/src/components/Pagination/Pagination.stories.tsx +0 -49
- package/src/components/Pagination/Pagination.tsx +0 -87
- package/src/components/Pagination/__tests__/Pagination.test.tsx +0 -56
- package/src/components/Popover/Popover.module.css +0 -74
- package/src/components/Popover/Popover.stories.tsx +0 -357
- package/src/components/Popover/Popover.tsx +0 -208
- package/src/components/Popover/__tests__/Popover.test.tsx +0 -98
- package/src/components/Progress/Progress.module.css +0 -20
- package/src/components/Progress/Progress.stories.tsx +0 -40
- package/src/components/Progress/Progress.tsx +0 -35
- package/src/components/Progress/__tests__/Progress.test.tsx +0 -31
- package/src/components/QuantityStepper/QuantityStepper.module.css +0 -67
- package/src/components/QuantityStepper/QuantityStepper.stories.tsx +0 -79
- package/src/components/QuantityStepper/QuantityStepper.tsx +0 -107
- package/src/components/RadioGroup/RadioGroup.module.css +0 -88
- package/src/components/RadioGroup/RadioGroup.stories.tsx +0 -69
- package/src/components/RadioGroup/RadioGroup.tsx +0 -99
- package/src/components/RadioGroup/__tests__/RadioGroup.test.tsx +0 -59
- package/src/components/Resizable/Resizable.module.css +0 -70
- package/src/components/Resizable/Resizable.stories.tsx +0 -82
- package/src/components/Resizable/Resizable.tsx +0 -313
- package/src/components/Resizable/__tests__/Resizable.test.tsx +0 -128
- package/src/components/ResponsiveSurface/ResponsiveSurface.tsx +0 -24
- package/src/components/ResponsiveSurface/__tests__/ResponsiveSurface.test.tsx +0 -73
- package/src/components/ScrollArea/ScrollArea.module.css +0 -20
- package/src/components/ScrollArea/ScrollArea.stories.tsx +0 -57
- package/src/components/ScrollArea/ScrollArea.tsx +0 -27
- package/src/components/ScrollArea/__tests__/ScrollArea.test.tsx +0 -39
- package/src/components/SegmentedControl/SegmentedControl.module.css +0 -55
- package/src/components/SegmentedControl/SegmentedControl.stories.tsx +0 -118
- package/src/components/SegmentedControl/SegmentedControl.tsx +0 -56
- package/src/components/SegmentedControl/__tests__/SegmentedControl.test.tsx +0 -63
- package/src/components/Select/Select.module.css +0 -82
- package/src/components/Select/Select.stories.tsx +0 -115
- package/src/components/Select/Select.tsx +0 -48
- package/src/components/Select/__tests__/Select.test.tsx +0 -59
- package/src/components/Sheet/Sheet.module.css +0 -55
- package/src/components/Sheet/Sheet.tsx +0 -109
- package/src/components/Sheet/__tests__/Sheet.test.tsx +0 -99
- package/src/components/Slider/Slider.module.css +0 -95
- package/src/components/Slider/Slider.stories.tsx +0 -64
- package/src/components/Slider/Slider.tsx +0 -77
- package/src/components/Slider/__tests__/Slider.test.tsx +0 -45
- package/src/components/Spinner/Spinner.module.css +0 -39
- package/src/components/Spinner/Spinner.tsx +0 -23
- package/src/components/SplitMethodSelector/SplitMethodSelector.stories.tsx +0 -33
- package/src/components/SplitMethodSelector/SplitMethodSelector.tsx +0 -43
- package/src/components/SplitPane/SplitPane.module.css +0 -85
- package/src/components/SplitPane/SplitPane.stories.tsx +0 -160
- package/src/components/SplitPane/SplitPane.tsx +0 -149
- package/src/components/SplitPane/__tests__/SplitPane.test.tsx +0 -51
- package/src/components/StatusBadge/StatusBadge.module.css +0 -59
- package/src/components/StatusBadge/StatusBadge.stories.tsx +0 -74
- package/src/components/StatusBadge/StatusBadge.tsx +0 -65
- package/src/components/SuggestionInput/SuggestionInput.module.css +0 -103
- package/src/components/SuggestionInput/SuggestionInput.stories.tsx +0 -144
- package/src/components/SuggestionInput/SuggestionInput.tsx +0 -207
- package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.module.css +0 -55
- package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.tsx +0 -227
- package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlide.tsx +0 -35
- package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideBody.tsx +0 -42
- package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideFooter.tsx +0 -18
- package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideHeader.tsx +0 -21
- package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideParts.module.css +0 -29
- package/src/components/SwipableMobileCarousel/__tests__/SwipableMobileCarousel.test.tsx +0 -133
- package/src/components/SwipableMobileCarousel/__tests__/SwipableMobileCarouselDevGuard.test.tsx +0 -61
- package/src/components/SwipableMobileCarousel/context.ts +0 -13
- package/src/components/SwipableMobileCarouselDots/SwipableMobileCarouselDots.module.css +0 -49
- package/src/components/SwipableMobileCarouselDots/SwipableMobileCarouselDots.tsx +0 -62
- package/src/components/SwipableMobileCarouselDots/__tests__/SwipableMobileCarouselDots.test.tsx +0 -63
- package/src/components/SystemBanner/SystemBanner.module.css +0 -69
- package/src/components/SystemBanner/SystemBanner.stories.tsx +0 -86
- package/src/components/SystemBanner/SystemBanner.tsx +0 -50
- package/src/components/SystemBanner/__tests__/SystemBanner.test.tsx +0 -50
- package/src/components/Table/Table.module.css +0 -45
- package/src/components/Table/Table.stories.tsx +0 -125
- package/src/components/Table/Table.tsx +0 -51
- package/src/components/Table/__tests__/Table.test.tsx +0 -50
- package/src/components/Tabs/Tabs.module.css +0 -56
- package/src/components/Tabs/Tabs.stories.tsx +0 -100
- package/src/components/Tabs/Tabs.tsx +0 -46
- package/src/components/Tabs/__tests__/Tabs.test.tsx +0 -45
- package/src/components/TagInput/TagInput.module.css +0 -141
- package/src/components/TagInput/TagInput.stories.tsx +0 -109
- package/src/components/TagInput/TagInput.tsx +0 -174
- package/src/components/TagInput/__tests__/TagInput.test.tsx +0 -69
- package/src/components/Textarea/Textarea.module.css +0 -39
- package/src/components/Textarea/Textarea.tsx +0 -15
- package/src/components/Textarea/__tests__/Textarea.test.tsx +0 -29
- package/src/components/Toast/Toast.module.css +0 -132
- package/src/components/Toast/Toast.stories.tsx +0 -191
- package/src/components/Toast/Toast.tsx +0 -139
- package/src/components/Toggle/Toggle.module.css +0 -57
- package/src/components/Toggle/Toggle.stories.tsx +0 -95
- package/src/components/Toggle/Toggle.tsx +0 -39
- package/src/components/Toggle/__tests__/Toggle.test.tsx +0 -47
- package/src/components/Tooltip/Tooltip.module.css +0 -56
- package/src/components/Tooltip/Tooltip.tsx +0 -30
- package/src/components/Typography/Typography.module.css +0 -47
- package/src/components/Typography/Typography.stories.tsx +0 -62
- package/src/components/Typography/Typography.tsx +0 -30
- package/src/components/Typography/__tests__/Typography.test.tsx +0 -34
- package/src/css-modules.d.ts +0 -4
- package/src/hooks/__tests__/useCarouselRouteSync.test.tsx +0 -126
- package/src/hooks/__tests__/useDoubleTap.test.tsx +0 -117
- package/src/hooks/__tests__/useIsMobile.test.tsx +0 -80
- package/src/hooks/__tests__/useIsOffline.test.tsx +0 -53
- package/src/hooks/__tests__/useLongPress.test.tsx +0 -158
- package/src/hooks/__tests__/useOfflineTileState.test.tsx +0 -69
- package/src/hooks/__tests__/useResponsiveLayout.test.tsx +0 -87
- package/src/hooks/__tests__/useSnapCarousel.test.tsx +0 -93
- package/src/hooks/__tests__/useSwipeReveal.test.tsx +0 -110
- package/src/hooks/index.ts +0 -24
- package/src/hooks/useCarouselRouteSync.ts +0 -92
- package/src/hooks/useCommitOnEnterOrBlur.ts +0 -39
- package/src/hooks/useDoubleTap.ts +0 -91
- package/src/hooks/useIsMobile.ts +0 -33
- package/src/hooks/useIsOffline.ts +0 -41
- package/src/hooks/useLongPress.ts +0 -185
- package/src/hooks/useOfflineTileState.ts +0 -45
- package/src/hooks/useResponsiveLayout.ts +0 -36
- package/src/hooks/useSnapCarousel.ts +0 -79
- package/src/hooks/useSwipeReveal.ts +0 -107
- package/src/index.ts +0 -251
- package/src/motion.ts +0 -78
- package/src/overlay-shell.ts +0 -94
- package/src/scroll-lock.ts +0 -22
- package/src/stories/Avatar.stories.tsx +0 -44
- package/src/stories/Card.stories.tsx +0 -35
- package/src/stories/Checkbox.stories.tsx +0 -45
- package/src/stories/DesignSystemOverview.stories.tsx +0 -3187
- package/src/stories/DeviceOnlyGate.stories.tsx +0 -35
- package/src/stories/DeviceStorageKeyGate.stories.tsx +0 -57
- package/src/stories/DragHandleRow.stories.tsx +0 -41
- package/src/stories/EmptyState.stories.tsx +0 -33
- package/src/stories/FileDropzone.stories.tsx +0 -69
- package/src/stories/FormField.stories.tsx +0 -47
- package/src/stories/InteractionHooks.stories.tsx +0 -925
- package/src/stories/MobilePatterns.stories.tsx +0 -1536
- package/src/stories/NavTabs.stories.tsx +0 -53
- package/src/stories/OfflineGate.stories.tsx +0 -42
- package/src/stories/PageContainer.stories.tsx +0 -37
- package/src/stories/PageHeader.stories.tsx +0 -42
- package/src/stories/ResponsiveSurface.stories.tsx +0 -47
- package/src/stories/Spinner.stories.tsx +0 -25
- package/src/stories/SwipableMobileCarousel.stories.tsx +0 -304
- package/src/stories/Textarea.stories.tsx +0 -51
- package/src/stories/TokenGallery.stories.tsx +0 -602
- package/src/stories/Tooltip.stories.tsx +0 -59
- package/src/tokens/primitives.css +0 -157
- package/src/tokens/semantic.css +0 -130
- package/src/tokens.css +0 -5
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
.field {
|
|
2
|
-
display: flex;
|
|
3
|
-
flex-direction: column;
|
|
4
|
-
gap: var(--sv-space-1);
|
|
5
|
-
/* Every local .field/.fieldGroup class this component replaced set this
|
|
6
|
-
explicitly. Without it, a parent with align-items: flex-start (needed so
|
|
7
|
-
submit buttons don't stretch full-width) shrink-wraps this wrapper to its
|
|
8
|
-
content's intrinsic width instead of filling the available space. */
|
|
9
|
-
width: 100%;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
.label {
|
|
13
|
-
font-size: var(--sv-font-size-sm);
|
|
14
|
-
font-weight: var(--sv-font-weight-medium);
|
|
15
|
-
color: var(--sv-color-text-primary);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
.required {
|
|
19
|
-
color: var(--sv-color-error-text);
|
|
20
|
-
margin-left: var(--sv-space-1);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
.hint {
|
|
24
|
-
margin: 0;
|
|
25
|
-
font-size: var(--sv-font-size-xs);
|
|
26
|
-
color: var(--sv-color-text-muted);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.error {
|
|
30
|
-
margin: 0;
|
|
31
|
-
font-size: var(--sv-font-size-xs);
|
|
32
|
-
color: var(--sv-color-error-text);
|
|
33
|
-
}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
2
|
-
import { useId } from 'react';
|
|
3
|
-
import styles from './FormField.module.css';
|
|
4
|
-
|
|
5
|
-
/** Props to spread onto the field's actual form control. */
|
|
6
|
-
export interface FormFieldRenderProps {
|
|
7
|
-
id: string;
|
|
8
|
-
'aria-describedby'?: string;
|
|
9
|
-
'aria-invalid'?: boolean;
|
|
10
|
-
required?: boolean;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface FormFieldProps {
|
|
14
|
-
label: string;
|
|
15
|
-
hint?: string;
|
|
16
|
-
error?: string;
|
|
17
|
-
/** Explicit control id. Auto-generated via `useId()` when omitted. */
|
|
18
|
-
id?: string;
|
|
19
|
-
required?: boolean;
|
|
20
|
-
className?: string;
|
|
21
|
-
/** Render prop — receives the props that must be spread onto the control
|
|
22
|
-
* so the label, hint, and error stay associated with it. */
|
|
23
|
-
children: (field: FormFieldRenderProps) => ReactNode;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* FormField — accessible label + hint/error wrapper for a single form control.
|
|
28
|
-
* Wires `htmlFor`/`id` and `aria-describedby`/`aria-invalid` onto the control
|
|
29
|
-
* itself (via the render-prop `field` object), not a surrounding element, so
|
|
30
|
-
* screen readers reliably announce the hint or error as part of the control.
|
|
31
|
-
*/
|
|
32
|
-
export function FormField({
|
|
33
|
-
label,
|
|
34
|
-
hint,
|
|
35
|
-
error,
|
|
36
|
-
id,
|
|
37
|
-
required = false,
|
|
38
|
-
className,
|
|
39
|
-
children,
|
|
40
|
-
}: FormFieldProps) {
|
|
41
|
-
const generatedId = useId();
|
|
42
|
-
const fieldId = id ?? generatedId;
|
|
43
|
-
const hintId = `${fieldId}-hint`;
|
|
44
|
-
const errorId = `${fieldId}-error`;
|
|
45
|
-
const describedBy =
|
|
46
|
-
[hint && !error && hintId, error && errorId].filter(Boolean).join(' ') || undefined;
|
|
47
|
-
|
|
48
|
-
const field: FormFieldRenderProps = {
|
|
49
|
-
id: fieldId,
|
|
50
|
-
'aria-describedby': describedBy,
|
|
51
|
-
'aria-invalid': error ? true : undefined,
|
|
52
|
-
required: required || undefined,
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
return (
|
|
56
|
-
<div className={[styles.field, className].filter(Boolean).join(' ')}>
|
|
57
|
-
<label className={styles.label} htmlFor={fieldId}>
|
|
58
|
-
{label}
|
|
59
|
-
{required && (
|
|
60
|
-
<span className={styles.required} aria-hidden="true">
|
|
61
|
-
*
|
|
62
|
-
</span>
|
|
63
|
-
)}
|
|
64
|
-
</label>
|
|
65
|
-
{children(field)}
|
|
66
|
-
{hint && !error && (
|
|
67
|
-
<p id={hintId} className={styles.hint}>
|
|
68
|
-
{hint}
|
|
69
|
-
</p>
|
|
70
|
-
)}
|
|
71
|
-
{error && (
|
|
72
|
-
<p id={errorId} className={styles.error} role="alert">
|
|
73
|
-
{error}
|
|
74
|
-
</p>
|
|
75
|
-
)}
|
|
76
|
-
</div>
|
|
77
|
-
);
|
|
78
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
3
|
-
import { cleanup, render, screen } from '@testing-library/react';
|
|
4
|
-
import { FormField } from '../FormField';
|
|
5
|
-
|
|
6
|
-
afterEach(cleanup);
|
|
7
|
-
|
|
8
|
-
describe('FormField', () => {
|
|
9
|
-
it('associates the label with the control via htmlFor/id', () => {
|
|
10
|
-
render(
|
|
11
|
-
<FormField label="Email" id="email">
|
|
12
|
-
{(field) => <input {...field} />}
|
|
13
|
-
</FormField>,
|
|
14
|
-
);
|
|
15
|
-
expect(screen.getByLabelText('Email')).toBeDefined();
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('generates an id when none is provided', () => {
|
|
19
|
-
render(<FormField label="Email">{(field) => <input {...field} />}</FormField>);
|
|
20
|
-
const input = screen.getByLabelText('Email') as HTMLInputElement;
|
|
21
|
-
expect(input.id).toBeTruthy();
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('wires the hint to the control via aria-describedby', () => {
|
|
25
|
-
render(
|
|
26
|
-
<FormField label="Username" hint="Letters and numbers only." id="username">
|
|
27
|
-
{(field) => <input {...field} />}
|
|
28
|
-
</FormField>,
|
|
29
|
-
);
|
|
30
|
-
const input = screen.getByLabelText('Username');
|
|
31
|
-
const hint = screen.getByText('Letters and numbers only.');
|
|
32
|
-
expect(input.getAttribute('aria-describedby')).toBe(hint.id);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('wires the error to the control via aria-describedby and marks it invalid', () => {
|
|
36
|
-
render(
|
|
37
|
-
<FormField label="Password" error="Too short." id="password">
|
|
38
|
-
{(field) => <input {...field} />}
|
|
39
|
-
</FormField>,
|
|
40
|
-
);
|
|
41
|
-
const input = screen.getByLabelText('Password');
|
|
42
|
-
const error = screen.getByText('Too short.');
|
|
43
|
-
expect(input.getAttribute('aria-describedby')).toBe(error.id);
|
|
44
|
-
expect(input.getAttribute('aria-invalid')).toBe('true');
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('renders the error with role="alert"', () => {
|
|
48
|
-
render(
|
|
49
|
-
<FormField label="Password" error="Too short." id="password">
|
|
50
|
-
{(field) => <input {...field} />}
|
|
51
|
-
</FormField>,
|
|
52
|
-
);
|
|
53
|
-
expect(screen.getByRole('alert').textContent).toBe('Too short.');
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('hides the hint when an error is present', () => {
|
|
57
|
-
render(
|
|
58
|
-
<FormField label="Password" hint="At least 8 characters." error="Too short." id="password">
|
|
59
|
-
{(field) => <input {...field} />}
|
|
60
|
-
</FormField>,
|
|
61
|
-
);
|
|
62
|
-
expect(screen.queryByText('At least 8 characters.')).toBeNull();
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('shows a required indicator and sets required on the control', () => {
|
|
66
|
-
render(
|
|
67
|
-
<FormField label="Full name" required id="name">
|
|
68
|
-
{(field) => <input {...field} />}
|
|
69
|
-
</FormField>,
|
|
70
|
-
);
|
|
71
|
-
const input = screen.getByLabelText(/Full name/) as HTMLInputElement;
|
|
72
|
-
expect(input.required).toBe(true);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('does not set aria-describedby when there is no hint or error', () => {
|
|
76
|
-
render(
|
|
77
|
-
<FormField label="Nickname" id="nickname">
|
|
78
|
-
{(field) => <input {...field} />}
|
|
79
|
-
</FormField>,
|
|
80
|
-
);
|
|
81
|
-
expect(screen.getByLabelText('Nickname').hasAttribute('aria-describedby')).toBe(false);
|
|
82
|
-
});
|
|
83
|
-
});
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
-
import { HoverCard } from './HoverCard';
|
|
3
|
-
import { Avatar } from '../Avatar/Avatar';
|
|
4
|
-
|
|
5
|
-
const meta = {
|
|
6
|
-
title: 'Components/HoverCard',
|
|
7
|
-
component: HoverCard,
|
|
8
|
-
parameters: {
|
|
9
|
-
layout: 'padded',
|
|
10
|
-
docs: {
|
|
11
|
-
description: {
|
|
12
|
-
component:
|
|
13
|
-
'Hover-triggered popover on desktop, tap-to-toggle on touch. Built on Popover for positioning/collision detection — this only adds hover-intent timing and the touch fallback. Opens on keyboard focus too.',
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
args: { trigger: <span />, children: null, 'aria-label': 'Preview' },
|
|
18
|
-
} satisfies Meta<typeof HoverCard>;
|
|
19
|
-
|
|
20
|
-
export default meta;
|
|
21
|
-
type Story = StoryObj<typeof meta>;
|
|
22
|
-
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
|
|
25
|
-
export const Default: Story = {
|
|
26
|
-
render: () => (
|
|
27
|
-
<HoverCard
|
|
28
|
-
aria-label="User preview"
|
|
29
|
-
trigger={
|
|
30
|
-
<button
|
|
31
|
-
type="button"
|
|
32
|
-
style={{ background: 'none', border: 'none', padding: 0, cursor: 'pointer' }}
|
|
33
|
-
>
|
|
34
|
-
<Avatar name="Jane Smith" size="sm" />
|
|
35
|
-
</button>
|
|
36
|
-
}
|
|
37
|
-
>
|
|
38
|
-
<div style={{ padding: 'var(--sv-space-3)', fontSize: 14 }}>
|
|
39
|
-
<div style={{ fontWeight: 600 }}>Jane Smith</div>
|
|
40
|
-
<div style={{ color: 'var(--sv-color-text-muted)' }}>jane@example.com</div>
|
|
41
|
-
</div>
|
|
42
|
-
</HoverCard>
|
|
43
|
-
),
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export const WithLink: Story = {
|
|
47
|
-
render: () => (
|
|
48
|
-
<p style={{ fontSize: 14 }}>
|
|
49
|
-
Assigned to{' '}
|
|
50
|
-
<HoverCard
|
|
51
|
-
aria-label="Assignee preview"
|
|
52
|
-
trigger={
|
|
53
|
-
<a href="/console/users/jane-smith" style={{ color: 'var(--sv-color-text-primary)' }}>
|
|
54
|
-
Jane Smith
|
|
55
|
-
</a>
|
|
56
|
-
}
|
|
57
|
-
>
|
|
58
|
-
<div style={{ padding: 'var(--sv-space-3)', fontSize: 14 }}>
|
|
59
|
-
<div style={{ fontWeight: 600 }}>Jane Smith</div>
|
|
60
|
-
<div style={{ color: 'var(--sv-color-text-muted)' }}>12 tasks completed this month</div>
|
|
61
|
-
</div>
|
|
62
|
-
</HoverCard>
|
|
63
|
-
.
|
|
64
|
-
</p>
|
|
65
|
-
),
|
|
66
|
-
};
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { cloneElement, useEffect, useRef, useState } from 'react';
|
|
4
|
-
import type { ReactElement, ReactNode } from 'react';
|
|
5
|
-
import { Popover } from '../Popover/Popover';
|
|
6
|
-
import styles from './HoverCard.module.css';
|
|
7
|
-
|
|
8
|
-
export interface HoverCardProps {
|
|
9
|
-
/** The element that opens the card — cloned with hover/focus handlers
|
|
10
|
-
* (desktop) or a tap handler (touch). */
|
|
11
|
-
trigger: ReactElement;
|
|
12
|
-
children: ReactNode;
|
|
13
|
-
align?: 'left' | 'right';
|
|
14
|
-
width?: number | 'trigger';
|
|
15
|
-
'aria-label': string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const OPEN_DELAY_MS = 400;
|
|
19
|
-
const CLOSE_DELAY_MS = 150;
|
|
20
|
-
|
|
21
|
-
/** Whether the primary pointer can hover. Distinct from viewport-width-based
|
|
22
|
-
* mobile detection: a touchscreen laptop with a mouse as its primary
|
|
23
|
-
* pointer should still get real hover behavior, and a large-viewport tablet
|
|
24
|
-
* with only touch should not — viewport width doesn't capture that, `(hover:
|
|
25
|
-
* hover)` does, matching the pointer-based gating this design system already
|
|
26
|
-
* uses in CSS (e.g. Button/Checkbox's touch-target expansion). Defaults to
|
|
27
|
-
* `true`: this only changes which event handlers get attached, not the
|
|
28
|
-
* initial DOM shape, so there's no hydration-mismatch risk to guard against
|
|
29
|
-
* the way `useIsMobile` does. */
|
|
30
|
-
function useHoverCapable(): boolean {
|
|
31
|
-
const [hoverCapable, setHoverCapable] = useState(true);
|
|
32
|
-
|
|
33
|
-
useEffect(() => {
|
|
34
|
-
const mql = window.matchMedia('(hover: hover)');
|
|
35
|
-
setHoverCapable(mql.matches);
|
|
36
|
-
function handleChange(e: MediaQueryListEvent) {
|
|
37
|
-
setHoverCapable(e.matches);
|
|
38
|
-
}
|
|
39
|
-
mql.addEventListener('change', handleChange);
|
|
40
|
-
return () => mql.removeEventListener('change', handleChange);
|
|
41
|
-
}, []);
|
|
42
|
-
|
|
43
|
-
return hoverCapable;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* HoverCard — hover-triggered popover on desktop, tap-to-toggle on touch.
|
|
48
|
-
*
|
|
49
|
-
* Built on `Popover` rather than reimplementing positioning/collision
|
|
50
|
-
* detection: this only adds the hover-intent open/close timing (avoids
|
|
51
|
-
* flicker on an accidental mouse pass-over, and gives the pointer time to
|
|
52
|
-
* travel from the trigger to the card itself across the gap between them)
|
|
53
|
-
* and the touch fallback. Also opens on keyboard focus and closes on blur —
|
|
54
|
-
* a hover-only trigger is a WCAG failure, since keyboard users can't hover.
|
|
55
|
-
*/
|
|
56
|
-
export function HoverCard({
|
|
57
|
-
trigger,
|
|
58
|
-
children,
|
|
59
|
-
align,
|
|
60
|
-
width,
|
|
61
|
-
'aria-label': ariaLabel,
|
|
62
|
-
}: HoverCardProps) {
|
|
63
|
-
const [open, setOpen] = useState(false);
|
|
64
|
-
const hoverCapable = useHoverCapable();
|
|
65
|
-
const openTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
|
66
|
-
const closeTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
|
67
|
-
|
|
68
|
-
function scheduleOpen() {
|
|
69
|
-
clearTimeout(closeTimer.current);
|
|
70
|
-
openTimer.current = setTimeout(() => setOpen(true), OPEN_DELAY_MS);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function scheduleClose() {
|
|
74
|
-
clearTimeout(openTimer.current);
|
|
75
|
-
closeTimer.current = setTimeout(() => setOpen(false), CLOSE_DELAY_MS);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function cancelScheduledClose() {
|
|
79
|
-
clearTimeout(closeTimer.current);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const triggerHandlers = hoverCapable
|
|
83
|
-
? {
|
|
84
|
-
onMouseEnter: scheduleOpen,
|
|
85
|
-
onMouseLeave: scheduleClose,
|
|
86
|
-
onFocus: () => setOpen(true),
|
|
87
|
-
onBlur: scheduleClose,
|
|
88
|
-
}
|
|
89
|
-
: { onClick: () => setOpen((o) => !o) };
|
|
90
|
-
|
|
91
|
-
const wrappedTrigger = cloneElement(trigger, triggerHandlers);
|
|
92
|
-
|
|
93
|
-
return (
|
|
94
|
-
<div
|
|
95
|
-
className={styles.root}
|
|
96
|
-
onMouseEnter={hoverCapable ? cancelScheduledClose : undefined}
|
|
97
|
-
onMouseLeave={hoverCapable ? scheduleClose : undefined}
|
|
98
|
-
>
|
|
99
|
-
<Popover
|
|
100
|
-
trigger={wrappedTrigger}
|
|
101
|
-
open={open}
|
|
102
|
-
onClose={() => setOpen(false)}
|
|
103
|
-
align={align}
|
|
104
|
-
width={width}
|
|
105
|
-
aria-label={ariaLabel}
|
|
106
|
-
>
|
|
107
|
-
{children}
|
|
108
|
-
</Popover>
|
|
109
|
-
</div>
|
|
110
|
-
);
|
|
111
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
-
import { act, cleanup, render, screen, fireEvent } from '@testing-library/react';
|
|
4
|
-
import { HoverCard } from '../HoverCard';
|
|
5
|
-
|
|
6
|
-
let matches = true;
|
|
7
|
-
|
|
8
|
-
function installMatchMedia() {
|
|
9
|
-
vi.stubGlobal(
|
|
10
|
-
'matchMedia',
|
|
11
|
-
vi.fn().mockImplementation((query: string) => ({
|
|
12
|
-
matches,
|
|
13
|
-
media: query,
|
|
14
|
-
addEventListener: vi.fn(),
|
|
15
|
-
removeEventListener: vi.fn(),
|
|
16
|
-
addListener: vi.fn(),
|
|
17
|
-
removeListener: vi.fn(),
|
|
18
|
-
dispatchEvent: vi.fn(),
|
|
19
|
-
})),
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
describe('HoverCard', () => {
|
|
24
|
-
beforeEach(() => {
|
|
25
|
-
matches = true; // hover-capable by default
|
|
26
|
-
installMatchMedia();
|
|
27
|
-
vi.useFakeTimers();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
afterEach(() => {
|
|
31
|
-
vi.runOnlyPendingTimers();
|
|
32
|
-
vi.useRealTimers();
|
|
33
|
-
vi.unstubAllGlobals();
|
|
34
|
-
cleanup();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('does not open immediately on mouse enter (hover-intent delay)', () => {
|
|
38
|
-
render(
|
|
39
|
-
<HoverCard trigger={<button type="button">Profile</button>} aria-label="Profile preview">
|
|
40
|
-
Card content
|
|
41
|
-
</HoverCard>,
|
|
42
|
-
);
|
|
43
|
-
fireEvent.mouseEnter(screen.getByRole('button', { name: 'Profile' }));
|
|
44
|
-
expect(screen.queryByText('Card content')).toBeNull();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('opens after the hover-intent delay elapses', () => {
|
|
48
|
-
render(
|
|
49
|
-
<HoverCard trigger={<button type="button">Profile</button>} aria-label="Profile preview">
|
|
50
|
-
Card content
|
|
51
|
-
</HoverCard>,
|
|
52
|
-
);
|
|
53
|
-
fireEvent.mouseEnter(screen.getByRole('button', { name: 'Profile' }));
|
|
54
|
-
act(() => {
|
|
55
|
-
vi.advanceTimersByTime(500);
|
|
56
|
-
});
|
|
57
|
-
expect(screen.getByText('Card content')).toBeDefined();
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('cancels the open timer if the pointer leaves before the delay elapses', () => {
|
|
61
|
-
render(
|
|
62
|
-
<HoverCard trigger={<button type="button">Profile</button>} aria-label="Profile preview">
|
|
63
|
-
Card content
|
|
64
|
-
</HoverCard>,
|
|
65
|
-
);
|
|
66
|
-
const trigger = screen.getByRole('button', { name: 'Profile' });
|
|
67
|
-
fireEvent.mouseEnter(trigger);
|
|
68
|
-
fireEvent.mouseLeave(trigger);
|
|
69
|
-
act(() => {
|
|
70
|
-
vi.advanceTimersByTime(1000);
|
|
71
|
-
});
|
|
72
|
-
expect(screen.queryByText('Card content')).toBeNull();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('opens immediately on keyboard focus, for keyboard accessibility', () => {
|
|
76
|
-
render(
|
|
77
|
-
<HoverCard trigger={<button type="button">Profile</button>} aria-label="Profile preview">
|
|
78
|
-
Card content
|
|
79
|
-
</HoverCard>,
|
|
80
|
-
);
|
|
81
|
-
fireEvent.focus(screen.getByRole('button', { name: 'Profile' }));
|
|
82
|
-
expect(screen.getByText('Card content')).toBeDefined();
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('falls back to tap-to-toggle when the pointer cannot hover', () => {
|
|
86
|
-
matches = false;
|
|
87
|
-
installMatchMedia();
|
|
88
|
-
render(
|
|
89
|
-
<HoverCard trigger={<button type="button">Profile</button>} aria-label="Profile preview">
|
|
90
|
-
Card content
|
|
91
|
-
</HoverCard>,
|
|
92
|
-
);
|
|
93
|
-
const trigger = screen.getByRole('button', { name: 'Profile' });
|
|
94
|
-
fireEvent.click(trigger);
|
|
95
|
-
expect(screen.getByText('Card content')).toBeDefined();
|
|
96
|
-
fireEvent.click(trigger);
|
|
97
|
-
expect(screen.queryByText('Card content')).toBeNull();
|
|
98
|
-
});
|
|
99
|
-
});
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
.root {
|
|
2
|
-
display: inline-block;
|
|
3
|
-
flex-shrink: 0;
|
|
4
|
-
/* Prevent the inline-block from adding line-height whitespace below the SVG. */
|
|
5
|
-
line-height: 0;
|
|
6
|
-
/* Color follows the surrounding text via currentColor — no token needed. */
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
.xs {
|
|
10
|
-
width: var(--sv-icon-size-xs);
|
|
11
|
-
height: var(--sv-icon-size-xs);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
.sm {
|
|
15
|
-
width: var(--sv-icon-size-sm);
|
|
16
|
-
height: var(--sv-icon-size-sm);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
.md {
|
|
20
|
-
width: var(--sv-icon-size-md);
|
|
21
|
-
height: var(--sv-icon-size-md);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
.lg {
|
|
25
|
-
width: var(--sv-icon-size-lg);
|
|
26
|
-
height: var(--sv-icon-size-lg);
|
|
27
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
-
import { ICONS, type IconName } from './icons';
|
|
3
|
-
import { Icon } from './Icon';
|
|
4
|
-
|
|
5
|
-
const ALL_NAMES = Object.keys(ICONS) as IconName[];
|
|
6
|
-
|
|
7
|
-
const meta = {
|
|
8
|
-
title: 'Components/Icon',
|
|
9
|
-
component: Icon,
|
|
10
|
-
parameters: {
|
|
11
|
-
layout: 'centered',
|
|
12
|
-
docs: {
|
|
13
|
-
description: {
|
|
14
|
-
component:
|
|
15
|
-
'SVG icon primitive. Icons are either **decorative** (`aria-hidden`) or **meaningful** (`aria-label`). Size is controlled by `--sv-icon-size-*` tokens.',
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
argTypes: {
|
|
20
|
-
name: { control: 'select', options: ALL_NAMES },
|
|
21
|
-
size: { control: 'select', options: ['xs', 'sm', 'md', 'lg'] },
|
|
22
|
-
},
|
|
23
|
-
} satisfies Meta<typeof Icon>;
|
|
24
|
-
|
|
25
|
-
export default meta;
|
|
26
|
-
type Story = StoryObj<typeof meta>;
|
|
27
|
-
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
|
|
30
|
-
export const Decorative: Story = {
|
|
31
|
-
args: { name: 'house', size: 'md', 'aria-hidden': true },
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export const Meaningful: Story = {
|
|
35
|
-
args: { name: 'settings', size: 'md', 'aria-label': 'Open settings' },
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
export const SizeExtraSmall: Story = { args: { name: 'bell', size: 'xs', 'aria-hidden': true } };
|
|
39
|
-
export const SizeSmall: Story = { args: { name: 'bell', size: 'sm', 'aria-hidden': true } };
|
|
40
|
-
export const SizeMedium: Story = { args: { name: 'bell', size: 'md', 'aria-hidden': true } };
|
|
41
|
-
export const SizeLarge: Story = { args: { name: 'bell', size: 'lg', 'aria-hidden': true } };
|
|
42
|
-
|
|
43
|
-
/** Full icon grid — every name across all four sizes. */
|
|
44
|
-
export const AllIcons: Story = {
|
|
45
|
-
parameters: { layout: 'padded' },
|
|
46
|
-
args: { name: 'house', 'aria-hidden': true },
|
|
47
|
-
render: (_args) => (
|
|
48
|
-
<div>
|
|
49
|
-
{(['xs', 'sm', 'md', 'lg'] as const).map((sz) => (
|
|
50
|
-
<section key={sz} style={{ marginBottom: 32 }}>
|
|
51
|
-
<h3
|
|
52
|
-
style={{
|
|
53
|
-
fontFamily: 'system-ui',
|
|
54
|
-
fontSize: 13,
|
|
55
|
-
fontWeight: 600,
|
|
56
|
-
color: 'var(--sv-color-text-muted)',
|
|
57
|
-
textTransform: 'uppercase',
|
|
58
|
-
letterSpacing: '0.08em',
|
|
59
|
-
marginBottom: 12,
|
|
60
|
-
}}
|
|
61
|
-
>
|
|
62
|
-
Size: {sz}
|
|
63
|
-
</h3>
|
|
64
|
-
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 16 }}>
|
|
65
|
-
{ALL_NAMES.map((name) => (
|
|
66
|
-
<div
|
|
67
|
-
key={name}
|
|
68
|
-
style={{
|
|
69
|
-
display: 'flex',
|
|
70
|
-
flexDirection: 'column',
|
|
71
|
-
alignItems: 'center',
|
|
72
|
-
gap: 4,
|
|
73
|
-
width: 64,
|
|
74
|
-
}}
|
|
75
|
-
>
|
|
76
|
-
<Icon name={name} size={sz} aria-hidden />
|
|
77
|
-
<span
|
|
78
|
-
style={{
|
|
79
|
-
fontFamily: 'monospace',
|
|
80
|
-
fontSize: 10,
|
|
81
|
-
color: 'var(--sv-color-text-muted)',
|
|
82
|
-
textAlign: 'center',
|
|
83
|
-
wordBreak: 'break-word',
|
|
84
|
-
}}
|
|
85
|
-
>
|
|
86
|
-
{name}
|
|
87
|
-
</span>
|
|
88
|
-
</div>
|
|
89
|
-
))}
|
|
90
|
-
</div>
|
|
91
|
-
</section>
|
|
92
|
-
))}
|
|
93
|
-
</div>
|
|
94
|
-
),
|
|
95
|
-
};
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { ICONS, type IconName } from './icons';
|
|
2
|
-
import styles from './Icon.module.css';
|
|
3
|
-
|
|
4
|
-
export type { IconName };
|
|
5
|
-
|
|
6
|
-
/** Decorative icon — visually meaningful but described by surrounding text. */
|
|
7
|
-
type DecorativeProps = { 'aria-hidden': true; 'aria-label'?: never };
|
|
8
|
-
/** Meaningful icon — standalone affordance that requires a label for screen readers. */
|
|
9
|
-
type MeaningfulProps = { 'aria-label': string; 'aria-hidden'?: never };
|
|
10
|
-
|
|
11
|
-
export interface IconProps {
|
|
12
|
-
/** Name of the icon from the curated Sovereign set. */
|
|
13
|
-
name: IconName;
|
|
14
|
-
/** Visual size. Defaults to `"md"` (20 px). */
|
|
15
|
-
size?: 'xs' | 'sm' | 'md' | 'lg';
|
|
16
|
-
/** Additional CSS class applied to the SVG element. */
|
|
17
|
-
className?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* `<Icon>` — the Sovereign design system icon primitive.
|
|
22
|
-
*
|
|
23
|
-
* Icons are either **decorative** (described by surrounding text — pass `aria-hidden`)
|
|
24
|
-
* or **meaningful** (standalone affordance — pass `aria-label`):
|
|
25
|
-
*
|
|
26
|
-
* ```tsx
|
|
27
|
-
* <Icon name="house" aria-hidden /> // decorative
|
|
28
|
-
* <Icon name="log-out" aria-label="Sign out" /> // meaningful
|
|
29
|
-
* ```
|
|
30
|
-
*
|
|
31
|
-
* Color follows `currentColor` so an icon automatically inherits the text color
|
|
32
|
-
* of its container and recolors with theme changes — no extra CSS required.
|
|
33
|
-
*/
|
|
34
|
-
export function Icon({
|
|
35
|
-
name,
|
|
36
|
-
size = 'md',
|
|
37
|
-
className,
|
|
38
|
-
...aria
|
|
39
|
-
}: IconProps & (DecorativeProps | MeaningfulProps)) {
|
|
40
|
-
const Svg = ICONS[name];
|
|
41
|
-
const classes = [styles.root, styles[size], className].filter(Boolean).join(' ');
|
|
42
|
-
return <Svg className={classes} role={aria['aria-label'] ? 'img' : undefined} {...aria} />;
|
|
43
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
import { render, screen } from '@testing-library/react';
|
|
3
|
-
import { describe, expect, it } from 'vitest';
|
|
4
|
-
import { Icon } from '../Icon';
|
|
5
|
-
|
|
6
|
-
describe('Icon', () => {
|
|
7
|
-
it('renders an SVG element', () => {
|
|
8
|
-
const { container } = render(<Icon name="house" aria-hidden />);
|
|
9
|
-
expect(container.querySelector('svg')).toBeTruthy();
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it('applies aria-hidden for decorative icons', () => {
|
|
13
|
-
const { container } = render(<Icon name="house" aria-hidden />);
|
|
14
|
-
expect(container.querySelector('svg')?.getAttribute('aria-hidden')).toBe('true');
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('applies aria-label and role="img" for meaningful icons', () => {
|
|
18
|
-
render(<Icon name="log-out" aria-label="Sign out" />);
|
|
19
|
-
const svg = screen.getByRole('img', { name: 'Sign out' });
|
|
20
|
-
expect(svg).toBeTruthy();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('defaults to md size', () => {
|
|
24
|
-
const { container } = render(<Icon name="settings" aria-hidden />);
|
|
25
|
-
const svg = container.querySelector('svg');
|
|
26
|
-
// SVG className is an SVGAnimatedString in the DOM — use getAttribute('class').
|
|
27
|
-
// CSS Modules are identity-mapped in the test environment, so 'md' is literal.
|
|
28
|
-
expect(svg?.getAttribute('class')).toContain('md');
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('applies the requested size class', () => {
|
|
32
|
-
const { container } = render(<Icon name="settings" size="lg" aria-hidden />);
|
|
33
|
-
expect(container.querySelector('svg')?.getAttribute('class')).toContain('lg');
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('merges an additional className', () => {
|
|
37
|
-
const { container } = render(<Icon name="x" aria-hidden className="my-class" />);
|
|
38
|
-
expect(container.querySelector('svg')?.getAttribute('class')).toContain('my-class');
|
|
39
|
-
});
|
|
40
|
-
});
|