@eocrm/design-system 0.0.0-stage → 0.6.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AI-PRIMER.md +341 -0
- package/LICENSE +202 -0
- package/README.md +266 -2
- package/docs/components/Accordion.md +114 -0
- package/docs/components/AgentAvatar.md +58 -0
- package/docs/components/Alert.md +81 -0
- package/docs/components/AppLayout.md +70 -0
- package/docs/components/Avatar.md +49 -0
- package/docs/components/AvatarGroup.md +47 -0
- package/docs/components/Badge.md +60 -0
- package/docs/components/Banner.md +89 -0
- package/docs/components/BrandIcon.md +32 -0
- package/docs/components/Breadcrumb.md +72 -0
- package/docs/components/Button.md +107 -0
- package/docs/components/ButtonGroup.md +78 -0
- package/docs/components/Calendar.md +158 -0
- package/docs/components/Card.md +165 -0
- package/docs/components/CatalogPicker.md +83 -0
- package/docs/components/Chart.md +114 -0
- package/docs/components/ChatBubble.md +55 -0
- package/docs/components/ChatLauncher.md +97 -0
- package/docs/components/Checkbox.md +55 -0
- package/docs/components/CircularProgress.md +53 -0
- package/docs/components/Cluster.md +96 -0
- package/docs/components/Code.md +35 -0
- package/docs/components/CodeBlock.md +67 -0
- package/docs/components/ColorPicker.md +115 -0
- package/docs/components/ConfirmationPopover.md +70 -0
- package/docs/components/Constrain.md +39 -0
- package/docs/components/CurrencySelect.md +57 -0
- package/docs/components/CursorPagination.md +43 -0
- package/docs/components/DashboardCanvas.md +79 -0
- package/docs/components/DashboardWidget.md +131 -0
- package/docs/components/DataTable.md +88 -0
- package/docs/components/DatePicker.md +68 -0
- package/docs/components/DateRangePicker.md +71 -0
- package/docs/components/DateStrip.md +56 -0
- package/docs/components/DefinitionList.md +82 -0
- package/docs/components/Divider.md +57 -0
- package/docs/components/Dot.md +26 -0
- package/docs/components/Drawer.md +94 -0
- package/docs/components/DropdownMenu.md +299 -0
- package/docs/components/EmojiPicker.md +84 -0
- package/docs/components/EmptyState.md +55 -0
- package/docs/components/EntityChip.md +102 -0
- package/docs/components/ErrorState.md +72 -0
- package/docs/components/Field.md +91 -0
- package/docs/components/FileUpload.md +109 -0
- package/docs/components/FilterChip.md +88 -0
- package/docs/components/FlowCanvas.md +108 -0
- package/docs/components/FormRow.md +31 -0
- package/docs/components/FormSection.md +37 -0
- package/docs/components/Grid.md +103 -0
- package/docs/components/Highlight.md +75 -0
- package/docs/components/HighlightedText.md +50 -0
- package/docs/components/IconPicker.md +134 -0
- package/docs/components/IconTile.md +30 -0
- package/docs/components/Image.md +84 -0
- package/docs/components/ImageCrop.md +163 -0
- package/docs/components/Indent.md +31 -0
- package/docs/components/InlineDatePicker.md +58 -0
- package/docs/components/InlineDateRangePicker.md +57 -0
- package/docs/components/Input.md +54 -0
- package/docs/components/Kanban.md +122 -0
- package/docs/components/Kbd.md +41 -0
- package/docs/components/Lightbox.md +105 -0
- package/docs/components/Link.md +65 -0
- package/docs/components/LinkCard.md +41 -0
- package/docs/components/LiquidEditor.md +84 -0
- package/docs/components/List.md +68 -0
- package/docs/components/LiveRegion.md +29 -0
- package/docs/components/LocaleProvider.md +25 -0
- package/docs/components/Logo.md +41 -0
- package/docs/components/Masonry.md +48 -0
- package/docs/components/MediaTile.md +95 -0
- package/docs/components/Modal.md +107 -0
- package/docs/components/MoneyInput.md +62 -0
- package/docs/components/OptionsPicker.md +123 -0
- package/docs/components/OtpInput.md +65 -0
- package/docs/components/Page.md +35 -0
- package/docs/components/PageHeader.md +133 -0
- package/docs/components/Pagination.md +44 -0
- package/docs/components/Palette.md +32 -0
- package/docs/components/PasswordInput.md +45 -0
- package/docs/components/PasswordStrengthMeter.md +27 -0
- package/docs/components/PersonDisplay.md +153 -0
- package/docs/components/PhoneInput.md +54 -0
- package/docs/components/PillMenu.md +91 -0
- package/docs/components/Popover.md +127 -0
- package/docs/components/Progress.md +56 -0
- package/docs/components/QrCode.md +34 -0
- package/docs/components/Radio.md +37 -0
- package/docs/components/RadioGroup.md +56 -0
- package/docs/components/Rail.md +191 -0
- package/docs/components/RelativeDateInput.md +114 -0
- package/docs/components/RichText.md +44 -0
- package/docs/components/RichTextEditor.md +112 -0
- package/docs/components/Screen.md +37 -0
- package/docs/components/ScrollArea.md +39 -0
- package/docs/components/Select.md +131 -0
- package/docs/components/SettingRow.md +98 -0
- package/docs/components/Skeleton.md +68 -0
- package/docs/components/Slider.md +92 -0
- package/docs/components/SlotGrid.md +55 -0
- package/docs/components/SocialButton.md +31 -0
- package/docs/components/Sortable.md +127 -0
- package/docs/components/SortableGroup.md +52 -0
- package/docs/components/Sparkline.md +51 -0
- package/docs/components/Split.md +76 -0
- package/docs/components/Stack.md +59 -0
- package/docs/components/StagePath.md +66 -0
- package/docs/components/StatGroup.md +61 -0
- package/docs/components/StatTile.md +58 -0
- package/docs/components/Sticky.md +69 -0
- package/docs/components/Switch.md +89 -0
- package/docs/components/Table.md +184 -0
- package/docs/components/TableOfContents.md +76 -0
- package/docs/components/Tabs.md +94 -0
- package/docs/components/Text.md +63 -0
- package/docs/components/Textarea.md +59 -0
- package/docs/components/Thread.md +52 -0
- package/docs/components/TimeField.md +70 -0
- package/docs/components/Timeline.md +57 -0
- package/docs/components/Title.md +54 -0
- package/docs/components/ToastViewport.md +61 -0
- package/docs/components/Tooltip.md +63 -0
- package/docs/components/TopBar.md +214 -0
- package/docs/components/Tour.md +85 -0
- package/docs/components/TreeView.md +113 -0
- package/docs/components/VisuallyHidden.md +43 -0
- package/docs/components/WidgetPreview.md +54 -0
- package/docs/components/useBelowBreakpoint.md +11 -0
- package/docs/components/useMonth.md +18 -0
- package/docs/setup.md +51 -0
- package/docs/theming.md +95 -0
- package/docs/tokens.md +123 -0
- package/docs/transient-state.md +56 -0
- package/package.json +86 -4
- package/src/_meta/manifest.ts +293 -0
- package/src/app/AppProvider.tsx +78 -0
- package/src/app/index.ts +3 -0
- package/src/app/themeTokens.ts +95 -0
- package/src/calendar/dateMath.ts +114 -0
- package/src/calendar/formatters.ts +75 -0
- package/src/calendar/index.ts +39 -0
- package/src/calendar/types.ts +36 -0
- package/src/calendar/useAgenda.ts +70 -0
- package/src/calendar/useDay.ts +51 -0
- package/src/calendar/useMonth.ts +82 -0
- package/src/calendar/useWeek.ts +62 -0
- package/src/calendar/weekInfo.ts +83 -0
- package/src/components/Accordion/Accordion.module.scss +304 -0
- package/src/components/Accordion/Accordion.tokens.scss +64 -0
- package/src/components/Accordion/Accordion.tsx +272 -0
- package/src/components/Accordion/AccordionContent.tsx +36 -0
- package/src/components/Accordion/AccordionItem.tsx +66 -0
- package/src/components/Accordion/AccordionTrigger.tsx +121 -0
- package/src/components/Accordion/context.ts +47 -0
- package/src/components/Accordion/index.ts +14 -0
- package/src/components/Alert/Alert.module.scss +92 -0
- package/src/components/Alert/Alert.tokens.scss +60 -0
- package/src/components/Alert/Alert.tsx +136 -0
- package/src/components/Alert/index.ts +2 -0
- package/src/components/AppLayout/AppLayout.module.scss +130 -0
- package/src/components/AppLayout/AppLayout.tokens.scss +27 -0
- package/src/components/AppLayout/AppLayout.tsx +211 -0
- package/src/components/AppLayout/index.ts +2 -0
- package/src/components/Avatar/AgentAvatar.module.scss +117 -0
- package/src/components/Avatar/AgentAvatar.tsx +122 -0
- package/src/components/Avatar/Avatar.module.scss +181 -0
- package/src/components/Avatar/Avatar.tokens.scss +107 -0
- package/src/components/Avatar/Avatar.tsx +210 -0
- package/src/components/Avatar/AvatarGroup.module.scss +128 -0
- package/src/components/Avatar/AvatarGroup.tsx +119 -0
- package/src/components/Avatar/AvatarGroupContext.tsx +20 -0
- package/src/components/Avatar/index.ts +6 -0
- package/src/components/Badge/Badge.module.scss +124 -0
- package/src/components/Badge/Badge.tokens.scss +74 -0
- package/src/components/Badge/Badge.tsx +164 -0
- package/src/components/Badge/index.ts +2 -0
- package/src/components/Banner/Banner.module.scss +73 -0
- package/src/components/Banner/Banner.tokens.scss +51 -0
- package/src/components/Banner/Banner.tsx +138 -0
- package/src/components/Banner/index.ts +2 -0
- package/src/components/BrandIcon/BrandIcon.module.scss +5 -0
- package/src/components/BrandIcon/BrandIcon.tsx +99 -0
- package/src/components/BrandIcon/index.ts +2 -0
- package/src/components/Breadcrumb/Breadcrumb.module.scss +32 -0
- package/src/components/Breadcrumb/Breadcrumb.tokens.scss +9 -0
- package/src/components/Breadcrumb/Breadcrumb.tsx +146 -0
- package/src/components/Breadcrumb/index.ts +2 -0
- package/src/components/Button/Button.module.scss +167 -0
- package/src/components/Button/Button.tokens.scss +90 -0
- package/src/components/Button/Button.tsx +205 -0
- package/src/components/Button/index.ts +2 -0
- package/src/components/ButtonGroup/ButtonGroup.module.scss +134 -0
- package/src/components/ButtonGroup/ButtonGroup.tokens.scss +42 -0
- package/src/components/ButtonGroup/ButtonGroup.tsx +278 -0
- package/src/components/ButtonGroup/ButtonGroupItem.tsx +74 -0
- package/src/components/ButtonGroup/context.ts +45 -0
- package/src/components/ButtonGroup/index.ts +2 -0
- package/src/components/Calendar/AgendaView.module.scss +231 -0
- package/src/components/Calendar/AgendaView.tsx +214 -0
- package/src/components/Calendar/AllDayBand.module.scss +18 -0
- package/src/components/Calendar/AllDayBand.tsx +75 -0
- package/src/components/Calendar/Calendar.module.scss +23 -0
- package/src/components/Calendar/Calendar.tokens.scss +262 -0
- package/src/components/Calendar/Calendar.tsx +370 -0
- package/src/components/Calendar/DayCell.module.scss +30 -0
- package/src/components/Calendar/DayCell.tsx +50 -0
- package/src/components/Calendar/DayView.module.scss +10 -0
- package/src/components/Calendar/DayView.tsx +163 -0
- package/src/components/Calendar/EventChip.module.scss +171 -0
- package/src/components/Calendar/EventChip.tsx +115 -0
- package/src/components/Calendar/HourGrid.module.scss +123 -0
- package/src/components/Calendar/HourGrid.tsx +381 -0
- package/src/components/Calendar/MonthView.module.scss +99 -0
- package/src/components/Calendar/MonthView.tsx +296 -0
- package/src/components/Calendar/TimedEvent.module.scss +205 -0
- package/src/components/Calendar/TimedEvent.tsx +247 -0
- package/src/components/Calendar/ViewSwitcher.module.scss +3 -0
- package/src/components/Calendar/ViewSwitcher.tsx +47 -0
- package/src/components/Calendar/WeekView.module.scss +10 -0
- package/src/components/Calendar/WeekView.tsx +137 -0
- package/src/components/Calendar/eventColor.ts +60 -0
- package/src/components/Calendar/index.ts +18 -0
- package/src/components/Calendar/types.ts +451 -0
- package/src/components/Calendar/useEventDrag.ts +783 -0
- package/src/components/Calendar/utils.ts +622 -0
- package/src/components/Card/Card.module.scss +264 -0
- package/src/components/Card/Card.tokens.scss +55 -0
- package/src/components/Card/Card.tsx +170 -0
- package/src/components/Card/CardBody.tsx +45 -0
- package/src/components/Card/CardHeader.tsx +113 -0
- package/src/components/Card/CardList.tsx +24 -0
- package/src/components/Card/CardListRow.tsx +24 -0
- package/src/components/Card/index.ts +7 -0
- package/src/components/CatalogPicker/CatalogPicker.module.scss +193 -0
- package/src/components/CatalogPicker/CatalogPicker.tokens.scss +26 -0
- package/src/components/CatalogPicker/CatalogPicker.tsx +339 -0
- package/src/components/CatalogPicker/index.ts +2 -0
- package/src/components/Chart/Chart.module.scss +287 -0
- package/src/components/Chart/Chart.tokens.scss +63 -0
- package/src/components/Chart/Chart.tsx +607 -0
- package/src/components/Chart/ChartLegend.tsx +99 -0
- package/src/components/Chart/ChartTable.tsx +62 -0
- package/src/components/Chart/ChartTooltip.tsx +91 -0
- package/src/components/Chart/chartModel.ts +689 -0
- package/src/components/Chart/index.ts +2 -0
- package/src/components/ChatBubble/ChatBubble.module.scss +93 -0
- package/src/components/ChatBubble/ChatBubble.tokens.scss +17 -0
- package/src/components/ChatBubble/ChatBubble.tsx +61 -0
- package/src/components/ChatBubble/ChatBubbleList.tsx +27 -0
- package/src/components/ChatBubble/index.ts +3 -0
- package/src/components/ChatLauncher/ChatLauncher.module.scss +68 -0
- package/src/components/ChatLauncher/ChatLauncher.tokens.scss +27 -0
- package/src/components/ChatLauncher/ChatLauncher.tsx +202 -0
- package/src/components/ChatLauncher/index.ts +2 -0
- package/src/components/ChatLauncher/isAtBottom.ts +12 -0
- package/src/components/Checkbox/Checkbox.module.scss +147 -0
- package/src/components/Checkbox/Checkbox.tokens.scss +44 -0
- package/src/components/Checkbox/Checkbox.tsx +188 -0
- package/src/components/Checkbox/index.ts +2 -0
- package/src/components/CircularProgress/CircularProgress.module.scss +133 -0
- package/src/components/CircularProgress/CircularProgress.tokens.scss +40 -0
- package/src/components/CircularProgress/CircularProgress.tsx +139 -0
- package/src/components/CircularProgress/index.ts +7 -0
- package/src/components/Cluster/Cluster.module.scss +145 -0
- package/src/components/Cluster/Cluster.tokens.scss +11 -0
- package/src/components/Cluster/Cluster.tsx +215 -0
- package/src/components/Cluster/index.ts +2 -0
- package/src/components/Code/Code.module.scss +28 -0
- package/src/components/Code/Code.tokens.scss +15 -0
- package/src/components/Code/Code.tsx +44 -0
- package/src/components/Code/index.ts +2 -0
- package/src/components/CodeBlock/CodeBlock.module.scss +146 -0
- package/src/components/CodeBlock/CodeBlock.tokens.scss +27 -0
- package/src/components/CodeBlock/CodeBlock.tsx +274 -0
- package/src/components/CodeBlock/highlight.ts +179 -0
- package/src/components/CodeBlock/index.ts +8 -0
- package/src/components/CodeBlock/sampleSync.ts +42 -0
- package/src/components/ColorPicker/ColorPicker.module.scss +286 -0
- package/src/components/ColorPicker/ColorPicker.tokens.scss +60 -0
- package/src/components/ColorPicker/ColorPicker.tsx +367 -0
- package/src/components/ColorPicker/ColorPickerPanel.tsx +265 -0
- package/src/components/ColorPicker/SVSquare.tsx +189 -0
- package/src/components/ColorPicker/colorMath.ts +141 -0
- package/src/components/ColorPicker/index.ts +6 -0
- package/src/components/ConfirmationPopover/ConfirmationPopover.module.scss +39 -0
- package/src/components/ConfirmationPopover/ConfirmationPopover.tokens.scss +13 -0
- package/src/components/ConfirmationPopover/ConfirmationPopover.tsx +395 -0
- package/src/components/ConfirmationPopover/index.ts +2 -0
- package/src/components/Constrain/Constrain.module.scss +99 -0
- package/src/components/Constrain/Constrain.tsx +79 -0
- package/src/components/Constrain/index.ts +2 -0
- package/src/components/CurrencySelect/CurrencySelect.module.scss +7 -0
- package/src/components/CurrencySelect/CurrencySelect.tsx +167 -0
- package/src/components/CurrencySelect/index.ts +3 -0
- package/src/components/CursorPagination/CursorPagination.module.scss +69 -0
- package/src/components/CursorPagination/CursorPagination.tokens.scss +32 -0
- package/src/components/CursorPagination/CursorPagination.tsx +121 -0
- package/src/components/CursorPagination/index.ts +2 -0
- package/src/components/DashboardCanvas/DashboardCanvas.module.scss +360 -0
- package/src/components/DashboardCanvas/DashboardCanvas.tokens.scss +48 -0
- package/src/components/DashboardCanvas/DashboardCanvas.tsx +1270 -0
- package/src/components/DashboardCanvas/DashboardCanvasItem.tsx +140 -0
- package/src/components/DashboardCanvas/DashboardCanvasSection.tsx +174 -0
- package/src/components/DashboardCanvas/engine.ts +363 -0
- package/src/components/DashboardCanvas/index.ts +9 -0
- package/src/components/DashboardWidget/DashboardWidget.module.scss +100 -0
- package/src/components/DashboardWidget/DashboardWidget.tokens.scss +16 -0
- package/src/components/DashboardWidget/DashboardWidget.tsx +188 -0
- package/src/components/DashboardWidget/index.ts +7 -0
- package/src/components/DataTable/BodyRow.tsx +197 -0
- package/src/components/DataTable/ColumnVisibilityTrigger.module.scss +7 -0
- package/src/components/DataTable/ColumnVisibilityTrigger.tsx +105 -0
- package/src/components/DataTable/DataTable.module.scss +511 -0
- package/src/components/DataTable/DataTable.tokens.scss +80 -0
- package/src/components/DataTable/DataTable.tsx +734 -0
- package/src/components/DataTable/HeaderCell.module.scss +215 -0
- package/src/components/DataTable/HeaderCell.tsx +440 -0
- package/src/components/DataTable/columnShift.ts +117 -0
- package/src/components/DataTable/index.ts +23 -0
- package/src/components/DataTable/pinStyle.ts +47 -0
- package/src/components/DataTable/reorderColumns.ts +66 -0
- package/src/components/DataTable/types.ts +289 -0
- package/src/components/DataTable/useColumnDragShift.ts +216 -0
- package/src/components/DataTable/useDataTable.ts +372 -0
- package/src/components/DataTable/useResizeHandle.ts +97 -0
- package/src/components/DatePicker/DatePicker.module.scss +180 -0
- package/src/components/DatePicker/DatePicker.tokens.scss +152 -0
- package/src/components/DatePicker/DatePicker.tsx +664 -0
- package/src/components/DatePicker/DatePickerGrid.module.scss +172 -0
- package/src/components/DatePicker/DatePickerGrid.tsx +341 -0
- package/src/components/DatePicker/InlineDatePicker.module.scss +32 -0
- package/src/components/DatePicker/InlineDatePicker.tsx +201 -0
- package/src/components/DatePicker/index.ts +4 -0
- package/src/components/DatePicker/utils.ts +364 -0
- package/src/components/DateRangePicker/DateRangePicker.module.scss +237 -0
- package/src/components/DateRangePicker/DateRangePicker.tokens.scss +78 -0
- package/src/components/DateRangePicker/DateRangePicker.tsx +720 -0
- package/src/components/DateRangePicker/InlineDateRangePicker.module.scss +94 -0
- package/src/components/DateRangePicker/InlineDateRangePicker.tsx +376 -0
- package/src/components/DateRangePicker/index.ts +5 -0
- package/src/components/DateRangePicker/utils.ts +151 -0
- package/src/components/DateStrip/DateStrip.module.scss +78 -0
- package/src/components/DateStrip/DateStrip.tokens.scss +23 -0
- package/src/components/DateStrip/DateStrip.tsx +205 -0
- package/src/components/DateStrip/index.ts +2 -0
- package/src/components/DefinitionList/DefinitionList.module.scss +102 -0
- package/src/components/DefinitionList/DefinitionList.tokens.scss +29 -0
- package/src/components/DefinitionList/DefinitionList.tsx +198 -0
- package/src/components/DefinitionList/index.ts +9 -0
- package/src/components/Divider/Divider.module.scss +144 -0
- package/src/components/Divider/Divider.tokens.scss +21 -0
- package/src/components/Divider/Divider.tsx +103 -0
- package/src/components/Divider/index.ts +2 -0
- package/src/components/Dot/Dot.module.scss +40 -0
- package/src/components/Dot/Dot.tokens.scss +4 -0
- package/src/components/Dot/Dot.tsx +45 -0
- package/src/components/Dot/index.ts +2 -0
- package/src/components/Drawer/Body.tsx +23 -0
- package/src/components/Drawer/Close.tsx +35 -0
- package/src/components/Drawer/Content.tsx +93 -0
- package/src/components/Drawer/Drawer.module.scss +232 -0
- package/src/components/Drawer/Drawer.tokens.scss +38 -0
- package/src/components/Drawer/Drawer.tsx +23 -0
- package/src/components/Drawer/DrawerRoot.tsx +190 -0
- package/src/components/Drawer/Footer.tsx +24 -0
- package/src/components/Drawer/Header.tsx +64 -0
- package/src/components/Drawer/Overlay.tsx +82 -0
- package/src/components/Drawer/context.ts +44 -0
- package/src/components/Drawer/index.ts +12 -0
- package/src/components/Drawer/useDragToClose.ts +149 -0
- package/src/components/DropdownMenu/CheckboxItem.tsx +144 -0
- package/src/components/DropdownMenu/Content.tsx +419 -0
- package/src/components/DropdownMenu/DropdownMenu.module.scss +280 -0
- package/src/components/DropdownMenu/DropdownMenu.tokens.scss +88 -0
- package/src/components/DropdownMenu/DropdownMenu.tsx +43 -0
- package/src/components/DropdownMenu/Group.tsx +76 -0
- package/src/components/DropdownMenu/Item.tsx +148 -0
- package/src/components/DropdownMenu/ItemIndicator.tsx +30 -0
- package/src/components/DropdownMenu/Radio.tsx +178 -0
- package/src/components/DropdownMenu/Root.tsx +100 -0
- package/src/components/DropdownMenu/Sub.tsx +332 -0
- package/src/components/DropdownMenu/Trigger.tsx +76 -0
- package/src/components/DropdownMenu/context.ts +65 -0
- package/src/components/DropdownMenu/index.ts +20 -0
- package/src/components/EmojiPicker/EmojiPicker.module.scss +84 -0
- package/src/components/EmojiPicker/EmojiPicker.tokens.scss +24 -0
- package/src/components/EmojiPicker/EmojiPicker.tsx +341 -0
- package/src/components/EmojiPicker/emojiData.ts +245 -0
- package/src/components/EmojiPicker/index.ts +3 -0
- package/src/components/EmptyState/EmptyState.module.scss +102 -0
- package/src/components/EmptyState/EmptyState.tokens.scss +28 -0
- package/src/components/EmptyState/EmptyState.tsx +112 -0
- package/src/components/EmptyState/index.ts +7 -0
- package/src/components/EntityChip/EntityChip.module.scss +275 -0
- package/src/components/EntityChip/EntityChip.tokens.scss +66 -0
- package/src/components/EntityChip/EntityChip.tsx +649 -0
- package/src/components/EntityChip/index.ts +10 -0
- package/src/components/ErrorState/ErrorState.module.scss +105 -0
- package/src/components/ErrorState/ErrorState.tokens.scss +32 -0
- package/src/components/ErrorState/ErrorState.tsx +125 -0
- package/src/components/ErrorState/index.ts +8 -0
- package/src/components/Field/Field.module.scss +46 -0
- package/src/components/Field/Field.tsx +185 -0
- package/src/components/Field/index.ts +2 -0
- package/src/components/FileUpload/FileUpload.module.scss +187 -0
- package/src/components/FileUpload/FileUpload.tokens.scss +81 -0
- package/src/components/FileUpload/FileUpload.tsx +569 -0
- package/src/components/FileUpload/formatBytes.ts +34 -0
- package/src/components/FileUpload/iconForFile.ts +40 -0
- package/src/components/FileUpload/index.ts +2 -0
- package/src/components/FilterChip/FilterChip.module.scss +61 -0
- package/src/components/FilterChip/FilterChip.tokens.scss +25 -0
- package/src/components/FilterChip/FilterChip.tsx +208 -0
- package/src/components/FilterChip/index.ts +5 -0
- package/src/components/FlowCanvas/FlowCanvas.module.scss +329 -0
- package/src/components/FlowCanvas/FlowCanvas.tokens.scss +45 -0
- package/src/components/FlowCanvas/FlowCanvas.tsx +1867 -0
- package/src/components/FlowCanvas/FlowControls.tsx +46 -0
- package/src/components/FlowCanvas/FlowEdge.tsx +58 -0
- package/src/components/FlowCanvas/FlowNode.tsx +76 -0
- package/src/components/FlowCanvas/edgePath.ts +85 -0
- package/src/components/FlowCanvas/index.ts +4 -0
- package/src/components/FlowCanvas/layout.ts +148 -0
- package/src/components/FlowCanvas/spatialNav.ts +76 -0
- package/src/components/FlowCanvas/types.ts +59 -0
- package/src/components/FlowCanvas/useViewport.ts +122 -0
- package/src/components/FormRow/FormRow.module.scss +2 -0
- package/src/components/FormRow/FormRow.tsx +37 -0
- package/src/components/FormRow/index.ts +2 -0
- package/src/components/FormSection/FormSection.module.scss +24 -0
- package/src/components/FormSection/FormSection.tsx +46 -0
- package/src/components/FormSection/index.ts +2 -0
- package/src/components/Grid/Grid.module.scss +162 -0
- package/src/components/Grid/Grid.tokens.scss +13 -0
- package/src/components/Grid/Grid.tsx +226 -0
- package/src/components/Grid/GridItem.tsx +77 -0
- package/src/components/Grid/index.ts +11 -0
- package/src/components/Highlight/Highlight.module.scss +43 -0
- package/src/components/Highlight/Highlight.tokens.scss +35 -0
- package/src/components/Highlight/Highlight.tsx +164 -0
- package/src/components/Highlight/HighlightOverlay.tsx +100 -0
- package/src/components/Highlight/index.ts +2 -0
- package/src/components/HighlightedText/HighlightedText.module.scss +11 -0
- package/src/components/HighlightedText/HighlightedText.tokens.scss +9 -0
- package/src/components/HighlightedText/HighlightedText.tsx +50 -0
- package/src/components/HighlightedText/highlightRanges.ts +44 -0
- package/src/components/HighlightedText/index.ts +2 -0
- package/src/components/IconPicker/IconPicker.module.scss +175 -0
- package/src/components/IconPicker/IconPicker.tokens.scss +40 -0
- package/src/components/IconPicker/IconPicker.tsx +629 -0
- package/src/components/IconPicker/index.ts +9 -0
- package/src/components/IconTile/IconTile.module.scss +57 -0
- package/src/components/IconTile/IconTile.tokens.scss +16 -0
- package/src/components/IconTile/IconTile.tsx +91 -0
- package/src/components/IconTile/index.ts +2 -0
- package/src/components/Image/Image.module.scss +266 -0
- package/src/components/Image/Image.tokens.scss +55 -0
- package/src/components/Image/Image.tsx +342 -0
- package/src/components/Image/index.ts +2 -0
- package/src/components/ImageCrop/ImageCrop.module.scss +274 -0
- package/src/components/ImageCrop/ImageCrop.tokens.scss +33 -0
- package/src/components/ImageCrop/ImageCrop.tsx +542 -0
- package/src/components/ImageCrop/cropGeometry.ts +120 -0
- package/src/components/ImageCrop/extractCropBlob.ts +140 -0
- package/src/components/ImageCrop/index.ts +11 -0
- package/src/components/ImageCrop/useCropPreview.ts +161 -0
- package/src/components/Indent/Indent.module.scss +33 -0
- package/src/components/Indent/Indent.tokens.scss +10 -0
- package/src/components/Indent/Indent.tsx +46 -0
- package/src/components/Indent/index.ts +2 -0
- package/src/components/Input/Input.module.scss +99 -0
- package/src/components/Input/Input.tokens.scss +54 -0
- package/src/components/Input/Input.tsx +105 -0
- package/src/components/Input/Suggestions.tsx +260 -0
- package/src/components/Input/index.ts +2 -0
- package/src/components/Kanban/Kanban.module.scss +39 -0
- package/src/components/Kanban/Kanban.tokens.scss +15 -0
- package/src/components/Kanban/Kanban.tsx +951 -0
- package/src/components/Kanban/index.ts +7 -0
- package/src/components/Kbd/Kbd.module.scss +47 -0
- package/src/components/Kbd/Kbd.tokens.scss +26 -0
- package/src/components/Kbd/Kbd.tsx +73 -0
- package/src/components/Kbd/index.ts +2 -0
- package/src/components/Lightbox/Lightbox.module.scss +283 -0
- package/src/components/Lightbox/Lightbox.tokens.scss +36 -0
- package/src/components/Lightbox/Lightbox.tsx +506 -0
- package/src/components/Lightbox/index.ts +2 -0
- package/src/components/Link/Link.module.scss +78 -0
- package/src/components/Link/Link.tokens.scss +28 -0
- package/src/components/Link/Link.tsx +114 -0
- package/src/components/Link/index.ts +2 -0
- package/src/components/LinkCard/LinkCard.module.scss +92 -0
- package/src/components/LinkCard/LinkCard.tokens.scss +9 -0
- package/src/components/LinkCard/LinkCard.tsx +72 -0
- package/src/components/LinkCard/index.ts +2 -0
- package/src/components/LiquidEditor/AutocompleteMenu.tsx +133 -0
- package/src/components/LiquidEditor/HighlightLayer.tsx +53 -0
- package/src/components/LiquidEditor/InsertVariableMenu.tsx +81 -0
- package/src/components/LiquidEditor/LiquidEditor.module.scss +339 -0
- package/src/components/LiquidEditor/LiquidEditor.tokens.scss +81 -0
- package/src/components/LiquidEditor/LiquidEditor.tsx +412 -0
- package/src/components/LiquidEditor/PreviewPane.tsx +29 -0
- package/src/components/LiquidEditor/caretCoordinates.ts +97 -0
- package/src/components/LiquidEditor/index.ts +7 -0
- package/src/components/LiquidEditor/liquidFilters.ts +23 -0
- package/src/components/LiquidEditor/liquidTokenizer.ts +235 -0
- package/src/components/LiquidEditor/types.ts +101 -0
- package/src/components/LiquidEditor/useLiquidAutocomplete.ts +178 -0
- package/src/components/List/List.module.scss +105 -0
- package/src/components/List/List.tokens.scss +42 -0
- package/src/components/List/List.tsx +124 -0
- package/src/components/List/index.ts +9 -0
- package/src/components/LiveRegion/LiveRegion.module.scss +3 -0
- package/src/components/LiveRegion/LiveRegion.tsx +116 -0
- package/src/components/LiveRegion/index.ts +2 -0
- package/src/components/Logo/Logo.module.scss +139 -0
- package/src/components/Logo/Logo.tokens.scss +14 -0
- package/src/components/Logo/Logo.tsx +126 -0
- package/src/components/Logo/index.ts +2 -0
- package/src/components/Masonry/Masonry.module.scss +42 -0
- package/src/components/Masonry/Masonry.tokens.scss +8 -0
- package/src/components/Masonry/Masonry.tsx +136 -0
- package/src/components/Masonry/index.ts +2 -0
- package/src/components/Masonry/masonryUtils.ts +42 -0
- package/src/components/MediaTile/MediaTile.module.scss +183 -0
- package/src/components/MediaTile/MediaTile.tokens.scss +60 -0
- package/src/components/MediaTile/MediaTile.tsx +208 -0
- package/src/components/MediaTile/index.ts +8 -0
- package/src/components/Modal/Body.tsx +27 -0
- package/src/components/Modal/Close.tsx +45 -0
- package/src/components/Modal/Content.tsx +98 -0
- package/src/components/Modal/Footer.tsx +28 -0
- package/src/components/Modal/Header.tsx +51 -0
- package/src/components/Modal/Modal.module.scss +262 -0
- package/src/components/Modal/Modal.tokens.scss +50 -0
- package/src/components/Modal/Modal.tsx +23 -0
- package/src/components/Modal/ModalRoot.tsx +299 -0
- package/src/components/Modal/Overlay.tsx +101 -0
- package/src/components/Modal/context.ts +60 -0
- package/src/components/Modal/index.ts +11 -0
- package/src/components/MoneyInput/MoneyInput.module.scss +57 -0
- package/src/components/MoneyInput/MoneyInput.tsx +433 -0
- package/src/components/MoneyInput/index.ts +2 -0
- package/src/components/OptionsPicker/OptionsPicker.module.scss +216 -0
- package/src/components/OptionsPicker/OptionsPicker.tokens.scss +68 -0
- package/src/components/OptionsPicker/OptionsPicker.tsx +936 -0
- package/src/components/OptionsPicker/index.ts +9 -0
- package/src/components/OtpInput/OtpInput.module.scss +69 -0
- package/src/components/OtpInput/OtpInput.tokens.scss +45 -0
- package/src/components/OtpInput/OtpInput.tsx +368 -0
- package/src/components/OtpInput/index.ts +2 -0
- package/src/components/Page/Page.module.scss +30 -0
- package/src/components/Page/Page.tokens.scss +11 -0
- package/src/components/Page/Page.tsx +53 -0
- package/src/components/Page/index.ts +2 -0
- package/src/components/PageHeader/PageHeader.module.scss +197 -0
- package/src/components/PageHeader/PageHeader.tokens.scss +41 -0
- package/src/components/PageHeader/PageHeader.tsx +310 -0
- package/src/components/PageHeader/index.ts +20 -0
- package/src/components/Pagination/Pagination.module.scss +117 -0
- package/src/components/Pagination/Pagination.tokens.scss +54 -0
- package/src/components/Pagination/Pagination.tsx +165 -0
- package/src/components/Pagination/index.ts +4 -0
- package/src/components/Pagination/paginationRange.ts +72 -0
- package/src/components/PasswordInput/PasswordInput.module.scss +120 -0
- package/src/components/PasswordInput/PasswordInput.tokens.scss +44 -0
- package/src/components/PasswordInput/PasswordInput.tsx +210 -0
- package/src/components/PasswordInput/index.ts +2 -0
- package/src/components/PasswordStrengthMeter/PasswordStrengthMeter.module.scss +80 -0
- package/src/components/PasswordStrengthMeter/PasswordStrengthMeter.tokens.scss +40 -0
- package/src/components/PasswordStrengthMeter/PasswordStrengthMeter.tsx +96 -0
- package/src/components/PasswordStrengthMeter/index.ts +2 -0
- package/src/components/PersonDisplay/PersonDisplay.module.scss +180 -0
- package/src/components/PersonDisplay/PersonDisplay.tokens.scss +35 -0
- package/src/components/PersonDisplay/PersonDisplay.tsx +420 -0
- package/src/components/PersonDisplay/index.ts +10 -0
- package/src/components/PhoneInput/PhoneInput.module.scss +23 -0
- package/src/components/PhoneInput/PhoneInput.tsx +271 -0
- package/src/components/PhoneInput/index.ts +4 -0
- package/src/components/PhoneInput/phone.ts +128 -0
- package/src/components/PillMenu/PillMenu.module.scss +117 -0
- package/src/components/PillMenu/PillMenu.tokens.scss +22 -0
- package/src/components/PillMenu/PillMenu.tsx +324 -0
- package/src/components/PillMenu/index.ts +2 -0
- package/src/components/Popover/Anchor.tsx +37 -0
- package/src/components/Popover/Close.tsx +45 -0
- package/src/components/Popover/Content.tsx +213 -0
- package/src/components/Popover/Heading.tsx +33 -0
- package/src/components/Popover/Popover.module.scss +177 -0
- package/src/components/Popover/Popover.tokens.scss +22 -0
- package/src/components/Popover/Popover.tsx +23 -0
- package/src/components/Popover/PopoverRoot.tsx +75 -0
- package/src/components/Popover/Trigger.tsx +86 -0
- package/src/components/Popover/context.ts +22 -0
- package/src/components/Popover/index.ts +7 -0
- package/src/components/Progress/Progress.module.scss +110 -0
- package/src/components/Progress/Progress.tokens.scss +44 -0
- package/src/components/Progress/Progress.tsx +129 -0
- package/src/components/Progress/index.ts +2 -0
- package/src/components/QrCode/QrCode.module.scss +97 -0
- package/src/components/QrCode/QrCode.tokens.scss +17 -0
- package/src/components/QrCode/QrCode.tsx +233 -0
- package/src/components/QrCode/index.ts +2 -0
- package/src/components/QrCode/qr.ts +161 -0
- package/src/components/Radio/Radio.module.scss +175 -0
- package/src/components/Radio/Radio.tokens.scss +59 -0
- package/src/components/Radio/Radio.tsx +127 -0
- package/src/components/Radio/RadioGroup.module.scss +42 -0
- package/src/components/Radio/RadioGroup.tsx +120 -0
- package/src/components/Radio/RadioGroupContext.tsx +30 -0
- package/src/components/Radio/index.ts +4 -0
- package/src/components/Rail/Rail.module.scss +510 -0
- package/src/components/Rail/Rail.tokens.scss +92 -0
- package/src/components/Rail/Rail.tsx +287 -0
- package/src/components/Rail/RailCollapseToggle.tsx +57 -0
- package/src/components/Rail/RailFooter.tsx +17 -0
- package/src/components/Rail/RailGroup.tsx +728 -0
- package/src/components/Rail/RailGroupContext.ts +9 -0
- package/src/components/Rail/RailHeader.tsx +23 -0
- package/src/components/Rail/RailItem.tsx +119 -0
- package/src/components/Rail/RailSection.tsx +39 -0
- package/src/components/Rail/RailSpacer.tsx +19 -0
- package/src/components/Rail/index.ts +9 -0
- package/src/components/RelativeDateInput/RelativeDateInput.module.scss +15 -0
- package/src/components/RelativeDateInput/RelativeDateInput.tokens.scss +6 -0
- package/src/components/RelativeDateInput/RelativeDateInput.tsx +474 -0
- package/src/components/RelativeDateInput/index.ts +10 -0
- package/src/components/RelativeDateInput/relativeDate.ts +127 -0
- package/src/components/RichText/RichText.module.scss +5 -0
- package/src/components/RichText/RichText.tsx +70 -0
- package/src/components/RichText/RichTextAttachment.module.scss +82 -0
- package/src/components/RichText/RichTextAttachment.tsx +122 -0
- package/src/components/RichText/_prose.scss +79 -0
- package/src/components/RichText/attachmentIcon.tsx +24 -0
- package/src/components/RichText/engine/attachment.ts +154 -0
- package/src/components/RichText/engine/autolink.ts +64 -0
- package/src/components/RichText/engine/blockUnit.ts +174 -0
- package/src/components/RichText/engine/colorMarks.ts +85 -0
- package/src/components/RichText/engine/escape.ts +11 -0
- package/src/components/RichText/engine/fromHtml.ts +353 -0
- package/src/components/RichText/engine/fromMarkdown.ts +20 -0
- package/src/components/RichText/engine/inlines.ts +82 -0
- package/src/components/RichText/engine/listDepths.ts +32 -0
- package/src/components/RichText/engine/marks.ts +80 -0
- package/src/components/RichText/engine/mdToHtml.ts +178 -0
- package/src/components/RichText/engine/model.ts +157 -0
- package/src/components/RichText/engine/position.ts +118 -0
- package/src/components/RichText/engine/renderDoc.tsx +395 -0
- package/src/components/RichText/engine/renderLink.ts +15 -0
- package/src/components/RichText/engine/renderMention.ts +16 -0
- package/src/components/RichText/engine/safeHref.ts +22 -0
- package/src/components/RichText/engine/toHtml.ts +154 -0
- package/src/components/RichText/engine/toMarkdown.ts +97 -0
- package/src/components/RichText/engine/transforms.ts +434 -0
- package/src/components/RichText/imageMaxHeightStyle.ts +10 -0
- package/src/components/RichText/index.ts +31 -0
- package/src/components/RichTextEditor/RichTextAttachmentConfig.tsx +264 -0
- package/src/components/RichTextEditor/RichTextBlockControls.tsx +365 -0
- package/src/components/RichTextEditor/RichTextBlockMenu.tsx +162 -0
- package/src/components/RichTextEditor/RichTextColorMenu.tsx +115 -0
- package/src/components/RichTextEditor/RichTextEditor.module.scss +298 -0
- package/src/components/RichTextEditor/RichTextEditor.tsx +1910 -0
- package/src/components/RichTextEditor/RichTextImageResizer.tsx +131 -0
- package/src/components/RichTextEditor/RichTextLinkEditor.tsx +151 -0
- package/src/components/RichTextEditor/RichTextMentionMenu.tsx +99 -0
- package/src/components/RichTextEditor/RichTextToolbar.tsx +379 -0
- package/src/components/RichTextEditor/autolinkInput.ts +65 -0
- package/src/components/RichTextEditor/blockDrop.ts +9 -0
- package/src/components/RichTextEditor/blockReflow.ts +62 -0
- package/src/components/RichTextEditor/commands.ts +300 -0
- package/src/components/RichTextEditor/history.ts +93 -0
- package/src/components/RichTextEditor/icons.tsx +152 -0
- package/src/components/RichTextEditor/imageSize.ts +70 -0
- package/src/components/RichTextEditor/index.ts +5 -0
- package/src/components/RichTextEditor/input.ts +209 -0
- package/src/components/RichTextEditor/inputRules.ts +71 -0
- package/src/components/RichTextEditor/links.ts +103 -0
- package/src/components/RichTextEditor/mentionContext.ts +37 -0
- package/src/components/RichTextEditor/mentions.ts +28 -0
- package/src/components/RichTextEditor/preventSelectionLoss.ts +15 -0
- package/src/components/RichTextEditor/selection.ts +415 -0
- package/src/components/RichTextEditor/shortcuts.ts +44 -0
- package/src/components/RichTextEditor/useAnchoredFloating.ts +50 -0
- package/src/components/RichTextEditor/useAttachmentConfig.ts +134 -0
- package/src/components/RichTextEditor/useDismissOnOutsidePointerDown.ts +26 -0
- package/src/components/RichTextEditor/useLinkEditor.ts +107 -0
- package/src/components/RichTextEditor/useMention.ts +226 -0
- package/src/components/RichTextEditor/useOverlayRect.ts +73 -0
- package/src/components/RichTextEditor/useUpload.ts +246 -0
- package/src/components/Screen/Screen.module.scss +46 -0
- package/src/components/Screen/Screen.tokens.scss +20 -0
- package/src/components/Screen/Screen.tsx +97 -0
- package/src/components/Screen/index.ts +2 -0
- package/src/components/ScrollArea/ScrollArea.module.scss +39 -0
- package/src/components/ScrollArea/ScrollArea.tokens.scss +15 -0
- package/src/components/ScrollArea/ScrollArea.tsx +154 -0
- package/src/components/ScrollArea/index.ts +2 -0
- package/src/components/Select/Chip.tsx +45 -0
- package/src/components/Select/Empty.tsx +39 -0
- package/src/components/Select/Error.tsx +51 -0
- package/src/components/Select/HiddenInputs.tsx +44 -0
- package/src/components/Select/Listbox.tsx +557 -0
- package/src/components/Select/Loading.tsx +30 -0
- package/src/components/Select/Select.module.scss +512 -0
- package/src/components/Select/Select.tokens.scss +140 -0
- package/src/components/Select/Select.tsx +685 -0
- package/src/components/Select/Trigger.tsx +952 -0
- package/src/components/Select/context.ts +101 -0
- package/src/components/Select/emptyStateText.ts +19 -0
- package/src/components/Select/index.ts +9 -0
- package/src/components/Select/useSelectState.ts +157 -0
- package/src/components/Select/utils-types.ts +7 -0
- package/src/components/Select/utils.ts +122 -0
- package/src/components/SettingRow/SettingRow.module.scss +166 -0
- package/src/components/SettingRow/SettingRow.tokens.scss +37 -0
- package/src/components/SettingRow/SettingRow.tsx +242 -0
- package/src/components/SettingRow/index.ts +7 -0
- package/src/components/Skeleton/Skeleton.module.scss +55 -0
- package/src/components/Skeleton/Skeleton.tokens.scss +17 -0
- package/src/components/Skeleton/Skeleton.tsx +131 -0
- package/src/components/Skeleton/index.ts +4 -0
- package/src/components/Skeleton/useSkeletonVisibility.ts +95 -0
- package/src/components/Slider/Slider.module.scss +293 -0
- package/src/components/Slider/Slider.tokens.scss +98 -0
- package/src/components/Slider/Slider.tsx +624 -0
- package/src/components/Slider/index.ts +9 -0
- package/src/components/SlotGrid/SlotGrid.module.scss +61 -0
- package/src/components/SlotGrid/SlotGrid.tokens.scss +21 -0
- package/src/components/SlotGrid/SlotGrid.tsx +111 -0
- package/src/components/SlotGrid/index.ts +2 -0
- package/src/components/SocialButton/SocialButton.module.scss +4 -0
- package/src/components/SocialButton/SocialButton.tsx +40 -0
- package/src/components/SocialButton/index.ts +2 -0
- package/src/components/Sortable/Sortable.module.scss +177 -0
- package/src/components/Sortable/Sortable.tokens.scss +27 -0
- package/src/components/Sortable/Sortable.tsx +572 -0
- package/src/components/Sortable/containerAwareCollision.ts +125 -0
- package/src/components/Sortable/index.ts +13 -0
- package/src/components/SortableGroup/SortableGroup.module.scss +23 -0
- package/src/components/SortableGroup/SortableGroup.tsx +315 -0
- package/src/components/SortableGroup/index.ts +4 -0
- package/src/components/SortableGroup/moveSortableItem.ts +39 -0
- package/src/components/Sparkline/Sparkline.module.scss +28 -0
- package/src/components/Sparkline/Sparkline.tokens.scss +7 -0
- package/src/components/Sparkline/Sparkline.tsx +133 -0
- package/src/components/Sparkline/index.ts +2 -0
- package/src/components/Sparkline/sparklineModel.ts +119 -0
- package/src/components/Split/Split.module.scss +158 -0
- package/src/components/Split/Split.tokens.scss +10 -0
- package/src/components/Split/Split.tsx +231 -0
- package/src/components/Split/index.ts +2 -0
- package/src/components/Stack/Stack.module.scss +67 -0
- package/src/components/Stack/Stack.tokens.scss +11 -0
- package/src/components/Stack/Stack.tsx +100 -0
- package/src/components/Stack/index.ts +2 -0
- package/src/components/StagePath/StagePath.module.scss +255 -0
- package/src/components/StagePath/StagePath.tokens.scss +43 -0
- package/src/components/StagePath/StagePath.tsx +176 -0
- package/src/components/StagePath/index.ts +2 -0
- package/src/components/StatGroup/StatGroup.module.scss +6 -0
- package/src/components/StatGroup/StatGroup.tsx +70 -0
- package/src/components/StatGroup/StatGroupContext.ts +10 -0
- package/src/components/StatGroup/StatTile.module.scss +99 -0
- package/src/components/StatGroup/StatTile.tokens.scss +35 -0
- package/src/components/StatGroup/StatTile.tsx +143 -0
- package/src/components/StatGroup/index.ts +4 -0
- package/src/components/Sticky/Sticky.module.scss +98 -0
- package/src/components/Sticky/Sticky.tokens.scss +17 -0
- package/src/components/Sticky/Sticky.tsx +103 -0
- package/src/components/Sticky/index.ts +2 -0
- package/src/components/Switch/Switch.module.scss +182 -0
- package/src/components/Switch/Switch.tokens.scss +59 -0
- package/src/components/Switch/Switch.tsx +214 -0
- package/src/components/Switch/index.ts +2 -0
- package/src/components/Table/Table.module.scss +308 -0
- package/src/components/Table/Table.tokens.scss +79 -0
- package/src/components/Table/Table.tsx +381 -0
- package/src/components/Table/index.ts +15 -0
- package/src/components/TableOfContents/TableOfContents.module.scss +83 -0
- package/src/components/TableOfContents/TableOfContents.tokens.scss +26 -0
- package/src/components/TableOfContents/TableOfContents.tsx +192 -0
- package/src/components/TableOfContents/activeSection.ts +18 -0
- package/src/components/TableOfContents/index.ts +2 -0
- package/src/components/Tabs/Tabs.module.scss +359 -0
- package/src/components/Tabs/Tabs.tokens.scss +50 -0
- package/src/components/Tabs/Tabs.tsx +538 -0
- package/src/components/Tabs/index.ts +2 -0
- package/src/components/Text/Text.module.scss +115 -0
- package/src/components/Text/Text.tokens.scss +55 -0
- package/src/components/Text/Text.tsx +197 -0
- package/src/components/Text/index.ts +2 -0
- package/src/components/Textarea/Textarea.module.scss +94 -0
- package/src/components/Textarea/Textarea.tokens.scss +48 -0
- package/src/components/Textarea/Textarea.tsx +246 -0
- package/src/components/Textarea/index.ts +2 -0
- package/src/components/Thread/Thread.module.scss +161 -0
- package/src/components/Thread/Thread.tokens.scss +32 -0
- package/src/components/Thread/Thread.tsx +176 -0
- package/src/components/Thread/index.ts +2 -0
- package/src/components/TimeField/TimeField.module.scss +232 -0
- package/src/components/TimeField/TimeField.tsx +793 -0
- package/src/components/TimeField/index.ts +2 -0
- package/src/components/Timeline/Timeline.module.scss +58 -0
- package/src/components/Timeline/Timeline.tokens.scss +13 -0
- package/src/components/Timeline/Timeline.tsx +53 -0
- package/src/components/Timeline/index.ts +2 -0
- package/src/components/Title/Title.module.scss +84 -0
- package/src/components/Title/Title.tokens.scss +37 -0
- package/src/components/Title/Title.tsx +150 -0
- package/src/components/Title/index.ts +2 -0
- package/src/components/Toast/Toast.module.scss +299 -0
- package/src/components/Toast/Toast.tokens.scss +65 -0
- package/src/components/Toast/Toast.tsx +122 -0
- package/src/components/Toast/ToastViewport.tsx +166 -0
- package/src/components/Toast/api.ts +175 -0
- package/src/components/Toast/index.ts +5 -0
- package/src/components/Toast/store.ts +133 -0
- package/src/components/Toast/useToastTimer.ts +94 -0
- package/src/components/Tooltip/Tooltip.module.scss +99 -0
- package/src/components/Tooltip/Tooltip.tokens.scss +23 -0
- package/src/components/Tooltip/Tooltip.tsx +341 -0
- package/src/components/Tooltip/index.ts +2 -0
- package/src/components/TopBar/TopBar.module.scss +235 -0
- package/src/components/TopBar/TopBar.tokens.scss +79 -0
- package/src/components/TopBar/TopBar.tsx +87 -0
- package/src/components/TopBar/TopBarEnd.tsx +20 -0
- package/src/components/TopBar/TopBarIconButton.tsx +86 -0
- package/src/components/TopBar/TopBarSearch.tsx +319 -0
- package/src/components/TopBar/TopBarSearchPanel.tsx +268 -0
- package/src/components/TopBar/TopBarStart.tsx +20 -0
- package/src/components/TopBar/index.ts +11 -0
- package/src/components/Tour/Spotlight.tsx +121 -0
- package/src/components/Tour/Tour.module.scss +210 -0
- package/src/components/Tour/Tour.tokens.scss +58 -0
- package/src/components/Tour/Tour.tsx +617 -0
- package/src/components/Tour/index.ts +2 -0
- package/src/components/Tour/useTourTarget.ts +115 -0
- package/src/components/TreeView/TreeView.module.scss +268 -0
- package/src/components/TreeView/TreeView.tokens.scss +36 -0
- package/src/components/TreeView/TreeView.tsx +536 -0
- package/src/components/TreeView/index.ts +3 -0
- package/src/components/TreeView/treeModel.ts +224 -0
- package/src/components/VisuallyHidden/VisuallyHidden.module.scss +7 -0
- package/src/components/VisuallyHidden/VisuallyHidden.tsx +29 -0
- package/src/components/VisuallyHidden/index.ts +2 -0
- package/src/components/WidgetPreview/WidgetPreview.module.scss +11 -0
- package/src/components/WidgetPreview/WidgetPreview.tokens.scss +26 -0
- package/src/components/WidgetPreview/WidgetPreview.tsx +36 -0
- package/src/components/WidgetPreview/WidgetShape.module.scss +140 -0
- package/src/components/WidgetPreview/WidgetShape.tsx +159 -0
- package/src/components/WidgetPreview/index.ts +2 -0
- package/src/components/_internal/Trend.tsx +27 -0
- package/src/components/_internal/choiceTile.scss +74 -0
- package/src/components/_internal/collapse.scss +10 -0
- package/src/components/_internal/collapse.ts +58 -0
- package/src/components/_internal/currency.ts +257 -0
- package/src/components/_internal/dragAnnouncements.ts +200 -0
- package/src/components/_internal/fieldWiring.ts +225 -0
- package/src/components/_internal/gridSpan.ts +54 -0
- package/src/components/_internal/isoDay.ts +35 -0
- package/src/components/_internal/letterKey.ts +26 -0
- package/src/components/_internal/overlay/focusRestore.ts +21 -0
- package/src/components/_internal/overlay/index.ts +12 -0
- package/src/components/_internal/overlay/tourHold.ts +23 -0
- package/src/components/_internal/overlay/useFocusTrap.ts +120 -0
- package/src/components/_internal/overlay/useInOverlay.ts +81 -0
- package/src/components/_internal/overlay/useOverlayStack.ts +232 -0
- package/src/components/_internal/overlay/useScrollLock.ts +112 -0
- package/src/components/_internal/refs.ts +48 -0
- package/src/components/_internal/statusColor.ts +26 -0
- package/src/components/_internal/timeUtils.ts +23 -0
- package/src/components/_internal/useAsyncOptions.ts +145 -0
- package/src/components/_internal/useClippedTooltip.ts +50 -0
- package/src/components/_internal/useControllableState.ts +50 -0
- package/src/components.manifest.json +1141 -0
- package/src/hooks/useBelowBreakpoint.ts +58 -0
- package/src/i18n/I18nProvider.tsx +52 -0
- package/src/i18n/LocaleProvider.tsx +17 -0
- package/src/i18n/en.ts +648 -0
- package/src/i18n/format.ts +71 -0
- package/src/i18n/index.ts +13 -0
- package/src/i18n/messages.ts +1275 -0
- package/src/i18n/ru.ts +677 -0
- package/src/i18n/useLocale.ts +19 -0
- package/src/i18n/useTranslation.ts +58 -0
- package/src/index.ts +913 -0
- package/src/palette/index.ts +2 -0
- package/src/palette/palette.ts +108 -0
- package/src/styles/dark.scss +4 -0
- package/src/styles/global.scss +7 -0
- package/src/styles/mixins.scss +143 -0
- package/src/styles/reset.scss +78 -0
- package/src/styles/tokens.scss +18 -0
- package/src/styles/typography.scss +44 -0
|
@@ -0,0 +1,951 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
cloneElement,
|
|
4
|
+
createContext,
|
|
5
|
+
forwardRef,
|
|
6
|
+
isValidElement,
|
|
7
|
+
useCallback,
|
|
8
|
+
useContext,
|
|
9
|
+
useEffect,
|
|
10
|
+
useLayoutEffect,
|
|
11
|
+
useMemo,
|
|
12
|
+
useRef,
|
|
13
|
+
useState,
|
|
14
|
+
type HTMLAttributes,
|
|
15
|
+
type ReactElement,
|
|
16
|
+
type ReactNode,
|
|
17
|
+
} from 'react';
|
|
18
|
+
import {
|
|
19
|
+
DndContext,
|
|
20
|
+
KeyboardSensor,
|
|
21
|
+
PointerSensor,
|
|
22
|
+
getScrollableAncestors,
|
|
23
|
+
useDroppable,
|
|
24
|
+
useSensor,
|
|
25
|
+
useSensors,
|
|
26
|
+
type CollisionDetection,
|
|
27
|
+
type DragEndEvent,
|
|
28
|
+
type DragOverEvent,
|
|
29
|
+
type DragStartEvent,
|
|
30
|
+
} from '@dnd-kit/core';
|
|
31
|
+
import {
|
|
32
|
+
SortableContext,
|
|
33
|
+
sortableKeyboardCoordinates,
|
|
34
|
+
useSortable,
|
|
35
|
+
verticalListSortingStrategy,
|
|
36
|
+
} from '@dnd-kit/sortable';
|
|
37
|
+
import { CSS, type Transform } from '@dnd-kit/utilities';
|
|
38
|
+
import clsx from 'clsx';
|
|
39
|
+
import {
|
|
40
|
+
SortableHandle,
|
|
41
|
+
SortableItemContext,
|
|
42
|
+
type SortableItemContextValue,
|
|
43
|
+
} from '../Sortable/Sortable';
|
|
44
|
+
import {
|
|
45
|
+
containerName,
|
|
46
|
+
dragLabelOf,
|
|
47
|
+
useDragAccessibility,
|
|
48
|
+
type DescribeDragTarget,
|
|
49
|
+
} from '../_internal/dragAnnouncements';
|
|
50
|
+
import { useTranslation } from '../../i18n/useTranslation';
|
|
51
|
+
import {
|
|
52
|
+
containerAwareClosestCorners,
|
|
53
|
+
isPointerOutsideContainers,
|
|
54
|
+
} from '../Sortable/containerAwareCollision';
|
|
55
|
+
import styles from './Kanban.module.scss';
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Internal context: Root → Column, passes effective cardIds for SortableContext
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
const KanbanColumnItemsContext = createContext<readonly (string | number)[] | null>(null);
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Public interfaces
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Payload fired by `onMove` after a successful drag-drop across or within
|
|
69
|
+
* a column. Consumer applies the move by updating their items state.
|
|
70
|
+
*/
|
|
71
|
+
export interface KanbanMoveEvent {
|
|
72
|
+
/** Source position — where the card was before the drag. */
|
|
73
|
+
from: { columnId: string | number; index: number };
|
|
74
|
+
/** Destination position — where the card landed after the drop. */
|
|
75
|
+
to: { columnId: string | number; index: number };
|
|
76
|
+
/** The id of the card that was dragged. */
|
|
77
|
+
cardId: string | number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface KanbanProps extends HTMLAttributes<HTMLDivElement> {
|
|
81
|
+
/**
|
|
82
|
+
* Fires once per drag (on drop) with the from/to positions and the cardId.
|
|
83
|
+
* NOT fired on drag cancel. NOT fired when from === to (no-op drop).
|
|
84
|
+
*
|
|
85
|
+
* Consumer should update their items state immutably, e.g.:
|
|
86
|
+
* ```ts
|
|
87
|
+
* const [cols, setCols] = useState({ todo: ['a', 'b'], done: ['c'] });
|
|
88
|
+
* const handleMove = ({ from, to, cardId }) => {
|
|
89
|
+
* setCols((prev) => {
|
|
90
|
+
* const next = { ...prev };
|
|
91
|
+
* next[from.columnId] = [...prev[from.columnId]];
|
|
92
|
+
* next[from.columnId].splice(from.index, 1);
|
|
93
|
+
* next[to.columnId] = [...prev[to.columnId]];
|
|
94
|
+
* next[to.columnId].splice(to.index, 0, cardId);
|
|
95
|
+
* return next;
|
|
96
|
+
* });
|
|
97
|
+
* };
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
onMove?: (event: KanbanMoveEvent) => void;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface KanbanColumnProps extends Omit<HTMLAttributes<HTMLDivElement>, 'id'> {
|
|
104
|
+
/**
|
|
105
|
+
* Stable identifier for this column. Must be unique within a `<Kanban>`.
|
|
106
|
+
* Used internally to track which cards belong to which column.
|
|
107
|
+
*/
|
|
108
|
+
id: string | number;
|
|
109
|
+
children?: ReactNode;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface KanbanCardProps extends Omit<HTMLAttributes<HTMLDivElement>, 'id'> {
|
|
113
|
+
/**
|
|
114
|
+
* Stable identifier for this card. Must be unique across ALL columns in
|
|
115
|
+
* the `<Kanban>`. Used by dnd-kit to track the card during drag.
|
|
116
|
+
*/
|
|
117
|
+
id: string | number;
|
|
118
|
+
children: ReactNode;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// containsHandle — mirrors Sortable's implementation; checks for KanbanHandle
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Recursively walk the children subtree looking for a `Kanban.Handle`
|
|
127
|
+
* (`SortableHandle`). Mirrors Sortable's `containsHandle` function.
|
|
128
|
+
*
|
|
129
|
+
* Limitation: only walks `React.Children` and `props.children` — a Handle
|
|
130
|
+
* wrapped inside a custom component is invisible to this walk.
|
|
131
|
+
*/
|
|
132
|
+
function containsHandle(children: ReactNode): boolean {
|
|
133
|
+
let found = false;
|
|
134
|
+
function walk(node: ReactNode) {
|
|
135
|
+
if (found) return;
|
|
136
|
+
if (!isValidElement(node)) return;
|
|
137
|
+
if (node.type === SortableHandle) {
|
|
138
|
+
found = true;
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const { children: nested } = node.props as { children?: ReactNode };
|
|
142
|
+
if (nested != null) Children.forEach(nested, walk);
|
|
143
|
+
}
|
|
144
|
+
Children.forEach(children, walk);
|
|
145
|
+
return found;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// Drag bounds — the dragged card may not leave the board's scrollable content
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Where the dragged card sits inside its scroll container, and how big that
|
|
154
|
+
* container's content is. Everything is expressed in the container's CONTENT
|
|
155
|
+
* space (origin = scroll offset 0), which is scroll-invariant: the numbers stay
|
|
156
|
+
* valid while auto-scroll moves the board under the card.
|
|
157
|
+
*
|
|
158
|
+
* @internal exported for unit tests; not part of the public API.
|
|
159
|
+
*/
|
|
160
|
+
export interface KanbanDragBounds {
|
|
161
|
+
/** Card centre, x, in content space. */
|
|
162
|
+
centerX: number;
|
|
163
|
+
/** Card centre, y, in content space. */
|
|
164
|
+
centerY: number;
|
|
165
|
+
/** Card layout width (untransformed). */
|
|
166
|
+
width: number;
|
|
167
|
+
/** Card layout height (untransformed). */
|
|
168
|
+
height: number;
|
|
169
|
+
/** `scrollWidth` of the container, measured with the card untransformed. */
|
|
170
|
+
contentWidth: number;
|
|
171
|
+
/** `scrollHeight` of the container, measured with the card untransformed. */
|
|
172
|
+
contentHeight: number;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Measure `node` against its nearest scrollable ancestor.
|
|
177
|
+
*
|
|
178
|
+
* The card's own transform (and transition) are stripped for the duration of
|
|
179
|
+
* the measurement — both readings would otherwise be poisoned by the transform:
|
|
180
|
+
* `getBoundingClientRect()` returns the TRANSFORMED box, and a card already
|
|
181
|
+
* hanging past the content edge inflates the very `scrollWidth` we are about to
|
|
182
|
+
* use as the bound. Stripping makes the measurement a fixed point, so repeated
|
|
183
|
+
* measurements during one drag cannot ratchet the bound outwards. The
|
|
184
|
+
* transition goes too: a keyboard drag leaves a real `transition` on the drag
|
|
185
|
+
* source (a pointer drag does not), and strip-then-restore would otherwise read
|
|
186
|
+
* as a computed-value change and animate the card back from identity. Both are
|
|
187
|
+
* restored before the browser paints (this runs in a layout effect), so nothing
|
|
188
|
+
* is visible.
|
|
189
|
+
*
|
|
190
|
+
* Which container: only the NEAREST scrollable ancestor matters — it clips
|
|
191
|
+
* everything below it, so no ancestor further up ever sees the overhang. Inside
|
|
192
|
+
* a `<Kanban>` that is always the board. It is whatever else the consumer made
|
|
193
|
+
* scrollable, though: a `<Kanban.Column>` given `overflow-y: auto` becomes the
|
|
194
|
+
* nearest scroller for its own cards.
|
|
195
|
+
*
|
|
196
|
+
* Returns null when nothing is measurable — no node, or a zero-sized scroll
|
|
197
|
+
* area, which is every element under jsdom (it lays nothing out). A null bound
|
|
198
|
+
* means "don't clamp", i.e. the pre-#373 behaviour. Note there is no
|
|
199
|
+
* "unscrollable" case to bail out of in a real browser:
|
|
200
|
+
* `getScrollableAncestors` falls back to `document.scrollingElement`, so a card
|
|
201
|
+
* with no scrollable ancestor is bounded by the page instead.
|
|
202
|
+
*
|
|
203
|
+
* @internal exported for unit tests; not part of the public API.
|
|
204
|
+
*/
|
|
205
|
+
export function measureKanbanDragBounds(node: HTMLElement | null): KanbanDragBounds | null {
|
|
206
|
+
if (!node) return null;
|
|
207
|
+
const [scroller] = getScrollableAncestors(node, 1);
|
|
208
|
+
if (!scroller) return null;
|
|
209
|
+
|
|
210
|
+
const previousTransform = node.style.transform;
|
|
211
|
+
const previousTransition = node.style.transition;
|
|
212
|
+
node.style.transform = 'none';
|
|
213
|
+
node.style.transition = 'none';
|
|
214
|
+
const rect = node.getBoundingClientRect();
|
|
215
|
+
const box = scroller.getBoundingClientRect();
|
|
216
|
+
const { scrollLeft, scrollTop, scrollWidth, scrollHeight, clientLeft, clientTop } = scroller;
|
|
217
|
+
node.style.transform = previousTransform;
|
|
218
|
+
node.style.transition = previousTransition;
|
|
219
|
+
|
|
220
|
+
if (scrollWidth === 0 || scrollHeight === 0) return null;
|
|
221
|
+
|
|
222
|
+
// Content-space origin: the padding-box corner, un-scrolled. The root
|
|
223
|
+
// scrolling element is the exception — its OWN box already moves with page
|
|
224
|
+
// scroll, so `box` is the un-scrolled origin and subtracting the scroll
|
|
225
|
+
// offset again would count it twice.
|
|
226
|
+
const isRootScroller = scroller === scroller.ownerDocument.scrollingElement;
|
|
227
|
+
const originX = box.left + clientLeft - (isRootScroller ? 0 : scrollLeft);
|
|
228
|
+
const originY = box.top + clientTop - (isRootScroller ? 0 : scrollTop);
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
centerX: rect.left + rect.width / 2 - originX,
|
|
232
|
+
centerY: rect.top + rect.height / 2 - originY,
|
|
233
|
+
width: rect.width,
|
|
234
|
+
height: rect.height,
|
|
235
|
+
contentWidth: scrollWidth,
|
|
236
|
+
contentHeight: scrollHeight,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Bound `transform` so the transformed card stays inside the scroll
|
|
242
|
+
* container's content box. Pass-through when either argument is null.
|
|
243
|
+
*
|
|
244
|
+
* This is what stops horizontal auto-scroll from running away (#373). The card
|
|
245
|
+
* is rendered IN FLOW (there is no `DragOverlay`), so a transform that carries
|
|
246
|
+
* it past the content edge extends the container's `scrollWidth`; dnd-kit's
|
|
247
|
+
* auto-scroll then chases the edge it just created, which moves the card
|
|
248
|
+
* further out, which extends the edge again — measured at 8910px of `scrollLeft`
|
|
249
|
+
* against a real maximum of 597. Clamping the APPLIED transform breaks the loop
|
|
250
|
+
* at its source: `scrollWidth` never changes, so `scrollLeft` plateaus at the
|
|
251
|
+
* real maximum and the last column becomes reachable.
|
|
252
|
+
*
|
|
253
|
+
* It has to happen here, where the transform is written to the DOM — a
|
|
254
|
+
* `DndContext` modifier is not enough, because dnd-kit computes
|
|
255
|
+
* `scrollAdjustedTranslate = modifiedTranslate + scrollAdjustment` and adds the
|
|
256
|
+
* scroll adjustment AFTER modifiers run (same reason `DataTable` clamps
|
|
257
|
+
* `event.delta` rather than only its modifier — see `useColumnDragShift`).
|
|
258
|
+
*
|
|
259
|
+
* `scaleX` / `scaleY` are honoured because dnd-kit scales the drag source to
|
|
260
|
+
* the `over` rect; the scaled half-extent, not the layout half-extent, is what
|
|
261
|
+
* reaches the edge.
|
|
262
|
+
*
|
|
263
|
+
* @internal exported for unit tests; not part of the public API.
|
|
264
|
+
*/
|
|
265
|
+
export function clampKanbanTransform(
|
|
266
|
+
transform: Transform | null,
|
|
267
|
+
bounds: KanbanDragBounds | null,
|
|
268
|
+
): Transform | null {
|
|
269
|
+
if (!transform || !bounds) return transform;
|
|
270
|
+
const halfWidth = (bounds.width * transform.scaleX) / 2;
|
|
271
|
+
const halfHeight = (bounds.height * transform.scaleY) / 2;
|
|
272
|
+
return {
|
|
273
|
+
...transform,
|
|
274
|
+
x: clamp(
|
|
275
|
+
transform.x,
|
|
276
|
+
halfWidth - bounds.centerX,
|
|
277
|
+
bounds.contentWidth - halfWidth - bounds.centerX,
|
|
278
|
+
),
|
|
279
|
+
y: clamp(
|
|
280
|
+
transform.y,
|
|
281
|
+
halfHeight - bounds.centerY,
|
|
282
|
+
bounds.contentHeight - halfHeight - bounds.centerY,
|
|
283
|
+
),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max);
|
|
288
|
+
|
|
289
|
+
// ---------------------------------------------------------------------------
|
|
290
|
+
// KanbanCard
|
|
291
|
+
// ---------------------------------------------------------------------------
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* One draggable card inside a `<Kanban.Column>`; needs a stable `id`.
|
|
295
|
+
* @see docs/components/Kanban.md
|
|
296
|
+
*/
|
|
297
|
+
export const KanbanCard = forwardRef<HTMLDivElement, KanbanCardProps>(function KanbanCard(
|
|
298
|
+
{ id, className, children, ...rest },
|
|
299
|
+
ref,
|
|
300
|
+
) {
|
|
301
|
+
// Announcement label: the consumer's `aria-label`, else the card's RENDERED
|
|
302
|
+
// text (so a closed menu inside the card contributes nothing — see
|
|
303
|
+
// `dragLabelOf`). The same ref is the drag-bounds node ref below.
|
|
304
|
+
const cardRef = useRef<HTMLDivElement | null>(null);
|
|
305
|
+
const dragLabel = rest['aria-label'];
|
|
306
|
+
const dragData = useMemo(() => ({ dragLabel, dragNode: cardRef }), [dragLabel]);
|
|
307
|
+
|
|
308
|
+
const {
|
|
309
|
+
setNodeRef,
|
|
310
|
+
setActivatorNodeRef,
|
|
311
|
+
listeners,
|
|
312
|
+
attributes,
|
|
313
|
+
transform,
|
|
314
|
+
transition,
|
|
315
|
+
isDragging,
|
|
316
|
+
index,
|
|
317
|
+
items,
|
|
318
|
+
} = useSortable({ id, data: dragData });
|
|
319
|
+
|
|
320
|
+
const hasHandle = useMemo(() => containsHandle(children), [children]);
|
|
321
|
+
|
|
322
|
+
const ctxValue = useMemo<SortableItemContextValue>(
|
|
323
|
+
() => ({ listeners, attributes, setActivatorNodeRef }),
|
|
324
|
+
[listeners, attributes, setActivatorNodeRef],
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
// Travel available to the dragged card, in its scroll container's content
|
|
328
|
+
// space (#373). Re-measured only when the card's LAYOUT position can have
|
|
329
|
+
// changed, which is never a plain pointer move — while the card only
|
|
330
|
+
// translates, its layout position and the content size are both constant, so
|
|
331
|
+
// measuring per move would force a full layout per move for no gain. It
|
|
332
|
+
// changes in exactly two ways:
|
|
333
|
+
// - a live re-seat WITHIN a column (`index` / `items` change, same parent —
|
|
334
|
+
// React moves the node), and
|
|
335
|
+
// - a live re-seat ACROSS columns, where `KanbanRoot` re-parents the card
|
|
336
|
+
// into another `KanbanColumnItemsContext` subtree. That is a remount, not
|
|
337
|
+
// a move: this component gets a fresh instance with `boundsRef` back to
|
|
338
|
+
// null, so its FIRST render clamps against nothing at all.
|
|
339
|
+
//
|
|
340
|
+
// The write-back below exists for that first render. Measuring alone can't
|
|
341
|
+
// fix it: React's mutation phase writes the frame's transform BEFORE refs
|
|
342
|
+
// attach and layout effects run, so by then the unbounded value is already on
|
|
343
|
+
// the node and nothing re-reads `boundsRef` until the next render. Only
|
|
344
|
+
// writing the bounded value back changes what that frame paints, and a layout
|
|
345
|
+
// effect does it pre-paint. `transform` is deliberately NOT a dependency —
|
|
346
|
+
// this effect runs only on the renders that (re)measure, and on those the
|
|
347
|
+
// closed-over `transform` is exactly the one just committed.
|
|
348
|
+
//
|
|
349
|
+
// Be clear about what it is: on today's dnd-kit it is a NO-OP, not a fix.
|
|
350
|
+
// `transform` is null on precisely these frames — a re-seat mutates the
|
|
351
|
+
// SortableContext items, so `itemsHaveChanged` forces `displaceItem` false;
|
|
352
|
+
// and on a transit `over` is the column, so `overIndex` is -1 and it's false
|
|
353
|
+
// again. A null transform short-circuits every guard below, so nothing is
|
|
354
|
+
// written. Matching that, the frame could not be made to inflate
|
|
355
|
+
// `scrollWidth` in the browser: 635 rAF-sampled frames over 14 transits at
|
|
356
|
+
// max scroll with the pointer held outside the board, write-back disabled,
|
|
357
|
+
// yielded a single distinct `scrollWidth`.
|
|
358
|
+
//
|
|
359
|
+
// It is kept as insurance against exactly that internal changing. If a
|
|
360
|
+
// dnd-kit bump ever emits a non-null transform on a re-seat frame, deleting
|
|
361
|
+
// these lines silently reinstates the runaway — and no test can catch it,
|
|
362
|
+
// because jsdom lays nothing out, so the clamp's only evidence is a manual
|
|
363
|
+
// browser drag. Two inert guarded lines against a hand-only regression.
|
|
364
|
+
const boundsRef = useRef<KanbanDragBounds | null>(null);
|
|
365
|
+
useLayoutEffect(() => {
|
|
366
|
+
const node = cardRef.current;
|
|
367
|
+
if (!isDragging || !node) {
|
|
368
|
+
boundsRef.current = null;
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
boundsRef.current = measureKanbanDragBounds(node);
|
|
372
|
+
const bounded = CSS.Transform.toString(clampKanbanTransform(transform, boundsRef.current));
|
|
373
|
+
if (bounded) node.style.transform = bounded;
|
|
374
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- transform is deliberately not a dependency, see above
|
|
375
|
+
}, [isDragging, index, items]);
|
|
376
|
+
|
|
377
|
+
const setRef = (node: HTMLDivElement | null) => {
|
|
378
|
+
cardRef.current = node;
|
|
379
|
+
setNodeRef(node);
|
|
380
|
+
// When no Handle is present, the Card itself is the drag activator.
|
|
381
|
+
if (!hasHandle) setActivatorNodeRef(node);
|
|
382
|
+
if (typeof ref === 'function') ref(node);
|
|
383
|
+
else if (ref) ref.current = node;
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
return (
|
|
387
|
+
<SortableItemContext.Provider value={ctxValue}>
|
|
388
|
+
<div
|
|
389
|
+
ref={setRef}
|
|
390
|
+
// Reading the ref during render is deliberate: the bound is written by
|
|
391
|
+
// the layout effect above and must be applied on the very next pointer
|
|
392
|
+
// move, without a re-render of its own.
|
|
393
|
+
style={{
|
|
394
|
+
transform: CSS.Transform.toString(clampKanbanTransform(transform, boundsRef.current)),
|
|
395
|
+
transition,
|
|
396
|
+
}}
|
|
397
|
+
data-dragging={isDragging ? 'true' : undefined}
|
|
398
|
+
className={clsx(styles.card, className)}
|
|
399
|
+
// dnd-kit drag attrs BEFORE {...rest} so consumer rest wins.
|
|
400
|
+
{...(hasHandle ? {} : listeners)}
|
|
401
|
+
{...(hasHandle ? {} : attributes)}
|
|
402
|
+
{...rest}
|
|
403
|
+
// role="article" AFTER {...rest} so it wins, but only when a Handle
|
|
404
|
+
// is present. Without a Handle, dnd-kit's role="button" is correct
|
|
405
|
+
// (Card IS the drag activator).
|
|
406
|
+
{...(hasHandle ? { role: 'article' } : {})}
|
|
407
|
+
>
|
|
408
|
+
{children}
|
|
409
|
+
</div>
|
|
410
|
+
</SortableItemContext.Provider>
|
|
411
|
+
);
|
|
412
|
+
});
|
|
413
|
+
KanbanCard.displayName = 'KanbanCard';
|
|
414
|
+
|
|
415
|
+
// ---------------------------------------------------------------------------
|
|
416
|
+
// KanbanColumn
|
|
417
|
+
// ---------------------------------------------------------------------------
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* One swimlane in a `<Kanban>`; needs a stable `id`.
|
|
421
|
+
* @see docs/components/Kanban.md
|
|
422
|
+
*/
|
|
423
|
+
export const KanbanColumn = forwardRef<HTMLDivElement, KanbanColumnProps>(function KanbanColumn(
|
|
424
|
+
{ id, className, children, ...rest },
|
|
425
|
+
ref,
|
|
426
|
+
) {
|
|
427
|
+
const { setNodeRef } = useDroppable({ id });
|
|
428
|
+
|
|
429
|
+
// Effective card ids are injected by KanbanRoot via context so that this
|
|
430
|
+
// column's SortableContext sees the live-reordered order.
|
|
431
|
+
const ctxCardIds = useContext(KanbanColumnItemsContext);
|
|
432
|
+
|
|
433
|
+
// Fallback: derive card ids from children if not in context (standalone use).
|
|
434
|
+
const fallbackCardIds = useMemo(() => {
|
|
435
|
+
if (ctxCardIds !== null) return null; // skip computation
|
|
436
|
+
const ids: (string | number)[] = [];
|
|
437
|
+
Children.forEach(children, (card) => {
|
|
438
|
+
if (!isValidElement(card) || card.type !== KanbanCard) return;
|
|
439
|
+
ids.push((card.props as KanbanCardProps).id);
|
|
440
|
+
});
|
|
441
|
+
return ids;
|
|
442
|
+
}, [children, ctxCardIds]);
|
|
443
|
+
|
|
444
|
+
const cardIds = ctxCardIds ?? fallbackCardIds ?? [];
|
|
445
|
+
|
|
446
|
+
const setRef = (node: HTMLDivElement | null) => {
|
|
447
|
+
setNodeRef(node);
|
|
448
|
+
if (typeof ref === 'function') ref(node);
|
|
449
|
+
else if (ref) ref.current = node;
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
return (
|
|
453
|
+
<div
|
|
454
|
+
ref={setRef}
|
|
455
|
+
className={clsx(styles.column, className)}
|
|
456
|
+
{...rest}
|
|
457
|
+
// A name on a role-less <div> is inert, so a labelled column gets a role
|
|
458
|
+
// to hang it on. AFTER {...rest} — but only when the consumer didn't
|
|
459
|
+
// already pick a role — so labelling can't silently do nothing.
|
|
460
|
+
{...((rest['aria-label'] || rest['aria-labelledby']) && rest.role == null
|
|
461
|
+
? { role: 'group' }
|
|
462
|
+
: {})}
|
|
463
|
+
>
|
|
464
|
+
<SortableContext
|
|
465
|
+
items={cardIds as (string | number)[]}
|
|
466
|
+
strategy={verticalListSortingStrategy}
|
|
467
|
+
>
|
|
468
|
+
{children}
|
|
469
|
+
</SortableContext>
|
|
470
|
+
</div>
|
|
471
|
+
);
|
|
472
|
+
});
|
|
473
|
+
KanbanColumn.displayName = 'KanbanColumn';
|
|
474
|
+
|
|
475
|
+
// ---------------------------------------------------------------------------
|
|
476
|
+
// KanbanRoot
|
|
477
|
+
// ---------------------------------------------------------------------------
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Multi-column drag-and-drop board with live cross-column reflow (`Kanban`, `Kanban.Column`, `Kanban.Card`, `Kanban.Handle`).
|
|
481
|
+
* @see docs/components/Kanban.md
|
|
482
|
+
*/
|
|
483
|
+
const KanbanRoot = forwardRef<HTMLDivElement, KanbanProps>(function KanbanRoot(
|
|
484
|
+
{ onMove, className, children, ...rest },
|
|
485
|
+
ref,
|
|
486
|
+
) {
|
|
487
|
+
const t = useTranslation();
|
|
488
|
+
// ------------------------------------------------------------------
|
|
489
|
+
// Parse children: extract column order, initial items, card elements,
|
|
490
|
+
// and the before/after non-card slices for each column.
|
|
491
|
+
// columnElements is built in the same pass so render-time lookup is O(1)
|
|
492
|
+
// instead of O(N) per column (i.e., O(N²) total per render).
|
|
493
|
+
// ------------------------------------------------------------------
|
|
494
|
+
const { columnOrder, columnElements, initialItems, cardElements, columnChildrenSlices } =
|
|
495
|
+
useMemo(() => {
|
|
496
|
+
const colOrder: (string | number)[] = [];
|
|
497
|
+
const colElems = new Map<string | number, ReactElement<KanbanColumnProps>>();
|
|
498
|
+
const initItems = new Map<string | number, (string | number)[]>();
|
|
499
|
+
const cardElems = new Map<string | number, ReactElement<KanbanCardProps>>();
|
|
500
|
+
const colSlices = new Map<string | number, { before: ReactNode[]; after: ReactNode[] }>();
|
|
501
|
+
|
|
502
|
+
Children.forEach(children, (child) => {
|
|
503
|
+
if (!isValidElement(child) || child.type !== KanbanColumn) return;
|
|
504
|
+
const columnElement = child as ReactElement<KanbanColumnProps>;
|
|
505
|
+
const colProps = columnElement.props;
|
|
506
|
+
const colId = colProps.id;
|
|
507
|
+
colOrder.push(colId);
|
|
508
|
+
colElems.set(colId, columnElement);
|
|
509
|
+
|
|
510
|
+
const colCardIds: (string | number)[] = [];
|
|
511
|
+
let firstCardIdx = -1;
|
|
512
|
+
let lastCardIdx = -1;
|
|
513
|
+
const colChildren = Children.toArray(colProps.children);
|
|
514
|
+
|
|
515
|
+
colChildren.forEach((grandchild, idx) => {
|
|
516
|
+
if (!isValidElement(grandchild) || grandchild.type !== KanbanCard) return;
|
|
517
|
+
const cardProps = grandchild.props as KanbanCardProps;
|
|
518
|
+
colCardIds.push(cardProps.id);
|
|
519
|
+
cardElems.set(cardProps.id, grandchild as ReactElement<KanbanCardProps>);
|
|
520
|
+
if (firstCardIdx < 0) firstCardIdx = idx;
|
|
521
|
+
lastCardIdx = idx;
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
// Split into before/after slices (non-card children outside the card block).
|
|
525
|
+
const before: ReactNode[] =
|
|
526
|
+
firstCardIdx >= 0 ? colChildren.slice(0, firstCardIdx) : colChildren;
|
|
527
|
+
const after: ReactNode[] = lastCardIdx >= 0 ? colChildren.slice(lastCardIdx + 1) : [];
|
|
528
|
+
|
|
529
|
+
initItems.set(colId, colCardIds);
|
|
530
|
+
colSlices.set(colId, { before, after });
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
return {
|
|
534
|
+
columnOrder: colOrder,
|
|
535
|
+
columnElements: colElems,
|
|
536
|
+
initialItems: initItems,
|
|
537
|
+
cardElements: cardElems,
|
|
538
|
+
columnChildrenSlices: colSlices,
|
|
539
|
+
};
|
|
540
|
+
}, [children]);
|
|
541
|
+
|
|
542
|
+
// ------------------------------------------------------------------
|
|
543
|
+
// Stable serialization of initialItems to detect consumer state changes.
|
|
544
|
+
// JSON.stringify is robust against ids that contain `:` or `,` characters,
|
|
545
|
+
// unlike a hand-rolled delimiter-join.
|
|
546
|
+
// ------------------------------------------------------------------
|
|
547
|
+
const initialItemsKey = useMemo(
|
|
548
|
+
() => JSON.stringify(Array.from(initialItems.entries())),
|
|
549
|
+
[initialItems],
|
|
550
|
+
);
|
|
551
|
+
|
|
552
|
+
// ------------------------------------------------------------------
|
|
553
|
+
// Internal drag state: null when idle, populated on drag start
|
|
554
|
+
// ------------------------------------------------------------------
|
|
555
|
+
const [liveItems, setLiveItems] = useState<Map<string | number, (string | number)[]> | null>(
|
|
556
|
+
null,
|
|
557
|
+
);
|
|
558
|
+
|
|
559
|
+
// Reset live items when consumer state mutates (e.g. external update mid-drag).
|
|
560
|
+
useEffect(() => {
|
|
561
|
+
setLiveItems(null);
|
|
562
|
+
}, [initialItemsKey]);
|
|
563
|
+
|
|
564
|
+
const effectiveItems = liveItems ?? initialItems;
|
|
565
|
+
|
|
566
|
+
// ------------------------------------------------------------------
|
|
567
|
+
// Sensors
|
|
568
|
+
// ------------------------------------------------------------------
|
|
569
|
+
const sensors = useSensors(
|
|
570
|
+
useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
|
|
571
|
+
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
// ------------------------------------------------------------------
|
|
575
|
+
// Container lookup: given an id (card or column), find which column owns it
|
|
576
|
+
// ------------------------------------------------------------------
|
|
577
|
+
const findContainer = useCallback(
|
|
578
|
+
(
|
|
579
|
+
id: string | number,
|
|
580
|
+
items: Map<string | number, (string | number)[]>,
|
|
581
|
+
): string | number | null => {
|
|
582
|
+
if (items.has(id)) return id; // id is a column id itself (drop on empty column)
|
|
583
|
+
for (const [colId, cards] of items) {
|
|
584
|
+
if (cards.includes(id)) return colId;
|
|
585
|
+
}
|
|
586
|
+
return null;
|
|
587
|
+
},
|
|
588
|
+
[],
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
// ------------------------------------------------------------------
|
|
592
|
+
// Drag handlers
|
|
593
|
+
// ------------------------------------------------------------------
|
|
594
|
+
const originRef = useRef<string | number | null>(null);
|
|
595
|
+
|
|
596
|
+
// Was the pointer outside every column the last time a drop target was
|
|
597
|
+
// resolved? `DragEndEvent` carries no pointer position — only `delta`, which
|
|
598
|
+
// is `translate - nodeRectDelta + scrollAdjustment` and so drifts from the
|
|
599
|
+
// real pointer by however far the board auto-scrolled and however far a live
|
|
600
|
+
// re-seat moved the card. Collision detection, on the other hand, is handed
|
|
601
|
+
// dnd-kit's own `pointerCoordinates` alongside the very rects `over` was
|
|
602
|
+
// ranked from, so answering the question there and remembering the answer
|
|
603
|
+
// makes the cancel decision agree with `over` by construction (#387).
|
|
604
|
+
//
|
|
605
|
+
// Written during render (collision detection runs in `DndContext`'s render);
|
|
606
|
+
// that is safe because it is a pure function of the arguments, so a double
|
|
607
|
+
// render under StrictMode writes the same value.
|
|
608
|
+
const outsideBoardRef = useRef(false);
|
|
609
|
+
const collisionDetection = useCallback<CollisionDetection>((args) => {
|
|
610
|
+
const isOutsideBoard = isPointerOutsideContainers(args);
|
|
611
|
+
outsideBoardRef.current = isOutsideBoard;
|
|
612
|
+
// Force an `over` transition to null at the board boundary. dnd-kit's
|
|
613
|
+
// announcement callback only runs when `over.id` changes, so retaining the
|
|
614
|
+
// nearest collision here would leave screen readers naming a stale column.
|
|
615
|
+
if (isOutsideBoard) return [];
|
|
616
|
+
return containerAwareClosestCorners(args);
|
|
617
|
+
}, []);
|
|
618
|
+
|
|
619
|
+
const handleDragStart = useCallback(
|
|
620
|
+
(event: DragStartEvent) => {
|
|
621
|
+
outsideBoardRef.current = false;
|
|
622
|
+
// The column the card started in. Used by handleDragOver to tell a
|
|
623
|
+
// genuine same-column reorder apart from "already transited, still
|
|
624
|
+
// moving inside the destination".
|
|
625
|
+
originRef.current = findContainer(event.active.id as string | number, initialItems);
|
|
626
|
+
// Snapshot: shallow-copy each cards array so mutations don't leak into initialItems.
|
|
627
|
+
const snapshot = new Map<string | number, (string | number)[]>();
|
|
628
|
+
for (const [colId, cards] of initialItems) snapshot.set(colId, [...cards]);
|
|
629
|
+
setLiveItems(snapshot);
|
|
630
|
+
},
|
|
631
|
+
[initialItems, findContainer],
|
|
632
|
+
);
|
|
633
|
+
|
|
634
|
+
const handleDragOver = useCallback(
|
|
635
|
+
(event: DragOverEvent) => {
|
|
636
|
+
const { active, over } = event;
|
|
637
|
+
if (!over) return;
|
|
638
|
+
const activeId = active.id as string | number;
|
|
639
|
+
const overId = over.id as string | number;
|
|
640
|
+
if (activeId === overId) return;
|
|
641
|
+
|
|
642
|
+
setLiveItems((prev) => {
|
|
643
|
+
if (!prev) return prev;
|
|
644
|
+
const activeContainer = findContainer(activeId, prev);
|
|
645
|
+
const overContainer = findContainer(overId, prev);
|
|
646
|
+
if (activeContainer == null || overContainer == null) return prev;
|
|
647
|
+
|
|
648
|
+
const isTransit = activeContainer !== overContainer;
|
|
649
|
+
// Pointer in the padding of the column the card has already transited
|
|
650
|
+
// into (no card under it). `over` is the column, so the sortable
|
|
651
|
+
// strategy computes overIndex === -1 and applies NO displacement —
|
|
652
|
+
// re-appending here is therefore stable (it can't fight a transform)
|
|
653
|
+
// and it's exactly what the user sees. Cheap to keep in sync.
|
|
654
|
+
const isForeignAppend =
|
|
655
|
+
!isTransit && overId === overContainer && overContainer !== originRef.current;
|
|
656
|
+
// Everything else with active and over in the same column is finalized
|
|
657
|
+
// at dragEnd from `over` (see handleDragEnd). Re-seating the card here
|
|
658
|
+
// on every dragOver would oscillate: the insertion re-measures the
|
|
659
|
+
// rects, which changes `over`, which re-fires this handler — and the
|
|
660
|
+
// strategy would still displace siblings on top of the new order, so
|
|
661
|
+
// the commit would drift from the preview again.
|
|
662
|
+
if (!isTransit && !isForeignAppend) return prev;
|
|
663
|
+
|
|
664
|
+
const activeCards = prev.get(activeContainer) ?? [];
|
|
665
|
+
if (!activeCards.includes(activeId)) return prev;
|
|
666
|
+
if (isForeignAppend && activeCards[activeCards.length - 1] === activeId) return prev;
|
|
667
|
+
|
|
668
|
+
// Slot math runs against the destination WITHOUT the active card, so
|
|
669
|
+
// the card's own slot is never counted (off-by-one when moving down).
|
|
670
|
+
const overCards = (prev.get(overContainer) ?? []).filter((id) => id !== activeId);
|
|
671
|
+
|
|
672
|
+
// Resolve the target slot inside overContainer:
|
|
673
|
+
// - over.id is a sibling card → use that card's index, plus 1 if
|
|
674
|
+
// the cursor is past the over card's vertical midpoint (active's
|
|
675
|
+
// translated rect tracks the cursor centered around it).
|
|
676
|
+
// - over.id is the column itself (empty drop / padding) → append
|
|
677
|
+
//
|
|
678
|
+
// The `isBelow` +1 rarely survives to the commit: the strategy
|
|
679
|
+
// immediately re-renders the card at `overIndex` and dragEnd commits
|
|
680
|
+
// from `over`, so both sides of the +1 produce the same drop. It still
|
|
681
|
+
// decides the seeded slot the frame after transit (while dnd-kit has
|
|
682
|
+
// transforms disabled to re-measure), and dragEnd's fallbacks — `over`
|
|
683
|
+
// resolving to the column or to the dragged card itself — commit that
|
|
684
|
+
// seeded slot verbatim. Keep it; without it the card visibly jumps a
|
|
685
|
+
// slot on entry.
|
|
686
|
+
let insertAt: number;
|
|
687
|
+
if (overCards.includes(overId)) {
|
|
688
|
+
const overIdx = overCards.indexOf(overId);
|
|
689
|
+
const activeRect = active.rect.current.translated;
|
|
690
|
+
const overRect = over.rect;
|
|
691
|
+
const isBelow =
|
|
692
|
+
activeRect != null &&
|
|
693
|
+
activeRect.top + activeRect.height / 2 > overRect.top + overRect.height / 2;
|
|
694
|
+
insertAt = overIdx + (isBelow ? 1 : 0);
|
|
695
|
+
} else if (overId === overContainer) {
|
|
696
|
+
insertAt = overCards.length;
|
|
697
|
+
} else {
|
|
698
|
+
return prev;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
const next = new Map(prev);
|
|
702
|
+
if (isTransit) {
|
|
703
|
+
next.set(
|
|
704
|
+
activeContainer,
|
|
705
|
+
activeCards.filter((id) => id !== activeId),
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
const newOver = [...overCards];
|
|
709
|
+
newOver.splice(insertAt, 0, activeId);
|
|
710
|
+
next.set(overContainer, newOver);
|
|
711
|
+
return next;
|
|
712
|
+
});
|
|
713
|
+
},
|
|
714
|
+
[findContainer],
|
|
715
|
+
);
|
|
716
|
+
|
|
717
|
+
const handleDragEnd = useCallback(
|
|
718
|
+
(event: DragEndEvent) => {
|
|
719
|
+
const { active, over } = event;
|
|
720
|
+
const activeId = active.id as string | number;
|
|
721
|
+
|
|
722
|
+
// Two ways a drag ends in nothing, and `over` only reports the first:
|
|
723
|
+
// - `over == null` — collision detection returned nothing at all, i.e.
|
|
724
|
+
// no droppable was measured.
|
|
725
|
+
// - the pointer left the board. `containerAwareClosestCorners` falls
|
|
726
|
+
// back to `closestCorners`, which ranks every measured droppable and
|
|
727
|
+
// rejects none, so `over` still names the nearest column after a
|
|
728
|
+
// release over unrelated page content — a column the user never aimed
|
|
729
|
+
// at and never saw highlighted. Committing it silently reassigns the
|
|
730
|
+
// card's column (#387), so the pointer test decides instead.
|
|
731
|
+
// Both snap the card back: dropping `liveItems` re-renders every column
|
|
732
|
+
// from the consumer's untouched state, and no `onMove` fires.
|
|
733
|
+
if (!over || outsideBoardRef.current) {
|
|
734
|
+
setLiveItems(null);
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
const overId = over.id as string | number;
|
|
739
|
+
const finalItems = liveItems ?? initialItems;
|
|
740
|
+
|
|
741
|
+
// Source (from-position): the card's pre-drag location.
|
|
742
|
+
let fromColumn: string | number | null = null;
|
|
743
|
+
let fromIndex = -1;
|
|
744
|
+
for (const [colId, cards] of initialItems) {
|
|
745
|
+
const idx = cards.indexOf(activeId);
|
|
746
|
+
if (idx >= 0) {
|
|
747
|
+
fromColumn = colId;
|
|
748
|
+
fromIndex = idx;
|
|
749
|
+
break;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
if (fromColumn == null) {
|
|
754
|
+
setLiveItems(null);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// Where does active currently live in liveItems? handleDragOver seats
|
|
759
|
+
// it in the destination on transit; the exact slot is resolved HERE,
|
|
760
|
+
// from `over`, because that's what the user was looking at:
|
|
761
|
+
// `verticalListSortingStrategy` renders each column as
|
|
762
|
+
// `arrayMove(liveItems[col], activeIndex, overIndex)` with
|
|
763
|
+
// `overIndex = liveItems[col].indexOf(over.id)`, so the previewed slot
|
|
764
|
+
// is `overIndex` whenever `over` is a sibling card, and the card's live
|
|
765
|
+
// slot when it isn't (`overIndex === -1` → the strategy displaces
|
|
766
|
+
// nothing). Committing the live slot alone would pin the drop to the
|
|
767
|
+
// boundary-crossing point (#376).
|
|
768
|
+
let liveColumn: string | number | null = null;
|
|
769
|
+
let liveIdx = -1;
|
|
770
|
+
for (const [colId, cards] of finalItems) {
|
|
771
|
+
const idx = cards.indexOf(activeId);
|
|
772
|
+
if (idx >= 0) {
|
|
773
|
+
liveColumn = colId;
|
|
774
|
+
liveIdx = idx;
|
|
775
|
+
break;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
let toColumn: string | number;
|
|
780
|
+
let toIndex: number;
|
|
781
|
+
|
|
782
|
+
if (liveColumn != null && liveColumn !== fromColumn) {
|
|
783
|
+
// Cross-column drop: the previewed slot wins (see the comment above).
|
|
784
|
+
toColumn = liveColumn;
|
|
785
|
+
const destCards = finalItems.get(liveColumn) ?? [];
|
|
786
|
+
const overIdx = overId === activeId ? -1 : destCards.indexOf(overId);
|
|
787
|
+
toIndex = overIdx >= 0 ? overIdx : liveIdx;
|
|
788
|
+
} else if (initialItems.has(overId)) {
|
|
789
|
+
// Within-source-column drop on the column's padding (no card target).
|
|
790
|
+
// Default to "drop at end" so the card lands where the user dragged
|
|
791
|
+
// it, rather than snapping back to its original slot.
|
|
792
|
+
toColumn = fromColumn;
|
|
793
|
+
const fromCol = initialItems.get(fromColumn) ?? [];
|
|
794
|
+
toIndex = fromCol.length - 1;
|
|
795
|
+
} else if (overId === activeId) {
|
|
796
|
+
// Cursor over the dragged card itself. The strategy displaces nothing
|
|
797
|
+
// (overIndex === activeIndex), so the preview IS the live order —
|
|
798
|
+
// commit that. A barely-moved drag resolves to its own starting index
|
|
799
|
+
// and gets dropped by the no-op guard below; a card that came back to
|
|
800
|
+
// its origin column lands where the preview put it.
|
|
801
|
+
toColumn = fromColumn;
|
|
802
|
+
toIndex = liveIdx;
|
|
803
|
+
} else {
|
|
804
|
+
// Within-column drop on a sibling card. arrayMove semantics:
|
|
805
|
+
// toIndex = sibling's index in the live column — same rule as the
|
|
806
|
+
// cross-column branch. For a plain same-column drag liveItems is
|
|
807
|
+
// untouched, so this is the sibling's index in the consumer's
|
|
808
|
+
// pre-move state; if the card visited another column and came back,
|
|
809
|
+
// the live order is what the preview showed, so it wins.
|
|
810
|
+
// For cross-column drops where the transit didn't commit (rare
|
|
811
|
+
// stale-closure case at the very first dragOver), the sibling may
|
|
812
|
+
// live in a different column — resolve the column from the card.
|
|
813
|
+
let overCol: string | number | null = null;
|
|
814
|
+
for (const [colId, cards] of finalItems) {
|
|
815
|
+
if (cards.includes(overId)) {
|
|
816
|
+
overCol = colId;
|
|
817
|
+
break;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
if (overCol == null) {
|
|
821
|
+
setLiveItems(null);
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
toColumn = overCol;
|
|
825
|
+
toIndex = (finalItems.get(toColumn) ?? []).indexOf(overId);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
setLiveItems(null);
|
|
829
|
+
|
|
830
|
+
if (toIndex < 0) return;
|
|
831
|
+
if (fromColumn === toColumn && fromIndex === toIndex) return;
|
|
832
|
+
|
|
833
|
+
onMove?.({
|
|
834
|
+
from: { columnId: fromColumn, index: fromIndex },
|
|
835
|
+
to: { columnId: toColumn, index: toIndex },
|
|
836
|
+
cardId: activeId,
|
|
837
|
+
});
|
|
838
|
+
},
|
|
839
|
+
[liveItems, initialItems, onMove],
|
|
840
|
+
);
|
|
841
|
+
|
|
842
|
+
const handleDragCancel = useCallback(() => {
|
|
843
|
+
setLiveItems(null);
|
|
844
|
+
}, []);
|
|
845
|
+
|
|
846
|
+
// ------------------------------------------------------------------
|
|
847
|
+
// Screen-reader announcements (Hard rule 9, #390)
|
|
848
|
+
// ------------------------------------------------------------------
|
|
849
|
+
// Names a card by its own text and a column by the `aria-label` the consumer
|
|
850
|
+
// put on `<Kanban.Column>` (falling back to its board position), so a drag
|
|
851
|
+
// says "Acme — 40 seats, position 2 of 3 in Qualified" rather than dnd-kit's
|
|
852
|
+
// "deal-1 was moved over droppable area col-2". Slots are read from
|
|
853
|
+
// `effectiveItems` — the order the user is actually looking at mid-drag.
|
|
854
|
+
// `||`, not `??`, for the reason SortableGroup's twin site states: already
|
|
855
|
+
// safe, because `containerName` rejects a blank `aria-label` itself, but
|
|
856
|
+
// aligned so the operator is not the thing a reader has to verify.
|
|
857
|
+
const columnLabel = (colId: string | number): string =>
|
|
858
|
+
containerName(columnElements.get(colId)?.props ?? {}) ||
|
|
859
|
+
t('kanban.unnamedColumn', {
|
|
860
|
+
index: columnOrder.indexOf(colId) + 1,
|
|
861
|
+
total: columnOrder.length,
|
|
862
|
+
});
|
|
863
|
+
const describeDrag: DescribeDragTarget = (entry, activeId) => {
|
|
864
|
+
if (!entry) return null;
|
|
865
|
+
const id = entry.id as string | number;
|
|
866
|
+
const isColumn = effectiveItems.has(id);
|
|
867
|
+
const colId = isColumn ? id : findContainer(id, effectiveItems);
|
|
868
|
+
if (colId == null) return null;
|
|
869
|
+
const cards = effectiveItems.get(colId) ?? [];
|
|
870
|
+
const label = dragLabelOf(entry.data.current);
|
|
871
|
+
const container = columnLabel(colId);
|
|
872
|
+
if (!isColumn) return { label, index: cards.indexOf(id) + 1, total: cards.length, container };
|
|
873
|
+
// `over` is the column itself (empty column, or the padding below the
|
|
874
|
+
// cards). If the dragged card has already been live-seated here it keeps
|
|
875
|
+
// its slot; otherwise it would be appended, so count it as one more.
|
|
876
|
+
const seated = cards.indexOf(activeId as string | number);
|
|
877
|
+
return seated >= 0
|
|
878
|
+
? { label, index: seated + 1, total: cards.length, container }
|
|
879
|
+
: { label, index: cards.length + 1, total: cards.length + 1, container };
|
|
880
|
+
};
|
|
881
|
+
// A release outside the board is a cancel, not a drop — `over` is non-null on
|
|
882
|
+
// that path (closestCorners rejects nothing), so without this dnd-kit would
|
|
883
|
+
// announce a drop that handleDragEnd deliberately threw away (#387).
|
|
884
|
+
const accessibility = useDragAccessibility(describeDrag, () => outsideBoardRef.current);
|
|
885
|
+
|
|
886
|
+
// ------------------------------------------------------------------
|
|
887
|
+
// Render
|
|
888
|
+
// ------------------------------------------------------------------
|
|
889
|
+
return (
|
|
890
|
+
<DndContext
|
|
891
|
+
accessibility={accessibility}
|
|
892
|
+
sensors={sensors}
|
|
893
|
+
collisionDetection={collisionDetection}
|
|
894
|
+
onDragStart={handleDragStart}
|
|
895
|
+
onDragOver={handleDragOver}
|
|
896
|
+
onDragEnd={handleDragEnd}
|
|
897
|
+
onDragCancel={handleDragCancel}
|
|
898
|
+
>
|
|
899
|
+
{/*
|
|
900
|
+
aria-label BEFORE {...rest} so consumer can pass a more specific
|
|
901
|
+
label (e.g. "Q3 sales pipeline") and have it win.
|
|
902
|
+
role="region" AFTER {...rest} so it stays locked — non-overridable
|
|
903
|
+
semantic guarantee.
|
|
904
|
+
*/}
|
|
905
|
+
<div
|
|
906
|
+
ref={ref}
|
|
907
|
+
aria-label={t('kanban.board')}
|
|
908
|
+
className={clsx(styles.board, className)}
|
|
909
|
+
{...rest}
|
|
910
|
+
role="region"
|
|
911
|
+
>
|
|
912
|
+
{columnOrder.map((colId) => {
|
|
913
|
+
const columnElement = columnElements.get(colId);
|
|
914
|
+
if (!columnElement) return null;
|
|
915
|
+
|
|
916
|
+
const cardIds = effectiveItems.get(colId) ?? [];
|
|
917
|
+
const cards = cardIds
|
|
918
|
+
.map((id) => cardElements.get(id))
|
|
919
|
+
.filter((c): c is ReactElement<KanbanCardProps> => c != null);
|
|
920
|
+
|
|
921
|
+
const slice = columnChildrenSlices.get(colId) ?? { before: [], after: [] };
|
|
922
|
+
|
|
923
|
+
// Re-clone the column with re-arranged children. KanbanColumn reads
|
|
924
|
+
// cardIds via KanbanColumnItemsContext so its SortableContext.items
|
|
925
|
+
// matches what's rendered.
|
|
926
|
+
return (
|
|
927
|
+
<KanbanColumnItemsContext.Provider key={colId} value={cardIds}>
|
|
928
|
+
{cloneElement(columnElement, {}, ...slice.before, ...cards, ...slice.after)}
|
|
929
|
+
</KanbanColumnItemsContext.Provider>
|
|
930
|
+
);
|
|
931
|
+
})}
|
|
932
|
+
</div>
|
|
933
|
+
</DndContext>
|
|
934
|
+
);
|
|
935
|
+
});
|
|
936
|
+
KanbanRoot.displayName = 'Kanban';
|
|
937
|
+
|
|
938
|
+
// ---------------------------------------------------------------------------
|
|
939
|
+
// Compound export
|
|
940
|
+
// ---------------------------------------------------------------------------
|
|
941
|
+
|
|
942
|
+
export const Kanban = Object.assign(KanbanRoot, {
|
|
943
|
+
Column: KanbanColumn,
|
|
944
|
+
Card: KanbanCard,
|
|
945
|
+
/**
|
|
946
|
+
* Re-exported from `<Sortable.Handle>`. Attaches drag listeners when placed
|
|
947
|
+
* inside a `<Kanban.Card>`. When present, only the Handle initiates drag
|
|
948
|
+
* (the Card div gets `role="article"`).
|
|
949
|
+
*/
|
|
950
|
+
Handle: SortableHandle,
|
|
951
|
+
});
|