Числовые поля
Числовой ввод — Number, Slider, Currency, Rating
Полный пример
Полный sandbox-пример, прочитанный напрямую из исходников form-develop-app /
form-develop-app-shadcn на сборке — переключите фреймворк/скин, чтобы увидеть то же поле в
разных реализациях.
'use client'
import { Box, Heading, VStack } from '@chakra-ui/react'
import { Form } from '@letar/forms'
import { useState } from 'react'
import { z } from 'zod/v4'
import { DemoPageLayout, SubmittedDataPreview } from '../_components'
/**
* Demo schema for numeric fields
*/
const NumericSchema = z.object({
// NumberInput fields
quantity: z
.number()
.min(1)
.max(100)
.meta({
ui: { title: 'Quantity', description: 'Number of items (1-100)' },
}),
temperature: z
.number()
.min(-50)
.max(50)
.meta({
ui: { title: 'Temperature', description: 'Temperature in Celsius' },
}),
// Currency fields
priceRub: z
.number()
.min(0)
.meta({
ui: { title: 'Price (RUB)', description: 'Price in Russian Rubles' },
}),
priceUsd: z
.number()
.min(0)
.meta({
ui: { title: 'Price (USD)', description: 'Price in US Dollars' },
}),
priceEur: z
.number()
.min(0)
.meta({
ui: { title: 'Price (EUR)', description: 'Price in Euros' },
}),
// Percentage fields
discount: z
.number()
.min(0)
.max(100)
.meta({
ui: { title: 'Discount', description: 'Discount percentage' },
}),
taxRate: z
.number()
.min(0)
.max(100)
.meta({
ui: { title: 'Tax Rate', description: 'Tax rate with decimals' },
}),
margin: z
.number()
.min(0)
.max(50)
.meta({
ui: { title: 'Profit Margin', description: 'Maximum 50%' },
}),
})
type NumericFormData = z.infer<typeof NumericSchema>
const initialValues: NumericFormData = {
quantity: 1,
temperature: 20,
priceRub: 1500,
priceUsd: 99.99,
priceEur: 49.5,
discount: 10,
taxRate: 20,
margin: 25,
}
export default function NumericDemoPage() {
const [submittedData, setSubmittedData] = useState<NumericFormData | null>(null)
const handleSubmit = (data: NumericFormData) => {
setSubmittedData(data)
}
return (
<DemoPageLayout
title="Numeric Fields Demo"
description="Form.Field.NumberInput, Form.Field.Currency, Form.Field.Percentage"
maxW="800px"
>
<Form initialValue={initialValues} schema={NumericSchema} onSubmit={handleSubmit}>
<VStack gap={6} align="stretch">
{/* NumberInput Section */}
<Box>
<Heading size="md" mb={4}>
NumberInput
</Heading>
<VStack gap={4} align="stretch">
<Form.Field.NumberInput name="quantity" min={1} max={100} step={1} allowMouseWheel />
<Form.Field.NumberInput
name="temperature"
min={-50}
max={50}
step={0.5}
formatOptions={{
style: 'unit',
unit: 'celsius',
unitDisplay: 'short',
}}
/>
</VStack>
</Box>
{/* Currency Section */}
<Box>
<Heading size="md" mb={4}>
Currency
</Heading>
<VStack gap={4} align="stretch">
<Form.Field.Currency name="priceRub" currency="RUB" />
<Form.Field.Currency name="priceUsd" currency="USD" />
<Form.Field.Currency name="priceEur" currency="EUR" />
</VStack>
</Box>
{/* Percentage Section */}
<Box>
<Heading size="md" mb={4}>
Percentage
</Heading>
<VStack gap={4} align="stretch">
<Form.Field.Percentage name="discount" />
<Form.Field.Percentage name="taxRate" />
<Form.Field.Percentage name="margin" max={50} />
</VStack>
</Box>
{/* Size Variants */}
<Box>
<Heading size="md" mb={4}>
Size Variants
</Heading>
<VStack gap={4} align="stretch">
<Form.Field.NumberInput name="quantity" label="Extra Small (xs)" size="xs" />
<Form.Field.NumberInput name="quantity" label="Small (sm)" size="sm" />
<Form.Field.NumberInput name="quantity" label="Medium (md)" size="md" />
<Form.Field.NumberInput name="quantity" label="Large (lg)" size="lg" />
</VStack>
</Box>
<Form.Button.Submit>Submit</Form.Button.Submit>
</VStack>
</Form>
<SubmittedDataPreview data={submittedData} />
</DemoPageLayout>
)
}'use client'
import { FieldCalculated, FieldCurrency, FieldNumberInput, FieldPercentage } from '@letar/forms-shadcn'
import { useState } from 'react'
import { DemoForm, DemoPageLayout, SubmittedDataPreview } from '../_components'
interface NumericValues {
price: number
discount: number
stock: number | undefined
finalPrice: number
}
const defaultValues: NumericValues = {
price: 1500,
discount: 15,
stock: 10,
finalPrice: 0,
}
export default function NumericDemoPage() {
const [submitted, setSubmitted] = useState<NumericValues | null>(null)
return (
<DemoPageLayout
title="Числовые поля"
description="Currency, Percentage, NumberInput, Calculated (вычисляемая цена со скидкой)"
>
<DemoForm<NumericValues> defaultValues={defaultValues} onSubmit={setSubmitted}>
<FieldCurrency name="price" label="Цена" />
<FieldPercentage name="discount" label="Скидка" />
<FieldNumberInput name="stock" label="Остаток на складе" min={0} max={999} />
<FieldCalculated
name="finalPrice"
label="Цена со скидкой"
compute={(v) => (v.price as number) * (1 - (v.discount as number) / 100)}
format={(v) => `${Number(v).toLocaleString('ru-RU')} ₽`}
deps={['price', 'discount']}
/>
<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 { FieldNumber } from '../../src/index'
/**
* Изолированный пример FieldNumber — самодостаточный файл (своя Zod-схема, свой AppForm).
*/
const schema = z.object({
rating: z.number().min(1).max(10).meta({ ui: { title: 'Рейтинг (1-10)' } }),
})
export const NumberDemo = defineComponent({
name: 'NumberDemo',
setup() {
const submitted = ref<Record<string, unknown> | null>(null)
return () =>
h('div', { class: 'space-y-6' }, [
h(
AppForm,
{
schema,
initialValue: { rating: 5 },
onSubmit: (value: Record<string, unknown>) => {
submitted.value = value
},
},
{
default: () => [
h(FieldNumber, { name: 'rating' }),
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,
])
},
})import { Component } from '@angular/core'
import { z } from 'zod'
import { AppFormComponent } from '../../src/lib/core/app-form.component'
import { FieldNumberComponent } from '../../src/lib/fields/field-number.component'
/**
* Изолированный пример FieldNumber — самодостаточный файл (своя Zod-схема, свой AppForm).
* Читается напрямую form-docs (P7 Этап 3) тем же приёмом, что у `libs/forms-vue-shadcn/demo/examples`.
*/
const schema = z.object({
quantity: z.number().min(1, 'Минимум 1').meta({ ui: { title: 'Количество' } }),
})
@Component({
standalone: true,
imports: [AppFormComponent, FieldNumberComponent],
template: `
<letar-app-form [schema]="schema" [initialValue]="initialValue" (formSubmit)="onSubmit($event)">
<letar-field-number name="quantity" />
<button type="submit">Сохранить</button>
</letar-app-form>
@if (submittedJson) {
<pre>{{ submittedJson }}</pre>
}
`,
})
export class NumberDemoComponent {
schema = schema
initialValue = { quantity: 1 }
submittedJson = ''
onSubmit(value: Record<string, unknown>): void {
this.submittedJson = JSON.stringify(value, null, 2)
}
}Number
API: Form.Field.Number
Числовое поле с кнопками +/-.
const Schema = z.object({
quantity: z.number().min(1).max(100).int().meta({
ui: { title: 'Количество' },
}),
})
<Form.Field.Number name="quantity" />Slider
API: Form.Field.Slider
Ползунок для выбора числа из диапазона.
const Schema = z.object({
volume: z.number().min(0).max(100).meta({
ui: { title: 'Громкость' },
}),
})
<Form.Field.Slider name="volume" />Продвинутые настройки
<Form.Field.Slider name="brightness" step={10} showValue />Currency
API: Form.Field.Currency
Поле для ввода денежных сумм.
const Schema = z.object({
price: z.number().min(0).meta({
ui: { title: 'Цена' },
}),
})
<Form.Field.Currency name="price" />Rating
API: Form.Field.Rating
Оценка звёздами.
const Schema = z.object({
rating: z.number().min(1).max(5).meta({
ui: { title: 'Оценка' },
}),
})
<Form.Field.Rating name="rating" />Варианты
// 10 звёзд с половинками
<Form.Field.Rating name="quality" count={10} allowHalf />
// Разные размеры
<Form.Field.Rating name="small" size="sm" />
<Form.Field.Rating name="large" size="lg" />