@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
@@ -1 +1 @@
1
- {"version":3,"file":"segmented-control.js","names":[],"sources":["../../../src/components/segmented-control/segmented-control.tsx"],"sourcesContent":["/// <reference types=\"uniwind/types\" />\n\nimport { createContext, type ReactNode, useContext, useState } from 'react'\nimport { Pressable, type PressableProps, Text, View, type ViewProps } from 'react-native'\nimport { cn } from '../../utils/cn'\n\ntype SegmentedContext = { value: string | undefined; select: (value: string) => void }\n\nconst Context = createContext<SegmentedContext | null>(null)\n\nexport type SegmentedControlProps = Omit<ViewProps, 'style'> & {\n className?: string\n /** Obrigatório: descreve o que o controle escolhe (ex.: \"Modo de exibição\"). */\n accessibilityLabel: string\n /** Controlado. Sem ele, o componente guarda o próprio estado (começa em `defaultValue`). */\n value?: string\n defaultValue?: string\n onValueChange?: (value: string) => void\n children: ReactNode\n}\n\n/** Escolha única entre 2–5 views do mesmo conteúdo. Para navegar entre seções, use Tabs. */\nexport function SegmentedControl({\n className,\n value,\n defaultValue,\n onValueChange,\n ...props\n}: SegmentedControlProps) {\n const [uncontrolled, setUncontrolled] = useState(defaultValue)\n const current = value ?? uncontrolled\n\n const select = (next: string) => {\n if (next === current) return\n setUncontrolled(next)\n onValueChange?.(next)\n }\n\n return (\n <Context.Provider value={{ value: current, select }}>\n <View\n accessibilityRole=\"radiogroup\"\n className={cn(\n 'flex-row items-start gap-1 self-start rounded-md bg-bg-tertiary p-1',\n className,\n )}\n {...props}\n />\n </Context.Provider>\n )\n}\n\nexport type SegmentedItemProps = Omit<PressableProps, 'style' | 'children' | 'onPress'> & {\n className?: string\n value: string\n children: ReactNode\n}\n\nexport function SegmentedItem({\n className,\n value,\n children,\n disabled,\n accessibilityState,\n ...props\n}: SegmentedItemProps) {\n const group = useContext(Context)\n if (!group) throw new Error('SegmentedItem deve ser usado dentro de SegmentedControl')\n\n const checked = group.value === value\n const isDisabled = Boolean(disabled)\n\n return (\n <Pressable\n accessibilityRole=\"radio\"\n accessibilityState={{ ...accessibilityState, checked, disabled: isDisabled }}\n disabled={isDisabled}\n onPress={() => group.select(value)}\n className={cn(\n 'items-center justify-center rounded-md px-4 py-2',\n checked ? 'bg-surface-primary shadow-sm' : 'active:bg-bg-secondary',\n className,\n )}\n {...props}\n >\n {typeof children === 'string' ? (\n <Text\n className={cn(\n 'text-sm leading-5 text-text-secondary',\n checked && 'font-medium text-text-primary',\n isDisabled && 'text-text-disabled',\n )}\n >\n {children}\n </Text>\n ) : (\n children\n )}\n </Pressable>\n )\n}\n"],"mappings":";;;;;AAQA,MAAM,UAAU,cAAuC,IAAI;;AAc3D,SAAgB,iBAAiB,EAC/B,WACA,OACA,cACA,eACA,GAAG,SACqB;CACxB,MAAM,CAAC,cAAc,mBAAmB,SAAS,YAAY;CAC7D,MAAM,UAAU,SAAS;CAEzB,MAAM,UAAU,SAAiB;EAC/B,IAAI,SAAS,SAAS;EACtB,gBAAgB,IAAI;EACpB,gBAAgB,IAAI;CACtB;CAEA,OACE,oBAAC,QAAQ,UAAT;EAAkB,OAAO;GAAE,OAAO;GAAS;EAAO;EAChD,UAAA,oBAAC,MAAD;GACE,mBAAkB;GAClB,WAAW,GACT,uEACA,SACF;GACA,GAAI;EACL,CAAA;CACe,CAAA;AAEtB;AAQA,SAAgB,cAAc,EAC5B,WACA,OACA,UACA,UACA,oBACA,GAAG,SACkB;CACrB,MAAM,QAAQ,WAAW,OAAO;CAChC,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,yDAAyD;CAErF,MAAM,UAAU,MAAM,UAAU;CAChC,MAAM,aAAa,QAAQ,QAAQ;CAEnC,OACE,oBAAC,WAAD;EACE,mBAAkB;EAClB,oBAAoB;GAAE,GAAG;GAAoB;GAAS,UAAU;EAAW;EAC3E,UAAU;EACV,eAAe,MAAM,OAAO,KAAK;EACjC,WAAW,GACT,oDACA,UAAU,iCAAiC,0BAC3C,SACF;EACA,GAAI;EAEH,UAAA,OAAO,aAAa,WACnB,oBAAC,MAAD;GACE,WAAW,GACT,yCACA,WAAW,iCACX,cAAc,oBAChB;GAEC;EACG,CAAA,IAEN;CAEO,CAAA;AAEf"}
1
+ {"version":3,"file":"segmented-control.js","names":[],"sources":["../../../src/components/segmented-control/segmented-control.tsx"],"sourcesContent":["/// <reference types=\"uniwind/types\" />\n\nimport { createContext, type ReactNode, useContext, useState } from 'react'\nimport { Pressable, type PressableProps, Text, View, type ViewProps } from 'react-native'\nimport { cn } from '../../utils/cn'\nimport { type Rect, SlidingIndicator, useItemLayouts } from '../_shared/sliding-indicator'\n\ntype SegmentedContext = {\n value: string | undefined\n select: (value: string) => void\n /** O pill já mediu o item marcado: os itens deixam de desenhar o próprio fundo. */\n slid: boolean\n setLayout: (value: string, rect: Rect) => void\n}\n\nconst Context = createContext<SegmentedContext | null>(null)\n\nexport type SegmentedControlProps = Omit<ViewProps, 'style'> & {\n className?: string\n /** Obrigatório: descreve o que o controle escolhe (ex.: \"Modo de exibição\"). */\n accessibilityLabel: string\n /** Controlado. Sem ele, o componente guarda o próprio estado (começa em `defaultValue`). */\n value?: string\n defaultValue?: string\n onValueChange?: (value: string) => void\n children: ReactNode\n}\n\n/** Escolha única entre 2–5 views do mesmo conteúdo. Para navegar entre seções, use Tabs. */\nexport function SegmentedControl({\n className,\n value,\n defaultValue,\n onValueChange,\n children,\n ...props\n}: SegmentedControlProps) {\n const [uncontrolled, setUncontrolled] = useState(defaultValue)\n const current = value ?? uncontrolled\n const { layouts, setLayout } = useItemLayouts()\n const active = current === undefined ? undefined : layouts[current]\n\n const select = (next: string) => {\n if (next === current) return\n setUncontrolled(next)\n onValueChange?.(next)\n }\n\n return (\n <Context.Provider value={{ value: current, select, slid: active !== undefined, setLayout }}>\n <View\n accessibilityRole=\"radiogroup\"\n className={cn(\n 'flex-row items-start gap-1 self-start rounded-md bg-bg-tertiary p-1',\n className,\n )}\n {...props}\n >\n {/* o fundo do item marcado é do indicador: fica sob os itens e desliza para o próximo */}\n <SlidingIndicator target={active} className=\"rounded-md bg-surface-primary shadow-sm\" />\n {children}\n </View>\n </Context.Provider>\n )\n}\n\nexport type SegmentedItemProps = Omit<PressableProps, 'style' | 'children' | 'onPress'> & {\n className?: string\n value: string\n children: ReactNode\n}\n\nexport function SegmentedItem({\n className,\n value,\n children,\n disabled,\n accessibilityState,\n onLayout,\n ...props\n}: SegmentedItemProps) {\n const group = useContext(Context)\n if (!group) throw new Error('SegmentedItem deve ser usado dentro de SegmentedControl')\n\n const checked = group.value === value\n const isDisabled = Boolean(disabled)\n\n return (\n <Pressable\n accessibilityRole=\"radio\"\n accessibilityState={{ ...accessibilityState, checked, disabled: isDisabled }}\n disabled={isDisabled}\n onPress={() => group.select(value)}\n onLayout={(event) => {\n group.setLayout(value, event.nativeEvent.layout)\n onLayout?.(event)\n }}\n className={cn(\n 'items-center justify-center rounded-md px-4 py-2',\n checked ? !group.slid && 'bg-surface-primary shadow-sm' : 'active:bg-bg-secondary',\n className,\n )}\n {...props}\n >\n {typeof children === 'string' ? (\n <Text\n className={cn(\n 'text-sm leading-5 text-text-secondary',\n checked && 'font-medium text-text-primary',\n isDisabled && 'text-text-disabled',\n )}\n >\n {children}\n </Text>\n ) : (\n children\n )}\n </Pressable>\n )\n}\n"],"mappings":";;;;;;AAeA,MAAM,UAAU,cAAuC,IAAI;;AAc3D,SAAgB,iBAAiB,EAC/B,WACA,OACA,cACA,eACA,UACA,GAAG,SACqB;CACxB,MAAM,CAAC,cAAc,mBAAmB,SAAS,YAAY;CAC7D,MAAM,UAAU,SAAS;CACzB,MAAM,EAAE,SAAS,cAAc,eAAe;CAC9C,MAAM,SAAS,YAAY,KAAA,IAAY,KAAA,IAAY,QAAQ;CAE3D,MAAM,UAAU,SAAiB;EAC/B,IAAI,SAAS,SAAS;EACtB,gBAAgB,IAAI;EACpB,gBAAgB,IAAI;CACtB;CAEA,OACE,oBAAC,QAAQ,UAAT;EAAkB,OAAO;GAAE,OAAO;GAAS;GAAQ,MAAM,WAAW,KAAA;GAAW;EAAU;EACvF,UAAA,qBAAC,MAAD;GACE,mBAAkB;GAClB,WAAW,GACT,uEACA,SACF;GACA,GAAI;GANN,UAAA,CASE,oBAAC,kBAAD;IAAkB,QAAQ;IAAQ,WAAU;GAA2C,CAAA,GACtF,QACG;;CACU,CAAA;AAEtB;AAQA,SAAgB,cAAc,EAC5B,WACA,OACA,UACA,UACA,oBACA,UACA,GAAG,SACkB;CACrB,MAAM,QAAQ,WAAW,OAAO;CAChC,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,yDAAyD;CAErF,MAAM,UAAU,MAAM,UAAU;CAChC,MAAM,aAAa,QAAQ,QAAQ;CAEnC,OACE,oBAAC,WAAD;EACE,mBAAkB;EAClB,oBAAoB;GAAE,GAAG;GAAoB;GAAS,UAAU;EAAW;EAC3E,UAAU;EACV,eAAe,MAAM,OAAO,KAAK;EACjC,WAAW,UAAU;GACnB,MAAM,UAAU,OAAO,MAAM,YAAY,MAAM;GAC/C,WAAW,KAAK;EAClB;EACA,WAAW,GACT,oDACA,UAAU,CAAC,MAAM,QAAQ,iCAAiC,0BAC1D,SACF;EACA,GAAI;EAEH,UAAA,OAAO,aAAa,WACnB,oBAAC,MAAD;GACE,WAAW,GACT,yCACA,WAAW,iCACX,cAAc,oBAChB;GAEC;EACG,CAAA,IAEN;CAEO,CAAA;AAEf"}
@@ -15,7 +15,7 @@ export type TabListProps = Omit<ScrollViewProps, 'horizontal' | 'style'> & {
15
15
  className?: string;
16
16
  };
17
17
  /** Lista de gatilhos. Rola na horizontal quando as abas não cabem na tela. */
18
- export declare function TabList({ className, ...props }: TabListProps): import("react").JSX.Element;
18
+ export declare function TabList({ className, children, ...props }: TabListProps): import("react").JSX.Element;
19
19
  export type TabProps = Omit<PressableProps, 'children' | 'style'> & {
20
20
  className?: string;
21
21
  /** Valor que liga a aba ao `TabPanel` de mesmo `value`. */
@@ -24,7 +24,7 @@ export type TabProps = Omit<PressableProps, 'children' | 'style'> & {
24
24
  icon?: ReactNode;
25
25
  children?: ReactNode;
26
26
  };
27
- export declare function Tab({ className, value, icon, children, disabled, onPress, accessibilityState, ...props }: TabProps): import("react").JSX.Element;
27
+ export declare function Tab({ className, value, icon, children, disabled, onPress, onLayout, accessibilityState, ...props }: TabProps): import("react").JSX.Element;
28
28
  export type TabPanelProps = ViewProps & {
29
29
  className?: string;
30
30
  value: string;
@@ -1,26 +1,34 @@
1
1
  import { cn } from "../../utils/cn.js";
2
+ import { SlidingIndicator, useItemLayouts } from "../_shared/sliding-indicator.js";
2
3
  import { createContext, useContext, useMemo, useState } from "react";
3
4
  import { Pressable, ScrollView, Text, View } from "react-native";
4
5
  import { jsx, jsxs } from "react/jsx-runtime";
5
6
  //#region src/components/tabs/tabs.tsx
6
7
  const TabsContext = createContext({
7
8
  value: void 0,
8
- setValue: () => {}
9
+ setValue: () => {},
10
+ layouts: {},
11
+ setLayout: () => {}
9
12
  });
10
13
  /** Raiz das abas: controla qual `Tab` está ativa (`value`/`defaultValue`/`onValueChange`). */
11
14
  function Tabs({ className, value, defaultValue, onValueChange, ...props }) {
12
15
  const [internal, setInternal] = useState(defaultValue);
13
16
  const current = value !== void 0 ? value : internal;
17
+ const { layouts, setLayout } = useItemLayouts();
14
18
  const context = useMemo(() => ({
15
19
  value: current,
16
20
  setValue: (next) => {
17
21
  if (value === void 0) setInternal(next);
18
22
  onValueChange?.(next);
19
- }
23
+ },
24
+ layouts,
25
+ setLayout
20
26
  }), [
21
27
  current,
22
28
  value,
23
- onValueChange
29
+ onValueChange,
30
+ layouts,
31
+ setLayout
24
32
  ]);
25
33
  return /* @__PURE__ */ jsx(TabsContext.Provider, {
26
34
  value: context,
@@ -31,19 +39,30 @@ function Tabs({ className, value, defaultValue, onValueChange, ...props }) {
31
39
  });
32
40
  }
33
41
  /** Lista de gatilhos. Rola na horizontal quando as abas não cabem na tela. */
34
- function TabList({ className, ...props }) {
35
- return /* @__PURE__ */ jsx(ScrollView, {
42
+ function TabList({ className, children, ...props }) {
43
+ const { value, layouts } = useContext(TabsContext);
44
+ const active = value === void 0 ? void 0 : layouts[value];
45
+ return /* @__PURE__ */ jsxs(ScrollView, {
36
46
  horizontal: true,
37
47
  showsHorizontalScrollIndicator: false,
38
48
  accessibilityRole: "tablist",
39
49
  className: cn("grow-0", className),
40
50
  contentContainerClassName: "items-start",
41
- ...props
51
+ ...props,
52
+ children: [/* @__PURE__ */ jsx(SlidingIndicator, {
53
+ target: active && {
54
+ ...active,
55
+ y: active.y + active.height - 2,
56
+ height: 2
57
+ },
58
+ className: "bg-interactive-primary"
59
+ }), children]
42
60
  });
43
61
  }
44
- function Tab({ className, value, icon, children, disabled, onPress, accessibilityState, ...props }) {
45
- const { value: active, setValue } = useContext(TabsContext);
62
+ function Tab({ className, value, icon, children, disabled, onPress, onLayout, accessibilityState, ...props }) {
63
+ const { value: active, setValue, layouts, setLayout } = useContext(TabsContext);
46
64
  const selected = active === value;
65
+ const slid = active !== void 0 && layouts[active] !== void 0;
47
66
  return /* @__PURE__ */ jsxs(Pressable, {
48
67
  accessibilityRole: "tab",
49
68
  accessibilityState: {
@@ -56,6 +75,10 @@ function Tab({ className, value, icon, children, disabled, onPress, accessibilit
56
75
  setValue(value);
57
76
  onPress?.(event);
58
77
  },
78
+ onLayout: (event) => {
79
+ setLayout(value, event.nativeEvent.layout);
80
+ onLayout?.(event);
81
+ },
59
82
  className: cn("items-start gap-[6px] px-3 pt-2", className),
60
83
  ...props,
61
84
  children: [/* @__PURE__ */ jsxs(View, {
@@ -64,7 +87,7 @@ function Tab({ className, value, icon, children, disabled, onPress, accessibilit
64
87
  className: cn("text-sm text-text-secondary", selected && "font-medium text-text-brand", disabled && "text-text-disabled"),
65
88
  children
66
89
  }) : children]
67
- }), /* @__PURE__ */ jsx(View, { className: cn("h-0.5 self-stretch bg-transparent", selected && "bg-interactive-primary") })]
90
+ }), /* @__PURE__ */ jsx(View, { className: cn("h-0.5 self-stretch bg-transparent", selected && !slid && "bg-interactive-primary") })]
68
91
  });
69
92
  }
70
93
  /** Painel da aba: só é montado enquanto a aba de mesmo `value` está ativa (igual ao web). */
@@ -1 +1 @@
1
- {"version":3,"file":"tabs.js","names":[],"sources":["../../../src/components/tabs/tabs.tsx"],"sourcesContent":["/// <reference types=\"uniwind/types\" />\n\nimport { createContext, type ReactNode, useContext, useMemo, useState } from 'react'\nimport {\n Pressable,\n type PressableProps,\n ScrollView,\n type ScrollViewProps,\n Text,\n View,\n type ViewProps,\n} from 'react-native'\nimport { cn } from '../../utils/cn'\n\ntype TabsContextValue = { value: string | undefined; setValue: (value: string) => void }\n\nconst TabsContext = createContext<TabsContextValue>({ value: undefined, setValue: () => {} })\n\nexport type TabsProps = ViewProps & {\n className?: string\n /** Aba ativa (controlado). */\n value?: string\n /** Aba ativa inicial (não controlado). */\n defaultValue?: string\n onValueChange?: (value: string) => void\n}\n\n/** Raiz das abas: controla qual `Tab` está ativa (`value`/`defaultValue`/`onValueChange`). */\nexport function Tabs({ className, value, defaultValue, onValueChange, ...props }: TabsProps) {\n const [internal, setInternal] = useState(defaultValue)\n const current = value !== undefined ? value : internal\n\n const context = useMemo(\n () => ({\n value: current,\n setValue: (next: string) => {\n if (value === undefined) setInternal(next)\n onValueChange?.(next)\n },\n }),\n [current, value, onValueChange],\n )\n\n return (\n <TabsContext.Provider value={context}>\n <View className={className} {...props} />\n </TabsContext.Provider>\n )\n}\n\nexport type TabListProps = Omit<ScrollViewProps, 'horizontal' | 'style'> & {\n className?: string\n}\n\n/** Lista de gatilhos. Rola na horizontal quando as abas não cabem na tela. */\nexport function TabList({ className, ...props }: TabListProps) {\n return (\n <ScrollView\n horizontal\n showsHorizontalScrollIndicator={false}\n accessibilityRole=\"tablist\"\n className={cn('grow-0', className)}\n contentContainerClassName=\"items-start\"\n {...props}\n />\n )\n}\n\nexport type TabProps = Omit<PressableProps, 'children' | 'style'> & {\n className?: string\n /** Valor que liga a aba ao `TabPanel` de mesmo `value`. */\n value: string\n /** Ícone à esquerda do rótulo (ex.: um ícone SVG de 24px). */\n icon?: ReactNode\n children?: ReactNode\n}\n\nexport function Tab({\n className,\n value,\n icon,\n children,\n disabled,\n onPress,\n accessibilityState,\n ...props\n}: TabProps) {\n const { value: active, setValue } = useContext(TabsContext)\n const selected = active === value\n\n return (\n <Pressable\n accessibilityRole=\"tab\"\n accessibilityState={{ ...accessibilityState, selected, disabled: Boolean(disabled) }}\n disabled={disabled}\n onPress={(event) => {\n setValue(value)\n onPress?.(event)\n }}\n className={cn('items-start gap-[6px] px-3 pt-2', className)}\n {...props}\n >\n <View className=\"flex-row items-center gap-1\">\n {icon}\n {typeof children === 'string' ? (\n <Text\n className={cn(\n 'text-sm text-text-secondary',\n selected && 'font-medium text-text-brand',\n disabled && 'text-text-disabled',\n )}\n >\n {children}\n </Text>\n ) : (\n children\n )}\n </View>\n <View\n className={cn('h-0.5 self-stretch bg-transparent', selected && 'bg-interactive-primary')}\n />\n </Pressable>\n )\n}\n\nexport type TabPanelProps = ViewProps & {\n className?: string\n value: string\n}\n\n/** Painel da aba: só é montado enquanto a aba de mesmo `value` está ativa (igual ao web). */\nexport function TabPanel({ className, value, ...props }: TabPanelProps) {\n const { value: active } = useContext(TabsContext)\n if (active !== value) return null\n return <View className={className} {...props} />\n}\n"],"mappings":";;;;;AAgBA,MAAM,cAAc,cAAgC;CAAE,OAAO,KAAA;CAAW,gBAAgB,CAAC;AAAE,CAAC;;AAY5F,SAAgB,KAAK,EAAE,WAAW,OAAO,cAAc,eAAe,GAAG,SAAoB;CAC3F,MAAM,CAAC,UAAU,eAAe,SAAS,YAAY;CACrD,MAAM,UAAU,UAAU,KAAA,IAAY,QAAQ;CAE9C,MAAM,UAAU,eACP;EACL,OAAO;EACP,WAAW,SAAiB;GAC1B,IAAI,UAAU,KAAA,GAAW,YAAY,IAAI;GACzC,gBAAgB,IAAI;EACtB;CACF,IACA;EAAC;EAAS;EAAO;CAAa,CAChC;CAEA,OACE,oBAAC,YAAY,UAAb;EAAsB,OAAO;EAC3B,UAAA,oBAAC,MAAD;GAAiB;GAAW,GAAI;EAAQ,CAAA;CACpB,CAAA;AAE1B;;AAOA,SAAgB,QAAQ,EAAE,WAAW,GAAG,SAAuB;CAC7D,OACE,oBAAC,YAAD;EACE,YAAA;EACA,gCAAgC;EAChC,mBAAkB;EAClB,WAAW,GAAG,UAAU,SAAS;EACjC,2BAA0B;EAC1B,GAAI;CACL,CAAA;AAEL;AAWA,SAAgB,IAAI,EAClB,WACA,OACA,MACA,UACA,UACA,SACA,oBACA,GAAG,SACQ;CACX,MAAM,EAAE,OAAO,QAAQ,aAAa,WAAW,WAAW;CAC1D,MAAM,WAAW,WAAW;CAE5B,OACE,qBAAC,WAAD;EACE,mBAAkB;EAClB,oBAAoB;GAAE,GAAG;GAAoB;GAAU,UAAU,QAAQ,QAAQ;EAAE;EACzE;EACV,UAAU,UAAU;GAClB,SAAS,KAAK;GACd,UAAU,KAAK;EACjB;EACA,WAAW,GAAG,mCAAmC,SAAS;EAC1D,GAAI;EATN,UAAA,CAWE,qBAAC,MAAD;GAAM,WAAU;GAAhB,UAAA,CACG,MACA,OAAO,aAAa,WACnB,oBAAC,MAAD;IACE,WAAW,GACT,+BACA,YAAY,+BACZ,YAAY,oBACd;IAEC;GACG,CAAA,IAEN,QAEE;EACN,CAAA,GAAA,oBAAC,MAAD,EACE,WAAW,GAAG,qCAAqC,YAAY,wBAAwB,EACxF,CAAA,CACQ;;AAEf;;AAQA,SAAgB,SAAS,EAAE,WAAW,OAAO,GAAG,SAAwB;CACtE,MAAM,EAAE,OAAO,WAAW,WAAW,WAAW;CAChD,IAAI,WAAW,OAAO,OAAO;CAC7B,OAAO,oBAAC,MAAD;EAAiB;EAAW,GAAI;CAAQ,CAAA;AACjD"}
1
+ {"version":3,"file":"tabs.js","names":[],"sources":["../../../src/components/tabs/tabs.tsx"],"sourcesContent":["/// <reference types=\"uniwind/types\" />\n\nimport { createContext, type ReactNode, useContext, useMemo, useState } from 'react'\nimport {\n Pressable,\n type PressableProps,\n ScrollView,\n type ScrollViewProps,\n Text,\n View,\n type ViewProps,\n} from 'react-native'\nimport { cn } from '../../utils/cn'\nimport { type Rect, SlidingIndicator, useItemLayouts } from '../_shared/sliding-indicator'\n\ntype TabsContextValue = {\n value: string | undefined\n setValue: (value: string) => void\n /** Posição de cada aba no contêiner da lista, para o indicador deslizante. */\n layouts: Record<string, Rect>\n setLayout: (value: string, rect: Rect) => void\n}\n\nconst TabsContext = createContext<TabsContextValue>({\n value: undefined,\n setValue: () => {},\n layouts: {},\n setLayout: () => {},\n})\n\nexport type TabsProps = ViewProps & {\n className?: string\n /** Aba ativa (controlado). */\n value?: string\n /** Aba ativa inicial (não controlado). */\n defaultValue?: string\n onValueChange?: (value: string) => void\n}\n\n/** Raiz das abas: controla qual `Tab` está ativa (`value`/`defaultValue`/`onValueChange`). */\nexport function Tabs({ className, value, defaultValue, onValueChange, ...props }: TabsProps) {\n const [internal, setInternal] = useState(defaultValue)\n const current = value !== undefined ? value : internal\n\n const { layouts, setLayout } = useItemLayouts()\n const context = useMemo(\n () => ({\n value: current,\n setValue: (next: string) => {\n if (value === undefined) setInternal(next)\n onValueChange?.(next)\n },\n layouts,\n setLayout,\n }),\n [current, value, onValueChange, layouts, setLayout],\n )\n\n return (\n <TabsContext.Provider value={context}>\n <View className={className} {...props} />\n </TabsContext.Provider>\n )\n}\n\nexport type TabListProps = Omit<ScrollViewProps, 'horizontal' | 'style'> & {\n className?: string\n}\n\n/** Lista de gatilhos. Rola na horizontal quando as abas não cabem na tela. */\nexport function TabList({ className, children, ...props }: TabListProps) {\n const { value, layouts } = useContext(TabsContext)\n const active = value === undefined ? undefined : layouts[value]\n return (\n <ScrollView\n horizontal\n showsHorizontalScrollIndicator={false}\n accessibilityRole=\"tablist\"\n className={cn('grow-0', className)}\n contentContainerClassName=\"items-start\"\n {...props}\n >\n {/* a linha de 2px é do indicador: fica na base da aba ativa e desliza para a próxima */}\n <SlidingIndicator\n target={active && { ...active, y: active.y + active.height - 2, height: 2 }}\n className=\"bg-interactive-primary\"\n />\n {children}\n </ScrollView>\n )\n}\n\nexport type TabProps = Omit<PressableProps, 'children' | 'style'> & {\n className?: string\n /** Valor que liga a aba ao `TabPanel` de mesmo `value`. */\n value: string\n /** Ícone à esquerda do rótulo (ex.: um ícone SVG de 24px). */\n icon?: ReactNode\n children?: ReactNode\n}\n\nexport function Tab({\n className,\n value,\n icon,\n children,\n disabled,\n onPress,\n onLayout,\n accessibilityState,\n ...props\n}: TabProps) {\n const { value: active, setValue, layouts, setLayout } = useContext(TabsContext)\n const selected = active === value\n // antes de o indicador medir a aba ativa, ela ainda se desenha sozinha\n const slid = active !== undefined && layouts[active] !== undefined\n\n return (\n <Pressable\n accessibilityRole=\"tab\"\n accessibilityState={{ ...accessibilityState, selected, disabled: Boolean(disabled) }}\n disabled={disabled}\n onPress={(event) => {\n setValue(value)\n onPress?.(event)\n }}\n onLayout={(event) => {\n setLayout(value, event.nativeEvent.layout)\n onLayout?.(event)\n }}\n className={cn('items-start gap-[6px] px-3 pt-2', className)}\n {...props}\n >\n <View className=\"flex-row items-center gap-1\">\n {icon}\n {typeof children === 'string' ? (\n <Text\n className={cn(\n 'text-sm text-text-secondary',\n selected && 'font-medium text-text-brand',\n disabled && 'text-text-disabled',\n )}\n >\n {children}\n </Text>\n ) : (\n children\n )}\n </View>\n <View\n className={cn(\n 'h-0.5 self-stretch bg-transparent',\n selected && !slid && 'bg-interactive-primary',\n )}\n />\n </Pressable>\n )\n}\n\nexport type TabPanelProps = ViewProps & {\n className?: string\n value: string\n}\n\n/** Painel da aba: só é montado enquanto a aba de mesmo `value` está ativa (igual ao web). */\nexport function TabPanel({ className, value, ...props }: TabPanelProps) {\n const { value: active } = useContext(TabsContext)\n if (active !== value) return null\n return <View className={className} {...props} />\n}\n"],"mappings":";;;;;;AAuBA,MAAM,cAAc,cAAgC;CAClD,OAAO,KAAA;CACP,gBAAgB,CAAC;CACjB,SAAS,CAAC;CACV,iBAAiB,CAAC;AACpB,CAAC;;AAYD,SAAgB,KAAK,EAAE,WAAW,OAAO,cAAc,eAAe,GAAG,SAAoB;CAC3F,MAAM,CAAC,UAAU,eAAe,SAAS,YAAY;CACrD,MAAM,UAAU,UAAU,KAAA,IAAY,QAAQ;CAE9C,MAAM,EAAE,SAAS,cAAc,eAAe;CAC9C,MAAM,UAAU,eACP;EACL,OAAO;EACP,WAAW,SAAiB;GAC1B,IAAI,UAAU,KAAA,GAAW,YAAY,IAAI;GACzC,gBAAgB,IAAI;EACtB;EACA;EACA;CACF,IACA;EAAC;EAAS;EAAO;EAAe;EAAS;CAAS,CACpD;CAEA,OACE,oBAAC,YAAY,UAAb;EAAsB,OAAO;EAC3B,UAAA,oBAAC,MAAD;GAAiB;GAAW,GAAI;EAAQ,CAAA;CACpB,CAAA;AAE1B;;AAOA,SAAgB,QAAQ,EAAE,WAAW,UAAU,GAAG,SAAuB;CACvE,MAAM,EAAE,OAAO,YAAY,WAAW,WAAW;CACjD,MAAM,SAAS,UAAU,KAAA,IAAY,KAAA,IAAY,QAAQ;CACzD,OACE,qBAAC,YAAD;EACE,YAAA;EACA,gCAAgC;EAChC,mBAAkB;EAClB,WAAW,GAAG,UAAU,SAAS;EACjC,2BAA0B;EAC1B,GAAI;EANN,UAAA,CASE,oBAAC,kBAAD;GACE,QAAQ,UAAU;IAAE,GAAG;IAAQ,GAAG,OAAO,IAAI,OAAO,SAAS;IAAG,QAAQ;GAAE;GAC1E,WAAU;EACX,CAAA,GACA,QACS;;AAEhB;AAWA,SAAgB,IAAI,EAClB,WACA,OACA,MACA,UACA,UACA,SACA,UACA,oBACA,GAAG,SACQ;CACX,MAAM,EAAE,OAAO,QAAQ,UAAU,SAAS,cAAc,WAAW,WAAW;CAC9E,MAAM,WAAW,WAAW;CAE5B,MAAM,OAAO,WAAW,KAAA,KAAa,QAAQ,YAAY,KAAA;CAEzD,OACE,qBAAC,WAAD;EACE,mBAAkB;EAClB,oBAAoB;GAAE,GAAG;GAAoB;GAAU,UAAU,QAAQ,QAAQ;EAAE;EACzE;EACV,UAAU,UAAU;GAClB,SAAS,KAAK;GACd,UAAU,KAAK;EACjB;EACA,WAAW,UAAU;GACnB,UAAU,OAAO,MAAM,YAAY,MAAM;GACzC,WAAW,KAAK;EAClB;EACA,WAAW,GAAG,mCAAmC,SAAS;EAC1D,GAAI;EAbN,UAAA,CAeE,qBAAC,MAAD;GAAM,WAAU;GAAhB,UAAA,CACG,MACA,OAAO,aAAa,WACnB,oBAAC,MAAD;IACE,WAAW,GACT,+BACA,YAAY,+BACZ,YAAY,oBACd;IAEC;GACG,CAAA,IAEN,QAEE;EACN,CAAA,GAAA,oBAAC,MAAD,EACE,WAAW,GACT,qCACA,YAAY,CAAC,QAAQ,wBACvB,EACD,CAAA,CACQ;;AAEf;;AAQA,SAAgB,SAAS,EAAE,WAAW,OAAO,GAAG,SAAwB;CACtE,MAAM,EAAE,OAAO,WAAW,WAAW,WAAW;CAChD,IAAI,WAAW,OAAO,OAAO;CAC7B,OAAO,oBAAC,MAAD;EAAiB;EAAW,GAAI;CAAQ,CAAA;AACjD"}
@@ -1,11 +1,16 @@
1
1
  import { CloseIcon, StatusIcon, statusRole, statusText, statusTextToken } from "../_shared/status.js";
2
2
  import { useToken } from "../_shared/use-token.js";
3
3
  import { Button } from "../button/button.js";
4
+ import { useReducedMotion } from "../_shared/sliding-indicator.js";
4
5
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
5
- import { Animated, Platform, Pressable, StatusBar, Text, View } from "react-native";
6
+ import { Animated, LayoutAnimation, PanResponder, Platform, Pressable, StatusBar, Text, UIManager, View } from "react-native";
6
7
  import { jsx, jsxs } from "react/jsx-runtime";
7
8
  //#region src/components/toast/toast.tsx
8
9
  const ToastContext = createContext(null);
10
+ if (Platform.OS === "android") UIManager.setLayoutAnimationEnabledExperimental?.(true);
11
+ /** Arrastar o toast para o lado além disto (px), ou mais rápido que isto (px/ms), o dispensa. */
12
+ const SWIPE_DISTANCE = 50;
13
+ const SWIPE_VELOCITY = .5;
9
14
  const topInset = Platform.OS === "ios" ? 56 : (StatusBar.currentHeight ?? 24) + 8;
10
15
  /**
11
16
  * Envolva o app uma vez; qualquer componente abaixo dispara toasts com `useToast()`. A pilha fica
@@ -20,9 +25,11 @@ function ToastProvider({ children, label = "Notificações", dismissLabel = "Fec
20
25
  closing: true
21
26
  } : item));
22
27
  }, []);
28
+ const reduced = useReducedMotion();
23
29
  const remove = useCallback((id) => {
30
+ if (!reduced) LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
24
31
  setItems((current) => current.filter((item) => item.id !== id));
25
- }, []);
32
+ }, [reduced]);
26
33
  const toast = useCallback((options) => {
27
34
  const id = nextId.current++;
28
35
  setItems((current) => [...current, {
@@ -67,6 +74,58 @@ function ToastProvider({ children, label = "Notificações", dismissLabel = "Fec
67
74
  function ToastView({ item, dismissLabel, onDismiss, onRemove }) {
68
75
  const { id, status = "info", title, description, action, duration = 5e3, showIcon = true, dismissible = true, closing } = item;
69
76
  const progress = useRef(new Animated.Value(0)).current;
77
+ const dragX = useRef(new Animated.Value(0)).current;
78
+ const [dragging, setDragging] = useState(false);
79
+ const latest = useRef({
80
+ id,
81
+ onDismiss
82
+ });
83
+ latest.current = {
84
+ id,
85
+ onDismiss
86
+ };
87
+ const panResponder = useRef(PanResponder.create({
88
+ onMoveShouldSetPanResponder: (_, g) => Math.abs(g.dx) > 6 && Math.abs(g.dx) > Math.abs(g.dy),
89
+ onPanResponderTerminationRequest: () => false,
90
+ onPanResponderGrant: () => setDragging(true),
91
+ onPanResponderMove: (_, g) => dragX.setValue(g.dx),
92
+ onPanResponderRelease: (_, g) => {
93
+ setDragging(false);
94
+ if (Math.abs(g.dx) > SWIPE_DISTANCE || Math.abs(g.vx) > SWIPE_VELOCITY) {
95
+ Animated.timing(dragX, {
96
+ toValue: (g.dx || g.vx) > 0 ? 400 : -400,
97
+ duration: 150,
98
+ useNativeDriver: true
99
+ }).start();
100
+ latest.current.onDismiss(latest.current.id);
101
+ } else Animated.timing(dragX, {
102
+ toValue: 0,
103
+ duration: 200,
104
+ useNativeDriver: true
105
+ }).start();
106
+ },
107
+ onPanResponderTerminate: () => {
108
+ setDragging(false);
109
+ Animated.timing(dragX, {
110
+ toValue: 0,
111
+ duration: 200,
112
+ useNativeDriver: true
113
+ }).start();
114
+ }
115
+ })).current;
116
+ const swipeFade = dragX.interpolate({
117
+ inputRange: [
118
+ -150,
119
+ 0,
120
+ 150
121
+ ],
122
+ outputRange: [
123
+ .2,
124
+ 1,
125
+ .2
126
+ ],
127
+ extrapolate: "clamp"
128
+ });
70
129
  const iconColor = useToken(statusTextToken[status]);
71
130
  const closeColor = useToken("--color-text-primary");
72
131
  useEffect(() => {
@@ -95,11 +154,12 @@ function ToastView({ item, dismissLabel, onDismiss, onRemove }) {
95
154
  progress
96
155
  ]);
97
156
  useEffect(() => {
98
- if (closing || duration <= 0) return;
157
+ if (closing || dragging || duration <= 0) return;
99
158
  const timer = setTimeout(() => onDismiss(id), duration);
100
159
  return () => clearTimeout(timer);
101
160
  }, [
102
161
  closing,
162
+ dragging,
103
163
  duration,
104
164
  id,
105
165
  onDismiss
@@ -109,9 +169,10 @@ function ToastView({ item, dismissLabel, onDismiss, onRemove }) {
109
169
  outputRange: [-16, 0]
110
170
  });
111
171
  return /* @__PURE__ */ jsx(Animated.View, {
172
+ ...panResponder.panHandlers,
112
173
  style: {
113
- opacity: progress,
114
- transform: [{ translateY }]
174
+ opacity: Animated.multiply(progress, swipeFade),
175
+ transform: [{ translateY }, { translateX: dragX }]
115
176
  },
116
177
  children: /* @__PURE__ */ jsxs(View, {
117
178
  accessibilityRole: statusRole(status),
@@ -1 +1 @@
1
- {"version":3,"file":"toast.js","names":[],"sources":["../../../src/components/toast/toast.tsx"],"sourcesContent":["/// <reference types=\"uniwind/types\" />\n\nimport {\n createContext,\n type ReactNode,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { Animated, Platform, Pressable, StatusBar, Text, View } from 'react-native'\nimport {\n CloseIcon,\n type Status,\n StatusIcon,\n statusRole,\n statusText,\n statusTextToken,\n} from '../_shared/status'\nimport { useToken } from '../_shared/use-token'\nimport { Button } from '../button/button'\n\nexport type ToastOptions = {\n status?: Status\n /** Resumo (opcional). */\n title?: string\n description: string\n /** Ação contextual (ex.: \"Desfazer\"). Tocar nela também fecha o toast. */\n action?: { label: string; onClick: () => void }\n /** Tempo até fechar sozinho, em ms. `0` não fecha sozinho. Padrão: 5000. */\n duration?: number\n /** Mostra o ícone do status. Padrão: `true`. */\n showIcon?: boolean\n /** Mostra o botão de fechar (×). Sem ele, o toast ainda fecha pelo `duration`. Padrão: `true`. */\n dismissible?: boolean\n}\n\ntype ToastItem = ToastOptions & { id: number; closing: boolean }\n\ntype ToastContextValue = {\n /** Exibe um toast e devolve o id, para fechá-lo com `dismiss`. */\n toast: (options: ToastOptions) => number\n dismiss: (id: number) => void\n}\n\nconst ToastContext = createContext<ToastContextValue | null>(null)\n\nexport type ToastProviderProps = {\n children: ReactNode\n /** Nome acessível da região de notificações. */\n label?: string\n /** Nome acessível do botão de fechar de cada toast. */\n dismissLabel?: string\n testID?: string\n}\n\n// Sem react-native-safe-area-context: margem fixa para o notch no iOS e a barra de status no Android.\nconst topInset = Platform.OS === 'ios' ? 56 : (StatusBar.currentHeight ?? 24) + 8\n\n/**\n * Envolva o app uma vez; qualquer componente abaixo dispara toasts com `useToast()`. A pilha fica\n * no topo da tela, acima do conteúdo do app, mas abaixo de Modal/Drawer (que são janelas nativas).\n */\nexport function ToastProvider({\n children,\n label = 'Notificações',\n dismissLabel = 'Fechar',\n testID,\n}: ToastProviderProps) {\n const [items, setItems] = useState<ToastItem[]>([])\n const nextId = useRef(0)\n\n // Marca como \"closing\"; o próprio toast anima a saída e então chama `remove`.\n const dismiss = useCallback((id: number) => {\n setItems((current) =>\n current.map((item) => (item.id === id ? { ...item, closing: true } : item)),\n )\n }, [])\n const remove = useCallback((id: number) => {\n setItems((current) => current.filter((item) => item.id !== id))\n }, [])\n const toast = useCallback((options: ToastOptions) => {\n const id = nextId.current++\n setItems((current) => [...current, { ...options, id, closing: false }])\n return id\n }, [])\n const value = useMemo(() => ({ toast, dismiss }), [toast, dismiss])\n\n return (\n <ToastContext.Provider value={value}>\n {children}\n <View\n pointerEvents=\"box-none\"\n accessibilityLabel={label}\n testID={testID}\n style={{ position: 'absolute', top: 0, left: 0, right: 0, zIndex: 9999, elevation: 9999 }}\n >\n <View\n pointerEvents=\"box-none\"\n className=\"w-full max-w-[360px] gap-2 self-center px-4\"\n style={{ paddingTop: topInset }}\n >\n {items.map((item) => (\n <ToastView\n key={item.id}\n item={item}\n dismissLabel={dismissLabel}\n onDismiss={dismiss}\n onRemove={remove}\n />\n ))}\n </View>\n </View>\n </ToastContext.Provider>\n )\n}\n\nfunction ToastView({\n item,\n dismissLabel,\n onDismiss,\n onRemove,\n}: {\n item: ToastItem\n dismissLabel: string\n onDismiss: (id: number) => void\n onRemove: (id: number) => void\n}) {\n const {\n id,\n status = 'info',\n title,\n description,\n action,\n duration = 5000,\n showIcon = true,\n dismissible = true,\n closing,\n } = item\n const progress = useRef(new Animated.Value(0)).current\n const iconColor = useToken(statusTextToken[status])\n const closeColor = useToken('--color-text-primary')\n\n useEffect(() => {\n if (closing) return\n const animation = Animated.timing(progress, {\n toValue: 1,\n duration: 200,\n useNativeDriver: true,\n })\n animation.start()\n return () => animation.stop()\n }, [closing, progress])\n\n useEffect(() => {\n if (!closing) return\n const animation = Animated.timing(progress, {\n toValue: 0,\n duration: 150,\n useNativeDriver: true,\n })\n animation.start(({ finished }) => finished && onRemove(id))\n return () => animation.stop()\n }, [closing, id, onRemove, progress])\n\n useEffect(() => {\n if (closing || duration <= 0) return\n const timer = setTimeout(() => onDismiss(id), duration)\n return () => clearTimeout(timer)\n }, [closing, duration, id, onDismiss])\n\n const translateY = progress.interpolate({ inputRange: [0, 1], outputRange: [-16, 0] })\n\n return (\n <Animated.View style={{ opacity: progress, transform: [{ translateY }] }}>\n <View\n accessibilityRole={statusRole(status)}\n // Erro e aviso interrompem o leitor de tela; o resto é anunciado com calma.\n accessibilityLiveRegion={statusRole(status) ? 'assertive' : 'polite'}\n className=\"flex-row items-start gap-3 rounded-md border border-border-default bg-surface-elevated py-3 pr-3 pl-4 shadow-md\"\n style={{ elevation: 6 }}\n >\n {showIcon && <StatusIcon status={status} color={iconColor} />}\n <View className=\"min-w-0 flex-1 items-start gap-1\">\n {title ? (\n <Text className={`text-sm font-medium leading-5 ${statusText[status]}`}>{title}</Text>\n ) : null}\n <Text className=\"text-sm leading-5 text-text-primary\">{description}</Text>\n {action && (\n <Button\n variant=\"ghost\"\n onPress={() => {\n action.onClick()\n onDismiss(id)\n }}\n >\n {action.label}\n </Button>\n )}\n </View>\n {dismissible && (\n <Pressable\n accessibilityRole=\"button\"\n accessibilityLabel={dismissLabel}\n hitSlop={8}\n onPress={() => onDismiss(id)}\n className=\"size-8 items-center justify-center rounded-full active:bg-button-secondary-bg-hover\"\n >\n <CloseIcon color={closeColor} />\n </Pressable>\n )}\n </View>\n </Animated.View>\n )\n}\n\nexport function useToast(): ToastContextValue {\n const context = useContext(ToastContext)\n if (!context) throw new Error('useToast precisa estar dentro de <ToastProvider>.')\n return context\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"toast.js","names":[],"sources":["../../../src/components/toast/toast.tsx"],"sourcesContent":["/// <reference types=\"uniwind/types\" />\n\nimport {\n createContext,\n type ReactNode,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport {\n Animated,\n LayoutAnimation,\n PanResponder,\n Platform,\n Pressable,\n StatusBar,\n Text,\n UIManager,\n View,\n} from 'react-native'\nimport { useReducedMotion } from '../_shared/sliding-indicator'\nimport {\n CloseIcon,\n type Status,\n StatusIcon,\n statusRole,\n statusText,\n statusTextToken,\n} from '../_shared/status'\nimport { useToken } from '../_shared/use-token'\nimport { Button } from '../button/button'\n\nexport type ToastOptions = {\n status?: Status\n /** Resumo (opcional). */\n title?: string\n description: string\n /** Ação contextual (ex.: \"Desfazer\"). Tocar nela também fecha o toast. */\n action?: { label: string; onClick: () => void }\n /** Tempo até fechar sozinho, em ms. `0` não fecha sozinho. Padrão: 5000. */\n duration?: number\n /** Mostra o ícone do status. Padrão: `true`. */\n showIcon?: boolean\n /** Mostra o botão de fechar (×). Sem ele, o toast ainda fecha pelo `duration`. Padrão: `true`. */\n dismissible?: boolean\n}\n\ntype ToastItem = ToastOptions & { id: number; closing: boolean }\n\ntype ToastContextValue = {\n /** Exibe um toast e devolve o id, para fechá-lo com `dismiss`. */\n toast: (options: ToastOptions) => number\n dismiss: (id: number) => void\n}\n\nconst ToastContext = createContext<ToastContextValue | null>(null)\n\nexport type ToastProviderProps = {\n children: ReactNode\n /** Nome acessível da região de notificações. */\n label?: string\n /** Nome acessível do botão de fechar de cada toast. */\n dismissLabel?: string\n testID?: string\n}\n\n// No Android antigo o LayoutAnimation precisa ser habilitado. Na New Architecture a chamada é no-op.\nif (Platform.OS === 'android') {\n UIManager.setLayoutAnimationEnabledExperimental?.(true)\n}\n\n/** Arrastar o toast para o lado além disto (px), ou mais rápido que isto (px/ms), o dispensa. */\nconst SWIPE_DISTANCE = 50\nconst SWIPE_VELOCITY = 0.5\n\n// Sem react-native-safe-area-context: margem fixa para o notch no iOS e a barra de status no Android.\nconst topInset = Platform.OS === 'ios' ? 56 : (StatusBar.currentHeight ?? 24) + 8\n\n/**\n * Envolva o app uma vez; qualquer componente abaixo dispara toasts com `useToast()`. A pilha fica\n * no topo da tela, acima do conteúdo do app, mas abaixo de Modal/Drawer (que são janelas nativas).\n */\nexport function ToastProvider({\n children,\n label = 'Notificações',\n dismissLabel = 'Fechar',\n testID,\n}: ToastProviderProps) {\n const [items, setItems] = useState<ToastItem[]>([])\n const nextId = useRef(0)\n\n // Marca como \"closing\"; o próprio toast anima a saída e então chama `remove`.\n const dismiss = useCallback((id: number) => {\n setItems((current) =>\n current.map((item) => (item.id === id ? { ...item, closing: true } : item)),\n )\n }, [])\n const reduced = useReducedMotion()\n const remove = useCallback(\n (id: number) => {\n // os de baixo deslizam para o lugar em vez de pular\n if (!reduced) LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)\n setItems((current) => current.filter((item) => item.id !== id))\n },\n [reduced],\n )\n const toast = useCallback((options: ToastOptions) => {\n const id = nextId.current++\n setItems((current) => [...current, { ...options, id, closing: false }])\n return id\n }, [])\n const value = useMemo(() => ({ toast, dismiss }), [toast, dismiss])\n\n return (\n <ToastContext.Provider value={value}>\n {children}\n <View\n pointerEvents=\"box-none\"\n accessibilityLabel={label}\n testID={testID}\n style={{ position: 'absolute', top: 0, left: 0, right: 0, zIndex: 9999, elevation: 9999 }}\n >\n <View\n pointerEvents=\"box-none\"\n className=\"w-full max-w-[360px] gap-2 self-center px-4\"\n style={{ paddingTop: topInset }}\n >\n {items.map((item) => (\n <ToastView\n key={item.id}\n item={item}\n dismissLabel={dismissLabel}\n onDismiss={dismiss}\n onRemove={remove}\n />\n ))}\n </View>\n </View>\n </ToastContext.Provider>\n )\n}\n\nfunction ToastView({\n item,\n dismissLabel,\n onDismiss,\n onRemove,\n}: {\n item: ToastItem\n dismissLabel: string\n onDismiss: (id: number) => void\n onRemove: (id: number) => void\n}) {\n const {\n id,\n status = 'info',\n title,\n description,\n action,\n duration = 5000,\n showIcon = true,\n dismissible = true,\n closing,\n } = item\n const progress = useRef(new Animated.Value(0)).current\n // arrastar para o lado dispensa (como o swipe do Radix no web); o auto-fechar pausa enquanto isso\n const dragX = useRef(new Animated.Value(0)).current\n const [dragging, setDragging] = useState(false)\n const latest = useRef({ id, onDismiss })\n latest.current = { id, onDismiss }\n const panResponder = useRef(\n PanResponder.create({\n onMoveShouldSetPanResponder: (_, g) => Math.abs(g.dx) > 6 && Math.abs(g.dx) > Math.abs(g.dy),\n onPanResponderTerminationRequest: () => false,\n onPanResponderGrant: () => setDragging(true),\n onPanResponderMove: (_, g) => dragX.setValue(g.dx),\n onPanResponderRelease: (_, g) => {\n setDragging(false)\n if (Math.abs(g.dx) > SWIPE_DISTANCE || Math.abs(g.vx) > SWIPE_VELOCITY) {\n // continua para o lado de onde o dedo ia, enquanto o toast some\n Animated.timing(dragX, {\n toValue: (g.dx || g.vx) > 0 ? 400 : -400,\n duration: 150,\n useNativeDriver: true,\n }).start()\n latest.current.onDismiss(latest.current.id)\n } else {\n Animated.timing(dragX, { toValue: 0, duration: 200, useNativeDriver: true }).start()\n }\n },\n onPanResponderTerminate: () => {\n setDragging(false)\n Animated.timing(dragX, { toValue: 0, duration: 200, useNativeDriver: true }).start()\n },\n }),\n ).current\n const swipeFade = dragX.interpolate({\n inputRange: [-150, 0, 150],\n outputRange: [0.2, 1, 0.2],\n extrapolate: 'clamp',\n })\n const iconColor = useToken(statusTextToken[status])\n const closeColor = useToken('--color-text-primary')\n\n useEffect(() => {\n if (closing) return\n const animation = Animated.timing(progress, {\n toValue: 1,\n duration: 200,\n useNativeDriver: true,\n })\n animation.start()\n return () => animation.stop()\n }, [closing, progress])\n\n useEffect(() => {\n if (!closing) return\n const animation = Animated.timing(progress, {\n toValue: 0,\n duration: 150,\n useNativeDriver: true,\n })\n animation.start(({ finished }) => finished && onRemove(id))\n return () => animation.stop()\n }, [closing, id, onRemove, progress])\n\n useEffect(() => {\n if (closing || dragging || duration <= 0) return\n const timer = setTimeout(() => onDismiss(id), duration)\n return () => clearTimeout(timer)\n }, [closing, dragging, duration, id, onDismiss])\n\n const translateY = progress.interpolate({ inputRange: [0, 1], outputRange: [-16, 0] })\n\n return (\n <Animated.View\n {...panResponder.panHandlers}\n style={{\n opacity: Animated.multiply(progress, swipeFade),\n transform: [{ translateY }, { translateX: dragX }],\n }}\n >\n <View\n accessibilityRole={statusRole(status)}\n // Erro e aviso interrompem o leitor de tela; o resto é anunciado com calma.\n accessibilityLiveRegion={statusRole(status) ? 'assertive' : 'polite'}\n className=\"flex-row items-start gap-3 rounded-md border border-border-default bg-surface-elevated py-3 pr-3 pl-4 shadow-md\"\n style={{ elevation: 6 }}\n >\n {showIcon && <StatusIcon status={status} color={iconColor} />}\n <View className=\"min-w-0 flex-1 items-start gap-1\">\n {title ? (\n <Text className={`text-sm font-medium leading-5 ${statusText[status]}`}>{title}</Text>\n ) : null}\n <Text className=\"text-sm leading-5 text-text-primary\">{description}</Text>\n {action && (\n <Button\n variant=\"ghost\"\n onPress={() => {\n action.onClick()\n onDismiss(id)\n }}\n >\n {action.label}\n </Button>\n )}\n </View>\n {dismissible && (\n <Pressable\n accessibilityRole=\"button\"\n accessibilityLabel={dismissLabel}\n hitSlop={8}\n onPress={() => onDismiss(id)}\n className=\"size-8 items-center justify-center rounded-full active:bg-button-secondary-bg-hover\"\n >\n <CloseIcon color={closeColor} />\n </Pressable>\n )}\n </View>\n </Animated.View>\n )\n}\n\nexport function useToast(): ToastContextValue {\n const context = useContext(ToastContext)\n if (!context) throw new Error('useToast precisa estar dentro de <ToastProvider>.')\n return context\n}\n"],"mappings":"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"}
@@ -14,15 +14,19 @@ export type TooltipProps = {
14
14
  side?: 'top' | 'bottom';
15
15
  /** Fecha sozinho depois deste tempo, em ms. `0` mantém aberto até tocar fora. Padrão: 3000. */
16
16
  duration?: number;
17
+ /** Seta apontando para o gatilho. Padrão: `true`. */
18
+ showArrow?: boolean;
19
+ /** Quanto tempo segurar o gatilho antes de abrir, em ms (o `delayLongPress` do RN, 500 por padrão). */
20
+ delayDuration?: number;
17
21
  className?: string;
18
22
  testID?: string;
19
23
  };
20
24
  /**
21
25
  * Dica contextual. No toque não há hover: abre ao pressionar e segurar o gatilho (`onLongPress`),
22
26
  * e fecha após `duration` ou ao tocar fora. Como o long press não é óbvio para leitores de tela,
23
- * o `content` (se for texto) vira o `accessibilityHint` do gatilho. Diferenças do web: sem
24
- * `delayDuration`, sem seta (`showArrow`) e sem lados `left`/`right`.
27
+ * o `content` (se for texto) vira o `accessibilityHint` do gatilho. Fica centrado no gatilho, com
28
+ * seta. Diferença do web: sem os lados `left`/`right`.
25
29
  */
26
- export declare function Tooltip({ open, defaultOpen, onOpenChange, children, content, title, side, duration, className, testID }: TooltipProps): import("react").JSX.Element;
30
+ export declare function Tooltip({ open, defaultOpen, onOpenChange, children, content, title, side, duration, showArrow, delayDuration, className, testID }: TooltipProps): import("react").JSX.Element;
27
31
  //#endregion
28
32
  //# sourceMappingURL=tooltip.d.ts.map
@@ -9,10 +9,10 @@ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
9
9
  /**
10
10
  * Dica contextual. No toque não há hover: abre ao pressionar e segurar o gatilho (`onLongPress`),
11
11
  * e fecha após `duration` ou ao tocar fora. Como o long press não é óbvio para leitores de tela,
12
- * o `content` (se for texto) vira o `accessibilityHint` do gatilho. Diferenças do web: sem
13
- * `delayDuration`, sem seta (`showArrow`) e sem lados `left`/`right`.
12
+ * o `content` (se for texto) vira o `accessibilityHint` do gatilho. Fica centrado no gatilho, com
13
+ * seta. Diferença do web: sem os lados `left`/`right`.
14
14
  */
15
- function Tooltip({ open, defaultOpen, onOpenChange, children, content, title, side = "top", duration = 3e3, className, testID }) {
15
+ function Tooltip({ open, defaultOpen, onOpenChange, children, content, title, side = "top", duration = 3e3, showArrow = true, delayDuration, className, testID }) {
16
16
  const [isOpen, setOpen] = useOpenState({
17
17
  open,
18
18
  defaultOpen,
@@ -34,7 +34,10 @@ function Tooltip({ open, defaultOpen, onOpenChange, children, content, title, si
34
34
  anchorRef: ref,
35
35
  event: "onLongPress",
36
36
  onTrigger: () => setOpen(true),
37
- extra: hint ? { accessibilityHint: hint } : void 0,
37
+ extra: {
38
+ ...hint ? { accessibilityHint: hint } : null,
39
+ ...delayDuration === void 0 ? null : { delayLongPress: delayDuration }
40
+ },
38
41
  children
39
42
  }), /* @__PURE__ */ jsx(AnchoredOverlay, {
40
43
  visible: isOpen,
@@ -42,6 +45,9 @@ function Tooltip({ open, defaultOpen, onOpenChange, children, content, title, si
42
45
  onRequestClose: close,
43
46
  placement: side,
44
47
  estimatedHeight: 80,
48
+ align: "center",
49
+ offset: showArrow ? 8 : 4,
50
+ arrowClassName: showArrow ? "bg-bg-inverse" : void 0,
45
51
  testID,
46
52
  className: cn("max-w-[240px] gap-1 rounded-sm bg-bg-inverse px-3 py-2", className),
47
53
  children: /* @__PURE__ */ jsxs(View, {
@@ -1 +1 @@
1
- {"version":3,"file":"tooltip.js","names":[],"sources":["../../../src/components/tooltip/tooltip.tsx"],"sourcesContent":["/// <reference types=\"uniwind/types\" />\n\nimport { type ReactElement, type ReactNode, useEffect } from 'react'\nimport { Text, View } from 'react-native'\nimport { cn } from '../../utils/cn'\nimport { AnchoredOverlay } from '../_shared/anchored-overlay'\nimport { AnchorTrigger, useControlledAnchor } from '../_shared/overlay-anchor'\nimport { useOpenState } from '../_shared/overlay-state'\n\nexport type TooltipProps = {\n open?: boolean\n defaultOpen?: boolean\n onOpenChange?: (open: boolean) => void\n /** O elemento que dispara o tooltip (ex.: um `<Button>`); recebe `onLongPress`. */\n children: ReactElement\n /** O texto principal. */\n content: ReactNode\n /** Título em destaque acima do texto (opcional). */\n title?: ReactNode\n /** Lado do gatilho em que o tooltip aparece; inverte se não couber. Padrão: `top`. */\n side?: 'top' | 'bottom'\n /** Fecha sozinho depois deste tempo, em ms. `0` mantém aberto até tocar fora. Padrão: 3000. */\n duration?: number\n className?: string\n testID?: string\n}\n\n/**\n * Dica contextual. No toque não há hover: abre ao pressionar e segurar o gatilho (`onLongPress`),\n * e fecha após `duration` ou ao tocar fora. Como o long press não é óbvio para leitores de tela,\n * o `content` (se for texto) vira o `accessibilityHint` do gatilho. Diferenças do web: sem\n * `delayDuration`, sem seta (`showArrow`) e sem lados `left`/`right`.\n */\nexport function Tooltip({\n open,\n defaultOpen,\n onOpenChange,\n children,\n content,\n title,\n side = 'top',\n duration = 3000,\n className,\n testID,\n}: TooltipProps) {\n const [isOpen, setOpen] = useOpenState({ open, defaultOpen, onOpenChange })\n const { ref, rect } = useControlledAnchor(isOpen)\n const close = () => setOpen(false)\n\n useEffect(() => {\n if (!isOpen || duration <= 0) return\n const timer = setTimeout(() => setOpen(false), duration)\n return () => clearTimeout(timer)\n }, [isOpen, duration, setOpen])\n\n const hint = typeof content === 'string' ? content : undefined\n\n return (\n <>\n <AnchorTrigger\n anchorRef={ref}\n event=\"onLongPress\"\n onTrigger={() => setOpen(true)}\n extra={hint ? { accessibilityHint: hint } : undefined}\n >\n {children}\n </AnchorTrigger>\n <AnchoredOverlay\n visible={isOpen}\n rect={rect}\n onRequestClose={close}\n placement={side}\n estimatedHeight={80}\n testID={testID}\n className={cn('max-w-[240px] gap-1 rounded-sm bg-bg-inverse px-3 py-2', className)}\n >\n <View accessibilityRole=\"text\">\n {title && (\n <Text className=\"text-[13px] font-medium leading-4 text-text-inverse\">{title}</Text>\n )}\n <Text className=\"text-xs leading-4 text-text-inverse-secondary\">{content}</Text>\n </View>\n </AnchoredOverlay>\n </>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;AAiCA,SAAgB,QAAQ,EACtB,MACA,aACA,cACA,UACA,SACA,OACA,OAAO,OACP,WAAW,KACX,WACA,UACe;CACf,MAAM,CAAC,QAAQ,WAAW,aAAa;EAAE;EAAM;EAAa;CAAa,CAAC;CAC1E,MAAM,EAAE,KAAK,SAAS,oBAAoB,MAAM;CAChD,MAAM,cAAc,QAAQ,KAAK;CAEjC,gBAAgB;EACd,IAAI,CAAC,UAAU,YAAY,GAAG;EAC9B,MAAM,QAAQ,iBAAiB,QAAQ,KAAK,GAAG,QAAQ;EACvD,aAAa,aAAa,KAAK;CACjC,GAAG;EAAC;EAAQ;EAAU;CAAO,CAAC;CAE9B,MAAM,OAAO,OAAO,YAAY,WAAW,UAAU,KAAA;CAErD,OACE,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,eAAD;EACE,WAAW;EACX,OAAM;EACN,iBAAiB,QAAQ,IAAI;EAC7B,OAAO,OAAO,EAAE,mBAAmB,KAAK,IAAI,KAAA;EAE3C;CACY,CAAA,GACf,oBAAC,iBAAD;EACE,SAAS;EACH;EACN,gBAAgB;EAChB,WAAW;EACX,iBAAiB;EACT;EACR,WAAW,GAAG,0DAA0D,SAAS;EAEjF,UAAA,qBAAC,MAAD;GAAM,mBAAkB;GAAxB,UAAA,CACG,SACC,oBAAC,MAAD;IAAM,WAAU;IAAuD,UAAA;GAAY,CAAA,GAErF,oBAAC,MAAD;IAAM,WAAU;IAAiD,UAAA;GAAc,CAAA,CAC3E;;CACS,CAAA,CACjB,EAAA,CAAA;AAEN"}
1
+ {"version":3,"file":"tooltip.js","names":[],"sources":["../../../src/components/tooltip/tooltip.tsx"],"sourcesContent":["/// <reference types=\"uniwind/types\" />\n\nimport { type ReactElement, type ReactNode, useEffect } from 'react'\nimport { Text, View } from 'react-native'\nimport { cn } from '../../utils/cn'\nimport { AnchoredOverlay } from '../_shared/anchored-overlay'\nimport { AnchorTrigger, useControlledAnchor } from '../_shared/overlay-anchor'\nimport { useOpenState } from '../_shared/overlay-state'\n\nexport type TooltipProps = {\n open?: boolean\n defaultOpen?: boolean\n onOpenChange?: (open: boolean) => void\n /** O elemento que dispara o tooltip (ex.: um `<Button>`); recebe `onLongPress`. */\n children: ReactElement\n /** O texto principal. */\n content: ReactNode\n /** Título em destaque acima do texto (opcional). */\n title?: ReactNode\n /** Lado do gatilho em que o tooltip aparece; inverte se não couber. Padrão: `top`. */\n side?: 'top' | 'bottom'\n /** Fecha sozinho depois deste tempo, em ms. `0` mantém aberto até tocar fora. Padrão: 3000. */\n duration?: number\n /** Seta apontando para o gatilho. Padrão: `true`. */\n showArrow?: boolean\n /** Quanto tempo segurar o gatilho antes de abrir, em ms (o `delayLongPress` do RN, 500 por padrão). */\n delayDuration?: number\n className?: string\n testID?: string\n}\n\n/**\n * Dica contextual. No toque não há hover: abre ao pressionar e segurar o gatilho (`onLongPress`),\n * e fecha após `duration` ou ao tocar fora. Como o long press não é óbvio para leitores de tela,\n * o `content` (se for texto) vira o `accessibilityHint` do gatilho. Fica centrado no gatilho, com\n * seta. Diferença do web: sem os lados `left`/`right`.\n */\nexport function Tooltip({\n open,\n defaultOpen,\n onOpenChange,\n children,\n content,\n title,\n side = 'top',\n duration = 3000,\n showArrow = true,\n delayDuration,\n className,\n testID,\n}: TooltipProps) {\n const [isOpen, setOpen] = useOpenState({ open, defaultOpen, onOpenChange })\n const { ref, rect } = useControlledAnchor(isOpen)\n const close = () => setOpen(false)\n\n useEffect(() => {\n if (!isOpen || duration <= 0) return\n const timer = setTimeout(() => setOpen(false), duration)\n return () => clearTimeout(timer)\n }, [isOpen, duration, setOpen])\n\n const hint = typeof content === 'string' ? content : undefined\n\n return (\n <>\n <AnchorTrigger\n anchorRef={ref}\n event=\"onLongPress\"\n onTrigger={() => setOpen(true)}\n extra={{\n ...(hint ? { accessibilityHint: hint } : null),\n ...(delayDuration === undefined ? null : { delayLongPress: delayDuration }),\n }}\n >\n {children}\n </AnchorTrigger>\n <AnchoredOverlay\n visible={isOpen}\n rect={rect}\n onRequestClose={close}\n placement={side}\n estimatedHeight={80}\n align=\"center\"\n offset={showArrow ? 8 : 4}\n arrowClassName={showArrow ? 'bg-bg-inverse' : undefined}\n testID={testID}\n className={cn('max-w-[240px] gap-1 rounded-sm bg-bg-inverse px-3 py-2', className)}\n >\n <View accessibilityRole=\"text\">\n {title && (\n <Text className=\"text-[13px] font-medium leading-4 text-text-inverse\">{title}</Text>\n )}\n <Text className=\"text-xs leading-4 text-text-inverse-secondary\">{content}</Text>\n </View>\n </AnchoredOverlay>\n </>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;AAqCA,SAAgB,QAAQ,EACtB,MACA,aACA,cACA,UACA,SACA,OACA,OAAO,OACP,WAAW,KACX,YAAY,MACZ,eACA,WACA,UACe;CACf,MAAM,CAAC,QAAQ,WAAW,aAAa;EAAE;EAAM;EAAa;CAAa,CAAC;CAC1E,MAAM,EAAE,KAAK,SAAS,oBAAoB,MAAM;CAChD,MAAM,cAAc,QAAQ,KAAK;CAEjC,gBAAgB;EACd,IAAI,CAAC,UAAU,YAAY,GAAG;EAC9B,MAAM,QAAQ,iBAAiB,QAAQ,KAAK,GAAG,QAAQ;EACvD,aAAa,aAAa,KAAK;CACjC,GAAG;EAAC;EAAQ;EAAU;CAAO,CAAC;CAE9B,MAAM,OAAO,OAAO,YAAY,WAAW,UAAU,KAAA;CAErD,OACE,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,eAAD;EACE,WAAW;EACX,OAAM;EACN,iBAAiB,QAAQ,IAAI;EAC7B,OAAO;GACL,GAAI,OAAO,EAAE,mBAAmB,KAAK,IAAI;GACzC,GAAI,kBAAkB,KAAA,IAAY,OAAO,EAAE,gBAAgB,cAAc;EAC3E;EAEC;CACY,CAAA,GACf,oBAAC,iBAAD;EACE,SAAS;EACH;EACN,gBAAgB;EAChB,WAAW;EACX,iBAAiB;EACjB,OAAM;EACN,QAAQ,YAAY,IAAI;EACxB,gBAAgB,YAAY,kBAAkB,KAAA;EACtC;EACR,WAAW,GAAG,0DAA0D,SAAS;EAEjF,UAAA,qBAAC,MAAD;GAAM,mBAAkB;GAAxB,UAAA,CACG,SACC,oBAAC,MAAD;IAAM,WAAU;IAAuD,UAAA;GAAY,CAAA,GAErF,oBAAC,MAAD;IAAM,WAAU;IAAiD,UAAA;GAAc,CAAA,CAC3E;;CACS,CAAA,CACjB,EAAA,CAAA;AAEN"}
@@ -0,0 +1,2 @@
1
+ import { VideoPlayer, VideoPlayerProps } from "./video-player.js";
2
+ export { VideoPlayer, type VideoPlayerProps };
@@ -0,0 +1,37 @@
1
+ import { ReactNode } from "react";
2
+ import { ViewProps } from "react-native";
3
+ //#region src/components/video-player/video-player.d.ts
4
+ export type VideoPlayerProps = Omit<ViewProps, 'style'> & {
5
+ className?: string;
6
+ /** A superfície de vídeo (ex.: o `VideoView` do `expo-video`), que preenche o player. */
7
+ children: ReactNode;
8
+ /** Nome acessível do player. */
9
+ label?: string;
10
+ /** Tocando agora: mostra o botão de pausar e esconde o play central. */
11
+ playing?: boolean;
12
+ /** Posição atual, em segundos. */
13
+ position?: number;
14
+ /** Duração total, em segundos. */
15
+ duration?: number;
16
+ /** Sem som: mostra o ícone de volume cortado. */
17
+ muted?: boolean;
18
+ /** Em tela cheia: mostra o ícone de sair. Só vale com `onToggleFullscreen`. */
19
+ fullscreen?: boolean;
20
+ onPlayPause?: () => void;
21
+ /** Chamado com a posição (em segundos) tocada ou arrastada na barra. */
22
+ onSeek?: (seconds: number) => void;
23
+ onToggleMute?: () => void;
24
+ /** Sem ele, o botão de tela cheia não aparece. */
25
+ onToggleFullscreen?: () => void;
26
+ /** Tempo sem interação, em ms, até esconder os controles durante a reprodução. Padrão: 2500. */
27
+ hideControlsAfter?: number;
28
+ };
29
+ /**
30
+ * Casca de player de vídeo: play central, posição, tempo, mudo e tela cheia sobre o seu vídeo.
31
+ * Não toca nada: quem usa guarda o estado (por exemplo com `expo-video`) e o passa por props,
32
+ * ligando os `on…` ao player de verdade. Tocar no vídeo alterna play/pause e mostra os controles,
33
+ * que somem depois de um tempo sem toques enquanto toca (com leitor de tela ligado, ficam).
34
+ */
35
+ export declare function VideoPlayer({ className, children, label, playing, position, duration, muted, fullscreen, onPlayPause, onSeek, onToggleMute, onToggleFullscreen, hideControlsAfter, ...props }: VideoPlayerProps): import("react").JSX.Element;
36
+ //#endregion
37
+ //# sourceMappingURL=video-player.d.ts.map