@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,70 @@
1
+ /**
2
+ * StatCard component styles – vanilla-extract with semantic design tokens
3
+ */
4
+
5
+ import { style } from '@vanilla-extract/css';
6
+
7
+ import { tokens } from '../../theme/themeContract.css';
8
+
9
+ export const variantDefault = style({
10
+ backgroundColor: tokens.color.background.default,
11
+ borderColor: tokens.color.stroke.default,
12
+ });
13
+
14
+ export const variantInformation = style({
15
+ backgroundColor: tokens.color.background.informationLight,
16
+ });
17
+
18
+ export const variantSuccess = style({
19
+ backgroundColor: tokens.color.background.successLight,
20
+ });
21
+
22
+ export const variantDanger = style({
23
+ backgroundColor: tokens.color.background.dangerLight,
24
+ });
25
+
26
+ export const variantWarning = style({
27
+ backgroundColor: tokens.color.background.warningLight,
28
+ });
29
+
30
+ export const variantSubdued = style({
31
+ backgroundColor: tokens.color.background.subduedLight,
32
+ });
33
+
34
+ export const iconContainer = style({
35
+ borderRadius: tokens.radius.full,
36
+ display: 'flex',
37
+ alignItems: 'center',
38
+ justifyContent: 'center',
39
+ flexShrink: 0,
40
+ });
41
+
42
+ export const iconDefault = style({
43
+ backgroundColor: tokens.color.background.subduedLight,
44
+ color: tokens.color.icon.default,
45
+ });
46
+
47
+ export const iconInformation = style({
48
+ backgroundColor: tokens.color.background.informationLightHover,
49
+ color: tokens.color.icon.informationStrong,
50
+ });
51
+
52
+ export const iconSuccess = style({
53
+ backgroundColor: tokens.color.background.successLightHover,
54
+ color: tokens.color.icon.successStrong,
55
+ });
56
+
57
+ export const iconDanger = style({
58
+ backgroundColor: tokens.color.background.dangerLightHover,
59
+ color: tokens.color.icon.dangerStrong,
60
+ });
61
+
62
+ export const iconWarning = style({
63
+ backgroundColor: tokens.color.background.warningLightHover,
64
+ color: tokens.color.icon.warningStrong,
65
+ });
66
+
67
+ export const iconSubdued = style({
68
+ backgroundColor: tokens.color.background.subduedLightHover,
69
+ color: tokens.color.icon.default,
70
+ });
@@ -0,0 +1,201 @@
1
+ import type { ComponentType, ReactNode } from 'react';
2
+
3
+ import { clsx } from 'clsx';
4
+
5
+ import { Box, Card, Group, Stack } from '../DesignSystemPrimitives';
6
+ import { Text, Title } from '../Typography';
7
+ import * as classes from './StatCard.css';
8
+
9
+ type StatCardVariant =
10
+ | 'default'
11
+ | 'information'
12
+ | 'success'
13
+ | 'danger'
14
+ | 'warning'
15
+ | 'subdued'
16
+ | 'destructive';
17
+ type StatCardLayout = 'left' | 'centered';
18
+ type StatCardIconSize = 'lg' | 'md' | 'sm';
19
+ type SemanticStatCardVariant =
20
+ | 'default'
21
+ | 'information'
22
+ | 'success'
23
+ | 'danger'
24
+ | 'warning'
25
+ | 'subdued';
26
+ type StatCardIconComponent = ComponentType<{ size?: number | string }>;
27
+
28
+ const cardVariantClassNames: Record<SemanticStatCardVariant, string> = {
29
+ default: classes.variantDefault,
30
+ information: classes.variantInformation,
31
+ success: classes.variantSuccess,
32
+ danger: classes.variantDanger,
33
+ warning: classes.variantWarning,
34
+ subdued: classes.variantSubdued,
35
+ };
36
+
37
+ const iconVariantClassNames: Record<SemanticStatCardVariant, string> = {
38
+ default: classes.iconDefault,
39
+ information: classes.iconInformation,
40
+ success: classes.iconSuccess,
41
+ danger: classes.iconDanger,
42
+ warning: classes.iconWarning,
43
+ subdued: classes.iconSubdued,
44
+ };
45
+
46
+ const toSemanticVariant = (
47
+ variant: StatCardVariant,
48
+ ): SemanticStatCardVariant => {
49
+ if (variant === 'destructive') {
50
+ return 'danger';
51
+ }
52
+
53
+ return variant;
54
+ };
55
+
56
+ const iconSizePx: Record<StatCardIconSize, number> = {
57
+ lg: 44,
58
+ md: 36,
59
+ sm: 24,
60
+ };
61
+
62
+ const iconGlyphSizePx: Record<StatCardIconSize, number> = {
63
+ lg: 24,
64
+ md: 20,
65
+ sm: 14,
66
+ };
67
+
68
+ export interface StatCardProps {
69
+ /**
70
+ * The title of the statistic
71
+ */
72
+ title: string;
73
+ /**
74
+ * The main value/number to display
75
+ */
76
+ value: string | number;
77
+ /**
78
+ * Optional subtitle or additional information
79
+ */
80
+ subtitle?: string;
81
+ /**
82
+ * Optional footer content rendered beneath the subtitle.
83
+ * Use for trend pills, sparklines, or other supporting UI.
84
+ */
85
+ children?: ReactNode;
86
+ /**
87
+ * Visual variant of the card
88
+ * @default 'default'
89
+ */
90
+ variant?: StatCardVariant;
91
+ /**
92
+ * Card content alignment
93
+ * @default 'left'
94
+ */
95
+ layout?: StatCardLayout;
96
+ /**
97
+ * Icon component to display (for example: icon={Monitor})
98
+ */
99
+ icon?: StatCardIconComponent;
100
+ /**
101
+ * Icon container size
102
+ * @default 'md'
103
+ */
104
+ iconSize?: StatCardIconSize;
105
+ /**
106
+ * Optional small rounded-square swatch rendered before the title. Takes any
107
+ * CSS color value — pass a design-system token value for consistency.
108
+ * Ignored when `icon` is also provided (the icon slot wins).
109
+ */
110
+ accentColor?: string;
111
+ }
112
+
113
+ /**
114
+ * StatCard component
115
+ * Displays a statistic with semantic variants and layout options
116
+ */
117
+ export function StatCard({
118
+ title,
119
+ value,
120
+ subtitle,
121
+ children,
122
+ variant = 'default',
123
+ layout = 'left',
124
+ icon,
125
+ iconSize = 'md',
126
+ accentColor,
127
+ }: StatCardProps) {
128
+ const semanticVariant = toSemanticVariant(variant);
129
+ const cardClassName = cardVariantClassNames[semanticVariant];
130
+ const iconClassName = iconVariantClassNames[semanticVariant];
131
+ const iconContainerSize = iconSizePx[iconSize];
132
+ const iconGlyphSize = iconGlyphSizePx[iconSize];
133
+ const Icon = icon;
134
+
135
+ let withBorder = false;
136
+ if (semanticVariant === 'default') {
137
+ withBorder = true;
138
+ }
139
+
140
+ let align: 'flex-start' | 'center' = 'flex-start';
141
+ let titleGroupJustify: 'flex-start' | 'center' = 'flex-start';
142
+ let textAlign: 'left' | 'center' = 'left';
143
+ if (layout === 'centered') {
144
+ align = 'center';
145
+ titleGroupJustify = 'center';
146
+ textAlign = 'center';
147
+ }
148
+
149
+ return (
150
+ <Card
151
+ padding="lg"
152
+ radius="lg"
153
+ withBorder={withBorder}
154
+ className={cardClassName}
155
+ >
156
+ <Stack gap="sm" align={align}>
157
+ <Group
158
+ gap="sm"
159
+ align="center"
160
+ justify={titleGroupJustify}
161
+ wrap="nowrap"
162
+ >
163
+ {Icon ? (
164
+ <Box
165
+ className={clsx(classes.iconContainer, iconClassName)}
166
+ style={{
167
+ width: `${iconContainerSize}px`,
168
+ height: `${iconContainerSize}px`,
169
+ }}
170
+ >
171
+ <Icon size={iconGlyphSize} />
172
+ </Box>
173
+ ) : accentColor ? (
174
+ <Box
175
+ aria-hidden
176
+ style={{
177
+ width: 10,
178
+ height: 10,
179
+ borderRadius: 2,
180
+ backgroundColor: accentColor,
181
+ flexShrink: 0,
182
+ }}
183
+ />
184
+ ) : null}
185
+ <Text variant="caption1" c="text.subdued.strong" ta={textAlign}>
186
+ {title}
187
+ </Text>
188
+ </Group>
189
+ <Title variant="heading2" order={2} ta={textAlign}>
190
+ {value}
191
+ </Title>
192
+ {subtitle && (
193
+ <Text variant="caption1" c="text.subdued.default" ta={textAlign}>
194
+ {subtitle}
195
+ </Text>
196
+ )}
197
+ {children}
198
+ </Stack>
199
+ </Card>
200
+ );
201
+ }
@@ -0,0 +1 @@
1
+ export { StatCard, type StatCardProps } from './StatCard';
@@ -0,0 +1,70 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ import { Badge, type BadgeProps, type BadgeSize } from '../Badge';
4
+
5
+ export type StatusBadgeTone =
6
+ | 'success'
7
+ | 'warning'
8
+ | 'info'
9
+ | 'destructive'
10
+ | 'neutral';
11
+
12
+ export interface StatusBadgeProps {
13
+ /**
14
+ * Semantic tone of the status. Drives both the Badge variant and the dot color.
15
+ */
16
+ tone: StatusBadgeTone;
17
+ /**
18
+ * Status label to render (e.g. "Active", "In progress").
19
+ */
20
+ children: ReactNode;
21
+ /**
22
+ * Badge size
23
+ * @default 'sm'
24
+ */
25
+ size?: BadgeSize;
26
+ }
27
+
28
+ const toneToVariant: Record<StatusBadgeTone, BadgeProps['variant']> = {
29
+ success: 'success',
30
+ warning: 'warning',
31
+ info: 'info',
32
+ destructive: 'destructive',
33
+ neutral: 'secondary',
34
+ };
35
+
36
+ function StatusDot() {
37
+ return (
38
+ <span
39
+ data-testid="statusbadge-dot"
40
+ aria-hidden="true"
41
+ style={{
42
+ display: 'inline-block',
43
+ width: 6,
44
+ height: 6,
45
+ borderRadius: '50%',
46
+ backgroundColor: 'currentColor',
47
+ }}
48
+ />
49
+ );
50
+ }
51
+
52
+ /**
53
+ * StatusBadge renders a tone-coded pill with a leading dot. Thin wrapper around
54
+ * Badge for the Budgets-style line-item status column (Active / In progress /
55
+ * Planned / Expired / …).
56
+ */
57
+ export function StatusBadge({ tone, children, size = 'sm' }: StatusBadgeProps) {
58
+ return (
59
+ <Badge
60
+ data-testid="statusbadge-root"
61
+ data-variant={toneToVariant[tone]}
62
+ variant={toneToVariant[tone]}
63
+ roundness="round"
64
+ size={size}
65
+ leftSection={<StatusDot />}
66
+ >
67
+ {children}
68
+ </Badge>
69
+ );
70
+ }
@@ -0,0 +1,2 @@
1
+ export { StatusBadge } from './StatusBadge';
2
+ export type { StatusBadgeProps, StatusBadgeTone } from './StatusBadge';
@@ -0,0 +1,67 @@
1
+ import { Box } from '@mantine/core';
2
+
3
+ /**
4
+ * Common status types used across the application
5
+ */
6
+ export type DeviceStatus = 'online' | 'offline' | 'unknown';
7
+ export type DeploymentStatus = 'success' | 'failed';
8
+
9
+ /**
10
+ * Maps common status values to StatusIndicator variants
11
+ * - 'online' | 'success' → 'default'
12
+ * - 'offline' | 'failed' | 'unknown' → 'error'
13
+ */
14
+ export function getStatusVariant(
15
+ status: DeviceStatus | DeploymentStatus,
16
+ ): 'default' | 'error' {
17
+ return status === 'online' || status === 'success' ? 'default' : 'error';
18
+ }
19
+
20
+ export interface StatusIndicatorProps {
21
+ /**
22
+ * The visual variant to display
23
+ * - 'default': Green indicator with light border (positive states)
24
+ * - 'error': Red indicator with light border (negative states)
25
+ */
26
+ variant: 'default' | 'error';
27
+ /**
28
+ * Size of the status indicator in pixels
29
+ * @default 16
30
+ */
31
+ size?: number;
32
+ /**
33
+ * Accessible label for screen readers
34
+ * Required to ensure semantic accuracy in different contexts
35
+ * Examples: 'Online', 'Success', 'Active', 'Healthy', 'Offline', 'Failed', 'Error'
36
+ */
37
+ label: string;
38
+ }
39
+
40
+ /**
41
+ * StatusIndicator component
42
+ * Displays a circular status indicator with color-coded states
43
+ * Matches Figma design with Default and Error variants
44
+ */
45
+ export function StatusIndicator({
46
+ variant,
47
+ size = 16,
48
+ label,
49
+ }: StatusIndicatorProps) {
50
+ // Map variant to colors (matching Figma design tokens)
51
+ const isDefault = variant === 'default';
52
+ const bgColor = isDefault ? 'green.6' : 'red.7';
53
+
54
+ return (
55
+ <Box
56
+ w={size}
57
+ h={size}
58
+ bg={bgColor}
59
+ role="status"
60
+ aria-label={label}
61
+ style={{
62
+ borderRadius: '50%',
63
+ border: '4px solid var(--color-stroke-default)',
64
+ }}
65
+ />
66
+ );
67
+ }
@@ -0,0 +1,6 @@
1
+ export { StatusIndicator, getStatusVariant } from './StatusIndicator';
2
+ export type {
3
+ StatusIndicatorProps,
4
+ DeviceStatus,
5
+ DeploymentStatus,
6
+ } from './StatusIndicator';
@@ -0,0 +1,72 @@
1
+ import { style } from '@vanilla-extract/css';
2
+
3
+ import { tokens } from '../../theme/themeContract.css';
4
+ import { textStyleVariants } from '../../tokens/text-styles';
5
+
6
+ export const root = style({
7
+ display: 'flex',
8
+ alignItems: 'center',
9
+ justifyContent: 'space-between',
10
+ paddingInline: tokens.spacing.sm,
11
+ paddingBlock: tokens.spacing.xs,
12
+ borderBottom: `1px solid ${tokens.color.stroke.default}`,
13
+ gap: tokens.spacing.md,
14
+ flexShrink: 0,
15
+ });
16
+
17
+ export const items = style({
18
+ display: 'flex',
19
+ alignItems: 'center',
20
+ gap: tokens.spacing.md,
21
+ });
22
+
23
+ export const actions = style({
24
+ display: 'flex',
25
+ alignItems: 'center',
26
+ gap: tokens.spacing.xs,
27
+ });
28
+
29
+ const itemBase = style({
30
+ display: 'inline-flex',
31
+ alignItems: 'center',
32
+ gap: tokens.spacing.xs,
33
+ paddingInline: tokens.spacing.xs,
34
+ paddingBlock: tokens.spacing['3xs'],
35
+ borderRadius: tokens.radius.md,
36
+ cursor: 'pointer',
37
+ textDecoration: 'none',
38
+ border: 'none',
39
+ background: 'transparent',
40
+ color: tokens.color.text.subduedDefault,
41
+ ...textStyleVariants['body1.strong'],
42
+ fontFamily: 'inherit',
43
+ transition: 'background-color 150ms ease, color 150ms ease',
44
+ selectors: {
45
+ '&:hover': {
46
+ backgroundColor: tokens.color.background.subduedUltralight,
47
+ color: tokens.color.text.subduedStrong,
48
+ },
49
+ },
50
+ });
51
+
52
+ export const item = itemBase;
53
+
54
+ export const itemActive = style({
55
+ backgroundColor: tokens.color.background.primaryLight,
56
+ color: tokens.color.text.primaryDefault,
57
+ selectors: {
58
+ '&:hover': {
59
+ backgroundColor: tokens.color.background.primaryLightHover,
60
+ color: tokens.color.text.primaryDefault,
61
+ },
62
+ },
63
+ });
64
+
65
+ export const itemIcon = style({
66
+ display: 'inline-flex',
67
+ alignItems: 'center',
68
+ justifyContent: 'center',
69
+ flexShrink: 0,
70
+ width: 20,
71
+ height: 20,
72
+ });
@@ -0,0 +1,104 @@
1
+ import {
2
+ type ComponentPropsWithoutRef,
3
+ type ElementType,
4
+ type ReactNode,
5
+ } from 'react';
6
+
7
+ import { Text } from '../Typography';
8
+ import * as classes from './SubNavigation.css';
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // SubNavigation root
12
+ // ---------------------------------------------------------------------------
13
+
14
+ export interface SubNavigationProps {
15
+ children: ReactNode;
16
+ className?: string;
17
+ }
18
+
19
+ function SubNavigationRoot({ children, className }: SubNavigationProps) {
20
+ return (
21
+ <nav className={`${classes.root}${className ? ` ${className}` : ''}`}>
22
+ {children}
23
+ </nav>
24
+ );
25
+ }
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // SubNavigation.Items – wraps the left-aligned navigation items
29
+ // ---------------------------------------------------------------------------
30
+
31
+ function SubNavigationItems({ children }: { children: ReactNode }) {
32
+ return <div className={classes.items}>{children}</div>;
33
+ }
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // SubNavigation.Actions – wraps trailing actions on the right side
37
+ // ---------------------------------------------------------------------------
38
+
39
+ function SubNavigationActions({ children }: { children: ReactNode }) {
40
+ return <div className={classes.actions}>{children}</div>;
41
+ }
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // SubNavigation.Item – polymorphic (renders as <button> by default,
45
+ // accepts `component` to render as a TanStack Router Link etc.)
46
+ // ---------------------------------------------------------------------------
47
+
48
+ type SubNavigationItemOwnProps = {
49
+ icon?: ReactNode;
50
+ label: string;
51
+ active?: boolean;
52
+ };
53
+
54
+ type PolymorphicProps<C extends ElementType> = SubNavigationItemOwnProps &
55
+ Omit<
56
+ ComponentPropsWithoutRef<C>,
57
+ keyof SubNavigationItemOwnProps | 'component'
58
+ > & {
59
+ component?: C;
60
+ };
61
+
62
+ function SubNavigationItemInner<C extends ElementType = 'button'>({
63
+ component,
64
+ icon,
65
+ label,
66
+ active = false,
67
+ className,
68
+ ...rest
69
+ }: PolymorphicProps<C>) {
70
+ const Component: ElementType = component ?? 'button';
71
+ const combinedClassName = [
72
+ classes.item,
73
+ active ? classes.itemActive : '',
74
+ className ?? '',
75
+ ]
76
+ .filter(Boolean)
77
+ .join(' ');
78
+
79
+ return (
80
+ <Component className={combinedClassName} {...rest}>
81
+ {icon && <span className={classes.itemIcon}>{icon}</span>}
82
+ <Text variant="body1.strong" c="inherit">
83
+ {label}
84
+ </Text>
85
+ </Component>
86
+ );
87
+ }
88
+
89
+ // Typed as a generic function component so callers get inference on `component`
90
+ const SubNavigationItem = SubNavigationItemInner as <
91
+ C extends ElementType = 'button',
92
+ >(
93
+ props: PolymorphicProps<C>,
94
+ ) => ReactNode;
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // Compound export
98
+ // ---------------------------------------------------------------------------
99
+
100
+ export const SubNavigation = Object.assign(SubNavigationRoot, {
101
+ Items: SubNavigationItems,
102
+ Item: SubNavigationItem,
103
+ Actions: SubNavigationActions,
104
+ });
@@ -0,0 +1,2 @@
1
+ export { SubNavigation } from './SubNavigation';
2
+ export type { SubNavigationProps } from './SubNavigation';
@@ -0,0 +1,22 @@
1
+ import { Center, Loader } from '@mantine/core';
2
+
3
+ import { zIndex } from '../tokens';
4
+
5
+ /**
6
+ * Fixed-position loader for Suspense boundaries
7
+ * Always centers on screen regardless of where the Suspense boundary is
8
+ */
9
+ export function SuspenseLoader() {
10
+ return (
11
+ <Center
12
+ pos="fixed"
13
+ top={0}
14
+ left={0}
15
+ right={0}
16
+ bottom={0}
17
+ style={{ zIndex: zIndex.loading }}
18
+ >
19
+ <Loader size="lg" />
20
+ </Center>
21
+ );
22
+ }