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.
Files changed (92) hide show
  1. package/README.md +4 -2
  2. package/lib/module/components/card/index.js +172 -3
  3. package/lib/module/components/card/index.js.map +1 -1
  4. package/lib/module/components/color-picker/index.js +1395 -0
  5. package/lib/module/components/color-picker/index.js.map +1 -0
  6. package/lib/module/components/combobox/index.js +87 -19
  7. package/lib/module/components/combobox/index.js.map +1 -1
  8. package/lib/module/components/drawer/index.js +13 -4
  9. package/lib/module/components/drawer/index.js.map +1 -1
  10. package/lib/module/components/kpi-chart/index.js +648 -0
  11. package/lib/module/components/kpi-chart/index.js.map +1 -0
  12. package/lib/module/components/map/index.js +72 -12
  13. package/lib/module/components/map/index.js.map +1 -1
  14. package/lib/module/components/menu/index.js +40 -10
  15. package/lib/module/components/menu/index.js.map +1 -1
  16. package/lib/module/components/pagination/index.js +19 -9
  17. package/lib/module/components/pagination/index.js.map +1 -1
  18. package/lib/module/components/panelside/index.js +68 -18
  19. package/lib/module/components/panelside/index.js.map +1 -1
  20. package/lib/module/components/radar-chart/index.js +704 -0
  21. package/lib/module/components/radar-chart/index.js.map +1 -0
  22. package/lib/module/components/section-rail/index.js +32 -7
  23. package/lib/module/components/section-rail/index.js.map +1 -1
  24. package/lib/module/components/slider/index.js +240 -39
  25. package/lib/module/components/slider/index.js.map +1 -1
  26. package/lib/module/components/tabs/index.js +194 -10
  27. package/lib/module/components/tabs/index.js.map +1 -1
  28. package/lib/module/components/text-animation/index.js +857 -0
  29. package/lib/module/components/text-animation/index.js.map +1 -0
  30. package/lib/module/icons/index.js +25 -7
  31. package/lib/module/icons/index.js.map +1 -1
  32. package/lib/module/index.js +5 -0
  33. package/lib/module/index.js.map +1 -1
  34. package/lib/module/theme/use-theme.js +1 -1
  35. package/lib/module/utils/chart.js +44 -0
  36. package/lib/module/utils/chart.js.map +1 -1
  37. package/lib/module/utils/color.js +268 -0
  38. package/lib/module/utils/color.js.map +1 -0
  39. package/lib/typescript/src/components/card/index.d.ts +20 -0
  40. package/lib/typescript/src/components/card/index.d.ts.map +1 -1
  41. package/lib/typescript/src/components/color-picker/index.d.ts +258 -0
  42. package/lib/typescript/src/components/color-picker/index.d.ts.map +1 -0
  43. package/lib/typescript/src/components/combobox/index.d.ts.map +1 -1
  44. package/lib/typescript/src/components/drawer/index.d.ts +13 -1
  45. package/lib/typescript/src/components/drawer/index.d.ts.map +1 -1
  46. package/lib/typescript/src/components/kpi-chart/index.d.ts +490 -0
  47. package/lib/typescript/src/components/kpi-chart/index.d.ts.map +1 -0
  48. package/lib/typescript/src/components/map/index.d.ts +37 -2
  49. package/lib/typescript/src/components/map/index.d.ts.map +1 -1
  50. package/lib/typescript/src/components/menu/index.d.ts.map +1 -1
  51. package/lib/typescript/src/components/pagination/index.d.ts.map +1 -1
  52. package/lib/typescript/src/components/panelside/index.d.ts +50 -4
  53. package/lib/typescript/src/components/panelside/index.d.ts.map +1 -1
  54. package/lib/typescript/src/components/radar-chart/index.d.ts +214 -0
  55. package/lib/typescript/src/components/radar-chart/index.d.ts.map +1 -0
  56. package/lib/typescript/src/components/section-rail/index.d.ts +8 -1
  57. package/lib/typescript/src/components/section-rail/index.d.ts.map +1 -1
  58. package/lib/typescript/src/components/slider/index.d.ts +19 -0
  59. package/lib/typescript/src/components/slider/index.d.ts.map +1 -1
  60. package/lib/typescript/src/components/tabs/index.d.ts +11 -1
  61. package/lib/typescript/src/components/tabs/index.d.ts.map +1 -1
  62. package/lib/typescript/src/components/text-animation/index.d.ts +268 -0
  63. package/lib/typescript/src/components/text-animation/index.d.ts.map +1 -0
  64. package/lib/typescript/src/icons/index.d.ts +6 -0
  65. package/lib/typescript/src/icons/index.d.ts.map +1 -1
  66. package/lib/typescript/src/index.d.ts +8 -3
  67. package/lib/typescript/src/index.d.ts.map +1 -1
  68. package/lib/typescript/src/utils/chart.d.ts +21 -0
  69. package/lib/typescript/src/utils/chart.d.ts.map +1 -1
  70. package/lib/typescript/src/utils/color.d.ts +74 -0
  71. package/lib/typescript/src/utils/color.d.ts.map +1 -0
  72. package/package.json +2 -1
  73. package/src/components/card/index.tsx +227 -2
  74. package/src/components/color-picker/index.tsx +1618 -0
  75. package/src/components/combobox/index.tsx +89 -19
  76. package/src/components/drawer/index.tsx +31 -9
  77. package/src/components/kpi-chart/index.tsx +719 -0
  78. package/src/components/map/index.tsx +158 -14
  79. package/src/components/menu/index.tsx +51 -6
  80. package/src/components/pagination/index.tsx +18 -6
  81. package/src/components/panelside/index.tsx +134 -19
  82. package/src/components/radar-chart/index.tsx +910 -0
  83. package/src/components/section-rail/index.tsx +42 -8
  84. package/src/components/slider/index.tsx +283 -41
  85. package/src/components/tabs/index.tsx +263 -8
  86. package/src/components/text-animation/index.tsx +1093 -0
  87. package/src/icons/index.tsx +23 -4
  88. package/src/index.ts +73 -1
  89. package/src/theme/use-theme.ts +1 -1
  90. package/src/utils/chart.ts +52 -0
  91. package/src/utils/color.ts +246 -0
  92. package/theme.css +125 -112
@@ -1,5 +1,35 @@
1
- import { forwardRef } from 'react';
2
- import { View, type ViewProps } from 'react-native';
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
  });