mirror of
https://github.com/navikt/aksel.git
synced 2026-11-01 02:14:05 +00:00
Compare commits
1
Commits
main
..
date-imports
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0c85c69fd |
No files matched your search
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@navikt/ds-react": patch
|
||||
---
|
||||
|
||||
Date utilities: Import date-fns functions and locales from their own paths, so unbundled Node imports (SSR, Vitest/Jest) no longer load every date-fns locale.
|
||||
@@ -142,7 +142,7 @@
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
.aksel-combobox2:not(:has(.aksel-combobox2__modal)) & {
|
||||
.aksel-combobox2:has(:not(.aksel-combobox2__modal)) & {
|
||||
outline: 1px solid var(--ax-border-neutral-subtleA);
|
||||
outline-offset: -1px;
|
||||
border-radius: var(--ax-radius-8);
|
||||
@@ -158,21 +158,26 @@
|
||||
margin: -1px;
|
||||
}
|
||||
|
||||
.aksel-combobox2__filter .aksel-search__wrapper:has(.aksel-search__input:focus-visible) {
|
||||
outline: none;
|
||||
position: relative;
|
||||
.aksel-combobox2__filter .aksel-search__wrapper {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
border: 2px solid var(--ax-border-focus);
|
||||
}
|
||||
&:has(.aksel-search__input:focus-visible) {
|
||||
outline: none;
|
||||
position: relative;
|
||||
|
||||
.aksel-combobox2:not(:has(.aksel-combobox2__modal)) &::after {
|
||||
border-top-left-radius: var(--ax-radius-8);
|
||||
border-top-right-radius: var(--ax-radius-8);
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
border: 2px solid var(--ax-border-focus);
|
||||
}
|
||||
|
||||
.aksel-combobox2:has(:not(.aksel-combobox2__modal)) &::after {
|
||||
border-top-left-radius: var(--ax-radius-8);
|
||||
border-top-right-radius: var(--ax-radius-8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +189,7 @@
|
||||
border-color: var(--ax-border-neutral-subtleA);
|
||||
}
|
||||
|
||||
.aksel-combobox2__popup[data-size="medium"] & {
|
||||
.aksel-combobox2[data-size="medium"] & {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
@@ -216,6 +221,15 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/************************************************
|
||||
* Group *
|
||||
************************************************/
|
||||
|
||||
.aksel-combobox2__group-label {
|
||||
padding: var(--ax-space-8) var(--ax-space-8);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/************************************************
|
||||
* Option *
|
||||
************************************************/
|
||||
@@ -239,7 +253,7 @@
|
||||
margin-top: var(--ax-space-2);
|
||||
float: left;
|
||||
|
||||
.aksel-combobox2__popup[data-size="small"] & {
|
||||
.aksel-combobox2[data-size="small"] & {
|
||||
--__axc-combobox2-option-checkmark-size: 1rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { type Locale, format } from "date-fns";
|
||||
import type { Locale } from "date-fns";
|
||||
import { format } from "date-fns/format";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import type { CalendarDay, Modifiers } from "react-day-picker";
|
||||
import { cl } from "../../utils/helpers";
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import {
|
||||
type Locale,
|
||||
format,
|
||||
getMonth,
|
||||
getYear,
|
||||
setMonth,
|
||||
setYear,
|
||||
startOfMonth,
|
||||
} from "date-fns";
|
||||
import type { Locale } from "date-fns";
|
||||
import { format } from "date-fns/format";
|
||||
import { getMonth } from "date-fns/getMonth";
|
||||
import { getYear } from "date-fns/getYear";
|
||||
import { setMonth } from "date-fns/setMonth";
|
||||
import { setYear } from "date-fns/setYear";
|
||||
import { startOfMonth } from "date-fns/startOfMonth";
|
||||
import React, { type ChangeEvent, useCallback } from "react";
|
||||
import { type CalendarMonth, useDayPicker } from "react-day-picker";
|
||||
import { ArrowLeftIcon, ArrowRightIcon } from "@navikt/aksel-icons";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isWeekend } from "date-fns";
|
||||
import { isWeekend } from "date-fns/isWeekend";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import {
|
||||
type ClassNames,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isSameDay } from "date-fns";
|
||||
import { isSameDay } from "date-fns/isSameDay";
|
||||
import React, { forwardRef, useRef, useState } from "react";
|
||||
import type { DateRange } from "react-day-picker";
|
||||
import { useId } from "../../utils-external";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { differenceInCalendarDays, isWeekend } from "date-fns";
|
||||
import { differenceInCalendarDays } from "date-fns/differenceInCalendarDays";
|
||||
import { isWeekend } from "date-fns/isWeekend";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { type DayEventHandler, dateMatchModifiers } from "react-day-picker";
|
||||
import { getLocaleFromString } from "../../utils/components/date/Date.locale";
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { differenceInCalendarDays, isSameDay, isWeekend } from "date-fns";
|
||||
import { differenceInCalendarDays } from "date-fns/differenceInCalendarDays";
|
||||
import { isSameDay } from "date-fns/isSameDay";
|
||||
import { isWeekend } from "date-fns/isWeekend";
|
||||
import React, { useState } from "react";
|
||||
import { dateMatchModifiers } from "react-day-picker";
|
||||
import { getLocaleFromString } from "../../utils/components/date/Date.locale";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { forwardRef } from "react";
|
||||
import React from "react";
|
||||
import { Dialog } from "../../dialog";
|
||||
import { BodyShort, ErrorMessage } from "../../typography";
|
||||
import { cl } from "../../utils/helpers";
|
||||
@@ -36,114 +36,109 @@ interface ComboboxProps<
|
||||
name?: string;
|
||||
}
|
||||
|
||||
const Combobox = forwardRef(
|
||||
<T extends ComboboxOptionData | ComboboxGroupData<ComboboxOptionData>>(
|
||||
function Combobox<
|
||||
T extends ComboboxOptionData | ComboboxGroupData<ComboboxOptionData>,
|
||||
>({
|
||||
label,
|
||||
hideLabel,
|
||||
name,
|
||||
size: sizeProp,
|
||||
error,
|
||||
errorId: errorIdProp,
|
||||
disabled: disabledProp,
|
||||
description,
|
||||
id,
|
||||
readOnly: readOnlyProp,
|
||||
multiple,
|
||||
...rest
|
||||
}: ComboboxProps<T>) {
|
||||
const {
|
||||
inputProps,
|
||||
errorId,
|
||||
showErrorMsg,
|
||||
hasError,
|
||||
size,
|
||||
inputDescriptionId,
|
||||
readOnly,
|
||||
} = useFormField(
|
||||
{
|
||||
label,
|
||||
hideLabel,
|
||||
name,
|
||||
size: sizeProp,
|
||||
description,
|
||||
disabled: disabledProp,
|
||||
error,
|
||||
errorId: errorIdProp,
|
||||
disabled: disabledProp,
|
||||
description,
|
||||
id,
|
||||
readOnly: readOnlyProp,
|
||||
multiple,
|
||||
...rest
|
||||
}: ComboboxProps<T>,
|
||||
ref: React.ForwardedRef<HTMLButtonElement>,
|
||||
) => {
|
||||
const {
|
||||
inputProps,
|
||||
errorId,
|
||||
showErrorMsg,
|
||||
hasError,
|
||||
size,
|
||||
inputDescriptionId,
|
||||
readOnly,
|
||||
} = useFormField(
|
||||
{
|
||||
description,
|
||||
disabled: disabledProp,
|
||||
error,
|
||||
errorId: errorIdProp,
|
||||
id,
|
||||
readOnly: readOnlyProp,
|
||||
size: sizeProp,
|
||||
},
|
||||
"combobox",
|
||||
);
|
||||
size: sizeProp,
|
||||
},
|
||||
"combobox",
|
||||
);
|
||||
|
||||
return (
|
||||
<ComboboxRoot
|
||||
size={size}
|
||||
disabled={inputProps.disabled}
|
||||
multiple={multiple}
|
||||
{...rest}
|
||||
return (
|
||||
<ComboboxRoot
|
||||
size={size}
|
||||
disabled={inputProps.disabled}
|
||||
multiple={multiple}
|
||||
{...rest}
|
||||
>
|
||||
<ComboboxLabel
|
||||
htmlFor={inputProps.id}
|
||||
hide={hideLabel}
|
||||
readOnly={readOnly}
|
||||
>
|
||||
<ComboboxLabel
|
||||
htmlFor={inputProps.id}
|
||||
hide={hideLabel}
|
||||
readOnly={readOnly}
|
||||
>
|
||||
{label}
|
||||
</ComboboxLabel>
|
||||
{!!description && (
|
||||
<BodyShort
|
||||
className={cl(
|
||||
"aksel-form-field__description aksel-combobox2__description",
|
||||
{ "aksel-sr-only": hideLabel },
|
||||
)}
|
||||
id={inputDescriptionId}
|
||||
size={size}
|
||||
as="div"
|
||||
>
|
||||
{description}
|
||||
</BodyShort>
|
||||
)}
|
||||
<ComboboxTrigger readOnly={readOnly} {...inputProps}>
|
||||
<ComboboxField ref={ref} hasError={hasError} />
|
||||
</ComboboxTrigger>
|
||||
<div
|
||||
className="aksel-form-field__error aksel-combobox2__error"
|
||||
id={errorId}
|
||||
aria-relevant="additions removals"
|
||||
aria-live="polite"
|
||||
>
|
||||
{showErrorMsg && (
|
||||
<ErrorMessage size={size} showIcon>
|
||||
{error}
|
||||
</ErrorMessage>
|
||||
{label}
|
||||
</ComboboxLabel>
|
||||
{!!description && (
|
||||
<BodyShort
|
||||
className={cl(
|
||||
"aksel-form-field__description aksel-combobox2__description",
|
||||
{ "aksel-sr-only": hideLabel },
|
||||
)}
|
||||
</div>
|
||||
|
||||
{name &&
|
||||
rest.value.map((val) => (
|
||||
<input key={val} type="hidden" name={name} value={val} />
|
||||
))}
|
||||
|
||||
<ComboboxOverlay
|
||||
mobileHeader={
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{label}</Dialog.Title>
|
||||
{!!description && (
|
||||
<Dialog.Description>{description}</Dialog.Description>
|
||||
)}
|
||||
</Dialog.Header>
|
||||
}
|
||||
id={inputDescriptionId}
|
||||
size={size}
|
||||
as="div"
|
||||
>
|
||||
<ComboboxPopup>
|
||||
<ComboboxFilter />
|
||||
<ComboboxList />
|
||||
</ComboboxPopup>
|
||||
</ComboboxOverlay>
|
||||
</ComboboxRoot>
|
||||
);
|
||||
},
|
||||
) as <T extends ComboboxOptionData | ComboboxGroupData<ComboboxOptionData>>(
|
||||
props: ComboboxProps<T> & React.RefAttributes<HTMLButtonElement>,
|
||||
) => React.ReactElement;
|
||||
{description}
|
||||
</BodyShort>
|
||||
)}
|
||||
<ComboboxTrigger readOnly={readOnly} {...inputProps}>
|
||||
<ComboboxField hasError={hasError} />
|
||||
</ComboboxTrigger>
|
||||
<div
|
||||
className="aksel-form-field__error aksel-combobox2__error"
|
||||
id={errorId}
|
||||
aria-relevant="additions removals"
|
||||
aria-live="polite"
|
||||
>
|
||||
{showErrorMsg && (
|
||||
<ErrorMessage size={size} showIcon>
|
||||
{error}
|
||||
</ErrorMessage>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{name &&
|
||||
rest.value.map((val) => (
|
||||
<input key={val} type="hidden" name={name} value={val} />
|
||||
))}
|
||||
|
||||
<ComboboxOverlay
|
||||
mobileHeader={
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{label}</Dialog.Title>
|
||||
{!!description && (
|
||||
<Dialog.Description>{description}</Dialog.Description>
|
||||
)}
|
||||
</Dialog.Header>
|
||||
}
|
||||
>
|
||||
<ComboboxPopup>
|
||||
<ComboboxFilter />
|
||||
<ComboboxList />
|
||||
</ComboboxPopup>
|
||||
</ComboboxOverlay>
|
||||
</ComboboxRoot>
|
||||
);
|
||||
}
|
||||
|
||||
// ----------- ComboboxSingle ----------- //
|
||||
|
||||
@@ -154,67 +149,44 @@ interface ComboboxSingleProps<
|
||||
* Called when selected option changes.
|
||||
*/
|
||||
onChange?: (
|
||||
newValue: NonNullable<ComboboxProps<T>["value"]>[number] | null,
|
||||
newValue: NonNullable<ComboboxProps<T>["value"]>[number] | undefined,
|
||||
) => void;
|
||||
/**
|
||||
* Value of the selected option.
|
||||
*/
|
||||
value?: NonNullable<ComboboxProps<T>["value"]>[number] | null;
|
||||
value?: NonNullable<ComboboxProps<T>["value"]>[number];
|
||||
/**
|
||||
* Initial value when uncontrolled.
|
||||
*/
|
||||
defaultValue?: NonNullable<ComboboxProps<T>["value"]>[number];
|
||||
}
|
||||
|
||||
/**
|
||||
* Component for selecting a value from a list of options that can be filtered.
|
||||
*
|
||||
* **WARNING: This component is in active development and may receive breaking changes outside major releases!**
|
||||
*
|
||||
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/TODO)
|
||||
* @see 🏷️ {@link ComboboxSingleProps}
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* <ComboboxSingle
|
||||
* label="Select country"
|
||||
* name="country"
|
||||
* options={countries}
|
||||
* />
|
||||
* ```
|
||||
*/
|
||||
const ComboboxSingle = forwardRef(
|
||||
<T extends ComboboxOptionData | ComboboxGroupData<ComboboxOptionData>>(
|
||||
{ onChange, value, defaultValue, ...rest }: ComboboxSingleProps<T>,
|
||||
ref: React.ForwardedRef<HTMLButtonElement>,
|
||||
function ComboboxSingle<
|
||||
T extends ComboboxOptionData | ComboboxGroupData<ComboboxOptionData>,
|
||||
>({ onChange, value, defaultValue, ...rest }: ComboboxSingleProps<T>) {
|
||||
const [localValue, setLocalValue] = useControllableState({
|
||||
defaultValue,
|
||||
value,
|
||||
onChange,
|
||||
});
|
||||
|
||||
const onToggleOption: ComboboxProps<T>["onToggleOption"] = (
|
||||
option,
|
||||
isSelected,
|
||||
) => {
|
||||
const [localValue, setLocalValue] = useControllableState({
|
||||
defaultValue,
|
||||
value,
|
||||
onChange,
|
||||
});
|
||||
setLocalValue(isSelected ? option.value : undefined);
|
||||
}; // TODO: Dumt at ikke får hele option-objektet... Legge til i onChange? Hvis vi tilbyr onToggle så blir det så likt onChange...
|
||||
|
||||
const onToggleOption: ComboboxProps<T>["onToggleOption"] = (
|
||||
option,
|
||||
isSelected,
|
||||
) => {
|
||||
setLocalValue(isSelected ? option.value : null);
|
||||
}; // TODO: Dumt at ikke får hele option-objektet... Legge til i onChange? Hvis vi tilbyr onToggle så blir det så likt onChange...
|
||||
|
||||
return (
|
||||
<Combobox
|
||||
{...rest}
|
||||
ref={ref}
|
||||
value={localValue ? [localValue] : []}
|
||||
defaultValue={defaultValue ? [defaultValue] : []}
|
||||
multiple={false}
|
||||
onToggleOption={onToggleOption}
|
||||
/>
|
||||
);
|
||||
},
|
||||
) as <T extends ComboboxOptionData | ComboboxGroupData<ComboboxOptionData>>(
|
||||
props: ComboboxSingleProps<T> & React.RefAttributes<HTMLButtonElement>,
|
||||
) => React.ReactElement;
|
||||
return (
|
||||
<Combobox
|
||||
{...rest}
|
||||
value={localValue ? [localValue] : []}
|
||||
defaultValue={defaultValue ? [defaultValue] : []}
|
||||
multiple={false}
|
||||
onToggleOption={onToggleOption}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
export namespace ComboboxSingle {
|
||||
|
||||
@@ -157,7 +157,7 @@ export const Name = () => (
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
const formData = new FormData(event.target);
|
||||
const land = formData.get("land");
|
||||
const land = formData.getAll("land");
|
||||
console.log(land);
|
||||
}}
|
||||
>
|
||||
@@ -168,7 +168,7 @@ export const Name = () => (
|
||||
);
|
||||
|
||||
export const Controlled = () => {
|
||||
const [value, setValue] = useState<MyOption["value"] | null>("opt-1");
|
||||
const [value, setValue] = useState<MyOption["value"] | undefined>("opt-1");
|
||||
|
||||
return (
|
||||
<VStack gap="space-8" minWidth="250px">
|
||||
@@ -180,7 +180,7 @@ export const Controlled = () => {
|
||||
/>
|
||||
Selected: {value}
|
||||
<div>
|
||||
<button type="button" onClick={() => setValue(null)}>
|
||||
<button type="button" onClick={() => setValue(undefined)}>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Meta } from "@storybook/react-vite";
|
||||
import React, { useRef, useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
import { expect, userEvent, within } from "storybook/test";
|
||||
import { ComboboxSingle } from "./Combobox";
|
||||
import { ComboboxFilter } from "./filter/ComboboxFilter";
|
||||
@@ -60,7 +60,7 @@ const groupedOptions = [
|
||||
function BaseCombobox<
|
||||
T extends ComboboxOptionData | ComboboxGroupData<ComboboxOptionData>,
|
||||
>(props: Partial<ComboboxSingle.Props<T>>) {
|
||||
const [value, setValue] = useState<string | null>("opt-1");
|
||||
const [value, setValue] = useState<string | undefined>("option-1");
|
||||
|
||||
return (
|
||||
<ComboboxSingle
|
||||
@@ -81,27 +81,6 @@ AccessibleNameAndDesc.play = async ({ canvasElement }) => {
|
||||
await expect(trigger).toHaveAccessibleDescription("Landet hvor du er født.");
|
||||
};
|
||||
|
||||
export const FocusWithRef = () => {
|
||||
const ref = useRef<HTMLButtonElement>(null);
|
||||
return (
|
||||
<div>
|
||||
<button type="button" onClick={() => ref.current?.focus()}>
|
||||
Fokuser combobox
|
||||
</button>
|
||||
<ComboboxSingle ref={ref} options={countries} label="Velg land" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
FocusWithRef.play = async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const combobox = await canvas.findByRole("combobox", { name: "Velg land" });
|
||||
await expect(combobox).not.toHaveFocus();
|
||||
await userEvent.click(
|
||||
canvas.getByRole("button", { name: "Fokuser combobox" }),
|
||||
);
|
||||
await expect(combobox).toHaveFocus();
|
||||
};
|
||||
|
||||
let optionMemoTestRenderCnt = 0;
|
||||
const optionMemoTestRenderFn = (
|
||||
optOrGroup: (typeof groupedOptions)[number],
|
||||
@@ -114,7 +93,7 @@ const optionMemoTestRenderFn = (
|
||||
return optOrGroup.label;
|
||||
};
|
||||
export const OptionMemoization = () => {
|
||||
const [value, setValue] = useState(["opt-1"]);
|
||||
const [value, setValue] = useState(["option-1"]);
|
||||
|
||||
return (
|
||||
<div style={{ minHeight: "450px" }}>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { forwardRef } from "react";
|
||||
import { ChevronDownIcon } from "@navikt/aksel-icons";
|
||||
import { cl } from "../../../utils/helpers";
|
||||
import { useI18n } from "../../../utils/i18n/i18n.hooks";
|
||||
import {
|
||||
type ComboboxRootProps,
|
||||
useComboboxRootContext,
|
||||
@@ -15,14 +14,10 @@ interface ComboboxFieldProps extends React.ButtonHTMLAttributes<HTMLButtonElemen
|
||||
const ComboboxField = forwardRef<HTMLButtonElement, ComboboxFieldProps>(
|
||||
({ children, hasError, ...rest }, ref) => {
|
||||
const { options, value, size } = useComboboxRootContext();
|
||||
const translate = useI18n("Combobox2");
|
||||
|
||||
const labels = getLabels(value, options);
|
||||
const label =
|
||||
children ??
|
||||
(labels.length > 1
|
||||
? translate("selectedCount", { count: labels.length })
|
||||
: labels[0]);
|
||||
children ?? (labels.length > 1 ? `${labels.length} valgt` : labels[0]);
|
||||
|
||||
return (
|
||||
<div className="aksel-combobox2__field-container">
|
||||
|
||||
@@ -42,7 +42,6 @@ export const ComboboxFilter = (props: ComboboxFilterProps) => {
|
||||
}
|
||||
}}
|
||||
aria-autocomplete="list"
|
||||
aria-expanded="true"
|
||||
inputMode="text"
|
||||
/>
|
||||
</Listbox.InputSlot>
|
||||
|
||||
@@ -55,7 +55,7 @@ function ComboboxListContent<
|
||||
useComboboxRootContext();
|
||||
const memoizedOnToggleOption = useEventCallback(onToggleOption);
|
||||
const deferredFilterString = useDeferredValue(filterString);
|
||||
const maybeAnnouceHits = useHitsAnnouncer(deferredFilterString);
|
||||
const maybeAnnouceHits = useHitsAnnouncer(deferredFilterString, filterString);
|
||||
const translate = useI18n("Combobox2");
|
||||
|
||||
const localOnToggleOption = useCallback(
|
||||
@@ -80,7 +80,6 @@ function ComboboxListContent<
|
||||
maybeAnnouceHits(filteredOptions);
|
||||
|
||||
if (filteredOptions.length === 0) {
|
||||
// TODO: Consider moving outside of role=listbox
|
||||
return (
|
||||
<div className="aksel-combobox2__no-results">{translate("noHits")}</div>
|
||||
);
|
||||
|
||||
@@ -18,9 +18,9 @@ function getNumberOfOptions<T extends ComboboxOptionData>(
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function useHitsAnnouncer(deferredFilterString: string) {
|
||||
function useHitsAnnouncer(deferredFilterString: string, filterString: string) {
|
||||
const translate = useI18n("Combobox2");
|
||||
const oldFilterStringRef = useRef(deferredFilterString);
|
||||
const shouldAnnounceRef = useRef(false);
|
||||
const announce = useAnnounce();
|
||||
|
||||
const announceHits = useMemo(() => {
|
||||
@@ -50,20 +50,20 @@ function useHitsAnnouncer(deferredFilterString: string) {
|
||||
ComboboxOptionData | ComboboxGroupData<ComboboxOptionData>
|
||||
)[],
|
||||
) => {
|
||||
// Announce number of hits when filter string changes.
|
||||
// We can't do this in a useEffect b.c. of how ComboboxList is structured. Consider refactoring.
|
||||
if (deferredFilterString === oldFilterStringRef.current) {
|
||||
return;
|
||||
// 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;
|
||||
}
|
||||
if (filteredOptions.length === 0) {
|
||||
announceHits.clear();
|
||||
announceNoHits();
|
||||
} else {
|
||||
const hits = getNumberOfOptions(filteredOptions);
|
||||
announceNoHits.clear();
|
||||
announceHits(hits);
|
||||
}
|
||||
oldFilterStringRef.current = deferredFilterString;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import { CheckmarkHeavyIcon } from "@navikt/aksel-icons";
|
||||
import { Listbox } from "../../../utils/components/Listbox/root/ListboxRoot";
|
||||
import type { ComboboxListProps } from "../list/ComboboxList";
|
||||
import type { ComboboxOptionData } from "../root/ComboboxRoot";
|
||||
import { highlightSubstring } from "./highlightSubstring";
|
||||
|
||||
export interface ComboboxOptionProps<T extends ComboboxOptionData> {
|
||||
option: T;
|
||||
@@ -50,6 +49,31 @@ function ComboboxOptionComponent<T extends ComboboxOptionData>({
|
||||
);
|
||||
}
|
||||
|
||||
const highlightSubstring = (text: string, substring: string) => {
|
||||
const indexOfHighlightedText = text
|
||||
.toLocaleLowerCase()
|
||||
.indexOf(substring.toLocaleLowerCase());
|
||||
if (indexOfHighlightedText === -1) {
|
||||
// This can happen if the consumer has implemented their own filtering logic
|
||||
return text;
|
||||
}
|
||||
const start = text.substring(0, indexOfHighlightedText);
|
||||
const highlight = text.substring(
|
||||
indexOfHighlightedText,
|
||||
indexOfHighlightedText + substring.length,
|
||||
);
|
||||
const end = text.substring(indexOfHighlightedText + substring.length);
|
||||
return (
|
||||
// Aria-label is used to fix testing-library wrongly evaluating the accessible name of the option when highlighting text
|
||||
// biome-ignore lint/a11y/useAriaPropsSupportedByRole: Doesn't matter if aria-label doesn't work
|
||||
<span aria-label={text}>
|
||||
{start}
|
||||
{highlight && <mark>{highlight}</mark>}
|
||||
{end}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export const ComboboxOption = React.memo(
|
||||
ComboboxOptionComponent,
|
||||
) as typeof ComboboxOptionComponent;
|
||||
@@ -1,27 +0,0 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { getHighlightRange } from "./highlightSubstring";
|
||||
|
||||
const highlighted = (text: string, substring: string) => {
|
||||
const range = getHighlightRange(text, substring);
|
||||
return range && text.substring(...range);
|
||||
};
|
||||
|
||||
describe("getHighlightRange", () => {
|
||||
test("matches case-insensitively", () => {
|
||||
expect(getHighlightRange("Norge", "OR")).toEqual([1, 3]);
|
||||
});
|
||||
|
||||
test("returns null when there is no match", () => {
|
||||
expect(getHighlightRange("Norge", "x")).toBeUndefined();
|
||||
});
|
||||
|
||||
test("handles characters that get longer when lowercased", () => {
|
||||
expect(highlighted("İstanbul", "an")).toBe("an");
|
||||
expect(highlighted("İzmir og Antalya", "an")).toBe("An");
|
||||
expect(highlighted("İzmir", "İz")).toBe("İz");
|
||||
});
|
||||
|
||||
test("includes the whole original character when only part of its lowercase form matches", () => {
|
||||
expect(highlighted("İstanbul", "i")).toBe("İ");
|
||||
});
|
||||
});
|
||||
@@ -1,50 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
function highlightSubstring(text: string, substring: string) {
|
||||
const range = getHighlightRange(text, substring);
|
||||
if (!range) {
|
||||
// This can happen if the consumer has implemented their own filtering logic
|
||||
return text;
|
||||
}
|
||||
const [rangeStart, rangeEnd] = range;
|
||||
return (
|
||||
// Aria-label is used to fix testing-library wrongly evaluating the accessible name of the option when highlighting text
|
||||
// biome-ignore lint/a11y/useAriaPropsSupportedByRole: Doesn't matter if it doesn't work in normal browsers
|
||||
<span aria-label={text}>
|
||||
{text.substring(0, rangeStart)}
|
||||
<mark>{text.substring(rangeStart, rangeEnd)}</mark>
|
||||
{text.substring(rangeEnd)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the first case-insensitive occurrence of `substring` in `text`.
|
||||
* Handles characters whose lowercase form has a different length than the original (e.g. "İ").
|
||||
* @returns The `[start, end]` range of the match in `text`, or `undefined` if there is no match.
|
||||
*/
|
||||
function getHighlightRange(text: string, substring: string) {
|
||||
const lowerSubstring = substring.toLocaleLowerCase();
|
||||
const matchIndex = text.toLocaleLowerCase().indexOf(lowerSubstring);
|
||||
if (matchIndex === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const matchEnd = matchIndex + lowerSubstring.length;
|
||||
let start = 0;
|
||||
let origPos = 0;
|
||||
let lowerPos = 0;
|
||||
|
||||
for (const char of text) {
|
||||
if (lowerPos <= matchIndex) {
|
||||
start = origPos;
|
||||
}
|
||||
origPos += char.length;
|
||||
lowerPos += char.toLocaleLowerCase().length;
|
||||
if (lowerPos >= matchEnd) {
|
||||
return [start, origPos] as const;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { highlightSubstring, getHighlightRange };
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useLayoutEffect, useRef, useState } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Listbox } from "../../../utils/components/Listbox/root/ListboxRoot";
|
||||
import { createStrictContext } from "../../../utils/helpers";
|
||||
import { useComboboxRootContext } from "../root/ComboboxRoot";
|
||||
@@ -29,7 +29,7 @@ export const ComboboxPopup = ({ children }: ComboboxPopupProps) => {
|
||||
const rootContext = useComboboxRootContext();
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useLayoutEffect(
|
||||
useEffect(
|
||||
function onOpen() {
|
||||
const canHover = window.matchMedia("(hover: hover)").matches;
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import {
|
||||
compareAsc,
|
||||
compareDesc,
|
||||
format,
|
||||
isSameMonth,
|
||||
setYear,
|
||||
} from "date-fns";
|
||||
import { compareAsc } from "date-fns/compareAsc";
|
||||
import { compareDesc } from "date-fns/compareDesc";
|
||||
import { format } from "date-fns/format";
|
||||
import { isSameMonth } from "date-fns/isSameMonth";
|
||||
import { setYear } from "date-fns/setYear";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { dateIsInCurrentMonth, isMatch } from "../../utils/date";
|
||||
import { cl } from "../../utils/helpers";
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import {
|
||||
format,
|
||||
isAfter,
|
||||
isBefore,
|
||||
setYear,
|
||||
startOfMonth,
|
||||
startOfYear,
|
||||
} from "date-fns";
|
||||
import { format } from "date-fns/format";
|
||||
import { isAfter } from "date-fns/isAfter";
|
||||
import { isBefore } from "date-fns/isBefore";
|
||||
import { setYear } from "date-fns/setYear";
|
||||
import { startOfMonth } from "date-fns/startOfMonth";
|
||||
import { startOfYear } from "date-fns/startOfYear";
|
||||
import React from "react";
|
||||
import { ArrowLeftIcon, ArrowRightIcon } from "@navikt/aksel-icons";
|
||||
import { Button } from "../../button";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { type Locale, isSameYear } from "date-fns";
|
||||
import type { Locale } from "date-fns";
|
||||
import { isSameYear } from "date-fns/isSameYear";
|
||||
import React from "react";
|
||||
import { clampDisplayYear } from "../../utils/date";
|
||||
import { createStrictContext } from "../../utils/helpers";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { setYear } from "date-fns";
|
||||
import { setYear } from "date-fns/setYear";
|
||||
import type { Matcher } from "../../utils/components/date/Date.typeutils";
|
||||
import { isMatch } from "../../utils/date";
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { isSameMonth, setMonth, setYear, startOfMonth } from "date-fns";
|
||||
import { isSameMonth } from "date-fns/isSameMonth";
|
||||
import { setMonth } from "date-fns/setMonth";
|
||||
import { setYear } from "date-fns/setYear";
|
||||
import { startOfMonth } from "date-fns/startOfMonth";
|
||||
import React, { useState } from "react";
|
||||
import { BodyShort } from "../../typography";
|
||||
import { isMatch } from "../../utils/date";
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import {
|
||||
type Locale,
|
||||
addDays,
|
||||
addMonths,
|
||||
addYears,
|
||||
differenceInDays,
|
||||
differenceInMonths,
|
||||
differenceInYears,
|
||||
endOfMonth,
|
||||
endOfYear,
|
||||
format,
|
||||
startOfDay,
|
||||
startOfMonth,
|
||||
startOfYear,
|
||||
subDays,
|
||||
} from "date-fns";
|
||||
import type { Locale } from "date-fns";
|
||||
import { addDays } from "date-fns/addDays";
|
||||
import { addMonths } from "date-fns/addMonths";
|
||||
import { addYears } from "date-fns/addYears";
|
||||
import { differenceInDays } from "date-fns/differenceInDays";
|
||||
import { differenceInMonths } from "date-fns/differenceInMonths";
|
||||
import { differenceInYears } from "date-fns/differenceInYears";
|
||||
import { endOfMonth } from "date-fns/endOfMonth";
|
||||
import { endOfYear } from "date-fns/endOfYear";
|
||||
import { format } from "date-fns/format";
|
||||
import { startOfDay } from "date-fns/startOfDay";
|
||||
import { startOfMonth } from "date-fns/startOfMonth";
|
||||
import { startOfYear } from "date-fns/startOfYear";
|
||||
import { subDays } from "date-fns/subDays";
|
||||
import React, { useCallback, useMemo } from "react";
|
||||
import { Detail } from "../../typography/Detail";
|
||||
import { useDateLocale, useI18n } from "../../utils/i18n/i18n.hooks";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isBefore } from "date-fns";
|
||||
import { isBefore } from "date-fns/isBefore";
|
||||
import React, { forwardRef } from "react";
|
||||
import { useTimelineContext } from "../root/TimelineRoot.context";
|
||||
import {
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
useHover,
|
||||
useInteractions,
|
||||
} from "@floating-ui/react";
|
||||
import { format } from "date-fns";
|
||||
import { format } from "date-fns/format";
|
||||
import React, { forwardRef, useMemo, useState } from "react";
|
||||
import { useMergeRefs } from "../../utils/hooks";
|
||||
import { useI18n } from "../../utils/i18n/i18n.hooks";
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { endOfDay, isSameDay, startOfDay } from "date-fns";
|
||||
import { endOfDay } from "date-fns/endOfDay";
|
||||
import { isSameDay } from "date-fns/isSameDay";
|
||||
import { startOfDay } from "date-fns/startOfDay";
|
||||
import React, { forwardRef, useMemo, useState } from "react";
|
||||
import { consoleWarning } from "../../utils/helpers/consoleWarning";
|
||||
import { TimelineAxisLabelsInternal } from "../axis-labels/TimelineAxisLabelsInternal";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { renderHook } from "@testing-library/react";
|
||||
import { addDays, isSameDay } from "date-fns";
|
||||
import { addDays } from "date-fns/addDays";
|
||||
import { isSameDay } from "date-fns/isSameDay";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import {
|
||||
useEarliestDate,
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { addDays, endOfDay, isAfter, startOfDay, subDays } from "date-fns";
|
||||
import { addDays } from "date-fns/addDays";
|
||||
import { endOfDay } from "date-fns/endOfDay";
|
||||
import { isAfter } from "date-fns/isAfter";
|
||||
import { startOfDay } from "date-fns/startOfDay";
|
||||
import { subDays } from "date-fns/subDays";
|
||||
import { useMemo } from "react";
|
||||
import { horizontalPositionAndWidth } from "../utils/calc";
|
||||
import { invisiblePeriods, withinADay } from "../utils/filter";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { format } from "date-fns";
|
||||
import { format } from "date-fns/format";
|
||||
import React, { forwardRef } from "react";
|
||||
import { BodyShort } from "../../typography/BodyShort";
|
||||
import { cl } from "../../utils/helpers";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { differenceInMilliseconds } from "date-fns";
|
||||
import { differenceInMilliseconds } from "date-fns/differenceInMilliseconds";
|
||||
|
||||
const position = (date: Date, start: Date, endInclusive: Date) => {
|
||||
const diff = differenceInMilliseconds(endInclusive, start);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { differenceInDays } from "date-fns";
|
||||
import { differenceInDays } from "date-fns/differenceInDays";
|
||||
import type { PeriodProps } from "../Timeline.types";
|
||||
import type { Spatial } from "./types.internal";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { format } from "date-fns";
|
||||
import { format } from "date-fns/format";
|
||||
import { cl } from "../../utils/helpers";
|
||||
import type { TFunction } from "../../utils/i18n/i18n.types";
|
||||
import type { PeriodProps } from "../Timeline.types";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { differenceInMilliseconds } from "date-fns";
|
||||
import { differenceInMilliseconds } from "date-fns/differenceInMilliseconds";
|
||||
import type { Period, PositionedPeriod } from "./types.internal";
|
||||
|
||||
const lastDate = (a: Date, b: Date): number => differenceInMilliseconds(a, b);
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import {
|
||||
addMonths,
|
||||
addYears,
|
||||
format,
|
||||
isSameDay,
|
||||
subMonths,
|
||||
subYears,
|
||||
} from "date-fns";
|
||||
import { addMonths } from "date-fns/addMonths";
|
||||
import { addYears } from "date-fns/addYears";
|
||||
import { format } from "date-fns/format";
|
||||
import { isSameDay } from "date-fns/isSameDay";
|
||||
import { subMonths } from "date-fns/subMonths";
|
||||
import { subYears } from "date-fns/subYears";
|
||||
import React, { forwardRef } from "react";
|
||||
import { Detail } from "../../typography/Detail";
|
||||
import { useI18n } from "../../utils/i18n/i18n.hooks";
|
||||
|
||||
@@ -39,12 +39,4 @@ function ListboxOptions({ children, ...rest }: ListboxOptionsProps) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Things that should be fixed before we allow focus directly
|
||||
* on the listbox (i.e. when there's no filter input/combobox elm.)
|
||||
* - Set aria-activedescendant
|
||||
* - Consider aria-multiselectable
|
||||
* - Probably aria-label(ledby)?
|
||||
*/
|
||||
|
||||
export { ListboxOptions };
|
||||
@@ -1,4 +1,6 @@
|
||||
import { enGB, nb, nn } from "date-fns/locale";
|
||||
import { enGB } from "date-fns/locale/en-GB";
|
||||
import { nb } from "date-fns/locale/nb";
|
||||
import { nn } from "date-fns/locale/nn";
|
||||
import { createStrictContext } from "../../helpers";
|
||||
import type { TFunction } from "../../i18n/i18n.types";
|
||||
import en_translations from "../../i18n/locales/en";
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import {
|
||||
addYears,
|
||||
endOfMonth,
|
||||
endOfYear,
|
||||
startOfDay,
|
||||
startOfMonth,
|
||||
startOfYear,
|
||||
} from "date-fns";
|
||||
import { addYears } from "date-fns/addYears";
|
||||
import { endOfMonth } from "date-fns/endOfMonth";
|
||||
import { endOfYear } from "date-fns/endOfYear";
|
||||
import { startOfDay } from "date-fns/startOfDay";
|
||||
import { startOfMonth } from "date-fns/startOfMonth";
|
||||
import { startOfYear } from "date-fns/startOfYear";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { calendarRange } from "./calendar-range";
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import {
|
||||
addYears,
|
||||
endOfMonth,
|
||||
endOfYear,
|
||||
startOfDay,
|
||||
startOfMonth,
|
||||
startOfYear,
|
||||
} from "date-fns";
|
||||
import { addYears } from "date-fns/addYears";
|
||||
import { endOfMonth } from "date-fns/endOfMonth";
|
||||
import { endOfYear } from "date-fns/endOfYear";
|
||||
import { startOfDay } from "date-fns/startOfDay";
|
||||
import { startOfMonth } from "date-fns/startOfMonth";
|
||||
import { startOfYear } from "date-fns/startOfYear";
|
||||
|
||||
/**
|
||||
* Generates the min and max-dates possible to show and navigate to in the calendar.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { setYear } from "date-fns";
|
||||
import { setYear } from "date-fns/setYear";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { dateIsInCurrentMonth, isValidDate } from "./check-dates";
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import {
|
||||
differenceInCalendarDays,
|
||||
isThisMonth,
|
||||
setYear,
|
||||
startOfDay,
|
||||
} from "date-fns";
|
||||
import { differenceInCalendarDays } from "date-fns/differenceInCalendarDays";
|
||||
import { isThisMonth } from "date-fns/isThisMonth";
|
||||
import { setYear } from "date-fns/setYear";
|
||||
import { startOfDay } from "date-fns/startOfDay";
|
||||
|
||||
export const dateIsInCurrentMonth = (
|
||||
date: Date,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { startOfMonth, startOfYear } from "date-fns";
|
||||
import { startOfMonth } from "date-fns/startOfMonth";
|
||||
import { startOfYear } from "date-fns/startOfYear";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { isDateOutsideRange } from "./check-dates";
|
||||
import { clampDisplayMonth, clampDisplayYear } from "./clamp-dates";
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { isAfter, isBefore, startOfMonth, startOfYear } from "date-fns";
|
||||
import { isAfter } from "date-fns/isAfter";
|
||||
import { isBefore } from "date-fns/isBefore";
|
||||
import { startOfMonth } from "date-fns/startOfMonth";
|
||||
import { startOfYear } from "date-fns/startOfYear";
|
||||
|
||||
/**
|
||||
* Makes sure the month is within the min and max daterange to avoid showing disabled months
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isSameDay } from "date-fns";
|
||||
import { isSameDay } from "date-fns/isSameDay";
|
||||
import { isDateRange } from "../components/date/Date.typeutils";
|
||||
|
||||
export const disableDate = (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { nb } from "date-fns/locale";
|
||||
import { nb } from "date-fns/locale/nb";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { getMonthOptions, getYearOptions } from "./dropdown-options";
|
||||
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import {
|
||||
type Locale,
|
||||
addYears,
|
||||
eachMonthOfInterval,
|
||||
endOfYear,
|
||||
format,
|
||||
getMonth,
|
||||
getYear,
|
||||
isBefore,
|
||||
isSameYear,
|
||||
startOfMonth,
|
||||
startOfYear,
|
||||
} from "date-fns";
|
||||
import type { Locale } from "date-fns";
|
||||
import { addYears } from "date-fns/addYears";
|
||||
import { eachMonthOfInterval } from "date-fns/eachMonthOfInterval";
|
||||
import { endOfYear } from "date-fns/endOfYear";
|
||||
import { format } from "date-fns/format";
|
||||
import { getMonth } from "date-fns/getMonth";
|
||||
import { getYear } from "date-fns/getYear";
|
||||
import { isBefore } from "date-fns/isBefore";
|
||||
import { isSameYear } from "date-fns/isSameYear";
|
||||
import { startOfMonth } from "date-fns/startOfMonth";
|
||||
import { startOfYear } from "date-fns/startOfYear";
|
||||
|
||||
/** Return the months to show in the dropdown. */
|
||||
export function getMonthOptions({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { type Locale, format } from "date-fns";
|
||||
import type { Locale } from "date-fns";
|
||||
import { format } from "date-fns/format";
|
||||
import {
|
||||
INPUT_DATE_STRING_FORMAT_DATE,
|
||||
INPUT_DATE_STRING_FORMAT_MONTH,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { nb } from "date-fns/locale";
|
||||
import { nb } from "date-fns/locale/nb";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { formatDateForInput } from "./format-date";
|
||||
import { parseDate } from "./parse-date";
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import {
|
||||
differenceInCalendarDays,
|
||||
isDate,
|
||||
isSameDay,
|
||||
isSameMonth,
|
||||
} from "date-fns";
|
||||
import { differenceInCalendarDays } from "date-fns/differenceInCalendarDays";
|
||||
import { isDate } from "date-fns/isDate";
|
||||
import { isSameDay } from "date-fns/isSameDay";
|
||||
import { isSameMonth } from "date-fns/isSameMonth";
|
||||
import {
|
||||
type DateRange,
|
||||
type Matcher,
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { type Locale, isBefore, parse, sub } from "date-fns";
|
||||
import type { Locale } from "date-fns";
|
||||
import { isBefore } from "date-fns/isBefore";
|
||||
import { parse } from "date-fns/parse";
|
||||
import { sub } from "date-fns/sub";
|
||||
import { isValidDate } from "./check-dates";
|
||||
|
||||
export const INPUT_DATE_STRING_FORMAT_DATE = "dd.MM.yyyy";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getMonth } from "date-fns";
|
||||
import { nb } from "date-fns/locale";
|
||||
import { getMonth } from "date-fns/getMonth";
|
||||
import { nb } from "date-fns/locale/nb";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { isValidDate } from "./check-dates";
|
||||
import { parseDate } from "./parse-date";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { isBefore, isSameDay } from "date-fns";
|
||||
import { isBefore } from "date-fns/isBefore";
|
||||
import { isSameDay } from "date-fns/isSameDay";
|
||||
import type { DateRange } from "../components/date/Date.typeutils";
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,8 +5,6 @@ const internalTranslations = {
|
||||
selected: "{count} rows selected",
|
||||
},
|
||||
Combobox2: {
|
||||
/** The "value" that is shown when multiple options are selected. */
|
||||
selectedCount: "{count} valgt",
|
||||
/** Label for filter input. Only visible to screen readers. */
|
||||
filter: "Filtrer",
|
||||
noHits: "Ingen treff",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { enGB } from "date-fns/locale";
|
||||
import { enGB } from "date-fns/locale/en-GB";
|
||||
import type { Translations } from "../i18n.types";
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { type Locale, nb } from "date-fns/locale";
|
||||
import type { Locale } from "date-fns";
|
||||
import { nb } from "date-fns/locale/nb";
|
||||
|
||||
interface TranslationMap {
|
||||
[component: string]: Record<string, string | Record<string, string> | Locale>;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { nn } from "date-fns/locale";
|
||||
import { nn } from "date-fns/locale/nn";
|
||||
import type { Translations } from "../i18n.types";
|
||||
|
||||
export default {
|
||||
|
||||
@@ -167,6 +167,31 @@ module.exports = defineConfig([
|
||||
"import/no-named-export": "error",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["@navikt/core/react/src/**/*.{ts,tsx}"],
|
||||
ignores: ["**/*.stories.tsx"],
|
||||
rules: {
|
||||
"@typescript-eslint/no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
paths: [
|
||||
{
|
||||
name: "date-fns",
|
||||
allowTypeImports: true,
|
||||
message:
|
||||
'Import functions from their own path, e.g. `import { format } from "date-fns/format"`. The root barrel makes unbundled Node imports load all of date-fns.',
|
||||
},
|
||||
{
|
||||
name: "date-fns/locale",
|
||||
allowTypeImports: true,
|
||||
message:
|
||||
'Import locales from their own path, e.g. `import { nb } from "date-fns/locale/nb"`. The barrel makes unbundled Node imports load all locales.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["@navikt/**"],
|
||||
ignores: ["**/*.stories.tsx"],
|
||||
|
||||
Reference in new issue
Block a user