@aoctech/ui 0.1.3 → 0.1.5
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.
|
@@ -5,7 +5,6 @@ interface CalendarProps {
|
|
|
5
5
|
disabled?: Matcher | Matcher[];
|
|
6
6
|
className?: string;
|
|
7
7
|
}
|
|
8
|
-
/** An inline, keyboard-operable calendar. Use DatePicker for a field trigger. */
|
|
9
8
|
declare function Calendar({ selected, onSelect, disabled, className }: CalendarProps): import("react").JSX.Element;
|
|
10
9
|
interface DatePickerProps {
|
|
11
10
|
value?: Date;
|
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Popover } from "@base-ui/react/popover";
|
|
4
|
+
import { format } from "date-fns";
|
|
4
5
|
import { ptBR } from "date-fns/locale/pt-BR";
|
|
5
6
|
import { useState } from "react";
|
|
6
7
|
import { DayPicker } from "react-day-picker";
|
|
7
8
|
import { cn } from "../lib/cn";
|
|
8
9
|
import { useThemeScope } from "./theme-provider";
|
|
9
10
|
/** An inline, keyboard-operable calendar. Use DatePicker for a field trigger. */
|
|
11
|
+
// react-day-picker's default labels are English ("Go to the Previous Month",
|
|
12
|
+
// "Today, …"): a screen reader in a pt-BR product read half its calendar in
|
|
13
|
+
// another language.
|
|
14
|
+
const LABELS = {
|
|
15
|
+
labelPrevious: () => "Mês anterior",
|
|
16
|
+
labelNext: () => "Próximo mês",
|
|
17
|
+
labelDayButton: (date, modifiers) => `${modifiers.today ? "Hoje, " : ""}${format(date, "PPPP", { locale: ptBR })}${modifiers.selected ? ", selecionado" : ""}`,
|
|
18
|
+
};
|
|
10
19
|
function Calendar({ selected, onSelect, disabled, className }) {
|
|
11
|
-
return (_jsx(DayPicker, { mode: "single",
|
|
20
|
+
return (_jsx(DayPicker, { mode: "single",
|
|
21
|
+
// Open on the selected date's month, not today's: editing a date months
|
|
22
|
+
// away otherwise starts by paging back to it.
|
|
23
|
+
defaultMonth: selected, labels: LABELS, selected: selected, onSelect: onSelect, disabled: disabled, locale: ptBR, classNames: {
|
|
12
24
|
month_caption: "calendar-month-caption",
|
|
13
25
|
caption_label: "calendar-caption-label",
|
|
14
26
|
nav: "calendar-nav",
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* A panel for a task that needs a form but not a new page (shadcn's Drawer, on
|
|
4
|
+
* Base UI's Drawer primitive): from the right on a wide screen, where the page
|
|
5
|
+
* behind stays readable, and from the bottom on a phone, where a side panel
|
|
6
|
+
* would be the whole screen anyway. Swipe it away in the direction it came
|
|
7
|
+
* from; focus trap, Escape, scroll lock and restore-focus come from Base UI.
|
|
8
|
+
*
|
|
9
|
+
* The body is the caller's: a form brings its own buttons, so there is no fixed
|
|
10
|
+
* footer unless `footer` is given (it stays visible while the body scrolls).
|
|
11
|
+
*/
|
|
12
|
+
declare const WIDTH: {
|
|
13
|
+
readonly md: "sm:max-w-md";
|
|
14
|
+
readonly lg: "sm:max-w-xl";
|
|
15
|
+
};
|
|
16
|
+
interface DrawerProps {
|
|
17
|
+
open: boolean;
|
|
18
|
+
onClose: () => void;
|
|
19
|
+
title: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
children?: ReactNode;
|
|
22
|
+
footer?: ReactNode;
|
|
23
|
+
size?: keyof typeof WIDTH;
|
|
24
|
+
}
|
|
25
|
+
declare function Drawer({ open, onClose, title, description, children, footer, size }: DrawerProps): import("react").JSX.Element;
|
|
26
|
+
export { Drawer, type DrawerProps };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Drawer as Primitive } from "@base-ui/react/drawer";
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
|
+
import { cn } from "../lib/cn";
|
|
6
|
+
import { Button } from "./button";
|
|
7
|
+
import { useThemeScope } from "./theme-provider";
|
|
8
|
+
/**
|
|
9
|
+
* A panel for a task that needs a form but not a new page (shadcn's Drawer, on
|
|
10
|
+
* Base UI's Drawer primitive): from the right on a wide screen, where the page
|
|
11
|
+
* behind stays readable, and from the bottom on a phone, where a side panel
|
|
12
|
+
* would be the whole screen anyway. Swipe it away in the direction it came
|
|
13
|
+
* from; focus trap, Escape, scroll lock and restore-focus come from Base UI.
|
|
14
|
+
*
|
|
15
|
+
* The body is the caller's: a form brings its own buttons, so there is no fixed
|
|
16
|
+
* footer unless `footer` is given (it stays visible while the body scrolls).
|
|
17
|
+
*/
|
|
18
|
+
const WIDTH = {
|
|
19
|
+
md: "sm:max-w-md",
|
|
20
|
+
lg: "sm:max-w-xl",
|
|
21
|
+
};
|
|
22
|
+
/** Right on a wide screen, down on a phone; down until the first effect runs (SSR). */
|
|
23
|
+
function useSide() {
|
|
24
|
+
const [wide, setWide] = useState(false);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
// Test environments (jsdom) and very old browsers have no matchMedia.
|
|
27
|
+
if (typeof window.matchMedia !== "function")
|
|
28
|
+
return;
|
|
29
|
+
const q = window.matchMedia("(min-width: 640px)");
|
|
30
|
+
const update = () => setWide(q.matches);
|
|
31
|
+
update();
|
|
32
|
+
q.addEventListener("change", update);
|
|
33
|
+
return () => q.removeEventListener("change", update);
|
|
34
|
+
}, []);
|
|
35
|
+
return wide ? "right" : "down";
|
|
36
|
+
}
|
|
37
|
+
function Drawer({ open, onClose, title, description, children, footer, size = "md" }) {
|
|
38
|
+
const { theme, density } = useThemeScope();
|
|
39
|
+
const side = useSide();
|
|
40
|
+
return (_jsx(Primitive.Root, { open: open, onOpenChange: next => !next && onClose(), swipeDirection: side, children: _jsxs(Primitive.Portal, { "data-ctech-theme": theme, "data-density": density, children: [_jsx(Primitive.Backdrop, { className: cn("fixed inset-0 z-40 bg-black/40 transition-opacity duration-200 ease-out", "data-[ending-style]:opacity-0 data-[starting-style]:opacity-0 motion-reduce:transition-none") }), _jsx(Primitive.Viewport, { className: "fixed inset-0 z-50", children: _jsxs(Primitive.Popup, { className: cn("fixed z-50 flex flex-col bg-background text-foreground shadow-modal outline-none", "transition-transform duration-200 ease-out data-[swiping]:transition-none motion-reduce:transition-none", side === "right"
|
|
41
|
+
? cn("inset-y-0 right-0 w-full border-l border-border", WIDTH[size], "[transform:translateX(var(--drawer-swipe-movement-x,0px))]", "data-[starting-style]:[transform:translateX(100%)] data-[ending-style]:[transform:translateX(100%)]")
|
|
42
|
+
: cn("inset-x-0 bottom-0 max-h-[90dvh] rounded-t-xl border-t border-border", "[transform:translateY(var(--drawer-swipe-movement-y,0px))]", "data-[starting-style]:[transform:translateY(100%)] data-[ending-style]:[transform:translateY(100%)]")), children: [side === "down" && _jsx("div", { "aria-hidden": true, className: "mx-auto mt-2 h-1.5 w-10 shrink-0 rounded-full bg-border" }), _jsxs("header", { className: "flex items-start justify-between gap-4 border-b border-border px-5 py-4", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Primitive.Title, { className: "text-base font-semibold text-foreground", children: title }), description && _jsx(Primitive.Description, { className: "text-sm text-muted-foreground", children: description })] }), _jsx(Primitive.Close, { render: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Fechar", className: "-mr-2 shrink-0 text-muted-foreground" }), children: _jsx(CloseGlyph, {}) })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-5 py-4", children: children }), footer && _jsx("footer", { className: "border-t border-border bg-surface px-5 py-3", children: footer })] }) })] }) }));
|
|
43
|
+
}
|
|
44
|
+
function CloseGlyph() {
|
|
45
|
+
return (_jsx("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, "aria-hidden": true, className: "size-4", children: _jsx("path", { d: "m4 4 8 8M12 4l-8 8", strokeLinecap: "round" }) }));
|
|
46
|
+
}
|
|
47
|
+
export { Drawer };
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export { EmptyState, type EmptyStateProps } from "./components/empty-state";
|
|
|
8
8
|
export { Field, type FieldProps } from "./components/field";
|
|
9
9
|
export { Input } from "./components/input";
|
|
10
10
|
export { Modal, type ModalProps } from "./components/modal";
|
|
11
|
+
export { Drawer, type DrawerProps } from "./components/drawer";
|
|
11
12
|
export { PageHeader, type PageHeaderProps } from "./components/page-header";
|
|
12
13
|
export { Separator } from "./components/separator";
|
|
13
14
|
export { Skeleton } from "./components/skeleton";
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export { EmptyState } from "./components/empty-state";
|
|
|
8
8
|
export { Field } from "./components/field";
|
|
9
9
|
export { Input } from "./components/input";
|
|
10
10
|
export { Modal } from "./components/modal";
|
|
11
|
+
export { Drawer } from "./components/drawer";
|
|
11
12
|
export { PageHeader } from "./components/page-header";
|
|
12
13
|
export { Separator } from "./components/separator";
|
|
13
14
|
export { Skeleton } from "./components/skeleton";
|
package/package.json
CHANGED