mirror of
https://github.com/navikt/aksel.git
synced 2026-10-13 01:02:02 +00:00
Compare commits
40
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5d2f7eb8c | ||
|
|
67b8b6626e | ||
|
|
8783f1821d | ||
|
|
d52085f256 | ||
|
|
67e8565da3 | ||
|
|
bb4ff1afc3 | ||
|
|
16c0c15c67 | ||
|
|
f506dca7e1 | ||
|
|
a44b2e2e7c | ||
|
|
d96d368e7b | ||
|
|
d3c0052c33 | ||
|
|
f848bea71a | ||
|
|
1585106c55 | ||
|
|
5ed06e19fe | ||
|
|
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 |
No files matched your search
@@ -12,7 +12,7 @@ body:
|
||||
attributes:
|
||||
label: Lenke til side
|
||||
description: Lenke til siden hvor eksemplet er
|
||||
placeholder:
|
||||
placeholder:
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
@@ -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,15 @@
|
||||
# @navikt/aksel-icons
|
||||
|
||||
## 5.10.4
|
||||
|
||||
## 5.10.3
|
||||
|
||||
## 5.10.2
|
||||
|
||||
## 5.10.1
|
||||
|
||||
## 5.10.0
|
||||
|
||||
## 5.9.2
|
||||
|
||||
## 5.9.1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel-icons",
|
||||
"version": "5.9.2",
|
||||
"version": "5.10.4",
|
||||
"author": "Aksel | NAV",
|
||||
"homepage": "https://aksel.nav.no/ikoner",
|
||||
"repository": {
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @navikt/aksel-stylelint
|
||||
|
||||
## 5.10.4
|
||||
|
||||
## 5.10.3
|
||||
|
||||
## 5.10.2
|
||||
|
||||
## 5.10.1
|
||||
|
||||
## 5.10.0
|
||||
|
||||
## 5.9.2
|
||||
|
||||
## 5.9.1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel-stylelint",
|
||||
"version": "5.9.2",
|
||||
"version": "5.10.4",
|
||||
"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.2",
|
||||
"@navikt/ds-tokens": "^5.9.2",
|
||||
"@navikt/ds-css": "^5.10.4",
|
||||
"@navikt/ds-tokens": "^5.10.4",
|
||||
"@types/jest": "^29.0.0",
|
||||
"concurrently": "7.2.1",
|
||||
"copyfiles": "2.4.1",
|
||||
|
||||
@@ -1,5 +1,40 @@
|
||||
# @navikt/aksel
|
||||
|
||||
## 5.10.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-css@5.10.4
|
||||
|
||||
## 5.10.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`d3c0052c3`](https://github.com/navikt/aksel/commit/d3c0052c3300189798d04f17c2c2892f7c304277)]:
|
||||
- @navikt/ds-css@5.10.3
|
||||
|
||||
## 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel",
|
||||
"version": "5.9.2",
|
||||
"version": "5.10.4",
|
||||
"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.2",
|
||||
"axios": "1.3.6",
|
||||
"@navikt/ds-css": "5.10.4",
|
||||
"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,27 @@
|
||||
# @navikt/ds-css
|
||||
|
||||
## 5.10.4
|
||||
|
||||
## 5.10.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- :bug: Textarea: Teller flyttet ut av tekstfeltet for å unngå overlapp og misforståelser ([#2483](https://github.com/navikt/aksel/pull/2483))
|
||||
|
||||
## 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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -37,10 +37,6 @@
|
||||
color: var(--ac-textarea-text, var(--__ac-textarea-text, var(--a-text-default)));
|
||||
}
|
||||
|
||||
.navds-textarea--counter {
|
||||
padding-bottom: var(--a-spacing-8);
|
||||
}
|
||||
|
||||
.navds-textarea__input::placeholder {
|
||||
color: var(--ac-textarea-placeholder, var(--__ac-textarea-placeholder, var(--a-text-subtle)));
|
||||
}
|
||||
@@ -65,19 +61,9 @@
|
||||
padding: var(--a-spacing-1-alt);
|
||||
}
|
||||
|
||||
.navds-form-field--small .navds-textarea--counter.navds-textarea__input {
|
||||
padding-bottom: var(--a-spacing-7);
|
||||
}
|
||||
|
||||
.navds-textarea__counter {
|
||||
pointer-events: none;
|
||||
margin-top: var(--a-spacing-05);
|
||||
color: var(--ac-textarea-counter-text, var(--__ac-textarea-counter-text, var(--a-text-subtle)));
|
||||
font-style: italic;
|
||||
position: absolute;
|
||||
text-align: left;
|
||||
left: 0.0625rem;
|
||||
bottom: 0.0625rem;
|
||||
padding: var(--a-spacing-1) var(--a-spacing-2);
|
||||
}
|
||||
|
||||
.navds-textarea__counter--error {
|
||||
|
||||
@@ -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.2",
|
||||
"version": "5.10.4",
|
||||
"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.2",
|
||||
"@navikt/ds-tokens": "^5.10.4",
|
||||
"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,61 @@
|
||||
# @navikt/ds-react
|
||||
|
||||
## 5.10.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Gjør lukknapp i Alert til type button ([#2497](https://github.com/navikt/aksel/pull/2497))
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-tokens@5.10.4
|
||||
- @navikt/aksel-icons@5.10.4
|
||||
|
||||
## 5.10.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- :wheelchair: Textarea: Byttet fra `aria-live` til `role=status` på telleren for bedre semantikk ([#2483](https://github.com/navikt/aksel/pull/2483))
|
||||
|
||||
- :bug: Textarea: Teller flyttet ut av tekstfeltet for å unngå overlapp og misforståelser ([#2483](https://github.com/navikt/aksel/pull/2483))
|
||||
|
||||
- ErrorSummary: Gjør det mulig å rendre ErrorSummary.Item conditionally ([#2484](https://github.com/navikt/aksel/pull/2484))
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-tokens@5.10.3
|
||||
- @navikt/aksel-icons@5.10.3
|
||||
|
||||
## 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-react",
|
||||
"version": "5.9.2",
|
||||
"version": "5.10.4",
|
||||
"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.2",
|
||||
"@navikt/ds-tokens": "^5.9.2",
|
||||
"@navikt/aksel-icons": "^5.10.4",
|
||||
"@navikt/ds-tokens": "^5.10.4",
|
||||
"@radix-ui/react-tabs": "1.0.0",
|
||||
"@radix-ui/react-toggle-group": "1.0.0",
|
||||
"clsx": "^1.2.1",
|
||||
|
||||
@@ -117,6 +117,7 @@ export const Alert = forwardRef<HTMLDivElement, AlertProps>(
|
||||
size="small"
|
||||
variant="tertiary-neutral"
|
||||
onClick={onClose}
|
||||
type="button"
|
||||
icon={<XMarkIcon title="Lukk Alert" />}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import cl from "clsx";
|
||||
import React, { forwardRef, useState } from "react";
|
||||
import { BodyShort, ErrorMessage, Label } from "../typography";
|
||||
import TextareaAutosize from "../util/TextareaAutoSize";
|
||||
import { FormFieldProps, useFormField } from "./useFormField";
|
||||
import { ReadOnlyIcon } from "./ReadOnlyIcon";
|
||||
import { omit, useId } from "../util";
|
||||
import TextareaAutosize from "../util/TextareaAutoSize";
|
||||
import { ReadOnlyIcon } from "./ReadOnlyIcon";
|
||||
import Counter from "./TextareaCounter";
|
||||
import { FormFieldProps, useFormField } from "./useFormField";
|
||||
|
||||
/**
|
||||
* TODO: Mulighet for lokalisering av sr-only/counter text
|
||||
@@ -27,7 +28,6 @@ export interface TextareaProps
|
||||
defaultValue?: string;
|
||||
/**
|
||||
* Maximum number of character rows to display.
|
||||
* @bug Internal scrolling with `maxLength` scrolls over maxLength-text
|
||||
*/
|
||||
maxRows?: number;
|
||||
/**
|
||||
@@ -50,7 +50,7 @@ export interface TextareaProps
|
||||
* i18n-translations for counter-text
|
||||
*/
|
||||
i18n?: {
|
||||
/** @default Antall tegn igjen */
|
||||
/** @default tegn igjen */
|
||||
counterLeft?: string;
|
||||
/** @default tegn for mye */
|
||||
counterTooMuch?: string;
|
||||
@@ -162,14 +162,11 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
className={cl(
|
||||
"navds-textarea__input",
|
||||
"navds-body-short",
|
||||
`navds-body-short--${size ?? "medium"}`,
|
||||
{
|
||||
"navds-textarea--counter": hasMaxLength,
|
||||
}
|
||||
`navds-body-short--${size ?? "medium"}`
|
||||
)}
|
||||
{...(describedBy ? { "aria-describedby": describedBy } : {})}
|
||||
/>
|
||||
{hasMaxLength && (
|
||||
{hasMaxLength && !readOnly && !inputProps.disabled && (
|
||||
<>
|
||||
<span id={maxLengthId} className="navds-sr-only">
|
||||
{`Tekstområde med plass til ${maxLength} tegn.`}
|
||||
@@ -198,22 +195,4 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
}
|
||||
);
|
||||
|
||||
export const Counter = ({ maxLength, currentLength, size, i18n }) => {
|
||||
const difference = maxLength - currentLength;
|
||||
|
||||
return (
|
||||
<BodyShort
|
||||
className={cl("navds-textarea__counter", {
|
||||
"navds-textarea__counter--error": difference < 0,
|
||||
})}
|
||||
aria-live={difference < 20 ? "polite" : "off"}
|
||||
size={size}
|
||||
>
|
||||
{difference < 0
|
||||
? `${Math.abs(difference)} ${i18n?.counterTooMuch ?? "tegn for mye"}`
|
||||
: `${difference} ${i18n?.counterLeft ?? "tegn igjen"}`}
|
||||
</BodyShort>
|
||||
);
|
||||
};
|
||||
|
||||
export default Textarea;
|
||||
@@ -0,0 +1,31 @@
|
||||
import cl from "clsx";
|
||||
import React from "react";
|
||||
import { BodyShort } from "../typography";
|
||||
import type { TextareaProps } from "./Textarea";
|
||||
|
||||
interface Props {
|
||||
maxLength: number;
|
||||
currentLength: number;
|
||||
size: TextareaProps["size"];
|
||||
i18n: TextareaProps["i18n"];
|
||||
}
|
||||
|
||||
const TextareaCounter = ({ maxLength, currentLength, size, i18n }: Props) => {
|
||||
const difference = maxLength - currentLength;
|
||||
|
||||
return (
|
||||
<BodyShort
|
||||
className={cl("navds-textarea__counter", {
|
||||
"navds-textarea__counter--error": difference < 0,
|
||||
})}
|
||||
role={difference < 20 ? "status" : undefined}
|
||||
size={size}
|
||||
>
|
||||
{difference < 0
|
||||
? `${Math.abs(difference)} ${i18n?.counterTooMuch ?? "tegn for mye"}`
|
||||
: `${difference} ${i18n?.counterLeft ?? "tegn igjen"}`}
|
||||
</BodyShort>
|
||||
);
|
||||
};
|
||||
|
||||
export default TextareaCounter;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { forwardRef, HTMLAttributes } from "react";
|
||||
import React, { forwardRef, HTMLAttributes, isValidElement } from "react";
|
||||
import cl from "clsx";
|
||||
import { Heading, BodyShort } from "../../typography";
|
||||
import ErrorSummaryItem, { ErrorSummaryItemType } from "./ErrorSummaryItem";
|
||||
@@ -100,7 +100,10 @@ export const ErrorSummary = forwardRef<HTMLDivElement, ErrorSummaryProps>(
|
||||
</Heading>
|
||||
<BodyShort as="ul" size={size} className="navds-error-summary__list">
|
||||
{React.Children.map(children, (child) => {
|
||||
return <li key={child?.toString()}>{child}</li>;
|
||||
if (!isValidElement(child)) {
|
||||
return null;
|
||||
}
|
||||
return <li key={child.toString()}>{child}</li>;
|
||||
})}
|
||||
</BodyShort>
|
||||
</section>
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
export {
|
||||
default as ConfirmationPanel,
|
||||
type ConfirmationPanelProps,
|
||||
} from "./ConfirmationPanel";
|
||||
export { Fieldset, FieldsetContext, type FieldsetProps } from "./Fieldset";
|
||||
export { default as Select, type SelectProps } from "./Select";
|
||||
export { default as Switch, type SwitchProps } from "./Switch";
|
||||
export { default as TextField, type TextFieldProps } from "./TextField";
|
||||
export { default as Textarea, type TextareaProps } from "./Textarea";
|
||||
export {
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
type CheckboxGroupProps,
|
||||
type CheckboxProps,
|
||||
} from "./checkbox";
|
||||
export {
|
||||
type ConfirmationPanelProps,
|
||||
default as ConfirmationPanel,
|
||||
} from "./ConfirmationPanel";
|
||||
export { Combobox as UNSAFE_Combobox, type ComboboxProps } from "./combobox";
|
||||
export { ErrorSummary, type ErrorSummaryProps } from "./error-summary";
|
||||
export { Fieldset, FieldsetContext, type FieldsetProps } from "./Fieldset";
|
||||
export {
|
||||
Radio,
|
||||
RadioGroup,
|
||||
@@ -17,8 +22,3 @@ export {
|
||||
type RadioProps,
|
||||
} from "./radio";
|
||||
export { Search, type SearchClearEvent, type SearchProps } from "./search";
|
||||
export { Combobox as UNSAFE_Combobox, type ComboboxProps } from "./combobox";
|
||||
export { default as Select, type SelectProps } from "./Select";
|
||||
export { default as Switch, type SwitchProps } from "./Switch";
|
||||
export { Counter, default as Textarea, type TextareaProps } from "./Textarea";
|
||||
export { default as TextField, type TextFieldProps } from "./TextField";
|
||||
@@ -14,7 +14,7 @@ export const Default: StoryObj<typeof Textarea> = {
|
||||
},
|
||||
|
||||
args: {
|
||||
maxLength: 100,
|
||||
maxLength: 0,
|
||||
label: "Ipsum enim quis culpa",
|
||||
resize: false,
|
||||
},
|
||||
@@ -28,6 +28,7 @@ export const Default: StoryObj<typeof Textarea> = {
|
||||
error: { type: "string" },
|
||||
hideLabel: { type: "boolean" },
|
||||
disabled: { type: "boolean" },
|
||||
readOnly: { type: "boolean" },
|
||||
maxRows: { type: "number" },
|
||||
minRows: { type: "number" },
|
||||
},
|
||||
@@ -66,6 +67,20 @@ export const Error = () => {
|
||||
error="Consectetur labore velit eiusmod Lorem ut nostrud mollit labore ullamco laboris laboris in."
|
||||
size="small"
|
||||
/>
|
||||
|
||||
<Textarea
|
||||
label="Ipsum enim quis culpa"
|
||||
error="Consectetur labore velit eiusmod Lorem ut nostrud mollit labore ullamco laboris laboris in."
|
||||
maxLength={20}
|
||||
/>
|
||||
|
||||
<Textarea
|
||||
label="Ipsum enim quis culpa"
|
||||
error="Consectetur labore velit eiusmod Lorem ut nostrud mollit labore ullamco laboris laboris in."
|
||||
value="Sed dignissim sollicitudin porta."
|
||||
maxLength={20}
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -1,13 +1,14 @@
|
||||
import React, { forwardRef } from "react";
|
||||
import cl from "clsx";
|
||||
import React, { forwardRef } from "react";
|
||||
import { OverridableComponent } from "../util/OverridableComponent";
|
||||
|
||||
export interface LinkProps
|
||||
extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
||||
/**
|
||||
* Inverts when the underline appears. If this is false,
|
||||
* the underline does not appear by default, but does appear when the link is hovered.
|
||||
* This makes it more suitable for use when inlined in text.
|
||||
* the underline only appears on hover.
|
||||
*
|
||||
* **NB: Underline can only be removed in menus etc. where it's obvious that it's a link.**
|
||||
* @default true
|
||||
*/
|
||||
underline?: boolean;
|
||||
|
||||
@@ -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,2 +1,3 @@
|
||||
src/tokens.ts
|
||||
tailwind.config.js
|
||||
tailwind.config.js
|
||||
!tailwind.config.d.ts
|
||||
@@ -1,5 +1,19 @@
|
||||
# @navikt/ds-tailwind
|
||||
|
||||
## 5.10.4
|
||||
|
||||
## 5.10.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- typescript types for tailwind preset ([#2486](https://github.com/navikt/aksel/pull/2486))
|
||||
|
||||
## 5.10.2
|
||||
|
||||
## 5.10.1
|
||||
|
||||
## 5.10.0
|
||||
|
||||
## 5.9.2
|
||||
|
||||
## 5.9.1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-tailwind",
|
||||
"version": "5.9.2",
|
||||
"version": "5.10.4",
|
||||
"description": "Tailwind config based on @navikt/ds-tokens",
|
||||
"author": "Aksel | NAV designsystem team",
|
||||
"keywords": [
|
||||
@@ -8,9 +8,11 @@
|
||||
"design-tokens"
|
||||
],
|
||||
"main": "tailwind.config.js",
|
||||
"types": "tailwind.config.d.ts",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"tailwind.config.js"
|
||||
"tailwind.config.js",
|
||||
"tailwind.config.d.ts"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "ts-node ./src",
|
||||
@@ -22,11 +24,12 @@
|
||||
"directory": "@navikt/core/tailwind"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@navikt/ds-tokens": "^5.9.2",
|
||||
"@navikt/ds-tokens": "^5.10.4",
|
||||
"@types/jest": "^29.0.0",
|
||||
"color": "4.2.3",
|
||||
"jest": "^29.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"ts-jest": "^29.0.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.1.6"
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import type { Config } from "tailwindcss";
|
||||
|
||||
declare const preset: Partial<Config>;
|
||||
|
||||
export default preset;
|
||||
@@ -1,5 +1,15 @@
|
||||
# @navikt/ds-tokens
|
||||
|
||||
## 5.10.4
|
||||
|
||||
## 5.10.3
|
||||
|
||||
## 5.10.2
|
||||
|
||||
## 5.10.1
|
||||
|
||||
## 5.10.0
|
||||
|
||||
## 5.9.2
|
||||
|
||||
## 5.9.1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-tokens",
|
||||
"version": "5.9.2",
|
||||
"version": "5.10.4",
|
||||
"description": "Design-tokens for NAV designsystem",
|
||||
"author": "Aksel | NAV designsystem team",
|
||||
"keywords": [
|
||||
|
||||
+324
-744
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.2",
|
||||
"@navikt/ds-css": "^5.9.2",
|
||||
"@navikt/ds-react": "^5.9.2",
|
||||
"@navikt/ds-tailwind": "^5.9.2",
|
||||
"@navikt/ds-tokens": "^5.9.2"
|
||||
"@navikt/aksel-icons": "^5.10.4",
|
||||
"@navikt/ds-css": "^5.10.4",
|
||||
"@navikt/ds-react": "^5.10.4",
|
||||
"@navikt/ds-tailwind": "^5.10.4",
|
||||
"@navikt/ds-tokens": "^5.10.4"
|
||||
},
|
||||
"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,62 @@
|
||||
// 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: [320, 480, 768, 1024, 1280, 1440],
|
||||
defaultWidths: [320, 1280], // will be supported if this PR is merged: https://github.com/seek-oss/playroom/pull/302
|
||||
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,7 +1,7 @@
|
||||
import { loadCsf } from "@storybook/csf-tools";
|
||||
import type { StorybookConfig } from "@storybook/nextjs";
|
||||
import { readFileSync } from "fs";
|
||||
import { dirname, join } from "path";
|
||||
import { dirname, join, resolve } from "path";
|
||||
|
||||
/**
|
||||
* This function is used to resolve the absolute path of a package.
|
||||
@@ -65,5 +65,74 @@ const config: StorybookConfig = {
|
||||
docs: {
|
||||
autodocs: "tag",
|
||||
},
|
||||
webpackFinal: async (sbConfig: any) => {
|
||||
if (!sbConfig.resolve.alias) {
|
||||
return sbConfig;
|
||||
}
|
||||
// Add path aliases
|
||||
sbConfig.resolve.alias["@/sb-util"] = resolve(__dirname, "./utils.tsx");
|
||||
sbConfig.resolve.alias["@/types"] = resolve(
|
||||
__dirname,
|
||||
"../components/types/index.ts"
|
||||
);
|
||||
sbConfig.resolve.alias["@/sanity-block"] = resolve(
|
||||
__dirname,
|
||||
"../components/website-modules/SanityBlockContent.tsx"
|
||||
);
|
||||
sbConfig.resolve.alias["@/hooks"] = resolve(
|
||||
__dirname,
|
||||
"../components/hooks/"
|
||||
);
|
||||
sbConfig.resolve.alias["@/logging"] = resolve(
|
||||
__dirname,
|
||||
"../components/logging/index.ts"
|
||||
);
|
||||
sbConfig.resolve.alias["@/utils"] = resolve(
|
||||
__dirname,
|
||||
"../components/utils/index.ts"
|
||||
);
|
||||
sbConfig.resolve.alias["@/utils"] = resolve(
|
||||
__dirname,
|
||||
"../components/utils/index.ts"
|
||||
);
|
||||
sbConfig.resolve.alias["@/layout"] = resolve(
|
||||
__dirname,
|
||||
"../components/layout/"
|
||||
);
|
||||
sbConfig.resolve.alias["@/assets"] = resolve(
|
||||
__dirname,
|
||||
"../components/assets/"
|
||||
);
|
||||
sbConfig.resolve.alias["@/web"] = resolve(
|
||||
__dirname,
|
||||
"../components/website-modules/"
|
||||
);
|
||||
sbConfig.resolve.alias["@/cms"] = resolve(
|
||||
__dirname,
|
||||
"../components/sanity-modules/"
|
||||
);
|
||||
sbConfig.resolve.alias["@/error-boundary"] = resolve(
|
||||
__dirname,
|
||||
"../components/website-modules/ErrorBoundary.tsx"
|
||||
);
|
||||
sbConfig.resolve.alias["@/sanity/config"] = resolve(
|
||||
__dirname,
|
||||
"../sanity/config.ts"
|
||||
);
|
||||
sbConfig.resolve.alias["@/sanity/client.server"] = resolve(
|
||||
__dirname,
|
||||
"../sanity/interface/client.server.js"
|
||||
);
|
||||
sbConfig.resolve.alias["@/sanity/interface"] = resolve(
|
||||
__dirname,
|
||||
"../sanity/interface/interface.ts"
|
||||
);
|
||||
sbConfig.resolve.alias["@/sanity/queries"] = resolve(
|
||||
__dirname,
|
||||
"../sanity/interface/queries.js"
|
||||
);
|
||||
|
||||
return sbConfig;
|
||||
},
|
||||
};
|
||||
export default config;
|
||||
@@ -1,73 +0,0 @@
|
||||
import { getClient } from "@/sanity/client.server";
|
||||
import { useLiveQuery, LiveQueryProvider } from "next-sanity/preview";
|
||||
import { useMemo, ComponentType } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useCheckAuth } from "components/website-modules/utils/useCheckAuth";
|
||||
|
||||
export const PreviewBanner = dynamic(
|
||||
() => import("components/website-modules/PreviewBanner"),
|
||||
{
|
||||
ssr: false,
|
||||
}
|
||||
);
|
||||
|
||||
function PreviewProvider({ children }: { children: React.ReactNode }) {
|
||||
const client = useMemo(() => {
|
||||
return getClient().withConfig({
|
||||
useCdn: false,
|
||||
ignoreBrowserTokenWarning: true,
|
||||
perspective: "previewDrafts",
|
||||
});
|
||||
}, []);
|
||||
return <LiveQueryProvider client={client}>{children}</LiveQueryProvider>;
|
||||
}
|
||||
|
||||
function LiveQuery({
|
||||
comp: Comp,
|
||||
query,
|
||||
params,
|
||||
props,
|
||||
validUser,
|
||||
}: {
|
||||
comp: ComponentType;
|
||||
query: string;
|
||||
props: any;
|
||||
params?: any;
|
||||
validUser: boolean;
|
||||
}) {
|
||||
const [data, loading] = useLiveQuery(props, query, params);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PreviewBanner loading={loading} validUser={validUser} />
|
||||
<Comp {...props} {...data} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const WithPreview = ({
|
||||
comp,
|
||||
query,
|
||||
params,
|
||||
props,
|
||||
}: {
|
||||
comp;
|
||||
query: string;
|
||||
props: any;
|
||||
params?: any;
|
||||
}) => {
|
||||
const validUser = useCheckAuth();
|
||||
return (
|
||||
<PreviewProvider>
|
||||
<LiveQuery
|
||||
props={props}
|
||||
query={query}
|
||||
params={params}
|
||||
comp={comp}
|
||||
validUser={validUser}
|
||||
/>
|
||||
</PreviewProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default WithPreview;
|
||||
@@ -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,131 +1,92 @@
|
||||
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 { FigmaIcon, GithubIcon, SlackIcon } from "@/assets/Icons";
|
||||
import AkselLogo from "@/assets/Logo";
|
||||
import { Box, HGrid, Page, VStack } from "@navikt/ds-react";
|
||||
import dynamic from "next/dynamic";
|
||||
/* import FooterForm from "./FooterForm"; */
|
||||
import LinkBlock from "./parts/LinkBlock";
|
||||
|
||||
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
|
||||
id="aksel-footer"
|
||||
data-hj-suppress
|
||||
<Box
|
||||
as="footer"
|
||||
data-theme="dark"
|
||||
className="toc-ignore text-text-on-inverted bg-deepblue-800 relative flex w-full justify-center"
|
||||
id="aksel-footer"
|
||||
className="relative"
|
||||
background="surface-alt-3-strong"
|
||||
paddingBlock="12 16"
|
||||
>
|
||||
<div className="relative z-10 mx-auto grid w-full max-w-screen-2xl gap-12 px-4 pb-16 pt-12 md:grid-cols-2 md:px-6 lg:grid-cols-4 xl:grid-cols-4 xl:gap-6">
|
||||
<LogoBlock />
|
||||
<Snarveier />
|
||||
<SideLenker />
|
||||
<Kontakt />
|
||||
{/* <FooterForm /> */}
|
||||
</div>
|
||||
<EditButton />
|
||||
</footer>
|
||||
<Page.Block width="2xl" gutters>
|
||||
<HGrid columns={{ xs: 1, md: 2, lg: 4 }} gap={{ xs: "12", xl: "6" }}>
|
||||
<VStack gap="4">
|
||||
<AkselLogo className="fill-white" />
|
||||
<div>
|
||||
<p>© {new Date().getFullYear()} NAV</p>
|
||||
<p>Arbeids- og velferdsetaten</p>
|
||||
</div>
|
||||
</VStack>
|
||||
<LinkBlock
|
||||
heading="Snarveier"
|
||||
links={[
|
||||
{
|
||||
url: "/god-praksis/artikler/skriv-for-aksel",
|
||||
text: "Skriv for Aksel",
|
||||
},
|
||||
{
|
||||
url: "/prinsipper/brukeropplevelse",
|
||||
text: "Prinsipper for brukeropplevelse",
|
||||
},
|
||||
{ url: "https://sikkerhet.nav.no/", text: "Security Playbook" },
|
||||
{
|
||||
url: "https://etterlevelse.intern.nav.no/",
|
||||
text: "Etterlevelse",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<LinkBlock
|
||||
heading="Om nettstedet"
|
||||
links={[
|
||||
{
|
||||
url: "/god-praksis/artikler/om-aksel",
|
||||
text: "Hva er Aksel?",
|
||||
},
|
||||
{
|
||||
url: "/side/personvernerklaering",
|
||||
text: "Personvernerklæring",
|
||||
},
|
||||
{
|
||||
url: "/side/tilgjengelighetserklaring-for-aksel",
|
||||
text: "Tilgjengelighetserklæring",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<LinkBlock
|
||||
heading="Finn oss"
|
||||
links={[
|
||||
{
|
||||
url: "https://www.figma.com/@nav_aksel",
|
||||
text: "Figma",
|
||||
icon: <FigmaIcon />,
|
||||
},
|
||||
{
|
||||
url: "https://github.com/navikt/aksel",
|
||||
text: "Github",
|
||||
icon: <GithubIcon />,
|
||||
},
|
||||
{
|
||||
url: "https://nav-it.slack.com/archives/C7NE7A8UF",
|
||||
text: "Slack",
|
||||
icon: <SlackIcon />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</HGrid>
|
||||
</Page.Block>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
function LogoBlock() {
|
||||
return (
|
||||
<div>
|
||||
<Logo className="fill-white" />
|
||||
<p className="mt-4">© {new Date().getFullYear()} NAV</p>
|
||||
<p>Arbeids- og velferdsetaten</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Snarveier() {
|
||||
return (
|
||||
<div>
|
||||
<Heading level="2" size="xsmall">
|
||||
Snarveier
|
||||
</Heading>
|
||||
<BodyShort as="ul" className="mt-3 grid gap-3">
|
||||
<FooterLink href="/god-praksis/artikler/skriv-for-aksel">
|
||||
Skriv for Aksel
|
||||
</FooterLink>
|
||||
<FooterLink href="/prinsipper/brukeropplevelse">
|
||||
Prinsipper for brukeropplevelse
|
||||
</FooterLink>
|
||||
{/* <FooterLink href="https://identitet.nav.no/">Identitet</FooterLink> */}
|
||||
<FooterLink href="https://sikkerhet.nav.no/">
|
||||
Security Playbook
|
||||
</FooterLink>
|
||||
<FooterLink href="https://etterlevelse.intern.nav.no/">
|
||||
Etterlevelse
|
||||
</FooterLink>
|
||||
</BodyShort>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Kontakt() {
|
||||
return (
|
||||
<div>
|
||||
<Heading level="2" size="xsmall">
|
||||
Finn oss
|
||||
</Heading>
|
||||
<BodyShort as="ul" className="mt-3 grid gap-3">
|
||||
<FooterLink href="https://www.figma.com/@nav_aksel">
|
||||
<FigmaIcon />
|
||||
Figma
|
||||
</FooterLink>
|
||||
<FooterLink href="https://github.com/navikt/aksel">
|
||||
<GithubIcon />
|
||||
Github
|
||||
</FooterLink>
|
||||
<FooterLink href="https://nav-it.slack.com/archives/C7NE7A8UF">
|
||||
<SlackIcon />
|
||||
Slack
|
||||
</FooterLink>
|
||||
</BodyShort>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
function SideLenker() {
|
||||
return (
|
||||
<div>
|
||||
<Heading level="2" size="xsmall">
|
||||
Om nettstedet
|
||||
</Heading>
|
||||
<BodyShort as="ul" className="mt-3 grid gap-3">
|
||||
<FooterLink href="/god-praksis/artikler/om-aksel">
|
||||
Hva er Aksel?
|
||||
</FooterLink>
|
||||
<FooterLink href="/side/personvernerklaering">
|
||||
Personvernerklæring
|
||||
</FooterLink>
|
||||
<FooterLink href="/side/tilgjengelighetserklaring-for-aksel">
|
||||
Tilgjengelighetserklæring
|
||||
</FooterLink>
|
||||
</BodyShort>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FooterLink({ children, href }) {
|
||||
return (
|
||||
<li>
|
||||
<Link
|
||||
className="text-text-on-inverted focus:shadow-focus focus:text-text-default flex w-fit items-center gap-1 underline hover:no-underline focus:bg-blue-200 focus:shadow-blue-200"
|
||||
href={href}
|
||||
onClick={(e) =>
|
||||
amplitudeLogNavigation("footer", e.currentTarget.getAttribute("href"))
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
export default Footer;
|
||||
@@ -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;
|
||||
@@ -0,0 +1,19 @@
|
||||
import { amplitudeLogNavigation } from "@/logging";
|
||||
import { Link } from "@navikt/ds-react";
|
||||
|
||||
function FooterLink({ children, href }) {
|
||||
return (
|
||||
<li>
|
||||
<Link
|
||||
className="text-text-on-inverted focus:shadow-focus focus:text-text-default flex w-fit items-center gap-1 underline hover:no-underline focus:bg-blue-200 focus:shadow-blue-200"
|
||||
href={href}
|
||||
onClick={(e) =>
|
||||
amplitudeLogNavigation("footer", e.currentTarget.getAttribute("href"))
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
export default FooterLink;
|
||||
@@ -0,0 +1,27 @@
|
||||
import { BodyShort, Heading } from "@navikt/ds-react";
|
||||
import FooterLink from "./FooterLink";
|
||||
|
||||
type LinkBlockPropsT = {
|
||||
heading: string;
|
||||
links: { url: string; text: string; icon?: React.ReactNode }[];
|
||||
};
|
||||
|
||||
function LinkBlock({ heading, links }: LinkBlockPropsT) {
|
||||
return (
|
||||
<div>
|
||||
<Heading level="2" size="xsmall">
|
||||
{heading}
|
||||
</Heading>
|
||||
<BodyShort as="ul" className="mt-3 grid gap-3">
|
||||
{links.map((link) => (
|
||||
<FooterLink key={link.url} href={link.url}>
|
||||
{link.icon}
|
||||
{link.text}
|
||||
</FooterLink>
|
||||
))}
|
||||
</BodyShort>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default LinkBlock;
|
||||
@@ -1,96 +0,0 @@
|
||||
import { MenuHamburgerIcon } from "@navikt/aksel-icons";
|
||||
|
||||
import { amplitudeLogNavigation } from "@/logging";
|
||||
import { Modal } from "@navikt/ds-react";
|
||||
import cl from "clsx";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { useState } from "react";
|
||||
|
||||
const LinkElement = ({ name, href, onClick }) => {
|
||||
const { asPath } = useRouter();
|
||||
return (
|
||||
<li className="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",
|
||||
{
|
||||
"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),
|
||||
"": !asPath.startsWith(href),
|
||||
}
|
||||
)}
|
||||
onClick={(e) => {
|
||||
amplitudeLogNavigation(
|
||||
"header",
|
||||
e.currentTarget.getAttribute("href")
|
||||
);
|
||||
onClick();
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
export const Hamburger = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<div className="mr-2 grid h-full place-content-center lg:hidden">
|
||||
<button
|
||||
onClick={() => setOpen(!open)}
|
||||
aria-expanded={open}
|
||||
className="focus-visible:shadow-focus hover:bg-surface-neutral-subtle-hover ml-2 flex aspect-square items-center justify-center rounded px-3 py-3 focus:outline-none"
|
||||
>
|
||||
<MenuHamburgerIcon
|
||||
fontSize="2rem"
|
||||
className="pointer-events-none"
|
||||
aria-label="Åpne meny"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
header={{ heading: "Meny" }}
|
||||
width="small"
|
||||
>
|
||||
<Modal.Body>
|
||||
<nav aria-label="hovedmeny">
|
||||
<ul>
|
||||
<LinkElement
|
||||
onClick={() => setOpen(false)}
|
||||
name="God praksis"
|
||||
href="/god-praksis"
|
||||
/>
|
||||
<LinkElement
|
||||
onClick={() => setOpen(false)}
|
||||
name="Grunnleggende"
|
||||
href="/grunnleggende"
|
||||
/>
|
||||
<LinkElement
|
||||
onClick={() => setOpen(false)}
|
||||
name="Ikoner"
|
||||
href="/ikoner"
|
||||
/>
|
||||
<LinkElement
|
||||
onClick={() => setOpen(false)}
|
||||
name="Komponenter"
|
||||
href="/komponenter"
|
||||
/>
|
||||
<LinkElement
|
||||
onClick={() => setOpen(false)}
|
||||
name="Blogg"
|
||||
href="/produktbloggen"
|
||||
/>
|
||||
</ul>
|
||||
</nav>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,118 +1,96 @@
|
||||
import AkselLogo from "@/assets/Logo";
|
||||
import { amplitudeLogNavigation } from "@/logging";
|
||||
import { MagnifyingGlassIcon } from "@navikt/aksel-icons";
|
||||
import { Button } from "@navikt/ds-react";
|
||||
import { Box, Button, HStack, Page, Show, Spacer } 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 "./parts/Hamburger";
|
||||
import HeaderLink from "./parts/HeaderLink";
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
const LinkElement = ({ name, href, prefetch = undefined }) => {
|
||||
const { asPath } = useRouter();
|
||||
return (
|
||||
<li>
|
||||
<Link
|
||||
href={href}
|
||||
prefetch={prefetch}
|
||||
className={cl(
|
||||
"text-deepblue-800 focus-visible:shadow-focus relative grid h-11 place-items-center rounded px-2 focus:outline-none",
|
||||
{
|
||||
"before:bg-deepblue-600 font-semibold before:absolute before:bottom-[1px] before:z-10 before:h-1 before:w-[calc(100%_-_16px)] before:rounded-full":
|
||||
asPath.startsWith(href),
|
||||
"hover:before:bg-border-subtle-hover before:absolute before:bottom-[1px] before:z-10 before:h-1 before:w-[calc(100%_-_16px)] before:rounded-full":
|
||||
!asPath.startsWith(href),
|
||||
}
|
||||
)}
|
||||
onClick={(e) =>
|
||||
amplitudeLogNavigation("header", e.currentTarget.getAttribute("href"))
|
||||
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"
|
||||
>
|
||||
{name}
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
Søk
|
||||
</Button>
|
||||
),
|
||||
ssr: false,
|
||||
}
|
||||
);
|
||||
|
||||
export const Header = ({
|
||||
const Header = ({
|
||||
variant = "default",
|
||||
}: {
|
||||
variant?: "blogg" | "subtle" | "default" | "transparent";
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<header
|
||||
className={cl("z-20", {
|
||||
"bg-[#FEFCE9]": variant === "blogg",
|
||||
"bg-surface-default": variant === "default",
|
||||
"bg-surface-subtle": variant === "subtle",
|
||||
"bg-surface-transparent": variant === "transparent",
|
||||
})}
|
||||
>
|
||||
<a className="skiplink" href="#hovedinnhold">
|
||||
Hopp til innhold
|
||||
</a>
|
||||
<header
|
||||
className={cl("h-header z-20", {
|
||||
"bg-[#FEFCE9]": variant === "blogg",
|
||||
"bg-surface-default": variant === "default",
|
||||
"bg-surface-subtle": variant === "subtle",
|
||||
"bg-surface-transparent": variant === "transparent",
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={cl(
|
||||
"mx-auto flex h-full max-w-screen-2xl items-center pr-4 lg:pr-6"
|
||||
)}
|
||||
>
|
||||
<div className="flex h-11 items-center pl-4 pr-4 sm:pl-4 sm:pr-6">
|
||||
<Link
|
||||
href="/"
|
||||
passHref
|
||||
onClick={(e) =>
|
||||
amplitudeLogNavigation(
|
||||
"header",
|
||||
e.currentTarget.getAttribute("href")
|
||||
)
|
||||
}
|
||||
className="focus-visible:shadow-focus grid h-full place-items-center rounded px-2 focus:outline-none"
|
||||
>
|
||||
<Logo className="text-deepblue-800" />
|
||||
<span className="sr-only">Aksel</span>
|
||||
</Link>
|
||||
</div>
|
||||
<nav
|
||||
className="ml-auto hidden h-full pr-2 lg:block lg:pr-8"
|
||||
aria-label="Hovedmeny"
|
||||
<Page.Block width="2xl">
|
||||
<div className="h-header flex items-center pr-4 lg:pr-6">
|
||||
<Link
|
||||
href="/"
|
||||
passHref
|
||||
onClick={(e) =>
|
||||
amplitudeLogNavigation(
|
||||
"header",
|
||||
e.currentTarget.getAttribute("href")
|
||||
)
|
||||
}
|
||||
className="focus-visible:shadow-focus mx-4 grid h-11 place-items-center rounded px-2 focus:outline-none sm:mr-6"
|
||||
>
|
||||
<ul className="hidden h-full items-center gap-2 md:flex">
|
||||
<LinkElement name="God praksis" href="/god-praksis" />
|
||||
<LinkElement name="Grunnleggende" href="/grunnleggende" />
|
||||
<LinkElement name="Ikoner" href="/ikoner" prefetch={false} />
|
||||
<LinkElement name="Komponenter" href="/komponenter" />
|
||||
<LinkElement name="Bloggen" href="/produktbloggen" />
|
||||
</ul>
|
||||
</nav>
|
||||
<div className="z-[1050] ml-auto mr-4 flex justify-center lg:ml-0 lg:mr-0">
|
||||
<GlobalSearch />
|
||||
</div>
|
||||
<AkselLogo className="text-deepblue-800" />
|
||||
<span className="sr-only">Aksel</span>
|
||||
</Link>
|
||||
|
||||
<Hamburger />
|
||||
<Spacer />
|
||||
<Show above="lg" asChild>
|
||||
<Box
|
||||
as="nav"
|
||||
paddingInline={{ xs: "0 2", lg: "0 8" }}
|
||||
aria-label="Hovedmeny"
|
||||
>
|
||||
<HStack as="ul" gap="2" align="center">
|
||||
<HeaderLink name="God praksis" href="/god-praksis" />
|
||||
<HeaderLink name="Grunnleggende" href="/grunnleggende" />
|
||||
<HeaderLink name="Ikoner" href="/ikoner" prefetch={false} />
|
||||
<HeaderLink name="Komponenter" href="/komponenter" />
|
||||
<HeaderLink name="Mønster & Maler" href="/monster-maler" />
|
||||
<HeaderLink name="Bloggen" href="/produktbloggen" />
|
||||
</HStack>
|
||||
</Box>
|
||||
</Show>
|
||||
<HStack align="center" gap="2">
|
||||
<GlobalSearch />
|
||||
<Hamburger />
|
||||
</HStack>
|
||||
</div>
|
||||
</header>
|
||||
</>
|
||||
</Page.Block>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
@@ -0,0 +1,69 @@
|
||||
import { MenuHamburgerIcon } from "@navikt/aksel-icons";
|
||||
import { Button, Modal, Show } from "@navikt/ds-react";
|
||||
import { useState } from "react";
|
||||
import HamburgerLink from "./HamburgerLink";
|
||||
|
||||
export const Hamburger = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<Show below="lg">
|
||||
<Button
|
||||
variant="tertiary-neutral"
|
||||
onClick={() => setOpen(!open)}
|
||||
aria-expanded={open}
|
||||
icon={
|
||||
<MenuHamburgerIcon
|
||||
fontSize="2rem"
|
||||
className="pointer-events-none"
|
||||
title="Åpne meny"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
header={{ heading: "Meny" }}
|
||||
width="small"
|
||||
closeOnBackdropClick
|
||||
>
|
||||
<Modal.Body>
|
||||
<nav aria-label="hovedmeny">
|
||||
<ul>
|
||||
<HamburgerLink
|
||||
onClick={() => setOpen(false)}
|
||||
name="God praksis"
|
||||
href="/god-praksis"
|
||||
/>
|
||||
<HamburgerLink
|
||||
onClick={() => setOpen(false)}
|
||||
name="Grunnleggende"
|
||||
href="/grunnleggende"
|
||||
/>
|
||||
<HamburgerLink
|
||||
onClick={() => setOpen(false)}
|
||||
name="Ikoner"
|
||||
href="/ikoner"
|
||||
/>
|
||||
<HamburgerLink
|
||||
onClick={() => setOpen(false)}
|
||||
name="Komponenter"
|
||||
href="/komponenter"
|
||||
/>
|
||||
<HamburgerLink
|
||||
onClick={() => setOpen(false)}
|
||||
name="Mønster & Maler"
|
||||
href="/monster-maler"
|
||||
/>
|
||||
<HamburgerLink
|
||||
onClick={() => setOpen(false)}
|
||||
name="Blogg"
|
||||
href="/produktbloggen"
|
||||
/>
|
||||
</ul>
|
||||
</nav>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import { amplitudeLogNavigation } from "@/logging";
|
||||
import cl from "clsx";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
function HamburgerLink({ name, href, onClick }) {
|
||||
const { asPath } = useRouter();
|
||||
return (
|
||||
<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 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),
|
||||
"": !asPath.startsWith(href),
|
||||
}
|
||||
)}
|
||||
onClick={(e) => {
|
||||
amplitudeLogNavigation(
|
||||
"header",
|
||||
e.currentTarget.getAttribute("href")
|
||||
);
|
||||
onClick();
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
export default HamburgerLink;
|
||||
@@ -0,0 +1,32 @@
|
||||
import { amplitudeLogNavigation } from "@/logging";
|
||||
import cl from "clsx";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
function HeaderLink({ name, href, prefetch = undefined }) {
|
||||
const { asPath } = useRouter();
|
||||
return (
|
||||
<li>
|
||||
<Link
|
||||
href={href}
|
||||
prefetch={prefetch}
|
||||
className={cl(
|
||||
"text-deepblue-800 focus-visible:shadow-focus relative grid h-11 place-items-center rounded px-2 focus:outline-none",
|
||||
{
|
||||
"before:bg-deepblue-600 font-semibold before:absolute before:bottom-[1px] before:z-10 before:h-1 before:w-[calc(100%_-_16px)] before:rounded-full":
|
||||
asPath.startsWith(href),
|
||||
"hover:before:bg-border-subtle-hover before:absolute before:bottom-[1px] before:z-10 before:h-1 before:w-[calc(100%_-_16px)] before:rounded-full":
|
||||
!asPath.startsWith(href),
|
||||
}
|
||||
)}
|
||||
onClick={(e) =>
|
||||
amplitudeLogNavigation("header", e.currentTarget.getAttribute("href"))
|
||||
}
|
||||
>
|
||||
{name}
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
export default HeaderLink;
|
||||
@@ -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,
|
||||
}: {
|
||||
link: { heading: string; slug: string; kategori: string; tag?: string };
|
||||
}) => {
|
||||
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,
|
||||
}
|
||||
)}
|
||||
>
|
||||
{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>
|
||||
);
|
||||
};
|
||||
|
||||
const Dropdown = ({
|
||||
title,
|
||||
links,
|
||||
}: {
|
||||
title: string;
|
||||
links: AkselSidebarT;
|
||||
}) => {
|
||||
const [open, setOpen] = useState(true);
|
||||
|
||||
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}
|
||||
>
|
||||
<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} />
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
export const Sidebar = ({
|
||||
const Sidebar = ({
|
||||
kategori,
|
||||
links,
|
||||
}: {
|
||||
kategori: "komponenter" | "grunnleggende" | "templates";
|
||||
links: AkselSidebarT;
|
||||
links: SidebarT;
|
||||
}) => {
|
||||
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} />
|
||||
<Show asChild above="md">
|
||||
<Box
|
||||
paddingBlock="0 8"
|
||||
paddingInline="2"
|
||||
aria-label={kategori}
|
||||
as="nav"
|
||||
className="w-sidebar shrink-0 self-start"
|
||||
>
|
||||
<ul className="space-y-6">
|
||||
{links.map((section) => (
|
||||
<Section key={section.value} section={section} />
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</Box>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
function Section({ section }: { section: SidebarT[number] }) {
|
||||
const { asPath } = useRouter();
|
||||
|
||||
const isActive = (slug: string) => {
|
||||
return asPath.split("#")[0] === `/${slug}`;
|
||||
};
|
||||
return (
|
||||
<li>
|
||||
<Label
|
||||
as="div"
|
||||
size="small"
|
||||
textColor="subtle"
|
||||
className="min-h-8 flex items-center px-4"
|
||||
>
|
||||
{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 default Sidebar;
|
||||
@@ -1,46 +0,0 @@
|
||||
import { AkselSidebarT } from "@/types";
|
||||
import { useMemo } from "react";
|
||||
import { sanityCategoryLookup } from "../../../sanity/config";
|
||||
|
||||
export const useSection = ({
|
||||
kategori,
|
||||
links,
|
||||
}: {
|
||||
kategori: "komponenter" | "grunnleggende" | "templates";
|
||||
links: AkselSidebarT;
|
||||
}) => {
|
||||
const sections = useMemo(
|
||||
() =>
|
||||
sanityCategoryLookup(kategori)
|
||||
.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;
|
||||
};
|
||||
+35
-46
@@ -24,6 +24,39 @@ 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,
|
||||
@@ -37,29 +70,7 @@ export const KomponenterLanding: Story = {
|
||||
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,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -76,28 +87,6 @@ export const Komponenter: Story = {
|
||||
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,
|
||||
},
|
||||
};
|
||||
+13
-7
@@ -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,9 +16,11 @@ export const WithSidebar = ({
|
||||
pageProps,
|
||||
variant = "landingPage",
|
||||
footer,
|
||||
toc,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
sidebar: AkselSidebarT;
|
||||
sidebar: SidebarT;
|
||||
toc?: TableOfContentsT;
|
||||
pageType: {
|
||||
type: "komponenter" | "grunnleggende" | "templates";
|
||||
title: string;
|
||||
@@ -41,7 +43,7 @@ export const WithSidebar = ({
|
||||
<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(
|
||||
@@ -111,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";
|
||||
+1
-1
@@ -60,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 = {
|
||||
|
||||
@@ -26,7 +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(),
|
||||
@@ -34,7 +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(),
|
||||
@@ -42,7 +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);
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -10,8 +10,9 @@ export const FrontpageBlock = ({ blocks }: FrontpageBlockProps) => {
|
||||
if (!blocks || !blocks.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="lg:px-18 px-2">
|
||||
<>
|
||||
{blocks.map((x) => {
|
||||
switch (x._type) {
|
||||
case "nytt_fra_aksel":
|
||||
@@ -20,7 +21,7 @@ export const FrontpageBlock = ({ blocks }: FrontpageBlockProps) => {
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
+2
-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;
|
||||
|
||||
+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,
|
||||
|
||||
@@ -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];
|
||||
|
||||
Loaded 100 of 585 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user