@imfusion/web-ui 0.11.1-dev.6.gf4b6c627 → 0.11.1-dev.62.gfcd31b6e

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 (93) hide show
  1. package/CHANGELOG.md +168 -1
  2. package/dist/{code-Cl9AzRyF.js → code-BaLXMEMa.js} +8 -8
  3. package/dist/components/autocomplete/autocomplete.d.ts +144 -0
  4. package/dist/components/autocomplete/autocomplete.meta.d.ts +2 -0
  5. package/dist/components/autocomplete/index.d.ts +2 -0
  6. package/dist/components/avatar/avatar.d.ts +1 -1
  7. package/dist/components/card/card.d.ts +3 -2
  8. package/dist/components/checkbox/checkbox.cva.d.ts +3 -0
  9. package/dist/components/combobox/combobox.cva.d.ts +37 -0
  10. package/dist/components/combobox/combobox.d.ts +171 -0
  11. package/dist/components/combobox/combobox.meta.d.ts +2 -0
  12. package/dist/components/combobox/index.d.ts +2 -0
  13. package/dist/components/dialog/dialog.d.ts +23 -3
  14. package/dist/components/file-drop-zone/collect-files.d.ts +56 -0
  15. package/dist/components/file-drop-zone/file-drop-zone-context.d.ts +15 -0
  16. package/dist/components/file-drop-zone/file-drop-zone.d.ts +185 -0
  17. package/dist/components/file-drop-zone/file-drop-zone.meta.d.ts +2 -0
  18. package/dist/components/file-drop-zone/format-bytes.d.ts +5 -0
  19. package/dist/components/file-drop-zone/index.d.ts +4 -0
  20. package/dist/components/image/image.d.ts +50 -0
  21. package/dist/components/image/image.meta.d.ts +2 -0
  22. package/dist/components/image/index.d.ts +2 -0
  23. package/dist/components/logo/logo.d.ts +1 -1
  24. package/dist/components/popover/popover.d.ts +2 -2
  25. package/dist/components/progress/index.d.ts +2 -0
  26. package/dist/components/progress/progress.d.ts +40 -0
  27. package/dist/components/progress/progress.meta.d.ts +2 -0
  28. package/dist/components/rich-link/rich-link.d.ts +1 -1
  29. package/dist/components/select/select.cva.d.ts +7 -0
  30. package/dist/components/select/select.d.ts +1 -1
  31. package/dist/components/tab-nav/index.d.ts +2 -0
  32. package/dist/components/tab-nav/tab-nav.d.ts +26 -0
  33. package/dist/components/tab-nav/tab-nav.meta.d.ts +2 -0
  34. package/dist/components/table/table.d.ts +8 -0
  35. package/dist/components/tabs/tabs-underline.cva.d.ts +6 -0
  36. package/dist/components/tabs/tabs.d.ts +13 -4
  37. package/dist/components/textarea/index.d.ts +2 -0
  38. package/dist/components/textarea/textarea.d.ts +32 -0
  39. package/dist/components/textarea/textarea.meta.d.ts +2 -0
  40. package/dist/components/toast/toast.d.ts +2 -2
  41. package/dist/components/typo/typo.d.ts +5 -1
  42. package/dist/docgen/component-sources.d.ts +4 -0
  43. package/dist/icon-button-roSB497o.js +171 -0
  44. package/dist/index.d.ts +9 -0
  45. package/dist/index.js +4536 -2958
  46. package/dist/integrations/code-highlight.js +2 -2
  47. package/dist/integrations/frame-selection.js +1 -1
  48. package/dist/integrations/image-display-options.js +73 -71
  49. package/dist/integrations/layout-mode/index.d.ts +2 -0
  50. package/dist/integrations/layout-mode/layout-mode-view.d.ts +32 -0
  51. package/dist/integrations/layout-mode/layout-mode.d.ts +13 -0
  52. package/dist/integrations/layout-mode/layout-mode.meta.d.ts +2 -0
  53. package/dist/integrations/layout-mode.js +103 -0
  54. package/dist/integrations/view-tools/index.d.ts +4 -0
  55. package/dist/integrations/view-tools/use-view-interaction.d.ts +21 -0
  56. package/dist/integrations/view-tools/view-tools-view.d.ts +23 -0
  57. package/dist/integrations/view-tools/view-tools.d.ts +14 -0
  58. package/dist/integrations/view-tools/view-tools.meta.d.ts +2 -0
  59. package/dist/integrations/view-tools.js +133 -0
  60. package/dist/{resolve-state-props-DQf9WiGv.js → rolldown-runtime-Dy4uBu1J.js} +1 -12
  61. package/dist/row-CImu8yXV.js +136 -0
  62. package/dist/{scroll-area-z9Lf9uIk.js → scroll-area-CXQ_SxPX.js} +2 -1
  63. package/dist/{slider-Bf274cMe.js → slider-CpIZMAFl.js} +2 -1
  64. package/dist/style.css +2 -1
  65. package/dist/tabs-BqgyMmo6.js +173 -0
  66. package/dist/toggle-group-Bhb3qicp.js +31 -0
  67. package/dist/toggle-group-eGr6_hgQ.js +125 -0
  68. package/dist/utils/email.d.ts +4 -1
  69. package/dist/utils/index.d.ts +2 -0
  70. package/dist/utils/initials.d.ts +4 -0
  71. package/dist/utils/item-parts.d.ts +23 -0
  72. package/dist/utils/stable-popup-height.d.ts +10 -0
  73. package/dist/utils-J6vbjIt1.js +46 -0
  74. package/package.json +4 -4
  75. package/src/docgen/doc.gen.json +5526 -1748
  76. package/src/llms/llms.gen.txt +52 -1
  77. package/src/llms/skills/imf-web-ui-audit/SKILL.md +5 -2
  78. package/src/llms/skills/imf-web-ui-conventions/SKILL.md +2 -2
  79. package/src/llms/skills/imf-web-ui-conventions/templates/AUDIT_CHECKLIST.md +19 -2
  80. package/src/llms/skills/imf-web-ui-conventions/topics/assets.md +28 -4
  81. package/src/llms/skills/imf-web-ui-conventions/topics/components.md +10 -0
  82. package/src/llms/skills/imf-web-ui-conventions/topics/library-boundary.md +3 -0
  83. package/src/llms/skills/imf-web-ui-conventions/topics/npm-project.md +2 -1
  84. package/src/llms/skills/imf-web-ui-conventions/topics/react/composition.md +54 -0
  85. package/src/llms/skills/imf-web-ui-conventions/topics/react/effects.md +59 -0
  86. package/src/llms/skills/imf-web-ui-conventions/topics/react/state.md +41 -0
  87. package/src/llms/skills/imf-web-ui-conventions/topics/react.md +7 -60
  88. package/src/llms/skills/imf-web-ui-conventions/topics/styling.md +22 -1
  89. package/src/llms/skills/imf-web-ui-conventions/topics/tooling.md +3 -2
  90. package/src/llms/skills/imf-web-ui-conventions/topics/validation.md +7 -0
  91. package/src/llms/skills/imf-web-ui-ux/references/forms.md +3 -2
  92. package/src/llms/tokens.gen.json +19 -1
  93. package/dist/tabs-DsDSCA--.js +0 -376
@@ -19,6 +19,12 @@ The signed-in person's account row: the avatar in a lead column, the name or ema
19
19
  Application layout shell for a header, sidebars, footer, and scrolling main area. Also called an app layout or dashboard shell; sizing is CSS-driven and sidebars can become sliding panels on small screens.
20
20
  - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .app-shell (jq: jq '.app-shell' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
21
21
 
22
+ ## Autocomplete
23
+ - category: Inputs, status: experimental
24
+ Text input that suggests options as you type, keeping free text. Also called a typeahead or search suggestions. Use Combobox when the value must come from the list.
25
+ - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .autocomplete (jq: jq '.autocomplete' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
26
+ - further reading (usage/anatomy, not props): https://base-ui.com/react/components/autocomplete.md
27
+
22
28
  ## Avatar
23
29
  - category: Display, status: experimental
24
30
  Round badge that stands for a person, showing their photo, their initials, or a person glyph. Also called a profile picture or user badge; for a signed-in account row with a name, use AccountCard.
@@ -78,6 +84,12 @@ Show-and-hide region for secondary content such as FAQs, settings, or details. A
78
84
  Light, System, and Dark segments that set and remember the page's color scheme. Also called an appearance or theme switch; built on ToggleGroup, which is the choice for any other segmented selection.
79
85
  - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .color-scheme-toggle (jq: jq '.color-scheme-toggle' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
80
86
 
87
+ ## Combobox
88
+ - category: Inputs, status: experimental
89
+ Text input with a filterable list of options, for choosing one or several from a long list. Also called a searchable select, typeahead, or multi-select. Use Select for short lists and Autocomplete when free text is allowed.
90
+ - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .combobox (jq: jq '.combobox' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
91
+ - further reading (usage/anatomy, not props): https://base-ui.com/react/components/combobox.md
92
+
81
93
  ## CopyButton
82
94
  - category: Buttons, status: stable
83
95
  Copies a value to the clipboard and briefly shows confirmation. Also called a copy-to-clipboard button; commonly used with Code.Block.
@@ -107,6 +119,11 @@ Semantic group for related form controls with a shared legend. Also called a for
107
119
  - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .fieldset (jq: jq '.fieldset' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
108
120
  - further reading (usage/anatomy, not props): https://base-ui.com/react/components/fieldset.md
109
121
 
122
+ ## FileDropZone
123
+ - category: Inputs, status: experimental
124
+ Drop zone with picker buttons for files or whole folders, reporting accepted and rejected files. Also called a file upload area or dropzone. For a single native file control inside a form, use Input.
125
+ - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .file-drop-zone (jq: jq '.file-drop-zone' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
126
+
110
127
  ## FrameSelection
111
128
  - category: Inputs, status: experimental
112
129
  Frame or slice picker bound to the focused frame of ImFusion image sets, with a focus and count readout. For a position picker not tied to the SDK, use Slider with variant="position".
@@ -123,6 +140,11 @@ Renders a Web UI icon with an optional semantic foreground color. The default in
123
140
  Square button that shows a single icon and no visible text, with its accessible name from `label`. Also called an icon-only or toolbar button. Use Button with `startIcon` or `endIcon` when a text label is visible.
124
141
  - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .icon-button (jq: jq '.icon-button' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
125
142
 
143
+ ## Image
144
+ - category: Display, status: experimental
145
+ Picture with AVIF and WebP sources, reserved space, lazy loading, and a fallback while it loads or when it fails. Also called a photo, thumbnail or media. Use Avatar for a person and Card.Image for a card's edge-to-edge slot.
146
+ - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .image (jq: jq '.image' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
147
+
126
148
  ## ImageDisplayOptions
127
149
  - category: Inputs, status: experimental
128
150
  Panel and toolbar controls for an image dataset's display options, including window, level, gamma, and invert settings.
@@ -140,6 +162,12 @@ Single-line text input for form data entry. Also called a text field; it support
140
162
  Shows a keyboard key or shortcut, such as the letter that activates a button. Also called a key hint; Button and Toggle render it through their `shortcut` prop.
141
163
  - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .kbd (jq: jq '.kbd' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
142
164
 
165
+ ## LayoutMode
166
+ - category: Inputs, status: experimental
167
+ Picker bound to the ImFusion viewer's layout mode: Auto, Rows, Focus + stack, or Focus + rows, as a compact icon row or a labeled vertical list. Also called a layout or view arrangement switch; for a segmented choice not tied to the SDK, use ToggleGroup.
168
+ - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .layout-mode (jq: jq '.layout-mode' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
169
+ - further reading (usage/anatomy, not props): https://docs.imfusion.com/
170
+
143
171
  ## Logo
144
172
  - category: Display, status: stable
145
173
  Renders a logo from a URL or inline React element with consistent sizing. Also called a wordmark or brand mark.
@@ -169,6 +197,12 @@ Floating panel anchored to a trigger for contextual content, quick forms, or act
169
197
  - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .popover (jq: jq '.popover' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
170
198
  - further reading (usage/anatomy, not props): https://base-ui.com/react/components/popover.md
171
199
 
200
+ ## Progress
201
+ - category: Display, status: experimental
202
+ Bar for the completion of a running task, such as an upload or export. Also called a progress bar. Indeterminate when the amount is unknown. Use Meter for a static measure within a range.
203
+ - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .progress (jq: jq '.progress' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
204
+ - further reading (usage/anatomy, not props): https://base-ui.com/react/components/progress.md
205
+
172
206
  ## Prose
173
207
  - category: Layout, status: experimental
174
208
  Flow container that spaces long-form text and the blocks inside it. Also called an article or rich-text container. Use Stack or Row for UI layout.
@@ -229,6 +263,11 @@ Two-state control for an on/off setting that takes effect immediately. Also call
229
263
  - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .switch (jq: jq '.switch' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
230
264
  - further reading (usage/anatomy, not props): https://base-ui.com/react/components/switch.md
231
265
 
266
+ ## TabNav
267
+ - category: Display, status: experimental
268
+ Row of links to routes with a bar under the current one, such as the sections of a project. Also called route tabs or link tabs; use Tabs to switch panels on one page.
269
+ - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .tab-nav (jq: jq '.tab-nav' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
270
+
232
271
  ## Table
233
272
  - category: Display, status: stable
234
273
  Presentational table parts for static tabular data. Pair them with a headless table library for sorting, pagination, or selection.
@@ -236,10 +275,16 @@ Presentational table parts for static tabular data. Pair them with a headless ta
236
275
 
237
276
  ## Tabs
238
277
  - category: Display, status: stable
239
- Switches between related panels within one view. Also called a tab strip or tab bar; it supports horizontal and vertical layouts.
278
+ Switches between related panels within one view. Also called a tab strip or tab bar; it supports horizontal and vertical layouts and a boxed or underline look. Use TabNav for links to routes.
240
279
  - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .tabs (jq: jq '.tabs' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
241
280
  - further reading (usage/anatomy, not props): https://base-ui.com/react/components/tabs.md
242
281
 
282
+ ## Textarea
283
+ - category: Inputs, status: experimental
284
+ Multi-line text input for longer free text, such as a description or note. Also called a text area; use Input for a single line. It reads validation state from Field.
285
+ - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .textarea (jq: jq '.textarea' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
286
+ - further reading (usage/anatomy, not props): https://base-ui.com/react/components/input.md
287
+
243
288
  ## Toast
244
289
  - category: Display, status: experimental
245
290
  Transient message stack that reports the outcome of a background action without interrupting the page. Use for save confirmations, undo offers, and async progress; attach one to an element for contextual feedback like a copy confirmation. Also called a snackbar, notification, or flash message; for a message that stays inline in the page use Callout instead.
@@ -268,3 +313,9 @@ Hover- or focus-triggered label for brief help about a control or term. Also cal
268
313
  - category: Display, status: stable
269
314
  Semantic text primitives for display and standard headings, paragraphs, lists, quotes, links, and inline code. The namespace uses native HTML elements and token-based text roles.
270
315
  - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .typo (jq: jq '.typo' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
316
+
317
+ ## ViewTools
318
+ - category: Inputs, status: experimental
319
+ Toolbar that limits mouse interaction in the ImFusion main 2D and MPR views to pan, zoom, or windowing, with a reset button for the view group. For a segmented control not tied to the SDK, use ToggleGroup.
320
+ - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .view-tools (jq: jq '.view-tools' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
321
+ - further reading (usage/anatomy, not props): https://docs.imfusion.com/
@@ -66,8 +66,11 @@ next action is the add from that topic. A Vite config that reads tsconfig `paths
66
66
  `vite-tsconfig-paths`) is Broken: it loads the React types package as React once Web UI comes from the registry. The fix is
67
67
  to drop it and give the `#/` alias through `imports` in `package.json`. A major or minor mismatch between the project and Web
68
68
  UI on a package listed under Aligned runtime packages in the `tooling` topic is a Deviation. Report both versions, with
69
- `npm install <package>@<Web UI's version>` as the fix. A route file that holds several views inline is a Deviation, and so is
70
- a route whose only view sits alone in its `-` folder. Name the views, with the move as the fix.
69
+ `npm install <package>@<Web UI's version>` as the fix. An `engines.node` that admits a Node outside the `engines.node` of the
70
+ installed `node_modules/@imfusion/web-ui/package.json` is a Deviation; report both ranges, with Web UI's as the fix. A route
71
+ file that holds several views inline is a Deviation, and so is a route whose only view sits alone in its `-` folder. Name the
72
+ views, with the move as the fix. A form whose state and validation are written by hand instead of TanStack Form is a
73
+ Deviation, however many forms share the pattern. Name each form, with TanStack Form and its existing schema as the fix.
71
74
 
72
75
  ## Topics
73
76
 
@@ -25,11 +25,11 @@ a gap or to make a deliberate deviation visible; do not refactor a project just
25
25
  | [styling](topics/styling.md) | Writing CSS or customizing Web UI. |
26
26
  | [tokens](topics/tokens.md) | Choosing an exact token name. |
27
27
  | [class-names](topics/class-names.md) | Mapping variants and `className` to CSS. |
28
- | [validation](topics/validation.md) | Accepting data from outside the frontend. |
28
+ | [validation](topics/validation.md) | Accepting data from outside the frontend, or building a form. |
29
29
  | [data](topics/data.md) | Adding API queries, mutations, or invalidation. |
30
30
  | [project-structure](topics/project-structure.md) | Adding files or routes. |
31
31
  | [testing](topics/testing.md) | Deciding what to test. |
32
- | [tooling](topics/tooling.md) | Adding a dependency or configuring a tool. |
32
+ | [tooling](topics/tooling.md) | Adding a dependency, configuring a tool, or building a form. |
33
33
  | [npm-project](topics/npm-project.md) | Changing `package.json`, scripts, pins, or Node. |
34
34
  | [git](topics/git.md) | Configuring hooks or choosing verification scope. |
35
35
  | [agent-tooling](topics/agent-tooling.md) | Installing skills or lifecycle hooks. |
@@ -2,7 +2,8 @@
2
2
 
3
3
  One block per topic, one box per topic section. Check a box only after checking that section's rules against the repo. A full
4
4
  audit checks every box; a scoped audit names its topics first and checks only their blocks. Blocks mirror each topic's `##`
5
- sections one to one — change a topic's sections and this file follows in the same commit.
5
+ sections one to one, and a topic split into a folder gets one `###` block per subtopic — change a topic's sections and this
6
+ file follows in the same commit.
6
7
 
7
8
  ## Library setup — `library-setup`
8
9
 
@@ -35,10 +36,23 @@ sections one to one — change a topic's sections and this file follows in the s
35
36
 
36
37
  ## React — `react`
37
38
 
39
+ ### Composition — `react/composition`
40
+
38
41
  - [ ] Composition: pages (smart containers), partials, dumb components
39
42
  - [ ] Compose, don't configure
43
+ - [ ] Render rules
44
+
45
+ ### State — `react/state`
46
+
40
47
  - [ ] Put state where its truth lives
48
+ - [ ] State shape
49
+ - [ ] Fallbacks without TanStack
50
+
51
+ ### Effects — `react/effects`
52
+
41
53
  - [ ] Effects: last resort, and named
54
+ - [ ] Dependencies
55
+ - [ ] Effect Events for values the effect should not react to
42
56
 
43
57
  ## Components — `components`
44
58
 
@@ -46,6 +60,7 @@ sections one to one — change a topic's sections and this file follows in the s
46
60
  - [ ] Grouping
47
61
  - [ ] One file or a folder
48
62
  - [ ] Colocation
63
+ - [ ] Semantic elements
49
64
 
50
65
  ## TypeScript & code style — `typescript`
51
66
 
@@ -80,6 +95,7 @@ sections one to one — change a topic's sections and this file follows in the s
80
95
  - [ ] Schema first, type derived
81
96
  - [ ] TanStack Router search params
82
97
  - [ ] Failure handling
98
+ - [ ] Forms
83
99
 
84
100
  ## Data — `data`
85
101
 
@@ -127,7 +143,8 @@ sections one to one — change a topic's sections and this file follows in the s
127
143
  ## Assets — `assets`
128
144
 
129
145
  - [ ] Importing
130
- - [ ] Photographs: WebP
146
+ - [ ] Photographs: AVIF and WebP
147
+ - [ ] Loading
131
148
  - [ ] Other formats
132
149
  - [ ] Scope
133
150
 
@@ -8,15 +8,39 @@ consumed by document metadata or manifests, with a build step that serves the fi
8
8
  For the library's favicon set and social sharing image, read `BrandAssets.mdx` through the packaged user-guide route in
9
9
  `imf-web-ui`.
10
10
 
11
- ## Photographs: WebP
11
+ ## Photographs: AVIF and WebP
12
12
 
13
- Convert photographs to WebP before adding them:
13
+ Encode photographs as AVIF and WebP at 800 and 1600px wide. A source narrower than a width skips it, and `srcSet` lists only
14
+ the widths that exist:
14
15
 
15
16
  ```sh
16
- magick source.png -resize 2000x -quality 80 -define webp:method=6 src/assets/name.webp
17
+ for w in 800 1600; do
18
+ magick source.png -resize "${w}x>" -quality 55 src/assets/name-$w.avif
19
+ magick source.png -resize "${w}x>" -quality 80 -define webp:method=6 src/assets/name-$w.webp
20
+ done
17
21
  ```
18
22
 
19
- Keep the long edge at 2000px or less. WebP is supported by the browser floor.
23
+ Render them through `<picture>`: an AVIF `<source>`, then a WebP `<source>`, each with `srcSet` and `sizes`, then an `<img>`
24
+ fallback. The browser takes the first format it supports at the width that fits. Both formats are supported by the browser
25
+ floor.
26
+
27
+ ```tsx
28
+ <picture>
29
+ <source type="image/avif" srcSet={`${name800Avif} 800w, ${name1600Avif} 1600w`} sizes="(max-width: 40rem) 100vw, 40rem" />
30
+ <source type="image/webp" srcSet={`${name800Webp} 800w, ${name1600Webp} 1600w`} sizes="(max-width: 40rem) 100vw, 40rem" />
31
+ <img src={name800Webp} width={800} height={400} alt="…" />
32
+ </picture>
33
+ ```
34
+
35
+ `Image` from `@imfusion/web-ui` renders this `<picture>` from `avifSrcSet`, `webpSrcSet`, `srcSet`, `sizes`, `width`,
36
+ `height` and `alt`. It adds lazy loading and async decoding, a pulse while the image loads and a glyph when it fails. Use it
37
+ for content images instead of writing the `<picture>` by hand.
38
+
39
+ ## Loading
40
+
41
+ Every `<img>` carries `width` and `height` in the source's aspect ratio, so the browser reserves its space before it loads.
42
+ An image below the fold gets `loading="lazy"` and `decoding="async"`. The one image that is the page's largest paint gets
43
+ `fetchpriority="high"` and loads eagerly.
20
44
 
21
45
  ## Other formats
22
46
 
@@ -53,3 +53,13 @@ Keep a component's stylesheet, tests, and local types beside the component. A co
53
53
  be a layout or dumb component instead.
54
54
 
55
55
  A route-local component keeps its stylesheet and tests in the route's `-` folder beside it.
56
+
57
+ ## Semantic elements
58
+
59
+ Inside `AppShell`, the shell's parts render the landmarks: `AppShell.Header` a `<header>`, `AppShell.Navbar` a `<nav>`,
60
+ `AppShell.Aside` an `<aside>`, `AppShell.Main` the page's one `<main>`, and `AppShell.Footer` a `<footer>`. A page puts its
61
+ content inside those parts.
62
+
63
+ Custom markup uses the element for its meaning: `<section>` and `<article>` for content groups, `<time dateTime>` for a date,
64
+ `Button` for an action, and the router's `Link` for a destination. A page has one `Typo.H1`, and heading levels descend
65
+ without skipping.
@@ -7,6 +7,9 @@ integration seams.
7
7
 
8
8
  - Do not import Base UI or another implementation package directly, including its stylesheet.
9
9
  - If Web UI is missing a component or part, report the gap instead of reaching around it.
10
+ - Use what Web UI already ships before building your own: a component, a part, or a helper such as `initialsFromName`. A
11
+ native element or local function that duplicates one, such as a `div role="separator"` or a hand-written initials function,
12
+ is a finding.
10
13
  - Import glyphs from `@imfusion/web-ui/icons` and render them with `Icon`.
11
14
 
12
15
  ## Stand in for a missing component
@@ -5,7 +5,8 @@ Keep project metadata, scripts, and dependency policy in `package.json` and the
5
5
  ## package.json
6
6
 
7
7
  - Use ESM with `"type": "module"`.
8
- - Set the Node floor with `engines.node` or `.nvmrc`.
8
+ - Set `engines.node` to the range in Web UI's own `package.json`, so the app promises no Node that Web UI or the `#/` alias
9
+ rejects. A `.nvmrc` selects a version inside that range.
9
10
  - Use the `#/` source alias when the project has one, and wire it through the package `imports` field.
10
11
  - Set `private: true` only for packages that are never published.
11
12
 
@@ -0,0 +1,54 @@
1
+ # React composition
2
+
3
+ Which role a component plays, and how parts combine.
4
+
5
+ ## Composition: pages (smart containers), partials, dumb components
6
+
7
+ - **Pages** live in `routes/`. They load data, read URL state, coordinate actions, and compose the view. Route files hold the
8
+ search schema, loaders, and pending, error, and not-found components. They do not own component CSS. Route-local views and
9
+ forms live in the route's `-` folder, as [project-structure.md](../project-structure.md) describes, and that folder's
10
+ `.module.css` files are component styles.
11
+ - **Partials** are compositions that more than one route uses, extracted because they read as a unit. They have light logic
12
+ and no styling of their own.
13
+ - **Layout components** arrange content with `Stack`, `Row`, grids, and token gaps, and wrap long-form text in `Prose`.
14
+ - **Dumb components** receive data and callbacks, render UI, and own their styles. They do not fetch, route, or contain
15
+ business rules.
16
+
17
+ ```tsx
18
+ function UserCard({ name, onEdit }: { name: string; onEdit: () => void }) {
19
+ return (
20
+ <Card.Root>
21
+ <Typo.P>{name}</Typo.P>
22
+ <Button onClick={onEdit}>Edit</Button>
23
+ </Card.Root>
24
+ );
25
+ }
26
+ ```
27
+
28
+ ## Compose, don't configure
29
+
30
+ Compose small parts instead of making one component configurable through many boolean props. Lift shared state to the nearest
31
+ common parent.
32
+
33
+ ## Render rules
34
+
35
+ These come from react.dev and hold for every component. Each names the React version it needs.
36
+
37
+ - **Render is pure (any version).** Never mutate props, state, context, or a value created before render. Mutating an object
38
+ the same render just created is fine.
39
+ - **No component defined inside another (any version).** A nested definition gets a new identity each render, so React
40
+ remounts it and its state resets. Define it at module level and pass data as props.
41
+ - **No `ref.current` during render (any version).** Read and write refs in effects and handlers, except for a lazy init. A
42
+ value that shapes the output belongs in state.
43
+ - **`ref` is a prop (React 19+).** Accept `ref` like any other prop in your own components; don't wrap them in `forwardRef`.
44
+ - **`useId` for accessibility ids (React 18+).** Use it for `htmlFor`, `aria-describedby`, and similar. Never use it for list
45
+ keys, and never hardcode its output in tests or selectors.
46
+ - **Error boundaries around UI units (any version).** Wrap a page section or widget, not every component. A boundary does not
47
+ catch errors from event handlers or async code; TanStack Query and Router surface those.
48
+ - **With the React Compiler on, no manual memoization by default.** `useMemo`, `useCallback`, and `React.memo` are only for a
49
+ measured case.
50
+
51
+ See [Keeping Components Pure](https://react.dev/learn/keeping-components-pure),
52
+ [Preserving and Resetting State](https://react.dev/learn/preserving-and-resetting-state),
53
+ [Referencing Values with Refs](https://react.dev/learn/referencing-values-with-refs), and
54
+ [`useId`](https://react.dev/reference/react/useId).
@@ -0,0 +1,59 @@
1
+ # React effects
2
+
3
+ When an effect is the right tool, and how to write the ones that stay.
4
+
5
+ ## Effects: last resort, and named
6
+
7
+ Use `useEffect` only to synchronize with something outside React. Do not use it for derived values, event responses, or
8
+ server sync.
9
+
10
+ A genuine effect belongs in a named custom hook. If it stays inline, name the callback:
11
+
12
+ ```tsx
13
+ useEffect(
14
+ function syncDocumentTitle() {
15
+ document.title = title;
16
+ },
17
+ [title]
18
+ );
19
+ ```
20
+
21
+ Destructure a primitive before putting it in an effect dependency array. Keep the returned query or route object intact
22
+ elsewhere so its namespace remains visible.
23
+
24
+ ## Dependencies
25
+
26
+ - **No objects or functions as dependencies (any version).** They are new each render, so the effect re-runs every time.
27
+ Create them inside the effect, move them outside the component, or depend on the primitive they read.
28
+ - **Hidden `<Activity>` cleans up effects (React 19.2+).** A hidden subtree unmounts its effects and re-runs them when shown.
29
+ Don't rely on an effect running while its UI is hidden.
30
+
31
+ See [Removing Effect Dependencies](https://react.dev/learn/removing-effect-dependencies).
32
+
33
+ ## Effect Events for values the effect should not react to
34
+
35
+ When an effect reads a value it should not react to, such as a callback prop or a setting used only once something happens,
36
+ move that logic into `useEffectEvent` (React 19.2+) instead of adding the value to the dependencies, mirroring it in a ref,
37
+ or suppressing the lint rule. Call the Effect Event only from inside the effect, and never use it to drop a value that should
38
+ re-run the effect. See [`useEffectEvent`](https://react.dev/reference/react/useEffectEvent).
39
+
40
+ ```tsx
41
+ const onConnected = useEffectEvent(function notifyConnected() {
42
+ if (!muted) toast("Connected");
43
+ });
44
+
45
+ useEffect(
46
+ function connectToRoom() {
47
+ const connection = createConnection(roomId);
48
+ connection.on("connected", onConnected);
49
+ connection.connect();
50
+
51
+ return function disconnectFromRoom() {
52
+ connection.disconnect();
53
+ };
54
+ },
55
+ [roomId]
56
+ );
57
+ ```
58
+
59
+ Read the React documentation when an effect decision is unclear.
@@ -0,0 +1,41 @@
1
+ # React state
2
+
3
+ Where each piece of state lives.
4
+
5
+ ## Put state where its truth lives
6
+
7
+ Choose the first suitable level:
8
+
9
+ 1. **URL state**: filters, sort, pagination, and active tab. Use TanStack Router search params and validate them.
10
+ 2. **Server state**: API data. Use TanStack Query; do not copy it into `useState`.
11
+ 3. **Subtree state**: state shared by a subtree and reset when it leaves. Use React context.
12
+ 4. **Client state**: app-wide persistent state. Use TanStack Store only when the earlier levels do not fit.
13
+ 5. **Local state**: one component's open state, draft, or input value. Use `useState`.
14
+
15
+ Shape state so it is not redundant or duplicated. Validate a form draft when it becomes a submitted domain value.
16
+
17
+ ## State shape
18
+
19
+ - **One status, not exclusive booleans (any version).** `status: "idle" | "saving" | "error"` instead of `isSaving` and
20
+ `isError` that can both be true. Merge state variables that always change together.
21
+ - **Reducers are pure (any version).** No requests, toasts, or timers inside a reducer. Dispatch one action per user
22
+ interaction, from the event handler.
23
+ - **Slow renders (React 18+).** Keep text input state out of a transition. When a slow list follows a prop or hook value,
24
+ render it from `useDeferredValue(value)`.
25
+
26
+ See [Choosing the State Structure](https://react.dev/learn/choosing-the-state-structure) and
27
+ [Extracting State Logic into a Reducer](https://react.dev/learn/extracting-state-logic-into-a-reducer).
28
+
29
+ ## Fallbacks without TanStack
30
+
31
+ TanStack Form and Query are the default. A project without them uses React's own tools:
32
+
33
+ - **Forms (React 19+):** `<form action={fn}>` needs no `preventDefault` and resets uncontrolled fields on success. Read
34
+ `useFormStatus` only in a child of the form. `useActionState` holds the result; dispatch it manually only inside an Action
35
+ or `startTransition`.
36
+ - **Optimistic updates (React 19+):** `useOptimistic`, with the setter called only inside an Action. Once the save succeeds,
37
+ the parent's value is the truth again.
38
+
39
+ See [`<form>`](https://react.dev/reference/react-dom/components/form),
40
+ [`useActionState`](https://react.dev/reference/react/useActionState), and
41
+ [`useOptimistic`](https://react.dev/reference/react/useOptimistic).
@@ -1,65 +1,12 @@
1
1
  # React
2
2
 
3
3
  Keep data flow one-way and components focused. Pages decide how data is loaded; presentational components decide how it
4
- looks.
4
+ looks. Read the subtopic that matches the task.
5
5
 
6
- ## Composition: pages (smart containers), partials, dumb components
6
+ | Subtopic | Read when |
7
+ | ----------------------------------- | --------------------------------------------------------------- |
8
+ | [composition](react/composition.md) | Deciding a component's role, or composing parts. |
9
+ | [state](react/state.md) | Deciding where state lives. |
10
+ | [effects](react/effects.md) | Writing or reviewing `useEffect`, or reading values in effects. |
7
11
 
8
- - **Pages** live in `routes/`. They load data, read URL state, coordinate actions, and compose the view. Route files hold the
9
- search schema, loaders, and pending, error, and not-found components. They do not own component CSS. Route-local views and
10
- forms live in the route's `-` folder, as [project-structure.md](project-structure.md) describes, and that folder's
11
- `.module.css` files are component styles.
12
- - **Partials** are compositions that more than one route uses, extracted because they read as a unit. They have light logic
13
- and no styling of their own.
14
- - **Layout components** arrange content with `Stack`, `Row`, grids, and token gaps, and wrap long-form text in `Prose`.
15
- - **Dumb components** receive data and callbacks, render UI, and own their styles. They do not fetch, route, or contain
16
- business rules.
17
-
18
- ```tsx
19
- function UserCard({ name, onEdit }: { name: string; onEdit: () => void }) {
20
- return (
21
- <Card.Root>
22
- <Typo.P>{name}</Typo.P>
23
- <Button onClick={onEdit}>Edit</Button>
24
- </Card.Root>
25
- );
26
- }
27
- ```
28
-
29
- ## Compose, don't configure
30
-
31
- Compose small parts instead of making one component configurable through many boolean props. Lift shared state to the nearest
32
- common parent.
33
-
34
- ## Put state where its truth lives
35
-
36
- Choose the first suitable level:
37
-
38
- 1. **URL state**: filters, sort, pagination, and active tab. Use TanStack Router search params and validate them.
39
- 2. **Server state**: API data. Use TanStack Query; do not copy it into `useState`.
40
- 3. **Subtree state**: state shared by a subtree and reset when it leaves. Use React context.
41
- 4. **Client state**: app-wide persistent state. Use TanStack Store only when the earlier levels do not fit.
42
- 5. **Local state**: one component's open state, draft, or input value. Use `useState`.
43
-
44
- Shape state so it is not redundant or duplicated. Validate a form draft when it becomes a submitted domain value.
45
-
46
- ## Effects: last resort, and named
47
-
48
- Use `useEffect` only to synchronize with something outside React. Do not use it for derived values, event responses, or
49
- server sync.
50
-
51
- A genuine effect belongs in a named custom hook. If it stays inline, name the callback:
52
-
53
- ```tsx
54
- useEffect(
55
- function syncDocumentTitle() {
56
- document.title = title;
57
- },
58
- [title]
59
- );
60
- ```
61
-
62
- Destructure a primitive before putting it in an effect dependency array. Keep the returned query or route object intact
63
- elsewhere so its namespace remains visible.
64
-
65
- Read the React documentation when a decomposition or effect decision is unclear.
12
+ Read the React documentation when a decomposition decision is unclear.
@@ -19,7 +19,8 @@ Use native CSS and Web UI's public seams. The library's CSS Modules and token na
19
19
  transition: opacity var(--imf-ui-duration-quick-2) var(--imf-ui-ease-2);
20
20
 
21
21
  &:focus-visible {
22
- outline: var(--imf-ui-border-size-2) solid var(--imf-ui-color-fg-primary);
22
+ outline: var(--imf-ui-focus-ring-width) solid var(--imf-ui-focus-ring-color);
23
+ outline-offset: var(--imf-ui-focus-ring-offset);
23
24
  }
24
25
 
25
26
  &[data-disabled] {
@@ -28,6 +29,13 @@ Use native CSS and Web UI's public seams. The library's CSS Modules and token na
28
29
  }
29
30
  ```
30
31
 
32
+ ## Focus ring
33
+
34
+ Web UI parts draw their own focus ring. Your own focusable elements draw the same one from the `--imf-ui-focus-ring-*`
35
+ tokens: `width` and `color` for the outline, and one of two offsets: `offset` outside a standalone control and `offset-inset`
36
+ for items inside a scrolling or tight container, which would clip an outside ring. A surface filled with a saturated color
37
+ sets `--imf-ui-focus-ring-color` to `--imf-ui-color-fg-oncolor` for everything inside it.
38
+
31
39
  ## Build custom UI from tokens
32
40
 
33
41
  Use names from the shipped token index for colors, type, size, radius, and shadow. A literal beside a tokenized concept is a
@@ -54,6 +62,19 @@ hook, not a state flag.
54
62
  }
55
63
  ```
56
64
 
65
+ Your own components follow the same seam. A container that changes a child sets a custom property; the child reads it with
66
+ its own default as the fallback, so it works with or without the container:
67
+
68
+ ```css
69
+ .panel {
70
+ --panel-row-gap: var(--imf-ui-size-2);
71
+ }
72
+
73
+ .row {
74
+ padding-block: var(--panel-row-gap, var(--imf-ui-size-3));
75
+ }
76
+ ```
77
+
57
78
  ## Spacing
58
79
 
59
80
  Web UI components have no outer margin. The parent sets the distance between its children: `Stack` and `Row` gaps in UI,
@@ -25,7 +25,8 @@ Use the tool's current documentation rather than memory.
25
25
 
26
26
  Use the library that already owns the problem. Adopt Form for form rules, Query for caching and refetching, Router for URL
27
27
  state, Table for sorting and pagination, and Hotkeys for keyboard shortcuts instead of rebuilding those layers. Web UI only
28
- shows a key; the app binds it. Keep an existing project choice when it works.
28
+ shows a key; the app binds it. Keep an existing project choice when it works. A layer rebuilt by hand, such as form state in
29
+ `useState`, is not a choice to keep.
29
30
 
30
31
  ## Devtools
31
32
 
@@ -44,7 +45,7 @@ the Agent Skills the TanStack package provides.
44
45
  Vite+ runs development, build, tests, format, lint, and staged checks through one `vp` binary configured in `vite.config.ts`.
45
46
  Pin `vite-plus` exactly, alias `vite` to `npm:@voidzero-dev/vite-plus-core@<vite-plus version>`, pin `vitest` to the version
46
47
  `vp toolchain vitest` reports, and repeat both pins under `overrides`. Import `defineConfig` from `vite-plus` and Vitest APIs
47
- from `vite-plus/test`. It needs Node `^22.22.1 || ^24.11 || >=26` and Git 2.32 or later.
48
+ from `vite-plus/test`. It needs Git 2.32 or later; the `npm-project` topic sets the Node range.
48
49
 
49
50
  `vp <command>` is a built-in; `vp run <script>` runs a package script.
50
51
 
@@ -54,3 +54,10 @@ Use `.catch()` for malformed values that can safely fall back. Use `.default()`
54
54
  - Use `parse` when invalid data is a contract failure that should reach the normal error boundary.
55
55
  - Use `safeParse` when failure is expected and the caller can show a validation message.
56
56
  - Keep trimming, coercion, transforms, and defaults in the boundary schema.
57
+
58
+ ## Forms
59
+
60
+ Build forms with TanStack Form and validate user input in the form's validators with the Zod schema, so a field error and the
61
+ submitted value's type come from one schema. Field values held in `useState` and checked by hand with `safeParse` are a gap,
62
+ even when the project's other forms do the same. A project without TanStack Form follows the fallback in
63
+ [react/state.md](react/state.md#fallbacks-without-tanstack).
@@ -1,7 +1,8 @@
1
1
  # Form UX
2
2
 
3
- Read this before building a form with more than two fields. The library supplies controls; the form library owns state and
4
- validation. These rules cover structure and feedback.
3
+ Read this before building a form with more than two fields. Build it with TanStack Form, the form library the
4
+ `imf-web-ui-conventions` tooling topic names: it owns state and validation, and Web UI supplies the controls. These rules
5
+ cover structure and feedback.
5
6
 
6
7
  ## Structure
7
8
 
@@ -462,6 +462,24 @@
462
462
  "value": "cubic-bezier(0, 0, 0.05, 1)"
463
463
  }
464
464
  ],
465
+ "focus": [
466
+ {
467
+ "name": "--imf-ui-focus-ring-color",
468
+ "value": "var(--imf-ui-color-fg-primary)"
469
+ },
470
+ {
471
+ "name": "--imf-ui-focus-ring-offset",
472
+ "value": "2px"
473
+ },
474
+ {
475
+ "name": "--imf-ui-focus-ring-offset-inset",
476
+ "value": "calc(var(--imf-ui-focus-ring-width) * -1)"
477
+ },
478
+ {
479
+ "name": "--imf-ui-focus-ring-width",
480
+ "value": "var(--imf-ui-border-size-2)"
481
+ }
482
+ ],
465
483
  "font": [
466
484
  {
467
485
  "name": "--imf-ui-font-family-body",
@@ -955,5 +973,5 @@
955
973
  }
956
974
  ]
957
975
  },
958
- "count": 233
976
+ "count": 237
959
977
  }