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:
parent
292f550466
commit
33ebac510c
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export * from './combobox';
|
||||
export * from './dropdown';
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -1,2 +1,4 @@
|
|||
export * from './button';
|
||||
export * from './combobox';
|
||||
export * from './icon';
|
||||
export * from './input';
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ export const spacing = {
|
|||
52: '13rem',
|
||||
56: '14rem',
|
||||
60: '15rem',
|
||||
62: '15.5rem',
|
||||
64: '16rem',
|
||||
72: '18rem',
|
||||
80: '20rem',
|
||||
|
|
|
|||
|
|
@ -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 }),
|
||||
|
|
|
|||
|
|
@ -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: [],
|
||||
};
|
||||
|
|
|
|||
12
ui/yarn.lock
12
ui/yarn.lock
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue