mirror of
https://github.com/navikt/aksel.git
synced 2026-10-14 17:52:02 +00:00
Compare commits
31
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5fec1f24f4 | ||
|
|
c27c1cfe26 | ||
|
|
5a649d2282 | ||
|
|
baac9cc251 | ||
|
|
d924d523e4 | ||
|
|
fc5a77081d | ||
|
|
1f3477a3e9 | ||
|
|
925450cb73 | ||
|
|
534a6a3b40 | ||
|
|
51eee7f011 | ||
|
|
909ae6aed1 | ||
|
|
cd3859a91e | ||
|
|
338f14e7ac | ||
|
|
aed1d082c3 | ||
|
|
31e9226b55 | ||
|
|
fe15b4d275 | ||
|
|
28e095d5a2 | ||
|
|
43377841ba | ||
|
|
2976ee9626 | ||
|
|
23c135b437 | ||
|
|
7f0498f250 | ||
|
|
5075c8d0a6 | ||
|
|
792eeaba42 | ||
|
|
0c7fc04935 | ||
|
|
0bb339e54a | ||
|
|
f5eb3adaea | ||
|
|
77cf71f814 | ||
|
|
f8c12b0757 | ||
|
|
be8265e13e | ||
|
|
0c7d6fb461 | ||
|
|
b97ac03c5a |
No files matched your search
@@ -0,0 +1,22 @@
|
||||
name: Eksempel på bruk
|
||||
description: Send et eksempel på ting fra Aksel som er brukt i det fri (tokens, komponenter, maler, etc)
|
||||
title: "[Eksempel på bruk]: "
|
||||
labels: ["dokumentasjon 📖"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Takk for at du bidrar til Aksel!
|
||||
- type: input
|
||||
id: title
|
||||
attributes:
|
||||
label: Lenke til side
|
||||
description: Lenke til siden hvor eksemplet er
|
||||
placeholder:
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: examples
|
||||
attributes:
|
||||
label: Forklaring
|
||||
description: Forklar om det er noe man må gjøre for å se eksemplet, eller hvor på siden det ligger
|
||||
@@ -1,5 +1,13 @@
|
||||
# @navikt/aksel-icons
|
||||
|
||||
## 5.9.2
|
||||
|
||||
## 5.9.1
|
||||
|
||||
## 5.9.0
|
||||
|
||||
## 5.8.0
|
||||
|
||||
## 5.7.6
|
||||
|
||||
## 5.7.5
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel-icons",
|
||||
"version": "5.7.6",
|
||||
"version": "5.9.2",
|
||||
"author": "Aksel | NAV",
|
||||
"homepage": "https://aksel.nav.no/ikoner",
|
||||
"repository": {
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @navikt/aksel-stylelint
|
||||
|
||||
## 5.9.2
|
||||
|
||||
## 5.9.1
|
||||
|
||||
## 5.9.0
|
||||
|
||||
## 5.8.0
|
||||
|
||||
## 5.7.6
|
||||
|
||||
## 5.7.5
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel-stylelint",
|
||||
"version": "5.7.6",
|
||||
"version": "5.9.2",
|
||||
"author": "Aksel | NAV",
|
||||
"homepage": "https://aksel.nav.no/grunnleggende/kode/stylelint",
|
||||
"repository": {
|
||||
@@ -35,8 +35,8 @@
|
||||
"dev": "yarn watch:lint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@navikt/ds-css": "^5.7.6",
|
||||
"@navikt/ds-tokens": "^5.7.6",
|
||||
"@navikt/ds-css": "^5.9.2",
|
||||
"@navikt/ds-tokens": "^5.9.2",
|
||||
"@types/jest": "^29.0.0",
|
||||
"concurrently": "7.2.1",
|
||||
"copyfiles": "2.4.1",
|
||||
|
||||
@@ -1,5 +1,33 @@
|
||||
# @navikt/aksel
|
||||
|
||||
## 5.9.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-css@5.9.2
|
||||
|
||||
## 5.9.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`925450cb7`](https://github.com/navikt/aksel/commit/925450cb73cd792cd3b6ad3ebf9f38ba022ee4df)]:
|
||||
- @navikt/ds-css@5.9.1
|
||||
|
||||
## 5.9.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`51eee7f01`](https://github.com/navikt/aksel/commit/51eee7f011aa016973bb952313cf65a1c55aa382), [`51eee7f01`](https://github.com/navikt/aksel/commit/51eee7f011aa016973bb952313cf65a1c55aa382), [`51eee7f01`](https://github.com/navikt/aksel/commit/51eee7f011aa016973bb952313cf65a1c55aa382), [`51eee7f01`](https://github.com/navikt/aksel/commit/51eee7f011aa016973bb952313cf65a1c55aa382)]:
|
||||
- @navikt/ds-css@5.9.0
|
||||
|
||||
## 5.8.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`0c7fc0493`](https://github.com/navikt/aksel/commit/0c7fc04935b96946b8ed1178c700077b6a1fcefd)]:
|
||||
- @navikt/ds-css@5.8.0
|
||||
|
||||
## 5.7.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel",
|
||||
"version": "5.7.6",
|
||||
"version": "5.9.2",
|
||||
"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": "5.7.6",
|
||||
"@navikt/ds-css": "5.9.2",
|
||||
"axios": "1.3.6",
|
||||
"chalk": "4.1.0",
|
||||
"clipboardy": "^2.3.0",
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
# @navikt/ds-css
|
||||
|
||||
## 5.9.2
|
||||
|
||||
## 5.9.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- :lipstick: Modal: Bedre håndtering av mobiler i landskapsmodus ([#2444](https://github.com/navikt/aksel/pull/2444))
|
||||
|
||||
## 5.9.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Datepicker/Monthpicker: Hvis man bruker komponentene i Modal vil Popover bli erstattet med Modal uansett om man er på desktop eller mobil. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
- Datepicker/Monthpicker/Popover: Fjernet `bubbleEscape`-prop. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
- useDatepicker/useMonthPicker/useRangedpicker: Fjernet `openOnFocus`-prop, kan nå bare åpnes ved klikk på date-knapp i input. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
- Datepicker/Monthpicker: Bytter nå automatisk til Modalvisning på mobil. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
## 5.8.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- :bug: Modal: Bedre støtte for Tooltip i Modal ([#2429](https://github.com/navikt/aksel/pull/2429))
|
||||
|
||||
## 5.7.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
+26
-18
@@ -149,18 +149,6 @@
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
/* Focus layering */
|
||||
.navds-date__field-input:focus-visible,
|
||||
.navds-date__field-button {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@supports not selector(:focus-visible) {
|
||||
.navds-date__field-input:focus {
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.navds-date .rdp-day_selected,
|
||||
.navds-monthpicker__month--selected {
|
||||
color: var(--ac-date-selected-text, var(--a-text-on-action));
|
||||
@@ -201,11 +189,11 @@
|
||||
}
|
||||
|
||||
.navds-date__field-input {
|
||||
padding-right: var(--a-spacing-14);
|
||||
padding-right: var(--a-spacing-12);
|
||||
}
|
||||
|
||||
.navds-form-field--small .navds-date__field-input {
|
||||
padding-right: var(--a-spacing-10);
|
||||
padding-right: var(--a-spacing-8);
|
||||
}
|
||||
|
||||
/* Error-handling */
|
||||
@@ -282,10 +270,6 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.navds-date__popover:where(.navds-popover) {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Readonly */
|
||||
.navds-date__field--readonly .navds-date__field-button {
|
||||
cursor: default;
|
||||
@@ -310,11 +294,35 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.navds-date__modal.navds-date {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.navds-date__modal-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
padding: var(--a-spacing-4);
|
||||
gap: var(--a-spacing-2);
|
||||
}
|
||||
|
||||
.navds-date__modal-body > .navds-date {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.navds-date__popover:where(.navds-popover) {
|
||||
border: none;
|
||||
}
|
||||
|
||||
@media (min-width: 480px) {
|
||||
.navds-date {
|
||||
padding: var(--a-spacing-5) var(--a-spacing-4);
|
||||
}
|
||||
|
||||
.navds-date__modal-body {
|
||||
padding: var(--a-spacing-6);
|
||||
}
|
||||
|
||||
.navds-date__caption {
|
||||
gap: var(--a-spacing-2);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
.navds-modal--polyfilled {
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
overflow: auto;
|
||||
|
||||
/* From polyfill (dialog-polyfill/dist/dialog-polyfill.css): */
|
||||
left: 0;
|
||||
@@ -33,6 +32,10 @@
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.navds-modal--polyfilled .navds-modal--polyfilled {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.navds-modal--polyfilled:not([open]) {
|
||||
display: none; /* from polyfill */
|
||||
}
|
||||
@@ -56,12 +59,17 @@
|
||||
|
||||
@media (min-width: 480px) {
|
||||
.navds-modal {
|
||||
max-width: calc((100% - 6px) - 2em);
|
||||
max-height: calc((100% - 6px) - 2em);
|
||||
max-width: calc(100% - 6px - 2em);
|
||||
}
|
||||
|
||||
.navds-modal--autowidth {
|
||||
max-width: min(700px, calc((100% - 6px) - 2em));
|
||||
max-width: min(700px, calc(100% - 6px - 2em));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-height: 480px) {
|
||||
.navds-modal {
|
||||
max-height: calc(100% - 6px - 2em);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,6 +135,14 @@
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* When Datepicker is used nested inside a Modal */
|
||||
.navds-modal--polyfilled .navds-modal--polyfilled.navds-date__nested-modal {
|
||||
min-width: fit-content;
|
||||
max-width: 100vw;
|
||||
max-height: 100vh;
|
||||
animation: none;
|
||||
}
|
||||
|
||||
@keyframes akselModalFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-css",
|
||||
"version": "5.7.6",
|
||||
"version": "5.9.2",
|
||||
"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": "^5.7.6",
|
||||
"@navikt/ds-tokens": "^5.9.2",
|
||||
"cssnano": "6.0.0",
|
||||
"fast-glob": "3.2.11",
|
||||
"lodash": "4.17.21",
|
||||
|
||||
@@ -1,5 +1,61 @@
|
||||
# @navikt/ds-react
|
||||
|
||||
## 5.9.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- :bug: Datepicker lukker ikke lenger modalen den er i ([#2451](https://github.com/navikt/aksel/pull/2451))
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-tokens@5.9.2
|
||||
- @navikt/aksel-icons@5.9.2
|
||||
|
||||
## 5.9.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-tokens@5.9.1
|
||||
- @navikt/aksel-icons@5.9.1
|
||||
|
||||
## 5.9.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Datepicker/Monthpicker: Hvis man bruker komponentene i Modal vil Popover bli erstattet med Modal uansett om man er på desktop eller mobil. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
- Datepicker/Monthpicker/Popover: Fjernet `bubbleEscape`-prop. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
- useDatepicker/useMonthPicker/useRangedpicker: Fjernet `openOnFocus`-prop, kan nå bare åpnes ved klikk på date-knapp i input. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
- Datepicker/Monthpicker: Bytter nå automatisk til Modalvisning på mobil. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Added useVirtualFocus hook - used in Combobox for now ([#2394](https://github.com/navikt/aksel/pull/2394))
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-tokens@5.9.0
|
||||
- @navikt/aksel-icons@5.9.0
|
||||
|
||||
## 5.8.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- :sparkles: Modal: Støtte for å lukke ved klikk utenfor ([#2386](https://github.com/navikt/aksel/pull/2386))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Autocomplete in combobox will not change formatting of the letters while being typed, but will use the casing of the autocompleted word when selecting the option. ([#2207](https://github.com/navikt/aksel/pull/2207))
|
||||
|
||||
- Accordion: Fikset dom-validering ved bruk av `div` i `button`. ([#2426](https://github.com/navikt/aksel/pull/2426))
|
||||
|
||||
- :bug: Modal: Bedre støtte for Tooltip i Modal ([#2429](https://github.com/navikt/aksel/pull/2429))
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-tokens@5.8.0
|
||||
- @navikt/aksel-icons@5.8.0
|
||||
|
||||
## 5.7.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-react",
|
||||
"version": "5.7.6",
|
||||
"version": "5.9.2",
|
||||
"description": "Aksel react-components for NAV designsystem",
|
||||
"author": "Aksel | NAV designsystem team",
|
||||
"license": "MIT",
|
||||
@@ -38,8 +38,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "0.25.4",
|
||||
"@navikt/aksel-icons": "^5.7.6",
|
||||
"@navikt/ds-tokens": "^5.7.6",
|
||||
"@navikt/aksel-icons": "^5.9.2",
|
||||
"@navikt/ds-tokens": "^5.9.2",
|
||||
"@radix-ui/react-tabs": "1.0.0",
|
||||
"@radix-ui/react-toggle-group": "1.0.0",
|
||||
"clsx": "^1.2.1",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { ChevronDownIcon } from "@navikt/aksel-icons";
|
||||
import cl from "clsx";
|
||||
import React, { forwardRef, useContext } from "react";
|
||||
import { Heading } from "../typography";
|
||||
import { AccordionContext } from "./AccordionContext";
|
||||
import { AccordionItemContext } from "./AccordionItem";
|
||||
import { ChevronDownIcon } from "@navikt/aksel-icons";
|
||||
|
||||
export interface AccordionHeaderProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
@@ -37,13 +37,13 @@ const AccordionHeader = forwardRef<HTMLButtonElement, AccordionHeaderProps>(
|
||||
aria-expanded={itemContext.open}
|
||||
type="button"
|
||||
>
|
||||
<div className="navds-accordion__icon-wrapper">
|
||||
<span className="navds-accordion__icon-wrapper">
|
||||
<ChevronDownIcon
|
||||
className="navds-accordion__header-chevron"
|
||||
title="Vis mer"
|
||||
aria-hidden
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
<Heading
|
||||
size={accordionContext?.headingSize ?? "small"}
|
||||
as="span"
|
||||
|
||||
@@ -13,13 +13,13 @@ interface DateContextContextProps {
|
||||
* Aria-connected ID
|
||||
*/
|
||||
ariaId?: string;
|
||||
/**
|
||||
* Flag for enabled-check
|
||||
*/
|
||||
defined: boolean;
|
||||
}
|
||||
|
||||
export const DateContext = createContext<DateContextContextProps>({
|
||||
open: false,
|
||||
onOpen: () => null,
|
||||
ariaId: undefined,
|
||||
});
|
||||
export const DateContext = createContext<DateContextContextProps | null>(null);
|
||||
|
||||
export const useDateInputContext = () => {
|
||||
const context = useContext(DateContext);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import cl from "clsx";
|
||||
import isWeekend from "date-fns/isWeekend";
|
||||
import React, { forwardRef, useRef, useState } from "react";
|
||||
import React, { forwardRef, useMemo, useRef, useState } from "react";
|
||||
import { DateRange, DayPicker, isMatch } from "react-day-picker";
|
||||
import { Popover } from "../../popover";
|
||||
import { omit, useId } from "../../util";
|
||||
import { DatePickerInput } from "../DateInput";
|
||||
import { mergeRefs, omit, useId } from "../../util";
|
||||
import { DateContext } from "../context";
|
||||
import { DatePickerInput } from "../parts/DateInput";
|
||||
import { DateWrapper } from "../parts/DateWrapper";
|
||||
import { getLocaleFromString, labels } from "../utils";
|
||||
import DatePickerStandalone from "./DatePickerStandalone";
|
||||
import Caption from "./parts/Caption";
|
||||
@@ -79,7 +79,6 @@ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(
|
||||
onClose,
|
||||
onOpenToggle,
|
||||
strategy,
|
||||
bubbleEscape = false,
|
||||
onWeekNumberClick,
|
||||
...rest
|
||||
},
|
||||
@@ -89,6 +88,7 @@ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(
|
||||
const [open, setOpen] = useState(_open ?? false);
|
||||
|
||||
const wrapperRef = useRef<HTMLDivElement | null>(null);
|
||||
const mergedRef = useMemo(() => mergeRefs([wrapperRef, ref]), [ref]);
|
||||
|
||||
const [selectedDates, setSelectedDates] = React.useState<
|
||||
Date | Date[] | DateRange | undefined
|
||||
@@ -110,6 +110,44 @@ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(
|
||||
rest?.onSelect?.(newSelected);
|
||||
};
|
||||
|
||||
const DatePickerComponent = (
|
||||
<DayPicker
|
||||
locale={getLocaleFromString(locale)}
|
||||
mode={mode}
|
||||
onSelect={handleSelect}
|
||||
selected={selected ?? selectedDates}
|
||||
components={{
|
||||
Caption: dropdownCaption ? DropdownCaption : Caption,
|
||||
Head: TableHead,
|
||||
HeadRow,
|
||||
WeekNumber,
|
||||
Row,
|
||||
Day: DayButton,
|
||||
}}
|
||||
className={cl("navds-date", className)}
|
||||
classNames={{
|
||||
vhidden: "navds-sr-only",
|
||||
}}
|
||||
disabled={(day) => {
|
||||
return (disableWeekends && isWeekend(day)) || isMatch(day, disabled);
|
||||
}}
|
||||
weekStartsOn={1}
|
||||
initialFocus={false}
|
||||
labels={labels as any}
|
||||
modifiers={{
|
||||
weekend: (day) => disableWeekends && isWeekend(day),
|
||||
}}
|
||||
modifiersClassNames={{
|
||||
weekend: "rdp-day__weekend",
|
||||
}}
|
||||
showWeekNumber={showWeekNumber}
|
||||
onWeekNumberClick={mode === "multiple" ? onWeekNumberClick : undefined}
|
||||
fixedWeeks
|
||||
showOutsideDays
|
||||
{...omit(rest, ["onSelect"])}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<DateContext.Provider
|
||||
value={{
|
||||
@@ -119,72 +157,27 @@ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(
|
||||
onOpenToggle?.();
|
||||
},
|
||||
ariaId,
|
||||
defined: true,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={wrapperRef}
|
||||
ref={mergedRef}
|
||||
className={cl("navds-date__wrapper", wrapperClassName)}
|
||||
>
|
||||
{children}
|
||||
{(_open ?? open) && (
|
||||
<Popover
|
||||
arrow={false}
|
||||
anchorEl={wrapperRef.current}
|
||||
open={_open ?? open}
|
||||
onClose={() => {
|
||||
onClose?.() ?? setOpen(false);
|
||||
}}
|
||||
placement="bottom-start"
|
||||
id={ariaId}
|
||||
role="dialog"
|
||||
ref={ref}
|
||||
strategy={strategy}
|
||||
className="navds-date__popover"
|
||||
bubbleEscape={bubbleEscape}
|
||||
flip={false}
|
||||
>
|
||||
<DayPicker
|
||||
locale={getLocaleFromString(locale)}
|
||||
mode={mode}
|
||||
onSelect={handleSelect}
|
||||
selected={selected ?? selectedDates}
|
||||
components={{
|
||||
Caption: dropdownCaption ? DropdownCaption : Caption,
|
||||
Head: TableHead,
|
||||
HeadRow,
|
||||
WeekNumber,
|
||||
Row,
|
||||
Day: DayButton,
|
||||
}}
|
||||
className={cl("navds-date", className)}
|
||||
classNames={{
|
||||
vhidden: "navds-sr-only",
|
||||
}}
|
||||
disabled={(day) => {
|
||||
return (
|
||||
(disableWeekends && isWeekend(day)) ||
|
||||
isMatch(day, disabled)
|
||||
);
|
||||
}}
|
||||
weekStartsOn={1}
|
||||
initialFocus={false}
|
||||
labels={labels as any}
|
||||
modifiers={{
|
||||
weekend: (day) => disableWeekends && isWeekend(day),
|
||||
}}
|
||||
modifiersClassNames={{
|
||||
weekend: "rdp-day__weekend",
|
||||
}}
|
||||
showWeekNumber={showWeekNumber}
|
||||
onWeekNumberClick={
|
||||
mode === "multiple" ? onWeekNumberClick : undefined
|
||||
}
|
||||
fixedWeeks
|
||||
showOutsideDays
|
||||
{...omit(rest, ["onSelect"])}
|
||||
/>
|
||||
</Popover>
|
||||
)}
|
||||
<DateWrapper
|
||||
open={_open ?? open}
|
||||
anchor={wrapperRef.current}
|
||||
onClose={() => onClose?.() ?? setOpen(false)}
|
||||
locale={locale}
|
||||
variant={mode}
|
||||
popoverProps={{
|
||||
id: ariaId,
|
||||
strategy,
|
||||
}}
|
||||
>
|
||||
{DatePickerComponent}
|
||||
</DateWrapper>
|
||||
</div>
|
||||
</DateContext.Provider>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Meta, StoryObj } from "@storybook/react";
|
||||
import isSameDay from "date-fns/isSameDay";
|
||||
import React, { useId, useState } from "react";
|
||||
import { useDatepicker, useRangeDatepicker } from "..";
|
||||
import { BodyLong, Button, HGrid, HStack, Modal, VStack } from "../..";
|
||||
import { BodyLong, Button, HGrid, Modal, VStack } from "../..";
|
||||
import DatePicker, { DatePickerProps } from "./DatePicker";
|
||||
|
||||
const disabledDays = [
|
||||
@@ -18,7 +18,6 @@ export default {
|
||||
|
||||
type DefaultStoryProps = DatePickerProps & {
|
||||
size: "medium" | "small";
|
||||
openOnFocus: boolean;
|
||||
inputfield: boolean;
|
||||
standalone: boolean;
|
||||
};
|
||||
@@ -30,13 +29,11 @@ export const Default: StoryObj<DefaultStoryProps> = {
|
||||
const rangeCtx = useRangeDatepicker({
|
||||
fromDate: new Date("Aug 23 2020"),
|
||||
toDate: new Date("Aug 23 2023"),
|
||||
openOnFocus: props.openOnFocus,
|
||||
});
|
||||
|
||||
const singleCtx = useDatepicker({
|
||||
fromDate: new Date("Aug 23 2020"),
|
||||
toDate: new Date("Aug 23 2023"),
|
||||
openOnFocus: props.openOnFocus,
|
||||
});
|
||||
|
||||
const newProps = {
|
||||
@@ -111,7 +108,6 @@ export const Default: StoryObj<DefaultStoryProps> = {
|
||||
disableWeekends: false,
|
||||
showWeekNumber: false,
|
||||
mode: "single",
|
||||
openOnFocus: true,
|
||||
inputfield: true,
|
||||
standalone: false,
|
||||
},
|
||||
@@ -186,21 +182,6 @@ export const UseRangedDatepicker = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const OpenOnFocus = () => {
|
||||
const { datepickerProps, inputProps } = useDatepicker({
|
||||
onDateChange: console.log,
|
||||
openOnFocus: false,
|
||||
});
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", gap: "1rem" }}>
|
||||
<DatePicker {...datepickerProps}>
|
||||
<DatePicker.Input {...inputProps} label="Velg dato" />
|
||||
</DatePicker>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const NB = () => (
|
||||
<DatePicker.Standalone locale="nb" today={new Date("2006-07-01")} />
|
||||
);
|
||||
@@ -333,15 +314,32 @@ export const Size = () => {
|
||||
fromDate: new Date("Aug 23 2019"),
|
||||
toDate: new Date("Feb 23 2024"),
|
||||
onDateChange: console.log,
|
||||
defaultSelected: new Date("Feb 23 2023"),
|
||||
});
|
||||
const { datepickerProps: d2, inputProps: i2 } = useDatepicker({
|
||||
fromDate: new Date("Aug 23 2019"),
|
||||
toDate: new Date("Feb 23 2024"),
|
||||
onDateChange: console.log,
|
||||
defaultSelected: new Date("Feb 23 2023"),
|
||||
});
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", gap: "1rem" }}>
|
||||
<DatePicker {...datepickerProps} dropdownCaption>
|
||||
<DatePicker.Input size="medium" {...inputProps} label="Velg dato" />
|
||||
<DatePicker.Input
|
||||
placeholder="10/10/2023"
|
||||
size="medium"
|
||||
{...inputProps}
|
||||
label="Velg dato"
|
||||
/>
|
||||
</DatePicker>
|
||||
<DatePicker {...datepickerProps} dropdownCaption>
|
||||
<DatePicker.Input size="small" {...inputProps} label="Velg dato" />
|
||||
<DatePicker {...d2} dropdownCaption>
|
||||
<DatePicker.Input
|
||||
placeholder="10/10/2023"
|
||||
size="small"
|
||||
{...i2}
|
||||
label="Velg dato"
|
||||
/>
|
||||
</DatePicker>
|
||||
</div>
|
||||
);
|
||||
@@ -439,35 +437,36 @@ export const ModalDemo = () => {
|
||||
toDate: new Date("Feb 23 2024"),
|
||||
onDateChange: console.log,
|
||||
});
|
||||
const [open, setOpen] = useState(true);
|
||||
|
||||
return (
|
||||
<Modal open header={{ heading: "Modal-demo" }} width="1024px">
|
||||
<Modal.Body style={{ position: "relative" }}>
|
||||
<BodyLong>
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Maiores nisi
|
||||
incidunt ipsum cupiditate nostrum nesciunt, corrupti nihil at atque
|
||||
animi ab aut. Quam iusto harum eligendi magnam nulla repudiandae
|
||||
molestias.
|
||||
</BodyLong>
|
||||
<>
|
||||
<Button onClick={() => setOpen(!open)}>Open modal</Button>
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
header={{ heading: "Modal-demo" }}
|
||||
>
|
||||
<Modal.Body style={{ position: "relative" }}>
|
||||
<BodyLong spacing>
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit.
|
||||
</BodyLong>
|
||||
|
||||
<HGrid gap="6" columns={{ xs: 1, lg: 2 }}>
|
||||
<HStack justify="center" align="start">
|
||||
<DatePicker.Standalone
|
||||
fromDate={new Date("Aug 23 2019")}
|
||||
toDate={new Date("Feb 23 2024")}
|
||||
/>
|
||||
</HStack>
|
||||
<DatePicker {...datepickerProps} dropdownCaption>
|
||||
<DatePicker.Input {...inputProps} label="Velg dato" />
|
||||
</DatePicker>
|
||||
<HStack justify="center">
|
||||
<DatePicker.Standalone
|
||||
fromDate={new Date("Aug 23 2019")}
|
||||
toDate={new Date("Feb 23 2024")}
|
||||
<DatePicker.Input
|
||||
{...inputProps}
|
||||
label="Velg dato"
|
||||
description="Format: dd.mm.yyyy"
|
||||
/>
|
||||
</HStack>
|
||||
</HGrid>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
</DatePicker>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button>Neste</Button>
|
||||
<Button variant="secondary">Tilbake</Button>
|
||||
<Button variant="tertiary">Avbryt</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
ModalDemo.parameters = { chromatic: { pauseAnimationAtEnd: true } };
|
||||
@@ -101,9 +101,4 @@ export interface DatePickerDefaultProps
|
||||
* @default See Popover
|
||||
*/
|
||||
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;
|
||||
}
|
||||
@@ -1,17 +1,15 @@
|
||||
import differenceInCalendarDays from "date-fns/differenceInCalendarDays";
|
||||
import isWeekend from "date-fns/isWeekend";
|
||||
import React, { useCallback, useRef, useState } from "react";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { DayClickEventHandler, isMatch } from "react-day-picker";
|
||||
import { DateInputProps } from "../DateInput";
|
||||
import { DatePickerProps } from "../datepicker/DatePicker";
|
||||
import { DateInputProps } from "../parts/DateInput";
|
||||
import {
|
||||
formatDateForInput,
|
||||
getLocaleFromString,
|
||||
isValidDate,
|
||||
parseDate,
|
||||
} from "../utils";
|
||||
import { useEscape } from "./useEscape";
|
||||
import { useOutsideClickHandler } from "./useOutsideClickHandler";
|
||||
|
||||
export interface UseDatepickerOptions
|
||||
extends Pick<
|
||||
@@ -59,10 +57,10 @@ export interface UseDatepickerOptions
|
||||
*/
|
||||
allowTwoDigitYear?: boolean;
|
||||
/**
|
||||
* Opens datepicker on input-focus
|
||||
* @default true
|
||||
* Will be removed in a future major-version
|
||||
* @deprecated
|
||||
*/
|
||||
openOnFocus?: boolean;
|
||||
openOnFocus?: false;
|
||||
}
|
||||
|
||||
interface UseDatepickerValue {
|
||||
@@ -76,7 +74,14 @@ interface UseDatepickerValue {
|
||||
inputProps: Pick<
|
||||
DateInputProps,
|
||||
"onChange" | "onFocus" | "onBlur" | "value"
|
||||
> & { ref: React.RefObject<HTMLInputElement> };
|
||||
> & {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setAnchorRef: React.Dispatch<
|
||||
React.SetStateAction<HTMLButtonElement | null>
|
||||
>;
|
||||
};
|
||||
/**
|
||||
* Resets all states (callback)
|
||||
*/
|
||||
@@ -143,14 +148,11 @@ export const useDatepicker = (
|
||||
onValidate,
|
||||
defaultMonth,
|
||||
allowTwoDigitYear = true,
|
||||
openOnFocus = true,
|
||||
} = opt;
|
||||
|
||||
const [anchorRef, setAnchorRef] = useState<HTMLButtonElement | null>(null);
|
||||
const locale = getLocaleFromString(_locale);
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [daypickerRef, setDaypickerRef] = useState<HTMLDivElement>();
|
||||
|
||||
const [defaultSelected, setDefaultSelected] = useState(_defaultSelected);
|
||||
|
||||
// Initialize states
|
||||
@@ -172,14 +174,6 @@ export const useDatepicker = (
|
||||
[defaultMonth, defaultSelected, selectedDay, today]
|
||||
);
|
||||
|
||||
useOutsideClickHandler(open, handleOpen, [
|
||||
daypickerRef,
|
||||
inputRef.current,
|
||||
inputRef.current?.nextSibling,
|
||||
]);
|
||||
|
||||
useEscape(open, handleOpen, inputRef);
|
||||
|
||||
const updateDate = (date?: Date) => {
|
||||
onDateChange?.(date);
|
||||
setSelectedDay(date);
|
||||
@@ -207,7 +201,6 @@ export const useDatepicker = (
|
||||
if (e.target.readOnly) {
|
||||
return;
|
||||
}
|
||||
!open && openOnFocus && handleOpen(true);
|
||||
const day = parseDate(
|
||||
e.target.value,
|
||||
today,
|
||||
@@ -243,7 +236,7 @@ export const useDatepicker = (
|
||||
const handleDayClick: DayClickEventHandler = (day, { selected }) => {
|
||||
if (day && !selected) {
|
||||
handleOpen(false);
|
||||
inputRef.current && inputRef.current.focus();
|
||||
anchorRef?.focus();
|
||||
}
|
||||
|
||||
if (!required && selected) {
|
||||
@@ -319,11 +312,13 @@ export const useDatepicker = (
|
||||
toDate,
|
||||
today,
|
||||
open,
|
||||
onClose: () => {
|
||||
handleOpen(false);
|
||||
anchorRef?.focus();
|
||||
},
|
||||
onOpenToggle: () => handleOpen(!open),
|
||||
disabled,
|
||||
disableWeekends,
|
||||
bubbleEscape: true,
|
||||
ref: setDaypickerRef,
|
||||
};
|
||||
|
||||
const inputProps = {
|
||||
@@ -331,7 +326,7 @@ export const useDatepicker = (
|
||||
onFocus: handleFocus,
|
||||
onBlur: handleBlur,
|
||||
value: inputValue,
|
||||
ref: inputRef,
|
||||
setAnchorRef,
|
||||
};
|
||||
|
||||
return { datepickerProps, inputProps, reset, selectedDay, setSelected };
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { useCallback, useEffect, RefObject } from "react";
|
||||
|
||||
export const useEscape = (
|
||||
open: boolean,
|
||||
setOpen: (openState: boolean) => void,
|
||||
focusRef: RefObject<HTMLElement>
|
||||
) => {
|
||||
const handleClose = useCallback(() => {
|
||||
setOpen(false);
|
||||
focusRef?.current && focusRef.current.focus();
|
||||
}, [focusRef, setOpen]);
|
||||
|
||||
const escape = useCallback(
|
||||
(event: KeyboardEvent) => {
|
||||
if (open && event.key === "Escape") {
|
||||
event.preventDefault(); // This prevents modal from closing when using datepicker inside modal
|
||||
handleClose();
|
||||
}
|
||||
},
|
||||
[handleClose, open]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("keydown", escape, false);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", escape, false);
|
||||
};
|
||||
}, [escape]);
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useMemo, useRef, useState } from "react";
|
||||
import { DateInputProps } from "../DateInput";
|
||||
import React, { useCallback, useMemo, useState } from "react";
|
||||
import { MonthPickerProps } from "../monthpicker/types";
|
||||
import { DateInputProps } from "../parts/DateInput";
|
||||
import {
|
||||
formatDateForInput,
|
||||
getLocaleFromString,
|
||||
@@ -8,8 +8,6 @@ import {
|
||||
isValidDate,
|
||||
parseDate,
|
||||
} from "../utils";
|
||||
import { useEscape } from "./useEscape";
|
||||
import { useOutsideClickHandler } from "./useOutsideClickHandler";
|
||||
|
||||
export interface UseMonthPickerOptions
|
||||
extends Pick<
|
||||
@@ -45,8 +43,8 @@ export interface UseMonthPickerOptions
|
||||
*/
|
||||
allowTwoDigitYear?: boolean;
|
||||
/**
|
||||
* Opens datepicker on input-focus
|
||||
* @default true
|
||||
* Will be removed in a future major-version
|
||||
* @deprecated
|
||||
*/
|
||||
openOnFocus?: boolean;
|
||||
}
|
||||
@@ -60,7 +58,12 @@ interface UseMonthPickerValue {
|
||||
* Use: <MonthPicker.Input {...inputProps} />
|
||||
*/
|
||||
inputProps: Pick<DateInputProps, "onChange" | "onFocus" | "value"> & {
|
||||
ref: React.RefObject<HTMLInputElement>;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setAnchorRef: React.Dispatch<
|
||||
React.SetStateAction<HTMLButtonElement | null>
|
||||
>;
|
||||
};
|
||||
/**
|
||||
* Currently selected Date
|
||||
@@ -138,17 +141,14 @@ export const useMonthpicker = (
|
||||
onValidate,
|
||||
defaultYear,
|
||||
allowTwoDigitYear = true,
|
||||
openOnFocus = true,
|
||||
} = opt;
|
||||
|
||||
const [anchorRef, setAnchorRef] = useState<HTMLButtonElement | null>(null);
|
||||
const [defaultSelected, setDefaultSelected] = useState(_defaultSelected);
|
||||
|
||||
const today = useMemo(() => new Date(), []);
|
||||
const locale = getLocaleFromString(_locale);
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [monthpickerRef, setMonthpickerRef] = useState<HTMLDivElement>();
|
||||
|
||||
// Initialize states
|
||||
const [year, setYear] = useState(defaultSelected ?? defaultYear ?? today);
|
||||
const [selectedMonth, setSelectedMonth] = useState(defaultSelected);
|
||||
@@ -169,14 +169,6 @@ export const useMonthpicker = (
|
||||
[defaultSelected, defaultYear, selectedMonth, today]
|
||||
);
|
||||
|
||||
useOutsideClickHandler(open, handleOpen, [
|
||||
monthpickerRef,
|
||||
inputRef.current,
|
||||
inputRef.current?.nextSibling,
|
||||
]);
|
||||
|
||||
useEscape(open, handleOpen, inputRef);
|
||||
|
||||
const updateMonth = (date?: Date) => {
|
||||
onMonthChange?.(date);
|
||||
setSelectedMonth(date);
|
||||
@@ -204,7 +196,7 @@ export const useMonthpicker = (
|
||||
if (e.target.readOnly) {
|
||||
return;
|
||||
}
|
||||
!open && openOnFocus && handleOpen(true);
|
||||
|
||||
const day = parseDate(
|
||||
e.target.value,
|
||||
today,
|
||||
@@ -239,8 +231,8 @@ export const useMonthpicker = (
|
||||
const handleMonthClick = (month?: Date) => {
|
||||
if (month) {
|
||||
handleOpen(false);
|
||||
inputRef.current && inputRef.current.focus();
|
||||
setYear(month);
|
||||
anchorRef?.focus();
|
||||
}
|
||||
|
||||
if (!required && !month) {
|
||||
@@ -314,9 +306,11 @@ export const useMonthpicker = (
|
||||
toDate,
|
||||
open,
|
||||
onOpenToggle: () => handleOpen(!open),
|
||||
onClose: () => {
|
||||
handleOpen(false);
|
||||
anchorRef?.focus();
|
||||
},
|
||||
disabled,
|
||||
bubbleEscape: true,
|
||||
ref: setMonthpickerRef,
|
||||
};
|
||||
|
||||
const inputProps = {
|
||||
@@ -324,7 +318,7 @@ export const useMonthpicker = (
|
||||
onFocus: handleFocus,
|
||||
onBlur: handleBlur,
|
||||
value: inputValue,
|
||||
ref: inputRef,
|
||||
setAnchorRef,
|
||||
};
|
||||
|
||||
return { monthpickerProps, inputProps, reset, selectedMonth, setSelected };
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import { useCallback, useEffect } from "react";
|
||||
|
||||
export const useOutsideClickHandler = (
|
||||
open: boolean,
|
||||
setOpen: (openState: boolean) => void,
|
||||
refs: Array<any>
|
||||
) => {
|
||||
const handleFocusIn = useCallback(
|
||||
(e) => {
|
||||
const composed = e.composedPath?.()?.[0];
|
||||
if (!e?.target || !e?.target?.nodeType || !composed) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!refs.some(
|
||||
(element) =>
|
||||
element?.contains(e.target) || element?.contains(composed)
|
||||
)
|
||||
) {
|
||||
open && setOpen(false);
|
||||
}
|
||||
},
|
||||
[open, refs, setOpen]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("focusin", handleFocusIn);
|
||||
window.addEventListener("pointerdown", handleFocusIn);
|
||||
return () => {
|
||||
window?.removeEventListener?.("focusin", handleFocusIn);
|
||||
window?.removeEventListener?.("pointerdown", handleFocusIn);
|
||||
};
|
||||
}, [handleFocusIn]);
|
||||
};
|
||||
@@ -1,10 +1,10 @@
|
||||
import differenceInCalendarDays from "date-fns/differenceInCalendarDays";
|
||||
import checkIsBefore from "date-fns/isBefore";
|
||||
import isWeekend from "date-fns/isWeekend";
|
||||
import React, { useRef, useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
import { DateRange, isMatch } from "react-day-picker";
|
||||
import { DateInputProps } from "../DateInput";
|
||||
import { DatePickerProps } from "../datepicker/DatePicker";
|
||||
import { DateInputProps } from "../parts/DateInput";
|
||||
import {
|
||||
formatDateForInput,
|
||||
getLocaleFromString,
|
||||
@@ -12,8 +12,6 @@ import {
|
||||
parseDate,
|
||||
} from "../utils";
|
||||
import { DateValidationT, UseDatepickerOptions } from "./useDatepicker";
|
||||
import { useEscape } from "./useEscape";
|
||||
import { useOutsideClickHandler } from "./useOutsideClickHandler";
|
||||
|
||||
export type RangeValidationT = {
|
||||
from: DateValidationT;
|
||||
@@ -74,14 +72,28 @@ interface UseRangeDatepickerValue {
|
||||
fromInputProps: Pick<
|
||||
DateInputProps,
|
||||
"onChange" | "onFocus" | "onBlur" | "value"
|
||||
> & { ref: React.RefObject<HTMLInputElement> };
|
||||
> & {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setAnchorRef: React.Dispatch<
|
||||
React.SetStateAction<HTMLButtonElement | null>
|
||||
>;
|
||||
};
|
||||
/**
|
||||
* Use: <DatePicker.Input label="to" {...toInputProps}/>
|
||||
*/
|
||||
toInputProps: Pick<
|
||||
DateInputProps,
|
||||
"onChange" | "onFocus" | "onBlur" | "value"
|
||||
> & { ref: React.RefObject<HTMLInputElement> };
|
||||
> & {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setAnchorRef?: React.Dispatch<
|
||||
React.SetStateAction<HTMLButtonElement | null>
|
||||
>;
|
||||
};
|
||||
/**
|
||||
* Resets all states (callback)
|
||||
*/
|
||||
@@ -218,14 +230,11 @@ export const useRangeDatepicker = (
|
||||
onValidate,
|
||||
defaultMonth,
|
||||
allowTwoDigitYear = true,
|
||||
openOnFocus = true,
|
||||
} = opt;
|
||||
|
||||
const locale = getLocaleFromString(_locale);
|
||||
const [anchorRef, setAnchorRef] = useState<HTMLButtonElement | null>(null);
|
||||
|
||||
const inputRefTo = useRef<HTMLInputElement>(null);
|
||||
const inputRefFrom = useRef<HTMLInputElement>(null);
|
||||
const [daypickerRef, setDaypickerRef] = useState<HTMLDivElement>();
|
||||
const locale = getLocaleFromString(_locale);
|
||||
|
||||
const [defaultSelected, setDefaultSelected] = useState(_defaultSelected);
|
||||
|
||||
@@ -255,20 +264,6 @@ export const useRangeDatepicker = (
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
useOutsideClickHandler(open, setOpen, [
|
||||
daypickerRef,
|
||||
inputRefTo.current,
|
||||
inputRefFrom.current,
|
||||
inputRefTo.current?.nextSibling,
|
||||
inputRefFrom.current?.nextSibling,
|
||||
]);
|
||||
|
||||
useEscape(
|
||||
open,
|
||||
setOpen,
|
||||
selectedRange?.from && !selectedRange?.to ? inputRefTo : inputRefFrom
|
||||
);
|
||||
|
||||
const updateRange = (range?: DateRange) => {
|
||||
onRangeChange?.(range);
|
||||
setSelectedRange(range);
|
||||
@@ -324,7 +319,6 @@ export const useRangeDatepicker = (
|
||||
if (e.target.readOnly) {
|
||||
return;
|
||||
}
|
||||
!open && openOnFocus && setOpen(true);
|
||||
const day = parseDate(
|
||||
e.target.value,
|
||||
today,
|
||||
@@ -378,6 +372,7 @@ export const useRangeDatepicker = (
|
||||
const handleSelect = (range) => {
|
||||
if (range?.from && range?.to) {
|
||||
setOpen(false);
|
||||
anchorRef?.focus();
|
||||
}
|
||||
const prevToRange =
|
||||
!selectedRange?.from && selectedRange?.to ? selectedRange?.to : range?.to;
|
||||
@@ -545,10 +540,12 @@ export const useRangeDatepicker = (
|
||||
mode: "range" as const,
|
||||
open,
|
||||
onOpenToggle: () => setOpen((x) => !x),
|
||||
onClose: () => {
|
||||
setOpen(false);
|
||||
anchorRef?.focus();
|
||||
},
|
||||
disabled,
|
||||
disableWeekends,
|
||||
bubbleEscape: true,
|
||||
ref: setDaypickerRef,
|
||||
};
|
||||
|
||||
const fromInputProps = {
|
||||
@@ -556,7 +553,7 @@ export const useRangeDatepicker = (
|
||||
onFocus: (e) => handleFocus(e, RANGE.FROM),
|
||||
onBlur: (e) => handleBlur(e, RANGE.FROM),
|
||||
value: fromInputValue,
|
||||
ref: inputRefFrom,
|
||||
setAnchorRef,
|
||||
};
|
||||
|
||||
const toInputProps = {
|
||||
@@ -564,7 +561,7 @@ export const useRangeDatepicker = (
|
||||
onFocus: (e) => handleFocus(e, RANGE.TO),
|
||||
onBlur: (e) => handleBlur(e, RANGE.TO),
|
||||
value: toInputValue,
|
||||
ref: inputRefTo,
|
||||
setAnchorRef,
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export { type DateInputProps } from "./DateInput";
|
||||
export {
|
||||
default as DatePicker,
|
||||
type DatePickerProps,
|
||||
@@ -15,3 +14,4 @@ export {
|
||||
export { default as MonthPicker } from "./monthpicker/MonthPicker";
|
||||
export { type MonthPickerStandaloneProps } from "./monthpicker/MonthPickerStandalone";
|
||||
export { type MonthPickerProps } from "./monthpicker/types";
|
||||
export { type DateInputProps } from "./parts/DateInput";
|
||||
@@ -1,10 +1,10 @@
|
||||
import cl from "clsx";
|
||||
import React, { forwardRef, useRef, useState } from "react";
|
||||
import React, { forwardRef, useMemo, useRef, useState } from "react";
|
||||
import { RootProvider } from "react-day-picker";
|
||||
import { Popover } from "../../popover";
|
||||
import { useId } from "../../util";
|
||||
import { MonthPickerInput } from "../DateInput";
|
||||
import { mergeRefs, useId } from "../../util";
|
||||
import { DateContext, SharedMonthProvider } from "../context";
|
||||
import { MonthPickerInput } from "../parts/DateInput";
|
||||
import { DateWrapper } from "../parts/DateWrapper";
|
||||
import { getLocaleFromString } from "../utils";
|
||||
import MonthCaption from "./MonthCaption";
|
||||
import MonthPickerStandalone from "./MonthPickerStandalone";
|
||||
@@ -74,7 +74,6 @@ export const MonthPicker = forwardRef<HTMLDivElement, MonthPickerProps>(
|
||||
year,
|
||||
onYearChange,
|
||||
strategy = "absolute",
|
||||
bubbleEscape = false,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
@@ -82,6 +81,7 @@ export const MonthPicker = forwardRef<HTMLDivElement, MonthPickerProps>(
|
||||
const [open, setOpen] = useState(_open ?? false);
|
||||
|
||||
const wrapperRef = useRef<HTMLDivElement | null>(null);
|
||||
const mergedRef = useMemo(() => mergeRefs([wrapperRef, ref]), [ref]);
|
||||
|
||||
const [selectedMonth, setSelectedMonth] = useState<Date | undefined>(
|
||||
defaultSelected
|
||||
@@ -107,51 +107,47 @@ export const MonthPicker = forwardRef<HTMLDivElement, MonthPickerProps>(
|
||||
onOpenToggle?.();
|
||||
},
|
||||
ariaId,
|
||||
defined: true,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={wrapperRef}
|
||||
ref={mergedRef}
|
||||
className={cl("navds-date__wrapper", wrapperClassName)}
|
||||
>
|
||||
{children}
|
||||
{(_open ?? open) && (
|
||||
<Popover
|
||||
arrow={false}
|
||||
anchorEl={wrapperRef.current}
|
||||
open={_open ?? open}
|
||||
onClose={() => onClose?.() ?? setOpen(false)}
|
||||
placement="bottom-start"
|
||||
role="dialog"
|
||||
ref={ref}
|
||||
id={ariaId}
|
||||
className="navds-date navds-date__popover"
|
||||
strategy={strategy}
|
||||
bubbleEscape={bubbleEscape}
|
||||
flip={false}
|
||||
<DateWrapper
|
||||
open={_open ?? open}
|
||||
anchor={wrapperRef.current}
|
||||
onClose={() => onClose?.() ?? setOpen(false)}
|
||||
locale={locale}
|
||||
variant="month"
|
||||
popoverProps={{
|
||||
id: ariaId,
|
||||
strategy,
|
||||
}}
|
||||
>
|
||||
<RootProvider
|
||||
locale={getLocaleFromString(locale)}
|
||||
selected={selected ?? selectedMonth}
|
||||
toDate={toDate}
|
||||
fromDate={fromDate}
|
||||
month={selected ?? selectedMonth}
|
||||
>
|
||||
<RootProvider
|
||||
locale={getLocaleFromString(locale)}
|
||||
selected={selected ?? selectedMonth}
|
||||
toDate={toDate}
|
||||
fromDate={fromDate}
|
||||
month={selected ?? selectedMonth}
|
||||
>
|
||||
<div className={cl("rdp-month", className)}>
|
||||
<SharedMonthProvider
|
||||
dropdownCaption={dropdownCaption}
|
||||
disabled={disabled}
|
||||
selected={selected ?? selectedMonth}
|
||||
onSelect={handleSelect}
|
||||
year={year}
|
||||
onYearChange={onYearChange}
|
||||
>
|
||||
<MonthCaption />
|
||||
<MonthSelector />
|
||||
</SharedMonthProvider>
|
||||
</div>
|
||||
</RootProvider>
|
||||
</Popover>
|
||||
)}
|
||||
<div className={cl("rdp-month", className)}>
|
||||
<SharedMonthProvider
|
||||
dropdownCaption={dropdownCaption}
|
||||
disabled={disabled}
|
||||
selected={selected ?? selectedMonth}
|
||||
onSelect={handleSelect}
|
||||
year={year}
|
||||
onYearChange={onYearChange}
|
||||
>
|
||||
<MonthCaption />
|
||||
<MonthSelector />
|
||||
</SharedMonthProvider>
|
||||
</div>
|
||||
</RootProvider>
|
||||
</DateWrapper>
|
||||
</div>
|
||||
</DateContext.Provider>
|
||||
);
|
||||
|
||||
@@ -75,9 +75,4 @@ 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;
|
||||
}
|
||||
+23
-12
@@ -1,11 +1,11 @@
|
||||
import { CalendarIcon } from "@navikt/aksel-icons";
|
||||
import cl from "clsx";
|
||||
import React, { forwardRef, InputHTMLAttributes } from "react";
|
||||
import { FormFieldProps, useFormField } from "../form/useFormField";
|
||||
import { useDateInputContext } from "./context";
|
||||
import { ReadOnlyIcon } from "../form/ReadOnlyIcon";
|
||||
import { BodyShort, ErrorMessage, Label } from "../typography";
|
||||
import { omit } from "../util";
|
||||
import React, { forwardRef, InputHTMLAttributes, useRef } from "react";
|
||||
import { ReadOnlyIcon } from "../../form/ReadOnlyIcon";
|
||||
import { FormFieldProps, useFormField } from "../../form/useFormField";
|
||||
import { BodyShort, ErrorMessage, Label } from "../../typography";
|
||||
import { omit } from "../../util";
|
||||
import { useDateInputContext } from "../context";
|
||||
|
||||
export interface DateInputProps
|
||||
extends FormFieldProps,
|
||||
@@ -28,6 +28,10 @@ export interface DateInputProps
|
||||
* @private
|
||||
*/
|
||||
variant?: "datepicker" | "monthpicker";
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setAnchorRef?: React.Dispatch<React.SetStateAction<HTMLButtonElement | null>>;
|
||||
}
|
||||
|
||||
const DateInput = forwardRef<HTMLInputElement, DateInputProps>((props, ref) => {
|
||||
@@ -37,9 +41,12 @@ const DateInput = forwardRef<HTMLInputElement, DateInputProps>((props, ref) => {
|
||||
label,
|
||||
description,
|
||||
variant = "datepicker",
|
||||
setAnchorRef,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const isDatepickerVariant = variant === "datepicker";
|
||||
|
||||
const conditionalVariables = {
|
||||
@@ -50,7 +57,7 @@ const DateInput = forwardRef<HTMLInputElement, DateInputProps>((props, ref) => {
|
||||
},
|
||||
};
|
||||
|
||||
const { onOpen, ariaId, open } = useDateInputContext();
|
||||
const context = useDateInputContext();
|
||||
|
||||
const {
|
||||
inputProps,
|
||||
@@ -108,7 +115,7 @@ const DateInput = forwardRef<HTMLInputElement, DateInputProps>((props, ref) => {
|
||||
{...omit(rest, ["error", "errorId", "size"])}
|
||||
{...inputProps}
|
||||
autoComplete="off"
|
||||
aria-controls={open ? ariaId : undefined}
|
||||
aria-controls={context?.open ? context.ariaId : undefined}
|
||||
readOnly={readOnly}
|
||||
className={cl(
|
||||
"navds-date__field-input",
|
||||
@@ -116,19 +123,23 @@ const DateInput = forwardRef<HTMLInputElement, DateInputProps>((props, ref) => {
|
||||
"navds-body-short",
|
||||
`navds-body-short--${size}`
|
||||
)}
|
||||
size={isDatepickerVariant ? 10 : 14}
|
||||
size={isDatepickerVariant ? 11 : 14}
|
||||
/>
|
||||
<button
|
||||
disabled={inputProps.disabled || readOnly}
|
||||
tabIndex={readOnly ? -1 : open ? -1 : 0}
|
||||
onClick={() => onOpen()}
|
||||
tabIndex={readOnly ? -1 : context?.open ? -1 : 0}
|
||||
onClick={() => {
|
||||
context?.onOpen();
|
||||
setAnchorRef?.(buttonRef.current);
|
||||
}}
|
||||
type="button"
|
||||
className="navds-date__field-button"
|
||||
ref={buttonRef}
|
||||
>
|
||||
<CalendarIcon
|
||||
pointerEvents="none"
|
||||
title={
|
||||
open
|
||||
context?.open
|
||||
? conditionalVariables.iconTitle.close
|
||||
: conditionalVariables.iconTitle.open
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import cl from "clsx";
|
||||
import React, { useContext, useRef } from "react";
|
||||
import { Button } from "../../button";
|
||||
import { Modal } from "../../modal";
|
||||
import { ModalContext } from "../../modal/ModalContext";
|
||||
import { Popover } from "../../popover";
|
||||
import { useMedia } from "../../util/useMedia";
|
||||
import { modalCloseButtonLabel, modalLabel } from "../utils/labels";
|
||||
|
||||
type DateWrapperProps = {
|
||||
open: boolean;
|
||||
children: React.ReactNode;
|
||||
onClose: () => void;
|
||||
anchor: HTMLDivElement | null;
|
||||
locale: "nb" | "nn" | "en";
|
||||
variant: "single" | "multiple" | "range" | "month";
|
||||
popoverProps: {
|
||||
id?: string;
|
||||
strategy?: "absolute" | "fixed";
|
||||
};
|
||||
};
|
||||
|
||||
export const DateWrapper = ({
|
||||
open,
|
||||
children,
|
||||
onClose,
|
||||
anchor,
|
||||
locale,
|
||||
variant,
|
||||
popoverProps,
|
||||
}: DateWrapperProps) => {
|
||||
const modalRef = useRef<HTMLDialogElement>(null);
|
||||
const isInModal = useContext(ModalContext) !== null;
|
||||
const hideModal =
|
||||
useMedia("screen and (min-width: 768px)", true) && !isInModal;
|
||||
|
||||
if (hideModal) {
|
||||
return (
|
||||
<Popover
|
||||
arrow={false}
|
||||
anchorEl={anchor}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
placement="bottom-start"
|
||||
role="dialog"
|
||||
className={cl("navds-date__popover", {
|
||||
"navds-date": variant === "month",
|
||||
})}
|
||||
flip={false}
|
||||
{...popoverProps}
|
||||
>
|
||||
{children}
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Modal
|
||||
ref={modalRef}
|
||||
open={open}
|
||||
onClose={(event) => {
|
||||
event.stopPropagation();
|
||||
onClose();
|
||||
}}
|
||||
aria-label={modalLabel(locale, variant)}
|
||||
className={cl("navds-date__modal", {
|
||||
"navds-date__nested-modal": isInModal,
|
||||
"navds-date": variant === "month",
|
||||
})}
|
||||
closeOnBackdropClick
|
||||
>
|
||||
<div className="navds-date__modal-body">
|
||||
{children}
|
||||
<Button
|
||||
variant="tertiary"
|
||||
onClick={() => modalRef?.current?.close()}
|
||||
size="small"
|
||||
>
|
||||
{modalCloseButtonLabel(locale)}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -133,3 +133,86 @@ export const labelWeek = (localeCode?: string): string => {
|
||||
return `Uke:`;
|
||||
}
|
||||
};
|
||||
|
||||
const modalLabelSingle = (localeCode?: string): string => {
|
||||
switch (localeCode) {
|
||||
case "nb":
|
||||
return `Velg dato`;
|
||||
case "nn":
|
||||
return `Vel dato`;
|
||||
case "en-GB":
|
||||
return `Pick a date`;
|
||||
default:
|
||||
return `Velg dato`;
|
||||
}
|
||||
};
|
||||
|
||||
const modalLabelMultiple = (localeCode?: string): string => {
|
||||
switch (localeCode) {
|
||||
case "nb":
|
||||
return `Velg datoer`;
|
||||
case "nn":
|
||||
return `Vel datoar`;
|
||||
case "en-GB":
|
||||
return `Pick dates`;
|
||||
default:
|
||||
return `Velg datoer`;
|
||||
}
|
||||
};
|
||||
|
||||
const modalLabelRanged = (localeCode?: string): string => {
|
||||
switch (localeCode) {
|
||||
case "nb":
|
||||
return `Velg start- og sluttdato`;
|
||||
case "nn":
|
||||
return `Vel start- og sluttdato`;
|
||||
case "en-GB":
|
||||
return `Pick a start and end date`;
|
||||
default:
|
||||
return `Velg start- og sluttdato`;
|
||||
}
|
||||
};
|
||||
|
||||
const modalLabelMonth = (localeCode?: string): string => {
|
||||
switch (localeCode) {
|
||||
case "nb":
|
||||
return `Velg måned`;
|
||||
case "nn":
|
||||
return `Vel månad`;
|
||||
case "en-GB":
|
||||
return `Pick a month`;
|
||||
default:
|
||||
return `Velg måned`;
|
||||
}
|
||||
};
|
||||
|
||||
export const modalLabel = (
|
||||
localeCode: string,
|
||||
variant: "single" | "multiple" | "range" | "month"
|
||||
) => {
|
||||
switch (variant) {
|
||||
case "single":
|
||||
return modalLabelSingle(localeCode);
|
||||
case "multiple":
|
||||
return modalLabelMultiple(localeCode);
|
||||
case "range":
|
||||
return modalLabelRanged(localeCode);
|
||||
case "month":
|
||||
return modalLabelMonth(localeCode);
|
||||
default:
|
||||
return modalLabelSingle(localeCode);
|
||||
}
|
||||
};
|
||||
|
||||
export const modalCloseButtonLabel = (localeCode?: string): string => {
|
||||
switch (localeCode) {
|
||||
case "nb":
|
||||
return `Lukk`;
|
||||
case "nn":
|
||||
return `Lukk`;
|
||||
case "en-GB":
|
||||
return `Close`;
|
||||
default:
|
||||
return `Lukk`;
|
||||
}
|
||||
};
|
||||
@@ -33,7 +33,7 @@ export const Combobox = forwardRef<
|
||||
|
||||
const toggleListButtonRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const { currentOption, toggleIsListOpen } = useFilteredOptionsContext();
|
||||
const { activeDecendantId, toggleIsListOpen } = useFilteredOptionsContext();
|
||||
const { selectedOptions } = useSelectedOptionsContext();
|
||||
|
||||
const {
|
||||
@@ -92,7 +92,7 @@ export const Combobox = forwardRef<
|
||||
"navds-combobox__wrapper-inner navds-text-field__input",
|
||||
{
|
||||
"navds-combobox__wrapper-inner--virtually-unfocused":
|
||||
currentOption !== null,
|
||||
activeDecendantId !== null,
|
||||
}
|
||||
)}
|
||||
onClick={focusInput}
|
||||
|
||||
@@ -65,7 +65,7 @@ const ComboboxProvider = forwardRef<HTMLInputElement, ComboboxProps>(
|
||||
size,
|
||||
}}
|
||||
>
|
||||
<CustomOptionsProvider>
|
||||
<CustomOptionsProvider value={{ isMultiSelect }}>
|
||||
<SelectedOptionsProvider
|
||||
value={{
|
||||
allowNewValues,
|
||||
@@ -81,7 +81,6 @@ const ComboboxProvider = forwardRef<HTMLInputElement, ComboboxProps>(
|
||||
filteredOptions,
|
||||
isListOpen,
|
||||
isLoading,
|
||||
isMultiSelect,
|
||||
options,
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useSelectedOptionsContext } from "../SelectedOptions/selectedOptionsCon
|
||||
import { useInputContext } from "../Input/inputContext";
|
||||
import { Loader } from "../../../loader";
|
||||
import { BodyShort, Label } from "../../../typography";
|
||||
import filteredOptionsUtil from "./filtered-options-util";
|
||||
|
||||
const FilteredOptions = () => {
|
||||
const {
|
||||
@@ -18,25 +19,25 @@ const FilteredOptions = () => {
|
||||
isLoading,
|
||||
isListOpen,
|
||||
filteredOptions,
|
||||
filteredOptionsIndex,
|
||||
filteredOptionsRef,
|
||||
setFilteredOptionsRef,
|
||||
isMouseLastUsedInputDevice,
|
||||
setIsMouseLastUsedInputDevice,
|
||||
isValueNew,
|
||||
setFilteredOptionsIndex,
|
||||
toggleIsListOpen,
|
||||
activeDecendantId,
|
||||
virtualFocus,
|
||||
} = useFilteredOptionsContext();
|
||||
const { isMultiSelect, selectedOptions, toggleOption } =
|
||||
useSelectedOptionsContext();
|
||||
|
||||
return (
|
||||
<ul
|
||||
ref={filteredOptionsRef}
|
||||
ref={setFilteredOptionsRef}
|
||||
className={cl("navds-combobox__list", {
|
||||
"navds-combobox__list--closed": !isListOpen,
|
||||
"navds-combobox__list--with-hover": isMouseLastUsedInputDevice,
|
||||
})}
|
||||
id={`${id}-filtered-options`}
|
||||
id={filteredOptionsUtil.getFilteredOptionsId(id)}
|
||||
role="listbox"
|
||||
tabIndex={-1}
|
||||
>
|
||||
@@ -45,7 +46,8 @@ const FilteredOptions = () => {
|
||||
className="navds-combobox__list-item--loading"
|
||||
role="option"
|
||||
aria-selected={false}
|
||||
id={`${id}-is-loading`}
|
||||
id={filteredOptionsUtil.getIsLoadingId(id)}
|
||||
data-no-focus="true"
|
||||
>
|
||||
<Loader aria-label="Søker..." />
|
||||
</li>
|
||||
@@ -54,8 +56,12 @@ const FilteredOptions = () => {
|
||||
<li
|
||||
tabIndex={-1}
|
||||
onMouseMove={() => {
|
||||
if (filteredOptionsIndex !== -1) {
|
||||
setFilteredOptionsIndex(-1);
|
||||
if (
|
||||
activeDecendantId !== filteredOptionsUtil.getAddNewOptionId(id)
|
||||
) {
|
||||
virtualFocus.moveFocusToElement(
|
||||
filteredOptionsUtil.getAddNewOptionId(id)
|
||||
);
|
||||
setIsMouseLastUsedInputDevice(true);
|
||||
}
|
||||
}}
|
||||
@@ -64,10 +70,10 @@ const FilteredOptions = () => {
|
||||
if (!isMultiSelect && !selectedOptions.includes(value))
|
||||
toggleIsListOpen(false);
|
||||
}}
|
||||
id={`${id}-combobox-new-option`}
|
||||
id={filteredOptionsUtil.getAddNewOptionId(id)}
|
||||
className={cl("navds-combobox__list-item__new-option", {
|
||||
"navds-combobox__list-item__new-option--focus":
|
||||
filteredOptionsIndex === -1,
|
||||
activeDecendantId === filteredOptionsUtil.getAddNewOptionId(id),
|
||||
})}
|
||||
role="option"
|
||||
aria-selected={false}
|
||||
@@ -86,24 +92,30 @@ const FilteredOptions = () => {
|
||||
className="navds-combobox__list-item__no-options"
|
||||
role="option"
|
||||
aria-selected={false}
|
||||
id={`${id}-no-hits`}
|
||||
id={filteredOptionsUtil.getNoHitsId(id)}
|
||||
data-no-focus="true"
|
||||
>
|
||||
Ingen søketreff
|
||||
</li>
|
||||
)}
|
||||
{filteredOptions.map((option, index) => (
|
||||
{filteredOptions.map((option) => (
|
||||
<li
|
||||
className={cl("navds-combobox__list-item", {
|
||||
"navds-combobox__list-item--focus": index === filteredOptionsIndex,
|
||||
"navds-combobox__list-item--focus":
|
||||
activeDecendantId === filteredOptionsUtil.getOptionId(id, option),
|
||||
"navds-combobox__list-item--selected":
|
||||
selectedOptions.includes(option),
|
||||
})}
|
||||
id={`${id}-option-${option.replace(" ", "-")}`}
|
||||
id={filteredOptionsUtil.getOptionId(id, option)}
|
||||
key={option}
|
||||
tabIndex={-1}
|
||||
onMouseMove={() => {
|
||||
if (filteredOptionsIndex !== index) {
|
||||
setFilteredOptionsIndex(index);
|
||||
if (
|
||||
activeDecendantId !== filteredOptionsUtil.getOptionId(id, option)
|
||||
) {
|
||||
virtualFocus.moveFocusToElement(
|
||||
filteredOptionsUtil.getOptionId(id, option)
|
||||
);
|
||||
setIsMouseLastUsedInputDevice(true);
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
const normalizeText = (text: string): string =>
|
||||
typeof text === "string" ? text.toLocaleLowerCase().trim() : "";
|
||||
|
||||
const isPartOfText = (value, text) =>
|
||||
normalizeText(text).startsWith(normalizeText(value ?? ""));
|
||||
|
||||
const isValueInList = (value, list) =>
|
||||
list?.find((listItem) => normalizeText(value) === normalizeText(listItem));
|
||||
|
||||
const getMatchingValuesFromList = (value, list) =>
|
||||
list?.filter((listItem) => isPartOfText(value, listItem));
|
||||
|
||||
const getFilteredOptionsId = (comboboxId: string) =>
|
||||
`${comboboxId}-filtered-options`;
|
||||
|
||||
const getOptionId = (comboboxId: string, option: string) =>
|
||||
`${comboboxId.toLocaleLowerCase()}-option-${option
|
||||
.replace(" ", "-")
|
||||
.toLocaleLowerCase()}`;
|
||||
|
||||
const getAddNewOptionId = (comboboxId: string) =>
|
||||
`${comboboxId}-combobox-new-option`;
|
||||
|
||||
const getIsLoadingId = (comboboxId: string) => `${comboboxId}-is-loading`;
|
||||
|
||||
const getNoHitsId = (comboboxId: string) => `${comboboxId}-no-hits`;
|
||||
|
||||
export default {
|
||||
normalizeText,
|
||||
isPartOfText,
|
||||
isValueInList,
|
||||
getMatchingValuesFromList,
|
||||
getFilteredOptionsId,
|
||||
getAddNewOptionId,
|
||||
getOptionId,
|
||||
getIsLoadingId,
|
||||
getNoHitsId,
|
||||
};
|
||||
@@ -1,11 +1,9 @@
|
||||
import React, {
|
||||
useState,
|
||||
useEffect,
|
||||
useMemo,
|
||||
createContext,
|
||||
useContext,
|
||||
useCallback,
|
||||
useRef,
|
||||
SetStateAction,
|
||||
} from "react";
|
||||
import cl from "clsx";
|
||||
@@ -13,26 +11,29 @@ import { useCustomOptionsContext } from "../customOptionsContext";
|
||||
import { useInputContext } from "../Input/inputContext";
|
||||
import usePrevious from "../../../util/usePrevious";
|
||||
import { useClientLayoutEffect } from "../../../util";
|
||||
import filteredOptionsUtils from "./filtered-options-util";
|
||||
import useVirtualFocus, { VirtualFocusType } from "./useVirtualFocus";
|
||||
import { ComboboxProps } from "../types";
|
||||
|
||||
const normalizeText = (text: string): string =>
|
||||
typeof text === "string" ? `${text}`.toLowerCase().trim() : "";
|
||||
|
||||
const isPartOfText = (value, text) =>
|
||||
normalizeText(text).startsWith(normalizeText(value ?? ""));
|
||||
|
||||
const isValueInList = (value, list) =>
|
||||
list?.find((listItem) => normalizeText(value) === normalizeText(listItem));
|
||||
|
||||
const getMatchingValuesFromList = (value, list) =>
|
||||
list?.filter((listItem) => isPartOfText(value, listItem));
|
||||
type FilteredOptionsProps = {
|
||||
children: any;
|
||||
value: Pick<
|
||||
ComboboxProps,
|
||||
| "allowNewValues"
|
||||
| "filteredOptions"
|
||||
| "isListOpen"
|
||||
| "isLoading"
|
||||
| "options"
|
||||
>;
|
||||
};
|
||||
|
||||
type FilteredOptionsContextType = {
|
||||
activeDecendantId?: string;
|
||||
allowNewValues?: boolean;
|
||||
ariaDescribedBy?: string;
|
||||
filteredOptionsRef: React.RefObject<HTMLUListElement>;
|
||||
filteredOptionsIndex: number | null;
|
||||
setFilteredOptionsIndex: (index: number) => void;
|
||||
setFilteredOptionsRef: React.Dispatch<
|
||||
React.SetStateAction<HTMLUListElement | null>
|
||||
>;
|
||||
isListOpen: boolean;
|
||||
isLoading?: boolean;
|
||||
filteredOptions: string[];
|
||||
@@ -40,19 +41,18 @@ type FilteredOptionsContextType = {
|
||||
setIsMouseLastUsedInputDevice: React.Dispatch<SetStateAction<boolean>>;
|
||||
isValueNew: boolean;
|
||||
toggleIsListOpen: (newState?: boolean) => void;
|
||||
currentOption: string | null;
|
||||
resetFilteredOptionsIndex: () => void;
|
||||
moveFocusUp: () => void;
|
||||
moveFocusDown: () => void;
|
||||
moveFocusToInput: () => void;
|
||||
moveFocusToEnd: () => void;
|
||||
currentOption?: string;
|
||||
shouldAutocomplete?: boolean;
|
||||
virtualFocus: VirtualFocusType;
|
||||
};
|
||||
const FilteredOptionsContext = createContext<FilteredOptionsContextType>(
|
||||
{} as FilteredOptionsContextType
|
||||
);
|
||||
|
||||
export const FilteredOptionsProvider = ({ children, value: props }) => {
|
||||
export const FilteredOptionsProvider = ({
|
||||
children,
|
||||
value: props,
|
||||
}: FilteredOptionsProps) => {
|
||||
const {
|
||||
allowNewValues,
|
||||
filteredOptions: externalFilteredOptions,
|
||||
@@ -60,7 +60,9 @@ export const FilteredOptionsProvider = ({ children, value: props }) => {
|
||||
isLoading,
|
||||
options,
|
||||
} = props;
|
||||
const filteredOptionsRef = useRef<HTMLUListElement | null>(null);
|
||||
const [filteredOptionsRef, setFilteredOptionsRef] =
|
||||
useState<HTMLUListElement | null>(null);
|
||||
const virtualFocus = useVirtualFocus(filteredOptionsRef);
|
||||
const {
|
||||
inputProps: { "aria-describedby": partialAriaDescribedBy, id },
|
||||
value,
|
||||
@@ -70,9 +72,6 @@ export const FilteredOptionsProvider = ({ children, value: props }) => {
|
||||
shouldAutocomplete,
|
||||
} = useInputContext();
|
||||
|
||||
const [filteredOptionsIndex, setFilteredOptionsIndex] = useState<
|
||||
number | null
|
||||
>(null);
|
||||
const [isInternalListOpen, setInternalListOpen] = useState(false);
|
||||
const { customOptions } = useCustomOptionsContext();
|
||||
|
||||
@@ -81,8 +80,7 @@ export const FilteredOptionsProvider = ({ children, value: props }) => {
|
||||
return externalFilteredOptions;
|
||||
}
|
||||
const opts = [...customOptions, ...options];
|
||||
setFilteredOptionsIndex(null);
|
||||
return getMatchingValuesFromList(searchTerm, opts);
|
||||
return filteredOptionsUtils.getMatchingValuesFromList(searchTerm, opts);
|
||||
}, [customOptions, externalFilteredOptions, options, searchTerm]);
|
||||
|
||||
const previousSearchTerm = usePrevious(searchTerm);
|
||||
@@ -90,13 +88,28 @@ export const FilteredOptionsProvider = ({ children, value: props }) => {
|
||||
const [isMouseLastUsedInputDevice, setIsMouseLastUsedInputDevice] =
|
||||
useState(false);
|
||||
|
||||
const filteredOptionsMap = useMemo(
|
||||
() =>
|
||||
options.reduce(
|
||||
(map, _option) => ({
|
||||
...map,
|
||||
[filteredOptionsUtils.getOptionId(id, _option)]: _option,
|
||||
}),
|
||||
{
|
||||
[filteredOptionsUtils.getAddNewOptionId(id)]: allowNewValues
|
||||
? value
|
||||
: undefined,
|
||||
}
|
||||
),
|
||||
[allowNewValues, id, options, value]
|
||||
);
|
||||
|
||||
useClientLayoutEffect(() => {
|
||||
if (
|
||||
shouldAutocomplete &&
|
||||
normalizeText(searchTerm) !== "" &&
|
||||
filteredOptionsUtils.normalizeText(searchTerm) !== "" &&
|
||||
(previousSearchTerm?.length || 0) < searchTerm.length &&
|
||||
filteredOptions.length > 0 &&
|
||||
!isValueInList(searchTerm, filteredOptions)
|
||||
filteredOptions.length > 0
|
||||
) {
|
||||
setValue(
|
||||
`${searchTerm}${filteredOptions[0].substring(searchTerm.length)}`
|
||||
@@ -116,29 +129,30 @@ export const FilteredOptionsProvider = ({ children, value: props }) => {
|
||||
return isExternalListOpen ?? isInternalListOpen;
|
||||
}, [isExternalListOpen, isInternalListOpen]);
|
||||
|
||||
const toggleIsListOpen = useCallback((newState?: boolean) => {
|
||||
setFilteredOptionsIndex(null);
|
||||
setInternalListOpen((oldState) => newState ?? !oldState);
|
||||
}, []);
|
||||
|
||||
const isValueNew = useMemo(
|
||||
() => Boolean(value) && !isValueInList(value, filteredOptions),
|
||||
[value, filteredOptions]
|
||||
const toggleIsListOpen = useCallback(
|
||||
(newState?: boolean) => {
|
||||
virtualFocus.moveFocusToTop();
|
||||
setInternalListOpen((oldState) => newState ?? !oldState);
|
||||
},
|
||||
[virtualFocus]
|
||||
);
|
||||
|
||||
const getMinimumIndex = useCallback(() => {
|
||||
return isValueNew && allowNewValues ? -1 : 0;
|
||||
}, [allowNewValues, isValueNew]);
|
||||
const isValueNew = useMemo(
|
||||
() =>
|
||||
Boolean(value) &&
|
||||
!filteredOptionsMap[filteredOptionsUtils.getOptionId(id, value)],
|
||||
[filteredOptionsMap, id, value]
|
||||
);
|
||||
|
||||
const ariaDescribedBy = useMemo(() => {
|
||||
let activeOption;
|
||||
if (!isLoading && filteredOptions.length === 0) {
|
||||
activeOption = `${id}-no-hits`;
|
||||
activeOption = filteredOptionsUtils.getNoHitsId(id);
|
||||
} else if ((value && value !== "") || isLoading) {
|
||||
if (shouldAutocomplete && filteredOptions[0]) {
|
||||
activeOption = `${id}-option-${filteredOptions[0].replace(" ", "-")}`;
|
||||
activeOption = filteredOptionsUtils.getOptionId(id, filteredOptions[0]);
|
||||
} else if (isListOpen && isLoading) {
|
||||
activeOption = `${id}-is-loading`;
|
||||
activeOption = filteredOptionsUtils.getIsLoadingId(id);
|
||||
}
|
||||
}
|
||||
return cl(activeOption, partialAriaDescribedBy) || undefined;
|
||||
@@ -152,102 +166,21 @@ export const FilteredOptionsProvider = ({ children, value: props }) => {
|
||||
id,
|
||||
]);
|
||||
|
||||
const currentOption = useMemo(() => {
|
||||
if (filteredOptionsIndex == null) {
|
||||
return null;
|
||||
}
|
||||
if (filteredOptionsIndex === -1) {
|
||||
return value;
|
||||
}
|
||||
return filteredOptions[filteredOptionsIndex];
|
||||
}, [filteredOptionsIndex, filteredOptions, value]);
|
||||
const currentOption = useMemo(
|
||||
() =>
|
||||
filteredOptionsMap[virtualFocus.activeElement?.getAttribute("id") || -1],
|
||||
[filteredOptionsMap, virtualFocus]
|
||||
);
|
||||
|
||||
const resetFilteredOptionsIndex = () => {
|
||||
setFilteredOptionsIndex(getMinimumIndex());
|
||||
};
|
||||
|
||||
const scrollToOption = useCallback((newIndex: number) => {
|
||||
if (
|
||||
filteredOptionsRef.current &&
|
||||
filteredOptionsRef.current.children[newIndex]
|
||||
) {
|
||||
const child = filteredOptionsRef.current.children[newIndex];
|
||||
const { top, bottom } = child.getBoundingClientRect();
|
||||
const parentRect = filteredOptionsRef.current.getBoundingClientRect();
|
||||
if (top < parentRect.top || bottom > parentRect.bottom) {
|
||||
child.scrollIntoView({ block: "nearest" });
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (filteredOptionsIndex !== null && isListOpen) {
|
||||
scrollToOption(filteredOptionsIndex);
|
||||
}
|
||||
}, [filteredOptionsIndex, isListOpen, scrollToOption]);
|
||||
|
||||
const moveFocusToInput = useCallback(() => {
|
||||
setFilteredOptionsIndex(null);
|
||||
toggleIsListOpen(false);
|
||||
}, [toggleIsListOpen]);
|
||||
|
||||
const moveFocusToEnd = useCallback(() => {
|
||||
const lastIndex = filteredOptions.length - 1;
|
||||
toggleIsListOpen(true);
|
||||
setFilteredOptionsIndex(lastIndex);
|
||||
}, [filteredOptions.length, toggleIsListOpen]);
|
||||
|
||||
const moveFocusUp = useCallback(() => {
|
||||
if (filteredOptionsIndex === null) {
|
||||
return;
|
||||
}
|
||||
if (filteredOptionsIndex === getMinimumIndex()) {
|
||||
toggleIsListOpen(false);
|
||||
setFilteredOptionsIndex(null);
|
||||
} else {
|
||||
const newIndex = Math.max(getMinimumIndex(), filteredOptionsIndex - 1);
|
||||
setFilteredOptionsIndex(newIndex);
|
||||
}
|
||||
}, [filteredOptionsIndex, getMinimumIndex, toggleIsListOpen]);
|
||||
|
||||
const moveFocusDown = useCallback(() => {
|
||||
if (filteredOptionsIndex === null || !isListOpen) {
|
||||
toggleIsListOpen(true);
|
||||
if (allowNewValues || filteredOptions.length >= 1) {
|
||||
setFilteredOptionsIndex(getMinimumIndex());
|
||||
}
|
||||
return;
|
||||
}
|
||||
const newIndex = Math.min(
|
||||
filteredOptionsIndex + 1,
|
||||
Math.max(getMinimumIndex(), filteredOptions.length - 1)
|
||||
);
|
||||
setFilteredOptionsIndex(newIndex);
|
||||
}, [
|
||||
allowNewValues,
|
||||
filteredOptions.length,
|
||||
filteredOptionsIndex,
|
||||
getMinimumIndex,
|
||||
isListOpen,
|
||||
toggleIsListOpen,
|
||||
]);
|
||||
|
||||
const activeDecendantId = useMemo(() => {
|
||||
if (filteredOptionsIndex === null) {
|
||||
return undefined;
|
||||
} else if (filteredOptionsIndex === -1) {
|
||||
return `${id}-combobox-new-option`;
|
||||
} else {
|
||||
return `${id}-option-${currentOption?.replace(" ", "-")}`;
|
||||
}
|
||||
}, [filteredOptionsIndex, currentOption, id]);
|
||||
const activeDecendantId = useMemo(
|
||||
() => virtualFocus.activeElement?.getAttribute("id") || undefined,
|
||||
[virtualFocus.activeElement]
|
||||
);
|
||||
|
||||
const filteredOptionsState = {
|
||||
activeDecendantId,
|
||||
allowNewValues,
|
||||
filteredOptionsRef,
|
||||
filteredOptionsIndex,
|
||||
setFilteredOptionsIndex,
|
||||
setFilteredOptionsRef,
|
||||
shouldAutocomplete,
|
||||
isListOpen,
|
||||
isLoading,
|
||||
@@ -257,11 +190,7 @@ export const FilteredOptionsProvider = ({ children, value: props }) => {
|
||||
isValueNew,
|
||||
toggleIsListOpen,
|
||||
currentOption,
|
||||
resetFilteredOptionsIndex,
|
||||
moveFocusUp,
|
||||
moveFocusDown,
|
||||
moveFocusToInput,
|
||||
moveFocusToEnd,
|
||||
virtualFocus,
|
||||
ariaDescribedBy,
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { Dispatch, SetStateAction, useState } from "react";
|
||||
|
||||
export type VirtualFocusType = {
|
||||
activeElement: HTMLElement | undefined;
|
||||
getElementById: (id: string) => HTMLElement | undefined;
|
||||
isFocusOnTheTop: boolean;
|
||||
isFocusOnTheBottom: boolean;
|
||||
setIndex: Dispatch<SetStateAction<number>>;
|
||||
moveFocusUp: () => void;
|
||||
moveFocusDown: () => void;
|
||||
moveFocusToElement: (id: string) => void;
|
||||
moveFocusToTop: () => void;
|
||||
moveFocusToBottom: () => void;
|
||||
};
|
||||
|
||||
const useVirtualFocus = (
|
||||
containerRef: HTMLElement | null
|
||||
): VirtualFocusType => {
|
||||
const [index, setIndex] = useState(-1);
|
||||
|
||||
const listOfAllChildren: Array<HTMLElement> = containerRef?.children
|
||||
? Array.prototype.slice.call(containerRef?.children)
|
||||
: [];
|
||||
const elementsAbleToReceiveFocus = listOfAllChildren.filter(
|
||||
(child) => child.getAttribute("data-no-focus") !== "true"
|
||||
);
|
||||
|
||||
const activeElement = elementsAbleToReceiveFocus[index];
|
||||
const getElementById = (id: string) =>
|
||||
listOfAllChildren.find((element) => element.id === id);
|
||||
const isFocusOnTheTop = index === 0;
|
||||
const isFocusOnTheBottom = index === elementsAbleToReceiveFocus.length - 1;
|
||||
|
||||
const scrollToOption = (newIndex: number) => {
|
||||
const indexOfElementToScrollTo = Math.min(
|
||||
Math.max(newIndex, 0),
|
||||
containerRef?.children.length || 0
|
||||
);
|
||||
if (containerRef?.children[indexOfElementToScrollTo]) {
|
||||
const child = containerRef.children[indexOfElementToScrollTo];
|
||||
const { top, bottom } = child.getBoundingClientRect();
|
||||
const parentRect = containerRef.getBoundingClientRect();
|
||||
if (top < parentRect.top || bottom > parentRect.bottom) {
|
||||
child.scrollIntoView({ block: "nearest" });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const _moveFocusAndScrollTo = (_index: number) => {
|
||||
setIndex(_index);
|
||||
scrollToOption(_index);
|
||||
};
|
||||
const moveFocusUp = () => _moveFocusAndScrollTo(Math.max(index - 1, -1));
|
||||
const moveFocusDown = () =>
|
||||
_moveFocusAndScrollTo(
|
||||
Math.min(index + 1, elementsAbleToReceiveFocus.length - 1)
|
||||
);
|
||||
const moveFocusToTop = () => _moveFocusAndScrollTo(-1);
|
||||
const moveFocusToBottom = () =>
|
||||
_moveFocusAndScrollTo(elementsAbleToReceiveFocus.length - 1);
|
||||
const moveFocusToElement = (id: string) => {
|
||||
const thisElement = elementsAbleToReceiveFocus.find(
|
||||
(_element) => _element.getAttribute("id") === id
|
||||
);
|
||||
const indexOfElement = thisElement
|
||||
? elementsAbleToReceiveFocus.indexOf(thisElement)
|
||||
: -1;
|
||||
if (indexOfElement >= 0) {
|
||||
setIndex(indexOfElement);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
activeElement,
|
||||
getElementById,
|
||||
isFocusOnTheTop,
|
||||
isFocusOnTheBottom,
|
||||
setIndex,
|
||||
moveFocusUp,
|
||||
moveFocusDown,
|
||||
moveFocusToElement,
|
||||
moveFocusToTop,
|
||||
moveFocusToBottom,
|
||||
};
|
||||
};
|
||||
|
||||
export default useVirtualFocus;
|
||||
@@ -9,6 +9,7 @@ import cl from "clsx";
|
||||
import { useSelectedOptionsContext } from "../SelectedOptions/selectedOptionsContext";
|
||||
import { useFilteredOptionsContext } from "../FilteredOptions/filteredOptionsContext";
|
||||
import { useInputContext } from "./inputContext";
|
||||
import filteredOptionsUtil from "../FilteredOptions/filtered-options-util";
|
||||
|
||||
interface InputProps
|
||||
extends Omit<InputHTMLAttributes<HTMLInputElement>, "value"> {
|
||||
@@ -31,44 +32,55 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
allowNewValues,
|
||||
currentOption,
|
||||
filteredOptions,
|
||||
isValueNew,
|
||||
toggleIsListOpen,
|
||||
isListOpen,
|
||||
filteredOptionsIndex,
|
||||
moveFocusUp,
|
||||
moveFocusDown,
|
||||
ariaDescribedBy,
|
||||
moveFocusToInput,
|
||||
moveFocusToEnd,
|
||||
setFilteredOptionsIndex,
|
||||
setIsMouseLastUsedInputDevice,
|
||||
shouldAutocomplete,
|
||||
virtualFocus,
|
||||
} = useFilteredOptionsContext();
|
||||
|
||||
const onEnter = useCallback(
|
||||
(event: React.KeyboardEvent) => {
|
||||
const isTextInSelectedOptions = (text: string) => {
|
||||
return selectedOptions.find(
|
||||
(item) => item.toLocaleLowerCase() === text.toLocaleLowerCase()
|
||||
);
|
||||
};
|
||||
|
||||
if (currentOption) {
|
||||
event.preventDefault();
|
||||
// Selecting a value from the dropdown / FilteredOptions
|
||||
toggleOption(currentOption, event);
|
||||
if (!isMultiSelect && !selectedOptions.includes(currentOption))
|
||||
if (!isMultiSelect && !isTextInSelectedOptions(currentOption)) {
|
||||
toggleIsListOpen(false);
|
||||
} else if (shouldAutocomplete && selectedOptions.includes(value)) {
|
||||
}
|
||||
} else if (shouldAutocomplete && isTextInSelectedOptions(value)) {
|
||||
event.preventDefault();
|
||||
// Trying to set the same value that is already set, so just clearing the input
|
||||
clearInput(event);
|
||||
} else if ((allowNewValues || shouldAutocomplete) && value !== "") {
|
||||
event.preventDefault();
|
||||
// Autocompleting or adding a new value
|
||||
toggleOption(value, event);
|
||||
if (!isMultiSelect && !selectedOptions.includes(value))
|
||||
const selectedValue =
|
||||
allowNewValues && isValueNew ? value : filteredOptions[0];
|
||||
toggleOption(selectedValue, event);
|
||||
if (
|
||||
!isMultiSelect &&
|
||||
!isTextInSelectedOptions(filteredOptions[0] || selectedValue)
|
||||
) {
|
||||
toggleIsListOpen(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
allowNewValues,
|
||||
clearInput,
|
||||
currentOption,
|
||||
filteredOptions,
|
||||
isMultiSelect,
|
||||
isValueNew,
|
||||
selectedOptions,
|
||||
shouldAutocomplete,
|
||||
toggleIsListOpen,
|
||||
@@ -89,10 +101,10 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
onEnter(e);
|
||||
break;
|
||||
case "Home":
|
||||
moveFocusToInput();
|
||||
virtualFocus.moveFocusToTop();
|
||||
break;
|
||||
case "End":
|
||||
moveFocusToEnd();
|
||||
virtualFocus.moveFocusToBottom();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -113,14 +125,20 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
// so we don't interfere with text editing
|
||||
if (e.target.selectionStart === value?.length) {
|
||||
e.preventDefault();
|
||||
moveFocusDown();
|
||||
if (virtualFocus.activeElement === null || !isListOpen) {
|
||||
toggleIsListOpen(true);
|
||||
}
|
||||
virtualFocus.moveFocusDown();
|
||||
}
|
||||
} else if (e.key === "ArrowUp") {
|
||||
// Check that the FilteredOptions list is open and has virtual focus.
|
||||
// Otherwise ignore keystrokes, so it doesn't interfere with text editing
|
||||
if (isListOpen && filteredOptionsIndex !== null) {
|
||||
if (isListOpen && activeDecendantId) {
|
||||
e.preventDefault();
|
||||
moveFocusUp();
|
||||
if (virtualFocus.isFocusOnTheTop) {
|
||||
toggleIsListOpen(false);
|
||||
}
|
||||
virtualFocus.moveFocusUp();
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -128,11 +146,11 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
value,
|
||||
selectedOptions,
|
||||
removeSelectedOption,
|
||||
moveFocusDown,
|
||||
isListOpen,
|
||||
filteredOptionsIndex,
|
||||
moveFocusUp,
|
||||
activeDecendantId,
|
||||
setIsMouseLastUsedInputDevice,
|
||||
toggleIsListOpen,
|
||||
virtualFocus,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -144,13 +162,14 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
} else if (filteredOptions.length === 0) {
|
||||
toggleIsListOpen(false);
|
||||
}
|
||||
virtualFocus.moveFocusToTop();
|
||||
onChange(event);
|
||||
},
|
||||
[filteredOptions.length, onChange, toggleIsListOpen]
|
||||
[filteredOptions.length, virtualFocus, onChange, toggleIsListOpen]
|
||||
);
|
||||
|
||||
const onBlur = () => {
|
||||
setFilteredOptionsIndex(-1);
|
||||
virtualFocus.moveFocusToTop();
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -165,7 +184,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
onBlur={onBlur}
|
||||
onKeyUp={handleKeyUp}
|
||||
onKeyDown={handleKeyDown}
|
||||
aria-controls={`${inputProps.id}-filtered-options`}
|
||||
aria-controls={filteredOptionsUtil.getFilteredOptionsId(inputProps.id)}
|
||||
aria-expanded={!!isListOpen}
|
||||
autoComplete="off"
|
||||
aria-autocomplete={shouldAutocomplete ? "both" : "list"}
|
||||
|
||||
@@ -467,6 +467,50 @@ export const TestThatCallbacksOnlyFireWhenExpected: StoryObj<{
|
||||
},
|
||||
};
|
||||
|
||||
export const TestCasingWhenAutoCompleting = {
|
||||
args: {
|
||||
onChange: jest.fn(),
|
||||
onClear: jest.fn(),
|
||||
onToggleSelected: jest.fn(),
|
||||
},
|
||||
render: (props) => {
|
||||
return (
|
||||
<UNSAFE_Combobox
|
||||
options={["Camel Case", "lowercase", "UPPERCASE"]}
|
||||
label="Liker du best store eller små bokstaver?"
|
||||
shouldAutocomplete
|
||||
allowNewValues
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const input = canvas.getByRole<HTMLInputElement>("combobox");
|
||||
|
||||
// With exisiting option
|
||||
userEvent.click(input);
|
||||
await userEvent.type(input, "cAmEl CaSe", { delay: 250 });
|
||||
await sleep(250);
|
||||
expect(input.value).toBe("cAmEl CaSe");
|
||||
await userEvent.type(input, "{Enter}");
|
||||
await sleep(250);
|
||||
const chips = canvas.getAllByRole("list")[0];
|
||||
const selectedUpperCaseChip = within(chips).getAllByRole("listitem")[0];
|
||||
expect(selectedUpperCaseChip).toHaveTextContent("Camel Case"); // A weird issue is preventing the accessible name from being used in the test, even if it works in VoiceOver
|
||||
|
||||
// With custom option
|
||||
userEvent.click(input);
|
||||
await userEvent.type(input, "cAmEl{Backspace}", { delay: 250 });
|
||||
await sleep(250);
|
||||
expect(input.value).toBe("cAmEl");
|
||||
await userEvent.type(input, "{Enter}");
|
||||
await sleep(250);
|
||||
const selectedNewValueChip = within(chips).getAllByRole("listitem")[0];
|
||||
expect(selectedNewValueChip).toHaveTextContent("cAmEl"); // A weird issue is preventing the accessible name from being used in the test, even if it works in VoiceOver
|
||||
},
|
||||
};
|
||||
|
||||
export const TestHoverAndFocusSwitching: StoryObject = {
|
||||
render: () => {
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useState, useCallback, createContext, useContext } from "react";
|
||||
import { useInputContext } from "./Input/inputContext";
|
||||
import { useSelectedOptionsContext } from "./SelectedOptions/selectedOptionsContext";
|
||||
|
||||
type CustomOptionsContextType = {
|
||||
customOptions: string[];
|
||||
@@ -13,13 +12,19 @@ const CustomOptionsContext = createContext<CustomOptionsContextType>(
|
||||
{} as CustomOptionsContextType
|
||||
);
|
||||
|
||||
export const CustomOptionsProvider = ({ children }) => {
|
||||
export const CustomOptionsProvider = ({
|
||||
children,
|
||||
value,
|
||||
}: {
|
||||
children: any;
|
||||
value: { isMultiSelect?: boolean };
|
||||
}) => {
|
||||
const [customOptions, setCustomOptions] = useState<string[]>([]);
|
||||
const { focusInput } = useInputContext();
|
||||
const { isMultiSelect } = useSelectedOptionsContext();
|
||||
const { isMultiSelect } = value;
|
||||
|
||||
const removeCustomOption = useCallback(
|
||||
(option) => {
|
||||
(option: string) => {
|
||||
setCustomOptions((prevCustomOptions) =>
|
||||
prevCustomOptions.filter((o) => o !== option)
|
||||
);
|
||||
@@ -29,7 +34,7 @@ export const CustomOptionsProvider = ({ children }) => {
|
||||
);
|
||||
|
||||
const addCustomOption = useCallback(
|
||||
(option) => {
|
||||
(option: string) => {
|
||||
if (isMultiSelect) {
|
||||
setCustomOptions((prevOptions) => [...prevOptions, option]);
|
||||
} else {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { InformationIcon } from "@navikt/aksel-icons";
|
||||
import { Meta } from "@storybook/react";
|
||||
import React from "react";
|
||||
import { BodyLong, GuidePanel, VStack } from "../index";
|
||||
import { Meta } from "@storybook/react";
|
||||
import { InformationIcon } from "@navikt/aksel-icons";
|
||||
|
||||
export default {
|
||||
title: "ds-react/GuidePanel",
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import cl from "clsx";
|
||||
import React, { forwardRef } from "react";
|
||||
import {
|
||||
ResponsiveProp,
|
||||
SpacingScale,
|
||||
getResponsiveProps,
|
||||
} from "../utilities/css";
|
||||
import { getResponsiveProps } from "../utilities/css";
|
||||
import { ResponsiveProp, SpacingScale } from "../utilities/types";
|
||||
import { Slot } from "../../util/Slot";
|
||||
|
||||
export type BleedSpacingInline = "0" | "full" | "px" | SpacingScale;
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import cl from "clsx";
|
||||
import React, { forwardRef } from "react";
|
||||
import { OverridableComponent } from "../../util/OverridableComponent";
|
||||
import { getResponsiveProps } from "../utilities/css";
|
||||
import {
|
||||
SpaceDelimitedAttribute,
|
||||
ResponsiveProp,
|
||||
SpacingScale,
|
||||
getResponsiveProps,
|
||||
} from "../utilities/css";
|
||||
import {
|
||||
BackgroundToken,
|
||||
BorderColorToken,
|
||||
ShadowToken,
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import React, { forwardRef, HTMLAttributes } from "react";
|
||||
import cl from "clsx";
|
||||
import {
|
||||
getResponsiveProps,
|
||||
getResponsiveValue,
|
||||
ResponsiveProp,
|
||||
SpacingScale,
|
||||
} from "../utilities/css";
|
||||
import { getResponsiveProps, getResponsiveValue } from "../utilities/css";
|
||||
import { ResponsiveProp, SpacingScale } from "../utilities/types";
|
||||
|
||||
export interface HGridProps extends HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import cl from "clsx";
|
||||
import React, { forwardRef, HTMLAttributes } from "react";
|
||||
import { BreakpointsAlias } from "../utilities/css";
|
||||
import { BreakpointsAlias } from "../utilities/types";
|
||||
import { Slot } from "../../util/Slot";
|
||||
|
||||
export interface ResponsiveProps extends HTMLAttributes<HTMLDivElement> {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from "react";
|
||||
import { Link } from "../../link";
|
||||
import { Heading, Label } from "../../typography";
|
||||
import { Box } from "../box";
|
||||
import { VStack } from "../stack";
|
||||
import { Link } from "../../link";
|
||||
|
||||
const LinkElement = ({ children }) => {
|
||||
return (
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import cl from "clsx";
|
||||
import React, { forwardRef, HTMLAttributes } from "react";
|
||||
import { OverridableComponent } from "../../util/OverridableComponent";
|
||||
import {
|
||||
getResponsiveProps,
|
||||
getResponsiveValue,
|
||||
ResponsiveProp,
|
||||
SpacingScale,
|
||||
} from "../utilities/css";
|
||||
import { getResponsiveProps, getResponsiveValue } from "../utilities/css";
|
||||
import { ResponsiveProp, SpacingScale } from "../utilities/types";
|
||||
|
||||
export interface StackProps extends HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
|
||||
@@ -1,39 +1,4 @@
|
||||
export type BreakpointsAlias = "xs" | "sm" | "md" | "lg" | "xl";
|
||||
|
||||
export type SpacingScale =
|
||||
| "0"
|
||||
| "05"
|
||||
| "1"
|
||||
| "1-alt"
|
||||
| "2"
|
||||
| "3"
|
||||
| "4"
|
||||
| "5"
|
||||
| "6"
|
||||
| "7"
|
||||
| "8"
|
||||
| "9"
|
||||
| "10"
|
||||
| "11"
|
||||
| "12"
|
||||
| "14"
|
||||
| "16"
|
||||
| "18"
|
||||
| "20"
|
||||
| "24"
|
||||
| "32";
|
||||
|
||||
export type SpaceDelimitedAttribute<T extends string> =
|
||||
| T
|
||||
| `${T} ${T}`
|
||||
| `${T} ${T} ${T}`
|
||||
| `${T} ${T} ${T} ${T}`;
|
||||
|
||||
type FixedResponsiveT<T> = {
|
||||
[Breakpoint in BreakpointsAlias]?: T;
|
||||
};
|
||||
|
||||
export type ResponsiveProp<T> = T | FixedResponsiveT<T>;
|
||||
import { ResponsiveProp } from "./types";
|
||||
|
||||
export function getResponsiveValue<T = string>(
|
||||
componentName: string,
|
||||
|
||||
@@ -3,6 +3,7 @@ import surfaceColors from "@navikt/ds-tokens/src/colors-surface.json";
|
||||
import borderColors from "@navikt/ds-tokens/src/colors-border.json";
|
||||
import borderRadii from "@navikt/ds-tokens/src/border.json";
|
||||
import shadows from "@navikt/ds-tokens/src/shadow.json";
|
||||
import Spacing from "@navikt/ds-tokens/src/spacing.json";
|
||||
|
||||
export type BackgroundToken =
|
||||
| keyof typeof bgColors.a
|
||||
@@ -12,3 +13,18 @@ export type BorderRadiiToken =
|
||||
| keyof (typeof borderRadii.a)["border-radius"]
|
||||
| "0";
|
||||
export type ShadowToken = keyof typeof shadows.a.shadow;
|
||||
|
||||
export type BreakpointsAlias = "xs" | "sm" | "md" | "lg" | "xl";
|
||||
|
||||
export type SpacingScale = keyof (typeof Spacing)["a"]["spacing"];
|
||||
|
||||
export type SpaceDelimitedAttribute<T extends string> =
|
||||
| T
|
||||
| `${T} ${T}`
|
||||
| `${T} ${T} ${T}`
|
||||
| `${T} ${T} ${T} ${T}`;
|
||||
type FixedResponsiveT<T> = {
|
||||
[Breakpoint in BreakpointsAlias]?: T;
|
||||
};
|
||||
|
||||
export type ResponsiveProp<T> = T | FixedResponsiveT<T>;
|
||||
@@ -52,7 +52,7 @@ export type LoaderType = React.ForwardRefExoticComponent<
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* <Loader size="3xlarge" title="venter..." />
|
||||
* <Loader size="3xlarge" title="Venter..." />
|
||||
* ```
|
||||
*/
|
||||
export const Loader: LoaderType = forwardRef<SVGSVGElement, LoaderProps>(
|
||||
|
||||
@@ -8,6 +8,7 @@ import React, {
|
||||
useRef,
|
||||
} from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { DateContext } from "../date/context";
|
||||
import { useProvider } from "../provider";
|
||||
import { Detail, Heading } from "../typography";
|
||||
import { mergeRefs, useId } from "../util";
|
||||
@@ -82,11 +83,13 @@ export const Modal = forwardRef<HTMLDialogElement, ModalProps>(
|
||||
open,
|
||||
onBeforeClose,
|
||||
onCancel,
|
||||
closeOnBackdropClick,
|
||||
width,
|
||||
portal,
|
||||
className,
|
||||
"aria-labelledby": ariaLabelledby,
|
||||
style,
|
||||
onClick,
|
||||
...rest
|
||||
}: ModalProps,
|
||||
ref
|
||||
@@ -97,7 +100,9 @@ export const Modal = forwardRef<HTMLDialogElement, ModalProps>(
|
||||
const rootElement = useProvider()?.rootElement;
|
||||
const portalNode = useFloatingPortalNode({ root: rootElement });
|
||||
|
||||
if (useContext(ModalContext)) {
|
||||
const dateContext = useContext(DateContext);
|
||||
const modalContext = useContext(ModalContext);
|
||||
if (modalContext && !dateContext) {
|
||||
console.error("Modals should not be nested");
|
||||
}
|
||||
|
||||
@@ -108,6 +113,11 @@ export const Modal = forwardRef<HTMLDialogElement, ModalProps>(
|
||||
if (needPolyfill && modalRef.current && portalNode) {
|
||||
dialogPolyfill.registerDialog(modalRef.current);
|
||||
}
|
||||
// We set autofocus on the dialog element to prevent the default behavior where first focusable element gets focus when modal is opened.
|
||||
// This is mainly to fix an edge case where having a Tooltip as the first focusable element would make it activate when you open the modal.
|
||||
// We have to use JS because it doesn't work to set it with a prop (React bug?)
|
||||
// Currently doesn't seem to work in Chrome. See also Tooltip.tsx
|
||||
if (modalRef.current && portalNode) modalRef.current.autofocus = true;
|
||||
}, [modalRef, portalNode]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -128,34 +138,57 @@ export const Modal = forwardRef<HTMLDialogElement, ModalProps>(
|
||||
const isWidthPreset =
|
||||
typeof width === "string" && ["small", "medium"].includes(width);
|
||||
|
||||
const mergedClassName = cl("navds-modal", className, {
|
||||
"navds-modal--polyfilled": needPolyfill,
|
||||
"navds-modal--autowidth": !width,
|
||||
[`navds-modal--${width}`]: isWidthPreset,
|
||||
});
|
||||
|
||||
const mergedStyle = {
|
||||
...style,
|
||||
...(!isWidthPreset ? { width } : {}),
|
||||
};
|
||||
|
||||
const mergedOnCancel: React.DialogHTMLAttributes<HTMLDialogElement>["onCancel"] =
|
||||
(event) => {
|
||||
if (onBeforeClose && onBeforeClose() === false) {
|
||||
event.preventDefault();
|
||||
} else if (onCancel) onCancel(event);
|
||||
};
|
||||
|
||||
const mergedOnClick =
|
||||
closeOnBackdropClick && !needPolyfill // closeOnBackdropClick has issues on polyfill when nesting modals (DatePicker)
|
||||
? (event: React.MouseEvent<HTMLDialogElement>) => {
|
||||
onClick && onClick(event);
|
||||
if (
|
||||
event.target === modalRef.current &&
|
||||
(!onBeforeClose || onBeforeClose() !== false)
|
||||
) {
|
||||
modalRef.current.close();
|
||||
}
|
||||
}
|
||||
: onClick;
|
||||
|
||||
const mergedAriaLabelledBy =
|
||||
!ariaLabelledby && !rest["aria-label"] && header
|
||||
? ariaLabelId
|
||||
: ariaLabelledby;
|
||||
|
||||
const component = (
|
||||
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions
|
||||
<dialog
|
||||
{...rest}
|
||||
ref={mergedRef}
|
||||
className={cl("navds-modal", className, {
|
||||
"navds-modal--polyfilled": needPolyfill,
|
||||
"navds-modal--autowidth": !width,
|
||||
[`navds-modal--${width}`]: isWidthPreset,
|
||||
})}
|
||||
style={{
|
||||
...style,
|
||||
...(!isWidthPreset ? { width } : {}),
|
||||
}}
|
||||
onCancel={(event) => {
|
||||
// FYI: onCancel fires when you press Esc
|
||||
if (onBeforeClose && onBeforeClose() === false) {
|
||||
event.preventDefault();
|
||||
} else if (onCancel) onCancel(event);
|
||||
}}
|
||||
aria-labelledby={
|
||||
!ariaLabelledby && !rest["aria-label"] && header
|
||||
? ariaLabelId
|
||||
: ariaLabelledby
|
||||
}
|
||||
className={mergedClassName}
|
||||
style={mergedStyle}
|
||||
onCancel={mergedOnCancel} // FYI: onCancel fires when you press Esc
|
||||
onClick={mergedOnClick}
|
||||
aria-labelledby={mergedAriaLabelledBy}
|
||||
>
|
||||
<ModalContext.Provider
|
||||
value={{
|
||||
closeHandler: getCloseHandler(modalRef, header, onBeforeClose),
|
||||
ref: modalRef,
|
||||
}}
|
||||
>
|
||||
{header && (
|
||||
|
||||
@@ -2,5 +2,6 @@ import React from "react";
|
||||
|
||||
interface ModalContextProps {
|
||||
closeHandler?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
ref: React.RefObject<HTMLDialogElement>;
|
||||
}
|
||||
export const ModalContext = React.createContext<ModalContextProps | null>(null);
|
||||
@@ -1,17 +1,19 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import { FileIcon } from "@navikt/aksel-icons";
|
||||
import { BodyLong, Button, Heading } from "..";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { BodyLong, Button, Heading, Tooltip } from "..";
|
||||
import Modal from "./Modal";
|
||||
|
||||
export default {
|
||||
const meta: Meta<typeof Modal> = {
|
||||
title: "ds-react/Modal",
|
||||
component: Modal,
|
||||
parameters: {
|
||||
chromatic: { pauseAnimationAtEnd: true },
|
||||
},
|
||||
};
|
||||
export default meta;
|
||||
|
||||
export const WithUseRef = () => {
|
||||
export const WithUseRef: StoryFn = () => {
|
||||
const ref = useRef<HTMLDialogElement>(null);
|
||||
const ref2 = useRef<HTMLDialogElement>(null);
|
||||
|
||||
@@ -27,6 +29,7 @@ export const WithUseRef = () => {
|
||||
heading: "Title",
|
||||
size: "small",
|
||||
}}
|
||||
closeOnBackdropClick
|
||||
>
|
||||
<Modal.Body>
|
||||
<BodyLong spacing>
|
||||
@@ -45,6 +48,7 @@ export const WithUseRef = () => {
|
||||
onBeforeClose={() =>
|
||||
window.confirm("Are you sure you want to close the modal?")
|
||||
}
|
||||
closeOnBackdropClick
|
||||
aria-labelledby="heading123"
|
||||
>
|
||||
<Modal.Header>
|
||||
@@ -85,7 +89,7 @@ export const WithUseRef = () => {
|
||||
};
|
||||
WithUseRef.storyName = "With useRef";
|
||||
|
||||
export const WithUseState = () => {
|
||||
export const WithUseState: StoryFn = () => {
|
||||
const [open, setOpen] = useState(true);
|
||||
const [open2, setOpen2] = useState(false);
|
||||
|
||||
@@ -111,6 +115,7 @@ export const WithUseState = () => {
|
||||
e.stopPropagation(); // onClose wil propagate to parent modal if not stopped
|
||||
setOpen2(false);
|
||||
}}
|
||||
closeOnBackdropClick
|
||||
aria-label="Nested modal"
|
||||
width={800}
|
||||
>
|
||||
@@ -137,31 +142,126 @@ export const WithUseState = () => {
|
||||
};
|
||||
WithUseState.storyName = "With useState";
|
||||
|
||||
export const EmptyHeader = () => (
|
||||
<div style={{ minWidth: "800px", minHeight: "600px" } /* For Chromatic */}>
|
||||
<Modal open>
|
||||
<Modal.Header />
|
||||
<Modal.Body>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
|
||||
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
|
||||
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
|
||||
commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
|
||||
velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
|
||||
occaecat cupidatat non proident, sunt in culpa qui officia deserunt
|
||||
mollit anim id est laborum.
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
</div>
|
||||
export const EmptyHeader: StoryFn = () => (
|
||||
<Modal open>
|
||||
<Modal.Header />
|
||||
<Modal.Body>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
|
||||
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
|
||||
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
|
||||
commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
|
||||
velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
|
||||
cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
|
||||
est laborum.
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
export const Small = () => (
|
||||
export const Small: StoryFn = () => (
|
||||
<Modal open width="small" header={{ heading: "Simple header" }}>
|
||||
<Modal.Body>Lorem ipsum dolor sit amet.</Modal.Body>
|
||||
</Modal>
|
||||
);
|
||||
Small.storyName = "Size = Small";
|
||||
|
||||
export const MediumWithPortal = () => (
|
||||
export const MediumWithPortal: StoryFn = () => (
|
||||
<Modal open portal width="medium" header={{ heading: "Simple header" }}>
|
||||
<Modal.Body>Lorem ipsum dolor sit amet.</Modal.Body>
|
||||
</Modal>
|
||||
);
|
||||
MediumWithPortal.storyName = "Size = Medium (with portal)";
|
||||
|
||||
export const Large800: StoryFn = () => (
|
||||
<Modal open width={800} header={{ heading: "Simple header" }}>
|
||||
<Modal.Body>Lorem ipsum dolor sit amet.</Modal.Body>
|
||||
</Modal>
|
||||
);
|
||||
Large800.storyName = "Size = 800px";
|
||||
|
||||
export const WithTooltip: StoryFn = () => {
|
||||
const ref = useRef<HTMLDialogElement>(null);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button onClick={() => ref.current?.showModal()}>Open Modal</Button>
|
||||
<Modal
|
||||
open={ref.current ? undefined : true /* initially open */}
|
||||
ref={ref}
|
||||
>
|
||||
<Modal.Body>
|
||||
<div style={{ marginBottom: "1rem" }}>
|
||||
<Tooltip content="This_is_the_first_tooltip">
|
||||
<Button>Test 1</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Tooltip content="This is the second tooltip">
|
||||
<Button>Test 2</Button>
|
||||
</Tooltip>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const ChromaticViewportTesting: StoryFn = () => (
|
||||
<div id="modal-story-wrapper" style={{ width: "100vw", height: "100vh" }}>
|
||||
<style>{`#storybook-root { padding: 0 !important }`}</style>
|
||||
<Modal
|
||||
open
|
||||
header={{ heading: "Chromatic Viewports Testing", label: "Test" }}
|
||||
>
|
||||
<Modal.Body>
|
||||
<BodyLong spacing>
|
||||
This story is tailored for testing the breakpoints with Chromatic.
|
||||
</BodyLong>
|
||||
<BodyLong spacing>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
||||
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
|
||||
minim veniam, quis nostrud exercitation ullamco laboris nisi ut
|
||||
aliquip ex ea commodo consequat.
|
||||
</BodyLong>
|
||||
<BodyLong spacing>
|
||||
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
|
||||
dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
|
||||
proident, sunt in culpa qui officia deserunt mollit anim id est
|
||||
laborum.
|
||||
</BodyLong>
|
||||
<BodyLong>
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem
|
||||
accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae
|
||||
ab illo inventore veritatis et quasi architecto beatae vitae dicta
|
||||
sunt explicabo.
|
||||
</BodyLong>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button>Primary</Button>
|
||||
<Button variant="secondary">Secondary</Button>
|
||||
<Button variant="tertiary">Tertiary</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
ChromaticViewportTesting.parameters = {
|
||||
chromatic: {
|
||||
modes: {
|
||||
mobile_portrait: {
|
||||
viewport: {
|
||||
width: 400,
|
||||
height: 850,
|
||||
},
|
||||
},
|
||||
mobile_landscape: {
|
||||
viewport: {
|
||||
width: 850,
|
||||
height: 400,
|
||||
},
|
||||
},
|
||||
desktop: {
|
||||
viewport: {
|
||||
width: 1280,
|
||||
height: 960,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -42,6 +42,13 @@ export interface ModalProps
|
||||
* Called when the user presses the Esc key, unless `onBeforeClose()` returns `false`.
|
||||
*/
|
||||
onCancel?: React.ReactEventHandler<HTMLDialogElement>;
|
||||
/**
|
||||
* Whether to close when clicking on the backdrop.
|
||||
*
|
||||
* **WARNING:** Users may click outside by accident. Don't use if closing can cause data loss, or the modal contains important info.
|
||||
* @default false
|
||||
*/
|
||||
closeOnBackdropClick?: boolean;
|
||||
/**
|
||||
* @default fit-content (up to 700px)
|
||||
* */
|
||||
|
||||
@@ -18,6 +18,7 @@ import React, {
|
||||
useMemo,
|
||||
useRef,
|
||||
} from "react";
|
||||
import { DateContext } from "../date/context";
|
||||
import { ModalContext } from "../modal/ModalContext";
|
||||
import { mergeRefs, useClientLayoutEffect, useEventListener } from "../util";
|
||||
import PopoverContent, { PopoverContentType } from "./PopoverContent";
|
||||
@@ -73,11 +74,6 @@ 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;
|
||||
/**
|
||||
* Changes placement of the floating element in order to keep it in view.
|
||||
* @default true
|
||||
@@ -124,7 +120,6 @@ export const Popover = forwardRef<HTMLDivElement, PopoverProps>(
|
||||
placement = "top",
|
||||
offset,
|
||||
strategy: userStrategy,
|
||||
bubbleEscape = false,
|
||||
flip: _flip = true,
|
||||
...rest
|
||||
},
|
||||
@@ -132,8 +127,9 @@ export const Popover = forwardRef<HTMLDivElement, PopoverProps>(
|
||||
) => {
|
||||
const arrowRef = useRef<HTMLDivElement | null>(null);
|
||||
const isInModal = useContext(ModalContext) !== null;
|
||||
const isInDatepicker = useContext(DateContext) !== null;
|
||||
const chosenStrategy = userStrategy ?? (isInModal ? "fixed" : "absolute");
|
||||
const chosenFlip = isInModal ? true : _flip;
|
||||
const chosenFlip = isInDatepicker ? false : _flip;
|
||||
|
||||
const {
|
||||
x,
|
||||
@@ -160,11 +156,7 @@ export const Popover = forwardRef<HTMLDivElement, PopoverProps>(
|
||||
|
||||
const { getFloatingProps } = useInteractions([
|
||||
useClick(context),
|
||||
useDismiss(context, {
|
||||
bubbles: {
|
||||
escapeKey: bubbleEscape,
|
||||
},
|
||||
}),
|
||||
useDismiss(context),
|
||||
]);
|
||||
|
||||
useClientLayoutEffect(() => {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
arrow as flArrow,
|
||||
autoUpdate,
|
||||
flip,
|
||||
FloatingPortal,
|
||||
autoUpdate,
|
||||
arrow as flArrow,
|
||||
flip,
|
||||
offset,
|
||||
safePolygon,
|
||||
shift,
|
||||
@@ -14,16 +14,18 @@ import {
|
||||
} from "@floating-ui/react";
|
||||
import cl from "clsx";
|
||||
import React, {
|
||||
HTMLAttributes,
|
||||
cloneElement,
|
||||
forwardRef,
|
||||
HTMLAttributes,
|
||||
useContext,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { ModalContext } from "../modal/ModalContext";
|
||||
import { useProvider } from "../provider";
|
||||
import { Detail } from "../typography";
|
||||
import { mergeRefs, useId } from "../util";
|
||||
import { useProvider } from "../provider";
|
||||
|
||||
export interface TooltipProps extends HTMLAttributes<HTMLDivElement> {
|
||||
/**
|
||||
@@ -110,7 +112,11 @@ export const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(
|
||||
) => {
|
||||
const [open, setOpen] = useState(defaultOpen);
|
||||
const arrowRef = useRef<HTMLDivElement | null>(null);
|
||||
const rootElement = useProvider()?.rootElement;
|
||||
const modalContext = useContext(ModalContext);
|
||||
const providerRootElement = useProvider()?.rootElement;
|
||||
const rootElement = modalContext
|
||||
? modalContext.ref.current
|
||||
: providerRootElement;
|
||||
|
||||
const {
|
||||
x,
|
||||
@@ -133,7 +139,13 @@ export const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(
|
||||
flip({ padding: 5, fallbackPlacements: ["bottom", "top"] }),
|
||||
flArrow({ element: arrowRef, padding: 5 }),
|
||||
],
|
||||
whileElementsMounted: autoUpdate,
|
||||
whileElementsMounted: modalContext
|
||||
? (reference, floating, update) =>
|
||||
// Reduces jumping in Chrome when used in a Modal and it's the first focusable element.
|
||||
// Can be removed when autofocus starts working on <dialog> in Chrome. See also Modal.tsx
|
||||
autoUpdate(reference, floating, update, { animationFrame: true })
|
||||
: autoUpdate,
|
||||
strategy: modalContext ? "fixed" : undefined,
|
||||
});
|
||||
|
||||
const { getReferenceProps, getFloatingProps } = useInteractions([
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import React from "react";
|
||||
import { useMedia } from "../useMedia";
|
||||
|
||||
function TestComponent({ fallback }: { fallback?: boolean }) {
|
||||
const media = useMedia("screen and (min-width: 1024px)", fallback);
|
||||
return <div data-testid="media-id">{`${media}`}</div>;
|
||||
}
|
||||
|
||||
describe("useMedia", () => {
|
||||
test("Should return 'undefined' when no fallback is given", async () => {
|
||||
render(<TestComponent />);
|
||||
expect(screen.getByTestId("media-id").innerHTML).toEqual("undefined");
|
||||
});
|
||||
test("Should return fallback", async () => {
|
||||
render(<TestComponent fallback={true} />);
|
||||
expect(screen.getByTestId("media-id").innerHTML).toEqual("true");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export const noMatchMedia =
|
||||
typeof window !== "undefined" && window.matchMedia === undefined;
|
||||
|
||||
/**
|
||||
* @example useMedia("screen and (min-width: 1024px)")
|
||||
* @param media string
|
||||
* @param fallback boolean
|
||||
* @returns boolean | undefined
|
||||
*/
|
||||
export const useMedia = (
|
||||
media: string,
|
||||
fallback?: boolean
|
||||
): boolean | undefined => {
|
||||
const [matches, setMatches] = useState(fallback);
|
||||
|
||||
useEffect(() => {
|
||||
if (noMatchMedia) {
|
||||
return;
|
||||
}
|
||||
const mediaQueryList = window.matchMedia(media);
|
||||
|
||||
setMatches(mediaQueryList.matches);
|
||||
|
||||
const listener = (evt: MediaQueryListEvent) => {
|
||||
setMatches(evt.matches);
|
||||
};
|
||||
|
||||
mediaQueryList.addEventListener("change", listener);
|
||||
|
||||
return () => {
|
||||
mediaQueryList.removeEventListener("change", listener);
|
||||
};
|
||||
}, [media]);
|
||||
|
||||
return matches;
|
||||
};
|
||||
@@ -1,5 +1,13 @@
|
||||
# @navikt/ds-tailwind
|
||||
|
||||
## 5.9.2
|
||||
|
||||
## 5.9.1
|
||||
|
||||
## 5.9.0
|
||||
|
||||
## 5.8.0
|
||||
|
||||
## 5.7.6
|
||||
|
||||
## 5.7.5
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-tailwind",
|
||||
"version": "5.7.6",
|
||||
"version": "5.9.2",
|
||||
"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": "^5.7.6",
|
||||
"@navikt/ds-tokens": "^5.9.2",
|
||||
"@types/jest": "^29.0.0",
|
||||
"color": "4.2.3",
|
||||
"jest": "^29.0.0",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @navikt/ds-tokens
|
||||
|
||||
## 5.9.2
|
||||
|
||||
## 5.9.1
|
||||
|
||||
## 5.9.0
|
||||
|
||||
## 5.8.0
|
||||
|
||||
## 5.7.6
|
||||
|
||||
## 5.7.5
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-tokens",
|
||||
"version": "5.7.6",
|
||||
"version": "5.9.2",
|
||||
"description": "Design-tokens for NAV designsystem",
|
||||
"author": "Aksel | NAV designsystem team",
|
||||
"keywords": [
|
||||
|
||||
+275
-223
@@ -1,25 +1,77 @@
|
||||
# Changelog
|
||||
|
||||
## 5.7.6
|
||||
## 5.9.2
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- :bug: Datepicker lukker ikke lenger modalen den er i ([#2451](https://github.com/navikt/aksel/pull/2451))
|
||||
|
||||
## 5.9.1
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- :lipstick: Chips.Toggle: Ikon i uvalgt tilstand ([#2395](https://github.com/navikt/aksel/pull/2395))
|
||||
- :lipstick: Modal: Bedre håndtering av mobiler i landskapsmodus ([#2444](https://github.com/navikt/aksel/pull/2444))
|
||||
|
||||
## 5.9.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Datepicker/Monthpicker: Hvis man bruker komponentene i Modal vil Popover bli erstattet med Modal uansett om man er på desktop eller mobil. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
- Datepicker/Monthpicker/Popover: Fjernet `bubbleEscape`-prop. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
- useDatepicker/useMonthPicker/useRangedpicker: Fjernet `openOnFocus`-prop, kan nå bare åpnes ved klikk på date-knapp i input. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
- Datepicker/Monthpicker: Bytter nå automatisk til Modalvisning på mobil. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
* Added useVirtualFocus hook - used in Combobox for now ([#2394](https://github.com/navikt/aksel/pull/2394))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Datepicker/Monthpicker: Hvis man bruker komponentene i Modal vil Popover bli erstattet med Modal uansett om man er på desktop eller mobil. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
- Datepicker/Monthpicker/Popover: Fjernet `bubbleEscape`-prop. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
- useDatepicker/useMonthPicker/useRangedpicker: Fjernet `openOnFocus`-prop, kan nå bare åpnes ved klikk på date-knapp i input. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
- Datepicker/Monthpicker: Bytter nå automatisk til Modalvisning på mobil. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
## 5.8.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- :sparkles: Modal: Støtte for å lukke ved klikk utenfor ([#2386](https://github.com/navikt/aksel/pull/2386))
|
||||
|
||||
* Autocomplete in combobox will not change formatting of the letters while being typed, but will use the casing of the autocompleted word when selecting the option. ([#2207](https://github.com/navikt/aksel/pull/2207))
|
||||
|
||||
* Accordion: Fikset dom-validering ved bruk av `div` i `button`. ([#2426](https://github.com/navikt/aksel/pull/2426))
|
||||
|
||||
* :bug: Modal: Bedre støtte for Tooltip i Modal ([#2429](https://github.com/navikt/aksel/pull/2429))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- :bug: Modal: Bedre støtte for Tooltip i Modal ([#2429](https://github.com/navikt/aksel/pull/2429))
|
||||
|
||||
## 5.7.6
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- :lipstick: Chips.Toggle: Ikon i uvalgt tilstand ([#2395](https://github.com/navikt/aksel/pull/2395))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- :lipstick: Chips.Toggle: Ikon i uvalgt tilstand ([#2395](https://github.com/navikt/aksel/pull/2395))
|
||||
|
||||
## 5.7.5
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Checkbox, Radio: Transparent-border fungerer nå bedre på mørkere bakgrunner ([#2388](https://github.com/navikt/aksel/pull/2388))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Loader: Har nå riktig `ref`-type. ([#2391](https://github.com/navikt/aksel/pull/2391))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Checkbox, Radio: Transparent-border fungerer nå bedre på mørkere bakgrunner ([#2388](https://github.com/navikt/aksel/pull/2388))
|
||||
|
||||
## 5.7.4
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -28,6 +80,12 @@
|
||||
|
||||
## 5.7.3
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Forms: Fikset bug der size='small' ikke oppdaterte typografi. ([#2372](https://github.com/navikt/aksel/pull/2372))
|
||||
|
||||
- HelpText: Knapp er visuelt mindre. Klikkflate er fortsatt 24px ([#2376](https://github.com/navikt/aksel/pull/2376))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Ny spacing token på 0.375rem ([#2370](https://github.com/navikt/aksel/pull/2370))
|
||||
@@ -36,12 +94,6 @@
|
||||
|
||||
- HelpText: Knapp er visuelt mindre. Klikkflate er fortsatt 24px ([#2376](https://github.com/navikt/aksel/pull/2376))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Forms: Fikset bug der size='small' ikke oppdaterte typografi. ([#2372](https://github.com/navikt/aksel/pull/2372))
|
||||
|
||||
- HelpText: Knapp er visuelt mindre. Klikkflate er fortsatt 24px ([#2376](https://github.com/navikt/aksel/pull/2376))
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- Ny spacing token på 0.375rem ([#2370](https://github.com/navikt/aksel/pull/2370))
|
||||
@@ -54,11 +106,11 @@
|
||||
|
||||
## 5.7.1
|
||||
|
||||
### @navikt/ds-css
|
||||
### @navikt/ds-react
|
||||
|
||||
- Radio: readonly håndterer nå hover-state bedre ([#2363](https://github.com/navikt/aksel/pull/2363))
|
||||
|
||||
### @navikt/ds-react
|
||||
### @navikt/ds-css
|
||||
|
||||
- Radio: readonly håndterer nå hover-state bedre ([#2363](https://github.com/navikt/aksel/pull/2363))
|
||||
|
||||
@@ -68,16 +120,16 @@
|
||||
|
||||
- Ikoner: Nye ikoner `ChevronLeftFirstCircle` og `ChevronRightLastCircle` ([#2365](https://github.com/navikt/aksel/pull/2365))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- :lipstick: CopyButton: Justert padding, gap og animasjon ([#2355](https://github.com/navikt/aksel/pull/2355))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Chores: Ryddet opp i sirkulære depdendencies ([#2366](https://github.com/navikt/aksel/pull/2366))
|
||||
|
||||
- :lipstick: CopyButton: Justert padding, gap og animasjon ([#2355](https://github.com/navikt/aksel/pull/2355))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- :lipstick: CopyButton: Justert padding, gap og animasjon ([#2355](https://github.com/navikt/aksel/pull/2355))
|
||||
|
||||
## 5.6.5
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -118,24 +170,6 @@
|
||||
|
||||
* Ikoner: Nye ikoner `ClipboardCheckmark` og `ClipboardLink` ([#2307](https://github.com/navikt/aksel/pull/2307))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Primitives: ny komponent Bleed ([#2278](https://github.com/navikt/aksel/pull/2278))
|
||||
|
||||
* :recycle: Box: Forenkling av CSS-variabler ([#2279](https://github.com/navikt/aksel/pull/2279))
|
||||
|
||||
* Accordion: Har nå 0.25rem padding-top mellom innhold og heading. ([#2310](https://github.com/navikt/aksel/pull/2310))
|
||||
|
||||
* Added cursor:pointer to Select component ([#2293](https://github.com/navikt/aksel/pull/2293))
|
||||
|
||||
* :bug: Box: sett border-radius riktig ([#2329](https://github.com/navikt/aksel/pull/2329))
|
||||
|
||||
* Stack: Kan nå endre direction, justify og align ved brekkpunkt. `Stack` er også nå en egen komponent sammen med `HStack` og `VStack`. ([#2286](https://github.com/navikt/aksel/pull/2286))
|
||||
|
||||
* Datepicker: Tilpasset padding og sizing på mobil. ([#2311](https://github.com/navikt/aksel/pull/2311))
|
||||
|
||||
* MonthPicker: Tilpasset padding og sizing på mobil. ([#2311](https://github.com/navikt/aksel/pull/2311))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- :recycle: Refaktorering og småfikser ([#2265](https://github.com/navikt/aksel/pull/2265))
|
||||
@@ -156,15 +190,33 @@
|
||||
|
||||
* Remove tabIndex="-1" on Popover to fix VoiceOver navigation issue inside Popover ([#2303](https://github.com/navikt/aksel/pull/2303))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Primitives: ny komponent Bleed ([#2278](https://github.com/navikt/aksel/pull/2278))
|
||||
|
||||
* :recycle: Box: Forenkling av CSS-variabler ([#2279](https://github.com/navikt/aksel/pull/2279))
|
||||
|
||||
* Accordion: Har nå 0.25rem padding-top mellom innhold og heading. ([#2310](https://github.com/navikt/aksel/pull/2310))
|
||||
|
||||
* Added cursor:pointer to Select component ([#2293](https://github.com/navikt/aksel/pull/2293))
|
||||
|
||||
* :bug: Box: sett border-radius riktig ([#2329](https://github.com/navikt/aksel/pull/2329))
|
||||
|
||||
* Stack: Kan nå endre direction, justify og align ved brekkpunkt. `Stack` er også nå en egen komponent sammen med `HStack` og `VStack`. ([#2286](https://github.com/navikt/aksel/pull/2286))
|
||||
|
||||
* Datepicker: Tilpasset padding og sizing på mobil. ([#2311](https://github.com/navikt/aksel/pull/2311))
|
||||
|
||||
* MonthPicker: Tilpasset padding og sizing på mobil. ([#2311](https://github.com/navikt/aksel/pull/2311))
|
||||
|
||||
## 5.5.0
|
||||
|
||||
### @navikt/ds-css
|
||||
### @navikt/ds-react
|
||||
|
||||
- Box: Ny primitive, erstatter dagens `Panel` ([#2195](https://github.com/navikt/aksel/pull/2195))
|
||||
|
||||
* Skeleton: Width fungerer nå med inline-variant av Skeleton ([#2273](https://github.com/navikt/aksel/pull/2273))
|
||||
|
||||
### @navikt/ds-react
|
||||
### @navikt/ds-css
|
||||
|
||||
- Box: Ny primitive, erstatter dagens `Panel` ([#2195](https://github.com/navikt/aksel/pull/2195))
|
||||
|
||||
@@ -182,18 +234,18 @@
|
||||
|
||||
## 5.4.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Primitives: Nye komponenter `Show` og `Hide` er lagt til. ([#2222](https://github.com/navikt/aksel/pull/2222))
|
||||
|
||||
* ErrorSummary: Har nå 8px border-radius ([#2256](https://github.com/navikt/aksel/pull/2256))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Primitives: Nye komponenter `Show` og `Hide` er lagt til. ([#2222](https://github.com/navikt/aksel/pull/2222))
|
||||
|
||||
* Search: Kjører nå bare preventDefault ved Escape når `input` inneholder tekst ([#2245](https://github.com/navikt/aksel/pull/2245))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Primitives: Nye komponenter `Show` og `Hide` er lagt til. ([#2222](https://github.com/navikt/aksel/pull/2222))
|
||||
|
||||
* ErrorSummary: Har nå 8px border-radius ([#2256](https://github.com/navikt/aksel/pull/2256))
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- Tokens: `data-theme="light"` vil nå tilbakestille theming ([#2238](https://github.com/navikt/aksel/pull/2238))
|
||||
@@ -216,31 +268,25 @@
|
||||
|
||||
## 5.3.3
|
||||
|
||||
### @navikt/ds-css
|
||||
### @navikt/ds-react
|
||||
|
||||
- HGrid: Har nå `align`-prop for bedre kontroll over child-elementer ([#2242](https://github.com/navikt/aksel/pull/2242))
|
||||
|
||||
### @navikt/ds-react
|
||||
### @navikt/ds-css
|
||||
|
||||
- HGrid: Har nå `align`-prop for bedre kontroll over child-elementer ([#2242](https://github.com/navikt/aksel/pull/2242))
|
||||
|
||||
## 5.3.2
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Skeleton: Lagt til as-prop for inline brk av Skeleton med span ([#2239](https://github.com/navikt/aksel/pull/2239))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Skeleton: Lagt til as-prop for inline brk av Skeleton med span ([#2239](https://github.com/navikt/aksel/pull/2239))
|
||||
|
||||
## 5.3.1
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- :bug: fix missing accordion bottom box-shadow on last element (when opened) ([#2229](https://github.com/navikt/aksel/pull/2229))
|
||||
- Skeleton: Lagt til as-prop for inline brk av Skeleton med span ([#2239](https://github.com/navikt/aksel/pull/2239))
|
||||
|
||||
- :lipstick: GuidePanel: justert design ([#2227](https://github.com/navikt/aksel/pull/2227))
|
||||
## 5.3.1
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
@@ -250,11 +296,13 @@
|
||||
|
||||
- :bug: DatePicker: Riktig skriftstørrelse på small input ([#2232](https://github.com/navikt/aksel/pull/2232))
|
||||
|
||||
## 5.3.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Typography: Oppdatert med typo-klasser for `textColor`, `weight`, `align`, `visuallyHidden` og `truncated`. ([#2211](https://github.com/navikt/aksel/pull/2211))
|
||||
- :bug: fix missing accordion bottom box-shadow on last element (when opened) ([#2229](https://github.com/navikt/aksel/pull/2229))
|
||||
|
||||
- :lipstick: GuidePanel: justert design ([#2227](https://github.com/navikt/aksel/pull/2227))
|
||||
|
||||
## 5.3.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
@@ -266,6 +314,10 @@
|
||||
|
||||
* :wheelchair: Textarea: Skjermleser-spesifikk tekst leses opp sammenhengende ([#2216](https://github.com/navikt/aksel/pull/2216))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Typography: Oppdatert med typo-klasser for `textColor`, `weight`, `align`, `visuallyHidden` og `truncated`. ([#2211](https://github.com/navikt/aksel/pull/2211))
|
||||
|
||||
## 5.2.1
|
||||
|
||||
### @navikt/ds-css
|
||||
@@ -278,12 +330,6 @@
|
||||
|
||||
- Ikoner: Oppdatert ikonpakke ([#2197](https://github.com/navikt/aksel/pull/2197))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- CSS: Popover har nå 8px border-radius. Fikset padding-bug i Select-small. ([#2219](https://github.com/navikt/aksel/pull/2219))
|
||||
|
||||
- Hovering over combobox dropdown will move selection/focus in the list, so we don't end up with a split focus, and reversely when moving focus while hovering ([#2193](https://github.com/navikt/aksel/pull/2193))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Button: Ved bruk av `as`-prop vil `role="button"` nå bli lagt til. Native `onKeyUp` for `Space` er også implementert slik at standard `button`-interaksjon vil være likere uansett html-tag. ([#2154](https://github.com/navikt/aksel/pull/2154))
|
||||
@@ -292,13 +338,13 @@
|
||||
|
||||
- Hovering over combobox dropdown will move selection/focus in the list, so we don't end up with a split focus, and reversely when moving focus while hovering ([#2193](https://github.com/navikt/aksel/pull/2193))
|
||||
|
||||
## 5.1.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- :lipstick: Modal: fjern kantlinje ([#2210](https://github.com/navikt/aksel/pull/2210))
|
||||
- CSS: Popover har nå 8px border-radius. Fikset padding-bug i Select-small. ([#2219](https://github.com/navikt/aksel/pull/2219))
|
||||
|
||||
- Tokens: Fikset table-token ([#2204](https://github.com/navikt/aksel/pull/2204))
|
||||
- Hovering over combobox dropdown will move selection/focus in the list, so we don't end up with a split focus, and reversely when moving focus while hovering ([#2193](https://github.com/navikt/aksel/pull/2193))
|
||||
|
||||
## 5.1.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
@@ -308,18 +354,24 @@
|
||||
|
||||
* Datepicker: Input setter ikke nå aria-controls før popover åpnes ([#2213](https://github.com/navikt/aksel/pull/2213))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- :lipstick: Modal: fjern kantlinje ([#2210](https://github.com/navikt/aksel/pull/2210))
|
||||
|
||||
- Tokens: Fikset table-token ([#2204](https://github.com/navikt/aksel/pull/2204))
|
||||
|
||||
## 5.0.3
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- ConfirmationPanel: Fikset opplesing av feilmelding ([#2199](https://github.com/navikt/aksel/pull/2199))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- ConfirmationPanel: Fikset opplesing av feilmelding ([#2199](https://github.com/navikt/aksel/pull/2199))
|
||||
|
||||
- Checkbox: Fikset checkbox-alignment ved bruk av tailwindcss ([#2199](https://github.com/navikt/aksel/pull/2199))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- ConfirmationPanel: Fikset opplesing av feilmelding ([#2199](https://github.com/navikt/aksel/pull/2199))
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- ConfirmationPanel: Fikset opplesing av feilmelding ([#2199](https://github.com/navikt/aksel/pull/2199))
|
||||
@@ -340,17 +392,6 @@
|
||||
|
||||
## 5.0.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Oppdatert Modal - Se [Migrering](https://aksel.nav.no/grunnleggende/kode/migrering) ([#2135](https://github.com/navikt/aksel/pull/2135))
|
||||
|
||||
- :sparkles: Støtte for header og footer
|
||||
- :boom: Bruker nå native dialog i stedet for react-modal (med polyfill for eldre nettlesere)
|
||||
|
||||
* Table: ExpandableRow har oppdatert knapp for å matche Accordion og ExpansionCard ([#2178](https://github.com/navikt/aksel/pull/2178))
|
||||
|
||||
* Table: Alle størrelser har justert padding. Small-size table bruker nå også standard typografi-størrelse (18px). ([#2178](https://github.com/navikt/aksel/pull/2178))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Oppdatert Modal - Se [Migrering](https://aksel.nav.no/grunnleggende/kode/migrering) ([#2135](https://github.com/navikt/aksel/pull/2135))
|
||||
@@ -363,6 +404,17 @@
|
||||
|
||||
* Table: Har lagt til ny size: 'large'. ([#2178](https://github.com/navikt/aksel/pull/2178))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Oppdatert Modal - Se [Migrering](https://aksel.nav.no/grunnleggende/kode/migrering) ([#2135](https://github.com/navikt/aksel/pull/2135))
|
||||
|
||||
- :sparkles: Støtte for header og footer
|
||||
- :boom: Bruker nå native dialog i stedet for react-modal (med polyfill for eldre nettlesere)
|
||||
|
||||
* Table: ExpandableRow har oppdatert knapp for å matche Accordion og ExpansionCard ([#2178](https://github.com/navikt/aksel/pull/2178))
|
||||
|
||||
* Table: Alle størrelser har justert padding. Small-size table bruker nå også standard typografi-størrelse (18px). ([#2178](https://github.com/navikt/aksel/pull/2178))
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- Gray: Gråfarger er nå mindre varme ([#2092](https://github.com/navikt/aksel/pull/2092))
|
||||
@@ -371,25 +423,15 @@
|
||||
|
||||
## 4.12.1
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Added red border to Combobox in error state ([#2184](https://github.com/navikt/aksel/pull/2184))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Added red border to Combobox in error state ([#2184](https://github.com/navikt/aksel/pull/2184))
|
||||
|
||||
## 4.12.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- CopyButton: Har nå prop 'iconPosition' for å høyre/venstre aligne ikon ([#2173](https://github.com/navikt/aksel/pull/2173))
|
||||
- Added red border to Combobox in error state ([#2184](https://github.com/navikt/aksel/pull/2184))
|
||||
|
||||
* List: Fikset sentrering, margins ([#2168](https://github.com/navikt/aksel/pull/2168))
|
||||
|
||||
* Checkbox: Checkmark er nå SVG-ikon og ikke Base64 ([#2171](https://github.com/navikt/aksel/pull/2171))
|
||||
|
||||
* Alert: Fikset alignment av status-ikon mot tekst ([#2179](https://github.com/navikt/aksel/pull/2179))
|
||||
## 4.12.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
@@ -409,6 +451,16 @@
|
||||
|
||||
* Combobox: Lukker nå nedtrekksmeny hvis man legger til ny option i singleselect ([#2177](https://github.com/navikt/aksel/pull/2177))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- CopyButton: Har nå prop 'iconPosition' for å høyre/venstre aligne ikon ([#2173](https://github.com/navikt/aksel/pull/2173))
|
||||
|
||||
* List: Fikset sentrering, margins ([#2168](https://github.com/navikt/aksel/pull/2168))
|
||||
|
||||
* Checkbox: Checkmark er nå SVG-ikon og ikke Base64 ([#2171](https://github.com/navikt/aksel/pull/2171))
|
||||
|
||||
* Alert: Fikset alignment av status-ikon mot tekst ([#2179](https://github.com/navikt/aksel/pull/2179))
|
||||
|
||||
## 4.11.2
|
||||
|
||||
### @navikt/ds-css
|
||||
@@ -423,14 +475,6 @@
|
||||
|
||||
## 4.11.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- HGrid: Ny komponent 'HGrid' er ute i Beta: [Dokumentasjon](https://aksel.nav.no/komponenter/core/hgrid) ([#1838](https://github.com/navikt/aksel/pull/1838))
|
||||
|
||||
* List: Fikset alignment av ikoner ([#2149](https://github.com/navikt/aksel/pull/2149))
|
||||
|
||||
* Font: La til egen font for semibold italic for bedre skalering cross-browser ([#2150](https://github.com/navikt/aksel/pull/2150))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- HGrid: Ny komponent 'HGrid' er ute i Beta: [Dokumentasjon](https://aksel.nav.no/komponenter/core/hgrid) ([#1838](https://github.com/navikt/aksel/pull/1838))
|
||||
@@ -439,6 +483,14 @@
|
||||
|
||||
* Grid: Markert som deprecated. Bruk nye 'HGrid' ([#2153](https://github.com/navikt/aksel/pull/2153))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- HGrid: Ny komponent 'HGrid' er ute i Beta: [Dokumentasjon](https://aksel.nav.no/komponenter/core/hgrid) ([#1838](https://github.com/navikt/aksel/pull/1838))
|
||||
|
||||
* List: Fikset alignment av ikoner ([#2149](https://github.com/navikt/aksel/pull/2149))
|
||||
|
||||
* Font: La til egen font for semibold italic for bedre skalering cross-browser ([#2150](https://github.com/navikt/aksel/pull/2150))
|
||||
|
||||
## 4.10.2
|
||||
|
||||
### @navikt/ds-css
|
||||
@@ -447,19 +499,15 @@
|
||||
|
||||
## 4.10.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Link: La til 'variant', 'underline' og 'inlineText'-prop ([#2093](https://github.com/navikt/aksel/pull/2093))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Link: La til 'variant', 'underline' og 'inlineText'-prop ([#2093](https://github.com/navikt/aksel/pull/2093))
|
||||
|
||||
## 4.9.1
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Tweaks to combobox - updated example, small bugfixes, better affordance for selected options and added flag for "isAddedByUser" to onToggleSelected ([#2144](https://github.com/navikt/aksel/pull/2144))
|
||||
- Link: La til 'variant', 'underline' og 'inlineText'-prop ([#2093](https://github.com/navikt/aksel/pull/2093))
|
||||
|
||||
## 4.9.1
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
@@ -467,36 +515,40 @@
|
||||
|
||||
- Tweaks to combobox - updated example, small bugfixes, better affordance for selected options and added flag for "isAddedByUser" to onToggleSelected ([#2144](https://github.com/navikt/aksel/pull/2144))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Tweaks to combobox - updated example, small bugfixes, better affordance for selected options and added flag for "isAddedByUser" to onToggleSelected ([#2144](https://github.com/navikt/aksel/pull/2144))
|
||||
|
||||
## 4.9.0
|
||||
|
||||
### @navikt/ds-css
|
||||
### @navikt/ds-react
|
||||
|
||||
- Table: ExpandableRow kan nå åpnes med 'expandOnRowClick'-prop ([#2127](https://github.com/navikt/aksel/pull/2127))
|
||||
|
||||
### @navikt/ds-react
|
||||
### @navikt/ds-css
|
||||
|
||||
- Table: ExpandableRow kan nå åpnes med 'expandOnRowClick'-prop ([#2127](https://github.com/navikt/aksel/pull/2127))
|
||||
|
||||
## 4.8.0
|
||||
|
||||
### @navikt/ds-css
|
||||
### @navikt/ds-react
|
||||
|
||||
- Nye komponenter `VStack`, `HStack` og `Spacer` for å enklere kunne lage layout med flexbox og spacing-variabler. ([#2040](https://github.com/navikt/aksel/pull/2040))
|
||||
|
||||
### @navikt/ds-react
|
||||
### @navikt/ds-css
|
||||
|
||||
- Nye komponenter `VStack`, `HStack` og `Spacer` for å enklere kunne lage layout med flexbox og spacing-variabler. ([#2040](https://github.com/navikt/aksel/pull/2040))
|
||||
|
||||
## 4.7.4
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Oppdatert bruk av REM i komponenter for forbedret utrykk ved font-scaling i browser ([#2126](https://github.com/navikt/aksel/pull/2126))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Textfield: La til type 'time' som tilgjengelig option ([#2137](https://github.com/navikt/aksel/pull/2137))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Oppdatert bruk av REM i komponenter for forbedret utrykk ved font-scaling i browser ([#2126](https://github.com/navikt/aksel/pull/2126))
|
||||
|
||||
## 4.7.3
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -505,11 +557,11 @@
|
||||
|
||||
## 4.7.2
|
||||
|
||||
### @navikt/ds-css
|
||||
### @navikt/ds-react
|
||||
|
||||
- Stepper: Fikset `hotizontal`-bug når step var `completed` ([#2116](https://github.com/navikt/aksel/pull/2116))
|
||||
|
||||
### @navikt/ds-react
|
||||
### @navikt/ds-css
|
||||
|
||||
- Stepper: Fikset `hotizontal`-bug når step var `completed` ([#2116](https://github.com/navikt/aksel/pull/2116))
|
||||
|
||||
@@ -521,10 +573,6 @@
|
||||
|
||||
## 4.7.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Ny komponent Combobox! ([#1868](https://github.com/navikt/aksel/pull/1868))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Ny komponent Combobox! ([#1868](https://github.com/navikt/aksel/pull/1868))
|
||||
@@ -533,38 +581,42 @@
|
||||
|
||||
* Combobox post-release tweaks ([#2112](https://github.com/navikt/aksel/pull/2112))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Ny komponent Combobox! ([#1868](https://github.com/navikt/aksel/pull/1868))
|
||||
|
||||
## 4.6.1
|
||||
|
||||
### @navikt/ds-css
|
||||
### @navikt/ds-react
|
||||
|
||||
- Skjema: Labels og Legends bruker nå inline-flex når readOnly er satt ([#2089](https://github.com/navikt/aksel/pull/2089))
|
||||
|
||||
### @navikt/ds-react
|
||||
### @navikt/ds-css
|
||||
|
||||
- Skjema: Labels og Legends bruker nå inline-flex når readOnly er satt ([#2089](https://github.com/navikt/aksel/pull/2089))
|
||||
|
||||
## 4.6.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Skjema: De fleste skjemakomponenter har nå styling for `readOnly`-state ([#2080](https://github.com/navikt/aksel/pull/2080))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Skjema: De fleste skjemakomponenter støtter nå `readOnly`-state ([#2080](https://github.com/navikt/aksel/pull/2080))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Skjema: De fleste skjemakomponenter har nå styling for `readOnly`-state ([#2080](https://github.com/navikt/aksel/pull/2080))
|
||||
|
||||
## 4.5.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- - Alert: La til `closeButton`-prop ([#2079](https://github.com/navikt/aksel/pull/2079))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- - Alert: La til `closeButton`-prop ([#2079](https://github.com/navikt/aksel/pull/2079))
|
||||
|
||||
* - Button: Fikset outline-bug i tertiary-variant ved `:active`-state ([#2079](https://github.com/navikt/aksel/pull/2079))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- - Alert: La til `closeButton`-prop ([#2079](https://github.com/navikt/aksel/pull/2079))
|
||||
|
||||
## 4.4.2
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -579,46 +631,46 @@
|
||||
|
||||
## 4.4.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Shadow-tokens er oppdatert: Datepicker har nå ikke border, Modal bruker shadow-xlarge, LinkCard bruker shadow-xsmall ([PR](https://github.com/navikt/aksel/pull/2041))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Fikset klassenavn brukt for popover i Datepicker og Monthpicker ([PR](https://github.com/navikt/aksel/pull/2041))
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- Shadow-tokens er oppdatert til mer tydeligere varianter ([PR](https://github.com/navikt/aksel/pull/2041))
|
||||
|
||||
### @navikt/ds-tailwind
|
||||
|
||||
- Shadow-tokens er oppdatert ([PR](https://github.com/navikt/aksel/pull/2041))
|
||||
|
||||
- Oppdatert text-subtle og icon-subtle tokens til 700-skala (tidligere 600). ([PR](https://github.com/navikt/aksel/pull/2036))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Shadow-tokens er oppdatert: Datepicker har nå ikke border, Modal bruker shadow-xlarge, LinkCard bruker shadow-xsmall ([PR](https://github.com/navikt/aksel/pull/2041))
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- Shadow-tokens er oppdatert til mer tydeligere varianter ([PR](https://github.com/navikt/aksel/pull/2041))
|
||||
|
||||
## 4.3.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Popover og Helptext har nå luft mot siden av skjerm på mindre flater ([PR](https://github.com/navikt/aksel/pull/2069))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Popover/Helptext: Maksbredde på 100vw - 1.5rem. Treffer ikke lengre kanten på skjermen. ([PR](https://github.com/navikt/aksel/pull/2069))
|
||||
|
||||
* ExpansionCard: Ved nesting av komponetene fikk man styling fra parent ([PR](https://github.com/navikt/aksel/pull/2067))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Popover og Helptext har nå luft mot siden av skjerm på mindre flater ([PR](https://github.com/navikt/aksel/pull/2069))
|
||||
|
||||
## 4.2.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Chat: `small`-size, innebygde varianter for farge og oppdatert utseende. ([PR](https://github.com/navikt/aksel/pull/2048))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Oppdatert Chat: `size` og `variant`-prop, optional `avatar`, uu og ui-forbedringer ([PR](https://github.com/navikt/aksel/pull/2048))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Chat: `small`-size, innebygde varianter for farge og oppdatert utseende. ([PR](https://github.com/navikt/aksel/pull/2048))
|
||||
|
||||
## 4.1.7
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -671,11 +723,9 @@
|
||||
|
||||
## 4.1.0
|
||||
|
||||
### @navikt/ds-css
|
||||
### @navikt/aksel-stylelint
|
||||
|
||||
- Chips: `neutral` og `action`-varianter av Chips.Toggle. Opt-out mulighet for Checkmark. Oppdatert checkmark-ikon ([PR](https://github.com/navikt/aksel/pull/2035))
|
||||
|
||||
- Alle description-felter på fieldsets har nå `text-subtle` som farge. ([PR](https://github.com/navikt/aksel/pull/2036))
|
||||
- Deprecated klassenavn `navds-chips--icon-left` ([PR](https://github.com/navikt/aksel/pull/2035))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
@@ -687,20 +737,22 @@
|
||||
|
||||
* Accordion: La til `indent`-prop ([PR](https://github.com/navikt/aksel/pull/2027))
|
||||
|
||||
### @navikt/ds-tailwind
|
||||
|
||||
- :tada: Fargetokens for datavisualisering. ([PR](https://github.com/navikt/aksel/pull/2032))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Chips: `neutral` og `action`-varianter av Chips.Toggle. Opt-out mulighet for Checkmark. Oppdatert checkmark-ikon ([PR](https://github.com/navikt/aksel/pull/2035))
|
||||
|
||||
- Alle description-felter på fieldsets har nå `text-subtle` som farge. ([PR](https://github.com/navikt/aksel/pull/2036))
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- :tada: Fargetokens for datavisualisering. ([PR](https://github.com/navikt/aksel/pull/2032))
|
||||
|
||||
- Oppdatert text-subtle og icon-subtle tokens til 700-skala (tidligere 600). ([PR](https://github.com/navikt/aksel/pull/2036))
|
||||
|
||||
### @navikt/ds-tailwind
|
||||
|
||||
- :tada: Fargetokens for datavisualisering. ([PR](https://github.com/navikt/aksel/pull/2032))
|
||||
|
||||
### @navikt/aksel-stylelint
|
||||
|
||||
- Deprecated klassenavn `navds-chips--icon-left` ([PR](https://github.com/navikt/aksel/pull/2035))
|
||||
|
||||
## 4.0.0
|
||||
|
||||
### @navikt/aksel
|
||||
@@ -709,6 +761,12 @@
|
||||
|
||||
- Codemods for migrering fra `@navikt/ds-react-internal` til `@navikt/ds-react` [Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h3b8538e1981d) ([PR](https://github.com/navikt/aksel/pull/2026))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Datepicker og Monthpicker er ute av beta. [Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h4ef68ae743b0) ([PR](https://github.com/navikt/aksel/pull/2026))
|
||||
|
||||
- Komponenter i `@navikt/ds-react-internal` er flyttet over til `@navikt/ds-react`. [Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h3b8538e1981d) ([PR](https://github.com/navikt/aksel/pull/2026))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- All styling fra `@navikt/ds-css-internal` er flyttet til `@navikt/ds-css`. ([PR](https://github.com/navikt/aksel/pull/2026))
|
||||
@@ -719,12 +777,6 @@
|
||||
|
||||
- Styling for flyttede komponenter finnes nå på CDN [Guide](https://aksel.nav.no/grunnleggende/kode/css-import)
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Datepicker og Monthpicker er ute av beta. [Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h4ef68ae743b0) ([PR](https://github.com/navikt/aksel/pull/2026))
|
||||
|
||||
- Komponenter i `@navikt/ds-react-internal` er flyttet over til `@navikt/ds-react`. [Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h3b8538e1981d) ([PR](https://github.com/navikt/aksel/pull/2026))
|
||||
|
||||
## 3.4.2
|
||||
|
||||
### @navikt/aksel-stylelint
|
||||
@@ -739,17 +791,17 @@
|
||||
|
||||
## 3.4.0
|
||||
|
||||
### @navikt/ds-css
|
||||
### @navikt/aksel-stylelint
|
||||
|
||||
- Tag: `moderate`-variant ([PR](https://github.com/navikt/aksel/pull/2010))
|
||||
- Stylelint-pakke for Aksel ([PR](https://github.com/navikt/aksel/pull/1973))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Tag: `moderate`-variant ([PR](https://github.com/navikt/aksel/pull/2010))
|
||||
|
||||
### @navikt/aksel-stylelint
|
||||
### @navikt/ds-css
|
||||
|
||||
- Stylelint-pakke for Aksel ([PR](https://github.com/navikt/aksel/pull/1973))
|
||||
- Tag: `moderate`-variant ([PR](https://github.com/navikt/aksel/pull/2010))
|
||||
|
||||
## 3.3.1
|
||||
|
||||
@@ -775,18 +827,18 @@
|
||||
|
||||
- Nye ikoner `FileParagraph` og `FilePlus` ([PR](https://github.com/navikt/aksel/pull/1998))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Datepicker: Fikset small-variant av datepicker-input. ([PR](https://github.com/navikt/aksel/pull/1993))
|
||||
|
||||
- CopyButton: `xsmall`-size for bruk i tabeller ([PR](https://github.com/navikt/aksel/pull/1994))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Datepicker: Oppdatert small-variant av Datepicker.Input. UI-oppdatert samtidig. ([PR](https://github.com/navikt/aksel/pull/1993))
|
||||
|
||||
- CopyButton: `xsmall`-variant for bruk i tabeller ([PR](https://github.com/navikt/aksel/pull/1994))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Datepicker: Fikset small-variant av datepicker-input. ([PR](https://github.com/navikt/aksel/pull/1993))
|
||||
|
||||
- CopyButton: `xsmall`-size for bruk i tabeller ([PR](https://github.com/navikt/aksel/pull/1994))
|
||||
|
||||
## 3.2.2
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -807,16 +859,16 @@
|
||||
|
||||
## 3.1.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Oppdatert Label og Description spacing for alle skjemakomponenter ([PR](https://github.com/navikt/aksel/pull/1967))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Alle komponenter bruker nå default `:focus-visible` for fokusmarkering. Medfølger også fallback for `:focus` ([PR](https://github.com/navikt/aksel/pull/1966))
|
||||
|
||||
* Oppdatert Label og Description spacing for skjemakomponenter ([PR](https://github.com/navikt/aksel/pull/1967))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Oppdatert Label og Description spacing for alle skjemakomponenter ([PR](https://github.com/navikt/aksel/pull/1967))
|
||||
|
||||
## 3.0.1
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -827,16 +879,16 @@
|
||||
|
||||
## 3.0.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Accordion: Chevron er left-aligned, deler av styling er refaktorert, `size`-props og `neutral`-variant ([PR](https://github.com/navikt/aksel/pull/1964))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Accordion: left-aligner chevron, `neutral`-variant, `size`-prop ([PR](https://github.com/navikt/aksel/pull/1964))
|
||||
|
||||
- Bruker nå nå `@navikt/aksel-icons` for interne ikoner ([PR](https://github.com/navikt/aksel/pull/1964))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Accordion: Chevron er left-aligned, deler av styling er refaktorert, `size`-props og `neutral`-variant ([PR](https://github.com/navikt/aksel/pull/1964))
|
||||
|
||||
## 2.9.8
|
||||
|
||||
### @navikt/aksel-icons
|
||||
@@ -931,14 +983,14 @@
|
||||
|
||||
## 2.8.7
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Button: token for tertiary
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Fikset typografi-bruk for `Radio` og `Checkbox`.
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Button: token for tertiary
|
||||
|
||||
## 2.8.6
|
||||
|
||||
### @navikt/ds-css
|
||||
@@ -967,14 +1019,14 @@
|
||||
|
||||
## 2.8.2
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- ExpansioCard: Oppdatert typografi ([PR](https://github.com/navikt/aksel/pull/1870))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- ExpansioCard: Oppdatert typografibruk ([PR](https://github.com/navikt/aksel/pull/1870))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- ExpansioCard: Oppdatert typografi ([PR](https://github.com/navikt/aksel/pull/1870))
|
||||
|
||||
## 2.8.1
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -1037,14 +1089,14 @@
|
||||
|
||||
## 2.5.0
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- Breakpoint-tokens lagt til ([PR](https://github.com/navikt/aksel/pull/1832))
|
||||
|
||||
### @navikt/ds-tailwind
|
||||
|
||||
- Breakpoint-tokens er lagt til. Overskriver native tailwind-breakpoints ([PR](https://github.com/navikt/aksel/pull/1832))
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- Breakpoint-tokens lagt til ([PR](https://github.com/navikt/aksel/pull/1832))
|
||||
|
||||
## 2.4.3
|
||||
|
||||
### @navikt/ds-css
|
||||
@@ -1053,14 +1105,14 @@
|
||||
|
||||
## 2.4.2
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- List: La til støtte for nesting ([PR](https://github.com/navikt/aksel/pull/1823))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- List: la til støtte for nestede lister ([PR](https://github.com/navikt/aksel/pull/1823))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- List: La til støtte for nesting ([PR](https://github.com/navikt/aksel/pull/1823))
|
||||
|
||||
## 2.4.1
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -1089,18 +1141,18 @@
|
||||
|
||||
## 2.2.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- ToggleGroup: `Neutral`-variant. ([PR](https://github.com/navikt/aksel/pull/1789))
|
||||
|
||||
- Button: `Neutral`-variant.
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- ToggleGroup: `neutral`-variant ([PR](https://github.com/navikt/aksel/pull/1789))
|
||||
|
||||
- Button: `neutral`-variant ([PR](https://github.com/navikt/aksel/pull/1789))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- ToggleGroup: `Neutral`-variant. ([PR](https://github.com/navikt/aksel/pull/1789))
|
||||
|
||||
- Button: `Neutral`-variant.
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- Justeringer av semantiske fargetokens, statusfarger nå mer tydelig ([PR](https://github.com/navikt/aksel/pull/1787))
|
||||
@@ -1221,24 +1273,24 @@
|
||||
|
||||
## 2.0.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Fonter blir bruk i all typografi blir nå lastet fra CDN
|
||||
|
||||
### @navikt/ds-tailwind
|
||||
|
||||
- Alle token er oppdatert til nytt format. ([Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h76f47744d112))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Fontlasting: Fonter lastes nå fra NAV-CDN
|
||||
|
||||
- Tokens: Alle komponenter bruker nå semantiske tokens for som standard, med innebygd støtte for komponent-spesifikke tokens.
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Fonter blir bruk i all typografi blir nå lastet fra CDN
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- Alle token er oppdatert til nytt format. ([Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h76f47744d112))
|
||||
|
||||
### @navikt/ds-tailwind
|
||||
|
||||
- Alle token er oppdatert til nytt format. ([Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h76f47744d112))
|
||||
|
||||
## 1.5.10
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -1283,16 +1335,16 @@
|
||||
|
||||
## 1.5.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Tag: `filled`-varianter ([PR](https://github.com/navikt/aksel/pull/1684))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Tag: `filles`-varianter ([PR](https://github.com/navikt/aksel/pull/1684))
|
||||
|
||||
- Ny komponent Chips! ([PR](https://github.com/navikt/aksel/pull/1668))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Tag: `filled`-varianter ([PR](https://github.com/navikt/aksel/pull/1684))
|
||||
|
||||
## 1.4.4
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -1319,10 +1371,6 @@
|
||||
|
||||
## 1.3.39
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Fikset bruk av `:watch` for diverse parsere (less, parcel)
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- useId-bug fikset ved bruk i både react 17 og 18
|
||||
@@ -1345,6 +1393,10 @@
|
||||
|
||||
- Stepper: `interactive` og `completed`-props for wizard-løsninger
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Fikset bruk av `:watch` for diverse parsere (less, parcel)
|
||||
|
||||
## 1.3.24
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
"website"
|
||||
],
|
||||
"dependencies": {
|
||||
"@navikt/aksel-icons": "^5.7.6",
|
||||
"@navikt/ds-css": "^5.7.6",
|
||||
"@navikt/ds-react": "^5.7.6",
|
||||
"@navikt/ds-tailwind": "^5.7.6",
|
||||
"@navikt/ds-tokens": "^5.7.6"
|
||||
"@navikt/aksel-icons": "^5.9.2",
|
||||
"@navikt/ds-css": "^5.9.2",
|
||||
"@navikt/ds-react": "^5.9.2",
|
||||
"@navikt/ds-tailwind": "^5.9.2",
|
||||
"@navikt/ds-tokens": "^5.9.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier-plugin-tailwindcss": "^0.2.3"
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { StorybookConfig } from "@storybook/nextjs";
|
||||
|
||||
import { readFileSync } from "fs";
|
||||
import { join, dirname } from "path";
|
||||
import { loadCsf } from "@storybook/csf-tools";
|
||||
import type { StorybookConfig } from "@storybook/nextjs";
|
||||
import { readFileSync } from "fs";
|
||||
import { dirname, join } from "path";
|
||||
|
||||
/**
|
||||
* This function is used to resolve the absolute path of a package.
|
||||
|
||||
@@ -6,29 +6,26 @@ import {
|
||||
PortableTextReactComponents,
|
||||
} from "@portabletext/react";
|
||||
import cl from "clsx";
|
||||
import ExpansionCard from "components/sanity-modules/ExpansionCard";
|
||||
import Accordion from "components/sanity-modules/accordion/Accordion";
|
||||
import Alert from "components/sanity-modules/alert/Alert";
|
||||
import Bilde from "components/sanity-modules/bilde/Bilde";
|
||||
import InnholdsKort from "components/sanity-modules/cards/InnholdsKort";
|
||||
import { InlineCode } from "components/website-modules/InlineCode";
|
||||
import { KBD } from "components/website-modules/KBD";
|
||||
import CodeExamples from "components/sanity-modules/code-examples/CodeExamples";
|
||||
import SnippetLazy from "components/sanity-modules/code-snippet/SnippetLazy";
|
||||
import DoDont from "components/sanity-modules/do-dont/DoDont";
|
||||
import ExpansionCard from "components/sanity-modules/expansioncard/ExpansionCard";
|
||||
import PropsSeksjon from "components/sanity-modules/props/PropsSeksjon";
|
||||
import RelatertInnhold from "components/sanity-modules/relatert-innhold/RelatertInnhold";
|
||||
import SideModul from "components/sanity-modules/side-modul/SideModul";
|
||||
import Tabell from "components/sanity-modules/tabell/Tabell";
|
||||
import TastaturModul from "components/sanity-modules/tastatur-tabell/TastaturTabell";
|
||||
import Tips from "components/sanity-modules/tips/Tips";
|
||||
import TokenTable from "components/sanity-modules/token-tabell/TokenTable";
|
||||
import Video from "components/sanity-modules/video/Video";
|
||||
import InlineCode from "components/website-modules/InlineCode";
|
||||
import KBD from "components/website-modules/KBD";
|
||||
import NextLink from "next/link";
|
||||
import { Children } from "react";
|
||||
import {
|
||||
Accordion,
|
||||
Alert,
|
||||
Bilde,
|
||||
CodeExamples,
|
||||
DoDont,
|
||||
PropsSeksjon,
|
||||
RelatertInnhold,
|
||||
SideModul,
|
||||
Snippet,
|
||||
Tabell,
|
||||
TastaturModul,
|
||||
Tips,
|
||||
TokenTable,
|
||||
UuFeedback,
|
||||
Video,
|
||||
} from ".";
|
||||
|
||||
const serializers: Partial<PortableTextReactComponents> = {
|
||||
types: {
|
||||
@@ -40,7 +37,7 @@ const serializers: Partial<PortableTextReactComponents> = {
|
||||
bilde: ({ value }) => <Bilde node={value} />,
|
||||
alert: ({ value }) => <Alert node={value} />,
|
||||
expansioncard: ({ value }) => <ExpansionCard node={value} />,
|
||||
kode: ({ value }) => <Snippet node={value} />,
|
||||
kode: ({ value }) => <SnippetLazy node={value} />,
|
||||
tabell_v2: ({ value }) => <Tabell node={value} />,
|
||||
accordion: ({ value }) => <Accordion node={value} />,
|
||||
props_seksjon: ({ value }) => <PropsSeksjon node={value} />,
|
||||
@@ -49,7 +46,6 @@ const serializers: Partial<PortableTextReactComponents> = {
|
||||
video: ({ value }) => <Video node={value} />,
|
||||
tips: ({ value }) => <Tips node={value} />,
|
||||
kode_eksempler: ({ value }) => <CodeExamples node={value} />,
|
||||
uufeedback: ({ value }) => <UuFeedback node={value} />,
|
||||
},
|
||||
unknownType: () => null,
|
||||
block: {
|
||||
@@ -130,6 +126,7 @@ const serializers: Partial<PortableTextReactComponents> = {
|
||||
|
||||
return (
|
||||
<Link
|
||||
as={NextLink}
|
||||
href={href}
|
||||
inlineText
|
||||
onClick={(e) =>
|
||||
|
||||
@@ -1,22 +1,4 @@
|
||||
export * from "./assets";
|
||||
export { default as Accordion } from "./sanity-modules/Accordion";
|
||||
export { default as Alert } from "./sanity-modules/Alert";
|
||||
export { default as Bilde } from "./sanity-modules/Bilde";
|
||||
export * from "./sanity-modules/cards";
|
||||
export * from "./sanity-modules/code";
|
||||
export * from "./sanity-modules/code-examples";
|
||||
export { default as DoDont } from "./sanity-modules/DoDont";
|
||||
export { default as IntroKomponent } from "./sanity-modules/IntroSeksjon";
|
||||
export { default as PropsSeksjon } from "./sanity-modules/props/PropsSeksjon";
|
||||
export { default as PropTable } from "./sanity-modules/props/PropTabell";
|
||||
export { default as SideModul } from "./sanity-modules/SideModul";
|
||||
export { default as Tabell } from "./sanity-modules/Tabell";
|
||||
export { default as TastaturModul } from "./sanity-modules/TastaturTabell";
|
||||
export { default as Tips } from "./sanity-modules/Tips";
|
||||
export { default as TokenTable } from "./sanity-modules/TokenTable";
|
||||
export * from "./sanity-modules/UuFeedback";
|
||||
export { default as Video } from "./sanity-modules/Video";
|
||||
export * from "./website-modules/BreadCrumbs";
|
||||
export { default as Feedback } from "./website-modules/feedback";
|
||||
export * from "./website-modules/toc/TOC";
|
||||
export * from "./website-modules/utils";
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { WithSidebar } from "./WithSidebar";
|
||||
import { SanityBlockContent } from "@/sanity-block";
|
||||
import { getBlocks } from "@/sb-util";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { WithSidebar } from "./WithSidebar";
|
||||
|
||||
const meta = {
|
||||
title: "Layout/WithSidebar",
|
||||
@@ -28,8 +28,10 @@ export const KomponenterLanding: Story = {
|
||||
args: {
|
||||
children,
|
||||
pageType: {
|
||||
type: "Komponenter",
|
||||
type: "komponenter",
|
||||
title: "Heading",
|
||||
rootUrl: "/komponenter",
|
||||
rootTitle: "Komponenter",
|
||||
},
|
||||
pageProps: { content: "1" },
|
||||
variant: "landingPage",
|
||||
@@ -65,8 +67,10 @@ export const Komponenter: Story = {
|
||||
args: {
|
||||
children,
|
||||
pageType: {
|
||||
type: "Komponenter",
|
||||
type: "komponenter",
|
||||
title: "Heading",
|
||||
rootUrl: "/komponenter",
|
||||
rootTitle: "Komponenter",
|
||||
},
|
||||
pageProps: { content: "1" },
|
||||
variant: "page",
|
||||
|
||||
@@ -4,7 +4,6 @@ import { capitalize } from "@/utils";
|
||||
import { Box, Detail, Heading, Link } from "@navikt/ds-react";
|
||||
import cl from "clsx";
|
||||
import { Sidebar } from "components/layout/sidebar/Sidebar";
|
||||
import Feedback from "components/website-modules/feedback";
|
||||
import { TableOfContentsv2 } from "components/website-modules/toc/TOCv2";
|
||||
import Image from "next/legacy/image";
|
||||
import NextLink from "next/link";
|
||||
@@ -21,8 +20,10 @@ export const WithSidebar = ({
|
||||
children: React.ReactNode;
|
||||
sidebar: AkselSidebarT;
|
||||
pageType: {
|
||||
type: "Komponenter" | "Grunnleggende";
|
||||
type: "komponenter" | "grunnleggende" | "templates";
|
||||
title: string;
|
||||
rootUrl: string;
|
||||
rootTitle: string;
|
||||
};
|
||||
pageProps: any;
|
||||
intro?: React.ReactNode;
|
||||
@@ -30,7 +31,11 @@ export const WithSidebar = ({
|
||||
variant?: "page" | "landingPage";
|
||||
}) => {
|
||||
return (
|
||||
<Box background="bg-default" paddingBlock="6 24">
|
||||
<Box
|
||||
background="bg-default"
|
||||
paddingBlock="6 24"
|
||||
className="min-h-screen-header"
|
||||
>
|
||||
<div className="mx-auto flex w-full max-w-screen-2xl gap-6">
|
||||
<Sidebar kategori={pageType.type} links={sidebar} />
|
||||
<main
|
||||
@@ -53,13 +58,9 @@ export const WithSidebar = ({
|
||||
<div className="z-[1]">
|
||||
{variant === "page" && pageProps?.kategori && (
|
||||
<Detail as="div" className="mb-2">
|
||||
<NextLink
|
||||
href={`/${pageType.type.toLowerCase()}`}
|
||||
passHref
|
||||
legacyBehavior
|
||||
>
|
||||
<NextLink href={pageType.rootUrl} passHref legacyBehavior>
|
||||
<Link className="text-text-default">
|
||||
{capitalize(pageType.type)}
|
||||
{pageType.rootTitle}
|
||||
</Link>
|
||||
</NextLink>{" "}
|
||||
/ {capitalize(pageProps.kategori)}
|
||||
@@ -111,12 +112,9 @@ export const WithSidebar = ({
|
||||
</div>
|
||||
|
||||
<div className={cl("sm:px-6 md:px-10", { flex: variant === "page" })}>
|
||||
{variant === "page" && (
|
||||
<TableOfContentsv2 changedState={pageProps["content"]} />
|
||||
)}
|
||||
{variant === "page" && <TableOfContentsv2 />}
|
||||
<div className="w-full">
|
||||
{children}
|
||||
<Feedback docId={pageProps?._id} docType={pageProps?._type} />
|
||||
{footer && <div className="w-full">{footer}</div>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { searchOptions } from "@/types";
|
||||
import { XMarkIcon } from "@navikt/aksel-icons";
|
||||
import { Button, Chips, Search } from "@navikt/ds-react";
|
||||
import { KBD } from "components/website-modules/KBD";
|
||||
import KBD from "components/website-modules/KBD";
|
||||
import { useContext, useRef } from "react";
|
||||
import { useShortcut } from "./hooks";
|
||||
import {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { SearchHitT, searchOptions } from "@/types";
|
||||
import { ChevronRightIcon } from "@navikt/aksel-icons";
|
||||
import { Chips } from "@navikt/ds-react";
|
||||
import cl from "clsx";
|
||||
import { Tag } from "components/sanity-modules/frontpage-blocks/Tag";
|
||||
import { Tag } from "components/sanity-modules/frontpage-blocks/latest-articles/Tag";
|
||||
import { StatusTag } from "components/website-modules/StatusTag";
|
||||
import Image from "next/legacy/image";
|
||||
import { default as Link, default as NextLink } from "next/link";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { GroupedHitsT, SearchHitT, searchOptions } from "@/types";
|
||||
import { Heading } from "@navikt/ds-react";
|
||||
import cl from "clsx";
|
||||
import { Tag } from "components/sanity-modules/frontpage-blocks/Tag";
|
||||
import { Tag } from "components/sanity-modules/frontpage-blocks/latest-articles/Tag";
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Hit } from "./Hit";
|
||||
|
||||
|
||||
@@ -95,6 +95,7 @@ describe("createSearchResult", () => {
|
||||
aksel_blogg: 1,
|
||||
aksel_prinsipp: 1,
|
||||
aksel_standalone: 1,
|
||||
templates_artikkel: 0,
|
||||
};
|
||||
const actual: SearchResultsT = createSearchResult(result, rawResults);
|
||||
expect(actual.hits).toEqual(expected);
|
||||
|
||||
@@ -29,6 +29,9 @@ export function createSearchResult(
|
||||
.length,
|
||||
aksel_blogg: rawResults.filter((x: any) => x.item._type === "aksel_blogg")
|
||||
.length,
|
||||
templates_artikkel: rawResults.filter(
|
||||
(x: any) => x.item._type === "templates_artikkel"
|
||||
).length,
|
||||
aksel_prinsipp: rawResults.filter(
|
||||
(x: any) => x.item._type === "aksel_prinsipp"
|
||||
).length,
|
||||
|
||||
@@ -15,15 +15,16 @@ import { useSection } from "./useSection";
|
||||
|
||||
const NavItem = ({
|
||||
link,
|
||||
asPath,
|
||||
}: {
|
||||
link: { heading: string; slug: string; kategori: string; tag?: string };
|
||||
asPath: string;
|
||||
}) => {
|
||||
const isActive = asPath === `/${link.slug}`;
|
||||
const { asPath } = useRouter();
|
||||
const isActive = asPath.split("#")[0] === `/${link.slug}`;
|
||||
|
||||
return (
|
||||
<li>
|
||||
<Link
|
||||
aria-current={isActive ? "page" : undefined}
|
||||
href={`/${link.slug}`}
|
||||
onClick={(e) => {
|
||||
amplitudeLogNavigation("meny", e.currentTarget.getAttribute("href"));
|
||||
@@ -77,7 +78,6 @@ const Dropdown = ({
|
||||
links: AkselSidebarT;
|
||||
}) => {
|
||||
const [open, setOpen] = useState(true);
|
||||
const { asPath } = useRouter();
|
||||
|
||||
return (
|
||||
<li className="peer w-full peer-data-[open=true]:mt-6" data-open={open}>
|
||||
@@ -103,7 +103,7 @@ const Dropdown = ({
|
||||
|
||||
<ul hidden={!open} className="pl-2">
|
||||
{links.map((z) => (
|
||||
<NavItem link={z} key={z?.heading} asPath={asPath} />
|
||||
<NavItem link={z} key={z?.heading} />
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
@@ -114,7 +114,7 @@ export const Sidebar = ({
|
||||
kategori,
|
||||
links,
|
||||
}: {
|
||||
kategori: "Komponenter" | "Grunnleggende";
|
||||
kategori: "komponenter" | "grunnleggende" | "templates";
|
||||
links: AkselSidebarT;
|
||||
}) => {
|
||||
const sections = useSection({ kategori, links });
|
||||
|
||||
@@ -1,23 +1,17 @@
|
||||
import { AkselSidebarT } from "@/types";
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
grunnleggendeKategorier,
|
||||
komponentKategorier,
|
||||
} from "../../../sanity/config";
|
||||
import { sanityCategoryLookup } from "../../../sanity/config";
|
||||
|
||||
export const useSection = ({
|
||||
kategori,
|
||||
links,
|
||||
}: {
|
||||
kategori: "Komponenter" | "Grunnleggende";
|
||||
kategori: "komponenter" | "grunnleggende" | "templates";
|
||||
links: AkselSidebarT;
|
||||
}) => {
|
||||
const sections = useMemo(
|
||||
() =>
|
||||
(kategori === "Komponenter"
|
||||
? komponentKategorier
|
||||
: grunnleggendeKategorier
|
||||
)
|
||||
sanityCategoryLookup(kategori)
|
||||
.map((x) => ({
|
||||
...x,
|
||||
pages: links
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import React from "react";
|
||||
import { withErrorBoundary } from "@/error-boundary";
|
||||
import TokenView from "./token-view";
|
||||
|
||||
type SpesialT = {
|
||||
_key: string;
|
||||
_type: "spesial_seksjon";
|
||||
modul?: "ikonsok" | "token_kategori";
|
||||
logs?: any[];
|
||||
komponenter?: any;
|
||||
farge?: any;
|
||||
token?: { title: string; kategori: string };
|
||||
};
|
||||
|
||||
const SideModul = ({ node }: { node: SpesialT }) => {
|
||||
if (!node || !node.modul) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const GetModule = () => {
|
||||
switch (node.modul) {
|
||||
case "ikonsok":
|
||||
return null;
|
||||
case "token_kategori":
|
||||
return <TokenView token={node.token} />;
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return <div className="mb-16">{GetModule()}</div>;
|
||||
};
|
||||
|
||||
export default withErrorBoundary(SideModul, "SideModul");
|
||||
@@ -1,98 +0,0 @@
|
||||
import { withErrorBoundary } from "@/error-boundary";
|
||||
import { TokenTableT } from "@/types";
|
||||
import { ChevronDownIcon } from "@navikt/aksel-icons";
|
||||
import core from "@navikt/ds-css/tokens.json";
|
||||
import { BodyLong, Button, CopyButton, Label, Link } from "@navikt/ds-react";
|
||||
import cl from "clsx";
|
||||
import { AkselTable, AkselTableRow } from "components/website-modules/Table";
|
||||
import NextLink from "next/link";
|
||||
import { useState } from "react";
|
||||
|
||||
const TokenTable = ({ node }: { node: TokenTableT }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const tokens: { [key: string]: string } | null =
|
||||
node.title in core ? core[node.title] : null;
|
||||
|
||||
if (!tokens) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const showMore = Object.keys(tokens).length > 8;
|
||||
|
||||
return (
|
||||
<div className="mb-7">
|
||||
<div
|
||||
className={cl({
|
||||
"after:to-bg-default relative max-h-80 overflow-y-hidden after:absolute after:inset-0 after:bg-gradient-to-b after:from-transparent after:via-transparent":
|
||||
!open && showMore,
|
||||
})}
|
||||
>
|
||||
<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-subtle bg-surface-default relative -mt-9 w-full rounded-b-lg border border-t-0 p-2 pr-14">
|
||||
<CopyButton
|
||||
copyText={Object.entries(tokens).reduce(
|
||||
(prev, cur) =>
|
||||
prev +
|
||||
`${cur[0]}: ${
|
||||
cur[1].startsWith("--") ? `var(${cur[1]})` : `${cur[1]}`
|
||||
};\n`,
|
||||
""
|
||||
)}
|
||||
className="absolute right-2 top-2 z-10"
|
||||
/>
|
||||
<Label size="small" as="span" spacing>
|
||||
Hva er dette?
|
||||
</Label>
|
||||
<BodyLong size="small">
|
||||
Komponent-tokens gir deg muligheten til å sette opp theming eller
|
||||
justere styling uten å måtte overskrive css-klasser. Les gjennom{" "}
|
||||
<NextLink
|
||||
href="/grunnleggende/styling/design-tokens#hec62d38bc813"
|
||||
passHref
|
||||
legacyBehavior
|
||||
>
|
||||
<Link>guiden vår</Link>
|
||||
</NextLink>{" "}
|
||||
eller utforsk alle{" "}
|
||||
<NextLink
|
||||
href="/grunnleggende/styling/design-tokens"
|
||||
passHref
|
||||
legacyBehavior
|
||||
>
|
||||
<Link>design tokens</Link>
|
||||
</NextLink>
|
||||
.
|
||||
</BodyLong>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!open && showMore && (
|
||||
<div className="bg-surface-default mx-auto grid w-fit -translate-y-[80%] place-content-center shadow-[0_0_8px_8px_var(--a-surface-default)]">
|
||||
<Button
|
||||
variant="secondary-neutral"
|
||||
onClick={() => setOpen(true)}
|
||||
className="group"
|
||||
icon={
|
||||
<ChevronDownIcon
|
||||
className="group-hover:translate-y-05 transition-transform"
|
||||
aria-hidden
|
||||
fontSize="1.5rem"
|
||||
/>
|
||||
}
|
||||
iconPosition="right"
|
||||
>
|
||||
Vis mer
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default withErrorBoundary(TokenTable, "Token-tabell");
|
||||
@@ -1,83 +0,0 @@
|
||||
import { FeedbackT } from "@/types";
|
||||
import { IdContext } from "@/utils";
|
||||
import { Button, Heading, Textarea } from "@navikt/ds-react";
|
||||
import cl from "clsx";
|
||||
import { useRouter } from "next/router";
|
||||
import { useContext, useState } from "react";
|
||||
|
||||
export const UuFeedback = ({ node }: { node: { vis: boolean } }) => {
|
||||
const [textValue, setTextValue] = useState("");
|
||||
const [errorMsg, setErrorMsg] = useState<string | null>(null);
|
||||
const [sent, setSent] = useState(false);
|
||||
|
||||
const idCtx = useContext(IdContext);
|
||||
const { asPath, basePath } = useRouter();
|
||||
|
||||
if (!node?.vis) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const fetchFeedback = () => {
|
||||
const body: FeedbackT = {
|
||||
message: textValue,
|
||||
url: `${basePath}${asPath}`,
|
||||
type: "uu",
|
||||
docId: idCtx?.id,
|
||||
};
|
||||
|
||||
fetch("/api/feedback", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
};
|
||||
|
||||
const handleSend = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (textValue === "") {
|
||||
setErrorMsg(
|
||||
"Tilbakemeldingen kan ikke være tom. Legg til tekst i feltet."
|
||||
);
|
||||
return;
|
||||
}
|
||||
setErrorMsg(null);
|
||||
setSent(true);
|
||||
fetchFeedback();
|
||||
setTextValue("");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="my-8">
|
||||
{!sent && (
|
||||
<form
|
||||
className={cl(
|
||||
"animate-fadeIn mt-4 flex w-full max-w-sm flex-col gap-4"
|
||||
)}
|
||||
>
|
||||
<Textarea
|
||||
error={errorMsg}
|
||||
value={textValue}
|
||||
onChange={(e) => setTextValue(e.target.value)}
|
||||
maxLength={600}
|
||||
minRows={3}
|
||||
label="Tilbakemelding"
|
||||
description="Ikke skriv inn navn eller andre personopplysninger"
|
||||
autoComplete="off"
|
||||
/>
|
||||
<Button className="mr-auto" onClick={handleSend}>
|
||||
Send inn tilbakemelding
|
||||
</Button>
|
||||
</form>
|
||||
)}
|
||||
<div aria-live="polite">
|
||||
{sent && (
|
||||
<>
|
||||
<Heading size="small" as="p" className="mt-8">
|
||||
Takk for tilbakemeldingen!
|
||||
</Heading>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+14
-5
@@ -1,10 +1,13 @@
|
||||
import { Accordion as DsAccordion } from "@navikt/ds-react";
|
||||
import React from "react";
|
||||
import { withErrorBoundary } from "@/error-boundary";
|
||||
import ErrorBoundary from "@/error-boundary";
|
||||
import { SanityBlockContent } from "@/sanity-block";
|
||||
import { AccordionT } from "@/types";
|
||||
import { Accordion as DsAccordion } from "@navikt/ds-react";
|
||||
|
||||
const Accordion = ({ node }: { node: AccordionT }) => {
|
||||
type AccordionProps = {
|
||||
node: AccordionT;
|
||||
};
|
||||
|
||||
const Accordion = ({ node }: AccordionProps) => {
|
||||
if (!node || node.list.length === 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -25,4 +28,10 @@ const Accordion = ({ node }: { node: AccordionT }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default withErrorBoundary(Accordion, "Accordion");
|
||||
export default function Component(props: AccordionProps) {
|
||||
return (
|
||||
<ErrorBoundary boundaryName="Accordion">
|
||||
<Accordion {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
+13
-2
@@ -1,8 +1,13 @@
|
||||
import ErrorBoundary from "@/error-boundary";
|
||||
import { SanityBlockContent } from "@/sanity-block";
|
||||
import { AlertT } from "@/types";
|
||||
import { Alert as DsAlert, Heading } from "@navikt/ds-react";
|
||||
|
||||
const Alert = ({ node }: { node: AlertT }) => {
|
||||
type AlertProps = {
|
||||
node: AlertT;
|
||||
};
|
||||
|
||||
const Alert = ({ node }: AlertProps) => {
|
||||
return (
|
||||
<div className="mb-7 max-w-2xl">
|
||||
<DsAlert variant={node.variant}>
|
||||
@@ -23,4 +28,10 @@ const Alert = ({ node }: { node: AlertT }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Alert;
|
||||
export default function Component(props: AlertProps) {
|
||||
return (
|
||||
<ErrorBoundary boundaryName="Alert">
|
||||
<Alert {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
+14
-3
@@ -1,12 +1,17 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { withErrorBoundary } from "@/error-boundary";
|
||||
import ErrorBoundary from "@/error-boundary";
|
||||
import { urlFor } from "@/sanity/interface";
|
||||
import { BildeT } from "@/types";
|
||||
import { BodyLong, Link } from "@navikt/ds-react";
|
||||
import cl from "clsx";
|
||||
import NextLink from "next/link";
|
||||
|
||||
const Bilde = ({ node, className }: { node: BildeT; className?: string }) => {
|
||||
type BildeProps = {
|
||||
node: BildeT;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const Bilde = ({ node, className }: BildeProps) => {
|
||||
if (!node || !node.asset) {
|
||||
return null;
|
||||
}
|
||||
@@ -65,4 +70,10 @@ const Bilde = ({ node, className }: { node: BildeT; className?: string }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default withErrorBoundary(Bilde, "Bilde");
|
||||
export default function Component(props: BildeProps) {
|
||||
return (
|
||||
<ErrorBoundary boundaryName="Bilde">
|
||||
<Bilde {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { withErrorBoundary } from "@/error-boundary";
|
||||
import ErrorBoundary from "@/error-boundary";
|
||||
|
||||
import { amplitudeLogNavigation } from "@/logging";
|
||||
import {
|
||||
@@ -12,6 +12,14 @@ import { useFormatedDate } from "components/website-modules/utils/getDate";
|
||||
import NextLink from "next/link";
|
||||
import { abbrName } from "../..";
|
||||
|
||||
type ArtikkelCardProps = ResolveContributorsSingleT<
|
||||
ResolveTemaT<ResolveSlugT<AkselGodPraksisDocT>>
|
||||
> & {
|
||||
source?: string;
|
||||
variant: string;
|
||||
level?: "2" | "3";
|
||||
};
|
||||
|
||||
const ArtikkelCard = ({
|
||||
slug,
|
||||
source,
|
||||
@@ -24,9 +32,7 @@ const ArtikkelCard = ({
|
||||
tema,
|
||||
level = "2",
|
||||
...rest
|
||||
}: ResolveContributorsSingleT<
|
||||
ResolveTemaT<ResolveSlugT<AkselGodPraksisDocT>>
|
||||
> & { source?: string; variant: string; level?: "2" | "3" }) => {
|
||||
}: ArtikkelCardProps) => {
|
||||
const date = useFormatedDate(
|
||||
(rest as any)?.updateInfo?.lastVerified ?? publishedAt ?? _updatedAt
|
||||
);
|
||||
@@ -89,4 +95,10 @@ const ArtikkelCard = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default withErrorBoundary(ArtikkelCard, "ArtikkelCard");
|
||||
export default function Component(props: ArtikkelCardProps) {
|
||||
return (
|
||||
<ErrorBoundary boundaryName="ArtikkelCard">
|
||||
<ArtikkelCard {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { withErrorBoundary } from "@/error-boundary";
|
||||
import ErrorBoundary from "@/error-boundary";
|
||||
import { amplitudeLogNavigation } from "@/logging";
|
||||
import { AkselBloggDocT, ResolveContributorsT, ResolveSlugT } from "@/types";
|
||||
import { getAuthors } from "@/utils";
|
||||
@@ -6,11 +6,11 @@ import { BodyLong, BodyShort, Heading, Link } from "@navikt/ds-react";
|
||||
import { useFormatedDate } from "components/website-modules/utils/getDate";
|
||||
import NextLink from "next/link";
|
||||
|
||||
const BloggCard = ({
|
||||
blog,
|
||||
}: {
|
||||
type BloggCardProps = {
|
||||
blog: ResolveContributorsT<ResolveSlugT<AkselBloggDocT>>;
|
||||
}) => {
|
||||
};
|
||||
|
||||
const BloggCard = ({ blog }: BloggCardProps) => {
|
||||
const date = useFormatedDate(blog?.publishedAt ?? blog._createdAt);
|
||||
return (
|
||||
<li
|
||||
@@ -46,4 +46,10 @@ const BloggCard = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default withErrorBoundary(BloggCard, "BloggCard");
|
||||
export default function Component(props: BloggCardProps) {
|
||||
return (
|
||||
<ErrorBoundary boundaryName="ArtikkelCard">
|
||||
<BloggCard {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { withErrorBoundary } from "@/error-boundary";
|
||||
import ErrorBoundary from "@/error-boundary";
|
||||
import { amplitudeLogNavigation } from "@/logging";
|
||||
import { urlFor } from "@/sanity/interface";
|
||||
import { AkselTemaT } from "@/types";
|
||||
@@ -7,7 +7,11 @@ import cl from "clsx";
|
||||
import Image from "next/legacy/image";
|
||||
import NextLink from "next/link";
|
||||
|
||||
const GodPraksisCard = ({ node }: { node: AkselTemaT }) => {
|
||||
type GodPraksisCardProps = {
|
||||
node: AkselTemaT;
|
||||
};
|
||||
|
||||
const GodPraksisCard = ({ node }: GodPraksisCardProps) => {
|
||||
if (
|
||||
!node?.pictogram ||
|
||||
!node?.slug?.current ||
|
||||
@@ -47,4 +51,10 @@ const GodPraksisCard = ({ node }: { node: AkselTemaT }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default withErrorBoundary(GodPraksisCard, "GodPraksisCard");
|
||||
export default function Component(props: GodPraksisCardProps) {
|
||||
return (
|
||||
<ErrorBoundary boundaryName="GodPraksisCard">
|
||||
<GodPraksisCard {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
import { withErrorBoundary } from "@/error-boundary";
|
||||
import ErrorBoundary from "@/error-boundary";
|
||||
import { amplitudeLogNavigation } from "@/logging";
|
||||
import { urlFor } from "@/sanity/interface";
|
||||
import { AkselTemaT } from "@/types";
|
||||
import Image from "next/legacy/image";
|
||||
import NextLink from "next/link";
|
||||
|
||||
const GodPraksisCardSimple = ({ node }: { node: Partial<AkselTemaT> }) => {
|
||||
type GodPraksisCardSimpleProps = {
|
||||
node: Partial<AkselTemaT>;
|
||||
};
|
||||
|
||||
const GodPraksisCardSimple = ({ node }: GodPraksisCardSimpleProps) => {
|
||||
if (!node?.pictogram || !node?.slug?.current || !node?.title) {
|
||||
return null;
|
||||
}
|
||||
@@ -36,4 +40,10 @@ const GodPraksisCardSimple = ({ node }: { node: Partial<AkselTemaT> }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default withErrorBoundary(GodPraksisCardSimple, "GodPraksisCardSimple");
|
||||
export default function Component(props: GodPraksisCardSimpleProps) {
|
||||
return (
|
||||
<ErrorBoundary boundaryName="GodPraksisCardSimple">
|
||||
<GodPraksisCardSimple {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
import { withErrorBoundary } from "@/error-boundary";
|
||||
import ErrorBoundary from "@/error-boundary";
|
||||
import { amplitudeLogNavigation } from "@/logging";
|
||||
import { SanityBlockContent } from "@/sanity-block";
|
||||
import { InnholdsKortPrinsipperT } from "@/types";
|
||||
import { Heading } from "@navikt/ds-react";
|
||||
import NextLink from "next/link";
|
||||
|
||||
const InnholdsKort = ({ node }: { node: InnholdsKortPrinsipperT }) => {
|
||||
type InnholdsKortProps = {
|
||||
node: InnholdsKortPrinsipperT;
|
||||
};
|
||||
|
||||
const InnholdsKort = ({ node }: InnholdsKortProps) => {
|
||||
if (!node.title || !node.body || !node.lenke) {
|
||||
return null;
|
||||
}
|
||||
@@ -39,4 +43,10 @@ const InnholdsKort = ({ node }: { node: InnholdsKortPrinsipperT }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default withErrorBoundary(InnholdsKort, "InnholdsKort");
|
||||
export default function Component(props: InnholdsKortProps) {
|
||||
return (
|
||||
<ErrorBoundary boundaryName="InnholdsKort">
|
||||
<InnholdsKort {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default as RelatertInnhold } from "./RelatertInnhold";
|
||||
+6
-1
@@ -1,6 +1,6 @@
|
||||
import { getKey } from "@/sb-util";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import CodeExample from "./Examples";
|
||||
import CodeExample from "./CodeExamples";
|
||||
|
||||
const meta = {
|
||||
title: "Sanity-modules/CodeExample",
|
||||
@@ -15,8 +15,10 @@ export const Designsystem: Story = {
|
||||
args: {
|
||||
node: {
|
||||
title: "button",
|
||||
|
||||
dir: {
|
||||
title: "button",
|
||||
variant: "templates",
|
||||
filer: [
|
||||
{
|
||||
_key: getKey(),
|
||||
@@ -24,6 +26,7 @@ export const Designsystem: Story = {
|
||||
innhold:
|
||||
'import { Button } from "@navikt/ds-react";\n\nconst Example = () => {\n return (\n <div className="flex flex-wrap gap-2">\n <Button variant="primary">Primary</Button>\n <Button variant="secondary">Secondary</Button>\n <Button variant="tertiary">Tertiary</Button>\n <Button variant="danger">Danger</Button>\n </div>\n );\n};',
|
||||
navn: "default",
|
||||
index: 1,
|
||||
},
|
||||
{
|
||||
_key: getKey(),
|
||||
@@ -31,6 +34,7 @@ export const Designsystem: Story = {
|
||||
innhold:
|
||||
'import { Button } from "@navikt/ds-react";\n\nconst Example = () => {\n return (\n <div className="flex flex-wrap gap-2">\n <Button variant="primary-neutral">Primary</Button>\n <Button variant="secondary-neutral">Secondary</Button>\n <Button variant="tertiary-neutral">Tertiary</Button>\n </div>\n );\n};',
|
||||
navn: "neutral",
|
||||
index: 1,
|
||||
},
|
||||
{
|
||||
_key: getKey(),
|
||||
@@ -38,6 +42,7 @@ export const Designsystem: Story = {
|
||||
innhold:
|
||||
'import { PencilIcon } from "@navikt/aksel-icons";\nimport { Button } from "@navikt/ds-react";\n\nconst Example = () => {\n return (\n <div className="flex flex-wrap gap-2">\n <Button size="small" variant="primary" icon={<PencilIcon aria-hidden />}>\n Primary\n </Button>\n <Button size="small" variant="secondary">\n Secondary\n </Button>\n <Button size="small" variant="tertiary">\n Tertiary\n </Button>\n <Button size="small" variant="danger">\n Danger\n </Button>\n </div>\n );\n};',
|
||||
navn: "small",
|
||||
index: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
+24
-16
@@ -1,5 +1,5 @@
|
||||
import { capitalize, Snippet } from "@/components";
|
||||
import { withErrorBoundary } from "@/error-boundary";
|
||||
import { capitalize } from "@/components";
|
||||
import ErrorBoundary from "@/error-boundary";
|
||||
import { CodeExamplesT } from "@/types";
|
||||
import {
|
||||
ExternalLinkIcon,
|
||||
@@ -8,14 +8,19 @@ import {
|
||||
} from "@navikt/aksel-icons";
|
||||
import { BodyLong, Button, Chips, HStack } from "@navikt/ds-react";
|
||||
import cl from "clsx";
|
||||
import SnippetLazy from "components/sanity-modules/code-snippet/SnippetLazy";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { CodeSandbox } from "./CodeSandbox";
|
||||
import { CodeSandbox } from "./parts/CodeSandbox";
|
||||
|
||||
const iframePadding = 192;
|
||||
const iframeId = "example-iframe";
|
||||
|
||||
const ComponentExamples = ({ node }: { node: CodeExamplesT }) => {
|
||||
type CodeExamplesProps = {
|
||||
node: CodeExamplesT;
|
||||
};
|
||||
|
||||
const ComponentExamples = ({ node }: CodeExamplesProps) => {
|
||||
const [activeExample, setActiveExample] = useState(null);
|
||||
const [frameState, setFrameState] = useState(300);
|
||||
const [unloaded, setUnloaded] = useState(true);
|
||||
@@ -30,7 +35,7 @@ const ComponentExamples = ({ node }: { node: CodeExamplesT }) => {
|
||||
) as HTMLIFrameElement;
|
||||
const exampleIframeDOM = exampleIframe?.contentDocument;
|
||||
const exampleWrapper = exampleIframeDOM?.getElementById("ds-example");
|
||||
if (exampleWrapper) {
|
||||
if (exampleWrapper && exampleWrapper.offsetHeight) {
|
||||
const newHeight = iframePadding + exampleWrapper.offsetHeight;
|
||||
clearInterval(waitForExampleContentToRender);
|
||||
setFrameState(newHeight < 300 ? 300 : newHeight);
|
||||
@@ -70,7 +75,7 @@ const ComponentExamples = ({ node }: { node: CodeExamplesT }) => {
|
||||
const fixName = (str: string) =>
|
||||
capitalize(
|
||||
str
|
||||
.replace(/[^\w]|_/g, " ")
|
||||
.replace(/[^\wæøå]|_/g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim()
|
||||
) ?? str;
|
||||
@@ -93,15 +98,12 @@ const ComponentExamples = ({ node }: { node: CodeExamplesT }) => {
|
||||
value={fil.navn}
|
||||
selected={active === fil.navn}
|
||||
id={`${node.dir.title.toLowerCase()}demo-${fil.navn}`}
|
||||
onClick={() => {
|
||||
setActiveExample(fil.navn);
|
||||
onClick={async () => {
|
||||
setUnloaded(true);
|
||||
router.replace(
|
||||
`#${node.dir.title.toLowerCase()}demo-${fil.navn}`,
|
||||
undefined,
|
||||
{
|
||||
shallow: true,
|
||||
}
|
||||
await router.replace(
|
||||
`${
|
||||
router.asPath.split("#")[0]
|
||||
}#${node.dir.title.toLowerCase()}demo-${fil.navn}`
|
||||
);
|
||||
}}
|
||||
>
|
||||
@@ -206,7 +208,7 @@ const ComponentExamples = ({ node }: { node: CodeExamplesT }) => {
|
||||
</HStack>
|
||||
</div>
|
||||
|
||||
<Snippet
|
||||
<SnippetLazy
|
||||
node={{
|
||||
code: { code: fil.innhold.trim(), language: "tsx" },
|
||||
}}
|
||||
@@ -220,4 +222,10 @@ const ComponentExamples = ({ node }: { node: CodeExamplesT }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default withErrorBoundary(ComponentExamples, "Eksempler kode");
|
||||
export default function Component(props: CodeExamplesProps) {
|
||||
return (
|
||||
<ErrorBoundary boundaryName="Kodeeksempler">
|
||||
<ComponentExamples {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default as CodeExamples } from "./Examples";
|
||||
+2
-2
@@ -31,7 +31,7 @@ export const CodeSandbox = ({ code }: { code: string }) => {
|
||||
} as any,
|
||||
isBinary: false,
|
||||
},
|
||||
"App.js": {
|
||||
"App.tsx": {
|
||||
content: getCode(code),
|
||||
isBinary: false,
|
||||
},
|
||||
@@ -54,7 +54,7 @@ export const CodeSandbox = ({ code }: { code: string }) => {
|
||||
className="h-8"
|
||||
>
|
||||
<input type="hidden" name="parameters" value={parameters} />
|
||||
<input type="hidden" name="query" value="module=App.js" />
|
||||
<input type="hidden" name="query" value="module=App.tsx" />
|
||||
<Button
|
||||
variant="tertiary-neutral"
|
||||
size="small"
|
||||
File renamed without changes.
+13
-3
@@ -1,4 +1,4 @@
|
||||
import { withErrorBoundary } from "@/error-boundary";
|
||||
import ErrorBoundary from "@/error-boundary";
|
||||
import { CodeSnippetT } from "@/types";
|
||||
import { ChevronRightIcon, TerminalIcon } from "@navikt/aksel-icons";
|
||||
import { CopyButton, Label } from "@navikt/ds-react";
|
||||
@@ -6,7 +6,11 @@ import cl from "clsx";
|
||||
import { Highlight, Language } from "prism-react-renderer";
|
||||
import theme from "./theme";
|
||||
|
||||
const CodeSnippet = ({ node: { code, title } }: { node: CodeSnippetT }) => {
|
||||
type CodeSnippetProps = {
|
||||
node: CodeSnippetT;
|
||||
};
|
||||
|
||||
const CodeSnippet = ({ node: { code, title } }: CodeSnippetProps) => {
|
||||
if (!code || !code.code) {
|
||||
return null;
|
||||
}
|
||||
@@ -94,4 +98,10 @@ const CodeSnippet = ({ node: { code, title } }: { node: CodeSnippetT }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default withErrorBoundary(CodeSnippet, "Kode snippet");
|
||||
export default function Component(props: CodeSnippetProps) {
|
||||
return (
|
||||
<ErrorBoundary boundaryName="Kodesnippet">
|
||||
<CodeSnippet {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
const SnippetLazy = dynamic(() => import("./Snippet"), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
export default SnippetLazy;
|
||||
File renamed without changes.
@@ -1,5 +0,0 @@
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
export const Snippet = dynamic(() => import("./Snippet"), {
|
||||
ssr: false,
|
||||
});
|
||||
File renamed without changes.
+13
-3
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { withErrorBoundary } from "@/error-boundary";
|
||||
import ErrorBoundary from "@/error-boundary";
|
||||
import { urlFor } from "@/sanity/interface";
|
||||
import { DoDontT } from "@/types";
|
||||
import {
|
||||
@@ -53,7 +53,11 @@ const Element = ({ block }: { block: DoDontT["blokker"][number] }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const DoDont = ({ node }: { node: DoDontT }) => {
|
||||
type DoDontProps = {
|
||||
node: DoDontT;
|
||||
};
|
||||
|
||||
const DoDont = ({ node }: DoDontProps) => {
|
||||
if (!node) return null;
|
||||
|
||||
return (
|
||||
@@ -101,4 +105,10 @@ function getText(s: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export default withErrorBoundary(DoDont, "DoDont");
|
||||
export default function Component(props: DoDontProps) {
|
||||
return (
|
||||
<ErrorBoundary boundaryName="Dodont">
|
||||
<DoDont {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
+12
-6
@@ -1,17 +1,17 @@
|
||||
import ErrorBoundary from "@/error-boundary";
|
||||
import { SanityBlockContent } from "@/sanity-block";
|
||||
import { ExpansionCard as DsExpansionCard } from "@navikt/ds-react";
|
||||
import { withErrorBoundary } from "@/error-boundary";
|
||||
|
||||
const ExpansionCard = ({
|
||||
node,
|
||||
}: {
|
||||
type ExpansionCardProps = {
|
||||
node: {
|
||||
heading: string;
|
||||
heading_level: "h3" | "h4";
|
||||
description?: string;
|
||||
body: any[];
|
||||
};
|
||||
}) => {
|
||||
};
|
||||
|
||||
const ExpansionCard = ({ node }: ExpansionCardProps) => {
|
||||
if (!node.heading || !node.body || !node.heading_level) {
|
||||
return null;
|
||||
}
|
||||
@@ -42,4 +42,10 @@ const ExpansionCard = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default withErrorBoundary(ExpansionCard, "ExpansionCard");
|
||||
export default function Component(props: ExpansionCardProps) {
|
||||
return (
|
||||
<ErrorBoundary boundaryName="ExpansionCard">
|
||||
<ExpansionCard {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
+14
-4
@@ -1,8 +1,12 @@
|
||||
import Latest, { LatestT } from "./Latest";
|
||||
import { withErrorBoundary } from "@/error-boundary";
|
||||
import ErrorBoundary from "@/error-boundary";
|
||||
import Latest, { LatestT } from "./latest-articles/Latest";
|
||||
export type BlocksT = LatestT;
|
||||
|
||||
export const FrontpageBlock = ({ blocks }: { blocks: BlocksT[] }) => {
|
||||
type FrontpageBlockProps = {
|
||||
blocks: BlocksT[];
|
||||
};
|
||||
|
||||
export const FrontpageBlock = ({ blocks }: FrontpageBlockProps) => {
|
||||
if (!blocks || !blocks.length) {
|
||||
return null;
|
||||
}
|
||||
@@ -20,4 +24,10 @@ export const FrontpageBlock = ({ blocks }: { blocks: BlocksT[] }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default withErrorBoundary(FrontpageBlock, "FrontpageBlock");
|
||||
export default function Component(props: FrontpageBlockProps) {
|
||||
return (
|
||||
<ErrorBoundary boundaryName="FrontpageBlock">
|
||||
<FrontpageBlock {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
+13
-8
@@ -1,4 +1,4 @@
|
||||
import { withErrorBoundary } from "@/error-boundary";
|
||||
import ErrorBoundary from "@/error-boundary";
|
||||
import { amplitudeLogNavigation } from "@/logging";
|
||||
import { urlFor } from "@/sanity/interface";
|
||||
import { abbrName } from "@/utils";
|
||||
@@ -29,15 +29,13 @@ export type ArticleT = {
|
||||
contributors?: { title: string }[];
|
||||
};
|
||||
|
||||
const Card = ({
|
||||
article,
|
||||
visible,
|
||||
index,
|
||||
}: {
|
||||
type CardProps = {
|
||||
article: ArticleT;
|
||||
visible: boolean;
|
||||
index: number;
|
||||
}) => {
|
||||
};
|
||||
|
||||
const Card = ({ article, visible, index }: CardProps) => {
|
||||
const date = useFormatedDate(article.publishedAt ?? article._updatedAt);
|
||||
|
||||
const showFooter = ["aksel_artikkel", "aksel_blogg"].includes(article._type);
|
||||
@@ -45,6 +43,7 @@ const Card = ({
|
||||
"ds_artikkel",
|
||||
"komponent_artikkel",
|
||||
"aksel_blogg",
|
||||
"templates_artikkel",
|
||||
].includes(article._type);
|
||||
|
||||
return (
|
||||
@@ -153,4 +152,10 @@ const Card = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default withErrorBoundary(Card, "FrontpageBlockCard");
|
||||
export default function Component(props: CardProps) {
|
||||
return (
|
||||
<ErrorBoundary boundaryName="FrontpageBlockCard">
|
||||
<Card {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
Loaded 100 of 235 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user