primitives
Input Otp
Input Otp — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).
Загрузка превью…
src/ui/input-otp/InputOTP.vue
<!--
Оригинал построен на npm-пакете `input-otp` (единственный визуально скрытый
`<input>` + React-контекст `OTPInputContext`, читаемый `InputOTPSlot`).
У Reka UI нет аналога этого пакета: примитив-замена — `PinInput`
(`PinInputRoot`/`PinInputInput`), где каждый слот — свой собственный
настоящий `<input>`, а не общий скрытый ввод с виртуальными слотами.
Здесь `InputOTP` — обёртка над `PinInputRoot`.
-->
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { computed } from "vue"
import { PinInputRoot, type PinInputRootEmits, type PinInputRootProps, useForwardPropsEmits } from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<
PinInputRootProps & {
/** Доп. классы. Имя `class` — соглашение shadcn-vue (не `className`). */
class?: HTMLAttributes["class"]
containerClassName?: HTMLAttributes["class"]
}
>()
const emits = defineEmits<PinInputRootEmits>()
const delegatedProps = computed(() => {
const { class: _class, containerClassName: _containerClassName, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<PinInputRoot
data-slot="input-otp"
v-bind="forwarded"
:class="cn('cn-input-otp flex items-center has-disabled:opacity-50', props.containerClassName)"
>
<slot />
</PinInputRoot>
</template>
src/ui/input-otp/InputOTPGroup.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-otp-group" :class="cn('cn-input-otp-group flex items-center', props.class)">
<slot />
</div>
</template>
src/ui/input-otp/InputOTPSeparator.vue
<!--
Иконка — инлайновый `<svg>` вместо апстримного `IconPlaceholder`
(путь lucide "minus"), тем же приёмом, что и в остальных примитивах.
-->
<script setup lang="ts"></script>
<template>
<div data-slot="input-otp-separator" class="cn-input-otp-separator flex items-center" role="separator">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M5 12h14" />
</svg>
</div>
</template>
src/ui/input-otp/InputOTPSlot.vue
<!--
Оригинал читает `char`/`hasFakeCaret`/`isActive` из `OTPInputContext`
(пакет `input-otp`) для одного общего скрытого `<input>`. У `PinInput`
(Reka UI) каждый слот — собственный настоящий `<input>`
(`PinInputInput`), поэтому `char`/фейковый каret из оригинала не имеют
прямого аналога: значение слота отображается самим `<input>`, а не
текстовым узлом поверх него. `data-active` (фокус слота) здесь не
воспроизведён — у Reka UI `PinInput` это состояние не публикуется
наружу как отдельный флаг на слоте.
Ещё одно следствие замены `<div>` на `<input>`: в оригинале `flex
items-center justify-center` центрирует текстовый узел `{char}` внутри
`<div>`. У `<input>` это тот же класс, приложенный к самому элементу, а
не к его контейнеру — flex-выравнивание не действует на содержимое
инпута (его текст позиционируется через `text-align`, а не flex), и
цифра садится по левому краю (браузерный дефолт `text-align: left`)
вместо центра. `text-center` добавлен как необходимая поправка на смену
типа элемента, а не как правка исходной строки Tailwind.
Известное непокрытое расхождение (см. docs/PORTING.md, §5): тема
`mira` в светлом режиме даёт стабильный вертикальный сдвиг цифры на
1px внутри `<input>` относительно `<div>` оригинала — расследовано, не
устранимо через CSS (см. PORTING.md за подробностями и доказательством).
-->
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { PinInputInput, type PinInputInputProps } from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<
PinInputInputProps & {
/** Доп. классы. Имя `class` — соглашение shadcn-vue (не `className`). */
class?: HTMLAttributes["class"]
}
>()
</script>
<template>
<PinInputInput
data-slot="input-otp-slot"
:index="props.index"
:disabled="props.disabled"
:class="cn('cn-input-otp-slot relative flex items-center justify-center text-center data-[active=true]:z-10', props.class)"
/>
</template>
src/ui/input-otp/index.ts
export { default as InputOTP } from "./InputOTP.vue"
export { default as InputOTPGroup } from "./InputOTPGroup.vue"
export { default as InputOTPSeparator } from "./InputOTPSeparator.vue"
export { default as InputOTPSlot } from "./InputOTPSlot.vue"
Установка
npx shadcn-vue@latest add https://revueui.rootapi.dev/r/input-otp.jsonЗависимости реестра
npm-зависимости
- reka-ui
Источник: порт из ReUI (Keenthemes, MIT)