@krapwoo/ds-viewer 0.9.1 → 0.11.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.
Files changed (37) hide show
  1. package/README.md +82 -0
  2. package/dist/cli/doctor.js +182 -3
  3. package/dist/cli/staticPage.js +129 -0
  4. package/dist/native/catalog/catalogNavigation.js +62 -3
  5. package/dist/native/catalog/comparison.js +14 -3
  6. package/dist/native/catalog/pageApi.js +408 -0
  7. package/dist/native/catalog/tokens.js +67 -12
  8. package/native/catalog/BoundedOverlayViewport.tsx +2 -2
  9. package/native/catalog/CatalogButton.tsx +2 -2
  10. package/native/catalog/CatalogSearchInput.tsx +3 -3
  11. package/native/catalog/CatalogShell.tsx +213 -32
  12. package/native/catalog/CatalogSidebar.tsx +69 -19
  13. package/native/catalog/ComparisonGrid.tsx +4 -4
  14. package/native/catalog/ComparisonGroups.tsx +3 -3
  15. package/native/catalog/ComparisonList.tsx +2 -2
  16. package/native/catalog/FamilyPage.tsx +1188 -0
  17. package/native/catalog/MotionSpecimen.tsx +3 -3
  18. package/native/catalog/PhoneFrame.tsx +2 -2
  19. package/native/catalog/PropsTable.tsx +7 -7
  20. package/native/catalog/ReferenceDetails.tsx +33 -22
  21. package/native/catalog/SectionBlock.tsx +57 -27
  22. package/native/catalog/SpacingScaleGallery.tsx +3 -3
  23. package/native/catalog/Swatch.tsx +3 -3
  24. package/native/catalog/TokenLayouts.tsx +6 -9
  25. package/native/catalog/TokenRow.tsx +2 -2
  26. package/native/catalog/TypeScaleGallery.tsx +2 -2
  27. package/native/catalog/UpdatePanel.tsx +29 -30
  28. package/native/catalog/VariantGroup.tsx +3 -6
  29. package/native/catalog/catalogNavigation.ts +89 -3
  30. package/native/catalog/comparison.ts +17 -6
  31. package/native/catalog/familyModel.ts +192 -0
  32. package/native/catalog/index.ts +8 -2
  33. package/native/catalog/overlayViewport.ts +30 -10
  34. package/native/catalog/pageApi.ts +357 -15
  35. package/native/catalog/tokens.ts +70 -12
  36. package/native/catalog/types.ts +106 -0
  37. package/package.json +1 -1
@@ -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, CATALOG_TYPE } from './tokens';
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: { fontSize: CATALOG_TYPE.sm, color: CATALOG_COLOR.textMuted },
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: { fontSize: CATALOG_TYPE.sm, fontWeight: '700', color: CATALOG_COLOR.text },
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, CATALOG_TYPE } from './tokens';
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', fontSize: CATALOG_TYPE.sm, color: CATALOG_COLOR.textMuted },
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 { CATALOG_TYPE, CATALOG_COLOR, CATALOG_SPACE } from './tokens';
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: { fontSize: CATALOG_TYPE.sm, fontFamily: CATALOG_COLOR.code, fontWeight: '700', color: CATALOG_COLOR.text },
90
- optionalMark: { fontWeight: '400', color: CATALOG_COLOR.textMuted },
91
- type: { fontSize: CATALOG_TYPE.xs, fontFamily: CATALOG_COLOR.code, color: CATALOG_COLOR.accent },
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: { fontSize: CATALOG_TYPE.sm, color: CATALOG_COLOR.textMuted, lineHeight: 17 },
95
- default: { fontSize: CATALOG_TYPE.xs, color: CATALOG_COLOR.textMuted },
96
- defaultVal: { fontFamily: CATALOG_COLOR.code, color: CATALOG_COLOR.textMuted },
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 },
@@ -1,6 +1,6 @@
1
1
  import React, { useState } from 'react';
2
2
  import { View, Text, StyleSheet, type LayoutChangeEvent, type TextStyle } from 'react-native';
3
- import { CATALOG_COLOR, CATALOG_LAYOUT, CATALOG_RADIUS, CATALOG_SPACE, CATALOG_TYPE } from './tokens';
3
+ import { CATALOG_COLOR, CATALOG_LAYOUT, CATALOG_RADIUS, CATALOG_SPACE, CATALOG_TEXT } from './tokens';
4
4
  import { PropsTable } from './PropsTable';
5
5
  import { propsColumns, referenceColumns } from './comparison';
6
6
  import type { ComposedOfEntry, SectionDef } from './types';
@@ -8,9 +8,12 @@ import type { ComposedOfEntry, SectionDef } from './types';
8
8
  // react-native-web reads `aria-level`; React Native's prop types do not declare it.
9
9
  const HEADING_LEVEL_2 = { 'aria-level': 2 } as Record<string, unknown>;
10
10
 
11
- function Fact({ label, value, mono = false }: { label: string; value: string; mono?: boolean }) {
11
+ /** `last` drops the trailing divider and its padding, the same convention `TokenRow`'s `last` and
12
+ * `PropsTable`'s `rowLast` already use. Without it the final fact's bottom padding stacks on the
13
+ * card's own `panelPadding`, so a card's contents sat ~10px lower than centred. */
14
+ function Fact({ label, value, mono = false, last = false }: { label: string; value: string; mono?: boolean; last?: boolean }) {
12
15
  return (
13
- <View style={styles.fact}>
16
+ <View style={[styles.fact, last && styles.factLast]}>
14
17
  <Text style={styles.factLabel}>{label}</Text>
15
18
  <Text style={[styles.factValue, mono && styles.mono]}>{value}</Text>
16
19
  </View>
@@ -47,11 +50,16 @@ function compositionLabel(entry: ComposedOfEntry): string {
47
50
  return entry.relationship === 'built-in' ? entry.component : `${entry.component} · ${entry.relationship}`;
48
51
  }
49
52
 
50
- function CompositionFacts({ composedOf }: { composedOf: ComposedOfEntry[] }) {
53
+ function CompositionFacts({ composedOf, lastIsFlush = false }: { composedOf: ComposedOfEntry[]; lastIsFlush?: boolean }) {
51
54
  return (
52
55
  <>
53
56
  {composedOf.map((entry, i) => (
54
- <Fact key={`${entry.component}-${entry.relationship}-${i}`} label={compositionLabel(entry)} value={entry.role} />
57
+ <Fact
58
+ key={`${entry.component}-${entry.relationship}-${i}`}
59
+ label={compositionLabel(entry)}
60
+ value={entry.role}
61
+ last={lastIsFlush && i === composedOf.length - 1}
62
+ />
55
63
  ))}
56
64
  </>
57
65
  );
@@ -91,11 +99,11 @@ export function ReferenceDetails<TId extends string>({ def }: { def: SectionDef<
91
99
  return (
92
100
  <View style={styles.card}>
93
101
  <Text role="heading" {...HEADING_LEVEL_2} style={styles.heading}>Quick reference</Text>
94
- {def.path && <Fact label="Source" value={def.path} mono />}
102
+ {def.path && <Fact label="Source" value={def.path} mono last={!(def.composedOf && def.composedOf.length > 0)} />}
95
103
  {def.composedOf && def.composedOf.length > 0 && (
96
104
  <>
97
105
  <Text style={styles.subheading}>Composition</Text>
98
- <CompositionFacts composedOf={def.composedOf} />
106
+ <CompositionFacts composedOf={def.composedOf} lastIsFlush />
99
107
  </>
100
108
  )}
101
109
  </View>
@@ -117,19 +125,25 @@ export function ReferenceDetails<TId extends string>({ def }: { def: SectionDef<
117
125
  </View>
118
126
  <View style={columnStyle}>
119
127
  <Text role="heading" {...HEADING_LEVEL_2} style={styles.heading}>Quick reference</Text>
120
- {def.path && <Fact label="Source" value={def.path} mono />}
121
- {!hide.accessibility && <Fact label="Accessibility" value={def.a11y ?? 'No accessibility notes documented.'} />}
128
+ {def.path && <Fact label="Source" value={def.path} mono last={hide.accessibility && !(!compositionIsOwnColumn && hasComposition)} />}
129
+ {!hide.accessibility && (
130
+ <Fact
131
+ label="Accessibility"
132
+ value={def.a11y ?? 'No accessibility notes documented.'}
133
+ last={!(!compositionIsOwnColumn && hasComposition)}
134
+ />
135
+ )}
122
136
  {!compositionIsOwnColumn && hasComposition && def.composedOf && (
123
137
  <>
124
138
  <Text style={styles.subheading}>Composition</Text>
125
- <CompositionFacts composedOf={def.composedOf} />
139
+ <CompositionFacts composedOf={def.composedOf} lastIsFlush />
126
140
  </>
127
141
  )}
128
142
  </View>
129
143
  {compositionIsOwnColumn && def.composedOf && (
130
144
  <View style={columnStyle}>
131
145
  <Text role="heading" {...HEADING_LEVEL_2} style={styles.heading}>Composition</Text>
132
- <CompositionFacts composedOf={def.composedOf} />
146
+ <CompositionFacts composedOf={def.composedOf} lastIsFlush />
133
147
  </View>
134
148
  )}
135
149
  </View>
@@ -153,17 +167,13 @@ const styles = StyleSheet.create({
153
167
  column: { flex: 1, minWidth: 0 },
154
168
  columnStacked: { minWidth: 0 },
155
169
  heading: {
156
- fontSize: CATALOG_TYPE.panelHeading,
157
- fontWeight: '700',
158
- letterSpacing: 0.52,
159
- textTransform: 'uppercase',
170
+ ...CATALOG_TEXT.label,
160
171
  color: CATALOG_COLOR.text,
161
172
  marginBottom: 10,
162
173
  },
163
- body: { fontSize: CATALOG_TYPE.md, lineHeight: 20, color: CATALOG_COLOR.textMuted },
174
+ body: { ...CATALOG_TEXT.body, color: CATALOG_COLOR.textMuted },
164
175
  subheading: {
165
- fontSize: CATALOG_TYPE.sm,
166
- fontWeight: '700',
176
+ ...CATALOG_TEXT.captionStrong,
167
177
  color: CATALOG_COLOR.text,
168
178
  marginTop: CATALOG_SPACE.md,
169
179
  marginBottom: CATALOG_SPACE.xs,
@@ -175,14 +185,15 @@ const styles = StyleSheet.create({
175
185
  borderBottomWidth: 1,
176
186
  borderBottomColor: CATALOG_COLOR.borderSubtle,
177
187
  },
178
- factLabel: { flexShrink: 1, minWidth: 0, fontSize: CATALOG_TYPE.md, fontWeight: '700', color: CATALOG_COLOR.text },
179
- factValue: { minWidth: 0, fontSize: CATALOG_TYPE.md, lineHeight: 20, textAlign: 'left', color: CATALOG_COLOR.textMuted },
188
+ factLast: { paddingBottom: 0, borderBottomWidth: 0 },
189
+ factLabel: { flexShrink: 1, minWidth: 0, ...CATALOG_TEXT.bodyStrong, color: CATALOG_COLOR.text },
190
+ factValue: { minWidth: 0, ...CATALOG_TEXT.body, textAlign: 'left', color: CATALOG_COLOR.textMuted },
180
191
  // `wordBreak` isn't in the installed `TextStyle` type (RN's own, not RNW's — RNW's own
181
192
  // `Text/types.js` declares it; a browser applies any valid CSS property name it recognizes
182
193
  // regardless), so a bare literal here would report an unknown property under `--noEmit` even
183
194
  // though it genuinely works: a long unbroken token (a source path with no spaces, e.g.
184
195
  // `AnimatedChevron.tsx`) has no space for the browser's default line-breaking to use, so without
185
196
  // this it stays one unbroken run and overflows a narrow card instead of wrapping at its '/'s.
186
- mono: { fontFamily: CATALOG_COLOR.code, wordBreak: 'break-word' } as TextStyle,
187
- empty: { fontSize: CATALOG_TYPE.sm, fontStyle: 'italic', color: CATALOG_COLOR.textMuted },
197
+ mono: { ...CATALOG_TEXT.code, wordBreak: 'break-word' } as TextStyle,
198
+ empty: { ...CATALOG_TEXT.caption, color: CATALOG_COLOR.textMuted },
188
199
  });
@@ -1,6 +1,6 @@
1
1
  import React, { useEffect, useMemo, useState } from 'react';
2
2
  import { View, Text, Pressable, StyleSheet, type LayoutChangeEvent } from 'react-native';
3
- import { CATALOG_COLOR, CATALOG_LAYOUT, CATALOG_RADIUS, CATALOG_SPACE, CATALOG_TYPE } from './tokens';
3
+ import { CATALOG_COLOR, CATALOG_LAYOUT, CATALOG_RADIUS, CATALOG_SPACE, CATALOG_TEXT } from './tokens';
4
4
  import { ComparisonGrid } from './ComparisonGrid';
5
5
  import { ComparisonGroups } from './ComparisonGroups';
6
6
  import { ComparisonList } from './ComparisonList';
@@ -53,7 +53,7 @@ export interface SectionPager<TId extends string> {
53
53
  labelOf?: (id: TId) => string;
54
54
  }
55
55
 
56
- function PagerButton<TId extends string>({
56
+ export function PagerButton<TId extends string>({
57
57
  direction,
58
58
  targetId,
59
59
  onNavigate,
@@ -209,7 +209,40 @@ function placementBlock(block: PresentationBlock, height: number): PlacementBloc
209
209
  * are never recorded while side by side, so the decision never feeds on its own result; a width
210
210
  * change returns to stacked and decides again.
211
211
  */
212
- function Blocks<TId extends string>({ blocks, def, surface }: { blocks: PresentationBlock[]; def: SectionDef<TId>; surface: SpecimenSurfaceKind }) {
212
+ /** The previous/next pager buttons, in their row — shared by `SectionBlock` and `FamilyPage` so a
213
+ * family page's own page head gets the same pager chrome as every other page. */
214
+ export function SectionPagerRow<TId extends string>({ pager }: { pager: SectionPager<TId> }) {
215
+ return (
216
+ <View style={styles.pager}>
217
+ <PagerButton direction="previous" targetId={pager.previousId} onNavigate={pager.onNavigate} labelOf={pager.labelOf} />
218
+ <PagerButton direction="next" targetId={pager.nextId} onNavigate={pager.onNavigate} labelOf={pager.labelOf} />
219
+ </View>
220
+ );
221
+ }
222
+
223
+ /** The page's specimen blocks (grid, grouped rows, list, or preview), stacked or side by side —
224
+ * exported so `FamilyPage` can render a member's own specimens through the exact same placement
225
+ * logic a normal page's blocks use, rather than a second copy of it. */
226
+ export function Blocks<TId extends string>({
227
+ blocks,
228
+ def,
229
+ surface,
230
+ showLabels = true,
231
+ trimOuterMargin = false,
232
+ }: {
233
+ blocks: PresentationBlock[];
234
+ def: SectionDef<TId>;
235
+ surface: SpecimenSurfaceKind;
236
+ /** V2b item 4: a family member's own specimens render with no uppercase "Variants" / "States /
237
+ * configurations" block label — v8 has none inside a member block. A page's own `SectionBlock`
238
+ * keeps its labels (the default, unchanged). */
239
+ showLabels?: boolean;
240
+ /** V2b item 5: drops this component's own page-level `marginBottom` — `FamilyPage`'s own
241
+ * wrapper (`memberSpecimens`) supplies the exact spacing a member block needs instead, so the
242
+ * two never double-stack. A page's own `SectionBlock` keeps the margin (the default,
243
+ * unchanged). */
244
+ trimOuterMargin?: boolean;
245
+ }) {
213
246
  const [available, setAvailable] = useState(0);
214
247
  const [heights, setHeights] = useState<number[]>([]);
215
248
  const [placement, setPlacement] = useState<'side' | 'stacked'>('stacked');
@@ -248,15 +281,22 @@ function Blocks<TId extends string>({ blocks, def, surface }: { blocks: Presenta
248
281
 
249
282
  const side = placement === 'side';
250
283
  return (
251
- <View onLayout={onContainerLayout} style={[styles.blocks, side && styles.blocksSide]}>
284
+ <View onLayout={onContainerLayout} style={[styles.blocks, side && styles.blocksSide, trimOuterMargin && styles.blocksNoMargin]}>
252
285
  {blocks.map((block, i) => (
253
286
  <View
254
- key={block.title || block.kind}
287
+ // A4: keyed by index, not `block.title || block.kind` — a member that sets the same
288
+ // `axis` on both `variants` and `states` now produces two blocks with identical titles,
289
+ // which would otherwise collide as React keys (as the family-section list already keys
290
+ // by index for the same reason — FamilyPage.tsx).
291
+ key={`${block.kind}-${i}`}
255
292
  onLayout={onBlockLayout(i)}
256
293
  // Side by side, each block fills its own half: a card always fills the column it is given.
257
294
  style={side ? styles.blockFill : undefined}
258
295
  >
259
- {block.title ? <Text style={styles.blockLabel}>{block.title}</Text> : null}
296
+ {/* A3: `axis` now reaches grid/grouped blocks too (not only list), so any block kind
297
+ carrying one shows its (already informative) title on a family page, which otherwise
298
+ hides every generic block label via `showLabels={false}`. */}
299
+ {(showLabels && block.title) || ('axis' in block && block.axis) ? <Text style={styles.blockLabel}>{('axis' in block && block.axis) || block.title}</Text> : null}
260
300
  <BlockContent block={block} def={def} surface={surface} />
261
301
  </View>
262
302
  ))}
@@ -318,12 +358,7 @@ export function SectionBlock<TId extends string>({
318
358
  )}
319
359
  <Text style={styles.desc}>{def.description}</Text>
320
360
  </View>
321
- {pager && (
322
- <View style={styles.pager}>
323
- <PagerButton direction="previous" targetId={pager.previousId} onNavigate={pager.onNavigate} labelOf={pager.labelOf} />
324
- <PagerButton direction="next" targetId={pager.nextId} onNavigate={pager.onNavigate} labelOf={pager.labelOf} />
325
- </View>
326
- )}
361
+ {pager && <SectionPagerRow pager={pager} />}
327
362
  </View>
328
363
 
329
364
  {blocks.length > 0 && <Blocks blocks={blocks} def={def} surface={surface} />}
@@ -334,7 +369,7 @@ export function SectionBlock<TId extends string>({
334
369
  }
335
370
 
336
371
  const styles = StyleSheet.create({
337
- breadcrumb: { fontSize: CATALOG_TYPE.sm, color: CATALOG_COLOR.textMuted },
372
+ breadcrumb: { ...CATALOG_TEXT.caption, color: CATALOG_COLOR.textMuted },
338
373
  titlebar: {
339
374
  flexDirection: 'row',
340
375
  alignItems: 'flex-start',
@@ -355,9 +390,9 @@ const styles = StyleSheet.create({
355
390
  borderColor: CATALOG_COLOR.borderStrong,
356
391
  backgroundColor: CATALOG_COLOR.surface,
357
392
  },
358
- osBadgeText: { fontSize: CATALOG_TYPE.xs, fontWeight: '700', letterSpacing: 0.4, color: CATALOG_COLOR.text },
359
- title: { fontSize: CATALOG_TYPE.pageTitle, fontWeight: '700', color: CATALOG_COLOR.text, marginBottom: 5 },
360
- desc: { fontSize: CATALOG_TYPE.md, lineHeight: 20, color: CATALOG_COLOR.textMuted, maxWidth: 700 },
393
+ osBadgeText: { ...CATALOG_TEXT.microLabel, color: CATALOG_COLOR.text },
394
+ title: { ...CATALOG_TEXT.pageTitle, color: CATALOG_COLOR.text, marginBottom: 5 },
395
+ desc: { ...CATALOG_TEXT.body, color: CATALOG_COLOR.textMuted, maxWidth: 700 },
361
396
  pager: { flexDirection: 'row', gap: 6, flexShrink: 0 },
362
397
  pagerButton: {
363
398
  width: CATALOG_LAYOUT.controlSize,
@@ -372,26 +407,21 @@ const styles = StyleSheet.create({
372
407
  pagerButtonActive: { backgroundColor: CATALOG_COLOR.surfacePressed },
373
408
  pagerButtonDisabled: { backgroundColor: CATALOG_COLOR.pageBackground },
374
409
  focusRing: { outlineWidth: CATALOG_LAYOUT.focusRingWidth, outlineStyle: 'solid', outlineColor: CATALOG_COLOR.focusRing },
375
- pagerGlyph: { fontSize: CATALOG_TYPE.lg, color: CATALOG_COLOR.text },
410
+ pagerGlyph: { ...CATALOG_TEXT.icon, color: CATALOG_COLOR.text },
376
411
  pagerGlyphDisabled: { color: CATALOG_COLOR.textMuted },
377
412
  blocks: { gap: CATALOG_LAYOUT.blockGap, marginBottom: CATALOG_LAYOUT.blockGap },
413
+ blocksNoMargin: { marginBottom: 0 },
378
414
  blocksSide: { flexDirection: 'row', alignItems: 'flex-start' },
379
415
  blockFill: { flex: 1, minWidth: 0 },
380
416
  // Centred in the card, like every other example (a single phone-width frame used to sit left).
381
417
  frames: { flexDirection: 'row', flexWrap: 'wrap', gap: CATALOG_SPACE.xl, alignItems: 'flex-start', justifyContent: 'center' },
382
418
  frame: { maxWidth: '100%', gap: CATALOG_SPACE.sm },
383
419
  frameLabel: {
384
- fontSize: CATALOG_TYPE.tableHeader,
385
- fontWeight: '800',
386
- letterSpacing: 0.44,
387
- textTransform: 'uppercase',
420
+ ...CATALOG_TEXT.label,
388
421
  color: CATALOG_COLOR.textMuted,
389
422
  },
390
423
  blockLabel: {
391
- fontSize: CATALOG_TYPE.sm,
392
- fontWeight: '700',
393
- letterSpacing: 0.72,
394
- textTransform: 'uppercase',
424
+ ...CATALOG_TEXT.label,
395
425
  color: CATALOG_COLOR.text,
396
426
  marginBottom: CATALOG_LAYOUT.blockLabelGap,
397
427
  },
@@ -423,10 +453,10 @@ const styles = StyleSheet.create({
423
453
  borderBottomWidth: 1,
424
454
  borderBottomColor: CATALOG_COLOR.border,
425
455
  },
426
- tableCaptionText: { fontSize: CATALOG_TYPE.tableHeader, fontWeight: '800', letterSpacing: 0.44, color: CATALOG_COLOR.text },
456
+ tableCaptionText: { ...CATALOG_TEXT.captionStrong, color: CATALOG_COLOR.text },
427
457
  // Same cell padding `ComparisonList` uses: the requested width instead reaches the live demo
428
458
  // untouched through `tableBodyViewport`, the inner width-qualified group nested inside this body.
429
459
  tableBody: { padding: MATRIX_LAYOUT.cellPadding, flexGrow: 1, alignItems: 'center', justifyContent: 'flex-start' },
430
460
  tableBodyViewport: { maxWidth: '100%' },
431
- emptyText: { fontSize: CATALOG_TYPE.sm, fontStyle: 'italic', color: CATALOG_COLOR.textMuted },
461
+ emptyText: { ...CATALOG_TEXT.caption, color: CATALOG_COLOR.textMuted },
432
462
  });
@@ -1,6 +1,6 @@
1
1
  import { View, Text, StyleSheet } from 'react-native';
2
2
  import { TokenRow, TokenRowGroup } from './TokenRow';
3
- import { CATALOG_TYPE, CATALOG_COLOR, CATALOG_SPACE, CATALOG_RADIUS } from './tokens';
3
+ import { CATALOG_TEXT, CATALOG_COLOR, CATALOG_SPACE, CATALOG_RADIUS } from './tokens';
4
4
 
5
5
  /**
6
6
  * Renders a spacing scale as a stack of `TokenRow`s, each showing a step's name, a bar sized to its
@@ -39,7 +39,7 @@ const styles = StyleSheet.create({
39
39
  // Wider gap than a plain gap-only stack — one row's use-note shouldn't crowd the next row's label.
40
40
  tokenStack: { gap: CATALOG_SPACE.lg },
41
41
  row: { flexDirection: 'row', alignItems: 'center', gap: CATALOG_SPACE.md },
42
- label: { width: 48, fontSize: CATALOG_TYPE.sm, fontWeight: '700', color: CATALOG_COLOR.text },
42
+ label: { width: 48, ...CATALOG_TEXT.captionStrong, color: CATALOG_COLOR.text },
43
43
  bar: { height: 12, borderRadius: CATALOG_RADIUS.sm, backgroundColor: CATALOG_COLOR.accent },
44
- value: { fontSize: CATALOG_TYPE.xs, color: CATALOG_COLOR.textMuted },
44
+ value: { ...CATALOG_TEXT.micro, color: CATALOG_COLOR.textMuted },
45
45
  });
@@ -1,5 +1,5 @@
1
1
  import { View, Text, StyleSheet } from 'react-native';
2
- import { CATALOG_TYPE, CATALOG_COLOR, CATALOG_SPACE, CATALOG_RADIUS } from './tokens';
2
+ import { CATALOG_TEXT, CATALOG_COLOR, CATALOG_SPACE, CATALOG_RADIUS } from './tokens';
3
3
 
4
4
  /** One token swatch: its rendered value (a colour chip) plus name/value as data — the shared shape
5
5
  * behind every Colors gallery (this framework's own, and any host app's, e.g. the Native App DS
@@ -35,6 +35,6 @@ const styles = StyleSheet.create({
35
35
  borderWidth: StyleSheet.hairlineWidth,
36
36
  borderColor: CATALOG_COLOR.border,
37
37
  },
38
- name: { fontSize: CATALOG_TYPE.sm, fontWeight: '700', color: CATALOG_COLOR.text },
39
- value: { fontSize: CATALOG_TYPE.xs, color: CATALOG_COLOR.textMuted },
38
+ name: { ...CATALOG_TEXT.captionStrong, color: CATALOG_COLOR.text },
39
+ value: { ...CATALOG_TEXT.micro, color: CATALOG_COLOR.textMuted },
40
40
  });
@@ -1,6 +1,6 @@
1
1
  import React, { useRef, useState } from 'react';
2
2
  import { View, Text, StyleSheet, type LayoutChangeEvent } from 'react-native';
3
- import { CATALOG_COLOR, CATALOG_LAYOUT, CATALOG_RADIUS, CATALOG_SPACE, CATALOG_TYPE } from './tokens';
3
+ import { CATALOG_COLOR, CATALOG_LAYOUT, CATALOG_RADIUS, CATALOG_SPACE, CATALOG_TEXT } from './tokens';
4
4
  import { sectionColumns, tileGeometry, TOKEN_LAYOUT_GAP, TOKEN_SECTION_MIN_WIDTH } from './tokenLayout';
5
5
  import type { TokenSection } from './types';
6
6
 
@@ -78,13 +78,10 @@ export function TokenTile({ name, value, note, children }: { name: string; value
78
78
  const styles = StyleSheet.create({
79
79
  sections: { flexDirection: 'row', flexWrap: 'wrap', gap: TOKEN_LAYOUT_GAP, rowGap: CATALOG_LAYOUT.blockGap, alignItems: 'flex-start' },
80
80
  sectionLabel: {
81
- fontSize: CATALOG_TYPE.sm,
82
- fontWeight: '700',
83
- letterSpacing: 0.72,
84
- textTransform: 'uppercase',
81
+ ...CATALOG_TEXT.label,
85
82
  color: CATALOG_COLOR.text,
86
83
  },
87
- sectionDesc: { fontSize: CATALOG_TYPE.sm, color: CATALOG_COLOR.textMuted, marginTop: CATALOG_SPACE.xs },
84
+ sectionDesc: { ...CATALOG_TEXT.caption, color: CATALOG_COLOR.textMuted, marginTop: CATALOG_SPACE.xs },
88
85
  card: {
89
86
  marginTop: CATALOG_LAYOUT.blockLabelGap,
90
87
  backgroundColor: CATALOG_COLOR.surface,
@@ -97,7 +94,7 @@ const styles = StyleSheet.create({
97
94
  grid: { flexDirection: 'row', flexWrap: 'wrap', gap: TOKEN_LAYOUT_GAP },
98
95
  tile: { gap: CATALOG_SPACE.xs },
99
96
  tilePreview: { minHeight: 56, justifyContent: 'center', marginBottom: CATALOG_SPACE.xs },
100
- tileName: { fontSize: CATALOG_TYPE.sm, fontWeight: '700', color: CATALOG_COLOR.text },
101
- tileValue: { fontSize: CATALOG_TYPE.xs, color: CATALOG_COLOR.textMuted },
102
- tileNote: { fontSize: CATALOG_TYPE.xs, color: CATALOG_COLOR.textMuted, lineHeight: 16 },
97
+ tileName: { ...CATALOG_TEXT.captionStrong, color: CATALOG_COLOR.text },
98
+ tileValue: { ...CATALOG_TEXT.micro, color: CATALOG_COLOR.textMuted },
99
+ tileNote: { ...CATALOG_TEXT.micro, color: CATALOG_COLOR.textMuted },
103
100
  });
@@ -1,6 +1,6 @@
1
1
  import React, { createContext, useCallback, useContext, useMemo, useState } from 'react';
2
2
  import { View, Text, StyleSheet, type LayoutChangeEvent } from 'react-native';
3
- import { CATALOG_TYPE, CATALOG_COLOR, CATALOG_SPACE } from './tokens';
3
+ import { CATALOG_TEXT, CATALOG_COLOR, CATALOG_SPACE } from './tokens';
4
4
 
5
5
  type Lane = { width: number; report: (width: number) => void };
6
6
 
@@ -104,5 +104,5 @@ const styles = StyleSheet.create({
104
104
  borderBottomWidth: 1,
105
105
  borderBottomColor: CATALOG_COLOR.border,
106
106
  },
107
- use: { fontSize: CATALOG_TYPE.sm, color: CATALOG_COLOR.textMuted },
107
+ use: { ...CATALOG_TEXT.caption, color: CATALOG_COLOR.textMuted },
108
108
  });
@@ -1,6 +1,6 @@
1
1
  import { View, Text, StyleSheet, type TextStyle } from 'react-native';
2
2
  import { TokenRow, TokenRowGroup } from './TokenRow';
3
- import { CATALOG_TYPE, CATALOG_COLOR, CATALOG_SPACE } from './tokens';
3
+ import { CATALOG_TEXT, CATALOG_COLOR, CATALOG_SPACE } from './tokens';
4
4
 
5
5
  /**
6
6
  * Renders a type scale as a stack of `TokenRow`s, each showing a step's name (rendered at its own
@@ -44,5 +44,5 @@ const styles = StyleSheet.create({
44
44
  // two read as one unit at a glance.
45
45
  row: { flexDirection: 'row', alignItems: 'baseline', gap: CATALOG_SPACE.sm },
46
46
  sample: { color: CATALOG_COLOR.text },
47
- meta: { fontSize: CATALOG_TYPE.xs, color: CATALOG_COLOR.textMuted },
47
+ meta: { ...CATALOG_TEXT.micro, color: CATALOG_COLOR.textMuted },
48
48
  });
@@ -1,6 +1,6 @@
1
1
  import React, { useCallback, useEffect, useRef, useState } from 'react';
2
2
  import { ActivityIndicator, Platform, Pressable, ScrollView, StyleSheet, Switch, Text, View } from 'react-native';
3
- import { CATALOG_COLOR, CATALOG_LAYOUT, CATALOG_RADIUS, CATALOG_SPACE, CATALOG_TYPE } from './tokens';
3
+ import { CATALOG_COLOR, CATALOG_LAYOUT, CATALOG_RADIUS, CATALOG_SPACE, CATALOG_TEXT } from './tokens';
4
4
  import { CatalogButton } from './CatalogButton';
5
5
  import { DS_VIEWER_SECRET_HEADER } from './catalogNavigation';
6
6
  import {
@@ -1080,9 +1080,9 @@ const styles = StyleSheet.create({
1080
1080
  resultNote: { borderRadius: CATALOG_RADIUS.md, padding: 14, marginTop: 12, marginBottom: 18, gap: 4 },
1081
1081
  resultNoteOk: { backgroundColor: CATALOG_COLOR.successSubtle },
1082
1082
  resultNoteErr: { backgroundColor: CATALOG_COLOR.dangerSubtle, borderWidth: 1, borderColor: CATALOG_COLOR.dangerBorder },
1083
- resultTitle: { fontSize: CATALOG_TYPE.lg, fontWeight: '800' },
1084
- resultDescription: { fontSize: CATALOG_TYPE.panelHeading, lineHeight: 19 },
1085
- resultDetail: { fontSize: CATALOG_TYPE.sm, lineHeight: 18 },
1083
+ resultTitle: { ...CATALOG_TEXT.heading3 },
1084
+ resultDescription: { ...CATALOG_TEXT.caption },
1085
+ resultDetail: { ...CATALOG_TEXT.caption },
1086
1086
  resultRow: { flexDirection: 'row', alignItems: 'center', gap: CATALOG_SPACE.md, marginTop: 8 },
1087
1087
  // The catalog's smallest control size (the major banner's close button), filled in the note's colour.
1088
1088
  resultButton: { height: 32, paddingHorizontal: 14, borderRadius: 16, alignItems: 'center', justifyContent: 'center' },
@@ -1091,15 +1091,14 @@ const styles = StyleSheet.create({
1091
1091
  // uses, so "unavailable" doesn't read as merely "pressed" (the note's own full-colour fill at
1092
1092
  // reduced opacity).
1093
1093
  resultButtonDisabled: { backgroundColor: CATALOG_COLOR.controlDisabled },
1094
- resultButtonText: { color: '#ffffff', fontSize: CATALOG_TYPE.sm, fontWeight: '700' },
1094
+ resultButtonText: { ...CATALOG_TEXT.captionStrong, color: '#ffffff' },
1095
1095
  resultButtonTextDisabled: { color: CATALOG_COLOR.controlDisabledText },
1096
- resultLink: { fontSize: CATALOG_TYPE.panelHeading, fontWeight: '700', textDecorationLine: 'underline' },
1097
- crumb: { fontSize: CATALOG_TYPE.sm, color: CATALOG_COLOR.textMuted },
1096
+ resultLink: { ...CATALOG_TEXT.captionStrong, textDecorationLine: 'underline' },
1097
+ crumb: { ...CATALOG_TEXT.caption, color: CATALOG_COLOR.textMuted },
1098
1098
  headingTarget: { marginTop: 4, marginBottom: 6 },
1099
- title: { fontSize: CATALOG_TYPE.pageTitle, fontWeight: '800', color: CATALOG_COLOR.text },
1099
+ title: { ...CATALOG_TEXT.pageTitle, color: CATALOG_COLOR.text },
1100
1100
  code: {
1101
- fontFamily: 'Menlo, monospace',
1102
- fontSize: CATALOG_TYPE.sm,
1101
+ ...CATALOG_TEXT.codeSmall,
1103
1102
  backgroundColor: CATALOG_COLOR.surfaceMuted,
1104
1103
  borderWidth: 1,
1105
1104
  borderColor: CATALOG_COLOR.border,
@@ -1108,39 +1107,39 @@ const styles = StyleSheet.create({
1108
1107
  paddingVertical: 2,
1109
1108
  },
1110
1109
  versionsRow: { flexDirection: 'row', alignItems: 'center', gap: 10 },
1111
- versionsText: { fontSize: CATALOG_TYPE['2xl'], fontWeight: '800', color: CATALOG_COLOR.text },
1112
- arrow: { fontSize: CATALOG_TYPE['2xl'], color: CATALOG_COLOR.textMuted },
1110
+ versionsText: { ...CATALOG_TEXT.heading1, color: CATALOG_COLOR.text },
1111
+ arrow: { ...CATALOG_TEXT.heading1, color: CATALOG_COLOR.textMuted },
1113
1112
  pill: { paddingHorizontal: 8, paddingVertical: 2, borderRadius: 10, backgroundColor: CATALOG_COLOR.accentSubtle },
1114
1113
  pillMajor: { backgroundColor: CATALOG_COLOR.warningSubtle },
1115
1114
  pillSuccess: { backgroundColor: CATALOG_COLOR.successSubtle },
1116
- pillText: { fontSize: CATALOG_TYPE.sm, fontWeight: '700', color: CATALOG_COLOR.accent },
1115
+ pillText: { ...CATALOG_TEXT.captionStrong, color: CATALOG_COLOR.accent },
1117
1116
  pillTextMajor: { color: CATALOG_COLOR.warning },
1118
1117
  pillTextSuccess: { color: CATALOG_COLOR.success },
1119
1118
  pillOff: { backgroundColor: CATALOG_COLOR.chip },
1120
1119
  pillTextOff: { color: CATALOG_COLOR.textMuted },
1121
1120
  facts: { marginTop: 18, borderTopWidth: 1, borderTopColor: CATALOG_COLOR.border },
1122
1121
  factRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: CATALOG_SPACE.md, paddingVertical: 10, borderBottomWidth: 1, borderBottomColor: CATALOG_COLOR.border },
1123
- factLabel: { fontSize: CATALOG_TYPE.md, color: CATALOG_COLOR.textMuted },
1124
- factValue: { fontSize: CATALOG_TYPE.md, color: CATALOG_COLOR.text },
1122
+ factLabel: { ...CATALOG_TEXT.body, color: CATALOG_COLOR.textMuted },
1123
+ factValue: { ...CATALOG_TEXT.body, color: CATALOG_COLOR.text },
1125
1124
  switchText: { flex: 1, gap: 2 },
1126
- switchLabel: { fontSize: CATALOG_TYPE.md, fontWeight: '700', color: CATALOG_COLOR.text },
1125
+ switchLabel: { ...CATALOG_TEXT.bodyStrong, color: CATALOG_COLOR.text },
1127
1126
  switch: { width: 44, height: 26 },
1128
- switchHelp: { fontSize: CATALOG_TYPE.sm, color: CATALOG_COLOR.textMuted },
1129
- muted: { fontSize: CATALOG_TYPE.sm, color: CATALOG_COLOR.textMuted, marginTop: 4 },
1127
+ switchHelp: { ...CATALOG_TEXT.caption, color: CATALOG_COLOR.textMuted },
1128
+ muted: { ...CATALOG_TEXT.caption, color: CATALOG_COLOR.textMuted, marginTop: 4 },
1130
1129
  block: { marginTop: 18 },
1131
- h3: { fontSize: CATALOG_TYPE.panelHeading, textTransform: 'uppercase', letterSpacing: 0.5, color: CATALOG_COLOR.text, marginBottom: 8 },
1132
- bullet: { fontSize: CATALOG_TYPE.md, color: CATALOG_COLOR.text, marginVertical: 2 },
1130
+ h3: { ...CATALOG_TEXT.label, color: CATALOG_COLOR.text, marginBottom: 8 },
1131
+ bullet: { ...CATALOG_TEXT.body, color: CATALOG_COLOR.text, marginVertical: 2 },
1133
1132
  filesBox: { borderWidth: 1, borderColor: CATALOG_COLOR.border, borderRadius: CATALOG_RADIUS.md },
1134
1133
  fileRow: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 8, paddingHorizontal: 12 },
1135
1134
  fileRowBorder: { borderTopWidth: 1, borderTopColor: CATALOG_COLOR.border },
1136
- filePath: { fontFamily: 'Menlo, monospace', fontSize: CATALOG_TYPE.sm, color: CATALOG_COLOR.text },
1137
- fileReason: { fontSize: CATALOG_TYPE.sm, color: CATALOG_COLOR.textMuted },
1138
- fileReasonDirty: { color: CATALOG_COLOR.warning, fontWeight: '700' },
1135
+ filePath: { ...CATALOG_TEXT.codeSmall, color: CATALOG_COLOR.text },
1136
+ fileReason: { ...CATALOG_TEXT.caption, color: CATALOG_COLOR.textMuted },
1137
+ fileReasonDirty: { ...CATALOG_TEXT.captionStrong, color: CATALOG_COLOR.warning },
1139
1138
  actions: { flexDirection: 'row', alignItems: 'center', gap: CATALOG_SPACE.md, marginTop: 20 },
1140
1139
  noteBox: { borderRadius: CATALOG_RADIUS.md, padding: 12, marginTop: 18 },
1141
1140
  noteBoxRow: { flexDirection: 'row', alignItems: 'flex-start', gap: 9 },
1142
- noteBoxText: { fontSize: CATALOG_TYPE.md, flexShrink: 1 },
1143
- noteBoxBold: { fontWeight: '700' },
1141
+ noteBoxText: { ...CATALOG_TEXT.body, flexShrink: 1 },
1142
+ noteBoxBold: { ...CATALOG_TEXT.bodyStrong },
1144
1143
  steps: { marginTop: 12 },
1145
1144
  stepRow: { flexDirection: 'row', alignItems: 'center', gap: 10, paddingVertical: 6 },
1146
1145
  stepIcon: { width: 20, height: 20, borderRadius: 10, alignItems: 'center', justifyContent: 'center' },
@@ -1151,10 +1150,10 @@ const styles = StyleSheet.create({
1151
1150
  stepIconNow: { borderWidth: 2, borderColor: CATALOG_COLOR.accent },
1152
1151
  stepIconTodo: { borderWidth: 2, borderColor: CATALOG_COLOR.borderStrong },
1153
1152
  stepIconFail: { backgroundColor: CATALOG_COLOR.dangerSubtle },
1154
- stepIconGlyph: { fontSize: CATALOG_TYPE.sm, fontWeight: '800', color: CATALOG_COLOR.text },
1155
- stepLabel: { fontSize: CATALOG_TYPE.md, color: CATALOG_COLOR.textMuted },
1156
- stepLabelNow: { fontSize: CATALOG_TYPE.md, color: CATALOG_COLOR.text, fontWeight: '700' },
1157
- stepLabelFail: { fontSize: CATALOG_TYPE.md, color: CATALOG_COLOR.text, fontWeight: '700' },
1153
+ stepIconGlyph: { ...CATALOG_TEXT.captionStrong, color: CATALOG_COLOR.text },
1154
+ stepLabel: { ...CATALOG_TEXT.body, color: CATALOG_COLOR.textMuted },
1155
+ stepLabelNow: { ...CATALOG_TEXT.bodyStrong, color: CATALOG_COLOR.text },
1156
+ stepLabelFail: { ...CATALOG_TEXT.bodyStrong, color: CATALOG_COLOR.text },
1158
1157
  logBox: { backgroundColor: '#1d1d1f', borderRadius: CATALOG_RADIUS.md, padding: 12, marginTop: 8, maxHeight: 240 },
1159
- logText: { color: '#e8e8e8', fontSize: CATALOG_TYPE.sm, fontFamily: 'Menlo, monospace' },
1158
+ logText: { ...CATALOG_TEXT.codeSmall, color: '#e8e8e8' },
1160
1159
  });
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { View, Text, StyleSheet } from 'react-native';
3
- import { CATALOG_TYPE, CATALOG_COLOR } from './tokens';
3
+ import { CATALOG_TEXT, CATALOG_COLOR } from './tokens';
4
4
 
5
5
  /** Labels one example (or small group of examples) inside a section's Examples card — a bold
6
6
  * uppercase name plus a one-line description stacked below it, sitting above whatever demo content
@@ -39,10 +39,7 @@ const styles = StyleSheet.create({
39
39
  group: { gap: 6 },
40
40
  groupCenter: { alignItems: 'center' },
41
41
  groupLeft: { alignItems: 'flex-start' },
42
- name: {
43
- fontSize: CATALOG_TYPE.xs, fontWeight: '700', color: CATALOG_COLOR.text,
44
- textTransform: 'uppercase', letterSpacing: 0.4,
45
- },
46
- desc: { fontSize: CATALOG_TYPE.sm, color: CATALOG_COLOR.textMuted },
42
+ name: { ...CATALOG_TEXT.microLabel, color: CATALOG_COLOR.text },
43
+ desc: { ...CATALOG_TEXT.caption, color: CATALOG_COLOR.textMuted },
47
44
  textCenter: { textAlign: 'center' },
48
45
  });