@innovui/react-native 0.1.0 → 0.3.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 (58) hide show
  1. package/README.md +1 -0
  2. package/dist/components/_shared/anchored-overlay.js +37 -12
  3. package/dist/components/_shared/anchored-overlay.js.map +1 -1
  4. package/dist/components/_shared/media-icons.js +125 -0
  5. package/dist/components/_shared/media-icons.js.map +1 -0
  6. package/dist/components/_shared/media.js +114 -0
  7. package/dist/components/_shared/media.js.map +1 -0
  8. package/dist/components/_shared/sliding-indicator.js +98 -0
  9. package/dist/components/_shared/sliding-indicator.js.map +1 -0
  10. package/dist/components/_shared/use-transition.js +53 -0
  11. package/dist/components/_shared/use-transition.js.map +1 -0
  12. package/dist/components/audio-player/audio-player.d.ts +34 -0
  13. package/dist/components/audio-player/audio-player.js +89 -0
  14. package/dist/components/audio-player/audio-player.js.map +1 -0
  15. package/dist/components/audio-player/index.d.ts +2 -0
  16. package/dist/components/carousel/carousel.d.ts +34 -0
  17. package/dist/components/carousel/carousel.js +110 -0
  18. package/dist/components/carousel/carousel.js.map +1 -0
  19. package/dist/components/carousel/index.d.ts +2 -0
  20. package/dist/components/drawer/drawer.d.ts +11 -1
  21. package/dist/components/drawer/drawer.js +37 -22
  22. package/dist/components/drawer/drawer.js.map +1 -1
  23. package/dist/components/drawer/use-swipe-to-close.js +143 -0
  24. package/dist/components/drawer/use-swipe-to-close.js.map +1 -0
  25. package/dist/components/gallery/gallery.d.ts +21 -0
  26. package/dist/components/gallery/gallery.js +54 -0
  27. package/dist/components/gallery/gallery.js.map +1 -0
  28. package/dist/components/gallery/index.d.ts +2 -0
  29. package/dist/components/input/input.d.ts +8 -2
  30. package/dist/components/input/input.js +10 -2
  31. package/dist/components/input/input.js.map +1 -1
  32. package/dist/components/lightbox/lightbox.d.ts +30 -0
  33. package/dist/components/lightbox/lightbox.js +147 -0
  34. package/dist/components/lightbox/lightbox.js.map +1 -0
  35. package/dist/components/modal/modal.js +59 -39
  36. package/dist/components/modal/modal.js.map +1 -1
  37. package/dist/components/segmented-control/segmented-control.d.ts +2 -2
  38. package/dist/components/segmented-control/segmented-control.js +20 -7
  39. package/dist/components/segmented-control/segmented-control.js.map +1 -1
  40. package/dist/components/tabs/tabs.d.ts +2 -2
  41. package/dist/components/tabs/tabs.js +32 -9
  42. package/dist/components/tabs/tabs.js.map +1 -1
  43. package/dist/components/toast/toast.js +66 -5
  44. package/dist/components/toast/toast.js.map +1 -1
  45. package/dist/components/tooltip/tooltip.d.ts +7 -3
  46. package/dist/components/tooltip/tooltip.js +10 -4
  47. package/dist/components/tooltip/tooltip.js.map +1 -1
  48. package/dist/components/video-player/index.d.ts +2 -0
  49. package/dist/components/video-player/video-player.d.ts +37 -0
  50. package/dist/components/video-player/video-player.js +178 -0
  51. package/dist/components/video-player/video-player.js.map +1 -0
  52. package/dist/index.d.ts +10 -1
  53. package/dist/index.js +6 -1
  54. package/dist/tokens/generated.d.ts +8 -0
  55. package/dist/tokens/generated.js +10 -2
  56. package/dist/tokens/generated.js.map +1 -1
  57. package/package.json +1 -1
  58. package/theme.css +8 -0
package/README.md CHANGED
@@ -39,6 +39,7 @@ export function App() {
39
39
  - **Sem `asChild`:** gatilhos de Modal, Drawer, Popover, Menu e Tooltip recebem o elemento clonado com `onPress` (o Tooltip abre no `onLongPress`).
40
40
  - **Overlays:** Modal, Drawer, Popover, Menu, Select e Combobox usam o `Modal` nativo do RN. Sem blur no backdrop. Envolva o app em `ToastProvider` para usar `useToast()`.
41
41
  - **Upload:** o RN não tem `<input type="file">`. O `Upload` é controlado: o consumidor abre o seletor (ex. `expo-document-picker`) em `onPick` e passa a lista em `files`.
42
+ - **AudioPlayer e VideoPlayer:** o React Native não tem player de mídia embutido, então são cascas controladas: você guarda o estado (por exemplo com `expo-audio` e `expo-video`) e o passa por props (`playing`, `position`, `duration`…), ligando `onPlayPause`, `onSeek` etc. ao player de verdade. A onda do AudioPlayer vem de `waveform` (ou da onda padrão): no native não há extração automática. Gallery, Lightbox e Carousel são completos.
42
43
  - **Grid e Table:** feitos com Views e flexbox (sem CSS grid). Breakpoints vêm de `useWindowDimensions`.
43
44
  - **Sombras no Android:** `shadow-*` não gera `elevation`.
44
45
 
@@ -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 left = Math.max(8, Math.min(rect.x, windowWidth - 8 - (matchAnchorWidth ? rect.width : 0)));
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: "fade",
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__ */ jsx(View, {
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
- onStartShouldSetResponder: () => true,
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 left = Math.max(8, Math.min(rect.x, windowWidth - 8 - (matchAnchorWidth ? rect.width : 0)))\n\n return (\n <Modal\n transparent\n visible={visible}\n animationType=\"fade\"\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 <View\n testID={testID}\n className={className}\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 }}\n // Toques dentro do conteúdo não chegam ao backdrop.\n onStartShouldSetResponder={() => true}\n >\n {children}\n </View>\n </Pressable>\n </Modal>\n )\n}\n"],"mappings":";;;;;;;;;;;;AAgBA,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;;AAsBA,SAAgB,gBAAgB,EAC9B,SACA,MACA,gBACA,UACA,YAAY,UACZ,SAAS,GACT,mBAAmB,OACnB,kBAAkB,KAClB,wBAAwB,MACxB,WACA,UACuB;CACvB,MAAM,EAAE,OAAO,aAAa,QAAQ,iBAAiB,oBAAoB;CACzE,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,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,GAAG,cAAc,KAAK,mBAAmB,KAAK,QAAQ,EAAE,CAAC;CAEhG,OACE,oBAAC,OAAD;EACE,aAAA;EACS;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,oBAAC,MAAD;IACU;IACG;IACX,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;IACjD;IAEA,iCAAiC;IAEhC;GACG,CAAA;EACG,CAAA;CACN,CAAA;AAEX"}
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,125 @@
1
+ import Svg, { Path, Rect } from "react-native-svg";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ //#region src/components/_shared/media-icons.tsx
4
+ const strokeProps = {
5
+ fill: "none",
6
+ strokeWidth: 2.4,
7
+ strokeLinecap: "round",
8
+ strokeLinejoin: "round"
9
+ };
10
+ const playPath = "M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z";
11
+ const speakerPath = "M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z";
12
+ function Icon({ color, size = 20, children, ...props }) {
13
+ return /* @__PURE__ */ jsx(Svg, {
14
+ width: size,
15
+ height: size,
16
+ viewBox: "0 0 24 24",
17
+ stroke: color,
18
+ ...strokeProps,
19
+ ...props,
20
+ children
21
+ });
22
+ }
23
+ /** Triângulo de reproduzir; `filled` preenche com `color` (botão central do vídeo). */
24
+ function PlayIcon({ filled, color, ...props }) {
25
+ return /* @__PURE__ */ jsx(Icon, {
26
+ color,
27
+ fill: filled ? color : "none",
28
+ ...props,
29
+ children: /* @__PURE__ */ jsx(Path, { d: playPath })
30
+ });
31
+ }
32
+ function PauseIcon(props) {
33
+ return /* @__PURE__ */ jsxs(Icon, {
34
+ ...props,
35
+ children: [/* @__PURE__ */ jsx(Rect, {
36
+ x: "14",
37
+ y: "3",
38
+ width: "5",
39
+ height: "18",
40
+ rx: "1"
41
+ }), /* @__PURE__ */ jsx(Rect, {
42
+ x: "5",
43
+ y: "3",
44
+ width: "5",
45
+ height: "18",
46
+ rx: "1"
47
+ })]
48
+ });
49
+ }
50
+ function VolumeIcon(props) {
51
+ return /* @__PURE__ */ jsxs(Icon, {
52
+ ...props,
53
+ children: [
54
+ /* @__PURE__ */ jsx(Path, { d: speakerPath }),
55
+ /* @__PURE__ */ jsx(Path, { d: "M16 9a5 5 0 0 1 0 6" }),
56
+ /* @__PURE__ */ jsx(Path, { d: "M19.364 18.364a9 9 0 0 0 0-12.728" })
57
+ ]
58
+ });
59
+ }
60
+ function VolumeMutedIcon(props) {
61
+ return /* @__PURE__ */ jsxs(Icon, {
62
+ ...props,
63
+ children: [
64
+ /* @__PURE__ */ jsx(Path, { d: speakerPath }),
65
+ /* @__PURE__ */ jsx(Path, { d: "M22 9l-6 6" }),
66
+ /* @__PURE__ */ jsx(Path, { d: "M16 9l6 6" })
67
+ ]
68
+ });
69
+ }
70
+ function MaximizeIcon(props) {
71
+ return /* @__PURE__ */ jsxs(Icon, {
72
+ ...props,
73
+ children: [
74
+ /* @__PURE__ */ jsx(Path, { d: "M8 3H5a2 2 0 0 0-2 2v3" }),
75
+ /* @__PURE__ */ jsx(Path, { d: "M21 8V5a2 2 0 0 0-2-2h-3" }),
76
+ /* @__PURE__ */ jsx(Path, { d: "M3 16v3a2 2 0 0 0 2 2h3" }),
77
+ /* @__PURE__ */ jsx(Path, { d: "M16 21h3a2 2 0 0 0 2-2v-3" })
78
+ ]
79
+ });
80
+ }
81
+ function MinimizeIcon(props) {
82
+ return /* @__PURE__ */ jsxs(Icon, {
83
+ ...props,
84
+ children: [
85
+ /* @__PURE__ */ jsx(Path, { d: "M8 3v3a2 2 0 0 1-2 2H3" }),
86
+ /* @__PURE__ */ jsx(Path, { d: "M21 8h-3a2 2 0 0 1-2-2V3" }),
87
+ /* @__PURE__ */ jsx(Path, { d: "M3 16h3a2 2 0 0 1 2 2v3" }),
88
+ /* @__PURE__ */ jsx(Path, { d: "M16 21v-3a2 2 0 0 1 2-2h3" })
89
+ ]
90
+ });
91
+ }
92
+ /** Quatro setas para fora: o ícone de ampliar sobre a miniatura. */
93
+ function ExpandIcon(props) {
94
+ return /* @__PURE__ */ jsxs(Icon, {
95
+ ...props,
96
+ children: [
97
+ /* @__PURE__ */ jsx(Path, { d: "m15 15 6 6" }),
98
+ /* @__PURE__ */ jsx(Path, { d: "m15 9 6-6" }),
99
+ /* @__PURE__ */ jsx(Path, { d: "M21 16v5h-5" }),
100
+ /* @__PURE__ */ jsx(Path, { d: "M21 8V3h-5" }),
101
+ /* @__PURE__ */ jsx(Path, { d: "M3 16v5h5" }),
102
+ /* @__PURE__ */ jsx(Path, { d: "m3 21 6-6" }),
103
+ /* @__PURE__ */ jsx(Path, { d: "M3 8V3h5" }),
104
+ /* @__PURE__ */ jsx(Path, { d: "M9 9 3 3" })
105
+ ]
106
+ });
107
+ }
108
+ /** ‹ de 20px (setas do carrossel e do lightbox). */
109
+ function PreviousIcon(props) {
110
+ return /* @__PURE__ */ jsx(Icon, {
111
+ ...props,
112
+ children: /* @__PURE__ */ jsx(Path, { d: "m15 18-6-6 6-6" })
113
+ });
114
+ }
115
+ /** › de 20px (setas do carrossel e do lightbox). */
116
+ function NextIcon(props) {
117
+ return /* @__PURE__ */ jsx(Icon, {
118
+ ...props,
119
+ children: /* @__PURE__ */ jsx(Path, { d: "m9 18 6-6-6-6" })
120
+ });
121
+ }
122
+ //#endregion
123
+ export { ExpandIcon, MaximizeIcon, MinimizeIcon, NextIcon, PauseIcon, PlayIcon, PreviousIcon, VolumeIcon, VolumeMutedIcon };
124
+
125
+ //# sourceMappingURL=media-icons.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media-icons.js","names":[],"sources":["../../../src/components/_shared/media-icons.tsx"],"sourcesContent":["/// <reference types=\"uniwind/types\" />\n\nimport Svg, { Path, Rect, type SvgProps } from 'react-native-svg'\n\ntype IconProps = Omit<SvgProps, 'stroke' | 'fill' | 'width' | 'height'> & {\n color?: string\n /** Lado em px. Padrão: 20. */\n size?: number\n}\n\n// Ícones do Lucide (viewBox 24); com traço 2.4 saem com 2px aos 20px, como no Figma.\nconst strokeProps = {\n fill: 'none',\n strokeWidth: 2.4,\n strokeLinecap: 'round',\n strokeLinejoin: 'round',\n} as const\n\nconst playPath =\n 'M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z'\nconst speakerPath =\n 'M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z'\n\nfunction Icon({\n color,\n size = 20,\n children,\n ...props\n}: IconProps & { fill?: string; children: React.ReactNode }) {\n return (\n <Svg width={size} height={size} viewBox=\"0 0 24 24\" stroke={color} {...strokeProps} {...props}>\n {children}\n </Svg>\n )\n}\n\n/** Triângulo de reproduzir; `filled` preenche com `color` (botão central do vídeo). */\nexport function PlayIcon({ filled, color, ...props }: IconProps & { filled?: boolean }) {\n return (\n <Icon color={color} fill={filled ? color : 'none'} {...props}>\n <Path d={playPath} />\n </Icon>\n )\n}\n\nexport function PauseIcon(props: IconProps) {\n return (\n <Icon {...props}>\n <Rect x=\"14\" y=\"3\" width=\"5\" height=\"18\" rx=\"1\" />\n <Rect x=\"5\" y=\"3\" width=\"5\" height=\"18\" rx=\"1\" />\n </Icon>\n )\n}\n\nexport function VolumeIcon(props: IconProps) {\n return (\n <Icon {...props}>\n <Path d={speakerPath} />\n <Path d=\"M16 9a5 5 0 0 1 0 6\" />\n <Path d=\"M19.364 18.364a9 9 0 0 0 0-12.728\" />\n </Icon>\n )\n}\n\nexport function VolumeMutedIcon(props: IconProps) {\n return (\n <Icon {...props}>\n <Path d={speakerPath} />\n <Path d=\"M22 9l-6 6\" />\n <Path d=\"M16 9l6 6\" />\n </Icon>\n )\n}\n\nexport function MaximizeIcon(props: IconProps) {\n return (\n <Icon {...props}>\n <Path d=\"M8 3H5a2 2 0 0 0-2 2v3\" />\n <Path d=\"M21 8V5a2 2 0 0 0-2-2h-3\" />\n <Path d=\"M3 16v3a2 2 0 0 0 2 2h3\" />\n <Path d=\"M16 21h3a2 2 0 0 0 2-2v-3\" />\n </Icon>\n )\n}\n\nexport function MinimizeIcon(props: IconProps) {\n return (\n <Icon {...props}>\n <Path d=\"M8 3v3a2 2 0 0 1-2 2H3\" />\n <Path d=\"M21 8h-3a2 2 0 0 1-2-2V3\" />\n <Path d=\"M3 16h3a2 2 0 0 1 2 2v3\" />\n <Path d=\"M16 21v-3a2 2 0 0 1 2-2h3\" />\n </Icon>\n )\n}\n\n/** Quatro setas para fora: o ícone de ampliar sobre a miniatura. */\nexport function ExpandIcon(props: IconProps) {\n return (\n <Icon {...props}>\n <Path d=\"m15 15 6 6\" />\n <Path d=\"m15 9 6-6\" />\n <Path d=\"M21 16v5h-5\" />\n <Path d=\"M21 8V3h-5\" />\n <Path d=\"M3 16v5h5\" />\n <Path d=\"m3 21 6-6\" />\n <Path d=\"M3 8V3h5\" />\n <Path d=\"M9 9 3 3\" />\n </Icon>\n )\n}\n\n/** ‹ de 20px (setas do carrossel e do lightbox). */\nexport function PreviousIcon(props: IconProps) {\n return (\n <Icon {...props}>\n <Path d=\"m15 18-6-6 6-6\" />\n </Icon>\n )\n}\n\n/** › de 20px (setas do carrossel e do lightbox). */\nexport function NextIcon(props: IconProps) {\n return (\n <Icon {...props}>\n <Path d=\"m9 18 6-6-6-6\" />\n </Icon>\n )\n}\n"],"mappings":";;;AAWA,MAAM,cAAc;CAClB,MAAM;CACN,aAAa;CACb,eAAe;CACf,gBAAgB;AAClB;AAEA,MAAM,WACJ;AACF,MAAM,cACJ;AAEF,SAAS,KAAK,EACZ,OACA,OAAO,IACP,UACA,GAAG,SACwD;CAC3D,OACE,oBAAC,KAAD;EAAK,OAAO;EAAM,QAAQ;EAAM,SAAQ;EAAY,QAAQ;EAAO,GAAI;EAAa,GAAI;EACrF;CACE,CAAA;AAET;;AAGA,SAAgB,SAAS,EAAE,QAAQ,OAAO,GAAG,SAA2C;CACtF,OACE,oBAAC,MAAD;EAAa;EAAO,MAAM,SAAS,QAAQ;EAAQ,GAAI;EACrD,UAAA,oBAAC,MAAD,EAAM,GAAG,SAAW,CAAA;CAChB,CAAA;AAEV;AAEA,SAAgB,UAAU,OAAkB;CAC1C,OACE,qBAAC,MAAD;EAAM,GAAI;EAAV,UAAA,CACE,oBAAC,MAAD;GAAM,GAAE;GAAK,GAAE;GAAI,OAAM;GAAI,QAAO;GAAK,IAAG;EAAK,CAAA,GACjD,oBAAC,MAAD;GAAM,GAAE;GAAI,GAAE;GAAI,OAAM;GAAI,QAAO;GAAK,IAAG;EAAK,CAAA,CAC5C;;AAEV;AAEA,SAAgB,WAAW,OAAkB;CAC3C,OACE,qBAAC,MAAD;EAAM,GAAI;EAAV,UAAA;GACE,oBAAC,MAAD,EAAM,GAAG,YAAc,CAAA;GACvB,oBAAC,MAAD,EAAM,GAAE,sBAAuB,CAAA;GAC/B,oBAAC,MAAD,EAAM,GAAE,oCAAqC,CAAA;EACzC;;AAEV;AAEA,SAAgB,gBAAgB,OAAkB;CAChD,OACE,qBAAC,MAAD;EAAM,GAAI;EAAV,UAAA;GACE,oBAAC,MAAD,EAAM,GAAG,YAAc,CAAA;GACvB,oBAAC,MAAD,EAAM,GAAE,aAAc,CAAA;GACtB,oBAAC,MAAD,EAAM,GAAE,YAAa,CAAA;EACjB;;AAEV;AAEA,SAAgB,aAAa,OAAkB;CAC7C,OACE,qBAAC,MAAD;EAAM,GAAI;EAAV,UAAA;GACE,oBAAC,MAAD,EAAM,GAAE,yBAA0B,CAAA;GAClC,oBAAC,MAAD,EAAM,GAAE,2BAA4B,CAAA;GACpC,oBAAC,MAAD,EAAM,GAAE,0BAA2B,CAAA;GACnC,oBAAC,MAAD,EAAM,GAAE,4BAA6B,CAAA;EACjC;;AAEV;AAEA,SAAgB,aAAa,OAAkB;CAC7C,OACE,qBAAC,MAAD;EAAM,GAAI;EAAV,UAAA;GACE,oBAAC,MAAD,EAAM,GAAE,yBAA0B,CAAA;GAClC,oBAAC,MAAD,EAAM,GAAE,2BAA4B,CAAA;GACpC,oBAAC,MAAD,EAAM,GAAE,0BAA2B,CAAA;GACnC,oBAAC,MAAD,EAAM,GAAE,4BAA6B,CAAA;EACjC;;AAEV;;AAGA,SAAgB,WAAW,OAAkB;CAC3C,OACE,qBAAC,MAAD;EAAM,GAAI;EAAV,UAAA;GACE,oBAAC,MAAD,EAAM,GAAE,aAAc,CAAA;GACtB,oBAAC,MAAD,EAAM,GAAE,YAAa,CAAA;GACrB,oBAAC,MAAD,EAAM,GAAE,cAAe,CAAA;GACvB,oBAAC,MAAD,EAAM,GAAE,aAAc,CAAA;GACtB,oBAAC,MAAD,EAAM,GAAE,YAAa,CAAA;GACrB,oBAAC,MAAD,EAAM,GAAE,YAAa,CAAA;GACrB,oBAAC,MAAD,EAAM,GAAE,WAAY,CAAA;GACpB,oBAAC,MAAD,EAAM,GAAE,WAAY,CAAA;EAChB;;AAEV;;AAGA,SAAgB,aAAa,OAAkB;CAC7C,OACE,oBAAC,MAAD;EAAM,GAAI;EACR,UAAA,oBAAC,MAAD,EAAM,GAAE,iBAAkB,CAAA;CACtB,CAAA;AAEV;;AAGA,SAAgB,SAAS,OAAkB;CACzC,OACE,oBAAC,MAAD;EAAM,GAAI;EACR,UAAA,oBAAC,MAAD,EAAM,GAAE,gBAAiB,CAAA;CACrB,CAAA;AAEV"}
@@ -0,0 +1,114 @@
1
+ import { useState } from "react";
2
+ //#region src/components/_shared/media.ts
3
+ /** `83` → `1:23`; com hora, `3725` → `1:02:05`. Valores inválidos viram `0:00`. */
4
+ function formatTime(seconds) {
5
+ const total = Number.isFinite(seconds) ? Math.max(0, Math.floor(seconds)) : 0;
6
+ const hours = Math.floor(total / 3600);
7
+ const minutes = Math.floor(total % 3600 / 60);
8
+ const secs = String(total % 60).padStart(2, "0");
9
+ return hours ? `${hours}:${String(minutes).padStart(2, "0")}:${secs}` : `${minutes}:${secs}`;
10
+ }
11
+ /** Alturas das barras do Figma (6–34px), como picos de 0 a 1. É a onda padrão do AudioPlayer. */
12
+ const DEFAULT_WAVEFORM = [
13
+ 6,
14
+ 18,
15
+ 34,
16
+ 20,
17
+ 12,
18
+ 22,
19
+ 16,
20
+ 26,
21
+ 20,
22
+ 14,
23
+ 28,
24
+ 18,
25
+ 34,
26
+ 22,
27
+ 16,
28
+ 24,
29
+ 20,
30
+ 14,
31
+ 18,
32
+ 26,
33
+ 16,
34
+ 30,
35
+ 22,
36
+ 12,
37
+ 34,
38
+ 18,
39
+ 26,
40
+ 16,
41
+ 10,
42
+ 22,
43
+ 14,
44
+ 20,
45
+ 24,
46
+ 16,
47
+ 28,
48
+ 12,
49
+ 34,
50
+ 18,
51
+ 26,
52
+ 16,
53
+ 10,
54
+ 20,
55
+ 14,
56
+ 18,
57
+ 24,
58
+ 30,
59
+ 22,
60
+ 28,
61
+ 34,
62
+ 16,
63
+ 24,
64
+ 18
65
+ ].map((height) => (height - 6) / 28);
66
+ /** Leva uma lista de picos para `bars` barras (pega o maior de cada trecho, ou repete se faltar). */
67
+ function resamplePeaks(peaks, bars) {
68
+ if (peaks.length === bars || peaks.length === 0) return peaks;
69
+ return Array.from({ length: bars }, (_, i) => {
70
+ const start = Math.floor(i * peaks.length / bars);
71
+ const end = Math.max(Math.floor((i + 1) * peaks.length / bars), start + 1);
72
+ return Math.max(...peaks.slice(start, end));
73
+ });
74
+ }
75
+ const SEEK_STEP = 5;
76
+ /**
77
+ * Props de uma barra de posição: tocar e arrastar posicionam, e o leitor de tela ajusta de 5 em
78
+ * 5 segundos (ações increment/decrement, como no Slider). Espalhe numa `View` cujos filhos têm
79
+ * `pointerEvents="none"`, para o `locationX` ser sempre relativo a ela.
80
+ */
81
+ function useSeek({ position, duration, label, onSeek }) {
82
+ const [width, setWidth] = useState(0);
83
+ const clamp = (value) => Math.min(Math.max(value, 0), duration);
84
+ const at = (x) => width ? clamp(x / width * duration) : 0;
85
+ return {
86
+ width,
87
+ props: {
88
+ onLayout: (event) => setWidth(event.nativeEvent.layout.width),
89
+ onStartShouldSetResponder: () => duration > 0,
90
+ onMoveShouldSetResponder: () => duration > 0,
91
+ onResponderTerminationRequest: () => false,
92
+ onResponderGrant: (event) => onSeek?.(at(event.nativeEvent.locationX)),
93
+ onResponderMove: (event) => onSeek?.(at(event.nativeEvent.locationX)),
94
+ accessible: true,
95
+ accessibilityRole: "adjustable",
96
+ accessibilityLabel: label,
97
+ accessibilityValue: {
98
+ min: 0,
99
+ max: Math.round(duration),
100
+ now: Math.round(position),
101
+ text: `${formatTime(position)} de ${formatTime(duration)}`
102
+ },
103
+ accessibilityActions: [{ name: "increment" }, { name: "decrement" }],
104
+ onAccessibilityAction: (event) => {
105
+ const direction = event.nativeEvent.actionName === "increment" ? 1 : -1;
106
+ onSeek?.(clamp(position + direction * SEEK_STEP));
107
+ }
108
+ }
109
+ };
110
+ }
111
+ //#endregion
112
+ export { DEFAULT_WAVEFORM, formatTime, resamplePeaks, useSeek };
113
+
114
+ //# sourceMappingURL=media.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.js","names":[],"sources":["../../../src/components/_shared/media.ts"],"sourcesContent":["import { useState } from 'react'\nimport type {\n AccessibilityActionEvent,\n GestureResponderEvent,\n LayoutChangeEvent,\n} from 'react-native'\n\n/** `83` → `1:23`; com hora, `3725` → `1:02:05`. Valores inválidos viram `0:00`. */\nexport function formatTime(seconds: number) {\n const total = Number.isFinite(seconds) ? Math.max(0, Math.floor(seconds)) : 0\n const hours = Math.floor(total / 3600)\n const minutes = Math.floor((total % 3600) / 60)\n const secs = String(total % 60).padStart(2, '0')\n return hours ? `${hours}:${String(minutes).padStart(2, '0')}:${secs}` : `${minutes}:${secs}`\n}\n\n/** Alturas das barras do Figma (6–34px), como picos de 0 a 1. É a onda padrão do AudioPlayer. */\nexport const DEFAULT_WAVEFORM = [\n 6, 18, 34, 20, 12, 22, 16, 26, 20, 14, 28, 18, 34, 22, 16, 24, 20, 14, 18, 26, 16, 30, 22, 12, 34,\n 18, 26, 16, 10, 22, 14, 20, 24, 16, 28, 12, 34, 18, 26, 16, 10, 20, 14, 18, 24, 30, 22, 28, 34,\n 16, 24, 18,\n].map((height) => (height - 6) / 28)\n\n/** Leva uma lista de picos para `bars` barras (pega o maior de cada trecho, ou repete se faltar). */\nexport function resamplePeaks(peaks: number[], bars: number) {\n if (peaks.length === bars || peaks.length === 0) return peaks\n return Array.from({ length: bars }, (_, i) => {\n const start = Math.floor((i * peaks.length) / bars)\n const end = Math.max(Math.floor(((i + 1) * peaks.length) / bars), start + 1)\n return Math.max(...peaks.slice(start, end))\n })\n}\n\nconst SEEK_STEP = 5\n\n/**\n * Props de uma barra de posição: tocar e arrastar posicionam, e o leitor de tela ajusta de 5 em\n * 5 segundos (ações increment/decrement, como no Slider). Espalhe numa `View` cujos filhos têm\n * `pointerEvents=\"none\"`, para o `locationX` ser sempre relativo a ela.\n */\nexport function useSeek({\n position,\n duration,\n label,\n onSeek,\n}: {\n position: number\n duration: number\n label: string\n onSeek?: (seconds: number) => void\n}) {\n const [width, setWidth] = useState(0)\n const clamp = (value: number) => Math.min(Math.max(value, 0), duration)\n const at = (x: number) => (width ? clamp((x / width) * duration) : 0)\n\n return {\n width,\n props: {\n onLayout: (event: LayoutChangeEvent) => setWidth(event.nativeEvent.layout.width),\n onStartShouldSetResponder: () => duration > 0,\n onMoveShouldSetResponder: () => duration > 0,\n onResponderTerminationRequest: () => false,\n onResponderGrant: (event: GestureResponderEvent) => onSeek?.(at(event.nativeEvent.locationX)),\n onResponderMove: (event: GestureResponderEvent) => onSeek?.(at(event.nativeEvent.locationX)),\n accessible: true,\n accessibilityRole: 'adjustable' as const,\n accessibilityLabel: label,\n accessibilityValue: {\n min: 0,\n max: Math.round(duration),\n now: Math.round(position),\n text: `${formatTime(position)} de ${formatTime(duration)}`,\n },\n accessibilityActions: [{ name: 'increment' }, { name: 'decrement' }],\n onAccessibilityAction: (event: AccessibilityActionEvent) => {\n const direction = event.nativeEvent.actionName === 'increment' ? 1 : -1\n onSeek?.(clamp(position + direction * SEEK_STEP))\n },\n },\n }\n}\n"],"mappings":";;;AAQA,SAAgB,WAAW,SAAiB;CAC1C,MAAM,QAAQ,OAAO,SAAS,OAAO,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,CAAC,IAAI;CAC5E,MAAM,QAAQ,KAAK,MAAM,QAAQ,IAAI;CACrC,MAAM,UAAU,KAAK,MAAO,QAAQ,OAAQ,EAAE;CAC9C,MAAM,OAAO,OAAO,QAAQ,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG;CAC/C,OAAO,QAAQ,GAAG,MAAM,GAAG,OAAO,OAAO,CAAC,CAAC,SAAS,GAAG,GAAG,EAAE,GAAG,SAAS,GAAG,QAAQ,GAAG;AACxF;;AAGA,MAAa,mBAAmB;CAC9B;CAAG;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAC/F;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAC5F;CAAI;CAAI;AACV,CAAC,CAAC,KAAK,YAAY,SAAS,KAAK,EAAE;;AAGnC,SAAgB,cAAc,OAAiB,MAAc;CAC3D,IAAI,MAAM,WAAW,QAAQ,MAAM,WAAW,GAAG,OAAO;CACxD,OAAO,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,MAAM;EAC5C,MAAM,QAAQ,KAAK,MAAO,IAAI,MAAM,SAAU,IAAI;EAClD,MAAM,MAAM,KAAK,IAAI,KAAK,OAAQ,IAAI,KAAK,MAAM,SAAU,IAAI,GAAG,QAAQ,CAAC;EAC3E,OAAO,KAAK,IAAI,GAAG,MAAM,MAAM,OAAO,GAAG,CAAC;CAC5C,CAAC;AACH;AAEA,MAAM,YAAY;;;;;;AAOlB,SAAgB,QAAQ,EACtB,UACA,UACA,OACA,UAMC;CACD,MAAM,CAAC,OAAO,YAAY,SAAS,CAAC;CACpC,MAAM,SAAS,UAAkB,KAAK,IAAI,KAAK,IAAI,OAAO,CAAC,GAAG,QAAQ;CACtE,MAAM,MAAM,MAAe,QAAQ,MAAO,IAAI,QAAS,QAAQ,IAAI;CAEnE,OAAO;EACL;EACA,OAAO;GACL,WAAW,UAA6B,SAAS,MAAM,YAAY,OAAO,KAAK;GAC/E,iCAAiC,WAAW;GAC5C,gCAAgC,WAAW;GAC3C,qCAAqC;GACrC,mBAAmB,UAAiC,SAAS,GAAG,MAAM,YAAY,SAAS,CAAC;GAC5F,kBAAkB,UAAiC,SAAS,GAAG,MAAM,YAAY,SAAS,CAAC;GAC3F,YAAY;GACZ,mBAAmB;GACnB,oBAAoB;GACpB,oBAAoB;IAClB,KAAK;IACL,KAAK,KAAK,MAAM,QAAQ;IACxB,KAAK,KAAK,MAAM,QAAQ;IACxB,MAAM,GAAG,WAAW,QAAQ,EAAE,MAAM,WAAW,QAAQ;GACzD;GACA,sBAAsB,CAAC,EAAE,MAAM,YAAY,GAAG,EAAE,MAAM,YAAY,CAAC;GACnE,wBAAwB,UAAoC;IAC1D,MAAM,YAAY,MAAM,YAAY,eAAe,cAAc,IAAI;IACrE,SAAS,MAAM,WAAW,YAAY,SAAS,CAAC;GAClD;EACF;CACF;AACF"}
@@ -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"}
@@ -0,0 +1,34 @@
1
+ import { ViewProps } from "react-native";
2
+ //#region src/components/audio-player/audio-player.d.ts
3
+ export type AudioPlayerProps = Omit<ViewProps, 'children' | 'style'> & {
4
+ className?: string;
5
+ /** Nome acessível do player. */
6
+ label?: string;
7
+ /** Tocando agora: mostra o botão de pausar. */
8
+ playing?: boolean;
9
+ /** Posição atual, em segundos. */
10
+ position?: number;
11
+ /** Duração total, em segundos. */
12
+ duration?: number;
13
+ /** Velocidade atual (controlado). Sem ela, o player guarda a escolhida. */
14
+ rate?: number;
15
+ /** Velocidades do botão (cada toque passa para a próxima). Padrão: 1x, 1.5x e 2x. */
16
+ speeds?: number[];
17
+ /** Picos da onda, de 0 a 1. Sem eles, usa uma onda padrão. */
18
+ waveform?: number[];
19
+ /** Quantidade de barras da onda. Padrão: 52. */
20
+ bars?: number;
21
+ onPlayPause?: () => void;
22
+ /** Chamado com a posição (em segundos) tocada ou arrastada na onda. */
23
+ onSeek?: (seconds: number) => void;
24
+ /** Chamado com a nova velocidade quando o botão é tocado. */
25
+ onRateChange?: (rate: number) => void;
26
+ };
27
+ /**
28
+ * Casca de player de áudio: botão, onda, tempo e velocidade. Não toca nada: quem usa guarda o
29
+ * estado (por exemplo com `expo-audio`) e o passa por props, ligando `onPlayPause`, `onSeek` e
30
+ * `onRateChange` ao player de verdade.
31
+ */
32
+ export declare function AudioPlayer({ className, label, playing, position, duration, rate, speeds, waveform, bars, onPlayPause, onSeek, onRateChange, ...props }: AudioPlayerProps): import("react").JSX.Element;
33
+ //#endregion
34
+ //# sourceMappingURL=audio-player.d.ts.map