@krapwoo/ds-viewer 0.6.9 → 0.6.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/dist/cli/explain.js +18 -7
- package/native/catalog/PhoneFrame.tsx +29 -3
- package/native/catalog/deviceFrame.ts +9 -0
- package/package.json +1 -1
- package/starter-kit/components/BottomSheet/BottomSheet.catalog.tsx +4 -0
- package/starter-kit/components/Dialog/Dialog.catalog.tsx +3 -0
package/README.md
CHANGED
|
@@ -502,6 +502,22 @@ Button
|
|
|
502
502
|
gallery or a whole-page layout.
|
|
503
503
|
- `internal-prop-unknown` — `internalProps` names something that is not a prop of the component,
|
|
504
504
|
so the exemption it was written for is not in effect. Correct the spelling or delete the entry.
|
|
505
|
+
|
|
506
|
+
The catalog also warns at render time when a page under-declares the width its examples need.
|
|
507
|
+
A `PhoneFrame` given a cell narrower than the 375px device scales the whole phone down, and its
|
|
508
|
+
own body text shrinks with it, so it reports the page, the example and the cell:
|
|
509
|
+
|
|
510
|
+
[Catalog] BottomSheet (variants/content-only) is shown at 75% of device size: its cell is
|
|
511
|
+
283px, narrower than the 375px device. Declare a larger specimenSize so the example keeps
|
|
512
|
+
the width it is used at.
|
|
513
|
+
|
|
514
|
+
`check:catalog` fails on any `[Catalog]` warning, so this is caught in CI. The threshold is 95%,
|
|
515
|
+
not 100%: a regular cell leaves 370px for a 375px device, and warning about a reduction nobody
|
|
516
|
+
can see would only train you to ignore the warning. Where the scaling *is* the subject — a page
|
|
517
|
+
documenting that a narrow cell shrinks the device rather than reflowing it — write
|
|
518
|
+
`<PhoneFrame intentionalScale>` in that example, so the exemption is visible where the decision
|
|
519
|
+
was made.
|
|
520
|
+
|
|
505
521
|
A page's `internalProps: { size: 'why' }` exempts a named prop from `option-not-covered` — for
|
|
506
522
|
plumbing a parent passes (a layout size, an internal surface kind) rather than a value the
|
|
507
523
|
reader picks. It needs a reason per prop, so the exemption is a visible declaration in the page;
|
package/dist/cli/explain.js
CHANGED
|
@@ -70,7 +70,7 @@ function toSectionDef(page) {
|
|
|
70
70
|
hide: (page.hideVariants || page.hideStates) ? { variants: page.hideVariants, states: page.hideStates } : undefined,
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
|
-
function explainBlock(block) {
|
|
73
|
+
function explainBlock(block, availableWidth) {
|
|
74
74
|
if (block.kind === 'grid') {
|
|
75
75
|
const rows = axisItems(block.comparison.rows).length;
|
|
76
76
|
const columns = axisItems(block.comparison.columns).length;
|
|
@@ -82,10 +82,10 @@ function explainBlock(block) {
|
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
84
|
if (block.kind === 'list') {
|
|
85
|
-
const geometry = listGeometry(block.items.length,
|
|
85
|
+
const geometry = listGeometry(block.items.length, availableWidth, block.size, block.maxColumns);
|
|
86
86
|
return {
|
|
87
87
|
kind: 'list', title: block.title,
|
|
88
|
-
reason: `${block.items.length} items (${block.size}) → ${geometry.columns} columns × ${geometry.rows} row${geometry.rows === 1 ? '' : 's'}` +
|
|
88
|
+
reason: `${block.items.length} items (${block.size}) → ${geometry.columns} column${geometry.columns === 1 ? '' : 's'} × ${geometry.rows} row${geometry.rows === 1 ? '' : 's'}` +
|
|
89
89
|
(geometry.fillers > 0 ? ` (${geometry.fillers} filler${geometry.fillers === 1 ? '' : 's'})` : '') + '.' +
|
|
90
90
|
surfaceOverrideNote(block.items) + alignOverrideNote(block.items),
|
|
91
91
|
};
|
|
@@ -149,29 +149,40 @@ export function explainPage(config, pageId, options = {}) {
|
|
|
149
149
|
return { pageId, notCheckable: true, blocks: [] };
|
|
150
150
|
const def = toSectionDef(page);
|
|
151
151
|
const blocks = presentationBlocks(def, { defaultPreviewWidths: [402] });
|
|
152
|
-
|
|
152
|
+
// Two blocks placed side by side get half the row each, which changes how many columns fit.
|
|
153
|
+
// Reporting the full-width counts for a page the viewer will halve states the wrong geometry as
|
|
154
|
+
// fact, so the width every block is measured at follows the placement decision.
|
|
155
|
+
const full = MATRIX_LAYOUT.laptopContentWidth;
|
|
156
|
+
const half = Math.floor((full - CATALOG_LAYOUT.blockGap) / 2);
|
|
153
157
|
let placement;
|
|
158
|
+
let blockWidth = full;
|
|
154
159
|
if (blocks.length === 2) {
|
|
155
160
|
if (options.heights) {
|
|
156
161
|
const [firstHeight, secondHeight] = options.heights;
|
|
157
162
|
const decision = choosePlacement({
|
|
158
|
-
available:
|
|
163
|
+
available: full,
|
|
159
164
|
gap: CATALOG_LAYOUT.blockGap,
|
|
160
165
|
first: toPlacementBlock(blocks[0], firstHeight),
|
|
161
166
|
second: toPlacementBlock(blocks[1], secondHeight),
|
|
162
167
|
});
|
|
168
|
+
if (decision === 'side')
|
|
169
|
+
blockWidth = half;
|
|
163
170
|
placement = {
|
|
164
171
|
decision,
|
|
165
|
-
reason: `With heights ${firstHeight}px/${secondHeight}px on a 1280px laptop (${
|
|
172
|
+
reason: `With heights ${firstHeight}px/${secondHeight}px on a 1280px laptop (${full}px content width): ${decision}` +
|
|
173
|
+
(decision === 'side' ? `, so each block gets ${half}px.` : `, so each block gets the full ${full}px.`),
|
|
166
174
|
};
|
|
167
175
|
}
|
|
168
176
|
else {
|
|
169
177
|
placement = {
|
|
170
178
|
decision: 'decided-in-viewer',
|
|
171
|
-
reason: "Side-by-side placement depends on each block's measured height — decided in the viewer at runtime.
|
|
179
|
+
reason: "Side-by-side placement depends on each block's measured height — decided in the viewer at runtime. " +
|
|
180
|
+
`The counts above are for the full ${full}px row; side by side each block gets ${half}px instead, which can fit fewer columns. ` +
|
|
181
|
+
'Pass --heights <first>,<second> to resolve it here.',
|
|
172
182
|
};
|
|
173
183
|
}
|
|
174
184
|
}
|
|
185
|
+
const explained = blocks.map((block) => explainBlock(block, blockWidth));
|
|
175
186
|
return { pageId, blocks: explained, placement, intentionalStaticPreviewReason: page.intentionalStaticPreviewReason };
|
|
176
187
|
}
|
|
177
188
|
export function formatExplain(result) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
1
|
+
import React, { useState, useRef } from 'react';
|
|
2
2
|
import { Platform, StyleSheet, Text, View, type LayoutChangeEvent } from 'react-native';
|
|
3
3
|
import { CATALOG_COLOR, CATALOG_RADIUS, CATALOG_TYPE } from './tokens';
|
|
4
|
-
import { DEVICE_FRAME, deviceFrameScale } from './deviceFrame';
|
|
4
|
+
import { DEVICE_FRAME, DEVICE_SCALE_WARN_BELOW, deviceFrameScale } from './deviceFrame';
|
|
5
5
|
import { resolveOverlayViewport } from './overlayViewport';
|
|
6
6
|
import { useSpecimenAddress } from './SpecimenAddress';
|
|
7
7
|
|
|
@@ -27,7 +27,16 @@ function isWeb(): boolean {
|
|
|
27
27
|
* Purely a documentation device: a real app screen never frames itself, so this lives in the
|
|
28
28
|
* catalog framework rather than in an app's component library.
|
|
29
29
|
*/
|
|
30
|
-
export function PhoneFrame({
|
|
30
|
+
export function PhoneFrame({
|
|
31
|
+
children,
|
|
32
|
+
intentionalScale = false,
|
|
33
|
+
}: {
|
|
34
|
+
children: React.ReactNode;
|
|
35
|
+
/** This example is *about* the scaling — a page demonstrating that a narrow cell shrinks the
|
|
36
|
+
* device rather than reflowing it. Declared here, in the example itself, so the exemption is
|
|
37
|
+
* visible where the decision was made instead of hidden in config. */
|
|
38
|
+
intentionalScale?: boolean;
|
|
39
|
+
}) {
|
|
31
40
|
const address = useSpecimenAddress();
|
|
32
41
|
const [available, setAvailable] = useState(0);
|
|
33
42
|
const [status, setStatus] = useState<'loading' | 'ready' | 'unavailable'>('loading');
|
|
@@ -36,6 +45,23 @@ export function PhoneFrame({ children }: { children: React.ReactNode }) {
|
|
|
36
45
|
if (width > 0 && width !== available) setAvailable(width);
|
|
37
46
|
};
|
|
38
47
|
const scale = deviceFrameScale(available);
|
|
48
|
+
// A scaled device is a page that under-declared its width: the cell it was given is narrower
|
|
49
|
+
// than the phone, so the whole viewport shrinks and the example's own text shrinks with it,
|
|
50
|
+
// which is not the size anyone reads the component at. The scale is already computed, so this
|
|
51
|
+
// needs no measurement of its own. Guarded to once per transition, like TokenSections' own
|
|
52
|
+
// degradation warning; `check:catalog` fails on any [Catalog] warning, so this blocks CI.
|
|
53
|
+
const warnedScale = useRef<string | undefined>(undefined);
|
|
54
|
+
if (!intentionalScale && available > 0 && scale < DEVICE_SCALE_WARN_BELOW && (globalThis as { __DEV__?: boolean }).__DEV__) {
|
|
55
|
+
const key = `${available}`;
|
|
56
|
+
if (warnedScale.current !== key) {
|
|
57
|
+
warnedScale.current = key;
|
|
58
|
+
const where = address ? `${address.pageId} (${address.slot}/${address.itemKey})` : 'a PhoneFrame';
|
|
59
|
+
console.warn(
|
|
60
|
+
`[Catalog] ${where} is shown at ${Math.round(scale * 100)}% of device size: its cell is ${available}px, ` +
|
|
61
|
+
`narrower than the ${DEVICE_FRAME.width}px device. Declare a larger specimenSize so the example keeps the width it is used at.`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
39
65
|
// Until the cell is measured, fit it at the device's proportions rather than drawing at full size
|
|
40
66
|
// for a frame (which would briefly overflow a cell narrower than the device).
|
|
41
67
|
const box = available > 0
|
|
@@ -12,3 +12,12 @@ export function deviceFrameScale(availableWidth: number): number {
|
|
|
12
12
|
if (!(availableWidth > 0)) return 1;
|
|
13
13
|
return Math.min(1, availableWidth / DEVICE_FRAME.width);
|
|
14
14
|
}
|
|
15
|
+
|
|
16
|
+
/** Below this, a device is scaled enough that its own text is no longer the size a reader sees it
|
|
17
|
+
* at, and the page should declare a wider specimen instead.
|
|
18
|
+
*
|
|
19
|
+
* Not exactly 1. A regular cell leaves 370px for a 375px device — a 1.3% reduction nobody can
|
|
20
|
+
* see, and warning about it would train authors to ignore the warning. The cases worth a page's
|
|
21
|
+
* attention are the ones that shrink a phone by a tenth or more: a 283px cell renders the device
|
|
22
|
+
* at 75%, which is the difference between readable body text and not. */
|
|
23
|
+
export const DEVICE_SCALE_WARN_BELOW = 0.95;
|
package/package.json
CHANGED
|
@@ -65,6 +65,10 @@ const dock = (close: () => void) => (
|
|
|
65
65
|
export default defineCatalogPage({
|
|
66
66
|
component: 'BottomSheet',
|
|
67
67
|
group: 'Components',
|
|
68
|
+
// Every example is a phone. At the default size two blocks share the row and each cell falls to
|
|
69
|
+
// 270px, which squeezes the frame until the sheet's own text is barely readable — 'wide' keeps a
|
|
70
|
+
// 402px minimum, the width the sheet is actually used at.
|
|
71
|
+
specimenSize: 'wide',
|
|
68
72
|
composedOf: [
|
|
69
73
|
{ component: 'TopNav', role: "The header slot, typically a TopNav with a close or back action.", relationship: 'slot' },
|
|
70
74
|
{ component: 'Dock', role: "The footer slot, typically a Dock with the sheet's primary actions; a Dock footer gets its shadow once the content scrolls.", relationship: 'slot' },
|
|
@@ -33,6 +33,9 @@ function Demo({ title, body, actions }: { title: string; body?: string; actions:
|
|
|
33
33
|
export default defineCatalogPage({
|
|
34
34
|
component: 'Dialog',
|
|
35
35
|
group: 'Components',
|
|
36
|
+
// Every example is a phone. The default size left 283px for a 375px device, rendering it at 75%
|
|
37
|
+
// — small enough that the dialog's own body text stopped being the size a reader sees.
|
|
38
|
+
specimenSize: 'wide',
|
|
36
39
|
composedOf: [
|
|
37
40
|
{ component: 'ButtonGroup', role: "Lays out the dialog's actions; a dialog's own children decide this, not Dialog.", relationship: 'related' },
|
|
38
41
|
],
|