Merge pull request #322 from webstudio-is/css-value-input-final
CSS Value Input that combines the TextField and Combobox updates all in one
No files matched your search
@@ -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
|
||||
|
||||
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
@@ -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 }) => {
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
size={2}
|
||||
name="secret"
|
||||
type="text"
|
||||
minLength={2}
|
||||
|
||||
@@ -55,7 +55,6 @@ export const SelectProjectCard = ({
|
||||
<Form method="post">
|
||||
<Flex gap="1">
|
||||
<TextField
|
||||
size={2}
|
||||
state={errors ? "invalid" : undefined}
|
||||
name="project"
|
||||
defaultValue={newProject}
|
||||
|
||||
@@ -10,19 +10,100 @@ import type { SelectedInstanceData } from "~/shared/canvas-components";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Combobox,
|
||||
ComboboxTextField,
|
||||
ComboboxPopperContent,
|
||||
Grid,
|
||||
TextField,
|
||||
Tooltip,
|
||||
useCombobox,
|
||||
ComboboxPopper,
|
||||
ComboboxPopperContent,
|
||||
ComboboxPopperAnchor,
|
||||
ComboboxListbox,
|
||||
ComboboxListboxItem,
|
||||
IconButton,
|
||||
} from "@webstudio-is/design-system";
|
||||
import {
|
||||
PlusIcon,
|
||||
TrashIcon,
|
||||
ExclamationTriangleIcon,
|
||||
ChevronDownIcon,
|
||||
} from "@webstudio-is/icons";
|
||||
import { handleChangePropType, usePropsLogic } from "./use-props-logic";
|
||||
|
||||
type ComboboxProps = {
|
||||
isReadonly: boolean;
|
||||
isInvalid: boolean;
|
||||
items: Array<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 (
|
||||
<ComboboxPopper>
|
||||
<Box {...getComboboxProps()}>
|
||||
<ComboboxPopperAnchor>
|
||||
<TextField
|
||||
{...getInputProps({
|
||||
onKeyPress: (event) => {
|
||||
if (event.key === "Enter") {
|
||||
onSubmit(event.currentTarget.value);
|
||||
}
|
||||
},
|
||||
})}
|
||||
name="prop"
|
||||
placeholder="Property"
|
||||
readOnly={isReadonly}
|
||||
state={isInvalid ? "invalid" : undefined}
|
||||
suffix={
|
||||
<IconButton {...getToggleButtonProps()}>
|
||||
<ChevronDownIcon />
|
||||
</IconButton>
|
||||
}
|
||||
/>
|
||||
</ComboboxPopperAnchor>
|
||||
<ComboboxPopperContent align="start" sideOffset={5}>
|
||||
<ComboboxListbox {...getMenuProps()}>
|
||||
{isOpen &&
|
||||
items.map((item, index) => {
|
||||
return (
|
||||
<ComboboxListboxItem
|
||||
{...getItemProps({ item, index })}
|
||||
key={index}
|
||||
>
|
||||
{item}
|
||||
</ComboboxListboxItem>
|
||||
);
|
||||
})}
|
||||
</ComboboxListbox>
|
||||
</ComboboxPopperContent>
|
||||
</Box>
|
||||
</ComboboxPopper>
|
||||
);
|
||||
};
|
||||
|
||||
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" }}
|
||||
>
|
||||
<Combobox
|
||||
name="prop"
|
||||
placeholder="Property"
|
||||
items={allProps}
|
||||
value={prop}
|
||||
itemToString={(item) => item ?? ""}
|
||||
onItemSelect={(value) => {
|
||||
onItemSelect={(value: PropertyProps["value"] | null) => {
|
||||
if (value !== null) {
|
||||
onChange(id, "prop", value);
|
||||
}
|
||||
}}
|
||||
onSubmit={(value) => {
|
||||
onChange(id, "prop", value);
|
||||
}}
|
||||
renderTextField={({ inputProps, toggleProps }) => (
|
||||
<ComboboxTextField
|
||||
toggleProps={toggleProps}
|
||||
inputProps={{
|
||||
...inputProps,
|
||||
readOnly: required,
|
||||
state: isInvalidProp ? "invalid" : undefined,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
renderPopperContent={(props) => (
|
||||
<ComboboxPopperContent {...props} align="start" sideOffset={5} />
|
||||
)}
|
||||
isInvalid={isInvalid}
|
||||
isReadonly={required}
|
||||
/>
|
||||
|
||||
{isInvalidProp ? (
|
||||
{isInvalid ? (
|
||||
<Tooltip content={`Invalid property name: ${prop}`}>
|
||||
<ExclamationTriangleIcon width={12} height={12} />
|
||||
</Tooltip>
|
||||
|
||||
@@ -30,7 +30,11 @@ export const Search = ({ onSearch, selectedInstanceData }: SearchProps) => {
|
||||
type="search"
|
||||
value={search}
|
||||
inputRef={inputRef}
|
||||
prefix={<MagnifyingGlassIcon />}
|
||||
prefix={
|
||||
<IconButton aria-label="Search">
|
||||
<MagnifyingGlassIcon />
|
||||
</IconButton>
|
||||
}
|
||||
suffix={
|
||||
search.length > 0 && (
|
||||
<IconButton
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"engines": {
|
||||
"npm": ">=7.0.0",
|
||||
"node": ">=14 <=16"
|
||||
"node": "16"
|
||||
},
|
||||
"dependencies": {},
|
||||
"packageManager": "yarn@1.22.19",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -11,11 +11,7 @@ export default {
|
||||
};
|
||||
|
||||
export const Simple: ComponentStory<typeof Combobox> = () => {
|
||||
const items = [
|
||||
{ label: "Apple" },
|
||||
{ label: "Banana" },
|
||||
{ label: "Orange" },
|
||||
] as const;
|
||||
const items = [{ label: "Apple" }, { label: "Banana" }, { label: "Orange" }];
|
||||
const [value, setValue] = React.useState<typeof items[number] | null>(null);
|
||||
return (
|
||||
<Combobox
|
||||
|
||||
@@ -61,12 +61,53 @@ export const ListboxItemBase: ForwardRefRenderFunction<
|
||||
);
|
||||
};
|
||||
|
||||
export const ComboboxListbox = Listbox;
|
||||
|
||||
export const ComboboxListboxItem = forwardRef(ListboxItemBase);
|
||||
|
||||
export const ComboboxPopper = Popper;
|
||||
|
||||
export const ComboboxPopperContent = PopperContent;
|
||||
|
||||
export const ComboboxPopperAnchor = PopperAnchor;
|
||||
|
||||
const useFilter = <Item,>({
|
||||
items,
|
||||
itemToString,
|
||||
}: {
|
||||
items: Array<Item>;
|
||||
itemToString: (item: Item | null) => string;
|
||||
}) => {
|
||||
const [filteredItems, setFilteredItems] = useState<Array<Item>>(items);
|
||||
const cachedItems = useRef(items);
|
||||
|
||||
useEffect(() => {
|
||||
cachedItems.current = items;
|
||||
}, [items]);
|
||||
|
||||
const filter = useCallback(
|
||||
(search?: string) => {
|
||||
const foundItems: Array<Item> = matchSorter(items, search ?? "", {
|
||||
keys: [(item) => itemToString(item)],
|
||||
});
|
||||
setFilteredItems(foundItems);
|
||||
},
|
||||
[itemToString, items]
|
||||
);
|
||||
|
||||
const resetFilter = useCallback(() => {
|
||||
setFilteredItems(cachedItems.current);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
filteredItems,
|
||||
filter,
|
||||
resetFilter,
|
||||
};
|
||||
};
|
||||
|
||||
type useComboboxProps<Item> = {
|
||||
items: ReadonlyArray<Item>;
|
||||
items: Array<Item>;
|
||||
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<Item> = {
|
||||
) => Partial<UseComboboxStateChangeOptions<Item>>;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line func-style
|
||||
export function useCombobox<Item>({
|
||||
export const useCombobox = <Item,>({
|
||||
items,
|
||||
value,
|
||||
itemToString,
|
||||
onItemSelect,
|
||||
onItemHighlight,
|
||||
stateReducer,
|
||||
}: useComboboxProps<Item>) {
|
||||
const [filteredItems, setFilteredItems] = useState(items);
|
||||
const cachedItems = useRef(items);
|
||||
stateReducer = (state, { changes }) => changes,
|
||||
}: useComboboxProps<Item>) => {
|
||||
const { filteredItems, filter, resetFilter } = useFilter<Item>({
|
||||
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<Item> = matchSorter(
|
||||
items,
|
||||
inputValue ?? "",
|
||||
{
|
||||
keys: [(item) => itemToString(item)],
|
||||
}
|
||||
);
|
||||
setFilteredItems(foundItems);
|
||||
filter(inputValue);
|
||||
},
|
||||
onSelectedItemChange({ selectedItem }) {
|
||||
onItemSelect?.(selectedItem ?? null);
|
||||
@@ -118,14 +153,10 @@ export function useCombobox<Item>({
|
||||
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<Item>({
|
||||
|
||||
return {
|
||||
...downshiftProps,
|
||||
items: filteredItems, // Return filtered items
|
||||
items: filteredItems,
|
||||
getItemProps: enhancedGetItemProps,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
type ComboboxProps<Item> = useComboboxProps<Item> & {
|
||||
name: string;
|
||||
@@ -153,8 +184,7 @@ type ComboboxProps<Item> = useComboboxProps<Item> & {
|
||||
placeholder?: string;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line func-style
|
||||
export function Combobox<Item>({
|
||||
export const Combobox = <Item,>({
|
||||
items,
|
||||
value = null,
|
||||
name,
|
||||
@@ -162,15 +192,7 @@ export function Combobox<Item>({
|
||||
itemToString,
|
||||
onItemSelect,
|
||||
onItemHighlight,
|
||||
}: ComboboxProps<Item>) {
|
||||
const stateReducer = useCallback((state, actionAndChanges) => {
|
||||
const { type, changes } = actionAndChanges;
|
||||
switch (type) {
|
||||
default:
|
||||
return changes; // otherwise business as usual.
|
||||
}
|
||||
}, []);
|
||||
|
||||
}: ComboboxProps<Item>) => {
|
||||
const {
|
||||
items: foundItems,
|
||||
getInputProps,
|
||||
@@ -185,7 +207,6 @@ export function Combobox<Item>({
|
||||
itemToString,
|
||||
onItemSelect,
|
||||
onItemHighlight,
|
||||
stateReducer,
|
||||
});
|
||||
return (
|
||||
<Popper>
|
||||
@@ -222,6 +243,6 @@ export function Combobox<Item>({
|
||||
</Box>
|
||||
</Popper>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Combobox.displayName = "Combobox";
|
||||
@@ -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",
|
||||
|
||||
@@ -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<typeof TextField> = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const Sizes: ComponentStory<typeof TextField> = () => {
|
||||
return (
|
||||
<Flex direction="column" gap={3}>
|
||||
<TextField size={1} />
|
||||
<TextField size={2} />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const Variants: ComponentStory<typeof TextField> = () => {
|
||||
return (
|
||||
<Flex direction="column" gap={3}>
|
||||
@@ -68,7 +59,7 @@ export const PrefixSuffix: ComponentStory<typeof TextField> = () => {
|
||||
return (
|
||||
<Flex direction="column" gap={3}>
|
||||
<TextField
|
||||
prefix={<BrushIcon />}
|
||||
prefix={<RowGapIcon />}
|
||||
suffix={
|
||||
<IconButton>
|
||||
<ChevronDownIcon />
|
||||
@@ -77,25 +68,16 @@ export const PrefixSuffix: ComponentStory<typeof TextField> = () => {
|
||||
/>
|
||||
<TextField
|
||||
state="invalid"
|
||||
prefix={<BrushIcon />}
|
||||
prefix={<RowGapIcon />}
|
||||
suffix={
|
||||
<IconButton>
|
||||
<ChevronDownIcon />
|
||||
</IconButton>
|
||||
}
|
||||
/>
|
||||
<TextField
|
||||
size={2}
|
||||
prefix={<BrushIcon />}
|
||||
suffix={
|
||||
<IconButton size={2}>
|
||||
<ChevronDownIcon />
|
||||
</IconButton>
|
||||
}
|
||||
/>
|
||||
<TextField
|
||||
disabled
|
||||
prefix={<BrushIcon />}
|
||||
prefix={<RowGapIcon />}
|
||||
suffix={
|
||||
<IconButton>
|
||||
<ChevronDownIcon />
|
||||
@@ -112,7 +94,7 @@ export const Layout: ComponentStory<typeof TextField> = () => {
|
||||
<Flex direction="row" gap={2} css={{ justifyContent: "space-between" }}>
|
||||
<TextField
|
||||
value="Long content comes here and it doesn't wrap"
|
||||
prefix={<BrushIcon />}
|
||||
prefix={<RowGapIcon />}
|
||||
suffix={
|
||||
<IconButton>
|
||||
<ChevronDownIcon />
|
||||
@@ -147,7 +129,7 @@ export const Layout: ComponentStory<typeof TextField> = () => {
|
||||
<TextField
|
||||
id="field"
|
||||
value="Long content comes here and it doesn't wrap"
|
||||
prefix={<BrushIcon />}
|
||||
prefix={<RowGapIcon />}
|
||||
/>
|
||||
</Box>
|
||||
<Box css={{ background: "$muted" }}>
|
||||
@@ -222,7 +204,7 @@ export const ClickCapture: ComponentStory<typeof TextField> = (args) => {
|
||||
placeholder="Click on the icon to focus input"
|
||||
prefix={
|
||||
<Flex title="icon">
|
||||
<BrushIcon />
|
||||
<RowGapIcon />
|
||||
</Flex>
|
||||
}
|
||||
suffix={
|
||||
|
||||
@@ -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<typeof TextFieldBase>,
|
||||
"size" | "variant" | "state" | "css"
|
||||
"variant" | "state" | "css"
|
||||
> &
|
||||
Omit<React.ComponentProps<"input">, "prefix" | "children"> & {
|
||||
inputRef?: React.Ref<HTMLInputElement>;
|
||||
@@ -164,7 +160,6 @@ export const TextField = React.forwardRef<HTMLDivElement, TextFieldProps>(
|
||||
css,
|
||||
disabled,
|
||||
inputRef,
|
||||
size,
|
||||
state,
|
||||
variant,
|
||||
onFocus,
|
||||
@@ -194,11 +189,12 @@ export const TextField = React.forwardRef<HTMLDivElement, TextFieldProps>(
|
||||
<TextFieldBase
|
||||
aria-disabled={disabled}
|
||||
ref={forwardedRef}
|
||||
size={size}
|
||||
state={state}
|
||||
variant={variant}
|
||||
css={css}
|
||||
{...focusWithinProps}
|
||||
{...(prefix && { "data-has-prefix": true })}
|
||||
{...(suffix && { "data-has-suffix": true })}
|
||||
onClickCapture={focusInnerInput}
|
||||
>
|
||||
{/* We want input to be the first element in DOM so it receives the focus first */}
|
||||
|
||||