@olympusoss/canvas 4.0.0 → 5.0.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 (297) hide show
  1. package/README.md +108 -0
  2. package/package.json +14 -3
  3. package/src/atoms/avatar/avatar.md +185 -0
  4. package/src/atoms/avatar/avatar.styles.ts +48 -0
  5. package/src/atoms/avatar/avatar.tsx +99 -0
  6. package/src/atoms/badge/badge.md +237 -0
  7. package/src/atoms/badge/badge.styles.ts +79 -0
  8. package/src/atoms/badge/badge.tsx +86 -0
  9. package/src/atoms/breadcrumb/breadcrumb.md +233 -0
  10. package/src/atoms/breadcrumb/breadcrumb.styles.ts +40 -0
  11. package/src/atoms/breadcrumb/breadcrumb.tsx +130 -0
  12. package/src/atoms/button/button.android.tsx +6 -0
  13. package/src/atoms/button/button.ios.tsx +6 -0
  14. package/src/atoms/button/button.md +184 -0
  15. package/src/atoms/button/button.shared.tsx +79 -0
  16. package/src/atoms/button/button.styles.ts +152 -0
  17. package/src/atoms/button/button.tsx +6 -0
  18. package/src/atoms/button-group/button-group.android.tsx +6 -0
  19. package/src/atoms/button-group/button-group.ios.tsx +6 -0
  20. package/src/atoms/button-group/button-group.md +120 -0
  21. package/src/atoms/button-group/button-group.shared.tsx +398 -0
  22. package/src/atoms/button-group/button-group.styles.ts +483 -0
  23. package/src/atoms/button-group/button-group.tsx +6 -0
  24. package/src/atoms/checkbox/checkbox.android.tsx +6 -0
  25. package/src/atoms/checkbox/checkbox.ios.tsx +6 -0
  26. package/src/atoms/checkbox/checkbox.md +150 -0
  27. package/src/atoms/checkbox/checkbox.shared.tsx +103 -0
  28. package/src/atoms/checkbox/checkbox.styles.ts +106 -0
  29. package/src/atoms/checkbox/checkbox.tsx +6 -0
  30. package/src/atoms/combobox/combobox.android.tsx +6 -0
  31. package/src/atoms/combobox/combobox.ios.tsx +6 -0
  32. package/src/atoms/combobox/combobox.md +213 -0
  33. package/src/atoms/combobox/combobox.shared.tsx +160 -0
  34. package/src/atoms/combobox/combobox.styles.ts +270 -0
  35. package/src/atoms/combobox/combobox.tsx +6 -0
  36. package/src/atoms/divider/divider.md +140 -0
  37. package/src/atoms/divider/divider.styles.ts +35 -0
  38. package/src/atoms/divider/divider.tsx +67 -0
  39. package/src/atoms/dropdown/dropdown.android.tsx +6 -0
  40. package/src/atoms/dropdown/dropdown.ios.tsx +6 -0
  41. package/src/atoms/dropdown/dropdown.md +221 -0
  42. package/src/atoms/dropdown/dropdown.shared.tsx +190 -0
  43. package/src/atoms/dropdown/dropdown.styles.ts +233 -0
  44. package/src/atoms/dropdown/dropdown.tsx +6 -0
  45. package/src/atoms/icon/icon.md +131 -0
  46. package/src/atoms/icon/icon.styles.ts +30 -0
  47. package/src/atoms/icon/icon.tsx +328 -0
  48. package/src/atoms/index.ts +24 -0
  49. package/src/atoms/input/input.android.tsx +6 -0
  50. package/src/atoms/input/input.ios.tsx +6 -0
  51. package/src/atoms/input/input.md +118 -0
  52. package/src/atoms/input/input.shared.tsx +203 -0
  53. package/src/atoms/input/input.styles.ts +286 -0
  54. package/src/atoms/input/input.tsx +6 -0
  55. package/src/atoms/kbd/kbd.md +91 -0
  56. package/src/atoms/kbd/kbd.styles.ts +33 -0
  57. package/src/atoms/kbd/kbd.tsx +27 -0
  58. package/src/atoms/listbox/listbox.md +177 -0
  59. package/src/atoms/listbox/listbox.styles.ts +60 -0
  60. package/src/atoms/listbox/listbox.tsx +113 -0
  61. package/src/atoms/pagination/pagination.android.tsx +6 -0
  62. package/src/atoms/pagination/pagination.ios.tsx +6 -0
  63. package/src/atoms/pagination/pagination.md +133 -0
  64. package/src/atoms/pagination/pagination.shared.tsx +289 -0
  65. package/src/atoms/pagination/pagination.styles.ts +245 -0
  66. package/src/atoms/pagination/pagination.tsx +6 -0
  67. package/src/atoms/popover/popover.android.tsx +8 -0
  68. package/src/atoms/popover/popover.ios.tsx +6 -0
  69. package/src/atoms/popover/popover.md +87 -0
  70. package/src/atoms/popover/popover.shared.tsx +124 -0
  71. package/src/atoms/popover/popover.styles.ts +144 -0
  72. package/src/atoms/popover/popover.tsx +6 -0
  73. package/src/atoms/radio/radio.android.tsx +6 -0
  74. package/src/atoms/radio/radio.ios.tsx +6 -0
  75. package/src/atoms/radio/radio.md +173 -0
  76. package/src/atoms/radio/radio.shared.tsx +98 -0
  77. package/src/atoms/radio/radio.styles.ts +109 -0
  78. package/src/atoms/radio/radio.tsx +6 -0
  79. package/src/atoms/select/select.android.tsx +6 -0
  80. package/src/atoms/select/select.ios.tsx +6 -0
  81. package/src/atoms/select/select.md +156 -0
  82. package/src/atoms/select/select.shared.tsx +143 -0
  83. package/src/atoms/select/select.styles.ts +310 -0
  84. package/src/atoms/select/select.tsx +6 -0
  85. package/src/atoms/skeleton/skeleton.md +135 -0
  86. package/src/atoms/skeleton/skeleton.styles.ts +117 -0
  87. package/src/atoms/skeleton/skeleton.tsx +145 -0
  88. package/src/atoms/spinner/spinner.android.tsx +7 -0
  89. package/src/atoms/spinner/spinner.ios.tsx +7 -0
  90. package/src/atoms/spinner/spinner.md +94 -0
  91. package/src/atoms/spinner/spinner.shared.tsx +92 -0
  92. package/src/atoms/spinner/spinner.styles.tsx +115 -0
  93. package/src/atoms/spinner/spinner.tsx +7 -0
  94. package/src/atoms/switch/switch.android.tsx +6 -0
  95. package/src/atoms/switch/switch.ios.tsx +6 -0
  96. package/src/atoms/switch/switch.md +91 -0
  97. package/src/atoms/switch/switch.shared.tsx +97 -0
  98. package/src/atoms/switch/switch.styles.ts +79 -0
  99. package/src/atoms/switch/switch.tsx +6 -0
  100. package/src/atoms/textarea/textarea.android.tsx +6 -0
  101. package/src/atoms/textarea/textarea.ios.tsx +6 -0
  102. package/src/atoms/textarea/textarea.md +140 -0
  103. package/src/atoms/textarea/textarea.shared.tsx +74 -0
  104. package/src/atoms/textarea/textarea.styles.ts +116 -0
  105. package/src/atoms/textarea/textarea.tsx +6 -0
  106. package/src/atoms/tooltip/tooltip.android.tsx +6 -0
  107. package/src/atoms/tooltip/tooltip.ios.tsx +7 -0
  108. package/src/atoms/tooltip/tooltip.md +122 -0
  109. package/src/atoms/tooltip/tooltip.shared.tsx +113 -0
  110. package/src/atoms/tooltip/tooltip.styles.ts +113 -0
  111. package/src/atoms/tooltip/tooltip.tsx +6 -0
  112. package/src/atoms/typography/typography.md +330 -0
  113. package/src/atoms/typography/typography.styles.ts +95 -0
  114. package/src/atoms/typography/typography.tsx +76 -0
  115. package/src/index.ts +12 -2
  116. package/src/molecules/action-panels/action-panels.md +133 -0
  117. package/src/molecules/action-panels/action-panels.styles.ts +39 -0
  118. package/src/molecules/action-panels/action-panels.tsx +113 -0
  119. package/src/molecules/alert/alert.md +119 -0
  120. package/src/molecules/alert/alert.styles.ts +88 -0
  121. package/src/molecules/alert/alert.tsx +74 -0
  122. package/src/molecules/alert-dialog/alert-dialog.android.tsx +6 -0
  123. package/src/molecules/alert-dialog/alert-dialog.ios.tsx +6 -0
  124. package/src/molecules/alert-dialog/alert-dialog.md +177 -0
  125. package/src/molecules/alert-dialog/alert-dialog.shared.tsx +187 -0
  126. package/src/molecules/alert-dialog/alert-dialog.styles.ts +248 -0
  127. package/src/molecules/alert-dialog/alert-dialog.tsx +6 -0
  128. package/src/molecules/card/card.md +190 -0
  129. package/src/molecules/card/card.styles.ts +67 -0
  130. package/src/molecules/card/card.tsx +176 -0
  131. package/src/molecules/code-block/code-block.md +159 -0
  132. package/src/molecules/code-block/code-block.styles.ts +167 -0
  133. package/src/molecules/code-block/code-block.tsx +176 -0
  134. package/src/molecules/description-lists/description-lists.md +129 -0
  135. package/src/molecules/description-lists/description-lists.styles.ts +102 -0
  136. package/src/molecules/description-lists/description-lists.tsx +133 -0
  137. package/src/molecules/empty-state/empty-state.md +218 -0
  138. package/src/molecules/empty-state/empty-state.styles.ts +63 -0
  139. package/src/molecules/empty-state/empty-state.tsx +77 -0
  140. package/src/molecules/feeds/feeds.md +102 -0
  141. package/src/molecules/feeds/feeds.styles.ts +120 -0
  142. package/src/molecules/feeds/feeds.tsx +167 -0
  143. package/src/molecules/field/field.md +117 -0
  144. package/src/molecules/field/field.styles.ts +85 -0
  145. package/src/molecules/field/field.tsx +175 -0
  146. package/src/molecules/fieldset/fieldset.md +141 -0
  147. package/src/molecules/fieldset/fieldset.styles.ts +79 -0
  148. package/src/molecules/fieldset/fieldset.tsx +182 -0
  149. package/src/molecules/form/form.md +137 -0
  150. package/src/molecules/form/form.styles.ts +39 -0
  151. package/src/molecules/form/form.tsx +246 -0
  152. package/src/molecules/grid-lists/grid-lists.md +114 -0
  153. package/src/molecules/grid-lists/grid-lists.styles.ts +79 -0
  154. package/src/molecules/grid-lists/grid-lists.tsx +157 -0
  155. package/src/molecules/index.ts +16 -0
  156. package/src/molecules/media-objects/media-objects.md +87 -0
  157. package/src/molecules/media-objects/media-objects.styles.ts +94 -0
  158. package/src/molecules/media-objects/media-objects.tsx +128 -0
  159. package/src/molecules/stacked-lists/stacked-lists.md +116 -0
  160. package/src/molecules/stacked-lists/stacked-lists.styles.ts +111 -0
  161. package/src/molecules/stacked-lists/stacked-lists.tsx +195 -0
  162. package/src/molecules/stats/stats.md +166 -0
  163. package/src/molecules/stats/stats.styles.ts +91 -0
  164. package/src/molecules/stats/stats.tsx +88 -0
  165. package/src/organisms/calendar/calendar.android.tsx +6 -0
  166. package/src/organisms/calendar/calendar.ios.tsx +6 -0
  167. package/src/organisms/calendar/calendar.md +114 -0
  168. package/src/organisms/calendar/calendar.shared.tsx +146 -0
  169. package/src/organisms/calendar/calendar.styles.ts +315 -0
  170. package/src/organisms/calendar/calendar.tsx +6 -0
  171. package/src/organisms/charts/charts.md +326 -0
  172. package/src/organisms/charts/charts.styles.ts +135 -0
  173. package/src/organisms/charts/charts.tsx +124 -0
  174. package/src/organisms/command/command.md +117 -0
  175. package/src/organisms/command/command.styles.ts +179 -0
  176. package/src/organisms/command/command.tsx +164 -0
  177. package/src/organisms/data-table/data-table.md +182 -0
  178. package/src/organisms/data-table/data-table.styles.ts +103 -0
  179. package/src/organisms/data-table/data-table.tsx +105 -0
  180. package/src/organisms/dialog/dialog.android.tsx +6 -0
  181. package/src/organisms/dialog/dialog.ios.tsx +6 -0
  182. package/src/organisms/dialog/dialog.md +271 -0
  183. package/src/organisms/dialog/dialog.shared.tsx +230 -0
  184. package/src/organisms/dialog/dialog.styles.ts +272 -0
  185. package/src/organisms/dialog/dialog.tsx +6 -0
  186. package/src/organisms/filter-panel/filter-panel.md +116 -0
  187. package/src/organisms/filter-panel/filter-panel.styles.ts +83 -0
  188. package/src/organisms/filter-panel/filter-panel.tsx +91 -0
  189. package/src/organisms/index.ts +13 -0
  190. package/src/organisms/navbars/navbars.android.tsx +6 -0
  191. package/src/organisms/navbars/navbars.ios.tsx +6 -0
  192. package/src/organisms/navbars/navbars.md +144 -0
  193. package/src/organisms/navbars/navbars.shared.tsx +137 -0
  194. package/src/organisms/navbars/navbars.styles.ts +251 -0
  195. package/src/organisms/navbars/navbars.tsx +6 -0
  196. package/src/organisms/overlays/overlays.android.tsx +6 -0
  197. package/src/organisms/overlays/overlays.ios.tsx +6 -0
  198. package/src/organisms/overlays/overlays.md +123 -0
  199. package/src/organisms/overlays/overlays.shared.tsx +175 -0
  200. package/src/organisms/overlays/overlays.styles.ts +309 -0
  201. package/src/organisms/overlays/overlays.tsx +6 -0
  202. package/src/organisms/row-menu/row-menu.android.tsx +6 -0
  203. package/src/organisms/row-menu/row-menu.ios.tsx +6 -0
  204. package/src/organisms/row-menu/row-menu.md +102 -0
  205. package/src/organisms/row-menu/row-menu.shared.tsx +105 -0
  206. package/src/organisms/row-menu/row-menu.styles.ts +262 -0
  207. package/src/organisms/row-menu/row-menu.tsx +6 -0
  208. package/src/organisms/sidebar/sidebar.android.tsx +6 -0
  209. package/src/organisms/sidebar/sidebar.ios.tsx +6 -0
  210. package/src/organisms/sidebar/sidebar.md +188 -0
  211. package/src/organisms/sidebar/sidebar.shared.tsx +167 -0
  212. package/src/organisms/sidebar/sidebar.styles.ts +262 -0
  213. package/src/organisms/sidebar/sidebar.tsx +6 -0
  214. package/src/organisms/stepper/stepper.android.tsx +6 -0
  215. package/src/organisms/stepper/stepper.ios.tsx +6 -0
  216. package/src/organisms/stepper/stepper.md +150 -0
  217. package/src/organisms/stepper/stepper.shared.tsx +158 -0
  218. package/src/organisms/stepper/stepper.styles.ts +280 -0
  219. package/src/organisms/stepper/stepper.tsx +6 -0
  220. package/src/organisms/tabs/tabs.android.tsx +6 -0
  221. package/src/organisms/tabs/tabs.ios.tsx +6 -0
  222. package/src/organisms/tabs/tabs.md +127 -0
  223. package/src/organisms/tabs/tabs.shared.tsx +281 -0
  224. package/src/organisms/tabs/tabs.styles.ts +398 -0
  225. package/src/organisms/tabs/tabs.tsx +6 -0
  226. package/src/style/color.ts +17 -0
  227. package/src/style/index.ts +14 -0
  228. package/src/style/primitives.ts +26 -0
  229. package/src/style/responsive.ts +45 -0
  230. package/src/style/shadow.ts +21 -0
  231. package/src/style/theme.tsx +56 -0
  232. package/src/style/tokens.ts +487 -0
  233. package/src/theme.ts +21 -0
  234. package/styles/canvas.css +128 -67
  235. package/tsconfig.json +4 -2
  236. package/src/cn.ts +0 -3
  237. package/styles/base.css +0 -17
  238. package/styles/components/alert.css +0 -66
  239. package/styles/components/app-shell.css +0 -46
  240. package/styles/components/avatar.css +0 -15
  241. package/styles/components/badge.css +0 -83
  242. package/styles/components/breadcrumb.css +0 -35
  243. package/styles/components/button-group.css +0 -23
  244. package/styles/components/button.css +0 -107
  245. package/styles/components/calendar.css +0 -73
  246. package/styles/components/card.css +0 -58
  247. package/styles/components/checkbox.css +0 -55
  248. package/styles/components/code-block.css +0 -18
  249. package/styles/components/combobox.css +0 -75
  250. package/styles/components/command.css +0 -94
  251. package/styles/components/data-table.css +0 -142
  252. package/styles/components/dialog.css +0 -72
  253. package/styles/components/dropdown.css +0 -54
  254. package/styles/components/empty-state.css +0 -17
  255. package/styles/components/field.css +0 -27
  256. package/styles/components/filter-panel.css +0 -58
  257. package/styles/components/form.css +0 -27
  258. package/styles/components/icon.css +0 -8
  259. package/styles/components/input-group.css +0 -45
  260. package/styles/components/input.css +0 -56
  261. package/styles/components/kbd.css +0 -15
  262. package/styles/components/page-header.css +0 -52
  263. package/styles/components/pagination.css +0 -48
  264. package/styles/components/popover.css +0 -14
  265. package/styles/components/radio.css +0 -28
  266. package/styles/components/row-menu.css +0 -69
  267. package/styles/components/section-card.css +0 -49
  268. package/styles/components/select.css +0 -57
  269. package/styles/components/separator.css +0 -32
  270. package/styles/components/sheet.css +0 -70
  271. package/styles/components/sidebar.css +0 -146
  272. package/styles/components/skeleton.css +0 -32
  273. package/styles/components/spinner.css +0 -26
  274. package/styles/components/stat-card.css +0 -71
  275. package/styles/components/stepper.css +0 -63
  276. package/styles/components/switch.css +0 -45
  277. package/styles/components/tabs.css +0 -40
  278. package/styles/components/textarea.css +0 -31
  279. package/styles/components/toast.css +0 -95
  280. package/styles/components/tooltip.css +0 -53
  281. package/styles/components/topbar.css +0 -24
  282. package/styles/components/typography.css +0 -105
  283. package/styles/patterns/backdrops.css +0 -35
  284. package/styles/patterns/density.css +0 -66
  285. package/styles/patterns/focus.css +0 -38
  286. package/styles/patterns/glass.css +0 -85
  287. package/styles/patterns/high-contrast.css +0 -70
  288. package/styles/patterns/reduced-motion.css +0 -12
  289. package/styles/patterns/scrollbar.css +0 -10
  290. package/styles/reset.css +0 -89
  291. package/styles/tokens/colors.css +0 -106
  292. package/styles/tokens/motion.css +0 -33
  293. package/styles/tokens/radius.css +0 -10
  294. package/styles/tokens/shadows.css +0 -35
  295. package/styles/tokens/spacing.css +0 -19
  296. package/styles/tokens/typography.css +0 -6
  297. package/styles/tokens/z-index.css +0 -12
@@ -0,0 +1,176 @@
1
+ import { type GestureResponderEvent } from "react-native";
2
+ import { View, Pressable, Text, useTheme, type StyleProp, type ViewStyle } from "../../style/index.js";
3
+ import * as s from "./code-block.styles.js";
4
+ import { type Variant } from "./code-block.styles.js";
5
+
6
+ // CodeBlock: a muted, rounded surface that shows preformatted code in a
7
+ // monospace face. Newlines in `code` survive verbatim because RN Text honors
8
+ // "\n", so a multi-line string renders as multiple lines without any markup.
9
+ //
10
+ // Boolean-prop API: one boolean per option, grouped by axis, first-match
11
+ // precedence within an axis (mirrors Badge's toneOf). Axes:
12
+ //
13
+ // - Variant (pick one; default is the plain block):
14
+ // `terminal` > `numbered` > `inline` > plain.
15
+ // - plain: the muted code surface, padded, monospace.
16
+ // - terminal: a dark window with a chrome bar (traffic-light dots + a "bash"
17
+ // label) over a dark body, for shell transcripts. A leading "$ " prompt is
18
+ // rendered for command lines.
19
+ // - numbered: the plain surface with a left gutter of right-aligned line
20
+ // numbers, one per line.
21
+ // - inline: a short, single token rendered as an inline pill (rounded bg-muted
22
+ // chip), for code mentioned mid-sentence.
23
+ // - `copy` (orthogonal): show a copy affordance pinned to the top-right corner.
24
+ // Ignored by the inline variant, which is too small to carry one.
25
+ // - `wrap` (orthogonal): let long lines wrap instead of overflowing. Ignored by
26
+ // the inline variant.
27
+ //
28
+ // RN has no font-mono utility, so the monospace face is requested via an inline
29
+ // `style={s.MONO}` on each code Text (the same approach Badge's `mono` uses).
30
+
31
+ export interface CodeBlockProps {
32
+ /** The code to render. Newlines are preserved (RN Text honors "\n"). */
33
+ code?: string;
34
+ /** Optional filename or language label for the header bar. */
35
+ filename?: string;
36
+ language?: string;
37
+
38
+ // Variant (pick one; default is the plain block).
39
+ terminal?: boolean;
40
+ numbered?: boolean;
41
+ inline?: boolean;
42
+
43
+ // Orthogonal modifiers.
44
+ copy?: boolean;
45
+ wrap?: boolean;
46
+
47
+ /** Called when the copy affordance is pressed (text is passed back). */
48
+ onCopy?: (code: string, event: GestureResponderEvent) => void;
49
+
50
+ /** Escape hatch for layout/positioning composition (mainly width, margins). */
51
+ style?: StyleProp<ViewStyle>;
52
+ }
53
+
54
+ // Variant precedence when more than one is passed: first match wins.
55
+ function variantOf(p: CodeBlockProps): Variant {
56
+ if (p.terminal) return "terminal";
57
+ if (p.numbered) return "numbered";
58
+ if (p.inline) return "inline";
59
+ return "plain";
60
+ }
61
+
62
+ const DEFAULT_CODE = 'const theme = getTheme();\nsetTheme(theme === "dark" ? "light" : "dark");';
63
+
64
+ // A small, neutral chip pinned to the top-right corner for the copy affordance.
65
+ function CopyButton({
66
+ text,
67
+ onCopy,
68
+ dark,
69
+ }: {
70
+ text: string;
71
+ onCopy?: (code: string, event: GestureResponderEvent) => void;
72
+ dark?: boolean;
73
+ }) {
74
+ const { tokens } = useTheme();
75
+ const isDark = !!dark;
76
+ return (
77
+ <Pressable
78
+ style={({ pressed }) => [s.copyButton(tokens, isDark), pressed ? { opacity: 0.8 } : null]}
79
+ onPress={(e) => onCopy?.(text, e)}
80
+ accessibilityRole="button"
81
+ accessibilityLabel="Copy code"
82
+ >
83
+ <Text style={s.copyText(tokens, isDark)}>Copy</Text>
84
+ </Pressable>
85
+ );
86
+ }
87
+
88
+ export function CodeBlock(props: CodeBlockProps) {
89
+ const { code = DEFAULT_CODE, filename, language, copy, wrap, onCopy, style } = props;
90
+ const variant = variantOf(props);
91
+ const { tokens } = useTheme();
92
+ const lines = code.split("\n");
93
+
94
+ // Inline: a short token rendered as an inline pill. No header, copy, or wrap.
95
+ if (variant === "inline") {
96
+ return (
97
+ <View style={[s.inlineBox(tokens), style]}>
98
+ <Text style={[s.codeText(tokens), s.MONO, { fontSize: 13 }]}>{code}</Text>
99
+ </View>
100
+ );
101
+ }
102
+
103
+ // Terminal: a dark window with a chrome bar over a dark body.
104
+ if (variant === "terminal") {
105
+ const label = language ?? filename ?? "bash";
106
+ return (
107
+ <View style={[s.terminalOuter(tokens), style]}>
108
+ {/* Chrome bar: three traffic-light dots and a faint label. */}
109
+ <View style={s.terminalChrome}>
110
+ <View style={s.trafficDot("red")} />
111
+ <View style={s.trafficDot("amber")} />
112
+ <View style={s.trafficDot("green")} />
113
+ <Text style={[s.terminalLabel, s.MONO]}>{label}</Text>
114
+ </View>
115
+ {/* Body: each line gets a non-selectable "$ " prompt. */}
116
+ <View style={s.terminalBody}>
117
+ {lines.map((line, i) => (
118
+ <View key={i} style={s.terminalRow}>
119
+ <Text style={[s.terminalPrompt, s.MONO]}>{"$ "}</Text>
120
+ <Text style={[s.terminalLine, s.MONO]} numberOfLines={wrap ? undefined : 1}>
121
+ {line}
122
+ </Text>
123
+ </View>
124
+ ))}
125
+ </View>
126
+ {copy ? <CopyButton text={code} onCopy={onCopy} dark /> : null}
127
+ </View>
128
+ );
129
+ }
130
+
131
+ // Numbered: the plain surface with a right-aligned line-number gutter.
132
+ if (variant === "numbered") {
133
+ return (
134
+ <View style={[s.relative, style]}>
135
+ <View style={[s.surface(tokens), s.numberedSurface]}>
136
+ {/* Gutter: right-aligned, dimmed line numbers. */}
137
+ <View style={s.numberedGutter}>
138
+ {lines.map((_, i) => (
139
+ <Text key={i} style={[s.codeType, s.gutterText(tokens), s.MONO]}>
140
+ {String(i + 1)}
141
+ </Text>
142
+ ))}
143
+ </View>
144
+ {/* Code column: one Text per line so the gutter stays aligned. */}
145
+ <View style={s.numberedCodeCol}>
146
+ {lines.map((line, i) => (
147
+ <Text
148
+ key={i}
149
+ style={[s.codeType, s.codeText(tokens), s.MONO]}
150
+ numberOfLines={wrap ? undefined : 1}
151
+ >
152
+ {line}
153
+ </Text>
154
+ ))}
155
+ </View>
156
+ </View>
157
+ {copy ? <CopyButton text={code} onCopy={onCopy} /> : null}
158
+ </View>
159
+ );
160
+ }
161
+
162
+ // Plain (default): the muted code surface, padded, monospace.
163
+ return (
164
+ <View style={[s.relative, style]}>
165
+ <View style={[s.surface(tokens), s.surfacePad]}>
166
+ <Text
167
+ style={[s.codeType, s.codeText(tokens), s.MONO]}
168
+ numberOfLines={wrap ? undefined : lines.length}
169
+ >
170
+ {code}
171
+ </Text>
172
+ </View>
173
+ {copy ? <CopyButton text={code} onCopy={onCopy} /> : null}
174
+ </View>
175
+ );
176
+ }
@@ -0,0 +1,129 @@
1
+ # Description Lists
2
+
3
+ Key-value pairs in stacked, two-column, or inline-edit layouts. Used for detail panels, settings, and profile views.
4
+
5
+ ## Usage
6
+
7
+ ```tsx
8
+ <DescriptionList
9
+ card
10
+ twoColumn
11
+ divided
12
+ title="Application details"
13
+ subtitle="Personal information and credentials."
14
+ items={[
15
+ { term: "Full name", value: "Rachel Chen" },
16
+ { term: "Email", value: "rachel.chen@example.com" },
17
+ { term: "Role", value: "admin", badge: true },
18
+ { term: "Status", value: "Active", status: true }
19
+ ]}
20
+ />
21
+ ```
22
+
23
+ ## Variants
24
+
25
+ ### Layout - inline-edit
26
+
27
+ ```tsx
28
+ <DescriptionList
29
+ card
30
+ twoColumn
31
+ divided
32
+ title="Profile"
33
+ items={[
34
+ { term: "Name", value: "Rachel Chen", update: true },
35
+ { term: "Email", value: "rachel.chen@example.com", update: true },
36
+ { term: "Title", value: "Senior Engineer", update: true }
37
+ ]}
38
+ />
39
+ ```
40
+
41
+ ### Layout - stacked
42
+
43
+ ```tsx
44
+ <DescriptionList
45
+ card
46
+ stacked
47
+ items={[
48
+ { term: "Full name", value: "Rachel Chen" },
49
+ { term: "Email", value: "rachel.chen@example.com" },
50
+ { term: "Client ID", value: "clnt_01H2X8K9P3Q7VN4W6R5T0JYMZF", mono: true }
51
+ ]}
52
+ />
53
+ ```
54
+
55
+ ## Do & Don't
56
+
57
+ ### Two-column
58
+
59
+ **Do** — Fix the label column wide enough for the longest term so every value lines up on one edge.
60
+
61
+ ```tsx
62
+ <DescriptionList twoColumn divided style={{ maxWidth: 420 }} items={[
63
+ { term: "Client identifier", value: "clnt_01H2X8K9P3Q7VN4W6R5T0JYMZF", mono: true },
64
+ { term: "Status", value: "Active", status: true }
65
+ ]} />
66
+ ```
67
+
68
+ **Don't** — A too-narrow label column wraps the longest term and knocks the two columns out of alignment.
69
+
70
+ ```tsx
71
+ <View style={{ maxWidth: 420 }}>
72
+ <View style={{ flexDirection: "row", alignItems: "baseline", gap: 16, borderBottomWidth: 1, borderColor: tokens.border, paddingVertical: 12 }}>
73
+ <Text style={{ width: 64, fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>Client identifier</Text>
74
+ <Text style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", fontSize: 12.5, fontWeight: "500", color: tokens.foreground, fontFamily: "monospace" }}>clnt_01H2X8K9P3Q7VN4W6R5T0JYMZF</Text>
75
+ </View>
76
+ <View style={{ flexDirection: "row", alignItems: "baseline", gap: 16, paddingVertical: 12 }}>
77
+ <Text style={{ width: 64, fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>Status</Text>
78
+ <View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%" }}>
79
+ <Badge status success>Active</Badge>
80
+ </View>
81
+ </View>
82
+ </View>
83
+ ```
84
+
85
+ ### Inline-edit
86
+
87
+ **Do** — Give every editable row a trailing Update affordance so the inline editor is discoverable.
88
+
89
+ ```tsx
90
+ <DescriptionList twoColumn style={{ maxWidth: 420 }} items={[
91
+ { term: "Name", value: "Rachel Chen", update: true },
92
+ { term: "Email", value: "rachel.chen@example.com", update: true }
93
+ ]} />
94
+ ```
95
+
96
+ **Don't** — Editable rows that look identical to read-only ones give no hint a value can be changed.
97
+
98
+ ```tsx
99
+ <DescriptionList twoColumn style={{ maxWidth: 420 }} items={[
100
+ { term: "Name", value: "Rachel Chen" },
101
+ { term: "Email", value: "rachel.chen@example.com" }
102
+ ]} />
103
+ ```
104
+
105
+ ### Stacked
106
+
107
+ **Do** — Keep the label small, uppercase, and muted above a full-weight value so the data stays primary.
108
+
109
+ ```tsx
110
+ <DescriptionList stacked style={{ maxWidth: 320 }} items={[
111
+ { term: "Full name", value: "Rachel Chen" },
112
+ { term: "Email", value: "rachel.chen@example.com" }
113
+ ]} />
114
+ ```
115
+
116
+ **Don't** — Muting the value and bolding nothing inverts the hierarchy; the label outweighs the data it describes.
117
+
118
+ ```tsx
119
+ <View style={{ maxWidth: 320, gap: 16 }}>
120
+ <View style={{ gap: 4 }}>
121
+ <Text style={{ fontSize: 14, lineHeight: 20, color: tokens.foreground }}>Full name</Text>
122
+ <Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>Rachel Chen</Text>
123
+ </View>
124
+ <View style={{ gap: 4 }}>
125
+ <Text style={{ fontSize: 14, lineHeight: 20, color: tokens.foreground }}>Email</Text>
126
+ <Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>rachel.chen@example.com</Text>
127
+ </View>
128
+ </View>
129
+ ```
@@ -0,0 +1,102 @@
1
+ import { type ViewStyle, type TextStyle } from "react-native";
2
+ import { type ColorTokens, shadow } from "../../style/index.js";
3
+
4
+ // Co-located DescriptionList styles. Layout-only fragments are static objects;
5
+ // anything that reads a color is a function of the active tokens (so the card
6
+ // surface follows light/dark and reads as glass when the ThemeProvider's surface
7
+ // is "glass", since tokens.card is swapped translucent at the theming level).
8
+
9
+ export type Layout = "inline" | "twoColumn" | "stacked";
10
+
11
+ // Card surface: rounded, bordered, card fill, soft shadow. When a header band is
12
+ // present the card carries no padding (the header and rows supply their own
13
+ // px-6) so the header rule spans the full width; otherwise the card pads itself.
14
+ export function cardSurface(tokens: ColorTokens): ViewStyle {
15
+ return { borderRadius: 8, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.card, ...shadow("sm") };
16
+ }
17
+
18
+ // The self-padding applied to a card with no header band (p-6).
19
+ export const cardPad: ViewStyle = { padding: 24 };
20
+
21
+ // gap-3 between rows when there is no header band wrapping them.
22
+ export const stackGap: ViewStyle = { gap: 12 };
23
+
24
+ // With a header, rows sit in their own px-6 group beneath the bordered band.
25
+ export const rowsWrap: ViewStyle = { paddingHorizontal: 24, gap: 12 };
26
+
27
+ // --- term / value text ------------------------------------------------------
28
+
29
+ // Term: small, muted label.
30
+ export function termLabel(tokens: ColorTokens): TextStyle {
31
+ return { fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] };
32
+ }
33
+
34
+ // Stacked term: uppercased and tracked so the label reads as secondary above a
35
+ // full-weight value.
36
+ export function termStacked(tokens: ColorTokens): TextStyle {
37
+ return {
38
+ fontSize: 12,
39
+ lineHeight: 16,
40
+ fontWeight: "500",
41
+ textTransform: "uppercase",
42
+ letterSpacing: 0.4,
43
+ color: tokens["muted-foreground"],
44
+ };
45
+ }
46
+
47
+ // The two-column term's fixed label column (w-40). Lands on a Text.
48
+ export const termColumn: TextStyle = { width: 160 };
49
+
50
+ // Value: full-weight data it describes.
51
+ export function valueLabel(tokens: ColorTokens): TextStyle {
52
+ return { fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground };
53
+ }
54
+
55
+ // The inline value is right-aligned in its row.
56
+ export const valueAlignRight: TextStyle = { textAlign: "right" };
57
+
58
+ // Monospace value face, for tokens, scopes, identifiers.
59
+ export const valueMono: TextStyle = { fontFamily: "monospace" };
60
+
61
+ // --- rows -------------------------------------------------------------------
62
+
63
+ // Row layout per axis.
64
+ export const rowLayout: Record<Layout, ViewStyle> = {
65
+ inline: { flexDirection: "row", alignItems: "baseline", justifyContent: "space-between", gap: 16 },
66
+ twoColumn: { flexDirection: "row", alignItems: "baseline", gap: 16 },
67
+ stacked: { gap: 4 },
68
+ };
69
+
70
+ // A divided list pads each row vertically so the rule sits clear of the text.
71
+ export const rowDividedPad: ViewStyle = { paddingBottom: 12 };
72
+
73
+ // The hairline beneath every divided row except the last.
74
+ export function rowDivider(tokens: ColorTokens): ViewStyle {
75
+ return { borderBottomWidth: 1, borderColor: tokens.border };
76
+ }
77
+
78
+ // The two-column value cell: grows to fill, with the value and trailing
79
+ // affordance pushed to opposite ends.
80
+ export const twoColumnValueCell: ViewStyle = {
81
+ flexGrow: 1,
82
+ flexShrink: 1,
83
+ flexBasis: "0%",
84
+ flexDirection: "row",
85
+ alignItems: "baseline",
86
+ justifyContent: "space-between",
87
+ gap: 16,
88
+ };
89
+
90
+ // --- header band ------------------------------------------------------------
91
+
92
+ export function headerBand(tokens: ColorTokens): ViewStyle {
93
+ return { borderBottomWidth: 1, borderColor: tokens.border, paddingHorizontal: 24, paddingVertical: 16, gap: 2 };
94
+ }
95
+
96
+ export function headerTitle(tokens: ColorTokens): TextStyle {
97
+ return { fontSize: 16, lineHeight: 24, fontWeight: "600", color: tokens.foreground };
98
+ }
99
+
100
+ export function headerSubtitle(tokens: ColorTokens): TextStyle {
101
+ return { fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] };
102
+ }
@@ -0,0 +1,133 @@
1
+ import { View, Text, useTheme, type StyleProp, type ViewStyle } from "../../style/index.js";
2
+ import { Badge } from "../../atoms/badge/badge.js";
3
+ import { Button } from "../../atoms/button/button.js";
4
+ import * as s from "./description-lists.styles.js";
5
+ import { type Layout } from "./description-lists.styles.js";
6
+
7
+ // DescriptionList: term/value pairs for detail panels, settings, and profile
8
+ // views. Each item is a { term, value } pair. The term is the small muted
9
+ // label; the value is the full-weight data it describes, so the data always
10
+ // outranks its label.
11
+ //
12
+ // Boolean-prop API: one boolean per option, grouped by axis, first-match
13
+ // precedence within an axis (mirrors Button's intentOf / Card's surfaceOf).
14
+ //
15
+ // - Layout (pick one; precedence inline > twoColumn > stacked):
16
+ // `inline` lays the term on the left and the value on the right of a single
17
+ // row; `twoColumn` puts the term in a fixed 160px label column with the value
18
+ // beside it (the read-only detail look); the default `stacked` puts a small
19
+ // uppercase muted label above a full-weight value.
20
+ // - Rows: `divided` draws a hairline (border-b border-border) under every row
21
+ // but the last, for the two-column detail look.
22
+ // - Surface: `card` wraps the whole list in a card surface (border, radius,
23
+ // padding) so it reads as a self-contained panel. `title`/`subtitle` add a
24
+ // bordered header band to the card.
25
+ //
26
+ // Per-item value affordances (all optional, render the value richly):
27
+ // `badge` renders the value as a secondary metadata Badge; `status` renders it
28
+ // as a success status Badge with a leading dot (live state); `mono` sets a
29
+ // monospace face for tokens/IDs; `update` appends a trailing "Update" link
30
+ // button so an editable row is discoverable (the inline-edit affordance).
31
+ //
32
+ // Layout, rows, and surface are orthogonal axes and combine freely.
33
+
34
+ export interface DescriptionListItem {
35
+ term: string;
36
+ value: string;
37
+ // Render the value as a secondary metadata badge (e.g. a role).
38
+ badge?: boolean;
39
+ // Render the value as a success status badge with a leading dot (live state).
40
+ status?: boolean;
41
+ // Monospace value face, for tokens, scopes, identifiers.
42
+ mono?: boolean;
43
+ // Append a trailing "Update" link affordance, marking the row editable.
44
+ update?: boolean;
45
+ }
46
+
47
+ export interface DescriptionListProps {
48
+ items: DescriptionListItem[];
49
+ // Card header band (only shown when `card`): a title and optional subtitle.
50
+ title?: string;
51
+ subtitle?: string;
52
+ // Layout (pick one; precedence inline > twoColumn > stacked).
53
+ inline?: boolean;
54
+ twoColumn?: boolean;
55
+ stacked?: boolean;
56
+ // Rows: hairline divider beneath every row but the last.
57
+ divided?: boolean;
58
+ // Surface: wrap the list in a card surface.
59
+ card?: boolean;
60
+ /** Escape hatch for layout/positioning composition (mainly width). */
61
+ style?: StyleProp<ViewStyle>;
62
+ }
63
+
64
+ // Layout precedence when more than one is passed: first match wins.
65
+ function layoutOf(p: DescriptionListProps): Layout {
66
+ if (p.inline) return "inline";
67
+ if (p.twoColumn) return "twoColumn";
68
+ return "stacked";
69
+ }
70
+
71
+ // Render a value cell: a badge family, a monospace token, or plain text.
72
+ function Value({ item, align }: { item: DescriptionListItem; align?: boolean }) {
73
+ const { tokens } = useTheme();
74
+ if (item.status) return <Badge status success>{item.value}</Badge>;
75
+ if (item.badge) return <Badge secondary>{item.value}</Badge>;
76
+ if (item.mono) {
77
+ return <Text style={[s.valueLabel(tokens), s.valueMono]}>{item.value}</Text>;
78
+ }
79
+ return <Text style={[s.valueLabel(tokens), align ? s.valueAlignRight : null]}>{item.value}</Text>;
80
+ }
81
+
82
+ export function DescriptionList(props: DescriptionListProps) {
83
+ const { items, title, subtitle, divided, card, style } = props;
84
+ const { tokens } = useTheme();
85
+ const layout = layoutOf(props);
86
+ const hasHeader = card && !!title;
87
+
88
+ const container: StyleProp<ViewStyle> = [
89
+ card ? s.cardSurface(tokens) : null,
90
+ // No global padding when a header band supplies its own px-6 per section.
91
+ card && !hasHeader ? s.cardPad : null,
92
+ !hasHeader ? s.stackGap : null,
93
+ style,
94
+ ];
95
+
96
+ const rows = items.map((item, index) => {
97
+ const last = index === items.length - 1;
98
+ // A divided list draws a hairline under each row except the last, and pads
99
+ // the row vertically so the rule sits clear of the text.
100
+ const row: StyleProp<ViewStyle> = [
101
+ s.rowLayout[layout],
102
+ divided ? s.rowDividedPad : null,
103
+ divided && !last ? s.rowDivider(tokens) : null,
104
+ ];
105
+ return (
106
+ <View key={`${item.term}-${index}`} style={row}>
107
+ <Text style={[layout === "stacked" ? s.termStacked(tokens) : s.termLabel(tokens), layout === "twoColumn" ? s.termColumn : null]}>
108
+ {item.term}
109
+ </Text>
110
+ <View style={layout === "twoColumn" ? s.twoColumnValueCell : null}>
111
+ <Value item={item} align={layout === "inline"} />
112
+ {item.update ? (
113
+ <Button link small>
114
+ Update
115
+ </Button>
116
+ ) : null}
117
+ </View>
118
+ </View>
119
+ );
120
+ });
121
+
122
+ return (
123
+ <View style={container}>
124
+ {hasHeader ? (
125
+ <View style={s.headerBand(tokens)}>
126
+ <Text style={s.headerTitle(tokens)}>{title}</Text>
127
+ {subtitle ? <Text style={s.headerSubtitle(tokens)}>{subtitle}</Text> : null}
128
+ </View>
129
+ ) : null}
130
+ {hasHeader ? <View style={s.rowsWrap}>{rows}</View> : rows}
131
+ </View>
132
+ );
133
+ }