@recursica/mui-adapter 0.21.0 → 0.22.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 +58 -0
- package/dist/index.d.ts +2929 -2
- package/dist/mui-adapter.cjs +68 -68
- package/dist/mui-adapter.cjs.map +1 -1
- package/dist/mui-adapter.css +1 -1
- package/dist/mui-adapter.js +5660 -5582
- 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/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.module.css +16 -7
- package/src/components/Chip/Chip.tsx +1 -1
- 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/dist/src/OverStyling.d.ts +0 -1
- package/dist/src/Version.d.ts +0 -1
- package/dist/src/components/Accordion/Accordion.d.ts +0 -77
- package/dist/src/components/Accordion/index.d.ts +0 -1
- package/dist/src/components/AssistiveElement/AssistiveElement.d.ts +0 -7
- package/dist/src/components/AssistiveElement/index.d.ts +0 -1
- package/dist/src/components/Autocomplete/Autocomplete.d.ts +0 -24
- package/dist/src/components/Autocomplete/index.d.ts +0 -1
- package/dist/src/components/Avatar/Avatar.d.ts +0 -25
- package/dist/src/components/Avatar/index.d.ts +0 -1
- package/dist/src/components/Badge/Badge.d.ts +0 -25
- package/dist/src/components/Badge/index.d.ts +0 -1
- package/dist/src/components/Box/Box.d.ts +0 -8
- package/dist/src/components/Box/index.d.ts +0 -1
- package/dist/src/components/Breadcrumb/Breadcrumb.d.ts +0 -20
- package/dist/src/components/Breadcrumb/index.d.ts +0 -1
- package/dist/src/components/Button/Button.d.ts +0 -37
- package/dist/src/components/Button/index.d.ts +0 -1
- package/dist/src/components/Card/Card.d.ts +0 -81
- package/dist/src/components/Card/index.d.ts +0 -1
- package/dist/src/components/Checkbox/Checkbox.d.ts +0 -14
- package/dist/src/components/Checkbox/CheckboxGroup.d.ts +0 -29
- package/dist/src/components/Checkbox/index.d.ts +0 -5
- package/dist/src/components/Chip/Chip.d.ts +0 -21
- package/dist/src/components/Chip/index.d.ts +0 -1
- package/dist/src/components/Container/Container.d.ts +0 -5
- package/dist/src/components/Container/index.d.ts +0 -1
- package/dist/src/components/DatePicker/DatePicker.d.ts +0 -20
- package/dist/src/components/DatePicker/index.d.ts +0 -1
- package/dist/src/components/Dropdown/BareDropdown.d.ts +0 -41
- package/dist/src/components/Dropdown/Dropdown.d.ts +0 -24
- package/dist/src/components/Dropdown/index.d.ts +0 -1
- package/dist/src/components/FileInput/FileInput.d.ts +0 -3
- package/dist/src/components/FileInput/index.d.ts +0 -1
- package/dist/src/components/FileUpload/FileUpload.d.ts +0 -3
- package/dist/src/components/FileUpload/index.d.ts +0 -1
- package/dist/src/components/Flex/Flex.d.ts +0 -5
- package/dist/src/components/Flex/index.d.ts +0 -1
- package/dist/src/components/FormControlLayout/FormControlLayout.d.ts +0 -6
- package/dist/src/components/FormControlLayout/index.d.ts +0 -1
- package/dist/src/components/FormControlWrapper/FormControlWrapper.d.ts +0 -11
- package/dist/src/components/FormControlWrapper/index.d.ts +0 -1
- package/dist/src/components/Grid/Grid.d.ts +0 -12
- package/dist/src/components/Grid/index.d.ts +0 -1
- package/dist/src/components/Group/Group.d.ts +0 -5
- package/dist/src/components/Group/index.d.ts +0 -1
- package/dist/src/components/HoverCard/HoverCard.d.ts +0 -29
- package/dist/src/components/HoverCard/index.d.ts +0 -1
- package/dist/src/components/Label/Label.d.ts +0 -7
- package/dist/src/components/Label/index.d.ts +0 -1
- package/dist/src/components/Link/Link.d.ts +0 -20
- package/dist/src/components/Link/index.d.ts +0 -1
- package/dist/src/components/Loader/Loader.d.ts +0 -5
- package/dist/src/components/Loader/index.d.ts +0 -1
- package/dist/src/components/Menu/Menu.d.ts +0 -54
- package/dist/src/components/Menu/index.d.ts +0 -1
- package/dist/src/components/Modal/Modal.d.ts +0 -78
- package/dist/src/components/Modal/index.d.ts +0 -1
- package/dist/src/components/NumberInput/NumberInput.d.ts +0 -24
- package/dist/src/components/NumberInput/index.d.ts +0 -1
- package/dist/src/components/Pagination/Pagination.d.ts +0 -23
- package/dist/src/components/Pagination/Pagination.icons.d.ts +0 -8
- package/dist/src/components/Pagination/index.d.ts +0 -1
- package/dist/src/components/Panel/Panel.d.ts +0 -52
- package/dist/src/components/Panel/index.d.ts +0 -1
- package/dist/src/components/Radio/Radio.d.ts +0 -13
- package/dist/src/components/Radio/RadioGroup.d.ts +0 -9
- package/dist/src/components/Radio/index.d.ts +0 -2
- package/dist/src/components/ReadOnlyField/ReadOnlyBooleanField.d.ts +0 -5
- package/dist/src/components/ReadOnlyField/ReadOnlyField.d.ts +0 -5
- package/dist/src/components/ReadOnlyField/ReadOnlySwitchField.d.ts +0 -5
- package/dist/src/components/ReadOnlyField/ReadOnlyTextField.d.ts +0 -8
- package/dist/src/components/ReadOnlyField/WithReadOnlyWrapper.d.ts +0 -28
- package/dist/src/components/ReadOnlyField/index.d.ts +0 -2
- package/dist/src/components/SegmentedControl/SegmentedControl.d.ts +0 -32
- package/dist/src/components/SegmentedControl/index.d.ts +0 -1
- package/dist/src/components/Slider/Slider.d.ts +0 -30
- package/dist/src/components/Slider/index.d.ts +0 -1
- package/dist/src/components/Stack/Stack.d.ts +0 -5
- package/dist/src/components/Stack/index.d.ts +0 -1
- package/dist/src/components/Stepper/Stepper.d.ts +0 -97
- package/dist/src/components/Stepper/index.d.ts +0 -1
- package/dist/src/components/Switch/Switch.d.ts +0 -10
- package/dist/src/components/Switch/SwitchGroup.d.ts +0 -24
- package/dist/src/components/Switch/index.d.ts +0 -2
- package/dist/src/components/Table/Table.d.ts +0 -154
- package/dist/src/components/Table/index.d.ts +0 -1
- package/dist/src/components/Tabs/Tabs.d.ts +0 -57
- package/dist/src/components/Tabs/index.d.ts +0 -1
- package/dist/src/components/Text/Text.d.ts +0 -20
- package/dist/src/components/Text/index.d.ts +0 -1
- package/dist/src/components/TextArea/TextArea.d.ts +0 -24
- package/dist/src/components/TextArea/index.d.ts +0 -1
- package/dist/src/components/TextField/TextField.d.ts +0 -24
- package/dist/src/components/TextField/index.d.ts +0 -1
- package/dist/src/components/TimePicker/TimePicker.d.ts +0 -17
- package/dist/src/components/TimePicker/index.d.ts +0 -1
- package/dist/src/components/Timeline/Timeline.d.ts +0 -16
- package/dist/src/components/Timeline/TimelineItem.d.ts +0 -29
- package/dist/src/components/Timeline/index.d.ts +0 -2
- package/dist/src/components/Title/Title.d.ts +0 -21
- package/dist/src/components/Title/index.d.ts +0 -1
- package/dist/src/components/Toast/Toast.d.ts +0 -32
- package/dist/src/components/Toast/index.d.ts +0 -1
- package/dist/src/components/Tooltip/Tooltip.d.ts +0 -28
- package/dist/src/components/Tooltip/index.d.ts +0 -1
- package/dist/src/components/TransferList/TransferList.d.ts +0 -4
- package/dist/src/components/TransferList/index.d.ts +0 -1
- package/dist/src/components/Tree/Tree.d.ts +0 -18
- package/dist/src/components/Tree/index.d.ts +0 -1
- package/dist/src/components/Typography/Typography.d.ts +0 -38
- package/dist/src/components/Typography/index.d.ts +0 -1
- package/dist/src/components/index.d.ts +0 -50
- package/dist/src/index.d.ts +0 -91
- package/dist/src/types/index.d.ts +0 -6
- package/dist/src/utils/ColorSchemeWrapper.d.ts +0 -3
- package/dist/src/utils/RequireAccessibleLabel.d.ts +0 -1
- package/dist/src/utils/copyToClipboard.d.ts +0 -8
- package/dist/src/utils/filterStylingProps.d.ts +0 -29
- package/dist/src/utils/index.d.ts +0 -1
- package/dist/vite.config.d.ts +0 -2
- package/dist/vitest.workspace.d.ts +0 -2
- package/src/types/mantine.d.ts +0 -7
|
@@ -1,13 +1,46 @@
|
|
|
1
|
-
/* HARDCODED VALUES
|
|
1
|
+
/* HARDCODED VALUES
|
|
2
2
|
*
|
|
3
3
|
* 1. track border: none; (we do not use border for the switch track)
|
|
4
4
|
* 2. thumb border: none; (we do not use border for the thumb)
|
|
5
|
-
* 3.
|
|
5
|
+
* 3. .switchBase background-color: transparent; (cancels MUI's built-in primary-color
|
|
6
|
+
* hover/focus circle so only our tokens and our own explicit focus ring show — see
|
|
7
|
+
* SWITCH_IMPLEMENTATION_NOTES.md #5)
|
|
8
|
+
* 4. .track opacity: 1; (cancels MUI's own always-on track opacity — 0.38 unchecked, 0.5
|
|
9
|
+
* checked, both MUI defaults with no equivalent in our tokens; our disabled-state opacity
|
|
10
|
+
* rules below are the only intended dimming)
|
|
6
11
|
*/
|
|
7
12
|
|
|
8
13
|
.root {
|
|
9
14
|
/* Provide a stable cursor when not disabled */
|
|
10
15
|
cursor: pointer;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.root:has(input:disabled) {
|
|
19
|
+
cursor: not-allowed;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* Sizes MUI's actual switch root span (mapped via classes.root — MUI requires that exact key,
|
|
23
|
+
but the class name it points to is ours). Kept separate from .root above: when a label is
|
|
24
|
+
present, .root is applied to *this component's own outer wrapper div* (switch + label side
|
|
25
|
+
by side), not to the switch itself — giving both the same box-model rules would squeeze that
|
|
26
|
+
wrapper (and the label inside it) down to the track's own width. See
|
|
27
|
+
SWITCH_IMPLEMENTATION_NOTES.md #5. */
|
|
28
|
+
.switchRoot {
|
|
29
|
+
/* Cancel MUI's own hardcoded root box (width: 58px, height: 38px, padding: 12px,
|
|
30
|
+
overflow: hidden for the medium size) — without this the track/thumb tokens below get
|
|
31
|
+
clipped/shrunk to MUI's default geometry instead of driving the real size. */
|
|
32
|
+
width: var(--recursica_ui-kit_components_switch_properties_track-width);
|
|
33
|
+
height: calc(
|
|
34
|
+
var(--recursica_ui-kit_components_switch_properties_thumb-height) +
|
|
35
|
+
(
|
|
36
|
+
var(
|
|
37
|
+
--recursica_ui-kit_components_switch_properties_track-inner-padding
|
|
38
|
+
) *
|
|
39
|
+
2
|
|
40
|
+
)
|
|
41
|
+
);
|
|
42
|
+
padding: 0;
|
|
43
|
+
overflow: visible;
|
|
11
44
|
|
|
12
45
|
--switch-track-padding: var(
|
|
13
46
|
--recursica_ui-kit_components_switch_properties_track-inner-padding
|
|
@@ -23,17 +56,19 @@
|
|
|
23
56
|
);
|
|
24
57
|
}
|
|
25
58
|
|
|
26
|
-
.root:has(input:disabled) {
|
|
27
|
-
cursor: not-allowed;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
59
|
/* Track/thumb dim per selection state; label dims via its own (single, state-agnostic) token */
|
|
31
|
-
.
|
|
60
|
+
/* In mantine-adapter, .thumb nests inside .track, so dimming .track's opacity visually dims
|
|
61
|
+
the thumb too "for free". MUI's DOM has .track and .thumb as siblings (documented
|
|
62
|
+
structural difference, see SWITCH_IMPLEMENTATION_NOTES.md), so the same selector only dimmed
|
|
63
|
+
the track — the thumb stayed at full color/opacity. Each selector targets both explicitly. */
|
|
64
|
+
.root:has(input:checked:disabled) .track,
|
|
65
|
+
.root:has(input:checked:disabled) .thumb {
|
|
32
66
|
opacity: var(
|
|
33
67
|
--recursica_ui-kit_components_switch_variants_selection-states_selected_variants_states_disabled_properties_opacity
|
|
34
68
|
);
|
|
35
69
|
}
|
|
36
|
-
.root:has(input:disabled):not(:has(input:checked)) .track
|
|
70
|
+
.root:has(input:disabled):not(:has(input:checked)) .track,
|
|
71
|
+
.root:has(input:disabled):not(:has(input:checked)) .thumb {
|
|
37
72
|
opacity: var(
|
|
38
73
|
--recursica_ui-kit_components_switch_variants_selection-states_unselected_variants_states_disabled_properties_opacity
|
|
39
74
|
);
|
|
@@ -51,7 +86,7 @@
|
|
|
51
86
|
--recursica_ui-kit_components_switch_variants_selection-states_unselected_properties_colors_track-color
|
|
52
87
|
);
|
|
53
88
|
}
|
|
54
|
-
.root
|
|
89
|
+
.root:has(input:checked) .track:hover {
|
|
55
90
|
background-color: var(
|
|
56
91
|
--recursica_ui-kit_components_switch_variants_selection-states_selected_properties_colors_track-color
|
|
57
92
|
);
|
|
@@ -69,28 +104,71 @@
|
|
|
69
104
|
box-shadow: var(
|
|
70
105
|
--recursica_ui-kit_components_switch_properties_track-elevation
|
|
71
106
|
);
|
|
72
|
-
padding: var(--switch-track-padding);
|
|
73
107
|
border: none; /* HARDCODE */
|
|
74
|
-
|
|
75
|
-
/* Mantine uses variables internally for these, but we explicitly override the CSS to ensure our dimensions */
|
|
76
|
-
--switch-width: var(--switch-track-width);
|
|
77
|
-
--switch-height: calc(
|
|
78
|
-
var(--switch-thumb-height) + (var(--switch-track-padding) * 2)
|
|
79
|
-
);
|
|
80
|
-
--switch-thumb-size: var(--switch-thumb-width);
|
|
81
|
-
|
|
82
|
-
display: flex;
|
|
83
|
-
align-items: center;
|
|
108
|
+
opacity: 1; /* HARDCODE */
|
|
84
109
|
box-sizing: border-box;
|
|
85
110
|
transition: background-color 150ms ease;
|
|
86
111
|
cursor: inherit;
|
|
87
112
|
}
|
|
88
113
|
|
|
89
|
-
.root
|
|
114
|
+
.root:has(input:checked) .track {
|
|
90
115
|
background-color: var(
|
|
91
116
|
--recursica_ui-kit_components_switch_variants_selection-states_selected_properties_colors_track-color
|
|
92
117
|
);
|
|
93
118
|
border-color: transparent;
|
|
119
|
+
/* HARDCODE #4: MUI's own checked-state selector (`.Mui-checked + .track { opacity: 0.5 }`)
|
|
120
|
+
is more specific than the base .track rule above, so it still wins there — needs its own
|
|
121
|
+
override here too. */
|
|
122
|
+
opacity: 1;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* Positions/sizes the thumb's clickable hit area (MUI's internal SwitchBase). MUI's own
|
|
126
|
+
defaults here (padding: 9px; top/left: 0) are calibrated for its own hardcoded 20px thumb
|
|
127
|
+
and 58x38 root — meaningless against our tokens, so every dimension is replaced. The thumb
|
|
128
|
+
itself (the visible circle) renders as this element's only child, sized by .thumb below;
|
|
129
|
+
with padding: 0 and matching width/height, .switchBase and .thumb become the same box, so
|
|
130
|
+
the thumb sits flush inside the track's own inner padding. See
|
|
131
|
+
SWITCH_IMPLEMENTATION_NOTES.md #5. */
|
|
132
|
+
.switchBase {
|
|
133
|
+
top: var(--switch-track-padding);
|
|
134
|
+
left: var(--switch-track-padding);
|
|
135
|
+
width: var(--switch-thumb-width);
|
|
136
|
+
height: var(--switch-thumb-height);
|
|
137
|
+
padding: 0;
|
|
138
|
+
box-sizing: border-box;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.switchBase:hover,
|
|
142
|
+
.switchBase:global(.Mui-focusVisible) {
|
|
143
|
+
background-color: transparent; /* HARDCODE */
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* Focus ring reads on the whole visible track (the pill), not the thumb's small 20x20 hit-area
|
|
147
|
+
circle .switchBase actually is — .switchBase and .track are siblings under .switchRoot (in
|
|
148
|
+
that DOM order), so the ring is drawn on .track via the general sibling combinator when the
|
|
149
|
+
hit area is focus-visible. */
|
|
150
|
+
.switchBase:global(.Mui-focusVisible) ~ .track {
|
|
151
|
+
outline: var(--recursica_brand_states_focus_border-size) solid
|
|
152
|
+
var(--recursica_brand_states_focus_color);
|
|
153
|
+
outline-offset: var(--recursica_brand_states_focus_margin);
|
|
154
|
+
box-shadow: 0 0 var(--recursica_brand_states_focus_blur)
|
|
155
|
+
var(--recursica_brand_states_focus_color);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/* Moves the thumb from resting-left to resting-right. MUI hardcodes this same transform to
|
|
159
|
+
translateX(20px) for its own default thumb; ours is calculated from our tokens instead —
|
|
160
|
+
the exact distance between the thumb's checked and unchecked resting positions inside the
|
|
161
|
+
track's inner padding. .Mui-checked is MUI's own global state class (not a CSS Modules
|
|
162
|
+
local class), so it must be wrapped in :global() — same as Accordion's .Mui-expanded and
|
|
163
|
+
Button's .Mui-disabled — or the build hashes/scopes it into a name that never matches the
|
|
164
|
+
real DOM. */
|
|
165
|
+
.switchBase:global(.Mui-checked) {
|
|
166
|
+
transform: translateX(
|
|
167
|
+
calc(
|
|
168
|
+
var(--switch-track-width) - var(--switch-thumb-width) -
|
|
169
|
+
(var(--switch-track-padding) * 2)
|
|
170
|
+
)
|
|
171
|
+
);
|
|
94
172
|
}
|
|
95
173
|
|
|
96
174
|
.thumb {
|
|
@@ -110,12 +188,61 @@
|
|
|
110
188
|
transition: background-color 150ms ease;
|
|
111
189
|
}
|
|
112
190
|
|
|
113
|
-
.root
|
|
191
|
+
.root:has(input:checked) .thumb {
|
|
114
192
|
background-color: var(
|
|
115
193
|
--recursica_ui-kit_components_switch_variants_selection-states_selected_properties_colors_thumb-color
|
|
116
194
|
);
|
|
117
195
|
}
|
|
118
196
|
|
|
197
|
+
.thumbIconWrapper {
|
|
198
|
+
display: flex;
|
|
199
|
+
align-items: center;
|
|
200
|
+
justify-content: center;
|
|
201
|
+
width: 100%;
|
|
202
|
+
height: 100%;
|
|
203
|
+
position: relative;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.checkIcon,
|
|
207
|
+
.closeIcon {
|
|
208
|
+
width: var(--recursica_ui-kit_components_switch_properties_thumb-icon-size);
|
|
209
|
+
height: var(--recursica_ui-kit_components_switch_properties_thumb-icon-size);
|
|
210
|
+
position: absolute;
|
|
211
|
+
transition:
|
|
212
|
+
opacity 150ms ease,
|
|
213
|
+
transform 150ms ease;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/* Unchecked state */
|
|
217
|
+
.checkIcon {
|
|
218
|
+
opacity: 0;
|
|
219
|
+
transform: scale(0.5);
|
|
220
|
+
color: var(
|
|
221
|
+
--recursica_ui-kit_components_switch_variants_selection-states_selected_properties_colors_icon-color
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.closeIcon {
|
|
226
|
+
opacity: 1;
|
|
227
|
+
transform: scale(1);
|
|
228
|
+
color: var(
|
|
229
|
+
--recursica_ui-kit_components_switch_variants_selection-states_unselected_properties_colors_icon-color
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/* Checked state — same :has() scoping already used for track/thumb color above; .thumb isn't
|
|
234
|
+
a sibling of input here (MUI nests both under switchBase), but :has() only needs input:checked
|
|
235
|
+
to be a descendant of .root, so this still scopes correctly. */
|
|
236
|
+
.root:has(input:checked) .thumb .checkIcon {
|
|
237
|
+
opacity: 1;
|
|
238
|
+
transform: scale(1);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.root:has(input:checked) .thumb .closeIcon {
|
|
242
|
+
opacity: 0;
|
|
243
|
+
transform: scale(0.5);
|
|
244
|
+
}
|
|
245
|
+
|
|
119
246
|
.labelWrapper {
|
|
120
247
|
display: flex;
|
|
121
248
|
flex-direction: column;
|
|
@@ -166,73 +293,9 @@
|
|
|
166
293
|
);
|
|
167
294
|
}
|
|
168
295
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
font-size: 12px; /* HARDCODE: mantine default xs */
|
|
173
|
-
line-height: 1.2;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
.error {
|
|
177
|
-
margin-top: 5px; /* HARDCODE: mantine default */
|
|
178
|
-
color: #fa5252; /* HARDCODE: mantine default error */
|
|
179
|
-
font-size: 12px; /* HARDCODE: mantine default xs */
|
|
180
|
-
line-height: 1.2;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
.description[data-disabled="true"],
|
|
184
|
-
.error[data-disabled="true"] {
|
|
185
|
-
opacity: var(
|
|
186
|
-
--recursica_ui-kit_components_switch-item_variants_states_disabled_properties_opacity
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
.thumbIconWrapper {
|
|
191
|
-
display: flex;
|
|
192
|
-
align-items: center;
|
|
193
|
-
justify-content: center;
|
|
194
|
-
width: 100%;
|
|
195
|
-
height: 100%;
|
|
196
|
-
position: relative;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
.checkIcon,
|
|
200
|
-
.closeIcon {
|
|
201
|
-
width: var(--recursica_ui-kit_components_switch_properties_thumb-icon-size);
|
|
202
|
-
height: var(--recursica_ui-kit_components_switch_properties_thumb-icon-size);
|
|
203
|
-
position: absolute;
|
|
204
|
-
transition:
|
|
205
|
-
opacity 150ms ease,
|
|
206
|
-
transform 150ms ease;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/* Unchecked state */
|
|
210
|
-
.checkIcon {
|
|
211
|
-
opacity: 0;
|
|
212
|
-
transform: scale(0.5);
|
|
213
|
-
color: var(
|
|
214
|
-
--recursica_ui-kit_components_switch_variants_selection-states_selected_properties_colors_icon-color
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
.closeIcon {
|
|
219
|
-
opacity: 1;
|
|
220
|
-
transform: scale(1);
|
|
221
|
-
color: var(
|
|
222
|
-
--recursica_ui-kit_components_switch_variants_selection-states_unselected_properties_colors_icon-color
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/* Checked state */
|
|
227
|
-
.root input:checked + .track .thumb .checkIcon {
|
|
228
|
-
opacity: 1;
|
|
229
|
-
transform: scale(1);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
.root input:checked + .track .thumb .closeIcon {
|
|
233
|
-
opacity: 0;
|
|
234
|
-
transform: scale(0.5);
|
|
235
|
-
}
|
|
296
|
+
/* description/error text renders via AssistiveElement directly (see Switch.tsx) —
|
|
297
|
+
it owns its own tokenized styling. AssistiveElement has no disabled state and
|
|
298
|
+
must never dim. */
|
|
236
299
|
|
|
237
300
|
/* --- GROUP STYLES --- */
|
|
238
301
|
.groupRoot {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { forwardRef } from "react";
|
|
1
|
+
import { forwardRef, useContext } from "react";
|
|
2
2
|
import {
|
|
3
3
|
Switch as MuiSwitch,
|
|
4
4
|
type SwitchProps as MuiSwitchProps,
|
|
@@ -17,9 +17,49 @@ import {
|
|
|
17
17
|
FormControlLayout,
|
|
18
18
|
type FormControlLayoutProps,
|
|
19
19
|
} from "../FormControlLayout/FormControlLayout";
|
|
20
|
+
import { AssistiveElement } from "../AssistiveElement/AssistiveElement";
|
|
20
21
|
import styles from "./Switch.module.css";
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
import { SwitchGroup, SwitchGroupContext } from "./SwitchGroup";
|
|
24
|
+
|
|
25
|
+
// Same glyphs as mantine-adapter's Switch (re-exported from @mantine/core's CheckIcon/CloseIcon,
|
|
26
|
+
// not importable here since mui-adapter doesn't depend on @mantine/core) — kept pixel-identical
|
|
27
|
+
// so adapter-tester's cross-adapter diff stays clean. Same precedent as Checkbox's inline icons.
|
|
28
|
+
function CheckIcon(props: React.ComponentProps<"svg">) {
|
|
29
|
+
return (
|
|
30
|
+
<svg
|
|
31
|
+
viewBox="0 0 10 7"
|
|
32
|
+
fill="none"
|
|
33
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
34
|
+
{...props}
|
|
35
|
+
>
|
|
36
|
+
<path
|
|
37
|
+
d="M4 4.586L1.707 2.293A1 1 0 1 0 .293 3.707l3 3a.997.997 0 0 0 1.414 0l5-5A1 1 0 1 0 8.293.293L4 4.586z"
|
|
38
|
+
fill="currentColor"
|
|
39
|
+
fillRule="evenodd"
|
|
40
|
+
clipRule="evenodd"
|
|
41
|
+
/>
|
|
42
|
+
</svg>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function CloseIcon(props: React.ComponentProps<"svg">) {
|
|
47
|
+
return (
|
|
48
|
+
<svg
|
|
49
|
+
viewBox="0 0 15 15"
|
|
50
|
+
fill="none"
|
|
51
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
52
|
+
{...props}
|
|
53
|
+
>
|
|
54
|
+
<path
|
|
55
|
+
d="M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z"
|
|
56
|
+
fill="currentColor"
|
|
57
|
+
fillRule="evenodd"
|
|
58
|
+
clipRule="evenodd"
|
|
59
|
+
/>
|
|
60
|
+
</svg>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
23
63
|
|
|
24
64
|
export type SwitchWrapperProps = Omit<
|
|
25
65
|
MuiSwitchProps,
|
|
@@ -37,7 +77,11 @@ export type RecursicaSwitchPropsAlias =
|
|
|
37
77
|
|
|
38
78
|
export type SwitchProps = RecursicaOverStyled<RecursicaSwitchPropsAlias>;
|
|
39
79
|
|
|
40
|
-
type SwitchComponent = React.
|
|
80
|
+
type SwitchComponent = React.ForwardRefExoticComponent<
|
|
81
|
+
SwitchProps & React.RefAttributes<HTMLInputElement>
|
|
82
|
+
> & {
|
|
83
|
+
Group: typeof SwitchGroup;
|
|
84
|
+
};
|
|
41
85
|
|
|
42
86
|
export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
|
|
43
87
|
function Switch(props, ref) {
|
|
@@ -46,7 +90,6 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
|
|
|
46
90
|
readOnly,
|
|
47
91
|
readOnlyComponent,
|
|
48
92
|
disabled,
|
|
49
|
-
// Removed thumbIcon from destructured props since it's not supported
|
|
50
93
|
formLayout,
|
|
51
94
|
labelSize,
|
|
52
95
|
controlMaxWidth,
|
|
@@ -66,31 +109,47 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
|
|
|
66
109
|
delete restRecord["radius"];
|
|
67
110
|
delete restRecord["variant"];
|
|
68
111
|
|
|
112
|
+
// NOTE: classes.root must stay keyed "root" (MUI's own slot name), but it targets a
|
|
113
|
+
// dedicated .switchRoot CSS class, not .root — .root is this component's own outer
|
|
114
|
+
// wrapper class (applied separately, see finalClass below) and sizing the actual MUI
|
|
115
|
+
// switch span the same way would squeeze that wrapper down to the track's own width.
|
|
116
|
+
// See SWITCH_IMPLEMENTATION_NOTES.md #5.
|
|
117
|
+
// NOTE: no "thumb" key here — the default MuiSwitch-thumb slot classes.thumb targets is
|
|
118
|
+
// never rendered. We supply our own icon/checkedIcon node below (see thumbNode), which
|
|
119
|
+
// replaces that slot outright (confirmed against MUI's SwitchBase/Switch source: icon and
|
|
120
|
+
// checkedIcon substitute the whole default thumb, they don't add to it), so the .thumb
|
|
121
|
+
// class is applied directly to our own node instead.
|
|
69
122
|
const mergedClassNames: Partial<Record<string, string>> = {
|
|
70
|
-
root: styles.
|
|
123
|
+
root: styles.switchRoot,
|
|
71
124
|
body: styles.body,
|
|
72
125
|
track: styles.track,
|
|
73
|
-
|
|
126
|
+
switchBase: styles.switchBase,
|
|
74
127
|
trackLabel: styles.trackLabel,
|
|
75
128
|
labelWrapper: styles.labelWrapper,
|
|
76
129
|
label: styles.label,
|
|
77
130
|
};
|
|
78
131
|
|
|
79
|
-
|
|
132
|
+
// NOTE: MUI's actual prop is "classes", not "classNames" (that's Mantine's naming) — this
|
|
133
|
+
// was reading the wrong key and silently doing nothing. Fixed. Also note "body"/
|
|
134
|
+
// "trackLabel"/"labelWrapper"/"label" below aren't real MUI `classes` slots at all (MUI
|
|
135
|
+
// only recognizes root/switchBase/track/thumb/input/checked/disabled/colorX/sizeX/edgeX) —
|
|
136
|
+
// those four are this component's own hardcoded elements, styled directly in the JSX below,
|
|
137
|
+
// not through MUI's classes mechanism. Only root/track/switchBase actually take effect via
|
|
138
|
+
// this override path; the rest are pre-existing dead weight, unrelated to the classNames bug.
|
|
139
|
+
const classesProp = restRecord.classes;
|
|
80
140
|
if (
|
|
81
|
-
|
|
82
|
-
typeof
|
|
83
|
-
!Array.isArray(
|
|
141
|
+
classesProp &&
|
|
142
|
+
typeof classesProp === "object" &&
|
|
143
|
+
!Array.isArray(classesProp)
|
|
84
144
|
) {
|
|
85
|
-
const o =
|
|
86
|
-
mergedClassNames.root = o.root
|
|
145
|
+
const o = classesProp as Partial<Record<string, string>>;
|
|
146
|
+
mergedClassNames.root = o.root
|
|
147
|
+
? `${styles.switchRoot} ${o.root}`
|
|
148
|
+
: styles.switchRoot;
|
|
87
149
|
mergedClassNames.body = o.body ? `${styles.body} ${o.body}` : styles.body;
|
|
88
150
|
mergedClassNames.track = o.track
|
|
89
151
|
? `${styles.track} ${o.track}`
|
|
90
152
|
: styles.track;
|
|
91
|
-
mergedClassNames.thumb = o.thumb
|
|
92
|
-
? `${styles.thumb} ${o.thumb}`
|
|
93
|
-
: styles.thumb;
|
|
94
153
|
mergedClassNames.trackLabel = o.trackLabel
|
|
95
154
|
? `${styles.trackLabel} ${o.trackLabel}`
|
|
96
155
|
: styles.trackLabel;
|
|
@@ -107,8 +166,29 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
|
|
|
107
166
|
? `${styles.root} ${classNameProp}`
|
|
108
167
|
: styles.root;
|
|
109
168
|
|
|
169
|
+
// Threads a grouped Switch's checked state through SwitchGroupContext (mirrors Checkbox's
|
|
170
|
+
// identical CheckboxGroupContext pattern) — FormGroup itself has no controlled-value
|
|
171
|
+
// concept, so this is the only place group membership is actually computed. Standalone
|
|
172
|
+
// (ungrouped) Switch usage is untouched: isChecked/isGroupReadOnly are no-ops outside a
|
|
173
|
+
// <Switch.Group>. See SWITCH_IMPLEMENTATION_NOTES.md.
|
|
174
|
+
const groupContext = useContext(SwitchGroupContext);
|
|
175
|
+
const isGrouped = groupContext !== null;
|
|
176
|
+
const isGroupReadOnly = isGrouped ? !!groupContext.readOnly : false;
|
|
177
|
+
const isChecked = isGrouped
|
|
178
|
+
? (groupContext.value || []).includes(restRecord.value as string)
|
|
179
|
+
: !!(restRecord.checked ?? restRecord.defaultChecked);
|
|
180
|
+
|
|
181
|
+
const handleGroupChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
182
|
+
if (!isGrouped) return;
|
|
183
|
+
const currentValue = groupContext.value || [];
|
|
184
|
+
const thisValue = restRecord.value as string;
|
|
185
|
+
const newValue = event.target.checked
|
|
186
|
+
? [...currentValue, thisValue]
|
|
187
|
+
: currentValue.filter((v) => v !== thisValue);
|
|
188
|
+
groupContext.onChange?.(event, newValue);
|
|
189
|
+
};
|
|
190
|
+
|
|
110
191
|
if (readOnly && !!readOnlyComponent) {
|
|
111
|
-
const isChecked = !!(restRecord.checked ?? restRecord.defaultChecked);
|
|
112
192
|
const ReadOnlyComp = readOnlyComponent;
|
|
113
193
|
const roNode = (
|
|
114
194
|
<ReadOnlyComp
|
|
@@ -134,17 +214,41 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
|
|
|
134
214
|
return <>{roNode}</>;
|
|
135
215
|
}
|
|
136
216
|
|
|
137
|
-
//
|
|
217
|
+
// Same node passed as both icon and checkedIcon (mirrors MUI's own Switch.js, which does
|
|
218
|
+
// the same: `icon={icon} checkedIcon={icon}`) so it never unmounts/remounts on toggle —
|
|
219
|
+
// that's what lets the checkIcon/closeIcon crossfade via CSS opacity instead of a hard swap,
|
|
220
|
+
// matching mantine-adapter's thumbIcon (single node, both glyphs always mounted).
|
|
221
|
+
const thumbNode = (
|
|
222
|
+
<span className={styles.thumb}>
|
|
223
|
+
<span className={styles.thumbIconWrapper}>
|
|
224
|
+
<CheckIcon className={styles.checkIcon} />
|
|
225
|
+
<CloseIcon className={styles.closeIcon} />
|
|
226
|
+
</span>
|
|
227
|
+
</span>
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
// We omit Mui's sizing/coloring so we rely strictly on variables from Switch.module.css.
|
|
231
|
+
// color is hardcoded to "default" (never left to MUI's "primary" fallback) so MUI's own
|
|
232
|
+
// palette-driven blue checked/hover/focus styling never activates in the first place —
|
|
233
|
+
// see SWITCH_IMPLEMENTATION_NOTES.md #5.
|
|
234
|
+
const isSwitchDisabled = readOnly || disabled || isGroupReadOnly;
|
|
235
|
+
|
|
138
236
|
const switchNode = (
|
|
139
237
|
<MuiSwitch
|
|
140
238
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
141
239
|
ref={ref as any}
|
|
142
240
|
className={!label ? `${finalClass} ${styles.inner}` : styles.inner}
|
|
143
241
|
classes={mergedClassNames}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
242
|
+
color="default"
|
|
243
|
+
disabled={isSwitchDisabled}
|
|
244
|
+
data-disabled={isSwitchDisabled || undefined}
|
|
245
|
+
icon={thumbNode}
|
|
246
|
+
checkedIcon={thumbNode}
|
|
147
247
|
{...(sanitizedProps as unknown as MuiSwitchProps)}
|
|
248
|
+
{...(isGrouped
|
|
249
|
+
? { checked: isChecked, onChange: handleGroupChange }
|
|
250
|
+
: {})}
|
|
251
|
+
disableRipple
|
|
148
252
|
/>
|
|
149
253
|
);
|
|
150
254
|
|
|
@@ -160,21 +264,23 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
|
|
|
160
264
|
>
|
|
161
265
|
{label as React.ReactNode}
|
|
162
266
|
</label>
|
|
163
|
-
{description
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
{description}
|
|
169
|
-
</div>
|
|
170
|
-
)}
|
|
171
|
-
{error && (
|
|
172
|
-
<div
|
|
173
|
-
className={styles.error}
|
|
174
|
-
data-disabled={readOnly || disabled ? true : undefined}
|
|
267
|
+
{/* description/error are mutually exclusive — error takes precedence */}
|
|
268
|
+
{error ? (
|
|
269
|
+
<AssistiveElement
|
|
270
|
+
assistiveVariant="error"
|
|
271
|
+
assistiveWithIcon={false}
|
|
175
272
|
>
|
|
176
273
|
{error}
|
|
177
|
-
</
|
|
274
|
+
</AssistiveElement>
|
|
275
|
+
) : (
|
|
276
|
+
description && (
|
|
277
|
+
<AssistiveElement
|
|
278
|
+
assistiveVariant="help"
|
|
279
|
+
assistiveWithIcon={false}
|
|
280
|
+
>
|
|
281
|
+
{description}
|
|
282
|
+
</AssistiveElement>
|
|
283
|
+
)
|
|
178
284
|
)}
|
|
179
285
|
</div>
|
|
180
286
|
</div>
|
|
@@ -201,3 +307,4 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
|
|
|
201
307
|
) as SwitchComponent;
|
|
202
308
|
|
|
203
309
|
Switch.displayName = "Switch";
|
|
310
|
+
Switch.Group = SwitchGroup;
|
|
@@ -13,6 +13,14 @@ import { type RecursicaFormControlWrapperProps } from "../FormControlWrapper/For
|
|
|
13
13
|
import { WithReadOnlyWrapper } from "../ReadOnlyField/WithReadOnlyWrapper";
|
|
14
14
|
import styles from "./Switch.module.css";
|
|
15
15
|
|
|
16
|
+
// eslint-disable-next-line react-refresh/only-export-components
|
|
17
|
+
export const SwitchGroupContext = React.createContext<{
|
|
18
|
+
value?: string[];
|
|
19
|
+
onChange?: (event: React.SyntheticEvent, value: string[]) => void;
|
|
20
|
+
name?: string;
|
|
21
|
+
readOnly?: boolean;
|
|
22
|
+
} | null>(null);
|
|
23
|
+
|
|
16
24
|
import { type RecursicaSwitchGroupProps as BaseRecursicaSwitchGroupProps } from "@recursica/adapter-common";
|
|
17
25
|
|
|
18
26
|
export interface RecursicaSwitchGroupProps
|
|
@@ -69,7 +77,6 @@ export const SwitchGroup = forwardRef<HTMLDivElement, SwitchGroupProps>(
|
|
|
69
77
|
emptyValueComponent,
|
|
70
78
|
value,
|
|
71
79
|
defaultValue,
|
|
72
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
73
80
|
onChange,
|
|
74
81
|
...rest
|
|
75
82
|
} = props;
|
|
@@ -79,11 +86,23 @@ export const SwitchGroup = forwardRef<HTMLDivElement, SwitchGroupProps>(
|
|
|
79
86
|
// Delete prohibited sizing hooks from bypassing the variables
|
|
80
87
|
delete restRecord["size"];
|
|
81
88
|
|
|
89
|
+
const handleChange = (
|
|
90
|
+
_event: React.SyntheticEvent,
|
|
91
|
+
childValue: string[],
|
|
92
|
+
) => {
|
|
93
|
+
if (onChange) {
|
|
94
|
+
onChange(childValue);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
82
98
|
return (
|
|
83
99
|
<WithReadOnlyWrapper
|
|
84
100
|
className={className}
|
|
85
101
|
style={style as React.CSSProperties}
|
|
86
|
-
|
|
102
|
+
// No group-level max-width: switch-item label-max-width (200px) caps a single switch's
|
|
103
|
+
// own label text, not the whole group — reusing it here made the side-by-side label
|
|
104
|
+
// column (fixed 224px) wider than its container, so it always wrapped onto its own line.
|
|
105
|
+
controlMaxWidth={undefined}
|
|
87
106
|
controlMinWidth={undefined}
|
|
88
107
|
overStyled={overStyled as true}
|
|
89
108
|
// Strictly override. ARIA grouping prohibits interactive switches nested natively inside <label>.
|
|
@@ -107,14 +126,23 @@ export const SwitchGroup = forwardRef<HTMLDivElement, SwitchGroupProps>(
|
|
|
107
126
|
readOnlyValue={value !== undefined ? value : defaultValue}
|
|
108
127
|
readOnlyNativeProps={props}
|
|
109
128
|
activeComponent={
|
|
110
|
-
<
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
129
|
+
<SwitchGroupContext.Provider
|
|
130
|
+
value={{
|
|
131
|
+
value: value !== undefined ? value : defaultValue,
|
|
132
|
+
onChange: handleChange,
|
|
133
|
+
name: restRecord.name as string | undefined,
|
|
134
|
+
readOnly: readOnly || !!(restRecord as any).disabled,
|
|
135
|
+
}}
|
|
115
136
|
>
|
|
116
|
-
|
|
117
|
-
|
|
137
|
+
<MuiFormGroup
|
|
138
|
+
ref={ref}
|
|
139
|
+
{...(sanitizedProps as unknown as MuiFormGroupProps)}
|
|
140
|
+
className={`${styles.groupRoot} ${(sanitizedProps as any).className || ""}`.trim()}
|
|
141
|
+
data-layout={formLayout}
|
|
142
|
+
>
|
|
143
|
+
{children}
|
|
144
|
+
</MuiFormGroup>
|
|
145
|
+
</SwitchGroupContext.Provider>
|
|
118
146
|
}
|
|
119
147
|
/>
|
|
120
148
|
);
|
|
@@ -2,3 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
- **Compositional API Dropped:** Mantine uses `<Tabs.List>`, `<Tabs.Tab>`, and `<Tabs.Panel>` natively with implicit context from `<Tabs>`. MUI relies on `@mui/lab/TabContext` and separates `Tabs` and `TabPanel`.
|
|
4
4
|
- **Monolithic API Adopted:** Following architectural review, we have opted to drop the broken dot-notation wrappers for `mui-adapter`. We now natively export `Tabs` (MUI List), `Tab` (MUI Item), and `TabPanel` (from `@mui/lab`). Developers must use `TabContext` (from `@mui/lab`) to manage state, just like native MUI. Storybook and visual regression tests have been updated to reflect this divergence while retaining core property mapping compatibility.
|
|
5
|
+
|
|
6
|
+
## Traps found via adapter-tester parity fixing (2026-08-15)
|
|
7
|
+
|
|
8
|
+
- **`Tabs` doesn't read `TabContext`:** only `TabPanel` (from `@mui/lab`) consumes it. `Tabs` (from `@mui/material`) needs its own explicit `value` prop or every tab renders permanently unselected (no error, just silently inert). See USAGE.md's warning callout.
|
|
9
|
+
- **`TabPanel` is a DOM sibling of `Tabs`, not nested inside it:** any `.root .panel`-style descendant selector in this file silently never matches. Scope panel rules off the bare class instead. Consumers must also wrap both in their own column-direction container (Storybook does this via `Flex`) — otherwise the panel renders beside the tab list, not below it, because there's nothing else establishing the block stacking.
|
|
10
|
+
- **`.Mui-selected` needs `:global(...)`:** written bare in a `.module.css` file, CSS Modules locally scopes it to something like `.Tabs-module__Mui-selected___xxx`, which never matches the real MUI-applied class. Every reference to a native MUI state class in this file must be `:global(.Mui-selected)`, not `.Mui-selected`. (SegmentedControl's `.control.Mui-selected` has this exact same latent bug — not fixed as part of this pass, flagged separately.)
|
|
11
|
+
- **MUI's Tab defaults to `min-height: 72px`** for icon+label tabs (48px otherwise); Mantine's tab height is purely padding/content-driven. Reset to `min-height: auto` or the whole row renders visibly taller than Mantine's.
|
|
12
|
+
- **MUI's `TabPanel` defaults to `padding: theme.spacing(3)` (24px)**; Mantine's `Tabs.Panel` has none — the gap is already carried by `.list`'s own margin. Reset to `padding: 0`.
|
|
13
|
+
- **The indicator (`classes.indicator`) is absolutely positioned against `.MuiTabs-root`, not `.list`:** `.list`'s own `margin-bottom` (the tabs-content-gap) collapses into that root's auto height instead of stopping at `.list`'s border edge, so the indicator's default `bottom: 0` lands one content-gap below the baseline. Offset it by the same content-gap token to bring it flush again.
|
|
14
|
+
- **All of the above needed `!important`** to reliably beat MUI's own emotion-injected styles for the same property — plain specificity/cascade order wasn't sufficient in this build.
|