@recursica/mui-adapter 0.17.0 → 0.18.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 +11 -0
- package/OVERSTYLING.md +1 -1
- package/dist/mui-adapter.cjs +21 -21
- package/dist/mui-adapter.cjs.map +1 -1
- package/dist/mui-adapter.css +1 -1
- package/dist/mui-adapter.js +1915 -1793
- package/dist/mui-adapter.js.map +1 -1
- package/dist/src/components/Grid/Grid.d.ts +12 -0
- package/dist/src/components/Grid/index.d.ts +1 -0
- package/dist/src/components/index.d.ts +1 -0
- package/dist/src/index.d.ts +4 -0
- package/llms.txt +1 -0
- package/package.json +1 -1
- package/src/components/Grid/Grid.module.css +72 -0
- package/src/components/Grid/Grid.stories.tsx +185 -0
- package/src/components/Grid/Grid.tsx +240 -0
- package/src/components/Grid/IMPLEMENTATION_NOTES.md +16 -0
- package/src/components/Grid/USAGE.md +55 -0
- package/src/components/Grid/index.ts +1 -0
- package/src/components/index.ts +1 -0
- package/src/index.ts +2 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { GridProps as MuiGridProps } from '@mui/material';
|
|
2
|
+
import { OmitSx, WithRecursicaSpacing } from '../../utils/filterStylingProps';
|
|
3
|
+
import { RecursicaGridProps, RecursicaGridColProps } from '@recursica/adapter-common';
|
|
4
|
+
export type GridProps = WithRecursicaSpacing<OmitSx<Omit<MuiGridProps, "container" | "size" | "offset">> & RecursicaGridProps>;
|
|
5
|
+
declare const GridBase: import('react').ForwardRefExoticComponent<Omit<GridProps, "ref"> & import('react').RefAttributes<HTMLDivElement>>;
|
|
6
|
+
export type GridColProps = WithRecursicaSpacing<OmitSx<Omit<MuiGridProps, "container" | "size" | "offset">> & RecursicaGridColProps>;
|
|
7
|
+
export declare const GridCol: import('react').ForwardRefExoticComponent<Omit<GridColProps, "ref"> & import('react').RefAttributes<HTMLDivElement>>;
|
|
8
|
+
type GridComponent = typeof GridBase & {
|
|
9
|
+
Col: typeof GridCol;
|
|
10
|
+
};
|
|
11
|
+
export declare const Grid: GridComponent;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Grid';
|
package/dist/src/index.d.ts
CHANGED
|
@@ -4,6 +4,10 @@ export declare const Box: <C extends React.ElementType = "div">(props: rawCompon
|
|
|
4
4
|
ref?: React.ForwardedRef<any>;
|
|
5
5
|
}) => React.ReactElement | null;
|
|
6
6
|
export declare const Flex: import('react').ForwardRefExoticComponent<Omit<rawComponents.FlexProps, "ref"> & import('react').RefAttributes<HTMLDivElement>>;
|
|
7
|
+
export declare const Grid: import('react').ForwardRefExoticComponent<Omit<rawComponents.GridProps, "ref"> & import('react').RefAttributes<HTMLDivElement>> & {
|
|
8
|
+
Col: typeof rawComponents.GridCol;
|
|
9
|
+
};
|
|
10
|
+
export declare const GridCol: import('react').ForwardRefExoticComponent<Omit<rawComponents.GridColProps, "ref"> & import('react').RefAttributes<HTMLDivElement>>;
|
|
7
11
|
export declare const Group: import('react').ForwardRefExoticComponent<Omit<rawComponents.GroupProps, "ref"> & import('react').RefAttributes<HTMLDivElement>>;
|
|
8
12
|
export declare const Stack: import('react').ForwardRefExoticComponent<Omit<rawComponents.StackProps, "ref"> & import('react').RefAttributes<HTMLDivElement>>;
|
|
9
13
|
export declare const Container: import('react').ForwardRefExoticComponent<Omit<rawComponents.ContainerProps, "ref"> & import('react').RefAttributes<HTMLDivElement>>;
|
package/llms.txt
CHANGED
|
@@ -40,6 +40,7 @@ Each component has its own `USAGE.md` documenting its props, adapter-specific be
|
|
|
40
40
|
- [Flex](src/components/Flex/USAGE.md)
|
|
41
41
|
- [FormControlLayout](src/components/FormControlLayout/USAGE.md)
|
|
42
42
|
- [FormControlWrapper](src/components/FormControlWrapper/USAGE.md)
|
|
43
|
+
- [Grid](src/components/Grid/USAGE.md)
|
|
43
44
|
- [Group](src/components/Group/USAGE.md)
|
|
44
45
|
- [HoverCard](src/components/HoverCard/USAGE.md)
|
|
45
46
|
- [Label](src/components/Label/USAGE.md)
|
package/package.json
CHANGED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* HARDCODED VALUES:
|
|
3
|
+
* - Breakpoint pixel thresholds below (600/900/1200/1536) match MUI's own default
|
|
4
|
+
* theme.breakpoints (xs/sm/md/lg/xl). They drive `visibleFrom`/`hiddenFrom`, which
|
|
5
|
+
* have no native MUI Grid equivalent to hook into. If the consuming app customizes
|
|
6
|
+
* its MUI theme breakpoints, these thresholds will not follow that change —
|
|
7
|
+
* structural layout mechanics, not a design token.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
.root {
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.col {
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.hiddenFromXs {
|
|
17
|
+
display: none;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@media (min-width: 600px) {
|
|
21
|
+
.hiddenFromSm {
|
|
22
|
+
display: none;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@media (min-width: 900px) {
|
|
27
|
+
.hiddenFromMd {
|
|
28
|
+
display: none;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@media (min-width: 1200px) {
|
|
33
|
+
.hiddenFromLg {
|
|
34
|
+
display: none;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@media (min-width: 1536px) {
|
|
39
|
+
.hiddenFromXl {
|
|
40
|
+
display: none;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@media (max-width: 599.98px) {
|
|
45
|
+
.visibleFromSm,
|
|
46
|
+
.visibleFromMd,
|
|
47
|
+
.visibleFromLg,
|
|
48
|
+
.visibleFromXl {
|
|
49
|
+
display: none;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@media (max-width: 899.98px) {
|
|
54
|
+
.visibleFromMd,
|
|
55
|
+
.visibleFromLg,
|
|
56
|
+
.visibleFromXl {
|
|
57
|
+
display: none;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@media (max-width: 1199.98px) {
|
|
62
|
+
.visibleFromLg,
|
|
63
|
+
.visibleFromXl {
|
|
64
|
+
display: none;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@media (max-width: 1535.98px) {
|
|
69
|
+
.visibleFromXl {
|
|
70
|
+
display: none;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
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 Grid, providing column spans, offsets, ordering, and breakpoint-based visibility with the same API as the mantine-adapter's Grid/Grid.Col.",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
controls: {
|
|
21
|
+
include: ["children", "gap", "columns", "grow", "justify", "align"],
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
args: {
|
|
25
|
+
gap: "rec-default",
|
|
26
|
+
columns: 12,
|
|
27
|
+
grow: false,
|
|
28
|
+
},
|
|
29
|
+
argTypes: {
|
|
30
|
+
gap: {
|
|
31
|
+
control: "select",
|
|
32
|
+
options: [
|
|
33
|
+
"rec-none",
|
|
34
|
+
"rec-sm",
|
|
35
|
+
"rec-default",
|
|
36
|
+
"rec-md",
|
|
37
|
+
"rec-lg",
|
|
38
|
+
"rec-xl",
|
|
39
|
+
"rec-2xl",
|
|
40
|
+
],
|
|
41
|
+
description: "Gap between columns",
|
|
42
|
+
},
|
|
43
|
+
columns: {
|
|
44
|
+
control: "number",
|
|
45
|
+
description: "Number of columns in each row",
|
|
46
|
+
},
|
|
47
|
+
grow: {
|
|
48
|
+
control: "boolean",
|
|
49
|
+
description: "Columns in the last row expand to fill available space",
|
|
50
|
+
},
|
|
51
|
+
justify: {
|
|
52
|
+
control: "select",
|
|
53
|
+
options: [
|
|
54
|
+
"flex-start",
|
|
55
|
+
"center",
|
|
56
|
+
"flex-end",
|
|
57
|
+
"space-between",
|
|
58
|
+
"space-around",
|
|
59
|
+
],
|
|
60
|
+
description: "Justify-content property",
|
|
61
|
+
},
|
|
62
|
+
align: {
|
|
63
|
+
control: "select",
|
|
64
|
+
options: ["flex-start", "center", "flex-end", "stretch"],
|
|
65
|
+
description: "Align-items property",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export default meta;
|
|
71
|
+
|
|
72
|
+
type Story = StoryObj<GridStoryProps>;
|
|
73
|
+
|
|
74
|
+
const Swatch = ({ children }: { children: React.ReactNode }) => (
|
|
75
|
+
<Card>
|
|
76
|
+
<Card.Content>
|
|
77
|
+
<Text>{children}</Text>
|
|
78
|
+
</Card.Content>
|
|
79
|
+
</Card>
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
export const Default: Story = {
|
|
83
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
|
|
84
|
+
render: ({ withLayer, layer, ...args }: any) => (
|
|
85
|
+
<Grid {...args}>
|
|
86
|
+
<Grid.Col span={4}>
|
|
87
|
+
<Swatch>span 4</Swatch>
|
|
88
|
+
</Grid.Col>
|
|
89
|
+
<Grid.Col span={4}>
|
|
90
|
+
<Swatch>span 4</Swatch>
|
|
91
|
+
</Grid.Col>
|
|
92
|
+
<Grid.Col span={4}>
|
|
93
|
+
<Swatch>span 4</Swatch>
|
|
94
|
+
</Grid.Col>
|
|
95
|
+
</Grid>
|
|
96
|
+
),
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export const ResponsiveSpans: Story = {
|
|
100
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
|
|
101
|
+
render: ({ withLayer, layer, ...args }: any) => (
|
|
102
|
+
<Grid {...args}>
|
|
103
|
+
<Grid.Col span={{ base: 12, sm: 6, md: 3 }}>
|
|
104
|
+
<Swatch>base 12 / sm 6 / md 3</Swatch>
|
|
105
|
+
</Grid.Col>
|
|
106
|
+
<Grid.Col span={{ base: 12, sm: 6, md: 3 }}>
|
|
107
|
+
<Swatch>base 12 / sm 6 / md 3</Swatch>
|
|
108
|
+
</Grid.Col>
|
|
109
|
+
<Grid.Col span={{ base: 12, sm: 6, md: 3 }}>
|
|
110
|
+
<Swatch>base 12 / sm 6 / md 3</Swatch>
|
|
111
|
+
</Grid.Col>
|
|
112
|
+
<Grid.Col span={{ base: 12, sm: 6, md: 3 }}>
|
|
113
|
+
<Swatch>base 12 / sm 6 / md 3</Swatch>
|
|
114
|
+
</Grid.Col>
|
|
115
|
+
</Grid>
|
|
116
|
+
),
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export const Offset: Story = {
|
|
120
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
|
|
121
|
+
render: ({ withLayer, layer, ...args }: any) => (
|
|
122
|
+
<Grid {...args}>
|
|
123
|
+
<Grid.Col span={4} offset={4}>
|
|
124
|
+
<Swatch>span 4, offset 4</Swatch>
|
|
125
|
+
</Grid.Col>
|
|
126
|
+
<Grid.Col span={4}>
|
|
127
|
+
<Swatch>span 4</Swatch>
|
|
128
|
+
</Grid.Col>
|
|
129
|
+
</Grid>
|
|
130
|
+
),
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export const Grow: Story = {
|
|
134
|
+
args: {
|
|
135
|
+
grow: true,
|
|
136
|
+
},
|
|
137
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
|
|
138
|
+
render: ({ withLayer, layer, ...args }: any) => (
|
|
139
|
+
<Grid {...args}>
|
|
140
|
+
<Grid.Col span={3}>
|
|
141
|
+
<Swatch>span 3</Swatch>
|
|
142
|
+
</Grid.Col>
|
|
143
|
+
<Grid.Col span={3}>
|
|
144
|
+
<Swatch>span 3</Swatch>
|
|
145
|
+
</Grid.Col>
|
|
146
|
+
<Grid.Col span={3}>
|
|
147
|
+
<Swatch>span 3 (grows to fill row)</Swatch>
|
|
148
|
+
</Grid.Col>
|
|
149
|
+
</Grid>
|
|
150
|
+
),
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export const CustomColumnCount: Story = {
|
|
154
|
+
args: {
|
|
155
|
+
columns: 6,
|
|
156
|
+
},
|
|
157
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
|
|
158
|
+
render: ({ withLayer, layer, ...args }: any) => (
|
|
159
|
+
<Grid {...args}>
|
|
160
|
+
<Grid.Col span={2}>
|
|
161
|
+
<Swatch>span 2 of 6</Swatch>
|
|
162
|
+
</Grid.Col>
|
|
163
|
+
<Grid.Col span={2}>
|
|
164
|
+
<Swatch>span 2 of 6</Swatch>
|
|
165
|
+
</Grid.Col>
|
|
166
|
+
<Grid.Col span={2}>
|
|
167
|
+
<Swatch>span 2 of 6</Swatch>
|
|
168
|
+
</Grid.Col>
|
|
169
|
+
</Grid>
|
|
170
|
+
),
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export const VisibleHiddenFrom: Story = {
|
|
174
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
|
|
175
|
+
render: ({ withLayer, layer, ...args }: any) => (
|
|
176
|
+
<Grid {...args}>
|
|
177
|
+
<Grid.Col span={6} hiddenFrom="sm">
|
|
178
|
+
<Swatch>hidden from sm and up</Swatch>
|
|
179
|
+
</Grid.Col>
|
|
180
|
+
<Grid.Col span={6} visibleFrom="sm">
|
|
181
|
+
<Swatch>visible from sm and up</Swatch>
|
|
182
|
+
</Grid.Col>
|
|
183
|
+
</Grid>
|
|
184
|
+
),
|
|
185
|
+
};
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grid layout wrapper.
|
|
3
|
+
*
|
|
4
|
+
* NOTE: MUI merges "container" and "item" into a single Grid component (no separate
|
|
5
|
+
* Grid.Col in the underlying library, and no native `order`, `justifyContent`,
|
|
6
|
+
* `alignItems`, or breakpoint-visibility support). To preserve API parity with the
|
|
7
|
+
* mantine-adapter (Grid + Grid.Col dot notation, matching Mantine's own Grid/Grid.Col),
|
|
8
|
+
* this file splits MUI's single component back into two wrappers: Grid always renders
|
|
9
|
+
* MUI's `<Grid container>`, GridCol always renders MUI's `<Grid>` in item mode.
|
|
10
|
+
*
|
|
11
|
+
* Like Flex, Stack, Group, and Container, this is a primitive layout component and
|
|
12
|
+
* does not use the `RecursicaOverStyled` gatekeeper — only the `sx` prop is stripped.
|
|
13
|
+
*/
|
|
14
|
+
import {
|
|
15
|
+
createContext,
|
|
16
|
+
forwardRef,
|
|
17
|
+
useContext,
|
|
18
|
+
type CSSProperties,
|
|
19
|
+
} from "react";
|
|
20
|
+
import { Grid as MuiGrid, type GridProps as MuiGridProps } from "@mui/material";
|
|
21
|
+
import {
|
|
22
|
+
SPACING_MAP,
|
|
23
|
+
type OmitSx,
|
|
24
|
+
filterSxProp,
|
|
25
|
+
type WithRecursicaSpacing,
|
|
26
|
+
} from "../../utils/filterStylingProps";
|
|
27
|
+
import styles from "./Grid.module.css";
|
|
28
|
+
|
|
29
|
+
import {
|
|
30
|
+
type RecursicaGridProps,
|
|
31
|
+
type RecursicaGridColProps,
|
|
32
|
+
} from "@recursica/adapter-common";
|
|
33
|
+
|
|
34
|
+
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
|
+
|
|
103
|
+
const HIDDEN_FROM_CLASS: Partial<Record<Breakpoint, string>> = {
|
|
104
|
+
xs: styles.hiddenFromXs,
|
|
105
|
+
sm: styles.hiddenFromSm,
|
|
106
|
+
md: styles.hiddenFromMd,
|
|
107
|
+
lg: styles.hiddenFromLg,
|
|
108
|
+
xl: styles.hiddenFromXl,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// "xs"/"base" intentionally absent: visible from the smallest breakpoint means never hidden.
|
|
112
|
+
const VISIBLE_FROM_CLASS: Partial<Record<Breakpoint, string>> = {
|
|
113
|
+
sm: styles.visibleFromSm,
|
|
114
|
+
md: styles.visibleFromMd,
|
|
115
|
+
lg: styles.visibleFromLg,
|
|
116
|
+
xl: styles.visibleFromXl,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
function normalizeBreakpoint(bp: RecursicaBreakpoint): Breakpoint {
|
|
120
|
+
return bp === "base" ? "xs" : bp;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ============================================================
|
|
124
|
+
// GRID
|
|
125
|
+
// ============================================================
|
|
126
|
+
|
|
127
|
+
export type GridProps = WithRecursicaSpacing<
|
|
128
|
+
OmitSx<Omit<MuiGridProps, "container" | "size" | "offset">> &
|
|
129
|
+
RecursicaGridProps
|
|
130
|
+
>;
|
|
131
|
+
|
|
132
|
+
const GridBase = forwardRef<HTMLDivElement, GridProps>(function Grid(
|
|
133
|
+
{
|
|
134
|
+
children,
|
|
135
|
+
gap = "rec-default",
|
|
136
|
+
columns = 12,
|
|
137
|
+
grow = false,
|
|
138
|
+
justify,
|
|
139
|
+
align,
|
|
140
|
+
style,
|
|
141
|
+
...rest
|
|
142
|
+
},
|
|
143
|
+
ref,
|
|
144
|
+
) {
|
|
145
|
+
const safeProps = filterSxProp(rest as Record<string, unknown>);
|
|
146
|
+
const resolvedGap =
|
|
147
|
+
typeof gap === "string" && gap in SPACING_MAP
|
|
148
|
+
? SPACING_MAP[gap as keyof typeof SPACING_MAP]
|
|
149
|
+
: gap;
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<GridContext.Provider value={{ grow }}>
|
|
153
|
+
<MuiGrid
|
|
154
|
+
ref={ref}
|
|
155
|
+
container
|
|
156
|
+
columns={columns}
|
|
157
|
+
spacing={resolvedGap}
|
|
158
|
+
className={styles.root}
|
|
159
|
+
{...(safeProps as unknown as MuiGridProps)}
|
|
160
|
+
style={{
|
|
161
|
+
justifyContent: justify,
|
|
162
|
+
alignItems: align,
|
|
163
|
+
...(style as CSSProperties),
|
|
164
|
+
}}
|
|
165
|
+
>
|
|
166
|
+
{children}
|
|
167
|
+
</MuiGrid>
|
|
168
|
+
</GridContext.Provider>
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
GridBase.displayName = "Grid";
|
|
172
|
+
|
|
173
|
+
// ============================================================
|
|
174
|
+
// GRID.COL
|
|
175
|
+
// ============================================================
|
|
176
|
+
|
|
177
|
+
export type GridColProps = WithRecursicaSpacing<
|
|
178
|
+
OmitSx<Omit<MuiGridProps, "container" | "size" | "offset">> &
|
|
179
|
+
RecursicaGridColProps
|
|
180
|
+
>;
|
|
181
|
+
|
|
182
|
+
export const GridCol = forwardRef<HTMLDivElement, GridColProps>(
|
|
183
|
+
function GridCol(
|
|
184
|
+
{
|
|
185
|
+
children,
|
|
186
|
+
span,
|
|
187
|
+
offset,
|
|
188
|
+
order,
|
|
189
|
+
visibleFrom,
|
|
190
|
+
hiddenFrom,
|
|
191
|
+
style,
|
|
192
|
+
className,
|
|
193
|
+
...rest
|
|
194
|
+
},
|
|
195
|
+
ref,
|
|
196
|
+
) {
|
|
197
|
+
const { grow } = useContext(GridContext);
|
|
198
|
+
const safeProps = filterSxProp(rest as Record<string, unknown>);
|
|
199
|
+
const resolvedOrder = resolveOrder(order);
|
|
200
|
+
|
|
201
|
+
const visibilityClass = visibleFrom
|
|
202
|
+
? VISIBLE_FROM_CLASS[normalizeBreakpoint(visibleFrom)]
|
|
203
|
+
: hiddenFrom
|
|
204
|
+
? HIDDEN_FROM_CLASS[normalizeBreakpoint(hiddenFrom)]
|
|
205
|
+
: undefined;
|
|
206
|
+
|
|
207
|
+
const finalClassName = [styles.col, visibilityClass, className]
|
|
208
|
+
.filter(Boolean)
|
|
209
|
+
.join(" ");
|
|
210
|
+
|
|
211
|
+
return (
|
|
212
|
+
<MuiGrid
|
|
213
|
+
ref={ref}
|
|
214
|
+
size={mapSpan(span)}
|
|
215
|
+
offset={mapOffset(offset)}
|
|
216
|
+
className={finalClassName}
|
|
217
|
+
{...(safeProps as unknown as MuiGridProps)}
|
|
218
|
+
style={{
|
|
219
|
+
order: resolvedOrder,
|
|
220
|
+
...(grow ? { flexGrow: 1 } : {}),
|
|
221
|
+
...(style as CSSProperties),
|
|
222
|
+
}}
|
|
223
|
+
>
|
|
224
|
+
{children}
|
|
225
|
+
</MuiGrid>
|
|
226
|
+
);
|
|
227
|
+
},
|
|
228
|
+
);
|
|
229
|
+
GridCol.displayName = "GridCol";
|
|
230
|
+
|
|
231
|
+
// ============================================================
|
|
232
|
+
// DOT NOTATION EXPORT
|
|
233
|
+
// ============================================================
|
|
234
|
+
|
|
235
|
+
type GridComponent = typeof GridBase & {
|
|
236
|
+
Col: typeof GridCol;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
export const Grid = GridBase as GridComponent;
|
|
240
|
+
Grid.Col = GridCol;
|
|
@@ -0,0 +1,16 @@
|
|
|
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 exact same public API as the mantine-adapter (`Grid` + `Grid.Col` dot notation, matching Mantine's own `Grid`/`Grid.Col`), this file hand-composes two wrappers around that single underlying component: `Grid` always renders `<MuiGrid container>`, `Grid.Col` always renders `<MuiGrid>` in item mode. A `GridContext` (internal, not exported) carries the parent's `grow` flag down to `Grid.Col`, since MUI has no equivalent React context of its own for this and siblings can't share props directly.
|
|
4
|
+
|
|
5
|
+
Prop mapping details and divergences from a literal 1:1 MUI wrap:
|
|
6
|
+
|
|
7
|
+
- **`gap` → `spacing`**: MUI's own container-spacing prop is `spacing` (not `gutter`, which is Mantine's term). Resolved through the same `rec-*` → `SPACING_MAP` lookup used elsewhere in this adapter.
|
|
8
|
+
- **`span` keyword inversion**: Recursica's `"auto"` (fill remaining space) and `"content"` (size to content) are the _inverse_ of MUI's own `size` keywords — MUI's `"auto"` means size-to-content and `"grow"` means fill-remaining-space. `mapSpanValue()` translates between them so the public `span` prop matches Mantine's semantics exactly. Verified against `@mui/system`'s `generateGridSizeStyles` (`grow` → `flexGrow:1`, `auto` → `flexBasis:auto`).
|
|
9
|
+
- **`offset`**: Maps directly to MUI's own `offset` prop — same name, same responsive shape and semantics. No translation needed.
|
|
10
|
+
- **`"base"` breakpoint key**: Recursica's breakpoint objects (matching Mantine's `Grid.Col` shape) use `base` for the default/smallest value; MUI has no `"base"` key, only `xs` as its smallest breakpoint (`min-width: 0`). `remapBase()` renames `base` → `xs` (an explicit `xs` in the same object wins over `base` if both are present).
|
|
11
|
+
- **`order`**: MUI's Grid has no `order` prop at all. A fixed number is applied via inline `style.order` (inline style always wins the cascade, so this works regardless of MUI's generated styles). A responsive object is **not** fully implemented — building full per-breakpoint CSS custom-property cascading for a rarely-used prop wasn't judged worth the added CSS surface for v1. `resolveOrder()` currently just takes the smallest specified breakpoint's value and applies it as a single static order. This is a known, documented gap, not a silent one — revisit if a real per-breakpoint ordering need comes up.
|
|
12
|
+
- **`grow`**: MUI's Grid has no equivalent. Mantine's own implementation (and this one) relies on a CSS quirk: setting `flex-grow: 1` on every column in a row only visibly affects the _last, incomplete_ row — fully-filled rows have zero remaining space to grow into. Applied via inline `style.flexGrow` on `Grid.Col`, read from `GridContext`.
|
|
13
|
+
- **`visibleFrom` / `hiddenFrom`**: No native MUI Grid mechanism. 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, so this doesn't go through the token pipeline.
|
|
14
|
+
- **`justifyContent` / `alignItems`**: MUI's `Grid` does not register these as recognized style-system props the way `Box` does (confirmed by reading `@mui/system`'s `gridGenerator.js` — no generator function reads them), so passing them as bare props would silently do nothing. They're applied via inline `style` instead.
|
|
15
|
+
|
|
16
|
+
**Cross-adapter parity caveat**: `visibleFrom`/`hiddenFrom` (and the eventual full responsive `order`) 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.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Grid - Usage Guide
|
|
2
|
+
|
|
3
|
+
This document describes how to integrate and use the `Grid` component in your projects using `@recursica/mui-adapter`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Import Reference
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { Grid } from "@recursica/mui-adapter";
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 2. Basic Example
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import React from "react";
|
|
19
|
+
import { Grid } from "@recursica/mui-adapter";
|
|
20
|
+
|
|
21
|
+
export default function Demo() {
|
|
22
|
+
return (
|
|
23
|
+
<Grid gap="rec-default">
|
|
24
|
+
<Grid.Col span={6}>Half width</Grid.Col>
|
|
25
|
+
<Grid.Col span={{ base: 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/mui-adapter` 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)) and is exempt from the `RecursicaOverStyled` gatekeeper — only the `sx` prop is stripped, everything else passes through freely.
|
|
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
|
+
`Grid` and `Grid.Col` expose the same public API as the mantine-adapter's `Grid`/`Grid.Col` (same prop names and shapes), so code written against one adapter ports to the other without changes. Internally, MUI has no native `AppShell`-style Grid/Col split — MUI merges "container" and "item" into a single `Grid` component — so this adapter hand-composes `Grid` (always `container`) and `Grid.Col` (always item mode) from that single underlying component.
|
|
48
|
+
|
|
49
|
+
Notable mapping details:
|
|
50
|
+
|
|
51
|
+
- `gap` maps to MUI's own `spacing` prop (same concept as `gutter` on the mantine side).
|
|
52
|
+
- `span`'s `"auto"`/`"content"` keywords are the inverse of MUI's own `"grow"`/`"auto"` keywords — this adapter translates between them internally, so the public `span` values match Mantine's semantics exactly regardless of adapter.
|
|
53
|
+
- `offset` maps directly to MUI's own `offset` prop (same name, same shape).
|
|
54
|
+
- `order` is applied directly for a fixed number. A responsive object (`{ base, sm, md, ... }`) is **not** fully supported yet — the smallest specified breakpoint's value is applied as a single static order, since MUI's Grid has no native per-breakpoint `order` mechanism. See `IMPLEMENTATION_NOTES.md`.
|
|
55
|
+
- `visibleFrom`/`hiddenFrom` are implemented via a small CSS module using MUI's own default breakpoint pixel values (600/900/1200/1536), since MUI's Grid has no built-in breakpoint-visibility mechanism.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Grid";
|
package/src/components/index.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -7,6 +7,8 @@ export * from "@recursica/adapter-common";
|
|
|
7
7
|
// Expose unwrapped structural layout primitives to preserve their polymorphic types
|
|
8
8
|
export const Box = rawComponents.Box;
|
|
9
9
|
export const Flex = rawComponents.Flex;
|
|
10
|
+
export const Grid = rawComponents.Grid;
|
|
11
|
+
export const GridCol = rawComponents.GridCol;
|
|
10
12
|
export const Group = rawComponents.Group;
|
|
11
13
|
export const Stack = rawComponents.Stack;
|
|
12
14
|
export const Container = rawComponents.Container;
|