Skip to content

Commit

Permalink
feat(tag-input): add flexible styling api for tag input elements and …
Browse files Browse the repository at this point in the history
…subcomponents
  • Loading branch information
JaleelB committed Jun 23, 2024
1 parent d227f82 commit 96cc82a
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 33 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/code-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ jobs:
- name: Run typecheck in website workspace
run: pnpm -C website run typecheck

- name: Run typecheck in packages workspace
run: pnpm -C packages/emblor run typecheck
# - name: Run typecheck in packages workspace
# run: pnpm -C packages/emblor run typecheck

- name: Check prettier format
run: pnpm run prettier-check
Expand Down
16 changes: 9 additions & 7 deletions packages/emblor/src/tag/autocomplete.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Command, CommandList, CommandItem, CommandGroup, CommandEmpty } from '../ui/command';
import { type Tag as TagType } from './tag-input';
import { TagInputStyleClassesProps, type Tag as TagType } from './tag-input';
import { cn } from '../utils';
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popoever';
import { Button } from '../ui/button';
Expand All @@ -14,6 +14,7 @@ type AutocompleteProps = {
allowDuplicates: boolean;
children: React.ReactNode;
inlineTags?: boolean;
classStyleProps: TagInputStyleClassesProps['autoComplete'];
};

export const Autocomplete: React.FC<AutocompleteProps> = ({
Expand All @@ -25,6 +26,7 @@ export const Autocomplete: React.FC<AutocompleteProps> = ({
allowDuplicates,
inlineTags,
children,
classStyleProps,
}) => {
const triggerContainerRef = useRef<HTMLDivElement | null>(null);
const triggerRef = useRef<HTMLButtonElement | null>(null);
Expand Down Expand Up @@ -130,7 +132,7 @@ export const Autocomplete: React.FC<AutocompleteProps> = ({
};

return (
<Command className="w-full h-full">
<Command className={cn('w-full h-full', classStyleProps.command)}>
<Popover open={isPopoverOpen} onOpenChange={handleOpenChange}>
<div
className="relative h-full flex items-center rounded-md border border-input bg-transparent pr-3"
Expand All @@ -146,7 +148,7 @@ export const Autocomplete: React.FC<AutocompleteProps> = ({
variant="ghost"
size="icon"
role="combobox"
className={cn(`hover:bg-transparent ${!inlineTags ? 'ml-auto' : ''}`)}
className={cn(`hover:bg-transparent ${!inlineTags ? 'ml-auto' : ''}`, classStyleProps.popoverTrigger)}
onClick={() => setIsPopoverOpen(!isPopoverOpen)}
>
<svg
Expand All @@ -170,18 +172,18 @@ export const Autocomplete: React.FC<AutocompleteProps> = ({
ref={popoverContentRef}
side="bottom"
align="start"
className={cn(`p-0 relative`)}
className={cn(`p-0 relative`, classStyleProps.popoverContent)}
style={{
top: `${popooverContentTop}px`,
marginLeft: `calc(-${popoverWidth}px + 36px)`,
width: `${popoverWidth}px`,
}}
>
<CommandList>
<CommandList className={cn(classStyleProps?.commandList)}>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions" className={cn('overflow-y-auto')}>
<CommandGroup heading="Suggestions" className={cn('overflow-y-auto', classStyleProps.commandGroup)}>
{autocompleteOptions.map((option) => (
<CommandItem key={option.id} className="cursor-pointer">
<CommandItem key={option.id} className={cn('cursor-pointer', classStyleProps.commandItem)}>
<div className="w-full flex items-center gap-2" onClick={() => toggleTag(option)}>
{option.text}
{tags.some((tag) => tag.text === option.text) && (
Expand Down
82 changes: 69 additions & 13 deletions packages/emblor/src/tag/tag-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ export type Tag = {
text: string;
};

interface TagInputStyleClassesProps {
export interface TagInputStyleClassesProps {
inlineTagsContainer?: string;
tagPopover?: {
content?: string;
trigger?: string;
popoverTrigger?: string;
popoverContent?: string;
};
tagList?: {
container?: string;
Expand All @@ -42,7 +42,7 @@ interface TagInputStyleClassesProps {
commandItem?: string;
};
tag?: {
container?: string;
body?: string;
closeButton?: string;
};
input?: string;
Expand Down Expand Up @@ -100,7 +100,7 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>((props, ref)
variant,
size,
shape,
className,
// className,
enableAutocomplete,
autocompleteOptions,
maxTags,
Expand Down Expand Up @@ -322,12 +322,19 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>((props, ref)
inlineTags={inlineTags}
activeTagIndex={activeTagIndex}
setActiveTagIndex={setActiveTagIndex}
classStyleProps={{
tagListClasses: styleClasses?.tagList,
tagClasses: styleClasses?.tag,
}}
/>
) : (
!enableAutocomplete && (
<div className="w-full">
<div
className={`flex flex-row flex-wrap items-center gap-2 p-2 w-full rounded-md border border-input bg-background text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50`}
className={cn(
`flex flex-row flex-wrap items-center gap-2 p-2 w-full rounded-md border border-input bg-background text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50`,
styleClasses?.inlineTagsContainer,
)}
>
<TagList
tags={truncatedTags}
Expand All @@ -348,6 +355,10 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>((props, ref)
inlineTags={inlineTags}
activeTagIndex={activeTagIndex}
setActiveTagIndex={setActiveTagIndex}
classStyleProps={{
tagListClasses: styleClasses?.tagList,
tagClasses: styleClasses?.tag,
}}
/>
<Input
ref={inputRef}
Expand All @@ -362,7 +373,8 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>((props, ref)
{...inputProps}
className={cn(
'border-0 h-5 bg-transparent focus-visible:ring-0 focus-visible:ring-transparent focus-visible:ring-offset-0 flex-1 w-fit',
className,
// className,
styleClasses?.input,
)}
autoComplete={enableAutocomplete ? 'on' : 'off'}
list={enableAutocomplete ? 'autocomplete-options' : undefined}
Expand All @@ -382,6 +394,14 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>((props, ref)
onTagAdd={onTagAdd}
allowDuplicates={allowDuplicates ?? false}
inlineTags={inlineTags}
classStyleProps={{
command: styleClasses?.autoComplete?.command,
popoverTrigger: styleClasses?.autoComplete?.popoverTrigger,
popoverContent: styleClasses?.autoComplete?.popoverContent,
commandList: styleClasses?.autoComplete?.commandList,
commandGroup: styleClasses?.autoComplete?.commandGroup,
commandItem: styleClasses?.autoComplete?.commandItem,
}}
>
{!usePopoverForTags ? (
!inlineTags ? (
Expand All @@ -394,11 +414,18 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>((props, ref)
onKeyDown={handleKeyDown}
onFocus={handleInputFocus}
onBlur={handleInputBlur}
className={cn('w-full', className)}
className={cn(
'w-full',
// className,
styleClasses?.input,
)}
/>
) : (
<div
className={`flex flex-row flex-wrap items-center p-2 gap-2 h-fit w-full bg-background text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50`}
className={cn(
`flex flex-row flex-wrap items-center p-2 gap-2 h-fit w-full bg-background text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50`,
styleClasses?.inlineTagsContainer,
)}
>
<TagList
tags={truncatedTags}
Expand All @@ -419,6 +446,10 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>((props, ref)
inlineTags={inlineTags}
activeTagIndex={activeTagIndex}
setActiveTagIndex={setActiveTagIndex}
classStyleProps={{
tagListClasses: styleClasses?.tagList,
tagClasses: styleClasses?.tag,
}}
/>
<CommandInput
placeholder={maxTags !== undefined && tags.length >= maxTags ? placeholderWhenFull : placeholder}
Expand All @@ -430,7 +461,11 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>((props, ref)
onFocus={handleInputFocus}
onBlur={handleInputBlur}
inlineTags={inlineTags}
className={cn('border-0 flex-1 w-fit h-5', className)}
className={cn(
'border-0 flex-1 w-fit h-5',
// className,
styleClasses?.input,
)}
/>
</div>
)
Expand All @@ -453,6 +488,11 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>((props, ref)
direction={direction}
activeTagIndex={activeTagIndex}
setActiveTagIndex={setActiveTagIndex}
classStyleProps={{
popoverClasses: styleClasses?.tagPopover,
tagListClasses: styleClasses?.tagList,
tagClasses: styleClasses?.tag,
}}
>
<CommandInput
placeholder={maxTags !== undefined && tags.length >= maxTags ? placeholderWhenFull : placeholder}
Expand All @@ -463,7 +503,11 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>((props, ref)
onKeyDown={handleKeyDown}
onFocus={handleInputFocus}
onBlur={handleInputBlur}
className={cn('w-full', className)}
className={cn(
'w-full',
// className,
styleClasses?.input,
)}
/>
</TagPopover>
)}
Expand All @@ -484,7 +528,10 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>((props, ref)
onFocus={handleInputFocus}
onBlur={handleInputBlur}
{...inputProps}
className={className}
className={cn(
styleClasses?.input,
// className
)}
autoComplete={enableAutocomplete ? 'on' : 'off'}
list={enableAutocomplete ? 'autocomplete-options' : undefined}
disabled={maxTags !== undefined && tags.length >= maxTags}
Expand All @@ -509,6 +556,11 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>((props, ref)
direction={direction}
activeTagIndex={activeTagIndex}
setActiveTagIndex={setActiveTagIndex}
classStyleProps={{
popoverClasses: styleClasses?.tagPopover,
tagListClasses: styleClasses?.tagList,
tagClasses: styleClasses?.tag,
}}
>
<Input
ref={inputRef}
Expand All @@ -524,7 +576,11 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>((props, ref)
autoComplete={enableAutocomplete ? 'on' : 'off'}
list={enableAutocomplete ? 'autocomplete-options' : undefined}
disabled={maxTags !== undefined && tags.length >= maxTags}
className={cn('border-0 w-full', className)}
className={cn(
'border-0 w-full',
styleClasses?.input,
// className
)}
/>
</TagPopover>
)}
Expand Down
31 changes: 25 additions & 6 deletions packages/emblor/src/tag/tag-list.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { type Tag as TagType } from './tag-input';
import { TagInputStyleClassesProps, type Tag as TagType } from './tag-input';
import { Tag, TagProps } from './tag';
import SortableList, { SortableItem } from 'react-easy-sort';
import { cn } from '../utils';
Expand All @@ -13,6 +13,10 @@ export type TagListProps = {
inlineTags?: boolean;
activeTagIndex?: number | null;
setActiveTagIndex?: (index: number | null) => void;
classStyleProps: {
tagListClasses: TagInputStyleClassesProps['tagList'];
tagClasses: TagInputStyleClassesProps['tag'];
};
} & Omit<TagProps, 'tagObj'>;

const DropTarget: React.FC = () => {
Expand All @@ -29,6 +33,7 @@ export const TagList: React.FC<TagListProps> = ({
inlineTags,
activeTagIndex,
setActiveTagIndex,
classStyleProps,
...tagListProps
}) => {
const [draggedTagId, setDraggedTagId] = React.useState<string | null>(null);
Expand All @@ -45,13 +50,23 @@ export const TagList: React.FC<TagListProps> = ({
<>
{!inlineTags ? (
<div
className={cn('rounded-md w-full', className, {
'flex flex-wrap gap-2': direction === 'row',
'flex flex-col gap-2': direction === 'column',
})}
className={cn(
'rounded-md w-full',
// className,
{
'flex flex-wrap gap-2': direction === 'row',
'flex flex-col gap-2': direction === 'column',
},
classStyleProps?.tagListClasses?.container,
)}
>
{draggable ? (
<SortableList onSortEnd={onSortEnd} className="flex flex-wrap gap-2 list" dropTarget={<DropTarget />}>
<SortableList
onSortEnd={onSortEnd}
// className="flex flex-wrap gap-2 list"
className={`flex flex-wrap gap-2 list ${classStyleProps?.tagListClasses?.sortableList}`}
dropTarget={<DropTarget />}
>
{tags.map((tagObj, index) => (
<SortableItem key={tagObj.id}>
<div
Expand All @@ -72,6 +87,7 @@ export const TagList: React.FC<TagListProps> = ({
isActiveTag={index === activeTagIndex}
direction={direction}
draggable={draggable}
tagClasses={classStyleProps?.tagClasses}
{...tagListProps}
/>
)}
Expand All @@ -90,6 +106,7 @@ export const TagList: React.FC<TagListProps> = ({
isActiveTag={index === activeTagIndex}
direction={direction}
draggable={draggable}
tagClasses={classStyleProps?.tagClasses}
{...tagListProps}
/>
),
Expand Down Expand Up @@ -120,6 +137,7 @@ export const TagList: React.FC<TagListProps> = ({
isActiveTag={index === activeTagIndex}
direction={direction}
draggable={draggable}
tagClasses={classStyleProps?.tagClasses}
{...tagListProps}
/>
)}
Expand All @@ -138,6 +156,7 @@ export const TagList: React.FC<TagListProps> = ({
isActiveTag={index === activeTagIndex}
direction={direction}
draggable={draggable}
tagClasses={classStyleProps?.tagClasses}
{...tagListProps}
/>
),
Expand Down
Loading

0 comments on commit 96cc82a

Please sign in to comment.