@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,135 @@
1
+ import React, { Component, type ReactNode } from 'react';
2
+
3
+ import { Alert, Stack } from '@mantine/core';
4
+
5
+ import { Button } from './Button';
6
+ import { Text } from './Typography';
7
+
8
+ export interface ErrorBoundaryProps {
9
+ /**
10
+ * Content to render when no error has occurred
11
+ */
12
+ children: ReactNode;
13
+
14
+ /**
15
+ * Fallback UI to render when an error is caught
16
+ * Can be a ReactNode or a function that receives the error and reset function
17
+ */
18
+ fallback?: ReactNode | ((error: Error, reset: () => void) => ReactNode);
19
+
20
+ /**
21
+ * Optional callback when an error is caught
22
+ */
23
+ onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
24
+
25
+ /**
26
+ * Optional callback when the error boundary is reset
27
+ */
28
+ onReset?: () => void;
29
+ }
30
+
31
+ interface ErrorBoundaryState {
32
+ hasError: boolean;
33
+ error: Error | null;
34
+ }
35
+
36
+ /**
37
+ * Error boundary component that catches JavaScript errors in child components
38
+ *
39
+ * Provides a fallback UI when errors occur and allows recovery via reset.
40
+ * Use this to prevent entire sections from crashing when a component fails.
41
+ *
42
+ * @example Basic usage with default fallback
43
+ * ```tsx
44
+ * <ErrorBoundary>
45
+ * <MyComponent />
46
+ * </ErrorBoundary>
47
+ * ```
48
+ *
49
+ * @example Custom fallback UI
50
+ * ```tsx
51
+ * <ErrorBoundary
52
+ * fallback={(error, reset) => (
53
+ * <Alert color="red">
54
+ * <Text>Failed to load data</Text>
55
+ * <Button onClick={reset}>Retry</Button>
56
+ * </Alert>
57
+ * )}
58
+ * >
59
+ * <MyComponent />
60
+ * </ErrorBoundary>
61
+ * ```
62
+ *
63
+ * @example With error logging
64
+ * ```tsx
65
+ * <ErrorBoundary
66
+ * onError={(error, errorInfo) => {
67
+ * console.error('Caught error:', error, errorInfo);
68
+ * // Send to error tracking service
69
+ * }}
70
+ * >
71
+ * <MyComponent />
72
+ * </ErrorBoundary>
73
+ * ```
74
+ */
75
+ export class ErrorBoundary extends Component<
76
+ ErrorBoundaryProps,
77
+ ErrorBoundaryState
78
+ > {
79
+ constructor(props: ErrorBoundaryProps) {
80
+ super(props);
81
+ this.state = { hasError: false, error: null };
82
+ }
83
+
84
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState {
85
+ return { hasError: true, error };
86
+ }
87
+
88
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
89
+ // Call optional error callback
90
+ this.props.onError?.(error, errorInfo);
91
+
92
+ // Log error to console in development (Vite: import.meta.env.DEV)
93
+ const isDev =
94
+ typeof import.meta !== 'undefined' &&
95
+ (import.meta as { env?: { DEV?: boolean } }).env?.DEV;
96
+ if (isDev) {
97
+ console.error('ErrorBoundary caught an error:', error, errorInfo);
98
+ }
99
+ }
100
+
101
+ resetErrorBoundary = (): void => {
102
+ this.props.onReset?.();
103
+ this.setState({ hasError: false, error: null });
104
+ };
105
+
106
+ render(): ReactNode {
107
+ if (this.state.hasError && this.state.error) {
108
+ // Use custom fallback if provided
109
+ if (this.props.fallback) {
110
+ if (typeof this.props.fallback === 'function') {
111
+ return this.props.fallback(this.state.error, this.resetErrorBoundary);
112
+ }
113
+ return this.props.fallback;
114
+ }
115
+
116
+ // Default fallback UI
117
+ return (
118
+ <Alert color="red" title="Something went wrong">
119
+ <Stack gap="sm">
120
+ <Text variant="caption1">{this.state.error.message}</Text>
121
+ <Button
122
+ size="sm"
123
+ variant="destructive"
124
+ onClick={this.resetErrorBoundary}
125
+ >
126
+ Try Again
127
+ </Button>
128
+ </Stack>
129
+ </Alert>
130
+ );
131
+ }
132
+
133
+ return this.props.children;
134
+ }
135
+ }
@@ -0,0 +1,197 @@
1
+ import type { ElementType, FunctionComponent, ReactNode } from 'react';
2
+
3
+ import { AlertCircle } from 'lucide-react';
4
+
5
+ import { Box, Card, Stack } from '@mantine/core';
6
+
7
+ import { tokens } from '../../theme/themeContract.css';
8
+ import { toCssVar, type BackgroundColorVar } from '../../theme/themeVars';
9
+ import { Button } from '../Button';
10
+ import { Text } from '../Typography';
11
+
12
+ export interface ErrorStateAction {
13
+ label: string;
14
+ onClick?: () => void;
15
+ /** React Router Link component or other polymorphic component */
16
+ component?: ElementType;
17
+ /** Link destination (when using component) */
18
+ to?: string;
19
+ /** Icon to show on the right side of button */
20
+ rightSection?: ReactNode;
21
+ }
22
+
23
+ export interface ErrorStateProps {
24
+ /**
25
+ * The icon to display at the top
26
+ * @default <AlertCircle size={48} />
27
+ */
28
+ icon?: ReactNode;
29
+ /**
30
+ * Icon size (width and height in pixels)
31
+ * @default 48
32
+ */
33
+ iconSize?: number;
34
+ /**
35
+ * The title/heading text
36
+ */
37
+ title?: string;
38
+ /**
39
+ * The description/body text
40
+ */
41
+ description?: string;
42
+ /**
43
+ * Primary action button configuration
44
+ */
45
+ action?: ErrorStateAction;
46
+ /**
47
+ * Secondary action button configuration (ghost variant)
48
+ */
49
+ secondaryAction?: ErrorStateAction;
50
+ /**
51
+ * Maximum width of the content area
52
+ * @default 400
53
+ */
54
+ maxWidth?: number | string;
55
+ /**
56
+ * Minimum height of the error container
57
+ * @default 200
58
+ */
59
+ minHeight?: number;
60
+ /**
61
+ * Wrap content in a Card with background and border
62
+ * @default true
63
+ */
64
+ showCard?: boolean;
65
+ /**
66
+ * Card background (theme contract background token). Only used when showCard=true.
67
+ * Pass tokens.color.background.* from @scalepad/ui.
68
+ */
69
+ cardBg?: BackgroundColorVar;
70
+ }
71
+
72
+ /**
73
+ * ErrorState component
74
+ * Displays a contained error message without breaking the page layout
75
+ * Similar API to EmptyState for consistency
76
+ *
77
+ * @example
78
+ * // Simple error state
79
+ * <ErrorState
80
+ * title="Failed to load data"
81
+ * description="An error occurred while fetching the data"
82
+ * action={{ label: 'Try Again', onClick: retry }}
83
+ * />
84
+ *
85
+ * @example
86
+ * // Compact error with custom icon
87
+ * <ErrorState
88
+ * icon={<XCircle size={32} />}
89
+ * iconSize={32}
90
+ * title="Connection failed"
91
+ * minHeight={120}
92
+ * />
93
+ */
94
+ export function ErrorState({
95
+ icon = <AlertCircle size={48} />,
96
+ iconSize = 48,
97
+ title,
98
+ description,
99
+ action,
100
+ secondaryAction,
101
+ maxWidth = 400,
102
+ minHeight = 200,
103
+ showCard = true,
104
+ cardBg = tokens.color.background.subduedLight,
105
+ }: ErrorStateProps) {
106
+ const iconElement = (
107
+ <Box
108
+ w={iconSize}
109
+ h={iconSize}
110
+ display="flex"
111
+ style={{
112
+ alignItems: 'center',
113
+ justifyContent: 'center',
114
+ color: toCssVar(tokens.color.icon.dangerDefault),
115
+ }}
116
+ >
117
+ {icon}
118
+ </Box>
119
+ );
120
+
121
+ const content = (
122
+ <Stack gap="md" align="center">
123
+ {iconElement}
124
+
125
+ <Stack gap="xs" align="center" maw={maxWidth}>
126
+ {title && (
127
+ <Text variant="body1.stronger" ta="center">
128
+ {title}
129
+ </Text>
130
+ )}
131
+ {description && (
132
+ <Text variant="caption1" c="text.subdued.default" ta="center">
133
+ {description}
134
+ </Text>
135
+ )}
136
+ </Stack>
137
+
138
+ {action && (
139
+ <Button
140
+ variant="outline"
141
+ onClick={action.onClick}
142
+ component={
143
+ action.component as
144
+ | FunctionComponent<Record<string, unknown>>
145
+ | undefined
146
+ }
147
+ to={action.to}
148
+ rightSection={action.rightSection}
149
+ radius="lg"
150
+ size="sm"
151
+ mt="xs"
152
+ >
153
+ {action.label}
154
+ </Button>
155
+ )}
156
+
157
+ {secondaryAction && (
158
+ <Button
159
+ variant="ghost"
160
+ color="dark"
161
+ onClick={secondaryAction.onClick}
162
+ component={
163
+ secondaryAction.component as
164
+ | FunctionComponent<Record<string, unknown>>
165
+ | undefined
166
+ }
167
+ to={secondaryAction.to}
168
+ rightSection={secondaryAction.rightSection}
169
+ radius="lg"
170
+ size="sm"
171
+ >
172
+ {secondaryAction.label}
173
+ </Button>
174
+ )}
175
+ </Stack>
176
+ );
177
+
178
+ if (showCard) {
179
+ return (
180
+ <Card
181
+ p="xl"
182
+ bg={cardBg != null ? toCssVar(cardBg) : undefined}
183
+ bd="1px solid var(--color-general-border)"
184
+ radius="lg"
185
+ style={{ minHeight }}
186
+ >
187
+ {content}
188
+ </Card>
189
+ );
190
+ }
191
+
192
+ return (
193
+ <Box py="2xl" maw={maxWidth} mx="auto" style={{ minHeight }}>
194
+ {content}
195
+ </Box>
196
+ );
197
+ }
@@ -0,0 +1,2 @@
1
+ export { ErrorState } from './ErrorState';
2
+ export type { ErrorStateAction, ErrorStateProps } from './ErrorState';
@@ -0,0 +1,42 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ import { Card, Flex, ThemeIcon } from '@mantine/core';
4
+
5
+ import { Text, Title } from './Typography';
6
+
7
+ export interface FeatureCardProps {
8
+ title: string;
9
+ description: string;
10
+ icon: ReactNode;
11
+ color?: string;
12
+ }
13
+
14
+ /**
15
+ * FeatureCard component for showcasing features
16
+ * Displays an icon, title, and description
17
+ */
18
+ export function FeatureCard({
19
+ title,
20
+ description,
21
+ icon,
22
+ color = 'blue',
23
+ }: FeatureCardProps) {
24
+ return (
25
+ <Card shadow="sm" padding="lg" radius="md" withBorder>
26
+ <Flex direction="column" gap="md">
27
+ <ThemeIcon size="xl" radius="md" variant="light" color={color}>
28
+ {icon}
29
+ </ThemeIcon>
30
+
31
+ <Flex direction="column">
32
+ <Title variant="heading4" mb="xs">
33
+ {title}
34
+ </Title>
35
+ <Text variant="caption1" c="text.subdued.default">
36
+ {description}
37
+ </Text>
38
+ </Flex>
39
+ </Flex>
40
+ </Card>
41
+ );
42
+ }
@@ -0,0 +1,30 @@
1
+ import figma from '@figma/code-connect';
2
+
3
+ import { FilterMenu } from './FilterMenu';
4
+
5
+ /**
6
+ * -- This file was auto-generated by Code Connect --
7
+ * None of your props could be automatically mapped to Figma properties.
8
+ * You should update the `props` object to include a mapping from your
9
+ * code props to Figma properties, and update the `example` function to
10
+ * return the code example you'd like to see in Figma
11
+ */
12
+
13
+ figma.connect(
14
+ FilterMenu,
15
+ 'https://www.figma.com/design/VCLfybgU3OaUUPrQdBaVmP/LM-Design-System?node-id=2291%3A16391',
16
+ {
17
+ props: {
18
+ // No matching props could be found for these Figma properties:
19
+ // "component": figma.instance('Component'),
20
+ // "spacing": figma.enum('Spacing', {
21
+ // "None": "none",
22
+ // "2px": "2px",
23
+ // "8px": "8px",
24
+ // "16px": "16px",
25
+ // "24px": "24px"
26
+ // })
27
+ },
28
+ example: _props => <FilterMenu filters={[]} />,
29
+ }
30
+ );
@@ -0,0 +1,198 @@
1
+ import { useCallback, useMemo, useState } from 'react';
2
+
3
+ import { ListFilter } from 'lucide-react';
4
+
5
+ import { Flex } from '@mantine/core';
6
+
7
+ import { Button } from '../Button';
8
+ import { Menu, type MenuProps } from '../Menu';
9
+ import { Text } from '../Typography';
10
+ import { defaultFilterSchemas } from './defaultFilterSchemas';
11
+ import { BooleanFilterSubmenu } from './FilterSubMenuTypes/BooleanFilterSubmenu';
12
+ import { SearchableFilterSubmenu } from './FilterSubMenuTypes/SearchableFilterSubmenu';
13
+ import { extractFilterValue, updateFilterCategories } from './helpers';
14
+
15
+ import type { FilterSchema, FilterValue } from './types';
16
+ import type { FilterCategory } from '../AppliedFiltersManagerBar/AppliedFiltersManagerBar';
17
+
18
+ export interface FilterItem {
19
+ id: string;
20
+ name: string;
21
+ }
22
+
23
+ export interface FilterMenuProps {
24
+ /** Current filter categories */
25
+ filters: FilterCategory[];
26
+ /** Callback when filters change */
27
+ onFiltersChange?: (filters: FilterCategory[]) => void;
28
+ /** Filter schemas defining available filter types (defaults to defaultFilterSchemas) */
29
+ filterSchemas?: FilterSchema[];
30
+ /** Whether to show the filter button trigger */
31
+ showButton?: boolean;
32
+ /** Custom button content */
33
+ buttonContent?: React.ReactNode;
34
+ /** Additional Mantine Menu props */
35
+ menuProps?: Partial<MenuProps>;
36
+ }
37
+
38
+ export function FilterMenu({
39
+ filters,
40
+ onFiltersChange,
41
+ filterSchemas = defaultFilterSchemas,
42
+ showButton = true,
43
+ buttonContent,
44
+ menuProps,
45
+ }: FilterMenuProps) {
46
+ const [menuOpened, setMenuOpened] = useState(false);
47
+
48
+ // Extract filter values from FilterCategory[] structure using schemas
49
+ const filterValues = useMemo(() => {
50
+ const values: Record<string, FilterValue> = {};
51
+ filterSchemas.forEach(schema => {
52
+ values[schema.key] = extractFilterValue(schema, filters);
53
+ });
54
+ return values;
55
+ }, [filterSchemas, filters]);
56
+
57
+ // Helper to update FilterCategory[] structure using schemas
58
+ const updateFilters = useCallback(
59
+ (updates: Record<string, FilterValue>) => {
60
+ if (!onFiltersChange) return;
61
+
62
+ const newFilters = updateFilterCategories(
63
+ filterSchemas,
64
+ updates,
65
+ filters,
66
+ );
67
+ onFiltersChange(newFilters);
68
+ },
69
+ [filterSchemas, filters, onFiltersChange],
70
+ );
71
+
72
+ // Create change handlers for each filter schema
73
+ const createChangeHandler = useCallback(
74
+ (schema: FilterSchema) => {
75
+ if (schema.type === 'boolean') {
76
+ return (checked: boolean) => {
77
+ updateFilters({ [schema.key]: checked });
78
+ };
79
+ }
80
+
81
+ if (schema.type === 'multi-select') {
82
+ return (selectedItems: FilterItem[]) => {
83
+ updateFilters({ [schema.key]: selectedItems });
84
+ };
85
+ }
86
+
87
+ // Fallback (should never reach here with proper typing)
88
+ return () => {};
89
+ },
90
+ [updateFilters],
91
+ );
92
+
93
+ const defaultButtonContent = (
94
+ <Flex align="center" gap="xs">
95
+ <ListFilter size={16} />
96
+ <Text variant="caption1">Filter</Text>
97
+ </Flex>
98
+ );
99
+
100
+ return (
101
+ <>
102
+ {showButton && (
103
+ <Menu
104
+ opened={menuOpened}
105
+ onChange={setMenuOpened}
106
+ position="bottom-start"
107
+ width={280}
108
+ {...menuProps}
109
+ >
110
+ <Menu.Target>
111
+ <Button variant="outline" size="sm" radius="lg">
112
+ {buttonContent || defaultButtonContent}
113
+ </Button>
114
+ </Menu.Target>
115
+
116
+ <Menu.Dropdown>
117
+ {filterSchemas.map((schema, index) => {
118
+ const isLast = index === filterSchemas.length - 1;
119
+
120
+ if (
121
+ schema.type === 'boolean' &&
122
+ schema.submenuType === 'boolean'
123
+ ) {
124
+ const checked = (filterValues[schema.key] as boolean) || false;
125
+ const onChange = createChangeHandler(schema) as (
126
+ checked: boolean,
127
+ ) => void;
128
+
129
+ return (
130
+ <div key={schema.key}>
131
+ <BooleanFilterSubmenu
132
+ label={schema.label}
133
+ checked={checked}
134
+ onChange={onChange}
135
+ />
136
+ {!isLast && <Menu.Divider />}
137
+ </div>
138
+ );
139
+ }
140
+
141
+ if (
142
+ schema.type === 'multi-select' &&
143
+ schema.submenuType === 'searchable'
144
+ ) {
145
+ const selectedItems =
146
+ (filterValues[schema.key] as FilterItem[]) || [];
147
+ const onSelectionChange = createChangeHandler(schema) as (
148
+ selectedItems: FilterItem[],
149
+ ) => void;
150
+
151
+ // Show "Clear All" option if showClearAll is true, or if custom onClearAll is provided
152
+ const handleClearAll =
153
+ schema.showClearAll || schema.onClearAll
154
+ ? schema.onClearAll ||
155
+ (() => {
156
+ onSelectionChange([]);
157
+ })
158
+ : undefined;
159
+
160
+ const itemsController = schema.itemsController;
161
+ const isLoading = itemsController?.status === 'loading';
162
+ const isError = itemsController?.status === 'error';
163
+
164
+ return (
165
+ <div key={schema.key}>
166
+ <SearchableFilterSubmenu
167
+ label={schema.label}
168
+ items={schema.items}
169
+ selectedItems={selectedItems}
170
+ onSelectionChange={onSelectionChange}
171
+ onClearAll={handleClearAll}
172
+ placeholder={schema.placeholder}
173
+ showSearch={schema.showSearch}
174
+ emptyMessage={schema.emptyMessage}
175
+ searchMode={schema.search?.mode ?? 'client'}
176
+ onSearchChange={schema.search?.onSearchChange}
177
+ searchDebounceMs={schema.search?.debounceMs}
178
+ isLoading={isLoading}
179
+ isError={isError}
180
+ errorMessage={itemsController?.errorMessage}
181
+ onRetry={itemsController?.retry}
182
+ hasMore={itemsController?.hasMore}
183
+ onLoadMore={itemsController?.loadMore}
184
+ isLoadingMore={itemsController?.isLoadingMore}
185
+ />
186
+ {!isLast && <Menu.Divider />}
187
+ </div>
188
+ );
189
+ }
190
+
191
+ return null;
192
+ })}
193
+ </Menu.Dropdown>
194
+ </Menu>
195
+ )}
196
+ </>
197
+ );
198
+ }
@@ -0,0 +1,46 @@
1
+ import { Checkbox } from '@mantine/core';
2
+
3
+ import { Menu } from '../../Menu';
4
+
5
+ export interface BooleanFilterSubmenuProps {
6
+ /** Label for the submenu (e.g., "Favourites") */
7
+ label: string;
8
+ /** Whether the filter is checked */
9
+ checked: boolean;
10
+ /** Callback when the checkbox toggle changes */
11
+ onChange: (checked: boolean) => void;
12
+ }
13
+
14
+ /**
15
+ * Boolean filter sub-menu: renders a checkbox whose label and input both
16
+ * toggle the value. The containing `Menu.Item` opts out of the default
17
+ * close-on-click so the user can see the checkbox flip state and then
18
+ * dismiss the menu explicitly (click outside or keyboard escape).
19
+ *
20
+ * All toggle events flow through the `Checkbox`'s native `onChange` — we
21
+ * don't also handle the surrounding `Menu.Item` click, because clicking the
22
+ * label would otherwise double-fire and net-cancel the toggle.
23
+ */
24
+ export function BooleanFilterSubmenu({
25
+ label,
26
+ checked,
27
+ onChange,
28
+ }: BooleanFilterSubmenuProps) {
29
+ return (
30
+ <Menu.Sub>
31
+ <Menu.Sub.Target>
32
+ <Menu.Sub.Item>{label}</Menu.Sub.Item>
33
+ </Menu.Sub.Target>
34
+ <Menu.Sub.Dropdown>
35
+ <Menu.Item closeMenuOnClick={false}>
36
+ <Checkbox
37
+ checked={checked}
38
+ onChange={event => onChange(event.currentTarget.checked)}
39
+ size="sm"
40
+ label={label}
41
+ />
42
+ </Menu.Item>
43
+ </Menu.Sub.Dropdown>
44
+ </Menu.Sub>
45
+ );
46
+ }