panelui-native 0.5.1 → 0.6.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 +10 -5
- package/lib/module/components/scroll-fade/index.js +156 -0
- package/lib/module/components/scroll-fade/index.js.map +1 -0
- package/lib/module/components/shimmer/index.js +120 -0
- package/lib/module/components/shimmer/index.js.map +1 -0
- package/lib/module/components/surface/index.js +64 -0
- package/lib/module/components/surface/index.js.map +1 -0
- package/lib/module/components/timeline/index.js +55 -30
- package/lib/module/components/timeline/index.js.map +1 -1
- package/lib/module/hooks/index.js +9 -0
- package/lib/module/hooks/index.js.map +1 -0
- package/lib/module/hooks/use-breakpoint.js +39 -0
- package/lib/module/hooks/use-breakpoint.js.map +1 -0
- package/lib/module/hooks/use-copy-to-clipboard.js +62 -0
- package/lib/module/hooks/use-copy-to-clipboard.js.map +1 -0
- package/lib/module/hooks/use-debounced-value.js +24 -0
- package/lib/module/hooks/use-debounced-value.js.map +1 -0
- package/lib/module/hooks/use-disclosure.js +43 -0
- package/lib/module/hooks/use-disclosure.js.map +1 -0
- package/lib/module/hooks/use-keyboard.js +36 -0
- package/lib/module/hooks/use-keyboard.js.map +1 -0
- package/lib/module/hooks/use-previous.js +25 -0
- package/lib/module/hooks/use-previous.js.map +1 -0
- package/lib/module/index.js +6 -0
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/components/scroll-fade/index.d.ts +38 -0
- package/lib/typescript/src/components/scroll-fade/index.d.ts.map +1 -0
- package/lib/typescript/src/components/shimmer/index.d.ts +42 -0
- package/lib/typescript/src/components/shimmer/index.d.ts.map +1 -0
- package/lib/typescript/src/components/surface/index.d.ts +30 -0
- package/lib/typescript/src/components/surface/index.d.ts.map +1 -0
- package/lib/typescript/src/components/timeline/index.d.ts.map +1 -1
- package/lib/typescript/src/hooks/index.d.ts +7 -0
- package/lib/typescript/src/hooks/index.d.ts.map +1 -0
- package/lib/typescript/src/hooks/use-breakpoint.d.ts +32 -0
- package/lib/typescript/src/hooks/use-breakpoint.d.ts.map +1 -0
- package/lib/typescript/src/hooks/use-copy-to-clipboard.d.ts +30 -0
- package/lib/typescript/src/hooks/use-copy-to-clipboard.d.ts.map +1 -0
- package/lib/typescript/src/hooks/use-debounced-value.d.ts +13 -0
- package/lib/typescript/src/hooks/use-debounced-value.d.ts.map +1 -0
- package/lib/typescript/src/hooks/use-disclosure.d.ts +32 -0
- package/lib/typescript/src/hooks/use-disclosure.d.ts.map +1 -0
- package/lib/typescript/src/hooks/use-keyboard.d.ts +20 -0
- package/lib/typescript/src/hooks/use-keyboard.d.ts.map +1 -0
- package/lib/typescript/src/hooks/use-previous.d.ts +15 -0
- package/lib/typescript/src/hooks/use-previous.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +4 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +8 -1
- package/src/components/scroll-fade/index.tsx +168 -0
- package/src/components/shimmer/index.tsx +135 -0
- package/src/components/surface/index.tsx +63 -0
- package/src/components/timeline/index.tsx +64 -38
- package/src/hooks/index.ts +19 -0
- package/src/hooks/use-breakpoint.ts +52 -0
- package/src/hooks/use-copy-to-clipboard.ts +81 -0
- package/src/hooks/use-debounced-value.ts +23 -0
- package/src/hooks/use-disclosure.ts +57 -0
- package/src/hooks/use-keyboard.ts +43 -0
- package/src/hooks/use-previous.ts +24 -0
- package/src/index.ts +9 -0
- package/theme.css +18 -0
|
@@ -22,13 +22,17 @@ export { InputGroup, type InputGroupProps, type InputGroupInputProps, type Input
|
|
|
22
22
|
export { Label, type LabelProps, type LabelTextProps } from './components/label/index.js';
|
|
23
23
|
export { Progress, type ProgressProps } from './components/progress/index.js';
|
|
24
24
|
export { RadioGroup, type RadioGroupProps, type RadioGroupItemProps, } from './components/radio-group/index.js';
|
|
25
|
+
export { ScrollFade, type ScrollFadeProps, } from './components/scroll-fade/index.js';
|
|
26
|
+
export { Shimmer, type ShimmerProps } from './components/shimmer/index.js';
|
|
25
27
|
export { Skeleton, type SkeletonProps } from './components/skeleton/index.js';
|
|
26
28
|
export { Spinner, type SpinnerProps } from './components/spinner/index.js';
|
|
27
29
|
export { Steps, type StepsProps, type StepsItemProps, type StepsTriggerProps, type StepsIndicatorProps, type StepsSeparatorProps, type StepState, type StepsOrientation, } from './components/steps/index.js';
|
|
30
|
+
export { Surface, type SurfaceProps } from './components/surface/index.js';
|
|
28
31
|
export { Switch, type SwitchProps } from './components/switch/index.js';
|
|
29
32
|
export { Timeline, type TimelineProps, type TimelineItemProps, type TimelineIndicatorProps, type TimelineStatProps, type TimelineVariant, type TimelineTone, } from './components/timeline/index.js';
|
|
30
33
|
export { Toast, ToastViewport, toast, useToast, type ToastProps, type ToastOptions, type ToastItem, type ToastVariant, type ToastPlacement, type ToastHandle, type ToastIndicatorProps, type ToastCloseProps, } from './components/toast/index.js';
|
|
31
34
|
export { Typography, type TypographyProps, type TypographyType, type TypographyHeadingProps, type TypographyParagraphProps, type TypographyCodeProps, } from './components/typography/index.js';
|
|
32
35
|
export { AlertTriangleIcon, AppleIcon, ArrowUpRightIcon, BellIcon, CardIcon, CheckCircleIcon, CheckIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, FacebookIcon, GoogleIcon, IconColorProvider, InfoIcon, MoonIcon, PackageIcon, PlusSquareIcon, ReceiptIcon, SearchIcon, SendIcon, ShareNodesIcon, ShieldAlertIcon, ShieldCheckIcon, SunIcon, XIcon, useIconColor, type IconProps, } from './icons/index.js';
|
|
36
|
+
export * from './hooks/index.js';
|
|
33
37
|
export { cn } from './utils/cn.js';
|
|
34
38
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.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.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "High-performance React Native UI components for Expo. Coss UI design language, powered by Uniwind (Tailwind v4) and Reanimated.",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"module": "./lib/module/index.js",
|
|
@@ -64,11 +64,13 @@
|
|
|
64
64
|
"license": "MIT",
|
|
65
65
|
"homepage": "https://github.com/panel-ui/PanelUI#readme",
|
|
66
66
|
"peerDependencies": {
|
|
67
|
+
"expo-clipboard": ">=7.0.0",
|
|
67
68
|
"react": "*",
|
|
68
69
|
"react-native": "*",
|
|
69
70
|
"react-native-gesture-handler": ">=2.0.0",
|
|
70
71
|
"react-native-reanimated": ">=3.0.0",
|
|
71
72
|
"react-native-safe-area-context": ">=4.0.0",
|
|
73
|
+
"react-native-svg": ">=15.0.0",
|
|
72
74
|
"tailwindcss": ">=4.0.0",
|
|
73
75
|
"uniwind": ">=1.0.0"
|
|
74
76
|
},
|
|
@@ -92,5 +94,10 @@
|
|
|
92
94
|
],
|
|
93
95
|
"typescript"
|
|
94
96
|
]
|
|
97
|
+
},
|
|
98
|
+
"peerDependenciesMeta": {
|
|
99
|
+
"expo-clipboard": {
|
|
100
|
+
"optional": true
|
|
101
|
+
}
|
|
95
102
|
}
|
|
96
103
|
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ScrollFade — fades the edges of a scroll container.
|
|
3
|
+
*
|
|
4
|
+
* The React Native equivalent of shadcn-ui/ui's `scroll-fade` utility
|
|
5
|
+
* (https://ui.shadcn.com/docs/utils/scroll-fade), which is CSS `mask-image`
|
|
6
|
+
* driven by a scroll timeline. React Native has neither masks nor scroll
|
|
7
|
+
* timelines, so the fades are SVG gradients overlaid on each edge, with their
|
|
8
|
+
* opacity following the scroll position.
|
|
9
|
+
*
|
|
10
|
+
* Composition follows heroui-native's ScrollShadow: wrap the scrollable and
|
|
11
|
+
* let the orientation be inferred from its `horizontal` prop.
|
|
12
|
+
*
|
|
13
|
+
* ```tsx
|
|
14
|
+
* <ScrollFade>
|
|
15
|
+
* <ScrollView horizontal>…</ScrollView>
|
|
16
|
+
* </ScrollFade>
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
import {
|
|
20
|
+
Children,
|
|
21
|
+
cloneElement,
|
|
22
|
+
isValidElement,
|
|
23
|
+
useState,
|
|
24
|
+
type ReactElement,
|
|
25
|
+
type ReactNode,
|
|
26
|
+
} from 'react';
|
|
27
|
+
import {
|
|
28
|
+
View,
|
|
29
|
+
type NativeScrollEvent,
|
|
30
|
+
type NativeSyntheticEvent,
|
|
31
|
+
type ViewProps,
|
|
32
|
+
} from 'react-native';
|
|
33
|
+
import Svg, { Defs, LinearGradient, Rect, Stop } from 'react-native-svg';
|
|
34
|
+
import { useCSSVariable } from 'uniwind';
|
|
35
|
+
|
|
36
|
+
/** Past this many pixels from an edge, that edge's fade is fully shown. */
|
|
37
|
+
const FADE_IN_DISTANCE = 24;
|
|
38
|
+
|
|
39
|
+
export interface ScrollFadeProps extends ViewProps {
|
|
40
|
+
className?: string;
|
|
41
|
+
/** Depth of the fade in pixels. */
|
|
42
|
+
size?: number;
|
|
43
|
+
/** Which edges fade. */
|
|
44
|
+
edges?: 'both' | 'start' | 'end';
|
|
45
|
+
/**
|
|
46
|
+
* Colour the fade resolves to — normally whatever sits behind the
|
|
47
|
+
* scrollable. Defaults to the theme's background.
|
|
48
|
+
*/
|
|
49
|
+
color?: string;
|
|
50
|
+
children?: ReactNode;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface ScrollableProps {
|
|
54
|
+
horizontal?: boolean;
|
|
55
|
+
onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
|
|
56
|
+
scrollEventThrottle?: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function ScrollFade({
|
|
60
|
+
className,
|
|
61
|
+
size = 48,
|
|
62
|
+
edges = 'both',
|
|
63
|
+
color,
|
|
64
|
+
children,
|
|
65
|
+
...props
|
|
66
|
+
}: ScrollFadeProps) {
|
|
67
|
+
const [startOpacity, setStartOpacity] = useState(0);
|
|
68
|
+
const [endOpacity, setEndOpacity] = useState(0);
|
|
69
|
+
|
|
70
|
+
const themeBackground = useCSSVariable('--color-background');
|
|
71
|
+
const fadeColor =
|
|
72
|
+
color ?? (typeof themeBackground === 'string' ? themeBackground : '#000000');
|
|
73
|
+
|
|
74
|
+
const child = Children.only(children) as ReactElement<ScrollableProps>;
|
|
75
|
+
const horizontal = isValidElement(child) ? !!child.props.horizontal : false;
|
|
76
|
+
|
|
77
|
+
const handleScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
78
|
+
const { contentOffset, contentSize, layoutMeasurement } = event.nativeEvent;
|
|
79
|
+
const offset = horizontal ? contentOffset.x : contentOffset.y;
|
|
80
|
+
const total = horizontal ? contentSize.width : contentSize.height;
|
|
81
|
+
const visible = horizontal ? layoutMeasurement.width : layoutMeasurement.height;
|
|
82
|
+
const remaining = total - visible - offset;
|
|
83
|
+
|
|
84
|
+
// Each edge fades in over the first FADE_IN_DISTANCE px of travel, so an
|
|
85
|
+
// edge with nothing past it shows nothing.
|
|
86
|
+
setStartOpacity(Math.min(offset / FADE_IN_DISTANCE, 1));
|
|
87
|
+
setEndOpacity(Math.min(Math.max(remaining, 0) / FADE_IN_DISTANCE, 1));
|
|
88
|
+
|
|
89
|
+
child.props.onScroll?.(event);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const scrollable = isValidElement(child)
|
|
93
|
+
? cloneElement(child, {
|
|
94
|
+
onScroll: handleScroll,
|
|
95
|
+
scrollEventThrottle: child.props.scrollEventThrottle ?? 16,
|
|
96
|
+
})
|
|
97
|
+
: child;
|
|
98
|
+
|
|
99
|
+
const showStart = edges !== 'end';
|
|
100
|
+
const showEnd = edges !== 'start';
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<View {...props} className={className}>
|
|
104
|
+
{scrollable}
|
|
105
|
+
|
|
106
|
+
{showStart ? (
|
|
107
|
+
<Fade
|
|
108
|
+
color={fadeColor}
|
|
109
|
+
size={size}
|
|
110
|
+
horizontal={horizontal}
|
|
111
|
+
opacity={startOpacity}
|
|
112
|
+
edge="start"
|
|
113
|
+
/>
|
|
114
|
+
) : null}
|
|
115
|
+
{showEnd ? (
|
|
116
|
+
<Fade
|
|
117
|
+
color={fadeColor}
|
|
118
|
+
size={size}
|
|
119
|
+
horizontal={horizontal}
|
|
120
|
+
opacity={endOpacity}
|
|
121
|
+
edge="end"
|
|
122
|
+
/>
|
|
123
|
+
) : null}
|
|
124
|
+
</View>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
ScrollFade.displayName = 'ScrollFade';
|
|
129
|
+
|
|
130
|
+
/** One edge's gradient, opaque at the edge and clear inwards. */
|
|
131
|
+
function Fade({
|
|
132
|
+
color,
|
|
133
|
+
size,
|
|
134
|
+
horizontal,
|
|
135
|
+
opacity,
|
|
136
|
+
edge,
|
|
137
|
+
}: {
|
|
138
|
+
color: string;
|
|
139
|
+
size: number;
|
|
140
|
+
horizontal: boolean;
|
|
141
|
+
opacity: number;
|
|
142
|
+
edge: 'start' | 'end';
|
|
143
|
+
}) {
|
|
144
|
+
const isStart = edge === 'start';
|
|
145
|
+
const id = `scroll-fade-${edge}-${horizontal ? 'x' : 'y'}`;
|
|
146
|
+
|
|
147
|
+
const position = horizontal
|
|
148
|
+
? { top: 0, bottom: 0, width: size, ...(isStart ? { left: 0 } : { right: 0 }) }
|
|
149
|
+
: { left: 0, right: 0, height: size, ...(isStart ? { top: 0 } : { bottom: 0 }) };
|
|
150
|
+
|
|
151
|
+
// The gradient runs from the screen edge inwards, so it is reversed on the
|
|
152
|
+
// end edge.
|
|
153
|
+
const [x2, y2] = horizontal ? [1, 0] : [0, 1];
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<View pointerEvents="none" style={{ position: 'absolute', opacity, ...position }}>
|
|
157
|
+
<Svg width="100%" height="100%">
|
|
158
|
+
<Defs>
|
|
159
|
+
<LinearGradient id={id} x1="0" y1="0" x2={String(x2)} y2={String(y2)}>
|
|
160
|
+
<Stop offset="0" stopColor={color} stopOpacity={isStart ? 1 : 0} />
|
|
161
|
+
<Stop offset="1" stopColor={color} stopOpacity={isStart ? 0 : 1} />
|
|
162
|
+
</LinearGradient>
|
|
163
|
+
</Defs>
|
|
164
|
+
<Rect x="0" y="0" width="100%" height="100%" fill={`url(#${id})`} />
|
|
165
|
+
</Svg>
|
|
166
|
+
</View>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shimmer — an animated highlight sweeping across content.
|
|
3
|
+
*
|
|
4
|
+
* The React Native equivalent of shadcn-ui/ui's `shimmer` utility
|
|
5
|
+
* (https://ui.shadcn.com/docs/utils/shimmer). That one is CSS:
|
|
6
|
+
* `background-clip: text` with a `color-mix()` highlight. React Native has
|
|
7
|
+
* neither, so the highlight is drawn as an SVG gradient band swept across an
|
|
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.
|
|
13
|
+
*
|
|
14
|
+
* ```tsx
|
|
15
|
+
* <Shimmer>
|
|
16
|
+
* <Text muted>Generating response…</Text>
|
|
17
|
+
* </Shimmer>
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
import { useEffect, useState, type ReactNode } from 'react';
|
|
21
|
+
import { View, type LayoutChangeEvent, type ViewProps } from 'react-native';
|
|
22
|
+
import Animated, {
|
|
23
|
+
cancelAnimation,
|
|
24
|
+
Easing,
|
|
25
|
+
useAnimatedStyle,
|
|
26
|
+
useReducedMotion,
|
|
27
|
+
useSharedValue,
|
|
28
|
+
withRepeat,
|
|
29
|
+
withTiming,
|
|
30
|
+
} from 'react-native-reanimated';
|
|
31
|
+
import Svg, { Defs, LinearGradient, Rect, Stop } from 'react-native-svg';
|
|
32
|
+
import { useCSSVariable } from 'uniwind';
|
|
33
|
+
import { cn } from '../../utils/cn';
|
|
34
|
+
|
|
35
|
+
export interface ShimmerProps extends ViewProps {
|
|
36
|
+
className?: string;
|
|
37
|
+
/** Milliseconds for one sweep. */
|
|
38
|
+
duration?: number;
|
|
39
|
+
/** Sweep once instead of looping. */
|
|
40
|
+
once?: boolean;
|
|
41
|
+
/** Sweep right-to-left. */
|
|
42
|
+
reverse?: boolean;
|
|
43
|
+
/** Width of the highlight band, as a fraction of the content width. */
|
|
44
|
+
spread?: number;
|
|
45
|
+
/** Highlight colour. Defaults to the theme's foreground. */
|
|
46
|
+
color?: string;
|
|
47
|
+
/** Peak opacity of the highlight. */
|
|
48
|
+
intensity?: number;
|
|
49
|
+
children?: ReactNode;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function Shimmer({
|
|
53
|
+
className,
|
|
54
|
+
duration = 2000,
|
|
55
|
+
once = false,
|
|
56
|
+
reverse = false,
|
|
57
|
+
spread = 0.4,
|
|
58
|
+
color,
|
|
59
|
+
intensity = 0.45,
|
|
60
|
+
children,
|
|
61
|
+
...props
|
|
62
|
+
}: ShimmerProps) {
|
|
63
|
+
const [width, setWidth] = useState(0);
|
|
64
|
+
const progress = useSharedValue(0);
|
|
65
|
+
const reducedMotion = useReducedMotion();
|
|
66
|
+
|
|
67
|
+
const themeForeground = useCSSVariable('--color-foreground');
|
|
68
|
+
const highlight =
|
|
69
|
+
color ?? (typeof themeForeground === 'string' ? themeForeground : '#ffffff');
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
// Respect the OS setting rather than animating regardless — the same
|
|
73
|
+
// thing shadcn's utility does with prefers-reduced-motion.
|
|
74
|
+
if (reducedMotion || width === 0) return;
|
|
75
|
+
|
|
76
|
+
progress.value = 0;
|
|
77
|
+
progress.value = withRepeat(
|
|
78
|
+
withTiming(1, { duration, easing: Easing.linear }),
|
|
79
|
+
once ? 1 : -1,
|
|
80
|
+
false
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
return () => cancelAnimation(progress);
|
|
84
|
+
}, [duration, once, reducedMotion, width, progress]);
|
|
85
|
+
|
|
86
|
+
const bandWidth = Math.max(width * spread, 1);
|
|
87
|
+
|
|
88
|
+
const bandStyle = useAnimatedStyle(() => {
|
|
89
|
+
// Travel from fully off one edge to fully off the other.
|
|
90
|
+
const from = -bandWidth;
|
|
91
|
+
const to = width;
|
|
92
|
+
const x = reverse
|
|
93
|
+
? to - progress.value * (to - from)
|
|
94
|
+
: from + progress.value * (to - from);
|
|
95
|
+
return { transform: [{ translateX: x }] };
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const onLayout = (event: LayoutChangeEvent) => {
|
|
99
|
+
setWidth(event.nativeEvent.layout.width);
|
|
100
|
+
props.onLayout?.(event);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<View
|
|
105
|
+
{...props}
|
|
106
|
+
onLayout={onLayout}
|
|
107
|
+
className={cn('overflow-hidden', className)}
|
|
108
|
+
>
|
|
109
|
+
{children}
|
|
110
|
+
|
|
111
|
+
{reducedMotion || width === 0 ? null : (
|
|
112
|
+
<Animated.View
|
|
113
|
+
pointerEvents="none"
|
|
114
|
+
style={[
|
|
115
|
+
{ position: 'absolute', top: 0, bottom: 0, width: bandWidth },
|
|
116
|
+
bandStyle,
|
|
117
|
+
]}
|
|
118
|
+
>
|
|
119
|
+
<Svg width="100%" height="100%">
|
|
120
|
+
<Defs>
|
|
121
|
+
<LinearGradient id="shimmer" x1="0" y1="0" x2="1" y2="0">
|
|
122
|
+
<Stop offset="0" stopColor={highlight} stopOpacity={0} />
|
|
123
|
+
<Stop offset="0.5" stopColor={highlight} stopOpacity={intensity} />
|
|
124
|
+
<Stop offset="1" stopColor={highlight} stopOpacity={0} />
|
|
125
|
+
</LinearGradient>
|
|
126
|
+
</Defs>
|
|
127
|
+
<Rect x="0" y="0" width="100%" height="100%" fill="url(#shimmer)" />
|
|
128
|
+
</Svg>
|
|
129
|
+
</Animated.View>
|
|
130
|
+
)}
|
|
131
|
+
</View>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
Shimmer.displayName = 'Shimmer';
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Surface — an elevated container.
|
|
3
|
+
*
|
|
4
|
+
* Adapted from: heroui-inc/heroui-native src/components/surface/
|
|
5
|
+
* (the variant ladder and the nesting model for building hierarchy).
|
|
6
|
+
*
|
|
7
|
+
* Both the fill and the corner radius follow the active theme: the variants
|
|
8
|
+
* resolve to themed `--color-surface-*` tokens, and `rounded-3xl` resolves
|
|
9
|
+
* through the per-theme radius scale — so Moon's surfaces are tight and
|
|
10
|
+
* Grass's are soft, without the component knowing anything about either.
|
|
11
|
+
*
|
|
12
|
+
* ```tsx
|
|
13
|
+
* <Surface>
|
|
14
|
+
* <Surface variant="secondary">
|
|
15
|
+
* <Surface variant="tertiary">…</Surface>
|
|
16
|
+
* </Surface>
|
|
17
|
+
* </Surface>
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
import { forwardRef } from 'react';
|
|
21
|
+
import { StyleSheet, View, type ViewProps } from 'react-native';
|
|
22
|
+
import { tv, type VariantProps } from 'tailwind-variants';
|
|
23
|
+
|
|
24
|
+
const surfaceVariants = tv({
|
|
25
|
+
base: 'overflow-hidden rounded-3xl p-4',
|
|
26
|
+
variants: {
|
|
27
|
+
variant: {
|
|
28
|
+
default: 'bg-surface',
|
|
29
|
+
secondary: 'bg-surface-secondary',
|
|
30
|
+
tertiary: 'bg-surface-tertiary',
|
|
31
|
+
transparent: 'bg-transparent',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
defaultVariants: {
|
|
35
|
+
variant: 'default',
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export interface SurfaceProps
|
|
40
|
+
extends ViewProps,
|
|
41
|
+
VariantProps<typeof surfaceVariants> {
|
|
42
|
+
className?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const Surface = forwardRef<View, SurfaceProps>(
|
|
46
|
+
({ className, variant, style, ...props }, ref) => (
|
|
47
|
+
<View
|
|
48
|
+
ref={ref}
|
|
49
|
+
// `borderCurve` has no Tailwind equivalent. On iOS it gives the
|
|
50
|
+
// continuous (squircle) corner Apple uses, which is visibly smoother
|
|
51
|
+
// than a circular arc at this radius; Android ignores it.
|
|
52
|
+
style={[styles.root, style]}
|
|
53
|
+
className={surfaceVariants({ variant, className })}
|
|
54
|
+
{...props}
|
|
55
|
+
/>
|
|
56
|
+
)
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
Surface.displayName = 'Surface';
|
|
60
|
+
|
|
61
|
+
const styles = StyleSheet.create({
|
|
62
|
+
root: { borderCurve: 'continuous' },
|
|
63
|
+
});
|
|
@@ -58,7 +58,9 @@ const timelineVariants = tv({
|
|
|
58
58
|
item: 'w-full flex-row gap-3',
|
|
59
59
|
aside: 'w-20 items-end gap-0.5 pt-0.5',
|
|
60
60
|
rail: 'items-center',
|
|
61
|
-
|
|
61
|
+
// Flat discs, no ring — the same restraint Steps uses. Rings on every node
|
|
62
|
+
// were most of what made the timeline read as noisy.
|
|
63
|
+
indicator: 'items-center justify-center rounded-full',
|
|
62
64
|
indicatorLabel: 'text-xs font-medium',
|
|
63
65
|
separator: 'w-0.5 flex-1 rounded-full',
|
|
64
66
|
body: 'flex-1 pb-6',
|
|
@@ -112,22 +114,48 @@ const timelineVariants = tv({
|
|
|
112
114
|
},
|
|
113
115
|
});
|
|
114
116
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
117
|
+
/*
|
|
118
|
+
* Colour rule, matching the Steps component: progress is solid, event kind is
|
|
119
|
+
* tinted.
|
|
120
|
+
*
|
|
121
|
+
* - untoned pending → bg-muted (Steps' inactive)
|
|
122
|
+
* - untoned completed → bg-primary (Steps' completed)
|
|
123
|
+
* - toned → bg-{tone}-soft, content in the tone's foreground
|
|
124
|
+
*
|
|
125
|
+
* The `-soft` fills are the same tinted tokens Alert uses, so a toned node sits
|
|
126
|
+
* in the same family as the rest of the library instead of shouting in raw
|
|
127
|
+
* brand colour.
|
|
128
|
+
*
|
|
129
|
+
* Nodes that hold something — an icon or a number — get a hairline so they read
|
|
130
|
+
* as a container. The `dot` and `card` discs stay bare: at 16px an outline is
|
|
131
|
+
* most of what made the original look busy. The border therefore lives on the
|
|
132
|
+
* tone class rather than the shared `indicator` slot, so a solid disc cannot
|
|
133
|
+
* pick one up by accident.
|
|
134
|
+
*/
|
|
135
|
+
const TONE_NODE: Record<TimelineTone, { solid: string; tinted: string }> = {
|
|
136
|
+
default: { solid: 'bg-primary', tinted: 'border border-border bg-muted' },
|
|
137
|
+
info: { solid: 'bg-info', tinted: 'border border-info/32 bg-info-soft' },
|
|
138
|
+
success: { solid: 'bg-success', tinted: 'border border-success/32 bg-success-soft' },
|
|
139
|
+
warning: { solid: 'bg-warning', tinted: 'border border-warning/32 bg-warning-soft' },
|
|
140
|
+
danger: {
|
|
141
|
+
solid: 'bg-destructive',
|
|
142
|
+
tinted: 'border border-destructive/32 bg-destructive-soft',
|
|
143
|
+
},
|
|
122
144
|
};
|
|
123
145
|
|
|
124
|
-
/**
|
|
125
|
-
|
|
146
|
+
/**
|
|
147
|
+
* The node an incomplete, untoned step gets — bordered when it holds an icon
|
|
148
|
+
* or a number, bare when it is a status disc.
|
|
149
|
+
*/
|
|
150
|
+
const PENDING_NODE = {
|
|
151
|
+
solid: 'bg-muted',
|
|
152
|
+
tinted: 'border border-border bg-muted',
|
|
153
|
+
} as const;
|
|
126
154
|
|
|
127
155
|
/**
|
|
128
|
-
* CSS variable
|
|
129
|
-
* `-foreground` tokens are the ones tuned to read
|
|
130
|
-
*
|
|
156
|
+
* CSS variable a tinted node's contents take their colour from. The
|
|
157
|
+
* `-foreground` tokens are the ones tuned to read in both light and dark,
|
|
158
|
+
* which is what sits on a soft tint.
|
|
131
159
|
*/
|
|
132
160
|
const TONE_ICON_VAR: Record<TimelineTone, string> = {
|
|
133
161
|
default: '--color-primary',
|
|
@@ -264,33 +292,31 @@ const TimelineIndicator = forwardRef<View, TimelineIndicatorProps>(
|
|
|
264
292
|
completed,
|
|
265
293
|
});
|
|
266
294
|
|
|
267
|
-
|
|
295
|
+
// dot/card nodes are small discs with nothing inside, so they keep full
|
|
296
|
+
// saturation — a soft tint at 16px would disappear. Nodes that hold an
|
|
297
|
+
// icon or a number take the tinted fill instead.
|
|
298
|
+
const isDisc = SOLID_VARIANTS.includes(variant);
|
|
268
299
|
const toned = tone !== 'default';
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
const nodeClass =
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
300
|
+
|
|
301
|
+
const fill = isDisc ? 'solid' : 'tinted';
|
|
302
|
+
const nodeClass = toned
|
|
303
|
+
? TONE_NODE[tone][fill]
|
|
304
|
+
: completed
|
|
305
|
+
? TONE_NODE.default.solid
|
|
306
|
+
: PENDING_NODE[fill];
|
|
307
|
+
|
|
308
|
+
// Contents sit on the tint (or on primary once complete), so they take the
|
|
309
|
+
// colour tuned to read against it.
|
|
310
|
+
const tintedContent = useCSSVariable(
|
|
311
|
+
toned ? TONE_ICON_VAR[tone] : '--color-muted-foreground'
|
|
278
312
|
);
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const solidColor =
|
|
287
|
-
tone === 'default'
|
|
288
|
-
? typeof primaryForeground === 'string'
|
|
289
|
-
? primaryForeground
|
|
290
|
-
: undefined
|
|
291
|
-
: '#ffffff';
|
|
292
|
-
|
|
293
|
-
const contentColor = isSolid && (completed || toned) ? solidColor : outlineColor;
|
|
313
|
+
const solidContent = useCSSVariable('--color-primary-foreground');
|
|
314
|
+
|
|
315
|
+
const resolved = (value: unknown) =>
|
|
316
|
+
typeof value === 'string' ? value : undefined;
|
|
317
|
+
|
|
318
|
+
const contentColor =
|
|
319
|
+
!toned && completed ? resolved(solidContent) : resolved(tintedContent);
|
|
294
320
|
|
|
295
321
|
return (
|
|
296
322
|
<View className={rail()}>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export {
|
|
2
|
+
useCopyToClipboard,
|
|
3
|
+
type UseCopyToClipboardOptions,
|
|
4
|
+
type UseCopyToClipboardResult,
|
|
5
|
+
} from './use-copy-to-clipboard';
|
|
6
|
+
export {
|
|
7
|
+
useDisclosure,
|
|
8
|
+
type UseDisclosureOptions,
|
|
9
|
+
type UseDisclosureResult,
|
|
10
|
+
} from './use-disclosure';
|
|
11
|
+
export {
|
|
12
|
+
useBreakpoint,
|
|
13
|
+
BREAKPOINTS,
|
|
14
|
+
type Breakpoint,
|
|
15
|
+
type UseBreakpointResult,
|
|
16
|
+
} from './use-breakpoint';
|
|
17
|
+
export { useKeyboard, type UseKeyboardResult } from './use-keyboard';
|
|
18
|
+
export { useDebouncedValue } from './use-debounced-value';
|
|
19
|
+
export { usePrevious } from './use-previous';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { useWindowDimensions } from 'react-native';
|
|
2
|
+
|
|
3
|
+
/** Tailwind's breakpoints, minus the ones no phone or tablet reaches. */
|
|
4
|
+
export const BREAKPOINTS = { sm: 640, md: 768, lg: 1024, xl: 1280 } as const;
|
|
5
|
+
|
|
6
|
+
export type Breakpoint = keyof typeof BREAKPOINTS;
|
|
7
|
+
|
|
8
|
+
export interface UseBreakpointResult {
|
|
9
|
+
/** Largest breakpoint the window currently satisfies, or `base`. */
|
|
10
|
+
current: Breakpoint | 'base';
|
|
11
|
+
/** True when the window is at least this wide. */
|
|
12
|
+
isAtLeast: (breakpoint: Breakpoint) => boolean;
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
isLandscape: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Responsive state from the window size — the React Native answer to
|
|
20
|
+
* coss.com/ui's `useMediaQuery`, which has no equivalent here.
|
|
21
|
+
*
|
|
22
|
+
* For styling, prefer Uniwind's responsive class prefixes (`md:flex-row`).
|
|
23
|
+
* Reach for this when the *behaviour* changes, not just the look — rendering
|
|
24
|
+
* a sheet on a phone and a dialog on a tablet, say.
|
|
25
|
+
*
|
|
26
|
+
* ```tsx
|
|
27
|
+
* const { isAtLeast } = useBreakpoint();
|
|
28
|
+
* return isAtLeast('md') ? <Dialog …/> : <BottomSheet …/>;
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function useBreakpoint(): UseBreakpointResult {
|
|
32
|
+
const { width, height } = useWindowDimensions();
|
|
33
|
+
|
|
34
|
+
const current: Breakpoint | 'base' =
|
|
35
|
+
width >= BREAKPOINTS.xl
|
|
36
|
+
? 'xl'
|
|
37
|
+
: width >= BREAKPOINTS.lg
|
|
38
|
+
? 'lg'
|
|
39
|
+
: width >= BREAKPOINTS.md
|
|
40
|
+
? 'md'
|
|
41
|
+
: width >= BREAKPOINTS.sm
|
|
42
|
+
? 'sm'
|
|
43
|
+
: 'base';
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
current,
|
|
47
|
+
isAtLeast: (breakpoint) => width >= BREAKPOINTS[breakpoint],
|
|
48
|
+
width,
|
|
49
|
+
height,
|
|
50
|
+
isLandscape: width > height,
|
|
51
|
+
};
|
|
52
|
+
}
|