@eocrm/design-system 0.0.0-stage → 0.6.18
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/AI-PRIMER.md +341 -0
- package/LICENSE +202 -0
- package/README.md +266 -2
- package/docs/components/Accordion.md +114 -0
- package/docs/components/AgentAvatar.md +58 -0
- package/docs/components/Alert.md +81 -0
- package/docs/components/AppLayout.md +70 -0
- package/docs/components/Avatar.md +49 -0
- package/docs/components/AvatarGroup.md +47 -0
- package/docs/components/Badge.md +60 -0
- package/docs/components/Banner.md +89 -0
- package/docs/components/BrandIcon.md +32 -0
- package/docs/components/Breadcrumb.md +72 -0
- package/docs/components/Button.md +107 -0
- package/docs/components/ButtonGroup.md +78 -0
- package/docs/components/Calendar.md +158 -0
- package/docs/components/Card.md +165 -0
- package/docs/components/CatalogPicker.md +83 -0
- package/docs/components/Chart.md +114 -0
- package/docs/components/ChatBubble.md +55 -0
- package/docs/components/ChatLauncher.md +97 -0
- package/docs/components/Checkbox.md +55 -0
- package/docs/components/CircularProgress.md +53 -0
- package/docs/components/Cluster.md +96 -0
- package/docs/components/Code.md +35 -0
- package/docs/components/CodeBlock.md +67 -0
- package/docs/components/ColorPicker.md +115 -0
- package/docs/components/ConfirmationPopover.md +70 -0
- package/docs/components/Constrain.md +39 -0
- package/docs/components/CurrencySelect.md +57 -0
- package/docs/components/CursorPagination.md +43 -0
- package/docs/components/DashboardCanvas.md +79 -0
- package/docs/components/DashboardWidget.md +131 -0
- package/docs/components/DataTable.md +88 -0
- package/docs/components/DatePicker.md +68 -0
- package/docs/components/DateRangePicker.md +71 -0
- package/docs/components/DateStrip.md +56 -0
- package/docs/components/DefinitionList.md +82 -0
- package/docs/components/Divider.md +57 -0
- package/docs/components/Dot.md +26 -0
- package/docs/components/Drawer.md +94 -0
- package/docs/components/DropdownMenu.md +299 -0
- package/docs/components/EmojiPicker.md +84 -0
- package/docs/components/EmptyState.md +55 -0
- package/docs/components/EntityChip.md +102 -0
- package/docs/components/ErrorState.md +72 -0
- package/docs/components/Field.md +91 -0
- package/docs/components/FileUpload.md +109 -0
- package/docs/components/FilterChip.md +88 -0
- package/docs/components/FlowCanvas.md +108 -0
- package/docs/components/FormRow.md +31 -0
- package/docs/components/FormSection.md +37 -0
- package/docs/components/Grid.md +103 -0
- package/docs/components/Highlight.md +75 -0
- package/docs/components/HighlightedText.md +50 -0
- package/docs/components/IconPicker.md +134 -0
- package/docs/components/IconTile.md +30 -0
- package/docs/components/Image.md +84 -0
- package/docs/components/ImageCrop.md +163 -0
- package/docs/components/Indent.md +31 -0
- package/docs/components/InlineDatePicker.md +58 -0
- package/docs/components/InlineDateRangePicker.md +57 -0
- package/docs/components/Input.md +54 -0
- package/docs/components/Kanban.md +122 -0
- package/docs/components/Kbd.md +41 -0
- package/docs/components/Lightbox.md +105 -0
- package/docs/components/Link.md +65 -0
- package/docs/components/LinkCard.md +41 -0
- package/docs/components/LiquidEditor.md +84 -0
- package/docs/components/List.md +68 -0
- package/docs/components/LiveRegion.md +29 -0
- package/docs/components/LocaleProvider.md +25 -0
- package/docs/components/Logo.md +41 -0
- package/docs/components/Masonry.md +48 -0
- package/docs/components/MediaTile.md +95 -0
- package/docs/components/Modal.md +107 -0
- package/docs/components/MoneyInput.md +62 -0
- package/docs/components/OptionsPicker.md +123 -0
- package/docs/components/OtpInput.md +65 -0
- package/docs/components/Page.md +35 -0
- package/docs/components/PageHeader.md +133 -0
- package/docs/components/Pagination.md +44 -0
- package/docs/components/Palette.md +32 -0
- package/docs/components/PasswordInput.md +45 -0
- package/docs/components/PasswordStrengthMeter.md +27 -0
- package/docs/components/PersonDisplay.md +153 -0
- package/docs/components/PhoneInput.md +54 -0
- package/docs/components/PillMenu.md +91 -0
- package/docs/components/Popover.md +127 -0
- package/docs/components/Progress.md +56 -0
- package/docs/components/QrCode.md +34 -0
- package/docs/components/Radio.md +37 -0
- package/docs/components/RadioGroup.md +56 -0
- package/docs/components/Rail.md +191 -0
- package/docs/components/RelativeDateInput.md +114 -0
- package/docs/components/RichText.md +44 -0
- package/docs/components/RichTextEditor.md +112 -0
- package/docs/components/Screen.md +37 -0
- package/docs/components/ScrollArea.md +39 -0
- package/docs/components/Select.md +131 -0
- package/docs/components/SettingRow.md +98 -0
- package/docs/components/Skeleton.md +68 -0
- package/docs/components/Slider.md +92 -0
- package/docs/components/SlotGrid.md +55 -0
- package/docs/components/SocialButton.md +31 -0
- package/docs/components/Sortable.md +127 -0
- package/docs/components/SortableGroup.md +52 -0
- package/docs/components/Sparkline.md +51 -0
- package/docs/components/Split.md +76 -0
- package/docs/components/Stack.md +59 -0
- package/docs/components/StagePath.md +66 -0
- package/docs/components/StatGroup.md +61 -0
- package/docs/components/StatTile.md +58 -0
- package/docs/components/Sticky.md +69 -0
- package/docs/components/Switch.md +89 -0
- package/docs/components/Table.md +184 -0
- package/docs/components/TableOfContents.md +76 -0
- package/docs/components/Tabs.md +94 -0
- package/docs/components/Text.md +63 -0
- package/docs/components/Textarea.md +59 -0
- package/docs/components/Thread.md +52 -0
- package/docs/components/TimeField.md +70 -0
- package/docs/components/Timeline.md +57 -0
- package/docs/components/Title.md +54 -0
- package/docs/components/ToastViewport.md +61 -0
- package/docs/components/Tooltip.md +63 -0
- package/docs/components/TopBar.md +214 -0
- package/docs/components/Tour.md +85 -0
- package/docs/components/TreeView.md +113 -0
- package/docs/components/VisuallyHidden.md +43 -0
- package/docs/components/WidgetPreview.md +54 -0
- package/docs/components/useBelowBreakpoint.md +11 -0
- package/docs/components/useMonth.md +18 -0
- package/docs/setup.md +51 -0
- package/docs/theming.md +95 -0
- package/docs/tokens.md +123 -0
- package/docs/transient-state.md +56 -0
- package/package.json +86 -4
- package/src/_meta/manifest.ts +293 -0
- package/src/app/AppProvider.tsx +78 -0
- package/src/app/index.ts +3 -0
- package/src/app/themeTokens.ts +95 -0
- package/src/calendar/dateMath.ts +114 -0
- package/src/calendar/formatters.ts +75 -0
- package/src/calendar/index.ts +39 -0
- package/src/calendar/types.ts +36 -0
- package/src/calendar/useAgenda.ts +70 -0
- package/src/calendar/useDay.ts +51 -0
- package/src/calendar/useMonth.ts +82 -0
- package/src/calendar/useWeek.ts +62 -0
- package/src/calendar/weekInfo.ts +83 -0
- package/src/components/Accordion/Accordion.module.scss +304 -0
- package/src/components/Accordion/Accordion.tokens.scss +64 -0
- package/src/components/Accordion/Accordion.tsx +272 -0
- package/src/components/Accordion/AccordionContent.tsx +36 -0
- package/src/components/Accordion/AccordionItem.tsx +66 -0
- package/src/components/Accordion/AccordionTrigger.tsx +121 -0
- package/src/components/Accordion/context.ts +47 -0
- package/src/components/Accordion/index.ts +14 -0
- package/src/components/Alert/Alert.module.scss +92 -0
- package/src/components/Alert/Alert.tokens.scss +60 -0
- package/src/components/Alert/Alert.tsx +136 -0
- package/src/components/Alert/index.ts +2 -0
- package/src/components/AppLayout/AppLayout.module.scss +130 -0
- package/src/components/AppLayout/AppLayout.tokens.scss +27 -0
- package/src/components/AppLayout/AppLayout.tsx +211 -0
- package/src/components/AppLayout/index.ts +2 -0
- package/src/components/Avatar/AgentAvatar.module.scss +117 -0
- package/src/components/Avatar/AgentAvatar.tsx +122 -0
- package/src/components/Avatar/Avatar.module.scss +181 -0
- package/src/components/Avatar/Avatar.tokens.scss +107 -0
- package/src/components/Avatar/Avatar.tsx +210 -0
- package/src/components/Avatar/AvatarGroup.module.scss +128 -0
- package/src/components/Avatar/AvatarGroup.tsx +119 -0
- package/src/components/Avatar/AvatarGroupContext.tsx +20 -0
- package/src/components/Avatar/index.ts +6 -0
- package/src/components/Badge/Badge.module.scss +124 -0
- package/src/components/Badge/Badge.tokens.scss +74 -0
- package/src/components/Badge/Badge.tsx +164 -0
- package/src/components/Badge/index.ts +2 -0
- package/src/components/Banner/Banner.module.scss +73 -0
- package/src/components/Banner/Banner.tokens.scss +51 -0
- package/src/components/Banner/Banner.tsx +138 -0
- package/src/components/Banner/index.ts +2 -0
- package/src/components/BrandIcon/BrandIcon.module.scss +5 -0
- package/src/components/BrandIcon/BrandIcon.tsx +99 -0
- package/src/components/BrandIcon/index.ts +2 -0
- package/src/components/Breadcrumb/Breadcrumb.module.scss +32 -0
- package/src/components/Breadcrumb/Breadcrumb.tokens.scss +9 -0
- package/src/components/Breadcrumb/Breadcrumb.tsx +146 -0
- package/src/components/Breadcrumb/index.ts +2 -0
- package/src/components/Button/Button.module.scss +167 -0
- package/src/components/Button/Button.tokens.scss +90 -0
- package/src/components/Button/Button.tsx +205 -0
- package/src/components/Button/index.ts +2 -0
- package/src/components/ButtonGroup/ButtonGroup.module.scss +134 -0
- package/src/components/ButtonGroup/ButtonGroup.tokens.scss +42 -0
- package/src/components/ButtonGroup/ButtonGroup.tsx +278 -0
- package/src/components/ButtonGroup/ButtonGroupItem.tsx +74 -0
- package/src/components/ButtonGroup/context.ts +45 -0
- package/src/components/ButtonGroup/index.ts +2 -0
- package/src/components/Calendar/AgendaView.module.scss +231 -0
- package/src/components/Calendar/AgendaView.tsx +214 -0
- package/src/components/Calendar/AllDayBand.module.scss +18 -0
- package/src/components/Calendar/AllDayBand.tsx +75 -0
- package/src/components/Calendar/Calendar.module.scss +23 -0
- package/src/components/Calendar/Calendar.tokens.scss +262 -0
- package/src/components/Calendar/Calendar.tsx +370 -0
- package/src/components/Calendar/DayCell.module.scss +30 -0
- package/src/components/Calendar/DayCell.tsx +50 -0
- package/src/components/Calendar/DayView.module.scss +10 -0
- package/src/components/Calendar/DayView.tsx +163 -0
- package/src/components/Calendar/EventChip.module.scss +171 -0
- package/src/components/Calendar/EventChip.tsx +115 -0
- package/src/components/Calendar/HourGrid.module.scss +123 -0
- package/src/components/Calendar/HourGrid.tsx +381 -0
- package/src/components/Calendar/MonthView.module.scss +99 -0
- package/src/components/Calendar/MonthView.tsx +296 -0
- package/src/components/Calendar/TimedEvent.module.scss +205 -0
- package/src/components/Calendar/TimedEvent.tsx +247 -0
- package/src/components/Calendar/ViewSwitcher.module.scss +3 -0
- package/src/components/Calendar/ViewSwitcher.tsx +47 -0
- package/src/components/Calendar/WeekView.module.scss +10 -0
- package/src/components/Calendar/WeekView.tsx +137 -0
- package/src/components/Calendar/eventColor.ts +60 -0
- package/src/components/Calendar/index.ts +18 -0
- package/src/components/Calendar/types.ts +451 -0
- package/src/components/Calendar/useEventDrag.ts +783 -0
- package/src/components/Calendar/utils.ts +622 -0
- package/src/components/Card/Card.module.scss +264 -0
- package/src/components/Card/Card.tokens.scss +55 -0
- package/src/components/Card/Card.tsx +170 -0
- package/src/components/Card/CardBody.tsx +45 -0
- package/src/components/Card/CardHeader.tsx +113 -0
- package/src/components/Card/CardList.tsx +24 -0
- package/src/components/Card/CardListRow.tsx +24 -0
- package/src/components/Card/index.ts +7 -0
- package/src/components/CatalogPicker/CatalogPicker.module.scss +193 -0
- package/src/components/CatalogPicker/CatalogPicker.tokens.scss +26 -0
- package/src/components/CatalogPicker/CatalogPicker.tsx +339 -0
- package/src/components/CatalogPicker/index.ts +2 -0
- package/src/components/Chart/Chart.module.scss +287 -0
- package/src/components/Chart/Chart.tokens.scss +63 -0
- package/src/components/Chart/Chart.tsx +607 -0
- package/src/components/Chart/ChartLegend.tsx +99 -0
- package/src/components/Chart/ChartTable.tsx +62 -0
- package/src/components/Chart/ChartTooltip.tsx +91 -0
- package/src/components/Chart/chartModel.ts +689 -0
- package/src/components/Chart/index.ts +2 -0
- package/src/components/ChatBubble/ChatBubble.module.scss +93 -0
- package/src/components/ChatBubble/ChatBubble.tokens.scss +17 -0
- package/src/components/ChatBubble/ChatBubble.tsx +61 -0
- package/src/components/ChatBubble/ChatBubbleList.tsx +27 -0
- package/src/components/ChatBubble/index.ts +3 -0
- package/src/components/ChatLauncher/ChatLauncher.module.scss +68 -0
- package/src/components/ChatLauncher/ChatLauncher.tokens.scss +27 -0
- package/src/components/ChatLauncher/ChatLauncher.tsx +202 -0
- package/src/components/ChatLauncher/index.ts +2 -0
- package/src/components/ChatLauncher/isAtBottom.ts +12 -0
- package/src/components/Checkbox/Checkbox.module.scss +147 -0
- package/src/components/Checkbox/Checkbox.tokens.scss +44 -0
- package/src/components/Checkbox/Checkbox.tsx +188 -0
- package/src/components/Checkbox/index.ts +2 -0
- package/src/components/CircularProgress/CircularProgress.module.scss +133 -0
- package/src/components/CircularProgress/CircularProgress.tokens.scss +40 -0
- package/src/components/CircularProgress/CircularProgress.tsx +139 -0
- package/src/components/CircularProgress/index.ts +7 -0
- package/src/components/Cluster/Cluster.module.scss +145 -0
- package/src/components/Cluster/Cluster.tokens.scss +11 -0
- package/src/components/Cluster/Cluster.tsx +215 -0
- package/src/components/Cluster/index.ts +2 -0
- package/src/components/Code/Code.module.scss +28 -0
- package/src/components/Code/Code.tokens.scss +15 -0
- package/src/components/Code/Code.tsx +44 -0
- package/src/components/Code/index.ts +2 -0
- package/src/components/CodeBlock/CodeBlock.module.scss +146 -0
- package/src/components/CodeBlock/CodeBlock.tokens.scss +27 -0
- package/src/components/CodeBlock/CodeBlock.tsx +274 -0
- package/src/components/CodeBlock/highlight.ts +179 -0
- package/src/components/CodeBlock/index.ts +8 -0
- package/src/components/CodeBlock/sampleSync.ts +42 -0
- package/src/components/ColorPicker/ColorPicker.module.scss +286 -0
- package/src/components/ColorPicker/ColorPicker.tokens.scss +60 -0
- package/src/components/ColorPicker/ColorPicker.tsx +367 -0
- package/src/components/ColorPicker/ColorPickerPanel.tsx +265 -0
- package/src/components/ColorPicker/SVSquare.tsx +189 -0
- package/src/components/ColorPicker/colorMath.ts +141 -0
- package/src/components/ColorPicker/index.ts +6 -0
- package/src/components/ConfirmationPopover/ConfirmationPopover.module.scss +39 -0
- package/src/components/ConfirmationPopover/ConfirmationPopover.tokens.scss +13 -0
- package/src/components/ConfirmationPopover/ConfirmationPopover.tsx +395 -0
- package/src/components/ConfirmationPopover/index.ts +2 -0
- package/src/components/Constrain/Constrain.module.scss +99 -0
- package/src/components/Constrain/Constrain.tsx +79 -0
- package/src/components/Constrain/index.ts +2 -0
- package/src/components/CurrencySelect/CurrencySelect.module.scss +7 -0
- package/src/components/CurrencySelect/CurrencySelect.tsx +167 -0
- package/src/components/CurrencySelect/index.ts +3 -0
- package/src/components/CursorPagination/CursorPagination.module.scss +69 -0
- package/src/components/CursorPagination/CursorPagination.tokens.scss +32 -0
- package/src/components/CursorPagination/CursorPagination.tsx +121 -0
- package/src/components/CursorPagination/index.ts +2 -0
- package/src/components/DashboardCanvas/DashboardCanvas.module.scss +360 -0
- package/src/components/DashboardCanvas/DashboardCanvas.tokens.scss +48 -0
- package/src/components/DashboardCanvas/DashboardCanvas.tsx +1270 -0
- package/src/components/DashboardCanvas/DashboardCanvasItem.tsx +140 -0
- package/src/components/DashboardCanvas/DashboardCanvasSection.tsx +174 -0
- package/src/components/DashboardCanvas/engine.ts +363 -0
- package/src/components/DashboardCanvas/index.ts +9 -0
- package/src/components/DashboardWidget/DashboardWidget.module.scss +100 -0
- package/src/components/DashboardWidget/DashboardWidget.tokens.scss +16 -0
- package/src/components/DashboardWidget/DashboardWidget.tsx +188 -0
- package/src/components/DashboardWidget/index.ts +7 -0
- package/src/components/DataTable/BodyRow.tsx +197 -0
- package/src/components/DataTable/ColumnVisibilityTrigger.module.scss +7 -0
- package/src/components/DataTable/ColumnVisibilityTrigger.tsx +105 -0
- package/src/components/DataTable/DataTable.module.scss +511 -0
- package/src/components/DataTable/DataTable.tokens.scss +80 -0
- package/src/components/DataTable/DataTable.tsx +734 -0
- package/src/components/DataTable/HeaderCell.module.scss +215 -0
- package/src/components/DataTable/HeaderCell.tsx +440 -0
- package/src/components/DataTable/columnShift.ts +117 -0
- package/src/components/DataTable/index.ts +23 -0
- package/src/components/DataTable/pinStyle.ts +47 -0
- package/src/components/DataTable/reorderColumns.ts +66 -0
- package/src/components/DataTable/types.ts +289 -0
- package/src/components/DataTable/useColumnDragShift.ts +216 -0
- package/src/components/DataTable/useDataTable.ts +372 -0
- package/src/components/DataTable/useResizeHandle.ts +97 -0
- package/src/components/DatePicker/DatePicker.module.scss +180 -0
- package/src/components/DatePicker/DatePicker.tokens.scss +152 -0
- package/src/components/DatePicker/DatePicker.tsx +664 -0
- package/src/components/DatePicker/DatePickerGrid.module.scss +172 -0
- package/src/components/DatePicker/DatePickerGrid.tsx +341 -0
- package/src/components/DatePicker/InlineDatePicker.module.scss +32 -0
- package/src/components/DatePicker/InlineDatePicker.tsx +201 -0
- package/src/components/DatePicker/index.ts +4 -0
- package/src/components/DatePicker/utils.ts +364 -0
- package/src/components/DateRangePicker/DateRangePicker.module.scss +237 -0
- package/src/components/DateRangePicker/DateRangePicker.tokens.scss +78 -0
- package/src/components/DateRangePicker/DateRangePicker.tsx +720 -0
- package/src/components/DateRangePicker/InlineDateRangePicker.module.scss +94 -0
- package/src/components/DateRangePicker/InlineDateRangePicker.tsx +376 -0
- package/src/components/DateRangePicker/index.ts +5 -0
- package/src/components/DateRangePicker/utils.ts +151 -0
- package/src/components/DateStrip/DateStrip.module.scss +78 -0
- package/src/components/DateStrip/DateStrip.tokens.scss +23 -0
- package/src/components/DateStrip/DateStrip.tsx +205 -0
- package/src/components/DateStrip/index.ts +2 -0
- package/src/components/DefinitionList/DefinitionList.module.scss +102 -0
- package/src/components/DefinitionList/DefinitionList.tokens.scss +29 -0
- package/src/components/DefinitionList/DefinitionList.tsx +198 -0
- package/src/components/DefinitionList/index.ts +9 -0
- package/src/components/Divider/Divider.module.scss +144 -0
- package/src/components/Divider/Divider.tokens.scss +21 -0
- package/src/components/Divider/Divider.tsx +103 -0
- package/src/components/Divider/index.ts +2 -0
- package/src/components/Dot/Dot.module.scss +40 -0
- package/src/components/Dot/Dot.tokens.scss +4 -0
- package/src/components/Dot/Dot.tsx +45 -0
- package/src/components/Dot/index.ts +2 -0
- package/src/components/Drawer/Body.tsx +23 -0
- package/src/components/Drawer/Close.tsx +35 -0
- package/src/components/Drawer/Content.tsx +93 -0
- package/src/components/Drawer/Drawer.module.scss +232 -0
- package/src/components/Drawer/Drawer.tokens.scss +38 -0
- package/src/components/Drawer/Drawer.tsx +23 -0
- package/src/components/Drawer/DrawerRoot.tsx +190 -0
- package/src/components/Drawer/Footer.tsx +24 -0
- package/src/components/Drawer/Header.tsx +64 -0
- package/src/components/Drawer/Overlay.tsx +82 -0
- package/src/components/Drawer/context.ts +44 -0
- package/src/components/Drawer/index.ts +12 -0
- package/src/components/Drawer/useDragToClose.ts +149 -0
- package/src/components/DropdownMenu/CheckboxItem.tsx +144 -0
- package/src/components/DropdownMenu/Content.tsx +419 -0
- package/src/components/DropdownMenu/DropdownMenu.module.scss +280 -0
- package/src/components/DropdownMenu/DropdownMenu.tokens.scss +88 -0
- package/src/components/DropdownMenu/DropdownMenu.tsx +43 -0
- package/src/components/DropdownMenu/Group.tsx +76 -0
- package/src/components/DropdownMenu/Item.tsx +148 -0
- package/src/components/DropdownMenu/ItemIndicator.tsx +30 -0
- package/src/components/DropdownMenu/Radio.tsx +178 -0
- package/src/components/DropdownMenu/Root.tsx +100 -0
- package/src/components/DropdownMenu/Sub.tsx +332 -0
- package/src/components/DropdownMenu/Trigger.tsx +76 -0
- package/src/components/DropdownMenu/context.ts +65 -0
- package/src/components/DropdownMenu/index.ts +20 -0
- package/src/components/EmojiPicker/EmojiPicker.module.scss +84 -0
- package/src/components/EmojiPicker/EmojiPicker.tokens.scss +24 -0
- package/src/components/EmojiPicker/EmojiPicker.tsx +341 -0
- package/src/components/EmojiPicker/emojiData.ts +245 -0
- package/src/components/EmojiPicker/index.ts +3 -0
- package/src/components/EmptyState/EmptyState.module.scss +102 -0
- package/src/components/EmptyState/EmptyState.tokens.scss +28 -0
- package/src/components/EmptyState/EmptyState.tsx +112 -0
- package/src/components/EmptyState/index.ts +7 -0
- package/src/components/EntityChip/EntityChip.module.scss +275 -0
- package/src/components/EntityChip/EntityChip.tokens.scss +66 -0
- package/src/components/EntityChip/EntityChip.tsx +649 -0
- package/src/components/EntityChip/index.ts +10 -0
- package/src/components/ErrorState/ErrorState.module.scss +105 -0
- package/src/components/ErrorState/ErrorState.tokens.scss +32 -0
- package/src/components/ErrorState/ErrorState.tsx +125 -0
- package/src/components/ErrorState/index.ts +8 -0
- package/src/components/Field/Field.module.scss +46 -0
- package/src/components/Field/Field.tsx +185 -0
- package/src/components/Field/index.ts +2 -0
- package/src/components/FileUpload/FileUpload.module.scss +187 -0
- package/src/components/FileUpload/FileUpload.tokens.scss +81 -0
- package/src/components/FileUpload/FileUpload.tsx +569 -0
- package/src/components/FileUpload/formatBytes.ts +34 -0
- package/src/components/FileUpload/iconForFile.ts +40 -0
- package/src/components/FileUpload/index.ts +2 -0
- package/src/components/FilterChip/FilterChip.module.scss +61 -0
- package/src/components/FilterChip/FilterChip.tokens.scss +25 -0
- package/src/components/FilterChip/FilterChip.tsx +208 -0
- package/src/components/FilterChip/index.ts +5 -0
- package/src/components/FlowCanvas/FlowCanvas.module.scss +329 -0
- package/src/components/FlowCanvas/FlowCanvas.tokens.scss +45 -0
- package/src/components/FlowCanvas/FlowCanvas.tsx +1867 -0
- package/src/components/FlowCanvas/FlowControls.tsx +46 -0
- package/src/components/FlowCanvas/FlowEdge.tsx +58 -0
- package/src/components/FlowCanvas/FlowNode.tsx +76 -0
- package/src/components/FlowCanvas/edgePath.ts +85 -0
- package/src/components/FlowCanvas/index.ts +4 -0
- package/src/components/FlowCanvas/layout.ts +148 -0
- package/src/components/FlowCanvas/spatialNav.ts +76 -0
- package/src/components/FlowCanvas/types.ts +59 -0
- package/src/components/FlowCanvas/useViewport.ts +122 -0
- package/src/components/FormRow/FormRow.module.scss +2 -0
- package/src/components/FormRow/FormRow.tsx +37 -0
- package/src/components/FormRow/index.ts +2 -0
- package/src/components/FormSection/FormSection.module.scss +24 -0
- package/src/components/FormSection/FormSection.tsx +46 -0
- package/src/components/FormSection/index.ts +2 -0
- package/src/components/Grid/Grid.module.scss +162 -0
- package/src/components/Grid/Grid.tokens.scss +13 -0
- package/src/components/Grid/Grid.tsx +226 -0
- package/src/components/Grid/GridItem.tsx +77 -0
- package/src/components/Grid/index.ts +11 -0
- package/src/components/Highlight/Highlight.module.scss +43 -0
- package/src/components/Highlight/Highlight.tokens.scss +35 -0
- package/src/components/Highlight/Highlight.tsx +164 -0
- package/src/components/Highlight/HighlightOverlay.tsx +100 -0
- package/src/components/Highlight/index.ts +2 -0
- package/src/components/HighlightedText/HighlightedText.module.scss +11 -0
- package/src/components/HighlightedText/HighlightedText.tokens.scss +9 -0
- package/src/components/HighlightedText/HighlightedText.tsx +50 -0
- package/src/components/HighlightedText/highlightRanges.ts +44 -0
- package/src/components/HighlightedText/index.ts +2 -0
- package/src/components/IconPicker/IconPicker.module.scss +175 -0
- package/src/components/IconPicker/IconPicker.tokens.scss +40 -0
- package/src/components/IconPicker/IconPicker.tsx +629 -0
- package/src/components/IconPicker/index.ts +9 -0
- package/src/components/IconTile/IconTile.module.scss +57 -0
- package/src/components/IconTile/IconTile.tokens.scss +16 -0
- package/src/components/IconTile/IconTile.tsx +91 -0
- package/src/components/IconTile/index.ts +2 -0
- package/src/components/Image/Image.module.scss +266 -0
- package/src/components/Image/Image.tokens.scss +55 -0
- package/src/components/Image/Image.tsx +342 -0
- package/src/components/Image/index.ts +2 -0
- package/src/components/ImageCrop/ImageCrop.module.scss +274 -0
- package/src/components/ImageCrop/ImageCrop.tokens.scss +33 -0
- package/src/components/ImageCrop/ImageCrop.tsx +542 -0
- package/src/components/ImageCrop/cropGeometry.ts +120 -0
- package/src/components/ImageCrop/extractCropBlob.ts +140 -0
- package/src/components/ImageCrop/index.ts +11 -0
- package/src/components/ImageCrop/useCropPreview.ts +161 -0
- package/src/components/Indent/Indent.module.scss +33 -0
- package/src/components/Indent/Indent.tokens.scss +10 -0
- package/src/components/Indent/Indent.tsx +46 -0
- package/src/components/Indent/index.ts +2 -0
- package/src/components/Input/Input.module.scss +99 -0
- package/src/components/Input/Input.tokens.scss +54 -0
- package/src/components/Input/Input.tsx +105 -0
- package/src/components/Input/Suggestions.tsx +260 -0
- package/src/components/Input/index.ts +2 -0
- package/src/components/Kanban/Kanban.module.scss +39 -0
- package/src/components/Kanban/Kanban.tokens.scss +15 -0
- package/src/components/Kanban/Kanban.tsx +951 -0
- package/src/components/Kanban/index.ts +7 -0
- package/src/components/Kbd/Kbd.module.scss +47 -0
- package/src/components/Kbd/Kbd.tokens.scss +26 -0
- package/src/components/Kbd/Kbd.tsx +73 -0
- package/src/components/Kbd/index.ts +2 -0
- package/src/components/Lightbox/Lightbox.module.scss +283 -0
- package/src/components/Lightbox/Lightbox.tokens.scss +36 -0
- package/src/components/Lightbox/Lightbox.tsx +506 -0
- package/src/components/Lightbox/index.ts +2 -0
- package/src/components/Link/Link.module.scss +78 -0
- package/src/components/Link/Link.tokens.scss +28 -0
- package/src/components/Link/Link.tsx +114 -0
- package/src/components/Link/index.ts +2 -0
- package/src/components/LinkCard/LinkCard.module.scss +92 -0
- package/src/components/LinkCard/LinkCard.tokens.scss +9 -0
- package/src/components/LinkCard/LinkCard.tsx +72 -0
- package/src/components/LinkCard/index.ts +2 -0
- package/src/components/LiquidEditor/AutocompleteMenu.tsx +133 -0
- package/src/components/LiquidEditor/HighlightLayer.tsx +53 -0
- package/src/components/LiquidEditor/InsertVariableMenu.tsx +81 -0
- package/src/components/LiquidEditor/LiquidEditor.module.scss +339 -0
- package/src/components/LiquidEditor/LiquidEditor.tokens.scss +81 -0
- package/src/components/LiquidEditor/LiquidEditor.tsx +412 -0
- package/src/components/LiquidEditor/PreviewPane.tsx +29 -0
- package/src/components/LiquidEditor/caretCoordinates.ts +97 -0
- package/src/components/LiquidEditor/index.ts +7 -0
- package/src/components/LiquidEditor/liquidFilters.ts +23 -0
- package/src/components/LiquidEditor/liquidTokenizer.ts +235 -0
- package/src/components/LiquidEditor/types.ts +101 -0
- package/src/components/LiquidEditor/useLiquidAutocomplete.ts +178 -0
- package/src/components/List/List.module.scss +105 -0
- package/src/components/List/List.tokens.scss +42 -0
- package/src/components/List/List.tsx +124 -0
- package/src/components/List/index.ts +9 -0
- package/src/components/LiveRegion/LiveRegion.module.scss +3 -0
- package/src/components/LiveRegion/LiveRegion.tsx +116 -0
- package/src/components/LiveRegion/index.ts +2 -0
- package/src/components/Logo/Logo.module.scss +139 -0
- package/src/components/Logo/Logo.tokens.scss +14 -0
- package/src/components/Logo/Logo.tsx +126 -0
- package/src/components/Logo/index.ts +2 -0
- package/src/components/Masonry/Masonry.module.scss +42 -0
- package/src/components/Masonry/Masonry.tokens.scss +8 -0
- package/src/components/Masonry/Masonry.tsx +136 -0
- package/src/components/Masonry/index.ts +2 -0
- package/src/components/Masonry/masonryUtils.ts +42 -0
- package/src/components/MediaTile/MediaTile.module.scss +183 -0
- package/src/components/MediaTile/MediaTile.tokens.scss +60 -0
- package/src/components/MediaTile/MediaTile.tsx +208 -0
- package/src/components/MediaTile/index.ts +8 -0
- package/src/components/Modal/Body.tsx +27 -0
- package/src/components/Modal/Close.tsx +45 -0
- package/src/components/Modal/Content.tsx +98 -0
- package/src/components/Modal/Footer.tsx +28 -0
- package/src/components/Modal/Header.tsx +51 -0
- package/src/components/Modal/Modal.module.scss +262 -0
- package/src/components/Modal/Modal.tokens.scss +50 -0
- package/src/components/Modal/Modal.tsx +23 -0
- package/src/components/Modal/ModalRoot.tsx +299 -0
- package/src/components/Modal/Overlay.tsx +101 -0
- package/src/components/Modal/context.ts +60 -0
- package/src/components/Modal/index.ts +11 -0
- package/src/components/MoneyInput/MoneyInput.module.scss +57 -0
- package/src/components/MoneyInput/MoneyInput.tsx +433 -0
- package/src/components/MoneyInput/index.ts +2 -0
- package/src/components/OptionsPicker/OptionsPicker.module.scss +216 -0
- package/src/components/OptionsPicker/OptionsPicker.tokens.scss +68 -0
- package/src/components/OptionsPicker/OptionsPicker.tsx +936 -0
- package/src/components/OptionsPicker/index.ts +9 -0
- package/src/components/OtpInput/OtpInput.module.scss +69 -0
- package/src/components/OtpInput/OtpInput.tokens.scss +45 -0
- package/src/components/OtpInput/OtpInput.tsx +368 -0
- package/src/components/OtpInput/index.ts +2 -0
- package/src/components/Page/Page.module.scss +30 -0
- package/src/components/Page/Page.tokens.scss +11 -0
- package/src/components/Page/Page.tsx +53 -0
- package/src/components/Page/index.ts +2 -0
- package/src/components/PageHeader/PageHeader.module.scss +197 -0
- package/src/components/PageHeader/PageHeader.tokens.scss +41 -0
- package/src/components/PageHeader/PageHeader.tsx +310 -0
- package/src/components/PageHeader/index.ts +20 -0
- package/src/components/Pagination/Pagination.module.scss +117 -0
- package/src/components/Pagination/Pagination.tokens.scss +54 -0
- package/src/components/Pagination/Pagination.tsx +165 -0
- package/src/components/Pagination/index.ts +4 -0
- package/src/components/Pagination/paginationRange.ts +72 -0
- package/src/components/PasswordInput/PasswordInput.module.scss +120 -0
- package/src/components/PasswordInput/PasswordInput.tokens.scss +44 -0
- package/src/components/PasswordInput/PasswordInput.tsx +210 -0
- package/src/components/PasswordInput/index.ts +2 -0
- package/src/components/PasswordStrengthMeter/PasswordStrengthMeter.module.scss +80 -0
- package/src/components/PasswordStrengthMeter/PasswordStrengthMeter.tokens.scss +40 -0
- package/src/components/PasswordStrengthMeter/PasswordStrengthMeter.tsx +96 -0
- package/src/components/PasswordStrengthMeter/index.ts +2 -0
- package/src/components/PersonDisplay/PersonDisplay.module.scss +180 -0
- package/src/components/PersonDisplay/PersonDisplay.tokens.scss +35 -0
- package/src/components/PersonDisplay/PersonDisplay.tsx +420 -0
- package/src/components/PersonDisplay/index.ts +10 -0
- package/src/components/PhoneInput/PhoneInput.module.scss +23 -0
- package/src/components/PhoneInput/PhoneInput.tsx +271 -0
- package/src/components/PhoneInput/index.ts +4 -0
- package/src/components/PhoneInput/phone.ts +128 -0
- package/src/components/PillMenu/PillMenu.module.scss +117 -0
- package/src/components/PillMenu/PillMenu.tokens.scss +22 -0
- package/src/components/PillMenu/PillMenu.tsx +324 -0
- package/src/components/PillMenu/index.ts +2 -0
- package/src/components/Popover/Anchor.tsx +37 -0
- package/src/components/Popover/Close.tsx +45 -0
- package/src/components/Popover/Content.tsx +213 -0
- package/src/components/Popover/Heading.tsx +33 -0
- package/src/components/Popover/Popover.module.scss +177 -0
- package/src/components/Popover/Popover.tokens.scss +22 -0
- package/src/components/Popover/Popover.tsx +23 -0
- package/src/components/Popover/PopoverRoot.tsx +75 -0
- package/src/components/Popover/Trigger.tsx +86 -0
- package/src/components/Popover/context.ts +22 -0
- package/src/components/Popover/index.ts +7 -0
- package/src/components/Progress/Progress.module.scss +110 -0
- package/src/components/Progress/Progress.tokens.scss +44 -0
- package/src/components/Progress/Progress.tsx +129 -0
- package/src/components/Progress/index.ts +2 -0
- package/src/components/QrCode/QrCode.module.scss +97 -0
- package/src/components/QrCode/QrCode.tokens.scss +17 -0
- package/src/components/QrCode/QrCode.tsx +233 -0
- package/src/components/QrCode/index.ts +2 -0
- package/src/components/QrCode/qr.ts +161 -0
- package/src/components/Radio/Radio.module.scss +175 -0
- package/src/components/Radio/Radio.tokens.scss +59 -0
- package/src/components/Radio/Radio.tsx +127 -0
- package/src/components/Radio/RadioGroup.module.scss +42 -0
- package/src/components/Radio/RadioGroup.tsx +120 -0
- package/src/components/Radio/RadioGroupContext.tsx +30 -0
- package/src/components/Radio/index.ts +4 -0
- package/src/components/Rail/Rail.module.scss +510 -0
- package/src/components/Rail/Rail.tokens.scss +92 -0
- package/src/components/Rail/Rail.tsx +287 -0
- package/src/components/Rail/RailCollapseToggle.tsx +57 -0
- package/src/components/Rail/RailFooter.tsx +17 -0
- package/src/components/Rail/RailGroup.tsx +728 -0
- package/src/components/Rail/RailGroupContext.ts +9 -0
- package/src/components/Rail/RailHeader.tsx +23 -0
- package/src/components/Rail/RailItem.tsx +119 -0
- package/src/components/Rail/RailSection.tsx +39 -0
- package/src/components/Rail/RailSpacer.tsx +19 -0
- package/src/components/Rail/index.ts +9 -0
- package/src/components/RelativeDateInput/RelativeDateInput.module.scss +15 -0
- package/src/components/RelativeDateInput/RelativeDateInput.tokens.scss +6 -0
- package/src/components/RelativeDateInput/RelativeDateInput.tsx +474 -0
- package/src/components/RelativeDateInput/index.ts +10 -0
- package/src/components/RelativeDateInput/relativeDate.ts +127 -0
- package/src/components/RichText/RichText.module.scss +5 -0
- package/src/components/RichText/RichText.tsx +70 -0
- package/src/components/RichText/RichTextAttachment.module.scss +82 -0
- package/src/components/RichText/RichTextAttachment.tsx +122 -0
- package/src/components/RichText/_prose.scss +79 -0
- package/src/components/RichText/attachmentIcon.tsx +24 -0
- package/src/components/RichText/engine/attachment.ts +154 -0
- package/src/components/RichText/engine/autolink.ts +64 -0
- package/src/components/RichText/engine/blockUnit.ts +174 -0
- package/src/components/RichText/engine/colorMarks.ts +85 -0
- package/src/components/RichText/engine/escape.ts +11 -0
- package/src/components/RichText/engine/fromHtml.ts +353 -0
- package/src/components/RichText/engine/fromMarkdown.ts +20 -0
- package/src/components/RichText/engine/inlines.ts +82 -0
- package/src/components/RichText/engine/listDepths.ts +32 -0
- package/src/components/RichText/engine/marks.ts +80 -0
- package/src/components/RichText/engine/mdToHtml.ts +178 -0
- package/src/components/RichText/engine/model.ts +157 -0
- package/src/components/RichText/engine/position.ts +118 -0
- package/src/components/RichText/engine/renderDoc.tsx +395 -0
- package/src/components/RichText/engine/renderLink.ts +15 -0
- package/src/components/RichText/engine/renderMention.ts +16 -0
- package/src/components/RichText/engine/safeHref.ts +22 -0
- package/src/components/RichText/engine/toHtml.ts +154 -0
- package/src/components/RichText/engine/toMarkdown.ts +97 -0
- package/src/components/RichText/engine/transforms.ts +434 -0
- package/src/components/RichText/imageMaxHeightStyle.ts +10 -0
- package/src/components/RichText/index.ts +31 -0
- package/src/components/RichTextEditor/RichTextAttachmentConfig.tsx +264 -0
- package/src/components/RichTextEditor/RichTextBlockControls.tsx +365 -0
- package/src/components/RichTextEditor/RichTextBlockMenu.tsx +162 -0
- package/src/components/RichTextEditor/RichTextColorMenu.tsx +115 -0
- package/src/components/RichTextEditor/RichTextEditor.module.scss +298 -0
- package/src/components/RichTextEditor/RichTextEditor.tsx +1910 -0
- package/src/components/RichTextEditor/RichTextImageResizer.tsx +131 -0
- package/src/components/RichTextEditor/RichTextLinkEditor.tsx +151 -0
- package/src/components/RichTextEditor/RichTextMentionMenu.tsx +99 -0
- package/src/components/RichTextEditor/RichTextToolbar.tsx +379 -0
- package/src/components/RichTextEditor/autolinkInput.ts +65 -0
- package/src/components/RichTextEditor/blockDrop.ts +9 -0
- package/src/components/RichTextEditor/blockReflow.ts +62 -0
- package/src/components/RichTextEditor/commands.ts +300 -0
- package/src/components/RichTextEditor/history.ts +93 -0
- package/src/components/RichTextEditor/icons.tsx +152 -0
- package/src/components/RichTextEditor/imageSize.ts +70 -0
- package/src/components/RichTextEditor/index.ts +5 -0
- package/src/components/RichTextEditor/input.ts +209 -0
- package/src/components/RichTextEditor/inputRules.ts +71 -0
- package/src/components/RichTextEditor/links.ts +103 -0
- package/src/components/RichTextEditor/mentionContext.ts +37 -0
- package/src/components/RichTextEditor/mentions.ts +28 -0
- package/src/components/RichTextEditor/preventSelectionLoss.ts +15 -0
- package/src/components/RichTextEditor/selection.ts +415 -0
- package/src/components/RichTextEditor/shortcuts.ts +44 -0
- package/src/components/RichTextEditor/useAnchoredFloating.ts +50 -0
- package/src/components/RichTextEditor/useAttachmentConfig.ts +134 -0
- package/src/components/RichTextEditor/useDismissOnOutsidePointerDown.ts +26 -0
- package/src/components/RichTextEditor/useLinkEditor.ts +107 -0
- package/src/components/RichTextEditor/useMention.ts +226 -0
- package/src/components/RichTextEditor/useOverlayRect.ts +73 -0
- package/src/components/RichTextEditor/useUpload.ts +246 -0
- package/src/components/Screen/Screen.module.scss +46 -0
- package/src/components/Screen/Screen.tokens.scss +20 -0
- package/src/components/Screen/Screen.tsx +97 -0
- package/src/components/Screen/index.ts +2 -0
- package/src/components/ScrollArea/ScrollArea.module.scss +39 -0
- package/src/components/ScrollArea/ScrollArea.tokens.scss +15 -0
- package/src/components/ScrollArea/ScrollArea.tsx +154 -0
- package/src/components/ScrollArea/index.ts +2 -0
- package/src/components/Select/Chip.tsx +45 -0
- package/src/components/Select/Empty.tsx +39 -0
- package/src/components/Select/Error.tsx +51 -0
- package/src/components/Select/HiddenInputs.tsx +44 -0
- package/src/components/Select/Listbox.tsx +557 -0
- package/src/components/Select/Loading.tsx +30 -0
- package/src/components/Select/Select.module.scss +512 -0
- package/src/components/Select/Select.tokens.scss +140 -0
- package/src/components/Select/Select.tsx +685 -0
- package/src/components/Select/Trigger.tsx +952 -0
- package/src/components/Select/context.ts +101 -0
- package/src/components/Select/emptyStateText.ts +19 -0
- package/src/components/Select/index.ts +9 -0
- package/src/components/Select/useSelectState.ts +157 -0
- package/src/components/Select/utils-types.ts +7 -0
- package/src/components/Select/utils.ts +122 -0
- package/src/components/SettingRow/SettingRow.module.scss +166 -0
- package/src/components/SettingRow/SettingRow.tokens.scss +37 -0
- package/src/components/SettingRow/SettingRow.tsx +242 -0
- package/src/components/SettingRow/index.ts +7 -0
- package/src/components/Skeleton/Skeleton.module.scss +55 -0
- package/src/components/Skeleton/Skeleton.tokens.scss +17 -0
- package/src/components/Skeleton/Skeleton.tsx +131 -0
- package/src/components/Skeleton/index.ts +4 -0
- package/src/components/Skeleton/useSkeletonVisibility.ts +95 -0
- package/src/components/Slider/Slider.module.scss +293 -0
- package/src/components/Slider/Slider.tokens.scss +98 -0
- package/src/components/Slider/Slider.tsx +624 -0
- package/src/components/Slider/index.ts +9 -0
- package/src/components/SlotGrid/SlotGrid.module.scss +61 -0
- package/src/components/SlotGrid/SlotGrid.tokens.scss +21 -0
- package/src/components/SlotGrid/SlotGrid.tsx +111 -0
- package/src/components/SlotGrid/index.ts +2 -0
- package/src/components/SocialButton/SocialButton.module.scss +4 -0
- package/src/components/SocialButton/SocialButton.tsx +40 -0
- package/src/components/SocialButton/index.ts +2 -0
- package/src/components/Sortable/Sortable.module.scss +177 -0
- package/src/components/Sortable/Sortable.tokens.scss +27 -0
- package/src/components/Sortable/Sortable.tsx +572 -0
- package/src/components/Sortable/containerAwareCollision.ts +125 -0
- package/src/components/Sortable/index.ts +13 -0
- package/src/components/SortableGroup/SortableGroup.module.scss +23 -0
- package/src/components/SortableGroup/SortableGroup.tsx +315 -0
- package/src/components/SortableGroup/index.ts +4 -0
- package/src/components/SortableGroup/moveSortableItem.ts +39 -0
- package/src/components/Sparkline/Sparkline.module.scss +28 -0
- package/src/components/Sparkline/Sparkline.tokens.scss +7 -0
- package/src/components/Sparkline/Sparkline.tsx +133 -0
- package/src/components/Sparkline/index.ts +2 -0
- package/src/components/Sparkline/sparklineModel.ts +119 -0
- package/src/components/Split/Split.module.scss +158 -0
- package/src/components/Split/Split.tokens.scss +10 -0
- package/src/components/Split/Split.tsx +231 -0
- package/src/components/Split/index.ts +2 -0
- package/src/components/Stack/Stack.module.scss +67 -0
- package/src/components/Stack/Stack.tokens.scss +11 -0
- package/src/components/Stack/Stack.tsx +100 -0
- package/src/components/Stack/index.ts +2 -0
- package/src/components/StagePath/StagePath.module.scss +255 -0
- package/src/components/StagePath/StagePath.tokens.scss +43 -0
- package/src/components/StagePath/StagePath.tsx +176 -0
- package/src/components/StagePath/index.ts +2 -0
- package/src/components/StatGroup/StatGroup.module.scss +6 -0
- package/src/components/StatGroup/StatGroup.tsx +70 -0
- package/src/components/StatGroup/StatGroupContext.ts +10 -0
- package/src/components/StatGroup/StatTile.module.scss +99 -0
- package/src/components/StatGroup/StatTile.tokens.scss +35 -0
- package/src/components/StatGroup/StatTile.tsx +143 -0
- package/src/components/StatGroup/index.ts +4 -0
- package/src/components/Sticky/Sticky.module.scss +98 -0
- package/src/components/Sticky/Sticky.tokens.scss +17 -0
- package/src/components/Sticky/Sticky.tsx +103 -0
- package/src/components/Sticky/index.ts +2 -0
- package/src/components/Switch/Switch.module.scss +182 -0
- package/src/components/Switch/Switch.tokens.scss +59 -0
- package/src/components/Switch/Switch.tsx +214 -0
- package/src/components/Switch/index.ts +2 -0
- package/src/components/Table/Table.module.scss +308 -0
- package/src/components/Table/Table.tokens.scss +79 -0
- package/src/components/Table/Table.tsx +381 -0
- package/src/components/Table/index.ts +15 -0
- package/src/components/TableOfContents/TableOfContents.module.scss +83 -0
- package/src/components/TableOfContents/TableOfContents.tokens.scss +26 -0
- package/src/components/TableOfContents/TableOfContents.tsx +192 -0
- package/src/components/TableOfContents/activeSection.ts +18 -0
- package/src/components/TableOfContents/index.ts +2 -0
- package/src/components/Tabs/Tabs.module.scss +359 -0
- package/src/components/Tabs/Tabs.tokens.scss +50 -0
- package/src/components/Tabs/Tabs.tsx +538 -0
- package/src/components/Tabs/index.ts +2 -0
- package/src/components/Text/Text.module.scss +115 -0
- package/src/components/Text/Text.tokens.scss +55 -0
- package/src/components/Text/Text.tsx +197 -0
- package/src/components/Text/index.ts +2 -0
- package/src/components/Textarea/Textarea.module.scss +94 -0
- package/src/components/Textarea/Textarea.tokens.scss +48 -0
- package/src/components/Textarea/Textarea.tsx +246 -0
- package/src/components/Textarea/index.ts +2 -0
- package/src/components/Thread/Thread.module.scss +161 -0
- package/src/components/Thread/Thread.tokens.scss +32 -0
- package/src/components/Thread/Thread.tsx +176 -0
- package/src/components/Thread/index.ts +2 -0
- package/src/components/TimeField/TimeField.module.scss +232 -0
- package/src/components/TimeField/TimeField.tsx +793 -0
- package/src/components/TimeField/index.ts +2 -0
- package/src/components/Timeline/Timeline.module.scss +58 -0
- package/src/components/Timeline/Timeline.tokens.scss +13 -0
- package/src/components/Timeline/Timeline.tsx +53 -0
- package/src/components/Timeline/index.ts +2 -0
- package/src/components/Title/Title.module.scss +84 -0
- package/src/components/Title/Title.tokens.scss +37 -0
- package/src/components/Title/Title.tsx +150 -0
- package/src/components/Title/index.ts +2 -0
- package/src/components/Toast/Toast.module.scss +299 -0
- package/src/components/Toast/Toast.tokens.scss +65 -0
- package/src/components/Toast/Toast.tsx +122 -0
- package/src/components/Toast/ToastViewport.tsx +166 -0
- package/src/components/Toast/api.ts +175 -0
- package/src/components/Toast/index.ts +5 -0
- package/src/components/Toast/store.ts +133 -0
- package/src/components/Toast/useToastTimer.ts +94 -0
- package/src/components/Tooltip/Tooltip.module.scss +99 -0
- package/src/components/Tooltip/Tooltip.tokens.scss +23 -0
- package/src/components/Tooltip/Tooltip.tsx +341 -0
- package/src/components/Tooltip/index.ts +2 -0
- package/src/components/TopBar/TopBar.module.scss +235 -0
- package/src/components/TopBar/TopBar.tokens.scss +79 -0
- package/src/components/TopBar/TopBar.tsx +87 -0
- package/src/components/TopBar/TopBarEnd.tsx +20 -0
- package/src/components/TopBar/TopBarIconButton.tsx +86 -0
- package/src/components/TopBar/TopBarSearch.tsx +319 -0
- package/src/components/TopBar/TopBarSearchPanel.tsx +268 -0
- package/src/components/TopBar/TopBarStart.tsx +20 -0
- package/src/components/TopBar/index.ts +11 -0
- package/src/components/Tour/Spotlight.tsx +121 -0
- package/src/components/Tour/Tour.module.scss +210 -0
- package/src/components/Tour/Tour.tokens.scss +58 -0
- package/src/components/Tour/Tour.tsx +617 -0
- package/src/components/Tour/index.ts +2 -0
- package/src/components/Tour/useTourTarget.ts +115 -0
- package/src/components/TreeView/TreeView.module.scss +268 -0
- package/src/components/TreeView/TreeView.tokens.scss +36 -0
- package/src/components/TreeView/TreeView.tsx +536 -0
- package/src/components/TreeView/index.ts +3 -0
- package/src/components/TreeView/treeModel.ts +224 -0
- package/src/components/VisuallyHidden/VisuallyHidden.module.scss +7 -0
- package/src/components/VisuallyHidden/VisuallyHidden.tsx +29 -0
- package/src/components/VisuallyHidden/index.ts +2 -0
- package/src/components/WidgetPreview/WidgetPreview.module.scss +11 -0
- package/src/components/WidgetPreview/WidgetPreview.tokens.scss +26 -0
- package/src/components/WidgetPreview/WidgetPreview.tsx +36 -0
- package/src/components/WidgetPreview/WidgetShape.module.scss +140 -0
- package/src/components/WidgetPreview/WidgetShape.tsx +159 -0
- package/src/components/WidgetPreview/index.ts +2 -0
- package/src/components/_internal/Trend.tsx +27 -0
- package/src/components/_internal/choiceTile.scss +74 -0
- package/src/components/_internal/collapse.scss +10 -0
- package/src/components/_internal/collapse.ts +58 -0
- package/src/components/_internal/currency.ts +257 -0
- package/src/components/_internal/dragAnnouncements.ts +200 -0
- package/src/components/_internal/fieldWiring.ts +225 -0
- package/src/components/_internal/gridSpan.ts +54 -0
- package/src/components/_internal/isoDay.ts +35 -0
- package/src/components/_internal/letterKey.ts +26 -0
- package/src/components/_internal/overlay/focusRestore.ts +21 -0
- package/src/components/_internal/overlay/index.ts +12 -0
- package/src/components/_internal/overlay/tourHold.ts +23 -0
- package/src/components/_internal/overlay/useFocusTrap.ts +120 -0
- package/src/components/_internal/overlay/useInOverlay.ts +81 -0
- package/src/components/_internal/overlay/useOverlayStack.ts +232 -0
- package/src/components/_internal/overlay/useScrollLock.ts +112 -0
- package/src/components/_internal/refs.ts +48 -0
- package/src/components/_internal/statusColor.ts +26 -0
- package/src/components/_internal/timeUtils.ts +23 -0
- package/src/components/_internal/useAsyncOptions.ts +145 -0
- package/src/components/_internal/useClippedTooltip.ts +50 -0
- package/src/components/_internal/useControllableState.ts +50 -0
- package/src/components.manifest.json +1141 -0
- package/src/hooks/useBelowBreakpoint.ts +58 -0
- package/src/i18n/I18nProvider.tsx +52 -0
- package/src/i18n/LocaleProvider.tsx +17 -0
- package/src/i18n/en.ts +648 -0
- package/src/i18n/format.ts +71 -0
- package/src/i18n/index.ts +13 -0
- package/src/i18n/messages.ts +1275 -0
- package/src/i18n/ru.ts +677 -0
- package/src/i18n/useLocale.ts +19 -0
- package/src/i18n/useTranslation.ts +58 -0
- package/src/index.ts +913 -0
- package/src/palette/index.ts +2 -0
- package/src/palette/palette.ts +108 -0
- package/src/styles/dark.scss +4 -0
- package/src/styles/global.scss +7 -0
- package/src/styles/mixins.scss +143 -0
- package/src/styles/reset.scss +78 -0
- package/src/styles/tokens.scss +18 -0
- package/src/styles/typography.scss +44 -0
|
@@ -0,0 +1,936 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
cloneElement,
|
|
4
|
+
createContext,
|
|
5
|
+
forwardRef,
|
|
6
|
+
isValidElement,
|
|
7
|
+
useCallback,
|
|
8
|
+
useContext,
|
|
9
|
+
useEffect,
|
|
10
|
+
useId,
|
|
11
|
+
useMemo,
|
|
12
|
+
useRef,
|
|
13
|
+
useState,
|
|
14
|
+
type KeyboardEvent as ReactKeyboardEvent,
|
|
15
|
+
type ReactElement,
|
|
16
|
+
type ReactNode,
|
|
17
|
+
type Ref,
|
|
18
|
+
} from 'react';
|
|
19
|
+
import clsx from 'clsx';
|
|
20
|
+
import { Search } from 'lucide-react';
|
|
21
|
+
import { mergeRefs } from '../_internal/refs';
|
|
22
|
+
import { useAsyncOptions } from '../_internal/useAsyncOptions';
|
|
23
|
+
import { Popover } from '../Popover';
|
|
24
|
+
import { Button } from '../Button';
|
|
25
|
+
import { Checkbox } from '../Checkbox';
|
|
26
|
+
import { Radio } from '../Radio';
|
|
27
|
+
import { Cluster } from '../Cluster';
|
|
28
|
+
import { Input } from '../Input';
|
|
29
|
+
import { Badge, type BadgeTone } from '../Badge';
|
|
30
|
+
import { type PaletteColor } from '../../palette';
|
|
31
|
+
import { Text } from '../Text';
|
|
32
|
+
import { useTranslation } from '../../i18n/useTranslation';
|
|
33
|
+
import styles from './OptionsPicker.module.scss';
|
|
34
|
+
|
|
35
|
+
// ----------------------------------------------------------------------------
|
|
36
|
+
// Public types
|
|
37
|
+
// ----------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
export type OptionsPickerMode = 'multi' | 'single';
|
|
40
|
+
|
|
41
|
+
export interface OptionsPickerOption {
|
|
42
|
+
/** Unique identifier; persisted as the selected value. */
|
|
43
|
+
value: string;
|
|
44
|
+
/** Displayed text. */
|
|
45
|
+
label: string;
|
|
46
|
+
/** Override what the search filter matches against. Defaults to `label`. */
|
|
47
|
+
searchText?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface OptionsPickerGroup {
|
|
51
|
+
/** Unique per group; used as React key and ARIA target. */
|
|
52
|
+
id: string;
|
|
53
|
+
/** Header text shown in the group label slot. */
|
|
54
|
+
label: string;
|
|
55
|
+
options: OptionsPickerOption[];
|
|
56
|
+
/** Colored dot tone for the group header. Default `'neutral'`. */
|
|
57
|
+
tone?: BadgeTone;
|
|
58
|
+
/**
|
|
59
|
+
* Categorical palette color. When set, takes precedence over `tone`:
|
|
60
|
+
* the group header dot uses the palette color, AND every option's
|
|
61
|
+
* Checkbox in this group adopts the same palette color for its
|
|
62
|
+
* checked / indeterminate fill. Use to give each namespace its own
|
|
63
|
+
* visual identity (e.g., auth.* = blue, role.* = amber).
|
|
64
|
+
*/
|
|
65
|
+
color?: PaletteColor;
|
|
66
|
+
/** Right-side hint label (e.g., `"auth.*"`). Omitted → no hint renders. */
|
|
67
|
+
hint?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface MultiProps {
|
|
71
|
+
mode?: 'multi';
|
|
72
|
+
/**
|
|
73
|
+
* Committed selection: `string[]` (multi) or `string | null` (single).
|
|
74
|
+
* Multi: pass a stable array (state, or memoized) — a new array identity on
|
|
75
|
+
* every render resets the open panel's draft.
|
|
76
|
+
*/
|
|
77
|
+
selected: string[];
|
|
78
|
+
/** Called on Apply with the committed values and their option objects, in the same order. */
|
|
79
|
+
onApply: (next: string[], options: OptionsPickerOption[]) => void;
|
|
80
|
+
onCancel?: () => void;
|
|
81
|
+
open?: boolean;
|
|
82
|
+
onOpenChange?: (open: boolean) => void;
|
|
83
|
+
children: ReactNode;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface SingleProps {
|
|
87
|
+
mode: 'single';
|
|
88
|
+
/** Committed value, or `null` when nothing is picked. */
|
|
89
|
+
selected: string | null;
|
|
90
|
+
/** Called on pick with the value and its option object (`null` / `null` when nothing is picked). */
|
|
91
|
+
onApply: (next: string | null, option: OptionsPickerOption | null) => void;
|
|
92
|
+
onCancel?: () => void;
|
|
93
|
+
open?: boolean;
|
|
94
|
+
onOpenChange?: (open: boolean) => void;
|
|
95
|
+
children: ReactNode;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export type OptionsPickerProps = MultiProps | SingleProps;
|
|
99
|
+
|
|
100
|
+
type FlatContentProps = {
|
|
101
|
+
options: OptionsPickerOption[];
|
|
102
|
+
groups?: never;
|
|
103
|
+
loadOptions?: never;
|
|
104
|
+
};
|
|
105
|
+
type GroupedContentProps = {
|
|
106
|
+
groups: OptionsPickerGroup[];
|
|
107
|
+
options?: never;
|
|
108
|
+
loadOptions?: never;
|
|
109
|
+
};
|
|
110
|
+
type AsyncContentProps = {
|
|
111
|
+
/**
|
|
112
|
+
* Server search: called with the search query (`''` on every open) after
|
|
113
|
+
* `searchDebounceMs`, and its answer replaces the list — no client-side
|
|
114
|
+
* filtering on top. Flat options only. The previous request is aborted via
|
|
115
|
+
* `signal` when a newer one starts. Takes precedence over `options` /
|
|
116
|
+
* `groups` (dev warning if both are passed). The latest function is always
|
|
117
|
+
* the one called; a new identity (e.g. an inline arrow) does not refetch.
|
|
118
|
+
*/
|
|
119
|
+
loadOptions: (query: string, signal: AbortSignal) => Promise<OptionsPickerOption[]>;
|
|
120
|
+
/**
|
|
121
|
+
* Option objects for currently selected values that may not be in any
|
|
122
|
+
* server answer (e.g. values restored from a URL), so they can be labelled.
|
|
123
|
+
*/
|
|
124
|
+
selectedOptions?: OptionsPickerOption[];
|
|
125
|
+
/** Delay between the last keystroke (or opening) and the `loadOptions` call. Default `250`. */
|
|
126
|
+
searchDebounceMs?: number;
|
|
127
|
+
options?: never;
|
|
128
|
+
groups?: never;
|
|
129
|
+
};
|
|
130
|
+
type SharedContentProps = {
|
|
131
|
+
/** Accessible label on the panel (the dialog's `aria-label`). */
|
|
132
|
+
label: string;
|
|
133
|
+
/**
|
|
134
|
+
* Rendered when search produces zero matches. Defaults to the
|
|
135
|
+
* `optionsPicker.noMatches` i18n string (en: `'No matches'`). With
|
|
136
|
+
* `loadOptions` the visible row is hidden from assistive tech and a live
|
|
137
|
+
* region announces it instead — this text when it is a string, otherwise the
|
|
138
|
+
* default.
|
|
139
|
+
*
|
|
140
|
+
* An EMPTY string counts as unset, matching every other label override in
|
|
141
|
+
* the library. There is no way to suppress the message entirely — a blank
|
|
142
|
+
* listbox reads as a broken filter rather than as a deliberate silence.
|
|
143
|
+
*/
|
|
144
|
+
emptyState?: ReactNode;
|
|
145
|
+
/**
|
|
146
|
+
* Footer count formatter (multi only). Default `'${selected} of ${total}'`.
|
|
147
|
+
* Not called with `loadOptions` — there is no known total, so the footer
|
|
148
|
+
* reads `optionsPicker.selectedCount` (en: `'N selected'`).
|
|
149
|
+
*/
|
|
150
|
+
footerCount?: (selected: number, total: number) => ReactNode;
|
|
151
|
+
/**
|
|
152
|
+
* Whether to render the search bar at the top of the panel. Defaults to
|
|
153
|
+
* `true`. Hide it (`false`) for small/curated option lists where typing
|
|
154
|
+
* filters would just be noise — the selection-count header is hidden
|
|
155
|
+
* alongside the search input (the footer's `N of TOTAL` text still
|
|
156
|
+
* shows in multi mode). Ignored with `loadOptions`: server search needs
|
|
157
|
+
* the input, so it always shows.
|
|
158
|
+
*/
|
|
159
|
+
searchable?: boolean;
|
|
160
|
+
className?: string;
|
|
161
|
+
};
|
|
162
|
+
export type OptionsPickerContentProps = (
|
|
163
|
+
| FlatContentProps
|
|
164
|
+
| GroupedContentProps
|
|
165
|
+
| AsyncContentProps
|
|
166
|
+
) &
|
|
167
|
+
SharedContentProps;
|
|
168
|
+
|
|
169
|
+
// ----------------------------------------------------------------------------
|
|
170
|
+
// Context
|
|
171
|
+
// ----------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
interface PickerContextValue {
|
|
174
|
+
mode: OptionsPickerMode;
|
|
175
|
+
/** Always normalized to string[] internally; single mode uses [] or [value]. */
|
|
176
|
+
selected: string[];
|
|
177
|
+
open: boolean;
|
|
178
|
+
setOpen: (next: boolean) => void;
|
|
179
|
+
/** Called when Apply (multi) or click (single) commits draft to consumer. */
|
|
180
|
+
commit: (next: string[], options: OptionsPickerOption[]) => void;
|
|
181
|
+
/** Called on Cancel/Esc/click-outside. */
|
|
182
|
+
cancel: () => void;
|
|
183
|
+
/** Stable id for the Content panel; shared with Trigger for aria-controls. */
|
|
184
|
+
contentId: string;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const PickerContext = createContext<PickerContextValue | null>(null);
|
|
188
|
+
|
|
189
|
+
function usePickerContext(label: string): PickerContextValue {
|
|
190
|
+
const ctx = useContext(PickerContext);
|
|
191
|
+
if (!ctx) {
|
|
192
|
+
throw new Error(`<OptionsPicker.${label}> must be used inside <OptionsPicker>.`);
|
|
193
|
+
}
|
|
194
|
+
return ctx;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ----------------------------------------------------------------------------
|
|
198
|
+
// Root
|
|
199
|
+
// ----------------------------------------------------------------------------
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Compound multi/single-select filter picker with search, optional grouping and draft-then-Apply commit; not a form field.
|
|
203
|
+
* @see docs/components/OptionsPicker.md
|
|
204
|
+
*/
|
|
205
|
+
function OptionsPickerRoot(props: OptionsPickerProps) {
|
|
206
|
+
const mode: OptionsPickerMode = props.mode ?? 'multi';
|
|
207
|
+
|
|
208
|
+
// Normalize selected to string[] internally. Keyed on the value itself, not
|
|
209
|
+
// `props` (a new object every parent render): Content resets its draft /
|
|
210
|
+
// filter / pinned snapshot when this identity changes.
|
|
211
|
+
const rawSelected = props.selected;
|
|
212
|
+
const normalizedSelected = useMemo<string[]>(() => {
|
|
213
|
+
if (mode === 'multi') return rawSelected as string[];
|
|
214
|
+
return rawSelected == null ? [] : [rawSelected as string];
|
|
215
|
+
}, [mode, rawSelected]);
|
|
216
|
+
|
|
217
|
+
// Open state — controlled or uncontrolled.
|
|
218
|
+
const [internalOpen, setInternalOpen] = useState(false);
|
|
219
|
+
const isControlled = props.open !== undefined;
|
|
220
|
+
const open = isControlled ? (props.open as boolean) : internalOpen;
|
|
221
|
+
|
|
222
|
+
// justCommittedRef: true when the most-recent open→close was triggered by
|
|
223
|
+
// a commit (Apply button / radio-click). When false on close, we fire onCancel.
|
|
224
|
+
const justCommittedRef = useRef(false);
|
|
225
|
+
// justFiredCancelRef: guards against double-fire when both Popover's
|
|
226
|
+
// document-level Escape listener AND Content's synthetic onKeyDown both
|
|
227
|
+
// call setOpen(false) in the same React event batch.
|
|
228
|
+
const justFiredCancelRef = useRef(false);
|
|
229
|
+
|
|
230
|
+
const setOpenRaw = useCallback(
|
|
231
|
+
(next: boolean) => {
|
|
232
|
+
if (!isControlled) setInternalOpen(next);
|
|
233
|
+
props.onOpenChange?.(next);
|
|
234
|
+
},
|
|
235
|
+
[isControlled, props],
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
// Intercepts every open→close transition and fires onCancel exactly once
|
|
239
|
+
// when the close was NOT preceded by a commit (i.e., Esc or click-outside).
|
|
240
|
+
const setOpen = useCallback(
|
|
241
|
+
(next: boolean) => {
|
|
242
|
+
if (!next && open && !justCommittedRef.current && !justFiredCancelRef.current) {
|
|
243
|
+
justFiredCancelRef.current = true;
|
|
244
|
+
(props as { onCancel?: () => void }).onCancel?.();
|
|
245
|
+
}
|
|
246
|
+
if (next) {
|
|
247
|
+
// Reset both guards when reopening.
|
|
248
|
+
justCommittedRef.current = false;
|
|
249
|
+
justFiredCancelRef.current = false;
|
|
250
|
+
}
|
|
251
|
+
setOpenRaw(next);
|
|
252
|
+
},
|
|
253
|
+
[open, props, setOpenRaw],
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
const commit = useCallback(
|
|
257
|
+
(next: string[], options: OptionsPickerOption[]) => {
|
|
258
|
+
justCommittedRef.current = true;
|
|
259
|
+
if (mode === 'multi') {
|
|
260
|
+
(props as MultiProps).onApply(next, options);
|
|
261
|
+
} else {
|
|
262
|
+
(props as SingleProps).onApply(next[0] ?? null, options[0] ?? null);
|
|
263
|
+
}
|
|
264
|
+
setOpen(false);
|
|
265
|
+
},
|
|
266
|
+
[mode, props, setOpen],
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
const cancel = useCallback(() => {
|
|
270
|
+
// Explicit cancel (Cancel button, Esc keydown on content) — mark as NOT a
|
|
271
|
+
// commit so setOpen fires onCancel via the interceptor above.
|
|
272
|
+
setOpen(false);
|
|
273
|
+
}, [setOpen]);
|
|
274
|
+
|
|
275
|
+
const contentId = useId();
|
|
276
|
+
|
|
277
|
+
const ctxValue = useMemo<PickerContextValue>(
|
|
278
|
+
() => ({ mode, selected: normalizedSelected, open, setOpen, commit, cancel, contentId }),
|
|
279
|
+
[mode, normalizedSelected, open, setOpen, commit, cancel, contentId],
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<PickerContext.Provider value={ctxValue}>
|
|
284
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
285
|
+
{props.children}
|
|
286
|
+
</Popover>
|
|
287
|
+
</PickerContext.Provider>
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ----------------------------------------------------------------------------
|
|
292
|
+
// Trigger
|
|
293
|
+
// ----------------------------------------------------------------------------
|
|
294
|
+
|
|
295
|
+
export interface OptionsPickerTriggerProps {
|
|
296
|
+
/** Must be a single React element that accepts a ref (e.g. `<Button>`). */
|
|
297
|
+
children: ReactElement;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* The element that opens the picker panel: a single child (typically a `<Button>`) that gets the open wiring and ARIA injected.
|
|
302
|
+
* @see docs/components/OptionsPicker.md
|
|
303
|
+
*/
|
|
304
|
+
const OptionsPickerTrigger = forwardRef<HTMLButtonElement, OptionsPickerTriggerProps>(
|
|
305
|
+
function OptionsPickerTrigger({ children }, ref) {
|
|
306
|
+
// usePickerContext validates that this component is inside an OptionsPicker.
|
|
307
|
+
usePickerContext('Trigger');
|
|
308
|
+
|
|
309
|
+
if (!isValidElement(children)) {
|
|
310
|
+
throw new Error('<OptionsPicker.Trigger> requires exactly one React element child.');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Merge the consumer's ref with the existing child ref so both receive the
|
|
314
|
+
// same DOM node. mergeRefs handles callback refs and object refs alike.
|
|
315
|
+
const child = Children.only(children) as ReactElement<{ ref?: Ref<HTMLButtonElement> }>;
|
|
316
|
+
const childRef = (child as ReactElement & { ref?: Ref<HTMLButtonElement> }).ref;
|
|
317
|
+
const mergedRef = ref ? mergeRefs<HTMLButtonElement>(ref, childRef ?? null) : childRef;
|
|
318
|
+
const childWithRef = cloneElement(child, { ref: mergedRef } as Partial<{
|
|
319
|
+
ref: Ref<HTMLButtonElement>;
|
|
320
|
+
}>);
|
|
321
|
+
|
|
322
|
+
return (
|
|
323
|
+
// Popover.Trigger handles aria-expanded, aria-controls, and click/keydown
|
|
324
|
+
// internally. We only override aria-haspopup to 'listbox' because the
|
|
325
|
+
// content panel surfaces a listbox role, not a generic dialog.
|
|
326
|
+
<Popover.Trigger aria-haspopup="listbox">{childWithRef}</Popover.Trigger>
|
|
327
|
+
);
|
|
328
|
+
},
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
// ----------------------------------------------------------------------------
|
|
332
|
+
// Content
|
|
333
|
+
// ----------------------------------------------------------------------------
|
|
334
|
+
|
|
335
|
+
function isGrouped(
|
|
336
|
+
props: OptionsPickerContentProps,
|
|
337
|
+
): props is GroupedContentProps & SharedContentProps {
|
|
338
|
+
// `loadOptions` wins over `groups` when a consumer passes both.
|
|
339
|
+
return !props.loadOptions && 'groups' in props && props.groups !== undefined;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function isAsync(
|
|
343
|
+
props: OptionsPickerContentProps,
|
|
344
|
+
): props is AsyncContentProps & SharedContentProps {
|
|
345
|
+
return props.loadOptions !== undefined;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function getAllOptions(props: OptionsPickerContentProps): OptionsPickerOption[] {
|
|
349
|
+
if (isGrouped(props)) return props.groups.flatMap((g) => g.options);
|
|
350
|
+
return props.options ?? [];
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function defaultFooterCount(selected: number, total: number): string {
|
|
354
|
+
return `${selected} of ${total}`;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
type TriState = 'false' | 'mixed' | 'true';
|
|
358
|
+
|
|
359
|
+
function tristate(groupOptionValues: string[], draft: string[]): TriState {
|
|
360
|
+
const selectedInGroup = groupOptionValues.filter((v) => draft.includes(v)).length;
|
|
361
|
+
if (selectedInGroup === 0) return 'false';
|
|
362
|
+
if (selectedInGroup === groupOptionValues.length) return 'true';
|
|
363
|
+
return 'mixed';
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* The picker panel: search bar, options list and (multi mode) Apply/Cancel footer; takes `options`, `groups` or `loadOptions`.
|
|
368
|
+
* @see docs/components/OptionsPicker.md
|
|
369
|
+
*/
|
|
370
|
+
const OptionsPickerContent = forwardRef<HTMLDivElement, OptionsPickerContentProps>(
|
|
371
|
+
function OptionsPickerContent(props, ref) {
|
|
372
|
+
const { label, className, searchable = true } = props;
|
|
373
|
+
const t = useTranslation();
|
|
374
|
+
const searchPlaceholder = t('optionsPicker.filter');
|
|
375
|
+
const ctx = usePickerContext('Content');
|
|
376
|
+
|
|
377
|
+
const contentId = ctx.contentId;
|
|
378
|
+
|
|
379
|
+
const [draft, setDraft] = useState<string[]>(ctx.selected);
|
|
380
|
+
const [filter, setFilter] = useState('');
|
|
381
|
+
// Async only: the selected values pinned above the server rows. A snapshot
|
|
382
|
+
// — taken on open and on every query change, never on tick/untick — so
|
|
383
|
+
// rows don't move under the pointer.
|
|
384
|
+
const [pinned, setPinned] = useState<string[]>(ctx.selected);
|
|
385
|
+
|
|
386
|
+
const async = isAsync(props);
|
|
387
|
+
// Latest consumer loadOptions, read at call time: the hook re-arms (and
|
|
388
|
+
// aborts the in-flight request) on a new function identity, so an inline
|
|
389
|
+
// arrow would refetch on every parent render. trackedLoad stays stable.
|
|
390
|
+
const loadRef = useRef(async ? props.loadOptions : undefined);
|
|
391
|
+
loadRef.current = async ? props.loadOptions : undefined;
|
|
392
|
+
// The hook's state outlives a close, so until a request of THIS open has
|
|
393
|
+
// settled its rows / error / loadedQuery belong to the previous open.
|
|
394
|
+
// Tracked here (not in the shared hook) by flagging when loadOptions is
|
|
395
|
+
// actually invoked; reset on close.
|
|
396
|
+
const [fetchedSinceOpen, setFetchedSinceOpen] = useState(false);
|
|
397
|
+
const trackedLoad = useCallback((q: string, signal: AbortSignal) => {
|
|
398
|
+
setFetchedSinceOpen(true);
|
|
399
|
+
return loadRef.current!(q, signal);
|
|
400
|
+
}, []);
|
|
401
|
+
const [settledSinceOpen, setSettledSinceOpen] = useState(false);
|
|
402
|
+
useEffect(() => {
|
|
403
|
+
if (!ctx.open) {
|
|
404
|
+
setFetchedSinceOpen(false);
|
|
405
|
+
setSettledSinceOpen(false);
|
|
406
|
+
}
|
|
407
|
+
}, [ctx.open]);
|
|
408
|
+
// `enabled` follows the open state, so every open fetches '' afresh.
|
|
409
|
+
const asyncResult = useAsyncOptions({
|
|
410
|
+
loadOptions: async ? trackedLoad : undefined,
|
|
411
|
+
query: filter,
|
|
412
|
+
enabled: ctx.open,
|
|
413
|
+
debounceMs: (async ? props.searchDebounceMs : undefined) ?? 250,
|
|
414
|
+
});
|
|
415
|
+
// Latches on the first settle of this open (later searches keep their
|
|
416
|
+
// rows during the refetch). Set during render — React's "adjust state on
|
|
417
|
+
// change" pattern — so no frame paints a stale 'loading'. runFetch flips
|
|
418
|
+
// `loading` in the same batch as the fetched flag, so this cannot latch
|
|
419
|
+
// on the previous open's state.
|
|
420
|
+
if (fetchedSinceOpen && !asyncResult.loading && !settledSinceOpen) {
|
|
421
|
+
setSettledSinceOpen(true);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Mirrors Select: `loadOptions` wins over static options; warn once per
|
|
425
|
+
// mount in dev. Stripped in prod builds.
|
|
426
|
+
const conflictingSources =
|
|
427
|
+
async &&
|
|
428
|
+
((props as { options?: unknown[] }).options?.length ||
|
|
429
|
+
(props as { groups?: unknown[] }).groups?.length);
|
|
430
|
+
useEffect(() => {
|
|
431
|
+
if (process.env.NODE_ENV !== 'production' && conflictingSources) {
|
|
432
|
+
console.warn(
|
|
433
|
+
'<OptionsPicker.Content> received both `options`/`groups` and `loadOptions`. `loadOptions` wins; `options`/`groups` are ignored.',
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
}, [conflictingSources]);
|
|
437
|
+
|
|
438
|
+
// Ref for the search input — used to focus it on open (see effect below).
|
|
439
|
+
const searchInputRef = useRef<HTMLInputElement>(null);
|
|
440
|
+
|
|
441
|
+
useEffect(() => {
|
|
442
|
+
if (ctx.open) {
|
|
443
|
+
setDraft(ctx.selected);
|
|
444
|
+
setPinned(ctx.selected);
|
|
445
|
+
setFilter('');
|
|
446
|
+
}
|
|
447
|
+
}, [ctx.open, ctx.selected]);
|
|
448
|
+
|
|
449
|
+
// Focus the search input on open. Deferred past Popover.Content's own
|
|
450
|
+
// queueMicrotask that focuses the panel div, so our microtask runs after.
|
|
451
|
+
useEffect(() => {
|
|
452
|
+
if (ctx.open) {
|
|
453
|
+
queueMicrotask(() => searchInputRef.current?.focus());
|
|
454
|
+
}
|
|
455
|
+
}, [ctx.open]);
|
|
456
|
+
|
|
457
|
+
const matchesFilter = useCallback(
|
|
458
|
+
(opt: OptionsPickerOption): boolean => {
|
|
459
|
+
if (filter === '') return true;
|
|
460
|
+
const haystack = (opt.searchText ?? opt.label).toLowerCase();
|
|
461
|
+
return haystack.includes(filter.toLowerCase());
|
|
462
|
+
},
|
|
463
|
+
[filter],
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
const optionByValue = useMemo(
|
|
467
|
+
() => new Map((async ? asyncResult.options : getAllOptions(props)).map((o) => [o.value, o])),
|
|
468
|
+
[async, asyncResult.options, props],
|
|
469
|
+
);
|
|
470
|
+
// Async label cache: every server answer and `selectedOptions` seen while
|
|
471
|
+
// mounted, so a pinned value keeps its label after a search omits it.
|
|
472
|
+
// Never cleared. The current answer still wins via `optionByValue`.
|
|
473
|
+
const labelCache = useRef(new Map<string, OptionsPickerOption>());
|
|
474
|
+
const selectedOptions = async ? props.selectedOptions : undefined;
|
|
475
|
+
// Unknown values (e.g. a preselected value not in `options`) fall back to value-as-label.
|
|
476
|
+
const resolveOption = useMemo(() => {
|
|
477
|
+
const cache = labelCache.current;
|
|
478
|
+
for (const o of asyncResult.options) cache.set(o.value, o);
|
|
479
|
+
for (const o of selectedOptions ?? []) cache.set(o.value, o);
|
|
480
|
+
return (value: string): OptionsPickerOption =>
|
|
481
|
+
optionByValue.get(value) ?? cache.get(value) ?? { value, label: value };
|
|
482
|
+
}, [optionByValue, asyncResult.options, selectedOptions]);
|
|
483
|
+
|
|
484
|
+
const toggle = useCallback(
|
|
485
|
+
(value: string) => {
|
|
486
|
+
if (ctx.mode === 'multi') {
|
|
487
|
+
setDraft((prev) =>
|
|
488
|
+
prev.includes(value) ? prev.filter((v) => v !== value) : [...prev, value],
|
|
489
|
+
);
|
|
490
|
+
} else {
|
|
491
|
+
ctx.commit([value], [resolveOption(value)]);
|
|
492
|
+
}
|
|
493
|
+
},
|
|
494
|
+
[ctx, resolveOption],
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
const toggleGroup = useCallback(
|
|
498
|
+
(groupOptions: OptionsPickerOption[]) => {
|
|
499
|
+
if (ctx.mode !== 'multi') return;
|
|
500
|
+
setDraft((prev) => {
|
|
501
|
+
const allValues = groupOptions.map((o) => o.value);
|
|
502
|
+
const allSelected = allValues.every((v) => prev.includes(v));
|
|
503
|
+
if (allSelected) return prev.filter((v) => !allValues.includes(v));
|
|
504
|
+
const next = [...prev];
|
|
505
|
+
for (const v of allValues) if (!next.includes(v)) next.push(v);
|
|
506
|
+
return next;
|
|
507
|
+
});
|
|
508
|
+
},
|
|
509
|
+
[ctx.mode],
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
// Async: the server's answer IS the list (no client filter), minus the
|
|
513
|
+
// pinned values that are rendered above it. An error replaces it with the
|
|
514
|
+
// error row, as in Select; pinned rows stay in every state.
|
|
515
|
+
const serverRows = useMemo(() => {
|
|
516
|
+
if (!async || asyncResult.error || !settledSinceOpen) return [];
|
|
517
|
+
return asyncResult.options.filter((o) => !pinned.includes(o.value));
|
|
518
|
+
}, [async, asyncResult.error, asyncResult.options, settledSinceOpen, pinned]);
|
|
519
|
+
|
|
520
|
+
const visibleFlat = useMemo(() => {
|
|
521
|
+
if (async) return [...pinned.map(resolveOption), ...serverRows];
|
|
522
|
+
if (isGrouped(props)) return [];
|
|
523
|
+
return (props.options ?? []).filter(matchesFilter);
|
|
524
|
+
}, [async, pinned, resolveOption, serverRows, props, matchesFilter]);
|
|
525
|
+
|
|
526
|
+
const visibleGroups = useMemo(() => {
|
|
527
|
+
if (!isGrouped(props)) return [];
|
|
528
|
+
return props.groups
|
|
529
|
+
.map((g) => ({ ...g, visibleOptions: g.options.filter(matchesFilter) }))
|
|
530
|
+
.filter((g) => g.visibleOptions.length > 0);
|
|
531
|
+
}, [props, matchesFilter]);
|
|
532
|
+
|
|
533
|
+
const hasAnyVisible = isGrouped(props) ? visibleGroups.length > 0 : visibleFlat.length > 0;
|
|
534
|
+
|
|
535
|
+
// Async state row, rendered below the pinned rows. Driven by the server
|
|
536
|
+
// answer alone: pinned rows never suppress it. Empty means the server
|
|
537
|
+
// returned nothing for the settled query on screen (during the debounce
|
|
538
|
+
// window the rows are the previous query's) — it describes the search, so
|
|
539
|
+
// it shows even with pinned rows above it. An answer holding only pinned
|
|
540
|
+
// values is not empty: those values matched.
|
|
541
|
+
const asyncState = !async
|
|
542
|
+
? null
|
|
543
|
+
: !settledSinceOpen
|
|
544
|
+
? 'loading'
|
|
545
|
+
: asyncResult.error
|
|
546
|
+
? 'error'
|
|
547
|
+
: asyncResult.loading && serverRows.length === 0
|
|
548
|
+
? 'loading'
|
|
549
|
+
: !asyncResult.loading &&
|
|
550
|
+
asyncResult.options.length === 0 &&
|
|
551
|
+
asyncResult.loadedQuery === filter
|
|
552
|
+
? 'empty'
|
|
553
|
+
: null;
|
|
554
|
+
const emptyText =
|
|
555
|
+
(typeof props.emptyState === 'string' && props.emptyState) || t('optionsPicker.noMatches');
|
|
556
|
+
// Hard rule 10: one live region (async only), mounted with the panel —
|
|
557
|
+
// Popover.Content renders nothing while closed. The text is '' while
|
|
558
|
+
// closed and set in an effect, so the region mounts empty on every open
|
|
559
|
+
// and the open-time state arrives after it (a region that mounts with
|
|
560
|
+
// its text announces nothing). The visible state rows are aria-hidden so
|
|
561
|
+
// readers meet each once.
|
|
562
|
+
const [statusText, setStatusText] = useState('');
|
|
563
|
+
useEffect(() => {
|
|
564
|
+
setStatusText(
|
|
565
|
+
!ctx.open
|
|
566
|
+
? ''
|
|
567
|
+
: asyncState === 'loading'
|
|
568
|
+
? t('optionsPicker.statusLoading')
|
|
569
|
+
: asyncState === 'error'
|
|
570
|
+
? t('optionsPicker.loadFailed')
|
|
571
|
+
: asyncState === 'empty'
|
|
572
|
+
? emptyText
|
|
573
|
+
: '',
|
|
574
|
+
);
|
|
575
|
+
}, [ctx.open, asyncState, emptyText, t]);
|
|
576
|
+
|
|
577
|
+
const allOptionsForCount = useMemo(() => getAllOptions(props), [props]);
|
|
578
|
+
|
|
579
|
+
const visibleOptionsInOrder = useMemo<OptionsPickerOption[]>(() => {
|
|
580
|
+
if (isGrouped(props)) return visibleGroups.flatMap((g) => g.visibleOptions);
|
|
581
|
+
return visibleFlat;
|
|
582
|
+
}, [props, visibleGroups, visibleFlat]);
|
|
583
|
+
|
|
584
|
+
const [focusedValue, setFocusedValue] = useState<string | null>(null);
|
|
585
|
+
|
|
586
|
+
useEffect(() => {
|
|
587
|
+
if (!ctx.open) {
|
|
588
|
+
setFocusedValue(null);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
if (focusedValue && !visibleOptionsInOrder.some((o) => o.value === focusedValue)) {
|
|
592
|
+
setFocusedValue(null);
|
|
593
|
+
}
|
|
594
|
+
}, [ctx.open, visibleOptionsInOrder, focusedValue]);
|
|
595
|
+
|
|
596
|
+
const handleKeyDown = useCallback(
|
|
597
|
+
(e: ReactKeyboardEvent<HTMLDivElement>) => {
|
|
598
|
+
if (visibleOptionsInOrder.length === 0) return;
|
|
599
|
+
const idx = focusedValue
|
|
600
|
+
? visibleOptionsInOrder.findIndex((o) => o.value === focusedValue)
|
|
601
|
+
: -1;
|
|
602
|
+
if (e.key === 'ArrowDown') {
|
|
603
|
+
e.preventDefault();
|
|
604
|
+
const next = idx + 1 >= visibleOptionsInOrder.length ? 0 : idx + 1;
|
|
605
|
+
setFocusedValue(visibleOptionsInOrder[next]!.value);
|
|
606
|
+
} else if (e.key === 'ArrowUp') {
|
|
607
|
+
e.preventDefault();
|
|
608
|
+
const next = idx <= 0 ? visibleOptionsInOrder.length - 1 : idx - 1;
|
|
609
|
+
setFocusedValue(visibleOptionsInOrder[next]!.value);
|
|
610
|
+
} else if (e.key === 'Home') {
|
|
611
|
+
e.preventDefault();
|
|
612
|
+
setFocusedValue(visibleOptionsInOrder[0]!.value);
|
|
613
|
+
} else if (e.key === 'End') {
|
|
614
|
+
e.preventDefault();
|
|
615
|
+
setFocusedValue(visibleOptionsInOrder[visibleOptionsInOrder.length - 1]!.value);
|
|
616
|
+
} else if ((e.metaKey || e.ctrlKey) && e.key === 'Enter' && ctx.mode === 'multi') {
|
|
617
|
+
e.preventDefault();
|
|
618
|
+
ctx.commit(draft, draft.map(resolveOption));
|
|
619
|
+
} else if (e.key === 'Enter' || e.key === ' ') {
|
|
620
|
+
// Enter acts on the row from the search input or the panel; Space
|
|
621
|
+
// only from the panel itself — in the search it types a space, and
|
|
622
|
+
// every native control (row checkbox/radio, Retry/Apply/Cancel, a
|
|
623
|
+
// consumer emptyState link) keeps its own Enter/Space.
|
|
624
|
+
const handles =
|
|
625
|
+
e.target === e.currentTarget ||
|
|
626
|
+
(e.key === 'Enter' && e.target === searchInputRef.current);
|
|
627
|
+
if (focusedValue && handles) {
|
|
628
|
+
e.preventDefault();
|
|
629
|
+
toggle(focusedValue);
|
|
630
|
+
}
|
|
631
|
+
} else if (e.key === 'Escape') {
|
|
632
|
+
e.preventDefault();
|
|
633
|
+
setDraft(ctx.selected);
|
|
634
|
+
ctx.cancel();
|
|
635
|
+
}
|
|
636
|
+
},
|
|
637
|
+
[visibleOptionsInOrder, focusedValue, toggle, ctx, draft, resolveOption],
|
|
638
|
+
);
|
|
639
|
+
|
|
640
|
+
return (
|
|
641
|
+
<Popover.Content
|
|
642
|
+
ref={ref}
|
|
643
|
+
className={clsx(styles.panel, className)}
|
|
644
|
+
aria-label={label}
|
|
645
|
+
onKeyDown={handleKeyDown}
|
|
646
|
+
>
|
|
647
|
+
{/*
|
|
648
|
+
Plain <div> instead of <Stack> — each region (searchBar, list,
|
|
649
|
+
footer) has its own padding + borders that separate them
|
|
650
|
+
visually. A Stack gap here would add unwanted whitespace between
|
|
651
|
+
the borders.
|
|
652
|
+
*/}
|
|
653
|
+
<div>
|
|
654
|
+
{(searchable || async) && (
|
|
655
|
+
<div className={styles.searchBar}>
|
|
656
|
+
<Search size={14} aria-hidden className={styles.searchIcon} />
|
|
657
|
+
<Input
|
|
658
|
+
ref={searchInputRef}
|
|
659
|
+
type="text"
|
|
660
|
+
value={filter}
|
|
661
|
+
onChange={(e) => {
|
|
662
|
+
setFilter(e.currentTarget.value);
|
|
663
|
+
setPinned(draft);
|
|
664
|
+
}}
|
|
665
|
+
placeholder={searchPlaceholder}
|
|
666
|
+
aria-label={searchPlaceholder}
|
|
667
|
+
className={styles.searchInput}
|
|
668
|
+
/>
|
|
669
|
+
</div>
|
|
670
|
+
)}
|
|
671
|
+
|
|
672
|
+
{async && (
|
|
673
|
+
<span role="status" aria-live="polite" className={styles.srOnly}>
|
|
674
|
+
{statusText}
|
|
675
|
+
</span>
|
|
676
|
+
)}
|
|
677
|
+
|
|
678
|
+
<div
|
|
679
|
+
className={styles.list}
|
|
680
|
+
role="listbox"
|
|
681
|
+
aria-multiselectable={ctx.mode === 'multi'}
|
|
682
|
+
aria-activedescendant={focusedValue ? `${contentId}-opt-${focusedValue}` : undefined}
|
|
683
|
+
>
|
|
684
|
+
{!async && !hasAnyVisible && (
|
|
685
|
+
<Text size="sm" tone="muted" className={styles.empty}>
|
|
686
|
+
{props.emptyState || t('optionsPicker.noMatches')}
|
|
687
|
+
</Text>
|
|
688
|
+
)}
|
|
689
|
+
{hasAnyVisible &&
|
|
690
|
+
isGrouped(props) &&
|
|
691
|
+
visibleGroups.map((g) => {
|
|
692
|
+
// When the group has a palette color, draw a 2px bottom
|
|
693
|
+
// border on the header in that color — visually carries
|
|
694
|
+
// the namespace identity from the dot across the full
|
|
695
|
+
// header width, separating each group's options.
|
|
696
|
+
//
|
|
697
|
+
// The width is load-bearing, not decorative: #510 gave the
|
|
698
|
+
// header an INSET focus ring (the list is a padding-less
|
|
699
|
+
// scroller, so an outset ring lost both vertical bands), and
|
|
700
|
+
// the inset band is --ring-width, also 2px. In multi mode,
|
|
701
|
+
// where the header is a focusable <button>, a focused header
|
|
702
|
+
// therefore covers this border completely — single mode's
|
|
703
|
+
// header is a <div role="presentation">, which never takes a
|
|
704
|
+
// ring and never occludes anything. That trade is only
|
|
705
|
+
// acceptable because the GroupDot beside the label carries the
|
|
706
|
+
// same --color-palette-<name>-fg and the ring does not touch
|
|
707
|
+
// it. Reasoning from the old "1px" gave a partial occlusion
|
|
708
|
+
// and a different verdict on whether the trade is sound.
|
|
709
|
+
const headerClassName = clsx(
|
|
710
|
+
styles.groupHeader,
|
|
711
|
+
g.color && styles.groupHeaderWithBorder,
|
|
712
|
+
);
|
|
713
|
+
const headerStyle = g.color
|
|
714
|
+
? { borderBottomColor: `var(--color-palette-${g.color}-fg)` }
|
|
715
|
+
: undefined;
|
|
716
|
+
return (
|
|
717
|
+
<div key={g.id} className={styles.group}>
|
|
718
|
+
{ctx.mode === 'multi' ? (
|
|
719
|
+
<button
|
|
720
|
+
type="button"
|
|
721
|
+
className={headerClassName}
|
|
722
|
+
style={headerStyle}
|
|
723
|
+
aria-pressed={tristate(
|
|
724
|
+
g.options.map((o) => o.value),
|
|
725
|
+
draft,
|
|
726
|
+
)}
|
|
727
|
+
aria-label={t('optionsPicker.toggleGroup', { label: g.label })}
|
|
728
|
+
aria-controls={g.options
|
|
729
|
+
.map((o) => `${contentId}-opt-${o.value}`)
|
|
730
|
+
.join(' ')}
|
|
731
|
+
onClick={() => toggleGroup(g.options)}
|
|
732
|
+
>
|
|
733
|
+
<GroupDot color={g.color} tone={g.tone} />
|
|
734
|
+
<Text size="xs" weight="semibold" className={styles.groupLabel}>
|
|
735
|
+
{g.label}
|
|
736
|
+
</Text>
|
|
737
|
+
{g.hint && (
|
|
738
|
+
<Text size="xs" tone="muted" className={styles.groupHint}>
|
|
739
|
+
{g.hint}
|
|
740
|
+
</Text>
|
|
741
|
+
)}
|
|
742
|
+
</button>
|
|
743
|
+
) : (
|
|
744
|
+
<div className={headerClassName} style={headerStyle} role="presentation">
|
|
745
|
+
<GroupDot color={g.color} tone={g.tone} />
|
|
746
|
+
<Text size="xs" weight="semibold" className={styles.groupLabel}>
|
|
747
|
+
{g.label}
|
|
748
|
+
</Text>
|
|
749
|
+
{g.hint && (
|
|
750
|
+
<Text size="xs" tone="muted" className={styles.groupHint}>
|
|
751
|
+
{g.hint}
|
|
752
|
+
</Text>
|
|
753
|
+
)}
|
|
754
|
+
</div>
|
|
755
|
+
)}
|
|
756
|
+
{g.visibleOptions.map((opt) => (
|
|
757
|
+
<OptionRow
|
|
758
|
+
key={opt.value}
|
|
759
|
+
option={opt}
|
|
760
|
+
checked={draft.includes(opt.value)}
|
|
761
|
+
mode={ctx.mode}
|
|
762
|
+
rowId={`${contentId}-opt-${opt.value}`}
|
|
763
|
+
focused={focusedValue === opt.value}
|
|
764
|
+
onToggle={toggle}
|
|
765
|
+
color={g.color}
|
|
766
|
+
/>
|
|
767
|
+
))}
|
|
768
|
+
</div>
|
|
769
|
+
);
|
|
770
|
+
})}
|
|
771
|
+
{hasAnyVisible &&
|
|
772
|
+
!isGrouped(props) &&
|
|
773
|
+
visibleFlat.map((opt) => (
|
|
774
|
+
<OptionRow
|
|
775
|
+
key={opt.value}
|
|
776
|
+
option={opt}
|
|
777
|
+
checked={draft.includes(opt.value)}
|
|
778
|
+
mode={ctx.mode}
|
|
779
|
+
rowId={`${contentId}-opt-${opt.value}`}
|
|
780
|
+
focused={focusedValue === opt.value}
|
|
781
|
+
onToggle={toggle}
|
|
782
|
+
/>
|
|
783
|
+
))}
|
|
784
|
+
{/* Async state rows sit below the pinned rows (server rows are empty then). */}
|
|
785
|
+
{asyncState === 'loading' && (
|
|
786
|
+
<Text size="sm" tone="muted" className={styles.empty} aria-hidden="true">
|
|
787
|
+
{t('optionsPicker.statusLoading')}
|
|
788
|
+
</Text>
|
|
789
|
+
)}
|
|
790
|
+
{asyncState === 'error' && (
|
|
791
|
+
<Cluster gap="sm" justify="center" className={styles.empty}>
|
|
792
|
+
<Text size="sm" tone="muted" aria-hidden="true">
|
|
793
|
+
{t('optionsPicker.loadFailed')}
|
|
794
|
+
</Text>
|
|
795
|
+
{/* Name stays plain: the status region keeps the failure text, so folding it in double-reads (as in Select/Error.tsx). */}
|
|
796
|
+
<Button
|
|
797
|
+
variant="secondary"
|
|
798
|
+
size="sm"
|
|
799
|
+
onClick={() => {
|
|
800
|
+
// Back to "nothing settled this open": the hook still holds
|
|
801
|
+
// the last good answer (maybe a previous open's), so show
|
|
802
|
+
// loading until the retry settles, not those rows.
|
|
803
|
+
setFetchedSinceOpen(false);
|
|
804
|
+
setSettledSinceOpen(false);
|
|
805
|
+
asyncResult.retry();
|
|
806
|
+
// Retry unmounts with this click; keep focus in the panel.
|
|
807
|
+
searchInputRef.current?.focus();
|
|
808
|
+
}}
|
|
809
|
+
>
|
|
810
|
+
{t('optionsPicker.retry')}
|
|
811
|
+
</Button>
|
|
812
|
+
</Cluster>
|
|
813
|
+
)}
|
|
814
|
+
{asyncState === 'empty' && (
|
|
815
|
+
<Text size="sm" tone="muted" className={styles.empty} aria-hidden="true">
|
|
816
|
+
{props.emptyState || t('optionsPicker.noMatches')}
|
|
817
|
+
</Text>
|
|
818
|
+
)}
|
|
819
|
+
</div>
|
|
820
|
+
|
|
821
|
+
{ctx.mode === 'multi' && (
|
|
822
|
+
<div className={styles.footer}>
|
|
823
|
+
<Text size="xs" tone="muted">
|
|
824
|
+
{async
|
|
825
|
+
? t('optionsPicker.selectedCount', { count: draft.length })
|
|
826
|
+
: (props.footerCount ?? defaultFooterCount)(
|
|
827
|
+
draft.length,
|
|
828
|
+
allOptionsForCount.length,
|
|
829
|
+
)}
|
|
830
|
+
</Text>
|
|
831
|
+
<Cluster gap="sm">
|
|
832
|
+
<Button
|
|
833
|
+
variant="secondary"
|
|
834
|
+
size="sm"
|
|
835
|
+
onClick={() => {
|
|
836
|
+
setDraft(ctx.selected);
|
|
837
|
+
ctx.cancel();
|
|
838
|
+
}}
|
|
839
|
+
>
|
|
840
|
+
{t('optionsPicker.cancel')}
|
|
841
|
+
</Button>
|
|
842
|
+
<Button
|
|
843
|
+
variant="primary"
|
|
844
|
+
size="sm"
|
|
845
|
+
onClick={() => ctx.commit(draft, draft.map(resolveOption))}
|
|
846
|
+
>
|
|
847
|
+
{t('optionsPicker.apply')}
|
|
848
|
+
</Button>
|
|
849
|
+
</Cluster>
|
|
850
|
+
</div>
|
|
851
|
+
)}
|
|
852
|
+
</div>
|
|
853
|
+
</Popover.Content>
|
|
854
|
+
);
|
|
855
|
+
},
|
|
856
|
+
);
|
|
857
|
+
|
|
858
|
+
/**
|
|
859
|
+
* Group-header dot. Default path uses `<Badge tone>` (the legacy
|
|
860
|
+
* semantic-tone behavior). When a palette `color` is set on the group,
|
|
861
|
+
* render a custom palette-colored dot instead — the same saturated
|
|
862
|
+
* `--color-palette-<name>-fg` token used by the group's Checkboxes,
|
|
863
|
+
* so the header and the row fills match exactly.
|
|
864
|
+
*/
|
|
865
|
+
function GroupDot({ color, tone }: { color?: PaletteColor; tone?: BadgeTone }) {
|
|
866
|
+
if (color) {
|
|
867
|
+
return (
|
|
868
|
+
<span
|
|
869
|
+
aria-hidden="true"
|
|
870
|
+
className={styles.groupDot}
|
|
871
|
+
style={{ background: `var(--color-palette-${color}-fg)` }}
|
|
872
|
+
data-palette={color}
|
|
873
|
+
/>
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
return <Badge tone={tone ?? 'neutral'} dot="start" size="sm" className={styles.groupDot} />;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
interface OptionRowProps {
|
|
880
|
+
option: OptionsPickerOption;
|
|
881
|
+
checked: boolean;
|
|
882
|
+
mode: OptionsPickerMode;
|
|
883
|
+
rowId: string;
|
|
884
|
+
focused: boolean;
|
|
885
|
+
onToggle: (value: string) => void;
|
|
886
|
+
color?: PaletteColor;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function OptionRow({ option, checked, mode, rowId, focused, onToggle, color }: OptionRowProps) {
|
|
890
|
+
const checkboxOnChange = useCallback(() => onToggle(option.value), [onToggle, option.value]);
|
|
891
|
+
const radioOnChange = useCallback(() => onToggle(option.value), [onToggle, option.value]);
|
|
892
|
+
// Click anywhere inside the row toggles — the inner Checkbox/Radio handles
|
|
893
|
+
// clicks on the input + label itself (via native <label> semantics + onChange),
|
|
894
|
+
// and this onClick covers the surrounding padding. The `target === currentTarget`
|
|
895
|
+
// guard prevents double-fire: the row's onClick only runs when the click landed
|
|
896
|
+
// directly on the row div (i.e., on the padding), not when it bubbled up from
|
|
897
|
+
// the inner input or label.
|
|
898
|
+
const handleRowClick = useCallback(
|
|
899
|
+
(e: React.MouseEvent<HTMLDivElement>) => {
|
|
900
|
+
if (e.target === e.currentTarget) onToggle(option.value);
|
|
901
|
+
},
|
|
902
|
+
[onToggle, option.value],
|
|
903
|
+
);
|
|
904
|
+
return (
|
|
905
|
+
<div
|
|
906
|
+
id={rowId}
|
|
907
|
+
className={clsx(styles.row, checked && styles.rowSelected, focused && styles.rowFocused)}
|
|
908
|
+
onClick={handleRowClick}
|
|
909
|
+
>
|
|
910
|
+
{mode === 'multi' ? (
|
|
911
|
+
<Checkbox
|
|
912
|
+
checked={checked}
|
|
913
|
+
onChange={checkboxOnChange}
|
|
914
|
+
label={<Text size="sm">{option.label}</Text>}
|
|
915
|
+
color={color}
|
|
916
|
+
/>
|
|
917
|
+
) : (
|
|
918
|
+
<Radio
|
|
919
|
+
value={option.value}
|
|
920
|
+
checked={checked}
|
|
921
|
+
onChange={radioOnChange}
|
|
922
|
+
label={<Text size="sm">{option.label}</Text>}
|
|
923
|
+
/>
|
|
924
|
+
)}
|
|
925
|
+
</div>
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// ----------------------------------------------------------------------------
|
|
930
|
+
// Compound export
|
|
931
|
+
// ----------------------------------------------------------------------------
|
|
932
|
+
|
|
933
|
+
export const OptionsPicker = Object.assign(OptionsPickerRoot, {
|
|
934
|
+
Trigger: OptionsPickerTrigger,
|
|
935
|
+
Content: OptionsPickerContent,
|
|
936
|
+
});
|