Number Fields
Numeric input fields — Number, Slider, Currency, Percentage, Rating
Full example
The complete sandbox example, read directly from the form-develop-app / form-develop-app-shadcn
source at build time — switch framework/skin to see the same field across implementations.
'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' },
}),
priceKopecks: z
.number()
.int()
.min(0)
.meta({
ui: { title: 'Price (kopecks, minorUnitScale=100)', description: 'Stored as integer kopecks, edited as rubles' },
}),
// 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%' },
}),
annualRateBps: z
.number()
.int()
.min(0)
.meta({
ui: {
title: 'Annual rate (basis points, minorUnitScale=100)',
description: 'Stored as integer basis points, edited as percent',
},
}),
})
type NumericFormData = z.infer<typeof NumericSchema>
const initialValues: NumericFormData = {
quantity: 1,
temperature: 20,
priceRub: 1500,
priceUsd: 99.99,
priceEur: 49.5,
priceKopecks: 150000,
discount: 10,
taxRate: 20,
margin: 25,
annualRateBps: 1350,
}
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" />
<Form.Field.Currency name="priceKopecks" currency="RUB" minorUnitScale={100} />
</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} />
<Form.Field.Percentage name="annualRateBps" min={0} max={10000} decimalScale={1} minorUnitScale={100} />
</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
Numeric input with increment/decrement stepper.
const Schema = z.object({
quantity: z.number().int().min(1).max(100).meta({
ui: { title: 'Quantity' },
}),
})
<Form.Field.Number name="quantity" />Auto Constraints
z.number().min(0).max(100)
// → min={0} max={100} step={1} on the input
z.number().int()
// → step={1} (integer only)Slider
API: Form.Field.Slider
Range slider for selecting a value within a range.
const Schema = z.object({
volume: z.number().min(0).max(100).meta({
ui: { title: 'Volume' },
}),
})
<Form.Field.Slider name="volume" />Currency
API: Form.Field.Currency
Money input with currency symbol formatting. currency is a component prop, not part of the
Zod meta().ui block — it defaults to 'RUB'.
const Schema = z.object({
price: z.number().min(0).meta({
ui: { title: 'Price' },
}),
})
<Form.Field.Currency name="price" currency="USD" />Percentage
API: Form.Field.Percentage
Percentage input (0-100%) with % suffix.
const Schema = z.object({
discount: z.number().min(0).max(100).meta({
ui: { title: 'Discount' },
}),
})
<Form.Field.Percentage name="discount" />Rating
API: Form.Field.Rating
Star rating input for reviews and feedback.
const Schema = z.object({
rating: z.number().min(1).max(5).meta({
ui: { title: 'Your Rating' },
}),
})
<Form.Field.Rating name="rating" />