panelui-native 0.40.0 → 0.43.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 +4 -2
- package/lib/module/components/card/index.js +172 -3
- package/lib/module/components/card/index.js.map +1 -1
- package/lib/module/components/color-picker/index.js +1395 -0
- package/lib/module/components/color-picker/index.js.map +1 -0
- package/lib/module/components/combobox/index.js +87 -19
- package/lib/module/components/combobox/index.js.map +1 -1
- package/lib/module/components/drawer/index.js +13 -4
- package/lib/module/components/drawer/index.js.map +1 -1
- package/lib/module/components/kpi-chart/index.js +648 -0
- package/lib/module/components/kpi-chart/index.js.map +1 -0
- package/lib/module/components/map/index.js +72 -12
- package/lib/module/components/map/index.js.map +1 -1
- package/lib/module/components/menu/index.js +40 -10
- package/lib/module/components/menu/index.js.map +1 -1
- package/lib/module/components/pagination/index.js +19 -9
- package/lib/module/components/pagination/index.js.map +1 -1
- package/lib/module/components/panelside/index.js +68 -18
- package/lib/module/components/panelside/index.js.map +1 -1
- package/lib/module/components/radar-chart/index.js +704 -0
- package/lib/module/components/radar-chart/index.js.map +1 -0
- package/lib/module/components/section-rail/index.js +32 -7
- package/lib/module/components/section-rail/index.js.map +1 -1
- package/lib/module/components/slider/index.js +240 -39
- package/lib/module/components/slider/index.js.map +1 -1
- package/lib/module/components/tabs/index.js +194 -10
- package/lib/module/components/tabs/index.js.map +1 -1
- package/lib/module/components/text-animation/index.js +857 -0
- package/lib/module/components/text-animation/index.js.map +1 -0
- package/lib/module/icons/index.js +25 -7
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/index.js +5 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/theme/use-theme.js +1 -1
- package/lib/module/utils/chart.js +44 -0
- package/lib/module/utils/chart.js.map +1 -1
- package/lib/module/utils/color.js +268 -0
- package/lib/module/utils/color.js.map +1 -0
- package/lib/typescript/src/components/card/index.d.ts +20 -0
- package/lib/typescript/src/components/card/index.d.ts.map +1 -1
- package/lib/typescript/src/components/color-picker/index.d.ts +258 -0
- package/lib/typescript/src/components/color-picker/index.d.ts.map +1 -0
- package/lib/typescript/src/components/combobox/index.d.ts.map +1 -1
- package/lib/typescript/src/components/drawer/index.d.ts +13 -1
- package/lib/typescript/src/components/drawer/index.d.ts.map +1 -1
- package/lib/typescript/src/components/kpi-chart/index.d.ts +490 -0
- package/lib/typescript/src/components/kpi-chart/index.d.ts.map +1 -0
- package/lib/typescript/src/components/map/index.d.ts +37 -2
- package/lib/typescript/src/components/map/index.d.ts.map +1 -1
- package/lib/typescript/src/components/menu/index.d.ts.map +1 -1
- package/lib/typescript/src/components/pagination/index.d.ts.map +1 -1
- package/lib/typescript/src/components/panelside/index.d.ts +50 -4
- package/lib/typescript/src/components/panelside/index.d.ts.map +1 -1
- package/lib/typescript/src/components/radar-chart/index.d.ts +214 -0
- package/lib/typescript/src/components/radar-chart/index.d.ts.map +1 -0
- package/lib/typescript/src/components/section-rail/index.d.ts +8 -1
- package/lib/typescript/src/components/section-rail/index.d.ts.map +1 -1
- package/lib/typescript/src/components/slider/index.d.ts +19 -0
- package/lib/typescript/src/components/slider/index.d.ts.map +1 -1
- package/lib/typescript/src/components/tabs/index.d.ts +11 -1
- package/lib/typescript/src/components/tabs/index.d.ts.map +1 -1
- package/lib/typescript/src/components/text-animation/index.d.ts +268 -0
- package/lib/typescript/src/components/text-animation/index.d.ts.map +1 -0
- package/lib/typescript/src/icons/index.d.ts +6 -0
- package/lib/typescript/src/icons/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +8 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/chart.d.ts +21 -0
- package/lib/typescript/src/utils/chart.d.ts.map +1 -1
- package/lib/typescript/src/utils/color.d.ts +74 -0
- package/lib/typescript/src/utils/color.d.ts.map +1 -0
- package/package.json +2 -1
- package/src/components/card/index.tsx +227 -2
- package/src/components/color-picker/index.tsx +1618 -0
- package/src/components/combobox/index.tsx +89 -19
- package/src/components/drawer/index.tsx +31 -9
- package/src/components/kpi-chart/index.tsx +719 -0
- package/src/components/map/index.tsx +158 -14
- package/src/components/menu/index.tsx +51 -6
- package/src/components/pagination/index.tsx +18 -6
- package/src/components/panelside/index.tsx +134 -19
- package/src/components/radar-chart/index.tsx +910 -0
- package/src/components/section-rail/index.tsx +42 -8
- package/src/components/slider/index.tsx +283 -41
- package/src/components/tabs/index.tsx +263 -8
- package/src/components/text-animation/index.tsx +1093 -0
- package/src/icons/index.tsx +23 -4
- package/src/index.ts +73 -1
- package/src/theme/use-theme.ts +1 -1
- package/src/utils/chart.ts +52 -0
- package/src/utils/color.ts +246 -0
- package/theme.css +125 -112
|
@@ -1,5 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Card — a content surface, and the layer that decorates it.
|
|
3
|
+
*
|
|
4
|
+
* ```tsx
|
|
5
|
+
* <Card>
|
|
6
|
+
* <Card.Header>
|
|
7
|
+
* <Card.Title>Living room sofa</Card.Title>
|
|
8
|
+
* <Card.Description>Three seats, oat linen</Card.Description>
|
|
9
|
+
* </Card.Header>
|
|
10
|
+
* <Card.Footer>
|
|
11
|
+
* <Button fullWidth>Buy now</Button>
|
|
12
|
+
* </Card.Footer>
|
|
13
|
+
* </Card>
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* All the padding lives on the slots and none of it on the root, which is why
|
|
17
|
+
* a card whose media reaches its own corners needs nothing but
|
|
18
|
+
* `overflow-hidden`.
|
|
19
|
+
*/
|
|
20
|
+
import { forwardRef, useEffect, useMemo } from 'react';
|
|
21
|
+
import { StyleSheet, View, type ViewProps } from 'react-native';
|
|
22
|
+
import Animated, {
|
|
23
|
+
Easing,
|
|
24
|
+
cancelAnimation,
|
|
25
|
+
useAnimatedStyle,
|
|
26
|
+
useReducedMotion,
|
|
27
|
+
useSharedValue,
|
|
28
|
+
withRepeat,
|
|
29
|
+
withTiming,
|
|
30
|
+
} from 'react-native-reanimated';
|
|
31
|
+
import { LinearGradient } from 'expo-linear-gradient';
|
|
32
|
+
import { useCSSVariable } from 'uniwind';
|
|
3
33
|
import { cn } from '../../utils/cn';
|
|
4
34
|
import { Text, type TextProps } from '../../primitives/text';
|
|
5
35
|
|
|
@@ -55,10 +85,205 @@ const CardFooter = forwardRef<View, CardProps>(({ className, ...props }, ref) =>
|
|
|
55
85
|
));
|
|
56
86
|
CardFooter.displayName = 'Card.Footer';
|
|
57
87
|
|
|
88
|
+
/* -------------------------------------------------------------------------- */
|
|
89
|
+
/* Card.Wash */
|
|
90
|
+
/* -------------------------------------------------------------------------- */
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* How sharply the wash dissolves upward. The exponent is the whole look: at 1
|
|
94
|
+
* the colour is already at half strength across the middle of the card and
|
|
95
|
+
* reads as a tinted panel, and it takes about a cube before the top is
|
|
96
|
+
* genuinely untouched and the colour arrives in the bottom third.
|
|
97
|
+
*/
|
|
98
|
+
const WASH_CURVE = 3;
|
|
99
|
+
/**
|
|
100
|
+
* Stops the curve is sampled at. Ten is enough that the straight lines the
|
|
101
|
+
* renderer draws between them are not a shape anyone can see, and the fade
|
|
102
|
+
* stays continuous — stepping it would put a horizontal seam at every step.
|
|
103
|
+
*/
|
|
104
|
+
const WASH_STOPS = 10;
|
|
105
|
+
/** How long one breath takes at `speed={1}`. */
|
|
106
|
+
const WASH_PERIOD = 4200;
|
|
107
|
+
/**
|
|
108
|
+
* How far the wash swells and settles over one breath, as a fraction of
|
|
109
|
+
* `intensity`. Small on purpose: the movement should be noticed only by not
|
|
110
|
+
* being there, and anything larger reads as a light being switched.
|
|
111
|
+
*/
|
|
112
|
+
const WASH_BREATH = 0.22;
|
|
113
|
+
|
|
114
|
+
function isToken(value: string | undefined): value is string {
|
|
115
|
+
return typeof value === 'string' && value.startsWith('--');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Gradients need a transparent stop of the *same* colour — `transparent` is
|
|
120
|
+
* black at zero alpha on Android, which shows as a grey smear.
|
|
121
|
+
*/
|
|
122
|
+
function withAlpha(color: string, alpha: number): string {
|
|
123
|
+
if (color.startsWith('#')) {
|
|
124
|
+
const hex = color.slice(1);
|
|
125
|
+
const full =
|
|
126
|
+
hex.length === 3
|
|
127
|
+
? hex
|
|
128
|
+
.split('')
|
|
129
|
+
.map((c) => c + c)
|
|
130
|
+
.join('')
|
|
131
|
+
: hex.slice(0, 6);
|
|
132
|
+
const r = parseInt(full.slice(0, 2), 16);
|
|
133
|
+
const g = parseInt(full.slice(2, 4), 16);
|
|
134
|
+
const b = parseInt(full.slice(4, 6), 16);
|
|
135
|
+
if (Number.isNaN(r + g + b)) return color;
|
|
136
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const channels = color.match(/rgba?\(([^)]+)\)/)?.[1];
|
|
140
|
+
if (channels) {
|
|
141
|
+
const [r, g, b] = channels.split(',').map((part) => part.trim());
|
|
142
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return color;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface CardWashProps extends Omit<ViewProps, 'children'> {
|
|
149
|
+
className?: string;
|
|
150
|
+
/**
|
|
151
|
+
* The colour the wash rises in. A theme token name such as
|
|
152
|
+
* `--color-primary`, or any colour string. Defaults to `--color-primary`.
|
|
153
|
+
*/
|
|
154
|
+
tint?: string;
|
|
155
|
+
/**
|
|
156
|
+
* How opaque the wash gets at the bottom edge, 0–1. It is meant to be small:
|
|
157
|
+
* past about 0.25 the card reads as tinted rather than washed, which is a
|
|
158
|
+
* different thing and usually not the one you wanted.
|
|
159
|
+
*/
|
|
160
|
+
intensity?: number;
|
|
161
|
+
/**
|
|
162
|
+
* Multiplier on how slowly the wash breathes. `0` holds it still, which is
|
|
163
|
+
* also what a reduced-motion setting does.
|
|
164
|
+
*/
|
|
165
|
+
speed?: number;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* A decorative layer for the back of a card: the tint rising from its bottom
|
|
170
|
+
* edge and fading out before it reaches the top.
|
|
171
|
+
*
|
|
172
|
+
* It fills its parent absolutely and takes no touches, so it goes first inside
|
|
173
|
+
* a `Card` and everything else is drawn over it. The card needs
|
|
174
|
+
* `overflow-hidden` — the layer reaches the root's edges, and the root is the
|
|
175
|
+
* thing with the corner radius.
|
|
176
|
+
*
|
|
177
|
+
* ```tsx
|
|
178
|
+
* <Card className="overflow-hidden">
|
|
179
|
+
* <Card.Wash tint="--color-primary" />
|
|
180
|
+
* <Card.Header>…</Card.Header>
|
|
181
|
+
* </Card>
|
|
182
|
+
* ```
|
|
183
|
+
*
|
|
184
|
+
* ## Why the rise is a curve and not a fade
|
|
185
|
+
*
|
|
186
|
+
* On a straight ramp the colour is already at half strength across the middle
|
|
187
|
+
* of the card, and what you see is a panel with a tint on it. On a curve there
|
|
188
|
+
* is nothing at all across the top two thirds and then the colour arrives —
|
|
189
|
+
* which is the difference between a card that has been coloured in and a card
|
|
190
|
+
* something is rising into.
|
|
191
|
+
*/
|
|
192
|
+
const CardWash = forwardRef<View, CardWashProps>(
|
|
193
|
+
(
|
|
194
|
+
{
|
|
195
|
+
className,
|
|
196
|
+
tint = '--color-primary',
|
|
197
|
+
intensity = 0.16,
|
|
198
|
+
speed = 1,
|
|
199
|
+
style,
|
|
200
|
+
...props
|
|
201
|
+
},
|
|
202
|
+
ref
|
|
203
|
+
) => {
|
|
204
|
+
const token = useCSSVariable(isToken(tint) ? tint : '--color-primary');
|
|
205
|
+
const resolved = typeof token === 'string' ? token : undefined;
|
|
206
|
+
const color = (isToken(tint) ? resolved : tint) ?? resolved ?? '#0a0a0a';
|
|
207
|
+
|
|
208
|
+
const reduced = useReducedMotion();
|
|
209
|
+
const still = reduced || speed <= 0;
|
|
210
|
+
|
|
211
|
+
const breath = useSharedValue(0);
|
|
212
|
+
|
|
213
|
+
useEffect(() => {
|
|
214
|
+
if (still) {
|
|
215
|
+
breath.value = 0;
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
breath.value = 0;
|
|
219
|
+
breath.value = withRepeat(
|
|
220
|
+
withTiming(1, {
|
|
221
|
+
duration: WASH_PERIOD / speed,
|
|
222
|
+
easing: Easing.inOut(Easing.ease),
|
|
223
|
+
}),
|
|
224
|
+
-1,
|
|
225
|
+
true
|
|
226
|
+
);
|
|
227
|
+
return () => cancelAnimation(breath);
|
|
228
|
+
}, [breath, speed, still]);
|
|
229
|
+
|
|
230
|
+
/*
|
|
231
|
+
* The breath is the layer's opacity rather than its colours: rebuilding
|
|
232
|
+
* ten `rgba()` strings per frame would be a JavaScript-thread animation of
|
|
233
|
+
* the kind this library does not do, and a view's opacity is a property
|
|
234
|
+
* the compositor already knows how to move.
|
|
235
|
+
*/
|
|
236
|
+
const breathing = useAnimatedStyle(() => ({
|
|
237
|
+
opacity: 1 - WASH_BREATH + breath.value * WASH_BREATH,
|
|
238
|
+
}));
|
|
239
|
+
|
|
240
|
+
const { colors, locations } = useMemo(() => {
|
|
241
|
+
const out: string[] = [];
|
|
242
|
+
const at: number[] = [];
|
|
243
|
+
for (let i = 0; i < WASH_STOPS; i += 1) {
|
|
244
|
+
const t = i / (WASH_STOPS - 1);
|
|
245
|
+
out.push(withAlpha(color, Math.pow(t, WASH_CURVE) * intensity));
|
|
246
|
+
at.push(t);
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
colors: out as unknown as readonly [string, string, ...string[]],
|
|
250
|
+
locations: at as unknown as readonly [number, number, ...number[]],
|
|
251
|
+
};
|
|
252
|
+
}, [color, intensity]);
|
|
253
|
+
|
|
254
|
+
return (
|
|
255
|
+
<Animated.View
|
|
256
|
+
ref={ref}
|
|
257
|
+
// Decorative and non-interactive: it is the back of the card, and a
|
|
258
|
+
// screen reader announcing "image" over every card would be noise.
|
|
259
|
+
pointerEvents="none"
|
|
260
|
+
accessibilityElementsHidden
|
|
261
|
+
importantForAccessibility="no-hide-descendants"
|
|
262
|
+
className={cn('absolute inset-0 overflow-hidden', className)}
|
|
263
|
+
style={[still ? undefined : breathing, style]}
|
|
264
|
+
{...props}
|
|
265
|
+
>
|
|
266
|
+
{/*
|
|
267
|
+
* Every stop is the tint at a different alpha, never `transparent`:
|
|
268
|
+
* that is black at zero alpha on Android, and a wash faded to it
|
|
269
|
+
* arrives through a grey smear on the way.
|
|
270
|
+
*/}
|
|
271
|
+
<LinearGradient
|
|
272
|
+
colors={colors}
|
|
273
|
+
locations={locations}
|
|
274
|
+
style={StyleSheet.absoluteFill}
|
|
275
|
+
/>
|
|
276
|
+
</Animated.View>
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
);
|
|
280
|
+
CardWash.displayName = 'Card.Wash';
|
|
281
|
+
|
|
58
282
|
export const Card = Object.assign(CardRoot, {
|
|
59
283
|
Header: CardHeader,
|
|
60
284
|
Title: CardTitle,
|
|
61
285
|
Description: CardDescription,
|
|
62
286
|
Content: CardContent,
|
|
63
287
|
Footer: CardFooter,
|
|
288
|
+
Wash: CardWash,
|
|
64
289
|
});
|