primitives

Native Select

Native Select — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).

Загрузка превью…

src/ui/native-select/NativeSelect.vue

<!--
  Не Radix-примитив: обёртка над нативным `<select>`. Иконка в оригинале —
  `IconPlaceholder` (dev-обвязка сайта ReUI). Здесь и в кейсе используется
  один и тот же инлайновый `<svg>` (путь lucide "chevron-down"), чтобы диф
  сравнивал сам компонент, а не выбор библиотеки иконок.
-->
<script setup lang="ts">
import { onMounted, useTemplateRef, type HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"

defineOptions({
  inheritAttrs: false,
})

// ponytail: не расширяем SelectHTMLAttributes целиком — @vue/compiler-sfc
// не может разрешить его "extends HTMLAttributes" в этом воркспейсе (падает
// с "Failed to resolve extends base type" в дев-сервере, хотя vue-tsc это
// пропускает — расхождение между тайпчеком и рантайм-компилятором). Прочие
// атрибуты `<select>` (value/disabled/multiple/…) и так доходят через
// `v-bind="$attrs"` ниже — см. тот же приём в Input.vue.
const props = withDefaults(
  defineProps<{
    /** Доп. классы. Имя `class` — соглашение shadcn-vue (не `className`). */
    class?: HTMLAttributes["class"]
    size?: "sm" | "default"
    /**
     * Неуправляемое начальное значение — аналог React `defaultValue` на
     * `<select>`. У нативного `<select>` во Vue эквивалента нет: HTML-атрибута
     * `defaultvalue` не существует, поэтому просто пробросить проп через
     * `v-bind="$attrs"` (как раньше) — баг: браузер молча его игнорирует и
     * оставляет выбранным первый `<option>`. Поэтому проп объявлен явно и
     * выставляется через DOM-свойство `select.value` при монтировании —
     * см. docs/PORTING.md, "NativeSelect: defaultValue не существует во Vue".
     */
    defaultValue?: string
  }>(),
  {
    size: "default",
  }
)

const selectRef = useTemplateRef<HTMLSelectElement>("select")

onMounted(() => {
  if (props.defaultValue !== undefined && selectRef.value) {
    selectRef.value.value = props.defaultValue
  }
})
</script>

<template>
  <div
    :class="cn('cn-native-select-wrapper group/native-select relative w-fit has-[select:disabled]:opacity-50', props.class)"
    data-slot="native-select-wrapper"
    :data-size="props.size"
  >
    <select
      ref="select"
      data-slot="native-select"
      :data-size="props.size"
      class="cn-native-select outline-none disabled:pointer-events-none disabled:cursor-not-allowed"
      v-bind="$attrs"
    >
      <slot />
    </select>
    <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"
      class="cn-native-select-icon pointer-events-none absolute select-none"
      aria-hidden="true"
      data-slot="native-select-icon"
    >
      <path d="m6 9 6 6 6-6" />
    </svg>
  </div>
</template>

src/ui/native-select/NativeSelectOptGroup.vue

<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"

// ponytail: не расширяем OptgroupHTMLAttributes целиком — та же причина,
// что в NativeSelectOption.vue (@vue/compiler-sfc не резолвит "extends
// HTMLAttributes" в дев-сервере). label/disabled доходят через fallthrough.
const props = defineProps<{
  /** Доп. классы. Имя `class` — соглашение shadcn-vue (не `className`). */
  class?: HTMLAttributes["class"]
}>()
</script>

<template>
  <optgroup data-slot="native-select-optgroup" :class="cn('bg-[Canvas] text-[CanvasText]', props.class)">
    <slot />
  </optgroup>
</template>

src/ui/native-select/NativeSelectOption.vue

<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"

// ponytail: не расширяем OptionHTMLAttributes целиком — @vue/compiler-sfc
// не может разрешить его "extends HTMLAttributes" в этом воркспейсе (падает
// с "Failed to resolve extends base type" в dev-сервере, хотя vue-tsc это
// пропускает). Остальные атрибуты (value/disabled/selected/label) и так
// доходят до <option> через автоматический fallthrough (inheritAttrs не
// переопределён, один корневой элемент) — см. Input.vue для того же приёма.
const props = defineProps<{
  /** Доп. классы. Имя `class` — соглашение shadcn-vue (не `className`). */
  class?: HTMLAttributes["class"]
}>()
</script>

<template>
  <option data-slot="native-select-option" :class="cn('bg-[Canvas] text-[CanvasText]', props.class)">
    <slot />
  </option>
</template>

src/ui/native-select/index.ts

export { default as NativeSelect } from "./NativeSelect.vue"
export { default as NativeSelectOptGroup } from "./NativeSelectOptGroup.vue"
export { default as NativeSelectOption } from "./NativeSelectOption.vue"

Установка

npx shadcn-vue@latest add https://revueui.rootapi.dev/r/native-select.json

Зависимости реестра

Источник: порт из ReUI (Keenthemes, MIT)