@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,81 @@
|
|
|
1
|
+
// FileUpload.tokens.scss — component-scoped tokens for <FileUpload>. Covers the drop-zone, drag-over state, and per-row item chrome (border, status icon colors).
|
|
2
|
+
:root {
|
|
3
|
+
// Root layout
|
|
4
|
+
--file-upload-gap: var(--space-3);
|
|
5
|
+
|
|
6
|
+
// Dropzone
|
|
7
|
+
--file-upload-dropzone-gap: var(--space-2);
|
|
8
|
+
--file-upload-dropzone-padding: var(--space-4);
|
|
9
|
+
--file-upload-dropzone-border-color: var(--color-border);
|
|
10
|
+
--file-upload-dropzone-radius: var(--radius-md);
|
|
11
|
+
--file-upload-dropzone-bg: var(--color-bg);
|
|
12
|
+
--file-upload-dropzone-fg: var(--color-fg-muted);
|
|
13
|
+
--file-upload-dropzone-border-color-hover: var(--color-accent);
|
|
14
|
+
--file-upload-dropzone-fg-hover: var(--color-fg);
|
|
15
|
+
|
|
16
|
+
// Drag-over (active drop target)
|
|
17
|
+
--file-upload-dragover-border-color: var(--color-accent);
|
|
18
|
+
--file-upload-dragover-bg: var(--color-accent-bg-subtle);
|
|
19
|
+
--file-upload-dragover-fg: var(--color-fg);
|
|
20
|
+
|
|
21
|
+
// Dropzone children
|
|
22
|
+
--file-upload-icon-fg: var(--color-fg-subtle);
|
|
23
|
+
--file-upload-label-font-size: var(--font-size-md);
|
|
24
|
+
--file-upload-hint-font-size: var(--font-size-sm);
|
|
25
|
+
|
|
26
|
+
// .dropzoneHint declares its own colour, so it wins over .dragOver's
|
|
27
|
+
// inherited --file-upload-dragover-fg -- the hint stays this colour through
|
|
28
|
+
// drag-over, whose --file-upload-dragover-bg is --color-accent-bg-subtle.
|
|
29
|
+
// The hint is --font-size-sm (12px), so WCAG AA small-text applies at 4.5:1;
|
|
30
|
+
// there is no large-text exemption to fall back on.
|
|
31
|
+
// #511: was --color-fg-subtle, well below AA there (3.97/3.61).
|
|
32
|
+
// --color-fg-muted was a substantial improvement that still missed AA in
|
|
33
|
+
// light, at 4.41, while dark cleared. #522 closed that at the PRIMITIVE
|
|
34
|
+
// rather than here: light --color-fg-muted moved #5e6c84 -> #5b6980, an
|
|
35
|
+
// imperceptible ΔE 0.0109 step that clears every light surface. So this
|
|
36
|
+
// token needed no change for #522 -- the annotations below moved, the
|
|
37
|
+
// declaration did not.
|
|
38
|
+
// --file-upload-icon-fg above is unaffected (icon, not text; separate
|
|
39
|
+
// token; not re-pointed).
|
|
40
|
+
// @contrast --color-fg-subtle on --color-accent-bg-subtle = 3.97:1 light
|
|
41
|
+
// @contrast --color-fg-subtle on --color-accent-bg-subtle = 3.61:1 dark
|
|
42
|
+
// @contrast --color-fg-muted on --color-accent-bg-subtle = 4.62:1 light
|
|
43
|
+
// @contrast --color-fg-muted on --color-accent-bg-subtle = 4.72:1 dark
|
|
44
|
+
--file-upload-hint-fg: var(--color-fg-muted);
|
|
45
|
+
|
|
46
|
+
// File list
|
|
47
|
+
--file-upload-list-gap: var(--space-2);
|
|
48
|
+
|
|
49
|
+
// Row — surface
|
|
50
|
+
--file-upload-row-gap: var(--space-3);
|
|
51
|
+
--file-upload-row-padding-y: var(--space-2);
|
|
52
|
+
--file-upload-row-padding-x: var(--space-3);
|
|
53
|
+
--file-upload-row-border-color: var(--color-border);
|
|
54
|
+
--file-upload-row-radius: var(--radius-md);
|
|
55
|
+
--file-upload-row-bg: var(--color-bg);
|
|
56
|
+
|
|
57
|
+
// Row — error variant
|
|
58
|
+
--file-upload-row-border-color-error: var(--color-danger);
|
|
59
|
+
|
|
60
|
+
// Row — status cell. The 4th grid column (`.rowStatus`) is `auto`-sized,
|
|
61
|
+
// which measures to 0 for an uploading row: <Progress> is `width: 100%`
|
|
62
|
+
// with no intrinsic size, so it has nothing to be 100% of (#588). This
|
|
63
|
+
// gives the uploading status cell a floor so the bar has somewhere to
|
|
64
|
+
// grow into. `--size-xl` is the closest existing shared size token to a
|
|
65
|
+
// sensible progress-bar minimum (80px) — no dedicated 80-120px token
|
|
66
|
+
// exists for this, so this reuses the primitive rather than inventing one.
|
|
67
|
+
--file-upload-progress-min-width: var(--size-xl);
|
|
68
|
+
|
|
69
|
+
// Row — text + icon colors
|
|
70
|
+
--file-upload-row-icon-fg: var(--color-fg-subtle);
|
|
71
|
+
--file-upload-row-name-font-size: var(--font-size-md);
|
|
72
|
+
--file-upload-row-name-fg: var(--color-fg);
|
|
73
|
+
--file-upload-row-meta-font-size: var(--font-size-sm);
|
|
74
|
+
--file-upload-row-meta-fg: var(--color-fg-subtle);
|
|
75
|
+
--file-upload-row-error-font-size: var(--font-size-sm);
|
|
76
|
+
--file-upload-row-error-fg: var(--color-danger);
|
|
77
|
+
--file-upload-row-done-fg: var(--color-success);
|
|
78
|
+
|
|
79
|
+
// Disabled state
|
|
80
|
+
--file-upload-opacity-disabled: var(--opacity-subtle);
|
|
81
|
+
}
|
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
import {
|
|
2
|
+
forwardRef,
|
|
3
|
+
useCallback,
|
|
4
|
+
useRef,
|
|
5
|
+
useEffect,
|
|
6
|
+
useState,
|
|
7
|
+
type DragEvent,
|
|
8
|
+
type HTMLAttributes,
|
|
9
|
+
type KeyboardEvent,
|
|
10
|
+
type ChangeEvent,
|
|
11
|
+
type ReactNode,
|
|
12
|
+
type MouseEvent,
|
|
13
|
+
} from 'react';
|
|
14
|
+
import { CloudUpload, Check, X } from 'lucide-react';
|
|
15
|
+
import clsx from 'clsx';
|
|
16
|
+
import { Button } from '../Button';
|
|
17
|
+
import { Progress } from '../Progress';
|
|
18
|
+
import { useTranslation } from '../../i18n/useTranslation';
|
|
19
|
+
import { formatBytes } from './formatBytes';
|
|
20
|
+
import { iconForFile } from './iconForFile';
|
|
21
|
+
import styles from './FileUpload.module.scss';
|
|
22
|
+
|
|
23
|
+
/** Lifecycle status for a file in the controlled `files` array. */
|
|
24
|
+
export type FileUploadStatus = 'pending' | 'uploading' | 'done' | 'error';
|
|
25
|
+
|
|
26
|
+
/** Reason a file was rejected by built-in or custom validation. */
|
|
27
|
+
export type FileRejectReason = 'invalid-type' | 'too-large' | 'too-many' | 'duplicate' | 'custom';
|
|
28
|
+
|
|
29
|
+
/** One entry in the controlled file list. */
|
|
30
|
+
export interface FileEntry {
|
|
31
|
+
/**
|
|
32
|
+
* Stable ID for React keys and consumer bookkeeping. Consumer assigns
|
|
33
|
+
* (typically `crypto.randomUUID()` when handling `onFilesAdded`).
|
|
34
|
+
* File objects don't have stable identity in JS, so this is required.
|
|
35
|
+
*/
|
|
36
|
+
id: string;
|
|
37
|
+
/** The underlying browser File. */
|
|
38
|
+
file: File;
|
|
39
|
+
/** Lifecycle status. Drives the per-row rendering. */
|
|
40
|
+
status: FileUploadStatus;
|
|
41
|
+
/**
|
|
42
|
+
* 0–100. Only meaningful when status='uploading' (renders via `<Progress>`).
|
|
43
|
+
* Ignored otherwise. Omitting it during upload renders the row with an
|
|
44
|
+
* indeterminate `<Progress>` (no value).
|
|
45
|
+
*/
|
|
46
|
+
progress?: number;
|
|
47
|
+
/** Error message rendered when status='error'. */
|
|
48
|
+
error?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface FileUploadProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {
|
|
52
|
+
/**
|
|
53
|
+
* Placed on the hidden native `<input type="file">`, not the root — it is
|
|
54
|
+
* the only labelable element, so a `<label for>` (Field / SettingRow wire
|
|
55
|
+
* one) opens the file picker when clicked (#568). A `role="button"` div is
|
|
56
|
+
* not labelable, so the id on the dropzone would make the label click a no-op.
|
|
57
|
+
* While the dropzone is hidden (single mode with a file) the id falls back
|
|
58
|
+
* to the root, so scroll-to-id / focus-first-invalid still find the field.
|
|
59
|
+
*/
|
|
60
|
+
id?: string;
|
|
61
|
+
/** Marks the dropzone `aria-invalid` for Field / SettingRow composition. @default false */
|
|
62
|
+
invalid?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Consumed for Field / SettingRow composition (they render the visible
|
|
65
|
+
* required marker). Not forwarded: `role="button"` does not support
|
|
66
|
+
* `aria-required`, and native `required` on the hidden input would block
|
|
67
|
+
* form submission with an invisible bubble — your state owns the files.
|
|
68
|
+
*/
|
|
69
|
+
required?: boolean;
|
|
70
|
+
/** Controlled file list. The component renders this; consumer owns the state. */
|
|
71
|
+
files: FileEntry[];
|
|
72
|
+
/**
|
|
73
|
+
* Called when the user drops or picks new files AND those files pass
|
|
74
|
+
* validation. The component does NOT mutate state — consumer is responsible
|
|
75
|
+
* for adding the returned `File[]` to `files` (typically wrapped in
|
|
76
|
+
* `FileEntry` shape with new IDs).
|
|
77
|
+
*/
|
|
78
|
+
onFilesAdded: (files: File[]) => void;
|
|
79
|
+
/** Called when the user clicks the X on a file row. Consumer removes from `files`. */
|
|
80
|
+
onFileRemove: (entry: FileEntry) => void;
|
|
81
|
+
/**
|
|
82
|
+
* Called when a file fails validation. `reason` is one of `FileRejectReason`;
|
|
83
|
+
* `message` is the custom message from `validator` (only present when
|
|
84
|
+
* reason='custom'). Use for toasts or inline error rendering.
|
|
85
|
+
*/
|
|
86
|
+
onFileReject?: (file: File, reason: FileRejectReason, message?: string) => void;
|
|
87
|
+
/**
|
|
88
|
+
* Allow multi-file selection. Default `false`. In single mode (default),
|
|
89
|
+
* `files` should have at most 1 entry; the dropzone hides once a file is
|
|
90
|
+
* present and re-appears after removal.
|
|
91
|
+
*/
|
|
92
|
+
multiple?: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Accepted file types — MIME types and/or extensions, comma-separated.
|
|
95
|
+
* Forwarded to the native `<input accept>` attribute AND used for the
|
|
96
|
+
* built-in `invalid-type` validation. Default: no filter.
|
|
97
|
+
*
|
|
98
|
+
* Examples:
|
|
99
|
+
* - `accept="image/*"` — any image
|
|
100
|
+
* - `accept=".csv,application/vnd.ms-excel"` — CSV or Excel
|
|
101
|
+
* - `accept="application/pdf,image/png,image/jpeg"` — explicit MIME list
|
|
102
|
+
*/
|
|
103
|
+
accept?: string;
|
|
104
|
+
/**
|
|
105
|
+
* Per-file size cap in bytes. Default: no limit.
|
|
106
|
+
* Files exceeding this fire `onFileReject` with reason `'too-large'`.
|
|
107
|
+
*/
|
|
108
|
+
maxSize?: number;
|
|
109
|
+
/**
|
|
110
|
+
* Total file count cap. Only meaningful when `multiple=true`. Counts existing
|
|
111
|
+
* entries in `files` PLUS new files being added. Default: no limit.
|
|
112
|
+
* Excess files fire `onFileReject` with reason `'too-many'`.
|
|
113
|
+
*/
|
|
114
|
+
maxFiles?: number;
|
|
115
|
+
/**
|
|
116
|
+
* Custom validation hook. Return `null` for valid; return a string error
|
|
117
|
+
* message for invalid (fires `onFileReject` with reason='custom', message=
|
|
118
|
+
* the returned string). Runs LAST in the validation chain (after type,
|
|
119
|
+
* size, count, duplicate checks).
|
|
120
|
+
*/
|
|
121
|
+
validator?: (file: File) => string | null;
|
|
122
|
+
/**
|
|
123
|
+
* Disable the entire component. Dropzone shows muted, drag/click handlers
|
|
124
|
+
* are no-ops, remove buttons disabled. Default `false`.
|
|
125
|
+
*/
|
|
126
|
+
disabled?: boolean;
|
|
127
|
+
/**
|
|
128
|
+
* Override the dropzone's main label. Default: the i18n value at
|
|
129
|
+
* `fileUpload.dragHint`. Pass a ReactNode for richer content (e.g. with a
|
|
130
|
+
* `<Code>` for accepted extensions).
|
|
131
|
+
*
|
|
132
|
+
* **A11y note:** when `dropzoneLabel` is a plain string, the component
|
|
133
|
+
* uses it as `aria-label` on the dropzone. When it's a ReactNode, the
|
|
134
|
+
* component falls back to the i18n value at `fileUpload.upload` and the
|
|
135
|
+
* rich content is visible-only. To give screen readers the equivalent
|
|
136
|
+
* text, pass `aria-label` via the spread (e.g. `aria-label="Upload CSV
|
|
137
|
+
* or Excel files"`).
|
|
138
|
+
*
|
|
139
|
+
* An EMPTY string counts as unset, not as an explicit blank — it drives both
|
|
140
|
+
* the visible label and the dropzone's `aria-label`, so an empty one left a
|
|
141
|
+
* `role="button"` with no accessible name.
|
|
142
|
+
*/
|
|
143
|
+
dropzoneLabel?: ReactNode;
|
|
144
|
+
/**
|
|
145
|
+
* Override the dropzone icon. Default: lucide `CloudUpload` at 32px.
|
|
146
|
+
* Pass an SVG ReactNode or another icon component instance.
|
|
147
|
+
*/
|
|
148
|
+
dropzoneIcon?: ReactNode;
|
|
149
|
+
/**
|
|
150
|
+
* Optional secondary line under the main label, typically describing
|
|
151
|
+
* accepted formats / size limits. Renders muted at `--font-size-sm`.
|
|
152
|
+
* Default: undefined (no secondary text).
|
|
153
|
+
*/
|
|
154
|
+
dropzoneHint?: ReactNode;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const STATUS_CLASS: Record<FileUploadStatus, string> = {
|
|
158
|
+
pending: styles.rowPending,
|
|
159
|
+
uploading: styles.rowUploading,
|
|
160
|
+
done: styles.rowDone,
|
|
161
|
+
error: styles.rowError,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Match a single file against an `accept` attribute string. Handles MIME
|
|
166
|
+
* patterns (`image/*`, `application/pdf`) and extension patterns (`.csv`,
|
|
167
|
+
* `.pdf`). Returns true if the file matches any entry in the comma-separated
|
|
168
|
+
* accept list, false otherwise.
|
|
169
|
+
*
|
|
170
|
+
* Private to FileUpload — encapsulated so the validation pipeline doesn't
|
|
171
|
+
* sprout an ad-hoc regex.
|
|
172
|
+
*/
|
|
173
|
+
function matchesAccept(file: File, accept: string): boolean {
|
|
174
|
+
const tokens = accept
|
|
175
|
+
.split(',')
|
|
176
|
+
.map((t) => t.trim())
|
|
177
|
+
.filter(Boolean);
|
|
178
|
+
if (tokens.length === 0) return true;
|
|
179
|
+
const mime = file.type;
|
|
180
|
+
const ext = file.name.slice(file.name.lastIndexOf('.')).toLowerCase();
|
|
181
|
+
for (const token of tokens) {
|
|
182
|
+
if (token.startsWith('.')) {
|
|
183
|
+
// Extension match (case-insensitive)
|
|
184
|
+
if (ext === token.toLowerCase()) return true;
|
|
185
|
+
} else if (token.endsWith('/*')) {
|
|
186
|
+
// Wildcard MIME (image/*, video/*, etc.)
|
|
187
|
+
const prefix = token.slice(0, -1); // "image/"
|
|
188
|
+
if (mime.startsWith(prefix)) return true;
|
|
189
|
+
} else {
|
|
190
|
+
// Exact MIME match (case-sensitive per RFC)
|
|
191
|
+
if (mime === token) return true;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Controlled, dropzone-style file picker with built-in validation and per-file status rows.
|
|
199
|
+
* @see docs/components/FileUpload.md
|
|
200
|
+
*/
|
|
201
|
+
export const FileUpload = forwardRef<HTMLDivElement, FileUploadProps>(function FileUpload(
|
|
202
|
+
{
|
|
203
|
+
files,
|
|
204
|
+
onFilesAdded,
|
|
205
|
+
onFileRemove,
|
|
206
|
+
onFileReject,
|
|
207
|
+
multiple = false,
|
|
208
|
+
accept,
|
|
209
|
+
maxSize,
|
|
210
|
+
maxFiles,
|
|
211
|
+
validator,
|
|
212
|
+
disabled = false,
|
|
213
|
+
dropzoneLabel,
|
|
214
|
+
dropzoneIcon,
|
|
215
|
+
dropzoneHint,
|
|
216
|
+
'aria-labelledby': ariaLabelledBy,
|
|
217
|
+
'aria-describedby': ariaDescribedBy,
|
|
218
|
+
id,
|
|
219
|
+
invalid = false,
|
|
220
|
+
// Consumed so it never lands on the root div (#568); see the prop's JSDoc.
|
|
221
|
+
required: _required = false,
|
|
222
|
+
className,
|
|
223
|
+
...rest
|
|
224
|
+
},
|
|
225
|
+
ref,
|
|
226
|
+
) {
|
|
227
|
+
const t = useTranslation();
|
|
228
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
229
|
+
const dragCounter = useRef(0);
|
|
230
|
+
const [isDragOver, setIsDragOver] = useState(false);
|
|
231
|
+
|
|
232
|
+
// Batch announcement text. Derived from the controlled `files` array, and
|
|
233
|
+
// deferred so region and text never mount together.
|
|
234
|
+
const total = files.length;
|
|
235
|
+
const settled = files.filter((f) => f.status === 'done' || f.status === 'error').length;
|
|
236
|
+
const failed = files.filter((f) => f.status === 'error').length;
|
|
237
|
+
const inFlight = total > 0 && settled < total;
|
|
238
|
+
const [batchStatus, setBatchStatus] = useState('');
|
|
239
|
+
// A settled batch is only announced if this component SAW it in flight.
|
|
240
|
+
// Deriving it from `files` alone announced a batch that never happened: a
|
|
241
|
+
// form opened on an entity with three existing attachments mounted already
|
|
242
|
+
// settled, and the region went from '' to "Uploaded: 3 / 3" with nothing on
|
|
243
|
+
// screen having changed. Hard rule 10 rules that out directly — gate the
|
|
244
|
+
// region on what the user can SEE change, not on the raw prop.
|
|
245
|
+
//
|
|
246
|
+
// Tracked INSIDE the effect, not during render. Mutating a ref while
|
|
247
|
+
// rendering breaks React's purity rule and a discarded concurrent render
|
|
248
|
+
// would leave the flag set for a transfer the user never saw. In the effect
|
|
249
|
+
// the ordering still works: the in-flight pass sets it, and the pass that
|
|
250
|
+
// observes the batch settled reads what that earlier pass wrote.
|
|
251
|
+
//
|
|
252
|
+
// The IDS seen in flight, not a boolean. A boolean was reset only when the
|
|
253
|
+
// list emptied, so a controlled parent swapping one already-settled `files`
|
|
254
|
+
// array for another — switching entities on a shared instance after any
|
|
255
|
+
// upload — carried it across and announced the new entity's pre-existing
|
|
256
|
+
// attachments. That is the same defect this guard exists to close, surviving
|
|
257
|
+
// on the prop-change path. Announcing requires the batch on screen to be one
|
|
258
|
+
// this component actually watched.
|
|
259
|
+
// Keyed on the FILE OBJECT, not the id. Ids are the consumer's, and a
|
|
260
|
+
// consumer numbering them per entity — `f1`, `f2` — hands the next entity
|
|
261
|
+
// the same ones, which inherited the flag and announced its pre-existing
|
|
262
|
+
// attachments. That is the fourth defect in this guard and the third of the
|
|
263
|
+
// same shape, so it now keys on something the consumer cannot collide: the
|
|
264
|
+
// `File` instance itself. A WeakSet also fails SAFE — a consumer that
|
|
265
|
+
// rebuilds `File` objects per render simply never matches, and silence is
|
|
266
|
+
// the harmless direction.
|
|
267
|
+
const seenInFlight = useRef<WeakSet<File>>(new WeakSet());
|
|
268
|
+
/** Last counts this region reported, to tell a completion from a deletion. */
|
|
269
|
+
const lastAnnounced = useRef({ total: 0, settled: 0, failed: 0 });
|
|
270
|
+
// A signature that actually changes when the BATCH changes. Ids alone did
|
|
271
|
+
// not: swapping to another entity whose file has the same id, count and
|
|
272
|
+
// status left every dependency identical, so the effect never re-ran and the
|
|
273
|
+
// region kept the previous entity's text — the guard was correct and never
|
|
274
|
+
// got to run. Name and size distinguish the files behind equal ids.
|
|
275
|
+
const signature = files
|
|
276
|
+
.map((f) => `${f.id}:${f.file.name}:${f.file.size}:${f.status}`)
|
|
277
|
+
.join('\u0000');
|
|
278
|
+
useEffect(() => {
|
|
279
|
+
if (total === 0) {
|
|
280
|
+
seenInFlight.current = new WeakSet();
|
|
281
|
+
setBatchStatus('');
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
// Only the files actually IN FLIGHT, not the whole batch. Recording all of
|
|
285
|
+
// them meant removing the last uploading row left a settled sibling still
|
|
286
|
+
// "seen", so a deletion announced "Uploaded: 1 / 1" for a transfer that
|
|
287
|
+
// never finished. Now the removal takes the watched id with it and the
|
|
288
|
+
// region stays silent.
|
|
289
|
+
if (inFlight)
|
|
290
|
+
for (const f of files)
|
|
291
|
+
if (f.status !== 'done' && f.status !== 'error') seenInFlight.current.add(f.file);
|
|
292
|
+
const watched = files.some((f) => seenInFlight.current.has(f.file));
|
|
293
|
+
// A SHRINKING batch is a deletion, not a completion. Removing the last
|
|
294
|
+
// in-flight row was fixed by tracking only in-flight files; removing a
|
|
295
|
+
// SETTLED sibling survived that, because a watched file remained and the
|
|
296
|
+
// counts simply recomputed — so deleting one of two finished uploads
|
|
297
|
+
// announced "Uploaded: 1 / 1." The premise was right and applied one row
|
|
298
|
+
// too narrowly: nothing settled, the array just got shorter.
|
|
299
|
+
const shrank = total < lastAnnounced.current.total;
|
|
300
|
+
const progressed =
|
|
301
|
+
settled > lastAnnounced.current.settled || failed > lastAnnounced.current.failed;
|
|
302
|
+
lastAnnounced.current = { total, settled, failed };
|
|
303
|
+
if (shrank && !progressed) {
|
|
304
|
+
// Cleared, not left alone: a deletion has nothing to report, and keeping
|
|
305
|
+
// the previous text would leave "Uploading: 1 / 2" describing a batch
|
|
306
|
+
// that no longer exists. Emptying a live region announces nothing.
|
|
307
|
+
setBatchStatus('');
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
setBatchStatus(
|
|
311
|
+
inFlight
|
|
312
|
+
? t('fileUpload.batchUploading', { done: settled, total })
|
|
313
|
+
: watched
|
|
314
|
+
? t('fileUpload.batchSettled', { done: total - failed, total, failed })
|
|
315
|
+
: '',
|
|
316
|
+
);
|
|
317
|
+
// `signature` rather than `files`: a controlled parent commonly passes a
|
|
318
|
+
// new array identity for the same batch on every render.
|
|
319
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
320
|
+
}, [inFlight, settled, total, failed, signature, t]);
|
|
321
|
+
|
|
322
|
+
const showDropzone = multiple || files.length === 0;
|
|
323
|
+
// In single mode the implicit cap is 1; multi mode uses maxFiles (Infinity if not set).
|
|
324
|
+
const effectiveMaxFiles = multiple ? (maxFiles ?? Infinity) : 1;
|
|
325
|
+
|
|
326
|
+
const processFiles = useCallback(
|
|
327
|
+
(incoming: File[]) => {
|
|
328
|
+
if (disabled) return;
|
|
329
|
+
const accepted: File[] = [];
|
|
330
|
+
let acceptedSoFar = 0;
|
|
331
|
+
for (const file of incoming) {
|
|
332
|
+
// 1. Type check
|
|
333
|
+
if (accept && !matchesAccept(file, accept)) {
|
|
334
|
+
onFileReject?.(file, 'invalid-type');
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
// 2. Size check
|
|
338
|
+
if (typeof maxSize === 'number' && file.size > maxSize) {
|
|
339
|
+
onFileReject?.(file, 'too-large');
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
// 3. Count check — counts existing entries + accepted-so-far in this batch.
|
|
343
|
+
if (files.length + acceptedSoFar >= effectiveMaxFiles) {
|
|
344
|
+
onFileReject?.(file, 'too-many');
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
// 4. Duplicate check — same name+size as an existing entry.
|
|
348
|
+
const isDuplicate = files.some(
|
|
349
|
+
(e) => e.file.name === file.name && e.file.size === file.size,
|
|
350
|
+
);
|
|
351
|
+
if (isDuplicate) {
|
|
352
|
+
onFileReject?.(file, 'duplicate');
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
// 5. Custom validator runs LAST.
|
|
356
|
+
if (validator) {
|
|
357
|
+
const message = validator(file);
|
|
358
|
+
if (message !== null) {
|
|
359
|
+
onFileReject?.(file, 'custom', message);
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
accepted.push(file);
|
|
364
|
+
acceptedSoFar++;
|
|
365
|
+
}
|
|
366
|
+
if (accepted.length > 0) {
|
|
367
|
+
onFilesAdded(accepted);
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
[accept, disabled, effectiveMaxFiles, files, maxSize, onFileReject, onFilesAdded, validator],
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
const handleDragEnter = useCallback(
|
|
374
|
+
(e: DragEvent<HTMLDivElement>) => {
|
|
375
|
+
if (disabled) return;
|
|
376
|
+
e.preventDefault();
|
|
377
|
+
dragCounter.current++;
|
|
378
|
+
if (dragCounter.current === 1) setIsDragOver(true);
|
|
379
|
+
},
|
|
380
|
+
[disabled],
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
const handleDragLeave = useCallback(
|
|
384
|
+
(e: DragEvent<HTMLDivElement>) => {
|
|
385
|
+
if (disabled) return;
|
|
386
|
+
e.preventDefault();
|
|
387
|
+
dragCounter.current = Math.max(0, dragCounter.current - 1);
|
|
388
|
+
if (dragCounter.current === 0) setIsDragOver(false);
|
|
389
|
+
},
|
|
390
|
+
[disabled],
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
const handleDragOver = useCallback(
|
|
394
|
+
(e: DragEvent<HTMLDivElement>) => {
|
|
395
|
+
// preventDefault on dragover signals "this is a valid drop target" — without
|
|
396
|
+
// it the drop event never fires.
|
|
397
|
+
if (disabled) return;
|
|
398
|
+
e.preventDefault();
|
|
399
|
+
},
|
|
400
|
+
[disabled],
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
const handleDrop = useCallback(
|
|
404
|
+
(e: DragEvent<HTMLDivElement>) => {
|
|
405
|
+
if (disabled) return;
|
|
406
|
+
e.preventDefault();
|
|
407
|
+
dragCounter.current = 0;
|
|
408
|
+
setIsDragOver(false);
|
|
409
|
+
const dropped = Array.from(e.dataTransfer.files);
|
|
410
|
+
if (dropped.length > 0) processFiles(dropped);
|
|
411
|
+
},
|
|
412
|
+
[disabled, processFiles],
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
const handleClick = useCallback(
|
|
416
|
+
(e: MouseEvent<HTMLDivElement>) => {
|
|
417
|
+
// A label-for click activates the input itself and bubbles here; the
|
|
418
|
+
// picker is already opening, so don't click it a second time.
|
|
419
|
+
if (disabled || e.target === inputRef.current) return;
|
|
420
|
+
inputRef.current?.click();
|
|
421
|
+
},
|
|
422
|
+
[disabled],
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
const handleKeyDown = useCallback(
|
|
426
|
+
(e: KeyboardEvent<HTMLDivElement>) => {
|
|
427
|
+
if (disabled) return;
|
|
428
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
429
|
+
e.preventDefault();
|
|
430
|
+
inputRef.current?.click();
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
[disabled],
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
const handleInputChange = useCallback(
|
|
437
|
+
(e: ChangeEvent<HTMLInputElement>) => {
|
|
438
|
+
const picked = Array.from(e.target.files ?? []);
|
|
439
|
+
// Reset the input so the same file can be re-picked (browsers cache the
|
|
440
|
+
// last value; without this, picking the same file twice in a row doesn't
|
|
441
|
+
// fire change a second time).
|
|
442
|
+
e.target.value = '';
|
|
443
|
+
if (picked.length > 0) processFiles(picked);
|
|
444
|
+
},
|
|
445
|
+
[processFiles],
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
// {...rest} last so consumer overrides win (Pattern A).
|
|
449
|
+
return (
|
|
450
|
+
<div
|
|
451
|
+
ref={ref}
|
|
452
|
+
// The id belongs on the file input (see the prop); while the dropzone is
|
|
453
|
+
// hidden (single mode with a file) the root keeps it so scroll-to-id /
|
|
454
|
+
// focus-first-invalid still find the field.
|
|
455
|
+
id={showDropzone ? undefined : id}
|
|
456
|
+
className={clsx(styles.root, disabled && styles.disabled, className)}
|
|
457
|
+
{...rest}
|
|
458
|
+
>
|
|
459
|
+
{/* ONE region for the whole batch. Per-row regions were the obvious
|
|
460
|
+
shape and the wrong one: twelve files resolving inside two seconds is
|
|
461
|
+
twelve announcements. Rendered unconditionally with its text deferred
|
|
462
|
+
so a FileUpload that mounts mid-transfer still announces — see
|
|
463
|
+
CLAUDE.md Hard rule 10 and #502. */}
|
|
464
|
+
<span role="status" aria-live="polite" className={styles.srOnly}>
|
|
465
|
+
{batchStatus}
|
|
466
|
+
</span>
|
|
467
|
+
{showDropzone && (
|
|
468
|
+
<div
|
|
469
|
+
role="button"
|
|
470
|
+
tabIndex={disabled ? -1 : 0}
|
|
471
|
+
aria-labelledby={ariaLabelledBy}
|
|
472
|
+
// `dropzoneLabel &&`, not just the typeof: `dropzoneLabel=""` is a
|
|
473
|
+
// string, so without it the dropzone got a literal `aria-label=""`,
|
|
474
|
+
// which names nothing and falls through to name-from-content — where
|
|
475
|
+
// the label span is empty for the same reason. A role="button" with
|
|
476
|
+
// no accessible name (#535).
|
|
477
|
+
aria-label={
|
|
478
|
+
typeof dropzoneLabel === 'string' && dropzoneLabel
|
|
479
|
+
? dropzoneLabel
|
|
480
|
+
: t('fileUpload.upload')
|
|
481
|
+
}
|
|
482
|
+
aria-describedby={ariaDescribedBy}
|
|
483
|
+
aria-invalid={invalid || undefined}
|
|
484
|
+
aria-disabled={disabled || undefined}
|
|
485
|
+
className={clsx(styles.dropzone, isDragOver && styles.dragOver)}
|
|
486
|
+
onDragEnter={handleDragEnter}
|
|
487
|
+
onDragOver={handleDragOver}
|
|
488
|
+
onDragLeave={handleDragLeave}
|
|
489
|
+
onDrop={handleDrop}
|
|
490
|
+
onClick={handleClick}
|
|
491
|
+
onKeyDown={handleKeyDown}
|
|
492
|
+
>
|
|
493
|
+
<input
|
|
494
|
+
ref={inputRef}
|
|
495
|
+
id={id}
|
|
496
|
+
type="file"
|
|
497
|
+
multiple={multiple}
|
|
498
|
+
accept={accept}
|
|
499
|
+
disabled={disabled}
|
|
500
|
+
onChange={handleInputChange}
|
|
501
|
+
className={styles.hiddenInput}
|
|
502
|
+
tabIndex={-1}
|
|
503
|
+
aria-hidden
|
|
504
|
+
/>
|
|
505
|
+
<span className={styles.dropzoneIcon}>
|
|
506
|
+
{dropzoneIcon ?? <CloudUpload size={32} aria-hidden />}
|
|
507
|
+
</span>
|
|
508
|
+
<span className={styles.dropzoneLabel}>{dropzoneLabel || t('fileUpload.dragHint')}</span>
|
|
509
|
+
{dropzoneHint && <span className={styles.dropzoneHint}>{dropzoneHint}</span>}
|
|
510
|
+
</div>
|
|
511
|
+
)}
|
|
512
|
+
{files.length > 0 && (
|
|
513
|
+
<ul className={styles.list}>
|
|
514
|
+
{files.map((entry) => {
|
|
515
|
+
const Icon = iconForFile(entry.file);
|
|
516
|
+
return (
|
|
517
|
+
<li key={entry.id} className={clsx(styles.row, STATUS_CLASS[entry.status])}>
|
|
518
|
+
<span className={styles.rowIcon}>
|
|
519
|
+
<Icon size={20} aria-hidden />
|
|
520
|
+
</span>
|
|
521
|
+
<span className={styles.rowName} title={entry.file.name}>
|
|
522
|
+
{entry.file.name}
|
|
523
|
+
</span>
|
|
524
|
+
<span className={styles.rowMeta}>{formatBytes(entry.file.size)}</span>
|
|
525
|
+
<span className={styles.rowStatus}>
|
|
526
|
+
{entry.status === 'uploading' && (
|
|
527
|
+
<Progress
|
|
528
|
+
size="sm"
|
|
529
|
+
value={entry.progress}
|
|
530
|
+
aria-label={t('fileUpload.uploadingAriaLabel', { name: entry.file.name })}
|
|
531
|
+
/>
|
|
532
|
+
)}
|
|
533
|
+
{entry.status === 'error' && entry.error && (
|
|
534
|
+
<span className={styles.rowErrorMsg}>{entry.error}</span>
|
|
535
|
+
)}
|
|
536
|
+
{entry.status === 'done' && (
|
|
537
|
+
<Check
|
|
538
|
+
className={styles.rowDoneIcon}
|
|
539
|
+
size={16}
|
|
540
|
+
role="img"
|
|
541
|
+
aria-label={t('fileUpload.done')}
|
|
542
|
+
/>
|
|
543
|
+
)}
|
|
544
|
+
</span>
|
|
545
|
+
<Button
|
|
546
|
+
variant="ghost"
|
|
547
|
+
size="xs"
|
|
548
|
+
iconOnly
|
|
549
|
+
onClick={() => onFileRemove(entry)}
|
|
550
|
+
disabled={disabled}
|
|
551
|
+
// A failed row's message is plain text in a non-focusable
|
|
552
|
+
// <li>, so a user tabbing here later learned nothing about
|
|
553
|
+
// it. Fold the failure into the one name they DO reach.
|
|
554
|
+
aria-label={
|
|
555
|
+
entry.status === 'error'
|
|
556
|
+
? t('fileUpload.removeFailedAriaLabel', { name: entry.file.name })
|
|
557
|
+
: t('fileUpload.removeAriaLabel', { name: entry.file.name })
|
|
558
|
+
}
|
|
559
|
+
>
|
|
560
|
+
<X size={16} aria-hidden />
|
|
561
|
+
</Button>
|
|
562
|
+
</li>
|
|
563
|
+
);
|
|
564
|
+
})}
|
|
565
|
+
</ul>
|
|
566
|
+
)}
|
|
567
|
+
</div>
|
|
568
|
+
);
|
|
569
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format a byte count into a human-readable string with a unit.
|
|
3
|
+
*
|
|
4
|
+
* Uses the 1024-based (KiB) convention internally but labels as `B`/`KB`/`MB`/
|
|
5
|
+
* `GB` for consumer familiarity — the CRM presents file sizes the way Finder /
|
|
6
|
+
* Explorer / Drive present them (1024 bytes = "1 KB"), not the SI 1000-base.
|
|
7
|
+
*
|
|
8
|
+
* Examples:
|
|
9
|
+
* - `formatBytes(0)` → `"0 B"`
|
|
10
|
+
* - `formatBytes(512)` → `"512 B"`
|
|
11
|
+
* - `formatBytes(1024)` → `"1 KB"`
|
|
12
|
+
* - `formatBytes(1536)` → `"1.5 KB"`
|
|
13
|
+
* - `formatBytes(1024 * 1024 * 1.5)` → `"1.5 MB"`
|
|
14
|
+
* - `formatBytes(1024 * 1024 * 1024 * 2.3)` → `"2.3 GB"`
|
|
15
|
+
*
|
|
16
|
+
* Bytes show as integers (no decimals). KB/MB/GB show as integers when whole,
|
|
17
|
+
* otherwise one decimal place.
|
|
18
|
+
*
|
|
19
|
+
* Private to the FileUpload directory — not re-exported from the package.
|
|
20
|
+
*/
|
|
21
|
+
export function formatBytes(bytes: number): string {
|
|
22
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
23
|
+
if (bytes < 1024 * 1024) return formatWithUnit(bytes / 1024, 'KB');
|
|
24
|
+
if (bytes < 1024 * 1024 * 1024) return formatWithUnit(bytes / (1024 * 1024), 'MB');
|
|
25
|
+
return formatWithUnit(bytes / (1024 * 1024 * 1024), 'GB');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function formatWithUnit(value: number, unit: string): string {
|
|
29
|
+
// Integer when whole; one decimal otherwise. Avoids "1.0 KB" while keeping
|
|
30
|
+
// "1.5 KB" / "2.3 GB" precision.
|
|
31
|
+
const rounded = Math.round(value * 10) / 10;
|
|
32
|
+
const display = Number.isInteger(rounded) ? rounded.toString() : rounded.toFixed(1);
|
|
33
|
+
return `${display} ${unit}`;
|
|
34
|
+
}
|