@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,377 @@
1
+ import { Icon } from './chunk-TKVFVF5D.js';
2
+ import * as React from 'react';
3
+ import { Animated, Easing, Pressable, Text, View } from 'react-native';
4
+ import { createStyleSheet, useStyles, useLoamTheme } from '@btndprojects/loam-theme-native';
5
+ import { jsx, jsxs } from 'react/jsx-runtime';
6
+
7
+ var SPEC = {
8
+ /** Row gap between the icon and the copy column. */
9
+ gap: 10,
10
+ // loam-allow-raw: ramp key, read via theme.space at the use site
11
+ /** Leading pad — 1px deeper than the other three, to optically inset the icon. */
12
+ padStart: 13,
13
+ /** Inline banner title size. */
14
+ titleSize: 13.5,
15
+ /** Message, action label and system-banner copy size. */
16
+ bodySize: 12.5,
17
+ /** Message line-height ratio. v2 moved this off `lineHeight.normal` (1.45). */
18
+ messageLineHeight: 1.4,
19
+ /** Trailing action row's negative end margin, so the label aligns to the edge. */
20
+ actionInsetEnd: -8,
21
+ /** Trailing action row's negative bottom margin, absorbing its own tap padding. */
22
+ actionInsetBottom: -6,
23
+ /**
24
+ * Visible height of the action label's box. Deliberately *below* `tapMin`: the
25
+ * label is unboxed, so the target is grown with `hitSlop` instead of with a
26
+ * 44px slab of empty space (`actionHitSlop` below closes the gap).
27
+ */
28
+ actionMinHeight: 28,
29
+ /** Action label horizontal padding. */
30
+ actionPadX: 8,
31
+ /**
32
+ * Negative block margins on the *inline* action, so its 28px box is absorbed
33
+ * into the copy column's own height (a title line is ~17px) and an inline
34
+ * action therefore adds **zero** height to the banner.
35
+ */
36
+ inlineActionInsetY: -6,
37
+ /**
38
+ * Longest action label that still sits inline beside the copy. Past this, the
39
+ * label would squeeze the message into a sliver, so it drops to its own row.
40
+ */
41
+ inlineActionMaxChars: 14,
42
+ /** Leading-icon and close-glyph size in the inline banner — between `icon.sm` and `icon.md`. */
43
+ glyph: 18,
44
+ /** Close-glyph size in the system banner. */
45
+ systemGlyph: 15,
46
+ /** Meta-line (last-updated stamp / service window / countdown) size. */
47
+ metaSize: 11.5,
48
+ /** Refresh affordance: gap between icon and label. */
49
+ refreshGap: 6,
50
+ /** Refresh affordance: horizontal padding. */
51
+ refreshPadX: 10,
52
+ /** Refresh affordance: box height (grown to `tapMin` with `hitSlop`). */
53
+ refreshMinHeight: 32,
54
+ /** Refresh affordance: glyph size. */
55
+ refreshGlyph: 15,
56
+ /**
57
+ * Negative margins on the system banner's `tapMin` close target, so growing
58
+ * the target from 26px to 44px does not inflate the banner's own padding.
59
+ */
60
+ systemDismissInsetY: -9,
61
+ systemDismissInsetEnd: -13
62
+ };
63
+ var TONE_ICON = {
64
+ info: "info",
65
+ success: "check-circle",
66
+ warn: "alert-triangle",
67
+ danger: "alert-triangle",
68
+ neutral: "info"
69
+ };
70
+ var VARIANT_META = {
71
+ offline: { tone: "neutral", icon: "cloud-off" },
72
+ stale: { tone: "neutral", icon: "clock" },
73
+ maintenance: { tone: "info", icon: "wrench" }
74
+ };
75
+ function useToneColor(tone) {
76
+ const theme = useLoamTheme();
77
+ switch (tone) {
78
+ case "success":
79
+ return theme.color.goodOnSoft;
80
+ case "warn":
81
+ return theme.color.warnOnSoft;
82
+ case "danger":
83
+ return theme.color.dangerOnSoft;
84
+ case "neutral":
85
+ return theme.status.pendingOnSoft;
86
+ default:
87
+ return theme.color.infoOnSoft;
88
+ }
89
+ }
90
+ function resolvePlacement(placement, label) {
91
+ if (placement !== "auto") return placement;
92
+ return label.length <= SPEC.inlineActionMaxChars ? "inline" : "block";
93
+ }
94
+ function Banner({
95
+ tone,
96
+ variant = "default",
97
+ placement = "section",
98
+ title,
99
+ message,
100
+ children,
101
+ icon,
102
+ actionLabel,
103
+ onAction,
104
+ actionPlacement = "auto",
105
+ onDismiss,
106
+ updatedLabel,
107
+ onRefresh,
108
+ refreshLabel = "Refresh",
109
+ timeWindow,
110
+ timezone,
111
+ countdown,
112
+ motion = "full"
113
+ }) {
114
+ const variantMeta = variant === "default" ? void 0 : VARIANT_META[variant];
115
+ const effectiveTone = tone ?? variantMeta?.tone ?? "info";
116
+ const { styles } = useStyles(sheet, { tone: effectiveTone, placement });
117
+ const theme = useLoamTheme();
118
+ const toneColor = useToneColor(effectiveTone);
119
+ const borderColor = toneColor + "55";
120
+ const iconName = icon ?? variantMeta?.icon ?? TONE_ICON[effectiveTone];
121
+ const actionPos = actionLabel ? resolvePlacement(actionPlacement, actionLabel) : void 0;
122
+ const dismissible = variant !== "offline" && Boolean(onDismiss);
123
+ const showRefresh = !actionLabel && Boolean(onRefresh);
124
+ const metaLine = variant === "stale" && updatedLabel ? `Updated ${updatedLabel}` : variant === "maintenance" && timeWindow ? timezone ? `${timeWindow} \xB7 ${timezone}` : timeWindow : null;
125
+ const actionHitSlop = Math.max(0, Math.round((theme.tapMin - SPEC.actionMinHeight) / 2));
126
+ const dismissHitSlop = Math.max(0, Math.round((theme.tapMin - SPEC.glyph) / 2));
127
+ const refreshHitSlop = Math.max(0, Math.round((theme.tapMin - SPEC.refreshMinHeight) / 2));
128
+ const animate = motion === "full" && !theme.reduceMotion;
129
+ const opacity = React.useRef(new Animated.Value(animate ? 0 : 1)).current;
130
+ React.useEffect(() => {
131
+ if (!animate) {
132
+ opacity.setValue(1);
133
+ return;
134
+ }
135
+ const anim = Animated.timing(opacity, {
136
+ toValue: 1,
137
+ duration: theme.motion.enter.duration,
138
+ easing: Easing.bezier(...theme.motion.enter.easing),
139
+ useNativeDriver: true
140
+ });
141
+ anim.start();
142
+ return () => anim.stop();
143
+ }, [animate, opacity, theme.motion.enter.duration, theme.motion.enter.easing]);
144
+ const action = actionLabel && actionPos ? /* @__PURE__ */ jsx(
145
+ Pressable,
146
+ {
147
+ accessibilityRole: "button",
148
+ accessibilityLabel: actionLabel,
149
+ onPress: onAction,
150
+ hitSlop: actionHitSlop,
151
+ style: actionPos === "inline" ? styles.actionInline : styles.action,
152
+ children: /* @__PURE__ */ jsx(Text, { style: [styles.actionLabel, { color: toneColor }], children: actionLabel })
153
+ }
154
+ ) : null;
155
+ const refresh = showRefresh ? /* @__PURE__ */ jsxs(
156
+ Pressable,
157
+ {
158
+ accessibilityRole: "button",
159
+ accessibilityLabel: refreshLabel,
160
+ onPress: onRefresh,
161
+ hitSlop: refreshHitSlop,
162
+ style: [styles.refresh, { borderColor: toneColor }],
163
+ children: [
164
+ /* @__PURE__ */ jsx(Icon, { name: "refresh", size: SPEC.refreshGlyph, color: toneColor }),
165
+ /* @__PURE__ */ jsx(Text, { style: [styles.actionLabel, { color: toneColor }], children: refreshLabel })
166
+ ]
167
+ }
168
+ ) : null;
169
+ return /* @__PURE__ */ jsxs(Animated.View, { accessibilityRole: "alert", style: [styles.base, { borderColor, opacity }], children: [
170
+ /* @__PURE__ */ jsx(View, { style: styles.iconSlot, children: /* @__PURE__ */ jsx(Icon, { name: iconName, size: SPEC.glyph, color: toneColor }) }),
171
+ /* @__PURE__ */ jsxs(View, { style: styles.content, children: [
172
+ title ? /* @__PURE__ */ jsx(Text, { style: styles.title, children: title }) : null,
173
+ message ? /* @__PURE__ */ jsx(Text, { style: styles.message, children: message }) : typeof children === "string" ? /* @__PURE__ */ jsx(Text, { style: styles.message, children }) : children,
174
+ metaLine ? /* @__PURE__ */ jsx(Text, { style: [styles.meta, { color: toneColor }], children: metaLine }) : null,
175
+ variant === "maintenance" && countdown ? /* @__PURE__ */ jsx(Text, { style: [styles.metaStrong, { color: toneColor }], children: countdown }) : null,
176
+ actionPos === "block" ? /* @__PURE__ */ jsx(View, { style: styles.actionRow, children: action }) : null
177
+ ] }),
178
+ actionPos === "inline" ? action : null,
179
+ refresh ? /* @__PURE__ */ jsx(View, { style: styles.refreshSlot, children: refresh }) : null,
180
+ dismissible ? /* @__PURE__ */ jsx(
181
+ Pressable,
182
+ {
183
+ accessibilityRole: "button",
184
+ accessibilityLabel: "Dismiss",
185
+ onPress: onDismiss,
186
+ hitSlop: dismissHitSlop,
187
+ style: styles.dismiss,
188
+ children: /* @__PURE__ */ jsx(Icon, { name: "x", size: SPEC.glyph, color: toneColor })
189
+ }
190
+ ) : null
191
+ ] });
192
+ }
193
+ function SystemBanner({
194
+ message,
195
+ icon = "star",
196
+ onDismiss
197
+ }) {
198
+ const { styles } = useStyles(sheet);
199
+ const theme = useLoamTheme();
200
+ return /* @__PURE__ */ jsxs(View, { accessibilityRole: "alert", style: styles.system, children: [
201
+ /* @__PURE__ */ jsx(Icon, { name: icon, size: 18, color: theme.color.accentInk }),
202
+ /* @__PURE__ */ jsx(Text, { style: styles.systemMessage, numberOfLines: 1, ellipsizeMode: "tail", children: message }),
203
+ onDismiss ? /* @__PURE__ */ jsx(
204
+ Pressable,
205
+ {
206
+ accessibilityRole: "button",
207
+ accessibilityLabel: "Dismiss",
208
+ onPress: onDismiss,
209
+ style: styles.systemDismiss,
210
+ children: /* @__PURE__ */ jsx(Icon, { name: "x", size: SPEC.systemGlyph, color: theme.color.accentInk })
211
+ }
212
+ ) : null
213
+ ] });
214
+ }
215
+ var sheet = createStyleSheet((theme) => ({
216
+ base: {
217
+ flexDirection: "row",
218
+ alignItems: "flex-start",
219
+ gap: theme.space[SPEC.gap],
220
+ paddingTop: theme.space.s3,
221
+ paddingBottom: theme.space.s3,
222
+ paddingEnd: theme.space.s3,
223
+ paddingStart: SPEC.padStart,
224
+ borderRadius: theme.radius.md,
225
+ borderWidth: theme.borderWidth.thin,
226
+ variants: {
227
+ tone: {
228
+ info: { backgroundColor: theme.color.infoSoft },
229
+ success: { backgroundColor: theme.color.goodSoft },
230
+ warn: { backgroundColor: theme.color.warnSoft },
231
+ danger: { backgroundColor: theme.color.dangerSoft },
232
+ neutral: { backgroundColor: theme.status.pendingSoft }
233
+ },
234
+ placement: {
235
+ // Full-bleed strip: flush to the edge, no radius, most emphasis.
236
+ page: { borderRadius: 0, alignSelf: "stretch" },
237
+ // The default inset card.
238
+ section: { borderRadius: theme.radius.md },
239
+ // Compact notice: tighter block padding so it threads into content.
240
+ inline: {
241
+ borderRadius: theme.radius.md,
242
+ paddingTop: theme.space.s2,
243
+ paddingBottom: theme.space.s2
244
+ }
245
+ }
246
+ }
247
+ },
248
+ iconSlot: {
249
+ // Nudge the icon onto the title's optical baseline.
250
+ paddingTop: theme.space[1]
251
+ },
252
+ // `minWidth: 0` is what lets a long word truncate instead of pushing the
253
+ // trailing edge of the card out.
254
+ content: {
255
+ flex: 1,
256
+ minWidth: 0,
257
+ flexDirection: "column",
258
+ // The 2px column gap replaces the message's old `marginTop`.
259
+ gap: theme.space[2]
260
+ },
261
+ title: {
262
+ fontFamily: theme.font.sans,
263
+ fontWeight: theme.fontWeight.bold,
264
+ fontSize: SPEC.titleSize,
265
+ lineHeight: SPEC.titleSize * theme.lineHeight.snug,
266
+ color: theme.color.text
267
+ },
268
+ message: {
269
+ fontFamily: theme.font.sans,
270
+ fontSize: SPEC.bodySize,
271
+ lineHeight: SPEC.bodySize * SPEC.messageLineHeight,
272
+ color: theme.color.text
273
+ },
274
+ // The block row carries no `marginTop`: the copy column's own 2px gap is the
275
+ // separation, and the negative bottom margin absorbs the label's tap padding.
276
+ actionRow: {
277
+ flexDirection: "row",
278
+ justifyContent: "flex-end",
279
+ marginEnd: SPEC.actionInsetEnd,
280
+ marginBottom: SPEC.actionInsetBottom
281
+ },
282
+ action: {
283
+ minHeight: SPEC.actionMinHeight,
284
+ paddingHorizontal: SPEC.actionPadX,
285
+ borderRadius: theme.radius.sm,
286
+ borderWidth: 0,
287
+ backgroundColor: "transparent",
288
+ alignItems: "center",
289
+ justifyContent: "center"
290
+ },
291
+ // Same unboxed label, on the banner's own row. `flexShrink: 0` keeps it whole
292
+ // while the copy column absorbs the width; the negative block margins keep it
293
+ // from adding any height, so an inline action is free.
294
+ actionInline: {
295
+ flexShrink: 0,
296
+ minHeight: SPEC.actionMinHeight,
297
+ paddingHorizontal: SPEC.actionPadX,
298
+ marginTop: SPEC.inlineActionInsetY,
299
+ marginBottom: SPEC.inlineActionInsetY,
300
+ borderRadius: theme.radius.sm,
301
+ borderWidth: 0,
302
+ backgroundColor: "transparent",
303
+ alignItems: "center",
304
+ justifyContent: "center"
305
+ },
306
+ actionLabel: {
307
+ fontFamily: theme.font.sans,
308
+ fontWeight: theme.fontWeight.bold,
309
+ fontSize: SPEC.bodySize
310
+ },
311
+ // A last-updated stamp / service window. Colored the tone's on-soft ink at the
312
+ // call site (AA on the tint); a plain weight so it reads as secondary copy.
313
+ meta: {
314
+ fontFamily: theme.font.sans,
315
+ fontWeight: theme.fontWeight.medium,
316
+ fontSize: SPEC.metaSize,
317
+ lineHeight: SPEC.metaSize * SPEC.messageLineHeight
318
+ },
319
+ // The maintenance countdown — the same meta line, emphasized.
320
+ metaStrong: {
321
+ fontFamily: theme.font.sans,
322
+ fontWeight: theme.fontWeight.bold,
323
+ fontSize: SPEC.metaSize,
324
+ lineHeight: SPEC.metaSize * SPEC.messageLineHeight
325
+ },
326
+ // The stale refresh affordance sits self-centered on the trailing edge.
327
+ refreshSlot: {
328
+ flexShrink: 0,
329
+ alignSelf: "center"
330
+ },
331
+ // An outline pill in the tone's on-soft ink; `borderColor` is set at the call
332
+ // site so it matches the icon and label. The 32px box grows to `tapMin` via
333
+ // `hitSlop`, not a 44px slab.
334
+ refresh: {
335
+ flexDirection: "row",
336
+ alignItems: "center",
337
+ justifyContent: "center",
338
+ gap: SPEC.refreshGap,
339
+ minHeight: SPEC.refreshMinHeight,
340
+ paddingHorizontal: SPEC.refreshPadX,
341
+ borderRadius: theme.radius.sm,
342
+ borderWidth: theme.borderWidth.thin,
343
+ backgroundColor: "transparent"
344
+ },
345
+ dismiss: {
346
+ paddingTop: theme.space[1]
347
+ },
348
+ system: {
349
+ flexDirection: "row",
350
+ alignItems: "center",
351
+ gap: theme.space[10],
352
+ paddingVertical: theme.space.s3,
353
+ paddingHorizontal: theme.space.s4,
354
+ backgroundColor: theme.color.accent
355
+ },
356
+ systemMessage: {
357
+ flex: 1,
358
+ minWidth: 0,
359
+ fontFamily: theme.font.sans,
360
+ fontWeight: theme.fontWeight.semibold,
361
+ fontSize: SPEC.bodySize,
362
+ color: theme.color.accentInk
363
+ },
364
+ // A full `tapMin` target pulled back with negative margins, so the reachable
365
+ // area is 44×44 while the banner keeps its 12px/16px padding.
366
+ systemDismiss: {
367
+ width: theme.tapMin,
368
+ height: theme.tapMin,
369
+ marginTop: SPEC.systemDismissInsetY,
370
+ marginBottom: SPEC.systemDismissInsetY,
371
+ marginEnd: SPEC.systemDismissInsetEnd,
372
+ alignItems: "center",
373
+ justifyContent: "center"
374
+ }
375
+ }));
376
+
377
+ export { Banner, SystemBanner };
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,196 @@
1
+ import { NumberRoll } from './chunk-CUMUNPXY.js';
2
+ import { Icon } from './chunk-TKVFVF5D.js';
3
+ import 'react';
4
+ import { View, Text } from 'react-native';
5
+ import { createStyleSheet, useStyles, useLoamTheme } from '@btndprojects/loam-theme-native';
6
+ import { jsxs, jsx } from 'react/jsx-runtime';
7
+
8
+ var DIRECTION_GLYPH = {
9
+ up: "arrow-up",
10
+ down: "arrow-down",
11
+ flat: "minus"
12
+ };
13
+ var DIRECTION_WORD = {
14
+ up: "Up",
15
+ down: "Down",
16
+ flat: "No change"
17
+ };
18
+ function resolveDirection(delta, deltaDirection) {
19
+ if (deltaDirection) return deltaDirection;
20
+ if (typeof delta === "number") return delta > 0 ? "up" : delta < 0 ? "down" : "flat";
21
+ return "flat";
22
+ }
23
+ function toneFor(direction, invert) {
24
+ if (direction === "flat") return "neutral";
25
+ const isGood = direction === "up" ? !invert : invert;
26
+ return isGood ? "good" : "bad";
27
+ }
28
+ function formatDelta(delta) {
29
+ if (typeof delta === "string") return delta;
30
+ const sign = delta > 0 ? "+" : delta < 0 ? "\u2212" : "";
31
+ return `${sign}${Math.abs(delta).toLocaleString()}`;
32
+ }
33
+ function Metric({
34
+ label,
35
+ value,
36
+ unit,
37
+ delta,
38
+ deltaDirection,
39
+ invert = false,
40
+ size = "md",
41
+ caption,
42
+ motion = "full",
43
+ animateValue = false,
44
+ accessibilityLabel
45
+ }) {
46
+ const hasDelta = delta !== void 0 && delta !== "";
47
+ const direction = resolveDirection(delta, deltaDirection);
48
+ const tone = toneFor(direction, invert);
49
+ const deltaText = hasDelta ? formatDelta(delta) : "";
50
+ const numeric = typeof value === "number";
51
+ const willAnimateValue = animateValue && numeric && motion !== "none" && motion !== "reduced";
52
+ const { styles } = useStyles(sheet, { size, tone });
53
+ const theme = useLoamTheme();
54
+ const glyphColor = tone === "good" ? theme.status.doneOnSoft : tone === "bad" ? theme.status.crashedOnSoft : theme.status.pendingOnSoft;
55
+ const iconSize = size === "sm" ? theme.icon.xs : size === "lg" ? theme.icon.md : theme.icon.sm;
56
+ return /* @__PURE__ */ jsxs(View, { style: styles.root, accessibilityLabel, children: [
57
+ /* @__PURE__ */ jsx(Text, { style: styles.label, children: label }),
58
+ /* @__PURE__ */ jsxs(View, { style: styles.valueRow, children: [
59
+ willAnimateValue ? /* @__PURE__ */ jsx(NumberRoll, { value, textStyle: styles.value, motion }) : /* @__PURE__ */ jsx(Text, { style: styles.value, children: value }),
60
+ unit !== void 0 ? /* @__PURE__ */ jsx(Text, { style: styles.unit, children: unit }) : null
61
+ ] }),
62
+ hasDelta ? /* @__PURE__ */ jsxs(
63
+ View,
64
+ {
65
+ style: styles.delta,
66
+ accessibilityLabel: deltaText ? `${DIRECTION_WORD[direction]} ${deltaText}` : DIRECTION_WORD[direction],
67
+ children: [
68
+ /* @__PURE__ */ jsx(Icon, { name: DIRECTION_GLYPH[direction], size: iconSize, color: glyphColor }),
69
+ deltaText ? /* @__PURE__ */ jsx(Text, { style: styles.deltaText, children: deltaText }) : null
70
+ ]
71
+ }
72
+ ) : null,
73
+ caption !== void 0 ? /* @__PURE__ */ jsx(Text, { style: styles.caption, children: caption }) : null
74
+ ] });
75
+ }
76
+ var sheet = createStyleSheet((theme) => ({
77
+ root: { alignItems: "flex-start", gap: theme.space.s1 },
78
+ // Muted secondary ink; clears AA on surface in both modes.
79
+ label: {
80
+ fontFamily: theme.font.sans,
81
+ color: theme.color.text2,
82
+ variants: {
83
+ size: {
84
+ sm: {
85
+ fontSize: theme.type.caption1.fontSize,
86
+ lineHeight: theme.type.caption1.lineHeight,
87
+ letterSpacing: theme.type.caption1.letterSpacing
88
+ },
89
+ md: {
90
+ fontSize: theme.type.footnote.fontSize,
91
+ lineHeight: theme.type.footnote.lineHeight,
92
+ letterSpacing: theme.type.footnote.letterSpacing
93
+ },
94
+ lg: {
95
+ fontSize: theme.type.subheadline.fontSize,
96
+ lineHeight: theme.type.subheadline.lineHeight,
97
+ letterSpacing: theme.type.subheadline.letterSpacing
98
+ }
99
+ }
100
+ }
101
+ },
102
+ valueRow: { flexDirection: "row", alignItems: "baseline", gap: theme.space.s1 },
103
+ // Big value — sans, bold, tabular so digits do not jitter as it updates.
104
+ value: {
105
+ fontFamily: theme.font.sans,
106
+ fontWeight: theme.fontWeight.bold,
107
+ color: theme.color.text,
108
+ fontVariant: ["tabular-nums"],
109
+ variants: {
110
+ size: {
111
+ sm: {
112
+ fontSize: theme.type.title3.fontSize,
113
+ lineHeight: theme.type.title3.lineHeight,
114
+ letterSpacing: theme.type.title3.letterSpacing
115
+ },
116
+ md: {
117
+ fontSize: theme.type.title1.fontSize,
118
+ lineHeight: theme.type.title1.lineHeight,
119
+ letterSpacing: theme.type.title1.letterSpacing
120
+ },
121
+ lg: {
122
+ fontSize: theme.type.largeTitle.fontSize,
123
+ lineHeight: theme.type.largeTitle.lineHeight,
124
+ letterSpacing: theme.type.largeTitle.letterSpacing
125
+ }
126
+ }
127
+ }
128
+ },
129
+ unit: {
130
+ fontFamily: theme.font.sans,
131
+ fontWeight: theme.fontWeight.medium,
132
+ color: theme.color.text2,
133
+ fontVariant: ["tabular-nums"],
134
+ variants: {
135
+ size: {
136
+ sm: {
137
+ fontSize: theme.type.footnote.fontSize,
138
+ lineHeight: theme.type.footnote.lineHeight,
139
+ letterSpacing: theme.type.footnote.letterSpacing
140
+ },
141
+ md: {
142
+ fontSize: theme.type.callout.fontSize,
143
+ lineHeight: theme.type.callout.lineHeight,
144
+ letterSpacing: theme.type.callout.letterSpacing
145
+ },
146
+ lg: {
147
+ fontSize: theme.type.title3.fontSize,
148
+ lineHeight: theme.type.title3.lineHeight,
149
+ letterSpacing: theme.type.title3.letterSpacing
150
+ }
151
+ }
152
+ }
153
+ },
154
+ // Soft-tinted delta chip: tint + on-soft ink, plus a tone edge that appears
155
+ // under high contrast when the tint collapses to the plain surface.
156
+ delta: {
157
+ flexDirection: "row",
158
+ alignItems: "center",
159
+ gap: theme.space.s1,
160
+ paddingHorizontal: theme.space.s2,
161
+ paddingVertical: theme.space.s1,
162
+ borderRadius: theme.radius.full,
163
+ borderWidth: theme.borderWidth.base,
164
+ variants: {
165
+ tone: {
166
+ good: { backgroundColor: theme.status.doneSoft, borderColor: theme.edge.good },
167
+ bad: { backgroundColor: theme.status.crashedSoft, borderColor: theme.edge.danger },
168
+ neutral: { backgroundColor: theme.status.pendingSoft, borderColor: theme.edge.neutral }
169
+ }
170
+ }
171
+ },
172
+ deltaText: {
173
+ fontFamily: theme.font.sans,
174
+ fontWeight: theme.fontWeight.semibold,
175
+ fontVariant: ["tabular-nums"],
176
+ fontSize: theme.type.footnote.fontSize,
177
+ lineHeight: theme.type.footnote.lineHeight,
178
+ letterSpacing: theme.type.footnote.letterSpacing,
179
+ variants: {
180
+ tone: {
181
+ good: { color: theme.status.doneOnSoft },
182
+ bad: { color: theme.status.crashedOnSoft },
183
+ neutral: { color: theme.status.pendingOnSoft }
184
+ }
185
+ }
186
+ },
187
+ caption: {
188
+ fontFamily: theme.font.sans,
189
+ color: theme.color.text2,
190
+ fontSize: theme.type.caption1.fontSize,
191
+ lineHeight: theme.type.caption1.lineHeight,
192
+ letterSpacing: theme.type.caption1.letterSpacing
193
+ }
194
+ }));
195
+
196
+ export { Metric };
@@ -0,0 +1,95 @@
1
+ import 'react';
2
+ import { Svg, Circle, Line, Rect, Polyline, Polygon, Ellipse, Path } from 'react-native-svg';
3
+ import { icons } from '@btndprojects/loam-icons';
4
+ import { useLoamTheme } from '@btndprojects/loam-theme-native';
5
+ import { jsx } from 'react/jsx-runtime';
6
+
7
+ // src/lib/icon.tsx
8
+ function Icon({ name, size = 20, color, strokeWidth = 1.5 }) {
9
+ const theme = useLoamTheme();
10
+ const stroke = color ?? theme.color.text;
11
+ return /* @__PURE__ */ jsx(Svg, { width: size, height: size, viewBox: "0 0 24 24", fill: "none", children: icons[name].map((p, i) => {
12
+ if (p.t === "circle")
13
+ return p.fill ? /* @__PURE__ */ jsx(Circle, { cx: p.cx, cy: p.cy, r: p.r, fill: stroke }, i) : /* @__PURE__ */ jsx(Circle, { cx: p.cx, cy: p.cy, r: p.r, stroke, strokeWidth }, i);
14
+ if (p.t === "line")
15
+ return /* @__PURE__ */ jsx(
16
+ Line,
17
+ {
18
+ x1: p.x1,
19
+ y1: p.y1,
20
+ x2: p.x2,
21
+ y2: p.y2,
22
+ stroke,
23
+ strokeWidth,
24
+ strokeLinecap: "round"
25
+ },
26
+ i
27
+ );
28
+ if (p.t === "rect")
29
+ return /* @__PURE__ */ jsx(
30
+ Rect,
31
+ {
32
+ x: p.x,
33
+ y: p.y,
34
+ width: p.width,
35
+ height: p.height,
36
+ rx: p.rx,
37
+ ry: p.ry,
38
+ stroke,
39
+ strokeWidth,
40
+ strokeLinejoin: "round"
41
+ },
42
+ i
43
+ );
44
+ if (p.t === "polyline")
45
+ return /* @__PURE__ */ jsx(
46
+ Polyline,
47
+ {
48
+ points: p.points,
49
+ stroke,
50
+ strokeWidth,
51
+ strokeLinecap: "round",
52
+ strokeLinejoin: "round"
53
+ },
54
+ i
55
+ );
56
+ if (p.t === "polygon")
57
+ return /* @__PURE__ */ jsx(
58
+ Polygon,
59
+ {
60
+ points: p.points,
61
+ stroke,
62
+ strokeWidth,
63
+ strokeLinecap: "round",
64
+ strokeLinejoin: "round"
65
+ },
66
+ i
67
+ );
68
+ if (p.t === "ellipse")
69
+ return /* @__PURE__ */ jsx(
70
+ Ellipse,
71
+ {
72
+ cx: p.cx,
73
+ cy: p.cy,
74
+ rx: p.rx,
75
+ ry: p.ry,
76
+ stroke,
77
+ strokeWidth
78
+ },
79
+ i
80
+ );
81
+ return /* @__PURE__ */ jsx(
82
+ Path,
83
+ {
84
+ d: p.d,
85
+ stroke,
86
+ strokeWidth,
87
+ strokeLinecap: "round",
88
+ strokeLinejoin: "round"
89
+ },
90
+ i
91
+ );
92
+ }) });
93
+ }
94
+
95
+ export { Icon };