@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,98 @@
1
+ # TextInput Component
2
+
3
+ A custom TextInput wrapper component that matches the Figma design specifications for input fields in the design system.
4
+
5
+ ## Figma Design Specifications
6
+
7
+ This component implements the following Figma design specifications from node `105:7045`:
8
+
9
+ | Property | Figma Spec | Implementation |
10
+ | --------------------- | ---------------------------------- | ------------------------------------------------------------- |
11
+ | **Border Radius** | `var(--semantic/rounded-lg, 8px)` | `radius="lg"` prop (8px) |
12
+ | **Padding** | `px: 12px, py: 7.5px` | CSS Module: `padding-top/bottom: 7.5px` (px: 12px from Mantine default) |
13
+ | **Shadow** | `0px 1px 2px 0px rgba(0,0,0,0.05)` | CSS Module: `box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.05)` |
14
+ | **Font Size** | `14px (paragraph small)` | `size="sm"` prop (14px) |
15
+ | **Height** | `36px total` | Automatic via padding (21px content + 15px padding) |
16
+ | **Border** | `1px solid var(--general/border)` | CSS Module: `border: 1px solid var(--color-general-border)` |
17
+ | **Background** | `var(--general/input, white)` | CSS Module: `background-color: var(--color-general-input)` |
18
+ | **Placeholder Color** | `var(--general/muted-foreground)` | CSS Module: `color: var(--color-general-muted-foreground)` |
19
+
20
+ ## Usage
21
+
22
+ ```tsx
23
+ import { Search } from 'lucide-react';
24
+
25
+ import { TextInput } from '@acme/ui';
26
+
27
+ function SearchBar() {
28
+ return (
29
+ <TextInput
30
+ placeholder="Search by Device Name or User Name"
31
+ leftSection={<Search size={16} />}
32
+ />
33
+ );
34
+ }
35
+ ```
36
+
37
+ ## Features
38
+
39
+ - ✅ Matches Figma design specifications exactly
40
+ - ✅ Automatic dark mode support
41
+ - ✅ Consistent with design system colors and spacing
42
+ - ✅ Proper focus and disabled states
43
+ - ✅ Supports all standard Mantine TextInput props
44
+ - ✅ TypeScript support with full type safety
45
+
46
+ ## Props
47
+
48
+ This component extends all Mantine `TextInput` props with sensible defaults:
49
+
50
+ - `size`: Defaults to `"sm"` (14px) to match Figma
51
+ - `radius`: Defaults to `"lg"` (8px) to match Figma
52
+ - All other props from `TextInputProps` are supported
53
+
54
+ ## Examples
55
+
56
+ ### Basic Input
57
+
58
+ ```tsx
59
+ <TextInput placeholder="Enter text" />
60
+ ```
61
+
62
+ ### With Icon
63
+
64
+ ```tsx
65
+ <TextInput placeholder="Search" leftSection={<Search size={16} />} />
66
+ ```
67
+
68
+ ### With Label and Error
69
+
70
+ ```tsx
71
+ <TextInput label="Email" placeholder="Enter your email" error="Invalid email address" />
72
+ ```
73
+
74
+ ### Disabled State
75
+
76
+ ```tsx
77
+ <TextInput placeholder="Disabled input" disabled />
78
+ ```
79
+
80
+ ## Design System Integration
81
+
82
+ This component is part of the `@acme/ui` package and follows the same wrapper pattern as other extended components:
83
+
84
+ - **Text** and **Title**: Typography components with semantic font weights
85
+ - **Button**: Button component with Figma design variants
86
+ - **TextInput**: Input component with Figma design specifications (this component)
87
+
88
+ All components use a combination of Mantine props and CSS Modules to achieve pixel-perfect Figma designs while maintaining flexibility.
89
+
90
+ ## Development
91
+
92
+ To view the component in Storybook:
93
+
94
+ ```bash
95
+ pnpm --filter @acme/ui storybook
96
+ ```
97
+
98
+ Navigate to **Components > TextInput** to see all variants and examples.
@@ -0,0 +1,22 @@
1
+ import figma from '@figma/code-connect';
2
+
3
+ import { SearchTextInput } from './SearchTextInput';
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
+ SearchTextInput,
15
+ 'https://www.figma.com/design/VCLfybgU3OaUUPrQdBaVmP/LM-Design-System?node-id=176%3A27332',
16
+ {
17
+ props: {},
18
+ example: _props => (
19
+ <SearchTextInput value="" onChange={() => {}} placeholder="Search" />
20
+ ),
21
+ },
22
+ );
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Search TextInput with search icon and clear button
3
+ * Uses TextInput with a search icon in rightSection when empty;
4
+ * when there is a value, shows an IconButton with X to clear.
5
+ * All other props pass through to TextInput.
6
+ * Controlled: pass value and onChange.
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * // Basic usage (fully controlled)
11
+ * <SearchTextInput
12
+ * placeholder="Search..."
13
+ * value={query}
14
+ * onChange={(e) => setQuery(e.currentTarget.value)}
15
+ * />
16
+ *
17
+ * // With debouncing for table searches
18
+ * <SearchTextInput
19
+ * placeholder="Search..."
20
+ * value={query}
21
+ * onChange={(e) => setQuery(e.currentTarget.value)}
22
+ * debounceMs={300}
23
+ * />
24
+ * ```
25
+ */
26
+
27
+ import {
28
+ forwardRef,
29
+ useCallback,
30
+ useEffect,
31
+ useRef,
32
+ useState,
33
+ type ChangeEvent,
34
+ } from 'react';
35
+
36
+ import { Search, X } from 'lucide-react';
37
+
38
+ import { useDebouncedValue } from '@scalepad/ui-utils/hooks';
39
+
40
+ import { IconButton } from '../IconButton';
41
+ import { TextInput, type TextInputProps } from './TextInput';
42
+
43
+ export interface SearchTextInputProps extends Omit<
44
+ TextInputProps,
45
+ 'rightSection'
46
+ > {
47
+ /**
48
+ * Optional debounce delay in milliseconds
49
+ * When set, internal state is used for instant input feedback
50
+ * and onChange is called with debounced value
51
+ * @default undefined (no debouncing)
52
+ */
53
+ debounceMs?: number;
54
+ }
55
+
56
+ export const SearchTextInput = forwardRef<
57
+ HTMLInputElement,
58
+ SearchTextInputProps
59
+ >(({ value = '', onChange, debounceMs, ...rest }, ref) => {
60
+ const inputRef = useRef<HTMLInputElement>(null);
61
+
62
+ // Use internal state when debouncing, otherwise use controlled value
63
+ const [localValue, setLocalValue] = useState(value);
64
+ const [debouncedValue] = useDebouncedValue(localValue, debounceMs ?? 0);
65
+
66
+ const displayValue = debounceMs !== undefined ? localValue : value;
67
+ const hasValue = String(displayValue ?? '').length > 0;
68
+
69
+ const setRef = useCallback(
70
+ (el: HTMLInputElement | null) => {
71
+ inputRef.current = el;
72
+ if (typeof ref === 'function') {
73
+ ref(el);
74
+ } else if (ref) {
75
+ (ref as React.MutableRefObject<HTMLInputElement | null>).current = el;
76
+ }
77
+ },
78
+ [ref],
79
+ );
80
+
81
+ // Sync debounced value to parent when debouncing is enabled
82
+ useEffect(() => {
83
+ if (debounceMs !== undefined && onChange) {
84
+ const syntheticEvent = {
85
+ currentTarget: { value: debouncedValue },
86
+ target: { value: debouncedValue },
87
+ } as ChangeEvent<HTMLInputElement>;
88
+ onChange(syntheticEvent);
89
+ }
90
+ }, [debouncedValue, debounceMs, onChange]);
91
+
92
+ const handleChange = useCallback(
93
+ (e: ChangeEvent<HTMLInputElement>) => {
94
+ if (debounceMs !== undefined) {
95
+ // With debouncing: update local state, debounced effect will call parent
96
+ setLocalValue(e.currentTarget.value);
97
+ } else {
98
+ // Without debouncing: call parent directly
99
+ onChange?.(e);
100
+ }
101
+ },
102
+ [debounceMs, onChange],
103
+ );
104
+
105
+ const clearInput = useCallback(() => {
106
+ const syntheticEvent = {
107
+ currentTarget: { value: '' },
108
+ target: { value: '' },
109
+ } as ChangeEvent<HTMLInputElement>;
110
+
111
+ if (debounceMs !== undefined) {
112
+ // With debouncing: clear local state, debounced effect will notify parent
113
+ setLocalValue('');
114
+ } else {
115
+ // Without debouncing: notify parent directly
116
+ onChange?.(syntheticEvent);
117
+ }
118
+
119
+ inputRef.current?.focus();
120
+ }, [debounceMs, onChange]);
121
+
122
+ const getRightSection = () => {
123
+ if (hasValue) {
124
+ return (
125
+ <IconButton
126
+ variant="ghost"
127
+ size="xs"
128
+ onClick={clearInput}
129
+ aria-label="Clear search"
130
+ type="button"
131
+ >
132
+ <X size={16} />
133
+ </IconButton>
134
+ );
135
+ }
136
+ return <Search size={16} />;
137
+ };
138
+
139
+ return (
140
+ <TextInput
141
+ ref={setRef}
142
+ value={displayValue}
143
+ onChange={handleChange}
144
+ rightSection={getRightSection()}
145
+ {...rest}
146
+ />
147
+ );
148
+ });
149
+
150
+ SearchTextInput.displayName = 'SearchTextInput';
@@ -0,0 +1,44 @@
1
+ import figma from '@figma/code-connect';
2
+
3
+ import { TextInput } from './TextInput';
4
+
5
+ /**
6
+ * -- This file was auto-generated by Code Connect --
7
+ * `props` includes a mapping from your code props to Figma properties.
8
+ * You should check this is correct, and update the `example` function
9
+ * to return the code example you'd like to see in Figma
10
+ */
11
+
12
+ figma.connect(
13
+ TextInput,
14
+ 'https://www.figma.com/design/VCLfybgU3OaUUPrQdBaVmP/LM-Design-System?node-id=16%3A1738',
15
+ {
16
+ props: {
17
+ // These props were automatically mapped based on your linked code:
18
+ size: figma.enum('Size', {
19
+ Small: 'sm',
20
+ }),
21
+ disabled: figma.enum('State', {
22
+ Disabled: true,
23
+ }),
24
+ readOnly: figma.enum('State', {
25
+ Disabled: true,
26
+ }),
27
+ // No matching props could be found for these Figma properties:
28
+ // "showDecorationLeft": figma.boolean('Show decoration left'),
29
+ // "showDecorationRight": figma.boolean('Show decoration right'),
30
+ // "showCursor": figma.boolean('Show cursor'),
31
+ // "roundness": figma.enum('Roundness', {
32
+ // "Default": "default",
33
+ // "Round": "round"
34
+ // })
35
+ },
36
+ example: props => (
37
+ <TextInput
38
+ size={props.size}
39
+ disabled={props.disabled}
40
+ readOnly={props.readOnly}
41
+ />
42
+ ),
43
+ },
44
+ );
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Extended TextInput Component
3
+ * Wrapper around Mantine TextInput with Figma design specifications
4
+ *
5
+ * @example
6
+ * ```tsx
7
+ * <TextInput
8
+ * placeholder="Search by Device Name"
9
+ * leftSection={<Search size={16} />}
10
+ * />
11
+ * ```
12
+ */
13
+
14
+ import { forwardRef } from 'react';
15
+
16
+ import {
17
+ TextInput as MantineTextInput,
18
+ type TextInputProps as MantineTextInputProps,
19
+ } from '@mantine/core';
20
+
21
+ export interface TextInputProps extends Omit<MantineTextInputProps, 'size'> {
22
+ /** Input size - defaults to 'sm' (14px) to match Figma */
23
+ size?: MantineTextInputProps['size'];
24
+ }
25
+
26
+ export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
27
+ ({ className, size = 'sm', radius = 'lg', ...props }, ref) => {
28
+ return (
29
+ <MantineTextInput
30
+ ref={ref}
31
+ size={size}
32
+ radius={radius}
33
+ classNames={{
34
+ input: className,
35
+ }}
36
+ {...props}
37
+ />
38
+ );
39
+ },
40
+ );
41
+
42
+ TextInput.displayName = 'TextInput';
@@ -0,0 +1,4 @@
1
+ export { SearchTextInput } from './SearchTextInput';
2
+ export { TextInput } from './TextInput';
3
+ export type { SearchTextInputProps } from './SearchTextInput';
4
+ export type { TextInputProps } from './TextInput';
@@ -0,0 +1,28 @@
1
+ import figma from '@figma/code-connect';
2
+
3
+ import { ThemeSwitcher } from './ThemeSwitcher';
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
+ ThemeSwitcher,
15
+ 'https://www.figma.com/design/VCLfybgU3OaUUPrQdBaVmP/LM-Design-System?node-id=16%3A1801',
16
+ {
17
+ props: {
18
+ // No matching props could be found for these Figma properties:
19
+ // "checked": figma.boolean('Checked?'),
20
+ // "state": figma.enum('State', {
21
+ // "Default": "default",
22
+ // "Focus": "focus",
23
+ // "Disabled": "disabled"
24
+ // })
25
+ },
26
+ example: _props => <ThemeSwitcher />,
27
+ },
28
+ );
@@ -0,0 +1,69 @@
1
+ import { Monitor, Moon, Sun } from 'lucide-react';
2
+
3
+ import { Group, useMantineColorScheme } from '@mantine/core';
4
+
5
+ import { IconButton } from './IconButton';
6
+
7
+ import type { ButtonVariants } from './Button/Button';
8
+
9
+ export interface ThemeSwitcherProps {
10
+ /** Size of the action icons */
11
+ size?: string | number;
12
+ /** Variant of the action icons */
13
+ variant?: ButtonVariants;
14
+ }
15
+
16
+ type ColorScheme = 'light' | 'dark' | 'auto';
17
+
18
+ /**
19
+ * ThemeSwitcher component for switching between light, dark, and auto themes
20
+ * Uses ActionIcon components to provide theme selection
21
+ */
22
+ export function ThemeSwitcher({
23
+ size = 'lg',
24
+ variant = 'ghost',
25
+ }: ThemeSwitcherProps) {
26
+ const { setColorScheme, colorScheme } = useMantineColorScheme();
27
+
28
+ const handleThemeChange = (scheme: ColorScheme) => {
29
+ setColorScheme(scheme);
30
+ };
31
+
32
+ return (
33
+ <Group gap="xs">
34
+ <IconButton
35
+ onClick={() => handleThemeChange('light')}
36
+ variant={
37
+ colorScheme === 'light' ? ('primary' as ButtonVariants) : variant
38
+ }
39
+ size={size}
40
+ aria-label="Light theme"
41
+ title="Light theme"
42
+ >
43
+ <Sun size={18} />
44
+ </IconButton>
45
+ <IconButton
46
+ onClick={() => handleThemeChange('dark')}
47
+ variant={
48
+ colorScheme === 'dark' ? ('primary' as ButtonVariants) : variant
49
+ }
50
+ size={size}
51
+ aria-label="Dark theme"
52
+ title="Dark theme"
53
+ >
54
+ <Moon size={18} />
55
+ </IconButton>
56
+ <IconButton
57
+ onClick={() => handleThemeChange('auto')}
58
+ variant={
59
+ colorScheme === 'auto' ? ('primary' as ButtonVariants) : variant
60
+ }
61
+ size={size}
62
+ aria-label="Auto theme"
63
+ title="Auto theme (follows system)"
64
+ >
65
+ <Monitor size={18} />
66
+ </IconButton>
67
+ </Group>
68
+ );
69
+ }
@@ -0,0 +1,76 @@
1
+ import { Minus, TrendingDown, TrendingUp } from 'lucide-react';
2
+
3
+ import { Badge, type BadgeProps, type BadgeSize } from '../Badge';
4
+
5
+ export type TrendDirection = 'up' | 'down' | 'flat';
6
+
7
+ export interface TrendBadgeProps {
8
+ /**
9
+ * Direction of change. Drives both the icon and the default semantic color.
10
+ */
11
+ direction: TrendDirection;
12
+ /**
13
+ * Pre-formatted value, e.g. "12.4%" or "$1.2k". Rendered after the icon.
14
+ */
15
+ value: string;
16
+ /**
17
+ * Optional trailing qualifier, e.g. "YoY", "MoM".
18
+ */
19
+ label?: string;
20
+ /**
21
+ * Invert the semantic mapping — by default up=success, down=destructive.
22
+ * Use invert=true for metrics where a decrease is desirable (e.g. costs).
23
+ */
24
+ invert?: boolean;
25
+ /**
26
+ * Badge size
27
+ * @default 'xs'
28
+ */
29
+ size?: BadgeSize;
30
+ }
31
+
32
+ const ICON_SIZE = 12;
33
+
34
+ function resolveVariant(
35
+ direction: TrendDirection,
36
+ invert: boolean,
37
+ ): BadgeProps['variant'] {
38
+ if (direction === 'flat') return 'secondary';
39
+ const positive = direction === 'up';
40
+ const isGood = invert ? !positive : positive;
41
+ return isGood ? 'success' : 'destructive';
42
+ }
43
+
44
+ function DirectionIcon({ direction }: { direction: TrendDirection }) {
45
+ if (direction === 'up') return <TrendingUp size={ICON_SIZE} />;
46
+ if (direction === 'down') return <TrendingDown size={ICON_SIZE} />;
47
+ return <Minus size={ICON_SIZE} />;
48
+ }
49
+
50
+ /**
51
+ * TrendBadge displays a directional delta — arrow icon + formatted value +
52
+ * optional qualifier — styled by semantic tone. Built on top of Badge so it
53
+ * inherits the design-system color tokens.
54
+ */
55
+ export function TrendBadge({
56
+ direction,
57
+ value,
58
+ label,
59
+ invert = false,
60
+ size = 'xs',
61
+ }: TrendBadgeProps) {
62
+ const variant = resolveVariant(direction, invert);
63
+ const text = label ? `${value} ${label}` : value;
64
+
65
+ return (
66
+ <Badge
67
+ data-testid="trendbadge-root"
68
+ data-variant={variant}
69
+ variant={variant}
70
+ size={size}
71
+ leftSection={<DirectionIcon direction={direction} />}
72
+ >
73
+ {text}
74
+ </Badge>
75
+ );
76
+ }
@@ -0,0 +1,2 @@
1
+ export { TrendBadge } from './TrendBadge';
2
+ export type { TrendBadgeProps, TrendDirection } from './TrendBadge';
@@ -0,0 +1,115 @@
1
+ import { useEffect, useRef, useState, type ReactNode } from 'react';
2
+
3
+ import { Tooltip } from '@mantine/core';
4
+
5
+ import { Text, type TextProps } from './Typography';
6
+
7
+ export interface TruncatedTextProps extends TextProps {
8
+ /**
9
+ * Text content to display
10
+ */
11
+ children: ReactNode;
12
+ /**
13
+ * Tooltip text to show on hover
14
+ * If not provided, uses the children text
15
+ * Set to null to disable tooltip
16
+ */
17
+ tooltip?: string | null;
18
+ /**
19
+ * Whether to always disable the tooltip (overrides overflow detection)
20
+ * Useful when you want conditional tooltips
21
+ * @default false
22
+ */
23
+ disableTooltip?: boolean;
24
+ }
25
+
26
+ /**
27
+ * TruncatedText Component
28
+ *
29
+ * Renders text with truncation (ellipsis) and automatically shows a tooltip
30
+ * ONLY when the text is actually truncated. This provides better UX by avoiding
31
+ * redundant tooltips for fully visible text.
32
+ *
33
+ * @example Basic usage (auto-tooltip only when truncated)
34
+ * ```tsx
35
+ * <TruncatedText>Very long text that will be truncated</TruncatedText>
36
+ * ```
37
+ *
38
+ * @example Custom tooltip
39
+ * ```tsx
40
+ * <TruncatedText tooltip="Full version string">
41
+ * v1.2.3
42
+ * </TruncatedText>
43
+ * ```
44
+ *
45
+ * @example Force disable tooltip
46
+ * ```tsx
47
+ * <TruncatedText disableTooltip={!hasValue}>
48
+ * {value || 'N/A'}
49
+ * </TruncatedText>
50
+ * ```
51
+ *
52
+ * @example With text props
53
+ * ```tsx
54
+ * <TruncatedText variant="caption1.strong" c="text.subdued.default">
55
+ * Long text
56
+ * </TruncatedText>
57
+ * ```
58
+ */
59
+ export function TruncatedText({
60
+ children,
61
+ tooltip,
62
+ disableTooltip = false,
63
+ ...textProps
64
+ }: TruncatedTextProps) {
65
+ const textRef = useRef<HTMLParagraphElement>(null);
66
+ const [isTruncated, setIsTruncated] = useState(false);
67
+
68
+ // Check if text is truncated by comparing scrollWidth with clientWidth
69
+ useEffect(() => {
70
+ const element = textRef.current;
71
+ if (!element) return;
72
+
73
+ const checkTruncation = () => {
74
+ setIsTruncated(element.scrollWidth > element.clientWidth);
75
+ };
76
+
77
+ // Initial check
78
+ checkTruncation();
79
+
80
+ // Use ResizeObserver to watch for element size changes
81
+ // This is more efficient than window resize listener and works for container resizes
82
+ const resizeObserver = new ResizeObserver(checkTruncation);
83
+ resizeObserver.observe(element);
84
+
85
+ return () => {
86
+ resizeObserver.disconnect();
87
+ };
88
+ }, [children]); // Re-check when children change
89
+
90
+ // Use provided tooltip, or fallback to children if it's a string
91
+ const tooltipLabel =
92
+ tooltip !== undefined
93
+ ? tooltip
94
+ : typeof children === 'string'
95
+ ? children
96
+ : null;
97
+
98
+ // Only show tooltip if:
99
+ // 1. Not explicitly disabled
100
+ // 2. Text is actually truncated
101
+ // 3. We have valid tooltip content
102
+ const shouldShowTooltip =
103
+ !disableTooltip &&
104
+ isTruncated &&
105
+ tooltipLabel !== null &&
106
+ Boolean(tooltipLabel);
107
+
108
+ return (
109
+ <Tooltip label={tooltipLabel} disabled={!shouldShowTooltip}>
110
+ <Text ref={textRef} truncate {...textProps}>
111
+ {children}
112
+ </Text>
113
+ </Tooltip>
114
+ );
115
+ }