@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,305 +0,0 @@
|
|
|
1
|
-
import { Fragment, type ReactNode } from 'react';
|
|
2
|
-
import { Typography } from '../Typography/Typography';
|
|
3
|
-
import styles from './Markdown.module.css';
|
|
4
|
-
|
|
5
|
-
export interface MarkdownProps {
|
|
6
|
-
/** Raw markdown source. Supports: headings (#/##/###), paragraphs,
|
|
7
|
-
* **bold**, *italic*, `code`, [links](url), unordered lists (-), ordered
|
|
8
|
-
* lists (1. / 1)), fenced code blocks (```), and blockquotes (>),
|
|
9
|
-
* including soft-wrapped continuation lines within a paragraph or list
|
|
10
|
-
* item. Not a general-purpose CommonMark parser — no tables, images, or
|
|
11
|
-
* nested blocks. */
|
|
12
|
-
content: string;
|
|
13
|
-
/** Renders every line within a paragraph on its own visual line (a
|
|
14
|
-
* `<br>` between them) instead of the default CommonMark-style soft-wrap
|
|
15
|
-
* (consecutive lines joined with a single space, requiring a blank line
|
|
16
|
-
* for a new paragraph). Off by default — the soft-wrap behavior is
|
|
17
|
-
* correct for this component's primary use (long-form, first-party
|
|
18
|
-
* content authored *as* markdown, where a hard line break is a
|
|
19
|
-
* deliberate `\` ` or blank-line choice). Turn this on when rendering
|
|
20
|
-
* plain, user-typed multi-line text that happens to be passed through
|
|
21
|
-
* this component (e.g. a card description or comment body edited in an
|
|
22
|
-
* ordinary `<textarea>`) — there, every Enter press is a real,
|
|
23
|
-
* intentional line break the user typed, and silently collapsing it
|
|
24
|
-
* into one run-on paragraph reads as broken, not as "markdown
|
|
25
|
-
* formatting applied." */
|
|
26
|
-
preserveLineBreaks?: boolean;
|
|
27
|
-
className?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
let inlineKey = 0;
|
|
31
|
-
|
|
32
|
-
/** Schemes allowed in a rendered `[text](url)` link's `href`. Anything else —
|
|
33
|
-
* most importantly `javascript:` — is rejected: `content` here can be
|
|
34
|
-
* LLM-generated (Warden's chat replies), so a prompt-injected document or a
|
|
35
|
-
* compromised model is a realistic way for an attacker-chosen URL to end up
|
|
36
|
-
* in this position. */
|
|
37
|
-
const SAFE_HREF_SCHEMES = new Set(['http', 'https', 'mailto', 'tel']);
|
|
38
|
-
|
|
39
|
-
function isSafeHref(href: string): boolean {
|
|
40
|
-
const scheme = /^([a-zA-Z][a-zA-Z\d+.-]*):/.exec(href)?.[1];
|
|
41
|
-
return scheme === undefined || SAFE_HREF_SCHEMES.has(scheme.toLowerCase());
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** Returns a link's `href` value ready to render, or `null` to reject it.
|
|
45
|
-
* Rejecting is what keeps an unsafe URL out of the DOM; `encodeURI` on top
|
|
46
|
-
* is the contextual output-encoding this class of finding calls for. The
|
|
47
|
-
* sink below reads this function's *return value*, never the raw `href` —
|
|
48
|
-
* a boolean guard around the original string still leaves the tainted
|
|
49
|
-
* value itself flowing into `href`. Browsers ignore tabs/newlines when
|
|
50
|
-
* parsing a URL's scheme, so those are stripped first to close a
|
|
51
|
-
* `jav\tascript:`-style bypass; `encodeURI` throws on a lone unpaired
|
|
52
|
-
* surrogate, hence the try/catch. */
|
|
53
|
-
function sanitizeHref(href: string): string | null {
|
|
54
|
-
const normalized = href.replace(/[\t\n\r]/g, '').trim();
|
|
55
|
-
if (!normalized || !isSafeHref(normalized)) return null;
|
|
56
|
-
try {
|
|
57
|
-
return encodeURI(normalized);
|
|
58
|
-
} catch {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** Parses **bold**, *italic*, `code`, and [text](url) within a line of text. */
|
|
64
|
-
function parseInline(text: string): ReactNode[] {
|
|
65
|
-
const nodes: ReactNode[] = [];
|
|
66
|
-
const pattern = /\[([^\]]+)\]\(([^)]+)\)|\*\*([^*]+)\*\*|\*([^*]+)\*|`([^`]+)`/;
|
|
67
|
-
let remaining = text;
|
|
68
|
-
|
|
69
|
-
while (remaining.length > 0) {
|
|
70
|
-
const match = pattern.exec(remaining);
|
|
71
|
-
if (!match) {
|
|
72
|
-
nodes.push(remaining);
|
|
73
|
-
break;
|
|
74
|
-
}
|
|
75
|
-
if (match.index > 0) nodes.push(remaining.slice(0, match.index));
|
|
76
|
-
|
|
77
|
-
const [full, linkText, linkHref, boldText, italicText, codeText] = match;
|
|
78
|
-
const key = `md-inline-${inlineKey++}`;
|
|
79
|
-
if (linkHref !== undefined) {
|
|
80
|
-
const safeHref = sanitizeHref(linkHref);
|
|
81
|
-
nodes.push(
|
|
82
|
-
safeHref !== null ? (
|
|
83
|
-
<a key={key} href={safeHref}>
|
|
84
|
-
{linkText}
|
|
85
|
-
</a>
|
|
86
|
-
) : (
|
|
87
|
-
linkText
|
|
88
|
-
),
|
|
89
|
-
);
|
|
90
|
-
} else if (boldText !== undefined) {
|
|
91
|
-
nodes.push(<strong key={key}>{boldText}</strong>);
|
|
92
|
-
} else if (italicText !== undefined) {
|
|
93
|
-
nodes.push(<em key={key}>{italicText}</em>);
|
|
94
|
-
} else if (codeText !== undefined) {
|
|
95
|
-
nodes.push(<code key={key}>{codeText}</code>);
|
|
96
|
-
}
|
|
97
|
-
remaining = remaining.slice(match.index + full.length);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return nodes;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const isQuote = (l: string) => l.startsWith('> ') || l === '>';
|
|
104
|
-
const isListItem = (l: string) => l.startsWith('- ');
|
|
105
|
-
const isHeading = (l: string) => /^#{1,3}\s+/.test(l);
|
|
106
|
-
/** ```` ```lang ```` — an opening (or closing) code fence. */
|
|
107
|
-
const isFence = (l: string) => /^\s*`{3,}/.test(l);
|
|
108
|
-
/** `1. item` or `1) item`. Bounded digit count so a long numeric line
|
|
109
|
-
* can't be mistaken for a list marker. */
|
|
110
|
-
const isOrderedItem = (l: string) => /^\d{1,9}[.)]\s+/.test(l);
|
|
111
|
-
/** A line that ends the current *paragraph* run — a blank line, or the
|
|
112
|
-
* start of a heading, quote, list, or code block. Anything else (including
|
|
113
|
-
* an indented continuation line) is folded into the paragraph, matching how
|
|
114
|
-
* the source markdown soft-wraps prose across lines. */
|
|
115
|
-
const endsParagraph = (l: string) =>
|
|
116
|
-
l.trim() === '' || isHeading(l) || isQuote(l) || isListItem(l) || isOrderedItem(l) || isFence(l);
|
|
117
|
-
/** A line that ends the current *unordered list* run — a blank line, or the
|
|
118
|
-
* start of a heading, quote, code block, or an *ordered* item. Deliberately
|
|
119
|
-
* does NOT include `isListItem`: a list item's own opening line legitimately
|
|
120
|
-
* satisfies `isListItem`, so reusing `endsParagraph` here (which does) would
|
|
121
|
-
* treat the first line of every list as ending the list before it started —
|
|
122
|
-
* the list-collection loop would then never advance, looping forever. */
|
|
123
|
-
const endsList = (l: string) =>
|
|
124
|
-
l.trim() === '' || isHeading(l) || isQuote(l) || isFence(l) || isOrderedItem(l);
|
|
125
|
-
/** The ordered-list mirror of `endsList` — excludes `isOrderedItem` for the
|
|
126
|
-
* same reason, and treats an unordered item as the start of a new block. */
|
|
127
|
-
const endsOrderedList = (l: string) =>
|
|
128
|
-
l.trim() === '' || isHeading(l) || isQuote(l) || isFence(l) || isListItem(l);
|
|
129
|
-
|
|
130
|
-
function parseBlocks(content: string, preserveLineBreaks: boolean): ReactNode[] {
|
|
131
|
-
const lines = content.replace(/\r\n/g, '\n').split('\n');
|
|
132
|
-
const blocks: ReactNode[] = [];
|
|
133
|
-
let i = 0;
|
|
134
|
-
let blockKey = 0;
|
|
135
|
-
|
|
136
|
-
while (i < lines.length) {
|
|
137
|
-
const line = lines[i] ?? '';
|
|
138
|
-
|
|
139
|
-
if (line.trim() === '') {
|
|
140
|
-
i++;
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Checked before every other block type: everything between the fences
|
|
145
|
-
// is verbatim, including blank lines and lines that would otherwise
|
|
146
|
-
// read as headings, quotes, or list items.
|
|
147
|
-
if (isFence(line)) {
|
|
148
|
-
const fenceMatch = /^\s*(`{3,})(.*)$/.exec(line);
|
|
149
|
-
const fence = fenceMatch?.[1] ?? '```';
|
|
150
|
-
const language = (fenceMatch?.[2] ?? '').trim();
|
|
151
|
-
const closing = new RegExp(`^\\s*${fence}\\s*$`);
|
|
152
|
-
i++;
|
|
153
|
-
const codeLines: string[] = [];
|
|
154
|
-
while (i < lines.length && !closing.test(lines[i] ?? '')) {
|
|
155
|
-
codeLines.push(lines[i] ?? '');
|
|
156
|
-
i++;
|
|
157
|
-
}
|
|
158
|
-
// Steps past the closing fence; a no-op at end of input, so an
|
|
159
|
-
// unterminated fence renders the remainder as code (as CommonMark
|
|
160
|
-
// does) rather than looping.
|
|
161
|
-
i++;
|
|
162
|
-
blocks.push(
|
|
163
|
-
<div key={blockKey++} className={styles.block}>
|
|
164
|
-
<pre className={styles.codeBlock}>
|
|
165
|
-
<code data-language={language || undefined}>{codeLines.join('\n')}</code>
|
|
166
|
-
</pre>
|
|
167
|
-
</div>,
|
|
168
|
-
);
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const heading = /^(#{1,3})\s+(.*)$/.exec(line);
|
|
173
|
-
if (heading) {
|
|
174
|
-
const level = (heading[1]?.length ?? 1) as 1 | 2 | 3;
|
|
175
|
-
blocks.push(
|
|
176
|
-
<div key={blockKey++} className={styles.block} data-heading>
|
|
177
|
-
<Typography variant={`h${level}` as 'h1' | 'h2' | 'h3'}>
|
|
178
|
-
{parseInline(heading[2] ?? '')}
|
|
179
|
-
</Typography>
|
|
180
|
-
</div>,
|
|
181
|
-
);
|
|
182
|
-
i++;
|
|
183
|
-
continue;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (isQuote(line)) {
|
|
187
|
-
const quoteLines: string[] = [];
|
|
188
|
-
while (i < lines.length && isQuote(lines[i] ?? '')) {
|
|
189
|
-
quoteLines.push((lines[i] ?? '').slice(2));
|
|
190
|
-
i++;
|
|
191
|
-
}
|
|
192
|
-
blocks.push(
|
|
193
|
-
<div key={blockKey++} className={styles.block}>
|
|
194
|
-
<blockquote className={styles.blockquote}>
|
|
195
|
-
<Typography variant="body" as="p">
|
|
196
|
-
{parseInline(quoteLines.join(' '))}
|
|
197
|
-
</Typography>
|
|
198
|
-
</blockquote>
|
|
199
|
-
</div>,
|
|
200
|
-
);
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (isOrderedItem(line)) {
|
|
205
|
-
const items: string[] = [];
|
|
206
|
-
let start: number | undefined;
|
|
207
|
-
while (i < lines.length && !endsOrderedList(lines[i] ?? '')) {
|
|
208
|
-
const current = lines[i] ?? '';
|
|
209
|
-
const match = /^(\d{1,9})[.)]\s+(.*)$/.exec(current);
|
|
210
|
-
if (match) {
|
|
211
|
-
// Honour the author's own starting number (a list resuming at 4),
|
|
212
|
-
// but let the browser number the rest — markdown's own numbers
|
|
213
|
-
// are conventionally ignored after the first.
|
|
214
|
-
if (start === undefined) start = Number(match[1]);
|
|
215
|
-
items.push(match[2] ?? '');
|
|
216
|
-
} else {
|
|
217
|
-
const lastIndex = items.length - 1;
|
|
218
|
-
items[lastIndex] = `${items[lastIndex] ?? ''} ${current.trim()}`;
|
|
219
|
-
}
|
|
220
|
-
i++;
|
|
221
|
-
}
|
|
222
|
-
blocks.push(
|
|
223
|
-
<div key={blockKey++} className={styles.block}>
|
|
224
|
-
<ol
|
|
225
|
-
className={styles.list}
|
|
226
|
-
start={start !== undefined && start !== 1 ? start : undefined}
|
|
227
|
-
>
|
|
228
|
-
{items.map((item, idx) => (
|
|
229
|
-
<Typography key={idx} variant="body" as="li">
|
|
230
|
-
{parseInline(item)}
|
|
231
|
-
</Typography>
|
|
232
|
-
))}
|
|
233
|
-
</ol>
|
|
234
|
-
</div>,
|
|
235
|
-
);
|
|
236
|
-
continue;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
if (isListItem(line)) {
|
|
240
|
-
const items: string[] = [];
|
|
241
|
-
while (i < lines.length && !endsList(lines[i] ?? '')) {
|
|
242
|
-
const current = lines[i] ?? '';
|
|
243
|
-
if (isListItem(current)) {
|
|
244
|
-
items.push(current.slice(2));
|
|
245
|
-
} else {
|
|
246
|
-
// Continuation of the previous item's wrapped text.
|
|
247
|
-
const lastIndex = items.length - 1;
|
|
248
|
-
items[lastIndex] = `${items[lastIndex] ?? ''} ${current.trim()}`;
|
|
249
|
-
}
|
|
250
|
-
i++;
|
|
251
|
-
}
|
|
252
|
-
blocks.push(
|
|
253
|
-
<div key={blockKey++} className={styles.block}>
|
|
254
|
-
<ul className={styles.list}>
|
|
255
|
-
{items.map((item, idx) => (
|
|
256
|
-
<Typography key={idx} variant="body" as="li">
|
|
257
|
-
{parseInline(item)}
|
|
258
|
-
</Typography>
|
|
259
|
-
))}
|
|
260
|
-
</ul>
|
|
261
|
-
</div>,
|
|
262
|
-
);
|
|
263
|
-
continue;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const paraLines: string[] = [];
|
|
267
|
-
while (i < lines.length && !endsParagraph(lines[i] ?? '')) {
|
|
268
|
-
paraLines.push((lines[i] ?? '').trim());
|
|
269
|
-
i++;
|
|
270
|
-
}
|
|
271
|
-
blocks.push(
|
|
272
|
-
<div key={blockKey++} className={styles.block}>
|
|
273
|
-
<Typography variant="body" as="p">
|
|
274
|
-
{preserveLineBreaks
|
|
275
|
-
? paraLines.map((paraLine, lineIdx) => (
|
|
276
|
-
<Fragment key={lineIdx}>
|
|
277
|
-
{lineIdx > 0 && <br />}
|
|
278
|
-
{parseInline(paraLine)}
|
|
279
|
-
</Fragment>
|
|
280
|
-
))
|
|
281
|
-
: parseInline(paraLines.join(' '))}
|
|
282
|
-
</Typography>
|
|
283
|
-
</div>,
|
|
284
|
-
);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return blocks;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/** Renders a constrained markdown subset (see MarkdownProps) as styled React
|
|
291
|
-
* elements built from Typography — no HTML string injection. Primarily
|
|
292
|
-
* meant for long-form, first-party content pages (privacy policy, terms of
|
|
293
|
-
* service) sourced from a single markdown file — not a general-purpose
|
|
294
|
-
* CommonMark renderer for arbitrary markdown. Also fine for plain,
|
|
295
|
-
* user-typed multi-line text (a card description, a comment body) edited
|
|
296
|
-
* in an ordinary `<textarea>` and passed through unchanged, as long as
|
|
297
|
-
* `preserveLineBreaks` is set — see that prop's own doc comment for why it
|
|
298
|
-
* isn't the default. */
|
|
299
|
-
export function Markdown({ content, preserveLineBreaks = false, className }: MarkdownProps) {
|
|
300
|
-
return (
|
|
301
|
-
<div className={[styles.root, className].filter(Boolean).join(' ')}>
|
|
302
|
-
{parseBlocks(content, preserveLineBreaks)}
|
|
303
|
-
</div>
|
|
304
|
-
);
|
|
305
|
-
}
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
3
|
-
import { cleanup, render } from '@testing-library/react';
|
|
4
|
-
import { Markdown } from '../Markdown';
|
|
5
|
-
|
|
6
|
-
afterEach(cleanup);
|
|
7
|
-
|
|
8
|
-
describe('Markdown', () => {
|
|
9
|
-
it('joins consecutive lines into one paragraph by default (soft-wrap)', () => {
|
|
10
|
-
const { container } = render(<Markdown content={'Line one.\nLine two.'} />);
|
|
11
|
-
const p = container.querySelector('p');
|
|
12
|
-
expect(p?.textContent).toBe('Line one. Line two.');
|
|
13
|
-
expect(p?.querySelector('br')).toBeNull();
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('preserveLineBreaks keeps each line on its own line via <br>', () => {
|
|
17
|
-
const { container } = render(
|
|
18
|
-
<Markdown content={'Line one.\nLine two.\nLine three.'} preserveLineBreaks />,
|
|
19
|
-
);
|
|
20
|
-
const p = container.querySelector('p');
|
|
21
|
-
expect(p?.querySelectorAll('br')).toHaveLength(2);
|
|
22
|
-
expect(p?.textContent).toBe('Line one.Line two.Line three.');
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('preserveLineBreaks still starts a new paragraph on a blank line', () => {
|
|
26
|
-
const { container } = render(
|
|
27
|
-
<Markdown content={'First paragraph.\n\nSecond paragraph.'} preserveLineBreaks />,
|
|
28
|
-
);
|
|
29
|
-
const paragraphs = container.querySelectorAll('p');
|
|
30
|
-
expect(paragraphs).toHaveLength(2);
|
|
31
|
-
expect(paragraphs[0]?.textContent).toBe('First paragraph.');
|
|
32
|
-
expect(paragraphs[1]?.textContent).toBe('Second paragraph.');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('preserveLineBreaks does not affect headings, quotes, or lists', () => {
|
|
36
|
-
const { container } = render(
|
|
37
|
-
<Markdown content={'# Heading\n\n- item one\n- item two'} preserveLineBreaks />,
|
|
38
|
-
);
|
|
39
|
-
expect(container.querySelector('h1')?.textContent).toBe('Heading');
|
|
40
|
-
expect(container.querySelectorAll('li')).toHaveLength(2);
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe('Markdown — ordered lists and fenced code', () => {
|
|
45
|
-
it('renders an ordered list, numbering from the browser', () => {
|
|
46
|
-
const { container } = render(<Markdown content={'1. first\n2. second\n3. third'} />);
|
|
47
|
-
|
|
48
|
-
const list = container.querySelector('ol');
|
|
49
|
-
expect(list).not.toBeNull();
|
|
50
|
-
expect([...(list?.querySelectorAll('li') ?? [])].map((li) => li.textContent)).toEqual([
|
|
51
|
-
'first',
|
|
52
|
-
'second',
|
|
53
|
-
'third',
|
|
54
|
-
]);
|
|
55
|
-
expect(list?.getAttribute('start')).toBeNull();
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('honours a list that starts at a number other than 1', () => {
|
|
59
|
-
const { container } = render(<Markdown content={'4. fourth\n5. fifth'} />);
|
|
60
|
-
expect(container.querySelector('ol')?.getAttribute('start')).toBe('4');
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('accepts the 1) marker style and applies inline formatting inside items', () => {
|
|
64
|
-
const { container } = render(<Markdown content={'1) do **this**'} />);
|
|
65
|
-
|
|
66
|
-
expect(container.querySelector('ol')).not.toBeNull();
|
|
67
|
-
expect(container.querySelector('ol strong')?.textContent).toBe('this');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('keeps ordered and unordered lists as separate blocks', () => {
|
|
71
|
-
const { container } = render(<Markdown content={'- bullet\n1. number'} />);
|
|
72
|
-
|
|
73
|
-
expect(container.querySelectorAll('ul')).toHaveLength(1);
|
|
74
|
-
expect(container.querySelectorAll('ol')).toHaveLength(1);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('renders a fenced code block verbatim, without parsing its contents', () => {
|
|
78
|
-
const content = ['```ts', 'const a = 1; // **not bold**', '', '# not a heading', '```'].join(
|
|
79
|
-
'\n',
|
|
80
|
-
);
|
|
81
|
-
const { container } = render(<Markdown content={content} />);
|
|
82
|
-
|
|
83
|
-
const code = container.querySelector('pre code');
|
|
84
|
-
expect(code?.textContent).toBe('const a = 1; // **not bold**\n\n# not a heading');
|
|
85
|
-
expect(container.querySelector('strong')).toBeNull();
|
|
86
|
-
expect(container.querySelector('h1')).toBeNull();
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('exposes the fence language for styling hooks', () => {
|
|
90
|
-
const { container } = render(<Markdown content={'```python\nx = 1\n```'} />);
|
|
91
|
-
expect(container.querySelector('pre code')?.getAttribute('data-language')).toBe('python');
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('terminates on an unclosed fence instead of looping', () => {
|
|
95
|
-
const { container } = render(<Markdown content={'text\n\n```\nnever closed'} />);
|
|
96
|
-
expect(container.querySelector('pre code')?.textContent).toBe('never closed');
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('separates a code block from the paragraph before it', () => {
|
|
100
|
-
const { container } = render(<Markdown content={'Try this:\n```\nrun me\n```'} />);
|
|
101
|
-
|
|
102
|
-
expect(container.querySelector('p')?.textContent).toBe('Try this:');
|
|
103
|
-
expect(container.querySelector('pre code')?.textContent).toBe('run me');
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
describe('Markdown — link hrefs', () => {
|
|
108
|
-
it('renders an http(s) link as a real anchor', () => {
|
|
109
|
-
const { container } = render(<Markdown content={'[docs](https://example.com/docs)'} />);
|
|
110
|
-
const link = container.querySelector('a');
|
|
111
|
-
expect(link?.getAttribute('href')).toBe('https://example.com/docs');
|
|
112
|
-
expect(link?.textContent).toBe('docs');
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('renders a relative link as a real anchor', () => {
|
|
116
|
-
const { container } = render(<Markdown content={'[here](/warden/models)'} />);
|
|
117
|
-
expect(container.querySelector('a')?.getAttribute('href')).toBe('/warden/models');
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('renders a mailto: link as a real anchor', () => {
|
|
121
|
-
const { container } = render(<Markdown content={'[email](mailto:a@example.com)'} />);
|
|
122
|
-
expect(container.querySelector('a')?.getAttribute('href')).toBe('mailto:a@example.com');
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it('neutralizes a javascript: URL instead of making it clickable', () => {
|
|
126
|
-
const { container } = render(
|
|
127
|
-
<Markdown content={'[click me](javascript:alert(document.cookie))'} />,
|
|
128
|
-
);
|
|
129
|
-
expect(container.querySelector('a')).toBeNull();
|
|
130
|
-
expect(container.textContent).toContain('click me');
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('neutralizes a javascript: URL disguised with an embedded tab', () => {
|
|
134
|
-
const { container } = render(<Markdown content={'[x](jav\tascript:alert(1))'} />);
|
|
135
|
-
expect(container.querySelector('a')).toBeNull();
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it('neutralizes a data: URL', () => {
|
|
139
|
-
const { container } = render(
|
|
140
|
-
<Markdown content={'[x](data:text/html,<script>alert(1)</script>)'} />,
|
|
141
|
-
);
|
|
142
|
-
expect(container.querySelector('a')).toBeNull();
|
|
143
|
-
});
|
|
144
|
-
});
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
.marker {
|
|
2
|
-
display: inline-flex;
|
|
3
|
-
align-items: center;
|
|
4
|
-
justify-content: center;
|
|
5
|
-
min-width: 16px;
|
|
6
|
-
height: 16px;
|
|
7
|
-
padding: 0 4px;
|
|
8
|
-
margin: 0 1px;
|
|
9
|
-
border: none;
|
|
10
|
-
border-radius: var(--sv-radius-full);
|
|
11
|
-
background: var(--sv-color-accent-subtle);
|
|
12
|
-
color: var(--sv-color-accent);
|
|
13
|
-
font-family: var(--sv-font-family);
|
|
14
|
-
font-size: var(--sv-font-size-label);
|
|
15
|
-
font-weight: var(--sv-font-weight-semibold);
|
|
16
|
-
line-height: 1;
|
|
17
|
-
vertical-align: super;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
button.marker {
|
|
21
|
-
cursor: pointer;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
button.marker:focus-visible {
|
|
25
|
-
outline: 2px solid var(--sv-color-focus-ring);
|
|
26
|
-
outline-offset: 1px;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
@media (hover: hover) {
|
|
30
|
-
button.marker:hover {
|
|
31
|
-
background: color-mix(in srgb, var(--sv-color-accent-subtle) 70%, var(--sv-color-accent) 30%);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
-
import { Marker } from './Marker';
|
|
3
|
-
|
|
4
|
-
const meta = {
|
|
5
|
-
title: 'Components/Marker',
|
|
6
|
-
component: Marker,
|
|
7
|
-
parameters: {
|
|
8
|
-
layout: 'padded',
|
|
9
|
-
docs: {
|
|
10
|
-
description: {
|
|
11
|
-
component:
|
|
12
|
-
'Inline citation/reference marker, for attributing part of an assistant answer to a source (Sovereign Harness "source trace").',
|
|
13
|
-
},
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
args: { index: 1, label: 'Source: Notes' },
|
|
17
|
-
} satisfies Meta<typeof Marker>;
|
|
18
|
-
|
|
19
|
-
export default meta;
|
|
20
|
-
type Story = StoryObj<typeof meta>;
|
|
21
|
-
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
|
|
24
|
-
export const Static: Story = {};
|
|
25
|
-
|
|
26
|
-
export const Clickable: Story = {
|
|
27
|
-
args: { onClick: () => {} },
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export const InContext: Story = {
|
|
31
|
-
render: () => (
|
|
32
|
-
<p style={{ fontSize: 14, maxWidth: 400 }}>
|
|
33
|
-
Your next task deadline is Thursday
|
|
34
|
-
<Marker index={1} label="Source: Tasks" onClick={() => {}} />, and you have two unread
|
|
35
|
-
messages from the team
|
|
36
|
-
<Marker index={2} label="Source: Notes" onClick={() => {}} />.
|
|
37
|
-
</p>
|
|
38
|
-
),
|
|
39
|
-
};
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import styles from './Marker.module.css';
|
|
2
|
-
|
|
3
|
-
export interface MarkerProps {
|
|
4
|
-
/** The number shown in the marker, e.g. 1, 2, 3. */
|
|
5
|
-
index: number;
|
|
6
|
-
/** Accessible label describing the citation, e.g. "Source: Notes". */
|
|
7
|
-
label: string;
|
|
8
|
-
/** Reveals source details (e.g. opens a source trace panel) when given. */
|
|
9
|
-
onClick?: () => void;
|
|
10
|
-
className?: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Marker — inline citation/reference marker, for attributing part of an
|
|
15
|
-
* assistant's answer to a source (matches RFC 0040's "source trace" —
|
|
16
|
-
* Harness shows which plugin/data source informed an answer).
|
|
17
|
-
*/
|
|
18
|
-
export function Marker({ index, label, onClick, className }: MarkerProps) {
|
|
19
|
-
const classes = [styles.marker, className].filter(Boolean).join(' ');
|
|
20
|
-
|
|
21
|
-
if (onClick) {
|
|
22
|
-
return (
|
|
23
|
-
<button type="button" onClick={onClick} aria-label={label} className={classes}>
|
|
24
|
-
{index}
|
|
25
|
-
</button>
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
<span aria-label={label} className={classes}>
|
|
31
|
-
{index}
|
|
32
|
-
</span>
|
|
33
|
-
);
|
|
34
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
-
import { cleanup, render, screen } from '@testing-library/react';
|
|
4
|
-
import { Marker } from '../Marker';
|
|
5
|
-
|
|
6
|
-
afterEach(cleanup);
|
|
7
|
-
|
|
8
|
-
describe('Marker', () => {
|
|
9
|
-
it('renders as a static span with an accessible label when no onClick is given', () => {
|
|
10
|
-
render(<Marker index={1} label="Source: Notes" />);
|
|
11
|
-
const el = screen.getByLabelText('Source: Notes');
|
|
12
|
-
expect(el.tagName).toBe('SPAN');
|
|
13
|
-
expect(el.textContent).toBe('1');
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('renders as a button and fires onClick when given', () => {
|
|
17
|
-
const onClick = vi.fn();
|
|
18
|
-
render(<Marker index={2} label="Source: Tasks" onClick={onClick} />);
|
|
19
|
-
const el = screen.getByRole('button', { name: 'Source: Tasks' });
|
|
20
|
-
el.click();
|
|
21
|
-
expect(onClick).toHaveBeenCalled();
|
|
22
|
-
});
|
|
23
|
-
});
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
.group {
|
|
2
|
-
display: grid;
|
|
3
|
-
gap: var(--sv-space-2);
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
.label {
|
|
7
|
-
font-size: var(--sv-font-size-xs);
|
|
8
|
-
font-weight: var(--sv-font-weight-semibold);
|
|
9
|
-
color: var(--sv-color-text-muted);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
.row {
|
|
13
|
-
display: flex;
|
|
14
|
-
align-items: center;
|
|
15
|
-
justify-content: space-between;
|
|
16
|
-
gap: var(--sv-space-2);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
.trailing {
|
|
20
|
-
flex-shrink: 0;
|
|
21
|
-
max-width: 8rem;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
.hint {
|
|
25
|
-
font-size: var(--sv-font-size-xs);
|
|
26
|
-
color: var(--sv-color-text-muted);
|
|
27
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
3
|
-
import { CurrencyInput } from '../CurrencyInput/CurrencyInput';
|
|
4
|
-
import { MemberMultiSelect, type MemberMultiSelectOption } from './MemberMultiSelect';
|
|
5
|
-
|
|
6
|
-
const meta = {
|
|
7
|
-
title: 'Components/MemberMultiSelect',
|
|
8
|
-
component: MemberMultiSelect,
|
|
9
|
-
parameters: {
|
|
10
|
-
layout: 'padded',
|
|
11
|
-
docs: {
|
|
12
|
-
description: {
|
|
13
|
-
component:
|
|
14
|
-
'Checkbox list for picking any number of people from an already-resolved set of options. Domain-agnostic about who an option represents — instance users and guest members are both just `{id, label}`, so nothing special is needed to include guests. Not a directory search or invite flow; pair with SuggestionInput for that.',
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
args: {
|
|
19
|
-
options: [],
|
|
20
|
-
selectedIds: new Set(),
|
|
21
|
-
onToggle: () => {},
|
|
22
|
-
},
|
|
23
|
-
} satisfies Meta<typeof MemberMultiSelect>;
|
|
24
|
-
|
|
25
|
-
export default meta;
|
|
26
|
-
type Story = StoryObj<typeof meta>;
|
|
27
|
-
|
|
28
|
-
const OPTIONS: MemberMultiSelectOption[] = [
|
|
29
|
-
{ id: '1', label: 'Priya' },
|
|
30
|
-
{ id: '2', label: 'Jamie (guest)' },
|
|
31
|
-
{ id: '3', label: 'Sam' },
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
function ControlledMultiSelect({ withTrailing = false }: { withTrailing?: boolean }) {
|
|
35
|
-
const [selected, setSelected] = useState<Set<string>>(new Set(['1', '3']));
|
|
36
|
-
const [amounts, setAmounts] = useState<Record<string, number | null>>({});
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<MemberMultiSelect
|
|
40
|
-
options={OPTIONS}
|
|
41
|
-
selectedIds={selected}
|
|
42
|
-
onToggle={(id, checked) => {
|
|
43
|
-
setSelected((prev) => {
|
|
44
|
-
const next = new Set(prev);
|
|
45
|
-
if (checked) next.add(id);
|
|
46
|
-
else next.delete(id);
|
|
47
|
-
return next;
|
|
48
|
-
});
|
|
49
|
-
}}
|
|
50
|
-
label="Split between"
|
|
51
|
-
hint={withTrailing ? 'Remaining: 0.00' : undefined}
|
|
52
|
-
renderTrailing={
|
|
53
|
-
withTrailing
|
|
54
|
-
? (id) => (
|
|
55
|
-
<CurrencyInput
|
|
56
|
-
valueCents={amounts[id] ?? null}
|
|
57
|
-
onValueChange={(cents) => setAmounts((prev) => ({ ...prev, [id]: cents }))}
|
|
58
|
-
placeholder="0.00"
|
|
59
|
-
aria-label={`Amount for ${OPTIONS.find((o) => o.id === id)?.label}`}
|
|
60
|
-
/>
|
|
61
|
-
)
|
|
62
|
-
: undefined
|
|
63
|
-
}
|
|
64
|
-
/>
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export const Default: Story = {
|
|
69
|
-
render: () => <ControlledMultiSelect />,
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
export const WithTrailingAmountInput: Story = {
|
|
73
|
-
render: () => <ControlledMultiSelect withTrailing />,
|
|
74
|
-
};
|