@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,242 @@
1
+ import { useCallback, useMemo } from 'react';
2
+
3
+ import { Stack } from '@mantine/core';
4
+ import type { DateValue } from '@mantine/dates';
5
+
6
+ import {
7
+ addMonths,
8
+ addYears,
9
+ endOfMonth,
10
+ endOfYear,
11
+ getCurrentDate,
12
+ getCurrentQuarter,
13
+ getPreviousQuarter,
14
+ getQuarterRange,
15
+ isAfter,
16
+ isBefore,
17
+ startOfMonth,
18
+ startOfYear,
19
+ } from '@scalepad/ui-utils/date';
20
+
21
+ import { Text } from '../Typography';
22
+
23
+ import type { DatePickerGranularity, DateRange } from './types';
24
+
25
+ interface QuickPicksSidebarProps {
26
+ minDate?: DateValue;
27
+ maxDate: DateValue;
28
+ allowedRangeTypes?: DatePickerGranularity[];
29
+ onChange?: (range: DateRange, granularity: DatePickerGranularity) => void;
30
+ }
31
+
32
+ export function QuickPicksSidebar({
33
+ minDate,
34
+ maxDate,
35
+ allowedRangeTypes,
36
+ onChange,
37
+ }: QuickPicksSidebarProps) {
38
+ const today = getCurrentDate();
39
+
40
+ // Filter allowed range types (default to all if not specified)
41
+ const allRangeTypes: DatePickerGranularity[] = [
42
+ 'monthly',
43
+ 'quarterly',
44
+ 'yearly',
45
+ 'custom',
46
+ ];
47
+ const availableRangeTypes = allowedRangeTypes || allRangeTypes;
48
+
49
+ // Check if a range type is allowed
50
+ const isRangeTypeAllowed = (granularity: DatePickerGranularity) =>
51
+ availableRangeTypes.includes(granularity);
52
+
53
+ // Quick pick handlers
54
+ const handleQuickPick = useCallback(
55
+ (type: string) => {
56
+ let newStartDate: Date | null = null;
57
+ let newEndDate: Date | null = null;
58
+ let newRangeType: DatePickerGranularity = 'custom';
59
+ let isThisQuarter = false;
60
+
61
+ switch (type) {
62
+ case 'thisMonth':
63
+ newStartDate = startOfMonth(today);
64
+ newEndDate = endOfMonth(today);
65
+ newRangeType = 'monthly';
66
+ break;
67
+ case 'lastMonth': {
68
+ const lastMonth = addMonths(today, -1);
69
+ newStartDate = startOfMonth(lastMonth);
70
+ newEndDate = endOfMonth(lastMonth);
71
+ newRangeType = 'monthly';
72
+ break;
73
+ }
74
+ case 'thisQuarter': {
75
+ const currentQ = getCurrentQuarter(today);
76
+ newStartDate = currentQ.start;
77
+ // Always use the full quarter end date, not clamped to today
78
+ newEndDate = currentQ.end;
79
+ newRangeType = 'quarterly';
80
+ isThisQuarter = true;
81
+ break;
82
+ }
83
+ case 'lastQuarter': {
84
+ const prevQ = getPreviousQuarter(today);
85
+ newStartDate = prevQ.start;
86
+ newEndDate = prevQ.end;
87
+ newRangeType = 'quarterly';
88
+ break;
89
+ }
90
+ case 'lastYear': {
91
+ const lastYear = addYears(today, -1);
92
+ newStartDate = startOfYear(lastYear);
93
+ newEndDate = endOfYear(lastYear);
94
+ newRangeType = 'yearly';
95
+ break;
96
+ }
97
+ case 'thisYear': {
98
+ newStartDate = startOfYear(today);
99
+ newEndDate = endOfYear(today);
100
+ newRangeType = 'yearly';
101
+ break;
102
+ }
103
+ default: {
104
+ // Handle quarter format like "Q3 2025"
105
+ const quarterMatch = type.match(/Q(\d)\s(\d{4})/);
106
+ if (quarterMatch) {
107
+ const quarter = parseInt(quarterMatch[1], 10) - 1;
108
+ const year = parseInt(quarterMatch[2], 10);
109
+ const quarterRange = getQuarterRange(year, quarter);
110
+ newStartDate = quarterRange.start;
111
+ newEndDate = quarterRange.end;
112
+ newRangeType = 'quarterly';
113
+ }
114
+ break;
115
+ }
116
+ }
117
+
118
+ if (newStartDate && newEndDate) {
119
+ // Ensure dates are within bounds
120
+ if (minDate) {
121
+ const minDateObj =
122
+ minDate instanceof Date
123
+ ? minDate
124
+ : minDate
125
+ ? new Date(minDate)
126
+ : null;
127
+ if (minDateObj && isBefore(newStartDate, minDateObj)) {
128
+ newStartDate = minDateObj;
129
+ }
130
+ }
131
+ const maxDateObj =
132
+ maxDate instanceof Date
133
+ ? maxDate
134
+ : maxDate
135
+ ? new Date(maxDate)
136
+ : null;
137
+ // Don't clamp end date for "thisQuarter" - always use full quarter end date
138
+ // For other cases, clamp if end date is after maxDate
139
+ if (maxDateObj && isAfter(newEndDate, maxDateObj) && !isThisQuarter) {
140
+ newEndDate = maxDateObj;
141
+ }
142
+
143
+ onChange?.(
144
+ {
145
+ startDate: newStartDate,
146
+ endDate: newEndDate,
147
+ },
148
+ newRangeType,
149
+ );
150
+ }
151
+ },
152
+ [today, minDate, maxDate, onChange],
153
+ );
154
+
155
+ // Generate recent quarters for quick picks
156
+ const recentQuarters = useMemo(() => {
157
+ const quarters: string[] = [];
158
+ let currentDate = today;
159
+ for (let i = 0; i < 3; i++) {
160
+ const quarterInfo =
161
+ i === 0
162
+ ? getCurrentQuarter(currentDate)
163
+ : getPreviousQuarter(currentDate);
164
+ quarters.push(`Q${quarterInfo.quarter + 1} ${quarterInfo.year}`);
165
+ // Move to the previous quarter for next iteration
166
+ currentDate = quarterInfo.start;
167
+ }
168
+ return quarters;
169
+ }, [today]);
170
+
171
+ return (
172
+ <Stack gap="md">
173
+ <Text variant="caption2.stronger" c="text.subdued.default">
174
+ Quick Picks
175
+ </Text>
176
+ {isRangeTypeAllowed('monthly') && (
177
+ <>
178
+ <Text
179
+ variant="caption1.strong"
180
+ style={{ cursor: 'pointer' }}
181
+ onClick={() => handleQuickPick('thisMonth')}
182
+ >
183
+ This Month
184
+ </Text>
185
+ <Text
186
+ variant="caption1.strong"
187
+ style={{ cursor: 'pointer' }}
188
+ onClick={() => handleQuickPick('lastMonth')}
189
+ >
190
+ Last Month
191
+ </Text>
192
+ </>
193
+ )}
194
+ {isRangeTypeAllowed('quarterly') && (
195
+ <>
196
+ <Text
197
+ variant="caption1.strong"
198
+ style={{ cursor: 'pointer' }}
199
+ onClick={() => handleQuickPick('thisQuarter')}
200
+ >
201
+ This Quarter
202
+ </Text>
203
+ <Text
204
+ variant="caption1.strong"
205
+ style={{ cursor: 'pointer' }}
206
+ onClick={() => handleQuickPick('lastQuarter')}
207
+ >
208
+ Last Quarter
209
+ </Text>
210
+ {recentQuarters.map(quarter => (
211
+ <Text
212
+ key={quarter}
213
+ variant="caption1.strong"
214
+ style={{ cursor: 'pointer' }}
215
+ onClick={() => handleQuickPick(quarter)}
216
+ >
217
+ {quarter}
218
+ </Text>
219
+ ))}
220
+ </>
221
+ )}
222
+ {isRangeTypeAllowed('yearly') && (
223
+ <>
224
+ <Text
225
+ variant="caption1.strong"
226
+ style={{ cursor: 'pointer' }}
227
+ onClick={() => handleQuickPick('lastYear')}
228
+ >
229
+ Last Year
230
+ </Text>
231
+ <Text
232
+ variant="caption1.strong"
233
+ style={{ cursor: 'pointer' }}
234
+ onClick={() => handleQuickPick('thisYear')}
235
+ >
236
+ This Year
237
+ </Text>
238
+ </>
239
+ )}
240
+ </Stack>
241
+ );
242
+ }
@@ -0,0 +1,171 @@
1
+ import { useMemo } from 'react';
2
+
3
+ import { Box, Card, Flex, Group, Stack } from '@mantine/core';
4
+ import type { DateValue } from '@mantine/dates';
5
+
6
+ import {
7
+ createDate,
8
+ endOfYear,
9
+ getCurrentDate,
10
+ getYear,
11
+ isAfter,
12
+ isBefore,
13
+ isSameDay,
14
+ startOfYear,
15
+ toDate,
16
+ } from '@scalepad/ui-utils/date';
17
+
18
+ import { Text } from '../Typography';
19
+
20
+ import type { DateRange } from './types';
21
+
22
+ interface YearlyRangePickerProps {
23
+ startDate: DateValue;
24
+ endDate: DateValue;
25
+ minDate?: DateValue;
26
+ maxDate: DateValue; // Required, should not be null
27
+ onChange?: (range: DateRange) => void;
28
+ }
29
+
30
+ export function YearlyRangePicker({
31
+ startDate,
32
+ endDate,
33
+ minDate,
34
+ maxDate,
35
+ onChange,
36
+ }: YearlyRangePickerProps) {
37
+ const today = getCurrentDate();
38
+ const currentYear = getYear(today);
39
+
40
+ // Generate years for display
41
+ const years = useMemo(() => {
42
+ const yearsList: number[] = [];
43
+ const startYear = minDate ? getYear(minDate) : currentYear - 5;
44
+ const endYear = currentYear;
45
+
46
+ for (let year = endYear; year >= startYear; year--) {
47
+ yearsList.push(year);
48
+ }
49
+
50
+ return yearsList;
51
+ }, [minDate, currentYear]);
52
+
53
+ // Check if a year is the current year
54
+ const isCurrentYear = (year: number) => {
55
+ return year === currentYear;
56
+ };
57
+
58
+ // Handle year selection
59
+ const handleYearSelect = (year: number) => {
60
+ const yearDate = createDate(year, 0, 1);
61
+ const yearStart = startOfYear(yearDate);
62
+ const yearEnd = endOfYear(yearDate);
63
+ // maxDate is required (non-null), safe to convert
64
+ const maxDateObj = maxDate ? toDate(maxDate) : getCurrentDate();
65
+
66
+ let newStartDate = yearStart;
67
+ let newEndDate = yearEnd;
68
+
69
+ // Ensure dates are within bounds
70
+ if (minDate) {
71
+ const minDateObj = toDate(minDate);
72
+ if (isBefore(newStartDate, minDateObj)) {
73
+ newStartDate = minDateObj;
74
+ }
75
+ }
76
+ if (isAfter(newEndDate, maxDateObj)) {
77
+ newEndDate = maxDateObj;
78
+ }
79
+
80
+ onChange?.({
81
+ startDate: newStartDate,
82
+ endDate: newEndDate,
83
+ });
84
+ };
85
+
86
+ // Check if year is selected
87
+ const isYearSelected = (year: number) => {
88
+ if (!startDate || !endDate) {
89
+ return false;
90
+ }
91
+ const yearDate = createDate(year, 0, 1);
92
+ const yearStart = startOfYear(yearDate);
93
+ const yearEnd = endOfYear(yearDate);
94
+ // Check if the selected range matches this year
95
+ return (
96
+ (isBefore(startDate, yearStart) || isSameDay(startDate, yearStart)) &&
97
+ (isAfter(endDate, yearEnd) ||
98
+ isSameDay(endDate, yearEnd) ||
99
+ (isAfter(endDate, yearStart) && isBefore(endDate, yearEnd)))
100
+ );
101
+ };
102
+
103
+ return (
104
+ <Stack gap="xs">
105
+ <Group gap="md">
106
+ {years.map(year => {
107
+ const isCurrent = isCurrentYear(year);
108
+ const isSelected = isYearSelected(year);
109
+ const yearDate = createDate(year, 0, 1);
110
+ const yearStart = startOfYear(yearDate);
111
+ const yearEnd = endOfYear(yearDate);
112
+ const minDateObj = minDate ? toDate(minDate) : null;
113
+ // Disable if before minDate or if the year hasn't started yet (completely in the future)
114
+ // Allow current year to be selected even if incomplete
115
+ const isDisabled =
116
+ (minDateObj && isBefore(yearEnd, minDateObj)) ||
117
+ isAfter(yearStart, today);
118
+
119
+ return (
120
+ <Card
121
+ key={year}
122
+ p="xs"
123
+ w={103}
124
+ mih={60}
125
+ withBorder
126
+ shadow="none"
127
+ pos="relative"
128
+ style={{
129
+ cursor: isDisabled ? 'not-allowed' : 'pointer',
130
+ opacity: isDisabled ? 0.5 : 1,
131
+ borderColor: isSelected
132
+ ? 'var(--color-stroke-primary-default)'
133
+ : undefined,
134
+ }}
135
+ onClick={() => !isDisabled && handleYearSelect(year)}
136
+ >
137
+ <Flex align="center" justify="center" h="100%">
138
+ <Text
139
+ variant="caption1.stronger"
140
+ c={
141
+ isSelected
142
+ ? 'text.primary.default'
143
+ : isDisabled
144
+ ? 'text.disabled.default'
145
+ : undefined
146
+ }
147
+ >
148
+ {year}
149
+ </Text>
150
+ </Flex>
151
+ {isCurrent && (
152
+ <Box
153
+ pos="absolute"
154
+ bottom={4}
155
+ left="50%"
156
+ w={6}
157
+ h={6}
158
+ style={{
159
+ transform: 'translateX(-50%)',
160
+ borderRadius: '50%',
161
+ backgroundColor: 'var(--color-background-primary-filled)',
162
+ }}
163
+ />
164
+ )}
165
+ </Card>
166
+ );
167
+ })}
168
+ </Group>
169
+ </Stack>
170
+ );
171
+ }
@@ -0,0 +1,7 @@
1
+ export { DateInput } from './DateInput';
2
+ export type { DateInputProps } from './DateInput';
3
+ export { DatePicker } from './DatePicker';
4
+ export type { DatePickerProps } from './DatePicker';
5
+ export { DateNavigator } from './DateNavigator';
6
+ export type { DateNavigatorProps } from './DateNavigator';
7
+ export type { DatePickerGranularity, DateRange } from './types';
@@ -0,0 +1,12 @@
1
+ import type { DateValue } from '@mantine/dates';
2
+
3
+ export interface DateRange {
4
+ startDate: DateValue;
5
+ endDate: DateValue;
6
+ }
7
+
8
+ export type DatePickerGranularity =
9
+ | 'quarterly'
10
+ | 'monthly'
11
+ | 'yearly'
12
+ | 'custom';
@@ -0,0 +1,44 @@
1
+ import {
2
+ Flex as MantineFlex,
3
+ type FlexProps as MantineFlexProps,
4
+ } from '@mantine/core';
5
+
6
+ import {
7
+ createDesignComponent,
8
+ type WithDesignColors,
9
+ } from '../../utils/createDesignComponent';
10
+
11
+ export type FluidGridProps = WithDesignColors<MantineFlexProps> & {
12
+ minColumnWidth: number | string;
13
+ maxColumnWidth?: number | string;
14
+ };
15
+
16
+ const DesignFlex = createDesignComponent<
17
+ MantineFlexProps,
18
+ 'div',
19
+ HTMLDivElement
20
+ >(MantineFlex, 'FluidGrid');
21
+
22
+ function toCssDimension(value: number | string): string {
23
+ return typeof value === 'number' ? `${value}px` : value;
24
+ }
25
+
26
+ export function FluidGrid({
27
+ minColumnWidth,
28
+ maxColumnWidth = '1fr',
29
+ style,
30
+ ...rest
31
+ }: FluidGridProps) {
32
+ return (
33
+ <DesignFlex
34
+ {...rest}
35
+ style={[
36
+ {
37
+ display: 'grid',
38
+ gridTemplateColumns: `repeat(auto-fit, minmax(${toCssDimension(minColumnWidth)}, ${toCssDimension(maxColumnWidth)}))`,
39
+ },
40
+ style,
41
+ ]}
42
+ />
43
+ );
44
+ }
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Design System Interactive Primitives
3
+ *
4
+ * Wrapped versions of Mantine interactive components that use the `color` prop.
5
+ * These restrict `color` to semantic design tokens.
6
+ */
7
+
8
+ import { forwardRef, type ElementRef } from 'react';
9
+
10
+ import {
11
+ createPolymorphicComponent,
12
+ Alert as MantineAlert,
13
+ Avatar as MantineAvatar,
14
+ Divider as MantineDivider,
15
+ Loader as MantineLoader,
16
+ NavLink as MantineNavLink,
17
+ Switch as MantineSwitch,
18
+ type AlertProps as MantineAlertProps,
19
+ type AvatarProps as MantineAvatarProps,
20
+ type DividerProps as MantineDividerProps,
21
+ type LoaderProps as MantineLoaderProps,
22
+ type NavLinkProps as MantineNavLinkProps,
23
+ type SwitchProps as MantineSwitchProps,
24
+ } from '@mantine/core';
25
+
26
+ import {
27
+ resolveColorToken,
28
+ type SemanticColor,
29
+ type StrokeColor,
30
+ } from '../../utils/color-props';
31
+
32
+ // ============ Alert ============
33
+
34
+ export type AlertProps = Omit<MantineAlertProps, 'color'> & {
35
+ /** Alert accent color - accepts semantic color tokens */
36
+ color?: SemanticColor;
37
+ };
38
+
39
+ export const Alert = forwardRef<HTMLDivElement, AlertProps>(
40
+ ({ color, ...rest }, ref) => {
41
+ return (
42
+ <MantineAlert
43
+ ref={ref}
44
+ color={resolveColorToken(color) as string}
45
+ {...rest}
46
+ />
47
+ );
48
+ },
49
+ );
50
+ Alert.displayName = 'Alert';
51
+
52
+ // ============ Avatar ============
53
+
54
+ export type AvatarProps = Omit<MantineAvatarProps, 'color'> & {
55
+ /** Avatar accent color - accepts semantic color tokens */
56
+ color?: SemanticColor;
57
+ };
58
+
59
+ export const Avatar = forwardRef<HTMLDivElement, AvatarProps>(
60
+ ({ color, ...rest }, ref) => {
61
+ return (
62
+ <MantineAvatar
63
+ ref={ref}
64
+ color={resolveColorToken(color) as string}
65
+ {...rest}
66
+ />
67
+ );
68
+ },
69
+ );
70
+ Avatar.displayName = 'Avatar';
71
+
72
+ // ============ Divider ============
73
+
74
+ export type DividerProps = Omit<MantineDividerProps, 'color'> & {
75
+ /** Divider color - accepts semantic stroke tokens */
76
+ color?: StrokeColor;
77
+ };
78
+
79
+ export const Divider = forwardRef<HTMLDivElement, DividerProps>(
80
+ ({ color, ...rest }, ref) => {
81
+ return (
82
+ <MantineDivider
83
+ ref={ref}
84
+ color={resolveColorToken(color) as string}
85
+ {...rest}
86
+ />
87
+ );
88
+ },
89
+ );
90
+ Divider.displayName = 'Divider';
91
+
92
+ // ============ Loader ============
93
+
94
+ export type LoaderProps = Omit<MantineLoaderProps, 'color'> & {
95
+ /** Loader color - accepts semantic color tokens */
96
+ color?: SemanticColor;
97
+ };
98
+
99
+ export const Loader = forwardRef<ElementRef<typeof MantineLoader>, LoaderProps>(
100
+ ({ color, ...rest }, ref) => {
101
+ return (
102
+ <MantineLoader
103
+ ref={ref}
104
+ color={resolveColorToken(color) as string}
105
+ {...rest}
106
+ />
107
+ );
108
+ },
109
+ );
110
+ Loader.displayName = 'Loader';
111
+
112
+ // ============ NavLink ============
113
+
114
+ export type NavLinkProps = Omit<MantineNavLinkProps, 'color'> & {
115
+ /** NavLink accent color - accepts semantic color tokens */
116
+ color?: SemanticColor;
117
+ };
118
+
119
+ const NavLinkBase = forwardRef<HTMLAnchorElement, NavLinkProps>(
120
+ ({ color, ...rest }, ref) => {
121
+ return (
122
+ <MantineNavLink
123
+ ref={ref}
124
+ color={resolveColorToken(color) as string}
125
+ {...rest}
126
+ />
127
+ );
128
+ },
129
+ );
130
+ NavLinkBase.displayName = 'NavLink';
131
+ // Use createPolymorphicComponent to preserve `component` prop support (e.g. component={Link})
132
+ export const NavLink = createPolymorphicComponent<'a', NavLinkProps>(
133
+ NavLinkBase,
134
+ );
135
+
136
+ // ============ Switch ============
137
+
138
+ export type SwitchProps = Omit<MantineSwitchProps, 'color'> & {
139
+ /** Switch accent color - accepts semantic color tokens */
140
+ color?: SemanticColor;
141
+ };
142
+
143
+ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
144
+ ({ color, ...rest }, ref) => {
145
+ return (
146
+ <MantineSwitch
147
+ ref={ref}
148
+ color={resolveColorToken(color) as string}
149
+ {...rest}
150
+ />
151
+ );
152
+ },
153
+ );
154
+ Switch.displayName = 'Switch';
155
+
156
+ // ============ Menu ============
157
+
158
+ /** Wrapped Menu.Item with typed color prop */
159
+ // export type MenuItemProps = Omit<MantineMenuItemProps, 'color'> & {
160
+ // /** Menu item color - accepts semantic color tokens */
161
+ // color?: SemanticColor;
162
+ // };
163
+
164
+ // const _MenuItem = forwardRef<HTMLButtonElement, MenuItemProps>(({ color, ...rest }, ref) => {
165
+ // return <MantineMenu.Item ref={ref} color={'black'} {...rest} />;
166
+ // });
167
+ // _MenuItem.displayName = 'MenuItem';
168
+ // const WrappedMenuItem = createPolymorphicComponent<'button', MenuItemProps>(_MenuItem);
169
+
170
+ // /**
171
+ // * Menu with typed Menu.Item color prop.
172
+ // * All other sub-components (Target, Dropdown, Label, Divider) pass through from Mantine.
173
+ // */
174
+ // export const Menu: typeof MantineMenu & { Item: typeof WrappedMenuItem } = Object.assign(
175
+ // MantineMenu,
176
+ // { Item: WrappedMenuItem }
177
+ // ) as typeof MantineMenu & { Item: typeof WrappedMenuItem };