@recursica/adapter-mantine-v8 1.2.2 → 1.3.1
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 +23 -0
- package/OVERSTYLING.md +1 -1
- package/SETUP.md +34 -3
- package/dist/adapter-mantine-v8.cjs +2 -2
- package/dist/adapter-mantine-v8.cjs.map +1 -1
- package/dist/adapter-mantine-v8.css +1 -1
- package/dist/adapter-mantine-v8.js +3171 -3010
- package/dist/adapter-mantine-v8.js.map +1 -1
- package/dist/index.d.ts +191 -164
- package/llms.txt +1 -1
- package/package.json +4 -4
- package/recursica_brand.json +2 -2
- package/recursica_tokens.json +2 -2
- package/recursica_ui-kit.json +2 -2
- package/recursica_variables_scoped.css +7 -3
- 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/Group/USAGE.md +2 -1
- package/src/components/LayoutGrid/LAYOUT_GRID_IMPLEMENTATION_NOTES.md +88 -0
- package/src/components/LayoutGrid/LayoutGrid.columns.dom.test.tsx +66 -0
- package/src/components/LayoutGrid/LayoutGrid.module.css +48 -0
- package/src/components/LayoutGrid/LayoutGrid.stories.tsx +61 -0
- package/src/components/LayoutGrid/LayoutGrid.test.tsx +1 -0
- package/src/components/LayoutGrid/LayoutGrid.tsx +309 -0
- package/src/components/LayoutGrid/USAGE.md +76 -0
- package/src/components/Menu/Menu.stories.tsx +0 -1
- package/src/components/Pagination/IMPLEMENTATION_NOTES.md +29 -8
- package/src/components/Pagination/Pagination.icons.tsx +0 -32
- package/src/components/Pagination/Pagination.manifest.dom.test.tsx +108 -0
- package/src/components/Pagination/Pagination.module.css +25 -155
- package/src/components/Pagination/Pagination.tsx +328 -197
- package/src/components/Pagination/USAGE.md +1 -0
- package/src/components/Panel/PANEL_IMPLEMENTATION_NOTES.md +8 -0
- package/src/components/Panel/Panel.module.css +27 -2
- 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 +26 -0
- package/src/components/Stack/USAGE.md +2 -1
- package/src/components/Stepper/Stepper.module.css +15 -0
- package/src/components/Switch/Switch.module.css +5 -0
- package/src/components/Tabs/Tabs.module.css +5 -0
- package/src/components/Tabs/Tabs.tsx +1 -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 +75 -0
- package/src/utils/index.ts +1 -0
- package/src/components/Grid/GRID_IMPLEMENTATION_NOTES.md +0 -85
- package/src/components/Grid/Grid.module.css +0 -41
- package/src/components/Grid/Grid.stories.tsx +0 -200
- package/src/components/Grid/Grid.test.tsx +0 -1
- package/src/components/Grid/Grid.tsx +0 -179
- package/src/components/Grid/USAGE.md +0 -61
|
@@ -86,7 +86,7 @@ export const Default: Story = {
|
|
|
86
86
|
},
|
|
87
87
|
render: ({ wrapHeaderText, ...args }: PanelStoryArgs) => {
|
|
88
88
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
89
|
-
const [opened, setOpened] = useState(
|
|
89
|
+
const [opened, setOpened] = useState(true);
|
|
90
90
|
|
|
91
91
|
return (
|
|
92
92
|
<>
|
|
@@ -129,7 +129,7 @@ export const LeftPlacement: Story = {
|
|
|
129
129
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
130
130
|
render: ({ withLayer, layer, ...args }: PanelStoryArgs) => {
|
|
131
131
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
132
|
-
const [opened, setOpened] = useState(
|
|
132
|
+
const [opened, setOpened] = useState(true);
|
|
133
133
|
|
|
134
134
|
return (
|
|
135
135
|
<>
|
|
@@ -162,7 +162,7 @@ export const ScrollableContent: Story = {
|
|
|
162
162
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
163
163
|
render: ({ withLayer, layer, ...args }: PanelStoryArgs) => {
|
|
164
164
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
165
|
-
const [opened, setOpened] = useState(
|
|
165
|
+
const [opened, setOpened] = useState(true);
|
|
166
166
|
|
|
167
167
|
return (
|
|
168
168
|
<>
|
|
@@ -204,7 +204,7 @@ export const LongTitle: Story = {
|
|
|
204
204
|
},
|
|
205
205
|
render: ({ ...args }: PanelStoryArgs) => {
|
|
206
206
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
207
|
-
const [opened, setOpened] = useState(
|
|
207
|
+
const [opened, setOpened] = useState(true);
|
|
208
208
|
|
|
209
209
|
return (
|
|
210
210
|
<>
|
|
@@ -75,7 +75,15 @@ const PanelBase = function Panel({
|
|
|
75
75
|
// Bind CSS module classes to Mantine's internal classNames API
|
|
76
76
|
const mergedClassNames = mergeClassNames(
|
|
77
77
|
{
|
|
78
|
-
content: styles.content
|
|
78
|
+
content: `${styles.content} ${
|
|
79
|
+
placement === "right"
|
|
80
|
+
? styles.contentRight
|
|
81
|
+
: placement === "left"
|
|
82
|
+
? styles.contentLeft
|
|
83
|
+
: placement === "top"
|
|
84
|
+
? styles.contentTop
|
|
85
|
+
: styles.contentBottom
|
|
86
|
+
}`,
|
|
79
87
|
header: styles.header,
|
|
80
88
|
title: wrapHeaderText ? styles.titleTruncate : styles.title,
|
|
81
89
|
body: styles.body,
|
|
@@ -87,6 +95,7 @@ const PanelBase = function Panel({
|
|
|
87
95
|
return (
|
|
88
96
|
<MantineDrawer
|
|
89
97
|
{...(sanitizedProps as unknown as MantineDrawerProps)}
|
|
98
|
+
size="var(--recursica_ui-kit_components_panel_properties_max-width)"
|
|
90
99
|
position={placement} /* Recursica default: right; Mantine default: left */
|
|
91
100
|
keepMounted={keepMounted}
|
|
92
101
|
closeOnClickOutside={withCallerOverride(
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
|
|
25
25
|
/* SideBySide Variant */
|
|
26
26
|
.groupRoot[data-layout="side-by-side"] {
|
|
27
|
-
flex-direction:
|
|
28
|
-
flex-wrap:
|
|
27
|
+
flex-direction: column;
|
|
28
|
+
flex-wrap: nowrap;
|
|
29
29
|
gap: var(
|
|
30
30
|
--recursica_ui-kit_components_radio-button-group_variants_layouts_side-by-side_properties_gutter
|
|
31
31
|
);
|
|
@@ -133,6 +133,32 @@
|
|
|
133
133
|
);
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
/* Vertical: Mantine's separator is a horizontal line across the item, so the divider token
|
|
137
|
+
sets its height; and the container radius is capped to stay concentric with the pill items. */
|
|
138
|
+
.root[data-orientation="vertical"] .control::before {
|
|
139
|
+
width: auto;
|
|
140
|
+
height: var(
|
|
141
|
+
--recursica_ui-kit_components_segmented-control_properties_divider-size
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.root[data-orientation="vertical"] {
|
|
146
|
+
border-radius: min(
|
|
147
|
+
var(
|
|
148
|
+
--recursica_ui-kit_components_segmented-control_properties_border-radius
|
|
149
|
+
),
|
|
150
|
+
calc(
|
|
151
|
+
var(
|
|
152
|
+
--recursica_ui-kit_components_segmented-control-item_properties_item_height
|
|
153
|
+
) /
|
|
154
|
+
2 +
|
|
155
|
+
var(
|
|
156
|
+
--recursica_ui-kit_components_segmented-control_properties_padding-vertical
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
136
162
|
/* Indicator (the moving selected block) */
|
|
137
163
|
.root .indicator {
|
|
138
164
|
background-color: var(
|
|
@@ -36,10 +36,11 @@ All Recursica components in the `@recursica/adapter-mantine-v8` package adhere s
|
|
|
36
36
|
|
|
37
37
|
> [!IMPORTANT]
|
|
38
38
|
>
|
|
39
|
-
> - **Anti-override protection**: Rogues style injections (like inline `style` or arbitrary `className`) are automatically blocked by our prop layer unless `overStyled={true}` is explicitly provided.
|
|
40
39
|
> - **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
40
|
> - **Variables and Theming**: Styling is entirely determined by local CSS variables defined in `recursica_variables_scoped.css` and mapped in the component's CSS module.
|
|
42
41
|
|
|
42
|
+
> [!NOTE] > **Responsive props use your Mantine theme's breakpoints, not Forge's.** Keys such as `{ base, sm, md }` come from `theme.breakpoints`; Forge's layout grids switch via plain CSS `@media` and never edit the Mantine theme. Build your theme with `breakpointsFromRecManifest` so the two agree (see [LayoutGrid](../LayoutGrid/USAGE.md)).
|
|
43
|
+
|
|
43
44
|
---
|
|
44
45
|
|
|
45
46
|
## 4. Key Integration Features & Constraints
|
|
@@ -84,6 +84,21 @@
|
|
|
84
84
|
); /* Move gap inside the step so the separator line can stretch through it */
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
/* Mantine core adds a min-height, step-body inline margin and description margins on top of the
|
|
88
|
+
step-gap / indicator-label-gap tokens; zero them so spacing comes only from Recursica tokens. */
|
|
89
|
+
.root.vertical .step {
|
|
90
|
+
min-height: 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.root.vertical .stepBody {
|
|
94
|
+
margin-inline: 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.root.vertical .stepDescription {
|
|
98
|
+
margin-top: 0;
|
|
99
|
+
margin-bottom: 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
87
102
|
.root.vertical .step:last-of-type {
|
|
88
103
|
padding-bottom: 0;
|
|
89
104
|
}
|
|
@@ -185,6 +185,11 @@
|
|
|
185
185
|
cursor: inherit;
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
+
/* Mantine's InlineInput adds label-offset padding on top of the label-gap token. */
|
|
189
|
+
.root .label {
|
|
190
|
+
padding-inline: 0;
|
|
191
|
+
}
|
|
192
|
+
|
|
188
193
|
.root:has(input:disabled) .label {
|
|
189
194
|
color: var(
|
|
190
195
|
--recursica_ui-kit_components_switch-item_properties_colors_disabled-text
|
|
@@ -265,6 +265,11 @@
|
|
|
265
265
|
}
|
|
266
266
|
|
|
267
267
|
/* Icon support */
|
|
268
|
+
/* Mantine core adds an xs margin between the section and label on top of the element-gap token. */
|
|
269
|
+
.root .tabSection {
|
|
270
|
+
margin-inline: 0;
|
|
271
|
+
}
|
|
272
|
+
|
|
268
273
|
.root .tab svg {
|
|
269
274
|
width: var(--tabs-icon-size);
|
|
270
275
|
height: var(--tabs-icon-size);
|
|
@@ -37,6 +37,7 @@ const _Tabs = forwardRef<HTMLDivElement, TabsProps>(function Tabs(props, ref) {
|
|
|
37
37
|
{
|
|
38
38
|
list: styles.list,
|
|
39
39
|
tab: styles.tab,
|
|
40
|
+
tabSection: styles.tabSection,
|
|
40
41
|
panel: styles.panel,
|
|
41
42
|
},
|
|
42
43
|
restRecord.classNames as Partial<Record<string, string>> | undefined,
|
package/src/components/index.ts
CHANGED
|
@@ -23,7 +23,7 @@ export * from "./FileUpload/FileUpload";
|
|
|
23
23
|
export type { RecursicaFileUploadProps } from "./FileUpload/FileUpload";
|
|
24
24
|
export * from "./Flex/Flex";
|
|
25
25
|
export * from "./FormControlLayout/FormControlLayout";
|
|
26
|
-
export * from "./
|
|
26
|
+
export * from "./LayoutGrid/LayoutGrid";
|
|
27
27
|
export * from "./Group/Group";
|
|
28
28
|
export * from "./Heading/Heading";
|
|
29
29
|
export * from "./HoverCard/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: "0px",
|
|
20
|
+
tablet: "481px",
|
|
21
|
+
default: "781px",
|
|
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,75 @@
|
|
|
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" }` breakpoints object from the manifest's layout grids, ready to
|
|
27
|
+
* pass to Mantine's `createTheme({ breakpoints })`. Opt-in: nothing in the
|
|
28
|
+
* adapters calls this for you, and the result is a plain object you can edit.
|
|
29
|
+
*
|
|
30
|
+
* Mantine 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 `0px`. 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 `{}`, so the theme is left untouched.
|
|
35
|
+
*
|
|
36
|
+
* Throws for a non-default grid with neither width, for non-numeric widths, and when no other grid
|
|
37
|
+
* has a `max-width` for `default` to start after.
|
|
38
|
+
*/
|
|
39
|
+
export function breakpointsFromRecManifest(
|
|
40
|
+
manifest: ManifestWithLayoutGrids,
|
|
41
|
+
): Record<string, string> {
|
|
42
|
+
const grids = (manifest.brand?.["layout-grids"] ?? {}) as Record<
|
|
43
|
+
string,
|
|
44
|
+
ManifestLayoutGrid
|
|
45
|
+
>;
|
|
46
|
+
const entries: [string, number][] = [];
|
|
47
|
+
let widestMax: number | undefined;
|
|
48
|
+
for (const [name, grid] of Object.entries(grids)) {
|
|
49
|
+
if (name === "default") continue;
|
|
50
|
+
const min = width(grid, "min-width");
|
|
51
|
+
const max = width(grid, "max-width");
|
|
52
|
+
if (min === undefined && max === undefined) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`layout-grid "${name}" has neither min-width nor max-width`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
if (max !== undefined && (widestMax === undefined || max > widestMax)) {
|
|
58
|
+
widestMax = max;
|
|
59
|
+
}
|
|
60
|
+
entries.push([name, min ?? 0]);
|
|
61
|
+
}
|
|
62
|
+
if (entries.length > 0) {
|
|
63
|
+
if (widestMax === undefined) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
'Cannot place the "default" layout-grid: no other grid has a max-width to start after',
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
entries.push(["default", widestMax + 1]);
|
|
69
|
+
}
|
|
70
|
+
return Object.fromEntries(
|
|
71
|
+
entries
|
|
72
|
+
.sort((a, b) => a[1] - b[1])
|
|
73
|
+
.map(([name, start]) => [name, `${start}px`]),
|
|
74
|
+
);
|
|
75
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
# Grid Implementation Notes
|
|
2
|
-
|
|
3
|
-
The `Grid` component wraps Mantine's `Grid`/`Grid.Col`, but — unlike the other primitive layout
|
|
4
|
-
components (`Flex`, `Stack`, `Group`, `Container`) — it now has a formal Recursica prop contract
|
|
5
|
-
(`RecursicaGridProps` in `adapter-common`) wiring the design system's `layout-grids` tokens.
|
|
6
|
-
Earlier versions of this file documented the opposite policy ("no intrinsic design-system styles
|
|
7
|
-
required"); that was revisited on 2026-09-21 once Forge's export added `layout-grids` tokens
|
|
8
|
-
(`default_columns`, `default_column-gutter`, `default_row-gutter`, `default_margin` — see
|
|
9
|
-
`docs/migration/2026-09-21-forge-token-export.md`) with no equivalent anywhere else to consume
|
|
10
|
-
them.
|
|
11
|
-
|
|
12
|
-
## Only `columns` is an integrator-facing prop
|
|
13
|
-
|
|
14
|
-
Column-gutter, row-gutter, and margin are design-system-managed values (Forge-controlled, applied
|
|
15
|
-
via CSS variables), not per-instance settings — the integrator does not configure them. Only
|
|
16
|
-
`columns` is exposed as an override, the same pattern `Container.size` uses for its own
|
|
17
|
-
token-backed default. An earlier draft of this component exposed `columnGutter`/`rowGutter`/
|
|
18
|
-
`margin` as props too; that was reverted after review — see git history on this file for that
|
|
19
|
-
approach if it's ever needed again.
|
|
20
|
-
|
|
21
|
-
## How each token actually gets applied
|
|
22
|
-
|
|
23
|
-
- **`columns`** has no CSS-variable equivalent in Mantine — it computes each column's flex-basis
|
|
24
|
-
from a plain number. The design system default (6) is baked into `Grid.tsx` as a JS default
|
|
25
|
-
(`columns ?? 6`), not wired live through CSS. If the token's value changes, this line needs a
|
|
26
|
-
manual update. Callers may still override it (see `CustomColumnCount` story).
|
|
27
|
-
- **`columnGutter`** (the design token, not a prop) is passed straight to Mantine's own `gutter`
|
|
28
|
-
prop as `var(--recursica_brand_layout-grids_default_column-gutter)` — always, not
|
|
29
|
-
caller-configurable. This is the only axis Mantine's Grid natively parameterizes, so it drives
|
|
30
|
-
Mantine's own internal width/margin-inline math unmodified.
|
|
31
|
-
- **`rowGutter`/`margin`** (the design tokens) have no Mantine equivalent at all. `Grid.module.css`
|
|
32
|
-
reads them directly, always:
|
|
33
|
-
- `.root.root` applies `margin: var(--recursica_brand_layout-grids_default_margin)` (Mantine has
|
|
34
|
-
no root-level margin concept to conflict with).
|
|
35
|
-
- `.inner.inner` overrides only `margin-block` (not the `margin` shorthand) with
|
|
36
|
-
`var(--recursica_brand_layout-grids_default_row-gutter)` so it doesn't disturb Mantine's own
|
|
37
|
-
horizontal `margin-inline` math, which stays driven by `columnGutter`.
|
|
38
|
-
- `.col.col` overrides only `padding-block` for the same reason (`padding-inline` stays Mantine's
|
|
39
|
-
own, driven by `gutter`).
|
|
40
|
-
- All three use the doubled-class-selector trick (`.root.root`, not `.root`) to reliably beat
|
|
41
|
-
Mantine's own compiled single-class specificity regardless of stylesheet load order — same
|
|
42
|
-
idiom used elsewhere in this codebase (e.g. Chip).
|
|
43
|
-
|
|
44
|
-
## `Grid.Col`'s formal contract is scaffolded, not filled in yet
|
|
45
|
-
|
|
46
|
-
`RecursicaGridColProps` exists in `adapter-common`, and `Grid.tsx` here intersects `GridColProps`
|
|
47
|
-
with it — but as of 2026-09-22 it only contributes `children`. `span`, `order`, `visibleFrom`, and
|
|
48
|
-
`hiddenFrom` were all drafted as real contract fields during this work and then commented out
|
|
49
|
-
(Matt: get Grid merged first, come back to this later), so `Grid.Col` still types all four
|
|
50
|
-
straight off `MantineGridColProps`, same as before any of this started. No runtime change — this
|
|
51
|
-
is purely about where the types come from, not behavior.
|
|
52
|
-
|
|
53
|
-
What's queued up in `adapter-common/src/components/Grid/RecursicaGridColProps.ts` (commented,
|
|
54
|
-
with a TODO) for whenever this gets picked back up:
|
|
55
|
-
|
|
56
|
-
- `span`/`order`: both mantine-v8 and mui-v7 independently built matching versions of these with
|
|
57
|
-
no shared contract driving either — worth formalizing. `span` also resolves a naming mismatch:
|
|
58
|
-
Mantine calls the column-width prop `span`, mui-v7 calls it `size` — Matt already decided on
|
|
59
|
-
Mantine's naming (2026-09-22), so mui-v7 will need to rename `size` to `span` when this lands.
|
|
60
|
-
- `visibleFrom`/`hiddenFrom`: same "both adapters independently agree" signal, but blocked on a
|
|
61
|
-
real Recursica breakpoint naming convention — their `xs`/`sm`/`md`/`lg`/`xl` values are
|
|
62
|
-
Mantine's own naming, not Recursica's, and Recursica doesn't have its own yet. Don't just copy
|
|
63
|
-
Mantine's keys back in.
|
|
64
|
-
- `offset`: never added — still undiffed between the two adapters.
|
|
65
|
-
- Also note: `span`/`order`'s own per-breakpoint map variants (`{ xs: 6, sm: 3 }`) have the same
|
|
66
|
-
`xs`/`sm`/`md`/`lg`/`xl` naming gap as `visibleFrom`/`hiddenFrom` — worth resolving together.
|
|
67
|
-
|
|
68
|
-
## Brand-layer exemption
|
|
69
|
-
|
|
70
|
-
`layout-grids` tokens have no `ui-kit`-layer representation in Forge's model — a grid is a
|
|
71
|
-
page-layout primitive, not a "component," so there was never going to be a
|
|
72
|
-
`ui-kit_components_grid_*` indirection layer for it to go through. `Grid.module.css` reads
|
|
73
|
-
`brand_layout-grids_default_*` directly, with `recursica-allow-brand:` exemption headers
|
|
74
|
-
documenting why (see the analyzer's own exemption-system docs,
|
|
75
|
-
`packages/recursica-token-analyzer/README.md` in the monorepo).
|
|
76
|
-
|
|
77
|
-
## Stories
|
|
78
|
-
|
|
79
|
-
`ResponsiveSpans` and `ResponsiveSizes` explicitly override `columns={12}` — they test Mantine's
|
|
80
|
-
own breakpoint/span system at standard 12-column proportions and aren't meant to exercise the
|
|
81
|
-
design system's 6-column default. `ResponsiveSizes` exists purely so mui-adapter's
|
|
82
|
-
`ui-kit-grid--responsive-sizes` story id has a source-of-truth counterpart to diff against (Mantine's
|
|
83
|
-
`Grid.Col` has no `size` prop, only `span` — `ResponsiveSpans` already covers that). `Default`,
|
|
84
|
-
`Offset`, `Grow`, and `CustomColumnCount` all exercise the real 6-column default (or an explicit
|
|
85
|
-
override of it) and were resized from the old 12-column assumptions accordingly.
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/* HARDCODED VALUES:
|
|
2
|
-
* None.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/* Brand-layer exemptions (recursica-allow-brand) — layout-grid tokens have no ui-kit-layer
|
|
6
|
-
* representation in Forge's model (a grid is a page-layout primitive, not a "component"), so
|
|
7
|
-
* Grid is the one component that reads brand_layout-grids_* directly. See
|
|
8
|
-
* GRID_IMPLEMENTATION_NOTES.md.
|
|
9
|
-
* recursica-allow-brand: --recursica_brand_layout-grids_default_column-gutter
|
|
10
|
-
* recursica-allow-brand: --recursica_brand_layout-grids_default_row-gutter
|
|
11
|
-
* recursica-allow-brand: --recursica_brand_layout-grids_default_margin
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
.root.root {
|
|
15
|
-
/* Mantine handles column geometry (span, gutter width/margin-inline math) natively — the
|
|
16
|
-
column-gutter design token is applied via Grid.tsx's `gutter` prop, not here. `margin` has no
|
|
17
|
-
Mantine equivalent at all, so it's applied directly on the root element, always from the
|
|
18
|
-
design token — margin is design-system-managed, not an integrator-facing override. Doubled
|
|
19
|
-
selector beats Mantine's own single-class specificity regardless of stylesheet load order
|
|
20
|
-
(same idiom as Chip/other components). */
|
|
21
|
-
margin: var(--recursica_brand_layout-grids_default_margin);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
.inner.inner {
|
|
25
|
-
/* Mantine's own `.inner` margin is uniform on both axes, computed from `gutter`. Overriding just
|
|
26
|
-
the block (vertical) component here lets row spacing differ from column spacing without
|
|
27
|
-
touching Mantine's horizontal width/margin-inline math, which stays driven by `gutter`. Always
|
|
28
|
-
the design token — row-gutter is design-system-managed, not an integrator-facing override. */
|
|
29
|
-
margin-block: calc(
|
|
30
|
-
var(--recursica_brand_layout-grids_default_row-gutter) / -2
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
.col.col {
|
|
35
|
-
/* Mantine handles column sizing (span, offset, order, visibleFrom/hiddenFrom) and inline padding
|
|
36
|
-
(from `gutter`) natively. Overriding just the block (vertical) padding here applies the
|
|
37
|
-
row-gutter design token independently of column-gutter, always from the design token. */
|
|
38
|
-
padding-block: calc(
|
|
39
|
-
var(--recursica_brand_layout-grids_default_row-gutter) / 2
|
|
40
|
-
);
|
|
41
|
-
}
|
|
@@ -1,200 +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 responsive grid layout mapped to Mantine's Grid/Grid.Col, providing column spans, offsets, ordering, and breakpoint-based visibility. Defaults to the design system's own layout-grid tokens: 6 columns, with design-system-managed column-gutter/row-gutter/margin values applied automatically (not integrator-configurable).",
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
args: {
|
|
22
|
-
grow: false,
|
|
23
|
-
},
|
|
24
|
-
argTypes: {
|
|
25
|
-
columns: {
|
|
26
|
-
control: "number",
|
|
27
|
-
description:
|
|
28
|
-
"Number of columns in each row. Defaults to the design system's default column count (6).",
|
|
29
|
-
},
|
|
30
|
-
grow: {
|
|
31
|
-
control: "boolean",
|
|
32
|
-
description: "Columns in the last row expand to fill available space",
|
|
33
|
-
},
|
|
34
|
-
justify: {
|
|
35
|
-
control: "select",
|
|
36
|
-
options: [
|
|
37
|
-
"flex-start",
|
|
38
|
-
"center",
|
|
39
|
-
"flex-end",
|
|
40
|
-
"space-between",
|
|
41
|
-
"space-around",
|
|
42
|
-
],
|
|
43
|
-
description: "Justify-content property",
|
|
44
|
-
},
|
|
45
|
-
align: {
|
|
46
|
-
control: "select",
|
|
47
|
-
options: ["flex-start", "center", "flex-end", "stretch"],
|
|
48
|
-
description: "Align-items property",
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
export default meta;
|
|
54
|
-
|
|
55
|
-
type Story = StoryObj<GridStoryProps>;
|
|
56
|
-
|
|
57
|
-
const Swatch = ({ children }: { children: React.ReactNode }) => (
|
|
58
|
-
<Card>
|
|
59
|
-
<Card.Content>
|
|
60
|
-
<Text>{children}</Text>
|
|
61
|
-
</Card.Content>
|
|
62
|
-
</Card>
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
// Uses the design system default (6 columns) with no explicit `columns` override.
|
|
66
|
-
export const Default: Story = {
|
|
67
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
|
|
68
|
-
render: ({ withLayer, layer, ...args }: any) => (
|
|
69
|
-
<Grid {...args}>
|
|
70
|
-
<Grid.Col span={2}>
|
|
71
|
-
<Swatch>span 2 of 6 (default)</Swatch>
|
|
72
|
-
</Grid.Col>
|
|
73
|
-
<Grid.Col span={2}>
|
|
74
|
-
<Swatch>span 2 of 6 (default)</Swatch>
|
|
75
|
-
</Grid.Col>
|
|
76
|
-
<Grid.Col span={2}>
|
|
77
|
-
<Swatch>span 2 of 6 (default)</Swatch>
|
|
78
|
-
</Grid.Col>
|
|
79
|
-
</Grid>
|
|
80
|
-
),
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
// Explicitly overrides `columns` to 12 — this story exercises Mantine's own breakpoint/span
|
|
84
|
-
// system at standard 12-column proportions, independent of the design system's 6-column default.
|
|
85
|
-
export const ResponsiveSpans: Story = {
|
|
86
|
-
args: {
|
|
87
|
-
columns: 12,
|
|
88
|
-
},
|
|
89
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
|
|
90
|
-
render: ({ withLayer, layer, ...args }: any) => (
|
|
91
|
-
<Grid {...args}>
|
|
92
|
-
<Grid.Col span={{ xs: 12, sm: 6, md: 3 }}>
|
|
93
|
-
<Swatch>xs 12 / sm 6 / md 3</Swatch>
|
|
94
|
-
</Grid.Col>
|
|
95
|
-
<Grid.Col span={{ xs: 12, sm: 6, md: 3 }}>
|
|
96
|
-
<Swatch>xs 12 / sm 6 / md 3</Swatch>
|
|
97
|
-
</Grid.Col>
|
|
98
|
-
<Grid.Col span={{ xs: 12, sm: 6, md: 3 }}>
|
|
99
|
-
<Swatch>xs 12 / sm 6 / md 3</Swatch>
|
|
100
|
-
</Grid.Col>
|
|
101
|
-
<Grid.Col span={{ xs: 12, sm: 6, md: 3 }}>
|
|
102
|
-
<Swatch>xs 12 / sm 6 / md 3</Swatch>
|
|
103
|
-
</Grid.Col>
|
|
104
|
-
</Grid>
|
|
105
|
-
),
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
// Mirrors mui-adapter's `ResponsiveSizes` story (same breakpoints/content) under mantine's own
|
|
109
|
-
// prop name — Mantine's Grid.Col has no `size` prop, only `span`, which is what `ResponsiveSpans`
|
|
110
|
-
// above already exercises. This one exists purely so mui-adapter's `responsive-sizes` story id
|
|
111
|
-
// has a source-of-truth counterpart to diff against (see GRID_IMPLEMENTATION_NOTES.md). Same
|
|
112
|
-
// explicit 12-column override as `ResponsiveSpans`, for the same reason.
|
|
113
|
-
export const ResponsiveSizes: Story = {
|
|
114
|
-
args: {
|
|
115
|
-
columns: 12,
|
|
116
|
-
},
|
|
117
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
|
|
118
|
-
render: ({ withLayer, layer, ...args }: any) => (
|
|
119
|
-
<Grid {...args}>
|
|
120
|
-
<Grid.Col span={{ xs: 12, sm: 6, md: 3 }}>
|
|
121
|
-
<Swatch>xs 12 / sm 6 / md 3</Swatch>
|
|
122
|
-
</Grid.Col>
|
|
123
|
-
<Grid.Col span={{ xs: 12, sm: 6, md: 3 }}>
|
|
124
|
-
<Swatch>xs 12 / sm 6 / md 3</Swatch>
|
|
125
|
-
</Grid.Col>
|
|
126
|
-
<Grid.Col span={{ xs: 12, sm: 6, md: 3 }}>
|
|
127
|
-
<Swatch>xs 12 / sm 6 / md 3</Swatch>
|
|
128
|
-
</Grid.Col>
|
|
129
|
-
<Grid.Col span={{ xs: 12, sm: 6, md: 3 }}>
|
|
130
|
-
<Swatch>xs 12 / sm 6 / md 3</Swatch>
|
|
131
|
-
</Grid.Col>
|
|
132
|
-
</Grid>
|
|
133
|
-
),
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
export const Offset: Story = {
|
|
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={2} offset={2}>
|
|
141
|
-
<Swatch>span 2, offset 2 (of 6)</Swatch>
|
|
142
|
-
</Grid.Col>
|
|
143
|
-
<Grid.Col span={2}>
|
|
144
|
-
<Swatch>span 2 (of 6)</Swatch>
|
|
145
|
-
</Grid.Col>
|
|
146
|
-
</Grid>
|
|
147
|
-
),
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
export const Grow: Story = {
|
|
151
|
-
args: {
|
|
152
|
-
grow: true,
|
|
153
|
-
},
|
|
154
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
|
|
155
|
-
render: ({ withLayer, layer, ...args }: any) => (
|
|
156
|
-
<Grid {...args}>
|
|
157
|
-
<Grid.Col span={3}>
|
|
158
|
-
<Swatch>span 3 (of 6)</Swatch>
|
|
159
|
-
</Grid.Col>
|
|
160
|
-
<Grid.Col span={3}>
|
|
161
|
-
<Swatch>span 3 (of 6)</Swatch>
|
|
162
|
-
</Grid.Col>
|
|
163
|
-
<Grid.Col span={3}>
|
|
164
|
-
<Swatch>span 3 (grows to fill row)</Swatch>
|
|
165
|
-
</Grid.Col>
|
|
166
|
-
</Grid>
|
|
167
|
-
),
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
// Overrides the design system's default column count (6) to demonstrate a genuinely custom value.
|
|
171
|
-
export const CustomColumnCount: Story = {
|
|
172
|
-
args: {
|
|
173
|
-
columns: 4,
|
|
174
|
-
},
|
|
175
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
|
|
176
|
-
render: ({ withLayer, layer, ...args }: any) => (
|
|
177
|
-
<Grid {...args}>
|
|
178
|
-
<Grid.Col span={2}>
|
|
179
|
-
<Swatch>span 2 of 4</Swatch>
|
|
180
|
-
</Grid.Col>
|
|
181
|
-
<Grid.Col span={2}>
|
|
182
|
-
<Swatch>span 2 of 4</Swatch>
|
|
183
|
-
</Grid.Col>
|
|
184
|
-
</Grid>
|
|
185
|
-
),
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
export const VisibleHiddenFrom: Story = {
|
|
189
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
|
|
190
|
-
render: ({ withLayer, layer, ...args }: any) => (
|
|
191
|
-
<Grid {...args}>
|
|
192
|
-
<Grid.Col span={3} hiddenFrom="sm">
|
|
193
|
-
<Swatch>hidden from sm and up</Swatch>
|
|
194
|
-
</Grid.Col>
|
|
195
|
-
<Grid.Col span={3} visibleFrom="sm">
|
|
196
|
-
<Swatch>visible from sm and up</Swatch>
|
|
197
|
-
</Grid.Col>
|
|
198
|
-
</Grid>
|
|
199
|
-
),
|
|
200
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
// Placeholder for future Grid component tests. Add tests here if/when needed.
|