@btndprojects/loam-ui-native 0.0.0-stage → 0.1.1

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/README.md +55 -2
  3. package/dist/accordion/index.js +3 -0
  4. package/dist/adaptive/index.js +2 -0
  5. package/dist/alert/index.js +3 -0
  6. package/dist/analyzer/index.js +6 -0
  7. package/dist/announcement/index.js +6 -0
  8. package/dist/audio-player/index.js +3 -0
  9. package/dist/avatar/index.js +3 -0
  10. package/dist/avatar-group/index.js +3 -0
  11. package/dist/badge/index.js +5 -0
  12. package/dist/banner/index.js +2 -0
  13. package/dist/board/index.js +7 -0
  14. package/dist/button/index.js +5 -0
  15. package/dist/camera-permission/index.js +13 -0
  16. package/dist/capture-result/index.js +18 -0
  17. package/dist/card/index.js +4 -0
  18. package/dist/card-form/index.js +10 -0
  19. package/dist/carousel/index.js +2 -0
  20. package/dist/chart/index.js +6 -0
  21. package/dist/charts/index.js +7 -0
  22. package/dist/checkbox/index.js +7 -0
  23. package/dist/checklist/index.js +10 -0
  24. package/dist/checkout/index.js +13 -0
  25. package/dist/chip/index.js +7 -0
  26. package/dist/chunk-223Q2T2Q.js +229 -0
  27. package/dist/chunk-2A5NIQAK.js +254 -0
  28. package/dist/chunk-2GK6PHGE.js +363 -0
  29. package/dist/chunk-2WUTXHMU.js +1 -0
  30. package/dist/chunk-2ZZ7TDJ4.js +324 -0
  31. package/dist/chunk-35OFI3JG.js +499 -0
  32. package/dist/chunk-3ARE4TES.js +195 -0
  33. package/dist/chunk-3ECPOSKO.js +1 -0
  34. package/dist/chunk-3ENTMPQY.js +229 -0
  35. package/dist/chunk-3HJH6FF4.js +98 -0
  36. package/dist/chunk-3NMD74ZU.js +4 -0
  37. package/dist/chunk-3WPW4KUN.js +182 -0
  38. package/dist/chunk-423B7SPN.js +993 -0
  39. package/dist/chunk-47FWHGUN.js +394 -0
  40. package/dist/chunk-4BXMXBJO.js +141 -0
  41. package/dist/chunk-4DS5ZWVC.js +83 -0
  42. package/dist/chunk-4GBOGOTV.js +295 -0
  43. package/dist/chunk-4SZYY6AN.js +99 -0
  44. package/dist/chunk-4YIP3CWS.js +237 -0
  45. package/dist/chunk-4YQOR7JM.js +204 -0
  46. package/dist/chunk-4ZAHCY44.js +177 -0
  47. package/dist/chunk-52X2VYTD.js +228 -0
  48. package/dist/chunk-5P26LBDL.js +1 -0
  49. package/dist/chunk-5XLRYDAX.js +1 -0
  50. package/dist/chunk-5YNTNN5A.js +177 -0
  51. package/dist/chunk-6HT2LP62.js +504 -0
  52. package/dist/chunk-6N33LFQA.js +35 -0
  53. package/dist/chunk-6X6PQ443.js +432 -0
  54. package/dist/chunk-7CFF4HXS.js +126 -0
  55. package/dist/chunk-7O2FMUPT.js +755 -0
  56. package/dist/chunk-AI4DWZCD.js +162 -0
  57. package/dist/chunk-AWJOCGFZ.js +213 -0
  58. package/dist/chunk-AZRX6SAC.js +285 -0
  59. package/dist/chunk-B54QE2FN.js +1 -0
  60. package/dist/chunk-BC475SJH.js +1 -0
  61. package/dist/chunk-BEDBFJND.js +202 -0
  62. package/dist/chunk-BTTFCMKG.js +166 -0
  63. package/dist/chunk-BV3B2HYZ.js +1635 -0
  64. package/dist/chunk-C2NTXVWM.js +98 -0
  65. package/dist/chunk-CDRZHT42.js +424 -0
  66. package/dist/chunk-CJ5EOYZE.js +62 -0
  67. package/dist/chunk-CUMUNPXY.js +128 -0
  68. package/dist/chunk-CUS3ZSCU.js +1 -0
  69. package/dist/chunk-CYRBHQQZ.js +411 -0
  70. package/dist/chunk-DHWADYZX.js +144 -0
  71. package/dist/chunk-DKCSPATL.js +1 -0
  72. package/dist/chunk-DM5QUKKO.js +326 -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-FMYDDECK.js +1521 -0
  80. package/dist/chunk-G3HOYSF7.js +1 -0
  81. package/dist/chunk-GDKZTW34.js +395 -0
  82. package/dist/chunk-GL7B2DOW.js +134 -0
  83. package/dist/chunk-GNNJ4GCC.js +471 -0
  84. package/dist/chunk-GNZ56EUI.js +1 -0
  85. package/dist/chunk-H6NIOQHK.js +346 -0
  86. package/dist/chunk-H6PC7RDA.js +1 -0
  87. package/dist/chunk-HET4LLMX.js +129 -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-HZ6CNMAF.js +870 -0
  92. package/dist/chunk-IIQTAY3J.js +715 -0
  93. package/dist/chunk-ITTYJZWX.js +271 -0
  94. package/dist/chunk-IVNRBPGC.js +1018 -0
  95. package/dist/chunk-J4KLDGI4.js +1 -0
  96. package/dist/chunk-J7Q7VCVC.js +254 -0
  97. package/dist/chunk-JDEOITQ2.js +1 -0
  98. package/dist/chunk-JNH2TYIN.js +96 -0
  99. package/dist/chunk-JNY4ZHAR.js +634 -0
  100. package/dist/chunk-JRLTGLN4.js +1 -0
  101. package/dist/chunk-JTRJJLKL.js +1 -0
  102. package/dist/chunk-JUAMLEHX.js +379 -0
  103. package/dist/chunk-JXJB2X6M.js +131 -0
  104. package/dist/chunk-KMI5ML5I.js +262 -0
  105. package/dist/chunk-KTAC4MGE.js +4 -0
  106. package/dist/chunk-LAECSR4H.js +178 -0
  107. package/dist/chunk-LK4BCSGX.js +1 -0
  108. package/dist/chunk-LOE5YNN3.js +470 -0
  109. package/dist/chunk-LRJNDEIA.js +302 -0
  110. package/dist/chunk-LSS54GLJ.js +116 -0
  111. package/dist/chunk-LZLHELAG.js +137 -0
  112. package/dist/chunk-M2LUXGKZ.js +1 -0
  113. package/dist/chunk-MOUJOP55.js +425 -0
  114. package/dist/chunk-N32RGOA5.js +159 -0
  115. package/dist/chunk-N5ZNE2YE.js +1 -0
  116. package/dist/chunk-NBNCBML6.js +1 -0
  117. package/dist/chunk-NKB7BZYK.js +167 -0
  118. package/dist/chunk-NQYBUO3K.js +1795 -0
  119. package/dist/chunk-NY2U3PBI.js +59 -0
  120. package/dist/chunk-O4AEHWII.js +464 -0
  121. package/dist/chunk-O7AE6DAF.js +146 -0
  122. package/dist/chunk-OETCUXQP.js +134 -0
  123. package/dist/chunk-OOPPQH27.js +179 -0
  124. package/dist/chunk-OPXFZ4Y5.js +204 -0
  125. package/dist/chunk-P23EQNRQ.js +338 -0
  126. package/dist/chunk-PJXUJYS6.js +248 -0
  127. package/dist/chunk-PNWUWOBF.js +1 -0
  128. package/dist/chunk-PUPM2YGF.js +376 -0
  129. package/dist/chunk-PZXZJBMP.js +620 -0
  130. package/dist/chunk-Q72LJOVE.js +169 -0
  131. package/dist/chunk-QCDXZBC5.js +247 -0
  132. package/dist/chunk-QDYDBCFP.js +197 -0
  133. package/dist/chunk-QJJ6MADV.js +274 -0
  134. package/dist/chunk-QKXCPTFS.js +349 -0
  135. package/dist/chunk-QWCMU5B2.js +860 -0
  136. package/dist/chunk-RIBDXGUG.js +108 -0
  137. package/dist/chunk-RJQOOHT2.js +214 -0
  138. package/dist/chunk-RNGLIHFA.js +191 -0
  139. package/dist/chunk-RQ3HASCC.js +6 -0
  140. package/dist/chunk-RSOJT5YW.js +124 -0
  141. package/dist/chunk-RTIKSY6A.js +1 -0
  142. package/dist/chunk-RUZHTGKB.js +237 -0
  143. package/dist/chunk-RW6ZL2FQ.js +284 -0
  144. package/dist/chunk-S7F6XLTK.js +540 -0
  145. package/dist/chunk-SBMNWWBV.js +155 -0
  146. package/dist/chunk-SDTQPRRB.js +1 -0
  147. package/dist/chunk-SYNOJOQO.js +377 -0
  148. package/dist/chunk-T753XT36.js +1 -0
  149. package/dist/chunk-TKQYWZ5V.js +196 -0
  150. package/dist/chunk-TKVFVF5D.js +95 -0
  151. package/dist/chunk-TOXAGRDQ.js +144 -0
  152. package/dist/chunk-TUTI5HOZ.js +112 -0
  153. package/dist/chunk-U7TFM2JA.js +140 -0
  154. package/dist/chunk-UC2WGUP3.js +627 -0
  155. package/dist/chunk-UWFLYMAR.js +1126 -0
  156. package/dist/chunk-UWSZB7BJ.js +1 -0
  157. package/dist/chunk-UYG5L34B.js +87 -0
  158. package/dist/chunk-VBOXAEPI.js +336 -0
  159. package/dist/chunk-VCDBXW5H.js +188 -0
  160. package/dist/chunk-VCXPHHDS.js +1 -0
  161. package/dist/chunk-VEAYTRPX.js +414 -0
  162. package/dist/chunk-VFCUIDRT.js +1 -0
  163. package/dist/chunk-VQHPQEUH.js +273 -0
  164. package/dist/chunk-VXDZFJGP.js +874 -0
  165. package/dist/chunk-WS56BL3D.js +1 -0
  166. package/dist/chunk-WSW6ZFAQ.js +98 -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-YTFKPRUK.js +845 -0
  181. package/dist/chunk-Z7H7VSFZ.js +323 -0
  182. package/dist/chunk-ZK2H4O5P.js +1 -0
  183. package/dist/chunk-ZVFE5QGU.js +524 -0
  184. package/dist/coachmark/index.js +4 -0
  185. package/dist/code-block/index.js +4 -0
  186. package/dist/color-field/index.js +7 -0
  187. package/dist/commerce/index.js +20 -0
  188. package/dist/connectivity/index.js +2 -0
  189. package/dist/context-menu/index.js +5 -0
  190. package/dist/copy-button/index.js +3 -0
  191. package/dist/data-grid/index.js +24 -0
  192. package/dist/date-range-picker/index.js +13 -0
  193. package/dist/description-list/index.js +2 -0
  194. package/dist/dialog/index.js +3 -0
  195. package/dist/empty-state/index.js +3 -0
  196. package/dist/error-summary/index.js +3 -0
  197. package/dist/fab/index.js +2 -0
  198. package/dist/file-upload/index.js +9 -0
  199. package/dist/filter-bar/index.js +8 -0
  200. package/dist/flow-run/index.js +14 -0
  201. package/dist/form/index.js +3 -0
  202. package/dist/gallery/index.js +2 -0
  203. package/dist/header/index.js +5 -0
  204. package/dist/hold-to-confirm/index.js +4 -0
  205. package/dist/index.d.ts +22883 -0
  206. package/dist/index.js +158 -0
  207. package/dist/infinite-list/index.js +2 -0
  208. package/dist/inline-edit/index.js +5 -0
  209. package/dist/input/index.js +5 -0
  210. package/dist/interactions/index.js +2 -0
  211. package/dist/invoice/index.js +10 -0
  212. package/dist/kbd/index.js +1 -0
  213. package/dist/keyboard/index.js +3 -0
  214. package/dist/list-row/index.js +7 -0
  215. package/dist/log-viewer/index.js +2 -0
  216. package/dist/map-view/index.js +3 -0
  217. package/dist/markdown/index.js +6 -0
  218. package/dist/metric/index.js +4 -0
  219. package/dist/money/index.js +3 -0
  220. package/dist/nav-drawer/index.js +2 -0
  221. package/dist/navigation/index.js +2 -0
  222. package/dist/notification-center/index.js +16 -0
  223. package/dist/number-roll/index.js +3 -0
  224. package/dist/one-handed/index.js +5 -0
  225. package/dist/os-surfaces/index.js +3 -0
  226. package/dist/otp/index.js +7 -0
  227. package/dist/page-header/index.js +1 -0
  228. package/dist/payment-method/index.js +12 -0
  229. package/dist/payment-status/index.js +16 -0
  230. package/dist/permission/index.js +2 -0
  231. package/dist/picker/index.js +7 -0
  232. package/dist/progress/index.js +5 -0
  233. package/dist/pull-to-refresh/index.js +4 -0
  234. package/dist/radio-group/index.js +6 -0
  235. package/dist/rating/index.js +2 -0
  236. package/dist/receivables/index.js +20 -0
  237. package/dist/refund/index.js +16 -0
  238. package/dist/save-status/index.js +3 -0
  239. package/dist/scroll-area/index.js +1 -0
  240. package/dist/search-bar/index.js +5 -0
  241. package/dist/segmented-control/index.js +5 -0
  242. package/dist/sheet/index.js +4 -0
  243. package/dist/skeleton/index.js +2 -0
  244. package/dist/slider/index.js +5 -0
  245. package/dist/spinner/index.js +2 -0
  246. package/dist/split-button/index.js +7 -0
  247. package/dist/spotlight/index.js +2 -0
  248. package/dist/stage-timeline/index.js +4 -0
  249. package/dist/status-dot/index.js +2 -0
  250. package/dist/stepper/index.js +6 -0
  251. package/dist/streaming-text/index.js +3 -0
  252. package/dist/survey/index.js +19 -0
  253. package/dist/switch/index.js +5 -0
  254. package/dist/system-state/index.js +4 -0
  255. package/dist/tab-bar/index.js +2 -0
  256. package/dist/tabs/index.js +2 -0
  257. package/dist/textarea/index.js +6 -0
  258. package/dist/thinking/index.js +2 -0
  259. package/dist/timeline/index.js +4 -0
  260. package/dist/toast/index.js +2 -0
  261. package/dist/toolbar/index.js +2 -0
  262. package/dist/tour/index.js +11 -0
  263. package/dist/truncation-notice/index.js +2 -0
  264. package/dist/update-notice/index.js +11 -0
  265. package/dist/video-player/index.js +2 -0
  266. package/dist/viewfinder/index.js +5 -0
  267. package/dist/widget/index.js +4 -0
  268. package/dist/wizard/index.js +6 -0
  269. package/package.json +76 -3
@@ -0,0 +1,1795 @@
1
+ import { Switch } from './chunk-YG3ZJHGU.js';
2
+ import { List, ListRow } from './chunk-IVNRBPGC.js';
3
+ import { SegmentedControl } from './chunk-5YNTNN5A.js';
4
+ import { Progress } from './chunk-2ZZ7TDJ4.js';
5
+ import { Amount, PriceRow } from './chunk-VBOXAEPI.js';
6
+ import { TextField } from './chunk-ITTYJZWX.js';
7
+ import { Badge } from './chunk-SBMNWWBV.js';
8
+ import { Button } from './chunk-3ARE4TES.js';
9
+ import { Icon } from './chunk-TKVFVF5D.js';
10
+ import * as React6 from 'react';
11
+ import { Platform, ScrollView, Text, View, Pressable } from 'react-native';
12
+ import { createStyleSheet, useStyles, useLoamTheme } from '@btndprojects/loam-theme-native';
13
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
14
+ import { yearlySaving, planDelta, PLAN_COPY, planCta, yearlyMonthly, money } from '@btndprojects/loam-pay';
15
+
16
+ function DeleteAccountScreen({
17
+ title = "Delete account",
18
+ deletes,
19
+ retains,
20
+ duration,
21
+ confirmationPhrase,
22
+ confirmationLabel,
23
+ confirmationValue,
24
+ onConfirmationChange,
25
+ confirmLabel = "Delete my account",
26
+ onConfirm,
27
+ cancelLabel = "Keep my account",
28
+ onCancel
29
+ }) {
30
+ const { styles } = useStyles(sheet);
31
+ const theme = useLoamTheme();
32
+ const isControlled = confirmationValue !== void 0;
33
+ const [internal, setInternal] = React6.useState("");
34
+ const typed = isControlled ? confirmationValue : internal;
35
+ const armed = typed.trim() === confirmationPhrase;
36
+ const change = (next) => {
37
+ if (!isControlled) setInternal(next);
38
+ onConfirmationChange?.(next);
39
+ };
40
+ return /* @__PURE__ */ jsxs(ScrollView, { style: styles.screen, contentContainerStyle: styles.scroll, children: [
41
+ /* @__PURE__ */ jsx(Text, { style: styles.title, children: title }),
42
+ /* @__PURE__ */ jsxs(View, { style: styles.group, children: [
43
+ /* @__PURE__ */ jsx(Text, { style: styles.caption, children: "What is deleted" }),
44
+ deletes.map((d) => /* @__PURE__ */ jsxs(View, { style: styles.row, children: [
45
+ /* @__PURE__ */ jsx(Icon, { name: "trash", size: theme.icon.sm, color: theme.color.danger }),
46
+ /* @__PURE__ */ jsx(Text, { style: styles.rowLabel, children: d })
47
+ ] }, d))
48
+ ] }),
49
+ /* @__PURE__ */ jsxs(View, { style: styles.group, children: [
50
+ /* @__PURE__ */ jsx(Text, { style: styles.caption, children: "What is kept, and why" }),
51
+ retains.map((r) => /* @__PURE__ */ jsxs(View, { style: styles.row, children: [
52
+ /* @__PURE__ */ jsx(Icon, { name: "lock", size: theme.icon.sm, color: theme.color.text3 }),
53
+ /* @__PURE__ */ jsx(Text, { style: styles.rowLabel, children: r })
54
+ ] }, r))
55
+ ] }),
56
+ /* @__PURE__ */ jsx(View, { style: styles.duration, children: /* @__PURE__ */ jsx(Text, { style: styles.durationText, children: `Deletion completes ${duration}.` }) }),
57
+ /* @__PURE__ */ jsx(
58
+ TextField,
59
+ {
60
+ label: confirmationLabel ?? `Type ${confirmationPhrase} to confirm`,
61
+ value: typed,
62
+ onChangeText: change,
63
+ autoCapitalize: "characters",
64
+ autoCorrect: false,
65
+ accessibilityLabel: confirmationLabel ?? `Type ${confirmationPhrase} to confirm`
66
+ }
67
+ ),
68
+ /* @__PURE__ */ jsx(
69
+ Pressable,
70
+ {
71
+ accessibilityRole: "button",
72
+ accessibilityLabel: confirmLabel,
73
+ accessibilityState: { disabled: !armed },
74
+ disabled: !armed,
75
+ onPress: armed ? onConfirm : void 0,
76
+ style: ({ pressed }) => [
77
+ styles.confirm,
78
+ armed ? null : styles.confirmDisabled,
79
+ pressed && armed ? styles.pressed : null
80
+ ],
81
+ children: /* @__PURE__ */ jsx(Text, { style: [styles.confirmLabel, armed ? null : styles.confirmLabelDisabled], children: confirmLabel })
82
+ }
83
+ ),
84
+ /* @__PURE__ */ jsx(
85
+ Pressable,
86
+ {
87
+ accessibilityRole: "button",
88
+ accessibilityLabel: cancelLabel,
89
+ onPress: onCancel,
90
+ style: ({ pressed }) => [styles.cancel, pressed ? styles.pressed : null],
91
+ children: /* @__PURE__ */ jsx(Text, { style: styles.cancelLabel, children: cancelLabel })
92
+ }
93
+ )
94
+ ] });
95
+ }
96
+ var sheet = createStyleSheet((theme) => ({
97
+ screen: { flex: 1, backgroundColor: theme.color.bg },
98
+ scroll: { padding: theme.layout.screenPad, gap: theme.space.s4 },
99
+ title: {
100
+ fontFamily: theme.font.display,
101
+ fontWeight: theme.font.displayWeight,
102
+ fontSize: theme.type.title2.fontSize,
103
+ lineHeight: theme.type.title2.lineHeight,
104
+ letterSpacing: theme.type.title2.letterSpacing,
105
+ color: theme.color.text
106
+ },
107
+ group: { gap: theme.space.s2 },
108
+ caption: {
109
+ fontFamily: theme.font.sans,
110
+ fontWeight: theme.fontWeight.bold,
111
+ fontSize: theme.type.caption2.fontSize,
112
+ lineHeight: theme.type.caption2.lineHeight,
113
+ letterSpacing: theme.type.caption2.fontSize * theme.tracking.caps,
114
+ textTransform: "uppercase",
115
+ color: theme.color.text3
116
+ },
117
+ row: { flexDirection: "row", alignItems: "flex-start", gap: theme.space.s2 },
118
+ rowLabel: {
119
+ flex: 1,
120
+ fontFamily: theme.font.sans,
121
+ fontSize: theme.type.footnote.fontSize,
122
+ lineHeight: theme.type.footnote.fontSize * theme.lineHeight.normal,
123
+ color: theme.color.text
124
+ },
125
+ duration: {
126
+ padding: theme.space.s3,
127
+ borderRadius: theme.radius.md,
128
+ backgroundColor: theme.color.warnSoft,
129
+ borderWidth: theme.borderWidth.base,
130
+ borderColor: theme.edge.warn
131
+ },
132
+ // Text on a `-soft` tint takes the tone itself, never the on-ink.
133
+ durationText: {
134
+ fontFamily: theme.font.sans,
135
+ fontSize: theme.type.footnote.fontSize,
136
+ lineHeight: theme.type.footnote.lineHeight,
137
+ color: theme.color.warnOnSoft
138
+ },
139
+ confirm: {
140
+ minHeight: theme.tapMin,
141
+ alignItems: "center",
142
+ justifyContent: "center",
143
+ borderRadius: theme.radius.md,
144
+ backgroundColor: theme.color.danger
145
+ },
146
+ // Disabled is the state layer plus `disabledOpacity`, never a hand-greyed colour.
147
+ confirmDisabled: { backgroundColor: theme.state.disabledFill },
148
+ // Solid `danger` fill, so its measured ink — not a flat white.
149
+ confirmLabel: {
150
+ fontFamily: theme.font.sans,
151
+ fontWeight: theme.fontWeight.bold,
152
+ fontSize: theme.type.callout.fontSize,
153
+ lineHeight: theme.type.callout.lineHeight,
154
+ color: theme.color.dangerInk
155
+ },
156
+ confirmLabelDisabled: { color: theme.color.text3, opacity: theme.state.disabledOpacity },
157
+ cancel: {
158
+ minHeight: theme.tapMin,
159
+ alignItems: "center",
160
+ justifyContent: "center"
161
+ },
162
+ cancelLabel: {
163
+ fontFamily: theme.font.sans,
164
+ fontWeight: theme.fontWeight.semibold,
165
+ fontSize: theme.type.subheadline.fontSize,
166
+ lineHeight: theme.type.subheadline.lineHeight,
167
+ color: theme.color.text2
168
+ },
169
+ pressed: { opacity: theme.state.pressedOpacity }
170
+ }));
171
+ var INDICATOR = { ring: 20, dot: 10 };
172
+ function Paywall({
173
+ title,
174
+ description,
175
+ features = [],
176
+ meter,
177
+ plans,
178
+ selectedPlanId,
179
+ onSelectPlan,
180
+ ctaLabel,
181
+ onPurchase,
182
+ busy = false,
183
+ legalText,
184
+ restoreLabel = "Restore purchase",
185
+ onRestore,
186
+ footNote,
187
+ links = [],
188
+ onLinkPress,
189
+ onClose,
190
+ closeLabel = "Close"
191
+ }) {
192
+ const { styles } = useStyles(sheet2);
193
+ const theme = useLoamTheme();
194
+ const fallback = plans.find((p) => p.recommended)?.id ?? plans[0]?.id;
195
+ const isControlled = selectedPlanId !== void 0;
196
+ const [internal, setInternal] = React6.useState(fallback);
197
+ const selected = isControlled ? selectedPlanId : internal;
198
+ const select = (id) => {
199
+ if (!isControlled) setInternal(id);
200
+ onSelectPlan?.(id);
201
+ };
202
+ return /* @__PURE__ */ jsxs(View, { style: styles.screen, children: [
203
+ /* @__PURE__ */ jsxs(ScrollView, { contentContainerStyle: styles.scroll, children: [
204
+ onClose ? /* @__PURE__ */ jsx(View, { style: styles.closeRow, children: /* @__PURE__ */ jsx(
205
+ Pressable,
206
+ {
207
+ accessibilityRole: "button",
208
+ accessibilityLabel: closeLabel,
209
+ onPress: onClose,
210
+ style: styles.close,
211
+ children: /* @__PURE__ */ jsx(Icon, { name: "x", size: theme.icon.md, color: theme.color.text3 })
212
+ }
213
+ ) }) : null,
214
+ /* @__PURE__ */ jsxs(View, { style: styles.intro, children: [
215
+ /* @__PURE__ */ jsx(Text, { style: styles.title, children: title }),
216
+ description ? /* @__PURE__ */ jsx(Text, { style: styles.description, children: description }) : null
217
+ ] }),
218
+ features.length > 0 ? /* @__PURE__ */ jsx(View, { style: styles.features, children: features.map((f) => (
219
+ // The glyph carries the same meaning as the tint, so the list still
220
+ // reads under forced colors / greyscale.
221
+ /* @__PURE__ */ jsxs(View, { style: styles.featureRow, children: [
222
+ /* @__PURE__ */ jsx(Icon, { name: "check", size: theme.icon.sm, color: theme.color.good }),
223
+ /* @__PURE__ */ jsx(Text, { style: styles.featureLabel, children: f })
224
+ ] }, f)
225
+ )) }) : null,
226
+ meter,
227
+ /* @__PURE__ */ jsx(View, { accessibilityRole: "radiogroup", style: styles.plans, children: plans.map((plan) => {
228
+ const on = plan.id === selected;
229
+ return /* @__PURE__ */ jsxs(
230
+ Pressable,
231
+ {
232
+ accessibilityRole: "radio",
233
+ accessibilityLabel: `${plan.name} ${plan.price} ${plan.period}`,
234
+ accessibilityState: { checked: on, selected: on },
235
+ "aria-checked": on,
236
+ onPress: () => select(plan.id),
237
+ style: ({ pressed }) => [
238
+ styles.plan,
239
+ on ? styles.planOn : null,
240
+ pressed ? styles.planPressed : null
241
+ ],
242
+ children: [
243
+ /* @__PURE__ */ jsx(View, { style: [styles.indicator, on ? styles.indicatorOn : null], children: on ? /* @__PURE__ */ jsx(View, { style: styles.indicatorDot }) : null }),
244
+ /* @__PURE__ */ jsxs(View, { style: styles.planMain, children: [
245
+ plan.recommended ? /* @__PURE__ */ jsx(Badge, { style: styles.rowBadge, variant: "accent", size: "sm", children: plan.recommendedLabel ?? "Best value" }) : null,
246
+ /* @__PURE__ */ jsxs(Text, { style: styles.planTitle, children: [
247
+ plan.name,
248
+ " \xB7 ",
249
+ plan.price,
250
+ " ",
251
+ plan.period
252
+ ] }),
253
+ plan.note ? /* @__PURE__ */ jsx(Text, { style: styles.planNote, children: plan.note }) : null
254
+ ] })
255
+ ]
256
+ },
257
+ plan.id
258
+ );
259
+ }) })
260
+ ] }),
261
+ /* @__PURE__ */ jsxs(View, { style: styles.footer, children: [
262
+ /* @__PURE__ */ jsx(
263
+ Button,
264
+ {
265
+ fullWidth: true,
266
+ size: "lg",
267
+ loading: busy,
268
+ onPress: selected ? () => onPurchase?.(selected) : void 0,
269
+ children: ctaLabel
270
+ }
271
+ ),
272
+ /* @__PURE__ */ jsx(Text, { style: styles.legal, children: legalText }),
273
+ footNote,
274
+ /* @__PURE__ */ jsxs(View, { style: styles.links, children: [
275
+ /* @__PURE__ */ jsx(
276
+ Pressable,
277
+ {
278
+ accessibilityRole: "button",
279
+ accessibilityLabel: restoreLabel,
280
+ onPress: onRestore,
281
+ children: /* @__PURE__ */ jsx(Text, { style: styles.link, children: restoreLabel })
282
+ }
283
+ ),
284
+ links.map((l) => /* @__PURE__ */ jsx(
285
+ Pressable,
286
+ {
287
+ accessibilityRole: "link",
288
+ accessibilityLabel: l.label,
289
+ onPress: () => onLinkPress?.(l.id),
290
+ children: /* @__PURE__ */ jsx(Text, { style: styles.link, children: l.label })
291
+ },
292
+ l.id
293
+ ))
294
+ ] })
295
+ ] })
296
+ ] });
297
+ }
298
+ var sheet2 = createStyleSheet((theme) => ({
299
+ screen: { flex: 1, backgroundColor: theme.color.bg },
300
+ scroll: { padding: theme.layout.screenPad, gap: theme.space.s4 },
301
+ closeRow: { flexDirection: "row", justifyContent: "flex-end" },
302
+ close: {
303
+ minWidth: theme.tapMin,
304
+ minHeight: theme.tapMin,
305
+ alignItems: "flex-end",
306
+ justifyContent: "center"
307
+ },
308
+ intro: { gap: theme.space.s2 },
309
+ // Product name = the display face at the "Title 1" role.
310
+ title: {
311
+ fontFamily: theme.font.display,
312
+ fontWeight: theme.font.displayWeight,
313
+ fontSize: theme.type.title1.fontSize,
314
+ lineHeight: theme.type.title1.lineHeight,
315
+ letterSpacing: theme.type.title1.letterSpacing,
316
+ color: theme.color.text
317
+ },
318
+ description: {
319
+ fontFamily: theme.font.sans,
320
+ fontSize: theme.type.subheadline.fontSize,
321
+ lineHeight: theme.type.subheadline.fontSize * theme.lineHeight.relaxed,
322
+ color: theme.color.text2
323
+ },
324
+ features: { gap: theme.space.s2 },
325
+ featureRow: { flexDirection: "row", alignItems: "center", gap: theme.space.s2 },
326
+ featureLabel: {
327
+ flex: 1,
328
+ fontFamily: theme.font.sans,
329
+ fontSize: theme.type.subheadline.fontSize,
330
+ lineHeight: theme.type.subheadline.lineHeight,
331
+ color: theme.color.text
332
+ },
333
+ plans: { gap: theme.space.s2 },
334
+ plan: {
335
+ flexDirection: "row",
336
+ alignItems: "center",
337
+ gap: theme.space.s3,
338
+ minHeight: theme.tapMin,
339
+ padding: theme.space.s3,
340
+ borderRadius: theme.radius.md,
341
+ // `borderWidth.thick` is the static scale: the plan outline is part of the
342
+ // drawing (it is what "selected" looks like), not resizable chrome.
343
+ borderWidth: theme.borderWidth.thick,
344
+ borderColor: theme.color.borderStrong,
345
+ backgroundColor: "transparent"
346
+ },
347
+ // `focus.ringColor`, not the raw accent: this border is what marks the chosen
348
+ // plan, and it is the only thing left doing that once high contrast flattens
349
+ // the `accentSoft` fill to the plain surface. It is a graphical object under
350
+ // SC 1.4.11 either way — see `focusRingColor()`, which is named for the focus
351
+ // ring but derives the accent at exactly the 3:1 hard-edge strength any
352
+ // meaning-carrying accent edge needs. Identical to the raw accent in 9 of the
353
+ // 14 palette x mode combos, so this moves only the four palettes that failed.
354
+ planOn: {
355
+ borderColor: theme.focus.ringColor,
356
+ backgroundColor: theme.color.accentSoft
357
+ },
358
+ planPressed: { backgroundColor: theme.state.pressed },
359
+ indicator: {
360
+ width: INDICATOR.ring,
361
+ height: INDICATOR.ring,
362
+ flexShrink: 0,
363
+ alignItems: "center",
364
+ justifyContent: "center",
365
+ borderRadius: theme.radius.full,
366
+ borderWidth: theme.borderWidth.thick,
367
+ borderColor: theme.color.borderStrong
368
+ },
369
+ // The radio ring's selected edge, on the same footing as `planOn` above.
370
+ indicatorOn: { borderColor: theme.focus.ringColor },
371
+ indicatorDot: {
372
+ width: INDICATOR.dot,
373
+ height: INDICATOR.dot,
374
+ borderRadius: theme.radius.full,
375
+ backgroundColor: theme.color.accent
376
+ },
377
+ planMain: { flex: 1, minWidth: 0, gap: theme.space.s1 },
378
+ planTitle: {
379
+ fontFamily: theme.font.sans,
380
+ fontWeight: theme.fontWeight.bold,
381
+ fontSize: theme.type.subheadline.fontSize,
382
+ lineHeight: theme.type.subheadline.lineHeight,
383
+ letterSpacing: theme.type.subheadline.letterSpacing,
384
+ color: theme.color.text
385
+ },
386
+ planNote: {
387
+ fontFamily: theme.font.sans,
388
+ fontSize: theme.type.caption1.fontSize,
389
+ lineHeight: theme.type.caption1.lineHeight,
390
+ color: theme.color.text2
391
+ },
392
+ footer: {
393
+ padding: theme.layout.screenPad,
394
+ gap: theme.space.s2,
395
+ borderTopWidth: theme.borderWidth.base,
396
+ borderTopColor: theme.color.border,
397
+ backgroundColor: theme.color.bg
398
+ },
399
+ legal: {
400
+ fontFamily: theme.font.sans,
401
+ fontSize: theme.type.caption2.fontSize,
402
+ lineHeight: theme.type.caption2.fontSize * theme.lineHeight.normal,
403
+ color: theme.color.text3,
404
+ textAlign: "center"
405
+ },
406
+ links: {
407
+ flexDirection: "row",
408
+ justifyContent: "center",
409
+ flexWrap: "wrap",
410
+ gap: theme.space.s4
411
+ },
412
+ link: {
413
+ fontFamily: theme.font.sans,
414
+ fontSize: theme.type.footnote.fontSize,
415
+ lineHeight: theme.type.footnote.lineHeight,
416
+ color: theme.color.text2
417
+ },
418
+ // Badge hugs with `flex-start`, which pins it to the top of this centred row.
419
+ rowBadge: { alignSelf: "flex-start" }
420
+ }));
421
+ var WEB_BLUR = Platform.OS === "web";
422
+ var WEB_BLUR_STYLE = { filter: "blur(8px) saturate(0.6)", opacity: 0.7 };
423
+ var FADE_STEPS = [0.25, 0.5, 0.75, 0.92];
424
+ function PaywallGate({
425
+ children,
426
+ locked = true,
427
+ treatment = "blur",
428
+ title,
429
+ description,
430
+ planLabel,
431
+ actionLabel,
432
+ onAction,
433
+ secondaryLabel,
434
+ onSecondary,
435
+ previewHeight,
436
+ size = "regular",
437
+ BlurComponent,
438
+ style
439
+ }) {
440
+ const { styles, theme } = useStyles(sheet3);
441
+ if (!locked) return /* @__PURE__ */ jsx(View, { style, children });
442
+ const compact = size === "compact";
443
+ const fade = treatment === "fade";
444
+ const preview = previewHeight ?? theme.panel.xs;
445
+ const card = /* @__PURE__ */ jsxs(
446
+ View,
447
+ {
448
+ accessibilityRole: "summary",
449
+ accessibilityLabel: title,
450
+ style: [styles.card, compact ? styles.cardCompact : styles.cardRegular],
451
+ children: [
452
+ /* @__PURE__ */ jsx(View, { style: [styles.lock, compact ? styles.lockCompact : styles.lockRegular], children: /* @__PURE__ */ jsx(
453
+ Icon,
454
+ {
455
+ name: "lock",
456
+ size: compact ? theme.icon.sm : theme.icon.md,
457
+ color: theme.color.accentOnSoft
458
+ }
459
+ ) }),
460
+ /* @__PURE__ */ jsxs(View, { style: styles.copy, children: [
461
+ planLabel ? /* @__PURE__ */ jsx(Text, { style: styles.plan, children: planLabel }) : null,
462
+ /* @__PURE__ */ jsx(Text, { accessibilityRole: "header", style: styles.title, children: title }),
463
+ description ? /* @__PURE__ */ jsx(Text, { style: styles.description, children: description }) : null
464
+ ] }),
465
+ /* @__PURE__ */ jsxs(View, { style: styles.actions, children: [
466
+ /* @__PURE__ */ jsx(Button, { size: "sm", variant: "primary", onPress: onAction, children: actionLabel }),
467
+ secondaryLabel && onSecondary ? /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onPress: onSecondary, children: secondaryLabel }) : null
468
+ ] })
469
+ ]
470
+ }
471
+ );
472
+ return /* @__PURE__ */ jsxs(View, { style: [styles.root, style], children: [
473
+ /* @__PURE__ */ jsxs(
474
+ View,
475
+ {
476
+ pointerEvents: "none",
477
+ accessibilityElementsHidden: true,
478
+ importantForAccessibility: "no-hide-descendants",
479
+ "aria-hidden": true,
480
+ style: [
481
+ treatment === "dim" ? styles.dimmed : null,
482
+ treatment === "blur" && WEB_BLUR ? WEB_BLUR_STYLE : null,
483
+ fade ? { maxHeight: preview + theme.space.s8, overflow: "hidden" } : null
484
+ ],
485
+ children: [
486
+ children,
487
+ treatment === "blur" ? BlurComponent ? /* @__PURE__ */ jsx(BlurComponent, { intensity: 40, style: styles.fill }) : WEB_BLUR ? null : /* @__PURE__ */ jsx(View, { style: [styles.fill, styles.veil] }) : null,
488
+ fade ? /* @__PURE__ */ jsx(View, { style: styles.fadeBands, children: FADE_STEPS.map((opacity) => /* @__PURE__ */ jsx(View, { style: [styles.fadeBand, { opacity }] }, opacity)) }) : null
489
+ ]
490
+ }
491
+ ),
492
+ /* @__PURE__ */ jsx(View, { style: fade ? styles.slotAfter : styles.slotOver, children: card })
493
+ ] });
494
+ }
495
+ var sheet3 = createStyleSheet((theme) => ({
496
+ root: { position: "relative", overflow: "hidden" },
497
+ fill: { position: "absolute", top: 0, right: 0, bottom: 0, left: 0 },
498
+ // The frosted fallback: opaque enough that no row of sample text is
499
+ // legible through it, translucent enough that the shape still shows.
500
+ veil: { backgroundColor: theme.color.surface, opacity: 0.72 },
501
+ dimmed: { opacity: theme.state.disabledOpacity },
502
+ fadeBands: { position: "absolute", left: 0, right: 0, bottom: 0 },
503
+ fadeBand: { height: theme.space.s2, backgroundColor: theme.color.bg },
504
+ slotOver: {
505
+ position: "absolute",
506
+ top: 0,
507
+ right: 0,
508
+ bottom: 0,
509
+ left: 0,
510
+ alignItems: "center",
511
+ justifyContent: "center",
512
+ padding: theme.space.s4
513
+ },
514
+ slotAfter: { alignItems: "center", marginTop: -theme.space.s6, padding: theme.space.s4 },
515
+ card: {
516
+ width: "100%",
517
+ alignItems: "center",
518
+ gap: theme.space.s3,
519
+ borderRadius: theme.radius.lg,
520
+ borderWidth: theme.borderWidth.base,
521
+ borderColor: theme.color.border,
522
+ backgroundColor: theme.color.surface,
523
+ ...theme.shadow.md
524
+ },
525
+ cardRegular: { maxWidth: theme.panel.xl, padding: theme.space.s5 },
526
+ cardCompact: { maxWidth: theme.panel.lg, padding: theme.space.s4 },
527
+ lock: {
528
+ alignItems: "center",
529
+ justifyContent: "center",
530
+ borderRadius: theme.radius.full,
531
+ backgroundColor: theme.color.accentSoft
532
+ },
533
+ lockRegular: { width: theme.ctl.md, height: theme.ctl.md },
534
+ lockCompact: { width: theme.ctl.sm, height: theme.ctl.sm },
535
+ copy: { alignItems: "center", gap: theme.space.s1 },
536
+ plan: {
537
+ fontFamily: theme.font.sans,
538
+ fontWeight: theme.fontWeight.bold,
539
+ fontSize: theme.type.caption2.fontSize,
540
+ lineHeight: theme.type.caption2.lineHeight,
541
+ letterSpacing: theme.tracking.caps,
542
+ textTransform: "uppercase",
543
+ color: theme.color.accent
544
+ },
545
+ title: {
546
+ fontFamily: theme.font.sans,
547
+ fontWeight: theme.fontWeight.bold,
548
+ fontSize: theme.fontSize[14],
549
+ color: theme.color.text,
550
+ textAlign: "center"
551
+ },
552
+ description: {
553
+ fontFamily: theme.font.sans,
554
+ fontSize: theme.type.footnote.fontSize,
555
+ lineHeight: theme.type.footnote.fontSize * theme.lineHeight.normal,
556
+ color: theme.color.text2,
557
+ textAlign: "center"
558
+ },
559
+ actions: {
560
+ flexDirection: "row",
561
+ flexWrap: "wrap",
562
+ justifyContent: "center",
563
+ gap: theme.space.s2
564
+ }
565
+ }));
566
+ function MeterBar({
567
+ value,
568
+ max,
569
+ unit,
570
+ tone = "accent",
571
+ label,
572
+ caption,
573
+ size = "md",
574
+ animate = false,
575
+ accessibilityLabel,
576
+ style
577
+ }) {
578
+ const { styles } = useStyles(sheet4);
579
+ const readout = `${value.toLocaleString()} of ${max.toLocaleString()}${unit ? ` ${unit}` : ""}`;
580
+ const name = accessibilityLabel ?? (label ? `${label}: ${readout}` : readout);
581
+ return /* @__PURE__ */ jsxs(View, { style: [styles.block, style], children: [
582
+ label == null && unit == null ? null : /* @__PURE__ */ jsxs(View, { style: styles.readoutRow, children: [
583
+ label == null ? null : /* @__PURE__ */ jsx(Text, { style: styles.label, children: label }),
584
+ /* @__PURE__ */ jsx(View, { style: styles.pushEnd, children: /* @__PURE__ */ jsx(Text, { style: styles.readout, children: readout }) })
585
+ ] }),
586
+ /* @__PURE__ */ jsx(
587
+ Progress,
588
+ {
589
+ value,
590
+ max,
591
+ size,
592
+ tone,
593
+ animateValue: animate,
594
+ accessibilityLabel: name
595
+ }
596
+ ),
597
+ caption ? /* @__PURE__ */ jsx(Text, { style: styles.caption, children: caption }) : null
598
+ ] });
599
+ }
600
+ var sheet4 = createStyleSheet((theme) => ({
601
+ block: { alignSelf: "stretch", gap: theme.space.s1 },
602
+ readoutRow: {
603
+ flexDirection: "row",
604
+ flexWrap: "wrap",
605
+ alignItems: "baseline",
606
+ gap: theme.space.s2
607
+ },
608
+ label: {
609
+ flexShrink: 1,
610
+ fontFamily: theme.font.sans,
611
+ fontSize: theme.type.footnote.fontSize,
612
+ lineHeight: theme.type.footnote.lineHeight,
613
+ color: theme.color.text2
614
+ },
615
+ pushEnd: { marginStart: "auto" },
616
+ readout: {
617
+ fontFamily: theme.font.sans,
618
+ fontWeight: theme.fontWeight.bold,
619
+ fontVariant: ["tabular-nums"],
620
+ fontSize: theme.type.footnote.fontSize,
621
+ lineHeight: theme.type.footnote.lineHeight,
622
+ color: theme.color.text
623
+ },
624
+ caption: {
625
+ fontFamily: theme.font.sans,
626
+ fontSize: theme.type.caption1.fontSize,
627
+ lineHeight: theme.type.caption1.fontSize * theme.lineHeight.normal,
628
+ color: theme.color.text3
629
+ }
630
+ }));
631
+ function SavingsChip({
632
+ savingMinor,
633
+ plan,
634
+ currency,
635
+ label = "Save",
636
+ per = "a year",
637
+ locale,
638
+ style
639
+ }) {
640
+ const theme = useLoamTheme();
641
+ const { styles } = useStyles(sheet5);
642
+ const saving = savingMinor ?? (plan ? yearlySaving(plan) : null);
643
+ if (saving == null) return null;
644
+ return /* @__PURE__ */ jsxs(View, { style: [styles.savingChip, style], children: [
645
+ /* @__PURE__ */ jsx(Text, { style: styles.savingWord, children: label }),
646
+ /* @__PURE__ */ jsx(
647
+ Amount,
648
+ {
649
+ minor: saving,
650
+ currency,
651
+ size: "sm",
652
+ weight: "bold",
653
+ locale,
654
+ style: { color: theme.color.goodOnSoft }
655
+ }
656
+ ),
657
+ /* @__PURE__ */ jsx(Text, { style: styles.savingWord, children: per })
658
+ ] });
659
+ }
660
+ function PlanPriceDelta({
661
+ fromMinor,
662
+ toMinor,
663
+ currency,
664
+ unit = PLAN_COPY.perSeat,
665
+ sameLabel = "Same price",
666
+ locale,
667
+ style
668
+ }) {
669
+ const { styles } = useStyles(sheet5);
670
+ const delta = planDelta(fromMinor, toMinor);
671
+ if (delta === 0) {
672
+ return /* @__PURE__ */ jsx(Text, { style: [styles.deltaUnit, style], children: sameLabel });
673
+ }
674
+ return /* @__PURE__ */ jsxs(View, { style: [styles.deltaRow, style], children: [
675
+ /* @__PURE__ */ jsx(
676
+ Amount,
677
+ {
678
+ minor: delta,
679
+ currency,
680
+ size: "sm",
681
+ weight: "bold",
682
+ signed: true,
683
+ tone: delta > 0 ? "warn" : "good",
684
+ locale
685
+ }
686
+ ),
687
+ /* @__PURE__ */ jsx(Text, { style: styles.deltaUnit, children: unit })
688
+ ] });
689
+ }
690
+ function PlanFeatureRow({
691
+ label,
692
+ included = true,
693
+ detail,
694
+ stateLabel,
695
+ style
696
+ }) {
697
+ const theme = useLoamTheme();
698
+ const { styles } = useStyles(sheet5);
699
+ const state = stateLabel ?? (included ? "Included" : "Not included");
700
+ return /* @__PURE__ */ jsxs(
701
+ View,
702
+ {
703
+ role: "listitem",
704
+ accessibilityLabel: `${state}: ${label}${detail ? `. ${detail}` : ""}`,
705
+ style: [styles.featureRow, style],
706
+ children: [
707
+ /* @__PURE__ */ jsx(
708
+ Icon,
709
+ {
710
+ name: included ? "check" : "minus",
711
+ size: theme.icon.sm,
712
+ color: included ? theme.color.good : theme.color.text3
713
+ }
714
+ ),
715
+ /* @__PURE__ */ jsxs(View, { style: styles.featureBody, children: [
716
+ /* @__PURE__ */ jsx(Text, { style: included ? styles.featureLabel : styles.featureLabelOut, children: label }),
717
+ detail ? /* @__PURE__ */ jsx(Text, { style: styles.featureDetail, children: detail }) : null
718
+ ] })
719
+ ]
720
+ }
721
+ );
722
+ }
723
+ function PlanPriceCell({
724
+ plan,
725
+ currency,
726
+ cadence = "mo",
727
+ size = "lg",
728
+ savingMinor,
729
+ locale,
730
+ style
731
+ }) {
732
+ const { styles } = useStyles(sheet5);
733
+ const words = planCta(plan).price;
734
+ const perMonth = yearlyMonthly(plan);
735
+ const yearly = cadence === "yr" && plan.yearly != null ? plan.yearly : null;
736
+ const headline = yearly != null && perMonth != null ? perMonth : plan.price;
737
+ return /* @__PURE__ */ jsxs(View, { style: [styles.priceCell, style], children: [
738
+ /* @__PURE__ */ jsx(View, { style: styles.priceHeadline, children: words ? /* @__PURE__ */ jsx(Text, { style: size === "hero" ? styles.priceWordsHero : styles.priceWords, children: words }) : headline == null ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
739
+ /* @__PURE__ */ jsx(Amount, { minor: headline, currency, size, weight: "bold", locale }),
740
+ /* @__PURE__ */ jsx(Text, { style: styles.priceUnit, children: PLAN_COPY.perSeat })
741
+ ] }) }),
742
+ yearly == null ? null : /* @__PURE__ */ jsxs(View, { style: styles.yearlyRow, children: [
743
+ /* @__PURE__ */ jsx(Amount, { minor: yearly, currency, size: "sm", weight: "bold", locale }),
744
+ /* @__PURE__ */ jsx(Text, { style: styles.priceUnit, children: "billed yearly" }),
745
+ /* @__PURE__ */ jsx(
746
+ SavingsChip,
747
+ {
748
+ savingMinor,
749
+ plan,
750
+ currency,
751
+ locale
752
+ }
753
+ )
754
+ ] })
755
+ ] });
756
+ }
757
+ function PlanCard({
758
+ plan,
759
+ currency,
760
+ cadence = "mo",
761
+ highlighted,
762
+ isCurrent = false,
763
+ savingMinor,
764
+ ctaLabel,
765
+ currentLabel = "Your plan",
766
+ onSelect,
767
+ locale,
768
+ style
769
+ }) {
770
+ const theme = useLoamTheme();
771
+ const { styles } = useStyles(sheet5);
772
+ const recommended = highlighted ?? plan.popular ?? false;
773
+ return /* @__PURE__ */ jsxs(
774
+ View,
775
+ {
776
+ testID: `plan-card-${plan.id}`,
777
+ style: [
778
+ styles.card,
779
+ recommended ? styles.cardRecommended : styles.cardPlain,
780
+ // `current` adds the accent tint so "this one is yours" survives next to
781
+ // a highlighted neighbour.
782
+ isCurrent ? styles.cardCurrent : null,
783
+ style
784
+ ],
785
+ children: [
786
+ /* @__PURE__ */ jsxs(View, { style: styles.cardHead, children: [
787
+ /* @__PURE__ */ jsx(Text, { accessibilityRole: "header", style: styles.planName, children: plan.name }),
788
+ recommended ? /* @__PURE__ */ jsx(Badge, { style: styles.rowBadge, variant: "accent", size: "sm", children: PLAN_COPY.popularBadge }) : null,
789
+ isCurrent ? /* @__PURE__ */ jsx(Badge, { variant: "neutral", size: "sm", outline: true, children: currentLabel }) : null
790
+ ] }),
791
+ /* @__PURE__ */ jsx(
792
+ PlanPriceCell,
793
+ {
794
+ plan,
795
+ currency,
796
+ cadence,
797
+ savingMinor,
798
+ locale
799
+ }
800
+ ),
801
+ /* @__PURE__ */ jsx(Text, { style: styles.blurb, children: plan.blurb }),
802
+ /* @__PURE__ */ jsx(View, { style: styles.rule }),
803
+ /* @__PURE__ */ jsx(View, { role: "list", accessibilityRole: "list", style: styles.featureList, children: plan.features.map((feature) => /* @__PURE__ */ jsx(PlanFeatureRow, { label: feature }, feature)) }),
804
+ isCurrent ? (
805
+ // Not a disabled button: a dead control invites the press that teaches
806
+ // nothing. The marker states the fact and takes no tab stop.
807
+ /* @__PURE__ */ jsxs(View, { style: styles.currentMarker, children: [
808
+ /* @__PURE__ */ jsx(Icon, { name: "check-circle", size: theme.icon.sm, color: theme.color.good }),
809
+ /* @__PURE__ */ jsx(Text, { style: styles.currentMarkerLabel, children: currentLabel })
810
+ ] })
811
+ ) : /* @__PURE__ */ jsx(
812
+ Button,
813
+ {
814
+ fullWidth: true,
815
+ variant: recommended ? "primary" : "outline",
816
+ onPress: () => onSelect?.(plan.id),
817
+ children: ctaLabel ?? planCta(plan).cta
818
+ }
819
+ )
820
+ ]
821
+ }
822
+ );
823
+ }
824
+ function BillingPeriodToggle({
825
+ value,
826
+ onValueChange,
827
+ monthlyLabel = PLAN_COPY.cadenceMonthly,
828
+ yearlyLabel = PLAN_COPY.cadenceYearly,
829
+ size = "md",
830
+ savingMinor,
831
+ currency,
832
+ locale,
833
+ style
834
+ }) {
835
+ const { styles } = useStyles(sheet5);
836
+ return /* @__PURE__ */ jsxs(View, { style: [styles.toggleRow, style], children: [
837
+ /* @__PURE__ */ jsx(
838
+ SegmentedControl,
839
+ {
840
+ size,
841
+ value,
842
+ onValueChange: (next) => onValueChange(next),
843
+ items: [
844
+ { value: "mo", label: monthlyLabel },
845
+ { value: "yr", label: yearlyLabel }
846
+ ]
847
+ }
848
+ ),
849
+ savingMinor != null && currency ? /* @__PURE__ */ jsx(SavingsChip, { savingMinor, currency, locale }) : null
850
+ ] });
851
+ }
852
+ function PlanGrid({
853
+ plans,
854
+ currency,
855
+ cadence,
856
+ onCadenceChange,
857
+ onSelect,
858
+ currentPlanId,
859
+ savings,
860
+ hideIds,
861
+ locale,
862
+ style
863
+ }) {
864
+ const { styles } = useStyles(sheet5);
865
+ const shown = hideIds?.length ? plans.filter((plan) => !hideIds.includes(plan.id)) : plans;
866
+ return /* @__PURE__ */ jsxs(View, { style: [styles.grid, style], children: [
867
+ onCadenceChange ? /* @__PURE__ */ jsx(
868
+ BillingPeriodToggle,
869
+ {
870
+ value: cadence,
871
+ onValueChange: onCadenceChange,
872
+ currency,
873
+ locale
874
+ }
875
+ ) : null,
876
+ /* @__PURE__ */ jsx(View, { style: styles.gridStack, children: shown.map((plan) => /* @__PURE__ */ jsx(
877
+ PlanCard,
878
+ {
879
+ plan,
880
+ currency,
881
+ cadence,
882
+ isCurrent: currentPlanId === plan.id,
883
+ savingMinor: savings?.[plan.id],
884
+ onSelect,
885
+ locale
886
+ },
887
+ plan.id
888
+ )) })
889
+ ] });
890
+ }
891
+ var sheet5 = createStyleSheet((theme) => ({
892
+ savingChip: {
893
+ flexDirection: "row",
894
+ alignItems: "baseline",
895
+ alignSelf: "flex-start",
896
+ gap: theme.space.s1,
897
+ paddingHorizontal: theme.space.s2,
898
+ paddingVertical: theme.space[2],
899
+ borderRadius: theme.radius.full,
900
+ // A soft tint needs its tone edge: the fill collapses to the plain surface
901
+ // under high contrast, where the edge is the only thing drawing it.
902
+ borderWidth: theme.borderWidth.base,
903
+ borderColor: theme.edge.good,
904
+ backgroundColor: theme.color.goodSoft
905
+ },
906
+ savingWord: {
907
+ fontFamily: theme.font.sans,
908
+ fontSize: theme.type.caption1.fontSize,
909
+ lineHeight: theme.type.caption1.lineHeight,
910
+ color: theme.color.goodOnSoft
911
+ },
912
+ deltaRow: {
913
+ flexDirection: "row",
914
+ alignItems: "baseline",
915
+ alignSelf: "flex-start",
916
+ gap: theme.space.s1
917
+ },
918
+ deltaUnit: {
919
+ fontFamily: theme.font.sans,
920
+ fontSize: theme.type.footnote.fontSize,
921
+ lineHeight: theme.type.footnote.lineHeight,
922
+ color: theme.color.text3
923
+ },
924
+ featureRow: { flexDirection: "row", alignItems: "flex-start", gap: theme.space.s2 },
925
+ featureBody: { flex: 1, gap: theme.space[2] },
926
+ featureLabel: {
927
+ fontFamily: theme.font.sans,
928
+ fontSize: theme.type.footnote.fontSize,
929
+ lineHeight: theme.type.footnote.fontSize * theme.lineHeight.normal,
930
+ color: theme.color.text2
931
+ },
932
+ featureLabelOut: {
933
+ fontFamily: theme.font.sans,
934
+ fontSize: theme.type.footnote.fontSize,
935
+ lineHeight: theme.type.footnote.fontSize * theme.lineHeight.normal,
936
+ color: theme.color.text3
937
+ },
938
+ featureDetail: {
939
+ fontFamily: theme.font.sans,
940
+ fontSize: theme.type.caption1.fontSize,
941
+ lineHeight: theme.type.caption1.lineHeight,
942
+ color: theme.color.text3
943
+ },
944
+ priceCell: { gap: theme.space.s1 },
945
+ priceHeadline: {
946
+ flexDirection: "row",
947
+ flexWrap: "wrap",
948
+ alignItems: "baseline",
949
+ columnGap: theme.space.s2,
950
+ rowGap: theme.space.s1
951
+ },
952
+ priceWords: {
953
+ fontFamily: theme.font.sans,
954
+ fontWeight: theme.fontWeight.bold,
955
+ fontSize: theme.type.title3.fontSize,
956
+ lineHeight: theme.type.title3.lineHeight,
957
+ color: theme.color.text
958
+ },
959
+ priceWordsHero: {
960
+ fontFamily: theme.font.sans,
961
+ fontWeight: theme.fontWeight.bold,
962
+ fontSize: theme.fontSize[32],
963
+ lineHeight: theme.fontSize[32] * theme.lineHeight.tight,
964
+ color: theme.color.text
965
+ },
966
+ priceUnit: {
967
+ fontFamily: theme.font.sans,
968
+ fontSize: theme.type.footnote.fontSize,
969
+ lineHeight: theme.type.footnote.lineHeight,
970
+ color: theme.color.text3
971
+ },
972
+ yearlyRow: {
973
+ flexDirection: "row",
974
+ flexWrap: "wrap",
975
+ alignItems: "baseline",
976
+ gap: theme.space.s2
977
+ },
978
+ card: {
979
+ alignSelf: "stretch",
980
+ gap: theme.space.s4,
981
+ padding: theme.space.s5,
982
+ borderRadius: theme.radius.lg,
983
+ backgroundColor: theme.color.surface
984
+ },
985
+ cardPlain: { borderWidth: theme.borderWidth.base, borderColor: theme.color.border },
986
+ // `focus.ringColor`, not the raw accent: this border is what marks the
987
+ // recommended plan, and it is a graphical object under SC 1.4.11 either way.
988
+ // Identical to the raw accent in 9 of the 14 palette × mode combos, so it
989
+ // moves only the four that failed. Same call `paywall.tsx` makes for `planOn`.
990
+ cardRecommended: {
991
+ borderWidth: theme.borderWidth.heavy,
992
+ borderColor: theme.focus.ringColor,
993
+ ...theme.shadow.md
994
+ },
995
+ cardCurrent: { backgroundColor: theme.color.accentSoft, borderColor: theme.edge.accent },
996
+ cardHead: {
997
+ flexDirection: "row",
998
+ flexWrap: "wrap",
999
+ alignItems: "center",
1000
+ gap: theme.space.s2
1001
+ },
1002
+ planName: {
1003
+ fontFamily: theme.font.sans,
1004
+ fontWeight: theme.fontWeight.bold,
1005
+ fontSize: theme.type.subheadline.fontSize,
1006
+ lineHeight: theme.type.subheadline.lineHeight,
1007
+ color: theme.color.text
1008
+ },
1009
+ blurb: {
1010
+ fontFamily: theme.font.sans,
1011
+ fontSize: theme.type.footnote.fontSize,
1012
+ lineHeight: theme.type.footnote.fontSize * theme.lineHeight.normal,
1013
+ color: theme.color.text2
1014
+ },
1015
+ rule: { height: theme.borderWidth.base, backgroundColor: theme.color.border },
1016
+ featureList: { gap: theme.space.s2 },
1017
+ currentMarker: {
1018
+ flexDirection: "row",
1019
+ alignItems: "center",
1020
+ justifyContent: "center",
1021
+ gap: theme.space.s1,
1022
+ paddingVertical: theme.space.s2,
1023
+ borderRadius: theme.radius.md,
1024
+ borderWidth: theme.borderWidth.base,
1025
+ borderColor: theme.color.border
1026
+ },
1027
+ currentMarkerLabel: {
1028
+ fontFamily: theme.font.sans,
1029
+ fontWeight: theme.fontWeight.bold,
1030
+ fontSize: theme.type.footnote.fontSize,
1031
+ lineHeight: theme.type.footnote.lineHeight,
1032
+ color: theme.color.text2
1033
+ },
1034
+ toggleRow: {
1035
+ flexDirection: "row",
1036
+ flexWrap: "wrap",
1037
+ alignItems: "center",
1038
+ justifyContent: "center",
1039
+ gap: theme.space.s3
1040
+ },
1041
+ grid: { alignSelf: "stretch", gap: theme.space.s5 },
1042
+ gridStack: { gap: theme.space.s4 },
1043
+ // Badge hugs with `flex-start`, which pins it to the top of this centred row.
1044
+ rowBadge: { alignSelf: "center" }
1045
+ }));
1046
+ function prorationWay(proration) {
1047
+ return proration.dueNow === 0 && proration.refund > 0 ? "downgrade" : "upgrade";
1048
+ }
1049
+ function ProrationLine({
1050
+ proration,
1051
+ currency,
1052
+ effectiveLabel,
1053
+ nextMinor,
1054
+ nextCadenceLabel = "monthly",
1055
+ locale,
1056
+ style
1057
+ }) {
1058
+ const { styles } = useStyles(sheet6);
1059
+ const downgrade = prorationWay(proration) === "downgrade";
1060
+ return /* @__PURE__ */ jsxs(View, { style: [styles.prorationLine, style], children: [
1061
+ /* @__PURE__ */ jsx(
1062
+ Amount,
1063
+ {
1064
+ minor: downgrade ? proration.refund : proration.dueNow,
1065
+ currency,
1066
+ size: "sm",
1067
+ weight: "bold",
1068
+ tone: downgrade ? "good" : "default",
1069
+ locale
1070
+ }
1071
+ ),
1072
+ /* @__PURE__ */ jsx(Text, { style: styles.prorationWord, children: downgrade ? "credited to your next invoice" : "today" }),
1073
+ nextMinor == null ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
1074
+ /* @__PURE__ */ jsx(Text, { style: styles.prorationSep, children: "\xB7" }),
1075
+ /* @__PURE__ */ jsx(Text, { style: styles.prorationWord, children: "then" }),
1076
+ /* @__PURE__ */ jsx(Amount, { minor: nextMinor, currency, size: "sm", weight: "bold", locale }),
1077
+ /* @__PURE__ */ jsx(Text, { style: styles.prorationWord, children: `${nextCadenceLabel} from ${effectiveLabel}` })
1078
+ ] })
1079
+ ] });
1080
+ }
1081
+ function ProrationTable({
1082
+ proration,
1083
+ currency,
1084
+ fromName,
1085
+ toName,
1086
+ daysLeft = proration.daysLeft,
1087
+ cycleDays = proration.cycleDays,
1088
+ effectiveLabel,
1089
+ nextMinor,
1090
+ nextCadenceLabel = "monthly",
1091
+ caption,
1092
+ note = PLAN_COPY.prorationNote,
1093
+ locale,
1094
+ style
1095
+ }) {
1096
+ const { styles } = useStyles(sheet6);
1097
+ const downgrade = prorationWay(proration) === "downgrade";
1098
+ return /* @__PURE__ */ jsxs(View, { style: [styles.prorationTable, style], children: [
1099
+ /* @__PURE__ */ jsx(Text, { style: styles.overline, children: caption ?? `Mid-cycle change \xB7 ${currency}` }),
1100
+ /* @__PURE__ */ jsx(
1101
+ PriceRow,
1102
+ {
1103
+ label: `Unused ${fromName}, ${daysLeft} of ${cycleDays} days`,
1104
+ minor: -proration.credit,
1105
+ currency,
1106
+ tone: "good",
1107
+ noSymbol: true,
1108
+ locale
1109
+ }
1110
+ ),
1111
+ /* @__PURE__ */ jsx(
1112
+ PriceRow,
1113
+ {
1114
+ label: `${toName} for the same ${daysLeft} days`,
1115
+ minor: proration.charge,
1116
+ currency,
1117
+ noSymbol: true,
1118
+ locale
1119
+ }
1120
+ ),
1121
+ /* @__PURE__ */ jsx(
1122
+ PriceRow,
1123
+ {
1124
+ label: downgrade ? "Credit on your next invoice" : "Charged today",
1125
+ minor: downgrade ? proration.refund : proration.dueNow,
1126
+ currency,
1127
+ tone: downgrade ? "good" : void 0,
1128
+ strong: true,
1129
+ rule: true,
1130
+ noSymbol: true,
1131
+ locale
1132
+ }
1133
+ ),
1134
+ nextMinor == null ? null : /* @__PURE__ */ jsx(
1135
+ PriceRow,
1136
+ {
1137
+ label: `From ${effectiveLabel}, ${nextCadenceLabel}`,
1138
+ minor: nextMinor,
1139
+ currency,
1140
+ noSymbol: true,
1141
+ locale
1142
+ }
1143
+ ),
1144
+ note === false ? null : /* @__PURE__ */ jsx(Text, { style: styles.prorationNote, children: note })
1145
+ ] });
1146
+ }
1147
+ function toPaywallPlan(plan, currency, cadence, savingMinor, isCurrent, currentLabel, locale) {
1148
+ const words = planCta(plan).price;
1149
+ const perMonth = yearlyMonthly(plan);
1150
+ const yearly = cadence === "yr" && plan.yearly != null ? plan.yearly : null;
1151
+ const headline = yearly != null && perMonth != null ? perMonth : plan.price;
1152
+ const saving = savingMinor ?? yearlySaving(plan);
1153
+ const notes = [];
1154
+ if (yearly != null) notes.push(`${money(yearly, currency, { locale })} billed yearly`);
1155
+ if (yearly != null && saving != null) {
1156
+ notes.push(`Save ${money(saving, currency, { locale })} a year`);
1157
+ }
1158
+ notes.push(plan.blurb);
1159
+ if (isCurrent) notes.push(currentLabel);
1160
+ return {
1161
+ id: plan.id,
1162
+ name: plan.name,
1163
+ price: words || (headline == null ? "" : money(headline, currency, { locale })),
1164
+ period: words ? "" : PLAN_COPY.perSeat,
1165
+ note: notes.join(" \xB7 "),
1166
+ recommended: plan.popular ?? false,
1167
+ recommendedLabel: PLAN_COPY.popularBadge
1168
+ };
1169
+ }
1170
+ function PaywallSurface({
1171
+ variant = "hard",
1172
+ title,
1173
+ subtitle,
1174
+ meter,
1175
+ plans,
1176
+ currency,
1177
+ cadence,
1178
+ onCadenceChange,
1179
+ onPickPlan,
1180
+ currentPlanId,
1181
+ currentLabel = "Your plan",
1182
+ savings,
1183
+ hideIds,
1184
+ featureOffer,
1185
+ billing = "ours",
1186
+ platformNote = "Billed by the app store on your account. Prices come from its catalogue.",
1187
+ legalText,
1188
+ footNote,
1189
+ dismissLabel = "Not now",
1190
+ onDismiss,
1191
+ restoreLabel = "Restore purchases",
1192
+ onRestore,
1193
+ upgradeLabel,
1194
+ onUpgrade,
1195
+ locale,
1196
+ style
1197
+ }) {
1198
+ const { styles } = useStyles(sheet6);
1199
+ const canDismiss = variant !== "hard" && Boolean(onDismiss);
1200
+ const showMeter = (variant === "metered" || variant === "trial") && meter != null;
1201
+ const offer = variant === "feature" ? featureOffer : void 0;
1202
+ const shown = React6.useMemo(() => {
1203
+ const list = plans ?? [];
1204
+ return hideIds?.length ? list.filter((p) => !hideIds.includes(p.id)) : list;
1205
+ }, [plans, hideIds]);
1206
+ const [selected, setSelected] = React6.useState(
1207
+ () => shown.find((p) => p.popular)?.id ?? shown[0]?.id
1208
+ );
1209
+ const selectedPlan = shown.find((p) => p.id === selected);
1210
+ const footer = /* @__PURE__ */ jsx(View, { style: styles.footerNotes, children: footNote ? /* @__PURE__ */ jsx(Text, { style: styles.footNote, children: footNote }) : null });
1211
+ const slot = /* @__PURE__ */ jsxs(View, { style: styles.slot, children: [
1212
+ billing === "platform" ? /* @__PURE__ */ jsx(Text, { style: styles.platformNote, children: platformNote }) : null,
1213
+ showMeter && meter ? /* @__PURE__ */ jsx(MeterBar, { ...meter }) : null,
1214
+ onCadenceChange ? /* @__PURE__ */ jsx(BillingPeriodToggle, { value: cadence, onValueChange: onCadenceChange }) : null
1215
+ ] });
1216
+ if (offer) {
1217
+ return /* @__PURE__ */ jsxs(View, { role: "region", accessibilityLabel: title, style: [styles.featureSurface, style], children: [
1218
+ /* @__PURE__ */ jsxs(View, { style: styles.featureHead, children: [
1219
+ /* @__PURE__ */ jsx(Text, { accessibilityRole: "header", style: styles.featureTitle, children: title }),
1220
+ subtitle ? /* @__PURE__ */ jsx(Text, { style: styles.featureSubtitle, children: subtitle }) : null
1221
+ ] }),
1222
+ billing === "platform" ? /* @__PURE__ */ jsx(Text, { style: styles.platformNote, children: platformNote }) : null,
1223
+ /* @__PURE__ */ jsx(
1224
+ PlanCard,
1225
+ {
1226
+ plan: offer.plan,
1227
+ currency,
1228
+ cadence,
1229
+ savingMinor: offer.savingMinor,
1230
+ highlighted: true,
1231
+ ctaLabel: upgradeLabel,
1232
+ onSelect: () => {
1233
+ onUpgrade?.();
1234
+ onPickPlan?.(offer.plan.id);
1235
+ },
1236
+ locale
1237
+ }
1238
+ ),
1239
+ offer.proration && offer.effectiveLabel ? /* @__PURE__ */ jsx(
1240
+ ProrationLine,
1241
+ {
1242
+ proration: offer.proration,
1243
+ currency,
1244
+ effectiveLabel: offer.effectiveLabel,
1245
+ nextMinor: offer.nextMinor,
1246
+ locale
1247
+ }
1248
+ ) : null,
1249
+ /* @__PURE__ */ jsx(Text, { style: styles.legal, children: legalText }),
1250
+ footNote ? /* @__PURE__ */ jsx(Text, { style: styles.footNote, children: footNote }) : null,
1251
+ /* @__PURE__ */ jsxs(View, { style: styles.featureActions, children: [
1252
+ /* @__PURE__ */ jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: restoreLabel, onPress: onRestore, children: /* @__PURE__ */ jsx(Text, { style: styles.link, children: restoreLabel }) }),
1253
+ canDismiss ? /* @__PURE__ */ jsx(
1254
+ Pressable,
1255
+ {
1256
+ accessibilityRole: "button",
1257
+ accessibilityLabel: dismissLabel,
1258
+ onPress: onDismiss,
1259
+ children: /* @__PURE__ */ jsx(Text, { style: styles.link, children: dismissLabel })
1260
+ }
1261
+ ) : null
1262
+ ] })
1263
+ ] });
1264
+ }
1265
+ return /* @__PURE__ */ jsx(View, { style: [styles.surface, style], children: /* @__PURE__ */ jsx(
1266
+ Paywall,
1267
+ {
1268
+ title,
1269
+ description: subtitle,
1270
+ meter: slot,
1271
+ plans: shown.map(
1272
+ (plan) => toPaywallPlan(
1273
+ plan,
1274
+ currency,
1275
+ cadence,
1276
+ savings?.[plan.id],
1277
+ currentPlanId === plan.id,
1278
+ currentLabel,
1279
+ locale
1280
+ )
1281
+ ),
1282
+ selectedPlanId: selected,
1283
+ onSelectPlan: setSelected,
1284
+ ctaLabel: selectedPlan ? planCta(selectedPlan).cta : restoreLabel,
1285
+ onPurchase: (id) => onPickPlan?.(id),
1286
+ legalText,
1287
+ footNote: footer,
1288
+ restoreLabel,
1289
+ onRestore,
1290
+ onClose: canDismiss ? onDismiss : void 0,
1291
+ closeLabel: dismissLabel
1292
+ }
1293
+ ) });
1294
+ }
1295
+ var sheet6 = createStyleSheet((theme) => ({
1296
+ surface: { flex: 1 },
1297
+ slot: { gap: theme.space.s3 },
1298
+ platformNote: {
1299
+ fontFamily: theme.font.sans,
1300
+ fontSize: theme.type.footnote.fontSize,
1301
+ lineHeight: theme.type.footnote.fontSize * theme.lineHeight.normal,
1302
+ color: theme.color.text2,
1303
+ textAlign: "center"
1304
+ },
1305
+ footerNotes: { gap: theme.space.s1 },
1306
+ footNote: {
1307
+ fontFamily: theme.font.sans,
1308
+ fontSize: theme.type.caption2.fontSize,
1309
+ lineHeight: theme.type.caption2.fontSize * theme.lineHeight.normal,
1310
+ color: theme.color.text3,
1311
+ textAlign: "center"
1312
+ },
1313
+ legal: {
1314
+ fontFamily: theme.font.sans,
1315
+ fontSize: theme.type.caption2.fontSize,
1316
+ lineHeight: theme.type.caption2.fontSize * theme.lineHeight.normal,
1317
+ color: theme.color.text3,
1318
+ textAlign: "center"
1319
+ },
1320
+ link: {
1321
+ fontFamily: theme.font.sans,
1322
+ fontSize: theme.type.footnote.fontSize,
1323
+ lineHeight: theme.type.footnote.lineHeight,
1324
+ color: theme.color.text2
1325
+ },
1326
+ featureSurface: {
1327
+ alignSelf: "stretch",
1328
+ gap: theme.space.s4,
1329
+ padding: theme.space.s5,
1330
+ borderRadius: theme.radius.lg,
1331
+ borderWidth: theme.borderWidth.base,
1332
+ borderColor: theme.color.border,
1333
+ backgroundColor: theme.color.surface
1334
+ },
1335
+ featureHead: { alignItems: "center", gap: theme.space.s2 },
1336
+ featureTitle: {
1337
+ fontFamily: theme.font.display,
1338
+ fontWeight: theme.font.displayWeight,
1339
+ fontSize: theme.type.title2.fontSize,
1340
+ lineHeight: theme.type.title2.lineHeight,
1341
+ letterSpacing: theme.type.title2.letterSpacing,
1342
+ color: theme.color.text,
1343
+ textAlign: "center"
1344
+ },
1345
+ featureSubtitle: {
1346
+ fontFamily: theme.font.sans,
1347
+ fontSize: theme.type.body.fontSize,
1348
+ lineHeight: theme.type.body.fontSize * theme.lineHeight.normal,
1349
+ color: theme.color.text2,
1350
+ textAlign: "center"
1351
+ },
1352
+ featureActions: {
1353
+ flexDirection: "row",
1354
+ flexWrap: "wrap",
1355
+ alignItems: "center",
1356
+ justifyContent: "center",
1357
+ gap: theme.space.s4
1358
+ },
1359
+ prorationLine: {
1360
+ flexDirection: "row",
1361
+ flexWrap: "wrap",
1362
+ alignItems: "baseline",
1363
+ columnGap: theme.space.s1,
1364
+ rowGap: theme.space[2]
1365
+ },
1366
+ prorationWord: {
1367
+ fontFamily: theme.font.sans,
1368
+ fontSize: theme.type.footnote.fontSize,
1369
+ lineHeight: theme.type.footnote.lineHeight,
1370
+ color: theme.color.text2
1371
+ },
1372
+ prorationSep: {
1373
+ fontFamily: theme.font.sans,
1374
+ fontSize: theme.type.footnote.fontSize,
1375
+ lineHeight: theme.type.footnote.lineHeight,
1376
+ color: theme.color.text3
1377
+ },
1378
+ prorationTable: {
1379
+ alignSelf: "stretch",
1380
+ gap: theme.space.s2,
1381
+ padding: theme.space.s4,
1382
+ borderRadius: theme.radius.md,
1383
+ borderWidth: theme.borderWidth.base,
1384
+ borderColor: theme.color.border,
1385
+ backgroundColor: theme.color.surface2
1386
+ },
1387
+ overline: {
1388
+ fontFamily: theme.font.sans,
1389
+ fontWeight: theme.fontWeight.bold,
1390
+ fontSize: theme.type.caption2.fontSize,
1391
+ lineHeight: theme.type.caption2.lineHeight,
1392
+ letterSpacing: theme.tracking.caps,
1393
+ textTransform: "uppercase",
1394
+ color: theme.color.text3
1395
+ },
1396
+ prorationNote: {
1397
+ fontFamily: theme.font.sans,
1398
+ fontSize: theme.type.caption1.fontSize,
1399
+ lineHeight: theme.type.caption1.fontSize * theme.lineHeight.normal,
1400
+ color: theme.color.text3
1401
+ }
1402
+ }));
1403
+ var STATUS = {
1404
+ active: { variant: "good", label: "Active" },
1405
+ trial: { variant: "info", label: "Trial" },
1406
+ canceled: { variant: "neutral", label: "Canceled" },
1407
+ "past-due": { variant: "warn", label: "Past due" }
1408
+ };
1409
+ function ManageSubscription({
1410
+ planName,
1411
+ status = "active",
1412
+ statusLabel,
1413
+ price,
1414
+ renewalDate,
1415
+ renewalLabel = "renews",
1416
+ paymentMethod,
1417
+ paymentLabel = "Payment",
1418
+ actions = [],
1419
+ onAction,
1420
+ cancelLabel = "Cancel subscription",
1421
+ onCancel,
1422
+ cancelNote
1423
+ }) {
1424
+ const { styles } = useStyles(sheet7);
1425
+ const theme = useLoamTheme();
1426
+ const badge = STATUS[status];
1427
+ return /* @__PURE__ */ jsxs(ScrollView, { style: styles.screen, contentContainerStyle: styles.scroll, children: [
1428
+ /* @__PURE__ */ jsxs(View, { style: styles.card, children: [
1429
+ /* @__PURE__ */ jsxs(View, { style: styles.cardHead, children: [
1430
+ /* @__PURE__ */ jsx(Text, { style: styles.planName, children: planName }),
1431
+ /* @__PURE__ */ jsx(Badge, { style: styles.rowBadge, variant: badge.variant, size: "sm", children: statusLabel ?? badge.label })
1432
+ ] }),
1433
+ /* @__PURE__ */ jsxs(Text, { style: styles.meta, children: [
1434
+ price,
1435
+ " \xB7 ",
1436
+ renewalLabel,
1437
+ " ",
1438
+ renewalDate
1439
+ ] }),
1440
+ paymentMethod ? /* @__PURE__ */ jsxs(Text, { style: styles.meta, children: [
1441
+ paymentLabel,
1442
+ " \xB7 ",
1443
+ paymentMethod
1444
+ ] }) : null
1445
+ ] }),
1446
+ actions.length > 0 ? /* @__PURE__ */ jsx(List, { children: actions.map((a) => /* @__PURE__ */ jsx(
1447
+ ListRow,
1448
+ {
1449
+ title: a.label,
1450
+ subtitle: a.value,
1451
+ leading: a.icon ? /* @__PURE__ */ jsx(Icon, { name: a.icon, size: theme.icon.md, color: theme.color.text2 }) : null,
1452
+ trailing: /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: theme.icon.sm, color: theme.color.text3 }),
1453
+ onPress: () => onAction?.(a.id)
1454
+ },
1455
+ a.id
1456
+ )) }) : null,
1457
+ onCancel ? /* @__PURE__ */ jsxs(Fragment, { children: [
1458
+ /* @__PURE__ */ jsx(
1459
+ Pressable,
1460
+ {
1461
+ accessibilityRole: "button",
1462
+ accessibilityLabel: cancelLabel,
1463
+ onPress: onCancel,
1464
+ style: ({ pressed }) => [styles.cancel, pressed ? styles.cancelPressed : null],
1465
+ children: /* @__PURE__ */ jsx(Text, { style: styles.cancelLabel, children: cancelLabel })
1466
+ }
1467
+ ),
1468
+ cancelNote ? /* @__PURE__ */ jsx(Text, { style: styles.cancelNote, children: cancelNote }) : null
1469
+ ] }) : null
1470
+ ] });
1471
+ }
1472
+ var sheet7 = createStyleSheet((theme) => ({
1473
+ screen: { flex: 1, backgroundColor: theme.color.bg },
1474
+ scroll: { padding: theme.layout.screenPad, gap: theme.space.s3 },
1475
+ card: {
1476
+ padding: theme.space.s4,
1477
+ borderRadius: theme.radius.lg,
1478
+ backgroundColor: theme.color.surface,
1479
+ borderWidth: theme.borderWidth.base,
1480
+ borderColor: theme.color.border,
1481
+ gap: theme.space.s2
1482
+ },
1483
+ cardHead: { flexDirection: "row", alignItems: "center", gap: theme.space.s2 },
1484
+ planName: {
1485
+ flexShrink: 1,
1486
+ fontFamily: theme.font.sans,
1487
+ fontWeight: theme.type.headline.fontWeight,
1488
+ fontSize: theme.type.headline.fontSize,
1489
+ lineHeight: theme.type.headline.lineHeight,
1490
+ letterSpacing: theme.type.headline.letterSpacing,
1491
+ color: theme.color.text
1492
+ },
1493
+ meta: {
1494
+ fontFamily: theme.font.sans,
1495
+ fontSize: theme.type.footnote.fontSize,
1496
+ lineHeight: theme.type.footnote.lineHeight,
1497
+ color: theme.color.text2
1498
+ },
1499
+ cancel: {
1500
+ minHeight: theme.tapMin,
1501
+ alignItems: "center",
1502
+ justifyContent: "center",
1503
+ borderRadius: theme.radius.md,
1504
+ // Static `w2`: the destructive outline is the drawing, not chrome.
1505
+ borderWidth: theme.borderWidth.thick,
1506
+ borderColor: theme.color.danger,
1507
+ backgroundColor: "transparent"
1508
+ },
1509
+ cancelPressed: { backgroundColor: theme.state.pressed },
1510
+ // Text on a transparent surface, so the tone itself — not `dangerInk`, which
1511
+ // belongs on a solid danger fill.
1512
+ cancelLabel: {
1513
+ fontFamily: theme.font.sans,
1514
+ fontWeight: theme.fontWeight.bold,
1515
+ fontSize: theme.type.callout.fontSize,
1516
+ lineHeight: theme.type.callout.lineHeight,
1517
+ color: theme.color.danger
1518
+ },
1519
+ cancelNote: {
1520
+ fontFamily: theme.font.sans,
1521
+ fontSize: theme.type.caption1.fontSize,
1522
+ lineHeight: theme.type.caption1.fontSize * theme.lineHeight.normal,
1523
+ color: theme.color.text3
1524
+ },
1525
+ // Badge hugs with `flex-start`, which pins it to the top of this centred row.
1526
+ rowBadge: { alignSelf: "center" }
1527
+ }));
1528
+ function ToggleRow({
1529
+ label,
1530
+ description,
1531
+ value,
1532
+ onValueChange,
1533
+ disabled = false,
1534
+ style,
1535
+ accessibilityLabel
1536
+ }) {
1537
+ const { styles } = useStyles(sheet8);
1538
+ const announced = accessibilityLabel ?? (description ? `${label}, ${description}` : label);
1539
+ return /* @__PURE__ */ jsxs(
1540
+ Pressable,
1541
+ {
1542
+ accessibilityRole: "switch",
1543
+ accessibilityLabel: announced,
1544
+ accessibilityState: { checked: value, disabled },
1545
+ "aria-checked": value,
1546
+ disabled,
1547
+ onPress: disabled ? void 0 : () => onValueChange?.(!value),
1548
+ style: ({ pressed }) => [
1549
+ styles.row,
1550
+ disabled ? styles.disabled : null,
1551
+ pressed && !disabled ? styles.pressed : null,
1552
+ style
1553
+ ],
1554
+ children: [
1555
+ /* @__PURE__ */ jsxs(View, { style: styles.main, children: [
1556
+ /* @__PURE__ */ jsx(Text, { style: styles.label, children: label }),
1557
+ description ? /* @__PURE__ */ jsx(Text, { style: styles.description, children: description }) : null
1558
+ ] }),
1559
+ /* @__PURE__ */ jsx(View, { "aria-hidden": true, style: styles.glyph, children: /* @__PURE__ */ jsx(Switch, { value, disabled, decorative: true }) })
1560
+ ]
1561
+ }
1562
+ );
1563
+ }
1564
+ var sheet8 = createStyleSheet((theme) => ({
1565
+ row: {
1566
+ flexDirection: "row",
1567
+ alignItems: "center",
1568
+ gap: theme.space.s3,
1569
+ minHeight: theme.tapMin,
1570
+ paddingHorizontal: theme.space.s4,
1571
+ paddingVertical: theme.space.s3,
1572
+ backgroundColor: "transparent"
1573
+ },
1574
+ pressed: { backgroundColor: theme.state.pressed },
1575
+ disabled: { opacity: theme.state.disabledOpacity },
1576
+ main: { flex: 1, minWidth: 0, gap: theme.space.s1 },
1577
+ label: {
1578
+ fontFamily: theme.font.sans,
1579
+ fontWeight: theme.type.headline.fontWeight,
1580
+ fontSize: theme.type.headline.fontSize,
1581
+ lineHeight: theme.type.headline.lineHeight,
1582
+ letterSpacing: theme.type.headline.letterSpacing,
1583
+ color: theme.color.text
1584
+ },
1585
+ description: {
1586
+ fontFamily: theme.font.sans,
1587
+ fontSize: theme.type.footnote.fontSize,
1588
+ lineHeight: theme.type.footnote.lineHeight,
1589
+ color: theme.color.text3
1590
+ },
1591
+ // `pointerEvents` in the style object, not as a prop: the prop form is
1592
+ // deprecated in RN 0.76.
1593
+ glyph: { flexShrink: 0, pointerEvents: "none" }
1594
+ }));
1595
+ function SettingsScreen({
1596
+ groups,
1597
+ onRowPress,
1598
+ onToggle
1599
+ }) {
1600
+ const { styles } = useStyles(sheet9);
1601
+ const theme = useLoamTheme();
1602
+ return /* @__PURE__ */ jsx(ScrollView, { style: styles.screen, contentContainerStyle: styles.scroll, children: groups.map((group) => /* @__PURE__ */ jsxs(View, { style: styles.group, children: [
1603
+ group.title ? /* @__PURE__ */ jsx(Text, { style: styles.caption, children: group.title }) : null,
1604
+ /* @__PURE__ */ jsx(List, { children: group.rows.map((row) => {
1605
+ if (row.kind === "switch") {
1606
+ return /* @__PURE__ */ jsx(
1607
+ ToggleRow,
1608
+ {
1609
+ label: row.label,
1610
+ description: row.description,
1611
+ value: row.value,
1612
+ disabled: row.disabled,
1613
+ onValueChange: (next) => {
1614
+ row.onValueChange?.(next);
1615
+ onToggle?.(row.id, next);
1616
+ }
1617
+ },
1618
+ row.id
1619
+ );
1620
+ }
1621
+ if (row.kind === "danger") {
1622
+ return /* @__PURE__ */ jsx(
1623
+ ListRow,
1624
+ {
1625
+ title: /* @__PURE__ */ jsx(Text, { style: styles.dangerLabel, children: row.label }),
1626
+ accessibilityLabel: row.label,
1627
+ onPress: () => {
1628
+ row.onPress?.();
1629
+ onRowPress?.(row.id);
1630
+ }
1631
+ },
1632
+ row.id
1633
+ );
1634
+ }
1635
+ return /* @__PURE__ */ jsx(
1636
+ ListRow,
1637
+ {
1638
+ title: row.label,
1639
+ accessibilityLabel: row.label,
1640
+ leading: row.icon ? /* @__PURE__ */ jsx(Icon, { name: row.icon, size: theme.icon.md, color: theme.color.text2 }) : null,
1641
+ trailing: /* @__PURE__ */ jsxs(View, { style: styles.trailing, children: [
1642
+ row.value ? /* @__PURE__ */ jsx(Text, { style: styles.value, children: row.value }) : null,
1643
+ /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: theme.icon.sm, color: theme.color.text3 })
1644
+ ] }),
1645
+ onPress: () => {
1646
+ row.onPress?.();
1647
+ onRowPress?.(row.id);
1648
+ }
1649
+ },
1650
+ row.id
1651
+ );
1652
+ }) }),
1653
+ group.footnote ? /* @__PURE__ */ jsx(Text, { style: styles.footnote, children: group.footnote }) : null
1654
+ ] }, group.id)) });
1655
+ }
1656
+ var sheet9 = createStyleSheet((theme) => ({
1657
+ screen: { flex: 1, backgroundColor: theme.color.bg },
1658
+ scroll: { padding: theme.layout.screenPad, gap: theme.space.s5 },
1659
+ group: { gap: theme.space.s2 },
1660
+ // Uppercase group caption. `tracking.caps` is an em multiple, so it is
1661
+ // multiplied by the font size to become RN's absolute letterSpacing.
1662
+ caption: {
1663
+ paddingStart: theme.space.s1,
1664
+ fontFamily: theme.font.sans,
1665
+ fontWeight: theme.fontWeight.bold,
1666
+ fontSize: theme.type.caption2.fontSize,
1667
+ lineHeight: theme.type.caption2.lineHeight,
1668
+ letterSpacing: theme.type.caption2.fontSize * theme.tracking.caps,
1669
+ textTransform: "uppercase",
1670
+ color: theme.color.text3
1671
+ },
1672
+ trailing: { flexDirection: "row", alignItems: "center", gap: theme.space.s1 },
1673
+ value: {
1674
+ fontFamily: theme.font.sans,
1675
+ fontSize: theme.type.footnote.fontSize,
1676
+ lineHeight: theme.type.footnote.lineHeight,
1677
+ color: theme.color.text3
1678
+ },
1679
+ // Destructive rows are the tone itself on a surface — `dangerInk` belongs on a
1680
+ // solid danger fill, not on a list row.
1681
+ dangerLabel: {
1682
+ fontFamily: theme.font.sans,
1683
+ fontWeight: theme.type.headline.fontWeight,
1684
+ fontSize: theme.type.headline.fontSize,
1685
+ lineHeight: theme.type.headline.lineHeight,
1686
+ letterSpacing: theme.type.headline.letterSpacing,
1687
+ color: theme.color.danger
1688
+ },
1689
+ footnote: {
1690
+ paddingStart: theme.space.s1,
1691
+ fontFamily: theme.font.sans,
1692
+ fontSize: theme.type.caption1.fontSize,
1693
+ lineHeight: theme.type.caption1.fontSize * theme.lineHeight.normal,
1694
+ color: theme.color.text3
1695
+ }
1696
+ }));
1697
+ var SYSTEM_NOTE = {
1698
+ on: "System notifications are on. Turn categories off here rather than muting the whole app.",
1699
+ off: "Notifications are turned off for this app in system settings. Your choices below are kept, and take effect as soon as you turn them back on."
1700
+ };
1701
+ function NotificationPreferences({
1702
+ systemEnabled = true,
1703
+ systemNote,
1704
+ systemActionLabel = "Open Settings",
1705
+ onOpenSystemSettings,
1706
+ categories,
1707
+ onChange
1708
+ }) {
1709
+ const { styles } = useStyles(sheet10);
1710
+ const note = systemNote ?? (systemEnabled ? SYSTEM_NOTE.on : SYSTEM_NOTE.off);
1711
+ return /* @__PURE__ */ jsxs(ScrollView, { style: styles.screen, contentContainerStyle: styles.scroll, children: [
1712
+ /* @__PURE__ */ jsxs(
1713
+ View,
1714
+ {
1715
+ accessibilityRole: systemEnabled ? "text" : "alert",
1716
+ style: [styles.banner, systemEnabled ? styles.bannerOn : styles.bannerOff],
1717
+ children: [
1718
+ /* @__PURE__ */ jsx(
1719
+ Text,
1720
+ {
1721
+ style: [styles.bannerText, systemEnabled ? styles.bannerTextOn : styles.bannerTextOff],
1722
+ children: note
1723
+ }
1724
+ ),
1725
+ !systemEnabled && onOpenSystemSettings ? /* @__PURE__ */ jsx(
1726
+ Pressable,
1727
+ {
1728
+ accessibilityRole: "button",
1729
+ accessibilityLabel: systemActionLabel,
1730
+ onPress: onOpenSystemSettings,
1731
+ style: ({ pressed }) => [styles.bannerAction, pressed ? styles.pressed : null],
1732
+ children: /* @__PURE__ */ jsx(Text, { style: styles.bannerActionLabel, children: systemActionLabel })
1733
+ }
1734
+ ) : null
1735
+ ]
1736
+ }
1737
+ ),
1738
+ categories.map((c) => /* @__PURE__ */ jsx(
1739
+ ToggleRow,
1740
+ {
1741
+ label: c.label,
1742
+ description: c.description,
1743
+ value: c.value,
1744
+ disabled: c.disabled === true || !systemEnabled,
1745
+ onValueChange: (next) => onChange?.(c.id, next),
1746
+ style: styles.card
1747
+ },
1748
+ c.id
1749
+ ))
1750
+ ] });
1751
+ }
1752
+ var sheet10 = createStyleSheet((theme) => ({
1753
+ screen: { flex: 1, backgroundColor: theme.color.bg },
1754
+ scroll: { padding: theme.layout.screenPad, gap: theme.space.s3 },
1755
+ banner: {
1756
+ padding: theme.space.s4,
1757
+ borderRadius: theme.radius.lg,
1758
+ borderWidth: theme.borderWidth.base,
1759
+ gap: theme.space.s3,
1760
+ alignItems: "flex-start"
1761
+ },
1762
+ bannerOn: { backgroundColor: theme.color.accentSoft, borderColor: theme.edge.accent },
1763
+ bannerOff: { backgroundColor: theme.color.warnSoft, borderColor: theme.edge.warn },
1764
+ bannerText: {
1765
+ fontFamily: theme.font.sans,
1766
+ fontSize: theme.type.footnote.fontSize,
1767
+ lineHeight: theme.type.footnote.fontSize * theme.lineHeight.normal
1768
+ },
1769
+ // Text on a `-soft` tint is a surface case: the tone itself, never the on-ink.
1770
+ bannerTextOn: { color: theme.color.accentOnSoft },
1771
+ bannerTextOff: { color: theme.color.warnOnSoft },
1772
+ bannerAction: {
1773
+ minHeight: theme.tapMin,
1774
+ justifyContent: "center",
1775
+ paddingHorizontal: theme.space.s3,
1776
+ borderRadius: theme.radius.md,
1777
+ backgroundColor: theme.color.warn
1778
+ },
1779
+ // Solid `warn` fill, so the measured ink for that fill.
1780
+ bannerActionLabel: {
1781
+ fontFamily: theme.font.sans,
1782
+ fontWeight: theme.fontWeight.bold,
1783
+ fontSize: theme.type.footnote.fontSize,
1784
+ color: theme.color.warnInk
1785
+ },
1786
+ pressed: { opacity: theme.state.pressedOpacity },
1787
+ card: {
1788
+ borderRadius: theme.radius.lg,
1789
+ backgroundColor: theme.color.surface,
1790
+ borderWidth: theme.borderWidth.base,
1791
+ borderColor: theme.color.border
1792
+ }
1793
+ }));
1794
+
1795
+ export { BillingPeriodToggle, DeleteAccountScreen, ManageSubscription, MeterBar, NotificationPreferences, Paywall, PaywallGate, PaywallSurface, PlanCard, PlanFeatureRow, PlanGrid, PlanPriceCell, PlanPriceDelta, ProrationLine, ProrationTable, SavingsChip, SettingsScreen, ToggleRow };