From 0d5e9dc99c79a141f0c1647b5b28c5b19b04edb9 Mon Sep 17 00:00:00 2001 From: Anders Johnsen Date: Thu, 25 Jun 2026 14:36:22 +0200 Subject: [PATCH] Grid-komponent --- .changeset/grid-component.md | 13 + indeks-css/css/layout/grid.css | 170 +++++++ indeks-css/css/layout/index.css | 1 + .../docs/komponenter/accessibility/grid.json | 75 +++ .../docs/komponenter/primitives/grid.mdx | 437 ++++++++++++++++++ indeks-docs/sidebars.ts | 2 +- .../src/theme/ReactLiveScope/index.tsx | 2 + indeks-eksempel/src/components/Layout.tsx | 1 + indeks-eksempel/src/main.tsx | 5 + .../src/pages/openPages/GridKomponent.tsx | 61 +++ indeks-react/lib/ui/layout/grid/Grid.test.tsx | 199 ++++++++ indeks-react/lib/ui/layout/grid/Grid.tsx | 124 +++++ indeks-react/lib/ui/layout/index.ts | 1 + indeks-react/lib/vite-env.d.ts | 9 + .../stories/layout/grid/Grid.stories.tsx | 210 +++++++++ indeks-storybook/tests/scanAll.dtest.ts | 1 + 16 files changed, 1310 insertions(+), 1 deletion(-) create mode 100644 .changeset/grid-component.md create mode 100644 indeks-css/css/layout/grid.css create mode 100644 indeks-docs/docs/komponenter/accessibility/grid.json create mode 100644 indeks-docs/docs/komponenter/primitives/grid.mdx create mode 100644 indeks-eksempel/src/pages/openPages/GridKomponent.tsx create mode 100644 indeks-react/lib/ui/layout/grid/Grid.test.tsx create mode 100644 indeks-react/lib/ui/layout/grid/Grid.tsx create mode 100644 indeks-storybook/stories/layout/grid/Grid.stories.tsx diff --git a/.changeset/grid-component.md b/.changeset/grid-component.md new file mode 100644 index 0000000..e6c08ca --- /dev/null +++ b/.changeset/grid-component.md @@ -0,0 +1,13 @@ +--- +"@sb1/indeks-css": minor +"@sb1/indeks-react": minor +--- + +Ny Grid-komponent for todimensjonal layout + +Grid er en layout-primitiv for å stable innhold i to dimensjoner. Den finnes som: +- Custom element: `` +- CSS-klasse: `.ix-grid` med modifier-klasser +- React-komponent: `` + +Støtter faste kolonner (1-12), responsiv layout (auto-fit/auto-fill), align/justify, og colspan/rowspan på barn. diff --git a/indeks-css/css/layout/grid.css b/indeks-css/css/layout/grid.css new file mode 100644 index 0000000..f6169b9 --- /dev/null +++ b/indeks-css/css/layout/grid.css @@ -0,0 +1,170 @@ +:where(ix-grid, .ix-grid) { + display: grid; + grid-template-columns: repeat(12, minmax(0, 1fr)); + gap: var(--ix-spacing-md); +} + +/* --- Kolonner --- */ + +:where(ix-grid[cols="1"]) { grid-template-columns: repeat(1, minmax(0, 1fr)); } +:where(ix-grid[cols="2"]) { grid-template-columns: repeat(2, minmax(0, 1fr)); } +:where(ix-grid[cols="3"]) { grid-template-columns: repeat(3, minmax(0, 1fr)); } +:where(ix-grid[cols="4"]) { grid-template-columns: repeat(4, minmax(0, 1fr)); } +:where(ix-grid[cols="5"]) { grid-template-columns: repeat(5, minmax(0, 1fr)); } +:where(ix-grid[cols="6"]) { grid-template-columns: repeat(6, minmax(0, 1fr)); } +:where(ix-grid[cols="7"]) { grid-template-columns: repeat(7, minmax(0, 1fr)); } +:where(ix-grid[cols="8"]) { grid-template-columns: repeat(8, minmax(0, 1fr)); } +:where(ix-grid[cols="9"]) { grid-template-columns: repeat(9, minmax(0, 1fr)); } +:where(ix-grid[cols="10"]) { grid-template-columns: repeat(10, minmax(0, 1fr)); } +:where(ix-grid[cols="11"]) { grid-template-columns: repeat(11, minmax(0, 1fr)); } +:where(ix-grid[cols="12"]) { grid-template-columns: repeat(12, minmax(0, 1fr)); } + +/* Auto-fit: tilpasser antall kolonner til tilgjengelig plass, strekker celler */ +:where(ix-grid[cols="auto-fit-xs"]) { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); } +:where(ix-grid[cols="auto-fit-sm"]) { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); } +:where(ix-grid[cols="auto-fit-md"]) { grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); } +:where(ix-grid[cols="auto-fit-lg"]) { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); } + +/* Auto-fill: tilpasser antall kolonner til tilgjengelig plass, beholder cellestørrelse */ +:where(ix-grid[cols="auto-fill-xs"]) { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); } +:where(ix-grid[cols="auto-fill-sm"]) { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); } +:where(ix-grid[cols="auto-fill-md"]) { grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); } +:where(ix-grid[cols="auto-fill-lg"]) { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); } + +/* --- Rader --- */ + +:where(ix-grid[rows="1"]) { grid-template-rows: repeat(1, minmax(0, 1fr)); } +:where(ix-grid[rows="2"]) { grid-template-rows: repeat(2, minmax(0, 1fr)); } +:where(ix-grid[rows="3"]) { grid-template-rows: repeat(3, minmax(0, 1fr)); } +:where(ix-grid[rows="4"]) { grid-template-rows: repeat(4, minmax(0, 1fr)); } + +/* --- Gap --- */ + +:where(ix-grid[gap="none"]) { gap: 0; } +:where(ix-grid[gap="2xs"]) { gap: var(--ix-spacing-2xs); } +:where(ix-grid[gap="xs"]) { gap: var(--ix-spacing-xs); } +:where(ix-grid[gap="sm"]) { gap: var(--ix-spacing-sm); } +:where(ix-grid[gap="md"]) { gap: var(--ix-spacing-md); } +:where(ix-grid[gap="lg"]) { gap: var(--ix-spacing-lg); } +:where(ix-grid[gap="xl"]) { gap: var(--ix-spacing-xl); } +:where(ix-grid[gap="2xl"]) { gap: var(--ix-spacing-2xl); } +:where(ix-grid[gap="3xl"]) { gap: var(--ix-spacing-3xl); } +:where(ix-grid[gap="4xl"]) { gap: var(--ix-spacing-4xl); } + +/* --- Align (align-items) --- */ + +:where(ix-grid[align="start"]) { align-items: start; } +:where(ix-grid[align="end"]) { align-items: end; } +:where(ix-grid[align="center"]) { align-items: center; } +:where(ix-grid[align="stretch"]) { align-items: stretch; } + +/* --- Justify (justify-items) --- */ + +:where(ix-grid[justify="start"]) { justify-items: start; } +:where(ix-grid[justify="end"]) { justify-items: end; } +:where(ix-grid[justify="center"]) { justify-items: center; } +:where(ix-grid[justify="stretch"]) { justify-items: stretch; } + +/* --- Inline --- */ + +:where(ix-grid[inline]) { + display: inline-grid; +} + +/* --- CSS-klasser for bruk på vanlige elementer --- */ + +:where(.ix-grid-cols-1) { grid-template-columns: repeat(1, minmax(0, 1fr)); } +:where(.ix-grid-cols-2) { grid-template-columns: repeat(2, minmax(0, 1fr)); } +:where(.ix-grid-cols-3) { grid-template-columns: repeat(3, minmax(0, 1fr)); } +:where(.ix-grid-cols-4) { grid-template-columns: repeat(4, minmax(0, 1fr)); } +:where(.ix-grid-cols-5) { grid-template-columns: repeat(5, minmax(0, 1fr)); } +:where(.ix-grid-cols-6) { grid-template-columns: repeat(6, minmax(0, 1fr)); } +:where(.ix-grid-cols-7) { grid-template-columns: repeat(7, minmax(0, 1fr)); } +:where(.ix-grid-cols-8) { grid-template-columns: repeat(8, minmax(0, 1fr)); } +:where(.ix-grid-cols-9) { grid-template-columns: repeat(9, minmax(0, 1fr)); } +:where(.ix-grid-cols-10) { grid-template-columns: repeat(10, minmax(0, 1fr)); } +:where(.ix-grid-cols-11) { grid-template-columns: repeat(11, minmax(0, 1fr)); } +:where(.ix-grid-cols-12) { grid-template-columns: repeat(12, minmax(0, 1fr)); } + +:where(.ix-grid-auto-fit-xs) { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); } +:where(.ix-grid-auto-fit-sm) { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); } +:where(.ix-grid-auto-fit-md) { grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); } +:where(.ix-grid-auto-fit-lg) { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); } + +:where(.ix-grid-auto-fill-xs) { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); } +:where(.ix-grid-auto-fill-sm) { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); } +:where(.ix-grid-auto-fill-md) { grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); } +:where(.ix-grid-auto-fill-lg) { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); } + +:where(.ix-grid-rows-1) { grid-template-rows: repeat(1, minmax(0, 1fr)); } +:where(.ix-grid-rows-2) { grid-template-rows: repeat(2, minmax(0, 1fr)); } +:where(.ix-grid-rows-3) { grid-template-rows: repeat(3, minmax(0, 1fr)); } +:where(.ix-grid-rows-4) { grid-template-rows: repeat(4, minmax(0, 1fr)); } + +:where(.ix-grid-align-start) { align-items: start; } +:where(.ix-grid-align-end) { align-items: end; } +:where(.ix-grid-align-center) { align-items: center; } +:where(.ix-grid-align-stretch) { align-items: stretch; } + +:where(.ix-grid-justify-start) { justify-items: start; } +:where(.ix-grid-justify-end) { justify-items: end; } +:where(.ix-grid-justify-center) { justify-items: center; } +:where(.ix-grid-justify-stretch) { justify-items: stretch; } + +:where(.ix-inline-grid) { + display: inline-grid; +} + +/* --- Col-span (settes på barn via attributt eller klasse) --- */ + +:where(ix-grid, .ix-grid) > :where([colspan="1"]), :where(.ix-col-span-1) { grid-column: span 1 / span 1; } +:where(ix-grid, .ix-grid) > :where([colspan="2"]), :where(.ix-col-span-2) { grid-column: span 2 / span 2; } +:where(ix-grid, .ix-grid) > :where([colspan="3"]), :where(.ix-col-span-3) { grid-column: span 3 / span 3; } +:where(ix-grid, .ix-grid) > :where([colspan="4"]), :where(.ix-col-span-4) { grid-column: span 4 / span 4; } +:where(ix-grid, .ix-grid) > :where([colspan="5"]), :where(.ix-col-span-5) { grid-column: span 5 / span 5; } +:where(ix-grid, .ix-grid) > :where([colspan="6"]), :where(.ix-col-span-6) { grid-column: span 6 / span 6; } +:where(ix-grid, .ix-grid) > :where([colspan="7"]), :where(.ix-col-span-7) { grid-column: span 7 / span 7; } +:where(ix-grid, .ix-grid) > :where([colspan="8"]), :where(.ix-col-span-8) { grid-column: span 8 / span 8; } +:where(ix-grid, .ix-grid) > :where([colspan="9"]), :where(.ix-col-span-9) { grid-column: span 9 / span 9; } +:where(ix-grid, .ix-grid) > :where([colspan="10"]), :where(.ix-col-span-10) { grid-column: span 10 / span 10; } +:where(ix-grid, .ix-grid) > :where([colspan="11"]), :where(.ix-col-span-11) { grid-column: span 11 / span 11; } +:where(ix-grid, .ix-grid) > :where([colspan="12"]), :where(.ix-col-span-12) { grid-column: span 12 / span 12; } +:where(ix-grid, .ix-grid) > :where([colspan="full"]), :where(.ix-col-span-full) { grid-column: 1 / -1; } + +/* --- Row-span (settes på barn via attributt eller klasse) --- */ + +:where(ix-grid, .ix-grid) > :where([rowspan="1"]), :where(.ix-row-span-1) { grid-row: span 1 / span 1; } +:where(ix-grid, .ix-grid) > :where([rowspan="2"]), :where(.ix-row-span-2) { grid-row: span 2 / span 2; } +:where(ix-grid, .ix-grid) > :where([rowspan="3"]), :where(.ix-row-span-3) { grid-row: span 3 / span 3; } +:where(ix-grid, .ix-grid) > :where([rowspan="4"]), :where(.ix-row-span-4) { grid-row: span 4 / span 4; } +:where(ix-grid, .ix-grid) > :where([rowspan="full"]), :where(.ix-row-span-full) { grid-row: 1 / -1; } + +/* --- Col-start/end (settes på barn) --- */ + +:where(.ix-col-start-1) { grid-column-start: 1; } +:where(.ix-col-start-2) { grid-column-start: 2; } +:where(.ix-col-start-3) { grid-column-start: 3; } +:where(.ix-col-start-4) { grid-column-start: 4; } +:where(.ix-col-start-5) { grid-column-start: 5; } +:where(.ix-col-start-6) { grid-column-start: 6; } +:where(.ix-col-start-7) { grid-column-start: 7; } +:where(.ix-col-start-8) { grid-column-start: 8; } +:where(.ix-col-start-9) { grid-column-start: 9; } +:where(.ix-col-start-10) { grid-column-start: 10; } +:where(.ix-col-start-11) { grid-column-start: 11; } +:where(.ix-col-start-12) { grid-column-start: 12; } +:where(.ix-col-start-13) { grid-column-start: 13; } + +:where(.ix-col-end-1) { grid-column-end: 1; } +:where(.ix-col-end-2) { grid-column-end: 2; } +:where(.ix-col-end-3) { grid-column-end: 3; } +:where(.ix-col-end-4) { grid-column-end: 4; } +:where(.ix-col-end-5) { grid-column-end: 5; } +:where(.ix-col-end-6) { grid-column-end: 6; } +:where(.ix-col-end-7) { grid-column-end: 7; } +:where(.ix-col-end-8) { grid-column-end: 8; } +:where(.ix-col-end-9) { grid-column-end: 9; } +:where(.ix-col-end-10) { grid-column-end: 10; } +:where(.ix-col-end-11) { grid-column-end: 11; } +:where(.ix-col-end-12) { grid-column-end: 12; } +:where(.ix-col-end-13) { grid-column-end: 13; } diff --git a/indeks-css/css/layout/index.css b/indeks-css/css/layout/index.css index cdfd793..4974732 100644 --- a/indeks-css/css/layout/index.css +++ b/indeks-css/css/layout/index.css @@ -1 +1,2 @@ +@import './grid.css'; @import './stack.css'; diff --git a/indeks-docs/docs/komponenter/accessibility/grid.json b/indeks-docs/docs/komponenter/accessibility/grid.json new file mode 100644 index 0000000..401fc60 --- /dev/null +++ b/indeks-docs/docs/komponenter/accessibility/grid.json @@ -0,0 +1,75 @@ +{ + "component": "Grid (ix-grid / Grid)", + "lastReviewed": "2026-06-17", + "consumerResponsibilities": [ + { + "summary": "Bruk semantiske elementer når konteksten krever det", + "details": "ix-grid er et ukjent HTML-element uten semantisk rolle — tilsvarende
. Nettleseren injiserer ingen ARIA. Bruk et semantisk native element med ix-grid-*-klasser (eller as-prop i React) når innholdet krever det — f.eks.
for innholdsseksjoner,
    for lister.", + "wcag": ["1.3.1"] + }, + { + "summary": "Pass på DOM-rekkefølgen ved komplekse grid-oppsett", + "details": "CSS Grid kan endre visuell rekkefølge uten å endre DOM-rekkefølgen. Skjermlesere og tastaturnavigasjon følger DOM. Sørg for at DOM-rekkefølgen gir mening uavhengig av visuell presentasjon.", + "wcag": ["1.3.2", "2.4.3"] + } + ], + "handled": [], + "issues": [], + "notRelevant": [ + { "id": "1.1.1", "reason": "Ingen bilder eller ikke-tekstlig innhold i selve layout-elementet." }, + { "id": "1.2.1" }, + { "id": "1.2.2" }, + { "id": "1.2.3" }, + { "id": "1.2.4" }, + { "id": "1.2.5" }, + { "id": "1.3.3", "reason": "Ingen sensoriske karakteristikker brukes for å formidle informasjon." }, + { "id": "1.3.4", "reason": "Ingen orientasjonslåsing." }, + { "id": "1.3.5", "reason": "Ingen skjemafelt." }, + { "id": "1.4.1", "reason": "Farge brukes ikke for å formidle informasjon." }, + { "id": "1.4.2", "reason": "Ingen lyd." }, + { "id": "1.4.3", "reason": "Ingen tekst i selve layout-elementet." }, + { "id": "1.4.4", "reason": "Ingen tekst i komponenten. Layout bruker relative enheter og skalerer korrekt." }, + { "id": "1.4.5", "reason": "Ingen bilder av tekst." }, + { "id": "1.4.10", "reason": "Grid-layout med auto-fit/auto-fill flyter naturlig ved smal viewport." }, + { "id": "1.4.11", "reason": "Ingen UI-komponenter med krav til ikke-tekstlig kontrast." }, + { "id": "1.4.12", "reason": "Ingen tekst i selve elementet." }, + { "id": "1.4.13", "reason": "Ingen hover/fokus-innhold." }, + { "id": "2.1.1", "reason": "Rent layout-element — ikke fokuserbart." }, + { "id": "2.1.2", "reason": "Ingen fokus-felle." }, + { "id": "2.1.4", "reason": "Ingen hurtigtaster." }, + { "id": "2.2.1", "reason": "Ingen tidsfrister." }, + { "id": "2.2.2", "reason": "Ingen bevegelig eller autooppdatert innhold." }, + { "id": "2.3.1", "reason": "Ingen blinking eller flimring." }, + { "id": "2.4.1", "reason": "Ingen blokker som må hoppes over." }, + { "id": "2.4.2", "reason": "Ikke en side-komponent." }, + { "id": "2.4.4", "reason": "Ingen lenker i komponenten." }, + { "id": "2.4.5", "reason": "Ikke en navigasjonsstruktur." }, + { "id": "2.4.6", "reason": "Ingen overskrifter eller labeler i komponenten." }, + { "id": "2.4.7", "reason": "Ikke fokuserbar." }, + { "id": "2.4.11", "reason": "Ikke fokuserbar." }, + { "id": "2.5.1", "reason": "Ingen peker-gester." }, + { "id": "2.5.2", "reason": "Ingen peker-kanselleringslogikk." }, + { "id": "2.5.3", "reason": "Ingen synlig label." }, + { "id": "2.5.4", "reason": "Ingen bevegelsesbasert aktivering." }, + { "id": "2.5.6", "reason": "Ingen begrensning på input-modalitet." }, + { "id": "2.5.7", "reason": "Ingen draing-funksjonalitet." }, + { "id": "2.5.8", "reason": "Ikke et interaktivt mål." }, + { "id": "3.1.1", "reason": "Ikke en side-komponent." }, + { "id": "3.1.2", "reason": "Ingen språkendringer." }, + { "id": "3.2.1", "reason": "Ikke fokuserbar." }, + { "id": "3.2.2", "reason": "Ingen input." }, + { "id": "3.2.3", "reason": "Ingen navigasjon." }, + { "id": "3.2.4", "reason": "Ingen gjentagende komponenter med inkonsistent identifisering." }, + { "id": "3.2.6", "reason": "Ingen hjelp-funksjonalitet." }, + { "id": "3.3.1", "reason": "Ingen skjemavalidering." }, + { "id": "3.3.2", "reason": "Ingen skjemafelt." }, + { "id": "3.3.3", "reason": "Ingen feilforslag." }, + { "id": "3.3.4", "reason": "Ingen juridisk forpliktende handlinger." }, + { "id": "3.3.7", "reason": "Ingen redundant input." }, + { "id": "3.3.8", "reason": "Ingen tilgjengelighetsautentisering." }, + { "id": "4.1.2", "reason": "Ikke et interaktivt element — ingen rolle, navn eller tilstand å eksponere." }, + { "id": "4.1.3", "reason": "Ingen statusmeldinger." } + ], + "keyboard": [], + "screenReader": [] +} diff --git a/indeks-docs/docs/komponenter/primitives/grid.mdx b/indeks-docs/docs/komponenter/primitives/grid.mdx new file mode 100644 index 0000000..b6a7eb4 --- /dev/null +++ b/indeks-docs/docs/komponenter/primitives/grid.mdx @@ -0,0 +1,437 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import AccessibilityTable from '@site/src/components/AccessibilityTable'; +import a11yData from '../accessibility/grid.json'; + +# Grid + +Grid er en layout-primitiv for å stable innhold i to dimensjoner — vertikalt og horisontalt. Den fungerer både som custom element (``) og som CSS-klasse (`.ix-grid`). + +## Egnet til + +- Layout i to dimensjoner — vertikalt og horisontalt samtidig +- Responsiv layout med automatisk tilpasning til tilgjengelig plass (`auto-fit`, `auto-fill`) +- Jevnt fordelte kolonner med kontrollert mellomrom +- Layout både på sidenivå og internt i komponenter eller seksjoner + +## Uegnet til + +- Layout i kun én dimensjon — bruk [Stack](./stack) i stedet +- Kompleks layout der du trenger ulik kolonnestørrelse per rad + +## Kom i gang + +`ix-grid` har 12 kolonner og `md` gap som standard. Bruk `cols`- og `gap`-attributtene (eller props) for å overstyre. + + + + +Grid finnes i to bruksformer i HTML: + +| Form | Eksempel | Styring | +|------|---------|---------| +| **Custom element** | `` | Attributter direkte på elementet | +| **CSS-klasse** | `
    ` | Modifier-klasser — attributter virker ikke her | + +`` bruker attributter: + +```jsx live + +
    1
    +
    2
    +
    3
    +
    4
    +
    5
    +
    6
    +
    +``` + +På vanlige elementer bruker du CSS-klasser — se [CSS-klasser for containeren](#css-klasser-for-containeren) i API-seksjonen. + + + + +```tsx live + +
    1
    +
    2
    +
    3
    +
    4
    +
    5
    +
    6
    +
    +``` + +
    + + +## Eksempler + +### Kolonner + + + + +```jsx live + +
    1
    +
    2
    +
    3
    +
    4
    +
    +``` + +```jsx live + +
    1
    +
    2
    +
    3
    +
    4
    +
    5
    +
    6
    +
    7
    +
    8
    +
    +``` + +
    + + +```tsx live + + +
    1
    +
    2
    +
    3
    +
    4
    +
    + +
    1
    +
    2
    +
    3
    +
    4
    +
    5
    +
    6
    +
    7
    +
    8
    +
    +
    +``` + +
    +
    + +### Auto-fit — responsiv layout + +`auto-fit` tilpasser antall kolonner til tilgjengelig plass og strekker cellene for å fylle hele bredden. + + + + +```jsx live + +
    Auto 1
    +
    Auto 2
    +
    Auto 3
    +
    Auto 4
    +
    Auto 5
    +
    +``` + +
    + + +```tsx live + +
    Auto 1
    +
    Auto 2
    +
    Auto 3
    +
    Auto 4
    +
    Auto 5
    +
    +``` + +
    +
    + +### Auto-fill — behold cellestørrelse + +`auto-fill` tilpasser antall kolonner, men beholder cellestørrelsen i stedet for å strekke dem. + + + + +```jsx live + +
    Fill 1
    +
    Fill 2
    +
    Fill 3
    +
    +``` + +
    + + +```tsx live + +
    Fill 1
    +
    Fill 2
    +
    Fill 3
    +
    +``` + +
    +
    + +### Col-span — ulik bredde per celle + +Bruk `colspan`-attributt eller `Grid.Item` for å la celler spenne over flere kolonner. Kombiner med 12-kolonners grid for fleksibel layout. + + + + +```jsx live + +
    4 kolonner
    +
    8 kolonner
    +
    6 kolonner
    +
    6 kolonner
    +
    Full bredde
    +
    +``` + +
    + + +```tsx live + + 4 kolonner + 8 kolonner + 6 kolonner + 6 kolonner + Full bredde + +``` + + +
    + +### Gap + +Standard gap er `md`. Bruk `gap`-prop/-attributt for å overstyre. + + + + +```jsx live + +
    1
    +
    2
    +
    3
    +
    4
    +
    5
    +
    6
    +
    +``` + +```jsx live + +
    1
    +
    2
    +
    3
    +
    4
    +
    5
    +
    6
    +
    +``` + +Tilgjengelige størrelser for `gap`-attributt: `none`, `2xs`, `xs`, `sm`, `md`, `lg`, `xl`, `2xl`, `3xl`, `4xl` + +
    + + +```tsx live + + +
    1
    +
    2
    +
    3
    +
    4
    +
    5
    +
    6
    +
    + +
    1
    +
    2
    +
    3
    +
    4
    +
    5
    +
    6
    +
    +
    +``` + +
    +
    + +### Align — vertikal justering av innhold + + + + +```jsx live + +
    Kort
    +
    Høy
    +
    Kort
    +
    +``` + +
    + + +```tsx live + +
    Kort
    +
    Høy
    +
    Kort
    +
    +``` + +
    +
    + +## Universell utforming + +### Hva du selv må sørge for + +- **Bruk semantiske elementer når konteksten krever det** — `ix-grid` er et ukjent HTML-element uten semantisk rolle. Nettleseren behandler det som generisk blokkinnhold, på samme måte som `
    `. Bruk `as`-prop i React (f.eks. ``) eller CSS-klasser direkte på et native element når innholdet krever semantikk. +- **Pass på DOM-rekkefølgen** — CSS Grid kan endre visuell rekkefølge uten å endre DOM-rekkefølgen. Skjermlesere og tastaturnavigasjon følger DOM. Sørg for at DOM-rekkefølgen gir mening uavhengig av visuell presentasjon. + +På native elementer (ikke ``) virker **ikke** attributter — bruk modifier-klasser: + +```html + +
    +
    Artikkel 1
    +
    Artikkel 2
    +
    Artikkel 3
    +
    + + +... +``` + +### WCAG-kriterier + + + +## Props / API + + + + +### `` — attributter + +Attributter kan bare brukes på ``-elementet, ikke på vanlige `
    `-er. + +| Attributt | Verdi | Beskrivelse | +|-----------|-------|-------------| +| `cols` | `1` ... `12` | Fast antall kolonner | +| `cols` | `auto-fit-xs` \| `auto-fit-sm` \| `auto-fit-md` \| `auto-fit-lg` | Responsivt antall kolonner, celler strekkes | +| `cols` | `auto-fill-xs` \| `auto-fill-sm` \| `auto-fill-md` \| `auto-fill-lg` | Responsivt antall kolonner, cellestørrelse beholdes | +| `rows` | `1` \| `2` \| `3` \| `4` | Fast antall rader | +| `gap` | `none` \| `2xs` \| `xs` \| `sm` \| `md` \| `lg` \| `xl` \| `2xl` \| `3xl` \| `4xl` | Avstand mellom celler. Standard: `md` | +| `align` | `start` \| `end` \| `center` \| `stretch` | Vertikal justering av innhold i celler | +| `justify` | `start` \| `end` \| `center` \| `stretch` | Horisontal justering av innhold i celler | +| `inline` | *(ingen verdi)* | Gjør grid til inline-grid | + +### CSS-klasser for containeren + +Klasser fungerer på både `` og vanlige elementer som `
    `. + +| Klasse | Beskrivelse | +|--------|-------------| +| `ix-grid` | Aktiverer grid-layout med standard gap (`md`) | +| `ix-grid-cols-1` ... `ix-grid-cols-12` | Fast antall kolonner (1-12) | +| `ix-grid-auto-fit-xs` ... `ix-grid-auto-fit-lg` | Responsivt antall kolonner, celler strekkes | +| `ix-grid-auto-fill-xs` ... `ix-grid-auto-fill-lg` | Responsivt antall kolonner, cellestørrelse beholdes | +| `ix-grid-rows-1` ... `ix-grid-rows-4` | Fast antall rader | +| `ix-grid-align-start` ... `ix-grid-align-stretch` | Vertikal justering | +| `ix-grid-justify-start` ... `ix-grid-justify-stretch` | Horisontal justering | +| `ix-inline-grid` | Inline-grid | + +### Attributter for barn (colspan, rowspan) + +Settes på barna inne i grid-containeren for å kontrollere hvor mange kolonner/rader de spenner over. + +| Attributt | Verdi | Beskrivelse | +|-----------|-------|-------------| +| `colspan` | `1` ... `12` \| `full` | Antall kolonner cellen spenner over | +| `rowspan` | `1` ... `4` \| `full` | Antall rader cellen spenner over | + +### CSS-klasser for barn + +Alternativt kan du bruke CSS-klasser i stedet for attributter. + +| Klasse | Beskrivelse | +|--------|-------------| +| `ix-col-span-1` ... `ix-col-span-12` | Antall kolonner cellen spenner over | +| `ix-col-span-full` | Spenner over alle kolonner | +| `ix-row-span-1` ... `ix-row-span-4` | Antall rader cellen spenner over | +| `ix-row-span-full` | Spenner over alle rader | +| `ix-col-start-1` ... `ix-col-start-13` | Start-kolonne | +| `ix-col-end-1` ... `ix-col-end-13` | Slutt-kolonne | + +Standard gap er `md`. Bruk `gap`-attributtet på `` eller `ix-gap-*` utility-klasser på andre elementer for å overstyre. + + + + +### Grid + +| Prop | Type | Standard | Beskrivelse | +|------|------|----------|-------------| +| `as` | `ElementType` | | Rendrer et native element med CSS-klasser i stedet for `` med attributter. Bruk for semantikk — f.eks. `as="section"`. | +| `cols` | `1` ... `12` \| `'auto-fit-xs'` \| `'auto-fit-sm'` \| `'auto-fit-md'` \| `'auto-fit-lg'` \| `'auto-fill-xs'` \| `'auto-fill-sm'` \| `'auto-fill-md'` \| `'auto-fill-lg'` | | Antall kolonner (1-12) eller responsivt mønster | +| `rows` | `1 \| 2 \| 3 \| 4` | | Antall rader | +| `gap` | `GapSize` | `'md'` | Avstand mellom celler | +| `align` | `'start' \| 'end' \| 'center' \| 'stretch'` | | Vertikal justering av innhold i celler | +| `justify` | `'start' \| 'end' \| 'center' \| 'stretch'` | | Horisontal justering av innhold i celler | +| `inline` | `boolean` | | Gjør grid til inline-grid | +| `className` | `string` | | Ekstra CSS-klasser | +| `ref` | `Ref` | | Ref til rotelementet | + +Støtter alle standard HTML-attributter for det rendrede elementet (`id`, `style`, `data-*` osv.). + +### Grid.Item + +Wrapper for grid-barn som trenger colspan eller rowspan. + +| Prop | Type | Standard | Beskrivelse | +|------|------|----------|-------------| +| `as` | `ElementType` | `'div'` | Rendrer som annet element | +| `colspan` | `1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 11 \| 12 \| 'full'` | | Antall kolonner cellen spenner over | +| `rowspan` | `1 \| 2 \| 3 \| 4 \| 'full'` | | Antall rader cellen spenner over | +| `className` | `string` | | Ekstra CSS-klasser | + + + + +## Tilpasning med CSS + +Grid-komponentene har ingen egne CSS custom properties. Bruk utility-klasser for ytterligere tilpasning: + +```html + +
    Sentrert
    +
    Sentrert
    +
    Sentrert
    +
    +``` + +## Relatert + +- [Stack](./stack) — layout i én dimensjon +- [Gap utility-klasser](../../utility-klasser/oversikt#gap) — `ix-gap-*`, `ix-row-gap-*`, `ix-col-gap-*` +- [Spacing tokens](../../grunnleggende/tokens/spacing) — størrelser tilgjengelig for gap diff --git a/indeks-docs/sidebars.ts b/indeks-docs/sidebars.ts index 32e7dc4..2abb2b0 100644 --- a/indeks-docs/sidebars.ts +++ b/indeks-docs/sidebars.ts @@ -111,7 +111,7 @@ const sidebars: SidebarsConfig = { type: 'category', label: 'Primitiver', collapsed: false, - items: ['komponenter/primitives/stack'], + items: ['komponenter/primitives/grid', 'komponenter/primitives/stack'], }, ], }, diff --git a/indeks-docs/src/theme/ReactLiveScope/index.tsx b/indeks-docs/src/theme/ReactLiveScope/index.tsx index c3e99fb..64859c0 100644 --- a/indeks-docs/src/theme/ReactLiveScope/index.tsx +++ b/indeks-docs/src/theme/ReactLiveScope/index.tsx @@ -6,6 +6,7 @@ import { Checkbox, Field, Form, + Grid, Heading, HStack, Icon, @@ -39,6 +40,7 @@ const ReactLiveScope = { Checkbox, Field, Form, + Grid, Heading, HStack, Icon, diff --git a/indeks-eksempel/src/components/Layout.tsx b/indeks-eksempel/src/components/Layout.tsx index 9dde28d..923af8a 100644 --- a/indeks-eksempel/src/components/Layout.tsx +++ b/indeks-eksempel/src/components/Layout.tsx @@ -20,6 +20,7 @@ export default function Layout() { { path: 'spacing-eksempler', label: 'Spacing-eksempler' }, { path: 'responsiv-layout', label: 'Responsiv layout' }, { path: 'spacing-responsiv', label: 'Responsiv spacing' }, + { path: 'grid-komponent', label: 'Grid-komponent' }, { path: 'typografi-eksempler', label: 'Typografi-eksempler' }, { path: 'fargeskalaer-eksempler', label: 'Fargeskalaer' }, ]; diff --git a/indeks-eksempel/src/main.tsx b/indeks-eksempel/src/main.tsx index 5c79de3..86746c4 100644 --- a/indeks-eksempel/src/main.tsx +++ b/indeks-eksempel/src/main.tsx @@ -16,6 +16,7 @@ import PMBetaling from './pages/pm/betaling/Betaling'; import PMOversikt from './pages/pm/oversikt/Oversikt'; import PMRegistrerBetaling from './pages/pm/RegistrerBetaling'; import PMWrapper from './pages/pm/Wrapper'; +import GridKomponent from './pages/openPages/GridKomponent'; const router = createHashRouter([ { @@ -65,6 +66,10 @@ const router = createHashRouter([ path: 'spacing-responsiv', element: , }, + { + path: 'grid-komponent', + element: , + }, { path: 'typografi-eksempler', element: , diff --git a/indeks-eksempel/src/pages/openPages/GridKomponent.tsx b/indeks-eksempel/src/pages/openPages/GridKomponent.tsx new file mode 100644 index 0000000..2b80437 --- /dev/null +++ b/indeks-eksempel/src/pages/openPages/GridKomponent.tsx @@ -0,0 +1,61 @@ +import { Grid, Heading } from '@sb1/indeks-react'; +import React from 'react'; + +const GridKomponent: React.FC = () => { + return ( +
    + Grid-komponent + + +
    1
    +
    2
    +
    3
    +
    + + +
    1
    +
    2
    +
    3
    +
    4
    +
    + + +
    1
    +
    2
    +
    3
    +
    4
    +
    5
    +
    6
    +
    + + + {Array.from({ length: 12 }, (_, index) => ( +
    + {index + 1} +
    + ))} +
    + + Colspan (12 kolonner by default) + + + 1 (colspan=4) + + + 2 (colspan=8) + + + 3 (colspan=6) + + + 4 (colspan=6) + + + 5 (colspan=full) + + +
    + ); +}; + +export default GridKomponent; diff --git a/indeks-react/lib/ui/layout/grid/Grid.test.tsx b/indeks-react/lib/ui/layout/grid/Grid.test.tsx new file mode 100644 index 0000000..798fb95 --- /dev/null +++ b/indeks-react/lib/ui/layout/grid/Grid.test.tsx @@ -0,0 +1,199 @@ +import { render } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import { Grid } from './Grid'; + +describe('Grid', () => { + it('rendrer uten feil', () => { + const { container } = render(Innhold); + expect(container.querySelector('ix-grid')).toBeTruthy(); + }); + + it('sender className videre som class', () => { + const { container } = render(Innhold); + const grid = container.querySelector('ix-grid'); + expect(grid?.getAttribute('class')).toBe('custom'); + }); + + it('setter cols-attributt', () => { + const { container } = render(Innhold); + const grid = container.querySelector('ix-grid'); + expect(grid?.getAttribute('cols')).toBe('3'); + }); + + it('setter cols-attributt med string-verdi', () => { + const { container } = render(Innhold); + const grid = container.querySelector('ix-grid'); + expect(grid?.getAttribute('cols')).toBe('auto-fit-md'); + }); + + it('setter rows-attributt', () => { + const { container } = render(Innhold); + const grid = container.querySelector('ix-grid'); + expect(grid?.getAttribute('rows')).toBe('2'); + }); + + it('setter gap-attributt', () => { + const { container } = render(Innhold); + const grid = container.querySelector('ix-grid'); + expect(grid?.getAttribute('gap')).toBe('lg'); + }); + + it('setter align-attributt', () => { + const { container } = render(Innhold); + const grid = container.querySelector('ix-grid'); + expect(grid?.getAttribute('align')).toBe('center'); + }); + + it('setter justify-attributt', () => { + const { container } = render(Innhold); + const grid = container.querySelector('ix-grid'); + expect(grid?.getAttribute('justify')).toBe('end'); + }); + + it('setter inline-attributt', () => { + const { container } = render(Innhold); + const grid = container.querySelector('ix-grid'); + expect(grid?.hasAttribute('inline')).toBe(true); + }); + + it('rendrer som native element med as-prop', () => { + const { container } = render(Innhold); + const section = container.querySelector('section'); + expect(section).toBeTruthy(); + expect(section?.classList.contains('ix-grid')).toBe(true); + }); + + it('setter CSS-klasser for cols med as-prop', () => { + const { container } = render( + + Innhold + , + ); + const div = container.querySelector('div'); + expect(div?.classList.contains('ix-grid-cols-4')).toBe(true); + }); + + it('setter CSS-klasser for auto-fit cols med as-prop', () => { + const { container } = render( + + Innhold + , + ); + const div = container.querySelector('div'); + expect(div?.classList.contains('ix-grid-auto-fit-lg')).toBe(true); + }); + + it('setter CSS-klasser for rows med as-prop', () => { + const { container } = render( + + Innhold + , + ); + const div = container.querySelector('div'); + expect(div?.classList.contains('ix-grid-rows-3')).toBe(true); + }); + + it('setter CSS-klasser for align med as-prop', () => { + const { container } = render( + + Innhold + , + ); + const div = container.querySelector('div'); + expect(div?.classList.contains('ix-grid-align-stretch')).toBe(true); + }); + + it('setter CSS-klasser for justify med as-prop', () => { + const { container } = render( + + Innhold + , + ); + const div = container.querySelector('div'); + expect(div?.classList.contains('ix-grid-justify-center')).toBe(true); + }); + + it('setter CSS-klasse for inline med as-prop', () => { + const { container } = render( + + Innhold + , + ); + const div = container.querySelector('div'); + expect(div?.classList.contains('ix-inline-grid')).toBe(true); + }); + + it('utelater gap-klasse for md (standardverdi) med as-prop', () => { + const { container } = render( + + Innhold + , + ); + const div = container.querySelector('div'); + expect(div?.className).not.toContain('ix-gap-'); + }); + + it('sender className videre med as-prop', () => { + const { container } = render( + + Innhold + , + ); + const div = container.querySelector('div'); + expect(div?.classList.contains('custom')).toBe(true); + expect(div?.classList.contains('ix-grid')).toBe(true); + }); +}); + +describe('Grid.Item', () => { + it('rendrer uten feil', () => { + const { container } = render(Innhold); + expect(container.querySelector('div')).toBeTruthy(); + }); + + it('setter colspan-klasse', () => { + const { container } = render(Innhold); + const item = container.querySelector('div'); + expect(item?.classList.contains('ix-col-span-4')).toBe(true); + }); + + it('setter colspan full', () => { + const { container } = render(Innhold); + const item = container.querySelector('div'); + expect(item?.classList.contains('ix-col-span-full')).toBe(true); + }); + + it('setter rowspan-klasse', () => { + const { container } = render(Innhold); + const item = container.querySelector('div'); + expect(item?.classList.contains('ix-row-span-2')).toBe(true); + }); + + it('kombinerer colspan og rowspan', () => { + const { container } = render( + + Innhold + , + ); + const item = container.querySelector('div'); + expect(item?.classList.contains('ix-col-span-6')).toBe(true); + expect(item?.classList.contains('ix-row-span-2')).toBe(true); + }); + + it('sender className videre', () => { + const { container } = render(Innhold); + const item = container.querySelector('div'); + expect(item?.classList.contains('custom')).toBe(true); + }); + + it('rendrer som annet element med as-prop', () => { + const { container } = render( + + Innhold + , + ); + const section = container.querySelector('section'); + expect(section).toBeTruthy(); + expect(section?.classList.contains('ix-col-span-3')).toBe(true); + }); +}); diff --git a/indeks-react/lib/ui/layout/grid/Grid.tsx b/indeks-react/lib/ui/layout/grid/Grid.tsx new file mode 100644 index 0000000..e8ce88c --- /dev/null +++ b/indeks-react/lib/ui/layout/grid/Grid.tsx @@ -0,0 +1,124 @@ +import clsx from 'clsx'; +import type { ComponentPropsWithoutRef, ElementType, JSX, ReactNode } from 'react'; +import type { GapSize } from '../../../types/types'; + +export type GridCols = + | 1 + | 2 + | 3 + | 4 + | 5 + | 6 + | 7 + | 8 + | 9 + | 10 + | 11 + | 12 + | 'auto-fit-xs' + | 'auto-fit-sm' + | 'auto-fit-md' + | 'auto-fit-lg' + | 'auto-fill-xs' + | 'auto-fill-sm' + | 'auto-fill-md' + | 'auto-fill-lg'; + +export type GridRows = 1 | 2 | 3 | 4; + +export type GridAlign = 'start' | 'end' | 'center' | 'stretch'; + +export type GridJustify = 'start' | 'end' | 'center' | 'stretch'; + +export type ColSpan = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 'full'; + +export type RowSpan = 1 | 2 | 3 | 4 | 'full'; + +export type GridProps = { + as?: As; + ref?: React.Ref; + children?: ReactNode; + className?: string; + cols?: GridCols; + rows?: GridRows; + gap?: GapSize; + align?: GridAlign; + justify?: GridJustify; + inline?: boolean; +} & Omit, 'as'>; + +export function Grid({ + as, + children, + className, + cols, + rows, + gap, + align, + justify, + inline, + ...restProps +}: GridProps): JSX.Element { + if (as) { + const Component = as as ElementType; + const colsClass = cols ? `ix-grid-${typeof cols === 'number' ? `cols-${cols}` : cols}` : undefined; + const rowsClass = rows ? `ix-grid-rows-${rows}` : undefined; + const gapClass = gap && gap !== 'md' ? `ix-gap-${gap}` : undefined; + const alignClass = align ? `ix-grid-align-${align}` : undefined; + const justifyClass = justify ? `ix-grid-justify-${justify}` : undefined; + const inlineClass = inline ? 'ix-inline-grid' : undefined; + return ( + + {children} + + ); + } + return ( + + {children} + + ); +} + +export type GridItemProps = { + as?: As; + ref?: React.Ref; + children?: ReactNode; + className?: string; + colspan?: ColSpan; + rowspan?: RowSpan; +} & Omit, 'as'>; + +function GridItem({ + as, + children, + className, + colspan, + rowspan, + ...restProps +}: GridItemProps): JSX.Element { + const Component = (as ?? 'div') as ElementType; + const colspanClass = colspan ? `ix-col-span-${colspan}` : undefined; + const rowspanClass = rowspan ? `ix-row-span-${rowspan}` : undefined; + return ( + + {children} + + ); +} + +export namespace Grid { + export const Item = GridItem; +} diff --git a/indeks-react/lib/ui/layout/index.ts b/indeks-react/lib/ui/layout/index.ts index 5978858..3a125d2 100644 --- a/indeks-react/lib/ui/layout/index.ts +++ b/indeks-react/lib/ui/layout/index.ts @@ -1,4 +1,5 @@ export { Box } from './box/Box'; export { Divider } from './divider/Divider'; +export { Grid } from './grid/Grid'; export { HStack } from './h-stack/HStack'; export { VStack } from './v-stack/VStack'; diff --git a/indeks-react/lib/vite-env.d.ts b/indeks-react/lib/vite-env.d.ts index 95ade28..c375380 100644 --- a/indeks-react/lib/vite-env.d.ts +++ b/indeks-react/lib/vite-env.d.ts @@ -37,6 +37,15 @@ declare module 'react' { nowrap?: boolean; gap?: import('./types/types').GapSize; }; + 'ix-grid': React.DetailedHTMLProps, HTMLElement> & { + class?: string; + cols?: string; + rows?: string; + gap?: import('./types/types').GapSize; + align?: 'start' | 'end' | 'center' | 'stretch'; + justify?: 'start' | 'end' | 'center' | 'stretch'; + inline?: '' | boolean; + }; } } } diff --git a/indeks-storybook/stories/layout/grid/Grid.stories.tsx b/indeks-storybook/stories/layout/grid/Grid.stories.tsx new file mode 100644 index 0000000..d17a4fe --- /dev/null +++ b/indeks-storybook/stories/layout/grid/Grid.stories.tsx @@ -0,0 +1,210 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; + +import { Grid } from '@sb1/indeks-react'; + +const meta = { + title: 'Layout/Grid', + component: Grid, + tags: ['autodocs'], + args: { + cols: 3, + gap: 'md', + children: ( + <> +
    1
    +
    2
    +
    3
    +
    4
    +
    5
    +
    6
    + + ), + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Standard: Story = {}; + +export const Cols2: Story = { + args: { cols: 2 }, +}; + +export const Cols4: Story = { + args: { cols: 4 }, +}; + +export const Cols6: Story = { + args: { cols: 6 }, +}; + +export const Cols12: Story = { + args: { + cols: 12, + children: ( + <> +
    1
    +
    2
    +
    3
    +
    4
    +
    5
    +
    6
    +
    7
    +
    8
    +
    9
    +
    10
    +
    11
    +
    12
    + + ), + }, +}; + +export const AutoFitMd: Story = { + args: { + cols: 'auto-fit-md', + children: ( + <> +
    Auto 1
    +
    Auto 2
    +
    Auto 3
    +
    Auto 4
    +
    Auto 5
    + + ), + }, +}; + +export const AutoFillSm: Story = { + args: { + cols: 'auto-fill-sm', + children: ( + <> +
    Fill 1
    +
    Fill 2
    +
    Fill 3
    + + ), + }, +}; + +export const ColSpan: Story = { + args: { + cols: 12, + children: ( + <> + + 4 kolonner + + + 8 kolonner + + + 6 kolonner + + + 6 kolonner + + + Full bredde + + + ), + }, +}; + +export const Rows2: Story = { + args: { + cols: 3, + rows: 2, + style: { height: '200px' }, + }, +}; + +export const GapLg: Story = { + args: { gap: 'lg' }, +}; + +export const GapNone: Story = { + args: { gap: 'none' }, +}; + +export const AlignCenter: Story = { + args: { + align: 'center', + style: { height: '150px' }, + children: ( + <> +
    Kort
    +
    Høy
    +
    Kort
    + + ), + }, +}; + +export const AlignStretch: Story = { + args: { + align: 'stretch', + style: { height: '150px' }, + children: ( + <> +
    Strekkes
    +
    Strekkes
    +
    Strekkes
    + + ), + }, +}; + +export const JustifyCenter: Story = { + args: { + justify: 'center', + children: ( + <> +
    + Sentrert +
    +
    + Sentrert +
    +
    + Sentrert +
    + + ), + }, +}; + +export const Inline: Story = { + args: { + inline: true, + cols: 2, + children: ( + <> +
    A
    +
    B
    + + ), + }, +}; + +export const HTML: Story = { + render: () => ( +
    +
    1
    +
    2
    +
    3
    +
    4
    +
    5
    +
    6
    + + `, + }} + /> + ), +}; diff --git a/indeks-storybook/tests/scanAll.dtest.ts b/indeks-storybook/tests/scanAll.dtest.ts index 3bd8a94..6d3f0b7 100644 --- a/indeks-storybook/tests/scanAll.dtest.ts +++ b/indeks-storybook/tests/scanAll.dtest.ts @@ -14,6 +14,7 @@ const ALLOWED_STORY_TITLES: string[] = [ 'Icons/Icon', 'Components/Button', 'Components/Spinner', + 'Layout/Grid', 'Layout/HStack', 'Layout/VStack' ];