flet-box 1.1.1 → 1.1.3
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/AGENTS_FOR_CODE_GEN.md +284 -0
- package/CONTRIBUTING.md +238 -0
- package/README.md +17 -13
- package/bin/cli.js +88 -1
- package/bin/commands/auditFix.js +181 -27
- package/bin/commands/checkApi.js +14 -3
- package/bin/commands/checkStructure.js +368 -0
- package/bin/commands/checkSyntax.js +293 -0
- package/bin/commands/checkUi.js +666 -14
- package/bin/commands/create.js +18 -1
- package/bin/commands/createBundle.js +20 -8
- package/bin/commands/metrics.js +448 -0
- package/bin/commands/release.js +18 -0
- package/bin/commands/report.js +849 -0
- package/bin/commands/tools.js +76 -0
- package/bin/commands/unpack.js +39 -11
- package/bin/commands/verify.js +879 -0
- package/bin/mcp.js +280 -0
- package/bin/utils/bundleConfig.js +47 -3
- package/bin/utils/contract.js +27 -0
- package/bin/utils/dtsProps.js +5 -1
- package/bin/utils/migrationGuide.js +146 -0
- package/bin/utils/moduleExports.js +9 -5
- package/bin/utils/templates.js +248 -30
- package/bin/utils/toolkit.js +191 -0
- package/docs/README.md +26 -1
- package/docs/arquitectura.md +1 -1
- package/docs/cli/README.md +201 -11
- package/docs/guides/README.md +6 -0
- package/docs/guides/accessibility.md +2 -1
- package/docs/guides/ai-toolkit.md +166 -0
- package/docs/guides/compatibility.md +1 -1
- package/docs/guides/first-app.md +6 -7
- package/docs/guides/migration.md +92 -0
- package/docs/guides/minification-and-protection.md +2 -1
- package/docs/guides/mobile-and-platforms.md +1 -1
- package/docs/guides/online-updates.md +20 -6
- package/docs/server/deployment.md +1 -1
- package/docs/tools/styling.md +2 -2
- package/docs/tools/theme.md +139 -4
- package/docs/widget/Accordion.md +74 -8
- package/docs/widget/AdMob.md +68 -3
- package/docs/widget/AdSense.md +67 -2
- package/docs/widget/AlertDialog.md +1 -1
- package/docs/widget/AnimatedBox.md +80 -39
- package/docs/widget/AnimatedText.md +74 -18
- package/docs/widget/Audio.md +73 -14
- package/docs/widget/Avatar.md +69 -4
- package/docs/widget/Badge.md +68 -3
- package/docs/widget/BottomSheet.md +2 -2
- package/docs/widget/Button.md +69 -3
- package/docs/widget/Card.md +69 -4
- package/docs/widget/Carousel.md +67 -2
- package/docs/widget/Chart.md +67 -2
- package/docs/widget/Checkbox.md +74 -8
- package/docs/widget/Chip.md +68 -3
- package/docs/widget/CircularBar.md +74 -15
- package/docs/widget/CircularChart.md +71 -10
- package/docs/widget/CodeViewer.md +67 -2
- package/docs/widget/Column.md +68 -3
- package/docs/widget/Container.md +68 -3
- package/docs/widget/DataTable.md +67 -2
- package/docs/widget/Divider.md +67 -2
- package/docs/widget/DraggBox.md +67 -2
- package/docs/widget/Dropdown.md +76 -10
- package/docs/widget/DroppBox.md +67 -2
- package/docs/widget/FloatingActionButton.md +68 -3
- package/docs/widget/GridView.md +67 -2
- package/docs/widget/Icon.md +71 -5
- package/docs/widget/Image.md +68 -3
- package/docs/widget/Input.md +68 -3
- package/docs/widget/Inspector.md +2 -2
- package/docs/widget/InstallButton.md +73 -13
- package/docs/widget/ListTile.md +69 -3
- package/docs/widget/ListView.md +67 -2
- package/docs/widget/Maps.md +68 -3
- package/docs/widget/Markdown.md +74 -15
- package/docs/widget/MatrixRain.md +72 -11
- package/docs/widget/Menu.md +209 -0
- package/docs/widget/Modal.md +3 -3
- package/docs/widget/Pagination.md +68 -3
- package/docs/widget/ParallaxBox.md +72 -12
- package/docs/widget/ProgressBar.md +69 -4
- package/docs/widget/QRCode.md +67 -2
- package/docs/widget/README.md +54 -50
- package/docs/widget/Radio.md +76 -10
- package/docs/widget/Rating.md +70 -6
- package/docs/widget/Row.md +68 -3
- package/docs/widget/Skeleton.md +68 -3
- package/docs/widget/Slider.md +75 -9
- package/docs/widget/SnackBar.md +2 -2
- package/docs/widget/Stack.md +68 -3
- package/docs/widget/Stepper.md +68 -3
- package/docs/widget/Switch.md +74 -8
- package/docs/widget/Text.md +71 -4
- package/docs/widget/ThemePicker.md +197 -0
- package/docs/widget/Tooltip.md +72 -7
- package/docs/widget/TreeView.md +70 -10
- package/docs/widget/Video.md +71 -10
- package/package.json +10 -3
- package/skills/fletbox-design/SKILL.md +89 -5
- package/skills/fletbox-motion/SKILL.md +1 -1
- package/skills/fletbox-rules/SKILL.md +56 -4
- package/src/core/runApp.js +12 -0
- package/src/index.d.ts +374 -101
- package/src/index.js +9 -0
- package/src/navigations/AdaptiveScaffold.js +2 -2
- package/src/navigations/BottomNavigation.js +1 -1
- package/src/navigations/Scaffold.js +18 -6
- package/src/tools/useState.js +11 -4
- package/src/utils/colors.js +275 -0
- package/src/utils/errorBoundary.js +288 -0
- package/src/utils/index.js +10 -2
- package/src/utils/stackPosition.js +39 -24
- package/src/utils/syntaxHighlight.js +4 -1
- package/src/utils/themes.js +279 -3
- package/src/widget-factory/addChildren.js +17 -10
- package/src/widget-factory/assignProps.js +18 -6
- package/src/widget-factory/createWidget.js +7 -0
- package/src/widget-factory/deprecations.js +77 -0
- package/src/widget-factory/translateProps.js +11 -1
- package/src/widget-factory/warnings.js +1 -27
- package/src/widget-factory/widgetFactory.js +59 -17
- package/src/widgets/Accordion.js +21 -7
- package/src/widgets/Badge.js +11 -2
- package/src/widgets/Button.js +15 -3
- package/src/widgets/Carousel.js +12 -4
- package/src/widgets/Chart.js +7 -2
- package/src/widgets/Checkbox.js +1 -2
- package/src/widgets/Chip.js +24 -10
- package/src/widgets/CircularBar.js +9 -3
- package/src/widgets/CircularChart.js +5 -3
- package/src/widgets/Container.js +6 -0
- package/src/widgets/Divider.js +3 -1
- package/src/widgets/DraggBox.js +9 -0
- package/src/widgets/Dropdown.js +3 -3
- package/src/widgets/DroppBox.js +5 -0
- package/src/widgets/FloatingActionButton.js +1 -1
- package/src/widgets/Icon.js +5 -1
- package/src/widgets/InstallButton.js +4 -2
- package/src/widgets/Markdown.js +171 -103
- package/src/widgets/Menu.js +340 -0
- package/src/widgets/Modal.js +7 -1
- package/src/widgets/Pagination.js +10 -5
- package/src/widgets/ProgressBar.js +5 -4
- package/src/widgets/QRCode.js +4 -0
- package/src/widgets/Radio.js +1 -2
- package/src/widgets/Rating.js +9 -0
- package/src/widgets/Slider.js +4 -1
- package/src/widgets/SnackBar.js +2 -2
- package/src/widgets/Switch.js +1 -2
- package/src/widgets/Text.js +9 -5
- package/src/widgets/ThemePicker.js +234 -0
- package/src/widgets/TreeView.js +2 -1
- package/src/widgets/index.js +2 -0
- package/docs/CONTRIBUTING.md +0 -156
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# The AI toolkit
|
|
2
|
+
|
|
3
|
+
A FletBox project ships with ten checkers, and each one is right about its own
|
|
4
|
+
slice: the API gate knows the prop is invented, the design gate knows the
|
|
5
|
+
spacing is off-scale, the type gate knows the editor is unhappy. What an
|
|
6
|
+
assistant lacks is not another checker — it is a **map from the question in front
|
|
7
|
+
of it to the command that answers it**, and one report that puts every answer on
|
|
8
|
+
the same page.
|
|
9
|
+
|
|
10
|
+
This chapter is that map. Three commands share one catalogue:
|
|
11
|
+
|
|
12
|
+
| Command | What it is for |
|
|
13
|
+
| --- | --- |
|
|
14
|
+
| `flet-box tools` | the map: every tool, the question it answers, the exact command |
|
|
15
|
+
| `flet-box report` | the diagnosis: every file of *this* project, its verdict, and what to do next |
|
|
16
|
+
| `flet-box mcp` | the same catalogue over MCP stdio, for an assistant with no shell |
|
|
17
|
+
|
|
18
|
+
All three are **read-only**. None of them edits a source, renames a prop, runs a
|
|
19
|
+
repair, builds or publishes. The single file they can produce is the report's
|
|
20
|
+
own HTML page, and only when `--html` asks for it.
|
|
21
|
+
|
|
22
|
+
## The map: `flet-box tools`
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
flet-box tools # the catalogue, printed as a map
|
|
26
|
+
flet-box tools --json # the same catalogue on stdout, for a script
|
|
27
|
+
flet-box tools --emit # write .fletbox/api/tools.json into the project
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Each entry carries the question in the words a person uses when they are stuck,
|
|
31
|
+
the command, its arguments, its output formats, whether it writes anything, and
|
|
32
|
+
the moments it applies. The nine tools are:
|
|
33
|
+
|
|
34
|
+
| id | Question it answers |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| `report` | Where is the work? Which file is wrong, how wrong, what is missing? |
|
|
37
|
+
| `widget_props` | Which props does this widget really accept, with types and deprecations? |
|
|
38
|
+
| `widget_contract` | Do declaration, engine and docs agree — and which props are dead? |
|
|
39
|
+
| `list_widgets` | What exists in this version of the framework? |
|
|
40
|
+
| `check` | Is any widget, prop or import invented? |
|
|
41
|
+
| `design` | Are the tokens, the scale, tap targets, labels and contrast legal? |
|
|
42
|
+
| `syntax` | Does every module parse? |
|
|
43
|
+
| `types` | What does the editor complain about? |
|
|
44
|
+
| `metrics` | Which mistakes keep coming back, and does a rule own each one? |
|
|
45
|
+
|
|
46
|
+
The catalogue is one file of pure data (`bin/utils/toolkit.js`). The CLI prints
|
|
47
|
+
it, the report links every finding back to the tool that proves it, and the MCP
|
|
48
|
+
server exposes the same entries — one source, three mouths. A tool that is
|
|
49
|
+
missing from that file does not exist for any of them, which is what keeps the
|
|
50
|
+
three from drifting apart.
|
|
51
|
+
|
|
52
|
+
## The diagnosis: `flet-box report`
|
|
53
|
+
|
|
54
|
+
`flet-box verify` answers a yes/no question — *may I say this is done* — gate by
|
|
55
|
+
gate, because that is what an exit code needs. An assistant that is **lost**
|
|
56
|
+
needs the other reading of the same facts: not which gate failed, but which file
|
|
57
|
+
is wrong, how wrong, and which one thing to do next.
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
flet-box report # text, in the terminal
|
|
61
|
+
flet-box report --json # the whole report as data
|
|
62
|
+
flet-box report --md # Markdown, to paste or commit
|
|
63
|
+
flet-box report --html # .fletbox/reports/report.html
|
|
64
|
+
flet-box report --html --out /tmp/report.html
|
|
65
|
+
flet-box report --tests # also run the project's own suites
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The command runs the six static gates once — `syntax`, `api`, `types`, `ui`,
|
|
69
|
+
`structure`, `surface` — and then **re-groups their findings by file**, because
|
|
70
|
+
the file is what the reader opens. Three decisions make it a tool rather than
|
|
71
|
+
another number:
|
|
72
|
+
|
|
73
|
+
1. **Every file is named**, clean ones included, with its line count, its
|
|
74
|
+
verdict, and the widgets it imports. A report that lists only the failures
|
|
75
|
+
cannot tell "this file is fine" from "this file was not read".
|
|
76
|
+
2. **Every finding carries a next step and a rule.** A code with no suggestion
|
|
77
|
+
is printed with the tool that proves it instead — "something is wrong
|
|
78
|
+
somewhere" is not a finding.
|
|
79
|
+
3. **It writes no metrics row.** A diagnostic must not change the record it is
|
|
80
|
+
diagnosing; only `verify` owns
|
|
81
|
+
`.fletbox/metrics/generations.md`.
|
|
82
|
+
|
|
83
|
+
The text report reads in this order:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
🩺 FletBox report · my-app · flet-box@1.1.2 · read-only
|
|
87
|
+
|
|
88
|
+
files: 12 clean · 0 with findings
|
|
89
|
+
0 error(s), 0 warning(s) · 24 widgets, 0 with contract gaps
|
|
90
|
+
|
|
91
|
+
✓ syntax clean
|
|
92
|
+
✓ api clean
|
|
93
|
+
✓ types clean
|
|
94
|
+
✓ ui clean
|
|
95
|
+
✓ structure clean
|
|
96
|
+
✓ surface clean
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
When something is wrong, a ranked **next** list follows, one line per finding:
|
|
100
|
+
the file at `line:column`, the code, the message, the fix, and the rule that
|
|
101
|
+
owns it. Then the files that are not clean (with their widgets), any findings
|
|
102
|
+
that name the installed package rather than your code — the framework's own
|
|
103
|
+
debt, kept separate on purpose — the widget contract ledger, and the metrics
|
|
104
|
+
line.
|
|
105
|
+
|
|
106
|
+
The HTML page carries the same report in one self-contained file: inline CSS,
|
|
107
|
+
no `<script>`, no network request. It opens offline, in any browser, and every
|
|
108
|
+
value is escaped, so a report about code is safe to open even when that code
|
|
109
|
+
contains markup.
|
|
110
|
+
|
|
111
|
+
The exit code is `1` when the report has errors, so `flet-box report` can stand
|
|
112
|
+
in for `flet-box verify` in a script that only needs the verdict.
|
|
113
|
+
|
|
114
|
+
## The server: `flet-box mcp`
|
|
115
|
+
|
|
116
|
+
An assistant with no shell cannot run `flet-box report`. `flet-box mcp` serves
|
|
117
|
+
the same catalogue over MCP stdio — newline-delimited JSON-RPC 2.0 on stdin and
|
|
118
|
+
stdout — so the same nine tools are callable without a terminal:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
flet-box mcp
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
It answers `initialize`, `ping`, `tools/list` and `tools/call`. Two request
|
|
125
|
+
lines are enough to see it work:
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"probe","version":"1"}}}
|
|
129
|
+
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"report","arguments":{"root":"."}}}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Every tool result is text (the same text the CLI prints), so an editor or an
|
|
133
|
+
agent reads exactly what a person would read. A missing required argument or a
|
|
134
|
+
directory that does not exist comes back as a result with `isError: true`; an
|
|
135
|
+
unknown tool name is a JSON-RPC error (`-32602`). Neither is a crash, and
|
|
136
|
+
neither can be mistaken for an answer. When stdin closes behind a slow request,
|
|
137
|
+
the server finishes it before exiting — a one-shot client gets its answer.
|
|
138
|
+
|
|
139
|
+
The server **never** calls the logging path: it runs the gates, not `verify`, so
|
|
140
|
+
asking a question through MCP cannot add a row to the metrics log either.
|
|
141
|
+
|
|
142
|
+
## Where to plug it in
|
|
143
|
+
|
|
144
|
+
- In an app's `package.json`, keep the catalogue current next to the API files:
|
|
145
|
+
`flet-box tools --emit` beside `flet-box unpack --all --md`.
|
|
146
|
+
- Commit `.fletbox/api/tools.json`: it is the map an assistant reads when it
|
|
147
|
+
cannot run a command.
|
|
148
|
+
- Treat `.fletbox/reports/` as generated output — a page you open, not a file
|
|
149
|
+
you review. Recreate it with `flet-box report --html`.
|
|
150
|
+
- Register `flet-box mcp` as an MCP server if your editor supports one; the
|
|
151
|
+
project path is the only argument it needs.
|
|
152
|
+
|
|
153
|
+
The loop stays the one the other chapters teach: `flet-box report` to find the
|
|
154
|
+
work, the named command to understand it, `npm run verify` to prove the fix.
|
|
155
|
+
The toolkit only removes the guess about **which** command answers the question
|
|
156
|
+
you have.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Continue reading
|
|
161
|
+
|
|
162
|
+
- **Previous:** [Migrate an app to a newer FletBox](migration.md) — Chapter 14
|
|
163
|
+
- **Next:** [Minify and protect a FletBox build](minification-and-protection.md) — Appendix A
|
|
164
|
+
- **Index:** [Guides index](README.md) · [The FletBox Book](../README.md)
|
|
165
|
+
|
|
166
|
+
You are reading **Chapter 15 · The AI toolkit**.
|
|
@@ -33,7 +33,7 @@ Private browsing, sandboxed iframes, and strict browser policies may disable som
|
|
|
33
33
|
|
|
34
34
|
### Node.js
|
|
35
35
|
|
|
36
|
-
Node.js
|
|
36
|
+
Node.js 20.9 or newer is required for the CLI and build tooling — the floor `package.json` declares in `engines`. FletBox widgets themselves are browser-oriented and should not be rendered directly in a Node process without a DOM environment.
|
|
37
37
|
|
|
38
38
|
## Build targets
|
|
39
39
|
|
package/docs/guides/first-app.md
CHANGED
|
@@ -4,24 +4,23 @@ This page is for **absolute beginners**. You do not need to know any trick — y
|
|
|
4
4
|
|
|
5
5
|
## Before you start
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
You need Node.js 20.9 or newer and one command, because FletBox is published on npm:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
|
|
11
|
-
cd flet-box
|
|
12
|
-
npm link # link the flet-box CLI globally
|
|
10
|
+
npm install -g flet-box # the CLI, once
|
|
13
11
|
```
|
|
14
12
|
|
|
15
|
-
Then create an empty project,
|
|
13
|
+
Then create an empty project, install the framework into it, and start the dev server:
|
|
16
14
|
|
|
17
15
|
```bash
|
|
18
|
-
cd ..
|
|
19
16
|
flet-box create my-app
|
|
20
17
|
cd my-app
|
|
21
|
-
npm
|
|
18
|
+
npm install # flet-box itself, from the registry
|
|
22
19
|
npm run dev
|
|
23
20
|
```
|
|
24
21
|
|
|
22
|
+
If you would rather not install anything globally, `npx flet-box create my-app` does the same thing.
|
|
23
|
+
|
|
25
24
|
Open your browser at `http://localhost:8000`. You should see "Hello FletBox" (or a small welcome screen). If you do, everything is working.
|
|
26
25
|
|
|
27
26
|
## Step 1: the smallest app
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Migrate an app to a newer FletBox
|
|
2
|
+
|
|
3
|
+
> **Level: Intermediate.** Read this page when a FletBox release renamed or removed something your app uses. It follows [Accessibility](accessibility.md) — Chapter 13 — in the book's reading path.
|
|
4
|
+
|
|
5
|
+
FletBox does not break your app on purpose. A prop that gets a better name keeps working under the old one, and the framework keeps both until the next major says otherwise. **But a name you keep writing is a name you keep paying for**: the old spelling disappears at the next major, and the app breaks then, on a machine that is not yours.
|
|
6
|
+
|
|
7
|
+
So this page exists for two moments:
|
|
8
|
+
|
|
9
|
+
- **You are upgrading.** The table below says, prop by prop, what changed and what to write instead.
|
|
10
|
+
- **You are publishing a major.** `flet-box release --major` refuses to publish a version that has no section here — a major is exactly the release where an app has to change something, so the guide has to exist before the release does.
|
|
11
|
+
|
|
12
|
+
## The rule the framework follows
|
|
13
|
+
|
|
14
|
+
A rename in FletBox is always done in this order:
|
|
15
|
+
|
|
16
|
+
1. The new name is declared and documented **first**.
|
|
17
|
+
2. The old name keeps working, marked `@deprecated` in `src/index.d.ts`.
|
|
18
|
+
3. The old name lands in `src/widget-factory/deprecations.js` — the one table the guide, the codemod and the contract test all read.
|
|
19
|
+
4. The old name is listed in the table below, with the version that renamed it.
|
|
20
|
+
5. The old name is removed in the next **major**, and that major's section says so.
|
|
21
|
+
|
|
22
|
+
Nothing here is a breaking change inside the 1.x line. Read the table as a list of names to stop writing, not as a list of things that already broke.
|
|
23
|
+
|
|
24
|
+
## 1.2.0
|
|
25
|
+
|
|
26
|
+
Every old name below still works in 1.2.0. Write the new one.
|
|
27
|
+
|
|
28
|
+
| Widget | From | To | What changed |
|
|
29
|
+
| --- | --- | --- | --- |
|
|
30
|
+
| `Accordion` | `onToggle` | `onPress` | The callback still receives the new expanded state; the name is now the framework-wide one. |
|
|
31
|
+
| `Checkbox` | `onCheck` | `onPress` | The callback still receives the new checked state. |
|
|
32
|
+
| `Dropdown` | `onChange` | `onPress` | The callback still receives the chosen value — and only the value. The second argument the old type promised was never sent. |
|
|
33
|
+
| `Radio` | `onSelect` | `onPress` | The callback still receives the new selected state. |
|
|
34
|
+
| `Slider` | `onChanged` | `onPress` | The callback still receives the new number. |
|
|
35
|
+
| `Switch` | `onToggle` | `onPress` | The callback still receives the new toggled state. |
|
|
36
|
+
| `Tooltip` | `tooltipDelay` | `delay` | Same number, same units: milliseconds. |
|
|
37
|
+
| `Scaffold` | `navSideBar` | `leftNavBar` | The side moved into the name. A call that also says `navSideBarPosition: "right"` becomes `rightNavBar`. |
|
|
38
|
+
| `Scaffold` | `navSideBarWidth` | `leftNavBarWidth` | The old width was accepted and then **ignored** — the panel kept the default width. The new name applies, so the panel finally takes the width you wrote. |
|
|
39
|
+
| `Scaffold` | `navSideBarPosition` | (removed) | The side now lives in the prop name (`leftNavBar` / `rightNavBar`). Nothing reads this prop any more, so the key goes with it. |
|
|
40
|
+
|
|
41
|
+
Two names that look like the ones above are **not** deprecated: `TreeView` still calls its own selection callback `onSelect` and its own expansion callback `onToggle`, because that is what a tree calls them. The rename was about the framework-wide `onPress`, and a tree's own vocabulary is not a candidate for it. `flet-box audit-fix` knows the difference: the table names a widget, not a spelling.
|
|
42
|
+
|
|
43
|
+
## Applying the table
|
|
44
|
+
|
|
45
|
+
The framework ships a codemod for the rows where the answer is mechanical:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npx flet-box audit-fix --only migrations
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
In a project made with `flet-box create` the same command is `npm run fix` (which also repairs imports and mistyped props). The codemod rewrites only the calls it can prove:
|
|
52
|
+
|
|
53
|
+
- the widget has to declare **both** the old name and the new one;
|
|
54
|
+
- the call must not already carry the new name — two callbacks for one event is a decision, not a rename;
|
|
55
|
+
- for `Scaffold`, the call must not already carry a new sidebar prop, because the legacy one is inert there.
|
|
56
|
+
|
|
57
|
+
Anything it cannot prove is printed under **"Left for you to decide"** and left untouched. A codemod that guesses is worse than one that stops: a wrong rename changes the screen silently, and the gate would no longer complain about it.
|
|
58
|
+
|
|
59
|
+
What the codemod will **not** do, on purpose:
|
|
60
|
+
|
|
61
|
+
- it will not change a callback's *signature* — `Dropdown`'s old `onChange(value, label)` promised a second argument that was never sent, and the codemod renames the prop without inventing the argument;
|
|
62
|
+
- it will not touch a call that spreads another object (`{ ...props }`), because the keys inside are not visible;
|
|
63
|
+
- it will not rename anything that is not a FletBox widget call;
|
|
64
|
+
- it will not edit `docs/`, `skills/` or the framework's own sources.
|
|
65
|
+
|
|
66
|
+
Run it, then read the diff:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npx flet-box audit-fix --only migrations
|
|
70
|
+
git diff
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Before you publish a major
|
|
74
|
+
|
|
75
|
+
A major release is the one that removes the old names for good. The framework will not let it through without this page saying what it removes:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
flet-box release --major
|
|
79
|
+
# ❌ the migration guide has no "## 2.0.0" section
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
To publish it, add the section here first — one row per removed or renamed prop, with the version as the heading — and the release goes through. The rows are read by `scripts/check-migrations.mjs`, which also proves that the table above, the deprecated markers in `src/index.d.ts`, the deprecation table in the framework and this codemod all name the same things. A prop that is deprecated in the types but missing from this page fails CI, not the user's app.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Continue reading
|
|
87
|
+
|
|
88
|
+
- **Previous:** [Accessibility](accessibility.md) — Chapter 13
|
|
89
|
+
- **Next:** [The AI toolkit](ai-toolkit.md) — Chapter 15
|
|
90
|
+
- **Index:** [Guides index](README.md) · [The FletBox Book](../README.md)
|
|
91
|
+
|
|
92
|
+
You are reading **Chapter 14 · Migrating to a newer FletBox**.
|
|
@@ -255,6 +255,7 @@ Minification reduces the web payload inside the native application. It does not
|
|
|
255
255
|
|
|
256
256
|
## Continue reading
|
|
257
257
|
|
|
258
|
-
- **Previous:** [
|
|
258
|
+
- **Previous:** [The AI toolkit](ai-toolkit.md) — Chapter 15
|
|
259
259
|
- **Next:** [Build FletBox for mobile and other platforms](mobile-and-platforms.md) — Appendix B
|
|
260
|
+
- **See also:** [Migrate an app to a newer FletBox](migration.md) — Chapter 14
|
|
260
261
|
- **Index:** [Guides index](README.md) · [The FletBox Book](../README.md)
|
|
@@ -26,7 +26,7 @@ Capacitor packages the compiled web application inside a native shell. You can a
|
|
|
26
26
|
|
|
27
27
|
### For every platform
|
|
28
28
|
|
|
29
|
-
- Node.js
|
|
29
|
+
- Node.js 20.9 or newer (the CLI's `engines` floor).
|
|
30
30
|
- npm.
|
|
31
31
|
- A working FletBox project.
|
|
32
32
|
- A production build that creates the directory configured as Capacitor's `webDir`.
|
|
@@ -116,7 +116,7 @@ Every bundle ships a small manifest at `www/config.json`. This is what a running
|
|
|
116
116
|
|
|
117
117
|
`update.url` is resolved automatically, in this order:
|
|
118
118
|
|
|
119
|
-
1. `update.url` in `fletbox.config.json` at your project root (explicit wins).
|
|
119
|
+
1. `update.url` in `fletbox.config.json` at your project root (explicit wins) — unless it is still the scaffold placeholder, which is not a choice. See [The Pages address of a generated app](#the-pages-address-of-a-generated-app).
|
|
120
120
|
2. Derived from the `repository` field of `package.json`, mapped to GitHub Pages: `https://<user>.github.io/<repo>/`.
|
|
121
121
|
3. Derived from the **current project's** git remote `origin` (when it is a GitHub URL). This means `flet-box createBundle` always points at the project it is built in, never at the flet-box framework installed as a dependency.
|
|
122
122
|
4. If none of these exist, OTA is **disabled** and the bundle simply runs what shipped in it.
|
|
@@ -152,10 +152,22 @@ things at once:
|
|
|
152
152
|
- writes that address into the header of the generated `.github/workflows/build.yml`;
|
|
153
153
|
- prints it in the closing summary, with the one-time setting Pages needs.
|
|
154
154
|
|
|
155
|
-
Without `--repo` nothing is invented. `fletbox.config.json` keeps
|
|
156
|
-
placeholder `https://TU-USUARIO.github.io/TU-REPO/`, the workflow
|
|
157
|
-
guess built from the folder name, and the workflow says
|
|
158
|
-
|
|
155
|
+
Without `--repo` nothing is invented at scaffold time. `fletbox.config.json` keeps
|
|
156
|
+
the generic placeholder `https://TU-USUARIO.github.io/TU-REPO/`, the workflow
|
|
157
|
+
header names a guess built from the folder name, and the workflow says in its own
|
|
158
|
+
comment which part of that address still has to be checked by hand.
|
|
159
|
+
|
|
160
|
+
The build does not trust that placeholder. `TU-USUARIO` and `TU-REPO` mean "not
|
|
161
|
+
configured yet", so `flet-box createBundle` falls through to rules 2 and 3 and
|
|
162
|
+
writes the address it derived — and prints that it derived, because a manifest
|
|
163
|
+
pointing at a real site next to a file that still says `TU-REPO` is a half-filled
|
|
164
|
+
config you should either finish or leave alone. A project with no GitHub remote
|
|
165
|
+
yet gets `update.url: null`: OTA is off, the bundle runs what shipped in it, and
|
|
166
|
+
nothing polls a host that does not exist.
|
|
167
|
+
|
|
168
|
+
That matters because the failure used to be silent. The app compares bundle
|
|
169
|
+
**hashes**, and OTA is built never to break the app, so a placeholder URL that
|
|
170
|
+
reached a bundle produced no error, no dialog and no update — ever.
|
|
159
171
|
|
|
160
172
|
**The workflow log is the authority.** The header names the address you typed at
|
|
161
173
|
scaffold time; the `Where this lives` step prints the address the run *actually*
|
|
@@ -200,6 +212,8 @@ Declare those files in `fletbox.config.json` and they become part of every relea
|
|
|
200
212
|
|
|
201
213
|
Globs are relative to the project root and support `**`, `*` and `?`. At build time `flet-box createBundle` copies each match into `www/` at the same path, hashes it, and lists it in the manifest's `assets` array — which is also what your CI publishes to GitHub Pages.
|
|
202
214
|
|
|
215
|
+
A project created by `flet-box create` already declares its own pictures. The scaffold creates `assets/` and puts `assets/logo.png` there, so that folder is where an app keeps its images; the generated `fletbox.config.json` lists `assets/**/*.png`, `.jpg`, `.jpeg`, `.svg` and `.webp`. Without that entry the bundle copied `src/assets` (icons and fonts) and nothing else: the built app served every screen with a broken picture while `npm run dev` — which serves the project root — looked perfect. Other formats, or a file kept outside `assets/`, still need their own glob; a source document parked in `assets/` (the PDF a catalogue was written from) is deliberately not matched.
|
|
216
|
+
|
|
203
217
|
The app then reads them through the loader instead of `fetch`:
|
|
204
218
|
|
|
205
219
|
```js
|
|
@@ -274,7 +288,7 @@ Requirements: a **public** GitHub repository, GitHub Pages enabled (Settings →
|
|
|
274
288
|
| Symptom | Likely cause |
|
|
275
289
|
| --- | --- |
|
|
276
290
|
| Build prints "THIS APP DOES NOT SUPPORT OTA UPDATES" | No public GitHub repo is reachable from the project (no `origin` remote, a non-GitHub remote, or nothing configured). The app still works — it runs the bundle it shipped with. Create the repo, push it, and rebuild. |
|
|
277
|
-
| No dialog, app runs old code | `update.url`
|
|
291
|
+
| No dialog, app runs old code | `update.url` wrong — a real address that is not where the bundle is (the scaffold placeholder is not one of those: the build derives the address and prints it). Or no `v*` tag was published — a normal push updates the site but deliberately leaves the manifest out, so there is nothing to compare against. |
|
|
278
292
|
| `hash mismatch` in console | The published `config.json` and bundle are from different builds. Re-run `flet-box release` so both are published together. |
|
|
279
293
|
| Dialog appears but download fails | The Pages site is not reachable/CORS-blocked from the app origin. Pages serves with permissive headers; check you are not behind a proxy that strips them. |
|
|
280
294
|
| Update applies but reverts on next launch | Cache Storage unavailable (private browsing). The bundle runs once but cannot persist. |
|
|
@@ -99,7 +99,7 @@ Queues also require Redis to coordinate jobs between workers.
|
|
|
99
99
|
|
|
100
100
|
## Production checklist
|
|
101
101
|
|
|
102
|
-
- Use Node.js
|
|
102
|
+
- Use Node.js 22 or newer — the floor `flet-box-server` declares in `engines` (the FletBox CLI itself needs 20.9).
|
|
103
103
|
- Run `npm ci --omit=dev` in deployment.
|
|
104
104
|
- Set a strong `JWT_SECRET`.
|
|
105
105
|
- Remove hardcoded admin credentials.
|
package/docs/tools/styling.md
CHANGED
|
@@ -18,7 +18,7 @@ import { border, margin, padding, gradient, shadow, color } from "flet-box";
|
|
|
18
18
|
| Function | Signature | Returns | Description |
|
|
19
19
|
| --- | --- | --- | --- |
|
|
20
20
|
| `animation` | `animation(name, duration?, timing?, iteration?)` | `string` | CSS `animation` shorthand. `name` can also be an options object (`{ name, duration, timing, delay, iteration, direction, fillMode }`). |
|
|
21
|
-
| `border` | `border(width = 1, style = "solid", color
|
|
21
|
+
| `border` | `border(width = 1, style = "solid", color)` | `string` | CSS `border` shorthand, e.g. `1px solid #e2e8f0`. The colour is required: a border without one is invisible. |
|
|
22
22
|
| `gradient` | `gradient(type, colors, angle = 135)` | `string` | A CSS gradient background. `type` is `"linear"`, `"circle"` (radial), or `"conic"`; `colors` is an array of color strings. |
|
|
23
23
|
| `margin` / `padding` | `margin(value)` / `padding(value)` | `string` | Spacing CSS. `value` can be a number, a string, or `{ all, horizontal, vertical, top, right, bottom, left }`. |
|
|
24
24
|
| `rgba` | `rgba(r, g, b, a = 1)` | `string` | `rgba(r, g, b, a)` with an alpha default of `1`. |
|
|
@@ -29,7 +29,7 @@ import { border, margin, padding, gradient, shadow, color } from "flet-box";
|
|
|
29
29
|
| `flex` | `flex(grow, shrink = 1, basis = "0%")` | `string` | CSS `flex` shorthand. `grow` can also be `{ grow, shrink, basis }`. |
|
|
30
30
|
| `grid` | `grid(options)` | `string` | CSS grid template from `{ columns, rows, gap }`; `gap` can be a number or `{ row, column }`. |
|
|
31
31
|
| `color` | `color(value)` | Color object | A mutable, chainable color. See the Color object below. |
|
|
32
|
-
| `stackPosition` | `stackPosition(widget, props)` | `Widget` |
|
|
32
|
+
| `stackPosition` | `stackPosition(widget, props)` | `Widget` | Anchors a widget absolutely over its positioned ancestor and returns it. Takes `top`/`right`/`bottom`/`left` (numbers go through the `rem` contract), the logical `start`/`end`, and the shorthands `centerX`, `centerY`, `center`, `fullWidth`, `fullHeight`, `full`. A `false` value means "do not apply". |
|
|
33
33
|
| `toREM` / `toPX` | `toREM(value)` / `toPX(value)` | `string \| undefined` | Convert a value between `rem` and `px`. |
|
|
34
34
|
| `setBaseFontSize` | `setBaseFontSize(px)` | `void` | Change the `rem` base (default `16`). |
|
|
35
35
|
| `getBaseFontSize` | `getBaseFontSize()` | `number` | The current `rem` base. |
|
package/docs/tools/theme.md
CHANGED
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
# Themes
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
|
-
FletBox ships a built-in color system: `colors` is the palette of the current theme, `palettes` holds all named palettes, and the theme functions switch between light and dark, subscribe to changes, and follow the system preference. A `ThemeProvider` and the `useTheme()` hook wrap
|
|
4
|
+
FletBox ships a built-in color system: `colors` is the palette of the current theme, `palettes` holds all named palettes, and the theme functions switch between light and dark, subscribe to changes, and follow the system preference. On top of that sit the accent presets — selectable hue families that paint `colors.primary`, its ink and its wash over whichever palette is active — and the theme functions carry them too. A `ThemeProvider` and the `useTheme()` hook wrap all of it for component-based apps.
|
|
5
5
|
|
|
6
6
|
## When to use
|
|
7
7
|
- Style widgets with semantic tokens (`colors.surface`, `colors.primary`, `colors.error`) instead of hard-coded hex values.
|
|
8
8
|
- Add a light/dark toggle that re-colors the whole app instantly.
|
|
9
9
|
- Follow the OS dark/light setting automatically.
|
|
10
|
+
- Let the reader pick the app's color — buttons, icons and plates and all — with [`ThemePicker`](../widget/ThemePicker.md) and `setAccent`.
|
|
10
11
|
|
|
11
12
|
## Import
|
|
12
13
|
|
|
13
14
|
```javascript
|
|
14
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
colors,
|
|
17
|
+
setTheme,
|
|
18
|
+
registerPalette,
|
|
19
|
+
toggleTheme,
|
|
20
|
+
subscribeTheme,
|
|
21
|
+
ThemeProvider,
|
|
22
|
+
registerAccent,
|
|
23
|
+
setAccent,
|
|
24
|
+
applyAccentPreference,
|
|
25
|
+
getAccent,
|
|
26
|
+
getAccentSurfaces,
|
|
27
|
+
listAccents,
|
|
28
|
+
} from "flet-box";
|
|
15
29
|
```
|
|
16
30
|
|
|
17
31
|
## Functions
|
|
@@ -20,6 +34,13 @@ import { colors, setTheme, registerPalette, toggleTheme, subscribeTheme, ThemePr
|
|
|
20
34
|
| --- | --- | --- | --- |
|
|
21
35
|
| `setTheme` | `setTheme('light' \| 'dark')` | `Record<string, string>` | Switch the active palette. |
|
|
22
36
|
| `registerPalette` | `registerPalette(themeName, palette)` | `Record<string, string>` | Merge brand tokens into a named palette and repaint the page. |
|
|
37
|
+
| `registerAccent` | `registerAccent({ id, label?, light, dark })` | `Accent \| null` | Add or replace one selectable accent. Each end of the pair is a hex or a five-stop ramp; `null` means the argument was unusable. |
|
|
38
|
+
| `setAccent` | `setAccent(id \| null, { surfaces? })` | `Record<string, string>` | Lay an accent over the live palette and store the choice. `surfaces: true` paints the page and its panels from the same ramp. `null`, or an id nothing registered, takes it back off. |
|
|
39
|
+
| `applyAccentPreference` | `applyAccentPreference(id, { surfaces? })` | `string \| null` | Paint the app's own brand at boot, and only when the reader has never picked one. Never stores anything. |
|
|
40
|
+
| `getAccent` | `getAccent()` | `string \| null` | The accent id on screen, or `null` while the palette's own `primary` shows. |
|
|
41
|
+
| `getAccentSurfaces` | `getAccentSurfaces()` | `boolean` | Whether the live accent also paints the surfaces, or only the brand. |
|
|
42
|
+
| `listAccents` | `listAccents()` | `Accent[]` | Every selectable family, in registration order — `{ id, label, light, dark }`. |
|
|
43
|
+
| `listAccentRamps` | `listAccentRamps()` | `Array` | The same families with all five stops of each theme beside them, for a swatch that shows the whole ramp. |
|
|
23
44
|
| `getTheme` | `getTheme()` | `string` | The active theme name. |
|
|
24
45
|
| `toggleTheme` | `toggleTheme()` | `string` | Flip light/dark and return the new name. |
|
|
25
46
|
| `getColor` | `getColor(name, opacity?)` | `string` | Resolve a token (optionally with alpha). |
|
|
@@ -27,7 +48,7 @@ import { colors, setTheme, registerPalette, toggleTheme, subscribeTheme, ThemePr
|
|
|
27
48
|
| `applySystemTheme` | `applySystemTheme()` | `string` | Apply light/dark from the OS preference. |
|
|
28
49
|
| `watchSystemTheme` | `watchSystemTheme()` | `() => void` | Follow the OS preference live; returns an unsubscribe function. |
|
|
29
50
|
| `ThemeProvider` | `ThemeProvider({ children, theme? })` | `Widget` | Provide a theme (defaults to system) for a subtree. |
|
|
30
|
-
| `useTheme` | `useTheme()` | object | `{ colors, setTheme, getTheme, toggleTheme, getColor, applySystemTheme }`. |
|
|
51
|
+
| `useTheme` | `useTheme()` | object | `{ colors, setTheme, getTheme, toggleTheme, getColor, applySystemTheme, setAccent, getAccent, listAccents }`. |
|
|
31
52
|
|
|
32
53
|
Plus the constants: `colors` (current palette) and `palettes` (all named palettes, keyed by name).
|
|
33
54
|
|
|
@@ -36,9 +57,10 @@ Plus the constants: `colors` (current palette) and `palettes` (all named palette
|
|
|
36
57
|
Every token is available on `colors`:
|
|
37
58
|
|
|
38
59
|
- **Brand:** `primary`, `secondary`, `info`, `success`, `warning`, `danger`, `error`.
|
|
60
|
+
- **On a fill:** `onPrimary` — the ink the theme puts on a `primary` plate. Ask the theme for it; a literal `#ffffff` is a bug the moment the reader picks a pale accent.
|
|
39
61
|
- **Surfaces:** `background`, `surface`, `card`, `overlay`.
|
|
40
62
|
- **Text:** `text`, `textSecondary`, `textDisabled`, `divider`, `border`, `shadow`.
|
|
41
|
-
- **Tints:** `gray50` … `gray900`, plus the soft backgrounds `errorBg`, `successBg`, `warningBg`, `infoBg`.
|
|
63
|
+
- **Tints:** `gray50` … `gray900`, plus the soft backgrounds `primaryBg`, `errorBg`, `successBg`, `warningBg`, `infoBg`.
|
|
42
64
|
- **Glass:** `glass` (the translucent surface) and `glassBorder` (its rim), meant to pair with `backdropFilter`. Light is white glass, dark is a slate pane with a light rim.
|
|
43
65
|
|
|
44
66
|
The `dark` palette overrides the surface/text tokens; `gray*` and brand colors also shift to keep contrast. Read [the palette walkthrough](../guides/utilities.md) for usage patterns.
|
|
@@ -80,6 +102,118 @@ it at the bottom of `src/database/themes.js` — the same file that declares
|
|
|
80
102
|
`lightColors` and `darkColors` — so a project that edits its brand colors is
|
|
81
103
|
already correct without touching `src/app.js`.
|
|
82
104
|
|
|
105
|
+
## Accent presets: `setAccent`
|
|
106
|
+
|
|
107
|
+
`registerPalette` says what a theme is. An **accent** is what the reader picks
|
|
108
|
+
inside it: one hue family painted over the palette's `primary`, its ink and its
|
|
109
|
+
wash, so every button, icon and selected plate on the screen moves together.
|
|
110
|
+
Twelve families ship (`listAccents()`), and each is a *pair* — the stop that hue
|
|
111
|
+
wears on a light page and the stop it wears on a dark one — which is why a
|
|
112
|
+
[`ThemePicker`](../widget/ThemePicker.md) swatch is split down the middle.
|
|
113
|
+
Worn with `surfaces: true`, the same family paints the page and its panels too,
|
|
114
|
+
and the picker is choosing a **theme** rather than a brand color; see
|
|
115
|
+
[Worn as a theme](#worn-as-a-theme) below.
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
import { ThemePicker, applyAccentPreference, listAccents } from "flet-box";
|
|
119
|
+
|
|
120
|
+
// At boot: the app's own brand, and only if the reader has never chosen.
|
|
121
|
+
applyAccentPreference("teal");
|
|
122
|
+
|
|
123
|
+
// In a settings sheet: the reader's choice, which paints and persists.
|
|
124
|
+
ThemePicker({
|
|
125
|
+
accents: listAccents().filter((a) => ["teal", "blue", "rose", "orange"].includes(a.id)),
|
|
126
|
+
size: 48,
|
|
127
|
+
onChange: (id) => console.log("accent:", id),
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
What it does:
|
|
132
|
+
|
|
133
|
+
- **It is a layer, not a second palette.** The one repaint funnel lays the
|
|
134
|
+
accent's `primary`, `onPrimary` and `primaryBg` over the theme's own tokens, so
|
|
135
|
+
a toggle, an OS flip or a `setTheme` from anywhere keeps the choice instead of
|
|
136
|
+
writing the brand back over it. The `--color-*` properties and the PWA status
|
|
137
|
+
bar move with it because they read the same tokens. Worn with `surfaces: true`
|
|
138
|
+
the same funnel lays five more tokens down — `background`, `surface`, `card`,
|
|
139
|
+
`border`, `divider` — and nothing else changes hands.
|
|
140
|
+
- **`setAccent` is the reader's verb, `applyAccentPreference` is the app's.** The
|
|
141
|
+
first stores `"fletbox-accent"`, so the picker opens on the answer the reader
|
|
142
|
+
gave; the second paints without storing, so it is a default the reader can
|
|
143
|
+
overrule and never a choice written into their name. An unknown id changes
|
|
144
|
+
nothing, and `setAccent(null)` gives the palette its own brand back.
|
|
145
|
+
- **A created app opens on `neutral`.** `flet-box create` writes
|
|
146
|
+
`applyAccentPreference("neutral")` at the bottom of `src/database/themes.js`,
|
|
147
|
+
beside the `registerPalette` calls that carry the same three values, so a new
|
|
148
|
+
project reads as paper and type rather than as somebody else's brand: the
|
|
149
|
+
accent is the page's own ink, near-black on a light page and near-white on a
|
|
150
|
+
dark one. The first thing a designer changes is that id — and the Preferences
|
|
151
|
+
screen already ships a `ThemePicker`, so a reader can change it without them.
|
|
152
|
+
- **A family is measured before it ships.** Each pair clears the numbers a
|
|
153
|
+
hand-built palette has to as well: white or near-black words on the accent at
|
|
154
|
+
4.5:1, the accent as text on the page at 4.5:1 and as a glyph on a card at 3:1,
|
|
155
|
+
page ink, secondary ink and the accent itself on its wash at 4.5:1, and the
|
|
156
|
+
wash lifting off the background by 1.04 to 1.5. `#3b82f6` fails the first one —
|
|
157
|
+
white words measure 3.15:1 on it — which is why an accent is a stop on a ramp
|
|
158
|
+
and never the middle of one.
|
|
159
|
+
- **A brand the list lacks is registered, not typed.**
|
|
160
|
+
`registerAccent` takes an `id`, a `label` and a `light`/`dark` pair: one hex
|
|
161
|
+
each, and the wash and `colors.onPrimary` are derived, or a five-stop ramp whose
|
|
162
|
+
stop 0 (light) and stop 1 (dark) are the ones painted. The call belongs in
|
|
163
|
+
`src/database/themes.js`, beside the palette it feeds.
|
|
164
|
+
|
|
165
|
+
### Worn as a theme
|
|
166
|
+
|
|
167
|
+
`setAccent(id, { surfaces: true })` is how an app lets the reader pick the color
|
|
168
|
+
of the *page* and not only of its buttons. Five more tokens arrive off the same
|
|
169
|
+
five stops the swatch paints, in the order a designer would build them by hand:
|
|
170
|
+
|
|
171
|
+
| Token | Light, from the ramp | Dark, from the ramp |
|
|
172
|
+
| --- | --- | --- |
|
|
173
|
+
| `background` | `ramp[4]` — the palest stop | `ramp[3]` scaled to 10% |
|
|
174
|
+
| `surface` | `ramp[3]` — the measured wash stop | `ramp[3]` scaled to 18% |
|
|
175
|
+
| `card` | `#ffffff`, the way it lifts off `#fafafa` today | `ramp[3]` scaled to 28% |
|
|
176
|
+
| `border`, `divider` | `ramp[3]` darkened 6% | `ramp[3]` scaled to 45% |
|
|
177
|
+
|
|
178
|
+
Teal, measured: a light page `#f0fdfa` with `#ccfbf1` panels, white cards and
|
|
179
|
+
`#c0ece3` hairlines; a dark page `#010c0b` with `#031514` panels, `#04211f`
|
|
180
|
+
cards and `#073531` hairlines. `applyAccentPreference("teal", { surfaces: true })`
|
|
181
|
+
boots an app in that theme for a reader who has never chosen.
|
|
182
|
+
|
|
183
|
+
Three rules the derivation keeps, and every one of them is measured by
|
|
184
|
+
`tests/regression.test.js` ("an accent worn as a theme keeps every ink on the
|
|
185
|
+
surfaces it paints") for all twelve families in both themes:
|
|
186
|
+
|
|
187
|
+
- **Only the surfaces move.** `text`, `textSecondary`, `textDisabled`, the brand
|
|
188
|
+
trio and every semantic signal — `success`, `danger`, their plates and alert
|
|
189
|
+
tints — answer the same values they had worn as a brand-only accent. The neutral
|
|
190
|
+
ink scale was measured against a near-white and a near-black page, and a tinted
|
|
191
|
+
stop of a saturated family sits within a few percent of both, so the text needs
|
|
192
|
+
re-measuring rather than re-deriving; and a tinted theme still has to be able to
|
|
193
|
+
say "this failed" in a hue that is not its own.
|
|
194
|
+
- **Dark scales toward black, never toward white.** Multiplying a channel keeps the
|
|
195
|
+
hue exactly; mixing toward white washes it out, and `#042f2e` stops reading as
|
|
196
|
+
teal at all. The steps also have to be *visible*, and a WCAG ratio cannot see
|
|
197
|
+
them — near black, every pair compresses to about 1.0 — so the three dark depths
|
|
198
|
+
are separated in CIELAB `L*`: blue's land at 2.3, 4.5 and 8.6, which is the range
|
|
199
|
+
the shipped neutral dark palette already occupies (`#09090b` at 2.5, `#18181b` at
|
|
200
|
+
8.4).
|
|
201
|
+
- **The plate keeps its own stop.** `primaryBg` still comes from the wash stop the
|
|
202
|
+
family was measured against, and on a light page that is now also the panel. A
|
|
203
|
+
selected row inside a panel is therefore the same color as its container, exactly
|
|
204
|
+
as it is in the neutral palette, where `#f4f4f5` sits on `#fafafa` at 1.05 — the
|
|
205
|
+
one place a tinted theme buys its tint by spending a layer.
|
|
206
|
+
|
|
207
|
+
An accent registered as a bare pair of hexes has no lightness to walk, so it paints
|
|
208
|
+
brand only: `{ surfaces: true }` is accepted and ignored, and the surfaces keep the
|
|
209
|
+
palette's own values.
|
|
210
|
+
|
|
211
|
+
The choice rides in one stored string — `"teal"` for a brand, `"teal+surfaces"`
|
|
212
|
+
for a theme — which is why `getAccent()` and `getAccentSurfaces()` answer together.
|
|
213
|
+
A bare id is what every app wrote before surfaces existed, so it still parses; and
|
|
214
|
+
a picker needs both answers, because the family already on screen means two
|
|
215
|
+
different things and moving between them is a repaint, not a no-op.
|
|
216
|
+
|
|
83
217
|
## Examples
|
|
84
218
|
|
|
85
219
|
### Everyday example
|
|
@@ -131,6 +265,7 @@ runApp(ThemeProvider({ children: ThemedApp() }), "root");
|
|
|
131
265
|
- [Device & environment](device.md) — previous chapter.
|
|
132
266
|
- [Markdown, code & HTML](markdown.md) — next chapter.
|
|
133
267
|
- [Colors and utilities guide](../guides/utilities.md) — palette walkthrough.
|
|
268
|
+
- [ThemePicker](../widget/ThemePicker.md) — the swatch grid that calls `setAccent`.
|
|
134
269
|
|
|
135
270
|
---
|
|
136
271
|
|