diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index efb144c5ed..f37e4cb4bf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,7 +9,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - node: ["14.x", "16.x"] + node: ["16.x"] os: [ubuntu-latest] steps: - name: Checkout repo 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 8172ed3d86..f300aa904f 100644 --- a/apps/designer/app/auth/login.tsx +++ b/apps/designer/app/auth/login.tsx @@ -1,9 +1,7 @@ import { LinksFunction, MetaFunction } from "@remix-run/node"; import { Form, useLoaderData } from "@remix-run/react"; import { useState } from "react"; - import { - Box, Card, Flex, Heading, @@ -12,7 +10,6 @@ import { 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"; @@ -87,7 +84,6 @@ export const Login = ({ errorMessage }: { errorMessage: string }) => { }} > ; + 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,14 @@ 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" && @@ -56,30 +137,20 @@ const Property = ({ css={{ gridTemplateColumns: "1fr 1fr auto", alignItems: "center" }} > 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/search.tsx b/apps/designer/app/designer/features/style-panel/search.tsx index 2dba14e608..299ce19933 100644 --- a/apps/designer/app/designer/features/style-panel/search.tsx +++ b/apps/designer/app/designer/features/style-panel/search.tsx @@ -30,7 +30,11 @@ export const Search = ({ onSearch, selectedInstanceData }: SearchProps) => { type="search" value={search} inputRef={inputRef} - prefix={} + prefix={ + + + + } suffix={ search.length > 0 && ( =7.0.0", - "node": ">=14 <=16" + "node": "16" }, "dependencies": {}, "packageManager": "yarn@1.22.19", diff --git a/packages/design-system/src/components/button.tsx b/packages/design-system/src/components/button.tsx index eb0394f82d..edee5f240b 100644 --- a/packages/design-system/src/components/button.tsx +++ b/packages/design-system/src/components/button.tsx @@ -25,7 +25,7 @@ export const Button = styled("button", { // Custom height: "$5", px: "$2", - fontFamily: "$untitled", + fontFamily: "$sans", fontSize: "$2", fontWeight: 500, fontVariantNumeric: "tabular-nums", diff --git a/packages/design-system/src/components/combobox.stories.tsx b/packages/design-system/src/components/combobox.stories.tsx index 5c1abba546..622ddea45a 100644 --- a/packages/design-system/src/components/combobox.stories.tsx +++ b/packages/design-system/src/components/combobox.stories.tsx @@ -11,11 +11,7 @@ export default { }; export const Simple: ComponentStory = () => { - const items = [ - { label: "Apple" }, - { label: "Banana" }, - { label: "Orange" }, - ] as const; + const items = [{ label: "Apple" }, { label: "Banana" }, { label: "Orange" }]; const [value, setValue] = React.useState(null); return ( ({ + items, + itemToString, +}: { + items: Array; + 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, + }; +}; + type useComboboxProps = { - items: ReadonlyArray; + 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; @@ -77,32 +118,26 @@ type useComboboxProps = { ) => Partial>; }; -// eslint-disable-next-line func-style -export function useCombobox({ +export const useCombobox = ({ items, value, itemToString, onItemSelect, onItemHighlight, - stateReducer, -}: useComboboxProps) { - const [filteredItems, setFilteredItems] = useState(items); - const cachedItems = useRef(items); + stateReducer = (state, { changes }) => changes, +}: useComboboxProps) => { + const { filteredItems, filter, resetFilter } = useFilter({ + items, + itemToString, + }); const downshiftProps = useDownshiftCombobox({ - items: filteredItems as Item[], - selectedItem: value, // Avoid downshift warning about switching controlled mode + items: filteredItems, + selectedItem: value, // Prevent downshift warning about switching controlled mode stateReducer, itemToString, onInputValueChange({ inputValue }) { - const foundItems: ReadonlyArray = matchSorter( - items, - inputValue ?? "", - { - keys: [(item) => itemToString(item)], - } - ); - setFilteredItems(foundItems); + filter(inputValue); }, onSelectedItemChange({ selectedItem }) { onItemSelect?.(selectedItem ?? null); @@ -118,14 +153,10 @@ export function useCombobox({ downshiftProps; useEffect(() => { - cachedItems.current = items; - }, [items]); - - useEffect(() => { - if (!isOpen) { - setFilteredItems(cachedItems.current); + if (isOpen === false) { + resetFilter(); } - }, [isOpen]); + }, [isOpen, resetFilter]); const enhancedGetItemProps = useCallback( (options) => { @@ -142,10 +173,10 @@ export function useCombobox({ return { ...downshiftProps, - items: filteredItems, // Return filtered items + items: filteredItems, getItemProps: enhancedGetItemProps, }; -} +}; type ComboboxProps = useComboboxProps & { name: string; @@ -153,8 +184,7 @@ type ComboboxProps = useComboboxProps & { placeholder?: string; }; -// eslint-disable-next-line func-style -export function Combobox({ +export const Combobox = ({ items, value = null, name, @@ -162,15 +192,7 @@ export function Combobox({ itemToString, onItemSelect, onItemHighlight, -}: ComboboxProps) { - const stateReducer = useCallback((state, actionAndChanges) => { - const { type, changes } = actionAndChanges; - switch (type) { - default: - return changes; // otherwise business as usual. - } - }, []); - +}: ComboboxProps) => { const { items: foundItems, getInputProps, @@ -185,7 +207,6 @@ export function Combobox({ itemToString, onItemSelect, onItemHighlight, - stateReducer, }); return ( @@ -222,6 +243,6 @@ export function Combobox({ ); -} +}; Combobox.displayName = "Combobox"; diff --git a/packages/design-system/src/components/menu.tsx b/packages/design-system/src/components/menu.tsx index cd0a2eea91..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", diff --git a/packages/design-system/src/components/text-field.stories.tsx b/packages/design-system/src/components/text-field.stories.tsx index 28b2bef61e..f49a1c0adb 100644 --- a/packages/design-system/src/components/text-field.stories.tsx +++ b/packages/design-system/src/components/text-field.stories.tsx @@ -2,7 +2,7 @@ import { expect } from "@storybook/jest"; import { userEvent, waitFor, within } from "@storybook/testing-library"; import React from "react"; import { ComponentStory } from "@storybook/react"; -import { BrushIcon, ChevronDownIcon } from "@webstudio-is/icons"; +import { RowGapIcon, ChevronDownIcon } from "@webstudio-is/icons"; import { Button } from "./button"; import { Flex } from "./flex"; import { IconButton } from "./icon-button"; @@ -36,15 +36,6 @@ export const NativeProps: ComponentStory = () => { ); }; -export const Sizes: ComponentStory = () => { - return ( - - - - - ); -}; - export const Variants: ComponentStory = () => { return ( @@ -68,7 +59,7 @@ export const PrefixSuffix: ComponentStory = () => { return ( } + prefix={} suffix={ @@ -77,25 +68,16 @@ export const PrefixSuffix: ComponentStory = () => { /> } + prefix={} suffix={ } /> - } - suffix={ - - - - } - /> } + prefix={} suffix={ @@ -112,7 +94,7 @@ export const Layout: ComponentStory = () => { } + prefix={} suffix={ @@ -147,7 +129,7 @@ export const Layout: ComponentStory = () => { } + prefix={} /> @@ -222,7 +204,7 @@ export const ClickCapture: ComponentStory = (args) => { placeholder="Click on the icon to focus input" prefix={ - + } suffix={ diff --git a/packages/design-system/src/components/text-field.tsx b/packages/design-system/src/components/text-field.tsx index c6e79b1ae8..d84988cee5 100644 --- a/packages/design-system/src/components/text-field.tsx +++ b/packages/design-system/src/components/text-field.tsx @@ -12,6 +12,7 @@ const InputBase = styled("input", { boxSizing: "border-box", fontFamily: "inherit", fontSize: "inherit", + color: "inherit", margin: "0", padding: "0", flexGrow: 1, @@ -43,7 +44,7 @@ const InputBase = styled("input", { }, "&:-webkit-autofill::first-line": { - fontFamily: "$untitled", + fontFamily: "$sans", color: "$hiContrast", }, @@ -69,9 +70,23 @@ const TextFieldBase = styled("div", { 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$blue8, 0px 0px 0px 1px $colors$blue8", + "inset 0px 0px 0px 1px $colors$blue10, 0px 0px 0px 1px $colors$blue10", }, "&[aria-disabled=true]": { @@ -86,22 +101,6 @@ const TextFieldBase = styled("div", { }, variants: { - size: { - "1": { - px: "$1", - borderRadius: "$1", - fontSize: "$1", - height: "$5", - lineHeight: "$sizes$5", - }, - "2": { - px: "$2", - borderRadius: "$2", - fontSize: "$3", - height: "$6", - lineHeight: "$sizes$6", - }, - }, variant: { ghost: { boxShadow: "none", @@ -114,7 +113,7 @@ const TextFieldBase = styled("div", { "&: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", @@ -141,14 +140,11 @@ const TextFieldBase = styled("div", { }, }, }, - defaultVariants: { - size: "1", - }, }); type TextFieldProps = Pick< React.ComponentProps, - "size" | "variant" | "state" | "css" + "variant" | "state" | "css" > & Omit, "prefix" | "children"> & { inputRef?: React.Ref; @@ -164,7 +160,6 @@ export const TextField = React.forwardRef( css, disabled, inputRef, - size, state, variant, onFocus, @@ -194,11 +189,12 @@ export const TextField = React.forwardRef( {/* We want input to be the first element in DOM so it receives the focus first */}