@recursica/mantine-adapter 0.42.1 → 0.43.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 (57) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/index.d.ts +93 -15
  3. package/dist/mantine-adapter.cjs +2 -2
  4. package/dist/mantine-adapter.cjs.map +1 -1
  5. package/dist/mantine-adapter.js +3547 -3292
  6. package/dist/mantine-adapter.js.map +1 -1
  7. package/package.json +1 -1
  8. package/src/components/Accordion/Accordion.tsx +32 -33
  9. package/src/components/AssistiveElement/AssistiveElement.tsx +1 -1
  10. package/src/components/AutoComplete/AutoComplete.tsx +26 -38
  11. package/src/components/Avatar/Avatar.tsx +22 -23
  12. package/src/components/Badge/Badge.tsx +12 -18
  13. package/src/components/Breadcrumb/Breadcrumb.tsx +10 -18
  14. package/src/components/Button/Button.tsx +31 -30
  15. package/src/components/Card/Card.tsx +48 -27
  16. package/src/components/Checkbox/Checkbox.tsx +29 -42
  17. package/src/components/Checkbox/CheckboxGroup.tsx +12 -5
  18. package/src/components/Chip/Chip.tsx +34 -27
  19. package/src/components/DatePicker/DatePicker.tsx +46 -47
  20. package/src/components/Dropdown/BareDropdown.tsx +25 -13
  21. package/src/components/Dropdown/Dropdown.tsx +25 -38
  22. package/src/components/FileInput/FileInput.tsx +1 -1
  23. package/src/components/FileUpload/FileUpload.tsx +1 -1
  24. package/src/components/HoverCard/HoverCard.tsx +18 -34
  25. package/src/components/Label/Label.tsx +20 -21
  26. package/src/components/Link/Link.tsx +6 -14
  27. package/src/components/Loader/Loader.tsx +8 -16
  28. package/src/components/Menu/Menu.tsx +55 -48
  29. package/src/components/Modal/Modal.tsx +134 -96
  30. package/src/components/NumberInput/NumberInput.tsx +14 -7
  31. package/src/components/Pagination/Pagination.tsx +25 -34
  32. package/src/components/Panel/Panel.tsx +19 -26
  33. package/src/components/Popover/Popover.tsx +19 -31
  34. package/src/components/Radio/Radio.tsx +32 -43
  35. package/src/components/Radio/RadioGroup.tsx +11 -4
  36. package/src/components/ReadOnlyField/ReadOnlyField.tsx +1 -1
  37. package/src/components/ReadOnlyField/ReadOnlyTextField.tsx +1 -1
  38. package/src/components/ReadOnlyField/WithReadOnlyWrapper.tsx +1 -1
  39. package/src/components/SegmentedControl/SegmentedControl.tsx +29 -33
  40. package/src/components/Slider/Slider.tsx +30 -43
  41. package/src/components/Stepper/Stepper.tsx +20 -13
  42. package/src/components/Switch/Switch.tsx +45 -45
  43. package/src/components/Switch/SwitchGroup.tsx +11 -4
  44. package/src/components/Table/Table.tsx +1 -1
  45. package/src/components/Tabs/Tabs.tsx +13 -6
  46. package/src/components/Text/Text.tsx +1 -1
  47. package/src/components/TextArea/TextArea.tsx +21 -25
  48. package/src/components/TextField/TextField.tsx +23 -30
  49. package/src/components/TimePicker/TimePicker.tsx +26 -12
  50. package/src/components/Timeline/Timeline.tsx +7 -21
  51. package/src/components/Timeline/TimelineItem.tsx +14 -26
  52. package/src/components/Title/Title.tsx +12 -2
  53. package/src/components/Toast/Toast.tsx +32 -31
  54. package/src/components/Tooltip/Tooltip.tsx +18 -34
  55. package/src/components/TransferList/TransferList.tsx +1 -1
  56. package/src/components/Tree/Tree.tsx +8 -8
  57. package/src/utils/filterStylingProps.ts +4 -0
@@ -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";
@@ -28,6 +30,14 @@ export interface RecursicaDropdownProps
28
30
 
29
31
  export type DropdownProps = RecursicaOverStyled<RecursicaDropdownProps>;
30
32
 
33
+ // Props this component intentionally doesn't support — deleted at runtime so they can't leak
34
+ // through even if a caller forces them via plain JavaScript, bypassing the `Omit<>` above.
35
+ const UNSUPPORTED_PROPS = [
36
+ "size", // Recursica controls sizing via the `size` variant + design tokens, not raw dimensions.
37
+ "variant", // Colors/variants are token-driven; the library's native palette isn't exposed.
38
+ "radius", // Recursica does not expose the library's native corner-radius system.
39
+ ] as const satisfies readonly (keyof MantineSelectProps)[];
40
+
31
41
  export const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
32
42
  function Dropdown(props, ref) {
33
43
  const {
@@ -60,46 +70,23 @@ export const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
60
70
  data,
61
71
  ...rest
62
72
  } = props;
63
- const sanitizedProps = filterStylingProps(rest, overStyled);
73
+ const sanitizedProps = omitUnsupportedProps(
74
+ filterStylingProps(rest, overStyled) as Record<string, unknown>,
75
+ UNSUPPORTED_PROPS,
76
+ );
64
77
  const restRecord = sanitizedProps as Record<string, unknown>;
65
78
 
66
- // Delete prohibited sizing hooks from bypassing variables natively
67
- delete restRecord["size"];
68
- delete restRecord["variant"];
69
- delete restRecord["radius"];
70
-
71
79
  // Securely map core native blocks down ensuring nested CSS modules map precisely
72
- const mergedClassNames: Partial<Record<string, string>> = {
73
- wrapper: styles.root, // The nested Input internal relative wrapper bounding box
74
- input: styles.input,
75
- section: styles.section,
76
- dropdown: styles.dropdown,
77
- option: styles.option,
78
- };
79
-
80
- const classNamesProp = restRecord.classNames;
81
- if (
82
- classNamesProp &&
83
- typeof classNamesProp === "object" &&
84
- !Array.isArray(classNamesProp)
85
- ) {
86
- const o = classNamesProp as Partial<Record<string, string>>;
87
- mergedClassNames.wrapper = o.wrapper
88
- ? `${styles.root} ${o.wrapper}`
89
- : styles.root;
90
- mergedClassNames.input = o.input
91
- ? `${styles.input} ${o.input}`
92
- : styles.input;
93
- mergedClassNames.section = o.section
94
- ? `${styles.section} ${o.section}`
95
- : styles.section;
96
- mergedClassNames.dropdown = o.dropdown
97
- ? `${styles.dropdown} ${o.dropdown}`
98
- : styles.dropdown;
99
- mergedClassNames.option = o.option
100
- ? `${styles.option} ${o.option}`
101
- : styles.option;
102
- }
80
+ const mergedClassNames = mergeClassNames(
81
+ {
82
+ wrapper: styles.root, // The nested Input internal relative wrapper bounding box
83
+ input: styles.input,
84
+ section: styles.section,
85
+ dropdown: styles.dropdown,
86
+ option: styles.option,
87
+ },
88
+ restRecord.classNames as Partial<Record<string, string>> | undefined,
89
+ );
103
90
 
104
91
  const injectedStyles = {
105
92
  ...((style as React.CSSProperties) || {}),
@@ -146,6 +133,7 @@ export const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
146
133
  /* Naked Select execution safely decoupled from Mantine's macro Input.Wrapper DOM hooks */
147
134
  <MantineSelect
148
135
  ref={ref}
136
+ {...(sanitizedProps as unknown as MantineSelectProps)}
149
137
  classNames={mergedClassNames}
150
138
  disabled={disabled}
151
139
  value={value}
@@ -169,7 +157,6 @@ export const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
169
157
  "data-error": error ? "true" : undefined,
170
158
  },
171
159
  }}
172
- {...(sanitizedProps as unknown as MantineSelectProps)}
173
160
  />
174
161
  }
175
162
  />
@@ -309,6 +309,7 @@ export const FileInput = forwardRef<HTMLDivElement, FileInputProps>(
309
309
  >
310
310
  <div
311
311
  ref={ref}
312
+ {...restRecord}
312
313
  className={styles.root}
313
314
  role="button"
314
315
  aria-label={browseLabel}
@@ -324,7 +325,6 @@ export const FileInput = forwardRef<HTMLDivElement, FileInputProps>(
324
325
  onDragLeave={interactive ? handleDragLeave : undefined}
325
326
  onDragOver={interactive ? handleDragOver : undefined}
326
327
  onDrop={interactive ? handleDrop : undefined}
327
- {...restRecord}
328
328
  >
329
329
  <span className={styles.leadingIcon} aria-hidden>
330
330
  {icon ?? <UploadIcon />}
@@ -268,10 +268,10 @@ export const FileUpload = forwardRef<HTMLDivElement, FileUploadProps>(
268
268
  >
269
269
  <div
270
270
  ref={ref}
271
+ {...restRecord}
271
272
  className={styles.root}
272
273
  data-disabled={disabled ? "true" : undefined}
273
274
  data-error={effectiveError ? "true" : undefined}
274
- {...restRecord}
275
275
  >
276
276
  {!readOnly && (
277
277
  <div
@@ -6,6 +6,7 @@ import {
6
6
  } from "@mantine/core";
7
7
  import {
8
8
  filterStylingProps,
9
+ mergeClassNames,
9
10
  type RecursicaOverStyled,
10
11
  } from "../../utils/filterStylingProps";
11
12
  import styles from "./HoverCard.module.css";
@@ -42,54 +43,37 @@ export type HoverCardProps = RecursicaOverStyled<
42
43
  const HoverCardBase = function HoverCard({
43
44
  overStyled = false,
44
45
  withBeak = true,
46
+ position = "top", // Recursica default; Mantine defaults to "bottom". Consumers can still override.
47
+ // arrowSize must be a JS number prop — Mantine uses it for inline width/height
48
+ // and positioning offset (-arrowSize/2) calculations that cannot be CSS-driven.
49
+ // Default to 16 to match the Recursica beak-size token (16px). Consumers can still override.
50
+ arrowSize = 16,
51
+ withArrow,
45
52
  ...rest
46
53
  }: HoverCardProps) {
47
54
  const sanitizedProps = filterStylingProps(rest, overStyled);
55
+ const restRecord = sanitizedProps as Record<string, unknown>;
48
56
 
49
57
  // Bind CSS module classes to Mantine's internal classNames API
50
- const mergedClassNames: Partial<Record<string, string>> = {
51
- dropdown: styles.dropdown,
52
- arrow: styles.arrow,
53
- };
54
-
55
- const classNamesProp = (sanitizedProps as Record<string, unknown>).classNames;
56
- if (
57
- classNamesProp &&
58
- typeof classNamesProp === "object" &&
59
- !Array.isArray(classNamesProp)
60
- ) {
61
- const o = classNamesProp as Record<string, string>;
62
- Object.keys(o).forEach((key) => {
63
- if (mergedClassNames[key]) {
64
- mergedClassNames[key] = `${mergedClassNames[key]} ${o[key]}`;
65
- } else {
66
- mergedClassNames[key] = o[key];
67
- }
68
- });
69
- }
70
-
71
- // arrowSize must be a JS number prop — Mantine uses it for inline width/height
72
- // and positioning offset (-arrowSize/2) calculations that cannot be CSS-driven.
73
- // Default to 16 to match the Recursica beak-size token (16px).
74
- const arrowSize =
75
- ((sanitizedProps as Record<string, unknown>).arrowSize as
76
- | number
77
- | undefined) ?? 16;
58
+ const mergedClassNames = mergeClassNames(
59
+ {
60
+ dropdown: styles.dropdown,
61
+ arrow: styles.arrow,
62
+ },
63
+ restRecord.classNames as Partial<Record<string, string>> | undefined,
64
+ );
78
65
 
79
66
  // Resolve withBeak (Recursica) vs withArrow (Mantine).
80
67
  // withBeak takes precedence when both are provided.
81
- const withArrow = (sanitizedProps as Record<string, unknown>).withArrow as
82
- | boolean
83
- | undefined;
84
68
  const resolvedWithArrow = withBeak ?? withArrow;
85
69
 
86
70
  return (
87
71
  <MantineHoverCard
88
- position="top" /* Recursica default; Mantine defaults to "bottom" */
72
+ {...(sanitizedProps as unknown as MantineHoverCardProps)}
73
+ position={position}
89
74
  arrowSize={arrowSize}
90
75
  withArrow={resolvedWithArrow}
91
76
  classNames={mergedClassNames}
92
- {...(sanitizedProps as unknown as MantineHoverCardProps)}
93
77
  />
94
78
  );
95
79
  };
@@ -128,8 +112,8 @@ const HoverCardDropdown = function HoverCardDropdown({
128
112
 
129
113
  return (
130
114
  <MantineHoverCard.Dropdown
131
- className={classNameProp}
132
115
  {...(sanitizedProps as unknown as MantineHoverCardDropdownProps)}
116
+ className={classNameProp}
133
117
  />
134
118
  );
135
119
  };
@@ -2,6 +2,8 @@ import React, { forwardRef } from "react";
2
2
  import { Input, type InputLabelProps } from "@mantine/core";
3
3
  import {
4
4
  filterStylingProps,
5
+ omitUnsupportedProps,
6
+ mergeClassNames,
5
7
  type RecursicaOverStyled,
6
8
  } from "../../utils/filterStylingProps";
7
9
  import styles from "./Label.module.css";
@@ -35,28 +37,25 @@ export const Label = forwardRef<HTMLLabelElement, LabelProps>(function Label(
35
37
  else if (labelOptionalText) resolvedOptionalText = labelOptionalText;
36
38
  }
37
39
 
38
- const sanitizedProps = filterStylingProps(rest, overStyled);
39
- const restRecord = sanitizedProps as Record<string, unknown>;
40
+ // Props this component intentionally doesn't support — deleted at runtime so they can't leak
41
+ // through even if a caller forces them via plain JavaScript, bypassing the `Omit<>` above.
42
+ const UNSUPPORTED_PROPS = [
43
+ "size", // Recursica controls label sizing via `labelSize` + design tokens, not Mantine's native `size`.
44
+ ] as const satisfies readonly (keyof InputLabelProps)[];
40
45
 
41
- const mergedClassNames: Partial<Record<string, string>> = {
42
- label: styles.root,
43
- required: styles.required,
44
- };
46
+ const sanitizedProps = omitUnsupportedProps(
47
+ filterStylingProps(rest, overStyled),
48
+ UNSUPPORTED_PROPS,
49
+ );
50
+ const restRecord = sanitizedProps as Record<string, unknown>;
45
51
 
46
- const classNamesProp = restRecord.classNames;
47
- if (
48
- classNamesProp &&
49
- typeof classNamesProp === "object" &&
50
- !Array.isArray(classNamesProp)
51
- ) {
52
- const o = classNamesProp as Partial<Record<string, string>>;
53
- mergedClassNames.label = o.label
54
- ? `${styles.root} ${o.label}`
55
- : styles.root;
56
- mergedClassNames.required = o.required
57
- ? `${styles.required} ${o.required}`
58
- : styles.required;
59
- }
52
+ const mergedClassNames = mergeClassNames(
53
+ {
54
+ label: styles.root,
55
+ required: styles.required,
56
+ },
57
+ restRecord.classNames as Partial<Record<string, string>> | undefined,
58
+ );
60
59
 
61
60
  const classNameProp = restRecord.className as string | undefined;
62
61
  const finalClass = classNameProp
@@ -66,12 +65,12 @@ export const Label = forwardRef<HTMLLabelElement, LabelProps>(function Label(
66
65
  return (
67
66
  <Input.Label
68
67
  ref={ref}
68
+ {...sanitizedProps}
69
69
  className={finalClass}
70
70
  classNames={mergedClassNames}
71
71
  data-size={labelSize}
72
72
  data-alignment={resolvedAlignment}
73
73
  required={required && !labelWithEditIcon}
74
- {...sanitizedProps}
75
74
  >
76
75
  <span className={styles.labelText}>{children}</span>
77
76
  {resolvedOptionalText && (
@@ -6,6 +6,7 @@ import {
6
6
  } from "@mantine/core";
7
7
  import {
8
8
  filterStylingProps,
9
+ mergeClassNames,
9
10
  type RecursicaOverStyled,
10
11
  } from "../../utils/filterStylingProps";
11
12
  import styles from "./Link.module.css";
@@ -23,19 +24,10 @@ const _Link = forwardRef<HTMLAnchorElement, LinkProps>(function Link(
23
24
  const sanitizedProps = filterStylingProps(rest, overStyled);
24
25
  const restRecord = sanitizedProps as Record<string, unknown>;
25
26
 
26
- const mergedClassNames: Partial<Record<string, string>> = {
27
- root: styles.root,
28
- };
29
-
30
- const classNamesProp = restRecord.classNames;
31
- if (
32
- classNamesProp &&
33
- typeof classNamesProp === "object" &&
34
- !Array.isArray(classNamesProp)
35
- ) {
36
- const o = classNamesProp as Partial<Record<string, string>>;
37
- mergedClassNames.root = o.root ? `${styles.root} ${o.root}` : styles.root;
38
- }
27
+ const mergedClassNames = mergeClassNames(
28
+ { root: styles.root },
29
+ restRecord.classNames as Partial<Record<string, string>> | undefined,
30
+ );
39
31
 
40
32
  const classNameProp = restRecord.className as string | undefined;
41
33
  const finalClass = classNameProp
@@ -45,11 +37,11 @@ const _Link = forwardRef<HTMLAnchorElement, LinkProps>(function Link(
45
37
  return (
46
38
  <MantineAnchor
47
39
  ref={ref}
40
+ {...(sanitizedProps as Record<string, unknown>)}
48
41
  className={finalClass}
49
42
  classNames={mergedClassNames}
50
43
  underline="never"
51
44
  {...(icon ? { "data-has-icon": "" } : {})}
52
- {...(sanitizedProps as Record<string, unknown>)}
53
45
  >
54
46
  {icon && (
55
47
  <span className={styles.iconWrapper} aria-hidden>
@@ -5,6 +5,7 @@ import {
5
5
  } from "@mantine/core";
6
6
  import {
7
7
  filterStylingProps,
8
+ mergeClassNames,
8
9
  type RecursicaOverStyled,
9
10
  } from "../../utils/filterStylingProps";
10
11
  import styles from "./Loader.module.css";
@@ -33,23 +34,14 @@ export const Loader = forwardRef<HTMLSpanElement, LoaderProps>(function Loader(
33
34
  // Strip all visual override injections unless developer has specifically opted into overStyling.
34
35
  // External layout props like margins are safely preserved.
35
36
  const sanitizedProps = filterStylingProps(rest, overStyled);
37
+ const restRecord = sanitizedProps as Record<string, unknown>;
36
38
 
37
- const mergedClassNames: Partial<Record<string, string>> = {
38
- root: styles.root,
39
- };
40
-
41
- const classNamesProp = (sanitizedProps as Record<string, unknown>).classNames;
42
- if (
43
- classNamesProp &&
44
- typeof classNamesProp === "object" &&
45
- !Array.isArray(classNamesProp)
46
- ) {
47
- const o = classNamesProp as Partial<Record<string, string>>;
48
- mergedClassNames.root = o.root ? `${styles.root} ${o.root}` : styles.root;
49
- }
39
+ const mergedClassNames = mergeClassNames(
40
+ { root: styles.root },
41
+ restRecord.classNames as Partial<Record<string, string>> | undefined,
42
+ );
50
43
 
51
- const classNameProp = (sanitizedProps as Record<string, unknown>)
52
- .className as string | undefined;
44
+ const classNameProp = restRecord.className as string | undefined;
53
45
  const finalClass = classNameProp
54
46
  ? `${styles.root} ${classNameProp}`
55
47
  : styles.root;
@@ -57,10 +49,10 @@ export const Loader = forwardRef<HTMLSpanElement, LoaderProps>(function Loader(
57
49
  return (
58
50
  <MantineLoader
59
51
  ref={ref}
52
+ {...sanitizedProps}
60
53
  type={variant}
61
54
  data-variant={variant}
62
55
  data-size={resolvedSize}
63
- {...sanitizedProps}
64
56
  className={finalClass}
65
57
  classNames={mergedClassNames}
66
58
  />
@@ -14,6 +14,8 @@ import {
14
14
  } from "@mantine/core";
15
15
  import {
16
16
  filterStylingProps,
17
+ omitUnsupportedProps,
18
+ mergeClassNames,
17
19
  type RecursicaOverStyled,
18
20
  } from "../../utils/filterStylingProps";
19
21
  import styles from "./Menu.module.css";
@@ -46,38 +48,26 @@ export type MenuProps = RecursicaOverStyled<
46
48
 
47
49
  const MenuBase = function Menu({ overStyled = false, ...rest }: MenuProps) {
48
50
  const sanitizedProps = filterStylingProps(rest, overStyled);
51
+ const restRecord = sanitizedProps as Record<string, unknown>;
49
52
 
50
53
  // Bind CSS module classes to Mantine's internal classNames API
51
- const mergedClassNames: Partial<Record<string, string>> = {
52
- dropdown: styles.dropdown,
53
- item: styles.item,
54
- itemLabel: styles.itemLabel,
55
- itemSection: styles.itemSection,
56
- divider: styles.divider,
57
- label: styles.label,
58
- chevron: styles.chevron,
59
- };
60
-
61
- const classNamesProp = (sanitizedProps as Record<string, unknown>).classNames;
62
- if (
63
- classNamesProp &&
64
- typeof classNamesProp === "object" &&
65
- !Array.isArray(classNamesProp)
66
- ) {
67
- const o = classNamesProp as Record<string, string>;
68
- Object.keys(o).forEach((key) => {
69
- if (mergedClassNames[key]) {
70
- mergedClassNames[key] = `${mergedClassNames[key]} ${o[key]}`;
71
- } else {
72
- mergedClassNames[key] = o[key];
73
- }
74
- });
75
- }
54
+ const mergedClassNames = mergeClassNames(
55
+ {
56
+ dropdown: styles.dropdown,
57
+ item: styles.item,
58
+ itemLabel: styles.itemLabel,
59
+ itemSection: styles.itemSection,
60
+ divider: styles.divider,
61
+ label: styles.label,
62
+ chevron: styles.chevron,
63
+ },
64
+ restRecord.classNames as Partial<Record<string, string>> | undefined,
65
+ );
76
66
 
77
67
  return (
78
68
  <MantineMenu
79
- classNames={mergedClassNames}
80
69
  {...(sanitizedProps as unknown as MantineMenuProps)}
70
+ classNames={mergedClassNames}
81
71
  />
82
72
  );
83
73
  };
@@ -91,10 +81,19 @@ MenuBase.displayName = "Menu";
91
81
  * Wrapper for the element that triggers the menu.
92
82
  * Requires a single child element that supports ref forwarding.
93
83
  */
94
- export type MenuTargetProps = MantineMenuTargetProps;
84
+ export type MenuTargetProps = RecursicaOverStyled<MantineMenuTargetProps>;
95
85
 
96
- const MenuTarget = function MenuTarget(props: MenuTargetProps) {
97
- return <MantineMenu.Target {...props} />;
86
+ const MenuTarget = function MenuTarget({
87
+ overStyled = false,
88
+ ...rest
89
+ }: MenuTargetProps) {
90
+ const sanitizedProps = filterStylingProps(rest, overStyled);
91
+
92
+ return (
93
+ <MantineMenu.Target
94
+ {...(sanitizedProps as unknown as MantineMenuTargetProps)}
95
+ />
96
+ );
98
97
  };
99
98
  MenuTarget.displayName = "MenuTarget";
100
99
 
@@ -113,9 +112,9 @@ const MenuDropdown = forwardRef<HTMLDivElement, MenuDropdownProps>(
113
112
 
114
113
  return (
115
114
  <MantineMenu.Dropdown
115
+ {...(sanitizedProps as unknown as MantineMenuDropdownProps)}
116
116
  ref={ref}
117
117
  className={classNameProp}
118
- {...(sanitizedProps as unknown as MantineMenuDropdownProps)}
119
118
  />
120
119
  );
121
120
  },
@@ -129,8 +128,7 @@ MenuDropdown.displayName = "MenuDropdown";
129
128
  /**
130
129
  * An individual actionable item within the menu dropdown.
131
130
  *
132
- * **Note:** Mantine's `color` prop is stripped in strict mode to enforce
133
- * design token adherence. Use `overStyled={true}` if you need to bypass.
131
+ * **Note:** Mantine's `color` prop is stripped to enforce design token adherence.
134
132
  */
135
133
  export type MenuItemProps = RecursicaOverStyled<
136
134
  Omit<MantineMenuItemProps, "color">
@@ -138,21 +136,25 @@ export type MenuItemProps = RecursicaOverStyled<
138
136
 
139
137
  const _MenuItem = forwardRef<HTMLButtonElement, MenuItemProps>(
140
138
  function MenuItem({ overStyled = false, ...rest }, ref) {
141
- const sanitizedProps = filterStylingProps(rest, overStyled);
139
+ // Props this component intentionally doesn't support — deleted at runtime so they can't leak
140
+ // through even if a caller forces them via plain JavaScript, bypassing the `Omit<>` above.
141
+ const UNSUPPORTED_PROPS = [
142
+ "color", // Colors are token-driven; the library's native palette isn't exposed.
143
+ ] as const satisfies readonly (keyof MantineMenuItemProps)[];
144
+
145
+ const sanitizedProps = omitUnsupportedProps(
146
+ filterStylingProps(rest, overStyled) as Record<string, unknown>,
147
+ UNSUPPORTED_PROPS,
148
+ );
142
149
  const restRecord = sanitizedProps as Record<string, unknown>;
143
150
 
144
- // Strip Mantine's `color` prop to enforce token-driven styling
145
- if (!overStyled) {
146
- delete restRecord["color"];
147
- }
148
-
149
151
  const classNameProp = restRecord.className as string | undefined;
150
152
 
151
153
  return (
152
154
  <MantineMenu.Item
155
+ {...(sanitizedProps as unknown as MantineMenuItemProps)}
153
156
  ref={ref}
154
157
  className={classNameProp}
155
- {...(sanitizedProps as unknown as MantineMenuItemProps)}
156
158
  />
157
159
  );
158
160
  },
@@ -185,9 +187,9 @@ const MenuDivider = forwardRef<HTMLHRElement, MenuDividerProps>(
185
187
 
186
188
  return (
187
189
  <MantineMenu.Divider
190
+ {...(sanitizedProps as unknown as MantineMenuDividerProps)}
188
191
  ref={ref}
189
192
  className={classNameProp}
190
- {...(sanitizedProps as unknown as MantineMenuDividerProps)}
191
193
  />
192
194
  );
193
195
  },
@@ -211,9 +213,9 @@ const MenuLabel = forwardRef<HTMLDivElement, MenuLabelProps>(function MenuLabel(
211
213
 
212
214
  return (
213
215
  <MantineMenu.Label
216
+ {...(sanitizedProps as unknown as MantineMenuLabelProps)}
214
217
  ref={ref}
215
218
  className={classNameProp}
216
- {...(sanitizedProps as unknown as MantineMenuLabelProps)}
217
219
  />
218
220
  );
219
221
  });
@@ -257,20 +259,25 @@ export type MenuSubItemProps = RecursicaOverStyled<
257
259
 
258
260
  const _MenuSubItem = forwardRef<HTMLButtonElement, MenuSubItemProps>(
259
261
  function MenuSubItem({ overStyled = false, ...rest }, ref) {
260
- const sanitizedProps = filterStylingProps(rest, overStyled);
262
+ // Props this component intentionally doesn't support — deleted at runtime so they can't leak
263
+ // through even if a caller forces them via plain JavaScript, bypassing the `Omit<>` above.
264
+ const UNSUPPORTED_PROPS = [
265
+ "color", // Colors are token-driven; the library's native palette isn't exposed.
266
+ ] as const satisfies readonly (keyof MantineMenuItemProps)[];
267
+
268
+ const sanitizedProps = omitUnsupportedProps(
269
+ filterStylingProps(rest, overStyled) as Record<string, unknown>,
270
+ UNSUPPORTED_PROPS,
271
+ );
261
272
  const restRecord = sanitizedProps as Record<string, unknown>;
262
273
 
263
- if (!overStyled) {
264
- delete restRecord["color"];
265
- }
266
-
267
274
  const classNameProp = restRecord.className as string | undefined;
268
275
 
269
276
  return (
270
277
  <MantineMenu.Sub.Item
278
+ {...(sanitizedProps as unknown as MantineMenuItemProps)}
271
279
  ref={ref}
272
280
  className={classNameProp}
273
- {...(sanitizedProps as unknown as MantineMenuItemProps)}
274
281
  />
275
282
  );
276
283
  },
@@ -302,9 +309,9 @@ const MenuSubDropdown = forwardRef<HTMLDivElement, MenuSubDropdownProps>(
302
309
 
303
310
  return (
304
311
  <MantineMenu.Sub.Dropdown
312
+ {...(sanitizedProps as unknown as MantineMenuSubDropdownProps)}
305
313
  ref={ref}
306
314
  className={classNameProp}
307
- {...(sanitizedProps as unknown as MantineMenuSubDropdownProps)}
308
315
  />
309
316
  );
310
317
  },