@recursica/mui-adapter 0.24.0 → 0.26.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 (72) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/index.d.ts +189 -28
  3. package/dist/mui-adapter.cjs +85 -85
  4. package/dist/mui-adapter.cjs.map +1 -1
  5. package/dist/mui-adapter.css +1 -1
  6. package/dist/mui-adapter.js +29694 -24393
  7. package/dist/mui-adapter.js.map +1 -1
  8. package/llms.txt +1 -0
  9. package/package.json +1 -1
  10. package/src/GlobalExemptions.modules.css +0 -6
  11. package/src/components/Accordion/Accordion.module.css +0 -8
  12. package/src/components/AssistiveElement/AssistiveElement.tsx +1 -1
  13. package/src/components/Autocomplete/Autocomplete.module.css +0 -8
  14. package/src/components/Avatar/Avatar.module.css +0 -8
  15. package/src/components/Button/BUTTON_IMPLEMENTATION_NOTES.md +12 -0
  16. package/src/components/Button/Button.module.css +6 -18
  17. package/src/components/Checkbox/Checkbox.tsx +4 -1
  18. package/src/components/Chip/Chip.module.css +0 -27
  19. package/src/components/DatePicker/DATEPICKER_IMPLEMENTATION_NOTES.md +29 -0
  20. package/src/components/DatePicker/DatePicker.icons.tsx +35 -0
  21. package/src/components/DatePicker/DatePicker.module.css +451 -42
  22. package/src/components/DatePicker/DatePicker.stories.tsx +23 -25
  23. package/src/components/DatePicker/DatePicker.tsx +229 -47
  24. package/src/components/DatePicker/USAGE.md +14 -1
  25. package/src/components/Dropdown/Dropdown.module.css +0 -7
  26. package/src/components/FileInput/FileInput.module.css +0 -21
  27. package/src/components/FileInput/FileInput.tsx +6 -0
  28. package/src/components/FileUpload/FileUpload.module.css +0 -11
  29. package/src/components/HoverCard/HoverCard.module.css +1 -7
  30. package/src/components/Label/Label.module.css +0 -6
  31. package/src/components/Link/Link.module.css +0 -13
  32. package/src/components/Menu/Menu.module.css +0 -5
  33. package/src/components/Modal/Modal.module.css +0 -11
  34. package/src/components/NumberInput/NumberInput.module.css +0 -7
  35. package/src/components/Pagination/Pagination.module.css +0 -81
  36. package/src/components/Popover/IMPLEMENTATION_NOTES.md +22 -0
  37. package/src/components/Popover/Popover.module.css +118 -0
  38. package/src/components/Popover/Popover.stories.tsx +133 -0
  39. package/src/components/Popover/Popover.tsx +275 -0
  40. package/src/components/Popover/USAGE.md +69 -0
  41. package/src/components/Popover/index.ts +1 -0
  42. package/src/components/SegmentedControl/IMPLEMENTATION_NOTES.md +6 -0
  43. package/src/components/SegmentedControl/SegmentedControl.module.css +32 -11
  44. package/src/components/SegmentedControl/SegmentedControl.tsx +18 -4
  45. package/src/components/Slider/IMPLEMENTATION_NOTES.md +29 -0
  46. package/src/components/Slider/Slider.module.css +80 -17
  47. package/src/components/Slider/Slider.stories.tsx +1 -1
  48. package/src/components/Slider/Slider.tsx +36 -1
  49. package/src/components/Stepper/IMPLEMENTATION_NOTES.md +54 -0
  50. package/src/components/Stepper/Stepper.module.css +139 -106
  51. package/src/components/Stepper/Stepper.tsx +76 -10
  52. package/src/components/Stepper/USAGE.md +4 -0
  53. package/src/components/Tabs/IMPLEMENTATION_NOTES.md +12 -0
  54. package/src/components/Tabs/Tabs.module.css +107 -24
  55. package/src/components/Tabs/Tabs.tsx +1 -0
  56. package/src/components/TextArea/TextArea.module.css +20 -11
  57. package/src/components/TextArea/TextArea.tsx +12 -23
  58. package/src/components/TextField/TextField.module.css +0 -8
  59. package/src/components/TimePicker/TimePicker.module.css +0 -16
  60. package/src/components/Timeline/IMPLEMENTATION_NOTES.md +24 -3
  61. package/src/components/Timeline/Timeline.module.css +55 -73
  62. package/src/components/Timeline/Timeline.tsx +23 -27
  63. package/src/components/Timeline/TimelineItem.tsx +38 -35
  64. package/src/components/Toast/Toast.module.css +0 -7
  65. package/src/components/Tooltip/Tooltip.module.css +0 -9
  66. package/src/components/TransferList/TRANSFERLIST_IMPLEMENTATION_NOTES.md +140 -0
  67. package/src/components/TransferList/TransferList.module.css +174 -38
  68. package/src/components/TransferList/TransferList.stories.tsx +110 -6
  69. package/src/components/TransferList/TransferList.tsx +417 -8
  70. package/src/components/TransferList/USAGE.md +37 -6
  71. package/src/components/index.ts +1 -0
  72. package/src/index.ts +3 -0
@@ -1,13 +1,26 @@
1
- /*
1
+ /*
2
2
  Recursica Stepper CSS module natively binding to Figma variables.
3
-
4
- HARDCODED CSS NOTE:
5
- For horizontal layout, Mantine natively renders the text horizontally alongside the icon.
6
- To achieve Recursica's layout where horizontal orientation stacks text below the icon,
7
- we force `.step` to be a column, `.stepBody` to remove margin and text-align center,
8
- and the `.separator`'s margin-top is dynamically calculated `calc((indicator-size / 2) - 1px)`
9
- to ensure it continues to align with the vertical center of the indicator rather than the
10
- whole step wrapper.
3
+
4
+ ARCHITECTURE NOTES (see IMPLEMENTATION_NOTES.md for full rationale):
5
+ - Horizontal orientation uses MUI Stepper's own `alternativeLabel` prop (icon on
6
+ top, label centered below, connector absolutely positioned circle-to-circle) —
7
+ this is MUI's real primitive for this exact layout, not a custom column hack.
8
+ - The visible circle/checkmark is rendered by our own `RecursicaStepIcon`
9
+ (`.stepIconCircle` / `.stepCompletedIcon`) instead of MUI's default `StepIcon`,
10
+ which draws its own self-colored (blue) circle+check SVG that never reads
11
+ our tokens.
12
+ - Vertical orientation's connecting line is drawn as a `.stepIcon::after` rail
13
+ (not the `StepConnector` element — see Stepper.tsx) so its length tracks each
14
+ step's actual rendered height, matching mantine's absolutely-positioned
15
+ separator.
16
+
17
+ HARDCODED VALUES:
18
+ - `.stepIconCircle` sets `box-sizing: border-box` so the indicator's border is
19
+ included in `--stepper-indicator-size`, keeping the connector's centering math
20
+ (indicator-size / 2) exact.
21
+ - `--stepper-connector-size` is a local bridge custom property (not a design
22
+ token) used only to let the vertical rail's centering `calc()` reference
23
+ whichever connector-size token is active for the current state.
11
24
  */
12
25
 
13
26
  .root {
@@ -24,21 +37,12 @@
24
37
  );
25
38
  }
26
39
 
27
- .root.horizontal .step {
28
- flex-direction: column;
29
- align-items: center;
30
- gap: var(
31
- --recursica_ui-kit_components_stepper_variants_orientation_horizontal_properties_indicator-label-gap
32
- );
33
- }
34
-
35
40
  .root.horizontal .stepBody {
36
41
  width: var(--stepper-max-text-width);
37
42
  display: flex;
38
43
  flex-direction: column;
39
44
  align-items: center;
40
45
  justify-content: flex-start;
41
- margin-left: 0; /* Mantine natively applies a margin-inline-start */
42
46
  gap: var(
43
47
  --recursica_ui-kit_components_stepper_variants_orientation_horizontal_properties_label-description-gap
44
48
  );
@@ -52,14 +56,41 @@
52
56
  white-space: normal;
53
57
  }
54
58
 
59
+ .root.horizontal .stepLabel {
60
+ /* Replaces MUI's own hardcoded `margin-top: 16px` (applied via its
61
+ `.Mui-alternativeLabel` state class) with the real indicator-label-gap token. */
62
+ margin-top: var(
63
+ --recursica_ui-kit_components_stepper_variants_orientation_horizontal_properties_indicator-label-gap
64
+ );
65
+ }
66
+
55
67
  .root.horizontal .separator {
56
- align-self: flex-start; /* Force to top regardless of container alignment */
57
- margin-top: calc(var(--stepper-indicator-size) / 2 - 1px);
58
- margin-left: calc(
59
- -1 * (var(--stepper-max-text-width) - var(--stepper-indicator-size)) / 2
68
+ /* Root only positions the connector; MUI's own `alternativeLabel` variant
69
+ already sets `position: absolute`. Centering is exact because our custom
70
+ StepIconComponent renders a `--stepper-indicator-size` circle flush at the
71
+ top of `.stepIcon`, centered horizontally within the step. */
72
+ top: calc(var(--stepper-indicator-size) / 2);
73
+ left: -50%;
74
+ right: 50%;
75
+ }
76
+
77
+ .root.horizontal .separatorLine {
78
+ border-top: none; /* replace MUI's default border-top line with a token-driven bar */
79
+ height: var(
80
+ --recursica_ui-kit_components_stepper_properties_upcoming-connector-size
60
81
  );
61
- margin-right: calc(
62
- -1 * (var(--stepper-max-text-width) - var(--stepper-indicator-size)) / 2
82
+ background-color: var(
83
+ --recursica_ui-kit_components_stepper_properties_colors_upcoming-connector-color
84
+ );
85
+ }
86
+
87
+ .root.horizontal .separator:global(.Mui-active) .separatorLine,
88
+ .root.horizontal .separator:global(.Mui-completed) .separatorLine {
89
+ height: var(
90
+ --recursica_ui-kit_components_stepper_properties_completed-connector-size
91
+ );
92
+ background-color: var(
93
+ --recursica_ui-kit_components_stepper_properties_colors_completed-connector-color
63
94
  );
64
95
  }
65
96
 
@@ -68,20 +99,15 @@
68
99
  --stepper-step-gap: var(
69
100
  --recursica_ui-kit_components_stepper_variants_orientation_vertical_properties_step-gap
70
101
  );
71
- --separator-spacing: 0px; /* Force the line to touch the circle */
72
102
  --stepper-max-text-width: var(
73
103
  --recursica_ui-kit_components_stepper_variants_orientation_vertical_properties_max-text-width
74
104
  );
75
105
  }
76
106
 
77
107
  .root.vertical .step {
78
- gap: var(
79
- --recursica_ui-kit_components_stepper_variants_orientation_vertical_properties_indicator-label-gap
80
- );
81
- margin-top: 0 !important; /* Remove Mantine's gap */
82
- padding-bottom: var(
83
- --stepper-step-gap
84
- ); /* Move gap inside the step so the separator line can stretch through it */
108
+ /* Moves the gap inside the step (instead of a margin between steps) so the
109
+ `.stepIcon::after` rail can extend through it into the next step. */
110
+ padding-bottom: var(--stepper-step-gap);
85
111
  }
86
112
 
87
113
  .root.vertical .step:last-of-type {
@@ -94,6 +120,54 @@
94
120
  );
95
121
  }
96
122
 
123
+ .root.vertical .stepLabelRoot {
124
+ /* Lets the icon column (`.stepIcon`) stretch to match the label column's
125
+ actual rendered height (multi-line descriptions included), so the
126
+ connecting rail's length always reaches the next step's icon. */
127
+ align-items: stretch;
128
+ /* Replaces MUI's own hardcoded `padding: 8px 0` for vertical orientation —
129
+ without zeroing this, the rail falls 16px short of the next icon (8px
130
+ here + 8px on the next step's own StepLabelRoot). */
131
+ padding: 0;
132
+ }
133
+
134
+ .root.vertical .stepIcon {
135
+ height: auto;
136
+ /* Replaces MUI's own hardcoded `padding-right: 8px` on the icon container. */
137
+ padding-right: var(
138
+ --recursica_ui-kit_components_stepper_variants_orientation_vertical_properties_indicator-label-gap
139
+ );
140
+ --stepper-connector-size: var(
141
+ --recursica_ui-kit_components_stepper_properties_upcoming-connector-size
142
+ );
143
+ }
144
+
145
+ .root.vertical .step:global(.Mui-completed) .stepIcon {
146
+ --stepper-connector-size: var(
147
+ --recursica_ui-kit_components_stepper_properties_completed-connector-size
148
+ );
149
+ }
150
+
151
+ .root.vertical .step:not(:last-of-type) .stepIcon::after {
152
+ content: "";
153
+ position: absolute;
154
+ top: var(--stepper-indicator-size);
155
+ bottom: calc(-1 * var(--stepper-step-gap));
156
+ left: calc(50% - (var(--stepper-connector-size) / 2));
157
+ width: var(--stepper-connector-size);
158
+ background-color: var(
159
+ --recursica_ui-kit_components_stepper_properties_colors_upcoming-connector-color
160
+ );
161
+ }
162
+
163
+ .root.vertical
164
+ .step:global(.Mui-completed):not(:last-of-type)
165
+ .stepIcon::after {
166
+ background-color: var(
167
+ --recursica_ui-kit_components_stepper_properties_colors_completed-connector-color
168
+ );
169
+ }
170
+
97
171
  /* Sizes */
98
172
  .large {
99
173
  --stepper-icon-size: var(
@@ -180,10 +254,6 @@
180
254
  }
181
255
 
182
256
  /* Core Element Structuring using mapped variables */
183
- .root .step {
184
- /* Default to Mantine's native flex behavior (stretch) so the vertical separator spans the full height */
185
- }
186
-
187
257
  .root .stepBody {
188
258
  max-width: var(--stepper-max-text-width);
189
259
  display: flex;
@@ -244,11 +314,27 @@
244
314
  );
245
315
  }
246
316
 
317
+ /* `.stepIcon` (MUI's `iconContainer` slot) is a layout host only — it never
318
+ renders the visible circle itself, so it can stretch (vertical) without
319
+ distorting it. The circle is `.stepIconCircle`, rendered by our own
320
+ `RecursicaStepIcon` (see Stepper.tsx). */
247
321
  .root .stepIcon {
322
+ display: flex;
323
+ flex-direction: column;
324
+ align-items: center;
325
+ justify-content: flex-start;
326
+ position: relative;
327
+ width: var(--stepper-indicator-size);
328
+ flex-shrink: 0;
329
+ }
330
+
331
+ .root .stepIconCircle {
332
+ box-sizing: border-box; /* HARDCODE: keep indicator-size math exact incl. border */
248
333
  width: var(--stepper-indicator-size);
249
334
  height: var(--stepper-indicator-size);
250
335
  min-width: var(--stepper-indicator-size);
251
336
  min-height: var(--stepper-indicator-size);
337
+ border-style: solid; /* HARDCODE: plain span has no default border-style */
252
338
  border-width: var(--stepper-border-size);
253
339
  border-radius: 50%; /* Override Figma token to ensure perfectly circular indicators */
254
340
 
@@ -264,7 +350,6 @@
264
350
  display: flex;
265
351
  align-items: center;
266
352
  justify-content: center;
267
- /* Font details are implicitly mapped by sizes, but colors map to states */
268
353
  /* Upcoming State (Pending) - Default */
269
354
  background-color: var(
270
355
  --recursica_ui-kit_components_stepper_properties_colors_upcoming-indicator-background-color
@@ -278,8 +363,7 @@
278
363
  }
279
364
 
280
365
  /* Completed State */
281
- .root .stepIcon.Mui-completed,
282
- .root .stepIcon:has(.Mui-completed) {
366
+ .root .stepIconCircle[data-completed] {
283
367
  background-color: var(
284
368
  --recursica_ui-kit_components_stepper_properties_colors_completed-indicator-background-color
285
369
  );
@@ -289,32 +373,27 @@
289
373
  }
290
374
 
291
375
  .root .stepCompletedIcon {
376
+ width: var(--stepper-svg-size);
377
+ height: var(--stepper-svg-size);
292
378
  color: var(
293
379
  --recursica_ui-kit_components_stepper_properties_colors_completed-indicator-text
294
380
  );
295
381
  }
296
382
 
297
- .root .stepCompletedIcon svg {
298
- width: var(--stepper-svg-size);
299
- height: var(--stepper-svg-size);
300
- }
301
-
302
- .root .stepLabel.Mui-completed {
383
+ .root .stepLabel:global(.Mui-completed) {
303
384
  color: var(
304
385
  --recursica_ui-kit_components_stepper_properties_colors_completed-label-color
305
386
  );
306
387
  }
307
388
 
308
- .root .stepDescription:has(~ .Mui-completed),
309
- .root .MuiStepLabel-root.Mui-completed .stepDescription {
389
+ .root .stepBody:has(.stepLabel:global(.Mui-completed)) .stepDescription {
310
390
  color: var(
311
391
  --recursica_ui-kit_components_stepper_properties_colors_completed-description-color
312
392
  );
313
393
  }
314
394
 
315
395
  /* Current State (Progressing) */
316
- .root .stepIcon.Mui-active,
317
- .root .stepIcon:has(.Mui-active) {
396
+ .root .stepIconCircle[data-active] {
318
397
  background-color: var(
319
398
  --recursica_ui-kit_components_stepper_properties_colors_current-indicator-background-color
320
399
  );
@@ -326,84 +405,38 @@
326
405
  );
327
406
  }
328
407
 
329
- .root .stepLabel.Mui-active {
408
+ .root .stepLabel:global(.Mui-active) {
330
409
  color: var(
331
410
  --recursica_ui-kit_components_stepper_properties_colors_current-label-color
332
411
  );
333
412
  }
334
413
 
335
- .root .stepDescription:has(~ .Mui-active),
336
- .root .MuiStepLabel-root.Mui-active .stepDescription {
414
+ .root .stepBody:has(.stepLabel:global(.Mui-active)) .stepDescription {
337
415
  color: var(
338
416
  --recursica_ui-kit_components_stepper_properties_colors_current-description-color
339
417
  );
340
418
  }
341
419
 
342
420
  /* Upcoming State (Pending Labels) */
343
- .root .stepLabel:not(.Mui-active):not(.Mui-completed) {
421
+ .root .stepLabel:not(:global(.Mui-active)):not(:global(.Mui-completed)) {
344
422
  color: var(
345
423
  --recursica_ui-kit_components_stepper_properties_colors_upcoming-label-color
346
424
  );
347
425
  }
348
426
 
349
- .root .MuiStepLabel-root:not(.Mui-active):not(.Mui-completed) .stepDescription {
427
+ .root
428
+ .stepBody:not(:has(.stepLabel:global(.Mui-active))):not(
429
+ :has(.stepLabel:global(.Mui-completed))
430
+ )
431
+ .stepDescription {
350
432
  color: var(
351
433
  --recursica_ui-kit_components_stepper_properties_colors_upcoming-description-color
352
434
  );
353
435
  }
354
436
 
355
- /* Horizontal Separator */
356
- .root .separator {
357
- margin: 0 var(--stepper-step-gap); /* Apply the gap logic to the separator natively */
358
- background-color: var(
359
- --recursica_ui-kit_components_stepper_properties_colors_upcoming-connector-color
360
- );
361
- height: var(
362
- --recursica_ui-kit_components_stepper_properties_upcoming-connector-size
363
- );
364
- border: none;
365
- min-width: 20px;
366
- flex: 1;
367
- }
368
-
369
- .root .separator.Mui-active,
370
- .root .separator.Mui-completed {
371
- background-color: var(
372
- --recursica_ui-kit_components_stepper_properties_colors_completed-connector-color
373
- );
374
- height: var(
375
- --recursica_ui-kit_components_stepper_properties_completed-connector-size
376
- );
377
- }
378
-
379
- /* Vertical Separator */
380
- .root .verticalSeparator {
381
- border-inline-start: var(
382
- --recursica_ui-kit_components_stepper_properties_upcoming-connector-size
383
- )
384
- solid
385
- var(
386
- --recursica_ui-kit_components_stepper_properties_colors_upcoming-connector-color
387
- );
388
- top: var(
389
- --stepper-icon-size
390
- ); /* Overrides Mantine's gap math to force the line to touch the icon */
391
- }
392
-
393
- .root .verticalSeparator.Mui-active,
394
- .root .verticalSeparator.Mui-completed {
395
- border-inline-start: var(
396
- --recursica_ui-kit_components_stepper_properties_completed-connector-size
397
- )
398
- solid
399
- var(
400
- --recursica_ui-kit_components_stepper_properties_colors_completed-connector-color
401
- );
402
- }
403
-
404
- /* Hide Content
405
- Recursica Steppers are purely navigational/structural. Content should be managed
406
- by the parent layout outside of the Stepper DOM.
437
+ /* Hide Content
438
+ Recursica Steppers are purely navigational/structural. Content should be managed
439
+ by the parent layout outside of the Stepper DOM.
407
440
  */
408
441
  .root .content {
409
442
  display: none;
@@ -10,6 +10,7 @@ import {
10
10
  type StepLabelProps as MuiStepLabelProps,
11
11
  type StepButtonProps as MuiStepButtonProps,
12
12
  type StepConnectorProps as MuiStepConnectorProps,
13
+ type StepIconProps as MuiStepIconProps,
13
14
  } from "@mui/material";
14
15
  import {
15
16
  filterStylingProps,
@@ -19,6 +20,49 @@ import styles from "./Stepper.module.css";
19
20
 
20
21
  import { type RecursicaStepperProps } from "@recursica/adapter-common";
21
22
 
23
+ // Same inline check glyph used by Checkbox.tsx (fill="currentColor" so the CSS
24
+ // module drives color via `.stepCompletedIcon`).
25
+ function CheckIcon(props: React.ComponentProps<"svg">) {
26
+ return (
27
+ <svg
28
+ viewBox="0 0 10 7"
29
+ fill="none"
30
+ xmlns="http://www.w3.org/2000/svg"
31
+ {...props}
32
+ >
33
+ <path
34
+ d="M4 4.586L1.707 2.293A1 1 0 1 0 .293 3.707l3 3a.997.997 0 0 0 1.414 0l5-5A1 1 0 1 0 8.293.293L4 4.586z"
35
+ fill="currentColor"
36
+ fillRule="evenodd"
37
+ clipRule="evenodd"
38
+ />
39
+ </svg>
40
+ );
41
+ }
42
+
43
+ // MUI's default StepIcon draws its own self-contained circle+check SVG
44
+ // (Material's `check_circle` glyph, colored via `theme.palette.primary.main`)
45
+ // whenever `icon` is the auto-injected step number — it never defers to our
46
+ // `.stepIcon` background/border tokens. We render the indicator ourselves so
47
+ // the recursica-token circle (`.stepIconCircle`) is the only circle, and the
48
+ // completed check mark is our own token-colored glyph.
49
+ function RecursicaStepIcon(props: MuiStepIconProps) {
50
+ const { icon, active, completed, error } = props;
51
+ if (typeof icon !== "number" && typeof icon !== "string") {
52
+ return <>{icon}</>;
53
+ }
54
+ return (
55
+ <span
56
+ className={styles.stepIconCircle}
57
+ data-active={active || undefined}
58
+ data-completed={completed || undefined}
59
+ data-error={error || undefined}
60
+ >
61
+ {completed ? <CheckIcon className={styles.stepCompletedIcon} /> : icon}
62
+ </span>
63
+ );
64
+ }
65
+
22
66
  export interface RecursicaStepperPropsExtended
23
67
  extends Omit<Partial<MuiStepperProps>, "size">,
24
68
  RecursicaStepperProps {}
@@ -37,10 +81,11 @@ export const Stepper = forwardRef<HTMLDivElement, StepperProps>(
37
81
  } = props;
38
82
 
39
83
  const sanitizedProps = filterStylingProps(rest, overStyled);
84
+ const isHorizontal = orientation === "horizontal";
40
85
 
41
86
  return (
42
87
  <div
43
- className={`${styles.root} ${orientation === "horizontal" ? styles.horizontal : styles.vertical} ${size === "large" ? styles.large : styles.small} ${className || ""}`}
88
+ className={`${styles.root} ${isHorizontal ? styles.horizontal : styles.vertical} ${size === "large" ? styles.large : styles.small} ${className || ""}`}
44
89
  style={style as React.CSSProperties}
45
90
  data-size={size}
46
91
  data-orientation={orientation}
@@ -50,15 +95,27 @@ export const Stepper = forwardRef<HTMLDivElement, StepperProps>(
50
95
  ref={ref as any}
51
96
  {...(sanitizedProps as MuiStepperProps)}
52
97
  orientation={orientation}
98
+ // `alternativeLabel` is MUI's real primitive for "label centered under
99
+ // the icon" (see its own prop doc) — required for the icon/label
100
+ // layout and for the connector's centered absolute positioning.
101
+ alternativeLabel={isHorizontal}
53
102
  connector={
54
- <MuiStepConnector
55
- classes={{
56
- root:
57
- orientation === "horizontal"
58
- ? styles.separator
59
- : styles.verticalSeparator,
60
- }}
61
- />
103
+ isHorizontal ? (
104
+ <MuiStepConnector
105
+ classes={{
106
+ root: styles.separator,
107
+ line: styles.separatorLine,
108
+ }}
109
+ />
110
+ ) : (
111
+ // Vertical connecting line is drawn as a `.stepIcon::after` rail
112
+ // (see module CSS) so its length tracks each step's actual
113
+ // rendered height (multi-line descriptions included) the same
114
+ // way mantine's absolutely-positioned separator does — MUI's
115
+ // own vertical `StepConnector` is a fixed-height sibling that
116
+ // can't do that without `StepContent`, which recursica hides.
117
+ <></>
118
+ )
62
119
  }
63
120
  classes={{
64
121
  root: styles.steps,
@@ -112,16 +169,25 @@ export type StepLabelProps = RecursicaOverStyled<
112
169
 
113
170
  export const StepLabel = forwardRef<HTMLDivElement, StepLabelProps>(
114
171
  function StepLabel(props, ref) {
115
- const { overStyled = false, className, description, ...rest } = props;
172
+ const {
173
+ overStyled = false,
174
+ className,
175
+ description,
176
+ StepIconComponent,
177
+ ...rest
178
+ } = props;
116
179
  return (
117
180
  <MuiStepLabel
118
181
  ref={ref}
119
182
  {...(filterStylingProps(rest, overStyled) as MuiStepLabelProps)}
120
183
  className={className || ""}
121
184
  classes={{
185
+ root: styles.stepLabelRoot,
122
186
  label: styles.stepLabel,
123
187
  iconContainer: styles.stepIcon,
188
+ labelContainer: styles.stepBody,
124
189
  }}
190
+ StepIconComponent={StepIconComponent ?? RecursicaStepIcon}
125
191
  optional={
126
192
  description ? (
127
193
  <div className={styles.stepDescription}>{description}</div>
@@ -39,3 +39,7 @@ All Recursica components in the `@recursica/mui-adapter` package adhere strictly
39
39
  > - **Anti-override protection**: Rogues style injections (like inline `style` or arbitrary `className`) are automatically blocked by our prop layer unless `overStyled={true}` is explicitly provided.
40
40
  > - **No Direct Layers**: Do not pass a `layer` prop to this component. To place it on a specific visual layer, wrap it in a `<Layer layer={0|1|2|3}>` component natively.
41
41
  > - **Variables and Theming**: Styling is entirely determined by local CSS variables defined in `recursica_variables_scoped.css` and mapped in the component's CSS module.
42
+ > - **Default step icon**: `StepLabel` renders its own token-driven circle (with a check mark
43
+ > once a step is completed) by default. Pass your own `StepIconComponent` prop to `StepLabel`
44
+ > to override it for a given step, or pass a custom `icon` node (e.g. `<StepLabel icon={<MyIcon />}>`)
45
+ > to render arbitrary content in place of the circle entirely.
@@ -12,3 +12,15 @@
12
12
  - **MUI's `TabPanel` defaults to `padding: theme.spacing(3)` (24px)**; Mantine's `Tabs.Panel` has none — the gap is already carried by `.list`'s own margin. Reset to `padding: 0`.
13
13
  - **The indicator (`classes.indicator`) is absolutely positioned against `.MuiTabs-root`, not `.list`:** `.list`'s own `margin-bottom` (the tabs-content-gap) collapses into that root's auto height instead of stopping at `.list`'s border edge, so the indicator's default `bottom: 0` lands one content-gap below the baseline. Offset it by the same content-gap token to bring it flush again.
14
14
  - **All of the above needed `!important`** to reliably beat MUI's own emotion-injected styles for the same property — plain specificity/cascade order wasn't sufficient in this build.
15
+
16
+ ## Follow-up parity fixes (2026-08-18)
17
+
18
+ - **`MuiTouchRipple-root` gets caught by `.root .tab > *`:** that rule exists to lift real content (icon/label) above the hover overlay's `z-index`, but it also matches the ripple span MUI injects into the tab on first click. Forcing that span from its native `position: absolute` to `position: relative` turns it into a real flex item, inserting one extra `--tabs-element-gap` into the tab's flex row and permanently growing the tab's width (it never gets removed from the DOM after the ripple animation ends) — this was the cause of a layout shift where selecting a tab pushed later siblings sideways. Excluded via `:not(:global(.MuiTouchRipple-root))`.
19
+ - **Outline's selected-tab border can't be "erased" with a same-element pseudo-element:** the open edge (0 border-width on the side touching the list's baseline) leaves the list's own `border-bottom`/`border-right` showing through, because that border is a real CSS border appended outside the tab's box, not an inset overlay sharing the tab's own bottom edge (unlike Mantine, where the list-baseline wrapper and the tab share the same reference edge, so the tab's own border naturally paints over it). A `::after` patch on the tab itself can't fix this either: MUI gives `Tab` (`ButtonBase`) `overflow: hidden` natively (to contain the ripple), which clips away anything extending past the tab's own box before it ever reaches the list's border. Fixed by repurposing the indicator element (`classes.indicator`) instead — it already lives in the scroller (a sibling of `.list`, not clipped) and MUI already keeps its position/size in sync with whichever tab is selected, so it just needed restyling into a small opaque patch instead of being `display: none`.
20
+ - **That patch needs a real opaque color, not the tab's own token:** outline's active background-color token is `transparent` by design (it's meant to let the ambient Layer surface show through), so it can't double as the eraser — painting "transparent over transparent" does nothing. Used `--recursica_brand_layer_0_properties_surface` (the default/outermost Layer surface) instead, since that's what a Tabs instance sits on absent an explicit wrapping `<Layer>`. This is an inherent approximation, same category as Mantine's own equivalent trick (which hardcodes `--mantine-color-body`): if a consumer nests outline Tabs inside a `<Layer layer={1|2|3}>`, the patch will mismatch that surface's color. No generic "current ambient layer" token exists in the schema to fix this properly — flagged for whoever owns the token schema.
21
+ - **Vertical `.list` doesn't stretch to the root's full height:** Mantine's vertical `.list` stretches via flex `align-items`, so its `border-right` divider runs the whole rail. MUI's scroller (the `.list`'s parent) is a plain block for vertical, not a flex container, so `.list` sizes to just its stacked tab content and the divider stopped at the last tab. Fixed with an explicit `height: 100%` on `.list` for vertical orientation.
22
+ - **Vertical indicator needs the same content-gap offset as horizontal, rotated 90°:** the list's `margin-right` (vertical's tabs-content-gap) inflates the root's auto width past the list's own border-right edge, same mechanism as the existing horizontal `bottom` offset trick — without a matching `right` offset, the indicator renders content-gap pixels to the right of the divider instead of on it.
23
+
24
+ ## Ripple removal (2026-08-19)
25
+
26
+ - **MUI `Tab` shows a click ripple; Mantine's has none.** `Tab` extends `ButtonBase`, which renders a `TouchRipple` on click by default. Fixed by passing `disableRipple` on `MuiTab` in `Tab` (the same official escape hatch already used by `Button`/`Switch`/`Radio`/`Checkbox`). With `disableRipple`, the `MuiTouchRipple-root` span is never rendered at all, which also makes the `.root .tab > *:not(:global(.MuiTouchRipple-root))` layout-shift workaround above moot going forward (left in place defensively).