@recursica/mantine-adapter 0.42.1 → 0.44.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.
- package/CHANGELOG.md +17 -0
- package/dist/index.d.ts +93 -15
- package/dist/mantine-adapter.cjs +2 -2
- package/dist/mantine-adapter.cjs.map +1 -1
- package/dist/mantine-adapter.css +1 -1
- package/dist/mantine-adapter.js +3534 -3266
- package/dist/mantine-adapter.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Accordion/Accordion.tsx +32 -33
- package/src/components/AssistiveElement/AssistiveElement.tsx +1 -1
- package/src/components/AutoComplete/AutoComplete.tsx +26 -38
- package/src/components/Avatar/Avatar.tsx +22 -23
- package/src/components/Badge/Badge.tsx +12 -18
- package/src/components/Breadcrumb/Breadcrumb.module.css +2 -0
- package/src/components/Breadcrumb/Breadcrumb.stories.tsx +0 -6
- package/src/components/Breadcrumb/Breadcrumb.tsx +25 -17
- package/src/components/Button/Button.tsx +31 -30
- package/src/components/Card/Card.tsx +48 -27
- package/src/components/Checkbox/Checkbox.tsx +29 -42
- package/src/components/Checkbox/CheckboxGroup.tsx +12 -5
- package/src/components/Chip/Chip.tsx +34 -27
- package/src/components/DatePicker/DatePicker.tsx +46 -47
- package/src/components/Dropdown/BareDropdown.tsx +25 -13
- package/src/components/Dropdown/DROPDOWN_IMPLEMENTATION_NOTES.md +2 -0
- package/src/components/Dropdown/Dropdown.module.css +61 -4
- package/src/components/Dropdown/Dropdown.stories.tsx +21 -0
- package/src/components/Dropdown/Dropdown.tsx +41 -37
- package/src/components/Dropdown/USAGE.md +1 -0
- package/src/components/FileInput/FileInput.tsx +1 -1
- package/src/components/FileUpload/FileUpload.tsx +1 -1
- package/src/components/HoverCard/HoverCard.tsx +18 -34
- package/src/components/Label/Label.tsx +20 -21
- package/src/components/Link/Link.tsx +6 -14
- package/src/components/Loader/Loader.tsx +8 -16
- package/src/components/Menu/Menu.stories.tsx +9 -1
- package/src/components/Menu/Menu.tsx +55 -48
- package/src/components/Modal/Modal.tsx +134 -96
- package/src/components/NumberInput/NumberInput.tsx +14 -7
- package/src/components/Pagination/Pagination.tsx +25 -34
- package/src/components/Panel/Panel.tsx +19 -26
- package/src/components/Popover/Popover.tsx +19 -31
- package/src/components/Radio/Radio.tsx +32 -43
- package/src/components/Radio/RadioGroup.tsx +11 -4
- package/src/components/ReadOnlyField/ReadOnlyField.tsx +1 -1
- package/src/components/ReadOnlyField/ReadOnlyTextField.tsx +1 -1
- package/src/components/ReadOnlyField/WithReadOnlyWrapper.tsx +1 -1
- package/src/components/SegmentedControl/SegmentedControl.tsx +29 -33
- package/src/components/Slider/Slider.tsx +30 -43
- package/src/components/Stepper/Stepper.tsx +20 -13
- package/src/components/Switch/Switch.tsx +45 -45
- package/src/components/Switch/SwitchGroup.tsx +11 -4
- package/src/components/Table/Table.tsx +1 -1
- package/src/components/Tabs/Tabs.tsx +13 -6
- package/src/components/Text/Text.tsx +1 -1
- package/src/components/TextArea/TextArea.tsx +21 -25
- package/src/components/TextField/TextField.tsx +23 -30
- package/src/components/TimePicker/TimePicker.tsx +26 -12
- package/src/components/Timeline/Timeline.tsx +7 -21
- package/src/components/Timeline/TimelineItem.tsx +14 -26
- package/src/components/Title/Title.tsx +12 -2
- package/src/components/Toast/Toast.tsx +32 -31
- package/src/components/Tooltip/Tooltip.tsx +18 -34
- package/src/components/TransferList/TransferList.tsx +1 -1
- package/src/components/Tree/Tree.tsx +8 -8
- package/src/utils/filterStylingProps.ts +4 -0
|
@@ -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
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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(
|
|
97
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
},
|
|
@@ -8,9 +8,12 @@ import {
|
|
|
8
8
|
type ModalHeaderProps as MantineModalHeaderProps,
|
|
9
9
|
type ModalTitleProps as MantineModalTitleProps,
|
|
10
10
|
type ModalCloseButtonProps as MantineModalCloseButtonProps,
|
|
11
|
+
type ModalBodyProps as MantineModalBodyProps,
|
|
11
12
|
} from "@mantine/core";
|
|
12
13
|
import {
|
|
13
14
|
filterStylingProps,
|
|
15
|
+
omitUnsupportedProps,
|
|
16
|
+
mergeClassNames,
|
|
14
17
|
type RecursicaOverStyled,
|
|
15
18
|
} from "../../utils/filterStylingProps";
|
|
16
19
|
import styles from "./Modal.module.css";
|
|
@@ -54,41 +57,40 @@ const ModalInner = React.forwardRef<HTMLDivElement, ModalProps>(function Modal(
|
|
|
54
57
|
},
|
|
55
58
|
ref,
|
|
56
59
|
) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
60
|
+
// Props this component intentionally doesn't support — deleted at runtime so they can't leak
|
|
61
|
+
// through even if a caller forces them via plain JavaScript, bypassing the `Omit<>` above.
|
|
62
|
+
const UNSUPPORTED_PROPS = [
|
|
63
|
+
"size", // Modal width/height are enforced via CSS module tokens (min/max-width, min/max-height), not Mantine's `size` scale.
|
|
64
|
+
"radius", // Modal corner radius is token-driven via `.content` in Modal.module.css, not Mantine's `radius` prop.
|
|
65
|
+
] as const satisfies readonly (keyof MantineModalProps)[];
|
|
66
|
+
|
|
67
|
+
const sanitizedProps = omitUnsupportedProps(
|
|
68
|
+
filterStylingProps(rest, overStyled) as Record<string, unknown>,
|
|
69
|
+
UNSUPPORTED_PROPS,
|
|
70
|
+
) as Partial<typeof rest>;
|
|
71
|
+
|
|
72
|
+
const mergedClassNames = mergeClassNames(
|
|
73
|
+
{
|
|
74
|
+
root: styles.root,
|
|
75
|
+
inner: styles.inner,
|
|
76
|
+
content: styles.content,
|
|
77
|
+
header: styles.header,
|
|
78
|
+
title: styles.title,
|
|
79
|
+
close: styles.close,
|
|
80
|
+
},
|
|
81
|
+
(sanitizedProps as Record<string, unknown>).classNames as
|
|
82
|
+
| Partial<Record<string, string>>
|
|
83
|
+
| undefined,
|
|
84
|
+
);
|
|
83
85
|
|
|
84
86
|
return (
|
|
85
87
|
<MantineModal.Root
|
|
86
88
|
ref={ref}
|
|
87
|
-
classNames={mergedClassNames}
|
|
88
89
|
{...(sanitizedProps as unknown as Omit<
|
|
89
90
|
MantineModalProps,
|
|
90
91
|
"size" | "radius" | "shadow"
|
|
91
92
|
>)}
|
|
93
|
+
classNames={mergedClassNames}
|
|
92
94
|
>
|
|
93
95
|
{withOverlay && <MantineModal.Overlay {...overlayProps} />}
|
|
94
96
|
<MantineModal.Content>
|
|
@@ -130,81 +132,87 @@ const ModalFooter = React.forwardRef<HTMLDivElement, ModalFooterProps>(
|
|
|
130
132
|
return (
|
|
131
133
|
<div
|
|
132
134
|
ref={ref}
|
|
133
|
-
className={`${styles.footer} ${className || ""}`}
|
|
134
135
|
{...sanitizedProps}
|
|
136
|
+
className={`${styles.footer} ${className || ""}`}
|
|
135
137
|
/>
|
|
136
138
|
);
|
|
137
139
|
},
|
|
138
140
|
);
|
|
139
141
|
ModalFooter.displayName = "Modal.Footer";
|
|
140
142
|
|
|
141
|
-
|
|
142
|
-
HTMLDivElement,
|
|
143
|
-
React.ComponentPropsWithoutRef<typeof MantineModal.Body>
|
|
144
|
-
>(function ModalBody({ className, onScroll, children, ...rest }, ref) {
|
|
145
|
-
const internalRef = React.useRef<HTMLDivElement>(null);
|
|
146
|
-
const [scrolledTop, setScrolledTop] = React.useState(false);
|
|
147
|
-
const [scrolledBottom, setScrolledBottom] = React.useState(false);
|
|
148
|
-
|
|
149
|
-
const checkScroll = React.useCallback(() => {
|
|
150
|
-
if (internalRef.current) {
|
|
151
|
-
const { scrollTop, scrollHeight, clientHeight } = internalRef.current;
|
|
152
|
-
setScrolledTop(scrollTop > 0);
|
|
153
|
-
setScrolledBottom(Math.ceil(scrollTop + clientHeight) < scrollHeight);
|
|
154
|
-
}
|
|
155
|
-
}, []);
|
|
156
|
-
|
|
157
|
-
// Re-check scroll on mount and window resize
|
|
158
|
-
React.useEffect(() => {
|
|
159
|
-
checkScroll();
|
|
160
|
-
window.addEventListener("resize", checkScroll);
|
|
161
|
-
return () => window.removeEventListener("resize", checkScroll);
|
|
162
|
-
}, [checkScroll, children]);
|
|
163
|
-
|
|
164
|
-
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
|
|
165
|
-
checkScroll();
|
|
166
|
-
onScroll?.(e);
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
// Intercept children to pull Modal.Footer out of the scrolling container
|
|
170
|
-
let footer: React.ReactNode = null;
|
|
171
|
-
const bodyChildren: React.ReactNode[] = [];
|
|
172
|
-
|
|
173
|
-
React.Children.forEach(children, (child) => {
|
|
174
|
-
if (
|
|
175
|
-
React.isValidElement(child) &&
|
|
176
|
-
(child.type === ModalFooter ||
|
|
177
|
-
(child.type as React.ComponentType)?.displayName === "Modal.Footer")
|
|
178
|
-
) {
|
|
179
|
-
footer = child;
|
|
180
|
-
} else {
|
|
181
|
-
bodyChildren.push(child);
|
|
182
|
-
}
|
|
183
|
-
});
|
|
143
|
+
export type ModalBodyProps = RecursicaOverStyled<MantineModalBodyProps>;
|
|
184
144
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
145
|
+
const ModalBody = React.forwardRef<HTMLDivElement, ModalBodyProps>(
|
|
146
|
+
function ModalBody(
|
|
147
|
+
{ overStyled = false, className, onScroll, children, ...rest },
|
|
148
|
+
ref,
|
|
149
|
+
) {
|
|
150
|
+
const sanitizedProps = filterStylingProps(rest, overStyled);
|
|
151
|
+
const internalRef = React.useRef<HTMLDivElement>(null);
|
|
152
|
+
const [scrolledTop, setScrolledTop] = React.useState(false);
|
|
153
|
+
const [scrolledBottom, setScrolledBottom] = React.useState(false);
|
|
154
|
+
|
|
155
|
+
const checkScroll = React.useCallback(() => {
|
|
156
|
+
if (internalRef.current) {
|
|
157
|
+
const { scrollTop, scrollHeight, clientHeight } = internalRef.current;
|
|
158
|
+
setScrolledTop(scrollTop > 0);
|
|
159
|
+
setScrolledBottom(Math.ceil(scrollTop + clientHeight) < scrollHeight);
|
|
160
|
+
}
|
|
161
|
+
}, []);
|
|
162
|
+
|
|
163
|
+
// Re-check scroll on mount and window resize
|
|
164
|
+
React.useEffect(() => {
|
|
165
|
+
checkScroll();
|
|
166
|
+
window.addEventListener("resize", checkScroll);
|
|
167
|
+
return () => window.removeEventListener("resize", checkScroll);
|
|
168
|
+
}, [checkScroll, children]);
|
|
169
|
+
|
|
170
|
+
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
|
|
171
|
+
checkScroll();
|
|
172
|
+
onScroll?.(e);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// Intercept children to pull Modal.Footer out of the scrolling container
|
|
176
|
+
let footer: React.ReactNode = null;
|
|
177
|
+
const bodyChildren: React.ReactNode[] = [];
|
|
178
|
+
|
|
179
|
+
React.Children.forEach(children, (child) => {
|
|
180
|
+
if (
|
|
181
|
+
React.isValidElement(child) &&
|
|
182
|
+
(child.type === ModalFooter ||
|
|
183
|
+
(child.type as React.ComponentType)?.displayName === "Modal.Footer")
|
|
184
|
+
) {
|
|
185
|
+
footer = child;
|
|
186
|
+
} else {
|
|
187
|
+
bodyChildren.push(child);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<MantineModal.Body
|
|
193
|
+
{...sanitizedProps}
|
|
194
|
+
ref={(node) => {
|
|
195
|
+
if (typeof ref === "function") ref(node);
|
|
196
|
+
else if (ref)
|
|
197
|
+
(ref as React.MutableRefObject<HTMLDivElement | null>).current =
|
|
198
|
+
node;
|
|
199
|
+
}}
|
|
200
|
+
className={`${styles.bodyWrapper} ${className || ""}`}
|
|
201
201
|
>
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
202
|
+
<div
|
|
203
|
+
ref={internalRef}
|
|
204
|
+
onScroll={handleScroll}
|
|
205
|
+
data-scrolled-top={scrolledTop || undefined}
|
|
206
|
+
data-scrolled-bottom={scrolledBottom || undefined}
|
|
207
|
+
className={styles.scrollArea}
|
|
208
|
+
>
|
|
209
|
+
{bodyChildren}
|
|
210
|
+
</div>
|
|
211
|
+
{footer}
|
|
212
|
+
</MantineModal.Body>
|
|
213
|
+
);
|
|
214
|
+
},
|
|
215
|
+
);
|
|
208
216
|
ModalBody.displayName = "Modal.Body";
|
|
209
217
|
|
|
210
218
|
// ============================================================
|
|
@@ -213,9 +221,20 @@ ModalBody.displayName = "Modal.Body";
|
|
|
213
221
|
|
|
214
222
|
export type ModalRootProps = RecursicaOverStyled<MantineModalRootProps>;
|
|
215
223
|
|
|
224
|
+
// Props this component intentionally doesn't support — deleted at runtime so they can't leak
|
|
225
|
+
// through even if a caller forces them via plain JavaScript. `Modal.Root` is used directly for
|
|
226
|
+
// advanced composition, so it doesn't benefit from the top-level `Modal`'s `Omit<>` protection.
|
|
227
|
+
const MODAL_ROOT_UNSUPPORTED_PROPS = [
|
|
228
|
+
"size", // Modal width/height are enforced via CSS module tokens, not Mantine's `size` scale.
|
|
229
|
+
"radius", // Modal corner radius is token-driven via `.content` in Modal.module.css, not Mantine's `radius` prop.
|
|
230
|
+
] as const satisfies readonly (keyof MantineModalRootProps)[];
|
|
231
|
+
|
|
216
232
|
const ModalRoot = React.forwardRef<HTMLDivElement, ModalRootProps>(
|
|
217
233
|
function ModalRoot({ overStyled = false, ...rest }, ref) {
|
|
218
|
-
const sanitizedProps =
|
|
234
|
+
const sanitizedProps = omitUnsupportedProps(
|
|
235
|
+
filterStylingProps(rest, overStyled) as Record<string, unknown>,
|
|
236
|
+
MODAL_ROOT_UNSUPPORTED_PROPS,
|
|
237
|
+
) as Partial<typeof rest>;
|
|
219
238
|
return (
|
|
220
239
|
<MantineModal.Root
|
|
221
240
|
ref={ref}
|
|
@@ -243,9 +262,18 @@ ModalOverlay.displayName = "Modal.Overlay";
|
|
|
243
262
|
|
|
244
263
|
export type ModalContentProps = RecursicaOverStyled<MantineModalContentProps>;
|
|
245
264
|
|
|
265
|
+
// Modal corner radius is token-driven via `.content` in Modal.module.css, not Mantine's
|
|
266
|
+
// native `radius` prop — deleted at runtime so it can't leak through via plain JavaScript.
|
|
267
|
+
const MODAL_CONTENT_UNSUPPORTED_PROPS = [
|
|
268
|
+
"radius",
|
|
269
|
+
] as const satisfies readonly (keyof MantineModalContentProps)[];
|
|
270
|
+
|
|
246
271
|
const ModalContent = React.forwardRef<HTMLDivElement, ModalContentProps>(
|
|
247
272
|
function ModalContent({ overStyled = false, ...rest }, ref) {
|
|
248
|
-
const sanitizedProps =
|
|
273
|
+
const sanitizedProps = omitUnsupportedProps(
|
|
274
|
+
filterStylingProps(rest, overStyled) as Record<string, unknown>,
|
|
275
|
+
MODAL_CONTENT_UNSUPPORTED_PROPS,
|
|
276
|
+
) as Partial<typeof rest>;
|
|
249
277
|
return (
|
|
250
278
|
<MantineModal.Content
|
|
251
279
|
ref={ref}
|
|
@@ -289,11 +317,21 @@ ModalTitle.displayName = "Modal.Title";
|
|
|
289
317
|
export type ModalCloseButtonProps =
|
|
290
318
|
RecursicaOverStyled<MantineModalCloseButtonProps>;
|
|
291
319
|
|
|
320
|
+
// The close button's dimensions and corner radius are enforced by Modal.module.css's `.close`
|
|
321
|
+
// (standard icon-button tokens), not Mantine's native `size`/`radius` scales.
|
|
322
|
+
const MODAL_CLOSE_BUTTON_UNSUPPORTED_PROPS = [
|
|
323
|
+
"size",
|
|
324
|
+
"radius",
|
|
325
|
+
] as const satisfies readonly (keyof MantineModalCloseButtonProps)[];
|
|
326
|
+
|
|
292
327
|
const ModalCloseButton = React.forwardRef<
|
|
293
328
|
HTMLButtonElement,
|
|
294
329
|
ModalCloseButtonProps
|
|
295
330
|
>(function ModalCloseButton({ overStyled = false, ...rest }, ref) {
|
|
296
|
-
const sanitizedProps =
|
|
331
|
+
const sanitizedProps = omitUnsupportedProps(
|
|
332
|
+
filterStylingProps(rest, overStyled) as Record<string, unknown>,
|
|
333
|
+
MODAL_CLOSE_BUTTON_UNSUPPORTED_PROPS,
|
|
334
|
+
) as Partial<typeof rest>;
|
|
297
335
|
return (
|
|
298
336
|
<MantineModal.CloseButton
|
|
299
337
|
ref={ref}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
import { type ReadOnlyControlProps } from "@recursica/adapter-common";
|
|
7
7
|
import {
|
|
8
8
|
filterStylingProps,
|
|
9
|
+
omitUnsupportedProps,
|
|
9
10
|
type RecursicaOverStyled,
|
|
10
11
|
} from "../../utils/filterStylingProps";
|
|
11
12
|
import { type RecursicaFormControlWrapperProps } from "../FormControlWrapper/FormControlWrapper";
|
|
@@ -35,6 +36,14 @@ export interface RecursicaNumberInputProps
|
|
|
35
36
|
|
|
36
37
|
export type NumberInputProps = RecursicaOverStyled<RecursicaNumberInputProps>;
|
|
37
38
|
|
|
39
|
+
// Props this component intentionally doesn't support — deleted at runtime so they can't leak
|
|
40
|
+
// through even if a caller forces them via plain JavaScript, bypassing the `Omit<>` above.
|
|
41
|
+
const UNSUPPORTED_PROPS = [
|
|
42
|
+
"size", // Recursica controls sizing via the `size` variant + design tokens, not raw dimensions.
|
|
43
|
+
"variant", // Colors/variants are token-driven; the library's native palette isn't exposed.
|
|
44
|
+
"radius", // Recursica does not expose the library's native corner-radius system.
|
|
45
|
+
] as const satisfies readonly (keyof MantineNumberInputProps)[];
|
|
46
|
+
|
|
38
47
|
export const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>(
|
|
39
48
|
function NumberInput(props, ref) {
|
|
40
49
|
const {
|
|
@@ -67,14 +76,12 @@ export const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>(
|
|
|
67
76
|
...rest
|
|
68
77
|
} = props;
|
|
69
78
|
|
|
70
|
-
const sanitizedProps =
|
|
79
|
+
const sanitizedProps = omitUnsupportedProps(
|
|
80
|
+
filterStylingProps(rest, overStyled) as Record<string, unknown>,
|
|
81
|
+
UNSUPPORTED_PROPS,
|
|
82
|
+
);
|
|
71
83
|
const restRecord = sanitizedProps as Record<string, unknown>;
|
|
72
84
|
|
|
73
|
-
// Delete prohibited sizing hooks from bypassing variables natively
|
|
74
|
-
delete restRecord["size"];
|
|
75
|
-
delete restRecord["variant"];
|
|
76
|
-
delete restRecord["radius"];
|
|
77
|
-
|
|
78
85
|
// Securely map core native blocks down ensuring nested CSS modules map precisely
|
|
79
86
|
const mergedClassNames: Partial<Record<string, string>> = {
|
|
80
87
|
wrapper: styles.root, // The nested Input internal relative wrapper bounding box
|
|
@@ -120,6 +127,7 @@ export const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>(
|
|
|
120
127
|
/* Naked Input execution safely decoupled from Mantine's macro Input.Wrapper DOM hooks */
|
|
121
128
|
<MantineNumberInput
|
|
122
129
|
ref={ref}
|
|
130
|
+
{...(sanitizedProps as unknown as MantineNumberInputProps)}
|
|
123
131
|
classNames={mergedClassNames}
|
|
124
132
|
disabled={disabled}
|
|
125
133
|
value={value}
|
|
@@ -139,7 +147,6 @@ export const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>(
|
|
|
139
147
|
"data-with-right-section":
|
|
140
148
|
restRecord.rightSection || !hideControls ? "true" : undefined,
|
|
141
149
|
}}
|
|
142
|
-
{...(sanitizedProps as unknown as MantineNumberInputProps)}
|
|
143
150
|
/>
|
|
144
151
|
}
|
|
145
152
|
/>
|