@recursica/mantine-adapter 0.20.0 → 0.22.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 (81) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/mantine-adapter.cjs +1 -1
  3. package/dist/mantine-adapter.cjs.map +1 -1
  4. package/dist/mantine-adapter.css +1 -1
  5. package/dist/mantine-adapter.js +1624 -1498
  6. package/dist/mantine-adapter.js.map +1 -1
  7. package/dist/src/components/AutoComplete/AutoComplete.d.ts +9 -0
  8. package/dist/src/components/Avatar/Avatar.d.ts +1 -1
  9. package/dist/src/components/Badge/Badge.d.ts +1 -1
  10. package/dist/src/components/Button/Button.d.ts +11 -1
  11. package/dist/src/components/Card/Card.d.ts +1 -1
  12. package/dist/src/components/Flex/Flex.d.ts +1 -1
  13. package/dist/src/components/Link/Link.d.ts +1 -1
  14. package/dist/src/components/SegmentedControl/SegmentedControl.d.ts +6 -1
  15. package/dist/src/components/Text/Text.d.ts +1 -1
  16. package/dist/src/components/Toast/Toast.d.ts +1 -1
  17. package/dist/src/components/index.d.ts +1 -0
  18. package/package.json +5 -2
  19. package/src/GlobalExemptions.modules.css +13 -0
  20. package/src/components/AutoComplete/AUTOCOMPLETE_IMPLEMENTATION_NOTES.md +21 -0
  21. package/src/components/AutoComplete/AutoComplete.module.css +311 -0
  22. package/src/components/AutoComplete/AutoComplete.stories.tsx +187 -0
  23. package/src/components/AutoComplete/AutoComplete.tsx +154 -0
  24. package/src/components/Avatar/Avatar.module.css +40 -36
  25. package/src/components/Badge/Badge.module.css +4 -1
  26. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +3 -10
  27. package/src/components/Button/Button.module.css +220 -57
  28. package/src/components/Button/Button.stories.tsx +68 -0
  29. package/src/components/Button/Button.tsx +42 -3
  30. package/src/components/Button/IMPLEMENTATION_NOTES.md +16 -0
  31. package/src/components/Card/Card.module.css +58 -1
  32. package/src/components/Card/Card.stories.tsx +4 -2
  33. package/src/components/Checkbox/Checkbox.module.css +74 -46
  34. package/src/components/Checkbox/Checkbox.stories.tsx +3 -0
  35. package/src/components/Checkbox/CheckboxGroup.tsx +1 -9
  36. package/src/components/Chip/Chip.module.css +19 -1
  37. package/src/components/DatePicker/DatePicker.module.css +45 -0
  38. package/src/components/Dropdown/Dropdown.module.css +42 -15
  39. package/src/components/Dropdown/Dropdown.stories.tsx +3 -0
  40. package/src/components/Dropdown/Dropdown.tsx +5 -1
  41. package/src/components/FileInput/FileInput.module.css +50 -0
  42. package/src/components/FileUpload/FileUpload.module.css +43 -0
  43. package/src/components/FormControlLayout/FormControlLayout.module.css +12 -2
  44. package/src/components/FormControlWrapper/FormControlWrapper.module.css +66 -0
  45. package/src/components/HoverCard/HoverCard.module.css +12 -5
  46. package/src/components/Label/Label.module.css +11 -3
  47. package/src/components/Link/Link.module.css +8 -0
  48. package/src/components/Menu/Menu.module.css +73 -30
  49. package/src/components/Modal/Modal.module.css +92 -47
  50. package/src/components/NumberInput/NumberInput.module.css +46 -15
  51. package/src/components/NumberInput/NumberInput.tsx +5 -1
  52. package/src/components/Pagination/Pagination.module.css +123 -41
  53. package/src/components/Panel/Panel.module.css +55 -3
  54. package/src/components/Popover/Popover.module.css +6 -4
  55. package/src/components/Radio/Radio.module.css +72 -59
  56. package/src/components/Radio/Radio.stories.tsx +3 -0
  57. package/src/components/Radio/RadioGroup.tsx +1 -8
  58. package/src/components/ReadOnlyField/ReadOnlyField.module.css +14 -0
  59. package/src/components/ReadOnlyField/ReadOnlyField.tsx +16 -1
  60. package/src/components/SegmentedControl/SegmentedControl.module.css +106 -41
  61. package/src/components/SegmentedControl/SegmentedControl.stories.tsx +1 -4
  62. package/src/components/SegmentedControl/SegmentedControl.tsx +18 -4
  63. package/src/components/Slider/Slider.module.css +89 -0
  64. package/src/components/Stepper/Stepper.module.css +53 -2
  65. package/src/components/Switch/Switch.module.css +62 -23
  66. package/src/components/Switch/Switch.stories.tsx +3 -0
  67. package/src/components/Switch/Switch.tsx +1 -0
  68. package/src/components/Switch/SwitchGroup.tsx +3 -10
  69. package/src/components/Tabs/Tabs.module.css +33 -12
  70. package/src/components/TextArea/TextArea.module.css +58 -120
  71. package/src/components/TextArea/TextArea.stories.tsx +3 -0
  72. package/src/components/TextArea/TextArea.tsx +7 -3
  73. package/src/components/TextField/TextField.module.css +46 -15
  74. package/src/components/TextField/TextField.tsx +5 -1
  75. package/src/components/TimePicker/TimePicker.module.css +44 -0
  76. package/src/components/Timeline/Timeline.module.css +6 -0
  77. package/src/components/Toast/Toast.module.css +62 -1
  78. package/src/components/Toast/Toast.tsx +1 -1
  79. package/src/components/Tooltip/Tooltip.module.css +9 -0
  80. package/src/components/TransferList/TransferList.module.css +41 -0
  81. package/src/components/index.ts +1 -0
@@ -1,3 +1,10 @@
1
+ /**
2
+ * IMPLEMENTATION NOTES:
3
+ *
4
+ * - Icon-Only Flexbox Hack: Mantine renders a hidden `.label` span even if `children` is empty.
5
+ * Because we apply `flex: 1` to `.label`, this empty span grows and pushes the `leftSection` (the icon)
6
+ * off-center. To fix this, we apply `display: none` to the label when `data-content="icon-only"`.
7
+ */
1
8
  /* Baseline and Reset */
2
9
  .root {
3
10
  box-sizing: border-box;
@@ -36,15 +43,20 @@
36
43
  }
37
44
 
38
45
  /* Internal Mantine reset & layout logic for Truncation */
39
- .root > * {
46
+ .root > *:not(.loader) {
40
47
  min-width: 0;
41
48
  position: relative;
42
49
  z-index: 1;
43
50
  }
44
51
 
52
+ .loader {
53
+ /* Mantine absolutely positions this; we just need a hook to exclude it from the child reset */
54
+ }
55
+
45
56
  .label {
46
57
  display: flex;
47
58
  align-items: center;
59
+ justify-content: center;
48
60
  min-width: 0;
49
61
  flex: 1 1 0;
50
62
  }
@@ -61,39 +73,114 @@
61
73
 
62
74
  /* ==== SIZES ==== */
63
75
  .root[data-size="default"] {
64
- border-radius: var(
65
- --recursica_ui-kit_components_button_variants_sizes_default_properties_border-radius
66
- );
67
- height: var(
68
- --recursica_ui-kit_components_button_variants_sizes_default_properties_height
69
- );
70
- min-width: var(
71
- --recursica_ui-kit_components_button_variants_sizes_default_properties_min-width
72
- );
76
+ /* Set baseline paddings and min-width using generic tokens as baseline defaults */
73
77
  padding: 0
74
78
  var(
75
79
  --recursica_ui-kit_components_button_variants_sizes_default_properties_horizontal-padding
76
80
  );
77
- }
78
- .root[data-size="default"] .labelText {
79
- max-width: var(
80
- --recursica_ui-kit_components_button_variants_sizes_default_properties_max-label-width
81
+ min-width: var(
82
+ --recursica_ui-kit_components_button_variants_sizes_default_properties_min-width
81
83
  );
84
+
85
+ height: var(
86
+ --recursica_ui-kit_components_button_variants_sizes_default_properties_height
87
+ );
88
+
89
+ .labelText {
90
+ max-width: var(
91
+ --recursica_ui-kit_components_button_variants_sizes_default_properties_max-label-width
92
+ );
93
+ }
94
+
95
+ .iconWrapper {
96
+ width: var(
97
+ --recursica_ui-kit_components_button_variants_sizes_default_properties_icon
98
+ );
99
+ height: var(
100
+ --recursica_ui-kit_components_button_variants_sizes_default_properties_icon
101
+ );
102
+ }
103
+
104
+ .section {
105
+ gap: var(
106
+ --recursica_ui-kit_components_button_variants_sizes_default_properties_icon-text-gap
107
+ );
108
+ }
109
+
110
+ &[data-content="label"] {
111
+ border-radius: var(
112
+ --recursica_ui-kit_components_button_variants_content_label_variants_sizes_default_properties_border-radius
113
+ );
114
+ min-width: var(
115
+ --recursica_ui-kit_components_button_variants_content_label_variants_sizes_default_properties_min-width
116
+ );
117
+ padding: 0
118
+ var(
119
+ --recursica_ui-kit_components_button_variants_content_label_variants_sizes_default_properties_horizontal-padding
120
+ );
121
+ }
122
+
123
+ &[data-content="icon-label"] {
124
+ border-radius: var(
125
+ --recursica_ui-kit_components_button_variants_content_icon-label_variants_sizes_default_properties_border-radius
126
+ );
127
+ min-width: var(
128
+ --recursica_ui-kit_components_button_variants_content_icon-label_variants_sizes_default_properties_min-width
129
+ );
130
+ padding: 0
131
+ var(
132
+ --recursica_ui-kit_components_button_variants_content_icon-label_variants_sizes_default_properties_horizontal-padding
133
+ );
134
+ }
135
+
136
+ &[data-content="icon-only"] {
137
+ border-radius: var(
138
+ --recursica_ui-kit_components_button_variants_content_icon-only_variants_sizes_default_properties_border-radius
139
+ );
140
+ min-width: var(
141
+ --recursica_ui-kit_components_button_variants_content_icon-only_variants_sizes_default_properties_min-width
142
+ );
143
+ padding: 0
144
+ var(
145
+ --recursica_ui-kit_components_button_variants_content_icon-only_variants_sizes_default_properties_horizontal-padding
146
+ );
147
+ }
82
148
  }
149
+
83
150
  .root[data-size="small"] {
84
- border-radius: var(
85
- --recursica_ui-kit_components_button_variants_sizes_small_properties_border-radius
151
+ /* Set baseline paddings and min-width using generic tokens as baseline defaults */
152
+ padding: 0
153
+ var(
154
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_horizontal-padding
155
+ );
156
+ min-width: var(
157
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_min-width
86
158
  );
159
+
87
160
  height: var(
88
161
  --recursica_ui-kit_components_button_variants_sizes_small_properties_height
89
162
  );
90
- min-width: var(
91
- --recursica_ui-kit_components_button_variants_sizes_small_properties_min-width
92
- );
93
- padding: 0
94
- var(
95
- --recursica_ui-kit_components_button_variants_sizes_small_properties_horizontal-padding
163
+
164
+ .labelText {
165
+ max-width: var(
166
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_max-label-width
167
+ );
168
+ }
169
+
170
+ .iconWrapper {
171
+ width: var(
172
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_icon
96
173
  );
174
+ height: var(
175
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_icon
176
+ );
177
+ }
178
+
179
+ .section {
180
+ gap: var(
181
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_icon-text-gap
182
+ );
183
+ }
97
184
 
98
185
  /* Override specific typography for small */
99
186
  font-family: var(
@@ -114,11 +201,75 @@
114
201
  --recursica_ui-kit_components_button_variants_sizes_default_properties_text_font-weight
115
202
  )
116
203
  );
117
- }
118
- .root[data-size="small"] .labelText {
119
- max-width: var(
120
- --recursica_ui-kit_components_button_variants_sizes_small_properties_max-label-width
204
+ font-style: var(
205
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_text_font-style,
206
+ var(
207
+ --recursica_ui-kit_components_button_variants_sizes_default_properties_text_font-style
208
+ )
121
209
  );
210
+ letter-spacing: var(
211
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_text_letter-spacing,
212
+ var(
213
+ --recursica_ui-kit_components_button_variants_sizes_default_properties_text_letter-spacing
214
+ )
215
+ );
216
+ line-height: var(
217
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_text_line-height,
218
+ var(
219
+ --recursica_ui-kit_components_button_variants_sizes_default_properties_text_line-height
220
+ )
221
+ );
222
+ text-decoration: var(
223
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_text_text-decoration,
224
+ var(
225
+ --recursica_ui-kit_components_button_variants_sizes_default_properties_text_text-decoration
226
+ )
227
+ );
228
+ text-transform: var(
229
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_text_text-transform,
230
+ var(
231
+ --recursica_ui-kit_components_button_variants_sizes_default_properties_text_text-transform
232
+ )
233
+ );
234
+
235
+ &[data-content="label"] {
236
+ border-radius: var(
237
+ --recursica_ui-kit_components_button_variants_content_label_variants_sizes_small_properties_border-radius
238
+ );
239
+ min-width: var(
240
+ --recursica_ui-kit_components_button_variants_content_label_variants_sizes_small_properties_min-width
241
+ );
242
+ padding: 0
243
+ var(
244
+ --recursica_ui-kit_components_button_variants_content_label_variants_sizes_small_properties_horizontal-padding
245
+ );
246
+ }
247
+
248
+ &[data-content="icon-label"] {
249
+ border-radius: var(
250
+ --recursica_ui-kit_components_button_variants_content_icon-label_variants_sizes_small_properties_border-radius
251
+ );
252
+ min-width: var(
253
+ --recursica_ui-kit_components_button_variants_content_icon-label_variants_sizes_small_properties_min-width
254
+ );
255
+ padding: 0
256
+ var(
257
+ --recursica_ui-kit_components_button_variants_content_icon-label_variants_sizes_small_properties_horizontal-padding
258
+ );
259
+ }
260
+
261
+ &[data-content="icon-only"] {
262
+ border-radius: var(
263
+ --recursica_ui-kit_components_button_variants_content_icon-only_variants_sizes_small_properties_border-radius
264
+ );
265
+ min-width: var(
266
+ --recursica_ui-kit_components_button_variants_content_icon-only_variants_sizes_small_properties_min-width
267
+ );
268
+ padding: 0
269
+ var(
270
+ --recursica_ui-kit_components_button_variants_content_icon-only_variants_sizes_small_properties_horizontal-padding
271
+ );
272
+ }
122
273
  }
123
274
 
124
275
  /* Icon Resets */
@@ -133,45 +284,22 @@
133
284
  height: 100%;
134
285
  object-fit: contain;
135
286
  }
136
- .root[data-size="default"] .iconWrapper {
137
- width: var(
138
- --recursica_ui-kit_components_button_variants_sizes_default_properties_icon
139
- );
140
- height: var(
141
- --recursica_ui-kit_components_button_variants_sizes_default_properties_icon
142
- );
143
- }
144
- .root[data-size="small"] .iconWrapper {
145
- width: var(
146
- --recursica_ui-kit_components_button_variants_sizes_small_properties_icon
147
- );
148
- height: var(
149
- --recursica_ui-kit_components_button_variants_sizes_small_properties_icon
150
- );
151
- }
152
287
 
153
288
  .section {
154
289
  min-width: 0;
155
290
  }
156
- .root[data-size="default"] .section {
157
- gap: var(
158
- --recursica_ui-kit_components_button_variants_sizes_default_properties_icon-text-gap
159
- );
160
- }
161
- .root[data-size="small"] .section {
162
- gap: var(
163
- --recursica_ui-kit_components_button_variants_sizes_small_properties_icon-text-gap
164
- );
165
- }
166
291
 
167
292
  /* Icon-only logic overrides */
168
- .root[data-icon-only] .section {
293
+ .root[data-content="icon-only"] .label {
294
+ display: none;
295
+ }
296
+ .root[data-content="icon-only"] .section {
169
297
  gap: 0;
170
298
  }
171
- .root[data-icon-only] .section[data-position="left"] {
299
+ .root[data-content="icon-only"] .section[data-position="left"] {
172
300
  margin-inline-end: 0;
173
301
  }
174
- .root[data-icon-only] .section[data-position="right"] {
302
+ .root[data-content="icon-only"] .section[data-position="right"] {
175
303
  margin-inline-start: 0;
176
304
  }
177
305
 
@@ -210,13 +338,21 @@
210
338
  --recursica_ui-kit_components_button_variants_sizes_small_properties_hover-color
211
339
  );
212
340
  }
213
- .root:hover:not(:disabled)::after {
341
+ .root[data-size="default"]:hover:not(:disabled)::after {
214
342
  opacity: var(
215
343
  --recursica_ui-kit_components_button_variants_sizes_default_properties_hover-opacity,
216
344
  0.1
217
345
  );
218
346
  }
219
-
347
+ .root[data-size="small"]:hover:not(:disabled)::after {
348
+ opacity: var(
349
+ --recursica_ui-kit_components_button_variants_sizes_small_properties_hover-opacity,
350
+ var(
351
+ --recursica_ui-kit_components_button_variants_sizes_default_properties_hover-opacity,
352
+ 0.1
353
+ )
354
+ );
355
+ }
220
356
  /* ==== VARIANTS ==== */
221
357
 
222
358
  /* SOLID */
@@ -236,11 +372,20 @@
236
372
  color: var(
237
373
  --recursica_ui-kit_components_button_variants_styles_solid_properties_colors_text
238
374
  );
375
+
376
+ .iconWrapper {
377
+ color: var(
378
+ --recursica_ui-kit_components_button_variants_styles_solid_properties_colors_icon-color
379
+ );
380
+ }
239
381
  }
240
382
  .root[data-variant="solid"]:hover:not(:disabled) {
241
383
  color: var(
242
384
  --recursica_ui-kit_components_button_variants_styles_solid_properties_colors_text-hover
243
385
  );
386
+ box-shadow: var(
387
+ --recursica_ui-kit_components_button_variants_styles_solid_properties_hover-elevation
388
+ );
244
389
  }
245
390
 
246
391
  /* OUTLINE */
@@ -260,11 +405,20 @@
260
405
  color: var(
261
406
  --recursica_ui-kit_components_button_variants_styles_outline_properties_colors_text
262
407
  );
408
+
409
+ .iconWrapper {
410
+ color: var(
411
+ --recursica_ui-kit_components_button_variants_styles_outline_properties_colors_icon-color
412
+ );
413
+ }
263
414
  }
264
415
  .root[data-variant="outline"]:hover:not(:disabled) {
265
416
  color: var(
266
417
  --recursica_ui-kit_components_button_variants_styles_outline_properties_colors_text-hover
267
418
  );
419
+ box-shadow: var(
420
+ --recursica_ui-kit_components_button_variants_styles_outline_properties_hover-elevation
421
+ );
268
422
  }
269
423
 
270
424
  /* TEXT */
@@ -284,9 +438,18 @@
284
438
  color: var(
285
439
  --recursica_ui-kit_components_button_variants_styles_text_properties_colors_text
286
440
  );
441
+
442
+ .iconWrapper {
443
+ color: var(
444
+ --recursica_ui-kit_components_button_variants_styles_text_properties_colors_icon-color
445
+ );
446
+ }
287
447
  }
288
448
  .root[data-variant="text"]:hover:not(:disabled) {
289
449
  color: var(
290
450
  --recursica_ui-kit_components_button_variants_styles_text_properties_colors_text-hover
291
451
  );
452
+ box-shadow: var(
453
+ --recursica_ui-kit_components_button_variants_styles_text_properties_hover-elevation
454
+ );
292
455
  }
@@ -10,6 +10,9 @@ const meta: Meta<ButtonStoryProps> = {
10
10
  component: Button,
11
11
  tags: ["autodocs"],
12
12
  argTypes: {
13
+ disabled: {
14
+ control: "boolean",
15
+ },
13
16
  variant: {
14
17
  control: "select",
15
18
  options: ["solid", "outline", "text"],
@@ -20,6 +23,30 @@ const meta: Meta<ButtonStoryProps> = {
20
23
  options: ["default", "small"],
21
24
  description: "The size of the button",
22
25
  },
26
+ loading: {
27
+ control: "boolean",
28
+ description: "Sets the button to a loading state",
29
+ },
30
+ useRecursicaLoader: {
31
+ control: "boolean",
32
+ description:
33
+ "Use the Recursica Loader component instead of the default Mantine loader",
34
+ },
35
+ loaderVariant: {
36
+ control: "select",
37
+ options: ["oval", "bars", "dots"],
38
+ description: "The visual variant of the Recursica Loader",
39
+ },
40
+ loaderSize: {
41
+ control: "select",
42
+ options: [undefined, "sm", "md", "lg", "small", "default", "large"],
43
+ description: "The size variant for the loader",
44
+ },
45
+ },
46
+ args: {
47
+ useRecursicaLoader: true,
48
+ loaderVariant: "oval",
49
+ loaderSize: undefined,
23
50
  },
24
51
  };
25
52
 
@@ -80,6 +107,30 @@ export const TextWithIcon: Story = {
80
107
  },
81
108
  };
82
109
 
110
+ export const IconOnly: Story = {
111
+ args: {
112
+ variant: "solid",
113
+ size: "default",
114
+ "aria-label": "Search",
115
+ icon: (
116
+ <svg
117
+ xmlns="http://www.w3.org/2000/svg"
118
+ width="24"
119
+ height="24"
120
+ viewBox="0 0 24 24"
121
+ fill="none"
122
+ stroke="currentColor"
123
+ strokeWidth="2"
124
+ strokeLinecap="round"
125
+ strokeLinejoin="round"
126
+ >
127
+ <circle cx="11" cy="11" r="8"></circle>
128
+ <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
129
+ </svg>
130
+ ),
131
+ },
132
+ };
133
+
83
134
  export const LayerOneSolid: Story = {
84
135
  args: {
85
136
  children: "Layer 1 Solid",
@@ -126,3 +177,20 @@ export const TruncatedLabel: Story = {
126
177
  </div>
127
178
  ),
128
179
  };
180
+
181
+ export const Loading: Story = {
182
+ args: {
183
+ children: "Saving Changes",
184
+ variant: "solid",
185
+ size: "default",
186
+ loading: true,
187
+ },
188
+ parameters: {
189
+ docs: {
190
+ description: {
191
+ story:
192
+ "When `loading={true}` is applied, the Button injects the Recursica `<Loader />` component. Per Recursica design rules, placing a Button in a loading state automatically forces the `disabled={true}` state on the underlying element. This ensures the button immediately receives the brand theme disabled opacities without relying solely on semantic logic.",
193
+ },
194
+ },
195
+ },
196
+ };
@@ -1,4 +1,6 @@
1
1
  import React, { forwardRef } from "react";
2
+ import { Loader } from "../Loader/Loader";
3
+ import type { RecursicaLoaderProps } from "../Loader/Loader";
2
4
  import {
3
5
  Button as MantineButton,
4
6
  type ButtonProps as MantineButtonProps,
@@ -11,9 +13,18 @@ import {
11
13
  import styles from "./Button.module.css";
12
14
 
13
15
  export interface RecursicaButtonProps {
16
+ /** The visual style variant of the button */
14
17
  variant?: "solid" | "outline" | "text";
18
+ /** The size of the button */
15
19
  size?: "default" | "small";
20
+ /** An optional icon element to display to the left of the button text. Replaces Mantine's leftSection. */
16
21
  icon?: React.ReactNode;
22
+ /** Which Recursica Loader variant to use */
23
+ loaderVariant?: RecursicaLoaderProps["variant"];
24
+ /** The size variant for the loader */
25
+ loaderSize?: RecursicaLoaderProps["size"];
26
+ /** Whether to use the Recursica loader or fallback to the Mantine loader */
27
+ useRecursicaLoader?: boolean;
17
28
  }
18
29
 
19
30
  export type ButtonProps = RecursicaOverStyled<
@@ -34,6 +45,9 @@ const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
34
45
  icon,
35
46
  children,
36
47
  overStyled = false,
48
+ loaderVariant = "oval",
49
+ loaderSize,
50
+ useRecursicaLoader = true,
37
51
  ...rest
38
52
  },
39
53
  ref,
@@ -57,8 +71,15 @@ const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
57
71
 
58
72
  const hasLeftSection = !!icon || !!restRecord["leftSection"];
59
73
  const hasRightSection = !!restRecord["rightSection"];
60
- const isIconOnly =
61
- (hasLeftSection || hasRightSection) && !hasVisibleChildren(children);
74
+ const hasVisibleText = hasVisibleChildren(children);
75
+ const isIconOnly = (hasLeftSection || hasRightSection) && !hasVisibleText;
76
+
77
+ let contentType = "label";
78
+ if (isIconOnly) {
79
+ contentType = "icon-only";
80
+ } else if (hasLeftSection || hasRightSection) {
81
+ contentType = "icon-label";
82
+ }
62
83
 
63
84
  if (
64
85
  typeof process !== "undefined" &&
@@ -75,6 +96,7 @@ const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
75
96
  root: styles.root,
76
97
  section: styles.section,
77
98
  label: styles.label,
99
+ loader: styles.loader,
78
100
  };
79
101
 
80
102
  const classNamesProp = restRecord.classNames;
@@ -94,6 +116,21 @@ const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
94
116
  ? `${styles.root} ${classNameProp}`
95
117
  : styles.root;
96
118
 
119
+ const userLoaderProps = restRecord.loaderProps as
120
+ | Record<string, any>
121
+ | undefined;
122
+
123
+ const resolvedLoaderSize =
124
+ loaderSize ?? (size === "small" ? "small" : "default");
125
+
126
+ let mergedLoaderProps = userLoaderProps;
127
+ if (useRecursicaLoader) {
128
+ mergedLoaderProps = {
129
+ children: <Loader variant={loaderVariant} size={resolvedLoaderSize} />,
130
+ ...userLoaderProps,
131
+ };
132
+ }
133
+
97
134
  return (
98
135
  <MantineButton
99
136
  ref={ref}
@@ -101,6 +138,7 @@ const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
101
138
  classNames={mergedClassNames}
102
139
  variant={mapVariant[variant]}
103
140
  size={mapSize[size]}
141
+ loaderProps={mergedLoaderProps}
104
142
  leftSection={
105
143
  icon != null ? (
106
144
  <span className={styles.iconWrapper} aria-hidden>
@@ -110,8 +148,9 @@ const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
110
148
  }
111
149
  data-variant={variant}
112
150
  data-size={size}
113
- {...(isIconOnly ? { "data-icon-only": "" } : {})}
151
+ data-content={contentType}
114
152
  {...sanitizedProps}
153
+ disabled={!!restRecord.disabled || !!restRecord.loading}
115
154
  >
116
155
  <span className={styles.labelText}>{children}</span>
117
156
  </MantineButton>
@@ -43,3 +43,19 @@ Mantine's `.mantine-Button-label` flex centering breaks primitive truncation log
43
43
  ## Disabled state: brand theme opacity (implicit)
44
44
 
45
45
  **Decision:** The UI kit enforces global brand theme disabled opacities. The `.root:disabled` logic implicitly overrides visibility locally via `var(--recursica_brand_states_disabled)`.
46
+
47
+ ---
48
+
49
+ ## Loader color contrast
50
+
51
+ **Decision:** When a Button is in a loading state, the `Recursica Loader` component is injected. The `Loader` component strictly defines its own colors and styles per variant, meaning it does not inherit the text color (`currentColor`) from the Button.
52
+
53
+ **Constraint:** This can lead to contrast issues (e.g., a blue dots loader inside a solid blue button). Design has explicitly decided not to address this at the moment. As such, developers using the `loading` prop must be aware that the loader's color is fixed by its internal tokens, not by the button's context.
54
+
55
+ ---
56
+
57
+ ## Loading state enforces disabled state
58
+
59
+ **Decision:** When `loading={true}` is passed to the Button, the component explicitly forces `disabled={true}` natively on the underlying element.
60
+
61
+ **Implementation:** This ensures that loading buttons automatically inherit the brand theme disabled opacities (via the `:disabled` CSS pseudo-class) rather than relying solely on Mantine's native `data-disabled` dataset logic, which may not trigger the strict visual fade required by the Recursica design system.
@@ -1,3 +1,9 @@
1
+ /* HARDCODED VALUES:
2
+ - display: flex on root
3
+ - flex-direction: column
4
+ - gap: section-gap
5
+ */
6
+
1
7
  .root {
2
8
  /* HARDCODE: Map Recursica explicitly into Mantine's variable format so internal components know the margins */
3
9
  --card-padding: var(--recursica_ui-kit_components_card_properties_padding);
@@ -33,7 +39,32 @@
33
39
  border-bottom: var(--recursica_ui-kit_components_card_properties_divider-size)
34
40
  solid
35
41
  var(--recursica_ui-kit_components_card_properties_colors_divider-color);
36
- composes: recursica_brand_typography_h3 from global;
42
+
43
+ /* Direct Figma Typography Mapping */
44
+ font-family: var(
45
+ --recursica_ui-kit_components_card_properties_header-style_fontFamily
46
+ );
47
+ font-size: var(
48
+ --recursica_ui-kit_components_card_properties_header-style_fontSize
49
+ );
50
+ font-style: var(
51
+ --recursica_ui-kit_components_card_properties_header-style_fontStyle
52
+ );
53
+ font-weight: var(
54
+ --recursica_ui-kit_components_card_properties_header-style_fontWeight
55
+ );
56
+ letter-spacing: var(
57
+ --recursica_ui-kit_components_card_properties_header-style_letterSpacing
58
+ );
59
+ line-height: var(
60
+ --recursica_ui-kit_components_card_properties_header-style_lineHeight
61
+ );
62
+ text-transform: var(
63
+ --recursica_ui-kit_components_card_properties_header-style_textCase
64
+ );
65
+ text-decoration: var(
66
+ --recursica_ui-kit_components_card_properties_header-style_textDecoration
67
+ );
37
68
  }
38
69
 
39
70
  .footer {
@@ -58,4 +89,30 @@
58
89
  flex-direction: column;
59
90
  gap: var(--recursica_ui-kit_components_card_properties_vertical-gutter);
60
91
  padding: 0;
92
+
93
+ /* Direct Figma Typography Mapping */
94
+ font-family: var(
95
+ --recursica_ui-kit_components_card_properties_content-style_fontFamily
96
+ );
97
+ font-size: var(
98
+ --recursica_ui-kit_components_card_properties_content-style_fontSize
99
+ );
100
+ font-style: var(
101
+ --recursica_ui-kit_components_card_properties_content-style_fontStyle
102
+ );
103
+ font-weight: var(
104
+ --recursica_ui-kit_components_card_properties_content-style_fontWeight
105
+ );
106
+ letter-spacing: var(
107
+ --recursica_ui-kit_components_card_properties_content-style_letterSpacing
108
+ );
109
+ line-height: var(
110
+ --recursica_ui-kit_components_card_properties_content-style_lineHeight
111
+ );
112
+ text-transform: var(
113
+ --recursica_ui-kit_components_card_properties_content-style_textCase
114
+ );
115
+ text-decoration: var(
116
+ --recursica_ui-kit_components_card_properties_content-style_textDecoration
117
+ );
61
118
  }
@@ -37,7 +37,8 @@ export const Default: Story = {
37
37
  <div
38
38
  style={{
39
39
  padding: "48px",
40
- backgroundColor: "var(--recursica_brand_palettes_neutral_050)",
40
+ backgroundColor:
41
+ "var(--recursica_brand_palettes_neutral_050_color_tone)",
41
42
  }}
42
43
  >
43
44
  <Layer layer={0}>
@@ -72,7 +73,8 @@ export const HeaderlessAndFooterless: Story = {
72
73
  <div
73
74
  style={{
74
75
  padding: "48px",
75
- backgroundColor: "var(--recursica_brand_palettes_neutral_050)",
76
+ backgroundColor:
77
+ "var(--recursica_brand_palettes_neutral_050_color_tone)",
76
78
  }}
77
79
  >
78
80
  <Layer layer={0}>