Comments (1)
I liked this a lot. Added a validator to it that can optionally be passed in:
'use client'
import { forwardRef, useEffect, useState } from 'react'
import { Option, pipe, ReadonlyArray, String } from 'effect'
import { XIcon } from 'lucide-react'
import type { z } from 'zod'
import { noOp } from '@steepleinc/shared'
import { Badge } from '~/components/ui/badge'
import { Button } from '~/components/ui/button'
import { cn } from '~/shared/utils'
import type { InputProps } from './input'
const parseTagOpt = (params: { tag: string; tagValidator: z.ZodString }) => {
const { tag, tagValidator } = params
const parsedTag = tagValidator.safeParse(tag)
if (parsedTag.success) {
return pipe(parsedTag.data, Option.some)
}
return Option.none()
}
type TagInputProps = Omit<InputProps, 'value' | 'onChange'> & {
value?: ReadonlyArray<string>
onChange: (value: ReadonlyArray<string>) => void
tagValidator?: z.ZodString
}
const TagInput = forwardRef<HTMLInputElement, TagInputProps>((props, ref) => {
const { className, value = [], onChange, tagValidator, ...domProps } = props
const [pendingDataPoint, setPendingDataPoint] = useState('')
useEffect(() => {
if (pendingDataPoint.includes(',')) {
const newDataPoints = new Set([
...value,
...pipe(
pendingDataPoint,
String.split(','),
ReadonlyArray.filterMap((x) => {
const trimmedX = pipe(x, String.trim)
return pipe(
tagValidator,
Option.fromNullable,
Option.match({
onNone: () => pipe(trimmedX, Option.some),
onSome: (y) => parseTagOpt({ tag: trimmedX, tagValidator: y }),
}),
)
}),
),
])
onChange(Array.from(newDataPoints))
setPendingDataPoint('')
}
}, [pendingDataPoint, onChange, value, tagValidator])
const addPendingDataPoint = () => {
if (pendingDataPoint) {
pipe(
tagValidator,
Option.fromNullable,
Option.match({
onNone: () => {
const newDataPoints = new Set([...value, pendingDataPoint])
onChange(Array.from(newDataPoints))
setPendingDataPoint('')
},
onSome: (y) =>
pipe(
parseTagOpt({ tag: pendingDataPoint, tagValidator: y }),
Option.match({
onNone: noOp,
onSome: (x) => {
const newDataPoints = new Set([...value, x])
onChange(Array.from(newDataPoints))
setPendingDataPoint('')
},
}),
),
}),
)
}
}
return (
<div
className={cn(
// caveat: :has() variant requires tailwind v3.4 or above: https://tailwindcss.com/blog/tailwindcss-v3-4#new-has-variant
'has-[:focus-visible]:ring-neutral-950 dark:has-[:focus-visible]:ring-neutral-300 border-neutral-200 dark:border-neutral-800 dark:bg-neutral-950 dark:ring-offset-neutral-950 flex min-h-10 w-full flex-wrap gap-2 rounded-md border bg-white px-3 py-2 text-sm ring-offset-white disabled:cursor-not-allowed disabled:opacity-50 has-[:focus-visible]:outline-none has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-offset-2',
className,
)}
>
{value.map((item) => (
<Badge key={item} variant={'secondary'}>
{item}
<Button
variant={'ghost'}
size={'icon'}
className={'ml-2 h-3 w-3'}
onClick={() => {
onChange(value.filter((i) => i !== item))
}}
>
<XIcon className={'w-3'} />
</Button>
</Badge>
))}
<input
className={
'placeholder:text-neutral-500 dark:placeholder:text-neutral-400 flex-1 outline-none'
}
value={pendingDataPoint}
onChange={(e) => setPendingDataPoint(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ',') {
e.preventDefault()
addPendingDataPoint()
} else if (
e.key === 'Backspace' &&
pendingDataPoint.length === 0 &&
value.length > 0
) {
e.preventDefault()
onChange(value.slice(0, -1))
}
}}
{...domProps}
ref={ref}
/>
</div>
)
})
TagInput.displayName = 'TagInput'
export { TagInput }
from ui.
Related Issues (20)
- Unintended Click Event Propagation from Sort Dropdown to Product Card in Next.js 14.2.2 Application [bug]:
- [bug]: Issue when using cloudflare zero trust (with extended tls decryption)
- [bug]: The command is disabled and never selectable.
- [bug]: The source code for the combobox form has a bug
- [bug]: Wrong selector for select component placeholder HOT 4
- [bug]: Script not found HOT 2
- [Accessibility]: Use <input list=""> or <select multiple > instead of combo-box to ensure accessibility across all devices
- [feat]: Sheet compatibility with Resizable
- [bug]: Multiple RadioGroup with overlapping Item values will conflict HOT 1
- [feat]: Fix the bun commands HOT 1
- [bug]: Error: 'React' is not defined. no-undef in sonner component
- [bug]: Error in documentation of combobox HOT 4
- [bug]: CLI for vite doesn't include index.html HOT 1
- [bug]: Unable to install HOT 3
- [bug]: AlertDialog does not come up on trigger click when in full screen mode of browser HOT 2
- [bug]: Sheet + Sonner toaster - toasts don't open up on mouseover HOT 1
- [bug]: Error when running npx shadcn-ui@latest init and npx shadcn-ui@latest add button with Node.js v20.13.1
- [feat]: Need a Rating component HOT 1
- [feat]: new date on click HOT 1
- [feat]: Event Calendar
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from ui.