@scalepad/ui 0.1.0
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/rules/date-handling.md +39 -0
- package/.ai/rules/figma-design-system.md +372 -0
- package/.ai/rules/figma-lm-design-system-keys.md +680 -0
- package/.ai/rules/file-extensions.md +13 -0
- package/.ai/rules/modal-confirmation-mutation.md +56 -0
- package/.ai/rules/react-hooks.md +29 -0
- package/.ai/rules/styling.md +83 -0
- package/AGENTS.md +37 -0
- package/README.md +125 -0
- package/figma.config.json +9 -0
- package/package.json +127 -0
- package/scripts/install-ai-rules.mjs +136 -0
- package/src/ThemeProvider.tsx +57 -0
- package/src/charts.ts +32 -0
- package/src/components/ActionCard/ActionCard.css.ts +60 -0
- package/src/components/ActionCard/ActionCard.tsx +154 -0
- package/src/components/ActionCard/index.ts +2 -0
- package/src/components/Anchor/Anchor.tsx +47 -0
- package/src/components/Anchor/index.ts +2 -0
- package/src/components/AppliedFiltersManagerBar/AppliedFiltersManagerBar.tsx +105 -0
- package/src/components/AppliedFiltersManagerBar/FilterBadge.css.ts +23 -0
- package/src/components/AppliedFiltersManagerBar/FilterBadge.tsx +50 -0
- package/src/components/AppliedFiltersManagerBar/index.ts +5 -0
- package/src/components/Badge/Badge.css.ts +72 -0
- package/src/components/Badge/Badge.figma.tsx +43 -0
- package/src/components/Badge/Badge.tsx +159 -0
- package/src/components/Badge/index.ts +2 -0
- package/src/components/BreadCrumb/BreadCrumb.tsx +62 -0
- package/src/components/BreadCrumb/index.ts +2 -0
- package/src/components/BulkActionBar/BulkActionBar.css.ts +26 -0
- package/src/components/BulkActionBar/BulkActionBar.tsx +164 -0
- package/src/components/BulkActionBar/index.ts +2 -0
- package/src/components/Button/Button.css.ts +272 -0
- package/src/components/Button/Button.figma.tsx +74 -0
- package/src/components/Button/Button.tsx +84 -0
- package/src/components/Button/index.ts +2 -0
- package/src/components/Charts/ChartTooltip.figma.tsx +33 -0
- package/src/components/Charts/ChartTooltip.tsx +101 -0
- package/src/components/Charts/MiniBarSparkline.tsx +75 -0
- package/src/components/Charts/StackedPatternBarChart.tsx +494 -0
- package/src/components/Charts/TrendAreaChart.css.ts +23 -0
- package/src/components/Charts/TrendAreaChart.tsx +210 -0
- package/src/components/Charts/index.ts +12 -0
- package/src/components/CodePanel/CodePanel.css.ts +113 -0
- package/src/components/CodePanel/CodePanel.tsx +121 -0
- package/src/components/CodePanel/index.ts +2 -0
- package/src/components/CommentComposer/CommentComposer.css.ts +60 -0
- package/src/components/CommentComposer/CommentComposer.tsx +181 -0
- package/src/components/CommentComposer/index.ts +2 -0
- package/src/components/ConfirmationModal/ConfirmationModal.tsx +149 -0
- package/src/components/ConfirmationModal/index.ts +2 -0
- package/src/components/ConfirmationTooltip/ConfirmationTooltip.tsx +132 -0
- package/src/components/ConfirmationTooltip/index.ts +2 -0
- package/src/components/DataDialog.figma.tsx +33 -0
- package/src/components/DataDialog.tsx +46 -0
- package/src/components/DataTable/DataTable.tsx +1042 -0
- package/src/components/DataTable/RowExpandToggle.tsx +105 -0
- package/src/components/DataTable/RowGroupHeader.tsx +190 -0
- package/src/components/DataTable/createActionsColumn.tsx +86 -0
- package/src/components/DataTable/index.ts +25 -0
- package/src/components/DatePicker/CustomRangePicker.tsx +59 -0
- package/src/components/DatePicker/DateInput.tsx +329 -0
- package/src/components/DatePicker/DateNavigator.tsx +486 -0
- package/src/components/DatePicker/DatePicker.tsx +242 -0
- package/src/components/DatePicker/MonthlyRangePicker.tsx +231 -0
- package/src/components/DatePicker/QuarterlyRangePicker.tsx +224 -0
- package/src/components/DatePicker/QuickPicksSidebar.tsx +242 -0
- package/src/components/DatePicker/YearlyRangePicker.tsx +171 -0
- package/src/components/DatePicker/index.ts +7 -0
- package/src/components/DatePicker/types.ts +12 -0
- package/src/components/DesignSystemPrimitives/FluidGrid.tsx +44 -0
- package/src/components/DesignSystemPrimitives/InteractivePrimitives.tsx +177 -0
- package/src/components/DesignSystemPrimitives/LayoutPrimitives.tsx +220 -0
- package/src/components/DesignSystemPrimitives/LayoutPrimitives.types.tsx +15 -0
- package/src/components/DesignSystemPrimitives/SurfacePrimitives.tsx +46 -0
- package/src/components/DesignSystemPrimitives/index.ts +55 -0
- package/src/components/Details/Details.css.ts +74 -0
- package/src/components/Details/Details.tsx +140 -0
- package/src/components/Details/index.ts +2 -0
- package/src/components/DownloadCard/DownloadCard.css.ts +22 -0
- package/src/components/DownloadCard/DownloadCard.tsx +63 -0
- package/src/components/DownloadCard/index.ts +2 -0
- package/src/components/Drawer/Drawer.css.ts +32 -0
- package/src/components/Drawer/Drawer.tsx +236 -0
- package/src/components/Drawer/hooks/useDetailDrawer.ts +61 -0
- package/src/components/Drawer/hooks/useDetailDrawerNavigation.ts +125 -0
- package/src/components/Drawer/hooks/useDetailDrawerNavigationContext.ts +66 -0
- package/src/components/EditableRichText/EditableRichText.css.ts +72 -0
- package/src/components/EditableRichText/EditableRichText.tsx +324 -0
- package/src/components/EditableRichText/index.ts +2 -0
- package/src/components/EditableSelect/EditableSelect.css.ts +62 -0
- package/src/components/EditableSelect/EditableSelect.tsx +224 -0
- package/src/components/EditableSelect/index.ts +2 -0
- package/src/components/EditableText/EditableText.tsx +377 -0
- package/src/components/EditableText/index.ts +2 -0
- package/src/components/EmptyState/EmptyState.figma.tsx +33 -0
- package/src/components/EmptyState/EmptyState.tsx +230 -0
- package/src/components/EmptyState/index.ts +2 -0
- package/src/components/ErrorBoundary.tsx +135 -0
- package/src/components/ErrorState/ErrorState.tsx +197 -0
- package/src/components/ErrorState/index.ts +2 -0
- package/src/components/FeatureCard.tsx +42 -0
- package/src/components/FilterMenu/FilterMenu.figma.tsx +30 -0
- package/src/components/FilterMenu/FilterMenu.tsx +198 -0
- package/src/components/FilterMenu/FilterSubMenuTypes/BooleanFilterSubmenu.tsx +46 -0
- package/src/components/FilterMenu/FilterSubMenuTypes/SearchableFilterSubmenu.tsx +239 -0
- package/src/components/FilterMenu/FilterSubMenuTypes/index.ts +8 -0
- package/src/components/FilterMenu/defaultFilterSchemas.ts +63 -0
- package/src/components/FilterMenu/helpers.ts +115 -0
- package/src/components/FilterMenu/index.ts +35 -0
- package/src/components/FilterMenu/types.ts +101 -0
- package/src/components/IconButton/IconButton.css.ts +272 -0
- package/src/components/IconButton/IconButton.figma.tsx +47 -0
- package/src/components/IconButton/IconButton.tsx +72 -0
- package/src/components/IconButton/README.md +230 -0
- package/src/components/IconButton/index.ts +2 -0
- package/src/components/InfiniteScrollSentinel.tsx +86 -0
- package/src/components/InfiniteScrollTrigger.tsx +78 -0
- package/src/components/InfoCard.figma.tsx +47 -0
- package/src/components/InfoCard.tsx +216 -0
- package/src/components/KbdHint/KbdHint.tsx +23 -0
- package/src/components/KbdHint/index.ts +2 -0
- package/src/components/LabeledField/LabeledField.tsx +21 -0
- package/src/components/LabeledField/index.ts +2 -0
- package/src/components/LookupSelect/LookupSelect.css.ts +149 -0
- package/src/components/LookupSelect/LookupSelect.tsx +325 -0
- package/src/components/LookupSelect/index.ts +2 -0
- package/src/components/Menu/Menu.css.ts +89 -0
- package/src/components/Menu/Menu.tsx +105 -0
- package/src/components/Menu/index.ts +2 -0
- package/src/components/MessageBox/MessageBox.tsx +168 -0
- package/src/components/MessageBox/index.ts +2 -0
- package/src/components/MetricDisplay/MetricDisplay.tsx +55 -0
- package/src/components/MetricDisplay/index.ts +1 -0
- package/src/components/MultiSelect/MultiSelect.tsx +278 -0
- package/src/components/MultiSelect/index.ts +2 -0
- package/src/components/Notifications/Notifications.tsx +12 -0
- package/src/components/Notifications/README.md +93 -0
- package/src/components/Notifications/index.ts +4 -0
- package/src/components/Notifications/showToast.tsx +100 -0
- package/src/components/PropertyRow/PropertyRow.tsx +96 -0
- package/src/components/PropertyRow/index.ts +2 -0
- package/src/components/RadioTile/RadioTile.tsx +253 -0
- package/src/components/RadioTile/index.ts +2 -0
- package/src/components/RichText/FormattingToolbar.css.ts +69 -0
- package/src/components/RichText/FormattingToolbar.tsx +112 -0
- package/src/components/RichText/RichTextInline.css.ts +54 -0
- package/src/components/RichText/RichTextInline.tsx +318 -0
- package/src/components/RichText/formattingCommands.ts +181 -0
- package/src/components/RichText/formattingTypes.ts +34 -0
- package/src/components/RichText/index.ts +49 -0
- package/src/components/RichText/richTextExtensions.ts +111 -0
- package/src/components/RichText/richTextHelpers.ts +65 -0
- package/src/components/RichText/richTextImage.ts +253 -0
- package/src/components/RichText/richTextImageHandlers.ts +244 -0
- package/src/components/RichText/richTextProse.css.ts +261 -0
- package/src/components/RichTextEditor/RichTextEditor.css.ts +82 -0
- package/src/components/RichTextEditor/RichTextEditor.tsx +204 -0
- package/src/components/RichTextEditor/index.ts +2 -0
- package/src/components/RichTextView/RichTextView.css.ts +11 -0
- package/src/components/RichTextView/RichTextView.tsx +114 -0
- package/src/components/RichTextView/index.ts +2 -0
- package/src/components/Schedule/Schedule.tsx +35 -0
- package/src/components/SchedulePicker/SchedulePicker.css.ts +42 -0
- package/src/components/SchedulePicker/SchedulePicker.tsx +130 -0
- package/src/components/SchedulePicker/index.ts +2 -0
- package/src/components/SearchableList/types.ts +30 -0
- package/src/components/SearchableSubMenu/SearchableSubMenu.css.ts +25 -0
- package/src/components/SearchableSubMenu/SearchableSubMenu.tsx +139 -0
- package/src/components/SearchableSubMenu/index.ts +2 -0
- package/src/components/Select/README.md +114 -0
- package/src/components/Select/Select.css.ts +110 -0
- package/src/components/Select/Select.tsx +133 -0
- package/src/components/Select/index.ts +2 -0
- package/src/components/SelectCreatable/SelectCreatable.css.ts +16 -0
- package/src/components/SelectCreatable/SelectCreatable.tsx +203 -0
- package/src/components/SelectCreatable/index.ts +2 -0
- package/src/components/SettingsCard/SettingsCard.tsx +98 -0
- package/src/components/SettingsCard/index.ts +2 -0
- package/src/components/Sidebar/Sidebar.css.ts +91 -0
- package/src/components/Sidebar/Sidebar.tsx +129 -0
- package/src/components/Sidebar/index.ts +5 -0
- package/src/components/SimpleList/SimpleList.css.ts +12 -0
- package/src/components/SimpleList/SimpleList.tsx +44 -0
- package/src/components/SimpleList/index.ts +2 -0
- package/src/components/SimpleTable/SimpleTable.tsx +296 -0
- package/src/components/SimpleTable/index.ts +2 -0
- package/src/components/SlashRichTextEditor/SelectionBubbleMenu.css.ts +62 -0
- package/src/components/SlashRichTextEditor/SelectionBubbleMenu.tsx +85 -0
- package/src/components/SlashRichTextEditor/SlashCommandMenu.css.ts +124 -0
- package/src/components/SlashRichTextEditor/SlashCommandMenu.tsx +168 -0
- package/src/components/SlashRichTextEditor/SlashRichTextEditor.css.ts +81 -0
- package/src/components/SlashRichTextEditor/SlashRichTextEditor.tsx +538 -0
- package/src/components/SlashRichTextEditor/SlashSuggestionExtension.ts +48 -0
- package/src/components/SlashRichTextEditor/index.ts +13 -0
- package/src/components/SlashRichTextEditor/types.ts +48 -0
- package/src/components/StatCard/StatCard.css.ts +70 -0
- package/src/components/StatCard/StatCard.tsx +201 -0
- package/src/components/StatCard/index.ts +1 -0
- package/src/components/StatusBadge/StatusBadge.tsx +70 -0
- package/src/components/StatusBadge/index.ts +2 -0
- package/src/components/StatusIndicator/StatusIndicator.tsx +67 -0
- package/src/components/StatusIndicator/index.ts +6 -0
- package/src/components/SubNavigation/SubNavigation.css.ts +72 -0
- package/src/components/SubNavigation/SubNavigation.tsx +104 -0
- package/src/components/SubNavigation/index.ts +2 -0
- package/src/components/SuspenseLoader.tsx +22 -0
- package/src/components/Table/SortableColumnHeader.tsx +99 -0
- package/src/components/Table/TableSkeletonRows.figma.tsx +22 -0
- package/src/components/Table/TableSkeletonRows.tsx +113 -0
- package/src/components/Table/index.ts +9 -0
- package/src/components/TableActionsMenu.tsx +58 -0
- package/src/components/TableCard.tsx +29 -0
- package/src/components/TableContainer/TableContainer.tsx +86 -0
- package/src/components/TableContainer/index.ts +2 -0
- package/src/components/TableControlBar/TableControlBar.tsx +156 -0
- package/src/components/TableControlBar/TableSelectionButton.tsx +57 -0
- package/src/components/TableControlBar/index.ts +13 -0
- package/src/components/TableControlBar/useTableControlBar.tsx +314 -0
- package/src/components/TableSelection/TableSelection.tsx +43 -0
- package/src/components/TableSelection/index.ts +5 -0
- package/src/components/Tabs/README.md +76 -0
- package/src/components/Tabs/Tabs.css.ts +54 -0
- package/src/components/Tabs/Tabs.figma.tsx +47 -0
- package/src/components/Tabs/Tabs.tsx +96 -0
- package/src/components/Tabs/index.ts +8 -0
- package/src/components/TextInput/README.md +98 -0
- package/src/components/TextInput/SearchTextInput.figma.tsx +22 -0
- package/src/components/TextInput/SearchTextInput.tsx +150 -0
- package/src/components/TextInput/TextInput.figma.tsx +44 -0
- package/src/components/TextInput/TextInput.tsx +42 -0
- package/src/components/TextInput/index.ts +4 -0
- package/src/components/ThemeSwitcher.figma.tsx +28 -0
- package/src/components/ThemeSwitcher.tsx +69 -0
- package/src/components/TrendBadge/TrendBadge.tsx +76 -0
- package/src/components/TrendBadge/index.ts +2 -0
- package/src/components/TruncatedText.tsx +115 -0
- package/src/components/Typography/Text.tsx +74 -0
- package/src/components/Typography/Title.tsx +100 -0
- package/src/components/Typography/index.ts +4 -0
- package/src/geist-fonts.ts +48 -0
- package/src/hooks/index.ts +31 -0
- package/src/hooks/useFilters.ts +152 -0
- package/src/hooks/useInfiniteScroll.ts +62 -0
- package/src/hooks/usePlatform.ts +33 -0
- package/src/hooks/useServerTable.ts +495 -0
- package/src/hooks/useTableSelection.ts +102 -0
- package/src/hooks/useTableSort.ts +259 -0
- package/src/index.ts +483 -0
- package/src/mantine.ts +25 -0
- package/src/theme/mantineVars.ts +12 -0
- package/src/theme/themeContract.css.ts +131 -0
- package/src/theme/themeVars.ts +31 -0
- package/src/theme.ts +168 -0
- package/src/tokens/color-types.ts +107 -0
- package/src/tokens/colors.ts +243 -0
- package/src/tokens/index.ts +14 -0
- package/src/tokens/radius.ts +17 -0
- package/src/tokens/semantic-colors.ts +224 -0
- package/src/tokens/semantic-tokens-css.ts +53 -0
- package/src/tokens/shadows.ts +11 -0
- package/src/tokens/spacing.ts +20 -0
- package/src/tokens/text-styles.ts +179 -0
- package/src/tokens/typography.ts +40 -0
- package/src/tokens/zIndex.ts +27 -0
- package/src/types/mantine-theme.d.ts +17 -0
- package/src/types/tanstack-table.d.ts +22 -0
- package/src/utils/avatar.ts +150 -0
- package/src/utils/chartHelpers.ts +53 -0
- package/src/utils/color-props.ts +77 -0
- package/src/utils/createDesignComponent.tsx +104 -0
- package/src/utils/nestFlatRows.ts +111 -0
- package/src/utils/withStaticComponents.ts +6 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic colour + initials helpers for identity chips (avatars,
|
|
3
|
+
* client logos, assignee swatches). Centralised here so every surface in
|
|
4
|
+
* the app — task rows, drawers, the sidebar, kanban cards, dashboards —
|
|
5
|
+
* picks the same swatch and the same letters for a given key.
|
|
6
|
+
*
|
|
7
|
+
* The picker is a simple polynomial hash that maps the key to one of 50
|
|
8
|
+
* curated hex colors. 50 is enough headroom that two adjacent
|
|
9
|
+
* client/user chips collide visually only in pathological cases — and
|
|
10
|
+
* crucially keeps every entry distinct enough to read at small sizes
|
|
11
|
+
* without bleeding into its neighbours.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 50-entry palette of saturated mid-tones with at least ~3:1 contrast
|
|
16
|
+
* against white text (the foreground colour every avatar surface in the
|
|
17
|
+
* app uses). Chosen from Tailwind's `600`–`800` shades and balanced across
|
|
18
|
+
* the hue wheel.
|
|
19
|
+
*
|
|
20
|
+
* Per-hue contrast notes (vs `#ffffff` foreground):
|
|
21
|
+
* - The Tailwind `500` shade of greener / cyan-er families
|
|
22
|
+
* (`#22c55e` green-500, `#10b981` emerald-500, `#14b8a6` teal-500,
|
|
23
|
+
* `#06b6d4` cyan-500) lands at roughly 2:1–2.5:1 against white — close
|
|
24
|
+
* to but below the conventional 3:1 floor for graphical objects, so we
|
|
25
|
+
* skip them and start every hue at the `600` or `700` shade instead.
|
|
26
|
+
* - The same is true for yellow/amber/lime `500` shades, which were
|
|
27
|
+
* already excluded.
|
|
28
|
+
* - For families that pass at `500` (red, orange, blue, indigo, violet,
|
|
29
|
+
* purple, fuchsia, pink, rose) we keep the `500` plus the two darker
|
|
30
|
+
* siblings so the palette still spans a wide hue range.
|
|
31
|
+
*
|
|
32
|
+
* Intentionally `as const` so `colorForKey` returns a literal-typed value
|
|
33
|
+
* and downstream consumers can narrow when useful.
|
|
34
|
+
*/
|
|
35
|
+
export const AVATAR_PALETTE = [
|
|
36
|
+
// Slate (neutral anchors so non-tagged entities still feel intentional)
|
|
37
|
+
'#64748b',
|
|
38
|
+
'#334155',
|
|
39
|
+
// Red
|
|
40
|
+
'#ef4444',
|
|
41
|
+
'#dc2626',
|
|
42
|
+
'#b91c1c',
|
|
43
|
+
// Orange
|
|
44
|
+
'#f97316',
|
|
45
|
+
'#ea580c',
|
|
46
|
+
'#c2410c',
|
|
47
|
+
// Amber (skip 500 — too light for white text)
|
|
48
|
+
'#d97706',
|
|
49
|
+
'#b45309',
|
|
50
|
+
// Yellow (skip 500 — way too light for white text)
|
|
51
|
+
'#ca8a04',
|
|
52
|
+
'#a16207',
|
|
53
|
+
// Lime (skip 500 — too light for white text)
|
|
54
|
+
'#65a30d',
|
|
55
|
+
'#4d7c0f',
|
|
56
|
+
// Green (skip 500 — `#22c55e` ≈ 2.1:1 vs white, below the 3:1 floor)
|
|
57
|
+
'#16a34a',
|
|
58
|
+
'#15803d',
|
|
59
|
+
'#166534',
|
|
60
|
+
// Emerald (skip 500 — `#10b981` ≈ 2.3:1 vs white)
|
|
61
|
+
'#059669',
|
|
62
|
+
'#047857',
|
|
63
|
+
'#065f46',
|
|
64
|
+
// Teal (skip 500 — `#14b8a6` ≈ 2.4:1 vs white)
|
|
65
|
+
'#0d9488',
|
|
66
|
+
'#0f766e',
|
|
67
|
+
'#115e59',
|
|
68
|
+
// Cyan (skip 500 — `#06b6d4` ≈ 2.6:1 vs white)
|
|
69
|
+
'#0891b2',
|
|
70
|
+
'#0e7490',
|
|
71
|
+
'#155e75',
|
|
72
|
+
// Sky
|
|
73
|
+
'#0ea5e9',
|
|
74
|
+
'#0284c7',
|
|
75
|
+
'#0369a1',
|
|
76
|
+
// Blue
|
|
77
|
+
'#3b82f6',
|
|
78
|
+
'#2563eb',
|
|
79
|
+
'#1d4ed8',
|
|
80
|
+
// Indigo
|
|
81
|
+
'#6366f1',
|
|
82
|
+
'#4f46e5',
|
|
83
|
+
'#4338ca',
|
|
84
|
+
// Violet
|
|
85
|
+
'#8b5cf6',
|
|
86
|
+
'#7c3aed',
|
|
87
|
+
'#6d28d9',
|
|
88
|
+
// Purple
|
|
89
|
+
'#a855f7',
|
|
90
|
+
'#9333ea',
|
|
91
|
+
'#7e22ce',
|
|
92
|
+
// Fuchsia
|
|
93
|
+
'#d946ef',
|
|
94
|
+
'#c026d3',
|
|
95
|
+
'#a21caf',
|
|
96
|
+
// Pink
|
|
97
|
+
'#ec4899',
|
|
98
|
+
'#db2777',
|
|
99
|
+
'#be185d',
|
|
100
|
+
// Rose
|
|
101
|
+
'#f43f5e',
|
|
102
|
+
'#e11d48',
|
|
103
|
+
'#be123c',
|
|
104
|
+
] as const;
|
|
105
|
+
|
|
106
|
+
export type AvatarColor = (typeof AVATAR_PALETTE)[number];
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Pick a stable swatch from `AVATAR_PALETTE` for the given identity key.
|
|
110
|
+
* The hash is intentionally tiny and unauthenticated — its only job is to
|
|
111
|
+
* spread keys across the palette so colour assignment is reproducible
|
|
112
|
+
* across renders, sessions and users without round-tripping to the
|
|
113
|
+
* server. Callers should pass a stable key (user id, client id, email)
|
|
114
|
+
* so the same identity reads as the same colour everywhere.
|
|
115
|
+
*/
|
|
116
|
+
export function colorForKey(key: string): AvatarColor {
|
|
117
|
+
let hash = 0;
|
|
118
|
+
for (let i = 0; i < key.length; i += 1) {
|
|
119
|
+
hash = (hash * 31 + key.charCodeAt(i)) >>> 0;
|
|
120
|
+
}
|
|
121
|
+
return AVATAR_PALETTE[hash % AVATAR_PALETTE.length]!;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Two-character initials suitable for user / assignee avatars. Splits on
|
|
126
|
+
* whitespace and combines the first letter of the first and last token —
|
|
127
|
+
* `Jane Doe` → `JD`, `Madonna` → `MA`. Returns `?` for empty input so
|
|
128
|
+
* downstream chips render the placeholder swatch instead of crashing.
|
|
129
|
+
*/
|
|
130
|
+
export function initials(value: string): string {
|
|
131
|
+
const trimmed = value.trim();
|
|
132
|
+
if (!trimmed) return '?';
|
|
133
|
+
const parts = trimmed.split(/\s+/u);
|
|
134
|
+
if (parts.length === 1) {
|
|
135
|
+
return parts[0]!.slice(0, 2).toUpperCase();
|
|
136
|
+
}
|
|
137
|
+
return (parts[0]![0]! + parts[parts.length - 1]![0]!).toUpperCase();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Single-character initial suitable for client / company avatars. Just
|
|
142
|
+
* the first letter of the first whitespace-separated token, uppercased.
|
|
143
|
+
* Mirrors `initials` for empty-input handling.
|
|
144
|
+
*/
|
|
145
|
+
export function clientInitial(value: string): string {
|
|
146
|
+
const trimmed = value.trim();
|
|
147
|
+
if (!trimmed) return '?';
|
|
148
|
+
const parts = trimmed.split(/\s+/u);
|
|
149
|
+
return parts[0]![0]!.toUpperCase();
|
|
150
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determines whether a tick label should be shown based on data density
|
|
3
|
+
* Implements intelligent label skipping to prevent crowding on charts
|
|
4
|
+
*
|
|
5
|
+
* @param index - Current tick index
|
|
6
|
+
* @param dataLength - Total number of data points
|
|
7
|
+
* @param isLast - Whether this is the last tick
|
|
8
|
+
* @returns true if the label should be shown, false if it should be skipped
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* // For 10 data points, shows labels at indices: 0, 3, 6, 9
|
|
12
|
+
* shouldShowTickLabel(0, 10, false) // true
|
|
13
|
+
* shouldShowTickLabel(1, 10, false) // false
|
|
14
|
+
* shouldShowTickLabel(3, 10, false) // true
|
|
15
|
+
*/
|
|
16
|
+
export function shouldShowTickLabel(
|
|
17
|
+
index: number,
|
|
18
|
+
dataLength: number,
|
|
19
|
+
isLast: boolean,
|
|
20
|
+
): boolean {
|
|
21
|
+
// For 36+ data points, let Recharts handle label density with 'preserveEnd'
|
|
22
|
+
if (dataLength > 35) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// For 10-35 data points, intelligently skip labels to avoid crowding
|
|
27
|
+
if (dataLength >= 10) {
|
|
28
|
+
// For 21-35 points, show every 6th label for consistent spacing, plus always show last
|
|
29
|
+
if (dataLength > 20) {
|
|
30
|
+
const step = 6;
|
|
31
|
+
return index % step === 0 || isLast;
|
|
32
|
+
}
|
|
33
|
+
// For 16-20 points, show exactly 4 evenly spaced labels
|
|
34
|
+
if (dataLength >= 16) {
|
|
35
|
+
const targetLabels = 4;
|
|
36
|
+
const evenIndices = Array.from({ length: targetLabels }, (_, i) =>
|
|
37
|
+
Math.round((i * (dataLength - 1)) / (targetLabels - 1)),
|
|
38
|
+
);
|
|
39
|
+
return evenIndices.includes(index);
|
|
40
|
+
}
|
|
41
|
+
// For exactly 10 points, use step of 3 for even spacing (0, 3, 6, 9)
|
|
42
|
+
if (dataLength === 10) {
|
|
43
|
+
const step = 3;
|
|
44
|
+
return index % step === 0;
|
|
45
|
+
}
|
|
46
|
+
// For 11-15 points, show every other label (plus always show last)
|
|
47
|
+
const step = 2;
|
|
48
|
+
return index % step === 0 || isLast;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// For fewer than 10 data points, show all labels
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Design System Color Props
|
|
3
|
+
*
|
|
4
|
+
* Utilities and types for mapping semantic color tokens to CSS variables.
|
|
5
|
+
* Used by component wrappers to enforce type-safe color props.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
BackgroundColor,
|
|
10
|
+
IconColor,
|
|
11
|
+
SemanticColor,
|
|
12
|
+
StrokeColor,
|
|
13
|
+
TextColor,
|
|
14
|
+
} from '../tokens';
|
|
15
|
+
import type { MantineColor, StyleProp } from '@mantine/core';
|
|
16
|
+
|
|
17
|
+
// Re-export color types for convenience
|
|
18
|
+
export type {
|
|
19
|
+
BackgroundColor,
|
|
20
|
+
IconColor,
|
|
21
|
+
SemanticColor,
|
|
22
|
+
StrokeColor,
|
|
23
|
+
TextColor,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** Special Mantine color values that should pass through without transformation */
|
|
27
|
+
const PASSTHROUGH_VALUES = new Set(['inherit', 'currentColor']);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Resolve a semantic color token name to its CSS variable.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* resolveColorToken('text-default') // 'var(--color-text-default)'
|
|
34
|
+
* resolveColorToken('inherit') // 'inherit'
|
|
35
|
+
* resolveColorToken(undefined) // undefined
|
|
36
|
+
*/
|
|
37
|
+
export function resolveColorToken(
|
|
38
|
+
token: string | undefined,
|
|
39
|
+
): StyleProp<MantineColor> | undefined {
|
|
40
|
+
if (!token || PASSTHROUGH_VALUES.has(token)) return token;
|
|
41
|
+
return `var(--color-${token.replace(/\./g, '-')})`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Color props for text color (`c` prop) */
|
|
45
|
+
export type TextColorProp = {
|
|
46
|
+
/** Text color - accepts semantic text tokens or 'inherit' */
|
|
47
|
+
c?: TextColor | 'inherit';
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** Color props for background color (`bg` prop) */
|
|
51
|
+
export type BackgroundColorProp = {
|
|
52
|
+
/** Background color - accepts semantic background tokens */
|
|
53
|
+
bg?: BackgroundColor;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** Color props for border/stroke color */
|
|
57
|
+
export type StrokeColorProp = {
|
|
58
|
+
/** Border color - accepts semantic stroke tokens. Generates `1px solid var(--color-...)` */
|
|
59
|
+
borderColor?: StrokeColor;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Standard color prop overrides for layout and container components.
|
|
64
|
+
* Replaces Mantine's untyped `c`, `bg` with design-system-restricted versions.
|
|
65
|
+
*/
|
|
66
|
+
export type DesignSystemColorProps = TextColorProp &
|
|
67
|
+
BackgroundColorProp &
|
|
68
|
+
StrokeColorProp;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Color prop for components that use Mantine's `color` prop (Button, Alert, etc.).
|
|
72
|
+
* These components use `color` to set their primary accent, not text/background.
|
|
73
|
+
*/
|
|
74
|
+
export type ComponentColorProp = {
|
|
75
|
+
/** Component accent color - accepts any semantic color token */
|
|
76
|
+
color?: SemanticColor;
|
|
77
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Design System Component Wrapper Factory
|
|
3
|
+
*
|
|
4
|
+
* Creates wrapped versions of Mantine components that restrict
|
|
5
|
+
* color props to design-system tokens with full type safety.
|
|
6
|
+
* Uses Mantine's `polymorphicFactory` so the resulting wrappers keep the
|
|
7
|
+
* same polymorphic/ref behavior shape expected by Mantine 9 consumers.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ComponentType, ElementType, Ref } from 'react';
|
|
11
|
+
|
|
12
|
+
import { polymorphicFactory, type PolymorphicFactory } from '@mantine/core';
|
|
13
|
+
|
|
14
|
+
import { resolveColorToken, type DesignSystemColorProps } from './color-props';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The Mantine style props that we intercept and replace with typed versions.
|
|
18
|
+
* These are removed from the original component props via Omit.
|
|
19
|
+
*/
|
|
20
|
+
export type InterceptedColorProps = 'c' | 'bg';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Given original component props P, replace the color props with our typed versions.
|
|
24
|
+
*/
|
|
25
|
+
export type WithDesignColors<P> = Omit<P, InterceptedColorProps> &
|
|
26
|
+
DesignSystemColorProps;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Create a design-system-aware wrapper around a Mantine component.
|
|
30
|
+
*
|
|
31
|
+
* The wrapper:
|
|
32
|
+
* 1. Replaces `c` and `bg` props with typed versions (TextColor, BackgroundColor)
|
|
33
|
+
* 2. Adds a `borderColor` convenience prop that generates `bd="1px solid var(--color-...)"`
|
|
34
|
+
* 3. Resolves semantic token names to CSS variable references before passing to Mantine
|
|
35
|
+
* 4. Preserves polymorphic component behavior (HTML props like onClick, style, etc.)
|
|
36
|
+
* 5. Forwards refs correctly
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```tsx
|
|
40
|
+
* import { Box as MantineBox, type BoxProps } from '@mantine/core';
|
|
41
|
+
* export const Box = createDesignComponent<BoxProps>(MantineBox, 'Box');
|
|
42
|
+
*
|
|
43
|
+
* // Usage - HTML props like onClick work because of createPolymorphicComponent:
|
|
44
|
+
* <Box bg="background-default" c="text-default" onClick={handleClick}>
|
|
45
|
+
* Content
|
|
46
|
+
* </Box>
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
type DesignComponentSource<Props extends object, DefaultRef> = ComponentType<
|
|
51
|
+
Props & { ref?: Ref<DefaultRef> }
|
|
52
|
+
>;
|
|
53
|
+
|
|
54
|
+
export function createDesignComponent<
|
|
55
|
+
Props extends object,
|
|
56
|
+
DefaultComponent extends ElementType,
|
|
57
|
+
DefaultRef,
|
|
58
|
+
>(Component: DesignComponentSource<Props, DefaultRef>, displayName: string) {
|
|
59
|
+
const Wrapped = polymorphicFactory<
|
|
60
|
+
PolymorphicFactory<{
|
|
61
|
+
props: WithDesignColors<Props>;
|
|
62
|
+
defaultComponent: DefaultComponent;
|
|
63
|
+
defaultRef: DefaultRef;
|
|
64
|
+
}>
|
|
65
|
+
>(
|
|
66
|
+
({
|
|
67
|
+
c,
|
|
68
|
+
bg,
|
|
69
|
+
borderColor,
|
|
70
|
+
ref,
|
|
71
|
+
...rest
|
|
72
|
+
}: WithDesignColors<Props> &
|
|
73
|
+
DesignSystemColorProps & {
|
|
74
|
+
ref?: Ref<DefaultRef>;
|
|
75
|
+
}) => {
|
|
76
|
+
const resolvedProps: Record<string, unknown> = { ...rest, ref };
|
|
77
|
+
|
|
78
|
+
// Resolve text color
|
|
79
|
+
const resolvedC = resolveColorToken(c);
|
|
80
|
+
if (resolvedC !== undefined) {
|
|
81
|
+
resolvedProps.c = resolvedC;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Resolve background color
|
|
85
|
+
const resolvedBg = resolveColorToken(bg);
|
|
86
|
+
if (resolvedBg !== undefined) {
|
|
87
|
+
resolvedProps.bg = resolvedBg;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Resolve borderColor convenience prop
|
|
91
|
+
if (borderColor) {
|
|
92
|
+
const resolvedBorderColor = resolveColorToken(borderColor);
|
|
93
|
+
resolvedProps.bd = `1px solid ${resolvedBorderColor}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<Component {...(resolvedProps as Props & { ref?: Ref<DefaultRef> })} />
|
|
98
|
+
);
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
Wrapped.displayName = displayName;
|
|
103
|
+
return Wrapped;
|
|
104
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for {@link nestFlatRows}.
|
|
3
|
+
*/
|
|
4
|
+
export interface NestFlatRowsOptions<T> {
|
|
5
|
+
/**
|
|
6
|
+
* Extract the stable id for a row.
|
|
7
|
+
* @default (row) => (row as { id: string }).id
|
|
8
|
+
*/
|
|
9
|
+
getId?: (row: T) => string;
|
|
10
|
+
/**
|
|
11
|
+
* Extract the parent id for a row. Return `null` or `undefined` for roots.
|
|
12
|
+
* @default (row) => (row as { parentId?: string | null }).parentId
|
|
13
|
+
*/
|
|
14
|
+
getParentId?: (row: T) => string | null | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* What to do when a row references a parentId that does not exist in the input.
|
|
17
|
+
* - `"throw"` (default): throw a descriptive `Error`.
|
|
18
|
+
* - `"drop"`: silently exclude the orphan row from the output.
|
|
19
|
+
*/
|
|
20
|
+
onOrphan?: 'throw' | 'drop';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Marker type: a row that may carry nested children under `subRows`.
|
|
25
|
+
* Used by consumers that need a typed view over the nested output.
|
|
26
|
+
*/
|
|
27
|
+
export interface WithSubRows<T> {
|
|
28
|
+
subRows?: T[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Convert a flat array of rows carrying `{ id, parentId? }` into a nested tree.
|
|
33
|
+
*
|
|
34
|
+
* - Root-level ordering follows the input order of rows whose `parentId` is
|
|
35
|
+
* nullish.
|
|
36
|
+
* - Children ordering follows the input order of rows that reference a given
|
|
37
|
+
* parent.
|
|
38
|
+
* - Input rows are never mutated. Each returned node is a shallow clone of
|
|
39
|
+
* its source row, so calling this function repeatedly on the same input
|
|
40
|
+
* is safe and will not accumulate duplicate `subRows` entries.
|
|
41
|
+
*
|
|
42
|
+
* By default, rows referencing an unknown `parentId` throw. Pass
|
|
43
|
+
* `{ onOrphan: "drop" }` to silently drop them (useful when the server may
|
|
44
|
+
* return partial trees).
|
|
45
|
+
*
|
|
46
|
+
* @example Nesting by default id / parentId fields
|
|
47
|
+
* ```ts
|
|
48
|
+
* nestFlatRows([
|
|
49
|
+
* { id: 'g1', label: 'Contracts' },
|
|
50
|
+
* { id: 'g1.a', parentId: 'g1', label: 'Acme' },
|
|
51
|
+
* ]);
|
|
52
|
+
* // => [{ id: 'g1', label: 'Contracts', subRows: [{ id: 'g1.a', parentId: 'g1', label: 'Acme' }] }]
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* @example Custom accessors
|
|
56
|
+
* ```ts
|
|
57
|
+
* nestFlatRows(rows, {
|
|
58
|
+
* getId: (r) => r.key,
|
|
59
|
+
* getParentId: (r) => r.parent_key,
|
|
60
|
+
* });
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export function nestFlatRows<T>(
|
|
64
|
+
rows: readonly T[],
|
|
65
|
+
options: NestFlatRowsOptions<T> = {},
|
|
66
|
+
): (T & WithSubRows<T>)[] {
|
|
67
|
+
const getId = options.getId ?? ((r: T) => (r as { id: string }).id);
|
|
68
|
+
const getParentId =
|
|
69
|
+
options.getParentId ??
|
|
70
|
+
((r: T) => (r as { parentId?: string | null }).parentId);
|
|
71
|
+
const onOrphan = options.onOrphan ?? 'throw';
|
|
72
|
+
|
|
73
|
+
type Node = T & WithSubRows<T>;
|
|
74
|
+
|
|
75
|
+
// Build a clone per row so we never mutate caller-owned objects. `subRows`
|
|
76
|
+
// is intentionally omitted here and lazily initialized when a child is
|
|
77
|
+
// pushed onto a parent, which also overwrites any stale `subRows` that
|
|
78
|
+
// might have been present on the source row from a previous invocation.
|
|
79
|
+
const byId = new Map<string, Node>();
|
|
80
|
+
for (const row of rows) {
|
|
81
|
+
const clone = { ...row } as Node;
|
|
82
|
+
delete (clone as WithSubRows<T>).subRows;
|
|
83
|
+
byId.set(getId(row), clone);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const roots: Node[] = [];
|
|
87
|
+
for (const row of rows) {
|
|
88
|
+
const id = getId(row);
|
|
89
|
+
const parentId = getParentId(row);
|
|
90
|
+
const node = byId.get(id)!;
|
|
91
|
+
|
|
92
|
+
if (parentId == null) {
|
|
93
|
+
roots.push(node);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const parent = byId.get(parentId);
|
|
98
|
+
if (!parent) {
|
|
99
|
+
if (onOrphan === 'throw') {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`nestFlatRows: unknown parentId "${parentId}" for row "${id}"`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
(parent.subRows ??= []).push(node);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return roots;
|
|
111
|
+
}
|