Skip to content

Commit

Permalink
✨ feat: implement TagSelect context and components with type definitions
Browse files Browse the repository at this point in the history
  • Loading branch information
futjesus committed Feb 13, 2025
1 parent 9cfa5a9 commit 74c5d56
Show file tree
Hide file tree
Showing 19 changed files with 383 additions and 210 deletions.
50 changes: 35 additions & 15 deletions lib/components/Tag/Tag.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,41 @@ const meta: Meta<typeof TagComponent> = {
export const Tag: Story = {
render: () => (
<div className="w-[350px] flex flex-col gap-3">
<TagComponent label="This is a tag with default color" />
<TagComponent color="gray" label="This is a tag gray" />
<TagComponent color="cyan" label="This is a tag cyan" />
<TagComponent color="gold" label="This is a tag gold" />
<TagComponent color="green" label="This is a tag green" />
<TagComponent color="light blue" label="This is a tag light blue" />
<TagComponent color="lime" label="This is a tag lime" />
<TagComponent color="pink" label="This is a tag pink" />
<TagComponent color="purple" label="This is a tag purple" />
<TagComponent color="emerald" label="This is a tag emerald" />
<TagComponent color="fuscia" label="This is a tag fuscia" />
<TagComponent color="indigo" label="This is a tag indigo" />
<TagComponent color="light-orange" label="This is a tag light-orange" />
<TagComponent color="dark-sky-blue" label="This is a tag dark-sky-blue" />
<TagComponent color="mistery" label="This is a tag mistery" />
<TagComponent id="default" label="This is a tag with default color" />
<TagComponent id="gray" color="gray" label="This is a tag gray" />
<TagComponent id="cyan" color="cyan" label="This is a tag cyan" />
<TagComponent id="gold" color="gold" label="This is a tag gold" />
<TagComponent id="green" color="green" label="This is a tag green" />
<TagComponent
id="light"
color="light blue"
label="This is a tag light blue"
/>
<TagComponent id="lime" color="lime" label="This is a tag lime" />
<TagComponent id="pink" color="pink" label="This is a tag pink" />
<TagComponent id="purple" color="purple" label="This is a tag purple" />
<TagComponent
id="emerald"
color="emerald"
label="This is a tag emerald"
/>
<TagComponent id="fuscia" color="fuscia" label="This is a tag fuscia" />
<TagComponent id="indigo" color="indigo" label="This is a tag indigo" />
<TagComponent
id="light"
color="light-orange"
label="This is a tag light-orange"
/>
<TagComponent
id="dark"
color="dark-sky-blue"
label="This is a tag dark-sky-blue"
/>
<TagComponent
id="mistery"
color="mistery"
label="This is a tag mistery"
/>
</div>
),
};
Expand Down
1 change: 1 addition & 0 deletions lib/components/Tag/Tag.types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export type TagProps = {
id: string | number;
label: string;
color?:
| 'gray'
Expand Down
28 changes: 14 additions & 14 deletions lib/components/TagSelect/TagSelect.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,20 @@ const meta: Meta<typeof TagSelectComponent> = {
export const TagSelect: Story = {
args: {
options: [
{ color: 'gray', label: 'gray' },
{ color: 'cyan', label: 'cyan' },
{ color: 'gold', label: 'gold' },
{ color: 'green', label: 'green' },
{ color: 'light blue', label: 'light blue' },
{ color: 'lime', label: 'lime' },
{ color: 'pink', label: 'pink' },
{ color: 'purple', label: 'purple' },
{ color: 'emerald', label: 'emerald' },
{ color: 'fuscia', label: 'fuscia' },
{ color: 'indigo', label: 'indigo' },
{ color: 'light-orange', label: 'light-orange' },
{ color: 'dark-sky-blue', label: 'dark-sky-blue' },
{ color: 'mistery', label: 'mistery' },
{ id: '1', color: 'gray', label: 'gray' },
{ id: '2', color: 'cyan', label: 'cyan' },
{ id: '3', color: 'gold', label: 'gold' },
{ id: '4', color: 'green', label: 'green' },
{ id: '5', color: 'light blue', label: 'light blue' },
{ id: '6', color: 'lime', label: 'lime' },
{ id: '7', color: 'pink', label: 'pink' },
{ id: '8', color: 'purple', label: 'purple' },
{ id: '9', color: 'emerald', label: 'emerald' },
{ id: '10', color: 'fuscia', label: 'fuscia' },
{ id: '11', color: 'indigo', label: 'indigo' },
{ id: '12', color: 'light-orange', label: 'light-orange' },
{ id: '13', color: 'dark-sky-blue', label: 'dark-sky-blue' },
{ id: '14', color: 'mistery', label: 'mistery' },
],
},
render: (args) => (
Expand Down
138 changes: 8 additions & 130 deletions lib/components/TagSelect/TagSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,136 +1,14 @@
import {
ComponentRef,
FC,
forwardRef,
useEffect,
useId,
useImperativeHandle,
useRef,
} from 'react';
import { ChevronUp } from 'react-feather';
import { FC, forwardRef } from 'react';

import { Tag } from '@/components/Tag/Tag';
import { useTheme } from '@/contexts';
import { cn } from '@/utils';

import { useTagSelect } from './hooks/useTagSelect';
import { Wrapper } from './components';
import { TagSelectProvider } from './contexts';
import { TagSelectProps } from './TagSelect.types';
import {
labelVariants,
tagItemVariants,
tagListVariants,
tagSelectVariants,
wrapperTagSelectVariants,
} from './TagSelect.variants';

export const TagSelect: FC<TagSelectProps> = forwardRef<
HTMLInputElement,
TagSelectProps
>(
(
{
label,
name,
options,
placeholder = 'Select a value...',
theme,
labelClassName,
wrapperClassName,
},
ref,
) => {
const id = useId();
const { theme: themeContext } = useTheme();
const inheritTheme = theme ?? themeContext;
const inputRef = useRef<ComponentRef<'input'>>(null);
const {
isOpen,
selectedTag,
value,
wrapperRef,
handleClickTag,
handleOpenDropdown,
} = useTagSelect();

useImperativeHandle(ref, () => inputRef.current!, [inputRef]);

useEffect(() => {
if (inputRef.current) {
inputRef.current.value = value;
}
}, [value]);

return (
<div
ref={wrapperRef}
className={cn(
wrapperTagSelectVariants({
theme: inheritTheme,
className: wrapperClassName,
}),
)}
>
{label ? (
<label
htmlFor={name ?? id}
className={cn(
labelVariants({
theme: inheritTheme,
className: labelClassName,
}),
)}
onClick={() => handleOpenDropdown(true)}
>
{label}
</label>
) : null}

<div
id={name ?? id}
className={cn(tagSelectVariants({ theme: inheritTheme }))}
role="combobox"
onClick={() => handleOpenDropdown()}
aria-expanded={isOpen}
>
{!selectedTag ? (
<span className="text-base text-inherit">{placeholder}</span>
) : (
<Tag {...selectedTag} />
)}

<ChevronUp
className={cn(
'w-4 h-4 text-inherit transition-all duration-50',
isOpen ? 'rotate-0' : 'rotate-180',
)}
/>
</div>

<input ref={inputRef} type="text" name={name} className="hidden" />

{isOpen ? (
<ul
role="listbox"
className={cn(tagListVariants({ theme: inheritTheme }))}
>
{options.map((tag) => (
<li
className={cn(tagItemVariants({ theme: inheritTheme }))}
role="option"
>
<button
type="button"
role="button"
className="m-0 p-0 w-full"
onClick={() => handleClickTag(tag)}
>
<Tag label={tag.label} color={tag.color} />
</button>
</li>
))}
</ul>
) : null}
</div>
);
},
);
>(({ ...delegated }, ref) => (
<TagSelectProvider>
<Wrapper ref={ref} {...delegated} />
</TagSelectProvider>
));
57 changes: 6 additions & 51 deletions lib/components/TagSelect/TagSelect.variants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { cva } from 'class-variance-authority';

export const wrapperTagSelectVariants = cva(
export const wrapperVariants = cva(
['flex', 'flex-col', 'w-full', 'relative', 'text-zinc-500'],
{
variants: {
Expand Down Expand Up @@ -47,10 +47,12 @@ export const tagSelectVariants = cva(
{
variants: {
theme: {
colony: ['aria-expanded:border-red-400', 'aria-expanded:text-red-400'],
colony: [
/*'aria-expanded:border-red-400', 'aria-expanded:text-red-400'*/
],
kubefirst: [
'aria-expanded:border-kubefirst-primary',
'aria-expanded:text-kubefirst-secondary',
// 'aria-expanded:border-kubefirst-primary',
// 'aria-expanded:text-kubefirst-secondary',
],
civo: '',
},
Expand All @@ -60,50 +62,3 @@ export const tagSelectVariants = cva(
},
},
);

export const tagListVariants = cva(
[
'absolute',
'bg-white',
'border',
'duration-100',
'ease-in-out',
'flex',
'flex-col',
'mt-1',
'rounded-md',
'shadow-sm',
'top-full',
'transition-all',
'w-full',
'z-10',
],
{
variants: {
theme: {
colony: ['border-red-400'],
kubefirst: ['border-kubefirst-primary'],
civo: '',
},
},
defaultVariants: {
theme: 'kubefirst',
},
},
);

export const tagItemVariants = cva(
['cursor-pointer', 'py-1', 'px-2', 'last:mb-2', 'first:mt-2', 'h-full'],
{
variants: {
theme: {
colony: 'hover:bg-red-100',
kubefirst: 'hover:bg-purple-100',
civo: '',
},
},
defaultVariants: {
theme: 'kubefirst',
},
},
);
26 changes: 26 additions & 0 deletions lib/components/TagSelect/components/Item/Item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { FC } from 'react';

import { Tag } from '@/components/Tag/Tag';
import { useTheme } from '@/contexts';
import { cn } from '@/utils';

import { ItemProps } from './Item.types';
import { wrapperVariants } from './Item.variants';

export const Item: FC<ItemProps> = ({ option, theme, handleClickTag }) => {
const { theme: contextTheme } = useTheme();
const inheritTheme = theme ?? contextTheme;

return (
<li className={cn(wrapperVariants({ theme: inheritTheme }))} role="option">
<button
type="button"
role="button"
className="m-0 p-0 w-full"
onClick={() => handleClickTag(option)}
>
<Tag {...option} />
</button>
</li>
);
};
9 changes: 9 additions & 0 deletions lib/components/TagSelect/components/Item/Item.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { TagProps } from '@/components/Tag/Tag.types';

import { ListProps } from '../List/List.types';

export type ItemProps = {
option: TagProps;
theme: ListProps['theme'];
handleClickTag: (option: TagProps) => void;
};
17 changes: 17 additions & 0 deletions lib/components/TagSelect/components/Item/Item.variants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { cva } from 'class-variance-authority';

export const wrapperVariants = cva(
['cursor-pointer', 'py-1', 'px-2', 'last:mb-2', 'first:mt-2', 'h-full'],
{
variants: {
theme: {
colony: 'hover:bg-red-100',
kubefirst: 'hover:bg-purple-100',
civo: '',
},
},
defaultVariants: {
theme: 'kubefirst',
},
},
);
26 changes: 26 additions & 0 deletions lib/components/TagSelect/components/List/List.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { FC } from 'react';

import { cn } from '@/utils';
import { useTheme } from '@/contexts';

import { Item } from '../Item/Item';

import { ListProps } from './List.types';
import { wrapperVariants } from './List.variants';

export const List: FC<ListProps> = ({ theme, options, handleClickTag }) => {
const { theme: contextTheme } = useTheme();
const inheritTheme = theme ?? contextTheme;

return (
<ul role="listbox" className={cn(wrapperVariants({ theme: inheritTheme }))}>
{options.map((option) => (
<Item
theme={inheritTheme}
option={option}
handleClickTag={handleClickTag}
/>
))}
</ul>
);
};
Loading

0 comments on commit 74c5d56

Please sign in to comment.