Skip to content

Commit 0823048

Browse files
author
jamesmoore
committed
use shadcn native input group
1 parent 899b538 commit 0823048

File tree

3 files changed

+214
-20
lines changed

3 files changed

+214
-20
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import * as React from "react"
2+
import { cva, type VariantProps } from "class-variance-authority"
3+
4+
import { cn } from "@/lib/utils"
5+
import { Button } from "@/components/ui/button"
6+
import { Input } from "@/components/ui/input"
7+
import { Textarea } from "@/components/ui/textarea"
8+
9+
function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
10+
return (
11+
<div
12+
data-slot="input-group"
13+
role="group"
14+
className={cn(
15+
"group/input-group border-input dark:bg-input/30 shadow-xs relative flex w-full items-center rounded-md border outline-none transition-[color,box-shadow]",
16+
"h-9 has-[>textarea]:h-auto",
17+
18+
// Variants based on alignment.
19+
"has-[>[data-align=inline-start]]:[&>input]:pl-2",
20+
"has-[>[data-align=inline-end]]:[&>input]:pr-2",
21+
"has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
22+
"has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
23+
24+
// Focus state.
25+
"has-[[data-slot=input-group-control]:focus-visible]:ring-ring has-[[data-slot=input-group-control]:focus-visible]:ring-1",
26+
27+
// Error state.
28+
"has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
29+
30+
className
31+
)}
32+
{...props}
33+
/>
34+
)
35+
}
36+
37+
const inputGroupAddonVariants = cva(
38+
"text-muted-foreground flex h-auto cursor-text select-none items-center justify-center gap-2 py-1.5 text-sm font-medium group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",
39+
{
40+
variants: {
41+
align: {
42+
"inline-start":
43+
"order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]",
44+
"inline-end":
45+
"order-last pr-3 has-[>button]:mr-[-0.4rem] has-[>kbd]:mr-[-0.35rem]",
46+
"block-start":
47+
"[.border-b]:pb-3 order-first w-full justify-start px-3 pt-3 group-has-[>input]/input-group:pt-2.5",
48+
"block-end":
49+
"[.border-t]:pt-3 order-last w-full justify-start px-3 pb-3 group-has-[>input]/input-group:pb-2.5",
50+
},
51+
},
52+
defaultVariants: {
53+
align: "inline-start",
54+
},
55+
}
56+
)
57+
58+
function InputGroupAddon({
59+
className,
60+
align = "inline-start",
61+
...props
62+
}: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
63+
return (
64+
<div
65+
role="group"
66+
data-slot="input-group-addon"
67+
data-align={align}
68+
className={cn(inputGroupAddonVariants({ align }), className)}
69+
onClick={(e) => {
70+
if ((e.target as HTMLElement).closest("button")) {
71+
return
72+
}
73+
e.currentTarget.parentElement?.querySelector("input")?.focus()
74+
}}
75+
{...props}
76+
/>
77+
)
78+
}
79+
80+
const inputGroupButtonVariants = cva(
81+
"flex items-center gap-2 text-sm shadow-none",
82+
{
83+
variants: {
84+
size: {
85+
xs: "h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-2 has-[>svg]:px-2 [&>svg:not([class*='size-'])]:size-3.5",
86+
sm: "h-8 gap-1.5 rounded-md px-2.5 has-[>svg]:px-2.5",
87+
"icon-xs":
88+
"size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
89+
"icon-sm": "size-8 p-0 has-[>svg]:p-0",
90+
},
91+
},
92+
defaultVariants: {
93+
size: "xs",
94+
},
95+
}
96+
)
97+
98+
function InputGroupButton({
99+
className,
100+
type = "button",
101+
variant = "ghost",
102+
size = "xs",
103+
...props
104+
}: Omit<React.ComponentProps<typeof Button>, "size"> &
105+
VariantProps<typeof inputGroupButtonVariants>) {
106+
return (
107+
<Button
108+
type={type}
109+
data-size={size}
110+
variant={variant}
111+
className={cn(inputGroupButtonVariants({ size }), className)}
112+
{...props}
113+
/>
114+
)
115+
}
116+
117+
function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
118+
return (
119+
<span
120+
className={cn(
121+
"text-muted-foreground flex items-center gap-2 text-sm [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none",
122+
className
123+
)}
124+
{...props}
125+
/>
126+
)
127+
}
128+
129+
function InputGroupInput({
130+
className,
131+
...props
132+
}: React.ComponentProps<"input">) {
133+
return (
134+
<Input
135+
data-slot="input-group-control"
136+
className={cn(
137+
"flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent",
138+
className
139+
)}
140+
{...props}
141+
/>
142+
)
143+
}
144+
145+
function InputGroupTextarea({
146+
className,
147+
...props
148+
}: React.ComponentProps<"textarea">) {
149+
return (
150+
<Textarea
151+
data-slot="input-group-control"
152+
className={cn(
153+
"flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent",
154+
className
155+
)}
156+
{...props}
157+
/>
158+
)
159+
}
160+
161+
export {
162+
InputGroup,
163+
InputGroupAddon,
164+
InputGroupButton,
165+
InputGroupText,
166+
InputGroupInput,
167+
InputGroupTextarea,
168+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import * as React from "react"
2+
3+
import { cn } from "@/lib/utils"
4+
5+
const Textarea = React.forwardRef<
6+
HTMLTextAreaElement,
7+
React.ComponentProps<"textarea">
8+
>(({ className, ...props }, ref) => {
9+
return (
10+
<textarea
11+
className={cn(
12+
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
13+
className
14+
)}
15+
ref={ref}
16+
{...props}
17+
/>
18+
)
19+
})
20+
Textarea.displayName = "Textarea"
21+
22+
export { Textarea }

SDMeta.Web/src/features/gallery/GalleryToolbar.tsx

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Loader2, RefreshCw, Settings, X } from 'lucide-react'
22
import type { Dispatch, SetStateAction } from 'react'
33
import { Badge } from '../../components/ui/badge'
44
import { Button } from '../../components/ui/button'
5-
import { Input } from '../../components/ui/input'
5+
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from '../../components/ui/input-group'
66
import {
77
Select,
88
SelectContent,
@@ -54,27 +54,31 @@ export function GalleryToolbar({
5454
return (
5555
<header className="sticky top-0 z-20 border-b border-neutral-700 bg-neutral-900/95 px-3 py-2 backdrop-blur">
5656
<div className="grid grid-cols-1 gap-2 md:grid-cols-[minmax(220px,320px)_minmax(200px,300px)_minmax(160px,190px)_auto_auto_auto_1fr_auto] md:items-center">
57-
<div className="relative">
58-
<Input
59-
value={filterInput}
60-
onChange={(event) => setFilterInput(event.target.value)}
61-
placeholder="filter"
62-
className="bg-neutral-100 pr-9 text-neutral-950"
63-
/>
64-
{filterInput.length > 0 && (
65-
<Button
66-
type="button"
67-
variant="ghost"
68-
size="icon"
69-
className="absolute right-0 top-0 h-10 w-10 text-neutral-600 hover:text-neutral-900"
70-
onClick={() => setFilterInput('')}
71-
title="Clear filter"
72-
>
73-
<X className="h-4 w-4" />
74-
</Button>
75-
)}
57+
<div>
58+
<InputGroup className="bg-neutral-100 text-neutral-950">
59+
<InputGroupInput
60+
value={filterInput}
61+
onChange={(event) => setFilterInput(event.target.value)}
62+
placeholder="filter"
63+
className="h-10"
64+
/>
65+
{filterInput.length > 0 && (
66+
<InputGroupAddon align="inline-end">
67+
<InputGroupButton
68+
size="icon-xs"
69+
variant="ghost"
70+
aria-label="Clear search"
71+
onClick={() => setFilterInput('')}
72+
title="Clear filter"
73+
>
74+
<X />
75+
</InputGroupButton>
76+
</InputGroupAddon>
77+
)}
78+
</InputGroup>
7679
</div>
7780

81+
7882
<Select
7983
value={selectedModelValue}
8084
onValueChange={(value) => {

0 commit comments

Comments
 (0)