@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.
Files changed (264) hide show
  1. package/README.md +4 -2
  2. package/dist/audio-player.js +1 -1
  3. package/dist/{chunk-EUPTXKH7.js → chunk-BRKMQ6JV.js} +2 -1
  4. package/dist/chunk-FDCMBWUU.js +168 -0
  5. package/dist/chunk-FDCMBWUU.js.map +1 -0
  6. package/dist/chunk-HCNOOPZE.js +210 -0
  7. package/dist/chunk-HCNOOPZE.js.map +1 -0
  8. package/dist/{chunk-X6TW3X6Y.js → chunk-LCZXS6AK.js} +2 -2
  9. package/dist/chunk-OGJI5X4W.js +157 -0
  10. package/dist/chunk-OGJI5X4W.js.map +1 -0
  11. package/dist/combobox.js +2 -2
  12. package/dist/data-table.d.ts +284 -24
  13. package/dist/data-table.js +914 -121
  14. package/dist/data-table.js.map +1 -1
  15. package/dist/date-picker.js +1 -1
  16. package/dist/filter-bar.d.ts +12 -12
  17. package/dist/filter-bar.js +9 -145
  18. package/dist/filter-bar.js.map +1 -1
  19. package/dist/form/server.d.ts +96 -0
  20. package/dist/form/server.js +82 -0
  21. package/dist/form/server.js.map +1 -0
  22. package/dist/form.d.ts +503 -0
  23. package/dist/form.js +760 -0
  24. package/dist/form.js.map +1 -0
  25. package/dist/image-cropper.js +1 -1
  26. package/dist/media-capture.js +1 -1
  27. package/dist/native/form.d.ts +376 -0
  28. package/dist/native/form.js +650 -0
  29. package/dist/native/form.js.map +1 -0
  30. package/dist/native/list.d.ts +95 -0
  31. package/dist/native/list.js +135 -0
  32. package/dist/native/list.js.map +1 -0
  33. package/dist/native.d.ts +113 -2
  34. package/dist/native.js +618 -388
  35. package/dist/native.js.map +1 -1
  36. package/dist/search-field.js +6 -160
  37. package/dist/search-field.js.map +1 -1
  38. package/dist/tag-input.js +2 -2
  39. package/dist/video-player.js +1 -1
  40. package/dist/video-trimmer.js +1 -1
  41. package/dist/virtual.d.ts +140 -0
  42. package/dist/virtual.js +19 -0
  43. package/dist/virtual.js.map +1 -0
  44. package/package.json +111 -8
  45. package/registry/achievement.json +1 -1
  46. package/registry/action-bar.json +1 -1
  47. package/registry/alert.json +1 -1
  48. package/registry/app-shell.json +1 -1
  49. package/registry/argument-card.json +1 -1
  50. package/registry/audio-player.json +1 -1
  51. package/registry/auth-layout.json +1 -1
  52. package/registry/avatar.json +1 -1
  53. package/registry/badge.json +1 -1
  54. package/registry/button.json +1 -1
  55. package/registry/card-grid.json +1 -1
  56. package/registry/card.json +1 -1
  57. package/registry/catalogue-page.json +1 -1
  58. package/registry/changelog-page.json +1 -1
  59. package/registry/checkbox.json +89 -4
  60. package/registry/chip-link.json +1 -1
  61. package/registry/chip.json +1 -1
  62. package/registry/choice-grid.json +1 -1
  63. package/registry/code-block.json +1 -1
  64. package/registry/combobox.json +1 -1
  65. package/registry/command.json +1 -1
  66. package/registry/component-page.json +1 -1
  67. package/registry/confirm-button.json +1 -1
  68. package/registry/contact-page.json +1 -1
  69. package/registry/continue-prompt.json +1 -1
  70. package/registry/countdown.json +1 -1
  71. package/registry/cta-panel.json +1 -1
  72. package/registry/dashboard-layout.json +1 -1
  73. package/registry/data-table.json +643 -49
  74. package/registry/date-picker.json +142 -4
  75. package/registry/decision-list.json +1 -1
  76. package/registry/detail-layout.json +1 -1
  77. package/registry/dialog.json +1 -1
  78. package/registry/docs-article-page.json +1 -1
  79. package/registry/docs-hub-page.json +1 -1
  80. package/registry/docs-layout.json +1 -1
  81. package/registry/dropdown-menu.json +1 -1
  82. package/registry/empty-state.json +1 -1
  83. package/registry/error-page.json +1 -1
  84. package/registry/examples/checkbox/native/terms.d.ts +4 -0
  85. package/registry/examples/checkbox/native/terms.dark.png +0 -0
  86. package/registry/examples/checkbox/native/terms.js +26 -0
  87. package/registry/examples/checkbox/native/terms.light.png +0 -0
  88. package/registry/examples/checkbox/native/terms.tsx +28 -0
  89. package/registry/examples/data-table/details.d.ts +4 -0
  90. package/registry/examples/data-table/details.js +33 -0
  91. package/registry/examples/data-table/details.tsx +31 -0
  92. package/registry/examples/data-table/filters.d.ts +4 -0
  93. package/registry/examples/data-table/filters.js +40 -0
  94. package/registry/examples/data-table/filters.tsx +40 -0
  95. package/registry/examples/data-table/grouped.d.ts +4 -0
  96. package/registry/examples/data-table/grouped.js +24 -0
  97. package/registry/examples/data-table/grouped.tsx +23 -0
  98. package/registry/examples/data-table/paginated.d.ts +4 -0
  99. package/registry/examples/data-table/paginated.js +23 -0
  100. package/registry/examples/data-table/paginated.tsx +22 -0
  101. package/registry/examples/data-table/pinned.d.ts +4 -0
  102. package/registry/examples/data-table/pinned.js +26 -0
  103. package/registry/examples/data-table/pinned.tsx +25 -0
  104. package/registry/examples/data-table/server.d.ts +4 -0
  105. package/registry/examples/data-table/server.js +40 -0
  106. package/registry/examples/data-table/server.tsx +40 -0
  107. package/registry/examples/data-table/virtualized.js +2 -2
  108. package/registry/examples/data-table/virtualized.tsx +3 -3
  109. package/registry/examples/date-picker/native/birth-date.d.ts +4 -0
  110. package/registry/examples/date-picker/native/birth-date.dark.png +0 -0
  111. package/registry/examples/date-picker/native/birth-date.js +30 -0
  112. package/registry/examples/date-picker/native/birth-date.light.png +0 -0
  113. package/registry/examples/date-picker/native/birth-date.tsx +27 -0
  114. package/registry/examples/field/native/default.d.ts +4 -0
  115. package/registry/examples/field/native/default.dark.png +0 -0
  116. package/registry/examples/field/native/default.js +29 -0
  117. package/registry/examples/field/native/default.light.png +0 -0
  118. package/registry/examples/field/native/default.tsx +23 -0
  119. package/registry/examples/form/amount.d.ts +4 -0
  120. package/registry/examples/form/amount.js +30 -0
  121. package/registry/examples/form/amount.tsx +36 -0
  122. package/registry/examples/form/audience.d.ts +4 -0
  123. package/registry/examples/form/audience.js +29 -0
  124. package/registry/examples/form/audience.tsx +31 -0
  125. package/registry/examples/form/native/amount.d.ts +4 -0
  126. package/registry/examples/form/native/amount.dark.png +0 -0
  127. package/registry/examples/form/native/amount.js +26 -0
  128. package/registry/examples/form/native/amount.light.png +0 -0
  129. package/registry/examples/form/native/amount.tsx +30 -0
  130. package/registry/examples/form/native/audience.d.ts +4 -0
  131. package/registry/examples/form/native/audience.dark.png +0 -0
  132. package/registry/examples/form/native/audience.js +24 -0
  133. package/registry/examples/form/native/audience.light.png +0 -0
  134. package/registry/examples/form/native/audience.tsx +24 -0
  135. package/registry/examples/form/native/profile.d.ts +4 -0
  136. package/registry/examples/form/native/profile.dark.png +0 -0
  137. package/registry/examples/form/native/profile.js +33 -0
  138. package/registry/examples/form/native/profile.light.png +0 -0
  139. package/registry/examples/form/native/profile.tsx +36 -0
  140. package/registry/examples/form/native/settings.d.ts +4 -0
  141. package/registry/examples/form/native/settings.dark.png +0 -0
  142. package/registry/examples/form/native/settings.js +25 -0
  143. package/registry/examples/form/native/settings.light.png +0 -0
  144. package/registry/examples/form/native/settings.tsx +23 -0
  145. package/registry/examples/form/native/sign-up.d.ts +4 -0
  146. package/registry/examples/form/native/sign-up.dark.png +0 -0
  147. package/registry/examples/form/native/sign-up.js +38 -0
  148. package/registry/examples/form/native/sign-up.light.png +0 -0
  149. package/registry/examples/form/native/sign-up.tsx +34 -0
  150. package/registry/examples/form/native/upload.d.ts +4 -0
  151. package/registry/examples/form/native/upload.dark.png +0 -0
  152. package/registry/examples/form/native/upload.js +23 -0
  153. package/registry/examples/form/native/upload.light.png +0 -0
  154. package/registry/examples/form/native/upload.tsx +22 -0
  155. package/registry/examples/form/profile.d.ts +4 -0
  156. package/registry/examples/form/profile.js +35 -0
  157. package/registry/examples/form/profile.tsx +40 -0
  158. package/registry/examples/form/report.d.ts +4 -0
  159. package/registry/examples/form/report.js +30 -0
  160. package/registry/examples/form/report.tsx +30 -0
  161. package/registry/examples/form/settings.d.ts +4 -0
  162. package/registry/examples/form/settings.js +30 -0
  163. package/registry/examples/form/settings.tsx +31 -0
  164. package/registry/examples/form/sign-up.d.ts +4 -0
  165. package/registry/examples/form/sign-up.js +38 -0
  166. package/registry/examples/form/sign-up.tsx +37 -0
  167. package/registry/examples/form/upload.d.ts +4 -0
  168. package/registry/examples/form/upload.js +32 -0
  169. package/registry/examples/form/upload.tsx +32 -0
  170. package/registry/examples/list/native/default.d.ts +4 -0
  171. package/registry/examples/list/native/default.dark.png +0 -0
  172. package/registry/examples/list/native/default.js +33 -0
  173. package/registry/examples/list/native/default.light.png +0 -0
  174. package/registry/examples/list/native/default.tsx +31 -0
  175. package/registry/examples/list/native/infinite.d.ts +4 -0
  176. package/registry/examples/list/native/infinite.dark.png +0 -0
  177. package/registry/examples/list/native/infinite.js +30 -0
  178. package/registry/examples/list/native/infinite.light.png +0 -0
  179. package/registry/examples/list/native/infinite.tsx +35 -0
  180. package/registry/examples/list/native/states.d.ts +4 -0
  181. package/registry/examples/list/native/states.dark.png +0 -0
  182. package/registry/examples/list/native/states.js +26 -0
  183. package/registry/examples/list/native/states.light.png +0 -0
  184. package/registry/examples/list/native/states.tsx +21 -0
  185. package/registry/examples/virtual/feed.d.ts +4 -0
  186. package/registry/examples/virtual/feed.js +26 -0
  187. package/registry/examples/virtual/feed.tsx +24 -0
  188. package/registry/examples/virtual/gallery.d.ts +4 -0
  189. package/registry/examples/virtual/gallery.js +31 -0
  190. package/registry/examples/virtual/gallery.tsx +28 -0
  191. package/registry/examples/virtual/messages.d.ts +4 -0
  192. package/registry/examples/virtual/messages.js +28 -0
  193. package/registry/examples/virtual/messages.tsx +28 -0
  194. package/registry/field.json +104 -4
  195. package/registry/file-upload.json +1 -1
  196. package/registry/filter-bar.json +1 -1
  197. package/registry/form.json +2940 -0
  198. package/registry/gallery-page.json +1 -1
  199. package/registry/glow-card.json +1 -1
  200. package/registry/goal-gauge.json +1 -1
  201. package/registry/how-it-works-page.json +1 -1
  202. package/registry/image-cropper.json +1 -1
  203. package/registry/index.json +86 -11
  204. package/registry/iso.json +1 -1
  205. package/registry/kbd.json +1 -1
  206. package/registry/landing-page.json +1 -1
  207. package/registry/landscape.json +1 -1
  208. package/registry/legal-page.json +1 -1
  209. package/registry/link-card.json +1 -1
  210. package/registry/list.json +249 -0
  211. package/registry/loader.json +1 -1
  212. package/registry/longform-page.json +1 -1
  213. package/registry/marketing-layout.json +1 -1
  214. package/registry/marquee.json +1 -1
  215. package/registry/mascot-quote.json +1 -1
  216. package/registry/mascot-state.json +2 -2
  217. package/registry/mascots.json +2 -2
  218. package/registry/media-capture.json +1 -1
  219. package/registry/meter.json +1 -1
  220. package/registry/nav-bar.json +1 -1
  221. package/registry/number-ticker.json +1 -1
  222. package/registry/page-header.json +1 -1
  223. package/registry/page-hero.json +1 -1
  224. package/registry/page.json +1 -1
  225. package/registry/pagination.json +1 -1
  226. package/registry/pointer-light.json +1 -1
  227. package/registry/popover.json +1 -1
  228. package/registry/pricing-page.json +1 -1
  229. package/registry/product-page.json +1 -1
  230. package/registry/profile-card.json +1 -1
  231. package/registry/progress.json +1 -1
  232. package/registry/promise-card.json +1 -1
  233. package/registry/radio-group.json +1 -1
  234. package/registry/scroll-stage.json +1 -1
  235. package/registry/search-field.json +1 -1
  236. package/registry/section-backdrop.json +1 -1
  237. package/registry/separator.json +1 -1
  238. package/registry/settings-layout.json +1 -1
  239. package/registry/skeleton.json +1 -1
  240. package/registry/slider.json +1 -1
  241. package/registry/slot.json +1 -1
  242. package/registry/spinner.json +1 -1
  243. package/registry/split-view.json +1 -1
  244. package/registry/stage-banner.json +1 -1
  245. package/registry/stat.json +1 -1
  246. package/registry/status-page.json +1 -1
  247. package/registry/stepper.json +1 -1
  248. package/registry/switch.json +1 -1
  249. package/registry/tabs.json +1 -1
  250. package/registry/tag-input.json +1 -1
  251. package/registry/terminal-replay.json +1 -1
  252. package/registry/theme.json +1 -1
  253. package/registry/toast.json +1 -1
  254. package/registry/toc.json +1 -1
  255. package/registry/tooltip.json +1 -1
  256. package/registry/video-player.json +1 -1
  257. package/registry/video-trimmer.json +1 -1
  258. package/registry/virtual.json +569 -0
  259. package/skills/data-table/SKILL.md +56 -0
  260. package/skills/form/SKILL.md +44 -0
  261. package/skills/native-list/SKILL.md +39 -0
  262. package/skills/virtual/SKILL.md +35 -0
  263. /package/dist/{chunk-EUPTXKH7.js.map → chunk-BRKMQ6JV.js.map} +0 -0
  264. /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`.