@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 RecursicaTextAreaProps
28
30
 
29
31
  export type TextAreaProps = RecursicaOverStyled<RecursicaTextAreaProps>;
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 MantineTextareaProps)[];
40
+
31
41
  export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
32
42
  function TextArea(props, ref) {
33
43
  const {
@@ -58,34 +68,20 @@ export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
58
68
  defaultValue,
59
69
  ...rest
60
70
  } = props;
61
- const sanitizedProps = filterStylingProps(rest, overStyled);
71
+ const sanitizedProps = omitUnsupportedProps(
72
+ filterStylingProps(rest, overStyled) as Record<string, unknown>,
73
+ UNSUPPORTED_PROPS,
74
+ );
62
75
  const restRecord = sanitizedProps as Record<string, unknown>;
63
76
 
64
- // Delete prohibited sizing hooks from bypassing variables natively
65
- delete restRecord["size"];
66
- delete restRecord["variant"];
67
- delete restRecord["radius"];
68
-
69
77
  // Securely map core native blocks down ensuring nested CSS modules map precisely
70
- const mergedClassNames: Partial<Record<string, string>> = {
71
- wrapper: styles.root, // The nested Input internal relative wrapper bounding box
72
- input: styles.input,
73
- };
74
-
75
- const classNamesProp = restRecord.classNames;
76
- if (
77
- classNamesProp &&
78
- typeof classNamesProp === "object" &&
79
- !Array.isArray(classNamesProp)
80
- ) {
81
- const o = classNamesProp as Partial<Record<string, string>>;
82
- mergedClassNames.wrapper = o.wrapper
83
- ? `${styles.root} ${o.wrapper}`
84
- : styles.root;
85
- mergedClassNames.input = o.input
86
- ? `${styles.input} ${o.input}`
87
- : styles.input;
88
- }
78
+ const mergedClassNames = mergeClassNames(
79
+ {
80
+ wrapper: styles.root, // The nested Input internal relative wrapper bounding box
81
+ input: styles.input,
82
+ },
83
+ restRecord.classNames as Partial<Record<string, string>> | undefined,
84
+ );
89
85
 
90
86
  const wrapperClass = className
91
87
  ? `${styles.layoutOverride} ${className}`
@@ -3,6 +3,8 @@ import { Input, type InputProps, type InputWrapperProps } from "@mantine/core";
3
3
  import { type ReadOnlyControlProps } from "@recursica/adapter-common";
4
4
  import {
5
5
  filterStylingProps,
6
+ omitUnsupportedProps,
7
+ mergeClassNames,
6
8
  type RecursicaOverStyled,
7
9
  } from "../../utils/filterStylingProps";
8
10
  import { type RecursicaFormControlWrapperProps } from "../FormControlWrapper/FormControlWrapper";
@@ -30,6 +32,14 @@ export interface RecursicaTextFieldProps
30
32
 
31
33
  export type TextFieldProps = RecursicaOverStyled<RecursicaTextFieldProps>;
32
34
 
35
+ // Props this component intentionally doesn't support — deleted at runtime so they can't leak
36
+ // through even if a caller forces them via plain JavaScript, bypassing the `Omit<>` above.
37
+ const UNSUPPORTED_PROPS = [
38
+ "size", // Recursica controls sizing via the `size` variant + design tokens, not raw dimensions.
39
+ "variant", // Colors/variants are token-driven; the library's native palette isn't exposed.
40
+ "radius", // Recursica does not expose the library's native corner-radius system.
41
+ ] as const satisfies readonly (keyof InputProps)[];
42
+
33
43
  export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
34
44
  function TextField(props, ref) {
35
45
  const {
@@ -60,38 +70,21 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
60
70
  defaultValue,
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
- };
77
-
78
- const classNamesProp = restRecord.classNames;
79
- if (
80
- classNamesProp &&
81
- typeof classNamesProp === "object" &&
82
- !Array.isArray(classNamesProp)
83
- ) {
84
- const o = classNamesProp as Partial<Record<string, string>>;
85
- mergedClassNames.wrapper = o.wrapper
86
- ? `${styles.root} ${o.wrapper}`
87
- : styles.root;
88
- mergedClassNames.input = o.input
89
- ? `${styles.input} ${o.input}`
90
- : styles.input;
91
- mergedClassNames.section = o.section
92
- ? `${styles.section} ${o.section}`
93
- : styles.section;
94
- }
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
+ },
86
+ restRecord.classNames as Partial<Record<string, string>> | undefined,
87
+ );
95
88
 
96
89
  const wrapperClass = className
97
90
  ? `${styles.layoutOverride} ${className}`
@@ -127,6 +120,7 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
127
120
  /* Naked Input execution safely decoupled from Mantine's macro Input.Wrapper DOM hooks */
128
121
  <Input
129
122
  ref={ref}
123
+ {...(sanitizedProps as unknown as InputProps)}
130
124
  classNames={mergedClassNames}
131
125
  disabled={disabled}
132
126
  value={value}
@@ -135,7 +129,6 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
135
129
  "data-disabled": disabled ? "true" : undefined,
136
130
  "data-error": error ? "true" : undefined,
137
131
  }}
138
- {...(sanitizedProps as unknown as InputProps)}
139
132
  />
140
133
  }
141
134
  />
@@ -7,6 +7,8 @@ import { type InputWrapperProps } from "@mantine/core";
7
7
  import { type ReadOnlyControlProps } from "@recursica/adapter-common";
8
8
  import {
9
9
  filterStylingProps,
10
+ omitUnsupportedProps,
11
+ mergeClassNames,
10
12
  type RecursicaOverStyled,
11
13
  } from "../../utils/filterStylingProps";
12
14
  import { type RecursicaFormControlWrapperProps } from "../FormControlWrapper/FormControlWrapper";
@@ -102,6 +104,14 @@ export interface RecursicaTimePickerProps
102
104
 
103
105
  export type TimePickerProps = RecursicaOverStyled<RecursicaTimePickerProps>;
104
106
 
107
+ // Props this component intentionally doesn't support — deleted at runtime so they can't leak
108
+ // through even if a caller forces them via plain JavaScript, bypassing the `Omit<>` above.
109
+ const UNSUPPORTED_PROPS = [
110
+ "size", // Recursica controls sizing via the `size` variant + design tokens, not raw dimensions.
111
+ "variant", // Colors/variants are token-driven; the library's native palette isn't exposed.
112
+ "radius", // Recursica does not expose the library's native corner-radius system.
113
+ ] as const satisfies readonly (keyof MantineTimePickerProps)[];
114
+
105
115
  export const TimePicker = forwardRef<HTMLDivElement, TimePickerProps>(
106
116
  function TimePicker(props, ref) {
107
117
  const {
@@ -137,13 +147,12 @@ export const TimePicker = forwardRef<HTMLDivElement, TimePickerProps>(
137
147
  ...rest
138
148
  } = props;
139
149
 
140
- const sanitizedProps = filterStylingProps(rest, overStyled);
150
+ const sanitizedProps = omitUnsupportedProps(
151
+ filterStylingProps(rest, overStyled) as Record<string, unknown>,
152
+ UNSUPPORTED_PROPS,
153
+ );
141
154
  const restRecord = sanitizedProps as Record<string, unknown>;
142
155
 
143
- delete restRecord["size"];
144
- delete restRecord["variant"];
145
- delete restRecord["radius"];
146
-
147
156
  // Internal full 24-hour value. Needed because Mantine's TimePicker (hour/minute/second entry)
148
157
  // and our own BareDropdown (AM/PM) both mutate the same conceptual value — see
149
158
  // TIMEPICKER_IMPLEMENTATION_NOTES.md.
@@ -241,12 +250,18 @@ export const TimePicker = forwardRef<HTMLDivElement, TimePickerProps>(
241
250
  >
242
251
  <MantineTimePicker
243
252
  ref={ref}
244
- classNames={{
245
- wrapper: styles.timeWrapper,
246
- input: styles.timeInput,
247
- fieldsGroup: styles.fieldsGroup,
248
- field: styles.timeField,
249
- }}
253
+ {...(sanitizedProps as unknown as MantineTimePickerProps)}
254
+ classNames={mergeClassNames(
255
+ {
256
+ wrapper: styles.timeWrapper,
257
+ input: styles.timeInput,
258
+ fieldsGroup: styles.fieldsGroup,
259
+ field: styles.timeField,
260
+ },
261
+ restRecord.classNames as
262
+ | Partial<Record<string, string>>
263
+ | undefined,
264
+ )}
250
265
  disabled={disabled}
251
266
  value={internalValue}
252
267
  onChange={handleFieldChange}
@@ -259,7 +274,6 @@ export const TimePicker = forwardRef<HTMLDivElement, TimePickerProps>(
259
274
  // to seed the mount-time AM default onto Mantine's own hidden native select. See
260
275
  // TIMEPICKER_IMPLEMENTATION_NOTES.md.
261
276
  amPmRef={amPmRef}
262
- {...(sanitizedProps as unknown as MantineTimePickerProps)}
263
277
  />
264
278
  <BareDropdown
265
279
  overStyled
@@ -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 "./Timeline.module.css";
@@ -46,36 +47,21 @@ interface TimelineComponent
46
47
  const TimelineInner = React.forwardRef<HTMLDivElement, TimelineProps>(
47
48
  function Timeline({ overStyled = false, ...rest }, ref) {
48
49
  const sanitizedProps = filterStylingProps(rest, overStyled);
50
+ const restRecord = sanitizedProps as Record<string, unknown>;
49
51
 
50
- const mergedClassNames: Partial<Record<string, string>> = {
51
- root: styles.root,
52
- };
53
-
54
- const classNamesProp = (sanitizedProps as Record<string, unknown>)
55
- .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
- }
52
+ const mergedClassNames = mergeClassNames(
53
+ { root: styles.root },
54
+ restRecord.classNames as Partial<Record<string, string>> | undefined,
55
+ );
70
56
 
71
57
  return (
72
58
  <MantineTimeline
73
59
  ref={ref}
74
- classNames={mergedClassNames}
75
60
  {...(sanitizedProps as unknown as Omit<
76
61
  MantineTimelineProps,
77
62
  "color" | "radius" | "bulletSize" | "lineWidth"
78
63
  >)}
64
+ classNames={mergedClassNames}
79
65
  />
80
66
  );
81
67
  },
@@ -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 "./Timeline.module.css";
@@ -36,31 +37,18 @@ export const TimelineItem = React.forwardRef<HTMLDivElement, TimelineItemProps>(
36
37
  ref,
37
38
  ) {
38
39
  const sanitizedProps = filterStylingProps(rest, overStyled);
40
+ const restRecord = sanitizedProps as Record<string, unknown>;
39
41
 
40
- const mergedClassNames: Partial<Record<string, string>> = {
41
- item: styles.item,
42
- itemBody: styles.itemBody,
43
- itemContent: styles.itemContent,
44
- itemBullet: styles.itemBullet,
45
- itemTitle: styles.itemTitle,
46
- };
47
-
48
- const classNamesProp = (sanitizedProps as Record<string, unknown>)
49
- .classNames;
50
- if (
51
- classNamesProp &&
52
- typeof classNamesProp === "object" &&
53
- !Array.isArray(classNamesProp)
54
- ) {
55
- const o = classNamesProp as Record<string, string>;
56
- Object.keys(o).forEach((key) => {
57
- if (mergedClassNames[key]) {
58
- mergedClassNames[key] = `${mergedClassNames[key]} ${o[key]}`;
59
- } else {
60
- mergedClassNames[key] = o[key];
61
- }
62
- });
63
- }
42
+ const mergedClassNames = mergeClassNames(
43
+ {
44
+ item: styles.item,
45
+ itemBody: styles.itemBody,
46
+ itemContent: styles.itemContent,
47
+ itemBullet: styles.itemBullet,
48
+ itemTitle: styles.itemTitle,
49
+ },
50
+ restRecord.classNames as Partial<Record<string, string>> | undefined,
51
+ );
64
52
 
65
53
  // Embed timestamp inside children if provided, wrapped in a specific class
66
54
  const content = timestamp ? (
@@ -75,12 +63,12 @@ export const TimelineItem = React.forwardRef<HTMLDivElement, TimelineItemProps>(
75
63
  return (
76
64
  <MantineTimelineItem
77
65
  ref={ref}
78
- classNames={mergedClassNames}
79
- data-variant={bulletVariant}
80
66
  {...(sanitizedProps as unknown as Omit<
81
67
  MantineTimelineItemProps,
82
68
  "radius" | "color" | "lineVariant"
83
69
  >)}
70
+ classNames={mergedClassNames}
71
+ data-variant={bulletVariant}
84
72
  >
85
73
  {content}
86
74
  </MantineTimelineItem>
@@ -5,6 +5,7 @@ import {
5
5
  } from "@mantine/core";
6
6
  import {
7
7
  filterStylingProps,
8
+ omitUnsupportedProps,
8
9
  type RecursicaOverStyled,
9
10
  } from "../../utils/filterStylingProps";
10
11
 
@@ -21,7 +22,16 @@ export const Title = forwardRef<HTMLHeadingElement, TitleProps>(function Title(
21
22
  { overStyled = false, order = 1, ...rest },
22
23
  ref,
23
24
  ) {
24
- const sanitizedProps = filterStylingProps(rest, overStyled);
25
+ // Props this component intentionally doesn't support — deleted at runtime so they can't leak
26
+ // through even if a caller forces them via plain JavaScript, bypassing the `Omit<>` above.
27
+ const UNSUPPORTED_PROPS = [
28
+ "size", // Recursica controls Title sizing via the `order` prop + typography tokens, not Mantine's native `size`.
29
+ ] as const satisfies readonly (keyof MantineTitleProps)[];
30
+
31
+ const sanitizedProps = omitUnsupportedProps(
32
+ filterStylingProps(rest, overStyled),
33
+ UNSUPPORTED_PROPS,
34
+ );
25
35
  const classNameProp = (sanitizedProps as Record<string, unknown>)
26
36
  .className as string | undefined;
27
37
 
@@ -33,9 +43,9 @@ export const Title = forwardRef<HTMLHeadingElement, TitleProps>(function Title(
33
43
  return (
34
44
  <MantineTitle
35
45
  ref={ref}
46
+ {...(sanitizedProps as unknown as MantineTitleProps)}
36
47
  order={order}
37
48
  className={mergedClassName}
38
- {...(sanitizedProps as unknown as MantineTitleProps)}
39
49
  />
40
50
  );
41
51
  });
@@ -5,6 +5,8 @@ import {
5
5
  } from "@mantine/core";
6
6
  import {
7
7
  filterStylingProps,
8
+ omitUnsupportedProps,
9
+ mergeClassNames,
8
10
  type RecursicaOverStyled,
9
11
  } from "../../utils/filterStylingProps";
10
12
  import styles from "./Toast.module.css";
@@ -31,48 +33,47 @@ export const Toast = React.forwardRef<HTMLDivElement, ToastProps>(
31
33
  },
32
34
  ref,
33
35
  ) {
34
- const sanitizedProps = filterStylingProps(rest, overStyled);
36
+ // Props this component intentionally doesn't support — deleted at runtime so they can't leak
37
+ // through even if a caller forces them via plain JavaScript, bypassing the `Omit<>` above.
38
+ const UNSUPPORTED_PROPS = [
39
+ "color", // Colors are token-driven via `data-variant`; Mantine's native palette isn't exposed.
40
+ "radius", // Toast corner radius is controlled by design tokens, not a raw radius prop.
41
+ "loading", // Toast never renders a loading state; always forced off via `loading={false}` below.
42
+ ] as const satisfies readonly (keyof MantineNotificationProps)[];
35
43
 
36
- // Bind CSS module classes to Mantine's internal classNames API
37
- const mergedClassNames: Partial<Record<string, string>> = {
38
- root: styles.root,
39
- body: styles.body,
40
- title: styles.title,
41
- description: styles.description,
42
- closeButton: styles.closeButton,
43
- icon: styles.icon,
44
- loader: styles.loader,
45
- };
44
+ const sanitizedProps = omitUnsupportedProps(
45
+ filterStylingProps(rest, overStyled),
46
+ UNSUPPORTED_PROPS,
47
+ );
46
48
 
47
- const classNamesProp = (sanitizedProps as Record<string, unknown>)
48
- .classNames;
49
- if (
50
- classNamesProp &&
51
- typeof classNamesProp === "object" &&
52
- !Array.isArray(classNamesProp)
53
- ) {
54
- const o = classNamesProp as Record<string, string>;
55
- Object.keys(o).forEach((key) => {
56
- if (mergedClassNames[key]) {
57
- mergedClassNames[key] = `${mergedClassNames[key]} ${o[key]}`;
58
- } else {
59
- mergedClassNames[key] = o[key];
60
- }
61
- });
62
- }
49
+ // Bind CSS module classes to Mantine's internal classNames API
50
+ const mergedClassNames = mergeClassNames(
51
+ {
52
+ root: styles.root,
53
+ body: styles.body,
54
+ title: styles.title,
55
+ description: styles.description,
56
+ closeButton: styles.closeButton,
57
+ icon: styles.icon,
58
+ loader: styles.loader,
59
+ },
60
+ (sanitizedProps as Record<string, unknown>).classNames as
61
+ | Partial<Record<string, string>>
62
+ | undefined,
63
+ );
63
64
 
64
65
  return (
65
66
  <MantineNotification
66
67
  ref={ref}
68
+ {...(sanitizedProps as unknown as Omit<
69
+ MantineNotificationProps,
70
+ "color" | "radius" | "variant" | "loading"
71
+ >)}
67
72
  withCloseButton={withCloseButton}
68
73
  withBorder={false} // Border is handled via CSS or tokens if needed
69
74
  data-variant={variant}
70
75
  classNames={mergedClassNames}
71
76
  loading={false}
72
- {...(sanitizedProps as unknown as Omit<
73
- MantineNotificationProps,
74
- "color" | "radius" | "variant" | "loading"
75
- >)}
76
77
  />
77
78
  );
78
79
  },
@@ -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 "./Tooltip.module.css";
@@ -43,55 +44,38 @@ export type TooltipProps = RecursicaOverStyled<
43
44
  const TooltipBase = function Tooltip({
44
45
  overStyled = false,
45
46
  withBeak = true,
47
+ position = "top", // Recursica default; Mantine defaults to "bottom"
48
+ // arrowSize must be a JS number prop — Mantine uses it for inline width/height
49
+ // and positioning offset (-arrowSize/2) calculations that cannot be CSS-driven.
50
+ // Default to 16 to match the Recursica beak-size token (16px).
51
+ arrowSize = 16,
46
52
  ...rest
47
53
  }: TooltipProps) {
48
54
  const sanitizedProps = filterStylingProps(rest, overStyled);
55
+ const restRecord = sanitizedProps as Record<string, unknown>;
49
56
 
50
57
  // Bind CSS module classes to Mantine's internal classNames API
51
- const mergedClassNames: Partial<Record<string, string>> = {
52
- tooltip: styles.tooltip,
53
- arrow: styles.arrow,
54
- };
55
-
56
- const classNamesProp = (sanitizedProps as Record<string, unknown>).classNames;
57
- if (
58
- classNamesProp &&
59
- typeof classNamesProp === "object" &&
60
- !Array.isArray(classNamesProp)
61
- ) {
62
- const o = classNamesProp as Record<string, string>;
63
- Object.keys(o).forEach((key) => {
64
- if (mergedClassNames[key]) {
65
- mergedClassNames[key] = `${mergedClassNames[key]} ${o[key]}`;
66
- } else {
67
- mergedClassNames[key] = o[key];
68
- }
69
- });
70
- }
71
-
72
- // arrowSize must be a JS number prop — Mantine uses it for inline width/height
73
- // and positioning offset (-arrowSize/2) calculations that cannot be CSS-driven.
74
- // Default to 16 to match the Recursica beak-size token (16px).
75
- const arrowSize =
76
- ((sanitizedProps as Record<string, unknown>).arrowSize as
77
- | number
78
- | undefined) ?? 16;
58
+ const mergedClassNames = mergeClassNames(
59
+ {
60
+ tooltip: styles.tooltip,
61
+ arrow: styles.arrow,
62
+ },
63
+ restRecord.classNames as Partial<Record<string, string>> | undefined,
64
+ );
79
65
 
80
66
  // Resolve withBeak (Recursica) vs withArrow (Mantine).
81
67
  // withBeak takes precedence when both are provided.
82
- const withArrow = (sanitizedProps as Record<string, unknown>).withArrow as
83
- | boolean
84
- | undefined;
68
+ const withArrow = restRecord.withArrow as boolean | undefined;
85
69
  const resolvedWithArrow = withBeak ?? withArrow;
86
70
 
87
71
  return (
88
72
  <MantineTooltip
89
- position="top" /* Recursica default; Mantine defaults to "bottom" */
90
- multiline /* Always allow text wrapping within max-width */
73
+ {...(sanitizedProps as unknown as MantineTooltipProps)}
74
+ position={position}
91
75
  arrowSize={arrowSize}
92
76
  withArrow={resolvedWithArrow}
77
+ multiline /* Always allow text wrapping within max-width */
93
78
  classNames={mergedClassNames}
94
- {...(sanitizedProps as unknown as MantineTooltipProps)}
95
79
  />
96
80
  );
97
81
  };
@@ -346,10 +346,10 @@ export const TransferList = forwardRef<HTMLDivElement, TransferListProps>(
346
346
  readOnlyNativeProps={props}
347
347
  activeComponent={
348
348
  <div
349
+ {...(sanitizedProps as Record<string, unknown>)}
349
350
  className={styles.root}
350
351
  data-disabled={disabled ? "true" : undefined}
351
352
  data-error={error ? "true" : undefined}
352
- {...(sanitizedProps as Record<string, unknown>)}
353
353
  >
354
354
  <div className={styles.panes}>
355
355
  {renderPane(
@@ -8,6 +8,7 @@ import {
8
8
  import { useMergedRef } from "@mantine/hooks";
9
9
  import {
10
10
  filterStylingProps,
11
+ mergeClassNames,
11
12
  type RecursicaOverStyled,
12
13
  } from "../../utils/filterStylingProps";
13
14
  import { type RecursicaTreeProps } from "@recursica/adapter-common";
@@ -186,8 +187,8 @@ export const Tree = forwardRef<HTMLUListElement, TreeProps>(function Tree(
186
187
  }, [disabled]);
187
188
  const mergedRef = useMergedRef(ref, rootRef);
188
189
 
189
- const classNameProp = (sanitizedProps as Record<string, unknown>)
190
- .className as string | undefined;
190
+ const restRecord = sanitizedProps as Record<string, unknown>;
191
+ const classNameProp = restRecord.className as string | undefined;
191
192
  const rootClass = classNameProp
192
193
  ? `${styles.root} ${classNameProp}`
193
194
  : styles.root;
@@ -195,6 +196,7 @@ export const Tree = forwardRef<HTMLUListElement, TreeProps>(function Tree(
195
196
  return (
196
197
  <MantineTree
197
198
  ref={mergedRef}
199
+ {...(sanitizedProps as unknown as Record<string, unknown>)}
198
200
  data={data}
199
201
  tree={tree}
200
202
  expandOnClick={false}
@@ -202,12 +204,10 @@ export const Tree = forwardRef<HTMLUListElement, TreeProps>(function Tree(
202
204
  expandOnSpace={false}
203
205
  renderNode={createRenderTreeNode(disabled)}
204
206
  data-disabled={disabled || undefined}
205
- classNames={{
206
- root: rootClass,
207
- node: styles.node,
208
- subtree: styles.subtree,
209
- }}
210
- {...(sanitizedProps as unknown as Record<string, unknown>)}
207
+ classNames={mergeClassNames(
208
+ { root: rootClass, node: styles.node, subtree: styles.subtree },
209
+ restRecord.classNames as Partial<Record<string, string>> | undefined,
210
+ )}
211
211
  />
212
212
  );
213
213
  });
@@ -67,6 +67,10 @@ export {
67
67
  type BlockedStylingKeys,
68
68
  type ForbiddenStyles,
69
69
  type WithRecursicaSpacing,
70
+ omitUnsupportedProps,
71
+ withCallerOverride,
72
+ mergeClassNames,
73
+ mergeStyles,
70
74
  } from "@recursica/adapter-common";
71
75
 
72
76
  const LAYOUT_PROPS = new Set([