@olympusoss/canvas 3.2.1 → 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 (302) hide show
  1. package/README.md +75 -65
  2. package/package.json +11 -5
  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/styles/canvas.css +127 -74
  234. package/tsconfig.json +4 -2
  235. package/src/cn.ts +0 -3
  236. package/styles/atoms/avatar.css +0 -22
  237. package/styles/atoms/badge.css +0 -83
  238. package/styles/atoms/breadcrumb.css +0 -35
  239. package/styles/atoms/button-group.css +0 -23
  240. package/styles/atoms/button.css +0 -107
  241. package/styles/atoms/checkbox.css +0 -55
  242. package/styles/atoms/combobox.css +0 -76
  243. package/styles/atoms/dropdown.css +0 -54
  244. package/styles/atoms/icon.css +0 -8
  245. package/styles/atoms/input-group.css +0 -45
  246. package/styles/atoms/input.css +0 -56
  247. package/styles/atoms/kbd.css +0 -15
  248. package/styles/atoms/pagination.css +0 -48
  249. package/styles/atoms/popover.css +0 -14
  250. package/styles/atoms/radio.css +0 -28
  251. package/styles/atoms/select.css +0 -57
  252. package/styles/atoms/separator.css +0 -32
  253. package/styles/atoms/skeleton.css +0 -32
  254. package/styles/atoms/spinner.css +0 -26
  255. package/styles/atoms/switch.css +0 -45
  256. package/styles/atoms/textarea.css +0 -31
  257. package/styles/atoms/tooltip.css +0 -53
  258. package/styles/atoms/typography.css +0 -105
  259. package/styles/base.css +0 -17
  260. package/styles/molecules/alert.css +0 -66
  261. package/styles/molecules/card.css +0 -58
  262. package/styles/molecules/code-block.css +0 -18
  263. package/styles/molecules/empty-state.css +0 -17
  264. package/styles/molecules/field.css +0 -27
  265. package/styles/molecules/form.css +0 -27
  266. package/styles/molecules/page-header.css +0 -52
  267. package/styles/molecules/section-card.css +0 -49
  268. package/styles/molecules/stat-card.css +0 -71
  269. package/styles/molecules/toast.css +0 -95
  270. package/styles/organisms/app-shell.css +0 -46
  271. package/styles/organisms/calendar.css +0 -73
  272. package/styles/organisms/command.css +0 -95
  273. package/styles/organisms/data-table.css +0 -142
  274. package/styles/organisms/dialog.css +0 -72
  275. package/styles/organisms/filter-panel.css +0 -58
  276. package/styles/organisms/row-menu.css +0 -69
  277. package/styles/organisms/sheet.css +0 -70
  278. package/styles/organisms/sidebar.css +0 -146
  279. package/styles/organisms/stepper.css +0 -63
  280. package/styles/organisms/tabs.css +0 -40
  281. package/styles/organisms/topbar.css +0 -24
  282. package/styles/patterns/backdrops.css +0 -35
  283. package/styles/patterns/density.css +0 -66
  284. package/styles/patterns/focus.css +0 -22
  285. package/styles/patterns/glass.css +0 -85
  286. package/styles/patterns/high-contrast.css +0 -70
  287. package/styles/patterns/reduced-motion.css +0 -12
  288. package/styles/patterns/scrollbar.css +0 -10
  289. package/styles/reset.css +0 -89
  290. package/styles/tokens/colors.css +0 -108
  291. package/styles/tokens/motion.css +0 -33
  292. package/styles/tokens/radius.css +0 -10
  293. package/styles/tokens/shadows.css +0 -35
  294. package/styles/tokens/spacing.css +0 -19
  295. package/styles/tokens/typography.css +0 -6
  296. package/styles/tokens/z-index.css +0 -12
  297. package/styles/utilities/display.css +0 -66
  298. package/styles/utilities/flexbox.css +0 -240
  299. package/styles/utilities/gap.css +0 -288
  300. package/styles/utilities/grid.css +0 -138
  301. package/styles/utilities/position.css +0 -78
  302. package/styles/utilities/sizing.css +0 -138
@@ -0,0 +1,289 @@
1
+ import { View, Pressable, Text, useTheme, type StyleProp, type ViewStyle, type ColorTokens } from "../../style/index.js";
2
+ import * as s from "./pagination.styles.js";
3
+ import { type Size, type PaginationSkin } from "./pagination.styles.js";
4
+
5
+ // Shared Pagination shell. The structure (numbered / compact / with-size
6
+ // variants, the windowing math, the clamp + go handler, the size-selector
7
+ // cycling), the accessibility, and the variant/size precedence live here once; a
8
+ // platform file supplies only its skin (the cell shape, the active-page fill,
9
+ // the label color, and the press feedback) and calls createPagination.
10
+ //
11
+ // Pagination is page-of-N navigation for tables and lists: a horizontal row of
12
+ // page-number buttons flanked by Prev/Next controls, with the current page
13
+ // highlighted. When there are too many pages to show at once, the middle is
14
+ // truncated with an ellipsis glyph, keeping the first page, the last page, and a
15
+ // small window around the current page.
16
+ //
17
+ // The brand survives on every platform (the indigo `primary` token fills the
18
+ // active page); only the native SHAPE (cell radius) and press feedback change:
19
+ // - iOS (HIG page controls): pill-rounded cells (radius ~8), the active page
20
+ // filled `primary`; press = opacity dim 0.8.
21
+ // - Android (M3): flat cells (radius ~8), active page a tonal alpha(primary)
22
+ // fill with a brand label; press = android_ripple.
23
+ // - Web: the established Canvas look (bordered boxes, radius 6, solid primary
24
+ // fill on the active page), lifted verbatim.
25
+ //
26
+ // Boolean-prop API across two axes (mirrors Button's intentOf precedence; first
27
+ // match wins within an axis, axes are orthogonal):
28
+ // - Variant: `withSize` prepends a "Rows per page" size selector to the
29
+ // compact Prev/Next + "Page X of N" layout; `compact` collapses the number
30
+ // row to just Prev/Next plus a "Page X of N" label; the default is the full
31
+ // numbered row. Precedence when more than one is passed: withSize, then
32
+ // compact, then the numbered default.
33
+ // - Size: `small`, `large` (omit for the default, medium size).
34
+ //
35
+ // There is no icon utility at this layer, so Prev/Next use reading-direction
36
+ // single guillemet glyphs ("‹" / "›") rendered as Text rather than SVG chevrons,
37
+ // the size selector uses a "▾" caret glyph, and the truncation gap is an ellipsis
38
+ // glyph ("…").
39
+
40
+ export interface PaginationProps {
41
+ /** Current page, 1-based. Clamped into the 1..total range before rendering. */
42
+ page?: number;
43
+ /** Total number of pages. */
44
+ total?: number;
45
+ /** Fired with the next 1-based page when a control or number is pressed. */
46
+ onChange?: (page: number) => void;
47
+
48
+ // Variant (pick one; default is the full numbered row).
49
+ /** Collapse to Prev/Next plus a "Page X of N" label, no number buttons. */
50
+ compact?: boolean;
51
+ /**
52
+ * Prepend a "Rows per page" size selector to the compact Prev/Next +
53
+ * "Page X of N" layout. Takes precedence over `compact`.
54
+ */
55
+ withSize?: boolean;
56
+
57
+ // Content for the `withSize` selector.
58
+ /** Currently selected rows-per-page value shown in the selector. */
59
+ pageSize?: number;
60
+ /** Selectable rows-per-page values; pressing the selector advances through them. */
61
+ pageSizes?: number[];
62
+ /** Fired with the next rows-per-page value when the selector is pressed. */
63
+ onPageSizeChange?: (size: number) => void;
64
+
65
+ // Size (pick one; default is the medium size).
66
+ small?: boolean;
67
+ large?: boolean;
68
+
69
+ disabled?: boolean;
70
+ /** Escape hatch for layout/positioning composition (margins, alignment). */
71
+ style?: StyleProp<ViewStyle>;
72
+ }
73
+
74
+ // Size precedence when more than one is passed: first match wins.
75
+ function sizeOf(p: PaginationProps): Size {
76
+ if (p.small) return "small";
77
+ if (p.large) return "large";
78
+ return "default";
79
+ }
80
+
81
+ type Variant = "withSize" | "compact" | "numbered";
82
+
83
+ // Variant precedence when more than one is passed: first match wins.
84
+ function variantOf(p: PaginationProps): Variant {
85
+ if (p.withSize) return "withSize";
86
+ if (p.compact) return "compact";
87
+ return "numbered";
88
+ }
89
+
90
+ // Sentinel inserted into the page list for a truncation gap.
91
+ const GAP = -1;
92
+
93
+ // Compute the windowed page list: first page, last page, and a one-page window
94
+ // around the current page, with GAP sentinels marking truncated stretches. For
95
+ // small totals (<= 7) every page is shown.
96
+ function pageWindow(current: number, total: number): number[] {
97
+ if (total <= 7) {
98
+ const all: number[] = [];
99
+ for (let p = 1; p <= total; p++) all.push(p);
100
+ return all;
101
+ }
102
+ const list: number[] = [];
103
+ const add = (p: number) => {
104
+ if (!list.includes(p)) list.push(p);
105
+ };
106
+ add(1);
107
+ if (current > 3) list.push(GAP);
108
+ for (let p = Math.max(2, current - 1); p <= Math.min(total - 1, current + 1); p++) add(p);
109
+ if (current < total - 2) list.push(GAP);
110
+ add(total);
111
+ return list;
112
+ }
113
+
114
+ /** Build a Pagination component from a platform skin. */
115
+ export function createPagination(skin: PaginationSkin) {
116
+ const ripple = skin.ripple;
117
+
118
+ interface ControlProps {
119
+ glyph: string;
120
+ size: Size;
121
+ tokens: ColorTokens;
122
+ disabled: boolean;
123
+ accessibilityLabel: string;
124
+ onPress: () => void;
125
+ }
126
+
127
+ // A Prev/Next chevron control. Reads as a square page button without a number.
128
+ function Control({ glyph, size, tokens, disabled, accessibilityLabel, onPress }: ControlProps) {
129
+ return (
130
+ <Pressable
131
+ style={({ pressed }) => [
132
+ skin.controlBox(tokens),
133
+ s.itemSize[size],
134
+ disabled ? { opacity: 0.5 } : null,
135
+ skin.pressedOpacity != null && pressed ? { opacity: skin.pressedOpacity } : null,
136
+ ]}
137
+ onPress={onPress}
138
+ disabled={disabled}
139
+ android_ripple={ripple ? ripple(tokens, false) : undefined}
140
+ accessibilityRole="button"
141
+ accessibilityLabel={accessibilityLabel}
142
+ accessibilityState={{ disabled }}
143
+ >
144
+ <Text style={[skin.controlLabel(tokens), s.labelSize[size]]}>{glyph}</Text>
145
+ </Pressable>
146
+ );
147
+ }
148
+
149
+ return function Pagination(props: PaginationProps) {
150
+ const { onChange, disabled, style } = props;
151
+ const size = sizeOf(props);
152
+ const variant = variantOf(props);
153
+ const { tokens } = useTheme();
154
+
155
+ // Clamp inputs so the control never renders an out-of-range current page.
156
+ const total = Math.max(1, Math.floor(props.total ?? 1));
157
+ const current = Math.min(Math.max(1, Math.floor(props.page ?? 1)), total);
158
+
159
+ const atStart = current <= 1;
160
+ const atEnd = current >= total;
161
+
162
+ const go = (next: number) => {
163
+ if (disabled) return;
164
+ const clamped = Math.min(Math.max(1, next), total);
165
+ if (clamped !== current) onChange?.(clamped);
166
+ };
167
+
168
+ const prev = (
169
+ <Control
170
+ glyph="‹"
171
+ size={size}
172
+ tokens={tokens}
173
+ disabled={disabled || atStart}
174
+ accessibilityLabel="Previous page"
175
+ onPress={() => go(current - 1)}
176
+ />
177
+ );
178
+ const next = (
179
+ <Control
180
+ glyph="›"
181
+ size={size}
182
+ tokens={tokens}
183
+ disabled={disabled || atEnd}
184
+ accessibilityLabel="Next page"
185
+ onPress={() => go(current + 1)}
186
+ />
187
+ );
188
+
189
+ // Compact: Prev/Next bracketing a "Page X of N" indicator, no number buttons.
190
+ if (variant === "compact") {
191
+ return (
192
+ <View style={[s.compactRow, style]}>
193
+ {prev}
194
+ <Text style={[skin.mutedLabel(tokens), s.labelSize[size]]}>
195
+ {`Page ${current} of ${total}`}
196
+ </Text>
197
+ {next}
198
+ </View>
199
+ );
200
+ }
201
+
202
+ // With-size: a "Rows per page" selector ahead of the compact indicator and the
203
+ // Prev/Next controls. There is no native select, so the selector is a closed
204
+ // trigger (value + caret) that advances through `pageSizes` on press.
205
+ if (variant === "withSize") {
206
+ const sizes = props.pageSizes ?? [10, 25, 50];
207
+ const pageSize = props.pageSize ?? sizes[0] ?? 10;
208
+ const cycleSize = () => {
209
+ if (disabled) return;
210
+ const i = sizes.indexOf(pageSize);
211
+ const nextSize = sizes[(i + 1) % sizes.length];
212
+ if (nextSize !== undefined && nextSize !== pageSize) props.onPageSizeChange?.(nextSize);
213
+ };
214
+ return (
215
+ <View style={[s.withSizeRow, style]}>
216
+ <View style={s.selectorCluster}>
217
+ <Text style={[skin.mutedLabel(tokens), s.labelSize[size]]}>Rows per page</Text>
218
+ <Pressable
219
+ style={({ pressed }) => [
220
+ skin.selectorBox(tokens),
221
+ s.itemSize[size],
222
+ disabled ? { opacity: 0.5 } : null,
223
+ skin.pressedOpacity != null && pressed ? { opacity: skin.pressedOpacity } : null,
224
+ ]}
225
+ onPress={cycleSize}
226
+ disabled={disabled}
227
+ android_ripple={ripple ? ripple(tokens, false) : undefined}
228
+ accessibilityRole="button"
229
+ accessibilityLabel="Rows per page"
230
+ >
231
+ <Text style={[skin.controlLabel(tokens), s.labelSize[size]]}>{pageSize}</Text>
232
+ <Text style={[skin.mutedLabel(tokens), s.labelSize[size]]}>▾</Text>
233
+ </Pressable>
234
+ </View>
235
+ <Text style={[skin.mutedLabel(tokens), s.labelSize[size]]}>
236
+ {`Page ${current} of ${total}`}
237
+ </Text>
238
+ <View style={s.controlPair}>
239
+ {prev}
240
+ {next}
241
+ </View>
242
+ </View>
243
+ );
244
+ }
245
+
246
+ // Numbered (default): a windowed row of page buttons with ellipsis gaps.
247
+ const window = pageWindow(current, total);
248
+
249
+ return (
250
+ <View style={[s.numberedRow, style]}>
251
+ {prev}
252
+ {window.map((p, i) => {
253
+ if (p === GAP) {
254
+ return (
255
+ <Text
256
+ key={`gap-${i}`}
257
+ style={[skin.gapLabel(tokens), s.labelSize[size]]}
258
+ accessibilityElementsHidden
259
+ >
260
+
261
+ </Text>
262
+ );
263
+ }
264
+ const selected = p === current;
265
+ return (
266
+ <Pressable
267
+ key={`page-${p}`}
268
+ style={({ pressed }) => [
269
+ skin.pageBox(tokens, selected),
270
+ s.itemSize[size],
271
+ disabled ? { opacity: 0.5 } : null,
272
+ skin.pressedOpacity != null && pressed ? { opacity: skin.pressedOpacity } : null,
273
+ ]}
274
+ onPress={() => go(p)}
275
+ disabled={disabled}
276
+ android_ripple={ripple ? ripple(tokens, selected) : undefined}
277
+ accessibilityRole="button"
278
+ accessibilityLabel={`Page ${p}`}
279
+ accessibilityState={{ selected, disabled: !!disabled }}
280
+ >
281
+ <Text style={[skin.pageLabel(tokens, selected), s.labelSize[size]]}>{p}</Text>
282
+ </Pressable>
283
+ );
284
+ })}
285
+ {next}
286
+ </View>
287
+ );
288
+ };
289
+ }
@@ -0,0 +1,245 @@
1
+ import { type ViewStyle, type TextStyle } from "react-native";
2
+ import { type ColorTokens, alpha } from "../../style/index.js";
3
+
4
+ // Co-located Pagination skins, one per platform. The page buttons, Prev/Next
5
+ // controls, and the size selector are square-ish boxes whose footprint scales
6
+ // with the size axis; labels scale on a smaller type ramp. The BRAND survives on
7
+ // every platform (the active page reads the indigo `primary` token, never a
8
+ // platform default), and only the native SHAPE (cell radius / border) and press
9
+ // feedback change per OS. This is a LIGHT platform touch: neither iOS nor
10
+ // Material 3 ships a true numbered pagination, so the prev/next/numbered
11
+ // structure stays identical and each platform applies only its shape + feedback.
12
+ // iOS (HIG page controls): pill-rounded cells (radius 8), no cell border, the
13
+ // ACTIVE page filled `primary`; press = opacity dim 0.8.
14
+ // Android (M3): flat cells (radius 8), no border, the ACTIVE page a tonal
15
+ // alpha(primary, .12) fill with a brand-indigo label; press = android_ripple.
16
+ // Web: the established Canvas look (1px bordered boxes, radius 6, the active
17
+ // page a solid `primary` fill/border), lifted verbatim.
18
+
19
+ export type Size = "small" | "default" | "large";
20
+
21
+ // The platform-varying surface. Everything color/shape-bearing the cells need
22
+ // lives here, built from the active tokens (so fills/borders follow light/dark
23
+ // and the glass surface). Layout (gaps, rows) is shared and stays in this file's
24
+ // static fragments below.
25
+ export interface PaginationSkin {
26
+ /** A Prev/Next chevron control: the bordered/filled box behind the glyph. */
27
+ controlBox: (t: ColorTokens) => ViewStyle;
28
+ /** A numbered page cell. Selected reads the brand fill; the rest are plain. */
29
+ pageBox: (t: ColorTokens, selected: boolean) => ViewStyle;
30
+ /** The rows-per-page selector trigger box (value + caret). */
31
+ selectorBox: (t: ColorTokens) => ViewStyle;
32
+ /** Control glyph / selector value color (foreground). */
33
+ controlLabel: (t: ColorTokens) => TextStyle;
34
+ /** A numbered page label; brand-on-fill when selected, foreground otherwise. */
35
+ pageLabel: (t: ColorTokens, selected: boolean) => TextStyle;
36
+ /** Muted supporting text: the "Page X of N" indicator, "Rows per page", caret. */
37
+ mutedLabel: (t: ColorTokens) => TextStyle;
38
+ /** The truncation ellipsis: muted, with a small horizontal inset. */
39
+ gapLabel: (t: ColorTokens) => TextStyle;
40
+ /** iOS/web dim the cell on press; Android uses a ripple instead (null). */
41
+ pressedOpacity: number | null;
42
+ /** Android ripple over a pressed cell; null on iOS/web. */
43
+ ripple?: (t: ColorTokens, selected: boolean) => { color: string; borderless: boolean };
44
+ }
45
+
46
+ // --- shared size scales (brand type/sizing, identical across platforms) ------
47
+
48
+ // Square-ish button footprint per size: height + matching min width + the
49
+ // horizontal pad (`h-8 min-w-8 px-2` / `h-9 min-w-9 px-2.5` / `h-10 min-w-10 px-3`).
50
+ export const itemSize: Record<Size, ViewStyle> = {
51
+ small: { height: 32, minWidth: 32, paddingHorizontal: 8 },
52
+ default: { height: 36, minWidth: 36, paddingHorizontal: 10 },
53
+ large: { height: 40, minWidth: 40, paddingHorizontal: 12 },
54
+ };
55
+
56
+ // Label type per size (`text-xs` for small, `text-sm` otherwise).
57
+ export const labelSize: Record<Size, TextStyle> = {
58
+ small: { fontSize: 12, lineHeight: 16 },
59
+ default: { fontSize: 14, lineHeight: 20 },
60
+ large: { fontSize: 14, lineHeight: 20 },
61
+ };
62
+
63
+ // --- shared layout fragments (color-free; identical across platforms) --------
64
+
65
+ // Row of [prev, numbers, next] for the numbered default (`gap-1`).
66
+ export const numberedRow: ViewStyle = { flexDirection: "row", alignItems: "center", gap: 4 };
67
+
68
+ // Compact row: Prev/Next bracketing the "Page X of N" label (`gap-2`).
69
+ export const compactRow: ViewStyle = { flexDirection: "row", alignItems: "center", gap: 8 };
70
+
71
+ // With-size outer row: the selector group, the indicator, and the controls (`gap-4`).
72
+ export const withSizeRow: ViewStyle = { flexDirection: "row", alignItems: "center", gap: 16 };
73
+
74
+ // The "Rows per page" label + selector cluster (`gap-2`).
75
+ export const selectorCluster: ViewStyle = { flexDirection: "row", alignItems: "center", gap: 8 };
76
+
77
+ // The Prev/Next pair inside the with-size row (`gap-1`).
78
+ export const controlPair: ViewStyle = { flexDirection: "row", alignItems: "center", gap: 4 };
79
+
80
+ // The centered-row base every cell shares (the skin layers fill/border/radius on top).
81
+ const CELL_ROW: ViewStyle = { flexDirection: "row", alignItems: "center", justifyContent: "center" };
82
+
83
+ // =============================================================================
84
+ // Web: the established Canvas look (lifted verbatim from the original file).
85
+ // =============================================================================
86
+
87
+ export const webSkin: PaginationSkin = {
88
+ // A Prev/Next chevron control: a square bordered box on the background fill
89
+ // (`rounded-md border border-input bg-background`).
90
+ controlBox(t) {
91
+ return {
92
+ ...CELL_ROW,
93
+ borderRadius: 6,
94
+ borderWidth: 1,
95
+ borderColor: t.input,
96
+ backgroundColor: t.background,
97
+ };
98
+ },
99
+ // A numbered page button. Selected uses the primary fill/border; the rest match
100
+ // the bordered background box (`border-primary bg-primary` vs `border-input bg-background`).
101
+ pageBox(t, selected) {
102
+ return {
103
+ ...CELL_ROW,
104
+ borderRadius: 6,
105
+ borderWidth: 1,
106
+ borderColor: selected ? t.primary : t.input,
107
+ backgroundColor: selected ? t.primary : t.background,
108
+ };
109
+ },
110
+ // The size selector trigger: value + caret pushed apart in a bordered box
111
+ // (`flex-row items-center justify-between gap-1 rounded-md border border-input bg-background`).
112
+ selectorBox(t) {
113
+ return {
114
+ flexDirection: "row",
115
+ alignItems: "center",
116
+ justifyContent: "space-between",
117
+ gap: 4,
118
+ borderRadius: 6,
119
+ borderWidth: 1,
120
+ borderColor: t.input,
121
+ backgroundColor: t.background,
122
+ };
123
+ },
124
+ // Control glyph / selector value color (`font-medium text-foreground`).
125
+ controlLabel(t) {
126
+ return { fontWeight: "500", color: t.foreground };
127
+ },
128
+ // A numbered page label: medium weight, primary-foreground when selected
129
+ // (`font-medium` + `text-primary-foreground` / `text-foreground`).
130
+ pageLabel(t, selected) {
131
+ return { fontWeight: "500", color: selected ? t["primary-foreground"] : t.foreground };
132
+ },
133
+ // Muted supporting text (`text-muted-foreground`).
134
+ mutedLabel(t) {
135
+ return { color: t["muted-foreground"] };
136
+ },
137
+ // The truncation ellipsis: muted, with a small horizontal inset (`px-1`).
138
+ gapLabel(t) {
139
+ return { paddingHorizontal: 4, color: t["muted-foreground"] };
140
+ },
141
+ pressedOpacity: 0.9,
142
+ };
143
+
144
+ // =============================================================================
145
+ // iOS (HIG page controls): pill-rounded cells, the active page filled primary.
146
+ // =============================================================================
147
+
148
+ export const iosSkin: PaginationSkin = {
149
+ // Pill-rounded (radius 8), no border; the chevron reads as a plain glyph on the
150
+ // background so the numbered cells carry the shape.
151
+ controlBox(t) {
152
+ return {
153
+ ...CELL_ROW,
154
+ borderRadius: 8,
155
+ backgroundColor: t.background,
156
+ };
157
+ },
158
+ // The active page is a filled `primary` pill (radius 8); inactive pages are
159
+ // plain background pills with no border (HIG dots: filled current, hollow rest).
160
+ pageBox(t, selected) {
161
+ return {
162
+ ...CELL_ROW,
163
+ borderRadius: 8,
164
+ backgroundColor: selected ? t.primary : t.background,
165
+ };
166
+ },
167
+ // The rows-per-page selector: a muted-filled pill trigger (radius 8), value +
168
+ // caret pushed apart, no border (iOS controls favor fills over outlines).
169
+ selectorBox(t) {
170
+ return {
171
+ flexDirection: "row",
172
+ alignItems: "center",
173
+ justifyContent: "space-between",
174
+ gap: 4,
175
+ borderRadius: 8,
176
+ backgroundColor: t.muted,
177
+ };
178
+ },
179
+ controlLabel(t) {
180
+ // SF-scale control glyph reads slightly heavier on iOS.
181
+ return { fontWeight: "600", color: t.foreground };
182
+ },
183
+ pageLabel(t, selected) {
184
+ return { fontWeight: "600", color: selected ? t["primary-foreground"] : t.foreground };
185
+ },
186
+ mutedLabel(t) {
187
+ return { color: t["muted-foreground"] };
188
+ },
189
+ gapLabel(t) {
190
+ return { paddingHorizontal: 4, color: t["muted-foreground"] };
191
+ },
192
+ pressedOpacity: 0.8,
193
+ };
194
+
195
+ // =============================================================================
196
+ // Android (Material 3): flat cells, the active page a tonal primary fill.
197
+ // =============================================================================
198
+
199
+ export const androidSkin: PaginationSkin = {
200
+ // Flat cell (radius 8), no border, transparent over the surface; the ripple is
201
+ // the press feedback.
202
+ controlBox() {
203
+ return {
204
+ ...CELL_ROW,
205
+ borderRadius: 8,
206
+ backgroundColor: "transparent",
207
+ };
208
+ },
209
+ // The active page is a tonal fill (secondaryContainer ≈ alpha(primary, .12)),
210
+ // M3's selected-cell treatment; inactive pages are flat/transparent.
211
+ pageBox(t, selected) {
212
+ return {
213
+ ...CELL_ROW,
214
+ borderRadius: 8,
215
+ backgroundColor: selected ? alpha(t.primary, 0.12) : "transparent",
216
+ };
217
+ },
218
+ // The selector trigger: a flat tonal pill (alpha(primary, .08)) with the value
219
+ // and caret pushed apart; the ripple supplies the press feedback.
220
+ selectorBox(t) {
221
+ return {
222
+ flexDirection: "row",
223
+ alignItems: "center",
224
+ justifyContent: "space-between",
225
+ gap: 4,
226
+ borderRadius: 8,
227
+ backgroundColor: alpha(t.primary, 0.08),
228
+ };
229
+ },
230
+ controlLabel(t) {
231
+ return { fontWeight: "500", color: t.foreground };
232
+ },
233
+ pageLabel(t, selected) {
234
+ // labelMedium; the active page reads in brand indigo (onSecondaryContainer ≈ primary).
235
+ return { fontWeight: "500", color: selected ? t.primary : t.foreground };
236
+ },
237
+ mutedLabel(t) {
238
+ return { color: t["muted-foreground"] };
239
+ },
240
+ gapLabel(t) {
241
+ return { paddingHorizontal: 4, color: t["muted-foreground"] };
242
+ },
243
+ pressedOpacity: null, // Android uses a ripple instead
244
+ ripple: (t) => ({ color: alpha(t.primary, 0.12), borderless: false }),
245
+ };
@@ -0,0 +1,6 @@
1
+ import { createPagination } from "./pagination.shared.js";
2
+ import { webSkin } from "./pagination.styles.js";
3
+
4
+ // Web Pagination (the base; Metro falls back to it on native, web bundlers resolve it).
5
+ export const Pagination = createPagination(webSkin);
6
+ export type { PaginationProps } from "./pagination.shared.js";
@@ -0,0 +1,8 @@
1
+ import { createPopover } from "./popover.shared.js";
2
+ import { androidSkin } from "./popover.styles.js";
3
+
4
+ // Android (elevated surface) Popover. Material 3 has no native popover, so this
5
+ // uses an elevated menu/dialog-style surface. Metro resolves this file on Android;
6
+ // the docs import it for preview.
7
+ export const Popover = createPopover(androidSkin);
8
+ export type { PopoverProps } from "./popover.shared.js";
@@ -0,0 +1,6 @@
1
+ import { createPopover } from "./popover.shared.js";
2
+ import { iosSkin } from "./popover.styles.js";
3
+
4
+ // iOS (HIG popover) Popover. Metro resolves this file on iOS; the docs import it for preview.
5
+ export const Popover = createPopover(iosSkin);
6
+ export type { PopoverProps } from "./popover.shared.js";
@@ -0,0 +1,87 @@
1
+ # Popover
2
+
3
+ Floating panel for rich content triggered by a click.
4
+
5
+ ## Usage
6
+
7
+ ```tsx
8
+ <Popover
9
+ trigger="Open popover"
10
+ title="Popover"
11
+ description="Place your rich content, form fields, or secondary actions here."
12
+ actionLabel="Close"
13
+ />
14
+ ```
15
+
16
+ ## Do & Don't
17
+
18
+ **Do** — Keep popovers compact: a focused prompt with one input and a clear action.
19
+
20
+ ```tsx
21
+ <View style={{ borderRadius: 6, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.popover, padding: 16, ...shadow("md"), alignSelf: "flex-start", minWidth: 240 }}>
22
+ <Text style={{ marginBottom: 8, fontSize: 14, lineHeight: 20, color: tokens["popover-foreground"] }}>Rename this project?</Text>
23
+ <Input value="Identity Platform" style={{ marginBottom: 8 }} />
24
+ <View style={{ flexDirection: "row", justifyContent: "flex-end", gap: 8 }}>
25
+ <Button outline small>Cancel</Button>
26
+ <Button primary small>Rename</Button>
27
+ </View>
28
+ </View>
29
+ ```
30
+
31
+ **Don't** — A full form belongs in a dialog; in a floating popover it is cramped and easy to dismiss by accident.
32
+
33
+ ```tsx
34
+ <View style={{ borderRadius: 6, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.popover, padding: 16, ...shadow("md"), alignSelf: "flex-start", minWidth: 260 }}>
35
+ <Field label="Name" placeholder="Ada Lovelace" style={{ marginBottom: 8 }} />
36
+ <Field label="Email" placeholder="ada@canvas.dev" style={{ marginBottom: 8 }} />
37
+ <View style={{ marginBottom: 8 }}>
38
+ <Text style={{ marginBottom: 6, fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>Role</Text>
39
+ <Select value="Engineer" options={["Engineer", "Designer", "Manager"]} />
40
+ </View>
41
+ <Field label="Team" placeholder="Identity Platform" style={{ marginBottom: 8 }} />
42
+ <View style={{ flexDirection: "row", justifyContent: "flex-end", gap: 8 }}>
43
+ <Button outline small>Cancel</Button>
44
+ <Button primary small>Save</Button>
45
+ </View>
46
+ </View>
47
+ ```
48
+
49
+ ### Triggered
50
+
51
+ **Do** — Wrap the trigger in a relative anchor and dismiss on outside click so the panel positions and closes predictably.
52
+
53
+ ```tsx
54
+ <Popover trigger="Open popover" open description="Anchored to the trigger, closes on outside click." actionLabel="Close" />
55
+ ```
56
+
57
+ **Don't** — A trigger with no relative anchor and no way to dismiss leaves the panel floating loose and stuck open.
58
+
59
+ ```tsx
60
+ <View style={{ alignSelf: "flex-start" }}>
61
+ <Button outline small>Open popover</Button>
62
+ <View style={{ borderRadius: 6, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.popover, padding: 16, ...shadow("md"), marginTop: 8, minWidth: 240 }}>
63
+ <Text style={{ fontSize: 14, lineHeight: 20, color: tokens["popover-foreground"] }}>No anchor, no dismiss, no Close.</Text>
64
+ </View>
65
+ </View>
66
+ ```
67
+
68
+ ### Inline
69
+
70
+ **Do** — Reserve the static panel for a brief always-on message with a single follow-up action.
71
+
72
+ ```tsx
73
+ <Popover inline description="Saved to drafts. Publish when ready." actionLabel="Publish" />
74
+ ```
75
+
76
+ **Don't** — An always-visible panel that scrolls internally is doing a card's or section's job; use the panel chrome only for short content.
77
+
78
+ ```tsx
79
+ <View style={{ borderRadius: 6, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.popover, padding: 16, ...shadow("md"), maxHeight: 120, minWidth: 260, overflow: "hidden" }}>
80
+ <ScrollView style={{ maxHeight: 88 }}>
81
+ <Field label="Street" placeholder="100 Market St" style={{ marginBottom: 8 }} />
82
+ <Field label="City" placeholder="San Francisco" style={{ marginBottom: 8 }} />
83
+ <Field label="Region" placeholder="California" style={{ marginBottom: 8 }} />
84
+ <Field label="Postal code" placeholder="94105" style={{ marginBottom: 8 }} />
85
+ </ScrollView>
86
+ </View>
87
+ ```