@eocrm/design-system 0.0.0-stage → 0.6.17
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/README.md +262 -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 +82 -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,952 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useMemo,
|
|
5
|
+
useRef,
|
|
6
|
+
type KeyboardEvent,
|
|
7
|
+
type MutableRefObject,
|
|
8
|
+
type PointerEvent,
|
|
9
|
+
type Ref,
|
|
10
|
+
} from 'react';
|
|
11
|
+
import clsx from 'clsx';
|
|
12
|
+
import { useSelectContext } from './context';
|
|
13
|
+
import { Chip } from './Chip';
|
|
14
|
+
import type { SelectOption } from './Select';
|
|
15
|
+
import type { FlatRow } from './utils';
|
|
16
|
+
import { useTranslation } from '../../i18n/useTranslation';
|
|
17
|
+
import styles from './Select.module.scss';
|
|
18
|
+
|
|
19
|
+
export interface TriggerProps {
|
|
20
|
+
placeholder?: string;
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
readOnly?: boolean;
|
|
23
|
+
invalid?: boolean;
|
|
24
|
+
clearable?: boolean;
|
|
25
|
+
/** Select the combobox text on open (single searchable trigger only). */
|
|
26
|
+
selectOnOpen?: boolean;
|
|
27
|
+
'aria-label'?: string;
|
|
28
|
+
'aria-labelledby'?: string;
|
|
29
|
+
'aria-describedby'?: string;
|
|
30
|
+
'aria-required'?: boolean | 'true' | 'false';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
34
|
+
// ClearButton — the trailing ✕ rendered inside the trigger when
|
|
35
|
+
// `effectiveClearable` is true AND a value is set. Wired by every variant.
|
|
36
|
+
//
|
|
37
|
+
// Wired as a real `<button>` so screen readers announce a focusable
|
|
38
|
+
// "Clear selection" action. `stopPropagation` on both pointerdown + click
|
|
39
|
+
// keeps the parent trigger's own toggle handler from firing (clicking ✕
|
|
40
|
+
// must NOT also open or close the listbox), and the explicit
|
|
41
|
+
// `setValue('')` / `setValue([])` path bypasses `toggleValue` so a
|
|
42
|
+
// multi-select wipe is one event, not N.
|
|
43
|
+
//
|
|
44
|
+
// `tabIndex={0}` (not `-1`) — the clear button MUST be reachable by
|
|
45
|
+
// keyboard for accessibility. The default Tab order takes the user from
|
|
46
|
+
// the trigger to ✕ and then on to the next focusable element on the page;
|
|
47
|
+
// Enter/Space on the focused ✕ activates the native button's `onClick` so
|
|
48
|
+
// no extra keyboard handler is needed.
|
|
49
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
50
|
+
function ClearButton({ variant }: { variant: 'overlay' | 'inline' }) {
|
|
51
|
+
const ctx = useSelectContext('Trigger.ClearButton');
|
|
52
|
+
const t = useTranslation();
|
|
53
|
+
return (
|
|
54
|
+
<button
|
|
55
|
+
type="button"
|
|
56
|
+
tabIndex={0}
|
|
57
|
+
className={clsx(styles.clearButton, variant === 'inline' && styles.clearButtonInline)}
|
|
58
|
+
aria-label={t('select.clear')}
|
|
59
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
60
|
+
onMouseDown={(e) => e.stopPropagation()}
|
|
61
|
+
onClick={(e) => {
|
|
62
|
+
e.stopPropagation();
|
|
63
|
+
if (ctx.multiple) {
|
|
64
|
+
ctx.setValue([]);
|
|
65
|
+
} else {
|
|
66
|
+
ctx.setValue('');
|
|
67
|
+
}
|
|
68
|
+
}}
|
|
69
|
+
>
|
|
70
|
+
<svg width="10" height="10" viewBox="0 0 10 10" aria-hidden="true">
|
|
71
|
+
<path d="M1 1 L9 9 M9 1 L1 9" stroke="currentColor" strokeWidth="1.5" fill="none" />
|
|
72
|
+
</svg>
|
|
73
|
+
</button>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Helper: would pressing "clear" change anything?
|
|
79
|
+
*
|
|
80
|
+
* Deliberately value-shaped, not lookup-shaped: clearing resets the Select
|
|
81
|
+
* to the empty value (`''` / `[]`), so the ✕ is only useful when the current
|
|
82
|
+
* value differs from that. A consumer who models "no explicit choice" as a
|
|
83
|
+
* real `value: ''` option (issue #470) is already sitting on the cleared
|
|
84
|
+
* value — there is nothing for ✕ to do, and it stays hidden.
|
|
85
|
+
*/
|
|
86
|
+
function selectHasValue(value: string | string[], multiple: boolean): boolean {
|
|
87
|
+
return multiple
|
|
88
|
+
? Array.isArray(value) && value.length > 0
|
|
89
|
+
: typeof value === 'string' && value !== '';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Resolve the selected option in single mode by looking `value` up in the
|
|
94
|
+
* supplied rows — never by treating `''` as "nothing selected" (issue #470).
|
|
95
|
+
*
|
|
96
|
+
* `''` is a legitimate option value: a common shape is one sentinel row
|
|
97
|
+
* ("Use the default scheme", `value: ''`) plus N real catalog ids. Such a
|
|
98
|
+
* row must render in the closed trigger exactly like any other option.
|
|
99
|
+
* "Nothing selected" is then simply "no row carries this value" — which is
|
|
100
|
+
* what an untouched Select (whose value is `''` with no `''` option in the
|
|
101
|
+
* list) resolves to anyway.
|
|
102
|
+
*
|
|
103
|
+
* Always searches `allRows`, never the query-filtered `rows`: the closed
|
|
104
|
+
* trigger must keep showing the selected label while the user filters the
|
|
105
|
+
* open searchable listbox down to something else.
|
|
106
|
+
*
|
|
107
|
+
* Returns `null` in multi mode, for a non-string value, or when no row
|
|
108
|
+
* matches (e.g. a stale value no longer present in `options`).
|
|
109
|
+
*/
|
|
110
|
+
function findSelectedOption(
|
|
111
|
+
allRows: readonly FlatRow<unknown>[],
|
|
112
|
+
value: string | string[],
|
|
113
|
+
multiple: boolean,
|
|
114
|
+
): SelectOption<unknown> | null {
|
|
115
|
+
if (multiple || typeof value !== 'string') return null;
|
|
116
|
+
const row = allRows.find((r) => r.kind === 'option' && r.option.value === value);
|
|
117
|
+
return row && row.kind === 'option' ? row.option : null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
121
|
+
// Shared helpers
|
|
122
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Computes the `aria-activedescendant` value for whichever trigger is
|
|
126
|
+
* currently mounted. Returns `undefined` when the listbox is closed,
|
|
127
|
+
* when the active row is a group header, or when no row is active —
|
|
128
|
+
* those states must omit the attribute entirely rather than dangling
|
|
129
|
+
* a stale id (screen readers will announce a non-existent element).
|
|
130
|
+
*/
|
|
131
|
+
function useActiveOptionId(): string | undefined {
|
|
132
|
+
const ctx = useSelectContext('Trigger');
|
|
133
|
+
if (!ctx.open || ctx.activeIndex < 0) return undefined;
|
|
134
|
+
const row = ctx.rows[ctx.activeIndex];
|
|
135
|
+
if (!row || row.kind !== 'option') return undefined;
|
|
136
|
+
return ctx.getOptionId(row.option.value);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Shared keyboard navigation hook for both trigger variants.
|
|
141
|
+
*
|
|
142
|
+
* Returns `handleNavKey(e)` which:
|
|
143
|
+
* - Returns `true` if the key was a navigation key the trigger handled
|
|
144
|
+
* (caller should NOT continue to its own variant-specific branch).
|
|
145
|
+
* - Returns `false` for keys this hook doesn't own — Tab, printable
|
|
146
|
+
* chars, anything not in the WAI-ARIA combobox navigation contract —
|
|
147
|
+
* so the caller can layer its own behaviour (typeahead for the button
|
|
148
|
+
* variant, query updates for the combobox-input variant).
|
|
149
|
+
*
|
|
150
|
+
* Tab returns `false` (and does NOT preventDefault) so the browser
|
|
151
|
+
* continues normal tab traversal after the side-effect of closing
|
|
152
|
+
* the popover.
|
|
153
|
+
*/
|
|
154
|
+
function useTriggerKeyboard(opts: { disabled?: boolean; readOnly?: boolean }) {
|
|
155
|
+
const ctx = useSelectContext('Trigger');
|
|
156
|
+
|
|
157
|
+
// Recompute on every render — these are cheap O(n) walks and depending
|
|
158
|
+
// on stable identity here means memoising over `ctx.rows`, which already
|
|
159
|
+
// re-creates per render of `<Select>`.
|
|
160
|
+
const firstSelectableIdx = ctx.rows.findIndex((r) => r.kind === 'option' && !r.option.disabled);
|
|
161
|
+
const lastSelectableIdx = (() => {
|
|
162
|
+
for (let i = ctx.rows.length - 1; i >= 0; i--) {
|
|
163
|
+
const r = ctx.rows[i];
|
|
164
|
+
if (r.kind === 'option' && !r.option.disabled) return i;
|
|
165
|
+
}
|
|
166
|
+
return -1;
|
|
167
|
+
})();
|
|
168
|
+
|
|
169
|
+
// Cyclic step through rows skipping headers + disabled options.
|
|
170
|
+
// `delta` of ±1 maps to ArrowDown/Up; PageDown/Up call this 5 times.
|
|
171
|
+
const moveActive = useCallback(
|
|
172
|
+
(delta: number) => {
|
|
173
|
+
const { rows, activeIndex } = ctx;
|
|
174
|
+
if (rows.length === 0) return;
|
|
175
|
+
const len = rows.length;
|
|
176
|
+
let i = activeIndex;
|
|
177
|
+
for (let step = 0; step < len; step++) {
|
|
178
|
+
i = (i + delta + len) % len;
|
|
179
|
+
const r = rows[i];
|
|
180
|
+
if (r.kind === 'option' && !r.option.disabled) {
|
|
181
|
+
ctx.setActiveIndex(i);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
[ctx],
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
// Typeahead state (only used by ButtonTrigger but lives here so the
|
|
190
|
+
// hook owns its own timer cleanup).
|
|
191
|
+
const typeaheadBufferRef = useRef('');
|
|
192
|
+
const typeaheadTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
193
|
+
|
|
194
|
+
useEffect(() => {
|
|
195
|
+
return () => {
|
|
196
|
+
if (typeaheadTimerRef.current) clearTimeout(typeaheadTimerRef.current);
|
|
197
|
+
};
|
|
198
|
+
}, []);
|
|
199
|
+
|
|
200
|
+
const stepTypeahead = useCallback(
|
|
201
|
+
(char: string) => {
|
|
202
|
+
if (typeaheadTimerRef.current) clearTimeout(typeaheadTimerRef.current);
|
|
203
|
+
typeaheadBufferRef.current += char.toLowerCase();
|
|
204
|
+
const buffer = typeaheadBufferRef.current;
|
|
205
|
+
// When activeIndex is -1 (no current cursor — typeahead opening the menu
|
|
206
|
+
// from the closed state), use -1 as the start so the `+1` first-char
|
|
207
|
+
// offset lands on index 0 instead of skipping past it.
|
|
208
|
+
const start = ctx.activeIndex >= 0 ? ctx.activeIndex : -1;
|
|
209
|
+
const len = ctx.rows.length;
|
|
210
|
+
for (let i = 0; i < len; i++) {
|
|
211
|
+
const idx = (start + i + (buffer.length === 1 ? 1 : 0)) % len;
|
|
212
|
+
const r = ctx.rows[idx];
|
|
213
|
+
if (r.kind !== 'option' || r.option.disabled) continue;
|
|
214
|
+
if (r.option.label.toLowerCase().startsWith(buffer)) {
|
|
215
|
+
ctx.setActiveIndex(idx);
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
typeaheadTimerRef.current = setTimeout(() => {
|
|
220
|
+
typeaheadBufferRef.current = '';
|
|
221
|
+
}, 500);
|
|
222
|
+
},
|
|
223
|
+
[ctx],
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
const handleNavKey = (e: KeyboardEvent<HTMLElement>): boolean => {
|
|
227
|
+
if (opts.disabled || opts.readOnly) return false;
|
|
228
|
+
|
|
229
|
+
if (!ctx.open) {
|
|
230
|
+
if (e.key === 'ArrowDown' || e.key === 'Enter') {
|
|
231
|
+
e.preventDefault();
|
|
232
|
+
ctx.setOpen(true);
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
if (e.key === 'ArrowUp') {
|
|
236
|
+
e.preventDefault();
|
|
237
|
+
ctx.setOpen(true);
|
|
238
|
+
// Override the Listbox open-effect's "first selectable" pick in
|
|
239
|
+
// a microtask so this runs after Listbox mounts and sets it.
|
|
240
|
+
queueMicrotask(() => ctx.setActiveIndex(lastSelectableIdx));
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
switch (e.key) {
|
|
247
|
+
case 'ArrowDown':
|
|
248
|
+
e.preventDefault();
|
|
249
|
+
moveActive(+1);
|
|
250
|
+
return true;
|
|
251
|
+
case 'ArrowUp':
|
|
252
|
+
e.preventDefault();
|
|
253
|
+
moveActive(-1);
|
|
254
|
+
return true;
|
|
255
|
+
case 'Home':
|
|
256
|
+
e.preventDefault();
|
|
257
|
+
if (firstSelectableIdx >= 0) ctx.setActiveIndex(firstSelectableIdx);
|
|
258
|
+
return true;
|
|
259
|
+
case 'End':
|
|
260
|
+
e.preventDefault();
|
|
261
|
+
if (lastSelectableIdx >= 0) ctx.setActiveIndex(lastSelectableIdx);
|
|
262
|
+
return true;
|
|
263
|
+
case 'PageDown':
|
|
264
|
+
e.preventDefault();
|
|
265
|
+
for (let step = 0; step < 5; step++) moveActive(+1);
|
|
266
|
+
return true;
|
|
267
|
+
case 'PageUp':
|
|
268
|
+
e.preventDefault();
|
|
269
|
+
for (let step = 0; step < 5; step++) moveActive(-1);
|
|
270
|
+
return true;
|
|
271
|
+
case 'Enter': {
|
|
272
|
+
e.preventDefault();
|
|
273
|
+
const row = ctx.rows[ctx.activeIndex];
|
|
274
|
+
if (row && row.kind === 'option' && !row.option.disabled) {
|
|
275
|
+
// Creatable: when the active row is the synthetic "+ Create"
|
|
276
|
+
// row, fire `onCreate` and commit the new value. The sentinel
|
|
277
|
+
// is `data.__create === true`; inline-detect it here rather
|
|
278
|
+
// than importing `isCreateRow` to keep the keyboard handler
|
|
279
|
+
// self-contained.
|
|
280
|
+
if (
|
|
281
|
+
typeof row.option.data === 'object' &&
|
|
282
|
+
row.option.data !== null &&
|
|
283
|
+
(row.option.data as { __create?: boolean }).__create
|
|
284
|
+
) {
|
|
285
|
+
ctx.onCreate?.(row.option.label);
|
|
286
|
+
if (ctx.multiple) {
|
|
287
|
+
const next = [...((ctx.value as string[]) ?? []), row.option.value];
|
|
288
|
+
ctx.setValue(next);
|
|
289
|
+
if (ctx.searchable) ctx.setQuery('');
|
|
290
|
+
} else {
|
|
291
|
+
ctx.setValue(row.option.value);
|
|
292
|
+
ctx.closeAndFocusTrigger();
|
|
293
|
+
}
|
|
294
|
+
} else if (ctx.multiple) {
|
|
295
|
+
ctx.toggleValue(row.option.value);
|
|
296
|
+
if (ctx.searchable) ctx.setQuery('');
|
|
297
|
+
} else {
|
|
298
|
+
ctx.setValue(row.option.value);
|
|
299
|
+
ctx.closeAndFocusTrigger();
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
case 'Escape':
|
|
305
|
+
e.preventDefault();
|
|
306
|
+
ctx.closeAndFocusTrigger();
|
|
307
|
+
return true;
|
|
308
|
+
case 'Tab':
|
|
309
|
+
// No preventDefault — browser continues Tab traversal from the
|
|
310
|
+
// (now-closed) trigger to the next focusable element. Return
|
|
311
|
+
// false so the caller doesn't think it owns the keystroke.
|
|
312
|
+
ctx.setOpen(false);
|
|
313
|
+
return false;
|
|
314
|
+
default:
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
return { handleNavKey, stepTypeahead, moveActive };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
323
|
+
// ButtonTrigger — single, non-searchable; also the fallback for multi-mode
|
|
324
|
+
// before Phase 5/6 lands their dedicated triggers.
|
|
325
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Button-styled trigger for the single, non-searchable Select mode.
|
|
329
|
+
*
|
|
330
|
+
* Owns the keyboard interaction for the closed-and-open lifecycle:
|
|
331
|
+
* - Closed: ArrowDown / Enter / Space open with the Listbox's default
|
|
332
|
+
* "first selectable" active row. ArrowUp opens and overrides the
|
|
333
|
+
* active row to the last selectable (via a microtask so it runs
|
|
334
|
+
* AFTER Listbox's open-effect picks the first one).
|
|
335
|
+
* - Open: ArrowUp/Down step through rows (skipping headers + disabled),
|
|
336
|
+
* Home/End jump to ends, PageUp/Down move ±5, Enter/Space commit the
|
|
337
|
+
* active option (single mode → close; multi → toggle), Escape closes
|
|
338
|
+
* without changing selection, Tab closes (no preventDefault — normal
|
|
339
|
+
* browser traversal continues).
|
|
340
|
+
*
|
|
341
|
+
* Printable chars (no modifier keys) drive the 500ms typeahead buffer —
|
|
342
|
+
* mirrors the DropdownMenu pattern.
|
|
343
|
+
*/
|
|
344
|
+
function ButtonTrigger(props: TriggerProps) {
|
|
345
|
+
const ctx = useSelectContext('Trigger');
|
|
346
|
+
const t = useTranslation();
|
|
347
|
+
const { handleNavKey, stepTypeahead } = useTriggerKeyboard({
|
|
348
|
+
disabled: props.disabled,
|
|
349
|
+
readOnly: props.readOnly,
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// Resolve the visible label string for both single and multi modes.
|
|
353
|
+
//
|
|
354
|
+
// Multi: walk `ctx.allRows` (NOT `ctx.rows` which is filtered when
|
|
355
|
+
// searchable) and comma-join the labels of every selected value so chip
|
|
356
|
+
// labels don't drop out of the summary when the user types a filter
|
|
357
|
+
// query that excludes them.
|
|
358
|
+
//
|
|
359
|
+
// Single: `allRows` lookup — keeps the closed-state label visible while
|
|
360
|
+
// the user is filtering in the open searchable variant (which actually
|
|
361
|
+
// renders the combobox-input trigger, but symmetry here is harmless and
|
|
362
|
+
// keeps the resolution logic in one place).
|
|
363
|
+
const selectedOption = findSelectedOption(ctx.allRows, ctx.value, ctx.multiple);
|
|
364
|
+
const label = (() => {
|
|
365
|
+
if (ctx.multiple) {
|
|
366
|
+
const selectedValues = Array.isArray(ctx.value) ? ctx.value : [];
|
|
367
|
+
if (selectedValues.length === 0) return '';
|
|
368
|
+
const labels: string[] = [];
|
|
369
|
+
for (const row of ctx.allRows) {
|
|
370
|
+
if (row.kind === 'option' && selectedValues.includes(row.option.value)) {
|
|
371
|
+
labels.push(row.option.label);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return labels.join(', ');
|
|
375
|
+
}
|
|
376
|
+
return selectedOption?.label ?? '';
|
|
377
|
+
})();
|
|
378
|
+
// Derived from whether an option MATCHED, not from whether the resolved
|
|
379
|
+
// label happens to be non-empty — a selected `value: ''` row (issue #470)
|
|
380
|
+
// has a real label and must not render placeholder-styled.
|
|
381
|
+
const hasValue = ctx.multiple ? label !== '' : selectedOption !== null;
|
|
382
|
+
|
|
383
|
+
const activeOptionId = useActiveOptionId();
|
|
384
|
+
|
|
385
|
+
// When multi-summary text overflows it's truncated with CSS ellipsis,
|
|
386
|
+
// so screen-reader users would miss the tail of the selection. Surface
|
|
387
|
+
// the full comma-joined list via aria-label, but ONLY when the consumer
|
|
388
|
+
// didn't pass their own aria-label — their string is authoritative.
|
|
389
|
+
const computedAriaLabel = (() => {
|
|
390
|
+
if (props['aria-label']) return props['aria-label'];
|
|
391
|
+
if (ctx.multiple && label) return t('select.selectedPrefix', { labels: label });
|
|
392
|
+
// An option may carry an empty label. It is still a selection — the
|
|
393
|
+
// rendered gate below uses `hasValue` so it doesn't show placeholder text
|
|
394
|
+
// over a checked row — but the trigger must not end up with NO accessible
|
|
395
|
+
// name at all, so the placeholder remains the naming fallback.
|
|
396
|
+
return label || props.placeholder || undefined;
|
|
397
|
+
})();
|
|
398
|
+
|
|
399
|
+
const handleKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => {
|
|
400
|
+
if (props.disabled || props.readOnly) return;
|
|
401
|
+
|
|
402
|
+
// Nav keys (Arrow/Home/End/Page/Enter/Escape/Tab) — let the shared
|
|
403
|
+
// hook handle. Returns true if it claimed the keystroke.
|
|
404
|
+
if (handleNavKey(e)) return;
|
|
405
|
+
|
|
406
|
+
// Space: same semantics as Enter (open-or-commit). The shared hook
|
|
407
|
+
// doesn't claim Space because the combobox-input variant needs it
|
|
408
|
+
// as a literal character in the query string.
|
|
409
|
+
if (e.key === ' ') {
|
|
410
|
+
e.preventDefault();
|
|
411
|
+
if (!ctx.open) {
|
|
412
|
+
ctx.setOpen(true);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
const row = ctx.rows[ctx.activeIndex];
|
|
416
|
+
if (row && row.kind === 'option' && !row.option.disabled) {
|
|
417
|
+
if (ctx.multiple) {
|
|
418
|
+
ctx.toggleValue(row.option.value);
|
|
419
|
+
} else {
|
|
420
|
+
ctx.setValue(row.option.value);
|
|
421
|
+
ctx.closeAndFocusTrigger();
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Printable char (no modifiers) → typeahead. Opens the menu first if
|
|
428
|
+
// closed so the user sees the highlighted option.
|
|
429
|
+
if (e.key.length === 1 && !e.metaKey && !e.ctrlKey && !e.altKey) {
|
|
430
|
+
e.preventDefault();
|
|
431
|
+
if (!ctx.open) ctx.setOpen(true);
|
|
432
|
+
stepTypeahead(e.key);
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
// Not `hasValue`: ✕ resets to the empty value, so it's shown when the
|
|
437
|
+
// current value differs from that — including for a value with no
|
|
438
|
+
// matching option (a stale id), which the user must still be able to
|
|
439
|
+
// clear, and excluding a selected `value: ''` option, which already IS
|
|
440
|
+
// the cleared state.
|
|
441
|
+
const showClear = props.clearable && selectHasValue(ctx.value, ctx.multiple);
|
|
442
|
+
|
|
443
|
+
// Resolve the visible label node. In single mode, `renderValue` (if
|
|
444
|
+
// supplied) replaces the bare string with whatever the consumer returns.
|
|
445
|
+
// Multi-summary deliberately stays as the comma-joined string — applying
|
|
446
|
+
// `renderValue` to it would mean passing N options, which doesn't fit the
|
|
447
|
+
// single-option signature; for richer multi labels use `renderTag` on
|
|
448
|
+
// chips mode instead.
|
|
449
|
+
//
|
|
450
|
+
// The accessible-name computation falls back to the option label string,
|
|
451
|
+
// not the rendered node, so screen readers still hear "Pending" even if
|
|
452
|
+
// the consumer wraps it in decoration.
|
|
453
|
+
const labelNode: React.ReactNode = (() => {
|
|
454
|
+
// Gated on `hasValue`, not on `label` being truthy: an option may
|
|
455
|
+
// legitimately carry an empty label, and gating the two on different
|
|
456
|
+
// predicates would drop the placeholder styling while still rendering
|
|
457
|
+
// placeholder text.
|
|
458
|
+
if (!hasValue) return null;
|
|
459
|
+
if (!ctx.multiple && ctx.renderValue && selectedOption) {
|
|
460
|
+
return ctx.renderValue(selectedOption as SelectOption);
|
|
461
|
+
}
|
|
462
|
+
return label;
|
|
463
|
+
})();
|
|
464
|
+
|
|
465
|
+
return (
|
|
466
|
+
<div ref={ctx.triggerRootRef as Ref<HTMLDivElement>} className={styles.triggerWrap}>
|
|
467
|
+
<button
|
|
468
|
+
type="button"
|
|
469
|
+
role="combobox"
|
|
470
|
+
id={ctx.triggerId}
|
|
471
|
+
ref={ctx.triggerRef as Ref<HTMLButtonElement>}
|
|
472
|
+
className={clsx(styles.trigger, styles.triggerButton, !hasValue && styles.placeholder)}
|
|
473
|
+
aria-haspopup="listbox"
|
|
474
|
+
aria-expanded={ctx.open}
|
|
475
|
+
aria-controls={ctx.open ? ctx.listboxId : undefined}
|
|
476
|
+
aria-activedescendant={activeOptionId}
|
|
477
|
+
aria-label={computedAriaLabel}
|
|
478
|
+
aria-labelledby={props['aria-labelledby']}
|
|
479
|
+
aria-describedby={props['aria-describedby']}
|
|
480
|
+
aria-required={props['aria-required']}
|
|
481
|
+
aria-invalid={props.invalid || undefined}
|
|
482
|
+
aria-readonly={props.readOnly || undefined}
|
|
483
|
+
disabled={props.disabled}
|
|
484
|
+
onClick={() => {
|
|
485
|
+
if (props.disabled || props.readOnly) return;
|
|
486
|
+
ctx.setOpen(!ctx.open);
|
|
487
|
+
}}
|
|
488
|
+
onKeyDown={handleKeyDown}
|
|
489
|
+
>
|
|
490
|
+
{/* Gated on `hasValue`, not on the label being truthy: an option may
|
|
491
|
+
legitimately carry an empty label, and it is still a selection —
|
|
492
|
+
rendering the placeholder there would say "nothing is selected"
|
|
493
|
+
about a row the listbox shows as checked. */}
|
|
494
|
+
{hasValue ? (
|
|
495
|
+
<span>{labelNode}</span>
|
|
496
|
+
) : (
|
|
497
|
+
<span className={styles.placeholder}>{props.placeholder ?? ''}</span>
|
|
498
|
+
)}
|
|
499
|
+
</button>
|
|
500
|
+
{showClear && <ClearButton variant="overlay" />}
|
|
501
|
+
<span className={styles.chevron} aria-hidden="true">
|
|
502
|
+
▾
|
|
503
|
+
</span>
|
|
504
|
+
</div>
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// `renderValue` deliberately does NOT apply to the ComboboxInputTrigger
|
|
509
|
+
// variant: the input's `value` prop must be a string, but `renderValue`
|
|
510
|
+
// returns a ReactNode. Consumers using `searchable` who want decorated
|
|
511
|
+
// labels should look at non-searchable mode or compose at the option level.
|
|
512
|
+
|
|
513
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
514
|
+
// ComboboxInputTrigger — single + searchable (WAI-ARIA combobox 1.2
|
|
515
|
+
// "input as trigger" pattern). Headless UI Combobox shape: focusing the
|
|
516
|
+
// input does NOT auto-open; clicking the input opens; typing opens and
|
|
517
|
+
// filters; Escape reverts the query to the selected label.
|
|
518
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
519
|
+
|
|
520
|
+
function ComboboxInputTrigger(props: TriggerProps) {
|
|
521
|
+
const ctx = useSelectContext('Trigger');
|
|
522
|
+
const { handleNavKey } = useTriggerKeyboard({
|
|
523
|
+
disabled: props.disabled,
|
|
524
|
+
readOnly: props.readOnly,
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
// Resolve the selected label from `allRows` (NOT `rows`). When the user
|
|
528
|
+
// types "arc" and the selected option "Pending" filters out of `rows`,
|
|
529
|
+
// we still need its label as the closed-state display + Escape fallback.
|
|
530
|
+
//
|
|
531
|
+
// Lookup-driven, never `value === ''`-driven: a consumer's sentinel
|
|
532
|
+
// `value: ''` option is a real selection and must display its label
|
|
533
|
+
// (issue #470).
|
|
534
|
+
const selectedOption = useMemo(
|
|
535
|
+
() => findSelectedOption(ctx.allRows, ctx.value, ctx.multiple),
|
|
536
|
+
[ctx.allRows, ctx.value, ctx.multiple],
|
|
537
|
+
);
|
|
538
|
+
const selectedLabel = selectedOption?.label ?? '';
|
|
539
|
+
const hasSelection = selectedOption !== null;
|
|
540
|
+
|
|
541
|
+
// Reset the query to '' whenever the listbox closes, so the next open
|
|
542
|
+
// shows the full option list (instead of the stale filter) and the
|
|
543
|
+
// input falls back to displaying the selected label.
|
|
544
|
+
//
|
|
545
|
+
// Depends only on `ctx.open` (not query / setQuery) — the goal is to
|
|
546
|
+
// run exactly once per close transition; including `ctx.query` in the
|
|
547
|
+
// deps would re-fire on every keystroke and wipe the user's input.
|
|
548
|
+
useEffect(() => {
|
|
549
|
+
if (!ctx.open) {
|
|
550
|
+
ctx.setQuery('');
|
|
551
|
+
}
|
|
552
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
553
|
+
}, [ctx.open]);
|
|
554
|
+
|
|
555
|
+
// Display rules:
|
|
556
|
+
// - closed → always show the selected label (query is reset to '' by
|
|
557
|
+
// the effect below when `ctx.open` flips false).
|
|
558
|
+
// - open & query is empty → show the selected label as a "ghost"
|
|
559
|
+
// starting value. The first onChange will detect the user typing
|
|
560
|
+
// "over" this label and strip the prefix.
|
|
561
|
+
// - open & query is non-empty → show the live query.
|
|
562
|
+
//
|
|
563
|
+
// This mirrors Headless UI's Combobox where the selected label sits
|
|
564
|
+
// in the input until the user starts editing, then is replaced.
|
|
565
|
+
const displayValue = ctx.open && ctx.query !== '' ? ctx.query : selectedLabel;
|
|
566
|
+
|
|
567
|
+
const activeOptionId = useActiveOptionId();
|
|
568
|
+
|
|
569
|
+
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
|
|
570
|
+
if (props.disabled || props.readOnly) return;
|
|
571
|
+
// Nav keys (Arrow/Home/End/Page/Enter/Escape/Tab). Crucially we do
|
|
572
|
+
// NOT layer typeahead on top — typing IS the filter mechanism here,
|
|
573
|
+
// handled by `onChange` below. Space is a literal char in the query
|
|
574
|
+
// and must reach the input's default `change` event, so the shared
|
|
575
|
+
// hook deliberately doesn't claim it.
|
|
576
|
+
handleNavKey(e);
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
580
|
+
if (props.disabled || props.readOnly) return;
|
|
581
|
+
let next = e.target.value;
|
|
582
|
+
// When the input was sitting on the selected label (closed, or open
|
|
583
|
+
// with an empty query), the browser's native key handling appends
|
|
584
|
+
// the typed char — producing `"<selectedLabel><typed>"`. Strip the
|
|
585
|
+
// prefix so the user's *intent* (replace, not append) wins.
|
|
586
|
+
if (ctx.query === '' && selectedLabel !== '' && next.startsWith(selectedLabel)) {
|
|
587
|
+
next = next.slice(selectedLabel.length);
|
|
588
|
+
}
|
|
589
|
+
if (!ctx.open) ctx.setOpen(true);
|
|
590
|
+
ctx.setQuery(next);
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
// `ctx.triggerRef` is typed as `RefObject<HTMLElement | null>`. Cast at
|
|
594
|
+
// the assignment site rather than widening the context shape; both
|
|
595
|
+
// <button> and <input> are HTMLElement and the callers that read this
|
|
596
|
+
// ref only need `.focus()` / `.contains()` which are on HTMLElement.
|
|
597
|
+
const inputRefHolder = ctx.triggerRef as MutableRefObject<HTMLInputElement | null>;
|
|
598
|
+
|
|
599
|
+
// On open, optionally select the visible text so the user can immediately
|
|
600
|
+
// type to replace the selected label (type-to-search). Gated on an empty
|
|
601
|
+
// query so opening BY TYPING (handleChange sets the query then opens) doesn't
|
|
602
|
+
// re-select + clobber that first character — only the label-showing open
|
|
603
|
+
// (click / keyboard) selects. Runs once per open transition.
|
|
604
|
+
useEffect(() => {
|
|
605
|
+
if (ctx.open && props.selectOnOpen && ctx.query === '') inputRefHolder.current?.select();
|
|
606
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
607
|
+
}, [ctx.open]);
|
|
608
|
+
|
|
609
|
+
const showClear = props.clearable && selectHasValue(ctx.value, ctx.multiple);
|
|
610
|
+
|
|
611
|
+
return (
|
|
612
|
+
<div ref={ctx.triggerRootRef as Ref<HTMLDivElement>} className={styles.triggerWrap}>
|
|
613
|
+
<input
|
|
614
|
+
type="text"
|
|
615
|
+
id={ctx.triggerId}
|
|
616
|
+
ref={(node) => {
|
|
617
|
+
inputRefHolder.current = node;
|
|
618
|
+
}}
|
|
619
|
+
className={clsx(styles.trigger, styles.triggerInput, !hasSelection && styles.placeholder)}
|
|
620
|
+
role="combobox"
|
|
621
|
+
aria-autocomplete="list"
|
|
622
|
+
aria-haspopup="listbox"
|
|
623
|
+
aria-expanded={ctx.open}
|
|
624
|
+
aria-controls={ctx.open ? ctx.listboxId : undefined}
|
|
625
|
+
aria-activedescendant={activeOptionId}
|
|
626
|
+
aria-label={props['aria-label']}
|
|
627
|
+
aria-labelledby={props['aria-labelledby']}
|
|
628
|
+
aria-describedby={props['aria-describedby']}
|
|
629
|
+
aria-required={props['aria-required']}
|
|
630
|
+
aria-invalid={props.invalid || undefined}
|
|
631
|
+
aria-readonly={props.readOnly || undefined}
|
|
632
|
+
autoComplete="off"
|
|
633
|
+
spellCheck={false}
|
|
634
|
+
disabled={props.disabled}
|
|
635
|
+
readOnly={props.readOnly}
|
|
636
|
+
value={displayValue}
|
|
637
|
+
placeholder={hasSelection ? undefined : props.placeholder}
|
|
638
|
+
onClick={() => {
|
|
639
|
+
if (props.disabled || props.readOnly) return;
|
|
640
|
+
// Click opens but does NOT toggle — matches Headless UI Combobox
|
|
641
|
+
// and avoids the surprising "click the focused input to close
|
|
642
|
+
// it" behaviour.
|
|
643
|
+
if (!ctx.open) ctx.setOpen(true);
|
|
644
|
+
}}
|
|
645
|
+
onChange={handleChange}
|
|
646
|
+
onKeyDown={handleKeyDown}
|
|
647
|
+
/>
|
|
648
|
+
{showClear && <ClearButton variant="overlay" />}
|
|
649
|
+
<span className={styles.chevron} aria-hidden="true">
|
|
650
|
+
▾
|
|
651
|
+
</span>
|
|
652
|
+
</div>
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
657
|
+
// ChipsInputTrigger — multi + chips + searchable. Selected options render as
|
|
658
|
+
// removable chips alongside an inline `<input role="combobox">` that owns
|
|
659
|
+
// the live filter query. Backspace on an empty input removes the trailing
|
|
660
|
+
// chip (matches GitHub label picker, Material UI Autocomplete multiple,
|
|
661
|
+
// Headless UI tag inputs). Pattern reference: WAI-ARIA APG combobox + the
|
|
662
|
+
// de-facto tag-input convention.
|
|
663
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Chips trigger with an inline `role="combobox"` input. Wrapper is a plain
|
|
667
|
+
* `<div>` (no role) because the input IS the WAI-ARIA combobox; assigning
|
|
668
|
+
* `role="button"` to the wrapper would create two competing focus targets.
|
|
669
|
+
* The visual wrapper owns positioning and outside-click containment while
|
|
670
|
+
* the input remains the semantic combobox and focus-restoration target.
|
|
671
|
+
*
|
|
672
|
+
* Backspace behaviour: removes the trailing chip ONLY when `ctx.query === ''`.
|
|
673
|
+
* On non-empty input it falls through to the browser's native text-deletion.
|
|
674
|
+
*
|
|
675
|
+
* Enter behaviour: `useTriggerKeyboard.handleNavKey` toggles the active
|
|
676
|
+
* option in multi mode but does NOT clear the query — we clear it here in
|
|
677
|
+
* a microtask so the input is empty for the next pick. Listbox's option
|
|
678
|
+
* click handler also clears the query for multi+searchable so mouse
|
|
679
|
+
* selection follows the same UX.
|
|
680
|
+
*/
|
|
681
|
+
function ChipsInputTrigger(props: TriggerProps) {
|
|
682
|
+
const ctx = useSelectContext('Trigger');
|
|
683
|
+
const { handleNavKey } = useTriggerKeyboard({
|
|
684
|
+
disabled: props.disabled,
|
|
685
|
+
readOnly: props.readOnly,
|
|
686
|
+
});
|
|
687
|
+
const activeOptionId = useActiveOptionId();
|
|
688
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
689
|
+
|
|
690
|
+
const selectedValues = Array.isArray(ctx.value) ? (ctx.value as string[]) : [];
|
|
691
|
+
// Walk `selectedValues` (selection order — `ctx.value` appends new picks
|
|
692
|
+
// to the end via `useSelectState.toggleValue`) so the latest selected chip
|
|
693
|
+
// renders last. Look options up from `allRows` so labels persist while
|
|
694
|
+
// typing filters `rows` down. Synthetic `{value, label: value}` fallback
|
|
695
|
+
// covers creatable values not yet in `allRows`.
|
|
696
|
+
const optionByValue = new Map<string, SelectOption>();
|
|
697
|
+
for (const row of ctx.allRows) {
|
|
698
|
+
if (row.kind === 'option') optionByValue.set(row.option.value, row.option as SelectOption);
|
|
699
|
+
}
|
|
700
|
+
const selectedOptions: SelectOption[] = selectedValues.map(
|
|
701
|
+
(v) => optionByValue.get(v) ?? { value: v, label: v },
|
|
702
|
+
);
|
|
703
|
+
|
|
704
|
+
const handleWrapperPointerDown = (e: PointerEvent<HTMLDivElement>) => {
|
|
705
|
+
if (props.disabled || props.readOnly) return;
|
|
706
|
+
// Redirect any pointer landing on the wrapper itself (gap between
|
|
707
|
+
// chips, padding area) into the input so the caret position stays
|
|
708
|
+
// predictable. Don't steal events that already target the input —
|
|
709
|
+
// the browser owns selection/caret placement there.
|
|
710
|
+
if (e.target !== inputRef.current) {
|
|
711
|
+
e.preventDefault();
|
|
712
|
+
inputRef.current?.focus();
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
const handleWrapperClick = () => {
|
|
717
|
+
if (props.disabled || props.readOnly) return;
|
|
718
|
+
inputRef.current?.focus();
|
|
719
|
+
if (!ctx.open) ctx.setOpen(true);
|
|
720
|
+
};
|
|
721
|
+
|
|
722
|
+
const handleInputKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
|
|
723
|
+
if (props.disabled || props.readOnly) return;
|
|
724
|
+
// Enter handling is layered: handleNavKey toggles in multi mode but
|
|
725
|
+
// doesn't clear the query. Schedule the clear AFTER the toggle so the
|
|
726
|
+
// input is empty for the next selection. Microtask + open-guard so we
|
|
727
|
+
// don't blow away a query if the user pressed Enter while closed.
|
|
728
|
+
if (e.key === 'Enter' && ctx.open) {
|
|
729
|
+
queueMicrotask(() => ctx.setQuery(''));
|
|
730
|
+
}
|
|
731
|
+
if (handleNavKey(e)) return;
|
|
732
|
+
// Backspace on empty input removes the trailing chip. On non-empty
|
|
733
|
+
// input we DO NOT preventDefault — the browser handles char deletion.
|
|
734
|
+
if (e.key === 'Backspace' && ctx.query === '' && selectedOptions.length > 0) {
|
|
735
|
+
e.preventDefault();
|
|
736
|
+
const last = selectedOptions[selectedOptions.length - 1];
|
|
737
|
+
ctx.toggleValue(last.value);
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
const inputRefHolder = ctx.triggerRef as MutableRefObject<HTMLInputElement | null>;
|
|
742
|
+
|
|
743
|
+
return (
|
|
744
|
+
<div
|
|
745
|
+
ref={ctx.triggerRootRef as Ref<HTMLDivElement>}
|
|
746
|
+
className={clsx(styles.trigger, styles.triggerChips, styles.triggerChipsInput)}
|
|
747
|
+
onClick={handleWrapperClick}
|
|
748
|
+
onPointerDown={handleWrapperPointerDown}
|
|
749
|
+
>
|
|
750
|
+
{selectedOptions.map((o) => {
|
|
751
|
+
const remove = () => {
|
|
752
|
+
if (props.disabled || props.readOnly) return;
|
|
753
|
+
ctx.toggleValue(o.value);
|
|
754
|
+
inputRef.current?.focus();
|
|
755
|
+
};
|
|
756
|
+
if (ctx.renderTag) {
|
|
757
|
+
// Wrap in a keyed Fragment so React can track ordering. The
|
|
758
|
+
// consumer owns the inner markup (click handlers, styling, etc.).
|
|
759
|
+
return <span key={o.value}>{ctx.renderTag(o, remove)}</span>;
|
|
760
|
+
}
|
|
761
|
+
return <Chip key={o.value} label={o.label} disabled={props.disabled} onRemove={remove} />;
|
|
762
|
+
})}
|
|
763
|
+
<input
|
|
764
|
+
ref={(node) => {
|
|
765
|
+
inputRef.current = node;
|
|
766
|
+
inputRefHolder.current = node;
|
|
767
|
+
}}
|
|
768
|
+
type="text"
|
|
769
|
+
id={ctx.triggerId}
|
|
770
|
+
role="combobox"
|
|
771
|
+
aria-autocomplete="list"
|
|
772
|
+
aria-haspopup="listbox"
|
|
773
|
+
aria-expanded={ctx.open}
|
|
774
|
+
aria-controls={ctx.open ? ctx.listboxId : undefined}
|
|
775
|
+
aria-activedescendant={activeOptionId}
|
|
776
|
+
aria-label={props['aria-label']}
|
|
777
|
+
aria-labelledby={props['aria-labelledby']}
|
|
778
|
+
aria-describedby={props['aria-describedby']}
|
|
779
|
+
aria-required={props['aria-required']}
|
|
780
|
+
aria-invalid={props.invalid || undefined}
|
|
781
|
+
disabled={props.disabled}
|
|
782
|
+
readOnly={props.readOnly}
|
|
783
|
+
autoComplete="off"
|
|
784
|
+
spellCheck={false}
|
|
785
|
+
className={styles.chipsInput}
|
|
786
|
+
placeholder={selectedOptions.length === 0 ? props.placeholder : undefined}
|
|
787
|
+
value={ctx.query}
|
|
788
|
+
onChange={(e) => {
|
|
789
|
+
if (!ctx.open) ctx.setOpen(true);
|
|
790
|
+
ctx.setQuery(e.target.value);
|
|
791
|
+
}}
|
|
792
|
+
onKeyDown={handleInputKeyDown}
|
|
793
|
+
/>
|
|
794
|
+
{props.clearable && selectedOptions.length > 0 && <ClearButton variant="inline" />}
|
|
795
|
+
</div>
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
800
|
+
// ChipsButtonTrigger — multi + chips + !searchable. Renders selected options
|
|
801
|
+
// as removable inline chips around a read-only combobox input. The visual
|
|
802
|
+
// wrapper is also the floating anchor and outside-click boundary.
|
|
803
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Chips trigger for multi-select without inline search. A dedicated read-only
|
|
807
|
+
* input owns combobox semantics and keyboard focus so chip remove buttons are
|
|
808
|
+
* siblings rather than descendants of the combobox. Chip resolution walks
|
|
809
|
+
* `ctx.allRows`, not `ctx.rows`, so the
|
|
810
|
+
* chip list stays stable even if a future hover-search filters the
|
|
811
|
+
* popover — unselected options never show as chips, but selected ones
|
|
812
|
+
* never disappear.
|
|
813
|
+
*
|
|
814
|
+
* Keyboard:
|
|
815
|
+
* - Closed: ArrowDown/Up/Enter open via the shared hook; printable chars
|
|
816
|
+
* open + start typeahead; Space opens.
|
|
817
|
+
* - Open: shared hook owns Arrow/Home/End/PageUp/Down/Enter/Escape/Tab.
|
|
818
|
+
* Enter toggles the active option without closing (multi semantics).
|
|
819
|
+
* - Backspace is NOT bound here (no input to detect emptiness against);
|
|
820
|
+
* the searchable variant handles it.
|
|
821
|
+
*/
|
|
822
|
+
function ChipsButtonTrigger(props: TriggerProps) {
|
|
823
|
+
const ctx = useSelectContext('Trigger');
|
|
824
|
+
const { handleNavKey, stepTypeahead } = useTriggerKeyboard({
|
|
825
|
+
disabled: props.disabled,
|
|
826
|
+
readOnly: props.readOnly,
|
|
827
|
+
});
|
|
828
|
+
const t = useTranslation();
|
|
829
|
+
const activeOptionId = useActiveOptionId();
|
|
830
|
+
|
|
831
|
+
const selectedValues = Array.isArray(ctx.value) ? (ctx.value as string[]) : [];
|
|
832
|
+
// Walk `selectedValues` (selection order — `ctx.value` appends new picks
|
|
833
|
+
// to the end via `useSelectState.toggleValue`) so the latest selected chip
|
|
834
|
+
// renders last. Look options up from `allRows` so labels persist while
|
|
835
|
+
// searching. Synthetic `{value, label: value}` fallback covers creatable
|
|
836
|
+
// values not yet in `allRows`.
|
|
837
|
+
const optionByValue = new Map<string, SelectOption>();
|
|
838
|
+
for (const row of ctx.allRows) {
|
|
839
|
+
if (row.kind === 'option') optionByValue.set(row.option.value, row.option as SelectOption);
|
|
840
|
+
}
|
|
841
|
+
const selectedOptions: SelectOption[] = selectedValues.map(
|
|
842
|
+
(v) => optionByValue.get(v) ?? { value: v, label: v },
|
|
843
|
+
);
|
|
844
|
+
|
|
845
|
+
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
|
|
846
|
+
if (props.disabled || props.readOnly) return;
|
|
847
|
+
if (handleNavKey(e)) return;
|
|
848
|
+
if (e.key === ' ') {
|
|
849
|
+
e.preventDefault();
|
|
850
|
+
if (!ctx.open) ctx.setOpen(true);
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
if (e.key.length === 1 && !e.metaKey && !e.ctrlKey && !e.altKey) {
|
|
854
|
+
e.preventDefault();
|
|
855
|
+
if (!ctx.open) ctx.setOpen(true);
|
|
856
|
+
stepTypeahead(e.key);
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
|
|
860
|
+
// Mirrors ButtonTrigger's computedAriaLabel pattern: consumer's
|
|
861
|
+
// aria-label wins; otherwise synthesize from selections so screen
|
|
862
|
+
// readers hear the full list even when chips overflow visually.
|
|
863
|
+
const labelForAria = selectedOptions.map((o) => o.label).join(', ');
|
|
864
|
+
const computedAriaLabel =
|
|
865
|
+
props['aria-label'] ||
|
|
866
|
+
(selectedOptions.length > 0
|
|
867
|
+
? t('select.selectedPrefix', { labels: labelForAria })
|
|
868
|
+
: t('select.openSelect'));
|
|
869
|
+
|
|
870
|
+
const handleWrapperClick = () => {
|
|
871
|
+
if (props.disabled || props.readOnly) return;
|
|
872
|
+
ctx.triggerRef.current?.focus();
|
|
873
|
+
ctx.setOpen(!ctx.open);
|
|
874
|
+
};
|
|
875
|
+
|
|
876
|
+
return (
|
|
877
|
+
<div
|
|
878
|
+
ref={ctx.triggerRootRef as Ref<HTMLDivElement>}
|
|
879
|
+
className={clsx(styles.trigger, styles.triggerChips)}
|
|
880
|
+
onClick={handleWrapperClick}
|
|
881
|
+
>
|
|
882
|
+
{selectedOptions.map((o) => {
|
|
883
|
+
const remove = () => {
|
|
884
|
+
if (props.disabled || props.readOnly) return;
|
|
885
|
+
ctx.toggleValue(o.value);
|
|
886
|
+
};
|
|
887
|
+
if (ctx.renderTag) {
|
|
888
|
+
return <span key={o.value}>{ctx.renderTag(o, remove)}</span>;
|
|
889
|
+
}
|
|
890
|
+
return <Chip key={o.value} label={o.label} disabled={props.disabled} onRemove={remove} />;
|
|
891
|
+
})}
|
|
892
|
+
{selectedOptions.length === 0 && (
|
|
893
|
+
<span aria-hidden="true" className={styles.placeholder}>
|
|
894
|
+
{props.placeholder ?? ''}
|
|
895
|
+
</span>
|
|
896
|
+
)}
|
|
897
|
+
<input
|
|
898
|
+
ref={ctx.triggerRef as Ref<HTMLInputElement>}
|
|
899
|
+
id={ctx.triggerId}
|
|
900
|
+
type="text"
|
|
901
|
+
role="combobox"
|
|
902
|
+
readOnly
|
|
903
|
+
tabIndex={props.disabled ? -1 : 0}
|
|
904
|
+
className={styles.chipsInput}
|
|
905
|
+
aria-haspopup="listbox"
|
|
906
|
+
aria-expanded={ctx.open}
|
|
907
|
+
aria-controls={ctx.open ? ctx.listboxId : undefined}
|
|
908
|
+
aria-activedescendant={activeOptionId}
|
|
909
|
+
aria-label={computedAriaLabel}
|
|
910
|
+
aria-labelledby={props['aria-labelledby']}
|
|
911
|
+
aria-describedby={props['aria-describedby']}
|
|
912
|
+
aria-required={props['aria-required']}
|
|
913
|
+
aria-invalid={props.invalid || undefined}
|
|
914
|
+
aria-readonly={props.readOnly || undefined}
|
|
915
|
+
aria-disabled={props.disabled || undefined}
|
|
916
|
+
value=""
|
|
917
|
+
onChange={() => undefined}
|
|
918
|
+
onKeyDown={handleKeyDown}
|
|
919
|
+
/>
|
|
920
|
+
{props.clearable && selectedOptions.length > 0 && <ClearButton variant="inline" />}
|
|
921
|
+
</div>
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
926
|
+
// Dispatcher — picks the variant based on context flags. The public
|
|
927
|
+
// `Trigger` export is what `<Select>` renders; the variants stay module-
|
|
928
|
+
// local so the future Phase 5/6 multi-mode variants slot in here too.
|
|
929
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
930
|
+
|
|
931
|
+
/**
|
|
932
|
+
* Internal dispatcher rendered by `<Select>`. Routes to the appropriate
|
|
933
|
+
* trigger variant based on mode flags from context.
|
|
934
|
+
*
|
|
935
|
+
* - `single + !searchable` → `ButtonTrigger`
|
|
936
|
+
* - `single + searchable` → `ComboboxInputTrigger`
|
|
937
|
+
* - `multi + chips + !searchable` → `ChipsButtonTrigger`
|
|
938
|
+
* - `multi + chips + searchable` → `ChipsInputTrigger` (Phase 6 task 15)
|
|
939
|
+
* - `multi + summary` (both forms) → `ButtonTrigger` w/ comma-joined labels
|
|
940
|
+
*/
|
|
941
|
+
export function Trigger(props: TriggerProps) {
|
|
942
|
+
const ctx = useSelectContext('Trigger');
|
|
943
|
+
if (!ctx.multiple && ctx.searchable) {
|
|
944
|
+
return <ComboboxInputTrigger {...props} />;
|
|
945
|
+
}
|
|
946
|
+
if (ctx.multiple && ctx.triggerDisplay === 'chips') {
|
|
947
|
+
return ctx.searchable ? <ChipsInputTrigger {...props} /> : <ChipsButtonTrigger {...props} />;
|
|
948
|
+
}
|
|
949
|
+
// Multi-summary (both searchable + non-searchable) renders as the
|
|
950
|
+
// comma-joined select-only combobox trigger.
|
|
951
|
+
return <ButtonTrigger {...props} />;
|
|
952
|
+
}
|