@recursica/mui-adapter 0.21.1 → 0.23.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/ARCHITECTURE.md +3 -0
- package/CHANGELOG.md +75 -0
- package/dist/index.d.ts +247 -41
- package/dist/mui-adapter.cjs +78 -78
- package/dist/mui-adapter.cjs.map +1 -1
- package/dist/mui-adapter.css +1 -1
- package/dist/mui-adapter.js +7624 -7311
- package/dist/mui-adapter.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Accordion/ACCORDION_IMPLEMENTATION_NOTES.md +63 -0
- package/src/components/Accordion/Accordion.module.css +21 -2
- package/src/components/Accordion/Accordion.stories.tsx +38 -0
- package/src/components/Accordion/Accordion.tsx +27 -12
- package/src/components/AssistiveElement/ASSISTIVEELEMENT_IMPLEMENTATION_NOTES.md +59 -0
- package/src/components/AssistiveElement/AssistiveElement.module.css +8 -1
- package/src/components/AssistiveElement/AssistiveElement.tsx +17 -7
- package/src/components/Autocomplete/Autocomplete.tsx +18 -2
- package/src/components/Avatar/AVATAR_IMPLEMENTATION_NOTES.md +36 -0
- package/src/components/Avatar/Avatar.tsx +6 -9
- package/src/components/Badge/Badge.module.css +0 -12
- package/src/components/Badge/Badge.tsx +0 -4
- package/src/components/Button/Button.module.css +13 -0
- package/src/components/Card/Card.tsx +5 -5
- package/src/components/Checkbox/Checkbox.module.css +17 -21
- package/src/components/Checkbox/Checkbox.tsx +24 -18
- package/src/components/Chip/CHIP_IMPLEMENTATION_NOTES.md +102 -0
- package/src/components/Chip/Chip.module.css +46 -11
- package/src/components/Chip/Chip.tsx +41 -5
- package/src/components/Chip/USAGE.md +19 -0
- package/src/components/FileUpload/FILEUPLOAD_IMPLEMENTATION_NOTES.md +249 -0
- package/src/components/FileUpload/FileUpload.module.css +203 -38
- package/src/components/FileUpload/FileUpload.stories.tsx +348 -4
- package/src/components/FileUpload/FileUpload.tsx +350 -6
- package/src/components/FileUpload/USAGE.md +163 -5
- package/src/components/Flex/Flex.tsx +1 -1
- package/src/components/FormControlWrapper/FORMCONTROLWRAPPER_IMPLEMENTATION_NOTES.md +35 -0
- package/src/components/FormControlWrapper/FormControlWrapper.tsx +24 -8
- package/src/components/Grid/Grid.tsx +1 -1
- package/src/components/Group/Group.tsx +1 -1
- package/src/components/HoverCard/HoverCard.tsx +1 -1
- package/src/components/Label/Label.module.css +15 -2
- package/src/components/Label/Label.tsx +7 -6
- package/src/components/Menu/Menu.tsx +1 -1
- package/src/components/Modal/Modal.tsx +1 -1
- package/src/components/NumberInput/NumberInput.tsx +1 -1
- package/src/components/Pagination/Pagination.tsx +1 -1
- package/src/components/Panel/Panel.tsx +1 -1
- package/src/components/Radio/Radio.module.css +33 -38
- package/src/components/Radio/Radio.tsx +58 -44
- package/src/components/Radio/RadioGroup.tsx +5 -0
- package/src/components/SegmentedControl/SegmentedControl.module.css +0 -7
- package/src/components/SegmentedControl/SegmentedControl.tsx +4 -8
- package/src/components/Slider/Slider.tsx +1 -1
- package/src/components/Stack/Stack.tsx +1 -1
- package/src/components/Stepper/Stepper.tsx +3 -3
- package/src/components/Switch/SWITCH_IMPLEMENTATION_NOTES.md +123 -0
- package/src/components/Switch/Switch.module.css +152 -89
- package/src/components/Switch/Switch.tsx +140 -33
- package/src/components/Switch/SwitchGroup.tsx +37 -9
- package/src/components/Table/Table.tsx +1 -1
- package/src/components/Tabs/IMPLEMENTATION_NOTES.md +10 -0
- package/src/components/Tabs/Tabs.module.css +120 -43
- package/src/components/Tabs/Tabs.stories.tsx +5 -2
- package/src/components/Tabs/Tabs.tsx +1 -1
- package/src/components/Tabs/USAGE.md +27 -11
- package/src/components/TextField/TextField.tsx +1 -1
- package/src/components/TimePicker/TimePicker.tsx +1 -1
- package/src/components/Timeline/Timeline.tsx +1 -1
- package/src/components/Timeline/TimelineItem.tsx +2 -2
- package/src/components/Toast/Toast.tsx +4 -4
- package/src/components/Tooltip/Tooltip.tsx +1 -1
- package/src/components/Tree/Tree.tsx +1 -1
- package/src/index.ts +3 -1
- package/src/types/mantine.d.ts +0 -7
|
@@ -46,6 +46,7 @@ export const Group = forwardRef<HTMLDivElement, GroupProps>(function Group(
|
|
|
46
46
|
return (
|
|
47
47
|
<MUIStack
|
|
48
48
|
ref={ref}
|
|
49
|
+
{...safeProps}
|
|
49
50
|
direction="row"
|
|
50
51
|
flexWrap={wrap || "wrap"}
|
|
51
52
|
justifyContent={justify}
|
|
@@ -55,7 +56,6 @@ export const Group = forwardRef<HTMLDivElement, GroupProps>(function Group(
|
|
|
55
56
|
...(resolvedRowGap ? { rowGap: resolvedRowGap } : {}),
|
|
56
57
|
...(resolvedColumnGap ? { columnGap: resolvedColumnGap } : {}),
|
|
57
58
|
}}
|
|
58
|
-
{...safeProps}
|
|
59
59
|
>
|
|
60
60
|
{children}
|
|
61
61
|
</MUIStack>
|
|
@@ -85,6 +85,7 @@ const HoverCardBase = function HoverCard({
|
|
|
85
85
|
|
|
86
86
|
return (
|
|
87
87
|
<MuiTooltip
|
|
88
|
+
{...(sanitizedProps as unknown as Record<string, unknown>)}
|
|
88
89
|
title={dropdownNode}
|
|
89
90
|
placement={position as unknown as MuiTooltipProps["placement"]}
|
|
90
91
|
arrow={withBeak}
|
|
@@ -104,7 +105,6 @@ const HoverCardBase = function HoverCard({
|
|
|
104
105
|
},
|
|
105
106
|
}}
|
|
106
107
|
classes={mergedClassNames as unknown as MuiTooltipProps["classes"]}
|
|
107
|
-
{...(sanitizedProps as unknown as Record<string, unknown>)}
|
|
108
108
|
>
|
|
109
109
|
{/* Tooltip requires a single valid React element child that accepts a ref */}
|
|
110
110
|
{isValidElement(targetNode) ? targetNode : <span>{targetNode}</span>}
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
|
|
49
49
|
.innerLayout {
|
|
50
50
|
display: flex;
|
|
51
|
+
flex-wrap: wrap;
|
|
51
52
|
align-items: center;
|
|
52
53
|
gap: var(
|
|
53
54
|
--recursica_ui-kit_components_label_properties_label-optional-text-gap
|
|
@@ -74,8 +75,8 @@
|
|
|
74
75
|
display: none !important;
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
/* MUI propagates .Mui-error to the label automatically on FormControl error.
|
|
78
|
-
We explicitly override this bleed to prevent the main label text from turning red,
|
|
78
|
+
/* MUI propagates .Mui-error to the label automatically on FormControl error.
|
|
79
|
+
We explicitly override this bleed to prevent the main label text from turning red,
|
|
79
80
|
while allowing the asterisk to take the error color! */
|
|
80
81
|
.root:global(.Mui-error) {
|
|
81
82
|
color: var(--recursica_ui-kit_components_label_properties_colors_text-color);
|
|
@@ -86,6 +87,15 @@
|
|
|
86
87
|
);
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
/* Same bleed, different trigger: MUI's FormControl propagates .Mui-focused to this label
|
|
91
|
+
whenever any descendant form control (e.g. a grouped Switch/Checkbox) receives focus,
|
|
92
|
+
turning the whole label MUI's primary blue via InputLabel's own default styling. No
|
|
93
|
+
Recursica design calls for a focus-reactive label color, so it's reset here — same fix as
|
|
94
|
+
the .Mui-error case above, just for the focused trigger instead of the error one. */
|
|
95
|
+
.root:global(.Mui-focused) {
|
|
96
|
+
color: var(--recursica_ui-kit_components_label_properties_colors_text-color);
|
|
97
|
+
}
|
|
98
|
+
|
|
89
99
|
.endNodes {
|
|
90
100
|
display: flex;
|
|
91
101
|
align-items: center;
|
|
@@ -95,6 +105,9 @@
|
|
|
95
105
|
}
|
|
96
106
|
|
|
97
107
|
.optionalText {
|
|
108
|
+
/* Forces the optional text onto its own row below the label text/end nodes,
|
|
109
|
+
mirroring mantine-adapter's flex-basis: 100% wrap behavior. */
|
|
110
|
+
flex-basis: 100%;
|
|
98
111
|
color: inherit;
|
|
99
112
|
font-family: var(
|
|
100
113
|
--recursica_ui-kit_components_label_properties_optional-text_font-family
|
|
@@ -46,9 +46,9 @@ export const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
|
|
|
46
46
|
return (
|
|
47
47
|
<InputLabel
|
|
48
48
|
ref={ref}
|
|
49
|
+
{...(sanitizedProps as InputLabelProps)}
|
|
49
50
|
shrink={true}
|
|
50
51
|
className={className ? `${styles.root} ${className}` : styles.root}
|
|
51
|
-
{...(sanitizedProps as InputLabelProps)}
|
|
52
52
|
>
|
|
53
53
|
<div className={styles.innerLayout}>
|
|
54
54
|
<span className={styles.textWrapper}>
|
|
@@ -60,11 +60,6 @@ export const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
|
|
|
60
60
|
{labelActionArea && (
|
|
61
61
|
<span className={styles.actionArea}>{labelActionArea}</span>
|
|
62
62
|
)}
|
|
63
|
-
{!required && labelOptionalText && !labelActionArea && (
|
|
64
|
-
<span className={styles.optionalText}>
|
|
65
|
-
{labelOptionalText === true ? "Optional" : labelOptionalText}
|
|
66
|
-
</span>
|
|
67
|
-
)}
|
|
68
63
|
{labelWithEditIcon && (
|
|
69
64
|
<button
|
|
70
65
|
type="button"
|
|
@@ -76,6 +71,12 @@ export const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
|
|
|
76
71
|
</button>
|
|
77
72
|
)}
|
|
78
73
|
</div>
|
|
74
|
+
|
|
75
|
+
{!required && labelOptionalText && !labelActionArea && (
|
|
76
|
+
<span className={styles.optionalText}>
|
|
77
|
+
{labelOptionalText === true ? "(optional)" : labelOptionalText}
|
|
78
|
+
</span>
|
|
79
|
+
)}
|
|
79
80
|
</div>
|
|
80
81
|
</InputLabel>
|
|
81
82
|
);
|
|
@@ -26,12 +26,12 @@ export const Menu = forwardRef<HTMLDivElement, MenuProps>(function Menu(
|
|
|
26
26
|
return (
|
|
27
27
|
<MuiMenu
|
|
28
28
|
ref={ref}
|
|
29
|
+
{...(sanitizedProps as MuiMenuProps)}
|
|
29
30
|
className={`${styles.dropdown} ${className || ""}`}
|
|
30
31
|
classes={{
|
|
31
32
|
paper: styles.dropdown,
|
|
32
33
|
list: styles.dropdown,
|
|
33
34
|
}}
|
|
34
|
-
{...(sanitizedProps as MuiMenuProps)}
|
|
35
35
|
/>
|
|
36
36
|
);
|
|
37
37
|
});
|
|
@@ -77,11 +77,11 @@ const ModalRoot = forwardRef<HTMLDivElement, ModalProps>(function ModalRoot(
|
|
|
77
77
|
return (
|
|
78
78
|
<MuiDialog
|
|
79
79
|
ref={ref}
|
|
80
|
+
{...(sanitizedProps as MuiDialogProps)}
|
|
80
81
|
className={`${styles.root} ${className || ""}`}
|
|
81
82
|
classes={{
|
|
82
83
|
paper: styles.inner,
|
|
83
84
|
}}
|
|
84
|
-
{...(sanitizedProps as MuiDialogProps)}
|
|
85
85
|
open={
|
|
86
86
|
opened ||
|
|
87
87
|
((sanitizedProps as Record<string, unknown>).open as boolean) ||
|
|
@@ -137,6 +137,7 @@ export const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>(
|
|
|
137
137
|
/* Naked Input execution safely decoupled from Mui's macro Input.Wrapper DOM hooks */
|
|
138
138
|
<MuiNumberInput
|
|
139
139
|
ref={ref}
|
|
140
|
+
{...(sanitizedProps as unknown as MuiNumberInputProps)}
|
|
140
141
|
classes={mergedClassNames}
|
|
141
142
|
disabled={disabled}
|
|
142
143
|
value={value}
|
|
@@ -154,7 +155,6 @@ export const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>(
|
|
|
154
155
|
step,
|
|
155
156
|
...(restRecord.inputProps as Record<string, unknown>),
|
|
156
157
|
}}
|
|
157
|
-
{...(sanitizedProps as unknown as MuiNumberInputProps)}
|
|
158
158
|
/>
|
|
159
159
|
}
|
|
160
160
|
/>
|
|
@@ -63,13 +63,13 @@ const _Pagination = forwardRef<HTMLDivElement, PaginationProps>(
|
|
|
63
63
|
return (
|
|
64
64
|
<div ref={ref} className={stylingParams.className}>
|
|
65
65
|
<MuiPagination
|
|
66
|
+
{...(sanitizedProps as MuiPaginationProps)}
|
|
66
67
|
count={total}
|
|
67
68
|
showFirstButton={withEdges}
|
|
68
69
|
showLastButton={withEdges}
|
|
69
70
|
hidePrevButton={withControls === false ? true : undefined}
|
|
70
71
|
hideNextButton={withControls === false ? true : undefined}
|
|
71
72
|
classes={stylingParams.classNames}
|
|
72
|
-
{...(sanitizedProps as MuiPaginationProps)}
|
|
73
73
|
/>
|
|
74
74
|
</div>
|
|
75
75
|
);
|
|
@@ -124,7 +124,7 @@ export const PanelFooter = forwardRef<HTMLDivElement, PanelFooterProps>(
|
|
|
124
124
|
? `${styles.footer} ${classNameProp}`
|
|
125
125
|
: styles.footer;
|
|
126
126
|
|
|
127
|
-
return <div ref={ref} className={finalClassName}
|
|
127
|
+
return <div ref={ref} {...sanitizedProps} className={finalClassName} />;
|
|
128
128
|
},
|
|
129
129
|
);
|
|
130
130
|
PanelFooter.displayName = "PanelFooter";
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
.
|
|
73
|
+
.inner {
|
|
74
74
|
display: flex;
|
|
75
75
|
align-items: center;
|
|
76
76
|
justify-content: center;
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
--recursica_ui-kit_components_radio-button-item_properties_text_font-size
|
|
83
83
|
);
|
|
84
84
|
/* Mathematically syncs the physical input radio bounding block visually with the center of the text's first line.
|
|
85
|
-
NOTE: Since the Figma line-height token resolves to an 'em' length (e.g. 1.05em), we CANNOT multiply it by font-size in CSS.
|
|
85
|
+
NOTE: Since the Figma line-height token resolves to an 'em' length (e.g. 1.05em), we CANNOT multiply it by font-size in CSS.
|
|
86
86
|
Instead, it naturally evaluates to the correct height because we applied the font-size above. */
|
|
87
87
|
margin-top: calc(
|
|
88
88
|
(
|
|
@@ -95,10 +95,16 @@
|
|
|
95
95
|
);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
/* Base explicit reset masking underlying framework defaults
|
|
99
|
-
|
|
98
|
+
/* Base explicit reset masking underlying framework defaults.
|
|
99
|
+
MUI's native <input type="radio"> stays invisible (SwitchBase overlays it with
|
|
100
|
+
opacity: 0) — it is not the visual circle. The circle is drawn by our own
|
|
101
|
+
icon/checkedIcon node (see Radio.tsx), a sibling of the input, so it's styled
|
|
102
|
+
here directly rather than through MUI's `classes` prop (MUI has no "radio" slot). */
|
|
103
|
+
.radio {
|
|
104
|
+
display: flex;
|
|
105
|
+
align-items: center;
|
|
106
|
+
justify-content: center;
|
|
100
107
|
cursor: pointer; /* HARDCODE: core interaction behavior */
|
|
101
|
-
appearance: none; /* HARDCODE: reset native widget */
|
|
102
108
|
box-sizing: border-box; /* HARDCODE: ensures size includes borders */
|
|
103
109
|
width: var(--recursica_ui-kit_components_radio-button_properties_size);
|
|
104
110
|
height: var(--recursica_ui-kit_components_radio-button_properties_size);
|
|
@@ -121,7 +127,7 @@
|
|
|
121
127
|
}
|
|
122
128
|
|
|
123
129
|
/* Checked State */
|
|
124
|
-
.
|
|
130
|
+
.radioChecked {
|
|
125
131
|
background-color: var(
|
|
126
132
|
--recursica_ui-kit_components_radio-button_variants_selection-states_selected_properties_colors_background-color
|
|
127
133
|
);
|
|
@@ -130,11 +136,14 @@
|
|
|
130
136
|
);
|
|
131
137
|
}
|
|
132
138
|
|
|
133
|
-
/* Disabled State (colors now differ per selection state
|
|
134
|
-
|
|
139
|
+
/* Disabled State (colors now differ per selection state; MUI applies its `disabled`
|
|
140
|
+
classKey to the same root element as `root`, so `.root.disabled` targets the
|
|
141
|
+
actual disabled instance and cascades to the decorative icon div inside it —
|
|
142
|
+
mirrors Checkbox's split). */
|
|
143
|
+
.root.disabled .radio {
|
|
135
144
|
cursor: not-allowed; /* HARDCODE: disabled state default */
|
|
136
145
|
}
|
|
137
|
-
.root .radio:
|
|
146
|
+
.root.disabled .radio:not(.radioChecked) {
|
|
138
147
|
opacity: var(
|
|
139
148
|
--recursica_ui-kit_components_radio-button_variants_selection-states_unselected_variants_states_disabled_properties_opacity
|
|
140
149
|
);
|
|
@@ -145,7 +154,7 @@
|
|
|
145
154
|
--recursica_ui-kit_components_radio-button_variants_selection-states_unselected_variants_states_disabled_properties_colors_border-color
|
|
146
155
|
);
|
|
147
156
|
}
|
|
148
|
-
.root
|
|
157
|
+
.root.disabled .radioChecked {
|
|
149
158
|
opacity: var(
|
|
150
159
|
--recursica_ui-kit_components_radio-button_variants_selection-states_selected_variants_states_disabled_properties_opacity
|
|
151
160
|
);
|
|
@@ -157,29 +166,32 @@
|
|
|
157
166
|
);
|
|
158
167
|
}
|
|
159
168
|
|
|
160
|
-
.
|
|
161
|
-
position: absolute;
|
|
162
|
-
inset: 0;
|
|
163
|
-
margin: auto;
|
|
169
|
+
.icon {
|
|
164
170
|
pointer-events: none;
|
|
165
171
|
width: var(--recursica_ui-kit_components_radio-button_properties_icon-size);
|
|
166
172
|
height: var(--recursica_ui-kit_components_radio-button_properties_icon-size);
|
|
167
173
|
}
|
|
168
174
|
/* No icon-color token exists for the unselected state (no icon is visible then) */
|
|
169
|
-
.
|
|
170
|
-
.root .radio:checked + .icon {
|
|
175
|
+
.radioChecked .icon {
|
|
171
176
|
color: var(
|
|
172
177
|
--recursica_ui-kit_components_radio-button_variants_selection-states_selected_properties_colors_icon-color
|
|
173
178
|
);
|
|
174
179
|
}
|
|
175
|
-
.root
|
|
176
|
-
.root .radio:checked:disabled + .icon {
|
|
180
|
+
.root.disabled .radioChecked .icon {
|
|
177
181
|
color: var(
|
|
178
182
|
--recursica_ui-kit_components_radio-button_variants_selection-states_selected_variants_states_disabled_properties_colors_icon-color
|
|
179
183
|
);
|
|
180
184
|
}
|
|
181
185
|
|
|
182
186
|
.root .labelWrapper {
|
|
187
|
+
/* display: flex (not the browser default block) blockifies the native inline
|
|
188
|
+
<label> child per the CSS Display spec's flex-item blockification rule —
|
|
189
|
+
matches Mantine's own labelWrapper (inline-flex), whose flex context does
|
|
190
|
+
the same to its label. Without it, the label stays inline and its line box
|
|
191
|
+
picks up extra half-leading height, visually shifting it below vertical
|
|
192
|
+
center against the circle. */
|
|
193
|
+
display: flex;
|
|
194
|
+
flex-direction: column;
|
|
183
195
|
padding: 0; /* HARDCODE: structural layout reset */
|
|
184
196
|
margin: 0; /* HARDCODE: structural layout reset */
|
|
185
197
|
flex: 1;
|
|
@@ -230,23 +242,6 @@
|
|
|
230
242
|
);
|
|
231
243
|
}
|
|
232
244
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
font-size: 12px; /* HARDCODE: mantine default xs */
|
|
237
|
-
line-height: 1.2;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
.root .error {
|
|
241
|
-
margin-top: 5px; /* HARDCODE: mantine default */
|
|
242
|
-
color: #fa5252; /* HARDCODE: mantine default error */
|
|
243
|
-
font-size: 12px; /* HARDCODE: mantine default xs */
|
|
244
|
-
line-height: 1.2;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
.root .description[data-disabled="true"],
|
|
248
|
-
.root .error[data-disabled="true"] {
|
|
249
|
-
opacity: var(
|
|
250
|
-
--recursica_ui-kit_components_radio-button-item_variants_states_disabled_properties_opacity
|
|
251
|
-
);
|
|
252
|
-
}
|
|
245
|
+
/* description/error text renders via AssistiveElement directly (see Radio.tsx) —
|
|
246
|
+
it owns its own tokenized styling. AssistiveElement has no disabled state and
|
|
247
|
+
must never dim. */
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
FormControlLayout,
|
|
18
18
|
type FormControlLayoutProps,
|
|
19
19
|
} from "../FormControlLayout/FormControlLayout";
|
|
20
|
+
import { AssistiveElement } from "../AssistiveElement/AssistiveElement";
|
|
20
21
|
|
|
21
22
|
import styles from "./Radio.module.css";
|
|
22
23
|
|
|
@@ -78,38 +79,36 @@ export const Radio = forwardRef<HTMLInputElement, RadioProps>(
|
|
|
78
79
|
delete restRecord["size"];
|
|
79
80
|
delete restRecord["color"];
|
|
80
81
|
|
|
82
|
+
// NOTE: MUI's actual prop is "classes", not "classNames" (that's Mantine's naming) — this
|
|
83
|
+
// was reading the wrong key and silently doing nothing. Fixed. Also "body"/"inner"/"radio"/
|
|
84
|
+
// "icon"/"labelWrapper"/"label" below were never real MUI `classes` slots (MUI's Radio only
|
|
85
|
+
// recognizes root/checked/disabled/colorPrimary/colorSecondary/sizeSmall, plus "input" from
|
|
86
|
+
// the underlying SwitchBase) — those slot names are Mantine's own Radio classNames API,
|
|
87
|
+
// copy-pasted over verbatim. Since none of them existed as real slots, MUI silently dropped
|
|
88
|
+
// them, so the circle (all its background/border CSS lived under `.radio`) never rendered —
|
|
89
|
+
// only the label showed. Fixed by keeping only the real slots here and drawing the circle as
|
|
90
|
+
// our own combined icon/checkedIcon node instead (see radioNode below), same pattern already
|
|
91
|
+
// used by Checkbox.
|
|
81
92
|
const mergedClassNames: Partial<Record<string, string>> = {
|
|
82
93
|
root: styles.root,
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
radio: styles.radio,
|
|
86
|
-
icon: styles.icon,
|
|
87
|
-
labelWrapper: styles.labelWrapper,
|
|
88
|
-
label: styles.label,
|
|
94
|
+
checked: styles.checked,
|
|
95
|
+
disabled: styles.disabled,
|
|
89
96
|
};
|
|
90
97
|
|
|
91
|
-
const
|
|
98
|
+
const classesProp = restRecord.classes;
|
|
92
99
|
if (
|
|
93
|
-
|
|
94
|
-
typeof
|
|
95
|
-
!Array.isArray(
|
|
100
|
+
classesProp &&
|
|
101
|
+
typeof classesProp === "object" &&
|
|
102
|
+
!Array.isArray(classesProp)
|
|
96
103
|
) {
|
|
97
|
-
const o =
|
|
104
|
+
const o = classesProp as Partial<Record<string, string>>;
|
|
98
105
|
mergedClassNames.root = o.root ? `${styles.root} ${o.root}` : styles.root;
|
|
99
|
-
mergedClassNames.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
: styles.radio;
|
|
106
|
-
mergedClassNames.icon = o.icon ? `${styles.icon} ${o.icon}` : styles.icon;
|
|
107
|
-
mergedClassNames.labelWrapper = o.labelWrapper
|
|
108
|
-
? `${styles.labelWrapper} ${o.labelWrapper}`
|
|
109
|
-
: styles.labelWrapper;
|
|
110
|
-
mergedClassNames.label = o.label
|
|
111
|
-
? `${styles.label} ${o.label}`
|
|
112
|
-
: styles.label;
|
|
106
|
+
mergedClassNames.checked = o.checked
|
|
107
|
+
? `${styles.checked} ${o.checked}`
|
|
108
|
+
: styles.checked;
|
|
109
|
+
mergedClassNames.disabled = o.disabled
|
|
110
|
+
? `${styles.disabled} ${o.disabled}`
|
|
111
|
+
: styles.disabled;
|
|
113
112
|
}
|
|
114
113
|
|
|
115
114
|
const classNameProp = restRecord.className as string | undefined;
|
|
@@ -144,17 +143,28 @@ export const Radio = forwardRef<HTMLInputElement, RadioProps>(
|
|
|
144
143
|
return <>{roNode}</>;
|
|
145
144
|
}
|
|
146
145
|
|
|
147
|
-
//
|
|
146
|
+
// MUI's icon/checkedIcon fully replace the visual (they're the only child SwitchBase
|
|
147
|
+
// renders alongside the invisible native input) — so the ring + dot both have to live in
|
|
148
|
+
// one combined node per state, same pattern as Checkbox's icon/checkedIcon divs.
|
|
149
|
+
// sanitizedProps spreads FIRST: these render-critical props (icon/checkedIcon/className/
|
|
150
|
+
// classes/disabled/disableRipple/sx) must win over anything of the same name coming through
|
|
151
|
+
// from the caller, or they'd silently clobber the exact rendering this component depends on.
|
|
148
152
|
const radioNode = (
|
|
149
153
|
<MuiRadio
|
|
150
154
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
151
155
|
ref={ref as any}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
156
|
+
{...(sanitizedProps as unknown as MuiRadioProps)}
|
|
157
|
+
icon={<div className={styles.radio} />}
|
|
158
|
+
checkedIcon={
|
|
159
|
+
<div className={`${styles.radio} ${styles.radioChecked}`}>
|
|
160
|
+
<RadioIcon className={styles.icon} />
|
|
161
|
+
</div>
|
|
162
|
+
}
|
|
163
|
+
className={!label ? `${finalClass} ${styles.inner}` : styles.inner}
|
|
155
164
|
classes={mergedClassNames}
|
|
156
165
|
disabled={readOnly || disabled}
|
|
157
|
-
|
|
166
|
+
disableRipple
|
|
167
|
+
sx={label ? { padding: 0 } : undefined}
|
|
158
168
|
/>
|
|
159
169
|
);
|
|
160
170
|
|
|
@@ -170,27 +180,31 @@ export const Radio = forwardRef<HTMLInputElement, RadioProps>(
|
|
|
170
180
|
>
|
|
171
181
|
{label as React.ReactNode}
|
|
172
182
|
</label>
|
|
173
|
-
{description
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
{description}
|
|
179
|
-
</div>
|
|
180
|
-
)}
|
|
181
|
-
{error && (
|
|
182
|
-
<div
|
|
183
|
-
className={styles.error}
|
|
184
|
-
data-disabled={readOnly || disabled ? true : undefined}
|
|
183
|
+
{/* description/error are mutually exclusive — error takes precedence */}
|
|
184
|
+
{error ? (
|
|
185
|
+
<AssistiveElement
|
|
186
|
+
assistiveVariant="error"
|
|
187
|
+
assistiveWithIcon={false}
|
|
185
188
|
>
|
|
186
189
|
{error}
|
|
187
|
-
</
|
|
190
|
+
</AssistiveElement>
|
|
191
|
+
) : (
|
|
192
|
+
description && (
|
|
193
|
+
<AssistiveElement
|
|
194
|
+
assistiveVariant="help"
|
|
195
|
+
assistiveWithIcon={false}
|
|
196
|
+
>
|
|
197
|
+
{description}
|
|
198
|
+
</AssistiveElement>
|
|
199
|
+
)
|
|
188
200
|
)}
|
|
189
201
|
</div>
|
|
190
202
|
</div>
|
|
191
203
|
</div>
|
|
192
204
|
) : (
|
|
193
|
-
|
|
205
|
+
<div className={finalClass} style={style as React.CSSProperties}>
|
|
206
|
+
{radioNode}
|
|
207
|
+
</div>
|
|
194
208
|
);
|
|
195
209
|
|
|
196
210
|
if (formLayout) {
|
|
@@ -70,6 +70,7 @@ export const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>(
|
|
|
70
70
|
emptyValueComponent,
|
|
71
71
|
value,
|
|
72
72
|
defaultValue,
|
|
73
|
+
onChange,
|
|
73
74
|
...rest
|
|
74
75
|
} = props;
|
|
75
76
|
const sanitizedProps = filterStylingProps(rest, overStyled);
|
|
@@ -111,6 +112,10 @@ export const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>(
|
|
|
111
112
|
{...(sanitizedProps as unknown as MuiRadioGroupProps)}
|
|
112
113
|
value={value}
|
|
113
114
|
defaultValue={defaultValue}
|
|
115
|
+
// MUI natively calls onChange(event, value); the Recursica contract is
|
|
116
|
+
// single-argument (value only), matching Mantine's native RadioGroup
|
|
117
|
+
// onChange (the cross-adapter source of truth) — normalize here.
|
|
118
|
+
onChange={onChange ? (_event, val) => onChange(val) : undefined}
|
|
114
119
|
className={styles.groupRoot}
|
|
115
120
|
data-layout={formLayout}
|
|
116
121
|
>
|
|
@@ -98,13 +98,6 @@
|
|
|
98
98
|
display: flex;
|
|
99
99
|
align-items: center;
|
|
100
100
|
justify-content: center;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/* Mantine natively wraps label contents in an innerLabel span */
|
|
104
|
-
.root .label :global(.mantine-SegmentedControl-innerLabel) {
|
|
105
|
-
display: flex;
|
|
106
|
-
align-items: center;
|
|
107
|
-
justify-content: center;
|
|
108
101
|
gap: var(
|
|
109
102
|
--recursica_ui-kit_components_segmented-control-item_properties_item_icon-text-gap
|
|
110
103
|
);
|
|
@@ -38,7 +38,6 @@ function useSegmentedControlClassNames(restRecord: Record<string, unknown>): {
|
|
|
38
38
|
root: styles.root,
|
|
39
39
|
control: styles.control,
|
|
40
40
|
label: styles.label,
|
|
41
|
-
indicator: styles.indicator,
|
|
42
41
|
};
|
|
43
42
|
|
|
44
43
|
const classNamesProp = restRecord.classNames;
|
|
@@ -55,9 +54,6 @@ function useSegmentedControlClassNames(restRecord: Record<string, unknown>): {
|
|
|
55
54
|
mergedClassNames.label = o.label
|
|
56
55
|
? `${styles.label} ${o.label}`
|
|
57
56
|
: styles.label;
|
|
58
|
-
mergedClassNames.indicator = o.indicator
|
|
59
|
-
? `${styles.indicator} ${o.indicator}`
|
|
60
|
-
: styles.indicator;
|
|
61
57
|
}
|
|
62
58
|
|
|
63
59
|
const classNameProp = restRecord.className as string | undefined;
|
|
@@ -101,6 +97,10 @@ const _SegmentedControl = forwardRef<HTMLDivElement, SegmentedControlProps>(
|
|
|
101
97
|
return (
|
|
102
98
|
<MuiSegmentedControl
|
|
103
99
|
ref={ref}
|
|
100
|
+
{...(sanitizedProps as Omit<
|
|
101
|
+
MuiSegmentedControlProps,
|
|
102
|
+
"variant" | "size" | "value" | "onChange"
|
|
103
|
+
>)}
|
|
104
104
|
className={stylingParams.className}
|
|
105
105
|
classes={stylingParams.classNames}
|
|
106
106
|
orientation={orientation}
|
|
@@ -113,10 +113,6 @@ const _SegmentedControl = forwardRef<HTMLDivElement, SegmentedControlProps>(
|
|
|
113
113
|
typeof MuiSegmentedControl
|
|
114
114
|
>["onChange"]
|
|
115
115
|
}
|
|
116
|
-
{...(sanitizedProps as Omit<
|
|
117
|
-
MuiSegmentedControlProps,
|
|
118
|
-
"variant" | "size" | "value" | "onChange"
|
|
119
|
-
>)}
|
|
120
116
|
>
|
|
121
117
|
{data.map((item) => {
|
|
122
118
|
const itemValue = typeof item === "string" ? item : item.value;
|
|
@@ -235,6 +235,7 @@ export const Slider = forwardRef<HTMLDivElement, SliderProps>(
|
|
|
235
235
|
|
|
236
236
|
<div className={styles.sliderTrackWrapper}>
|
|
237
237
|
<MuiSlider
|
|
238
|
+
{...(sanitizedProps as unknown as MuiSliderProps)}
|
|
238
239
|
classes={mergedClassNames}
|
|
239
240
|
disabled={disabled}
|
|
240
241
|
value={resolvedValue}
|
|
@@ -257,7 +258,6 @@ export const Slider = forwardRef<HTMLDivElement, SliderProps>(
|
|
|
257
258
|
) => React.ReactNode)
|
|
258
259
|
: undefined
|
|
259
260
|
}
|
|
260
|
-
{...(sanitizedProps as unknown as MuiSliderProps)}
|
|
261
261
|
/>
|
|
262
262
|
</div>
|
|
263
263
|
|
|
@@ -29,10 +29,10 @@ export const Stack = forwardRef<HTMLDivElement, StackProps>(function Stack(
|
|
|
29
29
|
return (
|
|
30
30
|
<MUIStack
|
|
31
31
|
ref={ref}
|
|
32
|
+
{...safeProps}
|
|
32
33
|
spacing={resolvedGap}
|
|
33
34
|
alignItems={align}
|
|
34
35
|
justifyContent={justify}
|
|
35
|
-
{...safeProps}
|
|
36
36
|
>
|
|
37
37
|
{children}
|
|
38
38
|
</MUIStack>
|
|
@@ -48,6 +48,7 @@ export const Stepper = forwardRef<HTMLDivElement, StepperProps>(
|
|
|
48
48
|
<MuiStepper
|
|
49
49
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
50
|
ref={ref as any}
|
|
51
|
+
{...(sanitizedProps as MuiStepperProps)}
|
|
51
52
|
orientation={orientation}
|
|
52
53
|
connector={
|
|
53
54
|
<MuiStepConnector
|
|
@@ -62,7 +63,6 @@ export const Stepper = forwardRef<HTMLDivElement, StepperProps>(
|
|
|
62
63
|
classes={{
|
|
63
64
|
root: styles.steps,
|
|
64
65
|
}}
|
|
65
|
-
{...(sanitizedProps as MuiStepperProps)}
|
|
66
66
|
/>
|
|
67
67
|
</div>
|
|
68
68
|
);
|
|
@@ -79,9 +79,9 @@ export const Step = forwardRef<HTMLDivElement, StepProps>(
|
|
|
79
79
|
return (
|
|
80
80
|
<MuiStep
|
|
81
81
|
ref={ref}
|
|
82
|
+
{...(filterStylingProps(rest, overStyled) as MuiStepProps)}
|
|
82
83
|
classes={{ root: styles.step }}
|
|
83
84
|
className={className || ""}
|
|
84
|
-
{...(filterStylingProps(rest, overStyled) as MuiStepProps)}
|
|
85
85
|
/>
|
|
86
86
|
);
|
|
87
87
|
},
|
|
@@ -116,6 +116,7 @@ export const StepLabel = forwardRef<HTMLDivElement, StepLabelProps>(
|
|
|
116
116
|
return (
|
|
117
117
|
<MuiStepLabel
|
|
118
118
|
ref={ref}
|
|
119
|
+
{...(filterStylingProps(rest, overStyled) as MuiStepLabelProps)}
|
|
119
120
|
className={className || ""}
|
|
120
121
|
classes={{
|
|
121
122
|
label: styles.stepLabel,
|
|
@@ -128,7 +129,6 @@ export const StepLabel = forwardRef<HTMLDivElement, StepLabelProps>(
|
|
|
128
129
|
rest.optional
|
|
129
130
|
)
|
|
130
131
|
}
|
|
131
|
-
{...(filterStylingProps(rest, overStyled) as MuiStepLabelProps)}
|
|
132
132
|
/>
|
|
133
133
|
);
|
|
134
134
|
},
|