@eocrm/design-system 0.0.0-stage → 0.6.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AI-PRIMER.md +341 -0
- package/README.md +262 -2
- package/docs/components/Accordion.md +114 -0
- package/docs/components/AgentAvatar.md +58 -0
- package/docs/components/Alert.md +81 -0
- package/docs/components/AppLayout.md +70 -0
- package/docs/components/Avatar.md +49 -0
- package/docs/components/AvatarGroup.md +47 -0
- package/docs/components/Badge.md +60 -0
- package/docs/components/Banner.md +89 -0
- package/docs/components/BrandIcon.md +32 -0
- package/docs/components/Breadcrumb.md +72 -0
- package/docs/components/Button.md +107 -0
- package/docs/components/ButtonGroup.md +78 -0
- package/docs/components/Calendar.md +158 -0
- package/docs/components/Card.md +165 -0
- package/docs/components/CatalogPicker.md +83 -0
- package/docs/components/Chart.md +114 -0
- package/docs/components/ChatBubble.md +55 -0
- package/docs/components/ChatLauncher.md +97 -0
- package/docs/components/Checkbox.md +55 -0
- package/docs/components/CircularProgress.md +53 -0
- package/docs/components/Cluster.md +96 -0
- package/docs/components/Code.md +35 -0
- package/docs/components/CodeBlock.md +67 -0
- package/docs/components/ColorPicker.md +115 -0
- package/docs/components/ConfirmationPopover.md +70 -0
- package/docs/components/Constrain.md +39 -0
- package/docs/components/CurrencySelect.md +57 -0
- package/docs/components/CursorPagination.md +43 -0
- package/docs/components/DashboardCanvas.md +79 -0
- package/docs/components/DashboardWidget.md +131 -0
- package/docs/components/DataTable.md +88 -0
- package/docs/components/DatePicker.md +68 -0
- package/docs/components/DateRangePicker.md +71 -0
- package/docs/components/DateStrip.md +56 -0
- package/docs/components/DefinitionList.md +82 -0
- package/docs/components/Divider.md +57 -0
- package/docs/components/Dot.md +26 -0
- package/docs/components/Drawer.md +94 -0
- package/docs/components/DropdownMenu.md +299 -0
- package/docs/components/EmojiPicker.md +84 -0
- package/docs/components/EmptyState.md +55 -0
- package/docs/components/EntityChip.md +102 -0
- package/docs/components/ErrorState.md +72 -0
- package/docs/components/Field.md +91 -0
- package/docs/components/FileUpload.md +109 -0
- package/docs/components/FilterChip.md +88 -0
- package/docs/components/FlowCanvas.md +108 -0
- package/docs/components/FormRow.md +31 -0
- package/docs/components/FormSection.md +37 -0
- package/docs/components/Grid.md +103 -0
- package/docs/components/Highlight.md +75 -0
- package/docs/components/HighlightedText.md +50 -0
- package/docs/components/IconPicker.md +134 -0
- package/docs/components/IconTile.md +30 -0
- package/docs/components/Image.md +84 -0
- package/docs/components/ImageCrop.md +163 -0
- package/docs/components/Indent.md +31 -0
- package/docs/components/InlineDatePicker.md +58 -0
- package/docs/components/InlineDateRangePicker.md +57 -0
- package/docs/components/Input.md +54 -0
- package/docs/components/Kanban.md +122 -0
- package/docs/components/Kbd.md +41 -0
- package/docs/components/Lightbox.md +105 -0
- package/docs/components/Link.md +65 -0
- package/docs/components/LinkCard.md +41 -0
- package/docs/components/LiquidEditor.md +84 -0
- package/docs/components/List.md +68 -0
- package/docs/components/LiveRegion.md +29 -0
- package/docs/components/LocaleProvider.md +25 -0
- package/docs/components/Logo.md +41 -0
- package/docs/components/Masonry.md +48 -0
- package/docs/components/MediaTile.md +95 -0
- package/docs/components/Modal.md +107 -0
- package/docs/components/MoneyInput.md +62 -0
- package/docs/components/OptionsPicker.md +123 -0
- package/docs/components/OtpInput.md +65 -0
- package/docs/components/Page.md +35 -0
- package/docs/components/PageHeader.md +133 -0
- package/docs/components/Pagination.md +44 -0
- package/docs/components/Palette.md +32 -0
- package/docs/components/PasswordInput.md +45 -0
- package/docs/components/PasswordStrengthMeter.md +27 -0
- package/docs/components/PersonDisplay.md +153 -0
- package/docs/components/PhoneInput.md +54 -0
- package/docs/components/PillMenu.md +91 -0
- package/docs/components/Popover.md +127 -0
- package/docs/components/Progress.md +56 -0
- package/docs/components/QrCode.md +34 -0
- package/docs/components/Radio.md +37 -0
- package/docs/components/RadioGroup.md +56 -0
- package/docs/components/Rail.md +191 -0
- package/docs/components/RelativeDateInput.md +114 -0
- package/docs/components/RichText.md +44 -0
- package/docs/components/RichTextEditor.md +112 -0
- package/docs/components/Screen.md +37 -0
- package/docs/components/ScrollArea.md +39 -0
- package/docs/components/Select.md +131 -0
- package/docs/components/SettingRow.md +98 -0
- package/docs/components/Skeleton.md +68 -0
- package/docs/components/Slider.md +92 -0
- package/docs/components/SlotGrid.md +55 -0
- package/docs/components/SocialButton.md +31 -0
- package/docs/components/Sortable.md +127 -0
- package/docs/components/SortableGroup.md +52 -0
- package/docs/components/Sparkline.md +51 -0
- package/docs/components/Split.md +76 -0
- package/docs/components/Stack.md +59 -0
- package/docs/components/StagePath.md +66 -0
- package/docs/components/StatGroup.md +61 -0
- package/docs/components/StatTile.md +58 -0
- package/docs/components/Sticky.md +69 -0
- package/docs/components/Switch.md +89 -0
- package/docs/components/Table.md +184 -0
- package/docs/components/TableOfContents.md +76 -0
- package/docs/components/Tabs.md +94 -0
- package/docs/components/Text.md +63 -0
- package/docs/components/Textarea.md +59 -0
- package/docs/components/Thread.md +52 -0
- package/docs/components/TimeField.md +70 -0
- package/docs/components/Timeline.md +57 -0
- package/docs/components/Title.md +54 -0
- package/docs/components/ToastViewport.md +61 -0
- package/docs/components/Tooltip.md +63 -0
- package/docs/components/TopBar.md +214 -0
- package/docs/components/Tour.md +85 -0
- package/docs/components/TreeView.md +113 -0
- package/docs/components/VisuallyHidden.md +43 -0
- package/docs/components/WidgetPreview.md +54 -0
- package/docs/components/useBelowBreakpoint.md +11 -0
- package/docs/components/useMonth.md +18 -0
- package/docs/setup.md +51 -0
- package/docs/theming.md +95 -0
- package/docs/tokens.md +123 -0
- package/docs/transient-state.md +56 -0
- package/package.json +82 -4
- package/src/_meta/manifest.ts +293 -0
- package/src/app/AppProvider.tsx +78 -0
- package/src/app/index.ts +3 -0
- package/src/app/themeTokens.ts +95 -0
- package/src/calendar/dateMath.ts +114 -0
- package/src/calendar/formatters.ts +75 -0
- package/src/calendar/index.ts +39 -0
- package/src/calendar/types.ts +36 -0
- package/src/calendar/useAgenda.ts +70 -0
- package/src/calendar/useDay.ts +51 -0
- package/src/calendar/useMonth.ts +82 -0
- package/src/calendar/useWeek.ts +62 -0
- package/src/calendar/weekInfo.ts +83 -0
- package/src/components/Accordion/Accordion.module.scss +304 -0
- package/src/components/Accordion/Accordion.tokens.scss +64 -0
- package/src/components/Accordion/Accordion.tsx +272 -0
- package/src/components/Accordion/AccordionContent.tsx +36 -0
- package/src/components/Accordion/AccordionItem.tsx +66 -0
- package/src/components/Accordion/AccordionTrigger.tsx +121 -0
- package/src/components/Accordion/context.ts +47 -0
- package/src/components/Accordion/index.ts +14 -0
- package/src/components/Alert/Alert.module.scss +92 -0
- package/src/components/Alert/Alert.tokens.scss +60 -0
- package/src/components/Alert/Alert.tsx +136 -0
- package/src/components/Alert/index.ts +2 -0
- package/src/components/AppLayout/AppLayout.module.scss +130 -0
- package/src/components/AppLayout/AppLayout.tokens.scss +27 -0
- package/src/components/AppLayout/AppLayout.tsx +211 -0
- package/src/components/AppLayout/index.ts +2 -0
- package/src/components/Avatar/AgentAvatar.module.scss +117 -0
- package/src/components/Avatar/AgentAvatar.tsx +122 -0
- package/src/components/Avatar/Avatar.module.scss +181 -0
- package/src/components/Avatar/Avatar.tokens.scss +107 -0
- package/src/components/Avatar/Avatar.tsx +210 -0
- package/src/components/Avatar/AvatarGroup.module.scss +128 -0
- package/src/components/Avatar/AvatarGroup.tsx +119 -0
- package/src/components/Avatar/AvatarGroupContext.tsx +20 -0
- package/src/components/Avatar/index.ts +6 -0
- package/src/components/Badge/Badge.module.scss +124 -0
- package/src/components/Badge/Badge.tokens.scss +74 -0
- package/src/components/Badge/Badge.tsx +164 -0
- package/src/components/Badge/index.ts +2 -0
- package/src/components/Banner/Banner.module.scss +73 -0
- package/src/components/Banner/Banner.tokens.scss +51 -0
- package/src/components/Banner/Banner.tsx +138 -0
- package/src/components/Banner/index.ts +2 -0
- package/src/components/BrandIcon/BrandIcon.module.scss +5 -0
- package/src/components/BrandIcon/BrandIcon.tsx +99 -0
- package/src/components/BrandIcon/index.ts +2 -0
- package/src/components/Breadcrumb/Breadcrumb.module.scss +32 -0
- package/src/components/Breadcrumb/Breadcrumb.tokens.scss +9 -0
- package/src/components/Breadcrumb/Breadcrumb.tsx +146 -0
- package/src/components/Breadcrumb/index.ts +2 -0
- package/src/components/Button/Button.module.scss +167 -0
- package/src/components/Button/Button.tokens.scss +90 -0
- package/src/components/Button/Button.tsx +205 -0
- package/src/components/Button/index.ts +2 -0
- package/src/components/ButtonGroup/ButtonGroup.module.scss +134 -0
- package/src/components/ButtonGroup/ButtonGroup.tokens.scss +42 -0
- package/src/components/ButtonGroup/ButtonGroup.tsx +278 -0
- package/src/components/ButtonGroup/ButtonGroupItem.tsx +74 -0
- package/src/components/ButtonGroup/context.ts +45 -0
- package/src/components/ButtonGroup/index.ts +2 -0
- package/src/components/Calendar/AgendaView.module.scss +231 -0
- package/src/components/Calendar/AgendaView.tsx +214 -0
- package/src/components/Calendar/AllDayBand.module.scss +18 -0
- package/src/components/Calendar/AllDayBand.tsx +75 -0
- package/src/components/Calendar/Calendar.module.scss +23 -0
- package/src/components/Calendar/Calendar.tokens.scss +262 -0
- package/src/components/Calendar/Calendar.tsx +370 -0
- package/src/components/Calendar/DayCell.module.scss +30 -0
- package/src/components/Calendar/DayCell.tsx +50 -0
- package/src/components/Calendar/DayView.module.scss +10 -0
- package/src/components/Calendar/DayView.tsx +163 -0
- package/src/components/Calendar/EventChip.module.scss +171 -0
- package/src/components/Calendar/EventChip.tsx +115 -0
- package/src/components/Calendar/HourGrid.module.scss +123 -0
- package/src/components/Calendar/HourGrid.tsx +381 -0
- package/src/components/Calendar/MonthView.module.scss +99 -0
- package/src/components/Calendar/MonthView.tsx +296 -0
- package/src/components/Calendar/TimedEvent.module.scss +205 -0
- package/src/components/Calendar/TimedEvent.tsx +247 -0
- package/src/components/Calendar/ViewSwitcher.module.scss +3 -0
- package/src/components/Calendar/ViewSwitcher.tsx +47 -0
- package/src/components/Calendar/WeekView.module.scss +10 -0
- package/src/components/Calendar/WeekView.tsx +137 -0
- package/src/components/Calendar/eventColor.ts +60 -0
- package/src/components/Calendar/index.ts +18 -0
- package/src/components/Calendar/types.ts +451 -0
- package/src/components/Calendar/useEventDrag.ts +783 -0
- package/src/components/Calendar/utils.ts +622 -0
- package/src/components/Card/Card.module.scss +264 -0
- package/src/components/Card/Card.tokens.scss +55 -0
- package/src/components/Card/Card.tsx +170 -0
- package/src/components/Card/CardBody.tsx +45 -0
- package/src/components/Card/CardHeader.tsx +113 -0
- package/src/components/Card/CardList.tsx +24 -0
- package/src/components/Card/CardListRow.tsx +24 -0
- package/src/components/Card/index.ts +7 -0
- package/src/components/CatalogPicker/CatalogPicker.module.scss +193 -0
- package/src/components/CatalogPicker/CatalogPicker.tokens.scss +26 -0
- package/src/components/CatalogPicker/CatalogPicker.tsx +339 -0
- package/src/components/CatalogPicker/index.ts +2 -0
- package/src/components/Chart/Chart.module.scss +287 -0
- package/src/components/Chart/Chart.tokens.scss +63 -0
- package/src/components/Chart/Chart.tsx +607 -0
- package/src/components/Chart/ChartLegend.tsx +99 -0
- package/src/components/Chart/ChartTable.tsx +62 -0
- package/src/components/Chart/ChartTooltip.tsx +91 -0
- package/src/components/Chart/chartModel.ts +689 -0
- package/src/components/Chart/index.ts +2 -0
- package/src/components/ChatBubble/ChatBubble.module.scss +93 -0
- package/src/components/ChatBubble/ChatBubble.tokens.scss +17 -0
- package/src/components/ChatBubble/ChatBubble.tsx +61 -0
- package/src/components/ChatBubble/ChatBubbleList.tsx +27 -0
- package/src/components/ChatBubble/index.ts +3 -0
- package/src/components/ChatLauncher/ChatLauncher.module.scss +68 -0
- package/src/components/ChatLauncher/ChatLauncher.tokens.scss +27 -0
- package/src/components/ChatLauncher/ChatLauncher.tsx +202 -0
- package/src/components/ChatLauncher/index.ts +2 -0
- package/src/components/ChatLauncher/isAtBottom.ts +12 -0
- package/src/components/Checkbox/Checkbox.module.scss +147 -0
- package/src/components/Checkbox/Checkbox.tokens.scss +44 -0
- package/src/components/Checkbox/Checkbox.tsx +188 -0
- package/src/components/Checkbox/index.ts +2 -0
- package/src/components/CircularProgress/CircularProgress.module.scss +133 -0
- package/src/components/CircularProgress/CircularProgress.tokens.scss +40 -0
- package/src/components/CircularProgress/CircularProgress.tsx +139 -0
- package/src/components/CircularProgress/index.ts +7 -0
- package/src/components/Cluster/Cluster.module.scss +145 -0
- package/src/components/Cluster/Cluster.tokens.scss +11 -0
- package/src/components/Cluster/Cluster.tsx +215 -0
- package/src/components/Cluster/index.ts +2 -0
- package/src/components/Code/Code.module.scss +28 -0
- package/src/components/Code/Code.tokens.scss +15 -0
- package/src/components/Code/Code.tsx +44 -0
- package/src/components/Code/index.ts +2 -0
- package/src/components/CodeBlock/CodeBlock.module.scss +146 -0
- package/src/components/CodeBlock/CodeBlock.tokens.scss +27 -0
- package/src/components/CodeBlock/CodeBlock.tsx +274 -0
- package/src/components/CodeBlock/highlight.ts +179 -0
- package/src/components/CodeBlock/index.ts +8 -0
- package/src/components/CodeBlock/sampleSync.ts +42 -0
- package/src/components/ColorPicker/ColorPicker.module.scss +286 -0
- package/src/components/ColorPicker/ColorPicker.tokens.scss +60 -0
- package/src/components/ColorPicker/ColorPicker.tsx +367 -0
- package/src/components/ColorPicker/ColorPickerPanel.tsx +265 -0
- package/src/components/ColorPicker/SVSquare.tsx +189 -0
- package/src/components/ColorPicker/colorMath.ts +141 -0
- package/src/components/ColorPicker/index.ts +6 -0
- package/src/components/ConfirmationPopover/ConfirmationPopover.module.scss +39 -0
- package/src/components/ConfirmationPopover/ConfirmationPopover.tokens.scss +13 -0
- package/src/components/ConfirmationPopover/ConfirmationPopover.tsx +395 -0
- package/src/components/ConfirmationPopover/index.ts +2 -0
- package/src/components/Constrain/Constrain.module.scss +99 -0
- package/src/components/Constrain/Constrain.tsx +79 -0
- package/src/components/Constrain/index.ts +2 -0
- package/src/components/CurrencySelect/CurrencySelect.module.scss +7 -0
- package/src/components/CurrencySelect/CurrencySelect.tsx +167 -0
- package/src/components/CurrencySelect/index.ts +3 -0
- package/src/components/CursorPagination/CursorPagination.module.scss +69 -0
- package/src/components/CursorPagination/CursorPagination.tokens.scss +32 -0
- package/src/components/CursorPagination/CursorPagination.tsx +121 -0
- package/src/components/CursorPagination/index.ts +2 -0
- package/src/components/DashboardCanvas/DashboardCanvas.module.scss +360 -0
- package/src/components/DashboardCanvas/DashboardCanvas.tokens.scss +48 -0
- package/src/components/DashboardCanvas/DashboardCanvas.tsx +1270 -0
- package/src/components/DashboardCanvas/DashboardCanvasItem.tsx +140 -0
- package/src/components/DashboardCanvas/DashboardCanvasSection.tsx +174 -0
- package/src/components/DashboardCanvas/engine.ts +363 -0
- package/src/components/DashboardCanvas/index.ts +9 -0
- package/src/components/DashboardWidget/DashboardWidget.module.scss +100 -0
- package/src/components/DashboardWidget/DashboardWidget.tokens.scss +16 -0
- package/src/components/DashboardWidget/DashboardWidget.tsx +188 -0
- package/src/components/DashboardWidget/index.ts +7 -0
- package/src/components/DataTable/BodyRow.tsx +197 -0
- package/src/components/DataTable/ColumnVisibilityTrigger.module.scss +7 -0
- package/src/components/DataTable/ColumnVisibilityTrigger.tsx +105 -0
- package/src/components/DataTable/DataTable.module.scss +511 -0
- package/src/components/DataTable/DataTable.tokens.scss +80 -0
- package/src/components/DataTable/DataTable.tsx +734 -0
- package/src/components/DataTable/HeaderCell.module.scss +215 -0
- package/src/components/DataTable/HeaderCell.tsx +440 -0
- package/src/components/DataTable/columnShift.ts +117 -0
- package/src/components/DataTable/index.ts +23 -0
- package/src/components/DataTable/pinStyle.ts +47 -0
- package/src/components/DataTable/reorderColumns.ts +66 -0
- package/src/components/DataTable/types.ts +289 -0
- package/src/components/DataTable/useColumnDragShift.ts +216 -0
- package/src/components/DataTable/useDataTable.ts +372 -0
- package/src/components/DataTable/useResizeHandle.ts +97 -0
- package/src/components/DatePicker/DatePicker.module.scss +180 -0
- package/src/components/DatePicker/DatePicker.tokens.scss +152 -0
- package/src/components/DatePicker/DatePicker.tsx +664 -0
- package/src/components/DatePicker/DatePickerGrid.module.scss +172 -0
- package/src/components/DatePicker/DatePickerGrid.tsx +341 -0
- package/src/components/DatePicker/InlineDatePicker.module.scss +32 -0
- package/src/components/DatePicker/InlineDatePicker.tsx +201 -0
- package/src/components/DatePicker/index.ts +4 -0
- package/src/components/DatePicker/utils.ts +364 -0
- package/src/components/DateRangePicker/DateRangePicker.module.scss +237 -0
- package/src/components/DateRangePicker/DateRangePicker.tokens.scss +78 -0
- package/src/components/DateRangePicker/DateRangePicker.tsx +720 -0
- package/src/components/DateRangePicker/InlineDateRangePicker.module.scss +94 -0
- package/src/components/DateRangePicker/InlineDateRangePicker.tsx +376 -0
- package/src/components/DateRangePicker/index.ts +5 -0
- package/src/components/DateRangePicker/utils.ts +151 -0
- package/src/components/DateStrip/DateStrip.module.scss +78 -0
- package/src/components/DateStrip/DateStrip.tokens.scss +23 -0
- package/src/components/DateStrip/DateStrip.tsx +205 -0
- package/src/components/DateStrip/index.ts +2 -0
- package/src/components/DefinitionList/DefinitionList.module.scss +102 -0
- package/src/components/DefinitionList/DefinitionList.tokens.scss +29 -0
- package/src/components/DefinitionList/DefinitionList.tsx +198 -0
- package/src/components/DefinitionList/index.ts +9 -0
- package/src/components/Divider/Divider.module.scss +144 -0
- package/src/components/Divider/Divider.tokens.scss +21 -0
- package/src/components/Divider/Divider.tsx +103 -0
- package/src/components/Divider/index.ts +2 -0
- package/src/components/Dot/Dot.module.scss +40 -0
- package/src/components/Dot/Dot.tokens.scss +4 -0
- package/src/components/Dot/Dot.tsx +45 -0
- package/src/components/Dot/index.ts +2 -0
- package/src/components/Drawer/Body.tsx +23 -0
- package/src/components/Drawer/Close.tsx +35 -0
- package/src/components/Drawer/Content.tsx +93 -0
- package/src/components/Drawer/Drawer.module.scss +232 -0
- package/src/components/Drawer/Drawer.tokens.scss +38 -0
- package/src/components/Drawer/Drawer.tsx +23 -0
- package/src/components/Drawer/DrawerRoot.tsx +190 -0
- package/src/components/Drawer/Footer.tsx +24 -0
- package/src/components/Drawer/Header.tsx +64 -0
- package/src/components/Drawer/Overlay.tsx +82 -0
- package/src/components/Drawer/context.ts +44 -0
- package/src/components/Drawer/index.ts +12 -0
- package/src/components/Drawer/useDragToClose.ts +149 -0
- package/src/components/DropdownMenu/CheckboxItem.tsx +144 -0
- package/src/components/DropdownMenu/Content.tsx +419 -0
- package/src/components/DropdownMenu/DropdownMenu.module.scss +280 -0
- package/src/components/DropdownMenu/DropdownMenu.tokens.scss +88 -0
- package/src/components/DropdownMenu/DropdownMenu.tsx +43 -0
- package/src/components/DropdownMenu/Group.tsx +76 -0
- package/src/components/DropdownMenu/Item.tsx +148 -0
- package/src/components/DropdownMenu/ItemIndicator.tsx +30 -0
- package/src/components/DropdownMenu/Radio.tsx +178 -0
- package/src/components/DropdownMenu/Root.tsx +100 -0
- package/src/components/DropdownMenu/Sub.tsx +332 -0
- package/src/components/DropdownMenu/Trigger.tsx +76 -0
- package/src/components/DropdownMenu/context.ts +65 -0
- package/src/components/DropdownMenu/index.ts +20 -0
- package/src/components/EmojiPicker/EmojiPicker.module.scss +84 -0
- package/src/components/EmojiPicker/EmojiPicker.tokens.scss +24 -0
- package/src/components/EmojiPicker/EmojiPicker.tsx +341 -0
- package/src/components/EmojiPicker/emojiData.ts +245 -0
- package/src/components/EmojiPicker/index.ts +3 -0
- package/src/components/EmptyState/EmptyState.module.scss +102 -0
- package/src/components/EmptyState/EmptyState.tokens.scss +28 -0
- package/src/components/EmptyState/EmptyState.tsx +112 -0
- package/src/components/EmptyState/index.ts +7 -0
- package/src/components/EntityChip/EntityChip.module.scss +275 -0
- package/src/components/EntityChip/EntityChip.tokens.scss +66 -0
- package/src/components/EntityChip/EntityChip.tsx +649 -0
- package/src/components/EntityChip/index.ts +10 -0
- package/src/components/ErrorState/ErrorState.module.scss +105 -0
- package/src/components/ErrorState/ErrorState.tokens.scss +32 -0
- package/src/components/ErrorState/ErrorState.tsx +125 -0
- package/src/components/ErrorState/index.ts +8 -0
- package/src/components/Field/Field.module.scss +46 -0
- package/src/components/Field/Field.tsx +185 -0
- package/src/components/Field/index.ts +2 -0
- package/src/components/FileUpload/FileUpload.module.scss +187 -0
- package/src/components/FileUpload/FileUpload.tokens.scss +81 -0
- package/src/components/FileUpload/FileUpload.tsx +569 -0
- package/src/components/FileUpload/formatBytes.ts +34 -0
- package/src/components/FileUpload/iconForFile.ts +40 -0
- package/src/components/FileUpload/index.ts +2 -0
- package/src/components/FilterChip/FilterChip.module.scss +61 -0
- package/src/components/FilterChip/FilterChip.tokens.scss +25 -0
- package/src/components/FilterChip/FilterChip.tsx +208 -0
- package/src/components/FilterChip/index.ts +5 -0
- package/src/components/FlowCanvas/FlowCanvas.module.scss +329 -0
- package/src/components/FlowCanvas/FlowCanvas.tokens.scss +45 -0
- package/src/components/FlowCanvas/FlowCanvas.tsx +1867 -0
- package/src/components/FlowCanvas/FlowControls.tsx +46 -0
- package/src/components/FlowCanvas/FlowEdge.tsx +58 -0
- package/src/components/FlowCanvas/FlowNode.tsx +76 -0
- package/src/components/FlowCanvas/edgePath.ts +85 -0
- package/src/components/FlowCanvas/index.ts +4 -0
- package/src/components/FlowCanvas/layout.ts +148 -0
- package/src/components/FlowCanvas/spatialNav.ts +76 -0
- package/src/components/FlowCanvas/types.ts +59 -0
- package/src/components/FlowCanvas/useViewport.ts +122 -0
- package/src/components/FormRow/FormRow.module.scss +2 -0
- package/src/components/FormRow/FormRow.tsx +37 -0
- package/src/components/FormRow/index.ts +2 -0
- package/src/components/FormSection/FormSection.module.scss +24 -0
- package/src/components/FormSection/FormSection.tsx +46 -0
- package/src/components/FormSection/index.ts +2 -0
- package/src/components/Grid/Grid.module.scss +162 -0
- package/src/components/Grid/Grid.tokens.scss +13 -0
- package/src/components/Grid/Grid.tsx +226 -0
- package/src/components/Grid/GridItem.tsx +77 -0
- package/src/components/Grid/index.ts +11 -0
- package/src/components/Highlight/Highlight.module.scss +43 -0
- package/src/components/Highlight/Highlight.tokens.scss +35 -0
- package/src/components/Highlight/Highlight.tsx +164 -0
- package/src/components/Highlight/HighlightOverlay.tsx +100 -0
- package/src/components/Highlight/index.ts +2 -0
- package/src/components/HighlightedText/HighlightedText.module.scss +11 -0
- package/src/components/HighlightedText/HighlightedText.tokens.scss +9 -0
- package/src/components/HighlightedText/HighlightedText.tsx +50 -0
- package/src/components/HighlightedText/highlightRanges.ts +44 -0
- package/src/components/HighlightedText/index.ts +2 -0
- package/src/components/IconPicker/IconPicker.module.scss +175 -0
- package/src/components/IconPicker/IconPicker.tokens.scss +40 -0
- package/src/components/IconPicker/IconPicker.tsx +629 -0
- package/src/components/IconPicker/index.ts +9 -0
- package/src/components/IconTile/IconTile.module.scss +57 -0
- package/src/components/IconTile/IconTile.tokens.scss +16 -0
- package/src/components/IconTile/IconTile.tsx +91 -0
- package/src/components/IconTile/index.ts +2 -0
- package/src/components/Image/Image.module.scss +266 -0
- package/src/components/Image/Image.tokens.scss +55 -0
- package/src/components/Image/Image.tsx +342 -0
- package/src/components/Image/index.ts +2 -0
- package/src/components/ImageCrop/ImageCrop.module.scss +274 -0
- package/src/components/ImageCrop/ImageCrop.tokens.scss +33 -0
- package/src/components/ImageCrop/ImageCrop.tsx +542 -0
- package/src/components/ImageCrop/cropGeometry.ts +120 -0
- package/src/components/ImageCrop/extractCropBlob.ts +140 -0
- package/src/components/ImageCrop/index.ts +11 -0
- package/src/components/ImageCrop/useCropPreview.ts +161 -0
- package/src/components/Indent/Indent.module.scss +33 -0
- package/src/components/Indent/Indent.tokens.scss +10 -0
- package/src/components/Indent/Indent.tsx +46 -0
- package/src/components/Indent/index.ts +2 -0
- package/src/components/Input/Input.module.scss +99 -0
- package/src/components/Input/Input.tokens.scss +54 -0
- package/src/components/Input/Input.tsx +105 -0
- package/src/components/Input/Suggestions.tsx +260 -0
- package/src/components/Input/index.ts +2 -0
- package/src/components/Kanban/Kanban.module.scss +39 -0
- package/src/components/Kanban/Kanban.tokens.scss +15 -0
- package/src/components/Kanban/Kanban.tsx +951 -0
- package/src/components/Kanban/index.ts +7 -0
- package/src/components/Kbd/Kbd.module.scss +47 -0
- package/src/components/Kbd/Kbd.tokens.scss +26 -0
- package/src/components/Kbd/Kbd.tsx +73 -0
- package/src/components/Kbd/index.ts +2 -0
- package/src/components/Lightbox/Lightbox.module.scss +283 -0
- package/src/components/Lightbox/Lightbox.tokens.scss +36 -0
- package/src/components/Lightbox/Lightbox.tsx +506 -0
- package/src/components/Lightbox/index.ts +2 -0
- package/src/components/Link/Link.module.scss +78 -0
- package/src/components/Link/Link.tokens.scss +28 -0
- package/src/components/Link/Link.tsx +114 -0
- package/src/components/Link/index.ts +2 -0
- package/src/components/LinkCard/LinkCard.module.scss +92 -0
- package/src/components/LinkCard/LinkCard.tokens.scss +9 -0
- package/src/components/LinkCard/LinkCard.tsx +72 -0
- package/src/components/LinkCard/index.ts +2 -0
- package/src/components/LiquidEditor/AutocompleteMenu.tsx +133 -0
- package/src/components/LiquidEditor/HighlightLayer.tsx +53 -0
- package/src/components/LiquidEditor/InsertVariableMenu.tsx +81 -0
- package/src/components/LiquidEditor/LiquidEditor.module.scss +339 -0
- package/src/components/LiquidEditor/LiquidEditor.tokens.scss +81 -0
- package/src/components/LiquidEditor/LiquidEditor.tsx +412 -0
- package/src/components/LiquidEditor/PreviewPane.tsx +29 -0
- package/src/components/LiquidEditor/caretCoordinates.ts +97 -0
- package/src/components/LiquidEditor/index.ts +7 -0
- package/src/components/LiquidEditor/liquidFilters.ts +23 -0
- package/src/components/LiquidEditor/liquidTokenizer.ts +235 -0
- package/src/components/LiquidEditor/types.ts +101 -0
- package/src/components/LiquidEditor/useLiquidAutocomplete.ts +178 -0
- package/src/components/List/List.module.scss +105 -0
- package/src/components/List/List.tokens.scss +42 -0
- package/src/components/List/List.tsx +124 -0
- package/src/components/List/index.ts +9 -0
- package/src/components/LiveRegion/LiveRegion.module.scss +3 -0
- package/src/components/LiveRegion/LiveRegion.tsx +116 -0
- package/src/components/LiveRegion/index.ts +2 -0
- package/src/components/Logo/Logo.module.scss +139 -0
- package/src/components/Logo/Logo.tokens.scss +14 -0
- package/src/components/Logo/Logo.tsx +126 -0
- package/src/components/Logo/index.ts +2 -0
- package/src/components/Masonry/Masonry.module.scss +42 -0
- package/src/components/Masonry/Masonry.tokens.scss +8 -0
- package/src/components/Masonry/Masonry.tsx +136 -0
- package/src/components/Masonry/index.ts +2 -0
- package/src/components/Masonry/masonryUtils.ts +42 -0
- package/src/components/MediaTile/MediaTile.module.scss +183 -0
- package/src/components/MediaTile/MediaTile.tokens.scss +60 -0
- package/src/components/MediaTile/MediaTile.tsx +208 -0
- package/src/components/MediaTile/index.ts +8 -0
- package/src/components/Modal/Body.tsx +27 -0
- package/src/components/Modal/Close.tsx +45 -0
- package/src/components/Modal/Content.tsx +98 -0
- package/src/components/Modal/Footer.tsx +28 -0
- package/src/components/Modal/Header.tsx +51 -0
- package/src/components/Modal/Modal.module.scss +262 -0
- package/src/components/Modal/Modal.tokens.scss +50 -0
- package/src/components/Modal/Modal.tsx +23 -0
- package/src/components/Modal/ModalRoot.tsx +299 -0
- package/src/components/Modal/Overlay.tsx +101 -0
- package/src/components/Modal/context.ts +60 -0
- package/src/components/Modal/index.ts +11 -0
- package/src/components/MoneyInput/MoneyInput.module.scss +57 -0
- package/src/components/MoneyInput/MoneyInput.tsx +433 -0
- package/src/components/MoneyInput/index.ts +2 -0
- package/src/components/OptionsPicker/OptionsPicker.module.scss +216 -0
- package/src/components/OptionsPicker/OptionsPicker.tokens.scss +68 -0
- package/src/components/OptionsPicker/OptionsPicker.tsx +936 -0
- package/src/components/OptionsPicker/index.ts +9 -0
- package/src/components/OtpInput/OtpInput.module.scss +69 -0
- package/src/components/OtpInput/OtpInput.tokens.scss +45 -0
- package/src/components/OtpInput/OtpInput.tsx +368 -0
- package/src/components/OtpInput/index.ts +2 -0
- package/src/components/Page/Page.module.scss +30 -0
- package/src/components/Page/Page.tokens.scss +11 -0
- package/src/components/Page/Page.tsx +53 -0
- package/src/components/Page/index.ts +2 -0
- package/src/components/PageHeader/PageHeader.module.scss +197 -0
- package/src/components/PageHeader/PageHeader.tokens.scss +41 -0
- package/src/components/PageHeader/PageHeader.tsx +310 -0
- package/src/components/PageHeader/index.ts +20 -0
- package/src/components/Pagination/Pagination.module.scss +117 -0
- package/src/components/Pagination/Pagination.tokens.scss +54 -0
- package/src/components/Pagination/Pagination.tsx +165 -0
- package/src/components/Pagination/index.ts +4 -0
- package/src/components/Pagination/paginationRange.ts +72 -0
- package/src/components/PasswordInput/PasswordInput.module.scss +120 -0
- package/src/components/PasswordInput/PasswordInput.tokens.scss +44 -0
- package/src/components/PasswordInput/PasswordInput.tsx +210 -0
- package/src/components/PasswordInput/index.ts +2 -0
- package/src/components/PasswordStrengthMeter/PasswordStrengthMeter.module.scss +80 -0
- package/src/components/PasswordStrengthMeter/PasswordStrengthMeter.tokens.scss +40 -0
- package/src/components/PasswordStrengthMeter/PasswordStrengthMeter.tsx +96 -0
- package/src/components/PasswordStrengthMeter/index.ts +2 -0
- package/src/components/PersonDisplay/PersonDisplay.module.scss +180 -0
- package/src/components/PersonDisplay/PersonDisplay.tokens.scss +35 -0
- package/src/components/PersonDisplay/PersonDisplay.tsx +420 -0
- package/src/components/PersonDisplay/index.ts +10 -0
- package/src/components/PhoneInput/PhoneInput.module.scss +23 -0
- package/src/components/PhoneInput/PhoneInput.tsx +271 -0
- package/src/components/PhoneInput/index.ts +4 -0
- package/src/components/PhoneInput/phone.ts +128 -0
- package/src/components/PillMenu/PillMenu.module.scss +117 -0
- package/src/components/PillMenu/PillMenu.tokens.scss +22 -0
- package/src/components/PillMenu/PillMenu.tsx +324 -0
- package/src/components/PillMenu/index.ts +2 -0
- package/src/components/Popover/Anchor.tsx +37 -0
- package/src/components/Popover/Close.tsx +45 -0
- package/src/components/Popover/Content.tsx +213 -0
- package/src/components/Popover/Heading.tsx +33 -0
- package/src/components/Popover/Popover.module.scss +177 -0
- package/src/components/Popover/Popover.tokens.scss +22 -0
- package/src/components/Popover/Popover.tsx +23 -0
- package/src/components/Popover/PopoverRoot.tsx +75 -0
- package/src/components/Popover/Trigger.tsx +86 -0
- package/src/components/Popover/context.ts +22 -0
- package/src/components/Popover/index.ts +7 -0
- package/src/components/Progress/Progress.module.scss +110 -0
- package/src/components/Progress/Progress.tokens.scss +44 -0
- package/src/components/Progress/Progress.tsx +129 -0
- package/src/components/Progress/index.ts +2 -0
- package/src/components/QrCode/QrCode.module.scss +97 -0
- package/src/components/QrCode/QrCode.tokens.scss +17 -0
- package/src/components/QrCode/QrCode.tsx +233 -0
- package/src/components/QrCode/index.ts +2 -0
- package/src/components/QrCode/qr.ts +161 -0
- package/src/components/Radio/Radio.module.scss +175 -0
- package/src/components/Radio/Radio.tokens.scss +59 -0
- package/src/components/Radio/Radio.tsx +127 -0
- package/src/components/Radio/RadioGroup.module.scss +42 -0
- package/src/components/Radio/RadioGroup.tsx +120 -0
- package/src/components/Radio/RadioGroupContext.tsx +30 -0
- package/src/components/Radio/index.ts +4 -0
- package/src/components/Rail/Rail.module.scss +510 -0
- package/src/components/Rail/Rail.tokens.scss +92 -0
- package/src/components/Rail/Rail.tsx +287 -0
- package/src/components/Rail/RailCollapseToggle.tsx +57 -0
- package/src/components/Rail/RailFooter.tsx +17 -0
- package/src/components/Rail/RailGroup.tsx +728 -0
- package/src/components/Rail/RailGroupContext.ts +9 -0
- package/src/components/Rail/RailHeader.tsx +23 -0
- package/src/components/Rail/RailItem.tsx +119 -0
- package/src/components/Rail/RailSection.tsx +39 -0
- package/src/components/Rail/RailSpacer.tsx +19 -0
- package/src/components/Rail/index.ts +9 -0
- package/src/components/RelativeDateInput/RelativeDateInput.module.scss +15 -0
- package/src/components/RelativeDateInput/RelativeDateInput.tokens.scss +6 -0
- package/src/components/RelativeDateInput/RelativeDateInput.tsx +474 -0
- package/src/components/RelativeDateInput/index.ts +10 -0
- package/src/components/RelativeDateInput/relativeDate.ts +127 -0
- package/src/components/RichText/RichText.module.scss +5 -0
- package/src/components/RichText/RichText.tsx +70 -0
- package/src/components/RichText/RichTextAttachment.module.scss +82 -0
- package/src/components/RichText/RichTextAttachment.tsx +122 -0
- package/src/components/RichText/_prose.scss +79 -0
- package/src/components/RichText/attachmentIcon.tsx +24 -0
- package/src/components/RichText/engine/attachment.ts +154 -0
- package/src/components/RichText/engine/autolink.ts +64 -0
- package/src/components/RichText/engine/blockUnit.ts +174 -0
- package/src/components/RichText/engine/colorMarks.ts +85 -0
- package/src/components/RichText/engine/escape.ts +11 -0
- package/src/components/RichText/engine/fromHtml.ts +353 -0
- package/src/components/RichText/engine/fromMarkdown.ts +20 -0
- package/src/components/RichText/engine/inlines.ts +82 -0
- package/src/components/RichText/engine/listDepths.ts +32 -0
- package/src/components/RichText/engine/marks.ts +80 -0
- package/src/components/RichText/engine/mdToHtml.ts +178 -0
- package/src/components/RichText/engine/model.ts +157 -0
- package/src/components/RichText/engine/position.ts +118 -0
- package/src/components/RichText/engine/renderDoc.tsx +395 -0
- package/src/components/RichText/engine/renderLink.ts +15 -0
- package/src/components/RichText/engine/renderMention.ts +16 -0
- package/src/components/RichText/engine/safeHref.ts +22 -0
- package/src/components/RichText/engine/toHtml.ts +154 -0
- package/src/components/RichText/engine/toMarkdown.ts +97 -0
- package/src/components/RichText/engine/transforms.ts +434 -0
- package/src/components/RichText/imageMaxHeightStyle.ts +10 -0
- package/src/components/RichText/index.ts +31 -0
- package/src/components/RichTextEditor/RichTextAttachmentConfig.tsx +264 -0
- package/src/components/RichTextEditor/RichTextBlockControls.tsx +365 -0
- package/src/components/RichTextEditor/RichTextBlockMenu.tsx +162 -0
- package/src/components/RichTextEditor/RichTextColorMenu.tsx +115 -0
- package/src/components/RichTextEditor/RichTextEditor.module.scss +298 -0
- package/src/components/RichTextEditor/RichTextEditor.tsx +1910 -0
- package/src/components/RichTextEditor/RichTextImageResizer.tsx +131 -0
- package/src/components/RichTextEditor/RichTextLinkEditor.tsx +151 -0
- package/src/components/RichTextEditor/RichTextMentionMenu.tsx +99 -0
- package/src/components/RichTextEditor/RichTextToolbar.tsx +379 -0
- package/src/components/RichTextEditor/autolinkInput.ts +65 -0
- package/src/components/RichTextEditor/blockDrop.ts +9 -0
- package/src/components/RichTextEditor/blockReflow.ts +62 -0
- package/src/components/RichTextEditor/commands.ts +300 -0
- package/src/components/RichTextEditor/history.ts +93 -0
- package/src/components/RichTextEditor/icons.tsx +152 -0
- package/src/components/RichTextEditor/imageSize.ts +70 -0
- package/src/components/RichTextEditor/index.ts +5 -0
- package/src/components/RichTextEditor/input.ts +209 -0
- package/src/components/RichTextEditor/inputRules.ts +71 -0
- package/src/components/RichTextEditor/links.ts +103 -0
- package/src/components/RichTextEditor/mentionContext.ts +37 -0
- package/src/components/RichTextEditor/mentions.ts +28 -0
- package/src/components/RichTextEditor/preventSelectionLoss.ts +15 -0
- package/src/components/RichTextEditor/selection.ts +415 -0
- package/src/components/RichTextEditor/shortcuts.ts +44 -0
- package/src/components/RichTextEditor/useAnchoredFloating.ts +50 -0
- package/src/components/RichTextEditor/useAttachmentConfig.ts +134 -0
- package/src/components/RichTextEditor/useDismissOnOutsidePointerDown.ts +26 -0
- package/src/components/RichTextEditor/useLinkEditor.ts +107 -0
- package/src/components/RichTextEditor/useMention.ts +226 -0
- package/src/components/RichTextEditor/useOverlayRect.ts +73 -0
- package/src/components/RichTextEditor/useUpload.ts +246 -0
- package/src/components/Screen/Screen.module.scss +46 -0
- package/src/components/Screen/Screen.tokens.scss +20 -0
- package/src/components/Screen/Screen.tsx +97 -0
- package/src/components/Screen/index.ts +2 -0
- package/src/components/ScrollArea/ScrollArea.module.scss +39 -0
- package/src/components/ScrollArea/ScrollArea.tokens.scss +15 -0
- package/src/components/ScrollArea/ScrollArea.tsx +154 -0
- package/src/components/ScrollArea/index.ts +2 -0
- package/src/components/Select/Chip.tsx +45 -0
- package/src/components/Select/Empty.tsx +39 -0
- package/src/components/Select/Error.tsx +51 -0
- package/src/components/Select/HiddenInputs.tsx +44 -0
- package/src/components/Select/Listbox.tsx +557 -0
- package/src/components/Select/Loading.tsx +30 -0
- package/src/components/Select/Select.module.scss +512 -0
- package/src/components/Select/Select.tokens.scss +140 -0
- package/src/components/Select/Select.tsx +685 -0
- package/src/components/Select/Trigger.tsx +952 -0
- package/src/components/Select/context.ts +101 -0
- package/src/components/Select/emptyStateText.ts +19 -0
- package/src/components/Select/index.ts +9 -0
- package/src/components/Select/useSelectState.ts +157 -0
- package/src/components/Select/utils-types.ts +7 -0
- package/src/components/Select/utils.ts +122 -0
- package/src/components/SettingRow/SettingRow.module.scss +166 -0
- package/src/components/SettingRow/SettingRow.tokens.scss +37 -0
- package/src/components/SettingRow/SettingRow.tsx +242 -0
- package/src/components/SettingRow/index.ts +7 -0
- package/src/components/Skeleton/Skeleton.module.scss +55 -0
- package/src/components/Skeleton/Skeleton.tokens.scss +17 -0
- package/src/components/Skeleton/Skeleton.tsx +131 -0
- package/src/components/Skeleton/index.ts +4 -0
- package/src/components/Skeleton/useSkeletonVisibility.ts +95 -0
- package/src/components/Slider/Slider.module.scss +293 -0
- package/src/components/Slider/Slider.tokens.scss +98 -0
- package/src/components/Slider/Slider.tsx +624 -0
- package/src/components/Slider/index.ts +9 -0
- package/src/components/SlotGrid/SlotGrid.module.scss +61 -0
- package/src/components/SlotGrid/SlotGrid.tokens.scss +21 -0
- package/src/components/SlotGrid/SlotGrid.tsx +111 -0
- package/src/components/SlotGrid/index.ts +2 -0
- package/src/components/SocialButton/SocialButton.module.scss +4 -0
- package/src/components/SocialButton/SocialButton.tsx +40 -0
- package/src/components/SocialButton/index.ts +2 -0
- package/src/components/Sortable/Sortable.module.scss +177 -0
- package/src/components/Sortable/Sortable.tokens.scss +27 -0
- package/src/components/Sortable/Sortable.tsx +572 -0
- package/src/components/Sortable/containerAwareCollision.ts +125 -0
- package/src/components/Sortable/index.ts +13 -0
- package/src/components/SortableGroup/SortableGroup.module.scss +23 -0
- package/src/components/SortableGroup/SortableGroup.tsx +315 -0
- package/src/components/SortableGroup/index.ts +4 -0
- package/src/components/SortableGroup/moveSortableItem.ts +39 -0
- package/src/components/Sparkline/Sparkline.module.scss +28 -0
- package/src/components/Sparkline/Sparkline.tokens.scss +7 -0
- package/src/components/Sparkline/Sparkline.tsx +133 -0
- package/src/components/Sparkline/index.ts +2 -0
- package/src/components/Sparkline/sparklineModel.ts +119 -0
- package/src/components/Split/Split.module.scss +158 -0
- package/src/components/Split/Split.tokens.scss +10 -0
- package/src/components/Split/Split.tsx +231 -0
- package/src/components/Split/index.ts +2 -0
- package/src/components/Stack/Stack.module.scss +67 -0
- package/src/components/Stack/Stack.tokens.scss +11 -0
- package/src/components/Stack/Stack.tsx +100 -0
- package/src/components/Stack/index.ts +2 -0
- package/src/components/StagePath/StagePath.module.scss +255 -0
- package/src/components/StagePath/StagePath.tokens.scss +43 -0
- package/src/components/StagePath/StagePath.tsx +176 -0
- package/src/components/StagePath/index.ts +2 -0
- package/src/components/StatGroup/StatGroup.module.scss +6 -0
- package/src/components/StatGroup/StatGroup.tsx +70 -0
- package/src/components/StatGroup/StatGroupContext.ts +10 -0
- package/src/components/StatGroup/StatTile.module.scss +99 -0
- package/src/components/StatGroup/StatTile.tokens.scss +35 -0
- package/src/components/StatGroup/StatTile.tsx +143 -0
- package/src/components/StatGroup/index.ts +4 -0
- package/src/components/Sticky/Sticky.module.scss +98 -0
- package/src/components/Sticky/Sticky.tokens.scss +17 -0
- package/src/components/Sticky/Sticky.tsx +103 -0
- package/src/components/Sticky/index.ts +2 -0
- package/src/components/Switch/Switch.module.scss +182 -0
- package/src/components/Switch/Switch.tokens.scss +59 -0
- package/src/components/Switch/Switch.tsx +214 -0
- package/src/components/Switch/index.ts +2 -0
- package/src/components/Table/Table.module.scss +308 -0
- package/src/components/Table/Table.tokens.scss +79 -0
- package/src/components/Table/Table.tsx +381 -0
- package/src/components/Table/index.ts +15 -0
- package/src/components/TableOfContents/TableOfContents.module.scss +83 -0
- package/src/components/TableOfContents/TableOfContents.tokens.scss +26 -0
- package/src/components/TableOfContents/TableOfContents.tsx +192 -0
- package/src/components/TableOfContents/activeSection.ts +18 -0
- package/src/components/TableOfContents/index.ts +2 -0
- package/src/components/Tabs/Tabs.module.scss +359 -0
- package/src/components/Tabs/Tabs.tokens.scss +50 -0
- package/src/components/Tabs/Tabs.tsx +538 -0
- package/src/components/Tabs/index.ts +2 -0
- package/src/components/Text/Text.module.scss +115 -0
- package/src/components/Text/Text.tokens.scss +55 -0
- package/src/components/Text/Text.tsx +197 -0
- package/src/components/Text/index.ts +2 -0
- package/src/components/Textarea/Textarea.module.scss +94 -0
- package/src/components/Textarea/Textarea.tokens.scss +48 -0
- package/src/components/Textarea/Textarea.tsx +246 -0
- package/src/components/Textarea/index.ts +2 -0
- package/src/components/Thread/Thread.module.scss +161 -0
- package/src/components/Thread/Thread.tokens.scss +32 -0
- package/src/components/Thread/Thread.tsx +176 -0
- package/src/components/Thread/index.ts +2 -0
- package/src/components/TimeField/TimeField.module.scss +232 -0
- package/src/components/TimeField/TimeField.tsx +793 -0
- package/src/components/TimeField/index.ts +2 -0
- package/src/components/Timeline/Timeline.module.scss +58 -0
- package/src/components/Timeline/Timeline.tokens.scss +13 -0
- package/src/components/Timeline/Timeline.tsx +53 -0
- package/src/components/Timeline/index.ts +2 -0
- package/src/components/Title/Title.module.scss +84 -0
- package/src/components/Title/Title.tokens.scss +37 -0
- package/src/components/Title/Title.tsx +150 -0
- package/src/components/Title/index.ts +2 -0
- package/src/components/Toast/Toast.module.scss +299 -0
- package/src/components/Toast/Toast.tokens.scss +65 -0
- package/src/components/Toast/Toast.tsx +122 -0
- package/src/components/Toast/ToastViewport.tsx +166 -0
- package/src/components/Toast/api.ts +175 -0
- package/src/components/Toast/index.ts +5 -0
- package/src/components/Toast/store.ts +133 -0
- package/src/components/Toast/useToastTimer.ts +94 -0
- package/src/components/Tooltip/Tooltip.module.scss +99 -0
- package/src/components/Tooltip/Tooltip.tokens.scss +23 -0
- package/src/components/Tooltip/Tooltip.tsx +341 -0
- package/src/components/Tooltip/index.ts +2 -0
- package/src/components/TopBar/TopBar.module.scss +235 -0
- package/src/components/TopBar/TopBar.tokens.scss +79 -0
- package/src/components/TopBar/TopBar.tsx +87 -0
- package/src/components/TopBar/TopBarEnd.tsx +20 -0
- package/src/components/TopBar/TopBarIconButton.tsx +86 -0
- package/src/components/TopBar/TopBarSearch.tsx +319 -0
- package/src/components/TopBar/TopBarSearchPanel.tsx +268 -0
- package/src/components/TopBar/TopBarStart.tsx +20 -0
- package/src/components/TopBar/index.ts +11 -0
- package/src/components/Tour/Spotlight.tsx +121 -0
- package/src/components/Tour/Tour.module.scss +210 -0
- package/src/components/Tour/Tour.tokens.scss +58 -0
- package/src/components/Tour/Tour.tsx +617 -0
- package/src/components/Tour/index.ts +2 -0
- package/src/components/Tour/useTourTarget.ts +115 -0
- package/src/components/TreeView/TreeView.module.scss +268 -0
- package/src/components/TreeView/TreeView.tokens.scss +36 -0
- package/src/components/TreeView/TreeView.tsx +536 -0
- package/src/components/TreeView/index.ts +3 -0
- package/src/components/TreeView/treeModel.ts +224 -0
- package/src/components/VisuallyHidden/VisuallyHidden.module.scss +7 -0
- package/src/components/VisuallyHidden/VisuallyHidden.tsx +29 -0
- package/src/components/VisuallyHidden/index.ts +2 -0
- package/src/components/WidgetPreview/WidgetPreview.module.scss +11 -0
- package/src/components/WidgetPreview/WidgetPreview.tokens.scss +26 -0
- package/src/components/WidgetPreview/WidgetPreview.tsx +36 -0
- package/src/components/WidgetPreview/WidgetShape.module.scss +140 -0
- package/src/components/WidgetPreview/WidgetShape.tsx +159 -0
- package/src/components/WidgetPreview/index.ts +2 -0
- package/src/components/_internal/Trend.tsx +27 -0
- package/src/components/_internal/choiceTile.scss +74 -0
- package/src/components/_internal/collapse.scss +10 -0
- package/src/components/_internal/collapse.ts +58 -0
- package/src/components/_internal/currency.ts +257 -0
- package/src/components/_internal/dragAnnouncements.ts +200 -0
- package/src/components/_internal/fieldWiring.ts +225 -0
- package/src/components/_internal/gridSpan.ts +54 -0
- package/src/components/_internal/isoDay.ts +35 -0
- package/src/components/_internal/letterKey.ts +26 -0
- package/src/components/_internal/overlay/focusRestore.ts +21 -0
- package/src/components/_internal/overlay/index.ts +12 -0
- package/src/components/_internal/overlay/tourHold.ts +23 -0
- package/src/components/_internal/overlay/useFocusTrap.ts +120 -0
- package/src/components/_internal/overlay/useInOverlay.ts +81 -0
- package/src/components/_internal/overlay/useOverlayStack.ts +232 -0
- package/src/components/_internal/overlay/useScrollLock.ts +112 -0
- package/src/components/_internal/refs.ts +48 -0
- package/src/components/_internal/statusColor.ts +26 -0
- package/src/components/_internal/timeUtils.ts +23 -0
- package/src/components/_internal/useAsyncOptions.ts +145 -0
- package/src/components/_internal/useClippedTooltip.ts +50 -0
- package/src/components/_internal/useControllableState.ts +50 -0
- package/src/components.manifest.json +1141 -0
- package/src/hooks/useBelowBreakpoint.ts +58 -0
- package/src/i18n/I18nProvider.tsx +52 -0
- package/src/i18n/LocaleProvider.tsx +17 -0
- package/src/i18n/en.ts +648 -0
- package/src/i18n/format.ts +71 -0
- package/src/i18n/index.ts +13 -0
- package/src/i18n/messages.ts +1275 -0
- package/src/i18n/ru.ts +677 -0
- package/src/i18n/useLocale.ts +19 -0
- package/src/i18n/useTranslation.ts +58 -0
- package/src/index.ts +913 -0
- package/src/palette/index.ts +2 -0
- package/src/palette/palette.ts +108 -0
- package/src/styles/dark.scss +4 -0
- package/src/styles/global.scss +7 -0
- package/src/styles/mixins.scss +143 -0
- package/src/styles/reset.scss +78 -0
- package/src/styles/tokens.scss +18 -0
- package/src/styles/typography.scss +44 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# `<EntityChip>` — inline entity-link chip
|
|
2
|
+
|
|
3
|
+
```tsx
|
|
4
|
+
// Inline, inside a sentence — canonically a link to its entity:
|
|
5
|
+
<Text>
|
|
6
|
+
Reassigned <EntityChip href="/contacts/12" icon={<User size={14} />} label="Priya Shah" /> to this deal.
|
|
7
|
+
</Text>
|
|
8
|
+
|
|
9
|
+
// href, prefix + status:
|
|
10
|
+
<EntityChip
|
|
11
|
+
href="/tasks/5"
|
|
12
|
+
prefix="ENG-5"
|
|
13
|
+
label="Fix login bug"
|
|
14
|
+
status={{ label: 'In progress', category: 'in_progress' }}
|
|
15
|
+
/>
|
|
16
|
+
|
|
17
|
+
// RouterLink via `as`, or a status color override:
|
|
18
|
+
import { Link as RouterLink } from 'react-router-dom';
|
|
19
|
+
<EntityChip as={RouterLink} to="/deals/9" label="Acme Corp" status={{ label: 'At risk', color: 'amber' }} />
|
|
20
|
+
|
|
21
|
+
// Chip fill override (categorical, independent of status):
|
|
22
|
+
<EntityChip href="/deals/9" label="Acme Corp" color="violet" />
|
|
23
|
+
|
|
24
|
+
// One-line list row: only the label ellipsizes; adornments inside the chip:
|
|
25
|
+
<EntityChip
|
|
26
|
+
truncate
|
|
27
|
+
href="/tasks/5"
|
|
28
|
+
icon={<CheckSquare size={14} />}
|
|
29
|
+
prefix="ENG-5"
|
|
30
|
+
label="Fix the login bug that only happens on Safari"
|
|
31
|
+
status={{ label: 'In progress', category: 'in_progress' }}
|
|
32
|
+
trailing={<ArrowUp size={14} aria-label="High priority" />}
|
|
33
|
+
/>
|
|
34
|
+
|
|
35
|
+
// Segmented task chip — coloured `before`/`after` parts, capped label:
|
|
36
|
+
<EntityChip
|
|
37
|
+
as={RouterLink} to="/tasks/ENG-15"
|
|
38
|
+
prefix="ENG-15" label="Fix the login bug on Safari" labelMaxWidth={40}
|
|
39
|
+
before={[{ kind: 'icon', icon: <Bug />, label: 'Bug', color: 'red' }]}
|
|
40
|
+
after={[
|
|
41
|
+
{ kind: 'icon', icon: <Equal />, label: 'Normal priority', color: 'slate' },
|
|
42
|
+
{ kind: 'text', text: 'Reported', color: 'amber' },
|
|
43
|
+
]}
|
|
44
|
+
/>
|
|
45
|
+
|
|
46
|
+
// `labelWeight="semibold"` — a heavier key + title:
|
|
47
|
+
<EntityChip href="/tasks/ENG-15" prefix="ENG-15" label="Fix the login bug on Safari" labelWeight="semibold" />
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
<!-- props:start -->
|
|
51
|
+
|
|
52
|
+
## Props
|
|
53
|
+
|
|
54
|
+
<!-- prettier-ignore -->
|
|
55
|
+
| Prop | Type | Required | Description |
|
|
56
|
+
|---|---|---|---|
|
|
57
|
+
| `icon` | `ReactNode` | no | Leading icon — consumer passes the element (e.g. a lucide icon). Rendered aria-hidden. |
|
|
58
|
+
| `label` | `ReactNode` | no | Entity name. Optional at the type level only to satisfy the polymorphic `forwardRef` generic (same workaround `<Rail.Item>` documents on its `children`); in practice every chip needs a label. |
|
|
59
|
+
| `prefix` | `ReactNode` | no | Muted leading run before the name (e.g. task key `ENG-5`). |
|
|
60
|
+
| `status` | `EntityChipStatus` | no | Inline workflow status `{ label, category?, color? }`, separated by a small dot and rendered in the status's own color. `category` (`to_do`/`in_progress`/`open`/`done`/`won`/`lost`) resolves a default palette color (same mapping as `<PillMenu>`); `color` overrides it. The dot takes the resolved color too. |
|
|
61
|
+
| `color` | `PaletteColor` | no | Optional categorical palette color for the chip fill. When set, takes precedence over the default accent chip tokens: the chip fills with the matching `--color-palette-<name>-bg/-fg` pair (same contract as `Badge color` — picking a readable pair is the consumer's call). Omit for the default fill, which matches RichText's `@mention` styling (`--color-accent-bg-subtle` / `--color-accent`) so a chip and a rendered mention read as the same visual object. The `status` run keeps its own resolved color independent of this prop, and `unavailable` still mutes the label/status text over any `color`. |
|
|
62
|
+
| `href` | `string` | no | Renders `as="a"` with this href when `as` is omitted. |
|
|
63
|
+
| `loading` | `boolean` | no | Loading placeholder: icon slot + `…` body, aria-busy; the label stays in the DOM visually hidden so the chip keeps its accessible name. Purely visual — with a link target (`href`/`as`) the chip stays a live, keyboard-reachable link while loading. The state is announced: a localized word is rendered visually hidden, so a linked chip's name becomes "Appointment (loading)". `aria-busy` alone did not do this. It is a GLOBAL ARIA state, so unlike `aria-disabled` it is perfectly valid on the chip's role-less span and browsers do expose it — but no mainstream screen reader conveys `busy` as a state on a non-live element, and the ellipsis is `aria-hidden`. The label reached the user fine; no signal of the LOADING STATE did. Know the trade: the accessible name CHANGES when loading resolves — and by more than this word. The loading branch renders neither `prefix` nor `status` (nor `before`/`after` segments), so a chip with both goes from "Fix login bug (loading)" to "ENG-5 Fix login bug In progress". That is unavoidable when announcing a transient state through the name, and it is why this was initially left alone. Note it also means a consumer query like `getByRole('link', { name: 'Appointment' })` no longer matches a LOADING chip. Measured, the mutation itself is benign: Chromium keeps the SAME accessibility node across the transition (same backendNodeId) and focus survives, so nothing is torn down and rebuilt — the name and `busy` simply change on the existing object. The alternative — an `aria-live` region — has to be owned by the consumer, because only the consumer knows whether a given chip resolving is worth interrupting someone for. To take that route, override this word to `''` at your TOP-LEVEL provider: the override is global to a provider subtree (there is no per-chip opt-out), `I18nProvider` requires a `locale`, and a nested provider replaces rather than extends its parent — so wrapping a single chip would silently discard every other override you have set. |
|
|
64
|
+
| `unavailable` | `boolean` | no | Entity missing/no access: muted styling. With a link target (`href`/`as`) the chip remains a real link — unavailable is a visual state, not a disabling one. Only a target-less chip (bare span) becomes non-interactive with aria-disabled. The state is also announced: a localized word is rendered visually hidden inside the chip. A linked chip's accessible name becomes "Appointment (unavailable)"; a target-less chip is `role=generic`, which has no accessible name at all, so the word is announced as part of the chip's text in reading order. Either way it reaches the user. That matters because the canonical use is to withhold the entity's name and show a TYPE word instead — without the state, a masked reference is indistinguishable from a real entity that happens to be called "Appointment". Colour alone cannot carry it, and `aria-disabled` does not either: browsers do expose it, but it carries no meaning on a non-widget role such as `generic`, so no assistive tech conveys it. `loading` is announced the same way — see that prop for why `aria-busy` alone was not enough, and for the name-mutation trade it carries. Do NOT put an `aria-label` on the chip. It replaces the whole name and takes the state word with it; the state lives in the chip's contents. Override the word via the i18n provider (`entityChip.unavailable`); it carries its own punctuation so a locale can choose different marks. |
|
|
65
|
+
| `truncate` | `boolean` | no | Single-line mode for list rows. The chip is capped at its container's width (`max-width: 100%`, `min-width: 0`, so it also shrinks as a flex item) and only the `label` ellipsizes — `icon`, `prefix`, `status` and `trailing` keep their full size. Default `false`: the label wraps, which is right for a chip inside running text. The full label stays in the DOM, so the accessible name is unchanged. A clipped label shows its full text in a tooltip on hover or keyboard focus — don't add a `title`, which would give a double tooltip. |
|
|
66
|
+
| `trailing` | `ReactNode` | no | Extra adornments inside the chip, after `status` (e.g. a priority icon or a status `<Badge>`), kept at full size under `truncate`. Not rendered while `loading` (same as `prefix`/`status`). Non-interactive content only: the chip is itself a link, so a button or link here is invalid nested interactive content. Its text JOINS the chip's accessible name ("ENG-5 Fix login bug High") — give a decorative icon `aria-hidden`, and a meaningful one a short text alternative. |
|
|
67
|
+
| `before` | `readonly EntityChipSegment[]` | no | Coloured segments before the chip's core, in order (e.g. the task type). Any segment turns the chip segmented: one line, only the outer corners rounded, only the label shrinks (as with `truncate`). Every segment's text joins the accessible name ("Bug ENG-15 Fix login bug Normal Reported"). Not rendered while `loading` or `unavailable`. |
|
|
68
|
+
| `after` | `readonly EntityChipSegment[]` | no | Coloured segments after the core, in order (e.g. priority, status). See `before`. |
|
|
69
|
+
| `labelMaxWidth` | `number` | no | Caps the label's width, a positive number of `ch`; past it the label ellipsizes on one line (also on a chip without `truncate`). For chips in running text, where the container edge is a whole paragraph away. The full label stays in the DOM and the accessible name; a clipped label shows it in a tooltip on hover or keyboard focus. |
|
|
70
|
+
| `labelEllipsis` | `'end' \| 'start'` | no | Which end of a clipped label is cut. Applies only where the label can clip at all (`truncate`, segments, or `labelMaxWidth`); ignored on a wrapping chip. - `'end'` (default) — `Fix the login bu…`. Right for titles, whose distinctive part is the start. - `'start'` — `…/pull/1116`. For labels whose distinctive part is the tail, e.g. a URL path under a host `prefix`. The text keeps its order; `prefix` and segments stay whole, and the tooltip still shows it all. |
|
|
71
|
+
| `labelWeight` | `'medium' \| 'semibold'` | no | Font weight for `label`. `'medium'` (default) matches a plain chip's fixed weight and the RichText `@mention` — most chips should leave this unset. `'medium'` does NOT apply to `prefix`: `.prefix` carries no font-weight rule of its own and simply inherits (normal), so a default chip's key is lighter than its title. `'semibold'` is the one value that touches BOTH — it sets `prefix` AND `label` to the same heavier weight (a task-chip design that wants its key heavier along with its title). Use `'semibold'` for a design that explicitly wants that. Don't reach for a styled `<Text weight="semibold">` around `label` to get a heavier title instead (see the clipped-label tooltip bullet in `docs/components/EntityChip.md`) — it buys nothing: the clipped-label tooltip reads the label's own `textContent` and always renders it plain, in the tooltip's own color, regardless of what the label node carries. Only this prop changes the rendered weight. |
|
|
72
|
+
| `as` | `ElementType` | no | |
|
|
73
|
+
| …native | | | plus native attributes of the `as` element (default `<a>`) |
|
|
74
|
+
|
|
75
|
+
<!-- props:end -->
|
|
76
|
+
|
|
77
|
+
- Polymorphic inline chip: optional `icon` (rendered `aria-hidden`), optional muted `prefix` (e.g. a task key), the `label`, and an optional colored `status`. All inline `<span>`s inside one root — safe to drop directly inside a `<p>`/`<Text>`.
|
|
78
|
+
- **An EntityChip is always a link to its entity**: pass `href` (renders `<a href>`) or `as` (`RouterLink`, `'button'`, any component — same polymorphic contract as `<Link>`). The bare `<span>` form (no target) is for rare non-navigable contexts only.
|
|
79
|
+
- Default chip fill matches RichText's `@mention` styling (`--color-accent-bg-subtle` / `--color-accent`) — a chip and a rendered mention read as the same visual object in both themes. `color?: PaletteColor` overrides the fill (same inline-injection contract as `Badge color`) — independent of `status`, which keeps its own resolved color. `unavailable` still mutes the label/status/dot over any `color`.
|
|
80
|
+
- ⚠️ **On a chip with a link target, both state words change the accessible name**, so a name-exact query stops matching: `getByRole('link', { name: 'Appointment' })` misses a chip that is loading or unavailable. If your tests select chips by name over lists that render placeholders, use a regex or query before the state applies.
|
|
81
|
+
- **`loading` is announced too.** Same mechanism, and `aria-busy` alone did not do it either — it is a _global_ ARIA state so it IS valid on the role-less span and browsers expose it, but no mainstream screen reader reliably conveys `busy` on a non-live element, and the `…` is `aria-hidden`. The label reached the user; the state did not. A linked chip's name becomes `"Appointment (loading)"`. **The trade:** the accessible name changes when loading resolves. Announcing a transient state through the name always costs that; the alternative is a consumer-owned `aria-live` region, since only the consumer knows whether a chip resolving is worth interrupting for. Override the word to `''` **at your top-level provider** if you'd rather handle it yourself — a nested `I18nProvider` replaces its parent rather than extending it, so wrapping a single chip would discard every other override you have set.
|
|
82
|
+
- **`unavailable` is announced, not just muted.** A localized word is rendered visually hidden inside the chip. A **linked** chip's accessible name becomes `"Appointment (unavailable)"`; a **target-less** chip is `role=generic`, which has no accessible name at all, so the word is announced as part of the chip's text in reading order. Either way it reaches the user.
|
|
83
|
+
- Why `aria-disabled` was not enough: browsers do expose it, but it carries no meaning on a non-widget role such as `generic`, so no assistive tech conveys it. This matters because the canonical use is to withhold the entity's name and show a TYPE word instead — without the state, a masked reference is indistinguishable from a real entity that happens to be called "Appointment".
|
|
84
|
+
- ❌ Don't put an `aria-label` on the chip — it replaces the whole name and takes the state word with it. The state lives in the chip's contents. Override the word via `<I18nProvider overrides={{ entityChip: { unavailable: '…' } }}>` instead; it carries its own punctuation so a locale can pick different marks.
|
|
85
|
+
- **`truncate`** — single-line mode for list rows: the chip caps at its container (`max-width: 100%`, `min-width: 0`) and only `label` ellipsizes; `icon`/`prefix`/`status`/`trailing` keep full size. The accessible name keeps the full label. Default wraps — right for chips in running text, so don't set `truncate` there.
|
|
86
|
+
- **`trailing`** — adornments inside the chip after `status` (a priority icon, a `<Badge>`), full size under `truncate`, not rendered while `loading`. Its text JOINS the link's name (`aria-hidden` a decorative icon). ❌ Never interactive content — the chip is a link; row actions go beside it.
|
|
87
|
+
- Hover affordance on link/button chips: the background deepens a step plus a brightness dip. Never a weight change, never an underline, even under aggressive consumer link CSS.
|
|
88
|
+
- Chip text inherits the surrounding font size — inside a heading it renders at heading size, by design (that's what keeps the chip box symmetric around the local text in any context).
|
|
89
|
+
- **Segments** (`before` / `after`): coloured parts butted against the chip — `{ kind: 'icon', icon, label, color?, size? }` (label = accessible name + tooltip) or `{ kind: 'text', text, color?, tooltip?, size? }`; `size` is in em of the chip text, at most 1 (larger is clamped, so a segment never makes the chip taller or knocks its text off the label's baseline); glyph default 0.85em, text default 0.9em. The whole chip stays one link; every segment joins its name. Any segment makes the chip one line (only the label shrinks) with only the outer corners rounded. Not rendered while `loading`/`unavailable`. `labelMaxWidth` (ch) caps the label in running text; a clipped label shows its full text in a tooltip on hover or keyboard focus. That text is always plain; a non-string `label` is read when the tooltip opens, so it can show old text if the label changes while the tooltip is open. `labelEllipsis="start"` cuts a clipped label from its START (`…/pull/1116`) for labels whose tail is the distinctive part, e.g. a URL path under a host `prefix`; default `'end'`. Ignored on a chip whose label can't clip.
|
|
90
|
+
- **A text segment's text sits on the label's baseline, not centred**: the segment's own box keeps the chip's font-size/line-height (so its box and first-line baseline match the core exactly); the smaller `--entity-chip-segment-text-size` lives on an inner span instead, which then sits on that baseline through normal inline layout. Icon (glyph) segments stay vertically centred, as before — only text segments changed.
|
|
91
|
+
- **The clipped-label tooltip is always plain text**: it reads the label element's own `textContent` when it opens, not the `label` node itself — so a styled `label` (e.g. a `<Text tone="accent">`) never leaks its color/weight into the tooltip. No prop controls this; it's automatic.
|
|
92
|
+
- **`labelWeight`**: `'medium'` (default, matches a plain chip / the RichText `@mention`) applies to `label` only — `prefix` has no weight rule of its own and stays inherited (normal). `'semibold'` is the one value that also touches `prefix`, setting BOTH `prefix` and `label` together (a heavier key + title, e.g. a segmented task chip). Backed by a component token (`--entity-chip-label-font-weight-semibold`), not by wrapping `label` in a styled `<Text>` — see the anti-pattern below.
|
|
93
|
+
- **When NOT to use**: plain status with no linked entity → `<Badge>`/`<PillMenu>`; standalone navigation with no icon/prefix/status chrome → `<Link>`; removable filter pills → `<FilterChip>`.
|
|
94
|
+
- **Anti-pattern**: nesting a `<Badge>` inside another `<Badge>` to fake an entity-with-status chip — `EntityChip` replaces that composition. `status.color` and the chip's own `color` are `PaletteColor` names, never raw hex strings. Omitting a link target (`href`/`as`) is also an anti-pattern — an EntityChip should link to its entity. Nesting `<IconTile>`/`<Badge>` in `icon`/`trailing` to fake coloured parts is also an anti-pattern — use `before`/`after` segments instead. Wrapping `label` in a styled `<Text weight="semibold">` to get a heavier title is also an anti-pattern — use `labelWeight="semibold"`; it buys nothing anyway, since the clipped-label tooltip always renders the label's plain text regardless of how `label` is styled.
|
|
95
|
+
- ❌ Block-level children (e.g. a `<div>`) inside `label` / `prefix` — the inline-safety contract requires span-only content.
|
|
96
|
+
- ❌ An icon segment without a meaningful `label` — it is the segment's accessible name; a decorative glyph belongs in `icon`, not a segment.
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
// Loading / unavailable — still live links with a target
|
|
100
|
+
<EntityChip href="/contacts/7" label="Contact" loading />
|
|
101
|
+
<EntityChip href="/contacts/9" label="Deleted contact" unavailable />
|
|
102
|
+
```
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# `<ErrorState>` — page-level status / result screen
|
|
2
|
+
|
|
3
|
+
```tsx
|
|
4
|
+
// 404 — neutral
|
|
5
|
+
<ErrorState
|
|
6
|
+
icon={<Compass size={48} aria-hidden="true" />}
|
|
7
|
+
title="Page not found"
|
|
8
|
+
description="The page you're looking for doesn't exist or has been moved."
|
|
9
|
+
actions={<Button>Go to homepage</Button>}
|
|
10
|
+
/>
|
|
11
|
+
|
|
12
|
+
// Error-boundary fallback — danger tone → role="alert"
|
|
13
|
+
<ErrorState
|
|
14
|
+
tone="danger"
|
|
15
|
+
icon={<TriangleAlert size={48} aria-hidden="true" />}
|
|
16
|
+
title="Something went wrong"
|
|
17
|
+
actions={<Button>Try again</Button>}
|
|
18
|
+
extra={<Text size="sm" tone="muted">Error ID: a1b2-c3d4</Text>}
|
|
19
|
+
/>
|
|
20
|
+
|
|
21
|
+
// Page-level loading → error transition. This Card intentionally owns the
|
|
22
|
+
// existing page-level status surface and stays mounted across both states.
|
|
23
|
+
<Card role="status" aria-busy={!failed}>
|
|
24
|
+
{failed ? (
|
|
25
|
+
<ErrorState
|
|
26
|
+
tone="danger"
|
|
27
|
+
role={undefined}
|
|
28
|
+
size="md"
|
|
29
|
+
headingLevel={2}
|
|
30
|
+
title="We couldn't load the account"
|
|
31
|
+
actions={<Button onClick={retry}>Try again</Button>}
|
|
32
|
+
/>
|
|
33
|
+
) : (
|
|
34
|
+
<Text>Loading account details…</Text>
|
|
35
|
+
)}
|
|
36
|
+
</Card>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
<!-- props:start -->
|
|
40
|
+
|
|
41
|
+
## Props
|
|
42
|
+
|
|
43
|
+
<!-- prettier-ignore -->
|
|
44
|
+
| Prop | Type | Required | Description |
|
|
45
|
+
|---|---|---|---|
|
|
46
|
+
| `icon` | `ReactNode` | no | Icon rendered above the title. Pass a lucide icon (sized by the consumer — lg=48, md=32, sm=24), custom SVG, or any ReactNode. The icon's color is set by `tone`; pass `aria-hidden="true"` on it when purely decorative. |
|
|
47
|
+
| `title` | `ReactNode` | yes | Required title, rendered as a semantic heading (default `<h1>` — it's usually the page heading). Accepts ReactNode for inline emphasis. |
|
|
48
|
+
| `description` | `ReactNode` | no | Optional description rendered below the title. |
|
|
49
|
+
| `actions` | `ReactNode` | no | Optional action(s) below the description — a `<Button>` or a `<Cluster gap="sm">` of buttons. Keep to ONE primary action. |
|
|
50
|
+
| `extra` | `ReactNode` | no | Optional supplemental content rendered below the actions — e.g. an `Error ID: …` line or a "view status" link. Reads as metadata, not primary copy. Distinct from `description`, which sits above the actions. |
|
|
51
|
+
| `tone` | `'neutral' \| 'danger'` | no | Status tone. Defaults to `'neutral'`. - `'neutral'` — informational (404 / not-found). Icon uses `--color-fg-muted`. - `'danger'` — an error (500 / crash). Icon uses `--color-danger`, and the wrapper gets `role="alert"` so an error-boundary fallback announces on mount. Override the role by passing your own `role`. |
|
|
52
|
+
| `size` | `'sm' \| 'md' \| 'lg'` | no | Visual size. Defaults to `'lg'` (full-page hero). Use `'sm'` / `'md'` when the state is embedded in a smaller surface. |
|
|
53
|
+
| `align` | `'center' \| 'start'` | no | Horizontal alignment of the stacked content. Defaults to `'center'`. Use `'start'` in a tight column where centering looks stranded. |
|
|
54
|
+
| `headingLevel` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | no | Heading level for `title`. Defaults to `1` (page-level). Lower it when the screen is nested under an existing heading. Values outside `1–6` clamp to `1`. |
|
|
55
|
+
| …native | | | plus native HTML attributes |
|
|
56
|
+
|
|
57
|
+
<!-- props:end -->
|
|
58
|
+
|
|
59
|
+
- Page-level sibling of `<EmptyState>` — the component EmptyState's docs point to for "page-level 404 / 500" and danger-tinted error states. Use `<EmptyState>` for "nothing here" inside a surface; use `<Alert tone="error">` for an in-flow banner.
|
|
60
|
+
- `tone="danger"` makes the wrapper `role="alert"` (announces the whole subtree assertively on mount — ideal for an error-boundary fallback). For a _standalone_ error page, pass `role={undefined}` so it isn't read as a wall of text on load. Override via `role`.
|
|
61
|
+
- **Loading → error in an existing page-level surface:** the example's Card is intentionally the stable page-level transition surface, not a recommendation to nest ErrorState in arbitrary cards. Keep that one `Card role="status" aria-busy={!failed}` mounted, with either loading content or `<ErrorState role={undefined}>` inside it. A live region mounted together with the error has no mutation to announce, and a page-sized assertive alert is inappropriate for this update.
|
|
62
|
+
- For `tone="neutral"`, the `<section>` is not a screen-reader landmark unless it has an accessible name — pass `aria-label` / `aria-labelledby` when it IS the page's primary region (typical for a full-page 404).
|
|
63
|
+
- No automatic `aria-hidden` on the icon — pass `aria-hidden="true"` for a decorative icon. No i18n — all copy is consumer-supplied.
|
|
64
|
+
- Not for a status the tone scale doesn't cover (warning / success) — extend the `ErrorStateTone` union rather than repurposing `danger`.
|
|
65
|
+
- ❌ Multiple primary buttons — one clear primary; secondaries are `variant="secondary"` / `ghost`. ❌ A long sentence as `title` — it is the page heading, keep it short.
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
// Centered inside a Screen (standalone page)
|
|
69
|
+
<Screen backdrop="accent">
|
|
70
|
+
<ErrorState title="Page not found" actions={<Button>Go home</Button>} />
|
|
71
|
+
</Screen>
|
|
72
|
+
```
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# `<Field>` — labeled-control unit
|
|
2
|
+
|
|
3
|
+
```tsx
|
|
4
|
+
<Field label="Work email" error={errors.email} required>
|
|
5
|
+
<Input type="email" />
|
|
6
|
+
</Field>
|
|
7
|
+
|
|
8
|
+
// wrapped / native control → render-prop, spread `field`:
|
|
9
|
+
<Field label="Email" error={errors.email}>
|
|
10
|
+
{(field) => <input type="email" {...field} />}
|
|
11
|
+
</Field>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
<!-- props:start -->
|
|
15
|
+
|
|
16
|
+
## Props
|
|
17
|
+
|
|
18
|
+
### `FieldProps`
|
|
19
|
+
|
|
20
|
+
<!-- prettier-ignore -->
|
|
21
|
+
| Prop | Type | Required | Description |
|
|
22
|
+
|---|---|---|---|
|
|
23
|
+
| `label` | `ReactNode` | no | Label text. Renders a `<label htmlFor>` (or, in `asGroup`, a `role="group"` caption). An empty or falsy value (e.g. `0`, `''`, `[]`, `<></>`) renders no label at all; see the anti-pattern below. |
|
|
24
|
+
| `description` | `ReactNode` | no | Helper text below the control. Hidden while `error` is present. |
|
|
25
|
+
| `error` | `ReactNode` | no | Error message. Replaces `description`, flips the control to `invalid`, and links it with `aria-describedby`. **Not announced, deliberately.** `aria-describedby` is read on FOCUS, so an error that appears after a submit reaches nobody unless focus moves into the field. Hard rule 10 permits chosen silence provided it is written down; this is that note. The alternative — a live region per Field — is worse in the common case: a validate-on-change form would announce on every keystroke, and a submit failing N fields would fire N announcements over each other. The right owner of that announcement is the FORM, which knows how many fields failed and when the user asked for the answer. So: announce a summary yourself on submit, and leave the per-field message to `aria-describedby` for when focus arrives. (example below) |
|
|
26
|
+
| `required` | `boolean` | no | Marks the field required: shows `*` and injects `required` onto the control. |
|
|
27
|
+
| `optional` | `boolean` | no | Marks the field optional: shows `(optional)`. Mutually exclusive with `required`. |
|
|
28
|
+
| `orientation` | `'vertical' \| 'horizontal'` | no | Label placement. Default `'vertical'`. `'horizontal'` puts the label beside the control. |
|
|
29
|
+
| `size` | `'sm' \| 'md' \| 'lg'` | no | Label/message type scale. Default `'md'`. Size primarily scales the label; the help/error message uses a compact fixed scale (`md` and `lg` both render the message at `sm`). |
|
|
30
|
+
| `id` | `string` | no | Explicit control id. Field owns the id by default (auto-generated) so the label always matches. |
|
|
31
|
+
| `asGroup` | `boolean` | no | Group mode for radio/checkbox sets: label becomes a `role="group"` caption (no `htmlFor`). |
|
|
32
|
+
| `children` | `FieldChild` | yes | A single control element (auto-wired) or a render-prop `(field) => ReactNode`. |
|
|
33
|
+
| …native | | | plus native `<div>` attributes |
|
|
34
|
+
|
|
35
|
+
**`error`** example:
|
|
36
|
+
|
|
37
|
+
<!-- prettier-ignore -->
|
|
38
|
+
```tsx
|
|
39
|
+
<div role="status" aria-live="polite">
|
|
40
|
+
{submitted && errorCount > 0 ? `${errorCount} fields need attention` : ''}
|
|
41
|
+
</div>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### `FieldRenderProps`
|
|
45
|
+
|
|
46
|
+
<!-- prettier-ignore -->
|
|
47
|
+
| Prop | Type | Required | Description |
|
|
48
|
+
|---|---|---|---|
|
|
49
|
+
| `id` | `string` | yes | |
|
|
50
|
+
| `aria-describedby` | `string \| undefined` | yes | |
|
|
51
|
+
| `aria-labelledby` | `string \| undefined` | yes | Id of the label element to name the control — set only when a label is rendered. |
|
|
52
|
+
| `aria-invalid` | `boolean \| undefined` | yes | |
|
|
53
|
+
| `invalid` | `boolean` | yes | |
|
|
54
|
+
| `required` | `boolean` | yes | |
|
|
55
|
+
| `labelId` | `string` | yes | Id of the label/caption element — for manual `aria-labelledby` wiring. |
|
|
56
|
+
|
|
57
|
+
<!-- props:end -->
|
|
58
|
+
|
|
59
|
+
- Wraps ONE control with its label + help/error + required marker, and auto-wires
|
|
60
|
+
`id` / `aria-labelledby` / `aria-describedby` / `invalid` (controls map `invalid → aria-invalid`).
|
|
61
|
+
- When a `label` is present, Field injects `aria-labelledby` onto the cloned child, so
|
|
62
|
+
composite controls that forward ARIA props (`Select`, `Slider`, `ColorPicker`,
|
|
63
|
+
`IconPicker`, `FileUpload`, `TimeField`) get an accessible name for free. The render-prop `field`
|
|
64
|
+
object also carries `aria-labelledby` for wrapped/nested DOM.
|
|
65
|
+
`optional` shows `(optional)`. `orientation="horizontal"` = label beside control.
|
|
66
|
+
- Field owns the control `id` — to set one, use `<Field id>`, not the control.
|
|
67
|
+
- Groups: `<Field asGroup>` around `<RadioGroup>` → label becomes a `role="group"` caption.
|
|
68
|
+
- ❌ Don't wrap a single `<Checkbox>`/`<Switch>` (they self-label). ❌ No validation/state — pass `error` from your form layer.
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
// Radio/checkbox group — label becomes a role="group" caption
|
|
72
|
+
<Field asGroup label="Notify me" error={errors.notify}>
|
|
73
|
+
<RadioGroup name="notify">
|
|
74
|
+
<Radio value="all" label="All activity" />
|
|
75
|
+
<Radio value="mentions" label="Only mentions" />
|
|
76
|
+
</RadioGroup>
|
|
77
|
+
</Field>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
#### When NOT to use
|
|
81
|
+
|
|
82
|
+
- A single `<Checkbox>` / `<Switch>` — they carry their own inline `label`; use the control's `label` prop instead of double-labeling.
|
|
83
|
+
- Read-only key/value display — use `<DefinitionList>`.
|
|
84
|
+
- Arranging multiple fields — that is `<FormRow>` / `<FormSection>` / `<Stack>`.
|
|
85
|
+
|
|
86
|
+
#### Anti-patterns
|
|
87
|
+
|
|
88
|
+
- ❌ Auto-wiring a raw native `<input>` and expecting `aria-invalid`: auto-clone injects the DS `invalid` prop. For a native element use the render-prop and spread `field` (it includes `aria-invalid`).
|
|
89
|
+
- ❌ Passing both `required` and `optional`.
|
|
90
|
+
- ⚠️ `label` / `description` / `error` treat `0`, `NaN`, `false`, `''`, an empty array and an empty fragment (`<></>`) as ABSENT. `error={errors.map(...)}` with no errors, `label={<></>}` or `description={[]}` render nothing — no `<label>`, no description, no error text, no `invalid` flip — so the control falls back to its own `aria-label` or ends up unnamed. A real but visually empty node (`label={<span />}`, `label=" "`) or an empty one-shot iterator (`Map.prototype.values()`, a generator) still counts as present. Pass `undefined` explicitly for "none" rather than a container that might be empty.
|
|
91
|
+
- ⚠️ An absent `label` also removes the `required` / `optional` marker, since both live inside the `<label>`: `<Field label={0} required>` shows no `*`.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# `<FileUpload>` — controlled file picker with dropzone
|
|
2
|
+
|
|
3
|
+
```tsx
|
|
4
|
+
<FileUpload
|
|
5
|
+
files={files} // controlled FileEntry[]
|
|
6
|
+
onFilesAdded={(files) => /* wrap with id + status: 'pending' */}
|
|
7
|
+
onFileRemove={(entry) => /* remove from state */}
|
|
8
|
+
onFileReject={(file, reason) => toast.error(`${file.name}: ${reason}`)}
|
|
9
|
+
multiple
|
|
10
|
+
accept=".csv,application/vnd.ms-excel"
|
|
11
|
+
maxSize={10 * 1024 * 1024}
|
|
12
|
+
maxFiles={5}
|
|
13
|
+
validator={(f) => f.name.includes(' ') ? 'No spaces in filenames' : null}
|
|
14
|
+
dropzoneHint="CSV or Excel, up to 10 MB"
|
|
15
|
+
/>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
<!-- props:start -->
|
|
19
|
+
|
|
20
|
+
## Props
|
|
21
|
+
|
|
22
|
+
<!-- prettier-ignore -->
|
|
23
|
+
| Prop | Type | Required | Description |
|
|
24
|
+
|---|---|---|---|
|
|
25
|
+
| `id` | `string` | no | Placed on the hidden native `<input type="file">`, not the root — it is the only labelable element, so a `<label for>` (Field / SettingRow wire one) opens the file picker when clicked. A `role="button"` div is not labelable, so the id on the dropzone would make the label click a no-op. While the dropzone is hidden (single mode with a file) the id falls back to the root, so scroll-to-id / focus-first-invalid still find the field. |
|
|
26
|
+
| `invalid` | `boolean` | no | Marks the dropzone `aria-invalid` for Field / SettingRow composition. Default: `false`. |
|
|
27
|
+
| `required` | `boolean` | no | Consumed for Field / SettingRow composition (they render the visible required marker). Not forwarded: `role="button"` does not support `aria-required`, and native `required` on the hidden input would block form submission with an invisible bubble — your state owns the files. |
|
|
28
|
+
| `files` | `FileEntry[]` | yes | Controlled file list. The component renders this; consumer owns the state. |
|
|
29
|
+
| `onFilesAdded` | `(files: File[]) => void` | yes | Called when the user drops or picks new files AND those files pass validation. The component does NOT mutate state — consumer is responsible for adding the returned `File[]` to `files` (typically wrapped in `FileEntry` shape with new IDs). |
|
|
30
|
+
| `onFileRemove` | `(entry: FileEntry) => void` | yes | Called when the user clicks the X on a file row. Consumer removes from `files`. |
|
|
31
|
+
| `onFileReject` | `((file: File, reason: FileRejectReason, message?: string) => void)` | no | Called when a file fails validation. `reason` is one of `FileRejectReason`; `message` is the custom message from `validator` (only present when reason='custom'). Use for toasts or inline error rendering. |
|
|
32
|
+
| `multiple` | `boolean` | no | Allow multi-file selection. Default `false`. In single mode (default), `files` should have at most 1 entry; the dropzone hides once a file is present and re-appears after removal. |
|
|
33
|
+
| `accept` | `string` | no | Accepted file types — MIME types and/or extensions, comma-separated. Forwarded to the native `<input accept>` attribute AND used for the built-in `invalid-type` validation. Default: no filter. Examples: - `accept="image/*"` — any image - `accept=".csv,application/vnd.ms-excel"` — CSV or Excel - `accept="application/pdf,image/png,image/jpeg"` — explicit MIME list |
|
|
34
|
+
| `maxSize` | `number` | no | Per-file size cap in bytes. Default: no limit. Files exceeding this fire `onFileReject` with reason `'too-large'`. |
|
|
35
|
+
| `maxFiles` | `number` | no | Total file count cap. Only meaningful when `multiple=true`. Counts existing entries in `files` PLUS new files being added. Default: no limit. Excess files fire `onFileReject` with reason `'too-many'`. |
|
|
36
|
+
| `validator` | `((file: File) => string \| null)` | no | Custom validation hook. Return `null` for valid; return a string error message for invalid (fires `onFileReject` with reason='custom', message= the returned string). Runs LAST in the validation chain (after type, size, count, duplicate checks). |
|
|
37
|
+
| `disabled` | `boolean` | no | Disable the entire component. Dropzone shows muted, drag/click handlers are no-ops, remove buttons disabled. Default `false`. |
|
|
38
|
+
| `dropzoneLabel` | `ReactNode` | no | Override the dropzone's main label. Default: the i18n value at `fileUpload.dragHint`. Pass a ReactNode for richer content (e.g. with a `<Code>` for accepted extensions). **A11y note:** when `dropzoneLabel` is a plain string, the component uses it as `aria-label` on the dropzone. When it's a ReactNode, the component falls back to the i18n value at `fileUpload.upload` and the rich content is visible-only. To give screen readers the equivalent text, pass `aria-label` via the spread (e.g. `aria-label="Upload CSV or Excel files"`). An EMPTY string counts as unset, not as an explicit blank — it drives both the visible label and the dropzone's `aria-label`, so an empty one left a `role="button"` with no accessible name. |
|
|
39
|
+
| `dropzoneIcon` | `ReactNode` | no | Override the dropzone icon. Default: lucide `CloudUpload` at 32px. Pass an SVG ReactNode or another icon component instance. |
|
|
40
|
+
| `dropzoneHint` | `ReactNode` | no | Optional secondary line under the main label, typically describing accepted formats / size limits. Renders muted at `--font-size-sm`. Default: undefined (no secondary text). |
|
|
41
|
+
| …native | | | plus native `<div>` attributes |
|
|
42
|
+
|
|
43
|
+
<!-- props:end -->
|
|
44
|
+
|
|
45
|
+
- **Pure UI shell.** Consumer owns the `files: FileEntry[]` state and the network code. The component handles drag/drop + click + validation + per-row rendering ONLY.
|
|
46
|
+
- **Inside `<Field>` / `<SettingRow>`:** the wired `id` goes on the hidden `<input type="file">` (the only labelable element), so clicking the row's label opens the picker; `invalid` marks the dropzone `aria-invalid`; `required` only drives the row's visible marker (a hidden native `required` would block submit invisibly — your state owns the files).
|
|
47
|
+
- `FileEntry`: `{ id: string, file: File, status: 'pending' | 'uploading' | 'done' | 'error', progress?: number, error?: string }`. Consumer assigns `id` (typically `crypto.randomUUID()`); File has no stable identity in JS.
|
|
48
|
+
- **Status drives the row:** `uploading` renders `<Progress size="sm" value={progress}>`; `error` renders the error string in danger color and tints the row border; `done` renders a green check icon; `pending` is neutral. The remove button (X) is always visible regardless of status.
|
|
49
|
+
- **Validation pipeline** (per file, in order): type (`accept`) → size (`maxSize`) → count (`maxFiles`, multi mode only) → duplicate (name + size) → custom (`validator`). First failure fires `onFileReject(file, reason, message?)`; passing files batch into ONE `onFilesAdded(File[])` call.
|
|
50
|
+
- **Single mode (default):** implicit count cap of 1. Dropzone HIDES once `files.length === 1` and re-appears after the user removes the file. Multi-file drops in single mode → first valid file accepted, rest rejected as `'too-many'`.
|
|
51
|
+
- **Defensive guards.** `NaN`, `Infinity`, and `max <= 0` on the underlying `<Progress>` fall back to indeterminate — covers the file-upload race condition where `bytes_uploaded / total_bytes` produces NaN before the total is known.
|
|
52
|
+
- **Drag and click both open the same hidden `<input type="file">`.** Drag is mouse-only; keyboard users use the dropzone's `role="button"` + Enter/Space to open the picker.
|
|
53
|
+
|
|
54
|
+
#### `FileRejectReason`
|
|
55
|
+
|
|
56
|
+
- `'invalid-type'` — didn't match `accept`
|
|
57
|
+
- `'too-large'` — exceeded `maxSize`
|
|
58
|
+
- `'too-many'` — would exceed `maxFiles` (or the implicit cap of 1 in single mode)
|
|
59
|
+
- `'duplicate'` — same name + size as an existing entry
|
|
60
|
+
- `'custom'` — `validator` returned a non-null string (message passed as 3rd arg to `onFileReject`)
|
|
61
|
+
|
|
62
|
+
#### Hard rule
|
|
63
|
+
|
|
64
|
+
- ❌ Hand-rolling a `<input type="file">` + dashed-border div per page. Use `<FileUpload>`.
|
|
65
|
+
- ❌ Wiring upload progress with a custom bar — use the `progress` field on `FileEntry`, which renders via `<Progress>` automatically.
|
|
66
|
+
- ❌ Storing the file list in the component (it has no internal state). Always pass `files` + the two callbacks.
|
|
67
|
+
- ❌ Setting `multiple=true` and showing only one file slot via custom CSS. The component decides dropzone visibility from `multiple` + `files.length`; don't fight it.
|
|
68
|
+
- ❌ Calling `onFilesAdded` from inside `onFileReject` (or vice versa) in an attempt to "auto-retry." Reject is terminal for that file; the user has to re-drop.
|
|
69
|
+
|
|
70
|
+
#### Examples and limits
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
// Single-file picker (controlled)
|
|
74
|
+
function ProfileImage() {
|
|
75
|
+
const [files, setFiles] = useState<FileEntry[]>([]);
|
|
76
|
+
return (
|
|
77
|
+
<FileUpload
|
|
78
|
+
files={files}
|
|
79
|
+
accept="image/*"
|
|
80
|
+
maxSize={2 * 1024 * 1024}
|
|
81
|
+
onFilesAdded={(added) =>
|
|
82
|
+
setFiles(added.map((f) => ({ id: crypto.randomUUID(), file: f, status: 'pending' })))
|
|
83
|
+
}
|
|
84
|
+
onFileRemove={(entry) => setFiles((prev) => prev.filter((e) => e.id !== entry.id))}
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Status walkthrough — the consumer moves each entry pending → uploading → done / error
|
|
90
|
+
const upload = async (entry: FileEntry) => {
|
|
91
|
+
setFiles((prev) =>
|
|
92
|
+
prev.map((e) => (e.id === entry.id ? { ...e, status: 'uploading', progress: 0 } : e)),
|
|
93
|
+
);
|
|
94
|
+
try {
|
|
95
|
+
await uploadToS3(entry.file, (pct) =>
|
|
96
|
+
setFiles((prev) => prev.map((e) => (e.id === entry.id ? { ...e, progress: pct } : e))),
|
|
97
|
+
);
|
|
98
|
+
setFiles((prev) =>
|
|
99
|
+
prev.map((e) => (e.id === entry.id ? { ...e, status: 'done', progress: 100 } : e)),
|
|
100
|
+
);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
setFiles((prev) =>
|
|
103
|
+
prev.map((e) => (e.id === entry.id ? { ...e, status: 'error', error: String(err) } : e)),
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
- When NOT to use: a hidden file-picker button with no dropzone (use `<Button>` + a raw `<input type="file" hidden>`); paste-from-clipboard image upload; directory upload (`webkitdirectory` is not exposed); image thumbnails of the picked files (generic file icons only); component-owned upload state.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# `<FilterChip>` — dismissible "active filter" pill
|
|
2
|
+
|
|
3
|
+
```tsx
|
|
4
|
+
<FilterChip onDismiss={() => removeFilter('event')}>
|
|
5
|
+
<FilterChip.Label>Event</FilterChip.Label>
|
|
6
|
+
<FilterChip.Value tone="info">auth.* (3)</FilterChip.Value>
|
|
7
|
+
</FilterChip>
|
|
8
|
+
|
|
9
|
+
// Value-only chip (no label slot):
|
|
10
|
+
<FilterChip onDismiss={() => removeFilter('tenant')}>
|
|
11
|
+
<FilterChip.Value>beta</FilterChip.Value>
|
|
12
|
+
</FilterChip>
|
|
13
|
+
|
|
14
|
+
// Read-only chip (no dismiss button):
|
|
15
|
+
<FilterChip>
|
|
16
|
+
<FilterChip.Label>Status</FilterChip.Label>
|
|
17
|
+
<FilterChip.Value>Active</FilterChip.Value>
|
|
18
|
+
</FilterChip>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
<!-- props:start -->
|
|
22
|
+
|
|
23
|
+
## Props
|
|
24
|
+
|
|
25
|
+
### `FilterChipProps`
|
|
26
|
+
|
|
27
|
+
<!-- prettier-ignore -->
|
|
28
|
+
| Prop | Type | Required | Description |
|
|
29
|
+
|---|---|---|---|
|
|
30
|
+
| `onDismiss` | `(() => void)` | no | Dismiss callback. When provided, the chip renders a trailing `×` button wired to this handler; the chip itself does NOT animate or unmount — the consumer's state update must remove the chip. Omit the prop to render a read-only chip with no dismiss button. |
|
|
31
|
+
| `dismissLabel` | `string` | no | Override the dismiss button's `aria-label`. Defaults to the i18n value at `filterChip.dismiss` (`'Remove filter'` in English) when omitted OR empty — an empty string is not an explicit name, so it takes the default too. Pass a contextual label (e.g., `'Remove Event: auth.* filter'`) when the chip's filter category isn't obvious from the surrounding screen-reader context. |
|
|
32
|
+
| `onActivate` | `(() => void)` | no | Makes the chip BODY interactive: when provided, the Label/Value content is wrapped in a `<button>` that fires `onActivate` on click and Enter/Space (native button keyboard). Use for *editable* filters — e.g. a date-range chip whose body re-opens its range-picker. Wire it to a controlled `<Popover open onOpenChange>` to open an editor popover. The dismiss ✕ stays a separate button whose click stops propagation, so removing the filter never fires `onActivate` (and never bubbles to an ancestor click handler). Omit for a read-only chip (current behavior). Controlled-only: FilterChip does NOT consume `Popover.Trigger`'s injected ref/ARIA, so wrapping it in `<Popover.Trigger>` won't auto-wire it — drive the popover's `open` yourself from this callback (see the example above). |
|
|
33
|
+
| `expanded` | `boolean` | no | Open state of the disclosure the body opens (e.g. the editor popover), surfaced as `aria-expanded` on the body button (which also carries `aria-haspopup="dialog"`). Only meaningful with `onActivate`. Omit if the body doesn't toggle a disclosure. |
|
|
34
|
+
| `children` | `ReactNode` | yes | One or two `FilterChip.Label` / `FilterChip.Value` subcomponents. Use both for `Label: Value` chips; pass just a Value for chips where the category is implicit (e.g., a tenant slug). |
|
|
35
|
+
| …native | | | plus native `<div>` attributes |
|
|
36
|
+
|
|
37
|
+
### `FilterChipLabelProps`
|
|
38
|
+
|
|
39
|
+
<!-- prettier-ignore -->
|
|
40
|
+
| Prop | Type | Required | Description |
|
|
41
|
+
|---|---|---|---|
|
|
42
|
+
| `children` | `ReactNode` | yes | The category text — typically a noun describing the filter dimension (`Event`, `Tenant`, `Stage`, `Owner`). Rendered muted so the Value is the visual anchor. |
|
|
43
|
+
| …native | | | plus native `<span>` attributes |
|
|
44
|
+
|
|
45
|
+
### `FilterChipValueProps`
|
|
46
|
+
|
|
47
|
+
<!-- prettier-ignore -->
|
|
48
|
+
| Prop | Type | Required | Description |
|
|
49
|
+
|---|---|---|---|
|
|
50
|
+
| `color` | `PaletteColor` | no | Optional full `PaletteColor` (one of the 30 named categorical colors) for the leading dot. Use when the 6 semantic tones aren't enough to distinguish filter categories (e.g., per-tenant or per-tag color coding that matches an `OptionsPicker` group). Takes precedence over `tone` when both are set. Renders a bare `<Dot>` in that color. |
|
|
51
|
+
| `tone` | `'neutral' \| 'info' \| 'success' \| 'warning' \| 'danger' \| 'purple'` | no | Optional dot tone. When set, prefixes a 6px colored circle before the value text — use to distinguish filter categories that share a screen (e.g., event filters get a tone-matched dot, tenant filters get no dot). Reuses Badge's tone palette: `neutral`, `info`, `success`, `warning`, `danger`, `purple`. Omit for plain text values. For a richer categorical color, use `color` instead. |
|
|
52
|
+
| `children` | `ReactNode` | yes | The value text — what the filter is actually filtering by (`auth.*`, `beta`, `Won`). Pair with an optional `tone` / `color` dot to categorize. |
|
|
53
|
+
| …native | | | plus native `<span>` attributes |
|
|
54
|
+
|
|
55
|
+
<!-- props:end -->
|
|
56
|
+
|
|
57
|
+
- Compound API: `<FilterChip>` root + optional `<FilterChip.Label>` and `<FilterChip.Value>` children.
|
|
58
|
+
- **Use for active-filter pills, not tags / status badges.** If it's a status or category, use `<Badge>`. If it's a clickable filter trigger that navigates or runs an action, use `<Button>` or `<OptionsPicker.Trigger>`.
|
|
59
|
+
- Root carries `role="group"` so screen readers announce the chip as one unit. A read-only chip's only interactive target is the dismiss ✕; an _editable_ chip adds a body button via `onActivate`.
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
// Editable chip — body re-opens an editor popover; ✕ removes the filter
|
|
63
|
+
const [open, setOpen] = useState(false);
|
|
64
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
65
|
+
<Popover.Trigger>
|
|
66
|
+
<FilterChip onActivate={() => setOpen((o) => !o)} expanded={open} onDismiss={remove}>
|
|
67
|
+
<FilterChip.Label>Range</FilterChip.Label>
|
|
68
|
+
<FilterChip.Value>Jun 1 – Jul 31</FilterChip.Value>
|
|
69
|
+
</FilterChip>
|
|
70
|
+
</Popover.Trigger>
|
|
71
|
+
<Popover.Content maxWidth={520}>{/* range picker */}</Popover.Content>
|
|
72
|
+
</Popover>;
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
// Mixed content — Value accepts any ReactNode
|
|
77
|
+
<FilterChip.Value tone="success">
|
|
78
|
+
<Avatar size="2xs" name="Sarah" /> Sarah
|
|
79
|
+
</FilterChip.Value>
|
|
80
|
+
|
|
81
|
+
// Palette-color dot (matches an OptionsPicker group color)
|
|
82
|
+
<FilterChip.Value color="violet">Design</FilterChip.Value>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
- `<FilterChip.Label>` is the muted category lead-in (`Event`, `Tenant`, `Stage`); omit it for value-only chips where the category is implicit.
|
|
86
|
+
- Not for tags on an entity (deal labels) — use `<Badge>`; tags don't carry a `Label: Value` shape.
|
|
87
|
+
- ❌ Interactive children inside `<FilterChip.Label>` / `<FilterChip.Value>` — the dismiss button (and the `onActivate` body button) are the only interactive targets; nesting a Button breaks the `role="group"` composition and confuses screen readers.
|
|
88
|
+
- ❌ Expecting the chip to animate out on `onDismiss` — the consumer's state update unmounts it; wrap it in your own transition if you need one.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# `<FlowCanvas>` — pan/zoom canvas for directed node-edge diagrams
|
|
2
|
+
|
|
3
|
+
Pan/zoom canvas for directed node-edge diagrams (workflow builders). Events-only: you own
|
|
4
|
+
`nodes`/`edges`; the canvas emits intents (`onNodeCreate`, `onNodeMove`, `onNodeOpen`,
|
|
5
|
+
`onNodeDelete`, `onEdgeCreate`, `onEdgeOpen`, `onEdgeDelete`, `onEdgeReconnect`) and never
|
|
6
|
+
mutates data. Nodes
|
|
7
|
+
without `position` auto-layout left → right and stay draggable for the session. Nodes with an
|
|
8
|
+
explicit `position` are pinned and don't affect the auto-layout of the others (nodes without a
|
|
9
|
+
`position` are auto-laid-out). `arrangeNodes(nodes, edges)` (exported) re-flows the whole graph
|
|
10
|
+
and returns the nodes with fresh positions — wire it to your own "Re-arrange" button. Selection is
|
|
11
|
+
single (`selection`/`defaultSelection`/`onSelectionChange`); `readOnly` disables editing but
|
|
12
|
+
keeps select/open. Validation of new connections via `isValidConnection` (default: no
|
|
13
|
+
self-loops, no duplicate pairs). **Rewire an existing edge:** select it, then drag either
|
|
14
|
+
endpoint handle onto another node (or press `R` / `Shift+R`) → `onEdgeReconnect(id, from, to)`
|
|
15
|
+
(reuses `isValidConnection`; reverts on empty/invalid drop). `allowConnections={false}` disables
|
|
16
|
+
all create + rewire while keeping node drag/move/delete/select. `confineNodesToView` clamps a
|
|
17
|
+
dragged node to the visible canvas area. `renderNodeActions(id)` / `renderEdgeActions(id)` return
|
|
18
|
+
a `ReactNode` floated as a toolbar on the selected node/edge, tracking it through pan/zoom (e.g.
|
|
19
|
+
a delete icon-button, or one behind a `ConfirmationPopover`). The canvas fills its parent — give
|
|
20
|
+
the wrapper a height. Full keyboard: arrows rove nodes, E cycles a node's edges, C connect mode,
|
|
21
|
+
R / Shift+R rewire the selected edge's target / source, Shift+arrows nudge, +/−/0 zoom/fit,
|
|
22
|
+
Ctrl+arrows pan, Delete deletes, Enter opens. Inside a `Modal`/`Drawer`, an armed connect gesture
|
|
23
|
+
(and a `Sortable`/`DataTable` reorder drag — keyboard or pointer) is an Escape-consuming **mode**:
|
|
24
|
+
the first Escape cancels the mode and the host survives that press; a mere selection does NOT hold
|
|
25
|
+
the host (Escape closes the host, deselect is incidental). Pass `controls` (a
|
|
26
|
+
`ReactNode`) to render your own buttons top-left; a built-in Maximize toggle (top-right / `F`
|
|
27
|
+
key, Escape to restore) expands the canvas in place to fill the viewport. `maximizeControl={false}`
|
|
28
|
+
hides the toggle if you drive `maximized` yourself. The canvas fits-to-content once on mount; to
|
|
29
|
+
re-center afterward — on a maximize toggle, a viewport resize, or a programmatic re-arrange that
|
|
30
|
+
moves every node — change the **`refitKey`** prop (any `string | number | boolean`): a new value
|
|
31
|
+
re-runs the fit **without a remount**, preserving viewport/selection/focus. Don't force a re-fit
|
|
32
|
+
with a changing React `key` — that remounts and drops focus to `<body>`. Bind `refitKey={maximized}`
|
|
33
|
+
to re-fit on maximize enter/exit, or bump a counter after a re-arrange.
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
const [nodes, setNodes] = useState<FlowCanvasNode[]>([
|
|
37
|
+
{ id: 'open', label: 'Open', color: '#0052CC', adornment: <Badge tone="info">Initial</Badge> },
|
|
38
|
+
{ id: 'done', label: 'Done', color: '#1F845A' },
|
|
39
|
+
]);
|
|
40
|
+
const [edges, setEdges] = useState<FlowCanvasEdge[]>([
|
|
41
|
+
{ id: 't1', from: 'open', to: 'done', label: <Badge tone="purple">Guard</Badge> },
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
<div style={{ height: 480 }}>
|
|
45
|
+
<FlowCanvas
|
|
46
|
+
nodes={nodes}
|
|
47
|
+
edges={edges}
|
|
48
|
+
controls={
|
|
49
|
+
<Button size="sm" onClick={addNode}>
|
|
50
|
+
Add node
|
|
51
|
+
</Button>
|
|
52
|
+
}
|
|
53
|
+
onEdgeCreate={(from, to) => createTransition(from, to)}
|
|
54
|
+
onNodeOpen={(id) => openStateModal(id)}
|
|
55
|
+
onNodeDelete={(id) => confirmDeleteState(id)}
|
|
56
|
+
/>
|
|
57
|
+
</div>;
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
<!-- props:start -->
|
|
61
|
+
|
|
62
|
+
## Props
|
|
63
|
+
|
|
64
|
+
<!-- prettier-ignore -->
|
|
65
|
+
| Prop | Type | Required | Description |
|
|
66
|
+
|---|---|---|---|
|
|
67
|
+
| `nodes` | `FlowCanvasNode[]` | yes | Nodes to render. The canvas never mutates this array. |
|
|
68
|
+
| `edges` | `FlowCanvasEdge[]` | yes | Directed edges between nodes. Edges referencing unknown ids are skipped (dev warning). |
|
|
69
|
+
| `onNodeCreate` | `((position: FlowCanvasPoint) => void)` | no | Called when the user requests a node at a canvas point (double-click empty canvas). |
|
|
70
|
+
| `onNodeMove` | `((id: string, position: FlowCanvasPoint) => void)` | no | Called when a node's position is committed (drag end, keyboard nudge). |
|
|
71
|
+
| `onNodeOpen` | `((id: string) => void)` | no | Called when a node is opened (Enter/Space or double-click). |
|
|
72
|
+
| `onNodeDelete` | `((id: string) => void)` | no | Called when deletion of the selected node is requested (Delete/Backspace). |
|
|
73
|
+
| `onEdgeCreate` | `((from: string, to: string) => void)` | no | Called when the user draws or confirms a connection between two nodes. |
|
|
74
|
+
| `onEdgeReconnect` | `((id: string, from: string, to: string) => void)` | no | Called when the user drags an existing edge's endpoint onto a different node, or confirms a keyboard rewire (`R` / `Shift+R`). `id` is the edge; `from`/`to` are its NEW endpoints. The canvas never mutates the edge — apply this to your state. Not fired on revert (empty-canvas drop, invalid target, or no change). Disabled by `readOnly` and `allowConnections={false}`. |
|
|
75
|
+
| `onEdgeOpen` | `((id: string) => void)` | no | Called when an edge is opened (Enter/Space or double-click). |
|
|
76
|
+
| `onEdgeDelete` | `((id: string) => void)` | no | Called when deletion of the selected edge is requested (Delete/Backspace). |
|
|
77
|
+
| `isValidConnection` | `((from: string, to: string) => boolean)` | no | Live validation while drawing a connection. Invalid targets can't be dropped on (pointer) and are skipped (keyboard). Default: `rejects self-loops and duplicate (from, to) pairs`. |
|
|
78
|
+
| `selection` | `FlowCanvasSelection` | no | Controlled selection. Use with `onSelectionChange`. A selection whose id is not (or no longer) in `nodes`/`edges` acts as no selection — e.g. after applying a delete intent — until the id reappears in the graph. |
|
|
79
|
+
| `defaultSelection` | `FlowCanvasSelection` | no | Initial selection when uncontrolled. Stale ids act as no selection. Default: `null`. |
|
|
80
|
+
| `onSelectionChange` | `((selection: FlowCanvasSelection) => void)` | no | Fires whenever the selection changes (click, focus, Escape). |
|
|
81
|
+
| `readOnly` | `boolean` | no | Render-only mode: create/move/connect/delete are disabled; selection and open still work. Default: `false`. |
|
|
82
|
+
| `allowConnections` | `boolean` | no | When false, disables creating and rewiring connections — the node connect handle is hidden, pointer/keyboard connect (`C`, handle drag) and edge rewiring (`R`, endpoint drag) are inert. Node drag/move/delete/selection still work. `readOnly` overrides this (it disables everything). Default: `true`. |
|
|
83
|
+
| `confineNodesToView` | `boolean` | no | When true, a dragged node is clamped so its whole card stays within the currently-visible canvas area (accounting for pan/zoom). Applies to pointer drag, the committed `onNodeMove`, and Shift+Arrow nudges. Default: `false`. |
|
|
84
|
+
| `renderNodeActions` | `((id: string) => ReactNode)` | no | Render a floating toolbar anchored to the top-right corner of the selected NODE. Called with the node id; return `null` to show nothing for that node. The toolbar is a screen-positioned overlay (a sibling of the transformed stage, so it is not scaled) that follows pan/zoom. Pressing it never starts a pan or clears the selection. Only rendered for the current single selection. Default: `none`. |
|
|
85
|
+
| `renderEdgeActions` | `((id: string) => ReactNode)` | no | Render a floating toolbar anchored near the midpoint of the selected EDGE. Called with the edge id; return `null` to show nothing for that edge. Same screen-positioned, pan/zoom-following overlay as `renderNodeActions`; safe to host a `ConfirmationPopover` here. Default: `none`. |
|
|
86
|
+
| `controls` | `ReactNode` | no | Consumer-rendered controls shown in a top-left toolbar overlay. Put design- system `<Button>`s here (e.g. an "Add node" button wired to your own state). Stays pinned when the canvas is maximized. Default: `none`. |
|
|
87
|
+
| `maximizeControl` | `boolean` | no | Show the built-in maximize / restore toggle (top-right). Set false to drive maximize entirely via the `maximized` prop. Default: `true`. |
|
|
88
|
+
| `maximized` | `boolean` | no | Controlled maximize state. Use with `onMaximizedChange`. |
|
|
89
|
+
| `defaultMaximized` | `boolean` | no | Initial maximize state when uncontrolled. Default: `false`. |
|
|
90
|
+
| `onMaximizedChange` | `((maximized: boolean) => void)` | no | Fires whenever maximize is toggled (button, `F` key, or Escape). |
|
|
91
|
+
| `refitKey` | `string \| number \| boolean` | no | Re-fit signal. Whenever this value CHANGES, the canvas re-runs its fit-to-content — the same fit as the Fit control / `0` key — re-centering and re-zooming to frame all content. It does this WITHOUT remounting, so the viewport, selection, keyboard focus, and the built-in maximize focus management are all preserved (unlike forcing a re-fit with a changing React `key`, which remounts and drops focus to `<body>`). Bind it to whatever should trigger a re-center: `refitKey={maximized}` to re-fit on maximize enter/exit (the container resizes), or bump a counter after a programmatic re-arrange that moves every node. The FIRST value never fits on its own — the mount fit handles the first frame — so any initial value is safe. Default: `none`. |
|
|
92
|
+
| …native | | | plus native `<div>` attributes |
|
|
93
|
+
|
|
94
|
+
<!-- props:end -->
|
|
95
|
+
|
|
96
|
+
When NOT to use: >100-node graphs (no virtualization); list/column reordering (use
|
|
97
|
+
Kanban/Sortable); undirected/free-form drawing; as the only editing surface for complex
|
|
98
|
+
attributes (anchor your own modals via the open callbacks — keep a form-based fallback).
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
// Read-only diagram (record page): no editing intents, still zoomable
|
|
102
|
+
<FlowCanvas nodes={nodes} edges={edges} readOnly aria-label="Deal workflow" />
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
- Not for undirected / free-form drawing (mind maps, whiteboards): edges are directed with arrowheads and the interaction model assumes a digraph.
|
|
106
|
+
- The inline surface is selection + spatial arrangement only; editors belong to you (anchor modals / popovers via the open callbacks).
|
|
107
|
+
- ❌ Hiding primary, always-needed actions solely behind Maximize or the `controls` slot — that is canvas chrome, not a substitute for the page's own toolbar.
|
|
108
|
+
- ❌ Relying on maximize inside an ancestor that creates a containing block via `transform` / `filter` / `perspective` / `will-change`: in-page maximize uses `position: fixed`, so it anchors to that ancestor instead of the viewport and won't fill the screen.
|