panelui-native 0.100.0 → 0.101.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 (31) hide show
  1. package/README.md +2 -0
  2. package/lib/module/components/compare/index.js +576 -0
  3. package/lib/module/components/compare/index.js.map +1 -0
  4. package/lib/module/components/sankey-chart/index.js +933 -0
  5. package/lib/module/components/sankey-chart/index.js.map +1 -0
  6. package/lib/module/components/sankey-chart/sankey-layout.js +530 -0
  7. package/lib/module/components/sankey-chart/sankey-layout.js.map +1 -0
  8. package/lib/module/icons/index.js +15 -3
  9. package/lib/module/icons/index.js.map +1 -1
  10. package/lib/module/index.js +2 -0
  11. package/lib/module/index.js.map +1 -1
  12. package/lib/module/utils/chart.js +31 -0
  13. package/lib/module/utils/chart.js.map +1 -1
  14. package/lib/typescript/src/components/compare/index.d.ts +183 -0
  15. package/lib/typescript/src/components/compare/index.d.ts.map +1 -0
  16. package/lib/typescript/src/components/sankey-chart/index.d.ts +307 -0
  17. package/lib/typescript/src/components/sankey-chart/index.d.ts.map +1 -0
  18. package/lib/typescript/src/components/sankey-chart/sankey-layout.d.ts +107 -0
  19. package/lib/typescript/src/components/sankey-chart/sankey-layout.d.ts.map +1 -0
  20. package/lib/typescript/src/icons/index.d.ts.map +1 -1
  21. package/lib/typescript/src/index.d.ts +2 -0
  22. package/lib/typescript/src/index.d.ts.map +1 -1
  23. package/lib/typescript/src/utils/chart.d.ts +17 -0
  24. package/lib/typescript/src/utils/chart.d.ts.map +1 -1
  25. package/package.json +1 -1
  26. package/src/components/compare/index.tsx +650 -0
  27. package/src/components/sankey-chart/index.tsx +1165 -0
  28. package/src/components/sankey-chart/sankey-layout.ts +645 -0
  29. package/src/icons/index.tsx +20 -6
  30. package/src/index.ts +25 -0
  31. package/src/utils/chart.ts +46 -0
@@ -1 +1 @@
1
- {"version":3,"file":"chart.d.ts","sourceRoot":"","sources":["../../../../src/utils/chart.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"chart.d.ts","sourceRoot":"","sources":["../../../../src/utils/chart.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "panelui-native",
3
- "version": "0.100.0",
3
+ "version": "0.101.0",
4
4
  "description": "High-performance React Native UI components for Expo. Semantic design tokens, powered by Uniwind (Tailwind v4) and Reanimated.",
5
5
  "main": "./lib/module/index.js",
6
6
  "module": "./lib/module/index.js",
@@ -0,0 +1,650 @@
1
+ /**
2
+ * Compare — two versions of one picture, with a seam the reader drags across
3
+ * it.
4
+ *
5
+ * ```tsx
6
+ * <Compare height={260}>
7
+ * <Compare.After>
8
+ * <Image source={retouched} style={{ width: '100%', height: '100%' }} />
9
+ * </Compare.After>
10
+ * <Compare.Before>
11
+ * <Image source={original} style={{ width: '100%', height: '100%' }} />
12
+ * </Compare.Before>
13
+ * <Compare.Handle />
14
+ * </Compare>
15
+ * ```
16
+ *
17
+ * ## What it is for
18
+ *
19
+ * Showing that two images differ, when they differ in a way a pair of
20
+ * thumbnails side by side will not carry. Retouching, a filter, a render at two
21
+ * quality settings, a map at two dates: the change is spread across the frame
22
+ * rather than gathered in one place, and the eye cannot hold one image well
23
+ * enough to spot it in the other.
24
+ *
25
+ * The seam works because both versions are in the same place on the screen at
26
+ * the same scale. Every pixel the reader is comparing is a pixel that was just
27
+ * under the one beside it, so the difference arrives as movement rather than as
28
+ * something to be remembered.
29
+ *
30
+ * ## It clips, it does not resize
31
+ *
32
+ * The revealed side is a window onto a full-size copy of its content, not a
33
+ * copy of the content squeezed into the window. That distinction is the whole
34
+ * implementation: a view whose width is animated will lay its children out
35
+ * again at every new width, so an image inside one is an image being squashed
36
+ * and stretched as the seam moves, and the two halves stop lining up — which
37
+ * is the one thing this component exists to guarantee.
38
+ *
39
+ * It follows that the content has to be told how big to be, and the only thing
40
+ * that knows is the container once it has been measured. So nothing is drawn
41
+ * until the first layout pass has run, and `Compare.Before` sizes its child to
42
+ * the measured box rather than to itself.
43
+ *
44
+ * ## It needs a height
45
+ *
46
+ * Everything inside is positioned absolutely, so the box has no height of its
47
+ * own to take from its content. `height` is what gives it one. An image told to
48
+ * fill the box will fill whatever height is set here, and cropping is
49
+ * `resizeMode` on the image rather than anything this component does.
50
+ *
51
+ * ## Dragging, and the two ways round it
52
+ *
53
+ * The whole frame is the drag target, not just the knob — a knob is a small
54
+ * thing to hit on a phone and the reader's finger is already over the picture.
55
+ * The gesture only claims horizontal movement, so a Compare inside a scrolling
56
+ * page still scrolls.
57
+ *
58
+ * A drag is not available to everyone, so the seam is also `adjustable`: a
59
+ * screen reader moves it a `step` at a time without one, and `value` drives it
60
+ * from anywhere else — a button that snaps to the ends, an animation, a slider
61
+ * somewhere else on the screen.
62
+ */
63
+ import {
64
+ Children,
65
+ createContext,
66
+ isValidElement,
67
+ useCallback,
68
+ useContext,
69
+ useEffect,
70
+ useMemo,
71
+ useState,
72
+ type ReactNode,
73
+ } from 'react';
74
+ import {
75
+ View,
76
+ type AccessibilityActionEvent,
77
+ type LayoutChangeEvent,
78
+ type ViewProps,
79
+ } from 'react-native';
80
+ import { Gesture, GestureDetector } from 'react-native-gesture-handler';
81
+ import Animated, {
82
+ runOnJS,
83
+ useAnimatedReaction,
84
+ useAnimatedStyle,
85
+ useDerivedValue,
86
+ useSharedValue,
87
+ type SharedValue,
88
+ } from 'react-native-reanimated';
89
+ import { tv } from 'tailwind-variants';
90
+ import { Text } from '../../primitives/text';
91
+ import { cn } from '../../utils/cn';
92
+ import { selectionTick } from '../../utils/haptics';
93
+ import { useDirectionSign } from '../../hooks/use-direction';
94
+
95
+ /** How tall the frame is when the caller does not say. */
96
+ const DEFAULT_HEIGHT = 240;
97
+
98
+ /** The knob's diameter, and so the width of the column the seam lives in. */
99
+ const KNOB = 36;
100
+
101
+ /** How far a screen reader's increment moves the seam, as a share of the box. */
102
+ const DEFAULT_STEP = 0.05;
103
+
104
+ /** Where the seam starts: the middle, so both sides are equally on show. */
105
+ const DEFAULT_VALUE = 0.5;
106
+
107
+ /** Movement along the axis before the drag is the gesture's rather than a scroll's. */
108
+ const CLAIM = 8;
109
+
110
+ /** And movement across it that hands the gesture back. */
111
+ const YIELD = 16;
112
+
113
+ export type CompareOrientation = 'horizontal' | 'vertical';
114
+
115
+ const compareVariants = tv({
116
+ slots: {
117
+ root: 'w-full overflow-hidden rounded-xl bg-muted',
118
+ seam: 'absolute items-center justify-center',
119
+ line: 'absolute bg-background',
120
+ knob: 'items-center justify-center rounded-full border border-border bg-background shadow-md',
121
+ grip: 'rounded-full bg-foreground/60',
122
+ },
123
+ variants: {
124
+ orientation: {
125
+ horizontal: {
126
+ seam: 'bottom-0 top-0',
127
+ line: 'h-full w-0.5',
128
+ grip: 'h-3.5 w-1',
129
+ },
130
+ vertical: {
131
+ seam: 'left-0 right-0',
132
+ line: 'h-0.5 w-full',
133
+ grip: 'h-1 w-3.5',
134
+ },
135
+ },
136
+ },
137
+ defaultVariants: { orientation: 'horizontal' },
138
+ });
139
+
140
+ interface CompareContextValue {
141
+ /** Where the seam is, `0` to `1` of the box, on the UI thread. */
142
+ ratio: SharedValue<number>;
143
+ /** The measured box. Nothing is drawn until both are above zero. */
144
+ width: number;
145
+ height: number;
146
+ orientation: CompareOrientation;
147
+ /**
148
+ * True where the frame reads right to left. A horizontal seam is anchored to
149
+ * the reading edge, so everything positioned along that axis flips with it.
150
+ */
151
+ mirrored: boolean;
152
+ disabled: boolean;
153
+ step: number;
154
+ /** Moves the seam and reports it, for the paths that are not the drag. */
155
+ nudge: (delta: number) => void;
156
+ }
157
+
158
+ const CompareContext = createContext<CompareContextValue | null>(null);
159
+
160
+ function useCompare(component: string): CompareContextValue {
161
+ const context = useContext(CompareContext);
162
+ if (!context) {
163
+ throw new Error(`${component} must be used within a <Compare>`);
164
+ }
165
+ return context;
166
+ }
167
+
168
+ function clamp(value: number): number {
169
+ 'worklet';
170
+ return value < 0 ? 0 : value > 1 ? 1 : value;
171
+ }
172
+
173
+ export interface CompareProps extends Omit<ViewProps, 'children'> {
174
+ className?: string;
175
+ /**
176
+ * How tall the frame is, in points.
177
+ *
178
+ * Required in practice rather than in the types: both sides are positioned
179
+ * absolutely, so there is no content left to give the box a height of its own.
180
+ */
181
+ height?: number;
182
+ /** Where the seam sits, `0` to `1`. Leave unset to let the frame track it. */
183
+ value?: number;
184
+ /** Where it starts when the frame is tracking it itself. */
185
+ defaultValue?: number;
186
+ /** Fires while the seam moves, with its new position. */
187
+ onValueChange?: (value: number) => void;
188
+ /** Fires once, when the finger is lifted. The one to persist. */
189
+ onValueCommit?: (value: number) => void;
190
+ /** Which way the seam runs. */
191
+ orientation?: CompareOrientation;
192
+ /** Freezes the seam where it is and takes it out of the accessibility tree. */
193
+ disabled?: boolean;
194
+ /** How far one screen-reader increment moves the seam, `0` to `1`. */
195
+ step?: number;
196
+ /** A tick when the seam reaches either end. */
197
+ haptics?: boolean;
198
+ children?: ReactNode;
199
+ }
200
+
201
+ function CompareRoot({
202
+ className,
203
+ height = DEFAULT_HEIGHT,
204
+ value,
205
+ defaultValue = DEFAULT_VALUE,
206
+ onValueChange,
207
+ onValueCommit,
208
+ orientation = 'horizontal',
209
+ disabled = false,
210
+ step = DEFAULT_STEP,
211
+ haptics = true,
212
+ children,
213
+ ...props
214
+ }: CompareProps) {
215
+ const [box, setBox] = useState({ width: 0, height: 0 });
216
+ const [internal, setInternal] = useState(defaultValue);
217
+ const sign = useDirectionSign();
218
+ const mirrored = sign === -1;
219
+
220
+ const controlled = value !== undefined;
221
+ const current = controlled ? value : internal;
222
+ const ratio = useSharedValue(clamp(current));
223
+ /** True between the finger landing and it lifting, so the prop can stand back. */
224
+ const dragging = useSharedValue(false);
225
+
226
+ /*
227
+ * A controlled `value` owns the seam except while a finger is on it. The
228
+ * drag writes the shared value directly and reports afterwards, so pushing
229
+ * the prop back in during the drag would fight it for the same number.
230
+ */
231
+ const settled = clamp(current);
232
+ useEffect(() => {
233
+ if (controlled && !dragging.value) ratio.value = settled;
234
+ }, [controlled, settled, ratio, dragging]);
235
+
236
+ const commit = useCallback(
237
+ (next: number) => {
238
+ if (!controlled) setInternal(next);
239
+ onValueChange?.(next);
240
+ },
241
+ [controlled, onValueChange]
242
+ );
243
+
244
+ const finish = useCallback(
245
+ (next: number) => {
246
+ if (!controlled) setInternal(next);
247
+ onValueCommit?.(next);
248
+ },
249
+ [controlled, onValueCommit]
250
+ );
251
+
252
+ /*
253
+ * Reported a whole percent at a time rather than every frame. The seam itself
254
+ * never needs the JS thread — it is drawn from the shared value — so a
255
+ * callback per frame would be sixty re-renders a second spent on a number
256
+ * nobody can read that fast.
257
+ */
258
+ const reported = useSharedValue(-1);
259
+ const report = useCallback(
260
+ (next: number) => {
261
+ onValueChange?.(next);
262
+ },
263
+ [onValueChange]
264
+ );
265
+
266
+ const nudge = useCallback(
267
+ (delta: number) => {
268
+ const next = clamp(ratio.value + delta);
269
+ ratio.value = next;
270
+ commit(next);
271
+ onValueCommit?.(next);
272
+ },
273
+ [ratio, commit, onValueCommit]
274
+ );
275
+
276
+ const horizontal = orientation === 'horizontal';
277
+ const extent = horizontal ? box.width : box.height;
278
+
279
+ const start = useSharedValue(0);
280
+ const edge = useSharedValue(false);
281
+
282
+ const pan = useMemo(() => {
283
+ const gesture = Gesture.Pan()
284
+ .enabled(!disabled)
285
+ .onBegin(() => {
286
+ start.value = ratio.value;
287
+ dragging.value = true;
288
+ edge.value = ratio.value <= 0 || ratio.value >= 1;
289
+ })
290
+ .onUpdate((event) => {
291
+ if (extent <= 0) return;
292
+ /*
293
+ * Under a right-to-left layout the leading edge is the right-hand one,
294
+ * so a drag towards it has to move the seam the other way for "towards
295
+ * the start" to keep meaning the same thing. Only the horizontal seam
296
+ * has a leading edge; down is down in both directions.
297
+ */
298
+ const travelled = horizontal ? event.translationX * sign : event.translationY;
299
+ const next = clamp(start.value + travelled / extent);
300
+ ratio.value = next;
301
+
302
+ // The one moment in this drag worth feeling: arriving at an end, where
303
+ // the seam stops following the finger. Without it the stall reads as
304
+ // the gesture having been dropped.
305
+ const ended = next <= 0 || next >= 1;
306
+ if (ended !== edge.value) {
307
+ edge.value = ended;
308
+ if (ended && haptics) runOnJS(selectionTick)();
309
+ }
310
+
311
+ const percent = Math.round(next * 100);
312
+ if (percent !== reported.value) {
313
+ reported.value = percent;
314
+ runOnJS(report)(next);
315
+ }
316
+ })
317
+ .onFinalize(() => {
318
+ dragging.value = false;
319
+ reported.value = -1;
320
+ runOnJS(finish)(ratio.value);
321
+ });
322
+
323
+ /*
324
+ * The seam only answers to the axis it moves on. Without that it claims any
325
+ * movement at all, which is a Compare inside a scrolling page eating the
326
+ * scroll the moment a finger lands on the picture.
327
+ */
328
+ return horizontal
329
+ ? gesture.activeOffsetX([-CLAIM, CLAIM]).failOffsetY([-YIELD, YIELD])
330
+ : gesture.activeOffsetY([-CLAIM, CLAIM]).failOffsetX([-YIELD, YIELD]);
331
+ }, [
332
+ disabled,
333
+ extent,
334
+ horizontal,
335
+ sign,
336
+ haptics,
337
+ ratio,
338
+ start,
339
+ edge,
340
+ dragging,
341
+ reported,
342
+ report,
343
+ finish,
344
+ ]);
345
+
346
+ const onLayout = (event: LayoutChangeEvent) => {
347
+ const { width: w, height: h } = event.nativeEvent.layout;
348
+ const next = { width: Math.round(w), height: Math.round(h) };
349
+ if (next.width !== box.width || next.height !== box.height) setBox(next);
350
+ props.onLayout?.(event);
351
+ };
352
+
353
+ const context = useMemo<CompareContextValue>(
354
+ () => ({
355
+ ratio,
356
+ width: box.width,
357
+ height: box.height,
358
+ orientation,
359
+ mirrored,
360
+ disabled,
361
+ step,
362
+ nudge,
363
+ }),
364
+ [ratio, box.width, box.height, orientation, mirrored, disabled, step, nudge]
365
+ );
366
+
367
+ const { root } = compareVariants({ orientation });
368
+
369
+ /*
370
+ * The background side is drawn first whatever order the caller wrote the
371
+ * children in, because the revealed side is a window laid over it — reversed,
372
+ * the window would be underneath and nothing would show through.
373
+ */
374
+ const layers: { base: ReactNode[]; clip: ReactNode[]; over: ReactNode[] } = {
375
+ base: [],
376
+ clip: [],
377
+ over: [],
378
+ };
379
+ Children.forEach(children, (child, index) => {
380
+ if (!isValidElement(child)) return;
381
+ const layer = (child.type as { layer?: keyof typeof layers }).layer ?? 'over';
382
+ layers[layer in layers ? layer : 'over'].push(
383
+ <ChildSlot key={index}>{child}</ChildSlot>
384
+ );
385
+ });
386
+
387
+ return (
388
+ <CompareContext.Provider value={context}>
389
+ <GestureDetector gesture={pan}>
390
+ <View
391
+ {...props}
392
+ onLayout={onLayout}
393
+ style={[{ height }, props.style]}
394
+ className={cn(root(), className)}
395
+ >
396
+ {box.width > 0 && box.height > 0 ? (
397
+ <>
398
+ {layers.base}
399
+ {layers.clip}
400
+ {layers.over}
401
+ </>
402
+ ) : null}
403
+ </View>
404
+ </GestureDetector>
405
+ </CompareContext.Provider>
406
+ );
407
+ }
408
+
409
+ function ChildSlot({ children }: { children: ReactNode }) {
410
+ return <>{children}</>;
411
+ }
412
+
413
+ export interface CompareAfterProps extends ViewProps {
414
+ className?: string;
415
+ children?: ReactNode;
416
+ }
417
+
418
+ /**
419
+ * The side the seam uncovers as it travels: the whole frame, underneath.
420
+ *
421
+ * It is drawn at full size and never clipped, so it is the one that decides
422
+ * what the frame looks like at either end of the travel.
423
+ */
424
+ function CompareAfter({ className, children, ...props }: CompareAfterProps) {
425
+ const { width, height } = useCompare('Compare.After');
426
+
427
+ return (
428
+ <View
429
+ {...props}
430
+ pointerEvents="none"
431
+ style={[{ position: 'absolute', left: 0, top: 0, width, height }, props.style]}
432
+ className={cn(className)}
433
+ >
434
+ {children}
435
+ </View>
436
+ );
437
+ }
438
+ CompareAfter.displayName = 'Compare.After';
439
+ CompareAfter.layer = 'base' as const;
440
+
441
+ export interface CompareBeforeProps extends ViewProps {
442
+ className?: string;
443
+ children?: ReactNode;
444
+ }
445
+
446
+ /**
447
+ * The side on the near edge of the seam: a window onto the content, sized to
448
+ * the frame.
449
+ *
450
+ * The child is given the frame's measured size in points rather than a
451
+ * percentage. A percentage would be a percentage *of the window*, which shrinks
452
+ * as the seam closes — so the image would slide and scale under the seam
453
+ * instead of standing still behind it, and the two halves would no longer be
454
+ * the same picture in the same place.
455
+ */
456
+ function CompareBefore({ className, children, ...props }: CompareBeforeProps) {
457
+ const { ratio, width, height, orientation, mirrored } = useCompare('Compare.Before');
458
+ const horizontal = orientation === 'horizontal';
459
+ /*
460
+ * A horizontal window opens from the edge the line starts at, which is the
461
+ * right-hand one under a right-to-left layout. Both the window and the copy
462
+ * inside it are pinned to that same edge — pinning only the window would
463
+ * slide the content along under the seam, which is the one thing the clip
464
+ * exists to prevent. Vertical is unaffected: down is down in both directions.
465
+ */
466
+ const anchor = horizontal && mirrored ? 'right' : 'left';
467
+
468
+ const clip = useAnimatedStyle(() =>
469
+ horizontal
470
+ ? { width: ratio.value * width, height }
471
+ : { width, height: ratio.value * height }
472
+ );
473
+
474
+ return (
475
+ <Animated.View
476
+ pointerEvents="none"
477
+ style={[{ position: 'absolute', [anchor]: 0, top: 0, overflow: 'hidden' }, clip]}
478
+ >
479
+ <View
480
+ {...props}
481
+ style={[{ position: 'absolute', [anchor]: 0, top: 0, width, height }, props.style]}
482
+ className={cn(className)}
483
+ >
484
+ {children}
485
+ </View>
486
+ </Animated.View>
487
+ );
488
+ }
489
+ CompareBefore.displayName = 'Compare.Before';
490
+ CompareBefore.layer = 'clip' as const;
491
+
492
+ export interface CompareHandleProps extends ViewProps {
493
+ className?: string;
494
+ /** Hide the two grip bars inside the knob. */
495
+ withGrip?: boolean;
496
+ /** Spoken name. */
497
+ accessibilityLabel?: string;
498
+ /** Replaces the knob. The line behind it is kept. */
499
+ children?: ReactNode;
500
+ }
501
+
502
+ /**
503
+ * The seam: a line across the frame with a knob on it.
504
+ *
505
+ * The knob is a marker rather than the target — the drag is on the whole frame,
506
+ * and a 36-point circle is not something to ask a thumb to find. What it is
507
+ * for is saying where the seam is and that it is the thing that moves, which a
508
+ * bare line does not.
509
+ *
510
+ * It carries the accessibility wiring for the same reason a splitter's handle
511
+ * does: it is the one part of this that is a control, so it is the part that is
512
+ * `adjustable` and takes the increment and decrement a screen reader sends
513
+ * instead of a drag.
514
+ */
515
+ function CompareHandle({
516
+ className,
517
+ withGrip = true,
518
+ accessibilityLabel = 'Compare',
519
+ children,
520
+ style,
521
+ ...props
522
+ }: CompareHandleProps) {
523
+ const { ratio, width, height, orientation, mirrored, disabled, step, nudge } =
524
+ useCompare('Compare.Handle');
525
+ const horizontal = orientation === 'horizontal';
526
+ const { seam, line, knob, grip } = compareVariants({ orientation });
527
+ // Pinned to the same edge the window opens from, and travelling away from it.
528
+ const anchor = horizontal && mirrored ? 'right' : 'left';
529
+ const towards = horizontal && mirrored ? -1 : 1;
530
+
531
+ const offset = useDerivedValue(() =>
532
+ horizontal ? ratio.value * width : ratio.value * height
533
+ );
534
+
535
+ const animatedStyle = useAnimatedStyle(() =>
536
+ horizontal
537
+ ? { transform: [{ translateX: (offset.value - KNOB / 2) * towards }] }
538
+ : { transform: [{ translateY: offset.value - KNOB / 2 }] }
539
+ );
540
+
541
+ const [now, setNow] = useState(() => Math.round(ratio.value * 100));
542
+ // The spoken value only has to be right when a screen reader reads it, so it
543
+ // is pulled back to the JS thread a percent at a time rather than every frame
544
+ // of a drag.
545
+ useAnimatedReaction(
546
+ () => Math.round(ratio.value * 100),
547
+ (percent, previous) => {
548
+ if (percent !== previous) runOnJS(setNow)(percent);
549
+ }
550
+ );
551
+
552
+ const onAccessibilityAction = useCallback(
553
+ (event: AccessibilityActionEvent) => {
554
+ if (event.nativeEvent.actionName === 'increment') nudge(step);
555
+ else if (event.nativeEvent.actionName === 'decrement') nudge(-step);
556
+ },
557
+ [nudge, step]
558
+ );
559
+
560
+ return (
561
+ <Animated.View
562
+ {...props}
563
+ accessible
564
+ accessibilityRole="adjustable"
565
+ accessibilityLabel={accessibilityLabel}
566
+ accessibilityState={{ disabled }}
567
+ accessibilityValue={{ min: 0, max: 100, now }}
568
+ accessibilityActions={disabled ? undefined : [{ name: 'increment' }, { name: 'decrement' }]}
569
+ onAccessibilityAction={disabled ? undefined : onAccessibilityAction}
570
+ pointerEvents="box-none"
571
+ style={[
572
+ horizontal ? { width: KNOB, [anchor]: 0 } : { height: KNOB, top: 0 },
573
+ style,
574
+ animatedStyle,
575
+ ]}
576
+ className={seam()}
577
+ >
578
+ <View className={line()} pointerEvents="none" />
579
+ {children ?? (
580
+ <View style={{ width: KNOB, height: KNOB }} className={knob()} pointerEvents="none">
581
+ {withGrip ? (
582
+ <View className={horizontal ? 'flex-row gap-1' : 'gap-1'}>
583
+ <View className={grip()} />
584
+ <View className={grip()} />
585
+ </View>
586
+ ) : null}
587
+ </View>
588
+ )}
589
+ </Animated.View>
590
+ );
591
+ }
592
+ CompareHandle.displayName = 'Compare.Handle';
593
+ CompareHandle.layer = 'over' as const;
594
+
595
+ export interface CompareLabelProps extends ViewProps {
596
+ className?: string;
597
+ /** Which side of the frame it sits on. */
598
+ side?: 'start' | 'end';
599
+ children?: ReactNode;
600
+ }
601
+
602
+ /**
603
+ * A caption pinned to one corner, for saying which side is which.
604
+ *
605
+ * Worth adding wherever the two versions are not obviously an original and an
606
+ * edit — two dates, two settings, two models. Where they are, it is one more
607
+ * thing over the picture and the picture is the point.
608
+ */
609
+ function CompareLabel({ className, side = 'start', children, ...props }: CompareLabelProps) {
610
+ const { orientation, mirrored } = useCompare('Compare.Label');
611
+ const horizontal = orientation === 'horizontal';
612
+ // `start` is the edge the line begins at, which the layout direction decides.
613
+ const start = horizontal && mirrored ? side !== 'start' : side === 'start';
614
+
615
+ return (
616
+ <View
617
+ {...props}
618
+ pointerEvents="none"
619
+ style={[
620
+ {
621
+ position: 'absolute',
622
+ ...(horizontal
623
+ ? { top: 8, ...(start ? { left: 8 } : { right: 8 }) }
624
+ : { left: 8, ...(start ? { top: 8 } : { bottom: 8 }) }),
625
+ },
626
+ props.style,
627
+ ]}
628
+ className={cn('rounded-md bg-background/80 px-2 py-1', className)}
629
+ >
630
+ {typeof children === 'string' ? (
631
+ <Text size="xs" weight="medium" numberOfLines={1}>
632
+ {children}
633
+ </Text>
634
+ ) : (
635
+ children
636
+ )}
637
+ </View>
638
+ );
639
+ }
640
+ CompareLabel.displayName = 'Compare.Label';
641
+ CompareLabel.layer = 'over' as const;
642
+
643
+ CompareRoot.displayName = 'Compare';
644
+
645
+ export const Compare = Object.assign(CompareRoot, {
646
+ Before: CompareBefore,
647
+ After: CompareAfter,
648
+ Handle: CompareHandle,
649
+ Label: CompareLabel,
650
+ });