@akhatmirzo/ui

Form

react-hook-form bilan formalar: FormField label, xato va qiymatni har qanday kutubxona komponentiga avtomatik ulaydi.

SMS kod yuboriladi

+998

Pasportdagi 14 xonali raqam

O'rnatish

@akhatmirzo/ui/form — alohida kirish nuqtasi. Asosiy @akhatmirzo/ui importi react-hook-form ni tortmaydi, shuning uchun formasiz loyihalarga ortiqcha kod tushmaydi. react-hook-form — ixtiyoriy peer-bog'liqlik, uni o'zingiz o'rnatasiz (zod va resolver — validatsiya uchun, tavsiya etiladi):

pnpm add react-hook-form @hookform/resolvers zod

Import

import { Form, FormField, FormSubmit, FormRootError, useFormField } from '@akhatmirzo/ui/form';

Qachon ishlatish

  • Validatsiyali har qanday forma — ro'yxatdan o'tish, profil, buyurtma. Label, izoh, xato matni, aria-* va fokus qo'lda yozilmaydi.
  • O'zbekcha maydonlar uchun zUz sxemalari bilan birga ishlating: telefon, JShShIR, pasport, summa — tayyor tekshiruv va o'zbekcha xato matnlari.
  • Bitta-ikkita maydonli oddiy forma uchun react-hook-form shart emas — Field va useState yetarli.
  • Har bir FormField ichida bitta boshqaruv elementi bo'lsin. Bir nechta element kerak bo'lsa — render yoki alohida FormField lar.

To'liq misol

zodResolver sxemani tekshiradi, zUz transformlari esa onSubmit ga tozalangan qiymat beradi: telefon "998901234567", summa "1 250 000" emas 1250000. Formadagi qiymat (foydalanuvchi ko'radigani) o'zgarmaydi.

register-form.tsx
'use client';

import { zUz } from '@akhatmirzo/core/zod';
import { Checkbox, Input, PhoneInput, PinflInput, RegionSelect } from '@akhatmirzo/ui';
import { Form, FormField, FormRootError, FormSubmit } from '@akhatmirzo/ui/form';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { z } from 'zod';

const schema = z.object({
  fullName: z.string().trim().min(3, "To'liq ismingizni kiriting"),
  phone: zUz.phone(),
  pinfl: zUz.pinfl(),
  region: zUz.region(),
  terms: z.literal(true, { error: 'Shartlarga rozilik bering' }),
});

export function RegisterForm() {
  const form = useForm({
    resolver: zodResolver(schema),
    defaultValues: { fullName: '', phone: '', pinfl: '', region: '' },
  });

  async function save(data: z.output<typeof schema>) {
    // data.phone === '998901234567'
    const res = await fetch('/api/register', { method: 'POST', body: JSON.stringify(data) });
    if (!res.ok) form.setError('root', { message: "Server javob bermadi, qayta urinib ko'ring" });
  }

  return (
    <Form form={form} onSubmit={save} className="flex flex-col gap-4">
      <FormField name="fullName" label="To'liq ism" required>
        <Input />
      </FormField>
      <FormField name="phone" label="Telefon" description="SMS kod yuboriladi">
        <PhoneInput />
      </FormField>
      <FormField name="pinfl" label="JShShIR">
        <PinflInput />
      </FormField>
      <FormField name="region" label="Hudud">
        <RegionSelect />
      </FormField>
      <FormField name="terms">
        <Checkbox label="Foydalanish shartlariga roziman" />
      </FormField>
      <FormRootError />
      <FormSubmit>Ro'yxatdan o'tish</FormSubmit>
    </Form>
  );
}

Form — <form noValidate> + FormProvider; onSubmit faqat validatsiyadan o'tganda chaqiriladi. FormSubmit yuborish davomida loading holatiga o'tadi, FormRootError esa setError('root', ...) (yoki 'root.server') xatosini Alert bilan ko'rsatadi.

Moslik jadvali

FormField bolaning turini aniqlaydi va unga mos prop'larni uzatadi. Kutubxona komponentlari uchun tayyor jadval bor; label, description, id, aria-describedby, aria-invalid, required va disabled esa Field konteksti orqali hammasiga avtomatik yetadi.

KomponentUzatiladigan prop'larForma qiymati
Input, Textareavalue, onChange(event), namestring
native <input>, <textarea>, <select>value, onChange(event), name, id, aria-*, disabledstring
native <input type="checkbox">checked, onChange(event), nameboolean
Checkbox, Switchchecked, onCheckedChangeboolean
RadioGroup, Select, RegionSelectvalue, onValueChange, namestring (UzRegionId)
SegmentedControl, ColorPicker, OtpInputvalue, onValueChangestring
MaskedInput, PhoneInput, CardInputvalue, onValueChangestring (toza raqamlar)
PinflInput, PassportInput, InnInput, BankAccountInput, CarPlateInputvalue, onValueChangestring (bo'shliqsiz)
MoneyInputvalue, onValueChangenumber | null
Slidervalue, onValueChangenumber[]
Ratingvalue, onValueChangenumber
Comboboxvalue, onValueChangestring | null · multiple — string[]
TagInputvalue, onValueChangestring[]
DatePickervalue, onValueChangeDate | null
DateRangePickervalue, onValueChange{ from, to } | null
FileUploadonFilesChange (boshqarilmaydi)FileUploadItem[]
boshqa komponentlarvalue, onValueChange (konvensiya)—

Qoidalar:

  • Bo'sh qiymat. Forma qiymati undefined / null bo'lsa, komponent boshqariladigan holatda qolishi uchun bo'sh qiymat beriladi: matnlarga '', Checkbox ga false, TagInput ga [], Rating ga 0, Slider ga [min]. Shu tufayli reset() har bir komponentni tozalaydi.
  • O'z handler'ingiz ham ishlaydi. <Input onChange={log} /> — avval sizniki, keyin formaniki chaqiriladi.
  • FileUpload ichki holatini o'zi boshqaradi: forma qiymati tashqaridan o'zgarsa (reset, setValue) komponent qayta yaratiladi va ro'yxat tozalanadi.
  • Fokus. Submit'dagi xato yoki form.setFocus(name) maydonning asosiy elementini fokuslaydi (input, Select tugmasi, slider dastagi ...). Bolaga ref uzatish shart emas.

Barcha qatorlar integratsiya testlari bilan tekshirilgan: qiymat kiradi va chiqadi, xato Field da chiqadi, fokus xatoli maydonga o'tadi, reset() ishlaydi.

Ko'p komponentli forma

value / onValueChange komponentlari (SegmentedControl, MoneyInput, TagInput) va boshqarilmaydigan FileUpload bitta formada. Narx zUz.money({ min, max }) bilan tekshiriladi.

10 000 so'mdan 500 mln so'mgacha

Enter yoki vergul bilan qo'shing

Faylni shu yerga tashlang yoki tanlangRasmlar · ko'pi bilan 4 ta

Zaxira yo'li: render

Jadvalda yo'q yoki boshqa API'li element uchun render ni ishlating — value, onValueChange va controlProps (id, aria-*) qo'lingizda:

<FormField
  name="city"
  label="Shahar"
  render={({ value, onValueChange, controlProps }) => (
    <select {...controlProps} value={value} onChange={(e) => onValueChange(e.target.value)}>
      <option value="tsh">Toshkent</option>
    </select>
  )}
/>

O'z komponentingiz

useFormField() — FormField ichidagi komponent maydonga o'zi ulanadi. Bunday bolaga adapter="none" bering, shunda unga prop'lar uzatilmaydi:

function Stepper() {
  const { value, onValueChange, id, invalid } = useFormField<number>();
  return (
    <button type="button" id={id} aria-invalid={invalid} onClick={() => onValueChange(value + 1)}>
      {value}
    </button>
  );
}

<FormField name="guests" label="Mehmonlar" adapter="none">
  <Stepper />
</FormField>;

createFormAdapter() — prop nomlari boshqacha bo'lgan komponent uchun:

import { createFormAdapter } from '@akhatmirzo/ui/form';

const pickerAdapter = createFormAdapter({
  valueProp: 'selected',
  changeProp: 'onSelect',
  empty: [],
});

<FormField name="ids" label="Mahsulotlar" adapter={pickerAdapter}>
  <ProductPicker />
</FormField>;

Tayyor nomlar: adapter="native" (value + onChange(event)), "value", "checked", "none".

Props

Form

Qolgan barcha <form> atributlari ham qabul qilinadi.

PropTipStandartTavsif
formmajburiyUseFormReturn<TFieldValues, TContext, TTransformedValues>—useForm(...) natijasi
onSubmitmajburiySubmitHandler<TTransformedValues>—Validatsiyadan o'tgan (resolver o'zgartirgan) qiymatlar bilan
onInvalidSubmitErrorHandler<TFieldValues>—Validatsiya xatolari bilan

FormField

label, description, required, disabled, orientation, className va boshqa Field prop'lari ham qabul qilinadi (error dan tashqari — u formadan olinadi).

PropTipStandartTavsif
namemajburiyFieldPath<TFieldValues>—Maydon nomi
childrenReactElement—Bitta boshqaruv elementi — prop'lar avtomatik uzatiladi
render(field: FormFieldRenderProps) => ReactNode—Zaxira yo'l: elementni o'zingiz chizasiz
adapterFormFieldAdapter | 'native' | 'nativeChecked' | 'value' | 'checked' | 'none'—Qiymat qanday uzatilishi; standart — avtomatik (moslik jadvali)
controlControl<TFieldValues>—Berilmasa — eng yaqin <Form> dan
rulesRegisterOptions—react-hook-form qoidalari
defaultValueFieldPathValue—Maydonning boshlang'ich qiymati
shouldUnregisterboolean—O'chirilganda qiymat ham olib tashlanadi

FormSubmit

Button prop'lari; type="submit", yuborilayotganda loading.

PropTipStandartTavsif
variantButtonVariant'primary'

FormRootError

Alert prop'lari. Xato bo'lmasa hech narsa chizilmaydi.

PropTipStandartTavsif
color'neutral' | 'primary' | 'success' | 'warning' | 'danger' | 'info''danger'
children(message: string) => ReactNode—Matn o'rniga o'z ko'rinishingiz

useFormField / render argumenti

PropTipStandartTavsif
namestring—
valueTValue—Formadagi joriy qiymat
onValueChange(value: TValue) => void—
onBlur() => void—Maydon "tegilgan" deb belgilanadi
invalidboolean—
errorstring | undefined—Xato matni
idstring—Label ulangan id
disabledboolean—
controlProps{ id; 'aria-describedby'?; 'aria-invalid'?; 'aria-required'? }—Faqat render argumentida — native elementga yoyiladi

Accessibility

  • Label va xato bog'langan. Har bir maydon Field orqali <label htmlFor>, aria-describedby (izoh + xato) va aria-invalid oladi; native elementlar va render ham shu atributlarni oladi.
  • Xato e'lon qilinadi. Xato matni aria-live="polite" hududida — submit'dan keyin ekran o'quvchi o'qiydi. FormRootError — role="alert".
  • Fokus xatoga o'tadi. Submit'da birinchi xatoli maydonning asosiy elementi fokuslanadi (shouldFocusError), klaviatura foydalanuvchisi darhol tuzatishni boshlaydi.
  • Brauzer validatsiyasi o'chirilgan (noValidate) — xabarlar bir xil ko'rinishda, o'zbekcha.
  • Portal'li komponentlar (Select, Combobox, DateRangePicker) ochilganda maydon "tark etilgan" hisoblanmaydi — mode: 'onBlur' da xato muddatidan oldin chiqmaydi.

Sahifada