@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,67 @@
1
+ import { type ViewStyle, type TextStyle } from "react-native";
2
+ import { type ColorTokens, shadow } from "../../style/index.js";
3
+
4
+ // Co-located Card styles. Layout-only parts are static objects; anything that
5
+ // reads a color is a function of the active tokens (so the surface follows
6
+ // light/dark, and reads as glass when the ThemeProvider's surface is "glass",
7
+ // since tokens.card is swapped translucent at the theming level).
8
+
9
+ export type Elevation = "raised" | "flat" | "default";
10
+ export type Density = "compact" | "comfortable" | "default";
11
+
12
+ export const cardBase: ViewStyle = { borderRadius: 8, borderWidth: 1 };
13
+
14
+ // The card surface fill + border. tokens.card goes translucent under glass.
15
+ export function cardSurface(tokens: ColorTokens): ViewStyle {
16
+ return { borderColor: tokens.border, backgroundColor: tokens.card };
17
+ }
18
+
19
+ // Elevation: raised lifts the shadow, flat drops it, default is the soft shadow.
20
+ export function elevation(e: Elevation): ViewStyle {
21
+ return e === "raised" ? shadow("md") : e === "flat" ? shadow("none") : shadow("sm");
22
+ }
23
+
24
+ // Density sets the card's own content padding and the gap between flat children.
25
+ export const density: Record<Density, ViewStyle> = {
26
+ compact: { padding: 16, gap: 12 },
27
+ comfortable: { padding: 32, gap: 24 },
28
+ default: {},
29
+ };
30
+
31
+ // The standard inset applied by `padded` when no density is set.
32
+ export const padded: ViewStyle = { padding: 24 };
33
+
34
+ // --- subcomponent styles ----------------------------------------------------
35
+
36
+ export const header: ViewStyle = { gap: 6, paddingHorizontal: 20, paddingBottom: 16, paddingTop: 20 };
37
+
38
+ export function title(tokens: ColorTokens): TextStyle {
39
+ return { fontSize: 20, lineHeight: 28, fontWeight: "600", letterSpacing: -0.4, color: tokens["card-foreground"] };
40
+ }
41
+
42
+ export function description(tokens: ColorTokens): TextStyle {
43
+ return { fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] };
44
+ }
45
+
46
+ export const content: ViewStyle = { paddingHorizontal: 20, paddingVertical: 20 };
47
+
48
+ export const footer: ViewStyle = {
49
+ flexDirection: "row",
50
+ alignItems: "center",
51
+ gap: 8,
52
+ paddingHorizontal: 20,
53
+ paddingBottom: 20,
54
+ paddingTop: 16,
55
+ };
56
+
57
+ export function separator(tokens: ColorTokens): ViewStyle {
58
+ return { height: 1, width: "100%", backgroundColor: tokens.border };
59
+ }
60
+
61
+ export function bodyText(tokens: ColorTokens): TextStyle {
62
+ return { fontSize: 14, lineHeight: 20, color: tokens["card-foreground"] };
63
+ }
64
+
65
+ export function footerText(tokens: ColorTokens): TextStyle {
66
+ return { fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] };
67
+ }
@@ -0,0 +1,176 @@
1
+ import { type ReactNode } from "react";
2
+ import { View, Pressable, Text, useTheme, type StyleProp, type ViewStyle, type TextStyle } from "../../style/index.js";
3
+ import * as s from "./card.styles.js";
4
+ import { type Elevation, type Density } from "./card.styles.js";
5
+
6
+ // Card: a surface container. The base surface gives you the border, radius, and
7
+ // shadow; you bring the content. Structure is composed from the subcomponents
8
+ // (CardHeader, CardTitle, CardDescription, CardContent, CardFooter), with a
9
+ // hairline CardSeparator anchoring a header above a body.
10
+ //
11
+ // Boolean-prop API: one boolean per option, grouped by axis, first-match
12
+ // precedence within an axis (mirrors Button's intentOf). Axes:
13
+ //
14
+ // - Elevation (pick one): `raised` > `flat` > default (shadow-sm). `flat`
15
+ // drops the shadow; `raised` lifts it to shadow-md.
16
+ // - Interaction: `interactive` (with onPress) gives a pressed affordance, for a
17
+ // card that behaves like a control.
18
+ // - Padding: `padded` pads the surface itself (good for a single block of
19
+ // content); omit when you compose CardHeader/CardContent, which carry their
20
+ // own padding.
21
+ // - Density (pick one): `compact` > `comfortable`. Sets the card's own content
22
+ // padding and the gap between flat children, tight (compact) or roomy
23
+ // (comfortable). A density prop pads the surface on its own, so it needs no
24
+ // `padded`, and it wins over `padded` when both are set.
25
+ //
26
+ // For the docs playground, which maps plain state to props (no JSX children),
27
+ // Card also accepts simple string props (title, description, body, footer):
28
+ // when no children are passed it renders the header/body/footer structure from
29
+ // those strings, so a representative card can be produced from flat state.
30
+
31
+ export interface CardProps {
32
+ children?: ReactNode;
33
+ // Simple content props for the children-less / data-driven case.
34
+ title?: string;
35
+ description?: string;
36
+ body?: string;
37
+ footer?: string;
38
+ // Elevation (pick one; default is a soft shadow-sm).
39
+ raised?: boolean;
40
+ flat?: boolean;
41
+ // Interaction and padding (orthogonal booleans).
42
+ interactive?: boolean;
43
+ padded?: boolean;
44
+ /** When set, the whole card becomes pressable (a card that behaves as a control). */
45
+ onPress?: () => void;
46
+ // Density (pick one; default is the standard inset). Scales the card's own
47
+ // content padding and the gap between flat children.
48
+ compact?: boolean;
49
+ comfortable?: boolean;
50
+ /** Escape hatch for layout/positioning composition (width, margins). */
51
+ style?: StyleProp<ViewStyle>;
52
+ }
53
+
54
+ // Elevation precedence when more than one is passed: first match wins.
55
+ function elevationOf(p: CardProps): Elevation {
56
+ if (p.raised) return "raised";
57
+ if (p.flat) return "flat";
58
+ return "default";
59
+ }
60
+
61
+ // Density precedence when more than one is passed: first match wins.
62
+ function densityOf(p: CardProps): Density {
63
+ if (p.compact) return "compact";
64
+ if (p.comfortable) return "comfortable";
65
+ return "default";
66
+ }
67
+
68
+ export function Card(props: CardProps) {
69
+ const { children, title, description, body, footer, padded, onPress, style } = props;
70
+ const { tokens } = useTheme();
71
+ const elev = elevationOf(props);
72
+ const dens = densityOf(props);
73
+
74
+ const container: StyleProp<ViewStyle> = [
75
+ s.cardBase,
76
+ s.cardSurface(tokens),
77
+ s.elevation(elev),
78
+ // Density pads + gaps on its own and wins over `padded`; otherwise `padded`
79
+ // applies the standard inset, and a bare card stays unpadded for composition.
80
+ dens !== "default" ? s.density[dens] : padded ? s.padded : null,
81
+ style,
82
+ ];
83
+
84
+ // Children win: when composed, render exactly what the caller passed.
85
+ // Otherwise build a representative structure from the simple string props.
86
+ let inner: ReactNode;
87
+ if (children != null) {
88
+ inner = children;
89
+ } else {
90
+ const hasHeader = title != null || description != null;
91
+ inner = (
92
+ <>
93
+ {hasHeader ? (
94
+ <CardHeader>
95
+ {title != null ? <CardTitle>{title}</CardTitle> : null}
96
+ {description != null ? <CardDescription>{description}</CardDescription> : null}
97
+ </CardHeader>
98
+ ) : null}
99
+ {hasHeader && body != null ? <CardSeparator /> : null}
100
+ {body != null ? (
101
+ <CardContent>
102
+ <Text style={s.bodyText(tokens)}>{body}</Text>
103
+ </CardContent>
104
+ ) : null}
105
+ {footer != null ? (
106
+ <>
107
+ <CardSeparator />
108
+ <CardFooter>
109
+ <Text style={s.footerText(tokens)}>{footer}</Text>
110
+ </CardFooter>
111
+ </>
112
+ ) : null}
113
+ </>
114
+ );
115
+ }
116
+
117
+ // A pressable card swaps View for Pressable, adding the pressed affordance.
118
+ if (onPress) {
119
+ return (
120
+ <Pressable
121
+ accessibilityRole="button"
122
+ onPress={onPress}
123
+ style={({ pressed }) => [container, pressed ? { opacity: 0.9 } : null]}
124
+ >
125
+ {inner}
126
+ </Pressable>
127
+ );
128
+ }
129
+ return <View style={container}>{inner}</View>;
130
+ }
131
+
132
+ export interface CardSectionProps {
133
+ children?: ReactNode;
134
+ /** Escape hatch for layout/positioning composition. */
135
+ style?: StyleProp<ViewStyle>;
136
+ }
137
+
138
+ export interface CardTextProps {
139
+ children?: ReactNode;
140
+ /** Escape hatch for layout/positioning composition. */
141
+ style?: StyleProp<TextStyle>;
142
+ }
143
+
144
+ // Header: the labeled top of the card, holding the title and description.
145
+ export function CardHeader({ children, style }: CardSectionProps) {
146
+ return <View style={[s.header, style]}>{children}</View>;
147
+ }
148
+
149
+ // Title: the card's heading. Semibold, tight tracking, card foreground.
150
+ export function CardTitle({ children, style }: CardTextProps) {
151
+ const { tokens } = useTheme();
152
+ return <Text style={[s.title(tokens), style]}>{children}</Text>;
153
+ }
154
+
155
+ // Description: the muted supporting line beneath the title.
156
+ export function CardDescription({ children, style }: CardTextProps) {
157
+ const { tokens } = useTheme();
158
+ return <Text style={[s.description(tokens), style]}>{children}</Text>;
159
+ }
160
+
161
+ // Content: the card body region. Carries the standard surface padding.
162
+ export function CardContent({ children, style }: CardSectionProps) {
163
+ return <View style={[s.content, style]}>{children}</View>;
164
+ }
165
+
166
+ // Footer: the bottom region for actions or a summary line.
167
+ export function CardFooter({ children, style }: CardSectionProps) {
168
+ return <View style={[s.footer, style]}>{children}</View>;
169
+ }
170
+
171
+ // Separator: the hairline that anchors a header above a body. A card composing
172
+ // a header and body keeps this divider between them.
173
+ export function CardSeparator({ style }: { style?: StyleProp<ViewStyle> }) {
174
+ const { tokens } = useTheme();
175
+ return <View style={[s.separator(tokens), style]} />;
176
+ }
@@ -0,0 +1,159 @@
1
+ # Code Block
2
+
3
+ Preformatted code block with monospace font and padding.
4
+
5
+ ## Usage
6
+
7
+ ```tsx
8
+ <CodeBlock
9
+ code={`const theme = getTheme();
10
+ setTheme(theme === "dark" ? "light" : "dark");`}
11
+ filename="theme.ts"
12
+ language="ts"
13
+ />
14
+ ```
15
+
16
+ ## Variants
17
+
18
+ ### Variant - terminal
19
+
20
+ ```tsx
21
+ <CodeBlock
22
+ code={`const theme = getTheme();
23
+ setTheme(theme === "dark" ? "light" : "dark");`}
24
+ filename="theme.ts"
25
+ language="ts"
26
+ terminal
27
+ />
28
+ ```
29
+
30
+ ### Variant - numbered
31
+
32
+ ```tsx
33
+ <CodeBlock
34
+ code={`const theme = getTheme();
35
+ setTheme(theme === "dark" ? "light" : "dark");`}
36
+ filename="theme.ts"
37
+ language="ts"
38
+ numbered
39
+ />
40
+ ```
41
+
42
+ ### Variant - inline
43
+
44
+ ```tsx
45
+ <CodeBlock
46
+ code={`const theme = getTheme();
47
+ setTheme(theme === "dark" ? "light" : "dark");`}
48
+ filename="theme.ts"
49
+ language="ts"
50
+ inline
51
+ />
52
+ ```
53
+
54
+ ### Copy button
55
+
56
+ ```tsx
57
+ <CodeBlock
58
+ code={`const theme = getTheme();
59
+ setTheme(theme === "dark" ? "light" : "dark");`}
60
+ filename="theme.ts"
61
+ language="ts"
62
+ copy
63
+ />
64
+ ```
65
+
66
+ ### Wrap long lines
67
+
68
+ ```tsx
69
+ <CodeBlock
70
+ code={`const theme = getTheme();
71
+ setTheme(theme === "dark" ? "light" : "dark");`}
72
+ filename="theme.ts"
73
+ language="ts"
74
+ wrap
75
+ />
76
+ ```
77
+
78
+ ## Do & Don't
79
+
80
+ ### Plain
81
+
82
+ **Do** — Use a pre element so whitespace, line breaks, and indentation survive verbatim.
83
+
84
+ ```tsx
85
+ <CodeBlock code={`const theme = getTheme();
86
+ setTheme(theme === "dark" ? "light" : "dark");`} />
87
+ ```
88
+
89
+ **Don't** — A paragraph collapses the line breaks and indentation, so multi-line code reads as one run-on string.
90
+
91
+ ```tsx
92
+ <View style={{ maxWidth: 360 }}>
93
+ <Text style={{ fontSize: 13, fontFamily: "monospace" }}>const theme = getTheme(); setTheme(theme === "dark" ? "light" : "dark");</Text>
94
+ </View>
95
+ ```
96
+
97
+ ### Terminal
98
+
99
+ **Do** — Mark the prompt select-none so a copy yields only the command, not the shell glyph.
100
+
101
+ ```tsx
102
+ <CodeBlock terminal code="npm install @olympusoss/canvas" />
103
+ ```
104
+
105
+ **Don't** — Selectable prompt text means a reader who copies the line drags the $ marker into their shell.
106
+
107
+ ```tsx
108
+ <View style={{ width: "100%", alignSelf: "flex-start", borderRadius: 8, backgroundColor: palette["zinc-900"], padding: 16 }}>
109
+ <Text style={{ fontSize: 13, color: palette["zinc-100"], fontFamily: "monospace" }}>$ npm install @olympusoss/canvas</Text>
110
+ </View>
111
+ ```
112
+
113
+ ### Numbered
114
+
115
+ **Do** — Keep the gutter select-none so copying the block returns clean, runnable code.
116
+
117
+ ```tsx
118
+ <CodeBlock numbered code={`const theme = getTheme();
119
+ setTheme(theme);`} />
120
+ ```
121
+
122
+ **Don't** — Selectable line numbers get swept into the selection and pasted as 1 2 ahead of every line.
123
+
124
+ ```tsx
125
+ <View style={{ width: "100%", alignSelf: "flex-start", flexDirection: "row", borderRadius: 8, borderWidth: 1, borderColor: tokens.border, backgroundColor: alpha(tokens.muted, 0.5), padding: 16 }}>
126
+ <View style={{ marginRight: 16, alignItems: "flex-end" }}>
127
+ <Text style={{ fontSize: 14, lineHeight: 28, color: alpha(tokens["muted-foreground"], 0.5), fontFamily: "monospace" }}>1</Text>
128
+ <Text style={{ fontSize: 14, lineHeight: 28, color: alpha(tokens["muted-foreground"], 0.5), fontFamily: "monospace" }}>2</Text>
129
+ </View>
130
+ <View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%" }}>
131
+ <Text style={{ fontSize: 14, lineHeight: 28, color: tokens.foreground, fontFamily: "monospace" }}>const theme = getTheme();</Text>
132
+ <Text style={{ fontSize: 14, lineHeight: 28, color: tokens.foreground, fontFamily: "monospace" }}>setTheme(theme);</Text>
133
+ </View>
134
+ </View>
135
+ ```
136
+
137
+ ### Inline
138
+
139
+ **Do** — Reserve inline code for short tokens; move anything multi-line into a block.
140
+
141
+ ```tsx
142
+ <View style={{ maxWidth: 360, gap: 6 }}>
143
+ <Text style={{ fontSize: 14, lineHeight: 28 }}>Run the setup command:</Text>
144
+ <CodeBlock code={`npm install @olympusoss/canvas
145
+ npm run build`} />
146
+ </View>
147
+ ```
148
+
149
+ **Don't** — A long, multi-step command crammed inline wraps mid-token and offers no horizontal scroll.
150
+
151
+ ```tsx
152
+ <View style={{ maxWidth: 360, flexDirection: "row", flexWrap: "wrap", alignItems: "center" }}>
153
+ <Text style={{ fontSize: 14, lineHeight: 28, color: tokens.foreground }}>Run </Text>
154
+ <View style={{ alignSelf: "flex-start", borderRadius: 4, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.muted, paddingHorizontal: 6, paddingVertical: 2 }}>
155
+ <Text style={{ fontSize: 13, color: tokens.foreground, fontFamily: "monospace" }}>npm install @olympusoss/canvas && npm run build && npm run preview</Text>
156
+ </View>
157
+ <Text style={{ fontSize: 14, lineHeight: 28, color: tokens.foreground }}> to start.</Text>
158
+ </View>
159
+ ```
@@ -0,0 +1,167 @@
1
+ import { type ViewStyle, type TextStyle } from "react-native";
2
+ import { type ColorTokens, palette, shadow, alpha } from "../../style/index.js";
3
+
4
+ // Co-located CodeBlock styles. Layout-only fragments are static objects; anything
5
+ // that reads a color is a function of the active tokens, so the muted surface
6
+ // follows light/dark and reads as glass at the theming level. The terminal
7
+ // variant is intentionally a fixed dark window built from the Tailwind palette
8
+ // (zinc/emerald/red/amber/green), so it stays dark in every scheme.
9
+
10
+ export type Variant = "terminal" | "numbered" | "inline" | "plain";
11
+
12
+ // Monospace face: requested inline since RN has no font-family utility (the same
13
+ // approach Badge's `mono` modifier uses).
14
+ export const MONO = { fontFamily: "monospace" } as const;
15
+
16
+ // Shared code type: text-sm leading-relaxed.
17
+ export const codeType: TextStyle = { fontSize: 14, lineHeight: 28 };
18
+
19
+ // Code foreground color (text-foreground).
20
+ export function codeText(tokens: ColorTokens): TextStyle {
21
+ return { color: tokens.foreground };
22
+ }
23
+
24
+ // The shared code surface: a muted, bordered, rounded card. (w-full self-start
25
+ // rounded-lg border border-border bg-muted/50)
26
+ export function surface(tokens: ColorTokens): ViewStyle {
27
+ return {
28
+ width: "100%",
29
+ alignSelf: "flex-start",
30
+ borderRadius: 8,
31
+ borderWidth: 1,
32
+ borderColor: tokens.border,
33
+ backgroundColor: alpha(tokens.muted, 0.5),
34
+ };
35
+ }
36
+
37
+ // Surface padding (p-4) added on top of `surface`.
38
+ export const surfacePad: ViewStyle = { padding: 16 };
39
+
40
+ // --- outer wrappers ---------------------------------------------------------
41
+
42
+ // relative
43
+ export const relative: ViewStyle = { position: "relative" };
44
+
45
+ // --- inline variant ---------------------------------------------------------
46
+
47
+ // self-start rounded border border-border bg-muted px-1.5 py-0.5
48
+ export function inlineBox(tokens: ColorTokens): ViewStyle {
49
+ return {
50
+ alignSelf: "flex-start",
51
+ borderRadius: 4,
52
+ borderWidth: 1,
53
+ borderColor: tokens.border,
54
+ backgroundColor: tokens.muted,
55
+ paddingHorizontal: 6,
56
+ paddingVertical: 2,
57
+ };
58
+ }
59
+
60
+ // --- terminal variant -------------------------------------------------------
61
+
62
+ // relative w-full self-start overflow-hidden rounded-lg border border-border shadow-sm
63
+ export function terminalOuter(tokens: ColorTokens): ViewStyle {
64
+ return {
65
+ position: "relative",
66
+ width: "100%",
67
+ alignSelf: "flex-start",
68
+ overflow: "hidden",
69
+ borderRadius: 8,
70
+ borderWidth: 1,
71
+ borderColor: tokens.border,
72
+ ...shadow("sm"),
73
+ };
74
+ }
75
+
76
+ // Chrome bar: flex-row items-center gap-1.5 border-b border-zinc-700 bg-zinc-800 px-4 py-2.5
77
+ export const terminalChrome: ViewStyle = {
78
+ flexDirection: "row",
79
+ alignItems: "center",
80
+ gap: 6,
81
+ borderBottomWidth: 1,
82
+ borderColor: palette["zinc-700"],
83
+ backgroundColor: palette["zinc-800"],
84
+ paddingHorizontal: 16,
85
+ paddingVertical: 10,
86
+ };
87
+
88
+ // Traffic-light dot: h-3 w-3 rounded-full bg-<hue>-500.
89
+ export function trafficDot(hue: "red" | "amber" | "green"): ViewStyle {
90
+ return { height: 12, width: 12, borderRadius: 9999, backgroundColor: palette[`${hue}-500`] };
91
+ }
92
+
93
+ // Chrome label: ml-2 text-xs text-zinc-400.
94
+ export const terminalLabel: TextStyle = {
95
+ marginLeft: 8,
96
+ fontSize: 12,
97
+ lineHeight: 16,
98
+ color: palette["zinc-400"],
99
+ };
100
+
101
+ // Terminal body: bg-zinc-900 p-4.
102
+ export const terminalBody: ViewStyle = { backgroundColor: palette["zinc-900"], padding: 16 };
103
+
104
+ // A single command row: flex-row.
105
+ export const terminalRow: ViewStyle = { flexDirection: "row" };
106
+
107
+ // The non-selectable "$ " prompt: text-emerald-400.
108
+ export const terminalPrompt: TextStyle = { color: palette["emerald-400"] };
109
+
110
+ // The command line itself: flex-1 text-zinc-100.
111
+ export const terminalLine: TextStyle = {
112
+ flexGrow: 1,
113
+ flexShrink: 1,
114
+ flexBasis: "0%",
115
+ color: palette["zinc-100"],
116
+ };
117
+
118
+ // --- numbered variant -------------------------------------------------------
119
+
120
+ // The numbered surface row: flex-row p-4 (on top of `surface`).
121
+ export const numberedSurface: ViewStyle = { flexDirection: "row", padding: 16 };
122
+
123
+ // The line-number gutter: mr-4 items-end.
124
+ export const numberedGutter: ViewStyle = { marginRight: 16, alignItems: "flex-end" };
125
+
126
+ // Dimmed line numbers (text-muted-foreground), sharing the code type.
127
+ export function gutterText(tokens: ColorTokens): TextStyle {
128
+ return { color: tokens["muted-foreground"] };
129
+ }
130
+
131
+ // The code column: flex-1.
132
+ export const numberedCodeCol: ViewStyle = { flexGrow: 1, flexShrink: 1, flexBasis: "0%" };
133
+
134
+ // --- copy affordance --------------------------------------------------------
135
+
136
+ // absolute right-2 top-2 z-10 flex-row items-center self-start rounded-md border px-2.5 py-1.
137
+ // The fill/border follows the dark branch: dark window vs light surface.
138
+ export function copyButton(tokens: ColorTokens, dark: boolean): ViewStyle {
139
+ const base: ViewStyle = {
140
+ position: "absolute",
141
+ right: 8,
142
+ top: 8,
143
+ zIndex: 10,
144
+ flexDirection: "row",
145
+ alignItems: "center",
146
+ alignSelf: "flex-start",
147
+ borderRadius: 6,
148
+ borderWidth: 1,
149
+ paddingHorizontal: 10,
150
+ paddingVertical: 4,
151
+ };
152
+ // The `dark` flag here is the CopyButton's own (terminal forces it); it picks
153
+ // the dark zinc chip vs the light background chip with a soft shadow.
154
+ return dark
155
+ ? { ...base, borderColor: palette["zinc-700"], backgroundColor: palette["zinc-800"] }
156
+ : { ...base, borderColor: tokens.border, backgroundColor: tokens.background, ...shadow("sm") };
157
+ }
158
+
159
+ // Copy label: text-xs font-medium, color per dark branch.
160
+ export function copyText(tokens: ColorTokens, dark: boolean): TextStyle {
161
+ return {
162
+ fontSize: 12,
163
+ lineHeight: 16,
164
+ fontWeight: "500",
165
+ color: dark ? palette["zinc-300"] : tokens["muted-foreground"],
166
+ };
167
+ }