chore: refactor style combobox (#209)

* refactor: refactor combobox based on designs

* style: combobox with search separated
This commit is contained in:
Camila Sosa Morales 2023-04-12 11:03:15 -03:00 committed by GitHub
parent 09e50adefc
commit 361855946a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 79 additions and 85 deletions

View File

@ -3,13 +3,7 @@ import {
ComboboxInputProps as ComboboxLibInputProps, ComboboxInputProps as ComboboxLibInputProps,
Transition, Transition,
} from '@headlessui/react'; } from '@headlessui/react';
import React, { import React, { forwardRef, Fragment, useEffect, useState } from 'react';
forwardRef,
Fragment,
useEffect,
useRef,
useState,
} from 'react';
import { Icon, IconName } from '@/components/core/icon'; import { Icon, IconName } from '@/components/core/icon';
import { Flex } from '@/components/layout'; import { Flex } from '@/components/layout';
@ -18,54 +12,28 @@ import { useDebounce } from '@/hooks/use-debounce';
import { Separator } from '../separator.styles'; import { Separator } from '../separator.styles';
import { cleanString } from './combobox.utils'; import { cleanString } from './combobox.utils';
type ComboboxInputProps = { type ComboboxInputProps = ComboboxLibInputProps<'input', ComboboxItem>;
/**
* If it's true, the list of options will be displayed
*/
open: boolean;
/**
* Name of the left icon to display in the input
*/
leftIcon: IconName;
/**
* Value to indicate it's invalid
*/
error?: boolean;
} & ComboboxLibInputProps<'input', ComboboxItem>;
const ComboboxInput: React.FC<ComboboxInputProps> = ({ const ComboboxInput: React.FC<ComboboxInputProps> = ({
open,
leftIcon,
error,
...props ...props
}: ComboboxInputProps) => ( }: ComboboxInputProps) => (
<div className="relative w-full"> <div className="relative w-full">
<Icon <Icon
name={leftIcon} name="search"
size="sm" size="sm"
css={{ css={{
position: 'absolute', position: 'absolute',
left: '$3', left: '$3',
top: '$3', top: '$3',
fontSize: '$xl', fontSize: '$xl',
color: 'slate8', color: '$slate8',
}} }}
/> />
<ComboboxLib.Input <ComboboxLib.Input
placeholder="Search" placeholder="Search"
className={`w-full border-solid border h-11 py-3 px-10 text-sm leading-5 text-slate11 outline-none ${ className={`w-full h-9 py-3 px-10 text-sm bg-transparent leading-5 text-slate11 outline-none `}
open
? 'border-b-0 rounded-t-xl bg-black border-slate6'
: `rounded-xl bg-transparent cursor-pointer ${
error ? 'border-red9' : 'border-slate7'
}`
}`}
displayValue={(selectedValue: ComboboxItem) => selectedValue.label}
{...props} {...props}
/> />
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-4">
<Icon name="chevron-down" css={{ fontSize: '$xs' }} />
</span>
</div> </div>
); );
@ -154,6 +122,7 @@ export type ComboboxProps = {
* Value to indicate it's invalid * Value to indicate it's invalid
*/ */
error?: boolean; error?: boolean;
css?: string; //tailwind css
}; };
export const Combobox: React.FC<ComboboxProps> = ({ export const Combobox: React.FC<ComboboxProps> = ({
@ -164,6 +133,7 @@ export const Combobox: React.FC<ComboboxProps> = ({
onChange, onChange,
onBlur, onBlur,
error = false, error = false,
css,
}) => { }) => {
const [filteredItems, setFilteredItems] = useState<ComboboxItem[]>([]); const [filteredItems, setFilteredItems] = useState<ComboboxItem[]>([]);
const [autocompleteItems, setAutocompleteItems] = useState<ComboboxItem[]>( const [autocompleteItems, setAutocompleteItems] = useState<ComboboxItem[]>(
@ -186,8 +156,6 @@ export const Combobox: React.FC<ComboboxProps> = ({
setFilteredItems(items); setFilteredItems(items);
}, [items]); }, [items]);
const buttonRef = useRef<HTMLButtonElement>(null);
const handleSearch = useDebounce((searchValue: string) => { const handleSearch = useDebounce((searchValue: string) => {
if (searchValue === '') { if (searchValue === '') {
setFilteredItems(items); setFilteredItems(items);
@ -216,10 +184,6 @@ export const Combobox: React.FC<ComboboxProps> = ({
handleSearch(event.target.value); handleSearch(event.target.value);
}; };
const handleInputClick = (): void => {
buttonRef.current?.click();
};
const handleComboboxChange = (optionSelected: ComboboxItem): void => { const handleComboboxChange = (optionSelected: ComboboxItem): void => {
onChange(optionSelected); onChange(optionSelected);
}; };
@ -239,16 +203,33 @@ export const Combobox: React.FC<ComboboxProps> = ({
onChange={handleComboboxChange} onChange={handleComboboxChange}
> >
{({ open }) => ( {({ open }) => (
<div className="relative"> <div className={`relative w-full ${css ? css : ''}`}>
<ComboboxInput <div className="relative w-full">
onChange={handleInputChange} <Icon
onClick={handleInputClick} name={leftIcon}
open={open} size="sm"
leftIcon={leftIcon} css={{
onBlur={onBlur} position: 'absolute',
error={error} left: '$3',
/> top: '$3',
<ComboboxLib.Button ref={buttonRef} className="hidden" /> fontSize: '$xl',
color: 'slate8',
}}
/>
<ComboboxLib.Button
className={`w-full text-left border-solid border rounded-xl h-11 py-3 px-10 text-sm leading-5 text-slate11 outline-none ${
error ? 'border-red9' : 'border-slate7'
}`}
onBlur={onBlur}
>
{selectedValue && selectedValue.label
? selectedValue.label
: 'Search'}
</ComboboxLib.Button>
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-4">
<Icon name="chevron-down" css={{ fontSize: '$xs' }} />
</span>
</div>
<Transition <Transition
show={open} show={open}
@ -259,28 +240,35 @@ export const Combobox: React.FC<ComboboxProps> = ({
leaveTo="opacity-0" leaveTo="opacity-0"
afterLeave={handleLeaveTransition} afterLeave={handleLeaveTransition}
> >
<ComboboxLib.Options className="absolute max-h-60 w-full z-10 overflow-auto rounded-b-xl border-solid border-slate6 border bg-black pt-2 px-3 text-base focus:outline-none sm:text-sm"> <div className="absolute max-h-60 mt-2 w-full z-10 overflow-auto rounded-xl border-solid border-slate6 border bg-black pt-2 px-3 text-base focus:outline-none sm:text-sm">
{[...autocompleteItems, ...filteredItems].length === 0 || <ComboboxInput onChange={handleInputChange} onBlur={onBlur} />
filteredItems === undefined ? ( <Separator />
<NoResults /> <ComboboxLib.Options className="mt-1">
) : ( {[...autocompleteItems, ...filteredItems].length === 0 ||
<> filteredItems === undefined ? (
{autocompleteItems.length > 0 && <span>Create new</span>} <NoResults />
{autocompleteItems.map((autocompleteOption: ComboboxItem) => ( ) : (
<ComboboxOption <>
key={autocompleteOption.value} {autocompleteItems.length > 0 && <span>Create new</span>}
option={autocompleteOption} {autocompleteItems.map(
/> (autocompleteOption: ComboboxItem) => (
))} <ComboboxOption
{autocompleteItems.length > 0 && filteredItems.length > 0 && ( key={autocompleteOption.value}
<Separator css={{ mb: '$2' }} /> option={autocompleteOption}
)} />
{filteredItems.map((option: ComboboxItem) => ( )
<ComboboxOption key={option.value} option={option} /> )}
))} {autocompleteItems.length > 0 &&
</> filteredItems.length > 0 && (
)} <Separator css={{ mb: '$2' }} />
</ComboboxLib.Options> )}
{filteredItems.map((option: ComboboxItem) => (
<ComboboxOption key={option.value} option={option} />
))}
</>
)}
</ComboboxLib.Options>
</div>
</Transition> </Transition>
</div> </div>
)} )}

View File

@ -10,13 +10,14 @@ export const LogoFileInput = StyledInputFile;
type InputProps = { type InputProps = {
leftIcon?: IconName; leftIcon?: IconName;
css?: string; //tailwind css
} & React.ComponentPropsWithRef<typeof InputStyled>; } & React.ComponentPropsWithRef<typeof InputStyled>;
export const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => { export const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
const { leftIcon, ...ownProps } = props; const { leftIcon, css, ...ownProps } = props;
return ( return (
<div className="relative"> <div className={`relative ${css ? css : ''}`}>
{leftIcon && ( {leftIcon && (
<InputIconStyled name={leftIcon} css={{ fontSize: '$lg' }} /> <InputIconStyled name={leftIcon} css={{ fontSize: '$lg' }} />
)} )}

View File

@ -1,6 +1,6 @@
import { useState } from 'react'; import { useState } from 'react';
import { Combobox, Flex } from '@/components'; import { Combobox, ComboboxItem, Flex } from '@/components';
const itemsCombobox = [ const itemsCombobox = [
{ label: 'Item 1', value: 'item-1' }, { label: 'Item 1', value: 'item-1' },
@ -9,15 +9,16 @@ const itemsCombobox = [
]; ];
export const ComboboxTest: React.FC = () => { export const ComboboxTest: React.FC = () => {
const [selectedValue, setSelectedValue] = useState(''); const [selectedValue, setSelectedValue] = useState({} as ComboboxItem);
const [selectedValueAutocomplete, setSelectedValueAutocomplete] = const [selectedValueAutocomplete, setSelectedValueAutocomplete] = useState(
useState(''); {} as ComboboxItem
);
const handleComboboxChange = (value: string): void => { const handleComboboxChange = (value: ComboboxItem): void => {
setSelectedValue(value); setSelectedValue(value);
}; };
const handleComboboxChangeAutocomplete = (value: string): void => { const handleComboboxChangeAutocomplete = (value: ComboboxItem): void => {
setSelectedValueAutocomplete(value); setSelectedValueAutocomplete(value);
}; };
@ -32,12 +33,14 @@ export const ComboboxTest: React.FC = () => {
}} }}
> >
<h1>Components Test</h1> <h1>Components Test</h1>
<Flex css={{ width: '400px', gap: '$2' }}> <Flex css={{ width: '600px', gap: '$2' }}>
<Combobox <Combobox
items={itemsCombobox} items={itemsCombobox}
selectedValue={selectedValue} selectedValue={selectedValue}
onChange={handleComboboxChange} onChange={handleComboboxChange}
leftIcon="github"
/> />
<Combobox <Combobox
items={itemsCombobox} items={itemsCombobox}
selectedValue={selectedValueAutocomplete} selectedValue={selectedValueAutocomplete}

View File

@ -7,9 +7,9 @@ import { ToastTest } from './toast-test';
export const ComponentsTest: React.FC = () => { export const ComponentsTest: React.FC = () => {
return ( return (
<Flex css={{ flexDirection: 'column' }}> <Flex css={{ flexDirection: 'column' }}>
<ComboboxTest />
<ColorPickerTest /> <ColorPickerTest />
<ToastTest /> <ToastTest />
<ComboboxTest />
</Flex> </Flex>
); );
}; };

View File

@ -85,6 +85,7 @@ export const GithubRepositoryConnection: React.FC = () => {
leftIcon="search" leftIcon="search"
placeholder="Search repo" placeholder="Search repo"
onChange={handleSearchChange} onChange={handleSearchChange}
css="flex-1"
/> />
</Flex> </Flex>
{queryLoading === 'loading' || {queryLoading === 'loading' ||

View File

@ -55,6 +55,7 @@ export const UserOrgsCombobox: React.FC = () => {
selectedValue={selectedUserOrg} selectedValue={selectedUserOrg}
onChange={handleUserOrgChange} onChange={handleUserOrgChange}
leftIcon="github" leftIcon="github"
css="flex-1"
/> />
); );
}; };