mirror of
https://github.com/navikt/aksel.git
synced 2026-11-13 08:14:05 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bb33a29fe | ||
|
|
8c88e4d1ba | ||
|
|
b9b51eaed9 | ||
|
|
8ae419c0b4 | ||
|
|
7e7943f196 | ||
|
|
f2702d7f3f | ||
|
|
bf0618c381 | ||
|
|
5b76273cd8 | ||
|
|
cb6a4156aa | ||
|
|
fbf354421b | ||
|
|
a21646db55 | ||
|
|
5f4ac75dd6 | ||
|
|
45944863ff | ||
|
|
7bd7f71fdd | ||
|
|
683d650d65 | ||
|
|
af2a993ad7 | ||
|
|
92f1a283fd | ||
|
|
b8a8ecf6f5 | ||
|
|
12ac05ba5b | ||
|
|
9757c93088 | ||
|
|
5a6c2c5a8f | ||
|
|
02a06f2414 | ||
|
|
9dba82dc0a | ||
|
|
f1952ce49c | ||
|
|
ca950dde84 | ||
|
|
b9dc9c84a8 | ||
|
|
835c70b0ab | ||
|
|
d1e83406a0 | ||
|
|
1a91fa08d2 | ||
|
|
d561ce4d23 | ||
|
|
483c8a30e4 | ||
|
|
411f61465f | ||
|
|
9d0d75005a | ||
|
|
f2f9ee8211 | ||
|
|
7db9f6af49 | ||
|
|
b7819cba85 | ||
|
|
c8b9afee99 | ||
|
|
093e33d01f | ||
|
|
ac1b471a84 | ||
|
|
a05b9b1f2a | ||
|
|
1737ca88cb | ||
|
|
ee132cb09a | ||
|
|
3ab39815b9 | ||
|
|
2dae2e6318 |
No files matched your search
@@ -34,9 +34,8 @@ Alle base-pakkene våre har `fixed versjonering`. Dette betyr at en bump på en
|
||||
```sh
|
||||
"@navikt/ds-react",
|
||||
"@navikt/ds-css",
|
||||
"@navikt/aksel-icons",
|
||||
"@navikt/ds-tokens",
|
||||
"@navikt/ds-tailwind",
|
||||
"@navikt/aksel-icons",
|
||||
"@navikt/aksel",
|
||||
"@navikt/aksel-stylelint"
|
||||
"@navikt/ds-codemod"
|
||||
```
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@4.0.0/schema.json",
|
||||
"$schema": "https://unpkg.com/@changesets/config@2.2.0/schema.json",
|
||||
"changelog": [
|
||||
"@svitejs/changesets-changelog-github-compact",
|
||||
{ "repo": "navikt/aksel" }
|
||||
@@ -21,16 +21,10 @@
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": [
|
||||
"eslint-plugin-aksel-local",
|
||||
"aksel-changelog",
|
||||
"aksel-analyzer",
|
||||
"aksel-workspace-scripts",
|
||||
"aksel.nav.no",
|
||||
"website",
|
||||
"aksel-sanity-studio",
|
||||
"aksel-icons-figma-plugin",
|
||||
"aksel-mcp",
|
||||
"aksel-playroom"
|
||||
"eslint-plugin-aksel-local",
|
||||
"scripts"
|
||||
],
|
||||
|
||||
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@navikt/ds-react": patch
|
||||
---
|
||||
|
||||
DataGrid: Faster rendering when many details panels are expanded.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@navikt/ds-react": patch
|
||||
---
|
||||
|
||||
DataGrid.Table: Keyboard navigation no longer rebuilds the internal grid map on every arrow key press, improving responsiveness in large tables.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@navikt/aksel-icons": minor
|
||||
---
|
||||
|
||||
Icons: All icons are now available as CSS custom properties. Import `@navikt/aksel-icons/css/<IconName>.css` for a single icon (e.g. `--aksel-icon-magnifying-glass`), or `@navikt/aksel-icons/css/index.css` for every icon.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@navikt/ds-react": patch
|
||||
---
|
||||
|
||||
Combobox: Ignore option selection when an open Combobox becomes disabled.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@navikt/ds-react": patch
|
||||
---
|
||||
|
||||
Timeline: Optimize period parsing.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@navikt/aksel-stylelint": patch
|
||||
---
|
||||
|
||||
Stylelint: `@navikt/aksel-stylelint/recommended` now references its plugin by explicit file path, fixing `Could not find "."` errors with Stylelint 18.
|
||||
@@ -1,7 +0,0 @@
|
||||
# https://min-copilot.ansatt.nav.no/cplt
|
||||
[propose]
|
||||
allow_docker = true
|
||||
allow_localhost_any = true
|
||||
|
||||
[propose.allow]
|
||||
localhost = [3000, 6006, 6007]
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Meld inn bugs eller feil
|
||||
title: "[BUG] "
|
||||
title: "[BUG]"
|
||||
labels: "bug \U0001F41B"
|
||||
assignees: ""
|
||||
---
|
||||
@@ -21,7 +21,7 @@ assignees: ""
|
||||
## Steg for å reprodusere problemet (valgfri)
|
||||
|
||||
<!--
|
||||
1. Steg én
|
||||
1. Steg en
|
||||
2. Steg to
|
||||
3. ....
|
||||
-->
|
||||
@@ -31,10 +31,10 @@ assignees: ""
|
||||
## Eksempel/Tilleggsinformasjon (valgfri)
|
||||
|
||||
<!--
|
||||
Screenshots, kodesnippets eller sandbox-lenke.
|
||||
Legg gjerne ved info som nettleser eller desktop/mobil hvis det er relevant.
|
||||
Screenshots eller kodesnippets
|
||||
Legg gjerne ved ekstrainfo som nettleser eller desktop/mobil hvis det er relevant.
|
||||
-->
|
||||
|
||||
```
|
||||
Hvis mulig, legg gjerne til kode her, ev. lenke til kode i repo.
|
||||
Hvis mulig, legg gjerne til kode her, evt lenke til kode i repo
|
||||
```
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Foreslå noe nytt!
|
||||
title: "[NEW] "
|
||||
labels: nytt ✨
|
||||
title: "[NEW]"
|
||||
labels: new ✨
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: Eksempel på bruk
|
||||
description: Send et eksempel på ting fra Aksel som er brukt i det fri (tokens, komponenter, maler, etc.)
|
||||
description: Send et eksempel på ting fra Aksel som er brukt i det fri (tokens, komponenter, maler, etc)
|
||||
title: "[Eksempel på bruk]: "
|
||||
labels: ["dokumentasjon 📖"]
|
||||
body:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: Ny komponent
|
||||
description: Send et ønske om én eller flere nye komponenter
|
||||
description: Send et ønske om en eller flere nye komponenter
|
||||
title: "[Ny komponent]: "
|
||||
labels: ["nytt ✨", "forespørsel 🥰", "komponenter 🧩"]
|
||||
body:
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Meld inn bugs eller feil
|
||||
title: "[BUG]"
|
||||
labels: "bug \U0001F41B"
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
## Hva forventet du å finne for dette søket?
|
||||
@@ -1,5 +1,5 @@
|
||||
name: Innspill til komponent
|
||||
description: Send inn tilbakemelding på komponent
|
||||
description: Send inn innspill til komponent
|
||||
title: "[Innspill til komponent]: "
|
||||
labels: ["forespørsel 🥰", "komponenter 🧩", "nytt ✨", "beta 🧪"]
|
||||
body:
|
||||
@@ -8,7 +8,7 @@ body:
|
||||
value: |
|
||||
Takk for at du bidrar inn i Aksel!
|
||||
- type: textarea
|
||||
id: feedback
|
||||
id: feeback
|
||||
attributes:
|
||||
label: Tilbakemelding
|
||||
description: Beskriv hvilke tilbakemeldinger du har til komponenten
|
||||
@@ -1,6 +1,6 @@
|
||||
name: Innspill til ikon
|
||||
description: Send inn tilbakemelding på ikon
|
||||
title: "[Innspill til ikon]: "
|
||||
description: Send inn innspill til ikon
|
||||
title: "[Tilbakemelding på ikon]: "
|
||||
labels: ["forespørsel 🥰", "ikoner 🖼️"]
|
||||
body:
|
||||
- type: markdown
|
||||
@@ -8,7 +8,7 @@ body:
|
||||
value: |
|
||||
Takk for at du bidrar inn i Aksel!
|
||||
- type: textarea
|
||||
id: feedback
|
||||
id: feeback
|
||||
attributes:
|
||||
label: Tilbakemelding
|
||||
description: Beskriv hvilke tilbakemeldinger du har til ikonet
|
||||
@@ -8,8 +8,10 @@
|
||||
- [ ] Examples
|
||||
- [ ] Documentation / Decision Records
|
||||
- [ ] Storybook
|
||||
- [ ] Style mappings (`@navikt/core/css/config/_mappings.js`)
|
||||
- [ ] Component tokens (`@navikt/core/css/tokens.json`)
|
||||
- [ ] CSS class deprecations (`@navikt/aksel-stylelint/src/deprecations.ts`)
|
||||
- [ ] Exports (`@navikt/core/react/src/index.ts` and `@navikt/core/react/package.json`)
|
||||
- [ ] New component? CSS import (`@navikt/core/css/index.css`)
|
||||
- [ ] Breaking change? Update migration guide. Consider codemod.
|
||||
- [ ] Changeset (Format: `<Component>: <gitmoji?> <Text>` E.g. "Button: :sparkles: Add feature xyz")
|
||||
- [ ] Changeset (Format: `<Component>: <gitmoji?> <Text>.` E.g. "Button: :sparkles: Add feature xyz.")
|
||||
@@ -19,12 +19,6 @@ inputs:
|
||||
sanity_read_no_drafts_token:
|
||||
description: "Sanity read-only token allowing only published documents"
|
||||
required: true
|
||||
sanity_deploy_studio:
|
||||
description: "Sanity studio deployment token"
|
||||
required: false
|
||||
next_encryption_key:
|
||||
description: "Overrides default NEXT_SERVER_ACTIONS_ENCRYPTION_KEY for production builds"
|
||||
required: false
|
||||
production:
|
||||
description: "Toggle to differentiate production build (inject stuff based on this)"
|
||||
default: false
|
||||
@@ -54,25 +48,11 @@ runs:
|
||||
echo "PRODUCTION=true" >> aksel.nav.no/website/.env
|
||||
shell: bash
|
||||
|
||||
- name: Update sanity manifest
|
||||
if: ${{ inputs.production == 'true' && inputs.sanity_deploy_studio != '' }}
|
||||
run: yarn update:sanity-manifest
|
||||
env:
|
||||
SANITY_AUTH_TOKEN: ${{ inputs.sanity_deploy_studio }}
|
||||
shell: bash
|
||||
|
||||
- name: Generate RSS feed
|
||||
run: yarn workspace website update:rss
|
||||
env:
|
||||
SANITY_READ_NO_DRAFTS: ${{ inputs.sanity_read_no_drafts_token }}
|
||||
shell: bash
|
||||
|
||||
- name: Build Next.js website
|
||||
run: yarn workspace website build:with-playroom
|
||||
run: yarn build:next
|
||||
env:
|
||||
SANITY_READ: ${{ inputs.sanity_read_token }}
|
||||
SANITY_READ_NO_DRAFTS: ${{ inputs.sanity_read_no_drafts_token }}
|
||||
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: ${{ inputs.next_encryption_key }}
|
||||
shell: bash
|
||||
|
||||
- name: Upload static files to Nav CDN
|
||||
|
||||
@@ -9,7 +9,7 @@ runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v7
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: yarn
|
||||
|
||||
+227
-70
@@ -1,92 +1,249 @@
|
||||
# Aksel - Copilot Instructions
|
||||
# Aksel – GitHub Copilot Instructions
|
||||
|
||||
## Approach
|
||||
This file tells Copilot how to work inside the Aksel monorepo. Prefer existing patterns and symbols, keep changes minimal, and reference real files in this repo.
|
||||
|
||||
- Narrow search space; don't replace reading code.
|
||||
- Before editing: read target file + nearest related files (`index.ts`, story, test, exports).
|
||||
- Prefer known paths/scripts over broad searches.
|
||||
- User often edits files between turns. Before reviewing/refactoring, re-read the files (or `git diff`); never rely on earlier reads.
|
||||
- Ignore release/deploy/changelog/ops scripts unless asked.
|
||||
## How to act in this monorepo (quick)
|
||||
|
||||
## Behavior
|
||||
- Edit only what’s asked. Don’t invent files/APIs or refactors.
|
||||
- One file at a time. Provide a single coherent diff per file.
|
||||
- Use existing exports, tokens, CSS layers, and patterns before adding new ones.
|
||||
- When touching packages under `@navikt/*`, also update exports and stories nearby.
|
||||
- Add or adjust tests and stories when behavior/visuals change.
|
||||
- Link files with repo paths like `@navikt/react/src/...` or `aksel.nav.no/website/...`.
|
||||
- Keep changes focused and minimal; avoid drive-by rewrites or formatting churn.
|
||||
- Preserve public APIs unless the request explicitly calls for a breaking change (add a changeset if user-facing).
|
||||
|
||||
- Respond in the language the user prompted in, unless they request another language.
|
||||
- Use concise, plain language without sacrificing technical precision or completeness.
|
||||
- Lead with the outcome. Avoid filler and unnecessary apologies; summarize meaningful changes and validation when useful.
|
||||
- Do not narrate routine tool use. Explain decisions, blockers, uncertainty, and tradeoffs when relevant.
|
||||
- Show only what changes. Minimal diffs; don't paste whole files.
|
||||
- Edit only the files the change needs (see Definition of done); no unrelated files. Ask only when truly blocked.
|
||||
- Favor perf/security; avoid re-renders, top-level side effects, unsafe HTML, env var leaks.
|
||||
## General code guidelines (Copilot)
|
||||
|
||||
## Repo summary
|
||||
- Verify before you claim. Don’t speculate.
|
||||
- Preserve unrelated code and existing structure.
|
||||
- No apologies, no “I think”, no change summaries.
|
||||
- Don’t ask to confirm info that’s already in context.
|
||||
- Prefer explicit, descriptive names and follow current style.
|
||||
- Favor performance and security; add robust error handling and safe logging.
|
||||
- Keep functions small and focused; remove duplication.
|
||||
- Replace hardcoded values with named constants when reasonable.
|
||||
- Prefer composition over deep inheritance; keep modules cohesive.
|
||||
|
||||
- Nav's design system. Yarn 4 workspaces monorepo: published packages under `@navikt/` + docs site `aksel.nav.no/` + `apps/` + `tooling/`.
|
||||
- Stack: TypeScript 6, React 19 (dev), Next.js 16, Storybook 10, Vitest 5, Oxlint 1 (with ESLint plugins as JS plugins), Stylelint 17, Prettier 3 (formatting). Node 22+, Yarn 4.18.0.
|
||||
- Published `@navikt/*` packages must stay React 17 compatible (peer `react ^17 || ^18 || ^19`).
|
||||
## Inline chat and editor completions
|
||||
|
||||
## Start and validate
|
||||
- Default tone: short and answer-first. Aim for 1–3 concise sentences before any code.
|
||||
- Show only what changes. Provide minimal diffs/snippets; don’t paste whole files or unrelated lines.
|
||||
- Keep edits scoped to a single file unless explicitly asked to touch more.
|
||||
- Use existing APIs, utilities, tokens, and patterns. Don’t add new deps unless requested.
|
||||
- Preserve style: imports, naming, types, JSX patterns, token usage, CSS layers.
|
||||
- Reference files and symbols with repo-relative paths and backticks, e.g. `@navikt/ds-react/src/...`.
|
||||
- Extract requirements into a tiny checklist and proceed. Ask a question only when truly blocked.
|
||||
- Avoid heavy formatting: bullets are OK; skip tables; use code fences only for code.
|
||||
- For tests/visual changes: add/update the closest test/story next to the changed file.
|
||||
- For Next.js: don’t mix `app/` and `pages/` in the same hierarchy; prefer server components where used; add "use client" only when required.
|
||||
- For CSS/tokens: never hard-code colors/spacing; use tokens and existing layers.
|
||||
- For performance/security: avoid unnecessary re-renders, side effects at module top-level, unsafe HTML, and leaking env vars.
|
||||
|
||||
1. `corepack enable`
|
||||
2. `corepack yarn install` (needs `NPM_AUTH_TOKEN` with `read:packages`). Without a token: `corepack yarn workspaces focus aksel @navikt/aksel-icons @navikt/ds-tokens @navikt/ds-css @navikt/ds-react @navikt/ds-tailwind @navikt/aksel @navikt/aksel-stylelint`
|
||||
3. `corepack yarn boot` (builds all public packages; rerun after `corepack yarn clean`)
|
||||
## Repository overview
|
||||
|
||||
- Run agent commands through `corepack yarn`. Order: existing root/workspace script → Yarn-local binary → add a pinned dependency only when new tooling is required.
|
||||
- Never use `npx`, `pnpx`, `pnpm dlx`, `yarn dlx`, global binaries or curl-piped installers. Public consumer docs (`npx @navikt/aksel`) are intentional; keep them.
|
||||
- If Corepack/Yarn fails because of user-level configuration, report the exact error. Never edit user-home config.
|
||||
- Under the `navikt/cplt` sandbox, `Parse error when loading ~/.yarnrc.yml` means the home rc file is unreadable (EPERM), not a repo issue. Stop retrying, report it and ask the user to fix it outside the sandbox. Don't touch the repo `.yarnrc.yml`.
|
||||
- Choosing checks: use the `aksel-local-validation` skill. Changed-file lint first, then the affected workspace, then root suites only when needed.
|
||||
Aksel is NAV’s design system: React components, CSS, design tokens, icons, tooling, and the documentation site. It’s a Yarn 4 workspaces monorepo.
|
||||
|
||||
## Definition of done
|
||||
- Languages: TypeScript, CSS, React, Next.js
|
||||
- Package manager: Yarn v4 (packageManager: `yarn@4.4.0`)
|
||||
- Build: Per-package TypeScript builds (run via root scripts)
|
||||
- Tests: Vitest + React Testing Library
|
||||
|
||||
- Behavior/visual change: update the implementation and closest existing story. Prefer Storybook play tests for UI behavior and unit tests for small utilities; combine overlapping coverage into existing stories instead of adding redundant stories.
|
||||
- New public export: sync component `index.ts`, `src/index.ts` (or `src/preview.ts`) and `package.json` exports, then `corepack yarn workspace aksel-playroom sync-imports` (root `lint` fails on stale playroom imports).
|
||||
- User-facing change to a published package: add a changeset (`add-changeset` skill) unless told otherwise.
|
||||
Related files: `package.json`, `eslint.config.js`, `biome.json`, `tsconfig.json`.
|
||||
|
||||
## Commands
|
||||
## Prerequisites
|
||||
|
||||
- `corepack yarn oxlint --deny-warnings <files...>` / `corepack yarn stylelint <files...>`
|
||||
- `corepack yarn tsc --noEmit --incremental false --project <tsconfig>` - targeted type-check
|
||||
- `corepack yarn workspace <name> <script>` - e.g. `@navikt/ds-react test` (Vitest + tsc), `website test`
|
||||
- `corepack yarn lint` / `corepack yarn test` / `corepack yarn boot` - root suites (slow; cross-workspace changes only)
|
||||
- `corepack yarn storybook` (:6006) / `corepack yarn storybook:aksel` (website examples, :6007, no secrets) / `corepack yarn storybook:test` (play tests, Playwright Firefox; Chromium when `COPILOT_CLI` is set) / `corepack yarn dev` (website, :3000, needs Sanity token)
|
||||
- Targeted play tests: `corepack yarn storybook:test <path/to/X.tests.stories.tsx>` (repo root). Run after changing play tests. Under Copilot CLI this runs Chromium; if the browser can't launch, report tests as not run with the exact command.
|
||||
- Yarn 4 via Corepack is expected by the repo (`packageManager` field).
|
||||
- Some maintenance scripts use Deno (for example `scripts/deno/changelog/createMainChangelog.ts`). Install Deno if you run those.
|
||||
- Most packages are public. If you encounter 401s from GitHub Packages in a custom setup, set `NPM_AUTH_TOKEN` with `read:packages`.
|
||||
|
||||
## Where to edit
|
||||
For limited installs (no website), you can focus core packages:
|
||||
|
||||
Path-specific rules load automatically from `.github/instructions/` (see each `applyTo`).
|
||||
```bash
|
||||
yarn workspaces focus @navikt/aksel-icons @navikt/ds-tokens @navikt/ds-css @navikt/ds-react @navikt/ds-tailwind @navikt/aksel @navikt/aksel-stylelint
|
||||
```
|
||||
|
||||
- `@navikt/core/react/src/<component>/` - components (`src/index.ts`, `src/preview.ts` + `package.json` exports = public API)
|
||||
- `@navikt/core/css/src/` - component CSS (`index.css` imports + layers)
|
||||
- `@navikt/core/tokens/src/` - design tokens · `@navikt/core/tailwind/` - Tailwind preset (generated from tokens)
|
||||
- `@navikt/aksel-icons/icons/` - source SVG + YML (`src/` generated)
|
||||
- `@navikt/aksel-stylelint/src/` - Stylelint rules + CSS class deprecations
|
||||
- `@navikt/aksel/src/` - CLI and codemods
|
||||
- `aksel.nav.no/website/` - Next.js docs site · `aksel.nav.no/sanity-studio/` - CMS schema
|
||||
- `apps/playroom/`, `apps/mcp-server/`, `apps/figma-icon-plugin/` - apps · `tooling/` - ESLint plugin, scripts, changelog, analyzer
|
||||
- `.changeset/` - release notes · Root config: `oxlint.config.mts`, `stylelint.config.mjs`, `vitest.config.mts`, `.storybook/`
|
||||
## Repository structure (short)
|
||||
|
||||
## Skills and agents
|
||||
```
|
||||
@navikt/
|
||||
├─ core/react # @navikt/ds-react – React components
|
||||
├─ core/css # @navikt/ds-css – CSS
|
||||
├─ core/tokens # @navikt/ds-tokens – tokens
|
||||
├─ core/tailwind # @navikt/ds-tailwind – Tailwind preset
|
||||
├─ aksel-icons # @navikt/aksel-icons – icons
|
||||
├─ aksel-stylelint # @navikt/aksel-stylelint – stylelint rules
|
||||
└─ aksel # @navikt/aksel – CLI/tooling
|
||||
|
||||
- `aksel-local-validation` - pick and run the right lint/type/test commands
|
||||
- `code-review` - review a branch, diff or PR for consumer-facing risk
|
||||
- `component-contract` - define and verify component behavior, state ownership and accessibility contracts
|
||||
- `add-changeset` - write `.changeset/*.md` (never run interactive `yarn changeset`)
|
||||
- `ds-component-restructure` - move/rename component files to canonical layout
|
||||
- `website-example` - add aksel.nav.no examples/templates
|
||||
aksel.nav.no/
|
||||
├─ website # Next.js documentation site
|
||||
└─ playroom # Component playground
|
||||
|
||||
## Gotchas
|
||||
examples/ # Example apps
|
||||
scripts/ # Build/maintenance scripts
|
||||
```
|
||||
|
||||
- `.yarnrc.yml`: `enableScripts: false` (dependency install scripts don't run, e.g. Playwright browsers), `npmMinimalAgeGate: 7d` (new package versions under 7 days old are rejected), `defaultSemverRangePrefix: ""` (exact versions).
|
||||
- Never hand-edit generated output: `esm/`, `cjs/`, `dist/`, `@navikt/aksel-icons/src/`, `@navikt/aksel/src/version.ts`, tokens `token_docs.js`, tailwind `tailwind.config.js`.
|
||||
- Don't run scripts that write to Sanity/Figma/releases (`update:*`, `backup`, `fetch-new:icons`, `release`, `create-version`) unless asked.
|
||||
## Build and development
|
||||
|
||||
## Coding defaults
|
||||
Setup:
|
||||
|
||||
- Small diffs. No drive-by refactors or new deps unless asked.
|
||||
- Propose the simplest maintainable solution first (explicit lists, member-name checks). No TS compiler API/AST tricks in tests (slow). Ask before escalating complexity.
|
||||
- Copy nearest existing pattern before writing new code.
|
||||
- Stable public APIs. Prefer additive props/exports.
|
||||
- `node:` imports for Node builtins. Never import from `esm/`/`cjs/` output.
|
||||
- Preserve JSDoc on public props/components when changing public APIs.
|
||||
- Reuse helpers: `cl`, `composeEventHandlers`, `useId`, `omit`, `useClientLayoutEffect`, `useEventListener`.
|
||||
- No tests for trivial logic. Only tests for complex logic that has a higher risk of breaking.
|
||||
```bash
|
||||
yarn && yarn boot
|
||||
```
|
||||
|
||||
Useful scripts (root `package.json`):
|
||||
|
||||
```bash
|
||||
yarn boot # Build all workspaces (topological)
|
||||
yarn storybook # Storybook dev (port 6006)
|
||||
yarn storybook:aksel # Storybook with website config (port 6007)
|
||||
yarn dev # Run documentation website
|
||||
yarn test # Run workspace tests
|
||||
yarn lint # ESLint + Stylelint (full install recommended)
|
||||
yarn clean # Clean generated files
|
||||
yarn changeset # Create a changeset
|
||||
yarn changelog # Generate main changelog (Deno)
|
||||
yarn biome:lint # Optional extra linting with Biome
|
||||
```
|
||||
|
||||
Note: `yarn lint` runs across the repo and may require full install (website ESLint plugins).
|
||||
|
||||
## CI and validation
|
||||
|
||||
- Pre-commit (Husky): `lint-staged`, plus internal version sync checks.
|
||||
- CI typically runs: lint, unit tests, example/website checks, and Chromatic when applicable.
|
||||
|
||||
Common failure causes:
|
||||
|
||||
- Missing dependencies in a focused install when running repo-wide scripts
|
||||
- TypeScript errors in dependent workspaces
|
||||
- Stylelint token checks failing due to missing token imports
|
||||
|
||||
Quality gates to check locally before opening a PR:
|
||||
|
||||
- Build: `yarn boot` succeeds without TS errors.
|
||||
- Lint/format: `yarn lint` (and optional `yarn biome:lint`) passes.
|
||||
- Tests: `yarn test` for changed workspaces are green.
|
||||
- Storybook/website (when UI changes): boots locally without errors.
|
||||
|
||||
## Tests
|
||||
|
||||
- Framework: Vitest (jsdom where needed)
|
||||
- Website config: `aksel.nav.no/website/config/vitest.config.ts`
|
||||
- Test locations: `**/__tests__/**` and `**/*.test.*`
|
||||
- Add tests for new behavior and keep existing tests passing
|
||||
- Prefer small, focused tests. Cover the happy path and 1–2 edge cases.
|
||||
- Use Testing Library queries that reflect user intent (role/name), avoid brittle selectors.
|
||||
|
||||
## Linting and formatting
|
||||
|
||||
- ESLint config lives at root: `eslint.config.js` (React, Next.js, Storybook, Testing Library, custom rules)
|
||||
- Stylelint is configured in root `package.json` with `@navikt/aksel-stylelint`
|
||||
- Tokens are validated via Stylelint and `csstools/value-no-unknown-custom-properties`
|
||||
- Biome config: `biome.json` (optional extra lint/format)
|
||||
- Prettier uses import-sort; website adds Tailwind plugin via override
|
||||
- Avoid disabling lint rules unless necessary; if you must, comment why.
|
||||
|
||||
## Packages and exports
|
||||
|
||||
- Synchronized versions: `@navikt/ds-react`, `@navikt/ds-css`, `@navikt/aksel-icons`, `@navikt/ds-tokens`, `@navikt/ds-tailwind`
|
||||
- Typical package layout:
|
||||
|
||||
```
|
||||
src/
|
||||
├─ index.ts # Package exports
|
||||
└─ <component>/
|
||||
├─ index.ts # Component exports
|
||||
├─ <component>.tsx # Implementation
|
||||
└─ <component>.stories.tsx
|
||||
```
|
||||
|
||||
When adding/editing components in `@navikt/react`:
|
||||
|
||||
- Update exports in `src/index.ts` and per-component `index.ts`
|
||||
- Add/maintain stories and tests next to the component
|
||||
- Follow existing props, theming, and token patterns
|
||||
- Keep props stable; prefer additive changes. Deprecate before removal when possible.
|
||||
- Ensure tree-shakeability; avoid side effects at module top-level.
|
||||
|
||||
## Common workflows
|
||||
|
||||
Add a new component
|
||||
|
||||
1. Implement under `@navikt/react/src/...`
|
||||
2. Add stories and tests
|
||||
3. Export from the package `index.ts`
|
||||
4. Run `yarn boot` and fix types/lint
|
||||
5. Create a changeset (`yarn changeset`)
|
||||
|
||||
Component change checklist (quick):
|
||||
|
||||
- [ ] Implementation updated under `@navikt/react/src/...`
|
||||
- [ ] Exports updated (`src/index.ts` and component `index.ts`)
|
||||
- [ ] Stories updated/added and render without errors
|
||||
- [ ] Tests updated/added and passing (Vitest)
|
||||
- [ ] Tokens/CSS use existing variables and layers (no hard-coded values)
|
||||
- [ ] Changeset added if user-facing (version bump and notes)
|
||||
|
||||
Modify an existing component
|
||||
|
||||
1. Keep the public API stable unless explicitly requested
|
||||
2. Update tests if behavior changes; update stories if visuals change
|
||||
3. Add a changeset for user-facing changes
|
||||
|
||||
Troubleshooting
|
||||
|
||||
1. `yarn clean && yarn boot`
|
||||
2. Check workspace dependency build order/issues
|
||||
3. If focusing workspaces, avoid running repo-wide scripts that depend on the website
|
||||
|
||||
## Darkside theming and tokens
|
||||
|
||||
- Use design tokens and CSS layers; avoid hard-coded colors/spacing.
|
||||
- Website imports Darkside CSS and tokens, see:
|
||||
- `aksel.nav.no/website/pages/global.css` (imports `@navikt/ds-css/darkside`)
|
||||
- `aksel.nav.no/website/pages/_app.tsx` (imports `@navikt/ds-tokens/darkside-css`)
|
||||
- Stylelint enforces token usage; unknown/missing custom properties will fail lint.
|
||||
|
||||
## Website (Next.js)
|
||||
|
||||
- The website uses Next.js 15 and contains both `app/` and `pages/`. Place new files in the router that matches the surrounding code.
|
||||
- Prefer server components where already used; mark client components with `"use client"` only when needed.
|
||||
- Use Next Image, metadata APIs, and established utilities already in `aksel.nav.no/website`.
|
||||
- Logging uses Next logger/pino; follow patterns in `aksel.nav.no/website` (see `next-logger.config.js`).
|
||||
- Use existing route conventions and file organization; don’t mix `app/` and `pages/` in the same hierarchy.
|
||||
- For Tailwind, prefer the shared preset `@navikt/ds-tailwind` and follow website `tailwind.config.js` patterns.
|
||||
|
||||
## Versioning and releases
|
||||
|
||||
- Use Changesets: `yarn changeset` and the root scripts:
|
||||
- `yarn create-version` (applies versions, updates changelog, reinstalls)
|
||||
- `yarn release` (boots, docgen, publish, git release helper)
|
||||
- Main changelog script: `scripts/deno/changelog/createMainChangelog.ts`
|
||||
|
||||
## Answer snippets (for Copilot)
|
||||
|
||||
- Build: “Run yarn boot” (see root `package.json`)
|
||||
- Storybook: “Run yarn storybook” (or `yarn storybook:aksel` for the website config)
|
||||
- Lint/format: “Run yarn lint” (full install recommended) and optional `yarn biome:lint`
|
||||
- Versioning: “Run yarn changeset, then yarn create-version”
|
||||
- Theming: “Import @navikt/ds-css/darkside in global CSS and ensure tokens via @navikt/ds-tokens/darkside-css”
|
||||
|
||||
## Code reviews (for Copilot)
|
||||
|
||||
- Summarize the pull-request on maximum two sentences
|
||||
- Avoid listing all changes, focus on the most important ones
|
||||
- Check the description against the actual changes
|
||||
- Ensure the PR title is descriptive and follows the conventional commit format
|
||||
- Look for: correctness, style, performance, security, tests, documentation
|
||||
- Check: existing patterns, exports, tokens, CSS layers
|
||||
- Make sure import paths are correct, updated and use existing aliases where possible
|
||||
- When components are changed, ensure stories and tests are updated accordingly
|
||||
- When components are changed, make sure related examples for website and playroom are updated
|
||||
- Make sure changes made to packages under `@navikt/` has a related changeset where relevant
|
||||
|
||||
### Extra review tips
|
||||
|
||||
- Watch for unnecessary bundle impact (large deps, dynamic imports, side effects).
|
||||
- Confirm a11y basics: semantic elements, labels, focus order, keyboard support.
|
||||
- Check security footguns: unsafe HTML, unescaped data, leaking env vars, weak CSP assumptions.
|
||||
+17
-42
@@ -3,11 +3,15 @@ registries:
|
||||
npm-github:
|
||||
type: npm-registry
|
||||
url: https://npm.pkg.github.com
|
||||
scope: "@navikt"
|
||||
token: ${{ secrets.READER_TOKEN }}
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
cooldown:
|
||||
default-days: 7
|
||||
semver-major-days: 30
|
||||
directory: /
|
||||
exclude-paths:
|
||||
- "examples/**"
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
@@ -16,48 +20,28 @@ updates:
|
||||
- npm-github
|
||||
groups:
|
||||
sanity:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "sanity"
|
||||
- "sanity-*"
|
||||
- "@sanity/*"
|
||||
- "@portabletext/*"
|
||||
- "@sanity/vision"
|
||||
- "@sanity/schema"
|
||||
eslint:
|
||||
patterns:
|
||||
- "eslint"
|
||||
- "@eslint/*"
|
||||
react:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "react"
|
||||
- "react-dom"
|
||||
storybook:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "*storybook*"
|
||||
vitest:
|
||||
applies-to: version-updates
|
||||
# Group all other minor and patch updates to reduce noise
|
||||
dependencies:
|
||||
patterns:
|
||||
- "vitest"
|
||||
- "@vitest/*"
|
||||
# Security updates ignore version-update groups, so mirror them to keep lockstep packages together.
|
||||
sanity-security:
|
||||
applies-to: security-updates
|
||||
patterns:
|
||||
- "sanity"
|
||||
- "sanity-*"
|
||||
- "@sanity/*"
|
||||
- "@portabletext/*"
|
||||
react-security:
|
||||
applies-to: security-updates
|
||||
patterns:
|
||||
- "react"
|
||||
- "react-dom"
|
||||
storybook-security:
|
||||
applies-to: security-updates
|
||||
patterns:
|
||||
- "*storybook*"
|
||||
vitest-security:
|
||||
applies-to: security-updates
|
||||
patterns:
|
||||
- "vitest"
|
||||
- "@vitest/*"
|
||||
- "*"
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/"
|
||||
@@ -71,12 +55,3 @@ updates:
|
||||
actions:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
- package-ecosystem: docker
|
||||
directories:
|
||||
- "/aksel.nav.no"
|
||||
- "/apps/*"
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: "04:00"
|
||||
@@ -1,21 +0,0 @@
|
||||
---
|
||||
description: "Writing changeset files for versioning public @navikt packages."
|
||||
applyTo: ".changeset/**"
|
||||
---
|
||||
|
||||
# Changesets
|
||||
|
||||
`yarn changeset` is interactive. Agents write `.changeset/<kebab-slug>.md` directly (see the `add-changeset` skill):
|
||||
|
||||
```md
|
||||
---
|
||||
"@navikt/ds-react": patch
|
||||
---
|
||||
|
||||
Component: Short, user-facing description of the change
|
||||
```
|
||||
|
||||
- Publishable packages (versioned together via `fixed` in `config.json`): `@navikt/ds-react`, `@navikt/ds-css`, `@navikt/ds-tokens`, `@navikt/ds-tailwind`, `@navikt/aksel-icons`, `@navikt/aksel`, `@navikt/aksel-stylelint`. List only the packages actually changed.
|
||||
- Everything in `config.json` `ignore` (website, playroom, mcp, tooling, sanity) never gets a changeset.
|
||||
- Summary format: `<Component>: <what changed for consumers>`. It ends up in the public changelog, so write for consumers, not reviewers.
|
||||
- Don't edit `config.json`, `CHANGELOG.md` files or package versions. The release workflow does that.
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
description: "JS/TS conventions not enforced by Oxlint: loops, export style and placement."
|
||||
applyTo: "**/*.ts,**/*.tsx,**/*.js,**/*.mts,**/*.mjs"
|
||||
---
|
||||
|
||||
# JS/TS conventions
|
||||
|
||||
Formatting, import order and `import type` are handled by Prettier. Don't hand-tune them.
|
||||
|
||||
- Use `for...of` instead of `Array.forEach`. It supports `break`/`continue`/`await` and reads top-down.
|
||||
- Use named exports only. Keep type exports separate from value exports (`export { X }` / `export type { XProps }`).
|
||||
- Group exports at the bottom of the file, values first, then types. `react-docgen-typescript` (`docgen:meta`) depends on this pattern.
|
||||
- Exceptions to default exports: `*.stories.tsx` (CSF meta default export, kept at the top), and Next.js pages/website examples that the framework requires.
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
description: "@navikt/aksel CLI and jscodeshift codemods: transforms, fixtures, migration registry."
|
||||
applyTo: "@navikt/aksel/**"
|
||||
---
|
||||
|
||||
# @navikt/aksel CLI and codemods
|
||||
|
||||
- Transforms live in `src/codemod/transforms/v<major>.0.0/<name>/<name>.ts`. Tests live in the sibling `tests/` folder.
|
||||
- Every transform is registered in `src/migrations-config.ts` (`value`, `path` without extension, `description`, `ignoredExtensions`). `value` must be unique and is the public CLI name (`v8-accordion-variant`).
|
||||
- Test with fixtures: `tests/<fixture>.input.js` → `tests/<fixture>.output.js`, run via `check(__dirname, { fixture, migration })` from `src/codemod/utils/check.ts`. Output is compared after Prettier, so whitespace doesn't matter.
|
||||
- Reuse helpers in `src/codemod/utils/` (imports, props, JSX values, token translation) before writing new AST code.
|
||||
- Fixtures (`*.input.*`, `*.output.*`) are excluded from Oxlint and TypeScript. Don't "fix" them to match lint rules.
|
||||
- `src/version.ts` is generated by `build`. Don't edit it.
|
||||
- `tests/migrations.test.ts` checks `dist/`. Run `corepack yarn workspace @navikt/aksel build` before `test`, or new transforms fail with "transform-path" errors.
|
||||
- Consumer docs (`README.md`, website) show `npx @navikt/aksel ...` on purpose. Keep them.
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
description: "Editing @navikt/ds-css component styles: file setup, layers, selectors, tokens, deprecations."
|
||||
applyTo: "@navikt/core/css/src/**"
|
||||
---
|
||||
|
||||
# ds-css
|
||||
|
||||
- One file per component in `src/` (`badge.css`). A new file must be `@import`ed in `src/index.css` with the right `layer(...)`, otherwise it isn't bundled. Most components use `aksel.components.modules`; form controls go through `form/index.css`.
|
||||
- Selectors use the `.aksel-<component>` prefix and BEM-ish `__part` / `--modifier`. State goes through `data-*` / ARIA attribute selectors, not extra modifier classes.
|
||||
- Use tokens only: public `--ax-*`, component-internal `--__axc-*`. No hardcoded colors, spacing or radii. Stylelint flags unknown custom properties.
|
||||
- Colors follow `data-color` (see nearby components). Don't add per-variant color selectors.
|
||||
- Focus uses `:focus-visible` (outline + `outline-offset`). Check `forced-colors`, print and dark theme in Storybook.
|
||||
- `prefers-reduced-motion` is handled globally in `baseline/baseline.css`. Don't duplicate it per component.
|
||||
- `baseline/tokens.css` re-exports `@navikt/ds-tokens` build output. Edit tokens in `@navikt/core/tokens/src`, not here.
|
||||
- Removing or renaming a class is breaking for consumers (for allready published components. Ignore for new components). Add it to `@navikt/aksel-stylelint/src/deprecations.ts`.
|
||||
@@ -1,35 +0,0 @@
|
||||
---
|
||||
description: "Editing or adding @navikt/ds-react components, hooks and their public exports."
|
||||
applyTo: "@navikt/core/react/src/**"
|
||||
---
|
||||
|
||||
# ds-react components
|
||||
|
||||
Read the whole component folder (`index.ts`, source, `.meta.ts`, stories, tests) before editing. Moving or renaming files: use the `ds-component-restructure` skill.
|
||||
|
||||
For changes to component state, callbacks, async behavior, composition, semantics, keyboard or focus behavior, use the [component-contract skill](../skills/component-contract/SKILL.md).
|
||||
|
||||
## Compatibility (why: peer range is `react ^17 || ^18 || ^19`)
|
||||
|
||||
- No React 18/19-only APIs. Oxlint `no-restricted-imports` blocks e.g. `React.useId`. Use the internal `useId`, `useClientLayoutEffect`, etc. from `utils-external/`.
|
||||
- Import React explicitly in TSX (`import React from "react"`). `tsconfig` uses classic `jsx: "react"`.
|
||||
- Component `index.ts` starts with `"use client"`.
|
||||
|
||||
## Component patterns
|
||||
|
||||
- Preserve `forwardRef`, `className` merged via `cl("aksel-<name>", className)`, `...rest` spread, and `as`/`OverridableComponent` where they already exist.
|
||||
- Compound components: `const X = Object.assign(XRoot, { Item: XItem })`. Never cast with `as XComponent`, because `docgen:meta` can't extract props from a cast.
|
||||
- Colors: `data-color?: AkselColor`, not new `variant` color values.
|
||||
- User-facing strings come from `useI18n("<Component>")` with keys in `utils/i18n/locales/{nb,nn,en}.ts`. Add the key to all three.
|
||||
- Public props and components need JSDoc. Component JSDoc sits above the exported value, with `@see 🏷️ {@link XProps}` and an `@example`. Add `@default` when the default isn't obvious.
|
||||
|
||||
## Public API surface (breaking = major changeset)
|
||||
|
||||
- Stable component: export it from component `index.ts`, add it to `src/index.ts`, and add a `"./<Name>"` entry to `package.json` `exports` (both `import` and `require`).
|
||||
- Preview/beta component: export from `src/preview.ts` plus `"./PREVIEW/<Name>"` in `package.json` instead of `src/index.ts`.
|
||||
- Standalone components have `<Component>.meta.ts` (see `accordion/Accordion.meta.ts`). Sub-components don't.
|
||||
- Changing exports: run `corepack yarn workspace aksel-playroom sync-imports`, or root `lint` fails.
|
||||
|
||||
## Validate
|
||||
|
||||
`corepack yarn workspace @navikt/ds-react test` (Vitest + `tsc` type-check). Add `build` when exports or types change.
|
||||
@@ -1,14 +0,0 @@
|
||||
---
|
||||
description: "@navikt/aksel-icons: adding/updating SVG icons and metadata; generated React components."
|
||||
applyTo: "@navikt/aksel-icons/**"
|
||||
---
|
||||
|
||||
# aksel-icons
|
||||
|
||||
- Source: `icons/<Name>.svg` + `icons/<Name>.yml`. Every SVG has a matching YML and vice versa (checked by `__tests__/validate-files.test.ts`). Most icons also have a `<Name>Fill` variant.
|
||||
- YML shape: copy a sibling, e.g. `icons/Airplane.yml` (`name`, `category`, `sub_category`, `keywords` (nb + en), `variant: Stroke|Fill`, `created_at: DD.MM.YYYY`).
|
||||
- SVG root: `width="24" height="24" viewBox="0 0 24 24" fill="none"`. Shapes use `fill="#202733"` (becomes `currentColor` at build) and `stroke-width` 1.5 if any. `__tests__/validate-svg*.test.ts` enforces this. Copy a sibling SVG.
|
||||
- `src/` React components, `dist/` and metadata are generated by `build` (SVGR). Never edit `src/` by hand.
|
||||
- `core-icons/` is only used for the downloadable zip. `fetch-new:icons` pulls from Figma and needs a token. Don't run it unless asked.
|
||||
- New icon = minor changeset for `@navikt/aksel-icons` (e.g. `Icons: New icons Timeline, TimelineFill`).
|
||||
- Validate: `corepack yarn workspace @navikt/aksel-icons test`. Run `build` when checking generated output.
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
description: "Writing Vitest unit tests and Storybook stories/play tests."
|
||||
applyTo: "**/*.test.ts,**/*.test.tsx,**/*.stories.tsx"
|
||||
---
|
||||
|
||||
# Tests and stories
|
||||
|
||||
- User flows: use a story `play` function in `<Component>.tests.stories.tsx`. Pure logic and hooks: use Vitest `*.test.ts(x)` next to the source file.
|
||||
- `corepack yarn storybook:test` runs every story that has a `play` function (Storybook auto-tags them `play-fn`, see root `vitest.config.mts`). To opt out, use `tags: ["skip-test"]` with a reason.
|
||||
- Play-test files set `parameters: { chromatic: { disable: true } }`. Visual stories feed Chromatic; use `renderStoriesForChromatic` from `utils/` when adding visual variants.
|
||||
- Import from `@storybook/react-vite` and `storybook/test`, not `@storybook/react` or `@storybook/test`.
|
||||
- Query by role and accessible name (`getByRole("button", { name })`). Avoid snapshots unless nearby tests already use them.
|
||||
- Cover the main path and meaningful edge cases in the nearest existing story/play-test where they belong. Prefer combining overlapping cases over adding redundant stories; add a separate story for a distinct user-visible scenario.
|
||||
- A behavior change updates the nearest story and test in the same change. Use unit tests for small utilities and pure logic; use Storybook play tests for UI behavior.
|
||||
- Describe behavioral coverage as starting state → action → observable result. Assert relevant intermediate states so a no-op cannot pass as success; pair a negative/blocked case with a successful control when it helps distinguish the behavior.
|
||||
- Match assertions to timing: assert synchronous outcomes immediately and wait only for behavior that is contractually eventual. When changing ARIA relationships, verify the referenced element and resulting relationship, not just that an attribute exists.
|
||||
- Before removing or consolidating tests, map each meaningful old scenario/assertion to its replacement story or test. Keep coverage that still represents a supported behavior; explain when a scenario is intentionally obsolete.
|
||||
- For state, accessibility, keyboard or focus behavior, use the [component-contract skill](../skills/component-contract/SKILL.md) to identify the contract and evidence to preserve.
|
||||
- ds-react unit tests run with `TZ=UTC`. Date logic must not depend on the local timezone.
|
||||
- Browser tests need Playwright Firefox (Chromium under Copilot CLI, see `vitest.config.mts`). Yarn has `enableScripts: false`, so if the browser is missing, report it instead of installing globally.
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
description: "@navikt/ds-tokens and @navikt/ds-tailwind: token sources, generated output, build order."
|
||||
applyTo: "@navikt/core/tokens/**,@navikt/core/tailwind/**"
|
||||
---
|
||||
|
||||
# Tokens and Tailwind
|
||||
|
||||
- Token source of truth: `@navikt/core/tokens/src/tokens/**` (TypeScript), built with Style Dictionary (`src/index.ts`). Generated output: `dist/`, `token_docs.js`, and in tailwind `tailwind.config.js` and `tailwind4.css`. Never edit generated output; they're untracked.
|
||||
- Public CSS variables are `--ax-*`. Renaming or removing a token is breaking: it needs a major changeset plus a token migration in `@navikt/aksel` (see `src/codemod/v8-tokens/`) when consumers must migrate.
|
||||
- Light and dark themes are both generated. Check both when changing semantic color tokens.
|
||||
- Tailwind is built from `@navikt/ds-tokens/js`, so build tokens first: `corepack yarn workspace @navikt/ds-tokens build`, then `corepack yarn workspace @navikt/ds-tailwind build`.
|
||||
- Tests: `corepack yarn workspace @navikt/ds-tokens test` and `corepack yarn workspace @navikt/ds-tailwind test`.
|
||||
- `build` also generates the Figma config/plugin (`src/figma`). Changes there affect design tooling. Mention it in the PR.
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
description: "aksel.nav.no Next.js website: routers, examples/templates, Sanity data."
|
||||
applyTo: "aksel.nav.no/website/**"
|
||||
---
|
||||
|
||||
# Website (aksel.nav.no)
|
||||
|
||||
- Both routers are in use: `app/` (App Router, `cacheComponents: true` in `next.config.ts`) and `pages/` (examples, templates, legacy). Stay in the router already used nearby. Don't move routes between them.
|
||||
- In `app/`, dynamic data needs Suspense or `"use cache"`, following nearby code. Cache Components errors at build time are real failures.
|
||||
- For website build, runtime, or performance issues, use the `website-debugging` skill to establish the cause before changing code.
|
||||
- `pages/eksempler/**` and `pages/templates/**` are public code snippets. Use the `website-example` skill. Oxlint rules `aksel-local/{import-check,comment-check,args-check}` enforce their shape.
|
||||
- Reuse `@/` utils and components nearby before adding abstractions.
|
||||
- Examples/templates: `corepack yarn storybook:aksel` (no secrets). `corepack yarn dev` only for app behavior; it needs `SANITY_READ_NO_DRAFTS` and `NPM_AUTH_TOKEN`.
|
||||
- Never run `update:*`, `backup` or other Sanity-writing scripts unless asked. They write to the CMS.
|
||||
- Tests: `corepack yarn workspace website test`. Examples: `corepack yarn workspace website validate:eksempler`.
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"lspServers": {
|
||||
"typescript": {
|
||||
"command": "node",
|
||||
"args": [
|
||||
"./node_modules/typescript-language-server/lib/cli.mjs",
|
||||
"--stdio"
|
||||
],
|
||||
"fileExtensions": {
|
||||
".ts": "typescript",
|
||||
".tsx": "typescriptreact",
|
||||
".js": "javascript",
|
||||
".jsx": "javascriptreact",
|
||||
".mjs": "javascript",
|
||||
".cjs": "javascript",
|
||||
".mts": "typescript",
|
||||
".cts": "typescript"
|
||||
},
|
||||
"rootUri": "."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
---
|
||||
name: add-changeset
|
||||
description: >
|
||||
Create a Changesets entry for a user-facing change to a published Aksel
|
||||
package without the interactive `yarn changeset` prompt. Use when a change
|
||||
touches @navikt/ds-react, ds-css, ds-tokens, ds-tailwind, aksel-icons,
|
||||
@navikt/aksel or aksel-stylelint and needs a version bump or changelog entry,
|
||||
or when asked to "add a changeset".
|
||||
argument-hint: "Package(s) and a one-line summary of the change"
|
||||
---
|
||||
|
||||
# Add changeset
|
||||
|
||||
`yarn changeset` is interactive and will hang an agent. Write the file directly.
|
||||
|
||||
## 1. Decide if a changeset is needed
|
||||
|
||||
Needed: any consumer-visible change in a published package: behavior, props, CSS output, tokens, icons, CLI/codemods, types, or bug fixes.
|
||||
|
||||
Not needed:
|
||||
|
||||
- Changes only in ignored workspaces (`.changeset/config.json` → `ignore`: website, sanity-studio, playroom, mcp, tooling, figma plugin).
|
||||
- Stories, tests, internal refactors with identical output, docs and repo tooling.
|
||||
|
||||
## 2. Pick the bump
|
||||
|
||||
| Bump | When |
|
||||
| ------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `patch` | Bug fix, style fix, internal change with visible effect, new icon keyword |
|
||||
| `minor` | New component, prop, export, token, icon or codemod; new preview component |
|
||||
| `major` | Removed/renamed prop, export, CSS class or token, or changed default. Only when explicitly agreed: needs a codemod or migration notes |
|
||||
|
||||
The packages in `config.json` `fixed` release together, so one bump moves all of them to the same version. Still list only the packages whose code changed.
|
||||
|
||||
## 3. Write the file
|
||||
|
||||
Path: `.changeset/<three-random-lowercase-words>.md` (e.g. `brave-owls-sing`). The slug must not already exist.
|
||||
|
||||
```md
|
||||
---
|
||||
"@navikt/ds-react": minor
|
||||
"@navikt/ds-css": minor
|
||||
---
|
||||
|
||||
Lookup: New component `Lookup`
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- The summary starts with `<Component or area>:` (e.g. `Button:`, `Tokens:`, `Icons:`, `CLI:`), then what changed **for consumers**. One changeset per logical change.
|
||||
- Optional gitmoji after the colon is fine (`:tada:` for new components), matching nearby entries.
|
||||
- Don't edit `CHANGELOG.md`, package versions or `config.json`.
|
||||
|
||||
## Done when
|
||||
|
||||
The file exists, its frontmatter lists only changed published packages with valid bumps, and Prettier leaves it unchanged (`./node_modules/.bin/prettier --check .changeset/<file>.md`, or `corepack yarn prettier --check ...`).
|
||||
@@ -1,108 +0,0 @@
|
||||
---
|
||||
name: aksel-local-validation
|
||||
description: >
|
||||
Select and run deterministic local validation for changes in the Aksel
|
||||
monorepo. Use when linting, type-checking, testing, building, formatting, or
|
||||
deciding which Yarn workspace commands cover changed files.
|
||||
argument-hint: "Changed files or feature area to validate"
|
||||
---
|
||||
|
||||
# Aksel local validation
|
||||
|
||||
Validate the smallest relevant surface with repository-pinned tools. Expand only when the result or dependency graph requires it.
|
||||
|
||||
## Hard rules
|
||||
|
||||
- Run repository commands through `corepack yarn`.
|
||||
- Prefer an existing root or workspace script. Otherwise use a binary already pinned in the root/workspace dependency graph.
|
||||
- Never use `npx`, `pnpx`, `pnpm dlx`, `yarn dlx`, a global binary, or a curl-piped installer.
|
||||
- Do not add a dependency only to run a one-off check. If the task truly needs new tooling, add and lock it explicitly.
|
||||
- Public consumer docs may intentionally show `npx @navikt/aksel`. Preserve those examples unless the task changes consumer setup.
|
||||
- If Corepack/Yarn reports a user-level configuration error, stop and report it. Never edit configuration outside the repository.
|
||||
- Do not claim type safety from Oxlint or tests. Run a TypeScript-aware command when TypeScript behavior changed.
|
||||
|
||||
## 1. Determine affected projects
|
||||
|
||||
Start from changed files, not the whole repository:
|
||||
|
||||
```sh
|
||||
git diff --name-only --diff-filter=ACMR
|
||||
git diff --cached --name-only --diff-filter=ACMR
|
||||
git ls-files --others --exclude-standard
|
||||
```
|
||||
|
||||
For each file, read its nearest `package.json`, `tsconfig.json`, test, story, and export surface before choosing commands.
|
||||
|
||||
| Path | Workspace | Preferred package checks |
|
||||
| ------------------------------- | -------------------------- | ---------------------------------------------------------------------------------- |
|
||||
| `@navikt/core/react/**` | `@navikt/ds-react` | `test` (Vitest + `tsc` type-check); `build` for export/type changes |
|
||||
| `@navikt/core/css/**` | `@navikt/ds-css` | no `test` script: targeted `stylelint`, then `build` for imports/layers |
|
||||
| `@navikt/core/tokens/**` | `@navikt/ds-tokens` | `test`, then `build` for generated output |
|
||||
| `@navikt/core/tailwind/**` | `@navikt/ds-tailwind` | build `@navikt/ds-tokens` first, then `test` and `build` |
|
||||
| `@navikt/aksel-icons/**` | `@navikt/aksel-icons` | `test`; `build` for generation/export changes |
|
||||
| `@navikt/aksel-stylelint/**` | `@navikt/aksel-stylelint` | `test` |
|
||||
| `@navikt/aksel/**` | `@navikt/aksel` | `build` **before** `test` (`migrations.test.ts` reads `dist/`) |
|
||||
| `aksel.nav.no/website/**` | `website` | `test`; `validate:eksempler` for examples/templates; avoid `build` (needs secrets) |
|
||||
| `aksel.nav.no/sanity-studio/**` | `aksel-sanity-studio` | `type-check`, then `test` when behavior changes |
|
||||
| `apps/playroom/**` | `aksel-playroom` | no `test` script: `sync-imports` when ds-react exports change |
|
||||
| `apps/mcp-server/**` | `aksel-mcp` | `test`, then `build` |
|
||||
| `apps/figma-icon-plugin/**` | `aksel-icons-figma-plugin` | no `test` script: `build:plugin` |
|
||||
| `tooling/changelog/**` | `aksel-changelog` | `typecheck` |
|
||||
| `tooling/analyzer/**` | `aksel-analyzer` | `test` |
|
||||
|
||||
Read the current manifest before running a table command. A manifest is source of truth if scripts change.
|
||||
|
||||
## 2. Run changed-file lint
|
||||
|
||||
Group only matching files:
|
||||
|
||||
```sh
|
||||
corepack yarn oxlint --deny-warnings <changed .ts/.tsx/.js/.jsx files>
|
||||
corepack yarn stylelint <changed .css files>
|
||||
```
|
||||
|
||||
Do not pass unsupported file types to make a command appear comprehensive. Documentation-only changes need no code validation unless a documentation check exists.
|
||||
|
||||
## 3. Run TypeScript validation
|
||||
|
||||
Use nearest applicable project:
|
||||
|
||||
```sh
|
||||
corepack yarn tsc --noEmit --incremental false --project path/to/tsconfig.json
|
||||
```
|
||||
|
||||
`--incremental false` prevents validation from creating `.tsbuildinfo` files. For a composite project, use its workspace build/type-check script instead.
|
||||
|
||||
Prefer the workspace's own `type-check`, `typecheck`, or `build` script instead when it generates inputs, uses a build-specific tsconfig, or performs required transforms:
|
||||
|
||||
```sh
|
||||
corepack yarn workspace <workspace> type-check
|
||||
corepack yarn workspace <workspace> typecheck
|
||||
corepack yarn workspace <workspace> build
|
||||
```
|
||||
|
||||
If no suitable TypeScript command exists, report the gap. Do not substitute lint as a type-check.
|
||||
|
||||
## 4. Run behavior tests
|
||||
|
||||
Run the closest test file or workspace test command supported by that package. For visual or interaction changes, include the nearest story and Storybook browser test required by repository instructions.
|
||||
|
||||
Examples:
|
||||
|
||||
```sh
|
||||
corepack yarn workspace @navikt/ds-react test
|
||||
corepack yarn workspace website test
|
||||
corepack yarn workspace aksel-mcp test
|
||||
```
|
||||
|
||||
## 5. Escalate deliberately
|
||||
|
||||
Escalation order:
|
||||
|
||||
1. changed-file lint;
|
||||
2. nearest TypeScript project;
|
||||
3. focused test or affected workspace script;
|
||||
4. dependent workspace build/test;
|
||||
5. root `lint`, `test`, or `boot` only for cross-workspace changes, shared configuration, generated public output, or a failure that requires broader evidence.
|
||||
|
||||
Stop when every behavior and type surface touched by the change has a passing check. Report pre-existing or environment failures exactly; do not hide them with a weaker fallback.
|
||||
@@ -1,143 +0,0 @@
|
||||
---
|
||||
name: code-review
|
||||
description: "Review Aksel changes or pull requests for consumer-breaking API changes, React 17/SSR compatibility, accessibility, export wiring, security, tests and changesets. Use when reviewing a branch, diff or pull request."
|
||||
argument-hint: "PR number, branch or files to review"
|
||||
---
|
||||
|
||||
# Aksel code review
|
||||
|
||||
## Repository context
|
||||
|
||||
- Aksel is Nav's design system. The published `@navikt/*` packages are used across Nav on React 17–19, with SSR and CSR, in light and dark themes.
|
||||
- One bad release breaks many teams at once. Public API, accessibility and compatibility matter most.
|
||||
- Report findings. Don't edit code unless asked.
|
||||
|
||||
## Process
|
||||
|
||||
1. **Scope**: identify what the user wants reviewed.
|
||||
- PR: get `gh pr diff <n>` and the PR description. Note the stated goal.
|
||||
- Branch: compare against its stated base (for example, `git diff origin/main...HEAD`).
|
||||
- Current worktree or "my changes": inspect staged, unstaged, and relevant untracked files. Do not substitute the committed branch diff for worktree changes.
|
||||
|
||||
```sh
|
||||
git status --short
|
||||
git diff
|
||||
git diff --cached
|
||||
git ls-files --others --exclude-standard
|
||||
```
|
||||
|
||||
Read in-scope untracked files too. If the requested review target is unclear, ask before choosing a scope.
|
||||
|
||||
2. **Read**: for every changed file, read the surrounding code, component `index.ts`, stories, tests and the matching `.github/instructions/*.instructions.md`. Never review from the diff alone.
|
||||
3. **Check**: run the smallest validation that covers the change (`aksel-local-validation` skill). Tool output replaces style comments.
|
||||
4. **Analyze** against the checklist below.
|
||||
5. **Report** in the output format.
|
||||
|
||||
For changes to component state, callbacks, async behavior, composition, semantics, keyboard or focus behavior, use the [component-contract skill](../component-contract/SKILL.md) to trace the affected contract and identify observable evidence.
|
||||
|
||||
## Priorities
|
||||
|
||||
- 🔴 **Blocker**: must fix before merge. Breaks consumers, regresses accessibility, security issue or wrong behavior.
|
||||
- 🟡 **Suggestion**: should fix. Missing test, docs or changeset, maintainability risk, over-editing.
|
||||
- 💭 **Nit**: optional. Leave it out when in doubt.
|
||||
|
||||
Skip anything Oxlint, Stylelint, Prettier or TypeScript already catches. No praise-only or speculative comments. Every finding says **why** it matters and what to do.
|
||||
|
||||
## Checklist
|
||||
|
||||
### Public API (🔴)
|
||||
|
||||
- Removed or renamed prop, export, CSS class, token or CLI command, or a changed default, on something already published → needs a `major` changeset, a deprecation path, or a revert.
|
||||
- New props and exports follow nearby naming and defaults. Prefer additive changes. Colors go through `data-color`, not new `variant` values.
|
||||
- Public props and components have JSDoc. `@default` and `@example` match behavior.
|
||||
|
||||
### Compatibility (🔴)
|
||||
|
||||
- No React 18/19-only APIs in `@navikt/*` (peer `react ^17 || ^18 || ^19`). Use the helpers in `@navikt/core/react/src/utils-external/` (`useId`, `useClientLayoutEffect`).
|
||||
- SSR-safe: no `window`, `document` or `localStorage` at module top level or during render.
|
||||
- No new runtime dependency in a published package without a clear reason (bundle size, consumer installs).
|
||||
|
||||
### Accessibility (🔴)
|
||||
|
||||
- Semantic HTML first. Correct roles, accessible names and states.
|
||||
- Full keyboard support, logical focus order, focus returned after close, visible `:focus-visible`.
|
||||
- Works in `forced-colors`, dark theme and at 200% zoom. Motion relies on the global `prefers-reduced-motion` handling.
|
||||
- User-facing strings use `useI18n`, with keys in `nb`, `nn` and `en` under `@navikt/core/react/src/utils/i18n/locales/`.
|
||||
- Review only the interaction pattern in scope; do not apply modal focus or dismissal rules to unrelated popup patterns. Check changed accessible names and relationships against their rendered targets.
|
||||
|
||||
### Wiring (🔴 for new public components)
|
||||
|
||||
- Stable export: component `index.ts` → `@navikt/core/react/src/index.ts` → `package.json` `exports`. Preview: `@navikt/core/react/src/preview.ts` plus `"./PREVIEW/<Name>"`.
|
||||
- New CSS file is imported in `@navikt/core/css/src/index.css` with a `layer(...)`.
|
||||
- Removed published CSS classes are listed in `@navikt/aksel-stylelint/src/deprecations.ts`.
|
||||
|
||||
### CSS and tokens (🟡)
|
||||
|
||||
- `.aksel-<component>` prefix. State through `data-*` or ARIA selectors, not modifier classes.
|
||||
- Tokens only (`--ax-*`, internal `--__axc-*`). No hardcoded colors, spacing or radii.
|
||||
- Renaming or removing a token is breaking (🔴). Semantic color changes are checked in both light and dark.
|
||||
|
||||
### Security and performance
|
||||
|
||||
- 🔴 No `dangerouslySetInnerHTML` or unsanitized HTML from props. No secrets or `process.env` values leaking into published bundles or website client code.
|
||||
- 🔴 GitHub Actions: minimal `permissions`, new third-party actions pinned to a commit SHA, no untrusted input (`github.event.*`) interpolated into `run:`.
|
||||
- 🟡 No top-level side effects in library modules (breaks tree-shaking). No avoidable re-renders: stable context values, no new object or function props per render in hot paths.
|
||||
|
||||
### Tests and docs (🟡)
|
||||
|
||||
- Complex logic with a higher risk of breaking has a story `play` test or Vitest test proving the user-facing contract. Queries use role and accessible name.
|
||||
- Tests are deterministic: no dependence on timezone, current time or randomness.
|
||||
- Edge cases that are easy to miss: controlled vs. uncontrolled, `ref`/`className`/`...rest` forwarding, `as` prop, empty and very long content.
|
||||
- For behavior changes, check that tests distinguish the intended outcome from a no-op and cover relevant intermediate states. When tests are consolidated or removed, verify meaningful old scenarios have replacement coverage.
|
||||
- Website examples (`aksel.nav.no/website/pages/eksempler/`) are updated when usage changes.
|
||||
|
||||
### Component contracts and public consumer impact
|
||||
|
||||
- `COMP-01` **Contract evidence (🟡; 🔴 if behavior is broken):** For changed state, callbacks, async behavior, composition, keyboard or focus behavior, use the [component-contract skill](../component-contract/SKILL.md). Check that a nearby story or test demonstrates the changed user-visible outcome; if coverage is missing, request it.
|
||||
- `COMP-02` **Public impact (🟡; 🔴 if a consumer contract regresses):** For changed hooks, contexts, CSS or tokens, trace affected exported components and inspect a representative consumer-visible contract. Do not stop at the implementation diff; request a public-boundary example or test when impact is not demonstrated.
|
||||
- `COMP-03` **Generated API metadata (🟡; 🔴 if published API is wrong):** For public prop or JSDoc changes, check the relevant generated props metadata as well as exports and tests; request the documented metadata update or correction when stale.
|
||||
|
||||
### Release (🟡, 🔴 if breaking)
|
||||
|
||||
- User-facing change to a publishable package has a `.changeset/*.md` with the right bump and a `<Component>: <consumer-facing text>` summary. Website, playroom and tooling changes get none.
|
||||
- Breaking change that consumers can migrate mechanically has a codemod in `@navikt/aksel/src/codemod/transforms/`, registered in `@navikt/aksel/src/migrations-config.ts`.
|
||||
|
||||
### Scope (🟡)
|
||||
|
||||
- Over-editing: diff out of proportion to the stated goal (unrelated renames, refactors, reformatting, extra validation). Tests don't catch it, and it makes the diff hard to review.
|
||||
- No unrelated changes bundled. No skipped tests (`skip`, `!play-fn`) without a stated reason.
|
||||
- AI-generated code: patterns copied without adapting to Aksel conventions, error handling that looks right but isn't, missing edge cases. Ask the author to explain non-obvious design choices.
|
||||
|
||||
## Output format
|
||||
|
||||
```md
|
||||
### Summary
|
||||
|
||||
What the change does, overall verdict and key risks (1–3 sentences).
|
||||
|
||||
### Evidence and replay
|
||||
|
||||
- **Inspected:** relevant files, stories and behavior states reviewed.
|
||||
- **Checks run:** exact command and result, or `Not run` with reason.
|
||||
- **Not inspected:** relevant surfaces or behavior not verified.
|
||||
- **Reviewer replay:** starting story/URL, action and expected result, or `Not needed`. Label unexecuted steps as unverified.
|
||||
|
||||
### Findings
|
||||
|
||||
| File | Line | Priority | Issue |
|
||||
| ------------ | ---- | -------- | ------------------------------------------------------- |
|
||||
| `Button.tsx` | 42 | 🔴 | `React.useId` breaks React 17: use internal `useId` |
|
||||
| `button.css` | 10 | 🟡 | Hardcoded `#fff`: use `--ax-text-*` token for dark mode |
|
||||
|
||||
### Details
|
||||
|
||||
For each 🔴: why it matters and a suggested fix (short code snippet if helpful).
|
||||
```
|
||||
|
||||
No findings: say so and list what you checked.
|
||||
|
||||
## Boundaries
|
||||
|
||||
- **Always**: read the actual code, run checks before reporting, order findings 🔴 → 🟡 → 💭.
|
||||
- **Ask first**: architectural changes, adding or removing dependencies, reviewing outside this repository.
|
||||
- **Never**: auto-fix or push, approve code you haven't read, skip the Public API, accessibility or security checks.
|
||||
@@ -1,36 +0,0 @@
|
||||
---
|
||||
name: component-contract
|
||||
description: "Define and verify component behavior, state ownership, accessibility and compatibility before changing a public component contract. Use when adding or changing controlled state, callbacks, async transitions, composition, keyboard or focus behavior, semantics, or consumer-facing props."
|
||||
---
|
||||
|
||||
# Component contract
|
||||
|
||||
Use this workflow for the behavior affected by a component change. Do not turn every dimension below into a new requirement when the change does not touch it.
|
||||
|
||||
## Establish the existing contract
|
||||
|
||||
Read the component folder, including its exports, metadata, stories, tests and relevant CSS. Trace related hooks, contexts and callers when the behavior crosses component boundaries. Preserve established behavior unless the requested change intentionally changes it.
|
||||
|
||||
For the affected behavior, record a compact contract:
|
||||
|
||||
| Area | Define when relevant |
|
||||
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| State ownership | Controlled and uncontrolled inputs, defaults, source of truth, and what callbacks signal. A controlled component must reflect the owner's prop; do not assume a requested update was accepted. |
|
||||
| Async transitions | Relevant pending, success, failure, cancellation or stale-result behavior, and what the user sees. |
|
||||
| Composition | Which component or context owns registration, event handling and cleanup; how nesting or portals affect the behavior. |
|
||||
| Accessibility | Native semantics, accessible name/content responsibility, roles, states and relationships. Identify what the library guarantees and what the consumer must provide. |
|
||||
| Keyboard and focus | Supported keys, focus entry/movement/return, and whether each outcome is synchronous or eventual. Apply pattern-specific behavior only where that pattern is used. |
|
||||
| Compatibility | Public API and React 17–19 / SSR constraints affected by the change. |
|
||||
|
||||
Keep the contract proportional to the change. Use existing documentation, JSDoc, stories and tests to record decisions; do not create a new metadata format or a separate document for every component.
|
||||
|
||||
## Verify the contract
|
||||
|
||||
- Identify the closest existing story or test that demonstrates each changed behavior. Prefer observable public-component outcomes over assertions on implementation details.
|
||||
- Check that the starting state and intermediate outcome distinguish the intended behavior from a no-op.
|
||||
- Cover meaningful accepted and rejected controlled updates, async outcomes, or nested interactions when they are part of the change.
|
||||
- Verify accessible names and relationships against the actual rendered elements when those semantics change. Do not infer accessibility from attribute presence alone.
|
||||
- Separate automated interaction evidence from visual inspection or manual assistive-technology checks; report what was not verified.
|
||||
- Use `aksel-local-validation` to select commands. This skill defines behavior, not a separate test or validation process.
|
||||
|
||||
Do not copy another library's state semantics, API, React hooks or architectural layers as Aksel's contract. Follow Aksel's existing component and compatibility instructions.
|
||||
@@ -1,304 +0,0 @@
|
||||
---
|
||||
name: ds-component-restructure
|
||||
description: "Restructure a ds-react component's files into canonical folder/naming conventions. Use when reorganizing a component's file layout, splitting a monolithic component file, or renaming files to match domain conventions."
|
||||
argument-hint: "Path to component folder, e.g. @navikt/core/react/src/accordion"
|
||||
---
|
||||
|
||||
# ds-component-restructure
|
||||
|
||||
Restructure a `ds-react` component folder to follow canonical conventions — without breaking the public API.
|
||||
|
||||
## Canonical Structure
|
||||
|
||||
```
|
||||
<component>/
|
||||
├── index.ts # "use client" + re-exports from root
|
||||
├── <Component>.stories.tsx # Storybook stories
|
||||
├── <Component>.tests.stories.tsx # Storybook play-tests (only if play tests exist)
|
||||
├── <Component>.meta.ts # Meta info for component. Used for documentation generation.
|
||||
├── root/
|
||||
│ ├── <Component>Root.tsx # Main compound component + sub-component namespace
|
||||
│ ├── <Component>Root.context.ts # Context (if shared across sub-components)
|
||||
│ ├── <Component>Root.test.ts # Unit tests for root (if needed)
|
||||
│ └── use<Hook>.ts # Files for root-level hooks (if needed)
|
||||
├── <subcomponent>/ # One dir per sub-component
|
||||
│ ├── <Component><Subcomponent>.tsx
|
||||
│ ├── <Component><Subcomponent>.test.ts # Unit tests for sub-component (if needed)
|
||||
│ └── use<Hook>.ts # Files for sub-component-level hooks (if needed)
|
||||
├── helpers/
|
||||
│ ├── <helper-name>.ts
|
||||
│ └── <helper-name>.test.ts # Unit tests co-located with helper
|
||||
└── <Component>.types.ts # Shared types (if needed across multiple files)
|
||||
```
|
||||
|
||||
### Naming Rules
|
||||
|
||||
| What | Pattern | Example |
|
||||
| ---------------------- | --------------------------------------- | ----------------------------- |
|
||||
| Root component file | `<Component>Root.tsx` | `AccordionRoot.tsx` |
|
||||
| Sub-component file | `<Component><Subcomponent>.tsx` | `AccordionItem.tsx` |
|
||||
| Internal sub-component | `<Component><Subcomponent>Internal.tsx` | `DialogBackdropInternal.tsx` |
|
||||
| Sub-component dir | kebab-case sub-component name | `item/` |
|
||||
| Context file | `<Component>Root.context.ts` | `AccordionRoot.context.ts` |
|
||||
| Shared types file | `<Component>.types.ts` | `Accordion.types.ts` |
|
||||
| Stories | `<Component>.stories.tsx` | `Accordion.stories.tsx` |
|
||||
| Play-test stories | `<Component>.tests.stories.tsx` | `Accordion.tests.stories.tsx` |
|
||||
| Unit test file | `<filename>.test.ts(x)` | `useAccordion.test.ts` |
|
||||
| Meta file | `<Component>.meta.ts` | `Accordion.meta.ts` |
|
||||
| `index.ts` | always `index.ts` | `index.ts` |
|
||||
|
||||
## Procedure
|
||||
|
||||
### 1. Read the Component
|
||||
|
||||
Read every file in the component folder — `index.ts`, all `.tsx`/`.ts` source files, meta file, stories, and test stories. Ignore inconsistencies in file naming and structure; the goal is to fix those, not preserve them. Use the `component`-field in the meta file to determine the proper naming if unsure.
|
||||
|
||||
**Done when:** every file's role (component, hook, type, context, test, metadata) is known, and every symbol currently exported from `index.ts` is recorded.
|
||||
|
||||
### 2. Map Files to Target Structure
|
||||
|
||||
Build this table before touching any files:
|
||||
|
||||
| Old path | New path | Renamed? | Publicly exported? | Importers to update |
|
||||
| ------------------- | --------------------------- | -------- | ------------------ | --------------------- |
|
||||
| `DialogTrigger.tsx` | `trigger/DialogTrigger.tsx` | No | Yes | `root/DialogRoot.tsx` |
|
||||
| ... | | | | |
|
||||
|
||||
For each file:
|
||||
|
||||
- Determine new path and whether it moves/renames
|
||||
- Check whether content needs splitting (e.g. types → `<Component>.types.ts`)
|
||||
- List every file that imports it (grep for the filename or exported symbol)
|
||||
- Flag circular dep risks: context importing sub-components, helpers importing component files, types importing non-types
|
||||
|
||||
Resolve circular deps before proceeding: move shared pieces to a lower-level file (types → `.types.ts`, utilities → `helpers/`).
|
||||
|
||||
**Done when:** every file in the component folder has a row in the table, and every circular-dependency risk is resolved or explicitly noted.
|
||||
|
||||
### 3. Create New Files
|
||||
|
||||
For each row in the mapping table where the path changes:
|
||||
|
||||
1. Create the file at the new path with all import paths updated to reflect new locations
|
||||
2. Update every file in the "Importers to update" column — use the mapping table, don't rely on memory
|
||||
3. Do NOT delete old files yet
|
||||
|
||||
> **Case-only renames** (path differs only in letter casing, e.g. `heading.stories.tsx` → `Heading.stories.tsx`) must NOT use the create-new + delete-old flow. On case-insensitive filesystems (macOS and Windows default) the two paths resolve to the **same file**, so creating the new file overwrites the old and the later "delete old" step destroys your content — and git records no rename. Handle these with the two-step `git mv` in the [Case-only renames](#case-only-renames) edge case instead.
|
||||
|
||||
### 4. Update Stories
|
||||
|
||||
Move stories to component root dir. If stories import from internal paths, update those paths.
|
||||
|
||||
**Exception:** A `stories/` subdirectory is acceptable if it already exists — do not flatten it.
|
||||
|
||||
**Casing:** Story filenames follow `<Component>.stories.tsx` (PascalCase). When the existing file is lowercase (e.g. `heading.stories.tsx`), this is a **case-only rename** — follow the [Case-only renames](#case-only-renames) procedure, do not create + delete.
|
||||
|
||||
### 5. Update `index.ts`
|
||||
|
||||
All exports are **named** — no `default` exports anywhere. For compound components, the root file is the single export source: it assembles the compound component, imports every publicly exported sub-component and its prop types, and exports all public values and types together. The component `index.ts` only re-exports these names from `./root/<Component>Root` — never directly from sub-component files or shared type files.
|
||||
|
||||
```ts
|
||||
"use client";
|
||||
export {
|
||||
<Component>, // compound root (named)
|
||||
<Component>Trigger, // sub-components (named)
|
||||
// ...
|
||||
} from "./root/<Component>Root";
|
||||
export type {
|
||||
<Component>Props,
|
||||
<Component>TriggerProps,
|
||||
// ...
|
||||
} from "./root/<Component>Root";
|
||||
```
|
||||
|
||||
Group values first, then types. If types live in `<Component>.types.ts`, import them into the root file and include them in its type export block. The `index.ts` still re-exports them from the root file.
|
||||
|
||||
#### Compound root file export pattern
|
||||
|
||||
In `<Component>Root.tsx`, assemble the compound component with `Object.assign` — **do not** use an `as <Component>Component` cast or a dedicated `<Component>Component` interface:
|
||||
|
||||
```tsx
|
||||
import {
|
||||
<Component>Trigger,
|
||||
type <Component>TriggerProps,
|
||||
} from "../trigger/<Component>Trigger";
|
||||
|
||||
const <Component>Root = forwardRef<HTMLDivElement, <Component>Props>((props, ref) => {
|
||||
// ...
|
||||
});
|
||||
|
||||
/**
|
||||
* Component-level JSDoc (description, `@see`, `@example`) goes directly
|
||||
* above the `Object.assign` — this is the exported `<Component>` value.
|
||||
*/
|
||||
const <Component> = Object.assign(<Component>Root, {
|
||||
/**
|
||||
* @see 🏷️ {@link <Component>TriggerProps}
|
||||
*/
|
||||
Trigger: <Component>Trigger,
|
||||
// ...
|
||||
});
|
||||
|
||||
export { <Component>, <Component>Trigger };
|
||||
export type { <Component>Props, <Component>TriggerProps };
|
||||
```
|
||||
|
||||
Follow `accordion/root/AccordionRoot.tsx`: export the imported sub-component values and prop types alongside the compound component at the bottom of the root file. Do not add internal-only sub-components to the public export chain. Sub-components must not import from the root file or component `index.ts`; use shared type/context/helper files to avoid circular dependencies.
|
||||
|
||||
Why `Object.assign` instead of `as`: the cast types the const as an interface that has no runtime `valueDeclaration`, so `react-docgen-typescript` (used by `yarn docgen:meta`) fails to extract props and can't document the component when it is exported via a grouped `export { }` statement. `Object.assign` lets TypeScript infer the intersection type from the value, so metadata generation works with named, bottom-of-file exports.
|
||||
|
||||
Sub-component files follow the same rule — named value export plus named type export at the bottom, no inline `export interface` and no default:
|
||||
|
||||
```tsx
|
||||
interface <Component>TriggerProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
// ...
|
||||
}
|
||||
|
||||
const <Component>Trigger = forwardRef<HTMLDivElement, <Component>TriggerProps>(/* ... */);
|
||||
|
||||
export { <Component>Trigger };
|
||||
export type { <Component>TriggerProps };
|
||||
```
|
||||
|
||||
### 6. Update Meta File
|
||||
|
||||
Update imports in `<Component>.meta.ts` to reflect new paths. If the meta file imports from the component's `index.ts`, no changes are needed.
|
||||
|
||||
If component has no meta file, create `<Component>.meta.ts` in the component root. Copy the shape of `@navikt/core/react/src/accordion/Accordion.meta.ts`: import from `./index`, `name`, a `components` map using dotted keys for sub-components (`"Accordion.Item": AccordionItem`), and `keywords`, then `export { metadata }`.
|
||||
Note that each standalone component should have a meta file. But a "sub-component" (like `AccordionItem`) does not need a meta file.
|
||||
|
||||
### 7. Validate No Breaking Changes
|
||||
|
||||
**Export diff.** Before deleting old files, compare the exported names recorded during Step 1 against the new `index.ts`. Every name must be present:
|
||||
|
||||
- Exports in new index.ts that are missing from original → regression
|
||||
- Exports in original that are missing from new index.ts → missing export (breaking change)
|
||||
|
||||
Concretely: grep for `export` lines in both versions and diff them. All component names and type names must match exactly.
|
||||
|
||||
**Build.** Run `corepack yarn workspace @navikt/ds-react build` — must succeed with no TypeScript errors.
|
||||
|
||||
**Package root.** Verify `@navikt/core/react/src/index.ts` still re-exports the component. No changes needed there unless the component's `index.ts` path changed (it shouldn't).
|
||||
|
||||
**Root-only re-exports.** For compound components, verify every value and type export in the component `index.ts` comes from `./root/<Component>Root`, and every forwarded name is exported by that root file.
|
||||
|
||||
**No case-duplicate tracked paths.** This is the most common restructure bug. On case-insensitive filesystems (macOS, Windows) git can end up tracking **two** entries that differ only in casing (e.g. `accordion.stories.tsx` **and** `Accordion.stories.tsx`) while the working tree shows only one file. The stale entry then breaks tests and checkout on other machines. `ls` / file search will NOT reveal it — you must ask git. Run:
|
||||
|
||||
```sh
|
||||
git ls-files | awk '{l=tolower($0); if(seen[l]++) print "CASE-DUP:", $0}'
|
||||
```
|
||||
|
||||
Any output is a leftover. Remove the stale entry from the index by its **exact old path** (this does not touch the correctly-cased file on disk):
|
||||
|
||||
```sh
|
||||
git rm --cached '<exact old lowercase path>'
|
||||
```
|
||||
|
||||
Then confirm only the intended casing remains, e.g. `git ls-files '*<component>*stories*'`.
|
||||
|
||||
**Done when:** the export diff is empty, the build succeeds, the package-root re-export is verified, and `git ls-files` reports no case-duplicate paths.
|
||||
|
||||
### 8. Remove Old Files
|
||||
|
||||
Only after verifying new files are correct and `index.ts` exports match the original, delete the old files.
|
||||
|
||||
Skip any rows already moved with `git mv` (case-only renames and other `git mv` moves) — there is no separate old file to delete for those.
|
||||
|
||||
### 9. Unit Tests
|
||||
|
||||
Unit tests live next to the file they test — same directory, same base name with `.test.ts` / `.test.tsx` suffix.
|
||||
|
||||
| What to test | File location |
|
||||
| ---------------------- | --------------------------------------------------- |
|
||||
| Helper utility | `helpers/<helper-name>.test.ts` |
|
||||
| Sub-component logic | `<subcomponent>/<Component><Subcomponent>.test.tsx` |
|
||||
| Root component / hooks | `root/<Component>Root.test.tsx` |
|
||||
| Context helpers | `root/<Component>Root.context.test.ts` |
|
||||
|
||||
Do NOT move existing test files unless their source file moves. When moving a source file, move its test file with it and update import paths.
|
||||
|
||||
## Edge Cases
|
||||
|
||||
### Case-only renames
|
||||
|
||||
A rename that changes **only letter casing** (e.g. `heading.stories.tsx` → `Heading.stories.tsx`, or `accordion.tsx` → `Accordion.tsx`) is a trap on case-insensitive filesystems (macOS and Windows defaults):
|
||||
|
||||
- Both paths point to the **same** file, so `create_file` at the new path overwrites the old content, and the Step 8 "delete old" then removes it entirely.
|
||||
- git does not see a rename (`core.ignorecase=true`), so history is lost and other machines get checkout/case-collision conflicts.
|
||||
|
||||
Do the rename in **two `git mv` steps through a temporary distinct name** so git records it and it works on every filesystem:
|
||||
|
||||
```sh
|
||||
git mv heading.stories.tsx heading.stories.tmp.tsx
|
||||
git mv heading.stories.tmp.tsx Heading.stories.tsx
|
||||
```
|
||||
|
||||
Then edit the file's contents/imports in place. Skip the create-new + delete-old flow for these rows entirely.
|
||||
|
||||
Guidelines:
|
||||
|
||||
- Prefer `git mv` for **all** moves/renames (not just case-only) so history is preserved; only fall back to create + delete when the file content is being split or substantially rewritten.
|
||||
- Detect case-only renames while building the Step 2 mapping table: compare old vs new path lowercased — if they're equal but the raw paths differ, mark the row as case-only.
|
||||
- The temporary name must differ by more than case (add `.tmp`), otherwise the same collision reoccurs.
|
||||
|
||||
### Flat components without sub-components
|
||||
|
||||
Leave flat components flat - do NOT introduce subdirs unless the component contains internal utility-components (files that render JSX but are not public sub-components). If such a utility-component exists, move it to its own subdir following the canonical naming.
|
||||
|
||||
Example: `button/Button.tsx` has no other sub-components, so should stay in root.
|
||||
|
||||
### `parts/` directory
|
||||
|
||||
If the component uses a `parts/` dir, break it up into separate sub-component dirs.
|
||||
|
||||
### Internal sub-components
|
||||
|
||||
Sub-components that are implementation details (not exported publicly) use the same dir/file naming as public ones (see Naming Rules) with an `Internal` suffix, but must NOT be exported from `index.ts`:
|
||||
|
||||
```
|
||||
|
||||
backdrop/
|
||||
└── DialogBackdropInternal.tsx ← internal, NOT exported from index.ts
|
||||
|
||||
```
|
||||
|
||||
### Nested subcomponents
|
||||
|
||||
Do NOT nest subcomponent dirs inside other subcomponent dirs. All sub-component dirs must be direct children of the component root:
|
||||
|
||||
```
|
||||
|
||||
# Wrong
|
||||
|
||||
dialog/
|
||||
└── header/
|
||||
├── DialogHeader.tsx
|
||||
└── title/
|
||||
└── DialogTitle.tsx
|
||||
|
||||
# Correct
|
||||
|
||||
dialog/
|
||||
├── header/
|
||||
│ └── DialogHeader.tsx
|
||||
└── title/
|
||||
└── DialogTitle.tsx
|
||||
|
||||
```
|
||||
|
||||
## Constraints
|
||||
|
||||
- **No breaking changes.** Public export names and prop shapes must be identical after restructuring.
|
||||
- **Named exports only.** No `default` exports; group named exports at the bottom of each file (values first, then types).
|
||||
- **Compound exports flow through the root.** Component `index.ts` re-exports public values and types only from `root/<Component>Root.tsx`, not directly from sub-component or shared type files.
|
||||
- **Compound roots use `Object.assign`.** No `as <Component>Component` cast or `<Component>Component` interface — it breaks `yarn docgen:meta` prop extraction.
|
||||
- Component rules (React 17 compatibility, `"use client"`, `forwardRef`/`className`/`...rest`/`as`, tokens) live in `.github/instructions/ds-react-component.instructions.md`. They still apply.
|
||||
- Keep JSDoc on public props and components. The component-level JSDoc block sits directly above the `Object.assign` call; sub-component `@see` tags go on the keys inside the `Object.assign` object literal.
|
||||
|
||||
## Reference Files
|
||||
|
||||
- Canonical example: `@navikt/core/react/src/dialog/`
|
||||
- Package public API: `@navikt/core/react/src/index.ts`
|
||||
- Component export pattern: `@navikt/core/react/src/accordion/root/AccordionRoot.tsx` and `@navikt/core/react/src/accordion/index.ts`
|
||||
- Context pattern: `@navikt/core/react/src/dialog/root/DialogRoot.context.ts`
|
||||
- Meta: `@navikt/core/react/src/accordion/Accordion.meta.ts`
|
||||
@@ -1,66 +0,0 @@
|
||||
---
|
||||
name: website-example
|
||||
description: >
|
||||
Add or update a code example or template shown on aksel.nav.no
|
||||
(aksel.nav.no/website/pages/eksempler/** or pages/templates/**). Use when
|
||||
asked to create a component example, demo, usage snippet or page template
|
||||
for the documentation site, or when an example fails the aksel-local Oxlint
|
||||
rules or validate:eksempler.
|
||||
argument-hint: "Component group and example name, e.g. button/loading"
|
||||
---
|
||||
|
||||
# Website example
|
||||
|
||||
Examples are public copy-paste snippets. The code above the marker comment is shown verbatim on the site and opened in the sandbox.
|
||||
|
||||
## 1. Locate
|
||||
|
||||
- Component examples: `aksel.nav.no/website/pages/eksempler/<group>/<example-name>.tsx` (group = lowercase component, e.g. `lookup`).
|
||||
- Page templates: `aksel.nav.no/website/pages/templates/<name>/...`.
|
||||
- Read 1-2 sibling examples in the same group first and match their tone. Content is in Norwegian (bokmål).
|
||||
|
||||
## 2. Write the file
|
||||
|
||||
```tsx
|
||||
import { Button } from "@navikt/ds-react";
|
||||
import { withDsExample } from "@/web/examples/withDsExample";
|
||||
|
||||
const Example = () => {
|
||||
return <Button>Lagre</Button>;
|
||||
};
|
||||
|
||||
// EXAMPLES DO NOT INCLUDE CONTENT BELOW THIS LINE
|
||||
export default withDsExample(Example);
|
||||
|
||||
/* Storybook story */
|
||||
export const Demo = {
|
||||
render: Example,
|
||||
};
|
||||
|
||||
export const args: ExampleArgsT = {
|
||||
index: 0,
|
||||
desc: "Kort hint om når eksemplet brukes.",
|
||||
};
|
||||
```
|
||||
|
||||
Hard rules (enforced by `tooling/eslint` rules `aksel-local/*`):
|
||||
|
||||
- With the sandbox enabled (default), import only from `@navikt/*`, `react`, relative `../__parts*` helpers and exactly `@/web/examples/withDsExample`. Anything else breaks the "open in sandbox" button. Set `sandbox: false` in `args` only if it's unavoidable.
|
||||
- The demo component must be named `Example` (the sandbox depends on it).
|
||||
- The marker comment `// EXAMPLES DO NOT INCLUDE CONTENT BELOW THIS LINE` sits directly above `export default`.
|
||||
- `export const args: ExampleArgsT` with `index` (sort order within the group). Optional: `desc`, `title`, `sandbox: false`.
|
||||
- `withDsExample(Example, { variant: "static" | ... })`: see its JSDoc for layout options.
|
||||
|
||||
## 3. Validate
|
||||
|
||||
```sh
|
||||
corepack yarn oxlint --deny-warnings <file>
|
||||
corepack yarn workspace website validate:eksempler
|
||||
corepack yarn storybook:aksel # visual check at :6007, needs no secrets
|
||||
```
|
||||
|
||||
Never run `update:eksempler`. It writes to the Sanity CMS. Tell the user that a maintainer syncs examples to Sanity and links a new group from the component page.
|
||||
|
||||
## Done when
|
||||
|
||||
Oxlint and `validate:eksempler` pass, and the story renders in `storybook:aksel`.
|
||||
@@ -0,0 +1,50 @@
|
||||
name: Deploy aksel.ansatt.dev.nav.no
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- next
|
||||
|
||||
jobs:
|
||||
build_and_deploy:
|
||||
name: Build and deploy to dev-gcp
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: "write"
|
||||
contents: "read"
|
||||
id-token: "write"
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup and build Next.js application
|
||||
uses: ./.github/actions/build-website
|
||||
with:
|
||||
use_cdn_assets: true
|
||||
npm_auth_token: ${{ secrets.READER_TOKEN }}
|
||||
nais_id_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
|
||||
nais_project_id: ${{ vars.NAIS_MANAGEMENT_PROJECT_ID }}
|
||||
sanity_read_token: ${{ secrets.SANITY_READ }}
|
||||
sanity_read_no_drafts_token: ${{ secrets.SANITY_READ_NO_DRAFTS }}
|
||||
|
||||
- name: Get complete tag
|
||||
run: echo "TAG=DEV-$( date +%s )" >> $GITHUB_ENV
|
||||
|
||||
- uses: nais/docker-build-push@v0
|
||||
id: docker-push
|
||||
with:
|
||||
team: designsystem
|
||||
push_image: true
|
||||
dockerfile: aksel.nav.no/Dockerfile
|
||||
project_id: ${{ vars.NAIS_MANAGEMENT_PROJECT_ID }}
|
||||
identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
|
||||
|
||||
- name: Deploy to dev-gcp
|
||||
uses: nais/deploy/actions/deploy@master
|
||||
env:
|
||||
APIKEY: ${{ secrets.NAIS_DEPLOY_APIKEY }}
|
||||
RESOURCE: aksel.nav.no/nais-dev.yaml
|
||||
CLUSTER: dev-gcp
|
||||
IMAGE: ${{ steps.docker-push.outputs.image }}
|
||||
@@ -0,0 +1,74 @@
|
||||
name: Deploy aksel.nav.no
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "aksel.nav.no/**"
|
||||
|
||||
env:
|
||||
IMAGE: ghcr.io/navikt/aksel
|
||||
|
||||
jobs:
|
||||
sync_data_with_sanity:
|
||||
name: Update component data
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup project
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
npm_auth_token: ${{ secrets.READER_TOKEN }}
|
||||
|
||||
- name: Run ts-docgen
|
||||
run: yarn docgen
|
||||
|
||||
- name: Update sanity sync
|
||||
env:
|
||||
SANITY_WRITE: ${{ secrets.SANITY_WRITE }}
|
||||
run: yarn workspace aksel.nav.no sanity:update
|
||||
|
||||
build_and_deploy:
|
||||
name: Build and deploy to prod-gcp
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: "write"
|
||||
contents: "read"
|
||||
id-token: "write"
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup and build Next.js application
|
||||
uses: ./.github/actions/build-website
|
||||
with:
|
||||
use_cdn_assets: true
|
||||
production: true
|
||||
npm_auth_token: ${{ secrets.READER_TOKEN }}
|
||||
nais_id_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
|
||||
nais_project_id: ${{ vars.NAIS_MANAGEMENT_PROJECT_ID }}
|
||||
sanity_read_token: ${{ secrets.SANITY_READ }}
|
||||
sanity_read_no_drafts_token: ${{ secrets.SANITY_READ_NO_DRAFTS }}
|
||||
|
||||
- name: Get complete tag
|
||||
run: echo "TAG=PROD-$( date +%s )" >> $GITHUB_ENV
|
||||
|
||||
- uses: nais/docker-build-push@v0
|
||||
id: docker-push
|
||||
with:
|
||||
team: designsystem
|
||||
push_image: true
|
||||
dockerfile: aksel.nav.no/Dockerfile
|
||||
project_id: ${{ vars.NAIS_MANAGEMENT_PROJECT_ID }}
|
||||
identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
|
||||
|
||||
- name: Deploy to prod-gcp
|
||||
uses: nais/deploy/actions/deploy@master
|
||||
env:
|
||||
APIKEY: ${{ secrets.NAIS_DEPLOY_APIKEY }}
|
||||
RESOURCE: aksel.nav.no/nais-prod.yaml
|
||||
CLUSTER: prod-gcp
|
||||
IMAGE: ${{ steps.docker-push.outputs.image }}
|
||||
@@ -0,0 +1,61 @@
|
||||
name: Deploy aksel-v7.nav.no
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- v7-aksel.nav.no
|
||||
|
||||
jobs:
|
||||
build_and_deploy:
|
||||
name: Build and deploy to prod-gcp
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/v7-aksel.nav.no'
|
||||
permissions:
|
||||
packages: "write"
|
||||
contents: "read"
|
||||
id-token: "write"
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd v7-aksel.nav.no && yarn --immutable
|
||||
shell: bash
|
||||
env:
|
||||
NPM_AUTH_TOKEN: ${{ secrets.READER_TOKEN }}
|
||||
|
||||
- name: Set production env var
|
||||
run: |
|
||||
echo "PRODUCTION=true" >> v7-aksel.nav.no/website/.env
|
||||
shell: bash
|
||||
|
||||
- name: Build Next.js website
|
||||
run: cd v7-aksel.nav.no/website && yarn build
|
||||
shell: bash
|
||||
|
||||
- name: Get complete tag
|
||||
run: echo "TAG=PROD-V7-$( date +%s )" >> $GITHUB_ENV
|
||||
|
||||
- uses: nais/docker-build-push@v0
|
||||
id: docker-push
|
||||
with:
|
||||
team: designsystem
|
||||
push_image: true
|
||||
dockerfile: v7-aksel.nav.no/Dockerfile
|
||||
project_id: ${{ vars.NAIS_MANAGEMENT_PROJECT_ID }}
|
||||
identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
|
||||
|
||||
- name: Deploy to prod-gcp
|
||||
uses: nais/deploy/actions/deploy@master
|
||||
env:
|
||||
APIKEY: ${{ secrets.NAIS_DEPLOY_APIKEY }}
|
||||
RESOURCE: v7-aksel.nav.no/nais-prod.yaml
|
||||
CLUSTER: prod-gcp
|
||||
IMAGE: ${{ steps.docker-push.outputs.image }}
|
||||
@@ -1,49 +0,0 @@
|
||||
name: Backup Sanity dataset
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 0 */3 * *"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
execute:
|
||||
name: Export and upload Sanity backup
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: yarn
|
||||
cache-dependency-path: "yarn.lock"
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd aksel.nav.no/sanity-studio && yarn
|
||||
env:
|
||||
NPM_AUTH_TOKEN: ${{ secrets.READER_TOKEN }}
|
||||
|
||||
- name: Export dataset
|
||||
run: cd aksel.nav.no/sanity-studio && yarn sanity:backup
|
||||
env:
|
||||
SANITY_READ: ${{ secrets.SANITY_READ }}
|
||||
|
||||
- name: Authenticate with GCP
|
||||
uses: "google-github-actions/auth@v3"
|
||||
with:
|
||||
credentials_json: "${{ secrets.gcp_credentials_prod }}"
|
||||
|
||||
- name: Upload backup to GCS bucket
|
||||
uses: "google-github-actions/upload-cloud-storage@v3"
|
||||
with:
|
||||
path: ./aksel.nav.no/sanity-studio
|
||||
destination: "aksel-website-prod/backups"
|
||||
glob: "backup-*.tar.gz"
|
||||
@@ -0,0 +1,39 @@
|
||||
name: Backup sanity dateset to gcp
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# 12.00 every 3 days
|
||||
- cron: "0 0 */3 * *"
|
||||
jobs:
|
||||
execute:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0 # Required to retrieve git history
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: yarn
|
||||
cache-dependency-path: "yarn.lock"
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd aksel.nav.no/website && yarn
|
||||
env:
|
||||
NPM_AUTH_TOKEN: ${{ secrets.READER_TOKEN }}
|
||||
|
||||
- name: Export dataset
|
||||
run: cd aksel.nav.no/website && yarn backup
|
||||
env:
|
||||
SANITY_READ: ${{ secrets.SANITY_READ }}
|
||||
|
||||
- name: Upload GCP bucket
|
||||
uses: "google-github-actions/auth@v3"
|
||||
with:
|
||||
credentials_json: "${{ secrets.gcp_credentials_prod }}"
|
||||
- uses: "google-github-actions/upload-cloud-storage@v3"
|
||||
with:
|
||||
path: ./aksel.nav.no/website
|
||||
destination: "aksel-website-prod/backups"
|
||||
glob: "backup-*.tar.gz"
|
||||
@@ -1,139 +0,0 @@
|
||||
name: E2E tests
|
||||
on:
|
||||
push:
|
||||
branches: [main, next]
|
||||
paths:
|
||||
- "aksel.nav.no/**"
|
||||
pull_request:
|
||||
branches: [main, next]
|
||||
paths:
|
||||
- "aksel.nav.no/**"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
prebuild-app-for-playwright:
|
||||
name: Build app for Playwright
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write # Push E2E image to GHCR
|
||||
env:
|
||||
IMAGE_NAME: ${{ github.repository }}-e2e
|
||||
REGISTRY: ghcr.io
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup and build Next.js application
|
||||
uses: ./.github/actions/build-website
|
||||
with:
|
||||
use_cdn_assets: false
|
||||
npm_auth_token: ${{ secrets.READER_TOKEN }}
|
||||
nais_id_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
|
||||
nais_project_id: ${{ vars.NAIS_MANAGEMENT_PROJECT_ID }}
|
||||
sanity_read_token: ${{ secrets.SANITY_READ }}
|
||||
sanity_read_no_drafts_token: ${{ secrets.SANITY_READ_NO_DRAFTS }}
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
file: aksel.nav.no/Dockerfile
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
|
||||
outputs:
|
||||
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
|
||||
|
||||
run-playwright:
|
||||
needs: [prebuild-app-for-playwright]
|
||||
name: Playwright shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read # Pull E2E image from GHCR
|
||||
container:
|
||||
image: mcr.microsoft.com/playwright:v1.61.1-noble
|
||||
services:
|
||||
testapp:
|
||||
image: ${{ needs.prebuild-app-for-playwright.outputs.image }}
|
||||
ports: ["3000:3000"]
|
||||
env:
|
||||
SANITY_READ: ${{ secrets.SANITY_READ }}
|
||||
SANITY_READ_NO_DRAFTS: ${{ secrets.SANITY_READ_NO_DRAFTS }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shardIndex: [1, 2, 3, 4, 5]
|
||||
shardTotal: [5]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: yarn
|
||||
cache-dependency-path: "yarn.lock"
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn --immutable
|
||||
shell: bash
|
||||
env:
|
||||
NPM_AUTH_TOKEN: ${{ secrets.READER_TOKEN }}
|
||||
|
||||
- name: Run Playwright
|
||||
shell: bash
|
||||
run: yarn e2e:docker --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
|
||||
env:
|
||||
SANITY_READ: ${{ secrets.SANITY_READ }}
|
||||
SANITY_READ_NO_DRAFTS: ${{ secrets.SANITY_READ_NO_DRAFTS }}
|
||||
|
||||
- name: Upload blob report to GitHub Actions Artifacts
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: blob-report-${{ matrix.shardIndex }}
|
||||
path: aksel.nav.no/website/blob-report
|
||||
retention-days: 1
|
||||
|
||||
merge-reports:
|
||||
name: Merge Playwright reports
|
||||
if: ${{ !cancelled() }}
|
||||
needs: [run-playwright]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download blob reports from GitHub Actions Artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: all-blob-reports
|
||||
pattern: blob-report-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Merge into HTML Report
|
||||
run: npx playwright merge-reports --reporter html ./all-blob-reports
|
||||
|
||||
- name: Upload HTML report
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: html-report--attempt-${{ github.run_attempt }}
|
||||
path: playwright-report
|
||||
retention-days: 14
|
||||
@@ -1,94 +0,0 @@
|
||||
name: Validation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- next
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint (Oxlint, Stylelint etc.)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup project
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
npm_auth_token: ${{ secrets.READER_TOKEN }}
|
||||
|
||||
- name: Lint
|
||||
run: yarn lint
|
||||
|
||||
validate-examples:
|
||||
name: Validate component examples
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup project
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
npm_auth_token: ${{ secrets.READER_TOKEN }}
|
||||
|
||||
- name: Validate example code
|
||||
run: yarn validate:eksempler
|
||||
|
||||
test:
|
||||
name: Unit and integration tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup project
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
npm_auth_token: ${{ secrets.READER_TOKEN }}
|
||||
|
||||
- name: Test
|
||||
run: yarn test
|
||||
|
||||
ui:
|
||||
name: UI tests (Storybook)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
container:
|
||||
image: mcr.microsoft.com/playwright:v1.61.1-noble
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup project
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
npm_auth_token: ${{ secrets.READER_TOKEN }}
|
||||
|
||||
- name: Test UI
|
||||
run: yarn storybook:test --max-workers=2
|
||||
env:
|
||||
HOME: /root
|
||||
@@ -1,87 +0,0 @@
|
||||
name: Visual regression
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
paths:
|
||||
- "@navikt/**"
|
||||
pull_request:
|
||||
branches: [main, next]
|
||||
paths:
|
||||
- "@navikt/**"
|
||||
- "aksel.nav.no/website/pages/templates/**"
|
||||
- ".storybook/**"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
chromatic:
|
||||
name: Run Chromatic
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup project
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
npm_auth_token: ${{ secrets.READER_TOKEN }}
|
||||
|
||||
- name: Chromatic
|
||||
if: github.actor != 'dependabot[bot]'
|
||||
uses: chromaui/action@latest
|
||||
id: chromatic_tests
|
||||
continue-on-error: false
|
||||
with:
|
||||
autoAcceptChanges: "main"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||
exitZeroOnChanges: true
|
||||
buildScriptName: "build:storybook-chromatic"
|
||||
onlyChanged: true
|
||||
untraced: "**/package.json|yarn.lock|**/*.md"
|
||||
externals: "**/core/css/src/**/*.css"
|
||||
traceChanged: true
|
||||
env:
|
||||
STORYBOOK_GITHUB_SHA: ${{ github.sha }}
|
||||
|
||||
- name: Get commit sha
|
||||
if: github.event_name != 'push' && !contains(github.event.head_commit.message, '[ci skip]') && github.actor != 'dependabot[bot]'
|
||||
id: vars
|
||||
shell: bash
|
||||
run: |
|
||||
echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Find Comment
|
||||
if: github.event_name != 'push' && !contains(github.event.head_commit.message, '[ci skip]') && github.actor != 'dependabot[bot]'
|
||||
uses: peter-evans/find-comment@v4
|
||||
id: fc
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-author: "github-actions[bot]"
|
||||
body-includes: Storybook demo
|
||||
|
||||
- name: Create or update comment
|
||||
if: github.event_name != 'push' && !contains(github.event.head_commit.message, '[ci skip]') && github.actor != 'dependabot[bot]'
|
||||
uses: peter-evans/create-or-update-comment@v5
|
||||
with:
|
||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
## [Storybook demo](${{steps.chromatic_tests.outputs.storybookUrl}}) / [Chromatic](${{steps.chromatic_tests.outputs.url}})
|
||||
|
||||
${{ steps.chromatic_tests.outputs.changeCount > 0 && format('### 📝 Changes to review: {0}', steps.chromatic_tests.outputs.changeCount) || '' }}
|
||||
${{ steps.chromatic_tests.outputs.errorCount > 0 && format('### ❌ Stories with errors: {0}', steps.chromatic_tests.outputs.errorCount) || '' }}
|
||||
${{ steps.chromatic_tests.outputs.interactionTestFailuresCount > 0 && format('### ❌ Failed interaction tests: {0}', steps.chromatic_tests.outputs.interactionTestFailuresCount) || '' }}
|
||||
|
||||
[${{ steps.vars.outputs.sha_short }}](https://github.com/navikt/aksel/commit/${{ steps.vars.outputs.sha_short }}) | ${{steps.chromatic_tests.outputs.componentCount}} components | ${{steps.chromatic_tests.outputs.specCount}} stories
|
||||
edit-mode: replace
|
||||
@@ -1,150 +0,0 @@
|
||||
name: Bundle check (main branch)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "@navikt/**"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check-title:
|
||||
name: Check PR title
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
should-run: ${{ steps.check.outputs.should-run }}
|
||||
steps:
|
||||
- id: check
|
||||
run: echo "should-run=${{ !contains(github.event.pull_request.title, 'Version Packages') }}" >> $GITHUB_OUTPUT
|
||||
|
||||
analyze-trunk:
|
||||
name: Analyze main branch
|
||||
runs-on: ubuntu-latest
|
||||
needs: check-title
|
||||
if: ${{ needs.check-title.outputs.should-run == 'true' }}
|
||||
steps:
|
||||
- name: Checkout main branch
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
ref: main
|
||||
|
||||
- name: Setup project
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
npm_auth_token: ${{ secrets.READER_TOKEN }}
|
||||
|
||||
- name: Analyze main branch
|
||||
run: yarn run:analyze-local-action
|
||||
|
||||
- name: Upload json report to GitHub Actions Artifacts
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: trunk-results.json
|
||||
path: tooling/analyzer
|
||||
retention-days: 1
|
||||
|
||||
analyze-local:
|
||||
name: Analyze PR branch
|
||||
runs-on: ubuntu-latest
|
||||
needs: check-title
|
||||
if: ${{ needs.check-title.outputs.should-run == 'true' }}
|
||||
steps:
|
||||
- name: Checkout PR branch
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup project
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
npm_auth_token: ${{ secrets.READER_TOKEN }}
|
||||
|
||||
- name: Analyze PR branch
|
||||
run: yarn run:analyze-branch-action
|
||||
|
||||
- name: Upload json report to GitHub Actions Artifacts
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: branch-results.json
|
||||
path: tooling/analyzer
|
||||
retention-days: 1
|
||||
|
||||
compare-against-trunk:
|
||||
name: Compare bundles (PR vs main)
|
||||
runs-on: ubuntu-latest
|
||||
needs: [analyze-trunk, analyze-local]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup project
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
npm_auth_token: ${{ secrets.READER_TOKEN }}
|
||||
|
||||
- name: Download trunk json from GitHub Actions Artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: tooling/analyzer
|
||||
name: trunk-results.json
|
||||
|
||||
- name: Download branch json from GitHub Actions Artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: tooling/analyzer
|
||||
name: branch-results.json
|
||||
|
||||
- name: Analyze compare branches
|
||||
run: yarn run:compare-bundles
|
||||
|
||||
- name: Upload comparison markdown to GitHub Actions Artifacts
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: compare-markdown.md
|
||||
path: tooling/analyzer
|
||||
retention-days: 1
|
||||
|
||||
update-comment:
|
||||
name: Post PR comment with results
|
||||
runs-on: ubuntu-latest
|
||||
needs: [compare-against-trunk]
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Download comparison markdown to GitHub Actions Artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: tooling/analyzer
|
||||
name: compare-markdown.md
|
||||
|
||||
- name: Find Comment
|
||||
uses: peter-evans/find-comment@v4
|
||||
id: fc
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-author: "github-actions[bot]"
|
||||
body-includes: Bundle Analysis Result
|
||||
|
||||
- name: Create or update comment
|
||||
uses: peter-evans/create-or-update-comment@v5
|
||||
with:
|
||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body-path: tooling/analyzer/compare-markdown.md
|
||||
edit-mode: replace
|
||||
@@ -1,85 +0,0 @@
|
||||
name: Bundle check (npm)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "@navikt/**"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check-title:
|
||||
name: Check PR title
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
should-run: ${{ steps.check.outputs.should-run }}
|
||||
steps:
|
||||
- id: check
|
||||
run: echo "should-run=${{ contains(github.event.pull_request.title, 'Version Packages') }}" >> $GITHUB_OUTPUT
|
||||
|
||||
analyze:
|
||||
name: Analyze npm packages
|
||||
runs-on: ubuntu-latest
|
||||
needs: check-title
|
||||
if: ${{ needs.check-title.outputs.should-run == 'true' }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup project
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
npm_auth_token: ${{ secrets.READER_TOKEN }}
|
||||
|
||||
- name: Analyze Npm and local packages
|
||||
run: yarn run:analyze-npm-action
|
||||
|
||||
- name: Run comparison analysis
|
||||
run: yarn run:compare-bundles
|
||||
|
||||
- name: Upload comparison markdown to GitHub Actions Artifacts
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: compare-markdown.md
|
||||
path: tooling/analyzer
|
||||
retention-days: 1
|
||||
|
||||
update-comment:
|
||||
name: Post PR comment with results
|
||||
runs-on: ubuntu-latest
|
||||
needs: [analyze]
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Download comparison markdown to GitHub Actions Artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: tooling/analyzer
|
||||
name: compare-markdown.md
|
||||
|
||||
- name: Find Comment
|
||||
uses: peter-evans/find-comment@v4
|
||||
id: fc
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-author: "github-actions[bot]"
|
||||
body-includes: Bundle Analysis Result
|
||||
|
||||
- name: Create or update comment
|
||||
uses: peter-evans/create-or-update-comment@v5
|
||||
with:
|
||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body-path: tooling/analyzer/compare-markdown.md
|
||||
edit-mode: replace
|
||||
@@ -1,57 +0,0 @@
|
||||
name: Upload Figma token config to CDN
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
paths:
|
||||
- "@navikt/core/tokens/**"
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
name: Build and upload token config
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/main'
|
||||
|
||||
permissions:
|
||||
contents: "read"
|
||||
id-token: "write"
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: yarn
|
||||
cache-dependency-path: "yarn.lock"
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
env:
|
||||
NPM_AUTH_TOKEN: ${{ secrets.READER_TOKEN }}
|
||||
|
||||
- name: Build tokens
|
||||
run: yarn workspace @navikt/ds-tokens build
|
||||
|
||||
- name: Upload config to CDN
|
||||
id: upload
|
||||
uses: nais/deploy/actions/cdn-upload/v2@master
|
||||
with:
|
||||
team: designsystem
|
||||
source: "./@navikt/core/tokens/figma-config.json"
|
||||
destination: "./@navikt/tokens"
|
||||
project_id: ${{ vars.NAIS_MANAGEMENT_PROJECT_ID }}
|
||||
identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
|
||||
source_keep_parent_name: false
|
||||
no_cache_paths: "@navikt/tokens/figma-config.json"
|
||||
|
||||
- name: Log uploaded file
|
||||
run: echo uploaded file ${{ steps.upload.outputs.uploaded }}
|
||||
shell: bash
|
||||
@@ -1,39 +0,0 @@
|
||||
name: Upload fonts to CDN
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
paths:
|
||||
- "fonts/**"
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
name: Upload fonts to CDN
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/main'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: "actions/checkout@v7"
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Upload fonts to CDN
|
||||
id: upload
|
||||
uses: nais/deploy/actions/cdn-upload/v2@master
|
||||
with:
|
||||
team: aksel
|
||||
source: ./fonts
|
||||
destination: "" # uploads folder to team root
|
||||
project_id: ${{ vars.NAIS_MANAGEMENT_PROJECT_ID }}
|
||||
identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
|
||||
|
||||
- name: Log uploaded file
|
||||
run: echo uploaded file ${{ steps.upload.outputs.uploaded }}
|
||||
shell: bash
|
||||
@@ -0,0 +1,85 @@
|
||||
name: Visual Regression Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
paths:
|
||||
- "@navikt/**"
|
||||
pull_request:
|
||||
branches: [main, next]
|
||||
paths:
|
||||
- "@navikt/**"
|
||||
- "aksel.nav.no/website/pages/templates/**"
|
||||
- ".storybook/**"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
chromatic:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup project
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
npm_auth_token: ${{ secrets.READER_TOKEN }}
|
||||
|
||||
- name: Chromatic
|
||||
uses: chromaui/action@latest
|
||||
id: chromatic_tests
|
||||
continue-on-error: false
|
||||
with:
|
||||
autoAcceptChanges: "main"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||
exitZeroOnChanges: true
|
||||
buildScriptName: "build:storybook-chromatic"
|
||||
onlyChanged: true
|
||||
untraced: "**/package.json|yarn.lock|**/*.md"
|
||||
externals: "**/core/css/*.css|**/core/css/baseline/*.css|**/core/css/form/*.css|**/core/css/primitives/*.css"
|
||||
traceChanged: true
|
||||
env:
|
||||
STORYBOOK_GITHUB_SHA: ${{ github.sha }}
|
||||
|
||||
- name: Get commit sha
|
||||
if: github.event_name != 'push' && !contains(github.event.head_commit.message, '[ci skip]')
|
||||
id: vars
|
||||
shell: bash
|
||||
run: |
|
||||
echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Find Comment
|
||||
if: github.event_name != 'push' && !contains(github.event.head_commit.message, '[ci skip]')
|
||||
uses: peter-evans/find-comment@v4
|
||||
id: fc
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-author: "github-actions[bot]"
|
||||
body-includes: Storybook demo
|
||||
|
||||
- name: Create or update comment
|
||||
if: github.event_name != 'push' && !contains(github.event.head_commit.message, '[ci skip]')
|
||||
uses: peter-evans/create-or-update-comment@v5
|
||||
with:
|
||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
## [Storybook demo](${{steps.chromatic_tests.outputs.storybookUrl}}) / [Chromatic](${{steps.chromatic_tests.outputs.url}})
|
||||
|
||||
${{ steps.chromatic_tests.outputs.changeCount > 0 && format('### 📝 Changes to review: {0}', steps.chromatic_tests.outputs.changeCount) || '' }}
|
||||
${{ steps.chromatic_tests.outputs.errorCount > 0 && format('### ❌ Stories with errors: {0}', steps.chromatic_tests.outputs.errorCount) || '' }}
|
||||
${{ steps.chromatic_tests.outputs.interactionTestFailuresCount > 0 && format('### ❌ Failed interaction tests: {0}', steps.chromatic_tests.outputs.interactionTestFailuresCount) || '' }}
|
||||
|
||||
[${{ steps.vars.outputs.sha_short }}](https://github.com/navikt/aksel/commit/${{ steps.vars.outputs.sha_short }}) | ${{steps.chromatic_tests.outputs.componentCount}} components | ${{steps.chromatic_tests.outputs.specCount}} stories
|
||||
edit-mode: replace
|
||||
@@ -0,0 +1,81 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- next
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: "Linter: eslint, stylelint"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup project
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
npm_auth_token: ${{ secrets.READER_TOKEN }}
|
||||
|
||||
- name: Lint
|
||||
run: yarn lint
|
||||
|
||||
validate-examples:
|
||||
name: "Validate Aksel.nav.no component examples"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup project
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
npm_auth_token: ${{ secrets.READER_TOKEN }}
|
||||
|
||||
- name: Validate example code
|
||||
run: yarn validate:eksempler
|
||||
|
||||
test:
|
||||
name: "Tests: Unit and integration"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup project
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
npm_auth_token: ${{ secrets.READER_TOKEN }}
|
||||
|
||||
- name: Test
|
||||
run: yarn test
|
||||
|
||||
ui:
|
||||
name: "Tests: UI"
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
container:
|
||||
image: mcr.microsoft.com/playwright:v1.57.0-noble
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup project
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
npm_auth_token: ${{ secrets.READER_TOKEN }}
|
||||
|
||||
- name: Test UI
|
||||
run: yarn storybook:test --max-workers=2
|
||||
@@ -1,26 +0,0 @@
|
||||
name: Cleanup E2E Docker images
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 4 * * 1"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
package-name:
|
||||
- aksel-e2e
|
||||
steps:
|
||||
- name: Delete old container images
|
||||
uses: actions/delete-package-versions@e5bc658cc4c965c472efe991f8beea3981499c55 # actions/delete-package-versions@v5
|
||||
with:
|
||||
package-name: ${{ matrix.package-name }}
|
||||
package-type: container
|
||||
delete-only-untagged-versions: false
|
||||
min-versions-to-keep: 1
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,52 +0,0 @@
|
||||
name: Deploy dev (aksel.ansatt.dev.nav.no)
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- next
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build_and_deploy:
|
||||
name: Build and deploy to dev-gcp
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: "read"
|
||||
id-token: "write"
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup and build Next.js application
|
||||
uses: ./.github/actions/build-website
|
||||
with:
|
||||
use_cdn_assets: true
|
||||
npm_auth_token: ${{ secrets.READER_TOKEN }}
|
||||
nais_id_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
|
||||
nais_project_id: ${{ vars.NAIS_MANAGEMENT_PROJECT_ID }}
|
||||
sanity_read_token: ${{ secrets.SANITY_READ }}
|
||||
sanity_read_no_drafts_token: ${{ secrets.SANITY_READ_NO_DRAFTS }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: nais/docker-build-push@v0
|
||||
id: docker-push
|
||||
with:
|
||||
team: designsystem
|
||||
push_image: true
|
||||
dockerfile: aksel.nav.no/Dockerfile
|
||||
project_id: ${{ vars.NAIS_MANAGEMENT_PROJECT_ID }}
|
||||
identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
|
||||
|
||||
- name: Deploy to dev-gcp
|
||||
uses: nais/deploy/actions/deploy@v3
|
||||
env:
|
||||
RESOURCE: aksel.nav.no/nais-dev.yaml
|
||||
CLUSTER: dev-gcp
|
||||
IMAGE: ${{ steps.docker-push.outputs.image }}
|
||||
@@ -1,56 +0,0 @@
|
||||
name: Deploy Aksel MCP server
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "apps/mcp-server/**"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
IMAGE: ghcr.io/navikt/aksel
|
||||
|
||||
jobs:
|
||||
build_and_deploy:
|
||||
name: Build and deploy to prod-gcp
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: "read"
|
||||
id-token: "write"
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup project
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
npm_auth_token: ${{ secrets.READER_TOKEN }}
|
||||
|
||||
- name: Test mcp-server
|
||||
run: cd apps/mcp-server && yarn test
|
||||
|
||||
- name: Build mcp-server
|
||||
run: cd apps/mcp-server && yarn build
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: nais/docker-build-push@v0
|
||||
id: docker-push
|
||||
with:
|
||||
team: designsystem
|
||||
dockerfile: apps/mcp-server/Dockerfile
|
||||
project_id: ${{ vars.NAIS_MANAGEMENT_PROJECT_ID }}
|
||||
identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
|
||||
|
||||
- name: Deploy to prod-gcp
|
||||
uses: nais/deploy/actions/deploy@v3
|
||||
env:
|
||||
RESOURCE: apps/mcp-server/.nais/app.yaml
|
||||
CLUSTER: prod-gcp
|
||||
IMAGE: ${{ steps.docker-push.outputs.image }}
|
||||
@@ -1,85 +0,0 @@
|
||||
name: Deploy prod (aksel.nav.no)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "aksel.nav.no/**"
|
||||
- "apps/playroom/**"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
IMAGE: ghcr.io/navikt/aksel
|
||||
|
||||
jobs:
|
||||
sync_data_with_sanity:
|
||||
name: Sync data to Sanity
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup project
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
npm_auth_token: ${{ secrets.READER_TOKEN }}
|
||||
|
||||
- name: Run metadata generation
|
||||
run: yarn docgen:meta
|
||||
|
||||
- name: Update Sanity
|
||||
env:
|
||||
SANITY_WRITE: ${{ secrets.SANITY_WRITE }}
|
||||
GCP_CREDENTIALS: ${{ secrets.GCP_CREDENTIALS_BQ_READ }}
|
||||
run: yarn workspace aksel.nav.no sanity:update
|
||||
|
||||
build_and_deploy:
|
||||
name: Build and deploy to prod-gcp
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: "read"
|
||||
id-token: "write"
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup and build Next.js application
|
||||
uses: ./.github/actions/build-website
|
||||
with:
|
||||
use_cdn_assets: true
|
||||
production: true
|
||||
npm_auth_token: ${{ secrets.READER_TOKEN }}
|
||||
nais_id_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
|
||||
nais_project_id: ${{ vars.NAIS_MANAGEMENT_PROJECT_ID }}
|
||||
sanity_read_token: ${{ secrets.SANITY_READ }}
|
||||
sanity_read_no_drafts_token: ${{ secrets.SANITY_READ_NO_DRAFTS }}
|
||||
sanity_deploy_studio: ${{ secrets.SANITY_DEPLOY_STUDIO }}
|
||||
next_encryption_key: ${{ secrets.NEXT_ENCRYPTION_KEY }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: nais/docker-build-push@v0
|
||||
id: docker-push
|
||||
with:
|
||||
team: designsystem
|
||||
push_image: true
|
||||
dockerfile: aksel.nav.no/Dockerfile
|
||||
project_id: ${{ vars.NAIS_MANAGEMENT_PROJECT_ID }}
|
||||
identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
|
||||
|
||||
- name: Deploy to prod-gcp
|
||||
uses: nais/deploy/actions/deploy@v3
|
||||
env:
|
||||
RESOURCE: aksel.nav.no/nais-prod.yaml
|
||||
CLUSTER: prod-gcp
|
||||
IMAGE: ${{ steps.docker-push.outputs.image }}
|
||||
@@ -1,61 +0,0 @@
|
||||
name: Deploy prod (aksel-v7.nav.no)
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- v7-aksel.nav.no
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build_and_deploy:
|
||||
name: Build and deploy to prod-gcp
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/v7-aksel.nav.no'
|
||||
permissions:
|
||||
contents: "read"
|
||||
id-token: "write"
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd v7-aksel.nav.no && yarn --immutable
|
||||
shell: bash
|
||||
|
||||
- name: Set production env var
|
||||
run: |
|
||||
echo "PRODUCTION=true" >> v7-aksel.nav.no/website/.env
|
||||
shell: bash
|
||||
|
||||
- name: Build Next.js website
|
||||
run: cd v7-aksel.nav.no && yarn build
|
||||
shell: bash
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: nais/docker-build-push@v0
|
||||
id: docker-push
|
||||
with:
|
||||
team: designsystem
|
||||
push_image: true
|
||||
dockerfile: v7-aksel.nav.no/Dockerfile
|
||||
project_id: ${{ vars.NAIS_MANAGEMENT_PROJECT_ID }}
|
||||
identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
|
||||
|
||||
- name: Deploy to prod-gcp
|
||||
uses: nais/deploy/actions/deploy@v3
|
||||
env:
|
||||
RESOURCE: v7-aksel.nav.no/nais-prod.yaml
|
||||
CLUSTER: prod-gcp
|
||||
IMAGE: ${{ steps.docker-push.outputs.image }}
|
||||
@@ -0,0 +1,129 @@
|
||||
name: E2E / Playwright Tests
|
||||
on:
|
||||
push:
|
||||
branches: [main, next]
|
||||
paths:
|
||||
- "aksel.nav.no/**"
|
||||
pull_request:
|
||||
branches: [main, next]
|
||||
paths:
|
||||
- "aksel.nav.no/**"
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
prebuild-app-for-playwright:
|
||||
name: "Prebuild app for Playwright tests"
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
IMAGE_NAME: ${{ github.repository }}-e2e
|
||||
REGISTRY: ghcr.io
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup and build Next.js application
|
||||
uses: ./.github/actions/build-website
|
||||
with:
|
||||
use_cdn_assets: false
|
||||
npm_auth_token: ${{ secrets.READER_TOKEN }}
|
||||
nais_id_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
|
||||
nais_project_id: ${{ vars.NAIS_MANAGEMENT_PROJECT_ID }}
|
||||
sanity_read_token: ${{ secrets.SANITY_READ }}
|
||||
sanity_read_no_drafts_token: ${{ secrets.SANITY_READ_NO_DRAFTS }}
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: aksel.nav.no/Dockerfile
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
|
||||
outputs:
|
||||
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
|
||||
|
||||
run-playwright:
|
||||
needs: [prebuild-app-for-playwright]
|
||||
name: Playwright shard ${{ matrix.shardIndex }}
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: mcr.microsoft.com/playwright:v1.57.0-noble
|
||||
services:
|
||||
testapp:
|
||||
image: ${{ needs.prebuild-app-for-playwright.outputs.image }}
|
||||
ports: ["3000:3000"]
|
||||
env:
|
||||
SANITY_READ: ${{ secrets.SANITY_READ }}
|
||||
SANITY_READ_NO_DRAFTS: ${{ secrets.SANITY_READ_NO_DRAFTS }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shardIndex: [1, 2, 3, 4, 5]
|
||||
shardTotal: [5]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: yarn
|
||||
cache-dependency-path: "yarn.lock"
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn --immutable
|
||||
shell: bash
|
||||
env:
|
||||
NPM_AUTH_TOKEN: ${{ secrets.READER_TOKEN }}
|
||||
|
||||
- name: Run Playwright
|
||||
shell: bash
|
||||
run: yarn e2e:docker --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
|
||||
env:
|
||||
SANITY_READ: ${{ secrets.SANITY_READ }}
|
||||
SANITY_READ_NO_DRAFTS: ${{ secrets.SANITY_READ_NO_DRAFTS }}
|
||||
|
||||
- name: Upload blob report to GitHub Actions Artifacts
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: blob-report-${{ matrix.shardIndex }}
|
||||
path: aksel.nav.no/website/blob-report
|
||||
retention-days: 1
|
||||
|
||||
merge-reports:
|
||||
if: ${{ !cancelled() }}
|
||||
needs: [run-playwright]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download blob reports from GitHub Actions Artifacts
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
path: all-blob-reports
|
||||
pattern: blob-report-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Merge into HTML Report
|
||||
run: npx playwright merge-reports --reporter html ./all-blob-reports
|
||||
|
||||
- name: Upload HTML report
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: html-report--attempt-${{ github.run_attempt }}
|
||||
path: playwright-report
|
||||
retention-days: 14
|
||||
@@ -0,0 +1,50 @@
|
||||
name: "Update remote token config for figma variables plugin"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
paths:
|
||||
- "@navikt/core/tokens/**"
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: "read"
|
||||
id-token: "write"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: yarn
|
||||
cache-dependency-path: "yarn.lock"
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
env:
|
||||
NPM_AUTH_TOKEN: ${{ secrets.READER_TOKEN }}
|
||||
|
||||
- name: Build tokens
|
||||
run: yarn workspace @navikt/ds-tokens build
|
||||
|
||||
- id: upload
|
||||
uses: nais/deploy/actions/cdn-upload/v2@master
|
||||
with:
|
||||
team: designsystem
|
||||
source: "./@navikt/core/tokens/figma-config.json"
|
||||
destination: "./@navikt/tokens"
|
||||
project_id: ${{ vars.NAIS_MANAGEMENT_PROJECT_ID }}
|
||||
identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
|
||||
source_keep_parent_name: false
|
||||
no_cache_paths: "@navikt/tokens/figma-config.json"
|
||||
|
||||
- run: echo uploaded file ${{ steps.upload.outputs.uploaded }}
|
||||
shell: bash
|
||||
@@ -0,0 +1,32 @@
|
||||
name: "push-to-cdn"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
paths:
|
||||
- "fonts/**"
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: "read"
|
||||
id-token: "write"
|
||||
|
||||
steps:
|
||||
- uses: "actions/checkout@v6"
|
||||
|
||||
- id: upload
|
||||
uses: nais/deploy/actions/cdn-upload/v2@master
|
||||
with:
|
||||
team: aksel
|
||||
source: ./fonts
|
||||
destination: "" # uploads folder to team root
|
||||
project_id: ${{ vars.NAIS_MANAGEMENT_PROJECT_ID }}
|
||||
identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
|
||||
|
||||
- run: echo uploaded file ${{ steps.upload.outputs.uploaded }}
|
||||
shell: bash
|
||||
@@ -0,0 +1,38 @@
|
||||
name: Deploy referansesider SPA
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: "read"
|
||||
id-token: "write"
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup project
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
npm_auth_token: ${{ secrets.READER_TOKEN }}
|
||||
|
||||
- run: cd ./examples/referansesider && yarn && yarn build
|
||||
- uses: nais/deploy/actions/spa-deploy/v2@master
|
||||
with:
|
||||
app: aksel-referansesider
|
||||
team: designsystem
|
||||
source: ./examples/referansesider/dist
|
||||
ingress: https://aksel-referansesider.ansatt.dev.nav.no
|
||||
environment: dev
|
||||
project_id: ${{ vars.NAIS_MANAGEMENT_PROJECT_ID }}
|
||||
identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
|
||||
cache_invalidation: true
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Release packages
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -13,9 +13,8 @@ concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Release packages
|
||||
# Only publish from main, also when dispatched manually
|
||||
if: github.ref == 'refs/heads/main'
|
||||
name: "Release packages"
|
||||
if: ${{!contains(github.event.head_commit.message, '[ci skip]')}}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: "write"
|
||||
@@ -23,18 +22,20 @@ jobs:
|
||||
packages: "write"
|
||||
pull-requests: "write"
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v7
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: yarn
|
||||
cache-dependency-path: "yarn.lock"
|
||||
|
||||
- uses: denoland/setup-deno@v2
|
||||
with:
|
||||
deno-version: v1.x
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
env:
|
||||
@@ -54,7 +55,7 @@ jobs:
|
||||
# Needed for push to main
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create .npmrc for GPR
|
||||
- name: Creating .npmrc
|
||||
run: |
|
||||
cat << EOF > "$HOME/.npmrc"
|
||||
@navikt:registry=https://npm.pkg.github.com
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# https://github.com/actions/stale
|
||||
name: Mark stale issues
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "31 3 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v10
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-issue-stale: 120
|
||||
days-before-pr-stale: -1
|
||||
days-before-close: 30
|
||||
any-of-labels: "Besvart"
|
||||
stale-issue-message: |-
|
||||
Hei! Det ser ut som det ikke har vært aktivitet her på en stund. Etter 30 dager vil saken bli lukket automatisk.
|
||||
|
||||
Om saken fortsatt er relevant eller du har oppdateringer, vil en kommentar hindre at den lukkes. Du kan alltids åpne den igjen hvis det skulle være nødvendig!
|
||||
stale-issue-label: "no-issue-activity"
|
||||
ascending: true
|
||||
operations-per-run: 200
|
||||
@@ -0,0 +1,28 @@
|
||||
name: Redeploy Naisjob (update-article-views)
|
||||
on:
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
execute:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: "write"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- uses: nais/docker-build-push@v0
|
||||
id: docker-push
|
||||
with:
|
||||
team: designsystem
|
||||
push_image: true
|
||||
dockerfile: scripts/deno/article-views/Dockerfile.update-article-views
|
||||
project_id: ${{ vars.NAIS_MANAGEMENT_PROJECT_ID }}
|
||||
identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
|
||||
|
||||
- name: start naisjob prod-gcp
|
||||
uses: nais/deploy/actions/deploy@master
|
||||
env:
|
||||
APIKEY: ${{ secrets.NAIS_DEPLOY_APIKEY }}
|
||||
RESOURCE: scripts/deno/article-views/nais-update-views-job.yml
|
||||
CLUSTER: prod-gcp
|
||||
IMAGE: ${{ steps.docker-push.outputs.image }}
|
||||
+9
-2
@@ -90,10 +90,15 @@ dist/
|
||||
assets.json
|
||||
lib/
|
||||
lerna-debug.log
|
||||
examples/legacy/node_modules
|
||||
examples/legacy/out
|
||||
examples/webpack/node_modules
|
||||
examples/webpack/out
|
||||
.gitattributes
|
||||
.iml
|
||||
.DS_Store
|
||||
|
||||
*.d.ts
|
||||
*-exports.less
|
||||
*/figma-api/icons
|
||||
*/ds-icons/png
|
||||
@@ -107,12 +112,13 @@ utilities/**/css
|
||||
*/**/NAV-ikonpakke-png.zip
|
||||
*/**/NAV-ikonpakke-svg.zip
|
||||
|
||||
react-docgen.json
|
||||
react-docgen-docs.json
|
||||
cjs
|
||||
esm
|
||||
@navikt/community/navno/css
|
||||
.vercel
|
||||
_docs.json
|
||||
_metadata.json
|
||||
**/component-names.generated.ts
|
||||
|
||||
.yarn/*
|
||||
.yarn/cache
|
||||
@@ -130,6 +136,7 @@ _metadata.json
|
||||
.next
|
||||
!*/website/lib/
|
||||
!*/website/public/
|
||||
!aksel.nav.no/**/*.d.ts
|
||||
!*/sanity/lib/
|
||||
!*/sanity/static/
|
||||
out
|
||||
|
||||
+34
-7
@@ -1,9 +1,36 @@
|
||||
echo "husky - DEPRECATED
|
||||
|
||||
Please remove the following two lines from $0:
|
||||
|
||||
#!/usr/bin/env sh
|
||||
. \"\$(dirname -- \"\$0\")/_/husky.sh\"
|
||||
if [ -z "$husky_skip_init" ]; then
|
||||
debug () {
|
||||
if [ "$HUSKY_DEBUG" = "1" ]; then
|
||||
echo "husky (debug) - $1"
|
||||
fi
|
||||
}
|
||||
|
||||
They WILL FAIL in v10.0.0
|
||||
"
|
||||
readonly hook_name="$(basename -- "$0")"
|
||||
debug "starting $hook_name..."
|
||||
|
||||
if [ "$HUSKY" = "0" ]; then
|
||||
debug "HUSKY env variable is set to 0, skipping hook"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -f ~/.huskyrc ]; then
|
||||
debug "sourcing ~/.huskyrc"
|
||||
. ~/.huskyrc
|
||||
fi
|
||||
|
||||
readonly husky_skip_init=1
|
||||
export husky_skip_init
|
||||
sh -e "$0" "$@"
|
||||
exitCode="$?"
|
||||
|
||||
if [ $exitCode != 0 ]; then
|
||||
echo "husky - $hook_name hook exited with code $exitCode (error)"
|
||||
fi
|
||||
|
||||
if [ $exitCode = 127 ]; then
|
||||
echo "husky - command not found in PATH=$PATH"
|
||||
fi
|
||||
|
||||
exit $exitCode
|
||||
fi
|
||||
+6
-10
@@ -1,13 +1,9 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
CHANGED=`git diff $1 $2 --stat -- yarn.lock | wc -l`
|
||||
if [ $CHANGED -gt 0 ];
|
||||
then
|
||||
printf '\n\033[1;33m'
|
||||
printf ' ╔══════════════════════════════════════════╗\n'
|
||||
printf ' ║ ║\n'
|
||||
printf ' ║ !! yarn.lock has changed !! ║\n'
|
||||
printf ' ║ ║\n'
|
||||
printf ' ║ Run: yarn install ║\n'
|
||||
printf ' ║ ║\n'
|
||||
printf ' ╚══════════════════════════════════════════╝\n'
|
||||
printf '\033[0m\n'
|
||||
fi
|
||||
echo
|
||||
echo "*** 🟡 yarn.lock has changed 🟡 ***"
|
||||
fi
|
||||
+6
-10
@@ -1,13 +1,9 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
CHANGED=`git diff HEAD@{1} --stat -- yarn.lock | wc -l`
|
||||
if [ $CHANGED -gt 0 ];
|
||||
then
|
||||
printf '\n\033[1;33m'
|
||||
printf ' ╔══════════════════════════════════════════╗\n'
|
||||
printf ' ║ ║\n'
|
||||
printf ' ║ !! yarn.lock has changed !! ║\n'
|
||||
printf ' ║ ║\n'
|
||||
printf ' ║ Run: yarn install ║\n'
|
||||
printf ' ║ ║\n'
|
||||
printf ' ╚══════════════════════════════════════════╝\n'
|
||||
printf '\033[0m\n'
|
||||
fi
|
||||
echo
|
||||
echo "*** 🟡 yarn.lock has changed 🟡 ***"
|
||||
fi
|
||||
+4
-2
@@ -1,2 +1,4 @@
|
||||
# Runs the local lint-staged checks before committing
|
||||
./node_modules/.bin/lint-staged
|
||||
#!/bin/sh
|
||||
. "$(dirname $0)/_/husky.sh"
|
||||
|
||||
yarn lint-staged && yarn tsx ./scripts/in-sync-versions.ts
|
||||
@@ -1,10 +0,0 @@
|
||||
# Only run tests when pushing to main branch
|
||||
while read local_ref local_sha remote_ref remote_sha; do
|
||||
if [ "$remote_ref" = "refs/heads/main" ]; then
|
||||
echo "Pushing to main branch - running tests...\n"
|
||||
yarn test
|
||||
exit $?
|
||||
fi
|
||||
done
|
||||
|
||||
exit 0
|
||||
@@ -1,3 +1,5 @@
|
||||
**/nais*.yaml
|
||||
**/nais*.yml
|
||||
**/codemod/**/tests/**/*.js
|
||||
**/@navikt/aksel/**/*.input.*
|
||||
**/@navikt/aksel/**/*.output.*
|
||||
|
||||
@@ -15,6 +15,11 @@ body {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.colgap[data-theme="dark"],
|
||||
.rowgap[data-theme="dark"] {
|
||||
background-color: var(--a-gray-900);
|
||||
}
|
||||
|
||||
.rowgap-wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
+36
-57
@@ -1,11 +1,13 @@
|
||||
import { defineMain } from "@storybook/react-vite/node";
|
||||
import { StorybookConfig } from "@storybook/react-vite";
|
||||
import FG from "fast-glob";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { loadCsf } from "storybook/internal/csf-tools";
|
||||
import { InlineConfig } from "vite";
|
||||
import turbosnap from "vite-plugin-turbosnap";
|
||||
import TsconfigPathsPlugin from "vite-tsconfig-paths";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
@@ -15,64 +17,35 @@ const includeWebsiteStories = process.env.WITH_WEBSITE === "true";
|
||||
|
||||
const indexRegex = /export const args = {\s+index: (\d+),/;
|
||||
|
||||
const addons = [
|
||||
getAbsolutePath("@storybook/addon-a11y"),
|
||||
getAbsolutePath("@storybook/addon-themes"),
|
||||
getAbsolutePath("@storybook/addon-docs"),
|
||||
getAbsolutePath("@storybook/addon-vitest"),
|
||||
];
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
addons.push(getAbsolutePath("@github-ui/storybook-addon-performance-panel"));
|
||||
}
|
||||
|
||||
export default defineMain({
|
||||
export default {
|
||||
experimental_indexers: (indexers) => {
|
||||
// Changes here might need to be reflected in aksel.nav.no/website/.storybook/main.ts
|
||||
const customIndexer = async (fileName: string, opts: any) => {
|
||||
try {
|
||||
let code = readFileSync(fileName, "utf-8").toString();
|
||||
let code = readFileSync(fileName, "utf-8").toString();
|
||||
|
||||
const index = indexRegex.exec(code)?.[1];
|
||||
const index = indexRegex.exec(code)?.[1];
|
||||
|
||||
code = code.split(
|
||||
/\/\/ EXAMPLES DO NOT INCLUDE CONTENT BELOW THIS LINE/,
|
||||
)[0];
|
||||
code = code.split(
|
||||
/\/\/ EXAMPLES DO NOT INCLUDE CONTENT BELOW THIS LINE/,
|
||||
)[0];
|
||||
|
||||
const templatePath = fileName.split(`pages/templates/`)[1];
|
||||
if (!templatePath) {
|
||||
console.warn(`[Storybook] Invalid template path: ${fileName}`);
|
||||
return [];
|
||||
}
|
||||
const [templateName, name] = fileName
|
||||
.split(`pages/templates/`)[1]
|
||||
.replace(".tsx", "")
|
||||
.split("/");
|
||||
const storyName = process.env.CHROMATIC
|
||||
? `${templateName} [${index}] ${name}` // Chromatic does not support folders, and doesn't like |
|
||||
: `${index} | ${name}`;
|
||||
|
||||
const [templateName, name] = templatePath
|
||||
.replace(".tsx", "")
|
||||
.split("/");
|
||||
|
||||
if (!templateName || !name) {
|
||||
console.warn(
|
||||
`[Storybook] Could not parse template name from: ${fileName}`,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
const storyName = process.env.CHROMATIC
|
||||
? `${templateName} [${index ?? "0"}] ${name}` // Chromatic does not support folders, and doesn't like |
|
||||
: `${index ?? "0"} | ${name}`;
|
||||
|
||||
code += `
|
||||
code += `
|
||||
export default { title: "Templates/${templateName}/${storyName}" };
|
||||
export const Demo = { render: Example };
|
||||
Demo.storyName = "${storyName}";`;
|
||||
|
||||
return loadCsf(code, {
|
||||
...opts,
|
||||
fileName,
|
||||
}).parse().indexInputs;
|
||||
} catch (error) {
|
||||
console.error(`[Storybook] Failed to index ${fileName}:`, error);
|
||||
return [];
|
||||
}
|
||||
return loadCsf(code, {
|
||||
...opts,
|
||||
fileName,
|
||||
}).parse().indexInputs;
|
||||
};
|
||||
|
||||
return [
|
||||
@@ -86,9 +59,14 @@ export default defineMain({
|
||||
|
||||
staticDirs: ["./public"],
|
||||
|
||||
stories: resolveStoriesPaths(),
|
||||
stories: resolveStoriesPaths,
|
||||
|
||||
addons,
|
||||
addons: [
|
||||
getAbsolutePath("@storybook/addon-a11y"),
|
||||
getAbsolutePath("@storybook/addon-themes"),
|
||||
getAbsolutePath("@storybook/addon-docs"),
|
||||
getAbsolutePath("@storybook/addon-vitest"),
|
||||
],
|
||||
|
||||
framework: {
|
||||
name: getAbsolutePath("@storybook/react-vite"),
|
||||
@@ -103,20 +81,21 @@ export default defineMain({
|
||||
async viteFinal(config, { configType }) {
|
||||
const { mergeConfig } = await import("vite");
|
||||
|
||||
// The TsconfigPathsPlugin is only used to silence errors when importing nextjs components, but the imports does not acutally work.
|
||||
const tsConfigPathsPluginOpts = { root: "aksel.nav.no/website/" };
|
||||
|
||||
return mergeConfig(config, {
|
||||
build: { cssMinify: "lightningcss" },
|
||||
resolve: {
|
||||
tsconfigPaths: true,
|
||||
},
|
||||
plugins:
|
||||
configType === "PRODUCTION"
|
||||
? [
|
||||
TsconfigPathsPlugin(tsConfigPathsPluginOpts),
|
||||
turbosnap({
|
||||
rootDir: config.root ?? process.cwd(),
|
||||
}),
|
||||
]
|
||||
: [],
|
||||
} satisfies typeof config);
|
||||
: [TsconfigPathsPlugin(tsConfigPathsPluginOpts)],
|
||||
} satisfies InlineConfig);
|
||||
},
|
||||
|
||||
/* Lets us preview Roboto-flex font in storybook. */
|
||||
@@ -129,9 +108,9 @@ export default defineMain({
|
||||
features: {
|
||||
actions: false,
|
||||
},
|
||||
});
|
||||
} satisfies StorybookConfig;
|
||||
|
||||
function getAbsolutePath(value: string) {
|
||||
function getAbsolutePath(value: string): any {
|
||||
return dirname(require.resolve(join(value, "package.json")));
|
||||
}
|
||||
|
||||
|
||||
+63
-133
@@ -1,104 +1,48 @@
|
||||
import addonPerformancePanel from "@github-ui/storybook-addon-performance-panel";
|
||||
import addonA11y from "@storybook/addon-a11y";
|
||||
import addonDocs from "@storybook/addon-docs";
|
||||
import addonThemes, { withThemeByClassName } from "@storybook/addon-themes";
|
||||
import addonVitest from "@storybook/addon-vitest";
|
||||
import { definePreview } from "@storybook/react-vite";
|
||||
import React, { useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import "../@navikt/core/css/src/data-token-filter.css";
|
||||
import dataTokenFilterStyles from "../@navikt/core/css/src/data-token-filter.css?inline";
|
||||
import "../@navikt/core/css/src/data-toolbar.css";
|
||||
import dataToolbarStyles from "../@navikt/core/css/src/data-toolbar.css?inline";
|
||||
import "../@navikt/core/css/src/form/combobox2.css";
|
||||
import combobox2Styles from "../@navikt/core/css/src/form/combobox2.css?inline";
|
||||
import "../@navikt/core/css/src/index.css";
|
||||
import dsStyles from "../@navikt/core/css/src/index.css?inline";
|
||||
import "../@navikt/core/css/src/listbox.css";
|
||||
import listboxStyles from "../@navikt/core/css/src/listbox.css?inline";
|
||||
import { withThemeByClassName } from "@storybook/addon-themes";
|
||||
import { Preview } from "@storybook/nextjs";
|
||||
import React, { useEffect, useLayoutEffect } from "react";
|
||||
// @ts-expect-error - Temporary
|
||||
import darksideCss from "../@navikt/core/css/darkside/index.css?inline";
|
||||
// @ts-expect-error - Temporary
|
||||
import defaultCss from "../@navikt/core/css/index.css?inline";
|
||||
import { Provider } from "../@navikt/core/react/src/provider";
|
||||
import type { Translations } from "../@navikt/core/react/src/utils/i18n/i18n.types";
|
||||
import { en, nb, nn } from "../@navikt/core/react/src/utils/i18n/locales";
|
||||
import { Theme } from "../@navikt/core/react/src/theme";
|
||||
import { Translations } from "../@navikt/core/react/src/util/i18n/i18n.types";
|
||||
import { en, nb, nn } from "../@navikt/core/react/src/util/i18n/locales";
|
||||
import "./layout.css";
|
||||
|
||||
type Language = "nb" | "nn" | "en";
|
||||
type WebComponentMode = "off" | "open" | "closed";
|
||||
const ModeDecorator = ({ children, mode, theme }) => {
|
||||
useLayoutEffect(() => {
|
||||
const style = document.createElement("style");
|
||||
style.innerHTML = mode === "darkside" ? darksideCss : defaultCss;
|
||||
document.head.appendChild(style);
|
||||
|
||||
if (mode === "darkside") {
|
||||
document.body.style.setProperty("background", "var(--ax-bg-default)");
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.head.removeChild(style);
|
||||
document.body.style.removeProperty("background");
|
||||
};
|
||||
}, [mode]);
|
||||
|
||||
return mode === "darkside" ? (
|
||||
<Theme theme={theme || undefined} hasBackground={false}>
|
||||
{children}
|
||||
</Theme>
|
||||
) : (
|
||||
children
|
||||
);
|
||||
};
|
||||
|
||||
type Language = "nb" | "nn" | "en";
|
||||
const locales: Record<Language, Translations> = {
|
||||
nb,
|
||||
nn,
|
||||
en,
|
||||
};
|
||||
|
||||
const StoryProviderDecorator = ({
|
||||
children,
|
||||
lang,
|
||||
rootElement,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
lang: Language | undefined;
|
||||
rootElement?: HTMLElement;
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
document.documentElement.lang = lang || "nb";
|
||||
}, [lang]);
|
||||
|
||||
return (
|
||||
<Provider
|
||||
rootElement={rootElement}
|
||||
locale={lang ? locales[lang] : undefined}
|
||||
>
|
||||
{children}
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const webComponentStyles = [
|
||||
dataTokenFilterStyles,
|
||||
dataToolbarStyles,
|
||||
listboxStyles,
|
||||
combobox2Styles,
|
||||
dsStyles,
|
||||
].join("\n");
|
||||
|
||||
const WebComponentDecorator = ({
|
||||
children,
|
||||
mode,
|
||||
}: {
|
||||
children: (rootElement: HTMLDivElement) => React.ReactNode;
|
||||
mode: Exclude<WebComponentMode, "off">;
|
||||
}) => {
|
||||
const hostRef = useRef<HTMLDivElement>(null);
|
||||
const [rootElement, setRootElement] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const host = hostRef.current;
|
||||
if (!host) {
|
||||
return;
|
||||
}
|
||||
|
||||
const shadowRoot = host.attachShadow({ mode });
|
||||
const style = document.createElement("style");
|
||||
style.textContent = webComponentStyles;
|
||||
shadowRoot.appendChild(style);
|
||||
|
||||
const mount = document.createElement("div");
|
||||
shadowRoot.appendChild(mount);
|
||||
setRootElement(mount);
|
||||
|
||||
return () => {
|
||||
setRootElement(null);
|
||||
};
|
||||
}, [mode]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div ref={hostRef} key={mode} />
|
||||
{rootElement ? createPortal(children(rootElement), rootElement) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const fonts = ["Source Sans 3", "Open Sans"];
|
||||
|
||||
const TypoDecorator = ({
|
||||
@@ -111,26 +55,13 @@ const TypoDecorator = ({
|
||||
useEffect(() => {
|
||||
const fontVariable = fonts.includes(font) ? `"${font}", sans-serif` : null;
|
||||
document.body.style.setProperty("--ax-font-family", fontVariable);
|
||||
|
||||
return () => {
|
||||
document.body.style.removeProperty("--ax-font-family");
|
||||
};
|
||||
document.body.style.setProperty("--a-font-family", fontVariable);
|
||||
}, [font]);
|
||||
|
||||
return <>{children}</>;
|
||||
return children;
|
||||
};
|
||||
|
||||
const addons = [addonA11y(), addonThemes(), addonDocs(), addonVitest()];
|
||||
|
||||
/**
|
||||
* Fixes flaky interaction tests
|
||||
*/
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
addons.push(addonPerformancePanel());
|
||||
}
|
||||
|
||||
export default definePreview({
|
||||
addons,
|
||||
export default {
|
||||
parameters: {
|
||||
options: {
|
||||
storySort: {
|
||||
@@ -162,6 +93,21 @@ export default definePreview({
|
||||
},
|
||||
|
||||
globalTypes: {
|
||||
mode: {
|
||||
name: "Darkside",
|
||||
toolbar: {
|
||||
items: [
|
||||
{
|
||||
value: "default",
|
||||
icon: "hourglass",
|
||||
title: "Legacy theme",
|
||||
},
|
||||
{ value: "darkside", icon: "beaker", title: "Darkside theme" },
|
||||
],
|
||||
showName: true,
|
||||
dynamicTitle: true,
|
||||
},
|
||||
},
|
||||
language: {
|
||||
toolbar: {
|
||||
icon: "globe",
|
||||
@@ -181,23 +127,11 @@ export default definePreview({
|
||||
dynamicTitle: true,
|
||||
},
|
||||
},
|
||||
webComponentWrapper: {
|
||||
toolbar: {
|
||||
icon: "component",
|
||||
items: [
|
||||
{ value: "off", title: "DOM: Default" },
|
||||
{ value: "open", title: "Web component: Open shadow root" },
|
||||
{ value: "closed", title: "Web component: Closed shadow root" },
|
||||
],
|
||||
dynamicTitle: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
initialGlobals: {
|
||||
mode: "default",
|
||||
font: "Source Sans 3",
|
||||
webComponentWrapper: "off",
|
||||
},
|
||||
|
||||
decorators: [
|
||||
@@ -206,22 +140,18 @@ export default definePreview({
|
||||
<StoryFn />
|
||||
</TypoDecorator>
|
||||
),
|
||||
(StoryFn, context) => (
|
||||
<ModeDecorator mode={context.globals.mode} theme={context.globals.theme}>
|
||||
<StoryFn />
|
||||
</ModeDecorator>
|
||||
),
|
||||
(StoryFn, context) =>
|
||||
context.globals.webComponentWrapper === "off" ? (
|
||||
<StoryProviderDecorator lang={context.globals.language}>
|
||||
context.globals.language ? (
|
||||
<Provider locale={locales[context.globals.language as Language]}>
|
||||
<StoryFn />
|
||||
</StoryProviderDecorator>
|
||||
</Provider>
|
||||
) : (
|
||||
<WebComponentDecorator mode={context.globals.webComponentWrapper}>
|
||||
{(rootElement) => (
|
||||
<StoryProviderDecorator
|
||||
lang={context.globals.language}
|
||||
rootElement={rootElement}
|
||||
>
|
||||
<StoryFn />
|
||||
</StoryProviderDecorator>
|
||||
)}
|
||||
</WebComponentDecorator>
|
||||
<StoryFn />
|
||||
),
|
||||
withThemeByClassName({
|
||||
themes: {
|
||||
@@ -231,4 +161,4 @@ export default definePreview({
|
||||
defaultTheme: "light",
|
||||
}),
|
||||
],
|
||||
});
|
||||
} satisfies Preview;
|
||||
@@ -0,0 +1,7 @@
|
||||
import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview";
|
||||
import { setProjectAnnotations } from "@storybook/react-vite";
|
||||
import * as projectAnnotations from "./preview";
|
||||
|
||||
// This is an important step to apply the right configuration when testing your stories.
|
||||
// More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations
|
||||
setProjectAnnotations([a11yAddonAnnotations, projectAnnotations]);
|
||||
Vendored
+5
-4
@@ -1,12 +1,13 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"oxc.oxc-vscode",
|
||||
"stylelint.vscode-stylelint",
|
||||
"esbenp.prettier-vscode",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"denoland.vscode-deno",
|
||||
"esbenp.prettier-vscode",
|
||||
"github.vscode-github-actions",
|
||||
"ms-playwright.playwright",
|
||||
"sanity-io.vscode-sanity",
|
||||
"vunguyentuan.vscode-css-variables"
|
||||
"stylelint.vscode-stylelint",
|
||||
"vunguyentuan.vscode-css-variables",
|
||||
"bradlc.vscode-tailwindcss"
|
||||
]
|
||||
}
|
||||
Vendored
+6
-2
@@ -1,5 +1,9 @@
|
||||
{
|
||||
"cssVariables.lookupFiles": ["@navikt/core/tokens/dist/tokens.css"],
|
||||
"cssVariables.lookupFiles": [
|
||||
"@navikt/core/tokens/dist/tokens.css",
|
||||
"@navikt/core/tokens/dist/darkside/tokens.css"
|
||||
],
|
||||
"eslint.problems.shortenToSingleLine": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"files.watcherExclude": {
|
||||
"**/.yarn/**": true,
|
||||
@@ -8,6 +12,6 @@
|
||||
"**/cjs": true
|
||||
},
|
||||
"playwright.reuseBrowser": true,
|
||||
"js/ts.tsdk.path": "node_modules/typescript/lib",
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"deno.enablePaths": ["./scripts/deno"]
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
// oxlint-disable-next-line typescript/no-require-imports
|
||||
const template = require("./config/template");
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -1,155 +1,5 @@
|
||||
# @navikt/aksel-icons
|
||||
|
||||
## 8.22.1
|
||||
|
||||
## 8.22.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Icons: New icons `Gift`, `ArrowsPointingIn` and `ArrowsPointingOut`. ([#5484](https://github.com/navikt/aksel/pull/5484))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ESM: Mark ESM output as side-effect free, so bundlers like webpack, Turbopack and esbuild can tree-shake unused components and icons. ([#5472](https://github.com/navikt/aksel/pull/5472))
|
||||
|
||||
## 8.21.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Icons: Add 46 icons for text editing, tables, keyboard input, and filtering. ([#5458](https://github.com/navikt/aksel/pull/5458))
|
||||
|
||||
## 8.20.1
|
||||
|
||||
## 8.20.0
|
||||
|
||||
## 8.19.2
|
||||
|
||||
## 8.19.1
|
||||
|
||||
## 8.19.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Icons: Added new icon MenuHamburgerMagnifyingGlass ([#5368](https://github.com/navikt/aksel/pull/5368))
|
||||
|
||||
## 8.18.0
|
||||
|
||||
## 8.17.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Package: Fixed regression where package was published without dist-files ([`9673de4`](https://github.com/navikt/aksel/commit/9673de4c264479dd8ca04897003532595acad255))
|
||||
|
||||
## 8.17.1
|
||||
|
||||
## 8.17.0
|
||||
|
||||
## 8.16.2
|
||||
|
||||
## 8.16.1
|
||||
|
||||
## 8.16.0
|
||||
|
||||
## 8.15.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- New icons: Timeline and TimelineFill ([#5050](https://github.com/navikt/aksel/pull/5050))
|
||||
|
||||
## 8.14.0
|
||||
|
||||
## 8.13.1
|
||||
|
||||
## 8.13.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Icons: New icons Armchair, FocusRoom, PhoneRoom and Sleep ([#4979](https://github.com/navikt/aksel/pull/4979))
|
||||
|
||||
## 8.12.1
|
||||
|
||||
## 8.12.0
|
||||
|
||||
## 8.11.1
|
||||
|
||||
## 8.11.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Icons: New icon `Newsletter` and `NewsletterFill` ([#4887](https://github.com/navikt/aksel/pull/4887))
|
||||
|
||||
## 8.10.6
|
||||
|
||||
## 8.10.5
|
||||
|
||||
## 8.10.4
|
||||
|
||||
## 8.10.3
|
||||
|
||||
## 8.10.2
|
||||
|
||||
## 8.10.1
|
||||
|
||||
## 8.10.0
|
||||
|
||||
## 8.9.1
|
||||
|
||||
## 8.9.0
|
||||
|
||||
## 8.8.0
|
||||
|
||||
## 8.7.0
|
||||
|
||||
## 8.6.0
|
||||
|
||||
## 8.5.2
|
||||
|
||||
## 8.5.1
|
||||
|
||||
## 8.5.0
|
||||
|
||||
## 8.4.1
|
||||
|
||||
## 8.4.0
|
||||
|
||||
## 8.3.0
|
||||
|
||||
## 8.2.2
|
||||
|
||||
## 8.2.1
|
||||
|
||||
## 8.2.0
|
||||
|
||||
## 8.1.0
|
||||
|
||||
## 8.0.1
|
||||
|
||||
## 8.0.0
|
||||
|
||||
## 7.40.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Icons: New icon MagnifyingGlassCheckmark ([#4489](https://github.com/navikt/aksel/pull/4489))
|
||||
|
||||
## 7.39.1
|
||||
|
||||
## 7.39.0
|
||||
|
||||
## 7.38.0
|
||||
|
||||
## 7.37.0
|
||||
|
||||
## 7.36.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Icons: Removed 'updated_at'-field from AkselIcon-type. ([#4399](https://github.com/navikt/aksel/pull/4399))
|
||||
|
||||
## 7.35.3
|
||||
|
||||
## 7.35.2
|
||||
|
||||
## 7.35.1
|
||||
|
||||
## 7.35.0
|
||||
|
||||
@@ -2,11 +2,45 @@
|
||||
|
||||
## Foreslå nye ikoner
|
||||
|
||||
Om du ikke finner ikonet du leter etter kan du [lage et issue på GitHub](https://github.com/navikt/aksel/issues/new?template=new-icon.yaml).
|
||||
Om du ikke finner et ikonet du leter etter kan du [åpne et issue på github](https://github.com/navikt/aksel/issues/new?labels=nytt+✨%2Cikoner+🖼%2Cforespørsel+🥰&template&template=new-icon.yaml&title=%5BNytt+ikon%5D%3A+).
|
||||
|
||||
## Oppdatere ikon
|
||||
|
||||
Hvis du tenker et ikon fortjener en oppdatering eller er utdatert kan [du foreslå det her](https://github.com/navikt/aksel/issues/new?template=update-icon.yml).
|
||||
Hvis du tenker et ikon fortjener en oppdatering eller er utdatert kan [du foreslå det her.](https://github.com/navikt/aksel/issues/new?labels=forespørsel+🥰&template=update-icon.yml&title=%5BInnspill+til+ikon%5D%3A+)
|
||||
|
||||
## Legge til nye ikoner selv
|
||||
|
||||
Hvis du selv ønsker å legge til nye ikoner kan du kopiere filenes fra `/template` og legge de inn i `/icons` med egen data. Alle ikonene har naming-format `PascalCase`. Alle ikonene har 1 SVG-fil og 1 tilhørende YML-fil.
|
||||
|
||||
### Svg
|
||||
|
||||
- Sizing, width og height må være 24x24: `width="24" height="24" `
|
||||
- Viewbox: `viewBox="0 0 24 24"`
|
||||
- Fill på path: `fill="#202733"`
|
||||
|
||||
### Yml
|
||||
|
||||
```yml
|
||||
name: Template # Samme som filnavn
|
||||
category: Template category # Hovedkategori
|
||||
sub_category: Sub category # Underkategori
|
||||
keywords: # Samling med søkeord
|
||||
- template1
|
||||
- template2
|
||||
- template3
|
||||
variant: Stroke # Stroke eller Fill
|
||||
updated_at: 02.01.2023 # Dato for siste oppdatering
|
||||
created_at: 01.01.2023 # Dato for opprettelse
|
||||
```
|
||||
|
||||
### Publisere oppdatering
|
||||
|
||||
Når du har lagt til nye ikoner kan du opprette en pull request. Denne vil bli automatisk testet og så gått gjennom av Aksel-teamet.
|
||||
Legg gjerne til en `changeset` ved å kjøre `yarn changeset` i terminalen. Dette vil gi oss bedre oversikt over hva som er endret i endringslogger og automatisk bumpe pakke-versjonen ved merge.
|
||||
|
||||
#### Teste nytt ikon
|
||||
|
||||
Hvis du ønsker å teste om alt stemmer, både i `svg` og `yml` før du lager en PR kan du kjøre `yarn && cd navikt/aksel-icons && yarn test` i terminalen lokalt.
|
||||
|
||||
## Design av nye ikoner
|
||||
|
||||
@@ -17,9 +51,9 @@ Hvis du tenker et ikon fortjener en oppdatering eller er utdatert kan [du foresl
|
||||
|
||||
### Stroke (linje)
|
||||
|
||||
- Ikonet må bestå av kun linjer.
|
||||
- Linjene skal være 1.5px, center-aligned.
|
||||
- Ikonet må bestå av kun linjer
|
||||
- Linjene skal være 1.5px, center-aligned
|
||||
|
||||
### Fill (fylt)
|
||||
|
||||
- Ikoner hvor shapes kan fylles, bør fylles.
|
||||
- Ikoner hvor shapes kan fylles, bør fylles
|
||||
@@ -59,37 +59,6 @@ declare module "*.svg" {
|
||||
}
|
||||
```
|
||||
|
||||
### CSS custom properties
|
||||
|
||||
All icons are available as CSS custom properties behind `/css`, one file per icon. Each file declares a `--aksel-icon-<kebab-case-name>` variable on `:root`, containing the icon as an SVG data URI.
|
||||
|
||||
```css
|
||||
@import "@navikt/aksel-icons/css/MagnifyingGlass.css";
|
||||
|
||||
.search-button::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
background-color: currentColor;
|
||||
mask: var(--aksel-icon-magnifying-glass) center / contain no-repeat;
|
||||
}
|
||||
|
||||
@media (forced-colors: active) {
|
||||
.search-button::before {
|
||||
background-color: CanvasText;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To get every icon, import `@navikt/aksel-icons/css/index.css`. This file contains all ~1000 icons (≈700 kB, ≈135 kB gzipped), so prefer importing only the icons you use.
|
||||
|
||||
Import icon files once in a global stylesheet (e.g. your root layout or `globals.css`), not in CSS modules or per-component stylesheets. The variables are global, and the SVG is inlined in each file, so importing the same icon in several places duplicates it in every CSS chunk.
|
||||
|
||||
- Icons added with CSS are decorative only. Give the parent element an accessible name, e.g. with visible text or `aria-label`.
|
||||
- In forced colors mode (e.g. Windows High Contrast), `background-color` is forced to the page background, which hides the icon. Use `background-color: CanvasText` inside `@media (forced-colors: active)` as shown above, so the icon follows the user's text color.
|
||||
- If you use a Content Security Policy, `img-src` must allow `data:`.
|
||||
|
||||
## [Learn more](https://aksel.nav.no/ikoner)
|
||||
|
||||
## License
|
||||
|
||||
@@ -3,7 +3,7 @@ import { JSON_SCHEMA, load } from "js-yaml";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import type { IconYml } from "../config/figma/make-configs";
|
||||
import { IconYml } from "../config/figma/make-configs";
|
||||
|
||||
const basePath = path.resolve(__dirname, "../icons");
|
||||
|
||||
@@ -24,6 +24,8 @@ describe(`Each icons YML-config is valid`, () => {
|
||||
expect(ymlData.keywords).toBeTruthy();
|
||||
expect(ymlData.variant).toBeTruthy();
|
||||
expect(ymlData.keywords.length).toBeGreaterThan(0);
|
||||
expect(ymlData.updated_at).toBeTruthy();
|
||||
expect(isDate(ymlData.updated_at)).toBeTruthy();
|
||||
expect(ymlData.created_at).toBeTruthy();
|
||||
expect(isDate(ymlData.created_at)).toBeTruthy();
|
||||
});
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import fastglob from "fast-glob";
|
||||
import path from "node:path";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import {
|
||||
optimizeSvg,
|
||||
svgToDataUri,
|
||||
toCssVariableName,
|
||||
// @ts-expect-error -- css-icons is JavaScript without a declaration file.
|
||||
} from "../config/css-icons";
|
||||
|
||||
describe("toCssVariableName", () => {
|
||||
test.each([
|
||||
["MagnifyingGlass", "--aksel-icon-magnifying-glass"],
|
||||
["Buildings2Fill", "--aksel-icon-buildings-2-fill"],
|
||||
["ClipboardXMark", "--aksel-icon-clipboard-x-mark"],
|
||||
["XMark", "--aksel-icon-x-mark"],
|
||||
])("%s -> %s", (iconName, expected) => {
|
||||
expect(toCssVariableName(iconName)).toBe(expected);
|
||||
});
|
||||
|
||||
test("is unique for every icon", () => {
|
||||
const names = fastglob
|
||||
.sync("*.svg", { cwd: path.resolve(__dirname, "../icons") })
|
||||
.map((file) => toCssVariableName(path.basename(file, ".svg")));
|
||||
|
||||
expect(new Set(names).size).toBe(names.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe("svgToDataUri", () => {
|
||||
const svg = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.5 3.25 4.75 10.5Z" fill="#202733"/>
|
||||
</svg>`;
|
||||
|
||||
test("keeps viewBox, uses currentColor and escapes unsafe characters", () => {
|
||||
const uri = svgToDataUri(optimizeSvg(svg));
|
||||
const data = uri.slice('url("'.length, -'")'.length);
|
||||
|
||||
expect(uri.startsWith('url("data:image/svg+xml,')).toBe(true);
|
||||
expect(data).toContain("viewBox='0 0 24 24'");
|
||||
expect(data).toContain("fill='currentColor'");
|
||||
expect(data).not.toMatch(/["#<>]|width=|height=/);
|
||||
});
|
||||
});
|
||||
@@ -28,7 +28,7 @@ describe(`SVG should be correctly parsed`, () => {
|
||||
</svg>
|
||||
`;
|
||||
|
||||
test("should replace width and height with 1em, and fill with currentColor", () => {
|
||||
test("a", () => {
|
||||
expect(parseIcon(simpleSvg)).toEqual(simpleSvgResult);
|
||||
expect(parseIcon(advancedSvg)).toEqual(advancedSvgResult);
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* Fikser https://github.com/navikt/aksel/issues/1758
|
||||
*/
|
||||
// oxlint-disable-next-line typescript/no-require-imports
|
||||
const fs = require("node:fs");
|
||||
|
||||
const fs = require("fs");
|
||||
|
||||
const basePath = "./dist/react/esm";
|
||||
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
// oxlint-disable typescript/no-require-imports
|
||||
const fastglob = require("fast-glob");
|
||||
const kebabCase = require("lodash/kebabCase");
|
||||
const path = require("node:path");
|
||||
const {
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
readFileSync,
|
||||
rmSync,
|
||||
writeFileSync,
|
||||
} = require("node:fs");
|
||||
const { optimize } = require("svgo");
|
||||
|
||||
/**
|
||||
* @param {string} iconName e.g. "MagnifyingGlass"
|
||||
* @returns {string} e.g. "--aksel-icon-magnifying-glass"
|
||||
*/
|
||||
function toCssVariableName(iconName) {
|
||||
return `--aksel-icon-${kebabCase(iconName)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} svgString
|
||||
* @returns {string} Minified SVG with currentColor fill and preserved viewBox
|
||||
*/
|
||||
function optimizeSvg(svgString) {
|
||||
return optimize(svgString.replaceAll("#202733", "currentColor"), {
|
||||
multipass: true,
|
||||
plugins: [
|
||||
{
|
||||
name: "preset-default",
|
||||
params: { overrides: { removeViewBox: false } },
|
||||
},
|
||||
{ name: "removeAttrs", params: { attrs: "svg:(width|height)" } },
|
||||
],
|
||||
}).data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes SVG for use in a CSS `url()`. Keeps readable characters unescaped for smaller output.
|
||||
* @param {string} svgString
|
||||
* @returns {string}
|
||||
*/
|
||||
function svgToDataUri(svgString) {
|
||||
const encoded = svgString
|
||||
.replaceAll('"', "'")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim()
|
||||
.replace(/[^\x20-\x7e]|[%#<>{}"\\]/g, encodeURIComponent);
|
||||
|
||||
return `url("data:image/svg+xml,${encoded}")`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} declarations
|
||||
* @returns {string}
|
||||
*/
|
||||
function createCssFile(declarations) {
|
||||
return `:root {\n${declarations.map((d) => ` ${d}`).join("\n")}\n}\n`;
|
||||
}
|
||||
|
||||
function main() {
|
||||
const basePath = path.resolve(__dirname, "../icons");
|
||||
const cssFolder = path.resolve(__dirname, "../dist/css");
|
||||
|
||||
const svgList = fastglob.sync("*.svg", { cwd: basePath }).sort();
|
||||
|
||||
if (existsSync(cssFolder)) {
|
||||
rmSync(cssFolder, { recursive: true, force: true });
|
||||
}
|
||||
mkdirSync(cssFolder, { recursive: true });
|
||||
|
||||
const declarations = [];
|
||||
|
||||
for (const svg of svgList) {
|
||||
const iconName = path.basename(svg, ".svg");
|
||||
const svgString = readFileSync(path.join(basePath, svg)).toString();
|
||||
const declaration = `${toCssVariableName(iconName)}: ${svgToDataUri(optimizeSvg(svgString))};`;
|
||||
|
||||
declarations.push(declaration);
|
||||
writeFileSync(
|
||||
path.join(cssFolder, `${iconName}.css`),
|
||||
createCssFile([declaration]),
|
||||
);
|
||||
}
|
||||
|
||||
writeFileSync(path.join(cssFolder, "index.css"), createCssFile(declarations));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createCssFile,
|
||||
main,
|
||||
optimizeSvg,
|
||||
svgToDataUri,
|
||||
toCssVariableName,
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import type {
|
||||
import {
|
||||
BadRequestErrorResponseWithErrMessage,
|
||||
ErrorResponsePayloadWithErrMessage,
|
||||
GetFileComponentsResponse,
|
||||
@@ -12,17 +12,20 @@ const FIGMA_FILE_KEY = "wEdyFjCQSBR3U7FvrMbPXa";
|
||||
export const fetchIcons = async (): Promise<PublishedComponent[]> => {
|
||||
console.group("Fetching list of published icons from Figma...");
|
||||
const data:
|
||||
GetFileComponentsResponse | BadRequestErrorResponseWithErrMessage =
|
||||
await fetch(`https://api.figma.com/v1/files/${FIGMA_FILE_KEY}/components`, {
|
||||
| GetFileComponentsResponse
|
||||
| BadRequestErrorResponseWithErrMessage = await fetch(
|
||||
`https://api.figma.com/v1/files/${FIGMA_FILE_KEY}/components`,
|
||||
{
|
||||
headers: {
|
||||
"Content-type": "application/json",
|
||||
"X-FIGMA-TOKEN": process.env.FIGMA_TOKEN ?? "",
|
||||
},
|
||||
})
|
||||
.then((x) => x.json())
|
||||
.catch((e) => {
|
||||
throw e.message;
|
||||
});
|
||||
},
|
||||
)
|
||||
.then((x) => x.json())
|
||||
.catch((e) => {
|
||||
throw e.message;
|
||||
});
|
||||
|
||||
if (data.status !== 200) {
|
||||
throw new Error(data.err);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PublishedComponent } from "@figma/rest-api-spec";
|
||||
import { PublishedComponent } from "@figma/rest-api-spec";
|
||||
import { camelCase, startCase } from "lodash";
|
||||
|
||||
export const resolveName = (icon: PublishedComponent) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PublishedComponent } from "@figma/rest-api-spec";
|
||||
import { PublishedComponent } from "@figma/rest-api-spec";
|
||||
import { dump } from "js-yaml";
|
||||
import { writeFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
@@ -10,6 +10,7 @@ export type IconYml = {
|
||||
sub_category: string;
|
||||
keywords: string[];
|
||||
variant: string;
|
||||
updated_at: string;
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
@@ -41,6 +42,12 @@ export const makeConfig = (
|
||||
variant: icon.name.includes("Variant=")
|
||||
? icon.name.replace("Variant=", "")
|
||||
: "Stroke",
|
||||
updated_at: new Date(icon.updated_at)
|
||||
.toISOString()
|
||||
.split("T")[0]
|
||||
.split("-")
|
||||
.reverse()
|
||||
.join("."),
|
||||
created_at: new Date(icon.created_at)
|
||||
.toISOString()
|
||||
.split("T")[0]
|
||||
@@ -52,7 +59,7 @@ export const makeConfig = (
|
||||
const yml = dump(config, {
|
||||
noRefs: true,
|
||||
skipInvalid: false,
|
||||
quoteStyle: "double",
|
||||
quotingType: '"',
|
||||
});
|
||||
|
||||
writeFileSync(resolve(dirLocation, `${config.name}.yml`), yml, {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// oxlint-disable-next-line typescript/no-require-imports
|
||||
const path = require("node:path");
|
||||
const path = require("path");
|
||||
|
||||
const defaultIndexTemplate = (paths) => {
|
||||
const exportEntries = paths.map((filePath) => {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// oxlint-disable typescript/no-require-imports
|
||||
const fastglob = require("fast-glob");
|
||||
const path = require("node:path");
|
||||
const { load, JSON_SCHEMA } = require("js-yaml");
|
||||
const fs = require("node:fs");
|
||||
const path = require("path");
|
||||
const jsYaml = require("js-yaml");
|
||||
const fs = require("fs");
|
||||
|
||||
const basePath = path.resolve(__dirname, "../icons");
|
||||
|
||||
@@ -13,9 +12,10 @@ const ymlList = fastglob
|
||||
const metadata = {};
|
||||
|
||||
ymlList.forEach((file) => {
|
||||
const ymlData = load(fs.readFileSync(`${basePath}/${file}`), {
|
||||
schema: JSON_SCHEMA,
|
||||
const ymlData = jsYaml.load(fs.readFileSync(`${basePath}/${file}`), {
|
||||
schema: jsYaml.JSON_SCHEMA,
|
||||
});
|
||||
// biome-ignore lint/complexity/useOptionalChain: optional chain not supported in enviroment. Consider revriting to Typescript
|
||||
if (ymlData.keywords && ymlData.keywords.includes("[ignore-docs]")) {
|
||||
return;
|
||||
}
|
||||
@@ -42,9 +42,10 @@ fs.writeFileSync(
|
||||
id: string;
|
||||
name: string;
|
||||
category: string;
|
||||
sub_category: string;
|
||||
sub_category: "Time",
|
||||
keywords: string[];
|
||||
variant: "stroke" | "fill";
|
||||
updated_at: Date;
|
||||
created_at: Date;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
// oxlint-disable typescript/no-require-imports
|
||||
const fastglob = require("fast-glob");
|
||||
const path = require("node:path");
|
||||
const path = require("path");
|
||||
const {
|
||||
existsSync,
|
||||
readFileSync,
|
||||
mkdirSync,
|
||||
rmSync,
|
||||
writeFileSync,
|
||||
} = require("node:fs");
|
||||
} = require("fs");
|
||||
|
||||
function main() {
|
||||
const basePath = path.resolve(__dirname, "../icons");
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// oxlint-disable typescript/no-require-imports
|
||||
const AdmZip = require("adm-zip");
|
||||
const {
|
||||
existsSync,
|
||||
@@ -6,10 +5,10 @@ const {
|
||||
mkdirSync,
|
||||
rmSync,
|
||||
copyFileSync,
|
||||
} = require("node:fs");
|
||||
const path = require("node:path");
|
||||
} = require("fs");
|
||||
const path = require("path");
|
||||
const fastGlob = require("fast-glob");
|
||||
const { load, JSON_SCHEMA } = require("js-yaml");
|
||||
const jsYaml = require("js-yaml");
|
||||
|
||||
const iconFolder = "./core-icons";
|
||||
|
||||
@@ -27,8 +26,8 @@ async function generateFile() {
|
||||
const categories = new Map();
|
||||
|
||||
ymlList.forEach((file) => {
|
||||
const ymlData = load(readFileSync(`${basePath}/${file}`), {
|
||||
schema: JSON_SCHEMA,
|
||||
const ymlData = jsYaml.load(readFileSync(`${basePath}/${file}`), {
|
||||
schema: jsYaml.JSON_SCHEMA,
|
||||
});
|
||||
categories.set(
|
||||
ymlData.category,
|
||||
@@ -40,12 +39,12 @@ async function generateFile() {
|
||||
|
||||
categories.forEach((value, key) => {
|
||||
mkdirSync(`${iconFolder}/${key}`);
|
||||
value.forEach((icon) => {
|
||||
value.forEach((icon) =>
|
||||
copyFileSync(
|
||||
`${basePath}/${icon}.svg`,
|
||||
`${iconFolder}/${key}/${icon}.svg`,
|
||||
);
|
||||
});
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
!src
|
||||
!*.d.ts
|
||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "aksel-icons-figma-plugin",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite -c ./vite.config.ui.ts",
|
||||
"build:icon-plugin": "tsc && npm run build:ui && npm run build:plugin",
|
||||
"build:ui": "vite build -c ./vite.config.ui.ts --emptyOutDir",
|
||||
"build:plugin": "vite build -c ./vite.config.plugin.ts",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@navikt/ds-css": "*",
|
||||
"@navikt/ds-react": "*",
|
||||
"fuse.js": "^7.0.0",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@figma/plugin-typings": "^1.100.2",
|
||||
"@svgr/core": "^8.1.0",
|
||||
"@types/node": "^24.10.0",
|
||||
"@types/postcss-url": "^10.0.4",
|
||||
"@types/react": "^18.3.11",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"@vitejs/plugin-react": "^5.1.0",
|
||||
"postcss": "^8.4.31",
|
||||
"postcss-url": "^10.1.3",
|
||||
"typescript": "5.9.3",
|
||||
"vite": "^7.2.4",
|
||||
"vite-plugin-singlefile": "^2.2.0",
|
||||
"vite-plugin-svgr": "^4.2.0"
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import fs from "node:fs";
|
||||
import type { Plugin } from "vite";
|
||||
import { Plugin } from "vite";
|
||||
|
||||
function svgToDataURL(svgStr: string) {
|
||||
const encoded = encodeURIComponent(svgStr)
|
||||
+3
-3
@@ -1,6 +1,5 @@
|
||||
import { type FilterPattern, createFilter } from "@rollup/pluginutils";
|
||||
import { FilterPattern, createFilter } from "@rollup/pluginutils";
|
||||
import type { Config } from "@svgr/core";
|
||||
import { transform } from "@svgr/core";
|
||||
import fs from "node:fs";
|
||||
import type { Plugin } from "vite";
|
||||
import { transformWithEsbuild } from "vite";
|
||||
@@ -23,8 +22,9 @@ export default function viteSvgr({
|
||||
return {
|
||||
name: "vite-svgr-component",
|
||||
|
||||
async transform(_, id) {
|
||||
async transform(code, id) {
|
||||
if (filter(id)) {
|
||||
const { transform } = await import("@svgr/core");
|
||||
const svgCode = await fs.promises.readFile(
|
||||
id.replace(/\?.*$/, ""),
|
||||
"utf8",
|
||||
File renamed without changes.
File renamed without changes.
Loaded 100 of 5817 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user