Compare commits

...
Author SHA1 Message Date
github-actions[bot] f787e91a2f Version Packages (#3737)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-11 14:47:22 +02:00
KenandCopilot e7ec9c191a Timeline: Add placement-prop for periods for more fine-grained control (#3736)
* Timeline: Add placement-prop for periods for more fine-grained controll over Popover-placement

* Update .changeset/lazy-coats-beam.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-04-11 12:35:22 +02:00
Ken c57fe7c396 🐛 Use option-value as id instead of label in Combobox (#3735)
* 🐛 Use option-value as id instead of label

* 🐛 Use unique option-value as react-key
2025-04-11 12:32:16 +02:00
github-actions[bot] 4178ec2647 Version Packages (#3733)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-10 17:23:19 +02:00
Ken 8c9cf92c8b 🐛 Single-select combobox assumed that input needed min-width (#3734) 2025-04-10 14:55:37 +02:00
Ken 208adb8b3f 🐛 Using Page-component would always give errors when using new Theme-component 2025-04-10 14:15:13 +02:00
Ken 1966c3aed5 🐛 Pages without TOC broke page-layouts with assumed grid-placements 2025-04-10 12:00:45 +02:00
Ken 3a523392c7 [Aksel.nav.no] Support cookies in Appdir (#3726)
* ✨ Added theming-support to appdir

* 📝 Added changelog docs for theme class change

* ♻️ Use inline head to set local styles instead of css-modules

* ✨ Setup overview-page

* 🐛 Use inline styles instead of Head element from nextjs

* WIP: V1 list view

* 💄 Align overview heading with regular page heading

* 🐛 Moved margin-bottom to top for overview grid

* 🎉 Add overview toggle for component pages

* 🎉 Added overview-pages to sidebar

* 🎉 Added routing support for overview pages

* 🎉 Setup staticparams and metadata for overview-pages

* 🚚 Moved overview components out to now dir

* ✨ Added overview support for komponenter, templates and grunnleggende

* 🔥 Removed unused sanity-field

* 🐛 Hide fallback-svg

* 🐛 Fix misspelling

* ✨ Setup appdir cookie-banner view

* 🎉 Moved cookie-handling to server-actions

* 🐛 Remove impure css module use

* 🐛 Use forced dynamic to allow consent-banner

* 📝 Add docs

* 🎉 Moved consent doc-page

* ♻️ Rename cookie-actions to use 'action'-suffix
2025-04-10 11:51:33 +02:00
Ken 65c791c1eb [Aksel.nav.no] Add support for "overview" pages (#3722)
* ✨ Added theming-support to appdir

* 📝 Added changelog docs for theme class change

* ♻️ Use inline head to set local styles instead of css-modules

* ✨ Setup overview-page

* 🐛 Use inline styles instead of Head element from nextjs

* WIP: V1 list view

* 💄 Align overview heading with regular page heading

* 🐛 Moved margin-bottom to top for overview grid

* 🎉 Add overview toggle for component pages

* 🎉 Added overview-pages to sidebar

* 🎉 Added routing support for overview pages

* 🎉 Setup staticparams and metadata for overview-pages

* 🚚 Moved overview components out to now dir

* ✨ Added overview support for komponenter, templates and grunnleggende

* 🔥 Removed unused sanity-field

* 🐛 Hide fallback-svg

* 🐛 Fix misspelling

* Update aksel.nav.no/website/app/dev/(designsystemet)/_ui/sidebar/Sidebar.tsx

* 🐛 Could not create overview-pages for grunnleggende or templates
2025-04-10 11:18:32 +02:00
github-actions[bot] f1e058b67b Version Packages (#3706)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-08 14:11:10 +02:00
Vegard Haugstvedt 977ecec1df Forgot darkside-fix in #3727, so I am just pushing it individually 2025-04-08 14:06:38 +02:00
Vegard Haugstvedt 8b31c3bc58 Bugfix/combobox chips not wrapping when togglelist button is hidden (#3727)
* Remove unused CSS that causes issues when Combobox' ToggleListButton is skipped

This code removed wrapping of chips if ToggleListButton is hidden, and will only ever apply to the button otherwise. That does not need a flex layout.

* Add changeset
2025-04-08 13:58:49 +02:00
Ken 0d71a48458 [aksel.nav.no] 🎉 Themable empty-state for app-dir search 2025-04-07 13:44:52 +02:00
Julian Nymark c413f427a5 increase deprecation visibility (#3719) 2025-04-07 11:53:20 +02:00
Ken 023c06d66f [Aksel.nav.no] Theme-support (#3720)
* ✨ Added theming-support to appdir

* 📝 Added changelog docs for theme class change

* ♻️ Use inline head to set local styles instead of css-modules

* 🐛 Use inline styles instead of Head element from nextjs
2025-04-03 11:01:52 +02:00
Julian Nymark 68a3cfc340 remove outdated design examples (also somewhat erroneous hide example) (#3718) 2025-04-03 10:58:19 +02:00
Ken 277c74a657 🎨 Update background color for togglegroup in darkside theme (#3715) 2025-04-02 09:54:46 +02:00
Ken f0d569ab5f 🏷️ Refine type definitions for PortableContentTypes and update customPortableTextComponents type constraints 2025-04-02 09:54:16 +02:00
Julian NymarkandKen 58bb2e69c8 [Migration]: box -> boxNew (#3669)
* 🚧 box migration scaffolding

* 🚚 boxnew -> darkside/boxnew

* 🚧 wip

* 🚧 working single substitution (pending lookup in replacements)

* ✅ working test

* import rename in output test

* ✅ import rename + tests

* use config comments, better import example

* commentblock function (append to string output) + update tests (failing)

* 🚧 almost good... needs to respect renamed imports

* ✅ passing tests

* ♻️ organize some utils

* 🧪 Added more tests

* cast JSXIdentifier type

* pass idempotency tests

* also refactor the usage when renaming a function

---------

Co-authored-by: Ken <ken.aleksander@gmail.com>
2025-04-02 09:20:13 +02:00
Ken 01a395bdf7 💄 Update text-sizing for app-router heading 2025-04-01 14:56:40 +02:00
Ken b4b316c639 [Aksel.nav.no] More updates to app-router DS-pages, added SEO (#3717)
* ♻️ Update TOC feedback, add layout padding bottom

* 🐛 Added missing type

* 🐛 Fix missing spacing in intro-section

* 🎉 Added metadata for changelog view support in mønster pages

* 🎨 Refactor layout styles and enhance sidebar button animations

* 🎉 Added SEO support

* 🐛 Add page SEO image to start of image list

* 🔥 Remove reduntant early exit code

* 🐛 Open feedback in new tab

* 🐛 Update footer to use responsive layout

* 🐛 Moved theme-colors to viewmport config

* 🐛 Show mobile-menu earlier

* 🐛 Use local schema in sanity-typegen

* 📝 Typo
2025-04-01 14:46:19 +02:00
Ken d859497f07 📝 Removed title from list example 2025-04-01 11:29:18 +02:00
KenandCopilot eb257db444 [Aksel.nav.no] Resolved remaining sanityblocks to the darkside and appdir (#3708)
* 🔥 Removed legacy block for riktekst_blokk

* 🚚 Migrated AkselTable component

* 🚚 Migrated TokenTable component

* 💄 More visible warning

* 💄 Use 1920 as max-width for ds layout

* 🚚 Moved sidebar to ds scope

* 🚚 Moved AkselTable to WebsiteTable

* ✨ Added grunnleggende route

* 🚚 Moved List to WebsiteList

* 🐛 Fix spacing between ds header and content

* 🚚 Migrated token-view

* 🚚 Migrated ExampleText block

* 🚚 Migrated Attachment block

* 🚚 Moved compare-images module

* 🎉 Migrated compareimages module

* 🎉 Added language migration

* 🎉 Stronger typing to make sure we resolve every sanity type

* 🏷️ stricter types

* Update aksel.nav.no/website/app/_ui/legacy-token-module/token-view/parts/categories/Breakpoints.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update aksel.nav.no/website/app/_ui/compare-images/CompareImages.handle.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* 🐛 Use static initial aria value labels

* 🐛 Sync updated sanity schema

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-03-31 13:24:52 +02:00
Karl-Magnus Thorsnes 619547cd0e SearchButton💄 Vis forskjellig hurtigtast ut fra brukers OS (#3710)
* Feat differensier hurtigtast-beskrivelse ut fra OS

* Bruk simplere metode for å detectere OS
2025-03-31 12:29:06 +02:00
Julian Nymark dfd88e19dc update playroom (#3709) 2025-03-31 10:35:15 +02:00
dependabot[bot] fa17760f64 ⬆️ Bump figlet from 1.6.0 to 1.8.0 (#3711)
Bumps [figlet](https://github.com/patorjk/figlet.js) from 1.6.0 to 1.8.0.
- [Release notes](https://github.com/patorjk/figlet.js/releases)
- [Commits](https://github.com/patorjk/figlet.js/compare/1.6.0...1.8.0)

---
updated-dependencies:
- dependency-name: figlet
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-31 10:33:47 +02:00
Ken d77e7e203e 💄 Updated global meta-purple color for darkside (#3703) 2025-03-28 12:10:47 +01:00
171 changed files with 8207 additions and 1566 deletions

No files matched your search

+7
View File
@@ -4,6 +4,13 @@ import { Meta, Title } from "@storybook/blocks";
# Darkside changelog
## v7.18 (02.04.25)
`<Theme />`-component no longer defaults to `light`-theme className when no theme is provided.
This change is made to make it possible to handle 'light' and 'dark' themes in a more flexible way. You can manually set `className="light"` (or dark) on any html-element higher in the hierarchy, and the `<Theme />`-component will not override this.
One of the cases where this is needed is with `next-themes` where defaulting to light, and changing to dark based on `useTheme`-hook from `next-themes` leads to hydratiob errors.
## v7.16 (03.03.25)
To allow loading old and new CSS files that the same time, we have now renamed all classes to start with `.aksel` instead of `.navds`.
+6
View File
@@ -1,5 +1,11 @@
# @navikt/aksel-icons
## 7.20.0
## 7.19.1
## 7.19.0
## 7.18.0
## 7.17.4
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-icons",
"version": "7.18.0",
"version": "7.20.0",
"author": "Aksel | Nav",
"homepage": "https://aksel.nav.no/ikoner",
"repository": {
+6
View File
@@ -1,5 +1,11 @@
# @navikt/aksel-stylelint
## 7.20.0
## 7.19.1
## 7.19.0
## 7.18.0
## 7.17.4
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-stylelint",
"version": "7.18.0",
"version": "7.20.0",
"author": "Aksel | Nav",
"homepage": "https://aksel.nav.no/grunnleggende/kode/stylelint",
"repository": {
@@ -35,8 +35,8 @@
"dev": "yarn watch:lint"
},
"devDependencies": {
"@navikt/ds-css": "^7.18.0",
"@navikt/ds-tokens": "^7.18.0",
"@navikt/ds-css": "^7.20.0",
"@navikt/ds-tokens": "^7.20.0",
"concurrently": "9.0.1",
"postcss-selector-parser": "^6.0.13",
"postcss-value-parser": "^4.2.0",
+24
View File
@@ -1,5 +1,29 @@
# @navikt/aksel
## 7.20.0
### Patch Changes
- Updated dependencies [[`c57fe7c`](https://github.com/navikt/aksel/commit/c57fe7c396e81814e49a3d56a706cc2f690841c8)]:
- @navikt/ds-css@7.20.0
- @navikt/ds-tokens@7.20.0
## 7.19.1
### Patch Changes
- Updated dependencies [[`8c9cf92`](https://github.com/navikt/aksel/commit/8c9cf92c8bddeec48b1490c4b911f39070e79a89)]:
- @navikt/ds-css@7.19.1
- @navikt/ds-tokens@7.19.1
## 7.19.0
### Patch Changes
- Updated dependencies [[`277c74a`](https://github.com/navikt/aksel/commit/277c74a657319047fd0ea3249a43badfcba4b54e), [`d77e7e2`](https://github.com/navikt/aksel/commit/d77e7e203e522ed3a7fcdef5ca6abe6169314fb6), [`8b31c3b`](https://github.com/navikt/aksel/commit/8b31c3bc58027dbf8919b14174716f2eacab68fb)]:
- @navikt/ds-css@7.19.0
- @navikt/ds-tokens@7.19.0
## 7.18.0
### Patch Changes
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel",
"version": "7.18.0",
"version": "7.20.0",
"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": "^7.18.0",
"@navikt/ds-tokens": "^7.18.0",
"@navikt/ds-css": "^7.20.0",
"@navikt/ds-tokens": "^7.20.0",
"axios": "1.8.3",
"chalk": "4.1.0",
"cli-progress": "^3.12.0",
@@ -38,7 +38,7 @@
"commander": "10.0.1",
"enquirer": "^2.3.6",
"fast-glob": "3.2.11",
"figlet": "1.6.0",
"figlet": "1.8.0",
"is-git-clean": "1.1.0",
"jscodeshift": "^0.15.1",
"jscodeshift-add-imports": "1.0.11",
+7
View File
@@ -123,6 +123,13 @@ export const migrations: {
value: "prop-deprecate",
path: "darkside/prop-deprecate/prop-deprecate",
},
{
description:
"Update Box to to BoxNew (future Box) using the new token system",
value: "box-to-boxnew",
path: "darkside/box-to-boxnew/box-to-boxnew",
warning: "Remember to check if 'aksel box migration'-comment were added to any files after migration. This comment will help you find and update Box-instances where we could not resolve the update for you.",
},
],
};
@@ -0,0 +1,193 @@
import type { API, FileInfo, JSCodeshift, JSXIdentifier } from "jscodeshift";
import { legacyTokenConfig } from "../../../../darkside/config/legacy.tokens";
import {
findComponentImport,
findJSXElement,
findProps,
} from "../../../utils/ast";
import { getLineTerminator } from "../../../utils/lineterminator";
import moveAndRenameImport, {
addPackageImport,
} from "../../../utils/packageImports";
const propsAffected = ["background", "borderColor", "shadow"];
export default function transformer(file: FileInfo, api: API) {
const j = api.jscodeshift;
const root = j(file.source);
const toSourceOptions = getLineTerminator(file.source);
if (file.source.includes("TODO: aksel box migration")) {
return root.toSource(toSourceOptions);
}
const localName = findComponentImport({
root,
j,
name: "Box",
packageType: "react",
});
if (!localName) {
return;
}
const astElements = root.find(j.JSXElement, {
openingElement: {
name: {
type: "JSXIdentifier",
name: localName,
},
},
});
const tokenComments: TokenComments = [];
for (const astElement of astElements.paths()) {
let encounteredUnmigratableProp = false;
for (const prop of propsAffected) {
findProps({ j, path: astElement, name: prop }).forEach((attr) => {
const attrvalue = attr.value.value;
if (attrvalue.type === "StringLiteral") {
const config = legacyTokenConfig[attrvalue.value];
if (config?.replacement) {
attrvalue.value = config.replacement;
} else {
encounteredUnmigratableProp = true;
const tokenComment: TokenComment = {
prop,
token: attrvalue.value,
};
if (config?.comment) {
tokenComment.comment = config.comment;
}
tokenComments.push(tokenComment);
}
}
});
}
if (!encounteredUnmigratableProp) {
// TODO: ?? Box -> BoxNew type fail? (but works)
(astElement.node.openingElement.name as JSXIdentifier).name = "BoxNew";
(astElement.node.closingElement.name as JSXIdentifier).name = "BoxNew";
}
}
const blockComment = createFileComments({ tokenComments });
const importAnalysis = analyzePartialMigration(
j,
root.toSource(toSourceOptions),
);
if (importAnalysis === "no new") {
// WHY: we do nothing to the import statements if we couldn't migrate any Box
}
if (importAnalysis === "mixed") {
// WHY: mixed Box and BoxNew == we keep old, and add the new import
addPackageImport({
j,
root,
packageName: "@navikt/ds-react/Box",
specifiers: ["BoxNew"],
});
}
if (importAnalysis === "all new") {
// WHY: when we have only new boxes == we replace the old import with the new one
moveAndRenameImport(j, root, {
fromImport: "@navikt/ds-react",
toImport: "@navikt/ds-react/Box",
fromName: "Box",
toName: "BoxNew",
ignoreAlias: localName !== "Box",
});
}
const output = `${blockComment ? blockComment + "\n\n" : ""}${root.toSource(
toSourceOptions,
)}`;
return output;
}
type TokenComment = {
prop: string;
token: string;
comment?: string;
};
type TokenComments = TokenComment[];
const createFileComments = ({
tokenComments,
}: {
tokenComments: TokenComments;
}) => {
if (tokenComments.length === 0) {
return null;
}
let constructedComment =
"/*\nTODO: aksel box migration:\nCould not migrate the following:\n";
for (const { prop, token, comment } of tokenComments.values()) {
constructedComment += ` - ${prop}=${token}\n`;
if (comment) {
constructedComment += ` - ${comment}\n`;
}
}
constructedComment += "*/";
return constructedComment;
};
type MigrationResult = "all new" | "mixed" | "no new";
const analyzePartialMigration = (
j: JSCodeshift,
source: string,
): MigrationResult => {
const root = j(source);
const astNewElements = findJSXElement({
root,
j,
name: "BoxNew",
originalName: "BoxNew",
});
if (astNewElements.length === 0) {
return "no new";
}
const localName = findComponentImport({
root,
j,
name: "Box",
packageType: "react",
});
if (!localName) {
// this should never happen
throw new Error(
'package imports have been tampered with before the package import "step" in the migration',
);
}
const astOldElements = findJSXElement({
root,
j,
name: localName,
originalName: "Box",
});
if (astOldElements.length === 0) {
return "all new";
}
return "mixed";
};
@@ -0,0 +1,21 @@
import { Box } from "@navikt/ds-react"
export const MyComponent = () => {
return (<>
<Box background="bg-subtle" borderColor="border-alt-1">
migratable
</Box>
<Box background="bg-subtle" borderColor="border-alt-1" shadow="large">
migratable + unmigratable (no comment)
</Box>
<Box background="surface-alt-3-strong" >
unmigratable (with comment)
</Box>
<Box borderWidth="4" padding={{ lg: "10", sm: "8" }} height="200rem">
old
</Box>
<Box borderWidth="4" background="bg-subtle">
old + migratable
</Box>
</>);
}
@@ -0,0 +1,30 @@
/*
TODO: aksel box migration:
Could not migrate the following:
- shadow=large
- background=surface-alt-3-strong
- Use 'bg-brand-blue-moderate' in theme 'dark'-mode.
*/
import { BoxNew } from "@navikt/ds-react/Box"
import { Box } from "@navikt/ds-react"
export const MyComponent = () => {
return (<>
<BoxNew background="bg-neutral-soft" borderColor="border-meta-purple">
migratable
</BoxNew>
<Box background="bg-neutral-soft" borderColor="border-meta-purple" shadow="large">
migratable + unmigratable (no comment)
</Box>
<Box background="surface-alt-3-strong" >
unmigratable (with comment)
</Box>
<BoxNew borderWidth="4" padding={{ lg: "10", sm: "8" }} height="200rem">
old
</BoxNew>
<BoxNew borderWidth="4" background="bg-neutral-soft">
old + migratable
</BoxNew>
</>);
}
@@ -0,0 +1,20 @@
/*
TODO: aksel box migration:
Could not migrate the following:
- shadow=large
- background=surface-alt-3-strong
- Use 'bg-brand-blue-moderate' in theme 'dark'-mode.
*/
import { Box } from "@navikt/ds-react"
export const MyComponent = () => {
return (<>
<Box background="bg-neutral-soft" borderColor="border-meta-purple" shadow="large">
migratable + unmigratable (no comment)
</Box>
<Box background="surface-alt-3-strong" >
unmigratable (with comment)
</Box>
</>);
}
@@ -0,0 +1,20 @@
/*
TODO: aksel box migration:
Could not migrate the following:
- shadow=large
- background=surface-alt-3-strong
- Use 'bg-brand-blue-moderate' in theme 'dark'-mode.
*/
import { Box } from "@navikt/ds-react"
export const MyComponent = () => {
return (<>
<Box background="bg-neutral-soft" borderColor="border-meta-purple" shadow="large">
migratable + unmigratable (no comment)
</Box>
<Box background="surface-alt-3-strong" >
unmigratable (with comment)
</Box>
</>);
}
@@ -0,0 +1,30 @@
/*
TODO: aksel box migration:
Could not migrate the following:
- shadow=large
- background=surface-alt-3-strong
- Use 'bg-brand-blue-moderate' in theme 'dark'-mode.
*/
import { BoxNew } from "@navikt/ds-react/Box"
import { Box } from "@navikt/ds-react"
export const MyComponent = () => {
return (<>
<BoxNew background="bg-neutral-soft" borderColor="border-meta-purple">
migratable
</BoxNew>
<Box background="bg-neutral-soft" borderColor="border-meta-purple" shadow="large">
migratable + unmigratable (no comment)
</Box>
<Box background="surface-alt-3-strong" >
unmigratable (with comment)
</Box>
<BoxNew borderWidth="4" padding={{ lg: "10", sm: "8" }} height="200rem">
old
</BoxNew>
<BoxNew borderWidth="4" background="bg-neutral-soft">
old + migratable
</BoxNew>
</>);
}
@@ -0,0 +1,30 @@
/*
TODO: aksel box migration:
Could not migrate the following:
- shadow=large
- background=surface-alt-3-strong
- Use 'bg-brand-blue-moderate' in theme 'dark'-mode.
*/
import { BoxNew } from "@navikt/ds-react/Box"
import { Box } from "@navikt/ds-react"
export const MyComponent = () => {
return (<>
<BoxNew background="bg-neutral-soft" borderColor="border-meta-purple">
migratable
</BoxNew>
<Box background="bg-neutral-soft" borderColor="border-meta-purple" shadow="large">
migratable + unmigratable (no comment)
</Box>
<Box background="surface-alt-3-strong" >
unmigratable (with comment)
</Box>
<BoxNew borderWidth="4" padding={{ lg: "10", sm: "8" }} height="200rem">
old
</BoxNew>
<BoxNew borderWidth="4" background="bg-neutral-soft">
old + migratable
</BoxNew>
</>);
}
@@ -0,0 +1,12 @@
import { Box as AkselBox } from "@navikt/ds-react"
export const MyComponent = () => {
return (<>
<AkselBox background="bg-subtle" borderColor="border-alt-1">
simple rename of import
</AkselBox>
<AkselBox shadow="medium">
simple rename of import
</AkselBox>
</>);
}
@@ -0,0 +1,19 @@
/*
TODO: aksel box migration:
Could not migrate the following:
- shadow=medium
*/
import { BoxNew } from "@navikt/ds-react/Box";
import { Box as AkselBox } from "@navikt/ds-react"
export const MyComponent = () => {
return (<>
<BoxNew background="bg-neutral-soft" borderColor="border-meta-purple">
simple rename of import
</BoxNew>
<AkselBox shadow="medium">
simple rename of import
</AkselBox>
</>);
}
@@ -0,0 +1,11 @@
import Box from '@mui/material/Box';
import { Box as AkselBox } from "@navikt/ds-react"
export const MyComponent = () => {
return (<>
<Box>unrelated Box from @mui/material</Box>
<AkselBox>
simple rename of import
</AkselBox>
</>);
}
@@ -0,0 +1,11 @@
import Box from '@mui/material/Box';
import { BoxNew } from "@navikt/ds-react/Box"
export const MyComponent = () => {
return (<>
<Box>unrelated Box from @mui/material</Box>
<BoxNew>
simple rename of import
</BoxNew>
</>);
}
@@ -0,0 +1,12 @@
import { check } from "../../../../utils/check";
const migration = "box-to-boxnew";
const fixtures = ["box", "import", "import2", "idempotent", "idempotent-2"];
for (const fixture of fixtures) {
check(__dirname, {
fixture,
migration,
extension: "js",
});
}
@@ -17,7 +17,7 @@ export default function transformer(file: FileInfo, api: API) {
for (const [component, props] of Object.entries(deprecationMap)) {
const sourceName = findComponentImport({
file,
root,
j,
name: component,
packageType: "react",
@@ -2,7 +2,7 @@ import type { API, FileInfo } from "jscodeshift";
import {
findComponentImport,
findJSXElement,
findProp,
findProps,
} from "../../../utils/ast";
import { getLineTerminator } from "../../../utils/lineterminator";
import { legacySpacingTokenMap } from "../spacing.utils";
@@ -41,7 +41,7 @@ export default function transformer(file: FileInfo, api: API) {
];
for (const primitive of primitives) {
const name = findComponentImport({ file, j, name: primitive });
const name = findComponentImport({ root, j, name: primitive });
if (!name) {
continue;
}
@@ -53,7 +53,7 @@ export default function transformer(file: FileInfo, api: API) {
originalName: primitive,
}).forEach((path) => {
for (const prop of affectedProps) {
findProp({ j, path, name: prop }).forEach((attr) => {
findProps({ j, path, name: prop }).forEach((attr) => {
const attrValue = attr.value.value;
if (attrValue.type === "StringLiteral") {
@@ -13,7 +13,7 @@ export default function transformer(file: FileInfo, api: API) {
const newCSSVar = translateToken(`--a-space-${newVar}`, "js");
const name = findComponentImport({
file,
root,
j,
name: oldCSSVar,
packageType: "tokens",
@@ -1,6 +1,6 @@
import chalk from "chalk";
import { getLineTerminator } from "../../../utils/lineterminator";
import moveAndRenameImport from "../../../utils/moveAndRenameImport";
import moveAndRenameImport from "../../../utils/packageImports";
import removePropsFromComponent from "../../../utils/removeProps";
/**
@@ -1,5 +1,5 @@
import { getLineTerminator } from "../../../utils/lineterminator";
import moveAndRenameImport from "../../../utils/moveAndRenameImport";
import moveAndRenameImport from "../../../utils/packageImports";
/**
* @param {import('jscodeshift').FileInfo} file
+5 -7
View File
@@ -1,7 +1,6 @@
import {
API,
ASTPath,
FileInfo,
ImportDeclaration,
JSXElement,
JSXOpeningElement,
@@ -10,20 +9,19 @@ import {
/**
* Finds a component import, accounting for sub-components and aliases.
* Returns the local name of the component. If the component is not found, returns null.
* root should ideally be the root of the file in most cases (imports happen at top level)
*/
function findComponentImport(input: {
j: API["j"];
file: FileInfo;
root: ReturnType<API["j"]>;
name: string;
packageType?: "react" | "tokens";
}) {
const { j, file, name: _name, packageType = "react" } = input;
const { j, root, name: _name, packageType = "react" } = input;
/* Account for sub-components */
const name = _name.includes(".") ? _name.split(".")[0] : _name;
const root = j(file.source);
let foundName: string | null = null;
root.find(j.ImportDeclaration).forEach((path) => {
@@ -92,7 +90,7 @@ function findJSXElement(input: {
/**
* Finds a prop in a JSX element.
*/
function findProp(input: {
function findProps(input: {
j: API["j"];
path: ASTPath<JSXElement>;
name: string;
@@ -128,4 +126,4 @@ function isAkselTokensImport(path: ASTPath<ImportDeclaration>) {
);
}
export { findComponentImport, findJSXElement, findProp };
export { findComponentImport, findJSXElement, findProps };
@@ -1,4 +1,31 @@
import core, { Collection } from "jscodeshift";
import core, { Collection, JSCodeshift } from "jscodeshift";
// add import declaration after first existing import declaration, or
// at the beginning of the file
export const addPackageImport = ({
j,
root,
packageName,
specifiers,
}: {
j: JSCodeshift;
root: Collection<any>;
packageName: string;
specifiers: string[];
}) => {
const existingImport = root.find(j.ImportDeclaration);
const importDecl = j.importDeclaration(
specifiers.map((specifier) => j.importSpecifier(j.identifier(specifier))),
j.stringLiteral(packageName),
);
if (existingImport.length === 0) {
root.get().node.program.body.unshift(importDecl);
} else {
existingImport.insertBefore(importDecl);
}
};
export default function moveAndRenameImport(
j: core.JSCodeshift,
@@ -691,7 +691,7 @@ const colors = {
"surface-alt-3-strong": {
ref: "deepblue-800",
raw: "rgba(0, 52, 83, 1)",
comment: "Use 'bg-brand-blue-moderate' in theme 'dark'-mode. ",
comment: "Use 'bg-brand-blue-moderate' in theme 'dark'-mode.",
replacement: "",
},
"surface-alt-3-moderate": {
@@ -1,7 +1,7 @@
import type { API, FileInfo } from "jscodeshift";
import { getImportSpecifier } from "../../codemod/utils/imports";
import { getLineTerminator } from "../../codemod/utils/lineterminator";
import moveAndRenameImport from "../../codemod/utils/moveAndRenameImport";
import moveAndRenameImport from "../../codemod/utils/packageImports";
import { translateToken } from "../../codemod/utils/translate-token";
import { legacyTokenConfig } from "../config/legacy.tokens";
+20
View File
@@ -1,5 +1,25 @@
# @navikt/ds-css
## 7.20.0
### Minor Changes
- Combobox: Use option-value as `id` instead of `label`. ([#3735](https://github.com/navikt/aksel/pull/3735))
## 7.19.1
### Patch Changes
- Combobox: Singleselect selected value now uses the complete input-width when possible. ([#3734](https://github.com/navikt/aksel/pull/3734))
## 7.19.0
### Patch Changes
- Darkside: Togglegroup now has bg-input background. ([#3715](https://github.com/navikt/aksel/pull/3715))
- Combobox: Fix wrapping issue when ToggleListButton is hidden ([#3727](https://github.com/navikt/aksel/pull/3727))
## 7.18.0
### Patch Changes
@@ -109,11 +109,6 @@
flex: 2;
}
& > :last-child {
display: flex;
flex-flow: row nowrap;
}
&:hover {
cursor: text;
}
@@ -185,6 +180,10 @@
.aksel-combobox__selected-options > li:only-child > & {
margin-left: var(--ax-space-4);
}
.aksel-combobox__selected-options[data-type="single"] & {
min-width: auto;
}
}
.aksel-combobox__input--hide-caret {
@@ -316,6 +315,12 @@
outline: 2px solid var(--ax-border-focus);
}
.aksel-combobox__list-item--focus,
.aksel-combobox__list--with-hover
.aksel-combobox__list-item:not([data-no-focus="true"], .aksel-combobox__list-item--new-option):hover {
cursor: pointer;
}
.aksel-combobox__list-item {
user-select: none;
@@ -11,7 +11,7 @@
/* Main Togglegroup */
.aksel-toggle-group {
border-radius: var(--ax-border-radius-large);
background-color: transparent;
background-color: var(--ax-bg-input);
box-shadow: inset 0 0 0 1px var(--ax-border-neutral);
display: inline-grid;
grid-auto-flow: column;
+4 -5
View File
@@ -71,11 +71,6 @@
flex: 2;
}
.navds-combobox__wrapper-inner > :last-child {
display: flex;
flex-flow: row nowrap;
}
.navds-combobox__wrapper-inner:hover {
cursor: text;
}
@@ -141,6 +136,10 @@
height: var(--__ac-combobox-input-height);
}
.navds-combobox__selected-options[data-type="single"] .navds-combobox__input {
min-width: auto;
}
.navds-combobox__input--hide-caret {
caret-color: transparent;
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-css",
"version": "7.18.0",
"version": "7.20.0",
"description": "CSS for Nav Designsystem",
"author": "Aksel | Nav designsystem team",
"keywords": [
@@ -27,7 +27,7 @@
"test": "yarn build && vitest run"
},
"devDependencies": {
"@navikt/ds-tokens": "^7.18.0",
"@navikt/ds-tokens": "^7.20.0",
"autoprefixer": "^10.4.20",
"browserslist": "^4.24.4",
"cssnano": "7.0.6",
+36
View File
@@ -1,5 +1,41 @@
# @navikt/ds-react
## 7.20.0
### Minor Changes
- Combobox: Use option-value as `id` instead of `label`. ([#3735](https://github.com/navikt/aksel/pull/3735))
- Timeline: Add `placement`-prop for periods for more fine-grained control over Popover-placement. ([#3736](https://github.com/navikt/aksel/pull/3736))
### Patch Changes
- Updated dependencies []:
- @navikt/ds-tokens@7.20.0
- @navikt/aksel-icons@7.20.0
## 7.19.1
### Patch Changes
- Darkside: Using `Page`-component would always give errors when using new `Theme`-component. ([`208adb8`](https://github.com/navikt/aksel/commit/208adb8b3f1a93a872782a8f309fed65b6b112d2))
- Updated dependencies []:
- @navikt/ds-tokens@7.19.1
- @navikt/aksel-icons@7.19.1
## 7.19.0
### Minor Changes
- Darkside: `<Theme />`-component no longer defaults to `light`-theme className when no theme is provided. ([#3720](https://github.com/navikt/aksel/pull/3720))
### Patch Changes
- Updated dependencies [[`d77e7e2`](https://github.com/navikt/aksel/commit/d77e7e203e522ed3a7fcdef5ca6abe6169314fb6)]:
- @navikt/ds-tokens@7.19.0
- @navikt/aksel-icons@7.19.0
## 7.18.0
### Minor Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-react",
"version": "7.18.0",
"version": "7.20.0",
"description": "React components from the Norwegian Labour and Welfare Administration.",
"author": "Aksel, a team part of the Norwegian Labour and Welfare Administration.",
"license": "MIT",
@@ -625,8 +625,8 @@
"dependencies": {
"@floating-ui/react": "0.25.4",
"@floating-ui/react-dom": "^2.0.9",
"@navikt/aksel-icons": "^7.18.0",
"@navikt/ds-tokens": "^7.18.0",
"@navikt/aksel-icons": "^7.20.0",
"@navikt/ds-tokens": "^7.20.0",
"clsx": "^2.1.0",
"date-fns": "^4.0.0",
"react-day-picker": "9.6.1"
@@ -31,7 +31,7 @@ interface AccordionComponent
export interface AccordionProps extends React.HTMLAttributes<HTMLDivElement> {
/**
* @deprecated Prop will be removed in future versions. "default" will be the only variant.
* @deprecated "default" will be the only variant.
* @default "default"
*/
variant?: "default" | "neutral";
@@ -43,29 +43,25 @@ const FilteredOptionsItem = ({ option }: { option: ComboboxOption }) => {
const isDisabled = (_option: ComboboxOption) =>
maxSelected.isLimitReached && !isInList(_option.value, selectedOptions);
const optionId = filteredOptionsUtil.getOptionId(id, option.value);
const isActive = activeDecendantId === optionId;
return (
<li
className={cn("navds-combobox__list-item", {
"navds-combobox__list-item--focus":
activeDecendantId ===
filteredOptionsUtil.getOptionId(id, option.label),
"navds-combobox__list-item--focus": isActive,
"navds-combobox__list-item--selected": isInList(
option.value,
selectedOptions,
),
})}
data-no-focus={isDisabled(option) || undefined}
id={filteredOptionsUtil.getOptionId(id, option.label)}
key={option.label}
id={optionId}
key={optionId}
tabIndex={-1}
onMouseMove={() => {
if (
activeDecendantId !==
filteredOptionsUtil.getOptionId(id, option.label)
) {
virtualFocus.moveFocusToElement(
filteredOptionsUtil.getOptionId(id, option.label),
);
if (!isActive) {
virtualFocus.moveFocusToElement(optionId);
setIsMouseLastUsedInputDevice(true);
}
}}
@@ -91,7 +91,7 @@ const FilteredOptionsProvider = ({
? toComboboxOption(value)
: undefined,
...customOptions.reduce((acc, customOption) => {
const _id = filteredOptionsUtils.getOptionId(id, customOption.label);
const _id = filteredOptionsUtils.getOptionId(id, customOption.value);
acc[_id] = customOption;
return acc;
}, {}),
@@ -99,7 +99,7 @@ const FilteredOptionsProvider = ({
// Add the options to the map
const finalMap = options.reduce((map, _option) => {
const _id = filteredOptionsUtils.getOptionId(id, _option.label);
const _id = filteredOptionsUtils.getOptionId(id, _option.value);
map[_id] = _option;
return map;
}, initialMap);
@@ -171,7 +171,7 @@ const FilteredOptionsProvider = ({
if (shouldAutocomplete && filteredOptions[0]) {
activeOption = filteredOptionsUtils.getOptionId(
id,
filteredOptions[0].label,
filteredOptions[0].value,
);
} else if (isListOpen && isLoading) {
activeOption = filteredOptionsUtils.getIsLoadingId(id);
+3 -3
View File
@@ -14,7 +14,7 @@ export interface PageProps extends React.HTMLAttributes<HTMLElement> {
* Background color.
* Accepts a [background color token](https://aksel.nav.no/grunnleggende/styling/design-tokens#753d1cf4d1d6).
* @default "bg-default"
* @deprecated 'background'-prop will be removed in future major-versions. Use `<Box asChild background="...">` wrapped around `<Page>`.
* @deprecated Use `<Box asChild background="...">` wrapped around `<Page>`.
*/
background?: BackgroundColorToken;
/**
@@ -47,7 +47,7 @@ export const PageComponent: OverridableComponent<PageProps, HTMLElement> =
footer,
children,
footerPosition,
background = "bg-default",
background,
contentBlockPadding = "end",
...rest
},
@@ -64,7 +64,7 @@ export const PageComponent: OverridableComponent<PageProps, HTMLElement> =
const style: React.CSSProperties = {
..._style,
"--__ac-page-background": `var(--a-${background})`,
"--__ac-page-background": `var(--a-${background ?? "bg-default"})`,
};
const belowFold = footerPosition === "belowFold";
+2 -2
View File
@@ -48,7 +48,7 @@ export const List = forwardRef<HTMLDivElement, ListProps>(
as: ListTag = "ul",
title,
description,
headingTag = "h3",
headingTag,
size,
"aria-label": _ariaLabel,
"aria-labelledby": _ariaLabelledBy,
@@ -114,7 +114,7 @@ export const List = forwardRef<HTMLDivElement, ListProps>(
className={cn("navds-list", `navds-list--${listSize}`, className)}
>
{title && (
<Heading size={headingSizeMap[listSize]} as={headingTag}>
<Heading size={headingSizeMap[listSize]} as={headingTag ?? "h3"}>
{title}
</Heading>
)}
+3 -3
View File
@@ -7,18 +7,18 @@ export interface ListProps extends React.HTMLAttributes<HTMLDivElement> {
as?: "ul" | "ol";
/**
* List heading title.
* @deprecated Prop will be removed in future versions.
* @deprecated Use a "plain" <Heading> instead of `title`, Composition of smaller components preferred over smarter components.
*/
title?: string;
/**
* List heading description.
* @deprecated Prop will be removed in future versions.
* @deprecated Use a "plain" <BodyShort> instead of `description`, Composition of smaller components preferred over smarter components.
*/
description?: string;
/**
* Allows setting a different HTML h-tag.
* @default "h3"
* @deprecated Prop will be removed in future versions.
* @deprecated The `title` prop gets deprecated, so this one follows suit, as it was a supplementary prop to the title.
*/
headingTag?: React.ElementType<any>;
/**
+1 -1
View File
@@ -72,7 +72,7 @@ const Theme = forwardRef<HTMLDivElement, ThemeProps>(
children,
className,
asChild = false,
theme = context?.theme ?? "light",
theme = context?.theme,
hasBackground: hasBackgroundProp = true,
} = props;
@@ -29,6 +29,11 @@ interface TimelineClickablePeriodProps extends PeriodProps {
) => void;
isActive?: boolean;
periodRef: React.ForwardedRef<HTMLButtonElement>;
/**
* Default orientation of popover
* @default "top"
*/
placement?: "top" | "bottom";
}
const ClickablePeriod = React.memo(
@@ -66,7 +71,7 @@ const ClickablePeriod = React.memo(
refs,
floatingStyles,
} = useFloating({
placement: "top",
placement: restProps?.placement ?? "top",
open,
onOpenChange: (_open) => setOpen(_open),
whileElementsMounted: autoUpdate,
@@ -43,6 +43,11 @@ export interface TimelinePeriodProps
* Make sure only one period is active at a time.
*/
isActive?: boolean;
/**
* Default orientation of popover
* @default "top"
*/
placement?: "top" | "bottom";
}
export interface PeriodType
@@ -54,7 +59,7 @@ export interface PeriodType
}
export const Period = forwardRef<HTMLDivElement, TimelinePeriodProps>(
({ icon }, ref) => {
({ icon }: TimelinePeriodProps, ref) => {
const { periods } = useRowContext();
const { periodId, restProps } = usePeriodContext();
@@ -386,6 +386,7 @@ export const ActivePeriod: StoryFn = () => {
end={new Date("Aug 20 2022")}
status="info"
icon={<CheckmarkCircleFillIcon aria-hidden />}
placement="bottom"
/>
</Timeline.Row>
</Timeline>
+1 -1
View File
@@ -8,7 +8,7 @@ export interface DetailProps
extends TypoProps,
React.HTMLAttributes<HTMLParagraphElement> {
/**
* @deprecated Medium === small
* @deprecated Medium is now the same as small.
*/
size?: "medium" | "small";
/**
+6
View File
@@ -1,5 +1,11 @@
# @navikt/ds-tailwind
## 7.20.0
## 7.19.1
## 7.19.0
## 7.18.0
## 7.17.4
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-tailwind",
"version": "7.18.0",
"version": "7.20.0",
"description": "Tailwind config based on @navikt/ds-tokens",
"author": "Aksel | Nav designsystem team",
"keywords": [
@@ -25,7 +25,7 @@
"directory": "@navikt/core/tailwind"
},
"devDependencies": {
"@navikt/ds-tokens": "^7.18.0",
"@navikt/ds-tokens": "^7.20.0",
"color": "4.2.3",
"lodash": "^4.17.21",
"tailwindcss": "^3.3.3",
+10
View File
@@ -1,5 +1,15 @@
# @navikt/ds-tokens
## 7.20.0
## 7.19.1
## 7.19.0
### Patch Changes
- Darkside: Update global 'meta-purple' color. ([#3703](https://github.com/navikt/aksel/pull/3703))
## 7.18.0
## 7.17.4
@@ -119,16 +119,16 @@ const globalLightTokensNoAlpha: GlobalConfigWithoutAlpha = {
"1000": { value: "#002942", type: "global-color", group: "brand-blue" },
},
"meta-purple": {
"100": { value: "#f7f5f9", type: "global-color", group: "meta-purple" },
"200": { value: "#f0edf4", type: "global-color", group: "meta-purple" },
"300": { value: "#e6e1ed", type: "global-color", group: "meta-purple" },
"400": { value: "#d7cfe3", type: "global-color", group: "meta-purple" },
"500": { value: "#9580b1", type: "global-color", group: "meta-purple" },
"600": { value: "#846ca4", type: "global-color", group: "meta-purple" },
"700": { value: "#725a91", type: "global-color", group: "meta-purple" },
"800": { value: "#6a5289", type: "global-color", group: "meta-purple" },
"900": { value: "#5e467c", type: "global-color", group: "meta-purple" },
"1000": { value: "#331a4d", type: "global-color", group: "meta-purple" },
"100": { value: "#f9f3ff", type: "global-color", group: "meta-purple" },
"200": { value: "#f4ebff", type: "global-color", group: "meta-purple" },
"300": { value: "#ebdefc", type: "global-color", group: "meta-purple" },
"400": { value: "#dfcaf9", type: "global-color", group: "meta-purple" },
"500": { value: "#a370e2", type: "global-color", group: "meta-purple" },
"600": { value: "#925ad6", type: "global-color", group: "meta-purple" },
"700": { value: "#7f45c8", type: "global-color", group: "meta-purple" },
"800": { value: "#763cc0", type: "global-color", group: "meta-purple" },
"900": { value: "#682eb2", type: "global-color", group: "meta-purple" },
"1000": { value: "#35086b", type: "global-color", group: "meta-purple" },
},
"meta-lime": {
"100": { value: "#f4f9d1", type: "global-color", group: "meta-lime" },
@@ -263,16 +263,16 @@ const globalDarkTokensNoAlpha: GlobalConfigWithoutAlpha = {
"1000": { value: "#b4ccdc", type: "global-color", group: "brand-blue" },
},
"meta-purple": {
"100": { value: "#221a2c", type: "global-color", group: "meta-purple" },
"200": { value: "#272032", type: "global-color", group: "meta-purple" },
"300": { value: "#2f273a", type: "global-color", group: "meta-purple" },
"400": { value: "#3a3245", type: "global-color", group: "meta-purple" },
"500": { value: "#776491", type: "global-color", group: "meta-purple" },
"600": { value: "#8976a2", type: "global-color", group: "meta-purple" },
"700": { value: "#9b8bb0", type: "global-color", group: "meta-purple" },
"800": { value: "#a495b7", type: "global-color", group: "meta-purple" },
"900": { value: "#b2a7c3", type: "global-color", group: "meta-purple" },
"1000": { value: "#cec4dc", type: "global-color", group: "meta-purple" },
"100": { value: "#1f1832", type: "global-color", group: "meta-purple" },
"200": { value: "#261e3b", type: "global-color", group: "meta-purple" },
"300": { value: "#2e2546", type: "global-color", group: "meta-purple" },
"400": { value: "#3a2f55", type: "global-color", group: "meta-purple" },
"500": { value: "#8157bb", type: "global-color", group: "meta-purple" },
"600": { value: "#936aca", type: "global-color", group: "meta-purple" },
"700": { value: "#a581d6", type: "global-color", group: "meta-purple" },
"800": { value: "#ae8cdb", type: "global-color", group: "meta-purple" },
"900": { value: "#bb9fe3", type: "global-color", group: "meta-purple" },
"1000": { value: "#efe4fd", type: "global-color", group: "meta-purple" },
},
"meta-lime": {
"100": { value: "#1c1f00", type: "global-color", group: "meta-lime" },
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-tokens",
"version": "7.18.0",
"version": "7.20.0",
"description": "Design-tokens for Nav designsystem",
"author": "Aksel | Nav designsystem team",
"keywords": [
+32
View File
@@ -1,5 +1,37 @@
# Changelog
## 7.20
### @navikt/ds-react
- Combobox: Use option-value as `id` instead of `label`. ([#3735](https://github.com/navikt/aksel/pull/3735))
- Timeline: Add `placement`-prop for periods for more fine-grained control over Popover-placement. ([#3736](https://github.com/navikt/aksel/pull/3736))
### @navikt/ds-css
- Combobox: Use option-value as `id` instead of `label`. ([#3735](https://github.com/navikt/aksel/pull/3735))
## 7.19
### @navikt/ds-react
- Darkside: Using `Page`-component would always give errors when using new `Theme`-component. ([`208adb8`](https://github.com/navikt/aksel/commit/208adb8b3f1a93a872782a8f309fed65b6b112d2))
* Darkside: `<Theme />`-component no longer defaults to `light`-theme className when no theme is provided. ([#3720](https://github.com/navikt/aksel/pull/3720))
### @navikt/ds-tokens
- Darkside: Update global 'meta-purple' color. ([#3703](https://github.com/navikt/aksel/pull/3703))
### @navikt/ds-css
- Combobox: Singleselect selected value now uses the complete input-width when possible. ([#3734](https://github.com/navikt/aksel/pull/3734))
* Darkside: Togglegroup now has bg-input background. ([#3715](https://github.com/navikt/aksel/pull/3715))
* Combobox: Fix wrapping issue when ToggleListButton is hidden ([#3727](https://github.com/navikt/aksel/pull/3727))
## 7.18
### @navikt/ds-react
+8
View File
@@ -34,6 +34,14 @@ yarn workspaces focus @navikt/aksel-icons @navikt/ds-tokens @navikt/ds-css @navi
Vi bruker [Storybook](https://storybook.js.org/) for utvikling av nye komponenter.
Lag [stories](https://storybook.js.org/docs/writing-stories) for komponentene du vil teste.
### safeguards
Om man vil ha en ekstra safeguard for å hindre at man kjører diverse sanity scripts fra lokal maskin mot produksjons-datasettet så kan du legge til denne variabelen i filen `./aksel.nav.no/website/.env`:
```sh
LOCAL_DATASET_OVERRIDE=development
```
## Utvikling av nye komponenter
Det er ikke satt opp noen template for nye komponenter, men vi følger en mal.
+5 -5
View File
@@ -15,10 +15,10 @@
"playroom"
],
"dependencies": {
"@navikt/aksel-icons": "^7.18.0",
"@navikt/ds-css": "^7.18.0",
"@navikt/ds-react": "^7.18.0",
"@navikt/ds-tailwind": "^7.18.0",
"@navikt/ds-tokens": "^7.18.0"
"@navikt/aksel-icons": "^7.20.0",
"@navikt/ds-css": "^7.20.0",
"@navikt/ds-react": "^7.20.0",
"@navikt/ds-tailwind": "^7.20.0",
"@navikt/ds-tokens": "^7.20.0"
}
}
+1 -1
View File
@@ -11,7 +11,7 @@
"babel-loader": "^9.1.3",
"babel-plugin-preval": "^5.1.0",
"css-loader": "^6.8.1",
"playroom": "^0.38.0",
"playroom": "^0.42.0",
"style-loader": "^3.3.3"
},
"dependencies": {
+80 -1
View File
@@ -1,5 +1,5 @@
import { defineQuery, groq } from "next-sanity";
import { destructureBlocks } from "@/sanity/queries";
import { contributorsAll, destructureBlocks } from "@/sanity/queries";
const DESIGNSYSTEM_TYPES = `"komponent_artikkel", "ds_artikkel", "templates_artikkel"`;
@@ -13,6 +13,25 @@ const DESIGNSYSTEM_SIDEBAR_QUERY =
"sidebarindex": sidebarindex,
}`);
const DESIGNSYSTEM_OVERVIEW_PAGES_QUERY = defineQuery(
`*[_type == "komponenter_landingsside" || _type == "grunnleggende_landingsside" || _type == "templates_landingsside"] {
_type,
overview_pages
}`,
);
const DESIGNSYSTEM_KOMPONENTER_LANDINGPAGE_QUERY = defineQuery(
`*[_type == "komponenter_landingsside"][0]`,
);
const DESIGNSYSTEM_GRUNNLEGGENDE_LANDINGPAGE_QUERY = defineQuery(
`*[_type == "grunnleggende_landingsside"][0]`,
);
const DESIGNSYSTEM_TEMPLATES_LANDINGPAGE_QUERY = defineQuery(
`*[_type == "templates_landingsside"][0]`,
);
const searchContent = groq`{
heading,
"slug": slug.current,
@@ -53,20 +72,80 @@ const KOMPONENT_BY_SLUG_QUERY =
},
}`);
const DESIGNSYSTEM_OVERVIEW_BY_CATEGORY_QUERY =
defineQuery(`*[_type == $docType && kategori == $category]
{
_id,
heading,
"slug": slug.current,
status,
kategori,
"sidebarindex": sidebarindex,
"description": seo.meta
}`);
const GRUNNLEGGENDE_BY_SLUG_QUERY =
defineQuery(`*[_type == "ds_artikkel" && slug.current == $slug][0]
{
...,
content[]{
...,
${destructureBlocks}
},
}`);
const MONSTER_MALER_BY_SLUG_QUERY =
defineQuery(`*[_type == "templates_artikkel" && slug.current == $slug][0]
{
...,
content[]{
...,
${destructureBlocks}
},
}`);
const BLOGG_BY_SLUG_QUERY =
defineQuery(`*[_type == "aksel_blogg" && slug.current == $slug][0]
{
...,
"slug": slug.current,
content[]{
...,
${destructureBlocks}
},
${contributorsAll}
}`);
const TOC_BY_SLUG_QUERY =
defineQuery(`*[slug.current == $slug][0].content[style match 'h2'][]{
"id": _key,
"title": pt::text(@)
}`);
const METADATA_BY_SLUG_QUERY = defineQuery(`*[slug.current == $slug][0]{
heading,
ingress,
publishedAt,
seo
}`);
const SLUG_BY_TYPE_QUERY = defineQuery(`
*[_type == $type && defined(slug.current)].slug.current
`);
export {
DESIGNSYSTEM_SIDEBAR_QUERY,
DESIGNSYSTEM_OVERVIEW_PAGES_QUERY,
DESIGNSYSTEM_KOMPONENTER_LANDINGPAGE_QUERY,
DESIGNSYSTEM_GRUNNLEGGENDE_LANDINGPAGE_QUERY,
DESIGNSYSTEM_TEMPLATES_LANDINGPAGE_QUERY,
GLOBAL_SEARCH_QUERY_ALL,
KOMPONENT_BY_SLUG_QUERY,
DESIGNSYSTEM_OVERVIEW_BY_CATEGORY_QUERY,
TOC_BY_SLUG_QUERY,
SLUG_BY_TYPE_QUERY,
GRUNNLEGGENDE_BY_SLUG_QUERY,
BLOGG_BY_SLUG_QUERY,
MONSTER_MALER_BY_SLUG_QUERY,
METADATA_BY_SLUG_QUERY,
};
File diff suppressed because it is too large. Load diff
+18 -6
View File
@@ -1,14 +1,26 @@
import { PortableTextComponentProps } from "next-sanity";
import { KOMPONENT_BY_SLUG_QUERYResult } from "./query-types";
import {
BLOGG_BY_SLUG_QUERYResult,
GRUNNLEGGENDE_BY_SLUG_QUERYResult,
KOMPONENT_BY_SLUG_QUERYResult,
MONSTER_MALER_BY_SLUG_QUERYResult,
} from "./query-types";
/* TODO: Currently only handles "komponenter". Extend to cover all doctypes */
type PortableContentTypes = NonNullable<
NonNullable<KOMPONENT_BY_SLUG_QUERYResult>["content"]
>[number]["_type"];
type QueryResults =
| KOMPONENT_BY_SLUG_QUERYResult
| GRUNNLEGGENDE_BY_SLUG_QUERYResult
| MONSTER_MALER_BY_SLUG_QUERYResult
| BLOGG_BY_SLUG_QUERYResult;
type PortableContentTypes = Exclude<
| NonNullable<NonNullable<QueryResults>["content"]>[number]["_type"]
| "token_kategori",
"block" | "reference"
>;
// Generic utility type to extract a specific type from the content array
type ExtractPortableType<T extends PortableContentTypes> = Extract<
NonNullable<NonNullable<KOMPONENT_BY_SLUG_QUERYResult>["content"]>[number],
NonNullable<NonNullable<QueryResults>["content"]>[number],
{ _type: T }
>;
@@ -0,0 +1,15 @@
.attachment {
display: flex;
gap: var(--ax-space-8);
border-radius: var(--ax-border-radius-large);
padding: var(--ax-space-12) var(--ax-space-16);
border: 1px solid var(--ax-border-subtle);
background-color: var(--ax-bg-moderate);
}
.attachmentIcon {
display: grid;
flex-shrink: 0;
place-content: center;
height: 1.5rem;
}
@@ -0,0 +1,50 @@
import { PortableTextBlock } from "next-sanity";
import { useId } from "react";
import { DownloadIcon } from "@navikt/aksel-icons";
import { Heading, Link, VStack } from "@navikt/ds-react";
import { CustomPortableText } from "@/app/CustomPortableText";
import { ExtractPortableComponentProps } from "@/app/_sanity/types";
import styles from "./Attachment.module.css";
function Attachment(props: ExtractPortableComponentProps<"attachment">) {
const { title, downloadLink, fileName, size, body } = props.value;
const id = useId();
if (!title || !downloadLink || !fileName || !size) {
return null;
}
const filetype = downloadLink.split(".").at(-1);
return (
<section
data-block-margin="space-28"
aria-labelledby={id}
className={styles.attachment}
data-color-role="aksel-brand-pink"
>
<span className={styles.attachmentIcon}>
<DownloadIcon aria-hidden fontSize="1.5rem" />
</span>
<div>
<Heading size="small" level="2" id={id} aria-hidden>
{title}
</Heading>
<VStack gap="space-8">
{body && <CustomPortableText value={body as PortableTextBlock[]} />}
<Link
href={`${downloadLink}?dl=${fileName}.${filetype}`}
rel="noreferrer noopener"
download={fileName}
>
{`${fileName}.${filetype} `}
</Link>
</VStack>
</div>
</section>
);
}
export { Attachment };
@@ -0,0 +1,21 @@
"use client";
import styles from "./CompareImages.module.css";
import { useCompareImages } from "./CompareImages.provider";
function CompareImagesContainer({ children }: { children: React.ReactNode }) {
const { container } = useCompareImages();
return (
<div
ref={container.ref}
style={container.styles}
className={styles.compareImagesContainer}
onPointerDown={container.onPointerDown}
>
{children}
</div>
);
}
export { CompareImagesContainer };
@@ -0,0 +1,35 @@
"use client";
import { ChevronLeftIcon, ChevronRightIcon } from "@navikt/aksel-icons";
import styles from "./CompareImages.module.css";
import { useCompareImages } from "./CompareImages.provider";
function CompareImagesHandle() {
const { handle, dragging } = useCompareImages();
return (
<button
ref={handle.ref}
onKeyDown={handle.onKeyDown}
className={styles.compareImagesHandle}
data-dragging={dragging.current}
aria-label="Endre verdi for å sammenligne bilder"
aria-orientation="horizontal"
aria-valuemin={0}
aria-valuemax={100}
/* Valuenow and valuetext are both updates manually in useCompareImages. */
aria-valuenow={50}
aria-valuetext="50%"
data-rcs="handle-container"
role="slider"
>
<span className={styles.compareImagesHandleLine} />
<span className={styles.compareImagesHandleNotch}>
<ChevronLeftIcon aria-hidden />
<ChevronRightIcon aria-hidden />
</span>
</button>
);
}
export { CompareImagesHandle };
@@ -0,0 +1,109 @@
.compareImagesFigure {
display: flex;
flex-direction: column;
margin: 0 auto;
}
.compareImagesContainer {
position: relative;
display: grid;
max-width: fit-content;
max-height: 100%;
touch-action: pan-y;
user-select: none;
overflow: hidden;
border-radius: var(--ax-border-radius-large);
&:focus-within {
outline: 3px solid var(--ax-border-focus);
outline-offset: 3px;
}
[data-border="true"] > & {
border: 1px solid var(--ax-border-neutral-subtle);
}
}
.compareImagesImgContainer {
grid-area: 1 / 1 / 2 / 2;
will-change: clip-path;
box-sizing: border-box;
max-width: fit-content;
cursor: pointer;
user-select: none;
overflow: hidden;
background-color: var(--image-bg, transparent);
&[data-position="left"] {
/* stylelint-disable-next-line csstools/value-no-unknown-custom-properties */
clip-path: inset(0 var(--image-clip-1) 0 0);
}
&[data-position="right"] {
/* stylelint-disable-next-line csstools/value-no-unknown-custom-properties */
clip-path: inset(0 0 0 var(--image-clip-2));
}
}
.compareImagesImg {
object-position: center;
object-fit: cover;
}
.compareImagesHandle {
position: absolute;
top: 0;
z-index: 10;
height: 100%;
appearance: none;
border: none;
background: none;
padding: 0;
outline: none;
transition: opacity 0.2s ease-in-out;
transform: translate3d(-50%, 0, 0);
/* stylelint-disable-next-line csstools/value-no-unknown-custom-properties */
left: var(--image-clip-2);
&[data-dragging="true"] {
cursor: ew-resize;
opacity: 1;
}
&[data-dragging="false"] {
opacity: 0.6;
&:focus,
.compareImagesContainer:hover & {
opacity: 1;
}
}
}
.compareImagesHandleLine {
position: absolute;
top: 0;
height: 100%;
width: 2px;
background-color: var(--ax-border-neutral-strong);
}
.compareImagesHandleNotch {
pointer-events: none;
position: relative;
z-index: 10;
display: flex;
align-items: center;
border-radius: var(--ax-border-radius-full);
background-color: var(--ax-bg-neutral-strong);
font-size: 1.75rem;
color: var(--ax-text-neutral-contrast);
}
.compareImagesCaption {
margin-top: var(--ax-space-8);
padding-inline: var(--ax-space-16);
display: grid;
place-content: center;
}
@@ -0,0 +1,196 @@
"use client";
import {
CSSProperties,
createContext,
useCallback,
useContext,
useEffect,
useRef,
useState,
} from "react";
import { Color } from "@/app/_sanity/query-types";
type CompareImagesContextT = {
container: {
ref: React.RefObject<HTMLDivElement>;
onPointerDown?: (event: React.PointerEvent<HTMLDivElement>) => void;
styles?: CSSProperties;
};
handle: {
ref: React.RefObject<HTMLButtonElement>;
onKeyDown?: (event: React.KeyboardEvent<HTMLButtonElement>) => void;
};
handlePosition: number;
dragging: {
current: boolean;
update: (value: boolean) => void;
};
};
const CompareImagesContext = createContext<CompareImagesContextT | null>(null);
function CompareImagesProvider({
children,
background,
}: {
children: React.ReactNode;
background?: Color;
}) {
const containerRef = useRef<HTMLDivElement>(null);
const handleRef = useRef<HTMLButtonElement>(null);
const handlePosition = useRef(50);
const [isDragging, setIsDragging] = useState(false);
const syncPosition = () => {
if (!containerRef.current || !handleRef.current) {
return;
}
containerRef.current.style.setProperty(
"--image-clip-1",
`${100 - handlePosition.current}%`,
);
containerRef.current.style.setProperty(
"--image-clip-2",
`${handlePosition.current}%`,
);
const rounded = Math.round(handlePosition.current);
handleRef.current.ariaValueNow = rounded.toString();
handleRef.current.ariaValueText = `${rounded}%`;
};
/**
* Update the position based on the cursor's current position within container.
*/
const updateOnCursorPosition = useCallback(
(event: React.PointerEvent<HTMLDivElement> | PointerEvent) => {
if (!containerRef.current) {
return;
}
const rect = containerRef.current.getBoundingClientRect();
const elementLeft = rect.left;
const elementWidth = rect.width;
// Get X coordinate relative to viewport
const clickX = event.clientX;
// Calculate X coordinate relative to the div (0 = left edge)
const relativeX = clickX - elementLeft;
// Get percentage
const percentageX = (relativeX / elementWidth) * 100;
// Clamp between 0 and 100
const clampedPercentageX = Math.max(-1, Math.min(100, percentageX));
handlePosition.current = Number(clampedPercentageX.toFixed(2));
syncPosition();
},
[],
);
const handlePointerMove = useCallback(
(event: React.PointerEvent<HTMLDivElement> | PointerEvent) => {
if (!isDragging || !containerRef.current) {
return;
}
updateOnCursorPosition(event);
},
[isDragging, updateOnCursorPosition],
);
useEffect(() => {
const stopDragging = () => setIsDragging(false);
globalThis.addEventListener("pointermove", handlePointerMove);
globalThis.addEventListener("pointerup", stopDragging);
return () => {
globalThis.removeEventListener("pointermove", handlePointerMove);
globalThis.removeEventListener("pointerup", stopDragging);
};
}, [handlePointerMove]);
/* Container */
const handlePointerDown = (event: React.PointerEvent<HTMLDivElement>) => {
// Only handle left mouse button (touch events also use 0).
if (event.button !== 0) {
return;
}
setIsDragging(true);
updateOnCursorPosition(event);
};
const movePosition = (offset: number) => {
handlePosition.current = Math.max(
-1,
Math.min(100, handlePosition.current + offset),
);
syncPosition();
};
/* Set on CompareHandle */
const handleKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => {
event.preventDefault();
if (event.key === "ArrowLeft" || event.key === "ArrowDown") {
movePosition(-5);
} else if (event.key === "ArrowRight" || event.key === "ArrowUp") {
movePosition(5);
} else if (event.key === "Home") {
movePosition(-100);
} else if (event.key === "End") {
movePosition(100);
}
};
const appliedStyle: CSSProperties = {
"--image-clip-2": `${handlePosition.current}%`,
"--image-clip-1": `${100 - handlePosition.current}%`,
"--image-bg": background
? `rgba(${background.rgb?.r},${background.rgb?.g},${background.rgb?.b},${background.rgb?.a})`
: undefined,
};
return (
<CompareImagesContext.Provider
value={{
container: {
ref: containerRef,
styles: appliedStyle,
onPointerDown: handlePointerDown,
},
handle: {
ref: handleRef,
onKeyDown: handleKeyDown,
},
handlePosition: handlePosition.current,
dragging: {
current: isDragging,
update: setIsDragging,
},
}}
>
{children}
</CompareImagesContext.Provider>
);
}
function useCompareImages() {
const context = useContext(CompareImagesContext);
if (context === null) {
throw new Error(
"useCompareImages must be used within a CompareImagesProvider",
);
}
return context;
}
export { CompareImagesProvider, useCompareImages };
@@ -0,0 +1,71 @@
/* eslint-disable @next/next/no-img-element */
import { Image } from "sanity";
import { BodyShort } from "@navikt/ds-react";
import { ExtractPortableComponentProps } from "@/app/_sanity/types";
import { urlForImage } from "@/app/_sanity/utils";
import { CompareImagesContainer } from "./CompareImages.container";
import { CompareImagesHandle } from "./CompareImages.handle";
import styles from "./CompareImages.module.css";
import { CompareImagesProvider } from "./CompareImages.provider";
function CompareImages(props: ExtractPortableComponentProps<"compare_images">) {
const { image_1, image_2, caption, border, background } = props.value;
if (!image_1 || !image_2) {
return null;
}
const imageOneUrl = urlForImage(image_1 as Image)
?.auto("format")
.url();
const imageTwoUrl = urlForImage(image_2 as Image)
?.auto("format")
.url();
if (!imageOneUrl || !imageTwoUrl) {
return null;
}
return (
<CompareImagesProvider background={background}>
<figure
data-block-margin="space-28"
data-border={border}
className={styles.compareImagesFigure}
>
<CompareImagesContainer>
<CompareImagesHandle />
<div
className={styles.compareImagesImgContainer}
data-position="left"
>
<img
src={imageOneUrl}
alt={image_1.alt}
className={styles.compareImagesImg}
/>
</div>
<div
className={styles.compareImagesImgContainer}
data-position="right"
>
<img
src={imageTwoUrl}
alt={image_2.alt}
className={styles.compareImagesImg}
/>
</div>
</CompareImagesContainer>
{caption && (
<figcaption className={styles.compareImagesCaption}>
<BodyShort as="span" size="small">
{caption}
</BodyShort>
</figcaption>
)}
</figure>
</CompareImagesProvider>
);
}
export { CompareImages };
@@ -0,0 +1,17 @@
.consentBannerRoot {
position: relative;
z-index: 10;
background-color: var(--ax-bg-neutral-soft);
display: flex;
padding: var(--ax-space-16) var(--ax-space-20);
border-bottom: 1px solid var(--ax-border-neutral-subtle);
}
.consentBannerSection {
max-width: 1920px;
margin: 0 auto;
}
.consentBannerBrandedText {
color: var(--ax-text-brand-blue);
}
@@ -0,0 +1,19 @@
import { showConsentBanner } from "./ConsentBanner.utils";
import { ConsentBannerView } from "./ConsentBanner.view";
/**
* ConsentBanner uses next/cookies to check if the current consent-state for the user.
* @note Since we use `next/cookies`, pages/layouts using component needs `export const dynamic = "force-dynamic";`
* to ensure the component is server rendered.
*/
async function ConsentBanner() {
const showBanner = await showConsentBanner();
if (!showBanner) {
return null;
}
return <ConsentBannerView />;
}
export { ConsentBanner };
@@ -0,0 +1,121 @@
"use server";
/**
* TODO:
* - Add next-logger instead of console.log
*/
import { cookies } from "next/headers";
import { client } from "@/app/_sanity/client";
const CURRENT_VERSION = 1;
const CONSENT_TRACKER_ID = "aksel-consent";
type CONSENT_TRACKER_STATE =
| "undecided"
| "accepted"
| "rejected"
| "no_action";
type CookieData = {
createdAt: string;
updatedAt: string;
version: number;
consents: {
tracking?: CONSENT_TRACKER_STATE;
};
};
const stateToSanityKey = {
accepted: "accept",
rejected: "decline",
} as const;
async function acceptCookiesAction(): Promise<void> {
await updateCookieConsent("accepted");
}
async function rejectCookiesAction(): Promise<void> {
await updateCookieConsent("rejected");
}
async function updateCookieConsent(
newState: CONSENT_TRACKER_STATE,
): Promise<void> {
if (!validateConsentState(newState)) {
throw new Error(`Invalid state: ${newState}`);
}
const cookieStore = await cookies();
let createdAt = new Date().toISOString();
const oldCookieValue = cookieStore.get(CONSENT_TRACKER_ID)?.value;
if (oldCookieValue) {
const oldCookieData = JSON.parse(oldCookieValue) as CookieData;
createdAt = oldCookieData.createdAt;
}
const cookieData: CookieData = {
createdAt,
updatedAt: new Date().toISOString(),
version: CURRENT_VERSION,
consents: {},
};
cookieData.consents.tracking = newState;
const cookieJson = JSON.stringify(cookieData);
cookieStore.set(CONSENT_TRACKER_ID, cookieJson, {
expires: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000),
});
if (["accepted", "rejected"].includes(newState)) {
await client
.config({
token: process.env.SANITY_WRITE,
})
.patch("cookie_tracker")
.inc({ total: 1, [stateToSanityKey[newState]]: 1 })
.commit()
.catch(() => console.warn("Cookie tracker document patch failed"));
}
}
async function getCookieConsent(): Promise<CONSENT_TRACKER_STATE> {
const cookieStore = await cookies();
const rawState = cookieStore.get(CONSENT_TRACKER_ID)?.value;
if (!rawState) {
return "undecided";
}
const cookieData = JSON.parse(rawState) as CookieData;
return cookieData.consents.tracking ?? "undecided";
}
/**
* Helper utility to check if the consent banner should be shown.
*/
async function showConsentBanner(): Promise<boolean> {
try {
const consent = await getCookieConsent();
return !["accepted", "rejected"].includes(consent);
} catch (error) {
console.error("Error getting cookie consent:", error);
return false;
}
}
function validateConsentState(state: string): state is CONSENT_TRACKER_STATE {
return ["accepted", "rejected", "undecided", "no_action"].includes(state);
}
export {
getCookieConsent,
showConsentBanner,
acceptCookiesAction,
rejectCookiesAction,
};
@@ -0,0 +1,104 @@
"use client";
import NextLink from "next/link";
import { useTransition } from "react";
import { BodyLong, Box, Button, Heading, Link, Stack } from "@navikt/ds-react";
import { WebsiteList, WebsiteListItem } from "@/app/_ui/typography/WebsiteList";
import styles from "./ConsentBanner.module.css";
import {
acceptCookiesAction,
rejectCookiesAction,
} from "./ConsentBanner.utils";
function ConsentBannerView() {
const [isPending, startTransition] = useTransition();
if (isPending) {
return null;
}
return (
<div className={styles.consentBannerRoot}>
<section
aria-labelledby="cookie_heading"
className={styles.consentBannerSection}
>
<Stack
as="div"
gap={{ xs: "4", lg: "8" }}
wrap={false}
align={{ xs: "start", lg: "center" }}
direction={{ xs: "column", lg: "row" }}
>
<div>
<Heading
id="cookie_heading"
size="medium"
level="2"
spacing
className={styles.consentBannerBrandedText}
>
Informasjonskapsler på aksel.nav.no
</Heading>
<BodyLong>
Uansett valg deler vi aldri dine data med andre.{" "}
<Link as={NextLink} href="/personvernerklaering">
Mer om informasjonskapsler på aksel.nav.no
</Link>
</BodyLong>
<Box marginBlock="space-8">
<WebsiteList as="ul">
<WebsiteListItem icon>
<strong className={styles.consentBannerBrandedText}>
Godta alle:
</strong>{" "}
Hjelper oss gjøre tjenestene bedre for deg basert på
anonymisert bruk.
</WebsiteListItem>
<WebsiteListItem icon>
<strong className={styles.consentBannerBrandedText}>
Bare nødvendige:
</strong>{" "}
Sikrer at tjenesten fungerer og er trygg. Kan ikke velges
bort.
</WebsiteListItem>
</WebsiteList>
</Box>
</div>
<Stack
gap="2"
align="start"
minWidth="fit-content"
direction={{ xs: "row", lg: "column" }}
>
<Button
type="button"
variant="secondary-neutral"
onClick={() => {
startTransition(() => {
rejectCookiesAction();
});
}}
>
Bare nødvendige
</Button>
<Button
type="button"
variant="secondary-neutral"
onClick={() => {
startTransition(() => {
acceptCookiesAction();
});
}}
>
Godkjenn alle
</Button>
</Stack>
</Stack>
</section>
</div>
);
}
export { ConsentBannerView };
@@ -0,0 +1,11 @@
.exampleText {
border-radius: var(--ax-border-radius-large);
padding: var(--ax-space-12) var(--ax-space-16);
border: 1px solid var(--ax-border-neutral-subtle);
background-color: var(--ax-bg-neutral-moderate);
}
.exampleTextDivider {
margin-block: var(--ax-space-12);
border-top: 1px solid var(--ax-border-neutral-subtleA);
}
@@ -0,0 +1,50 @@
import { useId } from "react";
import {
BodyLong,
CopyButton,
HStack,
Heading,
VStack,
} from "@navikt/ds-react";
import { ExtractPortableComponentProps } from "@/app/_sanity/types";
import styles from "./ExampleText.module.css";
function ExampleText(
props: ExtractPortableComponentProps<"exampletext_block">,
) {
const { title, text } = props.value;
const id = useId();
if (!text || !title) {
return null;
}
return (
<section
aria-labelledby={id}
className={styles.exampleText}
data-block-margin="space-28"
>
<HStack justify="space-between" gap="4" align="center" wrap={false}>
<Heading size="small" as="p" textColor="subtle" id={id} aria-hidden>
{title}
</Heading>
<CopyButton copyText={text} size="small" />
</HStack>
<hr aria-hidden className={styles.exampleTextDivider} />
<VStack gap="space-24">{formatText(text)}</VStack>
</section>
);
}
function formatText(text: string) {
if (!text) return null;
return text
.split("\n")
.filter(Boolean)
.map((line) => <BodyLong key={line}>{line}</BodyLong>);
}
export { ExampleText };
@@ -11,7 +11,7 @@
margin-inline: auto;
display: grid;
gap: var(--ax-space-24);
grid-template-columns: repeat(auto-fit, minmax(250px, auto));
grid-template-columns: 1fr 1fr 1fr 1fr;
}
.footerLogo {
+69 -64
View File
@@ -1,5 +1,8 @@
import Link from "next/link";
import { Heading } from "@navikt/ds-react";
import { HGrid, Heading } from "@navikt/ds-react";
/* @ts-expect-error Workspace cant resolve valid import */
import { PageBlock } from "@navikt/ds-react/Page";
import { FigmaIcon, GithubIcon, SlackIcon } from "@/assets/Icons";
import AkselLogo from "@/assets/Logo";
import styles from "./Footer.module.css";
@@ -7,70 +10,72 @@ import styles from "./Footer.module.css";
const Footer = () => {
return (
<footer className={`${styles.footer} dark`} id="aksel-footer">
<div className={styles.footerLayout}>
<div className={styles.footerLogo}>
<AkselLogo />
<div>
<p>&copy; {new Date().getFullYear()} Nav</p>
<p>Arbeids- og velferdsetaten</p>
<HGrid gap="space-24" asChild columns={{ xs: 1, md: 2, lg: 4 }}>
<PageBlock width="2xl">
<div className={styles.footerLogo}>
<AkselLogo />
<div>
<p>&copy; {new Date().getFullYear()} Nav</p>
<p>Arbeids- og velferdsetaten</p>
</div>
</div>
</div>
<LinkBlock
heading="Snarveier"
links={[
{
url: "/side/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.ansatt.nav.no/",
text: "Etterlevelse",
},
]}
/>
<LinkBlock
heading="Om nettstedet"
links={[
{
url: "/side/om-aksel",
text: "Hva er Aksel?",
},
{
url: "/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 />,
},
]}
/>
</div>
<LinkBlock
heading="Snarveier"
links={[
{
url: "/side/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.ansatt.nav.no/",
text: "Etterlevelse",
},
]}
/>
<LinkBlock
heading="Om nettstedet"
links={[
{
url: "/side/om-aksel",
text: "Hva er Aksel?",
},
{
url: "/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 />,
},
]}
/>
</PageBlock>
</HGrid>
</footer>
);
};
@@ -4,9 +4,9 @@ import Image from "next/image";
import Link from "next/link";
import React from "react";
import { Heading } from "@navikt/ds-react";
import { doctypeToColorRole } from "@/app/_ui/theming/theme-config";
import { urlFor } from "@/sanity/interface";
import { StatusTag } from "@/web/StatusTag";
import { doctypeToColorRole } from "../theme-config";
import { SearchHitT, SearchResultPageTypesT } from "./GlobalSearch.config";
import styles from "./GlobalSearch.module.css";
@@ -81,55 +81,76 @@ function GlobalSearchEmptySearchState() {
xmlns="http://www.w3.org/2000/svg"
aria-hidden
>
<rect width="200" height="200" fill="white" />
<path
d="M36.8896 98.813L33.6202 104.346C33.0894 105.244 33.0894 106.36 33.6202 107.258L36.8896 112.791C37.4046 113.663 38.3416 114.197 39.3538 114.197H46.2357C47.2593 114.197 48.2049 113.651 48.7159 112.764L51.9035 107.231C52.4131 106.347 52.4131 105.258 51.9035 104.373L48.7159 98.8403C48.2049 97.9534 47.2593 97.4069 46.2357 97.4069H39.3538C38.3416 97.4069 37.4046 97.9415 36.8896 98.813Z"
fill="#D7E6F0"
d="M34.7626 108.638L31.4932 114.171C30.9625 115.069 30.9625 116.185 31.4932 117.083L34.7626 122.616C35.2776 123.488 36.2146 124.022 37.2269 124.022H44.1088C45.1323 124.022 46.078 123.476 46.5889 122.589L49.7766 117.056C50.2861 116.172 50.2861 115.083 49.7766 114.198L46.5889 108.665C46.078 107.778 45.1323 107.232 44.1088 107.232H37.2269C36.2146 107.232 35.2776 107.767 34.7626 108.638Z"
fill="var(--ax-bg-brand-blue-moderate-pressed)"
/>
<circle cx="111.98" cy="29.7801" r="6.56944" fill="#D7E6F0" />
<circle
cx="68.6985"
cy="56.4138"
r="32.9387"
stroke="#417DA0"
strokeWidth="6"
cx="99.0343"
cy="29.5465"
r="6.56944"
fill="var(--ax-bg-brand-blue-moderate-pressed)"
/>
<path
d="M82.9395 86.0389L105.044 135.401"
stroke="#417DA0"
strokeWidth="6"
fillRule="evenodd"
clipRule="evenodd"
d="M70.9989 95.9112C86.9702 91.6317 96.4482 75.2152 92.1687 59.2439C87.8892 43.2726 71.4727 33.7945 55.5014 38.074C39.5301 42.3535 30.0521 58.77 34.3316 74.7413C38.6111 90.7126 55.0276 100.191 70.9989 95.9112ZM72.5518 101.707C91.7239 96.5696 103.101 76.8631 97.9643 57.691C92.8271 38.5189 73.1206 27.1413 53.9485 32.2785C34.7764 37.4156 23.3989 57.1221 28.536 76.2942C33.6731 95.4663 53.3797 106.844 72.5518 101.707Z"
fill="var(--ax-bg-brand-blue-strong)"
/>
<path
d="M152.2 89.3289C153.037 88.6692 154.277 89.1075 154.514 90.1473L157.259 102.201C157.513 103.316 156.437 104.274 155.359 103.892L142.901 99.4864C141.822 99.105 141.588 97.6843 142.487 96.9767L152.2 89.3289Z"
fill="#D7E6F0"
fillRule="evenodd"
clipRule="evenodd"
d="M116.475 135.774L82.3477 93.8154L87.0024 90.0295L121.129 131.989L116.475 135.774Z"
fill="var(--ax-bg-brand-blue-strong)"
/>
<path
d="M137.588 51.1529C138.507 50.2337 139.997 50.2337 140.916 51.1529L147.471 57.7075C148.39 58.6267 148.39 60.1171 147.471 61.0363L140.916 67.5909C139.997 68.5101 138.507 68.5101 137.588 67.5909L131.033 61.0363C130.114 60.1171 130.114 58.6267 131.033 57.7075L137.588 51.1529Z"
fill="#D7E6F0"
/>
<ellipse
cx="100"
cy="154.406"
rx="71.8774"
ry="25.1185"
fill="#417DA0"
d="M168.895 48.3449C169.733 47.6852 170.972 48.1235 171.209 49.1633L173.954 61.217C174.208 62.3323 173.133 63.2897 172.054 62.9083L159.596 58.5024C158.518 58.121 158.283 56.7003 159.182 55.9927L168.895 48.3449Z"
fill="var(--ax-bg-brand-blue-moderate-pressed)"
/>
<path
d="M99.4819 149.73L107.052 176.809C107.06 176.84 107.088 176.863 107.121 176.865C114.966 177.401 122.847 176.745 130.496 174.919L130.795 174.848C130.833 174.839 130.854 174.798 130.839 174.762L124.026 158.612C121.323 152.204 121.484 144.947 124.467 138.665L124.895 137.764C126.37 134.656 127.04 131.228 126.842 127.794L125.767 109.138C125.642 106.966 123.664 105.381 121.517 105.732C119.983 105.983 118.458 105.239 117.711 103.875L117.093 102.746C116.307 101.311 114.59 100.666 113.053 101.229C111.776 101.696 110.343 101.335 109.44 100.318L108.582 99.3511C107.635 98.2838 106.043 98.085 104.862 98.8867C103.665 99.6995 102.049 99.4827 101.108 98.3833L100.284 97.4196C99.2512 96.2125 97.6171 95.7057 96.0827 96.1166C94.2793 96.5996 93.0253 98.2337 93.0253 100.101V116.757C93.0253 118.743 91.9525 120.574 90.2201 121.545C87.531 123.052 86.6102 126.478 88.1812 129.13L97.1533 144.278C98.1647 145.985 98.9476 147.818 99.4819 149.73Z"
fill="#D7E6F0"
d="M121.718 67.3612C122.638 66.4419 124.128 66.442 125.047 67.3612L131.602 73.9158C132.521 74.835 132.521 76.3253 131.602 77.2445L125.047 83.7991C124.128 84.7183 122.638 84.7183 121.718 83.7991L115.164 77.2445C114.245 76.3253 114.245 74.835 115.164 73.9158L121.718 67.3612Z"
fill="var(--ax-bg-brand-blue-moderate-pressed)"
/>
<path
d="M61.248 48.5068V48.2486C61.248 45.2002 63.394 42.5731 66.3811 41.9648L66.7028 41.8993C68.733 41.4858 70.8304 42.1791 72.2142 43.7211V43.7211C74.1732 45.9039 74.2091 49.2012 72.2981 51.4262L70.5619 53.4477C70.2535 53.8067 69.9744 54.1898 69.7271 54.5933L69.4185 55.0969C67.905 57.5669 67.3567 60.5088 67.8786 63.3582V63.3582"
stroke="#417DA0"
strokeWidth="4"
strokeLinecap="round"
d="M174.266 163.502L102.266 177.022L30.2656 163.502L38.2656 137.543L102.266 124.022L166.266 137.543L174.266 163.502Z"
fill="url(#paint0_linear_271_5972)"
/>
<circle cx="68.5972" cy="70.0965" r="2.60311" fill="#417DA0" />
<path
d="M168.787 154.406C168.787 168.279 139.698 176.433 100.001 176.433C60.304 176.433 31.2148 168.279 31.2148 154.406"
stroke="#417DA0"
strokeWidth="6.18301"
d="M101.876 142.252L109.446 169.331C109.455 169.363 109.483 169.386 109.516 169.388C117.361 169.924 125.242 169.268 132.89 167.442L133.19 167.371C133.228 167.362 133.249 167.32 133.234 167.284L126.421 151.134C123.718 144.727 123.878 137.47 126.861 131.188L127.289 130.286C128.765 127.179 129.434 123.75 129.237 120.316L128.162 101.661C128.037 99.4891 126.058 97.9037 123.912 98.2547C122.377 98.5055 120.852 97.7613 120.106 96.3979L119.488 95.2692C118.701 93.8337 116.984 93.1885 115.447 93.7513C114.17 94.219 112.737 93.8577 111.835 92.8406L110.977 91.8738C110.029 90.8065 108.437 90.6078 107.257 91.4094C106.06 92.2222 104.443 92.0055 103.503 90.906L102.678 89.9423C101.646 88.7352 100.012 88.2284 98.4772 88.6393C96.6739 89.1223 95.4198 90.7564 95.4198 92.6233V109.28C95.4198 111.266 94.347 113.097 92.6146 114.068C89.9256 115.575 89.0047 119.001 90.5757 121.653L99.5479 136.801C100.559 138.508 101.342 140.341 101.876 142.252Z"
fill="var(--ax-bg-brand-blue-moderate-hover)"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M65.3839 55.6342C64.474 54.6203 63.095 54.1644 61.7601 54.4363L61.4384 54.5018C59.383 54.9204 57.9062 56.7282 57.9062 58.8258V59.0841C57.9062 60.1886 57.0108 61.0841 55.9062 61.0841C54.8017 61.0841 53.9062 60.1886 53.9062 59.0841V58.8258C53.9062 54.8267 56.7215 51.3803 60.6402 50.5823L60.9619 50.5167C63.6875 49.9617 66.5031 50.8924 68.3609 52.9625C70.9908 55.893 71.039 60.3195 68.4735 63.3065L66.7373 65.328C66.4984 65.6061 66.2822 65.9029 66.0907 66.2154L65.7821 66.7191C64.526 68.7689 64.0709 71.2104 64.5041 73.5751C64.7031 74.6616 63.9836 75.7037 62.8971 75.9027C61.8106 76.1017 60.7685 75.3823 60.5695 74.2958C59.9588 70.9616 60.6005 67.5194 62.3714 64.6292L62.68 64.1256C62.983 63.6311 63.325 63.1617 63.7029 62.7218L65.4391 60.7003C66.6956 59.2374 66.672 57.0694 65.3839 55.6342Z"
fill="var(--ax-bg-brand-blue-strong)"
/>
<path
d="M65.8586 80.6739C65.8586 82.1116 64.6931 83.277 63.2554 83.277C61.8178 83.277 60.6523 82.1116 60.6523 80.6739C60.6523 79.2363 61.8178 78.0708 63.2554 78.0708C64.6931 78.0708 65.8586 79.2363 65.8586 80.6739Z"
fill="var(--ax-bg-brand-blue-strong)"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M33.4414 163.287L34.5932 157.212L102.635 170.114L170.676 157.212L171.828 163.287L102.635 176.407L33.4414 163.287Z"
fill="var(--ax-bg-brand-blue-strong-hover)"
/>
<defs>
<linearGradient
id="paint0_linear_271_5972"
x1="112"
y1="124"
x2="102.266"
y2="177.022"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="var(--ax-bg-brand-blue-strong)" />
<stop
offset="1"
stopColor="var(--ax-bg-brand-blue-strong-pressed)"
/>
</linearGradient>
</defs>
</svg>
</div>
);
@@ -155,66 +176,99 @@ function GlobalSearchEmptyState() {
xmlns="http://www.w3.org/2000/svg"
aria-hidden
>
<rect width="200" height="200" fill="white" />
<path
d="M46.8963 30.8959L28.9467 61.6545C28.4969 62.4253 28.4969 63.3787 28.9467 64.1495L46.8963 94.9081C47.3399 95.6683 48.1538 96.1357 49.034 96.1357H85.9307C86.8206 96.1357 87.6419 95.658 88.082 94.8846L105.583 64.126C106.015 63.3671 106.015 62.4369 105.583 61.678L88.082 30.9194C87.6419 30.146 86.8206 29.6683 85.9307 29.6683H49.034C48.1538 29.6683 47.3399 30.1357 46.8963 30.8959Z"
fill="#D7E6F0"
/>
<path
d="M112.59 68.7668L94.6401 99.5254C94.1903 100.296 94.1903 101.25 94.6401 102.02L112.59 132.779C113.033 133.539 113.847 134.007 114.727 134.007H151.624C152.514 134.007 153.335 133.529 153.775 132.755L171.276 101.997C171.708 101.238 171.708 100.308 171.276 99.5489L153.775 68.7903C153.335 68.0169 152.514 67.5392 151.624 67.5392H114.727C113.847 67.5392 113.033 68.0067 112.59 68.7668Z"
fill="#D7E6F0"
/>
<path
d="M46.8963 105.092L28.9467 135.851C28.4969 136.621 28.4969 137.575 28.9467 138.346L46.8963 169.104C47.3399 169.864 48.1538 170.332 49.034 170.332H85.9307C86.8206 170.332 87.6419 169.854 88.082 169.081L105.583 138.322C106.015 137.563 106.015 136.633 105.583 135.874L88.082 105.116C87.6419 104.342 86.8206 103.864 85.9307 103.864H49.034C48.1538 103.864 47.3399 104.332 46.8963 105.092Z"
fill="#D7E6F0"
/>
<circle
cx="89.7162"
cy="97.1842"
cx="92.0352"
cy="85.9491"
r="40.622"
transform="rotate(-45 89.7162 97.1842)"
stroke="#417DA0"
transform="rotate(45 92.0352 85.9491)"
stroke="var(--ax-bg-brand-blue-strong)"
strokeWidth="6"
/>
<path
d="M120.227 123L141.014 143.787"
stroke="#417DA0"
d="M66.2188 116.459L45.4316 137.246"
stroke="var(--ax-bg-brand-blue-strong)"
strokeWidth="6"
/>
<path
d="M141.014 143.787L161.801 164.574"
stroke="#417DA0"
d="M45.4336 137.247L24.6465 158.034"
stroke="var(--ax-bg-brand-blue-strong)"
strokeWidth="14"
strokeLinecap="round"
/>
<circle
cx="116.23"
cy="137.484"
r="38.2574"
fill="var(--ax-bg-brand-blue-moderate-hover)"
/>
<path
d="M124.264 47.8309L135.084 32.3734"
stroke="#417DA0"
d="M99.2726 127.081L111.364 141.461C111.833 142.018 112.09 142.723 112.09 143.451V157.908C112.09 159.616 113.474 161 115.181 161H117.543C119.251 161 120.635 159.616 120.635 157.909V143.465C120.635 142.729 120.897 142.016 121.376 141.457L133.643 127.1C135.357 125.093 133.931 122 131.292 122H101.639C99.0112 122 97.5815 125.07 99.2726 127.081Z"
fill="var(--ax-bg-brand-blue-strong)"
/>
<path
d="M131.301 39.7158L142.121 24.2583"
stroke="var(--ax-bg-brand-blue-strong)"
strokeWidth="6"
strokeLinecap="round"
/>
<path
d="M136.629 56.3325L168.317 33.9191"
stroke="#417DA0"
d="M143.668 48.2176L175.356 25.8042"
stroke="var(--ax-bg-brand-blue-strong)"
strokeWidth="6"
strokeLinecap="round"
/>
<path
d="M142.039 71.0172L165.998 64.8342"
stroke="#417DA0"
d="M149.078 62.9022L173.037 56.7192"
stroke="var(--ax-bg-brand-blue-strong)"
strokeWidth="6"
strokeLinecap="round"
/>
<path
d="M66.4668 84.2435L79.9321 90.9761C81.0714 91.5458 81.0714 93.1716 79.9321 93.7412L66.4668 100.474"
stroke="#417DA0"
strokeWidth="4"
d="M42.1016 29.3691V36.6061"
stroke="var(--ax-bg-brand-blue-strong)"
strokeWidth="2"
strokeLinecap="round"
/>
<path
d="M112.84 101.247L99.3746 94.5141C98.2353 93.9444 98.2353 92.3186 99.3746 91.7489L112.84 85.0163"
stroke="#417DA0"
strokeWidth="4"
d="M42.1016 44.5015V51.7384"
stroke="var(--ax-bg-brand-blue-strong)"
strokeWidth="2"
strokeLinecap="round"
/>
<path
d="M52.6289 40.5532L45.392 40.5532"
stroke="var(--ax-bg-brand-blue-strong)"
strokeWidth="2"
strokeLinecap="round"
/>
<path
d="M38.1523 40.5532L30.9154 40.5532"
stroke="var(--ax-bg-brand-blue-strong)"
strokeWidth="2"
strokeLinecap="round"
/>
<path
d="M172.719 99.7012V106.938"
stroke="var(--ax-bg-brand-blue-strong)"
strokeWidth="2"
strokeLinecap="round"
/>
<path
d="M172.719 114.833V122.07"
stroke="var(--ax-bg-brand-blue-strong)"
strokeWidth="2"
strokeLinecap="round"
/>
<path
d="M183.246 110.885L176.009 110.885"
stroke="var(--ax-bg-brand-blue-strong)"
strokeWidth="2"
strokeLinecap="round"
/>
<path
d="M168.77 110.885L161.533 110.885"
stroke="var(--ax-bg-brand-blue-strong)"
strokeWidth="2"
strokeLinecap="round"
/>
</svg>
@@ -10,13 +10,31 @@ type HeaderLinkProps = {
};
function HeaderLink({ name, href }: HeaderLinkProps) {
const pathname = usePathname();
let pathname = usePathname();
if (pathname?.startsWith("/dev")) {
pathname = pathname.replace("/dev", "");
}
const isActive = () => {
if (pathname?.startsWith(href)) {
return true;
}
if (name.toLocaleLowerCase() === "designsystemet") {
return !!["/komponenter", "/grunnleggende", "/monster-maler"].find(
(path) => pathname?.startsWith(path),
);
}
return false;
};
return (
<Link
href={href}
prefetch={false}
data-current={pathname?.startsWith(href) ? "true" : "false"}
data-current={isActive()}
className={styles.headerLink}
data-umami-event="navigere"
data-umami-event-kilde="header"
@@ -12,7 +12,7 @@
display: flex;
align-items: center;
padding-inline: 0 var(--ax-space-16);
max-width: 1440px;
max-width: 1920px;
margin-inline: auto;
@media (min-width: 1024px) {
@@ -46,13 +46,12 @@
border-radius: var(--ax-border-radius-small);
padding-inline: var(--ax-space-8);
color: var(--aksel-brand-logo);
font-size: var(--ax-font-size-large);
&::before {
content: "";
position: absolute;
/* TODO: revisit with designer */
bottom: var(--ax-space-6);
bottom: var(--ax-space-4);
z-index: 10;
height: 3px;
width: calc(100% - var(--ax-space-16));
@@ -68,8 +67,10 @@
}
}
&:hover[data-current="false"]::before {
background-color: var(--ax-border-neutral-subtleA);
&[data-current="false"]:hover {
&::before {
background-color: var(--ax-border-neutral-subtleA);
}
}
&:focus-visible {
@@ -2,6 +2,7 @@ import Link from "next/link";
import { Box, HStack, Show, Spacer } from "@navikt/ds-react";
import { GlobalSearch } from "@/app/_ui/global-search/GlobalSearch";
import { MobileNav } from "@/app/_ui/mobile-nav/MobileNav";
import { ThemeButton } from "@/app/_ui/theming/Theme.button";
import AkselLogo from "@/assets/Logo";
import { HeaderLink } from "./Header.link";
import styles from "./Header.module.css";
@@ -31,7 +32,7 @@ function Header() {
</Link>
<Spacer />
<Show above="md" asChild>
<Show above="lg" asChild>
<Box
as="nav"
paddingInline={{ xs: "0 space-8", lg: "0 space-32" }}
@@ -48,9 +49,10 @@ function Header() {
</Show>
<HStack align="center" gap="2">
<GlobalSearch />
<Show below="md">
<Show below="lg">
<MobileNav />
</Show>
<ThemeButton />
</HStack>
</div>
</header>
@@ -0,0 +1,20 @@
import { ExtractPortableComponentProps } from "@/app/_sanity/types";
import { TokenView } from "./token-view/TokenView";
function LegacyTokenModule(
props: ExtractPortableComponentProps<"spesial_seksjon">,
) {
const { modul, token } = props.value;
if (!modul || !token || modul !== "token_kategori") {
return null;
}
return (
<div data-block-margin="space-28">
<TokenView token={token} />
</div>
);
}
export { LegacyTokenModule };
@@ -0,0 +1,76 @@
.legacyTokenViewGrid {
margin-block-start: var(--ax-space-16);
display: grid;
&[data-stacked="true"] {
gap: var(--ax-space-32);
}
&[data-stacked="false"] {
gap: var(--ax-space-24);
grid-template-columns: repeat(auto-fit, minmax(16rem /* 256px */, 1fr));
@media (min-width: 1280px) {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
}
.legacyTokenViewGlobalBlock {
z-index: 0;
display: flex;
width: fit-content;
align-items: center;
}
.legacyTokenViewBlockOuter {
margin-inline-end: var(--ax-space-12);
display: grid;
height: 4rem;
width: 4rem;
place-content: center;
border-radius: var(--ax-border-radius-large);
}
.legacyTokenViewBlockInner {
height: 4rem;
width: 4rem;
z-index: -1;
}
.legacyTokenViewDt {
display: inline-flex;
align-items: flex-start;
gap: var(--ax-space-4);
font-size: var(--ax-font-size-medium);
}
.legacyTokenViewName {
display: flex;
min-height: 2rem;
align-items: center;
}
.legacyTokenViewCheckeredBg {
background-color: #fff;
background-image: repeating-linear-gradient(
45deg,
rgb(241 241 241/ 0.8) 25%,
transparent 25%,
transparent 75%,
rgb(241 241 241/ 0.8) 75%,
rgb(241 241 241/ 0.8)
),
repeating-linear-gradient(
45deg,
rgb(241 241 241/ 0.8) 25%,
#fff 25%,
#fff 75%,
rgb(241 241 241/ 0.8) 75%,
rgb(241 241 241/ 0.8)
);
background-position:
0 0,
10px 10px;
background-size: 20px 20px;
}
@@ -0,0 +1,44 @@
import { ExtractPortableComponentProps } from "@/app/_sanity/types";
import { BreakpointsView } from "./parts/categories/Breakpoints";
import { FontView } from "./parts/categories/Font";
import { GlobalView } from "./parts/categories/Global";
import { SemanticView } from "./parts/categories/Semantic";
import { ShadowView } from "./parts/categories/Shadow";
import { ShapesView } from "./parts/categories/Shapes";
import { SpacingView } from "./parts/categories/Spacing";
import { ZindexView } from "./parts/categories/Zindex";
function TokenView({
token,
}: {
token: NonNullable<
ExtractPortableComponentProps<"spesial_seksjon">["value"]
>["token"];
}) {
if (!token || !token.title) {
return null;
}
switch (true) {
case token.title === "font":
return <FontView cat={token.title} />;
case token.title.startsWith("global"):
return <GlobalView cat={token.title} />;
case token.title.startsWith("semantic"):
return <SemanticView cat={token.title} />;
case token.title === "radius":
return <ShapesView cat={token.title} />;
case token.title === "shadow":
return <ShadowView cat={token.title} />;
case token.title === "spacing":
return <SpacingView cat={token.title} />;
case token.title === "z-index":
return <ZindexView cat={token.title} />;
case token.title === "breakpoints":
return <BreakpointsView cat={token.title} />;
default:
return null;
}
}
export { TokenView };
@@ -0,0 +1,14 @@
import React from "react";
import styles from "../TokenView.module.css";
export const Grid = ({
children,
stacked = false,
}: {
children: React.ReactNode;
stacked?: boolean;
}) => (
<div data-stacked={stacked} className={styles.legacyTokenViewGrid}>
{children}
</div>
);
@@ -0,0 +1,66 @@
import {
LaptopIcon,
MobileIcon,
MobileSmallIcon,
MonitorIcon,
TabletIcon,
} from "@navikt/aksel-icons";
import docs from "@navikt/ds-tokens/docs.json";
import {
WebsiteTable,
WebsiteTableRow,
} from "@/app/_ui/website-table/WebsiteTable";
export const BreakpointsView = ({ cat }: { cat: string }) => {
const breakpoints = docs[cat];
return (
<WebsiteTable
withCopy
th={[
{ text: "Enhet" },
{ text: "Verdi" },
{ text: "Media queries", hideOnSm: true },
{ text: "Bruk" },
]}
>
{breakpoints.map((x) => (
<WebsiteTableRow
key={x.name}
tr={[
{ text: getSvg(x.name) },
{ text: x.value },
{ text: x.description, hideOnSm: true },
{
text:
Number(x.value.replace("px", "")) % 2 === 0
? "Mobile first"
: "Desktop first",
},
]}
copyText={x.name}
/>
))}
</WebsiteTable>
);
};
function getSvg(token: string) {
switch (true) {
case token.includes("xs"):
return <MobileSmallIcon title="Mobile small" fontSize="2.5rem" />;
case token.includes("sm"):
return <MobileIcon title="Mobile regular" fontSize="2.5rem" />;
case token.includes("md"):
return <TabletIcon title="Tablet" fontSize="2.5rem" />;
case token.includes("lg"):
return <LaptopIcon title="Laptop" fontSize="2.5rem" />;
case token.includes("xl"):
return <MonitorIcon title="Desktop" fontSize="2.5rem" />;
default:
return null;
}
}
@@ -0,0 +1,62 @@
import docs from "@navikt/ds-tokens/docs.json";
import {
WebsiteTable,
WebsiteTableRow,
} from "@/app/_ui/website-table/WebsiteTable";
import { sanitizeName } from "../utilities";
export const FontView = ({ cat }: { cat: string }) => {
const fonts = docs[cat];
return (
<WebsiteTable
withCopy
th={[
{ text: "Token" },
{ text: "Verdi" },
{ text: "Demo", sronly: true },
]}
>
{fonts.map((x) => (
<WebsiteTableRow
key={x.name}
tr={[
{ text: sanitizeName(x.name.replace("--a-font-", "")) },
{ text: x.value },
{ text: getDemoCell(x) },
]}
copyText={x.name}
/>
))}
</WebsiteTable>
);
};
function getDemoCell({ value, name }: { value: string; name: string }) {
switch (true) {
case name.includes("weight"):
return <span style={{ fontWeight: value }}>Foreldrepenger</span>;
case name.includes("line-height"):
return (
<span style={{ lineHeight: value }}>
Hello <br />
World
</span>
);
case name.includes("size-heading"):
return (
<span
className="leading-none"
style={{ fontSize: value, fontWeight: value }}
>
Aa
</span>
);
case name.includes("size"):
return <span style={{ fontSize: value }}>Aa</span>;
default:
return <span style={{ fontSize: value }}>Foreldrepenger</span>;
}
}
@@ -0,0 +1,50 @@
import { BodyShort, CopyButton } from "@navikt/ds-react";
import docs from "@navikt/ds-tokens/docs.json";
import styles from "../../TokenView.module.css";
import { Grid } from "../Grid";
import { getColorString, sanitizeName } from "../utilities";
export const GlobalView = ({ cat }: { cat: string }) => {
const colors: { name: string; value: string }[] = docs[cat];
return (
<Grid>
{colors.map((x) => {
return (
<div
key={x.name}
id={x.name}
className={styles.legacyTokenViewGlobalBlock}
>
<div
style={{
background: x.value,
boxShadow: `inset 0 2px 4px 0 rgba(0,0,0,0.06)`,
}}
className={styles.legacyTokenViewBlockOuter}
>
<div
className={`${styles.legacyTokenViewBlockInner} ${styles.legacyTokenViewCheckeredBg}`}
/>
</div>
<dl>
<dt className={styles.legacyTokenViewDt}>
<span className={styles.legacyTokenViewName}>
{sanitizeName(x.name)}
</span>
<CopyButton
copyText={x.name}
title={`${sanitizeName(x.name)} kopier`}
size="small"
/>
</dt>
<BodyShort as="dd" textColor="subtle" size="small">
{getColorString(x.value)}
</BodyShort>
</dl>
</div>
);
})}
</Grid>
);
};
@@ -0,0 +1,52 @@
import { BodyShort, CopyButton } from "@navikt/ds-react";
import docs from "@navikt/ds-tokens/docs.json";
import styles from "../../TokenView.module.css";
import { Grid } from "../Grid";
import { getColorString, sanitizeName } from "../utilities";
export const SemanticView = ({ cat }: { cat: string }) => {
const colors = docs[cat];
return (
<Grid>
{colors.map((x) => {
return (
<div
key={x.name}
id={x.name}
className={styles.legacyTokenViewGlobalBlock}
>
<div
style={{
background: x.value,
boxShadow: `inset 0 2px 4px 0 rgba(0,0,0,0.06)`,
border: "1px solid var(--ax-border-neutral-subtle)",
}}
className={styles.legacyTokenViewBlockOuter}
>
<div
className={`${styles.legacyTokenViewBlockInner} ${styles.legacyTokenViewCheckeredBg}`}
/>
</div>
<dl>
<dt className={styles.legacyTokenViewDt}>
<span className={styles.legacyTokenViewName}>
{sanitizeName(x.name)}
</span>
<CopyButton
copyText={x.name}
title={`${sanitizeName(x.name)} kopier`}
size="small"
/>
</dt>
<BodyShort as="dd" textColor="subtle" size="small">
{getColorString(x.value)}
</BodyShort>
</dl>
</div>
);
})}
</Grid>
);
};
@@ -0,0 +1,25 @@
import docs from "@navikt/ds-tokens/docs.json";
import {
WebsiteTable,
WebsiteTableRow,
} from "@/app/_ui/website-table/WebsiteTable";
import { sanitizeName } from "../utilities";
export const ShadowView = ({ cat }: { cat: string }) => {
const shadows = docs[cat];
return (
<WebsiteTable withCopy th={[{ text: "Enhet" }, { text: "Verdi" }]}>
{shadows.map((token) => (
<WebsiteTableRow
key={token.name}
tr={[
{ text: sanitizeName(token.name.replace("shadow-", "")) },
{ text: token.value },
]}
copyText={`${token.name}`}
/>
))}
</WebsiteTable>
);
};
@@ -0,0 +1,25 @@
import docs from "@navikt/ds-tokens/docs.json";
import {
WebsiteTable,
WebsiteTableRow,
} from "@/app/_ui/website-table/WebsiteTable";
import { sanitizeName } from "../utilities";
export const ShapesView = ({ cat }: { cat: string }) => {
const shapes = docs[cat];
return (
<WebsiteTable withCopy th={[{ text: "Enhet" }, { text: "Verdi" }]}>
{shapes.map((token) => (
<WebsiteTableRow
key={token.name}
tr={[
{ text: sanitizeName(token.name.replace("border-radius-", "")) },
{ text: token.value },
]}
copyText={`${token.name}`}
/>
))}
</WebsiteTable>
);
};
@@ -0,0 +1,44 @@
import docs from "@navikt/ds-tokens/docs.json";
import {
WebsiteTable,
WebsiteTableRow,
} from "@/app/_ui/website-table/WebsiteTable";
export const SpacingView = ({ cat }: { cat: string }) => {
const spacings = docs[cat];
return (
<WebsiteTable
withCopy
th={[
{ text: "Token" },
{ text: "rem" },
{ text: "px" },
{ text: "demo", sronly: true, hideOnSm: true },
]}
>
{spacings.map((x) => (
<WebsiteTableRow
key={x.name}
tr={[
{ text: x.name.replace("--a-spacing-", "") },
{ text: x.value.replace("rem", "") },
{ text: Number(x.value.replace("rem", "") * 16) },
{
hideOnSm: true,
text: (
<div
className="mr-auto h-8 rounded-md bg-surface-alt-3-strong"
style={{
width: x.value,
}}
/>
),
},
]}
copyText={x.name}
/>
))}
</WebsiteTable>
);
};
@@ -0,0 +1,25 @@
import docs from "@navikt/ds-tokens/docs.json";
import {
WebsiteTable,
WebsiteTableRow,
} from "@/app/_ui/website-table/WebsiteTable";
import { sanitizeName } from "../utilities";
export const ZindexView = ({ cat }: { cat: string }) => {
const zindex = docs[cat] as { name: string; value: number }[];
return (
<WebsiteTable withCopy th={[{ text: "Enhet" }, { text: "Verdi" }]}>
{zindex.map((token) => (
<WebsiteTableRow
key={token.name}
tr={[
{ text: sanitizeName(token.name.replace("z-index-", "")) },
{ text: token.value },
]}
copyText={`${token.value}`}
/>
))}
</WebsiteTable>
);
};
@@ -0,0 +1,13 @@
import color from "tinycolor2";
const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
export const sanitizeName = (x: string) =>
capitalize(x.replace("--a-", "").replaceAll("-", " "));
export const getColorString = (s: string) => {
const c = color(s);
const alpha = c.getAlpha() !== 1;
return alpha ? c.toRgbString() : c.toHexString().toUpperCase();
};
@@ -0,0 +1,4 @@
.legacyTokenTableWarning {
color: var(--ax-text-subtle);
font-weight: var(--ax-font-weight-bold);
}
@@ -0,0 +1,52 @@
import { PortableTextComponentProps } from "next-sanity";
import core from "@navikt/ds-css/tokens.json";
import { BodyLong, Link } from "@navikt/ds-react";
import { Token_kategori } from "@/app/_sanity/query-types";
import {
WebsiteTable,
WebsiteTableRow,
} from "@/app/_ui/website-table/WebsiteTable";
import styles from "./LegacyTokenTable.module.css";
/**
* Lists out the "component"-level tokens.
*/
function LegacyTokenTable(props: PortableTextComponentProps<Token_kategori>) {
const { title } = props.value;
if (!title) {
return null;
}
const tokens: { [key: string]: string } | null =
title in core ? core[title] : null;
if (!tokens) {
return null;
}
return (
<>
<BodyLong>
<strong
data-color-role="warning"
className={styles.legacyTokenTableWarning}
>
Deprecation warning:{" "}
</strong>
<span>
I det nye systemet for theming og darkmode, er komponent-tokens
fjernet. Mer dokumentasjon for dette kommer,{" "}
</span>
<Link href="/darkside">midlertidig dokumentasjon finner du her.</Link>
</BodyLong>
<WebsiteTable th={[{ text: "Token" }, { text: "Fallback" }]}>
{Object.entries(tokens).map(([key, val]) => (
<WebsiteTableRow key={key} tr={[{ text: key }, { text: val }]} />
))}
</WebsiteTable>
</>
);
}
export { LegacyTokenTable };
@@ -1,8 +1,9 @@
"use client";
import { useTheme } from "next-themes";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useState } from "react";
import { useEffect, useState } from "react";
import {
ArrowLeftIcon,
ArrowRightIcon,
@@ -26,6 +27,13 @@ function MobileNavMenu({ children }: { children: React.ReactNode }) {
}
function InitialView({ toggleOpen }: { toggleOpen: () => void }) {
const { theme, setTheme } = useTheme();
/* https://github.com/pacocoursey/next-themes?tab=readme-ov-file#avoid-hydration-mismatch */
const [renderThemeToggle, setRenderThemeToggle] = useState(false);
useEffect(() => setRenderThemeToggle(true), [setTheme]);
return (
<div className={styles.mobileNavMenuIntialView}>
<BodyShort as="ul" className={styles.mobileNavMenuList}>
@@ -46,25 +54,27 @@ function InitialView({ toggleOpen }: { toggleOpen: () => void }) {
</Link>
</li>
</BodyShort>
{/* TODO: Add theming support */}
<ToggleGroup
fill
onChange={() => null}
defaultValue="light"
variant="neutral"
className={styles.mobileNavMenuThemeToggle}
>
<ToggleGroup.Item
value="light"
icon={<SunIcon aria-hidden />}
label="Light"
/>
<ToggleGroup.Item
value="dark"
icon={<MoonIcon aria-hidden />}
label="Dark"
/>
</ToggleGroup>
{renderThemeToggle && (
<ToggleGroup
fill
onChange={setTheme}
value={theme}
variant="neutral"
className={styles.mobileNavMenuThemeToggle}
>
<ToggleGroup.Item
value="light"
icon={<SunIcon aria-hidden />}
label="Light"
/>
<ToggleGroup.Item
value="dark"
icon={<MoonIcon aria-hidden />}
label="Dark"
/>
</ToggleGroup>
)}
</div>
);
}
@@ -30,7 +30,6 @@
border-radius: var(--ax-border-radius-medium);
position: sticky;
top: 0;
background-color: var(--ax-bg-default);
z-index: 20;
flex: 1 1 auto;
@@ -45,7 +44,7 @@
}
&:hover {
background-color: var(--ax-bg-neutral-moderate-hover);
background-color: var(--ax-bg-neutral-moderate-hoverA);
}
& > span:empty {
@@ -1,17 +1,16 @@
import { Sidebar } from "@/app/_ui/sidebar/Sidebar";
import { DesignsystemSidebar } from "@/app/dev/(designsystemet)/_ui/sidebar/Sidebar";
import { MobileNavButton } from "./MobileNav.button";
import { MobileNavDialog } from "./MobileNav.dialog";
import { MobileNavMenu } from "./MobileNav.menu";
import { MobileNavProvider } from "./MobileNav.provider";
/* TODO: avoid "new"section for ds, just show all lings on same menu like primer? */
async function MobileNav() {
return (
<MobileNavProvider>
<MobileNavButton />
<MobileNavDialog>
<MobileNavMenu>
<Sidebar layout="mobile" />
<DesignsystemSidebar layout="mobile" />
</MobileNavMenu>
</MobileNavDialog>
</MobileNavProvider>
@@ -1,12 +1,22 @@
import {
PortableText,
PortableTextBlockComponent,
type PortableTextComponents,
PortableTextMarkComponent,
} from "next-sanity";
import { Children } from "react";
import { BodyLong, BodyShort, Detail, Heading } from "@navikt/ds-react";
import {
ExtractPortableComponentProps,
PortableContentTypes,
} from "@/app/_sanity/types";
import { Attachment } from "@/app/_ui/attachment/Attachment";
import { SingleCodeBlock } from "@/app/_ui/code-block/CodeBlock.single";
import { CompareImages } from "@/app/_ui/compare-images/CompareImages";
import { ExampleText } from "@/app/_ui/example-text/ExampleText";
import { KodeEksempler } from "@/app/_ui/kode-eksempler/KodeEksempler";
import { LegacyTokenModule } from "@/app/_ui/legacy-token-module/LegacyTokenModule";
import { LegacyTokenTable } from "@/app/_ui/legacy-token-table/LegacyTokenTable";
import { Bilde } from "../bilde/Bilde";
import { DoDont } from "../do-dont/DoDont";
import { Kbd } from "../kbd/Kbd";
@@ -15,8 +25,8 @@ import { RelatertInnhold } from "../relatert-innhold/RelatertInnhold";
import { TableV2 } from "../table-v2/TableV2";
import { Tips } from "../tips/Tips";
import { Code } from "../typography/Code";
import { List, ListItem } from "../typography/List";
import { WebsiteLink } from "../typography/WebsiteLink";
import { WebsiteList, WebsiteListItem } from "../typography/WebsiteList";
import { Video } from "../video/Video";
import { WebsiteAccordion } from "../website-accordion/WebsiteAccordion";
import { WebsiteAlert } from "../website-alert/WebsiteAlert";
@@ -50,16 +60,24 @@ function customPortableTextComponents({
tips: Tips,
kode: SingleCodeBlock,
kode_eksempler: KodeEksempler,
} /* satisfies Record<PortableContentTypes, any> */,
token_kategori: LegacyTokenTable,
spesial_seksjon: LegacyTokenModule,
exampletext_block: ExampleText,
attachment: Attachment,
compare_images: CompareImages,
language: LocalCustomPortableText,
} satisfies Record<PortableContentTypes, (props: any) => React.ReactNode>,
block,
marks,
list: {
bullet: ({ children }) => <List as="ul">{children}</List>,
number: ({ children }) => <List as="ol">{children}</List>,
bullet: ({ children }) => <WebsiteList as="ul">{children}</WebsiteList>,
number: ({ children }) => <WebsiteList as="ol">{children}</WebsiteList>,
},
listItem: {
bullet: ({ children }) => <ListItem icon>{children}</ListItem>,
number: ({ children }) => <ListItem>{children}</ListItem>,
bullet: ({ children }) => (
<WebsiteListItem icon>{children}</WebsiteListItem>
),
number: ({ children }) => <WebsiteListItem>{children}</WebsiteListItem>,
},
unknownBlockStyle: ({ children }) =>
withSanitizedBlock(<BodyShort spacing>{children}</BodyShort>),
@@ -181,5 +199,24 @@ function withSanitizedBlock(node: React.ReactElement) {
return node;
}
function LocalCustomPortableText(
props: ExtractPortableComponentProps<"language">,
) {
const { language, body } = props.value;
if (!language) {
return null;
}
return (
<div lang={language}>
<PortableText
components={customPortableTextComponents({})}
value={body ?? []}
/>
</div>
);
}
export { customPortableTextComponents };
export type { CustomPortableTextComponentsProps };
@@ -1,5 +1,4 @@
import { PortableText, type PortableTextBlock } from "next-sanity";
import { useMemo } from "react";
import {
CustomPortableTextComponentsProps,
customPortableTextComponents,
@@ -7,24 +6,22 @@ import {
type CustomPortableTextProps = {
value: PortableTextBlock[];
lang?: string;
className?: string;
} & CustomPortableTextComponentsProps;
} & CustomPortableTextComponentsProps &
React.HTMLAttributes<HTMLDivElement>;
function CustomPortableText({
value,
typoConfig,
className,
lang,
...rest
}: CustomPortableTextProps) {
const customComponents = useMemo(
() => customPortableTextComponents({ typoConfig }),
[typoConfig],
);
return (
<div className={className} lang={lang}>
<PortableText components={customComponents} value={value ?? []} />
<div className={className} {...rest}>
<PortableText
components={customPortableTextComponents({ typoConfig })}
value={value ?? []}
/>
</div>
);
}
@@ -0,0 +1,19 @@
"use client";
import { Box } from "@navikt/ds-react";
import styles from "./PropsSeksjon.module.css";
function PropsSeksjonDeprecation({ text }: { text?: string }) {
if (!text) {
return null;
}
return (
<li className={`${styles.propsSeksjonLi} text-[--ax-text-danger-subtle]`}>
<div className={styles.propsSeksjonLiTitle}>Deprecated:</div>
<Box marginInline="space-8">{text}</Box>
</li>
);
}
export { PropsSeksjonDeprecation };
Loaded 100 of 171 files, more files were not shown because too many files have changed in this diff. Show more