@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,253 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ import { Box, Card, Flex } from '@mantine/core';
4
+
5
+ import { tokens } from '../../theme/themeContract.css';
6
+ import { Text } from '../Typography';
7
+
8
+ import type { BodyVariant } from '../../tokens/text-styles';
9
+ import type { BoxProps } from '../DesignSystemPrimitives';
10
+
11
+ type RadioTileTone = 'default' | 'danger' | 'warning' | 'success' | 'subdued';
12
+ type RadioTileVariant = 'default' | 'tone-filled';
13
+
14
+ export interface RadioTileProps {
15
+ label: ReactNode;
16
+ description?: ReactNode;
17
+ checked: boolean;
18
+ onChange?: () => void;
19
+ disabled?: boolean;
20
+ tone?: RadioTileTone;
21
+ variant?: RadioTileVariant;
22
+ /** Overrides label typography; defaults match the original RadioTile styles. */
23
+ labelVariant?: BodyVariant;
24
+ /** Overrides description typography when `description` is set. */
25
+ descriptionVariant?: BodyVariant;
26
+ /** Controls label casing; `uppercase` renders the label in uppercase and also exposes a stable test hook. */
27
+ labelTransform?: 'none' | 'uppercase';
28
+ /** Controls the alignment of the radio tile within its container. */
29
+ h?: BoxProps['h'];
30
+ }
31
+
32
+ /**
33
+ * Selectable radio-style card used for settings choices.
34
+ */
35
+ export function RadioTile({
36
+ label,
37
+ description,
38
+ checked,
39
+ onChange,
40
+ disabled = false,
41
+ tone = 'default',
42
+ variant = 'default',
43
+ labelVariant,
44
+ descriptionVariant,
45
+ labelTransform = 'none',
46
+ h = '100%',
47
+ }: RadioTileProps) {
48
+ const paletteByTone = {
49
+ default: {
50
+ unselectedBackground: tokens.color.background.default,
51
+ unselectedBorder: tokens.color.stroke.default,
52
+ unselectedText: tokens.color.text.default,
53
+ unselectedDescription: tokens.color.text.subduedDefault,
54
+ unselectedIndicatorBorder: tokens.color.stroke.default,
55
+ selectedBackground: tokens.color.background.primaryLight,
56
+ selectedBorder: tokens.color.stroke.primaryDefault,
57
+ selectedText: tokens.color.text.default,
58
+ selectedDescription: tokens.color.text.subduedDefault,
59
+ selectedIndicatorBorder: tokens.color.stroke.primaryDefault,
60
+ selectedDot: tokens.color.background.primaryFilled,
61
+ },
62
+ danger: {
63
+ unselectedBackground: tokens.color.background.dangerLight,
64
+ unselectedBorder: tokens.color.stroke.dangerDefault,
65
+ unselectedText: tokens.color.text.dangerStrong,
66
+ unselectedDescription: tokens.color.text.dangerStrong,
67
+ unselectedIndicatorBorder: tokens.color.stroke.default,
68
+ selectedBackground: tokens.color.background.dangerFilled,
69
+ selectedBorder: tokens.color.stroke.dangerDefault,
70
+ selectedText: tokens.color.text.inverse,
71
+ selectedDescription: tokens.color.text.inverse,
72
+ selectedIndicatorBorder: tokens.color.text.inverse,
73
+ selectedDot: tokens.color.background.dangerFilled,
74
+ },
75
+ warning: {
76
+ unselectedBackground: tokens.color.background.warningLight,
77
+ unselectedBorder: tokens.color.stroke.warningDefault,
78
+ unselectedText: tokens.color.text.warningStrong,
79
+ unselectedDescription: tokens.color.text.warningStrong,
80
+ unselectedIndicatorBorder: tokens.color.stroke.default,
81
+ selectedBackground: tokens.color.background.warningFilled,
82
+ selectedBorder: tokens.color.stroke.warningDefault,
83
+ selectedText: tokens.color.text.inverse,
84
+ selectedDescription: tokens.color.text.inverse,
85
+ selectedIndicatorBorder: tokens.color.text.inverse,
86
+ selectedDot: tokens.color.background.warningFilled,
87
+ },
88
+ success: {
89
+ unselectedBackground: tokens.color.background.successLight,
90
+ unselectedBorder: tokens.color.stroke.primaryDefault,
91
+ unselectedText: tokens.color.text.primaryDefault,
92
+ unselectedDescription: tokens.color.text.primaryDefault,
93
+ unselectedIndicatorBorder: tokens.color.stroke.default,
94
+ selectedBackground: tokens.color.background.successFilled,
95
+ selectedBorder: tokens.color.stroke.primaryDefault,
96
+ selectedText: tokens.color.text.inverse,
97
+ selectedDescription: tokens.color.text.inverse,
98
+ selectedIndicatorBorder: tokens.color.text.inverse,
99
+ selectedDot: tokens.color.background.successFilled,
100
+ },
101
+ subdued: {
102
+ unselectedBackground: tokens.color.background.subduedLight,
103
+ unselectedBorder: tokens.color.stroke.default,
104
+ unselectedText: tokens.color.text.default,
105
+ unselectedDescription: tokens.color.text.default,
106
+ unselectedIndicatorBorder: tokens.color.stroke.default,
107
+ selectedBackground: tokens.color.background.subduedFilled,
108
+ selectedBorder: tokens.color.stroke.default,
109
+ selectedText: tokens.color.text.inverse,
110
+ selectedDescription: tokens.color.text.inverse,
111
+ selectedIndicatorBorder: tokens.color.text.inverse,
112
+ selectedDot: tokens.color.background.subduedFilled,
113
+ },
114
+ } as const;
115
+
116
+ const palette = paletteByTone[tone];
117
+ const isToneFilled = variant === 'tone-filled';
118
+ const borderColor = checked
119
+ ? palette.selectedBorder
120
+ : isToneFilled
121
+ ? palette.unselectedBorder
122
+ : tokens.color.stroke.default;
123
+ const backgroundColor = checked
124
+ ? palette.selectedBackground
125
+ : isToneFilled
126
+ ? palette.unselectedBackground
127
+ : tokens.color.background.default;
128
+ const labelColor = isToneFilled
129
+ ? checked
130
+ ? palette.selectedText
131
+ : palette.unselectedText
132
+ : undefined;
133
+ const descriptionColor = isToneFilled
134
+ ? checked
135
+ ? palette.selectedDescription
136
+ : palette.unselectedDescription
137
+ : checked
138
+ ? palette.selectedDescription
139
+ : undefined;
140
+ const indicatorBorderColor = checked
141
+ ? palette.selectedIndicatorBorder
142
+ : isToneFilled
143
+ ? palette.unselectedIndicatorBorder
144
+ : tokens.color.stroke.default;
145
+
146
+ const labelTextVariant = labelVariant ?? 'caption1.strong';
147
+ const descriptionTextVariant = descriptionVariant ?? 'caption1';
148
+ const isLabelUppercase = labelTransform === 'uppercase';
149
+
150
+ return (
151
+ <Card
152
+ withBorder
153
+ radius="lg"
154
+ p="md"
155
+ role="radio"
156
+ h={h}
157
+ aria-checked={checked}
158
+ aria-disabled={disabled}
159
+ data-variant={variant}
160
+ data-tone={tone}
161
+ data-checked={checked ? 'true' : 'false'}
162
+ {...(isToneFilled ? { 'data-visual-lift': 'none' as const } : {})}
163
+ {...(labelVariant !== undefined
164
+ ? { 'data-label-variant': labelVariant }
165
+ : {})}
166
+ {...(descriptionVariant !== undefined
167
+ ? { 'data-description-variant': descriptionVariant }
168
+ : {})}
169
+ {...(isLabelUppercase
170
+ ? { 'data-label-transform': 'uppercase' as const }
171
+ : {})}
172
+ tabIndex={disabled ? -1 : 0}
173
+ onClick={disabled ? undefined : onChange}
174
+ onKeyDown={
175
+ disabled
176
+ ? undefined
177
+ : event => {
178
+ if (event.key === 'Enter' || event.key === ' ') {
179
+ event.preventDefault();
180
+ onChange?.();
181
+ }
182
+ }
183
+ }
184
+ style={{
185
+ borderColor,
186
+ backgroundColor,
187
+ boxShadow: checked && isToneFilled ? 'none' : undefined,
188
+ cursor: disabled ? 'not-allowed' : 'pointer',
189
+ opacity: disabled ? 0.7 : 1,
190
+ transform: checked && isToneFilled ? 'none' : undefined,
191
+ }}
192
+ >
193
+ <Flex align="flex-start" gap="md">
194
+ <Box
195
+ mt={3}
196
+ w={16}
197
+ h={16}
198
+ style={{
199
+ borderRadius: '9999px',
200
+ border: `1px solid ${indicatorBorderColor}`,
201
+ display: 'flex',
202
+ alignItems: 'center',
203
+ justifyContent: 'center',
204
+ flexShrink: 0,
205
+ backgroundColor: tokens.color.background.default,
206
+ }}
207
+ >
208
+ {checked ? (
209
+ <Box
210
+ w={8}
211
+ h={8}
212
+ style={{
213
+ borderRadius: '9999px',
214
+ backgroundColor: checked && tokens.color.icon.primaryDefault,
215
+ }}
216
+ />
217
+ ) : null}
218
+ </Box>
219
+
220
+ <Flex direction="column" gap={2} style={{ minWidth: 0 }}>
221
+ <Text
222
+ variant={labelTextVariant}
223
+ c={isToneFilled ? undefined : checked ? undefined : 'text.default'}
224
+ style={{
225
+ color: labelColor,
226
+ }}
227
+ >
228
+ {label}
229
+ </Text>
230
+ {description ? (
231
+ <Text
232
+ variant={descriptionTextVariant}
233
+ c={
234
+ isToneFilled
235
+ ? undefined
236
+ : checked
237
+ ? undefined
238
+ : tone === 'default'
239
+ ? 'text.subdued.default'
240
+ : 'text.default'
241
+ }
242
+ style={{
243
+ color: descriptionColor,
244
+ }}
245
+ >
246
+ {description}
247
+ </Text>
248
+ ) : null}
249
+ </Flex>
250
+ </Flex>
251
+ </Card>
252
+ );
253
+ }
@@ -0,0 +1,2 @@
1
+ export { RadioTile } from './RadioTile';
2
+ export type { RadioTileProps } from './RadioTile';
@@ -0,0 +1,69 @@
1
+ import { style } from '@vanilla-extract/css';
2
+
3
+ import { tokens } from '../../theme/themeContract.css';
4
+
5
+ /**
6
+ * Default styling for the canonical `FormattingToolbar`. Matches the slim
7
+ * 26x26-button look Figma calls out for the slash menu and the selection
8
+ * bubble menu — by hosting it here, every rich-text surface (slash editor,
9
+ * save/cancel editor, click-to-edit field) renders the same toolbar
10
+ * affordances by default. Consumers can still pass their own classes via
11
+ * the toolbar's className props when a particular surface needs a custom
12
+ * background (e.g. the bubble menu's pill chrome).
13
+ */
14
+
15
+ export const toolbar = style({
16
+ display: 'flex',
17
+ alignItems: 'center',
18
+ gap: 4,
19
+ padding: '5px 8px',
20
+ borderBottom: `1px solid ${tokens.color.stroke.subduedDefault}`,
21
+ });
22
+
23
+ /**
24
+ * Used by the slash menu when the user has typed a query — the toolbar
25
+ * recedes so attention shifts to the filtered command list below.
26
+ */
27
+ export const toolbarDimmed = style({
28
+ opacity: 0.5,
29
+ });
30
+
31
+ export const toolbarButton = style({
32
+ appearance: 'none',
33
+ display: 'inline-flex',
34
+ alignItems: 'center',
35
+ justifyContent: 'center',
36
+ width: 26,
37
+ height: 26,
38
+ padding: 0,
39
+ border: 'none',
40
+ background: 'transparent',
41
+ borderRadius: tokens.radius.sm,
42
+ color: tokens.color.text.default,
43
+ cursor: 'pointer',
44
+ flexShrink: 0,
45
+ selectors: {
46
+ '&:hover:not(:disabled)': {
47
+ backgroundColor: tokens.color.background.primaryLight,
48
+ },
49
+ '&[data-active="true"]': {
50
+ backgroundColor: tokens.color.background.primaryLight,
51
+ color: tokens.color.text.primaryDefault,
52
+ },
53
+ '&[data-active="true"]:hover:not(:disabled)': {
54
+ backgroundColor: tokens.color.background.primaryLightHover,
55
+ },
56
+ '&:disabled': {
57
+ cursor: 'not-allowed',
58
+ color: tokens.color.text.subduedDefault,
59
+ },
60
+ },
61
+ });
62
+
63
+ export const toolbarSeparator = style({
64
+ width: 1,
65
+ height: 16,
66
+ backgroundColor: tokens.color.stroke.subduedDefault,
67
+ flexShrink: 0,
68
+ margin: `0 ${tokens.spacing['3xs']}`,
69
+ });
@@ -0,0 +1,112 @@
1
+ import { Fragment, type MouseEvent } from 'react';
2
+
3
+ import { type Editor, type Range } from '@tiptap/react';
4
+
5
+ import * as classes from './FormattingToolbar.css';
6
+ import { Tooltip, UnstyledButton } from '../../mantine';
7
+
8
+ import type { FormattingToolbarCommand } from './formattingTypes';
9
+
10
+ export interface FormattingToolbarProps {
11
+ editor: Editor;
12
+ /**
13
+ * When supplied, toolbar buttons delete the matched range before applying
14
+ * formatting. Used by the slash menu to drop the `/<query>` token. Other
15
+ * callers omit it because the formatting target is the user's existing
16
+ * text selection.
17
+ */
18
+ range?: Range;
19
+ commands: FormattingToolbarCommand[];
20
+ /**
21
+ * Render at reduced opacity. The slash menu uses this when the user has
22
+ * typed a filter query so attention shifts to the field list.
23
+ */
24
+ dimmed?: boolean;
25
+ /** Optional override for the wrapper className. */
26
+ className?: string;
27
+ /** Optional override for the per-button className (e.g. bubble menu). */
28
+ buttonClassName?: string;
29
+ /** Optional override for the separator between groups. */
30
+ separatorClassName?: string;
31
+ ariaLabel?: string;
32
+ }
33
+
34
+ /**
35
+ * The canonical formatting toolbar shared by every rich-text surface in
36
+ * `@scalepad/ui`. Renders a horizontal run of `FormattingToolbarCommand`s
37
+ * with a vertical divider whenever the `group` field changes between
38
+ * adjacent commands. Hosting a single component here is what guarantees
39
+ * the editor toolbar, the slash menu's icon row, and the selection bubble
40
+ * menu all surface the same affordances with the same styling.
41
+ *
42
+ * Consumers can override `className`, `buttonClassName`, and
43
+ * `separatorClassName` when they need a custom container chrome (e.g. the
44
+ * bubble menu's pill background); the inner button + separator look stay
45
+ * consistent unless the consumer explicitly overrides them.
46
+ */
47
+ export function FormattingToolbar({
48
+ editor,
49
+ range,
50
+ commands,
51
+ dimmed = false,
52
+ className,
53
+ buttonClassName,
54
+ separatorClassName,
55
+ ariaLabel = 'Formatting',
56
+ }: FormattingToolbarProps) {
57
+ if (commands.length === 0) return null;
58
+
59
+ const wrapperClass = [
60
+ className ?? classes.toolbar,
61
+ dimmed ? classes.toolbarDimmed : null,
62
+ ]
63
+ .filter(Boolean)
64
+ .join(' ');
65
+
66
+ return (
67
+ <div className={wrapperClass} role="toolbar" aria-label={ariaLabel}>
68
+ {commands.map((cmd, i) => {
69
+ const prev = i > 0 ? commands[i - 1] : null;
70
+ const showSep = prev != null && prev.group !== cmd.group;
71
+ const Icon = cmd.icon;
72
+ const active = cmd.isActive(editor);
73
+ return (
74
+ <Fragment key={cmd.id}>
75
+ {showSep ? (
76
+ <span
77
+ aria-hidden
78
+ className={separatorClassName ?? classes.toolbarSeparator}
79
+ />
80
+ ) : null}
81
+ <Tooltip
82
+ label={
83
+ <span>
84
+ {cmd.label}
85
+ {cmd.markdown ? (
86
+ <>
87
+ {' '}
88
+ <kbd>{cmd.markdown}</kbd>
89
+ </>
90
+ ) : null}
91
+ </span>
92
+ }
93
+ withinPortal
94
+ openDelay={250}
95
+ >
96
+ <UnstyledButton
97
+ type="button"
98
+ className={buttonClassName ?? classes.toolbarButton}
99
+ aria-label={cmd.label}
100
+ data-active={active ? 'true' : undefined}
101
+ onMouseDown={(e: MouseEvent) => e.preventDefault()}
102
+ onClick={() => cmd.run({ editor, range })}
103
+ >
104
+ <Icon size={14} />
105
+ </UnstyledButton>
106
+ </Tooltip>
107
+ </Fragment>
108
+ );
109
+ })}
110
+ </div>
111
+ );
112
+ }
@@ -0,0 +1,54 @@
1
+ import { style } from '@vanilla-extract/css';
2
+
3
+ import { tokens } from '../../theme/themeContract.css';
4
+
5
+ export const root = style({
6
+ display: 'inline',
7
+ color: 'inherit',
8
+ });
9
+
10
+ export const empty = style({
11
+ color: tokens.color.text.subduedDefault,
12
+ });
13
+
14
+ export const headingStrong = style({
15
+ fontWeight: 700,
16
+ });
17
+
18
+ export const headingMedium = style({
19
+ fontWeight: 600,
20
+ });
21
+
22
+ export const blockquote = style({
23
+ color: tokens.color.text.subduedStrong,
24
+ });
25
+
26
+ export const code = style({
27
+ fontFamily: 'var(--font-family-monospace)',
28
+ background: tokens.color.background.subduedLight,
29
+ padding: '0 4px',
30
+ borderRadius: tokens.radius.xs,
31
+ fontSize: '0.95em',
32
+ });
33
+
34
+ export const link = style({
35
+ color: tokens.color.text.primaryDefault,
36
+ textDecoration: 'underline',
37
+ });
38
+
39
+ /** Bullet glyph rendered before each list item in inline mode. */
40
+ export const bullet = style({
41
+ marginRight: 6,
42
+ color: tokens.color.text.subduedStrong,
43
+ });
44
+
45
+ /**
46
+ * Visible separator between sibling blocks (paragraphs, headings, lists).
47
+ * The `·` glyph is muted so it reads as punctuation rather than content.
48
+ */
49
+ export const separator = style({
50
+ color: tokens.color.text.subduedDefault,
51
+ margin: '0 8px',
52
+ display: 'inline-block',
53
+ verticalAlign: 'middle',
54
+ });