Skip to content

Commit

Permalink
add dropdownchipinput
Browse files Browse the repository at this point in the history
  • Loading branch information
KhudaDad414 committed Jun 11, 2024
1 parent 786c802 commit 739dd5c
Show file tree
Hide file tree
Showing 8 changed files with 240 additions and 13 deletions.
3 changes: 2 additions & 1 deletion apps/design-system/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ npm-debug.log
.DS_Store# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
.turbo
storybook-static
src/styles/tailwind.output.css

# dependencies
/node_modules
Expand All @@ -26,4 +27,4 @@ storybook-static

npm-debug.log*
yarn-debug.log*
yarn-error.log*
yarn-error.log*
28 changes: 28 additions & 0 deletions apps/design-system/src/components/Chip.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Meta, StoryObj } from '@storybook/react';
import { fn } from '@storybook/test';
import { Chip } from '@asyncapi/studio-ui';


const meta: Meta<typeof Chip> = {
component: Chip,
parameters: {
layout: "fullscreen",
backgrounds: {
default: "dark",
},
},
args: {
onDelete: fn()
}
}
export default meta;


type Story = StoryObj<typeof Chip>


export const Default: Story = {
args: {
chip: 'Chip',
},
}
64 changes: 64 additions & 0 deletions apps/design-system/src/components/DropdownChipInput.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Meta, StoryObj } from '@storybook/react';
import { DropdownChipInput } from '@asyncapi/studio-ui';
import { useState } from 'react';

const meta: Meta<typeof DropdownChipInput> = {
component: DropdownChipInput,
parameters: {
layout: "fullscreen",
backgrounds: {
default: "light",
},
}
}
export default meta;


type Story = StoryObj<typeof DropdownChipInput>

const OPTIONS = ["string", "number", "boolean", "object", "array", "null"];

export const Default: Story = {
render: () => {
const [currentChips, setCurrentChips] = useState<string[]>(["boolean"]);
return (
<DropdownChipInput
className='w-96'
id="chip-input-id-chip-text"
chips={currentChips}
onChange={setCurrentChips}
chipsOptions={OPTIONS}/>
)
}
}


export const Disabled: Story = {
render: () => {
const [currentChips, setCurrentChips] = useState<string[]>(["string"]);
return (
<DropdownChipInput
className='w-96'
id="chip-input-id-chip-text"
chips={currentChips}
onChange={setCurrentChips}
isDisabled={true}
chipsOptions={OPTIONS}/>
)
}
}

export const WithPlaceholder: Story = {
render: () => {
const [currentChips, setCurrentChips] = useState<string[]>([]);
return (
<DropdownChipInput
className='w-96'
placeholder='Add a chip'
id="chip-input-id-chip-text"
chips={currentChips}
onChange={setCurrentChips}
chipsOptions={OPTIONS}/>
)
}
}
35 changes: 35 additions & 0 deletions packages/ui/components/Chip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from "react"
import { cn } from "@asyncapi/studio-utils"
interface ChipInputProps {
chip: string
onDelete: (chip: string) => void
}

export const Chip = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement> & ChipInputProps>(
({ chip, onDelete,className , ...props }, ref) => {
return (
<div
className={cn("m-1 w-fit bg-gray-100 text-gray-900 rounded px-2 py-1 flex items-center border border-gray-400 focus:border-blue-500 focus:border-2 focus:outline-none", className)}
style={{ height: "28px", borderStyle: "solid" }}
ref={ref}
onClick={(e) => {
console.log("Chip clicked")
}}
{...props}
>
<span>{chip}</span>
<button
onClick={(e) => {
console.log("Delete button clicked")
onDelete(chip)
}}
tabIndex={-1}
className="ml-1 text-gray-400 focus:outline-none"
aria-label="Close"
>
×
</button>
</div>
)
}
)
15 changes: 3 additions & 12 deletions packages/ui/components/ChipInput.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FunctionComponent, KeyboardEvent, useRef } from 'react';
import { Chip } from './Chip';

interface ChipInputProps {
name: string;
Expand Down Expand Up @@ -44,25 +45,15 @@ export const ChipInput: FunctionComponent<ChipInputProps> = ({
}
};

const handleDelete = (chipToDelete: string) => () => {
const handleDelete = (chipToDelete: string) => {
const updatedChips = chips.filter(chip => chip !== chipToDelete);
onChange(updatedChips);
};

return (
<div className={`${className} flex flex-wrapitems-center p-1 bg-gray-900 rounded border border-gray-800`} style={{ width: '862px', height: '46px' }}>
{chips.map((chip, index) => (
<div
key={chip}
className="m-1 bg-gray-100 text-gray-900 rounded px-2 py-1 flex items-center border border-gray-400 focus:border-blue-500 focus:border-2 focus:outline-none"
style={{ height: '28px', borderStyle: 'solid' }}
tabIndex={0}
onKeyDown={handleChipKeyDown(index)}
ref={index === 0 ? firstChipRef : undefined}
>
<span>{chip}</span>
<button onClick={handleDelete(chip)} tabIndex={-1} className="ml-1 text-gray-400 focus:outline-none" aria-label='Close'>×</button>
</div>
<Chip key={chip} chip={chip} tabIndex={0} onDelete={handleDelete} onKeyDown={handleChipKeyDown(index)} ref={index === 0 ? firstChipRef : undefined} />
))}
<input
ref={inputRef}
Expand Down
81 changes: 81 additions & 0 deletions packages/ui/components/DropdownChipInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"use client"

import { FunctionComponent, KeyboardEvent, useRef } from 'react';
import { DropdownMenuCheckboxItemProps } from "@radix-ui/react-dropdown-menu"
import { Chip } from './Chip';
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuTrigger,
} from "./DropdownMenu"
import { PlusCircleIcon } from './icons';
import { cn } from '@asyncapi/studio-utils';

type Checked = DropdownMenuCheckboxItemProps["checked"]

interface DropdownChipInputProps {
id: string;
className?: string;
chips: string[];
chipsOptions: string[];
placeholder?: string;
onChange: (chips: string[]) => void;
isDisabled?: boolean;
}

export const DropdownChipInput: FunctionComponent<DropdownChipInputProps> = ({
className,
chips,
chipsOptions,
onChange,
placeholder,
isDisabled = false,
}) => {
const firstChipRef = useRef<HTMLDivElement>(null);


const onChipCheckedChange = (chip: string) => {
if (chips.includes(chip)) {
handleDelete(chip);
} else {
onChange([...chips, chip]);
}
}
const handleChipKeyDown = (index: number) => (event: KeyboardEvent<HTMLDivElement>) => {
event.stopPropagation();
if (event.key === 'Backspace') {
const updatedChips = [...chips];
updatedChips.splice(index, 1);
onChange(updatedChips);
}
};

const handleDelete = (chipToDelete: string) => {
const updatedChips = chips.filter(chip => chip !== chipToDelete);
onChange(updatedChips);
};

return (
<div className={cn("flex h-11 p-1 bg-gray-900 rounded border border-gray-800 items-center",isDisabled && "opacity-50 pointer-events-none", className)}>
<div className='flex overflow-scroll no-scrollbar'>
{chips.length === 0 && placeholder && <span className='text-gray-500'>{placeholder}</span>}
{chips.map((chip, index) => (
<Chip key={chip} chip={chip} tabIndex={0} onDelete={handleDelete} onKeyDown={handleChipKeyDown(index)} ref={index === 0 ? firstChipRef : undefined} />
))}
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<PlusCircleIcon className='min-w-7 w-7 h-7 min-h-7 text-gray-500 ml-auto ' />
</DropdownMenuTrigger>
<DropdownMenuContent>
{chipsOptions.map((chipOption) => (
<DropdownMenuCheckboxItem checked={chips.includes(chipOption)} onCheckedChange={() => onChipCheckedChange(chipOption)}>
{chipOption}
</DropdownMenuCheckboxItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
);
};
25 changes: 25 additions & 0 deletions packages/ui/components/DropdownMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"

import { cn } from "@asyncapi/studio-utils"
import { CheckIcon } from './icons'

const DropdownMenu = DropdownMenuPrimitive.Root

Expand Down Expand Up @@ -66,12 +67,36 @@ const DropdownMenuSeparator = React.forwardRef<
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName

const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"flex items-center gap-2 text-gray-200 text-sm leading-7 px-2 cursor-pointer rounded outline-none select-none hover:bg-gray-700 focus:bg-gray-700",
className
)}
checked={checked}
{...props}
>
<span className="left-2 flex h-3.5 w-3.5">
<DropdownMenuPrimitive.ItemIndicator>
<CheckIcon className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName

export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuSeparator,
DropdownMenuGroup,
DropdownMenuPortal,
Expand Down
2 changes: 2 additions & 0 deletions packages/ui/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import './styles.css'

// components
export * from './Chip'
export * from './DropdownChipInput'
export * from './ChipInput'
export * from './EditorSwitch'
export * from './DropdownMenu'
Expand Down

0 comments on commit 739dd5c

Please sign in to comment.