@krapwoo/ds-viewer 0.9.0 → 0.10.0
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/dist/cli/dev.js +49 -5
- package/dist/cli/workspace.js +37 -1
- package/dist/native/catalog/tokens.js +60 -12
- package/native/catalog/BoundedOverlayViewport.tsx +2 -2
- package/native/catalog/CatalogButton.tsx +2 -2
- package/native/catalog/CatalogSearchInput.tsx +3 -3
- package/native/catalog/CatalogShell.tsx +7 -7
- package/native/catalog/CatalogSidebar.tsx +12 -16
- package/native/catalog/ComparisonGrid.tsx +4 -4
- package/native/catalog/ComparisonGroups.tsx +3 -3
- package/native/catalog/ComparisonList.tsx +2 -2
- package/native/catalog/MotionSpecimen.tsx +3 -3
- package/native/catalog/PhoneFrame.tsx +2 -2
- package/native/catalog/PropsTable.tsx +7 -7
- package/native/catalog/ReferenceDetails.tsx +33 -22
- package/native/catalog/SectionBlock.tsx +10 -16
- package/native/catalog/SpacingScaleGallery.tsx +3 -3
- package/native/catalog/Swatch.tsx +3 -3
- package/native/catalog/TokenLayouts.tsx +6 -9
- package/native/catalog/TokenRow.tsx +2 -2
- package/native/catalog/TypeScaleGallery.tsx +2 -2
- package/native/catalog/UpdatePanel.tsx +535 -78
- package/native/catalog/VariantGroup.tsx +3 -6
- package/native/catalog/index.ts +3 -0
- package/native/catalog/tokens.ts +63 -12
- package/native/catalog/updatePanelState.ts +173 -0
- package/package.json +1 -1
package/dist/cli/dev.js
CHANGED
|
@@ -6,7 +6,7 @@ import { execFile } from 'node:child_process';
|
|
|
6
6
|
import { promisify } from 'node:util';
|
|
7
7
|
import { resolveConfig } from "./config.js";
|
|
8
8
|
import { sync } from "./sync.js";
|
|
9
|
-
import { clearDevLock, liveDevLock, missingWorkspaceFiles, workspacePath, writeDevLock, writeWorkspace } from "./workspace.js";
|
|
9
|
+
import { clearDevLock, liveDevLock, missingWorkspaceFiles, readEndpointRecord, workspacePath, writeDevLock, writeEndpointRecord, writeWorkspace } from "./workspace.js";
|
|
10
10
|
import { findFreePort } from "./port.js";
|
|
11
11
|
import { globBaseFolder } from "./glob.js";
|
|
12
12
|
import { buildVersionStatus, createVersionController, defaultPreferencesDir, readPersonalAutoCheck, resolveAutoCheck, runUpdateCheck, statusFromCheck, writeUpdateFile, writeVersionFile, } from "./updateCheck.js";
|
|
@@ -362,6 +362,44 @@ export function repairWorkspaceIfMissing(deps) {
|
|
|
362
362
|
sync(config);
|
|
363
363
|
return { missing, repaired: true };
|
|
364
364
|
}
|
|
365
|
+
function listenOnce(server, port) {
|
|
366
|
+
return new Promise((resolve, reject) => {
|
|
367
|
+
const onError = (error) => {
|
|
368
|
+
server.removeListener('listening', onListening);
|
|
369
|
+
reject(error);
|
|
370
|
+
};
|
|
371
|
+
const onListening = () => {
|
|
372
|
+
server.removeListener('error', onError);
|
|
373
|
+
resolve(server.address().port);
|
|
374
|
+
};
|
|
375
|
+
server.once('error', onError);
|
|
376
|
+
server.once('listening', onListening);
|
|
377
|
+
server.listen(port, '127.0.0.1');
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
/** Resolves the update endpoint's real port: the stored record's port when it's actually free, a
|
|
381
|
+
* fresh OS-assigned one otherwise. Tries the real bind directly, once — a separate "is this free?"
|
|
382
|
+
* probe followed by a real bind would leave a window for something else to take the port in
|
|
383
|
+
* between, and could never tell "a foreign process holds it" from "nothing does" anyway, which
|
|
384
|
+
* Part 1 says this must never try to resolve: a bind failure for any reason falls back the same
|
|
385
|
+
* way, never killing, probing, or assuming ownership of whatever holds it. Mutates
|
|
386
|
+
* `secretHolder.secret` to a fresh value only on that fallback path — reusing the stored *port*
|
|
387
|
+
* without the *secret* minted for it would pair a stale secret with an address nobody recorded. */
|
|
388
|
+
export async function bindEndpointServer(server, secretHolder, storedPort) {
|
|
389
|
+
if (storedPort !== undefined) {
|
|
390
|
+
try {
|
|
391
|
+
const port = await listenOnce(server, storedPort);
|
|
392
|
+
return { port, reused: true };
|
|
393
|
+
}
|
|
394
|
+
catch {
|
|
395
|
+
// Taken — by a foreign process, a second catalog, anything. Fall through to a fresh port and
|
|
396
|
+
// secret below; never retry the same port or investigate what's holding it.
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
secretHolder.secret = randomBytes(24).toString('hex');
|
|
400
|
+
const port = await listenOnce(server, 0);
|
|
401
|
+
return { port, reused: false };
|
|
402
|
+
}
|
|
365
403
|
export async function dev(initialConfig, devOptions = {}) {
|
|
366
404
|
assertLocalInstall(initialConfig.projectRoot);
|
|
367
405
|
const ownVersion = readOwnVersion(import.meta.dirname);
|
|
@@ -480,14 +518,20 @@ export async function dev(initialConfig, devOptions = {}) {
|
|
|
480
518
|
let updateEndpoint;
|
|
481
519
|
let endpointServer;
|
|
482
520
|
if (!devOptions.noUpdateEndpoint) {
|
|
521
|
+
// Reused across restarts so an already-open page's baked-in endpoint keeps working: the
|
|
522
|
+
// stored record's secret is the candidate here, replaced by `bindEndpointServer` itself only
|
|
523
|
+
// if the stored port turns out to be taken.
|
|
524
|
+
const stored = readEndpointRecord(config);
|
|
525
|
+
endpointDeps.secret = stored?.secret ?? secret;
|
|
483
526
|
endpointServer = createUpdateEndpoint(endpointDeps);
|
|
484
|
-
|
|
485
|
-
|
|
527
|
+
const binding = await bindEndpointServer(endpointServer, endpointDeps, stored?.port);
|
|
528
|
+
if (!binding.reused)
|
|
529
|
+
writeEndpointRecord(config, { port: binding.port, secret: endpointDeps.secret });
|
|
486
530
|
// Printed for the same reason the Metro port above is printed: a developer (or Task 20's own
|
|
487
531
|
// verification) checking the endpoint directly (e.g. with curl) needs its port — the secret
|
|
488
532
|
// itself is never printed; the endpoint rejects anything that doesn't already have it.
|
|
489
|
-
console.log(`Update endpoint listening on 127.0.0.1:${
|
|
490
|
-
updateEndpoint = { baseUrl: `http://127.0.0.1:${
|
|
533
|
+
console.log(`Update endpoint listening on 127.0.0.1:${binding.port}`);
|
|
534
|
+
updateEndpoint = { baseUrl: `http://127.0.0.1:${binding.port}`, secret: endpointDeps.secret };
|
|
491
535
|
}
|
|
492
536
|
else {
|
|
493
537
|
console.log('Update endpoint disabled for this run (--no-update-endpoint).');
|
package/dist/cli/workspace.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
1
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { globBaseFolder } from "./glob.js";
|
|
4
4
|
import { DEFAULT_WORKSPACE_DIR } from "./types.js";
|
|
@@ -151,6 +151,42 @@ export function liveDevLock(config, ownPid = process.pid) {
|
|
|
151
151
|
return undefined;
|
|
152
152
|
return lock;
|
|
153
153
|
}
|
|
154
|
+
const ENDPOINT_RECORD_FILE = 'endpoint.json';
|
|
155
|
+
/** `bindEndpointServer` never writes a secret shorter than this (`randomBytes(24).toString('hex')`
|
|
156
|
+
* is 48 characters), so a shorter one can only come from a hand-written record — but a
|
|
157
|
+
* hand-written `{"secret":""}` is reused verbatim and `secretMatches` in `cli/endpoint.ts`
|
|
158
|
+
* compares two zero-length buffers as equal, authenticating every request. Minor finding,
|
|
159
|
+
* correction pass 4: treated the same as any other invalid record field below. */
|
|
160
|
+
const MIN_SECRET_LENGTH = 32;
|
|
161
|
+
/** The endpoint address a previous `dev` run bound, so a restart can reuse it instead of minting a
|
|
162
|
+
* fresh one — otherwise an already-open page's baked-in endpoint goes stale on every restart.
|
|
163
|
+
* Missing, corrupt, or partially-written is all treated the same: absent, so the caller falls
|
|
164
|
+
* back to generating a fresh pair exactly as if `dev` had never run before. */
|
|
165
|
+
export function readEndpointRecord(config) {
|
|
166
|
+
const recordPath = workspacePath(config, ENDPOINT_RECORD_FILE);
|
|
167
|
+
if (!existsSync(recordPath))
|
|
168
|
+
return undefined;
|
|
169
|
+
try {
|
|
170
|
+
const record = JSON.parse(readFileSync(recordPath, 'utf8'));
|
|
171
|
+
if (typeof record.port !== 'number' || record.port <= 0 || typeof record.secret !== 'string' || record.secret.length < MIN_SECRET_LENGTH)
|
|
172
|
+
return undefined;
|
|
173
|
+
return { port: record.port, secret: record.secret };
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return undefined; // A partial write from a process that crashed mid-write owns nothing.
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/** Written with mode 0600 — not a new exposure (the same secret already lands in cleartext inside
|
|
180
|
+
* the generated `entry.tsx`, see `writeWorkspace`), but it must not be any more exposed than
|
|
181
|
+
* that. `chmodSync` runs in addition to `writeFileSync`'s own `mode` option: that option only
|
|
182
|
+
* applies when the file is newly created, so a pre-existing record left with looser permissions
|
|
183
|
+
* would otherwise keep them across every later rewrite. */
|
|
184
|
+
export function writeEndpointRecord(config, record) {
|
|
185
|
+
mkdirSync(workspacePath(config), { recursive: true });
|
|
186
|
+
const recordPath = workspacePath(config, ENDPOINT_RECORD_FILE);
|
|
187
|
+
writeFileSync(recordPath, `${JSON.stringify(record, null, 2)}\n`, { mode: 0o600 });
|
|
188
|
+
chmodSync(recordPath, 0o600);
|
|
189
|
+
}
|
|
154
190
|
/** The files `writeWorkspace` always writes: the ones that define the workspace to Expo and Metro.
|
|
155
191
|
* `babel.config.js` is conditional and the update files are written separately, so neither counts
|
|
156
192
|
* as missing here. */
|
|
@@ -26,6 +26,30 @@ export const CATALOG_TYPE = {
|
|
|
26
26
|
'3xl': 32,
|
|
27
27
|
'4xl': 40,
|
|
28
28
|
};
|
|
29
|
+
export const CATALOG_FONT = { code: 'Menlo, monospace' };
|
|
30
|
+
/** The catalog's fixed set of typography roles, each a complete `fontSize`/`fontWeight`/
|
|
31
|
+
* `lineHeight` (plus `letterSpacing`/`textTransform`/`fontFamily` where the role needs them)
|
|
32
|
+
* built on `CATALOG_TYPE`. Every catalog-chrome text style spreads one of these rather than
|
|
33
|
+
* setting its own typography properties directly (enforced by
|
|
34
|
+
* `__tests__/textRoles.test.ts`). */
|
|
35
|
+
export const CATALOG_TEXT = {
|
|
36
|
+
pageTitle: { fontSize: CATALOG_TYPE.pageTitle, fontWeight: '700', lineHeight: 34 },
|
|
37
|
+
heading1: { fontSize: CATALOG_TYPE['2xl'], fontWeight: '700', lineHeight: 30 },
|
|
38
|
+
heading2: { fontSize: CATALOG_TYPE.xl, fontWeight: '700', lineHeight: 24 },
|
|
39
|
+
heading3: { fontSize: CATALOG_TYPE.lg, fontWeight: '700', lineHeight: 22 },
|
|
40
|
+
icon: { fontSize: CATALOG_TYPE.lg, fontWeight: '400', lineHeight: 22 },
|
|
41
|
+
body: { fontSize: CATALOG_TYPE.md, fontWeight: '400', lineHeight: 20 },
|
|
42
|
+
bodyStrong: { fontSize: CATALOG_TYPE.md, fontWeight: '700', lineHeight: 20 },
|
|
43
|
+
caption: { fontSize: CATALOG_TYPE.sm, fontWeight: '400', lineHeight: 18 },
|
|
44
|
+
captionStrong: { fontSize: CATALOG_TYPE.sm, fontWeight: '700', lineHeight: 18 },
|
|
45
|
+
label: { fontSize: CATALOG_TYPE.sm, fontWeight: '700', lineHeight: 18, letterSpacing: 0.72, textTransform: 'uppercase' },
|
|
46
|
+
micro: { fontSize: CATALOG_TYPE.xs, fontWeight: '400', lineHeight: 14 },
|
|
47
|
+
microLabel: { fontSize: CATALOG_TYPE.xs, fontWeight: '700', lineHeight: 14, letterSpacing: 0.6, textTransform: 'uppercase' },
|
|
48
|
+
code: { fontSize: CATALOG_TYPE.md, fontWeight: '400', lineHeight: 20, fontFamily: CATALOG_FONT.code },
|
|
49
|
+
codeSmall: { fontSize: CATALOG_TYPE.sm, fontWeight: '400', lineHeight: 18, fontFamily: CATALOG_FONT.code },
|
|
50
|
+
codeSmallStrong: { fontSize: CATALOG_TYPE.sm, fontWeight: '700', lineHeight: 18, fontFamily: CATALOG_FONT.code },
|
|
51
|
+
codeMicro: { fontSize: CATALOG_TYPE.xs, fontWeight: '400', lineHeight: 14, fontFamily: CATALOG_FONT.code },
|
|
52
|
+
};
|
|
29
53
|
export const CATALOG_SPACE = {
|
|
30
54
|
xs: 4,
|
|
31
55
|
sm: 8,
|
|
@@ -69,21 +93,42 @@ export const CATALOG_LAYOUT = {
|
|
|
69
93
|
/** Max width of the main content column. */
|
|
70
94
|
export const CATALOG_MAX_CONTENT_WIDTH = 1200;
|
|
71
95
|
/** When to reach for each catalog-chrome type size. Rendered in the framework catalog's Type
|
|
72
|
-
* Scale page.
|
|
96
|
+
* Scale page. Every size is now consumed through a `CATALOG_TEXT` role (see `CATALOG_TEXT_USE`)
|
|
97
|
+
* rather than directly. */
|
|
73
98
|
export const CATALOG_TYPE_USE = {
|
|
74
|
-
xs: '
|
|
75
|
-
tableHeader: '
|
|
76
|
-
sm: '
|
|
77
|
-
panelHeading: '
|
|
78
|
-
md: '
|
|
79
|
-
lg: '
|
|
80
|
-
brand: '
|
|
81
|
-
xl:
|
|
82
|
-
'2xl':
|
|
83
|
-
pageTitle: '
|
|
99
|
+
xs: 'Backs the `micro`, `microLabel` and `codeMicro` roles: token values and meta captions; sidebar group labels and OS badges (uppercase); a prop\'s type and default value (monospace).',
|
|
100
|
+
tableHeader: 'Reserved — no current consumer; grid column headers and list cell captions now use the `captionStrong` role (built on `sm`).',
|
|
101
|
+
sm: 'Backs the `caption`, `captionStrong`, `label`, `codeSmall` and `codeSmallStrong` roles: breadcrumbs, block/prop descriptions, swatch/token names, table captions, monospace prop names and file paths.',
|
|
102
|
+
panelHeading: 'Reserved — no current consumer; reference-panel and update-page headings now use the `label` role (built on `sm`).',
|
|
103
|
+
md: 'Backs the `body`, `bodyStrong` and `code` roles: descriptions, nav links, the filter field, switch labels, a long unbroken value that needs to wrap at its \'/\'s (monospace).',
|
|
104
|
+
lg: 'Backs the `heading3` and `icon` roles: a result note\'s title; pager arrows and the filter field\'s clear (×) glyph.',
|
|
105
|
+
brand: 'Reserved — no current consumer; the sidebar app name now uses the `heading2` role (built on `xl`).',
|
|
106
|
+
xl: 'Backs the `heading2` role: the app name at the top of the sidebar.',
|
|
107
|
+
'2xl': 'Backs the `heading1` role: the update page\'s version numbers and the arrow between them.',
|
|
108
|
+
pageTitle: 'Backs the `pageTitle` role: the selected page\'s title, including the update page\'s own title.',
|
|
84
109
|
'3xl': 'Reserved — no current consumer.',
|
|
85
110
|
'4xl': 'Reserved — no current consumer.',
|
|
86
111
|
};
|
|
112
|
+
/** When to reach for each catalog-chrome typography role. Rendered in the framework catalog's
|
|
113
|
+
* Type Scale page. */
|
|
114
|
+
export const CATALOG_TEXT_USE = {
|
|
115
|
+
pageTitle: 'The selected page\'s title, including the update page\'s own title.',
|
|
116
|
+
heading1: 'The update page\'s version numbers and the arrow between them.',
|
|
117
|
+
heading2: 'The app name at the top of the sidebar.',
|
|
118
|
+
heading3: 'A result note\'s title (the update page\'s success/failure banner).',
|
|
119
|
+
icon: 'Glyph-only controls: pager arrows, the filter field\'s clear (×) glyph, the update banner\'s close (×) glyph.',
|
|
120
|
+
body: 'Default body text: descriptions, nav links, the filter field, update-page facts/notes/steps.',
|
|
121
|
+
bodyStrong: 'Emphasised body text: grid/list row headers, switch and step labels, inline-bold notice text.',
|
|
122
|
+
caption: 'Secondary text: breadcrumbs, status captions, block/prop descriptions, sidebar subtitle/footer, update-page captions.',
|
|
123
|
+
captionStrong: 'Emphasised secondary text: grid column headers, grid/list/table captions, reference-panel subheadings, swatch/token names, replay and result links, step-icon glyphs.',
|
|
124
|
+
label: 'Uppercase labels and headings: block and preview-frame labels, token-section labels, reference-panel and update-page headings.',
|
|
125
|
+
micro: 'The smallest text: token values and meta captions, a prop\'s default-value label.',
|
|
126
|
+
microLabel: 'Uppercase micro labels: variant-group names, sidebar group labels, OS badges.',
|
|
127
|
+
code: 'Monospace body text: a long unbroken value that needs to wrap at its \'/\'s.',
|
|
128
|
+
codeSmall: 'Monospace secondary text: a prop\'s optional marker, the update page\'s inline code, file paths and log lines.',
|
|
129
|
+
codeSmallStrong: 'Emphasised monospace secondary text: a prop\'s own name.',
|
|
130
|
+
codeMicro: 'Monospace micro text: a prop\'s type and default value.',
|
|
131
|
+
};
|
|
87
132
|
/** When to reach for each catalog-chrome spacing step. Rendered in the framework catalog's
|
|
88
133
|
* Spacing page. */
|
|
89
134
|
export const CATALOG_SPACE_USE = {
|
|
@@ -127,7 +172,10 @@ export const CATALOG_COLOR = {
|
|
|
127
172
|
accentSubtle: '#e9efff',
|
|
128
173
|
/** 3px keyboard focus ring on every catalog control. */
|
|
129
174
|
focusRing: '#c9d7ff',
|
|
130
|
-
code
|
|
175
|
+
/** @deprecated kept for the public API; re-points at `CATALOG_FONT.code` so there is one source
|
|
176
|
+
* of truth. New code should reach for `CATALOG_FONT.code` (or a `CATALOG_TEXT` `code*` role)
|
|
177
|
+
* instead. */
|
|
178
|
+
code: CATALOG_FONT.code,
|
|
131
179
|
/** Major-update banner background/border/text (design §5 "Viewer notice"). */
|
|
132
180
|
warning: '#8a4b00',
|
|
133
181
|
warningSubtle: '#fff4e0',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import { Platform, StyleSheet, Text, View } from 'react-native';
|
|
3
|
-
import { CATALOG_COLOR,
|
|
3
|
+
import { CATALOG_COLOR, CATALOG_TEXT } from './tokens';
|
|
4
4
|
import { isFiniteOverlaySize, overlayStageDimensions, resolveOverlayViewport, type OverlaySlot } from './overlayViewport';
|
|
5
5
|
|
|
6
6
|
function isWeb(): boolean {
|
|
@@ -108,5 +108,5 @@ const styles = StyleSheet.create({
|
|
|
108
108
|
height: '100%',
|
|
109
109
|
borderWidth: 0,
|
|
110
110
|
},
|
|
111
|
-
status: { position: 'absolute',
|
|
111
|
+
status: { position: 'absolute', ...CATALOG_TEXT.caption, color: CATALOG_COLOR.textMuted },
|
|
112
112
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { forwardRef, useState } from 'react';
|
|
2
2
|
import { ActivityIndicator, Pressable, StyleSheet, Text, type View } from 'react-native';
|
|
3
|
-
import { CATALOG_COLOR, CATALOG_LAYOUT, CATALOG_RADIUS, CATALOG_SPACE,
|
|
3
|
+
import { CATALOG_COLOR, CATALOG_LAYOUT, CATALOG_RADIUS, CATALOG_SPACE, CATALOG_TEXT } from './tokens';
|
|
4
4
|
|
|
5
5
|
/** The viewer's own button, for catalog chrome and demo triggers (not an app component): a rounded
|
|
6
6
|
* rectangle, primary in the accent blue, secondary outlined. `disabled` greys it and stops the
|
|
@@ -77,7 +77,7 @@ const styles = StyleSheet.create({
|
|
|
77
77
|
disabled: { backgroundColor: CATALOG_COLOR.controlDisabled, borderColor: CATALOG_COLOR.controlDisabled },
|
|
78
78
|
// Same ring as the sidebar's: an outline, so focusing never shifts the layout.
|
|
79
79
|
focus: { outlineWidth: CATALOG_LAYOUT.focusRingWidth, outlineStyle: 'solid', outlineColor: CATALOG_COLOR.focusRing } as object,
|
|
80
|
-
label: {
|
|
80
|
+
label: { ...CATALOG_TEXT.bodyStrong },
|
|
81
81
|
labelPrimary: { color: '#ffffff' },
|
|
82
82
|
labelSecondary: { color: CATALOG_COLOR.text },
|
|
83
83
|
labelBusy: { color: CATALOG_COLOR.textMuted },
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useRef, useState } from 'react';
|
|
2
2
|
import { View, Text, TextInput, Pressable, StyleSheet } from 'react-native';
|
|
3
|
-
import {
|
|
3
|
+
import { CATALOG_TEXT, CATALOG_COLOR, CATALOG_SPACE, CATALOG_RADIUS, CATALOG_LAYOUT } from './tokens';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Plain search input for filtering the catalog's own sidebar nav. Deliberately built from bare
|
|
@@ -74,7 +74,7 @@ const styles = StyleSheet.create({
|
|
|
74
74
|
backgroundColor: CATALOG_COLOR.surfaceMuted,
|
|
75
75
|
},
|
|
76
76
|
boxFocused: { outlineWidth: CATALOG_LAYOUT.focusRingWidth, outlineStyle: 'solid', outlineColor: CATALOG_COLOR.focusRing },
|
|
77
|
-
input: { flex: 1,
|
|
77
|
+
input: { flex: 1, ...CATALOG_TEXT.body, color: CATALOG_COLOR.text, padding: 0 },
|
|
78
78
|
// A full 44×44 target: react-native-web does not honor hitSlop for pointer or keyboard.
|
|
79
79
|
clearButton: {
|
|
80
80
|
width: CATALOG_LAYOUT.controlSize, height: CATALOG_LAYOUT.controlSize,
|
|
@@ -82,5 +82,5 @@ const styles = StyleSheet.create({
|
|
|
82
82
|
},
|
|
83
83
|
focusRing: { outlineWidth: CATALOG_LAYOUT.focusRingWidth, outlineStyle: 'solid', outlineColor: CATALOG_COLOR.focusRing },
|
|
84
84
|
clearButtonActive: { backgroundColor: CATALOG_COLOR.surfacePressed },
|
|
85
|
-
clear: {
|
|
85
|
+
clear: { ...CATALOG_TEXT.icon, color: CATALOG_COLOR.textMuted, paddingHorizontal: 2 },
|
|
86
86
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { AccessibilityInfo, Platform, Pressable, ScrollView, StyleSheet, Text, View, findNodeHandle } from 'react-native';
|
|
3
3
|
import type { ImageSourcePropType } from 'react-native';
|
|
4
|
-
import { CATALOG_COLOR, CATALOG_LAYOUT, CATALOG_MAX_CONTENT_WIDTH, CATALOG_RADIUS, CATALOG_SPACE,
|
|
4
|
+
import { CATALOG_COLOR, CATALOG_LAYOUT, CATALOG_MAX_CONTENT_WIDTH, CATALOG_RADIUS, CATALOG_SPACE, CATALOG_TEXT } from './tokens';
|
|
5
5
|
import { CatalogSidebar } from './CatalogSidebar';
|
|
6
6
|
import { SectionBlock } from './SectionBlock';
|
|
7
7
|
import { UpdatePanel } from './UpdatePanel';
|
|
@@ -101,11 +101,11 @@ const bannerStyles = StyleSheet.create({
|
|
|
101
101
|
paddingHorizontal: 14,
|
|
102
102
|
marginBottom: 18,
|
|
103
103
|
},
|
|
104
|
-
text: { flex: 1,
|
|
105
|
-
bold: {
|
|
106
|
-
link: { color: CATALOG_COLOR.warning,
|
|
104
|
+
text: { flex: 1, ...CATALOG_TEXT.body, color: CATALOG_COLOR.warning },
|
|
105
|
+
bold: { ...CATALOG_TEXT.bodyStrong },
|
|
106
|
+
link: { ...CATALOG_TEXT.bodyStrong, color: CATALOG_COLOR.warning, textDecorationLine: 'underline' },
|
|
107
107
|
close: { width: 32, height: 32, borderRadius: CATALOG_RADIUS.sm, alignItems: 'center', justifyContent: 'center' },
|
|
108
|
-
closeGlyph: {
|
|
108
|
+
closeGlyph: { ...CATALOG_TEXT.icon, color: CATALOG_COLOR.warning },
|
|
109
109
|
});
|
|
110
110
|
|
|
111
111
|
/**
|
|
@@ -327,7 +327,7 @@ const styles = StyleSheet.create({
|
|
|
327
327
|
// A `BoundedOverlayViewport` child document's own root: no sidebar/chrome, just the one
|
|
328
328
|
// addressed node — sized by whatever that node itself renders, not the catalog's own layout.
|
|
329
329
|
overlayDocumentRoot: { flex: 1 },
|
|
330
|
-
overlayUnavailable: {
|
|
330
|
+
overlayUnavailable: { ...CATALOG_TEXT.body, color: CATALOG_COLOR.textMuted, padding: CATALOG_SPACE.lg },
|
|
331
331
|
main: { flex: 1 },
|
|
332
332
|
mainContent: {
|
|
333
333
|
width: '100%',
|
|
@@ -336,5 +336,5 @@ const styles = StyleSheet.create({
|
|
|
336
336
|
paddingHorizontal: CATALOG_LAYOUT.mainPaddingX,
|
|
337
337
|
paddingBottom: CATALOG_SPACE['3xl'],
|
|
338
338
|
},
|
|
339
|
-
empty: {
|
|
339
|
+
empty: { ...CATALOG_TEXT.body, color: CATALOG_COLOR.textMuted },
|
|
340
340
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
2
|
import { View, Text, ScrollView, Pressable, StyleSheet, Image } from 'react-native';
|
|
3
3
|
import type { ImageSourcePropType, LayoutChangeEvent, NativeSyntheticEvent, ImageLoadEventData } from 'react-native';
|
|
4
|
-
import { CATALOG_COLOR, CATALOG_LAYOUT, CATALOG_RADIUS, CATALOG_SPACE,
|
|
4
|
+
import { CATALOG_COLOR, CATALOG_LAYOUT, CATALOG_RADIUS, CATALOG_SPACE, CATALOG_TEXT } from './tokens';
|
|
5
5
|
import { logoLayout } from './logoLayout';
|
|
6
6
|
import { filterGroups, hashForId, UPDATE_PAGE_ID } from './catalogNavigation';
|
|
7
7
|
import type { NavGroup } from './types';
|
|
@@ -153,8 +153,8 @@ export function CatalogSidebar<TId extends string>({
|
|
|
153
153
|
const onLogoError = () => setLogoFailed(true);
|
|
154
154
|
// Wrap detection for the "mark" row: the name Text's own measured height, not `onTextLayout`
|
|
155
155
|
// (react-native-web's `Text` doesn't implement it) and not a character-count heuristic (wraps at
|
|
156
|
-
// a different length per name). More than one line of `
|
|
157
|
-
const NAME_LINE_HEIGHT =
|
|
156
|
+
// a different length per name). More than one line of `CATALOG_TEXT.heading2` means it wrapped.
|
|
157
|
+
const NAME_LINE_HEIGHT = CATALOG_TEXT.heading2.lineHeight;
|
|
158
158
|
const onNameLayout = (e: LayoutChangeEvent) => {
|
|
159
159
|
setNameWrapped(e.nativeEvent.layout.height > NAME_LINE_HEIGHT * 1.5);
|
|
160
160
|
};
|
|
@@ -235,8 +235,8 @@ const styles = StyleSheet.create({
|
|
|
235
235
|
paddingBottom: CATALOG_LAYOUT.sidebarPaddingTop,
|
|
236
236
|
},
|
|
237
237
|
header: { marginBottom: 20 },
|
|
238
|
-
logo: {
|
|
239
|
-
subtitle: {
|
|
238
|
+
logo: { ...CATALOG_TEXT.heading2, color: CATALOG_COLOR.text },
|
|
239
|
+
subtitle: { ...CATALOG_TEXT.caption, color: CATALOG_COLOR.textMuted, marginTop: 2 },
|
|
240
240
|
subtitleAfterWordmark: { marginTop: CATALOG_LAYOUT.logoCaptionGap },
|
|
241
241
|
mark: { width: CATALOG_LAYOUT.logoMark, height: CATALOG_LAYOUT.logoMark },
|
|
242
242
|
wordmark: { height: CATALOG_LAYOUT.logoWordmarkHeight, alignSelf: 'flex-start' },
|
|
@@ -244,13 +244,12 @@ const styles = StyleSheet.create({
|
|
|
244
244
|
inlineHeader: { flexDirection: 'row', alignItems: 'center', gap: CATALOG_SPACE.md },
|
|
245
245
|
inlineHeaderTop: { alignItems: 'flex-start' },
|
|
246
246
|
inlineText: { flex: 1, minWidth: 0 },
|
|
247
|
-
empty: {
|
|
247
|
+
empty: { ...CATALOG_TEXT.caption, color: CATALOG_COLOR.textMuted, paddingHorizontal: CATALOG_LAYOUT.navItemPaddingX, paddingVertical: 8 },
|
|
248
248
|
footerContainer: {
|
|
249
249
|
borderTopWidth: 1,
|
|
250
250
|
borderTopColor: CATALOG_COLOR.borderHairline,
|
|
251
251
|
paddingHorizontal: CATALOG_LAYOUT.sidebarPaddingX,
|
|
252
|
-
|
|
253
|
-
paddingBottom: 14,
|
|
252
|
+
paddingVertical: CATALOG_SPACE.md,
|
|
254
253
|
},
|
|
255
254
|
footerLink: {
|
|
256
255
|
flexDirection: 'row',
|
|
@@ -263,13 +262,10 @@ const styles = StyleSheet.create({
|
|
|
263
262
|
footerLinkHover: { backgroundColor: CATALOG_COLOR.surfacePressed },
|
|
264
263
|
footerLinkActive: { backgroundColor: CATALOG_COLOR.accentSubtle },
|
|
265
264
|
footerDot: { width: 8, height: 8, borderRadius: 4, backgroundColor: CATALOG_COLOR.accent, flexShrink: 0 },
|
|
266
|
-
footerLabel: {
|
|
267
|
-
footerLabelActive: { color: CATALOG_COLOR.accent
|
|
265
|
+
footerLabel: { ...CATALOG_TEXT.caption, color: CATALOG_COLOR.textMuted },
|
|
266
|
+
footerLabelActive: { ...CATALOG_TEXT.captionStrong, color: CATALOG_COLOR.accent },
|
|
268
267
|
groupLabel: {
|
|
269
|
-
|
|
270
|
-
fontWeight: '800',
|
|
271
|
-
letterSpacing: 1,
|
|
272
|
-
textTransform: 'uppercase',
|
|
268
|
+
...CATALOG_TEXT.microLabel,
|
|
273
269
|
color: CATALOG_COLOR.text,
|
|
274
270
|
marginTop: 22,
|
|
275
271
|
marginBottom: 7,
|
|
@@ -285,6 +281,6 @@ const styles = StyleSheet.create({
|
|
|
285
281
|
itemHover: { backgroundColor: CATALOG_COLOR.surfacePressed },
|
|
286
282
|
itemActive: { backgroundColor: CATALOG_COLOR.accentSubtle },
|
|
287
283
|
focusRing: { outlineWidth: CATALOG_LAYOUT.focusRingWidth, outlineStyle: 'solid', outlineColor: CATALOG_COLOR.focusRing },
|
|
288
|
-
label: {
|
|
289
|
-
labelActive: { color: CATALOG_COLOR.accent
|
|
284
|
+
label: { ...CATALOG_TEXT.body, color: CATALOG_COLOR.textMuted },
|
|
285
|
+
labelActive: { ...CATALOG_TEXT.bodyStrong, color: CATALOG_COLOR.accent },
|
|
290
286
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { View, Text, ScrollView, StyleSheet } from 'react-native';
|
|
3
|
-
import { CATALOG_COLOR, CATALOG_RADIUS,
|
|
3
|
+
import { CATALOG_COLOR, CATALOG_RADIUS, CATALOG_TEXT } from './tokens';
|
|
4
4
|
import { COLUMN_MIN_WIDTH, MATRIX_LAYOUT, axisItems, cellKey, gridMinWidth, indexCells, validateComparison } from './comparison';
|
|
5
5
|
import { SpecimenContent, SpecimenSurface } from './SpecimenSurface';
|
|
6
6
|
import { SpecimenAddressProvider } from './SpecimenAddress';
|
|
@@ -108,7 +108,7 @@ const styles = StyleSheet.create({
|
|
|
108
108
|
headerCell: { backgroundColor: CATALOG_COLOR.surfaceMuted, justifyContent: 'center' },
|
|
109
109
|
bodyRow: { minHeight: MATRIX_LAYOUT.rowMinHeight },
|
|
110
110
|
specimenCell: { alignItems: 'center', justifyContent: 'center' },
|
|
111
|
-
headerText: {
|
|
112
|
-
rowHeaderText: {
|
|
113
|
-
unavailable: {
|
|
111
|
+
headerText: { ...CATALOG_TEXT.captionStrong, color: CATALOG_COLOR.text },
|
|
112
|
+
rowHeaderText: { ...CATALOG_TEXT.bodyStrong, color: CATALOG_COLOR.text },
|
|
113
|
+
unavailable: { ...CATALOG_TEXT.caption, textAlign: 'center', color: CATALOG_COLOR.textMuted },
|
|
114
114
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { View, Text, ScrollView, StyleSheet } from 'react-native';
|
|
3
|
-
import { CATALOG_COLOR, CATALOG_RADIUS,
|
|
3
|
+
import { CATALOG_COLOR, CATALOG_RADIUS, CATALOG_TEXT } from './tokens';
|
|
4
4
|
import { COLUMN_MIN_WIDTH, MATRIX_LAYOUT, type ListGroup } from './comparison';
|
|
5
5
|
import { SpecimenContent, SpecimenSurface } from './SpecimenSurface';
|
|
6
6
|
import { SpecimenAddressProvider } from './SpecimenAddress';
|
|
@@ -90,7 +90,7 @@ const styles = StyleSheet.create({
|
|
|
90
90
|
borderRightWidth: 1,
|
|
91
91
|
borderRightColor: CATALOG_COLOR.border,
|
|
92
92
|
},
|
|
93
|
-
rowHeaderText: {
|
|
93
|
+
rowHeaderText: { ...CATALOG_TEXT.bodyStrong, color: CATALOG_COLOR.text },
|
|
94
94
|
cells: { flex: 1, flexDirection: 'row' },
|
|
95
95
|
cell: { flex: 1, borderRightWidth: 1, borderRightColor: CATALOG_COLOR.border },
|
|
96
96
|
lastColumn: { borderRightWidth: 0 },
|
|
@@ -100,7 +100,7 @@ const styles = StyleSheet.create({
|
|
|
100
100
|
borderBottomWidth: 1,
|
|
101
101
|
borderBottomColor: CATALOG_COLOR.border,
|
|
102
102
|
},
|
|
103
|
-
captionText: {
|
|
103
|
+
captionText: { ...CATALOG_TEXT.captionStrong, color: CATALOG_COLOR.text },
|
|
104
104
|
specimen: {
|
|
105
105
|
flexGrow: 1,
|
|
106
106
|
minHeight: MATRIX_LAYOUT.rowMinHeight,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import { View, Text, StyleSheet, type LayoutChangeEvent } from 'react-native';
|
|
3
|
-
import { CATALOG_COLOR, CATALOG_RADIUS,
|
|
3
|
+
import { CATALOG_COLOR, CATALOG_RADIUS, CATALOG_TEXT } from './tokens';
|
|
4
4
|
import { MATRIX_LAYOUT, listGeometry, type ListItem } from './comparison';
|
|
5
5
|
import { SpecimenContent, SpecimenSurface } from './SpecimenSurface';
|
|
6
6
|
import { SpecimenAddressProvider } from './SpecimenAddress';
|
|
@@ -107,7 +107,7 @@ const styles = StyleSheet.create({
|
|
|
107
107
|
borderBottomWidth: 1,
|
|
108
108
|
borderBottomColor: CATALOG_COLOR.border,
|
|
109
109
|
},
|
|
110
|
-
captionText: {
|
|
110
|
+
captionText: { ...CATALOG_TEXT.captionStrong, color: CATALOG_COLOR.text },
|
|
111
111
|
specimen: {
|
|
112
112
|
flexGrow: 1,
|
|
113
113
|
minHeight: MATRIX_LAYOUT.rowMinHeight,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { Animated, AccessibilityInfo, Pressable, StyleSheet, Text, View } from 'react-native';
|
|
3
|
-
import { CATALOG_COLOR, CATALOG_RADIUS, CATALOG_SPACE,
|
|
3
|
+
import { CATALOG_COLOR, CATALOG_RADIUS, CATALOG_SPACE, CATALOG_TEXT } from './tokens';
|
|
4
4
|
import { motionReplayLabel, motionTranslatesDirectly, motionValueTarget, type MotionValueRange } from './motionValueDomain';
|
|
5
5
|
|
|
6
6
|
/** `Animated.spring`'s physics fields — the same shape as a design system's own spring-config
|
|
@@ -137,7 +137,7 @@ const styles = StyleSheet.create({
|
|
|
137
137
|
backgroundColor: CATALOG_COLOR.accent,
|
|
138
138
|
},
|
|
139
139
|
footer: { flexDirection: 'row', alignItems: 'center', gap: CATALOG_SPACE.md },
|
|
140
|
-
label: {
|
|
140
|
+
label: { ...CATALOG_TEXT.caption, color: CATALOG_COLOR.textMuted },
|
|
141
141
|
labelSpacer: { flex: 1 },
|
|
142
142
|
replayButton: {
|
|
143
143
|
paddingHorizontal: CATALOG_SPACE.md,
|
|
@@ -148,5 +148,5 @@ const styles = StyleSheet.create({
|
|
|
148
148
|
backgroundColor: CATALOG_COLOR.surface,
|
|
149
149
|
},
|
|
150
150
|
replayButtonPressed: { backgroundColor: CATALOG_COLOR.surfacePressed },
|
|
151
|
-
replayText: {
|
|
151
|
+
replayText: { ...CATALOG_TEXT.captionStrong, color: CATALOG_COLOR.text },
|
|
152
152
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState, useRef } from 'react';
|
|
2
2
|
import { Platform, StyleSheet, Text, View, type LayoutChangeEvent } from 'react-native';
|
|
3
|
-
import { CATALOG_COLOR, CATALOG_RADIUS,
|
|
3
|
+
import { CATALOG_COLOR, CATALOG_RADIUS, CATALOG_TEXT } from './tokens';
|
|
4
4
|
import { DEVICE_FRAME, DEVICE_SCALE_WARN_BELOW, deviceFrameScale } from './deviceFrame';
|
|
5
5
|
import { resolveOverlayViewport } from './overlayViewport';
|
|
6
6
|
import { useSpecimenAddress } from './SpecimenAddress';
|
|
@@ -149,5 +149,5 @@ const styles = StyleSheet.create({
|
|
|
149
149
|
justifyContent: 'center',
|
|
150
150
|
overflow: 'hidden',
|
|
151
151
|
},
|
|
152
|
-
status: { position: 'absolute',
|
|
152
|
+
status: { position: 'absolute', ...CATALOG_TEXT.caption, color: CATALOG_COLOR.textMuted },
|
|
153
153
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { View, Text, StyleSheet } from 'react-native';
|
|
2
|
-
import {
|
|
2
|
+
import { CATALOG_TEXT, CATALOG_COLOR, CATALOG_SPACE } from './tokens';
|
|
3
3
|
import { PROPS_COLUMN_GAP } from './comparison';
|
|
4
4
|
import type { PropDef } from './types';
|
|
5
5
|
|
|
@@ -86,14 +86,14 @@ const styles = StyleSheet.create({
|
|
|
86
86
|
rowFirst: { paddingTop: 0 },
|
|
87
87
|
rowLast: { borderBottomWidth: 0, paddingBottom: 0 },
|
|
88
88
|
header: { width: 140, gap: 2 },
|
|
89
|
-
name: {
|
|
90
|
-
optionalMark: {
|
|
91
|
-
type: {
|
|
89
|
+
name: { ...CATALOG_TEXT.codeSmallStrong, color: CATALOG_COLOR.text },
|
|
90
|
+
optionalMark: { ...CATALOG_TEXT.codeSmall, color: CATALOG_COLOR.textMuted },
|
|
91
|
+
type: { ...CATALOG_TEXT.codeMicro, color: CATALOG_COLOR.accent },
|
|
92
92
|
// The second column — description + default — sized by the row's remaining width.
|
|
93
93
|
body: { flex: 1, gap: 2 },
|
|
94
|
-
desc: {
|
|
95
|
-
default: {
|
|
96
|
-
defaultVal: {
|
|
94
|
+
desc: { ...CATALOG_TEXT.caption, color: CATALOG_COLOR.textMuted },
|
|
95
|
+
default: { ...CATALOG_TEXT.micro, color: CATALOG_COLOR.textMuted },
|
|
96
|
+
defaultVal: { ...CATALOG_TEXT.codeMicro, color: CATALOG_COLOR.textMuted },
|
|
97
97
|
gridRow: { flexDirection: 'row', gap: PROPS_COLUMN_GAP },
|
|
98
98
|
cell: { flex: 1, minWidth: 0, paddingVertical: CATALOG_SPACE.md, gap: 4 },
|
|
99
99
|
cellDivider: { borderBottomWidth: 1, borderBottomColor: CATALOG_COLOR.border },
|