@honeypathkar/react-native-lq-bottom-bar 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.
- package/LICENSE +21 -0
- package/README.md +341 -2
- package/android/build.gradle +78 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/honeypathkar/lqbottombar/BackdropBlur.kt +428 -0
- package/android/src/main/java/com/honeypathkar/lqbottombar/LQBottomBarPackage.kt +34 -0
- package/android/src/main/java/com/honeypathkar/lqbottombar/LQBottomBarView.kt +503 -0
- package/android/src/main/java/com/honeypathkar/lqbottombar/LQBottomBarViewManager.kt +119 -0
- package/android/src/main/java/com/honeypathkar/lqbottombar/TabSelectedEvent.kt +30 -0
- package/ios/LQBottomBarView.swift +287 -0
- package/ios/LQBottomBarViewManager.m +21 -0
- package/ios/LQBottomBarViewManager.swift +36 -0
- package/lib/commonjs/NativeBottomBar.d.ts +33 -0
- package/lib/commonjs/NativeBottomBar.d.ts.map +1 -0
- package/lib/commonjs/NativeBottomBar.js +9 -0
- package/lib/commonjs/NativeBottomBar.js.map +1 -0
- package/lib/commonjs/index.d.ts +6 -0
- package/lib/commonjs/index.d.ts.map +1 -0
- package/lib/commonjs/index.js +131 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/rasterise.d.ts +20 -0
- package/lib/commonjs/rasterise.d.ts.map +1 -0
- package/lib/commonjs/rasterise.js +398 -0
- package/lib/commonjs/rasterise.js.map +1 -0
- package/lib/commonjs/types.d.ts +119 -0
- package/lib/commonjs/types.d.ts.map +1 -0
- package/lib/commonjs/types.js +3 -0
- package/lib/commonjs/types.js.map +1 -0
- package/package.json +65 -4
- package/react-native-lq-bottom-bar.podspec +27 -0
- package/src/NativeBottomBar.ts +43 -0
- package/src/index.tsx +139 -0
- package/src/rasterise.tsx +444 -0
- package/src/types.ts +136 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.useRasterisedIcons = void 0;
|
|
37
|
+
const react_1 = __importStar(require("react"));
|
|
38
|
+
const react_native_1 = require("react-native");
|
|
39
|
+
/**
|
|
40
|
+
* TURNING A REACT ICON INTO A UIImage.
|
|
41
|
+
*
|
|
42
|
+
* `UITabBar` renders its items natively and accepts only `UIImage`. That is
|
|
43
|
+
* not a limitation to argue with — it is the reason the bar looks and behaves
|
|
44
|
+
* like the system's own, since UIKit is drawing the items rather than
|
|
45
|
+
* compositing someone else's views over them.
|
|
46
|
+
*
|
|
47
|
+
* So the icon has to become an image BEFORE it reaches the bar. The way out
|
|
48
|
+
* is react-native-svg's `toDataURL()`: mount the icon off-screen once, ask the
|
|
49
|
+
* native SVG view to render itself into a PNG, hand the base64 to the bar,
|
|
50
|
+
* and remember the result. A lucide icon is a react-native-svg `<Svg>` with a
|
|
51
|
+
* forwarded ref, so one path covers lucide and hand-written SVG alike.
|
|
52
|
+
*
|
|
53
|
+
* THE ALTERNATIVE, for the record: overlay JS icons on top of the native bar.
|
|
54
|
+
* That accepts any renderer and needs none of this file — but it forfeits
|
|
55
|
+
* UIKit's selection animation, which is the thing the native bar is for. A
|
|
56
|
+
* one-off rasterisation is the cheaper trade.
|
|
57
|
+
*
|
|
58
|
+
* WHAT THIS COSTS. The first frame for an uncached icon has a title and no
|
|
59
|
+
* glyph. The cache is module-level, so it is one frame per distinct icon per
|
|
60
|
+
* app launch, not per mount — and a static `items` array, which is the normal
|
|
61
|
+
* case, pays it once at startup.
|
|
62
|
+
*/
|
|
63
|
+
/**
|
|
64
|
+
* Captured PNGs, keyed by icon identity. Module scope: see above.
|
|
65
|
+
*
|
|
66
|
+
* SUCCESSES ONLY. An earlier version also stored failures here, as an empty
|
|
67
|
+
* string, to stop an uncapturable icon being retried on every render. That
|
|
68
|
+
* was a trap: this map outlives every mount AND survives Fast Refresh, so a
|
|
69
|
+
* failure recorded once was permanent for the life of the JS context. Fixing
|
|
70
|
+
* the cause — an icon component that did not forward its ref — then changed
|
|
71
|
+
* nothing, because the poisoned entry was still there and the icon was never
|
|
72
|
+
* attempted again. Failures are tracked per mount instead; see `failed`.
|
|
73
|
+
*/
|
|
74
|
+
const cache = new Map();
|
|
75
|
+
/**
|
|
76
|
+
* Stable ids for component types, so a cache key can name "this icon
|
|
77
|
+
* component" without stringifying a function.
|
|
78
|
+
*
|
|
79
|
+
* Weak, because the key must not be the thing that keeps a lazily-imported
|
|
80
|
+
* icon module alive.
|
|
81
|
+
*/
|
|
82
|
+
const typeIds = new WeakMap();
|
|
83
|
+
let nextTypeId = 0;
|
|
84
|
+
const typeId = (type) => {
|
|
85
|
+
if (typeof type === 'string')
|
|
86
|
+
return type;
|
|
87
|
+
if (typeof type !== 'function' && typeof type !== 'object')
|
|
88
|
+
return 'anon';
|
|
89
|
+
if (type === null)
|
|
90
|
+
return 'anon';
|
|
91
|
+
const existing = typeIds.get(type);
|
|
92
|
+
if (existing)
|
|
93
|
+
return existing;
|
|
94
|
+
const id = `t${nextTypeId++}`;
|
|
95
|
+
typeIds.set(type, id);
|
|
96
|
+
return id;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* OPAQUE BLACK, and the colour genuinely does not matter.
|
|
100
|
+
*
|
|
101
|
+
* The capture is handed to UIKit as a TEMPLATE image: the alpha channel is
|
|
102
|
+
* the shape and the system chooses the colour. What does matter is that the
|
|
103
|
+
* colour is RESOLVABLE — lucide defaults `color` to the literal string
|
|
104
|
+
* `"currentColor"`, which resolves against the `<Svg>`'s own `color` prop, and
|
|
105
|
+
* with nothing to resolve against the glyph rasterises empty. Setting `color`
|
|
106
|
+
* on the root is what gives `currentColor` a value.
|
|
107
|
+
*/
|
|
108
|
+
const RASTER_COLOR = '#000000';
|
|
109
|
+
/**
|
|
110
|
+
* The dimension to ask `toDataURL` for, in whichever unit this platform means
|
|
111
|
+
* by it. See the long note at the call site for why they disagree.
|
|
112
|
+
*/
|
|
113
|
+
const captureSize = (size) => react_native_1.Platform.OS === 'android' ? Math.round(react_native_1.PixelRatio.get() * size) : size;
|
|
114
|
+
/** Props only ever serialised for the cache key, never compared deeply. */
|
|
115
|
+
const stableStringify = (value) => {
|
|
116
|
+
if (value === null || typeof value !== 'object') {
|
|
117
|
+
// A function in props cannot be serialised, and two renders produce two
|
|
118
|
+
// identities. Collapse it to its type: a caller whose icon varies by a
|
|
119
|
+
// callback should pass an explicit `key`.
|
|
120
|
+
return typeof value === 'function' ? 'fn' : String(value);
|
|
121
|
+
}
|
|
122
|
+
if (Array.isArray(value)) {
|
|
123
|
+
return `[${value.map(stableStringify).join(',')}]`;
|
|
124
|
+
}
|
|
125
|
+
const record = value;
|
|
126
|
+
const keys = Object.keys(record).sort();
|
|
127
|
+
return `{${keys
|
|
128
|
+
.map(k => `${k}:${stableStringify(record[k])}`)
|
|
129
|
+
.join(',')}}`;
|
|
130
|
+
};
|
|
131
|
+
/** True for the two icon forms that have to be drawn before iOS can use them. */
|
|
132
|
+
const needsRaster = (icon) => typeof icon === 'function' || (0, react_1.isValidElement)(icon);
|
|
133
|
+
const cacheKey = (item, size) => {
|
|
134
|
+
const { icon } = item;
|
|
135
|
+
if (!needsRaster(icon))
|
|
136
|
+
return null;
|
|
137
|
+
const base = item.key
|
|
138
|
+
? `k:${item.key}`
|
|
139
|
+
: typeof icon === 'function'
|
|
140
|
+
? `f:${typeId(icon)}`
|
|
141
|
+
: `e:${typeId(icon.type)}:${stableStringify(icon.props)}`;
|
|
142
|
+
return `${base}|${size}`;
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
145
|
+
* Resolve an icon to the element that should actually be drawn, sized and
|
|
146
|
+
* coloured for capture.
|
|
147
|
+
*/
|
|
148
|
+
const rasterElement = (icon, size) => {
|
|
149
|
+
var _a, _b, _c, _d, _e;
|
|
150
|
+
if (typeof icon === 'function') {
|
|
151
|
+
/*
|
|
152
|
+
ALWAYS THE UNSELECTED FACE. UIKit owns the selected appearance: it tints
|
|
153
|
+
the one template image it was given. Capturing the selected face too
|
|
154
|
+
would produce a second artwork fighting the system's own treatment —
|
|
155
|
+
which is also why `tintable` exists for icons that should opt out.
|
|
156
|
+
*/
|
|
157
|
+
return icon({ selected: false, color: RASTER_COLOR, size });
|
|
158
|
+
}
|
|
159
|
+
if (!(0, react_1.isValidElement)(icon))
|
|
160
|
+
return null;
|
|
161
|
+
const props = ((_a = icon.props) !== null && _a !== void 0 ? _a : {});
|
|
162
|
+
return (0, react_1.cloneElement)(icon, {
|
|
163
|
+
// Only filled in where the caller left them out, so an icon that sets its
|
|
164
|
+
// own size or colours keeps them.
|
|
165
|
+
size: (_b = props.size) !== null && _b !== void 0 ? _b : size,
|
|
166
|
+
width: (_c = props.width) !== null && _c !== void 0 ? _c : size,
|
|
167
|
+
height: (_d = props.height) !== null && _d !== void 0 ? _d : size,
|
|
168
|
+
color: (_e = props.color) !== null && _e !== void 0 ? _e : RASTER_COLOR,
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
/**
|
|
172
|
+
* How long to wait for a capture before re-issuing it, and how many times.
|
|
173
|
+
*
|
|
174
|
+
* Eight tries at 100ms is 800ms of patience before an icon is written off,
|
|
175
|
+
* which is generous for something that normally lands on the first frame and
|
|
176
|
+
* still short enough that a genuinely uncapturable icon does not hold the
|
|
177
|
+
* off-screen host mounted for the session.
|
|
178
|
+
*/
|
|
179
|
+
const RETRY_MS = 100;
|
|
180
|
+
const MAX_TRIES = 8;
|
|
181
|
+
/**
|
|
182
|
+
* One off-screen icon, captured once.
|
|
183
|
+
*
|
|
184
|
+
* `toDataURL` needs the NATIVE view to exist and, on Android, to have been
|
|
185
|
+
* drawn at least once — neither of which is true when the effect first runs:
|
|
186
|
+
* React has committed the tree, but the native view is mounted and laid out a
|
|
187
|
+
* frame later. Hence the frame of delay.
|
|
188
|
+
*
|
|
189
|
+
* THE WATCHDOG IS NOT BELT-AND-BRACES, it is the only thing covering the worst
|
|
190
|
+
* failure mode. On Android `SvgViewModule.toDataURL` checks `notRendered()`
|
|
191
|
+
* and, if the view has not drawn yet, parks the request as a task to be run
|
|
192
|
+
* from the next `onDraw` — and returns WITHOUT invoking the callback. So a
|
|
193
|
+
* retry driven from inside the callback cannot fire: nothing ever calls it.
|
|
194
|
+
* Re-issuing the whole request on a timer is what breaks that deadlock, and
|
|
195
|
+
* the attempt cap is what stops it running forever if the icon simply cannot
|
|
196
|
+
* be drawn.
|
|
197
|
+
*/
|
|
198
|
+
const Capture = ({ cacheKey: key, node, size, onCaptured }) => {
|
|
199
|
+
const ref = (0, react_1.useRef)(null);
|
|
200
|
+
/**
|
|
201
|
+
* Survives effect re-runs on purpose.
|
|
202
|
+
*
|
|
203
|
+
* `cancelled` below is per-run, so a re-run restarts a capture that has not
|
|
204
|
+
* landed. This one is per-COMPONENT, so a capture that already reported is
|
|
205
|
+
* never reported twice — which it would be if both flags were the same ref,
|
|
206
|
+
* and which the earlier version of this file got wrong in the other
|
|
207
|
+
* direction: a cleanup that set the shared flag killed every subsequent
|
|
208
|
+
* attempt, so any re-render before the first capture landed meant no glyph
|
|
209
|
+
* ever appeared.
|
|
210
|
+
*/
|
|
211
|
+
const reported = (0, react_1.useRef)(false);
|
|
212
|
+
(0, react_1.useEffect)(() => {
|
|
213
|
+
let cancelled = false;
|
|
214
|
+
let tries = 0;
|
|
215
|
+
let watchdog;
|
|
216
|
+
let frame = 0;
|
|
217
|
+
const report = (dataUri) => {
|
|
218
|
+
if (cancelled || reported.current)
|
|
219
|
+
return;
|
|
220
|
+
reported.current = true;
|
|
221
|
+
if (watchdog)
|
|
222
|
+
clearTimeout(watchdog);
|
|
223
|
+
onCaptured(key, dataUri);
|
|
224
|
+
};
|
|
225
|
+
const issue = () => {
|
|
226
|
+
if (cancelled || reported.current)
|
|
227
|
+
return;
|
|
228
|
+
if (tries >= MAX_TRIES) {
|
|
229
|
+
report(null);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
tries += 1;
|
|
233
|
+
const svg = ref.current;
|
|
234
|
+
if (!svg || typeof svg.toDataURL !== 'function') {
|
|
235
|
+
/*
|
|
236
|
+
Either the native view is not up yet, or this is not an <Svg> root at
|
|
237
|
+
all — the usual cause of the latter being an icon wrapped in a <View>,
|
|
238
|
+
where the ref lands on the wrapper and the SVG underneath is
|
|
239
|
+
unreachable. Both are retried, because the first resolves itself and
|
|
240
|
+
the second costs only the attempt budget before falling through to a
|
|
241
|
+
reported miss.
|
|
242
|
+
*/
|
|
243
|
+
watchdog = setTimeout(issue, RETRY_MS);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
watchdog = setTimeout(issue, RETRY_MS);
|
|
247
|
+
svg.toDataURL((base64) => {
|
|
248
|
+
if (base64) {
|
|
249
|
+
/*
|
|
250
|
+
`toDataURL` yields BARE base64, not a data URI — the name is a
|
|
251
|
+
long-standing misnomer in react-native-svg. The prefix is added
|
|
252
|
+
here so the native side receives something it recognises as an
|
|
253
|
+
image source.
|
|
254
|
+
*/
|
|
255
|
+
report(`data:image/png;base64,${base64}`);
|
|
256
|
+
}
|
|
257
|
+
// An empty result is left to the watchdog rather than retried here,
|
|
258
|
+
// so there is one retry path instead of two that can race.
|
|
259
|
+
},
|
|
260
|
+
/*
|
|
261
|
+
THE UNITS DIFFER BY PLATFORM, and getting it wrong fails quietly in
|
|
262
|
+
two different ways. Hence `captureSize`.
|
|
263
|
+
|
|
264
|
+
iOS takes POINTS. The renderer is a `UIGraphicsImageRenderer` at the
|
|
265
|
+
screen's scale, so a 24-point request yields a 24x24pt / 72x72px PNG
|
|
266
|
+
on a 3x device; PNG carries no scale, so the native side decodes it
|
|
267
|
+
with `UIScreen.main.scale` and the two cancel out. Asking for
|
|
268
|
+
`size * pixelRatio` here would double-apply the scale and draw the
|
|
269
|
+
glyph three times too large.
|
|
270
|
+
|
|
271
|
+
Android takes PIXELS. `SvgView.toDataURL(w, h)` allocates a bitmap of
|
|
272
|
+
exactly w by h pixels and then draws the SVG in the VIEW's own
|
|
273
|
+
coordinate space, which is already in pixels — so a 24-pixel bitmap
|
|
274
|
+
on a 3x device captures the top-left ninth of a 72-pixel glyph and
|
|
275
|
+
discards the rest. The bitmap has to be the full pixel size, and the
|
|
276
|
+
native side scales it back down to `size` dp when it draws.
|
|
277
|
+
*/
|
|
278
|
+
{ width: captureSize(size), height: captureSize(size) });
|
|
279
|
+
};
|
|
280
|
+
frame = requestAnimationFrame(issue);
|
|
281
|
+
return () => {
|
|
282
|
+
cancelled = true;
|
|
283
|
+
if (watchdog)
|
|
284
|
+
clearTimeout(watchdog);
|
|
285
|
+
cancelAnimationFrame(frame);
|
|
286
|
+
};
|
|
287
|
+
}, [key, size, onCaptured]);
|
|
288
|
+
return (0, react_1.cloneElement)(node, { ref });
|
|
289
|
+
};
|
|
290
|
+
const styles = react_native_1.StyleSheet.create({
|
|
291
|
+
/**
|
|
292
|
+
* OFF-SCREEN, not invisible, and NOT CLIPPED.
|
|
293
|
+
*
|
|
294
|
+
* `opacity: 0` and `display: 'none'` are both tempting and both wrong: the
|
|
295
|
+
* first relies on the renderer still drawing a fully transparent subtree,
|
|
296
|
+
* and the second detaches the native view so there is nothing left to
|
|
297
|
+
* capture. Moving it out of the viewport keeps a real, laid-out native view
|
|
298
|
+
* that the user cannot see.
|
|
299
|
+
*
|
|
300
|
+
* No size and no `overflow: 'hidden'` either, which an earlier version had.
|
|
301
|
+
* Pinning the host to 1x1 and clipping is the obvious way to keep it out of
|
|
302
|
+
* the way, but on Android a capture is only possible once the SVG has been
|
|
303
|
+
* drawn at least once — so clipping the icons out of the host is clipping
|
|
304
|
+
* away the very thing being waited on. Without a size the host sizes itself
|
|
305
|
+
* to its content, and being off-screen is enough on its own.
|
|
306
|
+
*/
|
|
307
|
+
host: {
|
|
308
|
+
position: 'absolute',
|
|
309
|
+
left: -10000,
|
|
310
|
+
top: -10000,
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
/**
|
|
314
|
+
* Capture every React-element icon in `items`, memoised by icon identity.
|
|
315
|
+
*
|
|
316
|
+
* Returns the captured sources plus the off-screen host that does the
|
|
317
|
+
* capturing — the caller has to render the host, since a hook cannot mount
|
|
318
|
+
* views on its own.
|
|
319
|
+
*/
|
|
320
|
+
const useRasterisedIcons = (items, size) => {
|
|
321
|
+
const keys = (0, react_1.useMemo)(() => items.map(item => cacheKey(item, size)), [items, size]);
|
|
322
|
+
/**
|
|
323
|
+
* Captures that landed during THIS mount.
|
|
324
|
+
*
|
|
325
|
+
* The module cache is the source of truth and survives unmounting, but a
|
|
326
|
+
* write to it is invisible to the reconciler. This mirror is what actually
|
|
327
|
+
* triggers the re-render; it is only ever added to, and it is seeded from
|
|
328
|
+
* nothing because a warm cache needs no re-render — `sources` reads it
|
|
329
|
+
* directly on the first pass.
|
|
330
|
+
*/
|
|
331
|
+
const [captured, setCaptured] = (0, react_1.useState)({});
|
|
332
|
+
/**
|
|
333
|
+
* Icons that could not be captured DURING THIS MOUNT.
|
|
334
|
+
*
|
|
335
|
+
* Held in a ref rather than in the module cache so the scope is right in
|
|
336
|
+
* both directions: within a mount an icon is attempted once, so a glyph that
|
|
337
|
+
* genuinely cannot be drawn does not spin; across mounts the slate is clean,
|
|
338
|
+
* so remounting the bar — or a Fast Refresh that fixed the icon — gets
|
|
339
|
+
* another go. A permanent record of failure is what made a fixed icon stay
|
|
340
|
+
* broken until the app was killed.
|
|
341
|
+
*/
|
|
342
|
+
const failed = (0, react_1.useRef)(new Set());
|
|
343
|
+
const onCaptured = (0, react_1.useCallback)((key, dataUri) => {
|
|
344
|
+
if (dataUri) {
|
|
345
|
+
cache.set(key, dataUri);
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
failed.current.add(key);
|
|
349
|
+
if (__DEV__) {
|
|
350
|
+
/*
|
|
351
|
+
LOUD, because the failure is otherwise invisible: the item renders
|
|
352
|
+
with its title and no glyph, which reads as a styling problem rather
|
|
353
|
+
than a capture that never happened.
|
|
354
|
+
*/
|
|
355
|
+
console.warn(`[lq-bottom-bar] Could not rasterise the icon for "${key}". ` +
|
|
356
|
+
'The icon must be a react-native-svg <Svg> element, or a component ' +
|
|
357
|
+
'that forwards its ref to one — a plain function component drops ' +
|
|
358
|
+
'the ref and cannot be captured. Wrap it in React.forwardRef.');
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
setCaptured(prev => ({ ...prev, [key]: dataUri !== null && dataUri !== void 0 ? dataUri : '' }));
|
|
362
|
+
}, []);
|
|
363
|
+
const sources = (0, react_1.useMemo)(() => keys.map(key => {
|
|
364
|
+
var _a;
|
|
365
|
+
if (!key)
|
|
366
|
+
return null;
|
|
367
|
+
return (_a = cache.get(key)) !== null && _a !== void 0 ? _a : null;
|
|
368
|
+
}),
|
|
369
|
+
// `captured` is what makes this re-run; the cache it reads is module state.
|
|
370
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
371
|
+
[keys, captured]);
|
|
372
|
+
const pending = (0, react_1.useMemo)(() => keys
|
|
373
|
+
.map((key, index) => ({ key, index }))
|
|
374
|
+
.filter((entry) => entry.key !== null &&
|
|
375
|
+
!cache.has(entry.key) &&
|
|
376
|
+
!failed.current.has(entry.key)),
|
|
377
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
378
|
+
[keys, captured]);
|
|
379
|
+
const host = pending.length === 0 ? null : (react_1.default.createElement(react_native_1.View, { style: styles.host, pointerEvents: "none", collapsable: false }, pending.map(({ key, index }) => {
|
|
380
|
+
const node = rasterElement(items[index].icon, size);
|
|
381
|
+
if (!node)
|
|
382
|
+
return null;
|
|
383
|
+
return (
|
|
384
|
+
/*
|
|
385
|
+
`onCaptured` IS PASSED AS-IS, not wrapped in an arrow.
|
|
386
|
+
|
|
387
|
+
An inline `dataUri => onCaptured(key, dataUri)` is a new function
|
|
388
|
+
identity on every render, which lands in `Capture`'s effect deps
|
|
389
|
+
and re-runs the effect — tearing down an in-flight capture on
|
|
390
|
+
every parent render. The key travels as a prop instead so the
|
|
391
|
+
callback can stay stable.
|
|
392
|
+
*/
|
|
393
|
+
react_1.default.createElement(Capture, { key: key, cacheKey: key, node: node, size: size, onCaptured: onCaptured }));
|
|
394
|
+
})));
|
|
395
|
+
return { sources, host };
|
|
396
|
+
};
|
|
397
|
+
exports.useRasterisedIcons = useRasterisedIcons;
|
|
398
|
+
//# sourceMappingURL=rasterise.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rasterise.js","sourceRoot":"","sources":["../../src/rasterise.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
import type { ImageSourcePropType, StyleProp, ViewProps, ViewStyle } from 'react-native';
|
|
3
|
+
/**
|
|
4
|
+
* What an item's icon can be.
|
|
5
|
+
*
|
|
6
|
+
* The union is wide because the two platforms want opposite things and the
|
|
7
|
+
* caller should not have to care which.
|
|
8
|
+
*
|
|
9
|
+
* A native `UITabBar` draws its own items and accepts only `UIImage`, so a
|
|
10
|
+
* React element has to BECOME an image before it reaches the bar. The JS
|
|
11
|
+
* Android bar has no such constraint and renders the element directly, at
|
|
12
|
+
* full fidelity. `src/rasterise.tsx` is the bridge between those two facts.
|
|
13
|
+
*/
|
|
14
|
+
export type LQIcon =
|
|
15
|
+
/**
|
|
16
|
+
* A react-native-svg element — which is what a lucide icon is, under the
|
|
17
|
+
* hood. Rasterised once on iOS, rendered live on Android.
|
|
18
|
+
*/
|
|
19
|
+
ReactElement
|
|
20
|
+
/**
|
|
21
|
+
* The same, but told whether it is selected. Preferred over a bare element
|
|
22
|
+
* when the icon changes shape (not just tint) between states: an outline
|
|
23
|
+
* glyph when idle and a filled one when active, which is what this app's
|
|
24
|
+
* own set does.
|
|
25
|
+
*
|
|
26
|
+
* iOS calls this ONCE, with `selected: false`, because UIKit owns the
|
|
27
|
+
* selected appearance — see `tintable`.
|
|
28
|
+
*/
|
|
29
|
+
| ((state: LQIconState) => ReactElement)
|
|
30
|
+
/** `require('./home.png')`, or `{ uri }`. */
|
|
31
|
+
| ImageSourcePropType
|
|
32
|
+
/** A name in the app's own asset catalogue. iOS only. */
|
|
33
|
+
| {
|
|
34
|
+
name: string;
|
|
35
|
+
}
|
|
36
|
+
/** An SF Symbol. iOS only; ignored on Android. */
|
|
37
|
+
| {
|
|
38
|
+
sfSymbol: string;
|
|
39
|
+
};
|
|
40
|
+
export interface LQIconState {
|
|
41
|
+
selected: boolean;
|
|
42
|
+
/** `selectedColor` when selected, `unselectedColor` otherwise. */
|
|
43
|
+
color: string;
|
|
44
|
+
size: number;
|
|
45
|
+
}
|
|
46
|
+
export interface LQBottomBarItem {
|
|
47
|
+
/** Shown under the glyph unless `labeled` is false. */
|
|
48
|
+
title?: string;
|
|
49
|
+
icon: LQIcon;
|
|
50
|
+
/**
|
|
51
|
+
* A STABLE identity for this item's icon, and the cache key the
|
|
52
|
+
* rasteriser memoises on.
|
|
53
|
+
*
|
|
54
|
+
* Without it the key is derived from the icon's component type and its
|
|
55
|
+
* props, which is correct but costs a serialisation per render and misses
|
|
56
|
+
* when a prop is a function or a style object. Pass this whenever the item
|
|
57
|
+
* list is static, which it almost always is.
|
|
58
|
+
*/
|
|
59
|
+
key?: string;
|
|
60
|
+
/**
|
|
61
|
+
* Whether UIKit may TINT this glyph, iOS only. Default true.
|
|
62
|
+
*
|
|
63
|
+
* True renders the image as a template: the alpha channel is the shape and
|
|
64
|
+
* the system picks the colour, selected against unselected. That is what
|
|
65
|
+
* makes one artwork cover both states and what makes the glyph follow the
|
|
66
|
+
* platform's own selected-state treatment.
|
|
67
|
+
*
|
|
68
|
+
* Set it false for an icon whose own colours ARE the content — an avatar, a
|
|
69
|
+
* brand mark — which then looks identical in both states.
|
|
70
|
+
*/
|
|
71
|
+
tintable?: boolean;
|
|
72
|
+
/** Accessibility label. Falls back to `title`. */
|
|
73
|
+
accessibilityLabel?: string;
|
|
74
|
+
}
|
|
75
|
+
export interface LQBottomBarProps {
|
|
76
|
+
items: LQBottomBarItem[];
|
|
77
|
+
/**
|
|
78
|
+
* Which item is lit. JS OWNS THIS, on both platforms: `onTabSelected` is a
|
|
79
|
+
* report, not a commit, so a press that should not switch tabs (one that
|
|
80
|
+
* opens a detail screen instead, say) cannot desync the bar.
|
|
81
|
+
*/
|
|
82
|
+
selectedIndex: number;
|
|
83
|
+
/** Tint for the selected item. Leave unset for the system default. */
|
|
84
|
+
selectedColor?: string;
|
|
85
|
+
/** Tint for everything else. */
|
|
86
|
+
unselectedColor?: string;
|
|
87
|
+
/** False hides the titles and re-centres the glyphs. Default true. */
|
|
88
|
+
labeled?: boolean;
|
|
89
|
+
/** Glyph size in points. Default 24. */
|
|
90
|
+
iconSize?: number;
|
|
91
|
+
onTabSelected?: (index: number) => void;
|
|
92
|
+
style?: StyleProp<ViewStyle>;
|
|
93
|
+
/**
|
|
94
|
+
* Forwarded to the native view, because a bar is very often absolutely
|
|
95
|
+
* positioned over a scrollable page.
|
|
96
|
+
*
|
|
97
|
+
* `"box-none"` is the useful value there: the bar's own items stay tappable
|
|
98
|
+
* while the empty space around them — the safe-area strip below, the gap
|
|
99
|
+
* either side of an inset pill — passes touches through to the content
|
|
100
|
+
* underneath instead of swallowing them.
|
|
101
|
+
*/
|
|
102
|
+
pointerEvents?: ViewProps['pointerEvents'];
|
|
103
|
+
/**
|
|
104
|
+
* Android only: blur strength behind the bar, 0 disables it and leaves the
|
|
105
|
+
* flat `androidBackgroundColor` scrim. Default 20.
|
|
106
|
+
*/
|
|
107
|
+
blurAmount?: number;
|
|
108
|
+
/**
|
|
109
|
+
* Android only: the material's TINT, painted over the blurred backdrop.
|
|
110
|
+
*
|
|
111
|
+
* That order is how UIKit composes a material — blur first, then a
|
|
112
|
+
* translucent colour on top — and it is why this must stay translucent. An
|
|
113
|
+
* opaque value stops being a tint and becomes a lid: the blur is still
|
|
114
|
+
* computed, still costs a software draw of the tree every frame, and is
|
|
115
|
+
* completely hidden behind it.
|
|
116
|
+
*/
|
|
117
|
+
androidBackgroundColor?: string;
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,KAAK,EACV,mBAAmB,EACnB,SAAS,EACT,SAAS,EACT,SAAS,EACV,MAAM,cAAc,CAAC;AAEtB;;;;;;;;;;GAUG;AACH,MAAM,MAAM,MAAM;AAChB;;;GAGG;AACD,YAAY;AACd;;;;;;;;GAQG;GACD,CAAC,CAAC,KAAK,EAAE,WAAW,KAAK,YAAY,CAAC;AACxC,6CAA6C;GAC3C,mBAAmB;AACrB,yDAAyD;GACvD;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE;AAClB,kDAAkD;GAChD;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzB,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,OAAO,CAAC;IAClB,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IAEb;;;;;;;;OAQG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,kDAAkD;IAClD,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,eAAe,EAAE,CAAC;IAEzB;;;;OAIG;IACH,aAAa,EAAE,MAAM,CAAC;IAEtB,sEAAsE;IACtE,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,gCAAgC;IAChC,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,sEAAsE;IACtE,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAExC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAE7B;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,SAAS,CAAC,eAAe,CAAC,CAAC;IAE3C;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;;;;;OAQG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,67 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@honeypathkar/react-native-lq-bottom-bar",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The real iOS UITabBar (liquid glass on iOS 26) and a matching blurred Android bar, taking React icons rather than only images",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"main": "lib/commonjs/index.js",
|
|
9
|
+
"module": "lib/commonjs/index.js",
|
|
10
|
+
"react-native": "src/index.tsx",
|
|
11
|
+
"source": "src/index.tsx",
|
|
12
|
+
"types": "lib/commonjs/index.d.ts",
|
|
13
|
+
"files": [
|
|
14
|
+
"src",
|
|
15
|
+
"lib",
|
|
16
|
+
"ios",
|
|
17
|
+
"android/src",
|
|
18
|
+
"android/build.gradle",
|
|
19
|
+
"react-native-lq-bottom-bar.podspec",
|
|
20
|
+
"README.md",
|
|
21
|
+
"!lib/tsconfig.tsbuildinfo"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc --build",
|
|
25
|
+
"clean": "rm -rf lib",
|
|
26
|
+
"prepare": "npm run build"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"react-native",
|
|
30
|
+
"ios",
|
|
31
|
+
"android",
|
|
32
|
+
"tab-bar",
|
|
33
|
+
"bottom-bar",
|
|
34
|
+
"uitabbar",
|
|
35
|
+
"liquid-glass",
|
|
36
|
+
"blur",
|
|
37
|
+
"lucide",
|
|
38
|
+
"svg"
|
|
39
|
+
],
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "git+https://github.com/honeypathkar/react-native-components.git",
|
|
43
|
+
"directory": "react-native-lq-bottom-bar"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://github.com/honeypathkar/react-native-components/tree/master/react-native-lq-bottom-bar#readme",
|
|
46
|
+
"bugs": {
|
|
47
|
+
"url": "https://github.com/honeypathkar/react-native-components/issues"
|
|
48
|
+
},
|
|
49
|
+
"author": "honeypathkar",
|
|
50
|
+
"license": "MIT",
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"react": "*",
|
|
53
|
+
"react-native": "*",
|
|
54
|
+
"react-native-svg": ">=13.0.0"
|
|
55
|
+
},
|
|
56
|
+
"peerDependenciesMeta": {
|
|
57
|
+
"react-native-svg": {
|
|
58
|
+
"optional": true
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@types/react": "^19.0.0",
|
|
63
|
+
"react": "19.2.0",
|
|
64
|
+
"react-native": "0.83.1",
|
|
65
|
+
"typescript": "^5.0.0"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
3
|
+
|
|
4
|
+
Pod::Spec.new do |s|
|
|
5
|
+
s.name = "react-native-lq-bottom-bar"
|
|
6
|
+
s.version = package["version"]
|
|
7
|
+
s.summary = package["description"]
|
|
8
|
+
s.license = package["license"]
|
|
9
|
+
s.authors = { "honeypathkar" => "tech@yekuminc.com" }
|
|
10
|
+
s.homepage = "https://github.com/honeypathkar/react-native-lq-bottom-bar"
|
|
11
|
+
s.platforms = { :ios => "15.0" }
|
|
12
|
+
s.source = { :git => "https://github.com/honeypathkar/react-native-lq-bottom-bar.git", :tag => "#{s.version}" }
|
|
13
|
+
s.source_files = "ios/**/*.{h,m,mm,swift}"
|
|
14
|
+
s.swift_version = "5.0"
|
|
15
|
+
|
|
16
|
+
# React Native's own helper, not a bare `React-Core` dependency. With the
|
|
17
|
+
# new architecture on, a pod that touches React headers needs the whole
|
|
18
|
+
# Fabric/JSI header surface and its flags; `React-Core` alone leaves
|
|
19
|
+
# `jsi/jsi.h` off the search path and the failure then surfaces inside
|
|
20
|
+
# React's OWN targets, which looks like a broken app rather than a bad
|
|
21
|
+
# podspec.
|
|
22
|
+
if respond_to?(:install_modules_dependencies, true)
|
|
23
|
+
install_modules_dependencies(s)
|
|
24
|
+
else
|
|
25
|
+
s.dependency "React-Core"
|
|
26
|
+
end
|
|
27
|
+
end
|