@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.
Files changed (58) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/OVERSTYLING.md +1 -1
  3. package/SETUP.md +34 -3
  4. package/dist/adapter-mantine-v8.cjs +2 -2
  5. package/dist/adapter-mantine-v8.cjs.map +1 -1
  6. package/dist/adapter-mantine-v8.css +1 -1
  7. package/dist/adapter-mantine-v8.js +3171 -3010
  8. package/dist/adapter-mantine-v8.js.map +1 -1
  9. package/dist/index.d.ts +191 -164
  10. package/llms.txt +1 -1
  11. package/package.json +4 -4
  12. package/recursica_brand.json +2 -2
  13. package/recursica_tokens.json +2 -2
  14. package/recursica_ui-kit.json +2 -2
  15. package/recursica_variables_scoped.css +7 -3
  16. package/src/components/Checkbox/Checkbox.module.css +1 -3
  17. package/src/components/Container/USAGE.md +0 -1
  18. package/src/components/FileInput/FileInput.module.css +8 -0
  19. package/src/components/FileUpload/FileUpload.module.css +1 -0
  20. package/src/components/FileUpload/FileUpload.tsx +1 -0
  21. package/src/components/Flex/USAGE.md +2 -1
  22. package/src/components/Group/USAGE.md +2 -1
  23. package/src/components/LayoutGrid/LAYOUT_GRID_IMPLEMENTATION_NOTES.md +88 -0
  24. package/src/components/LayoutGrid/LayoutGrid.columns.dom.test.tsx +66 -0
  25. package/src/components/LayoutGrid/LayoutGrid.module.css +48 -0
  26. package/src/components/LayoutGrid/LayoutGrid.stories.tsx +61 -0
  27. package/src/components/LayoutGrid/LayoutGrid.test.tsx +1 -0
  28. package/src/components/LayoutGrid/LayoutGrid.tsx +309 -0
  29. package/src/components/LayoutGrid/USAGE.md +76 -0
  30. package/src/components/Menu/Menu.stories.tsx +0 -1
  31. package/src/components/Pagination/IMPLEMENTATION_NOTES.md +29 -8
  32. package/src/components/Pagination/Pagination.icons.tsx +0 -32
  33. package/src/components/Pagination/Pagination.manifest.dom.test.tsx +108 -0
  34. package/src/components/Pagination/Pagination.module.css +25 -155
  35. package/src/components/Pagination/Pagination.tsx +328 -197
  36. package/src/components/Pagination/USAGE.md +1 -0
  37. package/src/components/Panel/PANEL_IMPLEMENTATION_NOTES.md +8 -0
  38. package/src/components/Panel/Panel.module.css +27 -2
  39. package/src/components/Panel/Panel.stories.tsx +4 -4
  40. package/src/components/Panel/Panel.tsx +10 -1
  41. package/src/components/Radio/Radio.module.css +2 -2
  42. package/src/components/SegmentedControl/SegmentedControl.module.css +26 -0
  43. package/src/components/Stack/USAGE.md +2 -1
  44. package/src/components/Stepper/Stepper.module.css +15 -0
  45. package/src/components/Switch/Switch.module.css +5 -0
  46. package/src/components/Tabs/Tabs.module.css +5 -0
  47. package/src/components/Tabs/Tabs.tsx +1 -0
  48. package/src/components/index.ts +1 -1
  49. package/src/index.ts +1 -0
  50. package/src/utils/breakpointsFromRecManifest.test.ts +40 -0
  51. package/src/utils/breakpointsFromRecManifest.ts +75 -0
  52. package/src/utils/index.ts +1 -0
  53. package/src/components/Grid/GRID_IMPLEMENTATION_NOTES.md +0 -85
  54. package/src/components/Grid/Grid.module.css +0 -41
  55. package/src/components/Grid/Grid.stories.tsx +0 -200
  56. package/src/components/Grid/Grid.test.tsx +0 -1
  57. package/src/components/Grid/Grid.tsx +0 -179
  58. 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(false);
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(false);
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(false);
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(false);
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: row;
28
- flex-wrap: 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,
@@ -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 "./Grid/Grid";
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
@@ -1,3 +1,4 @@
1
1
  import "@recursica/adapter-common/style.css";
2
2
 
3
3
  export * from "./components";
4
+ export * from "./utils/breakpointsFromRecManifest";
@@ -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
+ }
@@ -1 +1,2 @@
1
1
  export * from "./copyToClipboard";
2
+ export * from "./breakpointsFromRecManifest";
@@ -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.