panelui-native 0.6.0 → 0.9.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 +1 -1
- package/lib/module/components/accordion/index.js +7 -5
- package/lib/module/components/accordion/index.js.map +1 -1
- package/lib/module/components/alert/index.js +3 -6
- package/lib/module/components/alert/index.js.map +1 -1
- package/lib/module/components/attachment/index.js +219 -0
- package/lib/module/components/attachment/index.js.map +1 -0
- package/lib/module/components/bottom-sheet/index.js +82 -4
- package/lib/module/components/bottom-sheet/index.js.map +1 -1
- package/lib/module/components/button/index.js +36 -1
- package/lib/module/components/button/index.js.map +1 -1
- package/lib/module/components/dialog/index.js +6 -2
- package/lib/module/components/dialog/index.js.map +1 -1
- package/lib/module/components/empty-state/index.js +98 -22
- package/lib/module/components/empty-state/index.js.map +1 -1
- package/lib/module/components/inline-select/index.js +171 -46
- package/lib/module/components/inline-select/index.js.map +1 -1
- package/lib/module/components/input/index.js +28 -5
- package/lib/module/components/input/index.js.map +1 -1
- package/lib/module/components/input-group/index.js +0 -1
- package/lib/module/components/input-group/index.js.map +1 -1
- package/lib/module/components/item/index.js +359 -0
- package/lib/module/components/item/index.js.map +1 -0
- package/lib/module/components/label/index.js +5 -3
- package/lib/module/components/label/index.js.map +1 -1
- package/lib/module/components/line-chart/index.js +1024 -0
- package/lib/module/components/line-chart/index.js.map +1 -0
- package/lib/module/components/marker/index.js +190 -0
- package/lib/module/components/marker/index.js.map +1 -0
- package/lib/module/components/message/index.js +334 -0
- package/lib/module/components/message/index.js.map +1 -0
- package/lib/module/components/message-scroller/index.js +451 -0
- package/lib/module/components/message-scroller/index.js.map +1 -0
- package/lib/module/components/popover/index.js +500 -0
- package/lib/module/components/popover/index.js.map +1 -0
- package/lib/module/components/radio-group/index.js +104 -16
- package/lib/module/components/radio-group/index.js.map +1 -1
- package/lib/module/components/scroll-fade/index.js +119 -71
- package/lib/module/components/scroll-fade/index.js.map +1 -1
- package/lib/module/components/select/index.js +35 -6
- package/lib/module/components/select/index.js.map +1 -1
- package/lib/module/components/separator/index.js +91 -0
- package/lib/module/components/separator/index.js.map +1 -0
- package/lib/module/components/shimmer/index.js +111 -64
- package/lib/module/components/shimmer/index.js.map +1 -1
- package/lib/module/components/surface/index.js +24 -4
- package/lib/module/components/surface/index.js.map +1 -1
- package/lib/module/components/switch/index.js +20 -1
- package/lib/module/components/switch/index.js.map +1 -1
- package/lib/module/components/toast/index.js +7 -8
- package/lib/module/components/toast/index.js.map +1 -1
- package/lib/module/components/typography/index.js +3 -3
- package/lib/module/hooks/index.js +1 -0
- package/lib/module/hooks/index.js.map +1 -1
- package/lib/module/hooks/use-breakpoint.js +2 -2
- package/lib/module/hooks/use-copy-to-clipboard.js +0 -2
- package/lib/module/hooks/use-copy-to-clipboard.js.map +1 -1
- package/lib/module/hooks/use-keyboard-avoidance.js +120 -0
- package/lib/module/hooks/use-keyboard-avoidance.js.map +1 -0
- package/lib/module/icons/index.js +73 -4
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/index.js +13 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/native/index.js +53 -0
- package/lib/module/native/index.js.map +1 -0
- package/lib/module/primitives/keyboard-avoider.js +40 -0
- package/lib/module/primitives/keyboard-avoider.js.map +1 -0
- package/lib/module/primitives/scrim.js +72 -0
- package/lib/module/primitives/scrim.js.map +1 -0
- package/lib/module/providers/panel-ui-provider.js +24 -4
- package/lib/module/providers/panel-ui-provider.js.map +1 -1
- package/lib/module/theme/use-theme.js +1 -1
- package/lib/typescript/src/components/accordion/index.d.ts +7 -5
- package/lib/typescript/src/components/accordion/index.d.ts.map +1 -1
- package/lib/typescript/src/components/alert/index.d.ts +3 -6
- package/lib/typescript/src/components/alert/index.d.ts.map +1 -1
- package/lib/typescript/src/components/attachment/index.d.ts +93 -0
- package/lib/typescript/src/components/attachment/index.d.ts.map +1 -0
- package/lib/typescript/src/components/bottom-sheet/index.d.ts +27 -2
- package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
- package/lib/typescript/src/components/button/index.d.ts +11 -0
- package/lib/typescript/src/components/button/index.d.ts.map +1 -1
- package/lib/typescript/src/components/dialog/index.d.ts +7 -1
- package/lib/typescript/src/components/dialog/index.d.ts.map +1 -1
- package/lib/typescript/src/components/empty-state/index.d.ts +156 -7
- package/lib/typescript/src/components/empty-state/index.d.ts.map +1 -1
- package/lib/typescript/src/components/inline-select/index.d.ts +29 -6
- package/lib/typescript/src/components/inline-select/index.d.ts.map +1 -1
- package/lib/typescript/src/components/input/index.d.ts +13 -0
- package/lib/typescript/src/components/input/index.d.ts.map +1 -1
- package/lib/typescript/src/components/input-group/index.d.ts +0 -1
- package/lib/typescript/src/components/input-group/index.d.ts.map +1 -1
- package/lib/typescript/src/components/item/index.d.ts +286 -0
- package/lib/typescript/src/components/item/index.d.ts.map +1 -0
- package/lib/typescript/src/components/label/index.d.ts +5 -3
- package/lib/typescript/src/components/label/index.d.ts.map +1 -1
- package/lib/typescript/src/components/line-chart/index.d.ts +247 -0
- package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -0
- package/lib/typescript/src/components/marker/index.d.ts +137 -0
- package/lib/typescript/src/components/marker/index.d.ts.map +1 -0
- package/lib/typescript/src/components/message/index.d.ts +177 -0
- package/lib/typescript/src/components/message/index.d.ts.map +1 -0
- package/lib/typescript/src/components/message-scroller/index.d.ts +150 -0
- package/lib/typescript/src/components/message-scroller/index.d.ts.map +1 -0
- package/lib/typescript/src/components/popover/index.d.ts +130 -0
- package/lib/typescript/src/components/popover/index.d.ts.map +1 -0
- package/lib/typescript/src/components/radio-group/index.d.ts +32 -0
- package/lib/typescript/src/components/radio-group/index.d.ts.map +1 -1
- package/lib/typescript/src/components/scroll-fade/index.d.ts +18 -9
- package/lib/typescript/src/components/scroll-fade/index.d.ts.map +1 -1
- package/lib/typescript/src/components/select/index.d.ts +16 -1
- package/lib/typescript/src/components/select/index.d.ts.map +1 -1
- package/lib/typescript/src/components/separator/index.d.ts +47 -0
- package/lib/typescript/src/components/separator/index.d.ts.map +1 -0
- package/lib/typescript/src/components/shimmer/index.d.ts +38 -20
- package/lib/typescript/src/components/shimmer/index.d.ts.map +1 -1
- package/lib/typescript/src/components/surface/index.d.ts +51 -2
- package/lib/typescript/src/components/surface/index.d.ts.map +1 -1
- package/lib/typescript/src/components/switch/index.d.ts +10 -0
- package/lib/typescript/src/components/switch/index.d.ts.map +1 -1
- package/lib/typescript/src/components/toast/index.d.ts +4 -4
- package/lib/typescript/src/components/toast/index.d.ts.map +1 -1
- package/lib/typescript/src/hooks/index.d.ts +1 -0
- package/lib/typescript/src/hooks/index.d.ts.map +1 -1
- package/lib/typescript/src/hooks/use-breakpoint.d.ts +2 -2
- package/lib/typescript/src/hooks/use-copy-to-clipboard.d.ts +0 -2
- package/lib/typescript/src/hooks/use-copy-to-clipboard.d.ts.map +1 -1
- package/lib/typescript/src/hooks/use-keyboard-avoidance.d.ts +20 -0
- package/lib/typescript/src/hooks/use-keyboard-avoidance.d.ts.map +1 -0
- package/lib/typescript/src/icons/index.d.ts +10 -4
- package/lib/typescript/src/icons/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +11 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/native/index.d.ts +35 -0
- package/lib/typescript/src/native/index.d.ts.map +1 -0
- package/lib/typescript/src/primitives/keyboard-avoider.d.ts +22 -0
- package/lib/typescript/src/primitives/keyboard-avoider.d.ts.map +1 -0
- package/lib/typescript/src/primitives/scrim.d.ts +21 -0
- package/lib/typescript/src/primitives/scrim.d.ts.map +1 -0
- package/lib/typescript/src/providers/panel-ui-provider.d.ts +1 -1
- package/lib/typescript/src/providers/panel-ui-provider.d.ts.map +1 -1
- package/lib/typescript/src/theme/use-theme.d.ts +1 -1
- package/package.json +16 -3
- package/src/components/accordion/index.tsx +7 -5
- package/src/components/alert/index.tsx +3 -6
- package/src/components/attachment/index.tsx +279 -0
- package/src/components/bottom-sheet/index.tsx +102 -2
- package/src/components/button/index.tsx +52 -0
- package/src/components/dialog/index.tsx +10 -1
- package/src/components/empty-state/index.tsx +64 -19
- package/src/components/inline-select/index.tsx +191 -44
- package/src/components/input/index.tsx +39 -3
- package/src/components/input-group/index.tsx +0 -1
- package/src/components/item/index.tsx +387 -0
- package/src/components/label/index.tsx +5 -3
- package/src/components/line-chart/index.tsx +1224 -0
- package/src/components/marker/index.tsx +194 -0
- package/src/components/message/index.tsx +336 -0
- package/src/components/message-scroller/index.tsx +574 -0
- package/src/components/popover/index.tsx +599 -0
- package/src/components/radio-group/index.tsx +108 -17
- package/src/components/scroll-fade/index.tsx +163 -51
- package/src/components/select/index.tsx +54 -9
- package/src/components/separator/index.tsx +97 -0
- package/src/components/shimmer/index.tsx +151 -58
- package/src/components/surface/index.tsx +32 -5
- package/src/components/switch/index.tsx +27 -1
- package/src/components/toast/index.tsx +7 -8
- package/src/components/typography/index.tsx +3 -3
- package/src/hooks/index.ts +5 -0
- package/src/hooks/use-breakpoint.ts +2 -2
- package/src/hooks/use-copy-to-clipboard.ts +0 -2
- package/src/hooks/use-keyboard-avoidance.ts +140 -0
- package/src/icons/index.tsx +52 -4
- package/src/index.ts +92 -0
- package/src/native/index.ts +79 -0
- package/src/primitives/keyboard-avoider.tsx +49 -0
- package/src/primitives/scrim.tsx +95 -0
- package/src/providers/panel-ui-provider.tsx +30 -9
- package/src/theme/use-theme.ts +1 -1
- package/theme.css +63 -3
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Separator — a rule between content.
|
|
3
|
+
*
|
|
4
|
+
* React Native has no `<hr>` and no border shorthand that survives a flex row,
|
|
5
|
+
* so a separator here is a view with a size on one axis and a stretch on the
|
|
6
|
+
* other. That is why orientation is a prop rather than something the layout
|
|
7
|
+
* infers: the component has to know which axis carries the thickness.
|
|
8
|
+
*
|
|
9
|
+
* ```tsx
|
|
10
|
+
* <Separator />
|
|
11
|
+
* <Separator orientation="vertical" />
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* A vertical separator takes its length from the parent, so the parent needs a
|
|
15
|
+
* height — inside a `flex-row` with `items-stretch`, or with an explicit `h-*`
|
|
16
|
+
* on either the row or the separator itself. Without one it measures zero and
|
|
17
|
+
* nothing draws.
|
|
18
|
+
*/
|
|
19
|
+
import { forwardRef } from 'react';
|
|
20
|
+
import { View, type ViewProps } from 'react-native';
|
|
21
|
+
import { tv, type VariantProps } from 'tailwind-variants';
|
|
22
|
+
|
|
23
|
+
const separatorVariants = tv({
|
|
24
|
+
base: 'bg-border',
|
|
25
|
+
variants: {
|
|
26
|
+
orientation: {
|
|
27
|
+
horizontal: 'w-full',
|
|
28
|
+
vertical: 'h-full',
|
|
29
|
+
},
|
|
30
|
+
variant: {
|
|
31
|
+
thin: '',
|
|
32
|
+
thick: '',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
compoundVariants: [
|
|
36
|
+
// Thickness is on whichever axis is *not* the length, so it cannot be
|
|
37
|
+
// expressed by the orientation and variant classes independently.
|
|
38
|
+
{ orientation: 'horizontal', variant: 'thin', class: 'h-px' },
|
|
39
|
+
{ orientation: 'horizontal', variant: 'thick', class: 'h-1' },
|
|
40
|
+
{ orientation: 'vertical', variant: 'thin', class: 'w-px' },
|
|
41
|
+
{ orientation: 'vertical', variant: 'thick', class: 'w-1' },
|
|
42
|
+
],
|
|
43
|
+
defaultVariants: {
|
|
44
|
+
orientation: 'horizontal',
|
|
45
|
+
variant: 'thin',
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export interface SeparatorProps
|
|
50
|
+
extends ViewProps,
|
|
51
|
+
VariantProps<typeof separatorVariants> {
|
|
52
|
+
className?: string;
|
|
53
|
+
/**
|
|
54
|
+
* Thickness in pixels, overriding the variant. Sets the height of a
|
|
55
|
+
* horizontal separator and the width of a vertical one.
|
|
56
|
+
*/
|
|
57
|
+
thickness?: number;
|
|
58
|
+
/**
|
|
59
|
+
* Whether the separator is only visual. A decorative separator is skipped by
|
|
60
|
+
* screen readers; set false when the split itself carries meaning — between
|
|
61
|
+
* two groups of menu items, say — and it is announced instead.
|
|
62
|
+
*/
|
|
63
|
+
decorative?: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const Separator = forwardRef<View, SeparatorProps>(
|
|
67
|
+
(
|
|
68
|
+
{
|
|
69
|
+
className,
|
|
70
|
+
orientation = 'horizontal',
|
|
71
|
+
variant,
|
|
72
|
+
thickness,
|
|
73
|
+
decorative = true,
|
|
74
|
+
style,
|
|
75
|
+
...props
|
|
76
|
+
},
|
|
77
|
+
ref
|
|
78
|
+
) => (
|
|
79
|
+
<View
|
|
80
|
+
ref={ref}
|
|
81
|
+
// `role` rather than `accessibilityRole` — React Native's older role list
|
|
82
|
+
// has no separator, the ARIA-aligned one does.
|
|
83
|
+
role={decorative ? 'none' : 'separator'}
|
|
84
|
+
accessible={!decorative}
|
|
85
|
+
aria-orientation={decorative ? undefined : orientation}
|
|
86
|
+
className={separatorVariants({ orientation, variant, className })}
|
|
87
|
+
style={
|
|
88
|
+
thickness !== undefined
|
|
89
|
+
? [orientation === 'horizontal' ? { height: thickness } : { width: thickness }, style]
|
|
90
|
+
: style
|
|
91
|
+
}
|
|
92
|
+
{...props}
|
|
93
|
+
/>
|
|
94
|
+
)
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
Separator.displayName = 'Separator';
|
|
@@ -1,24 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shimmer —
|
|
2
|
+
* Shimmer — a highlight that sweeps *through* content rather than over it.
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* overlay by Reanimated, on the UI thread.
|
|
9
|
-
*
|
|
10
|
-
* The visible difference: the band passes *over* the content rather than
|
|
11
|
-
* being clipped to glyph shapes. On body-sized text the two read the same; on
|
|
12
|
-
* very large text the web version is crisper.
|
|
4
|
+
* The content is used as a mask, and a gradient band is swept behind it on the
|
|
5
|
+
* UI thread. Because the content is the mask, the highlight is clipped to the
|
|
6
|
+
* glyph shapes: text reads as though the letters themselves are catching a
|
|
7
|
+
* light, which is the "thinking…" treatment familiar from AI chat interfaces.
|
|
13
8
|
*
|
|
14
9
|
* ```tsx
|
|
15
|
-
* <Shimmer>
|
|
16
|
-
* <Text muted>Generating response…</Text>
|
|
17
|
-
* </Shimmer>
|
|
10
|
+
* <Shimmer>Thinking…</Shimmer>
|
|
18
11
|
* ```
|
|
12
|
+
*
|
|
13
|
+
* Set `as="view"` to shimmer arbitrary children (skeleton blocks, cards) — the
|
|
14
|
+
* mask is then the rendered subtree's alpha instead of a line of text.
|
|
19
15
|
*/
|
|
20
16
|
import { useEffect, useState, type ReactNode } from 'react';
|
|
21
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
StyleSheet,
|
|
19
|
+
View,
|
|
20
|
+
type LayoutChangeEvent,
|
|
21
|
+
type StyleProp,
|
|
22
|
+
type TextStyle,
|
|
23
|
+
type ViewProps,
|
|
24
|
+
} from 'react-native';
|
|
25
|
+
import MaskedView from '@react-native-masked-view/masked-view';
|
|
26
|
+
import { LinearGradient } from 'expo-linear-gradient';
|
|
22
27
|
import Animated, {
|
|
23
28
|
cancelAnimation,
|
|
24
29
|
Easing,
|
|
@@ -28,35 +33,64 @@ import Animated, {
|
|
|
28
33
|
withRepeat,
|
|
29
34
|
withTiming,
|
|
30
35
|
} from 'react-native-reanimated';
|
|
31
|
-
import Svg, { Defs, LinearGradient, Rect, Stop } from 'react-native-svg';
|
|
32
36
|
import { useCSSVariable } from 'uniwind';
|
|
37
|
+
import { Text } from '../../primitives/text';
|
|
33
38
|
import { cn } from '../../utils/cn';
|
|
34
39
|
|
|
35
40
|
export interface ShimmerProps extends ViewProps {
|
|
36
41
|
className?: string;
|
|
42
|
+
/**
|
|
43
|
+
* `text` renders `children` as a single styled string and masks the sweep to
|
|
44
|
+
* the glyphs. `view` masks the sweep to whatever subtree you pass.
|
|
45
|
+
*/
|
|
46
|
+
as?: 'text' | 'view';
|
|
37
47
|
/** Milliseconds for one sweep. */
|
|
38
48
|
duration?: number;
|
|
39
|
-
/**
|
|
49
|
+
/** Width of the highlight band, as a multiple of the content width. */
|
|
50
|
+
spread?: number;
|
|
51
|
+
/** Colour of the content at rest. Defaults to the theme's muted foreground. */
|
|
52
|
+
baseColor?: string;
|
|
53
|
+
/** Colour at the centre of the sweep. Defaults to the theme's foreground. */
|
|
54
|
+
shimmerColor?: string;
|
|
55
|
+
/** `loop` restarts from the left; `ping-pong` reverses on each pass. */
|
|
56
|
+
mode?: 'loop' | 'ping-pong';
|
|
57
|
+
/** Sweep once instead of repeating. */
|
|
40
58
|
once?: boolean;
|
|
41
59
|
/** Sweep right-to-left. */
|
|
42
60
|
reverse?: boolean;
|
|
43
|
-
/**
|
|
44
|
-
|
|
45
|
-
/**
|
|
61
|
+
/** Set false to render the content statically without animating. */
|
|
62
|
+
enabled?: boolean;
|
|
63
|
+
/** Extra classes for the text when `as="text"`. */
|
|
64
|
+
textClassName?: string;
|
|
65
|
+
/** Extra styles for the text when `as="text"`. */
|
|
66
|
+
textStyle?: StyleProp<TextStyle>;
|
|
67
|
+
/**
|
|
68
|
+
* @deprecated Use `shimmerColor`.
|
|
69
|
+
*/
|
|
46
70
|
color?: string;
|
|
47
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* Peak opacity of the highlight.
|
|
73
|
+
* @deprecated Set `shimmerColor` to a colour with the alpha you want.
|
|
74
|
+
*/
|
|
48
75
|
intensity?: number;
|
|
49
76
|
children?: ReactNode;
|
|
50
77
|
}
|
|
51
78
|
|
|
52
79
|
export function Shimmer({
|
|
53
80
|
className,
|
|
81
|
+
as = 'text',
|
|
54
82
|
duration = 2000,
|
|
83
|
+
spread = 2,
|
|
84
|
+
baseColor,
|
|
85
|
+
shimmerColor,
|
|
86
|
+
mode = 'loop',
|
|
55
87
|
once = false,
|
|
56
88
|
reverse = false,
|
|
57
|
-
|
|
89
|
+
enabled = true,
|
|
90
|
+
textClassName,
|
|
91
|
+
textStyle,
|
|
58
92
|
color,
|
|
59
|
-
intensity
|
|
93
|
+
intensity,
|
|
60
94
|
children,
|
|
61
95
|
...props
|
|
62
96
|
}: ShimmerProps) {
|
|
@@ -64,28 +98,39 @@ export function Shimmer({
|
|
|
64
98
|
const progress = useSharedValue(0);
|
|
65
99
|
const reducedMotion = useReducedMotion();
|
|
66
100
|
|
|
101
|
+
const themeMuted = useCSSVariable('--color-muted-foreground');
|
|
67
102
|
const themeForeground = useCSSVariable('--color-foreground');
|
|
103
|
+
|
|
104
|
+
const base =
|
|
105
|
+
baseColor ?? (typeof themeMuted === 'string' ? themeMuted : '#a1a1aa');
|
|
68
106
|
const highlight =
|
|
69
|
-
|
|
107
|
+
shimmerColor ??
|
|
108
|
+
color ??
|
|
109
|
+
(typeof themeForeground === 'string' ? themeForeground : '#ffffff');
|
|
110
|
+
|
|
111
|
+
// The band is wider than the content so the highlight ramps in and out
|
|
112
|
+
// instead of appearing at full strength against the edge.
|
|
113
|
+
const bandWidth = Math.max(width * spread, 1);
|
|
114
|
+
const animating = enabled && !reducedMotion && width > 0;
|
|
70
115
|
|
|
71
116
|
useEffect(() => {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
117
|
+
if (!animating) {
|
|
118
|
+
cancelAnimation(progress);
|
|
119
|
+
progress.value = 0;
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
75
122
|
|
|
76
123
|
progress.value = 0;
|
|
77
124
|
progress.value = withRepeat(
|
|
78
125
|
withTiming(1, { duration, easing: Easing.linear }),
|
|
79
126
|
once ? 1 : -1,
|
|
80
|
-
|
|
127
|
+
mode === 'ping-pong'
|
|
81
128
|
);
|
|
82
129
|
|
|
83
130
|
return () => cancelAnimation(progress);
|
|
84
|
-
}, [
|
|
131
|
+
}, [animating, duration, mode, once, progress]);
|
|
85
132
|
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
const bandStyle = useAnimatedStyle(() => {
|
|
133
|
+
const sweepStyle = useAnimatedStyle(() => {
|
|
89
134
|
// Travel from fully off one edge to fully off the other.
|
|
90
135
|
const from = -bandWidth;
|
|
91
136
|
const to = width;
|
|
@@ -100,36 +145,84 @@ export function Shimmer({
|
|
|
100
145
|
props.onLayout?.(event);
|
|
101
146
|
};
|
|
102
147
|
|
|
148
|
+
const content =
|
|
149
|
+
as === 'text' ? (
|
|
150
|
+
<Text className={textClassName} style={[{ color: base }, textStyle]}>
|
|
151
|
+
{children}
|
|
152
|
+
</Text>
|
|
153
|
+
) : (
|
|
154
|
+
children
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
// Without a measured width there is nothing to sweep across, and masking an
|
|
158
|
+
// unmeasured subtree just costs a layer — render the content plainly.
|
|
159
|
+
if (!animating) {
|
|
160
|
+
return (
|
|
161
|
+
<View {...props} onLayout={onLayout} className={className}>
|
|
162
|
+
{content}
|
|
163
|
+
</View>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const highlightWithAlpha =
|
|
168
|
+
intensity !== undefined ? withAlpha(highlight, intensity) : highlight;
|
|
169
|
+
|
|
103
170
|
return (
|
|
104
|
-
<View
|
|
105
|
-
{
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
</Svg>
|
|
129
|
-
</Animated.View>
|
|
130
|
-
)}
|
|
171
|
+
<View {...props} onLayout={onLayout} className={cn('overflow-hidden', className)}>
|
|
172
|
+
<MaskedView maskElement={<View>{content}</View>}>
|
|
173
|
+
{/* The mask only exposes the glyphs, so this layer is what they are
|
|
174
|
+
painted with: flat base colour, plus a moving gradient band. */}
|
|
175
|
+
<View style={{ backgroundColor: base }}>
|
|
176
|
+
<View style={{ opacity: 0 }}>{content}</View>
|
|
177
|
+
<Animated.View
|
|
178
|
+
pointerEvents="none"
|
|
179
|
+
style={[
|
|
180
|
+
StyleSheet.absoluteFill,
|
|
181
|
+
{ width: bandWidth, left: 0, right: undefined },
|
|
182
|
+
sweepStyle,
|
|
183
|
+
]}
|
|
184
|
+
>
|
|
185
|
+
<LinearGradient
|
|
186
|
+
colors={[base, highlightWithAlpha, base]}
|
|
187
|
+
locations={[0, 0.5, 1]}
|
|
188
|
+
start={{ x: 0, y: 0 }}
|
|
189
|
+
end={{ x: 1, y: 0 }}
|
|
190
|
+
style={StyleSheet.absoluteFill}
|
|
191
|
+
/>
|
|
192
|
+
</Animated.View>
|
|
193
|
+
</View>
|
|
194
|
+
</MaskedView>
|
|
131
195
|
</View>
|
|
132
196
|
);
|
|
133
197
|
}
|
|
134
198
|
|
|
135
199
|
Shimmer.displayName = 'Shimmer';
|
|
200
|
+
|
|
201
|
+
/** Applies an alpha to a `#rgb`/`#rrggbb`/`rgb()` colour. Supports `intensity`. */
|
|
202
|
+
function withAlpha(color: string, alpha: number): string {
|
|
203
|
+
const clamped = Math.max(0, Math.min(1, alpha));
|
|
204
|
+
|
|
205
|
+
if (color.startsWith('#')) {
|
|
206
|
+
const hex = color.slice(1);
|
|
207
|
+
const full =
|
|
208
|
+
hex.length === 3
|
|
209
|
+
? hex
|
|
210
|
+
.split('')
|
|
211
|
+
.map((c) => c + c)
|
|
212
|
+
.join('')
|
|
213
|
+
: hex.slice(0, 6);
|
|
214
|
+
const r = parseInt(full.slice(0, 2), 16);
|
|
215
|
+
const g = parseInt(full.slice(2, 4), 16);
|
|
216
|
+
const b = parseInt(full.slice(4, 6), 16);
|
|
217
|
+
if (Number.isNaN(r + g + b)) return color;
|
|
218
|
+
return `rgba(${r}, ${g}, ${b}, ${clamped})`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const channels = color.match(/rgba?\(([^)]+)\)/)?.[1];
|
|
222
|
+
if (channels) {
|
|
223
|
+
const [r, g, b] = channels.split(',').map((part) => part.trim());
|
|
224
|
+
return `rgba(${r}, ${g}, ${b}, ${clamped})`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return color;
|
|
228
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Surface — an elevated container.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* The variants form a ladder rather than a palette: each step is one level
|
|
5
|
+
* further from the background, and nesting them is how you build depth without
|
|
6
|
+
* hardcoding a colour anywhere.
|
|
6
7
|
*
|
|
7
8
|
* Both the fill and the corner radius follow the active theme: the variants
|
|
8
9
|
* resolve to themed `--color-surface-*` tokens, and `rounded-3xl` resolves
|
|
@@ -22,7 +23,7 @@ import { StyleSheet, View, type ViewProps } from 'react-native';
|
|
|
22
23
|
import { tv, type VariantProps } from 'tailwind-variants';
|
|
23
24
|
|
|
24
25
|
const surfaceVariants = tv({
|
|
25
|
-
base: 'overflow-hidden rounded-3xl
|
|
26
|
+
base: 'overflow-hidden rounded-3xl',
|
|
26
27
|
variants: {
|
|
27
28
|
variant: {
|
|
28
29
|
default: 'bg-surface',
|
|
@@ -30,9 +31,22 @@ const surfaceVariants = tv({
|
|
|
30
31
|
tertiary: 'bg-surface-tertiary',
|
|
31
32
|
transparent: 'bg-transparent',
|
|
32
33
|
},
|
|
34
|
+
padding: {
|
|
35
|
+
none: '',
|
|
36
|
+
sm: 'p-2.5',
|
|
37
|
+
default: 'p-4',
|
|
38
|
+
lg: 'p-6',
|
|
39
|
+
},
|
|
40
|
+
bordered: {
|
|
41
|
+
true: 'border border-border',
|
|
42
|
+
},
|
|
43
|
+
elevated: {
|
|
44
|
+
true: 'shadow-sm',
|
|
45
|
+
},
|
|
33
46
|
},
|
|
34
47
|
defaultVariants: {
|
|
35
48
|
variant: 'default',
|
|
49
|
+
padding: 'default',
|
|
36
50
|
},
|
|
37
51
|
});
|
|
38
52
|
|
|
@@ -40,17 +54,30 @@ export interface SurfaceProps
|
|
|
40
54
|
extends ViewProps,
|
|
41
55
|
VariantProps<typeof surfaceVariants> {
|
|
42
56
|
className?: string;
|
|
57
|
+
/**
|
|
58
|
+
* A hairline border. A surface the same colour as what it sits on needs one
|
|
59
|
+
* to read as a distinct plane rather than dissolving into the background.
|
|
60
|
+
*/
|
|
61
|
+
bordered?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* A soft shadow lifting the surface off the page. Off by default because a
|
|
64
|
+
* nested surface reads its depth from its fill, not from a shadow it would
|
|
65
|
+
* only cast onto its parent.
|
|
66
|
+
*/
|
|
67
|
+
elevated?: boolean;
|
|
68
|
+
/** Inner spacing. `none` is for a surface wrapping a bled image or a chart. */
|
|
69
|
+
padding?: 'none' | 'sm' | 'default' | 'lg';
|
|
43
70
|
}
|
|
44
71
|
|
|
45
72
|
export const Surface = forwardRef<View, SurfaceProps>(
|
|
46
|
-
({ className, variant, style, ...props }, ref) => (
|
|
73
|
+
({ className, variant, padding, bordered, elevated, style, ...props }, ref) => (
|
|
47
74
|
<View
|
|
48
75
|
ref={ref}
|
|
49
76
|
// `borderCurve` has no Tailwind equivalent. On iOS it gives the
|
|
50
77
|
// continuous (squircle) corner Apple uses, which is visibly smoother
|
|
51
78
|
// than a circular arc at this radius; Android ignores it.
|
|
52
79
|
style={[styles.root, style]}
|
|
53
|
-
className={surfaceVariants({ variant, className })}
|
|
80
|
+
className={surfaceVariants({ variant, padding, bordered, elevated, className })}
|
|
54
81
|
{...props}
|
|
55
82
|
/>
|
|
56
83
|
)
|
|
@@ -7,6 +7,7 @@ import Animated, {
|
|
|
7
7
|
withSpring,
|
|
8
8
|
} from 'react-native-reanimated';
|
|
9
9
|
import { tv, type VariantProps } from 'tailwind-variants';
|
|
10
|
+
import { getNativeUI } from '../../native';
|
|
10
11
|
|
|
11
12
|
const SPRING = { damping: 18, stiffness: 250, mass: 0.5 } as const;
|
|
12
13
|
|
|
@@ -37,6 +38,16 @@ export interface SwitchProps extends VariantProps<typeof switchVariants> {
|
|
|
37
38
|
value: boolean;
|
|
38
39
|
onValueChange?: (value: boolean) => void;
|
|
39
40
|
disabled?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Render the platform's own switch instead of this one. Requires the
|
|
43
|
+
* optional `@expo/ui` package; without it this prop does nothing.
|
|
44
|
+
*
|
|
45
|
+
* **Theme tokens do not apply** — the platform draws the control, so
|
|
46
|
+
* `className` and `size` are ignored.
|
|
47
|
+
*/
|
|
48
|
+
native?: boolean;
|
|
49
|
+
/** Text label drawn beside the control. Native mode only. */
|
|
50
|
+
label?: string;
|
|
40
51
|
}
|
|
41
52
|
|
|
42
53
|
/**
|
|
@@ -44,8 +55,9 @@ export interface SwitchProps extends VariantProps<typeof switchVariants> {
|
|
|
44
55
|
* UI thread; toggling never re-renders beyond the value change itself.
|
|
45
56
|
*/
|
|
46
57
|
export const Switch = forwardRef<View, SwitchProps>(
|
|
47
|
-
({ className, value, onValueChange, disabled, size = 'md' }, ref) => {
|
|
58
|
+
({ className, value, onValueChange, disabled, size = 'md', native, label }, ref) => {
|
|
48
59
|
const progress = useSharedValue(value ? 1 : 0);
|
|
60
|
+
const nativeUI = native ? getNativeUI() : null;
|
|
49
61
|
const slots = switchVariants({ size, disabled: !!disabled });
|
|
50
62
|
|
|
51
63
|
useEffect(() => {
|
|
@@ -62,6 +74,20 @@ export const Switch = forwardRef<View, SwitchProps>(
|
|
|
62
74
|
opacity: progress.value,
|
|
63
75
|
}));
|
|
64
76
|
|
|
77
|
+
if (nativeUI) {
|
|
78
|
+
const { Host, Switch: NativeSwitch } = nativeUI;
|
|
79
|
+
return (
|
|
80
|
+
<Host matchContents>
|
|
81
|
+
<NativeSwitch
|
|
82
|
+
value={value}
|
|
83
|
+
onValueChange={(next: boolean) => onValueChange?.(next)}
|
|
84
|
+
label={label}
|
|
85
|
+
disabled={disabled}
|
|
86
|
+
/>
|
|
87
|
+
</Host>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
65
91
|
return (
|
|
66
92
|
<Pressable
|
|
67
93
|
ref={ref}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Toast — transient notification.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Three ways to raise one, because the right amount of ceremony differs: a
|
|
5
|
+
* bare string, a config object with a title, description and action, or a
|
|
6
|
+
* custom component when none of that fits. The anatomy underneath is the same
|
|
7
|
+
* in all three.
|
|
8
8
|
*
|
|
9
9
|
* The viewport is mounted for you by PanelUIProvider; `useToast()` works
|
|
10
10
|
* anywhere below it.
|
|
@@ -71,8 +71,7 @@ const STACK_SCALE_STEP = 0.97;
|
|
|
71
71
|
const EDGE_INSET = 16;
|
|
72
72
|
|
|
73
73
|
/*
|
|
74
|
-
* Entering
|
|
75
|
-
* src/components/toast/toast.animation.ts.
|
|
74
|
+
* Entering and exiting animations.
|
|
76
75
|
*
|
|
77
76
|
* `mass(3)` is doing the important work: a heavy spring settles without the
|
|
78
77
|
* visible overshoot a default-mass one gives. `withInitialValues` keeps
|
|
@@ -285,7 +284,7 @@ export const Toast = Object.assign(ToastRoot, {
|
|
|
285
284
|
*
|
|
286
285
|
* Swiping is vertical and direction-aware: dragging toward the screen edge the
|
|
287
286
|
* toast came from dismisses it, dragging the other way rubber-bands against a
|
|
288
|
-
* hard limit.
|
|
287
|
+
* hard limit.
|
|
289
288
|
*/
|
|
290
289
|
function ToastSlot({
|
|
291
290
|
item,
|
|
@@ -376,7 +375,7 @@ function ToastSlot({
|
|
|
376
375
|
* Stack position plus the gesture transform.
|
|
377
376
|
*
|
|
378
377
|
* The whole deck takes the *newest* toast's height so the cards behind read
|
|
379
|
-
* as a uniform stack rather than a ragged pile
|
|
378
|
+
* as a uniform stack rather than a ragged pile.
|
|
380
379
|
*/
|
|
381
380
|
const stackStyle = useAnimatedStyle(() => {
|
|
382
381
|
// Fall back to this toast's own height until the front one is measured.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Typography — semantic text presets.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* primitive so every preset keeps `className` passthrough and theme colours.
|
|
4
|
+
* The type scale in one place, so headings stay consistent instead of being
|
|
5
|
+
* rebuilt out of size and weight classes at each call site. Built on the Text
|
|
6
|
+
* primitive, so every preset keeps `className` passthrough and theme colours.
|
|
7
7
|
*/
|
|
8
8
|
import { forwardRef } from 'react';
|
|
9
9
|
import { View, type Text as RNText, type ViewProps } from 'react-native';
|
package/src/hooks/index.ts
CHANGED
|
@@ -15,5 +15,10 @@ export {
|
|
|
15
15
|
type UseBreakpointResult,
|
|
16
16
|
} from './use-breakpoint';
|
|
17
17
|
export { useKeyboard, type UseKeyboardResult } from './use-keyboard';
|
|
18
|
+
export {
|
|
19
|
+
useKeyboardAvoidance,
|
|
20
|
+
type UseKeyboardAvoidanceOptions,
|
|
21
|
+
type UseKeyboardAvoidanceResult,
|
|
22
|
+
} from './use-keyboard-avoidance';
|
|
18
23
|
export { useDebouncedValue } from './use-debounced-value';
|
|
19
24
|
export { usePrevious } from './use-previous';
|
|
@@ -16,8 +16,8 @@ export interface UseBreakpointResult {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* Responsive state from the window size — the React Native answer to
|
|
20
|
-
*
|
|
19
|
+
* Responsive state from the window size — the React Native answer to a media
|
|
20
|
+
* query, which has no equivalent here.
|
|
21
21
|
*
|
|
22
22
|
* For styling, prefer Uniwind's responsive class prefixes (`md:flex-row`).
|
|
23
23
|
* Reach for this when the *behaviour* changes, not just the look — rendering
|
|
@@ -15,8 +15,6 @@ export interface UseCopyToClipboardResult {
|
|
|
15
15
|
/**
|
|
16
16
|
* Copy text to the clipboard, with a temporary "copied" state.
|
|
17
17
|
*
|
|
18
|
-
* Mirrors coss.com/ui's `useCopyToClipboard`.
|
|
19
|
-
*
|
|
20
18
|
* Requires `expo-clipboard`, which is an optional peer dependency — React
|
|
21
19
|
* Native removed `Clipboard` from core, and pulling in a required native
|
|
22
20
|
* module for one hook would cost every consumer who never calls it:
|