flet-box 1.1.2 → 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 +2 -2
- 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 +14 -0
- package/bin/commands/createBundle.js +2 -2
- 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/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 +225 -18
- package/bin/utils/toolkit.js +191 -0
- package/docs/README.md +5 -1
- package/docs/arquitectura.md +1 -1
- package/docs/cli/README.md +168 -2
- 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/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 +2 -0
- 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
package/src/index.js
CHANGED
|
@@ -146,6 +146,13 @@ export {
|
|
|
146
146
|
colors,
|
|
147
147
|
setTheme,
|
|
148
148
|
registerPalette,
|
|
149
|
+
registerAccent,
|
|
150
|
+
setAccent,
|
|
151
|
+
applyAccentPreference,
|
|
152
|
+
getAccent,
|
|
153
|
+
getAccentSurfaces,
|
|
154
|
+
listAccents,
|
|
155
|
+
listAccentRamps,
|
|
149
156
|
getTheme,
|
|
150
157
|
toggleTheme,
|
|
151
158
|
subscribeTheme,
|
|
@@ -225,6 +232,7 @@ export {
|
|
|
225
232
|
AlertDialog,
|
|
226
233
|
FloatingActionButton,
|
|
227
234
|
Stepper,
|
|
235
|
+
ThemePicker,
|
|
228
236
|
Skeleton,
|
|
229
237
|
CodeViewer,
|
|
230
238
|
Inspector,
|
|
@@ -240,6 +248,7 @@ export {
|
|
|
240
248
|
DataTable,
|
|
241
249
|
Carousel,
|
|
242
250
|
Tooltip,
|
|
251
|
+
Menu,
|
|
243
252
|
InstallButton,
|
|
244
253
|
Video,
|
|
245
254
|
Audio,
|
|
@@ -133,9 +133,9 @@ export const AdaptiveScaffold = (props) => {
|
|
|
133
133
|
widthCollapsed: 50,
|
|
134
134
|
bgColor: colors.surface,
|
|
135
135
|
// FIX: CollapsibleSideBar aplica `borderRight` por defecto (default
|
|
136
|
-
//
|
|
136
|
+
// a right sidebar would end up with a border that was
|
|
137
137
|
// con doble borde: `borderRight` (heredado del default) + `borderLeft`
|
|
138
|
-
// (
|
|
138
|
+
// (the one we requested here). We force `borderRight: "none"` so only
|
|
139
139
|
// tenga el borde izquierdo (el que da al contenido central).
|
|
140
140
|
borderRight: "none",
|
|
141
141
|
borderLeft: `1px solid ${colors.border}`,
|
|
@@ -238,7 +238,7 @@ export const BottomNavigation = (props) => {
|
|
|
238
238
|
if (showLabels && item.label) {
|
|
239
239
|
// FIX: `value` es una prop `special` que `applySpecial` solo maneja
|
|
240
240
|
// en <input>/<textarea>. En un <span> no hace nada → el label quedaba
|
|
241
|
-
//
|
|
241
|
+
// empty. The correct prop for text content is `text`.
|
|
242
242
|
labelWidget = Text({
|
|
243
243
|
text: item.label,
|
|
244
244
|
size: 12,
|
|
@@ -16,7 +16,7 @@ import { initRouter, getCurrentRoute, subscribe } from "./Router.js";
|
|
|
16
16
|
* @property {HTMLElement|Function} [rightNavBar] - Persistent right sidebar widget or factory function.
|
|
17
17
|
* @property {number} [rightNavBarWidth=260] - Width of the right sidebar in pixels.
|
|
18
18
|
* @property {HTMLElement|Function} [navSideBar] - Legacy alias for leftNavBar (deprecated).
|
|
19
|
-
* @property {number} [navSideBarWidth=260] -
|
|
19
|
+
* @property {number} [navSideBarWidth=260] - Width of the legacy navSideBar, on whichever side navSideBarPosition picks.
|
|
20
20
|
* @property {"left"|"right"} [navSideBarPosition="left"] - Legacy: which side navSideBar renders on.
|
|
21
21
|
* @property {Object} [routes] - Route map object for router mode (path → component).
|
|
22
22
|
* @property {string} [basePath] - Deployment prefix, e.g. "/flet-box-docs". Auto-detected when omitted.
|
|
@@ -94,14 +94,20 @@ export const Scaffold = (props) => {
|
|
|
94
94
|
initRouter(routesConfig, currentUrl, { base: basePath });
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
// Resolve the legacy navSideBar prop to the appropriate side
|
|
97
|
+
// Resolve the legacy navSideBar prop to the appropriate side, and carry its
|
|
98
|
+
// own width with it: reading the position but not the width made
|
|
99
|
+
// `navSideBarWidth` a documented no-op that always painted 260.
|
|
98
100
|
let finalLeftNavBar = leftNavBar;
|
|
99
101
|
let finalRightNavBar = rightNavBar;
|
|
102
|
+
let finalLeftWidth = leftNavBarWidth;
|
|
103
|
+
let finalRightWidth = rightNavBarWidth;
|
|
100
104
|
if (navSideBar && !leftNavBar && !rightNavBar) {
|
|
101
105
|
if (navSideBarPosition === "right") {
|
|
102
106
|
finalRightNavBar = navSideBar;
|
|
107
|
+
finalRightWidth = navSideBarWidth;
|
|
103
108
|
} else {
|
|
104
109
|
finalLeftNavBar = navSideBar;
|
|
110
|
+
finalLeftWidth = navSideBarWidth;
|
|
105
111
|
}
|
|
106
112
|
}
|
|
107
113
|
|
|
@@ -223,7 +229,13 @@ export const Scaffold = (props) => {
|
|
|
223
229
|
// the release of the handler it adds, which a hand-written
|
|
224
230
|
// `addEventListener` would leak.
|
|
225
231
|
menuIcon.removeAttribute("aria-hidden");
|
|
226
|
-
|
|
232
|
+
// The app's name wins. This is a fallback for an icon nobody named —
|
|
233
|
+
// not a rename — and the shell cannot know the app's language, so
|
|
234
|
+
// writing over an explicit label turned "Open the menu" into
|
|
235
|
+
// "Open menu" for every reader on a Spanish screen.
|
|
236
|
+
if (!menuIcon.hasAttribute("aria-label")) {
|
|
237
|
+
menuIcon.setAttribute("aria-label", "Open menu");
|
|
238
|
+
}
|
|
227
239
|
applyEffects(menuIcon);
|
|
228
240
|
}
|
|
229
241
|
}
|
|
@@ -295,7 +307,7 @@ export const Scaffold = (props) => {
|
|
|
295
307
|
if (widget) {
|
|
296
308
|
leftNavBarContainer.appendChild(widget);
|
|
297
309
|
if (mainContentContainer)
|
|
298
|
-
mainContentContainer.style.width = `calc(100% - ${toREM(
|
|
310
|
+
mainContentContainer.style.width = `calc(100% - ${toREM(finalLeftWidth)})`;
|
|
299
311
|
}
|
|
300
312
|
};
|
|
301
313
|
|
|
@@ -409,7 +421,7 @@ export const Scaffold = (props) => {
|
|
|
409
421
|
|
|
410
422
|
if (finalLeftNavBar) {
|
|
411
423
|
leftNavBarContainer = document.createElement("div");
|
|
412
|
-
leftNavBarContainer.style.width = toREM(
|
|
424
|
+
leftNavBarContainer.style.width = toREM(finalLeftWidth);
|
|
413
425
|
leftNavBarContainer.style.flexShrink = "0";
|
|
414
426
|
leftNavBarContainer.style.overflow = "auto";
|
|
415
427
|
leftNavBarContainer.style.height = "100%";
|
|
@@ -429,7 +441,7 @@ export const Scaffold = (props) => {
|
|
|
429
441
|
|
|
430
442
|
if (finalRightNavBar) {
|
|
431
443
|
rightNavBarContainer = document.createElement("div");
|
|
432
|
-
rightNavBarContainer.style.width = toREM(
|
|
444
|
+
rightNavBarContainer.style.width = toREM(finalRightWidth);
|
|
433
445
|
rightNavBarContainer.style.flexShrink = "0";
|
|
434
446
|
rightNavBarContainer.style.overflow = "auto";
|
|
435
447
|
rightNavBarContainer.style.height = "100%";
|
package/src/tools/useState.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/tools/useState.js
|
|
2
|
-
import { saveRam, getRam, subscribeRam } from "../services/RamStore.js";
|
|
2
|
+
import { saveRam, getRam, subscribeRam, hasRam } from "../services/RamStore.js";
|
|
3
3
|
import {
|
|
4
4
|
createReactiveValue,
|
|
5
5
|
isStructuralRead,
|
|
@@ -57,6 +57,10 @@ const ensureRamDispatcher = () => {
|
|
|
57
57
|
ramDispatcherReady = true;
|
|
58
58
|
|
|
59
59
|
subscribeRam((changedKey, newValue, oldValue) => {
|
|
60
|
+
// A save that stores the value the key already had is not a change. Patching
|
|
61
|
+
// every bound widget and re-running a structural render for it is how a
|
|
62
|
+
// redundant `saveRam(key, same)` became an endless rebuild loop.
|
|
63
|
+
if (changedKey !== null && newValue === oldValue) return;
|
|
60
64
|
const callbacks = subscribers.get(changedKey);
|
|
61
65
|
// A key nobody ever passed to `useState` has no listeners: ignore it.
|
|
62
66
|
if (!callbacks) return;
|
|
@@ -157,9 +161,12 @@ export const registerWidget = (key, widget, propName) => {
|
|
|
157
161
|
* onCount((newVal) => console.log('Counter changed to', newVal));
|
|
158
162
|
*/
|
|
159
163
|
export const useState = (key, initialState, widget = null, propName = null) => {
|
|
160
|
-
// Initialize the state in RamStore if it does not exist yet
|
|
161
|
-
|
|
162
|
-
|
|
164
|
+
// Initialize the state in RamStore if it does not exist yet. `hasRam` is the
|
|
165
|
+
// test, not `getRam`: that reader answers `null` both for a missing key and
|
|
166
|
+
// for a key whose value IS null, so a `null` state was re-seeded on every
|
|
167
|
+
// construction — and when the component had read it structurally, the save
|
|
168
|
+
// re-rendered the app, which re-seeded it again, forever.
|
|
169
|
+
if (!hasRam(key)) {
|
|
163
170
|
saveRam(
|
|
164
171
|
key,
|
|
165
172
|
typeof initialState === "function" ? initialState() : initialState,
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// src/utils/colors.js
|
|
2
|
+
// The framework's selectable accents: ten hue families an app can offer a reader
|
|
3
|
+
// without designing a palette of its own.
|
|
4
|
+
//
|
|
5
|
+
// This file is a token file (`checkUi`'s `isTokenFile`), which is why the hexes
|
|
6
|
+
// live here and not in the widget that paints them: an app that writes
|
|
7
|
+
// `color: "#0f766e"` has hard-coded a brand, and the clean-UI gate says so. A
|
|
8
|
+
// preset table is the same information in the one place the theme can move it.
|
|
9
|
+
//
|
|
10
|
+
// ## A family is a ramp, and the accent is a stop on it
|
|
11
|
+
//
|
|
12
|
+
// Each family carries five stops per theme, dark-to-light in `light` and
|
|
13
|
+
// light-to-dark in `dark`. The accent is never the same index in both themes:
|
|
14
|
+
//
|
|
15
|
+
// light theme → the deepest stop (index 0), which carries white words
|
|
16
|
+
// dark theme → the second stop (index 1), which carries the page's own ink
|
|
17
|
+
//
|
|
18
|
+
// That is the design skill's pair rule (35) — lift the lightness and drop the
|
|
19
|
+
// saturation going dark — applied to a brand instead of a surface. The middle
|
|
20
|
+
// stops of a scale are the ones designers reach for in a mockup and the ones
|
|
21
|
+
// that fail here: #3b82f6 puts white at 3.15:1 and #eab308 reads on a white page
|
|
22
|
+
// at 1.88:1, so a "blue button" copied straight from a palette card is either an
|
|
23
|
+
// unreadable label or an invisible link. Index 0 and index 1 are what survives.
|
|
24
|
+
//
|
|
25
|
+
// ## The veto
|
|
26
|
+
//
|
|
27
|
+
// Seven numbers decide whether a stop ships, in each theme, and the guard in
|
|
28
|
+
// `tests/regression.test.js` re-measures all of them on every run:
|
|
29
|
+
//
|
|
30
|
+
// 1. the accent's own ink on the accent ≥ 4.5 (a button label)
|
|
31
|
+
// 2. the accent read as words on the page ≥ 4.5 (a link, a tab)
|
|
32
|
+
// 3. the accent read as a glyph on a card ≥ 3 (an icon, a rim)
|
|
33
|
+
// 4. the page's ink on the family's wash ≥ 4.5 (a selected row)
|
|
34
|
+
// 5. the page's secondary ink on that wash ≥ 4.5 (its caption)
|
|
35
|
+
// 6. the accent on its own wash ≥ 4.5 (a chip)
|
|
36
|
+
// 7. the wash against the page 1.04–1.5 (a wash, not a plate)
|
|
37
|
+
//
|
|
38
|
+
// Number 7 is why the band is asymmetric in practice: on a white page a hue reads
|
|
39
|
+
// as a tint at 1.10, but on a black page the same family needs up to 1.48 of lift
|
|
40
|
+
// before the eye calls it coloured at all. Both ends of that band are real; a wash
|
|
41
|
+
// over 1.5 stops being a wash and starts competing with a card.
|
|
42
|
+
//
|
|
43
|
+
// Number 6 is the one that bites, and it is what a *predefined* set is for.
|
|
44
|
+
// Yellow's deep stop #a16207 sits 4.42:1 on its own wash #fef3c7 — a chip
|
|
45
|
+
// labelled in yellow fails AA by 0.08 — so that family takes the scale's next step
|
|
46
|
+
// down. Six more families were measured out of this table for the other failure a
|
|
47
|
+
// set can have: marine, violet, coral and amber each duplicate another family's
|
|
48
|
+
// accent in one of the two themes, and a picker with two squares of the same color
|
|
49
|
+
// is not a choice. Adding a family means passing all seven, in both themes, and
|
|
50
|
+
// landing somewhere a neighbour already does not.
|
|
51
|
+
|
|
52
|
+
import { color } from "../tools/color.js";
|
|
53
|
+
import { readableInk } from "./readableInk.js";
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The ink that reads on an accent plate.
|
|
57
|
+
*
|
|
58
|
+
* `readableInk` answers pure black or white; a bright plate in the dark theme is
|
|
59
|
+
* labelled with the dark palette's own page ink instead, because that is what the
|
|
60
|
+
* framework already does for its near-white `primary` and a second black in the
|
|
61
|
+
* system would be one more value to keep in sync.
|
|
62
|
+
*
|
|
63
|
+
* @param {string} primary - The accent's hex.
|
|
64
|
+
* @returns {string} `#ffffff` or `#09090b`.
|
|
65
|
+
*/
|
|
66
|
+
export const onAccent = (primary) =>
|
|
67
|
+
readableInk(primary, "#ffffff") === "#ffffff"
|
|
68
|
+
? "#ffffff"
|
|
69
|
+
: "#09090b";
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Which stop of a family is the accent, and which is its wash, per theme.
|
|
73
|
+
* `light` counts from the deep end and `dark` from the bright end, so index 0 is
|
|
74
|
+
* the deepest stop of one and the brightest of the other.
|
|
75
|
+
*/
|
|
76
|
+
const STOPS = {
|
|
77
|
+
light: { primary: 0, tint: 3 },
|
|
78
|
+
dark: { primary: 1, tint: 4 },
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// How far a wash is pushed from an accent that arrived without a ramp — a brand
|
|
82
|
+
// registered as a pair of hexes by an app that has a color and not a scale. Light
|
|
83
|
+
// lifts it almost to the page, dark drops it almost to the page; both measure
|
|
84
|
+
// 1.04–1.35 against their own background, which is the band a wash has to live
|
|
85
|
+
// in (deep enough to read as a plate, pale enough not to compete with one).
|
|
86
|
+
const WASH_LIGHTEN = 90;
|
|
87
|
+
const WASH_DARKEN = 82;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The families, in the order a picker should show them: the neutral one first,
|
|
91
|
+
* then the cool end of the wheel, then the warm, then the rose-purples that
|
|
92
|
+
* close it. `label` is the control's accessible name.
|
|
93
|
+
*
|
|
94
|
+
* @type {Array<{id: string, label: string, light: string[], dark: string[]}>}
|
|
95
|
+
*/
|
|
96
|
+
export const ACCENT_FAMILIES = [
|
|
97
|
+
{
|
|
98
|
+
id: "neutral",
|
|
99
|
+
label: "Neutral",
|
|
100
|
+
light: ["#0f172a", "#475569", "#94a3b8", "#e2e8f0", "#f8fafc"],
|
|
101
|
+
dark: ["#f8fafc", "#cbd5e1", "#64748b", "#334155", "#0f172a"],
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: "blue",
|
|
105
|
+
label: "Blue",
|
|
106
|
+
light: ["#1e3a8a", "#3b82f6", "#93c5fd", "#dbeafe", "#eff6ff"],
|
|
107
|
+
dark: ["#bfdbfe", "#60a5fa", "#3b82f6", "#1d4ed8", "#0b1220"],
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: "indigo",
|
|
111
|
+
label: "Indigo",
|
|
112
|
+
light: ["#3730a3", "#6366f1", "#a5b4fc", "#e0e7ff", "#eef2ff"],
|
|
113
|
+
dark: ["#a5b4fc", "#818cf8", "#6366f1", "#3730a3", "#1e1b4b"],
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: "purple",
|
|
117
|
+
label: "Purple",
|
|
118
|
+
light: ["#6d28d9", "#a855f7", "#c084fc", "#ede9fe", "#f5f3ff"],
|
|
119
|
+
dark: ["#ddd6fe", "#a78bfa", "#8b5cf6", "#6d28d9", "#2e1065"],
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
id: "pink",
|
|
123
|
+
label: "Pink",
|
|
124
|
+
light: ["#be185d", "#ec4899", "#f9a8d4", "#fce7f3", "#fdf2f8"],
|
|
125
|
+
dark: ["#f9a8d4", "#f472b6", "#ec4899", "#be185d", "#500724"],
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: "rose",
|
|
129
|
+
label: "Rose",
|
|
130
|
+
light: ["#9f1239", "#f43f5e", "#fda4af", "#ffe4e6", "#fff1f2"],
|
|
131
|
+
dark: ["#fecdd3", "#fb7185", "#f43f5e", "#be123c", "#4c0519"],
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: "red",
|
|
135
|
+
label: "Red",
|
|
136
|
+
light: ["#991b1b", "#ef4444", "#fca5a5", "#fee2e2", "#fef2f2"],
|
|
137
|
+
dark: ["#fecaca", "#f87171", "#ef4444", "#b91c1c", "#450a0a"],
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
id: "orange",
|
|
141
|
+
label: "Orange",
|
|
142
|
+
light: ["#c2410c", "#f97316", "#fdba74", "#ffedd5", "#fff7ed"],
|
|
143
|
+
dark: ["#fed7aa", "#fb923c", "#f97316", "#c2410c", "#431407"],
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: "yellow",
|
|
147
|
+
label: "Yellow",
|
|
148
|
+
// The one family whose deep stop is not deep enough: #a16207 measures 4.42:1
|
|
149
|
+
// on its own wash, so a chip labelled in it fails AA by 0.08. The accent takes
|
|
150
|
+
// the scale's next step down and the ramp keeps the rest.
|
|
151
|
+
light: ["#713f12", "#eab308", "#fde68a", "#fef3c7", "#fffbe8"],
|
|
152
|
+
dark: ["#fde68a", "#facc15", "#eab308", "#a16207", "#422006"],
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: "green",
|
|
156
|
+
label: "Green",
|
|
157
|
+
light: ["#166534", "#22c55e", "#86efac", "#dcfce7", "#f0fdf4"],
|
|
158
|
+
dark: ["#bbf7d0", "#4ade80", "#22c55e", "#15803d", "#052e16"],
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: "teal",
|
|
162
|
+
label: "Teal",
|
|
163
|
+
light: ["#0f766e", "#14b8a6", "#99f6e4", "#ccfbf1", "#f0fdfa"],
|
|
164
|
+
dark: ["#99f6e4", "#2dd4bf", "#14b8a6", "#0f766e", "#042f2e"],
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
id: "cyan",
|
|
168
|
+
label: "Cyan",
|
|
169
|
+
light: ["#155e75", "#06b6d4", "#67e8f9", "#cffafe", "#ecfeff"],
|
|
170
|
+
dark: ["#a5f3fc", "#22d3ee", "#06b6d4", "#0e7490", "#083344"],
|
|
171
|
+
},
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* The three tokens one accent writes into one theme's palette.
|
|
176
|
+
*
|
|
177
|
+
* @param {string} primary - The accent hex for that theme.
|
|
178
|
+
* @param {"light"|"dark"} theme - Which end of the pair this is.
|
|
179
|
+
* @param {string[]} [ramp] - The family's stops, when the accent came from one.
|
|
180
|
+
* @returns {{ primary: string, onPrimary: string, primaryBg: string }}
|
|
181
|
+
*/
|
|
182
|
+
export const accentTokens = (primary, theme, ramp) => ({
|
|
183
|
+
primary,
|
|
184
|
+
onPrimary: onAccent(primary),
|
|
185
|
+
primaryBg: ramp
|
|
186
|
+
? ramp[STOPS[theme].tint]
|
|
187
|
+
: theme === "dark"
|
|
188
|
+
? color(primary).darken(WASH_DARKEN).hex()
|
|
189
|
+
: color(primary).lighten(WASH_LIGHTEN).hex(),
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* The selectable accents, in the order a picker should show them.
|
|
194
|
+
*
|
|
195
|
+
* `light` and `dark` are the full five-stop ramps of one family, laid out so the
|
|
196
|
+
* accent stop and the wash stop are read off the same list the swatch paints;
|
|
197
|
+
* `accentTokens` is what turns one into the three tokens the theme consumes.
|
|
198
|
+
*
|
|
199
|
+
* @type {Array<{id: string, label: string, light: string[], dark: string[]}>}
|
|
200
|
+
*/
|
|
201
|
+
export const ACCENT_PRESETS = ACCENT_FAMILIES;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* The accent stop of a family's ramp — the hex a swatch paints and a label reads.
|
|
205
|
+
*
|
|
206
|
+
* @param {string[]} ramp
|
|
207
|
+
* @param {"light"|"dark"} theme
|
|
208
|
+
* @returns {string}
|
|
209
|
+
*/
|
|
210
|
+
export const accentStop = (ramp, theme) => ramp[STOPS[theme].primary];
|
|
211
|
+
|
|
212
|
+
// How far each surface sits from the stop the ramp hands it.
|
|
213
|
+
//
|
|
214
|
+
// The rule both themes follow: **the plate is not the surface layer's business.**
|
|
215
|
+
// `accentTokens` already writes `primaryBg` from the one stop the family was
|
|
216
|
+
// measured against — vetoes 4, 5 and 6 in this file's header put the page's inks
|
|
217
|
+
// and the accent on it at 4.5:1 — so a theme that invented its own plate would be
|
|
218
|
+
// re-deriving a number the set was chosen for passing. The layer adds what the
|
|
219
|
+
// accent never owned: the page, the panels, the cards and the hairlines, built
|
|
220
|
+
// around that stop.
|
|
221
|
+
//
|
|
222
|
+
// Light reads down the ramp: the palest stop is the page, the veto'd wash is the
|
|
223
|
+
// panel one step deeper, and a hairline steps just below it. A card goes back to
|
|
224
|
+
// white, the way it lifts off `#fafafa` today. The cost of keeping the measured
|
|
225
|
+
// wash is one collision — a selected row painted `primaryBg` inside a panel painted
|
|
226
|
+
// `surface` is the same color, exactly as it is in the neutral palette, where
|
|
227
|
+
// `#f4f4f5` sits on `#fafafa` at 1.05.
|
|
228
|
+
//
|
|
229
|
+
// Dark reads the same ramp from the other end, and it scales the *deep accent*
|
|
230
|
+
// stop rather than the darkest one: multiplying a channel toward black keeps the
|
|
231
|
+
// hue exactly (scaling toward white washes it out — `#042f2e` stops reading as
|
|
232
|
+
// teal at all), and the darkest stop is too dark to divide three ways with any
|
|
233
|
+
// visible step. Blue's `#0b1220` split into page/panel/card separates at L* 1.3
|
|
234
|
+
// and 1.5, which is no layers at all; the same three depths taken from `#1d4ed8`
|
|
235
|
+
// land at L* 2.3 / 4.5 / 8.6 — where the shipped neutral dark palette sits
|
|
236
|
+
// (`#09090b` at 2.5, `#18181b` at 8.4) — with the family's hue left intact.
|
|
237
|
+
const DARK_PAGE_DROP = 90;
|
|
238
|
+
const DARK_PANEL_DROP = 82;
|
|
239
|
+
const DARK_CARD_DROP = 72;
|
|
240
|
+
const DARK_EDGE_DROP = 55;
|
|
241
|
+
const LIGHT_EDGE_DROP = 6;
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* The surface tokens one family writes when the accent is chosen as a *theme*
|
|
245
|
+
* rather than as a brand color: page, panels, cards and hairlines.
|
|
246
|
+
*
|
|
247
|
+
* Only the surfaces move. The ink scale stays the palette's own neutral, because
|
|
248
|
+
* every one of those grays was measured against a near-white and a near-black page
|
|
249
|
+
* and a tinted stop of a saturated family is within a few percent of both — the
|
|
250
|
+
* text does not need re-deriving, it needs re-measuring, which is what the veto in
|
|
251
|
+
* `tests/regression.test.js` does. The semantic signals (`success`, `danger`,
|
|
252
|
+
* their plates and alert tints) keep their own hues for the same reason a tinted
|
|
253
|
+
* theme must still be able to say "this failed".
|
|
254
|
+
*
|
|
255
|
+
* @param {string[]} ramp - The family's five stops for this theme.
|
|
256
|
+
* @param {"light"|"dark"} theme
|
|
257
|
+
* @returns {{ background: string, surface: string, card: string, border: string, divider: string }}
|
|
258
|
+
*/
|
|
259
|
+
export const accentSurfaces = (ramp, theme) =>
|
|
260
|
+
theme === "dark"
|
|
261
|
+
? {
|
|
262
|
+
background: color(ramp[3]).darken(DARK_PAGE_DROP).hex(),
|
|
263
|
+
surface: color(ramp[3]).darken(DARK_PANEL_DROP).hex(),
|
|
264
|
+
card: color(ramp[3]).darken(DARK_CARD_DROP).hex(),
|
|
265
|
+
border: color(ramp[3]).darken(DARK_EDGE_DROP).hex(),
|
|
266
|
+
divider: color(ramp[3]).darken(DARK_EDGE_DROP).hex(),
|
|
267
|
+
}
|
|
268
|
+
: {
|
|
269
|
+
background: ramp[4],
|
|
270
|
+
surface: ramp[3],
|
|
271
|
+
card: "#ffffff",
|
|
272
|
+
border: color(ramp[3]).darken(LIGHT_EDGE_DROP).hex(),
|
|
273
|
+
divider: color(ramp[3]).darken(LIGHT_EDGE_DROP).hex(),
|
|
274
|
+
};
|
|
275
|
+
|