mirror of
https://github.com/navikt/aksel.git
synced 2026-10-29 00:44:05 +00:00
Compare commits
23
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3553bbaf6d | ||
|
|
9fc71b6e8a | ||
|
|
4ef7c2d85e | ||
|
|
fcdc1044ae | ||
|
|
1873e0f0a4 | ||
|
|
071ce07d4a | ||
|
|
d140ee84cc | ||
|
|
dfd3ddbe51 | ||
|
|
68c66f345a | ||
|
|
64726b8874 | ||
|
|
774e25dd27 | ||
|
|
1b639f4ede | ||
|
|
c234d98d7c | ||
|
|
a0c7f1fe54 | ||
|
|
13bd3cbfd0 | ||
|
|
d2325fb33b | ||
|
|
d8abc9bfeb | ||
|
|
769c477392 | ||
|
|
6fb7eda476 | ||
|
|
a013d5312f | ||
|
|
dabfb56def | ||
|
|
c571a7d4ba | ||
|
|
848783bb9f |
No files matched your search
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@navikt/ds-react": patch
|
||||
---
|
||||
|
||||
DatePicker: Preserve keyboard focus when controlled month changes replace the focused day.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@navikt/ds-react": patch
|
||||
---
|
||||
|
||||
Dialog, Lookup: Avoid setting aria-hidden on elements with role=alert/status when open
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@navikt/ds-css": patch
|
||||
---
|
||||
|
||||
DataTable: Constrain details panel width to the visible table area.
|
||||
@@ -1,5 +1,7 @@
|
||||
# @navikt/aksel-icons
|
||||
|
||||
## 8.20.1
|
||||
|
||||
## 8.20.0
|
||||
|
||||
## 8.19.2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel-icons",
|
||||
"version": "8.20.0",
|
||||
"version": "8.20.1",
|
||||
"author": "Aksel | Nav",
|
||||
"homepage": "https://aksel.nav.no/ikoner",
|
||||
"repository": {
|
||||
@@ -85,7 +85,7 @@
|
||||
"js-yaml": "^5.5.0",
|
||||
"lodash": "4.18.1",
|
||||
"prettier": "3.9.6",
|
||||
"react": "19.3.0",
|
||||
"react": "19.3.1",
|
||||
"rehype-parse": "9.0.1",
|
||||
"tsc-alias": "1.9.7",
|
||||
"tsx": "^4.23.15",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# @navikt/aksel-stylelint
|
||||
|
||||
## 8.20.1
|
||||
|
||||
## 8.20.0
|
||||
|
||||
## 8.19.2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel-stylelint",
|
||||
"version": "8.20.0",
|
||||
"version": "8.20.1",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=18.12.0"
|
||||
@@ -43,8 +43,8 @@
|
||||
"dev": "yarn watch:lint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@navikt/ds-css": "^8.20.0",
|
||||
"@navikt/ds-tokens": "^8.20.0",
|
||||
"@navikt/ds-css": "^8.20.1",
|
||||
"@navikt/ds-tokens": "^8.20.1",
|
||||
"concurrently": "10.0.6",
|
||||
"postcss-selector-parser": "^7.1.7",
|
||||
"postcss-value-parser": "^4.2.0",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# @navikt/aksel
|
||||
|
||||
## 8.20.1
|
||||
|
||||
## 8.20.0
|
||||
|
||||
## 8.19.2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel",
|
||||
"version": "8.20.0",
|
||||
"version": "8.20.1",
|
||||
"description": "Aksel command line interface. Codemods and other utilities for Aksel users.",
|
||||
"author": "Aksel | Nav designsystem team",
|
||||
"license": "MIT",
|
||||
@@ -40,7 +40,7 @@
|
||||
"url": "https://github.com/navikt/aksel/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": "5.6.2",
|
||||
"chalk": "6.0.1",
|
||||
"cli-progress": "3.12.0",
|
||||
"clipboardy": "5.3.2",
|
||||
"commander": "15.0.0",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @navikt/ds-css
|
||||
|
||||
## 8.20.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Badge: Increase inline padding by 1px for better optical balance ([#5443](https://github.com/navikt/aksel/pull/5443))
|
||||
|
||||
## 8.20.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-css",
|
||||
"version": "8.20.0",
|
||||
"version": "8.20.1",
|
||||
"description": "CSS for Nav Designsystem",
|
||||
"author": "Aksel | Nav designsystem team",
|
||||
"keywords": [
|
||||
@@ -32,7 +32,7 @@
|
||||
"css:get-version": "node config/get-version.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@navikt/ds-tokens": "^8.20.0",
|
||||
"@navikt/ds-tokens": "^8.20.1",
|
||||
"browserslist": "^4.28.7",
|
||||
"esbuild": "^0.28.2",
|
||||
"fast-glob": "3.3.3",
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
justify-content: center;
|
||||
min-width: var(--__axc-badge-size);
|
||||
height: var(--__axc-badge-size);
|
||||
padding: var(--ax-space-4);
|
||||
|
||||
/* 1px optical adjustment inline */
|
||||
padding: var(--ax-space-4) calc(var(--ax-space-4) + var(--ax-space-1));
|
||||
border-radius: var(--ax-radius-16);
|
||||
background: var(--ax-bg-strong);
|
||||
color: var(--ax-text-contrast);
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
container-type: inline-size;
|
||||
scroll-timeline: --horizontal-table-scroll inline;
|
||||
}
|
||||
|
||||
@@ -683,6 +684,17 @@ html[data-dragging-cursor="true"] *::after {
|
||||
}
|
||||
|
||||
.aksel-data-table__details-panel-row-cell {
|
||||
> div {
|
||||
position: sticky;
|
||||
|
||||
/* TODO: Sync 1px with `variants` that might not have outer border */
|
||||
inset-inline-start: 1px;
|
||||
|
||||
/* 3px magic number: Avoids content from overflowing when scrolling all the way to the right */
|
||||
max-inline-size: calc(100cqi - 3px);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--ax-border-focus);
|
||||
outline-offset: -4px;
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @navikt/ds-react
|
||||
|
||||
## 8.20.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-tokens@8.20.1
|
||||
- @navikt/aksel-icons@8.20.1
|
||||
|
||||
## 8.20.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-react",
|
||||
"version": "8.20.0",
|
||||
"version": "8.20.1",
|
||||
"description": "React components from the Norwegian Labour and Welfare Administration.",
|
||||
"author": "Aksel, a team part of the Norwegian Labour and Welfare Administration.",
|
||||
"license": "MIT",
|
||||
@@ -788,16 +788,16 @@
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "0.27.20",
|
||||
"@floating-ui/react-dom": "^2.1.9",
|
||||
"@navikt/aksel-icons": "^8.20.0",
|
||||
"@navikt/ds-tokens": "^8.20.0",
|
||||
"@navikt/aksel-icons": "^8.20.1",
|
||||
"@navikt/ds-tokens": "^8.20.1",
|
||||
"date-fns": "^4.0.0",
|
||||
"react-day-picker": "10.0.1"
|
||||
"react-day-picker": "10.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/dom": "10.4.1",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/dom": "10.4.2",
|
||||
"@testing-library/jest-dom": "^7.0.1",
|
||||
"@testing-library/react": "^16.3.3",
|
||||
"@testing-library/user-event": "^14.6.7",
|
||||
"@testing-library/user-event": "^14.6.8",
|
||||
"@types/jscodeshift": "17.3.0",
|
||||
"@types/node": "^24.10.9",
|
||||
"@types/react": "19.2.14",
|
||||
@@ -807,8 +807,8 @@
|
||||
"cross-env": "^10.1.0",
|
||||
"jscodeshift": "17.4.0",
|
||||
"jsdom": "29.1.1",
|
||||
"react": "19.3.0",
|
||||
"react-dom": "19.3.0",
|
||||
"react": "19.3.1",
|
||||
"react-dom": "19.3.1",
|
||||
"rimraf": "6.1.3",
|
||||
"swr": "^2.4.2",
|
||||
"tsc-alias": "1.9.7",
|
||||
|
||||
@@ -873,9 +873,11 @@ export const RowExpansionAll: Story = {
|
||||
selection={{
|
||||
mode: args.selection?.mode ?? "none",
|
||||
}}
|
||||
settings={{ stickyColumns: { start: 1 } }}
|
||||
>
|
||||
<DataGrid.Table<UserDataTest>
|
||||
onRowAction={() => console.info("Row clicked!")}
|
||||
|
||||
detailsPanel={{
|
||||
getContent: (rowData) => {
|
||||
return (
|
||||
|
||||
@@ -103,6 +103,66 @@ export const DropdownCaptionNavigation: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const ControlledMonthPreservesFocus: Story = {
|
||||
tags: ["play-fn", "!skip-test"],
|
||||
render: () => {
|
||||
const [month, setMonth] = React.useState(new Date(2026, 2, 15));
|
||||
const [selected, setSelected] = React.useState<Date | undefined>(
|
||||
new Date(2026, 2, 15),
|
||||
);
|
||||
|
||||
return (
|
||||
<fieldset
|
||||
onKeyDown={(event) => {
|
||||
if (event.key.toLowerCase() === "j") {
|
||||
const nextDate = new Date(2026, 10, 15);
|
||||
setMonth(nextDate);
|
||||
setSelected(nextDate);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<legend>Bytt måned med J</legend>
|
||||
<DatePicker.Standalone
|
||||
locale="nb"
|
||||
month={month}
|
||||
onMonthChange={setMonth}
|
||||
selected={selected}
|
||||
onSelect={setSelected}
|
||||
/>
|
||||
</fieldset>
|
||||
);
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const selectedDay = canvas.getByRole("button", {
|
||||
name: "søndag 15",
|
||||
pressed: true,
|
||||
});
|
||||
|
||||
await userEvent.click(selectedDay);
|
||||
await expect(selectedDay).toHaveFocus();
|
||||
await userEvent.keyboard("j");
|
||||
|
||||
await expect(canvas.getByText("november 2026")).toBeInTheDocument();
|
||||
await expect(
|
||||
canvas.getByRole("button", { name: "søndag 15", pressed: true }),
|
||||
).toHaveFocus();
|
||||
|
||||
await userEvent.keyboard("{ArrowRight}");
|
||||
await expect(
|
||||
canvas.getByRole("button", { name: "mandag 16" }),
|
||||
).toHaveFocus();
|
||||
|
||||
const nextMonthButton = canvas.getByRole("button", {
|
||||
name: "Gå til neste måned",
|
||||
});
|
||||
await userEvent.click(nextMonthButton);
|
||||
|
||||
await expect(canvas.getByText("desember 2026")).toBeInTheDocument();
|
||||
await expect(nextMonthButton).toHaveFocus();
|
||||
},
|
||||
};
|
||||
|
||||
export const Required: Story = {
|
||||
render: () => {
|
||||
const { datepickerProps } = useDatepicker({
|
||||
|
||||
@@ -19,7 +19,7 @@ export interface CheckboxProps
|
||||
*/
|
||||
children: React.ReactNode;
|
||||
/**
|
||||
* Visually hides label and description, and makes it viewable for screen-readers only.
|
||||
* Hides the label and description visually while keeping them available to screen readers.
|
||||
*/
|
||||
hideLabel?: boolean;
|
||||
/**
|
||||
|
||||
@@ -60,8 +60,7 @@ export interface ComboboxProps
|
||||
*/
|
||||
filteredOptions?: string[] | ComboboxOption[];
|
||||
/**
|
||||
* Optionally hide the label visually.
|
||||
* Not recommended, but can be considered for e.g. search fields in the top menu.
|
||||
* Hides the label and description visually while keeping them available to screen readers.
|
||||
*/
|
||||
hideLabel?: boolean;
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { Listbox } from "../../../utils/components/Listbox/root/ListboxRoot";
|
||||
import { useI18n } from "../../../utils/i18n/i18n.hooks";
|
||||
import { Search } from "../../search";
|
||||
import { useComboboxPopupContext } from "../popup/ComboboxPopup";
|
||||
import { useComboboxRootContext } from "../root/ComboboxRoot";
|
||||
@@ -10,6 +11,7 @@ type ComboboxFilterProps = Pick<
|
||||
>;
|
||||
|
||||
export const ComboboxFilter = (props: ComboboxFilterProps) => {
|
||||
const translate = useI18n("Combobox2");
|
||||
const { size } = useComboboxRootContext();
|
||||
const {
|
||||
filterString,
|
||||
@@ -26,7 +28,7 @@ export const ComboboxFilter = (props: ComboboxFilterProps) => {
|
||||
onChange={setFilterString}
|
||||
size={size}
|
||||
{...props}
|
||||
label="Filtrer"
|
||||
label={translate("filter")}
|
||||
hideLabel
|
||||
variant="simple"
|
||||
onKeyUp={(event) => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useCallback } from "react";
|
||||
import { Listbox } from "../../../utils/components/Listbox/root/ListboxRoot";
|
||||
import { useDeferredValue, useEventCallback } from "../../../utils/hooks";
|
||||
import { useI18n } from "../../../utils/i18n/i18n.hooks";
|
||||
import { ComboboxGroup } from "../group/ComboboxGroup";
|
||||
import { ComboboxOption } from "../option/ComboboxOption";
|
||||
import { useComboboxPopupContext } from "../popup/ComboboxPopup";
|
||||
@@ -10,6 +11,7 @@ import {
|
||||
type ResolveOption,
|
||||
useComboboxRootContext,
|
||||
} from "../root/ComboboxRoot";
|
||||
import { useHitsAnnouncer } from "./useHitsAnnouncer";
|
||||
|
||||
interface ComboboxListProps<
|
||||
T extends ComboboxOptionData | ComboboxGroupData<ComboboxOptionData>,
|
||||
@@ -47,12 +49,14 @@ function ComboboxList<
|
||||
function ComboboxListContent<
|
||||
T extends ComboboxOptionData | ComboboxGroupData<ComboboxOptionData>,
|
||||
>({ children }: ComboboxListProps<T>) {
|
||||
const { virtuallyFocusedOptionValue, filterString, setAnnounceText } =
|
||||
const { virtuallyFocusedOptionValue, filterString } =
|
||||
useComboboxPopupContext();
|
||||
const { setOpen, options, value, onToggleOption, multiple, triggerRef } =
|
||||
useComboboxRootContext();
|
||||
const memoizedOnToggleOption = useEventCallback(onToggleOption);
|
||||
const deferredFilterString = useDeferredValue(filterString);
|
||||
const maybeAnnouceHits = useHitsAnnouncer(deferredFilterString, filterString);
|
||||
const translate = useI18n("Combobox2");
|
||||
|
||||
const localOnToggleOption = useCallback(
|
||||
(option: ComboboxOptionData, isSelected: boolean) => {
|
||||
@@ -73,17 +77,14 @@ function ComboboxListContent<
|
||||
? filterOptions(options, deferredFilterString)
|
||||
: options;
|
||||
|
||||
maybeAnnouceHits(filteredOptions);
|
||||
|
||||
if (filteredOptions.length === 0) {
|
||||
setAnnounceText("Ingen treff");
|
||||
return (
|
||||
<div className="aksel-combobox2__no-results" role="status">
|
||||
Ingen treff
|
||||
</div>
|
||||
<div className="aksel-combobox2__no-results">{translate("noHits")}</div>
|
||||
);
|
||||
}
|
||||
|
||||
setAnnounceText("");
|
||||
|
||||
return filteredOptions.map((optOrGroup) =>
|
||||
"options" in optOrGroup ? (
|
||||
<ComboboxGroup
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
// Based on https://github.com/facebook/astryx/blob/main/packages/core/src/hooks/useAnnounce.ts
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
/**
|
||||
* Provides imperative screen-reader announcements via persistently-mounted
|
||||
* polite/assertive live regions. Regions are auto-cleared shortly after
|
||||
* announcing so stale status text does not linger in the accessibility tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Announcement urgency:
|
||||
* - `'polite'` (default): announced when the screen reader is idle. Use for
|
||||
* status updates, result counts, "no results", non-urgent confirmations.
|
||||
* - `'assertive'`: interrupts the current announcement. Reserve for errors and
|
||||
* time-sensitive alerts.
|
||||
*/
|
||||
type Politeness = "polite" | "assertive";
|
||||
|
||||
const clearTimers: {
|
||||
[K in Politeness]: ReturnType<typeof setTimeout> | null;
|
||||
} = {
|
||||
polite: null,
|
||||
assertive: null,
|
||||
};
|
||||
|
||||
function cancelScheduledClear(politeness: Politeness) {
|
||||
const timer = clearTimers[politeness];
|
||||
if (timer != null) {
|
||||
clearTimeout(timer);
|
||||
clearTimers[politeness] = null;
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleClear(politeness: Politeness, target: HTMLElement) {
|
||||
cancelScheduledClear(politeness);
|
||||
clearTimers[politeness] = setTimeout(() => {
|
||||
clearTimers[politeness] = null;
|
||||
target.textContent = "";
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
let regions: {
|
||||
polite: HTMLElement;
|
||||
assertive: HTMLElement;
|
||||
} | null = null;
|
||||
|
||||
function createRegion(politeness: Politeness) {
|
||||
const el = document.createElement("div");
|
||||
el.dataset.akselAnnouncer = "";
|
||||
el.className = "aksel-sr-only";
|
||||
el.role = politeness === "assertive" ? "alert" : "status";
|
||||
document.body.appendChild(el);
|
||||
return el;
|
||||
}
|
||||
|
||||
function getRegions() {
|
||||
if (typeof document === "undefined") {
|
||||
return null;
|
||||
}
|
||||
if (regions) {
|
||||
// Re-attach if a region was removed from the DOM (e.g. by a test cleanup).
|
||||
if (!regions.polite.isConnected) {
|
||||
document.body.appendChild(regions.polite);
|
||||
}
|
||||
if (!regions.assertive.isConnected) {
|
||||
document.body.appendChild(regions.assertive);
|
||||
}
|
||||
return regions;
|
||||
}
|
||||
regions = {
|
||||
polite: createRegion("polite"),
|
||||
assertive: createRegion("assertive"),
|
||||
};
|
||||
return regions;
|
||||
}
|
||||
|
||||
function announce(message: string, politeness: Politeness = "polite") {
|
||||
const r = getRegions();
|
||||
if (!r) {
|
||||
return;
|
||||
}
|
||||
const target = r[politeness];
|
||||
if (!message) {
|
||||
cancelScheduledClear(politeness);
|
||||
target.textContent = "";
|
||||
return;
|
||||
}
|
||||
// If the message is the same as the current text,
|
||||
// append a non-breaking space to force a re-announcement.
|
||||
target.textContent = message === target.textContent ? `${message} ` : message; // eslint-disable-line no-irregular-whitespace
|
||||
scheduleClear(politeness, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Speaks a message through a persistently-mounted, visually-hidden live region.
|
||||
*
|
||||
* The polite and assertive regions are created once and kept mounted.
|
||||
*
|
||||
* Each message is automatically cleared a couple of seconds after being
|
||||
* announced, so users browsing the DOM later do not encounter stale status
|
||||
* text. Announcing again before the clear simply resets the countdown.
|
||||
*/
|
||||
function useAnnounce() {
|
||||
getRegions(); // Ensure regions are created on beforehand
|
||||
// NOTE: Before using this hook several places, we should give each instance their own live regions to avoid conflicts.
|
||||
return announce;
|
||||
}
|
||||
|
||||
export { useAnnounce };
|
||||
@@ -0,0 +1,70 @@
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
import { debounce } from "../../../utils-external";
|
||||
import { useI18n } from "../../../utils/i18n/i18n.hooks";
|
||||
import type {
|
||||
ComboboxGroupData,
|
||||
ComboboxOptionData,
|
||||
} from "../root/ComboboxRoot";
|
||||
import { useAnnounce } from "./useAnnounce";
|
||||
|
||||
function getNumberOfOptions<T extends ComboboxOptionData>(
|
||||
options: (T | ComboboxGroupData<T>)[],
|
||||
) {
|
||||
return options.reduce((count, optOrGroup) => {
|
||||
if ("options" in optOrGroup) {
|
||||
return count + optOrGroup.options.length;
|
||||
}
|
||||
return count + 1;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function useHitsAnnouncer(deferredFilterString: string, filterString: string) {
|
||||
const translate = useI18n("Combobox2");
|
||||
const shouldAnnounceRef = useRef(false);
|
||||
const announce = useAnnounce();
|
||||
|
||||
const announceHits = useMemo(() => {
|
||||
return debounce((count: number) => {
|
||||
return announce(
|
||||
count === 1 ? translate("oneHit") : translate("hitsCount", { count }),
|
||||
);
|
||||
}, 2000);
|
||||
}, [announce, translate]);
|
||||
|
||||
const announceNoHits = useMemo(
|
||||
() => debounce(() => announce(translate("noHits")), 500),
|
||||
[announce, translate],
|
||||
);
|
||||
|
||||
useEffect(
|
||||
() =>
|
||||
function cleanUp() {
|
||||
announceHits.clear();
|
||||
announceNoHits.clear();
|
||||
},
|
||||
[announceHits, announceNoHits],
|
||||
);
|
||||
|
||||
return (
|
||||
filteredOptions: (
|
||||
ComboboxOptionData | ComboboxGroupData<ComboboxOptionData>
|
||||
)[],
|
||||
) => {
|
||||
// The options are filtered based on deferredFilterString, so we need to wait for it to settle.
|
||||
if (deferredFilterString !== filterString) {
|
||||
shouldAnnounceRef.current = true;
|
||||
} else if (shouldAnnounceRef.current) {
|
||||
if (filteredOptions.length === 0) {
|
||||
announceHits.clear();
|
||||
announceNoHits();
|
||||
} else {
|
||||
const hits = getNumberOfOptions(filteredOptions);
|
||||
announceNoHits.clear();
|
||||
announceHits(hits);
|
||||
}
|
||||
shouldAnnounceRef.current = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export { useHitsAnnouncer };
|
||||
@@ -8,7 +8,6 @@ interface ComboboxPopupContextProps {
|
||||
setVirtuallyFocusedOptionValue: (value: string) => void;
|
||||
filterString: string;
|
||||
setFilterString: (value: string) => void;
|
||||
setAnnounceText: (text: string) => void;
|
||||
}
|
||||
|
||||
export const {
|
||||
@@ -29,7 +28,6 @@ export const ComboboxPopup = ({ children }: ComboboxPopupProps) => {
|
||||
const [filterString, setFilterString] = useState("");
|
||||
const rootContext = useComboboxRootContext();
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
const [announceText, setAnnounceText] = useState("");
|
||||
|
||||
useEffect(
|
||||
function onOpen() {
|
||||
@@ -89,12 +87,8 @@ export const ComboboxPopup = ({ children }: ComboboxPopupProps) => {
|
||||
setFilterString(newString);
|
||||
setVirtuallyFocusedOptionValue("");
|
||||
}}
|
||||
setAnnounceText={setAnnounceText}
|
||||
>
|
||||
{children}
|
||||
<div className="aksel-sr-only" role="status">
|
||||
{announceText}
|
||||
</div>
|
||||
</ComboboxPopupContextProvider>
|
||||
</Listbox>
|
||||
);
|
||||
|
||||
@@ -39,7 +39,7 @@ export interface SearchProps
|
||||
*/
|
||||
label: React.ReactNode;
|
||||
/**
|
||||
* Shows label and description for screenreaders only.
|
||||
* Hides the label and description visually while keeping them available to screen readers.
|
||||
* @default true
|
||||
*/
|
||||
hideLabel?: boolean;
|
||||
|
||||
@@ -23,7 +23,7 @@ export interface SelectProps
|
||||
*/
|
||||
label: React.ReactNode;
|
||||
/**
|
||||
* Shows label and description for screenreaders only.
|
||||
* Hides the label and description visually while keeping them available to screen readers.
|
||||
*/
|
||||
hideLabel?: boolean;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export interface SwitchProps
|
||||
*/
|
||||
children: React.ReactNode;
|
||||
/**
|
||||
* If enabled shows the label and description for screenreaders only.
|
||||
* Hides the label and description visually while keeping them available to screen readers.
|
||||
*/
|
||||
hideLabel?: boolean;
|
||||
/**
|
||||
|
||||
@@ -36,7 +36,7 @@ export interface TextareaProps
|
||||
*/
|
||||
label: React.ReactNode;
|
||||
/**
|
||||
* If enabled shows the label and description for screenreaders only.
|
||||
* Hides the label and description visually while keeping them available to screen readers.
|
||||
*/
|
||||
hideLabel?: boolean;
|
||||
/**
|
||||
|
||||
@@ -20,7 +20,7 @@ export interface TextFieldProps
|
||||
*/
|
||||
htmlSize?: number;
|
||||
/**
|
||||
* If enabled shows the label and description for screenreaders only
|
||||
* Hides the label and description visually while keeping them available to screen readers
|
||||
*/
|
||||
hideLabel?: boolean;
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import React, { useState } from "react";
|
||||
import { expect, fireEvent, fn } from "storybook/test";
|
||||
import { expect, fireEvent, fn, within } from "storybook/test";
|
||||
import type { ListboxOptionProps } from "./option/ListboxOption";
|
||||
import { Listbox } from "./root/ListboxRoot";
|
||||
|
||||
@@ -212,10 +212,14 @@ export const EnterKey: StoryObj<{
|
||||
},
|
||||
};
|
||||
|
||||
const ListboxStoryWithSelectedOption = () => {
|
||||
const [virtuallyFocusedOptionId, setVirtuallyFocusedOptionId] = useState("");
|
||||
const ListboxStoryWithFilterInput = () => {
|
||||
const [virtuallyFocusedOptionId, setVirtuallyFocusedOptionId] =
|
||||
useState("second-item");
|
||||
return (
|
||||
<Listbox setVirtuallyFocusedOptionId={setVirtuallyFocusedOptionId}>
|
||||
<Listbox.InputSlot virtuallyFocusedOptionId={virtuallyFocusedOptionId}>
|
||||
<input type="text" aria-label="Filter" />
|
||||
</Listbox.InputSlot>
|
||||
<Listbox.Options>
|
||||
<Listbox.Option
|
||||
id="first-item"
|
||||
@@ -226,12 +230,12 @@ const ListboxStoryWithSelectedOption = () => {
|
||||
First item
|
||||
</Listbox.Option>
|
||||
<Listbox.Option
|
||||
id="selected-item"
|
||||
id="second-item"
|
||||
onClick={() => {}}
|
||||
hasVirtualFocus={virtuallyFocusedOptionId === "selected-item"}
|
||||
aria-selected
|
||||
hasVirtualFocus={virtuallyFocusedOptionId === "second-item"}
|
||||
aria-selected={false}
|
||||
>
|
||||
Selected item
|
||||
Second item
|
||||
</Listbox.Option>
|
||||
<Listbox.Option
|
||||
id="last-item"
|
||||
@@ -246,19 +250,30 @@ const ListboxStoryWithSelectedOption = () => {
|
||||
);
|
||||
};
|
||||
|
||||
// When there's a selected option, but no option has virtual focus,
|
||||
// pressing ArrowUp or ArrowDown should focus the selected option.
|
||||
export const ArrowDownFocusesSelectedOption: StoryObj = {
|
||||
render: ListboxStoryWithSelectedOption,
|
||||
// When a filter input has real focus, ArrowUp on first option and
|
||||
// ArrowDown on last option should remove virtual focus so that
|
||||
// perceived focus is returned to the input.
|
||||
export const ArrowUpOnFirstOptionFocusInput: StoryObj = {
|
||||
render: ListboxStoryWithFilterInput,
|
||||
play: async ({ canvasElement }) => {
|
||||
pressKey(canvasElement, "ArrowDown");
|
||||
expect(getVirtuallyFocusedValue(canvasElement)).toBe("selected-item");
|
||||
const canvas = within(canvasElement);
|
||||
const input = canvas.getByLabelText("Filter");
|
||||
input.focus();
|
||||
fireEvent.keyDown(input, { key: "ArrowUp" });
|
||||
expect(getVirtuallyFocusedValue(canvasElement)).toBe("first-item");
|
||||
fireEvent.keyDown(input, { key: "ArrowUp" });
|
||||
expect(getVirtuallyFocusedValue(canvasElement)).toBeUndefined();
|
||||
},
|
||||
};
|
||||
export const ArrowUpFocusesSelectedOption: StoryObj = {
|
||||
render: ListboxStoryWithSelectedOption,
|
||||
export const ArrowDownOnLastOptionFocusInput: StoryObj = {
|
||||
render: ListboxStoryWithFilterInput,
|
||||
play: async ({ canvasElement }) => {
|
||||
pressKey(canvasElement, "ArrowUp");
|
||||
expect(getVirtuallyFocusedValue(canvasElement)).toBe("selected-item");
|
||||
const canvas = within(canvasElement);
|
||||
const input = canvas.getByLabelText("Filter");
|
||||
input.focus();
|
||||
fireEvent.keyDown(input, { key: "ArrowDown" });
|
||||
expect(getVirtuallyFocusedValue(canvasElement)).toBe("last-item");
|
||||
fireEvent.keyDown(input, { key: "ArrowDown" });
|
||||
expect(getVirtuallyFocusedValue(canvasElement)).toBeUndefined();
|
||||
},
|
||||
};
|
||||
@@ -24,6 +24,7 @@ const ListboxInputSlot = ({
|
||||
// @ts-expect-error - You are meant to use an <input>, but Slot doesn't know that.
|
||||
autoComplete="off"
|
||||
role="combobox"
|
||||
data-aksel-listbox-input
|
||||
>
|
||||
{children}
|
||||
</Slot>
|
||||
|
||||
@@ -72,30 +72,34 @@ const ListboxComponent = forwardRef<HTMLDivElement, ListboxProps>(
|
||||
listbox.focus();
|
||||
}
|
||||
|
||||
const virtuallyFocusWithFallback = (
|
||||
getNextElement: (currentOption: HTMLElement) => HTMLElement | null,
|
||||
const handleArrowUpOrDown = (
|
||||
getNextOption: (currentOption: HTMLElement) => HTMLElement | null,
|
||||
getFallback: () => HTMLElement | null,
|
||||
) => {
|
||||
event.preventDefault();
|
||||
if (!focusedOptionElm) {
|
||||
const fallback =
|
||||
listbox.querySelector<HTMLElement>('[aria-selected="true"]') ||
|
||||
getFallback();
|
||||
virtuallyFocusOption(fallback);
|
||||
virtuallyFocusOption(getFallback());
|
||||
return;
|
||||
}
|
||||
const nextOption = getNextElement(focusedOptionElm);
|
||||
const nextOption = getNextOption(focusedOptionElm);
|
||||
if (nextOption) {
|
||||
virtuallyFocusOption(nextOption);
|
||||
return;
|
||||
}
|
||||
// No nextOption: Assume you pressed up on first option or down on last option
|
||||
const activeElement = document.activeElement as HTMLElement;
|
||||
if (activeElement.dataset?.akselListboxInput) {
|
||||
setVirtuallyFocusedOptionId("");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
switch (event.key) {
|
||||
case "ArrowUp":
|
||||
virtuallyFocusWithFallback(findPrevOption, getLastOption);
|
||||
handleArrowUpOrDown(findPrevOption, getLastOption);
|
||||
break;
|
||||
case "ArrowDown":
|
||||
virtuallyFocusWithFallback(findNextOption, getFirstOption);
|
||||
handleArrowUpOrDown(findNextOption, getFirstOption);
|
||||
break;
|
||||
case "PageUp":
|
||||
event.preventDefault();
|
||||
|
||||
@@ -49,7 +49,7 @@ export interface DateInputProps
|
||||
*/
|
||||
label: React.ReactNode;
|
||||
/**
|
||||
* Shows label and description for screen readers only
|
||||
* Hides the label and description visually while keeping them available to screen readers
|
||||
* @default false
|
||||
*/
|
||||
hideLabel?: boolean;
|
||||
|
||||
@@ -166,11 +166,11 @@ function hideNonTargetElements(avoidElements: Element[]): UndoFn {
|
||||
const body = ownerDocument(avoidElements[0]).body;
|
||||
|
||||
/**
|
||||
* Assume that elements with `aria-live` or `script` tags should not be hidden.
|
||||
* This ensures that live regions and scripts continue to function properly.
|
||||
* Assume that live regions and `script` tags should not be hidden.
|
||||
* This ensures that they continue to function properly.
|
||||
*/
|
||||
const ignoredElements = Array.from(
|
||||
body.querySelectorAll("[aria-live], script"),
|
||||
body.querySelectorAll("[aria-live], [role=alert], [role=status], script"),
|
||||
);
|
||||
|
||||
return applyAttributeToOthers(avoidElements.concat(ignoredElements), body);
|
||||
|
||||
@@ -4,6 +4,15 @@ const internalTranslations = {
|
||||
label: "Internal grid",
|
||||
selected: "{count} rows selected",
|
||||
},
|
||||
Combobox2: {
|
||||
/** Label for filter input. Only visible to screen readers. */
|
||||
filter: "Filtrer",
|
||||
noHits: "Ingen treff",
|
||||
/** Only announced to screen readers. */
|
||||
oneHit: "Ett treff",
|
||||
/** Only announced to screen readers. */
|
||||
hitsCount: "{count} treff",
|
||||
},
|
||||
} satisfies Record<string, Record<string, string | Record<string, string>>>;
|
||||
|
||||
export { internalTranslations };
|
||||
@@ -1,5 +1,7 @@
|
||||
# @navikt/ds-tailwind
|
||||
|
||||
## 8.20.1
|
||||
|
||||
## 8.20.0
|
||||
|
||||
## 8.19.2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-tailwind",
|
||||
"version": "8.20.0",
|
||||
"version": "8.20.1",
|
||||
"description": "Tailwind config based on @navikt/ds-tokens",
|
||||
"author": "Aksel | Nav designsystem team",
|
||||
"keywords": [
|
||||
@@ -28,7 +28,7 @@
|
||||
},
|
||||
"homepage": "https://aksel.nav.no/grunnleggende/kode/tailwind/",
|
||||
"devDependencies": {
|
||||
"@navikt/ds-tokens": "^8.20.0",
|
||||
"@navikt/ds-tokens": "^8.20.1",
|
||||
"@types/node": "^24.10.9",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"tsx": "^4.23.15",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# @navikt/ds-tokens
|
||||
|
||||
## 8.20.1
|
||||
|
||||
## 8.20.0
|
||||
|
||||
## 8.19.2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-tokens",
|
||||
"version": "8.20.0",
|
||||
"version": "8.20.1",
|
||||
"description": "Design-tokens for Nav designsystem",
|
||||
"author": "Aksel | Nav designsystem team",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## 8.20.1
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Badge: Increase inline padding by 1px for better optical balance ([#5443](https://github.com/navikt/aksel/pull/5443))
|
||||
|
||||
## 8.20.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
@@ -11,11 +11,11 @@
|
||||
"sanity:update": "yarn workspace website update:sanity"
|
||||
},
|
||||
"dependencies": {
|
||||
"@navikt/aksel": "^8.20.0",
|
||||
"@navikt/aksel-icons": "^8.20.0",
|
||||
"@navikt/ds-css": "^8.20.0",
|
||||
"@navikt/ds-react": "^8.20.0",
|
||||
"@navikt/ds-tailwind": "^8.20.0",
|
||||
"@navikt/ds-tokens": "^8.20.0"
|
||||
"@navikt/aksel": "^8.20.1",
|
||||
"@navikt/aksel-icons": "^8.20.1",
|
||||
"@navikt/ds-css": "^8.20.1",
|
||||
"@navikt/ds-react": "^8.20.1",
|
||||
"@navikt/ds-tailwind": "^8.20.1",
|
||||
"@navikt/ds-tokens": "^8.20.1"
|
||||
}
|
||||
}
|
||||
@@ -18,29 +18,29 @@
|
||||
"@portabletext/react": "^8.0.1",
|
||||
"@sanity/client": "7.27.0",
|
||||
"@sanity/code-input": "7.3.8",
|
||||
"@sanity/color-input": "6.1.4",
|
||||
"@sanity/color-input": "6.1.8",
|
||||
"@sanity/image-url": "2.1.1",
|
||||
"@sanity/locale-nb-no": "1.1.39",
|
||||
"@sanity/preview-url-secret": "4.1.5",
|
||||
"@sanity/table": "3.1.16",
|
||||
"@sanity/table": "3.1.20",
|
||||
"@sanity/vision": "6.17.0",
|
||||
"date-fns": "^4.0.0",
|
||||
"lodash": "4.18.1",
|
||||
"react": "19.3.0",
|
||||
"react-dom": "19.3.0",
|
||||
"react": "19.3.1",
|
||||
"react-dom": "19.3.1",
|
||||
"react-rx": "^5.1.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"sanity": "6.17.0",
|
||||
"sanity-plugin-iframe-pane": "5.0.35",
|
||||
"sanity-plugin-iframe-pane": "5.0.41",
|
||||
"sanity-plugin-media": "6.1.8",
|
||||
"sanity-plugin-references": "1.2.0",
|
||||
"styled-components": "6.4.4",
|
||||
"swr": "^2.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sanity/blueprints": "0.24.0",
|
||||
"@sanity/blueprints": "0.28.0",
|
||||
"@sanity/export": "7.0.3",
|
||||
"@sanity/functions": "1.7.2",
|
||||
"@sanity/functions": "1.10.1",
|
||||
"@sanity/schema": "6.17.0",
|
||||
"@types/lodash": "4.17.25",
|
||||
"@types/react": "19.2.14",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import DOMPurify from "isomorphic-dompurify";
|
||||
import { sanitize } from "isomorphic-dompurify";
|
||||
import "server-only";
|
||||
import styles from "./GodPraksisPictogram.module.css";
|
||||
|
||||
@@ -26,7 +26,7 @@ async function GodPraksisPictogram({
|
||||
|
||||
const formattedSvgString = formatSvgString(svgString, active);
|
||||
|
||||
const cleanedSvg = DOMPurify.sanitize(formattedSvgString, {
|
||||
const cleanedSvg = sanitize(formattedSvgString, {
|
||||
USE_PROFILES: { svg: true },
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import DOMPurify from "isomorphic-dompurify";
|
||||
import { sanitize } from "isomorphic-dompurify";
|
||||
import styles from "./ImageAsSvg.module.css";
|
||||
|
||||
/**
|
||||
@@ -27,7 +27,7 @@ async function ImageAsThemedSvg({
|
||||
|
||||
const formattedSvgString = formatSvgString(svgString);
|
||||
|
||||
const cleanedSvg = DOMPurify.sanitize(formattedSvgString, {
|
||||
const cleanedSvg = sanitize(formattedSvgString, {
|
||||
USE_PROFILES: { svg: true },
|
||||
});
|
||||
|
||||
|
||||
@@ -40,18 +40,18 @@
|
||||
"codesandbox-import-utils": "^2.2.3",
|
||||
"date-fns": "^4.0.0",
|
||||
"fuse.js": "7.4.2",
|
||||
"isomorphic-dompurify": "2.26.0",
|
||||
"isomorphic-dompurify": "4.6.0",
|
||||
"jscodeshift": "17.4.0",
|
||||
"lodash": "4.18.1",
|
||||
"lz-string": "^1.5.0",
|
||||
"next": "16.3.8",
|
||||
"next": "16.4.2",
|
||||
"next-logger": "^5.0.2",
|
||||
"next-sanity": "13.2.3",
|
||||
"next-themes": "^0.4.6",
|
||||
"pino": "^10.3.1",
|
||||
"prism-react-renderer": "^2.0.0",
|
||||
"react": "19.3.0",
|
||||
"react-dom": "19.3.0",
|
||||
"react": "19.3.1",
|
||||
"react-dom": "19.3.1",
|
||||
"react-hook-form": "^7.90.0",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-responsive-masonry": "2.7.2",
|
||||
@@ -72,8 +72,8 @@
|
||||
"@babel/parser": "^7.27.5",
|
||||
"@babel/traverse": "^7.27.4",
|
||||
"@google-cloud/bigquery": "^9.0.2",
|
||||
"@next/bundle-analyzer": "16.3.8",
|
||||
"@next/eslint-plugin-next": "16.3.8",
|
||||
"@next/bundle-analyzer": "16.4.2",
|
||||
"@next/eslint-plugin-next": "16.4.2",
|
||||
"@playwright/test": "1.61.1",
|
||||
"@portabletext/block-tools": "^6.0.0",
|
||||
"@portabletext/markdown": "^2.5.1",
|
||||
|
||||
@@ -39,6 +39,6 @@ export const Demo = {
|
||||
};
|
||||
|
||||
export const args: ExampleArgsT = {
|
||||
index: 6,
|
||||
index: 7,
|
||||
desc: "Med `allowNewValues` kan brukeren legge til egne alternativer som ikke finnes i listen.",
|
||||
};
|
||||
@@ -55,6 +55,6 @@ export const Demo = {
|
||||
};
|
||||
|
||||
export const args: ExampleArgsT = {
|
||||
index: 8,
|
||||
index: 9,
|
||||
desc: "Ved å sende inn `options` som objekter er det mulig å vise en brukervennlig tekst til brukeren, samtidig som systemet i bakkant kan forholde seg til en ID.",
|
||||
};
|
||||
@@ -64,6 +64,6 @@ export const Demo = {
|
||||
};
|
||||
|
||||
export const args: ExampleArgsT = {
|
||||
index: 9,
|
||||
index: 10,
|
||||
desc: "Ved å sette `shouldShowSelectedOptions=false` kan du vise valgte alternativer et annet sted. Dette gjøres ofte i løsninger med flere filtre. Dette er også en mulig løsning når alternativene har mye tekst eller du ønsker å vise ekstra informasjon tilknyttet alternativene.",
|
||||
};
|
||||
@@ -50,6 +50,6 @@ export const Demo = {
|
||||
};
|
||||
|
||||
export const args: ExampleArgsT = {
|
||||
index: 7,
|
||||
index: 8,
|
||||
desc: "`maxSelected` lar deg sette en grense for antall alternativer brukeren kan velge.",
|
||||
};
|
||||
@@ -79,7 +79,7 @@ export const Demo = {
|
||||
};
|
||||
|
||||
export const args: ExampleArgsT = {
|
||||
index: 10,
|
||||
index: 11,
|
||||
desc: "Eksempel på bruk med react-hook-form.",
|
||||
sandbox: false,
|
||||
};
|
||||
@@ -25,5 +25,5 @@ export const Demo = {
|
||||
};
|
||||
|
||||
export const args: ExampleArgsT = {
|
||||
index: 11,
|
||||
index: 12,
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
import { UNSAFE_Combobox } from "@navikt/ds-react";
|
||||
import { withDsExample } from "@/web/examples/withDsExample";
|
||||
|
||||
const Example = () => {
|
||||
return (
|
||||
<UNSAFE_Combobox
|
||||
label="Hva er de kuleste transportmidlene?"
|
||||
options={options}
|
||||
isMultiSelect
|
||||
size="small"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const options = [
|
||||
"Bil",
|
||||
"Buss",
|
||||
"Tog",
|
||||
"Skateboard",
|
||||
"Sykkel",
|
||||
"Motorsykkel",
|
||||
"Båt",
|
||||
"Fly",
|
||||
"Helikopter",
|
||||
"Lastebil",
|
||||
"Van",
|
||||
"Scooter",
|
||||
];
|
||||
|
||||
// EXAMPLES DO NOT INCLUDE CONTENT BELOW THIS LINE
|
||||
export default withDsExample(Example, {
|
||||
variant: "static",
|
||||
minHeight: "260px",
|
||||
});
|
||||
|
||||
/* Storybook story */
|
||||
export const Demo = {
|
||||
render: Example,
|
||||
};
|
||||
|
||||
export const args: ExampleArgsT = {
|
||||
index: 6,
|
||||
};
|
||||
@@ -11,12 +11,12 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@navikt/aksel-icons": "^8.20.0",
|
||||
"@navikt/ds-css": "^8.20.0",
|
||||
"@navikt/ds-react": "^8.20.0",
|
||||
"@navikt/aksel-icons": "^8.20.1",
|
||||
"@navikt/ds-css": "^8.20.1",
|
||||
"@navikt/ds-react": "^8.20.1",
|
||||
"fuse.js": "7.4.2",
|
||||
"react": "19.3.0",
|
||||
"react-dom": "19.3.0"
|
||||
"react": "19.3.1",
|
||||
"react-dom": "19.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@figma/plugin-typings": "^1.131.0",
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
"devDependencies": {
|
||||
"@modelcontextprotocol/inspector": "^2.11.0",
|
||||
"@modelcontextprotocol/sdk": "^1.32.0",
|
||||
"@navikt/aksel": "^8.20.0",
|
||||
"@navikt/aksel-icons": "^8.20.0",
|
||||
"@navikt/ds-tokens": "^8.20.0",
|
||||
"@navikt/aksel": "^8.20.1",
|
||||
"@navikt/aksel-icons": "^8.20.1",
|
||||
"@navikt/ds-tokens": "^8.20.1",
|
||||
"@sanity/client": "7.27.0",
|
||||
"@types/express": "^5.0.3",
|
||||
"@types/node": "^24.10.9",
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
"typescript": "6.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@navikt/aksel-icons": "^8.20.0",
|
||||
"@navikt/ds-css": "^8.20.0",
|
||||
"@navikt/ds-react": "^8.20.0",
|
||||
"react": "19.3.0"
|
||||
"@navikt/aksel-icons": "^8.20.1",
|
||||
"@navikt/ds-css": "^8.20.1",
|
||||
"@navikt/ds-react": "^8.20.1",
|
||||
"react": "19.3.1"
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -111,9 +111,9 @@
|
||||
"lint-staged": "17.6.0",
|
||||
"playwright": "1.61.1",
|
||||
"prettier": "3.9.6",
|
||||
"react": "19.3.0",
|
||||
"react": "19.3.1",
|
||||
"react-docgen-typescript": "2.4.0",
|
||||
"react-dom": "19.3.0",
|
||||
"react-dom": "19.3.1",
|
||||
"storybook": "^10.6.3",
|
||||
"stylelint": "^17.15.0",
|
||||
"stylelint-config-standard": "^40.0.0",
|
||||
|
||||
Reference in new issue
Block a user