Skip to content

Commit e4f1043

Browse files
chore(ui): install button group and input group component
1 parent 053b9d4 commit e4f1043

6 files changed

Lines changed: 200 additions & 42 deletions

File tree

resources/js/components/ui/button-group.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Separator } from '@/components/ui/separator';
55
import { cn } from '@/lib/utils';
66

77
const buttonGroupVariants = cva(
8-
"flex w-fit items-stretch *:focus-visible:z-10 *:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md has-[>[data-slot=button-group]]:gap-2",
8+
"flex w-fit items-stretch [&>*]:focus-visible:z-10 [&>*]:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md has-[>[data-slot=button-group]]:gap-2",
99
{
1010
variants: {
1111
orientation: {
@@ -66,7 +66,7 @@ function ButtonGroupSeparator({
6666
<Separator
6767
data-slot="button-group-separator"
6868
orientation={orientation}
69-
className={cn('relative m-0! self-stretch bg-input data-[orientation=vertical]:h-auto', className)}
69+
className={cn('relative !m-0 self-stretch bg-input data-[orientation=vertical]:h-auto', className)}
7070
{...props}
7171
/>
7272
);

resources/js/components/ui/button.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { Slot } from '@radix-ui/react-slot';
22
import { cva, type VariantProps } from 'class-variance-authority';
3+
import * as React from 'react';
34

45
import { cn } from '@/lib/utils';
5-
import { type ComponentProps } from 'react';
66

77
const buttonVariants = cva(
8-
"inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
8+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
99
{
1010
variants: {
1111
variant: {
@@ -40,7 +40,7 @@ function Button({
4040
size,
4141
asChild = false,
4242
...props
43-
}: ComponentProps<'button'> &
43+
}: React.ComponentProps<'button'> &
4444
VariantProps<typeof buttonVariants> & {
4545
asChild?: boolean;
4646
}) {
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
'use client';
2+
3+
import { cva, type VariantProps } from 'class-variance-authority';
4+
import * as React from 'react';
5+
6+
import { Button } from '@/components/ui/button';
7+
import { Input } from '@/components/ui/input';
8+
import { Textarea } from '@/components/ui/textarea';
9+
import { cn } from '@/lib/utils';
10+
11+
function InputGroup({ className, ...props }: React.ComponentProps<'div'>) {
12+
return (
13+
<div
14+
data-slot="input-group"
15+
role="group"
16+
className={cn(
17+
'group/input-group relative flex w-full items-center rounded-md border border-input shadow-xs transition-[color,box-shadow] outline-none dark:bg-input/30',
18+
'h-9 min-w-0 has-[>textarea]:h-auto',
19+
20+
// Variants based on alignment.
21+
'has-[>[data-align=inline-start]]:[&>input]:pl-2',
22+
'has-[>[data-align=inline-end]]:[&>input]:pr-2',
23+
'has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3',
24+
'has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3',
25+
26+
// Focus state.
27+
'has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50',
28+
29+
// Error state.
30+
'has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40',
31+
32+
className,
33+
)}
34+
{...props}
35+
/>
36+
);
37+
}
38+
39+
const inputGroupAddonVariants = cva(
40+
"text-muted-foreground flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium select-none [&>svg:not([class*='size-'])]:size-4 [&>kbd]:rounded-[calc(var(--radius)-5px)] group-data-[disabled=true]/input-group:opacity-50",
41+
{
42+
variants: {
43+
align: {
44+
'inline-start': 'order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]',
45+
'inline-end': 'order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]',
46+
'block-start':
47+
'order-first w-full justify-start px-3 pt-3 [.border-b]:pb-3 group-has-[>input]/input-group:pt-2.5',
48+
'block-end':
49+
'order-last w-full justify-start px-3 pb-3 [.border-t]:pt-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('text-sm shadow-none flex gap-2 items-center', {
81+
variants: {
82+
size: {
83+
xs: "h-6 gap-1 px-2 rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-3.5 has-[>svg]:px-2",
84+
sm: 'h-8 px-2.5 gap-1.5 rounded-md has-[>svg]:px-2.5',
85+
'icon-xs': 'size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0',
86+
'icon-sm': 'size-8 p-0 has-[>svg]:p-0',
87+
},
88+
},
89+
defaultVariants: {
90+
size: 'xs',
91+
},
92+
});
93+
94+
function InputGroupButton({
95+
className,
96+
type = 'button',
97+
variant = 'ghost',
98+
size = 'xs',
99+
...props
100+
}: Omit<React.ComponentProps<typeof Button>, 'size'> & VariantProps<typeof inputGroupButtonVariants>) {
101+
return (
102+
<Button
103+
type={type}
104+
data-size={size}
105+
variant={variant}
106+
className={cn(inputGroupButtonVariants({ size }), className)}
107+
{...props}
108+
/>
109+
);
110+
}
111+
112+
function InputGroupText({ className, ...props }: React.ComponentProps<'span'>) {
113+
return (
114+
<span
115+
className={cn(
116+
"flex items-center gap-2 text-sm text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
117+
className,
118+
)}
119+
{...props}
120+
/>
121+
);
122+
}
123+
124+
function InputGroupInput({ className, ...props }: React.ComponentProps<'input'>) {
125+
return (
126+
<Input
127+
data-slot="input-group-control"
128+
className={cn(
129+
'flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent',
130+
className,
131+
)}
132+
{...props}
133+
/>
134+
);
135+
}
136+
137+
function InputGroupTextarea({ className, ...props }: React.ComponentProps<'textarea'>) {
138+
return (
139+
<Textarea
140+
data-slot="input-group-control"
141+
className={cn(
142+
'flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent',
143+
className,
144+
)}
145+
{...props}
146+
/>
147+
);
148+
}
149+
150+
export { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea };
Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
1-
import { cn } from '@/lib/utils';
21
import * as React from 'react';
32

4-
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
5-
return (
6-
<input
7-
type={type}
8-
data-slot="input"
9-
className={cn(
10-
"border-input file:text-foreground placeholder:text-muted-foreground/50 dark:placeholder:text-muted-foreground/80 selection:bg-primary selection:text-primary-foreground flex h-9 w-full min-w-0 rounded-md border bg-input px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-input file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
11-
className
12-
)}
13-
{...props}
14-
/>
15-
)
3+
import { cn } from '@/lib/utils';
4+
5+
function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
6+
return (
7+
<input
8+
type={type}
9+
data-slot="input"
10+
className={cn(
11+
'h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30',
12+
'focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50',
13+
'aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40',
14+
className,
15+
)}
16+
{...props}
17+
/>
18+
);
1619
}
1720

18-
export { Input }
21+
export { Input };
Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
1-
import { cn } from '@/lib/utils';
21
import * as SeparatorPrimitive from '@radix-ui/react-separator';
3-
import { ComponentProps } from 'react';
2+
import * as React from 'react';
3+
4+
import { cn } from '@/lib/utils';
45

56
function Separator({
6-
className,
7-
orientation = "horizontal",
8-
decorative = true,
9-
...props
10-
}: ComponentProps<typeof SeparatorPrimitive.Root>) {
11-
return (
12-
<SeparatorPrimitive.Root
13-
data-slot="separator"
14-
decorative={decorative}
15-
orientation={orientation}
16-
className={cn(
17-
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
18-
className
19-
)}
20-
{...props}
21-
/>
22-
)
7+
className,
8+
orientation = 'horizontal',
9+
decorative = true,
10+
...props
11+
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
12+
return (
13+
<SeparatorPrimitive.Root
14+
data-slot="separator"
15+
decorative={decorative}
16+
orientation={orientation}
17+
className={cn(
18+
'shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px',
19+
className,
20+
)}
21+
{...props}
22+
/>
23+
);
2324
}
2425

25-
export { Separator }
26+
export { Separator };

resources/js/components/ui/textarea.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
import { cn } from '@/lib/utils';
21
import * as React from 'react';
32

4-
function Textarea({ className, ...props }: Readonly<React.ComponentProps<'textarea'>>) {
3+
import { cn } from '@/lib/utils';
4+
5+
function Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {
56
return (
67
<textarea
78
data-slot="textarea"
8-
className={cn('flex field-sizing-content dark:placeholder:text-muted-foreground/80 min-h-16 w-full rounded-md border border-input bg-input px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground/50 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 md:text-sm dark:aria-invalid:ring-destructive/40', className)}
9+
className={cn(
10+
'flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:ring-destructive/40',
11+
className,
12+
)}
913
{...props}
1014
/>
1115
);

0 commit comments

Comments
 (0)