@imfusion/web-ui 0.5.1-dev.56.gcf89961f → 0.5.1-dev.6.g9d275fa8
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 +27 -73
- package/bin/install-skill.js +180 -0
- package/dist/code-qBbqAHK-.js +190 -0
- package/dist/components/callout/callout.d.ts +1 -1
- package/dist/components/chip-link/chip-link.d.ts +1 -1
- package/dist/components/code/code.d.ts +18 -10
- package/dist/components/input/input.d.ts +1 -3
- package/dist/components/typo/typo.d.ts +2 -2
- package/dist/hooks/index.d.ts +0 -1
- package/dist/index.d.ts +1 -4
- package/dist/index.js +12206 -1789
- package/dist/integrations/code-highlight/code-highlight.d.ts +6 -8
- package/dist/integrations/code-highlight/highlighter.d.ts +3 -32
- package/dist/integrations/code-highlight.js +59 -198
- package/dist/integrations/image-display-options.js +69 -70
- package/dist/meta-B8C51eyL.js +74 -0
- package/dist/provider/web-ui-provider.d.ts +1 -4
- package/dist/style.css +1 -1
- package/dist/tabs-DqBFSqq6.js +3789 -0
- package/package.json +26 -48
- package/src/docgen/doc.gen.json +38 -381
- package/src/llms/llms.gen.txt +0 -17
- package/src/llms/skills/imf-web-ui/SKILL.md +12 -13
- package/src/llms/skills/imf-web-ui-components/SKILL.md +3 -56
- package/src/llms/skills/imf-web-ui-frontend-patterns/SKILL.md +93 -0
- package/src/llms/skills/imf-web-ui-frontend-patterns/references/code-conventions.md +133 -0
- package/src/llms/skills/imf-web-ui-frontend-patterns/references/react-patterns.md +94 -0
- package/src/llms/skills/imf-web-ui-imfusion-frontend-setup/SKILL.md +201 -0
- package/src/llms/skills/imf-web-ui-setup/SKILL.md +37 -67
- package/src/llms/skills/imf-web-ui-ux/SKILL.md +4 -4
- package/src/llms/skills/imf-web-ui-ux/references/forms.md +2 -2
- package/bin/install.js +0 -428
- package/bin/install.test.ts +0 -329
- package/dist/build/vite-css-module-names/index.d.ts +0 -20
- package/dist/build/vite-css-module-names.js +0 -17
- package/dist/chunk-DmhlhrBa.js +0 -11
- package/dist/code-Blo48PGr.js +0 -136
- package/dist/codegen/gen-icons.d.ts +0 -24
- package/dist/components/field/field.d.ts +0 -104
- package/dist/components/field/field.meta.d.ts +0 -2
- package/dist/components/field/index.d.ts +0 -2
- package/dist/components/fieldset/fieldset.d.ts +0 -29
- package/dist/components/fieldset/fieldset.meta.d.ts +0 -2
- package/dist/components/fieldset/index.d.ts +0 -2
- package/dist/components/icon/icon.d.ts +0 -16
- package/dist/components/icon/icon.meta.d.ts +0 -2
- package/dist/components/icon/index.d.ts +0 -4
- package/dist/components/icon/types.d.ts +0 -2
- package/dist/docgen/component-sources.d.ts +0 -7
- package/dist/hooks/use-resize-observer.d.ts +0 -2
- package/dist/icons/catalog.gen.d.ts +0 -8357
- package/dist/icons/icon-config-provider.d.ts +0 -8
- package/dist/icons/icon-context.d.ts +0 -4
- package/dist/icons/icons.gen.d.ts +0 -1672
- package/dist/icons/index.d.ts +0 -3
- package/dist/icons-wBmF0U2x.js +0 -78
- package/dist/icons.js +0 -2
- package/dist/integrations/code-highlight/language-patterns.d.ts +0 -7
- package/dist/integrations/code-highlight/languages/cmake.d.ts +0 -1
- package/dist/integrations/code-highlight/languages/cpp.d.ts +0 -1
- package/dist/integrations/code-highlight/languages/python.d.ts +0 -1
- package/dist/llms/gen-tokens.d.ts +0 -7
- package/dist/meta-CySnRuVp.js +0 -21
- package/dist/tabs-CMKvMF4E.js +0 -369
- package/src/llms/icon-catalog.gen.json +0 -11203
- package/src/llms/install-templates/AGENTS.md +0 -34
- package/src/llms/install-templates/codex-hooks.json +0 -44
- package/src/llms/install-templates/hooks/baseline-staleness.sh +0 -17
- package/src/llms/install-templates/hooks/session-start.sh +0 -5
- package/src/llms/install-templates/hooks/stop.sh +0 -18
- package/src/llms/install-templates/hooks/subagent-start.sh +0 -5
- package/src/llms/install-templates/hooks/user-prompt-submit.sh +0 -5
- package/src/llms/install-templates/settings.json +0 -45
- package/src/llms/skills/imf-web-ui-audit/SKILL.md +0 -119
- package/src/llms/skills/imf-web-ui-conventions/SKILL.md +0 -57
- package/src/llms/skills/imf-web-ui-conventions/templates/AUDIT_CHECKLIST.md +0 -141
- package/src/llms/skills/imf-web-ui-conventions/templates/REPORT.md +0 -45
- package/src/llms/skills/imf-web-ui-conventions/topics/agent-tooling.md +0 -82
- package/src/llms/skills/imf-web-ui-conventions/topics/assets.md +0 -27
- package/src/llms/skills/imf-web-ui-conventions/topics/authentication.md +0 -65
- package/src/llms/skills/imf-web-ui-conventions/topics/class-names.md +0 -50
- package/src/llms/skills/imf-web-ui-conventions/topics/components.md +0 -101
- package/src/llms/skills/imf-web-ui-conventions/topics/data.md +0 -221
- package/src/llms/skills/imf-web-ui-conventions/topics/docs-structure.md +0 -40
- package/src/llms/skills/imf-web-ui-conventions/topics/git.md +0 -34
- package/src/llms/skills/imf-web-ui-conventions/topics/library-boundary.md +0 -35
- package/src/llms/skills/imf-web-ui-conventions/topics/library-setup.md +0 -26
- package/src/llms/skills/imf-web-ui-conventions/topics/npm-project.md +0 -53
- package/src/llms/skills/imf-web-ui-conventions/topics/project-structure.md +0 -44
- package/src/llms/skills/imf-web-ui-conventions/topics/react.md +0 -109
- package/src/llms/skills/imf-web-ui-conventions/topics/styling.md +0 -88
- package/src/llms/skills/imf-web-ui-conventions/topics/testing.md +0 -25
- package/src/llms/skills/imf-web-ui-conventions/topics/tokens.md +0 -7
- package/src/llms/skills/imf-web-ui-conventions/topics/tooling.md +0 -116
- package/src/llms/skills/imf-web-ui-conventions/topics/typescript.md +0 -73
- package/src/llms/skills/imf-web-ui-conventions/topics/validation.md +0 -62
- package/src/llms/skills/imf-web-ui-update/SKILL.md +0 -157
- package/src/llms/tokens.gen.json +0 -887
package/src/llms/llms.gen.txt
CHANGED
|
@@ -73,23 +73,6 @@ Off-canvas panel that slides in from a screen edge. Use for mobile navigation, s
|
|
|
73
73
|
- props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .drawer (jq: jq '.drawer' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
|
|
74
74
|
- further reading (usage/anatomy, not props): https://base-ui.com/react/components/drawer.md
|
|
75
75
|
|
|
76
|
-
## Field
|
|
77
|
-
- category: Inputs, status: stable
|
|
78
|
-
Accessible field composition for a label, form control, supporting description, and inline validation message. Use it to associate a control such as Input, Checkbox, or Select with field state including valid, invalid, dirty, touched, filled, and focused.
|
|
79
|
-
- props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .field (jq: jq '.field' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
|
|
80
|
-
- further reading (usage/anatomy, not props): https://base-ui.com/react/components/field.md
|
|
81
|
-
|
|
82
|
-
## Fieldset
|
|
83
|
-
- category: Inputs, status: stable
|
|
84
|
-
Semantic grouping for related form controls with a shared legend. Also called a form group; use it to communicate a common topic and propagate disabled state without introducing a decorative container.
|
|
85
|
-
- 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)
|
|
86
|
-
- further reading (usage/anatomy, not props): https://base-ui.com/react/components/fieldset.md
|
|
87
|
-
|
|
88
|
-
## Icon
|
|
89
|
-
- category: Display, status: stable
|
|
90
|
-
Semantic color wrapper for an icon imported from the Web UI icons entry. Use it when an icon needs a named foreground role instead of the inherited currentColor.
|
|
91
|
-
- props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .icon (jq: jq '.icon' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
|
|
92
|
-
|
|
93
76
|
## ImageDisplayOptions
|
|
94
77
|
- category: Inputs, status: experimental
|
|
95
78
|
A panel or toolbar of controls bound to an image dataset's display options.
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
name: imf-web-ui
|
|
3
3
|
description:
|
|
4
4
|
"Entry point for UI work in a project that depends on @imfusion/web-ui. Decides whether guidance is needed at all, then
|
|
5
|
-
routes to the right companion skill — component reference, UX guidance, or frontend
|
|
6
|
-
|
|
5
|
+
routes to the right companion skill — component reference, UX guidance, or frontend patterns. Load when adding or editing
|
|
6
|
+
UI in a consumer repo."
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# imf-web-ui
|
|
@@ -24,17 +24,16 @@ Guidance skills exist to fill gaps, not to add ceremony to clear tasks.
|
|
|
24
24
|
|
|
25
25
|
## Routing
|
|
26
26
|
|
|
27
|
-
| The task at hand | Open
|
|
28
|
-
| ----------------------------------------------------------------------------------------------------- |
|
|
29
|
-
| Using a specific component; checking props, sub-components, or defaults | `imf-web-ui-components`
|
|
30
|
-
| First-time setup, adding a library dependency, or components rendering unstyled/broken | `imf-web-ui-setup`
|
|
31
|
-
| Building/reshaping a screen or flow; choosing between components; layout, density, hierarchy, states | `imf-web-ui-ux`
|
|
32
|
-
| Writing wrappers or custom UI; styling beyond defaults; adding files; TypeScript, naming, testing | `imf-web-ui-
|
|
33
|
-
| Setting up or auditing an **ImFusion** repo's tooling: formatting, linting, hooks, scripts, structure | `imf-web-ui-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
Being asked to add one config file is just that edit; make it, and don't open a skill to do so.
|
|
27
|
+
| The task at hand | Open |
|
|
28
|
+
| ----------------------------------------------------------------------------------------------------- | ------------------------------------ |
|
|
29
|
+
| Using a specific component; checking props, sub-components, or defaults | `imf-web-ui-components` |
|
|
30
|
+
| First-time setup, adding a library dependency, or components rendering unstyled/broken | `imf-web-ui-setup` |
|
|
31
|
+
| Building/reshaping a screen or flow; choosing between components; layout, density, hierarchy, states | `imf-web-ui-ux` |
|
|
32
|
+
| Writing wrappers or custom UI; styling beyond defaults; adding files; TypeScript, naming, testing | `imf-web-ui-frontend-patterns` |
|
|
33
|
+
| Setting up or auditing an **ImFusion** repo's tooling: formatting, linting, hooks, scripts, structure | `imf-web-ui-imfusion-frontend-setup` |
|
|
34
|
+
|
|
35
|
+
The last row is narrow on purpose. It's for "what is this project missing?" — a question about the repo as a whole. Being
|
|
36
|
+
asked to add one config file is just that edit; make it, and don't open a skill to do so.
|
|
38
37
|
|
|
39
38
|
Tasks routinely span two: building a screen usually means `imf-web-ui-ux` for the shape and `imf-web-ui-components` for the
|
|
40
39
|
APIs. That's normal — open both, in that order.
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: imf-web-ui-components
|
|
3
3
|
description:
|
|
4
|
-
"Look up @imfusion/web-ui component APIs
|
|
5
|
-
|
|
6
|
-
(imf-web-ui-setup
|
|
7
|
-
allowed-tools: Bash
|
|
4
|
+
"Look up @imfusion/web-ui component APIs without reading their source: the two-hop lookup (identity index -> prop data),
|
|
5
|
+
compound components, and integrations. Load when you need the props, sub-components, or defaults of a specific component —
|
|
6
|
+
not for choosing between components (imf-web-ui-ux) or first-time setup (imf-web-ui-setup)."
|
|
8
7
|
---
|
|
9
8
|
|
|
10
9
|
# imf-web-ui-components
|
|
@@ -16,8 +15,6 @@ reading source or checking out the library's repo:
|
|
|
16
15
|
and a one-sentence description of what it's for and what else it's called.
|
|
17
16
|
- **`node_modules/@imfusion/web-ui/src/docgen/doc.gen.json`** — full prop tables (name, type, default, description) for every
|
|
18
17
|
component, keyed by kebab-case folder name.
|
|
19
|
-
- **`node_modules/@imfusion/web-ui/src/llms/icon-catalog.gen.json`** — searchable icon names, styles, categories, and tags.
|
|
20
|
-
Read it only when the task needs an icon.
|
|
21
18
|
|
|
22
19
|
Neither file is reachable through the package's pretty import paths (`@imfusion/web-ui/llms.txt`,
|
|
23
20
|
`@imfusion/web-ui/docgen.json`) — those are Node module-resolution aliases, meaningless to `cat`/`jq`/`grep` reading files
|
|
@@ -75,56 +72,6 @@ don't guess at an API — that's a real gap to report, not something to work aro
|
|
|
75
72
|
maintainer, or file it in the [WEBSDK Jira project](https://imfusion.atlassian.net/browse/WEBSDK) if you have access. If the
|
|
76
73
|
gap is about _which_ component to use rather than a missing one, that's a design question: open `imf-web-ui-ux`.
|
|
77
74
|
|
|
78
|
-
## Icons
|
|
79
|
-
|
|
80
|
-
When the task mentions an icon, glyph, symbol, or `@imfusion/web-ui/icons`, query the icon catalog before choosing a name. Do
|
|
81
|
-
not load it for ordinary component work.
|
|
82
|
-
|
|
83
|
-
Search one relevant term at a time, then read only the matching records:
|
|
84
|
-
|
|
85
|
-
```sh
|
|
86
|
-
node -e '
|
|
87
|
-
const q = process.argv[1].toLowerCase();
|
|
88
|
-
const icons = JSON.parse(
|
|
89
|
-
require("fs").readFileSync(
|
|
90
|
-
"node_modules/@imfusion/web-ui/src/llms/icon-catalog.gen.json",
|
|
91
|
-
"utf8"
|
|
92
|
-
)
|
|
93
|
-
);
|
|
94
|
-
console.log(
|
|
95
|
-
JSON.stringify(
|
|
96
|
-
icons
|
|
97
|
-
.filter(icon => [icon.name, icon.category, ...icon.tags]
|
|
98
|
-
.join(" ")
|
|
99
|
-
.toLowerCase()
|
|
100
|
-
.includes(q))
|
|
101
|
-
.slice(0, 20),
|
|
102
|
-
null,
|
|
103
|
-
2
|
|
104
|
-
)
|
|
105
|
-
);
|
|
106
|
-
' "add"
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
Import the chosen glyph and `Icon` from `@imfusion/web-ui/icons`. Render it through `Icon`, including when it inherits the
|
|
110
|
-
surrounding color:
|
|
111
|
-
|
|
112
|
-
```tsx
|
|
113
|
-
import { ArrowRight, Icon } from "@imfusion/web-ui/icons";
|
|
114
|
-
|
|
115
|
-
<Icon glyph={ArrowRight} aria-hidden />;
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Use `Icon` for every rendered icon. Pass the component reference, not a rendered element:
|
|
119
|
-
|
|
120
|
-
```tsx
|
|
121
|
-
import { ArrowRight, Icon } from "@imfusion/web-ui/icons";
|
|
122
|
-
|
|
123
|
-
<Icon glyph={ArrowRight} size={16} variant="primary" aria-label="Continue" role="img" />;
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
Never import `iconoir-react` or another icon package directly.
|
|
127
|
-
|
|
128
75
|
## Compound components
|
|
129
76
|
|
|
130
77
|
A component whose docgen entry has a non-empty `subComponents` array is used as a namespace, not a single import — e.g.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: imf-web-ui-frontend-patterns
|
|
3
|
+
description:
|
|
4
|
+
"Raise the quality of frontend code written around @imfusion/web-ui — including quickly vibe-coded frontends. Library
|
|
5
|
+
boundary contract (tokens, CSS layers, type derivation), component roles, state placement, effects discipline, code
|
|
6
|
+
conventions (TypeScript, naming, file organisation, testing), and stack defaults. Load when writing wrapper components,
|
|
7
|
+
custom UI, styling beyond the defaults, adding new files to a consumer app, or choosing a routing, data-fetching, form, or
|
|
8
|
+
table library."
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# imf-web-ui-frontend-patterns
|
|
12
|
+
|
|
13
|
+
One guard, once: **if the host project already has a convention — a styling system, a state library, a folder shape — the
|
|
14
|
+
project wins.** These defaults fill vacuums. They are not a license to refactor a consumer codebase toward this document.
|
|
15
|
+
|
|
16
|
+
Everything else below is how to build.
|
|
17
|
+
|
|
18
|
+
## Stay behind the library
|
|
19
|
+
|
|
20
|
+
Never import Base UI (or any other upstream this library wraps) directly — no upstream stylesheets, no upstream components,
|
|
21
|
+
even when upstream docs show it that way. Everything a component needs ships in `@imfusion/web-ui`. If the library is missing
|
|
22
|
+
something upstream has, report the gap (see `imf-web-ui-components`); don't reach around it.
|
|
23
|
+
|
|
24
|
+
## Style through the sanctioned seams
|
|
25
|
+
|
|
26
|
+
All library styles live in the `imf-ui.components` CSS layer, so **any plain selector you write wins** — that's the whole
|
|
27
|
+
override contract:
|
|
28
|
+
|
|
29
|
+
- Target the stable hooks: `data-imf-ui-component` attributes and your own classes/wrappers.
|
|
30
|
+
- Never target the library's internal class names — they are generated and change without notice.
|
|
31
|
+
- Never `!important` — if you think you need it, you're targeting the wrong thing.
|
|
32
|
+
|
|
33
|
+
## Build custom UI from tokens
|
|
34
|
+
|
|
35
|
+
Anything you build that the library doesn't cover — a stat widget, a custom panel — uses `--imf-ui-*` variables for color,
|
|
36
|
+
spacing, radius, and type instead of hardcoded values. That's what makes custom UI look native next to library components,
|
|
37
|
+
and what keeps it correct when the theme changes. A hex code or a magic `px` next to a concept the tokens already name is a
|
|
38
|
+
defect.
|
|
39
|
+
|
|
40
|
+
## Derive types, don't import them
|
|
41
|
+
|
|
42
|
+
Prop types come from the components themselves: `React.ComponentProps<typeof Button>`. The library deliberately exports no
|
|
43
|
+
`Props` types — don't look for them, and don't re-declare prop shapes by hand.
|
|
44
|
+
|
|
45
|
+
## Integrations own their peers
|
|
46
|
+
|
|
47
|
+
Components under `@imfusion/web-ui/integrations/*` depend on optional peers (e.g. `@tanstack/highlight` for `CodeHighlight`).
|
|
48
|
+
Add the peer explicitly to the consumer's `package.json` — never rely on hoisting.
|
|
49
|
+
|
|
50
|
+
## React patterns
|
|
51
|
+
|
|
52
|
+
The full treatment — component roles with an example, state placement, effects discipline, and the react.dev sources to
|
|
53
|
+
consult while building — lives in [references/react-patterns.md](references/react-patterns.md). Read it before writing new
|
|
54
|
+
screens or wrappers. The core in one breath:
|
|
55
|
+
|
|
56
|
+
- **Three roles.** Dumb components own how things look, layout components own arrangement, smart containers own data and
|
|
57
|
+
logic. Styling never lives in containers.
|
|
58
|
+
- **State lives where its truth lives.** URL → query cache → context → store → local state; walk the list, stop at the first
|
|
59
|
+
match.
|
|
60
|
+
- **Effects are a last resort**, and always extracted into purpose-named hooks.
|
|
61
|
+
- **Compose, don't configure.** If a component's prop list reads like a settings page, it wanted to be two or three
|
|
62
|
+
components.
|
|
63
|
+
|
|
64
|
+
## Everything else about the code
|
|
65
|
+
|
|
66
|
+
TypeScript, naming, where files go, and what's worth testing live in
|
|
67
|
+
[references/code-conventions.md](references/code-conventions.md). Read it when you're adding files rather than editing
|
|
68
|
+
existing ones — that's when these choices get made and then inherited by everything after.
|
|
69
|
+
|
|
70
|
+
The split between the two references: `react-patterns.md` covers **writing React** — component roles, where state lives,
|
|
71
|
+
effects discipline, composition. `code-conventions.md` covers **the code around it** — TypeScript, JS style, naming, file
|
|
72
|
+
layout, testing. Starting a new feature usually wants both.
|
|
73
|
+
|
|
74
|
+
## Stack defaults
|
|
75
|
+
|
|
76
|
+
TanStack is the default for the tooling around web-ui, whether the app is greenfield or you're adding one screen to something
|
|
77
|
+
that already exists. The ones you'll reach for most: **Router** (URL state, type-safe search params), **Query** (server
|
|
78
|
+
state), **Form** (form state), and **Table** for data grids, which you pair with web-ui's styled `Table` parts
|
|
79
|
+
(`Table.SortableHeaderCell` carries the sort glue). The suite goes wider than those four — check what exists before adding a
|
|
80
|
+
non-TanStack dependency. This is the stack the state ladder assumes.
|
|
81
|
+
|
|
82
|
+
`useState` is the right tool for local UI state, and most of it is local: whether a panel is open, which tab is active, a
|
|
83
|
+
draft value being typed, a hover flag. Keep those in the component and don't reach for a library.
|
|
84
|
+
|
|
85
|
+
The line is what the state is _for_, not how much of it there is. A library owns the layer once you find yourself rebuilding
|
|
86
|
+
what it does: validation timing and cross-field rules (**Form**), caching and refetching (**Query**), URL as the source of
|
|
87
|
+
truth (**Router**), sorting and pagination over rows (**Table**). web-ui ships none of that logic, and that absence is not an
|
|
88
|
+
argument for writing it yourself. Adding the library mid-project is normal and cheap; unpicking a hand-rolled version of it
|
|
89
|
+
later is not.
|
|
90
|
+
|
|
91
|
+
For best practices and patterns within any of these libraries, go to the library's own guidance rather than working from
|
|
92
|
+
memory: `npx @tanstack/cli` for docs. Where a project has wired up `@tanstack/intent`, use it to reach the Agent Skills its
|
|
93
|
+
TanStack dependencies ship, and read those too.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Code conventions
|
|
2
|
+
|
|
3
|
+
The ImFusion defaults for everyday code around `@imfusion/web-ui` — the parts that aren't React-specific. TypeScript, file
|
|
4
|
+
organisation, naming, and testing. React component structure lives in [react-patterns.md](react-patterns.md); the two are
|
|
5
|
+
read together when starting new code.
|
|
6
|
+
|
|
7
|
+
These fill vacuums. Where the host project has already decided, the project wins.
|
|
8
|
+
|
|
9
|
+
## TypeScript
|
|
10
|
+
|
|
11
|
+
- **`any` is forbidden.** `unknown` at a boundary you genuinely can't type, narrowed before use. An `any` that silences an
|
|
12
|
+
error moves the failure from compile time to runtime, which is the opposite of the trade you wanted.
|
|
13
|
+
- **Lean on inference for locals; annotate the contract.** Restating a type the compiler already knows inside a function body
|
|
14
|
+
is a second thing to keep in sync. An **explicit return type on an exported function is worth writing**: it's the promise
|
|
15
|
+
the module makes, it stops an internal refactor silently widening the public shape, and it makes the error surface at the
|
|
16
|
+
function rather than at every call site.
|
|
17
|
+
- **No temporal coupling.** Don't initialise to `null` and fill the value in later — model the states instead, so "not loaded
|
|
18
|
+
yet" and "loaded, empty" aren't the same value.
|
|
19
|
+
- **Avoid `as`.** A type assertion tells the compiler to stop checking exactly where checking is worth most. Fix the type.
|
|
20
|
+
Assertions at an untyped third-party boundary are the honest exception; keep them at the boundary, not spread through call
|
|
21
|
+
sites.
|
|
22
|
+
|
|
23
|
+
**Derive types, don't duplicate them.** One source of truth, everything else follows from it:
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
const sizes = ["sm", "md", "lg"] as const;
|
|
27
|
+
type Size = (typeof sizes)[number];
|
|
28
|
+
|
|
29
|
+
const labels: Record<Size, string> = { sm: "S", md: "M", lg: "L" }; // compiler breaks if `sizes` changes
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The same rule crosses the library boundary: prop types come from the components themselves
|
|
33
|
+
(`React.ComponentProps<typeof Button>`), never re-declared by hand.
|
|
34
|
+
|
|
35
|
+
**Function signatures.** One or two positional arguments read fine. At three or more, take a single object and destructure —
|
|
36
|
+
call sites stop depending on argument order, and adding a parameter stops being a breaking change.
|
|
37
|
+
|
|
38
|
+
## Expressions over statements
|
|
39
|
+
|
|
40
|
+
Reach for the array methods before the loop. `map`, `filter`, `find`, `some`, `every`, `flatMap`, `reduce` — each names what
|
|
41
|
+
it's doing, where a `for` loop makes you read the body to find out.
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
// The name is the documentation
|
|
45
|
+
const activeNames = users.filter(u => u.isActive).map(u => u.name);
|
|
46
|
+
|
|
47
|
+
// vs. a loop you have to read to understand
|
|
48
|
+
const activeNames = [];
|
|
49
|
+
for (const u of users) {
|
|
50
|
+
if (u.isActive) activeNames.push(u.name);
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The deeper reason is mutation: the method chain produces a new value, so nothing else can observe a half-built array. Prefer
|
|
55
|
+
spreads and `structuredClone` over in-place edits, and `toSorted`/`toReversed` over `sort`/`reverse`, which mutate their
|
|
56
|
+
receiver and have surprised everyone at least once.
|
|
57
|
+
|
|
58
|
+
Two honest exceptions: a genuine early exit (`for` with `break` beats `find` returning a sentinel) and a hot loop over
|
|
59
|
+
thousands of items where the intermediate arrays actually measure. Neither is the common case, so reach for the method first
|
|
60
|
+
and justify the loop.
|
|
61
|
+
|
|
62
|
+
Keep the chain flat. Three or four steps read well; ten want intermediate named constants, and a `reduce` doing four things
|
|
63
|
+
at once wants to be a loop after all.
|
|
64
|
+
|
|
65
|
+
## Naming
|
|
66
|
+
|
|
67
|
+
- Say what it is, not what it is made of. `useUserQuery`, not `useUserHook`. `retryDelay`, not `num`.
|
|
68
|
+
- Booleans read as assertions: `isOpen`, `hasAccess`, `canSubmit`. A boolean called `status` will end up holding a string.
|
|
69
|
+
- Handlers are `onX` as props, `handleX` as implementations — the prop names the event, the function names the response.
|
|
70
|
+
- Match the vocabulary the product and the API already use. Inventing a synonym for a term the backend already named costs a
|
|
71
|
+
translation step on every read.
|
|
72
|
+
|
|
73
|
+
## File and folder organisation
|
|
74
|
+
|
|
75
|
+
Kebab-case throughout, folders and files.
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
src/
|
|
79
|
+
routes/ # TanStack Router file-based routes; routing only
|
|
80
|
+
api/<topic>/ # <topic>.ts (queries/mutations), query-key.ts, types.ts
|
|
81
|
+
components/ # grouped by kind of component — layouts/, primitives/, or a domain name
|
|
82
|
+
http/ # client, error normalisation
|
|
83
|
+
lib/ # framework-free helpers
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`api/` groups by topic: a query lives next to its key factory and its types, so a query key is never spelled out at a call
|
|
87
|
+
site. Routes compose and don't fetch inline. Transport concerns live in `http/` and nowhere else.
|
|
88
|
+
|
|
89
|
+
`components/` groups by kind — a layout component under `layouts/`, not beside a domain widget. Flat is fine while there are
|
|
90
|
+
few; let the grouping follow what the project has rather than imposing it up front. The kinds worth separating are the
|
|
91
|
+
component roles in [react-patterns.md](react-patterns.md): dumb components, layout components, smart containers.
|
|
92
|
+
|
|
93
|
+
A component gets a folder once it has more than one file, with an `index.ts` that only re-exports:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
components/data-table/
|
|
97
|
+
data-table.tsx
|
|
98
|
+
data-table-row.tsx
|
|
99
|
+
data-table.module.css
|
|
100
|
+
index.ts
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Colocate tests, styles, and types with their subject. A file you have to hunt for in a parallel tree gets edited less
|
|
104
|
+
carefully.
|
|
105
|
+
|
|
106
|
+
## Styling
|
|
107
|
+
|
|
108
|
+
**CSS Modules by default**, colocated as `<component>.module.css`. No CSS-in-JS, no utility-class framework. Compose from
|
|
109
|
+
`--imf-ui-*` tokens so custom UI stays consistent with library components and follows the theme; the override contract (CSS
|
|
110
|
+
layers, `data-imf-ui-component`, never the library's generated class names) is in the parent skill.
|
|
111
|
+
|
|
112
|
+
`imf-web-ui-imfusion-frontend-setup` sets up or audits this structure on an ImFusion project.
|
|
113
|
+
|
|
114
|
+
## Testing
|
|
115
|
+
|
|
116
|
+
Test the **decisions**, not the rendering.
|
|
117
|
+
|
|
118
|
+
- **Pure logic gets unit tests** — pricing, permissions, date math, parsing, reducers. These are cheap, fast, and the place
|
|
119
|
+
bugs actually hide.
|
|
120
|
+
- **Presentational components generally don't.** A component that maps props onto web-ui primitives has no logic of its own;
|
|
121
|
+
asserting that it rendered a `<Button>` tests React, not your code.
|
|
122
|
+
- **Behaviour a user performs gets an interaction test** — a form that validates, a flow with steps. Test it through the
|
|
123
|
+
interface the user has (roles, labels, visible text), not through internals.
|
|
124
|
+
- **Extract the decision out of a hook and test that.** A hook whose interesting part is a plain function is easier to test
|
|
125
|
+
as a plain function than through a render harness. A hook that only wraps a browser API has no decision to extract.
|
|
126
|
+
|
|
127
|
+
The measure isn't coverage percentage. It's whether a test failing tells you something you didn't already know.
|
|
128
|
+
|
|
129
|
+
## Formatting and linting
|
|
130
|
+
|
|
131
|
+
Don't argue about it in review — the tooling decides, and it runs before the commit lands. A formatter, a linter, and a
|
|
132
|
+
pre-commit hook wired so none of them is optional. On an ImFusion project, `imf-web-ui-imfusion-frontend-setup` carries the
|
|
133
|
+
baseline and the setup steps.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# React patterns
|
|
2
|
+
|
|
3
|
+
The house defaults for the React code around `@imfusion/web-ui`, in full. The links throughout are for **you, the agent**:
|
|
4
|
+
consult them while building — they are the authoritative source when a case here is ambiguous. Hand them to the human only if
|
|
5
|
+
asked.
|
|
6
|
+
|
|
7
|
+
## Component roles
|
|
8
|
+
|
|
9
|
+
Dumb/smart separation is standard React practice (it traces back to Dan Abramov's
|
|
10
|
+
["Presentational and Container Components"](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0) and
|
|
11
|
+
survives in [Thinking in React](https://react.dev/learn/thinking-in-react)). The house version has three roles:
|
|
12
|
+
|
|
13
|
+
- **Dumb components** own how things _look_. They style and compose library primitives, receive plain data and callbacks as
|
|
14
|
+
props, and know nothing about fetching, routing, or business logic. All non-layout styling lives here — and only here.
|
|
15
|
+
- **Layout components** own _arrangement_ — and nothing else. `Stack`- and `Row`-based wrappers with token gaps, a page grid,
|
|
16
|
+
a section frame. They exist because smart containers are styleless: when a container needs two panels side by side, that
|
|
17
|
+
arrangement is a layout component, not an inline style.
|
|
18
|
+
- **Smart containers** own how things _work_. Routes (or explicit container components) fetch data, hold orchestration logic,
|
|
19
|
+
and wire the other two together. Zero styling — the moment a container wants CSS, extract a layout component.
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
// Dumb — renders what it's given
|
|
23
|
+
function UserCard({ name, role, onEdit }: { name: string; role: string; onEdit: () => void }) {
|
|
24
|
+
return (
|
|
25
|
+
<Card.Root>
|
|
26
|
+
<Card.Content>
|
|
27
|
+
<Typo>{name}</Typo>
|
|
28
|
+
<Chip>{role}</Chip>
|
|
29
|
+
</Card.Content>
|
|
30
|
+
<Card.Footer>
|
|
31
|
+
<Button onClick={onEdit}>Edit</Button>
|
|
32
|
+
</Card.Footer>
|
|
33
|
+
</Card.Root>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Smart — knows where data comes from, renders the dumb component
|
|
38
|
+
function UserCardContainer({ userId }: { userId: string }) {
|
|
39
|
+
const { data } = useUserQuery(userId);
|
|
40
|
+
const openEditor = useEditorNavigation(userId);
|
|
41
|
+
return <UserCard name={data.name} role={data.role} onEdit={openEditor} />;
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Why it matters here: dumb components are the layer where `@imfusion/web-ui` lives. Keeping them free of logic keeps every
|
|
46
|
+
screen restylable, testable with plain props, and resilient to library updates. The one web-ui-specific addition: wrap
|
|
47
|
+
`experimental` components (marked in the identity index) in a dumb component once per app even if you add nothing yet — a
|
|
48
|
+
breaking upstream change then lands in one file instead of every call site.
|
|
49
|
+
|
|
50
|
+
## Compose, don't configure
|
|
51
|
+
|
|
52
|
+
Build screen-level pieces by composing primitives (`Stack`, `Row`, `Card`, your dumb components) rather than growing one
|
|
53
|
+
component with a dozen boolean props. If a component's prop list reads like a settings page, it wanted to be two or three
|
|
54
|
+
components. When state must be shared between siblings, lift it to the nearest common parent —
|
|
55
|
+
[Sharing State Between Components](https://react.dev/learn/sharing-state-between-components) — rather than syncing copies.
|
|
56
|
+
|
|
57
|
+
## Put state where its truth lives
|
|
58
|
+
|
|
59
|
+
Work down this list and stop at the first match:
|
|
60
|
+
|
|
61
|
+
1. **Shareable via URL?** (filters, sort, pagination, active tab) → router search params. Back button and copied links are UX
|
|
62
|
+
features you get for free.
|
|
63
|
+
2. **Comes from an API?** → the data-fetching layer's cache (e.g. TanStack Query). Never copy server data into `useState` —
|
|
64
|
+
that's how stale-UI bugs are born.
|
|
65
|
+
3. **Scoped to a subtree, resets on leave?** (wizard progress) → React context.
|
|
66
|
+
4. **App-wide and persistent?** → a client store, and only now.
|
|
67
|
+
5. **Local to one component?** (input value, open/closed) → `useState`.
|
|
68
|
+
|
|
69
|
+
Most frontends need far less of tier 4 than they think; tiers 1–2 usually dissolve the "we need a store" instinct. For
|
|
70
|
+
structuring the state itself, [Choosing the State Structure](https://react.dev/learn/choosing-the-state-structure) is the
|
|
71
|
+
reference — especially its rules on avoiding redundant and duplicated state.
|
|
72
|
+
|
|
73
|
+
## Effects: last resort, and named
|
|
74
|
+
|
|
75
|
+
Before writing `useEffect`, check: derived values belong in render (or `useMemo`), responses to user actions belong in the
|
|
76
|
+
event handler, and server synchronization belongs in the data-fetching layer. Effects are for synchronizing with systems
|
|
77
|
+
_outside_ React. The definitive catalog of effect misuses — read it before every effect you're tempted to write — is
|
|
78
|
+
[You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect).
|
|
79
|
+
|
|
80
|
+
When an effect is genuinely needed, extract it into a custom hook named for its purpose — `useSyncedScroll`,
|
|
81
|
+
`useDocumentTitle`, `useHotkey` — never an anonymous `useEffect` block inline in a component. The name documents intent, the
|
|
82
|
+
hook isolates the dependency array, and the component body stays declarative. Pattern reference:
|
|
83
|
+
[Reusing Logic with Custom Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks).
|
|
84
|
+
|
|
85
|
+
## Reading list
|
|
86
|
+
|
|
87
|
+
Consult while building; each is the authority for its topic:
|
|
88
|
+
|
|
89
|
+
- [Thinking in React](https://react.dev/learn/thinking-in-react) — decomposition and one-way data flow
|
|
90
|
+
- [Keeping Components Pure](https://react.dev/learn/keeping-components-pure) — why dumb components stay dumb
|
|
91
|
+
- [Choosing the State Structure](https://react.dev/learn/choosing-the-state-structure) — shaping state without duplication
|
|
92
|
+
- [Sharing State Between Components](https://react.dev/learn/sharing-state-between-components) — lifting state
|
|
93
|
+
- [You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect) — the effect misuse catalog
|
|
94
|
+
- [Reusing Logic with Custom Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks) — named effects live here
|