@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,274 @@
1
+ import { Icon } from './chunk-TKVFVF5D.js';
2
+ import * as React from 'react';
3
+ import { View, Text, Pressable } from 'react-native';
4
+ import { createStyleSheet, useStyles, useLoamTheme } from '@btndprojects/loam-theme-native';
5
+ import { jsxs, jsx } from 'react/jsx-runtime';
6
+
7
+ var POSTER = {
8
+ /** Inset from the top of the stage. */
9
+ padTop: 10,
10
+ /** Label size. Matches `type.caption2` on both platforms, but stated for clarity. */
11
+ fontSize: 11,
12
+ // loam-allow-raw: ramp key, read via theme.fontSize at the use site
13
+ /**
14
+ * Tracking as an `em` multiple. `theme.tracking` steps either side are `wide`
15
+ * (0.04) and `caps` (0.12), so the doc's 0.08em has no token — the value is
16
+ * kept and scaled by the font size, since RN `letterSpacing` is absolute.
17
+ */
18
+ trackingEm: 0.08
19
+ };
20
+ var PLAY_GLYPH_NUDGE = -1;
21
+ function formatTime(seconds) {
22
+ const s = Number.isFinite(seconds) && seconds > 0 ? Math.floor(seconds) : 0;
23
+ const h = Math.floor(s / 3600);
24
+ const m = Math.floor(s % 3600 / 60);
25
+ const sec = String(s % 60).padStart(2, "0");
26
+ return h > 0 ? `${h}:${String(m).padStart(2, "0")}:${sec}` : `${m}:${sec}`;
27
+ }
28
+ function VideoPlayer({
29
+ duration,
30
+ position,
31
+ playing,
32
+ title,
33
+ meta,
34
+ aspectRatio = 16 / 9,
35
+ captions = false,
36
+ quality,
37
+ playbackRate = 1,
38
+ onTogglePlay,
39
+ onSeek,
40
+ children,
41
+ // Inert (see prop docs); destructured so it never reaches a native element.
42
+ motion: _motion = "full"
43
+ }) {
44
+ const { styles } = useStyles(sheet);
45
+ const theme = useLoamTheme();
46
+ const [ownPlaying, setOwnPlaying] = React.useState(false);
47
+ const [ownPosition, setOwnPosition] = React.useState(0);
48
+ const [trackWidth, setTrackWidth] = React.useState(0);
49
+ const [controlsHeight, setControlsHeight] = React.useState(0);
50
+ const isPlaying = playing ?? ownPlaying;
51
+ const current = position ?? ownPosition;
52
+ const pct = duration > 0 ? Math.min(1, Math.max(0, current / duration)) : 0;
53
+ const toggle = () => {
54
+ const next = !isPlaying;
55
+ if (playing == null) setOwnPlaying(next);
56
+ onTogglePlay?.(next);
57
+ };
58
+ const seekFromX = (x) => {
59
+ if (trackWidth <= 0 || duration <= 0) return;
60
+ const next = Math.min(duration, Math.max(0, x / trackWidth * duration));
61
+ if (position == null) setOwnPosition(next);
62
+ onSeek?.(next);
63
+ };
64
+ return /* @__PURE__ */ jsxs(View, { style: styles.root, children: [
65
+ /* @__PURE__ */ jsxs(View, { style: [styles.stage, { aspectRatio }], children: [
66
+ children ?? /* @__PURE__ */ jsx(View, { style: styles.placeholder, children: /* @__PURE__ */ jsxs(Text, { style: styles.placeholderLabel, children: [
67
+ "Poster ",
68
+ Math.round(aspectRatio * 100) / 100
69
+ ] }) }),
70
+ /* @__PURE__ */ jsxs(
71
+ Pressable,
72
+ {
73
+ accessibilityRole: "button",
74
+ accessibilityLabel: isPlaying ? "Pause" : "Play",
75
+ accessibilityState: { selected: isPlaying },
76
+ onPress: toggle,
77
+ style: styles.stagePress,
78
+ children: [
79
+ /* @__PURE__ */ jsx(View, { style: [styles.scrim, isPlaying ? styles.scrimDim : null] }),
80
+ !isPlaying ? (
81
+ // Centred in the stage *above the transport bar*, with explicit insets
82
+ // so the answer is the same in Yoga and in the browser.
83
+ /* @__PURE__ */ jsx(View, { style: [styles.playSlot, { bottom: controlsHeight }], children: /* @__PURE__ */ jsx(View, { style: styles.playButton, children: /* @__PURE__ */ jsx(
84
+ Icon,
85
+ {
86
+ name: "play",
87
+ size: theme.icon.md,
88
+ color: theme.color.text,
89
+ strokeWidth: theme.icon.stroke
90
+ }
91
+ ) }) })
92
+ ) : null
93
+ ]
94
+ }
95
+ ),
96
+ /* @__PURE__ */ jsxs(
97
+ View,
98
+ {
99
+ style: [styles.controls, { pointerEvents: "box-none" }],
100
+ onLayout: (e) => setControlsHeight(e.nativeEvent.layout.height),
101
+ children: [
102
+ /* @__PURE__ */ jsx(
103
+ Pressable,
104
+ {
105
+ accessibilityRole: "adjustable",
106
+ accessibilityLabel: "Seek",
107
+ "aria-valuemin": 0,
108
+ "aria-valuemax": Math.round(duration),
109
+ "aria-valuenow": Math.round(current),
110
+ "aria-valuetext": `${formatTime(current)} of ${formatTime(duration)}`,
111
+ accessibilityActions: [{ name: "increment" }, { name: "decrement" }],
112
+ onAccessibilityAction: (e) => {
113
+ const delta = e.nativeEvent.actionName === "increment" ? 5 : -5;
114
+ const next = Math.min(duration, Math.max(0, current + delta));
115
+ if (position == null) setOwnPosition(next);
116
+ onSeek?.(next);
117
+ },
118
+ onLayout: (e) => setTrackWidth(e.nativeEvent.layout.width),
119
+ onPress: (e) => seekFromX(e.nativeEvent.locationX),
120
+ hitSlop: theme.space.s3,
121
+ style: styles.seek,
122
+ children: /* @__PURE__ */ jsxs(View, { style: styles.track, children: [
123
+ /* @__PURE__ */ jsx(View, { style: [styles.fill, { width: `${pct * 100}%` }] }),
124
+ /* @__PURE__ */ jsx(View, { style: [styles.knob, { start: `${pct * 100}%` }] })
125
+ ] })
126
+ }
127
+ ),
128
+ /* @__PURE__ */ jsxs(View, { style: styles.metaRow, children: [
129
+ /* @__PURE__ */ jsx(Text, { style: styles.metaText, children: formatTime(current) }),
130
+ /* @__PURE__ */ jsxs(View, { style: styles.badges, children: [
131
+ captions ? /* @__PURE__ */ jsx(Icon, { name: "captions", size: theme.icon.xs, color: theme.color.text }) : null,
132
+ /* @__PURE__ */ jsxs(Text, { style: styles.metaText, children: [
133
+ playbackRate,
134
+ "\xD7"
135
+ ] }),
136
+ quality ? /* @__PURE__ */ jsx(Text, { style: styles.metaText, children: quality }) : null
137
+ ] }),
138
+ /* @__PURE__ */ jsx(Text, { style: styles.metaText, children: formatTime(duration) })
139
+ ] })
140
+ ]
141
+ }
142
+ )
143
+ ] }),
144
+ title || meta ? /* @__PURE__ */ jsxs(View, { style: styles.footer, children: [
145
+ title ? /* @__PURE__ */ jsx(Text, { style: styles.title, children: title }) : null,
146
+ meta ? /* @__PURE__ */ jsx(Text, { style: styles.metaLine, children: meta }) : null
147
+ ] }) : null
148
+ ] });
149
+ }
150
+ var sheet = createStyleSheet((theme) => ({
151
+ root: {
152
+ borderRadius: theme.radius.xl,
153
+ borderWidth: theme.borderWidth.base,
154
+ borderColor: theme.color.border,
155
+ backgroundColor: theme.color.surface,
156
+ overflow: "hidden"
157
+ },
158
+ stage: { position: "relative", width: "100%", backgroundColor: theme.color.surface3 },
159
+ // Top-centre, not centred: the label captions the empty poster, it does not
160
+ // pretend to be the poster's content.
161
+ placeholder: {
162
+ ...cover(),
163
+ alignItems: "center",
164
+ justifyContent: "flex-start",
165
+ paddingTop: theme.space[POSTER.padTop]
166
+ },
167
+ placeholderLabel: {
168
+ fontFamily: theme.font.mono,
169
+ fontSize: theme.fontSize[POSTER.fontSize],
170
+ letterSpacing: theme.fontSize[POSTER.fontSize] * POSTER.trackingEm,
171
+ textTransform: "uppercase",
172
+ color: theme.color.text3
173
+ },
174
+ stagePress: cover(),
175
+ // A scrim, so white control glyphs never sit on unknown pixels. Fixed black
176
+ // rather than the mode-aware `overlay.*`: what is underneath is video, and
177
+ // darkening it is the only direction that makes a white glyph legible.
178
+ scrim: { ...cover(), backgroundColor: theme.black.a8 },
179
+ scrimDim: { backgroundColor: theme.black.a4 },
180
+ // The band of the stage the transport bar does not cover. `bottom` is set from
181
+ // the bar's measured height at render time.
182
+ playSlot: {
183
+ position: "absolute",
184
+ top: 0,
185
+ start: 0,
186
+ end: 0,
187
+ alignItems: "center",
188
+ justifyContent: "center"
189
+ },
190
+ // `tapMin`-sized *visual*, down from 56: the tap target is the whole stage, so
191
+ // the circle only has to be big enough to read as a play affordance. On a
192
+ // 16:9 stage 260pt wide, 56pt was a third of the frame's height and collided
193
+ // with the scrubber.
194
+ // loam-contrast-ignore — media chrome: the disc sits on video, `shadow.sm` is
195
+ // a lift rather than its boundary, and the glyph carries the state. An edge
196
+ // here would read as a defect over the frame.
197
+ playButton: {
198
+ width: theme.tapMin,
199
+ height: theme.tapMin,
200
+ borderRadius: theme.radius.full,
201
+ backgroundColor: theme.color.surface,
202
+ alignItems: "center",
203
+ justifyContent: "center",
204
+ // Width is fixed, so trailing padding narrows the content box and pulls the
205
+ // centred glyph back by half of it — see PLAY_GLYPH_NUDGE.
206
+ paddingEnd: PLAY_GLYPH_NUDGE * -2,
207
+ ...theme.shadow.sm
208
+ },
209
+ controls: {
210
+ position: "absolute",
211
+ start: 0,
212
+ end: 0,
213
+ bottom: 0,
214
+ paddingHorizontal: theme.space.s3,
215
+ paddingBottom: theme.space.s2,
216
+ gap: theme.space.s1
217
+ },
218
+ // The visible rule is 4px; this row is the target it lives in.
219
+ seek: { height: theme.space.s5, justifyContent: "center" },
220
+ track: {
221
+ height: theme.space.s1,
222
+ borderRadius: theme.radius.full,
223
+ backgroundColor: theme.white.a9,
224
+ justifyContent: "center"
225
+ },
226
+ fill: {
227
+ position: "absolute",
228
+ start: 0,
229
+ top: 0,
230
+ bottom: 0,
231
+ borderRadius: theme.radius.full,
232
+ backgroundColor: theme.color.accent
233
+ },
234
+ knob: {
235
+ position: "absolute",
236
+ width: theme.space.s3,
237
+ height: theme.space.s3,
238
+ marginStart: -theme.space.s3 / 2,
239
+ borderRadius: theme.radius.full,
240
+ backgroundColor: theme.color.surface,
241
+ ...theme.shadow.knob
242
+ },
243
+ metaRow: { flexDirection: "row", alignItems: "center", justifyContent: "space-between" },
244
+ badges: { flexDirection: "row", alignItems: "center", gap: theme.space.s2 },
245
+ metaText: {
246
+ fontFamily: theme.font.mono,
247
+ fontSize: theme.type.caption2.fontSize,
248
+ lineHeight: theme.type.caption2.lineHeight,
249
+ // The scrim over the poster is a light-mid tone in the tested (light) theme,
250
+ // so a bright caption failed AA (~2.3:1). The on-surface `text` token clears
251
+ // AA over the scrim (≥6.5:1) and matches the poster's own dark label.
252
+ color: theme.color.text
253
+ },
254
+ footer: { padding: theme.space.s4, gap: theme.space.s1 },
255
+ title: {
256
+ fontFamily: theme.font.display,
257
+ fontWeight: theme.font.displayWeight,
258
+ fontSize: theme.type.title3.fontSize,
259
+ lineHeight: theme.type.title3.lineHeight,
260
+ letterSpacing: theme.type.title3.letterSpacing,
261
+ color: theme.color.text
262
+ },
263
+ metaLine: {
264
+ fontFamily: theme.font.sans,
265
+ fontSize: theme.type.footnote.fontSize,
266
+ lineHeight: theme.type.footnote.lineHeight,
267
+ color: theme.color.text3
268
+ }
269
+ }));
270
+ function cover() {
271
+ return { position: "absolute", left: 0, right: 0, top: 0, bottom: 0 };
272
+ }
273
+
274
+ export { VideoPlayer };
@@ -0,0 +1,349 @@
1
+ import { Icon } from './chunk-TKVFVF5D.js';
2
+ import * as React from 'react';
3
+ import { Dimensions, Animated, Easing, View, StyleSheet, Pressable, Text, Modal } from 'react-native';
4
+ import { createStyleSheet, useLoamTheme, useStyles } from '@btndprojects/loam-theme-native';
5
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
6
+
7
+ var PANEL_FRACTION = 0.78;
8
+ var SPEC = {
9
+ /** Panel top padding, before the safe-area inset is added. */
10
+ panelPadTop: 34,
11
+ /** Panel bottom padding. */
12
+ panelPadBottom: 22,
13
+ /** Gap between the panel's children (header → items, item → item). */
14
+ itemGap: 3,
15
+ /** Gap between the avatar and the name/email block. */
16
+ headerGap: 11,
17
+ /** Identity header padding: top / horizontal / bottom. */
18
+ headerPadTop: 6,
19
+ headerPadX: 10,
20
+ /** Gap between the name and the email. */
21
+ identityGap: 1,
22
+ /** Avatar diameter. */
23
+ avatar: 40,
24
+ /** Display-name size. */
25
+ nameSize: 14.5,
26
+ /** Email size. */
27
+ emailSize: 11.5
28
+ };
29
+ function NavDrawer({
30
+ open,
31
+ onClose,
32
+ items,
33
+ userName,
34
+ userEmail,
35
+ avatarInitials,
36
+ closeLabel = "Close navigation",
37
+ safeAreaTop = 0,
38
+ motion = "full",
39
+ inline = false
40
+ }) {
41
+ const theme = useLoamTheme();
42
+ const { styles } = useStyles(navDrawer);
43
+ const panelWidth = React.useMemo(() => Dimensions.get("window").width * PANEL_FRACTION, []);
44
+ const reducedMotion = theme.reduceMotion || motion === "reduced";
45
+ const instantMotion = motion === "none";
46
+ const [visible, setVisible] = React.useState(open);
47
+ const translateX = React.useRef(new Animated.Value(-panelWidth)).current;
48
+ const scrimOpacity = React.useRef(new Animated.Value(0)).current;
49
+ const panelOpacity = React.useRef(new Animated.Value(1)).current;
50
+ const slideDuration = theme.motion.enter.duration;
51
+ const slideEasing = React.useMemo(
52
+ () => Easing.bezier(...theme.motion.enter.easing),
53
+ [theme.motion.enter.easing]
54
+ );
55
+ const fadeDuration = theme.duration.base;
56
+ const fadeEasing = React.useMemo(() => Easing.bezier(...theme.easing.base), [theme.easing.base]);
57
+ const animateIn = React.useCallback(() => {
58
+ if (instantMotion) {
59
+ translateX.setValue(0);
60
+ scrimOpacity.setValue(1);
61
+ panelOpacity.setValue(1);
62
+ return;
63
+ }
64
+ if (reducedMotion) {
65
+ translateX.setValue(0);
66
+ panelOpacity.setValue(0);
67
+ scrimOpacity.setValue(0);
68
+ Animated.parallel([
69
+ Animated.timing(panelOpacity, {
70
+ toValue: 1,
71
+ duration: fadeDuration,
72
+ easing: fadeEasing,
73
+ useNativeDriver: true
74
+ }),
75
+ Animated.timing(scrimOpacity, {
76
+ toValue: 1,
77
+ duration: fadeDuration,
78
+ easing: fadeEasing,
79
+ useNativeDriver: true
80
+ })
81
+ ]).start();
82
+ return;
83
+ }
84
+ translateX.setValue(-panelWidth);
85
+ scrimOpacity.setValue(0);
86
+ panelOpacity.setValue(1);
87
+ Animated.parallel([
88
+ Animated.timing(translateX, {
89
+ toValue: 0,
90
+ duration: slideDuration,
91
+ easing: slideEasing,
92
+ useNativeDriver: true
93
+ }),
94
+ Animated.timing(scrimOpacity, {
95
+ toValue: 1,
96
+ duration: fadeDuration,
97
+ easing: fadeEasing,
98
+ useNativeDriver: true
99
+ })
100
+ ]).start();
101
+ }, [
102
+ translateX,
103
+ scrimOpacity,
104
+ panelOpacity,
105
+ panelWidth,
106
+ slideDuration,
107
+ slideEasing,
108
+ fadeDuration,
109
+ fadeEasing,
110
+ reducedMotion,
111
+ instantMotion
112
+ ]);
113
+ const animateOut = React.useCallback(() => {
114
+ if (instantMotion) {
115
+ scrimOpacity.setValue(0);
116
+ panelOpacity.setValue(0);
117
+ setVisible(false);
118
+ return;
119
+ }
120
+ if (reducedMotion) {
121
+ Animated.parallel([
122
+ Animated.timing(panelOpacity, {
123
+ toValue: 0,
124
+ duration: fadeDuration,
125
+ easing: fadeEasing,
126
+ useNativeDriver: true
127
+ }),
128
+ Animated.timing(scrimOpacity, {
129
+ toValue: 0,
130
+ duration: fadeDuration,
131
+ easing: fadeEasing,
132
+ useNativeDriver: true
133
+ })
134
+ ]).start(({ finished }) => {
135
+ if (finished) setVisible(false);
136
+ });
137
+ return;
138
+ }
139
+ Animated.parallel([
140
+ Animated.timing(translateX, {
141
+ toValue: -panelWidth,
142
+ duration: slideDuration,
143
+ easing: slideEasing,
144
+ useNativeDriver: true
145
+ }),
146
+ Animated.timing(scrimOpacity, {
147
+ toValue: 0,
148
+ duration: fadeDuration,
149
+ easing: fadeEasing,
150
+ useNativeDriver: true
151
+ })
152
+ ]).start(({ finished }) => {
153
+ if (finished) setVisible(false);
154
+ });
155
+ }, [
156
+ translateX,
157
+ scrimOpacity,
158
+ panelOpacity,
159
+ panelWidth,
160
+ slideDuration,
161
+ slideEasing,
162
+ fadeDuration,
163
+ fadeEasing,
164
+ reducedMotion,
165
+ instantMotion
166
+ ]);
167
+ React.useEffect(() => {
168
+ if (open) {
169
+ setVisible(true);
170
+ } else if (visible) {
171
+ animateOut();
172
+ }
173
+ }, [open]);
174
+ React.useEffect(() => {
175
+ if (visible && open) animateIn();
176
+ }, [visible]);
177
+ const handleItemPress = (item) => {
178
+ item.onPress?.();
179
+ onClose();
180
+ };
181
+ const body = /* @__PURE__ */ jsxs(View, { style: [styles.root, inline && StyleSheet.absoluteFill], children: [
182
+ /* @__PURE__ */ jsx(Animated.View, { style: [StyleSheet.absoluteFill, styles.scrim, { opacity: scrimOpacity }], children: /* @__PURE__ */ jsx(
183
+ Pressable,
184
+ {
185
+ style: StyleSheet.absoluteFill,
186
+ accessibilityRole: "button",
187
+ accessibilityLabel: closeLabel,
188
+ onPress: onClose
189
+ }
190
+ ) }),
191
+ /* @__PURE__ */ jsxs(
192
+ Animated.View,
193
+ {
194
+ accessibilityViewIsModal: true,
195
+ accessibilityRole: "menu",
196
+ accessibilityLabel: userName ?? "Navigation",
197
+ style: [
198
+ styles.panel,
199
+ {
200
+ width: inline ? `${PANEL_FRACTION * 100}%` : panelWidth,
201
+ paddingTop: SPEC.panelPadTop + safeAreaTop,
202
+ opacity: panelOpacity,
203
+ transform: [{ translateX }]
204
+ }
205
+ ],
206
+ children: [
207
+ (avatarInitials || userName || userEmail) && /* @__PURE__ */ jsxs(View, { style: styles.header, children: [
208
+ avatarInitials ? /* @__PURE__ */ jsx(View, { style: styles.avatar, children: /* @__PURE__ */ jsx(Text, { style: styles.avatarText, children: avatarInitials }) }) : null,
209
+ /* @__PURE__ */ jsxs(View, { style: styles.identity, children: [
210
+ userName ? /* @__PURE__ */ jsx(Text, { style: styles.userName, numberOfLines: 1, children: userName }) : null,
211
+ userEmail ? /* @__PURE__ */ jsx(Text, { style: styles.userEmail, numberOfLines: 1, children: userEmail }) : null
212
+ ] })
213
+ ] }),
214
+ /* @__PURE__ */ jsx(View, { style: styles.list, children: items.map((item, i) => {
215
+ const active = item.active ?? false;
216
+ const fg = active ? theme.color.accentOnSoft : theme.color.text;
217
+ return /* @__PURE__ */ jsxs(
218
+ Pressable,
219
+ {
220
+ accessibilityRole: "menuitem",
221
+ accessibilityLabel: item.label,
222
+ accessibilityState: { selected: active },
223
+ onPress: () => handleItemPress(item),
224
+ style: [styles.item, active ? styles.itemActive : null],
225
+ children: [
226
+ /* @__PURE__ */ jsx(Icon, { name: item.icon, size: 20, color: fg }),
227
+ /* @__PURE__ */ jsx(Text, { style: [styles.itemLabel, { color: fg }], numberOfLines: 1, children: item.label })
228
+ ]
229
+ },
230
+ `${item.label}-${i}`
231
+ );
232
+ }) })
233
+ ]
234
+ }
235
+ )
236
+ ] });
237
+ if (inline) return visible ? body : /* @__PURE__ */ jsx(Fragment, {});
238
+ return /* @__PURE__ */ jsx(
239
+ Modal,
240
+ {
241
+ visible,
242
+ transparent: true,
243
+ animationType: "none",
244
+ onRequestClose: onClose,
245
+ statusBarTranslucent: true,
246
+ children: body
247
+ }
248
+ );
249
+ }
250
+ var navDrawer = createStyleSheet((theme) => ({
251
+ root: {
252
+ flex: 1,
253
+ flexDirection: "row"
254
+ },
255
+ scrim: {
256
+ backgroundColor: theme.color.scrim,
257
+ zIndex: theme.z.overlay
258
+ },
259
+ // `paddingTop` is applied at the use site — it folds in the safe-area inset.
260
+ // loam-contrast-ignore — the boundary here is the scrim, not the shadow.
261
+ // `color.scrim` is a 44% wash and is *not* re-resolved by high contrast, so
262
+ // this surface is still separated from the page when the elevation zeroes
263
+ // out. A border would put a hairline around every modal for no gain.
264
+ panel: {
265
+ height: "100%",
266
+ backgroundColor: theme.color.surface,
267
+ paddingHorizontal: theme.space.s3,
268
+ paddingBottom: SPEC.panelPadBottom,
269
+ gap: SPEC.itemGap,
270
+ zIndex: theme.z.modal,
271
+ ...theme.shadow.lg
272
+ },
273
+ header: {
274
+ flexDirection: "row",
275
+ alignItems: "center",
276
+ minWidth: 0,
277
+ gap: SPEC.headerGap,
278
+ paddingTop: SPEC.headerPadTop,
279
+ paddingHorizontal: SPEC.headerPadX,
280
+ paddingBottom: theme.space.s4
281
+ },
282
+ avatar: {
283
+ width: SPEC.avatar,
284
+ height: SPEC.avatar,
285
+ borderRadius: theme.radius.full,
286
+ backgroundColor: theme.color.accent,
287
+ alignItems: "center",
288
+ justifyContent: "center"
289
+ },
290
+ avatarText: {
291
+ fontFamily: theme.font.sans,
292
+ fontWeight: theme.fontWeight.bold,
293
+ fontSize: theme.fontSize[14],
294
+ color: theme.color.accentInk
295
+ },
296
+ // `minWidth: 0` lets a long name/email truncate instead of widening the row.
297
+ identity: {
298
+ flex: 1,
299
+ minWidth: 0,
300
+ flexDirection: "column",
301
+ gap: SPEC.identityGap
302
+ },
303
+ userName: {
304
+ fontFamily: theme.font.sans,
305
+ fontWeight: theme.fontWeight.bold,
306
+ fontSize: SPEC.nameSize,
307
+ lineHeight: SPEC.nameSize * theme.lineHeight.snug,
308
+ color: theme.color.text
309
+ },
310
+ userEmail: {
311
+ fontFamily: theme.font.sans,
312
+ fontSize: SPEC.emailSize,
313
+ lineHeight: SPEC.emailSize * theme.lineHeight.snug,
314
+ color: theme.color.text2
315
+ },
316
+ // Horizontal padding now lives on the panel, so the rows sit inside its 12px.
317
+ list: {
318
+ gap: SPEC.itemGap
319
+ },
320
+ item: {
321
+ flexDirection: "row",
322
+ alignItems: "center",
323
+ // Reference (buildDrawer): `gap:14px` — 14px is a step of the spacing ramp.
324
+ gap: theme.space[14],
325
+ // Reference: `padding:13px 12px` — use paddingVertical instead of fixed height.
326
+ paddingVertical: theme.space[13],
327
+ paddingHorizontal: theme.space.s3,
328
+ borderRadius: theme.radius.md,
329
+ backgroundColor: "transparent",
330
+ // Reserved on every row, not just the active one: a border that appears with
331
+ // the state would move every label 1px when the selection changes.
332
+ borderWidth: theme.borderWidth.base,
333
+ borderColor: "transparent"
334
+ },
335
+ itemActive: {
336
+ backgroundColor: theme.color.accentSoft,
337
+ // Which item you are on is carried by a fill alone, and a fill is what high
338
+ // contrast re-resolves away — the accent edge is what still says "here".
339
+ borderColor: theme.edge.accent
340
+ },
341
+ itemLabel: {
342
+ fontFamily: theme.font.sans,
343
+ fontWeight: theme.fontWeight.semibold,
344
+ // Reference (buildDrawer): `font-size:15px` — 15px, read from the ramp rather than hardcoded.
345
+ fontSize: theme.fontSize[15]
346
+ }
347
+ }));
348
+
349
+ export { NavDrawer };