diff --git a/apps/docs/src/ui/select-alignment.stories.tsx b/apps/docs/src/ui/select-alignment.stories.tsx
index 955af9d..163676c 100644
--- a/apps/docs/src/ui/select-alignment.stories.tsx
+++ b/apps/docs/src/ui/select-alignment.stories.tsx
@@ -55,6 +55,10 @@ const RightAlignedSelectExample = () => {
};
export const RightAligned: Story = {
+ args: {
+ options: fruits,
+ placeholder: 'Select a fruit...',
+ },
render: () => ,
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement);
@@ -79,7 +83,7 @@ export const RightAligned: Story = {
await waitFor(() => {
expect(document.activeElement).toBe(listbox);
});
- await userEvent.keyboard('{ArrowDown}', { focusTrap: false });
+ await userEvent.keyboard('{ArrowDown}');
await waitFor(() => {
const activeItem = document.querySelector('[cmdk-item][aria-selected="true"]');
expect(activeItem).not.toBeNull();
diff --git a/packages/components/package.json b/packages/components/package.json
index be7da4d..f43ba8e 100644
--- a/packages/components/package.json
+++ b/packages/components/package.json
@@ -58,7 +58,7 @@
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.6",
"@radix-ui/react-slider": "^1.3.4",
- "@radix-ui/react-slot": "^1.2.3",
+ "@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-switch": "^1.1.2",
"@radix-ui/react-tabs": "^1.1.11",
"@radix-ui/react-tooltip": "^1.1.6",
diff --git a/packages/components/src/ui/index.ts b/packages/components/src/ui/index.ts
index 8e2550a..bb9c48b 100644
--- a/packages/components/src/ui/index.ts
+++ b/packages/components/src/ui/index.ts
@@ -13,6 +13,7 @@ export * from './date-picker-field';
export * from './dropdown-menu';
export * from './form';
export * from './form-error-field';
+export * from './input-group';
export * from './label';
export * from './otp-input';
export * from './otp-input-field';
diff --git a/packages/components/src/ui/input-group.tsx b/packages/components/src/ui/input-group.tsx
new file mode 100644
index 0000000..fd4e6b7
--- /dev/null
+++ b/packages/components/src/ui/input-group.tsx
@@ -0,0 +1,117 @@
+'use client';
+
+import type * as React from 'react';
+import { cva, type VariantProps } from 'class-variance-authority';
+
+import { cn } from './utils';
+import { Button } from './button';
+import { TextInput } from './text-input';
+import { Textarea } from './textarea';
+
+function InputGroup({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+ // biome-ignore lint/a11y/useSemanticElements: role="group" is appropriate for input groups per WAI-ARIA
+
+ );
+}
+
+function InputGroupAddon({
+ className,
+ align = 'start',
+ ...props
+}: React.ComponentProps<'div'> & { align?: 'start' | 'end' }) {
+ const isPrefix = align === 'start';
+ const isSuffix = align === 'end';
+
+ return (
+
+ );
+}
+
+const inputGroupButtonVariants = cva('flex items-center gap-2 text-sm shadow-none', {
+ variants: {
+ size: {
+ xs: "h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-2 has-[>svg]:px-2 [&>svg:not([class*='size-'])]:size-3.5",
+ sm: 'h-8 gap-1.5 rounded-md px-2.5 has-[>svg]:px-2.5',
+ 'icon-xs': 'size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0',
+ 'icon-sm': 'size-8 p-0 has-[>svg]:p-0',
+ },
+ },
+ defaultVariants: {
+ size: 'xs',
+ },
+});
+
+function InputGroupButton({
+ className,
+ type = 'button',
+ variant = 'ghost',
+ size = 'xs',
+ ...props
+}: Omit, 'size'> & VariantProps) {
+ return (
+
+ );
+}
+
+function InputGroupText({ className, ...props }: React.ComponentProps<'span'>) {
+ return ;
+}
+
+function InputGroupInput({ className, ...props }: React.ComponentProps<'input'>) {
+ return (
+
+ );
+}
+
+function InputGroupTextarea({ className, ...props }: React.ComponentProps<'textarea'>) {
+ return (
+
+ );
+}
+
+export { InputGroup, InputGroupAddon, InputGroupButton, InputGroupText, InputGroupInput, InputGroupTextarea };
diff --git a/packages/components/src/ui/text-field.tsx b/packages/components/src/ui/text-field.tsx
index efcc3ba..5ea0735 100644
--- a/packages/components/src/ui/text-field.tsx
+++ b/packages/components/src/ui/text-field.tsx
@@ -10,34 +10,9 @@ import {
FormMessage,
} from './form';
import { type InputProps, TextInput } from './text-input';
+import { InputGroup, InputGroupAddon, InputGroupInput, InputGroupText } from './input-group';
import { cn } from './utils';
-export const FieldPrefix = ({ children, className }: { children: React.ReactNode; className?: string }) => {
- return (
-
- {children}
-
- );
-};
-
-export const FieldSuffix = ({ children, className }: { children: React.ReactNode; className?: string }) => {
- return (
-
- {children}
-
- );
-};
-
// Create a specific interface for the input props that includes className explicitly
export interface TextInputProps extends Omit {
control?: Control;
@@ -72,30 +47,44 @@ export const TextField = function TextField({
control={control}
name={name}
render={({ field, fieldState }) => {
+ // Use the new InputGroup pattern when prefix or suffix is provided
+ const hasAddon = prefix || suffix;
+
return (
{label && {label}}
-
- {prefix && {prefix}}
+ {hasAddon ? (
+ // New shadcn/ui InputGroup pattern
-
+
+ {prefix && (
+
+ {prefix}
+
+ )}
+
+ {suffix && (
+
+ {suffix}
+
+ )}
+
- {suffix && {suffix}}
-
+ ) : (
+ // Original pattern without addons
+
+
+
+ )}
{description && {description}}
{fieldState.error && (
{fieldState.error.message}
diff --git a/yarn.lock b/yarn.lock
index 8a0053a..42634fc 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1729,7 +1729,7 @@ __metadata:
"@radix-ui/react-select": "npm:^2.2.6"
"@radix-ui/react-separator": "npm:^1.1.6"
"@radix-ui/react-slider": "npm:^1.3.4"
- "@radix-ui/react-slot": "npm:^1.2.3"
+ "@radix-ui/react-slot": "npm:^1.2.4"
"@radix-ui/react-switch": "npm:^1.1.2"
"@radix-ui/react-tabs": "npm:^1.1.11"
"@radix-ui/react-tooltip": "npm:^1.1.6"
@@ -2639,7 +2639,7 @@ __metadata:
languageName: node
linkType: hard
-"@radix-ui/react-slot@npm:1.2.3, @radix-ui/react-slot@npm:^1.2.3":
+"@radix-ui/react-slot@npm:1.2.3":
version: 1.2.3
resolution: "@radix-ui/react-slot@npm:1.2.3"
dependencies:
@@ -2654,6 +2654,21 @@ __metadata:
languageName: node
linkType: hard
+"@radix-ui/react-slot@npm:^1.2.4":
+ version: 1.2.4
+ resolution: "@radix-ui/react-slot@npm:1.2.4"
+ dependencies:
+ "@radix-ui/react-compose-refs": "npm:1.1.2"
+ peerDependencies:
+ "@types/react": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ checksum: 10c0/8b719bb934f1ae5ac0e37214783085c17c2f1080217caf514c1c6cc3d9ca56c7e19d25470b26da79aa6e605ab36589edaade149b76f5fc0666f1063e2fc0a0dc
+ languageName: node
+ linkType: hard
+
"@radix-ui/react-switch@npm:^1.1.2":
version: 1.2.6
resolution: "@radix-ui/react-switch@npm:1.2.6"