@recursica/adapter-mui-v7 1.0.4 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/OVERSTYLING.md +1 -1
- package/SETUP.md +26 -3
- package/dist/adapter-mui-v7.cjs +64 -64
- package/dist/adapter-mui-v7.cjs.map +1 -1
- package/dist/adapter-mui-v7.css +1 -1
- package/dist/adapter-mui-v7.js +5869 -5645
- package/dist/adapter-mui-v7.js.map +1 -1
- package/dist/index.d.ts +123 -104
- package/llms.txt +1 -1
- package/package.json +4 -4
- package/recursica_brand.json +172 -235
- package/recursica_tokens.json +16 -127
- package/recursica_ui-kit.json +2195 -8127
- package/recursica_variables_scoped.css +4547 -9194
- package/src/components/Checkbox/Checkbox.module.css +1 -3
- package/src/components/Container/USAGE.md +0 -1
- package/src/components/FileInput/FileInput.module.css +8 -0
- package/src/components/FileUpload/FileUpload.module.css +1 -0
- package/src/components/FileUpload/FileUpload.tsx +1 -0
- package/src/components/Flex/USAGE.md +2 -1
- package/src/components/FormControlLayout/FormControlLayout.module.css +6 -3
- package/src/components/Group/USAGE.md +2 -1
- package/src/components/Heading/Heading.stories.tsx +62 -0
- package/src/components/Heading/Heading.tsx +6 -2
- package/src/components/Heading/USAGE.md +19 -1
- package/src/components/LayoutGrid/LAYOUT_GRID_IMPLEMENTATION_NOTES.md +82 -0
- package/src/components/LayoutGrid/LayoutGrid.columns.dom.test.tsx +78 -0
- package/src/components/{Grid/Grid.module.css → LayoutGrid/LayoutGrid.module.css} +13 -1
- package/src/components/LayoutGrid/LayoutGrid.stories.tsx +61 -0
- package/src/components/LayoutGrid/LayoutGrid.test.tsx +1 -0
- package/src/components/LayoutGrid/LayoutGrid.tsx +181 -0
- package/src/components/LayoutGrid/USAGE.md +79 -0
- package/src/components/LayoutGrid/index.ts +1 -0
- package/src/components/Menu/Menu.stories.tsx +0 -3
- package/src/components/Pagination/IMPLEMENTATION_NOTES.md +42 -38
- package/src/components/Pagination/Pagination.icons.tsx +0 -32
- package/src/components/Pagination/Pagination.manifest.dom.test.tsx +105 -0
- package/src/components/Pagination/Pagination.module.css +25 -164
- package/src/components/Pagination/Pagination.tsx +221 -97
- package/src/components/Pagination/USAGE.md +8 -1
- package/src/components/Panel/Panel.module.css +24 -1
- package/src/components/Panel/Panel.stories.tsx +4 -4
- package/src/components/Panel/Panel.tsx +10 -1
- package/src/components/Radio/Radio.module.css +2 -2
- package/src/components/SegmentedControl/SegmentedControl.module.css +18 -0
- package/src/components/Stack/USAGE.md +2 -1
- package/src/components/Stepper/Stepper.module.css +0 -21
- package/src/components/Text/Text.stories.tsx +52 -3
- package/src/components/Text/Text.tsx +13 -3
- package/src/components/Text/USAGE.md +20 -2
- package/src/components/Typography/Typography.module.css +39 -0
- package/src/components/index.ts +1 -1
- package/src/index.ts +1 -0
- package/src/utils/breakpointsFromRecManifest.test.ts +40 -0
- package/src/utils/breakpointsFromRecManifest.ts +72 -0
- package/src/utils/index.ts +1 -0
- package/src/components/Grid/Grid.stories.tsx +0 -161
- package/src/components/Grid/Grid.test.tsx +0 -1
- package/src/components/Grid/Grid.tsx +0 -155
- package/src/components/Grid/IMPLEMENTATION_NOTES.md +0 -24
- package/src/components/Grid/USAGE.md +0 -53
- package/src/components/Grid/index.ts +0 -1
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
* HARDCODED VALUES
|
|
3
3
|
* - `.root` `text-wrap: balance` — not a design token; a layout algorithm choice requested by
|
|
4
4
|
* UX (Matt Massey, 2026-08-28) to even out wrapped line lengths. See TEXT_IMPLEMENTATION_NOTES.md.
|
|
5
|
+
*
|
|
6
|
+
* Brand-layer exemptions (recursica-allow-brand) — see recursica-token-analyzer README.md.
|
|
7
|
+
* Semantic text color and emphasis are layer/theme-level concerns with no per-component UI-kit
|
|
8
|
+
* token; Text and Heading both bind the brand layer text-element + text-emphasis tokens
|
|
9
|
+
* directly, by design.
|
|
10
|
+
* recursica-allow-brand: --recursica_brand_layer_elements_text_color
|
|
11
|
+
* recursica-allow-brand: --recursica_brand_layer_elements_text_warning
|
|
12
|
+
* recursica-allow-brand: --recursica_brand_layer_elements_text_alert
|
|
13
|
+
* recursica-allow-brand: --recursica_brand_layer_elements_text_success
|
|
14
|
+
* recursica-allow-brand: --recursica_brand_text-emphasis_high
|
|
15
|
+
* recursica-allow-brand: --recursica_brand_text-emphasis_low
|
|
5
16
|
*/
|
|
6
17
|
|
|
7
18
|
.root {
|
|
@@ -11,3 +22,31 @@
|
|
|
11
22
|
just ignore the declaration. */
|
|
12
23
|
text-wrap: balance;
|
|
13
24
|
}
|
|
25
|
+
|
|
26
|
+
.root[data-color="default"] {
|
|
27
|
+
color: var(--recursica_brand_layer_elements_text_color);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.root[data-color="warning"] {
|
|
31
|
+
color: var(--recursica_brand_layer_elements_text_warning);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.root[data-color="alert"] {
|
|
35
|
+
color: var(--recursica_brand_layer_elements_text_alert);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.root[data-color="success"] {
|
|
39
|
+
color: var(--recursica_brand_layer_elements_text_success);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/*
|
|
43
|
+
* Emphasis — bound to the theme's text-emphasis opacity tokens via the `data-emphasis` attribute.
|
|
44
|
+
* `high` (the default) resolves to solid opacity, so only `low` needs to dim the text.
|
|
45
|
+
*/
|
|
46
|
+
.root[data-emphasis="high"] {
|
|
47
|
+
opacity: var(--recursica_brand_text-emphasis_high);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.root[data-emphasis="low"] {
|
|
51
|
+
opacity: var(--recursica_brand_text-emphasis_low);
|
|
52
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -24,7 +24,7 @@ export * from "./Flex";
|
|
|
24
24
|
export * from "./FormControlLayout";
|
|
25
25
|
export * from "./FormControlWrapper";
|
|
26
26
|
export type { RecursicaFormControlWrapperProps } from "./FormControlWrapper";
|
|
27
|
-
export * from "./
|
|
27
|
+
export * from "./LayoutGrid";
|
|
28
28
|
export * from "./Group";
|
|
29
29
|
export * from "./Heading";
|
|
30
30
|
export * from "./HoverCard";
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { breakpointsFromRecManifest } from "./breakpointsFromRecManifest";
|
|
3
|
+
|
|
4
|
+
const grid = (width: Record<string, number>) =>
|
|
5
|
+
Object.fromEntries(Object.entries(width).map(([k, v]) => [k, { $value: v }]));
|
|
6
|
+
|
|
7
|
+
describe("breakpointsFromRecManifest", () => {
|
|
8
|
+
it("starts each grid at its min-width and places default after the widest max-width", () => {
|
|
9
|
+
const manifest = {
|
|
10
|
+
brand: {
|
|
11
|
+
"layout-grids": {
|
|
12
|
+
default: {},
|
|
13
|
+
mobile: grid({ "max-width": 480 }),
|
|
14
|
+
tablet: grid({ "min-width": 481, "max-width": 780 }),
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
expect(breakpointsFromRecManifest(manifest)).toEqual({
|
|
19
|
+
mobile: 0,
|
|
20
|
+
tablet: 481,
|
|
21
|
+
default: 781,
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("returns an empty object when default is the only grid", () => {
|
|
26
|
+
expect(
|
|
27
|
+
breakpointsFromRecManifest({
|
|
28
|
+
brand: { "layout-grids": { default: {} } },
|
|
29
|
+
}),
|
|
30
|
+
).toEqual({});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("throws for a non-default grid with no width", () => {
|
|
34
|
+
expect(() =>
|
|
35
|
+
breakpointsFromRecManifest({
|
|
36
|
+
brand: { "layout-grids": { default: {}, odd: {} } },
|
|
37
|
+
}),
|
|
38
|
+
).toThrow(/neither min-width nor max-width/);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
interface ManifestNumber {
|
|
2
|
+
$value?: unknown;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
interface ManifestLayoutGrid {
|
|
6
|
+
"min-width"?: ManifestNumber;
|
|
7
|
+
"max-width"?: ManifestNumber;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface ManifestWithLayoutGrids {
|
|
11
|
+
brand?: { "layout-grids"?: Record<string, unknown> };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function width(grid: ManifestLayoutGrid, key: "min-width" | "max-width") {
|
|
15
|
+
const value = grid[key]?.$value;
|
|
16
|
+
if (value === undefined) return undefined;
|
|
17
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`layout-grid ${key} must be a non-negative number, got ${String(value)}`,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Builds a `{ name: <start px> }` breakpoint values object from the manifest's layout grids, to
|
|
27
|
+
* merge into MUI's `createTheme({ breakpoints: { values } })`. Opt-in: nothing in the adapter
|
|
28
|
+
* calls this for you, and the result is a plain object you can edit.
|
|
29
|
+
*
|
|
30
|
+
* MUI breakpoints are mobile-first start widths, so each non-default grid maps to its
|
|
31
|
+
* `min-width`; a grid with only a `max-width` (e.g. `mobile`, up to 480px) starts at `0`. The
|
|
32
|
+
* `default` grid has no width of its own: when other grids exist it is included, named `default`,
|
|
33
|
+
* starting one pixel past the widest `max-width`. A manifest with only the `default` grid (the
|
|
34
|
+
* stock Forge export) returns `{}`. MUI's `values` replaces its defaults rather than merging, so
|
|
35
|
+
* spread MUI's own defaults first: `values: { ...createTheme().breakpoints.values, ...result }`.
|
|
36
|
+
*
|
|
37
|
+
* Throws for a non-default grid with neither width, for non-numeric widths, and when no other grid
|
|
38
|
+
* has a `max-width` for `default` to start after.
|
|
39
|
+
*/
|
|
40
|
+
export function breakpointsFromRecManifest(
|
|
41
|
+
manifest: ManifestWithLayoutGrids,
|
|
42
|
+
): Record<string, number> {
|
|
43
|
+
const grids = (manifest.brand?.["layout-grids"] ?? {}) as Record<
|
|
44
|
+
string,
|
|
45
|
+
ManifestLayoutGrid
|
|
46
|
+
>;
|
|
47
|
+
const entries: [string, number][] = [];
|
|
48
|
+
let widestMax: number | undefined;
|
|
49
|
+
for (const [name, grid] of Object.entries(grids)) {
|
|
50
|
+
if (name === "default") continue;
|
|
51
|
+
const min = width(grid, "min-width");
|
|
52
|
+
const max = width(grid, "max-width");
|
|
53
|
+
if (min === undefined && max === undefined) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`layout-grid "${name}" has neither min-width nor max-width`,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
if (max !== undefined && (widestMax === undefined || max > widestMax)) {
|
|
59
|
+
widestMax = max;
|
|
60
|
+
}
|
|
61
|
+
entries.push([name, min ?? 0]);
|
|
62
|
+
}
|
|
63
|
+
if (entries.length > 0) {
|
|
64
|
+
if (widestMax === undefined) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
'Cannot place the "default" layout-grid: no other grid has a max-width to start after',
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
entries.push(["default", widestMax + 1]);
|
|
70
|
+
}
|
|
71
|
+
return Object.fromEntries(entries.sort((a, b) => a[1] - b[1]));
|
|
72
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
|
-
import { Grid } from "./Grid";
|
|
4
|
-
import { Card } from "../Card/Card";
|
|
5
|
-
import { Text } from "../Text/Text";
|
|
6
|
-
|
|
7
|
-
type GridStoryProps = React.ComponentProps<typeof Grid>;
|
|
8
|
-
|
|
9
|
-
const meta: Meta<GridStoryProps> = {
|
|
10
|
-
title: "UI-Kit/Grid",
|
|
11
|
-
component: Grid,
|
|
12
|
-
tags: ["autodocs"],
|
|
13
|
-
parameters: {
|
|
14
|
-
docs: {
|
|
15
|
-
description: {
|
|
16
|
-
component:
|
|
17
|
-
"Grid is a 12-column (by default) responsive grid layout hand-composed from MUI's own Grid, providing column sizing, offsets, ordering, and breakpoint-based visibility using MUI's native prop names.",
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
controls: {
|
|
21
|
-
include: ["children", "spacing", "columns", "direction", "wrap"],
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
args: {
|
|
25
|
-
spacing: "rec-default",
|
|
26
|
-
columns: 12,
|
|
27
|
-
},
|
|
28
|
-
argTypes: {
|
|
29
|
-
spacing: {
|
|
30
|
-
control: "select",
|
|
31
|
-
options: [
|
|
32
|
-
"rec-none",
|
|
33
|
-
"rec-sm",
|
|
34
|
-
"rec-default",
|
|
35
|
-
"rec-md",
|
|
36
|
-
"rec-lg",
|
|
37
|
-
"rec-xl",
|
|
38
|
-
"rec-2xl",
|
|
39
|
-
],
|
|
40
|
-
description: "Space between columns",
|
|
41
|
-
},
|
|
42
|
-
columns: {
|
|
43
|
-
control: "number",
|
|
44
|
-
description: "Number of columns in each row",
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
export default meta;
|
|
50
|
-
|
|
51
|
-
type Story = StoryObj<GridStoryProps>;
|
|
52
|
-
|
|
53
|
-
const Swatch = ({ children }: { children: React.ReactNode }) => (
|
|
54
|
-
<Card>
|
|
55
|
-
<Card.Content>
|
|
56
|
-
<Text>{children}</Text>
|
|
57
|
-
</Card.Content>
|
|
58
|
-
</Card>
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
export const Default: Story = {
|
|
62
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
|
|
63
|
-
render: ({ withLayer, layer, ...args }: any) => (
|
|
64
|
-
<Grid {...args}>
|
|
65
|
-
<Grid.Col size={4}>
|
|
66
|
-
<Swatch>size 4</Swatch>
|
|
67
|
-
</Grid.Col>
|
|
68
|
-
<Grid.Col size={4}>
|
|
69
|
-
<Swatch>size 4</Swatch>
|
|
70
|
-
</Grid.Col>
|
|
71
|
-
<Grid.Col size={4}>
|
|
72
|
-
<Swatch>size 4</Swatch>
|
|
73
|
-
</Grid.Col>
|
|
74
|
-
</Grid>
|
|
75
|
-
),
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
export const ResponsiveSizes: Story = {
|
|
79
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
|
|
80
|
-
render: ({ withLayer, layer, ...args }: any) => (
|
|
81
|
-
<Grid {...args}>
|
|
82
|
-
<Grid.Col size={{ xs: 12, sm: 6, md: 3 }}>
|
|
83
|
-
<Swatch>xs 12 / sm 6 / md 3</Swatch>
|
|
84
|
-
</Grid.Col>
|
|
85
|
-
<Grid.Col size={{ xs: 12, sm: 6, md: 3 }}>
|
|
86
|
-
<Swatch>xs 12 / sm 6 / md 3</Swatch>
|
|
87
|
-
</Grid.Col>
|
|
88
|
-
<Grid.Col size={{ xs: 12, sm: 6, md: 3 }}>
|
|
89
|
-
<Swatch>xs 12 / sm 6 / md 3</Swatch>
|
|
90
|
-
</Grid.Col>
|
|
91
|
-
<Grid.Col size={{ xs: 12, sm: 6, md: 3 }}>
|
|
92
|
-
<Swatch>xs 12 / sm 6 / md 3</Swatch>
|
|
93
|
-
</Grid.Col>
|
|
94
|
-
</Grid>
|
|
95
|
-
),
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
export const Offset: Story = {
|
|
99
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
|
|
100
|
-
render: ({ withLayer, layer, ...args }: any) => (
|
|
101
|
-
<Grid {...args}>
|
|
102
|
-
<Grid.Col size={4} offset={4}>
|
|
103
|
-
<Swatch>size 4, offset 4</Swatch>
|
|
104
|
-
</Grid.Col>
|
|
105
|
-
<Grid.Col size={4}>
|
|
106
|
-
<Swatch>size 4</Swatch>
|
|
107
|
-
</Grid.Col>
|
|
108
|
-
</Grid>
|
|
109
|
-
),
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
export const Grow: Story = {
|
|
113
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
|
|
114
|
-
render: ({ withLayer, layer, ...args }: any) => (
|
|
115
|
-
<Grid {...args}>
|
|
116
|
-
<Grid.Col size={3}>
|
|
117
|
-
<Swatch>size 3</Swatch>
|
|
118
|
-
</Grid.Col>
|
|
119
|
-
<Grid.Col size={3}>
|
|
120
|
-
<Swatch>size 3</Swatch>
|
|
121
|
-
</Grid.Col>
|
|
122
|
-
<Grid.Col size="grow">
|
|
123
|
-
<Swatch>size "grow" (fills remaining space)</Swatch>
|
|
124
|
-
</Grid.Col>
|
|
125
|
-
</Grid>
|
|
126
|
-
),
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
export const CustomColumnCount: Story = {
|
|
130
|
-
args: {
|
|
131
|
-
columns: 6,
|
|
132
|
-
},
|
|
133
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
|
|
134
|
-
render: ({ withLayer, layer, ...args }: any) => (
|
|
135
|
-
<Grid {...args}>
|
|
136
|
-
<Grid.Col size={2}>
|
|
137
|
-
<Swatch>size 2 of 6</Swatch>
|
|
138
|
-
</Grid.Col>
|
|
139
|
-
<Grid.Col size={2}>
|
|
140
|
-
<Swatch>size 2 of 6</Swatch>
|
|
141
|
-
</Grid.Col>
|
|
142
|
-
<Grid.Col size={2}>
|
|
143
|
-
<Swatch>size 2 of 6</Swatch>
|
|
144
|
-
</Grid.Col>
|
|
145
|
-
</Grid>
|
|
146
|
-
),
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
export const VisibleHiddenFrom: Story = {
|
|
150
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
|
|
151
|
-
render: ({ withLayer, layer, ...args }: any) => (
|
|
152
|
-
<Grid {...args}>
|
|
153
|
-
<Grid.Col size={6} hiddenFrom="sm">
|
|
154
|
-
<Swatch>hidden from sm and up</Swatch>
|
|
155
|
-
</Grid.Col>
|
|
156
|
-
<Grid.Col size={6} visibleFrom="sm">
|
|
157
|
-
<Swatch>visible from sm and up</Swatch>
|
|
158
|
-
</Grid.Col>
|
|
159
|
-
</Grid>
|
|
160
|
-
),
|
|
161
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
// Placeholder for future Grid component tests. Add tests here if/when needed.
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Grid layout wrapper.
|
|
3
|
-
*
|
|
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.
|
|
23
|
-
*
|
|
24
|
-
* Like Flex, Stack, Group, and Container, this is a primitive layout component and
|
|
25
|
-
* does not use the `RecursicaOverStyled` gatekeeper — only the `sx` prop is stripped.
|
|
26
|
-
*/
|
|
27
|
-
import { forwardRef, type CSSProperties } from "react";
|
|
28
|
-
import { Grid as MuiGrid, type GridProps as MuiGridProps } from "@mui/material";
|
|
29
|
-
import {
|
|
30
|
-
SPACING_MAP,
|
|
31
|
-
type OmitSx,
|
|
32
|
-
filterSxProp,
|
|
33
|
-
type WithRecursicaSpacing,
|
|
34
|
-
} from "../../utils/filterStylingProps";
|
|
35
|
-
import styles from "./Grid.module.css";
|
|
36
|
-
|
|
37
|
-
type Breakpoint = "xs" | "sm" | "md" | "lg" | "xl";
|
|
38
|
-
|
|
39
|
-
const HIDDEN_FROM_CLASS: Record<Breakpoint, string> = {
|
|
40
|
-
xs: styles.hiddenFromXs,
|
|
41
|
-
sm: styles.hiddenFromSm,
|
|
42
|
-
md: styles.hiddenFromMd,
|
|
43
|
-
lg: styles.hiddenFromLg,
|
|
44
|
-
xl: styles.hiddenFromXl,
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
// "xs" intentionally absent: visible from the smallest breakpoint means never hidden.
|
|
48
|
-
const VISIBLE_FROM_CLASS: Partial<Record<Breakpoint, string>> = {
|
|
49
|
-
sm: styles.visibleFromSm,
|
|
50
|
-
md: styles.visibleFromMd,
|
|
51
|
-
lg: styles.visibleFromLg,
|
|
52
|
-
xl: styles.visibleFromXl,
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
// ============================================================
|
|
56
|
-
// GRID
|
|
57
|
-
// ============================================================
|
|
58
|
-
|
|
59
|
-
export type GridProps = WithRecursicaSpacing<
|
|
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
|
-
};
|
|
67
|
-
|
|
68
|
-
const GridBase = forwardRef<HTMLDivElement, GridProps>(function Grid(
|
|
69
|
-
{
|
|
70
|
-
children,
|
|
71
|
-
spacing = "rec-default",
|
|
72
|
-
justifyContent,
|
|
73
|
-
alignItems,
|
|
74
|
-
style,
|
|
75
|
-
...rest
|
|
76
|
-
},
|
|
77
|
-
ref,
|
|
78
|
-
) {
|
|
79
|
-
const safeProps = filterSxProp(rest as Record<string, unknown>);
|
|
80
|
-
const resolvedSpacing =
|
|
81
|
-
typeof spacing === "string" && spacing in SPACING_MAP
|
|
82
|
-
? SPACING_MAP[spacing as keyof typeof SPACING_MAP]
|
|
83
|
-
: spacing;
|
|
84
|
-
|
|
85
|
-
return (
|
|
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>
|
|
96
|
-
);
|
|
97
|
-
});
|
|
98
|
-
GridBase.displayName = "Grid";
|
|
99
|
-
|
|
100
|
-
// ============================================================
|
|
101
|
-
// GRID.COL
|
|
102
|
-
// ============================================================
|
|
103
|
-
|
|
104
|
-
export type GridColProps = WithRecursicaSpacing<
|
|
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
|
-
};
|
|
114
|
-
|
|
115
|
-
export const GridCol = forwardRef<HTMLDivElement, GridColProps>(
|
|
116
|
-
function GridCol(
|
|
117
|
-
{ children, order, visibleFrom, hiddenFrom, style, className, ...rest },
|
|
118
|
-
ref,
|
|
119
|
-
) {
|
|
120
|
-
const safeProps = filterSxProp(rest as Record<string, unknown>);
|
|
121
|
-
|
|
122
|
-
const visibilityClass = visibleFrom
|
|
123
|
-
? VISIBLE_FROM_CLASS[visibleFrom]
|
|
124
|
-
: hiddenFrom
|
|
125
|
-
? HIDDEN_FROM_CLASS[hiddenFrom]
|
|
126
|
-
: undefined;
|
|
127
|
-
|
|
128
|
-
const finalClassName = [styles.col, visibilityClass, className]
|
|
129
|
-
.filter(Boolean)
|
|
130
|
-
.join(" ");
|
|
131
|
-
|
|
132
|
-
return (
|
|
133
|
-
<MuiGrid
|
|
134
|
-
ref={ref}
|
|
135
|
-
className={finalClassName}
|
|
136
|
-
{...(safeProps as unknown as MuiGridProps)}
|
|
137
|
-
style={{ order, ...(style as CSSProperties) }}
|
|
138
|
-
>
|
|
139
|
-
{children}
|
|
140
|
-
</MuiGrid>
|
|
141
|
-
);
|
|
142
|
-
},
|
|
143
|
-
);
|
|
144
|
-
GridCol.displayName = "GridCol";
|
|
145
|
-
|
|
146
|
-
// ============================================================
|
|
147
|
-
// DOT NOTATION EXPORT
|
|
148
|
-
// ============================================================
|
|
149
|
-
|
|
150
|
-
type GridComponent = typeof GridBase & {
|
|
151
|
-
Col: typeof GridCol;
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
export const Grid = GridBase as GridComponent;
|
|
155
|
-
Grid.Col = GridCol;
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
# Grid Implementation Notes
|
|
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 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
17
|
-
|
|
18
|
-
**Story parity exclude**: for the same `size`/`span` reason above, this story is named `ResponsiveSizes` here vs. mantine-adapter's `ResponsiveSpans` — different story ids. `ui-kit-grid--responsive-spans` is marked `exclude: true` in `adapter-tester.config.json`'s `story parity with source of truth` check rather than renamed to match, per developer direction (2026-08-30).
|
|
19
|
-
|
|
20
|
-
**Update (2026-08-30):** mantine-adapter now also has its own `ResponsiveSizes` story (using its
|
|
21
|
-
own `span` prop, same breakpoints/content as this one) purely so this adapter's `responsive-sizes`
|
|
22
|
-
story id has a source-of-truth golden to diff against — see mantine-adapter's own
|
|
23
|
-
`GRID_IMPLEMENTATION_NOTES.md`. The `responsive-spans`/`responsive-sizes` naming split itself is
|
|
24
|
-
unchanged/still intentional; this just closes the one-directional story-parity gap.
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
# Grid - Usage Guide
|
|
2
|
-
|
|
3
|
-
This document describes how to integrate and use the `Grid` component in your projects using `@recursica/adapter-mui-v7`.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## 1. Import Reference
|
|
8
|
-
|
|
9
|
-
```tsx
|
|
10
|
-
import { Grid } from "@recursica/adapter-mui-v7";
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
## 2. Basic Example
|
|
16
|
-
|
|
17
|
-
```tsx
|
|
18
|
-
import React from "react";
|
|
19
|
-
import { Grid } from "@recursica/adapter-mui-v7";
|
|
20
|
-
|
|
21
|
-
export default function Demo() {
|
|
22
|
-
return (
|
|
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
|
-
</Grid>
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
## 3. Design System Integration
|
|
34
|
-
|
|
35
|
-
All Recursica components in the `@recursica/adapter-mui-v7` package adhere strictly to design system spacing, scaling, and behavior patterns.
|
|
36
|
-
|
|
37
|
-
> [!IMPORTANT]
|
|
38
|
-
>
|
|
39
|
-
> - **Anti-override protection**: `Grid` is a primitive layout component (see [OVERSTYLING.md](../../../OVERSTYLING.md)) — only the `sx` prop is stripped, everything else passes through freely without needing `overStyled`.
|
|
40
|
-
> - **No Direct Layers**: Do not pass a `layer` prop to this component. To place it on a specific visual layer, wrap it in a `<Layer layer={0|1|2|3}>` component natively.
|
|
41
|
-
> - **Variables and Theming**: Spacing is entirely determined by the `rec-*` token scale, mapped transparently to MUI's `spacing` value.
|
|
42
|
-
|
|
43
|
-
---
|
|
44
|
-
|
|
45
|
-
## 4. Key Integration Features & Constraints
|
|
46
|
-
|
|
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, ... }`).
|
|
51
|
-
- `offset` shifts a column by a number of columns.
|
|
52
|
-
- `order` accepts a fixed number to control a column's visual order (single value only, no responsive object).
|
|
53
|
-
- `visibleFrom`/`hiddenFrom` show or hide a column at the standard breakpoints (600/900/1200/1536px).
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./Grid";
|