@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,394 @@
1
+ import { NO_UA_FOCUS_RING } from './chunk-3NMD74ZU.js';
2
+ import { useResolvedMotion } from './chunk-6N33LFQA.js';
3
+ import { Icon } from './chunk-TKVFVF5D.js';
4
+ import * as React from 'react';
5
+ import { View, TextInput, ScrollView, Text, Animated, Pressable, Easing } from 'react-native';
6
+ import { createStyleSheet, useStyles, useLoamTheme } from '@btndprojects/loam-theme-native';
7
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
8
+
9
+ var LEADING_ICON = 18;
10
+ var CANCEL_WIDTH = 52;
11
+ var RECENTS_MAX_H = 120;
12
+ function SearchBar({
13
+ data,
14
+ recents = [],
15
+ onSelect,
16
+ placeholder = "Search",
17
+ value,
18
+ defaultValue = "",
19
+ onChangeText
20
+ }) {
21
+ const { styles } = useStyles(sheet);
22
+ const theme = useLoamTheme();
23
+ const iconColor = theme.color.text3;
24
+ const [focused, setFocused] = React.useState(false);
25
+ const isControlled = value !== void 0;
26
+ const [internal, setInternal] = React.useState(defaultValue);
27
+ const query = isControlled ? value : internal;
28
+ const handleChangeText = (text) => {
29
+ if (!isControlled) setInternal(text);
30
+ onChangeText?.(text);
31
+ };
32
+ const isEmpty = query.length === 0;
33
+ const needle = query.toLowerCase();
34
+ const matches = isEmpty ? [] : data.filter((item) => item.toLowerCase().includes(needle));
35
+ const renderRow = (item, leading) => /* @__PURE__ */ jsxs(
36
+ Pressable,
37
+ {
38
+ accessibilityRole: "button",
39
+ accessibilityLabel: item,
40
+ onPress: onSelect ? () => onSelect(item) : void 0,
41
+ style: ({ pressed }) => [styles.row, pressed ? styles.rowPressed : null],
42
+ children: [
43
+ /* @__PURE__ */ jsx(Icon, { name: leading, size: LEADING_ICON, color: iconColor }),
44
+ /* @__PURE__ */ jsx(Text, { style: styles.rowLabel, numberOfLines: 1, children: item }),
45
+ /* @__PURE__ */ jsx(View, { style: styles.goIcon, children: /* @__PURE__ */ jsx(Icon, { name: "arrow-right", size: theme.icon.sm, color: iconColor }) })
46
+ ]
47
+ },
48
+ item
49
+ );
50
+ return /* @__PURE__ */ jsxs(View, { style: styles.container, children: [
51
+ /* @__PURE__ */ jsxs(View, { style: [styles.field, focused ? styles.fieldFocused : null], children: [
52
+ /* @__PURE__ */ jsx(Icon, { name: "search", size: LEADING_ICON, color: iconColor }),
53
+ /* @__PURE__ */ jsx(
54
+ TextInput,
55
+ {
56
+ style: [styles.input, NO_UA_FOCUS_RING],
57
+ value: query,
58
+ onChangeText: handleChangeText,
59
+ onFocus: () => setFocused(true),
60
+ onBlur: () => setFocused(false),
61
+ placeholder,
62
+ placeholderTextColor: theme.color.text3
63
+ }
64
+ )
65
+ ] }),
66
+ /* @__PURE__ */ jsx(ScrollView, { style: styles.results, contentContainerStyle: styles.resultsContent, children: isEmpty ? /* @__PURE__ */ jsxs(Fragment, { children: [
67
+ /* @__PURE__ */ jsx(Text, { style: styles.recentHeader, children: "Recent" }),
68
+ recents.map((item) => renderRow(item, "more-horizontal"))
69
+ ] }) : matches.length > 0 ? matches.map((item) => renderRow(item, "search")) : /* @__PURE__ */ jsxs(Text, { style: styles.empty, children: [
70
+ 'No matches for "',
71
+ query,
72
+ '"'
73
+ ] }) })
74
+ ] });
75
+ }
76
+ function SearchExpand({
77
+ recents = [],
78
+ placeholder = "Search",
79
+ value,
80
+ defaultValue = "",
81
+ onChangeText,
82
+ onCancel,
83
+ onSelectRecent,
84
+ motion = "full"
85
+ }) {
86
+ const { styles: sx } = useStyles(expandSheet);
87
+ const theme = useLoamTheme();
88
+ const resolved = useResolvedMotion(motion);
89
+ const animateTravel = resolved === "full";
90
+ const animateFade = resolved !== "none";
91
+ const isControlled = value !== void 0;
92
+ const [internal, setInternal] = React.useState(defaultValue);
93
+ const query = isControlled ? value : internal;
94
+ const handleChangeText = (text) => {
95
+ if (!isControlled) setInternal(text);
96
+ onChangeText?.(text);
97
+ };
98
+ const [focused, setFocused] = React.useState(false);
99
+ const cancelWidth = React.useRef(new Animated.Value(0)).current;
100
+ const cancelOpacity = React.useRef(new Animated.Value(0)).current;
101
+ const recentsHeight = React.useRef(new Animated.Value(0)).current;
102
+ const recentsOpacity = React.useRef(new Animated.Value(0)).current;
103
+ const drive = (val, to, kind, useNativeDriver) => {
104
+ const enabled = kind === "fade" ? animateFade : animateTravel;
105
+ if (!enabled) {
106
+ val.setValue(to);
107
+ return null;
108
+ }
109
+ return Animated.timing(val, {
110
+ toValue: to,
111
+ duration: theme.motion.toggle.duration,
112
+ easing: Easing.bezier(...theme.motion.toggle.easing),
113
+ useNativeDriver
114
+ });
115
+ };
116
+ const runToggle = (open) => {
117
+ const anims = [
118
+ drive(cancelWidth, open ? CANCEL_WIDTH : 0, "travel", false),
119
+ drive(cancelOpacity, open ? 1 : 0, "fade", true),
120
+ drive(recentsHeight, open ? RECENTS_MAX_H : 0, "travel", false),
121
+ drive(recentsOpacity, open ? 1 : 0, "fade", true)
122
+ ].filter((a) => a !== null);
123
+ if (anims.length > 0) Animated.parallel(anims).start();
124
+ };
125
+ const openAnim = () => runToggle(true);
126
+ const closeAnim = () => runToggle(false);
127
+ const inputRef = React.useRef(null);
128
+ const handleFocus = () => {
129
+ setFocused(true);
130
+ openAnim();
131
+ };
132
+ const handleBlur = () => {
133
+ setTimeout(() => {
134
+ if (!query) {
135
+ setFocused(false);
136
+ closeAnim();
137
+ }
138
+ }, 150);
139
+ };
140
+ const handleCancel = () => {
141
+ if (!isControlled) setInternal("");
142
+ onChangeText?.("");
143
+ inputRef.current?.blur();
144
+ setFocused(false);
145
+ closeAnim();
146
+ onCancel?.();
147
+ };
148
+ const handleChipPress = (label) => {
149
+ if (!isControlled) setInternal(label);
150
+ onChangeText?.(label);
151
+ onSelectRecent?.(label);
152
+ };
153
+ return /* @__PURE__ */ jsxs(View, { style: sx.wrapper, children: [
154
+ /* @__PURE__ */ jsxs(
155
+ View,
156
+ {
157
+ style: [sx.bar, focused ? sx.barFocused : null],
158
+ accessibilityRole: "search",
159
+ children: [
160
+ /* @__PURE__ */ jsx(Icon, { name: "search", size: LEADING_ICON, color: theme.color.text3 }),
161
+ /* @__PURE__ */ jsx(
162
+ TextInput,
163
+ {
164
+ ref: inputRef,
165
+ style: [sx.input, NO_UA_FOCUS_RING],
166
+ value: query,
167
+ onChangeText: handleChangeText,
168
+ onFocus: handleFocus,
169
+ onBlur: handleBlur,
170
+ placeholder,
171
+ placeholderTextColor: theme.color.text3,
172
+ returnKeyType: "search"
173
+ }
174
+ ),
175
+ /* @__PURE__ */ jsx(
176
+ Animated.View,
177
+ {
178
+ style: {
179
+ width: cancelWidth,
180
+ overflow: "hidden",
181
+ pointerEvents: focused ? "auto" : "none"
182
+ },
183
+ children: /* @__PURE__ */ jsx(Animated.View, { style: { opacity: cancelOpacity }, children: /* @__PURE__ */ jsx(
184
+ Pressable,
185
+ {
186
+ accessibilityRole: "button",
187
+ accessibilityLabel: "Cancel",
188
+ onPress: handleCancel,
189
+ style: sx.cancelBtn,
190
+ children: /* @__PURE__ */ jsx(Text, { style: sx.cancelText, children: "Cancel" })
191
+ }
192
+ ) })
193
+ }
194
+ )
195
+ ]
196
+ }
197
+ ),
198
+ /* @__PURE__ */ jsx(
199
+ Animated.View,
200
+ {
201
+ style: [
202
+ sx.recentsWrap,
203
+ {
204
+ height: recentsHeight,
205
+ opacity: recentsOpacity,
206
+ pointerEvents: focused ? "auto" : "none"
207
+ }
208
+ ],
209
+ children: /* @__PURE__ */ jsx(View, { style: sx.chipsRow, children: recents.map((label) => /* @__PURE__ */ jsxs(
210
+ Pressable,
211
+ {
212
+ accessibilityRole: "button",
213
+ accessibilityLabel: label,
214
+ onPress: () => handleChipPress(label),
215
+ style: sx.chip,
216
+ children: [
217
+ /* @__PURE__ */ jsx(Icon, { name: "search", size: 13, color: theme.color.text3 }),
218
+ /* @__PURE__ */ jsx(Text, { style: sx.chipText, children: label })
219
+ ]
220
+ },
221
+ label
222
+ )) })
223
+ }
224
+ )
225
+ ] });
226
+ }
227
+ var sheet = createStyleSheet((theme) => ({
228
+ container: { gap: theme.space.s3 },
229
+ field: {
230
+ flexDirection: "row",
231
+ alignItems: "center",
232
+ gap: theme.space.s2,
233
+ // A prominent, full-width field: the touch tier of the control-size ramp
234
+ // (44pt), which also clears the tap minimum on its own.
235
+ height: theme.ctl.lg,
236
+ paddingHorizontal: theme.space.s4,
237
+ borderRadius: theme.radius.full,
238
+ backgroundColor: theme.color.surface2,
239
+ // Transparent at rest rather than absent, so gaining focus tints the edge
240
+ // instead of growing the field by 1.5px and nudging the row below.
241
+ borderWidth: theme.borderWidth.thick,
242
+ borderColor: "transparent"
243
+ },
244
+ // Same focused treatment as `SearchExpand`: the two sit in the same product.
245
+ // The edge is the *measured* ring colour, not the raw
246
+ // accent. `NO_UA_FOCUS_RING` removes the browser's own outline, so this edge
247
+ // is the **only** focus affordance the field has — which makes it a focus
248
+ // indicator under SC 1.4.11 and holds it to 3:1 against both the fill inside
249
+ // it and the ground outside. The raw accent misses that in 10 of the 42
250
+ // palette x mode x ground cases (worst 1.98:1 on `surface2`, four palettes).
251
+ // `focus.ringColor` is the same accent lifted only where it could not clear
252
+ // 3:1, so it is byte-identical in 9 of the 14 palette x mode combos and moves
253
+ // exactly the five that were failing.
254
+ fieldFocused: { borderColor: theme.focus.ringColor, backgroundColor: theme.color.surface },
255
+ input: {
256
+ flex: 1,
257
+ padding: 0,
258
+ color: theme.color.text,
259
+ fontFamily: theme.font.sans,
260
+ fontSize: theme.fontSize[16],
261
+ backgroundColor: "transparent"
262
+ },
263
+ results: { alignSelf: "stretch" },
264
+ resultsContent: { gap: theme.space.s1 },
265
+ recentHeader: {
266
+ fontFamily: theme.font.mono,
267
+ fontSize: theme.fontSize[12],
268
+ color: theme.color.text3,
269
+ textTransform: "uppercase",
270
+ // `theme.tracking.*` are em multiples, so they must be scaled by the size.
271
+ letterSpacing: theme.tracking.wider * theme.fontSize[12],
272
+ paddingHorizontal: theme.space.s3,
273
+ paddingVertical: theme.space.s2
274
+ },
275
+ row: {
276
+ flexDirection: "row",
277
+ alignItems: "center",
278
+ gap: theme.space.s3,
279
+ paddingHorizontal: theme.space.s3,
280
+ paddingVertical: theme.space.s3,
281
+ borderRadius: theme.radius.md,
282
+ backgroundColor: "transparent"
283
+ },
284
+ rowPressed: { backgroundColor: theme.color.surface2 },
285
+ rowLabel: {
286
+ flex: 1,
287
+ fontFamily: theme.font.sans,
288
+ fontSize: theme.fontSize[16],
289
+ color: theme.color.text
290
+ },
291
+ goIcon: { transform: [{ rotate: "-45deg" }] },
292
+ empty: {
293
+ fontFamily: theme.font.sans,
294
+ fontSize: theme.fontSize[14],
295
+ color: theme.color.text3,
296
+ textAlign: "center",
297
+ paddingHorizontal: theme.space.s5,
298
+ paddingVertical: theme.space.s6
299
+ }
300
+ }));
301
+ var expandSheet = createStyleSheet((theme) => ({
302
+ wrapper: {
303
+ gap: theme.space[12]
304
+ // reference: `gap:12px` (line 2399)
305
+ },
306
+ // Bar — resting state: surface2 pill, no visible border (transparent).
307
+ // Geometry: `padding:12px 15px`, `border-radius:var(--r-full)` (line 2400).
308
+ bar: {
309
+ flexDirection: "row",
310
+ alignItems: "center",
311
+ gap: theme.space[10],
312
+ // reference: `gap:10px`
313
+ paddingVertical: theme.space[12],
314
+ paddingHorizontal: theme.space[15],
315
+ borderRadius: theme.radius.full,
316
+ backgroundColor: theme.color.surface2,
317
+ borderWidth: theme.borderWidth.thick,
318
+ borderColor: "transparent"
319
+ },
320
+ // Focused state: accent border + surface background (reference line 2407),
321
+ // through `focus.ringColor` for the reason given on `fieldFocused` above.
322
+ barFocused: {
323
+ borderColor: theme.focus.ringColor,
324
+ backgroundColor: theme.color.surface
325
+ },
326
+ input: {
327
+ flex: 1,
328
+ minWidth: 0,
329
+ padding: 0,
330
+ color: theme.color.text,
331
+ fontFamily: theme.font.sans,
332
+ fontSize: theme.fontSize[15],
333
+ // reference: `font-size:15px` (line 2402)
334
+ backgroundColor: "transparent"
335
+ },
336
+ // Cancel button content (reference: `color:var(--accent)`, `font-weight:600`,
337
+ // `font-size:14px`, line 2403 — the colour deliberately diverges, see below).
338
+ cancelBtn: {
339
+ alignItems: "center",
340
+ justifyContent: "center",
341
+ paddingHorizontal: theme.space[4]
342
+ },
343
+ // The label is real text at 14px semibold, so it is held to AA's 4.5:1 — not
344
+ // the 3:1 a glyph gets — against the `surface` the bar paints while focused
345
+ // (the only time Cancel is visible). The raw accent the reference names here
346
+ // misses 4.5:1 in 40 of the 56 palette x mode x ground cases, bottoming out at
347
+ // 1.75:1, so this takes `accentStrong`, which is measured against exactly the
348
+ // reading grounds `surface`/`bg`/`surface-2` and clears AA on all of them by
349
+ // construction. Unlike a chart mark or a knob glyph, there is no polarity
350
+ // trap here: the bar's ground follows the colour mode.
351
+ cancelText: {
352
+ color: theme.color.accentStrong,
353
+ fontFamily: theme.font.sans,
354
+ fontWeight: theme.fontWeight.semibold,
355
+ fontSize: theme.fontSize[14],
356
+ // reference: `font-size:14px` (line 2403)
357
+ whiteSpace: "nowrap"
358
+ },
359
+ // Recents chip row — hidden by default, revealed via animated height.
360
+ // Reference: `display:flex;flex-wrap:wrap;gap:8px` (line 2405).
361
+ recentsWrap: {
362
+ overflow: "hidden"
363
+ },
364
+ chipsRow: {
365
+ flexDirection: "row",
366
+ flexWrap: "wrap",
367
+ gap: theme.space[8]
368
+ // reference: `gap:8px`
369
+ },
370
+ // Each chip: `padding:8px 14px`, `border-radius:999px`, `font-size:13px`,
371
+ // `font-weight:600`, `border:1px solid var(--border-strong)`, `color:var(--text-2)`
372
+ // (reference line 2406).
373
+ chip: {
374
+ flexDirection: "row",
375
+ alignItems: "center",
376
+ gap: theme.space[7],
377
+ // reference: `gap:7px`
378
+ paddingVertical: theme.space[8],
379
+ paddingHorizontal: theme.space[14],
380
+ borderRadius: theme.radius.full,
381
+ borderWidth: theme.borderWidth.thin,
382
+ borderColor: theme.color.borderStrong,
383
+ backgroundColor: "transparent"
384
+ },
385
+ chipText: {
386
+ fontFamily: theme.font.sans,
387
+ fontWeight: theme.fontWeight.semibold,
388
+ fontSize: theme.fontSize[13],
389
+ // reference: `font-size:13px`
390
+ color: theme.color.text2
391
+ }
392
+ }));
393
+
394
+ export { SearchBar, SearchExpand };
@@ -0,0 +1,141 @@
1
+ import * as React from 'react';
2
+ import { View, Animated, Easing, ScrollView } from 'react-native';
3
+ import { Svg, Defs, LinearGradient, Stop, Rect } from 'react-native-svg';
4
+ import { createStyleSheet, useStyles, useLoamTheme } from '@btndprojects/loam-theme-native';
5
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
6
+
7
+ // src/scroll-area/scroll-area.tsx
8
+ var FADE_SIZE = 32;
9
+ function ScrollArea({
10
+ children,
11
+ orientation = "vertical",
12
+ fade = false,
13
+ maxHeight,
14
+ maxWidth,
15
+ motion = "full",
16
+ accessibilityLabel
17
+ }) {
18
+ const { styles } = useStyles(sheet);
19
+ const theme = useLoamTheme();
20
+ const horizontal = orientation === "horizontal";
21
+ const axis = horizontal ? "x" : "y";
22
+ const [container, setContainer] = React.useState(0);
23
+ const [content, setContent] = React.useState(0);
24
+ const [offset, setOffset] = React.useState(0);
25
+ const showStart = fade && offset > 1;
26
+ const showEnd = fade && content - container - offset > 1;
27
+ const onLayout = React.useCallback(
28
+ (e) => {
29
+ const { width, height } = e.nativeEvent.layout;
30
+ setContainer(axis === "x" ? width : height);
31
+ },
32
+ [axis]
33
+ );
34
+ const onContentSizeChange = React.useCallback(
35
+ (w, h) => setContent(axis === "x" ? w : h),
36
+ [axis]
37
+ );
38
+ const onScroll = React.useCallback(
39
+ (e) => {
40
+ const o = e.nativeEvent.contentOffset;
41
+ setOffset(axis === "x" ? o.x : o.y);
42
+ },
43
+ [axis]
44
+ );
45
+ const scroller = /* @__PURE__ */ jsx(
46
+ ScrollView,
47
+ {
48
+ focusable: true,
49
+ horizontal,
50
+ showsVerticalScrollIndicator: !horizontal,
51
+ showsHorizontalScrollIndicator: horizontal,
52
+ onLayout,
53
+ onContentSizeChange,
54
+ onScroll: fade ? onScroll : void 0,
55
+ scrollEventThrottle: 16,
56
+ children: orientation === "both" ? /* @__PURE__ */ jsx(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: true, focusable: true, children }) : children
57
+ }
58
+ );
59
+ return /* @__PURE__ */ jsxs(
60
+ View,
61
+ {
62
+ role: "group",
63
+ accessibilityLabel,
64
+ onLayout: horizontal ? void 0 : onLayout,
65
+ style: [styles.root, maxHeight != null && { maxHeight }, maxWidth != null && { maxWidth }],
66
+ children: [
67
+ scroller,
68
+ fade ? /* @__PURE__ */ jsxs(Fragment, { children: [
69
+ /* @__PURE__ */ jsx(
70
+ Fade,
71
+ {
72
+ side: horizontal ? "start" : "top",
73
+ color: theme.color.bg,
74
+ visible: showStart,
75
+ motion,
76
+ hoverDuration: theme.motion.hover.duration,
77
+ hoverEasing: theme.motion.hover.easing
78
+ }
79
+ ),
80
+ /* @__PURE__ */ jsx(
81
+ Fade,
82
+ {
83
+ side: horizontal ? "end" : "bottom",
84
+ color: theme.color.bg,
85
+ visible: showEnd,
86
+ motion,
87
+ hoverDuration: theme.motion.hover.duration,
88
+ hoverEasing: theme.motion.hover.easing
89
+ }
90
+ )
91
+ ] }) : null
92
+ ]
93
+ }
94
+ );
95
+ }
96
+ function Fade({
97
+ side,
98
+ color,
99
+ visible,
100
+ motion,
101
+ hoverDuration,
102
+ hoverEasing
103
+ }) {
104
+ const opacity = React.useRef(new Animated.Value(visible ? 1 : 0)).current;
105
+ React.useEffect(() => {
106
+ const to = visible ? 1 : 0;
107
+ if (motion === "full") {
108
+ Animated.timing(opacity, {
109
+ toValue: to,
110
+ duration: hoverDuration,
111
+ easing: Easing.bezier(hoverEasing[0], hoverEasing[1], hoverEasing[2], hoverEasing[3]),
112
+ useNativeDriver: true
113
+ }).start();
114
+ } else {
115
+ opacity.setValue(to);
116
+ }
117
+ }, [visible, motion, opacity, hoverDuration, hoverEasing]);
118
+ const theme = useLoamTheme();
119
+ const fade = theme.space[FADE_SIZE];
120
+ const vertical = side === "top" || side === "bottom";
121
+ const solidAtStart = side === "top" || side === "start";
122
+ const coords = vertical ? { x1: "0", y1: "0", x2: "0", y2: "1" } : { x1: "0", y1: "0", x2: "1", y2: "0" };
123
+ const box = side === "top" ? { top: 0, left: 0, right: 0, height: fade } : side === "bottom" ? { bottom: 0, left: 0, right: 0, height: fade } : side === "start" ? { top: 0, bottom: 0, start: 0, width: fade } : { top: 0, bottom: 0, end: 0, width: fade };
124
+ const id = `loam-scroll-fade-${side}`;
125
+ return /* @__PURE__ */ jsx(Animated.View, { style: [{ position: "absolute", opacity, pointerEvents: "none" }, box], children: /* @__PURE__ */ jsxs(Svg, { width: "100%", height: "100%", children: [
126
+ /* @__PURE__ */ jsx(Defs, { children: /* @__PURE__ */ jsxs(LinearGradient, { id, x1: coords.x1, y1: coords.y1, x2: coords.x2, y2: coords.y2, children: [
127
+ /* @__PURE__ */ jsx(Stop, { offset: "0", stopColor: color, stopOpacity: solidAtStart ? 1 : 0 }),
128
+ /* @__PURE__ */ jsx(Stop, { offset: "1", stopColor: color, stopOpacity: solidAtStart ? 0 : 1 })
129
+ ] }) }),
130
+ /* @__PURE__ */ jsx(Rect, { x: "0", y: "0", width: "100%", height: "100%", fill: `url(#${id})` })
131
+ ] }) });
132
+ }
133
+ var sheet = createStyleSheet((theme) => ({
134
+ root: {
135
+ flexShrink: 1,
136
+ overflow: "hidden",
137
+ borderRadius: theme.radius.md
138
+ }
139
+ }));
140
+
141
+ export { ScrollArea };
@@ -0,0 +1,83 @@
1
+ import 'react';
2
+ import { View, Text } from 'react-native';
3
+ import { createStyleSheet, useStyles } from '@btndprojects/loam-theme-native';
4
+ import { jsxs, jsx } from 'react/jsx-runtime';
5
+
6
+ // src/page-header/page-header.tsx
7
+ function PageHeader({
8
+ breadcrumb,
9
+ title,
10
+ titleAdornment,
11
+ meta,
12
+ actions,
13
+ back,
14
+ tabs,
15
+ // Accepted for API consistency; intentionally unused (see PageHeaderMotion).
16
+ motion: _motion = "none"
17
+ }) {
18
+ const { styles } = useStyles(sheet);
19
+ return /* @__PURE__ */ jsxs(View, { style: styles.container, children: [
20
+ back != null ? /* @__PURE__ */ jsx(View, { style: styles.backRow, children: back }) : null,
21
+ breadcrumb != null ? /* @__PURE__ */ jsx(View, { children: breadcrumb }) : null,
22
+ /* @__PURE__ */ jsxs(View, { style: styles.titleRow, children: [
23
+ /* @__PURE__ */ jsxs(View, { style: styles.titleGroup, children: [
24
+ /* @__PURE__ */ jsx(Text, { accessibilityRole: "header", style: styles.title, children: title }),
25
+ titleAdornment != null ? /* @__PURE__ */ jsx(View, { style: styles.adornment, children: titleAdornment }) : null
26
+ ] }),
27
+ actions != null ? /* @__PURE__ */ jsx(View, { style: styles.actions, children: actions }) : null
28
+ ] }),
29
+ meta != null ? typeof meta === "string" ? /* @__PURE__ */ jsx(Text, { style: styles.meta, children: meta }) : /* @__PURE__ */ jsx(View, { children: meta }) : null,
30
+ tabs != null ? /* @__PURE__ */ jsx(View, { style: styles.tabs, children: tabs }) : null
31
+ ] });
32
+ }
33
+ var sheet = createStyleSheet((theme) => ({
34
+ container: {
35
+ gap: theme.space.s3,
36
+ paddingHorizontal: theme.space.s4,
37
+ paddingVertical: theme.space.s4,
38
+ backgroundColor: theme.color.bg
39
+ },
40
+ backRow: { flexDirection: "row", alignItems: "center" },
41
+ // Wraps the actions block below the title on a narrow viewport.
42
+ titleRow: {
43
+ flexDirection: "row",
44
+ flexWrap: "wrap",
45
+ alignItems: "flex-start",
46
+ justifyContent: "space-between",
47
+ gap: theme.space.s3
48
+ },
49
+ // `flexShrink: 1` + `minWidth: 0` lets a long title ellipsize / wrap instead
50
+ // of pushing the actions off the row.
51
+ titleGroup: {
52
+ flexDirection: "row",
53
+ alignItems: "center",
54
+ flexShrink: 1,
55
+ minWidth: 0,
56
+ gap: theme.space.s2
57
+ },
58
+ // `title1` (28/34) is the content page-title role on the mobile ramp — one
59
+ // step below the app bar's `largeTitle`, so the two never read as the same
60
+ // element.
61
+ title: {
62
+ fontFamily: theme.font.display,
63
+ fontWeight: theme.font.displayWeight,
64
+ fontSize: theme.type.title1.fontSize,
65
+ lineHeight: theme.type.title1.lineHeight,
66
+ letterSpacing: theme.type.title1.letterSpacing,
67
+ color: theme.color.text,
68
+ flexShrink: 1
69
+ },
70
+ adornment: { flexDirection: "row", alignItems: "center" },
71
+ actions: { flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: theme.space.s2 },
72
+ // Secondary ink, callout size — the AA-safe muted line under the title.
73
+ meta: {
74
+ fontFamily: theme.font.sans,
75
+ fontSize: theme.type.callout.fontSize,
76
+ lineHeight: theme.type.callout.lineHeight,
77
+ letterSpacing: theme.type.callout.letterSpacing,
78
+ color: theme.color.text2
79
+ },
80
+ tabs: { marginTop: theme.space.s1 }
81
+ }));
82
+
83
+ export { PageHeader };