@imfusion/web-ui 0.11.1-dev.55.gecb71e07 → 0.11.1-dev.57.g3cec18fa
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/CHANGELOG.md +6 -0
- package/dist/components/rich-link/rich-link.d.ts +1 -1
- package/dist/index.js +1 -2
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/docgen/doc.gen.json +1 -1
- package/src/llms/skills/imf-web-ui-conventions/templates/AUDIT_CHECKLIST.md +15 -1
- package/src/llms/skills/imf-web-ui-conventions/topics/react/composition.md +54 -0
- package/src/llms/skills/imf-web-ui-conventions/topics/react/effects.md +59 -0
- package/src/llms/skills/imf-web-ui-conventions/topics/react/state.md +41 -0
- package/src/llms/skills/imf-web-ui-conventions/topics/react.md +7 -60
- package/src/llms/skills/imf-web-ui-conventions/topics/validation.md +2 -1
package/package.json
CHANGED
package/src/docgen/doc.gen.json
CHANGED
|
@@ -9350,7 +9350,7 @@
|
|
|
9350
9350
|
"required": false,
|
|
9351
9351
|
"type": "ReactNode",
|
|
9352
9352
|
"defaultValue": null,
|
|
9353
|
-
"description": "Second line under the label.
|
|
9353
|
+
"description": "Second line under the label."
|
|
9354
9354
|
},
|
|
9355
9355
|
{
|
|
9356
9356
|
"name": "endGlyph",
|
|
@@ -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
|
|
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
|
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
@@ -59,4 +59,5 @@ Use `.catch()` for malformed values that can safely fall back. Use `.default()`
|
|
|
59
59
|
|
|
60
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
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.
|
|
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).
|