Compare commits

..
Author SHA1 Message Date
github-actions[bot]andgithub-actions[bot] 99465ddbeb Version Packages (#3490)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-07 12:02:45 +01:00
Ken 25305ebba0 🐛 Use inside Alert got wrong text-color while focused or active (#3489) 2025-01-07 11:50:28 +01:00
Halvor HauganandKen ef3464c96c Support contributions from outside Nav + update some README files (#3483)
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
2025-01-06 11:36:42 +01:00
Ken 0480d785c4 🔇 Removed amplitude logging (#3485) 2025-01-01 12:56:36 +01:00
Halvor Haugan ef5e671cfe i18n: rename some files, add tests for useDateLocale (#3481) 2024-12-28 17:05:15 +01:00
Halvor HauganandKen fa156773e0 Upgrade vitest and some related packages (#3482)
* vitest

* Upgrade @testing-library

* Fix error building ds-react

* Upgrade "jsdom" in react-workspace (fixes "The `punycode` module is deprecated")

* ♻️ Matched JSDOM-versions across workspace (v25)

---------

Co-authored-by: Ken <ken.aleksander@gmail.com>
2024-12-28 17:03:37 +01:00
Halvor HauganandKen 137b4c49c1 Automatically add JSDoc with default values to nb.ts (#3479)
* Automatically add JSDoc with default values to nb.ts

* Added jscodeshift to devDeps

* 🐛 Use same jscodeshift versions

* 🐛 Use same types for jscodeshift

---------

Co-authored-by: Ken <ken.aleksander@gmail.com>
2024-12-23 12:03:07 +01:00
Ken 1fb72241fe [Aksel.nav.no] Image comparison module (#3473)
* WIP

* WIP CompareImages

* ✨ Handle more keys

* ♻️ Re-use positioning code

* 💄 Adjust for width

* ✨ Added sanity schema

* 💄 Moved focus to box, allow handle to overflow leftside by 1 px

* ✨ Added bg and alt-text support

* 🐛 Change env back to production
2024-12-23 11:55:52 +01:00
Julian Nymark 42470baaaf ❄️ ⛄ 🎶 🎄 Website: add snowfall (again!) (#3475)
* Website: add snowfall

* website: add egregious christmas ambience

* Revert "website: add egregious christmas ambience"

This reverts commit 3fb858b6ad.

* move dep

* yarn.lock
2024-12-20 15:23:19 +01:00
94 changed files with 1765 additions and 1921 deletions

No files matched your search

+14 -10
View File
@@ -1,31 +1,35 @@
# Changesets
For versjonering bruker vi løsningen [Changesets](https://github.com/changesets/changesets) for [versjonering](https://semver.org/) og publisering av pakker.
Vi bruker [Changesets](https://github.com/changesets/changesets) for [versjonering](https://semver.org/) og publisering av pakker.
## Howto
## Lage ny versjon
- For å lage en ny versjons-entry: `yarn changeset`
- Velg pakkene som skal versjoneres med `arrowkeys` + `Space`
1. `yarn changeset`
2. Svar på spørsmålene som kommer opp:
`🦋 Which packages would you like to include?`
Trykk `Space` for å velge pakkene som skal versjoneres, for så trykke `Enter` for å gå videre.
Trykk `Space` for å velge pakkene som skal versjoneres, trykk så `Enter` for å gå videre.
`🦋 Which packages should have a major bump?`
Trykk `Enter` for å gå videre _*eller*_ velg pakkene som skal marjor-bumpes med `arrowkeys` + `Space`
Trykk `Enter` for å gå videre _eller_ velg pakkene som skal marjor-bumpes med `arrowkeys` + `Space`.
`🦋 Which packages should have a minor bump?`
Trykk `Enter` for å gå videre _*eller*_ velg pakkene som skal marjor-bumpes med `arrowkeys` + `Space`
Trykk `Enter` for å gå videre _eller_ velg pakkene som skal minor-bumpes med `arrowkeys` + `Space`.
Hvis du ikke velger major eller minor, så blir pakkene få en patch-bump
Hvis du ikke velger major eller minor, så vil pakkene få en patch-bump.
- Commit og push.
`Please enter a summary for this change`
Skriv inn en kort beskrivelse av endringen på formatet `<Komponent>: <gitmoji?> <Beskrivelse>`, f.eks. "Button: ✨ Add feature xyz".
3. Commit og push.
## Fixed versjonering
Alle base-pakkene våre har `fixed versjonering`. Dette tilsier at en bump på en av disse vil bumpe alle til samme versjon
Alle base-pakkene våre har `fixed versjonering`. Dette betyr at en bump på en av disse vil bumpe alle til samme versjon
```sh
"@navikt/ds-react",
+2
View File
@@ -1,5 +1,7 @@
# @navikt/aksel-icons
## 7.9.1
## 7.9.0
## 7.8.1
+2 -2
View File
@@ -1,6 +1,6 @@
# Aksel icons
800+ open source icons made by Aksel, NAVs designsystem-team
800+ open source icons made by Aksel, Nav's design system team.
- Designed for 24x24px
- Available in React and SVG
@@ -30,7 +30,7 @@ Tip: Use the `title` prop for accessible icons.
### Sizing
Each icons `width` and `height` is default `1em`. This allows you to use the native `fontSize`-prop to adjust sizing. We recommend using at least 1.5rem/24px for best possible visual representation.
Each icon's `width` and `height` is default `1em`. This allows you to use the native `fontSize`-prop to adjust sizing. We recommend using at least 1.5rem/24px for the best possible visual representation.
```jsx
<StarIcon fontSize="1.5rem" />
+2 -2
View File
@@ -2,9 +2,9 @@
# Aksel Icons plugin
Pluginen gir deg mulighet til å se gjennom og hente ut alle ikonene i fra ikonpakken `@navikt/aksel-icons`. Du slipper da å hente nye oppdateringer fra Figma Community.
Pluginen gir deg mulighet til å se gjennom og hente ut alle ikonene fra ikonpakken `@navikt/aksel-icons`. Du slipper da å hente nye oppdateringer fra Figma Community.
Referer til [ikon-dokumentasjonen](https://aksel.nav.no/ikoner) for å se alle ikonene.
Gå til [ikon-dokumentasjonen](https://aksel.nav.no/ikoner) for å se alle ikonene.
Har du ønsker om nye ikoner, eller ønsker å bidra med å lage nye, lag et issue på [GitHub-repoet for designsystemet](https://github.com/navikt/aksel).
## Quickstart
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-icons",
"version": "7.9.0",
"version": "7.9.1",
"author": "Aksel | Nav",
"homepage": "https://aksel.nav.no/ikoner",
"repository": {
@@ -87,6 +87,6 @@
"tsx": "^4.7.1",
"typescript": "5.5.4",
"unified": "10.1.2",
"vitest": "^1.2.2"
"vitest": "^2.1.8"
}
}
+2
View File
@@ -1,5 +1,7 @@
# @navikt/aksel-stylelint
## 7.9.1
## 7.9.0
## 7.8.1
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-stylelint",
"version": "7.9.0",
"version": "7.9.1",
"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.9.0",
"@navikt/ds-tokens": "^7.9.0",
"@navikt/ds-css": "^7.9.1",
"@navikt/ds-tokens": "^7.9.1",
"concurrently": "9.0.1",
"postcss-selector-parser": "^6.0.13",
"postcss-value-parser": "^4.2.0",
+7
View File
@@ -1,5 +1,12 @@
# @navikt/aksel
## 7.9.1
### Patch Changes
- Updated dependencies [[`25305eb`](https://github.com/navikt/aksel/commit/25305ebba04a37e6315bc37bfabb39f04f83f146)]:
- @navikt/ds-css@7.9.1
## 7.9.0
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel",
"version": "7.9.0",
"version": "7.9.1",
"description": "Aksel command line interface. Handles css-imports, codemods and more",
"author": "Aksel | Nav designsystem team",
"license": "MIT",
@@ -29,7 +29,7 @@
},
"homepage": "https://aksel.nav.no/grunnleggende/kode/kommandolinje",
"dependencies": {
"@navikt/ds-css": "7.9.0",
"@navikt/ds-css": "7.9.1",
"axios": "1.7.4",
"chalk": "4.1.0",
"clipboardy": "^2.3.0",
@@ -46,7 +46,7 @@
"devDependencies": {
"rimraf": "6.0.1",
"typescript": "5.5.4",
"vitest": "^1.2.2"
"vitest": "^2.1.8"
},
"sideEffects": false,
"engines": {
+6
View File
@@ -1,5 +1,11 @@
# @navikt/ds-css
## 7.9.1
### Patch Changes
- Link: :bug: Use inside Alert got wrong text-color while focused or active. ([#3489](https://github.com/navikt/aksel/pull/3489))
## 7.9.0
### Minor Changes
+6 -6
View File
@@ -1,11 +1,11 @@
# Aksel styling
CSS for NAVs designsystem.
CSS for Nav's design system.
- Css tokens (Colors, border, shadows etc)
- Component-styling for `@navikt/ds-react` packages
- Css tokens (Colors, border, shadows etc.)
- Component styling for `@navikt/ds-react` packages
- Normalize.css v8
- Font-loading
- Font loading
## Installation
@@ -16,7 +16,7 @@ CSS for NAVs designsystem.
## Usage
Import styles once to make it accessible for your entire project. Typically you will add the following line to your root .tsx file.
Import styles once to make it accessible for your entire project. Typically you will add the following line to your root .tsx file:
```javascript
import "@navikt/ds-css";
@@ -26,7 +26,7 @@ function App() {
}
```
But you can import it in a .css-file
But you can import it in a .css-file as well:
```css
@import "@navikt/ds-css";
+1 -1
View File
@@ -30,7 +30,7 @@
text-decoration: underline;
}
.navds-alert:not(.navds-alert--inline) .navds-link,
.navds-alert:not(.navds-alert--inline) .navds-link:not(:active, :focus-visible),
.navds-confirmation-panel .navds-link {
color: var(--a-text-default);
}
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-css",
"version": "7.9.0",
"version": "7.9.1",
"description": "CSS for Nav Designsystem",
"author": "Aksel | Nav designsystem team",
"keywords": [
@@ -28,7 +28,7 @@
"test": "yarn build && vitest run"
},
"devDependencies": {
"@navikt/ds-tokens": "^7.9.0",
"@navikt/ds-tokens": "^7.9.1",
"@types/clean-css": "4.2.11",
"autoprefixer": "^10.4.20",
"browserslist": "^4.24.2",
@@ -44,7 +44,7 @@
"postcss-combine-duplicated-selectors": "10.0.3",
"postcss-import": "^14.1.0",
"tsx": "^4.7.1",
"vitest": "^1.2.2"
"vitest": "^2.1.8"
},
"browserslist": [
">0.2%",
+1
View File
@@ -0,0 +1 @@
scripts/*.backup
+8
View File
@@ -1,5 +1,13 @@
# @navikt/ds-react
## 7.9.1
### Patch Changes
- Updated dependencies []:
- @navikt/ds-tokens@7.9.1
- @navikt/aksel-icons@7.9.1
## 7.9.0
### Minor Changes
+2 -2
View File
@@ -1,9 +1,9 @@
# Aksel React-components
React-components for NAVs designsystem.
React-components for Nav's design system.
- Uses styling defined in `@navikt/ds-css`
- All components implemented with React.forwardRef!
- All components are implemented with React.forwardRef!
- Most components extend native html-elements.
## Installation
+17 -12
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-react",
"version": "7.9.0",
"version": "7.9.1",
"description": "React components from the Norwegian Labour and Welfare Administration.",
"author": "Aksel, a team part of the Norwegian Labour and Welfare Administration.",
"license": "MIT",
@@ -603,10 +603,11 @@
"./package.json": "./package.json"
},
"scripts": {
"docgen": "yarn tsx ../../../scripts/docgen.ts",
"docgen": "tsx ../../../scripts/docgen.ts",
"i18n-jsdoc": "tsx ./scripts/addJsdocToLocales.ts",
"write-packagejson": "echo '{\"type\": \"module\"}' > esm/package.json",
"clean": "rimraf cjs esm",
"build": "concurrently \"tsc -p tsconfig.build.json\" \"tsc -p tsconfig.esm.json && tsc-alias -p tsconfig.esm.json && yarn write-packagejson\" ",
"build": "yarn i18n-jsdoc && concurrently \"tsc -p tsconfig.build.json\" \"tsc -p tsconfig.esm.json && tsc-alias -p tsconfig.esm.json && yarn write-packagejson\" && yarn i18n-jsdoc --cleanup",
"watch": "tsc --watch -p tsconfig.esm.json",
"test": "TZ=UTC vitest run -c tests/vitest.config.ts",
"test:watch": "vitest watch"
@@ -614,20 +615,23 @@
"dependencies": {
"@floating-ui/react": "0.25.4",
"@floating-ui/react-dom": "^2.0.9",
"@navikt/aksel-icons": "^7.9.0",
"@navikt/ds-tokens": "^7.9.0",
"@navikt/aksel-icons": "^7.9.1",
"@navikt/ds-tokens": "^7.9.1",
"clsx": "^2.1.0",
"date-fns": "^3.0.0",
"react-day-picker": "8.10.1"
},
"devDependencies": {
"@testing-library/dom": "9.3.4",
"@testing-library/jest-dom": "^5.16.0",
"@testing-library/react": "^15.0.7",
"@testing-library/user-event": "^14.2.0",
"@testing-library/dom": "10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.1.0",
"@testing-library/user-event": "^14.5.2",
"@types/jscodeshift": "^0.11.11",
"aksel": "workspace:^",
"concurrently": "9.0.1",
"fast-glob": "3.2.11",
"jsdom": "24.0.0",
"jscodeshift": "^0.15.1",
"jsdom": "25.0.1",
"react-dom": "^18.0.0",
"react-router-dom": "^6.3.0",
"rimraf": "6.0.1",
@@ -635,7 +639,7 @@
"tsc-alias": "1.8.8",
"tsx": "^4.19.1",
"typescript": "5.5.4",
"vitest": "^1.2.2"
"vitest": "^2.1.8"
},
"peerDependencies": {
"@types/react": ">=17.0.30",
@@ -645,5 +649,6 @@
"@types/react": {
"optional": true
}
}
},
"_comment": "The 'aksel' dependency tells yarn to install the root deps when installing deps for this package. Helps make it possible to contribute without access to GPR."
}
@@ -0,0 +1,59 @@
import fs from "fs";
import jscodeshift from "jscodeshift";
const j = jscodeshift.withParser("ts");
const file = "src/util/i18n/locales/nb.ts";
const backup = "scripts/nb.backup";
function addJsdoc() {
if (fs.existsSync(backup)) {
// If last build failed, we need to restore the file so that we don't create duplicate JSDoc
console.info(
`WARNING: Restoring backup of ${file} - Uncommitted changes will be lost!`,
);
fs.copyFileSync(backup, file);
}
fs.copyFileSync(file, backup); // Create a copy of the original file that we can restore afterwards
const code = fs.readFileSync(file, "utf-8");
const parsedCode = j(code);
const defaultExport = parsedCode.find(j.ExportDefaultDeclaration);
const keys = defaultExport.find(j.ObjectProperty);
keys
.filter((key) => key.value.value.type === "StringLiteral")
.forEach((key) => {
// @ts-expect-error It works, doesn't it?
const value = key.value.value.value;
const foundCommentBlock = key.value.comments?.find(
(comment) => comment.type === "CommentBlock",
);
if (foundCommentBlock) {
foundCommentBlock.value = `${foundCommentBlock.value}\n* @default "${value}" `;
} else {
key.value.comments = [
...(key.value.comments || []),
{
type: "CommentBlock",
value: `* @default "${value}" `,
leading: true,
trailing: false,
},
];
}
});
const modifiedCode = parsedCode.toSource();
fs.writeFileSync(file, modifiedCode);
}
function cleanup() {
fs.copyFileSync(backup, file);
fs.unlinkSync(backup);
}
if (process.argv.includes("--cleanup")) {
cleanup();
} else {
addJsdoc();
}
+1 -1
View File
@@ -9,7 +9,7 @@ import {
} from "@navikt/aksel-icons";
import { Button } from "../button";
import { BodyLong } from "../typography";
import { useI18n } from "../util/i18n/i18n.context";
import { useI18n } from "../util/i18n/i18n.hooks";
export interface AlertProps extends React.HTMLAttributes<HTMLDivElement> {
/**
+1 -1
View File
@@ -2,7 +2,7 @@ import cl from "clsx";
import React, { forwardRef } from "react";
import { XMarkIcon } from "@navikt/aksel-icons";
import { composeEventHandlers } from "../util/composeEventHandlers";
import { useI18n } from "../util/i18n/i18n.context";
import { useI18n } from "../util/i18n/i18n.hooks";
export interface ChipsRemovableProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
@@ -10,7 +10,7 @@ import { CheckmarkIcon, FilesIcon } from "@navikt/aksel-icons";
import { Label } from "../typography";
import { composeEventHandlers } from "../util/composeEventHandlers";
import copy from "../util/copy";
import { useI18n } from "../util/i18n/i18n.context";
import { useI18n } from "../util/i18n/i18n.hooks";
export interface CopyButtonProps
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children"> {
@@ -1,5 +1,5 @@
import { createContext } from "../../util/create-context";
import { TFunction } from "../../util/i18n/i18n.context";
import { TFunction } from "../../util/i18n/i18n.types";
interface DateTranslationContextProps {
translate: TFunction<"DatePicker">;
@@ -5,7 +5,7 @@ import { DateRange, DayPicker, isMatch } from "react-day-picker";
import { omit } from "../../util";
import { useId } from "../../util/hooks";
import { useMergeRefs } from "../../util/hooks/useMergeRefs";
import { useDateLocale, useI18n } from "../../util/i18n/i18n.context";
import { useDateLocale, useI18n } from "../../util/i18n/i18n.hooks";
import { DateInputContext, DateTranslationContextProvider } from "../context";
import { DatePickerInput } from "../parts/DateInput";
import { DateWrapper } from "../parts/DateWrapper";
@@ -3,7 +3,7 @@ import { isWeekend } from "date-fns";
import React, { forwardRef } from "react";
import { DateRange, DayPicker, isMatch } from "react-day-picker";
import { omit } from "../../util";
import { useDateLocale, useI18n } from "../../util/i18n/i18n.context";
import { useDateLocale, useI18n } from "../../util/i18n/i18n.hooks";
import { DateTranslationContextProvider } from "../context";
import { getLocaleFromString, getTranslations } from "../utils";
import Caption from "./parts/Caption";
@@ -1,4 +1,4 @@
import { act, render, screen } from "@testing-library/react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import React from "react";
import { describe, test } from "vitest";
@@ -23,9 +23,6 @@ describe("Render datepicker", () => {
test("Should not crash when e.target is window", async () => {
render(<App />);
// eslint-disable-next-line testing-library/no-unnecessary-act -- https://kentcdodds.com/blog/fix-the-not-wrapped-in-act-warning
await act(async () => {
await userEvent.click(screen.getByText("Velg dato"));
});
await userEvent.click(screen.getByText("Velg dato"));
});
});
@@ -1,7 +1,7 @@
import { differenceInCalendarDays, isWeekend } from "date-fns";
import React, { useCallback, useState } from "react";
import { DayClickEventHandler, isMatch } from "react-day-picker";
import { useDateLocale } from "../../util/i18n/i18n.context";
import { useDateLocale } from "../../util/i18n/i18n.hooks";
import { DatePickerProps } from "../datepicker/DatePicker";
import { DateInputProps } from "../parts/DateInput";
import {
@@ -1,5 +1,5 @@
import React, { useCallback, useMemo, useState } from "react";
import { useDateLocale } from "../../util/i18n/i18n.context";
import { useDateLocale } from "../../util/i18n/i18n.hooks";
import { MonthPickerProps } from "../monthpicker/types";
import { DateInputProps } from "../parts/DateInput";
import {
@@ -1,5 +1,4 @@
/* eslint-disable testing-library/no-unnecessary-act -- https://kentcdodds.com/blog/fix-the-not-wrapped-in-act-warning */
import { act, render, screen } from "@testing-library/react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import React from "react";
import { describe, expect, test } from "vitest";
@@ -29,10 +28,8 @@ describe("Writing in input sets correct values", () => {
const fraInput = screen.getByRole("textbox", { name: "Fra" });
const tilInput = screen.getByRole("textbox", { name: "Til" });
await act(async () => {
await userEvent.type(fraInput, "03.08.2022");
await userEvent.type(tilInput, "03.08.2022");
});
await userEvent.type(fraInput, "03.08.2022");
await userEvent.type(tilInput, "03.08.2022");
const res = screen.getByTitle("res");
expect(res.innerHTML).toEqual(
JSON.stringify({
@@ -47,10 +44,8 @@ describe("Writing in input sets correct values", () => {
const fraInput = screen.getByRole("textbox", { name: "Fra" });
const tilInput = screen.getByRole("textbox", { name: "Til" });
await act(async () => {
await userEvent.type(fraInput, "03.08.2022");
await userEvent.type(tilInput, "02.08.2022");
});
await userEvent.type(fraInput, "03.08.2022");
await userEvent.type(tilInput, "02.08.2022");
const res = screen.getByTitle("res");
expect(res.innerHTML).toEqual(
JSON.stringify({
@@ -5,7 +5,7 @@ import {
} from "date-fns";
import React, { useState } from "react";
import { DateRange, isMatch } from "react-day-picker";
import { useDateLocale } from "../../util/i18n/i18n.context";
import { useDateLocale } from "../../util/i18n/i18n.hooks";
import { DatePickerProps } from "../datepicker/DatePicker";
import { DateInputProps } from "../parts/DateInput";
import {
@@ -3,7 +3,7 @@ import React, { forwardRef, useState } from "react";
import { DayPickerProvider } from "react-day-picker";
import { useId } from "../../util/hooks";
import { useMergeRefs } from "../../util/hooks/useMergeRefs";
import { useDateLocale, useI18n } from "../../util/i18n/i18n.context";
import { useDateLocale, useI18n } from "../../util/i18n/i18n.hooks";
import {
DateInputContext,
DateTranslationContextProvider,
@@ -1,7 +1,7 @@
import cl from "clsx";
import React, { forwardRef, useState } from "react";
import { DayPickerProvider } from "react-day-picker";
import { useDateLocale, useI18n } from "../../util/i18n/i18n.context";
import { useDateLocale, useI18n } from "../../util/i18n/i18n.hooks";
import {
DateTranslationContextProvider,
SharedMonthProvider,
@@ -5,7 +5,8 @@ import { Modal } from "../../modal";
import { useModalContext } from "../../modal/Modal.context";
import { Popover } from "../../popover";
import { useMedia } from "../../util/hooks";
import { TFunction, useI18n } from "../../util/i18n/i18n.context";
import { useI18n } from "../../util/i18n/i18n.hooks";
import { TFunction } from "../../util/i18n/i18n.types";
import { getGlobalTranslations } from "../utils";
const variantToLabel = {
@@ -1,7 +1,7 @@
import cl from "clsx";
import React, { forwardRef, useContext } from "react";
import { ChevronDownIcon } from "@navikt/aksel-icons";
import { useI18n } from "../util/i18n/i18n.context";
import { useI18n } from "../util/i18n/i18n.hooks";
import { ExpansionCardContext } from "./context";
export interface ExpansionCardHeaderProps
+1 -1
View File
@@ -1,6 +1,6 @@
import React from "react";
import { PadlockLockedFillIcon } from "@navikt/aksel-icons";
import { useI18n } from "../util/i18n/i18n.context";
import { useI18n } from "../util/i18n/i18n.hooks";
export const ReadOnlyIcon = () => (
<PadlockLockedFillIcon
@@ -2,7 +2,7 @@ import cl from "clsx";
import React from "react";
import { PlusIcon } from "@navikt/aksel-icons";
import { BodyShort, Label } from "../../../typography";
import { useI18n } from "../../../util/i18n/i18n.context";
import { useI18n } from "../../../util/i18n/i18n.hooks";
import { useInputContext } from "../Input/Input.context";
import { useSelectedOptionsContext } from "../SelectedOptions/selectedOptionsContext";
import { isInList, toComboboxOption } from "../combobox-utils";
@@ -1,6 +1,6 @@
import React from "react";
import { Loader } from "../../../loader";
import { useI18n } from "../../../util/i18n/i18n.context";
import { useI18n } from "../../../util/i18n/i18n.hooks";
import { useInputContext } from "../Input/Input.context";
import filteredOptionsUtil from "./filtered-options-util";
@@ -1,5 +1,5 @@
import React from "react";
import { useI18n } from "../../../util/i18n/i18n.context";
import { useI18n } from "../../../util/i18n/i18n.hooks";
import { useInputContext } from "../Input/Input.context";
import { useSelectedOptionsContext } from "../SelectedOptions/selectedOptionsContext";
import filteredOptionsUtil from "./filtered-options-util";
@@ -3,7 +3,7 @@ import React, { HTMLAttributes, forwardRef, useRef } from "react";
import { BodyShort, Heading } from "../../typography";
import { composeEventHandlers } from "../../util/composeEventHandlers";
import { useMergeRefs } from "../../util/hooks";
import { useI18n } from "../../util/i18n/i18n.context";
import { useI18n } from "../../util/i18n/i18n.hooks";
import ErrorSummaryItem from "./ErrorSummaryItem";
export interface ErrorSummaryProps
@@ -5,7 +5,7 @@ import { Button } from "../../../../button";
import { BodyShort, ErrorMessage, Label } from "../../../../typography";
import { composeEventHandlers } from "../../../../util/composeEventHandlers";
import { useId } from "../../../../util/hooks";
import { useI18n } from "../../../../util/i18n/i18n.context";
import { useI18n } from "../../../../util/i18n/i18n.hooks";
import { omit } from "../../../../util/omit";
import { useFormField } from "../../../useFormField";
import { useFileUploadTranslation } from "../../FileUpload.context";
@@ -3,7 +3,7 @@ import React, { MouseEvent, forwardRef } from "react";
import { ExclamationmarkTriangleIcon } from "@navikt/aksel-icons";
import { BodyShort } from "../../../../typography";
import { OverridableComponent } from "../../../../util";
import { useI18n } from "../../../../util/i18n/i18n.context";
import { useI18n } from "../../../../util/i18n/i18n.hooks";
import { ComponentTranslation } from "../../../../util/i18n/i18n.types";
import { useFileUploadTranslation } from "../../FileUpload.context";
import { FileItem } from "./Item.types";
@@ -6,7 +6,7 @@ import { HStack } from "../../layout/stack";
import { ProgressBar } from "../../progress-bar";
import { Stepper, StepperStepProps } from "../../stepper";
import { BodyShort } from "../../typography";
import { useI18n } from "../../util/i18n/i18n.context";
import { useI18n } from "../../util/i18n/i18n.hooks";
import { ComponentTranslation } from "../../util/i18n/i18n.types";
export interface FormProgressProps
@@ -1,7 +1,7 @@
import cl from "clsx";
import React, { forwardRef } from "react";
import { Link } from "../../link";
import { useI18n } from "../../util/i18n/i18n.context";
import { useI18n } from "../../util/i18n/i18n.hooks";
import { OverridableComponent } from "../../util/types";
export interface FormSummaryEditProps
@@ -11,7 +11,7 @@ import { useThemeInternal } from "../../theme/Theme";
import { BodyShort, ErrorMessage, Label } from "../../typography";
import { omit } from "../../util";
import { useMergeRefs } from "../../util/hooks/useMergeRefs";
import { useI18n } from "../../util/i18n/i18n.context";
import { useI18n } from "../../util/i18n/i18n.hooks";
import { FormFieldProps, useFormField } from "../useFormField";
import SearchButton, { SearchButtonType } from "./SearchButton";
import { SearchContext } from "./context";
@@ -3,7 +3,7 @@ import React, { forwardRef, useContext } from "react";
import { MagnifyingGlassIcon } from "@navikt/aksel-icons";
import { Button, ButtonProps } from "../../button";
import { composeEventHandlers } from "../../util/composeEventHandlers";
import { useI18n } from "../../util/i18n/i18n.context";
import { useI18n } from "../../util/i18n/i18n.hooks";
import { SearchContext } from "./context";
export interface SearchButtonProps
@@ -2,7 +2,7 @@ import cl from "clsx";
import React, { useEffect, useState } from "react";
import { BodyShort } from "../../typography";
import debounce from "../../util/debounce";
import { useI18n } from "../../util/i18n/i18n.context";
import { useI18n } from "../../util/i18n/i18n.hooks";
import type { TextareaProps } from "./Textarea";
interface Props {
@@ -1,5 +1,5 @@
import React from "react";
import { useI18n } from "../util/i18n/i18n.context";
import { useI18n } from "../util/i18n/i18n.hooks";
export const DarksideGudiepanelIllustration = () => {
const translate = useI18n("GuidePanel");
@@ -1,5 +1,5 @@
import React from "react";
import { useI18n } from "../util/i18n/i18n.context";
import { useI18n } from "../util/i18n/i18n.hooks";
export const DefaultIllustration = () => {
const translate = useI18n("GuidePanel");
@@ -4,7 +4,7 @@ import { Popover, PopoverProps } from "../popover";
import { useThemeInternal } from "../theme/Theme";
import { composeEventHandlers } from "../util/composeEventHandlers";
import { useMergeRefs } from "../util/hooks/useMergeRefs";
import { useI18n } from "../util/i18n/i18n.context";
import { useI18n } from "../util/i18n/i18n.hooks";
import { HelpTextIcon } from "./HelpTextIcon";
export interface HelpTextProps
+1 -1
View File
@@ -2,7 +2,7 @@ import cl from "clsx";
import React, { SVGProps, forwardRef } from "react";
import { omit } from "../util";
import { useId } from "../util/hooks";
import { useI18n } from "../util/i18n/i18n.context";
import { useI18n } from "../util/i18n/i18n.hooks";
export interface LoaderProps extends Omit<SVGProps<SVGSVGElement>, "ref"> {
/**
+1 -1
View File
@@ -2,7 +2,7 @@ import cl from "clsx";
import React, { forwardRef } from "react";
import { XMarkIcon } from "@navikt/aksel-icons";
import { Button } from "../button";
import { useI18n } from "../util/i18n/i18n.context";
import { useI18n } from "../util/i18n/i18n.hooks";
import { useModalContext } from "./Modal.context";
export interface ModalHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
@@ -3,7 +3,7 @@ import React, { forwardRef } from "react";
import { ChevronLeftIcon, ChevronRightIcon } from "@navikt/aksel-icons";
import { BodyShort, Heading } from "../typography";
import { useId } from "../util";
import { useI18n } from "../util/i18n/i18n.context";
import { useI18n } from "../util/i18n/i18n.hooks";
import PaginationItem, {
PaginationItemProps,
PaginationItemType,
@@ -1,6 +1,6 @@
import cl from "clsx";
import React, { HTMLAttributes, forwardRef, useEffect, useRef } from "react";
import { useI18n } from "../util/i18n/i18n.context";
import { useI18n } from "../util/i18n/i18n.hooks";
interface ProgressBarPropsBase
extends Omit<HTMLAttributes<HTMLDivElement>, "role"> {
@@ -4,7 +4,7 @@ import { ChevronDownIcon } from "@navikt/aksel-icons";
import { composeEventHandlers } from "../util/composeEventHandlers";
import { useId } from "../util/hooks";
import { useControllableState } from "../util/hooks/useControllableState";
import { useI18n } from "../util/i18n/i18n.context";
import { useI18n } from "../util/i18n/i18n.hooks";
import AnimateHeight from "./AnimateHeight";
import DataCell from "./DataCell";
import Row, { RowProps } from "./Row";
+4 -12
View File
@@ -1,4 +1,4 @@
import { act, fireEvent, render, screen } from "@testing-library/react";
import { fireEvent, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import React from "react";
import { describe, expect, test } from "vitest";
@@ -154,19 +154,11 @@ describe("Tabs", () => {
expect(screen.getByTestId("tab2")).toHaveFocus();
/* Move focus to tabPanel */
// eslint-disable-next-line testing-library/no-unnecessary-act
await act(async () => {
/* Tablist handles tabbing with setTimeout, so we need to use act */
await userEvent.tab();
});
await userEvent.tab();
expect(screen.getByTestId("tabpanel1")).toHaveFocus();
/* Move focus back to tablist, now tab1 should have focus */
// eslint-disable-next-line testing-library/no-unnecessary-act
await act(async () => {
await userEvent.tab({ shift: true });
});
/* Move focus back to tablist, now tab1 should have focus */
await userEvent.tab({ shift: true });
expect(screen.getByTestId("tab1")).toHaveFocus();
});
});
@@ -16,7 +16,8 @@ import {
} from "date-fns";
import React from "react";
import { Detail } from "../typography/Detail";
import { TFunction, useDateLocale, useI18n } from "../util/i18n/i18n.context";
import { useDateLocale, useI18n } from "../util/i18n/i18n.hooks";
import { TFunction } from "../util/i18n/i18n.types";
import { useTimelineContext } from "./hooks/useTimelineContext";
import { isVisible } from "./utils";
import { horizontalPositionAndWidth } from "./utils/calc";
+1 -1
View File
@@ -17,7 +17,7 @@ import { format } from "date-fns";
import React, { forwardRef, useRef, useState } from "react";
import { useThemeInternal } from "../theme/Theme";
import { useMergeRefs } from "../util/hooks/useMergeRefs";
import { useI18n } from "../util/i18n/i18n.context";
import { useI18n } from "../util/i18n/i18n.hooks";
import { useTimelineContext } from "./hooks/useTimelineContext";
import { position } from "./utils/calc";
import { TimelineComponentTypes } from "./utils/types.internal";
@@ -2,7 +2,7 @@ import cl from "clsx";
import { format } from "date-fns";
import React, { forwardRef } from "react";
import { BodyShort } from "../typography/BodyShort";
import { useI18n } from "../util/i18n/i18n.context";
import { useI18n } from "../util/i18n/i18n.hooks";
import { PeriodContext } from "./hooks/usePeriodContext";
import { useRowContext } from "./hooks/useRowContext";
import { useTimelineContext } from "./hooks/useTimelineContext";
@@ -17,7 +17,7 @@ import cl from "clsx";
import React, { useRef, useState } from "react";
import { useThemeInternal } from "../../theme/Theme";
import { useMergeRefs } from "../../util/hooks/useMergeRefs";
import { useI18n } from "../../util/i18n/i18n.context";
import { useI18n } from "../../util/i18n/i18n.hooks";
import { usePeriodContext } from "../hooks/usePeriodContext";
import { useRowContext } from "../hooks/useRowContext";
import { useTimelineContext } from "../hooks/useTimelineContext";
@@ -1,6 +1,6 @@
import cl from "clsx";
import React from "react";
import { useI18n } from "../../util/i18n/i18n.context";
import { useI18n } from "../../util/i18n/i18n.hooks";
import { ariaLabel, getConditionalClasses } from "../utils/period";
import type { PeriodProps } from "./types";
@@ -1,6 +1,6 @@
import cl from "clsx";
import { format } from "date-fns";
import type { TFunction } from "../../util/i18n/i18n.context";
import type { TFunction } from "../../util/i18n/i18n.types";
import type { PeriodProps } from "../period/types";
export const getConditionalClasses = (
@@ -8,7 +8,7 @@ import {
} from "date-fns";
import React, { forwardRef } from "react";
import { Detail } from "../../typography/Detail";
import { useI18n } from "../../util/i18n/i18n.context";
import { useI18n } from "../../util/i18n/i18n.hooks";
import { useTimelineContext } from "../hooks/useTimelineContext";
export interface TimelineZoomButtonProps {
@@ -2,9 +2,8 @@ import { renderHook } from "@testing-library/react";
import React from "react";
import { describe, expect, test } from "vitest";
import { Provider } from "../../provider";
import { useI18n } from "./i18n.context";
import en from "./locales/en";
import nb from "./locales/nb";
import { useDateLocale, useI18n } from "./i18n.hooks";
import { en, nb, nn } from "./locales";
describe("useI18n", () => {
test("should throw error if key is not found", () => {
@@ -112,3 +111,63 @@ describe("useI18n", () => {
expect(() => translate("item.uploading", { other: "John" })).toThrowError();
});
});
describe("useDateLocale", () => {
test("should return the default NB date locale when Provider is not used", () => {
const { result } = renderHook(() => useDateLocale());
const dateLocale = result.current;
expect(dateLocale).toBe(nb.global.dateLocale);
});
test("should return the default NB date locale when Provider is used without the locale prop", () => {
const { result } = renderHook(() => useDateLocale(), {
wrapper: ({ children }) => <Provider>{children}</Provider>,
});
const dateLocale = result.current;
expect(dateLocale).toBe(nb.global.dateLocale);
});
test("should return date locale from context.locale when using Provider with just locale prop", () => {
const { result } = renderHook(() => useDateLocale(), {
wrapper: ({ children }) => <Provider locale={en}>{children}</Provider>,
});
const dateLocale = result.current;
expect(dateLocale).toBe(en.global.dateLocale);
});
test("should return date locale from context.locale when it does not exist in translations", () => {
const { result } = renderHook(() => useDateLocale(), {
wrapper: ({ children }) => (
<Provider locale={en} translations={{ global: {} }}>
{children}
</Provider>
),
});
const dateLocale = result.current;
expect(dateLocale).toBe(en.global.dateLocale);
});
test("should return date locale from context.translations", () => {
const { result } = renderHook(() => useDateLocale(), {
wrapper: ({ children }) => (
<Provider locale={en} translations={nn}>
{children}
</Provider>
),
});
const dateLocale = result.current;
expect(dateLocale).toBe(nn.global.dateLocale);
});
test("should return date locale from second object in context.translations", () => {
const { result } = renderHook(() => useDateLocale(), {
wrapper: ({ children }) => (
<Provider locale={nb} translations={[{}, nn, en]}>
{children}
</Provider>
),
});
const dateLocale = result.current;
expect(dateLocale).toBe(nn.global.dateLocale);
});
});
@@ -5,26 +5,12 @@ import {
Component,
ComponentTranslation,
PartialTranslations,
Translations,
TFunction,
} from "./i18n.types";
/**
* https://regex101.com/r/LYKWi3/1
*/
/* https://regex101.com/r/LYKWi3/1 */
const REPLACE_REGEX = /{[^}]*}/g;
/* https://dev.to/pffigueiredo/typescript-utility-keyof-nested-object-2pa3 */
type NestedKeyOf<ObjectType extends object> = {
[Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object
? `${Key}.${NestedKeyOf<ObjectType[Key]>}`
: `${Key}`;
}[keyof ObjectType & (string | number)];
export type TFunction<T extends Component> = (
keypath: NestedKeyOf<Translations[T]>,
replacements?: Record<string, string | number>,
) => string;
export function useI18n<T extends Component>(
componentName: T,
...localTranslations: (ComponentTranslation<T> | undefined)[]
@@ -39,9 +25,7 @@ export function useI18n<T extends Component>(
context.locale[componentName],
];
/**
* https://github.com/Shopify/polaris/blob/2115f9ba2f5bcbf2ad15745233501bff2db81ecf/polaris-react/src/utilities/i18n/I18n.ts#L24
*/
/* https://github.com/Shopify/polaris/blob/2115f9ba2f5bcbf2ad15745233501bff2db81ecf/polaris-react/src/utilities/i18n/I18n.ts#L24 */
const translate: TFunction<T> = (keypath, replacements) => {
const text = get(keypath, i18nObjects);
@@ -1,5 +1,17 @@
import type nb from "./locales/nb";
/* https://dev.to/pffigueiredo/typescript-utility-keyof-nested-object-2pa3 */
type NestedKeyOf<ObjectType extends object> = {
[Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object
? `${Key}.${NestedKeyOf<ObjectType[Key]>}`
: `${Key}`;
}[keyof ObjectType & (string | number)];
export type TFunction<T extends Component> = (
keypath: NestedKeyOf<Translations[T]>,
replacements?: Record<string, string | number>,
) => string;
/* https://stackoverflow.com/questions/47914536/use-partial-in-nested-property-with-typescripts */
type RecursivePartial<T> = {
[P in keyof T]?: RecursivePartial<T[P]>;
+1 -7
View File
@@ -1,7 +1 @@
/**
* Setup-file is required to avoid "chai" type errors when using testing-library with vitest
*/
import matchers from "@testing-library/jest-dom/matchers";
import { expect } from "vitest";
expect.extend(matchers);
import "@testing-library/jest-dom/vitest";
+7 -1
View File
@@ -7,5 +7,11 @@
"declaration": true,
"sourceMap": true
},
"include": ["./src"]
"include": ["./src"],
"exclude": [
"./src/**/*.test.ts",
"./src/**/*.test.tsx",
"./src/**/*.test-d.ts",
"./src/**/*.stories.tsx"
]
}
+2 -1
View File
@@ -2,6 +2,7 @@
"extends": "../../../tsconfig.json",
"compilerOptions": {
"noImplicitAny": false,
"resolveJsonModule": true
"resolveJsonModule": true,
"types": ["vitest/globals"]
}
}
+2
View File
@@ -1,5 +1,7 @@
# @navikt/ds-tailwind
## 7.9.1
## 7.9.0
## 7.8.1
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-tailwind",
"version": "7.9.0",
"version": "7.9.1",
"description": "Tailwind config based on @navikt/ds-tokens",
"author": "Aksel | Nav designsystem team",
"keywords": [
@@ -24,13 +24,13 @@
"directory": "@navikt/core/tailwind"
},
"devDependencies": {
"@navikt/ds-tokens": "^7.9.0",
"@navikt/ds-tokens": "^7.9.1",
"color": "4.2.3",
"lodash": "^4.17.21",
"tailwindcss": "^3.3.3",
"tsx": "^4.19.1",
"typescript": "5.5.4",
"vitest": "^1.2.2"
"vitest": "^2.1.8"
},
"publishConfig": {
"access": "public",
+2
View File
@@ -1,5 +1,7 @@
# @navikt/ds-tokens
## 7.9.1
## 7.9.0
## 7.8.1
+6 -6
View File
@@ -1,16 +1,16 @@
# Aksel tokens
Design-tokens for NAVs designsystem and used when implementing @navikt/ds-css.
Design-tokens for Nav's design system.
Node: This package is consumed by `@navikt/ds-css`, so not needed separately for regular use of css-variables.
Node: This package is consumed by `@navikt/ds-css`, so not needed separately for regular use of CSS-variables.
Formats
Formats:
- CSS-variables <- Used in `@navikt/ds-css`
- Commonjs
- CommonJS
- ESM
- Less
- Scss
- Sass
## Installation
@@ -23,7 +23,7 @@ Formats
```css
color: var(--a-text-default);
backgroundcolor: var(--a-surface-default);
background-color: var(--a-surface-default);
```
## Learn more
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-tokens",
"version": "7.9.0",
"version": "7.9.1",
"description": "Design-tokens for Nav designsystem",
"author": "Aksel | Nav designsystem team",
"keywords": [
@@ -40,7 +40,7 @@
"style-dictionary": "^4.1.1",
"tsx": "^4.19.1",
"typescript": "5.5.4",
"vitest": "^1.2.2"
"vitest": "^2.1.8"
},
"publishConfig": {
"access": "public",
+570 -568
View File
File diff suppressed because it is too large. Load diff
+18 -10
View File
@@ -2,7 +2,7 @@
## TL;DR
Vi bruker yarn for pakkeversjonering. Har du ikke yarn? `npm i -g yarn`
Vi bruker yarn for pakkeversjonering. Har du ikke yarn? `corepack enable`
```sh
git clone git@github.com:navikt/aksel.git
@@ -17,24 +17,32 @@ yarn storybook
yarn dev
```
Storybook: [http://localhost:6006](http://localhost:6006)
Storybook: http://localhost:6006
Nettside: [http://localhost:3000](http://localhost:3000)
Nettside: http://localhost:3000
### Ikke Nav-ansatt?
Nettsiden avhenger av pakker som ikke er åpent tilgjengelig. Du kan likevel bidra på andre deler av repoet ved å kjøre denne kommandoen i stedet for `yarn install`:
```sh
yarn workspaces focus @navikt/aksel-icons @navikt/ds-tokens @navikt/ds-css @navikt/ds-react @navikt/ds-tailwind @navikt/aksel @navikt/aksel-stylelint
```
## Utviklingsmiljø
Vi bruker storybook [https://storybook.js.org/](https://storybook.js.org/) for utvikling av nye komponenter.
Lag `stories` for komponentene man vil teste [(hvordan skrive storybook stories)](https://storybook.js.org/docs/react/writing-stories/introduction)
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.
## Utvikling av nye komponenter
Det er ikke satt opp en template for nye komponenter, men vi følger en mal.
Det er ikke satt opp noen template for nye komponenter, men vi følger en mal.
```sh
@navikt/pakkenavn
└─ src # Komponenter
├─ index.ts # Alle exports
└─ pakkenavn
└─ komponent
├─ index.ts # Alle exports fra komponent
├─ komponent.tsx # Komponentkode
└─ komponent.stories.tsx # Stories
@@ -42,7 +50,7 @@ Det er ikke satt opp en template for nye komponenter, men vi følger en mal.
## Dokumentasjon
Dokumentasjon for komponenter finner man på [aksel.nav.no](https://aksel.nav.no/). Hvis man ønsker å skrive noe dokumentasjon selv gjør vi det via CMS et Sanity. Kode-eksempler blir skrevet lokalt i [/pages/eksempler](https://github.com/navikt/aksel/tree/main/aksel.nav.no/website/pages/eksempler)
Dokumentasjon for komponenter finner man på [aksel.nav.no](https://aksel.nav.no/). Hvis man ønsker å skrive noe dokumentasjon selv gjør vi det via CMS-et Sanity. Kode-eksempler blir skrevet lokalt i [/pages/eksempler](https://github.com/navikt/aksel/tree/main/aksel.nav.no/website/pages/eksempler)
## Versjonering av pakker
@@ -50,10 +58,10 @@ Vi bruker `changeset` for versjonering av pakker. Se [README](https://github.com
## Kodekvalitet og testing
Komponenter og løsninger blir testet med jest + react-testing-library.
Komponenter og løsninger blir testet med vitest + react-testing-library.
- Commit: Kjører prettier på endrede filer + linter
- Ved PR: Kjører tester, linter og visuell regresjonstester
- Ved PR: Kjører tester, linter og visuelle regresjonstester
## Oppdatere Aksel.nav.no
+16 -24
View File
@@ -1,24 +1,18 @@
<h1 align="center">
<img src="/aksel.nav.no/website/public/favicon.svg" style="width:13rem"/>
<img src="/aksel.nav.no/website/public/favicon.svg" style="width:13rem" />
<br/><br/>Aksel
</h1>
<div align="center">
<p>Nav's design system + Aksel.nav.no</p>
<p>
Navs designsystem + Aksel.nav.no
<a href="https://aksel.nav.no/storybook/"><img alt="Storybook" src="https://shields.io/badge/storybook-white?logo=storybook&style=flat" /></a>&nbsp;
<a href="https://github.com/navikt/aksel/pulls"><img alt="PRs" src="https://img.shields.io/badge/PRs-welcome-green.svg?color=%23262626" /></a>&nbsp;
<a href="https://www.npmjs.com/package/@navikt/ds-css"><img alt="npm" src="https://img.shields.io/npm/v/@navikt/ds-css?label=version" /></a>&nbsp;
</p>
<p>
<a href="https://aksel.nav.no">Nettside</a> | <a href="https://github.com/navikt/aksel/issues">Issues</a>
</p>
<div align="center">
<a href="https://aksel.nav.no/storybook/">
<img src="https://shields.io/badge/storybook-white?logo=storybook&style=flat"" />
</a>
<a href="https://github.com/navikt/aksel/pulls">
<img src="https://img.shields.io/badge/PRs-welcome-green.svg?color=%23262626"" />
</a>
<a href="https://www.npmjs.com/package/@navikt/ds-css"><img alt="npm" src="https://img.shields.io/npm/v/@navikt/ds-css?label=version"></a>
</div>
<br/>
<a href="https://aksel.nav.no">Nettside</a> | <a href="https://github.com/navikt/aksel/issues">Issues</a>
<br/>
</div>
## Innhold
@@ -34,15 +28,14 @@
└── aksel # @navikt/aksel, kommandolinje-verktøy
aksel.nav.no/
├── website # NEXT.JS app (v13)
├── website/sanity # Sanity CMS-app (v3)
└── playroom # aksel.nav.no/sandbox
├── website # Next.js-app
├── website/sanity # Sanity CMS-app
└── playroom # aksel.nav.no/sandbox
```
## Commands
## Kommandoer
Installer dependencies og bygg workspace
Installer dependencies og bygg workspace:
```sh
yarn && yarn boot
@@ -50,7 +43,7 @@ yarn && yarn boot
### Tilgang til Github Package Registry
Siden vi bruker avhengigheter som ligger i GPR, så må man sette opp tilgang til GPR med en PAT (personal access token) som har `read:packages`. Du kan [opprette PAT her](https://github.com/settings/tokens).
Siden vi bruker avhengigheter som ligger i GPR, så må man sette opp tilgang til GPR med en PAT (personal access token) som har `read:packages` og er autorisert for organisasjonen "navikt". Du kan [opprette PAT her](https://github.com/settings/tokens). (Ikke Nav-ansatt? Se [CONTRIBUTING.md](https://github.com/navikt/aksel/blob/main/CONTRIBUTING.md))
I din `.bashrc` eller `.zshrc`, sett følgende miljøvariabel:
@@ -64,14 +57,14 @@ I din `.bashrc` eller `.zshrc`, sett følgende miljøvariabel:
| `yarn storybook` | Starter storybook |
| `yarn dev` | Starter aksel-nettsiden [(Mer info)](https://github.com/navikt/aksel/blob/main/aksel.nav.no/README.md) |
| `yarn example:shadow-dom` | Starter demo-app med shadow-dom |
| `yarn lint` | Linter react and css workspaces |
| `yarn lint` | Linter kode og css |
| `yarn test` | Kjører tester |
| `yarn clean` | Fjerner genererte filer |
| `yarn changeset` | Ny versjon-entry [(how-to)](https://github.com/navikt/aksel/blob/main/.changeset/README.md) |
## Bidra
Vi tar gjerne i mot PR-er! [CONTRIBUTING.md](https://github.com/navikt/aksel/blob/main/CONTRIBUTING.md)
Vi tar gjerne imot PR-er! [CONTRIBUTING.md](https://github.com/navikt/aksel/blob/main/CONTRIBUTING.md)
## Lisenser
@@ -95,7 +88,6 @@ Løsningen forvaltes av team Aksel
Thanks to [Chromatic](https://www.chromatic.com/) for providing the visual testing platform that helps us review UI changes and catch visual regressions.
## Kode generert av GitHub Copilot
Dette repoet bruker GitHub Copilot til å generere kode.
+13 -15
View File
@@ -1,17 +1,17 @@
CMS og nettside for [aksel.nav.no](https://aksel.nav.no/)
```sh
├── /website # NEXT.JS app (v13)
└── /website/sanity # Sanity CMS-app (v3)
├── /website # Next.js-app
└── /website/sanity # Sanity CMS-app
```
### /website
[Nextjs](https://nextjs.org/) applikasjon som henter data fra et [sanity-datasett](https://www.sanity.io/).
[Next.js](https://nextjs.org/)-applikasjon som henter data fra et [Sanity](https://www.sanity.io/)-datasett.
### /website/sanity
Det publiserte studioet finner man på [aksel.nav.no/admin](https://aksel.nav.no/admin)(krever Nav SSO for innlogging).
Det publiserte studioet finner man på [aksel.nav.no/admin](https://aksel.nav.no/admin) (krever Nav SSO for innlogging).
`production`-datasettet er privat, så man må ha riktige tilganger for å kunne lese/jobbe med dataen der.
@@ -23,9 +23,7 @@ Branch `main` pusher rett til prod -> aksel.nav.no. For å utvikle nye features
## Kode-eksempler
Alle eksemplene våre ligger under `/website/pages/eksempler` og `/website/pages/templates`. For å se endringene live kan man starte opp storybook med `yarn storybook:aksel`.
> Du trenger ikke secrets for å teste storybook
Alle eksemplene våre ligger under `/website/pages/eksempler` og `/website/pages/templates`. For å se endringene live kan man starte opp storybook med `yarn storybook:aksel`. Du trenger ikke secrets for å kjøre storybook.
### Nettside (localhost:3000)
@@ -41,28 +39,28 @@ yarn serve:next
**Rettigheter**
Sanity-datasettet er privat, noe som betyr du må ha tilgang til sanity applikasjonen + følgende token for å få data lokalt:
Sanity-datasettet er privat, noe som betyr du må ha tilgang til Sanity-applikasjonen og følgende tokens:
```env
SANITY_READ
SANITY_READ_NO_DRAFTS
```
`SANITY_READ` er bare nødvendig for lokal forhåndsvisning.
### .env
Blir brukt flere keys i .env under `./website`, men kun `SANITY_READ_NO_DRAFTS` trengs for å teste lokalt
Det blir brukt flere keys i .env under `./website`, men kun `SANITY_READ_NO_DRAFTS` trengs for å teste lokalt.
- SANITY_WRITE:
For å sende dokumenter til sanity, brukt til å oppdatere sandboxes/examples/farger. Trengs bare hvis kode skal synkes fra lokal branch/teste feedback-løsning
- SANITY_READ: Gir appen tilgang til å lese draft innhold fra sanity i "preview"-mode. Trengs for å teste ikke publiserte-sider
- SANITY_READ_NO_DRAFTS: Gir appen tilgang til å lese innhold fra Sanity da datasettet er privat
For å sende dokumenter til Sanity. Blir brukt i lokale scripts til å synke data til nettsiden, som f.eks. kode-eksempler og changelog. Trengs normalt ikke lokalt.
- SANITY_READ: Gir appen tilgang til å lese både publisert og upublisert innhold fra Sanity. Trengs for å teste ikke-publiserte sider i "preview"-mode.
- SANITY_READ_NO_DRAFTS: Gir appen tilgang til å lese kun publisert innhold fra Sanity.
## Backups
Kjøres backups hver 3 dag til GCP-bucket under `designsystem-prod`
Det kjøres backups hver 3. dag til GCP-bucket under `designsystem-prod`.
## Henvendelser
Spørsmål og kontakt kan rettes til team Aksel
Spørsmål og kontakt kan rettes til team Aksel.
Aksel har også en egen Slack-kanal `#aksel-designsystemet`
+5 -5
View File
@@ -15,10 +15,10 @@
"playroom"
],
"dependencies": {
"@navikt/aksel-icons": "^7.9.0",
"@navikt/ds-css": "^7.9.0",
"@navikt/ds-react": "^7.9.0",
"@navikt/ds-tailwind": "^7.9.0",
"@navikt/ds-tokens": "^7.9.0"
"@navikt/aksel-icons": "^7.9.1",
"@navikt/ds-css": "^7.9.1",
"@navikt/ds-react": "^7.9.1",
"@navikt/ds-tailwind": "^7.9.1",
"@navikt/ds-tokens": "^7.9.1"
}
}
@@ -5,8 +5,9 @@ const batchedEvents: Parameters<Pick<Types.BrowserClient, "track">["track"]>[] =
[];
export let amplitude: Pick<Types.BrowserClient, "init" | "track"> = {
track: (...eventsData) => {
batchedEvents.push(eventsData);
track: (/* ...eventsData */) => {
/* Until re-enabled in _app.tsx, we can skip storing events */
/* batchedEvents.push(eventsData); */
return {
promise: new Promise<Types.Result>((resolve) =>
resolve({
@@ -0,0 +1,46 @@
"use client";
import cl from "clsx";
import { forwardRef } from "react";
import { ChevronLeftIcon, ChevronRightIcon } from "@navikt/aksel-icons";
interface CompareHandleProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
isDragging: boolean;
}
export const CompareHandle = forwardRef<HTMLButtonElement, CompareHandleProps>(
({ isDragging, ...rest }, ref) => {
return (
<button
ref={ref}
className={cl(
"absolute left-0 top-0 z-10 h-full appearance-none border-0 bg-none p-0 outline-none transition-opacity",
{
"cursor-ew-resize opacity-100": isDragging,
"opacity-40 focus:opacity-100 group-hover:opacity-100": !isDragging,
},
)}
aria-label="Endre verdi for å sammenligne bilder"
aria-orientation="horizontal"
aria-valuemin={0}
aria-valuemax={100}
aria-valuenow={50}
aria-valuetext="50%"
data-rcs="handle-container"
role="slider"
style={{
transform: "translate3d(-50%, 0, 0)",
left: `var(--image-clip-2)`,
}}
{...rest}
>
<span className="absolute top-0 h-full w-[2px] bg-surface-subtle shadow-[rgba(0,_0,_0,_0.35)_0px_0px_4px]" />
<span className="pointer-events-none relative z-10 flex items-center rounded-full bg-surface-subtle text-3xl">
<ChevronLeftIcon aria-hidden />
<ChevronRightIcon aria-hidden />
</span>
</button>
);
},
);
@@ -0,0 +1,202 @@
"use client";
import cl from "clsx";
import React, {
CSSProperties,
useCallback,
useEffect,
useRef,
useState,
} from "react";
import { BodyShort } from "@navikt/ds-react";
import { urlFor } from "@/sanity/interface";
import { CompareHandle } from "./CompareHandle";
import { CompareItem } from "./CompareItem";
/* eslint-disable @next/next/no-img-element */
type CompareImagesProps = {
node: {
image_1: { asset: any; alt: string };
image_2: { asset: any; alt: string };
caption?: string;
border?: boolean;
background?: {
rgb: { a: number; b: number; g: number; r: number };
alpha: number;
};
};
};
const CompareImages = ({ node }: CompareImagesProps) => {
const containerRef = useRef<HTMLDivElement>(null);
const handleRef = useRef<HTMLButtonElement>(null);
const internalPosition = useRef(50);
const [isDragging, setIsDragging] = useState(false);
const syncPosition = () => {
if (!containerRef.current || !handleRef.current) {
return;
}
containerRef.current.style.setProperty(
"--image-clip-1",
`${100 - internalPosition.current}%`,
);
containerRef.current.style.setProperty(
"--image-clip-2",
`${internalPosition.current}%`,
);
const rounded = Math.round(internalPosition.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));
internalPosition.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]);
if (!node || !node.image_1 || !node.image_2) {
return null;
}
/* 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) => {
internalPosition.current = Math.max(
-1,
Math.min(100, internalPosition.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": `${internalPosition.current}%`,
"--image-clip-1": `${100 - internalPosition.current}%`,
backgroundColor: node.background
? `rgba(${node.background.rgb.r},${node.background.rgb.g},${node.background.rgb.b},${node.background.rgb.a})`
: undefined,
};
return (
<figure className="m-0 mb-8 flex flex-col group-[.aksel-artikkel]/aksel:mx-auto">
<div
ref={containerRef}
style={appliedStyle}
onPointerDown={handlePointerDown}
className={cl(
"group relative grid max-h-full max-w-fit touch-pan-y select-none overflow-hidden rounded-lg",
"outline-2 outline-offset-4 outline-border-focus focus-within:outline",
{
"ring-1 ring-border-subtle": node.border,
},
)}
>
<CompareItem order="1">
<img
src={urlFor(node.image_1.asset).auto("format").url()}
alt={node.image_1.alt}
className="object-cover object-center"
/>
</CompareItem>
<CompareItem order="2">
<img
src={urlFor(node.image_2.asset).auto("format").url()}
alt={node.image_2.alt}
className="object-cover object-center"
/>
</CompareItem>
<CompareHandle
ref={handleRef}
onKeyDown={handleKeyDown}
isDragging={isDragging}
/>
</div>
{node.caption && (
<figcaption className="mt-2 grid gap-1 px-4">
<BodyShort as="span" size="small" className="self-center text-center">
{node.caption}
</BodyShort>
</figcaption>
)}
</figure>
);
};
export default CompareImages;
@@ -0,0 +1,29 @@
"use client";
import { CSSProperties } from "react";
export const CompareItem = ({
children,
order,
}: {
children: React.ReactNode;
order: "1" | "2";
}) => {
const appliedStyle: CSSProperties = {
gridArea: "1 / 1 / 2 / 2",
willChange: "clip-path",
clipPath:
order === "1"
? `inset(0 var(--image-clip-1) 0 0)`
: `inset(0 0 0 var(--image-clip-2))`,
};
return (
<div
style={appliedStyle}
className="box-border max-w-fit cursor-pointer select-none overflow-hidden"
>
{children}
</div>
);
};
@@ -13,6 +13,7 @@ import Bilde from "@/cms/bilde/Bilde";
import InnholdsKort from "@/cms/cards/InnholdsKort";
import CodeExamples from "@/cms/code-examples/CodeExamples";
import SnippetLazy from "@/cms/code-snippet/SnippetLazy";
import CompareImages from "@/cms/compare-images/CompareImages";
import DoDont from "@/cms/do-dont/DoDont";
import ExampletextBlock from "@/cms/exampletext-block/ExampletextBlock";
import ExpansionCard from "@/cms/expansioncard/ExpansionCard";
@@ -53,6 +54,7 @@ const serializers: Partial<PortableTextReactComponents> = {
language: ({ value }) => (
<SanityBlockContent lang={value.language} blocks={value.body} />
),
compare_images: ({ value }) => <CompareImages node={value} />,
},
unknownType: () => null,
block: {
+3 -2
View File
@@ -66,6 +66,7 @@
"react-markdown": "^9.0.1",
"react-responsive-masonry": "2.2.0",
"react-rx": "^2.1.3",
"react-snowfall": "^2.2.0",
"remark-gfm": "^4.0.0",
"sanity": "3.59.0",
"sanity-plugin-iframe-pane": "^3.1.6",
@@ -93,7 +94,7 @@
"babel-loader": "^8.2.0",
"copyfiles": "^2.4.1",
"cross-env": "^7.0.0",
"jsdom": "^22.1.0",
"jsdom": "25.0.1",
"json5": "2.2.1",
"pino-pretty": "^10.0.0",
"postcss": "^8.4.31",
@@ -104,6 +105,6 @@
"start-server-and-test": "^2.0.3",
"tsx": "^4.19.1",
"typescript": "5.5.4",
"vitest": "^1.2.2"
"vitest": "^2.1.8"
}
}
+3 -2
View File
@@ -3,13 +3,14 @@ import { useEffect } from "react";
import { useCheckAuth } from "@/hooks/useCheckAuth";
import { useHashScroll } from "@/hooks/useHashScroll";
import { SanityDataContext } from "@/hooks/useSanityData";
import { useAmplitudeInit } from "@/logging";
import { BaseSEO } from "@/web/seo/BaseSEO";
import "../components/styles/index.css";
function App({ Component, pageProps, router }: AppProps) {
useHashScroll();
useAmplitudeInit();
/* As of 01.01.25, removed until cookie compliance is implemented */
/* useAmplitudeInit(); */
useEffect(() => {
window.location.host === "design.nav.no" &&
@@ -1,6 +1,6 @@
# Kode eksempler
# Kode-eksempler
Kode eksempler som blir vist på komponentsider @ aksel.nav.no
Kode-eksempler som blir vist på komponentsider @ aksel.nav.no
## Anatomi
+12
View File
@@ -3,6 +3,7 @@ import cl from "clsx";
import { groq } from "next-sanity";
import { GetStaticProps } from "next/types";
import { useState } from "react";
import Snowfall from "react-snowfall";
import {
CompassIcon,
ComponentIcon,
@@ -197,6 +198,17 @@ const Forside = ({ page, tema, blocks }: PageProps["props"]) => {
/>
<Header />
<main tabIndex={-1} id="hovedinnhold" className="focus:outline-none">
{!pause && (
<div aria-hidden>
<Snowfall
style={{ zIndex: 1 }}
color="rgba(230, 241, 248, 0.9)"
speed={reducedMotion || pause ? [0, 0] : [0.1, 0.2]}
wind={reducedMotion || pause ? [0, 0] : [-0.2, 0.2]}
snowflakeCount={600}
/>
</div>
)}
<div className="z-20 pb-28">
<div className="relative mx-auto mb-12 mt-20 grid w-full place-items-center px-4 text-center sm:mt-36 sm:max-w-[632px] sm:px-6">
<Heading
@@ -39,7 +39,7 @@ export const Blogg = defineType({
description:
"Innholdet i artikkelen er riktekst. Tips: klikk på ikon i høyre hjørne for å skrive i fullskjerm.",
name: "content",
type: "riktekst_standard",
type: "riktekst_blogg",
group: "innhold",
}),
BaseSEOPreset,
@@ -62,6 +62,7 @@ export const schema: SchemaPluginOptions = {
object.UnikSidemodul,
object.Attachment,
object.Language,
object.CompareImages,
/* Riktekst */
object.RiktekstKomponent,
@@ -72,6 +73,7 @@ export const schema: SchemaPluginOptions = {
object.RiktekstPrinsipp,
object.RiktekstStandalone,
object.RiktekstAccordion,
object.RiktekstBlogg,
/* Komponentsider */
object.PropsSeksjon,
@@ -7,16 +7,17 @@ export * from "./prinsipper/herobilde";
export * from "./prinsipper/innholdskort";
export * from "./shared/accordion";
export * from "./shared/alert";
export * from "./shared/bilde";
export * from "./shared/dodont";
export * from "./shared/attachment";
export * from "./shared/bilde";
export * from "./shared/compare-images";
export * from "./shared/dodont";
export * from "./shared/expansion-card";
export * from "./shared/kode";
export * from "./shared/language";
export * from "./shared/relatert-innhold";
export * from "./shared/riktekst";
export * from "./shared/tabell";
export * from "./shared/tips";
export * from "./shared/unik-sidemodul";
export * from "./shared/video";
export * from "./shared/language";
export * from "./templates/ExampletextBlock";
@@ -0,0 +1,57 @@
import { defineField, defineType } from "sanity";
import { MigrationIcon } from "@navikt/aksel-icons";
export const CompareImages = defineType({
title: "Sammenlign bilder",
name: "compare_images",
type: "object",
icon: () => <MigrationIcon aria-hidden />,
fields: [
defineField({
title: "Bilde 1",
name: "image_1",
type: "image",
validation: (Rule) => Rule.required(),
fields: [
defineField({
name: "alt",
title: "Alt tekst",
type: "string",
validation: (Rule) =>
Rule.required().error("Bilde 1 må ha alt-tekst."),
}),
],
}),
defineField({
title: "Bilde 2",
name: "image_2",
type: "image",
validation: (Rule) => Rule.required(),
fields: [
defineField({
name: "alt",
title: "Alt tekst",
type: "string",
validation: (Rule) =>
Rule.required().error("Bilde 2 må ha alt-tekst."),
}),
],
}),
defineField({
name: "caption",
title: "Bildetekst",
type: "string",
}),
defineField({
name: "border",
title: "Border",
type: "boolean",
initialValue: true,
}),
defineField({
name: "background",
title: "Bakgrunnsfarge",
type: "color",
}),
],
});
@@ -143,6 +143,7 @@ export const headingStyles = [
const Riktekst = (
type:
| "blogg"
| "god-praksis"
| "grunnleggende"
| "templates"
@@ -188,8 +189,8 @@ const Riktekst = (
];
const templates = ["kode_eksempler", "exampletext_block"];
const grunnleggende = ["spesial_seksjon", "attachment", "props_seksjon"];
const blogg = ["compare_images"];
fields.push(...standard);
@@ -209,6 +210,9 @@ const Riktekst = (
case "templates":
fields.push(...templates);
break;
case "blogg":
fields.push(...blogg);
break;
default:
break;
}
@@ -287,3 +291,11 @@ export const RiktekstTemplates = {
of: Riktekst("templates"),
icon: () => <FileTextIcon aria-hidden />,
};
export const RiktekstBlogg = {
title: "Riktekst",
name: "riktekst_blogg",
type: "array",
of: Riktekst("blogg"),
icon: () => <FileTextIcon aria-hidden />,
};
+1 -1
View File
@@ -147,7 +147,7 @@
},
"devDependencies": {
"@biomejs/biome": "1.9.3",
"@changesets/cli": "2.26.2",
"@changesets/cli": "2.27.11",
"@storybook/addon-a11y": "^8.3.4",
"@storybook/addon-essentials": "^8.3.4",
"@storybook/addon-interactions": "^8.3.4",
+1
View File
@@ -1,6 +1,7 @@
{
"name": "eslint-plugin-aksel-local",
"version": "0.1.0",
"private": true,
"main": "index.js",
"module": "index.js"
}
+467 -1130
View File
File diff suppressed because it is too large. Load diff