Compare commits

...
Author SHA1 Message Date
github-actions[bot] 4ac8d81f63 Version Packages (#3741)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-30 09:20:50 +02:00
0404c4f156 [Aksel nettsiden] Produktbloggen landingsside (#3745)
* 🚧 scaffold for produktbloggen

* 🚧 query quirks

* 🚧 now renders sanity content

* 🚧 wip++

* css tw -> modules

* convert tw classes to modules

* better naming + use more tokens

* gitignore++

* purer css selectors (for modules)

* 🐛 Use correct body-component

* fixes for sanityContentBlock

* 🐛 Add spacing to list-block

* remove final unnecessary css override for produktbloggen

* Update aksel.nav.no/website/app/dev/produktbloggen/[...slug]/page.tsx

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* Update aksel.nav.no/website/app/dev/(designsystemet)/slug.ts

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* Update aksel.nav.no/website/app/dev/produktbloggen/[...slug]/page.tsx

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* Update aksel.nav.no/website/app/dev/produktbloggen/_ui/Produktbloggen.module.css

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* Update aksel.nav.no/website/app/dev/produktbloggen/layout.tsx

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* pr fixes + merge main in

* add import for Page + remove box-shadow

* remove unneeded margin (due to margin collapse) + 🔥 relatives

* uppercase prop redundancy

* diamond CSS tweaks

* 🔥 superflous CSS

* header variant > className

* adjust z-index

* initial conversion

* generateMetadata()

* Update aksel.nav.no/website/app/dev/(designsystemet)/slug.ts

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* use less custom CSS, update NextImage props

* 🔥 remove unused CSS

* generateStaticParams()

* move <main> to layout

* use <PageBlock> over custom CSS

* 🎨 small design tweaks

* box-shadow on top of image

* 🚧 from tw2cssmodules

* convert HighlightedBlogg

* use show & hide

* 🐛 add missing article render on md

* image radius & box-shadow

* simplify CSS

* generateMetadata()

* simplify CSS

* urlForImage > urlFor

* inline LatestBloggPosts

* Update aksel.nav.no/website/app/dev/produktbloggen/_ui/Produktbloggen.module.css

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* Link fixes

* desanity simple things

* split/optimize queries

* update schema

---------

Co-authored-by: Ken <ken.aleksander@gmail.com>
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
2025-04-30 09:18:01 +02:00
Julian Nymark 3a6da2564d make "hopp til innhold" disappear when not focused (#3760) 2025-04-29 14:37:24 +02:00
dependabot[bot]andKen 91955f5049 ⬆️ Bump the npm_and_yarn group with 3 updates (#3705)
* ⬆️ Bump the npm_and_yarn group with 3 updates

Bumps the npm_and_yarn group with 3 updates: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite), [esbuild](https://github.com/evanw/esbuild) and [axios](https://github.com/axios/axios).


Updates `vite` from 5.4.15 to 6.0.12
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.0.12/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.0.12/packages/vite)

Updates `esbuild` from 0.25.0 to 0.25.1
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.25.0...v0.25.1)

Updates `axios` from 1.8.3 to 1.8.4
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.8.3...v1.8.4)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
  dependency-group: npm_and_yarn
- dependency-name: esbuild
  dependency-type: direct:development
  dependency-group: npm_and_yarn
- dependency-name: axios
  dependency-type: direct:production
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>

* ⬆️ Upgrade `vite` to version `5.4.18` in `package.json`

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ken <ken.aleksander@gmail.com>
2025-04-29 11:48:45 +02:00
dependabot[bot] 34ce9cb72b ⬆️ Bump lightningcss from 1.29.2 to 1.29.3 (#3751)
Bumps [lightningcss](https://github.com/parcel-bundler/lightningcss) from 1.29.2 to 1.29.3.
- [Release notes](https://github.com/parcel-bundler/lightningcss/releases)
- [Commits](https://github.com/parcel-bundler/lightningcss/compare/v1.29.2...v1.29.3)

---
updated-dependencies:
- dependency-name: lightningcss
  dependency-version: 1.29.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-29 10:06:56 +02:00
dependabot[bot] 6b8d81e380 ⬆️ Bump @vitest/eslint-plugin from 1.1.7 to 1.1.43 (#3752)
Bumps [@vitest/eslint-plugin](https://github.com/vitest-dev/eslint-plugin-vitest) from 1.1.7 to 1.1.43.
- [Release notes](https://github.com/vitest-dev/eslint-plugin-vitest/releases)
- [Commits](https://github.com/vitest-dev/eslint-plugin-vitest/compare/v1.1.7...v1.1.43)

---
updated-dependencies:
- dependency-name: "@vitest/eslint-plugin"
  dependency-version: 1.1.43
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-29 10:05:48 +02:00
Ken e8f59ee29f 🔥 Remove actions.ts and unused .pageAuthor styling in CSS 2025-04-26 17:08:17 +02:00
Ken 5f3e0d619d Revert "🔥 Refactor queries and remove unused actions; update styling and layout for produktbloggen pages"
This reverts commit 9141a6f863.
2025-04-26 17:06:36 +02:00
Ken ab41aef076 🎨 Update max-width for data-text-prose and adjust list component styling 2025-04-26 13:51:52 +02:00
Ken 2decc636c8 🎨 Update theme button styling and enhance editor panel content structure 2025-04-26 13:51:52 +02:00
Ken 171410e7d4 🎨 Refactor Designsystemet components; introduce DesignsystemetEyebrow for consistent styling and layout 2025-04-26 13:51:52 +02:00
Ken 9141a6f863 🔥 Refactor queries and remove unused actions; update styling and layout for produktbloggen pages 2025-04-26 13:51:52 +02:00
Ken cc8742c179 🔊 Added umami-script
🔥 Refactor Attachment component, remove unused styles, and enhance EditorPanel integration
2025-04-26 13:51:51 +02:00
Ken e1b73178e1 [Website] Fix moduleresolution (#3750)
* ♻️ Use bundler as moduleresolution for website

* 📝 removed old ts-ignore
2025-04-25 15:02:41 +02:00
8eca3ceeac [Aksel nettsiden] Produktbloggen (#3716)
* 🚧 scaffold for produktbloggen

* 🚧 query quirks

* 🚧 now renders sanity content

* 🚧 wip++

* css tw -> modules

* convert tw classes to modules

* better naming + use more tokens

* gitignore++

* purer css selectors (for modules)

* 🐛 Use correct body-component

* fixes for sanityContentBlock

* 🐛 Add spacing to list-block

* remove final unnecessary css override for produktbloggen

* Update aksel.nav.no/website/app/dev/produktbloggen/[...slug]/page.tsx

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* Update aksel.nav.no/website/app/dev/(designsystemet)/slug.ts

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* Update aksel.nav.no/website/app/dev/produktbloggen/[...slug]/page.tsx

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* Update aksel.nav.no/website/app/dev/produktbloggen/_ui/Produktbloggen.module.css

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* Update aksel.nav.no/website/app/dev/produktbloggen/layout.tsx

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* pr fixes + merge main in

* add import for Page + remove box-shadow

* remove unneeded margin (due to margin collapse) + 🔥 relatives

* uppercase prop redundancy

* diamond CSS tweaks

* 🔥 superflous CSS

* header variant > className

---------

Co-authored-by: Ken <ken.aleksander@gmail.com>
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
2025-04-25 11:18:50 +02:00
Ken 3313720bc3 🐛 makes sure to filter out 'root'-pages for designsystem-docs in generateStaticParams 2025-04-24 16:31:48 +02:00
Ken 64705d06bc 🔥 Remove ConsentForm and associated styles, refactor page structure
🐛 Avoid redundant gap between non-existent grid-areas in linkcard

🐛 Tema-page slug missing forward-slash

🔥 removed underline thickness animation

📝 Ensure consistent query-naming
2025-04-24 16:15:33 +02:00
Ken 88fb8977ea 🐛 Dont use zod 'undefined', breaking server-components 2025-04-24 14:33:44 +02:00
KenandCopilot 8d805eae2f [Appdir] God praksis tema-page (#3742)
* ✨ feat: Add GOD_PRAKSIS_ALL_TEMA_QUERY and related types for improved data fetching
🎨 style: Update globals.css to enhance accessibility for skip links
📝 docs: Enhance theme.d.ts with new data-text-prose attribute for prose text styling
🎉 feat: Implement HeroCube component for visual representation in the hero section
🎨 style: Create Hero.module.css for styling the hero components
🎉 feat: Develop GodPraksisIntroHero component for the hero section
🎉 feat: Create TemaHero component for displaying theme navigation
🎉 feat: Implement HeroCardList for rendering theme cards
🎉 feat: Add HeroIntro component for displaying theme title and description
🎉 feat: Create HeroSelectButton for toggling theme selection
🎉 feat: Build page.tsx to fetch and display themes with the new components

* 🚚 Port over linkcard

* 💄 Replicate gp figma card

* 🎉 Synced hero

* ✨ Implemented GP frontpage

* 🔍 Add god-praksis metadata

* 🎉 Fetch and parse svg from sanity

* 🎉 Update use of linkcard on gp-pages

* 🎉 Use LinkCard on overview-pages

* Update aksel.nav.no/website/app/dev/(god-praksis)/god-praksis/page.tsx

* Update aksel.nav.no/website/app/dev/(god-praksis)/god-praksis/page.tsx

* 🐛 Clean up linkcard types

* ✨ Added data-fetching for tema-pages

* ✨ Gp-hero is now a self-fetching component

* ✨ Migrated state-handling for hero-banner

* ✨ Migrated CSS and interactions for HeroModule

* 🎉 Implemented chip-navigation logic

* 🐛 margin-animation did not work

* 🐛 Used undefined 'open' value

* ⚡ Optimize article mapping

* 📝 Add better metadata to categories

* ✨ Render articles as LinkCard

* ⚡ Fix URL parameter handling in GodPrakisChipsNavigationButton and add prefetch on hover

* 🐛 Chips nav had duplicate data

* 💄 Synced layout with Figma

* 🔍 Added metadata

* 🎨 Refactor padding in search form and improve header logo visibility based on screen size
💄 Add icon styling for GodPraksisHeroButton and create GodPraksisTaxonomyTag component

* 🐛 Add spacing below hero description

* 🐛 Remove spacing from description when no element below

* 🐛 Use data-attrb since last-child selector cant work

* 💄 Use new linear gradient for darkmode hero banner

* 🐛 Add missing focus-markings, word break for title

* 🐛 Use updated colors for fallback image

* [Appdir] God praksis article page Part 1 (#3743)

* 🎉 Scaffolding for gp article pages

* 🐛 Add spacing top to non-first child headings

* ✨ Implemented SystemPanel component

* ✨ Added sanity login check to outdated panel

* [Appdir] God praksis article page Part 2 (#3746)

* ✨ Added authors

* 📝 Formatting

* ✨ Added related articles content

* ♻️ Refactor UnderTemaLink to inline implementation with NextLink

* ✨ Integrate EditorPanel into multiple components and remove unused styles

* 🔍 Add metadata to page

* 🔥 Remove TOC from GP pages

* 🐛 Remove duplicate outdated panel render

* ✨ Add GodPraksisFeedback component with context and states for user feedback

* ✨ Added form-state and validation clientside

* ✨ Added RCS auth

* ♻️ Optimize slack-user lookup, inline inside action

* ✨ Refactor GodPraksisFeedback components and enhance page layout with relevant articles

* 💄 Remove bg from bilde-caption

* 💄 Fix list layout

* 🐛 Editorpanel dir had extra space

* Update aksel.nav.no/website/app/_ui/toc/TableOfContents.tsx

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

---------

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

* 🐛 Avoid getting updated types for legacy-implementation

* 💄 Move related links

* Update aksel.nav.no/website/app/_ui/toc/TableOfContents.tsx

* Update aksel.nav.no/website/app/dev/(god-praksis)/god-praksis/artikler/[slug]/page.tsx

---------

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-04-24 14:29:55 +02:00
Julian Nymark 9dc60ba560 prevent focus trap (#3748) 2025-04-24 13:08:32 +02:00
Ken 088d5e6b59 🎨 Refactor feedback link in TableOfContents, adjust padding and margins in Designsystemet styles, and enhance slug parsing logic 2025-04-24 11:35:00 +02:00
Ken b0f1151fd6 [Aksel.nav.no] Appdir God praksis landing-page (#3732)
* ✨ feat: Add GOD_PRAKSIS_ALL_TEMA_QUERY and related types for improved data fetching
🎨 style: Update globals.css to enhance accessibility for skip links
📝 docs: Enhance theme.d.ts with new data-text-prose attribute for prose text styling
🎉 feat: Implement HeroCube component for visual representation in the hero section
🎨 style: Create Hero.module.css for styling the hero components
🎉 feat: Develop GodPraksisIntroHero component for the hero section
🎉 feat: Create TemaHero component for displaying theme navigation
🎉 feat: Implement HeroCardList for rendering theme cards
🎉 feat: Add HeroIntro component for displaying theme title and description
🎉 feat: Create HeroSelectButton for toggling theme selection
🎉 feat: Build page.tsx to fetch and display themes with the new components

* 🚚 Port over linkcard

* 💄 Replicate gp figma card

* 🎉 Synced hero

* ✨ Implemented GP frontpage

* 🔍 Add god-praksis metadata

* 🎉 Fetch and parse svg from sanity

* 🎉 Update use of linkcard on gp-pages

* 🎉 Use LinkCard on overview-pages

* Update aksel.nav.no/website/app/dev/(god-praksis)/god-praksis/page.tsx

* Update aksel.nav.no/website/app/dev/(god-praksis)/god-praksis/page.tsx
2025-04-24 10:07:43 +02:00
Ken c9ede88246 💄 Update hover-effect for Chips based on figma-review (#3738) 2025-04-24 10:05:09 +02:00
dependabot[bot] 7cfae6cd1d ⬆️ Bump vite-tsconfig-paths from 4.3.2 to 5.1.4 (#3712)
Bumps [vite-tsconfig-paths](https://github.com/aleclarson/vite-tsconfig-paths) from 4.3.2 to 5.1.4.
- [Release notes](https://github.com/aleclarson/vite-tsconfig-paths/releases)
- [Commits](https://github.com/aleclarson/vite-tsconfig-paths/compare/v4.3.2...v5.1.4)

---
updated-dependencies:
- dependency-name: vite-tsconfig-paths
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-22 12:41:06 +02:00
Ken 0a98a75514 ⚡ Use recommended memory values for application requests 2025-04-22 12:20:18 +02:00
Ken dbff5f79f8 🐛 Allow fetching from github on server-side 2025-04-22 11:21:12 +02:00
KenandCopilot 84f1cfec49 [Aksel.nav.no] Appdir iconpage (#3730)
* ✨ Added theming-support to appdir

* 📝 Added changelog docs for theme class change

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

* ✨ Setup overview-page

* 🐛 Use inline styles instead of Head element from nextjs

* WIP: V1 list view

* 💄 Align overview heading with regular page heading

* 🐛 Moved margin-bottom to top for overview grid

* 🎉 Add overview toggle for component pages

* 🎉 Added overview-pages to sidebar

* 🎉 Added routing support for overview pages

* 🎉 Setup staticparams and metadata for overview-pages

* 🚚 Moved overview components out to now dir

* ✨ Added overview support for komponenter, templates and grunnleggende

* 🔥 Removed unused sanity-field

* 🐛 Hide fallback-svg

* 🐛 Fix misspelling

* 🎉 Added Iconpage filtering and navigation

* 🎉 Added modal view

* 🐛 Fixed searchParams type

* 🎉 Added empty-state

* 🔥 Removed forced use-client for emptystate

* 🎉 Support svg fetch

* 🔥 Refactor IconPage components and remove unused sidepanel

* 🎉 Added links

* 📝 Added metadata

* 🐛 Resolve impure css module class

* 🐛 Fix iconName validation logic in IconPage component

* 🎨 Improve layout and styling in IconPage components

* 🎨 Update HStack component to use 'as="ul"' for semantic HTML

* ✨ Added icon-page to sidebar

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

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

* Update aksel.nav.no/website/app/dev/(designsystemet)/_ui/icon-page/IconPage.button.tsx

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

* ⚡ Added zod-validation and debounce to search

* 🏷️ Resolved updated types

* ⚡ Use clientside updates for icon-page

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-04-22 10:43:56 +02:00
dependabot[bot] 39817f924c ⬆️ Bump @testing-library/react from 16.1.0 to 16.3.0 (#3724)
Bumps [@testing-library/react](https://github.com/testing-library/react-testing-library) from 16.1.0 to 16.3.0.
- [Release notes](https://github.com/testing-library/react-testing-library/releases)
- [Changelog](https://github.com/testing-library/react-testing-library/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/react-testing-library/compare/v16.1.0...v16.3.0)

---
updated-dependencies:
- dependency-name: "@testing-library/react"
  dependency-version: 16.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-22 10:26:15 +02:00
Ken 8ea18b07a3 ✨ New icon 'Cpu'. (#3740) 2025-04-22 10:11:24 +02:00
820a68f0a8 Chat: Add prop to control toptext heading level (#3739)
* Chat: Add prop to control toptext heading level

* Update @navikt/core/react/src/chat/Bubble.tsx

* Update @navikt/core/react/src/chat/Bubble.tsx

* Update @navikt/core/react/src/chat/chat.stories.tsx

* 📝 Typo

* 📝 Chat: Add 'toptextHeadingLevel'-prop to allow changing h-level based on semantics.

---------

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
Co-authored-by: Ken <ken.aleksander@gmail.com>
2025-04-22 10:05:31 +02:00
github-actions[bot] f787e91a2f Version Packages (#3737)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-11 14:47:22 +02:00
KenandCopilot e7ec9c191a Timeline: Add placement-prop for periods for more fine-grained control (#3736)
* Timeline: Add placement-prop for periods for more fine-grained controll over Popover-placement

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

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

---------

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

* 🐛 Use unique option-value as react-key
2025-04-11 12:32:16 +02:00
165 changed files with 9080 additions and 1347 deletions

No files matched your search

+1
View File
@@ -79,6 +79,7 @@ packages/**/lib
*.d.ts
.vscode
.nvim.lua
# File created on build in nav-frontend-core
*-exports.less
+1 -1
View File
@@ -13,7 +13,7 @@ Steps:
- 1. Import new CSS `@navikt/ds-css/darkside`
- 2. Import `Theme`-component from `@navikt/ds-react/Theme`
- 3. Wrap your app in the `Theme`-component. You do not have to set the theme, but it defaults to `light`.
- 3. Wrap your app in the `Theme`-component. You do not have to set the theme, as you can manually handle theme switching with other tools like `next-themes`.
```tsx
import "@navikt/ds-css/darkside";
+8
View File
@@ -1,5 +1,13 @@
# @navikt/aksel-icons
## 7.21.0
### Minor Changes
- Icons: New icon 'Cpu'. ([#3740](https://github.com/navikt/aksel/pull/3740))
## 7.20.0
## 7.19.1
## 7.19.0
+5 -1
View File
@@ -49,8 +49,12 @@ fs.writeFileSync(
created_at: Date;
}
type AkselIconName = ${Object.keys(metadata)
.map((icon) => `"${icon}"`)
.join(" | ")};
declare const metadata: {
[iconId: string]: AkselIcon;
[key in AkselIconName]: AkselIcon;
};
export default metadata;`,
@@ -28,7 +28,7 @@
"postcss": "^8.4.31",
"postcss-url": "^10.1.3",
"typescript": "5.5.4",
"vite": "^5.4.15",
"vite": "^5.4.18",
"vite-plugin-singlefile": "^1.0.0",
"vite-plugin-svgr": "^4.2.0"
}
+1 -1
View File
@@ -9,5 +9,5 @@ keywords:
- sortering
- sorting
variant: Stroke
updated_at: 17.07.2024
updated_at: 27.03.2025
created_at: 07.03.2023
+3
View File
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2.25C12.4142 2.25 12.75 2.58579 12.75 3V4.25H14.25V3C14.25 2.58579 14.5858 2.25 15 2.25C15.4142 2.25 15.75 2.58579 15.75 3V4.25H17C18.5188 4.25 19.75 5.48122 19.75 7V8.25H21C21.4142 8.25 21.75 8.58579 21.75 9C21.75 9.41421 21.4142 9.75 21 9.75H19.75V11.25H21C21.4142 11.25 21.75 11.5858 21.75 12C21.75 12.4142 21.4142 12.75 21 12.75H19.75V14.25H21C21.4142 14.25 21.75 14.5858 21.75 15C21.75 15.4142 21.4142 15.75 21 15.75H19.75V17C19.75 18.5188 18.5188 19.75 17 19.75H15.75V21C15.75 21.4142 15.4142 21.75 15 21.75C14.5858 21.75 14.25 21.4142 14.25 21V19.75H12.75V21C12.75 21.4142 12.4142 21.75 12 21.75C11.5858 21.75 11.25 21.4142 11.25 21V19.75H9.75V21C9.75 21.4142 9.41421 21.75 9 21.75C8.58579 21.75 8.25 21.4142 8.25 21V19.75H7C5.48122 19.75 4.25 18.5188 4.25 17V15.75H3C2.58579 15.75 2.25 15.4142 2.25 15C2.25 14.5858 2.58579 14.25 3 14.25H4.25V12.75H3C2.58579 12.75 2.25 12.4142 2.25 12C2.25 11.5858 2.58579 11.25 3 11.25H4.25V9.75H3C2.58579 9.75 2.25 9.41421 2.25 9C2.25 8.58579 2.58579 8.25 3 8.25H4.25V7C4.25 5.48122 5.48122 4.25 7 4.25H8.25V3C8.25 2.58579 8.58579 2.25 9 2.25C9.41421 2.25 9.75 2.58579 9.75 3V4.25H11.25V3C11.25 2.58579 11.5858 2.25 12 2.25ZM5.75 9V12V15L5.75 17C5.75 17.6904 6.30964 18.25 7 18.25H9H12H15H17C17.6904 18.25 18.25 17.6904 18.25 17V15V12V9V7C18.25 6.30964 17.6904 5.75 17 5.75H15H12H9H7C6.30964 5.75 5.75 6.30964 5.75 7L5.75 9ZM8 7.25C7.58579 7.25 7.25 7.58579 7.25 8V16C7.25 16.4142 7.58579 16.75 8 16.75H16C16.4142 16.75 16.75 16.4142 16.75 16V8C16.75 7.58579 16.4142 7.25 16 7.25H8ZM8.75 15.25V8.75H15.25V15.25H8.75Z" fill="#23262A"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

+8
View File
@@ -0,0 +1,8 @@
name: Cpu
category: Interface
sub_category: Devices
keywords:
- Prosessor
variant: Stroke
updated_at: 07.04.2025
created_at: 19.02.2025
+3
View File
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2.25C12.4142 2.25 12.75 2.58579 12.75 3V5H14.25V3C14.25 2.58579 14.5858 2.25 15 2.25C15.4142 2.25 15.75 2.58579 15.75 3V5H17C18.1046 5 19 5.89543 19 7V8.25H21C21.4142 8.25 21.75 8.58579 21.75 9C21.75 9.41421 21.4142 9.75 21 9.75H19V11.25H21C21.4142 11.25 21.75 11.5858 21.75 12C21.75 12.4142 21.4142 12.75 21 12.75H19V14.25H21C21.4142 14.25 21.75 14.5858 21.75 15C21.75 15.4142 21.4142 15.75 21 15.75H19V17C19 18.1046 18.1046 19 17 19H15.75V21C15.75 21.4142 15.4142 21.75 15 21.75C14.5858 21.75 14.25 21.4142 14.25 21V19H12.75V21C12.75 21.4142 12.4142 21.75 12 21.75C11.5858 21.75 11.25 21.4142 11.25 21V19H9.75V21C9.75 21.4142 9.41421 21.75 9 21.75C8.58579 21.75 8.25 21.4142 8.25 21V19H7C5.89543 19 5 18.1046 5 17L5 15.75H3C2.58579 15.75 2.25 15.4142 2.25 15C2.25 14.5858 2.58579 14.25 3 14.25H5V12.75H3C2.58579 12.75 2.25 12.4142 2.25 12C2.25 11.5858 2.58579 11.25 3 11.25H5V9.75H3C2.58579 9.75 2.25 9.41421 2.25 9C2.25 8.58579 2.58579 8.25 3 8.25H5L5 7C5 5.89543 5.89543 5 7 5H8.25V3C8.25 2.58579 8.58579 2.25 9 2.25C9.41421 2.25 9.75 2.58579 9.75 3V5H11.25V3C11.25 2.58579 11.5858 2.25 12 2.25ZM8 7.25C7.58579 7.25 7.25 7.58579 7.25 8V16C7.25 16.4142 7.58579 16.75 8 16.75H16C16.4142 16.75 16.75 16.4142 16.75 16V8C16.75 7.58579 16.4142 7.25 16 7.25H8ZM8.75 15.25V8.75H15.25V15.25H8.75Z" fill="#23262A"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

+8
View File
@@ -0,0 +1,8 @@
name: CpuFill
category: Interface
sub_category: Devices
keywords:
- Prosessor
variant: Fill
updated_at: 07.04.2025
created_at: 19.02.2025
+1 -1
View File
@@ -13,5 +13,5 @@ keywords:
- oversettelse
- bytt språk
variant: Stroke
updated_at: 05.01.2024
updated_at: 01.04.2025
created_at: 07.03.2023
+1 -1
View File
@@ -5,5 +5,5 @@ keywords:
- gange
- multiplisere
variant: Stroke
updated_at: 12.04.2024
updated_at: 11.04.2025
created_at: 07.03.2023
+1 -1
View File
@@ -11,5 +11,5 @@ keywords:
- empty
- tom
variant: Stroke
updated_at: 15.05.2024
updated_at: 01.04.2025
created_at: 15.05.2024
@@ -1,15 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 17V7C5 5.89543 5.89543 5 7 5H17C18.1046 5 19 5.89543 19 7V17C19 18.1046 18.1046 19 17 19H7C5.89543 19 5 18.1046 5 17ZM8 7.25C7.58579 7.25 7.25 7.58579 7.25 8V16C7.25 16.4142 7.58579 16.75 8 16.75H16C16.4142 16.75 16.75 16.4142 16.75 16V8C16.75 7.58579 16.4142 7.25 16 7.25H8ZM8.75 15.25V8.75H15.25V15.25H8.75Z" fill="#23262A"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 1.25C12.4142 1.25 12.75 1.58579 12.75 2V5C12.75 5.41421 12.4142 5.75 12 5.75C11.5858 5.75 11.25 5.41421 11.25 5V2C11.25 1.58579 11.5858 1.25 12 1.25Z" fill="#262626"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 18.25C12.4142 18.25 12.75 18.5858 12.75 19V22C12.75 22.4142 12.4142 22.75 12 22.75C11.5858 22.75 11.25 22.4142 11.25 22V19C11.25 18.5858 11.5858 18.25 12 18.25Z" fill="#262626"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.75 12C22.75 12.4142 22.4142 12.75 22 12.75L19 12.75C18.5858 12.75 18.25 12.4142 18.25 12C18.25 11.5858 18.5858 11.25 19 11.25L22 11.25C22.4142 11.25 22.75 11.5858 22.75 12Z" fill="#262626"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.75 12C5.75 12.4142 5.41421 12.75 5 12.75L2 12.75C1.58579 12.75 1.25 12.4142 1.25 12C1.25 11.5858 1.58579 11.25 2 11.25L5 11.25C5.41421 11.25 5.75 11.5858 5.75 12Z" fill="#262626"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 1.25C15.4142 1.25 15.75 1.58579 15.75 2V5C15.75 5.41421 15.4142 5.75 15 5.75C14.5858 5.75 14.25 5.41421 14.25 5V2C14.25 1.58579 14.5858 1.25 15 1.25Z" fill="#262626"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 18.25C15.4142 18.25 15.75 18.5858 15.75 19V22C15.75 22.4142 15.4142 22.75 15 22.75C14.5858 22.75 14.25 22.4142 14.25 22V19C14.25 18.5858 14.5858 18.25 15 18.25Z" fill="#262626"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.75 15C22.75 15.4142 22.4142 15.75 22 15.75L19 15.75C18.5858 15.75 18.25 15.4142 18.25 15C18.25 14.5858 18.5858 14.25 19 14.25L22 14.25C22.4142 14.25 22.75 14.5858 22.75 15Z" fill="#262626"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.75 15C5.75 15.4142 5.41421 15.75 5 15.75L2 15.75C1.58579 15.75 1.25 15.4142 1.25 15C1.25 14.5858 1.58579 14.25 2 14.25L5 14.25C5.41421 14.25 5.75 14.5858 5.75 15Z" fill="#262626"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 1.25C9.41421 1.25 9.75 1.58579 9.75 2V5C9.75 5.41421 9.41421 5.75 9 5.75C8.58579 5.75 8.25 5.41421 8.25 5V2C8.25 1.58579 8.58579 1.25 9 1.25Z" fill="#262626"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 18.25C9.41421 18.25 9.75 18.5858 9.75 19V22C9.75 22.4142 9.41421 22.75 9 22.75C8.58579 22.75 8.25 22.4142 8.25 22V19C8.25 18.5858 8.58579 18.25 9 18.25Z" fill="#262626"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.75 9C22.75 9.41421 22.4142 9.75 22 9.75L19 9.75C18.5858 9.75 18.25 9.41421 18.25 9C18.25 8.58579 18.5858 8.25 19 8.25L22 8.25C22.4142 8.25 22.75 8.58579 22.75 9Z" fill="#262626"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.75 9C5.75 9.41421 5.41421 9.75 5 9.75L2 9.75C1.58579 9.75 1.25 9.41421 1.25 9C1.25 8.58579 1.58579 8.25 2 8.25L5 8.25C5.41421 8.25 5.75 8.58579 5.75 9Z" fill="#262626"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.2 KiB

@@ -1,12 +0,0 @@
name: Property1Fill
category: Interface
sub_category: Devices
keywords:
- print
- skrive ut
- skriver
- printing room
- skriverom
variant: Stroke
updated_at: 19.02.2025
created_at: 19.02.2025
@@ -1,16 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 5.75C6.30964 5.75 5.75 6.30964 5.75 7V17C5.75 17.6904 6.30964 18.25 7 18.25H17C17.6904 18.25 18.25 17.6904 18.25 17V7C18.25 6.30964 17.6904 5.75 17 5.75H7ZM4.25 7C4.25 5.48122 5.48122 4.25 7 4.25H17C18.5188 4.25 19.75 5.48122 19.75 7V17C19.75 18.5188 18.5188 19.75 17 19.75H7C5.48122 19.75 4.25 18.5188 4.25 17V7Z" fill="#262626"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.25 8C7.25 7.58579 7.58579 7.25 8 7.25H16C16.4142 7.25 16.75 7.58579 16.75 8V16C16.75 16.4142 16.4142 16.75 16 16.75H8C7.58579 16.75 7.25 16.4142 7.25 16V8ZM8.75 8.75V15.25H15.25V8.75H8.75Z" fill="#262626"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 1.25C12.4142 1.25 12.75 1.58579 12.75 2V5C12.75 5.41421 12.4142 5.75 12 5.75C11.5858 5.75 11.25 5.41421 11.25 5V2C11.25 1.58579 11.5858 1.25 12 1.25Z" fill="#262626"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 18.25C12.4142 18.25 12.75 18.5858 12.75 19V22C12.75 22.4142 12.4142 22.75 12 22.75C11.5858 22.75 11.25 22.4142 11.25 22V19C11.25 18.5858 11.5858 18.25 12 18.25Z" fill="#262626"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.75 12C22.75 12.4142 22.4142 12.75 22 12.75L19 12.75C18.5858 12.75 18.25 12.4142 18.25 12C18.25 11.5858 18.5858 11.25 19 11.25L22 11.25C22.4142 11.25 22.75 11.5858 22.75 12Z" fill="#262626"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.75 12C5.75 12.4142 5.41421 12.75 5 12.75L2 12.75C1.58579 12.75 1.25 12.4142 1.25 12C1.25 11.5858 1.58579 11.25 2 11.25L5 11.25C5.41421 11.25 5.75 11.5858 5.75 12Z" fill="#262626"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 1.25C15.4142 1.25 15.75 1.58579 15.75 2V5C15.75 5.41421 15.4142 5.75 15 5.75C14.5858 5.75 14.25 5.41421 14.25 5V2C14.25 1.58579 14.5858 1.25 15 1.25Z" fill="#262626"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 18.25C15.4142 18.25 15.75 18.5858 15.75 19V22C15.75 22.4142 15.4142 22.75 15 22.75C14.5858 22.75 14.25 22.4142 14.25 22V19C14.25 18.5858 14.5858 18.25 15 18.25Z" fill="#262626"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.75 15C22.75 15.4142 22.4142 15.75 22 15.75L19 15.75C18.5858 15.75 18.25 15.4142 18.25 15C18.25 14.5858 18.5858 14.25 19 14.25L22 14.25C22.4142 14.25 22.75 14.5858 22.75 15Z" fill="#262626"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.75 15C5.75 15.4142 5.41421 15.75 5 15.75L2 15.75C1.58579 15.75 1.25 15.4142 1.25 15C1.25 14.5858 1.58579 14.25 2 14.25L5 14.25C5.41421 14.25 5.75 14.5858 5.75 15Z" fill="#262626"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 1.25C9.41421 1.25 9.75 1.58579 9.75 2V5C9.75 5.41421 9.41421 5.75 9 5.75C8.58579 5.75 8.25 5.41421 8.25 5V2C8.25 1.58579 8.58579 1.25 9 1.25Z" fill="#262626"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 18.25C9.41421 18.25 9.75 18.5858 9.75 19V22C9.75 22.4142 9.41421 22.75 9 22.75C8.58579 22.75 8.25 22.4142 8.25 22V19C8.25 18.5858 8.58579 18.25 9 18.25Z" fill="#262626"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.75 9C22.75 9.41421 22.4142 9.75 22 9.75L19 9.75C18.5858 9.75 18.25 9.41421 18.25 9C18.25 8.58579 18.5858 8.25 19 8.25L22 8.25C22.4142 8.25 22.75 8.58579 22.75 9Z" fill="#262626"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.75 9C5.75 9.41421 5.41421 9.75 5 9.75L2 9.75C1.58579 9.75 1.25 9.41421 1.25 9C1.25 8.58579 1.58579 8.25 2 8.25L5 8.25C5.41421 8.25 5.75 8.58579 5.75 9Z" fill="#262626"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

@@ -1,12 +0,0 @@
name: Property1Stroke
category: Interface
sub_category: Devices
keywords:
- print
- skrive ut
- skriver
- printing room
- skriverom
variant: Stroke
updated_at: 19.02.2025
created_at: 19.02.2025
+1 -1
View File
@@ -12,5 +12,5 @@ keywords:
- diagram
- analyse
variant: Stroke
updated_at: 26.09.2024
updated_at: 02.04.2025
created_at: 07.03.2023
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-icons",
"version": "7.19.1",
"version": "7.21.0",
"author": "Aksel | Nav",
"homepage": "https://aksel.nav.no/ikoner",
"repository": {
+4
View File
@@ -1,5 +1,9 @@
# @navikt/aksel-stylelint
## 7.21.0
## 7.20.0
## 7.19.1
## 7.19.0
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-stylelint",
"version": "7.19.1",
"version": "7.21.0",
"author": "Aksel | Nav",
"homepage": "https://aksel.nav.no/grunnleggende/kode/stylelint",
"repository": {
@@ -35,8 +35,8 @@
"dev": "yarn watch:lint"
},
"devDependencies": {
"@navikt/ds-css": "^7.19.1",
"@navikt/ds-tokens": "^7.19.1",
"@navikt/ds-css": "^7.21.0",
"@navikt/ds-tokens": "^7.21.0",
"concurrently": "9.0.1",
"postcss-selector-parser": "^6.0.13",
"postcss-value-parser": "^4.2.0",
+16
View File
@@ -1,5 +1,21 @@
# @navikt/aksel
## 7.21.0
### Patch Changes
- Updated dependencies [[`c9ede88`](https://github.com/navikt/aksel/commit/c9ede88246faec7712eb9c73309e3ff81c85efbf)]:
- @navikt/ds-css@7.21.0
- @navikt/ds-tokens@7.21.0
## 7.20.0
### Patch Changes
- Updated dependencies [[`c57fe7c`](https://github.com/navikt/aksel/commit/c57fe7c396e81814e49a3d56a706cc2f690841c8)]:
- @navikt/ds-css@7.20.0
- @navikt/ds-tokens@7.20.0
## 7.19.1
### Patch Changes
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel",
"version": "7.19.1",
"version": "7.21.0",
"description": "Aksel command line interface. Handles css-imports, codemods and more",
"author": "Aksel | Nav designsystem team",
"license": "MIT",
@@ -29,9 +29,9 @@
},
"homepage": "https://aksel.nav.no/grunnleggende/kode/kommandolinje",
"dependencies": {
"@navikt/ds-css": "^7.19.1",
"@navikt/ds-tokens": "^7.19.1",
"axios": "1.8.3",
"@navikt/ds-css": "^7.21.0",
"@navikt/ds-tokens": "^7.21.0",
"axios": "1.8.4",
"chalk": "4.1.0",
"cli-progress": "^3.12.0",
"clipboardy": "^2.3.0",
+12
View File
@@ -1,5 +1,17 @@
# @navikt/ds-css
## 7.21.0
### Patch Changes
- Darkside: Update Chips.Toggle hover-border. ([#3738](https://github.com/navikt/aksel/pull/3738))
## 7.20.0
### Minor Changes
- Combobox: Use option-value as `id` instead of `label`. ([#3735](https://github.com/navikt/aksel/pull/3735))
## 7.19.1
### Patch Changes
@@ -73,9 +73,11 @@
box-shadow: inset 0 0 0 1px var(--ax-border-accent-subtleA);
background-color: var(--ax-bg-accent-moderate);
color: var(--ax-text-accent);
transition: box-shadow 200ms cubic-bezier(0.15, 1, 0.3, 1);
&:hover {
background-color: var(--ax-bg-accent-moderate-hover);
box-shadow: inset 0 0 0 1px var(--ax-border-accent);
}
&[data-pressed="true"] {
@@ -93,9 +95,11 @@
box-shadow: inset 0 0 0 1px var(--ax-border-neutral-subtleA);
background-color: var(--ax-bg-neutral-moderate);
color: var(--ax-text-neutral);
transition: box-shadow 200ms cubic-bezier(0.15, 1, 0.3, 1);
&:hover {
background-color: var(--ax-bg-neutral-moderate-hover);
box-shadow: inset 0 0 0 1px var(--ax-border-neutral);
}
&[data-pressed="true"] {
@@ -315,6 +315,12 @@
outline: 2px solid var(--ax-border-focus);
}
.aksel-combobox__list-item--focus,
.aksel-combobox__list--with-hover
.aksel-combobox__list-item:not([data-no-focus="true"], .aksel-combobox__list-item--new-option):hover {
cursor: pointer;
}
.aksel-combobox__list-item {
user-select: none;
@@ -14,6 +14,7 @@
padding: var(--ax-space-2) var(--ax-space-8);
min-height: 2rem;
line-height: 1;
text-decoration: none;
&:has(.aksel-tag__icon--left) {
gap: var(--ax-space-2);
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-css",
"version": "7.19.1",
"version": "7.21.0",
"description": "CSS for Nav Designsystem",
"author": "Aksel | Nav designsystem team",
"keywords": [
@@ -27,13 +27,13 @@
"test": "yarn build && vitest run"
},
"devDependencies": {
"@navikt/ds-tokens": "^7.19.1",
"@navikt/ds-tokens": "^7.21.0",
"autoprefixer": "^10.4.20",
"browserslist": "^4.24.4",
"cssnano": "7.0.6",
"esbuild": "^0.25.0",
"esbuild": "^0.25.1",
"fast-glob": "3.2.11",
"lightningcss": "^1.29.2",
"lightningcss": "^1.29.3",
"lodash": "^4.17.21",
"normalize.css": "^8.0.1",
"postcss": "^8.4.31",
+26
View File
@@ -1,5 +1,31 @@
# @navikt/ds-react
## 7.21.0
### Minor Changes
- Chat: Add 'toptextHeadingLevel'-prop to allow changing h-level based on semantics. ([#3739](https://github.com/navikt/aksel/pull/3739))
### Patch Changes
- Updated dependencies [[`8ea18b0`](https://github.com/navikt/aksel/commit/8ea18b07a3bec913e04fc274983da83daca86983)]:
- @navikt/aksel-icons@7.21.0
- @navikt/ds-tokens@7.21.0
## 7.20.0
### Minor Changes
- Combobox: Use option-value as `id` instead of `label`. ([#3735](https://github.com/navikt/aksel/pull/3735))
- Timeline: Add `placement`-prop for periods for more fine-grained control over Popover-placement. ([#3736](https://github.com/navikt/aksel/pull/3736))
### Patch Changes
- Updated dependencies []:
- @navikt/ds-tokens@7.20.0
- @navikt/aksel-icons@7.20.0
## 7.19.1
### Patch Changes
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-react",
"version": "7.19.1",
"version": "7.21.0",
"description": "React components from the Norwegian Labour and Welfare Administration.",
"author": "Aksel, a team part of the Norwegian Labour and Welfare Administration.",
"license": "MIT",
@@ -625,8 +625,8 @@
"dependencies": {
"@floating-ui/react": "0.25.4",
"@floating-ui/react-dom": "^2.0.9",
"@navikt/aksel-icons": "^7.19.1",
"@navikt/ds-tokens": "^7.19.1",
"@navikt/aksel-icons": "^7.21.0",
"@navikt/ds-tokens": "^7.21.0",
"clsx": "^2.1.0",
"date-fns": "^4.0.0",
"react-day-picker": "9.6.1"
@@ -634,14 +634,14 @@
"devDependencies": {
"@testing-library/dom": "10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.1.0",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.5.2",
"@types/jscodeshift": "^0.11.11",
"aksel": "workspace:^",
"concurrently": "9.0.1",
"fast-glob": "3.2.11",
"jscodeshift": "^0.15.1",
"jsdom": "25.0.1",
"jsdom": "26.0.0",
"react-dom": "^18.0.0",
"react-router-dom": "^6.3.0",
"rimraf": "6.0.1",
+22 -4
View File
@@ -1,6 +1,6 @@
import React, { HTMLAttributes, forwardRef } from "react";
import { useRenameCSS } from "../theme/Theme";
import { Detail } from "../typography";
import { Detail, Heading, HeadingProps } from "../typography";
export interface ChatBubbleProps extends HTMLAttributes<HTMLDivElement> {
/**
@@ -19,16 +19,34 @@ export interface ChatBubbleProps extends HTMLAttributes<HTMLDivElement> {
* Overrides hoizontal position of toptext.
*/
toptextPosition?: "left" | "right";
/**
* The heading level for the toptext.
* @default "3"
*/
toptextHeadingLevel?: Exclude<HeadingProps["level"], "1">;
}
const Bubble = forwardRef<HTMLDivElement, ChatBubbleProps>(
({ children, className, name, timestamp, toptextPosition, ...rest }, ref) => {
(
{
children,
className,
name,
timestamp,
toptextPosition,
toptextHeadingLevel = "3",
...rest
},
ref,
) => {
const { cn } = useRenameCSS();
return (
<div ref={ref} className={cn("navds-chat__bubble", className)} {...rest}>
{(timestamp || name) && (
<h3
<Heading
size="xsmall"
level={toptextHeadingLevel}
className={cn(
`navds-chat__top-text`,
toptextPosition && `navds-chat__top-text--${toptextPosition}`,
@@ -41,7 +59,7 @@ const Bubble = forwardRef<HTMLDivElement, ChatBubbleProps>(
</Detail>
)}
{timestamp && <Detail as="span">{timestamp}</Detail>}
</h3>
</Heading>
)}
{children}
</div>
+8 -1
View File
@@ -1,6 +1,6 @@
import React, { HTMLAttributes, forwardRef } from "react";
import { useRenameCSS } from "../theme/Theme";
import { BodyLong } from "../typography";
import { BodyLong, HeadingProps } from "../typography";
import Bubble from "./Bubble";
export const VARIANTS = ["subtle", "info", "neutral"] as const;
@@ -47,6 +47,11 @@ export interface ChatProps extends HTMLAttributes<HTMLDivElement> {
* @default "medium"
*/
size?: (typeof SIZES)[number];
/**
* The heading level for the toptext.
* @default "3"
*/
toptextHeadingLevel?: Exclude<HeadingProps["level"], "1">;
}
interface ChatComponent
@@ -88,6 +93,7 @@ export const Chat = forwardRef<HTMLDivElement, ChatProps>(
variant = "neutral",
toptextPosition,
size = "medium",
toptextHeadingLevel = "3",
...rest
}: ChatProps,
ref,
@@ -120,6 +126,7 @@ export const Chat = forwardRef<HTMLDivElement, ChatProps>(
{React.cloneElement(child, {
name: name && i === 0 ? name : undefined,
timestamp: timestamp && i === 0 ? timestamp : undefined,
toptextHeadingLevel,
...child.props,
})}
</BodyLong>
@@ -43,6 +43,10 @@ export const Controls: Story = {
control: { type: "radio" },
options: [...POSITIONS],
},
toptextHeadingLevel: {
control: { type: "radio" },
options: ["2", "3", "4", "5", "6"],
},
size: {
control: { type: "radio" },
options: [...SIZES],
@@ -43,29 +43,25 @@ const FilteredOptionsItem = ({ option }: { option: ComboboxOption }) => {
const isDisabled = (_option: ComboboxOption) =>
maxSelected.isLimitReached && !isInList(_option.value, selectedOptions);
const optionId = filteredOptionsUtil.getOptionId(id, option.value);
const isActive = activeDecendantId === optionId;
return (
<li
className={cn("navds-combobox__list-item", {
"navds-combobox__list-item--focus":
activeDecendantId ===
filteredOptionsUtil.getOptionId(id, option.label),
"navds-combobox__list-item--focus": isActive,
"navds-combobox__list-item--selected": isInList(
option.value,
selectedOptions,
),
})}
data-no-focus={isDisabled(option) || undefined}
id={filteredOptionsUtil.getOptionId(id, option.label)}
key={option.label}
id={optionId}
key={optionId}
tabIndex={-1}
onMouseMove={() => {
if (
activeDecendantId !==
filteredOptionsUtil.getOptionId(id, option.label)
) {
virtualFocus.moveFocusToElement(
filteredOptionsUtil.getOptionId(id, option.label),
);
if (!isActive) {
virtualFocus.moveFocusToElement(optionId);
setIsMouseLastUsedInputDevice(true);
}
}}
@@ -91,7 +91,7 @@ const FilteredOptionsProvider = ({
? toComboboxOption(value)
: undefined,
...customOptions.reduce((acc, customOption) => {
const _id = filteredOptionsUtils.getOptionId(id, customOption.label);
const _id = filteredOptionsUtils.getOptionId(id, customOption.value);
acc[_id] = customOption;
return acc;
}, {}),
@@ -99,7 +99,7 @@ const FilteredOptionsProvider = ({
// Add the options to the map
const finalMap = options.reduce((map, _option) => {
const _id = filteredOptionsUtils.getOptionId(id, _option.label);
const _id = filteredOptionsUtils.getOptionId(id, _option.value);
map[_id] = _option;
return map;
}, initialMap);
@@ -171,7 +171,7 @@ const FilteredOptionsProvider = ({
if (shouldAutocomplete && filteredOptions[0]) {
activeOption = filteredOptionsUtils.getOptionId(
id,
filteredOptions[0].label,
filteredOptions[0].value,
);
} else if (isListOpen && isLoading) {
activeOption = filteredOptionsUtils.getIsLoadingId(id);
@@ -29,6 +29,11 @@ interface TimelineClickablePeriodProps extends PeriodProps {
) => void;
isActive?: boolean;
periodRef: React.ForwardedRef<HTMLButtonElement>;
/**
* Default orientation of popover
* @default "top"
*/
placement?: "top" | "bottom";
}
const ClickablePeriod = React.memo(
@@ -66,7 +71,7 @@ const ClickablePeriod = React.memo(
refs,
floatingStyles,
} = useFloating({
placement: "top",
placement: restProps?.placement ?? "top",
open,
onOpenChange: (_open) => setOpen(_open),
whileElementsMounted: autoUpdate,
@@ -43,6 +43,11 @@ export interface TimelinePeriodProps
* Make sure only one period is active at a time.
*/
isActive?: boolean;
/**
* Default orientation of popover
* @default "top"
*/
placement?: "top" | "bottom";
}
export interface PeriodType
@@ -54,7 +59,7 @@ export interface PeriodType
}
export const Period = forwardRef<HTMLDivElement, TimelinePeriodProps>(
({ icon }, ref) => {
({ icon }: TimelinePeriodProps, ref) => {
const { periods } = useRowContext();
const { periodId, restProps } = usePeriodContext();
@@ -386,6 +386,7 @@ export const ActivePeriod: StoryFn = () => {
end={new Date("Aug 20 2022")}
status="info"
icon={<CheckmarkCircleFillIcon aria-hidden />}
placement="bottom"
/>
</Timeline.Row>
</Timeline>
+4
View File
@@ -1,5 +1,9 @@
# @navikt/ds-tailwind
## 7.21.0
## 7.20.0
## 7.19.1
## 7.19.0
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-tailwind",
"version": "7.19.1",
"version": "7.21.0",
"description": "Tailwind config based on @navikt/ds-tokens",
"author": "Aksel | Nav designsystem team",
"keywords": [
@@ -25,7 +25,7 @@
"directory": "@navikt/core/tailwind"
},
"devDependencies": {
"@navikt/ds-tokens": "^7.19.1",
"@navikt/ds-tokens": "^7.21.0",
"color": "4.2.3",
"lodash": "^4.17.21",
"tailwindcss": "^3.3.3",
+4
View File
@@ -1,5 +1,9 @@
# @navikt/ds-tokens
## 7.21.0
## 7.20.0
## 7.19.1
## 7.19.0
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-tokens",
"version": "7.19.1",
"version": "7.21.0",
"description": "Design-tokens for Nav designsystem",
"author": "Aksel | Nav designsystem team",
"keywords": [
@@ -37,8 +37,8 @@
"@figma/plugin-typings": "^1.100.2",
"color": "4.2.3",
"colorjs.io": "^0.5.2",
"esbuild": "^0.25.0",
"lightningcss": "^1.29.2",
"esbuild": "^0.25.1",
"lightningcss": "^1.29.3",
"lodash": "^4.17.21",
"style-dictionary": "^4.1.1",
"tsx": "^4.19.1",
+578 -552
View File
File diff suppressed because it is too large. Load diff
+3 -2
View File
@@ -23,8 +23,8 @@ spec:
limits:
memory: 1Gi
requests:
cpu: 140m
memory: 640Mi
cpu: 100m
memory: 896Mi
liveness:
path: /api/isAlive
initialDelay: 20
@@ -47,6 +47,7 @@ spec:
- host: hnbe3yhs.apicdn.sanity.io
- host: cdn.sanity.io
- host: umami.nav.no
- host: raw.githubusercontent.com
rules: []
gcp:
+5 -5
View File
@@ -15,10 +15,10 @@
"playroom"
],
"dependencies": {
"@navikt/aksel-icons": "^7.19.1",
"@navikt/ds-css": "^7.19.1",
"@navikt/ds-react": "^7.19.1",
"@navikt/ds-tailwind": "^7.19.1",
"@navikt/ds-tokens": "^7.19.1"
"@navikt/aksel-icons": "^7.21.0",
"@navikt/ds-css": "^7.21.0",
"@navikt/ds-react": "^7.21.0",
"@navikt/ds-tailwind": "^7.21.0",
"@navikt/ds-tokens": "^7.21.0"
}
}
+90
View File
@@ -0,0 +1,90 @@
import { headers } from "next/headers";
import "server-only";
import { logger } from "@navikt/next-logger";
import { getToken, validateToken } from "@navikt/oasis";
const isLocalOrDemo = process.env.NODE_ENV !== "production";
async function isUserLoggedIn(): Promise<boolean> {
if (isLocalOrDemo) {
return true;
}
const requestHeaders = await headers();
const token = getToken(requestHeaders);
if (!token) {
return false;
}
const validationResult = await validateToken(token);
return validationResult.ok;
}
async function verifyUserLoggedIn(): Promise<
| {
ok: true;
user: ReturnType<typeof getAuthUser>;
}
| { ok: false; user: null }
> {
const requestHeaders = await headers();
if (isLocalOrDemo) {
logger.warn("Running locally, skipping RSC wonderwall auth");
return {
ok: true,
user: getAuthUser(
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjlHbW55RlBraGMzaE91UjIybXZTdmduTG83WSJ9.eyJhdWQiOiJmYWtlLWF1ZCIsImlzcyI6Imh0dHBzOi8vbG9naW4ubWljcm9zb2Z0b25saW5lLmNvbS85NjZhYzU3Mi1mNWI3LTRiYmUtYWE4OC1jNzY0MTljMGY4NTEvdjIuMCIsImlhdCI6MTY5ODQxMTU0NiwibmJmIjoxNjk4NDExNTQ2LCJleHAiOjE2OTg0MTY1MjYsImFpbyI6ImZha2UtYWlvIiwiYXpwIjoiZmFrZS1henAiLCJhenBhY3IiOiIyIiwiZ3JvdXBzIjpbImZha2UtZ3JvdXAiXSwibmFtZSI6IkZha2UgVG9rZW5lbnNzb24iLCJvaWQiOiJmYWtlLW9pZCIsInByZWZlcnJlZF91c2VybmFtZSI6ImZha2UtbG9uZy1lbWFpbC1hdEBleGFtcGxlLm5vIiwicmgiOiJmYWtlLXJoLiIsInNjcCI6ImRlZmF1bHRhY2Nlc3MiLCJzdWIiOiJmYWtlLXN1YiIsInRpZCI6ImZha2UtdGlkIiwidXRpIjoiekY4R0VGemhfRTZya3gtYzNnNFNBQSIsInZlciI6IjIuMCIsIk5BVmlkZW50IjoiWjk5MjM4OSIsImF6cF9uYW1lIjoiZGV2LWdjcDpmYWtlLXRlYW06aGVsc2VzamVray1ib3QifQ.OOP_Q0ZcUI6jQzM1UON9mA8d8BU_GhioQ5OLuepwebg",
),
};
}
const token = getToken(requestHeaders);
if (!token) {
logger.info("Found no token");
return {
ok: false,
user: null,
};
}
const validationResult = await validateToken(token);
if (!validationResult.ok) {
if (validationResult.errorType !== "token expired") {
logger.error(
new Error(
`Invalid JWT token found (cause: ${validationResult.errorType} ${validationResult.error.message}`,
{ cause: validationResult.error },
),
);
}
return {
ok: false,
user: null,
};
}
return {
ok: true,
user: getAuthUser(token),
};
}
function getAuthUser(token: string): {
name: string;
email: string;
} {
const jwt = JSON.parse(
Buffer.from(token.split(".")[1], "base64").toString("utf8"),
);
return {
name: jwt.name,
email: jwt.preferred_username,
};
}
export { verifyUserLoggedIn, isUserLoggedIn };
+1
View File
@@ -12,6 +12,7 @@ export const { sanityFetch, SanityLive } = defineLive({
client,
serverToken: token,
/* Only add after draftmode is put behind auth */
browserToken: "invalid token",
/* browserToken: token, */
stega: false,
});
+115 -1
View File
@@ -20,6 +20,25 @@ const DESIGNSYSTEM_OVERVIEW_PAGES_QUERY = defineQuery(
}`,
);
const BLOGG_LANDINGSSIDE_BLOGS_QUERY = defineQuery(`
*[_type == "blogg_landingsside"][0]{
"bloggposts": *[_type == "aksel_blogg"] | order(publishedAt desc, _createdAt desc){
seo,
heading,
ingress,
publishedAt,
_createdAt,
_id,
"slug": slug.current,
${contributorsAll}
}
}`);
const BLOGG_LANDINGSSIDE_PAGE_QUERY = defineQuery(`
*[_type == "blogg_landingsside"][0]{
"page": {...},
}`);
const DESIGNSYSTEM_KOMPONENTER_LANDINGPAGE_QUERY = defineQuery(
`*[_type == "komponenter_landingsside"][0]`,
);
@@ -113,7 +132,8 @@ const BLOGG_BY_SLUG_QUERY =
...,
${destructureBlocks}
},
${contributorsAll}
${contributorsAll},
publishedAt,
}`);
const TOC_BY_SLUG_QUERY =
@@ -133,6 +153,92 @@ const SLUG_BY_TYPE_QUERY = defineQuery(`
*[_type == $type && defined(slug.current)].slug.current
`);
/* ------------------------------- God praksis ------------------------------ */
const GOD_PRAKSIS_ALL_TEMA_QUERY =
defineQuery(`*[_type == "gp.tema"] | order(lower(title)){
title,
description,
pictogram,
"slug": slug.current,
"articles": *[_type=="aksel_artikkel"
&& (^._id in undertema[]->tema._ref)] {
heading,
"slug": slug.current,
"undertema": undertema[]->{title, "temaTitle": tema->title},
"innholdstype": innholdstype->title,
"views": *[_type == "article_views" && article_ref._ref == ^._id][0].views_month
} | order(coalesce(views, -1) desc)[0...4]{
heading,
slug,
undertema,
innholdstype
},
}`);
const GOD_PRAKSIS_LANDING_PAGE_SEO_QUERY = defineQuery(
`*[_type == "godpraksis_landingsside"][0].seo`,
);
const GOD_PRAKSIS_TEMA_BY_SLUG_QUERY = defineQuery(
`*[_type == "gp.tema" && slug.current == $slug][0]{
...,
"undertema": *[_type == "gp.tema.undertema" && tema->slug.current == $slug]{
title,
description
},
}`,
);
const GOD_PRAKSIS_ARTICLES_BY_TEMA_QUERY = defineQuery(
`*[_type == "aksel_artikkel" && defined(undertema) && $slug in undertema[]->tema->slug.current] | order(updateInfo.lastVerified desc) {
_id,
heading,
"displayDate": updateInfo.lastVerified,
"description": ingress,
"undertema": undertema[]->{title, "temaTitle": tema->title},
"innholdstype": innholdstype->title,
"slug": slug.current,
}`,
);
const GOD_PRAKSIS_ARTICLE_BY_SLUG_QUERY = defineQuery(
`*[slug.current == $slug && _type == "aksel_artikkel"][0]
{
...,
content[]{
...,
${destructureBlocks}
},
"innholdstype": innholdstype->title,
"undertema": undertema[]->{
title,
"tema": tema->{
title,
"slug": slug.current,
"image": seo.image
}
},
${contributorsAll},
relevante_artikler[]->{
heading,
ingress,
slug,
"innholdstype": innholdstype->title,
}
}`,
);
/* --------------------------------- Slack --------------------------------- */
const DOCUMENT_BY_ID_FOR_SLACK_QUERY = defineQuery(`*[_id == $id][0]{
"id": _id,
"title": heading,
"editors": contributors[]->email,
"slug": slug.current,
"contacts": undertema[]->tema->contacts[]->email
}`);
/* --------------------------------- Exports -------------------------------- */
export {
DESIGNSYSTEM_SIDEBAR_QUERY,
DESIGNSYSTEM_OVERVIEW_PAGES_QUERY,
@@ -148,4 +254,12 @@ export {
BLOGG_BY_SLUG_QUERY,
MONSTER_MALER_BY_SLUG_QUERY,
METADATA_BY_SLUG_QUERY,
BLOGG_LANDINGSSIDE_PAGE_QUERY,
BLOGG_LANDINGSSIDE_BLOGS_QUERY,
GOD_PRAKSIS_ALL_TEMA_QUERY,
GOD_PRAKSIS_LANDING_PAGE_SEO_QUERY,
GOD_PRAKSIS_TEMA_BY_SLUG_QUERY,
GOD_PRAKSIS_ARTICLES_BY_TEMA_QUERY,
GOD_PRAKSIS_ARTICLE_BY_SLUG_QUERY,
DOCUMENT_BY_ID_FOR_SLACK_QUERY,
};
File diff suppressed because it is too large. Load diff
@@ -1,15 +0,0 @@
.attachment {
display: flex;
gap: var(--ax-space-8);
border-radius: var(--ax-border-radius-large);
padding: var(--ax-space-12) var(--ax-space-16);
border: 1px solid var(--ax-border-subtle);
background-color: var(--ax-bg-moderate);
}
.attachmentIcon {
display: grid;
flex-shrink: 0;
place-content: center;
height: 1.5rem;
}
@@ -1,49 +1,37 @@
import { PortableTextBlock } from "next-sanity";
import { useId } from "react";
import { DownloadIcon } from "@navikt/aksel-icons";
import { Heading, Link, VStack } from "@navikt/ds-react";
import { Button } from "@navikt/ds-react";
import { CustomPortableText } from "@/app/CustomPortableText";
import { ExtractPortableComponentProps } from "@/app/_sanity/types";
import styles from "./Attachment.module.css";
import { EditorPanel } from "@/app/_ui/editor-panel/EditorPanel";
function Attachment(props: ExtractPortableComponentProps<"attachment">) {
const { title, downloadLink, fileName, size, body } = props.value;
const id = useId();
if (!title || !downloadLink || !fileName || !size) {
if (!title || !downloadLink || !fileName || !size || !body) {
return null;
}
const filetype = downloadLink.split(".").at(-1);
return (
<section
data-block-margin="space-28"
aria-labelledby={id}
className={styles.attachment}
data-color-role="aksel-brand-pink"
<EditorPanel
variant="attachment"
heading={title}
actionComponent={
<Button
variant="tertiary-neutral"
as="a"
href={`${downloadLink}?dl=${fileName}.${filetype}`}
rel="noreferrer noopener"
download={fileName}
icon={<DownloadIcon title="Laste ned logopakke" />}
size="small"
/>
}
>
<span className={styles.attachmentIcon}>
<DownloadIcon aria-hidden fontSize="1.5rem" />
</span>
<div>
<Heading size="small" level="2" id={id} aria-hidden>
{title}
</Heading>
<VStack gap="space-8">
{body && <CustomPortableText value={body as PortableTextBlock[]} />}
<Link
href={`${downloadLink}?dl=${fileName}.${filetype}`}
rel="noreferrer noopener"
download={fileName}
>
{`${fileName}.${filetype} `}
</Link>
</VStack>
</div>
</section>
<CustomPortableText value={body as PortableTextBlock[]} />
</EditorPanel>
);
}
@@ -26,18 +26,18 @@
.bildeFigure {
margin-inline: auto;
background-color: var(--bilde-background, var(--ax-bg-neutral-softA));
border-radius: var(--ax-border-radius-large);
&[data-image-border="true"] {
box-shadow: 0 0 0 1px var(--ax-border-neutral-subtleA);
}
}
.bildeImage {
margin-inline: auto;
padding: 0;
border-radius: var(--ax-border-radius-large);
background-color: var(--bilde-background, var(--ax-bg-neutral-softA));
&[data-image-border="true"] {
box-shadow: 0 0 0 1px var(--ax-border-neutral-subtleA);
}
&[data-image-size="small"] {
max-width: min(100%, 600px);
+10 -9
View File
@@ -29,14 +29,11 @@ function Bilde(props: ExtractPortableComponentProps<"bilde">) {
`rgba(${background.rgb?.r},${background.rgb?.g},${background.rgb?.b},${background.rgb?.a})`;
return (
<figure
data-block-margin="space-28"
className={styles.bildeFigure}
style={{ "--bilde-background": imageBg }}
data-image-border={border}
>
<figure data-block-margin="space-28" className={styles.bildeFigure}>
<img
data-image-border={border}
className={`light ${styles.bildeImage}`}
style={{ "--bilde-background": imageBg }}
alt={dekorativt ? "" : alt}
decoding="async"
src={imageUrl}
@@ -45,11 +42,15 @@ function Bilde(props: ExtractPortableComponentProps<"bilde">) {
{caption && (
<HGrid
as="figcaption"
marginBlock="space-8 0"
marginBlock="space-12 0"
marginInline="space-12"
gap="space-4"
paddingBlock="space-16"
>
<BodyLong as="span" size="small">
<BodyLong
as="span"
size="small"
align={kilde?.har_kilde ? "start" : "center"}
>
{caption}
</BodyLong>
{kilde?.har_kilde && (
@@ -48,15 +48,17 @@ function CodeBlockProvider({
children,
tabs,
showLineNumbers,
defaultWrap = true,
}: {
children: React.ReactNode;
tabs: CodeBlockTabsT;
showLineNumbers: boolean;
defaultWrap?: boolean;
}) {
const [currentCodeSnippet, setCurrentCodeSnippet] = useState<string | null>(
tabs?.[0]?.code ?? null,
);
const [wrapCode, setWrapCode] = useState(true);
const [wrapCode, setWrapCode] = useState(defaultWrap);
const [expanded, setExpanded] = useState(false);
const updateCodeSnippet = (value: string, isExpanded?: boolean) => {
@@ -4,8 +4,6 @@ import { Highlight } from "prism-react-renderer";
import { useId } from "react";
import { ChevronDownUpIcon, ChevronUpDownIcon } from "@navikt/aksel-icons";
import { Button, CopyButton, HStack, Spacer, Tabs } from "@navikt/ds-react";
/* @ts-expect-error Import is valid, workspace just can't resolve it */
import { TabsList, TabsPanel, TabsTab } from "@navikt/ds-react/Tabs";
import styles from "./CodeBlock.module.css";
import {
@@ -18,6 +16,7 @@ import { AkselPrismTheme } from "./CodePrismTheme";
type CodeBlockT = {
tabs: CodeBlockTabsT;
showLineNumbers?: boolean;
defaultWrap?: boolean;
};
/**
@@ -26,10 +25,14 @@ type CodeBlockT = {
* - Add support for DIFF
*/
function CodeBlock(props: CodeBlockT & React.HTMLAttributes<HTMLDivElement>) {
const { tabs, showLineNumbers = true, ...rest } = props;
const { tabs, showLineNumbers = true, defaultWrap, ...rest } = props;
return (
<CodeBlockProvider tabs={tabs} showLineNumbers={showLineNumbers}>
<CodeBlockProvider
tabs={tabs}
showLineNumbers={showLineNumbers}
defaultWrap={defaultWrap}
>
<CodeBlockView {...rest} />
</CodeBlockProvider>
);
@@ -9,7 +9,6 @@ import {
TextNeutralSubtle,
TextSuccess,
TextWarning,
/* @ts-expect-error Workspace cant resolve valid import */
} from "@navikt/ds-tokens/darkside-js";
const AkselPrismTheme: PrismTheme = {
@@ -138,14 +138,17 @@ function CompareImagesProvider({
/* Set on CompareHandle */
const handleKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => {
event.preventDefault();
if (event.key === "ArrowLeft" || event.key === "ArrowDown") {
event.preventDefault();
movePosition(-5);
} else if (event.key === "ArrowRight" || event.key === "ArrowUp") {
event.preventDefault();
movePosition(5);
} else if (event.key === "Home") {
event.preventDefault();
movePosition(-100);
} else if (event.key === "End") {
event.preventDefault();
movePosition(100);
}
};
@@ -0,0 +1,21 @@
/* stylelint-disable csstools/value-no-unknown-custom-properties */
.editorPanel {
border-radius: var(--ax-border-radius-xlarge);
border: 1px solid var(--ax-border-default);
overflow: hidden;
}
.editorPanelHeader {
display: flex;
align-items: center;
gap: var(--ax-space-4);
padding: var(--ax-space-8) var(--ax-space-20);
background: var(--ax-bg-moderateA);
border-bottom: 1px solid var(--ax-border-subtleA);
color: var(--ax-text-subtle);
}
.editorPanelContent {
padding-block: var(--ax-space-12) var(--ax-space-16);
padding-inline: var(--ax-space-20);
}
@@ -0,0 +1,147 @@
import {
DownloadIcon,
ExclamationmarkTriangleIcon,
FileTextIcon,
InformationSquareIcon,
LightBulbIcon,
LinkIcon,
ThumbDownIcon,
ThumbUpIcon,
} from "@navikt/aksel-icons";
import { Heading, Spacer } from "@navikt/ds-react";
import { GlobalColorRoles } from "@navikt/ds-tokens/types";
import { AkselBrandColors } from "@/app/theme";
import styles from "./EditorPanel.module.css";
type EditorPanelProps = {
children: React.ReactNode;
/**
* Overrides default heading
*/
heading?: string;
/**
* @default "h2"
*/
headingTag?: "h2" | "h3" | "h4" | "p";
/**
* Optional action
* @example <CopyButton />
*/
actionComponent?: React.ReactNode;
variant:
| "tips"
| "do"
| "dont"
| "caution"
| "info"
| "links"
| "attachment"
| "example-text";
};
const VariantConfig: Record<
EditorPanelProps["variant"],
{
heading: string;
icon: JSX.Element;
colorRole: GlobalColorRoles | AkselBrandColors;
}
> = {
tips: {
heading: "Tips",
icon: <LightBulbIcon aria-hidden fontSize="1.5rem" />,
colorRole: "brand-blue",
},
do: {
heading: "Gjør",
icon: <ThumbUpIcon aria-hidden fontSize="1.5rem" />,
colorRole: "brand-blue",
},
dont: {
heading: "Unngå",
icon: <ThumbDownIcon aria-hidden fontSize="1.5rem" />,
colorRole: "danger",
},
caution: {
heading: "Pass på",
icon: <ExclamationmarkTriangleIcon aria-hidden fontSize="1.5rem" />,
colorRole: "warning",
},
info: {
heading: "Info",
icon: <InformationSquareIcon aria-hidden fontSize="1.5rem" />,
colorRole: "brand-blue",
},
links: {
heading: "Lenker",
icon: <LinkIcon aria-hidden fontSize="1.5rem" />,
colorRole: "neutral",
},
attachment: {
heading: "Vedlegg",
icon: <DownloadIcon aria-hidden fontSize="1.5rem" />,
colorRole: "neutral",
},
"example-text": {
heading: "Eksempeltekst",
icon: <FileTextIcon aria-hidden fontSize="1.5rem" />,
colorRole: "neutral",
},
} as const;
function EditorPanel(props: EditorPanelProps) {
const { variant, children, heading, headingTag, actionComponent } = props;
const config = VariantConfig[variant];
return (
<div
data-block-margin="space-28"
className={styles.editorPanel}
data-color-role={config.colorRole}
>
<EditorPanelHeader
variant={variant}
heading={heading}
headingTag={headingTag}
actionComponent={actionComponent}
/>
<div data-text-prose className={styles.editorPanelContent}>
{children}
</div>
</div>
);
}
function EditorPanelHeader(
props: Pick<
EditorPanelProps,
"variant" | "heading" | "headingTag" | "actionComponent"
>,
) {
const {
variant,
heading,
headingTag = "h2",
actionComponent,
} = props;
const config = VariantConfig[variant];
return (
<div className={styles.editorPanelHeader} data-variant={variant}>
{config.icon}
<Heading as={headingTag} size="small">
{heading ?? config.heading}
</Heading>
{actionComponent && (
<>
<Spacer />
{actionComponent}
</>
)}
</div>
);
}
export { EditorPanel };
@@ -0,0 +1,240 @@
import { BodyShort, BoxNew, HGrid, HStack, Heading } from "@navikt/ds-react";
type EmptyStateCardProps = {
variant?: "default" | "questionmark";
actionComponent?: React.ReactNode;
};
function EmptyStateCard({
variant = "default",
actionComponent,
}: EmptyStateCardProps) {
return (
<BoxNew
asChild
borderWidth="1"
borderColor="neutral-subtleA"
borderRadius="xlarge"
>
<HGrid
padding="space-16"
gap="space-12"
columns={{ xs: "auto", md: "auto 1fr" }}
align="center"
>
<HStack justify="center">
{variant === "questionmark" ? (
<EmptyStateImageQuestionmark />
) : (
<EmptyStateImageSparkles />
)}
</HStack>
<div>
<Heading size="xsmall" level="3" spacing>
Ingen treff på filteret ditt
</Heading>
<BodyShort size="small" spacing>
Prøv å justere filteret ditt. Om du fortsatt sitter igjen tomhendt
kan du opprette et issue på Github, eller kontakte oss på Slack så
ser vi på det sammen.
</BodyShort>
{actionComponent && (
<BoxNew marginBlock="space-24 0">{actionComponent}</BoxNew>
)}
</div>
</HGrid>
</BoxNew>
);
}
function EmptyStateImageSparkles() {
return (
<svg
width="200"
height="200"
viewBox="0 0 200 200"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden
>
<circle
cx="92.0352"
cy="85.9491"
r="40.622"
transform="rotate(45 92.0352 85.9491)"
stroke="var(--ax-bg-brand-blue-strong)"
strokeWidth="6"
/>
<path
d="M66.2188 116.459L45.4316 137.246"
stroke="var(--ax-bg-brand-blue-strong)"
strokeWidth="6"
/>
<path
d="M45.4336 137.247L24.6465 158.034"
stroke="var(--ax-bg-brand-blue-strong)"
strokeWidth="14"
strokeLinecap="round"
/>
<circle
cx="116.23"
cy="137.484"
r="38.2574"
fill="var(--ax-bg-brand-blue-moderate-hover)"
/>
<path
d="M99.2726 127.081L111.364 141.461C111.833 142.018 112.09 142.723 112.09 143.451V157.908C112.09 159.616 113.474 161 115.181 161H117.543C119.251 161 120.635 159.616 120.635 157.909V143.465C120.635 142.729 120.897 142.016 121.376 141.457L133.643 127.1C135.357 125.093 133.931 122 131.292 122H101.639C99.0112 122 97.5815 125.07 99.2726 127.081Z"
fill="var(--ax-bg-brand-blue-strong)"
/>
<path
d="M131.301 39.7158L142.121 24.2583"
stroke="var(--ax-bg-brand-blue-strong)"
strokeWidth="6"
strokeLinecap="round"
/>
<path
d="M143.668 48.2176L175.356 25.8042"
stroke="var(--ax-bg-brand-blue-strong)"
strokeWidth="6"
strokeLinecap="round"
/>
<path
d="M149.078 62.9022L173.037 56.7192"
stroke="var(--ax-bg-brand-blue-strong)"
strokeWidth="6"
strokeLinecap="round"
/>
<path
d="M42.1016 29.3691V36.6061"
stroke="var(--ax-bg-brand-blue-strong)"
strokeWidth="2"
strokeLinecap="round"
/>
<path
d="M42.1016 44.5015V51.7384"
stroke="var(--ax-bg-brand-blue-strong)"
strokeWidth="2"
strokeLinecap="round"
/>
<path
d="M52.6289 40.5532L45.392 40.5532"
stroke="var(--ax-bg-brand-blue-strong)"
strokeWidth="2"
strokeLinecap="round"
/>
<path
d="M38.1523 40.5532L30.9154 40.5532"
stroke="var(--ax-bg-brand-blue-strong)"
strokeWidth="2"
strokeLinecap="round"
/>
<path
d="M172.719 99.7012V106.938"
stroke="var(--ax-bg-brand-blue-strong)"
strokeWidth="2"
strokeLinecap="round"
/>
<path
d="M172.719 114.833V122.07"
stroke="var(--ax-bg-brand-blue-strong)"
strokeWidth="2"
strokeLinecap="round"
/>
<path
d="M183.246 110.885L176.009 110.885"
stroke="var(--ax-bg-brand-blue-strong)"
strokeWidth="2"
strokeLinecap="round"
/>
<path
d="M168.77 110.885L161.533 110.885"
stroke="var(--ax-bg-brand-blue-strong)"
strokeWidth="2"
strokeLinecap="round"
/>
</svg>
);
}
function EmptyStateImageQuestionmark() {
return (
<svg
width="200"
height="200"
viewBox="0 0 200 200"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden
>
<path
d="M34.7626 108.638L31.4932 114.171C30.9625 115.069 30.9625 116.185 31.4932 117.083L34.7626 122.616C35.2776 123.488 36.2146 124.022 37.2269 124.022H44.1088C45.1323 124.022 46.078 123.476 46.5889 122.589L49.7766 117.056C50.2861 116.172 50.2861 115.083 49.7766 114.198L46.5889 108.665C46.078 107.778 45.1323 107.232 44.1088 107.232H37.2269C36.2146 107.232 35.2776 107.767 34.7626 108.638Z"
fill="var(--ax-bg-brand-blue-moderate-pressed)"
/>
<circle
cx="99.0343"
cy="29.5465"
r="6.56944"
fill="var(--ax-bg-brand-blue-moderate-pressed)"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M70.9989 95.9112C86.9702 91.6317 96.4482 75.2152 92.1687 59.2439C87.8892 43.2726 71.4727 33.7945 55.5014 38.074C39.5301 42.3535 30.0521 58.77 34.3316 74.7413C38.6111 90.7126 55.0276 100.191 70.9989 95.9112ZM72.5518 101.707C91.7239 96.5696 103.101 76.8631 97.9643 57.691C92.8271 38.5189 73.1206 27.1413 53.9485 32.2785C34.7764 37.4156 23.3989 57.1221 28.536 76.2942C33.6731 95.4663 53.3797 106.844 72.5518 101.707Z"
fill="var(--ax-bg-brand-blue-strong)"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M116.475 135.774L82.3477 93.8154L87.0024 90.0295L121.129 131.989L116.475 135.774Z"
fill="var(--ax-bg-brand-blue-strong)"
/>
<path
d="M168.895 48.3449C169.733 47.6852 170.972 48.1235 171.209 49.1633L173.954 61.217C174.208 62.3323 173.133 63.2897 172.054 62.9083L159.596 58.5024C158.518 58.121 158.283 56.7003 159.182 55.9927L168.895 48.3449Z"
fill="var(--ax-bg-brand-blue-moderate-pressed)"
/>
<path
d="M121.718 67.3612C122.638 66.4419 124.128 66.442 125.047 67.3612L131.602 73.9158C132.521 74.835 132.521 76.3253 131.602 77.2445L125.047 83.7991C124.128 84.7183 122.638 84.7183 121.718 83.7991L115.164 77.2445C114.245 76.3253 114.245 74.835 115.164 73.9158L121.718 67.3612Z"
fill="var(--ax-bg-brand-blue-moderate-pressed)"
/>
<path
d="M174.266 163.502L102.266 177.022L30.2656 163.502L38.2656 137.543L102.266 124.022L166.266 137.543L174.266 163.502Z"
fill="url(#paint0_linear_271_5972)"
/>
<path
d="M101.876 142.252L109.446 169.331C109.455 169.363 109.483 169.386 109.516 169.388C117.361 169.924 125.242 169.268 132.89 167.442L133.19 167.371C133.228 167.362 133.249 167.32 133.234 167.284L126.421 151.134C123.718 144.727 123.878 137.47 126.861 131.188L127.289 130.286C128.765 127.179 129.434 123.75 129.237 120.316L128.162 101.661C128.037 99.4891 126.058 97.9037 123.912 98.2547C122.377 98.5055 120.852 97.7613 120.106 96.3979L119.488 95.2692C118.701 93.8337 116.984 93.1885 115.447 93.7513C114.17 94.219 112.737 93.8577 111.835 92.8406L110.977 91.8738C110.029 90.8065 108.437 90.6078 107.257 91.4094C106.06 92.2222 104.443 92.0055 103.503 90.906L102.678 89.9423C101.646 88.7352 100.012 88.2284 98.4772 88.6393C96.6739 89.1223 95.4198 90.7564 95.4198 92.6233V109.28C95.4198 111.266 94.347 113.097 92.6146 114.068C89.9256 115.575 89.0047 119.001 90.5757 121.653L99.5479 136.801C100.559 138.508 101.342 140.341 101.876 142.252Z"
fill="var(--ax-bg-brand-blue-moderate-hover)"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M65.3839 55.6342C64.474 54.6203 63.095 54.1644 61.7601 54.4363L61.4384 54.5018C59.383 54.9204 57.9062 56.7282 57.9062 58.8258V59.0841C57.9062 60.1886 57.0108 61.0841 55.9062 61.0841C54.8017 61.0841 53.9062 60.1886 53.9062 59.0841V58.8258C53.9062 54.8267 56.7215 51.3803 60.6402 50.5823L60.9619 50.5167C63.6875 49.9617 66.5031 50.8924 68.3609 52.9625C70.9908 55.893 71.039 60.3195 68.4735 63.3065L66.7373 65.328C66.4984 65.6061 66.2822 65.9029 66.0907 66.2154L65.7821 66.7191C64.526 68.7689 64.0709 71.2104 64.5041 73.5751C64.7031 74.6616 63.9836 75.7037 62.8971 75.9027C61.8106 76.1017 60.7685 75.3823 60.5695 74.2958C59.9588 70.9616 60.6005 67.5194 62.3714 64.6292L62.68 64.1256C62.983 63.6311 63.325 63.1617 63.7029 62.7218L65.4391 60.7003C66.6956 59.2374 66.672 57.0694 65.3839 55.6342Z"
fill="var(--ax-bg-brand-blue-strong)"
/>
<path
d="M65.8586 80.6739C65.8586 82.1116 64.6931 83.277 63.2554 83.277C61.8178 83.277 60.6523 82.1116 60.6523 80.6739C60.6523 79.2363 61.8178 78.0708 63.2554 78.0708C64.6931 78.0708 65.8586 79.2363 65.8586 80.6739Z"
fill="var(--ax-bg-brand-blue-strong)"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M33.4414 163.287L34.5932 157.212L102.635 170.114L170.676 157.212L171.828 163.287L102.635 176.407L33.4414 163.287Z"
fill="var(--ax-bg-brand-blue-strong-hover)"
/>
<defs>
<linearGradient
id="paint0_linear_271_5972"
x1="112"
y1="124"
x2="102.266"
y2="177.022"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="var(--ax-bg-brand-blue-strong)" />
<stop offset="1" stopColor="var(--ax-bg-brand-blue-strong-pressed)" />
</linearGradient>
</defs>
</svg>
);
}
export { EmptyStateCard };
@@ -1,11 +0,0 @@
.exampleText {
border-radius: var(--ax-border-radius-large);
padding: var(--ax-space-12) var(--ax-space-16);
border: 1px solid var(--ax-border-neutral-subtle);
background-color: var(--ax-bg-neutral-moderate);
}
.exampleTextDivider {
margin-block: var(--ax-space-12);
border-top: 1px solid var(--ax-border-neutral-subtleA);
}
@@ -1,39 +1,26 @@
import { useId } from "react";
import {
BodyLong,
CopyButton,
HStack,
Heading,
VStack,
} from "@navikt/ds-react";
import { BodyLong, CopyButton, VStack } from "@navikt/ds-react";
import { ExtractPortableComponentProps } from "@/app/_sanity/types";
import styles from "./ExampleText.module.css";
import { EditorPanel } from "@/app/_ui/editor-panel/EditorPanel";
function ExampleText(
props: ExtractPortableComponentProps<"exampletext_block">,
) {
const { title, text } = props.value;
const id = useId();
if (!text || !title) {
return null;
}
return (
<section
aria-labelledby={id}
className={styles.exampleText}
data-block-margin="space-28"
>
<HStack justify="space-between" gap="4" align="center" wrap={false}>
<Heading size="small" as="p" textColor="subtle" id={id} aria-hidden>
{title}
</Heading>
<CopyButton copyText={text} size="small" />
</HStack>
<hr aria-hidden className={styles.exampleTextDivider} />
<VStack gap="space-24">{formatText(text)}</VStack>
<section aria-label={title}>
<EditorPanel
variant="example-text"
heading={title}
headingTag="p"
actionComponent={<CopyButton size="small" copyText={text} />}
>
<VStack gap="space-24">{formatText(text)}</VStack>
</EditorPanel>
</section>
);
}
@@ -1,7 +1,5 @@
import Link from "next/link";
import { HGrid, Heading } from "@navikt/ds-react";
/* @ts-expect-error Workspace cant resolve valid import */
import { PageBlock } from "@navikt/ds-react/Page";
import { FigmaIcon, GithubIcon, SlackIcon } from "@/assets/Icons";
import AkselLogo from "@/assets/Logo";
@@ -107,7 +107,7 @@ function resolveAnchor(match: FuseResultMatch, item: SearchPageT) {
return null;
}
function preloadSearchIndex() {
async function preloadSearchIndex() {
void fetchArticles();
}
@@ -3,7 +3,7 @@
display: flex;
align-items: center;
gap: var(--ax-space-8);
padding: var(--ax-space-4) var(--ax-space-8);
padding: var(--ax-space-8);
border-bottom: 1px solid var(--ax-border-neutral-subtle);
background-color: var(--ax-bg-neutral-soft);
@@ -3,10 +3,14 @@
background-color: var(--ax-bg-default);
position: sticky;
top: 0;
z-index: 1;
z-index: 20;
border-bottom: 1px solid var(--ax-border-neutral-subtleA);
}
.headerProduktbloggen {
background-color: var(--ax-bg-brand-blue-soft);
}
.headerContainer {
height: var(--website-header-height);
display: flex;
@@ -87,11 +91,12 @@
color: var(--ax-text-info-contrast);
text-decoration: none;
/* We move it further away to avoid it showing when scroll "overshoots" */
transform: translateY(-300%);
/* We move it further away and (just in case scale it to 0)
to avoid it showing when scroll "overshoots" */
transform: translateY(-300%) scale(0);
&:focus-within {
transform: translateY(0);
transform: translateY(0) scale(1);
outline: none;
}
}
+31 -4
View File
@@ -1,5 +1,6 @@
import clsx from "clsx";
import Link from "next/link";
import { Box, HStack, Show, Spacer } from "@navikt/ds-react";
import { Box, HStack, Hide, Show, Spacer } from "@navikt/ds-react";
import { GlobalSearch } from "@/app/_ui/global-search/GlobalSearch";
import { MobileNav } from "@/app/_ui/mobile-nav/MobileNav";
import { ThemeButton } from "@/app/_ui/theming/Theme.button";
@@ -13,9 +14,14 @@ const LINKS = [
{ name: "Bloggen", href: "/produktbloggen" },
];
function Header() {
function Header({ variant }: { variant?: "default" | "produktbloggen" }) {
variant = variant ? variant : "default";
return (
<header className={styles.header}>
<header
className={clsx(styles.header, {
[styles.headerProduktbloggen]: variant === "produktbloggen",
})}
>
<a className={styles.skiplink} href="#hovedinnhold">
Hopp til innhold
</a>
@@ -27,7 +33,28 @@ function Header() {
data-umami-event-kilde="header"
className={styles.headerLogoLink}
>
<AkselLogo className={styles.headerLogo} />
<Show above="sm">
<AkselLogo className={styles.headerLogo} />
</Show>
<Hide above="sm">
<svg
width="26"
height="26"
viewBox="0 0 48 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={styles.headerLogo}
aria-hidden
focusable="false"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M11.1213 2.05025C12.4341 0.737498 14.2146 0 16.0711 0H41C44.866 0 48 3.13401 48 7V31.9289C48 33.7854 47.2625 35.5659 45.9497 36.8787L45.8787 36.9497L35.4142 47.4142C34.6332 48.1953 33.3668 48.1953 32.5858 47.4142C31.8047 46.6332 31.8047 45.3668 32.5858 44.5858L43.0503 34.1213C44.2218 32.9497 44.2218 31.0503 43.0503 29.8787L18.1213 4.94974C16.9503 3.77876 15.0522 3.77817 13.8804 4.94799L13.8787 4.94975L3.41421 15.4142C2.63316 16.1953 1.36683 16.1953 0.585786 15.4142C-0.0976316 14.7308 -0.183059 13.6758 0.329504 12.8995C0.402728 12.7886 0.488155 12.6834 0.585786 12.5858L11.0503 2.12132L11.0524 2.11913L11.1213 2.05025ZM44 7V25.1716L22.8284 4H41C42.6569 4 44 5.34315 44 7ZM21.9142 28.9142C22.6953 28.1332 22.6953 26.8668 21.9142 26.0858C21.1332 25.3047 19.8668 25.3047 19.0858 26.0858L0.585786 44.5858C-0.195263 45.3668 -0.195263 46.6332 0.585786 47.4142C1.36683 48.1953 2.63316 48.1953 3.41421 47.4142L21.9142 28.9142Z"
fill="currentColor"
/>
</svg>
</Hide>
<span className="sr-only">Aksel</span>
</Link>
@@ -1,8 +1,6 @@
"use client";
import { BodyLong } from "@navikt/ds-react";
/* @ts-expect-error Workspace cant resolve valid import */
import { Chips, ChipsToggle } from "@navikt/ds-react/Chips";
import { ExtractPortableComponentProps } from "@/app/_sanity/types";
import { MarkdownText } from "@/app/_ui/typography/MarkdownText";
@@ -37,7 +35,7 @@ function KodeEksemplerNavigation(props: {
selected={activeExample.current?.navn === fil.navn}
onClick={() => activeExample.update(fil.navn)}
>
{fil.title}
{fil.title ?? ""}
</ChipsToggle>
);
})}
@@ -70,7 +70,11 @@ function customPortableTextComponents({
block,
marks,
list: {
bullet: ({ children }) => <WebsiteList as="ul">{children}</WebsiteList>,
bullet: ({ children }) => (
<WebsiteList as="ul" /* data-block-margin="space-28" */>
{children}
</WebsiteList>
),
number: ({ children }) => <WebsiteList as="ol">{children}</WebsiteList>,
},
listItem: {
@@ -110,7 +114,7 @@ function marksComponents() {
function blockComponents({
typoConfig = { type: "long", size: "medium" },
}: CustomPortableTextComponentsProps) {
const BodyComponent = typoConfig.type === "short" ? BodyLong : BodyShort;
const BodyComponent = typoConfig.type === "long" ? BodyLong : BodyShort;
return {
normal: ({ children }) =>
@@ -119,6 +123,7 @@ function blockComponents({
spacing
className={styles.removeSpacingForLast}
size={typoConfig.size}
data-text-prose
>
{children}
</BodyComponent>,
@@ -9,7 +9,7 @@
&[data-level="2"] {
margin-block: var(--ax-space-48) var(--ax-space-16);
&:first-of-type {
&:first-child {
margin-top: 0;
}
}
@@ -1,12 +0,0 @@
.relatertInnhold {
padding: var(--ax-space-12) var(--ax-space-16);
border-radius: var(--ax-border-radius-xlarge);
border: 1px solid var(--ax-border-neutral-subtleA);
background: var(--ax-bg-neutral-soft);
color: var(--ax-text-neutral-subtle);
/* TODO: Handle spacing after enabling new Aksel-theme */
& ul {
margin-block: 0;
}
}
@@ -1,9 +1,8 @@
import NextLink from "next/link";
import { LinkIcon } from "@navikt/aksel-icons";
import { HStack, Heading, Link } from "@navikt/ds-react";
import { Link } from "@navikt/ds-react";
import { ExtractPortableComponentProps } from "@/app/_sanity/types";
import { EditorPanel } from "@/app/_ui/editor-panel/EditorPanel";
import { WebsiteList, WebsiteListItem } from "@/app/_ui/typography/WebsiteList";
import styles from "./RelatertInnhold.module.css";
function RelatertInnhold(
props: ExtractPortableComponentProps<"relatert_innhold">,
@@ -26,17 +25,7 @@ function RelatertInnhold(
};
return (
<div
className={styles.relatertInnhold}
data-block-margin="space-28"
data-color-role="neutral"
>
<HStack gap="space-8" align="center" marginBlock="0 space-4">
<LinkIcon fontSize="1.5rem" title="Lenker" aria-hidden />
<Heading size="small" as="p">
{title || "Lenker"}
</Heading>
</HStack>
<EditorPanel variant="links" heading={title ?? undefined} headingTag="p">
<WebsiteList as="ul">
{lenker.map((pageLink) => (
<WebsiteListItem key={pageLink._key} icon>
@@ -52,7 +41,7 @@ function RelatertInnhold(
</WebsiteListItem>
))}
</WebsiteList>
</div>
</EditorPanel>
);
}
@@ -0,0 +1,77 @@
"use client";
import { useEffect, useState } from "react";
import { BodyLong, Button, Link, VStack } from "@navikt/ds-react";
import { LinkCardArrow } from "@/app/dev/(god-praksis)/_ui/link-card/LinkCard";
import { clientConfig } from "@/sanity/config";
import styles from "./SystemPanel.module.css";
function SystemPanelAction() {
return (
<div className={styles.systemPanelAction}>
<Button
size="small"
variant="secondary-neutral"
as="a"
href="https://github.com/navikt/aksel/issues/new?labels=foresp%C3%B8rsel+%F0%9F%A5%B0%2Ckomponenter+%F0%9F%A7%A9%2Cbeta+%F0%9F%A7%AA&template=update-component.yml&title=%5BInnspill+til+komponent%5D%3A+%3CActionMenu%20/%3E"
>
Send innspill
</Button>
</div>
);
}
function SystemPanelOutdatedAction({ docId }: { docId?: string }) {
const [user, setUser] = useState<boolean | null>(null);
useEffect(() => {
const fetchUser = async () => {
const response = await fetch(
`https://${clientConfig.projectId}.api.sanity.io/v1/users/me`,
{
credentials: "include",
},
).then((x) => x.text());
try {
setUser(!!JSON.parse(response)?.id);
return;
} catch {
setUser(false);
}
setUser(false);
};
fetchUser();
}, []);
if (!user) {
return null;
}
return (
<VStack marginBlock="space-4 space-0" gap="space-20" align="start">
<BodyLong>
<Link
href="https://aksel.nav.no/side/skriv-for-aksel#a5b79ddd59da"
variant="neutral"
>
Hvordan oppdatere innhold i Sanity
</Link>
</BodyLong>
<Button
as="a"
href={`https://aksel.nav.no/admin/prod/intent/edit/id=${docId}`}
target="_blank"
size="small"
variant="secondary-neutral"
iconPosition="right"
data-link-card-anchor
icon={<LinkCardArrow />}
>
Kontroller innholdet nå
</Button>
</VStack>
);
}
export { SystemPanelAction, SystemPanelOutdatedAction };
@@ -0,0 +1,35 @@
/* stylelint-disable csstools/value-no-unknown-custom-properties */
.systemPanel {
padding: var(--ax-space-20);
border-radius: var(--ax-border-radius-xlarge);
border: 1px solid;
border-color: var(--ax-border-subtleA);
background: var(--ax-bg-soft);
}
.systemPanelContent {
position: relative;
padding-inline-start: calc(var(--ax-space-16) + 0.5rem);
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 0.5rem;
height: 100%;
border-radius: var(--ax-border-radius-full);
background-color: var(--ax-bg-strong);
}
}
.systemPanelHeader {
display: flex;
align-items: center;
gap: var(--ax-space-4);
margin-block-end: var(--ax-space-4);
}
.systemPanelAction {
margin-block-start: var(--ax-space-20);
}
@@ -0,0 +1,93 @@
import {
HourglassBottomFilledIcon,
SparklesIcon,
TestFlaskIcon,
} from "@navikt/aksel-icons";
import { BodyLong, Heading } from "@navikt/ds-react";
import {
SystemPanelAction,
SystemPanelOutdatedAction,
} from "./SystemPanel.action";
import styles from "./SystemPanel.module.css";
type SystemPanelProps = {
variant: "outdated" | "beta" | "new";
/**
* @default false
*/
unsafeBeta?: boolean;
/**
* Sanity document id
*/
docId?: string;
};
const VariantConfig = {
outdated: {
heading: "Innholdet kan være utdatert",
description:
"Det er over 1 år siden innholdet ble revidert. Vi kan ikke være helt sikre på hvor nøyaktig artikkelen er lenger.",
icon: <HourglassBottomFilledIcon aria-hidden fontSize="1.5rem" />,
colorRole: "neutral",
},
beta: {
heading: "Beta",
description:
"Komponenten er under utvikling, men klar for adopsjon. Vi ønsker gjerne innspill på hvordan den fungerer og hvilke forbedringer vi kan gjøre.",
icon: <TestFlaskIcon aria-hidden fontSize="1.5rem" />,
colorRole: "meta-purple",
},
new: {
heading: "Ny funksjon",
description:
"Denne komponenten er ny eller oppdatert. Tar du den i bruk ønsker vi gjerne innspill til hvordan den fungerer i tjenesten din!",
icon: <SparklesIcon aria-hidden fontSize="1.5rem" />,
colorRole: "success",
},
} as const;
const unsafeDescription =
"Komponenten er under utvikling. Så lenge komponenten er prefikset med UNSAFE kan det også medføre breaking-changes i minor versjon av kodepakker og i Figma. Teamet ditt må selv ta stilling til om dere ønsker å bruke denne i produksjon.";
function SystemPanel(props: SystemPanelProps) {
const { variant, docId, unsafeBeta = false } = props;
const config = VariantConfig[variant];
return (
<div
data-block-margin="space-28"
className={styles.systemPanel}
data-color-role={config.colorRole}
>
<div className={styles.systemPanelContent}>
<SystemPanelHeader variant={variant} />
<BodyLong data-text-prose>
{unsafeBeta ? unsafeDescription : config.description}
</BodyLong>
{variant === "outdated" ? (
<SystemPanelOutdatedAction docId={docId} />
) : (
<SystemPanelAction />
)}
</div>
</div>
);
}
function SystemPanelHeader({ variant }: Pick<SystemPanelProps, "variant">) {
const config = VariantConfig[variant];
return (
<div className={styles.systemPanelHeader} data-variant={variant}>
{config.icon}
<Heading level="2" size="small">
{config.heading}
</Heading>
</div>
);
}
export { SystemPanel };
@@ -6,7 +6,6 @@ import {
TableHeader,
TableHeaderCell,
TableRow,
/* @ts-expect-error: Workspace cant resolve import */
} from "@navikt/ds-react/Table";
import { ExtractPortableComponentProps } from "@/app/_sanity/types";
@@ -9,7 +9,7 @@ function ThemeButton() {
return (
<>
<style id="testid-123">{`
<style id="theme-button">{`
:root, .light {
--website-theme-toggle-light-display: block;
--website-theme-toggle-dark-display: none;
@@ -17,6 +17,7 @@ function ThemeProvider({ children }: { children: React.ReactNode }) {
defaultTheme="light"
enableSystem={false}
themes={colorThemes}
disableTransitionOnChange
>
<Theme>{children}</Theme>
</NextThemeProvider>
@@ -1,11 +0,0 @@
.tips {
border-radius: var(--ax-border-radius-xlarge);
border: 1px solid var(--ax-border-info-subtleA);
background: var(--ax-bg-info-soft);
padding: var(--ax-space-12) var(--ax-space-16);
}
.tipsHeader {
color: var(--ax-text-info-subtle);
margin-block-end: var(--ax-space-4);
}
+3 -11
View File
@@ -1,9 +1,7 @@
import { PortableTextBlock } from "next-sanity";
import { LightBulbIcon } from "@navikt/aksel-icons";
import { HStack, Heading } from "@navikt/ds-react";
import { CustomPortableText } from "@/app/CustomPortableText";
import { ExtractPortableComponentProps } from "@/app/_sanity/types";
import styles from "./Tips.module.css";
import { EditorPanel } from "@/app/_ui/editor-panel/EditorPanel";
function Tips(props: ExtractPortableComponentProps<"tips">) {
const { body } = props.value;
@@ -13,15 +11,9 @@ function Tips(props: ExtractPortableComponentProps<"tips">) {
}
return (
<div data-block-margin="space-28" className={styles.tips}>
<HStack gap="space-4" align="center" className={styles.tipsHeader}>
<LightBulbIcon fontSize="1.5rem" title="tips" aria-hidden />
<Heading size="small" as="p">
Tips
</Heading>
</HStack>
<EditorPanel variant="tips" headingTag="p">
<CustomPortableText value={body as PortableTextBlock[]} />
</div>
</EditorPanel>
);
}
@@ -101,7 +101,7 @@ function TableOfContentsLinks({
variant="secondary-neutral"
size="small"
icon={<SparklesIcon aria-hidden />}
href={`https://github.com/navikt/aksel/issues/new?labels=foresp%C3%B8rsel+%F0%9F%A5%B0%2Ckomponenter+%F0%9F%A7%A9&template=update-component.yml&title=%5BInnspill+til+komponent%5D%3A+%3C${feedback.name}%20/%3E`}
href={`https://github.com/navikt/aksel/issues/new?labels=foresp%C3%B8rsel+%F0%9F%A5%B0%2Ckomponenter+%F0%9F%A7%A9&template=update-component.yml&title=%5BInnspill%5D%20${feedback.name}`}
data-umami-event="feedback-designsystem"
data-umami-event-kilde="toc"
target="_blank"
@@ -22,9 +22,14 @@
max-width: 600px;
/* stylelint-disable-next-line aksel/no-class-override */
.navds-body-long.navds-typo--spacing + & {
/* stylelint-disable-next-line selector-pseudo-class-no-unknown */
:global(.aksel-body-long.aksel-typo--spacing) + & {
margin-top: -1rem;
}
&:not(:last-child) {
margin-bottom: var(--ax-space-16);
}
}
.typoListIcon {
@@ -2,15 +2,15 @@ import {
List as AkselList,
ListItem as AkselListItem,
ListProps as AkselListProps,
/* @ts-expect-error Workspace can't understand these imports yet */
} from "@navikt/ds-react/List";
import styles from "./Typography.module.css";
type ListProps = Pick<AkselListProps, "as" | "children">;
type ListProps = Pick<AkselListProps, "as" | "children"> &
React.HTMLAttributes<HTMLDivElement>;
function WebsiteList({ children, as = "ul" }: ListProps) {
function WebsiteList({ children, as = "ul", ...restProps }: ListProps) {
return (
<AkselList className={styles.typoList} as={as}>
<AkselList {...restProps} className={styles.typoList} as={as}>
{children}
</AkselList>
);
@@ -0,0 +1,61 @@
"use client";
import Script from "next/script";
import { useEffect, useState } from "react";
type UmamiScriptProps = {
enabled: boolean;
};
const trackingId = process.env.UMAMI_TRACKING_ID;
function UmamiScript({ enabled }: UmamiScriptProps) {
const [umamiTag, setUmamiTag] = useState<string | undefined>();
useEffect(() => {
setUmamiTag(classifyTraffic());
}, []);
/* We only track with umami if optional cookies are accepted */
if (!enabled || !trackingId || !umamiTag) {
return null;
}
return (
<Script
defer
src="https://cdn.nav.no/team-researchops/sporing/sporing.js"
data-host-url="https://umami.nav.no"
data-website-id={trackingId}
data-tag={umamiTag}
data-exclude-search="true"
/>
);
}
/**
* Classifies the current traffic as either "organic" or "polluted"
*
* Organic traffic: Production traffic from real users on the main site
* Polluted traffic: Traffic from preview environments, example pages, templates, admin pages, or non-production environments
*/
function classifyTraffic() {
const isProdUrl = () => window.location.host === "aksel.nav.no";
const isPreview = () => !!document.getElementById("exit-preview-id");
const isExample = () => window.location.pathname.startsWith("/eksempler/");
const isTemplate = () => window.location.pathname.startsWith("/templates/");
const isAdmin = () => window.location.pathname.startsWith("/admin/");
if (
isProdUrl() &&
!isPreview() &&
!isExample() &&
!isTemplate() &&
!isAdmin()
) {
return "organic";
}
return "polluted";
}
export { UmamiScript };
@@ -0,0 +1,10 @@
import { getCookieConsent } from "@/app/_ui/consent-banner/ConsentBanner.utils";
import { UmamiScript } from "./Umami.script";
async function Umami() {
const consent = await getCookieConsent();
return <UmamiScript enabled={consent === "accepted"} />;
}
export { Umami };
@@ -4,7 +4,6 @@ import {
AccordionContent,
AccordionHeader,
AccordionItem,
/* @ts-expect-error Workspace cant resolve valid import */
} from "@navikt/ds-react/Accordion";
import { useWebsiteAccordion } from "./WebsiteAccordion.provider";
@@ -5,7 +5,6 @@ import {
ExpansionCardDescription,
ExpansionCardHeader,
ExpansionCardTitle,
/* @ts-expect-error Workspace cant resolve valid import */
} from "@navikt/ds-react/ExpansionCard";
import { CustomPortableText } from "@/app/CustomPortableText";
import { ExtractPortableComponentProps } from "@/app/_sanity/types";
-21
View File
@@ -1,21 +0,0 @@
"use server";
import { draftMode } from "next/headers";
export async function disableDraftMode() {
"use server";
await Promise.allSettled([
(await draftMode()).disable(),
// Simulate a delay to show the loading state
new Promise((resolve) => setTimeout(resolve, 1000)),
]);
}
export async function enableDraftMode() {
"use server";
await Promise.allSettled([
(await draftMode()).enable(),
// Simulate a delay to show the loading state
new Promise((resolve) => setTimeout(resolve, 1000)),
]);
}
@@ -0,0 +1,27 @@
import { BodyShort } from "@navikt/ds-react";
import styles from "./Designsystemet.module.css";
const eyebrowMap = {
komponent_artikkel: "Komponenter",
ds_artikkel: "Grunnleggende",
templates_artikkel: "Maler",
};
type DesignsystemetEyebrowProps = {
type?: "komponent_artikkel" | "ds_artikkel" | "templates_artikkel";
};
function DesignsystemetEyebrow({ type }: DesignsystemetEyebrowProps) {
if (!type) {
return null;
}
return (
<BodyShort className={styles.pageHeaderEyebrow}>
{eyebrowMap[type]}
</BodyShort>
);
}
export { DesignsystemetEyebrow };
export type { DesignsystemetEyebrowProps };
@@ -27,7 +27,7 @@
.pageLayoutMain {
width: fit-content;
margin: 0 auto;
padding-block: var(--ax-space-24) var(--ax-space-128);
padding-block: var(--ax-space-56) var(--ax-space-128);
padding-inline: var(--ax-space-40);
display: grid;
column-gap: var(--ax-space-40);
@@ -57,8 +57,12 @@
}
.pageHeaderHeading {
color: var(--ax-text-brand-blue);
margin-block-end: var(--ax-space-8);
margin-block-end: var(--ax-space-4);
}
.pageHeaderEyebrow {
color: var(--ax-text-brand-blue-subtle);
margin-block-end: var(--ax-space-2);
}
.thumbnailContainer {
@@ -1,5 +1,5 @@
import { PortableTextBlock } from "next-sanity";
import { Detail, HStack, Heading, Tag } from "@navikt/ds-react";
import { BodyShort, Box, HStack, Heading, Tag } from "@navikt/ds-react";
import {
GRUNNLEGGENDE_BY_SLUG_QUERYResult,
KOMPONENT_BY_SLUG_QUERYResult,
@@ -7,6 +7,7 @@ import {
} from "@/app/_sanity/query-types";
import { CustomPortableText } from "@/app/_ui/portable-text/CustomPortableText";
import { getStatusTag } from "@/app/_ui/theming/theme-config";
import { DesignsystemetEyebrow } from "@/app/dev/(designsystemet)/_ui/Designsystemet.eyebrow";
import { dateStr } from "@/utils";
import styles from "./Designsystemet.module.css";
import { DesignsystemetThumbnail } from "./Designsystemet.thumbnail";
@@ -44,17 +45,21 @@ type DesignsystemetPageT = {
async function DesignsystemetPageHeader({ data }: DesignsystemetPageT) {
/* TODO: refactor dateStr to appdir */
const publishDate = await dateStr(data?._updatedAt ?? data?._createdAt ?? "");
const updateDate = await dateStr(data?._updatedAt ?? data?._createdAt ?? "");
const statusTag = getStatusTag(data?.status?.tag);
const isComponentPage = data?._type === "komponent_artikkel";
return (
<div data-block-margin="space-28">
<Heading level="1" size="xlarge" className={styles.pageHeaderHeading}>
{data?.heading}
</Heading>
<Box marginBlock="space-0 space-28">
<DesignsystemetEyebrow type={data?._type} />
<Box marginBlock="space-0 space-8" asChild>
<Heading level="1" size="xlarge" data-aksel-heading-color>
{data?.heading}
</Heading>
</Box>
{isComponentPage && (
<CustomPortableText
value={data?.intro?.body as PortableTextBlock[]}
@@ -74,16 +79,16 @@ async function DesignsystemetPageHeader({ data }: DesignsystemetPageT) {
{statusTag.text}
</Tag>
)}
{publishDate && (
<Detail as="span" textColor="subtle">
{publishDate}
</Detail>
{updateDate && (
<BodyShort size="small" as="span" textColor="subtle">
{`Oppdatert ${updateDate}`}
</BodyShort>
)}
</HStack>
{isComponentPage && <KomponentLinks data={data} />}
<DesignsystemetThumbnail />
</div>
</Box>
);
}
@@ -0,0 +1,61 @@
"use client";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import styles from "./IconPage.module.css";
import { useIconPage } from "./IconPage.provider";
function IconPageButton({
activeIconName,
iconName,
icon,
}: {
activeIconName?: string;
iconName: string;
icon: React.ReactNode;
}) {
const searchParams = useSearchParams();
const pathname = usePathname();
const { replace } = useRouter();
const context = useIconPage();
const isActive = iconName === activeIconName;
const handleClick = () => {
const params = new URLSearchParams(searchParams?.toString());
if (isActive) {
params.delete("iconName");
} else {
params.set("iconName", iconName);
}
replace(`${pathname}?${params.toString()}`, { scroll: false });
};
return (
<>
<button
ref={(el) => {
if (isActive) {
context.setActiveIconButton(el);
}
}}
onClick={handleClick}
key={iconName}
id={iconName}
className={styles.iconButton}
data-state={isActive ? "active" : "inactive"}
aria-pressed={isActive}
aria-controls={isActive ? "icon-page-sidepanel" : undefined}
>
{icon}
</button>
{isActive && context.hideModal && (
<a className={styles.iconPageSkiplink} href="#icon-page-sidepanel">
Hopp til ikonpanel
</a>
)}
</>
);
}
export { IconPageButton };
@@ -0,0 +1,98 @@
"use client";
import * as Icons from "@navikt/aksel-icons";
import meta from "@navikt/aksel-icons/metadata";
import { BodyShort, Heading, VStack } from "@navikt/ds-react";
import { CodeBlock } from "@/app/_ui/code-block/CodeBlock";
import { CodeBlockTabsT } from "@/app/_ui/code-block/CodeBlock.provider";
import { IconPageIconDetails } from "./IconPage.icondetails";
import styles from "./IconPage.module.css";
function IconDetails({
iconName,
iconSvg,
}: {
iconName?: keyof typeof meta;
iconSvg?: string;
}) {
const IconComponent = Icons[`${iconName}Icon`]; // eslint-disable-line import/namespace
const metaData = iconName ? meta[iconName] : null;
if (!IconComponent) {
return null;
}
return (
<IconPageIconDetails
metaData={metaData}
iconComponent={IconComponent}
iconName={iconName}
iconSvg={iconSvg}
/>
);
}
function IntroInformation() {
const installTabs: CodeBlockTabsT = [
{
code: "npm install @navikt/aksel-icons",
lang: "markdown",
text: "npm",
value: "npm",
},
{
code: "yarn add @navikt/aksel-icons",
lang: "markdown",
text: "Yarn",
value: "yarn",
},
];
return (
<div className={styles.iconDetails}>
<Heading level="2" size="small" spacing>
React komponenter
</Heading>
<VStack gap="space-24">
<div>
<BodyShort spacing>
Alle ikonene er tilgjengelige som egne React-komponenter.
</BodyShort>
<CodeBlock
showLineNumbers={false}
tabs={installTabs}
data-block-margin="space-0"
/>
</div>
<div>
<BodyShort spacing>Bruk ikonene i React-prosjektet ditt:</BodyShort>
<CodeBlock
data-block-margin="space-0"
tabs={[
{
code: `import { ChevronDownIcon, TrashIcon, FilterIcon } from "@navikt/aksel-icons";
function MyComponent () {
return (
<div>
<FilterIcon />
<TrashIcon aria-hidden />
<ChevronDownIcon
title="Åpne panel"
/>
</div>
)
}`,
lang: "tsx",
text: "React",
value: "react",
},
]}
/>
</div>
</VStack>
</div>
);
}
export { IconDetails, IntroInformation };
@@ -0,0 +1,74 @@
"use client";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useMemo } from "react";
import { HGrid, Search, ToggleGroup, debounce } from "@navikt/ds-react";
function IconPageForm({
iconToggle,
iconQuery,
}: {
iconQuery?: string;
iconToggle: "stroke" | "fill";
}) {
const searchParams = useSearchParams();
const pathname = usePathname();
const { replace } = useRouter();
const debouncedSearch = useMemo(
() =>
debounce((query: string) => {
const params = new URLSearchParams(searchParams?.toString());
if (query && query.length > 2) {
params.set("iconQuery", query);
} else {
params.delete("iconQuery");
}
replace(`${pathname}?${params.toString()}`);
}),
[pathname, replace, searchParams],
);
const handleToggle = (query: string) => {
const params = new URLSearchParams(searchParams?.toString());
if (query) {
params.set("iconToggle", query);
} else {
params.delete("iconToggle");
}
replace(`${pathname}?${params.toString()}`);
};
return (
<HGrid
onSubmit={(e) => e.preventDefault()}
as="form"
width="fit-content"
align="center"
gap="space-24"
columns={{ xs: 1, sm: "1fr auto" }}
marginBlock="space-40 0"
>
<Search
variant="simple"
label="Ikonsøk"
placeholder="Søk"
autoComplete="off"
onChange={debouncedSearch}
clearButton={false}
defaultValue={iconQuery}
/>
<ToggleGroup
defaultValue={iconToggle}
onChange={handleToggle}
variant="neutral"
aria-label="Velg ikonvariant"
>
<ToggleGroup.Item value="stroke" label="Stroke" />
<ToggleGroup.Item value="fill" label="Fill" />
</ToggleGroup>
</HGrid>
);
}
export { IconPageForm };
@@ -0,0 +1,153 @@
"use client";
import { useEffect, useState } from "react";
import * as Icons from "@navikt/aksel-icons";
import { AirplaneIcon } from "@navikt/aksel-icons";
import type { AkselIcon } from "@navikt/aksel-icons/metadata";
import {
BodyShort,
Box,
Button,
HStack,
Heading,
Modal,
Tag,
VStack,
} from "@navikt/ds-react";
import { CodeBlock } from "@/app/_ui/code-block/CodeBlock";
import { CodeBlockTabsT } from "@/app/_ui/code-block/CodeBlock.provider";
import styles from "./IconPage.module.css";
import { useIconPage } from "./IconPage.provider";
function IconPageIconDetails({
iconName,
metaData,
iconComponent: IconComponent,
iconSvg,
}: {
iconName?: string;
metaData: AkselIcon | null;
iconComponent?: typeof AirplaneIcon;
iconSvg?: string;
}) {
const { hideModal } = useIconPage();
const [downloadLink, setDownloadLink] = useState<string>();
useEffect(() => {
if (!iconSvg) {
return;
}
const url = URL.createObjectURL(
new Blob([iconSvg], {
type: "image/svg+xml",
}),
);
setDownloadLink(url);
return () => {
URL.revokeObjectURL(url);
};
}, [iconSvg]);
if (!IconComponent) {
return null;
}
const codeTabs: CodeBlockTabsT = [
{
code: `<${iconName}Icon title="a11y-title" fontSize="1.5rem" />`,
lang: "tsx",
text: "React",
value: "react",
},
];
if (iconSvg) {
codeTabs.push({
code: iconSvg,
lang: "tsx",
text: "SVG",
value: "svg",
});
}
const HeaderComponent = !hideModal ? Modal.Header : "div";
const dedupedKeywords = new Set(metaData?.keywords ?? []);
return (
<div className={styles.iconDetails} data-inline>
{hideModal && (
<div className={styles.iconDetailsShowcase}>
<IconComponent fontSize="2rem" />
</div>
)}
<VStack gap="space-24" className={styles.iconDetailsContent}>
<HeaderComponent>
{!hideModal && (
<Box marginBlock="space-0 space-8">
<IconComponent aria-hidden fontSize="2rem" />
</Box>
)}
<Heading level="2" size="small">
{iconName}
</Heading>
<HStack gap="space-2">
<Icons.ArrowDownRightIcon fontSize="1.5rem" aria-hidden />
<BodyShort>{metaData?.sub_category}</BodyShort>
</HStack>
<HStack gap="space-8" marginBlock="space-12 0" as="ul">
{[...dedupedKeywords.keys()].map((keyword) => (
<li key={keyword}>
<Tag size="small" variant="neutral-moderate">
{keyword}
</Tag>
</li>
))}
</HStack>
</HeaderComponent>
<div className={styles.iconDetailsActions}>
<HStack gap="space-8">
{iconSvg && (
<Button as="a" href={downloadLink} download={`${iconName}.svg`}>
Last ned SVG
</Button>
)}
<Button
variant="secondary"
as="a"
href={`https://github.com/navikt/aksel/issues/new?labels=forespørsel+🥰&template=update-icon.yml&title=%5BInnspill+til+ikon%5D%3A+${iconName}`}
target="_blank"
rel="noreferrer noopener"
icon={<Icons.BugIcon aria-hidden />}
>
Meld fra bug
</Button>
</HStack>
<CodeBlock
showLineNumbers={false}
tabs={[
{
code: `import { ${iconName}Icon } from '@navikt/aksel-icons';`,
lang: "tsx",
text: "import",
value: "import",
},
]}
/>
<CodeBlock
showLineNumbers={false}
tabs={codeTabs}
defaultWrap={false}
/>
</div>
</VStack>
</div>
);
}
export { IconPageIconDetails };
@@ -0,0 +1,104 @@
.iconButton {
padding: var(--ax-space-8);
display: grid;
place-content: center;
background-color: var(--ax-bg-neutral-soft);
border: 1px solid var(--ax-border-neutral-subtleA);
color: var(--ax-text-neutral);
border-radius: var(--ax-border-radius-large);
&:hover {
background-color: var(--ax-bg-neutral-moderate-hover);
}
&[data-state="active"] {
background: var(--ax-bg-accent-strong-pressed);
color: var(--ax-text-accent-contrast);
border-color: transparent;
&:hover {
background: var(--ax-bg-accent-strong-hover);
}
}
&:focus-visible {
outline: 3px solid var(--ax-border-focus);
outline-offset: 3px;
}
}
.iconDetails {
border-radius: var(--ax-border-radius-xlarge);
border: 1px solid var(--ax-border-neutral-subtleA);
background: var(--ax-bg-raised);
overflow: clip;
padding: var(--ax-space-20);
&[data-inline] {
padding: 0;
}
[data-modal="true"] & {
border-radius: 0;
border: none;
padding-block-end: var(--ax-space-20);
}
}
.iconDetailsShowcase {
border-bottom: 1px solid var(--ax-border-neutral-subtleA);
background: var(--ax-bg-neutral-softA);
padding-block: var(--ax-space-8);
display: grid;
place-content: center;
}
.iconDetailsContent {
padding: var(--ax-space-24) var(--ax-space-20);
[data-modal="true"] & {
padding: 0;
}
}
.iconDetailsActions {
[data-modal="true"] & {
padding-inline: var(--ax-space-20);
}
}
.iconFeedback {
margin-top: var(--ax-space-16);
[data-modal="true"] & {
padding-inline: var(--ax-space-20);
padding-block: var(--ax-space-20);
}
}
.iconPageSidebar {
/* stylelint-disable-next-line csstools/value-no-unknown-custom-properties */
top: calc(var(--website-header-height-width-border) + var(--ax-space-24));
position: sticky;
height: min-content;
}
.iconPageSkiplink {
position: fixed;
right: 0;
bottom: 0;
background-color: var(--ax-bg-info-strong);
padding: var(--ax-space-16);
color: var(--ax-text-info-contrast);
text-decoration: none;
transform: translateY(200%);
&:focus-within {
transform: translateY(0);
outline: none;
}
}
.iconPageLinkLi {
list-style: none;
}
@@ -0,0 +1,42 @@
"use client";
import { createContext, useContext, useState } from "react";
import { useMedia } from "@/hooks/useMedia";
type IconPageContextT = {
activeIconButton: HTMLButtonElement | null;
setActiveIconButton: (button: HTMLButtonElement | null) => void;
hideModal: boolean;
};
const IconPageContext = createContext<IconPageContextT | null>(null);
function IconPageProvider({ children }: { children?: React.ReactNode }) {
const [activeIconButton, setActiveIconButton] =
useState<HTMLButtonElement | null>(null);
const hideModal = useMedia("screen and (min-width: 1280px)");
return (
<IconPageContext.Provider
value={{
activeIconButton,
setActiveIconButton,
hideModal,
}}
>
{children}
</IconPageContext.Provider>
);
}
function useIconPage() {
const context = useContext(IconPageContext);
if (context === null) {
throw new Error("useIconPage must be used within an IconPageProvider");
}
return context;
}
export { IconPageProvider, useIconPage };
@@ -0,0 +1,55 @@
"use client";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import meta from "@navikt/aksel-icons/metadata";
import { Modal } from "@navikt/ds-react";
import styles from "./IconPage.module.css";
import { useIconPage } from "./IconPage.provider";
function IconPageSidebar({
children,
iconName,
}: {
children: React.ReactNode;
iconName?: keyof typeof meta;
}) {
const { activeIconButton, hideModal } = useIconPage();
const searchParams = useSearchParams();
const pathname = usePathname();
const { replace } = useRouter();
const handleClose = () => {
const params = new URLSearchParams(searchParams?.toString());
params.delete("iconName");
replace(`${pathname}?${params.toString()}`);
activeIconButton?.focus();
};
return (
<>
{(hideModal || !iconName) && (
<section
aria-label={iconName ? `Ikon ${iconName}` : "Kom i gang med ikoner"}
id="icon-page-sidepanel"
className={styles.iconPageSidebar}
>
{children}
</section>
)}
{!hideModal && iconName && (
<Modal
open={!!iconName}
aria-label={`${iconName} ikon`}
onClose={handleClose}
data-modal={true}
>
{children}
</Modal>
)}
</>
);
}
export { IconPageSidebar };
@@ -0,0 +1,170 @@
"use client";
import NextLink from "next/link";
import * as Icons from "@navikt/aksel-icons";
import { BrailleIcon, DownloadIcon } from "@navikt/aksel-icons";
import meta from "@navikt/aksel-icons/metadata";
import {
BodyLong,
Box,
Button,
HGrid,
HStack,
Heading,
Link,
VStack,
} from "@navikt/ds-react";
import { EmptyStateCard } from "@/app/_ui/empty-state/EmptyState";
import { DesignsystemetEyebrow } from "@/app/dev/(designsystemet)/_ui/Designsystemet.eyebrow";
import { FigmaIcon } from "@/assets/Icons";
import { DesignsystemetPageLayout } from "../DesignsystemetPage";
import { IconPageButton } from "./IconPage.button";
import { IconDetails, IntroInformation } from "./IconPage.details";
import { IconPageForm } from "./IconPage.form";
import styles from "./IconPage.module.css";
import { IconPageProvider } from "./IconPage.provider";
import { IconPageSidebar } from "./IconPage.sidebar";
import { categorizeIcons, searchIcons } from "./IconPage.utils";
function IconPage({
iconName,
iconQuery,
iconToggle,
iconSvg,
}: {
iconName?: keyof typeof meta;
iconQuery?: string;
iconToggle: "stroke" | "fill";
iconSvg?: string;
}) {
const iconsWithCategories = categorizeIcons(
searchIcons({ query: iconQuery ?? "", toggle: iconToggle ?? "stroke" }),
);
return (
<DesignsystemetPageLayout>
<div>
<DesignsystemetEyebrow type="komponent_artikkel" />
<Heading level="1" size="xlarge" data-aksel-heading-color>
Ikoner
</Heading>
<Box marginBlock="space-8 space-0" asChild>
<BodyLong size="large">
{Object.keys(meta).length} open source-ikoner designet og utviklet
for Nav
</BodyLong>
</Box>
<HStack as="ul" gap="space-16" marginBlock="space-24 0">
<li className={styles.iconPageLinkLi}>
<NextLink
href="https://www.figma.com/community/file/1214869602572392330"
passHref
legacyBehavior
>
<Link
variant="subtle"
data-umami-event="navigere"
data-umami-event-kilde="ikonside"
>
<FigmaIcon /> <span>Figma</span>
</Link>
</NextLink>
</li>
<li className={styles.iconPageLinkLi}>
<NextLink
href="/god-praksis/artikler/tilgjengelig-ikonbruk"
passHref
legacyBehavior
>
<Link
variant="subtle"
data-umami-event="navigere"
data-umami-event-kilde="ikonside"
>
<BrailleIcon aria-hidden fontSize="1.5rem" />{" "}
<span>Tilgjengelighet</span>
</Link>
</NextLink>
</li>
<li className={styles.iconPageLinkLi}>
<Link
variant="subtle"
href="https://cdn.nav.no/aksel/icons/zip/aksel-icons.zip"
download="Ikonpakke"
data-umami-event="last ned"
data-umami-event-tema="ikon"
data-umami-event-type="zip"
data-umami-event-tittel="ikonpakke"
>
<DownloadIcon fontSize="1.5rem" aria-hidden /> Last ned ikonpakke
</Link>
</li>
</HStack>
</div>
<IconPageProvider>
<div>
<IconPageForm iconQuery={iconQuery} iconToggle={iconToggle} />
<HGrid
columns={{ xs: 1, xl: "3fr minmax(300px, 2fr)" }}
gap="space-40"
marginBlock="space-24 0"
>
<VStack as="section" aria-label="Ikonliste" gap="space-40">
{iconsWithCategories.length === 0 && (
<EmptyStateCard
variant="questionmark"
actionComponent={
<Button
as="a"
href="https://github.com/navikt/aksel/issues/new?labels=nytt+✨%2Cikoner+🖼%2Cforespørsel+🥰&template&template=new-icon.yaml&title=%5BNytt+ikon%5D%3A+"
variant="secondary-neutral"
>
Send innspill
</Button>
}
/>
)}
{iconsWithCategories.map((section) => {
return (
<div key={section.category}>
<Heading level="2" size="large" data-aksel-heading-color>
{section.category}
</Heading>
<HStack gap="space-8" marginBlock="space-16 space-0">
{section.icons.map((icon) => {
const T = Icons[`${icon.id}Icon`]; // eslint-disable-line import/namespace
if (T === undefined) {
return null;
}
return (
<IconPageButton
key={icon.id}
iconName={icon.id}
activeIconName={iconName}
icon={<T fontSize="1.5rem" title={icon.name} />}
/>
);
})}
</HStack>
</div>
);
})}
</VStack>
<IconPageSidebar iconName={iconName}>
{iconName ? (
<IconDetails iconName={iconName} iconSvg={iconSvg} />
) : (
<IntroInformation />
)}
</IconPageSidebar>
</HGrid>
</div>
</IconPageProvider>
</DesignsystemetPageLayout>
);
}
export { IconPage };
@@ -0,0 +1,89 @@
import Fuse from "fuse.js";
import meta, { AkselIcon } from "@navikt/aksel-icons/metadata";
const strokeIcons = Object.values(meta).filter(
(x) => x.variant.toLowerCase() === "stroke",
);
/* Some icons are mistakenly labeled with "filled", so we need to handle both "fill" and "filled" */
const fillIcons = Object.values(meta).filter(
(iconMetadata, _, iconArray) =>
iconMetadata.variant.toLowerCase().startsWith("fill") ||
noFill(iconMetadata, iconArray),
);
/**
* For icons with no fill variant, we want to show the stroke variant
* instead of the fill variant.
*/
function noFill(icon: AkselIcon, icons: AkselIcon[]) {
const foundFill = icons.find((x) => {
if (x.name.endsWith("Fill") || x.name.endsWith("Filled")) {
return (
x.name.replace("Fill", "") === icon.name ||
x.name.replace("Filled", "") === icon.name
);
}
return false;
});
return !foundFill;
}
/* ------------------------------ Categorizing ------------------------------ */
function categorizeIcons(icons: AkselIcon[]): {
category: string;
icons: AkselIcon[];
}[] {
const categoryMap = new Map<string, AkselIcon[]>();
for (const icon of icons) {
const category = categoryMap.get(icon.category);
if (!category) {
categoryMap.set(icon.category, [icon]);
} else {
category.push(icon);
}
}
return Array.from(categoryMap.entries())
.map(([category, _icons]) => ({ category, icons: _icons }))
.sort((a, b) => a.category.localeCompare(b.category));
}
/* --------------------------- Fuse search config --------------------------- */
function fuseSearch(icons: AkselIcon[]) {
return new Fuse(icons, {
threshold: 0.2,
keys: [
{ name: "name", weight: 3 },
{ name: "category", weight: 2 },
{ name: "sub_category", weight: 2 },
{ name: "keywords", weight: 3 },
{ name: "variant", weight: 1 },
],
shouldSort: false,
});
}
const strokeIconSearch = fuseSearch(strokeIcons);
const fillIconSearch = fuseSearch(fillIcons);
function searchIcons({
toggle,
query,
}: {
query: string;
toggle: "stroke" | "fill";
}) {
const icons = toggle === "stroke" ? strokeIcons : fillIcons;
const fuse = toggle === "stroke" ? strokeIconSearch : fillIconSearch;
if (!query || query.length < 2) {
return icons;
}
return fuse.search(query).map((result) => result.item);
}
export { strokeIcons, fillIcons, searchIcons, categorizeIcons };
Loaded 100 of 165 files, more files were not shown because too many files have changed in this diff. Show more