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.
Files changed (62) hide show
  1. package/README.md +10 -5
  2. package/lib/module/components/scroll-fade/index.js +156 -0
  3. package/lib/module/components/scroll-fade/index.js.map +1 -0
  4. package/lib/module/components/shimmer/index.js +120 -0
  5. package/lib/module/components/shimmer/index.js.map +1 -0
  6. package/lib/module/components/surface/index.js +64 -0
  7. package/lib/module/components/surface/index.js.map +1 -0
  8. package/lib/module/components/timeline/index.js +55 -30
  9. package/lib/module/components/timeline/index.js.map +1 -1
  10. package/lib/module/hooks/index.js +9 -0
  11. package/lib/module/hooks/index.js.map +1 -0
  12. package/lib/module/hooks/use-breakpoint.js +39 -0
  13. package/lib/module/hooks/use-breakpoint.js.map +1 -0
  14. package/lib/module/hooks/use-copy-to-clipboard.js +62 -0
  15. package/lib/module/hooks/use-copy-to-clipboard.js.map +1 -0
  16. package/lib/module/hooks/use-debounced-value.js +24 -0
  17. package/lib/module/hooks/use-debounced-value.js.map +1 -0
  18. package/lib/module/hooks/use-disclosure.js +43 -0
  19. package/lib/module/hooks/use-disclosure.js.map +1 -0
  20. package/lib/module/hooks/use-keyboard.js +36 -0
  21. package/lib/module/hooks/use-keyboard.js.map +1 -0
  22. package/lib/module/hooks/use-previous.js +25 -0
  23. package/lib/module/hooks/use-previous.js.map +1 -0
  24. package/lib/module/index.js +6 -0
  25. package/lib/module/index.js.map +1 -1
  26. package/lib/typescript/src/components/scroll-fade/index.d.ts +38 -0
  27. package/lib/typescript/src/components/scroll-fade/index.d.ts.map +1 -0
  28. package/lib/typescript/src/components/shimmer/index.d.ts +42 -0
  29. package/lib/typescript/src/components/shimmer/index.d.ts.map +1 -0
  30. package/lib/typescript/src/components/surface/index.d.ts +30 -0
  31. package/lib/typescript/src/components/surface/index.d.ts.map +1 -0
  32. package/lib/typescript/src/components/timeline/index.d.ts.map +1 -1
  33. package/lib/typescript/src/hooks/index.d.ts +7 -0
  34. package/lib/typescript/src/hooks/index.d.ts.map +1 -0
  35. package/lib/typescript/src/hooks/use-breakpoint.d.ts +32 -0
  36. package/lib/typescript/src/hooks/use-breakpoint.d.ts.map +1 -0
  37. package/lib/typescript/src/hooks/use-copy-to-clipboard.d.ts +30 -0
  38. package/lib/typescript/src/hooks/use-copy-to-clipboard.d.ts.map +1 -0
  39. package/lib/typescript/src/hooks/use-debounced-value.d.ts +13 -0
  40. package/lib/typescript/src/hooks/use-debounced-value.d.ts.map +1 -0
  41. package/lib/typescript/src/hooks/use-disclosure.d.ts +32 -0
  42. package/lib/typescript/src/hooks/use-disclosure.d.ts.map +1 -0
  43. package/lib/typescript/src/hooks/use-keyboard.d.ts +20 -0
  44. package/lib/typescript/src/hooks/use-keyboard.d.ts.map +1 -0
  45. package/lib/typescript/src/hooks/use-previous.d.ts +15 -0
  46. package/lib/typescript/src/hooks/use-previous.d.ts.map +1 -0
  47. package/lib/typescript/src/index.d.ts +4 -0
  48. package/lib/typescript/src/index.d.ts.map +1 -1
  49. package/package.json +8 -1
  50. package/src/components/scroll-fade/index.tsx +168 -0
  51. package/src/components/shimmer/index.tsx +135 -0
  52. package/src/components/surface/index.tsx +63 -0
  53. package/src/components/timeline/index.tsx +64 -38
  54. package/src/hooks/index.ts +19 -0
  55. package/src/hooks/use-breakpoint.ts +52 -0
  56. package/src/hooks/use-copy-to-clipboard.ts +81 -0
  57. package/src/hooks/use-debounced-value.ts +23 -0
  58. package/src/hooks/use-disclosure.ts +57 -0
  59. package/src/hooks/use-keyboard.ts +43 -0
  60. package/src/hooks/use-previous.ts +24 -0
  61. package/src/index.ts +9 -0
  62. package/theme.css +18 -0
@@ -0,0 +1,81 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+
3
+ export interface UseCopyToClipboardOptions {
4
+ /** Milliseconds before `copied` flips back. */
5
+ timeout?: number;
6
+ onCopy?: () => void;
7
+ }
8
+
9
+ export interface UseCopyToClipboardResult {
10
+ /** Writes `value` to the clipboard and sets `copied`. */
11
+ copy: (value: string) => Promise<void>;
12
+ copied: boolean;
13
+ }
14
+
15
+ /**
16
+ * Copy text to the clipboard, with a temporary "copied" state.
17
+ *
18
+ * Mirrors coss.com/ui's `useCopyToClipboard`.
19
+ *
20
+ * Requires `expo-clipboard`, which is an optional peer dependency — React
21
+ * Native removed `Clipboard` from core, and pulling in a required native
22
+ * module for one hook would cost every consumer who never calls it:
23
+ *
24
+ * ```sh
25
+ * npx expo install expo-clipboard
26
+ * ```
27
+ *
28
+ * ```tsx
29
+ * const { copy, copied } = useCopyToClipboard();
30
+ * <Button onPress={() => copy(url)}>{copied ? 'Copied' : 'Copy link'}</Button>
31
+ * ```
32
+ */
33
+ export function useCopyToClipboard({
34
+ timeout = 2000,
35
+ onCopy,
36
+ }: UseCopyToClipboardOptions = {}): UseCopyToClipboardResult {
37
+ const [copied, setCopied] = useState(false);
38
+ const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
39
+
40
+ // Never leave a timer running against an unmounted component.
41
+ useEffect(
42
+ () => () => {
43
+ if (timer.current) clearTimeout(timer.current);
44
+ },
45
+ []
46
+ );
47
+
48
+ const copy = useCallback(
49
+ async (value: string) => {
50
+ const setString = await loadClipboard();
51
+ await setString(value);
52
+
53
+ setCopied(true);
54
+ onCopy?.();
55
+
56
+ if (timer.current) clearTimeout(timer.current);
57
+ if (timeout > 0) {
58
+ timer.current = setTimeout(() => setCopied(false), timeout);
59
+ }
60
+ },
61
+ [timeout, onCopy]
62
+ );
63
+
64
+ return { copy, copied };
65
+ }
66
+
67
+ /**
68
+ * Resolves expo-clipboard lazily, so apps that never call this hook are not
69
+ * asked to install it. The import is awaited rather than top-level for the
70
+ * same reason.
71
+ */
72
+ async function loadClipboard(): Promise<(value: string) => Promise<unknown>> {
73
+ try {
74
+ const clipboard = await import('expo-clipboard');
75
+ return clipboard.setStringAsync;
76
+ } catch {
77
+ throw new Error(
78
+ 'useCopyToClipboard requires expo-clipboard. Install it with: npx expo install expo-clipboard'
79
+ );
80
+ }
81
+ }
@@ -0,0 +1,23 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ /**
4
+ * A copy of `value` that only updates once it has stopped changing for
5
+ * `delay` ms — for search fields, where you want the typed value on screen
6
+ * immediately but the query fired once.
7
+ *
8
+ * ```tsx
9
+ * const [query, setQuery] = useState('');
10
+ * const debounced = useDebouncedValue(query, 300);
11
+ * useEffect(() => { search(debounced); }, [debounced]);
12
+ * ```
13
+ */
14
+ export function useDebouncedValue<T>(value: T, delay = 300): T {
15
+ const [debounced, setDebounced] = useState(value);
16
+
17
+ useEffect(() => {
18
+ const timer = setTimeout(() => setDebounced(value), delay);
19
+ return () => clearTimeout(timer);
20
+ }, [value, delay]);
21
+
22
+ return debounced;
23
+ }
@@ -0,0 +1,57 @@
1
+ import { useCallback, useMemo, useState } from 'react';
2
+
3
+ export interface UseDisclosureOptions {
4
+ defaultOpen?: boolean;
5
+ /** Controlled open state. Pass this to drive it from outside. */
6
+ open?: boolean;
7
+ onOpenChange?: (open: boolean) => void;
8
+ }
9
+
10
+ export interface UseDisclosureResult {
11
+ isOpen: boolean;
12
+ open: () => void;
13
+ close: () => void;
14
+ toggle: () => void;
15
+ /** Spread onto Dialog, BottomSheet or Select. */
16
+ props: { open: boolean; onOpenChange: (open: boolean) => void };
17
+ }
18
+
19
+ /**
20
+ * Open/closed state for an overlay.
21
+ *
22
+ * The same controlled-or-uncontrolled pattern Dialog, BottomSheet and Select
23
+ * implement internally, lifted out for when you need to drive one from
24
+ * elsewhere — closing a sheet after a request settles, say.
25
+ *
26
+ * ```tsx
27
+ * const sheet = useDisclosure();
28
+ * <Button onPress={sheet.open}>Share</Button>
29
+ * <BottomSheet {...sheet.props}>…</BottomSheet>
30
+ * ```
31
+ */
32
+ export function useDisclosure({
33
+ defaultOpen = false,
34
+ open: controlledOpen,
35
+ onOpenChange,
36
+ }: UseDisclosureOptions = {}): UseDisclosureResult {
37
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
38
+ const isControlled = controlledOpen !== undefined;
39
+ const isOpen = isControlled ? controlledOpen : uncontrolledOpen;
40
+
41
+ const setOpen = useCallback(
42
+ (next: boolean) => {
43
+ if (!isControlled) setUncontrolledOpen(next);
44
+ onOpenChange?.(next);
45
+ },
46
+ [isControlled, onOpenChange]
47
+ );
48
+
49
+ const open = useCallback(() => setOpen(true), [setOpen]);
50
+ const close = useCallback(() => setOpen(false), [setOpen]);
51
+ const toggle = useCallback(() => setOpen(!isOpen), [setOpen, isOpen]);
52
+
53
+ return useMemo(
54
+ () => ({ isOpen, open, close, toggle, props: { open: isOpen, onOpenChange: setOpen } }),
55
+ [isOpen, open, close, toggle, setOpen]
56
+ );
57
+ }
@@ -0,0 +1,43 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { Keyboard, Platform } from 'react-native';
3
+
4
+ export interface UseKeyboardResult {
5
+ /** Height of the keyboard in px, or 0 when hidden. */
6
+ height: number;
7
+ isVisible: boolean;
8
+ }
9
+
10
+ /**
11
+ * Keyboard height and visibility, on the JS thread.
12
+ *
13
+ * Use this when the value drives rendering — showing a hint, swapping a
14
+ * layout. For *animating* against the keyboard, use Reanimated's
15
+ * `useAnimatedKeyboard` instead: it runs on the UI thread and tracks the
16
+ * keyboard frame-for-frame, where this fires once per show/hide.
17
+ *
18
+ * ```tsx
19
+ * const { isVisible } = useKeyboard();
20
+ * {isVisible ? null : <Footer />}
21
+ * ```
22
+ */
23
+ export function useKeyboard(): UseKeyboardResult {
24
+ const [height, setHeight] = useState(0);
25
+
26
+ useEffect(() => {
27
+ // iOS reports the frame before it animates; Android only on completion.
28
+ const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
29
+ const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
30
+
31
+ const show = Keyboard.addListener(showEvent, (event) =>
32
+ setHeight(event.endCoordinates.height)
33
+ );
34
+ const hide = Keyboard.addListener(hideEvent, () => setHeight(0));
35
+
36
+ return () => {
37
+ show.remove();
38
+ hide.remove();
39
+ };
40
+ }, []);
41
+
42
+ return { height, isVisible: height > 0 };
43
+ }
@@ -0,0 +1,24 @@
1
+ import { useEffect, useRef } from 'react';
2
+
3
+ /**
4
+ * The value from the previous render, or `undefined` on the first.
5
+ *
6
+ * Useful for reacting to a transition rather than a state — firing only when
7
+ * something *became* true.
8
+ *
9
+ * ```tsx
10
+ * const wasOpen = usePrevious(isOpen);
11
+ * useEffect(() => {
12
+ * if (isOpen && !wasOpen) track('sheet_opened');
13
+ * }, [isOpen, wasOpen]);
14
+ * ```
15
+ */
16
+ export function usePrevious<T>(value: T): T | undefined {
17
+ const ref = useRef<T | undefined>(undefined);
18
+
19
+ useEffect(() => {
20
+ ref.current = value;
21
+ }, [value]);
22
+
23
+ return ref.current;
24
+ }
package/src/index.ts CHANGED
@@ -90,6 +90,11 @@ export {
90
90
  type RadioGroupProps,
91
91
  type RadioGroupItemProps,
92
92
  } from './components/radio-group';
93
+ export {
94
+ ScrollFade,
95
+ type ScrollFadeProps,
96
+ } from './components/scroll-fade';
97
+ export { Shimmer, type ShimmerProps } from './components/shimmer';
93
98
  export { Skeleton, type SkeletonProps } from './components/skeleton';
94
99
  export { Spinner, type SpinnerProps } from './components/spinner';
95
100
  export {
@@ -102,6 +107,7 @@ export {
102
107
  type StepState,
103
108
  type StepsOrientation,
104
109
  } from './components/steps';
110
+ export { Surface, type SurfaceProps } from './components/surface';
105
111
  export { Switch, type SwitchProps } from './components/switch';
106
112
  export {
107
113
  Timeline,
@@ -166,5 +172,8 @@ export {
166
172
  type IconProps,
167
173
  } from './icons';
168
174
 
175
+ // Hooks
176
+ export * from './hooks';
177
+
169
178
  // Utils
170
179
  export { cn } from './utils/cn';
package/theme.css CHANGED
@@ -40,7 +40,13 @@
40
40
  --color-border: unset;
41
41
  --color-input: unset;
42
42
  --color-ring: unset;
43
+ /*
44
+ * Elevation ladder. Each step sits one notch further from the page
45
+ * background than the last, so nesting Surfaces reads as hierarchy.
46
+ */
43
47
  --color-surface: unset;
48
+ --color-surface-secondary: unset;
49
+ --color-surface-tertiary: unset;
44
50
  --color-skeleton: unset;
45
51
 
46
52
  /*
@@ -122,6 +128,8 @@
122
128
 
123
129
  /* grouping surface (Frame) and skeleton fill */
124
130
  --color-surface: #f7f7f7;
131
+ --color-surface-secondary: #f1f1f1;
132
+ --color-surface-tertiary: #eaeaea;
125
133
  --color-skeleton: rgba(0, 0, 0, 0.1);
126
134
 
127
135
  /* status fills: -soft = Alert (10%), -subtle = Badge (8%) */
@@ -193,6 +201,8 @@
193
201
 
194
202
  /* grouping surface (Frame) and skeleton fill */
195
203
  --color-surface: #1c1c1c;
204
+ --color-surface-secondary: #222222;
205
+ --color-surface-tertiary: #282828;
196
206
  --color-skeleton: rgba(255, 255, 255, 0.13);
197
207
 
198
208
  /* status fills: -soft = Alert (6%), -subtle = Badge (16%) */
@@ -274,6 +284,8 @@
274
284
  --color-ring: #171717;
275
285
 
276
286
  --color-surface: #fafafa;
287
+ --color-surface-secondary: #f4f4f4;
288
+ --color-surface-tertiary: #ededed;
277
289
  --color-skeleton: rgba(0, 0, 0, 0.08);
278
290
 
279
291
  --color-info-soft: rgba(0, 112, 243, 0.08);
@@ -334,6 +346,8 @@
334
346
  --color-ring: #ededed;
335
347
 
336
348
  --color-surface: #0f0f0f;
349
+ --color-surface-secondary: #161616;
350
+ --color-surface-tertiary: #1d1d1d;
337
351
  --color-skeleton: rgba(255, 255, 255, 0.11);
338
352
 
339
353
  --color-info-soft: rgba(0, 112, 243, 0.12);
@@ -397,6 +411,8 @@
397
411
  --color-ring: #3ecf8e;
398
412
 
399
413
  --color-surface: #f8f9fa;
414
+ --color-surface-secondary: #f1f3f5;
415
+ --color-surface-tertiary: #e9ecef;
400
416
  --color-skeleton: rgba(0, 0, 0, 0.08);
401
417
 
402
418
  --color-info-soft: rgba(59, 130, 246, 0.1);
@@ -457,6 +473,8 @@
457
473
  --color-ring: #3ecf8e;
458
474
 
459
475
  --color-surface: #242424;
476
+ --color-surface-secondary: #2a2a2a;
477
+ --color-surface-tertiary: #303030;
460
478
  --color-skeleton: rgba(255, 255, 255, 0.11);
461
479
 
462
480
  --color-info-soft: rgba(59, 130, 246, 0.08);