@krizaka/ui 2.10.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/audio-player.js +1 -1
- package/dist/{chunk-EUPTXKH7.js → chunk-BRKMQ6JV.js} +2 -1
- package/dist/chunk-FDCMBWUU.js +168 -0
- package/dist/chunk-FDCMBWUU.js.map +1 -0
- package/dist/chunk-HCNOOPZE.js +210 -0
- package/dist/chunk-HCNOOPZE.js.map +1 -0
- package/dist/{chunk-X6TW3X6Y.js → chunk-LCZXS6AK.js} +2 -2
- package/dist/chunk-OGJI5X4W.js +157 -0
- package/dist/chunk-OGJI5X4W.js.map +1 -0
- package/dist/combobox.js +2 -2
- package/dist/data-table.d.ts +284 -24
- package/dist/data-table.js +914 -121
- package/dist/data-table.js.map +1 -1
- package/dist/date-picker.js +1 -1
- package/dist/filter-bar.d.ts +12 -12
- package/dist/filter-bar.js +9 -145
- package/dist/filter-bar.js.map +1 -1
- package/dist/form/server.d.ts +96 -0
- package/dist/form/server.js +82 -0
- package/dist/form/server.js.map +1 -0
- package/dist/form.d.ts +503 -0
- package/dist/form.js +760 -0
- package/dist/form.js.map +1 -0
- package/dist/image-cropper.js +1 -1
- package/dist/media-capture.js +1 -1
- package/dist/native/form.d.ts +376 -0
- package/dist/native/form.js +650 -0
- package/dist/native/form.js.map +1 -0
- package/dist/native/list.d.ts +95 -0
- package/dist/native/list.js +135 -0
- package/dist/native/list.js.map +1 -0
- package/dist/native.d.ts +113 -2
- package/dist/native.js +618 -388
- package/dist/native.js.map +1 -1
- package/dist/search-field.js +6 -160
- package/dist/search-field.js.map +1 -1
- package/dist/tag-input.js +2 -2
- package/dist/video-player.js +1 -1
- package/dist/video-trimmer.js +1 -1
- package/dist/virtual.d.ts +140 -0
- package/dist/virtual.js +19 -0
- package/dist/virtual.js.map +1 -0
- package/package.json +111 -8
- package/registry/achievement.json +1 -1
- package/registry/action-bar.json +1 -1
- package/registry/alert.json +1 -1
- package/registry/app-shell.json +1 -1
- package/registry/argument-card.json +1 -1
- package/registry/audio-player.json +1 -1
- package/registry/auth-layout.json +1 -1
- package/registry/avatar.json +1 -1
- package/registry/badge.json +1 -1
- package/registry/button.json +1 -1
- package/registry/card-grid.json +1 -1
- package/registry/card.json +1 -1
- package/registry/catalogue-page.json +1 -1
- package/registry/changelog-page.json +1 -1
- package/registry/checkbox.json +89 -4
- package/registry/chip-link.json +1 -1
- package/registry/chip.json +1 -1
- package/registry/choice-grid.json +1 -1
- package/registry/code-block.json +1 -1
- package/registry/combobox.json +1 -1
- package/registry/command.json +1 -1
- package/registry/component-page.json +1 -1
- package/registry/confirm-button.json +1 -1
- package/registry/contact-page.json +1 -1
- package/registry/continue-prompt.json +1 -1
- package/registry/countdown.json +1 -1
- package/registry/cta-panel.json +1 -1
- package/registry/dashboard-layout.json +1 -1
- package/registry/data-table.json +643 -49
- package/registry/date-picker.json +142 -4
- package/registry/decision-list.json +1 -1
- package/registry/detail-layout.json +1 -1
- package/registry/dialog.json +1 -1
- package/registry/docs-article-page.json +1 -1
- package/registry/docs-hub-page.json +1 -1
- package/registry/docs-layout.json +1 -1
- package/registry/dropdown-menu.json +1 -1
- package/registry/empty-state.json +1 -1
- package/registry/error-page.json +1 -1
- package/registry/examples/checkbox/native/terms.d.ts +4 -0
- package/registry/examples/checkbox/native/terms.dark.png +0 -0
- package/registry/examples/checkbox/native/terms.js +26 -0
- package/registry/examples/checkbox/native/terms.light.png +0 -0
- package/registry/examples/checkbox/native/terms.tsx +28 -0
- package/registry/examples/data-table/details.d.ts +4 -0
- package/registry/examples/data-table/details.js +33 -0
- package/registry/examples/data-table/details.tsx +31 -0
- package/registry/examples/data-table/filters.d.ts +4 -0
- package/registry/examples/data-table/filters.js +40 -0
- package/registry/examples/data-table/filters.tsx +40 -0
- package/registry/examples/data-table/grouped.d.ts +4 -0
- package/registry/examples/data-table/grouped.js +24 -0
- package/registry/examples/data-table/grouped.tsx +23 -0
- package/registry/examples/data-table/paginated.d.ts +4 -0
- package/registry/examples/data-table/paginated.js +23 -0
- package/registry/examples/data-table/paginated.tsx +22 -0
- package/registry/examples/data-table/pinned.d.ts +4 -0
- package/registry/examples/data-table/pinned.js +26 -0
- package/registry/examples/data-table/pinned.tsx +25 -0
- package/registry/examples/data-table/server.d.ts +4 -0
- package/registry/examples/data-table/server.js +40 -0
- package/registry/examples/data-table/server.tsx +40 -0
- package/registry/examples/data-table/virtualized.js +2 -2
- package/registry/examples/data-table/virtualized.tsx +3 -3
- package/registry/examples/date-picker/native/birth-date.d.ts +4 -0
- package/registry/examples/date-picker/native/birth-date.dark.png +0 -0
- package/registry/examples/date-picker/native/birth-date.js +30 -0
- package/registry/examples/date-picker/native/birth-date.light.png +0 -0
- package/registry/examples/date-picker/native/birth-date.tsx +27 -0
- package/registry/examples/field/native/default.d.ts +4 -0
- package/registry/examples/field/native/default.dark.png +0 -0
- package/registry/examples/field/native/default.js +29 -0
- package/registry/examples/field/native/default.light.png +0 -0
- package/registry/examples/field/native/default.tsx +23 -0
- package/registry/examples/form/amount.d.ts +4 -0
- package/registry/examples/form/amount.js +30 -0
- package/registry/examples/form/amount.tsx +36 -0
- package/registry/examples/form/audience.d.ts +4 -0
- package/registry/examples/form/audience.js +29 -0
- package/registry/examples/form/audience.tsx +31 -0
- package/registry/examples/form/native/amount.d.ts +4 -0
- package/registry/examples/form/native/amount.dark.png +0 -0
- package/registry/examples/form/native/amount.js +26 -0
- package/registry/examples/form/native/amount.light.png +0 -0
- package/registry/examples/form/native/amount.tsx +30 -0
- package/registry/examples/form/native/audience.d.ts +4 -0
- package/registry/examples/form/native/audience.dark.png +0 -0
- package/registry/examples/form/native/audience.js +24 -0
- package/registry/examples/form/native/audience.light.png +0 -0
- package/registry/examples/form/native/audience.tsx +24 -0
- package/registry/examples/form/native/profile.d.ts +4 -0
- package/registry/examples/form/native/profile.dark.png +0 -0
- package/registry/examples/form/native/profile.js +33 -0
- package/registry/examples/form/native/profile.light.png +0 -0
- package/registry/examples/form/native/profile.tsx +36 -0
- package/registry/examples/form/native/settings.d.ts +4 -0
- package/registry/examples/form/native/settings.dark.png +0 -0
- package/registry/examples/form/native/settings.js +25 -0
- package/registry/examples/form/native/settings.light.png +0 -0
- package/registry/examples/form/native/settings.tsx +23 -0
- package/registry/examples/form/native/sign-up.d.ts +4 -0
- package/registry/examples/form/native/sign-up.dark.png +0 -0
- package/registry/examples/form/native/sign-up.js +38 -0
- package/registry/examples/form/native/sign-up.light.png +0 -0
- package/registry/examples/form/native/sign-up.tsx +34 -0
- package/registry/examples/form/native/upload.d.ts +4 -0
- package/registry/examples/form/native/upload.dark.png +0 -0
- package/registry/examples/form/native/upload.js +23 -0
- package/registry/examples/form/native/upload.light.png +0 -0
- package/registry/examples/form/native/upload.tsx +22 -0
- package/registry/examples/form/profile.d.ts +4 -0
- package/registry/examples/form/profile.js +35 -0
- package/registry/examples/form/profile.tsx +40 -0
- package/registry/examples/form/report.d.ts +4 -0
- package/registry/examples/form/report.js +30 -0
- package/registry/examples/form/report.tsx +30 -0
- package/registry/examples/form/settings.d.ts +4 -0
- package/registry/examples/form/settings.js +30 -0
- package/registry/examples/form/settings.tsx +31 -0
- package/registry/examples/form/sign-up.d.ts +4 -0
- package/registry/examples/form/sign-up.js +38 -0
- package/registry/examples/form/sign-up.tsx +37 -0
- package/registry/examples/form/upload.d.ts +4 -0
- package/registry/examples/form/upload.js +32 -0
- package/registry/examples/form/upload.tsx +32 -0
- package/registry/examples/list/native/default.d.ts +4 -0
- package/registry/examples/list/native/default.dark.png +0 -0
- package/registry/examples/list/native/default.js +33 -0
- package/registry/examples/list/native/default.light.png +0 -0
- package/registry/examples/list/native/default.tsx +31 -0
- package/registry/examples/list/native/infinite.d.ts +4 -0
- package/registry/examples/list/native/infinite.dark.png +0 -0
- package/registry/examples/list/native/infinite.js +30 -0
- package/registry/examples/list/native/infinite.light.png +0 -0
- package/registry/examples/list/native/infinite.tsx +35 -0
- package/registry/examples/list/native/states.d.ts +4 -0
- package/registry/examples/list/native/states.dark.png +0 -0
- package/registry/examples/list/native/states.js +26 -0
- package/registry/examples/list/native/states.light.png +0 -0
- package/registry/examples/list/native/states.tsx +21 -0
- package/registry/examples/virtual/feed.d.ts +4 -0
- package/registry/examples/virtual/feed.js +26 -0
- package/registry/examples/virtual/feed.tsx +24 -0
- package/registry/examples/virtual/gallery.d.ts +4 -0
- package/registry/examples/virtual/gallery.js +31 -0
- package/registry/examples/virtual/gallery.tsx +28 -0
- package/registry/examples/virtual/messages.d.ts +4 -0
- package/registry/examples/virtual/messages.js +28 -0
- package/registry/examples/virtual/messages.tsx +28 -0
- package/registry/field.json +104 -4
- package/registry/file-upload.json +1 -1
- package/registry/filter-bar.json +1 -1
- package/registry/form.json +2940 -0
- package/registry/gallery-page.json +1 -1
- package/registry/glow-card.json +1 -1
- package/registry/goal-gauge.json +1 -1
- package/registry/how-it-works-page.json +1 -1
- package/registry/image-cropper.json +1 -1
- package/registry/index.json +86 -11
- package/registry/iso.json +1 -1
- package/registry/kbd.json +1 -1
- package/registry/landing-page.json +1 -1
- package/registry/landscape.json +1 -1
- package/registry/legal-page.json +1 -1
- package/registry/link-card.json +1 -1
- package/registry/list.json +249 -0
- package/registry/loader.json +1 -1
- package/registry/longform-page.json +1 -1
- package/registry/marketing-layout.json +1 -1
- package/registry/marquee.json +1 -1
- package/registry/mascot-quote.json +1 -1
- package/registry/mascot-state.json +2 -2
- package/registry/mascots.json +2 -2
- package/registry/media-capture.json +1 -1
- package/registry/meter.json +1 -1
- package/registry/nav-bar.json +1 -1
- package/registry/number-ticker.json +1 -1
- package/registry/page-header.json +1 -1
- package/registry/page-hero.json +1 -1
- package/registry/page.json +1 -1
- package/registry/pagination.json +1 -1
- package/registry/pointer-light.json +1 -1
- package/registry/popover.json +1 -1
- package/registry/pricing-page.json +1 -1
- package/registry/product-page.json +1 -1
- package/registry/profile-card.json +1 -1
- package/registry/progress.json +1 -1
- package/registry/promise-card.json +1 -1
- package/registry/radio-group.json +1 -1
- package/registry/scroll-stage.json +1 -1
- package/registry/search-field.json +1 -1
- package/registry/section-backdrop.json +1 -1
- package/registry/separator.json +1 -1
- package/registry/settings-layout.json +1 -1
- package/registry/skeleton.json +1 -1
- package/registry/slider.json +1 -1
- package/registry/slot.json +1 -1
- package/registry/spinner.json +1 -1
- package/registry/split-view.json +1 -1
- package/registry/stage-banner.json +1 -1
- package/registry/stat.json +1 -1
- package/registry/status-page.json +1 -1
- package/registry/stepper.json +1 -1
- package/registry/switch.json +1 -1
- package/registry/tabs.json +1 -1
- package/registry/tag-input.json +1 -1
- package/registry/terminal-replay.json +1 -1
- package/registry/theme.json +1 -1
- package/registry/toast.json +1 -1
- package/registry/toc.json +1 -1
- package/registry/tooltip.json +1 -1
- package/registry/video-player.json +1 -1
- package/registry/video-trimmer.json +1 -1
- package/registry/virtual.json +569 -0
- package/skills/data-table/SKILL.md +56 -0
- package/skills/form/SKILL.md +44 -0
- package/skills/native-list/SKILL.md +39 -0
- package/skills/virtual/SKILL.md +35 -0
- /package/dist/{chunk-EUPTXKH7.js.map → chunk-BRKMQ6JV.js.map} +0 -0
- /package/dist/{chunk-X6TW3X6Y.js.map → chunk-LCZXS6AK.js.map} +0 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: krizaka-data-table
|
|
3
|
+
description: Tables of records in a Krizaka web app with @krizaka/ui/data-table v2 (TanStack Table v9 behind the Krizaka facade) — sorting, text/facet/range filters, grouping, detail panels and trees, pagination, selection, column visibility, pinning, resizing, CSV export, server mode with @krizaka/query, windowing of long lists, cards on a phone. Use it before writing any table, list of records, admin grid or <table> in a Krizaka product.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @krizaka/ui/data-table (v2)
|
|
7
|
+
|
|
8
|
+
## Rules
|
|
9
|
+
|
|
10
|
+
1. Import from `@krizaka/ui/data-table` — **never `@tanstack/react-table`** (ESLint `krizaka/no-direct-tanstack-import`,
|
|
11
|
+
dependency-cruiser). The escape hatch is `useDataTable().table` (the TanStack instance), inside the table's `toolbar`.
|
|
12
|
+
2. **No `<table>` written by hand** in an app for records (ratchet `krizaka/no-local-table-or-form`).
|
|
13
|
+
3. `rows` and `columns` are **stable**: module scope, state, a query's `data`, or `useMemo`. Never `rows={data ?? []}` —
|
|
14
|
+
use a module constant (`const NONE: Row[] = []`).
|
|
15
|
+
4. Words are props, passed translated: `header` of each column, `caption`, `labels` (`dataTableLabels` lists them).
|
|
16
|
+
5. A server list: `manual={{ sorting: true, filtering: true, pagination: true }}`, `rowCount`, `onStateChange` → the
|
|
17
|
+
`DataTableQuery` in state (or the URL) → the query key (`defineKeys`) → `krizakaFetch`. `placeholderData:
|
|
18
|
+
keepPreviousData` keeps the page while the next loads; `meta: { list: true }` keeps errors in the table.
|
|
19
|
+
|
|
20
|
+
## Columns
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
const columns: DataTableColumn<Job>[] = [
|
|
24
|
+
{ id: "name", header: t.jobs.name, sortable: true, card: "title", filter: "text" },
|
|
25
|
+
{ id: "status", header: t.jobs.status, filter: "facet", groupable: true, options: statusOptions, cell: (j) => <Badge>…</Badge> },
|
|
26
|
+
{ id: "cost", header: t.jobs.cost, align: "end", filter: "range", value: (j) => j.cents, aggregate: (rows) => sum(rows) },
|
|
27
|
+
];
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`value` is what sorts, filters, groups and exports (default `row[id]`); `cell` is what shows.
|
|
31
|
+
|
|
32
|
+
## Recipes
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
// Client table with filters, columns menu, export and pages
|
|
36
|
+
<DataTable caption={t.jobs.title} rows={jobs} columns={columns} getRowId={(j) => j.id} rowLabel={(j) => j.name}
|
|
37
|
+
selectable onSelectedChange={setSelected} defaultPageSize={25} pageSizeOptions={[25, 50, 100]}
|
|
38
|
+
toolbar={<><DataTableFilters /><DataTableColumnsMenu /><DataTableExport filename="jobs.csv" /></>}
|
|
39
|
+
labels={t.dataTable} />
|
|
40
|
+
|
|
41
|
+
// Server table
|
|
42
|
+
const [query, setQuery] = useState<DataTableQuery>({ sort: null, filters: {}, page: 1, pageSize: 25 });
|
|
43
|
+
const { data, isPending, error } = useQuery({ ...jobsQuery(query), placeholderData: keepPreviousData });
|
|
44
|
+
<DataTable rows={data?.rows ?? NONE} rowCount={data?.total} manual={{ sorting: true, filtering: true, pagination: true }}
|
|
45
|
+
defaultPageSize={25} onStateChange={setQuery} loading={isPending} error={error && <ErrorState … />} … />
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Also: `groupBy` / `defaultGroupBy`, `renderDetail` (a panel under a row), `getSubRows` (a tree), `pinning={{ start:
|
|
49
|
+
["name"], end: ["actions"] }}`, `resizing`, `columnVisibility`, `virtualized={{ height: 480 }}` (thousands of rows
|
|
50
|
+
loaded at once), `layout="cards"` / `auto` (cards below 36rem), `density="compact"`, `loading` / `empty` / `error`.
|
|
51
|
+
|
|
52
|
+
## Accessibility (built in)
|
|
53
|
+
|
|
54
|
+
Caption, `scope="col"`, `aria-sort`, `aria-expanded` on expand buttons, focusable resize separators (← →), a named
|
|
55
|
+
filters group with an announced count, `aria-busy` while loading, `role="alert"` on errors, `aria-rowcount` /
|
|
56
|
+
`aria-rowindex` when windowed. Give `caption` and `rowLabel` real words.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: krizaka-form
|
|
3
|
+
description: Forms in a Krizaka app (Next.js or Expo) with @krizaka/ui/form and @krizaka/ui/native/form — createKrizakaForm({ schema, defaultValues }) on a zod 4 schema shared with the server, typed fields (TextField, TextArea, Checkbox, Switch, RadioGroup, Select, Combobox, DateField, TagInput, FileUpload, Money, Slider), Submit, FormError, FieldArray, Watch, async checks (username availability), server Problem Details mapped onto fields, useFormWithAction for Server Actions. Use it instead of a hand-made <form>, useState per field, react-hook-form or a direct @tanstack/react-form import.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @krizaka/ui/form · @krizaka/ui/native/form
|
|
7
|
+
|
|
8
|
+
## Rules
|
|
9
|
+
|
|
10
|
+
1. A form = a schema (zod 4) + defaults, defined once beside the API contract (`packages/core` of a product):
|
|
11
|
+
`const signUp = createKrizakaForm({ schema, defaultValues })`. Web imports from `@krizaka/ui/form`, Expo from
|
|
12
|
+
`@krizaka/ui/native/form` — same definition, same `useForm`.
|
|
13
|
+
2. Never import `@tanstack/react-form`. Need a control the package lacks? `form.Field name="x">{(f) => …}` gives value,
|
|
14
|
+
setter, error and ids (`useKrizakaField`).
|
|
15
|
+
3. Words are keys: name refusals in the schema (`z.string().regex(re, "signUp.username.format")`), give the words in
|
|
16
|
+
`messages/*.json`, pass `i18n: { t, locale }`. Generic keys are `form.errors.*` (`required`, `tooShort {minimum}`,
|
|
17
|
+
`tooLong {maximum}`, `invalidEmail`, `invalidUrl`, `invalidDate`, `invalidNumber`, `tooFew`, `tooMany`, `invalid`);
|
|
18
|
+
the whole-form refusals are `apiErrors.<code>` (as `KrizakaApiError.messageKey()`).
|
|
19
|
+
4. Send with a mutation (`onSubmit: (v) => mutation.mutateAsync(v)`) or a Server Action (`form.useFormWithAction(action)`,
|
|
20
|
+
the action answers `{ ok: true, data } | { ok: false, problem }`, re-validating with `validateForm(schema, input)` of
|
|
21
|
+
`@krizaka/ui/form/server`). A thrown `KrizakaApiError` lands on its `fields`; `codeFields: { UNDERAGE: "dateOfBirth" }`
|
|
22
|
+
puts a code under its field; the rest shows in `<form.FormError />`.
|
|
23
|
+
5. Never disable Submit for an invalid form. Errors show once a field is left (or on submit) and go as soon as fixed.
|
|
24
|
+
|
|
25
|
+
## Recipes
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
const form = signUp.useForm({ i18n: { t }, codeFields: { USERNAME_RESERVED: "username" }, onSubmit: (v) => register.mutateAsync(v) });
|
|
29
|
+
<form.Form aria-label={t("signUp.title")}>
|
|
30
|
+
<form.TextField name="email" label={t("signUp.email")} type="email" autoComplete="email" />
|
|
31
|
+
<form.TextField name="username" label={t("signUp.username")} availableHint={t("signUp.free")}
|
|
32
|
+
validateAsync={async (u, signal) => ((await checkUsername(u, signal)).available ? null : { key: "signUp.username.taken" })} />
|
|
33
|
+
<form.DateField name="dateOfBirth" label={t("signUp.birth")} entry="parts" autoComplete="bday" /> {/* 18+: z.iso.date().refine((d) => d <= latestBirthDate(18), "form.errors.UNDERAGE") */}
|
|
34
|
+
<form.Checkbox name="acceptTerms" label={t("signUp.terms")} />
|
|
35
|
+
<form.FormError />
|
|
36
|
+
<form.Submit>{t("signUp.submit")}</form.Submit>
|
|
37
|
+
</form.Form>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- Amounts: `form.Money name="amount" currency="EUR"` stores integer cents (`z.number().int()`).
|
|
41
|
+
- Lists: `<form.FieldArray name="links">{(a) => a.items.map((_, i) => <form.TextField name={`links[${i}].url`} … />)}</form.FieldArray>`.
|
|
42
|
+
- Conditional parts: `<form.Watch name="audience">{(v) => v === "subscribers" ? <form.Money name="price" … /> : null}</form.Watch>`.
|
|
43
|
+
- Native: `<form.Form scroll>` makes the form the screen's scrolling content above the keyboard; the return key walks the
|
|
44
|
+
text fields and the last one sends; `<form.Submit label=… brand="orochia" />` turns the product's mark while sending.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: krizaka-native-list
|
|
3
|
+
description: Lists in a Krizaka React Native / Expo app with @krizaka/ui/native/list — List on FlashList v2 (separators, skeleton rows, empty/error states, pull to refresh, spinner for the next page) and useInfiniteList (useInfiniteQuery of @krizaka/query wired to onEndReached). Use it instead of FlatList, ScrollView+map or a direct @shopify/flash-list import.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @krizaka/ui/native/list
|
|
7
|
+
|
|
8
|
+
## Rules
|
|
9
|
+
|
|
10
|
+
1. Import `List` and `useInfiniteList` from `@krizaka/ui/native/list` — not `FlatList`, not `@shopify/flash-list`
|
|
11
|
+
directly. The app installs the optional peers: `@shopify/flash-list` (v2) and `@krizaka/query`.
|
|
12
|
+
2. Server data goes through `@krizaka/query` (never `@tanstack/react-query`): one `KrizakaQueryProvider` at the root.
|
|
13
|
+
3. `keyExtractor` returns an id; `renderItem` stays light (views are recycled); words are passed translated.
|
|
14
|
+
|
|
15
|
+
## Recipes
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
// A paginated API
|
|
19
|
+
const { listProps, error } = useInfiniteList({
|
|
20
|
+
queryKey: notificationKeys.list(),
|
|
21
|
+
queryFn: ({ pageParam, signal }) => krizakaFetch("/api/notifications", { schema: NotificationPage, query: { cursor: pageParam }, signal }),
|
|
22
|
+
initialPageParam: null as string | null,
|
|
23
|
+
getNextPageParam: (last) => last.nextCursor,
|
|
24
|
+
getItems: (page) => page.items,
|
|
25
|
+
});
|
|
26
|
+
return (
|
|
27
|
+
<List label={t.notifications.title} {...listProps} keyExtractor={(n) => n.id} separator="inset"
|
|
28
|
+
renderItem={(n) => <NotificationRow notification={n} />}
|
|
29
|
+
error={error ? <ErrorState title={t.errors.load} action={…} /> : undefined}
|
|
30
|
+
empty={{ title: t.notifications.empty, description: t.notifications.emptyHint }}
|
|
31
|
+
loadingMoreLabel={t.common.loadingMore} />
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// Static data
|
|
35
|
+
<List label={t.settings.devices} data={devices} keyExtractor={(d) => d.id} renderItem={(d) => <DeviceRow device={d} />} />
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`listProps` = `{ data, loading, refreshing, onRefresh, onEndReached, loadingMore }`: the next page is asked once at a
|
|
39
|
+
time and never past the last; pull to refresh refetches every loaded page.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: krizaka-virtual
|
|
3
|
+
description: Long lists, feeds and galleries in a Krizaka web app with @krizaka/ui/virtual (TanStack Virtual v3 behind the Krizaka facade) — VirtualList (own region or window scroll, measured items), VirtualGrid (responsive columns), infinite loading with @krizaka/query. Use it before rendering more than ~100 items, an infinite feed or a gallery.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @krizaka/ui/virtual
|
|
7
|
+
|
|
8
|
+
## Rules
|
|
9
|
+
|
|
10
|
+
1. Import from `@krizaka/ui/virtual` — **never `@tanstack/react-virtual`**, `react-window` or `react-virtualized`.
|
|
11
|
+
The raw hooks come through the facade: `useVirtualizer`, `useWindowVirtualizer`.
|
|
12
|
+
2. Records compared on columns → `@krizaka/ui/data-table` (`virtualized`), not a list. React Native → `@krizaka/ui/native/list`.
|
|
13
|
+
3. Always `getKey` (an id) and a `label` (translated): it names the focusable scrolling region.
|
|
14
|
+
|
|
15
|
+
## Recipes
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
// Messages of different heights, in their own region
|
|
19
|
+
<VirtualList items={messages} getKey={(m) => m.id} estimateSize={64} gap={8} height={480} label={t.chat.messages}
|
|
20
|
+
renderItem={(m) => <MessageBubble message={m} />} />
|
|
21
|
+
|
|
22
|
+
// A feed that scrolls the page, infinite with @krizaka/query
|
|
23
|
+
const feed = useInfiniteQuery({ ...feedQuery(), meta: { list: true } });
|
|
24
|
+
const posts = useMemo(() => feed.data?.pages.flatMap((p) => p.items) ?? NONE, [feed.data]);
|
|
25
|
+
<VirtualList scroll="window" items={posts} getKey={(p) => p.id} estimateSize={320} label={t.feed.title}
|
|
26
|
+
onEndReached={() => feed.hasNextPage && !feed.isFetchingNextPage && feed.fetchNextPage()}
|
|
27
|
+
footer={feed.isFetchingNextPage ? <Spinner label={t.common.loadingMore} /> : null} renderItem={(p) => <Post post={p} />} />
|
|
28
|
+
|
|
29
|
+
// A gallery: as many columns as fit (or `columns={4}`)
|
|
30
|
+
<VirtualGrid items={videos} getKey={(v) => v.id} estimateRowHeight={260} minItemWidth={220} label={t.explore.videos}
|
|
31
|
+
renderItem={(v) => <VideoCard video={v} />} />
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`ref` gives `scrollToIndex(i, align)` — smooth, or instant under reduced motion. Items are `listitem`s with
|
|
35
|
+
`aria-setsize` / `aria-posinset`.
|
|
File without changes
|
|
File without changes
|