@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
|
@@ -169,6 +169,39 @@
|
|
|
169
169
|
);
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
/* Mantine's own clear button (rendered via `clearButtonProps`, see Dropdown.tsx) is a bare
|
|
173
|
+
`CloseButton` by default — its own module CSS hardcodes a gray icon color and a circular gray
|
|
174
|
+
hover background, neither of which is Recursica-token-driven. The double class selector (same
|
|
175
|
+
specificity-boosting trick as Chip.module.css's `.root.root`) beats that default without
|
|
176
|
+
needing `!important`. Same icon-size/trailing-icon tokens as the rest of this section, same
|
|
177
|
+
focus-visible ring technique as Chip's own `.removeIcon` — the established inline icon-button
|
|
178
|
+
pattern elsewhere in this adapter. */
|
|
179
|
+
.clearButton.clearButton {
|
|
180
|
+
width: var(--recursica_ui-kit_components_dropdown_properties_icon-size);
|
|
181
|
+
height: var(--recursica_ui-kit_components_dropdown_properties_icon-size);
|
|
182
|
+
min-width: var(--recursica_ui-kit_components_dropdown_properties_icon-size);
|
|
183
|
+
min-height: var(--recursica_ui-kit_components_dropdown_properties_icon-size);
|
|
184
|
+
border-radius: 50%;
|
|
185
|
+
background: transparent;
|
|
186
|
+
color: var(
|
|
187
|
+
--recursica_ui-kit_components_dropdown_properties_colors_trailing-icon
|
|
188
|
+
);
|
|
189
|
+
outline: none;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.clearButton.clearButton:hover {
|
|
193
|
+
background: transparent;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.clearButton.clearButton:focus-visible {
|
|
197
|
+
box-shadow:
|
|
198
|
+
0 0 0 var(--recursica_brand_states_focus_border-size)
|
|
199
|
+
var(--recursica_brand_states_focus_color),
|
|
200
|
+
0 0 var(--recursica_brand_states_focus_blur)
|
|
201
|
+
var(--recursica_brand_states_focus_margin)
|
|
202
|
+
var(--recursica_brand_states_focus_color);
|
|
203
|
+
}
|
|
204
|
+
|
|
172
205
|
/* -------------------------------------
|
|
173
206
|
STATE CASCADE ARCHITECTURE
|
|
174
207
|
-------------------------------------- */
|
|
@@ -254,6 +287,17 @@
|
|
|
254
287
|
--recursica_ui-kit_components_dropdown_properties_border-radius
|
|
255
288
|
);
|
|
256
289
|
overflow: hidden;
|
|
290
|
+
|
|
291
|
+
/* The visible top/bottom gap before/after the option list was previously left to Mantine's own
|
|
292
|
+
untokenized `--combobox-padding` default (4px) — wire it to the same dropdown vertical-padding
|
|
293
|
+
token the closed control itself uses instead (mirrors the mui-adapter fix to the equivalent
|
|
294
|
+
gap there, which came from MUI's own untokenized 8px `.MuiList-padding` default). */
|
|
295
|
+
padding-top: var(
|
|
296
|
+
--recursica_ui-kit_components_dropdown_properties_vertical-padding
|
|
297
|
+
);
|
|
298
|
+
padding-bottom: var(
|
|
299
|
+
--recursica_ui-kit_components_dropdown_properties_vertical-padding
|
|
300
|
+
);
|
|
257
301
|
}
|
|
258
302
|
|
|
259
303
|
.option {
|
|
@@ -277,10 +321,8 @@
|
|
|
277
321
|
cursor: pointer;
|
|
278
322
|
}
|
|
279
323
|
|
|
280
|
-
/* No per-option
|
|
281
|
-
|
|
282
|
-
.option[data-selected="true"],
|
|
283
|
-
.option[data-combobox-selected="true"],
|
|
324
|
+
/* No per-option hovered token exists in the schema; use the generic overlay tint (same technique
|
|
325
|
+
as Table's row hover) to highlight a hovered-but-not-selected option. */
|
|
284
326
|
.option[data-hovered="true"],
|
|
285
327
|
.option:hover {
|
|
286
328
|
background-color: color-mix(
|
|
@@ -290,3 +332,18 @@
|
|
|
290
332
|
transparent
|
|
291
333
|
);
|
|
292
334
|
}
|
|
335
|
+
|
|
336
|
+
/* No dedicated dropdown-option "selected" token exists in the schema either, but the menu-item
|
|
337
|
+
component's selected-state colors are the closest real token family for the same concept (a
|
|
338
|
+
selected row in a list) — reused here rather than the neutral hover tint above, which is what
|
|
339
|
+
previously made the selected option read as grey instead of Recursica's brand color. Matches
|
|
340
|
+
the mui-adapter fix for the same gap. */
|
|
341
|
+
.option[data-selected="true"],
|
|
342
|
+
.option[data-combobox-selected="true"] {
|
|
343
|
+
background-color: var(
|
|
344
|
+
--recursica_ui-kit_components_menu-item_variants_selection-states_selected_properties_colors_background-color
|
|
345
|
+
);
|
|
346
|
+
color: var(
|
|
347
|
+
--recursica_ui-kit_components_menu-item_variants_selection-states_selected_properties_colors_text-color
|
|
348
|
+
);
|
|
349
|
+
}
|
|
@@ -65,6 +65,27 @@ export const SearchableClearable: Story = {
|
|
|
65
65
|
},
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
+
export const WithLeadingIcon: Story = {
|
|
69
|
+
args: {
|
|
70
|
+
label: "Destination",
|
|
71
|
+
leftSection: (
|
|
72
|
+
<svg
|
|
73
|
+
width="24"
|
|
74
|
+
height="24"
|
|
75
|
+
viewBox="0 0 24 24"
|
|
76
|
+
fill="none"
|
|
77
|
+
stroke="currentColor"
|
|
78
|
+
strokeWidth="2"
|
|
79
|
+
strokeLinecap="round"
|
|
80
|
+
strokeLinejoin="round"
|
|
81
|
+
>
|
|
82
|
+
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
|
|
83
|
+
<circle cx="12" cy="10" r="3"></circle>
|
|
84
|
+
</svg>
|
|
85
|
+
),
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
68
89
|
export const StaticError: Story = {
|
|
69
90
|
args: {
|
|
70
91
|
error: "You must choose a valid destination.",
|
|
@@ -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 {
|
|
@@ -58,48 +68,41 @@ export const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
|
|
|
58
68
|
value,
|
|
59
69
|
defaultValue,
|
|
60
70
|
data,
|
|
71
|
+
clearButtonProps,
|
|
61
72
|
...rest
|
|
62
73
|
} = props;
|
|
63
|
-
const sanitizedProps =
|
|
74
|
+
const sanitizedProps = omitUnsupportedProps(
|
|
75
|
+
filterStylingProps(rest, overStyled) as Record<string, unknown>,
|
|
76
|
+
UNSUPPORTED_PROPS,
|
|
77
|
+
);
|
|
64
78
|
const restRecord = sanitizedProps as Record<string, unknown>;
|
|
65
79
|
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
80
|
+
// Mantine's own clear button (rendered when `clearable` + a value are both present) otherwise
|
|
81
|
+
// renders unstyled — bare `CloseButton` defaults, no Recursica icon-button treatment. Merge in
|
|
82
|
+
// our own class (see `.clearButton` in Dropdown.module.css) alongside anything the caller
|
|
83
|
+
// already passed, same merge shape as `mergeClassNames` but for a single `className` string
|
|
84
|
+
// rather than a per-slot classNames map.
|
|
85
|
+
const consumerClearButtonProps = clearButtonProps as
|
|
86
|
+
| Record<string, unknown>
|
|
87
|
+
| undefined;
|
|
88
|
+
const mergedClearButtonProps = {
|
|
89
|
+
...consumerClearButtonProps,
|
|
90
|
+
className: consumerClearButtonProps?.className
|
|
91
|
+
? `${styles.clearButton} ${consumerClearButtonProps.className as string}`
|
|
92
|
+
: styles.clearButton,
|
|
78
93
|
};
|
|
79
94
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
}
|
|
95
|
+
// Securely map core native blocks down ensuring nested CSS modules map precisely
|
|
96
|
+
const mergedClassNames = mergeClassNames(
|
|
97
|
+
{
|
|
98
|
+
wrapper: styles.root, // The nested Input internal relative wrapper bounding box
|
|
99
|
+
input: styles.input,
|
|
100
|
+
section: styles.section,
|
|
101
|
+
dropdown: styles.dropdown,
|
|
102
|
+
option: styles.option,
|
|
103
|
+
},
|
|
104
|
+
restRecord.classNames as Partial<Record<string, string>> | undefined,
|
|
105
|
+
);
|
|
103
106
|
|
|
104
107
|
const injectedStyles = {
|
|
105
108
|
...((style as React.CSSProperties) || {}),
|
|
@@ -146,11 +149,13 @@ export const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
|
|
|
146
149
|
/* Naked Select execution safely decoupled from Mantine's macro Input.Wrapper DOM hooks */
|
|
147
150
|
<MantineSelect
|
|
148
151
|
ref={ref}
|
|
152
|
+
{...(sanitizedProps as unknown as MantineSelectProps)}
|
|
149
153
|
classNames={mergedClassNames}
|
|
150
154
|
disabled={disabled}
|
|
151
155
|
value={value}
|
|
152
156
|
defaultValue={defaultValue}
|
|
153
157
|
data={(data as unknown as MantineSelectProps["data"]) || []}
|
|
158
|
+
clearButtonProps={mergedClearButtonProps}
|
|
154
159
|
label={undefined}
|
|
155
160
|
description={undefined}
|
|
156
161
|
error={undefined}
|
|
@@ -169,7 +174,6 @@ export const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
|
|
|
169
174
|
"data-error": error ? "true" : undefined,
|
|
170
175
|
},
|
|
171
176
|
}}
|
|
172
|
-
{...(sanitizedProps as unknown as MantineSelectProps)}
|
|
173
177
|
/>
|
|
174
178
|
}
|
|
175
179
|
/>
|
|
@@ -45,3 +45,4 @@ All Recursica components in the `@recursica/mantine-adapter` package adhere stri
|
|
|
45
45
|
## 4. Notes
|
|
46
46
|
|
|
47
47
|
- `Dropdown` is styled independently from `TextField`; even though they look similar, they are themed using separate design tokens.
|
|
48
|
+
- Pass `leftSection` for a leading icon, and `clearable` (with a value present) to show a clear button — both render using the dropdown's own icon-color tokens.
|
|
@@ -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
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
|
|
39
|
-
|
|
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
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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
|
/>
|
|
@@ -199,7 +199,9 @@ export const Default: Story = {
|
|
|
199
199
|
position: "bottom-start",
|
|
200
200
|
withArrow: false,
|
|
201
201
|
offset: 5,
|
|
202
|
-
|
|
202
|
+
// Rendered open by default so this story is diffable against the MUI adapter
|
|
203
|
+
// without an interaction step.
|
|
204
|
+
opened: true,
|
|
203
205
|
},
|
|
204
206
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
205
207
|
render: ({ withLayer, layer, ...args }: MenuStoryArgs) => {
|
|
@@ -230,6 +232,9 @@ export const Default: Story = {
|
|
|
230
232
|
export const WithDisabledItems: Story = {
|
|
231
233
|
args: {
|
|
232
234
|
position: "bottom-start",
|
|
235
|
+
// Rendered open by default so this story is diffable against the MUI adapter
|
|
236
|
+
// without an interaction step.
|
|
237
|
+
opened: true,
|
|
233
238
|
},
|
|
234
239
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
235
240
|
render: ({ withLayer, layer, ...args }: MenuStoryArgs) => {
|
|
@@ -258,6 +263,9 @@ export const WithSubmenus: Story = {
|
|
|
258
263
|
args: {
|
|
259
264
|
position: "bottom-start",
|
|
260
265
|
width: 200,
|
|
266
|
+
// Rendered open by default so this story is diffable against the MUI adapter
|
|
267
|
+
// without an interaction step.
|
|
268
|
+
opened: true,
|
|
261
269
|
},
|
|
262
270
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
263
271
|
render: ({ withLayer, layer, ...args }: MenuStoryArgs) => {
|