mirror of
https://github.com/navikt/aksel.git
synced 2026-11-11 15:24:06 +00:00
Compare commits
44
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,7 +0,0 @@
|
||||
# https://min-copilot.ansatt.nav.no/cplt
|
||||
[propose]
|
||||
allow_docker = true
|
||||
allow_localhost_any = true
|
||||
|
||||
[propose.allow]
|
||||
localhost = [3000, 6006, 6007]
|
||||
@@ -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,19 +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: Build Next.js website
|
||||
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
-65
@@ -1,87 +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.
|
||||
- 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).
|
||||
|
||||
- Only respond in same language as the input. Prefer english always.
|
||||
- Use caveman-skill unless asked not to
|
||||
- No apologies, no "I think", no change summaries.
|
||||
- Answer-first. 0-2 sentences max.
|
||||
- No thinking aloud; do next edit. Explain only when asked or non-obvious.
|
||||
- Show only what changes. Minimal diffs; don't paste whole files.
|
||||
- Edits scoped to one file unless asked. 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 4, ESLint 9, Stylelint 17, Biome 2 (lint only), 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 @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.
|
||||
- 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 impl + closest story + test.
|
||||
- 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 eslint --max-warnings=0 --no-warn-ignored <files...>` / `corepack yarn stylelint <files...>` / `corepack yarn biome lint --no-errors-on-unmatched <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) / `corepack yarn dev` (website, :3000, needs Sanity token)
|
||||
- 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: `eslint.config.js`, `stylelint.config.mjs`, `biome.json`, `vitest.config.ts`, `.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
|
||||
- `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.
|
||||
- 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`.
|
||||
```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.
|
||||
+13
-17
@@ -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
|
||||
@@ -18,10 +22,8 @@ updates:
|
||||
sanity:
|
||||
patterns:
|
||||
- "sanity"
|
||||
- "sanity-*"
|
||||
- "@sanity/*"
|
||||
- "next-sanity"
|
||||
- "@portabletext/*"
|
||||
- "@sanity/vision"
|
||||
- "@sanity/schema"
|
||||
eslint:
|
||||
patterns:
|
||||
- "eslint"
|
||||
@@ -33,10 +35,13 @@ updates:
|
||||
storybook:
|
||||
patterns:
|
||||
- "*storybook*"
|
||||
vitest:
|
||||
# Group all other minor and patch updates to reduce noise
|
||||
dependencies:
|
||||
patterns:
|
||||
- "vitest"
|
||||
- "@vitest/*"
|
||||
- "*"
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/"
|
||||
@@ -50,12 +55,3 @@ updates:
|
||||
actions:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
- package-ecosystem: docker
|
||||
directories:
|
||||
- "/aksel.nav.no"
|
||||
- "/apps/*"
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: "04:00"
|
||||
@@ -1,78 +0,0 @@
|
||||
---
|
||||
description: >
|
||||
Ultra-compressed communication mode. Cuts token usage ~75% by speaking like caveman
|
||||
while keeping full technical accuracy. Supports intensity levels: lite, full (default), ultra.
|
||||
Always use caveman for code-related questions unless spesificed otherwise. For non-code topics, use caveman if user seems to want quick, concise answers.
|
||||
applyTo: "**"
|
||||
---
|
||||
|
||||
# caveman
|
||||
|
||||
ACTIVE EVERY RESPONSE.
|
||||
|
||||
default: full
|
||||
|
||||
switch mode:
|
||||
|
||||
- "/caveman lite" → lite
|
||||
- "/caveman full" → full
|
||||
- "/caveman ultra" → ultra
|
||||
- "stop caveman" | "normal mode" → off
|
||||
|
||||
persist: keep last state
|
||||
|
||||
## intensity
|
||||
|
||||
| Level | What change |
|
||||
| --------- | ------------------------------------------------------------------------------------------------------------- |
|
||||
| **lite** | short sentences, no filler, keep grammar |
|
||||
| **full** | fragments ok, drop articles, short words. Classic caveman |
|
||||
| **ultra** | Abbreviate (DB/auth/config/req/res/fn/impl), fragments, abbrev (db/api/req/res/fn), arrows (→), minimal words |
|
||||
|
||||
Example - "Why React component re-render?"
|
||||
|
||||
- lite: "Your component re-renders because you create a new object reference each render. Wrap it in `useMemo`."
|
||||
- full: "New object ref each render. Inline object prop = new ref = re-render. Wrap in `useMemo`."
|
||||
- ultra: "Inline obj prop → new ref → re-render. `useMemo`."
|
||||
|
||||
## global rules
|
||||
|
||||
- no filler (just/really/basically/etc)
|
||||
- no pleasantries
|
||||
- no hedging
|
||||
- keep tech exact
|
||||
- no long sentences
|
||||
- prefer symbols (→, =)
|
||||
|
||||
## patterns
|
||||
|
||||
Patterns: `[thing] [action] [reason]. [next step].`
|
||||
|
||||
## examples
|
||||
|
||||
lite:
|
||||
"Component re-renders because you create a new object each render. Use useMemo."
|
||||
|
||||
full:
|
||||
"New object each render → new ref → re-render. useMemo."
|
||||
|
||||
ultra:
|
||||
"inline obj → new ref → re-render. fix: useMemo."
|
||||
|
||||
## auto-Clarity
|
||||
|
||||
Drop caveman for: security warnings, irreversible action confirmations, multi-step sequences where fragment order risks misread, user asks to clarify or repeats question. Resume caveman after clear part done.
|
||||
|
||||
Example - destructive op:
|
||||
|
||||
> **Warning:** This will permanently delete all rows in the `users` table and cannot be undone.
|
||||
>
|
||||
> ```sql
|
||||
> DROP TABLE users;
|
||||
> ```
|
||||
>
|
||||
> Caveman resume. Verify backup exist first.
|
||||
|
||||
## boundaries
|
||||
|
||||
Code/commits/PRs: write normal. "stop caveman" or "normal mode": revert. Level persist until changed or session end
|
||||
@@ -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 ESLint/Biome: loops, export style and placement."
|
||||
applyTo: "**/*.ts,**/*.tsx,**/*.js,**/*.mts,**/*.mjs"
|
||||
---
|
||||
|
||||
# JS/TS conventions
|
||||
|
||||
Formatting, import order and `import type` are handled by Prettier and Biome. 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 ESLint 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,33 +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.
|
||||
|
||||
## Compatibility (why: peer range is `react ^17 || ^18 || ^19`)
|
||||
|
||||
- No React 18/19-only APIs. ESLint `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,15 +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.ts`). To opt out, use `tags: ["!play-fn", "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 plus 1-2 meaningful edge cases. A behavior change updates the nearest story and test in the same change.
|
||||
- ds-react unit tests run with `TZ=UTC`. Date logic must not depend on the local timezone.
|
||||
- Browser tests need Playwright Firefox. 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,14 +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.
|
||||
- `pages/eksempler/**` and `pages/templates/**` are public code snippets. Use the `website-example` skill. ESLint 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,109 +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 ESLint 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 eslint --max-warnings=0 --no-warn-ignored <changed .ts/.tsx/.js/.jsx files>
|
||||
corepack yarn stylelint <changed .css files>
|
||||
corepack yarn biome lint --no-errors-on-unmatched <changed supported source/config 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,113 +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**: get the diff (`gh pr diff <n>` or `git diff origin/main...HEAD`) and the PR description. Note the stated goal.
|
||||
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.
|
||||
|
||||
## 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 ESLint, Stylelint, Biome, 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/`.
|
||||
|
||||
### 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 (🟡)
|
||||
|
||||
- A behavior change 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.
|
||||
- Website examples (`aksel.nav.no/website/pages/eksempler/`) are updated when usage changes.
|
||||
|
||||
### 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).
|
||||
|
||||
### 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,294 +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. The component `index.ts` re-exports the compound component, its sub-components, and their types from the root file:
|
||||
|
||||
```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`, re-export them from there.
|
||||
|
||||
#### 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
|
||||
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 };
|
||||
```
|
||||
|
||||
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).
|
||||
|
||||
**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 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 `index.ts` pattern: `@navikt/core/react/src/dialog/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 ESLint
|
||||
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 eslint --max-warnings=0 --no-warn-ignored <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
|
||||
|
||||
ESLint 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,48 +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
|
||||
|
||||
- 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,134 +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:
|
||||
actions: read
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
prebuild-app-for-playwright:
|
||||
name: Build app for Playwright
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
IMAGE_NAME: ${{ github.repository }}-e2e
|
||||
REGISTRY: ghcr.io
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
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@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
|
||||
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
|
||||
|
||||
- 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,90 +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 (ESLint, Stylelint etc.)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
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 component examples
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
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: Unit and integration tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
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: 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
|
||||
|
||||
- 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:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- 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,147 +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
|
||||
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
|
||||
|
||||
- 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
|
||||
|
||||
- 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,84 +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
|
||||
|
||||
- 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,55 +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
|
||||
|
||||
permissions:
|
||||
contents: "read"
|
||||
id-token: "write"
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- 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,36 +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
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: "actions/checkout@v7"
|
||||
|
||||
- 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,53 +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:
|
||||
packages: "write"
|
||||
contents: "read"
|
||||
id-token: "write"
|
||||
deployments: "write"
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
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: 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@v2
|
||||
env:
|
||||
RESOURCE: aksel.nav.no/nais-dev.yaml
|
||||
CLUSTER: dev-gcp
|
||||
IMAGE: ${{ steps.docker-push.outputs.image }}
|
||||
@@ -1,55 +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
|
||||
|
||||
- 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@v2
|
||||
env:
|
||||
RESOURCE: apps/mcp-server/.nais/app.yaml
|
||||
CLUSTER: prod-gcp
|
||||
IMAGE: ${{ steps.docker-push.outputs.image }}
|
||||
@@ -1,83 +0,0 @@
|
||||
name: Deploy prod (aksel.nav.no)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "aksel.nav.no/**"
|
||||
|
||||
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
|
||||
|
||||
- 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:
|
||||
packages: "write"
|
||||
contents: "read"
|
||||
id-token: "write"
|
||||
deployments: "write"
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
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 }}
|
||||
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@v2
|
||||
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:
|
||||
packages: "write"
|
||||
contents: "read"
|
||||
id-token: "write"
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- 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@v2
|
||||
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:
|
||||
@@ -12,87 +12,69 @@ on:
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
version:
|
||||
name: Create release Pull Request
|
||||
if: ${{ !contains(github.event.head_commit.message, '[ci skip]') }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: "write"
|
||||
pull-requests: "write"
|
||||
outputs:
|
||||
has-changesets: ${{ steps.changesets.outputs.has-changesets }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- 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 }}
|
||||
|
||||
# Without `publish-script` the action only creates/updates the release PR
|
||||
- name: Create or update release Pull Request
|
||||
id: changesets
|
||||
uses: changesets/action@v2
|
||||
with:
|
||||
version-script: yarn create-version
|
||||
env:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Both publish jobs are idempotent: `changeset publish` only publishes versions missing from the target registry.
|
||||
# Re-run a failed job to retry that registry without affecting the other.
|
||||
publish-npm:
|
||||
name: Publish to npm
|
||||
needs: version
|
||||
if: ${{ needs.version.outputs.has-changesets == 'false' }}
|
||||
build:
|
||||
name: "Release packages"
|
||||
if: ${{!contains(github.event.head_commit.message, '[ci skip]')}}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: "write"
|
||||
contents: "write"
|
||||
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:
|
||||
NPM_AUTH_TOKEN: ${{ secrets.READER_TOKEN }}
|
||||
|
||||
# changeset v3 uses `yarn npm info`, which reads the @navikt scope registry (GPR) from .yarnrc.yml.
|
||||
# Only changes this job's checkout, after install.
|
||||
- name: Point @navikt scope to npm
|
||||
run: yarn config set npmScopes.navikt.npmRegistryServer "https://registry.npmjs.org"
|
||||
|
||||
- name: Publish to npm
|
||||
- name: Create release Pull Request or publish to NPM
|
||||
id: changesets
|
||||
uses: changesets/action@v2
|
||||
uses: changesets/action@v1
|
||||
with:
|
||||
publish-script: yarn release
|
||||
create-github-releases: false
|
||||
version: yarn create-version
|
||||
publish: yarn release
|
||||
createGithubReleases: false
|
||||
env:
|
||||
# No token: `yarn npm publish` falls back to npm OIDC (trusted publishing)
|
||||
# Use OIDC for npm authentication instead of NPM_TOKEN
|
||||
# https://github.com/changesets/changesets/issues/1152#issuecomment-3190884868
|
||||
NPM_TOKEN: ""
|
||||
# `yarn npm publish` ignores the scope's npmRegistryServer and defaults to registry.yarnpkg.com
|
||||
YARN_NPM_PUBLISH_REGISTRY: https://registry.npmjs.org
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Needed for push to main
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Creating .npmrc
|
||||
run: |
|
||||
cat << EOF > "$HOME/.npmrc"
|
||||
@navikt:registry=https://npm.pkg.github.com
|
||||
//npm.pkg.github.com/:_authToken=$NPM_TOKEN
|
||||
EOF
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.GIT_REPO }}
|
||||
|
||||
- name: Publish to GPR
|
||||
if: steps.changesets.outputs.published == 'true'
|
||||
id: changesets-gpr
|
||||
uses: changesets/action@v1
|
||||
with:
|
||||
publish: yarn changeset publish
|
||||
createGithubReleases: false
|
||||
env:
|
||||
NPM_TOKEN: ""
|
||||
# Needed for push to main
|
||||
GITHUB_TOKEN: ${{ secrets.GIT_REPO }}
|
||||
|
||||
- name: Generate icon.zip
|
||||
if: steps.changesets.outputs.published == 'true'
|
||||
@@ -109,11 +91,10 @@ jobs:
|
||||
identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
|
||||
|
||||
- name: Get @navikt/ds-css version
|
||||
if: steps.changesets.outputs.published == 'true'
|
||||
run: |
|
||||
VERSION=$(yarn css:get-version)
|
||||
echo "version=$VERSION" >> $GITHUB_ENV
|
||||
echo "Version of @navikt/ds-css: $VERSION"
|
||||
echo "Version of @navikt/ds-css: ${{ env.version }}"
|
||||
|
||||
- name: Upload CSS to Nav CDN
|
||||
if: steps.changesets.outputs.published == 'true'
|
||||
@@ -124,40 +105,3 @@ jobs:
|
||||
destination: "@navikt/ds-css"
|
||||
project_id: ${{ vars.NAIS_MANAGEMENT_PROJECT_ID }}
|
||||
identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
|
||||
|
||||
publish-gpr:
|
||||
name: Publish to GitHub Packages
|
||||
needs: version
|
||||
if: ${{ needs.version.outputs.has-changesets == 'false' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# Needed for `publishConfig.provenance`
|
||||
id-token: "write"
|
||||
contents: "read"
|
||||
packages: "write"
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- 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 packages
|
||||
run: yarn build:release
|
||||
|
||||
# Git tags and GitHub releases are handled by the npm job
|
||||
- name: Publish to GitHub Packages
|
||||
run: yarn changeset publish --no-git-tag
|
||||
env:
|
||||
NPM_AUTH_TOKEN: ${{ secrets.GIT_REPO }}
|
||||
# `yarn npm publish` ignores the scope's npmRegistryServer and defaults to registry.yarnpkg.com
|
||||
YARN_NPM_PUBLISH_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
|
||||
@@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
CHANGED=`git diff $1 $2 --stat -- yarn.lock | wc -l`
|
||||
if [ $CHANGED -gt 0 ];
|
||||
then
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
CHANGED=`git diff HEAD@{1} --stat -- yarn.lock | wc -l`
|
||||
if [ $CHANGED -gt 0 ];
|
||||
then
|
||||
|
||||
+4
-1
@@ -1 +1,4 @@
|
||||
yarn 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
+4
-4
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"stylelint.vscode-stylelint",
|
||||
"biomejs.biome",
|
||||
"esbenp.prettier-vscode",
|
||||
"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
+5
-2
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"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": {
|
||||
@@ -9,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"]
|
||||
}
|
||||
@@ -17,6 +17,7 @@ defaultSemverRangePrefix: ""
|
||||
npmMinimalAgeGate: 7d
|
||||
npmPreapprovedPackages:
|
||||
- "@navikt/*"
|
||||
|
||||
enableTelemetry: false
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.18.0.cjs
|
||||
@@ -1,115 +1,5 @@
|
||||
# @navikt/aksel-icons
|
||||
|
||||
## 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?labels=nytt+✨%2Cikoner+🖼%2Cforespørsel+🥰&template=new-icon.yaml&title=%5BNytt+ikon%5D%3A+).
|
||||
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?labels=forespørsel+🥰&template=update-icon.yml&title=%5BInnspill+til+ikon%5D%3A+).
|
||||
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
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Fikser https://github.com/navikt/aksel/issues/1758
|
||||
*/
|
||||
|
||||
const fs = require("node:fs");
|
||||
const fs = require("fs");
|
||||
|
||||
const basePath = "./dist/react/esm";
|
||||
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
const path = require("node:path");
|
||||
const path = require("path");
|
||||
|
||||
const defaultIndexTemplate = (paths) => {
|
||||
const exportEntries = paths.map((filePath) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
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");
|
||||
|
||||
@@ -12,8 +12,8 @@ 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]")) {
|
||||
@@ -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,12 +1,12 @@
|
||||
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");
|
||||
|
||||
@@ -5,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";
|
||||
|
||||
@@ -26,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,
|
||||
@@ -39,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.
File renamed without changes.
@@ -0,0 +1,4 @@
|
||||
<svg width="32" height="32" viewBox="0 0 170 170" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.07318 54.5734C-0.692218 51.806 -0.690888 47.3204 2.07614 44.5546L38.6019 8.04512C38.7787 7.8684 38.9576 7.69433 39.1387 7.52295L39.1205 7.50476C42.7235 3.90124 47.1588 1.63753 51.8075 0.71364C53.8316 0.243049 55.9182 1.07479e-05 58.0297 1.01387e-05L142.517 0C157.695 5.55246e-06 170 12.3065 170 27.4874V111.958C170 113.916 169.791 115.852 169.386 117.737C168.522 122.523 166.241 127.103 162.542 130.803L162.533 130.794C162.342 130.997 162.148 131.197 161.95 131.395L125.425 167.925C122.659 170.692 118.174 170.692 115.408 167.925C112.642 165.158 112.642 160.673 115.408 157.906L151.933 121.376C153.698 119.61 154.917 117.4 155.48 115.008C155.999 111.779 155.014 108.355 152.525 105.866L64.1955 17.5236C61.632 14.9597 58.0865 13.9765 54.7698 14.574C52.4605 15.1572 50.3292 16.3547 48.6162 18.0669L12.0905 54.5764C9.32349 57.3422 4.83857 57.3409 2.07318 54.5734ZM80.8759 14.1688L155.833 89.1374V27.4874C155.833 20.1317 149.871 14.1688 142.517 14.1688L80.8759 14.1688Z" fill="#004367"/>
|
||||
<path d="M2.07466 167.925C-0.691554 165.158 -0.691554 160.673 2.07466 157.906L65.8247 94.1468C68.5909 91.3801 73.0758 91.3801 75.842 94.1468C78.6082 96.9134 78.6082 101.399 75.842 104.166L12.092 167.925C9.32579 170.692 4.84087 170.692 2.07466 167.925Z" fill="#004367"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
+1
-3
@@ -2,7 +2,7 @@ figma.showUI(__html__, { width: 480, height: 600 });
|
||||
|
||||
figma.ui.onmessage = (msg) => {
|
||||
if (msg.type === "create-icon") {
|
||||
const nodes: FrameNode[] = [];
|
||||
const nodes = [];
|
||||
|
||||
const icon = figma.createNodeFromSvg(msg.svg);
|
||||
icon.name = msg.name;
|
||||
@@ -43,6 +43,4 @@ figma.on("drop", (event) => {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
+3
-5
@@ -1,9 +1,9 @@
|
||||
/* eslint-disable react/react-in-jsx-scope */
|
||||
import Fuse from "fuse.js";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import * as Icons from "@navikt/aksel-icons";
|
||||
import meta from "@navikt/aksel-icons/metadata";
|
||||
import { Heading, Search, ToggleGroup } from "@navikt/ds-react";
|
||||
import * as Icons from "../../../";
|
||||
import meta from "../../../dist/metadata";
|
||||
import "./app.css";
|
||||
import { categorizeIcons, getFillIcon } from "./utils";
|
||||
|
||||
@@ -154,8 +154,7 @@ const App = () => {
|
||||
</Heading>
|
||||
<div className="icons-wrapper">
|
||||
{sub.icons.map((i) => {
|
||||
// biome-ignore lint/performance/noDynamicNamespaceImportAccess: We do not know which icon will be rendered at build time
|
||||
const T = Icons[`${i.id}Icon`];
|
||||
const T = Icons[`${i.id}Icon`]; // eslint-disable-line import/namespace
|
||||
if (T === undefined) {
|
||||
return null;
|
||||
}
|
||||
@@ -168,7 +167,6 @@ const App = () => {
|
||||
}}
|
||||
draggable="true"
|
||||
className="icon-classname icon-button"
|
||||
type="button"
|
||||
>
|
||||
<T
|
||||
fontSize="2rem"
|
||||
@@ -0,0 +1,79 @@
|
||||
@import "@navikt/ds-css";
|
||||
|
||||
.wrapper {
|
||||
background-color: var(--a-bg-default);
|
||||
}
|
||||
|
||||
.form {
|
||||
border-bottom: 1px solid var(--a-border-subtle);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 20;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: fit-content;
|
||||
width: 100%;
|
||||
gap: 1rem;
|
||||
padding-block: 0.5rem;
|
||||
padding-inline: 1rem;
|
||||
background-color: var(--a-bg-default);
|
||||
}
|
||||
|
||||
.form-togglegroup {
|
||||
min-width: 10rem;
|
||||
}
|
||||
|
||||
.form-search {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.categories {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
place-content: start;
|
||||
gap: 2rem;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.sub-categories {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.sub-heading {
|
||||
color: var(--a-text-subtle);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.icons-wrapper {
|
||||
gap: 0.125rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
min-height: 3rem;
|
||||
min-width: 3rem;
|
||||
border-radius: var(--a-border-radius-small);
|
||||
display: grid;
|
||||
place-content: center;
|
||||
background-color: var(--a-surface-default);
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.icon-button:hover {
|
||||
background-color: var(--a-surface-hover);
|
||||
}
|
||||
|
||||
.icon-button:active {
|
||||
background-color: var(--a-surface-neutral-subtle-hover);
|
||||
}
|
||||
|
||||
.icon-button:focus-visible {
|
||||
outline: 2px solid var(--a-border-focus);
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,56 @@
|
||||
import { AkselIcon } from "@navikt/aksel-icons/metadata";
|
||||
|
||||
const subCategorizeIcons = (
|
||||
icons: AkselIcon[],
|
||||
): { sub_category: string; icons: AkselIcon[] }[] => {
|
||||
const categories: { sub_category: string; icons: AkselIcon[] }[] = [];
|
||||
|
||||
for (const icon of icons) {
|
||||
const i = categories.findIndex(
|
||||
({ sub_category }) => icon.sub_category === sub_category,
|
||||
);
|
||||
i !== -1
|
||||
? categories[i].icons.push(icon)
|
||||
: categories.push({ sub_category: icon.sub_category, icons: [icon] });
|
||||
}
|
||||
return categories.sort((a, b) =>
|
||||
a.sub_category.localeCompare(b.sub_category),
|
||||
);
|
||||
};
|
||||
|
||||
export const categorizeIcons = (
|
||||
icons: AkselIcon[],
|
||||
): {
|
||||
category: string;
|
||||
sub_categories: { sub_category: string; icons: AkselIcon[] }[];
|
||||
}[] => {
|
||||
const categories: {
|
||||
category: string;
|
||||
icons: AkselIcon[];
|
||||
}[] = [];
|
||||
|
||||
for (const icon of icons) {
|
||||
const i = categories.findIndex(
|
||||
({ category }) => icon.category === category,
|
||||
);
|
||||
i !== -1
|
||||
? categories[i].icons.push(icon)
|
||||
: categories.push({ category: icon.category, icons: [icon] });
|
||||
}
|
||||
return categories
|
||||
.sort((a, b) => a.category.localeCompare(b.category))
|
||||
.map((x) => ({ ...x, sub_categories: subCategorizeIcons(x.icons) }));
|
||||
};
|
||||
|
||||
const noFill = (icon: AkselIcon, icons: AkselIcon[]) => {
|
||||
const foundFill = icons.find(
|
||||
(x) => x.name.endsWith("Fill") && x.name.replace("Fill", "") === icon.name,
|
||||
);
|
||||
return !foundFill;
|
||||
};
|
||||
|
||||
export const getFillIcon = (icons: AkselIcon[]) => {
|
||||
return icons.filter(
|
||||
(x, _, z) => x.variant.toLowerCase() === "fill" || noFill(x, z),
|
||||
);
|
||||
};
|
||||
Vendored
+1
@@ -18,6 +18,7 @@ declare module "**/metadata.mjs" {
|
||||
sub_category: "Time";
|
||||
keywords: string[];
|
||||
variant: "stroke" | "fill";
|
||||
updated_at: Date;
|
||||
created_at: Date;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "CommonJS",
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "Node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": false,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||
"rootDir": ".",
|
||||
"baseUrl": ".",
|
||||
"outDir": "dist/",
|
||||
"sourceMap": false,
|
||||
"noImplicitAny": false,
|
||||
"types": ["@figma/plugin-typings"]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.d.ts",
|
||||
"src/**/*.d.ts",
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.js",
|
||||
"src/**/*.jsx"
|
||||
],
|
||||
"exclude": ["src/common/**/*"]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": [
|
||||
"vite.config.plugin.ts",
|
||||
"vite.config.ui.ts",
|
||||
"./scripts/vite/*.ts"
|
||||
]
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
@@ -1,7 +1,7 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import React from "react";
|
||||
import * as AkselIcons from "@navikt/aksel-icons";
|
||||
import type { StarFillIcon } from "@navikt/aksel-icons";
|
||||
import { StarFillIcon } from "@navikt/aksel-icons";
|
||||
|
||||
const meta: Meta<typeof StarFillIcon> = {
|
||||
title: "aksel-icons/Icons",
|
||||
@@ -12,16 +12,54 @@ export default meta;
|
||||
type Story = StoryObj<typeof StarFillIcon>;
|
||||
|
||||
export const Icons: Story = {
|
||||
render: () => (
|
||||
<div>
|
||||
{Object.entries(AkselIcons).map(([key, Icon]) => (
|
||||
<Icon key={key} fontSize="3rem" aria-hidden title={key} />
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
render: () => {
|
||||
return (
|
||||
<div className="icons">
|
||||
{Object.entries(AkselIcons).map(([key, Value]) => (
|
||||
<React.Fragment key={key}>
|
||||
<Value fontSize="3rem" aria-hidden title={key} />
|
||||
</React.Fragment>
|
||||
))}
|
||||
<style>
|
||||
{`
|
||||
.icons{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const IconsInverted: Story = {
|
||||
render: Icons.render,
|
||||
globals: { theme: "dark" },
|
||||
render: () => {
|
||||
return (
|
||||
<div className="icons">
|
||||
{Object.entries(AkselIcons).map(([key, Value]) => (
|
||||
<React.Fragment key={key}>
|
||||
<Value
|
||||
fontSize="3rem"
|
||||
aria-hidden
|
||||
title={key}
|
||||
className="icon-color"
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
<style>
|
||||
{`
|
||||
.icons{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
background: var(--a-gray-900);
|
||||
}
|
||||
.icon-color{
|
||||
color: var(--a-icon-on-inverted);
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -6,4 +6,5 @@ keywords:
|
||||
- reise
|
||||
- travel
|
||||
variant: Stroke
|
||||
updated_at: 14.05.2025
|
||||
created_at: 07.03.2023
|
||||
@@ -6,4 +6,5 @@ keywords:
|
||||
- reise
|
||||
- travel
|
||||
variant: Fill
|
||||
updated_at: 14.05.2025
|
||||
created_at: 07.03.2023
|
||||
Loaded 100 of 4597 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user