@sovereignfs/ui 0.83.1 → 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/AppsLauncher.module.css +6 -0
- package/dist/Button.module.css +5 -0
- package/dist/Card.module.css +6 -0
- package/dist/CardTile.module.css +6 -0
- package/dist/CheckableListRow.module.css +8 -0
- package/dist/Checkbox.module.css +8 -0
- package/dist/CodeTextarea.module.css +6 -0
- package/dist/ColorPicker.module.css +6 -0
- package/dist/Combobox.module.css +6 -0
- package/dist/Command.module.css +6 -0
- package/dist/ConfirmDialog.module.css +6 -0
- package/dist/DatePicker.module.css +6 -0
- package/dist/Dialog.module.css +1 -1
- package/dist/DragHandleRow.module.css +6 -0
- package/dist/FileDropzone.module.css +25 -1
- package/dist/Header.module.css +6 -0
- package/dist/IconPicker.module.css +14 -4
- package/dist/Input.module.css +6 -0
- package/dist/LegalLinks.module.css +9 -2
- package/dist/Markdown.module.css +5 -0
- package/dist/Message.module.css +9 -0
- package/dist/MobileAppsDrawer.module.css +9 -5
- package/dist/MobileFooter.module.css +6 -0
- package/dist/NavList.module.css +11 -2
- package/dist/NavTabs.module.css +7 -1
- package/dist/NotificationsPanel.module.css +10 -0
- package/dist/QrCode.module.css +17 -0
- package/dist/QuantityStepper.module.css +4 -2
- package/dist/RadioGroup.module.css +6 -0
- package/dist/SegmentedControl.module.css +6 -0
- package/dist/Select.module.css +6 -0
- package/dist/Sheet.module.css +16 -1
- package/dist/SplitPane.module.css +1 -1
- package/dist/StepStrip.module.css +5 -3
- package/dist/SuggestionInput.module.css +12 -1
- package/dist/SwipableMobileCarouselDots.module.css +6 -0
- package/dist/SystemBanner.module.css +6 -0
- package/dist/Tabs.module.css +6 -0
- package/dist/TagInput.module.css +6 -0
- package/dist/Textarea.module.css +6 -0
- package/dist/Toast.module.css +5 -0
- package/dist/Toggle.module.css +7 -0
- package/dist/Tooltip.module.css +6 -0
- package/dist/UserMenu.module.css +5 -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 +121 -43
- package/dist/index.js +1410 -1152
- package/dist/tokens/fonts.css +53 -0
- package/dist/tokens/primitives.css +12 -12
- package/dist/tokens.css +2 -1
- package/package.json +24 -13
- package/src/__tests__/motion.test.tsx +0 -105
- package/src/__tests__/package-json.test.ts +0 -8
- 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/AppsLauncher/AppsLauncher.module.css +0 -96
- package/src/components/AppsLauncher/AppsLauncher.stories.tsx +0 -58
- package/src/components/AppsLauncher/AppsLauncher.tsx +0 -117
- package/src/components/AppsLauncher/__tests__/AppsLauncher.test.tsx +0 -52
- 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 -177
- package/src/components/Badge/Badge.stories.tsx +0 -238
- package/src/components/Badge/Badge.tsx +0 -84
- package/src/components/Badge/__tests__/Badge.test.tsx +0 -90
- 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 -169
- 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/CardTile/CardTile.module.css +0 -181
- package/src/components/CardTile/CardTile.stories.tsx +0 -86
- package/src/components/CardTile/CardTile.tsx +0 -128
- package/src/components/CardTile/__tests__/CardTile.test.tsx +0 -56
- 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/ColorPicker/ColorPicker.module.css +0 -132
- package/src/components/ColorPicker/ColorPicker.stories.tsx +0 -80
- package/src/components/ColorPicker/ColorPicker.tsx +0 -157
- package/src/components/ColorPicker/__tests__/ColorPicker.test.tsx +0 -117
- 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 -150
- package/src/components/ConfirmDialog/__tests__/ConfirmDialog.test.tsx +0 -214
- package/src/components/ConsentPrompt/ConsentPrompt.module.css +0 -21
- package/src/components/ConsentPrompt/ConsentPrompt.stories.tsx +0 -31
- package/src/components/ConsentPrompt/ConsentPrompt.tsx +0 -66
- 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 -335
- package/src/components/Dialog/Dialog.stories.tsx +0 -324
- package/src/components/Dialog/Dialog.tsx +0 -254
- package/src/components/Dialog/__tests__/Dialog.test.tsx +0 -318
- package/src/components/DragHandleRow/DragHandleRow.module.css +0 -57
- package/src/components/DragHandleRow/DragHandleRow.tsx +0 -42
- package/src/components/Drawer/Drawer.module.css +0 -121
- package/src/components/Drawer/Drawer.stories.tsx +0 -132
- package/src/components/Drawer/Drawer.tsx +0 -175
- package/src/components/Drawer/__tests__/Drawer.test.tsx +0 -226
- 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/GripIcon/GripIcon.tsx +0 -27
- package/src/components/Header/Header.module.css +0 -62
- package/src/components/Header/Header.stories.tsx +0 -452
- package/src/components/Header/Header.tsx +0 -102
- package/src/components/Header/__tests__/Header.test.tsx +0 -104
- package/src/components/HeaderFooterLayout/HeaderFooterLayout.module.css +0 -21
- package/src/components/HeaderFooterLayout/HeaderFooterLayout.stories.tsx +0 -111
- package/src/components/HeaderFooterLayout/HeaderFooterLayout.tsx +0 -54
- package/src/components/HeaderFooterLayout/__tests__/HeaderFooterLayout.test.tsx +0 -58
- 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/arrow-left-right.tsx +0 -23
- 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/bold.tsx +0 -20
- 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-chevron-left.tsx +0 -21
- package/src/components/Icon/icons/circle-x.tsx +0 -22
- package/src/components/Icon/icons/code.tsx +0 -21
- package/src/components/Icon/icons/coffee.tsx +0 -23
- package/src/components/Icon/icons/cookie.tsx +0 -25
- package/src/components/Icon/icons/copy.tsx +0 -21
- package/src/components/Icon/icons/credit-card.tsx +0 -21
- package/src/components/Icon/icons/croissant.tsx +0 -24
- package/src/components/Icon/icons/cup-soda.tsx +0 -23
- package/src/components/Icon/icons/database.tsx +0 -22
- package/src/components/Icon/icons/download.tsx +0 -22
- 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-text.tsx +0 -24
- package/src/components/Icon/icons/file.tsx +0 -21
- package/src/components/Icon/icons/fish.tsx +0 -25
- package/src/components/Icon/icons/folder-closed.tsx +0 -21
- package/src/components/Icon/icons/folder-open.tsx +0 -20
- package/src/components/Icon/icons/folder.tsx +0 -20
- package/src/components/Icon/icons/folders.tsx +0 -21
- package/src/components/Icon/icons/grid-2x2.tsx +0 -22
- package/src/components/Icon/icons/hat-glasses.tsx +0 -24
- package/src/components/Icon/icons/history.tsx +0 -22
- package/src/components/Icon/icons/house.tsx +0 -21
- package/src/components/Icon/icons/inbox.tsx +0 -21
- package/src/components/Icon/icons/index.ts +0 -394
- package/src/components/Icon/icons/info.tsx +0 -22
- package/src/components/Icon/icons/italic.tsx +0 -22
- package/src/components/Icon/icons/layers.tsx +0 -22
- package/src/components/Icon/icons/layout-dashboard.tsx +0 -23
- package/src/components/Icon/icons/layout-grid.tsx +0 -23
- package/src/components/Icon/icons/link.tsx +0 -21
- package/src/components/Icon/icons/list-ordered.tsx +0 -25
- package/src/components/Icon/icons/list.tsx +0 -25
- package/src/components/Icon/icons/lock.tsx +0 -21
- package/src/components/Icon/icons/log-out.tsx +0 -22
- package/src/components/Icon/icons/luggage.tsx +0 -24
- package/src/components/Icon/icons/mail.tsx +0 -21
- package/src/components/Icon/icons/map-pin.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/minus.tsx +0 -20
- package/src/components/Icon/icons/package.tsx +0 -23
- package/src/components/Icon/icons/paint-bucket.tsx +0 -23
- package/src/components/Icon/icons/panel-left.tsx +0 -21
- package/src/components/Icon/icons/pencil.tsx +0 -21
- package/src/components/Icon/icons/pin.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/rectangle-ellipsis.tsx +0 -23
- package/src/components/Icon/icons/refresh-cw.tsx +0 -23
- package/src/components/Icon/icons/rotate-ccw.tsx +0 -21
- package/src/components/Icon/icons/rotate-cw.tsx +0 -21
- package/src/components/Icon/icons/route.tsx +0 -22
- package/src/components/Icon/icons/salad.tsx +0 -24
- package/src/components/Icon/icons/search.tsx +0 -21
- package/src/components/Icon/icons/send.tsx +0 -21
- package/src/components/Icon/icons/settings.tsx +0 -21
- package/src/components/Icon/icons/share-2.tsx +0 -24
- package/src/components/Icon/icons/sheet.tsx +0 -24
- 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/square-kanban.tsx +0 -23
- package/src/components/Icon/icons/table.tsx +0 -23
- 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-round-plus.tsx +0 -23
- package/src/components/Icon/icons/user.tsx +0 -21
- package/src/components/Icon/icons/users.tsx +0 -23
- 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 -20
- 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 -73
- package/src/components/Markdown/Markdown.stories.tsx +0 -40
- package/src/components/Markdown/Markdown.tsx +0 -305
- package/src/components/Markdown/__tests__/Markdown.test.tsx +0 -144
- 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 -90
- package/src/components/Menu/Menu.tsx +0 -209
- package/src/components/Menu/__tests__/Menu.test.tsx +0 -227
- 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 -65
- package/src/components/MessageScroller/MessageScroller.stories.tsx +0 -84
- package/src/components/MessageScroller/MessageScroller.tsx +0 -84
- package/src/components/MessageScroller/__tests__/MessageScroller.test.tsx +0 -105
- package/src/components/MobileAppsDrawer/MobileAppsDrawer.module.css +0 -82
- 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 -122
- package/src/components/MobileFooter/__tests__/MobileFooter.test.tsx +0 -129
- package/src/components/MobileHeader/MobileHeader.module.css +0 -76
- package/src/components/MobileHeader/MobileHeader.stories.tsx +0 -57
- package/src/components/MobileHeader/MobileHeader.tsx +0 -90
- package/src/components/MobileHeader/__tests__/MobileHeader.test.tsx +0 -113
- package/src/components/NavList/NavList.module.css +0 -130
- package/src/components/NavList/NavList.tsx +0 -136
- package/src/components/NavList/__tests__/NavList.test.tsx +0 -109
- 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/NotificationsPanel/NotificationsPanel.module.css +0 -258
- package/src/components/NotificationsPanel/NotificationsPanel.stories.tsx +0 -61
- package/src/components/NotificationsPanel/NotificationsPanel.tsx +0 -187
- package/src/components/NotificationsPanel/__tests__/NotificationsPanel.test.tsx +0 -78
- 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 -77
- package/src/components/OverlayHeader/__tests__/OverlayHeader.test.tsx +0 -69
- package/src/components/PageContainer/PageContainer.module.css +0 -82
- package/src/components/PageContainer/PageContainer.tsx +0 -64
- package/src/components/PageContainer/__tests__/PageContainer.test.tsx +0 -70
- package/src/components/PageHeader/PageHeader.module.css +0 -86
- package/src/components/PageHeader/PageHeader.tsx +0 -176
- package/src/components/PageHeader/__tests__/PageHeader.test.tsx +0 -103
- package/src/components/PageLayout/PageLayout.module.css +0 -51
- package/src/components/PageLayout/PageLayout.stories.tsx +0 -73
- package/src/components/PageLayout/PageLayout.tsx +0 -51
- package/src/components/PageLayout/__tests__/PageLayout.test.tsx +0 -34
- 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 -290
- package/src/components/Popover/__tests__/Popover.test.tsx +0 -263
- 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/RootLayout/RootLayout.module.css +0 -12
- package/src/components/RootLayout/RootLayout.stories.tsx +0 -125
- package/src/components/RootLayout/RootLayout.tsx +0 -185
- package/src/components/RootLayout/__tests__/RootLayout.test.tsx +0 -161
- 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 -58
- 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/StepStrip/StepStrip.module.css +0 -53
- package/src/components/StepStrip/StepStrip.tsx +0 -74
- 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 -64
- package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.tsx +0 -243
- 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 -214
- 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 -64
- package/src/components/SwipableMobileCarouselDots/SwipableMobileCarouselDots.tsx +0 -73
- package/src/components/SwipableMobileCarouselDots/__tests__/SwipableMobileCarouselDots.test.tsx +0 -63
- package/src/components/SwipeStack/SwipeStack.module.css +0 -101
- package/src/components/SwipeStack/SwipeStack.tsx +0 -199
- package/src/components/SwipeStack/SwipeStackCard.tsx +0 -22
- package/src/components/SwipeStack/__tests__/SwipeStack.test.tsx +0 -169
- package/src/components/SystemBanner/SystemBanner.module.css +0 -79
- 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 -47
- package/src/components/Textarea/Textarea.tsx +0 -38
- package/src/components/Textarea/__tests__/Textarea.test.tsx +0 -51
- package/src/components/ThreeColumnLayout/ThreeColumnLayout.module.css +0 -35
- package/src/components/ThreeColumnLayout/ThreeColumnLayout.stories.tsx +0 -152
- package/src/components/ThreeColumnLayout/ThreeColumnLayout.tsx +0 -87
- package/src/components/ThreeColumnLayout/__tests__/ThreeColumnLayout.test.tsx +0 -78
- package/src/components/Toast/Toast.module.css +0 -191
- 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/components/UserMenu/UserMenu.module.css +0 -90
- package/src/components/UserMenu/UserMenu.stories.tsx +0 -49
- package/src/components/UserMenu/UserMenu.tsx +0 -132
- package/src/components/UserMenu/__tests__/UserMenu.test.tsx +0 -77
- 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__/usePublishShellChromeHeight.test.tsx +0 -118
- package/src/hooks/__tests__/useReorderSensors.test.ts +0 -26
- package/src/hooks/__tests__/useResponsiveLayout.test.tsx +0 -87
- package/src/hooks/__tests__/useSnapCarousel.test.tsx +0 -175
- package/src/hooks/__tests__/useSwipeReveal.test.tsx +0 -110
- package/src/hooks/__tests__/useSwipeStack.test.tsx +0 -155
- package/src/hooks/index.ts +0 -33
- 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/usePublishShellChromeHeight.ts +0 -96
- package/src/hooks/useReorderSensors.ts +0 -88
- package/src/hooks/useResponsiveLayout.ts +0 -36
- package/src/hooks/useSnapCarousel.ts +0 -284
- package/src/hooks/useSwipeReveal.ts +0 -107
- package/src/hooks/useSwipeStack.ts +0 -235
- package/src/index.ts +0 -311
- package/src/motion.ts +0 -78
- package/src/overlay-shell.ts +0 -159
- 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 -3793
- 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/GripIcon.stories.tsx +0 -29
- package/src/stories/InteractionHooks.stories.tsx +0 -925
- package/src/stories/LayoutSystem.stories.tsx +0 -639
- package/src/stories/MenuEntries.stories.tsx +0 -86
- package/src/stories/MobilePatterns.stories.tsx +0 -1536
- package/src/stories/NavList.stories.tsx +0 -147
- package/src/stories/NavTabs.stories.tsx +0 -53
- package/src/stories/OfflineGate.stories.tsx +0 -42
- package/src/stories/PageContainer.stories.tsx +0 -82
- package/src/stories/PageHeader.stories.tsx +0 -95
- package/src/stories/ResponsiveSurface.stories.tsx +0 -47
- package/src/stories/Spinner.stories.tsx +0 -25
- package/src/stories/StepStrip.stories.tsx +0 -76
- package/src/stories/SwipableMobileCarousel.stories.tsx +0 -362
- package/src/stories/SwipeStack.stories.tsx +0 -200
- package/src/stories/Textarea.stories.tsx +0 -63
- package/src/stories/TokenGallery.stories.tsx +0 -652
- package/src/stories/Tooltip.stories.tsx +0 -59
- package/src/tokens/primitives.css +0 -168
- package/src/tokens/semantic.css +0 -162
- package/src/tokens/theme-presets.css +0 -33
- package/src/tokens/theme-presets.ts +0 -72
- package/src/tokens.css +0 -5
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
3
|
-
import { Command } from './Command';
|
|
4
|
-
import { Button } from '../Button/Button';
|
|
5
|
-
|
|
6
|
-
const meta = {
|
|
7
|
-
title: 'Components/Command',
|
|
8
|
-
component: Command,
|
|
9
|
-
parameters: {
|
|
10
|
-
layout: 'padded',
|
|
11
|
-
docs: {
|
|
12
|
-
description: {
|
|
13
|
-
component:
|
|
14
|
-
'⌘K-style command palette. Opened via Dialog, which supplies the modal shell — this adds the search input, substring filtering, and arrow-key/Enter selection on top of it. Controlled: the consumer owns the open state and any global shortcut listener that flips it.',
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
args: { open: false, onClose: () => {}, items: [] },
|
|
19
|
-
} satisfies Meta<typeof Command>;
|
|
20
|
-
|
|
21
|
-
export default meta;
|
|
22
|
-
type Story = StoryObj<typeof meta>;
|
|
23
|
-
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
|
|
26
|
-
function CommandDemo() {
|
|
27
|
-
const [open, setOpen] = useState(false);
|
|
28
|
-
const [lastSelected, setLastSelected] = useState<string | null>(null);
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<div>
|
|
32
|
-
<Button onClick={() => setOpen(true)}>Open command palette</Button>
|
|
33
|
-
{lastSelected && (
|
|
34
|
-
<p
|
|
35
|
-
style={{
|
|
36
|
-
marginTop: 'var(--sv-space-3)',
|
|
37
|
-
fontSize: 14,
|
|
38
|
-
color: 'var(--sv-color-text-muted)',
|
|
39
|
-
}}
|
|
40
|
-
>
|
|
41
|
-
Last selected: {lastSelected}
|
|
42
|
-
</p>
|
|
43
|
-
)}
|
|
44
|
-
<Command
|
|
45
|
-
open={open}
|
|
46
|
-
onClose={() => setOpen(false)}
|
|
47
|
-
aria-label="Command palette"
|
|
48
|
-
items={[
|
|
49
|
-
{
|
|
50
|
-
id: 'new',
|
|
51
|
-
label: 'New conversation',
|
|
52
|
-
group: 'Actions',
|
|
53
|
-
icon: 'plus',
|
|
54
|
-
onSelect: () => setLastSelected('New conversation'),
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
id: 'export',
|
|
58
|
-
label: 'Export chat',
|
|
59
|
-
group: 'Actions',
|
|
60
|
-
icon: 'upload',
|
|
61
|
-
keywords: 'download save',
|
|
62
|
-
onSelect: () => setLastSelected('Export chat'),
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
id: 'profile',
|
|
66
|
-
label: 'Go to profile',
|
|
67
|
-
group: 'Navigation',
|
|
68
|
-
icon: 'user',
|
|
69
|
-
onSelect: () => setLastSelected('Go to profile'),
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
id: 'settings',
|
|
73
|
-
label: 'Open settings',
|
|
74
|
-
group: 'Navigation',
|
|
75
|
-
icon: 'settings',
|
|
76
|
-
onSelect: () => setLastSelected('Open settings'),
|
|
77
|
-
},
|
|
78
|
-
]}
|
|
79
|
-
/>
|
|
80
|
-
</div>
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export const Default: Story = {
|
|
85
|
-
render: () => <CommandDemo />,
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
export const OpenWithNoResults: Story = {
|
|
89
|
-
render: () => {
|
|
90
|
-
function Demo() {
|
|
91
|
-
const [open, setOpen] = useState(true);
|
|
92
|
-
return (
|
|
93
|
-
<Command
|
|
94
|
-
open={open}
|
|
95
|
-
onClose={() => setOpen(false)}
|
|
96
|
-
aria-label="Command palette"
|
|
97
|
-
placeholder="Try typing something with no matches…"
|
|
98
|
-
items={[{ id: 'a', label: 'Alpha', onSelect: () => {} }]}
|
|
99
|
-
/>
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
return <Demo />;
|
|
103
|
-
},
|
|
104
|
-
};
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
4
|
-
import type { KeyboardEvent as ReactKeyboardEvent } from 'react';
|
|
5
|
-
import { Dialog } from '../Dialog/Dialog';
|
|
6
|
-
import { Icon, type IconName } from '../Icon/Icon';
|
|
7
|
-
import styles from './Command.module.css';
|
|
8
|
-
|
|
9
|
-
export interface CommandItem {
|
|
10
|
-
id: string;
|
|
11
|
-
label: string;
|
|
12
|
-
onSelect: () => void;
|
|
13
|
-
icon?: IconName;
|
|
14
|
-
/** Section header this item is grouped under. */
|
|
15
|
-
group?: string;
|
|
16
|
-
/** Extra searchable text beyond the label (keywords, description). */
|
|
17
|
-
keywords?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface CommandProps {
|
|
21
|
-
open: boolean;
|
|
22
|
-
onClose: () => void;
|
|
23
|
-
items: CommandItem[];
|
|
24
|
-
placeholder?: string;
|
|
25
|
-
'aria-label'?: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** Case-insensitive substring match against label + keywords. Not a scored
|
|
29
|
-
* fuzzy matcher — a real fuzzy-match algorithm is a meaningfully bigger
|
|
30
|
-
* undertaking than this primitive needs until a consumer's command list
|
|
31
|
-
* is large enough that substring filtering stops being good enough. */
|
|
32
|
-
function matches(item: CommandItem, query: string): boolean {
|
|
33
|
-
if (!query) return true;
|
|
34
|
-
const haystack = `${item.label} ${item.keywords ?? ''}`.toLowerCase();
|
|
35
|
-
return haystack.includes(query.toLowerCase());
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Command — a ⌘K-style palette. Opened via `Dialog`, which supplies the
|
|
40
|
-
* modal shell (scrim, panel, mobile full-screen sheet, focus handling) —
|
|
41
|
-
* this only adds the search input, filtering, and arrow-key/Enter
|
|
42
|
-
* selection on top of it. Controlled (`open`/`onClose`): the consumer wires
|
|
43
|
-
* up their own global shortcut listener to flip `open` — a DS primitive
|
|
44
|
-
* shouldn't own a document-level keydown listener itself.
|
|
45
|
-
*/
|
|
46
|
-
export function Command({
|
|
47
|
-
open,
|
|
48
|
-
onClose,
|
|
49
|
-
items,
|
|
50
|
-
placeholder = 'Type a command…',
|
|
51
|
-
'aria-label': ariaLabel = 'Command palette',
|
|
52
|
-
}: CommandProps) {
|
|
53
|
-
const [query, setQuery] = useState('');
|
|
54
|
-
const [highlightedIndex, setHighlightedIndex] = useState(0);
|
|
55
|
-
const inputRef = useRef<HTMLInputElement>(null);
|
|
56
|
-
const listId = useId();
|
|
57
|
-
|
|
58
|
-
const filtered = useMemo(() => items.filter((item) => matches(item, query)), [items, query]);
|
|
59
|
-
|
|
60
|
-
// Reset on every open, and whenever the query narrows/widens the result
|
|
61
|
-
// set — the top match should always be the one Enter would select.
|
|
62
|
-
useEffect(() => {
|
|
63
|
-
setHighlightedIndex(0);
|
|
64
|
-
}, [query, open]);
|
|
65
|
-
|
|
66
|
-
useEffect(() => {
|
|
67
|
-
if (open) {
|
|
68
|
-
setQuery('');
|
|
69
|
-
// Dialog mounts its content in the same tick it flips `open`; focus
|
|
70
|
-
// needs the next paint to land on an element that actually exists.
|
|
71
|
-
requestAnimationFrame(() => inputRef.current?.focus());
|
|
72
|
-
}
|
|
73
|
-
}, [open]);
|
|
74
|
-
|
|
75
|
-
function handleKeyDown(e: ReactKeyboardEvent<HTMLInputElement>) {
|
|
76
|
-
if (e.key === 'ArrowDown') {
|
|
77
|
-
e.preventDefault();
|
|
78
|
-
setHighlightedIndex((i) => Math.min(i + 1, filtered.length - 1));
|
|
79
|
-
} else if (e.key === 'ArrowUp') {
|
|
80
|
-
e.preventDefault();
|
|
81
|
-
setHighlightedIndex((i) => Math.max(i - 1, 0));
|
|
82
|
-
} else if (e.key === 'Enter') {
|
|
83
|
-
e.preventDefault();
|
|
84
|
-
const item = filtered[highlightedIndex];
|
|
85
|
-
if (item) {
|
|
86
|
-
onClose();
|
|
87
|
-
item.onSelect();
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Group items in their original order, without moving an item's position
|
|
93
|
-
// relative to its own group even if groups aren't contiguous in `items`.
|
|
94
|
-
const groups = useMemo(() => {
|
|
95
|
-
const order: (string | undefined)[] = [];
|
|
96
|
-
const byGroup = new Map<string | undefined, CommandItem[]>();
|
|
97
|
-
for (const item of filtered) {
|
|
98
|
-
let bucket = byGroup.get(item.group);
|
|
99
|
-
if (!bucket) {
|
|
100
|
-
bucket = [];
|
|
101
|
-
order.push(item.group);
|
|
102
|
-
byGroup.set(item.group, bucket);
|
|
103
|
-
}
|
|
104
|
-
bucket.push(item);
|
|
105
|
-
}
|
|
106
|
-
return order.map((group) => ({ group, items: byGroup.get(group) ?? [] }));
|
|
107
|
-
}, [filtered]);
|
|
108
|
-
|
|
109
|
-
const highlightedId = filtered[highlightedIndex]
|
|
110
|
-
? `${listId}-${filtered[highlightedIndex].id}`
|
|
111
|
-
: undefined;
|
|
112
|
-
|
|
113
|
-
return (
|
|
114
|
-
<Dialog open={open} onClose={onClose} aria-label={ariaLabel} size="sm">
|
|
115
|
-
<div className={styles.root}>
|
|
116
|
-
<input
|
|
117
|
-
ref={inputRef}
|
|
118
|
-
type="text"
|
|
119
|
-
role="combobox"
|
|
120
|
-
aria-expanded={filtered.length > 0}
|
|
121
|
-
aria-controls={listId}
|
|
122
|
-
aria-activedescendant={highlightedId}
|
|
123
|
-
aria-autocomplete="list"
|
|
124
|
-
className={styles.input}
|
|
125
|
-
placeholder={placeholder}
|
|
126
|
-
value={query}
|
|
127
|
-
onChange={(e) => setQuery(e.target.value)}
|
|
128
|
-
onKeyDown={handleKeyDown}
|
|
129
|
-
/>
|
|
130
|
-
<ul id={listId} role="listbox" className={styles.list}>
|
|
131
|
-
{filtered.length === 0 && <li className={styles.empty}>No matching commands.</li>}
|
|
132
|
-
{groups.map(({ group, items: groupItems }) => (
|
|
133
|
-
<li key={group ?? '__ungrouped'}>
|
|
134
|
-
{group && <div className={styles.groupLabel}>{group}</div>}
|
|
135
|
-
<ul className={styles.groupList}>
|
|
136
|
-
{groupItems.map((item) => {
|
|
137
|
-
const index = filtered.indexOf(item);
|
|
138
|
-
const isHighlighted = index === highlightedIndex;
|
|
139
|
-
return (
|
|
140
|
-
<li key={item.id}>
|
|
141
|
-
<button
|
|
142
|
-
id={`${listId}-${item.id}`}
|
|
143
|
-
type="button"
|
|
144
|
-
role="option"
|
|
145
|
-
aria-selected={isHighlighted}
|
|
146
|
-
className={[styles.item, isHighlighted ? styles.itemHighlighted : '']
|
|
147
|
-
.filter(Boolean)
|
|
148
|
-
.join(' ')}
|
|
149
|
-
onMouseEnter={() => setHighlightedIndex(index)}
|
|
150
|
-
onClick={() => {
|
|
151
|
-
onClose();
|
|
152
|
-
item.onSelect();
|
|
153
|
-
}}
|
|
154
|
-
>
|
|
155
|
-
{item.icon && <Icon name={item.icon} size="sm" aria-hidden />}
|
|
156
|
-
{item.label}
|
|
157
|
-
</button>
|
|
158
|
-
</li>
|
|
159
|
-
);
|
|
160
|
-
})}
|
|
161
|
-
</ul>
|
|
162
|
-
</li>
|
|
163
|
-
))}
|
|
164
|
-
</ul>
|
|
165
|
-
</div>
|
|
166
|
-
</Dialog>
|
|
167
|
-
);
|
|
168
|
-
}
|
|
@@ -1,162 +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 { Command, type CommandItem } from '../Command';
|
|
5
|
-
|
|
6
|
-
// Command renders inside Dialog, whose exit animation reads
|
|
7
|
-
// prefers-reduced-motion via matchMedia, which jsdom does not implement —
|
|
8
|
-
// see Dialog.test.tsx's identical setup.
|
|
9
|
-
function installMatchMedia() {
|
|
10
|
-
vi.stubGlobal(
|
|
11
|
-
'matchMedia',
|
|
12
|
-
vi.fn().mockImplementation((query: string) => ({
|
|
13
|
-
matches: false,
|
|
14
|
-
media: query,
|
|
15
|
-
addEventListener: vi.fn(),
|
|
16
|
-
removeEventListener: vi.fn(),
|
|
17
|
-
addListener: vi.fn(),
|
|
18
|
-
removeListener: vi.fn(),
|
|
19
|
-
dispatchEvent: vi.fn(),
|
|
20
|
-
})),
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
beforeEach(installMatchMedia);
|
|
25
|
-
afterEach(() => {
|
|
26
|
-
vi.unstubAllGlobals();
|
|
27
|
-
cleanup();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
const items: CommandItem[] = [
|
|
31
|
-
{ id: 'new', label: 'New conversation', onSelect: vi.fn(), group: 'Actions' },
|
|
32
|
-
{
|
|
33
|
-
id: 'export',
|
|
34
|
-
label: 'Export chat',
|
|
35
|
-
onSelect: vi.fn(),
|
|
36
|
-
group: 'Actions',
|
|
37
|
-
keywords: 'download save',
|
|
38
|
-
},
|
|
39
|
-
{ id: 'profile', label: 'Go to profile', onSelect: vi.fn(), group: 'Navigation' },
|
|
40
|
-
];
|
|
41
|
-
|
|
42
|
-
describe('Command', () => {
|
|
43
|
-
it('renders nothing when closed', () => {
|
|
44
|
-
render(<Command open={false} onClose={vi.fn()} items={items} />);
|
|
45
|
-
expect(screen.queryByRole('combobox')).toBeNull();
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('lists all items when the query is empty', () => {
|
|
49
|
-
render(<Command open onClose={vi.fn()} items={items} />);
|
|
50
|
-
expect(screen.getAllByRole('option')).toHaveLength(3);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('filters by label', () => {
|
|
54
|
-
render(<Command open onClose={vi.fn()} items={items} />);
|
|
55
|
-
fireEvent.change(screen.getByRole('combobox'), { target: { value: 'profile' } });
|
|
56
|
-
expect(screen.getAllByRole('option')).toHaveLength(1);
|
|
57
|
-
expect(screen.getByRole('option', { name: 'Go to profile' })).toBeDefined();
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('filters by keywords in addition to the label', () => {
|
|
61
|
-
render(<Command open onClose={vi.fn()} items={items} />);
|
|
62
|
-
fireEvent.change(screen.getByRole('combobox'), { target: { value: 'download' } });
|
|
63
|
-
expect(screen.getAllByRole('option')).toHaveLength(1);
|
|
64
|
-
expect(screen.getByRole('option', { name: 'Export chat' })).toBeDefined();
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('shows an empty state when nothing matches', () => {
|
|
68
|
-
render(<Command open onClose={vi.fn()} items={items} />);
|
|
69
|
-
fireEvent.change(screen.getByRole('combobox'), { target: { value: 'zzz' } });
|
|
70
|
-
expect(screen.queryAllByRole('option')).toHaveLength(0);
|
|
71
|
-
expect(screen.getByText('No matching commands.')).toBeDefined();
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('renders group headers', () => {
|
|
75
|
-
render(<Command open onClose={vi.fn()} items={items} />);
|
|
76
|
-
expect(screen.getByText('Actions')).toBeDefined();
|
|
77
|
-
expect(screen.getByText('Navigation')).toBeDefined();
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('highlights the first item by default', () => {
|
|
81
|
-
render(<Command open onClose={vi.fn()} items={items} />);
|
|
82
|
-
expect(
|
|
83
|
-
screen.getByRole('option', { name: 'New conversation' }).getAttribute('aria-selected'),
|
|
84
|
-
).toBe('true');
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('ArrowDown/ArrowUp move the highlighted item', () => {
|
|
88
|
-
render(<Command open onClose={vi.fn()} items={items} />);
|
|
89
|
-
const input = screen.getByRole('combobox');
|
|
90
|
-
fireEvent.keyDown(input, { key: 'ArrowDown' });
|
|
91
|
-
expect(screen.getByRole('option', { name: 'Export chat' }).getAttribute('aria-selected')).toBe(
|
|
92
|
-
'true',
|
|
93
|
-
);
|
|
94
|
-
fireEvent.keyDown(input, { key: 'ArrowUp' });
|
|
95
|
-
expect(
|
|
96
|
-
screen.getByRole('option', { name: 'New conversation' }).getAttribute('aria-selected'),
|
|
97
|
-
).toBe('true');
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('ArrowDown does not move past the last item', () => {
|
|
101
|
-
render(<Command open onClose={vi.fn()} items={items} />);
|
|
102
|
-
const input = screen.getByRole('combobox');
|
|
103
|
-
fireEvent.keyDown(input, { key: 'ArrowDown' });
|
|
104
|
-
fireEvent.keyDown(input, { key: 'ArrowDown' });
|
|
105
|
-
fireEvent.keyDown(input, { key: 'ArrowDown' });
|
|
106
|
-
expect(
|
|
107
|
-
screen.getByRole('option', { name: 'Go to profile' }).getAttribute('aria-selected'),
|
|
108
|
-
).toBe('true');
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('resets the highlighted index when the query changes', () => {
|
|
112
|
-
render(<Command open onClose={vi.fn()} items={items} />);
|
|
113
|
-
const input = screen.getByRole('combobox');
|
|
114
|
-
fireEvent.keyDown(input, { key: 'ArrowDown' });
|
|
115
|
-
fireEvent.change(input, { target: { value: 'e' } });
|
|
116
|
-
const [firstMatch] = screen.getAllByRole('option');
|
|
117
|
-
expect(firstMatch?.getAttribute('aria-selected')).toBe('true');
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('Enter selects the highlighted item and closes', () => {
|
|
121
|
-
const onClose = vi.fn();
|
|
122
|
-
const onSelect = vi.fn();
|
|
123
|
-
const localItems: CommandItem[] = [{ id: 'a', label: 'Alpha', onSelect }];
|
|
124
|
-
render(<Command open onClose={onClose} items={localItems} />);
|
|
125
|
-
fireEvent.keyDown(screen.getByRole('combobox'), { key: 'Enter' });
|
|
126
|
-
expect(onSelect).toHaveBeenCalled();
|
|
127
|
-
expect(onClose).toHaveBeenCalled();
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it('clicking an item selects it and closes', () => {
|
|
131
|
-
const onClose = vi.fn();
|
|
132
|
-
const onSelect = vi.fn();
|
|
133
|
-
const localItems: CommandItem[] = [{ id: 'a', label: 'Alpha', onSelect }];
|
|
134
|
-
render(<Command open onClose={onClose} items={localItems} />);
|
|
135
|
-
fireEvent.click(screen.getByRole('option', { name: 'Alpha' }));
|
|
136
|
-
expect(onSelect).toHaveBeenCalled();
|
|
137
|
-
expect(onClose).toHaveBeenCalled();
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it('sets aria-activedescendant to the highlighted option', () => {
|
|
141
|
-
render(<Command open onClose={vi.fn()} items={items} />);
|
|
142
|
-
const input = screen.getByRole('combobox');
|
|
143
|
-
const firstOption = screen.getByRole('option', { name: 'New conversation' });
|
|
144
|
-
expect(input.getAttribute('aria-activedescendant')).toBe(firstOption.id);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('resets the query each time it opens', () => {
|
|
148
|
-
const { rerender } = render(<Command open onClose={vi.fn()} items={items} />);
|
|
149
|
-
fireEvent.change(screen.getByRole('combobox'), { target: { value: 'profile' } });
|
|
150
|
-
rerender(<Command open={false} onClose={vi.fn()} items={items} />);
|
|
151
|
-
rerender(<Command open onClose={vi.fn()} items={items} />);
|
|
152
|
-
expect(screen.getByRole('combobox').getAttribute('value')).toBe('');
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it('focuses the input on open', async () => {
|
|
156
|
-
render(<Command open onClose={vi.fn()} items={items} />);
|
|
157
|
-
await act(async () => {
|
|
158
|
-
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
159
|
-
});
|
|
160
|
-
expect(document.activeElement).toBe(screen.getByRole('combobox'));
|
|
161
|
-
});
|
|
162
|
-
});
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
/* ConfirmDialog — built on the native <dialog> element (see the component's
|
|
2
|
-
* own doc comment for why: content-sized, top-layer stacking, ::backdrop for
|
|
3
|
-
* free). Tokens only. */
|
|
4
|
-
|
|
5
|
-
.dialog {
|
|
6
|
-
border: none;
|
|
7
|
-
border-radius: var(--sv-radius-lg);
|
|
8
|
-
background: var(--sv-color-surface);
|
|
9
|
-
box-shadow: var(--sv-shadow-overlay);
|
|
10
|
-
padding: 0;
|
|
11
|
-
width: 22rem;
|
|
12
|
-
max-width: calc(100vw - var(--sv-space-8));
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
.dialog::backdrop {
|
|
16
|
-
background: var(--sv-color-scrim);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
.body {
|
|
20
|
-
display: flex;
|
|
21
|
-
flex-direction: column;
|
|
22
|
-
gap: var(--sv-space-4);
|
|
23
|
-
padding: var(--sv-space-6);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
.title {
|
|
27
|
-
margin: 0;
|
|
28
|
-
font-size: var(--sv-font-size-lg);
|
|
29
|
-
font-weight: var(--sv-font-weight-semibold);
|
|
30
|
-
color: var(--sv-color-text-primary);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.message {
|
|
34
|
-
margin: 0;
|
|
35
|
-
font-size: var(--sv-font-size-sm);
|
|
36
|
-
color: var(--sv-color-text-muted);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
.error {
|
|
40
|
-
margin: 0;
|
|
41
|
-
font-size: var(--sv-font-size-xs);
|
|
42
|
-
color: var(--sv-color-error-text);
|
|
43
|
-
padding: var(--sv-space-2) var(--sv-space-3);
|
|
44
|
-
border: var(--sv-border-width-hairline) solid var(--sv-color-error-border);
|
|
45
|
-
border-radius: var(--sv-radius-sm);
|
|
46
|
-
background-color: var(--sv-color-error-surface);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.actions {
|
|
50
|
-
display: flex;
|
|
51
|
-
gap: var(--sv-space-3);
|
|
52
|
-
justify-content: flex-end;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/* Matches Button's own .button + .md structural rules exactly (padding,
|
|
56
|
-
* radius, font weight, focus-visible ring, disabled state, touch target,
|
|
57
|
-
* hover guard, active/pressed state) so it reads as the same control family
|
|
58
|
-
* — only the color scheme differs. Not built as an override of Button's own
|
|
59
|
-
* CSS module: cross-module class-order in the cascade isn't guaranteed, and
|
|
60
|
-
* an inline-style override would defeat Button's own :hover rule (inline
|
|
61
|
-
* styles always win over an external stylesheet, hover or not). A plain
|
|
62
|
-
* button with its own tokens sidesteps both problems. */
|
|
63
|
-
.destructiveConfirm {
|
|
64
|
-
display: inline-flex;
|
|
65
|
-
align-items: center;
|
|
66
|
-
justify-content: center;
|
|
67
|
-
gap: var(--sv-space-2);
|
|
68
|
-
/* Kept in sync with Button's .md rule by hand (see design-system.md
|
|
69
|
-
"Button and Checkbox touch ergonomics" — not a literal reuse of
|
|
70
|
-
Button). Matches Button's md padding, updated alongside it (10px,
|
|
71
|
-
36px rendered height). */
|
|
72
|
-
padding: 10px var(--sv-space-4);
|
|
73
|
-
border: var(--sv-border-width-hairline) solid transparent;
|
|
74
|
-
border-radius: var(--sv-radius-md);
|
|
75
|
-
font-family: var(--sv-font-family);
|
|
76
|
-
font-size: var(--sv-font-size-sm);
|
|
77
|
-
font-weight: var(--sv-font-weight-semibold);
|
|
78
|
-
line-height: 1;
|
|
79
|
-
cursor: pointer;
|
|
80
|
-
touch-action: manipulation;
|
|
81
|
-
background-color: var(--sv-color-error-solid);
|
|
82
|
-
color: var(--sv-color-text-on-error);
|
|
83
|
-
transition: background-color 0.15s ease;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
@media (pointer: coarse) {
|
|
87
|
-
.destructiveConfirm {
|
|
88
|
-
min-height: var(--sv-touch-target-min, 44px);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/* Derived via color-mix from the semantic token, not a raw --sv-red-800
|
|
93
|
-
primitive (the original .dangerButton pattern this replaces did reference
|
|
94
|
-
the primitive directly — fixed here, not carried forward). */
|
|
95
|
-
@media (hover: hover) {
|
|
96
|
-
.destructiveConfirm:hover:not(:disabled) {
|
|
97
|
-
background-color: color-mix(in srgb, var(--sv-color-error-solid) 85%, black);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
.destructiveConfirm:active:not(:disabled) {
|
|
102
|
-
background-color: color-mix(in srgb, var(--sv-color-error-solid) 80%, black);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
.destructiveConfirm:focus-visible {
|
|
106
|
-
outline: 2px solid var(--sv-color-focus-ring);
|
|
107
|
-
outline-offset: 2px;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
.destructiveConfirm:disabled {
|
|
111
|
-
opacity: 0.5;
|
|
112
|
-
cursor: not-allowed;
|
|
113
|
-
}
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { type ReactNode, useEffect, useRef } from 'react';
|
|
4
|
-
import { registerOpenOverlay, unregisterOpenOverlay } from '../../overlay-shell';
|
|
5
|
-
import { Button } from '../Button/Button';
|
|
6
|
-
import styles from './ConfirmDialog.module.css';
|
|
7
|
-
|
|
8
|
-
export interface ConfirmDialogProps {
|
|
9
|
-
/** Whether the dialog is shown. */
|
|
10
|
-
open: boolean;
|
|
11
|
-
/** Called on Esc, backdrop click, or the Cancel button. */
|
|
12
|
-
onClose: () => void;
|
|
13
|
-
title: string;
|
|
14
|
-
/** Accepts rich content (e.g. `Remove <strong>{name}</strong>?`), not just
|
|
15
|
-
* plain text. */
|
|
16
|
-
message: ReactNode;
|
|
17
|
-
/** Called when the confirm action is clicked. Does not close the dialog
|
|
18
|
-
* itself — pass `pending`/close it via `onClose` from the caller once the
|
|
19
|
-
* action settles, so an async action that fails can keep the dialog open
|
|
20
|
-
* to show `error`. */
|
|
21
|
-
onConfirm: () => void;
|
|
22
|
-
confirmLabel?: string;
|
|
23
|
-
cancelLabel?: string;
|
|
24
|
-
/** Styles the confirm action as a destructive (red) action. */
|
|
25
|
-
destructive?: boolean;
|
|
26
|
-
/** Disables both actions — pair with a `confirmLabel` reflecting the
|
|
27
|
-
* in-flight state (e.g. "Removing…") for an `onConfirm` that triggers an
|
|
28
|
-
* async action. */
|
|
29
|
-
pending?: boolean;
|
|
30
|
-
/** Optional inline error, shown between the message and the actions — e.g.
|
|
31
|
-
* a failed async `onConfirm` that keeps the dialog open to report it. */
|
|
32
|
-
error?: ReactNode;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* ConfirmDialog — a small, content-sized confirm/cancel prompt. Deliberately
|
|
37
|
-
* NOT built on `Dialog`, which is a fixed-size box by design (so switching
|
|
38
|
-
* views inside it never resizes the panel) — a confirm prompt is the opposite
|
|
39
|
-
* case, sized to its message. Built on the native `<dialog>` element instead,
|
|
40
|
-
* same as the pattern this component replaces (duplicated across the account
|
|
41
|
-
* and console plugins): top-layer rendering and `::backdrop` come from the
|
|
42
|
-
* browser for free, which is what makes a confirm opened *from inside*
|
|
43
|
-
* another overlay (Dialog, Sheet) reliably stack above it without a manual
|
|
44
|
-
* z-index scheme.
|
|
45
|
-
*
|
|
46
|
-
* Same presentation on desktop and mobile — a small centered card, not a
|
|
47
|
-
* full-screen sheet (unlike Dialog/Sheet, which do fork by viewport).
|
|
48
|
-
*/
|
|
49
|
-
export function ConfirmDialog({
|
|
50
|
-
open,
|
|
51
|
-
onClose,
|
|
52
|
-
title,
|
|
53
|
-
message,
|
|
54
|
-
onConfirm,
|
|
55
|
-
confirmLabel = 'Confirm',
|
|
56
|
-
cancelLabel = 'Cancel',
|
|
57
|
-
destructive = false,
|
|
58
|
-
pending = false,
|
|
59
|
-
error,
|
|
60
|
-
}: ConfirmDialogProps) {
|
|
61
|
-
const dialogRef = useRef<HTMLDialogElement>(null);
|
|
62
|
-
|
|
63
|
-
// Registers into the same open-overlay stack Dialog/Drawer/Sheet use
|
|
64
|
-
// (overlay-shell.ts) purely so a Dialog this ConfirmDialog was opened
|
|
65
|
-
// from inside of knows it is no longer the topmost overlay, and defers
|
|
66
|
-
// Escape to this one instead of closing itself. ConfirmDialog does not
|
|
67
|
-
// otherwise adopt useOverlayFocusCapture/useOverlayScrollLock/
|
|
68
|
-
// useOverlayKeyboardTrap — see this component's own doc comment for why
|
|
69
|
-
// it stays on the native <dialog> element for everything else.
|
|
70
|
-
useEffect(() => {
|
|
71
|
-
if (!open) return;
|
|
72
|
-
const id = registerOpenOverlay();
|
|
73
|
-
return () => unregisterOpenOverlay(id);
|
|
74
|
-
}, [open]);
|
|
75
|
-
|
|
76
|
-
useEffect(() => {
|
|
77
|
-
const el = dialogRef.current;
|
|
78
|
-
if (!el) return;
|
|
79
|
-
if (open) el.showModal();
|
|
80
|
-
else el.close();
|
|
81
|
-
}, [open]);
|
|
82
|
-
|
|
83
|
-
// The native <dialog> fires its own 'close' event on Esc (built-in, no
|
|
84
|
-
// listener needed for that) and whenever .close() runs — including the
|
|
85
|
-
// effect above reacting to `open` going false. Listening for it here is
|
|
86
|
-
// what makes Esc call the caller's onClose without a manual keydown handler.
|
|
87
|
-
// It is also the SINGLE call site for onClose: Cancel and backdrop-click
|
|
88
|
-
// below call dialogRef.current?.close() rather than onClose directly, so
|
|
89
|
-
// every dismissal path funnels through this one 'close' listener. Calling
|
|
90
|
-
// onClose directly from those handlers used to double-fire it — the
|
|
91
|
-
// consumer's onClose sets `open` to false as documented, which re-runs the
|
|
92
|
-
// effect above, which calls el.close() again, firing this same 'close'
|
|
93
|
-
// listener a second time for what the user experienced as one click.
|
|
94
|
-
useEffect(() => {
|
|
95
|
-
const el = dialogRef.current;
|
|
96
|
-
if (!el) return;
|
|
97
|
-
const handleClose = () => onClose();
|
|
98
|
-
el.addEventListener('close', handleClose);
|
|
99
|
-
return () => el.removeEventListener('close', handleClose);
|
|
100
|
-
}, [onClose]);
|
|
101
|
-
|
|
102
|
-
return (
|
|
103
|
-
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions
|
|
104
|
-
<dialog
|
|
105
|
-
ref={dialogRef}
|
|
106
|
-
className={styles.dialog}
|
|
107
|
-
aria-label={title}
|
|
108
|
-
onClick={(e) => {
|
|
109
|
-
if (e.target === e.currentTarget) dialogRef.current?.close();
|
|
110
|
-
}}
|
|
111
|
-
>
|
|
112
|
-
<div className={styles.body}>
|
|
113
|
-
<h2 className={styles.title}>{title}</h2>
|
|
114
|
-
<div className={styles.message}>{message}</div>
|
|
115
|
-
{error && <p className={styles.error}>{error}</p>}
|
|
116
|
-
<div className={styles.actions}>
|
|
117
|
-
<Button
|
|
118
|
-
type="button"
|
|
119
|
-
variant="secondary"
|
|
120
|
-
onClick={() => dialogRef.current?.close()}
|
|
121
|
-
disabled={pending}
|
|
122
|
-
>
|
|
123
|
-
{cancelLabel}
|
|
124
|
-
</Button>
|
|
125
|
-
{destructive ? (
|
|
126
|
-
// A solid red fill, not Button's own `destructive` variant (an
|
|
127
|
-
// outlined/ghost style meant for lower-emphasis placements) — a
|
|
128
|
-
// confirm action should read as strongly as `Button`'s primary
|
|
129
|
-
// variant does. Uses the same --sv-color-error-solid /
|
|
130
|
-
// -text-on-error tokens the codebase already introduced for this
|
|
131
|
-
// exact "reads as a filled action button, not a tinted status
|
|
132
|
-
// surface" case (see semantic.css).
|
|
133
|
-
<button
|
|
134
|
-
type="button"
|
|
135
|
-
className={styles.destructiveConfirm}
|
|
136
|
-
onClick={onConfirm}
|
|
137
|
-
disabled={pending}
|
|
138
|
-
>
|
|
139
|
-
{confirmLabel}
|
|
140
|
-
</button>
|
|
141
|
-
) : (
|
|
142
|
-
<Button type="button" variant="primary" onClick={onConfirm} disabled={pending}>
|
|
143
|
-
{confirmLabel}
|
|
144
|
-
</Button>
|
|
145
|
-
)}
|
|
146
|
-
</div>
|
|
147
|
-
</div>
|
|
148
|
-
</dialog>
|
|
149
|
-
);
|
|
150
|
-
}
|