@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.
Files changed (273) hide show
  1. package/.ai/rules/date-handling.md +39 -0
  2. package/.ai/rules/figma-design-system.md +372 -0
  3. package/.ai/rules/figma-lm-design-system-keys.md +680 -0
  4. package/.ai/rules/file-extensions.md +13 -0
  5. package/.ai/rules/modal-confirmation-mutation.md +56 -0
  6. package/.ai/rules/react-hooks.md +29 -0
  7. package/.ai/rules/styling.md +83 -0
  8. package/AGENTS.md +37 -0
  9. package/README.md +125 -0
  10. package/figma.config.json +9 -0
  11. package/package.json +127 -0
  12. package/scripts/install-ai-rules.mjs +136 -0
  13. package/src/ThemeProvider.tsx +57 -0
  14. package/src/charts.ts +32 -0
  15. package/src/components/ActionCard/ActionCard.css.ts +60 -0
  16. package/src/components/ActionCard/ActionCard.tsx +154 -0
  17. package/src/components/ActionCard/index.ts +2 -0
  18. package/src/components/Anchor/Anchor.tsx +47 -0
  19. package/src/components/Anchor/index.ts +2 -0
  20. package/src/components/AppliedFiltersManagerBar/AppliedFiltersManagerBar.tsx +105 -0
  21. package/src/components/AppliedFiltersManagerBar/FilterBadge.css.ts +23 -0
  22. package/src/components/AppliedFiltersManagerBar/FilterBadge.tsx +50 -0
  23. package/src/components/AppliedFiltersManagerBar/index.ts +5 -0
  24. package/src/components/Badge/Badge.css.ts +72 -0
  25. package/src/components/Badge/Badge.figma.tsx +43 -0
  26. package/src/components/Badge/Badge.tsx +159 -0
  27. package/src/components/Badge/index.ts +2 -0
  28. package/src/components/BreadCrumb/BreadCrumb.tsx +62 -0
  29. package/src/components/BreadCrumb/index.ts +2 -0
  30. package/src/components/BulkActionBar/BulkActionBar.css.ts +26 -0
  31. package/src/components/BulkActionBar/BulkActionBar.tsx +164 -0
  32. package/src/components/BulkActionBar/index.ts +2 -0
  33. package/src/components/Button/Button.css.ts +272 -0
  34. package/src/components/Button/Button.figma.tsx +74 -0
  35. package/src/components/Button/Button.tsx +84 -0
  36. package/src/components/Button/index.ts +2 -0
  37. package/src/components/Charts/ChartTooltip.figma.tsx +33 -0
  38. package/src/components/Charts/ChartTooltip.tsx +101 -0
  39. package/src/components/Charts/MiniBarSparkline.tsx +75 -0
  40. package/src/components/Charts/StackedPatternBarChart.tsx +494 -0
  41. package/src/components/Charts/TrendAreaChart.css.ts +23 -0
  42. package/src/components/Charts/TrendAreaChart.tsx +210 -0
  43. package/src/components/Charts/index.ts +12 -0
  44. package/src/components/CodePanel/CodePanel.css.ts +113 -0
  45. package/src/components/CodePanel/CodePanel.tsx +121 -0
  46. package/src/components/CodePanel/index.ts +2 -0
  47. package/src/components/CommentComposer/CommentComposer.css.ts +60 -0
  48. package/src/components/CommentComposer/CommentComposer.tsx +181 -0
  49. package/src/components/CommentComposer/index.ts +2 -0
  50. package/src/components/ConfirmationModal/ConfirmationModal.tsx +149 -0
  51. package/src/components/ConfirmationModal/index.ts +2 -0
  52. package/src/components/ConfirmationTooltip/ConfirmationTooltip.tsx +132 -0
  53. package/src/components/ConfirmationTooltip/index.ts +2 -0
  54. package/src/components/DataDialog.figma.tsx +33 -0
  55. package/src/components/DataDialog.tsx +46 -0
  56. package/src/components/DataTable/DataTable.tsx +1042 -0
  57. package/src/components/DataTable/RowExpandToggle.tsx +105 -0
  58. package/src/components/DataTable/RowGroupHeader.tsx +190 -0
  59. package/src/components/DataTable/createActionsColumn.tsx +86 -0
  60. package/src/components/DataTable/index.ts +25 -0
  61. package/src/components/DatePicker/CustomRangePicker.tsx +59 -0
  62. package/src/components/DatePicker/DateInput.tsx +329 -0
  63. package/src/components/DatePicker/DateNavigator.tsx +486 -0
  64. package/src/components/DatePicker/DatePicker.tsx +242 -0
  65. package/src/components/DatePicker/MonthlyRangePicker.tsx +231 -0
  66. package/src/components/DatePicker/QuarterlyRangePicker.tsx +224 -0
  67. package/src/components/DatePicker/QuickPicksSidebar.tsx +242 -0
  68. package/src/components/DatePicker/YearlyRangePicker.tsx +171 -0
  69. package/src/components/DatePicker/index.ts +7 -0
  70. package/src/components/DatePicker/types.ts +12 -0
  71. package/src/components/DesignSystemPrimitives/FluidGrid.tsx +44 -0
  72. package/src/components/DesignSystemPrimitives/InteractivePrimitives.tsx +177 -0
  73. package/src/components/DesignSystemPrimitives/LayoutPrimitives.tsx +220 -0
  74. package/src/components/DesignSystemPrimitives/LayoutPrimitives.types.tsx +15 -0
  75. package/src/components/DesignSystemPrimitives/SurfacePrimitives.tsx +46 -0
  76. package/src/components/DesignSystemPrimitives/index.ts +55 -0
  77. package/src/components/Details/Details.css.ts +74 -0
  78. package/src/components/Details/Details.tsx +140 -0
  79. package/src/components/Details/index.ts +2 -0
  80. package/src/components/DownloadCard/DownloadCard.css.ts +22 -0
  81. package/src/components/DownloadCard/DownloadCard.tsx +63 -0
  82. package/src/components/DownloadCard/index.ts +2 -0
  83. package/src/components/Drawer/Drawer.css.ts +32 -0
  84. package/src/components/Drawer/Drawer.tsx +236 -0
  85. package/src/components/Drawer/hooks/useDetailDrawer.ts +61 -0
  86. package/src/components/Drawer/hooks/useDetailDrawerNavigation.ts +125 -0
  87. package/src/components/Drawer/hooks/useDetailDrawerNavigationContext.ts +66 -0
  88. package/src/components/EditableRichText/EditableRichText.css.ts +72 -0
  89. package/src/components/EditableRichText/EditableRichText.tsx +324 -0
  90. package/src/components/EditableRichText/index.ts +2 -0
  91. package/src/components/EditableSelect/EditableSelect.css.ts +62 -0
  92. package/src/components/EditableSelect/EditableSelect.tsx +224 -0
  93. package/src/components/EditableSelect/index.ts +2 -0
  94. package/src/components/EditableText/EditableText.tsx +377 -0
  95. package/src/components/EditableText/index.ts +2 -0
  96. package/src/components/EmptyState/EmptyState.figma.tsx +33 -0
  97. package/src/components/EmptyState/EmptyState.tsx +230 -0
  98. package/src/components/EmptyState/index.ts +2 -0
  99. package/src/components/ErrorBoundary.tsx +135 -0
  100. package/src/components/ErrorState/ErrorState.tsx +197 -0
  101. package/src/components/ErrorState/index.ts +2 -0
  102. package/src/components/FeatureCard.tsx +42 -0
  103. package/src/components/FilterMenu/FilterMenu.figma.tsx +30 -0
  104. package/src/components/FilterMenu/FilterMenu.tsx +198 -0
  105. package/src/components/FilterMenu/FilterSubMenuTypes/BooleanFilterSubmenu.tsx +46 -0
  106. package/src/components/FilterMenu/FilterSubMenuTypes/SearchableFilterSubmenu.tsx +239 -0
  107. package/src/components/FilterMenu/FilterSubMenuTypes/index.ts +8 -0
  108. package/src/components/FilterMenu/defaultFilterSchemas.ts +63 -0
  109. package/src/components/FilterMenu/helpers.ts +115 -0
  110. package/src/components/FilterMenu/index.ts +35 -0
  111. package/src/components/FilterMenu/types.ts +101 -0
  112. package/src/components/IconButton/IconButton.css.ts +272 -0
  113. package/src/components/IconButton/IconButton.figma.tsx +47 -0
  114. package/src/components/IconButton/IconButton.tsx +72 -0
  115. package/src/components/IconButton/README.md +230 -0
  116. package/src/components/IconButton/index.ts +2 -0
  117. package/src/components/InfiniteScrollSentinel.tsx +86 -0
  118. package/src/components/InfiniteScrollTrigger.tsx +78 -0
  119. package/src/components/InfoCard.figma.tsx +47 -0
  120. package/src/components/InfoCard.tsx +216 -0
  121. package/src/components/KbdHint/KbdHint.tsx +23 -0
  122. package/src/components/KbdHint/index.ts +2 -0
  123. package/src/components/LabeledField/LabeledField.tsx +21 -0
  124. package/src/components/LabeledField/index.ts +2 -0
  125. package/src/components/LookupSelect/LookupSelect.css.ts +149 -0
  126. package/src/components/LookupSelect/LookupSelect.tsx +325 -0
  127. package/src/components/LookupSelect/index.ts +2 -0
  128. package/src/components/Menu/Menu.css.ts +89 -0
  129. package/src/components/Menu/Menu.tsx +105 -0
  130. package/src/components/Menu/index.ts +2 -0
  131. package/src/components/MessageBox/MessageBox.tsx +168 -0
  132. package/src/components/MessageBox/index.ts +2 -0
  133. package/src/components/MetricDisplay/MetricDisplay.tsx +55 -0
  134. package/src/components/MetricDisplay/index.ts +1 -0
  135. package/src/components/MultiSelect/MultiSelect.tsx +278 -0
  136. package/src/components/MultiSelect/index.ts +2 -0
  137. package/src/components/Notifications/Notifications.tsx +12 -0
  138. package/src/components/Notifications/README.md +93 -0
  139. package/src/components/Notifications/index.ts +4 -0
  140. package/src/components/Notifications/showToast.tsx +100 -0
  141. package/src/components/PropertyRow/PropertyRow.tsx +96 -0
  142. package/src/components/PropertyRow/index.ts +2 -0
  143. package/src/components/RadioTile/RadioTile.tsx +253 -0
  144. package/src/components/RadioTile/index.ts +2 -0
  145. package/src/components/RichText/FormattingToolbar.css.ts +69 -0
  146. package/src/components/RichText/FormattingToolbar.tsx +112 -0
  147. package/src/components/RichText/RichTextInline.css.ts +54 -0
  148. package/src/components/RichText/RichTextInline.tsx +318 -0
  149. package/src/components/RichText/formattingCommands.ts +181 -0
  150. package/src/components/RichText/formattingTypes.ts +34 -0
  151. package/src/components/RichText/index.ts +49 -0
  152. package/src/components/RichText/richTextExtensions.ts +111 -0
  153. package/src/components/RichText/richTextHelpers.ts +65 -0
  154. package/src/components/RichText/richTextImage.ts +253 -0
  155. package/src/components/RichText/richTextImageHandlers.ts +244 -0
  156. package/src/components/RichText/richTextProse.css.ts +261 -0
  157. package/src/components/RichTextEditor/RichTextEditor.css.ts +82 -0
  158. package/src/components/RichTextEditor/RichTextEditor.tsx +204 -0
  159. package/src/components/RichTextEditor/index.ts +2 -0
  160. package/src/components/RichTextView/RichTextView.css.ts +11 -0
  161. package/src/components/RichTextView/RichTextView.tsx +114 -0
  162. package/src/components/RichTextView/index.ts +2 -0
  163. package/src/components/Schedule/Schedule.tsx +35 -0
  164. package/src/components/SchedulePicker/SchedulePicker.css.ts +42 -0
  165. package/src/components/SchedulePicker/SchedulePicker.tsx +130 -0
  166. package/src/components/SchedulePicker/index.ts +2 -0
  167. package/src/components/SearchableList/types.ts +30 -0
  168. package/src/components/SearchableSubMenu/SearchableSubMenu.css.ts +25 -0
  169. package/src/components/SearchableSubMenu/SearchableSubMenu.tsx +139 -0
  170. package/src/components/SearchableSubMenu/index.ts +2 -0
  171. package/src/components/Select/README.md +114 -0
  172. package/src/components/Select/Select.css.ts +110 -0
  173. package/src/components/Select/Select.tsx +133 -0
  174. package/src/components/Select/index.ts +2 -0
  175. package/src/components/SelectCreatable/SelectCreatable.css.ts +16 -0
  176. package/src/components/SelectCreatable/SelectCreatable.tsx +203 -0
  177. package/src/components/SelectCreatable/index.ts +2 -0
  178. package/src/components/SettingsCard/SettingsCard.tsx +98 -0
  179. package/src/components/SettingsCard/index.ts +2 -0
  180. package/src/components/Sidebar/Sidebar.css.ts +91 -0
  181. package/src/components/Sidebar/Sidebar.tsx +129 -0
  182. package/src/components/Sidebar/index.ts +5 -0
  183. package/src/components/SimpleList/SimpleList.css.ts +12 -0
  184. package/src/components/SimpleList/SimpleList.tsx +44 -0
  185. package/src/components/SimpleList/index.ts +2 -0
  186. package/src/components/SimpleTable/SimpleTable.tsx +296 -0
  187. package/src/components/SimpleTable/index.ts +2 -0
  188. package/src/components/SlashRichTextEditor/SelectionBubbleMenu.css.ts +62 -0
  189. package/src/components/SlashRichTextEditor/SelectionBubbleMenu.tsx +85 -0
  190. package/src/components/SlashRichTextEditor/SlashCommandMenu.css.ts +124 -0
  191. package/src/components/SlashRichTextEditor/SlashCommandMenu.tsx +168 -0
  192. package/src/components/SlashRichTextEditor/SlashRichTextEditor.css.ts +81 -0
  193. package/src/components/SlashRichTextEditor/SlashRichTextEditor.tsx +538 -0
  194. package/src/components/SlashRichTextEditor/SlashSuggestionExtension.ts +48 -0
  195. package/src/components/SlashRichTextEditor/index.ts +13 -0
  196. package/src/components/SlashRichTextEditor/types.ts +48 -0
  197. package/src/components/StatCard/StatCard.css.ts +70 -0
  198. package/src/components/StatCard/StatCard.tsx +201 -0
  199. package/src/components/StatCard/index.ts +1 -0
  200. package/src/components/StatusBadge/StatusBadge.tsx +70 -0
  201. package/src/components/StatusBadge/index.ts +2 -0
  202. package/src/components/StatusIndicator/StatusIndicator.tsx +67 -0
  203. package/src/components/StatusIndicator/index.ts +6 -0
  204. package/src/components/SubNavigation/SubNavigation.css.ts +72 -0
  205. package/src/components/SubNavigation/SubNavigation.tsx +104 -0
  206. package/src/components/SubNavigation/index.ts +2 -0
  207. package/src/components/SuspenseLoader.tsx +22 -0
  208. package/src/components/Table/SortableColumnHeader.tsx +99 -0
  209. package/src/components/Table/TableSkeletonRows.figma.tsx +22 -0
  210. package/src/components/Table/TableSkeletonRows.tsx +113 -0
  211. package/src/components/Table/index.ts +9 -0
  212. package/src/components/TableActionsMenu.tsx +58 -0
  213. package/src/components/TableCard.tsx +29 -0
  214. package/src/components/TableContainer/TableContainer.tsx +86 -0
  215. package/src/components/TableContainer/index.ts +2 -0
  216. package/src/components/TableControlBar/TableControlBar.tsx +156 -0
  217. package/src/components/TableControlBar/TableSelectionButton.tsx +57 -0
  218. package/src/components/TableControlBar/index.ts +13 -0
  219. package/src/components/TableControlBar/useTableControlBar.tsx +314 -0
  220. package/src/components/TableSelection/TableSelection.tsx +43 -0
  221. package/src/components/TableSelection/index.ts +5 -0
  222. package/src/components/Tabs/README.md +76 -0
  223. package/src/components/Tabs/Tabs.css.ts +54 -0
  224. package/src/components/Tabs/Tabs.figma.tsx +47 -0
  225. package/src/components/Tabs/Tabs.tsx +96 -0
  226. package/src/components/Tabs/index.ts +8 -0
  227. package/src/components/TextInput/README.md +98 -0
  228. package/src/components/TextInput/SearchTextInput.figma.tsx +22 -0
  229. package/src/components/TextInput/SearchTextInput.tsx +150 -0
  230. package/src/components/TextInput/TextInput.figma.tsx +44 -0
  231. package/src/components/TextInput/TextInput.tsx +42 -0
  232. package/src/components/TextInput/index.ts +4 -0
  233. package/src/components/ThemeSwitcher.figma.tsx +28 -0
  234. package/src/components/ThemeSwitcher.tsx +69 -0
  235. package/src/components/TrendBadge/TrendBadge.tsx +76 -0
  236. package/src/components/TrendBadge/index.ts +2 -0
  237. package/src/components/TruncatedText.tsx +115 -0
  238. package/src/components/Typography/Text.tsx +74 -0
  239. package/src/components/Typography/Title.tsx +100 -0
  240. package/src/components/Typography/index.ts +4 -0
  241. package/src/geist-fonts.ts +48 -0
  242. package/src/hooks/index.ts +31 -0
  243. package/src/hooks/useFilters.ts +152 -0
  244. package/src/hooks/useInfiniteScroll.ts +62 -0
  245. package/src/hooks/usePlatform.ts +33 -0
  246. package/src/hooks/useServerTable.ts +495 -0
  247. package/src/hooks/useTableSelection.ts +102 -0
  248. package/src/hooks/useTableSort.ts +259 -0
  249. package/src/index.ts +483 -0
  250. package/src/mantine.ts +25 -0
  251. package/src/theme/mantineVars.ts +12 -0
  252. package/src/theme/themeContract.css.ts +131 -0
  253. package/src/theme/themeVars.ts +31 -0
  254. package/src/theme.ts +168 -0
  255. package/src/tokens/color-types.ts +107 -0
  256. package/src/tokens/colors.ts +243 -0
  257. package/src/tokens/index.ts +14 -0
  258. package/src/tokens/radius.ts +17 -0
  259. package/src/tokens/semantic-colors.ts +224 -0
  260. package/src/tokens/semantic-tokens-css.ts +53 -0
  261. package/src/tokens/shadows.ts +11 -0
  262. package/src/tokens/spacing.ts +20 -0
  263. package/src/tokens/text-styles.ts +179 -0
  264. package/src/tokens/typography.ts +40 -0
  265. package/src/tokens/zIndex.ts +27 -0
  266. package/src/types/mantine-theme.d.ts +17 -0
  267. package/src/types/tanstack-table.d.ts +22 -0
  268. package/src/utils/avatar.ts +150 -0
  269. package/src/utils/chartHelpers.ts +53 -0
  270. package/src/utils/color-props.ts +77 -0
  271. package/src/utils/createDesignComponent.tsx +104 -0
  272. package/src/utils/nestFlatRows.ts +111 -0
  273. 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
+ }
@@ -0,0 +1,6 @@
1
+ export function withStaticComponents<
2
+ TRoot extends object,
3
+ TStatic extends object,
4
+ >(root: TRoot, staticComponents: TStatic): TRoot & TStatic {
5
+ return Object.assign(root, staticComponents);
6
+ }