@krapwoo/ds-viewer 0.4.9 → 0.6.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/README.md +74 -8
- package/dist/cli/doctor.js +92 -5
- package/dist/cli/explain.js +11 -20
- package/dist/cli/main.js +11 -2
- package/dist/cli/render.js +338 -0
- package/dist/cli/staticPage.js +27 -4
- package/dist/native/catalog/comparison.js +69 -30
- package/dist/native/catalog/tokens.js +8 -6
- package/native/catalog/CatalogButton.tsx +47 -0
- package/native/catalog/CatalogShell.tsx +29 -11
- package/native/catalog/CatalogSidebar.tsx +18 -6
- package/native/catalog/ComparisonGrid.tsx +9 -8
- package/native/catalog/ComparisonGroups.tsx +6 -5
- package/native/catalog/ComparisonList.tsx +12 -3
- package/native/catalog/OverlayDemo.tsx +52 -0
- package/native/catalog/PhoneScreen.tsx +51 -0
- package/native/catalog/SectionBlock.tsx +16 -11
- package/native/catalog/SpecimenSurface.tsx +4 -4
- package/native/catalog/TokenLayouts.tsx +14 -3
- package/native/catalog/UpdatePanel.tsx +4 -4
- package/native/catalog/comparison.ts +77 -33
- package/native/catalog/index.ts +4 -1
- package/native/catalog/overlayDemoState.ts +8 -0
- package/native/catalog/pageApi.ts +3 -0
- package/native/catalog/specimenSurfaceStyle.ts +11 -0
- package/native/catalog/tokens.ts +8 -6
- package/native/catalog/types.ts +10 -3
- package/package.json +1 -1
- package/starter-kit/components/BottomSheet/BottomSheet.catalog.tsx +38 -48
- package/starter-kit/components/Dialog/Dialog.catalog.tsx +16 -16
- package/starter-kit/components/Toast/Toast.catalog.tsx +9 -10
- package/starter-kit/pages/ReportIssue.catalog.tsx +17 -13
- package/starter-kit/pages/SavedTrips.catalog.tsx +114 -119
|
@@ -17,7 +17,6 @@ import type React from 'react';
|
|
|
17
17
|
* content width is a 1280px viewport minus the 264px sidebar and 32px side padding. */
|
|
18
18
|
export const MATRIX_LAYOUT = {
|
|
19
19
|
rowHeaderWidth: 120,
|
|
20
|
-
columnMaxWidth: 402,
|
|
21
20
|
cellPadding: 16,
|
|
22
21
|
rowMinHeight: 150,
|
|
23
22
|
laptopContentWidth: 952,
|
|
@@ -43,8 +42,9 @@ export const REFERENCE_MIN_COLUMN_WIDTH = 280;
|
|
|
43
42
|
* use). */
|
|
44
43
|
export const REFERENCE_COLUMN_GAP = 32;
|
|
45
44
|
|
|
46
|
-
/** Smallest column or cell width per specimen size.
|
|
47
|
-
|
|
45
|
+
/** Smallest column or cell width per specimen size. `wide` is phone width as a minimum, not a
|
|
46
|
+
* maximum: cells share their container equally above it. */
|
|
47
|
+
export const COLUMN_MIN_WIDTH: Record<SpecimenSize, number> = { compact: 160, regular: 240, wide: 402, page: 512 };
|
|
48
48
|
|
|
49
49
|
/** `Array.isArray`'s built-in type guard narrows to a mutable `any[]`, which a `readonly T[]`
|
|
50
50
|
* union member is never assignable to — so the `else` branch of `Array.isArray(axis) ? ... :
|
|
@@ -106,12 +106,11 @@ export function gridColumnLimit(size: SpecimenSize): number {
|
|
|
106
106
|
return Math.floor((MATRIX_LAYOUT.laptopContentWidth - MATRIX_LAYOUT.rowHeaderWidth) / COLUMN_MIN_WIDTH[size]);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
};
|
|
109
|
+
/** The narrowest a grid card may be: the row header plus every column at its minimum. A card wider
|
|
110
|
+
* than its container scrolls inside itself; above this width the columns share the container
|
|
111
|
+
* equally, because the phone-width cap belongs to the specimen inside a cell, not to the column. */
|
|
112
|
+
export function gridMinWidth(columnCount: number, size: SpecimenSize): number {
|
|
113
|
+
return MATRIX_LAYOUT.rowHeaderWidth + Math.max(0, columnCount) * COLUMN_MIN_WIDTH[size];
|
|
115
114
|
}
|
|
116
115
|
|
|
117
116
|
export interface ListGeometry {
|
|
@@ -123,7 +122,8 @@ export interface ListGeometry {
|
|
|
123
122
|
}
|
|
124
123
|
|
|
125
124
|
/** Balanced wrapping for a one-axis list inside a single bordered card (1px outer border). Each
|
|
126
|
-
* cell's 1px divider sits inside its own width
|
|
125
|
+
* cell's 1px divider sits inside its own width. Cells share the container equally — phone width
|
|
126
|
+
* caps the specimen inside a cell, not the cell. Uses as few
|
|
127
127
|
* rows as fit, then spreads items evenly across them so a short last row leaves as little empty
|
|
128
128
|
* space as possible. `fillers` blank cells complete the last row. `columnCap` (from
|
|
129
129
|
* `VariantSlot.maxColumns`) only ever lowers the natural, width-based column count — a narrow
|
|
@@ -139,7 +139,7 @@ export function listGeometry(itemCount: number, availableWidth: number, size: Sp
|
|
|
139
139
|
const rows = Math.ceil(itemCount / maxColumns);
|
|
140
140
|
const columns = Math.ceil(itemCount / rows);
|
|
141
141
|
const fitted = Math.floor(inner / columns);
|
|
142
|
-
const cellWidth =
|
|
142
|
+
const cellWidth = Math.max(min, fitted);
|
|
143
143
|
return {
|
|
144
144
|
columns,
|
|
145
145
|
rows,
|
|
@@ -253,7 +253,7 @@ export type PresentationBlock =
|
|
|
253
253
|
| { kind: 'grid'; title: string; size: SpecimenSize; comparison: ComparisonDef }
|
|
254
254
|
| { kind: 'grouped'; title: string; size: SpecimenSize; groups: ListGroup[] }
|
|
255
255
|
| { kind: 'list'; title: string; size: SpecimenSize; items: ListItem[]; maxColumns?: 1 | 2 | 3 | 4 | 5 }
|
|
256
|
-
| { kind: 'preview'; title: string; widths: PreviewWidths }
|
|
256
|
+
| { kind: 'preview'; title: string; widths: PreviewWidths; layout?: 'frames' | 'table' }
|
|
257
257
|
| { kind: 'tokenSections'; title: string; sections: TokenSection[]; columns: 1 | 2 | 3 }
|
|
258
258
|
| { kind: 'empty'; title: string; message: string };
|
|
259
259
|
|
|
@@ -278,10 +278,11 @@ function slotColumnCap(slot: VariantSlot): { maxColumns?: 1 | 2 | 3 | 4 | 5 } {
|
|
|
278
278
|
return slot.itemsFill ? { maxColumns: FILLED_SLOT_MAX_COLUMNS } : {};
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
-
function slotItems(slot: VariantSlot, from: 'variants' | 'states'): ListItem[] {
|
|
281
|
+
function slotItems(slot: VariantSlot, from: 'variants' | 'states', fillByDefault = false): ListItem[] {
|
|
282
282
|
// Nullish, not `||`: an item's own explicit `fill: false` must override an inherited
|
|
283
283
|
// `itemsFill: true`, which `false || slot.itemsFill` would silently discard.
|
|
284
|
-
|
|
284
|
+
// Page-width examples (layouts wider than a phone) fill their cell unless they say otherwise.
|
|
285
|
+
return slot.items.map((item) => ({ key: item.key, slot: from, label: item.name, node: item.node, fill: item.fill ?? slot.itemsFill ?? (fillByDefault || undefined), surface: item.surface, align: item.align }));
|
|
285
286
|
}
|
|
286
287
|
|
|
287
288
|
/** Preview widths, each capped at phone width. 'full' is kept for catalog chrome and token pages. */
|
|
@@ -350,11 +351,11 @@ export function presentationBlocks<TId extends string>(def: SectionDef<TId>, opt
|
|
|
350
351
|
kind: 'list',
|
|
351
352
|
title: 'Variants',
|
|
352
353
|
size: slotSize(def, def.variants),
|
|
353
|
-
items: slotItems(def.variants, 'variants'),
|
|
354
|
+
items: slotItems(def.variants, 'variants', def.specimenSize === 'page'),
|
|
354
355
|
...slotColumnCap(def.variants),
|
|
355
356
|
});
|
|
356
357
|
} else if (def.render) {
|
|
357
|
-
blocks.push({ kind: 'preview', title: 'Preview', widths: previewWidths(def.previewWidths, options.defaultPreviewWidths) });
|
|
358
|
+
blocks.push({ kind: 'preview', title: 'Preview', widths: previewWidths(def.previewWidths, options.defaultPreviewWidths), ...(def.previewLayout ? { layout: def.previewLayout } : {}) });
|
|
358
359
|
}
|
|
359
360
|
}
|
|
360
361
|
|
|
@@ -364,7 +365,7 @@ export function presentationBlocks<TId extends string>(def: SectionDef<TId>, opt
|
|
|
364
365
|
kind: 'list',
|
|
365
366
|
title: secondary ? 'Other configurations' : 'States / configurations',
|
|
366
367
|
size: slotSize(def, states),
|
|
367
|
-
items: slotItems(states, 'states'),
|
|
368
|
+
items: slotItems(states, 'states', def.specimenSize === 'page'),
|
|
368
369
|
...slotColumnCap(states),
|
|
369
370
|
});
|
|
370
371
|
}
|
|
@@ -377,9 +378,11 @@ export function presentationBlocks<TId extends string>(def: SectionDef<TId>, opt
|
|
|
377
378
|
|
|
378
379
|
export interface PlacementBlock {
|
|
379
380
|
kind: PresentationBlock['kind'];
|
|
380
|
-
/**
|
|
381
|
-
|
|
382
|
-
|
|
381
|
+
/** The narrowest this block renders without its cells dropping below their minimum, from
|
|
382
|
+
* `blockMinWidth`. A block that can only fill a whole row (a 'full' preview, token sections)
|
|
383
|
+
* reports `Infinity` and never shares one. */
|
|
384
|
+
minWidth?: number;
|
|
385
|
+
/** Measured height of the whole block, label included, while stacked at the full width. */
|
|
383
386
|
height: number;
|
|
384
387
|
/** For a list: item count, specimen size, and any explicit column cap, so its side-by-side
|
|
385
388
|
* height can be predicted from the same geometry the rendered list uses. */
|
|
@@ -388,20 +391,61 @@ export interface PlacementBlock {
|
|
|
388
391
|
maxColumns?: 1 | 2 | 3 | 4 | 5;
|
|
389
392
|
}
|
|
390
393
|
|
|
391
|
-
/**
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
+
/** The narrowest a block renders at. Every card fills the column it is given, so this is a floor,
|
|
395
|
+
* not the width it will take: below it a grid scrolls inside itself and a list would push its
|
|
396
|
+
* cells under their minimum. `Infinity` means the block can only have a whole row. */
|
|
397
|
+
/** What a preview card adds around its frames: `padding` (CATALOG_SPACE.xl) and a 1px border on
|
|
398
|
+
* each side. `blockMinWidth` must add it, or a preview is offered a half-row that fits its frame
|
|
399
|
+
* only after the card has taken its share, and the frame silently renders under phone width.
|
|
400
|
+
* SectionBlock's `previewCard` style is built from these, so the two cannot drift. */
|
|
401
|
+
export const PREVIEW_CARD_CHROME = { padding: 24, border: 1 } as const;
|
|
402
|
+
/** The same for a `previewLayout: 'table'` card: no outer padding, a 1px border, and each cell
|
|
403
|
+
* carries `cellPadding` on both sides of its frame. */
|
|
404
|
+
export const TABLE_PREVIEW_CHROME = { padding: MATRIX_LAYOUT.cellPadding, border: 1 } as const;
|
|
405
|
+
/** Total horizontal chrome for one preview frame in either card. */
|
|
406
|
+
export function previewChrome(layout: 'frames' | 'table' | undefined): number {
|
|
407
|
+
const chrome = layout === 'table' ? TABLE_PREVIEW_CHROME : PREVIEW_CARD_CHROME;
|
|
408
|
+
return 2 * chrome.padding + 2 * chrome.border;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export function blockMinWidth(block: PresentationBlock): number {
|
|
412
|
+
switch (block.kind) {
|
|
413
|
+
case 'grid':
|
|
414
|
+
return gridMinWidth(axisItems(block.comparison.columns).length, block.size);
|
|
415
|
+
case 'grouped':
|
|
416
|
+
return MATRIX_LAYOUT.rowHeaderWidth + Math.max(1, ...block.groups.map((g) => g.items.length)) * COLUMN_MIN_WIDTH[block.size] + 2;
|
|
417
|
+
case 'list':
|
|
418
|
+
// One column: a list always wraps further rather than going below its size's minimum.
|
|
419
|
+
return COLUMN_MIN_WIDTH[block.size] + 2;
|
|
420
|
+
case 'preview':
|
|
421
|
+
// A 'full' preview is the row. Otherwise the *widest* frame sets the minimum: the frames wrap,
|
|
422
|
+
// so each one sits on its own row and `maxWidth: '100%'` would squeeze any frame wider than
|
|
423
|
+
// the card. The card's own chrome counts too — a frame keeps its width only if the row also
|
|
424
|
+
// covers the padding and border around it.
|
|
425
|
+
return block.widths === 'full' ? Infinity : Math.max(...block.widths) + previewChrome(block.layout);
|
|
426
|
+
default:
|
|
427
|
+
return Infinity;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/** The height a block takes at `width`, from the height measured while stacked at `available`.
|
|
432
|
+
* Only a list reflows: its rows change with the width, and every row is the same height. */
|
|
433
|
+
function heightAtWidth(block: PlacementBlock, width: number, available: number): number {
|
|
434
|
+
if (block.kind !== 'list' || !block.itemCount || !block.size) return block.height;
|
|
435
|
+
const stackedRows = listGeometry(block.itemCount, available, block.size, block.maxColumns).rows;
|
|
436
|
+
const rows = listGeometry(block.itemCount, width, block.size, block.maxColumns).rows;
|
|
437
|
+
return (block.height / Math.max(1, stackedRows)) * rows;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/** Side by side or stacked, for a page with exactly two blocks. Side by side splits the row into
|
|
441
|
+
* two equal columns and each block fills its own, so it is only possible when both blocks keep
|
|
442
|
+
* their minimum width in half a row; it is only taken when it saves at least PLACEMENT_MIN_SAVING
|
|
443
|
+
* against stacking, measuring each block at the width it would actually have. */
|
|
394
444
|
export function choosePlacement({ available, gap, first, second }: { available: number; gap: number; first: PlacementBlock; second: PlacementBlock }): 'side' | 'stacked' {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
if (
|
|
398
|
-
const
|
|
399
|
-
const minCell = second.size === 'wide' ? MATRIX_LAYOUT.columnMaxWidth : COLUMN_MIN_WIDTH[second.size];
|
|
400
|
-
if (room < minCell + 2) return 'stacked';
|
|
401
|
-
const stackedRows = listGeometry(second.itemCount, available, second.size, second.maxColumns).rows;
|
|
402
|
-
const sideRows = listGeometry(second.itemCount, room, second.size, second.maxColumns).rows;
|
|
403
|
-
const rowHeight = second.height / Math.max(1, stackedRows);
|
|
404
|
-
const sideHeight = Math.max(first.height, sideRows * rowHeight);
|
|
445
|
+
const half = Math.floor((available - gap) / 2);
|
|
446
|
+
if (half <= 0) return 'stacked';
|
|
447
|
+
if ((first.minWidth ?? Infinity) > half || (second.minWidth ?? Infinity) > half) return 'stacked';
|
|
448
|
+
const sideHeight = Math.max(heightAtWidth(first, half, available), heightAtWidth(second, half, available));
|
|
405
449
|
const stackedHeight = first.height + gap + second.height;
|
|
406
450
|
return sideHeight <= stackedHeight - PLACEMENT_MIN_SAVING ? 'side' : 'stacked';
|
|
407
451
|
}
|
package/native/catalog/index.ts
CHANGED
|
@@ -31,6 +31,9 @@ export { TokenSections, TokenGrid, TokenTile } from './TokenLayouts';
|
|
|
31
31
|
export { DividedStack } from './DividedStack';
|
|
32
32
|
export { Swatch } from './Swatch';
|
|
33
33
|
export { PhoneFrame } from './PhoneFrame';
|
|
34
|
+
export { PhoneScreen } from './PhoneScreen';
|
|
35
|
+
export { OverlayDemo } from './OverlayDemo';
|
|
36
|
+
export { CatalogButton } from './CatalogButton';
|
|
34
37
|
export { BoundedOverlayViewport } from './BoundedOverlayViewport';
|
|
35
38
|
export type { BoundedOverlayViewportProps } from './BoundedOverlayViewport';
|
|
36
39
|
export { comparisonCellItemKey } from './overlayViewport';
|
|
@@ -47,7 +50,7 @@ export type {
|
|
|
47
50
|
} from './types';
|
|
48
51
|
export { UpdatePanel } from './UpdatePanel';
|
|
49
52
|
export { UPDATE_PAGE_ID } from './catalogNavigation';
|
|
50
|
-
export type { UpdateNotice } from './types';
|
|
53
|
+
export type { UpdateNotice, VersionStatus, AutoCheckSource } from './types';
|
|
51
54
|
export type { ListGroup, ListItem } from './comparison';
|
|
52
55
|
export {
|
|
53
56
|
CATALOG_TYPE,
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** What `OverlayDemo` shows for a given open state. Kept free of runtime imports for `node --test`. */
|
|
2
|
+
export function overlayTrigger(
|
|
3
|
+
open: boolean,
|
|
4
|
+
options: { triggerLabel: string | ((open: boolean) => string); keepTrigger?: boolean },
|
|
5
|
+
): { visible: boolean; label: string } {
|
|
6
|
+
const label = typeof options.triggerLabel === 'function' ? options.triggerLabel(open) : options.triggerLabel;
|
|
7
|
+
return { visible: !open || Boolean(options.keepTrigger), label };
|
|
8
|
+
}
|
|
@@ -37,6 +37,8 @@ export interface CatalogPageInput<TId extends string = string> {
|
|
|
37
37
|
composedOf?: ComposedOfEntry[];
|
|
38
38
|
/** See `SectionDef.intentionalStaticPreview`. */
|
|
39
39
|
intentionalStaticPreview?: { reason: string };
|
|
40
|
+
/** See `SectionDef.internalProps`. */
|
|
41
|
+
internalProps?: Record<string, string>;
|
|
40
42
|
/** Export name this page documents; defaults to `id` for component pages. */
|
|
41
43
|
component?: string;
|
|
42
44
|
/** Sidebar group label. */
|
|
@@ -175,6 +177,7 @@ export function buildCatalogSections<TId extends string>(
|
|
|
175
177
|
previewLayout: page.previewLayout,
|
|
176
178
|
hide: component ? page.hide : { ...page.hide, props: true },
|
|
177
179
|
composedOf: page.composedOf,
|
|
180
|
+
internalProps: page.internalProps,
|
|
178
181
|
intentionalStaticPreview: page.intentionalStaticPreview,
|
|
179
182
|
};
|
|
180
183
|
});
|
|
@@ -142,3 +142,14 @@ export function visibleBoxes(root: LayoutBox): { left: number; right: number; pa
|
|
|
142
142
|
walk(root, true);
|
|
143
143
|
return out;
|
|
144
144
|
}
|
|
145
|
+
|
|
146
|
+
/** The widest a filled example gets: a phone's content width (402 minus 16 px padding each side).
|
|
147
|
+
* Fields, dropdowns and full-width buttons fill a phone's width in real use, so a wider cell would
|
|
148
|
+
* misrepresent them. Examples that aren't filled keep their own width. */
|
|
149
|
+
export const FILLED_CONTENT_MAX_WIDTH = 370;
|
|
150
|
+
|
|
151
|
+
/** The wrapper style for a filled example: full width up to `FILLED_CONTENT_MAX_WIDTH`, centred.
|
|
152
|
+
* `capped: false` (page-width examples) fills the whole cell. */
|
|
153
|
+
export function filledContentStyle(capped = true): { width: '100%'; maxWidth?: number; alignSelf: 'center' } {
|
|
154
|
+
return capped ? { width: '100%', maxWidth: FILLED_CONTENT_MAX_WIDTH, alignSelf: 'center' } : { width: '100%', alignSelf: 'center' };
|
|
155
|
+
}
|
package/native/catalog/tokens.ts
CHANGED
|
@@ -11,15 +11,17 @@
|
|
|
11
11
|
* `textMuted` stays #666666 because the references' #777777 fails WCAG AA contrast.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
/** Owner decision 2026-10-11: only 10, 12, 14, 16, 18, 24, 28, 32 and 40. Named roles share a
|
|
15
|
+
* step (tableHeader and panelHeading are 12, brand is 18). */
|
|
14
16
|
export const CATALOG_TYPE = {
|
|
15
17
|
xs: 10,
|
|
16
|
-
tableHeader:
|
|
18
|
+
tableHeader: 12,
|
|
17
19
|
sm: 12,
|
|
18
|
-
panelHeading:
|
|
20
|
+
panelHeading: 12,
|
|
19
21
|
md: 14,
|
|
20
22
|
lg: 16,
|
|
21
|
-
brand:
|
|
22
|
-
xl:
|
|
23
|
+
brand: 18,
|
|
24
|
+
xl: 18,
|
|
23
25
|
'2xl': 24,
|
|
24
26
|
pageTitle: 28,
|
|
25
27
|
'3xl': 32,
|
|
@@ -82,8 +84,8 @@ export const CATALOG_TYPE_USE: Record<keyof typeof CATALOG_TYPE, string> = {
|
|
|
82
84
|
md: 'Default body text: descriptions, nav links, grid row headers, quick-reference rows, the filter field.',
|
|
83
85
|
lg: 'Pager arrows and the filter field\'s clear (×) glyph.',
|
|
84
86
|
brand: 'The app name at the top of the sidebar.',
|
|
85
|
-
xl: '
|
|
86
|
-
'2xl': '
|
|
87
|
+
xl: "The major-update banner's close (×) glyph.",
|
|
88
|
+
'2xl': "The update page's version numbers and the arrow between them.",
|
|
87
89
|
pageTitle: 'The selected page\'s title.',
|
|
88
90
|
'3xl': 'Reserved — no current consumer.',
|
|
89
91
|
'4xl': 'Reserved — no current consumer.',
|
package/native/catalog/types.ts
CHANGED
|
@@ -75,9 +75,10 @@ export interface VariantSlot {
|
|
|
75
75
|
items: VariantExample[];
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
/** Width class for a page's specimens: compact (min 160px), regular (min 240px),
|
|
79
|
-
*
|
|
80
|
-
|
|
78
|
+
/** Width class for a page's specimens: compact (min 160px), regular (min 240px), wide (min 402px,
|
|
79
|
+
* phone width), or page (min 512px) for components wider than a phone, such as a whole catalog
|
|
80
|
+
* page. Each is a minimum: cells share the row they are given. Sets grid column and cell widths. */
|
|
81
|
+
export type SpecimenSize = 'compact' | 'regular' | 'wide' | 'page';
|
|
81
82
|
|
|
82
83
|
/** Widths for a component preview: one or more phone widths (each capped at 402px), or 'full' for
|
|
83
84
|
* content that is not a phone component (catalog chrome, token galleries). */
|
|
@@ -267,6 +268,12 @@ export interface SectionDef<TId extends string = string> {
|
|
|
267
268
|
* sentence explaining why (not free-form prose) — e.g. "Selection is already demonstrated by X's
|
|
268
269
|
* working preview." Omit when no such declaration applies; the advisory then runs normally. */
|
|
269
270
|
intentionalStaticPreview?: { reason: string };
|
|
271
|
+
/** Props the reader never chooses: plumbing a parent passes (a layout size, an internal surface
|
|
272
|
+
* kind). `cli/doctor.ts`'s `option-not-covered` advisory then skips them, instead of asking for
|
|
273
|
+
* an example of every option of a prop nobody picks by hand. Each entry needs a short reason,
|
|
274
|
+
* so the exemption is a visible declaration in the page rather than a silent gap — write an
|
|
275
|
+
* example instead whenever the reader really does choose the value. */
|
|
276
|
+
internalProps?: Record<string, string>;
|
|
270
277
|
}
|
|
271
278
|
|
|
272
279
|
/** A labeled group of section ids in the sidebar (e.g. "Components" vs "Tokens"). */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react';
|
|
2
2
|
import { View, Text, StyleSheet } from 'react-native';
|
|
3
|
-
import { defineCatalogPage,
|
|
3
|
+
import { defineCatalogPage, OverlayDemo } from '@krapwoo/ds-viewer';
|
|
4
4
|
import { Button } from '../Button';
|
|
5
5
|
import { Dock } from '../Dock';
|
|
6
6
|
import { TopNav } from '../TopNav';
|
|
@@ -17,53 +17,43 @@ const styles = StyleSheet.create({
|
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
function BottomSheetDemo() {
|
|
20
|
-
const [visible, setVisible] = useState(false);
|
|
21
20
|
return (
|
|
22
|
-
<
|
|
23
|
-
{
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
planned maintenance.
|
|
59
|
-
</Text>
|
|
60
|
-
<Text style={styles.cardBody}>
|
|
61
|
-
Elevators are available at 168 St, 137 St, and 125 St — check the map for accessible
|
|
62
|
-
entrances before you travel.
|
|
63
|
-
</Text>
|
|
64
|
-
</View>
|
|
65
|
-
</BottomSheet>
|
|
66
|
-
</PhoneFrame>
|
|
21
|
+
<OverlayDemo triggerLabel="Open sheet">
|
|
22
|
+
{({ open, close }) => (
|
|
23
|
+
<BottomSheet
|
|
24
|
+
visible={open}
|
|
25
|
+
onDismiss={close}
|
|
26
|
+
header={
|
|
27
|
+
<TopNav
|
|
28
|
+
title="Trip details"
|
|
29
|
+
trailing={<Button variant="secondary" size="small" showIcon showLabel={false} iconName="clear" accessibilityLabel="Close" onPress={close} />}
|
|
30
|
+
/>
|
|
31
|
+
}
|
|
32
|
+
footer={
|
|
33
|
+
<Dock>
|
|
34
|
+
<Button label="Confirm" onPress={close} />
|
|
35
|
+
</Dock>
|
|
36
|
+
}
|
|
37
|
+
>
|
|
38
|
+
<View style={styles.sheetContent}>
|
|
39
|
+
<Text style={styles.cardTitle}>Uptown & The Bronx</Text>
|
|
40
|
+
<Text style={styles.cardBody}>Next train in 4 min · every 6–8 min.</Text>
|
|
41
|
+
{/* Long enough to overflow the phone frame's fixed height — demonstrates the Dock footer's
|
|
42
|
+
`elevated` shadow turning on automatically once this content area actually scrolls. */}
|
|
43
|
+
<Text style={styles.cardBody}>Board at the front car for a faster transfer at Union Sq.</Text>
|
|
44
|
+
<Text style={styles.cardBody}>
|
|
45
|
+
This line runs express between 96 St and 168 St during rush hours, skipping local stops in
|
|
46
|
+
between. Weekend service runs local along the full route, with some stations closed for
|
|
47
|
+
planned maintenance.
|
|
48
|
+
</Text>
|
|
49
|
+
<Text style={styles.cardBody}>
|
|
50
|
+
Elevators are available at 168 St, 137 St, and 125 St — check the map for accessible
|
|
51
|
+
entrances before you travel.
|
|
52
|
+
</Text>
|
|
53
|
+
</View>
|
|
54
|
+
</BottomSheet>
|
|
55
|
+
)}
|
|
56
|
+
</OverlayDemo>
|
|
67
57
|
);
|
|
68
58
|
}
|
|
69
59
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react';
|
|
2
2
|
import { View, Text, StyleSheet } from 'react-native';
|
|
3
|
-
import { defineCatalogPage,
|
|
3
|
+
import { defineCatalogPage, OverlayDemo } from '@krapwoo/ds-viewer';
|
|
4
4
|
import { Button } from '../Button';
|
|
5
5
|
import { ButtonGroup } from '../ButtonGroup';
|
|
6
6
|
import { Dialog } from './Dialog';
|
|
@@ -13,21 +13,21 @@ const styles = StyleSheet.create({
|
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
function DialogDemo() {
|
|
16
|
-
const [visible, setVisible] = useState(false);
|
|
17
16
|
return (
|
|
18
|
-
<
|
|
19
|
-
{
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
17
|
+
<OverlayDemo triggerLabel="Open dialog">
|
|
18
|
+
{({ open, close }) => (
|
|
19
|
+
<Dialog visible={open} onDismiss={close}>
|
|
20
|
+
<Text style={styles.cardTitle}>Delete this trip?</Text>
|
|
21
|
+
<Text style={styles.cardBody}>This can't be undone.</Text>
|
|
22
|
+
<View style={styles.dialogActions}>
|
|
23
|
+
<ButtonGroup>
|
|
24
|
+
<Button label="Cancel" variant="tertiary" onPress={close} />
|
|
25
|
+
<Button label="Delete" onPress={close} />
|
|
26
|
+
</ButtonGroup>
|
|
27
|
+
</View>
|
|
28
|
+
</Dialog>
|
|
29
|
+
)}
|
|
30
|
+
</OverlayDemo>
|
|
31
31
|
);
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react';
|
|
2
2
|
import { View, StyleSheet } from 'react-native';
|
|
3
|
-
import { defineCatalogPage,
|
|
4
|
-
import { Button } from '../Button';
|
|
3
|
+
import { defineCatalogPage, OverlayDemo } from '@krapwoo/ds-viewer';
|
|
5
4
|
import { Toast } from './Toast';
|
|
6
5
|
import { DS_SPACING } from '../../tokens';
|
|
7
6
|
|
|
@@ -15,14 +14,14 @@ const styles = StyleSheet.create({
|
|
|
15
14
|
// pattern) so this demo actually exercises the slide-in-from-top/fade animation, not just the two
|
|
16
15
|
// static end states.
|
|
17
16
|
function ToastDemo() {
|
|
18
|
-
const [visible, setVisible] = useState(true);
|
|
19
17
|
return (
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
<OverlayDemo triggerLabel={(open) => (open ? 'Hide toast' : 'Show toast')} keepTrigger initiallyOpen>
|
|
19
|
+
{({ open }) => (
|
|
20
|
+
<View style={styles.toastDemoOverlay} pointerEvents="box-none">
|
|
21
|
+
<Toast message="Trip saved" variant="success" visible={open} />
|
|
22
|
+
</View>
|
|
23
|
+
)}
|
|
24
|
+
</OverlayDemo>
|
|
26
25
|
);
|
|
27
26
|
}
|
|
28
27
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import { View, Text, StyleSheet } from 'react-native';
|
|
3
|
-
import { defineCatalogPage,
|
|
3
|
+
import { defineCatalogPage, OverlayDemo } from '@krapwoo/ds-viewer';
|
|
4
4
|
import { Banner } from '../components/Banner';
|
|
5
5
|
import { BottomSheet } from '../components/BottomSheet';
|
|
6
6
|
import { Button } from '../components/Button';
|
|
@@ -51,7 +51,6 @@ const REPORT_ISSUE_TYPE_PILLS: PillRowItem[] = [
|
|
|
51
51
|
];
|
|
52
52
|
|
|
53
53
|
function ReportIssueDemo() {
|
|
54
|
-
const [visible, setVisible] = useState(false);
|
|
55
54
|
const [confirmDiscard, setConfirmDiscard] = useState(false);
|
|
56
55
|
const [issueType, setIssueType] = useState('delay');
|
|
57
56
|
const [line, setLine] = useState('4');
|
|
@@ -60,17 +59,19 @@ function ReportIssueDemo() {
|
|
|
60
59
|
const [visibility, setVisibility] = useState('public');
|
|
61
60
|
const [notify, setNotify] = useState(true);
|
|
62
61
|
|
|
63
|
-
const requestClose = () => setConfirmDiscard(true);
|
|
64
|
-
const discard = () => {
|
|
65
|
-
setConfirmDiscard(false);
|
|
66
|
-
setVisible(false);
|
|
67
|
-
};
|
|
68
|
-
|
|
69
62
|
return (
|
|
70
|
-
<
|
|
71
|
-
{
|
|
63
|
+
<OverlayDemo triggerLabel="Report an issue">
|
|
64
|
+
{({ open, close }) => {
|
|
65
|
+
// Closing asks first; discarding confirms and closes the sheet.
|
|
66
|
+
const requestClose = () => setConfirmDiscard(true);
|
|
67
|
+
const discard = () => {
|
|
68
|
+
setConfirmDiscard(false);
|
|
69
|
+
close();
|
|
70
|
+
};
|
|
71
|
+
return (
|
|
72
|
+
<>
|
|
72
73
|
<BottomSheet
|
|
73
|
-
visible={
|
|
74
|
+
visible={open}
|
|
74
75
|
onDismiss={requestClose}
|
|
75
76
|
header={
|
|
76
77
|
<TopNav
|
|
@@ -82,7 +83,7 @@ function ReportIssueDemo() {
|
|
|
82
83
|
}
|
|
83
84
|
footer={
|
|
84
85
|
<Dock>
|
|
85
|
-
<Button label="Submit report" onPress={
|
|
86
|
+
<Button label="Submit report" onPress={close} />
|
|
86
87
|
</Dock>
|
|
87
88
|
}
|
|
88
89
|
>
|
|
@@ -135,7 +136,10 @@ function ReportIssueDemo() {
|
|
|
135
136
|
</ButtonGroup>
|
|
136
137
|
</View>
|
|
137
138
|
</Dialog>
|
|
138
|
-
|
|
139
|
+
</>
|
|
140
|
+
);
|
|
141
|
+
}}
|
|
142
|
+
</OverlayDemo>
|
|
139
143
|
);
|
|
140
144
|
}
|
|
141
145
|
|