primitives
Input Group
Input Group — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).
Загрузка превью…
src/ui/input-group/InputGroup.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{
/** Доп. классы. Имя `class` — соглашение shadcn-vue (не `className`). */
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<div
data-slot="input-group"
role="group"
:class="
cn(
'group/input-group cn-input-group relative flex w-full min-w-0 items-center outline-none has-[>textarea]:h-auto',
props.class
)
"
>
<slot />
</div>
</template>
src/ui/input-group/InputGroupAddon.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
import { inputGroupAddonVariants, type InputGroupAddonVariants } from "./variants"
const props = withDefaults(
defineProps<{
/** Доп. классы. Имя `class` — соглашение shadcn-vue (не `className`). */
class?: HTMLAttributes["class"]
align?: InputGroupAddonVariants["align"]
}>(),
{
align: "inline-start",
}
)
/** Клик по обёртке фокусирует соседний `<input>`, кроме кликов по кнопке. */
function onClick(e: MouseEvent) {
if ((e.target as HTMLElement).closest("button")) {
return
}
;(e.currentTarget as HTMLElement).parentElement?.querySelector("input")?.focus()
}
</script>
<template>
<div
role="group"
data-slot="input-group-addon"
:data-align="props.align"
:class="cn(inputGroupAddonVariants({ align: props.align }), props.class)"
@click="onClick"
>
<slot />
</div>
</template>
src/ui/input-group/InputGroupButton.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { Button, type ButtonVariants } from "@/components/ui/button"
import { cn } from "@/lib/utils"
import { inputGroupButtonVariants, type InputGroupButtonVariants } from "./variants"
const props = withDefaults(
defineProps<{
/** Доп. классы. Имя `class` — соглашение shadcn-vue (не `className`). */
class?: HTMLAttributes["class"]
type?: "button" | "submit" | "reset"
variant?: ButtonVariants["variant"]
size?: InputGroupButtonVariants["size"]
}>(),
{
type: "button",
variant: "ghost",
size: "xs",
}
)
</script>
<template>
<Button
:type="props.type"
:data-size="props.size"
:variant="props.variant"
:class="cn(inputGroupButtonVariants({ size: props.size }), props.class)"
>
<slot />
</Button>
</template>
src/ui/input-group/InputGroupInput.vue
<script setup lang="ts">
import type { HTMLAttributes, InputHTMLAttributes } from "vue"
import { Input } from "@/components/ui/input"
import { cn } from "@/lib/utils"
const props = defineProps<{
/** Доп. классы. Имя `class` — соглашение shadcn-vue (не `className`). */
class?: HTMLAttributes["class"]
type?: InputHTMLAttributes["type"]
}>()
</script>
<template>
<Input
data-slot="input-group-control"
:type="props.type"
:class="cn('cn-input-group-input flex-1', props.class)"
/>
</template>
src/ui/input-group/InputGroupText.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{
/** Доп. классы. Имя `class` — соглашение shadcn-vue (не `className`). */
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<span :class="cn('cn-input-group-text flex items-center [&_svg]:pointer-events-none', props.class)">
<slot />
</span>
</template>
src/ui/input-group/InputGroupTextarea.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { Textarea } from "@/components/ui/textarea"
import { cn } from "@/lib/utils"
const props = defineProps<{
/** Доп. классы. Имя `class` — соглашение shadcn-vue (не `className`). */
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<Textarea
data-slot="input-group-control"
:class="cn('cn-input-group-textarea flex-1 resize-none', props.class)"
/>
</template>
src/ui/input-group/index.ts
export { default as InputGroup } from "./InputGroup.vue"
export { default as InputGroupAddon } from "./InputGroupAddon.vue"
export { default as InputGroupButton } from "./InputGroupButton.vue"
export { default as InputGroupText } from "./InputGroupText.vue"
export { default as InputGroupInput } from "./InputGroupInput.vue"
export { default as InputGroupTextarea } from "./InputGroupTextarea.vue"
export {
inputGroupAddonVariants,
type InputGroupAddonVariants,
inputGroupButtonVariants,
type InputGroupButtonVariants,
} from "./variants"
src/ui/input-group/variants.ts
import { cva, type VariantProps } from "class-variance-authority"
/**
* Перенесено дословно из shadcn (registry/bases/radix/ui/input-group.tsx, MIT).
*
* cva-блоки — чистые JS-объекты и переносятся между React и Vue без единой
* правки. Вся визуальная семантика живёт здесь, а фреймворк-специфичной
* остаётся только разметка.
*/
export const inputGroupAddonVariants = cva(
"cn-input-group-addon flex cursor-text items-center justify-center select-none",
{
variants: {
align: {
"inline-start": "cn-input-group-addon-align-inline-start order-first",
"inline-end": "cn-input-group-addon-align-inline-end order-last",
"block-start":
"cn-input-group-addon-align-block-start order-first w-full justify-start",
"block-end":
"cn-input-group-addon-align-block-end order-last w-full justify-start",
},
},
defaultVariants: {
align: "inline-start",
},
}
)
export type InputGroupAddonVariants = VariantProps<typeof inputGroupAddonVariants>
export const inputGroupButtonVariants = cva(
"cn-input-group-button flex items-center shadow-none",
{
variants: {
size: {
xs: "cn-input-group-button-size-xs",
sm: "cn-input-group-button-size-sm",
"icon-xs": "cn-input-group-button-size-icon-xs",
"icon-sm": "cn-input-group-button-size-icon-sm",
},
},
defaultVariants: {
size: "xs",
},
}
)
export type InputGroupButtonVariants = VariantProps<typeof inputGroupButtonVariants>