diff --git a/.changeset/red-crabs-sell.md b/.changeset/red-crabs-sell.md new file mode 100644 index 00000000000..457cdb8303f --- /dev/null +++ b/.changeset/red-crabs-sell.md @@ -0,0 +1,6 @@ +--- +"@navikt/ds-react": minor +"@navikt/ds-css": minor +--- + +:sparkles: Combobox: Mulighet for å begrense hvor mange valg bruker kan ta diff --git a/@navikt/core/css/form/combobox.css b/@navikt/core/css/form/combobox.css index 4b24759ffba..688dc788561 100644 --- a/@navikt/core/css/form/combobox.css +++ b/@navikt/core/css/form/combobox.css @@ -74,7 +74,6 @@ } .navds-combobox__input { - z-index: 1; flex: 1; border: none; padding: 0; @@ -208,7 +207,7 @@ height: 1.25rem; } -/* dropdown list */ +/* dropdown & non selectable dropdown items */ .navds-combobox__list { max-height: 290px; @@ -216,20 +215,16 @@ position: absolute; left: 0; right: 0; - z-index: 9999; + z-index: var(--a-z-index-popover); top: calc(100% + var(--a-spacing-2)); - list-style: none; - margin: 0; border: 1px solid var(--ac-combobox-list-border-color, var(--a-border-divider)); display: flex; flex-direction: column; - align-items: flex-start; - padding: 0; box-shadow: var(--a-shadow-small); border-radius: var(--a-border-radius-medium); - gap: var(--a-spacing-1) 0; background-color: var(--ac-combobox-list-bg, var(--a-surface-default)); color: var(--ac-combobox-list-text, var(--a-text-default)); + gap: var(--a-spacing-1) 0; } .navds-combobox__list--closed { @@ -241,34 +236,70 @@ width: 1.5rem; } +.navds-combobox__list_non-selectables { + position: sticky; + top: 0; + left: 0; + right: 0; + z-index: 1; +} + .navds-combobox__list-item, -.navds-combobox__list-item__no-options, -.navds-combobox__list-item__new-option { +.navds-combobox__list-item--loading, +.navds-combobox__list-item--no-options, +.navds-combobox__list-item--new-option, +.navds-combobox__list-item--max-selected { display: flex; flex-direction: row; justify-content: space-between; - align-items: center; padding: var(--a-spacing-3); width: 100%; background-color: var(--ac-combobox-list-item-bg, var(--a-surface-default)); + scroll-margin-top: 50px; } .navds-form-field--small .navds-combobox__list-item, -.navds-form-field--small .navds-combobox__list-item__no-options, -.navds-form-field--small .navds-combobox__list-item__new-option { +.navds-form-field--small .navds-combobox__list-item--loading, +.navds-form-field--small .navds-combobox__list-item--no-options, +.navds-form-field--small .navds-combobox__list-item--new-option, +.navds-form-field--small .navds-combobox__list-item--max-selected { padding: calc(var(--a-spacing-3) / 2) var(--a-spacing-2); } .navds-combobox__list-item--loading { - display: flex; justify-content: center; - padding: var(--a-spacing-3); background-color: var(--ac-combobox-list-item-loading-bg, var(--a-surface-default)); +} + +.navds-combobox__list-item--max-selected { + background: var(--ac-combobox-list-item-max-selected-bg, var(--a-surface-info-subtle)); + border-start-start-radius: calc(var(--a-border-radius-medium) - 1px); + border-start-end-radius: calc(var(--a-border-radius-medium) - 1px); + border: 1px solid var(--ac-combobox-list-item-max-selected-border, var(--a-border-info)); + margin-bottom: calc(var(--a-spacing-1) * -1); +} + +.navds-combobox__list_non-selectables:hover { + cursor: default; +} + +/* ul-list and selectable li-items */ + +.navds-combobox__list-options { + list-style: none; + margin: 0; + padding: 0; width: 100%; + display: inherit; + flex-direction: inherit; + gap: inherit; + background-color: inherit; + align-items: flex-start; } .navds-combobox__list-item--focus, -.navds-combobox__list--with-hover .navds-combobox__list-item:hover { +.navds-combobox__list--with-hover + .navds-combobox__list-item:not([data-no-focus="true"], .navds-combobox__list-item--new-option):hover { background-color: var(--ac-combobox-list-item-hover-bg, var(--a-surface-hover)); cursor: pointer; border-left: 4px solid var(--ac-combobox-list-item-hover-border-left, var(--a-border-strong)); @@ -280,6 +311,11 @@ padding-left: calc(var(--a-spacing-2) - 4px); } +.navds-combobox__list-item[data-no-focus="true"] { + cursor: not-allowed; + opacity: 0.4; +} + .navds-combobox__list-item--selected { background-color: var(--ac-combobox-list-item-selected-bg, var(--a-surface-selected)); } @@ -295,7 +331,7 @@ padding-left: calc(var(--a-spacing-3) - 4px); } -.navds-combobox__list-item__new-option { +.navds-combobox__list-item--new-option { border-bottom: 1px solid var(--a-border-divider); background: var(--a-surface-neutral-subtle); cursor: pointer; @@ -303,12 +339,12 @@ gap: 0.25rem; } -.navds-combobox__list--with-hover .navds-combobox__list-item__new-option:hover { +.navds-combobox__list--with-hover .navds-combobox__list-item--new-option:hover { border-bottom: 1px solid var(--a-border-divider); background: var(--a-surface-neutral-subtle-hover); } -.navds-combobox__list-item__new-option--focus { +.navds-combobox__list-item--new-option--focus { box-shadow: var(--a-shadow-focus) inset, var(--a-border-action) 0 0 0 5px inset; diff --git a/@navikt/core/css/tokens.json b/@navikt/core/css/tokens.json index c1efcd28436..d80adfa43ec 100644 --- a/@navikt/core/css/tokens.json +++ b/@navikt/core/css/tokens.json @@ -392,6 +392,8 @@ "--ac-combobox-list-item-loading-bg": "--a-surface-default", "--ac-combobox-list-item-hover-border-left": "--a-border-strong", "--ac-combobox-list-item-selected-hover-border-left": "--a-border-focus", + "--ac-combobox-list-item-max-selected-bg": "--a-surface-info-subtle", + "--ac-combobox-list-item-max-selected-border": "--a-border-info", "--ac-combobox-error-border": "--a-border-danger" }, "select": { diff --git a/@navikt/core/react/src/form/combobox/Combobox.tsx b/@navikt/core/react/src/form/combobox/Combobox.tsx index 1b63de3fe8d..3562f4a4f16 100644 --- a/@navikt/core/react/src/form/combobox/Combobox.tsx +++ b/@navikt/core/react/src/form/combobox/Combobox.tsx @@ -89,7 +89,7 @@ export const Combobox = forwardRef< "navds-combobox__wrapper-inner navds-text-field__input", { "navds-combobox__wrapper-inner--virtually-unfocused": - activeDecendantId !== null, + activeDecendantId !== undefined, }, )} onClick={focusInput} diff --git a/@navikt/core/react/src/form/combobox/ComboboxProvider.tsx b/@navikt/core/react/src/form/combobox/ComboboxProvider.tsx index a31c968f21c..486c570b297 100644 --- a/@navikt/core/react/src/form/combobox/ComboboxProvider.tsx +++ b/@navikt/core/react/src/form/combobox/ComboboxProvider.tsx @@ -43,6 +43,7 @@ const ComboboxProvider = forwardRef( isMultiSelect, onToggleSelected, selectedOptions, + maxSelected, options, value, onChange, @@ -71,6 +72,7 @@ const ComboboxProvider = forwardRef( allowNewValues, isMultiSelect, selectedOptions, + maxSelected, onToggleSelected, options, }} diff --git a/@navikt/core/react/src/form/combobox/ComboboxWrapper.tsx b/@navikt/core/react/src/form/combobox/ComboboxWrapper.tsx index 25bf238236e..f7853cf142f 100644 --- a/@navikt/core/react/src/form/combobox/ComboboxWrapper.tsx +++ b/@navikt/core/react/src/form/combobox/ComboboxWrapper.tsx @@ -57,7 +57,6 @@ const ComboboxWrapper = ({ )} onFocus={onFocusInsideWrapper} onBlur={onBlurWrapper} - tabIndex={-1} > {children} diff --git a/@navikt/core/react/src/form/combobox/FilteredOptions/FilteredOptions.tsx b/@navikt/core/react/src/form/combobox/FilteredOptions/FilteredOptions.tsx index eeb6c10039a..f5539b1fb6d 100644 --- a/@navikt/core/react/src/form/combobox/FilteredOptions/FilteredOptions.tsx +++ b/@navikt/core/react/src/form/combobox/FilteredOptions/FilteredOptions.tsx @@ -27,111 +27,150 @@ const FilteredOptions = () => { activeDecendantId, virtualFocus, } = useFilteredOptionsContext(); - const { isMultiSelect, selectedOptions, toggleOption } = + const { isMultiSelect, selectedOptions, toggleOption, maxSelected } = useSelectedOptionsContext(); + const isDisabled = (option) => + maxSelected?.isLimitReached && !selectedOptions.includes(option); + + const shouldRenderNonSelectables = + maxSelected?.isLimitReached || // Render maxSelected message + isLoading || // Render loading message + (!isLoading && filteredOptions.length === 0); // Render no hits message + + const shouldRenderFilteredOptionsList = + (allowNewValues && isValueNew && !maxSelected?.isLimitReached) || // Render add new option + filteredOptions.length > 0; // Render filtered options + return ( - )} - {filteredOptions.map((option) => ( -
  • { - if ( - activeDecendantId !== filteredOptionsUtil.getOptionId(id, option) - ) { - virtualFocus.moveFocusToElement( - filteredOptionsUtil.getOptionId(id, option), - ); - setIsMouseLastUsedInputDevice(true); - } - }} - onPointerUp={(event) => { - toggleOption(option, event); - if (!isMultiSelect && !selectedOptions.includes(option)) - toggleIsListOpen(false); - }} - role="option" - aria-selected={selectedOptions.includes(option)} - > - {option} - {selectedOptions.includes(option) && } -
  • - ))} - + ); }; diff --git a/@navikt/core/react/src/form/combobox/FilteredOptions/filtered-options-util.ts b/@navikt/core/react/src/form/combobox/FilteredOptions/filtered-options-util.ts index f3a45e80021..af99b274977 100644 --- a/@navikt/core/react/src/form/combobox/FilteredOptions/filtered-options-util.ts +++ b/@navikt/core/react/src/form/combobox/FilteredOptions/filtered-options-util.ts @@ -7,8 +7,11 @@ const isPartOfText = (value, text) => const isValueInList = (value, list) => list?.find((listItem) => normalizeText(value) === normalizeText(listItem)); -const getMatchingValuesFromList = (value, list) => - list?.filter((listItem) => isPartOfText(value, listItem)); +const getMatchingValuesFromList = (value, list, alwaysIncluded) => + list?.filter( + (listItem) => + isPartOfText(value, listItem) || alwaysIncluded.includes(listItem), + ); const getFilteredOptionsId = (comboboxId: string) => `${comboboxId}-filtered-options`; @@ -25,6 +28,9 @@ const getIsLoadingId = (comboboxId: string) => `${comboboxId}-is-loading`; const getNoHitsId = (comboboxId: string) => `${comboboxId}-no-hits`; +const getMaxSelectedOptionsId = (comboboxId: string) => + `${comboboxId}-max-selected-options`; + export default { normalizeText, isPartOfText, @@ -35,4 +41,5 @@ export default { getOptionId, getIsLoadingId, getNoHitsId, + getMaxSelectedOptionsId, }; diff --git a/@navikt/core/react/src/form/combobox/FilteredOptions/filteredOptionsContext.tsx b/@navikt/core/react/src/form/combobox/FilteredOptions/filteredOptionsContext.tsx index fe3536f38ad..e67d52020e4 100644 --- a/@navikt/core/react/src/form/combobox/FilteredOptions/filteredOptionsContext.tsx +++ b/@navikt/core/react/src/form/combobox/FilteredOptions/filteredOptionsContext.tsx @@ -9,6 +9,7 @@ import React, { } from "react"; import { useClientLayoutEffect, usePrevious } from "../../../util/hooks"; import { useInputContext } from "../Input/inputContext"; +import { useSelectedOptionsContext } from "../SelectedOptions/selectedOptionsContext"; import { useCustomOptionsContext } from "../customOptionsContext"; import { ComboboxProps } from "../types"; import filteredOptionsUtils from "./filtered-options-util"; @@ -70,6 +71,7 @@ export const FilteredOptionsProvider = ({ setSearchTerm, shouldAutocomplete, } = useInputContext(); + const { selectedOptions, maxSelected } = useSelectedOptionsContext(); const [isInternalListOpen, setInternalListOpen] = useState(false); const { customOptions } = useCustomOptionsContext(); @@ -79,8 +81,18 @@ export const FilteredOptionsProvider = ({ return externalFilteredOptions; } const opts = [...customOptions, ...options]; - return filteredOptionsUtils.getMatchingValuesFromList(searchTerm, opts); - }, [customOptions, externalFilteredOptions, options, searchTerm]); + return filteredOptionsUtils.getMatchingValuesFromList( + searchTerm, + opts, + selectedOptions, + ); + }, [ + customOptions, + externalFilteredOptions, + options, + searchTerm, + selectedOptions, + ]); const previousSearchTerm = usePrevious(searchTerm); @@ -154,10 +166,17 @@ export const FilteredOptionsProvider = ({ activeOption = filteredOptionsUtils.getIsLoadingId(id); } } - return cl(activeOption, partialAriaDescribedBy) || undefined; + const maybeMaxSelectedOptionsId = + maxSelected?.isLimitReached && + filteredOptionsUtils.getMaxSelectedOptionsId(id); + return ( + cl(activeOption, maybeMaxSelectedOptionsId, partialAriaDescribedBy) || + undefined + ); }, [ isListOpen, isLoading, + maxSelected?.isLimitReached, value, partialAriaDescribedBy, shouldAutocomplete, diff --git a/@navikt/core/react/src/form/combobox/FilteredOptions/useVirtualFocus.ts b/@navikt/core/react/src/form/combobox/FilteredOptions/useVirtualFocus.ts index 13087794880..4fd5eafbfdb 100644 --- a/@navikt/core/react/src/form/combobox/FilteredOptions/useVirtualFocus.ts +++ b/@navikt/core/react/src/form/combobox/FilteredOptions/useVirtualFocus.ts @@ -1,11 +1,10 @@ -import { Dispatch, SetStateAction, useState } from "react"; +import { useState } from "react"; export type VirtualFocusType = { activeElement: HTMLElement | undefined; getElementById: (id: string) => HTMLElement | undefined; - isFocusOnTheTop: boolean; - isFocusOnTheBottom: boolean; - setIndex: Dispatch>; + isFocusOnTheTop: () => boolean; + isFocusOnTheBottom: () => boolean; moveFocusUp: () => void; moveFocusDown: () => void; moveFocusToElement: (id: string) => void; @@ -16,57 +15,77 @@ export type VirtualFocusType = { const useVirtualFocus = ( containerRef: HTMLElement | null, ): VirtualFocusType => { - const [index, setIndex] = useState(-1); - - const listOfAllChildren: HTMLElement[] = containerRef?.children - ? Array.prototype.slice.call(containerRef?.children) - : []; - const elementsAbleToReceiveFocus = listOfAllChildren.filter( - (child) => child.getAttribute("data-no-focus") !== "true", + const [activeElement, setActiveElement] = useState( + undefined, ); - const activeElement = elementsAbleToReceiveFocus[index]; + const getListOfAllChildren = (): HTMLElement[] => + Array.from(containerRef?.children ?? []) as HTMLElement[]; + const getElementsAbleToReceiveFocus = () => + getListOfAllChildren().filter( + (child) => child.getAttribute("data-no-focus") !== "true", + ); + const getElementById = (id: string) => - listOfAllChildren.find((element) => element.id === id); - const isFocusOnTheTop = index === 0; - const isFocusOnTheBottom = index === elementsAbleToReceiveFocus.length - 1; + getListOfAllChildren().find((element) => element.id === id); + const isFocusOnTheTop = () => + activeElement + ? getElementsAbleToReceiveFocus().indexOf(activeElement) === 0 + : false; + const isFocusOnTheBottom = () => { + const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus(); + return activeElement + ? elementsAbleToReceiveFocus.indexOf(activeElement) === + elementsAbleToReceiveFocus.length - 1 + : false; + }; - const scrollToOption = (newIndex: number) => { - const indexOfElementToScrollTo = Math.min( - Math.max(newIndex, 0), - containerRef?.children.length || 0, - ); - if (containerRef?.children[indexOfElementToScrollTo]) { - const child = containerRef.children[indexOfElementToScrollTo]; - const { top, bottom } = child.getBoundingClientRect(); - const parentRect = containerRef.getBoundingClientRect(); - if (top < parentRect.top || bottom > parentRect.bottom) { - child.scrollIntoView({ block: "nearest" }); - } + const _moveFocusAndScrollTo = (_element?: HTMLElement) => { + setActiveElement(_element); + _element?.scrollIntoView?.({ block: "nearest" }); + }; + + const moveFocusUp = () => { + if (!activeElement) { + return; + } + const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus(); + const _currentIndex = elementsAbleToReceiveFocus.indexOf(activeElement); + const elementAbove = elementsAbleToReceiveFocus[_currentIndex - 1]; + if (_currentIndex === 0) { + setActiveElement(undefined); + } else { + _moveFocusAndScrollTo(elementAbove); } }; - const _moveFocusAndScrollTo = (_index: number) => { - setIndex(_index); - scrollToOption(_index); + const moveFocusDown = () => { + const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus(); + if (!activeElement) { + _moveFocusAndScrollTo(elementsAbleToReceiveFocus[0]); + return; + } + const _currentIndex = elementsAbleToReceiveFocus.indexOf(activeElement); + if (_currentIndex === elementsAbleToReceiveFocus.length - 1) { + return; + } else { + _moveFocusAndScrollTo(elementsAbleToReceiveFocus[_currentIndex + 1]); + } }; - const moveFocusUp = () => _moveFocusAndScrollTo(Math.max(index - 1, -1)); - const moveFocusDown = () => - _moveFocusAndScrollTo( - Math.min(index + 1, elementsAbleToReceiveFocus.length - 1), + + const moveFocusToTop = () => _moveFocusAndScrollTo(undefined); + const moveFocusToBottom = () => { + const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus(); + return _moveFocusAndScrollTo( + elementsAbleToReceiveFocus[elementsAbleToReceiveFocus.length - 1], ); - const moveFocusToTop = () => _moveFocusAndScrollTo(-1); - const moveFocusToBottom = () => - _moveFocusAndScrollTo(elementsAbleToReceiveFocus.length - 1); + }; const moveFocusToElement = (id: string) => { - const thisElement = elementsAbleToReceiveFocus.find( - (_element) => _element.getAttribute("id") === id, + const _element = getElementsAbleToReceiveFocus().find( + (_focusableElement) => _focusableElement.getAttribute("id") === id, ); - const indexOfElement = thisElement - ? elementsAbleToReceiveFocus.indexOf(thisElement) - : -1; - if (indexOfElement >= 0) { - setIndex(indexOfElement); + if (_element) { + setActiveElement(_element); } }; @@ -75,7 +94,6 @@ const useVirtualFocus = ( getElementById, isFocusOnTheTop, isFocusOnTheBottom, - setIndex, moveFocusUp, moveFocusDown, moveFocusToElement, diff --git a/@navikt/core/react/src/form/combobox/Input/Input.tsx b/@navikt/core/react/src/form/combobox/Input/Input.tsx index a8927711b48..a19c0b24b58 100644 --- a/@navikt/core/react/src/form/combobox/Input/Input.tsx +++ b/@navikt/core/react/src/form/combobox/Input/Input.tsx @@ -101,9 +101,11 @@ const Input = forwardRef( onEnter(e); break; case "Home": + toggleIsListOpen(false); virtualFocus.moveFocusToTop(); break; case "End": + toggleIsListOpen(true); virtualFocus.moveFocusToBottom(); break; default: @@ -135,7 +137,7 @@ const Input = forwardRef( // Otherwise ignore keystrokes, so it doesn't interfere with text editing if (isListOpen && activeDecendantId) { e.preventDefault(); - if (virtualFocus.isFocusOnTheTop) { + if (virtualFocus.isFocusOnTheTop()) { toggleIsListOpen(false); } virtualFocus.moveFocusUp(); diff --git a/@navikt/core/react/src/form/combobox/SelectedOptions/selectedOptionsContext.tsx b/@navikt/core/react/src/form/combobox/SelectedOptions/selectedOptionsContext.tsx index 6eb4fbff09f..c6c1ab15e14 100644 --- a/@navikt/core/react/src/form/combobox/SelectedOptions/selectedOptionsContext.tsx +++ b/@navikt/core/react/src/form/combobox/SelectedOptions/selectedOptionsContext.tsx @@ -8,7 +8,7 @@ import React, { import { usePrevious } from "../../../util/hooks"; import { useInputContext } from "../Input/inputContext"; import { useCustomOptionsContext } from "../customOptionsContext"; -import { ComboboxProps } from "../types"; +import { ComboboxProps, MaxSelected } from "../types"; type SelectedOptionsContextType = { addSelectedOption: (option: string) => void; @@ -16,6 +16,7 @@ type SelectedOptionsContextType = { removeSelectedOption: (option: string) => void; prevSelectedOptions?: string[]; selectedOptions: string[]; + maxSelected?: MaxSelected & { isLimitReached: boolean }; setSelectedOptions: (any) => void; toggleOption: ( option: string, @@ -39,6 +40,7 @@ export const SelectedOptionsProvider = ({ | "options" | "selectedOptions" | "onToggleSelected" + | "maxSelected" >; }) => { const { clearInput, focusInput } = useInputContext(); @@ -54,6 +56,7 @@ export const SelectedOptionsProvider = ({ selectedOptions: externalSelectedOptions, onToggleSelected, options, + maxSelected, } = value; const [internalSelectedOptions, setSelectedOptions] = useState([]); const selectedOptions = useMemo( @@ -129,6 +132,9 @@ export const SelectedOptionsProvider = ({ const prevSelectedOptions = usePrevious(selectedOptions); + const isLimitReached = + !!maxSelected?.limit && selectedOptions.length >= maxSelected.limit; + const selectedOptionsState = { addSelectedOption, isMultiSelect, @@ -137,6 +143,10 @@ export const SelectedOptionsProvider = ({ selectedOptions, setSelectedOptions, toggleOption, + maxSelected: maxSelected && { + ...maxSelected, + isLimitReached, + }, }; return ( diff --git a/@navikt/core/react/src/form/combobox/combobox.stories.tsx b/@navikt/core/react/src/form/combobox/combobox.stories.tsx index 1492aa97c87..686b37a356b 100644 --- a/@navikt/core/react/src/form/combobox/combobox.stories.tsx +++ b/@navikt/core/react/src/form/combobox/combobox.stories.tsx @@ -1,6 +1,6 @@ import { Meta, StoryFn, StoryObj } from "@storybook/react"; import { expect, fn, userEvent, within } from "@storybook/test"; -import React, { useId, useMemo, useState } from "react"; +import React, { useId, useMemo, useRef, useState } from "react"; import { Chips, ComboboxProps, TextField, UNSAFE_Combobox } from "../../index"; export default { @@ -37,11 +37,16 @@ Default.args = { label: "Hva er dine favorittfrukter?", shouldAutocomplete: true, isLoading: false, + isMultiSelect: false, + allowNewValues: false, }; Default.argTypes = { isListOpen: { control: { type: "boolean" }, }, + maxSelected: { + control: { type: "number" }, + }, size: { options: ["medium", "small"], defaultValue: "medium", @@ -284,6 +289,36 @@ export const ComboboxSizes = () => ( ); +export const MaxSelectedOptions: StoryFunction = () => { + const id = useId(); + const [value, setValue] = useState(""); + const [selectedOptions, setSelectedOptions] = useState([ + options[0], + options[1], + ]); + const comboboxRef = useRef(null); + return ( + + isSelected + ? setSelectedOptions([...selectedOptions, option]) + : setSelectedOptions(selectedOptions.filter((o) => o !== option)) + } + isMultiSelect + allowNewValues + isListOpen={comboboxRef.current ? undefined : true} + value={value} + onChange={(event) => setValue(event?.target.value)} + ref={comboboxRef} + /> + ); +}; + export const WithError: StoryFunction = (props) => { const [hasSelectedValue, setHasSelectedValue] = useState(false); const [isLoading, setIsLoading] = useState(false); diff --git a/@navikt/core/react/src/form/combobox/combobox.test.tsx b/@navikt/core/react/src/form/combobox/combobox.test.tsx index d892340b7d9..e5b4edf7836 100644 --- a/@navikt/core/react/src/form/combobox/combobox.test.tsx +++ b/@navikt/core/react/src/form/combobox/combobox.test.tsx @@ -74,9 +74,7 @@ describe("Render combobox", () => { it("Should show loading icon when loading (used for async search)", async () => { render(); - expect( - await screen.findByRole("option", { name: "venter..." }), - ).toBeInTheDocument(); + expect(await screen.findByText("Søker...")).toBeInTheDocument(); }); }); diff --git a/@navikt/core/react/src/form/combobox/types.ts b/@navikt/core/react/src/form/combobox/types.ts index dae5e454b81..bc5d51e13de 100644 --- a/@navikt/core/react/src/form/combobox/types.ts +++ b/@navikt/core/react/src/form/combobox/types.ts @@ -1,6 +1,17 @@ import React, { ChangeEvent, InputHTMLAttributes } from "react"; import { FormFieldProps } from "../useFormField"; +export type MaxSelected = { + /** + * The limit for maximum selected options + */ + limit: number; + /** + * Override the message to display when the limit for maximum selected options has been reached + */ + message?: string; +}; + export interface ComboboxProps extends FormFieldProps, Omit, "size" | "onChange" | "value"> { @@ -97,6 +108,10 @@ export interface ComboboxProps * e.g. for a filter, where options can be toggled elsewhere/programmatically. */ selectedOptions?: string[]; + /** + * Options for the maximum number of selected options. + */ + maxSelected?: MaxSelected; /** * Set to "true" to enable inline autocomplete. * diff --git a/aksel.nav.no/website/pages/eksempler/combobox/multi-select-controlled.tsx b/aksel.nav.no/website/pages/eksempler/combobox/multi-select-controlled.tsx index 79bd1d5e968..ba07845a92d 100644 --- a/aksel.nav.no/website/pages/eksempler/combobox/multi-select-controlled.tsx +++ b/aksel.nav.no/website/pages/eksempler/combobox/multi-select-controlled.tsx @@ -70,6 +70,11 @@ const initialSelectedOptions = ["Norge"]; // EXAMPLES DO NOT INCLUDE CONTENT BELOW THIS LINE export default withDsExample(Example, { variant: "static" }); +/* Storybook story */ +export const Demo = { + render: Example, +}; + export const args = { index: 1, desc: "Du kan overstyre blant annet value, selectedOptions, filteredOptions.", diff --git a/aksel.nav.no/website/pages/eksempler/combobox/multi-select-with-new-options.tsx b/aksel.nav.no/website/pages/eksempler/combobox/multi-select-with-new-options.tsx index 7b10bb270d2..57c0239710d 100644 --- a/aksel.nav.no/website/pages/eksempler/combobox/multi-select-with-new-options.tsx +++ b/aksel.nav.no/website/pages/eksempler/combobox/multi-select-with-new-options.tsx @@ -32,6 +32,11 @@ const initialOptions = [ // EXAMPLES DO NOT INCLUDE CONTENT BELOW THIS LINE export default withDsExample(Example, { variant: "static" }); +/* Storybook story */ +export const Demo = { + render: Example, +}; + export const args = { index: 1, desc: "Ved Multi Select kan brukeren velge flere valg fra nedtrekkslisten.", diff --git a/aksel.nav.no/website/pages/eksempler/combobox/multi-select.tsx b/aksel.nav.no/website/pages/eksempler/combobox/multi-select.tsx index 13ccb141e28..c70cb1a5e5e 100644 --- a/aksel.nav.no/website/pages/eksempler/combobox/multi-select.tsx +++ b/aksel.nav.no/website/pages/eksempler/combobox/multi-select.tsx @@ -31,6 +31,11 @@ const initialOptions = [ // EXAMPLES DO NOT INCLUDE CONTENT BELOW THIS LINE export default withDsExample(Example, { variant: "static" }); +/* Storybook story */ +export const Demo = { + render: Example, +}; + export const args = { index: 1, desc: "Ved Multi Select kan brukeren velge flere valg fra listen.", diff --git a/aksel.nav.no/website/pages/eksempler/combobox/single-select-with-autocomplete.tsx b/aksel.nav.no/website/pages/eksempler/combobox/single-select-with-autocomplete.tsx index 5f42dbb2758..82742e2a4d8 100644 --- a/aksel.nav.no/website/pages/eksempler/combobox/single-select-with-autocomplete.tsx +++ b/aksel.nav.no/website/pages/eksempler/combobox/single-select-with-autocomplete.tsx @@ -30,6 +30,11 @@ const initialOptions = [ // EXAMPLES DO NOT INCLUDE CONTENT BELOW THIS LINE export default withDsExample(Example, { variant: "static" }); +/* Storybook story */ +export const Demo = { + render: Example, +}; + export const args = { index: 0, desc: "Ved Single Select velger brukeren ett valg fra listen. Med autocomplete foreslås et valg fra listen som matcher det brukeren skriver.", diff --git a/aksel.nav.no/website/pages/eksempler/combobox/single-select.tsx b/aksel.nav.no/website/pages/eksempler/combobox/single-select.tsx index 7c2ab34b074..d61d425a528 100644 --- a/aksel.nav.no/website/pages/eksempler/combobox/single-select.tsx +++ b/aksel.nav.no/website/pages/eksempler/combobox/single-select.tsx @@ -30,6 +30,11 @@ const initialOptions = [ // EXAMPLES DO NOT INCLUDE CONTENT BELOW THIS LINE export default withDsExample(Example, { variant: "static" }); +/* Storybook story */ +export const Demo = { + render: Example, +}; + export const args = { index: 0, desc: "Ved Single Select velger brukeren kun ett valg fra nedtrekkslisten.", diff --git a/aksel.nav.no/website/pages/eksempler/combobox/with-error.tsx b/aksel.nav.no/website/pages/eksempler/combobox/with-error.tsx index 0ef4f53f1d9..bea6ad7f626 100644 --- a/aksel.nav.no/website/pages/eksempler/combobox/with-error.tsx +++ b/aksel.nav.no/website/pages/eksempler/combobox/with-error.tsx @@ -31,6 +31,11 @@ const initialOptions = [ // EXAMPLES DO NOT INCLUDE CONTENT BELOW THIS LINE export default withDsExample(Example, { variant: "static" }); +/* Storybook story */ +export const Demo = { + render: Example, +}; + export const args = { index: 0, desc: "Ved Single Select velger brukeren kun ett valg fra nedtrekkslisten.", diff --git a/aksel.nav.no/website/pages/eksempler/combobox/with-max-selected-limit.tsx b/aksel.nav.no/website/pages/eksempler/combobox/with-max-selected-limit.tsx new file mode 100644 index 00000000000..b451e10ae11 --- /dev/null +++ b/aksel.nav.no/website/pages/eksempler/combobox/with-max-selected-limit.tsx @@ -0,0 +1,54 @@ +import { useState } from "react"; +import { UNSAFE_Combobox } from "@navikt/ds-react"; +import { withDsExample } from "@/web/examples/withDsExample"; + +const Example = () => { + const [selectedOptions, setSelectedOptions] = useState([ + options[0], + options[1], + ]); + return ( +
    + + isSelected + ? setSelectedOptions([...selectedOptions, option]) + : setSelectedOptions(selectedOptions.filter((o) => o !== option)) + } + /> +
    + ); +}; + +const options = [ + "car", + "bus", + "train", + "skateboard", + "bicycle", + "motorcycle", + "boat", + "airplane", + "helicopter", + "truck", + "van", + "scooter", +]; + +// EXAMPLES DO NOT INCLUDE CONTENT BELOW THIS LINE +export default withDsExample(Example, { variant: "static" }); + +/* Storybook story */ +export const Demo = { + render: Example, +}; + +export const args = { + index: 1, + desc: "Ved å sette en grense for maks antall valgte vil brukeren få opp en beskjed om at hen ikke kan velge flere når grensen er nådd. Resterende valg vil også bli inaktive.", +}; diff --git a/yarn.lock b/yarn.lock index 1d1fd214984..f982aeb9144 100644 --- a/yarn.lock +++ b/yarn.lock @@ -121,19 +121,19 @@ __metadata: linkType: hard "@axe-core/playwright@npm:^4.5.2": - version: 4.8.3 - resolution: "@axe-core/playwright@npm:4.8.3" + version: 4.8.4 + resolution: "@axe-core/playwright@npm:4.8.4" dependencies: axe-core: ~4.8.3 peerDependencies: playwright-core: ">= 1.0.0" - checksum: d391dfb8beae02ed8c6bfa1189e82ee7d89ae3a76da7806d08f920c225d188a2632c3c904da61c3928c694e921419e901535a7ffc580d062b82756be5fda608d + checksum: f61efffb64ab06b1ad45aab264d4269ab08cb94518ba4e11a09fd91955d81ceda952ea9cc20a6df0777aa5105a50da476cd3e1bdb2596a412aacb5164f3142b8 languageName: node linkType: hard "@babel/cli@npm:^7.19.3": - version: 7.23.4 - resolution: "@babel/cli@npm:7.23.4" + version: 7.23.9 + resolution: "@babel/cli@npm:7.23.9" dependencies: "@jridgewell/trace-mapping": ^0.3.17 "@nicolo-ribaudo/chokidar-2": 2.1.8-no-fsevents.3 @@ -154,7 +154,7 @@ __metadata: bin: babel: ./bin/babel.js babel-external-helpers: ./bin/babel-external-helpers.js - checksum: 5a4f296cdf0b15a8578a860ad42675a358d888e11088c91ee5e510b48598d1dd88d83686d6fe6744c0a9cbcddfd34e79bc75ea425ced8ec9a2531e08c2655279 + checksum: 2952312b73f9a0a2566e556f821ca04502aa747118530af5186a1943f338aec5d6abbe4e14b3916d27428b77c5ebc223e83a9c6cea8868342c51368613acb51c languageName: node linkType: hard @@ -176,25 +176,25 @@ __metadata: linkType: hard "@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.18.9, @babel/core@npm:^7.19.0, @babel/core@npm:^7.19.6, @babel/core@npm:^7.20.12, @babel/core@npm:^7.20.5, @babel/core@npm:^7.21.3, @babel/core@npm:^7.23.0, @babel/core@npm:^7.23.2, @babel/core@npm:^7.23.5": - version: 7.23.7 - resolution: "@babel/core@npm:7.23.7" + version: 7.23.9 + resolution: "@babel/core@npm:7.23.9" dependencies: "@ampproject/remapping": ^2.2.0 "@babel/code-frame": ^7.23.5 "@babel/generator": ^7.23.6 "@babel/helper-compilation-targets": ^7.23.6 "@babel/helper-module-transforms": ^7.23.3 - "@babel/helpers": ^7.23.7 - "@babel/parser": ^7.23.6 - "@babel/template": ^7.22.15 - "@babel/traverse": ^7.23.7 - "@babel/types": ^7.23.6 + "@babel/helpers": ^7.23.9 + "@babel/parser": ^7.23.9 + "@babel/template": ^7.23.9 + "@babel/traverse": ^7.23.9 + "@babel/types": ^7.23.9 convert-source-map: ^2.0.0 debug: ^4.1.0 gensync: ^1.0.0-beta.2 json5: ^2.2.3 semver: ^6.3.1 - checksum: 32d5bf73372a47429afaae9adb0af39e47bcea6a831c4b5dcbb4791380cda6949cb8cb1a2fea8b60bb1ebe189209c80e333903df1fa8e9dcb04798c0ce5bf59e + checksum: 634a511f74db52a5f5a283c1121f25e2227b006c095b84a02a40a9213842489cd82dc7d61cdc74e10b5bcd9bb0a4e28bab47635b54c7e2256d47ab57356e2a76 languageName: node linkType: hard @@ -253,8 +253,8 @@ __metadata: linkType: hard "@babel/helper-create-class-features-plugin@npm:^7.22.15, @babel/helper-create-class-features-plugin@npm:^7.23.6": - version: 7.23.7 - resolution: "@babel/helper-create-class-features-plugin@npm:7.23.7" + version: 7.23.9 + resolution: "@babel/helper-create-class-features-plugin@npm:7.23.9" dependencies: "@babel/helper-annotate-as-pure": ^7.22.5 "@babel/helper-environment-visitor": ^7.22.20 @@ -267,7 +267,7 @@ __metadata: semver: ^6.3.1 peerDependencies: "@babel/core": ^7.0.0 - checksum: 33e60714b856c3816a7965d4c76278cc8f430644a2dfc4eeafad2f7167c4fbd2becdb74cbfeb04b02efd6bbd07176ef53c6683262b588e65d378438e9c55c26b + checksum: 0f0c8592ec8833c0fd1d131655de929af07942fd626049d1e8fae5d85c1fe33fad97f7e9457a14b10258bc926a0cb39debc54a553abe8b4f7575c446d1c16d80 languageName: node linkType: hard @@ -284,21 +284,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-define-polyfill-provider@npm:^0.4.4": - version: 0.4.4 - resolution: "@babel/helper-define-polyfill-provider@npm:0.4.4" - dependencies: - "@babel/helper-compilation-targets": ^7.22.6 - "@babel/helper-plugin-utils": ^7.22.5 - debug: ^4.1.1 - lodash.debounce: ^4.0.8 - resolve: ^1.14.2 - peerDependencies: - "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 2453cdd79f18a4cb8653d8a7e06b2eb0d8e31bae0d35070fc5abadbddca246a36d82b758064b421cca49b48d0e696d331d54520ba8582c1d61fb706d6d831817 - languageName: node - linkType: hard - "@babel/helper-define-polyfill-provider@npm:^0.5.0": version: 0.5.0 resolution: "@babel/helper-define-polyfill-provider@npm:0.5.0" @@ -474,14 +459,14 @@ __metadata: languageName: node linkType: hard -"@babel/helpers@npm:^7.23.7": - version: 7.23.8 - resolution: "@babel/helpers@npm:7.23.8" +"@babel/helpers@npm:^7.23.9": + version: 7.23.9 + resolution: "@babel/helpers@npm:7.23.9" dependencies: - "@babel/template": ^7.22.15 - "@babel/traverse": ^7.23.7 - "@babel/types": ^7.23.6 - checksum: 8b522d527921f8df45a983dc7b8e790c021250addf81ba7900ba016e165442a527348f6f877aa55e1debb3eef9e860a334b4e8d834e6c9b438ed61a63d9a7ad4 + "@babel/template": ^7.23.9 + "@babel/traverse": ^7.23.9 + "@babel/types": ^7.23.9 + checksum: 2678231192c0471dbc2fc403fb19456cc46b1afefcfebf6bc0f48b2e938fdb0fef2e0fe90c8c8ae1f021dae5012b700372e4b5d15867f1d7764616532e4a6324 languageName: node linkType: hard @@ -496,12 +481,12 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.5, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.22.15, @babel/parser@npm:^7.23.0, @babel/parser@npm:^7.23.6": - version: 7.23.6 - resolution: "@babel/parser@npm:7.23.6" +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.5, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.0, @babel/parser@npm:^7.23.9": + version: 7.23.9 + resolution: "@babel/parser@npm:7.23.9" bin: parser: ./bin/babel-parser.js - checksum: 140801c43731a6c41fd193f5c02bc71fd647a0360ca616b23d2db8be4b9739b9f951a03fc7c2db4f9b9214f4b27c1074db0f18bc3fa653783082d5af7c8860d5 + checksum: e7cd4960ac8671774e13803349da88d512f9292d7baa952173260d3e8f15620a28a3701f14f709d769209022f9e7b79965256b8be204fc550cfe783cdcabe7c7 languageName: node linkType: hard @@ -804,9 +789,9 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-async-generator-functions@npm:^7.23.7": - version: 7.23.7 - resolution: "@babel/plugin-transform-async-generator-functions@npm:7.23.7" +"@babel/plugin-transform-async-generator-functions@npm:^7.23.9": + version: 7.23.9 + resolution: "@babel/plugin-transform-async-generator-functions@npm:7.23.9" dependencies: "@babel/helper-environment-visitor": ^7.22.20 "@babel/helper-plugin-utils": ^7.22.5 @@ -814,7 +799,7 @@ __metadata: "@babel/plugin-syntax-async-generators": ^7.8.4 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: b1f66b23423933c27336b1161ac92efef46683321caea97e2255a666f992979376f47a5559f64188d3831fa66a4b24c2a7a40838cc0e9737e90eebe20e8e6372 + checksum: d402494087a6b803803eb5ab46b837aab100a04c4c5148e38bfa943ea1bbfc1ecfb340f1ced68972564312d3580f550c125f452372e77607a558fbbaf98c31c0 languageName: node linkType: hard @@ -1086,9 +1071,9 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-modules-systemjs@npm:^7.23.3": - version: 7.23.3 - resolution: "@babel/plugin-transform-modules-systemjs@npm:7.23.3" +"@babel/plugin-transform-modules-systemjs@npm:^7.23.9": + version: 7.23.9 + resolution: "@babel/plugin-transform-modules-systemjs@npm:7.23.9" dependencies: "@babel/helper-hoist-variables": ^7.22.5 "@babel/helper-module-transforms": ^7.23.3 @@ -1096,7 +1081,7 @@ __metadata: "@babel/helper-validator-identifier": ^7.22.20 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 0d2fdd993c785aecac9e0850cd5ed7f7d448f0fbb42992a950cc0590167144df25d82af5aac9a5c99ef913d2286782afa44e577af30c10901c5ee8984910fa1f + checksum: cec6abeae6be66fd1a5940c482fe9ff94b689c71fcf4147e179119e4accd09d17d476e36528bc9cb4ab0ec6728fedf48b1c49d0551ea707fb192575d8eac9167 languageName: node linkType: hard @@ -1354,18 +1339,18 @@ __metadata: linkType: hard "@babel/plugin-transform-runtime@npm:^7.23.2": - version: 7.23.7 - resolution: "@babel/plugin-transform-runtime@npm:7.23.7" + version: 7.23.9 + resolution: "@babel/plugin-transform-runtime@npm:7.23.9" dependencies: "@babel/helper-module-imports": ^7.22.15 "@babel/helper-plugin-utils": ^7.22.5 - babel-plugin-polyfill-corejs2: ^0.4.7 - babel-plugin-polyfill-corejs3: ^0.8.7 - babel-plugin-polyfill-regenerator: ^0.5.4 + babel-plugin-polyfill-corejs2: ^0.4.8 + babel-plugin-polyfill-corejs3: ^0.9.0 + babel-plugin-polyfill-regenerator: ^0.5.5 semver: ^6.3.1 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: b3cc760afbfdddac5fec3ba3a3916a448d152ada213dcb3ffe54115eaa09db1249f1661b7f271d79c8e6b03ebd5315c049800287cde372900f2557a6e2fe3333 + checksum: 7789fd48f3f5e18fe70a41751ed7495777adee6b2aa702e4e8727002576f918550b79df6778e4ea575670f3499cfaa3181d1fbe82bc2def9b4765c0bf7aff7f6 languageName: node linkType: hard @@ -1487,8 +1472,8 @@ __metadata: linkType: hard "@babel/preset-env@npm:^7.20.2, @babel/preset-env@npm:^7.23.2": - version: 7.23.8 - resolution: "@babel/preset-env@npm:7.23.8" + version: 7.23.9 + resolution: "@babel/preset-env@npm:7.23.9" dependencies: "@babel/compat-data": ^7.23.5 "@babel/helper-compilation-targets": ^7.23.6 @@ -1517,7 +1502,7 @@ __metadata: "@babel/plugin-syntax-top-level-await": ^7.14.5 "@babel/plugin-syntax-unicode-sets-regex": ^7.18.6 "@babel/plugin-transform-arrow-functions": ^7.23.3 - "@babel/plugin-transform-async-generator-functions": ^7.23.7 + "@babel/plugin-transform-async-generator-functions": ^7.23.9 "@babel/plugin-transform-async-to-generator": ^7.23.3 "@babel/plugin-transform-block-scoped-functions": ^7.23.3 "@babel/plugin-transform-block-scoping": ^7.23.4 @@ -1539,7 +1524,7 @@ __metadata: "@babel/plugin-transform-member-expression-literals": ^7.23.3 "@babel/plugin-transform-modules-amd": ^7.23.3 "@babel/plugin-transform-modules-commonjs": ^7.23.3 - "@babel/plugin-transform-modules-systemjs": ^7.23.3 + "@babel/plugin-transform-modules-systemjs": ^7.23.9 "@babel/plugin-transform-modules-umd": ^7.23.3 "@babel/plugin-transform-named-capturing-groups-regex": ^7.22.5 "@babel/plugin-transform-new-target": ^7.23.3 @@ -1565,14 +1550,14 @@ __metadata: "@babel/plugin-transform-unicode-regex": ^7.23.3 "@babel/plugin-transform-unicode-sets-regex": ^7.23.3 "@babel/preset-modules": 0.1.6-no-external-plugins - babel-plugin-polyfill-corejs2: ^0.4.7 - babel-plugin-polyfill-corejs3: ^0.8.7 - babel-plugin-polyfill-regenerator: ^0.5.4 + babel-plugin-polyfill-corejs2: ^0.4.8 + babel-plugin-polyfill-corejs3: ^0.9.0 + babel-plugin-polyfill-regenerator: ^0.5.5 core-js-compat: ^3.31.0 semver: ^6.3.1 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: b850f99fc4aed4ba22c7d9207bd2bbc7a729b49ea6f2c2c36e819fe209e309b96fba336096e555b46f791b39f7cdd5ac41246b556283d435a99106eb825a209f + checksum: 23a48468ba820c68ba34ea2c1dbc62fd2ff9cf858cfb69e159cabb0c85c72dc4c2266ce20ca84318d8742de050cb061e7c66902fbfddbcb09246afd248847933 languageName: node linkType: hard @@ -1656,29 +1641,29 @@ __metadata: linkType: hard "@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.1, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.22.15, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": - version: 7.23.8 - resolution: "@babel/runtime@npm:7.23.8" + version: 7.23.9 + resolution: "@babel/runtime@npm:7.23.9" dependencies: regenerator-runtime: ^0.14.0 - checksum: 0bd5543c26811153822a9f382fd39886f66825ff2a397a19008011376533747cd05c33a91f6248c0b8b0edf0448d7c167ebfba34786088f1b7eb11c65be7dfc3 + checksum: 6bbebe8d27c0c2dd275d1ac197fc1a6c00e18dab68cc7aaff0adc3195b45862bae9c4cc58975629004b0213955b2ed91e99eccb3d9b39cabea246c657323d667 languageName: node linkType: hard "@babel/standalone@npm:^7.20.6": - version: 7.23.8 - resolution: "@babel/standalone@npm:7.23.8" - checksum: 0542cf0dcaa868a374739860458be00439ebdbf672786d250a775a15342f3997a0a0eda978a081ed50200fa74d6d6bbf73c1cda6f9356ba220a30edbcf3eb493 + version: 7.23.9 + resolution: "@babel/standalone@npm:7.23.9" + checksum: 60e582d8083d3875c6566c1e2823eda3025b43c19146ff0e5f728f9703aea7eb6c285dd9e242efb01582118d1b48bbd94b4e1a801b9e826df6b3f0ce48ab63b3 languageName: node linkType: hard -"@babel/template@npm:^7.22.15, @babel/template@npm:^7.3.3": - version: 7.22.15 - resolution: "@babel/template@npm:7.22.15" +"@babel/template@npm:^7.22.15, @babel/template@npm:^7.23.9, @babel/template@npm:^7.3.3": + version: 7.23.9 + resolution: "@babel/template@npm:7.23.9" dependencies: - "@babel/code-frame": ^7.22.13 - "@babel/parser": ^7.22.15 - "@babel/types": ^7.22.15 - checksum: 1f3e7dcd6c44f5904c184b3f7fe280394b191f2fed819919ffa1e529c259d5b197da8981b6ca491c235aee8dbad4a50b7e31304aa531271cb823a4a24a0dd8fd + "@babel/code-frame": ^7.23.5 + "@babel/parser": ^7.23.9 + "@babel/types": ^7.23.9 + checksum: 6e67414c0f7125d7ecaf20c11fab88085fa98a96c3ef10da0a61e962e04fdf3a18a496a66047005ddd1bb682a7cc7842d556d1db2f3f3f6ccfca97d5e445d342 languageName: node linkType: hard @@ -1700,9 +1685,9 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.19.0, @babel/traverse@npm:^7.23.2, @babel/traverse@npm:^7.23.7, @babel/traverse@npm:^7.4.5": - version: 7.23.7 - resolution: "@babel/traverse@npm:7.23.7" +"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.19.0, @babel/traverse@npm:^7.23.2, @babel/traverse@npm:^7.23.9, @babel/traverse@npm:^7.4.5": + version: 7.23.9 + resolution: "@babel/traverse@npm:7.23.9" dependencies: "@babel/code-frame": ^7.23.5 "@babel/generator": ^7.23.6 @@ -1710,11 +1695,11 @@ __metadata: "@babel/helper-function-name": ^7.23.0 "@babel/helper-hoist-variables": ^7.22.5 "@babel/helper-split-export-declaration": ^7.22.6 - "@babel/parser": ^7.23.6 - "@babel/types": ^7.23.6 + "@babel/parser": ^7.23.9 + "@babel/types": ^7.23.9 debug: ^4.3.1 globals: ^11.1.0 - checksum: d4a7afb922361f710efc97b1e25ec343fab8b2a4ddc81ca84f9a153f22d4482112cba8f263774be8d297918b6c4767c7a98988ab4e53ac73686c986711dd002e + checksum: a932f7aa850e158c00c97aad22f639d48c72805c687290f6a73e30c5c4957c07f5d28310c9bf59648e2980fe6c9d16adeb2ff92a9ca0f97fa75739c1328fc6c3 languageName: node linkType: hard @@ -1728,14 +1713,14 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.17.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.22.15, @babel/types@npm:^7.22.19, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0, @babel/types@npm:^7.23.4, @babel/types@npm:^7.23.6, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": - version: 7.23.6 - resolution: "@babel/types@npm:7.23.6" +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.17.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.22.15, @babel/types@npm:^7.22.19, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0, @babel/types@npm:^7.23.4, @babel/types@npm:^7.23.6, @babel/types@npm:^7.23.9, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": + version: 7.23.9 + resolution: "@babel/types@npm:7.23.9" dependencies: "@babel/helper-string-parser": ^7.23.4 "@babel/helper-validator-identifier": ^7.22.20 to-fast-properties: ^2.0.0 - checksum: 68187dbec0d637f79bc96263ac95ec8b06d424396678e7e225492be866414ce28ebc918a75354d4c28659be6efe30020b4f0f6df81cc418a2d30645b690a8de0 + checksum: 0a9b008e9bfc89beb8c185e620fa0f8ed6c771f1e1b2e01e1596870969096fec7793898a1d64a035176abf1dd13e2668ee30bf699f2d92c210a8128f4b151e65 languageName: node linkType: hard @@ -2818,28 +2803,28 @@ __metadata: linkType: hard "@figma/plugin-typings@npm:^1.84.0": - version: 1.84.0 - resolution: "@figma/plugin-typings@npm:1.84.0" - checksum: 1c73f3740a56973430f8cb5cccf19b70e824b5830369e5b8808df0b7e256810f87564531880b64051d83b460a8462968be449372596d5f77705054841a8a4cda + version: 1.85.0 + resolution: "@figma/plugin-typings@npm:1.85.0" + checksum: f563a34510b61e7cab1b4d4f5b44e523c52a7ed2da4c2b6ba55aa666e21d6251d08541955b6f8490ce42c9a4eaaab21f924338c246468ad5299b2b5914515e23 languageName: node linkType: hard -"@floating-ui/core@npm:^1.5.3": - version: 1.5.3 - resolution: "@floating-ui/core@npm:1.5.3" +"@floating-ui/core@npm:^1.6.0": + version: 1.6.0 + resolution: "@floating-ui/core@npm:1.6.0" dependencies: - "@floating-ui/utils": ^0.2.0 - checksum: 72af8563e1742791acee82e86f82a0fbca7445809988d31eea3fd5771909463aa7655a6cb001cc244f8fe3a9de600420257e4dfb887ca33e2a31ac47b52e39a2 + "@floating-ui/utils": ^0.2.1 + checksum: 2e25c53b0c124c5c9577972f8ae21d081f2f7895e6695836a53074463e8c65b47722744d6d2b5a993164936da006a268bcfe87fe68fd24dc235b1cb86bed3127 languageName: node linkType: hard -"@floating-ui/dom@npm:^1.0.1, @floating-ui/dom@npm:^1.2.7, @floating-ui/dom@npm:^1.5.4": - version: 1.5.4 - resolution: "@floating-ui/dom@npm:1.5.4" +"@floating-ui/dom@npm:^1.0.1, @floating-ui/dom@npm:^1.2.7, @floating-ui/dom@npm:^1.6.0": + version: 1.6.0 + resolution: "@floating-ui/dom@npm:1.6.0" dependencies: - "@floating-ui/core": ^1.5.3 - "@floating-ui/utils": ^0.2.0 - checksum: 5e6f05532ff4e6daf9f2d91534184d8f942ddb8fd260c2543a49bdf0c0ff69fd0867937ce1d023126008724ac238f8fc89b5e48f82cdf9f8355a1d04edd085bd + "@floating-ui/core": ^1.6.0 + "@floating-ui/utils": ^0.2.1 + checksum: 6bbb67f9241f4e07030cd564e8079dbd8b692805e94d54db63b124853318a901a5df1e0b950068e416fa7b868345d165017ec0c19c486560729d8ac856b4bd25 languageName: node linkType: hard @@ -2856,14 +2841,14 @@ __metadata: linkType: hard "@floating-ui/react-dom@npm:^2.0.0, @floating-ui/react-dom@npm:^2.0.2": - version: 2.0.6 - resolution: "@floating-ui/react-dom@npm:2.0.6" + version: 2.0.7 + resolution: "@floating-ui/react-dom@npm:2.0.7" dependencies: - "@floating-ui/dom": ^1.5.4 + "@floating-ui/dom": ^1.6.0 peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: 3608537be6cae5f0442d3f826379b8e4a9ce5c4bdecf1d2b34e6709842d80444be1a00eca3641d680e2e6405d833092f58005d1b120a9d39ffd341c60b0c017c + checksum: 3c3907722f17c2cc11819c92d81d53962db049ea96dcd73a886e5d807b3a5d4e31c472e53f63af74f2a5c110bcc5ced68f49f16b4df11c02312fed2c7708de59 languageName: node linkType: hard @@ -2888,7 +2873,7 @@ __metadata: languageName: node linkType: hard -"@floating-ui/utils@npm:^0.2.0": +"@floating-ui/utils@npm:^0.2.1": version: 0.2.1 resolution: "@floating-ui/utils@npm:0.2.1" checksum: 9ed4380653c7c217cd6f66ae51f20fdce433730dbc77f95b5abfb5a808f5fdb029c6ae249b4e0490a816f2453aa6e586d9a873cd157fdba4690f65628efc6e06 @@ -7307,14 +7292,14 @@ __metadata: linkType: hard "@types/express-serve-static-core@npm:*, @types/express-serve-static-core@npm:^4.17.33": - version: 4.17.41 - resolution: "@types/express-serve-static-core@npm:4.17.41" + version: 4.17.42 + resolution: "@types/express-serve-static-core@npm:4.17.42" dependencies: "@types/node": "*" "@types/qs": "*" "@types/range-parser": "*" "@types/send": "*" - checksum: 12750f6511dd870bbaccfb8208ad1e79361cf197b147f62a3bedc19ec642f3a0f9926ace96705f4bc88ec2ae56f61f7ca8c2438e6b22f5540842b5569c28a121 + checksum: 58273f80fcc94de42691f48e22542e69f0b17863378e3216ce8b782ace012f32241bfeb02a2be837f0e2b4ef96e916979adc30bbfea13f6545bd3ab81b7d2773 languageName: node linkType: hard @@ -7592,11 +7577,11 @@ __metadata: linkType: hard "@types/node@npm:*": - version: 20.11.6 - resolution: "@types/node@npm:20.11.6" + version: 20.11.7 + resolution: "@types/node@npm:20.11.7" dependencies: undici-types: ~5.26.4 - checksum: 54b3739f42d9c2266fd724d8ecbf50bc64eb0563343b65a6ea874a51a7fc8bb4793bf3a1b2222e40e3b7bd62cf5af5609899bf1a3de8b69748dcac65e63e8bdc + checksum: 61ea0718bccda31110c643190518407b7c50d26698a20e3522871608db5fa3d2d43d1ae57c609068eae6996d563db43326045a90f22a9aacc825e8d6c7aea2ce languageName: node linkType: hard @@ -7608,11 +7593,11 @@ __metadata: linkType: hard "@types/node@npm:^18.0.0, @types/node@npm:^18.15.11": - version: 18.19.9 - resolution: "@types/node@npm:18.19.9" + version: 18.19.10 + resolution: "@types/node@npm:18.19.10" dependencies: undici-types: ~5.26.4 - checksum: 8ffd4043ada193cf32d4c13eb850e8c510d4c96504c62fdc5326b3bb2c7ddc2c81892c351b2c0c403c29df3ab13c864a370f0282daf6d3380ba3c2001728a643 + checksum: eea429c1fe8d25702c1e860f5c4ac053db3c52a5884646f458513b622a507660a6c88c717ee4f106e63c82f4ec6cafbf999e3f3f1d3083f7a40b4f715e03332b languageName: node linkType: hard @@ -7901,9 +7886,9 @@ __metadata: linkType: hard "@types/uuid@npm:^9.0.1": - version: 9.0.7 - resolution: "@types/uuid@npm:9.0.7" - checksum: c7321194aeba9ea173efd1e721403bdf4e7ae6945f8f8cdbc87c791f4b505ccf3dbc4a8883d90b394ef13b7c2dc778045792b05dbb23b3c746f8ea347804d448 + version: 9.0.8 + resolution: "@types/uuid@npm:9.0.8" + checksum: b8c60b7ba8250356b5088302583d1704a4e1a13558d143c549c408bf8920535602ffc12394ede77f8a8083511b023704bc66d1345792714002bfa261b17c5275 languageName: node linkType: hard @@ -9633,7 +9618,7 @@ __metadata: languageName: node linkType: hard -"babel-plugin-polyfill-corejs2@npm:^0.4.7": +"babel-plugin-polyfill-corejs2@npm:^0.4.8": version: 0.4.8 resolution: "babel-plugin-polyfill-corejs2@npm:0.4.8" dependencies: @@ -9646,19 +9631,19 @@ __metadata: languageName: node linkType: hard -"babel-plugin-polyfill-corejs3@npm:^0.8.7": - version: 0.8.7 - resolution: "babel-plugin-polyfill-corejs3@npm:0.8.7" +"babel-plugin-polyfill-corejs3@npm:^0.9.0": + version: 0.9.0 + resolution: "babel-plugin-polyfill-corejs3@npm:0.9.0" dependencies: - "@babel/helper-define-polyfill-provider": ^0.4.4 - core-js-compat: ^3.33.1 + "@babel/helper-define-polyfill-provider": ^0.5.0 + core-js-compat: ^3.34.0 peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 51bc215ab0c062bbb2225d912f69f8a6705d1837c8e01f9651307b5b937804287c1d73ebd8015689efcc02c3c21f37688b9ee6f5997635619b7a9cc4b7d9908d + checksum: 65bbf59fc0145c7a264822777403632008dce00015b4b5c7ec359125ef4faf9e8f494ae5123d2992104feb6f19a3cff85631992862e48b6d7bd64eb7e755ee1f languageName: node linkType: hard -"babel-plugin-polyfill-regenerator@npm:^0.5.4": +"babel-plugin-polyfill-regenerator@npm:^0.5.5": version: 0.5.5 resolution: "babel-plugin-polyfill-regenerator@npm:0.5.5" dependencies: @@ -11290,7 +11275,7 @@ __metadata: languageName: node linkType: hard -"core-js-compat@npm:^3.31.0, core-js-compat@npm:^3.33.1": +"core-js-compat@npm:^3.31.0, core-js-compat@npm:^3.34.0": version: 3.35.1 resolution: "core-js-compat@npm:3.35.1" dependencies: @@ -12705,9 +12690,9 @@ __metadata: linkType: hard "electron-to-chromium@npm:^1.4.601": - version: 1.4.645 - resolution: "electron-to-chromium@npm:1.4.645" - checksum: ac7d23b8123f09e2343016216b1a8f297ccfb4ae9dccefe3716023344cda8a81656916d40a87039fa3d448cac31c2c4147c6b913b22178a3a00d0221a8019513 + version: 1.4.647 + resolution: "electron-to-chromium@npm:1.4.647" + checksum: fd79098e08a03025fb64a0608dd20942a7004bb38a8c7fd6d18d8b1767712866a3dae2df7e69ddbc7c627352278cbd07ce1a7368b6c037129e68a042802e108c languageName: node linkType: hard @@ -15284,9 +15269,9 @@ __metadata: linkType: hard "groq-js@npm:^1.1.12": - version: 1.4.0 - resolution: "groq-js@npm:1.4.0" - checksum: 39dab99cdd2766bf2880d6e71078ee95d35040804e501a9d038f5d6059f8bf53f24c9912992aa760ef5b620159695f8516261e2accde2dc4bfddf5ba8798fa4a + version: 1.4.1 + resolution: "groq-js@npm:1.4.1" + checksum: 5f2e5def5be64487e1e39715602628cf2dd00ffd48726443f03efb612a14e192d5cdbdc3de4127208f52197034621691efb43a9c3ad0f5bd451deb7f496a3a1b languageName: node linkType: hard @@ -18389,9 +18374,9 @@ __metadata: linkType: hard "lru-cache@npm:^10.0.1, lru-cache@npm:^9.1.1 || ^10.0.0": - version: 10.1.0 - resolution: "lru-cache@npm:10.1.0" - checksum: 58056d33e2500fbedce92f8c542e7c11b50d7d086578f14b7074d8c241422004af0718e08a6eaae8705cee09c77e39a61c1c79e9370ba689b7010c152e6a76ab + version: 10.2.0 + resolution: "lru-cache@npm:10.2.0" + checksum: eee7ddda4a7475deac51ac81d7dd78709095c6fa46e8350dc2d22462559a1faa3b81ed931d5464b13d48cbd7e08b46100b6f768c76833912bc444b99c37e25db languageName: node linkType: hard @@ -18702,13 +18687,20 @@ __metadata: languageName: node linkType: hard -"mendoza@npm:3.0.3, mendoza@npm:^3.0.0": +"mendoza@npm:3.0.3": version: 3.0.3 resolution: "mendoza@npm:3.0.3" checksum: 8bee64b018b88ddaf6e268bc0f10a656054a55430c53f06625a525227c43d18962440c112a6625225bdb6b31e998094064b13e3381a5fa5395db65c10a30ecec languageName: node linkType: hard +"mendoza@npm:^3.0.0": + version: 3.0.4 + resolution: "mendoza@npm:3.0.4" + checksum: cc4e7abe6c8df93a0c246285da73c074ee9b076b3a4e7b3d7784436d18e307d5304cda622ef9fb8b63be2cedc16b936403d3c67bd3c764085a1264b1141cbe3d + languageName: node + linkType: hard + "meow@npm:^6.0.0": version: 6.1.1 resolution: "meow@npm:6.1.1"