Hooki
Wszystkie hooki udostępniane przez @deenruv/react-ui-devkit — zarządzanie formularzami, listami, tłumaczeniami, zasobami i więcej
Pakiet @deenruv/react-ui-devkit udostępnia zestaw dedykowanych hooków React do budowania pluginów UI panelu administracyjnego. Hooki te obsługują typowe wzorce, takie jak stan formularza, paginowane listy, tłumaczenia i zarządzanie zasobami.
Zarządzanie formularzami
Deenruv wykorzystuje React Hook Form + Zod do zarządzania stanem formularzy i walidacji. Devkit udostępnia trzy komplementarne API:
| API | Zastosowanie |
|---|---|
useDeenruvForm | Główny hook formularza — opakowuje RHF z walidacją Zod |
createFormSchema | Wygodny helper do budowania schematów Zod |
useZodValidators | Gotowe, zlokalizowane walidatory Zod |
Współpracują one z prymitywami formularzy (Form, FormField, FormItem, FormLabel, FormControl, FormMessage), tworząc w pełni dostępne, walidowane formularze.
useDeenruvForm — Główny hook formularza
Standardowy sposób tworzenia stanu formularza w pluginach panelu administracyjnego Deenruv. Opakowuje useForm z React Hook Form z walidacją schematu Zod, udostępniając wygodny helper setField oraz flagi poprawności.
Sygnatura
function useDeenruvForm<T extends FieldValues>(
options: UseDeenruvFormOptions<T>,
): UseDeenruvFormReturn<T>;
interface UseDeenruvFormOptions<T extends FieldValues> {
/** Schemat Zod do walidacji — przekaż z.object({...}) lub dowolny konkretny typ Zod. */
schema: ZodSchema<T>;
defaultValues?: UseFormProps<T>['defaultValues'];
mode?: UseFormProps<T>['mode']; // domyślnie: 'onTouched'
}Podstawowe użycie
import { useDeenruvForm, z } from '@deenruv/react-ui-devkit';
const schema = z.object({
name: z.string().min(1, 'Nazwa jest wymagana'),
slug: z.string().min(1, 'Slug jest wymagany'),
description: z.string(),
enabled: z.boolean(),
});
const form = useDeenruvForm({
schema,
defaultValues: { name: '', slug: '', description: '', enabled: true },
});Dostęp do stanu i aktualizacja
// Obserwuj wartość pola (reaktywnie)
const name = form.watch('name');
// Ustaw pojedyncze pole (auto-walidacja + oznaczenie jako zmienione)
form.setField('name', 'Nowy Produkt');
// Użyj pełnego API RHF
form.setValue('slug', 'nowy-produkt');
form.reset({ name: '', slug: '', description: '', enabled: true });Użycie z prymitywami formularza
import {
useDeenruvForm, z,
DeenruvForm,
FormField, FormItem, FormLabel, FormControl, FormMessage,
Input, Button,
} from '@deenruv/react-ui-devkit';
const schema = z.object({
name: z.string().min(1, 'Nazwa jest wymagana'),
slug: z.string(),
});
function ProductForm() {
const form = useDeenruvForm({
schema,
defaultValues: { name: '', slug: '' },
});
const onSubmit = async (data: z.infer<typeof schema>) => {
await apiClient('mutation')({
updateProduct: [{ input: { id: productId, ...data } }, { id: true }],
});
};
return (
<DeenruvForm form={form} onSubmit={onSubmit}>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Nazwa produktu</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="slug"
render={({ field }) => (
<FormItem>
<FormLabel>Slug</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Zapisz</Button>
</DeenruvForm>
);
}DeenruvForm opakowuje provider shadcn/ui Form i element <form>. Przekazuje cały wynik useDeenruvForm do kontekstu RHF, dzięki czemu FormField, FormMessage itd. działają automatycznie.
Użycie z DetailView
import {
DetailView, useDeenruvForm, z,
DeenruvForm,
FormField, FormItem, FormLabel, FormControl, FormMessage,
Input, Button, Switch, PageBlock, RichTextEditor, useTranslation,
} from '@deenruv/react-ui-devkit';
const productSchema = z.object({
name: z.string().min(1, 'Wymagane'),
slug: z.string(),
description: z.string(),
enabled: z.boolean(),
});
type ProductFormValues = z.infer<typeof productSchema>;
function ProductDetailPage() {
const { id } = useParams();
const { t } = useTranslation('catalog');
const form = useDeenruvForm({
schema: productSchema,
defaultValues: { name: '', slug: '', description: '', enabled: true },
});
const handleSave = async (data: ProductFormValues) => {
await apiClient('mutation')({
updateProduct: [
{
input: {
id,
enabled: data.enabled,
translations: [{
languageCode: LanguageCode.en,
name: data.name,
slug: data.slug,
description: data.description,
}],
},
},
{ id: true },
],
});
};
return (
<DeenruvForm form={form} onSubmit={handleSave}>
<DetailView
title={form.watch('name') || t('product.new')}
locationId="products-detail-view"
tabs={[
{
id: 'product',
label: t('tabs.product'),
content: (
<PageBlock title={t('product.basicInfo')}>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t('product.name')}</FormLabel>
<FormControl><Input {...field} /></FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="slug"
render={({ field }) => (
<FormItem>
<FormLabel>{t('product.slug')}</FormLabel>
<FormControl><Input {...field} /></FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>{t('product.description')}</FormLabel>
<FormControl>
<RichTextEditor value={field.value} onChange={field.onChange} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</PageBlock>
),
},
]}
sidebar={
<FormField
control={form.control}
name="enabled"
render={({ field }) => (
<FormItem>
<FormLabel>{t('product.enabled')}</FormLabel>
<FormControl>
<Switch checked={field.value} onCheckedChange={field.onChange} />
</FormControl>
</FormItem>
)}
/>
}
actions={<Button type="submit">{t('common.save')}</Button>}
/>
</DeenruvForm>
);
}Opis zwracanych wartości
useDeenruvForm zwraca wszystko z useForm React Hook Form, plus:
| Właściwość | Typ | Opis |
|---|---|---|
setField | (field, value) => void | Ustaw pole z automatyczną walidacją i oznaczeniem jako zmienione |
hasErrors | boolean | Czy formularz ma aktualnie jakiekolwiek błędy walidacji |
isFormValid | boolean | Czy formularz spełnia wszystkie reguły walidacji |
control | Control<T> | Kontrolka RHF — przekaż do FormField |
watch | (name?) => T[K] | Subskrybuj zmiany wartości pola |
handleSubmit | (onValid) => (e) => void | Handler wysłania formularza |
reset | (values?) => void | Zresetuj formularz do domyślnych lub podanych wartości |
setValue | (name, value, options?) => void | Niskopoziomowy setter pola RHF |
getValues | () => T | Pobierz wszystkie aktualne wartości pól |
formState | FormState<T> | Pełny stan formularza RHF (errors, isDirty, isSubmitting itp.) |
createFormSchema — Budowniczy schematów
Wygodny wrapper wokół z.object() dla spójnego tworzenia schematów:
import { createFormSchema } from '@deenruv/react-ui-devkit';
import { z } from 'zod';
const schema = createFormSchema({
name: z.string().min(1, 'Nazwa jest wymagana'),
code: z.string().min(1, 'Kod jest wymagany'),
price: z.number().min(0),
customFields: z.record(z.unknown()).optional(),
});To odpowiednik bezpośredniego wywołania z.object(...), ale czyni intencję jasną i zapewnia standardowy wzorzec dla schematów formularzy w panelu administracyjnym.
useZodValidators — Gotowe walidatory
Udostępnia zlokalizowane schematy Zod dla typowych wzorców walidacji. Komunikaty błędów są automatycznie tłumaczone przy użyciu systemu tłumaczeń panelu administracyjnego.
import { useZodValidators, createFormSchema, useDeenruvForm } from '@deenruv/react-ui-devkit';
function MyForm() {
const { requiredString, email, positiveNumber, customFields } = useZodValidators();
const schema = createFormSchema({
name: requiredString(), // z.string().min(1, t('validation.required'))
emailAddress: email(), // z.string().email(t('validation.invalidEmail'))
price: positiveNumber(), // z.number().min(0, t('validation.mustBePositive'))
customFields, // z.record(z.unknown()).optional().default({})
});
const form = useDeenruvForm({
schema,
defaultValues: { name: '', emailAddress: '', price: 0, customFields: {} },
});
// ...
}Dostępne walidatory
| Walidator | Zwraca | Opis |
|---|---|---|
requiredString(msg?) | z.ZodString | Niepusty string, domyślnie zlokalizowany "Wymagane" |
email(msg?) | z.ZodString | Poprawny email, domyślnie zlokalizowany "Nieprawidłowy email" |
positiveNumber(msg?) | z.ZodNumber | Liczba >= 0, domyślnie zlokalizowany "Musi być dodatnia" |
nonEmptyArray(msg?) | z.ZodArray | Tablica z co najmniej 1 elementem |
translationsWithName | z.ZodArray | Tablica obiektów tłumaczeń z niepustym name |
customFields | z.ZodOptional | Permisywny record dla pól niestandardowych, domyślnie {} |
DeenruvForm — Komponent wrappera formularza
Komponent wrapper, który zapewnia kontekst formularza i obsługuje wysyłanie:
import { DeenruvForm } from '@deenruv/react-ui-devkit';
<DeenruvForm form={form} onSubmit={handleSubmit} className="space-y-4">
{/* Komponenty FormField tutaj */}
</DeenruvForm>| Prop | Typ | Opis |
|---|---|---|
form | UseDeenruvFormReturn<T> | Wartość zwracana z useDeenruvForm |
onSubmit | (data: T) => void | Promise<void> | Wywoływany ze zwalidowanymi danymi przy wysłaniu formularza |
children | ReactNode | Treść formularza (komponenty FormField, przyciski itp.) |
className | string? | Opcjonalna klasa CSS dla elementu <form> |
Prymitywy formularza
Devkit reeksportuje prymitywy formularzy shadcn/ui do budowania dostępnych, walidowanych pól:
| Komponent | Opis |
|---|---|
Form | RHF FormProvider — zapewnia kontekst formularza dla dzieci |
FormField | Wrapper RHF Controller — łączy pole z formularzem |
FormItem | Kontener pola z odpowiednimi odstępami |
FormLabel | Dostępna etykieta, która zmienia kolor na czerwony przy błędach walidacji |
FormControl | Slot przekazujący aria-invalid i aria-describedby do inputa |
FormDescription | Opcjonalny tekst opisu pola |
FormMessage | Wyświetla komunikat błędu walidacji pola |
Standardowy wzorzec
<FormField
control={form.control}
name="fieldName"
render={({ field }) => (
<FormItem>
<FormLabel>Etykieta</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>Tekst pomocniczy</FormDescription>
<FormMessage />
</FormItem>
)}
/>Dostępność: Gdy pole ma błędy walidacji, FormControl ustawia aria-invalid="true" na inpucie i łączy go z FormMessage przez aria-describedby. Czytniki ekranu ogłaszają błędy automatycznie.
useList — Paginowane listy
Zarządza stanem paginowanej listy z parametrami URL do sortowania, filtrowania i paginacji. Zwraca gotowy komponent JSX Paginate.
Podstawowe użycie
import { useList, apiClient } from '@deenruv/react-ui-devkit';
const {
Paginate, // Gotowy komponent paginacji JSX
objects, // Elementy bieżącej strony
total, // Łączna liczba elementów
setSort, // Ustaw kolumnę sortowania
setFilter, // Ustaw obiekt filtra
setFilterField, // Ustaw pojedyncze pole filtra
removeFilterField, // Usuń pojedyncze pole filtra
resetFilter, // Wyczyść wszystkie filtry
optionInfo, // Bieżące { page, perPage, sort, filter, filterOperator }
refetch, // Ręczne ponowne pobranie
isFilterOn, // Czy jakiekolwiek filtry są aktywne
} = useList({
route: (options) =>
apiClient('query')({
products: [
{
options: {
take: options.perPage,
skip: (options.page - 1) * options.perPage,
sort: options.sort
? { [options.sort.key]: options.sort.sortDir }
: undefined,
filter: options.filter,
},
},
{ totalItems: true, items: { id: true, name: true, slug: true } },
],
}).then((r) => r.products),
listType: 'products',
});Użycie z szablonem DetailList
Hook useList jest używany wewnętrznie przez komponent szablonu DetailList. Jeśli budujesz standardową stronę listy, preferuj DetailList dla interfejsu wyższego poziomu. Użyj useList bezpośrednio, gdy potrzebujesz pełnej kontroli nad układem listy.
Opis zwracanych wartości
| Właściwość | Typ | Opis |
|---|---|---|
Paginate | JSX.Element | Gotowy komponent paginacji |
objects | T[] | Elementy bieżącej strony |
total | number | Łączna liczba na wszystkich stronach |
setSort | (sort) => void | Ustaw kolumnę i kierunek sortowania |
setFilter | (filter) => void | Ustaw pełny obiekt filtra |
setFilterField | (field, value) => void | Ustaw pojedyncze pole filtra |
removeFilterField | (field) => void | Usuń pojedyncze pole filtra |
resetFilter | () => void | Wyczyść wszystkie filtry |
optionInfo | object | Bieżąca strona, perPage, sort, filter, filterOperator |
refetch | () => void | Uruchom ręczne ponowne pobranie |
isFilterOn | boolean | Czy jakikolwiek filtr jest aktywny |
useTranslation — Internacjonalizacja
Nigdy nie importuj react-i18next bezpośrednio. Zawsze używaj useTranslation z @deenruv/react-ui-devkit. Ten hook wiąże się z globalną instancją i18n Deenruv poprzez window.__DEENRUV_SETTINGS__.i18n, zapewniając prawidłowe działanie tłumaczeń z systemem językowym panelu administracyjnego.
Podstawowe użycie
import { useTranslation } from '@deenruv/react-ui-devkit';
const { t, tEntity, i18n } = useTranslation('my-plugin-namespace');
// Standardowe tłumaczenie
t('my.key'); // "Mój przetłumaczony tekst"
// Tłumaczenie uwzględniające encje z pluralizacją
tEntity('entity.title', 'Product', 'one'); // "Product"
tEntity('entity.title', 'Product', 'many'); // "Products"
tEntity('entity.title', 'Product', 5); // "5 Products"W komponentach
function MyComponent() {
const { t } = useTranslation('reviews');
return (
<div>
<h1>{t('page.title')}</h1>
<p>{t('page.description')}</p>
</div>
);
}useAssets — Zarządzanie zasobami
Zarządza przeglądaniem zasobów z paginacją, wyszukiwaniem tekstowym i filtrowaniem tagów:
import { useAssets } from '@deenruv/react-ui-devkit';
const {
assets, // Bieżąca strona zasobów
isPending, // Stan ładowania
error, // Komunikat błędu
totalItems, // Łączna liczba zasobów
refetchData, // Ręczne ponowne pobranie
page, setPage,
perPage, setPerPage,
searchTerm, setSearchTerm,
searchTags, setSearchTags,
totalPages,
} = useAssets();Opis zwracanych wartości
| Właściwość | Typ | Opis |
|---|---|---|
assets | Asset[] | Bieżąca strona zasobów |
isPending | boolean | Czy trwa pobieranie |
error | string | undefined | Komunikat błędu jeśli pobieranie się nie powiodło |
totalItems | number | Łączna liczba zasobów |
refetchData | () => void | Uruchom ręczne ponowne pobranie |
page / setPage | number / (n) => void | Bieżąca strona |
perPage / setPerPage | number / (n) => void | Elementów na stronę |
searchTerm / setSearchTerm | string / (s) => void | Zapytanie wyszukiwania tekstowego |
searchTags / setSearchTags | string[] / (tags) => void | Filtr tagów |
totalPages | number | Obliczona łączna liczba stron |
useDebounce
Reeksportowany z pakietu use-debounce. Opóźnia wartość z konfigurowalnym czasem:
import { useDebounce } from '@deenruv/react-ui-devkit';
const [debouncedSearch] = useDebounce(searchTerm, 300);useLocalStorage
Trwałe zarządzanie stanem z użyciem localStorage przeglądarki:
import { useLocalStorage } from '@deenruv/react-ui-devkit';
const [viewMode, setViewMode] = useLocalStorage('plugin-view-mode', 'grid');useErrorHandler
Scentralizowana obsługa błędów dla błędów GraphQL i API:
import { useErrorHandler } from '@deenruv/react-ui-devkit';
const { handleError } = useErrorHandler();
try {
await apiClient('mutation')({ /* ... */ });
} catch (error) {
handleError(error);
}useCustomSearchParams
Helper do zarządzania parametrami URL w widokach list. Używany wewnętrznie przez useList:
import { useCustomSearchParams } from '@deenruv/react-ui-devkit';
const { searchParams, setSearchParam } = useCustomSearchParams();