diff --git a/.lostpixel/baseline/components-combobox--complex.png b/.lostpixel/baseline/components-combobox--complex.png new file mode 100644 index 0000000000..4e21c7d287 Binary files /dev/null and b/.lostpixel/baseline/components-combobox--complex.png differ diff --git a/.lostpixel/baseline/components-combobox--simple.png b/.lostpixel/baseline/components-combobox--simple.png index 572fc3220a..dae0b9a6e9 100644 Binary files a/.lostpixel/baseline/components-combobox--simple.png and b/.lostpixel/baseline/components-combobox--simple.png differ diff --git a/.lostpixel/baseline/components-text-field--click-capture.png b/.lostpixel/baseline/components-text-field--click-capture.png new file mode 100644 index 0000000000..3511c42f5b Binary files /dev/null and b/.lostpixel/baseline/components-text-field--click-capture.png differ diff --git a/.lostpixel/baseline/components-combobox--custom-input.png b/.lostpixel/baseline/components-text-field--default.png similarity index 65% rename from .lostpixel/baseline/components-combobox--custom-input.png rename to .lostpixel/baseline/components-text-field--default.png index 572fc3220a..f7db5735a2 100644 Binary files a/.lostpixel/baseline/components-combobox--custom-input.png and b/.lostpixel/baseline/components-text-field--default.png differ diff --git a/.lostpixel/baseline/components-text-field--focus-events.png b/.lostpixel/baseline/components-text-field--focus-events.png new file mode 100644 index 0000000000..5542e8888a Binary files /dev/null and b/.lostpixel/baseline/components-text-field--focus-events.png differ diff --git a/.lostpixel/baseline/components-text-field--interactive.png b/.lostpixel/baseline/components-text-field--interactive.png new file mode 100644 index 0000000000..ce559ea3f9 Binary files /dev/null and b/.lostpixel/baseline/components-text-field--interactive.png differ diff --git a/.lostpixel/baseline/components-text-field--layout.png b/.lostpixel/baseline/components-text-field--layout.png new file mode 100644 index 0000000000..8dfadf669c Binary files /dev/null and b/.lostpixel/baseline/components-text-field--layout.png differ diff --git a/.lostpixel/baseline/components-text-field--native-props.png b/.lostpixel/baseline/components-text-field--native-props.png new file mode 100644 index 0000000000..6f0764f33b Binary files /dev/null and b/.lostpixel/baseline/components-text-field--native-props.png differ diff --git a/.lostpixel/baseline/components-text-field--prefix-suffix.png b/.lostpixel/baseline/components-text-field--prefix-suffix.png new file mode 100644 index 0000000000..6cbb07805a Binary files /dev/null and b/.lostpixel/baseline/components-text-field--prefix-suffix.png differ diff --git a/.lostpixel/baseline/components-text-field--state.png b/.lostpixel/baseline/components-text-field--state.png new file mode 100644 index 0000000000..c770474c3a Binary files /dev/null and b/.lostpixel/baseline/components-text-field--state.png differ diff --git a/.lostpixel/baseline/components-text-field--variants.png b/.lostpixel/baseline/components-text-field--variants.png new file mode 100644 index 0000000000..f7db5735a2 Binary files /dev/null and b/.lostpixel/baseline/components-text-field--variants.png differ diff --git a/apps/designer/app/auth/login.tsx b/apps/designer/app/auth/login.tsx index 476776ebf6..f300aa904f 100644 --- a/apps/designer/app/auth/login.tsx +++ b/apps/designer/app/auth/login.tsx @@ -1,16 +1,15 @@ import { LinksFunction, MetaFunction } from "@remix-run/node"; import { Form, useLoaderData } from "@remix-run/react"; import { useState } from "react"; - import { Card, Flex, Heading, + Button, __DEPRECATED__Text, TextField, } from "@webstudio-is/design-system"; import interStyles from "~/shared/font-faces/inter.css"; - import { GithubIcon, CommitIcon, GoogleIcon } from "@webstudio-is/icons"; import { LoginButton } from "./components/login-button"; import loginStyles from "./login.css"; @@ -44,59 +43,72 @@ export const Login = ({ errorMessage }: { errorMessage: string }) => { justify="center" > - + Login - {errorMessage.length ? ( - <__DEPRECATED__Text css={{ textAlign: "center" }} variant="red"> - {errorMessage} - - ) : null} - -
- - - - Login with GitHub - - -
-
- - - - Login with Google - - -
- {data.isDevLogin && ( - <> - {isDevLoginOpen ? ( -
+ + {errorMessage.length ? ( + <__DEPRECATED__Text css={{ textAlign: "center" }} variant="red"> + {errorMessage} + + ) : null} + + + + + + + + Login with GitHub + + + +
+ + + + + + Login with Google + + +
+ {data.isDevLogin && + (isDevLoginOpen ? ( + - + + ) : ( openDevLogin(true)} > - - + + + + Dev Login - )} - - )} + ))} +
diff --git a/apps/designer/app/dashboard/components/card.tsx b/apps/designer/app/dashboard/components/card.tsx index aa1ec752f8..5e4a9f1c58 100644 --- a/apps/designer/app/dashboard/components/card.tsx +++ b/apps/designer/app/dashboard/components/card.tsx @@ -55,7 +55,6 @@ export const SelectProjectCard = ({
( ; + value: string; + onItemSelect: (value: string | null) => void; + onSubmit: (value: string) => void; +}; + +const Combobox = ({ + isReadonly, + isInvalid, + items: itemsProp, + value, + onItemSelect, + onSubmit, +}: ComboboxProps) => { + const { + items, + getInputProps, + getComboboxProps, + getToggleButtonProps, + getMenuProps, + getItemProps, + isOpen, + } = useCombobox({ + items: itemsProp, + value, + itemToString: (item) => item ?? "", + onItemSelect, + }); + + return ( + + + + { + if (event.key === "Enter") { + onSubmit(event.currentTarget.value); + } + }, + })} + name="prop" + placeholder="Property" + readOnly={isReadonly} + state={isInvalid ? "invalid" : undefined} + suffix={ + + + + } + /> + + + + {isOpen && + items.map((item, index) => { + return ( + + {item} + + ); + })} + + + + + ); +}; + type PropertyProps = UserProp & { component: Instance["component"]; onChange: handleChangePropType; @@ -33,14 +114,15 @@ const Property = ({ id, prop, value, - required, + required = false, component, onChange, onDelete, }: PropertyProps) => { const meta = componentsMeta[component]; const argType = meta.argTypes?.[prop as keyof typeof meta.argTypes]; - const isInvalidProp = + const isInvalid = + prop != null && prop.length > 0 && typeof argType === "undefined" && !prop.match(/^data-(.)+/); @@ -51,32 +133,24 @@ const Property = ({ return ( item ?? ""} - onItemSelect={(value) => { + onItemSelect={(value: PropertyProps["value"] | null) => { + if (value !== null) { + onChange(id, "prop", value); + } + }} + onSubmit={(value) => { onChange(id, "prop", value); }} - renderTextField={({ inputProps, toggleProps }) => ( - - )} - renderPopperContent={(props) => ( - - )} + isInvalid={isInvalid} + isReadonly={required} /> - {isInvalidProp ? ( + {isInvalid ? ( diff --git a/apps/designer/app/designer/features/style-panel/controls/color/color-control.tsx b/apps/designer/app/designer/features/style-panel/controls/color/color-control.tsx index d81672f355..e5674aba13 100644 --- a/apps/designer/app/designer/features/style-panel/controls/color/color-control.tsx +++ b/apps/designer/app/designer/features/style-panel/controls/color/color-control.tsx @@ -9,7 +9,6 @@ import { PopoverContent, PopoverPortal, TextField, - Tooltip, css, } from "@webstudio-is/design-system"; import { PropertyName } from "../../shared/property-name"; @@ -54,73 +53,56 @@ const ColorPicker = ({ onChange, onChangeComplete, id, - styleConfig, }: ColorPickerProps) => { const [displayColorPicker, setDisplayColorPicker] = useState(false); // Color picker will use 0 as alpha value, which will force user to set alpha every time they have to change from transparent if (value === "transparent") value = ""; return ( - - - onChange(e.target.value)} + value={value} + id={id} + prefix={ + - setDisplayColorPicker((shown) => !shown)} > - - onChange(e.target.value)} - onClick={() => setDisplayColorPicker((shown) => !shown)} - value={value} - id={id} - /> - - - - - - onChange(stringifyRGBA(color.rgb)) - } - onChangeComplete={(color: ColorResult) => { - onChangeComplete(stringifyRGBA(color.rgb)); - }} - // @todo to remove both when we have preset colors - presetColors={[]} - className={pickerStyle()} - styles={defaultPickerStyles} - /> - - - + + + + + onChange(stringifyRGBA(color.rgb)) + } + onChangeComplete={(color: ColorResult) => { + onChangeComplete(stringifyRGBA(color.rgb)); + }} + // @todo to remove both when we have preset colors + presetColors={[]} + className={pickerStyle()} + styles={defaultPickerStyles} + /> + + + + } + /> ); }; diff --git a/apps/designer/app/designer/features/style-panel/controls/text/text-control.tsx b/apps/designer/app/designer/features/style-panel/controls/text/text-control.tsx index b66d4c7e8f..e66679689a 100644 --- a/apps/designer/app/designer/features/style-panel/controls/text/text-control.tsx +++ b/apps/designer/app/designer/features/style-panel/controls/text/text-control.tsx @@ -1,6 +1,5 @@ import { ChangeEvent, FocusEvent, KeyboardEvent, useRef } from "react"; import { - Box, Text, Grid, IconButton, @@ -25,35 +24,6 @@ const commonUnitsSubset = units : 1 ); -const gridTemplateStyle = { - gridTemplateColumns: "$sizes$6 auto $sizes$6", - gridTemplateRows: "repeat(1, 1fr)", -}; - -const gridLeftStyle = { - "& ~ input": { - paddingLeft: "calc($sizes$6 + $nudge$3)", - }, - gridArea: "1 / 1 / 2 / 2", -}; - -const gridRightStyle = { - gridArea: "-1 / -1 / -2 / -2", - "& ~ input": { - paddingRight: "calc($sizes$5 + $nudge$3)", - }, -}; - -const iconButtonStyle = { - borderRadius: 2, - height: "calc($sizes$6 - $nudge$3)", - width: "calc($sizes$6 - $nudge$3)", - transform: "translate(3px, 3px)", - "&:focus": { - boxShadow: "none", - }, -}; - const iconButtonActiveStyle = { bc: "$colors$blue4", "&:not(:hover)": { @@ -61,24 +31,6 @@ const iconButtonActiveStyle = { }, }; -const iconButtonGridLeftStyle = { - ...gridLeftStyle, - ...iconButtonStyle, -}; - -const iconButtonGridRightStyle = { - ...gridRightStyle, - "& button": { ...iconButtonStyle }, -}; - -const textFieldStyle = { - height: "$6", - fontWeight: "500", - paddingLeft: "calc($sizes$1 + $nudge$3)", - gridArea: "1 / 1 / -1 / -1", - cursor: "default", -}; - export const TextControl = ({ currentStyle, inheritedStyle, @@ -102,107 +54,106 @@ export const TextControl = ({ const Icon = iconConfigs[styleConfig.property]?.normal; return ( - + - - {Icon && ( - - { - if (value.type !== "unit" || !textFieldRef.current) return; - const textFieldNode = textFieldRef.current; - numericScrubRef.current = - numericScrubRef.current || - numericScrubControl(event.target as HTMLInputElement, { - minValue: 0, - initialValue: value.value as number, - onValueChange: ({ value }) => { - textFieldNode.value = String(value); - setValue(String(value), { isEphemeral: true }); - }, - }); - }} - onPointerLeave={() => { - if (value.type !== "unit") return; - numericScrubRef.current?.disconnectedCallback(); - numericScrubRef.current = undefined; - }} - onPointerUp={(event) => { - setValue((event.target as HTMLInputElement).value); - }} - css={{ - ...iconButtonGridLeftStyle, - ...(isCurrentBreakpoint && iconButtonActiveStyle), - }} + + - {Icon && } - - - )} - {value.type === "unit" ? ( - ({ - name: unit, - label: unit, - }))} - onChange={(unit) => setValue(value.value + unit)} - onHover={(unit) => - setValue(value.value + unit, { isEphemeral: true }) - } - /> - ) : ( - setValue(item)} - onHover={(item) => setValue(item, { isEphemeral: true })} - /> - )} - ) => - event.target.select() + { + if (value.type !== "unit" || !textFieldRef.current) return; + const textFieldNode = textFieldRef.current; + numericScrubRef.current = + numericScrubRef.current || + numericScrubControl(event.target as HTMLInputElement, { + minValue: 0, + initialValue: value.value as number, + onValueChange: ({ value }) => { + textFieldNode.value = String(value); + setValue(String(value), { isEphemeral: true }); + }, + }); + }} + onPointerLeave={() => { + if (value.type !== "unit") return; + numericScrubRef.current?.disconnectedCallback(); + numericScrubRef.current = undefined; + }} + onPointerUp={(event) => { + setValue((event.target as HTMLInputElement).value); + }} + css={{ + ...(isCurrentBreakpoint && iconButtonActiveStyle), + }} + > + {Icon && } + + + ) + } + suffix={ + value.type === "unit" ? ( + ({ + name: unit, + label: unit, + }))} + onChange={(unit) => setValue(value.value + unit)} + onHover={(unit) => + setValue(value.value + unit, { isEphemeral: true }) + } + /> + ) : ( + setValue(item)} + onHover={(item) => setValue(item, { isEphemeral: true })} + /> + ) + } + onFocus={(event: FocusEvent) => event.target.select()} + onBlur={(event: ChangeEvent) => + setValue(event.target.value) + } + onKeyDown={(event: KeyboardEvent) => { + const target = event.target as HTMLInputElement; + if (event.code === "Enter") { + setValue(target.value); + const number = parseFloat(target.value); + if (!isNaN(number)) target.value = String(number); } - onBlur={(event: ChangeEvent) => - setValue(event.target.value) - } - onKeyDown={(event: KeyboardEvent) => { - const target = event.target as HTMLInputElement; - if (event.code === "Enter") { - setValue(target.value); - const number = parseFloat(target.value); - if (!isNaN(number)) target.value = String(number); - } - if (value.type !== "unit") return; - if (!["ArrowUp", "ArrowDown"].includes(event.code)) return; - event.preventDefault(); - let currentValue = parseFloat(target.value); - let currentDelta = 1; - if (event.shiftKey) currentDelta = 10; - if (event.altKey) currentDelta = 0.1; - if (event.code === "ArrowUp") - currentValue = currentValue + currentDelta; - if (event.code === "ArrowDown") - currentValue = currentValue - currentDelta; - const currentValueAsString = - currentValue % 1 - ? currentValue.toPrecision( - Math.abs(currentValue).toString().indexOf(".") + 2 - ) - : String(currentValue); - target.value = currentValueAsString; - setValue(currentValueAsString, { isEphemeral: true }); - }} - /> - + if (value.type !== "unit") return; + if (!["ArrowUp", "ArrowDown"].includes(event.code)) return; + event.preventDefault(); + let currentValue = parseFloat(target.value); + let currentDelta = 1; + if (event.shiftKey) currentDelta = 10; + if (event.altKey) currentDelta = 0.1; + if (event.code === "ArrowUp") + currentValue = currentValue + currentDelta; + if (event.code === "ArrowDown") + currentValue = currentValue - currentDelta; + const currentValueAsString = + currentValue % 1 + ? currentValue.toPrecision( + Math.abs(currentValue).toString().indexOf(".") + 2 + ) + : String(currentValue); + target.value = currentValueAsString; + setValue(currentValueAsString, { isEphemeral: true }); + }} + /> ); }; @@ -219,19 +170,17 @@ const Units = ({ onHover?: (value: string) => void; }) => { return ( - - - {value === "number" ? "—" : value} - - } - items={items} - value={String(value)} - onChange={onChange} - onHover={onHover} - /> - + + {value === "number" ? "—" : value} + + } + items={items} + value={String(value)} + onChange={onChange} + onHover={onHover} + /> ); }; @@ -248,14 +197,12 @@ const Items = ({ }) => { if (!items?.length) return null; return ( - - } - items={items} - value={String(value)} - onChange={onChange} - onHover={onHover} - /> - + } + items={items} + value={String(value)} + onChange={onChange} + onHover={onHover} + /> ); }; diff --git a/apps/designer/app/designer/features/style-panel/search.tsx b/apps/designer/app/designer/features/style-panel/search.tsx index b547278880..299ce19933 100644 --- a/apps/designer/app/designer/features/style-panel/search.tsx +++ b/apps/designer/app/designer/features/style-panel/search.tsx @@ -1,9 +1,8 @@ -import { KeyboardEvent, useState } from "react"; +import { useRef, useState } from "react"; import { IconButton, TextField } from "@webstudio-is/design-system"; import { Cross2Icon, MagnifyingGlassIcon } from "@webstudio-is/icons"; import { components } from "@webstudio-is/react-sdk"; import type { SelectedInstanceData } from "~/shared/canvas-components"; -import { Box } from "@webstudio-is/design-system"; type OnSearch = (search: string) => void; @@ -25,55 +24,35 @@ type SearchProps = { export const Search = ({ onSearch, selectedInstanceData }: SearchProps) => { const [search, setSearch] = useSearch(onSearch); + const inputRef = useRef(null); return ( - { - setSearch(""); + + + + } + suffix={ + search.length > 0 && ( + { + setSearch(""); + inputRef.current?.focus(); + }} + > + + + ) + } + placeholder={components[selectedInstanceData.component].label} + onChange={(event) => { + const { value } = event.target; + setSearch(value.toLowerCase()); }} - onKeyDown={(event: KeyboardEvent) => { - if (event.key === "Escape") { - event.currentTarget.reset(); - } - }} - > - { - const { value } = event.target; - setSearch(value.toLowerCase()); - }} - /> - - {search.length === 0 ? : } - - + /> ); }; diff --git a/apps/designer/app/designer/features/style-panel/style-panel.tsx b/apps/designer/app/designer/features/style-panel/style-panel.tsx index d8044699ae..b326756ce3 100644 --- a/apps/designer/app/designer/features/style-panel/style-panel.tsx +++ b/apps/designer/app/designer/features/style-panel/style-panel.tsx @@ -55,13 +55,13 @@ export const StylePanel = ({ return ( <> + + + - - - = () => { const items = [{ label: "Apple" }, { label: "Banana" }, { label: "Orange" }]; - const [value, setValue] = React.useState(items[0]); + const [value, setValue] = React.useState(null); return ( - ); -}; - -export const CustomInput: ComponentStory = () => { - const items = [ - { label: "Apple", value: "apple", disabled: true }, - { label: "Banana", value: "banana" }, - { label: "Orange", value: "orange" }, - ]; - const [value, setValue] = React.useState(items[0]); - - return ( - { - action("Highlight")(item); + onItemSelect={(value) => { + // If the value is cleared, we revert to previous state + if (value !== null) { + setValue(value); + } }} - renderTextField={({ inputProps, toggleProps }) => ( - { - inputProps.onKeyDown?.(event); - switch (event.key) { - case "Enter": { - action("Enter")(inputProps.value); - break; - } - } - }, - onBlur: (event) => { - inputProps.onBlur?.(event); - action("Blur")(inputProps.value); - }, - }} - /> - )} + itemToString={(item) => item?.label ?? ""} /> ); }; + +export const Complex: ComponentStory = () => { + const [value, setValue] = React.useState(null); + + const stateReducer = useCallback((state, actionAndChanges) => { + const { type, changes } = actionAndChanges; + switch (type) { + // on item selection. + case useDownshiftCombobox.stateChangeTypes.ItemClick: + case useDownshiftCombobox.stateChangeTypes.InputKeyDownEnter: + case useDownshiftCombobox.stateChangeTypes.InputBlur: + case useDownshiftCombobox.stateChangeTypes + .ControlledPropUpdatedSelectedItem: + return { + ...changes, + // if we have a selected item. + ...(changes.selectedItem && { + // we will set the input value to "" (empty string). + inputValue: "", + }), + }; + + // Remove "reset" action + case useDownshiftCombobox.stateChangeTypes.InputKeyDownEscape: { + return { + ...state, + }; + } + + default: + return changes; // otherwise business as usual. + } + }, []); + + const { items, getComboboxProps, getMenuProps, getItemProps, getInputProps } = + useCombobox({ + items: ["Apple", "Banana", "Orange"], + value, + itemToString: (item) => item ?? "", + stateReducer, + onItemSelect: (value) => { + setValue(value); + }, + }); + + return ( + + } + {...getInputProps({})} + /> +
+ Choose an item + + {items.map((item, index) => { + return ( + + {item} + + ); + })} + +
+
+ ); +}; diff --git a/packages/design-system/src/components/combobox.tsx b/packages/design-system/src/components/combobox.tsx index 11eadbde54..e2916240f4 100644 --- a/packages/design-system/src/components/combobox.tsx +++ b/packages/design-system/src/components/combobox.tsx @@ -1,13 +1,19 @@ import { useState, forwardRef, - useEffect, + useCallback, type ComponentProps, type ForwardRefRenderFunction, + useEffect, + useRef, } from "react"; import { CheckIcon, ChevronDownIcon } from "@webstudio-is/icons"; import { Popper, PopperContent, PopperAnchor } from "@radix-ui/react-popper"; -import { useCombobox, type UseComboboxGetItemPropsOptions } from "downshift"; +import { + DownshiftState, + UseComboboxStateChangeOptions, + useCombobox as useDownshiftCombobox, +} from "downshift"; import { matchSorter } from "match-sorter"; import { styled } from "../stitches.config"; import { IconButton } from "./icon-button"; @@ -17,41 +23,6 @@ import { TextField } from "./text-field"; import { Box } from "./box"; import { Grid } from "./grid"; -type Label = string; - -type BaseItem = { label: Label; disabled?: boolean } | Label; - -type ComboboxTextFieldProps = { - inputProps: ComponentProps; - toggleProps: ComponentProps; - highlightedItem?: Item; -}; - -const ComboboxTextFieldBase: ForwardRefRenderFunction< - HTMLDivElement, - ComboboxTextFieldProps -> = ({ inputProps, toggleProps }, ref) => { - return ( - - - - - - - ); -}; - -export const ComboboxTextField = forwardRef(ComboboxTextFieldBase); - -ComboboxTextField.displayName = "ComboboxTextField"; - const Listbox = styled("ul", panelStyles, { padding: 0, margin: 0, @@ -66,115 +37,110 @@ const ListboxItem = styled("li", itemCss, { margin: 0, }); -type ListProps = { - containerProps: ComponentProps; - items: Array; - getItemProps: ( - options: UseComboboxGetItemPropsOptions - ) => ComponentProps; - highlightedIndex: number; - selectedItem: Item | null; - itemToString: (item: Item | null) => string; -}; - -export const List = ({ - containerProps, - items, - getItemProps, - highlightedIndex, - selectedItem, - itemToString, -}: ListProps) => { +export const ListboxItemBase: ForwardRefRenderFunction< + HTMLLIElement, + ComponentProps & { + disabled?: boolean; + selected?: boolean; + highlighted?: boolean; + } +> = (props, ref) => { + const { disabled, selected, highlighted, children, ...rest } = props; return ( - - {items.map((item, index) => { - const itemProps: Record = getItemProps({ - item, - index, - key: index, - ...(typeof item === "object" && item.disabled - ? { "data-disabled": true, disabled: true } - : {}), - ...(highlightedIndex === index ? { "data-found": true } : {}), - }); - - return ( - // eslint-disable-next-line react/jsx-key - - - {selectedItem === item && } - {itemToString(item)} - - - ); - })} - + + + {selected && } + {children} + + ); }; +export const ComboboxListbox = Listbox; + +export const ComboboxListboxItem = forwardRef(ListboxItemBase); + +export const ComboboxPopper = Popper; + export const ComboboxPopperContent = PopperContent; -type ComboboxProps = { - name: string; - label?: string; +export const ComboboxPopperAnchor = PopperAnchor; + +const useFilter = ({ + items, + itemToString, +}: { items: Array; - value?: Item; - onItemSelect?: (value: Item) => void; - onItemHighlight?: (value?: Item) => void; - itemToString?: (item: Item | null) => string; - renderTextField?: ( - props: ComponentProps - ) => JSX.Element; - renderList?: (props: ListProps) => JSX.Element; - renderPopperContent?: ( - props: ComponentProps - ) => JSX.Element; + itemToString: (item: Item | null) => string; +}) => { + const [filteredItems, setFilteredItems] = useState>(items); + const cachedItems = useRef(items); + + useEffect(() => { + cachedItems.current = items; + }, [items]); + + const filter = useCallback( + (search?: string) => { + const foundItems: Array = matchSorter(items, search ?? "", { + keys: [(item) => itemToString(item)], + }); + setFilteredItems(foundItems); + }, + [itemToString, items] + ); + + const resetFilter = useCallback(() => { + setFilteredItems(cachedItems.current); + }, []); + + return { + filteredItems, + filter, + resetFilter, + }; }; -export const Combobox = ({ +type useComboboxProps = { + items: Array; + itemToString: (item: Item | null) => string; + value: Item | null; // This is to prevent: "downshift: A component has changed the uncontrolled prop "selectedItem" to be controlled." + onItemSelect?: (value: Item | null) => void; + onItemHighlight?: (value: Item | null) => void; + stateReducer?: ( + state: DownshiftState, + changes: UseComboboxStateChangeOptions + ) => Partial>; +}; + +export const useCombobox = ({ items, value, - name, - itemToString = (item) => - typeof item === "object" && item !== null && "label" in item - ? item.label - : item ?? "", + itemToString, onItemSelect, onItemHighlight, - renderTextField = (props) => , - // IMPORTANT! Without Item passed to list typescript is 10x slower! - renderList = (props) => {...props} />, - renderPopperContent = (props) => , -}: ComboboxProps) => { - const [foundItems, setFoundItems] = useState(items); - const { - isOpen, - getToggleButtonProps, - // getLabelProps, - getMenuProps, - getInputProps, - getComboboxProps, - highlightedIndex, - getItemProps, - selectedItem, - } = useCombobox({ - onInputValueChange({ inputValue }) { - if (inputValue) { - const options = - typeof items[0] === "object" && "label" in items[0] - ? { keys: ["label"] } - : undefined; - const foundItems = matchSorter(items, inputValue, options); - setFoundItems(foundItems); - } - }, - items: foundItems, - selectedItem: value, + stateReducer = (state, { changes }) => changes, +}: useComboboxProps) => { + const { filteredItems, filter, resetFilter } = useFilter({ + items, itemToString, + }); + + const downshiftProps = useDownshiftCombobox({ + items: filteredItems, + selectedItem: value, // Prevent downshift warning about switching controlled mode + stateReducer, + itemToString, + onInputValueChange({ inputValue }) { + filter(inputValue); + }, onSelectedItemChange({ selectedItem }) { - if (selectedItem) { - onItemSelect?.(selectedItem); - } + onItemSelect?.(selectedItem ?? null); }, onHighlightedIndexChange({ highlightedIndex }) { if (highlightedIndex !== undefined) { @@ -183,35 +149,97 @@ export const Combobox = ({ }, }); + const { isOpen, getItemProps, highlightedIndex, selectedItem } = + downshiftProps; + useEffect(() => { if (isOpen === false) { - setFoundItems(items); + resetFilter(); } - }, [isOpen, items]); + }, [isOpen, resetFilter]); - const inputProps: Record = getInputProps({ name }); - const toggleProps: Record = getToggleButtonProps(); - const comboboxProps: Record = getComboboxProps(); - const menuProps: Record = getMenuProps(); - const highlightedItem = foundItems[highlightedIndex]; + const enhancedGetItemProps = useCallback( + (options) => { + return getItemProps({ + highlighted: highlightedIndex === options.index, + // We need to either deep compare objects here or use itemToString to get primitive types + selected: itemToString(selectedItem) === itemToString(options.item), + key: options.id, + ...options, + }); + }, + [getItemProps, highlightedIndex, itemToString, selectedItem] + ); + return { + ...downshiftProps, + items: filteredItems, + getItemProps: enhancedGetItemProps, + }; +}; + +type ComboboxProps = useComboboxProps & { + name: string; + label?: string; + placeholder?: string; +}; + +export const Combobox = ({ + items, + value = null, + name, + placeholder, + itemToString, + onItemSelect, + onItemHighlight, +}: ComboboxProps) => { + const { + items: foundItems, + getInputProps, + getComboboxProps, + getToggleButtonProps, + getMenuProps, + getItemProps, + isOpen, + } = useCombobox({ + items, + value, + itemToString, + onItemSelect, + onItemHighlight, + }); return ( - - - {renderTextField({ inputProps, toggleProps, highlightedItem })} + + + + + + } + /> - {renderPopperContent({ - style: { zIndex: 1 }, - children: renderList({ - containerProps: menuProps, - items: isOpen ? foundItems : [], - getItemProps, - highlightedIndex, - selectedItem, - itemToString, - }), - })} + + + {isOpen && + foundItems.map((item, index) => { + return ( + // eslint-disable-next-line react/jsx-key + + {itemToString(item)} + + ); + })} + + ); diff --git a/packages/design-system/src/components/menu.tsx b/packages/design-system/src/components/menu.tsx index 903c3c77b4..0019f338ad 100644 --- a/packages/design-system/src/components/menu.tsx +++ b/packages/design-system/src/components/menu.tsx @@ -10,7 +10,7 @@ export const baseItemCss = css({ display: "flex", alignItems: "center", justifyContent: "space-between", - fontFamily: "$untitled", + fontFamily: "$sans", fontSize: "$1", fontVariantNumeric: "tabular-nums", lineHeight: "1", @@ -24,12 +24,12 @@ export const baseItemCss = css({ export const itemCss = css(baseItemCss, { position: "relative", color: "$hiContrast", - "&:focus, &[data-found]": { + "&:focus, &[data-found], &[aria-selected=true]": { outline: "none", backgroundColor: "$blue9", color: "white", }, - "&[data-disabled]": { + "&[data-disabled], &[aria-disabled]": { color: "$slate9", }, }); diff --git a/packages/design-system/src/components/text-field.stories.tsx b/packages/design-system/src/components/text-field.stories.tsx new file mode 100644 index 0000000000..f49a1c0adb --- /dev/null +++ b/packages/design-system/src/components/text-field.stories.tsx @@ -0,0 +1,226 @@ +import { expect } from "@storybook/jest"; +import { userEvent, waitFor, within } from "@storybook/testing-library"; +import React from "react"; +import { ComponentStory } from "@storybook/react"; +import { RowGapIcon, ChevronDownIcon } from "@webstudio-is/icons"; +import { Button } from "./button"; +import { Flex } from "./flex"; +import { IconButton } from "./icon-button"; +import { TextField } from "./text-field"; +import { Box } from "./box"; +import { Grid } from "./grid"; +import { Text } from "./text"; + +export default { + component: TextField, + argTypes: { + onFocus: { action: true }, + onBlur: { action: true }, + }, +}; + +export const Default: ComponentStory = () => { + return ; +}; + +export const NativeProps: ComponentStory = () => { + return ( + + + + + + + + + ); +}; + +export const Variants: ComponentStory = () => { + return ( + + + + + ); +}; + +export const State: ComponentStory = () => { + return ( + + + + + + ); +}; + +export const PrefixSuffix: ComponentStory = () => { + return ( + + } + suffix={ + + + + } + /> + } + suffix={ + + + + } + /> + } + suffix={ + + + + } + /> + + ); +}; + +export const Layout: ComponentStory = () => { + return ( + <> + + } + suffix={ + + + + } + css={{ + flexGrow: 1, + maxWidth: "25%", + }} + /> + Content + + + + + + + This is a label + + + + } + /> + + + + This is a label + + + + + + ); +}; + +export const Interactive: ComponentStory = () => { + const [value, setValue] = React.useState(""); + const wrapperRef = React.useRef(null); + const inputRef = React.useRef(null); + + return ( + + + + + + ); +}; + +export const FocusEvents: ComponentStory = (args) => { + return ( + + ); +}; +FocusEvents.play = async ({ args, canvasElement }) => { + const canvas = within(canvasElement); + + const el = canvas.getByLabelText("Focus and blur:"); + + await userEvent.tab(); + await waitFor(() => expect(el).toHaveFocus()); + await userEvent.tab(); + await waitFor(() => expect(args.onBlur).not.toHaveBeenCalled()); + await userEvent.tab(); + await waitFor(() => expect(args.onBlur).toHaveBeenCalled()); + + await waitFor(() => expect(args.onFocus).toHaveBeenCalledTimes(1)); + await waitFor(() => expect(args.onBlur).toHaveBeenCalledTimes(1)); +}; + +export const ClickCapture: ComponentStory = (args) => { + return ( + + +
+ } + suffix={ + + + + } + {...args} + /> + ); +}; +ClickCapture.play = async ({ canvasElement }) => { + const canvas = within(canvasElement); + const input = canvas.getByPlaceholderText("Click on the icon to focus input"); + await userEvent.click(canvas.getByTitle("icon")); + await waitFor(() => expect(input).toHaveFocus()); + await userEvent.click(canvas.getByRole("button")); + await waitFor(() => expect(input).toHaveFocus()); +}; diff --git a/packages/design-system/src/components/text-field.tsx b/packages/design-system/src/components/text-field.tsx index 440e4ffb2f..d84988cee5 100644 --- a/packages/design-system/src/components/text-field.tsx +++ b/packages/design-system/src/components/text-field.tsx @@ -1,64 +1,39 @@ +import { mergeRefs } from "@react-aria/utils"; +import React from "react"; +import { useFocusWithin } from "@react-aria/interactions"; import { styled } from "../stitches.config"; +import { Flex } from "./flex"; -export const TextField = styled("input", { +const InputBase = styled("input", { // Reset appearance: "none", borderWidth: "0", + backgroundColor: "transparent", boxSizing: "border-box", fontFamily: "inherit", + fontSize: "inherit", + color: "inherit", margin: "0", - outline: "none", padding: "0", + flexGrow: 1, + flexShrink: 1, + minWidth: 0, width: "100%", + textOverflow: "ellipsis", + outline: "none", WebkitTapHighlightColor: "rgba(0,0,0,0)", - "&::before": { - boxSizing: "border-box", - }, - "&::after": { - boxSizing: "border-box", + + // Focus should start on the input element then move to prefix and suffix elements. + // DOM order reflects focus path and visually we use order to put them into the correct visual order. + order: 1, + + '&[type="search"]': { + "&::-webkit-search-decoration, &::-webkit-search-cancel-button, &::-webkit-search-results-button, &::-webkit-search-results-decoration": + { + display: "none", + }, }, - // Custom - backgroundColor: "$loContrast", - boxShadow: "inset 0 0 0 1px $colors$slate7", - color: "$hiContrast", - fontVariantNumeric: "tabular-nums", - - "&:-webkit-autofill": { - boxShadow: "inset 0 0 0 1px $colors$blue6, inset 0 0 0 100px $colors$blue3", - }, - - "&:-webkit-autofill::first-line": { - fontFamily: "$untitled", - color: "$hiContrast", - }, - - "&:focus": { - boxShadow: - "inset 0px 0px 0px 1px $colors$blue8, 0px 0px 0px 1px $colors$blue8", - "&:-webkit-autofill": { - boxShadow: - "inset 0px 0px 0px 1px $colors$blue8, 0px 0px 0px 1px $colors$blue8, inset 0 0 0 100px $colors$blue3", - }, - }, - "&::placeholder": { - color: "$slate9", - }, - "&:disabled": { - pointerEvents: "none", - backgroundColor: "$slate2", - color: "$slate8", - cursor: "not-allowed", - "&::placeholder": { - color: "$slate7", - }, - }, - "&:read-only": { - backgroundColor: "$slate2", - "&:focus": { - boxShadow: "inset 0px 0px 0px 1px $colors$slate7", - }, - }, '&[type="number"]': { "&::-webkit-outer-spin-button, &::-webkit-inner-spin-button": { WebkitAppearance: "none", @@ -68,29 +43,64 @@ export const TextField = styled("input", { }, }, - variants: { - size: { - "1": { - borderRadius: "$1", - height: "$5", - fontSize: "$1", - px: "$1", - lineHeight: "$sizes$5", - "&:-webkit-autofill::first-line": { - fontSize: "$1", - }, - }, - "2": { - borderRadius: "$2", - height: "$6", - fontSize: "$3", - px: "$2", - lineHeight: "$sizes$6", - "&:-webkit-autofill::first-line": { - fontSize: "$3", - }, - }, + "&:-webkit-autofill::first-line": { + fontFamily: "$sans", + color: "$hiContrast", + }, + + "&::placeholder": { + color: "$slate9", + }, + + "&:disabled": { + color: "$slate8", + cursor: "not-allowed", + "&::placeholder": { + color: "$slate7", }, + }, +}); + +const TextFieldBase = styled("div", { + // Custom + display: "flex", + gap: "$1", + backgroundColor: "$loContrast", + boxShadow: "inset 0 0 0 1px $colors$slate7", + color: "$hiContrast", + fontVariantNumeric: "tabular-nums", + + px: "$2", + borderRadius: "$1", + fontFamily: "$sans", + fontSize: "$1", + height: 28, // @todo waiting for the sizing scale + lineHeight: 1, + "&[data-has-prefix]": { + paddingLeft: 2, + }, + + "&[data-has-suffix]": { + paddingRight: 2, + }, + + "&:focus-within": { + boxShadow: + "inset 0px 0px 0px 1px $colors$blue10, 0px 0px 0px 1px $colors$blue10", + }, + + "&[aria-disabled=true]": { + pointerEvents: "none", + backgroundColor: "$slate2", + }, + "&:has(input:read-only)": { + backgroundColor: "$slate2", + "&:focus": { + boxShadow: "inset 0px 0px 0px 1px $colors$slate7", + }, + }, + + variants: { variant: { ghost: { boxShadow: "none", @@ -103,7 +113,7 @@ export const TextField = styled("input", { "&:focus": { backgroundColor: "$loContrast", boxShadow: - "inset 0px 0px 0px 1px $colors$blue8, 0px 0px 0px 1px $colors$blue8", + "inset 0px 0px 0px 1px $colors$blue8, 0px 0px 0px 1px $colors$blue10", }, "&:disabled": { backgroundColor: "transparent", @@ -129,19 +139,97 @@ export const TextField = styled("input", { }, }, }, - cursor: { - default: { - cursor: "default", - "&:focus": { - cursor: "text", - }, - }, - text: { - cursor: "text", - }, - }, - }, - defaultVariants: { - size: "1", }, }); + +type TextFieldProps = Pick< + React.ComponentProps, + "variant" | "state" | "css" +> & + Omit, "prefix" | "children"> & { + inputRef?: React.Ref; + prefix?: React.ReactNode; + suffix?: React.ReactNode; + }; + +export const TextField = React.forwardRef( + (props, forwardedRef) => { + const { + prefix, + suffix, + css, + disabled, + inputRef, + state, + variant, + onFocus, + onBlur, + ...textFieldProps + } = props; + + const internalInputRef = React.useRef(null); + + const focusInnerInput = React.useCallback(() => { + internalInputRef.current?.focus(); + }, [internalInputRef]); + + const { focusWithinProps } = useFocusWithin({ + isDisabled: disabled, + onFocusWithin: (event) => { + // @ts-expect-error Type mismatch from react-aria + onFocus?.(event); + }, + onBlurWithin: (event) => { + // @ts-expect-error Type mismatch from react-aria + onBlur?.(event); + }, + }); + + return ( + + {/* We want input to be the first element in DOM so it receives the focus first */} + + + {prefix && ( + + {prefix} + + )} + + {suffix && ( + + {suffix} + + )} + + ); + } +); + +TextField.displayName = "TextField"; diff --git a/yarn.lock b/yarn.lock index b0fe4f9694..100c3d98b2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@adobe/css-tools@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.0.1.tgz#b38b444ad3aa5fedbb15f2f746dcd934226a12dd" + integrity sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g== + "@ampproject/remapping@^2.1.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" @@ -1979,7 +1984,7 @@ dependencies: regenerator-runtime "^0.13.2" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.14.8", "@babel/runtime@^7.17.8", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.0", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.14.8", "@babel/runtime@^7.17.8", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.0", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": version "7.19.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.0.tgz#22b11c037b094d27a8a2504ea4dcff00f50e2259" integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA== @@ -2336,6 +2341,13 @@ "@types/node" "*" jest-mock "^27.5.1" +"@jest/expect-utils@^29.1.2": + version "29.1.2" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.1.2.tgz#66dbb514d38f7d21456bc774419c9ae5cca3f88d" + integrity sha512-4a48bhKfGj/KAH39u0ppzNTABXQ8QPccWAFUFobWBaEMSMp+sB31Z2fK/l47c4a/Mu1po2ffmfAIPxXbVTXdtg== + dependencies: + jest-get-type "^29.0.0" + "@jest/fake-timers@^27.5.1": version "27.5.1" resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.5.1.tgz#76979745ce0579c8a94a4678af7a748eda8ada74" @@ -2388,6 +2400,13 @@ terminal-link "^2.0.0" v8-to-istanbul "^8.1.0" +"@jest/schemas@^29.0.0": + version "29.0.0" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.0.0.tgz#5f47f5994dd4ef067fb7b4188ceac45f77fe952a" + integrity sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA== + dependencies: + "@sinclair/typebox" "^0.24.1" + "@jest/source-map@^27.5.1": version "27.5.1" resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-27.5.1.tgz#6608391e465add4205eae073b55e7f279e04e8cf" @@ -2481,6 +2500,18 @@ "@types/yargs" "^16.0.0" chalk "^4.0.0" +"@jest/types@^29.1.2": + version "29.1.2" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.1.2.tgz#7442d32b16bcd7592d9614173078b8c334ec730a" + integrity sha512-DcXGtoTykQB5jiwCmVr8H4vdg2OJhQex3qPkG+ISyDO7xQXbt/4R6dowcRyPemRnkH7JoHvZuxPBdlq+9JxFCg== + dependencies: + "@jest/schemas" "^29.0.0" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + "@jridgewell/gen-mapping@^0.1.0": version "0.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" @@ -3401,6 +3432,45 @@ dependencies: "@babel/runtime" "^7.13.10" +"@react-aria/interactions@^3.11.0": + version "3.11.0" + resolved "https://registry.yarnpkg.com/@react-aria/interactions/-/interactions-3.11.0.tgz#aa6118af58ff443670152393edab97e403d6d359" + integrity sha512-ZjK4m8u6FlV7Q9/1h9P2Ii6j/NwKR3BmTeGeBQssS2i4dV2pJeOPePnGzVQQGG8FzGQ+TcNRvZPXKaU4AlnBjw== + dependencies: + "@babel/runtime" "^7.6.2" + "@react-aria/utils" "^3.13.3" + "@react-types/shared" "^3.14.1" + +"@react-aria/ssr@^3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@react-aria/ssr/-/ssr-3.3.0.tgz#25e81daf0c7a270a4a891159d8d984578e4512d8" + integrity sha512-yNqUDuOVZIUGP81R87BJVi/ZUZp/nYOBXbPsRe7oltJOfErQZD+UezMpw4vM2KRz18cURffvmC8tJ6JTeyDtaQ== + dependencies: + "@babel/runtime" "^7.6.2" + +"@react-aria/utils@^3.13.3": + version "3.13.3" + resolved "https://registry.yarnpkg.com/@react-aria/utils/-/utils-3.13.3.tgz#1b27912e4630f0db6a7b39eb1013f6c4f710075c" + integrity sha512-wqjGNFX4TrXriUU1gvCaoqRhuckdoYogUWN0iyQRkTmzvb7H/NNzQzHou5ggWAdts/NzJUInwKarBWM9hCZZbg== + dependencies: + "@babel/runtime" "^7.6.2" + "@react-aria/ssr" "^3.3.0" + "@react-stately/utils" "^3.5.1" + "@react-types/shared" "^3.14.1" + clsx "^1.1.1" + +"@react-stately/utils@^3.5.1": + version "3.5.1" + resolved "https://registry.yarnpkg.com/@react-stately/utils/-/utils-3.5.1.tgz#502de762e5d33e892347c5f58053674e06d3bc92" + integrity sha512-INeQ5Er2Jm+db8Py4upKBtgfzp3UYgwXYmbU/XJn49Xw27ktuimH9e37qP3bgHaReb5L3g8IrGs38tJUpnGPHA== + dependencies: + "@babel/runtime" "^7.6.2" + +"@react-types/shared@^3.14.1": + version "3.14.1" + resolved "https://registry.yarnpkg.com/@react-types/shared/-/shared-3.14.1.tgz#8fe25f729426e8043054e442eb5392364200e028" + integrity sha512-yPPgVRWWanXqbdxFTgJmVwx0JlcnEK3dqkKDIbVk6mxAHvEESI9+oDnHvO8IMHqF+GbrTCzVtAs0zwhYI/uHJA== + "@remix-run/dev@^1.6.4": version "1.6.4" resolved "https://registry.yarnpkg.com/@remix-run/dev/-/dev-1.6.4.tgz#7a2a7fc24e66434c5cf64d6e79a62d35d09b73de" @@ -3701,6 +3771,11 @@ dependencies: "@sentry/cli" "^1.74.4" +"@sinclair/typebox@^0.24.1": + version "0.24.44" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.44.tgz#0a0aa3bf4a155a678418527342a3ee84bd8caa5c" + integrity sha512-ka0W0KN5i6LfrSocduwliMMpqVgohtPFidKdMEOUjoOFCHcOOYkKsPRxfs5f15oPNHTm6ERAm0GV/+/LTKeiWg== + "@sindresorhus/is@^4.0.0": version "4.6.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" @@ -3847,6 +3922,27 @@ regenerator-runtime "^0.13.7" ts-dedent "^2.0.0" +"@storybook/addon-interactions@^6.5.12": + version "6.5.12" + resolved "https://registry.yarnpkg.com/@storybook/addon-interactions/-/addon-interactions-6.5.12.tgz#b9b7ae50de4c41ea2c9023dcaacf3f318a072ea4" + integrity sha512-wAkKqUX57qZMzgykMIltjDrFfA0II+pKz2SchK4hXHK3/2rtOe+Ah0Sj5nTQo++Ee5oC1PVZWIq/Y1kufCTBJQ== + dependencies: + "@devtools-ds/object-inspector" "^1.1.2" + "@storybook/addons" "6.5.12" + "@storybook/api" "6.5.12" + "@storybook/client-logger" "6.5.12" + "@storybook/components" "6.5.12" + "@storybook/core-common" "6.5.12" + "@storybook/core-events" "6.5.12" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/instrumenter" "6.5.12" + "@storybook/theming" "6.5.12" + core-js "^3.8.2" + global "^4.4.0" + jest-mock "^27.0.6" + polished "^4.2.2" + ts-dedent "^2.2.0" + "@storybook/addon-interactions@^6.5.6", "@storybook/addon-interactions@^6.5.9": version "6.5.9" resolved "https://registry.yarnpkg.com/@storybook/addon-interactions/-/addon-interactions-6.5.9.tgz#4356e96beae8f44000955d8870c3acc6c8d1fb3a" @@ -3946,6 +4042,23 @@ prop-types "^15.7.2" regenerator-runtime "^0.13.7" +"@storybook/addons@6.5.12": + version "6.5.12" + resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-6.5.12.tgz#891767b5f88ea99b956cf19e9e2893594068adc7" + integrity sha512-y3cgxZq41YGnuIlBJEuJjSFdMsm8wnvlNOGUP9Q+Er2dgfx8rJz4Q22o4hPjpvpaj4XdBtxCJXI2NeFpN59+Cw== + dependencies: + "@storybook/api" "6.5.12" + "@storybook/channels" "6.5.12" + "@storybook/client-logger" "6.5.12" + "@storybook/core-events" "6.5.12" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/router" "6.5.12" + "@storybook/theming" "6.5.12" + "@types/webpack-env" "^1.16.0" + core-js "^3.8.2" + global "^4.4.0" + regenerator-runtime "^0.13.7" + "@storybook/addons@6.5.9": version "6.5.9" resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-6.5.9.tgz#5a9d7395c579a9cbc44dfc122362fb3c95dfb9d5" @@ -3963,6 +4076,29 @@ global "^4.4.0" regenerator-runtime "^0.13.7" +"@storybook/api@6.5.12": + version "6.5.12" + resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.5.12.tgz#7cc82087fc9298be03f15bf4ab9c4aab294b3bac" + integrity sha512-DuUZmMlQxkFNU9Vgkp9aNfCkAongU76VVmygvCuSpMVDI9HQ2lG0ydL+ppL4XKoSMCCoXTY6+rg4hJANnH+1AQ== + dependencies: + "@storybook/channels" "6.5.12" + "@storybook/client-logger" "6.5.12" + "@storybook/core-events" "6.5.12" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/router" "6.5.12" + "@storybook/semver" "^7.3.2" + "@storybook/theming" "6.5.12" + core-js "^3.8.2" + fast-deep-equal "^3.1.3" + global "^4.4.0" + lodash "^4.17.21" + memoizerific "^1.11.3" + regenerator-runtime "^0.13.7" + store2 "^2.12.0" + telejson "^6.0.8" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + "@storybook/api@6.5.9": version "6.5.9" resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.5.9.tgz#303733214c9de0422d162f7c54ae05d088b89bf9" @@ -4107,6 +4243,15 @@ global "^4.4.0" telejson "^6.0.8" +"@storybook/channels@6.5.12": + version "6.5.12" + resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-6.5.12.tgz#98baf01691d263e2ac341853361ec69c1a6621bc" + integrity sha512-X5XaKbe4b7LXJ4sUakBo00x6pXnW78JkOonHoaKoWsccHLlEzwfBZpVVekhVZnqtCoLT23dB8wjKgA71RYWoiw== + dependencies: + core-js "^3.8.2" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + "@storybook/channels@6.5.9": version "6.5.9" resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-6.5.9.tgz#abfab89a6587a2688e9926d4aafeb11c9d8b2e79" @@ -4142,6 +4287,14 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" +"@storybook/client-logger@6.5.12": + version "6.5.12" + resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-6.5.12.tgz#d9809e13dc7939eb61452a5e94b1ccb61c4a022c" + integrity sha512-IrkMr5KZcudX935/C2balFbxLHhkvQnJ78rbVThHDVckQ7l3oIXTh66IMzldeOabVFDZEMiW8AWuGEYof+JtLw== + dependencies: + core-js "^3.8.2" + global "^4.4.0" + "@storybook/client-logger@6.5.9", "@storybook/client-logger@^6.4.0", "@storybook/client-logger@^6.4.0 || >=6.5.0-0": version "6.5.9" resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-6.5.9.tgz#dc1669abe8c45af1cc38f74c6f4b15ff33e63014" @@ -4150,6 +4303,20 @@ core-js "^3.8.2" global "^4.4.0" +"@storybook/components@6.5.12": + version "6.5.12" + resolved "https://registry.yarnpkg.com/@storybook/components/-/components-6.5.12.tgz#e137f0683ea92e22de116bfa62cfd65ce4efe01d" + integrity sha512-NAAGl5PDXaHdVLd6hA+ttmLwH3zAVGXeUmEubzKZ9bJzb+duhFKxDa9blM4YEkI+palumvgAMm0UgS7ou680Ig== + dependencies: + "@storybook/client-logger" "6.5.12" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/theming" "6.5.12" + core-js "^3.8.2" + memoizerific "^1.11.3" + qs "^6.10.0" + regenerator-runtime "^0.13.7" + util-deprecate "^1.0.2" + "@storybook/components@6.5.9": version "6.5.9" resolved "https://registry.yarnpkg.com/@storybook/components/-/components-6.5.9.tgz#97e07ffe11ab76c01ccee380888991bd161f75b2" @@ -4192,6 +4359,62 @@ unfetch "^4.2.0" util-deprecate "^1.0.2" +"@storybook/core-common@6.5.12": + version "6.5.12" + resolved "https://registry.yarnpkg.com/@storybook/core-common/-/core-common-6.5.12.tgz#9f8d5cb3812382c49c84dcfb4279a39e228a1b83" + integrity sha512-gG20+eYdIhwQNu6Xs805FLrOCWtkoc8Rt8gJiRt8yXzZh9EZkU4xgCRoCxrrJ03ys/gTiCFbBOfRi749uM3z4w== + dependencies: + "@babel/core" "^7.12.10" + "@babel/plugin-proposal-class-properties" "^7.12.1" + "@babel/plugin-proposal-decorators" "^7.12.12" + "@babel/plugin-proposal-export-default-from" "^7.12.1" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.12.1" + "@babel/plugin-proposal-object-rest-spread" "^7.12.1" + "@babel/plugin-proposal-optional-chaining" "^7.12.7" + "@babel/plugin-proposal-private-methods" "^7.12.1" + "@babel/plugin-proposal-private-property-in-object" "^7.12.1" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-transform-arrow-functions" "^7.12.1" + "@babel/plugin-transform-block-scoping" "^7.12.12" + "@babel/plugin-transform-classes" "^7.12.1" + "@babel/plugin-transform-destructuring" "^7.12.1" + "@babel/plugin-transform-for-of" "^7.12.1" + "@babel/plugin-transform-parameters" "^7.12.1" + "@babel/plugin-transform-shorthand-properties" "^7.12.1" + "@babel/plugin-transform-spread" "^7.12.1" + "@babel/preset-env" "^7.12.11" + "@babel/preset-react" "^7.12.10" + "@babel/preset-typescript" "^7.12.7" + "@babel/register" "^7.12.1" + "@storybook/node-logger" "6.5.12" + "@storybook/semver" "^7.3.2" + "@types/node" "^14.0.10 || ^16.0.0" + "@types/pretty-hrtime" "^1.0.0" + babel-loader "^8.0.0" + babel-plugin-macros "^3.0.1" + babel-plugin-polyfill-corejs3 "^0.1.0" + chalk "^4.1.0" + core-js "^3.8.2" + express "^4.17.1" + file-system-cache "^1.0.5" + find-up "^5.0.0" + fork-ts-checker-webpack-plugin "^6.0.4" + fs-extra "^9.0.1" + glob "^7.1.6" + handlebars "^4.7.7" + interpret "^2.2.0" + json5 "^2.1.3" + lazy-universal-dotenv "^3.0.1" + picomatch "^2.3.0" + pkg-dir "^5.0.0" + pretty-hrtime "^1.0.3" + resolve-from "^5.0.0" + slash "^3.0.0" + telejson "^6.0.8" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + webpack "4" + "@storybook/core-common@6.5.9": version "6.5.9" resolved "https://registry.yarnpkg.com/@storybook/core-common/-/core-common-6.5.9.tgz#7ca8258ea2634b1d64695c1e4262f71cc7457989" @@ -4248,6 +4471,13 @@ util-deprecate "^1.0.2" webpack "4" +"@storybook/core-events@6.5.12": + version "6.5.12" + resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-6.5.12.tgz#28bd727cc4216012409bfac412fcb708346c56bc" + integrity sha512-0AMyMM19R/lHsYRfWqM8zZTXthasTAK2ExkSRzYi2GkIaVMxRKtM33YRwxKIpJ6KmIKIs8Ru3QCXu1mfCmGzNg== + dependencies: + core-js "^3.8.2" + "@storybook/core-events@6.5.9": version "6.5.9" resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-6.5.9.tgz#5b0783c7d22a586c0f5e927a61fe1b1223e19637" @@ -4361,6 +4591,24 @@ lodash "^4.17.21" regenerator-runtime "^0.13.7" +"@storybook/expect@storybook-jest": + version "27.5.2-0" + resolved "https://registry.yarnpkg.com/@storybook/expect/-/expect-27.5.2-0.tgz#131fc4336106805ccd100809a3c2852d31f1da68" + integrity sha512-cP99mhWN/JeCp7VSIiymvj5tmuMY050iFohvp8Zq+kewKsBSZ6/qpTJAGCCZk6pneTcp4S0Fm5BSqyxzbyJ3gw== + dependencies: + "@types/jest" ">=26.0.0" + +"@storybook/instrumenter@6.5.12": + version "6.5.12" + resolved "https://registry.yarnpkg.com/@storybook/instrumenter/-/instrumenter-6.5.12.tgz#acd721c50b65183a53fb5913fe7cdb35b06e2433" + integrity sha512-VMl+Yg6ab79A1VqlhC69sBFI6ySw4Lganqdf9JJf6Y7uNRcZwsjfGpGfYiL4WGH1q72tGiq/25XKGIJV0jgi1Q== + dependencies: + "@storybook/addons" "6.5.12" + "@storybook/client-logger" "6.5.12" + "@storybook/core-events" "6.5.12" + core-js "^3.8.2" + global "^4.4.0" + "@storybook/instrumenter@6.5.9", "@storybook/instrumenter@^6.4.0", "@storybook/instrumenter@^6.4.0 || >=6.5.0-0": version "6.5.9" resolved "https://registry.yarnpkg.com/@storybook/instrumenter/-/instrumenter-6.5.9.tgz#885d9dec31b7b7fa6ea29b446105480450e527b8" @@ -4372,6 +4620,16 @@ core-js "^3.8.2" global "^4.4.0" +"@storybook/jest@^0.0.10": + version "0.0.10" + resolved "https://registry.yarnpkg.com/@storybook/jest/-/jest-0.0.10.tgz#4fbc43ebc3ae6cce8232d6bd9b56ef710f481e52" + integrity sha512-qeYLIplpcOUQXboJde5pRCjTvkGmF80jEszRUoNYCNcEPfC2sMK68Wq6Ct8EQj3CoEdJqsK54O2YYh+7D9S+ag== + dependencies: + "@storybook/expect" storybook-jest + "@storybook/instrumenter" "^6.4.0" + "@testing-library/jest-dom" "^5.16.2" + jest-mock "^27.3.0" + "@storybook/manager-webpack4@6.5.9", "@storybook/manager-webpack4@^6.5.6", "@storybook/manager-webpack4@^6.5.9": version "6.5.9" resolved "https://registry.yarnpkg.com/@storybook/manager-webpack4/-/manager-webpack4-6.5.9.tgz#c75d2cced4550c8a786f00b0e57b203d613e706c" @@ -4468,6 +4726,17 @@ prettier ">=2.2.1 <=2.3.0" ts-dedent "^2.0.0" +"@storybook/node-logger@6.5.12": + version "6.5.12" + resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-6.5.12.tgz#0f9efcd1a37c7aae493b22fe33cacca87c135b9b" + integrity sha512-jdLtT3mX5GQKa+0LuX0q0sprKxtCGf6HdXlKZGD5FEuz4MgJUGaaiN0Hgi+U7Z4tVNOtSoIbYBYXHqfUgJrVZw== + dependencies: + "@types/npmlog" "^4.1.2" + chalk "^4.1.0" + core-js "^3.8.2" + npmlog "^5.0.1" + pretty-hrtime "^1.0.3" + "@storybook/node-logger@6.5.9": version "6.5.9" resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-6.5.9.tgz#129cfe0d0f79cab4f6a2ba194d39516680b1626f" @@ -4562,6 +4831,17 @@ util-deprecate "^1.0.2" webpack ">=4.43.0 <6.0.0" +"@storybook/router@6.5.12": + version "6.5.12" + resolved "https://registry.yarnpkg.com/@storybook/router/-/router-6.5.12.tgz#58efbc1f2f301c8584802af1c710b2f6f03f948c" + integrity sha512-xHubde9YnBbpkDY5+zGO4Pr6VPxP8H9J2v4OTF3H82uaxCIKR0PKG0utS9pFKIsEiP3aM62Hb9qB8nU+v1nj3w== + dependencies: + "@storybook/client-logger" "6.5.12" + core-js "^3.8.2" + memoizerific "^1.11.3" + qs "^6.10.0" + regenerator-runtime "^0.13.7" + "@storybook/router@6.5.9": version "6.5.9" resolved "https://registry.yarnpkg.com/@storybook/router/-/router-6.5.9.tgz#4740248f8517425b2056273fb366ace8a17c65e8" @@ -4658,6 +4938,16 @@ "@testing-library/user-event" "^13.2.1" ts-dedent "^2.2.0" +"@storybook/theming@6.5.12": + version "6.5.12" + resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.5.12.tgz#7df1b52913d49c5e84fc1f2e837c02d9fa8cc639" + integrity sha512-uWOo84qMQ2R6c1C0faZ4Q0nY01uNaX7nXoJKieoiJ6ZqY9PSYxJl1kZLi3uPYnrxLZjzjVyXX8MgdxzbppYItA== + dependencies: + "@storybook/client-logger" "6.5.12" + core-js "^3.8.2" + memoizerific "^1.11.3" + regenerator-runtime "^0.13.7" + "@storybook/theming@6.5.9": version "6.5.9" resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.5.9.tgz#13f60a3a3cd73ceb5caf9f188e1627e79f1891aa" @@ -4716,6 +5006,21 @@ lz-string "^1.4.4" pretty-format "^27.0.2" +"@testing-library/jest-dom@^5.16.2": + version "5.16.5" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz#3912846af19a29b2dbf32a6ae9c31ef52580074e" + integrity sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA== + dependencies: + "@adobe/css-tools" "^4.0.1" + "@babel/runtime" "^7.9.2" + "@types/testing-library__jest-dom" "^5.9.1" + aria-query "^5.0.0" + chalk "^3.0.0" + css.escape "^1.5.1" + dom-accessibility-api "^0.5.6" + lodash "^4.17.15" + redent "^3.0.0" + "@testing-library/react-hooks@^8.0.1": version "8.0.1" resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz#0924bbd5b55e0c0c0502d1754657ada66947ca12" @@ -4983,6 +5288,14 @@ dependencies: "@types/istanbul-lib-report" "*" +"@types/jest@*", "@types/jest@>=26.0.0": + version "29.1.1" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.1.1.tgz#cf21a0835a1ba9a30ea1966019f1261c6a114c92" + integrity sha512-U9Ey07dGWl6fUFaIaUQUKWG5NoKi/zizeVQCGV8s4nSU0jPgqphVZvS64+8BtWYvrc3ZGw6wo943NSYPxkrp/g== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + "@types/jest@^27.4.0", "@types/jest@^27.4.1": version "27.5.2" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.5.2.tgz#ec49d29d926500ffb9fd22b84262e862049c026c" @@ -5236,6 +5549,13 @@ resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.8.tgz#b94a4391c85666c7b73299fd3ad79d4faa435310" integrity sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ== +"@types/testing-library__jest-dom@^5.9.1": + version "5.14.5" + resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz#d113709c90b3c75fdb127ec338dad7d5f86c974f" + integrity sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ== + dependencies: + "@types/jest" "*" + "@types/uglify-js@*": version "3.16.0" resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.16.0.tgz#2cf74a0e6ebb6cd54c0d48e509d5bd91160a9602" @@ -5303,6 +5623,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yargs@^17.0.8": + version "17.0.13" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.13.tgz#34cced675ca1b1d51fcf4d34c3c6f0fa142a5c76" + integrity sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg== + dependencies: + "@types/yargs-parser" "*" + "@typescript-eslint/eslint-plugin@^5.36.2": version "5.36.2" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.36.2.tgz#6df092a20e0f9ec748b27f293a12cb39d0c1fe4d" @@ -7051,6 +7378,14 @@ chalk@^2.0.0, chalk@^2.4.1: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -7330,7 +7665,7 @@ clsx@1.1.0: resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.0.tgz#62937c6adfea771247c34b54d320fb99624f5702" integrity sha512-3avwM37fSK5oP6M5rQ9CNe99lwxhXDOeSWVPAOYF6OazUTgZCMb0yWlJpmdD74REy1gkEaFiub2ULv4fq9GUhA== -clsx@^1.0.4: +clsx@^1.0.4, clsx@^1.1.1: version "1.2.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== @@ -7839,6 +8174,11 @@ css-what@^6.0.1: resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== +css.escape@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== + cssesc@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" @@ -8121,6 +8461,11 @@ diff-sequences@^27.5.1: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== +diff-sequences@^29.0.0: + version "29.0.0" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.0.0.tgz#bae49972ef3933556bcb0800b72e8579d19d9e4f" + integrity sha512-7Qe/zd1wxSDL4D/X/FPjOMB+ZMDt71W94KYaq05I2l0oQqgXgs7s4ftYYmV38gBSrPz2vcygxfs1xn0FT+rKNA== + diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" @@ -8168,7 +8513,7 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dom-accessibility-api@^0.5.9: +dom-accessibility-api@^0.5.6, dom-accessibility-api@^0.5.9: version "0.5.14" resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz#56082f71b1dc7aac69d83c4285eef39c15d93f56" integrity sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg== @@ -9397,6 +9742,17 @@ expect@^27.5.1: jest-matcher-utils "^27.5.1" jest-message-util "^27.5.1" +expect@^29.0.0: + version "29.1.2" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.1.2.tgz#82f8f28d7d408c7c68da3a386a490ee683e1eced" + integrity sha512-AuAGn1uxva5YBbBlXb+2JPxJRuemZsmlGcapPXWNSBNsQtAULfjioREGBWuI0EOvYUKjDnrCy8PW5Zlr1md5mw== + dependencies: + "@jest/expect-utils" "^29.1.2" + jest-get-type "^29.0.0" + jest-matcher-utils "^29.1.2" + jest-message-util "^29.1.2" + jest-util "^29.1.2" + express@4.17.3: version "4.17.3" resolved "https://registry.yarnpkg.com/express/-/express-4.17.3.tgz#f6c7302194a4fb54271b73a1fe7a06478c8f85a1" @@ -11615,6 +11971,16 @@ jest-diff@^27.5.1: jest-get-type "^27.5.1" pretty-format "^27.5.1" +jest-diff@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.1.2.tgz#bb7aaf5353227d6f4f96c5e7e8713ce576a607dc" + integrity sha512-4GQts0aUopVvecIT4IwD/7xsBaMhKTYoM4/njE/aVw9wpw+pIUVp8Vab/KnSzSilr84GnLBkaP3JLDnQYCKqVQ== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.0.0" + jest-get-type "^29.0.0" + pretty-format "^29.1.2" + jest-docblock@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.5.1.tgz#14092f364a42c6108d42c33c8cf30e058e25f6c0" @@ -11663,6 +12029,11 @@ jest-get-type@^27.5.1: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== +jest-get-type@^29.0.0: + version "29.0.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.0.0.tgz#843f6c50a1b778f7325df1129a0fd7aa713aef80" + integrity sha512-83X19z/HuLKYXYHskZlBAShO7UfLFXu/vWajw9ZNJASN32li8yHMaVGAQqxFW1RCFOkB7cubaL6FaJVQqqJLSw== + jest-haste-map@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa" @@ -11745,6 +12116,16 @@ jest-matcher-utils@^27.0.0, jest-matcher-utils@^27.5.1: jest-get-type "^27.5.1" pretty-format "^27.5.1" +jest-matcher-utils@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.1.2.tgz#e68c4bcc0266e70aa1a5c13fb7b8cd4695e318a1" + integrity sha512-MV5XrD3qYSW2zZSHRRceFzqJ39B2z11Qv0KPyZYxnzDHFeYZGJlgGi0SW+IXSJfOewgJp/Km/7lpcFT+cgZypw== + dependencies: + chalk "^4.0.0" + jest-diff "^29.1.2" + jest-get-type "^29.0.0" + pretty-format "^29.1.2" + jest-message-util@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.5.1.tgz#bdda72806da10d9ed6425e12afff38cd1458b6cf" @@ -11760,7 +12141,22 @@ jest-message-util@^27.5.1: slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^27.0.6, jest-mock@^27.5.1: +jest-message-util@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.1.2.tgz#c21a33c25f9dc1ebfcd0f921d89438847a09a501" + integrity sha512-9oJ2Os+Qh6IlxLpmvshVbGUiSkZVc2FK+uGOm6tghafnB2RyjKAxMZhtxThRMxfX1J1SOMhTn9oK3/MutRWQJQ== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.1.2" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.1.2" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^27.0.6, jest-mock@^27.3.0, jest-mock@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.5.1.tgz#19948336d49ef4d9c52021d34ac7b5f36ff967d6" integrity sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og== @@ -11931,6 +12327,18 @@ jest-util@^27.5.1: graceful-fs "^4.2.9" picomatch "^2.2.3" +jest-util@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.1.2.tgz#ac5798e93cb6a6703084e194cfa0898d66126df1" + integrity sha512-vPCk9F353i0Ymx3WQq3+a4lZ07NXu9Ca8wya6o4Fe4/aO1e1awMMprZ3woPFpKwghEOW+UXgd15vVotuNN9ONQ== + dependencies: + "@jest/types" "^29.1.2" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + jest-validate@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-27.5.1.tgz#9197d54dc0bdb52260b8db40b46ae668e04df067" @@ -14642,6 +15050,15 @@ pretty-format@^27.0.0, pretty-format@^27.0.2, pretty-format@^27.5.1: ansi-styles "^5.0.0" react-is "^17.0.1" +pretty-format@^29.0.0, pretty-format@^29.1.2: + version "29.1.2" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.1.2.tgz#b1f6b75be7d699be1a051f5da36e8ae9e76a8e6a" + integrity sha512-CGJ6VVGXVRP2o2Dorl4mAwwvDWT25luIsYhkyVQW32E4nL+TgW939J7LlKT/npq5Cpq6j3s+sy+13yk7xYpBmg== + dependencies: + "@jest/schemas" "^29.0.0" + ansi-styles "^5.0.0" + react-is "^18.0.0" + pretty-hrtime@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" @@ -15033,7 +15450,7 @@ react-is@17.0.2, react-is@^17.0.1, react-is@^17.0.2: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -"react-is@^16.12.0 || ^17.0.0 || ^18.0.0": +"react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.0.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== @@ -15271,6 +15688,14 @@ redent@^1.0.0: indent-string "^2.1.0" strip-indent "^1.0.1" +redent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== + dependencies: + indent-string "^4.0.0" + strip-indent "^3.0.0" + refractor@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/refractor/-/refractor-3.6.0.tgz#ac318f5a0715ead790fcfb0c71f4dd83d977935a"