@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,158 @@
|
|
|
1
|
+
# `<Calendar>` — month / week / day / agenda views
|
|
2
|
+
|
|
3
|
+
```tsx
|
|
4
|
+
// Uncontrolled
|
|
5
|
+
<Calendar events={events} defaultView="week" />;
|
|
6
|
+
|
|
7
|
+
// Controlled cursor + view
|
|
8
|
+
const [cursor, setCursor] = useState(new Date());
|
|
9
|
+
const [view, setView] = useState<CalendarView>('month');
|
|
10
|
+
<Calendar
|
|
11
|
+
value={cursor}
|
|
12
|
+
onChange={setCursor}
|
|
13
|
+
view={view}
|
|
14
|
+
onViewChange={setView}
|
|
15
|
+
events={events}
|
|
16
|
+
onEventClick={(e) => openDetail(e)}
|
|
17
|
+
/>;
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
<!-- props:start -->
|
|
21
|
+
|
|
22
|
+
## Props
|
|
23
|
+
|
|
24
|
+
<!-- prettier-ignore -->
|
|
25
|
+
| Prop | Type | Required | Description |
|
|
26
|
+
|---|---|---|---|
|
|
27
|
+
| `events` | `readonly CalendarEvent[]` | no | Events to display. |
|
|
28
|
+
| `view` | `'month' \| 'week' \| 'day' \| 'agenda'` | no | Active view (controlled). Pair with `onViewChange`. |
|
|
29
|
+
| `defaultView` | `'month' \| 'week' \| 'day' \| 'agenda'` | no | Initial view (uncontrolled). Defaults to `'month'`. |
|
|
30
|
+
| `onViewChange` | `((view: CalendarView) => void)` | no | Fires when the user clicks the view switcher. |
|
|
31
|
+
| `value` | `Date` | no | Navigation cursor (controlled). |
|
|
32
|
+
| `defaultValue` | `Date` | no | Initial cursor (uncontrolled). Defaults to `new Date()`. |
|
|
33
|
+
| `onChange` | `((date: Date) => void)` | no | Fires on prev/next/today/keyboard navigation. |
|
|
34
|
+
| `onDayClick` | `((date: Date) => void)` | no | Fires when the user clicks a day cell's empty space. - Month view: empty space in a day cell, the "+N more" overflow chip, or keyboard activation (Enter/Space) on a focused cell. - Week/Day view: empty hour-grid space inside that day's column — **mouse click only**. There is no keyboard equivalent in v3; consumers needing keyboard activation in week/day should surface their detail UI through the focusable event chips (which fire `onEventClick`). |
|
|
35
|
+
| `onEventClick` | `((event: CalendarEvent) => void)` | no | Fires when the user clicks an event chip. |
|
|
36
|
+
| `locale` | `string` | no | Override locale. Defaults to `useLocale()`. |
|
|
37
|
+
| `weekStartsOn` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6` | no | Override locale-derived first day of week. |
|
|
38
|
+
| `maxLanesPerWeek` | `number` | no | Lane cap per week (month view). Default 3. Events beyond it collapse into a "+N more" chip; clicking it fires `onDayClick(date)`. |
|
|
39
|
+
| `hourRange` | `[number, number]` | no | Hour range shown in week/day views (inclusive start, exclusive end). Defaults to `[7, 19]` (7am–7pm). Hours outside this range are NOT rendered (no row in the grid). |
|
|
40
|
+
| `hourRowHeight` | `number` | no | Pixel height per hour row in week/day views. Default 48. |
|
|
41
|
+
| `resources` | `readonly CalendarResource[]` | no | Split `view="day"` into one column per bookable subject — practitioner, chair, room, bay — all sharing a single time axis and a single scroll container. This is the "resource day view" of scheduling products. Events are routed by `CalendarEvent.resourceId`. A timed event whose `resourceId` matches nothing (or is absent) lands in a trailing "unassigned" column, which is only rendered when such an event exists. All-day events without a `resourceId` are day-wide and span every column. Ignored by month, week and agenda views — those have their own column meaning. Don't approximate it with N side-by-side `<Calendar view="day">` in a `Split`: each brings its own header, gutter and scroll, so they drift. |
|
|
42
|
+
| `backgroundIntervals` | `readonly CalendarBackgroundInterval[]` | no | Availability underlay for week/day views: non-interactive bands painted behind the events, distinguishing "closed" from "open and free". "Open and busy" already reads from the event blocks themselves. An interval list rather than a weekly-hours object, because working hours vary by day (a lunch break), by date (a holiday, a short day) and by column (per-practitioner shifts). Bands take no part in the event collision cascade and never intercept clicks — `onDayClick` still fires through them. Every interval is clipped to each column's own date, so a week view needs one interval per day rather than one that all seven columns share. |
|
|
43
|
+
| `onEventMove` | `((event: CalendarEvent, next: CalendarEventMove) => CalendarDropResult)` | no | Enables drag-to-reschedule in week/day views. Called on drop with the proposed placement; the event's duration is preserved. **Nothing moves until you commit it.** `events` stays the source of truth — apply the change to your own state to make it stick. To refuse a drop (the server rejects overlaps, out-of-hours bookings, …) return `false`, or a promise that resolves `false` or rejects: the block snaps back and the preview is discarded. Prefer `canDropEvent` for rules you can evaluate synchronously — it refuses the drop *during* the drag, so the user sees it before releasing. Omit to keep today's read-only behaviour. |
|
|
44
|
+
| `onEventResize` | `((event: CalendarEvent, next: CalendarEventResize) => CalendarDropResult)` | no | Enables drag-to-resize in week/day views (a handle on the block's bottom edge). `startsAt` is unchanged; only `endsAt` moves. Same commit and rejection semantics as `onEventMove`. |
|
|
45
|
+
| `canDropEvent` | `((event: CalendarEvent, next: CalendarDropCandidate) => boolean)` | no | Synchronous veto, evaluated continuously while dragging. Return `false` and the placement renders as a refused drop and is never proposed to `onEventMove` / `onEventResize`. Use for rules you can check locally (opening hours, overlaps within the loaded event set); use the handler's own return value for rules only the server can settle. `next.mode` says which gesture is being judged, and narrows the payload: `'move'` carries the target `resourceId`, `'resize'` cannot change the column so it doesn't. Rules like "shortening is always fine, relocating needs approval" are otherwise inexpressible — the predicate fires while the pointer is still at the origin, where both gestures report the event's own start. Note that `backgroundIntervals` does NOT gate drops. Shading a slot closed is presentation; enforcing it is this predicate's job. Derive both from one source so they cannot drift. |
|
|
46
|
+
| `dragSnapMinutes` | `number` | no | Snap granularity for drags, in minutes. Default 15 — a drag lands on a quarter-hour boundary rather than an arbitrary pixel offset. Also the step size for the keyboard equivalent (Alt + arrow keys). Values below 1 (including 0 and negatives) are treated as 1 minute. |
|
|
47
|
+
| `renderEvent` | `RenderEvent` | no | Optional custom renderer for the inner content of every event chip across views (month chips, week/day timed blocks, all-day band chips). The returned ReactNode replaces the default `<time> · <title>` markup inside the chip's button wrapper — the wrapper still handles tone background, click, tooltip, and (for timed blocks) absolute positioning. To override colors, return content with inline styling that fills the wrapper (e.g., `position: absolute; inset: 0; background: <color>`). The `ctx` argument carries view-aware metadata: `view`, `asAllDay`, `continuesLeft`/`continuesRight` (multi-day), `timeLabel`, `duration`. |
|
|
48
|
+
| …native | | | plus native `<div>` attributes |
|
|
49
|
+
|
|
50
|
+
<!-- props:end -->
|
|
51
|
+
|
|
52
|
+
- Four views: `'month'` (continuous event bars across the grid), `'week'` (7 columns × hour rows + all-day band), `'day'` (single column × hour rows), `'agenda'` (chronological list of the cursor's current week, grouped by day with empty days hidden).
|
|
53
|
+
- Events are `{ id, title, startsAt, endsAt?, tone?, color?, allDay?, resourceId? }`. Multi-day events render as continuous bars in both the month grid and the week/day all-day band. `resourceId` routes a timed event to its column in the resource day view and is ignored by every other view.
|
|
54
|
+
- **Two colour axes, and they do not compete.** `tone` is _semantic_ — what STATE the event is in: `neutral` (default) / `accent` / `success` / `warning` / `danger`. `color` is _identity_ — WHICH category it belongs to, as one of the 30 `PaletteColor`s. Use `color` when your events belong to a colour-bearing category the tenant chose (an appointment type, a calendar, a resource class).
|
|
55
|
+
- With **no** `color`, tone paints the whole block exactly as it always has, and `allDay: true` renders as a tone-filled band (no time prefix). With `color` set, `color` takes the fill (palette tint background, saturated palette colour for text and border) and a non-`neutral` `tone` moves to a saturated stripe down the event's **leading edge** — so a category-coloured event can still show a state that must not be missed. There is no precedence rule to remember because the axes occupy different pixels.
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
// Category only — a clean single colour.
|
|
59
|
+
{ id: '1', title: 'Consultation', startsAt, color: 'violet' }
|
|
60
|
+
// Category AND state — violet block, danger stripe. Both readable.
|
|
61
|
+
{ id: '2', title: 'Consultation', startsAt, color: 'violet', tone: 'danger' }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Don't map categories onto tones instead: tones are semantic and finite, two categories would collide on one tone, and the result is wrong in both directions — a `danger` chip that is merely a category, and a genuinely urgent event that looks like one.
|
|
65
|
+
|
|
66
|
+
- **This is the opposite of `Badge`**, where `color` takes precedence over `tone` and the tone is discarded. Calendar renders both, because a calendar tone can carry states closer to safety than decoration (a masked hour, a released slot) and dropping one would make a free hour read as a booked one. Don't carry the Badge rule across.
|
|
67
|
+
- **Both axes are visual only** — neither sets ARIA, and a 3px band is not an accessible signal (same contract as `Badge`). If a state must reach assistive tech, keep it in the event `title`. And pick category colours that contrast with the tones you use — measured RGB distance between the band colour and the category's own saturated colour — grouped by tone and ordered within each group: `success` + `mint` (11) / `emerald` (15) / `teal` (35) / `green` (41); `danger` + `red` (17) / `coral` (44); `warning` + `orange` (16) / `coral` (25) / `red` (43) / `amber` (44); `accent` + `blue` (40). Read it as a threshold, not a ranking. The worst pair is `success` + `mint` at 11, and anything under ~20 is effectively invisible at the rendered 4px (a 3px band plus the 1px border in the same colour; the agenda row has no border, so 3px there); the low 20s are detectable side-by-side but unreliable alone, and the low 40s are where this list stops rather than where the problem does. These moved (`success` + `mint` 18 → 11, `danger` + `red` 36 → 17) when those tones were raised to clear WCAG AA; `Calendar.collisions.test.ts` recomputes the list from the shipped tokens. Nothing enforces this — keep the state in the title too when a tenant's category colour collides.
|
|
68
|
+
|
|
69
|
+
- In the **agenda** view the row is never filled (a tint would clash with the today-group accent and the hover background), so `color` sets the leading dot and `tone` still gets the edge stripe.
|
|
70
|
+
- A coloured **all-day** chip uses the same palette tint as a timed one rather than the tone-filled treatment: the palette ships a `bg`/`fg` pair, and a filled variant would mean inventing 30 more tokens and asserting contrast at 12px for each. **The visible consequence:** a coloured all-day event renders at _timed-chip weight_ — same tint, same border, only the time prefix missing — so in a band that mixes coloured and uncoloured all-day events, the coloured ones read lighter. Colour the whole category or none of it if that bothers you.
|
|
71
|
+
- On a multi-day bar the band repeats on **every** segment, including ones that continue from a previous week. It marks the event's state, not its start.
|
|
72
|
+
- Controlled cursor via `value` / `onChange`, or uncontrolled via `defaultValue`. Controlled view via `view` / `onViewChange`, or uncontrolled via `defaultView`.
|
|
73
|
+
- Overlapping timed events in week/day views render as a Google-Calendar-style cascade: each lane is offset to the right by a small constant step but every block extends to the column's right edge, with later lanes overlaying earlier ones via `z-index`. Hovering or keyboard-focusing a block lifts it to full width on top of all neighbours. A horizontal "now" line marks the current time in today's column.
|
|
74
|
+
- Locale-aware via `useLocale()`; override with `locale` prop. UI strings (`today`, `viewMonth`, etc.) come from the i18n provider (`useTranslation`) — override them with `<I18nProvider overrides={{ calendar: { today: '…' } }}>`. There is no `labels` prop. For Russian copy and dates: `<I18nProvider locale="ru"><Calendar locale="ru-RU" … /></I18nProvider>`.
|
|
75
|
+
- Read-mostly **by default**: `onDayClick` and `onEventClick` callbacks; opt into editing by wiring `onEventMove` / `onEventResize` (see _Drag to move and resize_ below). `onDayClick` fires across all views — month-cell click, "+N more" chip, keyboard activation (Enter/Space) on a focused cell, and (in week/day views) clicks on the empty hour-grid space of a day column. No built-in popover or modal — wire your own detail UI.
|
|
76
|
+
- ARIA: month view is `role="grid" aria-readonly="true"`; arrow keys move focus, PageUp/PageDown navigates months, Enter/Space calls `onDayClick`. Week/day views are also `role="grid"` with `role="row"` + `role="columnheader"` headers and standard sequential tab order for event blocks — `aria-readonly="true"` there too, until a drag handler is wired (see below). Agenda view exposes the visible week as `role="list"` with each day group as a `role="listitem"` and the day label as an `<h3>` heading inside — screen readers announce the date grouping before reading each event row. **Known v3 gap:** in week/day views, `onDayClick` on empty hour-grid space is **mouse-only** (no keyboard equivalent). Consumers needing keyboard activation should drive their detail UI through the focusable event chips via `onEventClick`.
|
|
77
|
+
|
|
78
|
+
#### Resource columns — one day, N lanes
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
<Calendar
|
|
82
|
+
view="day"
|
|
83
|
+
events={appointments} // each carries resourceId
|
|
84
|
+
resources={[
|
|
85
|
+
{ id: 'ana', label: 'Ana' },
|
|
86
|
+
{ id: 'ben', label: 'Ben' },
|
|
87
|
+
]}
|
|
88
|
+
/>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
- Routing is by `CalendarEvent.resourceId`. A **timed** event whose `resourceId` matches nothing (or is absent) lands in a trailing **Unassigned** column, which is only rendered when at least one such event exists. An **all-day** event with no `resourceId` is day-wide and spans every column; with one, it pins to that column.
|
|
92
|
+
- Two bookings at the same hour in _different_ columns are not a collision — each takes lane 0 and full column width. The cascade still applies within one column.
|
|
93
|
+
- Month, week and agenda views ignore `resources` — their columns already mean something else.
|
|
94
|
+
|
|
95
|
+
#### Availability underlay — shade closed time, tint free time
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
<Calendar
|
|
99
|
+
view="day"
|
|
100
|
+
events={appointments}
|
|
101
|
+
resources={resources}
|
|
102
|
+
backgroundIntervals={[
|
|
103
|
+
{ startsAt: at(7, 0), endsAt: at(9, 0), resourceId: 'ana', tone: 'unavailable' },
|
|
104
|
+
{ startsAt: at(9, 0), endsAt: at(17, 0), resourceId: 'ana', tone: 'available' },
|
|
105
|
+
]}
|
|
106
|
+
/>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
- A booking calendar has three states and `hourRange` alone expresses none of them: **closed** (rendered but greyed), **busy** (an event covers it — already works), **free** (clickable). `backgroundIntervals` paints the first and third behind the events.
|
|
110
|
+
- An interval list, not a weekly-hours object, because working hours vary by day (a lunch break), by date (a holiday, a short day) and by column (per-practitioner shifts).
|
|
111
|
+
- `tone`: `'unavailable'` (default) shades; `'available'` tints. `resourceId` restricts the band to one resource column and is ignored in week view (and in a resource-less day view).
|
|
112
|
+
- **Intervals are always clipped to each column's own date**, `resourceId` or not. Shading 08:00–09:00 across a week takes seven intervals, one per day — a single interval does not span the columns.
|
|
113
|
+
- Bands render beneath events, take no part in the collision cascade, are `aria-hidden`, and are `pointer-events: none` — `onDayClick` still fires through them. Intervals are clipped to each column's day and to `hourRange`; ones that fall entirely outside are dropped.
|
|
114
|
+
- Don't fake this with all-day events — that pollutes the `events` array you also read back.
|
|
115
|
+
- **The underlay is paint, not policy.** Bands never gate drops — `backgroundIntervals` and `canDropEvent` are independent, and shading a slot closed does not stop a drag landing in it. A screen that shades shifts _and_ enables drag must express the same rule twice: once as intervals to draw it, once in `canDropEvent` to enforce it. Derive both from one source so they cannot drift.
|
|
116
|
+
|
|
117
|
+
#### Drag to move and resize
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
<Calendar
|
|
121
|
+
view="day"
|
|
122
|
+
events={appointments}
|
|
123
|
+
resources={resources}
|
|
124
|
+
dragSnapMinutes={15}
|
|
125
|
+
canDropEvent={(ev, next) => isWithinOpeningHours(next)}
|
|
126
|
+
onEventMove={async (ev, next) => {
|
|
127
|
+
const ok = await api.reschedule(ev.id, next);
|
|
128
|
+
if (!ok) return false; // rejected → the block snaps back
|
|
129
|
+
setAppointments((prev) => applyMove(prev, ev.id, next));
|
|
130
|
+
}}
|
|
131
|
+
onEventResize={(ev, next) => setAppointments((p) => applyResize(p, ev.id, next))}
|
|
132
|
+
/>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
- Both handlers are optional and additive — omit them and the calendar behaves exactly as before (read-only). `onEventMove` makes blocks draggable; `onEventResize` adds a handle on the block's bottom edge.
|
|
136
|
+
- **A drop is proposed, never applied.** `events` stays the source of truth: commit the change to your own state or the block snaps back. `next` is `{ startsAt, endsAt, resourceId? }` for a move (duration preserved; `resourceId` is the column it landed on) and `{ startsAt, endsAt }` for a resize (`startsAt` unchanged).
|
|
137
|
+
- **Two ways to refuse a drop.** `canDropEvent` is a synchronous predicate evaluated _continuously during_ the drag — the placement renders as refused for as long as the pointer rests on it, and never reaches the handler. Its `next.mode` says which gesture is being judged and narrows the payload (`'move'` carries the target `resourceId`; `'resize'` can't change the column, so it doesn't), which is what makes rules like "shortening is fine, relocating needs approval" expressible. The handler itself can return `false`, or a promise resolving `false` or rejecting, for rules only the server can settle; that verdict is announced in the live region rather than styled (the gesture is over by then), and the preview is held until the promise settles so an accepted drop doesn't flash back mid-request.
|
|
138
|
+
- Dragging sideways crosses columns: another weekday in week view, another resource lane in the resource day view.
|
|
139
|
+
- **Keyboard equivalent**, so rescheduling isn't mouse-only: on a focused block, `Alt`+`↑`/`↓` moves by one snap step, `Alt`+`←`/`→` moves a column, `Alt`+`Shift`+`↑`/`↓` changes the end time. Every block carries the full shortcut list as its `aria-describedby` description — it has to live on the blocks, since `role="grid"` here is not focusable and a description there would never be announced — and an assertive live region names the event and reports each proposed slot, each refusal, and each gesture that was already at the edge of what the bounds allow. With a drag handler wired, the grid drops `aria-readonly`.
|
|
140
|
+
- **Bounds.** One rule governs both gestures: **the allowed range always contains where the event already is**, so a bound can refuse to move something further but never moves it the opposite way. Within that, a move keeps the start inside the visible `hourRange` — an unclamped upward drag would project into the previous calendar day — while the end runs past it freely, so an overnight booking keeps its duration; and a resize may grow or shrink the event by at most a day per gesture — the bound is on the CHANGE, not the absolute duration, so a multi-day booking stays adjustable — which lets an event be extended past midnight while stopping a runaway pointer delta rolling through whole dates. An event that already starts before `hourRange` (its block renders clipped at the top) is _not_ pulled into the window.
|
|
141
|
+
- A gesture the bounds refuse outright proposes nothing: the handler is not called (it is typically an API write) and the live region says the event cannot move further.
|
|
142
|
+
- Every time comes from the `CalendarEvent`, never from the rendered block — the layout clips a block that ends on a later day, so it is shorter than the event it represents.
|
|
143
|
+
- **A move preserves ELAPSED duration**, not the row count. The two differ only on a daylight-saving day, where the grid necessarily draws wall-clock rows (01:00–05:00 occupies four rows though three hours pass) while a booking is its real duration — a 30-minute appointment takes 30 real minutes on any day of the year. This is also why a drop onto the skipped hour can't come back zero-length.
|
|
144
|
+
- **Known gaps.** `onDayClick` still reports only a date, so in a resource day view it cannot say which column a free slot belongs to. After a keyboard column move, the block re-parents into the new column and loses focus, so nudges can't be chained. `touch-action: none` on a movable block means a touch that starts on an event drags it rather than scrolling the grid. Resource columns are `1fr` with no minimum width, so a dozen lanes squash rather than scroll — the all-day band lays itself out separately and would drift out of alignment if only the hour grid could scroll.
|
|
145
|
+
- The click that terminates a drag is swallowed, so `onEventClick` doesn't fire on a reschedule. A plain click still opens your detail UI.
|
|
146
|
+
- Drag-to-**create** (dragging empty grid space to draft a new event) is still out of scope — use `onDayClick` plus your own form.
|
|
147
|
+
|
|
148
|
+
#### When NOT to use
|
|
149
|
+
|
|
150
|
+
- ❌ Single-date or date-range selection → `<DatePicker>` / `<DateRangePicker>`.
|
|
151
|
+
- ❌ Drag-to-create (see above).
|
|
152
|
+
|
|
153
|
+
#### Anti-patterns
|
|
154
|
+
|
|
155
|
+
- ❌ Mounting `<Calendar>` with no `events` AND no `onDayClick`: the grid is fully inert.
|
|
156
|
+
- ❌ Pre-grouping events by day in the consumer. Pass the flat array; the layout algorithms group and lane internally.
|
|
157
|
+
- ❌ Treating `onEventMove` as the thing that moves the event. It proposes; your state disposes, and not committing means the block snaps back.
|
|
158
|
+
- ❌ Faking the availability underlay with all-day events, or approximating resource columns with N side-by-side day calendars (see above). Use `backgroundIntervals` and `resources`.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# `<Card>` — bordered container
|
|
2
|
+
|
|
3
|
+
- `fill` makes the Card fill a definite-height parent (`height: 100%`) and
|
|
4
|
+
applies `min-width: 0` so it can shrink inside narrow Grid, Sortable, and
|
|
5
|
+
DashboardCanvas cells. When a direct `Card.Body` is present, it also creates
|
|
6
|
+
Card's internal column/min-height chain. It does not create a height by
|
|
7
|
+
itself; bound the parent (for example with `Constrain height`) when needed.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
<Card padding="md">
|
|
11
|
+
<Stack gap="md">...</Stack>
|
|
12
|
+
</Card>
|
|
13
|
+
|
|
14
|
+
// Tone-coded stat card — 3px left-edge stripe in the tone color:
|
|
15
|
+
<Card padding="md" tone="accent">Open deals</Card>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
<!-- props:start -->
|
|
19
|
+
|
|
20
|
+
## Props
|
|
21
|
+
|
|
22
|
+
### `CardProps`
|
|
23
|
+
|
|
24
|
+
<!-- prettier-ignore -->
|
|
25
|
+
| Prop | Type | Required | Description |
|
|
26
|
+
|---|---|---|---|
|
|
27
|
+
| `padding` | `'none' \| 'sm' \| 'md' \| 'lg'` | no | Inner padding. - `none` — use when the card contains a table or list that should bleed edge-to-edge; inner sections manage their own padding. - `sm` (12px) — dense info cards. - `md` (16px) — default for plain-content cards. - `lg` (24px) — emphasis cards, marketing-style panels. When omitted, defaults to `'md'` for plain content, or `'none'` when the card contains compound subcomponents (`Card.Header` / `Card.Body` / `Card.List` / `Card.ListRow`) so its sections bleed to the card edge automatically. Pass an explicit value to override the auto-detect. **Detection is shallow:** only direct children are inspected. Fragments (`<>...</>`) are transparent — the detection recurses into them, so conditional renders that wrap compound children in a Fragment still trigger the auto-detect. But wrapping a `Card.Header` in any other element (`<div>`, `<Stack>`, etc.) defeats the heuristic — pass `padding="none"` explicitly in that case. |
|
|
28
|
+
| `tone` | `'accent' \| 'info' \| 'success' \| 'warning' \| 'danger'` | no | Optional left-edge tone stripe (3px). Useful for "stat card" / "status card" patterns where one card in a row needs visual emphasis. Default: no stripe (the card keeps its standard bordered look). Uses the same tone vocabulary as `Alert`: `accent` / `info` / `success` / `warning` / `danger`. |
|
|
29
|
+
| `overflow` | `'hidden' \| 'visible'` | no | How the card clips its children at the rounded border. - `hidden` (default) — children are clipped to the rounded border. This prevents the visible seam that appears at the corners when a child has square corners (Table's internal scroll wrapper, images, full-bleed media). Overlays in this library (DropdownMenu, Tooltip, Popover, Drawer, Modal) portal to `document.body` and are NOT clipped by this. Focus rings are NOT exempt: an `outline` is clipped by an ancestor's overflow, so a focusable flush against the card edge loses that band — draw its ring inset by passing `$offset: calc(-1 * var(--ring-offset))` to the `focus-ring` mixin, not a separate `outline-offset` after the `@include` (a `structure.test.ts` gate fails the build on that shape). - `visible` — opt out of clipping. Use when a direct child needs to overhang the card edge — decorative badges that protrude from a corner, hover-lift transforms whose shadow extends past the card border, etc. |
|
|
30
|
+
| `fill` | `boolean` | no | Fill the containing block's height and allow the Card to shrink within a narrow grid cell. Use in stretched Grid, Sortable, or DashboardCanvas cells whose wrapper already has a definite height. When a direct `Card.Body` child is present, `fill` also establishes Card's internal column and minimum-height chain; add `scroll` to the Body to keep a sibling Header fixed above a scrolling content region. Defaults to `false`. |
|
|
31
|
+
| …native | | | plus native `<div>` attributes |
|
|
32
|
+
|
|
33
|
+
### `CardBodyProps`
|
|
34
|
+
|
|
35
|
+
<!-- prettier-ignore -->
|
|
36
|
+
| Prop | Type | Required | Description |
|
|
37
|
+
|---|---|---|---|
|
|
38
|
+
| `scroll` | `boolean` | no | Makes the body the flexible vertical scroll region of a `fill` Card while sibling `Card.Header` content stays fixed. The Card's parent must provide a definite height. Defaults to `false`. |
|
|
39
|
+
| …native | | | plus native `<div>` attributes |
|
|
40
|
+
|
|
41
|
+
### `CardHeaderProps`
|
|
42
|
+
|
|
43
|
+
<!-- prettier-ignore -->
|
|
44
|
+
| Prop | Type | Required | Description |
|
|
45
|
+
|---|---|---|---|
|
|
46
|
+
| `headerLevel` | `'h2' \| 'h3' \| 'h4' \| 'h5' \| 'h6'` | no | Heading level for the title text. Defaults to `'h3'` (assumes the page has an h1/h2 above). Override when this section sits beneath an h1 directly (`headerLevel="h2"`) or further nested (`headerLevel="h4"`). |
|
|
47
|
+
| `surface` | `'default' \| 'subtle'` | no | `'subtle'` paints the header on a tinted (sunken) surface, for a nested editable sub-section inside a `Card.Body` (e.g. a condition group). Defaults to `'default'`. |
|
|
48
|
+
| `controls` | `ReactNode` | no | Optional interactive slot right after the title (e.g. a `ButtonGroup` mode switch). Rendered outside the heading, so it is not part of the heading's name. Order: title, controls, meta, action. Controls never shrink: on a wide header the title ellipsizes down to a short minimum width; on a narrow header (under ~360px of content) the controls wrap onto a second row under the title, which keeps the first row with meta and action. Keep titles short, or pass a `title` attribute for long ones. |
|
|
49
|
+
| `action` | `ReactNode` | no | Optional right-aligned slot — typically a `<Link>` or `<Button>` that lets the user navigate to a full list or take a section-level action. Rendered inside a `<span>` that never shrinks or wraps (flex-shrink: 0; with `meta` or `controls`, min-width: max-content). Beside `controls` it is the removal/section action of a nested sub-group (e.g. a ghost delete button), right-aligned after any meta. |
|
|
50
|
+
| `meta` | `ReactNode` | no | Optional secondary, non-interactive text beside the title — freshness ("Updated 3 minutes ago") or status. Muted and single-line; sits just before `action` and is the first thing to give way at narrow widths: it truncates with an ellipsis before the title gives up any width, while `action` keeps its width. In a `fill` Card (which needs a parent of definite width) it is also visually hidden, still read by screen readers, once only a sliver would show. Not part of the heading's name. Put controls in `controls` or `action`, never here. `false` and `''` count as no meta. |
|
|
51
|
+
| `children` | `ReactNode` | yes | Title content. Becomes the inner text of the heading element. Typically a plain string, but can contain inline elements if needed. |
|
|
52
|
+
| …native | | | plus native `<div>` attributes |
|
|
53
|
+
|
|
54
|
+
### `CardListProps`
|
|
55
|
+
|
|
56
|
+
<!-- prettier-ignore -->
|
|
57
|
+
| Prop | Type | Required | Description |
|
|
58
|
+
|---|---|---|---|
|
|
59
|
+
| `children` | `ReactNode` | yes | Row items — typically `<Card.ListRow>` elements. |
|
|
60
|
+
| …native | | | plus native `<ul>` attributes |
|
|
61
|
+
|
|
62
|
+
### `CardListRowProps`
|
|
63
|
+
|
|
64
|
+
<!-- prettier-ignore -->
|
|
65
|
+
| Prop | Type | Required | Description |
|
|
66
|
+
|---|---|---|---|
|
|
67
|
+
| `children` | `ReactNode` | yes | Row content — typically a `<Stack>` or `<Cluster>` of text and metadata. |
|
|
68
|
+
| …native | | | plus native `<li>` attributes |
|
|
69
|
+
|
|
70
|
+
<!-- props:end -->
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
// Fixed header + scrolling body in a definite-height dashboard cell.
|
|
74
|
+
<Constrain height="sm">
|
|
75
|
+
<Card fill>
|
|
76
|
+
<Card.Header>Pipeline</Card.Header>
|
|
77
|
+
<Card.Body scroll>
|
|
78
|
+
<Stack gap="sm">...</Stack>
|
|
79
|
+
</Card.Body>
|
|
80
|
+
</Card>
|
|
81
|
+
</Constrain>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
// Compound API — section card with header + list (Dashboard's "Deals needing attention" pattern).
|
|
86
|
+
// No `padding` prop needed — Card auto-detects compound children and defaults to padding="none".
|
|
87
|
+
<Card>
|
|
88
|
+
<Card.Header
|
|
89
|
+
action={
|
|
90
|
+
<Link as={RouterLink} to="/deals">
|
|
91
|
+
View all
|
|
92
|
+
</Link>
|
|
93
|
+
}
|
|
94
|
+
>
|
|
95
|
+
Deals needing attention
|
|
96
|
+
</Card.Header>
|
|
97
|
+
<Card.List>
|
|
98
|
+
{deals.map((d) => (
|
|
99
|
+
<Card.ListRow key={d.id}>
|
|
100
|
+
<Stack gap="xs">
|
|
101
|
+
<span>{d.title}</span>
|
|
102
|
+
<span>{d.company}</span>
|
|
103
|
+
</Stack>
|
|
104
|
+
<Avatar name={d.owner} size="sm" />
|
|
105
|
+
</Card.ListRow>
|
|
106
|
+
))}
|
|
107
|
+
</Card.List>
|
|
108
|
+
</Card>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
- **Compound API** — `Card.Header` / `Card.Body` / `Card.List` / `Card.ListRow` for section-card patterns. Drop `padding="none"` — the parent Card auto-detects compound children.
|
|
112
|
+
- `Card.Header`: title row (`h3` by default, override via `headerLevel`) with optional right-aligned `action` slot and bottom-border separator. Optional `meta` is muted secondary text (freshness, status) just before `action`; at narrow widths it truncates before the title shrinks (in a `fill` Card it then hides visually, staying readable by screen readers), and `action` never shrinks. Keep controls out of `meta`. With `controls` the order is title · controls … meta · action: controls and `action` never shrink, meta truncates first, and `action` stays right-aligned.
|
|
113
|
+
- With `controls`, the title stays on one line. On a wide header it ellipsizes down to a short minimum width rather than pushing the controls out; on a narrow header (under ~360px of content) the controls wrap onto a second row under the title, and the first row keeps title · meta · action. For long titles beside `controls`, pass a `title` attribute so the full text stays reachable (`<Card.Header controls={…}><span title={name}>{name}</span></Card.Header>`), or keep titles short.
|
|
114
|
+
- `Card.Body`: padded `<div>` content section. Add `scroll` beneath a Header in a `fill` Card to make Body the flexible vertical scroll region while Header stays fixed. Card intentionally owns this layout because it relates only its own compound pieces; the parent still owns the Card's definite outer height. `scroll` also sets `tabIndex={0}`, because a scroll container with no focusable descendant is unreachable by keyboard (axe `scrollable-region-focusable`) — pair it with `role="group"` and an `aria-label` when the content is worth naming, and pass `tabIndex={-1}` to opt out if the body already contains something focusable.
|
|
115
|
+
- `Card.List`: semantic `<ul>` with list-reset styling — screen readers announce "list with N items".
|
|
116
|
+
- `Card.ListRow`: `<li>` with padded content and bottom dividing border; last-child border suppressed automatically.
|
|
117
|
+
- **Never nest Card in Card** — except an editable, repeatable sub-group (e.g. a condition group) inside a `Card.Body`, using `Card.Header surface="subtle"`; never for display-only content or as a layout wrapper. `controls` holds the sub-section's inline controls and `action` its remove button:
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
<Card>
|
|
121
|
+
<Card.Header>Rules</Card.Header>
|
|
122
|
+
<Card.Body>
|
|
123
|
+
<Card>
|
|
124
|
+
<Card.Header
|
|
125
|
+
surface="subtle"
|
|
126
|
+
controls={
|
|
127
|
+
<ButtonGroup value={mode} onValueChange={setMode} aria-label="Match">
|
|
128
|
+
<ButtonGroup.Item value="all">Match all</ButtonGroup.Item>
|
|
129
|
+
<ButtonGroup.Item value="any">Match any</ButtonGroup.Item>
|
|
130
|
+
</ButtonGroup>
|
|
131
|
+
}
|
|
132
|
+
action={
|
|
133
|
+
<Button iconOnly variant="ghost" aria-label="Delete group">
|
|
134
|
+
<X size={12} />
|
|
135
|
+
</Button>
|
|
136
|
+
}
|
|
137
|
+
>
|
|
138
|
+
Group
|
|
139
|
+
</Card.Header>
|
|
140
|
+
<Card.Body>…conditions…</Card.Body>
|
|
141
|
+
</Card>
|
|
142
|
+
</Card.Body>
|
|
143
|
+
</Card>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
- `Card.Header` sits flush with the card edge inside `<Card padding="none">`. It renders a `<div>` holding the heading element plus an optional `<span>` wrapping `action` (with `meta`, a muted `<span>` between them and the header switches to a grid). With `controls`, the header is an inline-size container holding one inner `<div>` grid of four items — heading · controls `<span>` · meta (or an empty spacer) · action (or an empty placeholder) — so `action` stays right-aligned; a container query moves the controls to a second row when the header is narrow. The containment means a controls header contributes no intrinsic width, so give its Card a definite width (any block parent does). Under an `h1` with no `h2` above, pass `headerLevel="h2"`.
|
|
147
|
+
- `Card.List` / `Card.ListRow`: row dividers use a `:last-child` selector to suppress the final bottom border. Fragments are transparent, but wrapping rows in an extra `<div>` or other element breaks the match and leaves a stray divider on the last row. Render rows as direct children (or via `.map`).
|
|
148
|
+
- `Card.Body` keeps `scroll` for a bounded `fill` Card (Grid, Sortable or DashboardCanvas cell); for page-level scrolling let the page or `AppLayout` own the scroll area. Keep it a direct child of Card (Fragments are transparent) so Card can detect the compound structure.
|
|
149
|
+
|
|
150
|
+
#### When NOT to use
|
|
151
|
+
|
|
152
|
+
- ❌ Inside another Card, except the editable sub-group exception above (`Card.Header surface="subtle"` in a `Card.Body`). Never as the only child of another Card, or as a layout primitive (use `Stack` / `Cluster` / `Grid`). Card is for semantic grouping.
|
|
153
|
+
- ❌ For every container. If the page looks like a deck of cards, nothing is visually grouped.
|
|
154
|
+
- ❌ `Card.Body` outside a Card. It owns padding and sizing only as part of Card's compound structure.
|
|
155
|
+
|
|
156
|
+
#### Anti-patterns
|
|
157
|
+
|
|
158
|
+
- ❌ `<Card style={{ padding: 20 }}>`: use `padding`. A missing value is a token/scale conversation.
|
|
159
|
+
- ❌ `<Card style={{ overflow: 'visible' }}>`: use `<Card overflow="visible">`.
|
|
160
|
+
- ❌ `<Card style={{ height: '100%' }}>`: use `<Card fill>`, which also applies the shrink-safe minimum width for narrow grid cells.
|
|
161
|
+
- ❌ Hand-rolling a flex column and scrolling body in a Card, or consumer CSS for `flex: 1`, `min-height: 0` or `overflow-y: auto`. Use `<Card fill>` with `<Card.Body scroll>`.
|
|
162
|
+
- ❌ Expecting `scroll` to create a height. The Card's parent must provide a definite height; `fill` carries that bound into the body.
|
|
163
|
+
- ❌ Hover shadows to make Cards "interactive". If the whole card is clickable, that's a different component (`LinkCard`).
|
|
164
|
+
- ❌ Hand-rolling a left stripe via `className` / `style`. Use `tone`; it reserves the border-left space so layout never shifts.
|
|
165
|
+
- ❌ Hand-rolling `.cardHeader` / `.list` / `.listRow` SCSS. Use the compound API.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# `<CatalogPicker>` — searchable card picker for a catalog
|
|
2
|
+
|
|
3
|
+
A search box, optional category pills, a live result count and an auto-fill grid of cards, for choosing one item from a catalog (e.g. "Add widget"). It is a surface, not an overlay: drop it into `Drawer.Body` or `Modal.Body`. Selecting is an action (`onSelect(id)`), not a value; the app decides whether to add immediately or open a configure step.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Badge, CatalogPicker, Drawer, WidgetPreview } from '@eocrm/design-system';
|
|
7
|
+
|
|
8
|
+
<Drawer open={open} onOpenChange={setOpen}>
|
|
9
|
+
<Drawer.Header>Add widget</Drawer.Header>
|
|
10
|
+
<Drawer.Body>
|
|
11
|
+
<CatalogPicker
|
|
12
|
+
label="Widget catalog"
|
|
13
|
+
categories={[
|
|
14
|
+
{ id: 'sales', label: 'Sales' },
|
|
15
|
+
{ id: 'tasks', label: 'Tasks' },
|
|
16
|
+
]}
|
|
17
|
+
items={[
|
|
18
|
+
{
|
|
19
|
+
id: 'open-deals',
|
|
20
|
+
title: 'Open deals',
|
|
21
|
+
description: 'Count of deals in open stages.',
|
|
22
|
+
category: 'sales',
|
|
23
|
+
tags: ['pipeline', 'kpi'],
|
|
24
|
+
badge: <Badge>New</Badge>,
|
|
25
|
+
preview: <WidgetPreview variant="kpi" />,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 'my-tasks',
|
|
29
|
+
title: 'My tasks',
|
|
30
|
+
category: 'tasks',
|
|
31
|
+
preview: <WidgetPreview variant="list" />,
|
|
32
|
+
disabledReason: 'Already on dashboard',
|
|
33
|
+
},
|
|
34
|
+
]}
|
|
35
|
+
onSelect={(id) => addOrConfigure(id)}
|
|
36
|
+
/>
|
|
37
|
+
</Drawer.Body>
|
|
38
|
+
</Drawer>;
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
<!-- props:start -->
|
|
42
|
+
|
|
43
|
+
## Props
|
|
44
|
+
|
|
45
|
+
<!-- prettier-ignore -->
|
|
46
|
+
| Prop | Type | Required | Description |
|
|
47
|
+
|---|---|---|---|
|
|
48
|
+
| `items` | `CatalogPickerItem[]` | yes | The catalog. Rendered in the given order. |
|
|
49
|
+
| `categories` | `CatalogPickerCategory[]` | no | Category pills (after a leading localized "All"). Omit or pass `[]` for no pills. |
|
|
50
|
+
| `onSelect` | `(id: string) => void` | yes | Called with the item id when an AVAILABLE item is clicked or activated with Enter/Space. |
|
|
51
|
+
| `label` | `string` | yes | Accessible name of the results listbox, e.g. "Widget catalog". Required. |
|
|
52
|
+
| …native | | | plus native `<div>` attributes |
|
|
53
|
+
|
|
54
|
+
<!-- props:end -->
|
|
55
|
+
|
|
56
|
+
- **`onSelect` is an action.** It fires on click, Enter or Space of an available item. Options are `aria-selected="false"` always; nothing stays selected. Close the overlay or open the next step in the handler.
|
|
57
|
+
- **Search** matches a case-insensitive substring of `title`, `description` or any of `tags` (tags are not rendered). Combined with the category pill (AND). Items keep your order.
|
|
58
|
+
- **Categories.** Omit or pass `[]` for no pills; otherwise a localised "All" pill is added first and is the default. Items without a `category` only appear under "All".
|
|
59
|
+
- **Result count** is a `role="status"` element ("N results") that updates as the filter changes. When nothing matches, an `EmptyState` replaces the grid.
|
|
60
|
+
- **Unavailable items** (`disabledReason`): stay visible and focusable, dimmed, `aria-disabled`, reason shown as text and added to the option's description. `onSelect` is not called. Never hide them.
|
|
61
|
+
- **Keyboard** (one tab stop; roving focus inside the grid):
|
|
62
|
+
- `ArrowDown` in the search field focuses the first result.
|
|
63
|
+
- `←` / `→` move by one item (flipped in RTL); `↑` / `↓` move by one row (column count read from the rendered grid); `Home` / `End` jump to first / last.
|
|
64
|
+
- `↑` from the first row returns focus to the search field.
|
|
65
|
+
- `Enter` / `Space` activate (no-op on an unavailable item).
|
|
66
|
+
- Changing the query or category resets the focus target to the first result.
|
|
67
|
+
- **The toolbar is sticky** so search stays reachable while the drawer/modal body scrolls.
|
|
68
|
+
- **Options must not contain interactive content.** Each card is a single `role="option"`; `preview` and `badge` are display-only.
|
|
69
|
+
- Fills its container width; the grid fills the row with as many columns as fit (about 2 in a Drawer, 3 to 4 in a Modal).
|
|
70
|
+
|
|
71
|
+
#### When NOT to use
|
|
72
|
+
|
|
73
|
+
- ❌ A persistent form value (a field that stores a choice) → `<Select>` / `<IconPicker>`.
|
|
74
|
+
- ❌ Filtering a list or table → `<OptionsPicker>` / `<FilterChip>`.
|
|
75
|
+
- ❌ Picking emoji → `<EmojiPicker>`.
|
|
76
|
+
- ❌ Multi-select. It selects one item per activation.
|
|
77
|
+
|
|
78
|
+
#### Anti-patterns
|
|
79
|
+
|
|
80
|
+
- ❌ Hiding unavailable items instead of passing `disabledReason`. Users then cannot tell why a widget is missing.
|
|
81
|
+
- ❌ Putting buttons or links inside `preview` / `badge`. Options must not contain interactive content; nested controls are unreachable and invalid ARIA.
|
|
82
|
+
- ❌ Building overlay behaviour into the picker — put it in `Drawer.Body` / `Modal.Body`.
|
|
83
|
+
- ❌ Passing `label` as a placeholder-style hint. It is the listbox's accessible name ("Widget catalog").
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# `<Chart>` — time-series chart for a dashboard widget
|
|
2
|
+
|
|
3
|
+
Line, bar, stacked bar or area chart over app-bucketed categories. Fills a `DashboardWidget variant="chart"` body, reflows to any cell size, and ships its own legend, tooltip, keyboard inspection, data table and empty state. The app owns data, formatting, loading and errors.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Chart, DashboardWidget, ErrorState } from '@eocrm/design-system';
|
|
7
|
+
|
|
8
|
+
<DashboardWidget variant="chart" title="Deals won" loading={isLoading}>
|
|
9
|
+
{error ? (
|
|
10
|
+
<ErrorState title="Could not load" headingLevel={4} />
|
|
11
|
+
) : (
|
|
12
|
+
<Chart
|
|
13
|
+
type="line"
|
|
14
|
+
label="Deals won per week"
|
|
15
|
+
categories={['W1', 'W2', 'W3', 'W4']}
|
|
16
|
+
series={[
|
|
17
|
+
{ key: 'won', label: 'Deals won', values: [3, 5, null, 8] },
|
|
18
|
+
{ key: 'won-prev', label: 'Previous period', values: [2, 4, 6, 5], comparisonOf: 'won' },
|
|
19
|
+
]}
|
|
20
|
+
formatValue={(n) => numberFormat.format(n)}
|
|
21
|
+
/>
|
|
22
|
+
)}
|
|
23
|
+
</DashboardWidget>;
|
|
24
|
+
|
|
25
|
+
// Stacked, with the previous period as a total line
|
|
26
|
+
<Chart
|
|
27
|
+
type="stacked-bar"
|
|
28
|
+
label="Deals won by owner"
|
|
29
|
+
categories={weeks}
|
|
30
|
+
series={[
|
|
31
|
+
...owners.map((o) => ({ key: o.id, label: o.name, values: o.values })),
|
|
32
|
+
{ key: 'prev', label: 'Previous period', values: prevTotals, comparisonOf: 'total' },
|
|
33
|
+
]}
|
|
34
|
+
formatValue={formatCurrency}
|
|
35
|
+
/>;
|
|
36
|
+
|
|
37
|
+
// Markers: an event line, a campaign band, weekends as subtle decorative bands
|
|
38
|
+
<Chart
|
|
39
|
+
type="line"
|
|
40
|
+
label="Calls per day"
|
|
41
|
+
categories={days}
|
|
42
|
+
series={calls}
|
|
43
|
+
formatValue={formatCount}
|
|
44
|
+
markers={[
|
|
45
|
+
{ key: 'launch', label: 'Launch', at: '10 Jul' },
|
|
46
|
+
{ key: 'promo', label: 'Summer promo', from: '21 Jul', to: '30 Jul' },
|
|
47
|
+
...weekends.map(([sat, sun]) => ({ key: sat, from: sat, to: sun, tone: 'subtle' as const })),
|
|
48
|
+
]}
|
|
49
|
+
/>;
|
|
50
|
+
|
|
51
|
+
// Footnote: a short note under the plot; the plot fills the rest of the cell
|
|
52
|
+
<DashboardWidget variant="chart" title="Revenue">
|
|
53
|
+
<Chart
|
|
54
|
+
type="line"
|
|
55
|
+
label="Revenue per month"
|
|
56
|
+
categories={months}
|
|
57
|
+
series={revenue}
|
|
58
|
+
formatValue={formatCurrency}
|
|
59
|
+
footnote={`${excluded} deals in other currencies not included`}
|
|
60
|
+
/>
|
|
61
|
+
</DashboardWidget>;
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
<!-- props:start -->
|
|
65
|
+
|
|
66
|
+
## Props
|
|
67
|
+
|
|
68
|
+
<!-- prettier-ignore -->
|
|
69
|
+
| Prop | Type | Required | Description |
|
|
70
|
+
|---|---|---|---|
|
|
71
|
+
| `type` | `'line' \| 'bar' \| 'stacked-bar' \| 'area'` | yes | Chart form. Required. - `'line'` — one smoothed line per series; trends over time. Comparison series dashed. - `'bar'` — grouped bars per category; compare a few series per bucket. Comparison bars outlined + light fill. - `'stacked-bar'` — bars stacked per category; parts of a total. Comparison only as `comparisonOf: 'total'` (dashed total line). - `'area'` — smoothed stacked areas; parts of a total over time. Same comparison rule as `stacked-bar`. |
|
|
72
|
+
| `label` | `string` | yes | Accessible name of the figure and caption of its data table, e.g. "Deals won per week". Required. |
|
|
73
|
+
| `categories` | `string[]` | yes | X-axis buckets, already formatted by the app ("12 Jan", "W3", "Mar"). `series[].values[i]` belongs to `categories[i]`. |
|
|
74
|
+
| `series` | `ChartSeries[]` | yes | Series to plot, in a stable order: colour follows position (non-comparison series 1–8 get palette slots 1–8), so keep the order fixed when filtering. At most 8 primary series — fold the rest into an "Other" series in the app (series 9+ still render, dashed/outlined, with a dev warning). `values` shorter than `categories` are padded with gaps; `null` is a gap (lines break, bars are absent). `comparisonOf` marks a de-emphasised comparison (e.g. previous period): a series key (line / bar) or `'total'` (stacked-bar / area). |
|
|
75
|
+
| `formatValue` | `(n: number) => string` | yes | Formats a number for axis ticks, tooltip and data table (the app owns i18n and currency). Required. |
|
|
76
|
+
| `emptyMessage` | `ReactNode` | no | EmptyState title when there are no categories or every value is null/zero. Default: translated "No data for this period". |
|
|
77
|
+
| `hiddenSeries` | `string[]` | no | Controlled list of hidden series keys (legend toggles). Omit for uncontrolled. Unknown keys are ignored. If it would hide every primary series, it is ignored for primaries (the last primary can't be hidden). |
|
|
78
|
+
| `onHiddenSeriesChange` | `((keys: string[]) => void)` | no | Called with the next hidden keys when the user toggles a legend item. |
|
|
79
|
+
| `markers` | `ChartMarker[]` | no | Static annotations pinned to `categories`: `{ key, label?, at }` draws a vertical line at one category, `{ key, label?, from, to }` shades a category range (inclusive), e.g. a campaign or weekends. `tone`: `'neutral'` for events (the tone default) or `'subtle'` for recurring context. Labelled markers are also listed in the tooltip, the keyboard announcement and the data table; unlabelled ones are decorative. A marker naming an unknown category is ignored with a dev warning. |
|
|
80
|
+
| `footnote` | `ReactNode` | no | Short note under the plot, e.g. "3 deals in other currencies not included" or why a comparison was omitted. Takes its own row (wraps, never truncates) and the plot fills the rest; also describes the plot for screen readers. Shown in the empty state too, where it often explains the emptiness. |
|
|
81
|
+
| …native | | | plus native HTML attributes |
|
|
82
|
+
|
|
83
|
+
<!-- props:end -->
|
|
84
|
+
|
|
85
|
+
- **Pre-bucketed data.** `categories` are x labels the app already formatted; `series[].values[i]` belongs to `categories[i]`. Shorter `values` are padded with gaps, extras ignored. `null` and non-finite values (NaN, Infinity) are gaps: lines and areas break, bars are absent.
|
|
86
|
+
- **Types.** `line` (trend), `bar` (grouped per bucket), `stacked-bar` (parts of a total), `area` (always stacked; overlapping areas hide each other).
|
|
87
|
+
- **Smoothing.** Every line (comparisons and the `stacked-bar` total included) and `area` is drawn with a monotone curve: it passes through every value and never leaves the range of the two values it joins, so there is no dip before a step, no bump after it, no peak above a real maximum and no negative between counts. Peaks, troughs and flat runs ease in flat. Two-point runs are straight. Stacked areas are the exception: each layer stays within its own values, but where layers trade places the stacked top can rise slightly above the largest real total; the y axis grows to keep it inside the plot. Stacked areas smooth each series on its own and stack the curves, so a steady layer stays steady whatever its neighbours do, a series that is 0 has no thickness, and with non-negative data no layer turns inside out or crosses its neighbour. A `'total'` comparison equal to the stack stays close to its top but is smoothed on its own, so next to a spike or a series' zero it can drift up to about a tenth of the plot height (typically 2–4%). Markers and the tooltip sit on the real points; the data table has the exact values. Not configurable.
|
|
88
|
+
- **Y axis** always includes 0, with rounded ticks formatted by `formatValue`. In `bar` and `line`, negative values grow down from 0; `stacked-bar` diverges around 0 (positives up, negatives down); `area` stacks without a diverging offset, so negatives accumulate.
|
|
89
|
+
- **Colour** follows a series' position among non-comparison series: 1–8 get the 8 validated palette colours (light + dark). Keep the order stable so filtering never repaints survivors.
|
|
90
|
+
- **Comparison series** (`comparisonOf`): in `line` / `bar`, name the parent's key — it inherits the parent's colour, dashed (line) or outlined with a light fill (bar), sits after its parent in legend and tooltip, and hides with it. In `stacked-bar` / `area`, use `comparisonOf: 'total'` — a dashed neutral line of the previous period's total. Mismatches are ignored with a dev warning.
|
|
91
|
+
- **Markers** (`markers`) are static annotations pinned to `categories`: `{ key, label?, at }` draws a dashed vertical line at the centre of one category; `{ key, label?, from, to }` shades whole buckets from `from` to `to` (inclusive, either order). Positions snap to buckets: there is no sub-bucket time. `tone: 'neutral'` (default) for events, `'subtle'` for recurring context such as weekends (one marker per weekend). Bands sit behind the grid and data, lines behind the data. Labels are drawn at the top of the plot when they fit (earlier markers win a collision; hidden when the chart is compact) and are always listed in the tooltip, the keyboard announcement and a "Markers" column of the data table. Unlabelled markers are decorative. A marker naming an unknown category is ignored with a dev warning. Building markers with `.map` inside an array literal widens `tone` to `string`: write `'subtle' as const`. Don't encode status with markers (no red "incident" tone): put the meaning in the label.
|
|
92
|
+
- **Footnote** (`footnote`) is a short muted note in its own row under the plot, e.g. excluded currencies or why a comparison was dropped. It wraps (never truncates) and the plot shrinks to make room, so keep it to a line or two. It is also the plot's accessible description (after the key hint), and stays visible in the empty state, where it often explains why there is no data. Don't use it for errors (use `ErrorState`) or for legends.
|
|
93
|
+
- **Legend** appears with more than one series. Hover or focus an item to highlight its series; click to hide/show it (`aria-pressed`). The last visible primary series can't be hidden: its button is `aria-disabled` but stays focusable. A comparison's button is disabled while its parent is hidden. Control with `hiddenSeries` / `onHiddenSeriesChange` to persist per widget. Below 120px height the legend shrinks to one row with a "+N" counter; overflow items are not rendered, so they are not keyboard-reachable (the data table still lists every series) — except hidden series, which always render so they can be re-shown (the "+N" counts only the remaining visible ones).
|
|
94
|
+
- **Tooltip** on hover or tap shows every visible series at that bucket. On touch, tapping the plot shows it and it stays until you tap outside the plot. **Keyboard:** the plot is one tab stop (named by `label`, with the key hint as its description); ←/→ move, Home/End jump, Esc closes. Screen-reader users in browse mode never receive the arrow keys on the plot — they should read the data table instead. Values are announced politely only while a keyboard user inspects — never on load, so a dashboard of charts stays quiet.
|
|
95
|
+
- **Screen readers** (browse mode: use the table, not the plot's arrow keys) get a visually hidden data table (caption = `label`) with exactly the plotted values (a null cell reads "No data"); the SVG is hidden from them. Identity never relies on colour alone: dashes for comparisons, marker shapes per series, labels everywhere.
|
|
96
|
+
- **Empty** (no categories, or every value null/0) renders `EmptyState` with `emptyMessage` (default "No data for this period"; an empty string falls back to the default).
|
|
97
|
+
- **Sizing.** Fills its parent (`width/height: 100%`) and redraws on resize. The parent must have a definite height (e.g. a `DashboardWidget` chart body); in an auto-height parent the plot measures 0 and only the legend renders. X labels never overlap: end labels are clamped inside the chart and the last label is dropped if it would collide. It never scrolls horizontally.
|
|
98
|
+
- **Performance.** Layout is recomputed when the `formatValue`, `categories` or `series` identities change. Memoise them (`useCallback` / `useMemo`) when the parent re-renders often.
|
|
99
|
+
|
|
100
|
+
#### When NOT to use
|
|
101
|
+
|
|
102
|
+
- ❌ One headline number → `DashboardWidget variant="kpi"` or `StatGroup`.
|
|
103
|
+
- ❌ More than 8 groups at once → fold the tail into an "Other" series in the app (the DS can't know whether your metric is additive).
|
|
104
|
+
- ❌ Two measures with different units → two charts. There is no dual axis.
|
|
105
|
+
- ❌ Non-time categorical comparisons that need sorting, pies, scatter or zoom — not supported.
|
|
106
|
+
|
|
107
|
+
#### Anti-patterns
|
|
108
|
+
|
|
109
|
+
- ❌ Wrapping `Chart` in a fixed-height or scrolling div inside the widget body. The cell is the size.
|
|
110
|
+
- ❌ Rendering a note next to `Chart` in the widget body (it steals the plot's height unpredictably). Use `footnote`.
|
|
111
|
+
- ❌ Passing `loading` / error UI into `Chart`. Use `DashboardWidget loading` and an `ErrorState` child.
|
|
112
|
+
- ❌ Reordering `series` by value each refresh — colours jump between entities.
|
|
113
|
+
- ❌ Formatting inside `categories` with the value (`"W1: 3"`); values go in `series`, labels in `categories`.
|
|
114
|
+
- ❌ Comparison series on 8 groups (16 marks): compare only ungrouped or with ≤ 4 groups.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# `<ChatBubble>` — one chat message, plus `ChatBubble.List` (the `role="log"` container)
|
|
2
|
+
|
|
3
|
+
A message bubble aligned by speaker: `from="member"` (the signed-in user, end side, accent tint) or `from="assistant"` (start side, neutral). Always put bubbles inside `<ChatBubble.List aria-label="…">`: it is a `role="log"` + `aria-live="polite"` region, so a screen reader announces each new message, with the speaker, as it arrives.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Button, ChatBubble } from '@eocrm/design-system';
|
|
7
|
+
|
|
8
|
+
<ChatBubble.List aria-label="Conversation with Scout">
|
|
9
|
+
<ChatBubble from="member">Which deals are at risk?</ChatBubble>
|
|
10
|
+
<ChatBubble from="assistant" footer={<Button size="xs">Helpful</Button>}>
|
|
11
|
+
Three deals have had no activity for 14 days.
|
|
12
|
+
</ChatBubble>
|
|
13
|
+
{waiting && <ChatBubble from="assistant" pending />}
|
|
14
|
+
</ChatBubble.List>;
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
<!-- props:start -->
|
|
18
|
+
|
|
19
|
+
## Props
|
|
20
|
+
|
|
21
|
+
### `ChatBubbleProps`
|
|
22
|
+
|
|
23
|
+
<!-- prettier-ignore -->
|
|
24
|
+
| Prop | Type | Required | Description |
|
|
25
|
+
|---|---|---|---|
|
|
26
|
+
| `from` | `'member' \| 'assistant'` | yes | Speaker. `member` = the user (accent tint, end side); `assistant` = the AI (neutral, start side). |
|
|
27
|
+
| `children` | `ReactNode` | no | Message content. Ignored while `pending`. |
|
|
28
|
+
| `footer` | `ReactNode` | no | Slot below the coloured body, same side (feedback buttons, sources). |
|
|
29
|
+
| `pending` | `boolean` | no | Show a "typing" indicator instead of `children`; announced once when inserted into a `ChatBubble.List`. Intended for `from="assistant"`. Default: `false`. |
|
|
30
|
+
| …native | | | plus native `<div>` attributes |
|
|
31
|
+
|
|
32
|
+
### `ChatBubbleListProps`
|
|
33
|
+
|
|
34
|
+
<!-- prettier-ignore -->
|
|
35
|
+
| Prop | Type | Required | Description |
|
|
36
|
+
|---|---|---|---|
|
|
37
|
+
| `children` | `ReactNode` | yes | The `ChatBubble`s of the conversation, oldest first. Pass an `aria-label` naming the conversation. |
|
|
38
|
+
| …native | | | plus native `<div>` attributes |
|
|
39
|
+
|
|
40
|
+
<!-- props:end -->
|
|
41
|
+
|
|
42
|
+
- **The speaker is spoken.** Every bubble starts with a visually hidden, localised "You said: " / "Assistant said: " (`chatBubble.*` i18n keys, no label props).
|
|
43
|
+
- **`pending`** shows animated dots (static under `prefers-reduced-motion`) and a hidden "Assistant is typing…"; `children` are ignored. Inserted into the `role="log"` list it is announced once, and the real reply is announced when it replaces it. `aria-busy` is not used. It carries no "said" prefix; the typing text names the speaker.
|
|
44
|
+
- **`footer`** renders below the coloured body, same side, outside the tint.
|
|
45
|
+
- `ChatBubble.List` spreads props first: `role` and `aria-live` always win. Gap between bubbles is `--chat-bubble-list-gap`.
|
|
46
|
+
- Bubble width is capped at `--chat-bubble-max-width` (80%). The root is a full-width row; alignment is `justify-content`, so the parent never needs to align it.
|
|
47
|
+
|
|
48
|
+
**When NOT to use:** threaded/commented records (use `Thread`), toasts or banners (use `toast` / `Banner`), a bubble outside a `ChatBubble.List` for a live conversation (nothing will be announced), rich per-message layout (the body is plain content; compose it inside `children`).
|
|
49
|
+
|
|
50
|
+
**Anti-patterns**
|
|
51
|
+
|
|
52
|
+
- A `ChatBubble.List` without `aria-label`.
|
|
53
|
+
- Overriding `role` on `ChatBubble.List`: ignored — it is always `role="log"`. Don't wrap the bubbles in your own `role="list"`/`listitem` markup either: a list inside the log adds noise ("list, 3 items") to every announcement.
|
|
54
|
+
- Setting `aria-busy` or building your own typing indicator: use `pending`.
|
|
55
|
+
- Re-adding a "You:" / "Assistant:" prefix in `children`: it would be read twice.
|