Поля выбора
Компоненты выбора — Select, Combobox, RadioGroup, Checkbox, Switch
Полный пример
Полный sandbox-пример (все select-подобные поля на одной странице), прочитанный напрямую из
исходников form-develop-app / form-develop-app-shadcn на сборке — переключи скин, чтобы
увидеть те же поля в обеих реализациях.
'use client'
import { Box, Heading, Text, VStack } from '@chakra-ui/react'
import { Form } from '@letar/forms'
import { useState } from 'react'
import { z } from 'zod/v4'
import { DemoPageLayout, SubmittedDataPreview } from '../_components'
/**
* Демо-схема для NativeSelect и CascadingSelect
*/
const DemoSchema = z
.object({
// NativeSelect fields
simpleSelect: z.string().meta({
ui: { title: 'Simple NativeSelect', placeholder: 'Select option...' },
}),
sizeSelect: z.string().meta({
ui: { title: 'Size Selection' },
}),
prioritySelect: z
.string()
.optional()
.meta({
ui: { title: 'Priority (optional)' },
}),
// CascadingSelect fields
country: z.string().meta({
ui: { title: 'Country', placeholder: 'Select country...' },
}),
city: z
.string()
.optional()
.meta({
ui: { title: 'City', placeholder: 'Select city...' },
}),
// Вложенные поля
address: z.object({
region: z.string().meta({
ui: { title: 'Region', placeholder: 'Select region...' },
}),
district: z
.string()
.optional()
.meta({
ui: { title: 'District', placeholder: 'Select district...' },
}),
}),
})
.strip()
type DemoData = z.infer<typeof DemoSchema>
const initialData: DemoData = {
simpleSelect: '',
sizeSelect: 'md',
prioritySelect: undefined,
country: '',
city: undefined,
address: {
region: '',
district: undefined,
},
}
// Данные для NativeSelect
const simpleOptions = [
{ title: 'Option 1', value: 'opt1' },
{ title: 'Option 2', value: 'opt2' },
{ title: 'Option 3', value: 'opt3' },
]
const sizeOptions = [
{ title: 'Extra Small', value: 'xs' },
{ title: 'Small', value: 'sm' },
{ title: 'Medium', value: 'md' },
{ title: 'Large', value: 'lg' },
]
const priorityOptions = [
{ title: 'Low Priority', value: 'low' },
{ title: 'Medium Priority', value: 'medium' },
{ title: 'High Priority', value: 'high' },
{ title: 'Critical', value: 'critical' },
]
// Данные для CascadingSelect
const countries = [
{ label: 'Russia', value: 'ru' },
{ label: 'USA', value: 'us' },
{ label: 'Germany', value: 'de' },
]
const citiesByCountry: Record<string, { label: string; value: string }[]> = {
ru: [
{ label: 'Moscow', value: 'msk' },
{ label: 'Saint Petersburg', value: 'spb' },
{ label: 'Novosibirsk', value: 'nsk' },
],
us: [
{ label: 'New York', value: 'nyc' },
{ label: 'Los Angeles', value: 'la' },
{ label: 'Chicago', value: 'chi' },
],
de: [
{ label: 'Berlin', value: 'ber' },
{ label: 'Munich', value: 'mun' },
{ label: 'Hamburg', value: 'ham' },
],
}
const regions = [
{ label: 'Moscow Region', value: 'msk_reg' },
{ label: 'Leningrad Region', value: 'len_reg' },
{ label: 'Krasnodar Region', value: 'krd_reg' },
]
const districtsByRegion: Record<string, { label: string; value: string }[]> = {
msk_reg: [
{ label: 'Odintsovo', value: 'odin' },
{ label: 'Khimki', value: 'khim' },
{ label: 'Balashikha', value: 'bal' },
],
len_reg: [
{ label: 'Vsevolozhsk', value: 'vsev' },
{ label: 'Gatchina', value: 'gat' },
{ label: 'Vyborg', value: 'vyb' },
],
krd_reg: [
{ label: 'Sochi', value: 'soc' },
{ label: 'Novorossiysk', value: 'nov' },
{ label: 'Anapa', value: 'ana' },
],
}
export default function SelectDemoPage() {
const [submitted, setSubmitted] = useState<DemoData | null>(null)
return (
<DemoPageLayout title="Select Demo" description="NativeSelect и CascadingSelect компоненты" maxW="800px">
<Form
schema={DemoSchema}
initialValue={initialData}
onSubmit={(data) => {
setSubmitted(data)
}}
>
{/* NativeSelect секция */}
<Box borderWidth={1} borderRadius="md" p={4} mb={6}>
<Heading size="md" mb={4}>
NativeSelect
</Heading>
<Text color="fg.muted" mb={4}>
Нативный браузерный select для лучшего UX на мобильных устройствах
</Text>
<VStack gap={4} align="stretch">
<Form.Field.NativeSelect name="simpleSelect" options={simpleOptions} />
<Form.Field.NativeSelect name="sizeSelect" options={sizeOptions} />
<Form.Field.NativeSelect name="prioritySelect" options={priorityOptions} />
</VStack>
</Box>
{/* CascadingSelect секция */}
<Box borderWidth={1} borderRadius="md" p={4} mb={6}>
<Heading size="md" mb={4}>
CascadingSelect
</Heading>
<Text color="fg.muted" mb={4}>
Каскадный select с зависимостью от другого поля (Страна → Город)
</Text>
<VStack gap={4} align="stretch">
<Form.Field.Select name="country" options={countries} />
<Form.Field.CascadingSelect
name="city"
dependsOn="country"
loadOptions={async (parentValue) => {
// Имитация загрузки с сервера
await new Promise((r) => setTimeout(r, 300))
const countryCode = parentValue as string | undefined
if (!countryCode) {
return []
}
return citiesByCountry[countryCode] ?? []
}}
/>
</VStack>
</Box>
{/* Вложенные CascadingSelect */}
<Box borderWidth={1} borderRadius="md" p={4} mb={6}>
<Heading size="md" mb={4}>
Nested CascadingSelect
</Heading>
<Text color="fg.muted" mb={4}>
Каскадные select с вложенными путями (address.region → address.district)
</Text>
<VStack gap={4} align="stretch">
<Form.Field.Select name="address.region" options={regions} />
<Form.Field.CascadingSelect
name="address.district"
dependsOn="address.region"
loadOptions={async (parentValue) => {
await new Promise((r) => setTimeout(r, 200))
const regionCode = parentValue as string | undefined
if (!regionCode) {
return []
}
return districtsByRegion[regionCode] ?? []
}}
/>
</VStack>
</Box>
<Form.Button.Submit>Submit</Form.Button.Submit>
</Form>
<SubmittedDataPreview data={submitted} />
</DemoPageLayout>
)
}'use client'
import { FieldCascadingSelect, FieldCombobox, FieldNativeSelect, FieldSelect } from '@letar/forms-shadcn'
import { useState } from 'react'
import { DemoForm, DemoPageLayout, SubmittedDataPreview } from '../_components'
const frameworkOptions = [
{ label: 'React', value: 'react' },
{ label: 'Vue', value: 'vue' },
{ label: 'Svelte', value: 'svelte' },
]
const shippingCountryOptions = [
{ label: 'Россия', value: 'ru' },
{ label: 'Казахстан', value: 'kz' },
]
const CITIES_BY_COUNTRY: Record<string, { label: string; value: string }[]> = {
ru: [{ label: 'Москва', value: 'msk' }, { label: 'Казань', value: 'kzn' }],
kz: [{ label: 'Алматы', value: 'alm' }, { label: 'Астана', value: 'ast' }],
}
interface SelectValues {
framework: string
country: string
frameworkSearch: string
shippingCountry: string
shippingCity: string
}
const defaultValues: SelectValues = {
framework: '',
country: '',
frameworkSearch: '',
shippingCountry: '',
shippingCity: '',
}
export default function SelectDemoPage() {
const [submitted, setSubmitted] = useState<SelectValues | null>(null)
return (
<DemoPageLayout
title="Select-поля"
description="Select, NativeSelect, Combobox, CascadingSelect (зависимый select)"
>
<DemoForm<SelectValues> defaultValues={defaultValues} onSubmit={setSubmitted}>
<FieldSelect name="framework" label="Фреймворк" options={frameworkOptions} placeholder="Выберите" />
<FieldNativeSelect
name="country"
label="Страна"
options={[
{ label: 'Россия', value: 'ru' },
{ label: 'Казахстан', value: 'kz' },
]}
/>
<FieldCombobox name="frameworkSearch" label="Поиск фреймворка" options={frameworkOptions} />
<FieldSelect
name="shippingCountry"
label="Страна доставки"
options={shippingCountryOptions}
placeholder="Выберите"
/>
<FieldCascadingSelect
name="shippingCity"
label="Город доставки"
dependsOn="shippingCountry"
loadOptions={async (country) => CITIES_BY_COUNTRY[country ?? ''] ?? []}
placeholderWhenDisabled="Сначала выберите страну"
/>
<button
type="submit"
className="bg-primary text-primary-foreground rounded-md px-4 py-2 text-sm font-medium"
>
Отправить
</button>
</DemoForm>
<SubmittedDataPreview data={submitted} />
</DemoPageLayout>
)
}import { AppForm } from '@letar/forms-vue'
import { defineComponent, h, ref } from 'vue'
import { z } from 'zod'
import { FieldSelect } from '../../src/index'
/**
* Изолированный пример FieldSelect — самодостаточный файл (своя Zod-схема, свой AppForm).
*/
const schema = z.object({
category: z.string().meta({ ui: { title: 'Категория' } }),
})
const CATEGORY_OPTIONS = [
{ value: 'furniture', label: 'Мебель' },
{ value: 'electronics', label: 'Электроника' },
{ value: 'books', label: 'Книги' },
]
export const SelectDemo = defineComponent({
name: 'SelectDemo',
setup() {
const submitted = ref<Record<string, unknown> | null>(null)
return () =>
h('div', { class: 'space-y-6' }, [
h(
AppForm,
{
schema,
initialValue: { category: '' },
onSubmit: (value: Record<string, unknown>) => {
submitted.value = value
},
},
{
default: () => [
h(FieldSelect, { name: 'category', options: CATEGORY_OPTIONS }),
h(
'button',
{ type: 'submit', class: 'bg-primary text-primary-foreground rounded-md px-4 py-2 text-sm' },
'Сохранить',
),
],
},
),
submitted.value
? h('pre', { class: 'bg-muted mt-4 rounded-md p-3 text-xs' }, JSON.stringify(submitted.value, null, 2))
: null,
])
},
})Angular-пример для этого поля появится позже.
Select
API: Form.Field.Select
Выпадающий список для выбора одного варианта.
const Schema = z.object({
role: z.enum(['user', 'admin', 'moderator']).meta({
ui: { title: 'Роль' },
}),
})
const options = [
{ value: 'user', label: 'Пользователь' },
{ value: 'admin', label: 'Администратор' },
{ value: 'moderator', label: 'Модератор' },
]
<Form.Field.Select name="role" options={options} />Combobox
API: Form.Field.Combobox
Выпадающий список с поиском. Поддерживает асинхронную загрузку опций.
<Form.Field.Combobox name="city" options={cityOptions} placeholder="Начните вводить город..." />RadioGroup
API: Form.Field.RadioGroup
Группа радиокнопок для выбора одного варианта.
<Form.Field.RadioGroup name="plan" options={planOptions} />
// Горизонтальная ориентация
<Form.Field.RadioGroup name="plan" options={planOptions} orientation="horizontal" />RadioCard
API: Form.Field.RadioCard
Карточки для выбора одного варианта — визуально богаче чем RadioGroup.
const options = [
{ value: 'starter', label: 'Starter', description: '$0/мес' },
{ value: 'pro', label: 'Pro', description: '$29/мес' },
]
<Form.Field.RadioCard name="plan" options={options} />Checkbox
API: Form.Field.Checkbox
Одиночный чекбокс (да/нет).
<Form.Field.Checkbox name="agree" />Switch
API: Form.Field.Switch
Переключатель (вкл/выкл).
<Form.Field.Switch name="notifications" />Tags
API: Form.Field.Tags
Ввод тегов — множественные текстовые значения.
<Form.Field.Tags name="skills" />
// С ограничением количества
<Form.Field.Tags name="skills" maxTags={5} />CheckboxCard
API: Form.Field.CheckboxCard
Карточки для множественного выбора.
<Form.Field.CheckboxCard
name="features"
options={[
{ value: 'auth', label: 'Аутентификация', description: 'OAuth, magic links' },
{ value: 'payments', label: 'Платежи', description: 'Интеграция Stripe' },
]}
/>Listbox
API: Form.Field.Listbox
Выпадающий список для единичного или множественного выбора.
<Form.Field.Listbox name="permissions" options={permissionOptions} multiple />NativeSelect
API: Form.Field.NativeSelect
Стандартный HTML <select>. Легче чем Select, использует нативный UI браузера.
<Form.Field.NativeSelect name="country" options={countryOptions} />SegmentedGroup
API: Form.Field.SegmentedGroup
Сегментированный контрол — переключатель для небольших наборов опций.
<Form.Field.SegmentedGroup
name="view"
options={[
{ value: 'grid', label: 'Сетка' },
{ value: 'list', label: 'Список' },
]}
/>