mirror of
https://github.com/navikt/aksel.git
synced 2026-10-26 15:42:05 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a504a1f9f | ||
|
|
2be908b51e |
No files matched your search
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@navikt/ds-react": patch
|
||||
"@navikt/ds-css": patch
|
||||
---
|
||||
|
||||
Timeline: Adapt axis label density to available width
|
||||
@@ -1,7 +1,5 @@
|
||||
# @navikt/aksel-icons
|
||||
|
||||
## 8.20.1
|
||||
|
||||
## 8.20.0
|
||||
|
||||
## 8.19.2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel-icons",
|
||||
"version": "8.20.1",
|
||||
"version": "8.20.0",
|
||||
"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.1",
|
||||
"react": "19.3.0",
|
||||
"rehype-parse": "9.0.1",
|
||||
"tsc-alias": "1.9.7",
|
||||
"tsx": "^4.23.15",
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# @navikt/aksel-stylelint
|
||||
|
||||
## 8.20.1
|
||||
|
||||
## 8.20.0
|
||||
|
||||
## 8.19.2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel-stylelint",
|
||||
"version": "8.20.1",
|
||||
"version": "8.20.0",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=18.12.0"
|
||||
@@ -43,8 +43,8 @@
|
||||
"dev": "yarn watch:lint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@navikt/ds-css": "^8.20.1",
|
||||
"@navikt/ds-tokens": "^8.20.1",
|
||||
"@navikt/ds-css": "^8.20.0",
|
||||
"@navikt/ds-tokens": "^8.20.0",
|
||||
"concurrently": "10.0.6",
|
||||
"postcss-selector-parser": "^7.1.7",
|
||||
"postcss-value-parser": "^4.2.0",
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# @navikt/aksel
|
||||
|
||||
## 8.20.1
|
||||
|
||||
## 8.20.0
|
||||
|
||||
## 8.19.2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel",
|
||||
"version": "8.20.1",
|
||||
"version": "8.20.0",
|
||||
"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": "6.0.1",
|
||||
"chalk": "5.6.2",
|
||||
"cli-progress": "3.12.0",
|
||||
"clipboardy": "5.3.2",
|
||||
"commander": "15.0.0",
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
# @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.1",
|
||||
"version": "8.20.0",
|
||||
"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.1",
|
||||
"@navikt/ds-tokens": "^8.20.0",
|
||||
"browserslist": "^4.28.7",
|
||||
"esbuild": "^0.28.2",
|
||||
"fast-glob": "3.3.3",
|
||||
|
||||
@@ -16,9 +16,7 @@
|
||||
justify-content: center;
|
||||
min-width: var(--__axc-badge-size);
|
||||
height: var(--__axc-badge-size);
|
||||
|
||||
/* 1px optical adjustment inline */
|
||||
padding: var(--ax-space-4) calc(var(--ax-space-4) + var(--ax-space-1));
|
||||
padding: var(--ax-space-4);
|
||||
border-radius: var(--ax-radius-16);
|
||||
background: var(--ax-bg-strong);
|
||||
color: var(--ax-text-contrast);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
box-sizing: content-box;
|
||||
grid-column: 2;
|
||||
margin-bottom: var(--ax-space-4);
|
||||
overflow-x: clip;
|
||||
}
|
||||
|
||||
.aksel-timeline__row-label {
|
||||
@@ -30,13 +31,39 @@
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.aksel-timeline__axislabels-tier {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
container-name: aksel-timeline-axis-labels;
|
||||
container-type: inline-size;
|
||||
}
|
||||
|
||||
.aksel-timeline__axislabels-label {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
color: var(--ax-text-neutral-subtle);
|
||||
white-space: nowrap;
|
||||
overflow-x: clip;
|
||||
}
|
||||
|
||||
.aksel-timeline__axislabels-tier > .aksel-timeline__axislabels-tier {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/*
|
||||
* 4.5rem leaves room for the longest default abbreviated date label.
|
||||
* Below that, hide this tier and reveal the next coarser one in the same query.
|
||||
*/
|
||||
@container aksel-timeline-axis-labels (inline-size < 4.5rem) {
|
||||
.aksel-timeline__axislabels-tier:not([data-axis-labels-coarsest]) > .aksel-timeline__axislabels-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.aksel-timeline__axislabels-tier > .aksel-timeline__axislabels-tier {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.aksel-timeline__row {
|
||||
display: flex;
|
||||
background: var(--ax-bg-neutral-softA);
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
# @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.1",
|
||||
"version": "8.20.0",
|
||||
"description": "React components from the Norwegian Labour and Welfare Administration.",
|
||||
"author": "Aksel, a team part of the Norwegian Labour and Welfare Administration.",
|
||||
"license": "MIT",
|
||||
@@ -788,16 +788,16 @@
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "0.27.20",
|
||||
"@floating-ui/react-dom": "^2.1.9",
|
||||
"@navikt/aksel-icons": "^8.20.1",
|
||||
"@navikt/ds-tokens": "^8.20.1",
|
||||
"@navikt/aksel-icons": "^8.20.0",
|
||||
"@navikt/ds-tokens": "^8.20.0",
|
||||
"date-fns": "^4.0.0",
|
||||
"react-day-picker": "10.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/dom": "10.4.2",
|
||||
"@testing-library/jest-dom": "^7.0.1",
|
||||
"@testing-library/dom": "10.4.1",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.3.3",
|
||||
"@testing-library/user-event": "^14.6.8",
|
||||
"@testing-library/user-event": "^14.6.7",
|
||||
"@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.1",
|
||||
"react-dom": "19.3.1",
|
||||
"react": "19.3.0",
|
||||
"react-dom": "19.3.0",
|
||||
"rimraf": "6.1.3",
|
||||
"swr": "^2.4.2",
|
||||
"tsc-alias": "1.9.7",
|
||||
|
||||
@@ -19,7 +19,7 @@ export interface CheckboxProps
|
||||
*/
|
||||
children: React.ReactNode;
|
||||
/**
|
||||
* Hides the label and description visually while keeping them available to screen readers.
|
||||
* Visually hides label and description, and makes it viewable for screen-readers only.
|
||||
*/
|
||||
hideLabel?: boolean;
|
||||
/**
|
||||
|
||||
@@ -60,7 +60,8 @@ export interface ComboboxProps
|
||||
*/
|
||||
filteredOptions?: string[] | ComboboxOption[];
|
||||
/**
|
||||
* Hides the label and description visually while keeping them available to screen readers.
|
||||
* Optionally hide the label visually.
|
||||
* Not recommended, but can be considered for e.g. search fields in the top menu.
|
||||
*/
|
||||
hideLabel?: boolean;
|
||||
/**
|
||||
|
||||
@@ -39,7 +39,7 @@ export interface SearchProps
|
||||
*/
|
||||
label: React.ReactNode;
|
||||
/**
|
||||
* Hides the label and description visually while keeping them available to screen readers.
|
||||
* Shows label and description for screenreaders only.
|
||||
* @default true
|
||||
*/
|
||||
hideLabel?: boolean;
|
||||
|
||||
@@ -23,7 +23,7 @@ export interface SelectProps
|
||||
*/
|
||||
label: React.ReactNode;
|
||||
/**
|
||||
* Hides the label and description visually while keeping them available to screen readers.
|
||||
* Shows label and description for screenreaders only.
|
||||
*/
|
||||
hideLabel?: boolean;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export interface SwitchProps
|
||||
*/
|
||||
children: React.ReactNode;
|
||||
/**
|
||||
* Hides the label and description visually while keeping them available to screen readers.
|
||||
* If enabled shows the label and description for screenreaders only.
|
||||
*/
|
||||
hideLabel?: boolean;
|
||||
/**
|
||||
|
||||
@@ -36,7 +36,7 @@ export interface TextareaProps
|
||||
*/
|
||||
label: React.ReactNode;
|
||||
/**
|
||||
* Hides the label and description visually while keeping them available to screen readers.
|
||||
* If enabled shows the label and description for screenreaders only.
|
||||
*/
|
||||
hideLabel?: boolean;
|
||||
/**
|
||||
|
||||
@@ -20,7 +20,7 @@ export interface TextFieldProps
|
||||
*/
|
||||
htmlSize?: number;
|
||||
/**
|
||||
* Hides the label and description visually while keeping them available to screen readers
|
||||
* If enabled shows the label and description for screenreaders only
|
||||
*/
|
||||
hideLabel?: boolean;
|
||||
/**
|
||||
|
||||
@@ -599,7 +599,15 @@ export const CustomLabel: StoryFn = () => {
|
||||
|
||||
export const OverflowingAxisLabels: StoryFn = () => {
|
||||
return (
|
||||
<div style={{ width: "800px" }}>
|
||||
<div
|
||||
style={{
|
||||
width: "800px",
|
||||
maxWidth: "100%",
|
||||
minWidth: "200px",
|
||||
resize: "horizontal",
|
||||
overflow: "auto",
|
||||
}}
|
||||
>
|
||||
<Timeline
|
||||
startDate={new Date("2026-05-04")}
|
||||
endDate={new Date("2026-05-31")}
|
||||
|
||||
@@ -97,6 +97,40 @@ function TwoRowTimelineWithPins() {
|
||||
);
|
||||
}
|
||||
|
||||
function ResponsiveAxisTimeline() {
|
||||
return (
|
||||
<>
|
||||
{(["left", "right"] as const).flatMap((direction) =>
|
||||
[
|
||||
["2026-05-04", "2026-05-31"],
|
||||
["2024-02-15", "2025-03-05"],
|
||||
["2022-06-15", "2047-07-05"],
|
||||
].map(([start, end]) => (
|
||||
<div
|
||||
key={`${direction}-${start}`}
|
||||
data-axis-size
|
||||
style={{ width: "320px" }}
|
||||
>
|
||||
<Timeline
|
||||
direction={direction}
|
||||
startDate={new Date(start)}
|
||||
endDate={new Date(end)}
|
||||
>
|
||||
<Timeline.Row label="Row">
|
||||
<Timeline.Period
|
||||
start={new Date(start)}
|
||||
end={new Date(end)}
|
||||
status="success"
|
||||
/>
|
||||
</Timeline.Row>
|
||||
</Timeline>
|
||||
</div>
|
||||
)),
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const HorizontalPeriodNavigation: Story = {
|
||||
render: () => <TwoRowTimeline />,
|
||||
play: async ({ canvasElement }) => {
|
||||
@@ -179,3 +213,86 @@ export const PinNavigation: Story = {
|
||||
expect(pins[0]).toHaveFocus();
|
||||
},
|
||||
};
|
||||
|
||||
export const AxisLabelsAdaptToWidth: Story = {
|
||||
render: () => <ResponsiveAxisTimeline />,
|
||||
play: async ({ canvasElement }) => {
|
||||
const containers =
|
||||
canvasElement.querySelectorAll<HTMLElement>("[data-axis-size]");
|
||||
expect(containers).toHaveLength(6);
|
||||
|
||||
for (const container of containers) {
|
||||
const axis = container.querySelector<HTMLElement>(
|
||||
".aksel-timeline__axislabels",
|
||||
);
|
||||
const finestTier = axis?.querySelector<HTMLElement>(
|
||||
".aksel-timeline__axislabels-tier",
|
||||
);
|
||||
if (!axis || !finestTier) {
|
||||
throw new Error("Timeline axis or label tier not found");
|
||||
}
|
||||
const markup = axis.innerHTML;
|
||||
const height = axis.getBoundingClientRect().height;
|
||||
const selectedDensities = new Set<string | null>();
|
||||
const labelColumnWidth =
|
||||
container.getBoundingClientRect().width -
|
||||
axis.getBoundingClientRect().width;
|
||||
const minSlotWidth =
|
||||
4.5 *
|
||||
Number.parseFloat(
|
||||
window.getComputedStyle(document.documentElement).fontSize,
|
||||
);
|
||||
|
||||
const breakpoint =
|
||||
(minSlotWidth * 100) / Number.parseFloat(finestTier.style.width) +
|
||||
labelColumnWidth;
|
||||
|
||||
for (const width of [200, breakpoint, 2400]) {
|
||||
container.style.width = `${width}px`;
|
||||
await new Promise<void>((resolve) =>
|
||||
requestAnimationFrame(() => resolve()),
|
||||
);
|
||||
|
||||
const visibleLabels = [
|
||||
...axis.querySelectorAll<HTMLElement>(
|
||||
".aksel-timeline__axislabels-label",
|
||||
),
|
||||
].filter((label) => label.getClientRects().length > 0);
|
||||
const densities = new Set(
|
||||
visibleLabels.map((label) =>
|
||||
label.getAttribute("data-axis-labels-density"),
|
||||
),
|
||||
);
|
||||
expect(densities.size, `Axis density at container width ${width}`).toBe(
|
||||
1,
|
||||
);
|
||||
for (const density of densities) {
|
||||
selectedDensities.add(density);
|
||||
}
|
||||
|
||||
const axisBounds = axis.getBoundingClientRect();
|
||||
const textBounds = visibleLabels
|
||||
.map((label) => {
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(label);
|
||||
return range.getBoundingClientRect();
|
||||
})
|
||||
.sort((a, b) => a.left - b.left);
|
||||
|
||||
const labelsFit = textBounds.every(
|
||||
(bounds, index) =>
|
||||
bounds.left >= axisBounds.left - 1 &&
|
||||
bounds.right <= axisBounds.right + 1 &&
|
||||
(index === 0 || bounds.left >= textBounds[index - 1].right),
|
||||
);
|
||||
expect(
|
||||
labelsFit,
|
||||
`Labels stay within the axis without overlap at container width ${width}`,
|
||||
).toBe(true);
|
||||
expect(axisBounds.height).toBe(height);
|
||||
expect(axis.innerHTML).toBe(markup);
|
||||
}
|
||||
expect(selectedDensities.size).toBeGreaterThan(1);
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
addMonths,
|
||||
addYears,
|
||||
differenceInDays,
|
||||
differenceInMilliseconds,
|
||||
differenceInMonths,
|
||||
differenceInYears,
|
||||
endOfMonth,
|
||||
@@ -14,31 +15,45 @@ import {
|
||||
startOfYear,
|
||||
subDays,
|
||||
} from "date-fns";
|
||||
import React, { useCallback, useMemo } from "react";
|
||||
import React, { useMemo } from "react";
|
||||
import { Detail } from "../../typography/Detail";
|
||||
import { useDateLocale, useI18n } from "../../utils/i18n/i18n.hooks";
|
||||
import type { TFunction } from "../../utils/i18n/i18n.types";
|
||||
import { useTimelineContext } from "../root/TimelineRoot.context";
|
||||
import { isVisible } from "../utils";
|
||||
import { horizontalPositionAndWidth } from "../utils/calc";
|
||||
import type { AxisLabel, AxisLabelTemplates } from "../utils/types.external";
|
||||
|
||||
// Use 23 hours as a conservative day length so DST-shortened days still qualify.
|
||||
const MIN_SAFE_DAY_IN_MILLISECONDS = 23 * 60 * 60 * 1000;
|
||||
const MAX_LABEL_COUNT = 100;
|
||||
|
||||
interface AxisLabelSet {
|
||||
id: string;
|
||||
labels: AxisLabel[];
|
||||
slotWidth: number;
|
||||
}
|
||||
|
||||
const dayLabels = (
|
||||
start: Date,
|
||||
end: Date,
|
||||
totalDays: number,
|
||||
intervalDays: number,
|
||||
direction: "left" | "right",
|
||||
template: string,
|
||||
locale: Locale,
|
||||
): AxisLabel[] => {
|
||||
const increment = Math.ceil(totalDays / 10);
|
||||
const totalDays = differenceInDays(end, start);
|
||||
const count = Math.ceil(totalDays / intervalDays) + 1;
|
||||
|
||||
if (count > MAX_LABEL_COUNT) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const lastDay = startOfDay(end);
|
||||
const count = Math.ceil(totalDays / increment);
|
||||
return Array.from({ length: count }, (_, i) => {
|
||||
const day: Date = subDays(lastDay, i * increment);
|
||||
const day: Date = subDays(lastDay, i * intervalDays);
|
||||
const { horizontalPosition, width } = horizontalPositionAndWidth(
|
||||
day,
|
||||
addDays(day, 1),
|
||||
addDays(day, intervalDays),
|
||||
start,
|
||||
end,
|
||||
);
|
||||
@@ -55,18 +70,25 @@ const dayLabels = (
|
||||
const monthLabels = (
|
||||
start: Date,
|
||||
end: Date,
|
||||
intervalMonths: number,
|
||||
direction: "left" | "right",
|
||||
template: string,
|
||||
locale: Locale,
|
||||
): AxisLabel[] => {
|
||||
const startMonth = startOfMonth(start);
|
||||
const endMonth = endOfMonth(end);
|
||||
const numberOfMonths = differenceInMonths(endMonth, startMonth) + 1;
|
||||
// Count calendar boundaries at this interval, not elapsed 30-day periods.
|
||||
const numberOfMonths =
|
||||
Math.floor(differenceInMonths(endMonth, startMonth) / intervalMonths) + 1;
|
||||
if (numberOfMonths > MAX_LABEL_COUNT) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Array.from({ length: numberOfMonths }, (_, i) => {
|
||||
const month: Date = addMonths(startMonth, i);
|
||||
const month: Date = addMonths(startMonth, i * intervalMonths);
|
||||
const { horizontalPosition, width } = horizontalPositionAndWidth(
|
||||
month,
|
||||
addMonths(month, 1),
|
||||
addMonths(month, intervalMonths),
|
||||
start,
|
||||
end,
|
||||
);
|
||||
@@ -83,18 +105,25 @@ const monthLabels = (
|
||||
const yearLabels = (
|
||||
start: Date,
|
||||
end: Date,
|
||||
intervalYears: number,
|
||||
direction: "left" | "right",
|
||||
template: string,
|
||||
locale: Locale,
|
||||
): AxisLabel[] => {
|
||||
const firstYear = startOfYear(start);
|
||||
const lastYear = endOfYear(end);
|
||||
const yearCount = differenceInYears(lastYear, start) + 1;
|
||||
|
||||
const yearCount =
|
||||
Math.floor(differenceInYears(lastYear, start) / intervalYears) + 1;
|
||||
if (yearCount > MAX_LABEL_COUNT) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Array.from({ length: yearCount }, (_, i) => {
|
||||
const year: Date = addYears(firstYear, i);
|
||||
const year: Date = addYears(firstYear, i * intervalYears);
|
||||
const { horizontalPosition, width } = horizontalPositionAndWidth(
|
||||
year,
|
||||
addYears(year, 1),
|
||||
addYears(year, intervalYears),
|
||||
start,
|
||||
end,
|
||||
);
|
||||
@@ -108,32 +137,99 @@ const yearLabels = (
|
||||
});
|
||||
};
|
||||
|
||||
const getLabels = (
|
||||
const getLabelSets = (
|
||||
start: Date,
|
||||
end: Date,
|
||||
direction: "left" | "right",
|
||||
locale: Locale,
|
||||
translate: TFunction<"Timeline">,
|
||||
): AxisLabel[] => {
|
||||
const totalDays = differenceInDays(end, start);
|
||||
|
||||
/* Zero or negative total days can break further calculations */
|
||||
if (totalDays <= 0) {
|
||||
): AxisLabelSet[] => {
|
||||
const duration = differenceInMilliseconds(end, start);
|
||||
if (!Number.isFinite(duration) || duration <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (totalDays < 40) {
|
||||
const dayTemplate = translate("dayFormat");
|
||||
return dayLabels(start, end, totalDays, direction, dayTemplate, locale);
|
||||
}
|
||||
|
||||
if (totalDays < 370) {
|
||||
const monthTemplate = translate("monthFormat");
|
||||
return monthLabels(start, end, direction, monthTemplate, locale);
|
||||
}
|
||||
|
||||
const dayTemplate = translate("dayFormat");
|
||||
const monthTemplate = translate("monthFormat");
|
||||
const yearTemplate = translate("yearFormat");
|
||||
return yearLabels(start, end, direction, yearTemplate, locale);
|
||||
|
||||
const intervals: {
|
||||
id: string;
|
||||
template: string;
|
||||
getLabels: () => AxisLabel[];
|
||||
}[] = [];
|
||||
|
||||
/* CSS progressively reveals nested tiers */
|
||||
for (const intervalDays of [1, 2, 7, 8, 14]) {
|
||||
if (intervalDays * MIN_SAFE_DAY_IN_MILLISECONDS > duration) {
|
||||
continue;
|
||||
}
|
||||
intervals.push({
|
||||
id: `day-${intervalDays}`,
|
||||
template: dayTemplate,
|
||||
getLabels: () =>
|
||||
dayLabels(start, end, intervalDays, direction, dayTemplate, locale),
|
||||
});
|
||||
}
|
||||
|
||||
for (const intervalMonths of [1, 2, 3, 6]) {
|
||||
const intervalDays = intervalMonths * 28;
|
||||
if (intervalDays * MIN_SAFE_DAY_IN_MILLISECONDS > duration) {
|
||||
continue;
|
||||
}
|
||||
intervals.push({
|
||||
id: `month-${intervalMonths}`,
|
||||
template: monthTemplate,
|
||||
getLabels: () =>
|
||||
monthLabels(
|
||||
start,
|
||||
end,
|
||||
intervalMonths,
|
||||
direction,
|
||||
monthTemplate,
|
||||
locale,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
for (let intervalYears = 1; ; intervalYears *= 2) {
|
||||
const intervalDays = intervalYears * 365;
|
||||
if (intervalDays * MIN_SAFE_DAY_IN_MILLISECONDS > duration) {
|
||||
break;
|
||||
}
|
||||
intervals.push({
|
||||
id: `year-${intervalYears}`,
|
||||
template: yearTemplate,
|
||||
getLabels: () =>
|
||||
yearLabels(start, end, intervalYears, direction, yearTemplate, locale),
|
||||
});
|
||||
}
|
||||
|
||||
return intervals
|
||||
.map(({ id, template, getLabels }) => {
|
||||
const labels = getLabels();
|
||||
/* Use the narrowest calendar slot so this tier fits even in shorter periods */
|
||||
const slotWidth = Math.min(100, ...labels.map(({ width }) => width));
|
||||
const visibleLabels = labels
|
||||
.filter(
|
||||
({ horizontalPosition }) =>
|
||||
horizontalPosition >= 0 && horizontalPosition + slotWidth <= 100,
|
||||
)
|
||||
.sort((a, b) => a.horizontalPosition - b.horizontalPosition);
|
||||
|
||||
/* Coarse interval may have no calendar boundary inside the range */
|
||||
if (labels.length > 0 && visibleLabels.length === 0) {
|
||||
visibleLabels.push({
|
||||
...labels[0],
|
||||
date: start,
|
||||
horizontalPosition: 0,
|
||||
label: format(start, template, { locale }),
|
||||
});
|
||||
}
|
||||
|
||||
return { id, slotWidth, labels: visibleLabels };
|
||||
})
|
||||
.filter(({ labels }) => labels.length > 0);
|
||||
};
|
||||
|
||||
const TimelineAxisLabelsInternal = ({
|
||||
@@ -150,41 +246,51 @@ const TimelineAxisLabelsInternal = ({
|
||||
});
|
||||
const locale = useDateLocale();
|
||||
|
||||
const labels = useMemo(
|
||||
() =>
|
||||
getLabels(startDate, endDate, direction, locale, translate)
|
||||
.filter(isVisible)
|
||||
.sort((a, b) => a.horizontalPosition - b.horizontalPosition),
|
||||
const labelSets = useMemo(
|
||||
() => getLabelSets(startDate, endDate, direction, locale, translate),
|
||||
[startDate, endDate, direction, locale, translate],
|
||||
);
|
||||
|
||||
const getLabelWidth = useCallback(
|
||||
(index: number): string => {
|
||||
const next = labels[index + 1];
|
||||
const bound = next ? next.horizontalPosition : 100;
|
||||
return `${(bound - labels[index].horizontalPosition).toFixed(3)}%`;
|
||||
},
|
||||
[labels],
|
||||
);
|
||||
|
||||
const justifyContent = direction === "left" ? "flex-start" : "flex-end";
|
||||
|
||||
return (
|
||||
<div className="aksel-timeline__axislabels" aria-hidden="true">
|
||||
{labels.map((etikett, index) => (
|
||||
<Detail
|
||||
className="aksel-timeline__axislabels-label"
|
||||
as="div"
|
||||
key={etikett.label}
|
||||
style={{
|
||||
justifyContent,
|
||||
[direction]: `${etikett.horizontalPosition.toFixed(3)}%`,
|
||||
width: getLabelWidth(index),
|
||||
}}
|
||||
>
|
||||
{etikett.label}
|
||||
</Detail>
|
||||
))}
|
||||
{labelSets.reduceRight<React.ReactNode>(
|
||||
(coarserTier, labelSet, index) => (
|
||||
<div
|
||||
className="aksel-timeline__axislabels-tier"
|
||||
key={labelSet.id}
|
||||
data-axis-labels-coarsest={
|
||||
index === labelSets.length - 1 || undefined
|
||||
}
|
||||
style={{
|
||||
[direction]: 0,
|
||||
width: `${
|
||||
(labelSet.slotWidth /
|
||||
(labelSets[index - 1]?.slotWidth ?? 100)) *
|
||||
100
|
||||
}%`,
|
||||
}}
|
||||
>
|
||||
{labelSet.labels.map((label) => (
|
||||
<Detail
|
||||
className="aksel-timeline__axislabels-label"
|
||||
as="div"
|
||||
key={label.date.getTime()}
|
||||
data-axis-labels-density={labelSet.id}
|
||||
style={{
|
||||
textAlign: direction,
|
||||
[direction]: `${
|
||||
(label.horizontalPosition / labelSet.slotWidth) * 100
|
||||
}%`,
|
||||
}}
|
||||
>
|
||||
{label.label}
|
||||
</Detail>
|
||||
))}
|
||||
{coarserTier}
|
||||
</div>
|
||||
),
|
||||
null,
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -86,6 +86,18 @@ const TimelineRowBase = forwardRef<HTMLOListElement, TimelineRowProps>(
|
||||
onKeyDown={handleRowKeyDown}
|
||||
data-timeline-row
|
||||
>
|
||||
<span
|
||||
className="aksel-timeline__row-line"
|
||||
style={{ left: "12.75%" }}
|
||||
/>
|
||||
<span
|
||||
className="aksel-timeline__row-line"
|
||||
style={{ left: "24.28%" }}
|
||||
/>
|
||||
<span
|
||||
className="aksel-timeline__row-line"
|
||||
style={{ left: "49.37%" }}
|
||||
/>
|
||||
<ol
|
||||
{...rest}
|
||||
ref={ref}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import React, { useState } from "react";
|
||||
import { expect, fireEvent, fn, within } from "storybook/test";
|
||||
import { expect, fireEvent, fn } from "storybook/test";
|
||||
import type { ListboxOptionProps } from "./option/ListboxOption";
|
||||
import { Listbox } from "./root/ListboxRoot";
|
||||
|
||||
@@ -212,14 +212,10 @@ export const EnterKey: StoryObj<{
|
||||
},
|
||||
};
|
||||
|
||||
const ListboxStoryWithFilterInput = () => {
|
||||
const [virtuallyFocusedOptionId, setVirtuallyFocusedOptionId] =
|
||||
useState("second-item");
|
||||
const ListboxStoryWithSelectedOption = () => {
|
||||
const [virtuallyFocusedOptionId, setVirtuallyFocusedOptionId] = useState("");
|
||||
return (
|
||||
<Listbox setVirtuallyFocusedOptionId={setVirtuallyFocusedOptionId}>
|
||||
<Listbox.InputSlot virtuallyFocusedOptionId={virtuallyFocusedOptionId}>
|
||||
<input type="text" aria-label="Filter" />
|
||||
</Listbox.InputSlot>
|
||||
<Listbox.Options>
|
||||
<Listbox.Option
|
||||
id="first-item"
|
||||
@@ -230,12 +226,12 @@ const ListboxStoryWithFilterInput = () => {
|
||||
First item
|
||||
</Listbox.Option>
|
||||
<Listbox.Option
|
||||
id="second-item"
|
||||
id="selected-item"
|
||||
onClick={() => {}}
|
||||
hasVirtualFocus={virtuallyFocusedOptionId === "second-item"}
|
||||
aria-selected={false}
|
||||
hasVirtualFocus={virtuallyFocusedOptionId === "selected-item"}
|
||||
aria-selected
|
||||
>
|
||||
Second item
|
||||
Selected item
|
||||
</Listbox.Option>
|
||||
<Listbox.Option
|
||||
id="last-item"
|
||||
@@ -250,30 +246,19 @@ const ListboxStoryWithFilterInput = () => {
|
||||
);
|
||||
};
|
||||
|
||||
// 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,
|
||||
// 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,
|
||||
play: async ({ canvasElement }) => {
|
||||
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();
|
||||
pressKey(canvasElement, "ArrowDown");
|
||||
expect(getVirtuallyFocusedValue(canvasElement)).toBe("selected-item");
|
||||
},
|
||||
};
|
||||
export const ArrowDownOnLastOptionFocusInput: StoryObj = {
|
||||
render: ListboxStoryWithFilterInput,
|
||||
export const ArrowUpFocusesSelectedOption: StoryObj = {
|
||||
render: ListboxStoryWithSelectedOption,
|
||||
play: async ({ canvasElement }) => {
|
||||
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();
|
||||
pressKey(canvasElement, "ArrowUp");
|
||||
expect(getVirtuallyFocusedValue(canvasElement)).toBe("selected-item");
|
||||
},
|
||||
};
|
||||
@@ -24,7 +24,6 @@ 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,34 +72,30 @@ const ListboxComponent = forwardRef<HTMLDivElement, ListboxProps>(
|
||||
listbox.focus();
|
||||
}
|
||||
|
||||
const handleArrowUpOrDown = (
|
||||
getNextOption: (currentOption: HTMLElement) => HTMLElement | null,
|
||||
const virtuallyFocusWithFallback = (
|
||||
getNextElement: (currentOption: HTMLElement) => HTMLElement | null,
|
||||
getFallback: () => HTMLElement | null,
|
||||
) => {
|
||||
event.preventDefault();
|
||||
if (!focusedOptionElm) {
|
||||
virtuallyFocusOption(getFallback());
|
||||
const fallback =
|
||||
listbox.querySelector<HTMLElement>('[aria-selected="true"]') ||
|
||||
getFallback();
|
||||
virtuallyFocusOption(fallback);
|
||||
return;
|
||||
}
|
||||
const nextOption = getNextOption(focusedOptionElm);
|
||||
const nextOption = getNextElement(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":
|
||||
handleArrowUpOrDown(findPrevOption, getLastOption);
|
||||
virtuallyFocusWithFallback(findPrevOption, getLastOption);
|
||||
break;
|
||||
case "ArrowDown":
|
||||
handleArrowUpOrDown(findNextOption, getFirstOption);
|
||||
virtuallyFocusWithFallback(findNextOption, getFirstOption);
|
||||
break;
|
||||
case "PageUp":
|
||||
event.preventDefault();
|
||||
|
||||
@@ -49,7 +49,7 @@ export interface DateInputProps
|
||||
*/
|
||||
label: React.ReactNode;
|
||||
/**
|
||||
* Hides the label and description visually while keeping them available to screen readers
|
||||
* Shows label and description for screen readers only
|
||||
* @default false
|
||||
*/
|
||||
hideLabel?: boolean;
|
||||
|
||||
@@ -27,7 +27,8 @@ export function useControllableState<StateT, ChangeT extends StateT = StateT>({
|
||||
|
||||
const setValue = useEventCallback(
|
||||
(next: ChangeT | ((prevState: StateT) => ChangeT)) => {
|
||||
const nextValue = isFunction(next) ? next(value) : next;
|
||||
const setter = next as (prevState?: StateT) => ChangeT;
|
||||
const nextValue = typeof next === "function" ? setter(value) : next;
|
||||
|
||||
if (!controlled) {
|
||||
setUncontrolledState(nextValue);
|
||||
@@ -39,5 +40,3 @@ export function useControllableState<StateT, ChangeT extends StateT = StateT>({
|
||||
|
||||
return [value, setValue] as const;
|
||||
}
|
||||
|
||||
const isFunction = (value: unknown) => typeof value === "function";
|
||||
@@ -1,7 +1,5 @@
|
||||
# @navikt/ds-tailwind
|
||||
|
||||
## 8.20.1
|
||||
|
||||
## 8.20.0
|
||||
|
||||
## 8.19.2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-tailwind",
|
||||
"version": "8.20.1",
|
||||
"version": "8.20.0",
|
||||
"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.1",
|
||||
"@navikt/ds-tokens": "^8.20.0",
|
||||
"@types/node": "^24.10.9",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"tsx": "^4.23.15",
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# @navikt/ds-tokens
|
||||
|
||||
## 8.20.1
|
||||
|
||||
## 8.20.0
|
||||
|
||||
## 8.19.2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-tokens",
|
||||
"version": "8.20.1",
|
||||
"version": "8.20.0",
|
||||
"description": "Design-tokens for Nav designsystem",
|
||||
"author": "Aksel | Nav designsystem team",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
# 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.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"
|
||||
"@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"
|
||||
}
|
||||
}
|
||||
@@ -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.8",
|
||||
"@sanity/color-input": "6.1.4",
|
||||
"@sanity/image-url": "2.1.1",
|
||||
"@sanity/locale-nb-no": "1.1.39",
|
||||
"@sanity/preview-url-secret": "4.1.5",
|
||||
"@sanity/table": "3.1.20",
|
||||
"@sanity/table": "3.1.16",
|
||||
"@sanity/vision": "6.17.0",
|
||||
"date-fns": "^4.0.0",
|
||||
"lodash": "4.18.1",
|
||||
"react": "19.3.1",
|
||||
"react-dom": "19.3.1",
|
||||
"react": "19.3.0",
|
||||
"react-dom": "19.3.0",
|
||||
"react-rx": "^5.1.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"sanity": "6.17.0",
|
||||
"sanity-plugin-iframe-pane": "5.0.41",
|
||||
"sanity-plugin-iframe-pane": "5.0.35",
|
||||
"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.28.0",
|
||||
"@sanity/blueprints": "0.24.0",
|
||||
"@sanity/export": "7.0.3",
|
||||
"@sanity/functions": "1.10.1",
|
||||
"@sanity/functions": "1.7.2",
|
||||
"@sanity/schema": "6.17.0",
|
||||
"@types/lodash": "4.17.25",
|
||||
"@types/react": "19.2.14",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { sanitize } from "isomorphic-dompurify";
|
||||
import DOMPurify 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 = sanitize(formattedSvgString, {
|
||||
const cleanedSvg = DOMPurify.sanitize(formattedSvgString, {
|
||||
USE_PROFILES: { svg: true },
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { sanitize } from "isomorphic-dompurify";
|
||||
import DOMPurify from "isomorphic-dompurify";
|
||||
import styles from "./ImageAsSvg.module.css";
|
||||
|
||||
/**
|
||||
@@ -27,7 +27,7 @@ async function ImageAsThemedSvg({
|
||||
|
||||
const formattedSvgString = formatSvgString(svgString);
|
||||
|
||||
const cleanedSvg = sanitize(formattedSvgString, {
|
||||
const cleanedSvg = DOMPurify.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": "4.6.0",
|
||||
"isomorphic-dompurify": "2.26.0",
|
||||
"jscodeshift": "17.4.0",
|
||||
"lodash": "4.18.1",
|
||||
"lz-string": "^1.5.0",
|
||||
"next": "16.4.2",
|
||||
"next": "16.3.8",
|
||||
"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.1",
|
||||
"react-dom": "19.3.1",
|
||||
"react": "19.3.0",
|
||||
"react-dom": "19.3.0",
|
||||
"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.4.2",
|
||||
"@next/eslint-plugin-next": "16.4.2",
|
||||
"@next/bundle-analyzer": "16.3.8",
|
||||
"@next/eslint-plugin-next": "16.3.8",
|
||||
"@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: 7,
|
||||
index: 6,
|
||||
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: 9,
|
||||
index: 8,
|
||||
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: 10,
|
||||
index: 9,
|
||||
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: 8,
|
||||
index: 7,
|
||||
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: 11,
|
||||
index: 10,
|
||||
desc: "Eksempel på bruk med react-hook-form.",
|
||||
sandbox: false,
|
||||
};
|
||||
@@ -25,5 +25,5 @@ export const Demo = {
|
||||
};
|
||||
|
||||
export const args: ExampleArgsT = {
|
||||
index: 12,
|
||||
index: 11,
|
||||
};
|
||||
@@ -1,43 +0,0 @@
|
||||
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.1",
|
||||
"@navikt/ds-css": "^8.20.1",
|
||||
"@navikt/ds-react": "^8.20.1",
|
||||
"@navikt/aksel-icons": "^8.20.0",
|
||||
"@navikt/ds-css": "^8.20.0",
|
||||
"@navikt/ds-react": "^8.20.0",
|
||||
"fuse.js": "7.4.2",
|
||||
"react": "19.3.1",
|
||||
"react-dom": "19.3.1"
|
||||
"react": "19.3.0",
|
||||
"react-dom": "19.3.0"
|
||||
},
|
||||
"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.1",
|
||||
"@navikt/aksel-icons": "^8.20.1",
|
||||
"@navikt/ds-tokens": "^8.20.1",
|
||||
"@navikt/aksel": "^8.20.0",
|
||||
"@navikt/aksel-icons": "^8.20.0",
|
||||
"@navikt/ds-tokens": "^8.20.0",
|
||||
"@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.1",
|
||||
"@navikt/ds-css": "^8.20.1",
|
||||
"@navikt/ds-react": "^8.20.1",
|
||||
"react": "19.3.1"
|
||||
"@navikt/aksel-icons": "^8.20.0",
|
||||
"@navikt/ds-css": "^8.20.0",
|
||||
"@navikt/ds-react": "^8.20.0",
|
||||
"react": "19.3.0"
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -111,9 +111,9 @@
|
||||
"lint-staged": "17.6.0",
|
||||
"playwright": "1.61.1",
|
||||
"prettier": "3.9.6",
|
||||
"react": "19.3.1",
|
||||
"react": "19.3.0",
|
||||
"react-docgen-typescript": "2.4.0",
|
||||
"react-dom": "19.3.1",
|
||||
"react-dom": "19.3.0",
|
||||
"storybook": "^10.6.3",
|
||||
"stylelint": "^17.15.0",
|
||||
"stylelint-config-standard": "^40.0.0",
|
||||
|
||||
Reference in new issue
Block a user