@innovui/react-native 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +57 -2
- package/dist/components/_shared/anchored-overlay.js +74 -0
- package/dist/components/_shared/anchored-overlay.js.map +1 -0
- package/dist/components/_shared/backdrop.js +25 -0
- package/dist/components/_shared/backdrop.js.map +1 -0
- package/dist/components/_shared/overlay-anchor.js +31 -0
- package/dist/components/_shared/overlay-anchor.js.map +1 -0
- package/dist/components/_shared/overlay-state.js +31 -0
- package/dist/components/_shared/overlay-state.js.map +1 -0
- package/dist/components/_shared/status.d.ts +5 -0
- package/dist/components/_shared/status.js +98 -0
- package/dist/components/_shared/status.js.map +1 -0
- package/dist/components/_shared/use-token.js +15 -0
- package/dist/components/_shared/use-token.js.map +1 -0
- package/dist/components/accordion/accordion.d.ts +43 -0
- package/dist/components/accordion/accordion.js +90 -0
- package/dist/components/accordion/accordion.js.map +1 -0
- package/dist/components/accordion/index.d.ts +2 -0
- package/dist/components/alert/alert.d.ts +25 -0
- package/dist/components/alert/alert.js +61 -0
- package/dist/components/alert/alert.js.map +1 -0
- package/dist/components/alert/index.d.ts +2 -0
- package/dist/components/avatar/avatar.d.ts +17 -0
- package/dist/components/avatar/avatar.js +51 -0
- package/dist/components/avatar/avatar.js.map +1 -0
- package/dist/components/avatar/index.d.ts +2 -0
- package/dist/components/badge/badge.d.ts +16 -0
- package/dist/components/badge/badge.js +57 -0
- package/dist/components/badge/badge.js.map +1 -0
- package/dist/components/badge/index.d.ts +2 -0
- package/dist/components/banner/banner.d.ts +23 -0
- package/dist/components/banner/banner.js +50 -0
- package/dist/components/banner/banner.js.map +1 -0
- package/dist/components/banner/index.d.ts +2 -0
- package/dist/components/breadcrumb/breadcrumb.d.ts +22 -0
- package/dist/components/breadcrumb/breadcrumb.js +54 -0
- package/dist/components/breadcrumb/breadcrumb.js.map +1 -0
- package/dist/components/breadcrumb/index.d.ts +2 -0
- package/dist/components/button/button.d.ts +21 -0
- package/dist/components/button/button.js +89 -0
- package/dist/components/button/button.js.map +1 -0
- package/dist/components/button/index.d.ts +2 -0
- package/dist/components/card/card.d.ts +17 -0
- package/dist/components/card/card.js +28 -0
- package/dist/components/card/card.js.map +1 -0
- package/dist/components/card/index.d.ts +2 -0
- package/dist/components/checkbox/checkbox.d.ts +18 -0
- package/dist/components/checkbox/checkbox.js +64 -0
- package/dist/components/checkbox/checkbox.js.map +1 -0
- package/dist/components/checkbox/index.d.ts +2 -0
- package/dist/components/combobox/combobox.d.ts +40 -0
- package/dist/components/combobox/combobox.js +101 -0
- package/dist/components/combobox/combobox.js.map +1 -0
- package/dist/components/combobox/index.d.ts +2 -0
- package/dist/components/container/container.d.ts +17 -0
- package/dist/components/container/container.js +42 -0
- package/dist/components/container/container.js.map +1 -0
- package/dist/components/container/index.d.ts +2 -0
- package/dist/components/date-picker/calendar.d.ts +38 -0
- package/dist/components/date-picker/calendar.js +137 -0
- package/dist/components/date-picker/calendar.js.map +1 -0
- package/dist/components/date-picker/date-picker.d.ts +24 -0
- package/dist/components/date-picker/date-picker.js +52 -0
- package/dist/components/date-picker/date-picker.js.map +1 -0
- package/dist/components/date-picker/date-presets.d.ts +15 -0
- package/dist/components/date-picker/date-presets.js +52 -0
- package/dist/components/date-picker/date-presets.js.map +1 -0
- package/dist/components/date-picker/date-range-picker.d.ts +24 -0
- package/dist/components/date-picker/date-range-picker.js +78 -0
- package/dist/components/date-picker/date-range-picker.js.map +1 -0
- package/dist/components/date-picker/date-sheet.js +33 -0
- package/dist/components/date-picker/date-sheet.js.map +1 -0
- package/dist/components/date-picker/date-trigger.js +14 -0
- package/dist/components/date-picker/date-trigger.js.map +1 -0
- package/dist/components/date-picker/dates.d.ts +8 -0
- package/dist/components/date-picker/dates.js +40 -0
- package/dist/components/date-picker/dates.js.map +1 -0
- package/dist/components/date-picker/index.d.ts +6 -0
- package/dist/components/divider/divider.d.ts +11 -0
- package/dist/components/divider/divider.js +16 -0
- package/dist/components/divider/divider.js.map +1 -0
- package/dist/components/divider/index.d.ts +2 -0
- package/dist/components/drawer/drawer.d.ts +34 -0
- package/dist/components/drawer/drawer.js +144 -0
- package/dist/components/drawer/drawer.js.map +1 -0
- package/dist/components/drawer/index.d.ts +2 -0
- package/dist/components/field/field.d.ts +31 -0
- package/dist/components/field/field.js +52 -0
- package/dist/components/field/field.js.map +1 -0
- package/dist/components/field/index.d.ts +2 -0
- package/dist/components/grid/grid.d.ts +35 -0
- package/dist/components/grid/grid.js +64 -0
- package/dist/components/grid/grid.js.map +1 -0
- package/dist/components/grid/index.d.ts +2 -0
- package/dist/components/icon-button/icon-button.d.ts +23 -0
- package/dist/components/icon-button/icon-button.js +48 -0
- package/dist/components/icon-button/icon-button.js.map +1 -0
- package/dist/components/icon-button/index.d.ts +2 -0
- package/dist/components/input/index.d.ts +2 -0
- package/dist/components/input/input.d.ts +22 -0
- package/dist/components/input/input.js +86 -0
- package/dist/components/input/input.js.map +1 -0
- package/dist/components/link/index.d.ts +2 -0
- package/dist/components/link/link.d.ts +13 -0
- package/dist/components/link/link.js +20 -0
- package/dist/components/link/link.js.map +1 -0
- package/dist/components/list/index.d.ts +2 -0
- package/dist/components/list/list.d.ts +26 -0
- package/dist/components/list/list.js +45 -0
- package/dist/components/list/list.js.map +1 -0
- package/dist/components/menu/index.d.ts +2 -0
- package/dist/components/menu/menu.d.ts +47 -0
- package/dist/components/menu/menu.js +81 -0
- package/dist/components/menu/menu.js.map +1 -0
- package/dist/components/modal/index.d.ts +2 -0
- package/dist/components/modal/modal.d.ts +41 -0
- package/dist/components/modal/modal.js +92 -0
- package/dist/components/modal/modal.js.map +1 -0
- package/dist/components/pagination/index.d.ts +2 -0
- package/dist/components/pagination/pagination.d.ts +21 -0
- package/dist/components/pagination/pagination.js +99 -0
- package/dist/components/pagination/pagination.js.map +1 -0
- package/dist/components/popover/index.d.ts +2 -0
- package/dist/components/popover/popover.d.ts +29 -0
- package/dist/components/popover/popover.js +54 -0
- package/dist/components/popover/popover.js.map +1 -0
- package/dist/components/radio/index.d.ts +2 -0
- package/dist/components/radio/radio.d.ts +16 -0
- package/dist/components/radio/radio.js +37 -0
- package/dist/components/radio/radio.js.map +1 -0
- package/dist/components/segmented-control/index.d.ts +2 -0
- package/dist/components/segmented-control/segmented-control.d.ts +23 -0
- package/dist/components/segmented-control/segmented-control.js +53 -0
- package/dist/components/segmented-control/segmented-control.js.map +1 -0
- package/dist/components/select/field-trigger.d.ts +6 -0
- package/dist/components/select/field-trigger.js +90 -0
- package/dist/components/select/field-trigger.js.map +1 -0
- package/dist/components/select/index.d.ts +2 -0
- package/dist/components/select/select.d.ts +35 -0
- package/dist/components/select/select.js +98 -0
- package/dist/components/select/select.js.map +1 -0
- package/dist/components/skeleton/index.d.ts +2 -0
- package/dist/components/skeleton/skeleton.d.ts +17 -0
- package/dist/components/skeleton/skeleton.js +53 -0
- package/dist/components/skeleton/skeleton.js.map +1 -0
- package/dist/components/slider/index.d.ts +2 -0
- package/dist/components/slider/slider.d.ts +26 -0
- package/dist/components/slider/slider.js +102 -0
- package/dist/components/slider/slider.js.map +1 -0
- package/dist/components/spinner/index.d.ts +2 -0
- package/dist/components/spinner/spinner.d.ts +13 -0
- package/dist/components/spinner/spinner.js +47 -0
- package/dist/components/spinner/spinner.js.map +1 -0
- package/dist/components/stack/index.d.ts +2 -0
- package/dist/components/stack/stack.d.ts +17 -0
- package/dist/components/stack/stack.js +60 -0
- package/dist/components/stack/stack.js.map +1 -0
- package/dist/components/stepper/index.d.ts +2 -0
- package/dist/components/stepper/stepper.d.ts +20 -0
- package/dist/components/stepper/stepper.js +61 -0
- package/dist/components/stepper/stepper.js.map +1 -0
- package/dist/components/switch/index.d.ts +2 -0
- package/dist/components/switch/switch.d.ts +15 -0
- package/dist/components/switch/switch.js +51 -0
- package/dist/components/switch/switch.js.map +1 -0
- package/dist/components/table/index.d.ts +2 -0
- package/dist/components/table/table.d.ts +42 -0
- package/dist/components/table/table.js +83 -0
- package/dist/components/table/table.js.map +1 -0
- package/dist/components/tabs/index.d.ts +2 -0
- package/dist/components/tabs/tabs.d.ts +35 -0
- package/dist/components/tabs/tabs.js +82 -0
- package/dist/components/tabs/tabs.js.map +1 -0
- package/dist/components/tag/index.d.ts +2 -0
- package/dist/components/tag/tag.d.ts +18 -0
- package/dist/components/tag/tag.js +37 -0
- package/dist/components/tag/tag.js.map +1 -0
- package/dist/components/textarea/index.d.ts +2 -0
- package/dist/components/textarea/textarea.d.ts +16 -0
- package/dist/components/textarea/textarea.js +46 -0
- package/dist/components/textarea/textarea.js.map +1 -0
- package/dist/components/toast/index.d.ts +2 -0
- package/dist/components/toast/toast.d.ts +41 -0
- package/dist/components/toast/toast.js +167 -0
- package/dist/components/toast/toast.js.map +1 -0
- package/dist/components/tooltip/index.d.ts +2 -0
- package/dist/components/tooltip/tooltip.d.ts +28 -0
- package/dist/components/tooltip/tooltip.js +62 -0
- package/dist/components/tooltip/tooltip.js.map +1 -0
- package/dist/components/upload/index.d.ts +2 -0
- package/dist/components/upload/upload.d.ts +65 -0
- package/dist/components/upload/upload.js +130 -0
- package/dist/components/upload/upload.js.map +1 -0
- package/dist/index.d.ts +87 -0
- package/dist/index.js +46 -0
- package/dist/tokens/generated.d.ts +371 -0
- package/dist/tokens/generated.js +373 -0
- package/dist/tokens/generated.js.map +1 -0
- package/dist/utils/cn.d.ts +6 -0
- package/dist/utils/cn.js +11 -0
- package/dist/utils/cn.js.map +1 -0
- package/package.json +61 -3
- package/theme.css +191 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import { VariantProps } from "class-variance-authority";
|
|
3
|
+
//#region src/components/modal/modal.d.ts
|
|
4
|
+
declare const modalVariants: (props?: ({
|
|
5
|
+
size?: "lg" | "md" | "sm" | null | undefined;
|
|
6
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
7
|
+
export type ModalProps = VariantProps<typeof modalVariants> & {
|
|
8
|
+
open?: boolean;
|
|
9
|
+
defaultOpen?: boolean;
|
|
10
|
+
onOpenChange?: (open: boolean) => void;
|
|
11
|
+
/** Título do diálogo; também é o nome acessível dele. */
|
|
12
|
+
title: ReactNode;
|
|
13
|
+
/** O corpo do diálogo. */
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
/** Ações do rodapé (primária à direita). Use `ModalClose` para fechar. Sem ele, não há rodapé. */
|
|
16
|
+
footer?: ReactNode;
|
|
17
|
+
/** Elemento que abre o modal (ex.: um `<Button>`); recebe `onPress`. Dispensável no modo controlado. */
|
|
18
|
+
trigger?: ReactNode;
|
|
19
|
+
/** Mostra o botão de fechar (×) no cabeçalho. */
|
|
20
|
+
showClose?: boolean;
|
|
21
|
+
/** Linha entre o cabeçalho e o corpo. Padrão: `true`. */
|
|
22
|
+
showHeaderDivider?: boolean;
|
|
23
|
+
/** Linha entre o corpo e o rodapé (só aparece com `footer`). Padrão: `true`. */
|
|
24
|
+
showFooterDivider?: boolean;
|
|
25
|
+
/** Nome acessível do botão de fechar. */
|
|
26
|
+
closeLabel?: string;
|
|
27
|
+
className?: string;
|
|
28
|
+
testID?: string;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Diálogo que bloqueia o conteúdo de trás, com backdrop. Usa o `Modal` nativo: o botão voltar do
|
|
32
|
+
* Android e o toque no fundo fecham. Não há foco preso nem `Esc`; `accessibilityViewIsModal`
|
|
33
|
+
* mantém o leitor de tela dentro do diálogo.
|
|
34
|
+
*/
|
|
35
|
+
export declare function Modal({ open, defaultOpen, onOpenChange, title, children, footer, trigger, showClose, showHeaderDivider, showFooterDivider, closeLabel, size, className, testID }: ModalProps): import("react").JSX.Element;
|
|
36
|
+
/** Fecha o modal ao pressionar. Envolva um `<Button>` do rodapé (injeta `onPress`, como o `asChild`). */
|
|
37
|
+
export declare function ModalClose({ children }: {
|
|
38
|
+
children: ReactNode;
|
|
39
|
+
}): import("react").JSX.Element;
|
|
40
|
+
//#endregion
|
|
41
|
+
//# sourceMappingURL=modal.d.ts.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { cn } from "../../utils/cn.js";
|
|
2
|
+
import { CloseIcon } from "../_shared/status.js";
|
|
3
|
+
import { useToken } from "../_shared/use-token.js";
|
|
4
|
+
import { Backdrop } from "../_shared/backdrop.js";
|
|
5
|
+
import { injectHandler, useOpenState } from "../_shared/overlay-state.js";
|
|
6
|
+
import { createContext, useContext } from "react";
|
|
7
|
+
import { Modal, Pressable, ScrollView, Text, View } from "react-native";
|
|
8
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
import { cva } from "class-variance-authority";
|
|
10
|
+
//#region src/components/modal/modal.tsx
|
|
11
|
+
const modalVariants = cva("max-h-full w-full overflow-hidden rounded-lg border border-border-default bg-surface-elevated shadow-2xl", {
|
|
12
|
+
variants: { size: {
|
|
13
|
+
sm: "max-w-[480px]",
|
|
14
|
+
md: "max-w-[640px]",
|
|
15
|
+
lg: "max-w-[800px]"
|
|
16
|
+
} },
|
|
17
|
+
defaultVariants: { size: "sm" }
|
|
18
|
+
});
|
|
19
|
+
const CloseContext = createContext(null);
|
|
20
|
+
/**
|
|
21
|
+
* Diálogo que bloqueia o conteúdo de trás, com backdrop. Usa o `Modal` nativo: o botão voltar do
|
|
22
|
+
* Android e o toque no fundo fecham. Não há foco preso nem `Esc`; `accessibilityViewIsModal`
|
|
23
|
+
* mantém o leitor de tela dentro do diálogo.
|
|
24
|
+
*/
|
|
25
|
+
function Modal$1({ open, defaultOpen, onOpenChange, title, children, footer, trigger, showClose = true, showHeaderDivider = true, showFooterDivider = true, closeLabel = "Fechar", size, className, testID }) {
|
|
26
|
+
const [isOpen, setOpen] = useOpenState({
|
|
27
|
+
open,
|
|
28
|
+
defaultOpen,
|
|
29
|
+
onOpenChange
|
|
30
|
+
});
|
|
31
|
+
const close = () => setOpen(false);
|
|
32
|
+
const iconColor = useToken("--color-text-primary");
|
|
33
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [injectHandler(trigger, "onPress", () => setOpen(true)), /* @__PURE__ */ jsx(Modal, {
|
|
34
|
+
transparent: true,
|
|
35
|
+
statusBarTranslucent: true,
|
|
36
|
+
animationType: "fade",
|
|
37
|
+
visible: isOpen,
|
|
38
|
+
onRequestClose: close,
|
|
39
|
+
children: /* @__PURE__ */ jsx(CloseContext.Provider, {
|
|
40
|
+
value: close,
|
|
41
|
+
children: /* @__PURE__ */ jsxs(View, {
|
|
42
|
+
className: "flex-1 items-center justify-center p-4",
|
|
43
|
+
accessibilityViewIsModal: true,
|
|
44
|
+
children: [/* @__PURE__ */ jsx(Backdrop, {
|
|
45
|
+
onPress: close,
|
|
46
|
+
testID: testID ? `${testID}-backdrop` : void 0
|
|
47
|
+
}), /* @__PURE__ */ jsxs(View, {
|
|
48
|
+
testID,
|
|
49
|
+
className: cn(modalVariants({ size }), className),
|
|
50
|
+
children: [
|
|
51
|
+
/* @__PURE__ */ jsxs(View, {
|
|
52
|
+
className: cn("flex-row items-center gap-4 px-6 py-4", showHeaderDivider && "border-b border-border-default"),
|
|
53
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
54
|
+
accessibilityRole: "header",
|
|
55
|
+
className: "flex-1 text-lg font-semibold leading-6 text-text-primary",
|
|
56
|
+
children: title
|
|
57
|
+
}), showClose && /* @__PURE__ */ jsx(Pressable, {
|
|
58
|
+
accessibilityRole: "button",
|
|
59
|
+
accessibilityLabel: closeLabel,
|
|
60
|
+
hitSlop: 8,
|
|
61
|
+
onPress: close,
|
|
62
|
+
className: "size-8 items-center justify-center rounded-full active:bg-button-secondary-bg-hover",
|
|
63
|
+
children: /* @__PURE__ */ jsx(CloseIcon, { color: iconColor })
|
|
64
|
+
})]
|
|
65
|
+
}),
|
|
66
|
+
/* @__PURE__ */ jsx(ScrollView, {
|
|
67
|
+
className: "shrink",
|
|
68
|
+
contentContainerClassName: "gap-4 p-6",
|
|
69
|
+
children: typeof children === "string" ? /* @__PURE__ */ jsx(Text, {
|
|
70
|
+
className: "text-sm leading-5 text-text-secondary",
|
|
71
|
+
children
|
|
72
|
+
}) : children
|
|
73
|
+
}),
|
|
74
|
+
footer && /* @__PURE__ */ jsx(View, {
|
|
75
|
+
className: cn("flex-row items-center justify-end gap-2 px-6 py-4", showFooterDivider && "border-t border-border-default"),
|
|
76
|
+
children: footer
|
|
77
|
+
})
|
|
78
|
+
]
|
|
79
|
+
})]
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
})] });
|
|
83
|
+
}
|
|
84
|
+
/** Fecha o modal ao pressionar. Envolva um `<Button>` do rodapé (injeta `onPress`, como o `asChild`). */
|
|
85
|
+
function ModalClose({ children }) {
|
|
86
|
+
const close = useContext(CloseContext);
|
|
87
|
+
return /* @__PURE__ */ jsx(Fragment$1, { children: injectHandler(children, "onPress", () => close?.()) });
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
export { Modal$1 as Modal, ModalClose };
|
|
91
|
+
|
|
92
|
+
//# sourceMappingURL=modal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modal.js","names":["Modal","RNModal"],"sources":["../../../src/components/modal/modal.tsx"],"sourcesContent":["/// <reference types=\"uniwind/types\" />\n\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport { createContext, type ReactNode, useContext } from 'react'\nimport { Pressable, Modal as RNModal, ScrollView, Text, View } from 'react-native'\nimport { cn } from '../../utils/cn'\nimport { Backdrop } from '../_shared/backdrop'\nimport { injectHandler, useOpenState } from '../_shared/overlay-state'\nimport { CloseIcon } from '../_shared/status'\nimport { useToken } from '../_shared/use-token'\n\nconst modalVariants = cva(\n 'max-h-full w-full overflow-hidden rounded-lg border border-border-default bg-surface-elevated shadow-2xl',\n {\n variants: {\n size: { sm: 'max-w-[480px]', md: 'max-w-[640px]', lg: 'max-w-[800px]' },\n },\n defaultVariants: { size: 'sm' },\n },\n)\n\nconst CloseContext = createContext<(() => void) | null>(null)\n\nexport type ModalProps = VariantProps<typeof modalVariants> & {\n open?: boolean\n defaultOpen?: boolean\n onOpenChange?: (open: boolean) => void\n /** Título do diálogo; também é o nome acessível dele. */\n title: ReactNode\n /** O corpo do diálogo. */\n children: ReactNode\n /** Ações do rodapé (primária à direita). Use `ModalClose` para fechar. Sem ele, não há rodapé. */\n footer?: ReactNode\n /** Elemento que abre o modal (ex.: um `<Button>`); recebe `onPress`. Dispensável no modo controlado. */\n trigger?: ReactNode\n /** Mostra o botão de fechar (×) no cabeçalho. */\n showClose?: boolean\n /** Linha entre o cabeçalho e o corpo. Padrão: `true`. */\n showHeaderDivider?: boolean\n /** Linha entre o corpo e o rodapé (só aparece com `footer`). Padrão: `true`. */\n showFooterDivider?: boolean\n /** Nome acessível do botão de fechar. */\n closeLabel?: string\n className?: string\n testID?: string\n}\n\n/**\n * Diálogo que bloqueia o conteúdo de trás, com backdrop. Usa o `Modal` nativo: o botão voltar do\n * Android e o toque no fundo fecham. Não há foco preso nem `Esc`; `accessibilityViewIsModal`\n * mantém o leitor de tela dentro do diálogo.\n */\nexport function Modal({\n open,\n defaultOpen,\n onOpenChange,\n title,\n children,\n footer,\n trigger,\n showClose = true,\n showHeaderDivider = true,\n showFooterDivider = true,\n closeLabel = 'Fechar',\n size,\n className,\n testID,\n}: ModalProps) {\n const [isOpen, setOpen] = useOpenState({ open, defaultOpen, onOpenChange })\n const close = () => setOpen(false)\n const iconColor = useToken('--color-text-primary')\n\n return (\n <>\n {injectHandler(trigger, 'onPress', () => setOpen(true))}\n <RNModal\n transparent\n statusBarTranslucent\n animationType=\"fade\"\n visible={isOpen}\n onRequestClose={close}\n >\n <CloseContext.Provider value={close}>\n <View className=\"flex-1 items-center justify-center p-4\" accessibilityViewIsModal>\n <Backdrop onPress={close} testID={testID ? `${testID}-backdrop` : undefined} />\n <View testID={testID} className={cn(modalVariants({ size }), className)}>\n <View\n className={cn(\n 'flex-row items-center gap-4 px-6 py-4',\n showHeaderDivider && 'border-b border-border-default',\n )}\n >\n <Text\n accessibilityRole=\"header\"\n className=\"flex-1 text-lg font-semibold leading-6 text-text-primary\"\n >\n {title}\n </Text>\n {showClose && (\n <Pressable\n accessibilityRole=\"button\"\n accessibilityLabel={closeLabel}\n hitSlop={8}\n onPress={close}\n className=\"size-8 items-center justify-center rounded-full active:bg-button-secondary-bg-hover\"\n >\n <CloseIcon color={iconColor} />\n </Pressable>\n )}\n </View>\n <ScrollView className=\"shrink\" contentContainerClassName=\"gap-4 p-6\">\n {typeof children === 'string' ? (\n <Text className=\"text-sm leading-5 text-text-secondary\">{children}</Text>\n ) : (\n children\n )}\n </ScrollView>\n {footer && (\n <View\n className={cn(\n 'flex-row items-center justify-end gap-2 px-6 py-4',\n showFooterDivider && 'border-t border-border-default',\n )}\n >\n {footer}\n </View>\n )}\n </View>\n </View>\n </CloseContext.Provider>\n </RNModal>\n </>\n )\n}\n\n/** Fecha o modal ao pressionar. Envolva um `<Button>` do rodapé (injeta `onPress`, como o `asChild`). */\nexport function ModalClose({ children }: { children: ReactNode }) {\n const close = useContext(CloseContext)\n return <>{injectHandler(children, 'onPress', () => close?.())}</>\n}\n"],"mappings":";;;;;;;;;;AAWA,MAAM,gBAAgB,IACpB,4GACA;CACE,UAAU,EACR,MAAM;EAAE,IAAI;EAAiB,IAAI;EAAiB,IAAI;CAAgB,EACxE;CACA,iBAAiB,EAAE,MAAM,KAAK;AAChC,CACF;AAEA,MAAM,eAAe,cAAmC,IAAI;;;;;;AA+B5D,SAAgBA,QAAM,EACpB,MACA,aACA,cACA,OACA,UACA,QACA,SACA,YAAY,MACZ,oBAAoB,MACpB,oBAAoB,MACpB,aAAa,UACb,MACA,WACA,UACa;CACb,MAAM,CAAC,QAAQ,WAAW,aAAa;EAAE;EAAM;EAAa;CAAa,CAAC;CAC1E,MAAM,cAAc,QAAQ,KAAK;CACjC,MAAM,YAAY,SAAS,sBAAsB;CAEjD,OACE,qBAAA,YAAA,EAAA,UAAA,CACG,cAAc,SAAS,iBAAiB,QAAQ,IAAI,CAAC,GACtD,oBAACC,OAAD;EACE,aAAA;EACA,sBAAA;EACA,eAAc;EACd,SAAS;EACT,gBAAgB;EAEhB,UAAA,oBAAC,aAAa,UAAd;GAAuB,OAAO;GAC5B,UAAA,qBAAC,MAAD;IAAM,WAAU;IAAyC,0BAAA;IAAzD,UAAA,CACE,oBAAC,UAAD;KAAU,SAAS;KAAO,QAAQ,SAAS,GAAG,OAAO,aAAa,KAAA;IAAY,CAAA,GAC9E,qBAAC,MAAD;KAAc;KAAQ,WAAW,GAAG,cAAc,EAAE,KAAK,CAAC,GAAG,SAAS;KAAtE,UAAA;MACE,qBAAC,MAAD;OACE,WAAW,GACT,yCACA,qBAAqB,gCACvB;OAJF,UAAA,CAME,oBAAC,MAAD;QACE,mBAAkB;QAClB,WAAU;QAET,UAAA;OACG,CAAA,GACL,aACC,oBAAC,WAAD;QACE,mBAAkB;QAClB,oBAAoB;QACpB,SAAS;QACT,SAAS;QACT,WAAU;QAEV,UAAA,oBAAC,WAAD,EAAW,OAAO,UAAY,CAAA;OACrB,CAAA,CAET;;MACN,oBAAC,YAAD;OAAY,WAAU;OAAS,2BAA0B;OACtD,UAAA,OAAO,aAAa,WACnB,oBAAC,MAAD;QAAM,WAAU;QAAyC;OAAe,CAAA,IAExE;MAEQ,CAAA;MACX,UACC,oBAAC,MAAD;OACE,WAAW,GACT,qDACA,qBAAqB,gCACvB;OAEC,UAAA;MACG,CAAA;KAEJ;IACF,CAAA,CAAA;;EACe,CAAA;CAChB,CAAA,CACT,EAAA,CAAA;AAEN;;AAGA,SAAgB,WAAW,EAAE,YAAqC;CAChE,MAAM,QAAQ,WAAW,YAAY;CACrC,OAAO,oBAAA,YAAA,EAAA,UAAG,cAAc,UAAU,iBAAiB,QAAQ,CAAC,EAAI,CAAA;AAClE"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ViewProps } from "react-native";
|
|
2
|
+
//#region src/components/pagination/pagination.d.ts
|
|
3
|
+
export type PaginationProps = ViewProps & {
|
|
4
|
+
className?: string;
|
|
5
|
+
/** Página atual, começando em 1. */
|
|
6
|
+
page: number;
|
|
7
|
+
/** Total de páginas. */
|
|
8
|
+
pageCount: number;
|
|
9
|
+
/** Chamado com o número da página escolhida. */
|
|
10
|
+
onPageChange: (page: number) => void;
|
|
11
|
+
/** Quantas páginas vizinhas da atual mostrar de cada lado. */
|
|
12
|
+
siblingCount?: number;
|
|
13
|
+
/** Nome acessível do botão de página anterior. */
|
|
14
|
+
previousLabel?: string;
|
|
15
|
+
/** Nome acessível do botão de próxima página. */
|
|
16
|
+
nextLabel?: string;
|
|
17
|
+
};
|
|
18
|
+
/** Navegação entre páginas de uma lista ou tabela. Controlado: você guarda o estado de `page`. */
|
|
19
|
+
export declare function Pagination({ className, page, pageCount, onPageChange, siblingCount, previousLabel, nextLabel, accessibilityLabel, ...props }: PaginationProps): import("react").JSX.Element;
|
|
20
|
+
//#endregion
|
|
21
|
+
//# sourceMappingURL=pagination.d.ts.map
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { cn } from "../../utils/cn.js";
|
|
2
|
+
import { ChevronRightIcon } from "../_shared/status.js";
|
|
3
|
+
import { useToken } from "../_shared/use-token.js";
|
|
4
|
+
import { Pressable, Text, View } from "react-native";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
//#region src/components/pagination/pagination.tsx
|
|
7
|
+
/**
|
|
8
|
+
* Páginas a mostrar: a primeira, a última, a atual e `siblingCount` vizinhas de cada lado, com
|
|
9
|
+
* reticências no que sobra. Mantém sempre o mesmo número de itens, para a barra não pular.
|
|
10
|
+
*/
|
|
11
|
+
function getPages(page, pageCount, siblingCount = 1) {
|
|
12
|
+
if (pageCount <= siblingCount * 2 + 5) return Array.from({ length: pageCount }, (_, i) => i + 1);
|
|
13
|
+
const range = (from, to) => Array.from({ length: to - from + 1 }, (_, i) => from + i);
|
|
14
|
+
const left = Math.max(page - siblingCount, 1);
|
|
15
|
+
const right = Math.min(page + siblingCount, pageCount);
|
|
16
|
+
const dotsLeft = left > 2;
|
|
17
|
+
const dotsRight = right < pageCount - 1;
|
|
18
|
+
const edge = 3 + siblingCount * 2;
|
|
19
|
+
if (!dotsLeft) return [
|
|
20
|
+
...range(1, edge),
|
|
21
|
+
"ellipsis-right",
|
|
22
|
+
pageCount
|
|
23
|
+
];
|
|
24
|
+
if (!dotsRight) return [
|
|
25
|
+
1,
|
|
26
|
+
"ellipsis-left",
|
|
27
|
+
...range(pageCount - edge + 1, pageCount)
|
|
28
|
+
];
|
|
29
|
+
return [
|
|
30
|
+
1,
|
|
31
|
+
"ellipsis-left",
|
|
32
|
+
...range(left, right),
|
|
33
|
+
"ellipsis-right",
|
|
34
|
+
pageCount
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
function ArrowButton({ label, disabled, reversed, onPress }) {
|
|
38
|
+
const color = useToken(disabled ? "--color-text-disabled" : "--color-text-primary");
|
|
39
|
+
return /* @__PURE__ */ jsx(Pressable, {
|
|
40
|
+
accessibilityRole: "button",
|
|
41
|
+
accessibilityLabel: label,
|
|
42
|
+
accessibilityState: { disabled },
|
|
43
|
+
disabled,
|
|
44
|
+
onPress,
|
|
45
|
+
className: "size-9 items-center justify-center rounded-full bg-transparent active:bg-button-secondary-bg-hover",
|
|
46
|
+
children: /* @__PURE__ */ jsx(View, {
|
|
47
|
+
style: reversed ? { transform: [{ rotate: "180deg" }] } : void 0,
|
|
48
|
+
children: /* @__PURE__ */ jsx(ChevronRightIcon, { color })
|
|
49
|
+
})
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/** Navegação entre páginas de uma lista ou tabela. Controlado: você guarda o estado de `page`. */
|
|
53
|
+
function Pagination({ className, page, pageCount, onPageChange, siblingCount = 1, previousLabel = "Página anterior", nextLabel = "Próxima página", accessibilityLabel = "Paginação", ...props }) {
|
|
54
|
+
return /* @__PURE__ */ jsxs(View, {
|
|
55
|
+
accessibilityLabel,
|
|
56
|
+
className: cn("flex-row items-center gap-1", className),
|
|
57
|
+
...props,
|
|
58
|
+
children: [
|
|
59
|
+
/* @__PURE__ */ jsx(ArrowButton, {
|
|
60
|
+
label: previousLabel,
|
|
61
|
+
reversed: true,
|
|
62
|
+
disabled: page <= 1,
|
|
63
|
+
onPress: () => onPageChange(page - 1)
|
|
64
|
+
}),
|
|
65
|
+
getPages(page, pageCount, siblingCount).map((item) => {
|
|
66
|
+
if (typeof item === "string") return /* @__PURE__ */ jsx(View, {
|
|
67
|
+
accessible: false,
|
|
68
|
+
importantForAccessibility: "no-hide-descendants",
|
|
69
|
+
className: "size-9 items-center justify-center",
|
|
70
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
71
|
+
className: "text-sm text-text-secondary",
|
|
72
|
+
children: "…"
|
|
73
|
+
})
|
|
74
|
+
}, item);
|
|
75
|
+
const current = item === page;
|
|
76
|
+
return /* @__PURE__ */ jsx(Pressable, {
|
|
77
|
+
accessibilityRole: "button",
|
|
78
|
+
accessibilityLabel: `Página ${item}`,
|
|
79
|
+
accessibilityState: { selected: current },
|
|
80
|
+
onPress: () => onPageChange(item),
|
|
81
|
+
className: cn("size-9 items-center justify-center rounded-full", current ? "bg-button-primary-bg active:bg-button-primary-bg-active" : "bg-transparent active:bg-button-secondary-bg-hover"),
|
|
82
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
83
|
+
className: cn("text-sm font-medium", current ? "text-button-primary-text" : "text-button-ghost-text"),
|
|
84
|
+
children: item
|
|
85
|
+
})
|
|
86
|
+
}, item);
|
|
87
|
+
}),
|
|
88
|
+
/* @__PURE__ */ jsx(ArrowButton, {
|
|
89
|
+
label: nextLabel,
|
|
90
|
+
disabled: page >= pageCount,
|
|
91
|
+
onPress: () => onPageChange(page + 1)
|
|
92
|
+
})
|
|
93
|
+
]
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
97
|
+
export { Pagination, getPages };
|
|
98
|
+
|
|
99
|
+
//# sourceMappingURL=pagination.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pagination.js","names":[],"sources":["../../../src/components/pagination/pagination.tsx"],"sourcesContent":["/// <reference types=\"uniwind/types\" />\n\nimport { Pressable, Text, View, type ViewProps } from 'react-native'\nimport { cn } from '../../utils/cn'\nimport { ChevronRightIcon } from '../_shared/status'\nimport { useToken } from '../_shared/use-token'\n\n/**\n * Páginas a mostrar: a primeira, a última, a atual e `siblingCount` vizinhas de cada lado, com\n * reticências no que sobra. Mantém sempre o mesmo número de itens, para a barra não pular.\n */\nexport function getPages(page: number, pageCount: number, siblingCount = 1) {\n const slots = siblingCount * 2 + 5\n if (pageCount <= slots) return Array.from({ length: pageCount }, (_, i) => i + 1)\n\n const range = (from: number, to: number) =>\n Array.from({ length: to - from + 1 }, (_, i) => from + i)\n const left = Math.max(page - siblingCount, 1)\n const right = Math.min(page + siblingCount, pageCount)\n const dotsLeft = left > 2\n const dotsRight = right < pageCount - 1\n const edge = 3 + siblingCount * 2\n\n if (!dotsLeft) return [...range(1, edge), 'ellipsis-right' as const, pageCount]\n if (!dotsRight) return [1, 'ellipsis-left' as const, ...range(pageCount - edge + 1, pageCount)]\n return [1, 'ellipsis-left' as const, ...range(left, right), 'ellipsis-right' as const, pageCount]\n}\n\nexport type PaginationProps = ViewProps & {\n className?: string\n /** Página atual, começando em 1. */\n page: number\n /** Total de páginas. */\n pageCount: number\n /** Chamado com o número da página escolhida. */\n onPageChange: (page: number) => void\n /** Quantas páginas vizinhas da atual mostrar de cada lado. */\n siblingCount?: number\n /** Nome acessível do botão de página anterior. */\n previousLabel?: string\n /** Nome acessível do botão de próxima página. */\n nextLabel?: string\n}\n\nfunction ArrowButton({\n label,\n disabled,\n reversed,\n onPress,\n}: {\n label: string\n disabled: boolean\n reversed?: boolean\n onPress: () => void\n}) {\n const color = useToken(disabled ? '--color-text-disabled' : '--color-text-primary')\n\n return (\n <Pressable\n accessibilityRole=\"button\"\n accessibilityLabel={label}\n accessibilityState={{ disabled }}\n disabled={disabled}\n onPress={onPress}\n className=\"size-9 items-center justify-center rounded-full bg-transparent active:bg-button-secondary-bg-hover\"\n >\n {/* Valor dinâmico: a seta anterior é a próxima girada em 180°. */}\n <View style={reversed ? { transform: [{ rotate: '180deg' }] } : undefined}>\n <ChevronRightIcon color={color} />\n </View>\n </Pressable>\n )\n}\n\n/** Navegação entre páginas de uma lista ou tabela. Controlado: você guarda o estado de `page`. */\nexport function Pagination({\n className,\n page,\n pageCount,\n onPageChange,\n siblingCount = 1,\n previousLabel = 'Página anterior',\n nextLabel = 'Próxima página',\n accessibilityLabel = 'Paginação',\n ...props\n}: PaginationProps) {\n return (\n // RN não tem role \"navigation\": o nome acessível fica no accessibilityLabel.\n <View\n accessibilityLabel={accessibilityLabel}\n className={cn('flex-row items-center gap-1', className)}\n {...props}\n >\n <ArrowButton\n label={previousLabel}\n reversed\n disabled={page <= 1}\n onPress={() => onPageChange(page - 1)}\n />\n {getPages(page, pageCount, siblingCount).map((item) => {\n if (typeof item === 'string') {\n return (\n <View\n key={item}\n accessible={false}\n importantForAccessibility=\"no-hide-descendants\"\n className=\"size-9 items-center justify-center\"\n >\n <Text className=\"text-sm text-text-secondary\">…</Text>\n </View>\n )\n }\n const current = item === page\n return (\n <Pressable\n key={item}\n accessibilityRole=\"button\"\n accessibilityLabel={`Página ${item}`}\n // RN não tem aria-current: a página atual vai como `selected`.\n accessibilityState={{ selected: current }}\n onPress={() => onPageChange(item)}\n className={cn(\n 'size-9 items-center justify-center rounded-full',\n current\n ? 'bg-button-primary-bg active:bg-button-primary-bg-active'\n : 'bg-transparent active:bg-button-secondary-bg-hover',\n )}\n >\n <Text\n className={cn(\n 'text-sm font-medium',\n current ? 'text-button-primary-text' : 'text-button-ghost-text',\n )}\n >\n {item}\n </Text>\n </Pressable>\n )\n })}\n <ArrowButton\n label={nextLabel}\n disabled={page >= pageCount}\n onPress={() => onPageChange(page + 1)}\n />\n </View>\n )\n}\n"],"mappings":";;;;;;;;;;AAWA,SAAgB,SAAS,MAAc,WAAmB,eAAe,GAAG;CAE1E,IAAI,aADU,eAAe,IAAI,GACT,OAAO,MAAM,KAAK,EAAE,QAAQ,UAAU,IAAI,GAAG,MAAM,IAAI,CAAC;CAEhF,MAAM,SAAS,MAAc,OAC3B,MAAM,KAAK,EAAE,QAAQ,KAAK,OAAO,EAAE,IAAI,GAAG,MAAM,OAAO,CAAC;CAC1D,MAAM,OAAO,KAAK,IAAI,OAAO,cAAc,CAAC;CAC5C,MAAM,QAAQ,KAAK,IAAI,OAAO,cAAc,SAAS;CACrD,MAAM,WAAW,OAAO;CACxB,MAAM,YAAY,QAAQ,YAAY;CACtC,MAAM,OAAO,IAAI,eAAe;CAEhC,IAAI,CAAC,UAAU,OAAO;EAAC,GAAG,MAAM,GAAG,IAAI;EAAG;EAA2B;CAAS;CAC9E,IAAI,CAAC,WAAW,OAAO;EAAC;EAAG;EAA0B,GAAG,MAAM,YAAY,OAAO,GAAG,SAAS;CAAC;CAC9F,OAAO;EAAC;EAAG;EAA0B,GAAG,MAAM,MAAM,KAAK;EAAG;EAA2B;CAAS;AAClG;AAkBA,SAAS,YAAY,EACnB,OACA,UACA,UACA,WAMC;CACD,MAAM,QAAQ,SAAS,WAAW,0BAA0B,sBAAsB;CAElF,OACE,oBAAC,WAAD;EACE,mBAAkB;EAClB,oBAAoB;EACpB,oBAAoB,EAAE,SAAS;EACrB;EACD;EACT,WAAU;EAGV,UAAA,oBAAC,MAAD;GAAM,OAAO,WAAW,EAAE,WAAW,CAAC,EAAE,QAAQ,SAAS,CAAC,EAAE,IAAI,KAAA;GAC9D,UAAA,oBAAC,kBAAD,EAAyB,MAAQ,CAAA;EAC7B,CAAA;CACG,CAAA;AAEf;;AAGA,SAAgB,WAAW,EACzB,WACA,MACA,WACA,cACA,eAAe,GACf,gBAAgB,mBAChB,YAAY,kBACZ,qBAAqB,aACrB,GAAG,SACe;CAClB,OAEE,qBAAC,MAAD;EACsB;EACpB,WAAW,GAAG,+BAA+B,SAAS;EACtD,GAAI;EAHN,UAAA;GAKE,oBAAC,aAAD;IACE,OAAO;IACP,UAAA;IACA,UAAU,QAAQ;IAClB,eAAe,aAAa,OAAO,CAAC;GACrC,CAAA;GACA,SAAS,MAAM,WAAW,YAAY,CAAC,CAAC,KAAK,SAAS;IACrD,IAAI,OAAO,SAAS,UAClB,OACE,oBAAC,MAAD;KAEE,YAAY;KACZ,2BAA0B;KAC1B,WAAU;KAEV,UAAA,oBAAC,MAAD;MAAM,WAAU;MAA8B,UAAA;KAAO,CAAA;IACjD,GANC,IAMD;IAGV,MAAM,UAAU,SAAS;IACzB,OACE,oBAAC,WAAD;KAEE,mBAAkB;KAClB,oBAAoB,UAAU;KAE9B,oBAAoB,EAAE,UAAU,QAAQ;KACxC,eAAe,aAAa,IAAI;KAChC,WAAW,GACT,mDACA,UACI,4DACA,oDACN;KAEA,UAAA,oBAAC,MAAD;MACE,WAAW,GACT,uBACA,UAAU,6BAA6B,wBACzC;MAEC,UAAA;KACG,CAAA;IACG,GArBJ,IAqBI;GAEf,CAAC;GACD,oBAAC,aAAD;IACE,OAAO;IACP,UAAU,QAAQ;IAClB,eAAe,aAAa,OAAO,CAAC;GACrC,CAAA;EACG;;AAEV"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
//#region src/components/popover/popover.d.ts
|
|
3
|
+
export type PopoverProps = {
|
|
4
|
+
open?: boolean;
|
|
5
|
+
defaultOpen?: boolean;
|
|
6
|
+
onOpenChange?: (open: boolean) => void;
|
|
7
|
+
/** Lado do gatilho em que o painel abre; inverte se não couber. Padrão: `bottom`. */
|
|
8
|
+
side?: 'top' | 'bottom';
|
|
9
|
+
/** Elemento que abre o popover (ex.: um `<Button>`); recebe `onPress`. */
|
|
10
|
+
trigger: ReactNode;
|
|
11
|
+
/** Nome acessível do painel: descreve o que há nele, ex.: "Filtros". */
|
|
12
|
+
label: string;
|
|
13
|
+
/** O conteúdo do popover. Pode ter elementos interativos. */
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
className?: string;
|
|
16
|
+
testID?: string;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Painel flutuante ancorado ao gatilho, aberto por toque. O conteúdo pode ser interativo; tocar
|
|
20
|
+
* fora ou o botão voltar do Android fecha. Diferenças do web: sem `align` (o painel alinha à
|
|
21
|
+
* esquerda do gatilho), sem lados `left`/`right` e sem devolução de foco.
|
|
22
|
+
*/
|
|
23
|
+
export declare function Popover({ open, defaultOpen, onOpenChange, side, trigger, label, children, className, testID }: PopoverProps): import("react").JSX.Element;
|
|
24
|
+
/** Fecha o popover ao pressionar. Envolva um `<Button>` dentro do conteúdo (injeta `onPress`). */
|
|
25
|
+
export declare function PopoverClose({ children }: {
|
|
26
|
+
children: ReactNode;
|
|
27
|
+
}): import("react").JSX.Element;
|
|
28
|
+
//#endregion
|
|
29
|
+
//# sourceMappingURL=popover.d.ts.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { cn } from "../../utils/cn.js";
|
|
2
|
+
import { AnchoredOverlay } from "../_shared/anchored-overlay.js";
|
|
3
|
+
import { injectHandler, useOpenState } from "../_shared/overlay-state.js";
|
|
4
|
+
import { AnchorTrigger, useControlledAnchor } from "../_shared/overlay-anchor.js";
|
|
5
|
+
import { createContext, useContext } from "react";
|
|
6
|
+
import { View } from "react-native";
|
|
7
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
//#region src/components/popover/popover.tsx
|
|
9
|
+
const CloseContext = createContext(null);
|
|
10
|
+
/**
|
|
11
|
+
* Painel flutuante ancorado ao gatilho, aberto por toque. O conteúdo pode ser interativo; tocar
|
|
12
|
+
* fora ou o botão voltar do Android fecha. Diferenças do web: sem `align` (o painel alinha à
|
|
13
|
+
* esquerda do gatilho), sem lados `left`/`right` e sem devolução de foco.
|
|
14
|
+
*/
|
|
15
|
+
function Popover({ open, defaultOpen, onOpenChange, side = "bottom", trigger, label, children, className, testID }) {
|
|
16
|
+
const [isOpen, setOpen] = useOpenState({
|
|
17
|
+
open,
|
|
18
|
+
defaultOpen,
|
|
19
|
+
onOpenChange
|
|
20
|
+
});
|
|
21
|
+
const { ref, rect } = useControlledAnchor(isOpen);
|
|
22
|
+
const close = () => setOpen(false);
|
|
23
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(AnchorTrigger, {
|
|
24
|
+
anchorRef: ref,
|
|
25
|
+
event: "onPress",
|
|
26
|
+
onTrigger: () => setOpen(!isOpen),
|
|
27
|
+
children: trigger
|
|
28
|
+
}), /* @__PURE__ */ jsx(AnchoredOverlay, {
|
|
29
|
+
visible: isOpen,
|
|
30
|
+
rect,
|
|
31
|
+
onRequestClose: close,
|
|
32
|
+
placement: side,
|
|
33
|
+
offset: 8,
|
|
34
|
+
testID,
|
|
35
|
+
className: cn("max-w-xs rounded-md border border-border-default bg-surface-elevated p-4 shadow-md", className),
|
|
36
|
+
children: /* @__PURE__ */ jsx(CloseContext.Provider, {
|
|
37
|
+
value: close,
|
|
38
|
+
children: /* @__PURE__ */ jsx(View, {
|
|
39
|
+
accessibilityLabel: label,
|
|
40
|
+
accessibilityViewIsModal: true,
|
|
41
|
+
children
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
})] });
|
|
45
|
+
}
|
|
46
|
+
/** Fecha o popover ao pressionar. Envolva um `<Button>` dentro do conteúdo (injeta `onPress`). */
|
|
47
|
+
function PopoverClose({ children }) {
|
|
48
|
+
const close = useContext(CloseContext);
|
|
49
|
+
return /* @__PURE__ */ jsx(Fragment$1, { children: injectHandler(children, "onPress", () => close?.()) });
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
export { Popover, PopoverClose };
|
|
53
|
+
|
|
54
|
+
//# sourceMappingURL=popover.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"popover.js","names":[],"sources":["../../../src/components/popover/popover.tsx"],"sourcesContent":["/// <reference types=\"uniwind/types\" />\n\nimport { createContext, type ReactNode, useContext } from 'react'\nimport { View } from 'react-native'\nimport { cn } from '../../utils/cn'\nimport { AnchoredOverlay } from '../_shared/anchored-overlay'\nimport { AnchorTrigger, useControlledAnchor } from '../_shared/overlay-anchor'\nimport { injectHandler, useOpenState } from '../_shared/overlay-state'\n\nconst CloseContext = createContext<(() => void) | null>(null)\n\nexport type PopoverProps = {\n open?: boolean\n defaultOpen?: boolean\n onOpenChange?: (open: boolean) => void\n /** Lado do gatilho em que o painel abre; inverte se não couber. Padrão: `bottom`. */\n side?: 'top' | 'bottom'\n /** Elemento que abre o popover (ex.: um `<Button>`); recebe `onPress`. */\n trigger: ReactNode\n /** Nome acessível do painel: descreve o que há nele, ex.: \"Filtros\". */\n label: string\n /** O conteúdo do popover. Pode ter elementos interativos. */\n children: ReactNode\n className?: string\n testID?: string\n}\n\n/**\n * Painel flutuante ancorado ao gatilho, aberto por toque. O conteúdo pode ser interativo; tocar\n * fora ou o botão voltar do Android fecha. Diferenças do web: sem `align` (o painel alinha à\n * esquerda do gatilho), sem lados `left`/`right` e sem devolução de foco.\n */\nexport function Popover({\n open,\n defaultOpen,\n onOpenChange,\n side = 'bottom',\n trigger,\n label,\n children,\n className,\n testID,\n}: PopoverProps) {\n const [isOpen, setOpen] = useOpenState({ open, defaultOpen, onOpenChange })\n const { ref, rect } = useControlledAnchor(isOpen)\n const close = () => setOpen(false)\n\n return (\n <>\n <AnchorTrigger anchorRef={ref} event=\"onPress\" onTrigger={() => setOpen(!isOpen)}>\n {trigger}\n </AnchorTrigger>\n <AnchoredOverlay\n visible={isOpen}\n rect={rect}\n onRequestClose={close}\n placement={side}\n offset={8}\n testID={testID}\n className={cn(\n 'max-w-xs rounded-md border border-border-default bg-surface-elevated p-4 shadow-md',\n className,\n )}\n >\n <CloseContext.Provider value={close}>\n <View accessibilityLabel={label} accessibilityViewIsModal>\n {children}\n </View>\n </CloseContext.Provider>\n </AnchoredOverlay>\n </>\n )\n}\n\n/** Fecha o popover ao pressionar. Envolva um `<Button>` dentro do conteúdo (injeta `onPress`). */\nexport function PopoverClose({ children }: { children: ReactNode }) {\n const close = useContext(CloseContext)\n return <>{injectHandler(children, 'onPress', () => close?.())}</>\n}\n"],"mappings":";;;;;;;;AASA,MAAM,eAAe,cAAmC,IAAI;;;;;;AAuB5D,SAAgB,QAAQ,EACtB,MACA,aACA,cACA,OAAO,UACP,SACA,OACA,UACA,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,OACE,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,eAAD;EAAe,WAAW;EAAK,OAAM;EAAU,iBAAiB,QAAQ,CAAC,MAAM;EAC5E,UAAA;CACY,CAAA,GACf,oBAAC,iBAAD;EACE,SAAS;EACH;EACN,gBAAgB;EAChB,WAAW;EACX,QAAQ;EACA;EACR,WAAW,GACT,sFACA,SACF;EAEA,UAAA,oBAAC,aAAa,UAAd;GAAuB,OAAO;GAC5B,UAAA,oBAAC,MAAD;IAAM,oBAAoB;IAAO,0BAAA;IAC9B;GACG,CAAA;EACe,CAAA;CACR,CAAA,CACjB,EAAA,CAAA;AAEN;;AAGA,SAAgB,aAAa,EAAE,YAAqC;CAClE,MAAM,QAAQ,WAAW,YAAY;CACrC,OAAO,oBAAA,YAAA,EAAA,UAAG,cAAc,UAAU,iBAAiB,QAAQ,CAAC,EAAI,CAAA;AAClE"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import { PressableProps } from "react-native";
|
|
3
|
+
//#region src/components/radio/radio.d.ts
|
|
4
|
+
export type RadioProps = Omit<PressableProps, 'children' | 'style' | 'onPress'> & {
|
|
5
|
+
className?: string;
|
|
6
|
+
label?: ReactNode;
|
|
7
|
+
/** Controlado. No native não há `name`: para formar um grupo, controle `checked` de cada opção. */
|
|
8
|
+
checked?: boolean;
|
|
9
|
+
defaultChecked?: boolean;
|
|
10
|
+
/** Chamado com `true` ao selecionar; tocar numa opção já marcada não faz nada. */
|
|
11
|
+
onCheckedChange?: (checked: boolean) => void;
|
|
12
|
+
error?: boolean;
|
|
13
|
+
};
|
|
14
|
+
export declare function Radio({ label, checked, defaultChecked, onCheckedChange, error, disabled, className, accessibilityState, ...props }: RadioProps): import("react").JSX.Element;
|
|
15
|
+
//#endregion
|
|
16
|
+
//# sourceMappingURL=radio.d.ts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { cn } from "../../utils/cn.js";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Pressable, Text, View } from "react-native";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
//#region src/components/radio/radio.tsx
|
|
6
|
+
function Radio({ label, checked, defaultChecked = false, onCheckedChange, error, disabled, className, accessibilityState, ...props }) {
|
|
7
|
+
const [uncontrolled, setUncontrolled] = useState(defaultChecked);
|
|
8
|
+
const isChecked = checked ?? uncontrolled;
|
|
9
|
+
const isDisabled = Boolean(disabled);
|
|
10
|
+
return /* @__PURE__ */ jsxs(Pressable, {
|
|
11
|
+
accessibilityRole: "radio",
|
|
12
|
+
accessibilityState: {
|
|
13
|
+
...accessibilityState,
|
|
14
|
+
checked: isChecked,
|
|
15
|
+
disabled: isDisabled
|
|
16
|
+
},
|
|
17
|
+
disabled: isDisabled,
|
|
18
|
+
onPress: () => {
|
|
19
|
+
if (isChecked) return;
|
|
20
|
+
setUncontrolled(true);
|
|
21
|
+
onCheckedChange?.(true);
|
|
22
|
+
},
|
|
23
|
+
className: cn("min-h-11 flex-row items-center gap-3 self-start", className),
|
|
24
|
+
...props,
|
|
25
|
+
children: [/* @__PURE__ */ jsx(View, {
|
|
26
|
+
className: cn("size-5 items-center justify-center rounded-full border-2 border-selection-border-unselected bg-selection-bg-unselected", isChecked && "border-selection-border-selected bg-selection-bg-selected", isDisabled && "border-selection-border-disabled bg-selection-bg-disabled", error && "border-status-error-border"),
|
|
27
|
+
children: isChecked ? /* @__PURE__ */ jsx(View, { className: cn("size-2.5 rounded-full bg-selection-indicator-selected", isDisabled && "bg-selection-text-disabled") }) : null
|
|
28
|
+
}), typeof label === "string" ? /* @__PURE__ */ jsx(Text, {
|
|
29
|
+
className: cn("text-sm font-medium text-selection-text-label", isDisabled && "text-selection-text-disabled"),
|
|
30
|
+
children: label
|
|
31
|
+
}) : label]
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
export { Radio };
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=radio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"radio.js","names":[],"sources":["../../../src/components/radio/radio.tsx"],"sourcesContent":["/// <reference types=\"uniwind/types\" />\n\nimport { type ReactNode, useState } from 'react'\nimport { Pressable, type PressableProps, Text, View } from 'react-native'\nimport { cn } from '../../utils/cn'\n\nexport type RadioProps = Omit<PressableProps, 'children' | 'style' | 'onPress'> & {\n className?: string\n label?: ReactNode\n /** Controlado. No native não há `name`: para formar um grupo, controle `checked` de cada opção. */\n checked?: boolean\n defaultChecked?: boolean\n /** Chamado com `true` ao selecionar; tocar numa opção já marcada não faz nada. */\n onCheckedChange?: (checked: boolean) => void\n error?: boolean\n}\n\nexport function Radio({\n label,\n checked,\n defaultChecked = false,\n onCheckedChange,\n error,\n disabled,\n className,\n accessibilityState,\n ...props\n}: RadioProps) {\n const [uncontrolled, setUncontrolled] = useState(defaultChecked)\n const isChecked = checked ?? uncontrolled\n const isDisabled = Boolean(disabled)\n\n return (\n <Pressable\n accessibilityRole=\"radio\"\n accessibilityState={{ ...accessibilityState, checked: isChecked, disabled: isDisabled }}\n disabled={isDisabled}\n onPress={() => {\n if (isChecked) return\n setUncontrolled(true)\n onCheckedChange?.(true)\n }}\n className={cn('min-h-11 flex-row items-center gap-3 self-start', className)}\n {...props}\n >\n <View\n className={cn(\n 'size-5 items-center justify-center rounded-full border-2 border-selection-border-unselected bg-selection-bg-unselected',\n isChecked && 'border-selection-border-selected bg-selection-bg-selected',\n isDisabled && 'border-selection-border-disabled bg-selection-bg-disabled',\n error && 'border-status-error-border',\n )}\n >\n {isChecked ? (\n <View\n className={cn(\n 'size-2.5 rounded-full bg-selection-indicator-selected',\n isDisabled && 'bg-selection-text-disabled',\n )}\n />\n ) : null}\n </View>\n {typeof label === 'string' ? (\n <Text\n className={cn(\n 'text-sm font-medium text-selection-text-label',\n isDisabled && 'text-selection-text-disabled',\n )}\n >\n {label}\n </Text>\n ) : (\n label\n )}\n </Pressable>\n )\n}\n"],"mappings":";;;;;AAiBA,SAAgB,MAAM,EACpB,OACA,SACA,iBAAiB,OACjB,iBACA,OACA,UACA,WACA,oBACA,GAAG,SACU;CACb,MAAM,CAAC,cAAc,mBAAmB,SAAS,cAAc;CAC/D,MAAM,YAAY,WAAW;CAC7B,MAAM,aAAa,QAAQ,QAAQ;CAEnC,OACE,qBAAC,WAAD;EACE,mBAAkB;EAClB,oBAAoB;GAAE,GAAG;GAAoB,SAAS;GAAW,UAAU;EAAW;EACtF,UAAU;EACV,eAAe;GACb,IAAI,WAAW;GACf,gBAAgB,IAAI;GACpB,kBAAkB,IAAI;EACxB;EACA,WAAW,GAAG,mDAAmD,SAAS;EAC1E,GAAI;EAVN,UAAA,CAYE,oBAAC,MAAD;GACE,WAAW,GACT,0HACA,aAAa,6DACb,cAAc,6DACd,SAAS,4BACX;GAEC,UAAA,YACC,oBAAC,MAAD,EACE,WAAW,GACT,yDACA,cAAc,4BAChB,EACD,CAAA,IACC;EACA,CAAA,GACL,OAAO,UAAU,WAChB,oBAAC,MAAD;GACE,WAAW,GACT,iDACA,cAAc,8BAChB;GAEC,UAAA;EACG,CAAA,IAEN,KAEO;;AAEf"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import { PressableProps, ViewProps } from "react-native";
|
|
3
|
+
//#region src/components/segmented-control/segmented-control.d.ts
|
|
4
|
+
export type SegmentedControlProps = Omit<ViewProps, 'style'> & {
|
|
5
|
+
className?: string;
|
|
6
|
+
/** Obrigatório: descreve o que o controle escolhe (ex.: "Modo de exibição"). */
|
|
7
|
+
accessibilityLabel: string;
|
|
8
|
+
/** Controlado. Sem ele, o componente guarda o próprio estado (começa em `defaultValue`). */
|
|
9
|
+
value?: string;
|
|
10
|
+
defaultValue?: string;
|
|
11
|
+
onValueChange?: (value: string) => void;
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
};
|
|
14
|
+
/** Escolha única entre 2–5 views do mesmo conteúdo. Para navegar entre seções, use Tabs. */
|
|
15
|
+
export declare function SegmentedControl({ className, value, defaultValue, onValueChange, ...props }: SegmentedControlProps): import("react").JSX.Element;
|
|
16
|
+
export type SegmentedItemProps = Omit<PressableProps, 'style' | 'children' | 'onPress'> & {
|
|
17
|
+
className?: string;
|
|
18
|
+
value: string;
|
|
19
|
+
children: ReactNode;
|
|
20
|
+
};
|
|
21
|
+
export declare function SegmentedItem({ className, value, children, disabled, accessibilityState, ...props }: SegmentedItemProps): import("react").JSX.Element;
|
|
22
|
+
//#endregion
|
|
23
|
+
//# sourceMappingURL=segmented-control.d.ts.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { cn } from "../../utils/cn.js";
|
|
2
|
+
import { createContext, useContext, useState } from "react";
|
|
3
|
+
import { Pressable, Text, View } from "react-native";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
//#region src/components/segmented-control/segmented-control.tsx
|
|
6
|
+
const Context = createContext(null);
|
|
7
|
+
/** Escolha única entre 2–5 views do mesmo conteúdo. Para navegar entre seções, use Tabs. */
|
|
8
|
+
function SegmentedControl({ className, value, defaultValue, onValueChange, ...props }) {
|
|
9
|
+
const [uncontrolled, setUncontrolled] = useState(defaultValue);
|
|
10
|
+
const current = value ?? uncontrolled;
|
|
11
|
+
const select = (next) => {
|
|
12
|
+
if (next === current) return;
|
|
13
|
+
setUncontrolled(next);
|
|
14
|
+
onValueChange?.(next);
|
|
15
|
+
};
|
|
16
|
+
return /* @__PURE__ */ jsx(Context.Provider, {
|
|
17
|
+
value: {
|
|
18
|
+
value: current,
|
|
19
|
+
select
|
|
20
|
+
},
|
|
21
|
+
children: /* @__PURE__ */ jsx(View, {
|
|
22
|
+
accessibilityRole: "radiogroup",
|
|
23
|
+
className: cn("flex-row items-start gap-1 self-start rounded-md bg-bg-tertiary p-1", className),
|
|
24
|
+
...props
|
|
25
|
+
})
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function SegmentedItem({ className, value, children, disabled, accessibilityState, ...props }) {
|
|
29
|
+
const group = useContext(Context);
|
|
30
|
+
if (!group) throw new Error("SegmentedItem deve ser usado dentro de SegmentedControl");
|
|
31
|
+
const checked = group.value === value;
|
|
32
|
+
const isDisabled = Boolean(disabled);
|
|
33
|
+
return /* @__PURE__ */ jsx(Pressable, {
|
|
34
|
+
accessibilityRole: "radio",
|
|
35
|
+
accessibilityState: {
|
|
36
|
+
...accessibilityState,
|
|
37
|
+
checked,
|
|
38
|
+
disabled: isDisabled
|
|
39
|
+
},
|
|
40
|
+
disabled: isDisabled,
|
|
41
|
+
onPress: () => group.select(value),
|
|
42
|
+
className: cn("items-center justify-center rounded-md px-4 py-2", checked ? "bg-surface-primary shadow-sm" : "active:bg-bg-secondary", className),
|
|
43
|
+
...props,
|
|
44
|
+
children: typeof children === "string" ? /* @__PURE__ */ jsx(Text, {
|
|
45
|
+
className: cn("text-sm leading-5 text-text-secondary", checked && "font-medium text-text-primary", isDisabled && "text-text-disabled"),
|
|
46
|
+
children
|
|
47
|
+
}) : children
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { SegmentedControl, SegmentedItem };
|
|
52
|
+
|
|
53
|
+
//# sourceMappingURL=segmented-control.js.map
|
|
@@ -0,0 +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"}
|