panelui-native 0.22.5 → 0.25.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/README.md +8 -3
- package/lib/module/components/area-chart/index.js +826 -0
- package/lib/module/components/area-chart/index.js.map +1 -0
- package/lib/module/components/avatar/index.js +1 -1
- package/lib/module/components/avatar/index.js.map +1 -1
- package/lib/module/components/bar-chart/index.js +853 -0
- package/lib/module/components/bar-chart/index.js.map +1 -0
- package/lib/module/components/bottom-sheet/index.js +207 -23
- package/lib/module/components/bottom-sheet/index.js.map +1 -1
- package/lib/module/components/breadcrumb/index.js +275 -0
- package/lib/module/components/breadcrumb/index.js.map +1 -0
- package/lib/module/components/chip/index.js +2 -2
- package/lib/module/components/direction/index.js +13 -19
- package/lib/module/components/direction/index.js.map +1 -1
- package/lib/module/components/frame/index.js +17 -0
- package/lib/module/components/frame/index.js.map +1 -1
- package/lib/module/components/heatmap-chart/index.js +28 -8
- package/lib/module/components/heatmap-chart/index.js.map +1 -1
- package/lib/module/components/input-group/index.js +2 -2
- package/lib/module/components/input-group/index.js.map +1 -1
- package/lib/module/components/line-chart/index.js +4 -146
- package/lib/module/components/line-chart/index.js.map +1 -1
- package/lib/module/components/loader/index.js +758 -0
- package/lib/module/components/loader/index.js.map +1 -0
- package/lib/module/components/map/basemap.js +265 -0
- package/lib/module/components/map/basemap.js.map +1 -0
- package/lib/module/components/map/index.js +850 -0
- package/lib/module/components/map/index.js.map +1 -0
- package/lib/module/components/map/maplibre.js +87 -0
- package/lib/module/components/map/maplibre.js.map +1 -0
- package/lib/module/components/message/index.js +8 -7
- package/lib/module/components/message/index.js.map +1 -1
- package/lib/module/components/otp-input/index.js +2 -2
- package/lib/module/components/progress/index.js +8 -2
- package/lib/module/components/progress/index.js.map +1 -1
- package/lib/module/components/ring-chart/index.js +423 -0
- package/lib/module/components/ring-chart/index.js.map +1 -0
- package/lib/module/components/section-rail/index.js +2 -2
- package/lib/module/components/separator/index.js +76 -22
- package/lib/module/components/separator/index.js.map +1 -1
- package/lib/module/components/shimmer/index.js +10 -1
- package/lib/module/components/shimmer/index.js.map +1 -1
- package/lib/module/components/slider/index.js +19 -5
- package/lib/module/components/slider/index.js.map +1 -1
- package/lib/module/components/steps/index.js +1 -1
- package/lib/module/components/switch/index.js +6 -1
- package/lib/module/components/switch/index.js.map +1 -1
- package/lib/module/components/typography/index.js +5 -0
- package/lib/module/components/typography/index.js.map +1 -1
- package/lib/module/hooks/use-direction.js +43 -0
- package/lib/module/hooks/use-direction.js.map +1 -0
- package/lib/module/icons/index.js +117 -2
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/index.js +8 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/primitives/text.js +30 -10
- package/lib/module/primitives/text.js.map +1 -1
- package/lib/module/utils/chart.js +279 -0
- package/lib/module/utils/chart.js.map +1 -0
- package/lib/typescript/src/components/area-chart/index.d.ts +197 -0
- package/lib/typescript/src/components/area-chart/index.d.ts.map +1 -0
- package/lib/typescript/src/components/bar-chart/index.d.ts +228 -0
- package/lib/typescript/src/components/bar-chart/index.d.ts.map +1 -0
- package/lib/typescript/src/components/bottom-sheet/index.d.ts +75 -4
- package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
- package/lib/typescript/src/components/breadcrumb/index.d.ts +100 -0
- package/lib/typescript/src/components/breadcrumb/index.d.ts.map +1 -0
- package/lib/typescript/src/components/direction/index.d.ts +11 -10
- package/lib/typescript/src/components/direction/index.d.ts.map +1 -1
- package/lib/typescript/src/components/frame/index.d.ts +17 -0
- package/lib/typescript/src/components/frame/index.d.ts.map +1 -1
- package/lib/typescript/src/components/heatmap-chart/index.d.ts +22 -3
- package/lib/typescript/src/components/heatmap-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/line-chart/index.d.ts +1 -0
- package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/loader/index.d.ts +38 -0
- package/lib/typescript/src/components/loader/index.d.ts.map +1 -0
- package/lib/typescript/src/components/map/basemap.d.ts +76 -0
- package/lib/typescript/src/components/map/basemap.d.ts.map +1 -0
- package/lib/typescript/src/components/map/index.d.ts +367 -0
- package/lib/typescript/src/components/map/index.d.ts.map +1 -0
- package/lib/typescript/src/components/map/maplibre.d.ts +193 -0
- package/lib/typescript/src/components/map/maplibre.d.ts.map +1 -0
- package/lib/typescript/src/components/message/index.d.ts.map +1 -1
- package/lib/typescript/src/components/progress/index.d.ts.map +1 -1
- package/lib/typescript/src/components/ring-chart/index.d.ts +156 -0
- package/lib/typescript/src/components/ring-chart/index.d.ts.map +1 -0
- package/lib/typescript/src/components/separator/index.d.ts +36 -1
- package/lib/typescript/src/components/separator/index.d.ts.map +1 -1
- package/lib/typescript/src/components/shimmer/index.d.ts.map +1 -1
- package/lib/typescript/src/components/slider/index.d.ts.map +1 -1
- package/lib/typescript/src/components/switch/index.d.ts.map +1 -1
- package/lib/typescript/src/components/typography/index.d.ts +6 -0
- package/lib/typescript/src/components/typography/index.d.ts.map +1 -1
- package/lib/typescript/src/hooks/use-direction.d.ts +20 -0
- package/lib/typescript/src/hooks/use-direction.d.ts.map +1 -0
- package/lib/typescript/src/icons/index.d.ts +5 -0
- package/lib/typescript/src/icons/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +9 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/primitives/text.d.ts.map +1 -1
- package/lib/typescript/src/utils/chart.d.ts +96 -0
- package/lib/typescript/src/utils/chart.d.ts.map +1 -0
- package/package.json +9 -1
- package/src/components/area-chart/index.tsx +994 -0
- package/src/components/avatar/index.tsx +1 -1
- package/src/components/bar-chart/index.tsx +1048 -0
- package/src/components/bottom-sheet/index.tsx +280 -20
- package/src/components/breadcrumb/index.tsx +365 -0
- package/src/components/chip/index.tsx +2 -2
- package/src/components/direction/index.tsx +20 -21
- package/src/components/frame/index.tsx +17 -0
- package/src/components/heatmap-chart/index.tsx +58 -11
- package/src/components/input-group/index.tsx +2 -2
- package/src/components/line-chart/index.tsx +13 -181
- package/src/components/loader/index.tsx +812 -0
- package/src/components/map/basemap.ts +304 -0
- package/src/components/map/index.tsx +1118 -0
- package/src/components/map/maplibre.ts +201 -0
- package/src/components/message/index.tsx +8 -7
- package/src/components/otp-input/index.tsx +2 -2
- package/src/components/progress/index.tsx +10 -2
- package/src/components/ring-chart/index.tsx +543 -0
- package/src/components/section-rail/index.tsx +2 -2
- package/src/components/separator/index.tsx +77 -19
- package/src/components/shimmer/index.tsx +10 -1
- package/src/components/slider/index.tsx +20 -5
- package/src/components/steps/index.tsx +1 -1
- package/src/components/switch/index.tsx +6 -1
- package/src/components/typography/index.tsx +5 -0
- package/src/hooks/use-direction.ts +45 -0
- package/src/icons/index.tsx +66 -0
- package/src/index.ts +84 -0
- package/src/primitives/text.tsx +25 -7
- package/src/utils/chart.ts +355 -0
|
@@ -0,0 +1,758 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Loading animations.
|
|
5
|
+
*
|
|
6
|
+
* ## Why one component and not nine
|
|
7
|
+
*
|
|
8
|
+
* These are interchangeable. A loader is chosen once, for the character it
|
|
9
|
+
* gives a screen, and then it is the same loader everywhere — so the thing
|
|
10
|
+
* that varies is a value, not a component. `variant` keeps the swap to one
|
|
11
|
+
* word and keeps every one of them on the same props: the same colour
|
|
12
|
+
* resolution, the same `speed`, the same reduced-motion behaviour, the same
|
|
13
|
+
* accessibility. Nine exports would mean nine chances for those to drift.
|
|
14
|
+
*
|
|
15
|
+
* ## Why they are drawn the way they are
|
|
16
|
+
*
|
|
17
|
+
* Anything with a fixed, small number of moving pieces is a view per piece
|
|
18
|
+
* with a `useAnimatedStyle` on it: three dots is three transforms a frame, and
|
|
19
|
+
* that is cheaper than rebuilding a path string.
|
|
20
|
+
*
|
|
21
|
+
* Anything whose count is high, or whose shape is derived rather than
|
|
22
|
+
* declared, is a single SVG path rebuilt on the UI thread. Fifteen bars
|
|
23
|
+
* following a bouncing ball is one animated prop that way and fifteen the
|
|
24
|
+
* other, and the fifteen do not stay in step with each other for free.
|
|
25
|
+
*
|
|
26
|
+
* ## Reduced motion
|
|
27
|
+
*
|
|
28
|
+
* Every variant draws a representative still frame rather than freezing at
|
|
29
|
+
* whatever it happened to be showing. A stopped animation should look like a
|
|
30
|
+
* shape someone drew, not like a bug caught mid-cycle.
|
|
31
|
+
*/
|
|
32
|
+
import { memo, useEffect } from 'react';
|
|
33
|
+
import { View } from 'react-native';
|
|
34
|
+
import Svg, { Ellipse, Path } from 'react-native-svg';
|
|
35
|
+
import Animated, { Easing, cancelAnimation, useAnimatedProps, useAnimatedStyle, useFrameCallback, useReducedMotion, useSharedValue, withDelay, withRepeat, withTiming } from 'react-native-reanimated';
|
|
36
|
+
import { useCSSVariable } from 'uniwind';
|
|
37
|
+
import { tv } from 'tailwind-variants';
|
|
38
|
+
import { useIconColor } from "../../icons/index.js";
|
|
39
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
40
|
+
const AnimatedPath = Animated.createAnimatedComponent(Path);
|
|
41
|
+
const AnimatedEllipse = Animated.createAnimatedComponent(Ellipse);
|
|
42
|
+
const loaderVariants = tv({
|
|
43
|
+
base: 'items-center justify-center'
|
|
44
|
+
});
|
|
45
|
+
/** Geometry at `md`. Every number here is multiplied by the size scale. */
|
|
46
|
+
const GEOMETRY = {
|
|
47
|
+
'pulse-dots': {
|
|
48
|
+
dot: 10,
|
|
49
|
+
gap: 6,
|
|
50
|
+
count: 3
|
|
51
|
+
},
|
|
52
|
+
'bounce-dots': {
|
|
53
|
+
dot: 10,
|
|
54
|
+
gap: 6,
|
|
55
|
+
count: 3,
|
|
56
|
+
travel: 8
|
|
57
|
+
},
|
|
58
|
+
'pulsating-dots': {
|
|
59
|
+
dot: 11,
|
|
60
|
+
gap: 8,
|
|
61
|
+
count: 3
|
|
62
|
+
},
|
|
63
|
+
'liquid-dots': {
|
|
64
|
+
radius: 11,
|
|
65
|
+
travel: 15,
|
|
66
|
+
width: 72,
|
|
67
|
+
height: 32
|
|
68
|
+
},
|
|
69
|
+
'bar-cascade': {
|
|
70
|
+
bar: 5,
|
|
71
|
+
gap: 4,
|
|
72
|
+
count: 5,
|
|
73
|
+
min: 8,
|
|
74
|
+
max: 26
|
|
75
|
+
},
|
|
76
|
+
'bouncing-bars': {
|
|
77
|
+
bar: 5,
|
|
78
|
+
gap: 6,
|
|
79
|
+
count: 3,
|
|
80
|
+
height: 30
|
|
81
|
+
},
|
|
82
|
+
'symmetric-wave': {
|
|
83
|
+
bar: 5,
|
|
84
|
+
gap: 5,
|
|
85
|
+
count: 9,
|
|
86
|
+
min: 7,
|
|
87
|
+
max: 26
|
|
88
|
+
},
|
|
89
|
+
'morph-ring': {
|
|
90
|
+
box: 34,
|
|
91
|
+
border: 3
|
|
92
|
+
},
|
|
93
|
+
'wave-physics': {
|
|
94
|
+
bar: 5,
|
|
95
|
+
gap: 3,
|
|
96
|
+
count: 15,
|
|
97
|
+
base: 7,
|
|
98
|
+
peak: 26
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const SIZE_SCALE = {
|
|
102
|
+
sm: 0.75,
|
|
103
|
+
md: 1,
|
|
104
|
+
lg: 1.35
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* A theme token rather than a colour. Tokens are named, not written, so the
|
|
109
|
+
* leading `--` tells the two apart without the caller having to say which.
|
|
110
|
+
*/
|
|
111
|
+
function isToken(value) {
|
|
112
|
+
return typeof value === 'string' && value.startsWith('--');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** One decimal is past the point a path string can show, and cheaper to build. */
|
|
116
|
+
function q(value) {
|
|
117
|
+
'worklet';
|
|
118
|
+
|
|
119
|
+
return Math.round(value * 10) / 10;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** A closed circle as a path subpath, so several can share one `Path`. */
|
|
123
|
+
function circle(cx, cy, r) {
|
|
124
|
+
'worklet';
|
|
125
|
+
|
|
126
|
+
return `M${q(cx - r)},${q(cy)}` + `A${q(r)},${q(r)} 0 1 0 ${q(cx + r)},${q(cy)}` + `A${q(r)},${q(r)} 0 1 0 ${q(cx - r)},${q(cy)}Z`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* -------------------------------------------------------------------------- */
|
|
130
|
+
/* dots */
|
|
131
|
+
/* -------------------------------------------------------------------------- */
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* One dot.
|
|
135
|
+
*
|
|
136
|
+
* A component rather than a loop of `useAnimatedStyle` inside the parent: the
|
|
137
|
+
* count is geometry, and a loop of hooks is a rule waiting to be broken by
|
|
138
|
+
* whoever changes it.
|
|
139
|
+
*/
|
|
140
|
+
const Dot = /*#__PURE__*/memo(function Dot({
|
|
141
|
+
size,
|
|
142
|
+
color,
|
|
143
|
+
delay,
|
|
144
|
+
duration,
|
|
145
|
+
from,
|
|
146
|
+
to,
|
|
147
|
+
property,
|
|
148
|
+
still,
|
|
149
|
+
animate
|
|
150
|
+
}) {
|
|
151
|
+
const progress = useSharedValue(animate ? from : still);
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
if (!animate) {
|
|
154
|
+
progress.value = still;
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
progress.value = from;
|
|
158
|
+
progress.value = withDelay(delay,
|
|
159
|
+
// Reversing rather than a three-stop keyframe: every one of these
|
|
160
|
+
// animations returns to where it started, and a reversed repeat says so
|
|
161
|
+
// in one line without a midpoint to keep in step.
|
|
162
|
+
withRepeat(withTiming(to, {
|
|
163
|
+
duration,
|
|
164
|
+
easing: Easing.inOut(Easing.quad)
|
|
165
|
+
}), -1, true));
|
|
166
|
+
return () => cancelAnimation(progress);
|
|
167
|
+
}, [animate, delay, duration, from, to, still, progress]);
|
|
168
|
+
const style = useAnimatedStyle(() => {
|
|
169
|
+
if (property === 'opacity') return {
|
|
170
|
+
opacity: progress.value
|
|
171
|
+
};
|
|
172
|
+
if (property === 'translateY') {
|
|
173
|
+
return {
|
|
174
|
+
transform: [{
|
|
175
|
+
translateY: progress.value
|
|
176
|
+
}]
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
opacity: 0.5 + (progress.value - 1) * 1,
|
|
181
|
+
transform: [{
|
|
182
|
+
scale: progress.value
|
|
183
|
+
}]
|
|
184
|
+
};
|
|
185
|
+
});
|
|
186
|
+
return /*#__PURE__*/_jsx(Animated.View, {
|
|
187
|
+
style: [{
|
|
188
|
+
width: size,
|
|
189
|
+
height: size,
|
|
190
|
+
borderRadius: size / 2,
|
|
191
|
+
backgroundColor: color
|
|
192
|
+
}, style]
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
/* -------------------------------------------------------------------------- */
|
|
197
|
+
/* bars */
|
|
198
|
+
/* -------------------------------------------------------------------------- */
|
|
199
|
+
|
|
200
|
+
/** One bar. Same reasoning as `Dot`. */
|
|
201
|
+
const Bar = /*#__PURE__*/memo(function Bar({
|
|
202
|
+
width,
|
|
203
|
+
color,
|
|
204
|
+
delay,
|
|
205
|
+
duration,
|
|
206
|
+
from,
|
|
207
|
+
to,
|
|
208
|
+
mode,
|
|
209
|
+
barHeight,
|
|
210
|
+
still,
|
|
211
|
+
animate
|
|
212
|
+
}) {
|
|
213
|
+
const progress = useSharedValue(animate ? from : still);
|
|
214
|
+
useEffect(() => {
|
|
215
|
+
if (!animate) {
|
|
216
|
+
progress.value = still;
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
progress.value = from;
|
|
220
|
+
progress.value = withDelay(delay, withRepeat(withTiming(to, {
|
|
221
|
+
duration,
|
|
222
|
+
easing: Easing.inOut(Easing.quad)
|
|
223
|
+
}), -1, true));
|
|
224
|
+
return () => cancelAnimation(progress);
|
|
225
|
+
}, [animate, delay, duration, from, to, still, progress]);
|
|
226
|
+
const style = useAnimatedStyle(() => mode === 'height' ? {
|
|
227
|
+
height: progress.value
|
|
228
|
+
} : {
|
|
229
|
+
transform: [{
|
|
230
|
+
scaleY: progress.value
|
|
231
|
+
}]
|
|
232
|
+
});
|
|
233
|
+
return /*#__PURE__*/_jsx(Animated.View, {
|
|
234
|
+
style: [{
|
|
235
|
+
width,
|
|
236
|
+
borderRadius: width / 2,
|
|
237
|
+
backgroundColor: color,
|
|
238
|
+
...(mode === 'scale' ? {
|
|
239
|
+
height: barHeight
|
|
240
|
+
} : null)
|
|
241
|
+
}, style]
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
/* -------------------------------------------------------------------------- */
|
|
246
|
+
/* liquid dots */
|
|
247
|
+
/* -------------------------------------------------------------------------- */
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Two blobs that merge as they cross.
|
|
251
|
+
*
|
|
252
|
+
* The merge is the whole animation, and it is drawn rather than filtered: a
|
|
253
|
+
* blur-and-threshold pass is how this is usually done, and SVG filters do not
|
|
254
|
+
* render on native at all. Instead the two circles and the neck between them
|
|
255
|
+
* are three subpaths of one path, unioned by the non-zero fill rule — which
|
|
256
|
+
* costs one animated prop a frame, and gives a crisper edge than a threshold
|
|
257
|
+
* ever does.
|
|
258
|
+
*
|
|
259
|
+
* The neck is what sells it. It pinches inward as the blobs separate and
|
|
260
|
+
* vanishes once they are far enough apart, so they let go of each other
|
|
261
|
+
* instead of snapping.
|
|
262
|
+
*/
|
|
263
|
+
function LiquidDots({
|
|
264
|
+
color,
|
|
265
|
+
scale,
|
|
266
|
+
speed,
|
|
267
|
+
animate
|
|
268
|
+
}) {
|
|
269
|
+
const g = GEOMETRY['liquid-dots'];
|
|
270
|
+
const r = g.radius * scale;
|
|
271
|
+
const travel = g.travel * scale;
|
|
272
|
+
const width = g.width * scale;
|
|
273
|
+
const height = g.height * scale;
|
|
274
|
+
const cy = height / 2;
|
|
275
|
+
const mid = width / 2;
|
|
276
|
+
|
|
277
|
+
// -1 to 1: where the left blob is along its travel. The right blob mirrors.
|
|
278
|
+
const t = useSharedValue(animate ? -1 : -0.35);
|
|
279
|
+
useEffect(() => {
|
|
280
|
+
if (!animate) {
|
|
281
|
+
t.value = -0.35;
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
t.value = -1;
|
|
285
|
+
t.value = withRepeat(withTiming(1, {
|
|
286
|
+
duration: 2000 / speed,
|
|
287
|
+
easing: Easing.inOut(Easing.quad)
|
|
288
|
+
}), -1, true);
|
|
289
|
+
return () => cancelAnimation(t);
|
|
290
|
+
}, [animate, speed, t]);
|
|
291
|
+
const props = useAnimatedProps(() => {
|
|
292
|
+
const x1 = mid + t.value * travel;
|
|
293
|
+
const x2 = mid - t.value * travel;
|
|
294
|
+
const left = Math.min(x1, x2);
|
|
295
|
+
const right = Math.max(x1, x2);
|
|
296
|
+
const gap = right - left;
|
|
297
|
+
let d = circle(left, cy, r) + circle(right, cy, r);
|
|
298
|
+
|
|
299
|
+
/*
|
|
300
|
+
* The neck only exists while the blobs are close enough to be pulling on
|
|
301
|
+
* each other: `1` when they are concentric, and gone by the time they are
|
|
302
|
+
* a radius apart. Squared, so it thins out quickly rather than trailing.
|
|
303
|
+
*
|
|
304
|
+
* The thickness is what the cutoff is really on. A falloff that only
|
|
305
|
+
* approaches zero leaves a hairline spanning the gap for the whole of the
|
|
306
|
+
* rest of the travel, and a one-pixel bar between two circles reads as a
|
|
307
|
+
* drawing mistake rather than as a thinning neck — so below the point
|
|
308
|
+
* where it is still legibly a neck, it is not drawn at all.
|
|
309
|
+
*/
|
|
310
|
+
const pull = Math.max(0, 1 - gap / (r * 2.2));
|
|
311
|
+
const half = r * pull * pull * 0.95;
|
|
312
|
+
if (half > r * 0.12 && gap > 1) {
|
|
313
|
+
// Pinched at the middle: the control point sits inside the straight
|
|
314
|
+
// line between the two, which is what makes the join read as surface
|
|
315
|
+
// tension rather than as a rectangle.
|
|
316
|
+
const waist = half * 0.55;
|
|
317
|
+
d += `M${q(left)},${q(cy - half)}` + `Q${q((left + right) / 2)},${q(cy - waist)} ${q(right)},${q(cy - half)}` + `L${q(right)},${q(cy + half)}` + `Q${q((left + right) / 2)},${q(cy + waist)} ${q(left)},${q(cy + half)}Z`;
|
|
318
|
+
}
|
|
319
|
+
return {
|
|
320
|
+
d
|
|
321
|
+
};
|
|
322
|
+
});
|
|
323
|
+
return /*#__PURE__*/_jsx(Svg, {
|
|
324
|
+
width: width,
|
|
325
|
+
height: height,
|
|
326
|
+
children: /*#__PURE__*/_jsx(AnimatedPath, {
|
|
327
|
+
animatedProps: props,
|
|
328
|
+
fill: color,
|
|
329
|
+
fillRule: "nonzero"
|
|
330
|
+
})
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/* -------------------------------------------------------------------------- */
|
|
335
|
+
/* wave physics */
|
|
336
|
+
/* -------------------------------------------------------------------------- */
|
|
337
|
+
|
|
338
|
+
/** How many bounces the ball takes crossing the row once. */
|
|
339
|
+
const BOUNCES = 4;
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* A ball bouncing along a row of bars, pushing them down where it lands.
|
|
343
|
+
*
|
|
344
|
+
* The only variant with anything like physics in it, and the reason it is
|
|
345
|
+
* procedural rather than a keyframe list: the bar heights are a function of
|
|
346
|
+
* where the ball is, so they are derived every frame instead of stored. Fifteen
|
|
347
|
+
* bars precomputed would be fifteen arrays that have to agree with each other
|
|
348
|
+
* and with the ball, and none of them could respond to `speed`.
|
|
349
|
+
*
|
|
350
|
+
* Both the bars and the ball read the same clock, which is what keeps the
|
|
351
|
+
* dent under the ball rather than near it.
|
|
352
|
+
*/
|
|
353
|
+
function WavePhysics({
|
|
354
|
+
color,
|
|
355
|
+
scale,
|
|
356
|
+
speed,
|
|
357
|
+
animate
|
|
358
|
+
}) {
|
|
359
|
+
const g = GEOMETRY['wave-physics'];
|
|
360
|
+
const barWidth = g.bar * scale;
|
|
361
|
+
const step = (g.bar + g.gap) * scale;
|
|
362
|
+
const base = g.base * scale;
|
|
363
|
+
const peak = g.peak * scale;
|
|
364
|
+
const ballR = g.bar * 1.1 * scale;
|
|
365
|
+
const bounce = peak * 0.9;
|
|
366
|
+
const width = (g.count - 1) * step + barWidth;
|
|
367
|
+
const height = base + peak + bounce + ballR * 2;
|
|
368
|
+
const floor = height;
|
|
369
|
+
const clock = useSharedValue(0);
|
|
370
|
+
const frame = useFrameCallback(info => {
|
|
371
|
+
'worklet';
|
|
372
|
+
|
|
373
|
+
// Accumulated rather than read off the total, so `speed` can change
|
|
374
|
+
// mid-animation without the ball jumping to where the new rate would have
|
|
375
|
+
// put it. A dropped frame is clamped: a 300ms hitch played at full rate
|
|
376
|
+
// is a lurch, not a bounce.
|
|
377
|
+
const delta = Math.min(info.timeSincePreviousFrame ?? 16, 48) / 1000;
|
|
378
|
+
clock.value += delta * speed * 0.35;
|
|
379
|
+
}, false);
|
|
380
|
+
const {
|
|
381
|
+
setActive
|
|
382
|
+
} = frame;
|
|
383
|
+
useEffect(() => {
|
|
384
|
+
setActive(animate);
|
|
385
|
+
return () => setActive(false);
|
|
386
|
+
}, [animate, setActive]);
|
|
387
|
+
|
|
388
|
+
/** Where the ball is, as a bar index, and how far through its hop. */
|
|
389
|
+
const ballAt = time => {
|
|
390
|
+
'worklet';
|
|
391
|
+
|
|
392
|
+
// Back and forth along the row, then back again — one continuous pass.
|
|
393
|
+
const cycle = time % 2;
|
|
394
|
+
const across = cycle < 1 ? cycle : 2 - cycle;
|
|
395
|
+
const index = across * (g.count - 1);
|
|
396
|
+
let phase = across * BOUNCES % 1;
|
|
397
|
+
if (across === 0 || across === 1) phase = 0;
|
|
398
|
+
// A parabola per hop: the ball is slowest at the top, which is the only
|
|
399
|
+
// part of a bounce anyone actually reads.
|
|
400
|
+
const lift = 4 * phase * (1 - phase);
|
|
401
|
+
return {
|
|
402
|
+
index,
|
|
403
|
+
lift
|
|
404
|
+
};
|
|
405
|
+
};
|
|
406
|
+
const barProps = useAnimatedProps(() => {
|
|
407
|
+
const {
|
|
408
|
+
index,
|
|
409
|
+
lift
|
|
410
|
+
} = animate ? ballAt(clock.value) : {
|
|
411
|
+
index: 7,
|
|
412
|
+
lift: 0.5
|
|
413
|
+
};
|
|
414
|
+
// The ball presses hardest when it is on the ground, so the dent is the
|
|
415
|
+
// inverse of the hop.
|
|
416
|
+
const press = Math.max(0, 1 - lift * 2);
|
|
417
|
+
let d = '';
|
|
418
|
+
for (let i = 0; i < g.count; i++) {
|
|
419
|
+
const distance = Math.abs(i - index);
|
|
420
|
+
// A raised crest travelling with the ball, three bars wide either side.
|
|
421
|
+
const crest = distance < 3 ? Math.cos(distance / 3 * (Math.PI / 2)) : 0;
|
|
422
|
+
// And a dent right under it, narrower than the crest so the bar the
|
|
423
|
+
// ball is standing on is pushed down through its own rise.
|
|
424
|
+
const dent = distance < 1.5 ? Math.cos(distance / 1.5 * (Math.PI / 2)) * press * (peak * 0.5) : 0;
|
|
425
|
+
const barHeight = Math.max(base * 0.5, base + crest * peak - dent);
|
|
426
|
+
const x = q(barWidth / 2 + i * step);
|
|
427
|
+
d += `M${x},${q(floor)}L${x},${q(floor - barHeight)}`;
|
|
428
|
+
}
|
|
429
|
+
return {
|
|
430
|
+
d
|
|
431
|
+
};
|
|
432
|
+
});
|
|
433
|
+
const ballProps = useAnimatedProps(() => {
|
|
434
|
+
const {
|
|
435
|
+
index,
|
|
436
|
+
lift
|
|
437
|
+
} = animate ? ballAt(clock.value) : {
|
|
438
|
+
index: 7,
|
|
439
|
+
lift: 0.5
|
|
440
|
+
};
|
|
441
|
+
const press = Math.max(0, 1 - lift * 2);
|
|
442
|
+
// The ball rides the bar directly under it, which is the crest of the
|
|
443
|
+
// wave (so the full `peak`) minus however hard it is pressing.
|
|
444
|
+
const rest = base + peak - press * (peak * 0.5);
|
|
445
|
+
|
|
446
|
+
// Squash on the ground, stretch at the top — the oldest trick there is,
|
|
447
|
+
// and the thing that separates a bouncing ball from a moving circle.
|
|
448
|
+
const squash = press;
|
|
449
|
+
return {
|
|
450
|
+
cx: q(barWidth / 2 + index * step),
|
|
451
|
+
cy: q(floor - rest - ballR + squash * ballR * 0.3 - lift * bounce),
|
|
452
|
+
rx: q(ballR * (1 + squash * 0.25)),
|
|
453
|
+
ry: q(ballR * (1 - squash * 0.28))
|
|
454
|
+
};
|
|
455
|
+
});
|
|
456
|
+
return /*#__PURE__*/_jsxs(Svg, {
|
|
457
|
+
width: width,
|
|
458
|
+
height: height,
|
|
459
|
+
children: [/*#__PURE__*/_jsx(AnimatedPath, {
|
|
460
|
+
animatedProps: barProps,
|
|
461
|
+
stroke: color,
|
|
462
|
+
strokeWidth: barWidth,
|
|
463
|
+
strokeLinecap: "round",
|
|
464
|
+
fill: "none",
|
|
465
|
+
opacity: 0.55
|
|
466
|
+
}), /*#__PURE__*/_jsx(AnimatedEllipse, {
|
|
467
|
+
animatedProps: ballProps,
|
|
468
|
+
fill: color
|
|
469
|
+
})]
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/* -------------------------------------------------------------------------- */
|
|
474
|
+
/* morph ring */
|
|
475
|
+
/* -------------------------------------------------------------------------- */
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* A square turning into a circle and back while it rotates.
|
|
479
|
+
*
|
|
480
|
+
* The rotation is what makes the morph legible: a square becoming a circle in
|
|
481
|
+
* place mostly reads as a square getting smaller, and turning it means the
|
|
482
|
+
* corners are visibly going somewhere as they round off.
|
|
483
|
+
*/
|
|
484
|
+
function MorphRing({
|
|
485
|
+
color,
|
|
486
|
+
scale,
|
|
487
|
+
speed,
|
|
488
|
+
animate
|
|
489
|
+
}) {
|
|
490
|
+
const g = GEOMETRY['morph-ring'];
|
|
491
|
+
const box = g.box * scale;
|
|
492
|
+
const border = Math.max(2, g.border * scale);
|
|
493
|
+
const progress = useSharedValue(animate ? 0 : 0.5);
|
|
494
|
+
useEffect(() => {
|
|
495
|
+
if (!animate) {
|
|
496
|
+
progress.value = 0.5;
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
progress.value = 0;
|
|
500
|
+
progress.value = withRepeat(withTiming(1, {
|
|
501
|
+
duration: 2000 / speed,
|
|
502
|
+
easing: Easing.inOut(Easing.quad)
|
|
503
|
+
}), -1, true);
|
|
504
|
+
return () => cancelAnimation(progress);
|
|
505
|
+
}, [animate, speed, progress]);
|
|
506
|
+
const style = useAnimatedStyle(() => ({
|
|
507
|
+
borderRadius: box * (0.1 + progress.value * 0.4),
|
|
508
|
+
transform: [{
|
|
509
|
+
rotate: `${progress.value * 180}deg`
|
|
510
|
+
}]
|
|
511
|
+
}));
|
|
512
|
+
|
|
513
|
+
/*
|
|
514
|
+
* The padding is the rotation's. A square turned 45° needs its diagonal, so
|
|
515
|
+
* it sweeps past its own box by nearly half its width on each side — laid
|
|
516
|
+
* out flush, the corners spill over whatever is next to it.
|
|
517
|
+
*/
|
|
518
|
+
const overhang = (box * Math.SQRT2 - box) / 2;
|
|
519
|
+
return /*#__PURE__*/_jsx(View, {
|
|
520
|
+
style: {
|
|
521
|
+
padding: overhang
|
|
522
|
+
},
|
|
523
|
+
children: /*#__PURE__*/_jsx(Animated.View, {
|
|
524
|
+
style: [{
|
|
525
|
+
width: box,
|
|
526
|
+
height: box,
|
|
527
|
+
borderWidth: border,
|
|
528
|
+
borderColor: color
|
|
529
|
+
}, style]
|
|
530
|
+
})
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/* -------------------------------------------------------------------------- */
|
|
535
|
+
/* root */
|
|
536
|
+
/* -------------------------------------------------------------------------- */
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* A loading animation.
|
|
540
|
+
*
|
|
541
|
+
* Nine of them, chosen with `variant`. They all take the same colour, the same
|
|
542
|
+
* `speed`, and the same treatment under reduced motion, so swapping one for
|
|
543
|
+
* another is a one-word change.
|
|
544
|
+
*/
|
|
545
|
+
export const Loader = /*#__PURE__*/memo(function Loader({
|
|
546
|
+
variant = 'pulse-dots',
|
|
547
|
+
size = 'md',
|
|
548
|
+
color,
|
|
549
|
+
speed = 1,
|
|
550
|
+
label = 'Loading',
|
|
551
|
+
className,
|
|
552
|
+
...props
|
|
553
|
+
}) {
|
|
554
|
+
const reducedMotion = useReducedMotion();
|
|
555
|
+
const animate = !reducedMotion && speed > 0;
|
|
556
|
+
const scale = SIZE_SCALE[size];
|
|
557
|
+
const rate = speed > 0 ? speed : 1;
|
|
558
|
+
|
|
559
|
+
/*
|
|
560
|
+
* A loader inside a filled surface has to be drawn in that surface's
|
|
561
|
+
* foreground rather than the page's: a button in the primary colour is one
|
|
562
|
+
* of the most likely places for a loader to appear, and in most themes the
|
|
563
|
+
* primary colour *is* the page's foreground — so one resolving
|
|
564
|
+
* `--color-foreground` for itself would be invisible exactly there.
|
|
565
|
+
* Surfaces already publish their readable foreground for icons.
|
|
566
|
+
*/
|
|
567
|
+
const inherited = useIconColor();
|
|
568
|
+
const token = useCSSVariable(isToken(color) ? color : '--color-foreground');
|
|
569
|
+
const resolved = typeof token === 'string' ? token : undefined;
|
|
570
|
+
const ink = (isToken(color) ? resolved : color) ?? inherited ?? resolved ?? '#0a0a0a';
|
|
571
|
+
const frame = content => /*#__PURE__*/_jsx(View, {
|
|
572
|
+
accessibilityRole: "progressbar",
|
|
573
|
+
accessibilityLabel: label,
|
|
574
|
+
className: loaderVariants({
|
|
575
|
+
className
|
|
576
|
+
}),
|
|
577
|
+
...props,
|
|
578
|
+
children: content
|
|
579
|
+
});
|
|
580
|
+
if (variant === 'liquid-dots') {
|
|
581
|
+
return frame(/*#__PURE__*/_jsx(LiquidDots, {
|
|
582
|
+
color: ink,
|
|
583
|
+
scale: scale,
|
|
584
|
+
speed: rate,
|
|
585
|
+
animate: animate
|
|
586
|
+
}));
|
|
587
|
+
}
|
|
588
|
+
if (variant === 'wave-physics') {
|
|
589
|
+
return frame(/*#__PURE__*/_jsx(WavePhysics, {
|
|
590
|
+
color: ink,
|
|
591
|
+
scale: scale,
|
|
592
|
+
speed: rate,
|
|
593
|
+
animate: animate
|
|
594
|
+
}));
|
|
595
|
+
}
|
|
596
|
+
if (variant === 'morph-ring') {
|
|
597
|
+
return frame(/*#__PURE__*/_jsx(MorphRing, {
|
|
598
|
+
color: ink,
|
|
599
|
+
scale: scale,
|
|
600
|
+
speed: rate,
|
|
601
|
+
animate: animate
|
|
602
|
+
}));
|
|
603
|
+
}
|
|
604
|
+
if (variant === 'pulse-dots') {
|
|
605
|
+
const g = GEOMETRY['pulse-dots'];
|
|
606
|
+
return frame(/*#__PURE__*/_jsx(View, {
|
|
607
|
+
className: "flex-row items-center",
|
|
608
|
+
style: {
|
|
609
|
+
gap: g.gap * scale
|
|
610
|
+
},
|
|
611
|
+
children: Array.from({
|
|
612
|
+
length: g.count
|
|
613
|
+
}, (_unused, i) => /*#__PURE__*/_jsx(Dot, {
|
|
614
|
+
size: g.dot * scale,
|
|
615
|
+
color: ink,
|
|
616
|
+
delay: i * 200 / rate,
|
|
617
|
+
duration: 700 / rate,
|
|
618
|
+
from: 0.2,
|
|
619
|
+
to: 1,
|
|
620
|
+
still: i === 1 ? 1 : 0.45,
|
|
621
|
+
property: "opacity",
|
|
622
|
+
animate: animate
|
|
623
|
+
}, i))
|
|
624
|
+
}));
|
|
625
|
+
}
|
|
626
|
+
if (variant === 'bounce-dots') {
|
|
627
|
+
const g = GEOMETRY['bounce-dots'];
|
|
628
|
+
const travel = g.travel * scale;
|
|
629
|
+
return frame(/*#__PURE__*/_jsx(View, {
|
|
630
|
+
className: "flex-row items-center",
|
|
631
|
+
style: {
|
|
632
|
+
gap: g.gap * scale,
|
|
633
|
+
paddingTop: travel
|
|
634
|
+
},
|
|
635
|
+
children: Array.from({
|
|
636
|
+
length: g.count
|
|
637
|
+
}, (_unused, i) => /*#__PURE__*/_jsx(Dot, {
|
|
638
|
+
size: g.dot * scale,
|
|
639
|
+
color: ink,
|
|
640
|
+
delay: i * 100 / rate,
|
|
641
|
+
duration: 300 / rate,
|
|
642
|
+
from: 0,
|
|
643
|
+
to: -travel,
|
|
644
|
+
still: -travel * (i === 1 ? 1 : 0.35),
|
|
645
|
+
property: "translateY",
|
|
646
|
+
animate: animate
|
|
647
|
+
}, i))
|
|
648
|
+
}));
|
|
649
|
+
}
|
|
650
|
+
if (variant === 'pulsating-dots') {
|
|
651
|
+
const g = GEOMETRY['pulsating-dots'];
|
|
652
|
+
return frame(/*#__PURE__*/_jsx(View, {
|
|
653
|
+
className: "flex-row items-center"
|
|
654
|
+
// A dot at its largest is half again its laid-out size, and it grows
|
|
655
|
+
// about its centre — so a quarter of a dot on each side is the room
|
|
656
|
+
// the scale needs, plus a little for the rounding.
|
|
657
|
+
,
|
|
658
|
+
style: {
|
|
659
|
+
gap: g.gap * scale,
|
|
660
|
+
padding: g.dot * scale * 0.3
|
|
661
|
+
},
|
|
662
|
+
children: Array.from({
|
|
663
|
+
length: g.count
|
|
664
|
+
}, (_unused, i) => /*#__PURE__*/_jsx(Dot, {
|
|
665
|
+
size: g.dot * scale,
|
|
666
|
+
color: ink,
|
|
667
|
+
delay: i * 200 / rate,
|
|
668
|
+
duration: 600 / rate,
|
|
669
|
+
from: 1,
|
|
670
|
+
to: 1.5,
|
|
671
|
+
still: i === 1 ? 1.4 : 1.1,
|
|
672
|
+
property: "scale",
|
|
673
|
+
animate: animate
|
|
674
|
+
}, i))
|
|
675
|
+
}));
|
|
676
|
+
}
|
|
677
|
+
if (variant === 'bar-cascade') {
|
|
678
|
+
const g = GEOMETRY['bar-cascade'];
|
|
679
|
+
return frame(/*#__PURE__*/_jsx(View, {
|
|
680
|
+
className: "flex-row items-center",
|
|
681
|
+
style: {
|
|
682
|
+
gap: g.gap * scale,
|
|
683
|
+
height: g.max * scale
|
|
684
|
+
},
|
|
685
|
+
children: Array.from({
|
|
686
|
+
length: g.count
|
|
687
|
+
}, (_unused, i) => /*#__PURE__*/_jsx(Bar, {
|
|
688
|
+
width: g.bar * scale,
|
|
689
|
+
color: ink,
|
|
690
|
+
delay: i * 100 / rate,
|
|
691
|
+
duration: 500 / rate,
|
|
692
|
+
from: g.min * scale,
|
|
693
|
+
to: g.max * scale
|
|
694
|
+
// A still frame that shows the cascade's shape rather than a row
|
|
695
|
+
// of equal bars, which would say nothing about what it does.
|
|
696
|
+
,
|
|
697
|
+
still: (g.min + (g.max - g.min) * Math.abs(Math.sin(i * 0.9))) * scale,
|
|
698
|
+
mode: "height",
|
|
699
|
+
animate: animate
|
|
700
|
+
}, i))
|
|
701
|
+
}));
|
|
702
|
+
}
|
|
703
|
+
if (variant === 'bouncing-bars') {
|
|
704
|
+
const g = GEOMETRY['bouncing-bars'];
|
|
705
|
+
return frame(/*#__PURE__*/_jsx(View, {
|
|
706
|
+
className: "flex-row items-center",
|
|
707
|
+
style: {
|
|
708
|
+
gap: g.gap * scale,
|
|
709
|
+
height: g.height * scale
|
|
710
|
+
},
|
|
711
|
+
children: Array.from({
|
|
712
|
+
length: g.count
|
|
713
|
+
}, (_unused, i) => /*#__PURE__*/_jsx(Bar, {
|
|
714
|
+
width: g.bar * scale,
|
|
715
|
+
color: ink,
|
|
716
|
+
delay: i * 200 / rate,
|
|
717
|
+
duration: 500 / rate,
|
|
718
|
+
from: 0.3,
|
|
719
|
+
to: 1,
|
|
720
|
+
barHeight: g.height * scale,
|
|
721
|
+
still: 0.4 + i * 0.25,
|
|
722
|
+
mode: "scale",
|
|
723
|
+
animate: animate
|
|
724
|
+
}, i))
|
|
725
|
+
}));
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// symmetric-wave
|
|
729
|
+
const g = GEOMETRY['symmetric-wave'];
|
|
730
|
+
/*
|
|
731
|
+
* The delay is symmetric about the middle rather than running left to right,
|
|
732
|
+
* so the wave opens outward from the centre and arrives back at it. A
|
|
733
|
+
* straight ramp reads as a queue; this reads as one thing breathing.
|
|
734
|
+
*/
|
|
735
|
+
const offsets = [0, 1, 2, 3, 4, 3, 2, 1, 0];
|
|
736
|
+
return frame(/*#__PURE__*/_jsx(View, {
|
|
737
|
+
className: "flex-row items-center",
|
|
738
|
+
style: {
|
|
739
|
+
gap: g.gap * scale,
|
|
740
|
+
height: g.max * scale
|
|
741
|
+
},
|
|
742
|
+
children: Array.from({
|
|
743
|
+
length: g.count
|
|
744
|
+
}, (_unused, i) => /*#__PURE__*/_jsx(Bar, {
|
|
745
|
+
width: g.bar * scale,
|
|
746
|
+
color: ink,
|
|
747
|
+
delay: (offsets[i] ?? 0) * 100 / rate,
|
|
748
|
+
duration: 600 / rate,
|
|
749
|
+
from: g.min * scale,
|
|
750
|
+
to: g.max * scale,
|
|
751
|
+
still: (g.min + (g.max - g.min) * (1 - (offsets[i] ?? 0) / 4)) * scale,
|
|
752
|
+
mode: "height",
|
|
753
|
+
animate: animate
|
|
754
|
+
}, i))
|
|
755
|
+
}));
|
|
756
|
+
});
|
|
757
|
+
Loader.displayName = 'Loader';
|
|
758
|
+
//# sourceMappingURL=index.js.map
|