@recursica/mui-adapter 0.25.0 → 0.27.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 (78) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/index.d.ts +105 -19
  3. package/dist/mui-adapter.cjs +77 -77
  4. package/dist/mui-adapter.cjs.map +1 -1
  5. package/dist/mui-adapter.css +1 -1
  6. package/dist/mui-adapter.js +29867 -25282
  7. package/dist/mui-adapter.js.map +1 -1
  8. package/package.json +1 -1
  9. package/src/GlobalExemptions.modules.css +0 -6
  10. package/src/components/Accordion/Accordion.module.css +0 -8
  11. package/src/components/Autocomplete/Autocomplete.module.css +0 -8
  12. package/src/components/Autocomplete/Autocomplete.tsx +46 -34
  13. package/src/components/Avatar/Avatar.module.css +0 -8
  14. package/src/components/Avatar/Avatar.tsx +11 -21
  15. package/src/components/Badge/Badge.tsx +9 -13
  16. package/src/components/Breadcrumb/Breadcrumb.tsx +12 -18
  17. package/src/components/Button/Button.module.css +0 -13
  18. package/src/components/Button/Button.tsx +17 -11
  19. package/src/components/Card/Card.tsx +9 -19
  20. package/src/components/Checkbox/Checkbox.tsx +22 -26
  21. package/src/components/Checkbox/CheckboxGroup.tsx +3 -3
  22. package/src/components/Chip/Chip.module.css +0 -27
  23. package/src/components/Chip/Chip.tsx +12 -19
  24. package/src/components/DatePicker/DATEPICKER_IMPLEMENTATION_NOTES.md +29 -0
  25. package/src/components/DatePicker/DatePicker.icons.tsx +35 -0
  26. package/src/components/DatePicker/DatePicker.module.css +451 -42
  27. package/src/components/DatePicker/DatePicker.stories.tsx +23 -25
  28. package/src/components/DatePicker/DatePicker.tsx +229 -47
  29. package/src/components/DatePicker/USAGE.md +14 -1
  30. package/src/components/Dropdown/BareDropdown.tsx +14 -8
  31. package/src/components/Dropdown/Dropdown.module.css +0 -7
  32. package/src/components/Dropdown/Dropdown.tsx +13 -6
  33. package/src/components/FileInput/FileInput.module.css +0 -21
  34. package/src/components/FileInput/FileInput.tsx +1 -1
  35. package/src/components/FileUpload/FileUpload.module.css +0 -11
  36. package/src/components/FileUpload/FileUpload.tsx +1 -1
  37. package/src/components/FormControlLayout/FormControlLayout.tsx +1 -1
  38. package/src/components/FormControlWrapper/FormControlWrapper.tsx +1 -1
  39. package/src/components/HoverCard/HoverCard.module.css +1 -7
  40. package/src/components/HoverCard/HoverCard.tsx +13 -21
  41. package/src/components/Label/Label.module.css +0 -6
  42. package/src/components/Link/Link.module.css +0 -13
  43. package/src/components/Menu/Menu.module.css +0 -5
  44. package/src/components/Menu/Menu.tsx +14 -6
  45. package/src/components/Modal/Modal.module.css +0 -11
  46. package/src/components/Modal/Modal.tsx +12 -6
  47. package/src/components/NumberInput/NumberInput.module.css +0 -7
  48. package/src/components/NumberInput/NumberInput.tsx +30 -15
  49. package/src/components/Pagination/Pagination.module.css +0 -81
  50. package/src/components/Pagination/Pagination.tsx +5 -15
  51. package/src/components/Panel/Panel.tsx +17 -25
  52. package/src/components/Popover/Popover.module.css +0 -5
  53. package/src/components/Popover/Popover.tsx +10 -18
  54. package/src/components/Radio/Radio.tsx +21 -26
  55. package/src/components/Radio/RadioGroup.tsx +3 -4
  56. package/src/components/ReadOnlyField/ReadOnlyTextField.tsx +1 -1
  57. package/src/components/SegmentedControl/SegmentedControl.module.css +0 -8
  58. package/src/components/SegmentedControl/SegmentedControl.tsx +20 -25
  59. package/src/components/Slider/Slider.tsx +40 -43
  60. package/src/components/Stepper/Stepper.tsx +34 -14
  61. package/src/components/Switch/Switch.tsx +33 -49
  62. package/src/components/Switch/SwitchGroup.tsx +3 -3
  63. package/src/components/Tabs/Tabs.tsx +12 -4
  64. package/src/components/TextArea/TextArea.module.css +0 -7
  65. package/src/components/TextArea/TextArea.tsx +30 -7
  66. package/src/components/TextField/TextField.module.css +0 -8
  67. package/src/components/TextField/TextField.tsx +30 -26
  68. package/src/components/TimePicker/TimePicker.module.css +0 -16
  69. package/src/components/Timeline/Timeline.module.css +0 -6
  70. package/src/components/Timeline/Timeline.tsx +9 -1
  71. package/src/components/Timeline/TimelineItem.tsx +2 -2
  72. package/src/components/Toast/Toast.module.css +0 -7
  73. package/src/components/Toast/Toast.tsx +14 -23
  74. package/src/components/Tooltip/Tooltip.module.css +0 -9
  75. package/src/components/Tooltip/Tooltip.tsx +14 -27
  76. package/src/components/TransferList/TransferList.module.css +0 -8
  77. package/src/components/TransferList/TransferList.tsx +1 -1
  78. package/src/utils/filterStylingProps.ts +4 -0
@@ -9,6 +9,7 @@ import {
9
9
  } from "@mui/material";
10
10
  import {
11
11
  filterStylingProps,
12
+ mergeClassNames,
12
13
  type RecursicaOverStyled,
13
14
  } from "../../utils/filterStylingProps";
14
15
  import styles from "./Menu.module.css";
@@ -23,15 +24,22 @@ export const Menu = forwardRef<HTMLDivElement, MenuProps>(function Menu(
23
24
  ) {
24
25
  const sanitizedProps = filterStylingProps(rest, overStyled);
25
26
 
27
+ const mergedClassNames = mergeClassNames(
28
+ {
29
+ paper: styles.dropdown,
30
+ list: styles.dropdown,
31
+ },
32
+ (sanitizedProps as Record<string, unknown>).classes as
33
+ | Partial<Record<string, string>>
34
+ | undefined,
35
+ );
36
+
26
37
  return (
27
38
  <MuiMenu
28
39
  ref={ref}
29
40
  {...(sanitizedProps as MuiMenuProps)}
30
41
  className={`${styles.dropdown} ${className || ""}`}
31
- classes={{
32
- paper: styles.dropdown,
33
- list: styles.dropdown,
34
- }}
42
+ classes={mergedClassNames}
35
43
  />
36
44
  );
37
45
  });
@@ -47,8 +55,8 @@ export const MenuItem = forwardRef<HTMLLIElement, MenuItemProps>(
47
55
  return (
48
56
  <MuiMenuItem
49
57
  ref={ref}
50
- className={`${styles.item} ${className || ""}`}
51
58
  {...(sanitizedProps as MuiMenuItemProps)}
59
+ className={`${styles.item} ${className || ""}`}
52
60
  />
53
61
  );
54
62
  },
@@ -65,8 +73,8 @@ export const MenuDivider = forwardRef<HTMLHRElement, MenuDividerProps>(
65
73
  return (
66
74
  <MuiDivider
67
75
  ref={ref}
68
- className={`${styles.divider} ${className || ""}`}
69
76
  {...(sanitizedProps as MuiDividerProps)}
77
+ className={`${styles.divider} ${className || ""}`}
70
78
  />
71
79
  );
72
80
  },
@@ -1,14 +1,3 @@
1
- /* EXEMPTIONS:
2
- - content-style and header-style parent variables represent raw JSON styling objects rather than
3
- direct CSS variables and cannot be applied directly in standard CSS files.
4
- Sub-properties are fully and individually mapped to elements natively.
5
- - header/footer background-color are ignored because .header/.footer are intentionally
6
- transparent (inheriting .content's background), matching Panel's header/footer treatment. */
7
- /* recursica-ignore: --recursica_ui-kit_components_modal_properties_content-style */
8
- /* recursica-ignore: --recursica_ui-kit_components_modal_properties_header-style */
9
- /* recursica-ignore: --recursica_ui-kit_components_modal_properties_colors_footer-background-color */
10
- /* recursica-ignore: --recursica_ui-kit_components_modal_properties_colors_header-background-color */
11
-
12
1
  .root {
13
2
  }
14
3
 
@@ -12,6 +12,7 @@ import {
12
12
  } from "@mui/material";
13
13
  import {
14
14
  filterStylingProps,
15
+ mergeClassNames,
15
16
  type RecursicaOverStyled,
16
17
  } from "../../utils/filterStylingProps";
17
18
  import styles from "./Modal.module.css";
@@ -58,6 +59,13 @@ const ModalRoot = forwardRef<HTMLDivElement, ModalProps>(function ModalRoot(
58
59
  ) {
59
60
  const sanitizedProps = filterStylingProps(rest, overStyled);
60
61
 
62
+ const mergedClassNames = mergeClassNames(
63
+ { paper: styles.inner },
64
+ (sanitizedProps as Record<string, unknown>).classes as
65
+ | Partial<Record<string, string>>
66
+ | undefined,
67
+ );
68
+
61
69
  let footer: React.ReactNode = null;
62
70
  const bodyChildren: React.ReactNode[] = [];
63
71
 
@@ -79,9 +87,7 @@ const ModalRoot = forwardRef<HTMLDivElement, ModalProps>(function ModalRoot(
79
87
  ref={ref}
80
88
  {...(sanitizedProps as MuiDialogProps)}
81
89
  className={`${styles.root} ${className || ""}`}
82
- classes={{
83
- paper: styles.inner,
84
- }}
90
+ classes={mergedClassNames}
85
91
  open={
86
92
  opened ||
87
93
  ((sanitizedProps as Record<string, unknown>).open as boolean) ||
@@ -124,8 +130,8 @@ const ModalTitle = forwardRef<HTMLDivElement, ModalTitleProps>(
124
130
  <MuiDialogTitle
125
131
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
126
132
  ref={ref as any}
127
- className={`${styles.title} ${className || ""}`}
128
133
  {...(sanitizedProps as MuiDialogTitleProps)}
134
+ className={`${styles.title} ${className || ""}`}
129
135
  />
130
136
  );
131
137
  },
@@ -174,11 +180,11 @@ const ModalBody = forwardRef<HTMLDivElement, ModalBodyProps>(function ModalBody(
174
180
  internalRef as React.MutableRefObject<HTMLDivElement | null>
175
181
  ).current = node as HTMLDivElement;
176
182
  }}
183
+ {...(sanitizedProps as MuiDialogContentProps)}
177
184
  onScroll={() => checkScroll()}
178
185
  data-scrolled-top={scrolledTop || undefined}
179
186
  data-scrolled-bottom={scrolledBottom || undefined}
180
187
  className={`${styles.content} ${styles.scrollArea} ${className || ""}`}
181
- {...(sanitizedProps as MuiDialogContentProps)}
182
188
  >
183
189
  {children}
184
190
  </MuiDialogContent>
@@ -201,8 +207,8 @@ const ModalFooter = forwardRef<HTMLDivElement, ModalFooterProps>(
201
207
  <MuiDialogActions
202
208
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
203
209
  ref={ref as any}
204
- className={`${styles.footer} ${className || ""}`}
205
210
  {...(sanitizedProps as MuiDialogActionsProps)}
211
+ className={`${styles.footer} ${className || ""}`}
206
212
  />
207
213
  );
208
214
  },
@@ -1,10 +1,3 @@
1
- /* EXEMPTIONS:
2
- - border-size variables are ignored because the input box uses a uniform static boundary to prevent
3
- unexpected layout shifts or flickering when switching between focused, disabled, or error states. */
4
- /* recursica-ignore: --recursica_ui-kit_components_number-input_properties_border-size */
5
- /* recursica-ignore: --recursica_ui-kit_components_number-input_variants_states_disabled_properties_border-size */
6
- /* recursica-ignore: --recursica_ui-kit_components_number-input_variants_states_error_properties_border-size */
7
-
8
1
  /* LAYOUT SPACING OVERRIDES:
9
2
  - Sets the --form-control-margin-bottom spacing hook to map component-specific layout tokens.
10
3
  - Also sets the --number-input-control-{max,min}-width hooks consumed inline in NumberInput.tsx,
@@ -6,6 +6,8 @@ import {
6
6
  import { type ReadOnlyControlProps } from "@recursica/adapter-common";
7
7
  import {
8
8
  filterStylingProps,
9
+ omitUnsupportedProps,
10
+ mergeClassNames,
9
11
  type RecursicaOverStyled,
10
12
  } from "../../utils/filterStylingProps";
11
13
  import { type RecursicaFormControlWrapperProps } from "../FormControlWrapper/FormControlWrapper";
@@ -73,22 +75,33 @@ export const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>(
73
75
  ...rest
74
76
  } = props;
75
77
 
76
- const sanitizedProps = filterStylingProps(rest, overStyled);
77
- const restRecord = sanitizedProps as Record<string, unknown>;
78
+ // Props this component intentionally doesn't support — deleted at runtime so they can't leak
79
+ // through even if a caller forces them via plain JavaScript, bypassing the Omit<> above.
80
+ const UNSUPPORTED_PROPS = [
81
+ "size", // Recursica controls sizing via design tokens, not MUI's native small/medium size
82
+ "variant", // Recursica styles the naked input directly; MUI's standard/filled/outlined unused
83
+ ] as const satisfies readonly (keyof MuiNumberInputProps)[];
78
84
 
79
- // Delete prohibited sizing hooks from bypassing variables natively
80
- delete restRecord["size"];
81
- delete restRecord["variant"];
82
- delete restRecord["radius"];
85
+ const sanitizedProps = omitUnsupportedProps(
86
+ filterStylingProps(rest, overStyled),
87
+ UNSUPPORTED_PROPS,
88
+ );
89
+ const restRecord = sanitizedProps as Record<string, unknown>;
83
90
 
84
- // Securely map core native blocks down ensuring nested CSS modules map precisely
85
- const mergedClassNames: Partial<Record<string, string>> = {
86
- wrapper: styles.root, // The nested Input internal relative wrapper bounding box
87
- input: styles.input,
88
- section: styles.section,
89
- controls: styles.controls,
90
- control: styles.control,
91
- };
91
+ // Securely map core native blocks down ensuring nested CSS modules map precisely. This
92
+ // previously always overwrote the caller's `classes` prop wholesale (no merge at all) —
93
+ // now merged per-slot via mergeClassNames so a caller-supplied slot value extends rather
94
+ // than replaces ours.
95
+ const mergedClassNames = mergeClassNames(
96
+ {
97
+ wrapper: styles.root, // The nested Input internal relative wrapper bounding box
98
+ input: styles.input,
99
+ section: styles.section,
100
+ controls: styles.controls,
101
+ control: styles.control,
102
+ },
103
+ restRecord.classes as Partial<Record<string, string>> | undefined,
104
+ );
92
105
 
93
106
  const wrapperClass = className
94
107
  ? `${styles.layoutOverride} ${className}`
@@ -138,7 +151,9 @@ export const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>(
138
151
  <MuiNumberInput
139
152
  ref={ref}
140
153
  {...(sanitizedProps as unknown as MuiNumberInputProps)}
141
- classes={mergedClassNames}
154
+ classes={
155
+ mergedClassNames as unknown as MuiNumberInputProps["classes"]
156
+ }
142
157
  disabled={disabled}
143
158
  value={value}
144
159
  defaultValue={defaultValue}
@@ -1,84 +1,3 @@
1
- /* EXEMPTIONS:
2
- - All pagination subcomponent properties (active-pages, inactive-pages, navigation-controls) are ignored
3
- because we are not implementing custom subcomponent styling for pagination. Instead, pagination items
4
- directly inherit the unified, standard brand button components for complete UI/UX consistency, rendering
5
- these generated subcomponent tokens redundant. */
6
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_border-size */
7
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_disabled-opacity */
8
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_elevation */
9
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_height */
10
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_horizontal-padding */
11
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_hover-color */
12
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_hover-elevation */
13
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_hover-opacity */
14
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_icon */
15
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_icon-text-gap */
16
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_max-label-width */
17
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_min-width */
18
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_text_font-family */
19
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_text_font-size */
20
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_text_font-style */
21
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_text_font-weight */
22
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_text_letter-spacing */
23
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_text_line-height */
24
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_text_text-decoration */
25
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_text_text-transform */
26
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_colors_background */
27
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_colors_border-color */
28
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_colors_icon-color */
29
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_colors_text */
30
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_active-pages_subcomponent_colors_text-hover */
31
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_border-size */
32
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_disabled-opacity */
33
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_elevation */
34
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_height */
35
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_horizontal-padding */
36
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_hover-color */
37
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_hover-elevation */
38
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_hover-opacity */
39
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_icon */
40
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_icon-text-gap */
41
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_max-label-width */
42
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_min-width */
43
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_text_font-family */
44
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_text_font-size */
45
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_text_font-style */
46
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_text_font-weight */
47
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_text_letter-spacing */
48
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_text_line-height */
49
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_text_text-decoration */
50
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_text_text-transform */
51
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_colors_background */
52
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_colors_border-color */
53
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_colors_icon-color */
54
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_colors_text */
55
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_inactive-pages_subcomponent_colors_text-hover */
56
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_border-size */
57
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_disabled-opacity */
58
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_elevation */
59
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_height */
60
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_horizontal-padding */
61
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_hover-color */
62
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_hover-elevation */
63
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_hover-opacity */
64
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_icon */
65
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_icon-text-gap */
66
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_max-label-width */
67
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_min-width */
68
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_text_font-family */
69
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_text_font-size */
70
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_text_font-style */
71
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_text_font-weight */
72
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_text_letter-spacing */
73
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_text_line-height */
74
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_text_text-decoration */
75
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_text_text-transform */
76
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_colors_background */
77
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_colors_border-color */
78
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_colors_icon-color */
79
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_colors_text */
80
- /* recursica-ignore: --recursica_ui-kit_components_pagination_properties_navigation-controls_subcomponent_colors_text-hover */
81
-
82
1
  .root {
83
2
  display: flex;
84
3
  align-items: center;
@@ -5,6 +5,7 @@ import {
5
5
  } from "@mui/material";
6
6
  import {
7
7
  filterStylingProps,
8
+ mergeClassNames,
8
9
  type RecursicaOverStyled,
9
10
  } from "../../utils/filterStylingProps";
10
11
  import styles from "./Pagination.module.css";
@@ -19,21 +20,10 @@ function usePaginationClassNames(restRecord: Record<string, unknown>): {
19
20
  className: string;
20
21
  classNames: Partial<Record<string, string>>;
21
22
  } {
22
- const mergedClassNames: Partial<Record<string, string>> = {
23
- root: styles.root,
24
- ul: styles.ul,
25
- };
26
-
27
- const classNamesProp = restRecord.classNames;
28
- if (
29
- classNamesProp &&
30
- typeof classNamesProp === "object" &&
31
- !Array.isArray(classNamesProp)
32
- ) {
33
- const o = classNamesProp as Partial<Record<string, string>>;
34
- mergedClassNames.root = o.root ? `${styles.root} ${o.root}` : styles.root;
35
- mergedClassNames.ul = o.ul ? `${styles.ul} ${o.ul}` : styles.ul;
36
- }
23
+ const mergedClassNames = mergeClassNames(
24
+ { root: styles.root, ul: styles.ul },
25
+ restRecord.classNames as Partial<Record<string, string>> | undefined,
26
+ );
37
27
 
38
28
  const classNameProp = restRecord.className as string | undefined;
39
29
  const finalClass = classNameProp
@@ -5,6 +5,7 @@ import {
5
5
  } from "@mui/material";
6
6
  import {
7
7
  filterStylingProps,
8
+ mergeClassNames,
8
9
  type RecursicaOverStyled,
9
10
  } from "../../utils/filterStylingProps";
10
11
  import styles from "./Panel.module.css";
@@ -63,30 +64,21 @@ const PanelBase = function Panel({
63
64
  }: PanelProps) {
64
65
  const sanitizedProps = filterStylingProps(rest, overStyled);
65
66
 
66
- // Bind CSS module classes to Mui's internal classNames API
67
- const mergedClassNames: Partial<Record<string, string>> = {
68
- content: styles.content,
69
- header: styles.header,
70
- title: wrapHeaderText ? styles.titleTruncate : styles.title,
71
- body: styles.body,
72
- inner: styles.inner,
73
- };
74
-
75
- const classNamesProp = (sanitizedProps as Record<string, unknown>).classNames;
76
- if (
77
- classNamesProp &&
78
- typeof classNamesProp === "object" &&
79
- !Array.isArray(classNamesProp)
80
- ) {
81
- const o = classNamesProp as Record<string, string>;
82
- Object.keys(o).forEach((key) => {
83
- if (mergedClassNames[key]) {
84
- mergedClassNames[key] = `${mergedClassNames[key]} ${o[key]}`;
85
- } else {
86
- mergedClassNames[key] = o[key];
87
- }
88
- });
89
- }
67
+ // Bind CSS module classes to Mui's internal classNames API. Note MUI's actual prop is
68
+ // "classes", not "classNames" (that's Mantine's naming) — this used to read the wrong key,
69
+ // silently no-op-ing any caller-supplied classes.
70
+ const mergedClassNames = mergeClassNames(
71
+ {
72
+ content: styles.content,
73
+ header: styles.header,
74
+ title: wrapHeaderText ? styles.titleTruncate : styles.title,
75
+ body: styles.body,
76
+ inner: styles.inner,
77
+ },
78
+ (sanitizedProps as Record<string, unknown>).classes as
79
+ | Partial<Record<string, string>>
80
+ | undefined,
81
+ );
90
82
 
91
83
  return (
92
84
  <MuiDrawer
@@ -94,7 +86,7 @@ const PanelBase = function Panel({
94
86
  keepMounted={keepMounted}
95
87
  open={Boolean(opened)}
96
88
  {...(sanitizedProps as unknown as MuiDrawerProps)}
97
- classes={mergedClassNames}
89
+ classes={mergedClassNames as unknown as MuiDrawerProps["classes"]}
98
90
  />
99
91
  );
100
92
  };
@@ -13,11 +13,6 @@
13
13
  Popper/Tooltip behavior. We only override visual design tokens (colors, typography,
14
14
  spacing, borders).
15
15
  */
16
- /* EXEMPTIONS:
17
- - beak-size is ignored here because Mui's Tooltip arrow is a fixed-size CSS shape
18
- (1em / 0.71em, relative to the tooltip's own font-size) with no size prop of its own —
19
- unlike Mantine, there is no JS/pixel hook to bind this token to. */
20
- /* recursica-ignore: --recursica_ui-kit_components_hover-card-popover_properties_beak-size = 16px */
21
16
 
22
17
  /* ======================================
23
18
  DROPDOWN CONTAINER
@@ -11,6 +11,7 @@ import {
11
11
  } from "@mui/material";
12
12
  import {
13
13
  filterStylingProps,
14
+ mergeClassNames,
14
15
  type RecursicaOverStyled,
15
16
  } from "../../utils/filterStylingProps";
16
17
  import styles from "./Popover.module.css";
@@ -85,24 +86,15 @@ const PopoverBase = function Popover({
85
86
  );
86
87
 
87
88
  // Bind CSS module classes to Mui's internal classNames API
88
- const mergedClassNames: Partial<Record<string, string>> = {
89
- tooltip: styles.dropdown,
90
- arrow: styles.arrow,
91
- };
92
-
93
- const classesProp = (sanitizedProps as Record<string, unknown>).classes;
94
- if (
95
- classesProp &&
96
- typeof classesProp === "object" &&
97
- !Array.isArray(classesProp)
98
- ) {
99
- const o = classesProp as Record<string, string>;
100
- Object.keys(o).forEach((key) => {
101
- mergedClassNames[key] = mergedClassNames[key]
102
- ? `${mergedClassNames[key]} ${o[key]}`
103
- : o[key];
104
- });
105
- }
89
+ const mergedClassNames = mergeClassNames(
90
+ {
91
+ tooltip: styles.dropdown,
92
+ arrow: styles.arrow,
93
+ },
94
+ (sanitizedProps as Record<string, unknown>).classes as
95
+ | Partial<Record<string, string>>
96
+ | undefined,
97
+ );
106
98
 
107
99
  const [internalOpened, setInternalOpened] = useState(defaultOpened);
108
100
  const isControlled = opened !== undefined;
@@ -7,6 +7,8 @@ import { type ReadOnlyControlProps } from "@recursica/adapter-common";
7
7
  import { RadioGroup } from "./RadioGroup";
8
8
  import {
9
9
  filterStylingProps,
10
+ omitUnsupportedProps,
11
+ mergeClassNames,
10
12
  type RecursicaOverStyled,
11
13
  } from "../../utils/filterStylingProps";
12
14
  import {
@@ -72,13 +74,19 @@ export const Radio = forwardRef<HTMLInputElement, RadioProps>(
72
74
  style,
73
75
  ...rest
74
76
  } = props;
75
- const sanitizedProps = filterStylingProps(rest, overStyled);
77
+ // Props this component intentionally doesn't support — deleted at runtime so they can't leak
78
+ // through even if a caller forces them via plain JavaScript, bypassing the Omit<> above.
79
+ const UNSUPPORTED_PROPS = [
80
+ "size", // Recursica controls sizing via design tokens, not MUI's native small/medium size
81
+ "color", // Colors are token-driven; MUI's native palette isn't exposed
82
+ ] as const satisfies readonly (keyof MuiRadioProps)[];
83
+
84
+ const sanitizedProps = omitUnsupportedProps(
85
+ filterStylingProps(rest, overStyled),
86
+ UNSUPPORTED_PROPS,
87
+ );
76
88
  const restRecord = sanitizedProps as Record<string, unknown>;
77
89
 
78
- // Actively delete dimension bindings that bypass the abstraction
79
- delete restRecord["size"];
80
- delete restRecord["color"];
81
-
82
90
  // NOTE: MUI's actual prop is "classes", not "classNames" (that's Mantine's naming) — this
83
91
  // was reading the wrong key and silently doing nothing. Fixed. Also "body"/"inner"/"radio"/
84
92
  // "icon"/"labelWrapper"/"label" below were never real MUI `classes` slots (MUI's Radio only
@@ -89,27 +97,14 @@ export const Radio = forwardRef<HTMLInputElement, RadioProps>(
89
97
  // only the label showed. Fixed by keeping only the real slots here and drawing the circle as
90
98
  // our own combined icon/checkedIcon node instead (see radioNode below), same pattern already
91
99
  // used by Checkbox.
92
- const mergedClassNames: Partial<Record<string, string>> = {
93
- root: styles.root,
94
- checked: styles.checked,
95
- disabled: styles.disabled,
96
- };
97
-
98
- const classesProp = restRecord.classes;
99
- if (
100
- classesProp &&
101
- typeof classesProp === "object" &&
102
- !Array.isArray(classesProp)
103
- ) {
104
- const o = classesProp as Partial<Record<string, string>>;
105
- mergedClassNames.root = o.root ? `${styles.root} ${o.root}` : styles.root;
106
- mergedClassNames.checked = o.checked
107
- ? `${styles.checked} ${o.checked}`
108
- : styles.checked;
109
- mergedClassNames.disabled = o.disabled
110
- ? `${styles.disabled} ${o.disabled}`
111
- : styles.disabled;
112
- }
100
+ const mergedClassNames = mergeClassNames(
101
+ {
102
+ root: styles.root,
103
+ checked: styles.checked,
104
+ disabled: styles.disabled,
105
+ },
106
+ restRecord.classes as Partial<Record<string, string>> | undefined,
107
+ );
113
108
 
114
109
  const classNameProp = restRecord.className as string | undefined;
115
110
  const finalClass = classNameProp
@@ -73,11 +73,10 @@ export const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>(
73
73
  onChange,
74
74
  ...rest
75
75
  } = props;
76
+ // NOTE: MuiRadioGroupProps (via FormGroupProps) has no native "size" (or other
77
+ // Recursica-unsupported) prop to leak through here — nothing to add to an UNSUPPORTED_PROPS
78
+ // list beyond what filterStylingProps already blocks.
76
79
  const sanitizedProps = filterStylingProps(rest, overStyled);
77
- const restRecord = sanitizedProps as Record<string, unknown>;
78
-
79
- // Delete prohibited sizing hooks from bypassing the variables
80
- delete restRecord["size"];
81
80
 
82
81
  return (
83
82
  <WithReadOnlyWrapper
@@ -27,12 +27,12 @@ export const ReadOnlyTextField: React.FC<ReadOnlyTextFieldProps> = ({
27
27
  return (
28
28
  <Box
29
29
  component="p"
30
+ {...sanitizedProps}
30
31
  className={
31
32
  classNameProp
32
33
  ? `${styles.textField} ${classNameProp}`
33
34
  : styles.textField
34
35
  }
35
- {...sanitizedProps}
36
36
  >
37
37
  {value != null
38
38
  ? React.isValidElement(value)
@@ -9,14 +9,6 @@
9
9
  above that rule); needed because the item text-transform token has no valid scoped value
10
10
  */
11
11
 
12
- /* EXEMPTIONS:
13
- - segmented-control-item_properties_item_border-radius is ignored because the item border-radius
14
- is fully governed by the per-selection-state tokens (unselected/selected `properties_border-radius`,
15
- already applied to `.label` and `.control:global(.Mui-selected)` below); this generic, state-agnostic radius
16
- token has no distinct consumption site without conflicting with those state-specific overrides.
17
- The Mantine reference adapter exempts this same variable for the same reason. */
18
- /* recursica-ignore: --recursica_ui-kit_components_segmented-control-item_properties_item_border-radius */
19
-
20
12
  .root {
21
13
  background-color: var(
22
14
  --recursica_ui-kit_components_segmented-control_properties_colors_background-color
@@ -6,6 +6,8 @@ import {
6
6
  } from "@mui/material";
7
7
  import {
8
8
  filterStylingProps,
9
+ omitUnsupportedProps,
10
+ mergeClassNames,
9
11
  type RecursicaOverStyled,
10
12
  } from "../../utils/filterStylingProps";
11
13
  import styles from "./SegmentedControl.module.css";
@@ -34,27 +36,14 @@ function useSegmentedControlClassNames(restRecord: Record<string, unknown>): {
34
36
  className: string;
35
37
  classNames: Partial<Record<string, string>>;
36
38
  } {
37
- const mergedClassNames: Partial<Record<string, string>> = {
38
- root: styles.root,
39
- control: styles.control,
40
- label: styles.label,
41
- };
42
-
43
- const classNamesProp = restRecord.classNames;
44
- if (
45
- classNamesProp &&
46
- typeof classNamesProp === "object" &&
47
- !Array.isArray(classNamesProp)
48
- ) {
49
- const o = classNamesProp as Partial<Record<string, string>>;
50
- mergedClassNames.root = o.root ? `${styles.root} ${o.root}` : styles.root;
51
- mergedClassNames.control = o.control
52
- ? `${styles.control} ${o.control}`
53
- : styles.control;
54
- mergedClassNames.label = o.label
55
- ? `${styles.label} ${o.label}`
56
- : styles.label;
57
- }
39
+ const mergedClassNames = mergeClassNames(
40
+ {
41
+ root: styles.root,
42
+ control: styles.control,
43
+ label: styles.label,
44
+ },
45
+ restRecord.classNames as Partial<Record<string, string>> | undefined,
46
+ );
58
47
 
59
48
  const classNameProp = restRecord.className as string | undefined;
60
49
  const finalClass = classNameProp
@@ -77,11 +66,17 @@ const _SegmentedControl = forwardRef<HTMLDivElement, SegmentedControlProps>(
77
66
  },
78
67
  ref,
79
68
  ) {
80
- const sanitizedProps = filterStylingProps(rest, overStyled);
81
- const restRecord = sanitizedProps as Record<string, unknown>;
69
+ // Props this component intentionally doesn't support — deleted at runtime so they can't leak
70
+ // through even if a caller forces them via plain JavaScript, bypassing the Omit<> above.
71
+ const UNSUPPORTED_PROPS = [
72
+ "disabled", // Recursica controls per-item disabled state via `data.disabled`, not the group
73
+ ] as const satisfies readonly (keyof MuiSegmentedControlProps)[];
82
74
 
83
- // Explicitly prevent consumers from bypassing the disabled restriction
84
- delete restRecord["disabled"];
75
+ const sanitizedProps = omitUnsupportedProps(
76
+ filterStylingProps(rest, overStyled),
77
+ UNSUPPORTED_PROPS,
78
+ );
79
+ const restRecord = sanitizedProps as Record<string, unknown>;
85
80
 
86
81
  const stylingParams = useSegmentedControlClassNames(restRecord);
87
82