chore: dropdown component (#90)

* wip: add dropdown component

* wip: added search functionality on dropdown

* chore: dropdown component with properties

* style: set width pase on parent

* refactor: remove old dropdown component and add headless ui

* chore: remove unsued radix component dependency

* chore: add yarn.lock on root fodler

* refactor: remove old folders from root project

* chore: add import on index

* chore: apply PR review
This commit is contained in:
Camila Sosa Morales 2023-02-08 09:10:59 -05:00 committed by GitHub
parent 292f550466
commit 33ebac510c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 294 additions and 2 deletions

View File

@ -14,6 +14,7 @@
"author": "Fleek",
"dependencies": {
"@ethersproject/providers": "^5.7.2",
"@headlessui/react": "^1.7.8",
"@radix-ui/colors": "^0.1.8",
"@react-icons/all-files": "^4.1.0",
"@reduxjs/toolkit": "^1.9.1",

View File

@ -0,0 +1,160 @@
import { Fragment, useRef, useState } from 'react';
import { Combobox as ComboboxLib, Transition } from '@headlessui/react';
import { Icon, IconName } from '@/components/core/icon';
import { Flex } from '@/components/layout';
type ComboboxInputProps = {
open: boolean;
handleInputChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
handleInputClick: () => void;
};
const ComboboxInput = ({
open,
handleInputChange,
handleInputClick,
}: ComboboxInputProps) => (
<div className="relative">
<Icon
name="search"
size="sm"
css={{
position: 'absolute',
left: '$3',
top: '0.9375rem',
color: 'slate8',
}}
/>
<ComboboxLib.Input
placeholder="Search"
className={`w-full border-solid border border-slate7 py-3 pl-8 pr-10 text-sm bg-transparent leading-5 text-slate11 outline-none ${
open ? 'border-b-0 rounded-t-xl bg-black border-slate6' : 'rounded-xl'
}`}
displayValue={(selectedValue: ComboboxItem) => selectedValue.label}
onChange={handleInputChange}
onClick={handleInputClick}
/>
</div>
);
type ComboboxOptionProps = {
option: ComboboxItem;
};
const ComboboxOption = ({ option }: ComboboxOptionProps) => (
<ComboboxLib.Option
key={option.value}
value={option}
className={({ active }) =>
`relative cursor-default select-none py-2 px-3.5 text-slate11 rounded-xl mb-2 text-sm ${
active ? 'bg-slate5 text-slate12' : 'bg-transparent'
}`
}
>
{({ selected, active }) => (
<Flex css={{ justifyContent: 'space-between' }}>
<Flex css={{ flexDirection: 'row' }}>
{option.icon && <Icon name={option.icon} css={{ mr: '$2' }} />}
<span className={`${active ? 'text-slate12' : 'text-slate11'}`}>
{option.label}
</span>
</Flex>
{selected && <Icon name="check" color="white" />}
</Flex>
)}
</ComboboxLib.Option>
);
const NoResults = () => (
<div className="relative cursor-default select-none pt-2 px-3.5 pb-4 text-slate11">
Nothing found.
</div>
);
export type ComboboxItem = {
value: string;
label: string;
icon?: IconName;
};
export type ComboboxProps = {
items: ComboboxItem[];
selectedValue: ComboboxItem | undefined;
onChange(option: ComboboxItem): void;
};
export const Combobox: React.FC<ComboboxProps> = ({
items,
selectedValue = { value: '', label: '' },
onChange,
}) => {
const [query, setQuery] = useState('');
const buttonRef = useRef<HTMLButtonElement>(null);
const filteredItems =
query === ''
? items
: items.filter((person) =>
person.label
.toLowerCase()
.replace(/\s+/g, '')
.includes(query.toLowerCase().replace(/\s+/g, ''))
);
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setQuery(event.target.value);
};
const handleInputClick = () => {
buttonRef.current?.click();
};
const handleComboboxChange = (option: ComboboxItem) => {
onChange(option);
};
const handleLeaveTransition = () => {
setQuery('');
};
return (
<ComboboxLib
value={selectedValue}
by="value"
onChange={handleComboboxChange}
>
{({ open }) => (
<>
<div className="w-full cursor-default overflow-hidden bg-transparent text-left focus:outline-none sm:text-sm">
<ComboboxInput
handleInputChange={handleInputChange}
handleInputClick={handleInputClick}
open={open}
/>
<ComboboxLib.Button ref={buttonRef} className="hidden" />
</div>
<Transition
show={open}
as={Fragment}
enter="transition duration-400 ease-out"
leave="transition ease-out duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
afterLeave={handleLeaveTransition}
>
<ComboboxLib.Options className="absolute max-h-60 w-inherit 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">
{filteredItems.length === 0 && query !== '' ? (
<NoResults />
) : (
filteredItems.map((option) => {
return <ComboboxOption option={option} />;
})
)}
</ComboboxLib.Options>
</Transition>
</>
)}
</ComboboxLib>
);
};

View File

@ -0,0 +1,91 @@
import { Fragment } from 'react';
import { Listbox, Transition } from '@headlessui/react';
import { Icon } from '@/components/core/icon';
import { Flex } from '@/components';
type DropdownOptionProps = {
option: DropdownItem;
};
const DropdownOption = ({ option }: DropdownOptionProps) => (
<Listbox.Option
key={option.value}
className={({ active }) =>
`relative cursor-default select-none py-2 px-3.5 text-slate11 rounded-xl mb-2 text-sm ${
active ? 'bg-slate5 text-slate12' : 'bg-transparent'
}`
}
value={option}
>
{({ selected, active }) => (
<Flex css={{ justifyContent: 'space-between' }}>
<span className={`${active ? 'text-slate12' : 'text-slate11'}`}>
{option.label}
</span>
{selected && <Icon name="check" color="white" />}
</Flex>
)}
</Listbox.Option>
);
type DropdownButtonProps = {
selectedValue: DropdownItem | undefined;
open: boolean;
};
const DropdownButton = ({ selectedValue, open }: DropdownButtonProps) => (
<Listbox.Button
className={`relative w-full cursor-default bg-transparent border-solid border border-slate7 py-3 pl-3.5 pr-10 text-left focus:outline-none sm:text-sm ${
open ? 'border-b-0 rounded-t-xl bg-black border-slate6' : 'rounded-xl'
}`}
>
<span className="block truncate text-slate11">
{selectedValue ? selectedValue.label : 'Select'}
</span>
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-4">
<Icon name="chevron-down" />
</span>
</Listbox.Button>
);
export type DropdownItem = {
value: string;
label: string;
};
export type DropdownProps = {
items: DropdownItem[];
selectedValue: DropdownItem | undefined;
onChange(option: DropdownItem): void;
};
export const Dropdown: React.FC<DropdownProps> = ({
items,
selectedValue,
onChange,
}) => {
const handleDropdownChange = (option: DropdownItem) => {
onChange(option);
};
return (
<Listbox value={selectedValue} by="value" onChange={handleDropdownChange}>
{({ open }) => (
<div className="relative">
<DropdownButton selectedValue={selectedValue} open={open} />
<Transition
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Listbox.Options className="absolute max-h-fit w-full z-10 overflow-auto rounded-b-xl bg-black px-3 pt-2 border-solid border-slate6 border text-base focus:outline-none sm:text-sm">
{items.map((option: DropdownItem) => (
<DropdownOption option={option} />
))}
</Listbox.Options>
</Transition>
</div>
)}
</Listbox>
);
};

View File

@ -0,0 +1,2 @@
export * from './combobox';
export * from './dropdown';

View File

@ -1,14 +1,17 @@
import { IoLogoGithub } from '@react-icons/all-files/io5/IoLogoGithub';
import { IoArrowBackCircleSharp } from '@react-icons/all-files/io5/IoArrowBackCircleSharp';
import { IoInformationCircleSharp } from '@react-icons/all-files/io5/IoInformationCircleSharp';
import { IoCloudUploadSharp } from '@react-icons/all-files/io5/IoCloudUploadSharp';
import { AiOutlineCheck } from '@react-icons/all-files/ai/AiOutlineCheck';
import { AiOutlineDown } from '@react-icons/all-files/ai/AiOutlineDown';
import { BiSearch } from '@react-icons/all-files/bi/BiSearch';
import { IoCloudUploadSharp } from '@react-icons/all-files/io5/IoCloudUploadSharp';
import { MetamaskIcon, EthereumIcon } from './custom';
import { BiSearch } from '@react-icons/all-files/bi/BiSearch';
export const IconLibrary = Object.freeze({
back: IoArrowBackCircleSharp,
check: AiOutlineCheck,
'chevron-down': AiOutlineDown,
ethereum: EthereumIcon,
github: IoLogoGithub,
info: IoInformationCircleSharp,

View File

@ -1,2 +1,4 @@
export * from './button';
export * from './combobox';
export * from './icon';
export * from './input';

View File

@ -32,6 +32,7 @@ export const spacing = {
52: '13rem',
56: '14rem',
60: '15rem',
62: '15.5rem',
64: '16rem',
72: '18rem',
80: '20rem',

View File

@ -64,6 +64,14 @@ export const utils = {
btlr: (value: Stitches.PropertyValue<'borderTopLeftRadius'>) => ({
borderTopLeftRadius: value,
}),
bt: (value: Stitches.PropertyValue<'borderTopLeftRadius'>) => ({
borderTopLeftRadius: value,
borderTopRightRadius: value,
}),
bb: (value: Stitches.PropertyValue<'borderBottomLeftRadius'>) => ({
borderBottomRightRadius: value,
borderBottomLeftRadius: value,
}),
ox: (value: Stitches.PropertyValue<'overflowX'>) => ({ overflowX: value }),
oy: (value: Stitches.PropertyValue<'overflowY'>) => ({ overflowY: value }),

View File

@ -2,7 +2,19 @@
module.exports = {
content: ['./src/**/*.tsx'],
theme: {
extend: {},
extend: {
colors: {
//TODO if we're gonna have ligth mode we should add also the light colors cause tailwind doesn't have them
slate5: 'rgba(43, 47, 49, 1)',
slate6: 'rgba(49, 53, 56, 1)',
slate7: 'rgba(58, 63, 66, 1)',
slate11: 'rgba(155, 161, 166, 1)',
slate12: 'rgba(236, 237, 238, 1)',
},
width: {
inherit: 'inherit',
},
},
},
plugins: [],
};

View File

@ -1585,6 +1585,13 @@
resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==
"@headlessui/react@^1.7.8":
version "1.7.8"
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.7.8.tgz#d7b4a95d50f9a386f7d1259d5ff8a6d7eb552ef0"
integrity sha512-zcwb0kd7L05hxmoAMIioEaOn235Dg0fUO+iGbLPgLVSjzl/l39V6DTpC2Df49PE5aG5/f5q0PZ9ZHZ78ENNV+A==
dependencies:
client-only "^0.0.1"
"@humanwhocodes/config-array@^0.11.8":
version "0.11.8"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9"
@ -4611,6 +4618,11 @@ cli-table3@^0.6.1:
optionalDependencies:
"@colors/colors" "1.5.0"
client-only@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
cliui@^7.0.2:
version "7.0.4"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"