@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,154 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useMemo, useState } from 'react';
|
|
4
|
-
import type { ReactNode } from 'react';
|
|
5
|
-
import { Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow } from '../Table/Table';
|
|
6
|
-
import { Icon } from '../Icon/Icon';
|
|
7
|
-
import { EmptyState } from '../EmptyState/EmptyState';
|
|
8
|
-
import styles from './DataTable.module.css';
|
|
9
|
-
|
|
10
|
-
export type SortDirection = 'asc' | 'desc';
|
|
11
|
-
|
|
12
|
-
export interface DataTableColumn<T> {
|
|
13
|
-
key: string;
|
|
14
|
-
header: string;
|
|
15
|
-
sortable?: boolean;
|
|
16
|
-
/** Cell content. Defaults to String(row[key]) when omitted. */
|
|
17
|
-
render?: (row: T) => ReactNode;
|
|
18
|
-
/** Value compared when sorting by this column. Defaults to row[key].
|
|
19
|
-
* Required if `key` isn't a property on T (a computed/joined column). */
|
|
20
|
-
sortValue?: (row: T) => string | number;
|
|
21
|
-
align?: 'start' | 'end';
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface DataTableProps<T> {
|
|
25
|
-
columns: DataTableColumn<T>[];
|
|
26
|
-
data: T[];
|
|
27
|
-
getRowKey: (row: T) => string;
|
|
28
|
-
/** Initial sort — uncontrolled after mount, like Table itself has no
|
|
29
|
-
* externally-controlled state. */
|
|
30
|
-
defaultSortKey?: string;
|
|
31
|
-
defaultSortDirection?: SortDirection;
|
|
32
|
-
emptyMessage?: string;
|
|
33
|
-
className?: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function compareValues(a: string | number, b: string | number): number {
|
|
37
|
-
if (typeof a === 'number' && typeof b === 'number') return a - b;
|
|
38
|
-
return String(a).localeCompare(String(b));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* DataTable — sortable table built on Table's own primitives (no
|
|
43
|
-
* virtualization or filtering — those stay out of scope, matching Table's
|
|
44
|
-
* own "not a data grid" boundary). Column-driven: pass `columns` + `data`
|
|
45
|
-
* rather than composing `<TableRow>`/`<TableCell>` by hand.
|
|
46
|
-
*
|
|
47
|
-
* Sort state is internal (uncontrolled) and click-cycles
|
|
48
|
-
* ascending → descending → unsorted for a sortable column, mirroring the
|
|
49
|
-
* "toggle → toggle → clear" pattern most sortable tables use rather than
|
|
50
|
-
* being permanently stuck in whichever direction was clicked last.
|
|
51
|
-
*/
|
|
52
|
-
export function DataTable<T>({
|
|
53
|
-
columns,
|
|
54
|
-
data,
|
|
55
|
-
getRowKey,
|
|
56
|
-
defaultSortKey,
|
|
57
|
-
defaultSortDirection = 'asc',
|
|
58
|
-
emptyMessage = 'No results.',
|
|
59
|
-
className,
|
|
60
|
-
}: DataTableProps<T>) {
|
|
61
|
-
const [sortKey, setSortKey] = useState<string | undefined>(defaultSortKey);
|
|
62
|
-
const [sortDirection, setSortDirection] = useState<SortDirection>(defaultSortDirection);
|
|
63
|
-
|
|
64
|
-
const sortColumn = columns.find((c) => c.key === sortKey);
|
|
65
|
-
|
|
66
|
-
const sortedData = useMemo(() => {
|
|
67
|
-
if (!sortColumn) return data;
|
|
68
|
-
const getValue =
|
|
69
|
-
sortColumn.sortValue ??
|
|
70
|
-
((row: T) => (row as Record<string, unknown>)[sortColumn.key] as string | number);
|
|
71
|
-
const sorted = [...data].sort((a, b) => compareValues(getValue(a), getValue(b)));
|
|
72
|
-
return sortDirection === 'asc' ? sorted : sorted.reverse();
|
|
73
|
-
}, [data, sortColumn, sortDirection]);
|
|
74
|
-
|
|
75
|
-
function handleSort(column: DataTableColumn<T>) {
|
|
76
|
-
if (!column.sortable) return;
|
|
77
|
-
if (sortKey !== column.key) {
|
|
78
|
-
setSortKey(column.key);
|
|
79
|
-
setSortDirection('asc');
|
|
80
|
-
} else if (sortDirection === 'asc') {
|
|
81
|
-
setSortDirection('desc');
|
|
82
|
-
} else {
|
|
83
|
-
setSortKey(undefined);
|
|
84
|
-
setSortDirection('asc');
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (data.length === 0) {
|
|
89
|
-
return <EmptyState heading={emptyMessage} />;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return (
|
|
93
|
-
<Table className={className}>
|
|
94
|
-
<TableHead>
|
|
95
|
-
<TableRow>
|
|
96
|
-
{columns.map((column) => {
|
|
97
|
-
const isSorted = sortKey === column.key;
|
|
98
|
-
const ariaSort = !column.sortable
|
|
99
|
-
? undefined
|
|
100
|
-
: isSorted
|
|
101
|
-
? sortDirection === 'asc'
|
|
102
|
-
? 'ascending'
|
|
103
|
-
: 'descending'
|
|
104
|
-
: 'none';
|
|
105
|
-
return (
|
|
106
|
-
<TableHeaderCell
|
|
107
|
-
key={column.key}
|
|
108
|
-
aria-sort={ariaSort}
|
|
109
|
-
style={column.align === 'end' ? { textAlign: 'end' } : undefined}
|
|
110
|
-
>
|
|
111
|
-
{column.sortable ? (
|
|
112
|
-
<button
|
|
113
|
-
type="button"
|
|
114
|
-
className={styles.sortButton}
|
|
115
|
-
onClick={() => handleSort(column)}
|
|
116
|
-
>
|
|
117
|
-
{column.header}
|
|
118
|
-
<span className={styles.sortIcon}>
|
|
119
|
-
{isSorted && (
|
|
120
|
-
<Icon
|
|
121
|
-
name={sortDirection === 'asc' ? 'chevron-up' : 'chevron-down'}
|
|
122
|
-
size="sm"
|
|
123
|
-
aria-hidden
|
|
124
|
-
/>
|
|
125
|
-
)}
|
|
126
|
-
</span>
|
|
127
|
-
</button>
|
|
128
|
-
) : (
|
|
129
|
-
column.header
|
|
130
|
-
)}
|
|
131
|
-
</TableHeaderCell>
|
|
132
|
-
);
|
|
133
|
-
})}
|
|
134
|
-
</TableRow>
|
|
135
|
-
</TableHead>
|
|
136
|
-
<TableBody>
|
|
137
|
-
{sortedData.map((row) => (
|
|
138
|
-
<TableRow key={getRowKey(row)}>
|
|
139
|
-
{columns.map((column) => (
|
|
140
|
-
<TableCell
|
|
141
|
-
key={column.key}
|
|
142
|
-
style={column.align === 'end' ? { textAlign: 'end' } : undefined}
|
|
143
|
-
>
|
|
144
|
-
{column.render
|
|
145
|
-
? column.render(row)
|
|
146
|
-
: String((row as Record<string, unknown>)[column.key] ?? '')}
|
|
147
|
-
</TableCell>
|
|
148
|
-
))}
|
|
149
|
-
</TableRow>
|
|
150
|
-
))}
|
|
151
|
-
</TableBody>
|
|
152
|
-
</Table>
|
|
153
|
-
);
|
|
154
|
-
}
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
3
|
-
import { cleanup, fireEvent, render, screen, within } from '@testing-library/react';
|
|
4
|
-
import { DataTable, type DataTableColumn } from '../DataTable';
|
|
5
|
-
|
|
6
|
-
afterEach(cleanup);
|
|
7
|
-
|
|
8
|
-
interface Plugin {
|
|
9
|
-
id: string;
|
|
10
|
-
name: string;
|
|
11
|
-
installs: number;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const data: Plugin[] = [
|
|
15
|
-
{ id: 'a', name: 'Charlie', installs: 12 },
|
|
16
|
-
{ id: 'b', name: 'Alpha', installs: 30 },
|
|
17
|
-
{ id: 'c', name: 'Bravo', installs: 5 },
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
const columns: DataTableColumn<Plugin>[] = [
|
|
21
|
-
{ key: 'name', header: 'Name', sortable: true },
|
|
22
|
-
{ key: 'installs', header: 'Installs', sortable: true, align: 'end' },
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
function rowsText() {
|
|
26
|
-
return screen
|
|
27
|
-
.getAllByRole('row')
|
|
28
|
-
.slice(1)
|
|
29
|
-
.map((row) => within(row).getAllByRole('cell')[0]?.textContent);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
describe('DataTable', () => {
|
|
33
|
-
it('renders a row per data item and a header per column', () => {
|
|
34
|
-
render(<DataTable columns={columns} data={data} getRowKey={(row) => row.id} />);
|
|
35
|
-
expect(screen.getAllByRole('row')).toHaveLength(4); // 1 header + 3 body rows
|
|
36
|
-
expect(screen.getByRole('columnheader', { name: 'Name' })).toBeDefined();
|
|
37
|
-
expect(screen.getByRole('columnheader', { name: 'Installs' })).toBeDefined();
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('renders cell values from row[key] by default', () => {
|
|
41
|
-
render(<DataTable columns={columns} data={data} getRowKey={(row) => row.id} />);
|
|
42
|
-
expect(screen.getByText('Charlie')).toBeDefined();
|
|
43
|
-
expect(screen.getByText('30')).toBeDefined();
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('uses a custom render function when provided', () => {
|
|
47
|
-
const withRender: DataTableColumn<Plugin>[] = [
|
|
48
|
-
{ key: 'name', header: 'Name' },
|
|
49
|
-
{ key: 'installs', header: 'Installs', render: (row) => `${row.installs} installs` },
|
|
50
|
-
];
|
|
51
|
-
render(<DataTable columns={withRender} data={data} getRowKey={(row) => row.id} />);
|
|
52
|
-
expect(screen.getByText('30 installs')).toBeDefined();
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('shows an empty state instead of a table when data is empty', () => {
|
|
56
|
-
render(
|
|
57
|
-
<DataTable
|
|
58
|
-
columns={columns}
|
|
59
|
-
data={[]}
|
|
60
|
-
getRowKey={(row) => row.id}
|
|
61
|
-
emptyMessage="No plugins"
|
|
62
|
-
/>,
|
|
63
|
-
);
|
|
64
|
-
expect(screen.queryByRole('table')).toBeNull();
|
|
65
|
-
expect(screen.getByText('No plugins')).toBeDefined();
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('non-sortable columns render as plain text, not a button', () => {
|
|
69
|
-
const withPlain: DataTableColumn<Plugin>[] = [{ key: 'name', header: 'Name' }];
|
|
70
|
-
render(<DataTable columns={withPlain} data={data} getRowKey={(row) => row.id} />);
|
|
71
|
-
expect(screen.queryByRole('button', { name: 'Name' })).toBeNull();
|
|
72
|
-
expect(screen.getByRole('columnheader', { name: 'Name' }).getAttribute('aria-sort')).toBeNull();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('clicking a sortable header sorts ascending, then descending, then clears', () => {
|
|
76
|
-
render(<DataTable columns={columns} data={data} getRowKey={(row) => row.id} />);
|
|
77
|
-
const nameHeader = screen.getByRole('columnheader', { name: 'Name' });
|
|
78
|
-
const button = within(nameHeader).getByRole('button');
|
|
79
|
-
|
|
80
|
-
fireEvent.click(button);
|
|
81
|
-
expect(rowsText()).toEqual(['Alpha', 'Bravo', 'Charlie']);
|
|
82
|
-
expect(nameHeader.getAttribute('aria-sort')).toBe('ascending');
|
|
83
|
-
|
|
84
|
-
fireEvent.click(button);
|
|
85
|
-
expect(rowsText()).toEqual(['Charlie', 'Bravo', 'Alpha']);
|
|
86
|
-
expect(nameHeader.getAttribute('aria-sort')).toBe('descending');
|
|
87
|
-
|
|
88
|
-
fireEvent.click(button);
|
|
89
|
-
expect(rowsText()).toEqual(['Charlie', 'Alpha', 'Bravo']);
|
|
90
|
-
expect(nameHeader.getAttribute('aria-sort')).toBe('none');
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('sorts numeric columns numerically, not lexicographically', () => {
|
|
94
|
-
render(<DataTable columns={columns} data={data} getRowKey={(row) => row.id} />);
|
|
95
|
-
const installsHeader = screen.getByRole('columnheader', { name: 'Installs' });
|
|
96
|
-
fireEvent.click(within(installsHeader).getByRole('button'));
|
|
97
|
-
expect(rowsText()).toEqual(['Bravo', 'Charlie', 'Alpha']); // 5, 12, 30
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('switching the sort column resets to ascending', () => {
|
|
101
|
-
render(<DataTable columns={columns} data={data} getRowKey={(row) => row.id} />);
|
|
102
|
-
const nameHeader = screen.getByRole('columnheader', { name: 'Name' });
|
|
103
|
-
const installsHeader = screen.getByRole('columnheader', { name: 'Installs' });
|
|
104
|
-
fireEvent.click(within(nameHeader).getByRole('button'));
|
|
105
|
-
fireEvent.click(within(nameHeader).getByRole('button')); // now descending
|
|
106
|
-
fireEvent.click(within(installsHeader).getByRole('button'));
|
|
107
|
-
expect(installsHeader.getAttribute('aria-sort')).toBe('ascending');
|
|
108
|
-
expect(nameHeader.getAttribute('aria-sort')).toBe('none');
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('respects defaultSortKey/defaultSortDirection', () => {
|
|
112
|
-
render(
|
|
113
|
-
<DataTable
|
|
114
|
-
columns={columns}
|
|
115
|
-
data={data}
|
|
116
|
-
getRowKey={(row) => row.id}
|
|
117
|
-
defaultSortKey="installs"
|
|
118
|
-
defaultSortDirection="desc"
|
|
119
|
-
/>,
|
|
120
|
-
);
|
|
121
|
-
expect(rowsText()).toEqual(['Alpha', 'Charlie', 'Bravo']); // 30, 12, 5
|
|
122
|
-
});
|
|
123
|
-
});
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/* DatePicker — trigger styled to match Input's height/border/radius/focus
|
|
2
|
-
* ring for visual consistency as a form field. Kept in sync with Input's
|
|
3
|
-
* own sizing by hand (36px rendered height via explicit height +
|
|
4
|
-
* border-box, not a literal reuse of Input). */
|
|
5
|
-
|
|
6
|
-
.trigger {
|
|
7
|
-
display: flex;
|
|
8
|
-
align-items: center;
|
|
9
|
-
justify-content: space-between;
|
|
10
|
-
gap: var(--sv-space-2);
|
|
11
|
-
width: 100%;
|
|
12
|
-
height: 36px;
|
|
13
|
-
box-sizing: border-box;
|
|
14
|
-
padding: 4px var(--sv-space-3);
|
|
15
|
-
font-family: var(--sv-font-family);
|
|
16
|
-
font-size: var(--sv-font-size-sm);
|
|
17
|
-
line-height: 1.5;
|
|
18
|
-
color: var(--sv-color-text-primary);
|
|
19
|
-
background-color: var(--sv-color-surface);
|
|
20
|
-
border: 1px solid var(--sv-color-border-strong);
|
|
21
|
-
border-radius: var(--sv-radius-md);
|
|
22
|
-
cursor: pointer;
|
|
23
|
-
transition:
|
|
24
|
-
border-color 0.15s ease,
|
|
25
|
-
box-shadow 0.15s ease;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/* Same iOS auto-zoom fix as Input — see its own module.css for why. */
|
|
29
|
-
@media (pointer: coarse) {
|
|
30
|
-
.trigger {
|
|
31
|
-
font-size: var(--sv-font-size-md);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.trigger:focus-visible {
|
|
36
|
-
outline: none;
|
|
37
|
-
border-color: var(--sv-color-accent);
|
|
38
|
-
box-shadow: 0 0 0 2px var(--sv-color-focus-ring);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
.trigger:disabled {
|
|
42
|
-
opacity: 0.5;
|
|
43
|
-
cursor: not-allowed;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
.triggerValue {
|
|
47
|
-
color: var(--sv-color-text-primary);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
.triggerPlaceholder {
|
|
51
|
-
color: var(--sv-color-text-muted);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
.popoverBody {
|
|
55
|
-
padding: var(--sv-space-3);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
.drawerBody {
|
|
59
|
-
padding: var(--sv-space-4);
|
|
60
|
-
padding-bottom: max(var(--sv-space-4), env(safe-area-inset-bottom));
|
|
61
|
-
}
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useState } from 'react';
|
|
4
|
-
import { useIsMobile } from '../../hooks';
|
|
5
|
-
import { Calendar } from '../Calendar/Calendar';
|
|
6
|
-
import { formatDateShort } from '../Calendar/dateUtils';
|
|
7
|
-
import { Drawer } from '../Drawer/Drawer';
|
|
8
|
-
import { Icon } from '../Icon/Icon';
|
|
9
|
-
import { Popover } from '../Popover/Popover';
|
|
10
|
-
import styles from './DatePicker.module.css';
|
|
11
|
-
|
|
12
|
-
export interface DatePickerProps {
|
|
13
|
-
value: Date | null;
|
|
14
|
-
onChange: (date: Date) => void;
|
|
15
|
-
minDate?: Date;
|
|
16
|
-
maxDate?: Date;
|
|
17
|
-
placeholder?: string;
|
|
18
|
-
'aria-label': string;
|
|
19
|
-
disabled?: boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Wide enough for the 7-column Calendar grid (44px touch-target cells) plus
|
|
23
|
-
// its own padding, without being so wide it looks odd as a Popover panel.
|
|
24
|
-
const POPOVER_WIDTH = 320;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* DatePicker — a form field pairing a compact trigger with `Calendar`:
|
|
28
|
-
* `Popover` on desktop, a bottom-sheet `Drawer` on mobile (the platform's
|
|
29
|
-
* standard adaptive-surface pattern, matching `Menu`). Date-only (decision
|
|
30
|
-
* D6 in the mobile design-system plan) — time and range selection are future
|
|
31
|
-
* scope; recurrence UI stays plugin-side.
|
|
32
|
-
*
|
|
33
|
-
* Unlike `Menu`, the trigger is built in rather than supplied by the caller
|
|
34
|
-
* — a date picker is a form field (consistent with `Input`/`Select`), not an
|
|
35
|
-
* arbitrary action-menu trigger.
|
|
36
|
-
*/
|
|
37
|
-
export function DatePicker({
|
|
38
|
-
value,
|
|
39
|
-
onChange,
|
|
40
|
-
minDate,
|
|
41
|
-
maxDate,
|
|
42
|
-
placeholder = 'Select date',
|
|
43
|
-
'aria-label': ariaLabel,
|
|
44
|
-
disabled = false,
|
|
45
|
-
}: DatePickerProps) {
|
|
46
|
-
const [open, setOpen] = useState(false);
|
|
47
|
-
const isMobile = useIsMobile();
|
|
48
|
-
|
|
49
|
-
function handleSelect(date: Date) {
|
|
50
|
-
onChange(date);
|
|
51
|
-
setOpen(false);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const trigger = (
|
|
55
|
-
<button
|
|
56
|
-
type="button"
|
|
57
|
-
className={styles.trigger}
|
|
58
|
-
disabled={disabled}
|
|
59
|
-
aria-label={ariaLabel}
|
|
60
|
-
aria-haspopup="dialog"
|
|
61
|
-
aria-expanded={open}
|
|
62
|
-
onClick={() => setOpen((o) => !o)}
|
|
63
|
-
>
|
|
64
|
-
<span className={value ? styles.triggerValue : styles.triggerPlaceholder}>
|
|
65
|
-
{value ? formatDateShort(value) : placeholder}
|
|
66
|
-
</span>
|
|
67
|
-
<Icon name="calendar" size="sm" aria-hidden />
|
|
68
|
-
</button>
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
const calendar = (
|
|
72
|
-
<Calendar
|
|
73
|
-
value={value}
|
|
74
|
-
onChange={handleSelect}
|
|
75
|
-
minDate={minDate}
|
|
76
|
-
maxDate={maxDate}
|
|
77
|
-
aria-label={ariaLabel}
|
|
78
|
-
/>
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
if (isMobile) {
|
|
82
|
-
return (
|
|
83
|
-
<>
|
|
84
|
-
{trigger}
|
|
85
|
-
<Drawer open={open} onClose={() => setOpen(false)} aria-label={ariaLabel}>
|
|
86
|
-
<div className={styles.drawerBody}>{calendar}</div>
|
|
87
|
-
</Drawer>
|
|
88
|
-
</>
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return (
|
|
93
|
-
<Popover
|
|
94
|
-
trigger={trigger}
|
|
95
|
-
open={open}
|
|
96
|
-
onClose={() => setOpen(false)}
|
|
97
|
-
aria-label={ariaLabel}
|
|
98
|
-
width={POPOVER_WIDTH}
|
|
99
|
-
>
|
|
100
|
-
<div className={styles.popoverBody}>{calendar}</div>
|
|
101
|
-
</Popover>
|
|
102
|
-
);
|
|
103
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
-
import { cleanup, fireEvent, render, screen } from '@testing-library/react';
|
|
4
|
-
import { DatePicker } from '../DatePicker';
|
|
5
|
-
|
|
6
|
-
let mobile = false;
|
|
7
|
-
function installMatchMedia() {
|
|
8
|
-
vi.stubGlobal(
|
|
9
|
-
'matchMedia',
|
|
10
|
-
vi.fn().mockImplementation((query: string) => ({
|
|
11
|
-
// useIsMobile's query is '(max-width: ...)'; Drawer's own
|
|
12
|
-
// prefers-reduced-motion check defaults to false — see Menu's
|
|
13
|
-
// identical test setup for why.
|
|
14
|
-
matches: query.includes('max-width') ? mobile : false,
|
|
15
|
-
media: query,
|
|
16
|
-
addEventListener: vi.fn(),
|
|
17
|
-
removeEventListener: vi.fn(),
|
|
18
|
-
addListener: vi.fn(),
|
|
19
|
-
removeListener: vi.fn(),
|
|
20
|
-
dispatchEvent: vi.fn(),
|
|
21
|
-
})),
|
|
22
|
-
);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const JAN_15_2026 = new Date(2026, 0, 15);
|
|
26
|
-
|
|
27
|
-
describe('DatePicker', () => {
|
|
28
|
-
beforeEach(() => {
|
|
29
|
-
mobile = false;
|
|
30
|
-
installMatchMedia();
|
|
31
|
-
});
|
|
32
|
-
afterEach(() => {
|
|
33
|
-
vi.unstubAllGlobals();
|
|
34
|
-
cleanup();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('shows the placeholder when no date is selected', () => {
|
|
38
|
-
render(
|
|
39
|
-
<DatePicker
|
|
40
|
-
value={null}
|
|
41
|
-
onChange={() => {}}
|
|
42
|
-
placeholder="Pick a date"
|
|
43
|
-
aria-label="Due date"
|
|
44
|
-
/>,
|
|
45
|
-
);
|
|
46
|
-
expect(screen.getByRole('button', { name: 'Due date' }).textContent).toContain('Pick a date');
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('shows the formatted value when a date is selected', () => {
|
|
50
|
-
render(<DatePicker value={JAN_15_2026} onChange={() => {}} aria-label="Due date" />);
|
|
51
|
-
expect(screen.getByRole('button', { name: 'Due date' }).textContent).toContain('Jan 15, 2026');
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('opens the calendar as a Popover on desktop', () => {
|
|
55
|
-
render(<DatePicker value={null} onChange={() => {}} aria-label="Due date" />);
|
|
56
|
-
fireEvent.click(screen.getByRole('button', { name: 'Due date' }));
|
|
57
|
-
const panel = screen.getByRole('dialog', { name: 'Due date' });
|
|
58
|
-
expect(panel.getAttribute('aria-modal')).toBe('false');
|
|
59
|
-
expect(screen.getByRole('grid')).toBeTruthy();
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('opens the calendar as a Drawer on mobile', () => {
|
|
63
|
-
mobile = true;
|
|
64
|
-
installMatchMedia();
|
|
65
|
-
render(<DatePicker value={null} onChange={() => {}} aria-label="Due date" />);
|
|
66
|
-
fireEvent.click(screen.getByRole('button', { name: 'Due date' }));
|
|
67
|
-
const panel = screen.getByRole('dialog', { name: 'Due date' });
|
|
68
|
-
expect(panel.getAttribute('aria-modal')).toBe('true');
|
|
69
|
-
expect(screen.getByRole('grid')).toBeTruthy();
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('calls onChange and closes the picker when a date is selected', () => {
|
|
73
|
-
const onChange = vi.fn();
|
|
74
|
-
render(<DatePicker value={JAN_15_2026} onChange={onChange} aria-label="Due date" />);
|
|
75
|
-
fireEvent.click(screen.getByRole('button', { name: 'Due date' }));
|
|
76
|
-
fireEvent.click(screen.getByRole('button', { name: /January 20, 2026/ }));
|
|
77
|
-
expect(onChange).toHaveBeenCalledOnce();
|
|
78
|
-
const called = onChange.mock.calls[0]?.[0] as Date;
|
|
79
|
-
expect(called.getDate()).toBe(20);
|
|
80
|
-
expect(screen.queryByRole('grid')).toBeNull();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('disables the trigger when disabled', () => {
|
|
84
|
-
render(<DatePicker value={null} onChange={() => {}} aria-label="Due date" disabled />);
|
|
85
|
-
expect((screen.getByRole('button', { name: 'Due date' }) as HTMLButtonElement).disabled).toBe(
|
|
86
|
-
true,
|
|
87
|
-
);
|
|
88
|
-
});
|
|
89
|
-
});
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import type { ReactNode } from 'react';
|
|
4
|
-
import { EmptyState } from '../EmptyState/EmptyState';
|
|
5
|
-
|
|
6
|
-
export interface DeviceOnlyGateProps {
|
|
7
|
-
children: ReactNode;
|
|
8
|
-
/** Defaults to a generic "this app" phrasing — pass the plugin's own name. */
|
|
9
|
-
surfaceName?: string;
|
|
10
|
-
/**
|
|
11
|
-
* The caller's own `isDeviceOnlyTierAvailable()` result (from
|
|
12
|
-
* `@sovereignfs/sdk/device-client`) — taken as a plain boolean rather than
|
|
13
|
-
* computed inside this component, so `@sovereignfs/ui` never depends on
|
|
14
|
-
* `@sovereignfs/sdk` (this package must stay usable standalone, outside the
|
|
15
|
-
* plugin runtime — RFC 0073).
|
|
16
|
-
*/
|
|
17
|
-
available: boolean;
|
|
18
|
-
className?: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* DeviceOnlyGate — blocks a `device-only`-tier plugin's content from
|
|
23
|
-
* rendering on a surface that cannot provide the durable, encrypted,
|
|
24
|
-
* device-auth-gated store that tier requires, showing an explanatory empty
|
|
25
|
-
* state instead of a broken screen (research 0012, epic tasks 2.33 + 3.36).
|
|
26
|
-
*
|
|
27
|
-
* A `device-only` plugin wraps its own root content in this the same way
|
|
28
|
-
* Console and Account wrap theirs in `OfflineGate` — an opt-in pattern each
|
|
29
|
-
* surface applies to itself, not a platform-level route gate. The launcher
|
|
30
|
-
* tile's own "Phone only" badge (`useOfflineTileState`) is advisory UI on top
|
|
31
|
-
* of this; this component is the actual gate, since a user can always reach a
|
|
32
|
-
* route directly (a bookmark, a deep link) without passing through the
|
|
33
|
-
* launcher tile at all.
|
|
34
|
-
*
|
|
35
|
-
* **Not a security boundary.** Availability here is a capability signal, not
|
|
36
|
-
* an authorization check — the real protection for `device-only` data is
|
|
37
|
-
* that it is encrypted and the key requires device auth to release (epic task
|
|
38
|
-
* 1.22), so it stays inaccessible regardless of whether this gate is present,
|
|
39
|
-
* bypassed, or simply never reached (e.g. a hand-crafted request against the
|
|
40
|
-
* plugin's own API routes).
|
|
41
|
-
*/
|
|
42
|
-
export function DeviceOnlyGate({
|
|
43
|
-
children,
|
|
44
|
-
surfaceName,
|
|
45
|
-
available,
|
|
46
|
-
className,
|
|
47
|
-
}: DeviceOnlyGateProps) {
|
|
48
|
-
if (available) return <>{children}</>;
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
<EmptyState
|
|
52
|
-
icon="smartphone"
|
|
53
|
-
heading="Phone only"
|
|
54
|
-
description={`${surfaceName ?? 'This app'} is only available on a phone with secure storage set up.`}
|
|
55
|
-
className={className}
|
|
56
|
-
/>
|
|
57
|
-
);
|
|
58
|
-
}
|
|
@@ -1,45 +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 { DeviceOnlyGate } from '../DeviceOnlyGate';
|
|
5
|
-
|
|
6
|
-
describe('DeviceOnlyGate', () => {
|
|
7
|
-
afterEach(cleanup);
|
|
8
|
-
|
|
9
|
-
it('renders children unchanged when available', () => {
|
|
10
|
-
render(
|
|
11
|
-
<DeviceOnlyGate available={true}>
|
|
12
|
-
<p>Wallet content</p>
|
|
13
|
-
</DeviceOnlyGate>,
|
|
14
|
-
);
|
|
15
|
-
expect(screen.getByText('Wallet content')).toBeDefined();
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('replaces children with an explanatory empty state when unavailable', () => {
|
|
19
|
-
render(
|
|
20
|
-
<DeviceOnlyGate available={false}>
|
|
21
|
-
<p>Wallet content</p>
|
|
22
|
-
</DeviceOnlyGate>,
|
|
23
|
-
);
|
|
24
|
-
expect(screen.queryByText('Wallet content')).toBeNull();
|
|
25
|
-
expect(screen.getByText('Phone only')).toBeDefined();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('names the surface in the message when provided', () => {
|
|
29
|
-
render(
|
|
30
|
-
<DeviceOnlyGate available={false} surfaceName="Wallet">
|
|
31
|
-
<p>Wallet content</p>
|
|
32
|
-
</DeviceOnlyGate>,
|
|
33
|
-
);
|
|
34
|
-
expect(screen.getByText(/Wallet is only available on a phone/)).toBeDefined();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('falls back to generic phrasing with no surface name', () => {
|
|
38
|
-
render(
|
|
39
|
-
<DeviceOnlyGate available={false}>
|
|
40
|
-
<p>Wallet content</p>
|
|
41
|
-
</DeviceOnlyGate>,
|
|
42
|
-
);
|
|
43
|
-
expect(screen.getByText(/This app is only available on a phone/)).toBeDefined();
|
|
44
|
-
});
|
|
45
|
-
});
|