Compare commits

...
Author SHA1 Message Date
github-actions[bot]andgithub-actions[bot] da0fbc3299 Version Packages (#2057)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-19 13:02:23 +02:00
Ken 9f59e67e2a ⚡ Lazy-loading date-fns: sparer ~10kB first-load (#2058) 2023-06-19 12:53:28 +02:00
Ken ed7aafcac8 🎨 Oppdatert deploy image (#2059) 2023-06-19 12:53:08 +02:00
Ken dfbbe92a63 🎨 Helptext har nå en prop for wrapperClassName (#2056) 2023-06-19 11:48:53 +02:00
Ken 42b5af64ab 🐛 Timeline Period brakk ved bruk av JSODOM 2023-06-17 10:25:45 +02:00
Ken 329119fa04 💄 DoDont-ikon er nå høyrestilt 2023-06-16 16:21:19 +02:00
Ken 0fc80f45cb ♿ Fikset regression der sanity ikke lengre støttet skjermlesere 2023-06-16 16:07:37 +02:00
Ken 337b9fda13 🐛 Fikset inlinecode bruk i heading 2023-06-16 12:58:26 +02:00
github-actions[bot]andgithub-actions[bot] 659e835da9 Version Packages (#2055)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-15 16:38:08 +02:00
Ken e10f4375b1 Merge branch 'main' of https://github.com/navikt/aksel 2023-06-15 16:34:25 +02:00
Ken 9a8bb26c36 🐛 Timeline brakk ved bruk av JSDOM-tester 2023-06-15 16:33:40 +02:00
github-actions[bot]andgithub-actions[bot] 69aea38784 Version Packages (#2053)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-15 15:55:48 +02:00
Ken 8f16d1571e 🎨 Oppdatert ikoner (#2054) 2023-06-15 15:51:47 +02:00
Marianne Røsvik e2265c9a78 Added two new icons (#2047) 2023-06-15 15:06:48 +02:00
Ken 925aa7a140 🐛 OverridableComponent omitter nå 'as' (#2051) 2023-06-15 14:46:34 +02:00
Ken 7c17e40cac 🐛 Datepicker og Monthpicker lukker nå popover ved escape (#2052) 2023-06-15 14:46:07 +02:00
github-actions[bot]andgithub-actions[bot] 3c8889ea88 Version Packages (#2050)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-13 16:49:54 +02:00
Ken e060961050 🐛 Fikset bruk av text-subtle på skjemaelementer (#2049) 2023-06-13 16:45:35 +02:00
Ken 18aa4ee7fb Merge branch 'main' of https://github.com/navikt/aksel 2023-06-09 14:36:38 +02:00
Ken f44edd76a2 Merge branch 'next' 2023-06-09 14:31:11 +02:00
github-actions[bot]andgithub-actions[bot] 1fa636bdd5 Version Packages (#2044)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-09 14:16:43 +02:00
Ken 44a75acb6b 🐛 className ble ikke riktig forwardet til period 2023-06-09 14:14:15 +02:00
Ken 550faee900 💄 Checkboard-pattern for transparent visning av tokens 2023-06-09 13:53:29 +02:00
Karl O 88b6174b0b 📝 La til next-logger så backend logging får rett log-level (#2042) 2023-06-09 13:34:31 +02:00
Ken fbf016b3bd ✨ Forbedringer for Aksel.nav.no UI (#2043) 2023-06-09 13:05:59 +02:00
09fa503b98 initial template for PR (#2019)
* initial template for PR

* update template

* update template

* 📝 Oppdatert template

---------

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
Co-authored-by: Ken <ken.aleksander@gmail.com>
2023-06-09 09:55:28 +02:00
Ken f5688485f5 ⬆️ Oppdatert Sanity 2023-06-08 18:43:14 +02:00
github-actions[bot]andgithub-actions[bot] 6385c4686e Version Packages (#2039)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-08 16:07:32 +02:00
Ken 30068dca3f 📝 La til jsdoc for skeleton-komponent 2023-06-08 16:03:27 +02:00
github-actions[bot]andgithub-actions[bot] 3aa04d2375 Version Packages (#2038)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-08 15:25:09 +02:00
Ken 91343563c9 🐛 Fikset markering av tekst internt i Skeleton-komponent 2023-06-08 15:21:52 +02:00
Ken d5c8112339 📝 Fikset typo i skeleton-docs 2023-06-08 15:02:24 +02:00
115 changed files with 3676 additions and 2859 deletions

No files matched your search

+17
View File
@@ -0,0 +1,17 @@
### :memo: PR Checklist
<!-- Remove non-relevant fields -->
- [ ] :sparkles: New component? Add CSS-mappings to `@navikt/core/css/config/_mappings.js`.
- [ ] :wastebasket: Are you deprecating a CSS class? (are you removing the last known reference to a CSS class?). Add it to `@navikt/aksel-stylelint/src/deprecations.ts` with a suitable description.
- [ ] :balance_scale: Is the feature/component/code sufficiently complex to warrant tests? Add some unit-tests :test_tube:
- [ ] :balance_scale: Does the feature/component add some complex user-interactions? Consider adding some [interaction-tests with storybook](https://storybook.js.org/docs/react/writing-tests/interaction-testing) :test_tube:
- [ ] :boom: Are there breaking changes? Consider adding a codemod for easier migration.
- [ ] :test_tube: Are all variations and cases shown in the storybook stories? Stories are used for [visual regression testing](https://www.chromatic.com/docs/), so make sure they are complete.
- [ ] Run `yarn changeset` for version and changelog generation
+21
View File
@@ -1,5 +1,26 @@
# @navikt/aksel-icons
## 4.1.7
## 4.1.6
## 4.1.5
### Patch Changes
- [#2054](https://github.com/navikt/aksel/pull/2054) [`8f16d1571`](https://github.com/navikt/aksel/commit/8f16d1571ec7cf452c09618a9a918ee2a634c75a) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :tada: Nye ikoner
- ChevronRightLast
- ChevronLeftLast
## 4.1.4
## 4.1.3
## 4.1.2
## 4.1.1
## 4.1.0
## 4.0.0
+1 -1
View File
@@ -15,5 +15,5 @@ keywords:
- Sammenføye
- Kildekontroll
variant: Stroke
updated_at: 08.03.2023
updated_at: 24.05.2023
created_at: 07.03.2023
@@ -5,5 +5,5 @@ keywords:
- left
- venstre
variant: Stroke
updated_at: 08.03.2023
updated_at: 14.06.2023
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="M7.5 6.25C7.5 5.83579 7.16421 5.5 6.75 5.5C6.33579 5.5 6 5.83579 6 6.25V17.75C6 18.1642 6.33579 18.5 6.75 18.5C7.16421 18.5 7.5 18.1642 7.5 17.75V6.25ZM17.7803 7.03033C18.0732 6.73744 18.0732 6.26256 17.7803 5.96967C17.4874 5.67678 17.0126 5.67678 16.7197 5.96967L11.2197 11.4697C11.079 11.6103 11 11.8011 11 12C11 12.1989 11.079 12.3897 11.2197 12.5303L16.7197 18.0303C17.0126 18.3232 17.4874 18.3232 17.7803 18.0303C18.0732 17.7374 18.0732 17.2626 17.7803 16.9697L12.8107 12L17.7803 7.03033Z" fill="#262626"/>
</svg>

After

Width:  |  Height:  |  Size: 664 B

@@ -0,0 +1,12 @@
name: ChevronLeftFirst
category: Arrows
sub_category: Chevron
keywords:
- left
- venstre
- side
- first
- først
variant: Stroke
updated_at: 15.06.2023
created_at: 15.06.2023
@@ -6,5 +6,5 @@ keywords:
- høyre
- side
variant: Stroke
updated_at: 08.03.2023
updated_at: 09.06.2023
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="M18 6.25C18 5.83579 17.6642 5.5 17.25 5.5C16.8358 5.5 16.5 5.83579 16.5 6.25V17.75C16.5 18.1642 16.8358 18.5 17.25 18.5C17.6642 18.5 18 18.1642 18 17.75V6.25ZM7.28033 6.21967C6.98744 5.92678 6.51256 5.92678 6.21967 6.21967C5.92678 6.51256 5.92678 6.98744 6.21967 7.28033L11.1893 12.25L6.21967 17.2197C5.92678 17.5126 5.92678 17.9874 6.21967 18.2803C6.51256 18.5732 6.98744 18.5732 7.28033 18.2803L12.7803 12.7803C13.0732 12.4874 13.0732 12.0126 12.7803 11.7197L7.28033 6.21967Z" fill="#262626"/>
</svg>

After

Width:  |  Height:  |  Size: 648 B

@@ -0,0 +1,10 @@
name: ChevronRightLast
category: Arrows
sub_category: Chevron
keywords:
- right
- høyre
- side
variant: Stroke
updated_at: 15.06.2023
created_at: 15.06.2023
+1 -1
View File
@@ -15,5 +15,5 @@ keywords:
- markup-språk
- kildekode
variant: Stroke
updated_at: 08.03.2023
updated_at: 24.05.2023
created_at: 07.03.2023
+1 -1
View File
@@ -13,5 +13,5 @@ keywords:
- dra og slippe
- skifte
variant: Stroke
updated_at: 08.03.2023
updated_at: 26.05.2023
created_at: 07.03.2023
+1 -1
View File
@@ -23,5 +23,5 @@ keywords:
- milepæler
- hendelser
variant: Stroke
updated_at: 08.03.2023
updated_at: 31.05.2023
created_at: 07.03.2023
+1 -1
View File
@@ -7,5 +7,5 @@ keywords:
- delete
- slett
variant: Stroke
updated_at: 08.03.2023
updated_at: 24.05.2023
created_at: 07.03.2023
+1 -1
View File
@@ -6,5 +6,5 @@ keywords:
- uploading
- send
variant: Stroke
updated_at: 08.03.2023
updated_at: 08.06.2023
created_at: 07.03.2023
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-icons",
"version": "4.1.0",
"version": "4.1.7",
"author": "Aksel | NAV",
"homepage": "https://aksel.nav.no/ikoner",
"repository": {
+14
View File
@@ -1,5 +1,19 @@
# @navikt/aksel-stylelint
## 4.1.7
## 4.1.6
## 4.1.5
## 4.1.4
## 4.1.3
## 4.1.2
## 4.1.1
## 4.1.0
### Minor Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-stylelint",
"version": "4.1.0",
"version": "4.1.7",
"author": "Aksel | NAV",
"homepage": "https://aksel.nav.no/grunnleggende/kode/stylelint",
"repository": {
@@ -35,8 +35,8 @@
"dev": "yarn watch:lint"
},
"devDependencies": {
"@navikt/ds-css": "^4.1.0",
"@navikt/ds-tokens": "^4.1.0",
"@navikt/ds-css": "^4.1.7",
"@navikt/ds-tokens": "^4.1.7",
"@types/jest": "^29.0.0",
"concurrently": "7.2.1",
"copyfiles": "2.4.1",
+49
View File
@@ -1,5 +1,54 @@
# @navikt/aksel
## 4.1.7
### Patch Changes
- Updated dependencies [[`42b5af64a`](https://github.com/navikt/aksel/commit/42b5af64ab35d0f2d126d41f8fc3e61fe2834b36)]:
- @navikt/ds-css@4.1.7
## 4.1.6
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@4.1.6
## 4.1.5
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@4.1.5
## 4.1.4
### Patch Changes
- Updated dependencies [[`e06096105`](https://github.com/navikt/aksel/commit/e060961050c75ec815e5c125001f1ff99106e298)]:
- @navikt/ds-css@4.1.4
## 4.1.3
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@4.1.3
## 4.1.2
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@4.1.2
## 4.1.1
### Patch Changes
- Updated dependencies [[`91343563c`](https://github.com/navikt/aksel/commit/91343563c916a554d8ade5401c28ab4c3b3e26af)]:
- @navikt/ds-css@4.1.1
## 4.1.0
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel",
"version": "4.1.0",
"version": "4.1.7",
"description": "Aksel command line interface. Handles css-imports, codemods and more",
"author": "Aksel | NAV designsystem team",
"license": "MIT",
@@ -29,7 +29,7 @@
},
"homepage": "https://aksel.nav.no/grunnleggende/kode/kommandolinje",
"dependencies": {
"@navikt/ds-css": "4.1.0",
"@navikt/ds-css": "4.1.7",
"axios": "1.3.6",
"chalk": "4.1.0",
"clipboardy": "^2.3.0",
+26
View File
@@ -1,5 +1,31 @@
# @navikt/ds-css
## 4.1.7
### Patch Changes
- [`42b5af64a`](https://github.com/navikt/aksel/commit/42b5af64ab35d0f2d126d41f8fc3e61fe2834b36) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Fikset jsdom-problemer ved rendring av Timeline
## 4.1.6
## 4.1.5
## 4.1.4
### Patch Changes
- [#2049](https://github.com/navikt/aksel/pull/2049) [`e06096105`](https://github.com/navikt/aksel/commit/e060961050c75ec815e5c125001f1ff99106e298) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :bug: Subtle-description på form-elementer brakk bruk av Readmore
## 4.1.3
## 4.1.2
## 4.1.1
### Patch Changes
- [`91343563c`](https://github.com/navikt/aksel/commit/91343563c916a554d8ade5401c28ab4c3b3e26af) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :bug: Fikset markering av tekst i skeleton.
## 4.1.0
### Minor Changes
+1 -1
View File
@@ -33,7 +33,7 @@
.navds-help-text__popover.navds-popover {
background-color: var(--ac-help-text-popover-bg, var(--a-surface-info-subtle));
max-width: 65ch;
max-width: min(65ch, 90vw);
--ac-popover-border: var(--a-border-info);
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-css",
"version": "4.1.0",
"version": "4.1.7",
"description": "CSS for NAV Designsystem",
"author": "Aksel | NAV designsystem team",
"keywords": [
@@ -27,7 +27,7 @@
"css:get-version": "node config/get-version.js"
},
"devDependencies": {
"@navikt/ds-tokens": "^4.1.0",
"@navikt/ds-tokens": "^4.1.7",
"cssnano": "6.0.0",
"fast-glob": "3.2.11",
"lodash": "4.17.21",
+1
View File
@@ -41,6 +41,7 @@
border-left: 2px solid var(--ac-read-more-line, var(--a-border-divider));
margin-left: 13px;
padding-left: 13px;
color: var(--a-text-default);
}
.navds-read-more--small .navds-read-more__content {
+1
View File
@@ -3,6 +3,7 @@
background-color: var(--ac-skeleton-bg, var(--a-surface-neutral-moderate));
height: 1.3em;
animation: akselSkeletonAnimation 0.8s linear infinite alternate;
pointer-events: none;
}
.navds-skeleton--has-children {
+4
View File
@@ -61,6 +61,10 @@
list-style-type: none;
}
.navds-timeline__row-periods:focus {
outline: none;
}
.navds-timeline__period {
height: 100%;
border-radius: var(--a-border-radius-full);
+63
View File
@@ -1,5 +1,68 @@
# @navikt/ds-react
## 4.1.7
### Patch Changes
- [`42b5af64a`](https://github.com/navikt/aksel/commit/42b5af64ab35d0f2d126d41f8fc3e61fe2834b36) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Fikset jsdom-problemer ved rendring av Timeline
- [#2056](https://github.com/navikt/aksel/pull/2056) [`dfbbe92a6`](https://github.com/navikt/aksel/commit/dfbbe92a63210278d9dd4d154c01f116ad921fc1) Thanks [@KenAJoh](https://github.com/KenAJoh)! - La til `wrapperClassname`-prop for HelpText
- Updated dependencies []:
- @navikt/aksel-icons@4.1.7
## 4.1.6
### Patch Changes
- [`9a8bb26c3`](https://github.com/navikt/aksel/commit/9a8bb26c36cfbb1a95cf28a043d2df3dbfe7185d) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :bug: Timeline-period brakk ved bruk av jsdom-tester
- Updated dependencies []:
- @navikt/aksel-icons@4.1.6
## 4.1.5
### Patch Changes
- [#2051](https://github.com/navikt/aksel/pull/2051) [`925aa7a14`](https://github.com/navikt/aksel/commit/925aa7a140bc0cd63e8884bea0bd478838c1aa17) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :bug: OverridableComponent fungerer nå med komponenter som allerede bruker 'as'-prop
- [#2052](https://github.com/navikt/aksel/pull/2052) [`7c17e40ca`](https://github.com/navikt/aksel/commit/7c17e40cac6703f00ce756276e9616deb73f6098) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :bug: floating-ui sendte ikke escape-events opp gjennom dom-treet
- Updated dependencies [[`8f16d1571`](https://github.com/navikt/aksel/commit/8f16d1571ec7cf452c09618a9a918ee2a634c75a)]:
- @navikt/aksel-icons@4.1.5
## 4.1.4
### Patch Changes
- Updated dependencies []:
- @navikt/aksel-icons@4.1.4
## 4.1.3
### Patch Changes
- [`44a75acb6`](https://github.com/navikt/aksel/commit/44a75acb6b904d614a01344f42111761a48cb7e2) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :bug: Classname ble ikke riktig forwardet til ikke-klikkbar Period i Timeline
- Updated dependencies []:
- @navikt/aksel-icons@4.1.3
## 4.1.2
### Patch Changes
- [`30068dca3`](https://github.com/navikt/aksel/commit/30068dca3fcdf4bf7bbfbfb3d09baa9fee003962) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Jsdoc for Skeleton-komponent
- Updated dependencies []:
- @navikt/aksel-icons@4.1.2
## 4.1.1
### Patch Changes
- Updated dependencies []:
- @navikt/aksel-icons@4.1.1
## 4.1.0
### Minor Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-react",
"version": "4.1.0",
"version": "4.1.7",
"description": "Aksel react-components for NAV designsystem",
"author": "Aksel | NAV designsystem team",
"license": "MIT",
@@ -38,7 +38,7 @@
},
"dependencies": {
"@floating-ui/react": "0.24.1",
"@navikt/aksel-icons": "^4.1.0",
"@navikt/aksel-icons": "^4.1.7",
"@radix-ui/react-tabs": "1.0.0",
"@radix-ui/react-toggle-group": "1.0.0",
"clsx": "^1.2.1",
@@ -114,6 +114,11 @@ export interface DatePickerDefaultProps
* @default "absolute"
*/
strategy?: "absolute" | "fixed";
/**
* Bubbles Escape keydown-event up trough DOM-tree. This is set to false by default to prevent closing components like Modal on Escape
* @default false
*/
bubbleEscape?: boolean;
}
export type DatePickerProps = DatePickerDefaultProps & ConditionalModeProps;
@@ -180,6 +185,7 @@ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(
onClose,
onOpenToggle,
strategy = "absolute",
bubbleEscape = false,
...rest
},
ref
@@ -251,6 +257,7 @@ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(
role="dialog"
ref={ref}
strategy={strategy}
bubbleEscape={bubbleEscape}
>
<DayPicker
locale={getLocaleFromString(locale)}
@@ -319,6 +319,7 @@ export const useDatepicker = (
onOpenToggle: () => handleOpen(!open),
disabled,
disableWeekends,
bubbleEscape: true,
ref: setDaypickerRef,
};
@@ -312,6 +312,7 @@ export const useMonthpicker = (
open,
onOpenToggle: () => handleOpen(!open),
disabled,
bubbleEscape: true,
ref: setMonthpickerRef,
};
@@ -544,6 +544,7 @@ export const useRangeDatepicker = (
onOpenToggle: () => setOpen((x) => !x),
disabled,
disableWeekends,
bubbleEscape: true,
ref: setDaypickerRef,
};
@@ -86,6 +86,11 @@ export interface MonthPickerProps extends React.HTMLAttributes<HTMLDivElement> {
* @default "absolute"
*/
strategy?: "absolute" | "fixed";
/**
* Bubbles Escape keydown-event up trough DOM-tree. This is set to false by default to prevent closing components like Modal on Escape
* @default false
*/
bubbleEscape?: boolean;
}
interface MonthPickerComponent
@@ -153,6 +158,7 @@ export const MonthPicker = forwardRef<HTMLDivElement, MonthPickerProps>(
year,
onYearChange,
strategy = "absolute",
bubbleEscape = false,
},
ref
) => {
@@ -204,6 +210,7 @@ export const MonthPicker = forwardRef<HTMLDivElement, MonthPickerProps>(
id={ariaId}
className="navds-date"
strategy={strategy}
bubbleEscape={bubbleEscape}
>
<RootProvider
locale={getLocaleFromString(locale)}
@@ -29,6 +29,10 @@ export interface HelpTextProps
| "right-end"
| "left-start"
| "left-end";
/**
* Classname for wrapper
*/
wrapperClassName?: string;
}
/**
@@ -53,6 +57,7 @@ export const HelpText = forwardRef<HTMLButtonElement, HelpTextProps>(
strategy = "absolute",
title = "hjelp",
onClick,
wrapperClassName,
...rest
},
ref
@@ -62,7 +67,7 @@ export const HelpText = forwardRef<HTMLButtonElement, HelpTextProps>(
const [open, setOpen] = useState(false);
return (
<div className="navds-help-text">
<div className={cl("navds-help-text", wrapperClassName)}>
<button
{...rest}
ref={mergedRef}
@@ -1,5 +1,5 @@
import React, { useEffect, useRef } from "react";
import { HelpText } from "..";
import { BodyLong, Heading, HelpText } from "..";
export default {
title: "ds-react/HelpText",
@@ -65,3 +65,36 @@ export const Open = () => {
</HelpText>
);
};
export const WrapperClassName = {
render: () => {
return (
<div>
<BodyLong spacing>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Soluta
perspiciatis vero voluptatum, asperiores cumque, deserunt accusantium
ullam ipsa accusamus officia cupiditate quae unde esse culpa, ratione
ab quam ea quas?
</BodyLong>
<Heading level="2" size="medium">
67 år og 1 måneder øklasdjkl askdak døkasøk daøkdkølasøkld asølkdøka
<HelpText wrapperClassName="testClass">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Soluta
perspiciatis vero voluptatum, asperiores cumque, deserunt
accusantium ullam ipsa accusamus officia cupiditate quae unde esse
culpa, ratione ab quam ea quas?
</HelpText>
<style>{`.testClass {display: inline-block; margin-left: 0.5rem; vertical-align: middle;}`}</style>
</Heading>
<BodyLong spacing>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Soluta
perspiciatis vero voluptatum, asperiores cumque, deserunt accusantium
ullam ipsa accusamus officia cupiditate quae unde esse culpa, ratione
ab quam ea quas?
</BodyLong>
</div>
);
},
};
+11 -1
View File
@@ -72,6 +72,11 @@ export interface PopoverProps extends HTMLAttributes<HTMLDivElement> {
* @default "absolute"
*/
strategy?: "absolute" | "fixed";
/**
* Bubbles Escape keydown-event up trough DOM-tree. This is set to false by default to prevent closing components like Modal on Escape
* @default false
*/
bubbleEscape?: boolean;
}
interface PopoverComponent
@@ -113,6 +118,7 @@ export const Popover = forwardRef<HTMLDivElement, PopoverProps>(
placement = "top",
offset,
strategy: userStrategy = "absolute",
bubbleEscape = false,
...rest
},
ref
@@ -143,7 +149,11 @@ export const Popover = forwardRef<HTMLDivElement, PopoverProps>(
const { getFloatingProps } = useInteractions([
useClick(context),
useDismiss(context),
useDismiss(context, {
bubbles: {
escapeKey: bubbleEscape,
},
}),
]);
useClientLayoutEffect(() => {
+10 -1
View File
@@ -12,11 +12,20 @@ export interface SkeletonProps extends React.HTMLAttributes<HTMLDivElement> {
*/
height?: number | string;
/**
* When not infering width from children, you must specify width
* When not inferring width from children, you must specify width
*/
width?: number | string;
}
/**
* Simple skeleton loader
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/skeleton)
* @see 🏷️ {@link SkeletonProps}
* @example
* ```jsx
* <Skeleton variant="circle" height="5rem" width="5rem" />
* ```
*/
export const Skeleton = forwardRef<HTMLDivElement, SkeletonProps>(
(
{ className, children, height, width, style, variant = "text", ...rest },
+6 -24
View File
@@ -11,15 +11,9 @@ import {
useHover,
useInteractions,
} from "@floating-ui/react";
import { useEventListener, mergeRefs } from "../util";
import { format } from "date-fns";
import React, {
forwardRef,
useCallback,
useMemo,
useRef,
useState,
} from "react";
import React, { forwardRef, useMemo, useRef, useState } from "react";
import { mergeRefs } from "../util";
import { useTimelineContext } from "./hooks/useTimelineContext";
import { position } from "./utils/calc";
import { TimelineComponentTypes } from "./utils/types.internal";
@@ -83,22 +77,6 @@ export const Pin = forwardRef<HTMLButtonElement, TimelinePinProps>(
[ref, refs.setReference]
);
useEventListener(
"focusin",
useCallback(
(e: FocusEvent) => {
if (
![refs.domReference.current, refs?.floating?.current].some(
(element) => element?.contains(e.target as Node)
)
) {
open && setOpen(false);
}
},
[open, refs.domReference, refs?.floating]
)
);
const staticSide = {
top: "bottom",
right: "left",
@@ -139,6 +117,10 @@ export const Pin = forwardRef<HTMLButtonElement, TimelinePinProps>(
ref={refs.setFloating}
{...getFloatingProps({
tabIndex: -1,
onBlur: (e) =>
e.target.previousSibling !== document.activeElement &&
open &&
setOpen(false),
})}
style={{
...floatingStyles,
@@ -72,6 +72,7 @@ export const TimelineRow = forwardRef<HTMLOListElement, TimelineRowProps>(
>
<ol
{...rest}
tabIndex={-1}
ref={ref}
aria-label={
periods.length === 0
@@ -11,9 +11,9 @@ import {
useHover,
useInteractions,
} from "@floating-ui/react";
import { useEventListener, mergeRefs } from "../../util";
import cl from "clsx";
import React, { useCallback, useMemo, useRef, useState } from "react";
import React, { useMemo, useRef, useState } from "react";
import { mergeRefs } from "../../util";
import { usePeriodContext } from "../hooks/usePeriodContext";
import { useRowContext } from "../hooks/useRowContext";
import { useTimelineContext } from "../hooks/useTimelineContext";
@@ -85,22 +85,6 @@ const ClickablePeriod = React.memo(
[periodRef, refs.setReference]
);
useEventListener(
"focusin",
useCallback(
(e: FocusEvent) => {
if (
![refs.domReference.current, refs?.floating?.current].some(
(element) => element?.contains(e.target as Node)
)
) {
open && setOpen(false);
}
},
[open, refs.domReference, refs?.floating]
)
);
const staticSide = {
top: "bottom",
right: "left",
@@ -165,6 +149,10 @@ const ClickablePeriod = React.memo(
ref={refs.setFloating}
{...getFloatingProps({
tabIndex: -1,
onBlur: (e) =>
e.target.previousSibling !== document.activeElement &&
open &&
setOpen(false),
})}
style={{
...floatingStyles,
@@ -26,7 +26,7 @@ const NonClickablePeriod = ({
{...restProps}
className={cl(
getConditionalClasses(cropped, direction, status),
restProps?.classname
restProps?.className
)}
style={{
width: `${width}%`,
@@ -7,6 +7,6 @@ export interface OverridableComponent<Component, Element extends HTMLElement> {
props: {
as: As;
} & Component &
Omit<React.ComponentPropsWithRef<As>, keyof Component>
Omit<React.ComponentPropsWithRef<As>, keyof Component | "as">
): ReturnType<React.FC>;
}
+14
View File
@@ -1,5 +1,19 @@
# @navikt/ds-tailwind
## 4.1.7
## 4.1.6
## 4.1.5
## 4.1.4
## 4.1.3
## 4.1.2
## 4.1.1
## 4.1.0
## 4.0.0
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-tailwind",
"version": "4.1.0",
"version": "4.1.7",
"description": "Tailwind config based on @navikt/ds-tokens",
"author": "Aksel | NAV designsystem team",
"keywords": [
@@ -22,7 +22,7 @@
"directory": "@navikt/core/tailwind"
},
"devDependencies": {
"@navikt/ds-tokens": "^4.1.0",
"@navikt/ds-tokens": "^4.1.7",
"@types/jest": "^29.0.0",
"color": "4.2.3",
"jest": "^29.0.0",
+14
View File
@@ -1,5 +1,19 @@
# @navikt/ds-tokens
## 4.1.7
## 4.1.6
## 4.1.5
## 4.1.4
## 4.1.3
## 4.1.2
## 4.1.1
## 4.1.0
### Minor Changes
+66 -37
View File
@@ -47,27 +47,27 @@
"name": "--a-font-family",
"value": "\"Source Sans Pro\", Arial, sans-serif"
},
{ "name": "--a-font-weight-bold", "value": "600" },
{ "name": "--a-font-weight-regular", "value": "400" },
{ "name": "--a-font-line-height-heading-2xlarge", "value": "3.25rem" },
{ "name": "--a-font-line-height-heading-xlarge", "value": "2.5rem" },
{ "name": "--a-font-line-height-heading-large", "value": "2.25rem" },
{ "name": "--a-font-line-height-heading-medium", "value": "2rem" },
{ "name": "--a-font-line-height-heading-small", "value": "1.75rem" },
{ "name": "--a-font-line-height-heading-xsmall", "value": "1.5rem" },
{ "name": "--a-font-line-height-xlarge", "value": "1.75rem" },
{ "name": "--a-font-line-height-large", "value": "1.5rem" },
{ "name": "--a-font-line-height-medium", "value": "1.25rem" },
{ "name": "--a-font-size-heading-2xlarge", "value": "2.5rem" },
{ "name": "--a-font-size-heading-xlarge", "value": "2rem" },
{ "name": "--a-font-size-heading-large", "value": "1.75rem" },
{ "name": "--a-font-size-heading-medium", "value": "1.5rem" },
{ "name": "--a-font-size-heading-small", "value": "1.25rem" },
{ "name": "--a-font-size-heading-xsmall", "value": "1.125rem" },
{ "name": "--a-font-size-xlarge", "value": "1.25rem" },
{ "name": "--a-font-size-large", "value": "1.125rem" },
{ "name": "--a-font-weight-bold", "value": "600" },
{ "name": "--a-font-size-small", "value": "0.875rem" },
{ "name": "--a-font-size-medium", "value": "1rem" },
{ "name": "--a-font-size-small", "value": "0.875rem" }
{ "name": "--a-font-size-large", "value": "1.125rem" },
{ "name": "--a-font-size-xlarge", "value": "1.25rem" },
{ "name": "--a-font-size-heading-xsmall", "value": "1.125rem" },
{ "name": "--a-font-size-heading-small", "value": "1.25rem" },
{ "name": "--a-font-size-heading-medium", "value": "1.5rem" },
{ "name": "--a-font-size-heading-large", "value": "1.75rem" },
{ "name": "--a-font-size-heading-xlarge", "value": "2rem" },
{ "name": "--a-font-size-heading-2xlarge", "value": "2.5rem" },
{ "name": "--a-font-line-height-medium", "value": "1.25rem" },
{ "name": "--a-font-line-height-large", "value": "1.5rem" },
{ "name": "--a-font-line-height-xlarge", "value": "1.75rem" },
{ "name": "--a-font-line-height-heading-xsmall", "value": "1.5rem" },
{ "name": "--a-font-line-height-heading-small", "value": "1.75rem" },
{ "name": "--a-font-line-height-heading-medium", "value": "2rem" },
{ "name": "--a-font-line-height-heading-large", "value": "2.25rem" },
{ "name": "--a-font-line-height-heading-xlarge", "value": "2.5rem" },
{ "name": "--a-font-line-height-heading-2xlarge", "value": "3.25rem" }
],
"global-base": [
{ "name": "--a-transparent", "value": "rgba(255, 255, 255, 0)" },
@@ -195,26 +195,34 @@
{ "name": "--a-purple-900", "value": "rgba(31, 20, 47, 1)" }
],
"semantic-border": [
"semantic-border-default": [
{ "name": "--a-border-default", "value": "rgba(0, 0, 0, 0.44)" },
{ "name": "--a-border-strong", "value": "rgba(0, 0, 0, 0.65)" },
{ "name": "--a-border-divider", "value": "rgba(0, 0, 0, 0.19)" },
{ "name": "--a-border-subtle", "value": "rgba(0, 0, 0, 0.1)" },
{ "name": "--a-border-subtle-hover", "value": "rgba(0, 0, 0, 0.19)" },
{ "name": "--a-border-on-inverted", "value": "rgba(229, 229, 229, 1)" },
{ "name": "--a-border-on-inverted-subtle", "value": "rgba(89, 89, 89, 1)" }
],
"semantic-border-action": [
{ "name": "--a-border-action", "value": "rgba(0, 103, 197, 1)" },
{ "name": "--a-border-action-selected", "value": "rgba(0, 91, 130, 1)" },
{ "name": "--a-border-selected", "value": "rgba(0, 103, 197, 1)" },
{ "name": "--a-border-selected", "value": "rgba(0, 103, 197, 1)" }
],
"semantic-border-status": [
{ "name": "--a-border-success", "value": "rgba(6, 137, 58, 1)" },
{ "name": "--a-border-danger", "value": "rgba(195, 0, 0, 1)" },
{ "name": "--a-border-warning", "value": "rgba(199, 115, 0, 1)" },
{ "name": "--a-border-info", "value": "rgba(54, 141, 168, 1)" },
{ "name": "--a-border-on-inverted", "value": "rgba(229, 229, 229, 1)" },
{ "name": "--a-border-on-inverted-subtle", "value": "rgba(89, 89, 89, 1)" },
{ "name": "--a-border-info", "value": "rgba(54, 141, 168, 1)" }
],
"semantic-border-focus": [
{ "name": "--a-border-focus", "value": "rgba(0, 52, 125, 1)" },
{
"name": "--a-border-focus-on-inverted",
"value": "rgba(153, 195, 255, 1)"
},
}
],
"semantic-border-alt": [
{ "name": "--a-border-alt-1", "value": "rgba(130, 105, 162, 1)" },
{ "name": "--a-border-alt-2", "value": "rgba(127, 137, 0, 1)" },
{ "name": "--a-border-alt-3", "value": "rgba(0, 91, 130, 1)" }
@@ -241,7 +249,7 @@
{ "name": "--a-text-on-alt-2", "value": "rgba(38, 38, 38, 1)" },
{ "name": "--a-text-on-alt-3", "value": "rgba(255, 255, 255, 1)" }
],
"semantic-surface": [
"semantic-surface-default": [
{ "name": "--a-surface-default", "value": "rgba(255, 255, 255, 1)" },
{ "name": "--a-surface-hover", "value": "rgba(0, 0, 0, 0.05)" },
{ "name": "--a-surface-active", "value": "rgba(0, 0, 0, 0.1)" },
@@ -251,7 +259,9 @@
{ "name": "--a-surface-backdrop", "value": "rgba(0, 0, 0, 0.65)" },
{ "name": "--a-surface-inverted-hover", "value": "rgba(64, 64, 64, 1)" },
{ "name": "--a-surface-inverted-active", "value": "rgba(89, 89, 89, 1)" },
{ "name": "--a-surface-inverted", "value": "rgba(38, 38, 38, 1)" },
{ "name": "--a-surface-inverted", "value": "rgba(38, 38, 38, 1)" }
],
"semantic-surface-action": [
{ "name": "--a-surface-action-subtle", "value": "rgba(230, 240, 255, 1)" },
{
"name": "--a-surface-action-subtle-hover",
@@ -264,7 +274,9 @@
{
"name": "--a-surface-action-selected-hover",
"value": "rgba(0, 80, 119, 1)"
},
}
],
"semantic-surface-neutral": [
{ "name": "--a-surface-neutral-subtle", "value": "rgba(0, 0, 0, 0.05)" },
{
"name": "--a-surface-neutral-subtle-hover",
@@ -274,7 +286,9 @@
{ "name": "--a-surface-neutral", "value": "rgba(89, 89, 89, 1)" },
{ "name": "--a-surface-neutral-hover", "value": "rgba(64, 64, 64, 1)" },
{ "name": "--a-surface-neutral-selected", "value": "rgba(38, 38, 38, 1)" },
{ "name": "--a-surface-neutral-active", "value": "rgba(38, 38, 38, 1)" },
{ "name": "--a-surface-neutral-active", "value": "rgba(38, 38, 38, 1)" }
],
"semantic-surface-success": [
{ "name": "--a-surface-success-subtle", "value": "rgba(204, 241, 214, 1)" },
{
"name": "--a-surface-success-subtle-hover",
@@ -285,7 +299,9 @@
"value": "rgba(153, 222, 173, 1)"
},
{ "name": "--a-surface-success", "value": "rgba(6, 137, 58, 1)" },
{ "name": "--a-surface-success-hover", "value": "rgba(0, 124, 46, 1)" },
{ "name": "--a-surface-success-hover", "value": "rgba(0, 124, 46, 1)" }
],
"semantic-surface-danger": [
{ "name": "--a-surface-danger-subtle", "value": "rgba(255, 194, 194, 1)" },
{
"name": "--a-surface-danger-subtle-hover",
@@ -297,7 +313,9 @@
},
{ "name": "--a-surface-danger", "value": "rgba(195, 0, 0, 1)" },
{ "name": "--a-surface-danger-hover", "value": "rgba(173, 0, 0, 1)" },
{ "name": "--a-surface-danger-active", "value": "rgba(140, 0, 0, 1)" },
{ "name": "--a-surface-danger-active", "value": "rgba(140, 0, 0, 1)" }
],
"semantic-surface-warning": [
{ "name": "--a-surface-warning-subtle", "value": "rgba(255, 236, 204, 1)" },
{
"name": "--a-surface-warning-moderate",
@@ -307,14 +325,18 @@
{
"name": "--a-surface-warning-subtle-hover",
"value": "rgba(255, 215, 153, 1)"
},
}
],
"semantic-surface-info": [
{ "name": "--a-surface-info-subtle", "value": "rgba(216, 249, 255, 1)" },
{
"name": "--a-surface-info-subtle-hover",
"value": "rgba(181, 241, 255, 1)"
},
{ "name": "--a-surface-info-moderate", "value": "rgba(181, 241, 255, 1)" },
{ "name": "--a-surface-info", "value": "rgba(102, 203, 236, 1)" },
{ "name": "--a-surface-info", "value": "rgba(102, 203, 236, 1)" }
],
"semantic-surface-alt": [
{ "name": "--a-surface-alt-1", "value": "rgba(130, 105, 162, 1)" },
{ "name": "--a-surface-alt-1-subtle", "value": "rgba(224, 216, 233, 1)" },
{ "name": "--a-surface-alt-1-moderate", "value": "rgba(192, 178, 210, 1)" },
@@ -337,18 +359,22 @@
"value": "rgba(241, 241, 241, 1)"
}
],
"semantic-icon": [
"semantic-icon-defaults": [
{ "name": "--a-icon-default", "value": "rgba(38, 38, 38, 1)" },
{ "name": "--a-icon-subtle", "value": "rgba(0, 0, 0, 0.65)" },
{ "name": "--a-icon-on-neutral", "value": "rgba(255, 255, 255, 1)" },
{ "name": "--a-icon-on-inverted", "value": "rgba(255, 255, 255, 1)" },
{ "name": "--a-icon-on-inverted", "value": "rgba(255, 255, 255, 1)" }
],
"semantic-icon-action": [
{ "name": "--a-icon-action", "value": "rgba(0, 103, 197, 1)" },
{ "name": "--a-icon-action-selected", "value": "rgba(0, 91, 130, 1)" },
{
"name": "--a-icon-action-on-action-subtle",
"value": "rgba(0, 86, 180, 1)"
},
{ "name": "--a-icon-on-action", "value": "rgba(255, 255, 255, 1)" },
{ "name": "--a-icon-on-action", "value": "rgba(255, 255, 255, 1)" }
],
"semantic-icon-status": [
{ "name": "--a-icon-success", "value": "rgba(6, 137, 58, 1)" },
{ "name": "--a-icon-on-success", "value": "rgba(255, 255, 255, 1)" },
{ "name": "--a-icon-danger", "value": "rgba(195, 0, 0, 1)" },
@@ -356,11 +382,14 @@
{ "name": "--a-icon-warning", "value": "rgba(199, 115, 0, 1)" },
{ "name": "--a-icon-on-warning", "value": "rgba(38, 38, 38, 1)" },
{ "name": "--a-icon-info", "value": "rgba(35, 107, 125, 1)" },
{ "name": "--a-icon-on-info", "value": "rgba(38, 38, 38, 1)" },
{ "name": "--a-icon-on-info", "value": "rgba(38, 38, 38, 1)" }
],
"semantic-icon-alt": [
{ "name": "--a-icon-alt-1", "value": "rgba(99, 70, 137, 1)" },
{ "name": "--a-icon-alt-2", "value": "rgba(127, 137, 0, 1)" },
{ "name": "--a-icon-alt-3", "value": "rgba(51, 128, 165, 1)" }
],
"semantic-data-surface": [
{
"name": "--a-data-surface-1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-tokens",
"version": "4.1.0",
"version": "4.1.7",
"description": "Design-tokens for NAV designsystem",
"author": "Aksel | NAV designsystem team",
"keywords": [
+5 -12
View File
@@ -1,19 +1,12 @@
FROM node:18.1-alpine3.14
RUN apk add --no-cache libc6-compat
FROM gcr.io/distroless/nodejs:18 as runtime
ENV NODE_ENV production
WORKDIR /app
COPY aksel.nav.no/website/.next/standalone ./
# COPY aksel.nav.no/website/package.json .
# COPY aksel.nav.no/website/.next/ .next/
# COPY aksel.nav.no/website/public/ public
# COPY aksel.nav.no/website/.env .
# COPY aksel.nav.no/website/next.config.js .
# COPY node_modules/ node_modules/
# COPY @navikt/ @navikt/
# CMD yarn start
COPY aksel.nav.no/website/next-logger.config.js ./
ENV NODE_OPTIONS '-r next-logger'
EXPOSE 3000
CMD ["node", "aksel.nav.no/website/server.js"]
CMD ["aksel.nav.no/website/server.js"]
+4
View File
@@ -35,3 +35,7 @@ spec:
- host: cdn.sanity.io
rules: []
env:
filesFrom:
- emptyDir:
medium: Memory
mountPath: /app/aksel.nav.no/website/.next/cache
+4
View File
@@ -46,3 +46,7 @@ spec:
age: 7
numNewerVersions: 3
withState: ANY
filesFrom:
- emptyDir:
medium: Memory
mountPath: /app/aksel.nav.no/website/.next/cache
+5 -5
View File
@@ -15,11 +15,11 @@
"website"
],
"dependencies": {
"@navikt/aksel-icons": "^4.1.0",
"@navikt/ds-css": "^4.1.0",
"@navikt/ds-react": "^4.1.0",
"@navikt/ds-tailwind": "^4.1.0",
"@navikt/ds-tokens": "^4.1.0"
"@navikt/aksel-icons": "^4.1.7",
"@navikt/ds-css": "^4.1.7",
"@navikt/ds-react": "^4.1.7",
"@navikt/ds-tailwind": "^4.1.7",
"@navikt/ds-tokens": "^4.1.7"
},
"devDependencies": {
"prettier-plugin-tailwindcss": "^0.2.3"
@@ -25,20 +25,8 @@ import {
UuFeedback,
Video,
} from ".";
export const InlineCode = (props: React.HTMLAttributes<HTMLElement>) => (
<code
className="bg-surface-alt-3-subtle/50 py-05 rounded px-1 leading-normal [font-size:_calc(16em_/_18)]"
{...props}
/>
);
export const KBD = (props: React.HTMLAttributes<HTMLElement>) => (
<kbd
className="text-text-default bg-surface-default py-05 text-medium whitespace-nowrap rounded px-1 leading-normal shadow-[0_0_0_1px_var(--a-border-strong)]"
{...props}
/>
);
import { KBD } from "components/website-modules/KBD";
import { InlineCode } from "components/website-modules/InlineCode";
const serializers = {
types: {
@@ -169,7 +157,7 @@ const serializers = {
return (
<BodyLong
as="li"
className="ml-5 mb-3 max-w-[calc(theme(spacing.text)_-_1em)]"
className="mb-3 ml-5 max-w-[calc(theme(spacing.text)_-_1em)]"
>
{props.children}
</BodyLong>
@@ -75,7 +75,7 @@ export const WithSidebar = ({
<>
<Header />
<div className="bg-bg-default">
<div className="mx-auto mt-6 mb-24 flex w-full max-w-screen-2xl gap-6">
<div className="mx-auto mb-24 mt-6 flex w-full max-w-screen-2xl gap-6">
<Sidebar kategori={pageType.type} links={sidebar} />
<div className="relative w-full">
<main
@@ -83,76 +83,80 @@ export const WithSidebar = ({
id="hovedinnhold"
className="min-h-screen-header md:max-w-screen-sidebar relative w-full px-4 focus:outline-none sm:pl-6 sm:pr-6 md:pl-0"
>
<div
className={cl(
"relative mb-10 min-h-[12.5rem] overflow-hidden rounded-xl pl-6 pr-4 md:pl-10 lg:pr-10",
{
"bg-surface-subtle flex items-center justify-between":
variant === "page",
"bg-deepblue-700 grid py-[4.25rem] pb-6":
variant !== "page",
}
)}
>
<div className="z-[1]">
{variant === "page" && pageProps?.kategori && (
<Detail as="div" className="mb-2">
<NextLink
href={`/${pageType.type.toLowerCase()}`}
passHref
legacyBehavior
>
<Link className="text-text-default">
{capitalize(pageType.type)}
</Link>
</NextLink>{" "}
/ {capitalize(pageProps.kategori)}
</Detail>
)}
<Heading
level="1"
size="xlarge"
className={cl({
"text-deepblue-800": variant === "page",
"text-text-on-action bg-deepblue-700/80 w-fit":
<div className="from-deepblue-700 rounded-xl bg-gradient-to-br via-blue-500 to-violet-700">
<div
className={cl(
"relative mb-10 min-h-[12.5rem] overflow-hidden rounded-xl pl-6 pr-4 md:pl-10 lg:pr-10",
{
"bg-surface-subtle flex items-center justify-between":
variant === "page",
"bg-deepblue-700/80 grid py-[4.25rem] pb-6":
variant !== "page",
})}
>
{pageType.title}
</Heading>
<div
className={cl({
"bg-deepblue-700/80 w-fit": variant !== "page",
})}
>
{intro && intro}
</div>
</div>
{variant === "page" && pageProps.status?.bilde && (
<div
className={cl(
"relative hidden aspect-square h-[12.5rem] lg:block xl:mr-40",
{
"hue-rotate-[65deg]": pageProps?.status?.tag === "beta",
}
}
)}
>
<div className="z-[1]">
{variant === "page" && pageProps?.kategori && (
<Detail as="div" className="mb-2">
<NextLink
href={`/${pageType.type.toLowerCase()}`}
passHref
legacyBehavior
>
<Link className="text-text-default">
{capitalize(pageType.type)}
</Link>
</NextLink>{" "}
/ {capitalize(pageProps.kategori)}
</Detail>
)}
>
<Image
src={urlFor(pageProps.status?.bilde).auto("format").url()}
decoding="async"
layout="fill"
objectFit="contain"
aria-hidden
priority
/>
<Heading
level="1"
size="xlarge"
className={cl({
"text-deepblue-800": variant === "page",
"text-text-on-action w-fit": variant !== "page",
})}
>
{pageType.title}
</Heading>
<div
className={cl({
" w-fit": variant !== "page",
})}
>
{intro && intro}
</div>
</div>
)}
{variant === "landingPage" && (
<div className="pointer-events-none absolute top-0 right-0 hidden sm:block">
<HeaderCube className="text-deepblue-300 z-0 max-h-full" />
</div>
)}
{variant === "page" && pageProps.status?.bilde && (
<div
className={cl(
"relative hidden aspect-square h-[12.5rem] lg:block xl:mr-40",
{
"hue-rotate-[65deg]":
pageProps?.status?.tag === "beta",
}
)}
>
<Image
src={urlFor(pageProps.status?.bilde)
.auto("format")
.url()}
decoding="async"
layout="fill"
objectFit="contain"
aria-hidden
priority
/>
</div>
)}
{variant === "landingPage" && (
<div className="pointer-events-none absolute right-0 top-0 hidden sm:block">
<HeaderCube className="text-deepblue-300 z-[-1] max-h-full" />
</div>
)}
</div>
</div>
<div className={cl("sm:px-6 md:px-10", { flex: withToc })}>
{withToc && (
@@ -1,24 +0,0 @@
import Footer from "./Footer";
import { Meta } from "@storybook/react";
import { NextDecorator } from "../../StoryDecorator";
const MetaData: Meta = {
component: Footer,
tags: ["autodocs"],
decorators: [
(Story) => (
<NextDecorator>
<Story />
</NextDecorator>
),
],
parameters: {
layout: "fullscreen",
},
};
export default MetaData;
export const FooterDemo = {
render: () => <Footer />,
};
@@ -50,7 +50,7 @@ export function Hit({
<span className="mt-4 flex gap-2 empty:mt-0">
{hit.item?.tema &&
hit.item?.tema.map((x) => (
<Tag variant="alt3" size="xsmall" key={x}>
<Tag variant="neutral-moderate" size="xsmall" key={x}>
{x}
</Tag>
))}
@@ -1,20 +1,20 @@
import { useDebounce, logSearch } from "@/utils";
import { SearchLogT, SearchResultsT, searchOptions } from "@/types";
import { logSearch, useDebounce } from "@/utils";
import { MagnifyingGlassIcon } from "@navikt/aksel-icons";
import {
Button,
Checkbox,
CheckboxGroup,
Detail,
Loader,
Search,
} from "@navikt/ds-react";
import { ChangeLogIconOutline } from "components/assets";
import { SearchLogT, searchOptions, SearchResultsT } from "@/types";
import { KBD } from "components/website-modules/KBD";
import { useRouter } from "next/router";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import ReactModal from "react-modal";
import { Group, GroupComponent } from "./Group";
import styles from "../header.module.css";
import { MagnifyingGlassIcon } from "@navikt/aksel-icons";
import { Group, GroupComponent } from "./Group";
/**
* https://www.figma.com/file/71Sm1h6VV23lbBbQ3CJJ9t/Aksel-v2?node-id=1861%3A186079&t=ARKgZcA6B7ysmG3V-0
@@ -189,7 +189,7 @@ export const GlobalSearch = () => {
};
return (
<div className="z-[1050] mr-4 ml-auto flex justify-center lg:mr-0 lg:ml-0">
<div className="z-[1050] ml-auto mr-4 flex justify-center lg:ml-0 lg:mr-0">
<Button
variant="primary"
className="hover:bg-deepblue-700 bg-deepblue-600 focus-visible:shadow-focus-gap h-11 focus:shadow-none"
@@ -215,12 +215,21 @@ export const GlobalSearch = () => {
overlayClassName={styles.modalOverlaySearch}
>
<div className="search-grid-wrapper relative mx-auto max-w-4xl gap-4 gap-x-8 py-24">
<button
className="focus-visible:shadow-focus hover:bg-surface-neutral-subtle-hover absolute top-8 right-4 flex items-center justify-center rounded py-3 px-2 text-lg focus:outline-none"
onClick={() => handleClose()}
{/* <button
className="focus-visible:shadow-focus hover:bg-surface-neutral-subtle-hover absolute right-4 top-8 flex items-center justify-center gap-2 rounded px-2 py-3 text-lg focus:outline-none"
>
Lukk søk <KBD>ESC</KBD>
</button>
<span>Lukk søk</span>
</button> */}
<Button
className="absolute right-4 top-8"
variant="tertiary-neutral"
icon={<KBD>ESC</KBD>}
onClick={() => handleClose()}
iconPosition="right"
>
Lukk søk
</Button>
<div className="search-grid-filter mt-8">
<CheckboxGroup legend="Filter" onChange={setTags}>
{Object.entries(searchOptions)
@@ -247,8 +256,8 @@ export const GlobalSearch = () => {
<form role="search" onSubmit={(e) => e.preventDefault()}>
<Search
label={
<span className="flex items-center">
Søk i hele Aksel{" "}
<span className="flex items-center gap-2">
<span>Søk i hele Aksel</span>
{os === "mac" ? <KBD>CMD + B</KBD> : <KBD>CTRL + B</KBD>}
</span>
}
@@ -359,14 +368,3 @@ export const GlobalSearch = () => {
</div>
);
};
function KBD({ children }: { children: React.ReactNode }) {
return (
<Detail
as="kbd"
className="bg-surface-neutral-subtle-hover ml-2 hidden rounded px-2 font-sans font-semibold uppercase md:inline-block"
>
{children}
</Detail>
);
}
@@ -1,7 +1,7 @@
import { AkselSidebarT } from "@/types";
import { logNav } from "@/utils";
import { ChevronDownIcon } from "@navikt/aksel-icons";
import { BodyShort, Detail, Tag } from "@navikt/ds-react";
import { Detail, Tag } from "@navikt/ds-react";
import cl from "clsx";
import Link from "next/link";
import { useRouter } from "next/router";
@@ -17,61 +17,52 @@ const NavItem = ({
}) => {
const isActive = asPath === `/${link.slug}`;
return (
<li
className={cl(
"focus-within:shadow-focus peer relative rounded-sm pl-2 before:absolute before:left-0 before:z-[-1]",
{
"before:border-l-border-action-selected before:top-1/2 before:h-6 before:-translate-y-1/2 before:rounded-full before:border-l-[4px]":
isActive,
"before:h-full before:rounded-full before:border-l before:border-l-gray-200 hover:before:top-1/2 hover:before:h-6 hover:before:-translate-y-1/2 hover:before:border-l-2 hover:before:border-l-gray-400":
!isActive,
}
)}
>
<Link href={`/${link.slug}`} passHref legacyBehavior>
<BodyShort
as="a"
size="small"
onClick={(e) => {
logNav(
"meny",
window.location.pathname,
e.currentTarget.getAttribute("href")
);
}}
className={cl(
"hover:text-deepblue-800 relative flex overflow-hidden px-2 py-[6px] no-underline focus:outline-none",
{
"text-deepblue-800 font-semibold": isActive,
"text-text-subtle": !isActive,
}
)}
>
{link.heading}
<span className="ml-auto capitalize">
{link.tag && ["deprecated", "beta", "new"].includes(link.tag) && (
<Tag
size="xsmall"
variant={
link.tag === "beta"
? "alt1"
: link.tag === "deprecated"
? "neutral"
: "info"
}
className={cl({
"border-violet-300 bg-violet-50": link.tag === "beta",
})}
>
{link.tag === "new"
? "ny"
<li>
<Link
href={`/${link.slug}`}
onClick={(e) => {
logNav(
"meny",
window.location.pathname,
e.currentTarget.getAttribute("href")
);
}}
className={cl(
"hover:text-deepblue-800 focus-visible:shadow-focus text-medium relative flex overflow-hidden rounded-sm py-1 pl-4 pr-2 leading-snug before:rounded-full hover:before:transition-colors focus:outline-none",
{
"before:border-l-border-action-selected before:absolute before:left-0 before:top-1/2 before:h-6 before:-translate-y-1/2 before:border-l-[4px]":
isActive,
"text-deepblue-800 font-semibold": isActive,
"text-text-subtle": !isActive,
"before:absolute before:left-0 before:h-full before:border-l before:border-l-gray-200 hover:before:top-1/2 hover:before:h-6 hover:before:-translate-y-1/2 hover:before:border-l-2 hover:before:border-l-gray-400":
!isActive,
}
)}
>
{link.heading}
<span className="ml-auto capitalize">
{link.tag && ["deprecated", "beta", "new"].includes(link.tag) && (
<Tag
size="xsmall"
variant={
link.tag === "beta"
? "alt1"
: link.tag === "deprecated"
? "Avviklet"
: link.tag}
</Tag>
)}
</span>
</BodyShort>
? "neutral"
: "info"
}
className={cl({
"border-violet-300 bg-violet-50": link.tag === "beta",
})}
>
{link.tag === "new"
? "ny"
: link.tag === "deprecated"
? "Avviklet"
: link.tag}
</Tag>
)}
</span>
</Link>
</li>
);
@@ -88,10 +79,10 @@ const Dropdown = ({
const { asPath } = useRouter();
return (
<li className="sidebar-dropdown w-full" data-open={open}>
<li className="peer w-full peer-data-[open=true]:mt-6" data-open={open}>
<button
onClick={() => setOpen((x) => !x)}
className="min-h-8 text-text-subtle hover:text-deepblue-800 group z-10 flex w-full cursor-pointer items-center justify-between pr-2 focus:outline-none"
className="min-h-8 text-text-subtle hover:text-deepblue-800 group z-10 flex w-full cursor-pointer items-center justify-between focus:outline-none"
aria-expanded={open}
>
<Detail
@@ -109,16 +100,11 @@ const Dropdown = ({
</Detail>
</button>
<ul hidden={!open} className="px-2">
<ul hidden={!open} className="pl-2">
{links.map((z) => (
<NavItem link={z} key={z?.heading} asPath={asPath} />
))}
</ul>
<style jsx>{`
.sidebar-dropdown[data-open="true"] + .sidebar-dropdown {
margin-top: var(--a-spacing-6);
}
`}</style>
</li>
);
};
@@ -135,10 +121,10 @@ export const Sidebar = ({
return (
<div
data-testid="ds-sidebar"
className="toc-ignore w-sidebar bg-surface-default z-[1002] hidden shrink-0 self-start pb-4 md:block"
className="toc-ignore w-sidebar bg-surface-default z-[1002] hidden shrink-0 self-start overflow-x-auto pb-8 pl-6 pr-2 md:block"
>
<nav aria-label={kategori} className={cl("overflow-x-auto")}>
<ul className="pb-4 pl-6">
<nav aria-label={kategori}>
<ul>
{sections.map((kat) => (
<Dropdown links={kat.pages} title={kat.title} key={kat.title} />
))}
@@ -3,37 +3,36 @@ import { withErrorBoundary } from "@/error-boundary";
import { urlFor } from "@/sanity/interface";
import { DoDontT } from "@/types";
import {
CheckmarkCircleFillIcon,
ExclamationmarkTriangleFillIcon,
XMarkOctagonFillIcon,
CheckmarkIcon,
ExclamationmarkIcon,
XMarkIcon,
} from "@navikt/aksel-icons";
import { BodyShort } from "@navikt/ds-react";
import cl from "clsx";
import React from "react";
const GetIcon = (s: string) => {
switch (s) {
case "do":
return (
<CheckmarkCircleFillIcon
<CheckmarkIcon
aria-hidden
fontSize="1.5rem"
fontSize="2rem"
className="flex-shrink-0 text-green-500"
/>
);
case "dont":
return (
<XMarkOctagonFillIcon
<XMarkIcon
aria-hidden
fontSize="1.5rem"
fontSize="2rem"
className="flex-shrink-0 text-red-500"
/>
);
case "warning":
return (
<ExclamationmarkTriangleFillIcon
<ExclamationmarkIcon
aria-hidden
fontSize="1.5rem"
fontSize="2rem"
className="flex-shrink-0 text-orange-500"
/>
);
@@ -42,42 +41,56 @@ const GetIcon = (s: string) => {
}
};
const getText = (s: string) => {
switch (s) {
case "do":
return "Do: ";
case "dont":
return "Don't: ";
case "warning":
return "Fare på ferde: ";
default:
return "";
}
};
const Element = ({ block }: { block: DoDontT["blokker"][number] }) => {
if (!block.picture) return null;
return (
<figure
className={cl("sm:min-w-80 flex min-w-full flex-1 flex-col rounded-t", {
className={cl("sm:min-w-80 flex min-w-full flex-1 flex-col", {
"basis-full": block?.fullwidth,
"max-w-sm": !block?.fullwidth,
})}
>
<img
className="ring-border-subtle rounded-t bg-gray-50 ring-1"
alt={block.alt}
loading="lazy"
decoding="async"
src={urlFor(block.picture).auto("format").url()}
/>
<div
className={cl(
"z-10 -ml-[1px] w-[calc(100%_+_2px)] rounded-b border-t-8",
"relative z-10 -ml-[1px] w-[calc(100%_+_2px)] border-t-4",
{
"border-t-border-success": block.variant === "do",
"border-t-border-danger": block.variant === "dont",
"border-t-surface-warning": block.variant === "warning",
}
)}
aria-hidden
>
<span className="absolute right-[10px] z-10 translate-y-2">
{GetIcon(block.variant)}
</span>
</div>
<img
className="ring-border-subtle bg-gray-50 ring-1"
alt={block.alt}
loading="lazy"
decoding="async"
src={urlFor(block.picture).auto("format").url()}
/>
<figcaption data-variant={block.variant}>
<div className="mt-3">
{block.description && (
<BodyShort
size="small"
as="span"
className="inline-flex items-center gap-2"
>
{GetIcon(block.variant)}
{block.description}
<BodyShort size="small" as="span">
<span className="font-semibold">{getText(block.variant)}</span>
<span>{block.description}</span>
</BodyShort>
)}
</div>
@@ -1,16 +1,9 @@
import { withErrorBoundary } from "@/error-boundary";
import { UUTableT } from "@/types";
import cl from "clsx";
import React from "react";
import { KBD } from "components/website-modules/KBD";
const KBD = (props: React.HTMLAttributes<HTMLElement>) => (
<kbd
className={cl(
"mx-05 my-0 inline-block min-w-[2rem] rounded-md border border-gray-300 bg-gray-100 py-[1px] px-2 text-center font-mono text-sm text-gray-900"
)}
{...props}
/>
);
import { AkselTable, AkselTableRow } from "components/website-modules/Table";
import React from "react";
const UuSeksjon = ({ node }: { node: UUTableT }) => {
if (!node || !node?.tastatur) {
@@ -18,7 +11,7 @@ const UuSeksjon = ({ node }: { node: UUTableT }) => {
}
const getKey = (s: string) => (
<span className="flex w-full flex-wrap gap-x-1 gap-y-2">
<span className="my-1 flex w-full flex-wrap gap-x-1 gap-y-2">
{s
.trim()
.split(" ")
@@ -32,27 +25,14 @@ const UuSeksjon = ({ node }: { node: UUTableT }) => {
);
return (
<table className="mb-7 w-full border-separate border-spacing-0 rounded border border-gray-300">
<thead>
<tr className="rounded-t">
<td className="rounded-tl bg-gray-50 p-2 ">Kommando</td>
<td className="rounded-tr bg-gray-50 p-2 ">Interaksjon</td>
</tr>
</thead>
<tbody className="">
{node.tastatur.map((x) => (
<tr
key={x._key}
className="peer border-b border-t border-gray-300 text-base last-of-type:rounded-b"
>
<td className="border-t border-r border-gray-300 p-2">
{getKey(x.key)}
</td>
<td className="border-t border-gray-300 p-2">{x.action}</td>
</tr>
))}
</tbody>
</table>
<AkselTable th={[{ text: "Kommando" }, { text: "Interaksjon" }]}>
{node.tastatur.map((x) => (
<AkselTableRow
key={x._key}
tr={[{ text: getKey(x.key) }, { text: x.action }]}
/>
))}
</AkselTable>
);
};
@@ -4,6 +4,7 @@ import { ChevronDownCircleIcon } from "@navikt/aksel-icons";
import core from "@navikt/ds-css/tokens.json";
import { BodyLong, CopyButton, Label, Link } from "@navikt/ds-react";
import cl from "classnames";
import { AkselTable, AkselTableRow } from "components/website-modules/Table";
import NextLink from "next/link";
import { useState } from "react";
@@ -27,27 +28,13 @@ const TokenTable = ({ node }: { node: TokenTableT }) => {
!open && showMore,
})}
>
<table className="border-border-divider w-full border-separate border-spacing-0 rounded-t border">
<thead>
<tr className="rounded-t">
<td className="rounded-tl bg-gray-50 p-2 ">Token</td>
<td className="rounded-tr bg-gray-50 p-2 ">Fallback</td>
</tr>
</thead>
<tbody>
{Object.entries(tokens).map(([key, val]) => (
<tr
key={key}
className="peer border-b border-t border-gray-300 font-mono text-sm last-of-type:rounded-b"
>
<td className="border-r border-t border-gray-300 p-2">{key}</td>
<td className="border-t border-gray-300 p-2">{val}</td>
</tr>
))}
</tbody>
</table>
<AkselTable th={[{ text: "Token" }, { text: "Fallback" }]}>
{Object.entries(tokens).map(([key, val]) => (
<AkselTableRow key={key} tr={[{ text: key }, { text: val }]} />
))}
</AkselTable>
{(open || !showMore) && (
<div className="border-border-divider bg-surface-neutral-subtle relative w-full rounded-b border border-t-0 p-2 pr-14">
<div className="border-border-subtle bg-surface-default relative -mt-8 w-full rounded-b border border-t-0 p-2 pr-14">
<CopyButton
copyText={Object.entries(tokens).reduce(
(prev, cur) =>
@@ -1,14 +1,15 @@
import { withErrorBoundary } from "@/error-boundary";
import {
ResolveContributorsSingleT,
ResolveTemaT,
ResolveSlugT,
AkselGodPraksisDocT,
ResolveContributorsSingleT,
ResolveSlugT,
ResolveTemaT,
} from "@/types";
import { BodyShort, Detail, Heading } from "@navikt/ds-react";
import { useFormatedDate } from "components/website-modules/utils/getDate";
import NextLink from "next/link";
import { abbrName, dateStr, logNav } from "../..";
import { abbrName, logNav } from "../..";
const ArtikkelCard = ({
slug,
@@ -25,11 +26,9 @@ const ArtikkelCard = ({
}: ResolveContributorsSingleT<
ResolveTemaT<ResolveSlugT<AkselGodPraksisDocT>>
> & { source: string; variant: string; level?: "2" | "3" }) => {
const date = (rest as any)?.updateInfo?.lastVerified
? (rest as any)?.updateInfo?.lastVerified
: publishedAt
? publishedAt
: _updatedAt;
const date = useFormatedDate(
(rest as any)?.updateInfo?.lastVerified ?? publishedAt ?? _updatedAt
);
return (
<div className="hover:shadow-small focus-within:ring-border-focus bg-surface-default ring-border-subtle group relative rounded-lg p-3 pb-16 ring-1 focus-within:ring-[3px] sm:p-5 sm:pb-16">
@@ -70,7 +69,7 @@ const ArtikkelCard = ({
</Detail>
)}
<Detail as="span" className="text-text-subtle">
{dateStr(date)}
{date}
</Detail>
</span>
) : (
@@ -1,15 +1,16 @@
import { withErrorBoundary } from "@/error-boundary";
import { AkselBloggDocT, ResolveContributorsT, ResolveSlugT } from "@/types";
import { BodyLong, BodyShort, Heading, Link } from "@navikt/ds-react";
import { getAuthors } from "components/website-modules/LatestBloggs";
import { useFormatedDate } from "components/website-modules/utils/getDate";
import NextLink from "next/link";
import { dateStr, logNav } from "../..";
import { getAuthors, logNav } from "@/utils";
const BloggCard = ({
blog,
}: {
blog: ResolveContributorsT<ResolveSlugT<AkselBloggDocT>>;
}) => {
const date = useFormatedDate(blog?.publishedAt ?? blog._createdAt);
return (
<li
key={blog._id}
@@ -38,7 +39,7 @@ const BloggCard = ({
className="text-text-subtle mt-auto flex gap-2 justify-self-end"
>
<span className="font-semibold">{getAuthors(blog)[0]}</span>
<span>{dateStr(blog?.publishedAt ?? blog._createdAt)}</span>
<span>{date}</span>
</BodyShort>
)}
</li>
@@ -82,6 +82,7 @@ const ComponentExamples = ({ node }: { node: CodeExapmplesT }) => {
{node.dir.filer.map((fil) => {
return (
<Chips.Toggle
checkmark={false}
key={fil._key}
value={fil.navn}
selected={active === fil.navn}
@@ -46,8 +46,9 @@ const CodeSnippet = ({
>
<CopyButton
data-theme="dark"
size="small"
copyText={code.code}
className={cl(style.copybutton, "absolute top-2 right-2 z-10")}
className={cl(style.copybutton, "absolute right-2 top-2 z-10")}
/>
<Highlight
code={code.code}
@@ -1,13 +1,14 @@
import { abbrName, dateStr } from "@/utils";
import { BodyShort, Detail, Heading } from "@navikt/ds-react";
import { logNav } from "components/website-modules/utils/amplitude";
import NextLink from "next/link";
import NextImage from "next/legacy/image";
import { Tag } from "./Tag";
import cl from "clsx";
import { getImage } from "components/website-modules/utils/get-image";
import { urlFor } from "@/sanity/interface";
import { withErrorBoundary } from "@/error-boundary";
import { urlFor } from "@/sanity/interface";
import { abbrName } from "@/utils";
import { BodyShort, Detail, Heading } from "@navikt/ds-react";
import cl from "clsx";
import { logNav } from "components/website-modules/utils/amplitude";
import { getImage } from "components/website-modules/utils/get-image";
import { useFormatedDate } from "components/website-modules/utils/getDate";
import NextImage from "next/legacy/image";
import NextLink from "next/link";
import { Tag } from "./Tag";
export type ArticleT = {
_key: string;
@@ -37,7 +38,7 @@ const Card = ({
visible: boolean;
index: number;
}) => {
const date = article.publishedAt ? article.publishedAt : article._updatedAt;
const date = useFormatedDate(article.publishedAt ?? article._updatedAt);
const showFooter = ["aksel_artikkel", "aksel_blogg"].includes(article._type);
const showImage = [
@@ -142,7 +143,7 @@ const Card = ({
{abbrName(article?.contributors[0]?.title)}
</Detail>
)}
<Detail as="span">{dateStr(date)}</Detail>
<Detail as="span">{date}</Detail>
</span>
)}
</div>
@@ -0,0 +1,111 @@
import { urlFor } from "@/sanity/interface";
import { getAuthors, logNav } from "@/utils";
import { BodyLong, BodyShort, Heading, Link } from "@navikt/ds-react";
import cl from "clsx";
import { getImage } from "components/website-modules/utils/get-image";
import { useFormatedDate } from "components/website-modules/utils/getDate";
import Image from "next/legacy/image";
import NextLink from "next/link";
import { ArticleT } from "./Card";
import { Tag } from "./Tag";
export const Highlight = ({
article,
compact,
}: {
article: ArticleT;
compact: boolean;
}) => {
const showFooter = ["aksel_artikkel", "aksel_blogg"].includes(article._type);
const useStatusImage =
["ds_artikkel", "komponent_artikkel"].includes(article._type) &&
article.status?.bilde;
const date = useFormatedDate(article?.publishedAt ?? article._createdAt);
return (
<section
aria-label={`Fremhevet artikkel: ${article?.heading}`}
className={cl({
"grid content-start gap-8 md:grid-cols-2": compact,
"grid content-start gap-6": !compact,
})}
>
<div className="relative block aspect-video">
{useStatusImage ? (
<Image
src={urlFor(article.status.bilde).quality(100).auto("format").url()}
quality={100}
layout="fill"
aria-hidden
priority
className={cl(
"bg-deepblue-200 rounded-lg object-cover sm:object-contain",
{
"hue-rotate-[65deg]": article?.status?.tag === "beta",
}
)}
decoding="sync"
/>
) : article?.seo?.image ? (
<Image
src={urlFor(article.seo.image).quality(100).auto("format").url()}
quality={100}
layout="fill"
objectFit="cover"
aria-hidden
priority
className={cl("rounded-lg", {
"hue-rotate-[65deg]": article?.status?.tag === "beta",
})}
decoding="sync"
/>
) : (
<Image
src={getImage(article?.heading ?? "", "thumbnail")}
layout="fill"
objectFit="contain"
aria-hidden
priority
className="rounded-lg"
decoding="sync"
/>
)}
</div>
<div>
<Tag
type={article._type}
text={article.tema ? article.tema[0] : undefined}
beta={article?.status?.tag === "beta"}
/>
<NextLink href={`/${article.slug.current}`} passHref legacyBehavior>
<Link
onClick={(e) =>
logNav(
"artikkel-kort",
window.location.pathname,
e.currentTarget.getAttribute("href")
)
}
className="text-text-default mb-5 mt-2 no-underline hover:underline"
>
<Heading size="large" level="3">
{article?.heading}
</Heading>
</Link>
</NextLink>
<BodyLong className="mb-4" size="small">
{article?.ingress ?? article.seo?.meta}
</BodyLong>
{showFooter && getAuthors(article as any).length > 0 && (
<BodyShort size="small" className="text-text-subtle flex gap-2">
<span className="font-semibold">
{getAuthors(article as any)[0]}
</span>
<span>{date}</span>
</BodyShort>
)}
</div>
</section>
);
};
@@ -1,16 +1,10 @@
import { BodyLong, BodyShort, Heading, Link } from "@navikt/ds-react";
import Masonry, { ResponsiveMasonry } from "react-responsive-masonry";
import { withErrorBoundary } from "@/error-boundary";
import Image from "next/legacy/image";
import NextLink from "next/link";
import { getImage } from "components/website-modules/utils/get-image";
import { getAuthors } from "components/website-modules/LatestBloggs";
import { dateStr, logNav } from "@/utils";
import { Tag } from "./Tag";
import Card, { ArticleT } from "./Card";
import { Heading } from "@navikt/ds-react";
import cl from "clsx";
import { urlFor } from "@/sanity/interface";
import { useEffect, useRef, useState } from "react";
import Masonry, { ResponsiveMasonry } from "react-responsive-masonry";
import Card, { ArticleT } from "./Card";
import { Highlight } from "./Highlight";
export type LatestT = {
_type: "nytt_fra_aksel";
@@ -29,9 +23,12 @@ const Latest = ({ block }: { block: LatestT }) => {
const section = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
entry.isIntersecting && setIntersected(entry.isIntersecting);
});
const observer = new IntersectionObserver(
([entry]) => {
entry.isIntersecting && setIntersected(entry.isIntersecting);
},
{ rootMargin: "0px 0px 100px 0px" }
);
observer.observe(section.current);
return () => observer.disconnect();
}, []);
@@ -74,99 +71,9 @@ function Highlights({ highlights }: { highlights: ArticleT[] }) {
<div
className={cl({ "grid gap-8 md:grid-cols-2": highlights?.length === 2 })}
>
{highlights.map((x, idx) => {
const showFooter = ["aksel_artikkel", "aksel_blogg"].includes(x._type);
const useStatusImage =
["ds_artikkel", "komponent_artikkel"].includes(x._type) &&
x.status?.bilde;
return (
<section
aria-label={`Fremhevet artikkel: ${x?.heading}`}
key={idx}
className={cl({
"grid content-start gap-8 md:grid-cols-2":
highlights?.length === 1,
"grid content-start gap-6": highlights?.length === 2,
})}
>
<div className="relative block aspect-video">
{useStatusImage ? (
<Image
src={urlFor(x.status.bilde).quality(100).auto("format").url()}
quality={100}
layout="fill"
aria-hidden
priority
className={cl(
"bg-deepblue-200 rounded-lg object-cover sm:object-contain",
{
"hue-rotate-[65deg]": x?.status?.tag === "beta",
}
)}
decoding="sync"
/>
) : x?.seo?.image ? (
<Image
src={urlFor(x.seo.image).quality(100).auto("format").url()}
quality={100}
layout="fill"
objectFit="cover"
aria-hidden
priority
className={cl("rounded-lg", {
"hue-rotate-[65deg]": x?.status?.tag === "beta",
})}
decoding="sync"
/>
) : (
<Image
src={getImage(x?.heading ?? "", "thumbnail")}
layout="fill"
objectFit="contain"
aria-hidden
priority
className="rounded-lg"
decoding="sync"
/>
)}
</div>
<div>
<Tag
type={x._type}
text={x.tema ? x.tema[0] : undefined}
beta={x?.status?.tag === "beta"}
/>
<NextLink href={`/${x.slug.current}`} passHref legacyBehavior>
<Link
onClick={(e) =>
logNav(
"artikkel-kort",
window.location.pathname,
e.currentTarget.getAttribute("href")
)
}
className="text-text-default mb-5 mt-2 no-underline hover:underline"
>
<Heading size="large" level="3">
{x?.heading}
</Heading>
</Link>
</NextLink>
<BodyLong className="mb-4" size="small">
{x?.ingress ?? x.seo?.meta}
</BodyLong>
{showFooter && getAuthors(x as any).length > 0 && (
<BodyShort size="small" className="text-text-subtle flex gap-2">
<span className="font-semibold">
{getAuthors(x as any)[0]}
</span>
<span>{dateStr(x?.publishedAt ?? x._createdAt)}</span>
</BodyShort>
)}
</div>
</section>
);
})}
{highlights.map((x, idx) => (
<Highlight article={x} key={idx} compact={highlights.length === 1} />
))}
</div>
);
}
@@ -1,45 +0,0 @@
import { TipsT } from "@/types";
import Tips from "./index";
export default {
title: "Components/Tips",
component: Tips,
};
const mockData: TipsT = {
_key: "eb522d23896b",
body: [
{
_key: "86a3e733e9bb",
_type: "block",
children: [
{
_key: "5d65a8d0f1760",
_type: "span",
marks: [],
text: "Cupidatat nostrud occaecat incididunt elit adipisicing consequat eu ex anim qui officia mollit.",
},
{
_key: "5d65a8d0f1760",
_type: "span",
marks: [],
text: "Sint incididunt non incididunt ipsum ad esse officia.",
},
],
markDefs: [],
style: "normal",
},
],
eksperiment: false,
};
export const TipsDemo = () => <Tips node={mockData as any} />;
export const TipsDemoAksel = () => (
<div className="aksel-artikkel bg-surface-subtle">
<Tips node={mockData as any} />
</div>
);
export const TipsDemoExperiment = () => (
<Tips node={{ ...mockData, eksperiment: true } as any} />
);
@@ -0,0 +1,49 @@
import { CheckmarkIcon, ClipboardIcon } from "@navikt/aksel-icons";
import { useEffect, useRef, useState } from "react";
import cl from "clsx";
export const Copy = ({ text, copyText }) => {
const [active, setActive] = useState(false);
const [canfade, setCanfade] = useState(false);
const timeoutRef = useRef<number>();
useEffect(() => {
return () => {
timeoutRef.current && clearTimeout(timeoutRef.current);
};
}, []);
const handleClick = () => {
timeoutRef.current && clearTimeout(timeoutRef.current);
setCanfade(true);
navigator.clipboard.writeText(copyText);
setActive(true);
timeoutRef.current = window.setTimeout(() => {
setActive(false);
}, 2000);
};
return (
<button
type="button"
aria-live="polite"
className="py-05 focus-visible:shadow-focus min-h-8 text-medium hover:bg-surface-neutral-subtle-hover group rounded px-1 leading-none focus:outline-none"
onClick={handleClick}
>
<span className="inline-flex flex-row-reverse items-center gap-1">
<span>
{active ? (
<CheckmarkIcon title="Kopiert" className="animate-textbounce" />
) : (
<ClipboardIcon
title="Kopier css-variabel"
className={cl(canfade && "animate-fadeIn")}
/>
)}
</span>
<span aria-live="polite">{text}</span>
</span>
</button>
);
};
@@ -1,70 +0,0 @@
import { CheckmarkIcon, FilesIcon } from "@navikt/aksel-icons";
import cl from "clsx";
import copystring from "copy-to-clipboard";
import { useEffect, useRef, useState } from "react";
export const CopyToken = ({ val }: { val: string }) => {
const timerRef = useRef(null);
const [copyTimer, setCopyTimer] = useState(false);
const [done, setDone] = useState(true);
const copyToken = () => {
copystring(`var(${val})`);
if (done) {
clearTimeout(timerRef.current);
setCopyTimer(true);
setDone(false);
timerRef.current = setTimeout(() => {
setCopyTimer(false);
setDone(true);
}, 2000);
}
};
useEffect(() => {
return () => clearTimeout(timerRef.current);
}, []);
return (
<button
onClick={() => copyToken()}
className="bg-surface-subtle z-1000 hover:text-text-subtle focus-visible:border-border-focus border-surface-default group relative h-8 w-full overflow-x-hidden overflow-y-hidden whitespace-nowrap rounded border-2 font-mono text-sm focus-visible:outline-none"
aria-label={
copyTimer ? "kopierte token" : `kopier ${val.replace("--a-", "")}`
}
>
<span
className={cl("flex h-7 w-full items-center justify-between px-2", {
"animate-popout -translate-y-full": copyTimer,
})}
>
<span aria-hidden>{val}</span>
<span className="text-text-subtle bg-surface-subtle group-hover:text-text-default text-md absolute right-2 flex h-7 translate-x-3/4 items-center gap-1 px-1 shadow-[-7px_0_8px_0px_var(--a-surface-subbg-surface-subtle)] transition-transform duration-200 group-hover:translate-x-0">
<FilesIcon aria-hidden className="h-4 w-4 flex-shrink-0" />
<span className="opacity-0 transition-opacity duration-300 group-hover:opacity-100">
Kopier
</span>
</span>
</span>
<span
className={cl(
"bg-surface-action-selected text-md text-text-on-action flex h-7 w-full px-2 font-sans text-sm",
{ "animate-popup translate-y-[-100%]": copyTimer }
)}
aria-hidden
>
<span
className={cl("flex w-full items-center justify-center gap-1", {
"animate-textbounce": copyTimer,
})}
>
<CheckmarkIcon aria-hidden className="h-5 w-5 flex-shrink-0" />
Kopiert
</span>
</span>
</button>
);
};
@@ -1,77 +0,0 @@
import { Detail } from "@navikt/ds-react";
import { CopyToken } from "./CopyToken";
export const Frame = ({
tokens,
styles,
element: Element,
showHex,
}: {
tokens: { name: string; value: string; description?: string }[];
styles?: string;
element?: ({ token, name }: { token: string; name?: string }) => JSX.Element;
showHex?: boolean;
}) => {
const hasDescription = !!tokens.find((x) => x?.description);
return (
<table className="w-full">
<thead className="sr-only">
<tr>
<td />
<th>Token navn</th>
<th>Verdi</th>
{hasDescription && <th>Beskrivelse</th>}
</tr>
</thead>
<tbody className="component-token-grid grid grid-cols-[repeat(auto-fill,_minmax(min(20rem,_100%),_1fr))] gap-4">
{tokens.map((c) => (
<tr
key={c.name}
className="ring-border-subtle flex flex-col justify-start rounded-md p-3 align-top ring-1"
>
<td className="component-checkered-bg rounded-md p-0">
{Element ? (
<Element token={c.value} name={c.name} />
) : (
<div
style={{ [styles]: c.value }}
className="min-h-16 shadow-small h-full w-full rounded-md"
aria-hidden
/>
)}
</td>
<td className="mt-3 w-full overflow-x-hidden">
<CopyToken val={c.name} />
</td>
<td className="mt-2 font-mono text-sm tracking-tighter">
{c.value}
</td>
<Detail as="td" className="mt-1">
{showHex ? getHex(c.value) : c.description}
</Detail>
</tr>
))}
</tbody>
</table>
);
};
function getHex(value: string) {
return RGBAToHexA(value.replace(", 1)", ""));
}
/* https://stackoverflow.com/a/73401564 */
function RGBAToHexA(rgba, forceRemoveAlpha = false) {
return (
"#" +
rgba
.replace(/^rgba?\(|\s+|\)$/g, "") // Get's rgba / rgb string values
.split(",") // splits them at ","
.filter((string, index) => !forceRemoveAlpha || index !== 3)
.map((string) => parseFloat(string)) // Converts them to numbers
.map((number, index) => (index === 3 ? Math.round(number * 255) : number)) // Converts alpha to 255 number
.map((number) => number.toString(16)) // Converts numbers to hex
.map((string) => (string.length === 1 ? "0" + string : string)) // Adds 0 when length of one number is 1
.join("")
); // Puts the array to togehter to a string
}
@@ -0,0 +1,20 @@
import React from "react";
import cl from "clsx";
export const Grid = ({
children,
stacked = false,
}: {
children: React.ReactNode;
stacked?: boolean;
}) => (
<div
className={cl("mt-8 grid", {
"grid-cols-[repeat(auto-fit,_minmax(16rem,_1fr))] gap-6 xl:grid-cols-3":
!stacked,
"gap-8": stacked,
})}
>
{children}
</div>
);
@@ -1,126 +1,62 @@
import {
LaptopIcon,
MobileIcon,
MobileSmallIcon,
MonitorIcon,
TabletIcon,
} from "@navikt/aksel-icons";
import docs from "@navikt/ds-tokens/docs.json";
import { Frame } from "../Frame";
import { AkselTable, AkselTableRow } from "components/website-modules/Table";
export const BreakpointsView = ({ cat }: { cat: string }) => {
const breakpoints = docs[cat];
return (
<Frame
tokens={breakpoints}
element={({ name }: { token: string; name?: string }) => {
const Svg = getSvg(name);
return (
<div
className="min-h-16 bg-surface-default flex h-full w-full items-center justify-start rounded-md px-4 text-5xl font-semibold"
aria-hidden
>
{Svg}
</div>
);
}}
/>
<AkselTable
withCopy
th={[
{ text: "Enhet" },
{ text: "Verdi" },
{ text: "Media queries", hideOnSm: true },
{ text: "Bruk" },
]}
>
{breakpoints.map((x) => (
<AkselTableRow
key={x.name}
tr={[
{ text: getSvg(x.name) },
{ text: x.value },
{ text: x.description, hideOnSm: true },
{
text:
Number(x.value.replace("px", "")) % 2 === 0
? "Mobile first"
: "Desktop first",
},
]}
copyText={x.name}
/>
))}
</AkselTable>
);
};
function getSvg(token: string) {
switch (true) {
case token.includes("xs"):
return (
<svg
width="1em"
height="1em"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
focusable={false}
aria-hidden
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M7.75 5.77778C7.75 5.16382 8.20527 4.75 8.66667 4.75H15.3333C15.7947 4.75 16.25 5.16382 16.25 5.77778V18.2222C16.25 18.8362 15.7947 19.25 15.3333 19.25H8.66667C8.20527 19.25 7.75 18.8362 7.75 18.2222V5.77778ZM8.66667 3.25C7.28712 3.25 6.25 4.42806 6.25 5.77778V18.2222C6.25 19.5719 7.28712 20.75 8.66667 20.75H15.3333C16.7129 20.75 17.75 19.5719 17.75 18.2222V5.77778C17.75 4.42806 16.7129 3.25 15.3333 3.25H8.66667ZM12 16.75C11.5858 16.75 11.25 17.0858 11.25 17.5C11.25 17.9142 11.5858 18.25 12 18.25H12.01C12.4242 18.25 12.76 17.9142 12.76 17.5C12.76 17.0858 12.4242 16.75 12.01 16.75H12Z"
fill="#262626"
/>
</svg>
);
return <MobileSmallIcon title="Mobile small" fontSize="2.5rem" />;
case token.includes("sm"):
return (
<svg
width="1em"
height="1em"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
focusable={false}
aria-hidden
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M6.75 5C6.75 4.30964 7.30964 3.75 8 3.75H16C16.6904 3.75 17.25 4.30964 17.25 5V19C17.25 19.6904 16.6904 20.25 16 20.25H8C7.30964 20.25 6.75 19.6904 6.75 19V5ZM8 2.25C6.48122 2.25 5.25 3.48122 5.25 5V19C5.25 20.5188 6.48122 21.75 8 21.75H16C17.5188 21.75 18.75 20.5188 18.75 19V5C18.75 3.48122 17.5188 2.25 16 2.25H8ZM11.5 4.75C11.0858 4.75 10.75 5.08579 10.75 5.5C10.75 5.91421 11.0858 6.25 11.5 6.25H12.5C12.9142 6.25 13.25 5.91421 13.25 5.5C13.25 5.08579 12.9142 4.75 12.5 4.75H11.5Z"
fill="#262626"
/>
</svg>
);
return <MobileIcon title="Mobile regular" fontSize="2.5rem" />;
case token.includes("md"):
return (
<svg
width="1em"
height="1em"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
focusable={false}
aria-hidden
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M3.75 6C3.75 5.30964 4.30964 4.75 5 4.75H19C19.6904 4.75 20.25 5.30964 20.25 6V18C20.25 18.6904 19.6904 19.25 19 19.25H5C4.30964 19.25 3.75 18.6904 3.75 18V6ZM5 3.25C3.48122 3.25 2.25 4.48122 2.25 6V18C2.25 19.5188 3.48122 20.75 5 20.75H19C20.5188 20.75 21.75 19.5188 21.75 18V6C21.75 4.48122 20.5188 3.25 19 3.25H5ZM10.5 5.75C10.0858 5.75 9.75 6.08579 9.75 6.5C9.75 6.91421 10.0858 7.25 10.5 7.25H13.5C13.9142 7.25 14.25 6.91421 14.25 6.5C14.25 6.08579 13.9142 5.75 13.5 5.75H10.5Z"
fill="#262626"
/>
</svg>
);
return <TabletIcon title="Tablet" fontSize="2.5rem" />;
case token.includes("lg"):
return (
<svg
width="1em"
height="1em"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
focusable={false}
aria-hidden
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M5.5 4.25C4.5335 4.25 3.75 5.0335 3.75 6V15C3.75 15.9665 4.5335 16.75 5.5 16.75H18.5C19.4665 16.75 20.25 15.9665 20.25 15V6C20.25 5.0335 19.4665 4.25 18.5 4.25H5.5ZM5.25 6C5.25 5.86193 5.36193 5.75 5.5 5.75H18.5C18.6381 5.75 18.75 5.86193 18.75 6V15C18.75 15.1381 18.6381 15.25 18.5 15.25H5.5C5.36193 15.25 5.25 15.1381 5.25 15V6ZM3 18.25C2.58579 18.25 2.25 18.5858 2.25 19C2.25 19.4142 2.58579 19.75 3 19.75H21C21.4142 19.75 21.75 19.4142 21.75 19C21.75 18.5858 21.4142 18.25 21 18.25H3Z"
fill="#262626"
/>
</svg>
);
return <LaptopIcon title="Laptop" fontSize="2.5rem" />;
case token.includes("xl"):
return (
<svg
width="1em"
height="1em"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
focusable={false}
aria-hidden
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M2.25 4.5C2.25 3.80964 2.80964 3.25 3.5 3.25H20.5C21.1904 3.25 21.75 3.80964 21.75 4.5V15.5C21.75 16.1904 21.1904 16.75 20.5 16.75H12.75V19.25H19C19.4142 19.25 19.75 19.5858 19.75 20C19.75 20.4142 19.4142 20.75 19 20.75H6C5.58579 20.75 5.25 20.4142 5.25 20C5.25 19.5858 5.58579 19.25 6 19.25H11.25V16.75H3.5C2.80964 16.75 2.25 16.1904 2.25 15.5V4.5ZM3.75 4.75V15.25H20.25V4.75H3.75Z"
fill="#262626"
/>
</svg>
);
return <MonitorIcon title="Dekstop" fontSize="2.5rem" />;
default:
return null;
@@ -1,71 +1,59 @@
import docs from "@navikt/ds-tokens/docs.json";
import { Frame } from "../Frame";
const text = "The quick brown fox jumps over the lazy dog";
import { sanitizeName } from "../utilities";
import { AkselTable, AkselTableRow } from "components/website-modules/Table";
export const FontView = ({ cat }: { cat: string }) => {
const shapes = docs[cat];
const fonts = docs[cat];
return (
<Frame
tokens={shapes}
styles="borderRadius"
element={({ token, name }: { token: string; name?: string }) => {
if (name.includes("font-family")) {
return (
<div
className="min-h-16 bg-surface-default flex h-full w-full items-end rounded-md px-4 text-xl"
aria-hidden
>
{text}
</div>
);
}
if (name.includes("font-weight")) {
return (
<div
className="min-h-16 bg-surface-default flex h-full w-full items-end rounded-md px-4 text-xl"
aria-hidden
style={{
fontWeight: token,
}}
>
{text}
</div>
);
}
if (name.includes("font-line-height")) {
return (
<div className="bg-surface-default">
<div
className="min-h-16 flex h-full max-w-xs items-end rounded-md px-4 text-xl"
aria-hidden
style={{
lineHeight: token,
}}
>
{text}
</div>
</div>
);
}
if (name.includes("font-size")) {
return (
<div
className="min-h-16 bg-surface-default flex h-full w-full items-end rounded-md px-4 text-xl"
aria-hidden
style={{
fontSize: token,
fontWeight: name.includes("size-heading") ? "600" : "400",
lineHeight: 1.33,
}}
>
{text}
</div>
);
}
return null;
}}
/>
<AkselTable
withCopy
th={[
{ text: "Token" },
{ text: "Verdi" },
{ text: "Demo", sronly: true },
]}
>
{fonts.map((x) => (
<AkselTableRow
key={x.name}
tr={[
{ text: sanitizeName(x.name.replace("--a-font-", "")) },
{ text: x.value },
{ text: getDemoCell(x) },
]}
copyText={x.name}
/>
))}
</AkselTable>
);
};
function getDemoCell({ value, name }: { value: string; name: string }) {
switch (true) {
case name.includes("weight"):
return <span style={{ fontWeight: value }}>Foreldrepenger</span>;
case name.includes("line-height"):
return (
<span style={{ lineHeight: value }}>
Hello <br />
World
</span>
);
case name.includes("size-heading"):
return (
<span
className="leading-none"
style={{ fontSize: value, fontWeight: value }}
>
Aa
</span>
);
case name.includes("size"):
return <span style={{ fontSize: value }}>Aa</span>;
default:
return <span style={{ fontSize: value }}>Foreldrepenger</span>;
}
}
@@ -1,9 +1,35 @@
import docs from "@navikt/ds-tokens/docs.json";
import { Frame } from "../Frame";
import { Grid } from "../Grid";
import { getColorString, sanitizeName } from "../utilities";
import { Copy } from "../Copy";
export const GlobalView = ({ cat }: { cat: string }) => {
const colors = docs[cat];
return <Frame tokens={colors} styles="background" showHex />;
const colors: { name: string; value: string }[] = docs[cat];
return (
<Grid>
{colors.map((x) => {
return (
<div key={x.name} id={x.name} className="z-0 flex w-fit items-center">
<div
style={{
background: `var(${x.name})`,
boxShadow: `inset 0 2px 4px 0 rgba(0,0,0,0.06)`,
}}
className="mr-3 grid h-16 w-16 place-content-center rounded-lg"
>
<div className="component-checkered-bg z-[-1] h-16 w-16" />
</div>
<dl className="grid h-full">
<dt className="inline-flex items-center gap-2">
<Copy text={sanitizeName(x.name)} copyText={x.name} />
</dt>
<dd className="text-text-subtle text-medium mt-auto">
{getColorString(x.value)}
</dd>
</dl>
</div>
);
})}
</Grid>
);
};
@@ -1,99 +1,311 @@
import { PersonCircleIcon } from "@navikt/aksel-icons";
import docs from "@navikt/ds-tokens/docs.json";
import Link from "next/link";
import color from "tinycolor2";
import { Frame } from "../Frame";
import { Copy } from "../Copy";
import { Grid } from "../Grid";
import { getColorString, getGlobalReference, sanitizeName } from "../utilities";
export const SemanticView = ({ cat }: { cat: string }) => {
const colors = docs[cat];
if (cat === "semantic-text") {
if (cat.startsWith("semantic-text")) {
return (
<Frame
tokens={colors}
styles="color"
showHex
element={({ token }: { token: string }) => {
const c = color(token);
const isLigth = c.getLuminance() > 0.9;
<Grid>
{colors.map((x) => {
const ref = getGlobalReference(x.value);
const c = color(x.value);
const isLight = c.getLuminance() > 0.9;
return (
<div
className="min-h-16 flex h-full w-full items-end rounded-md px-4 text-5xl font-semibold"
aria-hidden
style={{
color: token,
background: isLigth
? `var(--a-surface-inverted)`
: `var(--a-surface-default)`,
}}
>
Aa
</div>
);
}}
/>
);
}
if (cat === "semantic-icon") {
return (
<Frame
tokens={colors}
styles="color"
showHex
element={({ token }: { token: string }) => {
const c = color(token);
const isLigth = c.getLuminance() > 0.9;
return (
<div
className="min-h-16 flex h-full w-full items-center justify-center rounded-md px-4 text-4xl font-semibold"
aria-hidden
style={{
color: token,
background: isLigth
? `var(--a-surface-inverted)`
: `var(--a-surface-default)`,
}}
>
<PersonCircleIcon aria-hidden />
</div>
);
}}
/>
);
}
if (cat === "semantic-border" || cat === "semantic-data-border") {
return (
<Frame
tokens={colors}
styles="color"
showHex
element={({ token }: { token: string }) => {
const c = color(token);
const isLigth = c.getLuminance() > 0.7;
return (
<div
className="min-h-16 flex h-full w-full items-center justify-center rounded-md px-4 text-5xl font-semibold"
aria-hidden
style={{
background: isLigth
? `var(--a-surface-inverted)`
: `var(--a-surface-default)`,
}}
>
<div key={x.name} id={x.name} className="flex w-fit items-center">
<div
className="h-12 w-full rounded-md"
style={{
border: `2px solid ${token}`,
background: isLight ? `var(--a-surface-inverted)` : undefined,
color: x.value,
boxShadow: `inset 0 2px 4px 0 rgba(0,0,0,0.06)`,
}}
/>
className="mr-3 grid h-16 w-16 place-content-center rounded-lg text-2xl leading-none"
>
Aa
</div>
<dl className="grid h-full">
<dt className="inline-flex items-center gap-2">
<Copy
text={sanitizeName(x.name.replace("text-", ""))}
copyText={x.name}
/>
</dt>
<dd className="text-text-subtle text-medium mt-auto">
{ref ? (
<Link
href={`#${ref.name}`}
className="focus-visible:bg-border-focus focus-visible:text-text-on-action hover:underline focus:outline-none"
>
{sanitizeName(ref.name)}
</Link>
) : (
getColorString(x.value)
)}
</dd>
</dl>
</div>
);
}}
/>
})}
</Grid>
);
}
return <Frame tokens={colors} styles="background" showHex />;
if (cat.startsWith("semantic-icon")) {
return (
<Grid>
{colors.map((x) => {
const ref = getGlobalReference(x.value);
const c = color(x.value);
const isLight = c.getLuminance() > 0.9;
return (
<div key={x.name} id={x.name} className="flex w-fit items-center">
<div
style={{
background: isLight ? `var(--a-surface-inverted)` : undefined,
boxShadow: `inset 0 2px 4px 0 rgba(0,0,0,0.06)`,
}}
className="mr-3 grid h-16 w-16 place-content-center rounded-lg text-4xl leading-none"
>
<PersonCircleIcon
aria-hidden
style={{
color: x.value,
}}
/>
</div>
<dl className="grid h-full">
<dt className="inline-flex items-center gap-2">
<Copy
text={sanitizeName(x.name.replace("icon-", ""))}
copyText={x.name}
/>
</dt>
<dd className="text-text-subtle text-medium mt-auto">
{ref ? (
<Link
href={`#${ref.name}`}
className="focus-visible:bg-border-focus focus-visible:text-text-on-action hover:underline focus:outline-none"
>
{sanitizeName(ref.name)}
</Link>
) : (
getColorString(x.value)
)}
</dd>
</dl>
</div>
);
})}
</Grid>
);
}
if (cat.startsWith("semantic-border")) {
return (
<Grid>
{colors.map((x) => {
const ref = getGlobalReference(x.value);
return (
<div key={x.name} id={x.name} className="flex w-fit items-center">
<div
style={{
boxShadow: `inset 0 0 0 3px var(${x.name})`,
}}
className="mr-3 grid h-16 w-16 place-content-center rounded-lg text-4xl leading-none"
/>
<dl className="grid h-full">
<dt className="inline-flex items-center gap-2">
<Copy
text={sanitizeName(x.name.replace("border-", ""))}
copyText={x.name}
/>
</dt>
<dd className="text-text-subtle text-medium mt-auto">
{ref ? (
<Link
href={`#${ref.name}`}
className="focus-visible:bg-border-focus focus-visible:text-text-on-action hover:underline focus:outline-none"
>
{sanitizeName(ref.name)}
</Link>
) : (
getColorString(x.value)
)}
</dd>
</dl>
</div>
);
})}
</Grid>
);
}
if (cat.startsWith("semantic-data-border")) {
return (
<Grid>
{colors.map((x) => {
const ref = getGlobalReference(x.value);
return (
<div
key={x.name}
id={x.name}
className="z-0 flex w-fit items-center"
>
<div className="mr-3 flex h-16 w-16 items-end justify-evenly rounded-lg text-4xl leading-none">
<span
className="rounded-t-small h-full w-3"
style={{
border: `1px solid var(${x.name})`,
backgroundColor: `var(${x.name.replace(
"border",
"surface"
)}-subtle)`,
}}
/>
<span
className="rounded-t-small h-2/3 w-3"
style={{
border: `1px solid var(${x.name})`,
backgroundColor: `var(${x.name.replace(
"border",
"surface"
)}-subtle)`,
}}
/>
<span
className="rounded-t-small h-1/3 w-3"
style={{
border: `1px solid var(${x.name})`,
backgroundColor: `var(${x.name.replace(
"border",
"surface"
)}-subtle)`,
}}
/>
</div>
<dl className="grid h-full">
<dt className="inline-flex items-center gap-2">
<Copy
text={sanitizeName(x.name.replace("surface-", ""))}
copyText={x.name}
/>
</dt>
<dd className="text-text-subtle text-medium mt-auto">
{ref ? (
<Link
href={`#${ref.name}`}
className="focus-visible:bg-border-focus focus-visible:text-text-on-action hover:underline focus:outline-none"
>
{sanitizeName(ref.name)}
</Link>
) : (
getColorString(x.value)
)}
</dd>
</dl>
</div>
);
})}
</Grid>
);
}
if (cat.startsWith("semantic-data-surface")) {
return (
<Grid>
{colors.map((x) => {
const ref = getGlobalReference(x.value);
return (
<div
key={x.name}
id={x.name}
className="z-0 flex w-fit items-center"
>
<div className="mr-3 flex h-16 w-16 items-end justify-evenly rounded-lg text-4xl leading-none">
<span
className="rounded-t-small h-full w-3"
style={{ backgroundColor: `var(${x.name})` }}
/>
<span
className="rounded-t-small h-2/3 w-3"
style={{ backgroundColor: `var(${x.name})` }}
/>
<span
className="rounded-t-small h-1/3 w-3"
style={{ backgroundColor: `var(${x.name})` }}
/>
</div>
<dl className="grid h-full">
<dt className="inline-flex items-center gap-2">
<Copy
text={sanitizeName(x.name.replace("surface-", ""))}
copyText={x.name}
/>
</dt>
<dd className="text-text-subtle text-medium mt-auto">
{ref ? (
<Link
href={`#${ref.name}`}
className="focus-visible:bg-border-focus focus-visible:text-text-on-action hover:underline focus:outline-none"
>
{sanitizeName(ref.name)}
</Link>
) : (
getColorString(x.value)
)}
</dd>
</dl>
</div>
);
})}
</Grid>
);
}
return (
<Grid>
{colors.map((x) => {
const ref = getGlobalReference(x.value);
return (
<div key={x.name} id={x.name} className="z-0 flex w-fit items-center">
<div
style={{
background: `var(${x.name})`,
boxShadow: `inset 0 2px 4px 0 rgba(0,0,0,0.06)`,
}}
className="mr-3 grid h-16 w-16 place-content-center rounded-lg text-4xl leading-none"
>
<div className="component-checkered-bg z-[-1] h-16 w-16" />
</div>
<dl className="grid h-full">
<dt className="inline-flex items-center gap-2">
<Copy
text={sanitizeName(x.name.replace("surface-", ""))}
copyText={x.name}
/>
</dt>
<dd className="text-text-subtle text-medium mt-auto">
{ref ? (
<Link
href={`#${ref.name}`}
className="focus-visible:bg-border-focus focus-visible:text-text-on-action hover:underline focus:outline-none"
>
{sanitizeName(ref.name)}
</Link>
) : (
getColorString(x.value)
)}
</dd>
</dl>
</div>
);
})}
</Grid>
);
};
@@ -1,38 +1,36 @@
import docs from "@navikt/ds-tokens/docs.json";
import { Frame } from "../Frame";
import { Copy } from "../Copy";
import { Grid } from "../Grid";
import { sanitizeName } from "../utilities";
export const ShadowView = ({ cat }: { cat: string }) => {
const shadows = docs[cat];
return (
<Frame
tokens={shadows}
styles="boxShadow"
element={({ token, name }: { token: string; name?: string }) => {
const isLigth = name && name.includes("focus-inverted");
<Grid stacked>
{shadows.map((x) => {
return (
<div
className="min-h-16 flex h-full w-full items-center justify-center rounded-md px-4 text-5xl font-semibold"
aria-hidden
style={{
background: isLigth
? `var(--a-surface-inverted)`
: `var(--a-surface-default)`,
}}
>
<div key={x.name} id={x.name} className="flex w-fit items-center">
<div
className="h-8 w-full rounded-md"
style={{
boxShadow: `${token}`,
background: isLigth
? "var(--a-surface-inverted)"
: "var(--a-bg-subtle)",
boxShadow: `var(${x.name})`,
}}
className="mr-3 grid h-16 w-16 place-content-center rounded-lg text-4xl leading-none"
/>
<dl className="grid h-full">
<dt className="inline-flex items-center gap-2">
<Copy
text={sanitizeName(x.name.replace("shadow-", ""))}
copyText={x.name}
/>
</dt>
<dd className="text-text-subtle text-medium mt-auto">
{x.value}
</dd>
</dl>
</div>
);
}}
/>
})}
</Grid>
);
};
@@ -1,37 +1,34 @@
import docs from "@navikt/ds-tokens/docs.json";
import { Frame } from "../Frame";
import { Copy } from "../Copy";
import { Grid } from "../Grid";
import { sanitizeName } from "../utilities";
export const ShapesView = ({ cat }: { cat: string }) => {
const shapes = docs[cat];
return (
<Frame
tokens={shapes}
styles="borderRadius"
element={({ token }: { token: string; name?: string }) => {
<Grid>
{shapes.map((x) => {
return (
<div
className="min-h-16 flex h-full w-full items-center justify-center gap-4 rounded-md px-4 text-5xl font-semibold"
aria-hidden
style={{
background: `var(--a-surface-default)`,
}}
>
<div key={x.name} id={x.name} className="flex w-fit items-center">
<div
className="bg-surface-alt-3-strong h-8 w-8 rounded-md"
style={{
borderRadius: `${token}`,
}}
/>
<div
className="bg-surface-alt-3-strong h-8 w-20 rounded-md"
style={{
borderRadius: `${token}`,
}}
style={{ borderRadius: x.value }}
className="bg-surface-alt-3-strong mr-3 grid h-16 w-16 place-content-center rounded-lg text-4xl leading-none"
/>
<dl className="grid h-full">
<dt className="inline-flex items-center gap-2">
<Copy
text={sanitizeName(x.name.replace("border-radius-", ""))}
copyText={x.name}
/>
</dt>
<dd className="text-text-subtle text-medium mt-auto">
{x.value}
</dd>
</dl>
</div>
);
}}
/>
})}
</Grid>
);
};
@@ -1,31 +1,41 @@
import docs from "@navikt/ds-tokens/docs.json";
import { Frame } from "../Frame";
import { AkselTable, AkselTableRow } from "components/website-modules/Table";
export const SpacingView = ({ cat }: { cat: string }) => {
const spacings = docs[cat];
return (
<Frame
tokens={spacings}
styles="boxShadow"
element={({ token }: { token: string; name?: string }) => {
return (
<div
className="min-h-16 flex h-full w-full items-center justify-start rounded-md px-4 text-5xl font-semibold"
aria-hidden
style={{
background: `var(--a-surface-default)`,
}}
>
<div
className="bg-surface-alt-3-strong h-8 rounded-md"
style={{
width: token,
}}
/>
</div>
);
}}
/>
<AkselTable
withCopy
th={[
{ text: "Token" },
{ text: "rem" },
{ text: "px" },
{ text: "demo", sronly: true, hideOnSm: true },
]}
>
{spacings.map((x) => (
<AkselTableRow
key={x.name}
tr={[
{ text: x.name.replace("--a-spacing-", "") },
{ text: x.value.replace("rem", "") },
{ text: Number(x.value.replace("rem", "") * 16) },
{
hideOnSm: true,
text: (
<div
className="bg-surface-alt-3-strong mr-auto h-8 rounded-md"
style={{
width: x.value,
}}
/>
),
},
]}
copyText={x.name}
/>
))}
</AkselTable>
);
};
@@ -1,26 +1,49 @@
import docs from "@navikt/ds-tokens/docs.json";
import { Frame } from "../Frame";
import cl from "clsx";
import { Copy } from "../Copy";
import { Grid } from "../Grid";
import { sanitizeName } from "../utilities";
export const ZindexView = ({ cat }: { cat: string }) => {
const shapes = docs[cat];
const zindex = docs[cat];
return (
<Frame
tokens={shapes}
styles="borderRadius"
element={({ token }: { token: string; name?: string }) => {
<Grid>
{zindex.map((x, y) => {
return (
<div
className="min-h-16 flex h-full w-full items-end rounded-md px-4 text-5xl font-semibold"
aria-hidden
style={{
background: `var(--a-surface-default)`,
}}
>
{token}
<div key={x.name} id={x.name} className="flex w-fit items-center">
<div
style={{ boxShadow: `inset 0 2px 4px 0 rgba(0,0,0,0.06)` }}
className="bg-surface-subtle mr-3 flex h-16 w-16 flex-col-reverse justify-between overflow-hidden rounded-lg text-4xl leading-none"
>
{zindex.map((x, xi) => (
<div
aria-hidden
key={x.value}
className={cl(
"h-3 w-16 group-hover:opacity-10 peer-focus-visible:opacity-10",
{
"bg-surface-alt-3-strong": y === xi,
"bg-surface-neutral-subtle": y !== xi,
}
)}
/>
))}
</div>
<dl className="grid h-full">
<dt className="inline-flex items-center gap-2">
<Copy
text={sanitizeName(x.name.replace("z-index-", ""))}
copyText={x.name}
/>
</dt>
<dd className="text-text-subtle text-medium mt-auto">
{x.value}
</dd>
</dl>
</div>
);
}}
/>
})}
</Grid>
);
};
@@ -1,5 +1,5 @@
import { withErrorBoundary } from "@/error-boundary";
import { BreakpointsView } from "components/sanity-modules/token-view/categories/Breakpoints";
import { BreakpointsView } from "./categories/Breakpoints";
import { FontView } from "./categories/Font";
import { GlobalView } from "./categories/Global";
import { SemanticView } from "./categories/Semantic";
@@ -0,0 +1,37 @@
import { FontView } from "./categories/Font";
import { GlobalView } from "./categories/Global";
import { SemanticView } from "./categories/Semantic";
import { ShadowView } from "./categories/Shadow";
import { SpacingView } from "./categories/Spacing";
import { ZindexView } from "./categories/Zindex";
import { BreakpointsView } from "./categories/Breakpoints";
import { ShapesView } from "./categories/Shapes";
import "../../../dist/tw.css";
export default {
title: "sanity-modules/tokenview",
parameters: {
layout: "fullscreen",
},
};
export const FontViewDemo = () => <FontView cat="font" />;
export const GlobalViewDemo = () => <GlobalView cat="global-blue" />;
export const SemanticViewDemo = () => <SemanticView cat="semantic-surface" />;
export const SemanticBorderView = () => <SemanticView cat="semantic-border" />;
export const SemanticIconView = () => <SemanticView cat="semantic-icon" />;
export const SemanticTextView = () => <SemanticView cat="semantic-text" />;
export const RadiusViewDemo = () => <ShapesView cat="radius" />;
export const ShadowViewDemo = () => <ShadowView cat="shadow" />;
export const SpacingViewDemo = () => <SpacingView cat="spacing" />;
export const IndexView = () => <ZindexView cat="z-index" />;
export const BreakpointView = () => <BreakpointsView cat="breakpoints" />;
@@ -0,0 +1,24 @@
import color from "tinycolor2";
import docs from "@navikt/ds-tokens/docs.json";
const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
export const sanitizeName = (x: string) =>
capitalize(x.replace("--a-", "").replaceAll("-", " "));
export const getColorString = (s: string) => {
const c = color(s);
const alpha = c.getAlpha() !== 1;
return alpha ? c.toRgbString() : c.toHexString().toUpperCase();
};
export const getGlobalReference = (
semanticValue: string
): { name: string; value: string } => {
const globalRefs = Object.entries(docs)
.filter(([key]) => key.startsWith("global-"))
.reduce((acc, [_, value]) => [...acc, ...value], []);
return globalRefs.find(({ value }) => semanticValue === value) ?? null;
};
@@ -0,0 +1,11 @@
export const InlineCode = (props: React.HTMLAttributes<HTMLElement>) => (
<code className="text-deepblue-800 font-mono text-sm font-semibold leading-normal before:contents">
<span aria-hidden className="inline-code-amp">
`
</span>
<span>{props.children}</span>
<span aria-hidden className="inline-code-amp">
`
</span>
</code>
);
@@ -0,0 +1,17 @@
import cl from "clsx";
export const KBD = (props: React.HTMLAttributes<HTMLElement>) => (
<kbd
className={cl(
"mx-05 ring-border-subtle my-0 inline-block min-w-[2rem] rounded-md px-2 py-[1px] text-center font-mono text-sm text-gray-900 ring-1 ring-inset",
"from-surface-default via-surface-default to-surface-neutral-subtle bg-gradient-to-b"
)}
>
<span className="kbd">{props.children}</span>
<style jsx>{`
.kbd {
text-shadow: 0 1px 0px var(--a-grayalpha-200);
}
`}</style>
</kbd>
);
@@ -1,270 +0,0 @@
import { SanityBlockContent } from "@/sanity-block";
import { urlFor } from "@/sanity/interface";
import { AkselBloggDocT, ResolveContributorsT, ResolveSlugT } from "@/types";
import { dateStr } from "@/utils";
import { BodyLong, BodyShort, Heading, Link } from "@navikt/ds-react";
import { getImage } from "components/website-modules/utils/get-image";
import Image from "next/legacy/image";
import NextLink from "next/link";
export const getAuthors = (
blog: ResolveContributorsT<ResolveSlugT<AkselBloggDocT>>
) => (blog?.contributors as any)?.map((x) => x?.title) ?? [];
export const LatestBloggposts = ({
bloggs,
title,
level = "1",
intro,
}: {
bloggs: ResolveContributorsT<ResolveSlugT<AkselBloggDocT>>[];
title: string;
level?: "1" | "2";
intro?: any[];
}) => {
if (!bloggs || bloggs.length < 3) {
return null;
}
return (
<div className="mt-20">
<Heading
level={level}
size="xlarge"
className="text-deepblue-700 mx-auto w-full text-5xl md:mx-0 md:max-w-none"
>
{title}
</Heading>
{intro && (
<SanityBlockContent
blocks={intro}
isIngress
noLastMargin
className="mt-4"
/>
)}
{/* Desktop-view */}
<div className="my-12 hidden grid-cols-2 gap-12 md:grid">
<div className="col-span-1">
<div className="relative mb-10 block aspect-video">
{bloggs[0]?.seo?.image ? (
<Image
src={urlFor(bloggs[0].seo.image)
.quality(100)
.auto("format")
.url()}
quality={100}
layout="fill"
objectFit="cover"
aria-hidden
priority
className="rounded-lg"
decoding="sync"
/>
) : (
<Image
src={getImage(bloggs[0]?.heading ?? "", "thumbnail")}
layout="fill"
objectFit="contain"
aria-hidden
priority
className="rounded-lg"
decoding="sync"
/>
)}
</div>
<NextLink href={`/${bloggs[0].slug}`} passHref legacyBehavior>
<Link className="text-deepblue-500 no-underline hover:underline">
<Heading
size="large"
level={(Number(level) + 1).toString() as any}
>
{bloggs[0].heading}
</Heading>
</Link>
</NextLink>
<BodyLong className="mt-4" size="small">
{bloggs[0]?.ingress}
</BodyLong>
{getAuthors(bloggs[0]).length > 0 && (
<BodyShort
size="small"
className="text-text-subtle mt-4 flex gap-2"
>
<span className="font-semibold">{getAuthors(bloggs[0])[0]}</span>
<span>
{dateStr(bloggs[0]?.publishedAt ?? bloggs[0]._createdAt)}
</span>
</BodyShort>
)}
</div>
<div className="col-span-1 grid place-content-start gap-12">
{bloggs.slice(1, 3).map((blog) => (
<div key={blog._id} className="flex gap-6">
<div className="relative hidden aspect-square h-[11.75rem] lg:block">
{blog?.seo?.image ? (
<Image
src={urlFor(blog.seo.image).auto("format").url()}
layout="fill"
objectFit="cover"
aria-hidden
priority
className="rounded-lg"
/>
) : (
<Image
src={getImage(blog?.heading ?? "", "thumbnail")}
layout="fill"
objectFit="cover"
aria-hidden
priority
className="rounded-lg"
/>
)}
</div>
<div>
<NextLink href={`/${blog.slug}`} passHref legacyBehavior>
<Link className="text-deepblue-500 no-underline hover:underline">
<Heading
size="small"
level={(Number(level) + 1).toString() as any}
>
{blog.heading}
</Heading>
</Link>
</NextLink>
<BodyLong className="mt-4" size="small">
{blog?.ingress}
</BodyLong>
{getAuthors(blog).length > 0 ? (
<BodyShort
size="small"
className="text-text-subtle mt-4 flex gap-2"
>
<span className="font-semibold">{getAuthors(blog)[0]}</span>
<span>{dateStr(blog?.publishedAt ?? blog._createdAt)}</span>
</BodyShort>
) : (
<BodyShort size="small" className="text-text-subtle mt-4">
<span>{dateStr(blog?.publishedAt ?? blog._createdAt)}</span>
</BodyShort>
)}
</div>
</div>
))}
</div>
</div>
{/* Mobile view */}
<div className="my-10 mx-auto grid gap-12 md:hidden">
<div className="w-full">
<div className="relative mb-10 block aspect-video">
{bloggs[0]?.seo?.image ? (
<Image
src={urlFor(bloggs[0].seo.image)
.quality(100)
.auto("format")
.url()}
quality={100}
layout="fill"
objectFit="cover"
aria-hidden
priority
className="rounded-lg"
/>
) : (
<Image
src={getImage(bloggs[0]?.heading ?? "", "thumbnail")}
layout="fill"
objectFit="contain"
aria-hidden
priority
className="rounded-lg"
/>
)}
</div>
<NextLink href={`/${bloggs[0].slug}`} passHref legacyBehavior>
<Link className="text-deepblue-500 no-underline hover:underline">
<Heading
size="large"
level={(Number(level) + 1).toString() as any}
>
{bloggs[0].heading}
</Heading>
</Link>
</NextLink>
<BodyLong className="mt-4" size="small">
{bloggs[0]?.ingress}
</BodyLong>
{getAuthors(bloggs[0]).length > 0 && (
<BodyShort
size="small"
className="text-text-subtle mt-4 flex gap-2"
>
<span className="font-semibold">{getAuthors(bloggs[0])[0]}</span>
<span>
{dateStr(bloggs[0]?.publishedAt ?? bloggs[0]._createdAt)}
</span>
</BodyShort>
)}
</div>
<div className="grid w-full gap-12">
{bloggs.slice(1, 3).map((blog) => (
<div key={blog._id}>
<div className="relative mb-6 block aspect-video">
{blog?.seo?.image ? (
<Image
src={urlFor(blog.seo.image).auto("format").url()}
layout="fill"
objectFit="cover"
aria-hidden
priority
className="rounded-lg"
/>
) : (
<Image
src={getImage(blog?.heading ?? "", "thumbnail")}
layout="fill"
objectFit="cover"
aria-hidden
priority
className="rounded-lg"
/>
)}
</div>
<div>
<NextLink href={`/${blog.slug}`} passHref legacyBehavior>
<Link className="text-deepblue-500 no-underline hover:underline">
<Heading
size="small"
level={(Number(level) + 1).toString() as any}
>
{blog.heading}
</Heading>
</Link>
</NextLink>
<BodyLong className="mt-4" size="small">
{blog?.ingress}
</BodyLong>
{getAuthors(blog).length > 0 ? (
<BodyShort
size="small"
className="text-text-subtle mt-4 flex gap-2"
>
<span className="font-semibold">{getAuthors(blog)[0]}</span>
<span>{dateStr(blog?.publishedAt ?? blog._createdAt)}</span>
</BodyShort>
) : (
<BodyShort size="small" className="text-text-subtle mt-4">
<span>{dateStr(blog?.publishedAt ?? blog._createdAt)}</span>
</BodyShort>
)}
</div>
</div>
))}
</div>
</div>
</div>
);
};
@@ -0,0 +1,74 @@
import { CopyButton } from "@navikt/ds-react";
import cl from "clsx";
import { createContext, useContext } from "react";
const TableContext = createContext<boolean>(false);
export function AkselTable({
children,
th,
withCopy,
}: {
children: React.ReactNode;
th: { text: string; hideOnSm?: boolean; sronly?: boolean }[];
withCopy?: boolean;
}) {
return (
<table className="border-border-subtle mb-7 w-full border-separate border-spacing-0 rounded border">
<thead>
<tr className="rounded-t text-left">
{th.map((x) => (
<th
key={x.text}
className={cl("font-regular bg-surface-subtle rounded-tl p-2", {
"hidden sm:table-cell": !!x.hideOnSm,
})}
>
{x?.sronly ? <span className="sr-only">{x.text}</span> : x.text}
</th>
))}
{withCopy && (
<th className="font-regular bg-surface-subtle hidden rounded-tl p-2 sm:table-cell">
<span className="sr-only">Kopi</span>
</th>
)}
</tr>
</thead>
<tbody>
<TableContext.Provider value={withCopy ?? false}>
{children}
</TableContext.Provider>
</tbody>
</table>
);
}
export function AkselTableRow({
tr,
copyText = "",
}: {
tr: { text: string | React.ReactNode; hideOnSm?: boolean }[];
copyText?: string;
}) {
const useCopy = useContext(TableContext);
return (
<tr className="peer border-b border-t border-gray-200 text-base last-of-type:rounded-b">
{tr.map((x, xi) => (
<td
key={xi}
className={cl("border-t border-gray-200 px-2 py-1", {
"hidden sm:table-cell": !!x.hideOnSm,
})}
>
{x.text}
</td>
))}
{useCopy && (
<td className="hidden border-t border-gray-200 px-2 py-1 sm:table-cell">
<CopyButton copyText={copyText} size="small" className="ml-auto" />
</td>
)}
</tr>
);
}
@@ -0,0 +1,117 @@
import { urlFor } from "@/sanity/interface";
import { AkselBloggDocT, ResolveContributorsT, ResolveSlugT } from "@/types";
import { getAuthors } from "@/utils";
import { BodyLong, BodyShort, Heading, Link } from "@navikt/ds-react";
import { getImage } from "components/website-modules/utils/get-image";
import { useFormatedDate } from "components/website-modules/utils/getDate";
import Image from "next/legacy/image";
import NextLink from "next/link";
export const BloggList = ({
blogg,
}: {
blogg: ResolveContributorsT<ResolveSlugT<AkselBloggDocT>>;
}) => {
const date = useFormatedDate(blogg?.publishedAt ?? blogg._createdAt);
return (
<div>
<div className="hidden gap-6 md:flex">
<div className="relative hidden aspect-square h-[11.75rem] lg:block">
{blogg?.seo?.image ? (
<Image
src={urlFor(blogg.seo.image).auto("format").url()}
layout="fill"
objectFit="cover"
aria-hidden
priority
className="rounded-lg"
/>
) : (
<Image
src={getImage(blogg?.heading ?? "", "thumbnail")}
layout="fill"
objectFit="cover"
aria-hidden
priority
className="rounded-lg"
/>
)}
</div>
<div>
<NextLink href={`/${blogg.slug}`} passHref legacyBehavior>
<Link className="text-deepblue-500 no-underline hover:underline">
<Heading size="small" level="2">
{blogg.heading}
</Heading>
</Link>
</NextLink>
<BodyLong className="mt-4" size="small">
{blogg?.ingress}
</BodyLong>
{getAuthors(blogg).length > 0 ? (
<BodyShort
size="small"
className="text-text-subtle mt-4 flex gap-2"
>
<span className="font-semibold">{getAuthors(blogg)[0]}</span>
<span className="animate-fadeIn">{date}</span>
</BodyShort>
) : (
<BodyShort size="small" className="text-text-subtle mt-4">
<span className="animate-fadeIn">{date}</span>
</BodyShort>
)}
</div>
</div>
<div className="md:hidden">
<div className="relative mb-6 block aspect-video">
{blogg?.seo?.image ? (
<Image
src={urlFor(blogg.seo.image).auto("format").url()}
layout="fill"
objectFit="cover"
aria-hidden
priority
className="rounded-lg"
/>
) : (
<Image
src={getImage(blogg?.heading ?? "", "thumbnail")}
layout="fill"
objectFit="cover"
aria-hidden
priority
className="rounded-lg"
/>
)}
</div>
<div className="md:hidden">
<NextLink href={`/${blogg.slug}`} passHref legacyBehavior>
<Link className="text-deepblue-500 no-underline hover:underline">
<Heading size="small" level="2">
{blogg.heading}
</Heading>
</Link>
</NextLink>
<BodyLong className="mt-4" size="small">
{blogg?.ingress}
</BodyLong>
{getAuthors(blogg).length > 0 ? (
<BodyShort
size="small"
className="text-text-subtle mt-4 flex gap-2"
>
<span className="font-semibold">{getAuthors(blogg)[0]}</span>
<span>{date}</span>
</BodyShort>
) : (
<BodyShort size="small" className="text-text-subtle mt-4">
<span>{date}</span>
</BodyShort>
)}
</div>
</div>
</div>
);
};
@@ -0,0 +1,103 @@
import { urlFor } from "@/sanity/interface";
import { AkselBloggDocT, ResolveContributorsT, ResolveSlugT } from "@/types";
import { getAuthors } from "@/utils";
import { BodyLong, BodyShort, Heading, Link } from "@navikt/ds-react";
import { getImage } from "components/website-modules/utils/get-image";
import { useFormatedDate } from "components/website-modules/utils/getDate";
import Image from "next/legacy/image";
import NextLink from "next/link";
export const HighlightedBlogg = ({
blogg,
}: {
blogg: ResolveContributorsT<ResolveSlugT<AkselBloggDocT>>;
}) => {
const date = useFormatedDate(blogg?.publishedAt ?? blogg._createdAt);
return (
<div>
<div className="col-span-1 hidden md:block">
<div className="relative mb-10 block aspect-video">
{blogg?.seo?.image ? (
<Image
src={urlFor(blogg.seo.image).quality(100).auto("format").url()}
quality={100}
layout="fill"
objectFit="cover"
aria-hidden
priority
className="rounded-lg"
decoding="sync"
/>
) : (
<Image
src={getImage(blogg?.heading ?? "", "thumbnail")}
layout="fill"
objectFit="contain"
aria-hidden
priority
className="rounded-lg"
decoding="sync"
/>
)}
</div>
<NextLink href={`/${blogg.slug}`} passHref legacyBehavior>
<Link className="text-deepblue-500 no-underline hover:underline">
<Heading size="large" level="2">
{blogg.heading}
</Heading>
</Link>
</NextLink>
<BodyLong className="mt-4" size="small">
{blogg?.ingress}
</BodyLong>
{getAuthors(blogg).length > 0 && (
<BodyShort size="small" className="text-text-subtle mt-4 flex gap-2">
<span className="font-semibold">{getAuthors(blogg)[0]}</span>
<span>{date}</span>
</BodyShort>
)}
</div>
{/* Mobile view */}
<div className="w-full md:hidden">
<div className="relative mb-10 block aspect-video">
{blogg?.seo?.image ? (
<Image
src={urlFor(blogg.seo.image).quality(100).auto("format").url()}
quality={100}
layout="fill"
objectFit="cover"
aria-hidden
priority
className="rounded-lg"
/>
) : (
<Image
src={getImage(blogg?.heading ?? "", "thumbnail")}
layout="fill"
objectFit="contain"
aria-hidden
priority
className="rounded-lg"
/>
)}
</div>
<NextLink href={`/${blogg.slug}`} passHref legacyBehavior>
<Link className="text-deepblue-500 no-underline hover:underline">
<Heading size="large" level="2">
{blogg.heading}
</Heading>
</Link>
</NextLink>
<BodyLong className="mt-4" size="small">
{blogg?.ingress}
</BodyLong>
{getAuthors(blogg).length > 0 && (
<BodyShort size="small" className="text-text-subtle mt-4 flex gap-2">
<span className="font-semibold">{getAuthors(blogg)[0]}</span>
<span>{date}</span>
</BodyShort>
)}
</div>
</div>
);
};
@@ -0,0 +1,48 @@
import { SanityBlockContent } from "@/sanity-block";
import { AkselBloggDocT, ResolveContributorsT, ResolveSlugT } from "@/types";
import { Heading } from "@navikt/ds-react";
import { BloggList } from "./BloggList";
import { HighlightedBlogg } from "./HighlightedBlogg";
export const LatestBloggposts = ({
bloggs,
title,
intro,
}: {
bloggs: ResolveContributorsT<ResolveSlugT<AkselBloggDocT>>[];
title: string;
intro?: any[];
}) => {
if (!bloggs || bloggs.length < 3) {
return null;
}
return (
<div className="mt-20">
<Heading
level="1"
size="xlarge"
className="text-deepblue-700 mx-auto w-full text-5xl md:mx-0 md:max-w-none"
>
{title}
</Heading>
{intro && (
<SanityBlockContent
blocks={intro}
isIngress
noLastMargin
className="mt-4"
/>
)}
{/* Desktop-view */}
<div className="mx-auto my-10 grid gap-12 md:my-12 md:grid-cols-2">
<HighlightedBlogg blogg={bloggs[0]} />
<div className="col-span-1 grid place-content-start gap-12">
{bloggs.slice(1, 3).map((blogg) => (
<BloggList blogg={blogg} key={blogg._id} />
))}
</div>
</div>
</div>
);
};
@@ -126,7 +126,7 @@ export const IconPage = ({ name }: { name: string }) => {
onSubmit={(e) => e.preventDefault()}
className="flex h-fit w-full flex-wrap-reverse items-center gap-4 px-4 py-2"
>
<div className="min-w-40">
<div className="min-w-[10.25rem]">
<ToggleGroup
value={toggle}
onChange={(v) => setToggle(v as any)}
@@ -137,7 +137,7 @@ export const IconPage = ({ name }: { name: string }) => {
<ToggleGroup.Item value="fill">Fill</ToggleGroup.Item>
</ToggleGroup>
</div>
<div className=" min-w-48 flex w-full items-center gap-2 md:w-fit">
<div className="min-w-48 md:min-w-96 flex w-full items-center gap-2 md:w-fit">
<Search
variant="simple"
label="Ikonsøk"
@@ -209,9 +209,9 @@ export const IconPage = ({ name }: { name: string }) => {
}
}}
className={cl(
"hover:bg-surface-hover bg-surface-default active:bg-surface-neutral-subtle-hover group relative grid aspect-square w-11 shrink-0 place-items-center rounded focus:outline-none focus:ring-2 focus:ring-blue-800",
"hover:bg-surface-hover bg-surface-default active:bg-surface-neutral-subtle-hover group relative grid aspect-square w-11 shrink-0 place-items-center rounded focus:outline-none focus:ring-blue-800 focus-visible:ring-2",
{
"bg-surface-selected ring-border-alt-3 z-10 ring-1":
"from-surface-selected bg-surface-selected ring-border-alt-3 z-10 bg-gradient-to-br to-teal-50 ring-1":
i.id === name,
}
)}
@@ -184,7 +184,7 @@ export const IconSidebar = ({
theme={undefined}
>
{({ tokens, getLineProps, getTokenProps }) => (
<pre className="relative m-0 max-w-[80vw] overflow-x-auto overflow-y-auto rounded-b-lg p-3 font-mono lg:max-w-[20rem]">
<pre className="relative m-0 max-w-[80vw] overflow-x-auto overflow-y-auto rounded-b-lg p-3 font-mono lg:max-w-sm">
{tokens.map((line, i) => (
<div
key={i}
@@ -217,7 +217,7 @@ export const IconSidebar = ({
theme={undefined}
>
{({ tokens, getLineProps, getTokenProps }) => (
<pre className="w-text relative m-0 max-w-[80vw] overflow-x-auto overflow-y-auto rounded-b-lg p-3 font-mono lg:w-auto lg:max-w-[20rem]">
<pre className="w-text relative m-0 max-w-[80vw] overflow-x-auto overflow-y-auto rounded-b-lg p-3 font-mono lg:w-auto lg:max-w-sm">
{tokens.map((line, i) => (
<div
key={i}
@@ -1,41 +0,0 @@
import { SuggestionBlock } from "./index";
import { Meta } from "@storybook/react";
import { NextDecorator } from "../../StoryDecorator";
const MetaData: Meta = {
component: SuggestionBlock,
tags: ["autodocs"],
decorators: [
(Story) => (
<NextDecorator>
<Story />
</NextDecorator>
),
],
};
export default MetaData;
export const Ikoner = {
render: () => <SuggestionBlock variant="ikoner" />,
};
export const Ikon = {
render: () => <SuggestionBlock variant="ikon" />,
};
export const Komponent = {
render: () => <SuggestionBlock variant="komponent" />,
};
export const KomponentBeta = {
render: () => <SuggestionBlock variant="komponent-beta" />,
};
export const KomponentNy = {
render: () => <SuggestionBlock variant="komponent-ny" />,
};
export const Komponenter = {
render: () => <SuggestionBlock variant="komponenter" />,
};
@@ -21,12 +21,12 @@ describe("Utils test", () => {
expect(capitalize(str4)).toEqual("Teststr");
});
test("dateStr", () => {
test("dateStr", async () => {
const date = "2022-06-09T11:05:48Z";
const date2 = "2021-03-02T12:05:48Z";
expect(dateStr(date)).toEqual("9. juni 2022");
expect(dateStr(date2)).toEqual("2. mars 2021");
expect(await dateStr(date)).toEqual("9. juni 2022");
expect(await dateStr(date2)).toEqual("2. mars 2021");
});
test("abbrName", () => {
@@ -0,0 +1,5 @@
export const dateStr = async (date: string) => {
const format = (await import("date-fns/format")).default;
const locale = (await import("date-fns/locale/nb")).default;
return format(new Date(date), "d. MMMM YYY", { locale: locale });
};
@@ -0,0 +1,12 @@
import { dateStr } from "components/website-modules/utils/date-string";
import { useEffect, useState } from "react";
export const useFormatedDate = (date: string) => {
const [_date, setDate] = useState(null);
useEffect(() => {
const handleDate = async () => setDate(await dateStr(date));
handleDate();
}, [date]);
return _date;
};
@@ -1,16 +1,10 @@
import nbLocale from "date-fns/locale/nb";
import { format } from "date-fns";
import { AkselBloggDocT, ResolveContributorsT, ResolveSlugT } from "@/types";
export * from "./util";
export * from "./amplitude";
export * from "./hooks";
export * from "./contexts";
export const dateStr = (date: string) =>
format(new Date(date), "d. MMMM YYY", { locale: nbLocale });
export const dateTimeStr = (date: string) =>
format(new Date(date), "d-MM-YYY", { locale: nbLocale });
export * from "./date-string";
export const isNew = (date: string) => {
const date1 = new Date(date);
@@ -32,3 +26,7 @@ export const abbrName = (name: string): string => {
)
.join(" ");
};
export const getAuthors = (
blog: ResolveContributorsT<ResolveSlugT<AkselBloggDocT>>
) => (blog?.contributors as any)?.map((x) => x?.title) ?? [];
+7
View File
@@ -0,0 +1,7 @@
import pino from "pino";
import { logger as sharedPinoConfig } from "./next-logger.config";
/**
* This logger is not isomorphic, and should only be used on the server
*/
export const logger: pino.Logger = sharedPinoConfig();
@@ -0,0 +1,32 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const pino = require("pino");
const logger = (defaultConfig = {}) =>
pino({
...defaultConfig,
timestamp: false,
formatters: {
level: (label) => {
return { level: label };
},
log: (object) => {
if (object.err) {
// massage the error object so logs.adeo.no shows stack traces
const err =
object.err instanceof Error
? pino.stdSerializers.err(object.err)
: object.err;
object.stack_trace = err.stack;
object.type = err.type;
object.message = err.message;
delete object.err;
}
return object;
},
},
});
module.exports = {
logger,
};
+8 -6
View File
@@ -5,7 +5,7 @@
"description": "Aksel.nav.no website",
"author": "Aksel | NAV designsystem team",
"scripts": {
"dev": "next",
"dev": "next | pino-pretty",
"build:old": "next build",
"build": "next build && yarn cp:assets",
"start": "cd .next/standalone/aksel.nav.no/website && node server.js",
@@ -27,26 +27,27 @@
},
"dependencies": {
"@sanity/block-content-to-react": "^3.0.0",
"@sanity/client": "^6.1.2",
"@sanity/client": "^6.1.3",
"@sanity/code-input": "4.1.1",
"@sanity/color-input": "^3.1.0",
"@sanity/icons": "2.3.1",
"@sanity/image-url": "1.0.2",
"@sanity/preview-kit": "^1.5.4",
"@sanity/table": "1.0.1",
"@sanity/ui": "^1.3.3",
"@sanity/vision": "3.11.5",
"@sanity/ui": "^1.3.0",
"@sanity/vision": "3.12.1",
"@svgr/webpack": "^5.5.0",
"amplitude-js": "^8.21.5",
"boring-avatars": "1.7.0",
"clsx": "^1.2.1",
"codesandbox": "^2.2.3",
"copy-to-clipboard": "^3.3.1",
"date-fns": "2.29.3",
"fuse.js": "^6.6.2",
"lodash": "^4.17.21",
"next": "^13.2.1",
"next-logger": "^3.0.2",
"next-sanity": "^4.3.3",
"pino": "^8.14.1",
"prism-react-renderer": "^1.3.0",
"prismjs": "^1.27.0",
"react": "^18.0.0",
@@ -54,7 +55,7 @@
"react-modal": "3.15.1",
"react-responsive-masonry": "^2.1.7",
"recharts": "^2.2.0",
"sanity": "3.11.5",
"sanity": "3.9.1",
"sanity-plugin-asset-source-unsplash": "^1.1.0",
"sanity-plugin-media": "^2.0.5",
"sharp": "0.31.3",
@@ -82,6 +83,7 @@
"jest": "^29.0.0",
"jest-environment-jsdom": "^29.0.0",
"json5": "2.2.1",
"pino-pretty": "^10.0.0",
"postcss": "^8.4.0",
"postcss-import": "^14.1.0",
"postcss-loader": "7.0.0",
+3
View File
@@ -1,3 +1,6 @@
// This is imported to allow the Output File Tracing feature of Next.js to work correctly with the log patcher
import "next-logger";
import { Html, Head, Main, NextScript } from "next/document";
export default function Document() {
Loaded 100 of 115 files, more files were not shown because too many files have changed in this diff. Show more