@recursica/mui-adapter 0.34.0 → 0.34.3
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 +27 -0
- package/dist/index.d.ts +219 -154
- package/dist/mui-adapter.cjs +55 -55
- package/dist/mui-adapter.cjs.map +1 -1
- package/dist/mui-adapter.css +1 -1
- package/dist/mui-adapter.js +3383 -3440
- package/dist/mui-adapter.js.map +1 -1
- package/package.json +9 -4
- package/src/OverStyling.tsx +2 -2
- package/src/components/Accordion/Accordion.test.tsx +1 -0
- package/src/components/AssistiveElement/AssistiveElement.test.tsx +1 -0
- package/src/components/Autocomplete/Autocomplete.test.tsx +1 -0
- package/src/components/Avatar/Avatar.test.tsx +1 -0
- package/src/components/Badge/Badge.test.tsx +1 -0
- package/src/components/Box/Box.test.tsx +1 -0
- package/src/components/Breadcrumb/Breadcrumb.test.tsx +1 -0
- package/src/components/Button/BUTTON_IMPLEMENTATION_NOTES.md +10 -0
- package/src/components/Button/Button.module.css +1 -0
- package/src/components/Button/Button.styleIsolation.dom.test.tsx +98 -0
- package/src/components/Card/Card.test.tsx +1 -0
- package/src/components/Checkbox/Checkbox.test.tsx +1 -0
- package/src/components/Chip/Chip.test.tsx +1 -0
- package/src/components/Container/Container.test.tsx +1 -0
- package/src/components/DatePicker/DatePicker.stories.tsx +25 -0
- package/src/components/DatePicker/DatePicker.test.tsx +1 -0
- package/src/components/Dropdown/Dropdown.test.tsx +1 -0
- package/src/components/FileInput/FileInput.test.tsx +1 -0
- package/src/components/FileUpload/FileUpload.test.tsx +1 -0
- package/src/components/Flex/Flex.test.tsx +1 -0
- package/src/components/Flex/Flex.tsx +21 -4
- package/src/components/FormControlLayout/FormControlLayout.test.tsx +1 -0
- package/src/components/FormControlWrapper/FormControlWrapper.test.tsx +1 -0
- package/src/components/Grid/Grid.stories.tsx +38 -62
- package/src/components/Grid/Grid.test.tsx +1 -0
- package/src/components/Grid/Grid.tsx +59 -144
- package/src/components/Grid/IMPLEMENTATION_NOTES.md +11 -11
- package/src/components/Grid/USAGE.md +8 -6
- package/src/components/Group/Group.test.tsx +1 -0
- package/src/components/Group/Group.tsx +24 -2
- package/src/components/HoverCard/HoverCard.test.tsx +1 -0
- package/src/components/Label/Label.test.tsx +1 -0
- package/src/components/Link/Link.test.tsx +1 -0
- package/src/components/Loader/Loader.test.tsx +1 -0
- package/src/components/Menu/Menu.test.tsx +1 -0
- package/src/components/Modal/Modal.module.css +103 -1
- package/src/components/Modal/Modal.stories.tsx +28 -2
- package/src/components/Modal/Modal.test.tsx +1 -0
- package/src/components/NumberInput/NumberInput.test.tsx +1 -0
- package/src/components/Pagination/Pagination.test.tsx +1 -0
- package/src/components/Panel/Panel.test.tsx +1 -0
- package/src/components/Popover/Popover.test.tsx +1 -0
- package/src/components/Radio/Radio.test.tsx +1 -0
- package/src/components/ReadOnlyField/ReadOnlyField.test.tsx +1 -0
- package/src/components/SegmentedControl/SegmentedControl.test.tsx +1 -0
- package/src/components/Slider/Slider.test.tsx +1 -0
- package/src/components/Stack/Stack.stories.tsx +9 -9
- package/src/components/Stack/Stack.test.tsx +1 -0
- package/src/components/Stack/Stack.tsx +15 -14
- package/src/components/Stack/USAGE.md +1 -1
- package/src/components/Stepper/Stepper.test.tsx +1 -0
- package/src/components/Switch/Switch.stories.tsx +1 -1
- package/src/components/Switch/Switch.test.tsx +1 -0
- package/src/components/Table/Table.test.tsx +1 -0
- package/src/components/Tabs/Tabs.test.tsx +1 -0
- package/src/components/Text/Text.stories.tsx +3 -16
- package/src/components/Text/Text.test.tsx +1 -0
- package/src/components/TextArea/TextArea.test.tsx +1 -0
- package/src/components/TextField/TextField.test.tsx +1 -0
- package/src/components/TimePicker/TimePicker.test.tsx +1 -0
- package/src/components/Timeline/Timeline.test.tsx +1 -0
- package/src/components/Title/Title.stories.tsx +3 -16
- package/src/components/Title/Title.test.tsx +1 -0
- package/src/components/Toast/Toast.test.tsx +1 -0
- package/src/components/Tooltip/Tooltip.test.tsx +1 -0
- package/src/components/TransferList/TransferList.test.tsx +1 -0
- package/src/components/Tree/Tree.test.tsx +1 -0
- package/src/components/Typography/Typography.test.tsx +1 -0
- package/src/utils/filterStylingProps.test.ts +85 -0
|
@@ -1,22 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Grid layout wrapper.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* MUI merges "container" and "item" into a single `Grid` component (no separate Grid.Col,
|
|
5
|
+
* unlike Mantine). To preserve the Grid/Grid.Col dot-notation shape across adapters, this
|
|
6
|
+
* file splits MUI's single component back into two thin wrappers: Grid always renders MUI's
|
|
7
|
+
* `<Grid container>`, GridCol always renders a plain MUI `<Grid>` (item mode).
|
|
8
|
+
*
|
|
9
|
+
* Per the layout-components rule, both wrappers simply pass through MUI's own Grid props
|
|
10
|
+
* (`size`, `offset`, `spacing`, `columns`, `direction`, `wrap`) with no Recursica renaming.
|
|
11
|
+
* MUI has no per-item "grow to fill remaining space" container flag like Mantine's `grow`;
|
|
12
|
+
* use MUI's own `size="grow"` on individual columns instead. `visibleFrom`/`hiddenFrom`
|
|
13
|
+
* don't exist in MUI at all, so — per the rule that a missing kit feature is built following
|
|
14
|
+
* Mantine's own shape — they're added here via CSS classes, keyed on MUI's own breakpoint
|
|
15
|
+
* scale (`xs`/`sm`/`md`/`lg`/`xl`).
|
|
16
|
+
*
|
|
17
|
+
* `justifyContent`/`alignItems` (container) and `order` (item) are typed on `MuiGridProps`
|
|
18
|
+
* (inherited generically from `SystemProps`) but MUI's Grid style generator — verified by
|
|
19
|
+
* reading `@mui/system`'s `gridGenerator.js`/`createGrid.js` — only wires up `size`, `offset`,
|
|
20
|
+
* `columns`, `spacing`/`rowSpacing`/`columnSpacing`, `direction`, and `wrap`; passing these
|
|
21
|
+
* three straight through would silently no-op. They're pulled out and applied via inline
|
|
22
|
+
* `style` instead, still using MUI's own native names.
|
|
10
23
|
*
|
|
11
24
|
* Like Flex, Stack, Group, and Container, this is a primitive layout component and
|
|
12
25
|
* does not use the `RecursicaOverStyled` gatekeeper — only the `sx` prop is stripped.
|
|
13
26
|
*/
|
|
14
|
-
import {
|
|
15
|
-
createContext,
|
|
16
|
-
forwardRef,
|
|
17
|
-
useContext,
|
|
18
|
-
type CSSProperties,
|
|
19
|
-
} from "react";
|
|
27
|
+
import { forwardRef, type CSSProperties } from "react";
|
|
20
28
|
import { Grid as MuiGrid, type GridProps as MuiGridProps } from "@mui/material";
|
|
21
29
|
import {
|
|
22
30
|
SPACING_MAP,
|
|
@@ -26,81 +34,9 @@ import {
|
|
|
26
34
|
} from "../../utils/filterStylingProps";
|
|
27
35
|
import styles from "./Grid.module.css";
|
|
28
36
|
|
|
29
|
-
import {
|
|
30
|
-
type RecursicaGridProps,
|
|
31
|
-
type RecursicaGridColProps,
|
|
32
|
-
} from "@recursica/adapter-common";
|
|
33
|
-
|
|
34
37
|
type Breakpoint = "xs" | "sm" | "md" | "lg" | "xl";
|
|
35
|
-
type RecursicaBreakpoint = "base" | Breakpoint;
|
|
36
|
-
|
|
37
|
-
const GridContext = createContext<{ grow: boolean }>({ grow: false });
|
|
38
|
-
|
|
39
|
-
/** Remaps the Recursica-only "base" breakpoint key to MUI's "xs" (its smallest breakpoint). */
|
|
40
|
-
function remapBase<T>(
|
|
41
|
-
value: T | Partial<Record<RecursicaBreakpoint, T>> | undefined,
|
|
42
|
-
): T | Partial<Record<Breakpoint, T>> | undefined {
|
|
43
|
-
if (value === undefined || typeof value !== "object") {
|
|
44
|
-
return value as T | undefined;
|
|
45
|
-
}
|
|
46
|
-
const { base, ...rest } = value as Partial<Record<RecursicaBreakpoint, T>>;
|
|
47
|
-
return base !== undefined
|
|
48
|
-
? ({ xs: base, ...rest } as Partial<Record<Breakpoint, T>>)
|
|
49
|
-
: (rest as Partial<Record<Breakpoint, T>>);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Recursica's "auto" (grow to fill remaining space) and "content" (size to content)
|
|
54
|
-
* are the inverse of MUI's own "auto" (size to content) and "grow" (fill remaining
|
|
55
|
-
* space) keywords.
|
|
56
|
-
*/
|
|
57
|
-
function mapSpanValue(
|
|
58
|
-
value: number | "auto" | "content" | undefined,
|
|
59
|
-
): number | "auto" | "grow" | undefined {
|
|
60
|
-
if (value === "auto") return "grow";
|
|
61
|
-
if (value === "content") return "auto";
|
|
62
|
-
return value;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function mapSpan(span: RecursicaGridColProps["span"]): MuiGridProps["size"] {
|
|
66
|
-
const remapped = remapBase(span);
|
|
67
|
-
if (remapped === undefined || typeof remapped !== "object") {
|
|
68
|
-
return mapSpanValue(remapped as number | "auto" | "content" | undefined);
|
|
69
|
-
}
|
|
70
|
-
const mapped: Partial<Record<Breakpoint, number | "auto" | "grow">> = {};
|
|
71
|
-
(
|
|
72
|
-
Object.entries(remapped) as [Breakpoint, number | "auto" | "content"][]
|
|
73
|
-
).forEach(([key, value]) => {
|
|
74
|
-
mapped[key] = mapSpanValue(value);
|
|
75
|
-
});
|
|
76
|
-
return mapped as MuiGridProps["size"];
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function mapOffset(
|
|
80
|
-
offset: RecursicaGridColProps["offset"],
|
|
81
|
-
): MuiGridProps["offset"] {
|
|
82
|
-
return remapBase(offset) as MuiGridProps["offset"];
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* `order` has no native MUI Grid equivalent. A fixed number is applied directly.
|
|
87
|
-
* A responsive object is NOT fully supported in this v1 — the smallest specified
|
|
88
|
-
* breakpoint's value is applied as a single static order (documented in
|
|
89
|
-
* IMPLEMENTATION_NOTES.md), rather than building bespoke per-breakpoint CSS for it.
|
|
90
|
-
*/
|
|
91
|
-
function resolveOrder(
|
|
92
|
-
order: RecursicaGridColProps["order"],
|
|
93
|
-
): number | undefined {
|
|
94
|
-
if (order === undefined || typeof order === "number") {
|
|
95
|
-
return order;
|
|
96
|
-
}
|
|
97
|
-
const remapped = remapBase(order) as Partial<Record<Breakpoint, number>>;
|
|
98
|
-
return (
|
|
99
|
-
remapped.xs ?? remapped.sm ?? remapped.md ?? remapped.lg ?? remapped.xl
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
38
|
|
|
103
|
-
const HIDDEN_FROM_CLASS:
|
|
39
|
+
const HIDDEN_FROM_CLASS: Record<Breakpoint, string> = {
|
|
104
40
|
xs: styles.hiddenFromXs,
|
|
105
41
|
sm: styles.hiddenFromSm,
|
|
106
42
|
md: styles.hiddenFromMd,
|
|
@@ -108,7 +44,7 @@ const HIDDEN_FROM_CLASS: Partial<Record<Breakpoint, string>> = {
|
|
|
108
44
|
xl: styles.hiddenFromXl,
|
|
109
45
|
};
|
|
110
46
|
|
|
111
|
-
// "xs"
|
|
47
|
+
// "xs" intentionally absent: visible from the smallest breakpoint means never hidden.
|
|
112
48
|
const VISIBLE_FROM_CLASS: Partial<Record<Breakpoint, string>> = {
|
|
113
49
|
sm: styles.visibleFromSm,
|
|
114
50
|
md: styles.visibleFromMd,
|
|
@@ -116,56 +52,47 @@ const VISIBLE_FROM_CLASS: Partial<Record<Breakpoint, string>> = {
|
|
|
116
52
|
xl: styles.visibleFromXl,
|
|
117
53
|
};
|
|
118
54
|
|
|
119
|
-
function normalizeBreakpoint(bp: RecursicaBreakpoint): Breakpoint {
|
|
120
|
-
return bp === "base" ? "xs" : bp;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
55
|
// ============================================================
|
|
124
56
|
// GRID
|
|
125
57
|
// ============================================================
|
|
126
58
|
|
|
127
59
|
export type GridProps = WithRecursicaSpacing<
|
|
128
|
-
OmitSx<Omit<MuiGridProps, "container" | "
|
|
129
|
-
|
|
130
|
-
|
|
60
|
+
OmitSx<Omit<MuiGridProps, "container" | "justifyContent" | "alignItems">>
|
|
61
|
+
> & {
|
|
62
|
+
/** Sets `justify-content` on the container. Applied via inline style — see notes above. */
|
|
63
|
+
justifyContent?: CSSProperties["justifyContent"];
|
|
64
|
+
/** Sets `align-items` on the container. Applied via inline style — see notes above. */
|
|
65
|
+
alignItems?: CSSProperties["alignItems"];
|
|
66
|
+
};
|
|
131
67
|
|
|
132
68
|
const GridBase = forwardRef<HTMLDivElement, GridProps>(function Grid(
|
|
133
69
|
{
|
|
134
70
|
children,
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
justify,
|
|
139
|
-
align,
|
|
71
|
+
spacing = "rec-default",
|
|
72
|
+
justifyContent,
|
|
73
|
+
alignItems,
|
|
140
74
|
style,
|
|
141
75
|
...rest
|
|
142
76
|
},
|
|
143
77
|
ref,
|
|
144
78
|
) {
|
|
145
79
|
const safeProps = filterSxProp(rest as Record<string, unknown>);
|
|
146
|
-
const
|
|
147
|
-
typeof
|
|
148
|
-
? SPACING_MAP[
|
|
149
|
-
:
|
|
80
|
+
const resolvedSpacing =
|
|
81
|
+
typeof spacing === "string" && spacing in SPACING_MAP
|
|
82
|
+
? SPACING_MAP[spacing as keyof typeof SPACING_MAP]
|
|
83
|
+
: spacing;
|
|
150
84
|
|
|
151
85
|
return (
|
|
152
|
-
<
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
alignItems: align,
|
|
163
|
-
...(style as CSSProperties),
|
|
164
|
-
}}
|
|
165
|
-
>
|
|
166
|
-
{children}
|
|
167
|
-
</MuiGrid>
|
|
168
|
-
</GridContext.Provider>
|
|
86
|
+
<MuiGrid
|
|
87
|
+
ref={ref}
|
|
88
|
+
{...(safeProps as unknown as MuiGridProps)}
|
|
89
|
+
container
|
|
90
|
+
spacing={resolvedSpacing}
|
|
91
|
+
className={styles.root}
|
|
92
|
+
style={{ justifyContent, alignItems, ...(style as CSSProperties) }}
|
|
93
|
+
>
|
|
94
|
+
{children}
|
|
95
|
+
</MuiGrid>
|
|
169
96
|
);
|
|
170
97
|
});
|
|
171
98
|
GridBase.displayName = "Grid";
|
|
@@ -175,33 +102,27 @@ GridBase.displayName = "Grid";
|
|
|
175
102
|
// ============================================================
|
|
176
103
|
|
|
177
104
|
export type GridColProps = WithRecursicaSpacing<
|
|
178
|
-
OmitSx<Omit<MuiGridProps, "container" | "
|
|
179
|
-
|
|
180
|
-
|
|
105
|
+
OmitSx<Omit<MuiGridProps, "container" | "order">>
|
|
106
|
+
> & {
|
|
107
|
+
/** Sets the CSS `order` property. Applied via inline style — see notes above. */
|
|
108
|
+
order?: number;
|
|
109
|
+
/** Hides the column below the given breakpoint. MUI has no native equivalent. */
|
|
110
|
+
visibleFrom?: Breakpoint;
|
|
111
|
+
/** Hides the column above the given breakpoint. MUI has no native equivalent. */
|
|
112
|
+
hiddenFrom?: Breakpoint;
|
|
113
|
+
};
|
|
181
114
|
|
|
182
115
|
export const GridCol = forwardRef<HTMLDivElement, GridColProps>(
|
|
183
116
|
function GridCol(
|
|
184
|
-
{
|
|
185
|
-
children,
|
|
186
|
-
span,
|
|
187
|
-
offset,
|
|
188
|
-
order,
|
|
189
|
-
visibleFrom,
|
|
190
|
-
hiddenFrom,
|
|
191
|
-
style,
|
|
192
|
-
className,
|
|
193
|
-
...rest
|
|
194
|
-
},
|
|
117
|
+
{ children, order, visibleFrom, hiddenFrom, style, className, ...rest },
|
|
195
118
|
ref,
|
|
196
119
|
) {
|
|
197
|
-
const { grow } = useContext(GridContext);
|
|
198
120
|
const safeProps = filterSxProp(rest as Record<string, unknown>);
|
|
199
|
-
const resolvedOrder = resolveOrder(order);
|
|
200
121
|
|
|
201
122
|
const visibilityClass = visibleFrom
|
|
202
|
-
? VISIBLE_FROM_CLASS[
|
|
123
|
+
? VISIBLE_FROM_CLASS[visibleFrom]
|
|
203
124
|
: hiddenFrom
|
|
204
|
-
? HIDDEN_FROM_CLASS[
|
|
125
|
+
? HIDDEN_FROM_CLASS[hiddenFrom]
|
|
205
126
|
: undefined;
|
|
206
127
|
|
|
207
128
|
const finalClassName = [styles.col, visibilityClass, className]
|
|
@@ -211,15 +132,9 @@ export const GridCol = forwardRef<HTMLDivElement, GridColProps>(
|
|
|
211
132
|
return (
|
|
212
133
|
<MuiGrid
|
|
213
134
|
ref={ref}
|
|
214
|
-
size={mapSpan(span)}
|
|
215
|
-
offset={mapOffset(offset)}
|
|
216
135
|
className={finalClassName}
|
|
217
136
|
{...(safeProps as unknown as MuiGridProps)}
|
|
218
|
-
style={{
|
|
219
|
-
order: resolvedOrder,
|
|
220
|
-
...(grow ? { flexGrow: 1 } : {}),
|
|
221
|
-
...(style as CSSProperties),
|
|
222
|
-
}}
|
|
137
|
+
style={{ order, ...(style as CSSProperties) }}
|
|
223
138
|
>
|
|
224
139
|
{children}
|
|
225
140
|
</MuiGrid>
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
# Grid Implementation Notes
|
|
2
2
|
|
|
3
|
-
MUI has no native Grid/Grid.Col split — MUI's own `Grid` component merges "container" and "item" behavior into one component (toggled by the `container` boolean and a `size` prop). To keep the
|
|
3
|
+
MUI has no native Grid/Grid.Col split — MUI's own `Grid` component merges "container" and "item" behavior into one component (toggled by the `container` boolean and a `size` prop). To keep the same `Grid` + `Grid.Col` dot-notation shape as the mantine-adapter, this file hand-composes two thin wrappers around that single underlying component: `Grid` always renders `<MuiGrid container>`, `Grid.Col` always renders `<MuiGrid>` in item mode. This is a structural convenience only — the props on each wrapper are MUI's own, not a Recursica-defined contract.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Per the layout-components rule (no formal Recursica common props definition — layout components simply pass through the underlying kit's own props), this Grid does **not** attempt prop-name parity with the mantine-adapter's Grid. Its public API is MUI's own vocabulary:
|
|
6
6
|
|
|
7
|
-
- **`
|
|
8
|
-
- **`
|
|
9
|
-
- **`offset
|
|
10
|
-
-
|
|
11
|
-
- **`
|
|
12
|
-
- **`
|
|
13
|
-
- **`
|
|
14
|
-
- **`
|
|
7
|
+
- **`spacing`** (not Mantine's `gutter`/the old Recursica `gap` alias) — MUI's own container-spacing prop. Still accepts `rec-*` tokens, resolved through the same `SPACING_MAP` lookup used elsewhere in this adapter.
|
|
8
|
+
- **`size`** (not Mantine's `span`) — MUI's own item-sizing prop on `Grid.Col`: a column count, `"auto"` (size to content), `"grow"` (fill remaining space), or a responsive object keyed by MUI's own breakpoints. No inversion/translation against Mantine's `span` semantics.
|
|
9
|
+
- **`offset`** — MUI's own prop, used as-is.
|
|
10
|
+
- **Breakpoint objects** use MUI's own scale (`xs`/`sm`/`md`/`lg`/`xl`) directly. There is no `"base"` alias — `xs` is MUI's smallest breakpoint, same role Mantine's own smallest breakpoint plays.
|
|
11
|
+
- **`grow` (container-level)**: not implemented. Mantine's container `grow` flag ("last row's columns expand to fill available space") has no MUI equivalent, but MUI's own `size="grow"` on individual `Grid.Col`s already covers the same real-world need — set it per-column instead of on the container.
|
|
12
|
+
- **`order`**: MUI types `order` generically (via `SystemProps`) but its Grid style generator (`gridGenerator.js`/`createGrid.js`, read directly to confirm) never wires it up — passing it straight through silently no-ops. Applied here via inline `style.order` instead, single value only (no per-breakpoint object).
|
|
13
|
+
- **`justifyContent` / `alignItems`** (container only): same issue as `order` — typed but not read by MUI's Grid generator (confirmed by reading `gridGenerator.js`: only `size`, `offset`, `columns`, spacing, `direction`, and `wrap` have generator functions). Applied via inline `style` instead, using MUI's own prop names (not Mantine's `justify`/`align`).
|
|
14
|
+
- **`visibleFrom` / `hiddenFrom`**: no native MUI Grid mechanism at all, so — per the rule that a missing kit feature is built following Mantine's own shape — implemented via `Grid.module.css` media-query classes, hardcoded to MUI's own default `theme.breakpoints` pixel values (`xs:0, sm:600, md:900, lg:1200, xl:1536`). If the consuming app customizes its MUI theme breakpoints, these thresholds won't follow — structural layout mechanics, not a design token.
|
|
15
15
|
|
|
16
|
-
**Cross-adapter parity caveat**: `visibleFrom`/`hiddenFrom`
|
|
16
|
+
**Cross-adapter parity caveat**: `visibleFrom`/`hiddenFrom` are pinned to MUI's default breakpoints here, while the mantine-adapter's equivalent behavior is driven by Mantine's own default breakpoints (576/768/992/1200/1408). The two adapters' Grid stories may show different columns at the exact same viewport width in the `adapter-tester` visual-regression suite as a result — a real, structural difference between the two underlying libraries' breakpoint scales, not a bug in either adapter. The two adapters' Grid APIs are also no longer prop-name-identical (`spacing`/`size` here vs. `gutter`/`span` in mantine-adapter) — this is intentional per the layout-components rule; each adapter's Grid speaks its own kit's vocabulary.
|
|
@@ -20,9 +20,9 @@ import { Grid } from "@recursica/mui-adapter";
|
|
|
20
20
|
|
|
21
21
|
export default function Demo() {
|
|
22
22
|
return (
|
|
23
|
-
<Grid
|
|
24
|
-
<Grid.Col
|
|
25
|
-
<Grid.Col
|
|
23
|
+
<Grid spacing="rec-default">
|
|
24
|
+
<Grid.Col size={6}>Half width</Grid.Col>
|
|
25
|
+
<Grid.Col size={{ xs: 12, sm: 6, md: 3 }}>Responsive width</Grid.Col>
|
|
26
26
|
</Grid>
|
|
27
27
|
);
|
|
28
28
|
}
|
|
@@ -44,8 +44,10 @@ All Recursica components in the `@recursica/mui-adapter` package adhere strictly
|
|
|
44
44
|
|
|
45
45
|
## 4. Key Integration Features & Constraints
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
This Grid uses MUI's own prop vocabulary directly (see [IMPLEMENTATION_NOTES.md](./IMPLEMENTATION_NOTES.md)) — it is not name-for-name identical to the mantine-adapter's Grid.
|
|
48
|
+
|
|
49
|
+
- `spacing` controls the space between grid items (accepts `rec-*` tokens).
|
|
50
|
+
- `size` accepts a column count, `"auto"`, `"grow"`, or a responsive object (`{ xs, sm, md, ... }`).
|
|
49
51
|
- `offset` shifts a column by a number of columns.
|
|
50
|
-
- `order` accepts a fixed number to control a column's visual order
|
|
52
|
+
- `order` accepts a fixed number to control a column's visual order (single value only, no responsive object).
|
|
51
53
|
- `visibleFrom`/`hiddenFrom` show or hide a column at the standard breakpoints (600/900/1200/1536px).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Placeholder for future Group component tests. Add tests here if/when needed.
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Group layout wrapper.
|
|
3
|
+
*
|
|
4
|
+
* NOTE: MUI has no native `<Group>` component. Per the layout-components rule, when a UI kit
|
|
5
|
+
* lacks an equivalent component it's built following a prop definition similar to Mantine's
|
|
6
|
+
* own `Group` (gap/rowGap/columnGap/justify/align/wrap) — this shape is owned locally here,
|
|
7
|
+
* not shared as a formal cross-adapter Recursica contract. It's implemented as a row-direction,
|
|
8
|
+
* wrapping MUI `Stack`.
|
|
9
|
+
*/
|
|
1
10
|
import { forwardRef } from "react";
|
|
2
11
|
import {
|
|
3
12
|
Stack as MUIStack,
|
|
@@ -10,10 +19,23 @@ import {
|
|
|
10
19
|
type WithRecursicaSpacing,
|
|
11
20
|
} from "../../utils/filterStylingProps";
|
|
12
21
|
|
|
13
|
-
|
|
22
|
+
interface GroupOwnProps {
|
|
23
|
+
/** Gap spacing */
|
|
24
|
+
gap?: string | number;
|
|
25
|
+
/** Row gap spacing */
|
|
26
|
+
rowGap?: string | number;
|
|
27
|
+
/** Column gap spacing */
|
|
28
|
+
columnGap?: string | number;
|
|
29
|
+
/** Align items justify properties */
|
|
30
|
+
justify?: React.CSSProperties["justifyContent"];
|
|
31
|
+
/** Align items vertical alignment */
|
|
32
|
+
align?: React.CSSProperties["alignItems"];
|
|
33
|
+
/** Flex-wrap settings */
|
|
34
|
+
wrap?: React.CSSProperties["flexWrap"];
|
|
35
|
+
}
|
|
14
36
|
|
|
15
37
|
export type GroupProps = WithRecursicaSpacing<
|
|
16
|
-
OmitSx<Omit<MUIStackProps, "spacing" | "direction"> &
|
|
38
|
+
OmitSx<Omit<MUIStackProps, "spacing" | "direction"> & GroupOwnProps>
|
|
17
39
|
>;
|
|
18
40
|
|
|
19
41
|
export const Group = forwardRef<HTMLDivElement, GroupProps>(function Group(
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Placeholder for future HoverCard component tests. Add tests here if/when needed.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Placeholder for future Label component tests. Add tests here if/when needed.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Placeholder for future Link component tests. Add tests here if/when needed.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Placeholder for future Loader component tests. Add tests here if/when needed.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Placeholder for future Menu component tests. Add tests here if/when needed.
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/* Brand-layer exemptions (recursica-allow-brand) — see recursica-token-analyzer README.md.
|
|
2
|
+
* Close button is styled to match the Button component's text/icon-only variant, so it reuses
|
|
3
|
+
* the same global hover/focus state tokens Button.module.css exempts.
|
|
4
|
+
* recursica-allow-brand: --recursica_brand_states_focus_blur
|
|
5
|
+
* recursica-allow-brand: --recursica_brand_states_focus_border-size
|
|
6
|
+
* recursica-allow-brand: --recursica_brand_states_focus_color
|
|
7
|
+
* recursica-allow-brand: --recursica_brand_states_focus_margin
|
|
8
|
+
* recursica-allow-brand: --recursica_brand_states_hover_color
|
|
9
|
+
* recursica-allow-brand: --recursica_brand_states_hover_opacity
|
|
10
|
+
*/
|
|
11
|
+
|
|
1
12
|
.root {
|
|
2
13
|
}
|
|
3
14
|
|
|
@@ -40,6 +51,9 @@
|
|
|
40
51
|
}
|
|
41
52
|
|
|
42
53
|
.header {
|
|
54
|
+
display: flex; /* HARDCODE: puts the title and close button side-by-side so the title has a bounded width to truncate against */
|
|
55
|
+
align-items: center;
|
|
56
|
+
justify-content: space-between;
|
|
43
57
|
padding: var(
|
|
44
58
|
--recursica_ui-kit_components_modal_properties_header-footer-vertical-padding
|
|
45
59
|
)
|
|
@@ -50,6 +64,12 @@
|
|
|
50
64
|
}
|
|
51
65
|
|
|
52
66
|
.title {
|
|
67
|
+
flex: 1 1 auto; /* HARDCODE: let the title claim the space between the header edge and the close button */
|
|
68
|
+
min-width: 0; /* HARDCODE: required for text-overflow ellipsis to take effect on a flex child */
|
|
69
|
+
overflow: hidden;
|
|
70
|
+
white-space: nowrap;
|
|
71
|
+
text-overflow: ellipsis;
|
|
72
|
+
|
|
53
73
|
color: var(--recursica_ui-kit_components_modal_properties_colors_title);
|
|
54
74
|
|
|
55
75
|
/* Direct Figma Typography Mapping */
|
|
@@ -173,5 +193,87 @@
|
|
|
173
193
|
}
|
|
174
194
|
|
|
175
195
|
.close {
|
|
176
|
-
/*
|
|
196
|
+
/* Matches the Button component's text-variant, icon-only, small-size visual treatment
|
|
197
|
+
(see Button.module.css) so the modal close control looks like a Recursica Button
|
|
198
|
+
rather than MUI's native IconButton. */
|
|
199
|
+
box-sizing: border-box;
|
|
200
|
+
display: flex;
|
|
201
|
+
align-items: center;
|
|
202
|
+
justify-content: center;
|
|
203
|
+
position: relative;
|
|
204
|
+
overflow: hidden;
|
|
205
|
+
transition: all 0.2s ease;
|
|
206
|
+
|
|
207
|
+
height: var(
|
|
208
|
+
--recursica_ui-kit_components_button_variants_sizes_small_properties_height
|
|
209
|
+
);
|
|
210
|
+
min-width: var(
|
|
211
|
+
--recursica_ui-kit_components_button_variants_content_icon-only_variants_sizes_small_properties_min-width
|
|
212
|
+
);
|
|
213
|
+
padding: 0
|
|
214
|
+
var(
|
|
215
|
+
--recursica_ui-kit_components_button_variants_content_icon-only_variants_sizes_small_properties_horizontal-padding
|
|
216
|
+
);
|
|
217
|
+
border-radius: var(
|
|
218
|
+
--recursica_ui-kit_components_button_variants_content_icon-only_variants_sizes_small_properties_border-radius
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
border-style: solid;
|
|
222
|
+
border-width: var(
|
|
223
|
+
--recursica_ui-kit_components_button_variants_styles_text_properties_border-size
|
|
224
|
+
);
|
|
225
|
+
border-color: var(
|
|
226
|
+
--recursica_ui-kit_components_button_variants_styles_text_properties_colors_border-color
|
|
227
|
+
);
|
|
228
|
+
background-color: var(
|
|
229
|
+
--recursica_ui-kit_components_button_variants_styles_text_properties_colors_background-color
|
|
230
|
+
);
|
|
231
|
+
color: var(
|
|
232
|
+
--recursica_ui-kit_components_button_variants_styles_text_properties_colors_icon-color
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.close svg {
|
|
237
|
+
position: relative;
|
|
238
|
+
z-index: 1;
|
|
239
|
+
width: var(
|
|
240
|
+
--recursica_ui-kit_components_button_variants_sizes_small_properties_icon
|
|
241
|
+
);
|
|
242
|
+
height: var(
|
|
243
|
+
--recursica_ui-kit_components_button_variants_sizes_small_properties_icon
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.close::after {
|
|
248
|
+
content: "";
|
|
249
|
+
position: absolute;
|
|
250
|
+
inset: 0;
|
|
251
|
+
border-radius: inherit;
|
|
252
|
+
z-index: 0;
|
|
253
|
+
pointer-events: none;
|
|
254
|
+
transition: opacity 150ms ease;
|
|
255
|
+
opacity: 0;
|
|
256
|
+
background-color: var(--recursica_brand_states_hover_color);
|
|
257
|
+
}
|
|
258
|
+
.close:hover:not(:disabled)::after {
|
|
259
|
+
opacity: var(--recursica_brand_states_hover_opacity);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/* MuiIconButton injects its own `:hover` background via emotion at render time, which lands in
|
|
263
|
+
the DOM after this stylesheet and wins the tie at equal specificity — same class of conflict
|
|
264
|
+
documented in DatePicker.module.css's error/disabled state overrides. `!important` keeps the
|
|
265
|
+
button's own background transparent so only the `::after` overlay above renders the hover
|
|
266
|
+
feedback, matching Button's single-overlay hover treatment exactly. */
|
|
267
|
+
.close:hover {
|
|
268
|
+
background-color: transparent !important;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.close:focus-visible {
|
|
272
|
+
outline: none;
|
|
273
|
+
box-shadow:
|
|
274
|
+
0 0 0 var(--recursica_brand_states_focus_border-size)
|
|
275
|
+
var(--recursica_brand_states_focus_color),
|
|
276
|
+
0 0 var(--recursica_brand_states_focus_blur)
|
|
277
|
+
var(--recursica_brand_states_focus_margin)
|
|
278
|
+
var(--recursica_brand_states_focus_color);
|
|
177
279
|
}
|
|
@@ -19,7 +19,8 @@ export default meta;
|
|
|
19
19
|
type Story = StoryObj<typeof Modal>;
|
|
20
20
|
|
|
21
21
|
const DefaultWrapper = (args: ModalProps) => {
|
|
22
|
-
|
|
22
|
+
// Starts opened so the modal is visible without pressing a button first.
|
|
23
|
+
const [opened, setOpened] = useState(true);
|
|
23
24
|
return (
|
|
24
25
|
<>
|
|
25
26
|
<Modal {...args} opened={opened} onClose={() => setOpened(false)}>
|
|
@@ -43,8 +44,33 @@ export const Default: Story = {
|
|
|
43
44
|
render: (args) => <DefaultWrapper {...args} />,
|
|
44
45
|
};
|
|
45
46
|
|
|
47
|
+
const LongTitleWrapper = (args: ModalProps) => {
|
|
48
|
+
const [opened, setOpened] = useState(true);
|
|
49
|
+
return (
|
|
50
|
+
<>
|
|
51
|
+
<Modal {...args} opened={opened} onClose={() => setOpened(false)}>
|
|
52
|
+
The title above is longer than the header can display, so it truncates
|
|
53
|
+
with an ellipsis instead of wrapping onto a second line.
|
|
54
|
+
<Modal.Footer>
|
|
55
|
+
<Button onClick={() => setOpened(false)}>Got it</Button>
|
|
56
|
+
</Modal.Footer>
|
|
57
|
+
</Modal>
|
|
58
|
+
<Button onClick={() => setOpened(true)}>Open Modal</Button>
|
|
59
|
+
</>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const LongTitle: Story = {
|
|
64
|
+
args: {
|
|
65
|
+
title:
|
|
66
|
+
"This Modal Title Is Deliberately Long Enough To Exceed The Available Header Width",
|
|
67
|
+
},
|
|
68
|
+
render: (args) => <LongTitleWrapper {...args} />,
|
|
69
|
+
};
|
|
70
|
+
|
|
46
71
|
const ScrollingWrapper = (args: ModalProps) => {
|
|
47
|
-
|
|
72
|
+
// Starts opened so the modal is visible without pressing a button first.
|
|
73
|
+
const [opened, setOpened] = useState(true);
|
|
48
74
|
return (
|
|
49
75
|
<>
|
|
50
76
|
<Modal {...args} opened={opened} onClose={() => setOpened(false)}>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Placeholder for future Modal component tests. Add tests here if/when needed.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Placeholder for future NumberInput component tests. Add tests here if/when needed.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Placeholder for future Pagination component tests. Add tests here if/when needed.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Placeholder for future Panel component tests. Add tests here if/when needed.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Placeholder for future Popover component tests. Add tests here if/when needed.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Placeholder for future Radio component tests. Add tests here if/when needed.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Placeholder for future ReadOnlyField component tests. Add tests here if/when needed.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Placeholder for future SegmentedControl component tests. Add tests here if/when needed.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Placeholder for future Slider component tests. Add tests here if/when needed.
|