@innovui/react-native 0.0.0 → 0.2.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/LICENSE +21 -0
- package/dist/components/_shared/anchored-overlay.js +37 -12
- package/dist/components/_shared/anchored-overlay.js.map +1 -1
- package/dist/components/_shared/sliding-indicator.js +98 -0
- package/dist/components/_shared/sliding-indicator.js.map +1 -0
- package/dist/components/_shared/use-transition.js +53 -0
- package/dist/components/_shared/use-transition.js.map +1 -0
- package/dist/components/drawer/drawer.d.ts +11 -1
- package/dist/components/drawer/drawer.js +37 -22
- package/dist/components/drawer/drawer.js.map +1 -1
- package/dist/components/drawer/use-swipe-to-close.js +143 -0
- package/dist/components/drawer/use-swipe-to-close.js.map +1 -0
- package/dist/components/input/input.d.ts +8 -2
- package/dist/components/input/input.js +10 -2
- package/dist/components/input/input.js.map +1 -1
- package/dist/components/modal/modal.js +59 -39
- package/dist/components/modal/modal.js.map +1 -1
- package/dist/components/segmented-control/segmented-control.d.ts +2 -2
- package/dist/components/segmented-control/segmented-control.js +20 -7
- package/dist/components/segmented-control/segmented-control.js.map +1 -1
- package/dist/components/tabs/tabs.d.ts +2 -2
- package/dist/components/tabs/tabs.js +32 -9
- package/dist/components/tabs/tabs.js.map +1 -1
- package/dist/components/toast/toast.js +66 -5
- package/dist/components/toast/toast.js.map +1 -1
- package/dist/components/tooltip/tooltip.d.ts +7 -3
- package/dist/components/tooltip/tooltip.js +10 -4
- package/dist/components/tooltip/tooltip.js.map +1 -1
- package/package.json +9 -9
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 InnovTech Developers
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { useTransition } from "./use-transition.js";
|
|
1
2
|
import { useCallback, useRef, useState } from "react";
|
|
2
|
-
import { Modal, Pressable, View, useWindowDimensions } from "react-native";
|
|
3
|
-
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Animated, Modal, Pressable, View, useWindowDimensions } from "react-native";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
5
|
//#region src/components/_shared/anchored-overlay.tsx
|
|
5
6
|
/**
|
|
6
7
|
* Medição de um gatilho para ancorar um overlay (Popover, Menu, Select, Combobox, DatePicker,
|
|
@@ -35,17 +36,25 @@ function useAnchor() {
|
|
|
35
36
|
};
|
|
36
37
|
}
|
|
37
38
|
/** Overlay transparente, posicionado junto ao gatilho, dentro de um Modal nativo (fica acima de tudo). */
|
|
38
|
-
function AnchoredOverlay({ visible, rect, onRequestClose, children, placement = "bottom", offset = 4, matchAnchorWidth = false, estimatedHeight = 320, dismissOnPressOutside = true, className, testID }) {
|
|
39
|
+
function AnchoredOverlay({ visible, rect, onRequestClose, children, placement = "bottom", offset = 4, matchAnchorWidth = false, estimatedHeight = 320, align = "start", arrowClassName, dismissOnPressOutside = true, className, testID }) {
|
|
39
40
|
const { width: windowWidth, height: windowHeight } = useWindowDimensions();
|
|
41
|
+
const { mounted, progress } = useTransition(visible, {
|
|
42
|
+
duration: 150,
|
|
43
|
+
exitDuration: 100
|
|
44
|
+
});
|
|
45
|
+
const [contentWidth, setContentWidth] = useState(0);
|
|
40
46
|
if (!rect) return null;
|
|
41
47
|
const spaceBelow = windowHeight - (rect.y + rect.height);
|
|
42
48
|
const spaceAbove = rect.y;
|
|
43
49
|
const goTop = placement === "top" ? spaceAbove >= estimatedHeight || spaceAbove >= spaceBelow : spaceBelow < estimatedHeight && spaceAbove > spaceBelow;
|
|
44
|
-
const
|
|
50
|
+
const anchorCenter = rect.x + rect.width / 2;
|
|
51
|
+
const left = align === "center" ? Math.max(8, Math.min(anchorCenter - contentWidth / 2, windowWidth - 8 - contentWidth)) : Math.max(8, Math.min(rect.x, windowWidth - 8 - (matchAnchorWidth ? rect.width : 0)));
|
|
52
|
+
const measured = align !== "center" || contentWidth > 0;
|
|
53
|
+
const arrowLeft = Math.max(8, Math.min(anchorCenter - left, Math.max(contentWidth, 16) - 8)) - 3;
|
|
45
54
|
return /* @__PURE__ */ jsx(Modal, {
|
|
46
55
|
transparent: true,
|
|
47
|
-
visible,
|
|
48
|
-
animationType: "
|
|
56
|
+
visible: mounted,
|
|
57
|
+
animationType: "none",
|
|
49
58
|
onRequestClose,
|
|
50
59
|
statusBarTranslucent: true,
|
|
51
60
|
children: /* @__PURE__ */ jsx(Pressable, {
|
|
@@ -53,17 +62,33 @@ function AnchoredOverlay({ visible, rect, onRequestClose, children, placement =
|
|
|
53
62
|
className: "flex-1",
|
|
54
63
|
onPress: dismissOnPressOutside ? onRequestClose : void 0,
|
|
55
64
|
testID: testID ? `${testID}-backdrop` : void 0,
|
|
56
|
-
children: /* @__PURE__ */
|
|
57
|
-
testID,
|
|
58
|
-
className,
|
|
65
|
+
children: /* @__PURE__ */ jsxs(Animated.View, {
|
|
59
66
|
style: {
|
|
60
67
|
position: "absolute",
|
|
61
68
|
left,
|
|
62
69
|
...goTop ? { bottom: windowHeight - rect.y + offset } : { top: rect.y + rect.height + offset },
|
|
63
|
-
...matchAnchorWidth ? { width: rect.width } : null
|
|
70
|
+
...matchAnchorWidth ? { width: rect.width } : null,
|
|
71
|
+
opacity: measured ? progress : 0,
|
|
72
|
+
transform: [{ scale: progress.interpolate({
|
|
73
|
+
inputRange: [0, 1],
|
|
74
|
+
outputRange: [.96, 1]
|
|
75
|
+
}) }],
|
|
76
|
+
transformOrigin: goTop ? "bottom left" : "top left"
|
|
64
77
|
},
|
|
65
|
-
|
|
66
|
-
children
|
|
78
|
+
onLayout: (e) => align === "center" && setContentWidth(e.nativeEvent.layout.width),
|
|
79
|
+
children: [/* @__PURE__ */ jsx(View, {
|
|
80
|
+
testID,
|
|
81
|
+
className,
|
|
82
|
+
onStartShouldSetResponder: () => true,
|
|
83
|
+
children
|
|
84
|
+
}), arrowClassName && /* @__PURE__ */ jsx(View, {
|
|
85
|
+
pointerEvents: "none",
|
|
86
|
+
className: `absolute size-1.5 rotate-45 ${arrowClassName}`,
|
|
87
|
+
style: {
|
|
88
|
+
left: arrowLeft,
|
|
89
|
+
...goTop ? { bottom: -3 } : { top: -3 }
|
|
90
|
+
}
|
|
91
|
+
})]
|
|
67
92
|
})
|
|
68
93
|
})
|
|
69
94
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"anchored-overlay.js","names":[],"sources":["../../../src/components/_shared/anchored-overlay.tsx"],"sourcesContent":["/// <reference types=\"uniwind/types\" />\n\nimport { type ReactNode, type RefObject, useCallback, useRef, useState } from 'react'\nimport { Modal, Pressable, useWindowDimensions, View } from 'react-native'\n\nexport type Placement = 'bottom' | 'top'\ntype Rect = { x: number; y: number; width: number; height: number }\n\n/**\n * Medição de um gatilho para ancorar um overlay (Popover, Menu, Select, Combobox, DatePicker,\n * Tooltip). `open()` mede o gatilho na janela e só então abre.\n *\n * const { ref, rect, visible, open, close } = useAnchor()\n * <View ref={ref} collapsable={false}> ...gatilho... </View>\n * <AnchoredOverlay visible={visible} rect={rect} onRequestClose={close}> ... </AnchoredOverlay>\n */\nexport function useAnchor() {\n const ref = useRef<View>(null)\n const [rect, setRect] = useState<Rect | null>(null)\n const [visible, setVisible] = useState(false)\n\n const open = useCallback(() => {\n const node = ref.current\n if (!node) return\n node.measureInWindow((x, y, width, height) => {\n setRect({ x, y, width, height })\n setVisible(true)\n })\n }, [])\n const close = useCallback(() => setVisible(false), [])\n\n return { ref: ref as RefObject<View>, rect, visible, open, close }\n}\n\nexport type AnchoredOverlayProps = {\n visible: boolean\n rect: Rect | null\n onRequestClose: () => void\n children: ReactNode\n /** Lado preferido; inverte se não couber. Padrão: `bottom`. */\n placement?: Placement\n /** Distância do gatilho. Padrão: 4. */\n offset?: number\n /** Largura do conteúdo: `anchor` iguala à do gatilho (Select, Combobox). Padrão: automática. */\n matchAnchorWidth?: boolean\n /** Altura máxima estimada do conteúdo, para decidir a inversão. Padrão: 320. */\n estimatedHeight?: number\n /** Toque fora fecha. Padrão: true. */\n dismissOnPressOutside?: boolean\n className?: string\n testID?: string\n}\n\n/** Overlay transparente, posicionado junto ao gatilho, dentro de um Modal nativo (fica acima de tudo). */\nexport function AnchoredOverlay({\n visible,\n rect,\n onRequestClose,\n children,\n placement = 'bottom',\n offset = 4,\n matchAnchorWidth = false,\n estimatedHeight = 320,\n dismissOnPressOutside = true,\n className,\n testID,\n}: AnchoredOverlayProps) {\n const { width: windowWidth, height: windowHeight } = useWindowDimensions()\n if (!rect) return null\n\n const spaceBelow = windowHeight - (rect.y + rect.height)\n const spaceAbove = rect.y\n const wantsTop = placement === 'top'\n const goTop = wantsTop\n ? spaceAbove >= estimatedHeight || spaceAbove >= spaceBelow\n : spaceBelow < estimatedHeight && spaceAbove > spaceBelow\n\n const
|
|
1
|
+
{"version":3,"file":"anchored-overlay.js","names":[],"sources":["../../../src/components/_shared/anchored-overlay.tsx"],"sourcesContent":["/// <reference types=\"uniwind/types\" />\n\nimport { type ReactNode, type RefObject, useCallback, useRef, useState } from 'react'\nimport { Animated, Modal, Pressable, useWindowDimensions, View } from 'react-native'\nimport { useTransition } from './use-transition'\n\nexport type Placement = 'bottom' | 'top'\ntype Rect = { x: number; y: number; width: number; height: number }\n\n/**\n * Medição de um gatilho para ancorar um overlay (Popover, Menu, Select, Combobox, DatePicker,\n * Tooltip). `open()` mede o gatilho na janela e só então abre.\n *\n * const { ref, rect, visible, open, close } = useAnchor()\n * <View ref={ref} collapsable={false}> ...gatilho... </View>\n * <AnchoredOverlay visible={visible} rect={rect} onRequestClose={close}> ... </AnchoredOverlay>\n */\nexport function useAnchor() {\n const ref = useRef<View>(null)\n const [rect, setRect] = useState<Rect | null>(null)\n const [visible, setVisible] = useState(false)\n\n const open = useCallback(() => {\n const node = ref.current\n if (!node) return\n node.measureInWindow((x, y, width, height) => {\n setRect({ x, y, width, height })\n setVisible(true)\n })\n }, [])\n const close = useCallback(() => setVisible(false), [])\n\n return { ref: ref as RefObject<View>, rect, visible, open, close }\n}\n\nexport type AnchoredOverlayProps = {\n visible: boolean\n rect: Rect | null\n onRequestClose: () => void\n children: ReactNode\n /** Lado preferido; inverte se não couber. Padrão: `bottom`. */\n placement?: Placement\n /** Distância do gatilho. Padrão: 4. */\n offset?: number\n /** Largura do conteúdo: `anchor` iguala à do gatilho (Select, Combobox). Padrão: automática. */\n matchAnchorWidth?: boolean\n /** Altura máxima estimada do conteúdo, para decidir a inversão. Padrão: 320. */\n estimatedHeight?: number\n /** Alinhamento com o gatilho: `start` (borda esquerda) ou `center` (centrado, como o Tooltip). */\n align?: 'start' | 'center'\n /** Seta apontando para o gatilho (classes do desenho, ex.: a cor do fundo do conteúdo). */\n arrowClassName?: string\n /** Toque fora fecha. Padrão: true. */\n dismissOnPressOutside?: boolean\n className?: string\n testID?: string\n}\n\n/** Overlay transparente, posicionado junto ao gatilho, dentro de um Modal nativo (fica acima de tudo). */\nexport function AnchoredOverlay({\n visible,\n rect,\n onRequestClose,\n children,\n placement = 'bottom',\n offset = 4,\n matchAnchorWidth = false,\n estimatedHeight = 320,\n align = 'start',\n arrowClassName,\n dismissOnPressOutside = true,\n className,\n testID,\n}: AnchoredOverlayProps) {\n const { width: windowWidth, height: windowHeight } = useWindowDimensions()\n // Como no web: fade com zoom de 96% a partir do gatilho (o `Modal` nativo só fecha depois da saída)\n const { mounted, progress } = useTransition(visible, { duration: 150, exitDuration: 100 })\n // centrado precisa da largura do conteúdo: mede e só então mostra\n const [contentWidth, setContentWidth] = useState(0)\n if (!rect) return null\n\n const spaceBelow = windowHeight - (rect.y + rect.height)\n const spaceAbove = rect.y\n const wantsTop = placement === 'top'\n const goTop = wantsTop\n ? spaceAbove >= estimatedHeight || spaceAbove >= spaceBelow\n : spaceBelow < estimatedHeight && spaceAbove > spaceBelow\n\n const anchorCenter = rect.x + rect.width / 2\n const left =\n align === 'center'\n ? Math.max(8, Math.min(anchorCenter - contentWidth / 2, windowWidth - 8 - contentWidth))\n : Math.max(8, Math.min(rect.x, windowWidth - 8 - (matchAnchorWidth ? rect.width : 0)))\n const measured = align !== 'center' || contentWidth > 0\n // seta de 6px girada 45° (um losango de ~8px de largura e ~4px para fora), sobre a borda do conteúdo\n const arrowLeft = Math.max(8, Math.min(anchorCenter - left, Math.max(contentWidth, 16) - 8)) - 3\n\n return (\n <Modal\n transparent\n visible={mounted}\n animationType=\"none\"\n onRequestClose={onRequestClose}\n statusBarTranslucent\n >\n <Pressable\n accessible={false}\n className=\"flex-1\"\n onPress={dismissOnPressOutside ? onRequestClose : undefined}\n testID={testID ? `${testID}-backdrop` : undefined}\n >\n <Animated.View\n style={{\n position: 'absolute',\n left,\n ...(goTop\n ? { bottom: windowHeight - rect.y + offset }\n : { top: rect.y + rect.height + offset }),\n ...(matchAnchorWidth ? { width: rect.width } : null),\n opacity: measured ? progress : 0,\n transform: [\n { scale: progress.interpolate({ inputRange: [0, 1], outputRange: [0.96, 1] }) },\n ],\n transformOrigin: goTop ? 'bottom left' : 'top left',\n }}\n onLayout={(e) => align === 'center' && setContentWidth(e.nativeEvent.layout.width)}\n >\n <View\n testID={testID}\n className={className}\n // Toques dentro do conteúdo não chegam ao backdrop.\n onStartShouldSetResponder={() => true}\n >\n {children}\n </View>\n {arrowClassName && (\n <View\n pointerEvents=\"none\"\n className={`absolute size-1.5 rotate-45 ${arrowClassName}`}\n style={{ left: arrowLeft, ...(goTop ? { bottom: -3 } : { top: -3 }) }}\n />\n )}\n </Animated.View>\n </Pressable>\n </Modal>\n )\n}\n"],"mappings":";;;;;;;;;;;;;AAiBA,SAAgB,YAAY;CAC1B,MAAM,MAAM,OAAa,IAAI;CAC7B,MAAM,CAAC,MAAM,WAAW,SAAsB,IAAI;CAClD,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAY5C,OAAO;EAAO;EAAwB;EAAM;EAAS,MAVxC,kBAAkB;GAC7B,MAAM,OAAO,IAAI;GACjB,IAAI,CAAC,MAAM;GACX,KAAK,iBAAiB,GAAG,GAAG,OAAO,WAAW;IAC5C,QAAQ;KAAE;KAAG;KAAG;KAAO;IAAO,CAAC;IAC/B,WAAW,IAAI;GACjB,CAAC;EACH,GAAG,CAAC,CAGoD;EAAG,OAF7C,kBAAkB,WAAW,KAAK,GAAG,CAAC,CAEW;CAAE;AACnE;;AA0BA,SAAgB,gBAAgB,EAC9B,SACA,MACA,gBACA,UACA,YAAY,UACZ,SAAS,GACT,mBAAmB,OACnB,kBAAkB,KAClB,QAAQ,SACR,gBACA,wBAAwB,MACxB,WACA,UACuB;CACvB,MAAM,EAAE,OAAO,aAAa,QAAQ,iBAAiB,oBAAoB;CAEzE,MAAM,EAAE,SAAS,aAAa,cAAc,SAAS;EAAE,UAAU;EAAK,cAAc;CAAI,CAAC;CAEzF,MAAM,CAAC,cAAc,mBAAmB,SAAS,CAAC;CAClD,IAAI,CAAC,MAAM,OAAO;CAElB,MAAM,aAAa,gBAAgB,KAAK,IAAI,KAAK;CACjD,MAAM,aAAa,KAAK;CAExB,MAAM,QADW,cAAc,QAE3B,cAAc,mBAAmB,cAAc,aAC/C,aAAa,mBAAmB,aAAa;CAEjD,MAAM,eAAe,KAAK,IAAI,KAAK,QAAQ;CAC3C,MAAM,OACJ,UAAU,WACN,KAAK,IAAI,GAAG,KAAK,IAAI,eAAe,eAAe,GAAG,cAAc,IAAI,YAAY,CAAC,IACrF,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,GAAG,cAAc,KAAK,mBAAmB,KAAK,QAAQ,EAAE,CAAC;CACzF,MAAM,WAAW,UAAU,YAAY,eAAe;CAEtD,MAAM,YAAY,KAAK,IAAI,GAAG,KAAK,IAAI,eAAe,MAAM,KAAK,IAAI,cAAc,EAAE,IAAI,CAAC,CAAC,IAAI;CAE/F,OACE,oBAAC,OAAD;EACE,aAAA;EACA,SAAS;EACT,eAAc;EACE;EAChB,sBAAA;EAEA,UAAA,oBAAC,WAAD;GACE,YAAY;GACZ,WAAU;GACV,SAAS,wBAAwB,iBAAiB,KAAA;GAClD,QAAQ,SAAS,GAAG,OAAO,aAAa,KAAA;GAExC,UAAA,qBAAC,SAAS,MAAV;IACE,OAAO;KACL,UAAU;KACV;KACA,GAAI,QACA,EAAE,QAAQ,eAAe,KAAK,IAAI,OAAO,IACzC,EAAE,KAAK,KAAK,IAAI,KAAK,SAAS,OAAO;KACzC,GAAI,mBAAmB,EAAE,OAAO,KAAK,MAAM,IAAI;KAC/C,SAAS,WAAW,WAAW;KAC/B,WAAW,CACT,EAAE,OAAO,SAAS,YAAY;MAAE,YAAY,CAAC,GAAG,CAAC;MAAG,aAAa,CAAC,KAAM,CAAC;KAAE,CAAC,EAAE,CAChF;KACA,iBAAiB,QAAQ,gBAAgB;IAC3C;IACA,WAAW,MAAM,UAAU,YAAY,gBAAgB,EAAE,YAAY,OAAO,KAAK;IAdnF,UAAA,CAgBE,oBAAC,MAAD;KACU;KACG;KAEX,iCAAiC;KAEhC;IACG,CAAA,GACL,kBACC,oBAAC,MAAD;KACE,eAAc;KACd,WAAW,+BAA+B;KAC1C,OAAO;MAAE,MAAM;MAAW,GAAI,QAAQ,EAAE,QAAQ,GAAG,IAAI,EAAE,KAAK,GAAG;KAAG;IACrE,CAAA,CAEU;;EACN,CAAA;CACN,CAAA;AAEX"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { AccessibilityInfo, Animated, Easing, View } from "react-native";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
//#region src/components/_shared/sliding-indicator.tsx
|
|
5
|
+
/** Posições medidas (`onLayout`) dos itens de um grupo, por chave. */
|
|
6
|
+
function useItemLayouts() {
|
|
7
|
+
const [layouts, setLayouts] = useState({});
|
|
8
|
+
return {
|
|
9
|
+
layouts,
|
|
10
|
+
setLayout: useCallback((key, rect) => {
|
|
11
|
+
setLayouts((current) => {
|
|
12
|
+
const before = current[key];
|
|
13
|
+
return before && before.x === rect.x && before.y === rect.y && before.width === rect.width && before.height === rect.height ? current : {
|
|
14
|
+
...current,
|
|
15
|
+
[key]: rect
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
}, [])
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/** "Reduzir movimento" do sistema: o indicador pula direto em vez de deslizar. */
|
|
22
|
+
function useReducedMotion() {
|
|
23
|
+
const [reduced, setReduced] = useState(false);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
let alive = true;
|
|
26
|
+
AccessibilityInfo.isReduceMotionEnabled?.()?.then((value) => alive && value && setReduced(true));
|
|
27
|
+
const subscription = AccessibilityInfo.addEventListener?.("reduceMotionChanged", setReduced);
|
|
28
|
+
return () => {
|
|
29
|
+
alive = false;
|
|
30
|
+
subscription?.remove?.();
|
|
31
|
+
};
|
|
32
|
+
}, []);
|
|
33
|
+
return reduced;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Indicador único que desliza e se redimensiona até `target` (o sublinhado das Tabs, o pill do
|
|
37
|
+
* SegmentedControl). Fica no contêiner dos itens, com posição absoluta, e usa as medidas
|
|
38
|
+
* `onLayout` dos itens. Na primeira medição aparece direto no lugar, sem deslizar do canto.
|
|
39
|
+
* A animação é do lado do JS (posição e tamanho são `layout`, que o driver nativo não anima).
|
|
40
|
+
*/
|
|
41
|
+
function SlidingIndicator({ target, className }) {
|
|
42
|
+
const reduced = useReducedMotion();
|
|
43
|
+
const values = useRef(null);
|
|
44
|
+
if (target && !values.current) values.current = {
|
|
45
|
+
left: new Animated.Value(target.x),
|
|
46
|
+
top: new Animated.Value(target.y),
|
|
47
|
+
width: new Animated.Value(target.width),
|
|
48
|
+
height: new Animated.Value(target.height)
|
|
49
|
+
};
|
|
50
|
+
const x = target?.x;
|
|
51
|
+
const y = target?.y;
|
|
52
|
+
const width = target?.width;
|
|
53
|
+
const height = target?.height;
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
const current = values.current;
|
|
56
|
+
if (!current || x === void 0 || y === void 0 || !width || !height) return;
|
|
57
|
+
const to = {
|
|
58
|
+
left: x,
|
|
59
|
+
top: y,
|
|
60
|
+
width,
|
|
61
|
+
height
|
|
62
|
+
};
|
|
63
|
+
if (reduced) {
|
|
64
|
+
for (const key of Object.keys(to)) current[key].setValue(to[key]);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const animation = Animated.parallel(Object.keys(to).map((key) => Animated.timing(current[key], {
|
|
68
|
+
toValue: to[key],
|
|
69
|
+
duration: 200,
|
|
70
|
+
easing: Easing.out(Easing.cubic),
|
|
71
|
+
useNativeDriver: false
|
|
72
|
+
})));
|
|
73
|
+
animation.start();
|
|
74
|
+
return () => animation.stop();
|
|
75
|
+
}, [
|
|
76
|
+
x,
|
|
77
|
+
y,
|
|
78
|
+
width,
|
|
79
|
+
height,
|
|
80
|
+
reduced
|
|
81
|
+
]);
|
|
82
|
+
if (!target || !values.current) return null;
|
|
83
|
+
return /* @__PURE__ */ jsx(Animated.View, {
|
|
84
|
+
pointerEvents: "none",
|
|
85
|
+
style: {
|
|
86
|
+
position: "absolute",
|
|
87
|
+
...values.current
|
|
88
|
+
},
|
|
89
|
+
children: /* @__PURE__ */ jsx(View, {
|
|
90
|
+
className,
|
|
91
|
+
style: { flex: 1 }
|
|
92
|
+
})
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
export { SlidingIndicator, useItemLayouts, useReducedMotion };
|
|
97
|
+
|
|
98
|
+
//# sourceMappingURL=sliding-indicator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sliding-indicator.js","names":[],"sources":["../../../src/components/_shared/sliding-indicator.tsx"],"sourcesContent":["/// <reference types=\"uniwind/types\" />\n\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { AccessibilityInfo, Animated, Easing, View } from 'react-native'\n\nexport type Rect = { x: number; y: number; width: number; height: number }\n\n/** Posições medidas (`onLayout`) dos itens de um grupo, por chave. */\nexport function useItemLayouts() {\n const [layouts, setLayouts] = useState<Record<string, Rect>>({})\n const setLayout = useCallback((key: string, rect: Rect) => {\n setLayouts((current) => {\n const before = current[key]\n const same =\n before &&\n before.x === rect.x &&\n before.y === rect.y &&\n before.width === rect.width &&\n before.height === rect.height\n return same ? current : { ...current, [key]: rect }\n })\n }, [])\n return { layouts, setLayout }\n}\n\n/** \"Reduzir movimento\" do sistema: o indicador pula direto em vez de deslizar. */\nexport function useReducedMotion() {\n const [reduced, setReduced] = useState(false)\n useEffect(() => {\n let alive = true\n // só ligar quando o sistema pede (o padrão é `false`): evita um render à toa em todo overlay\n AccessibilityInfo.isReduceMotionEnabled?.()?.then((value) => alive && value && setReduced(true))\n const subscription = AccessibilityInfo.addEventListener?.('reduceMotionChanged', setReduced)\n return () => {\n alive = false\n subscription?.remove?.()\n }\n }, [])\n return reduced\n}\n\n/**\n * Indicador único que desliza e se redimensiona até `target` (o sublinhado das Tabs, o pill do\n * SegmentedControl). Fica no contêiner dos itens, com posição absoluta, e usa as medidas\n * `onLayout` dos itens. Na primeira medição aparece direto no lugar, sem deslizar do canto.\n * A animação é do lado do JS (posição e tamanho são `layout`, que o driver nativo não anima).\n */\nexport function SlidingIndicator({\n target,\n className,\n}: {\n target: Rect | undefined\n /** Classes do desenho (cor, raio, sombra) do indicador, que preenche o retângulo. */\n className?: string\n}) {\n const reduced = useReducedMotion()\n const values = useRef<Record<'left' | 'top' | 'width' | 'height', Animated.Value> | null>(null)\n if (target && !values.current) {\n values.current = {\n left: new Animated.Value(target.x),\n top: new Animated.Value(target.y),\n width: new Animated.Value(target.width),\n height: new Animated.Value(target.height),\n }\n }\n\n const x = target?.x\n const y = target?.y\n const width = target?.width\n const height = target?.height\n useEffect(() => {\n const current = values.current\n if (!current || x === undefined || y === undefined || !width || !height) return\n const to = { left: x, top: y, width, height }\n if (reduced) {\n for (const key of Object.keys(to) as (keyof typeof to)[]) current[key].setValue(to[key])\n return\n }\n const animation = Animated.parallel(\n (Object.keys(to) as (keyof typeof to)[]).map((key) =>\n Animated.timing(current[key], {\n toValue: to[key],\n duration: 200,\n easing: Easing.out(Easing.cubic),\n useNativeDriver: false,\n }),\n ),\n )\n animation.start()\n return () => animation.stop()\n }, [x, y, width, height, reduced])\n\n if (!target || !values.current) return null\n return (\n <Animated.View pointerEvents=\"none\" style={{ position: 'absolute', ...values.current }}>\n <View className={className} style={{ flex: 1 }} />\n </Animated.View>\n )\n}\n"],"mappings":";;;;;AAQA,SAAgB,iBAAiB;CAC/B,MAAM,CAAC,SAAS,cAAc,SAA+B,CAAC,CAAC;CAa/D,OAAO;EAAE;EAAS,WAZA,aAAa,KAAa,SAAe;GACzD,YAAY,YAAY;IACtB,MAAM,SAAS,QAAQ;IAOvB,OALE,UACA,OAAO,MAAM,KAAK,KAClB,OAAO,MAAM,KAAK,KAClB,OAAO,UAAU,KAAK,SACtB,OAAO,WAAW,KAAK,SACX,UAAU;KAAE,GAAG;MAAU,MAAM;IAAK;GACpD,CAAC;EACH,GAAG,CAAC,CACsB;CAAE;AAC9B;;AAGA,SAAgB,mBAAmB;CACjC,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAC5C,gBAAgB;EACd,IAAI,QAAQ;EAEZ,kBAAkB,wBAAwB,CAAC,EAAE,MAAM,UAAU,SAAS,SAAS,WAAW,IAAI,CAAC;EAC/F,MAAM,eAAe,kBAAkB,mBAAmB,uBAAuB,UAAU;EAC3F,aAAa;GACX,QAAQ;GACR,cAAc,SAAS;EACzB;CACF,GAAG,CAAC,CAAC;CACL,OAAO;AACT;;;;;;;AAQA,SAAgB,iBAAiB,EAC/B,QACA,aAKC;CACD,MAAM,UAAU,iBAAiB;CACjC,MAAM,SAAS,OAA2E,IAAI;CAC9F,IAAI,UAAU,CAAC,OAAO,SACpB,OAAO,UAAU;EACf,MAAM,IAAI,SAAS,MAAM,OAAO,CAAC;EACjC,KAAK,IAAI,SAAS,MAAM,OAAO,CAAC;EAChC,OAAO,IAAI,SAAS,MAAM,OAAO,KAAK;EACtC,QAAQ,IAAI,SAAS,MAAM,OAAO,MAAM;CAC1C;CAGF,MAAM,IAAI,QAAQ;CAClB,MAAM,IAAI,QAAQ;CAClB,MAAM,QAAQ,QAAQ;CACtB,MAAM,SAAS,QAAQ;CACvB,gBAAgB;EACd,MAAM,UAAU,OAAO;EACvB,IAAI,CAAC,WAAW,MAAM,KAAA,KAAa,MAAM,KAAA,KAAa,CAAC,SAAS,CAAC,QAAQ;EACzE,MAAM,KAAK;GAAE,MAAM;GAAG,KAAK;GAAG;GAAO;EAAO;EAC5C,IAAI,SAAS;GACX,KAAK,MAAM,OAAO,OAAO,KAAK,EAAE,GAA0B,QAAQ,IAAI,CAAC,SAAS,GAAG,IAAI;GACvF;EACF;EACA,MAAM,YAAY,SAAS,SACxB,OAAO,KAAK,EAAE,CAAC,CAAyB,KAAK,QAC5C,SAAS,OAAO,QAAQ,MAAM;GAC5B,SAAS,GAAG;GACZ,UAAU;GACV,QAAQ,OAAO,IAAI,OAAO,KAAK;GAC/B,iBAAiB;EACnB,CAAC,CACH,CACF;EACA,UAAU,MAAM;EAChB,aAAa,UAAU,KAAK;CAC9B,GAAG;EAAC;EAAG;EAAG;EAAO;EAAQ;CAAO,CAAC;CAEjC,IAAI,CAAC,UAAU,CAAC,OAAO,SAAS,OAAO;CACvC,OACE,oBAAC,SAAS,MAAV;EAAe,eAAc;EAAO,OAAO;GAAE,UAAU;GAAY,GAAG,OAAO;EAAQ;EACnF,UAAA,oBAAC,MAAD;GAAiB;GAAW,OAAO,EAAE,MAAM,EAAE;EAAI,CAAA;CACpC,CAAA;AAEnB"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { useReducedMotion } from "./sliding-indicator.js";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { Animated } from "react-native";
|
|
4
|
+
//#region src/components/_shared/use-transition.ts
|
|
5
|
+
/**
|
|
6
|
+
* Entrada e saída de um overlay: `progress` vai de 0 a 1 (nativo, sem passar pelo JS) e `mounted`
|
|
7
|
+
* mantém o conteúdo na tela até a saída terminar (o `Modal` nativo só é fechado depois). Com
|
|
8
|
+
* "Reduzir movimento" ligado no sistema, abre e fecha direto.
|
|
9
|
+
*/
|
|
10
|
+
function useTransition(isOpen, { duration = 200, exitDuration = 150, onClosed } = {}) {
|
|
11
|
+
const reduced = useReducedMotion();
|
|
12
|
+
const [mounted, setMounted] = useState(isOpen);
|
|
13
|
+
const progress = useRef(new Animated.Value(isOpen ? 1 : 0)).current;
|
|
14
|
+
const closed = useRef(onClosed);
|
|
15
|
+
closed.current = onClosed;
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (isOpen) setMounted(true);
|
|
18
|
+
if (reduced) {
|
|
19
|
+
progress.setValue(isOpen ? 1 : 0);
|
|
20
|
+
if (!isOpen) {
|
|
21
|
+
setMounted(false);
|
|
22
|
+
closed.current?.();
|
|
23
|
+
}
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const animation = Animated.timing(progress, {
|
|
27
|
+
toValue: isOpen ? 1 : 0,
|
|
28
|
+
duration: isOpen ? duration : exitDuration,
|
|
29
|
+
useNativeDriver: true
|
|
30
|
+
});
|
|
31
|
+
animation.start(({ finished }) => {
|
|
32
|
+
if (finished && !isOpen) {
|
|
33
|
+
setMounted(false);
|
|
34
|
+
closed.current?.();
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
return () => animation.stop();
|
|
38
|
+
}, [
|
|
39
|
+
isOpen,
|
|
40
|
+
reduced,
|
|
41
|
+
duration,
|
|
42
|
+
exitDuration,
|
|
43
|
+
progress
|
|
44
|
+
]);
|
|
45
|
+
return {
|
|
46
|
+
mounted,
|
|
47
|
+
progress
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { useTransition };
|
|
52
|
+
|
|
53
|
+
//# sourceMappingURL=use-transition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-transition.js","names":[],"sources":["../../../src/components/_shared/use-transition.ts"],"sourcesContent":["import { useEffect, useRef, useState } from 'react'\nimport { Animated } from 'react-native'\nimport { useReducedMotion } from './sliding-indicator'\n\ntype Options = {\n /** Duração da entrada (ms). */\n duration?: number\n /** Duração da saída (ms). */\n exitDuration?: number\n /** Chamado quando a saída termina e o conteúdo é desmontado. */\n onClosed?: () => void\n}\n\n/**\n * Entrada e saída de um overlay: `progress` vai de 0 a 1 (nativo, sem passar pelo JS) e `mounted`\n * mantém o conteúdo na tela até a saída terminar (o `Modal` nativo só é fechado depois). Com\n * \"Reduzir movimento\" ligado no sistema, abre e fecha direto.\n */\nexport function useTransition(\n isOpen: boolean,\n { duration = 200, exitDuration = 150, onClosed }: Options = {},\n) {\n const reduced = useReducedMotion()\n const [mounted, setMounted] = useState(isOpen)\n const progress = useRef(new Animated.Value(isOpen ? 1 : 0)).current\n const closed = useRef(onClosed)\n closed.current = onClosed\n\n useEffect(() => {\n if (isOpen) setMounted(true)\n if (reduced) {\n progress.setValue(isOpen ? 1 : 0)\n if (!isOpen) {\n setMounted(false)\n closed.current?.()\n }\n return\n }\n const animation = Animated.timing(progress, {\n toValue: isOpen ? 1 : 0,\n duration: isOpen ? duration : exitDuration,\n useNativeDriver: true,\n })\n animation.start(({ finished }) => {\n if (finished && !isOpen) {\n setMounted(false)\n closed.current?.()\n }\n })\n return () => animation.stop()\n }, [isOpen, reduced, duration, exitDuration, progress])\n\n return { mounted, progress }\n}\n"],"mappings":";;;;;;;;;AAkBA,SAAgB,cACd,QACA,EAAE,WAAW,KAAK,eAAe,KAAK,aAAsB,CAAC,GAC7D;CACA,MAAM,UAAU,iBAAiB;CACjC,MAAM,CAAC,SAAS,cAAc,SAAS,MAAM;CAC7C,MAAM,WAAW,OAAO,IAAI,SAAS,MAAM,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC;CAC5D,MAAM,SAAS,OAAO,QAAQ;CAC9B,OAAO,UAAU;CAEjB,gBAAgB;EACd,IAAI,QAAQ,WAAW,IAAI;EAC3B,IAAI,SAAS;GACX,SAAS,SAAS,SAAS,IAAI,CAAC;GAChC,IAAI,CAAC,QAAQ;IACX,WAAW,KAAK;IAChB,OAAO,UAAU;GACnB;GACA;EACF;EACA,MAAM,YAAY,SAAS,OAAO,UAAU;GAC1C,SAAS,SAAS,IAAI;GACtB,UAAU,SAAS,WAAW;GAC9B,iBAAiB;EACnB,CAAC;EACD,UAAU,OAAO,EAAE,eAAe;GAChC,IAAI,YAAY,CAAC,QAAQ;IACvB,WAAW,KAAK;IAChB,OAAO,UAAU;GACnB;EACF,CAAC;EACD,aAAa,UAAU,KAAK;CAC9B,GAAG;EAAC;EAAQ;EAAS;EAAU;EAAc;CAAQ,CAAC;CAEtD,OAAO;EAAE;EAAS;CAAS;AAC7B"}
|
|
@@ -16,6 +16,16 @@ export type DrawerProps = VariantProps<typeof drawerVariants> & {
|
|
|
16
16
|
footer?: ReactNode;
|
|
17
17
|
/** Elemento que abre o painel (ex.: um `<Button>`); recebe `onPress`. Dispensável no modo controlado. */
|
|
18
18
|
trigger?: ReactNode;
|
|
19
|
+
/**
|
|
20
|
+
* Arrastar o painel para a borda de onde ele veio fecha. Não toma o gesto da rolagem do corpo
|
|
21
|
+
* (só arrasta com o corpo no começo, no `top`/`bottom`) e o backdrop clareia junto. Padrão: `true`.
|
|
22
|
+
*/
|
|
23
|
+
swipeToClose?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Barrinha de arraste na borda do painel. Só aparece em `top` e `bottom`, e só com
|
|
26
|
+
* `swipeToClose`. Padrão: `true`.
|
|
27
|
+
*/
|
|
28
|
+
showHandle?: boolean;
|
|
19
29
|
/** Nome acessível do botão de fechar (×). */
|
|
20
30
|
closeLabel?: string;
|
|
21
31
|
className?: string;
|
|
@@ -25,7 +35,7 @@ export type DrawerProps = VariantProps<typeof drawerVariants> & {
|
|
|
25
35
|
* Painel colado a uma borda da tela, que desliza ao abrir e fechar. Usa o `Modal` nativo (botão
|
|
26
36
|
* voltar do Android e toque no fundo fecham). Não há foco preso nem `Esc`.
|
|
27
37
|
*/
|
|
28
|
-
export declare function Drawer({ open, defaultOpen, onOpenChange, title, children, footer, trigger, closeLabel, side, className, testID }: DrawerProps): import("react").JSX.Element;
|
|
38
|
+
export declare function Drawer({ open, defaultOpen, onOpenChange, title, children, footer, trigger, closeLabel, swipeToClose, showHandle, side, className, testID }: DrawerProps): import("react").JSX.Element;
|
|
29
39
|
/** Fecha o painel ao pressionar. Envolva um `<Button>` do rodapé (injeta `onPress`, como o `asChild`). */
|
|
30
40
|
export declare function DrawerClose({ children }: {
|
|
31
41
|
children: ReactNode;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { cn } from "../../utils/cn.js";
|
|
2
2
|
import { CloseIcon } from "../_shared/status.js";
|
|
3
3
|
import { useToken } from "../_shared/use-token.js";
|
|
4
|
+
import { useTransition } from "../_shared/use-transition.js";
|
|
4
5
|
import { Backdrop } from "../_shared/backdrop.js";
|
|
5
6
|
import { injectHandler, useOpenState } from "../_shared/overlay-state.js";
|
|
6
|
-
import {
|
|
7
|
+
import { useSwipeToClose } from "./use-swipe-to-close.js";
|
|
8
|
+
import { createContext, useContext, useRef } from "react";
|
|
7
9
|
import { Animated, Modal, Pressable, ScrollView, Text, View, useWindowDimensions } from "react-native";
|
|
8
10
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
9
11
|
import { cva } from "class-variance-authority";
|
|
@@ -31,7 +33,7 @@ const CloseContext = createContext(null);
|
|
|
31
33
|
* Painel colado a uma borda da tela, que desliza ao abrir e fechar. Usa o `Modal` nativo (botão
|
|
32
34
|
* voltar do Android e toque no fundo fecham). Não há foco preso nem `Esc`.
|
|
33
35
|
*/
|
|
34
|
-
function Drawer({ open, defaultOpen, onOpenChange, title, children, footer, trigger, closeLabel = "Fechar", side, className, testID }) {
|
|
36
|
+
function Drawer({ open, defaultOpen, onOpenChange, title, children, footer, trigger, closeLabel = "Fechar", swipeToClose = true, showHandle = true, side, className, testID }) {
|
|
35
37
|
const [isOpen, setOpen] = useOpenState({
|
|
36
38
|
open,
|
|
37
39
|
defaultOpen,
|
|
@@ -42,25 +44,32 @@ function Drawer({ open, defaultOpen, onOpenChange, title, children, footer, trig
|
|
|
42
44
|
const { width, height } = useWindowDimensions();
|
|
43
45
|
const placement = side ?? "right";
|
|
44
46
|
const horizontal = placement === "left" || placement === "right";
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
47
|
+
const isOpenRef = useRef(isOpen);
|
|
48
|
+
isOpenRef.current = isOpen;
|
|
49
|
+
const swipe = useSwipeToClose({
|
|
50
|
+
side: placement,
|
|
51
|
+
enabled: swipeToClose,
|
|
52
|
+
fallbackSize: horizontal ? width : height,
|
|
53
|
+
onClose: close,
|
|
54
|
+
isOpen: () => isOpenRef.current
|
|
55
|
+
});
|
|
56
|
+
const hasHandle = showHandle && swipeToClose && !horizontal;
|
|
57
|
+
const { mounted, progress } = useTransition(isOpen, {
|
|
58
|
+
duration: 250,
|
|
59
|
+
exitDuration: 250,
|
|
60
|
+
onClosed: swipe.resetDrag
|
|
61
|
+
});
|
|
59
62
|
const offset = horizontal ? width : height;
|
|
60
63
|
const sign = placement === "right" || placement === "bottom" ? 1 : -1;
|
|
61
|
-
const translate = progress.interpolate({
|
|
64
|
+
const translate = Animated.add(progress.interpolate({
|
|
62
65
|
inputRange: [0, 1],
|
|
63
66
|
outputRange: [sign * offset, 0]
|
|
67
|
+
}), Animated.multiply(swipe.drag, sign));
|
|
68
|
+
const handle = hasHandle && /* @__PURE__ */ jsx(View, {
|
|
69
|
+
accessibilityElementsHidden: true,
|
|
70
|
+
importantForAccessibility: "no-hide-descendants",
|
|
71
|
+
className: "h-6 shrink-0 items-center justify-center",
|
|
72
|
+
children: /* @__PURE__ */ jsx(View, { className: "h-1 w-10 rounded-lg bg-border-strong" })
|
|
64
73
|
});
|
|
65
74
|
return /* @__PURE__ */ jsxs(Fragment$1, { children: [injectHandler(trigger, "onPress", () => setOpen(true)), /* @__PURE__ */ jsx(Modal, {
|
|
66
75
|
transparent: true,
|
|
@@ -81,13 +90,16 @@ function Drawer({ open, defaultOpen, onOpenChange, title, children, footer, trig
|
|
|
81
90
|
left: 0,
|
|
82
91
|
right: 0,
|
|
83
92
|
bottom: 0,
|
|
84
|
-
opacity: progress
|
|
93
|
+
opacity: Animated.multiply(progress, swipe.dragFade)
|
|
85
94
|
},
|
|
86
95
|
children: /* @__PURE__ */ jsx(Backdrop, {
|
|
87
96
|
onPress: close,
|
|
88
97
|
testID: testID ? `${testID}-backdrop` : void 0
|
|
89
98
|
})
|
|
90
99
|
}), /* @__PURE__ */ jsx(Animated.View, {
|
|
100
|
+
...swipe.panHandlers,
|
|
101
|
+
onLayout: swipe.onPanelLayout,
|
|
102
|
+
testID: testID ? `${testID}-sheet` : void 0,
|
|
91
103
|
style: [horizontal ? {
|
|
92
104
|
width: "85%",
|
|
93
105
|
maxWidth: 400
|
|
@@ -99,8 +111,9 @@ function Drawer({ open, defaultOpen, onOpenChange, title, children, footer, trig
|
|
|
99
111
|
testID,
|
|
100
112
|
className: cn(drawerVariants({ side }), horizontal && "flex-1", className),
|
|
101
113
|
children: [
|
|
114
|
+
placement === "bottom" && handle,
|
|
102
115
|
/* @__PURE__ */ jsxs(View, {
|
|
103
|
-
className: "flex-row items-center gap-4 border-b border-border-default px-6 py-4",
|
|
116
|
+
className: cn("flex-row items-center gap-4 border-b border-border-default px-6 py-4", hasHandle && placement === "bottom" && "pt-0"),
|
|
104
117
|
children: [/* @__PURE__ */ jsx(Text, {
|
|
105
118
|
accessibilityRole: "header",
|
|
106
119
|
className: "flex-1 text-lg font-semibold leading-6 text-text-primary",
|
|
@@ -116,16 +129,18 @@ function Drawer({ open, defaultOpen, onOpenChange, title, children, footer, trig
|
|
|
116
129
|
}),
|
|
117
130
|
/* @__PURE__ */ jsx(ScrollView, {
|
|
118
131
|
className: "shrink",
|
|
119
|
-
contentContainerClassName: "gap-4 p-6",
|
|
132
|
+
contentContainerClassName: cn("gap-4 p-6", hasHandle && placement === "top" && !footer && "pb-2"),
|
|
133
|
+
...swipe.scrollProps,
|
|
120
134
|
children: typeof children === "string" ? /* @__PURE__ */ jsx(Text, {
|
|
121
135
|
className: "text-sm leading-5 text-text-secondary",
|
|
122
136
|
children
|
|
123
137
|
}) : children
|
|
124
138
|
}),
|
|
125
139
|
footer && /* @__PURE__ */ jsx(View, {
|
|
126
|
-
className: "flex-row items-center justify-end gap-2 border-t border-border-default px-6 py-4",
|
|
140
|
+
className: cn("flex-row items-center justify-end gap-2 border-t border-border-default px-6 py-4", hasHandle && placement === "top" && "pb-0"),
|
|
127
141
|
children: footer
|
|
128
|
-
})
|
|
142
|
+
}),
|
|
143
|
+
placement === "top" && handle
|
|
129
144
|
]
|
|
130
145
|
})
|
|
131
146
|
})]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drawer.js","names":["RNModal"],"sources":["../../../src/components/drawer/drawer.tsx"],"sourcesContent":["/// <reference types=\"uniwind/types\" />\n\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport { createContext, type ReactNode, useContext, useEffect, useRef, useState } from 'react'\nimport {\n Animated,\n Pressable,\n Modal as RNModal,\n ScrollView,\n Text,\n useWindowDimensions,\n View,\n} from 'react-native'\nimport { cn } from '../../utils/cn'\nimport { Backdrop } from '../_shared/backdrop'\nimport { injectHandler, useOpenState } from '../_shared/overlay-state'\nimport { CloseIcon } from '../_shared/status'\nimport { useToken } from '../_shared/use-token'\n\nconst drawerVariants = cva('overflow-hidden border-border-default bg-surface-elevated shadow-2xl', {\n variants: {\n side: {\n right: 'border-l',\n left: 'border-r',\n top: 'border-b',\n bottom: 'border-t',\n },\n },\n defaultVariants: { side: 'right' },\n})\n\nconst containerVariants = cva('flex-1', {\n variants: {\n side: {\n right: 'flex-row justify-end',\n left: 'flex-row justify-start',\n top: 'justify-start',\n bottom: 'justify-end',\n },\n },\n defaultVariants: { side: 'right' },\n})\n\nconst CloseContext = createContext<(() => void) | null>(null)\n\ntype Side = 'right' | 'left' | 'top' | 'bottom'\n\nexport type DrawerProps = VariantProps<typeof drawerVariants> & {\n open?: boolean\n defaultOpen?: boolean\n onOpenChange?: (open: boolean) => void\n /** Título do painel; também é o nome acessível dele. */\n title: ReactNode\n /** O corpo do painel. */\n children: ReactNode\n /** Ações do rodapé (primária à direita). Use `DrawerClose` para fechar. Sem ele, não há rodapé. */\n footer?: ReactNode\n /** Elemento que abre o painel (ex.: um `<Button>`); recebe `onPress`. Dispensável no modo controlado. */\n trigger?: ReactNode\n /** Nome acessível do botão de fechar (×). */\n closeLabel?: string\n className?: string\n testID?: string\n}\n\n/**\n * Painel colado a uma borda da tela, que desliza ao abrir e fechar. Usa o `Modal` nativo (botão\n * voltar do Android e toque no fundo fecham). Não há foco preso nem `Esc`.\n */\nexport function Drawer({\n open,\n defaultOpen,\n onOpenChange,\n title,\n children,\n footer,\n trigger,\n closeLabel = 'Fechar',\n side,\n className,\n testID,\n}: DrawerProps) {\n const [isOpen, setOpen] = useOpenState({ open, defaultOpen, onOpenChange })\n const close = () => setOpen(false)\n const iconColor = useToken('--color-text-primary')\n const { width, height } = useWindowDimensions()\n const placement: Side = side ?? 'right'\n const horizontal = placement === 'left' || placement === 'right'\n\n // O Modal fica montado durante a animação de saída.\n const [mounted, setMounted] = useState(isOpen)\n const progress = useRef(new Animated.Value(isOpen ? 1 : 0)).current\n\n useEffect(() => {\n if (isOpen) setMounted(true)\n const animation = Animated.timing(progress, {\n toValue: isOpen ? 1 : 0,\n duration: 250,\n useNativeDriver: true,\n })\n animation.start(({ finished }) => {\n if (finished && !isOpen) setMounted(false)\n })\n return () => animation.stop()\n }, [isOpen, progress])\n\n const offset = horizontal ? width : height\n const sign = placement === 'right' || placement === 'bottom' ? 1 : -1\n const translate = progress.interpolate({ inputRange: [0, 1], outputRange: [sign * offset, 0] })\n\n return (\n <>\n {injectHandler(trigger, 'onPress', () => setOpen(true))}\n <RNModal\n transparent\n statusBarTranslucent\n animationType=\"none\"\n visible={mounted}\n onRequestClose={close}\n >\n <CloseContext.Provider value={close}>\n <View className={containerVariants({ side })} accessibilityViewIsModal>\n <Animated.View\n pointerEvents=\"box-none\"\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n opacity: progress,\n }}\n >\n <Backdrop onPress={close} testID={testID ? `${testID}-backdrop` : undefined} />\n </Animated.View>\n <Animated.View\n style={[\n horizontal ? { width: '85%', maxWidth: 400 } : { width: '100%', maxHeight: '80%' },\n { transform: [horizontal ? { translateX: translate } : { translateY: translate }] },\n ]}\n >\n <View\n testID={testID}\n className={cn(drawerVariants({ side }), horizontal && 'flex-1', className)}\n >\n <View className=\"flex-row items-center gap-4 border-b border-border-default px-6 py-4\">\n <Text\n accessibilityRole=\"header\"\n className=\"flex-1 text-lg font-semibold leading-6 text-text-primary\"\n >\n {title}\n </Text>\n <Pressable\n accessibilityRole=\"button\"\n accessibilityLabel={closeLabel}\n hitSlop={8}\n onPress={close}\n className=\"size-8 items-center justify-center rounded-full active:bg-button-secondary-bg-hover\"\n >\n <CloseIcon color={iconColor} />\n </Pressable>\n </View>\n <ScrollView className=\"shrink\" contentContainerClassName=\"gap-4 p-6\">\n {typeof children === 'string' ? (\n <Text className=\"text-sm leading-5 text-text-secondary\">{children}</Text>\n ) : (\n children\n )}\n </ScrollView>\n {footer && (\n <View className=\"flex-row items-center justify-end gap-2 border-t border-border-default px-6 py-4\">\n {footer}\n </View>\n )}\n </View>\n </Animated.View>\n </View>\n </CloseContext.Provider>\n </RNModal>\n </>\n )\n}\n\n/** Fecha o painel ao pressionar. Envolva um `<Button>` do rodapé (injeta `onPress`, como o `asChild`). */\nexport function DrawerClose({ children }: { children: ReactNode }) {\n const close = useContext(CloseContext)\n return <>{injectHandler(children, 'onPress', () => close?.())}</>\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"drawer.js","names":["RNModal"],"sources":["../../../src/components/drawer/drawer.tsx"],"sourcesContent":["/// <reference types=\"uniwind/types\" />\n\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport { createContext, type ReactNode, useContext, useRef } from 'react'\nimport {\n Animated,\n Pressable,\n Modal as RNModal,\n ScrollView,\n Text,\n useWindowDimensions,\n View,\n} from 'react-native'\nimport { cn } from '../../utils/cn'\nimport { Backdrop } from '../_shared/backdrop'\nimport { injectHandler, useOpenState } from '../_shared/overlay-state'\nimport { CloseIcon } from '../_shared/status'\nimport { useToken } from '../_shared/use-token'\nimport { useTransition } from '../_shared/use-transition'\nimport { useSwipeToClose } from './use-swipe-to-close'\n\nconst drawerVariants = cva('overflow-hidden border-border-default bg-surface-elevated shadow-2xl', {\n variants: {\n side: {\n right: 'border-l',\n left: 'border-r',\n top: 'border-b',\n bottom: 'border-t',\n },\n },\n defaultVariants: { side: 'right' },\n})\n\nconst containerVariants = cva('flex-1', {\n variants: {\n side: {\n right: 'flex-row justify-end',\n left: 'flex-row justify-start',\n top: 'justify-start',\n bottom: 'justify-end',\n },\n },\n defaultVariants: { side: 'right' },\n})\n\nconst CloseContext = createContext<(() => void) | null>(null)\n\ntype Side = 'right' | 'left' | 'top' | 'bottom'\n\nexport type DrawerProps = VariantProps<typeof drawerVariants> & {\n open?: boolean\n defaultOpen?: boolean\n onOpenChange?: (open: boolean) => void\n /** Título do painel; também é o nome acessível dele. */\n title: ReactNode\n /** O corpo do painel. */\n children: ReactNode\n /** Ações do rodapé (primária à direita). Use `DrawerClose` para fechar. Sem ele, não há rodapé. */\n footer?: ReactNode\n /** Elemento que abre o painel (ex.: um `<Button>`); recebe `onPress`. Dispensável no modo controlado. */\n trigger?: ReactNode\n /**\n * Arrastar o painel para a borda de onde ele veio fecha. Não toma o gesto da rolagem do corpo\n * (só arrasta com o corpo no começo, no `top`/`bottom`) e o backdrop clareia junto. Padrão: `true`.\n */\n swipeToClose?: boolean\n /**\n * Barrinha de arraste na borda do painel. Só aparece em `top` e `bottom`, e só com\n * `swipeToClose`. Padrão: `true`.\n */\n showHandle?: boolean\n /** Nome acessível do botão de fechar (×). */\n closeLabel?: string\n className?: string\n testID?: string\n}\n\n/**\n * Painel colado a uma borda da tela, que desliza ao abrir e fechar. Usa o `Modal` nativo (botão\n * voltar do Android e toque no fundo fecham). Não há foco preso nem `Esc`.\n */\nexport function Drawer({\n open,\n defaultOpen,\n onOpenChange,\n title,\n children,\n footer,\n trigger,\n closeLabel = 'Fechar',\n swipeToClose = true,\n showHandle = true,\n side,\n className,\n testID,\n}: DrawerProps) {\n const [isOpen, setOpen] = useOpenState({ open, defaultOpen, onOpenChange })\n const close = () => setOpen(false)\n const iconColor = useToken('--color-text-primary')\n const { width, height } = useWindowDimensions()\n const placement: Side = side ?? 'right'\n const horizontal = placement === 'left' || placement === 'right'\n const isOpenRef = useRef(isOpen)\n isOpenRef.current = isOpen\n const swipe = useSwipeToClose({\n side: placement,\n enabled: swipeToClose,\n fallbackSize: horizontal ? width : height,\n onClose: close,\n isOpen: () => isOpenRef.current,\n })\n const hasHandle = showHandle && swipeToClose && !horizontal\n\n // O Modal fica montado durante a animação de saída; o arraste zera quando ele desmonta.\n const { mounted, progress } = useTransition(isOpen, {\n duration: 250,\n exitDuration: 250,\n onClosed: swipe.resetDrag,\n })\n\n const offset = horizontal ? width : height\n const sign = placement === 'right' || placement === 'bottom' ? 1 : -1\n // abrir/fechar + o que o dedo arrastou (`drag` vai no sentido de fechar)\n const translate = Animated.add(\n progress.interpolate({ inputRange: [0, 1], outputRange: [sign * offset, 0] }),\n Animated.multiply(swipe.drag, sign),\n )\n const handle = hasHandle && (\n // Figma \"Drawer/navigation\": traço de 4px em `border/strong` numa faixa de 24px (variante xl, 40px)\n <View\n accessibilityElementsHidden\n importantForAccessibility=\"no-hide-descendants\"\n className=\"h-6 shrink-0 items-center justify-center\"\n >\n <View className=\"h-1 w-10 rounded-lg bg-border-strong\" />\n </View>\n )\n\n return (\n <>\n {injectHandler(trigger, 'onPress', () => setOpen(true))}\n <RNModal\n transparent\n statusBarTranslucent\n animationType=\"none\"\n visible={mounted}\n onRequestClose={close}\n >\n <CloseContext.Provider value={close}>\n <View className={containerVariants({ side })} accessibilityViewIsModal>\n <Animated.View\n pointerEvents=\"box-none\"\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n opacity: Animated.multiply(progress, swipe.dragFade),\n }}\n >\n <Backdrop onPress={close} testID={testID ? `${testID}-backdrop` : undefined} />\n </Animated.View>\n <Animated.View\n {...swipe.panHandlers}\n onLayout={swipe.onPanelLayout}\n testID={testID ? `${testID}-sheet` : undefined}\n style={[\n horizontal ? { width: '85%', maxWidth: 400 } : { width: '100%', maxHeight: '80%' },\n { transform: [horizontal ? { translateX: translate } : { translateY: translate }] },\n ]}\n >\n <View\n testID={testID}\n className={cn(drawerVariants({ side }), horizontal && 'flex-1', className)}\n >\n {placement === 'bottom' && handle}\n {/* ao lado da barrinha, o conteúdo vizinho encosta na faixa de 24px (sem somar o padding) */}\n <View\n className={cn(\n 'flex-row items-center gap-4 border-b border-border-default px-6 py-4',\n hasHandle && placement === 'bottom' && 'pt-0',\n )}\n >\n <Text\n accessibilityRole=\"header\"\n className=\"flex-1 text-lg font-semibold leading-6 text-text-primary\"\n >\n {title}\n </Text>\n <Pressable\n accessibilityRole=\"button\"\n accessibilityLabel={closeLabel}\n hitSlop={8}\n onPress={close}\n className=\"size-8 items-center justify-center rounded-full active:bg-button-secondary-bg-hover\"\n >\n <CloseIcon color={iconColor} />\n </Pressable>\n </View>\n <ScrollView\n className=\"shrink\"\n contentContainerClassName={cn(\n 'gap-4 p-6',\n hasHandle && placement === 'top' && !footer && 'pb-2',\n )}\n {...swipe.scrollProps}\n >\n {typeof children === 'string' ? (\n <Text className=\"text-sm leading-5 text-text-secondary\">{children}</Text>\n ) : (\n children\n )}\n </ScrollView>\n {footer && (\n <View\n className={cn(\n 'flex-row items-center justify-end gap-2 border-t border-border-default px-6 py-4',\n hasHandle && placement === 'top' && 'pb-0',\n )}\n >\n {footer}\n </View>\n )}\n {placement === 'top' && handle}\n </View>\n </Animated.View>\n </View>\n </CloseContext.Provider>\n </RNModal>\n </>\n )\n}\n\n/** Fecha o painel ao pressionar. Envolva um `<Button>` do rodapé (injeta `onPress`, como o `asChild`). */\nexport function DrawerClose({ children }: { children: ReactNode }) {\n const close = useContext(CloseContext)\n return <>{injectHandler(children, 'onPress', () => close?.())}</>\n}\n"],"mappings":";;;;;;;;;;;;AAqBA,MAAM,iBAAiB,IAAI,wEAAwE;CACjG,UAAU,EACR,MAAM;EACJ,OAAO;EACP,MAAM;EACN,KAAK;EACL,QAAQ;CACV,EACF;CACA,iBAAiB,EAAE,MAAM,QAAQ;AACnC,CAAC;AAED,MAAM,oBAAoB,IAAI,UAAU;CACtC,UAAU,EACR,MAAM;EACJ,OAAO;EACP,MAAM;EACN,KAAK;EACL,QAAQ;CACV,EACF;CACA,iBAAiB,EAAE,MAAM,QAAQ;AACnC,CAAC;AAED,MAAM,eAAe,cAAmC,IAAI;;;;;AAoC5D,SAAgB,OAAO,EACrB,MACA,aACA,cACA,OACA,UACA,QACA,SACA,aAAa,UACb,eAAe,MACf,aAAa,MACb,MACA,WACA,UACc;CACd,MAAM,CAAC,QAAQ,WAAW,aAAa;EAAE;EAAM;EAAa;CAAa,CAAC;CAC1E,MAAM,cAAc,QAAQ,KAAK;CACjC,MAAM,YAAY,SAAS,sBAAsB;CACjD,MAAM,EAAE,OAAO,WAAW,oBAAoB;CAC9C,MAAM,YAAkB,QAAQ;CAChC,MAAM,aAAa,cAAc,UAAU,cAAc;CACzD,MAAM,YAAY,OAAO,MAAM;CAC/B,UAAU,UAAU;CACpB,MAAM,QAAQ,gBAAgB;EAC5B,MAAM;EACN,SAAS;EACT,cAAc,aAAa,QAAQ;EACnC,SAAS;EACT,cAAc,UAAU;CAC1B,CAAC;CACD,MAAM,YAAY,cAAc,gBAAgB,CAAC;CAGjD,MAAM,EAAE,SAAS,aAAa,cAAc,QAAQ;EAClD,UAAU;EACV,cAAc;EACd,UAAU,MAAM;CAClB,CAAC;CAED,MAAM,SAAS,aAAa,QAAQ;CACpC,MAAM,OAAO,cAAc,WAAW,cAAc,WAAW,IAAI;CAEnE,MAAM,YAAY,SAAS,IACzB,SAAS,YAAY;EAAE,YAAY,CAAC,GAAG,CAAC;EAAG,aAAa,CAAC,OAAO,QAAQ,CAAC;CAAE,CAAC,GAC5E,SAAS,SAAS,MAAM,MAAM,IAAI,CACpC;CACA,MAAM,SAAS,aAEb,oBAAC,MAAD;EACE,6BAAA;EACA,2BAA0B;EAC1B,WAAU;EAEV,UAAA,oBAAC,MAAD,EAAM,WAAU,uCAAwC,CAAA;CACpD,CAAA;CAGR,OACE,qBAAA,YAAA,EAAA,UAAA,CACG,cAAc,SAAS,iBAAiB,QAAQ,IAAI,CAAC,GACtD,oBAACA,OAAD;EACE,aAAA;EACA,sBAAA;EACA,eAAc;EACd,SAAS;EACT,gBAAgB;EAEhB,UAAA,oBAAC,aAAa,UAAd;GAAuB,OAAO;GAC5B,UAAA,qBAAC,MAAD;IAAM,WAAW,kBAAkB,EAAE,KAAK,CAAC;IAAG,0BAAA;IAA9C,UAAA,CACE,oBAAC,SAAS,MAAV;KACE,eAAc;KACd,OAAO;MACL,UAAU;MACV,KAAK;MACL,MAAM;MACN,OAAO;MACP,QAAQ;MACR,SAAS,SAAS,SAAS,UAAU,MAAM,QAAQ;KACrD;KAEA,UAAA,oBAAC,UAAD;MAAU,SAAS;MAAO,QAAQ,SAAS,GAAG,OAAO,aAAa,KAAA;KAAY,CAAA;IACjE,CAAA,GACf,oBAAC,SAAS,MAAV;KACE,GAAI,MAAM;KACV,UAAU,MAAM;KAChB,QAAQ,SAAS,GAAG,OAAO,UAAU,KAAA;KACrC,OAAO,CACL,aAAa;MAAE,OAAO;MAAO,UAAU;KAAI,IAAI;MAAE,OAAO;MAAQ,WAAW;KAAM,GACjF,EAAE,WAAW,CAAC,aAAa,EAAE,YAAY,UAAU,IAAI,EAAE,YAAY,UAAU,CAAC,EAAE,CACpF;KAEA,UAAA,qBAAC,MAAD;MACU;MACR,WAAW,GAAG,eAAe,EAAE,KAAK,CAAC,GAAG,cAAc,UAAU,SAAS;MAF3E,UAAA;OAIG,cAAc,YAAY;OAE3B,qBAAC,MAAD;QACE,WAAW,GACT,wEACA,aAAa,cAAc,YAAY,MACzC;QAJF,UAAA,CAME,oBAAC,MAAD;SACE,mBAAkB;SAClB,WAAU;SAET,UAAA;QACG,CAAA,GACN,oBAAC,WAAD;SACE,mBAAkB;SAClB,oBAAoB;SACpB,SAAS;SACT,SAAS;SACT,WAAU;SAEV,UAAA,oBAAC,WAAD,EAAW,OAAO,UAAY,CAAA;QACrB,CAAA,CACP;;OACN,oBAAC,YAAD;QACE,WAAU;QACV,2BAA2B,GACzB,aACA,aAAa,cAAc,SAAS,CAAC,UAAU,MACjD;QACA,GAAI,MAAM;QAET,UAAA,OAAO,aAAa,WACnB,oBAAC,MAAD;SAAM,WAAU;SAAyC;QAAe,CAAA,IAExE;OAEQ,CAAA;OACX,UACC,oBAAC,MAAD;QACE,WAAW,GACT,oFACA,aAAa,cAAc,SAAS,MACtC;QAEC,UAAA;OACG,CAAA;OAEP,cAAc,SAAS;MACpB;;IACO,CAAA,CACX;;EACe,CAAA;CAChB,CAAA,CACT,EAAA,CAAA;AAEN;;AAGA,SAAgB,YAAY,EAAE,YAAqC;CACjE,MAAM,QAAQ,WAAW,YAAY;CACrC,OAAO,oBAAA,YAAA,EAAA,UAAG,cAAc,UAAU,iBAAiB,QAAQ,CAAC,EAAI,CAAA;AAClE"}
|