@krapwoo/ds-viewer 0.9.1 → 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.
@@ -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: 'Sidebar group labels (uppercase); a prop\'s type annotation.',
75
- tableHeader: 'Grid column headers and list cell captions (uppercase).',
76
- sm: 'Breadcrumb, catalog caption, block labels, prop names and descriptions.',
77
- panelHeading: 'Reference-panel headings: Guidance, Quick reference, Props (uppercase).',
78
- md: 'Default body text: descriptions, nav links, grid row headers, quick-reference rows, the filter field.',
79
- lg: 'Pager arrows and the filter field\'s clear (×) glyph.',
80
- brand: 'The app name at the top of the sidebar.',
81
- xl: "The major-update banner's close (×) glyph.",
82
- '2xl': "The update page's version numbers and the arrow between them.",
83
- pageTitle: 'The selected page\'s title.',
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: 'Menlo',
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, CATALOG_TYPE } from './tokens';
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', fontSize: CATALOG_TYPE.sm, color: CATALOG_COLOR.textMuted },
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, CATALOG_TYPE } from './tokens';
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: { fontSize: CATALOG_TYPE.md, fontWeight: '700' },
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 { CATALOG_TYPE, CATALOG_COLOR, CATALOG_SPACE, CATALOG_RADIUS, CATALOG_LAYOUT } from './tokens';
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, fontSize: CATALOG_TYPE.md, color: CATALOG_COLOR.text, padding: 0 },
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: { fontSize: CATALOG_TYPE.lg, color: CATALOG_COLOR.textMuted, paddingHorizontal: 2 },
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, CATALOG_TYPE } from './tokens';
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, fontSize: CATALOG_TYPE.md, color: CATALOG_COLOR.warning },
105
- bold: { fontWeight: '700' },
106
- link: { color: CATALOG_COLOR.warning, fontWeight: '700', textDecorationLine: 'underline' },
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: { fontSize: CATALOG_TYPE.xl, color: CATALOG_COLOR.warning },
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: { fontSize: CATALOG_TYPE.md, color: CATALOG_COLOR.textMuted, padding: CATALOG_SPACE.lg },
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: { fontSize: CATALOG_TYPE.md, color: CATALOG_COLOR.textMuted },
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, CATALOG_TYPE } from './tokens';
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 `CATALOG_TYPE.brand` means it wrapped.
157
- const NAME_LINE_HEIGHT = Math.round(CATALOG_TYPE.brand * 1.3);
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: { fontSize: CATALOG_TYPE.brand, fontWeight: '800', color: CATALOG_COLOR.text },
239
- subtitle: { fontSize: CATALOG_TYPE.sm, color: CATALOG_COLOR.textMuted, marginTop: 2 },
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: { fontSize: CATALOG_TYPE.sm, color: CATALOG_COLOR.textMuted, paddingHorizontal: CATALOG_LAYOUT.navItemPaddingX, paddingVertical: 8 },
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
- paddingTop: 10,
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: { fontSize: CATALOG_TYPE.sm, color: CATALOG_COLOR.textMuted },
267
- footerLabelActive: { color: CATALOG_COLOR.accent, fontWeight: '700' },
265
+ footerLabel: { ...CATALOG_TEXT.caption, color: CATALOG_COLOR.textMuted },
266
+ footerLabelActive: { ...CATALOG_TEXT.captionStrong, color: CATALOG_COLOR.accent },
268
267
  groupLabel: {
269
- fontSize: CATALOG_TYPE.xs,
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: { fontSize: CATALOG_TYPE.md, color: CATALOG_COLOR.textMuted },
289
- labelActive: { color: CATALOG_COLOR.accent, fontWeight: '700' },
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, CATALOG_TYPE } from './tokens';
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: { fontSize: CATALOG_TYPE.tableHeader, fontWeight: '800', letterSpacing: 0.44, color: CATALOG_COLOR.text },
112
- rowHeaderText: { fontSize: CATALOG_TYPE.md, fontWeight: '700', color: CATALOG_COLOR.text },
113
- unavailable: { fontSize: CATALOG_TYPE.sm, fontStyle: 'italic', textAlign: 'center', color: CATALOG_COLOR.textMuted },
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, CATALOG_TYPE } from './tokens';
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: { fontSize: CATALOG_TYPE.md, fontWeight: '700', color: CATALOG_COLOR.text },
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: { fontSize: CATALOG_TYPE.tableHeader, fontWeight: '800', letterSpacing: 0.44, color: CATALOG_COLOR.text },
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, CATALOG_TYPE } from './tokens';
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: { fontSize: CATALOG_TYPE.tableHeader, fontWeight: '800', letterSpacing: 0.44, color: CATALOG_COLOR.text },
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, 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';
@@ -334,7 +334,7 @@ export function SectionBlock<TId extends string>({
334
334
  }
335
335
 
336
336
  const styles = StyleSheet.create({
337
- breadcrumb: { fontSize: CATALOG_TYPE.sm, color: CATALOG_COLOR.textMuted },
337
+ breadcrumb: { ...CATALOG_TEXT.caption, color: CATALOG_COLOR.textMuted },
338
338
  titlebar: {
339
339
  flexDirection: 'row',
340
340
  alignItems: 'flex-start',
@@ -355,9 +355,9 @@ const styles = StyleSheet.create({
355
355
  borderColor: CATALOG_COLOR.borderStrong,
356
356
  backgroundColor: CATALOG_COLOR.surface,
357
357
  },
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 },
358
+ osBadgeText: { ...CATALOG_TEXT.microLabel, color: CATALOG_COLOR.text },
359
+ title: { ...CATALOG_TEXT.pageTitle, color: CATALOG_COLOR.text, marginBottom: 5 },
360
+ desc: { ...CATALOG_TEXT.body, color: CATALOG_COLOR.textMuted, maxWidth: 700 },
361
361
  pager: { flexDirection: 'row', gap: 6, flexShrink: 0 },
362
362
  pagerButton: {
363
363
  width: CATALOG_LAYOUT.controlSize,
@@ -372,7 +372,7 @@ const styles = StyleSheet.create({
372
372
  pagerButtonActive: { backgroundColor: CATALOG_COLOR.surfacePressed },
373
373
  pagerButtonDisabled: { backgroundColor: CATALOG_COLOR.pageBackground },
374
374
  focusRing: { outlineWidth: CATALOG_LAYOUT.focusRingWidth, outlineStyle: 'solid', outlineColor: CATALOG_COLOR.focusRing },
375
- pagerGlyph: { fontSize: CATALOG_TYPE.lg, color: CATALOG_COLOR.text },
375
+ pagerGlyph: { ...CATALOG_TEXT.icon, color: CATALOG_COLOR.text },
376
376
  pagerGlyphDisabled: { color: CATALOG_COLOR.textMuted },
377
377
  blocks: { gap: CATALOG_LAYOUT.blockGap, marginBottom: CATALOG_LAYOUT.blockGap },
378
378
  blocksSide: { flexDirection: 'row', alignItems: 'flex-start' },
@@ -381,17 +381,11 @@ const styles = StyleSheet.create({
381
381
  frames: { flexDirection: 'row', flexWrap: 'wrap', gap: CATALOG_SPACE.xl, alignItems: 'flex-start', justifyContent: 'center' },
382
382
  frame: { maxWidth: '100%', gap: CATALOG_SPACE.sm },
383
383
  frameLabel: {
384
- fontSize: CATALOG_TYPE.tableHeader,
385
- fontWeight: '800',
386
- letterSpacing: 0.44,
387
- textTransform: 'uppercase',
384
+ ...CATALOG_TEXT.label,
388
385
  color: CATALOG_COLOR.textMuted,
389
386
  },
390
387
  blockLabel: {
391
- fontSize: CATALOG_TYPE.sm,
392
- fontWeight: '700',
393
- letterSpacing: 0.72,
394
- textTransform: 'uppercase',
388
+ ...CATALOG_TEXT.label,
395
389
  color: CATALOG_COLOR.text,
396
390
  marginBottom: CATALOG_LAYOUT.blockLabelGap,
397
391
  },
@@ -423,10 +417,10 @@ const styles = StyleSheet.create({
423
417
  borderBottomWidth: 1,
424
418
  borderBottomColor: CATALOG_COLOR.border,
425
419
  },
426
- tableCaptionText: { fontSize: CATALOG_TYPE.tableHeader, fontWeight: '800', letterSpacing: 0.44, color: CATALOG_COLOR.text },
420
+ tableCaptionText: { ...CATALOG_TEXT.captionStrong, color: CATALOG_COLOR.text },
427
421
  // Same cell padding `ComparisonList` uses: the requested width instead reaches the live demo
428
422
  // untouched through `tableBodyViewport`, the inner width-qualified group nested inside this body.
429
423
  tableBody: { padding: MATRIX_LAYOUT.cellPadding, flexGrow: 1, alignItems: 'center', justifyContent: 'flex-start' },
430
424
  tableBodyViewport: { maxWidth: '100%' },
431
- emptyText: { fontSize: CATALOG_TYPE.sm, fontStyle: 'italic', color: CATALOG_COLOR.textMuted },
425
+ emptyText: { ...CATALOG_TEXT.caption, color: CATALOG_COLOR.textMuted },
432
426
  });
@@ -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
  });
@@ -55,6 +55,9 @@ export type { ListGroup, ListItem } from './comparison';
55
55
  export {
56
56
  CATALOG_TYPE,
57
57
  CATALOG_TYPE_USE,
58
+ CATALOG_FONT,
59
+ CATALOG_TEXT,
60
+ CATALOG_TEXT_USE,
58
61
  CATALOG_SPACE,
59
62
  CATALOG_SPACE_USE,
60
63
  CATALOG_RADIUS,
@@ -28,6 +28,32 @@ export const CATALOG_TYPE = {
28
28
  '4xl': 40,
29
29
  } as const;
30
30
 
31
+ export const CATALOG_FONT = { code: 'Menlo, monospace' } as const;
32
+
33
+ /** The catalog's fixed set of typography roles, each a complete `fontSize`/`fontWeight`/
34
+ * `lineHeight` (plus `letterSpacing`/`textTransform`/`fontFamily` where the role needs them)
35
+ * built on `CATALOG_TYPE`. Every catalog-chrome text style spreads one of these rather than
36
+ * setting its own typography properties directly (enforced by
37
+ * `__tests__/textRoles.test.ts`). */
38
+ export const CATALOG_TEXT = {
39
+ pageTitle: { fontSize: CATALOG_TYPE.pageTitle, fontWeight: '700', lineHeight: 34 },
40
+ heading1: { fontSize: CATALOG_TYPE['2xl'], fontWeight: '700', lineHeight: 30 },
41
+ heading2: { fontSize: CATALOG_TYPE.xl, fontWeight: '700', lineHeight: 24 },
42
+ heading3: { fontSize: CATALOG_TYPE.lg, fontWeight: '700', lineHeight: 22 },
43
+ icon: { fontSize: CATALOG_TYPE.lg, fontWeight: '400', lineHeight: 22 },
44
+ body: { fontSize: CATALOG_TYPE.md, fontWeight: '400', lineHeight: 20 },
45
+ bodyStrong: { fontSize: CATALOG_TYPE.md, fontWeight: '700', lineHeight: 20 },
46
+ caption: { fontSize: CATALOG_TYPE.sm, fontWeight: '400', lineHeight: 18 },
47
+ captionStrong: { fontSize: CATALOG_TYPE.sm, fontWeight: '700', lineHeight: 18 },
48
+ label: { fontSize: CATALOG_TYPE.sm, fontWeight: '700', lineHeight: 18, letterSpacing: 0.72, textTransform: 'uppercase' },
49
+ micro: { fontSize: CATALOG_TYPE.xs, fontWeight: '400', lineHeight: 14 },
50
+ microLabel: { fontSize: CATALOG_TYPE.xs, fontWeight: '700', lineHeight: 14, letterSpacing: 0.6, textTransform: 'uppercase' },
51
+ code: { fontSize: CATALOG_TYPE.md, fontWeight: '400', lineHeight: 20, fontFamily: CATALOG_FONT.code },
52
+ codeSmall: { fontSize: CATALOG_TYPE.sm, fontWeight: '400', lineHeight: 18, fontFamily: CATALOG_FONT.code },
53
+ codeSmallStrong: { fontSize: CATALOG_TYPE.sm, fontWeight: '700', lineHeight: 18, fontFamily: CATALOG_FONT.code },
54
+ codeMicro: { fontSize: CATALOG_TYPE.xs, fontWeight: '400', lineHeight: 14, fontFamily: CATALOG_FONT.code },
55
+ } as const;
56
+
31
57
  export const CATALOG_SPACE = {
32
58
  xs: 4,
33
59
  sm: 8,
@@ -75,22 +101,44 @@ export const CATALOG_LAYOUT = {
75
101
  export const CATALOG_MAX_CONTENT_WIDTH = 1200;
76
102
 
77
103
  /** When to reach for each catalog-chrome type size. Rendered in the framework catalog's Type
78
- * Scale page. */
104
+ * Scale page. Every size is now consumed through a `CATALOG_TEXT` role (see `CATALOG_TEXT_USE`)
105
+ * rather than directly. */
79
106
  export const CATALOG_TYPE_USE: Record<keyof typeof CATALOG_TYPE, string> = {
80
- xs: 'Sidebar group labels (uppercase); a prop\'s type annotation.',
81
- tableHeader: 'Grid column headers and list cell captions (uppercase).',
82
- sm: 'Breadcrumb, catalog caption, block labels, prop names and descriptions.',
83
- panelHeading: 'Reference-panel headings: Guidance, Quick reference, Props (uppercase).',
84
- md: 'Default body text: descriptions, nav links, grid row headers, quick-reference rows, the filter field.',
85
- lg: 'Pager arrows and the filter field\'s clear (×) glyph.',
86
- brand: 'The app name at the top of the sidebar.',
87
- xl: "The major-update banner's close (×) glyph.",
88
- '2xl': "The update page's version numbers and the arrow between them.",
89
- pageTitle: 'The selected page\'s title.',
107
+ 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).',
108
+ tableHeader: 'Reserved — no current consumer; grid column headers and list cell captions now use the `captionStrong` role (built on `sm`).',
109
+ 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.',
110
+ panelHeading: 'Reserved — no current consumer; reference-panel and update-page headings now use the `label` role (built on `sm`).',
111
+ 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).',
112
+ lg: 'Backs the `heading3` and `icon` roles: a result note\'s title; pager arrows and the filter field\'s clear (×) glyph.',
113
+ brand: 'Reserved — no current consumer; the sidebar app name now uses the `heading2` role (built on `xl`).',
114
+ xl: 'Backs the `heading2` role: the app name at the top of the sidebar.',
115
+ '2xl': 'Backs the `heading1` role: the update page\'s version numbers and the arrow between them.',
116
+ pageTitle: 'Backs the `pageTitle` role: the selected page\'s title, including the update page\'s own title.',
90
117
  '3xl': 'Reserved — no current consumer.',
91
118
  '4xl': 'Reserved — no current consumer.',
92
119
  };
93
120
 
121
+ /** When to reach for each catalog-chrome typography role. Rendered in the framework catalog's
122
+ * Type Scale page. */
123
+ export const CATALOG_TEXT_USE: Record<keyof typeof CATALOG_TEXT, string> = {
124
+ pageTitle: 'The selected page\'s title, including the update page\'s own title.',
125
+ heading1: 'The update page\'s version numbers and the arrow between them.',
126
+ heading2: 'The app name at the top of the sidebar.',
127
+ heading3: 'A result note\'s title (the update page\'s success/failure banner).',
128
+ icon: 'Glyph-only controls: pager arrows, the filter field\'s clear (×) glyph, the update banner\'s close (×) glyph.',
129
+ body: 'Default body text: descriptions, nav links, the filter field, update-page facts/notes/steps.',
130
+ bodyStrong: 'Emphasised body text: grid/list row headers, switch and step labels, inline-bold notice text.',
131
+ caption: 'Secondary text: breadcrumbs, status captions, block/prop descriptions, sidebar subtitle/footer, update-page captions.',
132
+ captionStrong: 'Emphasised secondary text: grid column headers, grid/list/table captions, reference-panel subheadings, swatch/token names, replay and result links, step-icon glyphs.',
133
+ label: 'Uppercase labels and headings: block and preview-frame labels, token-section labels, reference-panel and update-page headings.',
134
+ micro: 'The smallest text: token values and meta captions, a prop\'s default-value label.',
135
+ microLabel: 'Uppercase micro labels: variant-group names, sidebar group labels, OS badges.',
136
+ code: 'Monospace body text: a long unbroken value that needs to wrap at its \'/\'s.',
137
+ codeSmall: 'Monospace secondary text: a prop\'s optional marker, the update page\'s inline code, file paths and log lines.',
138
+ codeSmallStrong: 'Emphasised monospace secondary text: a prop\'s own name.',
139
+ codeMicro: 'Monospace micro text: a prop\'s type and default value.',
140
+ };
141
+
94
142
  /** When to reach for each catalog-chrome spacing step. Rendered in the framework catalog's
95
143
  * Spacing page. */
96
144
  export const CATALOG_SPACE_USE: Record<keyof typeof CATALOG_SPACE, string> = {
@@ -135,7 +183,10 @@ export const CATALOG_COLOR = {
135
183
  accentSubtle: '#e9efff',
136
184
  /** 3px keyboard focus ring on every catalog control. */
137
185
  focusRing: '#c9d7ff',
138
- code: 'Menlo',
186
+ /** @deprecated kept for the public API; re-points at `CATALOG_FONT.code` so there is one source
187
+ * of truth. New code should reach for `CATALOG_FONT.code` (or a `CATALOG_TEXT` `code*` role)
188
+ * instead. */
189
+ code: CATALOG_FONT.code,
139
190
  /** Major-update banner background/border/text (design §5 "Viewer notice"). */
140
191
  warning: '#8a4b00',
141
192
  warningSubtle: '#fff4e0',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krapwoo/ds-viewer",
3
- "version": "0.9.1",
3
+ "version": "0.10.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "A browsable, always-current catalog for an Expo / React Native design system.",