@btndprojects/loam-ui-native 0.0.0-stage → 0.1.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 (269) hide show
  1. package/LICENSE +21 -0
  2. package/dist/accordion/index.js +3 -0
  3. package/dist/adaptive/index.js +2 -0
  4. package/dist/alert/index.js +3 -0
  5. package/dist/analyzer/index.js +6 -0
  6. package/dist/announcement/index.js +6 -0
  7. package/dist/audio-player/index.js +3 -0
  8. package/dist/avatar/index.js +3 -0
  9. package/dist/avatar-group/index.js +3 -0
  10. package/dist/badge/index.js +5 -0
  11. package/dist/banner/index.js +2 -0
  12. package/dist/board/index.js +7 -0
  13. package/dist/button/index.js +5 -0
  14. package/dist/camera-permission/index.js +13 -0
  15. package/dist/capture-result/index.js +18 -0
  16. package/dist/card/index.js +4 -0
  17. package/dist/card-form/index.js +10 -0
  18. package/dist/carousel/index.js +2 -0
  19. package/dist/chart/index.js +6 -0
  20. package/dist/charts/index.js +7 -0
  21. package/dist/checkbox/index.js +7 -0
  22. package/dist/checklist/index.js +10 -0
  23. package/dist/checkout/index.js +13 -0
  24. package/dist/chip/index.js +7 -0
  25. package/dist/chunk-223Q2T2Q.js +229 -0
  26. package/dist/chunk-2A5NIQAK.js +254 -0
  27. package/dist/chunk-2GK6PHGE.js +363 -0
  28. package/dist/chunk-2WUTXHMU.js +1 -0
  29. package/dist/chunk-2ZZ7TDJ4.js +324 -0
  30. package/dist/chunk-35OFI3JG.js +499 -0
  31. package/dist/chunk-3ARE4TES.js +195 -0
  32. package/dist/chunk-3ECPOSKO.js +1 -0
  33. package/dist/chunk-3ENTMPQY.js +229 -0
  34. package/dist/chunk-3HJH6FF4.js +98 -0
  35. package/dist/chunk-3NMD74ZU.js +4 -0
  36. package/dist/chunk-3WPW4KUN.js +182 -0
  37. package/dist/chunk-47FWHGUN.js +394 -0
  38. package/dist/chunk-4BXMXBJO.js +141 -0
  39. package/dist/chunk-4DS5ZWVC.js +83 -0
  40. package/dist/chunk-4GBOGOTV.js +295 -0
  41. package/dist/chunk-4SZYY6AN.js +99 -0
  42. package/dist/chunk-4YIP3CWS.js +237 -0
  43. package/dist/chunk-4YQOR7JM.js +204 -0
  44. package/dist/chunk-4ZAHCY44.js +177 -0
  45. package/dist/chunk-52X2VYTD.js +228 -0
  46. package/dist/chunk-5P26LBDL.js +1 -0
  47. package/dist/chunk-5XLRYDAX.js +1 -0
  48. package/dist/chunk-5YNTNN5A.js +177 -0
  49. package/dist/chunk-66SG2H23.js +129 -0
  50. package/dist/chunk-6HT2LP62.js +504 -0
  51. package/dist/chunk-6N33LFQA.js +35 -0
  52. package/dist/chunk-6X6PQ443.js +432 -0
  53. package/dist/chunk-7CFF4HXS.js +126 -0
  54. package/dist/chunk-7O2FMUPT.js +755 -0
  55. package/dist/chunk-AI4DWZCD.js +162 -0
  56. package/dist/chunk-AWJOCGFZ.js +213 -0
  57. package/dist/chunk-AZRX6SAC.js +285 -0
  58. package/dist/chunk-B54QE2FN.js +1 -0
  59. package/dist/chunk-BC475SJH.js +1 -0
  60. package/dist/chunk-BEDBFJND.js +202 -0
  61. package/dist/chunk-BTTFCMKG.js +166 -0
  62. package/dist/chunk-BV3B2HYZ.js +1635 -0
  63. package/dist/chunk-C2NTXVWM.js +98 -0
  64. package/dist/chunk-CDRZHT42.js +424 -0
  65. package/dist/chunk-CJ5EOYZE.js +62 -0
  66. package/dist/chunk-CUMUNPXY.js +128 -0
  67. package/dist/chunk-CUS3ZSCU.js +1 -0
  68. package/dist/chunk-CYRBHQQZ.js +411 -0
  69. package/dist/chunk-DHWADYZX.js +144 -0
  70. package/dist/chunk-DKCSPATL.js +1 -0
  71. package/dist/chunk-DM5QUKKO.js +326 -0
  72. package/dist/chunk-DSJBXLUG.js +845 -0
  73. package/dist/chunk-DYBH2I6W.js +1 -0
  74. package/dist/chunk-DZ255O2F.js +1 -0
  75. package/dist/chunk-E35BPSGW.js +1 -0
  76. package/dist/chunk-EJBDRVIH.js +166 -0
  77. package/dist/chunk-F43NP42L.js +1 -0
  78. package/dist/chunk-F5XNPWS7.js +1024 -0
  79. package/dist/chunk-FIF2M24N.js +1521 -0
  80. package/dist/chunk-FOKG57CR.js +993 -0
  81. package/dist/chunk-G3HOYSF7.js +1 -0
  82. package/dist/chunk-GDKZTW34.js +395 -0
  83. package/dist/chunk-GL7B2DOW.js +134 -0
  84. package/dist/chunk-GNNJ4GCC.js +471 -0
  85. package/dist/chunk-GNZ56EUI.js +1 -0
  86. package/dist/chunk-H6NIOQHK.js +346 -0
  87. package/dist/chunk-H6PC7RDA.js +1 -0
  88. package/dist/chunk-HPPW424V.js +334 -0
  89. package/dist/chunk-HQOQIBJI.js +1 -0
  90. package/dist/chunk-HU2EQNRZ.js +44 -0
  91. package/dist/chunk-IIQTAY3J.js +715 -0
  92. package/dist/chunk-ITTYJZWX.js +271 -0
  93. package/dist/chunk-IVNRBPGC.js +1018 -0
  94. package/dist/chunk-J4KLDGI4.js +1 -0
  95. package/dist/chunk-J7Q7VCVC.js +254 -0
  96. package/dist/chunk-JDEOITQ2.js +1 -0
  97. package/dist/chunk-JNH2TYIN.js +96 -0
  98. package/dist/chunk-JRLTGLN4.js +1 -0
  99. package/dist/chunk-JTRJJLKL.js +1 -0
  100. package/dist/chunk-JUAMLEHX.js +379 -0
  101. package/dist/chunk-JXJB2X6M.js +131 -0
  102. package/dist/chunk-KMI5ML5I.js +262 -0
  103. package/dist/chunk-KTAC4MGE.js +4 -0
  104. package/dist/chunk-LAECSR4H.js +178 -0
  105. package/dist/chunk-LK4BCSGX.js +1 -0
  106. package/dist/chunk-LOE5YNN3.js +470 -0
  107. package/dist/chunk-LRJNDEIA.js +302 -0
  108. package/dist/chunk-LSS54GLJ.js +116 -0
  109. package/dist/chunk-LZLHELAG.js +137 -0
  110. package/dist/chunk-M2LUXGKZ.js +1 -0
  111. package/dist/chunk-MOUJOP55.js +425 -0
  112. package/dist/chunk-N32RGOA5.js +159 -0
  113. package/dist/chunk-N5ZNE2YE.js +1 -0
  114. package/dist/chunk-NBNCBML6.js +1 -0
  115. package/dist/chunk-NKB7BZYK.js +167 -0
  116. package/dist/chunk-NQYBUO3K.js +1795 -0
  117. package/dist/chunk-NY2U3PBI.js +59 -0
  118. package/dist/chunk-O4AEHWII.js +464 -0
  119. package/dist/chunk-O7AE6DAF.js +146 -0
  120. package/dist/chunk-OETCUXQP.js +134 -0
  121. package/dist/chunk-OOPPQH27.js +179 -0
  122. package/dist/chunk-OPXFZ4Y5.js +204 -0
  123. package/dist/chunk-P23EQNRQ.js +338 -0
  124. package/dist/chunk-PJXUJYS6.js +248 -0
  125. package/dist/chunk-PNWUWOBF.js +1 -0
  126. package/dist/chunk-PUPM2YGF.js +376 -0
  127. package/dist/chunk-PZXZJBMP.js +620 -0
  128. package/dist/chunk-Q72LJOVE.js +169 -0
  129. package/dist/chunk-QCDXZBC5.js +247 -0
  130. package/dist/chunk-QDYDBCFP.js +197 -0
  131. package/dist/chunk-QJJ6MADV.js +274 -0
  132. package/dist/chunk-QKXCPTFS.js +349 -0
  133. package/dist/chunk-QWCMU5B2.js +860 -0
  134. package/dist/chunk-RGQWWKLA.js +634 -0
  135. package/dist/chunk-RIBDXGUG.js +108 -0
  136. package/dist/chunk-RJQOOHT2.js +214 -0
  137. package/dist/chunk-RNGLIHFA.js +191 -0
  138. package/dist/chunk-RQ3HASCC.js +6 -0
  139. package/dist/chunk-RSOJT5YW.js +124 -0
  140. package/dist/chunk-RTIKSY6A.js +1 -0
  141. package/dist/chunk-RUZHTGKB.js +237 -0
  142. package/dist/chunk-RW6ZL2FQ.js +284 -0
  143. package/dist/chunk-S7F6XLTK.js +540 -0
  144. package/dist/chunk-SBMNWWBV.js +155 -0
  145. package/dist/chunk-SDTQPRRB.js +1 -0
  146. package/dist/chunk-SYNOJOQO.js +377 -0
  147. package/dist/chunk-T753XT36.js +1 -0
  148. package/dist/chunk-TKQYWZ5V.js +196 -0
  149. package/dist/chunk-TKVFVF5D.js +95 -0
  150. package/dist/chunk-TOXAGRDQ.js +144 -0
  151. package/dist/chunk-TUTI5HOZ.js +112 -0
  152. package/dist/chunk-U7TFM2JA.js +140 -0
  153. package/dist/chunk-UC2WGUP3.js +627 -0
  154. package/dist/chunk-UWFLYMAR.js +1126 -0
  155. package/dist/chunk-UWSZB7BJ.js +1 -0
  156. package/dist/chunk-UYG5L34B.js +87 -0
  157. package/dist/chunk-VBOXAEPI.js +336 -0
  158. package/dist/chunk-VCDBXW5H.js +188 -0
  159. package/dist/chunk-VCXPHHDS.js +1 -0
  160. package/dist/chunk-VEAYTRPX.js +414 -0
  161. package/dist/chunk-VFCUIDRT.js +1 -0
  162. package/dist/chunk-VQHPQEUH.js +273 -0
  163. package/dist/chunk-VXDZFJGP.js +874 -0
  164. package/dist/chunk-WS56BL3D.js +1 -0
  165. package/dist/chunk-WSW6ZFAQ.js +98 -0
  166. package/dist/chunk-WVKKJA6U.js +870 -0
  167. package/dist/chunk-XNUVB5OL.js +375 -0
  168. package/dist/chunk-XOSCY2CW.js +242 -0
  169. package/dist/chunk-XXXEFXY5.js +99 -0
  170. package/dist/chunk-Y5CSSPV4.js +1 -0
  171. package/dist/chunk-Y6DU4J47.js +1 -0
  172. package/dist/chunk-YC6WORFL.js +535 -0
  173. package/dist/chunk-YDGVJETP.js +60 -0
  174. package/dist/chunk-YF4NSSMT.js +366 -0
  175. package/dist/chunk-YG3ZJHGU.js +223 -0
  176. package/dist/chunk-YIJ55TBC.js +1 -0
  177. package/dist/chunk-YKZ4RZZF.js +1 -0
  178. package/dist/chunk-YLLCXTMC.js +37 -0
  179. package/dist/chunk-YTA74UZR.js +1 -0
  180. package/dist/chunk-Z7H7VSFZ.js +323 -0
  181. package/dist/chunk-ZK2H4O5P.js +1 -0
  182. package/dist/chunk-ZVFE5QGU.js +524 -0
  183. package/dist/coachmark/index.js +4 -0
  184. package/dist/code-block/index.js +4 -0
  185. package/dist/color-field/index.js +7 -0
  186. package/dist/commerce/index.js +20 -0
  187. package/dist/connectivity/index.js +2 -0
  188. package/dist/context-menu/index.js +5 -0
  189. package/dist/copy-button/index.js +3 -0
  190. package/dist/data-grid/index.js +24 -0
  191. package/dist/date-range-picker/index.js +13 -0
  192. package/dist/description-list/index.js +2 -0
  193. package/dist/dialog/index.js +3 -0
  194. package/dist/empty-state/index.js +3 -0
  195. package/dist/error-summary/index.js +3 -0
  196. package/dist/fab/index.js +2 -0
  197. package/dist/file-upload/index.js +9 -0
  198. package/dist/filter-bar/index.js +8 -0
  199. package/dist/flow-run/index.js +14 -0
  200. package/dist/form/index.js +3 -0
  201. package/dist/gallery/index.js +2 -0
  202. package/dist/header/index.js +5 -0
  203. package/dist/hold-to-confirm/index.js +4 -0
  204. package/dist/index.d.ts +22883 -0
  205. package/dist/index.js +158 -0
  206. package/dist/infinite-list/index.js +2 -0
  207. package/dist/inline-edit/index.js +5 -0
  208. package/dist/input/index.js +5 -0
  209. package/dist/interactions/index.js +2 -0
  210. package/dist/invoice/index.js +10 -0
  211. package/dist/kbd/index.js +1 -0
  212. package/dist/keyboard/index.js +3 -0
  213. package/dist/list-row/index.js +7 -0
  214. package/dist/log-viewer/index.js +2 -0
  215. package/dist/map-view/index.js +3 -0
  216. package/dist/markdown/index.js +6 -0
  217. package/dist/metric/index.js +4 -0
  218. package/dist/money/index.js +3 -0
  219. package/dist/nav-drawer/index.js +2 -0
  220. package/dist/navigation/index.js +2 -0
  221. package/dist/notification-center/index.js +16 -0
  222. package/dist/number-roll/index.js +3 -0
  223. package/dist/one-handed/index.js +5 -0
  224. package/dist/os-surfaces/index.js +3 -0
  225. package/dist/otp/index.js +7 -0
  226. package/dist/page-header/index.js +1 -0
  227. package/dist/payment-method/index.js +12 -0
  228. package/dist/payment-status/index.js +16 -0
  229. package/dist/permission/index.js +2 -0
  230. package/dist/picker/index.js +7 -0
  231. package/dist/progress/index.js +5 -0
  232. package/dist/pull-to-refresh/index.js +4 -0
  233. package/dist/radio-group/index.js +6 -0
  234. package/dist/rating/index.js +2 -0
  235. package/dist/receivables/index.js +20 -0
  236. package/dist/refund/index.js +16 -0
  237. package/dist/save-status/index.js +3 -0
  238. package/dist/scroll-area/index.js +1 -0
  239. package/dist/search-bar/index.js +5 -0
  240. package/dist/segmented-control/index.js +5 -0
  241. package/dist/sheet/index.js +4 -0
  242. package/dist/skeleton/index.js +2 -0
  243. package/dist/slider/index.js +5 -0
  244. package/dist/spinner/index.js +2 -0
  245. package/dist/split-button/index.js +7 -0
  246. package/dist/spotlight/index.js +2 -0
  247. package/dist/stage-timeline/index.js +4 -0
  248. package/dist/status-dot/index.js +2 -0
  249. package/dist/stepper/index.js +6 -0
  250. package/dist/streaming-text/index.js +3 -0
  251. package/dist/survey/index.js +19 -0
  252. package/dist/switch/index.js +5 -0
  253. package/dist/system-state/index.js +4 -0
  254. package/dist/tab-bar/index.js +2 -0
  255. package/dist/tabs/index.js +2 -0
  256. package/dist/textarea/index.js +6 -0
  257. package/dist/thinking/index.js +2 -0
  258. package/dist/timeline/index.js +4 -0
  259. package/dist/toast/index.js +2 -0
  260. package/dist/toolbar/index.js +2 -0
  261. package/dist/tour/index.js +11 -0
  262. package/dist/truncation-notice/index.js +2 -0
  263. package/dist/update-notice/index.js +11 -0
  264. package/dist/video-player/index.js +2 -0
  265. package/dist/viewfinder/index.js +5 -0
  266. package/dist/widget/index.js +4 -0
  267. package/dist/wizard/index.js +6 -0
  268. package/package.json +76 -3
  269. package/README.md +0 -3
@@ -0,0 +1,1024 @@
1
+ import { Avatar } from './chunk-QDYDBCFP.js';
2
+ import { Icon } from './chunk-TKVFVF5D.js';
3
+ import * as React4 from 'react';
4
+ import { Platform, View, Text, Pressable, Animated, Easing } from 'react-native';
5
+ import { createStyleSheet, useStyles, useLoamTheme } from '@btndprojects/loam-theme-native';
6
+ import { MEDIA, OVERLAY_ALPHA } from '@btndprojects/loam-tokens';
7
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
8
+
9
+ var APP_ICON_ASSET_PX = {
10
+ store: 1024,
11
+ home: 180,
12
+ spotlight: 120,
13
+ settings: 58
14
+ };
15
+ var APP_ICON_PREVIEW_PX = {
16
+ store: 76,
17
+ home: 56,
18
+ spotlight: 40,
19
+ settings: 28
20
+ };
21
+ var IOS_ICON_CORNER_RATIO = 0.23;
22
+ var ANDROID_ICON_MASK_RATIO = 0.5;
23
+ var ICON_SAFE_MARGIN = { iOS: 0.18, android: 0.25 };
24
+ var ICON_MARK_RATIO = { size: 0.46, radius: 0.1 };
25
+ var LAUNCH_MARK = { size: 64, radius: 18 };
26
+ var DYNAMIC_ISLAND = { height: 37, minWidth: 122 };
27
+ var DYNAMIC_ISLAND_FILL = "#000000";
28
+ var LIVE_ACTIVITY_MAX_H = 160;
29
+ var LIVE_ACTIVITY_CARD = { tile: 34, tileRadius: 11, trackH: 4 };
30
+ var WIDGET_SIZE = {
31
+ small: { width: 170, height: 170 },
32
+ medium: { width: 364, height: 170 },
33
+ large: { width: 364, height: 382 }
34
+ };
35
+ var WIDGET_CORNER = { iOS: 22, android: 28 };
36
+ var WIDGET_INNER_CORNER_ANDROID = 20;
37
+ var IOS_EDIT_MENU_RADIUS = 10;
38
+ var SHEET_GRABBER_W = 36;
39
+ var OS_MATERIAL = {
40
+ /** Live-activity / prominent card fill (reference: `rgba(255,255,255,.14)`). */
41
+ fill: `rgba(255,255,255,${OVERLAY_ALPHA.a5})`,
42
+ /** Notification card fill (reference: `rgba(255,255,255,.12)`). */
43
+ fillWeak: `rgba(255,255,255,${OVERLAY_ALPHA.a4})`,
44
+ /** Progress track over material (reference: `rgba(255,255,255,.25)`). */
45
+ track: `rgba(255,255,255,${OVERLAY_ALPHA.a8})`,
46
+ /** Primary ink on material. White, because the backdrop is a wallpaper. */
47
+ ink: MEDIA.ink,
48
+ /**
49
+ * Divider between items in a system menu (reference: `rgba(128,128,128,.35)`).
50
+ *
51
+ * The one value here that is genuinely not a ramp stop, and could not be: a
52
+ * mid-grey at 35% is the only wash that stays visible over *both* light and
53
+ * dark OS chrome, which is a different requirement from every other entry —
54
+ * those all know the backdrop is dark. Neither a white nor a black stop can
55
+ * do it, so the literal stands, with this as the reason.
56
+ */
57
+ divider: "rgba(128,128,128,0.35)"
58
+ };
59
+ var OS_MATERIAL_INK_OPACITY = { secondary: 0.85, tertiary: 0.7 };
60
+ var LOCK_SCREEN_WALLPAPER = "#2b2620";
61
+ function AppIconPreview({
62
+ sizeClass = "home",
63
+ size,
64
+ shape,
65
+ showCaption = false,
66
+ accessibilityLabel
67
+ }) {
68
+ const { styles } = useStyles(sheet);
69
+ const theme = useLoamTheme();
70
+ const px = size ?? APP_ICON_PREVIEW_PX[sizeClass];
71
+ const resolved = shape ?? (Platform.OS === "android" ? "circle" : "squircle");
72
+ const radius = resolved === "circle" ? px * ANDROID_ICON_MASK_RATIO : Math.round(px * IOS_ICON_CORNER_RATIO);
73
+ const mark = Math.round(px * ICON_MARK_RATIO.size);
74
+ const caption = `${APP_ICON_ASSET_PX[sizeClass]} ${sizeClass}`;
75
+ return /* @__PURE__ */ jsxs(View, { style: styles.cell, children: [
76
+ /* @__PURE__ */ jsx(
77
+ View,
78
+ {
79
+ accessibilityRole: "image",
80
+ accessibilityLabel: accessibilityLabel ?? `App icon preview, ${APP_ICON_ASSET_PX[sizeClass]} px ${sizeClass}, ${resolved} mask`,
81
+ style: [
82
+ styles.tile,
83
+ {
84
+ width: px,
85
+ height: px,
86
+ borderRadius: radius,
87
+ // The reference paints a 160° accent gradient; RN has no gradient
88
+ // primitive without a dependency, so the icon is a flat accent.
89
+ backgroundColor: theme.color.accent
90
+ }
91
+ ],
92
+ children: /* @__PURE__ */ jsx(
93
+ View,
94
+ {
95
+ style: [
96
+ styles.mark,
97
+ {
98
+ width: mark,
99
+ height: mark,
100
+ borderRadius: Math.round(px * ICON_MARK_RATIO.radius)
101
+ }
102
+ ]
103
+ }
104
+ )
105
+ }
106
+ ),
107
+ showCaption ? /* @__PURE__ */ jsx(Text, { style: styles.caption, children: caption }) : null
108
+ ] });
109
+ }
110
+ var sheet = createStyleSheet((theme) => ({
111
+ cell: { alignItems: "center", gap: theme.space.s1 },
112
+ tile: {
113
+ alignItems: "center",
114
+ justifyContent: "center",
115
+ overflow: "hidden",
116
+ ...theme.shadow.md
117
+ },
118
+ // `accentInk` is the measured foreground for the accent fill — the mark must
119
+ // stay legible on it in both modes and under every tenant.
120
+ mark: { backgroundColor: theme.color.accentInk, opacity: theme.opacity[92] },
121
+ caption: {
122
+ fontFamily: theme.font.mono,
123
+ fontSize: theme.type.caption2.fontSize,
124
+ lineHeight: theme.type.caption2.lineHeight,
125
+ color: theme.color.text3
126
+ }
127
+ }));
128
+ function LaunchScreenPreview({
129
+ wordmark,
130
+ reviewNote,
131
+ accessibilityLabel = "Launch screen preview"
132
+ }) {
133
+ const { styles } = useStyles(sheet2);
134
+ return /* @__PURE__ */ jsxs(View, { accessibilityRole: "image", accessibilityLabel, style: styles.screen, children: [
135
+ /* @__PURE__ */ jsxs(View, { style: styles.stack, children: [
136
+ /* @__PURE__ */ jsx(View, { style: styles.mark }),
137
+ wordmark ? /* @__PURE__ */ jsx(Text, { style: styles.wordmark, children: wordmark }) : null
138
+ ] }),
139
+ reviewNote ? /* @__PURE__ */ jsx(Text, { style: styles.note, children: reviewNote }) : null
140
+ ] });
141
+ }
142
+ var sheet2 = createStyleSheet((theme) => ({
143
+ // The reference paints a flat `var(--accent)` here — the one screen in the
144
+ // system that is a single brand colour edge to edge.
145
+ screen: {
146
+ flex: 1,
147
+ backgroundColor: theme.color.accent,
148
+ alignItems: "center",
149
+ justifyContent: "center",
150
+ padding: theme.layout.screenPad
151
+ },
152
+ stack: { alignItems: "center", gap: theme.space.s4 },
153
+ // OS-mandated-ish geometry: 64px at an 18px radius, from the reference. It has
154
+ // to match the app icon's mark, not a Loam radius step.
155
+ mark: {
156
+ width: LAUNCH_MARK.size,
157
+ height: LAUNCH_MARK.size,
158
+ borderRadius: LAUNCH_MARK.radius,
159
+ backgroundColor: theme.color.accentInk,
160
+ opacity: theme.opacity[96]
161
+ },
162
+ wordmark: {
163
+ fontFamily: theme.font.display,
164
+ fontWeight: theme.font.displayWeight,
165
+ fontSize: theme.type.title3.fontSize,
166
+ lineHeight: theme.type.title3.lineHeight,
167
+ letterSpacing: theme.type.title3.letterSpacing,
168
+ color: theme.color.accentInk
169
+ },
170
+ note: {
171
+ position: "absolute",
172
+ bottom: theme.space.s6,
173
+ fontFamily: theme.font.sans,
174
+ fontSize: theme.type.caption1.fontSize,
175
+ lineHeight: theme.type.caption1.lineHeight,
176
+ // Full `accentInk` (no alpha): at 0.7 it composited to ~#ecccc4 on the accent
177
+ // fill = 3.02:1 (WCAG fail); the solid ink clears AA at 4.53:1.
178
+ color: theme.color.accentInk,
179
+ textAlign: "center"
180
+ }
181
+ }));
182
+ var NOTIFICATION_TILE = { size: 26, radius: 8 };
183
+ var GROUP_STACK_OFFSET = 6;
184
+ function announce({ title, body, time }) {
185
+ return [title, body, time].filter(Boolean).join(", ");
186
+ }
187
+ function NotificationBanner(props) {
188
+ const { styles } = useStyles(sheet3);
189
+ const theme = useLoamTheme();
190
+ const { appName, icon, title, body, time, actionLabel, onAction, onPress, groupCount, style } = props;
191
+ const android = Platform.OS === "android";
192
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
193
+ /* @__PURE__ */ jsx(View, { style: styles.tile, children: /* @__PURE__ */ jsx(Icon, { name: icon ?? "bell", size: theme.icon.sm, color: theme.color.accentInk }) }),
194
+ /* @__PURE__ */ jsxs(View, { style: styles.main, children: [
195
+ android && appName ? /* @__PURE__ */ jsxs(Text, { style: styles.appName, children: [
196
+ appName,
197
+ time ? ` \xB7 ${time}` : ""
198
+ ] }) : null,
199
+ /* @__PURE__ */ jsxs(View, { style: styles.titleRow, children: [
200
+ /* @__PURE__ */ jsx(Text, { style: styles.title, numberOfLines: 1, children: title }),
201
+ !android && time ? /* @__PURE__ */ jsx(Text, { style: styles.time, children: time }) : null
202
+ ] }),
203
+ /* @__PURE__ */ jsx(Text, { style: styles.body, numberOfLines: 2, children: body }),
204
+ groupCount && groupCount > 0 ? /* @__PURE__ */ jsx(Text, { style: styles.group, children: `+${groupCount} more` }) : null
205
+ ] })
206
+ ] });
207
+ return /* @__PURE__ */ jsxs(View, { style: styles.stackRoot, children: [
208
+ /* @__PURE__ */ jsxs(View, { style: [styles.banner, style], children: [
209
+ onPress ? /* @__PURE__ */ jsx(
210
+ Pressable,
211
+ {
212
+ accessibilityRole: "button",
213
+ accessibilityLabel: announce(props),
214
+ onPress,
215
+ style: ({ pressed }) => [styles.bannerBody, pressed ? styles.pressed : null],
216
+ children: content
217
+ }
218
+ ) : /* @__PURE__ */ jsx(View, { accessibilityRole: "image", accessibilityLabel: announce(props), style: styles.bannerBody, children: content }),
219
+ actionLabel ? /* @__PURE__ */ jsx(
220
+ Pressable,
221
+ {
222
+ accessibilityRole: "button",
223
+ accessibilityLabel: actionLabel,
224
+ onPress: onAction,
225
+ style: ({ pressed }) => [styles.action, pressed ? styles.pressed : null],
226
+ children: /* @__PURE__ */ jsx(Text, { style: styles.actionLabel, children: actionLabel })
227
+ }
228
+ ) : null
229
+ ] }),
230
+ groupCount && groupCount > 0 ? /* @__PURE__ */ jsx(View, { "aria-hidden": true, style: styles.stackPeek }) : null
231
+ ] });
232
+ }
233
+ function LockScreenNotification(props) {
234
+ const { styles } = useStyles(sheet3);
235
+ const { title, body, time, actionLabel, onAction, onPress, groupCount, style } = props;
236
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
237
+ /* @__PURE__ */ jsx(View, { style: styles.materialTile }),
238
+ /* @__PURE__ */ jsxs(View, { style: styles.main, children: [
239
+ /* @__PURE__ */ jsxs(View, { style: styles.titleRow, children: [
240
+ /* @__PURE__ */ jsx(Text, { style: styles.materialTitle, numberOfLines: 1, children: title }),
241
+ time ? /* @__PURE__ */ jsx(Text, { style: styles.materialTime, children: time }) : null
242
+ ] }),
243
+ /* @__PURE__ */ jsx(Text, { style: styles.materialBody, numberOfLines: 2, children: body }),
244
+ groupCount && groupCount > 0 ? /* @__PURE__ */ jsx(Text, { style: styles.materialTime, children: `+${groupCount} more` }) : null
245
+ ] })
246
+ ] });
247
+ return /* @__PURE__ */ jsxs(View, { style: styles.stackRoot, children: [
248
+ /* @__PURE__ */ jsxs(View, { style: [styles.materialCard, style], children: [
249
+ onPress ? /* @__PURE__ */ jsx(
250
+ Pressable,
251
+ {
252
+ accessibilityRole: "button",
253
+ accessibilityLabel: announce(props),
254
+ onPress,
255
+ style: ({ pressed }) => [styles.materialBodyRow, pressed ? styles.pressed : null],
256
+ children: content
257
+ }
258
+ ) : /* @__PURE__ */ jsx(View, { accessibilityRole: "image", accessibilityLabel: announce(props), style: styles.materialBodyRow, children: content }),
259
+ actionLabel ? /* @__PURE__ */ jsx(
260
+ Pressable,
261
+ {
262
+ accessibilityRole: "button",
263
+ accessibilityLabel: actionLabel,
264
+ onPress: onAction,
265
+ style: ({ pressed }) => [styles.materialAction, pressed ? styles.pressed : null],
266
+ children: /* @__PURE__ */ jsx(Text, { style: styles.materialActionLabel, children: actionLabel })
267
+ }
268
+ ) : null
269
+ ] }),
270
+ groupCount && groupCount > 0 ? /* @__PURE__ */ jsx(View, { "aria-hidden": true, style: [styles.stackPeek, styles.stackPeekMaterial] }) : null
271
+ ] });
272
+ }
273
+ function LockScreenPreview({
274
+ time,
275
+ date,
276
+ activity,
277
+ notifications = [],
278
+ onNotificationPress,
279
+ wallpaperColor = LOCK_SCREEN_WALLPAPER
280
+ }) {
281
+ const { styles } = useStyles(sheet3);
282
+ return /* @__PURE__ */ jsxs(
283
+ View,
284
+ {
285
+ role: "group",
286
+ accessibilityLabel: "Lock screen preview",
287
+ style: [styles.lockScreen, { backgroundColor: wallpaperColor }],
288
+ children: [
289
+ /* @__PURE__ */ jsxs(View, { style: styles.clock, children: [
290
+ /* @__PURE__ */ jsx(Text, { style: styles.clockDate, children: date }),
291
+ /* @__PURE__ */ jsx(Text, { style: styles.clockTime, children: time })
292
+ ] }),
293
+ activity,
294
+ notifications.map((n) => /* @__PURE__ */ jsx(
295
+ LockScreenNotification,
296
+ {
297
+ title: n.title,
298
+ body: n.body,
299
+ time: n.time,
300
+ appName: n.appName,
301
+ icon: n.icon,
302
+ onPress: onNotificationPress ? () => onNotificationPress(n.id) : void 0
303
+ },
304
+ n.id
305
+ ))
306
+ ]
307
+ }
308
+ );
309
+ }
310
+ var sheet3 = createStyleSheet((theme) => ({
311
+ stackRoot: { alignSelf: "stretch" },
312
+ // Sits directly under the card, inset on both sides and rounded only at the
313
+ // bottom, so it reads as a second card behind the front one without ever
314
+ // intersecting it (no translucency doubling) or leaving the entry's own box.
315
+ stackPeek: {
316
+ height: theme.space[GROUP_STACK_OFFSET],
317
+ marginHorizontal: theme.space.s2,
318
+ borderBottomStartRadius: theme.radius.lg,
319
+ borderBottomEndRadius: theme.radius.lg,
320
+ backgroundColor: theme.color.surface2
321
+ },
322
+ // Dimmed, because two layers of the same material would read as one tall card
323
+ // rather than as a stack. `surface2` behind the opaque banner needs no help.
324
+ stackPeekMaterial: {
325
+ backgroundColor: OS_MATERIAL.fillWeak,
326
+ opacity: OS_MATERIAL_INK_OPACITY.tertiary
327
+ },
328
+ // ---- banner (over the app's own window: Loam tokens) ----
329
+ banner: {
330
+ flexDirection: "row",
331
+ alignItems: "center",
332
+ gap: theme.space.s3,
333
+ padding: theme.space.s3,
334
+ borderRadius: theme.radius.lg,
335
+ backgroundColor: theme.color.surface,
336
+ borderWidth: theme.borderWidth.base,
337
+ borderColor: theme.color.border,
338
+ ...theme.shadow.lg
339
+ },
340
+ bannerBody: {
341
+ flex: 1,
342
+ flexDirection: "row",
343
+ alignItems: "center",
344
+ gap: theme.space.s3,
345
+ minHeight: theme.tapMin
346
+ },
347
+ pressed: { opacity: theme.state.pressedOpacity },
348
+ tile: {
349
+ width: NOTIFICATION_TILE.size,
350
+ height: NOTIFICATION_TILE.size,
351
+ borderRadius: NOTIFICATION_TILE.radius,
352
+ flexShrink: 0,
353
+ alignItems: "center",
354
+ justifyContent: "center",
355
+ backgroundColor: theme.color.accent
356
+ },
357
+ main: { flex: 1, minWidth: 0, gap: theme.space.s1 },
358
+ titleRow: { flexDirection: "row", alignItems: "center", gap: theme.space.s2 },
359
+ appName: {
360
+ fontFamily: theme.font.sans,
361
+ fontSize: theme.type.caption2.fontSize,
362
+ lineHeight: theme.type.caption2.lineHeight,
363
+ color: theme.color.text3
364
+ },
365
+ title: {
366
+ flex: 1,
367
+ fontFamily: theme.font.sans,
368
+ fontWeight: theme.fontWeight.bold,
369
+ fontSize: theme.type.footnote.fontSize,
370
+ lineHeight: theme.type.footnote.lineHeight,
371
+ color: theme.color.text
372
+ },
373
+ time: {
374
+ fontFamily: theme.font.sans,
375
+ fontSize: theme.type.caption2.fontSize,
376
+ lineHeight: theme.type.caption2.lineHeight,
377
+ color: theme.color.text3
378
+ },
379
+ body: {
380
+ fontFamily: theme.font.sans,
381
+ fontSize: theme.type.footnote.fontSize,
382
+ lineHeight: theme.type.footnote.lineHeight,
383
+ color: theme.color.text2
384
+ },
385
+ group: {
386
+ fontFamily: theme.font.sans,
387
+ fontSize: theme.type.caption2.fontSize,
388
+ lineHeight: theme.type.caption2.lineHeight,
389
+ color: theme.color.text3
390
+ },
391
+ action: {
392
+ minHeight: theme.tapMin,
393
+ justifyContent: "center",
394
+ paddingHorizontal: theme.space.s3,
395
+ flexShrink: 0
396
+ },
397
+ actionLabel: {
398
+ fontFamily: theme.font.sans,
399
+ fontWeight: theme.fontWeight.bold,
400
+ fontSize: theme.type.footnote.fontSize,
401
+ color: theme.color.accentStrong
402
+ },
403
+ // ---- lock screen / notification centre (OS material over a wallpaper) ----
404
+ lockScreen: {
405
+ flex: 1,
406
+ paddingHorizontal: theme.space.s3,
407
+ paddingTop: theme.space.s6,
408
+ gap: theme.space.s3
409
+ },
410
+ clock: { alignItems: "center", gap: theme.space.s1 },
411
+ clockDate: {
412
+ fontFamily: theme.font.sans,
413
+ fontSize: theme.type.caption1.fontSize,
414
+ lineHeight: theme.type.caption1.lineHeight,
415
+ color: OS_MATERIAL.ink,
416
+ opacity: OS_MATERIAL_INK_OPACITY.secondary
417
+ },
418
+ // The lock-screen clock is OS chrome with no counterpart in the semantic ramp,
419
+ // so it takes the size-only `hero` step (52) the reference specifies.
420
+ clockTime: {
421
+ fontFamily: theme.font.display,
422
+ fontWeight: theme.font.displayWeight,
423
+ fontSize: theme.fontSize[56],
424
+ lineHeight: theme.fontSize[56] * theme.lineHeight.tight,
425
+ color: OS_MATERIAL.ink
426
+ },
427
+ materialCard: {
428
+ flexDirection: "row",
429
+ alignItems: "center",
430
+ gap: theme.space.s3,
431
+ padding: theme.space.s3,
432
+ borderRadius: theme.radius.lg,
433
+ // Translucent scrim, NOT a blur: RN has no `backdrop-filter`. See OS_MATERIAL.
434
+ backgroundColor: OS_MATERIAL.fillWeak
435
+ },
436
+ materialBodyRow: {
437
+ flex: 1,
438
+ flexDirection: "row",
439
+ alignItems: "center",
440
+ gap: theme.space.s3,
441
+ minHeight: theme.tapMin
442
+ },
443
+ materialTile: {
444
+ width: NOTIFICATION_TILE.size,
445
+ height: NOTIFICATION_TILE.size,
446
+ borderRadius: NOTIFICATION_TILE.radius,
447
+ flexShrink: 0,
448
+ backgroundColor: theme.color.accent
449
+ },
450
+ materialTitle: {
451
+ flex: 1,
452
+ fontFamily: theme.font.sans,
453
+ fontWeight: theme.fontWeight.bold,
454
+ fontSize: theme.type.footnote.fontSize,
455
+ lineHeight: theme.type.footnote.lineHeight,
456
+ color: OS_MATERIAL.ink
457
+ },
458
+ materialTime: {
459
+ fontFamily: theme.font.sans,
460
+ fontSize: theme.type.caption2.fontSize,
461
+ lineHeight: theme.type.caption2.lineHeight,
462
+ color: OS_MATERIAL.ink,
463
+ opacity: OS_MATERIAL_INK_OPACITY.tertiary
464
+ },
465
+ materialBody: {
466
+ fontFamily: theme.font.sans,
467
+ fontSize: theme.type.footnote.fontSize,
468
+ lineHeight: theme.type.footnote.lineHeight,
469
+ color: OS_MATERIAL.ink,
470
+ opacity: OS_MATERIAL_INK_OPACITY.secondary
471
+ },
472
+ materialAction: {
473
+ minHeight: theme.tapMin,
474
+ justifyContent: "center",
475
+ paddingHorizontal: theme.space.s3,
476
+ flexShrink: 0
477
+ },
478
+ materialActionLabel: {
479
+ fontFamily: theme.font.sans,
480
+ fontWeight: theme.fontWeight.bold,
481
+ fontSize: theme.type.footnote.fontSize,
482
+ color: OS_MATERIAL.ink
483
+ }
484
+ }));
485
+ function clamp01(n) {
486
+ return Math.max(0, Math.min(1, n));
487
+ }
488
+ function LiveActivityPreview({
489
+ title,
490
+ progress,
491
+ status,
492
+ detail,
493
+ icon = "upload",
494
+ presentation = "lockScreen",
495
+ cancelLabel = "Cancel",
496
+ onCancel,
497
+ accessibilityLabel
498
+ }) {
499
+ const { styles } = useStyles(sheet4);
500
+ const theme = useLoamTheme();
501
+ const announced = accessibilityLabel ?? [title, status, detail].filter(Boolean).join(", ");
502
+ const fraction = progress === void 0 ? void 0 : clamp01(progress);
503
+ const percent = fraction === void 0 ? void 0 : Math.round(fraction * 100);
504
+ const grow = React4.useRef(new Animated.Value(fraction ?? 0)).current;
505
+ React4.useEffect(() => {
506
+ if (fraction === void 0) return;
507
+ Animated.timing(grow, {
508
+ toValue: fraction,
509
+ duration: theme.motion.expand.duration,
510
+ easing: Easing.bezier(...theme.motion.expand.easing),
511
+ useNativeDriver: false
512
+ }).start();
513
+ }, [fraction, grow, theme.motion.expand.duration, theme.motion.expand.easing]);
514
+ const width = grow.interpolate({
515
+ inputRange: [0, 1],
516
+ outputRange: ["0%", "100%"]
517
+ });
518
+ if (presentation === "island") {
519
+ const android = Platform.OS === "android";
520
+ return /* @__PURE__ */ jsxs(
521
+ View,
522
+ {
523
+ accessibilityRole: "progressbar",
524
+ accessibilityLabel: announced,
525
+ accessibilityValue: percent === void 0 ? void 0 : { now: percent, min: 0, max: 100 },
526
+ "aria-valuenow": percent,
527
+ "aria-valuemin": percent === void 0 ? void 0 : 0,
528
+ "aria-valuemax": percent === void 0 ? void 0 : 100,
529
+ style: [styles.island, android ? styles.islandAndroid : null],
530
+ children: [
531
+ /* @__PURE__ */ jsx(
532
+ Icon,
533
+ {
534
+ name: icon,
535
+ size: theme.icon.sm,
536
+ color: android ? theme.color.accentInk : OS_MATERIAL.ink
537
+ }
538
+ ),
539
+ /* @__PURE__ */ jsx(
540
+ Text,
541
+ {
542
+ style: [styles.islandLabel, android ? styles.islandLabelAndroid : null],
543
+ numberOfLines: 1,
544
+ children: title
545
+ }
546
+ ),
547
+ status ? /* @__PURE__ */ jsx(Text, { style: [styles.islandStatus, android ? styles.islandLabelAndroid : null], children: status }) : null
548
+ ]
549
+ }
550
+ );
551
+ }
552
+ return (
553
+ // A `group`, not a `progressbar`: the card also holds the interactive cancel
554
+ // control, and `progressbar` (like `img`) makes its children presentational —
555
+ // nesting a focusable button inside it is a nested-interactive violation. The
556
+ // progressbar role lives on the track below, which owns no interactive content.
557
+ /* @__PURE__ */ jsxs(View, { role: "group", style: styles.card, children: [
558
+ /* @__PURE__ */ jsx(View, { style: styles.cardTile, children: /* @__PURE__ */ jsx(Icon, { name: icon, size: theme.icon.sm, color: theme.color.accentInk }) }),
559
+ /* @__PURE__ */ jsxs(View, { style: styles.cardMain, children: [
560
+ /* @__PURE__ */ jsxs(View, { style: styles.cardTitleRow, children: [
561
+ /* @__PURE__ */ jsx(Text, { style: styles.cardTitle, numberOfLines: 1, children: title }),
562
+ status ? /* @__PURE__ */ jsx(Text, { style: styles.cardStatus, numberOfLines: 1, children: status }) : null
563
+ ] }),
564
+ /* @__PURE__ */ jsx(
565
+ View,
566
+ {
567
+ accessibilityRole: "progressbar",
568
+ accessibilityLabel: announced,
569
+ accessibilityValue: percent === void 0 ? void 0 : { now: percent, min: 0, max: 100 },
570
+ "aria-valuenow": percent,
571
+ "aria-valuemin": percent === void 0 ? void 0 : 0,
572
+ "aria-valuemax": percent === void 0 ? void 0 : 100,
573
+ style: styles.track,
574
+ children: /* @__PURE__ */ jsx(
575
+ Animated.View,
576
+ {
577
+ style: [styles.fill, fraction === void 0 ? styles.fillIndeterminate : { width }]
578
+ }
579
+ )
580
+ }
581
+ ),
582
+ detail ? /* @__PURE__ */ jsx(Text, { style: styles.cardDetail, numberOfLines: 1, children: detail }) : null
583
+ ] }),
584
+ onCancel ? /* @__PURE__ */ jsx(
585
+ Pressable,
586
+ {
587
+ accessibilityRole: "button",
588
+ accessibilityLabel: cancelLabel,
589
+ onPress: onCancel,
590
+ style: ({ pressed }) => [styles.cancel, pressed ? styles.pressed : null],
591
+ children: /* @__PURE__ */ jsx(Icon, { name: "x", size: theme.icon.sm, color: OS_MATERIAL.ink })
592
+ }
593
+ ) : null
594
+ ] })
595
+ );
596
+ }
597
+ var sheet4 = createStyleSheet((theme) => ({
598
+ // ---- compact: the Dynamic Island pill ----
599
+ island: {
600
+ flexDirection: "row",
601
+ alignItems: "center",
602
+ alignSelf: "flex-start",
603
+ gap: theme.space.s2,
604
+ minWidth: DYNAMIC_ISLAND.minWidth,
605
+ height: DYNAMIC_ISLAND.height,
606
+ // Fully rounded: the housing is a capsule, so the radius is half its height,
607
+ // not a Loam radius step.
608
+ borderRadius: DYNAMIC_ISLAND.height / 2,
609
+ paddingHorizontal: theme.space.s3,
610
+ backgroundColor: DYNAMIC_ISLAND_FILL
611
+ },
612
+ islandAndroid: { backgroundColor: theme.color.accent },
613
+ islandLabel: {
614
+ flexShrink: 1,
615
+ fontFamily: theme.font.sans,
616
+ fontWeight: theme.fontWeight.bold,
617
+ fontSize: theme.type.caption1.fontSize,
618
+ lineHeight: theme.type.caption1.lineHeight,
619
+ color: OS_MATERIAL.ink
620
+ },
621
+ islandLabelAndroid: { color: theme.color.accentInk },
622
+ islandStatus: {
623
+ marginStart: "auto",
624
+ fontFamily: theme.font.mono,
625
+ fontSize: theme.type.caption2.fontSize,
626
+ lineHeight: theme.type.caption2.lineHeight,
627
+ color: OS_MATERIAL.ink,
628
+ opacity: OS_MATERIAL_INK_OPACITY.secondary
629
+ },
630
+ // ---- expanded: the lock-screen card, on system material ----
631
+ card: {
632
+ flexDirection: "row",
633
+ alignItems: "center",
634
+ gap: theme.space.s3,
635
+ padding: theme.space.s3,
636
+ // The OS clips an expanded Live Activity at 160pt.
637
+ maxHeight: LIVE_ACTIVITY_MAX_H,
638
+ borderRadius: theme.radius.lg,
639
+ // Translucent scrim, NOT a blur — RN has no `backdrop-filter`. See OS_MATERIAL.
640
+ backgroundColor: OS_MATERIAL.fill
641
+ },
642
+ cardTile: {
643
+ width: LIVE_ACTIVITY_CARD.tile,
644
+ height: LIVE_ACTIVITY_CARD.tile,
645
+ borderRadius: LIVE_ACTIVITY_CARD.tileRadius,
646
+ flexShrink: 0,
647
+ alignItems: "center",
648
+ justifyContent: "center",
649
+ backgroundColor: theme.color.accent
650
+ },
651
+ cardMain: { flex: 1, minWidth: 0, gap: theme.space.s1 },
652
+ cardTitleRow: { flexDirection: "row", alignItems: "baseline", gap: theme.space.s2 },
653
+ cardTitle: {
654
+ flex: 1,
655
+ fontFamily: theme.font.sans,
656
+ fontWeight: theme.fontWeight.bold,
657
+ fontSize: theme.type.footnote.fontSize,
658
+ lineHeight: theme.type.footnote.lineHeight,
659
+ color: OS_MATERIAL.ink
660
+ },
661
+ cardDetail: {
662
+ fontFamily: theme.font.sans,
663
+ fontSize: theme.type.caption2.fontSize,
664
+ lineHeight: theme.type.caption2.lineHeight,
665
+ color: OS_MATERIAL.ink,
666
+ opacity: OS_MATERIAL_INK_OPACITY.secondary
667
+ },
668
+ track: {
669
+ height: LIVE_ACTIVITY_CARD.trackH,
670
+ borderRadius: LIVE_ACTIVITY_CARD.trackH / 2,
671
+ overflow: "hidden",
672
+ backgroundColor: OS_MATERIAL.track
673
+ },
674
+ fill: { height: "100%", backgroundColor: OS_MATERIAL.ink },
675
+ fillIndeterminate: { width: "100%", opacity: OS_MATERIAL_INK_OPACITY.tertiary },
676
+ cardStatus: {
677
+ flexShrink: 0,
678
+ fontFamily: theme.font.mono,
679
+ fontSize: theme.type.caption2.fontSize,
680
+ lineHeight: theme.type.caption2.lineHeight,
681
+ color: OS_MATERIAL.ink,
682
+ opacity: OS_MATERIAL_INK_OPACITY.secondary
683
+ },
684
+ cancel: {
685
+ width: theme.icon["2xl"],
686
+ height: theme.icon["2xl"],
687
+ flexShrink: 0,
688
+ alignItems: "center",
689
+ justifyContent: "center"
690
+ },
691
+ pressed: { opacity: theme.state.pressedOpacity }
692
+ }));
693
+ var STAT_BUDGET = { small: 1, medium: 2, large: 4 };
694
+ var TITLE_LINES = { small: 2, medium: 2, large: 3 };
695
+ function WidgetPreview({
696
+ sizeClass = "small",
697
+ appName,
698
+ icon = "grid",
699
+ title,
700
+ subtitle,
701
+ stats = [],
702
+ caption,
703
+ onPress,
704
+ accessibilityLabel
705
+ }) {
706
+ const { styles } = useStyles(sheet5);
707
+ const theme = useLoamTheme();
708
+ const size = WIDGET_SIZE[sizeClass];
709
+ const android = Platform.OS === "android";
710
+ const radius = android ? WIDGET_CORNER.android : WIDGET_CORNER.iOS;
711
+ const innerRadius = android ? WIDGET_INNER_CORNER_ANDROID : theme.radius.md;
712
+ const shown = stats.slice(0, STAT_BUDGET[sizeClass]);
713
+ const announced = accessibilityLabel ?? [appName, title, subtitle].filter(Boolean).join(", ");
714
+ const body = /* @__PURE__ */ jsxs(Fragment, { children: [
715
+ /* @__PURE__ */ jsxs(View, { style: styles.glance, children: [
716
+ /* @__PURE__ */ jsxs(View, { style: styles.header, children: [
717
+ /* @__PURE__ */ jsx(Icon, { name: icon, size: theme.icon.xs, color: theme.color.accentStrong }),
718
+ /* @__PURE__ */ jsx(Text, { style: styles.appName, numberOfLines: 1, children: appName })
719
+ ] }),
720
+ /* @__PURE__ */ jsx(Text, { style: styles.title, numberOfLines: TITLE_LINES[sizeClass], children: title }),
721
+ subtitle ? /* @__PURE__ */ jsx(Text, { style: styles.subtitle, numberOfLines: 1, children: subtitle }) : null
722
+ ] }),
723
+ shown.length > 0 || caption ? /* @__PURE__ */ jsxs(View, { style: styles.readout, children: [
724
+ shown.length > 0 ? /* @__PURE__ */ jsx(View, { style: [styles.stats, sizeClass === "large" ? styles.statsWrap : null], children: shown.map((s) => /* @__PURE__ */ jsxs(View, { style: [styles.stat, { borderRadius: innerRadius }], children: [
725
+ /* @__PURE__ */ jsx(Text, { style: styles.statValue, numberOfLines: 1, children: s.value }),
726
+ /* @__PURE__ */ jsx(Text, { style: styles.statLabel, numberOfLines: 1, children: s.label })
727
+ ] }, s.id)) }) : null,
728
+ caption ? /* @__PURE__ */ jsx(Text, { style: styles.caption, numberOfLines: 1, children: caption }) : null
729
+ ] }) : null
730
+ ] });
731
+ const frame = [styles.widget, { width: size.width, height: size.height, borderRadius: radius }];
732
+ if (onPress) {
733
+ return /* @__PURE__ */ jsx(
734
+ Pressable,
735
+ {
736
+ accessibilityRole: "button",
737
+ accessibilityLabel: announced,
738
+ onPress,
739
+ style: frame,
740
+ children: body
741
+ }
742
+ );
743
+ }
744
+ return /* @__PURE__ */ jsx(View, { accessibilityRole: "image", accessibilityLabel: announced, style: frame, children: body });
745
+ }
746
+ var sheet5 = createStyleSheet((theme) => ({
747
+ // `s3` padding, not `s4`: a `systemSmall` widget is 170pt square, so 16pt on
748
+ // every side spent a fifth of the canvas on margin and left the glance and the
749
+ // stat tile fighting over what was left.
750
+ widget: {
751
+ overflow: "hidden",
752
+ padding: theme.space.s3,
753
+ gap: theme.space.s2,
754
+ backgroundColor: theme.color.surface,
755
+ borderWidth: theme.borderWidth.base,
756
+ borderColor: theme.color.border,
757
+ ...theme.shadow.md
758
+ },
759
+ glance: { gap: theme.space.s1 },
760
+ readout: { gap: theme.space.s1, marginTop: "auto" },
761
+ header: { flexDirection: "row", alignItems: "center", gap: theme.space.s1 },
762
+ appName: {
763
+ flex: 1,
764
+ fontFamily: theme.font.sans,
765
+ fontWeight: theme.fontWeight.bold,
766
+ fontSize: theme.type.caption2.fontSize,
767
+ lineHeight: theme.type.caption2.lineHeight,
768
+ letterSpacing: theme.type.caption2.fontSize * theme.tracking.caps,
769
+ textTransform: "uppercase",
770
+ color: theme.color.text3
771
+ },
772
+ // `subheadline`, not `title3`: two lines of a 20pt serif is 50pt of a 146pt
773
+ // content box, which is what made the headline collide with the rows either
774
+ // side of it. 15/20 is the smallest step that still reads as the glance's
775
+ // headline, and two lines of it fit the family with room to spare.
776
+ title: {
777
+ fontFamily: theme.font.display,
778
+ fontWeight: theme.font.displayWeight,
779
+ fontSize: theme.type.subheadline.fontSize,
780
+ lineHeight: theme.type.subheadline.lineHeight,
781
+ letterSpacing: theme.type.subheadline.letterSpacing,
782
+ color: theme.color.text
783
+ },
784
+ subtitle: {
785
+ fontFamily: theme.font.sans,
786
+ fontSize: theme.type.caption1.fontSize,
787
+ lineHeight: theme.type.caption1.lineHeight,
788
+ color: theme.color.text2
789
+ },
790
+ stats: { flexDirection: "row", gap: theme.space.s2 },
791
+ statsWrap: { flexWrap: "wrap" },
792
+ stat: {
793
+ flexGrow: 1,
794
+ flexBasis: 0,
795
+ minWidth: theme.space.s8,
796
+ paddingVertical: theme.space.s1,
797
+ paddingHorizontal: theme.space.s2,
798
+ backgroundColor: theme.color.surface2
799
+ },
800
+ statValue: {
801
+ fontFamily: theme.font.sans,
802
+ fontWeight: theme.fontWeight.bold,
803
+ fontSize: theme.type.subheadline.fontSize,
804
+ lineHeight: theme.type.subheadline.lineHeight,
805
+ color: theme.color.text
806
+ },
807
+ statLabel: {
808
+ fontFamily: theme.font.sans,
809
+ fontSize: theme.type.caption2.fontSize,
810
+ lineHeight: theme.type.caption2.lineHeight,
811
+ color: theme.color.text3
812
+ },
813
+ caption: {
814
+ fontFamily: theme.font.mono,
815
+ fontSize: theme.type.caption2.fontSize,
816
+ lineHeight: theme.type.caption2.lineHeight,
817
+ color: theme.color.text3
818
+ }
819
+ }));
820
+ var SHARE_TILE = 40;
821
+ function ShareSheetPreview({
822
+ fileName,
823
+ fileMeta,
824
+ icon = "filedoc",
825
+ people = [],
826
+ onPersonPress,
827
+ actions = [],
828
+ onActionPress
829
+ }) {
830
+ const { styles } = useStyles(sheet6);
831
+ const theme = useLoamTheme();
832
+ return /* @__PURE__ */ jsxs(View, { role: "group", accessibilityLabel: "Share sheet preview", style: styles.sheet, children: [
833
+ /* @__PURE__ */ jsx(View, { style: styles.grabber }),
834
+ /* @__PURE__ */ jsxs(View, { style: styles.preview, children: [
835
+ /* @__PURE__ */ jsx(View, { style: styles.previewTile, children: /* @__PURE__ */ jsx(Icon, { name: icon, size: theme.icon.md, color: theme.color.accentOnSoft }) }),
836
+ /* @__PURE__ */ jsxs(View, { style: styles.previewMain, children: [
837
+ /* @__PURE__ */ jsx(Text, { style: styles.previewTitle, numberOfLines: 1, children: fileName }),
838
+ fileMeta ? /* @__PURE__ */ jsx(Text, { style: styles.previewMeta, numberOfLines: 1, children: fileMeta }) : null
839
+ ] })
840
+ ] }),
841
+ people.length > 0 ? /* @__PURE__ */ jsx(View, { style: styles.people, children: people.map((p) => /* @__PURE__ */ jsxs(
842
+ Pressable,
843
+ {
844
+ accessibilityRole: "button",
845
+ accessibilityLabel: `Share with ${p.name}`,
846
+ onPress: onPersonPress ? () => onPersonPress(p.id) : void 0,
847
+ style: ({ pressed }) => [styles.person, pressed ? styles.pressed : null],
848
+ children: [
849
+ /* @__PURE__ */ jsx(Avatar, { name: p.name, src: p.src, size: "lg", accent: true }),
850
+ /* @__PURE__ */ jsx(Text, { style: styles.personName, numberOfLines: 1, children: p.name.split(" ")[0] })
851
+ ]
852
+ },
853
+ p.id
854
+ )) }) : null,
855
+ actions.length > 0 ? /* @__PURE__ */ jsx(View, { style: styles.actions, children: actions.map((a, i) => /* @__PURE__ */ jsxs(
856
+ Pressable,
857
+ {
858
+ accessibilityRole: "button",
859
+ accessibilityLabel: a.label,
860
+ onPress: onActionPress ? () => onActionPress(a.id) : void 0,
861
+ style: ({ pressed }) => [
862
+ styles.actionRow,
863
+ i > 0 ? styles.actionRowDivided : null,
864
+ pressed ? styles.pressed : null
865
+ ],
866
+ children: [
867
+ a.icon ? /* @__PURE__ */ jsx(Icon, { name: a.icon, size: theme.icon.md, color: theme.color.text2 }) : null,
868
+ /* @__PURE__ */ jsx(Text, { style: styles.actionLabel, children: a.label })
869
+ ]
870
+ },
871
+ a.id
872
+ )) }) : null
873
+ ] });
874
+ }
875
+ function TextSelectionMenu({
876
+ items,
877
+ onItemPress,
878
+ platform
879
+ }) {
880
+ const { styles } = useStyles(sheet6);
881
+ const resolved = platform ?? (Platform.OS === "android" ? "Android" : "iOS");
882
+ const ios = resolved === "iOS";
883
+ return /* @__PURE__ */ jsx(
884
+ View,
885
+ {
886
+ accessibilityRole: "menu",
887
+ accessibilityLabel: "Text selection menu",
888
+ style: [styles.menu, ios ? styles.menuIOS : styles.menuAndroid],
889
+ children: items.map((item, i) => /* @__PURE__ */ jsx(
890
+ Pressable,
891
+ {
892
+ accessibilityRole: "menuitem",
893
+ accessibilityLabel: item.label,
894
+ onPress: onItemPress ? () => onItemPress(item.id) : void 0,
895
+ style: ({ pressed }) => [
896
+ styles.menuItem,
897
+ i > 0 ? styles.menuItemDivided : null,
898
+ pressed ? styles.pressed : null
899
+ ],
900
+ children: /* @__PURE__ */ jsx(Text, { style: [styles.menuLabel, ios ? styles.menuLabelIOS : styles.menuLabelAndroid], children: item.label })
901
+ },
902
+ item.id
903
+ ))
904
+ }
905
+ );
906
+ }
907
+ var sheet6 = createStyleSheet((theme) => ({
908
+ // ---- share sheet ----
909
+ sheet: {
910
+ // A sheet's top corners are `radius['2xl']` per the reference; the bottom two are
911
+ // square because it is anchored to the screen edge.
912
+ borderTopLeftRadius: theme.radius["2xl"],
913
+ borderTopRightRadius: theme.radius["2xl"],
914
+ backgroundColor: theme.color.surface,
915
+ padding: theme.space.s4,
916
+ gap: theme.space.s4,
917
+ // Anchored to the screen edge with no scrim of its own: `edge.neutral` is
918
+ // what draws the sheet once high contrast zeroes the elevation, and it is
919
+ // `transparent` before that.
920
+ borderTopWidth: theme.borderWidth.base,
921
+ borderTopColor: theme.edge.neutral,
922
+ ...theme.shadow.lg
923
+ },
924
+ grabber: {
925
+ alignSelf: "center",
926
+ width: SHEET_GRABBER_W,
927
+ // `sheetGrabber` is fixed at 5 and is deliberately not scaled by density.
928
+ height: theme.layout.sheetGrabber,
929
+ borderRadius: theme.radius.full,
930
+ backgroundColor: theme.color.surface3
931
+ },
932
+ preview: { flexDirection: "row", alignItems: "center", gap: theme.space.s3 },
933
+ previewTile: {
934
+ width: SHARE_TILE,
935
+ height: SHARE_TILE,
936
+ borderRadius: theme.radius.md,
937
+ alignItems: "center",
938
+ justifyContent: "center",
939
+ backgroundColor: theme.color.accentSoft,
940
+ borderWidth: theme.borderWidth.base,
941
+ borderColor: theme.edge.accent
942
+ },
943
+ previewMain: { flex: 1, minWidth: 0, gap: theme.space.s1 },
944
+ previewTitle: {
945
+ fontFamily: theme.font.sans,
946
+ fontWeight: theme.type.headline.fontWeight,
947
+ fontSize: theme.type.headline.fontSize,
948
+ lineHeight: theme.type.headline.lineHeight,
949
+ letterSpacing: theme.type.headline.letterSpacing,
950
+ color: theme.color.text
951
+ },
952
+ previewMeta: {
953
+ fontFamily: theme.font.sans,
954
+ fontSize: theme.type.footnote.fontSize,
955
+ lineHeight: theme.type.footnote.lineHeight,
956
+ color: theme.color.text3
957
+ },
958
+ people: { flexDirection: "row", gap: theme.space.s4, overflow: "hidden" },
959
+ person: { alignItems: "center", gap: theme.space.s1, maxWidth: theme.space.s8 },
960
+ personName: {
961
+ fontFamily: theme.font.sans,
962
+ fontSize: theme.type.caption2.fontSize,
963
+ lineHeight: theme.type.caption2.lineHeight,
964
+ color: theme.color.text2
965
+ },
966
+ actions: {
967
+ borderRadius: theme.radius.md,
968
+ backgroundColor: theme.color.surface2,
969
+ overflow: "hidden"
970
+ },
971
+ actionRow: {
972
+ flexDirection: "row",
973
+ alignItems: "center",
974
+ gap: theme.space.s3,
975
+ minHeight: theme.tapMin,
976
+ paddingHorizontal: theme.space.s3
977
+ },
978
+ actionRowDivided: {
979
+ borderTopWidth: theme.borderWidth.base,
980
+ borderTopColor: theme.color.border
981
+ },
982
+ actionLabel: {
983
+ flex: 1,
984
+ fontFamily: theme.font.sans,
985
+ fontWeight: theme.fontWeight.semibold,
986
+ fontSize: theme.type.subheadline.fontSize,
987
+ lineHeight: theme.type.subheadline.lineHeight,
988
+ color: theme.color.text
989
+ },
990
+ pressed: { opacity: theme.state.pressedOpacity },
991
+ // ---- system edit menu ----
992
+ menu: {
993
+ flexDirection: "row",
994
+ alignSelf: "flex-start",
995
+ overflow: "hidden",
996
+ ...theme.shadow.lg
997
+ },
998
+ // iOS: dark chrome at the system callout's own tight radius (OS-mandated, not a
999
+ // Loam radius step) — the menu has to look like the OS's, not ours.
1000
+ menuIOS: { backgroundColor: theme.color.text, borderRadius: IOS_EDIT_MENU_RADIUS },
1001
+ // Android: a Material menu surface, which *is* a Loam radius.
1002
+ menuAndroid: { backgroundColor: theme.color.surface, borderRadius: theme.radius.md },
1003
+ menuItem: {
1004
+ minHeight: theme.tapMin,
1005
+ justifyContent: "center",
1006
+ paddingHorizontal: theme.space.s3
1007
+ },
1008
+ // A grey with enough contrast on both the dark iOS chrome and the light Android
1009
+ // surface — the OS's own separator, not a Loam border token.
1010
+ menuItemDivided: {
1011
+ borderStartWidth: theme.borderWidth.base,
1012
+ borderStartColor: OS_MATERIAL.divider
1013
+ },
1014
+ menuLabel: {
1015
+ fontFamily: theme.font.sans,
1016
+ fontWeight: theme.fontWeight.semibold,
1017
+ fontSize: theme.type.footnote.fontSize,
1018
+ lineHeight: theme.type.footnote.lineHeight
1019
+ },
1020
+ menuLabelIOS: { color: theme.color.bg },
1021
+ menuLabelAndroid: { color: theme.color.text }
1022
+ }));
1023
+
1024
+ export { ANDROID_ICON_MASK_RATIO, APP_ICON_ASSET_PX, APP_ICON_PREVIEW_PX, AppIconPreview, DYNAMIC_ISLAND, DYNAMIC_ISLAND_FILL, ICON_MARK_RATIO, ICON_SAFE_MARGIN, IOS_EDIT_MENU_RADIUS, IOS_ICON_CORNER_RATIO, LAUNCH_MARK, LIVE_ACTIVITY_CARD, LIVE_ACTIVITY_MAX_H, LOCK_SCREEN_WALLPAPER, LaunchScreenPreview, LiveActivityPreview, LockScreenNotification, LockScreenPreview, NotificationBanner, OS_MATERIAL, OS_MATERIAL_INK_OPACITY, SHEET_GRABBER_W, ShareSheetPreview, TextSelectionMenu, WIDGET_CORNER, WIDGET_INNER_CORNER_ANDROID, WIDGET_SIZE, WidgetPreview };