mirror of
https://github.com/navikt/aksel.git
synced 2026-10-13 01:02:02 +00:00
Compare commits
33
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ac8d81f63 | ||
|
|
0404c4f156 | ||
|
|
3a6da2564d | ||
|
|
91955f5049 | ||
|
|
34ce9cb72b | ||
|
|
6b8d81e380 | ||
|
|
e8f59ee29f | ||
|
|
5f3e0d619d | ||
|
|
ab41aef076 | ||
|
|
2decc636c8 | ||
|
|
171410e7d4 | ||
|
|
9141a6f863 | ||
|
|
cc8742c179 | ||
|
|
e1b73178e1 | ||
|
|
8eca3ceeac | ||
|
|
3313720bc3 | ||
|
|
64705d06bc | ||
|
|
88fb8977ea | ||
|
|
8d805eae2f | ||
|
|
9dc60ba560 | ||
|
|
088d5e6b59 | ||
|
|
b0f1151fd6 | ||
|
|
c9ede88246 | ||
|
|
7cfae6cd1d | ||
|
|
0a98a75514 | ||
|
|
dbff5f79f8 | ||
|
|
84f1cfec49 | ||
|
|
39817f924c | ||
|
|
8ea18b07a3 | ||
|
|
820a68f0a8 | ||
|
|
f787e91a2f | ||
|
|
e7ec9c191a | ||
|
|
c57fe7c396 |
No files matched your search
@@ -79,6 +79,7 @@ packages/**/lib
|
||||
*.d.ts
|
||||
|
||||
.vscode
|
||||
.nvim.lua
|
||||
# File created on build in nav-frontend-core
|
||||
*-exports.less
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -9,5 +9,5 @@ keywords:
|
||||
- sortering
|
||||
- sorting
|
||||
variant: Stroke
|
||||
updated_at: 17.07.2024
|
||||
updated_at: 27.03.2025
|
||||
created_at: 07.03.2023
|
||||
@@ -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 |
@@ -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
|
||||
@@ -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 |
@@ -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
|
||||
@@ -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
|
||||
@@ -5,5 +5,5 @@ keywords:
|
||||
- gange
|
||||
- multiplisere
|
||||
variant: Stroke
|
||||
updated_at: 12.04.2024
|
||||
updated_at: 11.04.2025
|
||||
created_at: 07.03.2023
|
||||
@@ -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
|
||||
@@ -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,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": {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# @navikt/aksel-stylelint
|
||||
|
||||
## 7.21.0
|
||||
|
||||
## 7.20.0
|
||||
|
||||
## 7.19.1
|
||||
|
||||
## 7.19.0
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# @navikt/ds-tailwind
|
||||
|
||||
## 7.21.0
|
||||
|
||||
## 7.20.0
|
||||
|
||||
## 7.19.1
|
||||
|
||||
## 7.19.0
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# @navikt/ds-tokens
|
||||
|
||||
## 7.21.0
|
||||
|
||||
## 7.20.0
|
||||
|
||||
## 7.19.1
|
||||
|
||||
## 7.19.0
|
||||
|
||||
@@ -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
File diff suppressed because it is too large.
Load diff
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
@@ -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,
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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
Reference in new issue
Block a user