mirror of
https://github.com/navikt/aksel.git
synced 2026-10-15 02:02:02 +00:00
Compare commits
32
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0f334061b | ||
|
|
79bddd9caa | ||
|
|
4a68409898 | ||
|
|
786bff67ab | ||
|
|
8a17caea6c | ||
|
|
3f43d0bf3b | ||
|
|
26541984ac | ||
|
|
10303602aa | ||
|
|
42254543d8 | ||
|
|
81c84e4817 | ||
|
|
b441175028 | ||
|
|
75362a0dc1 | ||
|
|
d8732ffa0e | ||
|
|
0ea9155085 | ||
|
|
92b9687ed1 | ||
|
|
1c022b14c9 | ||
|
|
abdeb616d2 | ||
|
|
37165e9f38 | ||
|
|
d19920ca78 | ||
|
|
60d3481300 | ||
|
|
f486937439 | ||
|
|
c62058abc1 | ||
|
|
84d9b8260f | ||
|
|
c108063dcb | ||
|
|
2a8533ad56 | ||
|
|
9b990a2178 | ||
|
|
5fec1f24f4 | ||
|
|
c27c1cfe26 | ||
|
|
5a649d2282 | ||
|
|
baac9cc251 | ||
|
|
d924d523e4 | ||
|
|
fc5a77081d |
No files matched your search
@@ -0,0 +1,22 @@
|
||||
name: Eksempel på bruk
|
||||
description: Send et eksempel på ting fra Aksel som er brukt i det fri (tokens, komponenter, maler, etc)
|
||||
title: "[Eksempel på bruk]: "
|
||||
labels: ["dokumentasjon 📖"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Takk for at du bidrar til Aksel!
|
||||
- type: input
|
||||
id: title
|
||||
attributes:
|
||||
label: Lenke til side
|
||||
description: Lenke til siden hvor eksemplet er
|
||||
placeholder:
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: examples
|
||||
attributes:
|
||||
label: Forklaring
|
||||
description: Forklar om det er noe man må gjøre for å se eksemplet, eller hvor på siden det ligger
|
||||
@@ -2,8 +2,6 @@ name: Ny komponent
|
||||
description: Send et ønske om en eller flere nye komponenter
|
||||
title: "[Ny komponent]: "
|
||||
labels: ["nytt ✨", "forespørsel 🥰", "komponenter 🧩"]
|
||||
assignees:
|
||||
- KenAJoh
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
||||
@@ -2,8 +2,6 @@ name: Nytt ikon
|
||||
description: Send et ønske om et eller flere nye ikoner
|
||||
title: "[Nytt ikon]: "
|
||||
labels: ["nytt ✨", "ikoner 🖼", "forespørsel 🥰"]
|
||||
assignees:
|
||||
- KenAJoh
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
||||
@@ -2,8 +2,6 @@ name: Innspill til komponent
|
||||
description: Send inn innspill til komponent
|
||||
title: "[Innspill til komponent]: "
|
||||
labels: ["forespørsel 🥰", "komponenter 🧩", "nytt ✨", "beta 🧪"]
|
||||
assignees:
|
||||
- KenAJoh
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
||||
@@ -2,8 +2,6 @@ name: Innspill til ikon
|
||||
description: Send inn innspill til ikon
|
||||
title: "[Tilbakemelding på ikon]: "
|
||||
labels: ["forespørsel 🥰", "ikoner 🖼️"]
|
||||
assignees:
|
||||
- KenAJoh
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
||||
+2
-1
@@ -151,4 +151,5 @@ playwright-report/
|
||||
backup-*.tar.gz
|
||||
sitemap-urls.json
|
||||
*.tar.gz
|
||||
searchindex.json
|
||||
searchindex.json
|
||||
aksel.nav.no/website/public/rss
|
||||
@@ -1,5 +1,13 @@
|
||||
# @navikt/aksel-icons
|
||||
|
||||
## 5.10.2
|
||||
|
||||
## 5.10.1
|
||||
|
||||
## 5.10.0
|
||||
|
||||
## 5.9.2
|
||||
|
||||
## 5.9.1
|
||||
|
||||
## 5.9.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel-icons",
|
||||
"version": "5.9.1",
|
||||
"version": "5.10.2",
|
||||
"author": "Aksel | NAV",
|
||||
"homepage": "https://aksel.nav.no/ikoner",
|
||||
"repository": {
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @navikt/aksel-stylelint
|
||||
|
||||
## 5.10.2
|
||||
|
||||
## 5.10.1
|
||||
|
||||
## 5.10.0
|
||||
|
||||
## 5.9.2
|
||||
|
||||
## 5.9.1
|
||||
|
||||
## 5.9.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel-stylelint",
|
||||
"version": "5.9.1",
|
||||
"version": "5.10.2",
|
||||
"author": "Aksel | NAV",
|
||||
"homepage": "https://aksel.nav.no/grunnleggende/kode/stylelint",
|
||||
"repository": {
|
||||
@@ -35,8 +35,8 @@
|
||||
"dev": "yarn watch:lint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@navikt/ds-css": "^5.9.1",
|
||||
"@navikt/ds-tokens": "^5.9.1",
|
||||
"@navikt/ds-css": "^5.10.2",
|
||||
"@navikt/ds-tokens": "^5.10.2",
|
||||
"@types/jest": "^29.0.0",
|
||||
"concurrently": "7.2.1",
|
||||
"copyfiles": "2.4.1",
|
||||
|
||||
@@ -1,5 +1,33 @@
|
||||
# @navikt/aksel
|
||||
|
||||
## 5.10.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`79bddd9ca`](https://github.com/navikt/aksel/commit/79bddd9caa9467b60e1e0c1c15b16f76dccbc304)]:
|
||||
- @navikt/ds-css@5.10.2
|
||||
|
||||
## 5.10.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-css@5.10.1
|
||||
|
||||
## 5.10.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`60d348130`](https://github.com/navikt/aksel/commit/60d34813009377a1a7d84bed6627c60524849957)]:
|
||||
- @navikt/ds-css@5.10.0
|
||||
|
||||
## 5.9.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-css@5.9.2
|
||||
|
||||
## 5.9.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel",
|
||||
"version": "5.9.1",
|
||||
"version": "5.10.2",
|
||||
"description": "Aksel command line interface. Handles css-imports, codemods and more",
|
||||
"author": "Aksel | NAV designsystem team",
|
||||
"license": "MIT",
|
||||
@@ -29,8 +29,8 @@
|
||||
},
|
||||
"homepage": "https://aksel.nav.no/grunnleggende/kode/kommandolinje",
|
||||
"dependencies": {
|
||||
"@navikt/ds-css": "5.9.1",
|
||||
"axios": "1.3.6",
|
||||
"@navikt/ds-css": "5.10.2",
|
||||
"axios": "1.6.0",
|
||||
"chalk": "4.1.0",
|
||||
"clipboardy": "^2.3.0",
|
||||
"commander": "10.0.1",
|
||||
@@ -38,7 +38,7 @@
|
||||
"fast-glob": "3.2.11",
|
||||
"figlet": "1.6.0",
|
||||
"is-git-clean": "1.1.0",
|
||||
"jscodeshift": "0.13.1",
|
||||
"jscodeshift": "^0.15.1",
|
||||
"jscodeshift-add-imports": "1.0.10",
|
||||
"lodash": "4.17.21",
|
||||
"react-scanner": "^1.1.0"
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @navikt/ds-css
|
||||
|
||||
## 5.10.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Primtives: CSS-import for primitives er nå mer spesifikk ([#2469](https://github.com/navikt/aksel/pull/2469))
|
||||
|
||||
## 5.10.1
|
||||
|
||||
## 5.10.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Primitives: Ny komponent `Page` og `Page.Block` ([#2456](https://github.com/navikt/aksel/pull/2456))
|
||||
|
||||
## 5.9.2
|
||||
|
||||
## 5.9.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -35,6 +35,10 @@ const StyleMappings = {
|
||||
component: "Box",
|
||||
main: primitivesCss,
|
||||
},
|
||||
{
|
||||
component: "Page",
|
||||
main: primitivesCss,
|
||||
},
|
||||
{
|
||||
component: "HGrid",
|
||||
main: primitivesCss,
|
||||
@@ -183,7 +187,7 @@ const StyleMappings = {
|
||||
{
|
||||
component: "DatePicker",
|
||||
main: "date.css",
|
||||
dependencies: [typoCss, primitivesCss, "button.css", "popover.css"],
|
||||
dependencies: [typoCss, "button.css", "popover.css", primitivesCss],
|
||||
},
|
||||
{
|
||||
component: "MonthPicker",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
@charset "UTF-8";
|
||||
@import "baseline/index.css";
|
||||
@import "typography.css";
|
||||
@import "primitives/index.css";
|
||||
@import "grid.css";
|
||||
@import "accordion.css";
|
||||
@import "alert.css";
|
||||
@@ -34,3 +33,4 @@
|
||||
@import "table.css";
|
||||
@import "tabs.css";
|
||||
@import "list.css";
|
||||
@import "primitives/index.css";
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-css",
|
||||
"version": "5.9.1",
|
||||
"version": "5.10.2",
|
||||
"description": "CSS for NAV Designsystem",
|
||||
"author": "Aksel | NAV designsystem team",
|
||||
"keywords": [
|
||||
@@ -27,7 +27,7 @@
|
||||
"css:get-version": "node config/get-version.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@navikt/ds-tokens": "^5.9.1",
|
||||
"@navikt/ds-tokens": "^5.10.2",
|
||||
"cssnano": "6.0.0",
|
||||
"fast-glob": "3.2.11",
|
||||
"lodash": "4.17.21",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@import "box.css";
|
||||
@import "bleed.css";
|
||||
@import "page.css";
|
||||
@import "hgrid.css";
|
||||
@import "stack.css";
|
||||
@import "responsive.css";
|
||||
@@ -0,0 +1,50 @@
|
||||
.navds-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
min-height: 100lvh;
|
||||
|
||||
/* stylelint-disable-next-line csstools/value-no-unknown-custom-properties */
|
||||
background-color: var(--__ac-page-background);
|
||||
}
|
||||
|
||||
/* Page.Block */
|
||||
.navds-pageblock {
|
||||
margin-inline: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.navds-page__content--grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.navds-page__content--fullheight {
|
||||
min-height: 100vh;
|
||||
min-height: 100lvh;
|
||||
}
|
||||
|
||||
.navds-page__content--padding {
|
||||
padding-block-end: var(--a-spacing-8);
|
||||
}
|
||||
|
||||
.navds-pageblock--lg {
|
||||
max-width: 1024px;
|
||||
}
|
||||
|
||||
.navds-pageblock--xl {
|
||||
max-width: 1280px;
|
||||
}
|
||||
|
||||
.navds-pageblock--2xl {
|
||||
max-width: 1440px;
|
||||
}
|
||||
|
||||
.navds-pageblock--gutters {
|
||||
padding-inline: var(--a-spacing-4);
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.navds-pageblock--gutters {
|
||||
padding-inline: var(--a-spacing-12);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,47 @@
|
||||
# @navikt/ds-react
|
||||
|
||||
## 5.10.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- OverridableComponent: Fungerer nå bedre ved bruk av typeof <komponent> ([#2474](https://github.com/navikt/aksel/pull/2474))
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-tokens@5.10.2
|
||||
- @navikt/aksel-icons@5.10.2
|
||||
|
||||
## 5.10.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Table: Håndterer nå sjekk for interaktive elementer i ExpandableRow bedre ([#2461](https://github.com/navikt/aksel/pull/2461))
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-tokens@5.10.1
|
||||
- @navikt/aksel-icons@5.10.1
|
||||
|
||||
## 5.10.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Primitives: Ny komponent `Page` og `Page.Block` ([#2456](https://github.com/navikt/aksel/pull/2456))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-tokens@5.10.0
|
||||
- @navikt/aksel-icons@5.10.0
|
||||
|
||||
## 5.9.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- :bug: Datepicker lukker ikke lenger modalen den er i ([#2451](https://github.com/navikt/aksel/pull/2451))
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-tokens@5.9.2
|
||||
- @navikt/aksel-icons@5.9.2
|
||||
|
||||
## 5.9.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-react",
|
||||
"version": "5.9.1",
|
||||
"version": "5.10.2",
|
||||
"description": "Aksel react-components for NAV designsystem",
|
||||
"author": "Aksel | NAV designsystem team",
|
||||
"license": "MIT",
|
||||
@@ -38,8 +38,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "0.25.4",
|
||||
"@navikt/aksel-icons": "^5.9.1",
|
||||
"@navikt/ds-tokens": "^5.9.1",
|
||||
"@navikt/aksel-icons": "^5.10.2",
|
||||
"@navikt/ds-tokens": "^5.10.2",
|
||||
"@radix-ui/react-tabs": "1.0.0",
|
||||
"@radix-ui/react-toggle-group": "1.0.0",
|
||||
"clsx": "^1.2.1",
|
||||
|
||||
@@ -437,28 +437,36 @@ export const ModalDemo = () => {
|
||||
toDate: new Date("Feb 23 2024"),
|
||||
onDateChange: console.log,
|
||||
});
|
||||
const [open, setOpen] = useState(true);
|
||||
|
||||
return (
|
||||
<Modal open header={{ heading: "Modal-demo" }}>
|
||||
<Modal.Body style={{ position: "relative" }}>
|
||||
<BodyLong spacing>
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit.
|
||||
</BodyLong>
|
||||
<>
|
||||
<Button onClick={() => setOpen(!open)}>Open modal</Button>
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
header={{ heading: "Modal-demo" }}
|
||||
>
|
||||
<Modal.Body style={{ position: "relative" }}>
|
||||
<BodyLong spacing>
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit.
|
||||
</BodyLong>
|
||||
|
||||
<DatePicker {...datepickerProps} dropdownCaption>
|
||||
<DatePicker.Input
|
||||
{...inputProps}
|
||||
label="Velg dato"
|
||||
description="Format: dd.mm.yyyy"
|
||||
/>
|
||||
</DatePicker>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button>Neste</Button>
|
||||
<Button variant="secondary">Tilbake</Button>
|
||||
<Button variant="tertiary">Avbryt</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
<DatePicker {...datepickerProps} dropdownCaption>
|
||||
<DatePicker.Input
|
||||
{...inputProps}
|
||||
label="Velg dato"
|
||||
description="Format: dd.mm.yyyy"
|
||||
/>
|
||||
</DatePicker>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button>Neste</Button>
|
||||
<Button variant="secondary">Tilbake</Button>
|
||||
<Button variant="tertiary">Avbryt</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
ModalDemo.parameters = { chromatic: { pauseAnimationAtEnd: true } };
|
||||
@@ -57,7 +57,10 @@ export const DateWrapper = ({
|
||||
<Modal
|
||||
ref={modalRef}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onClose={(event) => {
|
||||
event.stopPropagation();
|
||||
onClose();
|
||||
}}
|
||||
aria-label={modalLabel(locale, variant)}
|
||||
className={cl("navds-date__modal", {
|
||||
"navds-date__nested-modal": isInModal,
|
||||
|
||||
@@ -38,3 +38,4 @@ export * from "./layout/stack";
|
||||
export * from "./layout/grid";
|
||||
export * from "./layout/content-container";
|
||||
export * from "./layout/responsive";
|
||||
export * from "./layout/page";
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from "react";
|
||||
import type { Meta } from "@storybook/react";
|
||||
import React from "react";
|
||||
import { Box, HStack, VStack } from "../..";
|
||||
import { BodyLong } from "../../typography";
|
||||
import { Bleed } from "./Bleed";
|
||||
import { Box, HStack, VStack } from "../..";
|
||||
|
||||
export default {
|
||||
title: "ds-react/Primitives/Bleed",
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
import bgColors from "@navikt/ds-tokens/src/colors-bg.json";
|
||||
import type { Meta, StoryFn } from "@storybook/react";
|
||||
import React, { useEffect } from "react";
|
||||
import { Box } from "../box";
|
||||
import { HGrid } from "../grid";
|
||||
import Page from "./Page";
|
||||
import { widths } from "./parts/PageBlock";
|
||||
|
||||
const meta: Meta = {
|
||||
title: "ds-react/Primitives/Page",
|
||||
component: Page,
|
||||
parameters: {
|
||||
layout: "fullscreen",
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default: StoryFn = ({
|
||||
belowFold,
|
||||
background,
|
||||
width,
|
||||
gutters,
|
||||
contentBlockPadding,
|
||||
}) => (
|
||||
<Page
|
||||
footer={<Footer width={width} gutters={gutters} />}
|
||||
footerPosition={belowFold ? "belowFold" : undefined}
|
||||
background={background}
|
||||
contentBlockPadding={contentBlockPadding}
|
||||
>
|
||||
<Header width={width} gutters={gutters} />
|
||||
<Content width={width} gutters={gutters} />
|
||||
</Page>
|
||||
);
|
||||
|
||||
Default.argTypes = {
|
||||
width: {
|
||||
control: "radio",
|
||||
options: widths,
|
||||
},
|
||||
background: {
|
||||
control: "radio",
|
||||
options: Object.keys(bgColors.a),
|
||||
},
|
||||
belowFold: {
|
||||
control: "boolean",
|
||||
},
|
||||
gutters: {
|
||||
control: "boolean",
|
||||
},
|
||||
contentBlockPadding: {
|
||||
control: "radio",
|
||||
options: ["end", "none"],
|
||||
},
|
||||
};
|
||||
|
||||
Default.args = {
|
||||
belowFold: false,
|
||||
gutters: false,
|
||||
contentBlockPadding: "end",
|
||||
};
|
||||
|
||||
export const BelowFold: StoryFn = () => (
|
||||
<Page
|
||||
footer={<Footer />}
|
||||
footerPosition="belowFold"
|
||||
contentBlockPadding="end"
|
||||
>
|
||||
<Header />
|
||||
<Content />
|
||||
</Page>
|
||||
);
|
||||
|
||||
export const Background: StoryFn = () => (
|
||||
<HGrid columns={2} gap="4">
|
||||
<Page
|
||||
background="bg-default"
|
||||
footer={<div>footer</div>}
|
||||
contentBlockPadding="end"
|
||||
>
|
||||
<div>header</div>
|
||||
<div>content</div>
|
||||
</Page>
|
||||
<Page
|
||||
background="bg-subtle"
|
||||
footer={<div>footer</div>}
|
||||
contentBlockPadding="end"
|
||||
>
|
||||
<div>header</div>
|
||||
<div>content</div>
|
||||
</Page>
|
||||
</HGrid>
|
||||
);
|
||||
|
||||
export const ContentBlockPadding: StoryFn = () => (
|
||||
<HGrid columns={2} gap="6" align="start">
|
||||
<Page
|
||||
footer={<Footer width="lg" gutters />}
|
||||
contentBlockPadding="end"
|
||||
background="bg-subtle"
|
||||
>
|
||||
<Header width="lg" gutters />
|
||||
<Page.Block width="lg" gutters as="main">
|
||||
<Box background="surface-alt-3-subtle" style={{ height: "80vh" }}>
|
||||
Main
|
||||
</Box>
|
||||
</Page.Block>
|
||||
</Page>
|
||||
<Page
|
||||
footer={<Footer width="lg" gutters />}
|
||||
contentBlockPadding="none"
|
||||
background="bg-subtle"
|
||||
>
|
||||
<Header width="lg" gutters />
|
||||
<Page.Block width="lg" gutters as="main">
|
||||
<Box background="surface-alt-3-subtle" style={{ height: "80vh" }}>
|
||||
Main
|
||||
</Box>
|
||||
</Page.Block>
|
||||
</Page>
|
||||
</HGrid>
|
||||
);
|
||||
|
||||
export const Gutters: StoryFn = () => (
|
||||
<HGrid columns={2} gap="6" align="start">
|
||||
<Page footer={<Footer width="lg" gutters />} background="bg-subtle">
|
||||
<Header width="lg" gutters />
|
||||
<Page.Block width="lg" gutters as="main">
|
||||
<Box background="surface-alt-3-subtle" style={{ height: "80vh" }}>
|
||||
Main
|
||||
</Box>
|
||||
</Page.Block>
|
||||
</Page>
|
||||
<Page footer={<Footer width="lg" />} background="bg-subtle">
|
||||
<Header width="lg" />
|
||||
<Page.Block width="lg" as="main">
|
||||
<Box background="surface-alt-3-subtle" style={{ height: "80vh" }}>
|
||||
Main
|
||||
</Box>
|
||||
</Page.Block>
|
||||
</Page>
|
||||
</HGrid>
|
||||
);
|
||||
|
||||
Gutters.parameters = {
|
||||
chromatic: {
|
||||
modes: {
|
||||
mobile_portrait: {
|
||||
viewport: {
|
||||
width: 400,
|
||||
height: 850,
|
||||
},
|
||||
},
|
||||
desktop: {
|
||||
viewport: {
|
||||
width: 1280,
|
||||
height: 960,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const MILJO_URL = "https://www.nav.no/dekoratoren";
|
||||
|
||||
export const WithDecorator: StoryFn = () => {
|
||||
return (
|
||||
<Page
|
||||
contentBlockPadding="end"
|
||||
footerPosition="belowFold"
|
||||
footer={<div id="decorator-footer"></div>}
|
||||
>
|
||||
<div id="decorator-header"></div>
|
||||
<Content width="lg" gutters />
|
||||
<div
|
||||
id="decorator-env"
|
||||
data-src={`${MILJO_URL}/env?context=privatperson`}
|
||||
></div>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
WithDecorator.decorators = [
|
||||
(Story) => {
|
||||
useEffect(() => {
|
||||
const script = document.createElement("script");
|
||||
script.src = `${MILJO_URL}/client.js`;
|
||||
script.async = true;
|
||||
document.body.appendChild(script);
|
||||
|
||||
const styles = document.createElement("link");
|
||||
styles.href = `${MILJO_URL}/css/client.css`;
|
||||
styles.rel = "stylesheet";
|
||||
document.head.appendChild(styles);
|
||||
|
||||
return () => {
|
||||
document.body.removeChild(script);
|
||||
document.head.removeChild(styles);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return <Story />;
|
||||
},
|
||||
];
|
||||
|
||||
WithDecorator.parameters = {
|
||||
chromatic: {
|
||||
disable: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const OutsideBackground = () => {
|
||||
return (
|
||||
<Page
|
||||
contentBlockPadding="end"
|
||||
footer={
|
||||
<Box
|
||||
background="surface-alt-3-subtle"
|
||||
style={{ height: 100 }}
|
||||
as="footer"
|
||||
>
|
||||
<Page.Block width="lg" gutters>
|
||||
Footer
|
||||
</Page.Block>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Box background="surface-alt-1-subtle" style={{ height: 64 }} as="header">
|
||||
<Page.Block width="lg" gutters>
|
||||
Header
|
||||
</Page.Block>
|
||||
</Box>
|
||||
<Box background="surface-alt-2-subtle" style={{ height: 300 }} as="main">
|
||||
<Page.Block width="lg" gutters>
|
||||
main
|
||||
</Page.Block>
|
||||
</Box>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
function Header({ width = "lg", gutters = false }: any) {
|
||||
return (
|
||||
<Page.Block as="header" width={width} gutters={gutters}>
|
||||
<Box background="surface-alt-3-subtle" style={{ height: 64 }}>
|
||||
Header
|
||||
</Box>
|
||||
</Page.Block>
|
||||
);
|
||||
}
|
||||
|
||||
function Content({ width = "lg", gutters = false }: any) {
|
||||
return (
|
||||
<Page.Block width={width} gutters={gutters} as="main">
|
||||
<Box background="surface-alt-3-subtle" style={{ height: 300 }}>
|
||||
Main
|
||||
</Box>
|
||||
</Page.Block>
|
||||
);
|
||||
}
|
||||
|
||||
function Footer({ width = "lg", gutters = false }: any) {
|
||||
return (
|
||||
<Page.Block width={width} gutters={gutters} as="footer">
|
||||
<Box background="surface-alt-3-subtle" style={{ height: 100 }}>
|
||||
Footer
|
||||
</Box>
|
||||
</Page.Block>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import bgColors from "@navikt/ds-tokens/src/colors-bg.json";
|
||||
import cl from "clsx";
|
||||
import React, { forwardRef } from "react";
|
||||
import { OverridableComponent } from "../../util";
|
||||
import { PageBlock } from "./parts/PageBlock";
|
||||
|
||||
export interface PageProps extends React.HTMLAttributes<HTMLElement> {
|
||||
/**
|
||||
* Overrides html-tag
|
||||
* @default "div"
|
||||
*/
|
||||
as?: "div" | "body";
|
||||
/**
|
||||
* Background color. Accepts a color token.
|
||||
* @default bg-default
|
||||
*/
|
||||
background?: keyof typeof bgColors.a;
|
||||
/**
|
||||
* Allows better positioning of footer
|
||||
*/
|
||||
footer?: React.ReactNode;
|
||||
/**
|
||||
* Places footer below page-fold
|
||||
*/
|
||||
footerPosition?: "belowFold";
|
||||
/**
|
||||
* Adds a standardised padding of 4rem between content and footer
|
||||
* @default block-end
|
||||
*/
|
||||
contentBlockPadding?: "end" | "none";
|
||||
}
|
||||
|
||||
interface PageComponentType
|
||||
extends OverridableComponent<PageProps, HTMLElement> {
|
||||
Block: typeof PageBlock;
|
||||
}
|
||||
|
||||
export const PageComponent: OverridableComponent<PageProps, HTMLElement> =
|
||||
forwardRef(
|
||||
(
|
||||
{
|
||||
as: Component = "div",
|
||||
className,
|
||||
style: _style,
|
||||
footer,
|
||||
children,
|
||||
footerPosition,
|
||||
background = "bg-default",
|
||||
contentBlockPadding = "end",
|
||||
...rest
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const style: React.CSSProperties = {
|
||||
..._style,
|
||||
"--__ac-page-background": `var(--a-${background})`,
|
||||
};
|
||||
|
||||
const belowFold = footerPosition === "belowFold";
|
||||
|
||||
return (
|
||||
<Component
|
||||
{...rest}
|
||||
className={cl("navds-page", className)}
|
||||
ref={ref}
|
||||
style={style}
|
||||
>
|
||||
<div
|
||||
className={cl({
|
||||
"navds-page__content--fullheight": belowFold,
|
||||
"navds-page__content--grow": !belowFold,
|
||||
"navds-page__content--padding": contentBlockPadding === "end",
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
{footer}
|
||||
</Component>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Page helps with establishing a top-level layout for your page
|
||||
*
|
||||
* @see [📝 Documentation](https://aksel.nav.no/komponenter/primitives/page)
|
||||
* @see 🏷️ {@link PageProps}
|
||||
* @see [🤖 OverridableComponent](https://aksel.nav.no/grunnleggende/kode/overridablecomponent) support
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* <Page
|
||||
* footer={<Page.Block width="xl" gutters />}
|
||||
* >
|
||||
* <Page.Block width="xl" gutters />// Header
|
||||
* <Page.Block width="xl" gutters />// Content
|
||||
* </Page>
|
||||
* ```
|
||||
* @example
|
||||
* Footer placed below page-fold
|
||||
* ```jsx
|
||||
* <Page
|
||||
* footer={<Page.Block width="xl" gutters />}
|
||||
* footerPosition="belowFold"
|
||||
* >
|
||||
* <Page.Block width="xl" gutters />// Header
|
||||
* <Page.Block width="xl" gutters />// Content
|
||||
* </Page>
|
||||
* ```
|
||||
*/
|
||||
const Page = PageComponent as PageComponentType;
|
||||
|
||||
Page.Block = PageBlock;
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as Page, type PageProps } from "./Page";
|
||||
export { type PageBlockProps } from "./parts/PageBlock";
|
||||
@@ -0,0 +1,72 @@
|
||||
import cl from "clsx";
|
||||
import React, { forwardRef } from "react";
|
||||
import { OverridableComponent } from "../../../util/OverridableComponent";
|
||||
|
||||
export const widths = ["lg", "xl", "2xl"] as const;
|
||||
|
||||
export interface PageBlockProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
/**
|
||||
* Predefined max-width
|
||||
* @example
|
||||
* lg: 1024px
|
||||
* xl: 1280px
|
||||
* 2xl: 1440px
|
||||
* @default max-width: 100%;
|
||||
*/
|
||||
width?: (typeof widths)[number];
|
||||
/**
|
||||
* Adds a standardised responsive padding-inline
|
||||
* @example
|
||||
* 3rem on > md
|
||||
* 1rem on < md
|
||||
* @default false
|
||||
*/
|
||||
gutters?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Acts as a top-level container for defining max-width, gutters and horizontal centering
|
||||
*
|
||||
* @see [📝 Documentation](https://aksel.nav.no/komponenter/primitives/page)
|
||||
* @see 🏷️ {@link PageBlockProps}
|
||||
* @see [🤖 OverridableComponent](https://aksel.nav.no/grunnleggende/kode/overridablecomponent) support
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* <Page
|
||||
* footer={<Page.Block width="xl" gutters />}
|
||||
* >
|
||||
* <Page.Block width="xl" gutters />// Header
|
||||
* <Page.Block width="xl" gutters />// Content
|
||||
* </Page>
|
||||
* ```
|
||||
* @example
|
||||
* With background bleed
|
||||
* Wrapping `Page.Block` with `Box` allows the background-color to use full screen-width
|
||||
* ```jsx
|
||||
* <Page
|
||||
* footer={<Box background="..."><Page.Block width="xl" gutters /></Box>}
|
||||
* footerPosition="belowFold"
|
||||
* >
|
||||
* <Box background="..."><Page.Block width="xl" gutters /></Box>//Header
|
||||
* <Box background="..."><Page.Block width="xl" gutters /></Box>//Content
|
||||
* </Page>
|
||||
* ```
|
||||
*/
|
||||
export const PageBlock: OverridableComponent<PageBlockProps, HTMLDivElement> =
|
||||
forwardRef(
|
||||
({ as: Component = "div", gutters, className, width, ...rest }, ref) => {
|
||||
return (
|
||||
<Component
|
||||
{...rest}
|
||||
className={cl(
|
||||
"navds-pageblock",
|
||||
`navds-pageblock--${width}`,
|
||||
className,
|
||||
{ "navds-pageblock--gutters": gutters }
|
||||
)}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -81,11 +81,8 @@ export const ExpandableRow: ExpandableRowType = forwardRef(
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const onRowClick = (e) => {
|
||||
if (e.target.nodeName === "TD" || e.target.nodeName === "TH") {
|
||||
expansionHandler(e);
|
||||
}
|
||||
};
|
||||
const onRowClick = (e) =>
|
||||
!isInteractiveTarget(e.target) && expansionHandler(e);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -143,4 +140,19 @@ export const ExpandableRow: ExpandableRowType = forwardRef(
|
||||
}
|
||||
);
|
||||
|
||||
function isInteractiveTarget(elm: HTMLElement) {
|
||||
if (elm.nodeName === "TD" || elm.nodeName === "TH" || !elm.parentElement) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
["BUTTON", "DETAILS", "LABEL", "SELECT", "TEXTAREA", "INPUT", "A"].includes(
|
||||
elm.nodeName
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return isInteractiveTarget(elm.parentElement);
|
||||
}
|
||||
|
||||
export default ExpandableRow;
|
||||
@@ -0,0 +1,100 @@
|
||||
import { expect, jest } from "@storybook/jest";
|
||||
import { userEvent, within } from "@storybook/testing-library";
|
||||
import React from "react";
|
||||
import { Table } from "../..";
|
||||
|
||||
export default {
|
||||
title: "ds-react/Table/Tests",
|
||||
component: Table,
|
||||
};
|
||||
|
||||
export const ClickableRowTest = {
|
||||
render: ({ onOpenChange }) => {
|
||||
return (
|
||||
<>
|
||||
<Table zebraStripes>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell aria-hidden />
|
||||
<Table.HeaderCell aria-hidden />
|
||||
<Table.HeaderCell aria-hidden />
|
||||
<Table.HeaderCell aria-hidden />
|
||||
<Table.HeaderCell aria-hidden />
|
||||
<Table.HeaderCell aria-hidden />
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.ExpandableRow
|
||||
content={<div>placeholder row 2</div>}
|
||||
togglePlacement="right"
|
||||
data-testid="row1"
|
||||
expandOnRowClick
|
||||
onOpenChange={onOpenChange}
|
||||
>
|
||||
<Table.DataCell>
|
||||
<div data-testid="cell1">Should be clickable</div>
|
||||
</Table.DataCell>
|
||||
<Table.DataCell data-testid="cell2">
|
||||
Should also be clickable
|
||||
</Table.DataCell>
|
||||
|
||||
<Table.DataCell>
|
||||
<button data-testid="cell3">Should not be clickable</button>
|
||||
</Table.DataCell>
|
||||
<Table.DataCell>
|
||||
<div>
|
||||
<div>
|
||||
<button data-testid="cell4">
|
||||
Nested should not be clickable
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Table.DataCell>
|
||||
<Table.DataCell>
|
||||
<div>
|
||||
<div>
|
||||
<button data-testid="cell4">
|
||||
<span>2x nested should not be clickable</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Table.DataCell>
|
||||
</Table.ExpandableRow>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
</>
|
||||
);
|
||||
},
|
||||
args: {
|
||||
onOpenChange: jest.fn(),
|
||||
},
|
||||
play: async ({ canvasElement, args }) => {
|
||||
args.onOpenChange.mockClear();
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const cell1 = canvas.getByText("Should be clickable");
|
||||
const cell2 = canvas.getByText("Should also be clickable");
|
||||
const cell3 = canvas.getByText("Should not be clickable");
|
||||
const cell4 = canvas.getByText("Nested should not be clickable");
|
||||
const cell5 = canvas.getByText("2x nested should not be clickable");
|
||||
|
||||
await userEvent.click(cell1);
|
||||
expect(args.onOpenChange.mock.calls).toHaveLength(1);
|
||||
await userEvent.click(cell1);
|
||||
expect(args.onOpenChange.mock.calls).toHaveLength(2);
|
||||
|
||||
await userEvent.click(cell2);
|
||||
expect(args.onOpenChange.mock.calls).toHaveLength(3);
|
||||
await userEvent.click(cell2);
|
||||
expect(args.onOpenChange.mock.calls).toHaveLength(4);
|
||||
|
||||
await userEvent.click(cell3);
|
||||
expect(args.onOpenChange.mock.calls).toHaveLength(4);
|
||||
|
||||
await userEvent.click(cell4);
|
||||
expect(args.onOpenChange.mock.calls).toHaveLength(4);
|
||||
|
||||
await userEvent.click(cell5);
|
||||
expect(args.onOpenChange.mock.calls).toHaveLength(4);
|
||||
},
|
||||
};
|
||||
@@ -1,11 +1,28 @@
|
||||
import React from "react";
|
||||
|
||||
/**
|
||||
* This interface represents a component that can be overridden with different props and elements.
|
||||
* @template Component The type of the props for the component.
|
||||
* @template Element The type of the HTML element that the component renders.
|
||||
*/
|
||||
export interface OverridableComponent<Component, Element extends HTMLElement> {
|
||||
/**
|
||||
* This is a function type that takes props and returns a React functional component.
|
||||
* @param props The props for the component, which are a combination of the Component type and React.RefAttributes for the Element type.
|
||||
* @returns A React functional component.
|
||||
*/
|
||||
(props: Component & React.RefAttributes<Element>): ReturnType<React.FC>;
|
||||
|
||||
/**
|
||||
* This is a function type that takes props and returns a React functional component.
|
||||
* It allows for the 'as' prop to override the type of element that the component renders.
|
||||
* @template As The type of the element that the component should render as.
|
||||
* @param props The props for the component, which are a combination of the Component type, the 'as' prop, and any other props that are not part of the Component type or the 'as' prop.
|
||||
* @returns A React functional component.
|
||||
*/
|
||||
<As extends React.ElementType>(
|
||||
props: {
|
||||
as: As;
|
||||
as?: As;
|
||||
} & Component &
|
||||
Omit<React.ComponentPropsWithRef<As>, keyof Component | "as">
|
||||
): ReturnType<React.FC>;
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @navikt/ds-tailwind
|
||||
|
||||
## 5.10.2
|
||||
|
||||
## 5.10.1
|
||||
|
||||
## 5.10.0
|
||||
|
||||
## 5.9.2
|
||||
|
||||
## 5.9.1
|
||||
|
||||
## 5.9.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-tailwind",
|
||||
"version": "5.9.1",
|
||||
"version": "5.10.2",
|
||||
"description": "Tailwind config based on @navikt/ds-tokens",
|
||||
"author": "Aksel | NAV designsystem team",
|
||||
"keywords": [
|
||||
@@ -22,7 +22,7 @@
|
||||
"directory": "@navikt/core/tailwind"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@navikt/ds-tokens": "^5.9.1",
|
||||
"@navikt/ds-tokens": "^5.10.2",
|
||||
"@types/jest": "^29.0.0",
|
||||
"color": "4.2.3",
|
||||
"jest": "^29.0.0",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @navikt/ds-tokens
|
||||
|
||||
## 5.10.2
|
||||
|
||||
## 5.10.1
|
||||
|
||||
## 5.10.0
|
||||
|
||||
## 5.9.2
|
||||
|
||||
## 5.9.1
|
||||
|
||||
## 5.9.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-tokens",
|
||||
"version": "5.9.1",
|
||||
"version": "5.10.2",
|
||||
"description": "Design-tokens for NAV designsystem",
|
||||
"author": "Aksel | NAV designsystem team",
|
||||
"keywords": [
|
||||
|
||||
+430
-858
File diff suppressed because it is too large.
Load diff
@@ -4,7 +4,7 @@ ENV NODE_ENV production
|
||||
|
||||
WORKDIR /app
|
||||
COPY aksel.nav.no/website/.next/standalone ./
|
||||
COPY aksel.nav.no/website/next-logger.config.js ./
|
||||
COPY aksel.nav.no/website/config/next-logger.config.js ./
|
||||
|
||||
ENV NODE_OPTIONS '-r next-logger'
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ Det publiserte studioet finner man på [aksel.nav.no/admin](https://aksel.nav.no
|
||||
|
||||
### Bidrag
|
||||
|
||||
Branch `main` blir pusher rett til prod -> aksel.nav.no. For å utvikle nye features kan branch `next` brukes, som ved push publiseres til devmiljø https://aksel.ekstern.dev.nav.no/. Kan også publisere andre branches til dev ved bruk av workflow-dispatch.
|
||||
Branch `main` pusher rett til prod -> aksel.nav.no. For å utvikle nye features kan branch `next` brukes, som ved push publiseres til devmiljø https://aksel.ekstern.dev.nav.no/. Kan også publisere andre branches til dev ved bruk av workflow-dispatch.
|
||||
|
||||
## Kode-eksempler
|
||||
|
||||
|
||||
@@ -12,14 +12,15 @@
|
||||
"test": "yarn workspace website test"
|
||||
},
|
||||
"workspaces": [
|
||||
"website"
|
||||
"website",
|
||||
"playroom"
|
||||
],
|
||||
"dependencies": {
|
||||
"@navikt/aksel-icons": "^5.9.1",
|
||||
"@navikt/ds-css": "^5.9.1",
|
||||
"@navikt/ds-react": "^5.9.1",
|
||||
"@navikt/ds-tailwind": "^5.9.1",
|
||||
"@navikt/ds-tokens": "^5.9.1"
|
||||
"@navikt/aksel-icons": "^5.10.2",
|
||||
"@navikt/ds-css": "^5.10.2",
|
||||
"@navikt/ds-react": "^5.10.2",
|
||||
"@navikt/ds-tailwind": "^5.10.2",
|
||||
"@navikt/ds-tokens": "^5.10.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier-plugin-tailwindcss": "^0.2.3"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# playroom
|
||||
|
||||
https://github.com/seek-oss/playroom
|
||||
|
||||
This is the playroom / code sandbox that is built as a standalone static app, and then it's added to the aksel website through an iframe.
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* @type {import('@babel/core').TransformOptions}
|
||||
*/
|
||||
module.exports = function (api) {
|
||||
const envName = api.env();
|
||||
const development = envName === "development" || envName === "test";
|
||||
return {
|
||||
presets: [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{ useBuiltIns: "entry", corejs: "3.0", bugfixes: true },
|
||||
],
|
||||
["@babel/preset-typescript"],
|
||||
["@babel/preset-react", { development, useBuiltIns: true }],
|
||||
],
|
||||
assumptions: {
|
||||
setPublicClassFields: true,
|
||||
privateFieldsAsProperties: true,
|
||||
// nothing accesses `document.all`:
|
||||
noDocumentAll: true,
|
||||
// nothing relies on class constructors invoked without `new` throwing:
|
||||
noClassCalls: true,
|
||||
// nothing should be relying on tagged template strings being frozen:
|
||||
mutableTemplateObject: true,
|
||||
// nothing is relying on Function.prototype.length:
|
||||
ignoreFunctionLength: true,
|
||||
// nothing is relying on mutable re-exported bindings:
|
||||
constantReexports: true,
|
||||
// don't bother marking Module records non-enumerable:
|
||||
enumerableModuleMeta: true,
|
||||
// nothing uses [[Symbol.toPrimitive]]:
|
||||
// (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toPrimitive)
|
||||
ignoreToPrimitiveHint: true,
|
||||
// nothing relies on spread copying Symbol keys: ({...{ [Symbol()]: 1 }})
|
||||
objectRestNoSymbols: true,
|
||||
// nothing relies on `new (() => {})` throwing:
|
||||
noNewArrows: true,
|
||||
// transpile object spread to assignment instead of defineProperty():
|
||||
setSpreadProperties: true,
|
||||
// nothing should be using custom iterator protocol:
|
||||
skipForOfIteratorClosing: true,
|
||||
// nothing inherits from a constructor function with explicit return value:
|
||||
superIsCallableConstructor: true,
|
||||
// nothing relies on CJS-transpiled namespace imports having all properties prior to module execution completing:
|
||||
noIncompleteNsImportDetection: true,
|
||||
},
|
||||
babelrcRoots: ["."],
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "playroom",
|
||||
"scripts": {
|
||||
"start": "playroom start",
|
||||
"build:playroom": "playroom build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.23.2",
|
||||
"@babel/preset-react": "^7.22.15",
|
||||
"@babel/preset-typescript": "^7.23.2",
|
||||
"@navikt/ds-css": "^5.9.2",
|
||||
"@navikt/ds-react": "^5.9.2",
|
||||
"babel-loader": "^9.1.3",
|
||||
"babel-plugin-preval": "^5.1.0",
|
||||
"css-loader": "^6.8.1",
|
||||
"playroom": "^0.32.1",
|
||||
"style-loader": "^3.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Note: Hot module reloading for dev has been turned off for this environment
|
||||
// Why? We embed playroom in an iframe, and that iframe is loaded from the public asset folder
|
||||
// So we need to build into that public asset folder. Hence no dev mode and HMR
|
||||
// Workaround: restart the next dev server after each change to the playroom config and associated files.
|
||||
|
||||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
baseUrl: "./",
|
||||
components: path.resolve("./src/components.ts"),
|
||||
outputPath: path.resolve("./dist"),
|
||||
// Optional:
|
||||
title: "Aksel Sandbox",
|
||||
snippets: path.resolve("./src/snippets.ts"),
|
||||
widths: [480, 768, 1024, 1280],
|
||||
port: 9000,
|
||||
frameComponent: path.resolve("./src/FrameComponent.tsx"),
|
||||
useScope: path.resolve("./src/useScope.tsx"),
|
||||
openBrowser: false,
|
||||
paramType: "search", // default is 'hash'
|
||||
iframeSandbox: "allow-scripts allow-same-origin",
|
||||
exampleCode: `
|
||||
<Heading>This is a sandbox!</Heading>
|
||||
<HStack gap="10">
|
||||
<Box> box 1</Box> <Box> box 2</Box> <Box> box 3</Box>
|
||||
</HStack>
|
||||
<Button>test</Button>
|
||||
<BodyShort>This is a bodyshort</BodyShort>
|
||||
`,
|
||||
webpackConfig: () => ({
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(js|ts|tsx)$/,
|
||||
include: [path.resolve("./")],
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve("babel-loader"),
|
||||
options: {
|
||||
presets: [
|
||||
[
|
||||
require.resolve("@babel/preset-env"),
|
||||
{ shippedProposals: true },
|
||||
],
|
||||
require.resolve("@babel/preset-react"),
|
||||
require.resolve("@babel/preset-typescript"),
|
||||
],
|
||||
plugins: [require.resolve("babel-plugin-preval")],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
include: [require.resolve("@navikt/ds-css")],
|
||||
use: ["style-loader", "css-loader"],
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
import "@navikt/ds-css";
|
||||
import * as React from "react";
|
||||
|
||||
const FrameComponent = ({ children }: { children: React.ReactNode }) => {
|
||||
return <div id="sandbox-wrapper">{children}</div>;
|
||||
};
|
||||
|
||||
export default FrameComponent;
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "@navikt/aksel-icons";
|
||||
export * from "@navikt/ds-react";
|
||||
@@ -0,0 +1,29 @@
|
||||
const snippets = [
|
||||
{
|
||||
group: "Templates",
|
||||
name: "Single Component",
|
||||
code: `<Button variant="primary">Button</Button>`,
|
||||
},
|
||||
{
|
||||
group: "Templates",
|
||||
name: "Multiple Components",
|
||||
code: ` <Button variant="primary">First</Button>
|
||||
<Button>Second</Button>`,
|
||||
},
|
||||
{
|
||||
group: "Templates",
|
||||
name: "Component with state",
|
||||
code: `{
|
||||
(() => {
|
||||
const [count, setCount] = React.useState(0);
|
||||
|
||||
return (
|
||||
<Button variant="primary" onClick={() => setCount(count + 1)}>Count: {count}</Button>
|
||||
)
|
||||
})()
|
||||
}
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
export default snippets;
|
||||
@@ -0,0 +1,2 @@
|
||||
public/sandbox
|
||||
test-results/
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { StorybookConfig } from "@storybook/nextjs";
|
||||
|
||||
import { readFileSync } from "fs";
|
||||
import { join, dirname } from "path";
|
||||
import { loadCsf } from "@storybook/csf-tools";
|
||||
import type { StorybookConfig } from "@storybook/nextjs";
|
||||
import { readFileSync } from "fs";
|
||||
import { dirname, join } from "path";
|
||||
|
||||
/**
|
||||
* This function is used to resolve the absolute path of a package.
|
||||
|
||||
@@ -199,3 +199,20 @@ export const StorybookIcon = ({ ...props }) => (
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const CodeSandboxLogo = ({ ...props }) => (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="8 3 24 24"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M20.54 25a.681.681 0 00.158-.074l6.174-3.69c.734-.438 1.128-.885 1.128-2.084v-6.444c0-.248-.016-.447-.063-.629l-7.397 4.434zm-8.477-12.921l7.397 4.434V25a.514.514 0 01-.15-.074l-6.182-3.69c-.726-.438-1.128-.885-1.128-2.084v-6.444c0-.248.016-.447.063-.629zM20.004 7c.443 0 .9.143 1.359.406l.068.04 5.567 3.326c.155.088.284.177.394.282l.04.04-7.428 4.426-7.436-4.425c.11-.109.233-.198.378-.286l.063-.037 4.07-2.442h.002l1.488-.883c.434-.262.868-.415 1.296-.443l.071-.003z"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
|
||||
const Logo = (props) => (
|
||||
const AkselLogo = (props) => (
|
||||
<svg
|
||||
width="119"
|
||||
height="26"
|
||||
@@ -43,4 +43,4 @@ const Logo = (props) => (
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default Logo;
|
||||
export default AkselLogo;
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./Icons";
|
||||
File renamed without changes.
+2
-1
@@ -1,5 +1,5 @@
|
||||
import { dateStr } from "components/website-modules/utils/date-string";
|
||||
import { useEffect, useState } from "react";
|
||||
import { dateStr } from "@/utils";
|
||||
|
||||
export const useFormatedDate = (date: string) => {
|
||||
const [_date, setDate] = useState(null);
|
||||
@@ -8,5 +8,6 @@ export const useFormatedDate = (date: string) => {
|
||||
const handleDate = async () => setDate(await dateStr(date));
|
||||
handleDate();
|
||||
}, [date]);
|
||||
|
||||
return _date;
|
||||
};
|
||||
+1
-7
@@ -2,7 +2,7 @@ import { useEffect } from "react";
|
||||
|
||||
// https://stackoverflow.com/questions/38588346/anchor-a-tags-not-working-in-chrome-when-using/38588927#38588927
|
||||
// https://github.com/vercel/next.js/discussions/13134
|
||||
export function useScrollToHashOnPageLoad(): void {
|
||||
export function useHashScroll(): void {
|
||||
useEffect(() => {
|
||||
if (window.location.hash) {
|
||||
const hash = window.location.hash;
|
||||
@@ -13,9 +13,3 @@ export function useScrollToHashOnPageLoad(): void {
|
||||
}
|
||||
}, []);
|
||||
}
|
||||
|
||||
export const removeEmojies = (s: string) =>
|
||||
s.replace(
|
||||
/([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g,
|
||||
""
|
||||
);
|
||||
File renamed without changes.
@@ -0,0 +1,13 @@
|
||||
import { createContext, useContext } from "react";
|
||||
|
||||
export const SanityDocIdContext = createContext<{ id?: string }>(null);
|
||||
|
||||
export function useSanityDocId() {
|
||||
const ctx = useContext(SanityDocIdContext);
|
||||
if (!ctx) {
|
||||
console.error(
|
||||
"useSanityDocId has to be used inside SanityDocIdContext.provider"
|
||||
);
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export * from "./assets";
|
||||
export * from "./website-modules/BreadCrumbs";
|
||||
export * from "./website-modules/toc/TOC";
|
||||
export * from "./website-modules/utils";
|
||||
@@ -1,18 +1,13 @@
|
||||
import { FigmaIcon, GithubIcon, SlackIcon } from "@/assets/Icons";
|
||||
import AkselLogo from "@/assets/Logo";
|
||||
import { amplitudeLogNavigation } from "@/logging";
|
||||
import { BodyShort, Heading, Link } from "@navikt/ds-react";
|
||||
import { FigmaIcon, GithubIcon, SlackIcon } from "components/assets";
|
||||
import Logo from "components/assets/Logo";
|
||||
import dynamic from "next/dynamic";
|
||||
/* import FooterForm from "./FooterForm"; */
|
||||
|
||||
export const EditButton = dynamic(
|
||||
() => import("components/website-modules/EditButton"),
|
||||
{
|
||||
ssr: false,
|
||||
}
|
||||
);
|
||||
export const EditButton = dynamic(() => import("@/web/EditButton"), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
/* FooterForm is disabled to better comply with policy */
|
||||
const Footer = () => {
|
||||
return (
|
||||
<footer
|
||||
@@ -26,7 +21,6 @@ const Footer = () => {
|
||||
<Snarveier />
|
||||
<SideLenker />
|
||||
<Kontakt />
|
||||
{/* <FooterForm /> */}
|
||||
</div>
|
||||
<EditButton />
|
||||
</footer>
|
||||
@@ -36,7 +30,7 @@ const Footer = () => {
|
||||
function LogoBlock() {
|
||||
return (
|
||||
<div>
|
||||
<Logo className="fill-white" />
|
||||
<AkselLogo className="fill-white" />
|
||||
<p className="mt-4">© {new Date().getFullYear()} NAV</p>
|
||||
<p>Arbeids- og velferdsetaten</p>
|
||||
</div>
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
import { Button, Label, Textarea } from "@navikt/ds-react";
|
||||
import { IdContext } from "components/website-modules/utils/contexts/id-context";
|
||||
import { FeedbackT } from "@/types";
|
||||
import { useRouter } from "next/router";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
|
||||
const FooterForm = () => {
|
||||
const idCtx = useContext(IdContext);
|
||||
|
||||
const [contactForm, setContactForm] = useState({
|
||||
content: "",
|
||||
hasWritten: false,
|
||||
});
|
||||
|
||||
const [contentError, setContentError] = useState({ content: "" });
|
||||
const [sent, setSent] = useState({ status: false });
|
||||
|
||||
const { asPath, basePath } = useRouter();
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
let fail = false;
|
||||
if (!contactForm?.content || contactForm?.content.trim().length === 0) {
|
||||
setContentError({
|
||||
...contentError,
|
||||
content: "Melding kan ikke være tom. Fyll inn meldingen.",
|
||||
});
|
||||
fail = true;
|
||||
}
|
||||
|
||||
if (fail) return;
|
||||
setContentError({ content: "" });
|
||||
|
||||
const body: FeedbackT = {
|
||||
message: contactForm.content,
|
||||
url: `${basePath}${asPath}`,
|
||||
type: "footer",
|
||||
docId: idCtx?.id,
|
||||
};
|
||||
|
||||
fetch("/api/feedback", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
setSent({ status: true });
|
||||
setContactForm({ content: "", hasWritten: false });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setSent({ status: false });
|
||||
setContactForm({ content: "", hasWritten: false });
|
||||
setContentError({ content: "" });
|
||||
}, [asPath]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="flex w-full max-w-md flex-col lg:col-span-2 xl:col-span-1"
|
||||
data-theme="dark"
|
||||
>
|
||||
<div aria-live="polite">
|
||||
{sent.status && (
|
||||
<Label spacing as="p">
|
||||
Meldingen er sendt til Aksel, takk for tilbakemeldingen!
|
||||
</Label>
|
||||
)}
|
||||
</div>
|
||||
{!sent.status && (
|
||||
<form onSubmit={(e) => handleSubmit(e)} className="w-full">
|
||||
<div className="mb-3 flex flex-col gap-4">
|
||||
<Textarea
|
||||
className="textarea-override"
|
||||
error={contentError.content}
|
||||
autoComplete="off"
|
||||
label="Gi oss en tilbakemelding"
|
||||
value={contactForm.content}
|
||||
onChange={(e) => {
|
||||
setContactForm({
|
||||
...contactForm,
|
||||
content: e.target.value,
|
||||
hasWritten: true,
|
||||
});
|
||||
e.target.value &&
|
||||
e.target.value.trim().length !== 0 &&
|
||||
setContentError({ ...contentError, content: "" });
|
||||
}}
|
||||
description="Ikke skriv inn personopplysninger"
|
||||
minRows={2}
|
||||
/>
|
||||
</div>
|
||||
<Button size="small">Send melding</Button>
|
||||
<style>{`.textarea-override {
|
||||
--ac-textarea-bg: var(--a-surface-default);
|
||||
--ac-textarea-text: var(--a-text-default);
|
||||
}`}</style>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default FooterForm;
|
||||
@@ -10,12 +10,12 @@ import { useState } from "react";
|
||||
const LinkElement = ({ name, href, onClick }) => {
|
||||
const { asPath } = useRouter();
|
||||
return (
|
||||
<li className="flex h-11 items-center">
|
||||
<li className="my-2 flex h-11 items-center">
|
||||
<Link
|
||||
href={href}
|
||||
passHref
|
||||
className={cl(
|
||||
"hover:bg-surface-action-subtle-hover focus-visible:shadow-focus-inset relative flex h-full w-full items-center rounded px-2 focus:outline-none",
|
||||
"hover:bg-surface-action-subtle-hover focus-visible:shadow-focus relative flex h-full w-full items-center rounded px-2 focus:outline-none",
|
||||
{
|
||||
"before:bg-surface-action-selected pl-4 font-semibold before:absolute before:left-0 before:h-6 before:w-1 before:rounded-full":
|
||||
asPath.startsWith(href),
|
||||
@@ -58,6 +58,7 @@ export const Hamburger = () => {
|
||||
onClose={() => setOpen(false)}
|
||||
header={{ heading: "Meny" }}
|
||||
width="small"
|
||||
closeOnBackdropClick
|
||||
>
|
||||
<Modal.Body>
|
||||
<nav aria-label="hovedmeny">
|
||||
@@ -82,6 +83,11 @@ export const Hamburger = () => {
|
||||
name="Komponenter"
|
||||
href="/komponenter"
|
||||
/>
|
||||
<LinkElement
|
||||
onClick={() => setOpen(false)}
|
||||
name="Mønster & Maler"
|
||||
href="/monster-maler"
|
||||
/>
|
||||
<LinkElement
|
||||
onClick={() => setOpen(false)}
|
||||
name="Blogg"
|
||||
|
||||
@@ -1,33 +1,36 @@
|
||||
import AkselLogo from "@/assets/Logo";
|
||||
import { amplitudeLogNavigation } from "@/logging";
|
||||
import { MagnifyingGlassIcon } from "@navikt/aksel-icons";
|
||||
import { Button } from "@navikt/ds-react";
|
||||
import cl from "clsx";
|
||||
import Logo from "components/assets/Logo";
|
||||
import { Hamburger } from "components/layout/header/Hamburger";
|
||||
import dynamic from "next/dynamic";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { Hamburger } from "./Hamburger";
|
||||
|
||||
export const GlobalSearch = dynamic(() => import("./search"), {
|
||||
loading: () => (
|
||||
<Button
|
||||
variant="primary"
|
||||
className="hover:bg-deepblue-700 bg-deepblue-600 h-11"
|
||||
aria-keyshortcuts="Control+b"
|
||||
icon={
|
||||
<MagnifyingGlassIcon
|
||||
className="pointer-events-none -mt-[1px] shrink-0 text-2xl"
|
||||
aria-label="Åpne meny"
|
||||
aria-hidden
|
||||
/>
|
||||
}
|
||||
iconPosition="left"
|
||||
>
|
||||
Søk
|
||||
</Button>
|
||||
),
|
||||
ssr: false,
|
||||
});
|
||||
export const GlobalSearch = dynamic(
|
||||
() => import("../../website-modules/search/Search"),
|
||||
{
|
||||
loading: () => (
|
||||
<Button
|
||||
variant="primary"
|
||||
className="hover:bg-deepblue-700 bg-deepblue-600 h-11"
|
||||
aria-keyshortcuts="Control+b"
|
||||
icon={
|
||||
<MagnifyingGlassIcon
|
||||
className="pointer-events-none -mt-[1px] shrink-0 text-2xl"
|
||||
aria-label="Åpne meny"
|
||||
aria-hidden
|
||||
/>
|
||||
}
|
||||
iconPosition="left"
|
||||
>
|
||||
Søk
|
||||
</Button>
|
||||
),
|
||||
ssr: false,
|
||||
}
|
||||
);
|
||||
|
||||
const LinkElement = ({ name, href, prefetch = undefined }) => {
|
||||
const { asPath } = useRouter();
|
||||
@@ -55,7 +58,7 @@ const LinkElement = ({ name, href, prefetch = undefined }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const Header = ({
|
||||
const Header = ({
|
||||
variant = "default",
|
||||
}: {
|
||||
variant?: "blogg" | "subtle" | "default" | "transparent";
|
||||
@@ -90,7 +93,7 @@ export const Header = ({
|
||||
}
|
||||
className="focus-visible:shadow-focus grid h-full place-items-center rounded px-2 focus:outline-none"
|
||||
>
|
||||
<Logo className="text-deepblue-800" />
|
||||
<AkselLogo className="text-deepblue-800" />
|
||||
<span className="sr-only">Aksel</span>
|
||||
</Link>
|
||||
</div>
|
||||
@@ -103,6 +106,7 @@ export const Header = ({
|
||||
<LinkElement name="Grunnleggende" href="/grunnleggende" />
|
||||
<LinkElement name="Ikoner" href="/ikoner" prefetch={false} />
|
||||
<LinkElement name="Komponenter" href="/komponenter" />
|
||||
<LinkElement name="Mønster & Maler" href="/monster-maler" />
|
||||
<LinkElement name="Bloggen" href="/produktbloggen" />
|
||||
</ul>
|
||||
</nav>
|
||||
@@ -116,3 +120,5 @@ export const Header = ({
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
@@ -1 +0,0 @@
|
||||
export { default as Footer } from "./footer/Footer";
|
||||
@@ -1,136 +1,79 @@
|
||||
import { amplitudeLogNavigation } from "@/logging";
|
||||
import { AkselSidebarT } from "@/types";
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
SparklesIcon,
|
||||
TestFlaskIcon,
|
||||
TrashIcon,
|
||||
} from "@navikt/aksel-icons";
|
||||
import { Detail } from "@navikt/ds-react";
|
||||
import cl from "clsx";
|
||||
import { SidebarT } from "@/types";
|
||||
import { StatusTag } from "@/web/StatusTag";
|
||||
import { Box, Label, Show } from "@navikt/ds-react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { useState } from "react";
|
||||
import { useSection } from "./useSection";
|
||||
|
||||
const NavItem = ({
|
||||
link,
|
||||
const Sidebar = ({
|
||||
kategori,
|
||||
links,
|
||||
}: {
|
||||
link: { heading: string; slug: string; kategori: string; tag?: string };
|
||||
kategori: "komponenter" | "grunnleggende" | "templates";
|
||||
links: SidebarT;
|
||||
}) => {
|
||||
const { asPath } = useRouter();
|
||||
const isActive = asPath.split("#")[0] === `/${link.slug}`;
|
||||
|
||||
return (
|
||||
<li>
|
||||
<Link
|
||||
aria-current={isActive ? "page" : undefined}
|
||||
href={`/${link.slug}`}
|
||||
onClick={(e) => {
|
||||
amplitudeLogNavigation("meny", e.currentTarget.getAttribute("href"));
|
||||
}}
|
||||
className={cl(
|
||||
"hover:text-deepblue-800 focus-visible:shadow-focus text-medium pr-05 relative flex overflow-hidden py-1 pl-4 leading-snug before:rounded-full hover:before:transition-colors focus:outline-none focus-visible:z-10 focus-visible:rounded-sm",
|
||||
{
|
||||
"before:border-l-border-action-selected before:absolute before:left-0 before:top-1/2 before:h-6 before:-translate-y-1/2 before:border-l-[4px]":
|
||||
isActive,
|
||||
"text-deepblue-800 font-semibold": isActive,
|
||||
"text-text-subtle": !isActive,
|
||||
"before:absolute before:left-0 before:top-0 before:h-full before:border-l before:border-l-gray-200 hover:before:top-1/2 hover:before:h-6 hover:before:min-h-[70%] hover:before:-translate-y-1/2 hover:before:border-l-2 hover:before:border-l-gray-400":
|
||||
!isActive,
|
||||
}
|
||||
)}
|
||||
<Show asChild above="md">
|
||||
<Box
|
||||
paddingBlock="0 8"
|
||||
paddingInline="2"
|
||||
aria-label={kategori}
|
||||
as="nav"
|
||||
className="w-sidebar shrink-0 self-start"
|
||||
>
|
||||
{link.heading}
|
||||
<span className="ml-auto capitalize">
|
||||
{link?.tag === "beta" && (
|
||||
<TestFlaskIcon
|
||||
aria-hidden
|
||||
className="text-violet-800"
|
||||
fontSize="1.25rem"
|
||||
/>
|
||||
)}
|
||||
{link?.tag === "new" && (
|
||||
<SparklesIcon
|
||||
aria-hidden
|
||||
className="text-lightblue-700"
|
||||
fontSize="1.25rem"
|
||||
/>
|
||||
)}
|
||||
{link?.tag === "deprecated" && (
|
||||
<TrashIcon
|
||||
aria-hidden
|
||||
className="text-text-default"
|
||||
fontSize="1.25rem"
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</Link>
|
||||
</li>
|
||||
<ul className="space-y-6">
|
||||
{links.map((section) => (
|
||||
<Section key={section.value} section={section} />
|
||||
))}
|
||||
</ul>
|
||||
</Box>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
const Dropdown = ({
|
||||
title,
|
||||
links,
|
||||
}: {
|
||||
title: string;
|
||||
links: AkselSidebarT;
|
||||
}) => {
|
||||
const [open, setOpen] = useState(true);
|
||||
function Section({ section }: { section: SidebarT[number] }) {
|
||||
const { asPath } = useRouter();
|
||||
|
||||
const isActive = (slug: string) => {
|
||||
return asPath.split("#")[0] === `/${slug}`;
|
||||
};
|
||||
return (
|
||||
<li className="peer w-full peer-data-[open=true]:mt-6" data-open={open}>
|
||||
<button
|
||||
onClick={() => setOpen((x) => !x)}
|
||||
className="min-h-8 text-text-subtle hover:text-deepblue-800 group z-10 flex w-full cursor-pointer items-center justify-between focus:outline-none"
|
||||
aria-expanded={open}
|
||||
<li>
|
||||
<Label
|
||||
as="div"
|
||||
size="small"
|
||||
textColor="subtle"
|
||||
className="min-h-8 flex items-center px-4"
|
||||
>
|
||||
<Detail
|
||||
as="span"
|
||||
className="group-focus-visible:shadow-focus group-hover:bg-surface-neutral-subtle group-active:bg-surface-neutral-subtle-hover mt-6 flex w-full items-center justify-between rounded-sm pl-2 font-semibold transition-colors first:mt-0"
|
||||
>
|
||||
{title}
|
||||
<span className="flex h-6 w-6 items-center justify-center rounded">
|
||||
<ChevronDownIcon
|
||||
className={cl("text-base", { "rotate-180": open })}
|
||||
aria-hidden
|
||||
title={!open ? `åpne ${title}` : `lukk ${title}`}
|
||||
/>
|
||||
</span>
|
||||
</Detail>
|
||||
</button>
|
||||
|
||||
<ul hidden={!open} className="pl-2">
|
||||
{links.map((z) => (
|
||||
<NavItem link={z} key={z?.heading} />
|
||||
{section.title}
|
||||
</Label>
|
||||
<ul className="space-y-px pl-3">
|
||||
{section.pages.map((page) => (
|
||||
<li
|
||||
aria-current={isActive(page.slug) ? "page" : undefined}
|
||||
key={page.slug}
|
||||
data-active={isActive(page.slug)}
|
||||
className="rounded-medium hover:bg-surface-hover min-h-8 data-active:bg-surface-selected data-active:text-deepblue-800 data-active:shadow-xsmall data-active:font-semibold relative mx-2 flex items-center transition-[background-color] ease-out"
|
||||
>
|
||||
<Link
|
||||
prefetch={false}
|
||||
href={`/${page.slug}`}
|
||||
className="text-medium min-h-8 rounded-medium focus-visible:shadow-focus flex w-full items-center justify-between gap-2 break-words px-2 focus:outline-none focus-visible:z-10"
|
||||
onClick={(e) => {
|
||||
amplitudeLogNavigation(
|
||||
"meny",
|
||||
e.currentTarget.getAttribute("href")
|
||||
);
|
||||
}}
|
||||
>
|
||||
{page.heading}
|
||||
<StatusTag size="xsmall" status={page.tag} />
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export const Sidebar = ({
|
||||
kategori,
|
||||
links,
|
||||
}: {
|
||||
kategori: "Komponenter" | "Grunnleggende";
|
||||
links: AkselSidebarT;
|
||||
}) => {
|
||||
const sections = useSection({ kategori, links });
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="ds-sidebar"
|
||||
className="toc-ignore w-sidebar bg-surface-default z-[1002] hidden shrink-0 self-start overflow-x-auto pb-8 pl-6 pr-2 md:block"
|
||||
>
|
||||
<nav aria-label={kategori}>
|
||||
<ul>
|
||||
{sections.map((kat) => (
|
||||
<Dropdown links={kat.pages} title={kat.title} key={kat.title} />
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default Sidebar;
|
||||
@@ -1,52 +0,0 @@
|
||||
import { AkselSidebarT } from "@/types";
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
grunnleggendeKategorier,
|
||||
komponentKategorier,
|
||||
} from "../../../sanity/config";
|
||||
|
||||
export const useSection = ({
|
||||
kategori,
|
||||
links,
|
||||
}: {
|
||||
kategori: "Komponenter" | "Grunnleggende";
|
||||
links: AkselSidebarT;
|
||||
}) => {
|
||||
const sections = useMemo(
|
||||
() =>
|
||||
(kategori === "Komponenter"
|
||||
? komponentKategorier
|
||||
: grunnleggendeKategorier
|
||||
)
|
||||
.map((x) => ({
|
||||
...x,
|
||||
pages: links
|
||||
.filter((y) => y?.kategori === x.value)
|
||||
.sort((a, b) => {
|
||||
if (a?.tag === "deprecated" && b?.tag === "deprecated") {
|
||||
return 0;
|
||||
} else if (a?.tag === "deprecated") {
|
||||
return 1;
|
||||
} else if (b.tag === "deprecated") {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a.sidebarindex !== null || b.sidebarindex !== null) {
|
||||
if (a.sidebarindex !== null && b.sidebarindex !== null) {
|
||||
return a.sidebarindex - b.sidebarindex;
|
||||
} else if (a.sidebarindex !== null) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return a?.heading.localeCompare(b?.heading);
|
||||
}),
|
||||
}))
|
||||
.filter((x) => !(!x.pages || x.pages.length === 0)),
|
||||
[links, kategori]
|
||||
);
|
||||
|
||||
return sections;
|
||||
};
|
||||
+43
-50
@@ -1,7 +1,7 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { WithSidebar } from "./WithSidebar";
|
||||
import { SanityBlockContent } from "@/sanity-block";
|
||||
import { getBlocks } from "@/sb-util";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { WithSidebar } from "./WithSidebar";
|
||||
|
||||
const meta = {
|
||||
title: "Layout/WithSidebar",
|
||||
@@ -24,40 +24,53 @@ const children = (
|
||||
</>
|
||||
);
|
||||
|
||||
const baseItem = { slug: "/123", tag: "beta" as const, sidebarindex: null };
|
||||
|
||||
const sidebar = [
|
||||
{
|
||||
pages: [
|
||||
{ ...baseItem, kategori: "primitives", heading: "f", sidebarindex: 0 },
|
||||
{ ...baseItem, kategori: "primitives", heading: "e", sidebarindex: 1 },
|
||||
{
|
||||
...baseItem,
|
||||
kategori: "primitives",
|
||||
heading: "d",
|
||||
tag: "deprecated" as const,
|
||||
sidebarindex: 2,
|
||||
},
|
||||
],
|
||||
title: "Primitives",
|
||||
value: "primitives",
|
||||
},
|
||||
{
|
||||
pages: [
|
||||
{ ...baseItem, kategori: "core", heading: "b" },
|
||||
{
|
||||
...baseItem,
|
||||
kategori: "core",
|
||||
heading: "c",
|
||||
tag: "deprecated" as const,
|
||||
},
|
||||
],
|
||||
title: "Core",
|
||||
value: "core",
|
||||
},
|
||||
];
|
||||
|
||||
export const KomponenterLanding: Story = {
|
||||
args: {
|
||||
children,
|
||||
pageType: {
|
||||
type: "Komponenter",
|
||||
type: "komponenter",
|
||||
title: "Heading",
|
||||
rootUrl: "/komponenter",
|
||||
rootTitle: "Komponenter",
|
||||
},
|
||||
pageProps: { content: "1" },
|
||||
variant: "landingPage",
|
||||
footer: "footer",
|
||||
intro: "intro",
|
||||
sidebar: [
|
||||
{
|
||||
heading: "Link 1",
|
||||
slug: "#",
|
||||
kategori: "core",
|
||||
tag: "ready",
|
||||
sidebarindex: null,
|
||||
},
|
||||
{
|
||||
heading: "Link 2",
|
||||
slug: "#",
|
||||
kategori: "primitives",
|
||||
tag: "ready",
|
||||
sidebarindex: null,
|
||||
},
|
||||
{
|
||||
heading: "Link e",
|
||||
slug: "#",
|
||||
kategori: "primitives",
|
||||
tag: "ready",
|
||||
sidebarindex: null,
|
||||
},
|
||||
],
|
||||
sidebar,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -65,35 +78,15 @@ export const Komponenter: Story = {
|
||||
args: {
|
||||
children,
|
||||
pageType: {
|
||||
type: "Komponenter",
|
||||
type: "komponenter",
|
||||
title: "Heading",
|
||||
rootUrl: "/komponenter",
|
||||
rootTitle: "Komponenter",
|
||||
},
|
||||
pageProps: { content: "1" },
|
||||
variant: "page",
|
||||
footer: "footer",
|
||||
intro: "intro",
|
||||
sidebar: [
|
||||
{
|
||||
heading: "Link 1",
|
||||
slug: "#",
|
||||
kategori: "core",
|
||||
tag: "ready",
|
||||
sidebarindex: null,
|
||||
},
|
||||
{
|
||||
heading: "Link 2",
|
||||
slug: "#",
|
||||
kategori: "primitives",
|
||||
tag: "ready",
|
||||
sidebarindex: null,
|
||||
},
|
||||
{
|
||||
heading: "Link 3",
|
||||
slug: "#",
|
||||
kategori: "primitives",
|
||||
tag: "ready",
|
||||
sidebarindex: null,
|
||||
},
|
||||
],
|
||||
sidebar,
|
||||
},
|
||||
};
|
||||
+23
-15
@@ -1,10 +1,10 @@
|
||||
import Sidebar from "@/layout/sidebar/Sidebar";
|
||||
import { urlFor } from "@/sanity/interface";
|
||||
import { AkselSidebarT } from "@/types";
|
||||
import { SidebarT, TableOfContentsT } from "@/types";
|
||||
import { capitalize } from "@/utils";
|
||||
import TableOfContents from "@/web/toc/TableOfContents";
|
||||
import { Box, Detail, Heading, Link } from "@navikt/ds-react";
|
||||
import cl from "clsx";
|
||||
import { Sidebar } from "components/layout/sidebar/Sidebar";
|
||||
import { TableOfContentsv2 } from "components/website-modules/toc/TOCv2";
|
||||
import Image from "next/legacy/image";
|
||||
import NextLink from "next/link";
|
||||
|
||||
@@ -16,12 +16,16 @@ export const WithSidebar = ({
|
||||
pageProps,
|
||||
variant = "landingPage",
|
||||
footer,
|
||||
toc,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
sidebar: AkselSidebarT;
|
||||
sidebar: SidebarT;
|
||||
toc?: TableOfContentsT;
|
||||
pageType: {
|
||||
type: "Komponenter" | "Grunnleggende";
|
||||
type: "komponenter" | "grunnleggende" | "templates";
|
||||
title: string;
|
||||
rootUrl: string;
|
||||
rootTitle: string;
|
||||
};
|
||||
pageProps: any;
|
||||
intro?: React.ReactNode;
|
||||
@@ -29,13 +33,17 @@ export const WithSidebar = ({
|
||||
variant?: "page" | "landingPage";
|
||||
}) => {
|
||||
return (
|
||||
<Box background="bg-default" paddingBlock="6 24">
|
||||
<Box
|
||||
background="bg-default"
|
||||
paddingBlock="6 24"
|
||||
className="min-h-screen-header"
|
||||
>
|
||||
<div className="mx-auto flex w-full max-w-screen-2xl gap-6">
|
||||
<Sidebar kategori={pageType.type} links={sidebar} />
|
||||
<main
|
||||
tabIndex={-1}
|
||||
id="hovedinnhold"
|
||||
className="md:max-w-screen-sidebar relative z-0 w-full px-4 focus:outline-none sm:pl-6 sm:pr-6 md:pl-0"
|
||||
className="relative z-0 w-full px-4 focus:outline-none sm:pl-6 sm:pr-6 md:pl-0"
|
||||
>
|
||||
<div
|
||||
className={cl(
|
||||
@@ -52,13 +60,9 @@ export const WithSidebar = ({
|
||||
<div className="z-[1]">
|
||||
{variant === "page" && pageProps?.kategori && (
|
||||
<Detail as="div" className="mb-2">
|
||||
<NextLink
|
||||
href={`/${pageType.type.toLowerCase()}`}
|
||||
passHref
|
||||
legacyBehavior
|
||||
>
|
||||
<NextLink href={pageType.rootUrl} passHref legacyBehavior>
|
||||
<Link className="text-text-default">
|
||||
{capitalize(pageType.type)}
|
||||
{pageType.rootTitle}
|
||||
</Link>
|
||||
</NextLink>{" "}
|
||||
/ {capitalize(pageProps.kategori)}
|
||||
@@ -109,8 +113,12 @@ export const WithSidebar = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={cl("sm:px-6 md:px-10", { flex: variant === "page" })}>
|
||||
{variant === "page" && <TableOfContentsv2 />}
|
||||
<div
|
||||
className={cl("sm:px-6 md:px-10", {
|
||||
"flex gap-10": variant === "page",
|
||||
})}
|
||||
>
|
||||
{variant === "page" && toc && <TableOfContents toc={toc} />}
|
||||
<div className="w-full">
|
||||
{children}
|
||||
{footer && <div className="w-full">{footer}</div>}
|
||||
File renamed without changes.
File renamed without changes.
+1
-1
@@ -1,3 +1,3 @@
|
||||
export { amplitudeLogNavigation } from "./event-tracking";
|
||||
export { AmplitudeEvents } from "./events";
|
||||
export { amplitude } from "./useAmplitude";
|
||||
export { default as useAmplitudeInit, amplitude } from "./useAmplitude";
|
||||
+3
-2
@@ -29,10 +29,11 @@ const useAmplitudeInit = () => {
|
||||
useEffect(() => {
|
||||
const isProdUrl = () => window.location.host === "aksel.nav.no";
|
||||
const isExample = () => window.location.pathname.includes("eksempler/");
|
||||
const isTemplate = () => window.location.pathname.includes("templates/");
|
||||
const isPreview = () => !!document.getElementById("exit-preview-id");
|
||||
|
||||
const initAmplitude = async () => {
|
||||
if (isExample()) {
|
||||
if (isExample() || isTemplate()) {
|
||||
return;
|
||||
} else if (!isProdUrl()) {
|
||||
mockAmplitude();
|
||||
@@ -59,7 +60,7 @@ const useAmplitudeInit = () => {
|
||||
.forEach(([event, eventData]) => amplitude.track(event, eventData))
|
||||
)
|
||||
.catch(async () => {
|
||||
const { logger } = await import("logger");
|
||||
const { logger } = await import("../../config/logger");
|
||||
logger.error("Failed logging batched events");
|
||||
});
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import ErrorBoundary from "@/error-boundary";
|
||||
import { useFormatedDate } from "@/hooks/useFormatedDate";
|
||||
|
||||
import { amplitudeLogNavigation } from "@/logging";
|
||||
import {
|
||||
@@ -7,10 +8,9 @@ import {
|
||||
ResolveSlugT,
|
||||
ResolveTemaT,
|
||||
} from "@/types";
|
||||
import { abbrName } from "@/utils";
|
||||
import { BodyShort, Detail, Heading } from "@navikt/ds-react";
|
||||
import { useFormatedDate } from "components/website-modules/utils/getDate";
|
||||
import NextLink from "next/link";
|
||||
import { abbrName } from "../..";
|
||||
|
||||
type ArtikkelCardProps = ResolveContributorsSingleT<
|
||||
ResolveTemaT<ResolveSlugT<AkselGodPraksisDocT>>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import ErrorBoundary from "@/error-boundary";
|
||||
import { useFormatedDate } from "@/hooks/useFormatedDate";
|
||||
import { amplitudeLogNavigation } from "@/logging";
|
||||
import { AkselBloggDocT, ResolveContributorsT, ResolveSlugT } from "@/types";
|
||||
import { getAuthors } from "@/utils";
|
||||
import { BodyLong, BodyShort, Heading, Link } from "@navikt/ds-react";
|
||||
import { useFormatedDate } from "components/website-modules/utils/getDate";
|
||||
import NextLink from "next/link";
|
||||
|
||||
type BloggCardProps = {
|
||||
|
||||
@@ -15,8 +15,10 @@ export const Designsystem: Story = {
|
||||
args: {
|
||||
node: {
|
||||
title: "button",
|
||||
|
||||
dir: {
|
||||
title: "button",
|
||||
variant: "templates",
|
||||
filer: [
|
||||
{
|
||||
_key: getKey(),
|
||||
@@ -24,6 +26,9 @@ export const Designsystem: Story = {
|
||||
innhold:
|
||||
'import { Button } from "@navikt/ds-react";\n\nconst Example = () => {\n return (\n <div className="flex flex-wrap gap-2">\n <Button variant="primary">Primary</Button>\n <Button variant="secondary">Secondary</Button>\n <Button variant="tertiary">Tertiary</Button>\n <Button variant="danger">Danger</Button>\n </div>\n );\n};',
|
||||
navn: "default",
|
||||
title: "title",
|
||||
index: 1,
|
||||
sandboxBase64: "fakebase64",
|
||||
},
|
||||
{
|
||||
_key: getKey(),
|
||||
@@ -31,6 +36,9 @@ export const Designsystem: Story = {
|
||||
innhold:
|
||||
'import { Button } from "@navikt/ds-react";\n\nconst Example = () => {\n return (\n <div className="flex flex-wrap gap-2">\n <Button variant="primary-neutral">Primary</Button>\n <Button variant="secondary-neutral">Secondary</Button>\n <Button variant="tertiary-neutral">Tertiary</Button>\n </div>\n );\n};',
|
||||
navn: "neutral",
|
||||
title: "title",
|
||||
index: 1,
|
||||
sandboxBase64: "fakebase64",
|
||||
},
|
||||
{
|
||||
_key: getKey(),
|
||||
@@ -38,6 +46,9 @@ export const Designsystem: Story = {
|
||||
innhold:
|
||||
'import { PencilIcon } from "@navikt/aksel-icons";\nimport { Button } from "@navikt/ds-react";\n\nconst Example = () => {\n return (\n <div className="flex flex-wrap gap-2">\n <Button size="small" variant="primary" icon={<PencilIcon aria-hidden />}>\n Primary\n </Button>\n <Button size="small" variant="secondary">\n Secondary\n </Button>\n <Button size="small" variant="tertiary">\n Tertiary\n </Button>\n <Button size="small" variant="danger">\n Danger\n </Button>\n </div>\n );\n};',
|
||||
navn: "small",
|
||||
title: "title",
|
||||
index: 1,
|
||||
sandboxBase64: "fakebase64",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { capitalize } from "@/components";
|
||||
import SnippetLazy from "@/cms/code-snippet/SnippetLazy";
|
||||
import ErrorBoundary from "@/error-boundary";
|
||||
import { CodeExamplesT } from "@/types";
|
||||
import { capitalize } from "@/utils";
|
||||
import {
|
||||
ExternalLinkIcon,
|
||||
LaptopIcon,
|
||||
@@ -8,9 +9,9 @@ import {
|
||||
} from "@navikt/aksel-icons";
|
||||
import { BodyLong, Button, Chips, HStack } from "@navikt/ds-react";
|
||||
import cl from "clsx";
|
||||
import SnippetLazy from "components/sanity-modules/code-snippet/SnippetLazy";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Sandbox } from "./parts/Sandbox";
|
||||
import { CodeSandbox } from "./parts/CodeSandbox";
|
||||
|
||||
const iframePadding = 192;
|
||||
@@ -34,11 +35,19 @@ const ComponentExamples = ({ node }: CodeExamplesProps) => {
|
||||
iframeId
|
||||
) as HTMLIFrameElement;
|
||||
const exampleIframeDOM = exampleIframe?.contentDocument;
|
||||
const exampleWrapper = exampleIframeDOM?.getElementById("ds-example");
|
||||
|
||||
let exampleWrapper: HTMLElement;
|
||||
|
||||
if (node.dir.variant === "templates") {
|
||||
exampleWrapper = exampleIframeDOM?.getElementById("__next");
|
||||
} else {
|
||||
exampleWrapper = exampleIframeDOM?.getElementById("ds-example");
|
||||
}
|
||||
|
||||
if (exampleWrapper && exampleWrapper.offsetHeight) {
|
||||
const newHeight = iframePadding + exampleWrapper.offsetHeight;
|
||||
clearInterval(waitForExampleContentToRender);
|
||||
setFrameState(newHeight < 300 ? 300 : newHeight);
|
||||
setFrameState(Math.min(Math.max(newHeight, 300), 900));
|
||||
setUnloaded(false);
|
||||
}
|
||||
|
||||
@@ -75,7 +84,7 @@ const ComponentExamples = ({ node }: CodeExamplesProps) => {
|
||||
const fixName = (str: string) =>
|
||||
capitalize(
|
||||
str
|
||||
.replace(/[^\w]|_/g, " ")
|
||||
.replace(/[^\wæøå]|_/g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim()
|
||||
) ?? str;
|
||||
@@ -85,6 +94,7 @@ const ComponentExamples = ({ node }: CodeExamplesProps) => {
|
||||
}
|
||||
|
||||
const active = activeExample ?? node?.dir?.filer?.[0]?.navn;
|
||||
const demoVariant = node.dir?.variant;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -107,7 +117,7 @@ const ComponentExamples = ({ node }: CodeExamplesProps) => {
|
||||
);
|
||||
}}
|
||||
>
|
||||
{fixName(fil.navn)}
|
||||
{fixName(fil.title)}
|
||||
</Chips.Toggle>
|
||||
);
|
||||
})}
|
||||
@@ -138,7 +148,7 @@ const ComponentExamples = ({ node }: CodeExamplesProps) => {
|
||||
)}
|
||||
>
|
||||
<iframe
|
||||
src={`/eksempler/${node.dir.title}/${fil.navn.replace(
|
||||
src={`/${demoVariant}/${node.dir.title}/${fil.navn.replace(
|
||||
".tsx",
|
||||
""
|
||||
)}`}
|
||||
@@ -148,7 +158,9 @@ const ComponentExamples = ({ node }: CodeExamplesProps) => {
|
||||
aria-label={`${node?.dir?.title} ${fil.navn} eksempel`}
|
||||
className={cl(
|
||||
"min-w-80 block w-full max-w-full resize-x bg-white shadow-[20px_0_20px_-20px_rgba(0,0,0,0.22)]",
|
||||
{ invisible: unloaded }
|
||||
{
|
||||
invisible: unloaded,
|
||||
}
|
||||
)}
|
||||
title="Kode-eksempler"
|
||||
ref={iframeRef}
|
||||
@@ -189,6 +201,9 @@ const ComponentExamples = ({ node }: CodeExamplesProps) => {
|
||||
</div>
|
||||
|
||||
<HStack gap="2">
|
||||
{fil?.sandboxEnabled && (
|
||||
<Sandbox code={fil?.sandboxBase64} />
|
||||
)}
|
||||
<CodeSandbox code={fil.innhold.trim()} />
|
||||
<Button
|
||||
variant="tertiary-neutral"
|
||||
@@ -199,10 +214,9 @@ const ComponentExamples = ({ node }: CodeExamplesProps) => {
|
||||
target="_blank"
|
||||
className="si-ignore"
|
||||
as="a"
|
||||
href={`/eksempler/${node.dir.title}/${fil.navn.replace(
|
||||
".tsx",
|
||||
""
|
||||
)}`}
|
||||
href={`/${demoVariant}/${
|
||||
node.dir.title
|
||||
}/${fil.navn.replace(".tsx", "")}`}
|
||||
/>
|
||||
</HStack>
|
||||
</HStack>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PencilIcon } from "@navikt/aksel-icons";
|
||||
import { CodeSandboxLogo } from "@/assets/Icons";
|
||||
import { Button } from "@navikt/ds-react";
|
||||
import { getParameters } from "codesandbox/lib/api/define";
|
||||
|
||||
@@ -58,11 +58,10 @@ export const CodeSandbox = ({ code }: { code: string }) => {
|
||||
<Button
|
||||
variant="tertiary-neutral"
|
||||
size="small"
|
||||
aria-label="Codesandbox"
|
||||
type="submit"
|
||||
icon={<PencilIcon aria-hidden fontSize="1.5rem" />}
|
||||
>
|
||||
CodeSandbox
|
||||
</Button>
|
||||
icon={<CodeSandboxLogo aria-hidden />}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
import { PencilIcon } from "@navikt/aksel-icons";
|
||||
import { Button } from "@navikt/ds-react";
|
||||
|
||||
export const Sandbox = ({ code }: { code: string }) => {
|
||||
return (
|
||||
<Button
|
||||
href={`/sandbox/index.html?code=${code}`}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
as="a"
|
||||
variant="tertiary-neutral"
|
||||
size="small"
|
||||
icon={<PencilIcon aria-hidden fontSize="1.5rem" />}
|
||||
>
|
||||
Sandbox
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
+1
-1
@@ -1,8 +1,8 @@
|
||||
import { urlFor } from "@/sanity/interface";
|
||||
import { ArticleListT } from "@/types";
|
||||
import { StatusTag } from "@/web/StatusTag";
|
||||
import { Heading } from "@navikt/ds-react";
|
||||
import cl from "clsx";
|
||||
import { StatusTag } from "components/website-modules/StatusTag";
|
||||
import Image from "next/legacy/image";
|
||||
import Nextlink from "next/link";
|
||||
|
||||
|
||||
+3
-3
@@ -1,14 +1,13 @@
|
||||
import ErrorBoundary from "@/error-boundary";
|
||||
import { amplitudeLogNavigation } from "@/logging";
|
||||
import { urlFor } from "@/sanity/interface";
|
||||
import { abbrName } from "@/utils";
|
||||
import { abbrName, getImage } from "@/utils";
|
||||
import { BodyShort, Detail, Heading } from "@navikt/ds-react";
|
||||
import cl from "clsx";
|
||||
import { getImage } from "components/website-modules/utils/get-image";
|
||||
import { useFormatedDate } from "components/website-modules/utils/getDate";
|
||||
import NextImage from "next/legacy/image";
|
||||
import NextLink from "next/link";
|
||||
import { Tag } from "./Tag";
|
||||
import { useFormatedDate } from "@/hooks/useFormatedDate";
|
||||
|
||||
export type ArticleT = {
|
||||
_key: string;
|
||||
@@ -43,6 +42,7 @@ const Card = ({ article, visible, index }: CardProps) => {
|
||||
"ds_artikkel",
|
||||
"komponent_artikkel",
|
||||
"aksel_blogg",
|
||||
"templates_artikkel",
|
||||
].includes(article._type);
|
||||
|
||||
return (
|
||||
|
||||
+2
-3
@@ -1,14 +1,13 @@
|
||||
import { amplitudeLogNavigation } from "@/logging";
|
||||
import { urlFor } from "@/sanity/interface";
|
||||
import { getAuthors } from "@/utils";
|
||||
import { getAuthors, getImage } from "@/utils";
|
||||
import { BodyLong, BodyShort, Heading, Link } from "@navikt/ds-react";
|
||||
import cl from "clsx";
|
||||
import { getImage } from "components/website-modules/utils/get-image";
|
||||
import { useFormatedDate } from "components/website-modules/utils/getDate";
|
||||
import Image from "next/legacy/image";
|
||||
import NextLink from "next/link";
|
||||
import { ArticleT } from "./Card";
|
||||
import { Tag } from "./Tag";
|
||||
import { useFormatedDate } from "@/hooks/useFormatedDate";
|
||||
|
||||
export const Highlight = ({
|
||||
article,
|
||||
|
||||
+21
-2
@@ -1,4 +1,4 @@
|
||||
import { Heading, Label, Tag as DsTag } from "@navikt/ds-react";
|
||||
import { Tag as DsTag, Heading, Label } from "@navikt/ds-react";
|
||||
import cl from "clsx";
|
||||
|
||||
export const Tag = ({
|
||||
@@ -65,7 +65,26 @@ export const Tag = ({
|
||||
<Comp className="text-deepblue-600 flex items-center gap-3">
|
||||
Grunnleggende
|
||||
{beta && (
|
||||
<DsTag variant="alt1" size="small">
|
||||
<DsTag
|
||||
variant="alt1"
|
||||
size="small"
|
||||
className="border-violet-300 bg-violet-50"
|
||||
>
|
||||
Beta
|
||||
</DsTag>
|
||||
)}
|
||||
</Comp>
|
||||
);
|
||||
case "templates_artikkel":
|
||||
return (
|
||||
<Comp className="text-deepblue-600 flex items-center gap-3">
|
||||
Mønster og Maler
|
||||
{beta && (
|
||||
<DsTag
|
||||
variant="alt1"
|
||||
size="small"
|
||||
className="border-violet-300 bg-violet-50"
|
||||
>
|
||||
Beta
|
||||
</DsTag>
|
||||
)}
|
||||
|
||||
@@ -9,11 +9,11 @@ export const DtList = ({ prop, parent }: { prop: any; parent: string }) => {
|
||||
return (
|
||||
<Detail
|
||||
as="div"
|
||||
className="block overflow-x-auto border border-t-0 border-gray-300 p-2 font-mono first-of-type:border-t last-of-type:rounded-b"
|
||||
className="block overflow-x-auto border border-t-0 border-gray-300 p-2 first-of-type:border-t last-of-type:rounded-b"
|
||||
>
|
||||
<dt>
|
||||
{!prop.defaultValue ? (
|
||||
<span className="font-semibold">{`${prop.name}${
|
||||
<span className="font-mono font-semibold">{`${prop.name}${
|
||||
prop?.required ? "" : "?"
|
||||
} `}</span>
|
||||
) : (
|
||||
@@ -22,21 +22,21 @@ export const DtList = ({ prop, parent }: { prop: any; parent: string }) => {
|
||||
arrow={false}
|
||||
delay={0}
|
||||
>
|
||||
<span className="mr-2 cursor-pointer border-b border-dashed border-gray-600 font-semibold">{`${
|
||||
<span className="mr-2 cursor-pointer border-b border-dashed border-gray-600 font-mono font-semibold">{`${
|
||||
prop.name
|
||||
}${prop?.required ? "" : "?"}`}</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
<span>{prop.type ? <>{Highlighter({ type: prop.type })}</> : ""}</span>
|
||||
<span className="font-mono">
|
||||
{prop.type ? <>{Highlighter({ type: prop.type })}</> : ""}
|
||||
</span>
|
||||
</dt>
|
||||
{prop.description && (
|
||||
<dd className="whitespace-pre-wrap font-sans text-base">
|
||||
{prop.description}
|
||||
</dd>
|
||||
<dd className="whitespace-pre-wrap text-base">{prop.description}</dd>
|
||||
)}
|
||||
{prop.name === "ref" && prop.type.includes("Ref<") && (
|
||||
<dd className="font-sans text-base">
|
||||
<dd className="text-base">
|
||||
{`${parent} extends ${prop.type.slice(
|
||||
prop.type.indexOf("<") + 1,
|
||||
prop.type.lastIndexOf(">")
|
||||
|
||||
@@ -14,7 +14,7 @@ const PropTable = ({ komponent }: PropTableProps) => {
|
||||
size="xsmall"
|
||||
level="3"
|
||||
className="scroll-m-8 rounded-t-lg border border-b-0 border-gray-300 bg-gray-50 p-2"
|
||||
id={`${komponent._key}`}
|
||||
id={komponent._key}
|
||||
>
|
||||
{komponent?.title ? komponent.title : "Props"}
|
||||
</Heading>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import ErrorBoundary from "@/error-boundary";
|
||||
import { UUTableT } from "@/types";
|
||||
import KBD from "components/website-modules/KBD";
|
||||
import KBD from "@/web/KBD";
|
||||
|
||||
import { AkselTable, AkselTableRow } from "components/website-modules/Table";
|
||||
import { AkselTable, AkselTableRow } from "@/web/Table";
|
||||
import React from "react";
|
||||
|
||||
type UUTableProps = { node: UUTableT };
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import ErrorBoundary from "@/error-boundary";
|
||||
import { TokenTableT } from "@/types";
|
||||
import { AkselTable, AkselTableRow } from "@/web/Table";
|
||||
import core from "@navikt/ds-css/tokens.json";
|
||||
import { Bleed, BodyLong, CopyButton, Label, Link } from "@navikt/ds-react";
|
||||
import { AkselTable, AkselTableRow } from "components/website-modules/Table";
|
||||
import NextLink from "next/link";
|
||||
import ShowMore from "./parts/ShowMore";
|
||||
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
import { CheckmarkIcon, ClipboardIcon } from "@navikt/aksel-icons";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import cl from "clsx";
|
||||
|
||||
export const Copy = ({ text, copyText }) => {
|
||||
const [active, setActive] = useState(false);
|
||||
const [canfade, setCanfade] = useState(false);
|
||||
const timeoutRef = useRef<number>();
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
timeoutRef.current && clearTimeout(timeoutRef.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleClick = () => {
|
||||
timeoutRef.current && clearTimeout(timeoutRef.current);
|
||||
setCanfade(true);
|
||||
navigator.clipboard.writeText(copyText);
|
||||
setActive(true);
|
||||
|
||||
timeoutRef.current = window.setTimeout(() => {
|
||||
setActive(false);
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
aria-live="polite"
|
||||
className="py-05 focus-visible:shadow-focus min-h-8 text-medium hover:bg-surface-neutral-subtle-hover group rounded px-1 leading-none focus:outline-none"
|
||||
onClick={handleClick}
|
||||
>
|
||||
<span className="inline-flex flex-row-reverse items-center gap-1">
|
||||
<span>
|
||||
{active ? (
|
||||
<CheckmarkIcon title="Kopiert" className="animate-textbounce" />
|
||||
) : (
|
||||
<ClipboardIcon
|
||||
title="Kopier css-variabel"
|
||||
className={cl(canfade && "animate-fadeIn")}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
<span aria-live="polite">{text}</span>
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
+1
-1
@@ -1,3 +1,4 @@
|
||||
import { AkselTable, AkselTableRow } from "@/web/Table";
|
||||
import {
|
||||
LaptopIcon,
|
||||
MobileIcon,
|
||||
@@ -6,7 +7,6 @@ import {
|
||||
TabletIcon,
|
||||
} from "@navikt/aksel-icons";
|
||||
import docs from "@navikt/ds-tokens/docs.json";
|
||||
import { AkselTable, AkselTableRow } from "components/website-modules/Table";
|
||||
|
||||
export const BreakpointsView = ({ cat }: { cat: string }) => {
|
||||
const breakpoints = docs[cat];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AkselTable, AkselTableRow } from "@/web/Table";
|
||||
import docs from "@navikt/ds-tokens/docs.json";
|
||||
import { sanitizeName } from "../utilities";
|
||||
import { AkselTable, AkselTableRow } from "components/website-modules/Table";
|
||||
|
||||
export const FontView = ({ cat }: { cat: string }) => {
|
||||
const fonts = docs[cat];
|
||||
|
||||
+7
-2
@@ -1,7 +1,7 @@
|
||||
import docs from "@navikt/ds-tokens/docs.json";
|
||||
import { Grid } from "../Grid";
|
||||
import { getColorString, sanitizeName } from "../utilities";
|
||||
import { Copy } from "../Copy";
|
||||
import { CopyButton } from "@navikt/ds-react";
|
||||
|
||||
export const GlobalView = ({ cat }: { cat: string }) => {
|
||||
const colors: { name: string; value: string }[] = docs[cat];
|
||||
@@ -21,7 +21,12 @@ export const GlobalView = ({ cat }: { cat: string }) => {
|
||||
</div>
|
||||
<dl className="grid h-full">
|
||||
<dt className="inline-flex items-center gap-2">
|
||||
<Copy text={sanitizeName(x.name)} copyText={x.name} />
|
||||
<CopyButton
|
||||
text={sanitizeName(x.name)}
|
||||
copyText={x.name}
|
||||
size="small"
|
||||
iconPosition="right"
|
||||
/>
|
||||
</dt>
|
||||
<dd className="text-text-subtle text-medium mt-auto">
|
||||
{getColorString(x.value)}
|
||||
|
||||
+20
-8
@@ -2,9 +2,9 @@ import { PersonCircleIcon } from "@navikt/aksel-icons";
|
||||
import docs from "@navikt/ds-tokens/docs.json";
|
||||
import Link from "next/link";
|
||||
import color from "tinycolor2";
|
||||
import { Copy } from "../Copy";
|
||||
import { Grid } from "../Grid";
|
||||
import { getColorString, getGlobalReference, sanitizeName } from "../utilities";
|
||||
import { CopyButton } from "@navikt/ds-react";
|
||||
|
||||
export const SemanticView = ({ cat }: { cat: string }) => {
|
||||
const colors = docs[cat];
|
||||
@@ -31,9 +31,11 @@ export const SemanticView = ({ cat }: { cat: string }) => {
|
||||
</div>
|
||||
<dl className="grid h-full">
|
||||
<dt className="inline-flex items-center gap-2">
|
||||
<Copy
|
||||
text={sanitizeName(x.name.replace("text-", ""))}
|
||||
<CopyButton
|
||||
copyText={x.name}
|
||||
text={sanitizeName(x.name.replace("text-", ""))}
|
||||
size="small"
|
||||
iconPosition="right"
|
||||
/>
|
||||
</dt>
|
||||
<dd className="text-text-subtle text-medium mt-auto">
|
||||
@@ -80,9 +82,11 @@ export const SemanticView = ({ cat }: { cat: string }) => {
|
||||
</div>
|
||||
<dl className="grid h-full">
|
||||
<dt className="inline-flex items-center gap-2">
|
||||
<Copy
|
||||
<CopyButton
|
||||
text={sanitizeName(x.name.replace("icon-", ""))}
|
||||
copyText={x.name}
|
||||
size="small"
|
||||
iconPosition="right"
|
||||
/>
|
||||
</dt>
|
||||
<dd className="text-text-subtle text-medium mt-auto">
|
||||
@@ -121,9 +125,11 @@ export const SemanticView = ({ cat }: { cat: string }) => {
|
||||
/>
|
||||
<dl className="grid h-full">
|
||||
<dt className="inline-flex items-center gap-2">
|
||||
<Copy
|
||||
<CopyButton
|
||||
text={sanitizeName(x.name.replace("border-", ""))}
|
||||
copyText={x.name}
|
||||
size="small"
|
||||
iconPosition="right"
|
||||
/>
|
||||
</dt>
|
||||
<dd className="text-text-subtle text-medium mt-auto">
|
||||
@@ -191,9 +197,11 @@ export const SemanticView = ({ cat }: { cat: string }) => {
|
||||
</div>
|
||||
<dl className="grid h-full">
|
||||
<dt className="inline-flex items-center gap-2">
|
||||
<Copy
|
||||
<CopyButton
|
||||
text={sanitizeName(x.name.replace("surface-", ""))}
|
||||
copyText={x.name}
|
||||
size="small"
|
||||
iconPosition="right"
|
||||
/>
|
||||
</dt>
|
||||
<dd className="text-text-subtle text-medium mt-auto">
|
||||
@@ -243,9 +251,11 @@ export const SemanticView = ({ cat }: { cat: string }) => {
|
||||
</div>
|
||||
<dl className="grid h-full">
|
||||
<dt className="inline-flex items-center gap-2">
|
||||
<Copy
|
||||
<CopyButton
|
||||
text={sanitizeName(x.name.replace("surface-", ""))}
|
||||
copyText={x.name}
|
||||
size="small"
|
||||
iconPosition="right"
|
||||
/>
|
||||
</dt>
|
||||
<dd className="text-text-subtle text-medium mt-auto">
|
||||
@@ -286,9 +296,11 @@ export const SemanticView = ({ cat }: { cat: string }) => {
|
||||
</div>
|
||||
<dl className="grid h-full">
|
||||
<dt className="inline-flex items-center gap-2">
|
||||
<Copy
|
||||
<CopyButton
|
||||
text={sanitizeName(x.name.replace("surface-", ""))}
|
||||
copyText={x.name}
|
||||
size="small"
|
||||
iconPosition="right"
|
||||
/>
|
||||
</dt>
|
||||
<dd className="text-text-subtle text-medium mt-auto">
|
||||
|
||||
+4
-2
@@ -1,7 +1,7 @@
|
||||
import docs from "@navikt/ds-tokens/docs.json";
|
||||
import { Copy } from "../Copy";
|
||||
import { Grid } from "../Grid";
|
||||
import { sanitizeName } from "../utilities";
|
||||
import { CopyButton } from "@navikt/ds-react";
|
||||
|
||||
export const ShadowView = ({ cat }: { cat: string }) => {
|
||||
const shadows = docs[cat];
|
||||
@@ -19,9 +19,11 @@ export const ShadowView = ({ cat }: { cat: string }) => {
|
||||
/>
|
||||
<dl className="grid h-full">
|
||||
<dt className="inline-flex items-center gap-2">
|
||||
<Copy
|
||||
<CopyButton
|
||||
text={sanitizeName(x.name.replace("shadow-", ""))}
|
||||
copyText={x.name}
|
||||
size="small"
|
||||
iconPosition="right"
|
||||
/>
|
||||
</dt>
|
||||
<dd className="text-text-subtle text-medium mt-auto">
|
||||
|
||||
+4
-2
@@ -1,7 +1,7 @@
|
||||
import docs from "@navikt/ds-tokens/docs.json";
|
||||
import { Copy } from "../Copy";
|
||||
import { Grid } from "../Grid";
|
||||
import { sanitizeName } from "../utilities";
|
||||
import { CopyButton } from "@navikt/ds-react";
|
||||
|
||||
export const ShapesView = ({ cat }: { cat: string }) => {
|
||||
const shapes = docs[cat];
|
||||
@@ -17,9 +17,11 @@ export const ShapesView = ({ cat }: { cat: string }) => {
|
||||
/>
|
||||
<dl className="grid h-full">
|
||||
<dt className="inline-flex items-center gap-2">
|
||||
<Copy
|
||||
<CopyButton
|
||||
text={sanitizeName(x.name.replace("border-radius-", ""))}
|
||||
copyText={x.name}
|
||||
size="small"
|
||||
iconPosition="right"
|
||||
/>
|
||||
</dt>
|
||||
<dd className="text-text-subtle text-medium mt-auto">
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { AkselTable, AkselTableRow } from "@/web/Table";
|
||||
import docs from "@navikt/ds-tokens/docs.json";
|
||||
import { AkselTable, AkselTableRow } from "components/website-modules/Table";
|
||||
|
||||
export const SpacingView = ({ cat }: { cat: string }) => {
|
||||
const spacings = docs[cat];
|
||||
|
||||
+4
-2
@@ -1,6 +1,6 @@
|
||||
import { CopyButton } from "@navikt/ds-react";
|
||||
import docs from "@navikt/ds-tokens/docs.json";
|
||||
import cl from "clsx";
|
||||
import { Copy } from "../Copy";
|
||||
import { Grid } from "../Grid";
|
||||
import { sanitizeName } from "../utilities";
|
||||
|
||||
@@ -36,9 +36,11 @@ export const ZindexView = ({ cat }: { cat: string }) => {
|
||||
</div>
|
||||
<dl className="grid h-full">
|
||||
<dt className="inline-flex items-center gap-2">
|
||||
<Copy
|
||||
<CopyButton
|
||||
text={sanitizeName(token.name.replace("z-index-", ""))}
|
||||
copyText={token.name}
|
||||
size="small"
|
||||
iconPosition="right"
|
||||
/>
|
||||
</dt>
|
||||
<dd className="text-text-subtle text-medium mt-auto">
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+2
-2
@@ -1,4 +1,4 @@
|
||||
export * from "./sanity-schema";
|
||||
export * from "./next";
|
||||
export * from "./feedback";
|
||||
export * from "./sanity-schema";
|
||||
export * from "./search";
|
||||
export * from "./toc";
|
||||
Loaded 100 of 571 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user