@recursica/mui-adapter 0.25.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/dist/index.d.ts +105 -19
- package/dist/mui-adapter.cjs +77 -77
- package/dist/mui-adapter.cjs.map +1 -1
- package/dist/mui-adapter.css +1 -1
- package/dist/mui-adapter.js +29867 -25282
- package/dist/mui-adapter.js.map +1 -1
- package/package.json +1 -1
- package/src/GlobalExemptions.modules.css +0 -6
- package/src/components/Accordion/Accordion.module.css +0 -8
- package/src/components/Autocomplete/Autocomplete.module.css +0 -8
- package/src/components/Autocomplete/Autocomplete.tsx +46 -34
- package/src/components/Avatar/Avatar.module.css +0 -8
- package/src/components/Avatar/Avatar.tsx +11 -21
- package/src/components/Badge/Badge.tsx +9 -13
- package/src/components/Breadcrumb/Breadcrumb.tsx +12 -18
- package/src/components/Button/Button.module.css +0 -13
- package/src/components/Button/Button.tsx +17 -11
- package/src/components/Card/Card.tsx +9 -19
- package/src/components/Checkbox/Checkbox.tsx +22 -26
- package/src/components/Checkbox/CheckboxGroup.tsx +3 -3
- package/src/components/Chip/Chip.module.css +0 -27
- package/src/components/Chip/Chip.tsx +12 -19
- package/src/components/DatePicker/DATEPICKER_IMPLEMENTATION_NOTES.md +29 -0
- package/src/components/DatePicker/DatePicker.icons.tsx +35 -0
- package/src/components/DatePicker/DatePicker.module.css +451 -42
- package/src/components/DatePicker/DatePicker.stories.tsx +23 -25
- package/src/components/DatePicker/DatePicker.tsx +229 -47
- package/src/components/DatePicker/USAGE.md +14 -1
- package/src/components/Dropdown/BareDropdown.tsx +14 -8
- package/src/components/Dropdown/Dropdown.module.css +0 -7
- package/src/components/Dropdown/Dropdown.tsx +13 -6
- package/src/components/FileInput/FileInput.module.css +0 -21
- package/src/components/FileInput/FileInput.tsx +1 -1
- package/src/components/FileUpload/FileUpload.module.css +0 -11
- package/src/components/FileUpload/FileUpload.tsx +1 -1
- package/src/components/FormControlLayout/FormControlLayout.tsx +1 -1
- package/src/components/FormControlWrapper/FormControlWrapper.tsx +1 -1
- package/src/components/HoverCard/HoverCard.module.css +1 -7
- package/src/components/HoverCard/HoverCard.tsx +13 -21
- package/src/components/Label/Label.module.css +0 -6
- package/src/components/Link/Link.module.css +0 -13
- package/src/components/Menu/Menu.module.css +0 -5
- package/src/components/Menu/Menu.tsx +14 -6
- package/src/components/Modal/Modal.module.css +0 -11
- package/src/components/Modal/Modal.tsx +12 -6
- package/src/components/NumberInput/NumberInput.module.css +0 -7
- package/src/components/NumberInput/NumberInput.tsx +30 -15
- package/src/components/Pagination/Pagination.module.css +0 -81
- package/src/components/Pagination/Pagination.tsx +5 -15
- package/src/components/Panel/Panel.tsx +17 -25
- package/src/components/Popover/Popover.module.css +0 -5
- package/src/components/Popover/Popover.tsx +10 -18
- package/src/components/Radio/Radio.tsx +21 -26
- package/src/components/Radio/RadioGroup.tsx +3 -4
- package/src/components/ReadOnlyField/ReadOnlyTextField.tsx +1 -1
- package/src/components/SegmentedControl/SegmentedControl.module.css +0 -8
- package/src/components/SegmentedControl/SegmentedControl.tsx +20 -25
- package/src/components/Slider/Slider.tsx +40 -43
- package/src/components/Stepper/Stepper.tsx +34 -14
- package/src/components/Switch/Switch.tsx +33 -49
- package/src/components/Switch/SwitchGroup.tsx +3 -3
- package/src/components/Tabs/Tabs.tsx +12 -4
- package/src/components/TextArea/TextArea.module.css +0 -7
- package/src/components/TextArea/TextArea.tsx +30 -7
- package/src/components/TextField/TextField.module.css +0 -8
- package/src/components/TextField/TextField.tsx +30 -26
- package/src/components/TimePicker/TimePicker.module.css +0 -16
- package/src/components/Timeline/Timeline.module.css +0 -6
- package/src/components/Timeline/Timeline.tsx +9 -1
- package/src/components/Timeline/TimelineItem.tsx +2 -2
- package/src/components/Toast/Toast.module.css +0 -7
- package/src/components/Toast/Toast.tsx +14 -23
- package/src/components/Tooltip/Tooltip.module.css +0 -9
- package/src/components/Tooltip/Tooltip.tsx +14 -27
- package/src/components/TransferList/TransferList.module.css +0 -8
- package/src/components/TransferList/TransferList.tsx +1 -1
- package/src/utils/filterStylingProps.ts +4 -0
package/package.json
CHANGED
|
@@ -5,9 +5,3 @@
|
|
|
5
5
|
* recursica_variables_scoped.css but are not used by any specific component.
|
|
6
6
|
* Isolating them here keeps individual component stylesheets clean and focused.
|
|
7
7
|
*/
|
|
8
|
-
|
|
9
|
-
/* ==== GLOBAL ICON STYLE ====
|
|
10
|
-
Global layout option specifying the overall SVG rendering style (solid/outline).
|
|
11
|
-
Note to Forge Devs: style is not a CSS property or variable type and cannot be applied in standard CSS stylesheets.
|
|
12
|
-
This variable is ignored in the UI adapters and should be reviewed by the Forge team. */
|
|
13
|
-
/* recursica-ignore: --recursica_ui-kit_globals_icon_style */
|
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/* EXEMPTIONS:
|
|
2
|
-
- accordion-header open-appearance background-color/border-color are ignored because Forge does
|
|
3
|
-
not apply a visual swap on expand, even though the token schema defines "open" appearance
|
|
4
|
-
colors distinct from "closed" (see .control below, which intentionally keeps a constant
|
|
5
|
-
background/border across collapsed/expanded). Mirrors the mantine-adapter reference. */
|
|
6
|
-
/* recursica-ignore: --recursica_ui-kit_components_accordion-header_variants_appearance_open_properties_colors_background-color */
|
|
7
|
-
/* recursica-ignore: --recursica_ui-kit_components_accordion-header_variants_appearance_open_properties_colors_border-color */
|
|
8
|
-
|
|
9
1
|
/* ==== ACCORDION CONTAINER ==== */
|
|
10
2
|
.root {
|
|
11
3
|
box-sizing: border-box;
|
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/* EXEMPTIONS:
|
|
2
|
-
- state-specific border-size variables are ignored because the input box uses a uniform static boundary to prevent
|
|
3
|
-
unexpected layout shifts or flickering when switching between active, focused, or error states. */
|
|
4
|
-
/* recursica-ignore: --recursica_ui-kit_components_autocomplete_variants_states_default_properties_border-size */
|
|
5
|
-
/* recursica-ignore: --recursica_ui-kit_components_autocomplete_variants_states_disabled_properties_border-size */
|
|
6
|
-
/* recursica-ignore: --recursica_ui-kit_components_autocomplete_variants_states_error_properties_border-size */
|
|
7
|
-
/* recursica-ignore: --recursica_ui-kit_components_autocomplete_variants_states_focus_properties_border-size */
|
|
8
|
-
|
|
9
1
|
/* LAYOUT SPACING OVERRIDES:
|
|
10
2
|
- Sets the --form-control-margin-bottom spacing hook to map component-specific layout tokens.
|
|
11
3
|
- Also sets the --autocomplete-control-{max,min}-width hooks consumed inline in Autocomplete.tsx,
|
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
import { type ReadOnlyControlProps } from "@recursica/adapter-common";
|
|
9
9
|
import {
|
|
10
10
|
filterStylingProps,
|
|
11
|
+
omitUnsupportedProps,
|
|
12
|
+
mergeClassNames,
|
|
11
13
|
type RecursicaOverStyled,
|
|
12
14
|
} from "../../utils/filterStylingProps";
|
|
13
15
|
import { type RecursicaFormControlWrapperProps } from "../FormControlWrapper/FormControlWrapper";
|
|
@@ -77,42 +79,50 @@ export const Autocomplete = forwardRef<HTMLInputElement, AutocompleteProps>(
|
|
|
77
79
|
ListboxProps,
|
|
78
80
|
...rest
|
|
79
81
|
} = props;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
// Props this component intentionally doesn't support — deleted at runtime so they can't leak
|
|
83
|
+
// through even if a caller forces them via plain JavaScript, bypassing the Omit<> above.
|
|
84
|
+
const UNSUPPORTED_PROPS = [
|
|
85
|
+
"size", // Recursica controls sizing via design tokens, not MUI's native small/medium size
|
|
86
|
+
] as const satisfies readonly (keyof MuiAutocompleteProps<
|
|
87
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
88
|
+
any,
|
|
89
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
90
|
+
any,
|
|
91
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
92
|
+
any,
|
|
93
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
94
|
+
any,
|
|
95
|
+
"div"
|
|
96
|
+
>)[];
|
|
87
97
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
option: styles.option,
|
|
94
|
-
};
|
|
98
|
+
const sanitizedProps = omitUnsupportedProps(
|
|
99
|
+
filterStylingProps(rest, overStyled),
|
|
100
|
+
UNSUPPORTED_PROPS,
|
|
101
|
+
);
|
|
102
|
+
const restRecord = sanitizedProps as Record<string, unknown>;
|
|
95
103
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
: styles.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
:
|
|
115
|
-
|
|
104
|
+
// Securely map core native blocks down ensuring nested CSS modules map precisely. Note MUI's
|
|
105
|
+
// actual prop is "classes", not "classNames" (that's Mantine's naming) — this used to read
|
|
106
|
+
// the wrong key, silently no-op-ing any caller-supplied classes. The caller-facing slot names
|
|
107
|
+
// below (wrapper/input/dropdown/option) mirror the mantine-adapter's own Autocomplete
|
|
108
|
+
// classNames slots, translated to MUI's real classes slot names (root/inputRoot/listbox/option).
|
|
109
|
+
const callerClasses = restRecord.classes as
|
|
110
|
+
| Partial<Record<string, string>>
|
|
111
|
+
| undefined;
|
|
112
|
+
const mergedClassNames = mergeClassNames(
|
|
113
|
+
{
|
|
114
|
+
root: styles.root,
|
|
115
|
+
inputRoot: styles.input, // Map Mantine .input (wrapper) to MUI's inputRoot
|
|
116
|
+
listbox: styles.dropdown, // Map Mantine .dropdown to MUI's listbox
|
|
117
|
+
option: styles.option,
|
|
118
|
+
},
|
|
119
|
+
callerClasses && {
|
|
120
|
+
root: callerClasses.wrapper,
|
|
121
|
+
inputRoot: callerClasses.input,
|
|
122
|
+
listbox: callerClasses.dropdown,
|
|
123
|
+
option: callerClasses.option,
|
|
124
|
+
},
|
|
125
|
+
);
|
|
116
126
|
|
|
117
127
|
const wrapperClass = className
|
|
118
128
|
? `${styles.layoutOverride} ${className}`
|
|
@@ -148,6 +158,8 @@ export const Autocomplete = forwardRef<HTMLInputElement, AutocompleteProps>(
|
|
|
148
158
|
/* Naked Input execution safely decoupled from Mui's macro Input.Wrapper DOM hooks */
|
|
149
159
|
<MuiAutocomplete
|
|
150
160
|
ref={ref}
|
|
161
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
162
|
+
{...(sanitizedProps as any)}
|
|
151
163
|
freeSolo
|
|
152
164
|
disableClearable
|
|
153
165
|
classes={mergedClassNames}
|
|
@@ -7,14 +7,6 @@ HARDCODED VALUES:
|
|
|
7
7
|
- Image has no explicit size tokens other than root size, assume it scales 100%
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
/* EXEMPTIONS:
|
|
11
|
-
The generic size variables below are defined in Figma but unused.
|
|
12
|
-
We explicitly style the avatar's dimensions using the dedicated width and height tokens
|
|
13
|
-
(e.g., small_properties_width and small_properties_height) for precise component layout. */
|
|
14
|
-
/* recursica-ignore: --recursica_ui-kit_components_avatar_variants_sizes_default_properties_size */
|
|
15
|
-
/* recursica-ignore: --recursica_ui-kit_components_avatar_variants_sizes_large_properties_size */
|
|
16
|
-
/* recursica-ignore: --recursica_ui-kit_components_avatar_variants_sizes_small_properties_size */
|
|
17
|
-
|
|
18
10
|
.root {
|
|
19
11
|
box-sizing: border-box;
|
|
20
12
|
margin: 0;
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from "@mui/material";
|
|
6
6
|
import {
|
|
7
7
|
filterStylingProps,
|
|
8
|
+
mergeClassNames,
|
|
8
9
|
type RecursicaOverStyled,
|
|
9
10
|
} from "../../utils/filterStylingProps";
|
|
10
11
|
import styles from "./Avatar.module.css";
|
|
@@ -42,27 +43,16 @@ const _Avatar = forwardRef<HTMLDivElement, AvatarProps>(function Avatar(
|
|
|
42
43
|
computedStyle = "icon";
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
!Array.isArray(classNamesProp)
|
|
56
|
-
) {
|
|
57
|
-
const o = classNamesProp as Partial<Record<string, string>>;
|
|
58
|
-
mergedClassNames.root = o.root ? `${styles.root} ${o.root}` : styles.root;
|
|
59
|
-
mergedClassNames.image = o.image
|
|
60
|
-
? `${styles.image} ${o.image}`
|
|
61
|
-
: styles.image;
|
|
62
|
-
mergedClassNames.placeholder = o.placeholder
|
|
63
|
-
? `${styles.placeholder} ${o.placeholder}`
|
|
64
|
-
: styles.placeholder;
|
|
65
|
-
}
|
|
46
|
+
// Note MUI's actual prop is "classes", not "classNames" (that's Mantine's naming) — this
|
|
47
|
+
// used to read the wrong key, silently no-op-ing any caller-supplied classes.
|
|
48
|
+
const mergedClassNames = mergeClassNames(
|
|
49
|
+
{
|
|
50
|
+
root: styles.root,
|
|
51
|
+
image: styles.image,
|
|
52
|
+
placeholder: styles.placeholder,
|
|
53
|
+
},
|
|
54
|
+
restRecord.classes as Partial<Record<string, string>> | undefined,
|
|
55
|
+
);
|
|
66
56
|
|
|
67
57
|
const classNameProp = restRecord.className as string | undefined;
|
|
68
58
|
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from "@mui/material";
|
|
6
6
|
import {
|
|
7
7
|
filterStylingProps,
|
|
8
|
+
mergeClassNames,
|
|
8
9
|
type RecursicaOverStyled,
|
|
9
10
|
} from "../../utils/filterStylingProps";
|
|
10
11
|
import styles from "./Badge.module.css";
|
|
@@ -24,19 +25,14 @@ const _Badge = forwardRef<HTMLDivElement, BadgeProps>(function Badge(
|
|
|
24
25
|
// External layout props like margins are safely preserved.
|
|
25
26
|
const sanitizedProps = filterStylingProps(rest, overStyled);
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
!Array.isArray(classNamesProp)
|
|
36
|
-
) {
|
|
37
|
-
const o = classNamesProp as Partial<Record<string, string>>;
|
|
38
|
-
mergedClassNames.root = o.root ? `${styles.root} ${o.root}` : styles.root;
|
|
39
|
-
}
|
|
28
|
+
// Note MUI's actual prop is "classes", not "classNames" (that's Mantine's naming) — this
|
|
29
|
+
// used to read the wrong key, silently no-op-ing any caller-supplied classes.
|
|
30
|
+
const mergedClassNames = mergeClassNames(
|
|
31
|
+
{ root: styles.root },
|
|
32
|
+
(sanitizedProps as Record<string, unknown>).classes as
|
|
33
|
+
| Partial<Record<string, string>>
|
|
34
|
+
| undefined,
|
|
35
|
+
);
|
|
40
36
|
|
|
41
37
|
const classNameProp = (sanitizedProps as Record<string, unknown>)
|
|
42
38
|
.className as string | undefined;
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from "@mui/material";
|
|
6
6
|
import {
|
|
7
7
|
filterStylingProps,
|
|
8
|
+
mergeClassNames,
|
|
8
9
|
type RecursicaOverStyled,
|
|
9
10
|
} from "../../utils/filterStylingProps";
|
|
10
11
|
import styles from "./Breadcrumb.module.css";
|
|
@@ -22,24 +23,17 @@ export const Breadcrumb = forwardRef<HTMLDivElement, BreadcrumbProps>(
|
|
|
22
23
|
overStyled,
|
|
23
24
|
);
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
) {
|
|
37
|
-
const o = classNamesProp as Partial<Record<string, string>>;
|
|
38
|
-
mergedClassNames.root = o.root ? `${styles.root} ${o.root}` : styles.root;
|
|
39
|
-
mergedClassNames.separator = o.separator
|
|
40
|
-
? `${styles.separator} ${o.separator}`
|
|
41
|
-
: styles.separator;
|
|
42
|
-
}
|
|
26
|
+
// Note MUI's actual prop is "classes", not "classNames" (that's Mantine's naming) — this
|
|
27
|
+
// used to read the wrong key, silently no-op-ing any caller-supplied classes.
|
|
28
|
+
const mergedClassNames = mergeClassNames(
|
|
29
|
+
{
|
|
30
|
+
root: styles.root,
|
|
31
|
+
separator: styles.separator,
|
|
32
|
+
},
|
|
33
|
+
(sanitizedProps as Record<string, unknown>).classes as
|
|
34
|
+
| Partial<Record<string, string>>
|
|
35
|
+
| undefined,
|
|
36
|
+
);
|
|
43
37
|
|
|
44
38
|
const classNameProp = (sanitizedProps as Record<string, unknown>)
|
|
45
39
|
.className as string | undefined;
|
|
@@ -378,16 +378,3 @@
|
|
|
378
378
|
var(--recursica_brand_states_focus_margin)
|
|
379
379
|
var(--recursica_brand_states_focus_color);
|
|
380
380
|
}
|
|
381
|
-
|
|
382
|
-
/* EXEMPTIONS:
|
|
383
|
-
These general button properties are redundant because visual mapping has been refactored
|
|
384
|
-
to content-specific variables (_content_label_sizes_... etc.) or are handled by design defaults. */
|
|
385
|
-
/* recursica-ignore: --recursica_ui-kit_components_button_variants_sizes_default_properties_horizontal-padding */
|
|
386
|
-
/* recursica-ignore: --recursica_ui-kit_components_button_variants_sizes_default_properties_min-width */
|
|
387
|
-
/* recursica-ignore: --recursica_ui-kit_components_button_variants_sizes_small_properties_horizontal-padding */
|
|
388
|
-
/* recursica-ignore: --recursica_ui-kit_components_button_variants_sizes_small_properties_hover-opacity */
|
|
389
|
-
/* recursica-ignore: --recursica_ui-kit_components_button_variants_sizes_small_properties_min-width */
|
|
390
|
-
/* recursica-ignore: --recursica_ui-kit_components_button_variants_sizes_small_properties_text_font-style */
|
|
391
|
-
/* recursica-ignore: --recursica_ui-kit_components_button_variants_sizes_small_properties_text_letter-spacing */
|
|
392
|
-
/* recursica-ignore: --recursica_ui-kit_components_button_variants_sizes_small_properties_text_text-decoration */
|
|
393
|
-
/* recursica-ignore: --recursica_ui-kit_components_button_variants_sizes_small_properties_text_text-transform */
|
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
} from "@mui/material";
|
|
7
7
|
import {
|
|
8
8
|
filterStylingProps,
|
|
9
|
+
omitUnsupportedProps,
|
|
10
|
+
withCallerOverride,
|
|
9
11
|
type RecursicaOverStyled,
|
|
10
12
|
} from "../../utils/filterStylingProps";
|
|
11
13
|
import styles from "./Button.module.css";
|
|
@@ -54,11 +56,17 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
54
56
|
},
|
|
55
57
|
ref,
|
|
56
58
|
) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
// Props this component intentionally doesn't support — deleted at runtime so they can't leak
|
|
60
|
+
// through even if a caller forces them via plain JavaScript, bypassing the Omit<> above.
|
|
61
|
+
const UNSUPPORTED_PROPS = [
|
|
62
|
+
"color", // Recursica handles colors internally via tokens
|
|
63
|
+
] as const satisfies readonly (keyof MuiButtonProps)[];
|
|
59
64
|
|
|
60
|
-
|
|
61
|
-
|
|
65
|
+
const sanitizedProps = omitUnsupportedProps(
|
|
66
|
+
filterStylingProps(rest, overStyled),
|
|
67
|
+
UNSUPPORTED_PROPS,
|
|
68
|
+
);
|
|
69
|
+
const restRecord = sanitizedProps as Record<string, unknown>;
|
|
62
70
|
|
|
63
71
|
const hasStartIcon = !!icon || !!restRecord["startIcon"];
|
|
64
72
|
const hasEndIcon = !!restRecord["endIcon"];
|
|
@@ -87,18 +95,16 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
87
95
|
const finalClass = classNameProp
|
|
88
96
|
? `${styles.root} ${classNameProp}`
|
|
89
97
|
: styles.root;
|
|
90
|
-
// className is merged explicitly above — don't let the {...sanitizedProps} spread below
|
|
91
|
-
// silently overwrite finalClass with just the caller's own class (same bug class as
|
|
92
|
-
// mui-adapter's Dropdown/BareDropdown.tsx had).
|
|
93
|
-
delete restRecord["className"];
|
|
94
98
|
|
|
95
99
|
// We don't map Recursica variant/size to MUI's because we want to completely disable MUI's native
|
|
96
100
|
// variant logic (e.g., elevation, shadows) and style everything strictly through our CSS Modules.
|
|
97
101
|
// However, Mui Button requires some string, but we can just leave it as standard or ignore since
|
|
98
102
|
// our CSS resets its properties anyway, but to be clean we just don't pass variant to MUI.
|
|
99
103
|
|
|
100
|
-
const resolvedLoaderSize =
|
|
101
|
-
|
|
104
|
+
const resolvedLoaderSize = withCallerOverride(
|
|
105
|
+
size === "small" ? "small" : "default",
|
|
106
|
+
loaderSize,
|
|
107
|
+
);
|
|
102
108
|
|
|
103
109
|
let loadingIndicator = restRecord.loadingIndicator as React.ReactNode;
|
|
104
110
|
if (useRecursicaLoader) {
|
|
@@ -110,6 +116,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
110
116
|
return (
|
|
111
117
|
<MuiButton
|
|
112
118
|
ref={ref}
|
|
119
|
+
{...sanitizedProps}
|
|
113
120
|
disableRipple
|
|
114
121
|
disableElevation
|
|
115
122
|
className={finalClass}
|
|
@@ -131,7 +138,6 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
131
138
|
data-size={size}
|
|
132
139
|
data-content={contentType}
|
|
133
140
|
{...(restRecord.loading ? { "data-loading": "true" } : {})}
|
|
134
|
-
{...sanitizedProps}
|
|
135
141
|
disabled={!!restRecord.disabled || !!restRecord.loading}
|
|
136
142
|
>
|
|
137
143
|
<span className={styles.labelText}>{children}</span>
|
|
@@ -2,6 +2,7 @@ import { forwardRef } from "react";
|
|
|
2
2
|
import { Card as MuiCard, type CardProps as MuiCardProps } from "@mui/material";
|
|
3
3
|
import {
|
|
4
4
|
filterStylingProps,
|
|
5
|
+
mergeClassNames,
|
|
5
6
|
type RecursicaOverStyled,
|
|
6
7
|
} from "../../utils/filterStylingProps";
|
|
7
8
|
import styles from "./Card.module.css";
|
|
@@ -51,25 +52,14 @@ const CardBase = forwardRef<HTMLDivElement, CardProps>(function Card(
|
|
|
51
52
|
}
|
|
52
53
|
});
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
!Array.isArray(classNamesProp)
|
|
63
|
-
) {
|
|
64
|
-
const o = classNamesProp as Record<string, string>;
|
|
65
|
-
Object.keys(o).forEach((key) => {
|
|
66
|
-
if (mergedClassNames[key]) {
|
|
67
|
-
mergedClassNames[key] = `${mergedClassNames[key]} ${o[key]}`;
|
|
68
|
-
} else {
|
|
69
|
-
mergedClassNames[key] = o[key];
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
}
|
|
55
|
+
// Note MUI's actual prop is "classes", not "classNames" (that's Mantine's naming) — this
|
|
56
|
+
// used to read the wrong key, silently no-op-ing any caller-supplied classes.
|
|
57
|
+
const mergedClassNames = mergeClassNames(
|
|
58
|
+
{ root: styles.root },
|
|
59
|
+
(sanitizedProps as Record<string, unknown>).classes as
|
|
60
|
+
| Partial<Record<string, string>>
|
|
61
|
+
| undefined,
|
|
62
|
+
);
|
|
73
63
|
|
|
74
64
|
const classNameProp = (sanitizedProps as Record<string, unknown>)
|
|
75
65
|
.className as string | undefined;
|
|
@@ -7,6 +7,8 @@ import { type ReadOnlyControlProps } from "@recursica/adapter-common";
|
|
|
7
7
|
import { CheckboxGroup, CheckboxGroupContext } from "./CheckboxGroup";
|
|
8
8
|
import {
|
|
9
9
|
filterStylingProps,
|
|
10
|
+
omitUnsupportedProps,
|
|
11
|
+
mergeClassNames,
|
|
10
12
|
type RecursicaOverStyled,
|
|
11
13
|
} from "../../utils/filterStylingProps";
|
|
12
14
|
import {
|
|
@@ -58,33 +60,27 @@ export const Checkbox = forwardRef<HTMLButtonElement, CheckboxProps>(
|
|
|
58
60
|
...rest
|
|
59
61
|
} = props;
|
|
60
62
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
// Props this component intentionally doesn't support — deleted at runtime so they can't leak
|
|
64
|
+
// through even if a caller forces them via plain JavaScript, bypassing the Omit<> above.
|
|
65
|
+
const UNSUPPORTED_PROPS = [
|
|
66
|
+
"size", // Recursica controls sizing via design tokens, not MUI's native small/medium size
|
|
67
|
+
"color", // Colors are token-driven; MUI's native palette isn't exposed
|
|
68
|
+
] as const satisfies readonly (keyof MuiCheckboxProps)[];
|
|
66
69
|
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
const sanitizedProps = omitUnsupportedProps(
|
|
71
|
+
filterStylingProps(rest, overStyled),
|
|
72
|
+
UNSUPPORTED_PROPS,
|
|
73
|
+
);
|
|
74
|
+
const restRecord = sanitizedProps as Record<string, unknown>;
|
|
72
75
|
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
mergedClassNames.checked = o.checked
|
|
82
|
-
? `${styles.checked} ${o.checked}`
|
|
83
|
-
: styles.checked;
|
|
84
|
-
mergedClassNames.disabled = o.disabled
|
|
85
|
-
? `${styles.disabled} ${o.disabled}`
|
|
86
|
-
: styles.disabled;
|
|
87
|
-
}
|
|
76
|
+
const mergedClassNames = mergeClassNames(
|
|
77
|
+
{
|
|
78
|
+
root: styles.root,
|
|
79
|
+
checked: styles.checked,
|
|
80
|
+
disabled: styles.disabled,
|
|
81
|
+
},
|
|
82
|
+
restRecord.classes as Partial<Record<string, string>> | undefined,
|
|
83
|
+
);
|
|
88
84
|
|
|
89
85
|
const classNameProp = restRecord.className as string | undefined;
|
|
90
86
|
const finalClass = classNameProp
|
|
@@ -172,10 +168,10 @@ export const Checkbox = forwardRef<HTMLButtonElement, CheckboxProps>(
|
|
|
172
168
|
const checkboxNode = (
|
|
173
169
|
<MuiCheckbox
|
|
174
170
|
ref={ref}
|
|
171
|
+
{...(sanitizedProps as unknown as MuiCheckboxProps)}
|
|
175
172
|
className={!label ? `${finalClass} ${styles.inner}` : styles.inner}
|
|
176
173
|
classes={mergedClassNames}
|
|
177
174
|
disabled={readOnly || disabled || isGroupReadOnly}
|
|
178
|
-
{...(sanitizedProps as unknown as MuiCheckboxProps)}
|
|
179
175
|
{...(isControlled ? { checked: isChecked as boolean } : {})}
|
|
180
176
|
onChange={handleChange}
|
|
181
177
|
disableRipple
|
|
@@ -75,12 +75,12 @@ export const CheckboxGroup = forwardRef<HTMLDivElement, CheckboxGroupProps>(
|
|
|
75
75
|
row,
|
|
76
76
|
...rest
|
|
77
77
|
} = props;
|
|
78
|
+
// NOTE: this component's props surface (HTMLAttributes<HTMLDivElement>-based) has no native
|
|
79
|
+
// "size" (or other Recursica-unsupported) prop to leak through here — nothing to add to an
|
|
80
|
+
// UNSUPPORTED_PROPS list beyond what filterStylingProps already blocks.
|
|
78
81
|
const sanitizedProps = filterStylingProps(rest, overStyled);
|
|
79
82
|
const restRecord = sanitizedProps as Record<string, unknown>;
|
|
80
83
|
|
|
81
|
-
// Delete prohibited sizing hooks
|
|
82
|
-
delete restRecord["size"];
|
|
83
|
-
|
|
84
84
|
const handleChange = (_event: React.SyntheticEvent, childValue: any) => {
|
|
85
85
|
if (onChange) {
|
|
86
86
|
onChange(childValue);
|
|
@@ -1,30 +1,3 @@
|
|
|
1
|
-
/* EXEMPTIONS:
|
|
2
|
-
- close-icon-color, leading-icon-color, and select-specific state icon variables are ignored because
|
|
3
|
-
Chip uses explicit state color definitions (e.g. unselected, selected, error, error-selected variant colors)
|
|
4
|
-
to drive pixel-perfect hover and click transitions, rather than generic single-color overrides.
|
|
5
|
-
- text-size is ignored since actual chip sizing is dynamically driven by precise vertical padding and line-heights. */
|
|
6
|
-
/* recursica-ignore: --recursica_ui-kit_components_chip_properties_close-icon-color */
|
|
7
|
-
/* recursica-ignore: --recursica_ui-kit_components_chip_properties_leading-icon-color */
|
|
8
|
-
/* recursica-ignore: --recursica_ui-kit_components_chip_properties_text-size */
|
|
9
|
-
/* recursica-ignore: --recursica_ui-kit_components_chip_variants_styles_error_properties_colors_leading-icon-color */
|
|
10
|
-
/* recursica-ignore: --recursica_ui-kit_components_chip_variants_styles_error_properties_colors_selected-icon-color */
|
|
11
|
-
/* recursica-ignore: --recursica_ui-kit_components_chip_variants_styles_error-selected_properties_colors_icon */
|
|
12
|
-
/* recursica-ignore: --recursica_ui-kit_components_chip_variants_styles_error-selected_properties_colors_leading-icon-color */
|
|
13
|
-
/* recursica-ignore: --recursica_ui-kit_components_chip_variants_styles_selected_properties_colors_leading-icon-color */
|
|
14
|
-
/* recursica-ignore: --recursica_ui-kit_components_chip_variants_styles_unselected_properties_colors_selected-icon-color */
|
|
15
|
-
|
|
16
|
-
/* Additional exemptions matching the reference mantine-adapter's Chip.module.css: per selection/error
|
|
17
|
-
state, Figma defines multiple icon-color slots (leading-icon-color, selected-icon-color, and for
|
|
18
|
-
error states also a generic icon-color), but each state only ever consumes ONE of them (leading
|
|
19
|
-
when unselected, selected when selected). The remaining "off" slots per state are intentionally
|
|
20
|
-
unused, same as in the mantine reference. */
|
|
21
|
-
/* recursica-ignore: --recursica_ui-kit_components_chip_variants_selection-states_selected_properties_colors_leading-icon-color */
|
|
22
|
-
/* recursica-ignore: --recursica_ui-kit_components_chip_variants_selection-states_selected_variants_states_error_properties_colors_icon-color */
|
|
23
|
-
/* recursica-ignore: --recursica_ui-kit_components_chip_variants_selection-states_selected_variants_states_error_properties_colors_leading-icon-color */
|
|
24
|
-
/* recursica-ignore: --recursica_ui-kit_components_chip_variants_selection-states_unselected_properties_colors_selected-icon-color */
|
|
25
|
-
/* recursica-ignore: --recursica_ui-kit_components_chip_variants_selection-states_unselected_variants_states_error_properties_colors_icon-color */
|
|
26
|
-
/* recursica-ignore: --recursica_ui-kit_components_chip_variants_selection-states_unselected_variants_states_error_properties_colors_selected-icon-color */
|
|
27
|
-
|
|
28
1
|
/* HARDCODED VALUES
|
|
29
2
|
border-style: solid; (Baseline reset)
|
|
30
3
|
box-sizing: border-box; (Baseline reset)
|
|
@@ -2,6 +2,7 @@ import React, { forwardRef } from "react";
|
|
|
2
2
|
import { Chip as MuiChip, type ChipProps as MuiChipProps } from "@mui/material";
|
|
3
3
|
import {
|
|
4
4
|
filterStylingProps,
|
|
5
|
+
mergeClassNames,
|
|
5
6
|
type RecursicaOverStyled,
|
|
6
7
|
} from "../../utils/filterStylingProps";
|
|
7
8
|
import styles from "./Chip.module.css";
|
|
@@ -75,25 +76,17 @@ export const Chip = forwardRef<HTMLInputElement, ChipProps>(function Chip(
|
|
|
75
76
|
const sanitizedProps = filterStylingProps(rest, overStyled);
|
|
76
77
|
const restRecord = sanitizedProps as Record<string, unknown>;
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
!Array.isArray(classNamesProp)
|
|
90
|
-
) {
|
|
91
|
-
const o = classNamesProp as Partial<Record<string, string>>;
|
|
92
|
-
mergedClassNames.root = o.root ? `${styles.root} ${o.root}` : styles.root;
|
|
93
|
-
mergedClassNames.label = o.label
|
|
94
|
-
? `${styles.label} ${o.label}`
|
|
95
|
-
: styles.label;
|
|
96
|
-
}
|
|
79
|
+
// Note MUI's actual prop is "classes", not "classNames" (that's Mantine's naming) — this
|
|
80
|
+
// used to read the wrong key, silently no-op-ing any caller-supplied classes.
|
|
81
|
+
const mergedClassNames = mergeClassNames(
|
|
82
|
+
{
|
|
83
|
+
root: styles.root,
|
|
84
|
+
label: styles.label,
|
|
85
|
+
icon: styles.leadingIcon,
|
|
86
|
+
deleteIcon: styles.removeIconWrapper,
|
|
87
|
+
},
|
|
88
|
+
restRecord.classes as Partial<Record<string, string>> | undefined,
|
|
89
|
+
);
|
|
97
90
|
|
|
98
91
|
const classNameProp = restRecord.className as string | undefined;
|
|
99
92
|
const finalClass = classNameProp
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# DatePicker Implementation Notes
|
|
2
|
+
|
|
3
|
+
## Architecture Overview
|
|
4
|
+
|
|
5
|
+
The `DatePicker` component is a wrapper around MUI X's `@mui/x-date-pickers` `DatePicker`, implementing the `FormControlWrapper` macro structure for Recursica. This ensures that the component visually and structurally aligns with standard Recursica input primitives, and mirrors the mantine-adapter's own `DatePicker` behavior and token mapping wherever the two libraries' APIs allow it.
|
|
6
|
+
|
|
7
|
+
## Structural Constraints
|
|
8
|
+
|
|
9
|
+
1. **Naked Field Usage**: We intentionally pass `label={undefined}`/`description`/`error` through `WithReadOnlyWrapper` rather than MUI X's own field label/helper-text props, so only our `WithReadOnlyWrapper` > `FormControlWrapper` orchestrates labels, description text, and ARIA state error boundaries — same convention as TimePicker.tsx.
|
|
10
|
+
2. **Value Type**: MUI X's `DatePicker` only speaks `Dayjs` (via `AdapterDayjs`); the public API converts to/from plain `Date` internally so `value`/`defaultValue`/`onChange` match the mantine-adapter's convention exactly.
|
|
11
|
+
3. **Read-Only Rendering**: The selected date is displayed formatted as `MM/DD/YY` (via `dayjs(...).format(...)`), not a raw `String(value)` cast (unlike the mantine-adapter, which has no reason to reach for `dayjs` outside read-only formatting). Pass `readOnlyComponent` to override.
|
|
12
|
+
4. **Typing vs. Popover Entry**: Unlike the mantine-adapter (`DatePickerInput`, popover-only), MUI X's field natively supports typing directly into masked date segments in addition to the popup calendar — no adapter code needed to enable this, it's the library default.
|
|
13
|
+
5. **Calendar Popover (Figma Token Issue)**: Same root cause as the mantine-adapter — the UI Kit's `date-picker` component exports no calendar-specific tokens (surface, selected day, hover, today, in-range) — so the calendar reuses the closest existing tokens, mapped 1:1 to the mantine-adapter's choices:
|
|
14
|
+
- **Surface** (`.dropdown`, applied to `slotProps.desktopPaper`) & **selected day** (`.MuiPickerDay-root.Mui-selected`): `--recursica_ui-kit_components_hover-card-popover` tokens for elevation/padding/surface, `--recursica_ui-kit_components_button_variants_styles_solid` tokens for the selected day fill.
|
|
15
|
+
- **Header content, weekday row** (`.MuiPickersCalendarHeader-root`, `.MuiDayCalendar-weekDayLabel`): the hover-card-popover content-color token.
|
|
16
|
+
- **Header nav controls** (`.MuiPickersArrowSwitcher-button`, `.MuiPickersCalendarHeader-switchViewButton`) **& month/year label** (`.MuiPickersCalendarHeader-label`): styled with `--recursica_ui-kit_components_button_variants_styles_text` tokens (border/background/color/elevation) plus small-size `Button` sizing tokens — the same tokens the mantine-adapter's `.calendarHeaderControl`/`.calendarHeaderLevel` use. Note the DOM shape differs from mantine: MUI splits the month/year control into a plain, non-interactive `.label` text node (`role="none"` on its `labelContainer` parent) plus a separate small `.switchViewButton` that's the actual clickable control — only the label's _color/typography_ is recolored to match (no border/background chrome on a non-interactive element), while the switchViewButton gets the full button treatment.
|
|
17
|
+
- **Hover** (day cells, header nav, header level button): the generic `--recursica_brand_states_hover_color`/`_hover_opacity` pair via the same `::after`-overlay technique the mantine-adapter and `Button`/`Menu` use — a plain `background-color`+`opacity` change on the element itself would dim its icon/text along with the fill, not just tint the surface.
|
|
18
|
+
- **Disabled / outside-month days** (`.Mui-disabled`, `.MuiPickerDay-dayOutsideMonth`): the generic `--recursica_brand_states_disabled` opacity token, same convention used repo-wide. Disabled header nav/label controls use `Button`'s own text-variant disabled opacity/elevation tokens instead.
|
|
19
|
+
- **Today marker** (`.MuiPickerDay-today`): reuses the hover-card-popover border-size/color tokens already used for the dropdown surface's own border — same reasoning as mantine-adapter (no dedicated "today" token exists). MUI ships its own default light-primary-color background tint on this class, which is explicitly reset to `transparent` since the design system has no equivalent.
|
|
20
|
+
- **Range selection / weekend styling**: not mapped. MUI X's default `DatePicker` doesn't render range selection or weekend-specific styling at all (unlike Mantine, which colors weekends red by default and needed an explicit override), so neither state needed a Recursica mapping.
|
|
21
|
+
|
|
22
|
+
## Styling Quirks
|
|
23
|
+
|
|
24
|
+
- **Native field decoration suppression**: MUI X's field variant can't be forced to a bare/unstyled mode from this component's public API (same constraint TimePicker.module.css documents) — whichever native outline/underline decoration it renders by default is suppressed via `:global(...)` overrides, and `.field` draws the Recursica border/background/color instead.
|
|
25
|
+
- **Popover `slotProps` gotcha, same shape as mantine-adapter's `popoverProps` bug**: the calendar popup is portaled to `document.body`, entirely outside this component's own DOM subtree, so it can only be reached by handing MUI X's `slotProps.desktopPaper` a `className` directly — a descendant CSS selector from `.root` would never match it. `DatePicker.tsx` merges any consumer-supplied `slotProps.field`/`slotProps.desktopPaper` (and their `className`s) with our own defaults instead of overwriting them.
|
|
26
|
+
- **`.dropdown`'s `!important`s**: unlike the mantine-adapter's equivalent rule (plain CSS-module specificity, no `!important` needed — `@mantine/dates`' own stylesheet reliably loads first), MUI X injects its own `Paper`/`Popper` styles at runtime via emotion, with no guaranteed load-order relative to this CSS module. `!important` here just re-asserts token values against a same-specificity emotion rule that could otherwise win on source order alone.
|
|
27
|
+
- **Default display/entry format**: `format="MM/DD/YY"` (a `dayjs` format string, MUI X's own default is `"MM/DD/YYYY"`), matching the mantine-adapter's `valueFormat` default.
|
|
28
|
+
- **Default leading icon**: the calendar icon is MUI X's own built-in "open picker" button (`slots.openPickerIcon`, positioned via `openPickerButtonPosition="start"` on the field slot) rather than a separate decorative `InputAdornment` — clicking it opens the calendar, unlike the mantine-adapter's purely decorative `leftSection` icon (there, the whole field is the click target). Overridable via `slots={{ openPickerIcon: MyIcon }}`, following the same override-prop convention as `leftSection` elsewhere in this adapter.
|
|
29
|
+
- **No native `::placeholder`**: empty date segments ("MM", "DD", "YYYY") render via MUI X's own internal placeholder styling, not a native `::placeholder` pseudo-element — there's no selector to hook the `placeholder-opacity` token onto, same constraint TimePicker.module.css documents for its own empty sections.
|