supa-ui 0.0.0-stage → 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 (60) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +52 -2
  4. package/dist/index.d.ts +30 -0
  5. package/dist/index.js +25 -0
  6. package/dist/layout/container.d.ts +5 -0
  7. package/dist/layout/container.js +12 -0
  8. package/dist/layout/footer.d.ts +13 -0
  9. package/dist/layout/footer.js +6 -0
  10. package/dist/layout/header.d.ts +19 -0
  11. package/dist/layout/header.js +5 -0
  12. package/dist/layout/layout.d.ts +10 -0
  13. package/dist/layout/layout.js +5 -0
  14. package/dist/layout/navbar.d.ts +17 -0
  15. package/dist/layout/navbar.js +19 -0
  16. package/dist/layout/sidebar.d.ts +10 -0
  17. package/dist/layout/sidebar.js +11 -0
  18. package/dist/lib/cn.d.ts +1 -0
  19. package/dist/lib/cn.js +4 -0
  20. package/dist/shared/empty-state.d.ts +11 -0
  21. package/dist/shared/empty-state.js +14 -0
  22. package/dist/shared/loading-state.d.ts +5 -0
  23. package/dist/shared/loading-state.js +6 -0
  24. package/dist/shared/page-header.d.ts +8 -0
  25. package/dist/shared/page-header.js +5 -0
  26. package/dist/styles.css +2 -0
  27. package/dist/toastly/provider.d.ts +3 -0
  28. package/dist/toastly/provider.js +111 -0
  29. package/dist/toastly/toastly.d.ts +28 -0
  30. package/dist/toastly/toastly.js +92 -0
  31. package/dist/toastly/types.d.ts +36 -0
  32. package/dist/toastly/types.js +1 -0
  33. package/dist/ui/avatar.d.ts +7 -0
  34. package/dist/ui/avatar.js +12 -0
  35. package/dist/ui/badge.d.ts +6 -0
  36. package/dist/ui/badge.js +15 -0
  37. package/dist/ui/button.d.ts +10 -0
  38. package/dist/ui/button.js +18 -0
  39. package/dist/ui/card.d.ts +12 -0
  40. package/dist/ui/card.js +20 -0
  41. package/dist/ui/checkbox.d.ts +6 -0
  42. package/dist/ui/checkbox.js +8 -0
  43. package/dist/ui/dropdown.d.ts +19 -0
  44. package/dist/ui/dropdown.js +43 -0
  45. package/dist/ui/input.d.ts +10 -0
  46. package/dist/ui/input.js +19 -0
  47. package/dist/ui/link.d.ts +11 -0
  48. package/dist/ui/link.js +17 -0
  49. package/dist/ui/modal.d.ts +12 -0
  50. package/dist/ui/modal.js +28 -0
  51. package/dist/ui/select.d.ts +14 -0
  52. package/dist/ui/select.js +17 -0
  53. package/dist/ui/spinner.d.ts +5 -0
  54. package/dist/ui/spinner.js +6 -0
  55. package/dist/ui/tabs.d.ts +16 -0
  56. package/dist/ui/tabs.js +39 -0
  57. package/dist/ui/textarea.d.ts +7 -0
  58. package/dist/ui/textarea.js +10 -0
  59. package/package.json +58 -4
  60. package/src/theme.css +130 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # Changelog
2
+
3
+ ## 0.3.0
4
+
5
+ - Première version publique : composants UI, layouts, Toastly (toasts, alertes, confirmations).
6
+ - Variables CSS préfixées `--sui-*`.
7
+ - CSS précompilé (`supa-ui/styles.css`) et tokens Tailwind (`supa-ui/theme.css`).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nana Elie
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,53 @@
1
- # Temporary Holding Version
1
+ # supa-ui
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ UI kit inspiré du dashboard Supabase pour React et Next.js, avec Toastly (toasts, alertes, confirmations) intégré.
4
+
5
+ ## Installation
6
+
7
+ npm install supa-ui
8
+
9
+ Dans `app/layout.tsx`, importer le CSS compilé (aucun Tailwind requis dans le projet) :
10
+
11
+ import "supa-ui/styles.css";
12
+
13
+ Thème sombre : ajouter la classe `dark` sur `<html>`.
14
+
15
+ Toutes les variables du kit sont préfixées `--sui-*` : elles n'entrent pas en conflit avec les
16
+ `--background` / `--foreground` de ton projet.
17
+
18
+ ## Si ton projet utilise déjà Tailwind CSS v4 (et que tu veux les tokens dans tes propres classes)
19
+
20
+ Dans `globals.css`, à la place de l'import compilé :
21
+
22
+ @import "tailwindcss";
23
+ @import "supa-ui/theme.css";
24
+ @source "../../node_modules/supa-ui/dist";
25
+
26
+ Les classes comme `bg-card`, `border-border` ou `text-foreground-muted` sont alors disponibles dans ton code.
27
+
28
+ ## Composants
29
+
30
+ ui : Button, Input, Select, Textarea, Checkbox, Badge, Avatar, Spinner, Modal, Link, Tabs, Dropdown, Card
31
+ layout : Header, Navbar (menu mobile), Sidebar, Footer, Container, Layout
32
+ shared : PageHeader, EmptyState, LoadingState
33
+
34
+ ## Toastly
35
+
36
+ import { ToastlyProvider, toastly } from "supa-ui";
37
+
38
+ <ToastlyProvider position="bottom-right" duration={4000}>{children}</ToastlyProvider>
39
+
40
+ toastly.success("Profil enregistré");
41
+ toastly.promise(save(), { loading: "Envoi...", success: "Envoyé", error: "Erreur" });
42
+ await toastly.alert({ title: "Session", message: "Votre session expire bientôt." });
43
+ const ok = await toastly.confirm({ title: "Supprimer ?", danger: true });
44
+
45
+ ## Développement et publication
46
+
47
+ npm install
48
+ npm run build # tsc (dist/*.js) + CSS compilé (dist/styles.css)
49
+ npm pack --dry-run # vérifier le contenu
50
+ npm publish
51
+
52
+ Limite connue : les noms de classes (`bg-background`, `text-foreground`...) peuvent entrer en collision
53
+ avec ceux d'un projet qui définit les mêmes noms (thème shadcn par exemple).
@@ -0,0 +1,30 @@
1
+ export { default as Button } from "./ui/button";
2
+ export { default as Input } from "./ui/input";
3
+ export { default as Select } from "./ui/select";
4
+ export { default as Textarea } from "./ui/textarea";
5
+ export { default as Checkbox } from "./ui/checkbox";
6
+ export { default as Badge } from "./ui/badge";
7
+ export { default as Avatar } from "./ui/avatar";
8
+ export { default as Spinner } from "./ui/spinner";
9
+ export { default as Modal } from "./ui/modal";
10
+ export { default as Link } from "./ui/link";
11
+ export { default as Tabs } from "./ui/tabs";
12
+ export { default as Dropdown } from "./ui/dropdown";
13
+ export { default as ToastlyProvider } from "./toastly/provider";
14
+ export { toastly } from "./toastly/toastly";
15
+ export { default as Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "./ui/card";
16
+ export { default as Header } from "./layout/header";
17
+ export { default as Navbar } from "./layout/navbar";
18
+ export { default as Sidebar } from "./layout/sidebar";
19
+ export { default as Footer } from "./layout/footer";
20
+ export { default as Container } from "./layout/container";
21
+ export { default as Layout } from "./layout/layout";
22
+ export { default as PageHeader } from "./shared/page-header";
23
+ export { default as EmptyState } from "./shared/empty-state";
24
+ export { default as LoadingState } from "./shared/loading-state";
25
+ export { cn } from "./lib/cn";
26
+ export type { LinkProps } from "./ui/link";
27
+ export type { NavbarItem } from "./layout/navbar";
28
+ export type { TabItem } from "./ui/tabs";
29
+ export type { DropdownEntry } from "./ui/dropdown";
30
+ export type { ToastVariant, ToastEvent, ToastInput, AlertEvent, ConfirmEvent, ToastlyPosition, ToastlyProviderProps } from "./toastly/types";
package/dist/index.js ADDED
@@ -0,0 +1,25 @@
1
+ export { default as Button } from "./ui/button";
2
+ export { default as Input } from "./ui/input";
3
+ export { default as Select } from "./ui/select";
4
+ export { default as Textarea } from "./ui/textarea";
5
+ export { default as Checkbox } from "./ui/checkbox";
6
+ export { default as Badge } from "./ui/badge";
7
+ export { default as Avatar } from "./ui/avatar";
8
+ export { default as Spinner } from "./ui/spinner";
9
+ export { default as Modal } from "./ui/modal";
10
+ export { default as Link } from "./ui/link";
11
+ export { default as Tabs } from "./ui/tabs";
12
+ export { default as Dropdown } from "./ui/dropdown";
13
+ export { default as ToastlyProvider } from "./toastly/provider";
14
+ export { toastly } from "./toastly/toastly";
15
+ export { default as Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "./ui/card";
16
+ export { default as Header } from "./layout/header";
17
+ export { default as Navbar } from "./layout/navbar";
18
+ export { default as Sidebar } from "./layout/sidebar";
19
+ export { default as Footer } from "./layout/footer";
20
+ export { default as Container } from "./layout/container";
21
+ export { default as Layout } from "./layout/layout";
22
+ export { default as PageHeader } from "./shared/page-header";
23
+ export { default as EmptyState } from "./shared/empty-state";
24
+ export { default as LoadingState } from "./shared/loading-state";
25
+ export { cn } from "./lib/cn";
@@ -0,0 +1,5 @@
1
+ import * as React from "react";
2
+ export type ContainerProps = {
3
+ size?: "sm" | "md" | "lg" | "xl" | "full";
4
+ } & React.HTMLAttributes<HTMLDivElement>;
5
+ export default function Container({ size, className, ...props }: ContainerProps): React.JSX.Element;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cn } from "../lib/cn";
3
+ export default function Container({ size = "xl", className = "", ...props }) {
4
+ const sizes = {
5
+ sm: "max-w-3xl",
6
+ md: "max-w-5xl",
7
+ lg: "max-w-6xl",
8
+ xl: "max-w-7xl",
9
+ full: "max-w-none"
10
+ };
11
+ return _jsx("div", { className: cn("mx-auto w-full px-4 sm:px-6 lg:px-8", sizes[size], className), ...props });
12
+ }
@@ -0,0 +1,13 @@
1
+ import * as React from "react";
2
+ export type FooterColumn = {
3
+ title: string;
4
+ content: React.ReactNode;
5
+ };
6
+ export type FooterProps = {
7
+ brand?: React.ReactNode;
8
+ columns?: FooterColumn[];
9
+ bottom?: React.ReactNode;
10
+ bottomRight?: React.ReactNode;
11
+ className?: string;
12
+ };
13
+ export default function Footer({ brand, columns, bottom, bottomRight, className }: FooterProps): React.JSX.Element;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import Container from "./container";
3
+ import { cn } from "../lib/cn";
4
+ export default function Footer({ brand, columns = [], bottom, bottomRight, className = "" }) {
5
+ return (_jsx("footer", { className: cn("mt-auto border-t border-border bg-background text-sm text-foreground-muted", className), children: _jsxs(Container, { className: "py-10", children: [_jsxs("div", { className: "grid grid-cols-1 gap-8 md:grid-cols-4", children: [brand && _jsx("div", { className: "flex flex-col gap-3", children: brand }), columns.map((column) => (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsx("h4", { className: "text-sm font-medium text-foreground", children: column.title }), _jsx("div", { className: "flex flex-col gap-2", children: column.content })] }, column.title)))] }), (bottom || bottomRight) && (_jsxs("div", { className: "mt-10 flex flex-col items-center justify-between gap-3 border-t border-border pt-6 text-xs sm:flex-row", children: [_jsx("div", { children: bottom }), _jsx("div", { className: "flex items-center gap-4", children: bottomRight })] }))] }) }));
6
+ }
@@ -0,0 +1,19 @@
1
+ import * as React from "react";
2
+ type HeaderSegment = {
3
+ label: string;
4
+ href?: string;
5
+ icon?: React.ReactNode;
6
+ badge?: string;
7
+ active?: boolean;
8
+ menu?: React.ReactNode;
9
+ };
10
+ type HeaderProps = {
11
+ logo?: React.ReactNode;
12
+ brandHref?: string;
13
+ segments?: HeaderSegment[];
14
+ actions?: React.ReactNode;
15
+ utilities?: React.ReactNode;
16
+ mobile?: boolean;
17
+ } & React.HTMLAttributes<HTMLElement>;
18
+ export default function Header({ logo, brandHref, segments, actions, utilities, mobile, className, ...props }: HeaderProps): React.JSX.Element;
19
+ export {};
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ export default function Header({ logo, brandHref = "/", segments = [], actions, utilities, mobile = true, className = "", ...props }) {
4
+ return (_jsx("header", { className: `flex h-12 w-full shrink-0 items-center border-b border-border bg-background ${className}`, ...props, children: _jsxs("div", { className: "flex h-full min-w-0 flex-1 items-center justify-between gap-4 overflow-x-auto px-4", children: [_jsxs("div", { className: "flex min-w-0 h-full items-center gap-4", children: [_jsx("a", { href: brandHref, className: "flex shrink-0 items-center justify-center", "aria-label": "Home", children: logo }), segments.length > 0 && (_jsx("nav", { "aria-label": "Breadcrumb", className: "flex h-full min-w-0 items-center gap-2", children: segments.map((segment, index) => (_jsxs(React.Fragment, { children: [_jsx("span", { "aria-hidden": "true", className: "h-4 w-px shrink-0 rotate-27 bg-border-strong" }), _jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [segment.href ? (_jsxs("a", { href: segment.href, "aria-current": segment.active ? "page" : undefined, className: `flex min-w-0 items-center gap-2 text-sm transition-colors hover:text-foreground ${segment.active ? "text-foreground" : "text-foreground-muted"}`, children: [segment.icon, _jsx("span", { className: "max-w-32 truncate lg:max-w-64", children: segment.label })] })) : (_jsxs("span", { className: "flex min-w-0 items-center gap-2 text-sm text-foreground", children: [segment.icon, _jsx("span", { className: "max-w-32 truncate lg:max-w-64", children: segment.label })] })), segment.badge && (_jsx("span", { className: "inline-flex shrink-0 items-center rounded-full border border-border bg-muted px-1.5 py-0.5 text-[9px] font-medium uppercase tracking-wider text-foreground-muted", children: segment.badge })), segment.menu] })] }, `${segment.label}-${index}`))) })), actions && (_jsx("div", { className: "flex shrink-0 items-center gap-2", children: actions }))] }), utilities && (_jsx("div", { className: "flex shrink-0 items-center gap-2", children: utilities }))] }) }));
5
+ }
@@ -0,0 +1,10 @@
1
+ import * as React from "react";
2
+ export type LayoutProps = {
3
+ header?: React.ReactNode;
4
+ sidebar?: React.ReactNode;
5
+ footer?: React.ReactNode;
6
+ stickyHeader?: boolean;
7
+ children?: React.ReactNode;
8
+ className?: string;
9
+ };
10
+ export default function Layout({ header, sidebar, footer, stickyHeader, children, className }: LayoutProps): React.JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../lib/cn";
3
+ export default function Layout({ header, sidebar, footer, stickyHeader = true, children, className = "" }) {
4
+ return (_jsxs("div", { style: { "--sui-sidebar-width": "13rem", "--sui-header-height": "3rem" }, className: cn("flex min-h-svh w-full flex-col bg-background text-foreground", className), children: [_jsx("a", { href: "#main", className: "fixed left-2.5 top-0 z-100 w-fit -translate-y-[calc(100%+8px)] rounded-md bg-popover px-2.5 py-1 text-xs font-medium text-foreground shadow transition-transform duration-200 ease-out focus:translate-y-[10px]", children: "Skip to content" }), header && (_jsx("div", { className: cn("z-40 shrink-0 bg-background", stickyHeader && "sticky top-0"), children: header })), _jsxs("div", { className: "flex min-w-0 flex-1", children: [sidebar && (_jsx("div", { className: "sticky top-(--sui-header-height) hidden h-[calc(100svh-var(--sui-header-height))] shrink-0 md:block", children: sidebar })), _jsx("main", { id: "main", tabIndex: -1, className: "min-w-0 flex-1 outline-none", children: children })] }), footer] }));
5
+ }
@@ -0,0 +1,17 @@
1
+ import * as React from "react";
2
+ export type NavbarItem = {
3
+ label: string;
4
+ href?: string;
5
+ active?: boolean;
6
+ onClick?: () => void;
7
+ };
8
+ export type NavbarProps = {
9
+ logo: React.ReactNode;
10
+ onLogoClick?: () => void;
11
+ brandHref?: string;
12
+ items?: NavbarItem[];
13
+ actions?: React.ReactNode;
14
+ blur?: boolean;
15
+ className?: string;
16
+ };
17
+ export default function Navbar({ logo, onLogoClick, brandHref, items, actions, blur, className }: NavbarProps): React.JSX.Element;
@@ -0,0 +1,19 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import Container from "./container";
5
+ import { cn } from "../lib/cn";
6
+ export default function Navbar({ logo, onLogoClick, brandHref = "/", items = [], actions, blur = true, className = "" }) {
7
+ const [open, setOpen] = React.useState(false);
8
+ const itemClass = (active, mobile) => cn("inline-flex cursor-pointer items-center whitespace-nowrap rounded-md text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", mobile ? "h-10 w-full px-3" : "h-8 px-2.5", active ? "bg-accent font-medium text-foreground" : "text-foreground-lighter hover:bg-accent hover:text-foreground");
9
+ const renderItem = (item, mobile) => item.href ? (_jsx("a", { href: item.href, "aria-current": item.active ? "page" : undefined, onClick: () => setOpen(false), className: itemClass(item.active, mobile), children: item.label }, item.label)) : (_jsx("button", { type: "button", "aria-current": item.active ? "page" : undefined, onClick: () => {
10
+ item.onClick?.();
11
+ setOpen(false);
12
+ }, className: itemClass(item.active, mobile), children: item.label }, item.label));
13
+ return (_jsxs("header", { className: cn("relative w-full border-b border-border", blur ? "bg-background/80 backdrop-blur-md" : "bg-background", className), children: [_jsxs(Container, { className: "flex h-12 items-center justify-between gap-2 sm:gap-6", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-1", children: [items.length > 0 && (_jsx("button", { type: "button", "aria-label": "Menu", "aria-expanded": open, onClick: () => setOpen((v) => !v), className: "-ml-2 inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md text-foreground-lighter hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring md:hidden", children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", className: "size-5", children: open ? _jsx("path", { d: "M18 6 6 18M6 6l12 12" }) : _jsx("path", { d: "M4 7h16M4 12h16M4 17h16" }) }) })), _jsx("a", { href: brandHref, onClick: (event) => {
14
+ if (onLogoClick) {
15
+ event.preventDefault();
16
+ onLogoClick();
17
+ }
18
+ }, className: "flex shrink-0 items-center gap-2 text-sm font-semibold text-foreground", children: logo })] }), items.length > 0 && (_jsx("nav", { "aria-label": "Navigation", className: "hidden min-w-0 items-center gap-1 overflow-x-auto md:flex", children: items.map((item) => renderItem(item)) })), actions && _jsx("div", { className: "flex shrink-0 items-center gap-1", children: actions })] }), open && items.length > 0 && (_jsx("nav", { "aria-label": "Navigation mobile", className: "absolute inset-x-0 top-full flex flex-col gap-1 border-b border-border bg-background p-2 shadow-lg md:hidden", children: items.map((item) => renderItem(item, true)) }))] }));
19
+ }
@@ -0,0 +1,10 @@
1
+ import * as React from "react";
2
+ import { LinkProps } from "../ui/link";
3
+ export type SidebarProps = {
4
+ groups: LinkProps[][];
5
+ pathname?: string;
6
+ linkAs?: React.ElementType;
7
+ footer?: React.ReactNode;
8
+ className?: string;
9
+ };
10
+ export default function Sidebar({ groups, pathname, linkAs, footer, className }: SidebarProps): React.JSX.Element;
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import Link from "../ui/link";
4
+ export default function Sidebar({ groups, pathname = "", linkAs, footer, className = "" }) {
5
+ const isActive = (url) => {
6
+ if (url === "/")
7
+ return pathname === "/";
8
+ return pathname === url || pathname.startsWith(`${url}/`);
9
+ };
10
+ return (_jsx("aside", { className: `hidden h-full w-(--sui-sidebar-width) shrink-0 border-r border-border bg-background text-foreground md:flex ${className}`, children: _jsxs("div", { className: "flex h-full w-full flex-col", children: [_jsx("div", { className: "min-h-0 flex-1 overflow-y-auto py-2", children: _jsx("nav", { "aria-label": "Navigation", children: groups.map((group, index) => (_jsxs(React.Fragment, { children: [_jsx("ul", { className: "flex w-full flex-col gap-1 px-2 py-1", children: group.map((link) => (_jsx("li", { children: _jsx(Link, { ...link, as: linkAs ?? link.as, active: link.active ?? isActive(link.url), variant: "default", size: "md" }) }, link.url))) }), index !== groups.length - 1 && (_jsx("div", { className: "mx-auto my-2 h-px w-[calc(100%-1rem)] bg-border" }))] }, index))) }) }), footer && (_jsx("div", { className: "flex shrink-0 flex-col gap-2 border-t border-border p-2", children: footer }))] }) }));
11
+ }
@@ -0,0 +1 @@
1
+ export declare function cn(...classes: Array<string | false | null | undefined>): string;
package/dist/lib/cn.js ADDED
@@ -0,0 +1,4 @@
1
+ // TODO: remplacer par clsx + tailwind-merge dans les projets qui les utilisent déjà
2
+ export function cn(...classes) {
3
+ return classes.filter(Boolean).join(" ");
4
+ }
@@ -0,0 +1,11 @@
1
+ import * as React from "react";
2
+ type EmptyStateProps = {
3
+ title?: string;
4
+ description?: string;
5
+ icon?: React.ReactNode;
6
+ action?: React.ReactNode;
7
+ variant?: "default" | "bordered" | "minimal";
8
+ size?: "sm" | "md" | "lg";
9
+ } & React.HTMLAttributes<HTMLElement>;
10
+ export default function EmptyState({ title, description, icon, action, variant, size, className, ...props }: EmptyStateProps): React.JSX.Element;
11
+ export {};
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export default function EmptyState({ title, description, icon, action, variant = "default", size = "md", className = "", ...props }) {
3
+ const variants = {
4
+ default: "border border-dashed border-border bg-muted/50",
5
+ bordered: "border border-border bg-card",
6
+ minimal: "border border-transparent bg-transparent"
7
+ };
8
+ const sizes = {
9
+ sm: "px-4 py-6 gap-y-2 [&_svg]:size-8",
10
+ md: "px-4 py-10 gap-y-3 [&_svg]:size-10",
11
+ lg: "px-6 py-14 gap-y-4 [&_svg]:size-12"
12
+ };
13
+ return (_jsxs("aside", { role: "status", className: `flex w-full flex-col items-center rounded-lg text-center ${variants[variant]} ${sizes[size]} ${className}`, ...props, children: [icon && (_jsx("div", { className: "flex items-center justify-center text-foreground-muted [&_svg]:shrink-0", children: icon })), (title || description) && (_jsxs("div", { className: "flex max-w-160 flex-col items-center gap-y-1 text-balance", children: [title && (_jsx("h3", { className: "text-sm font-medium text-foreground", children: title })), description && (_jsx("p", { className: "text-sm leading-relaxed text-foreground-muted", children: description }))] })), action && (_jsx("div", { className: "flex items-center gap-2", children: action }))] }));
14
+ }
@@ -0,0 +1,5 @@
1
+ export type LoadingStateProps = {
2
+ label?: string;
3
+ className?: string;
4
+ };
5
+ export default function LoadingState({ label, className }: LoadingStateProps): import("react").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import Spinner from "../ui/spinner";
3
+ import { cn } from "../lib/cn";
4
+ export default function LoadingState({ label = "Loading...", className = "" }) {
5
+ return (_jsxs("div", { className: cn("flex w-full flex-col items-center justify-center gap-3 py-16 text-sm text-foreground-muted", className), children: [_jsx(Spinner, { size: "lg" }), _jsx("span", { children: label })] }));
6
+ }
@@ -0,0 +1,8 @@
1
+ import * as React from "react";
2
+ export type PageHeaderProps = {
3
+ title: string;
4
+ description?: string;
5
+ actions?: React.ReactNode;
6
+ className?: string;
7
+ };
8
+ export default function PageHeader({ title, description, actions, className }: PageHeaderProps): React.JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../lib/cn";
3
+ export default function PageHeader({ title, description, actions, className = "" }) {
4
+ return (_jsxs("div", { className: cn("flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between", className), children: [_jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [_jsx("h1", { className: "truncate text-xl font-medium text-foreground", children: title }), description && _jsx("p", { className: "text-sm text-foreground-muted", children: description })] }), actions && _jsx("div", { className: "flex shrink-0 items-center gap-2", children: actions })] }));
5
+ }
@@ -0,0 +1,2 @@
1
+ /*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
2
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-amber-500:oklch(76.9% .188 70.08);--color-black:#000;--spacing:.25rem;--container-sm:24rem;--container-lg:32rem;--container-2xl:42rem;--container-3xl:48rem;--container-5xl:64rem;--container-6xl:72rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--font-weight-medium:500;--font-weight-semibold:600;--tracking-wider:.05em;--leading-relaxed:1.625;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--ease-out:cubic-bezier(0, 0, .2, 1);--animate-spin:spin 1s linear infinite;--blur-xs:4px;--blur-md:12px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}body{background-color:var(--sui-background);color:var(--sui-foreground)}}@layer components;@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-x-0{inset-inline:0}.top-\(--sui-header-height\){top:var(--sui-header-height)}.top-0{top:0}.top-1\/2{top:50%}.top-4{top:calc(var(--spacing) * 4)}.top-full{top:100%}.right-0{right:0}.right-3{right:calc(var(--spacing) * 3)}.right-4{right:calc(var(--spacing) * 4)}.bottom-4{bottom:calc(var(--spacing) * 4)}.left-0{left:0}.left-1\/2{left:50%}.left-2\.5{left:calc(var(--spacing) * 2.5)}.left-3{left:calc(var(--spacing) * 3)}.left-4{left:calc(var(--spacing) * 4)}.z-40{z-index:40}.z-50{z-index:50}.z-100{z-index:100}.z-\[80\]{z-index:80}.mx-auto{margin-inline:auto}.my-1{margin-block:var(--spacing)}.my-2{margin-block:calc(var(--spacing) * 2)}.-mt-0\.5{margin-top:calc(var(--spacing) * -.5)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-10{margin-top:calc(var(--spacing) * 10)}.mt-auto{margin-top:auto}.-mr-1{margin-right:calc(var(--spacing) * -1)}.-mr-1\.5{margin-right:calc(var(--spacing) * -1.5)}.-mb-px{margin-bottom:-1px}.-ml-2{margin-left:calc(var(--spacing) * -2)}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.size-3\.5{width:calc(var(--spacing) * 3.5);height:calc(var(--spacing) * 3.5)}.size-4{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.size-5{width:calc(var(--spacing) * 5);height:calc(var(--spacing) * 5)}.size-6{width:calc(var(--spacing) * 6);height:calc(var(--spacing) * 6)}.size-7{width:calc(var(--spacing) * 7);height:calc(var(--spacing) * 7)}.size-8{width:calc(var(--spacing) * 8);height:calc(var(--spacing) * 8)}.size-10{width:calc(var(--spacing) * 10);height:calc(var(--spacing) * 10)}.size-full{width:100%;height:100%}.h-4{height:calc(var(--spacing) * 4)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-10{height:calc(var(--spacing) * 10)}.h-12{height:calc(var(--spacing) * 12)}.h-\[26px\]{height:26px}.h-\[34px\]{height:34px}.h-\[calc\(100svh-var\(--sui-header-height\)\)\]{height:calc(100svh - var(--sui-header-height))}.h-full{height:100%}.h-px{height:1px}.max-h-\[90vh\]{max-height:90vh}.min-h-0{min-height:0}.min-h-svh{min-height:100svh}.w-\(--sui-sidebar-width\){width:var(--sui-sidebar-width)}.w-80{width:calc(var(--spacing) * 80)}.w-\[calc\(100\%-1rem\)\]{width:calc(100% - 1rem)}.w-fit{width:fit-content}.w-full{width:100%}.w-px{width:1px}.max-w-2xl{max-width:var(--container-2xl)}.max-w-3xl{max-width:var(--container-3xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-6xl{max-width:var(--container-6xl)}.max-w-7xl{max-width:var(--container-7xl)}.max-w-32{max-width:calc(var(--spacing) * 32)}.max-w-160{max-width:calc(var(--spacing) * 160)}.max-w-\[calc\(100vw-2rem\)\]{max-width:calc(100vw - 2rem)}.max-w-lg{max-width:var(--container-lg)}.max-w-none{max-width:none}.max-w-sm{max-width:var(--container-sm)}.min-w-0{min-width:0}.min-w-44{min-width:calc(var(--spacing) * 44)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.origin-top-left{transform-origin:0 0}.origin-top-right{transform-origin:100% 0}.-translate-x-1\/2{--tw-translate-x:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-\[calc\(100\%\+8px\)\]{--tw-translate-y:calc(calc(100% + 8px) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.rotate-27{rotate:27deg}.animate-spin{animation:var(--animate-spin)}.cursor-pointer{cursor:pointer}.resize-y{resize:vertical}.appearance-none{appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-8{gap:calc(var(--spacing) * 8)}.gap-y-1{row-gap:var(--spacing)}.gap-y-2{row-gap:calc(var(--spacing) * 2)}.gap-y-3{row-gap:calc(var(--spacing) * 3)}.gap-y-4{row-gap:calc(var(--spacing) * 4)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-amber-500\/40{border-color:#f99c0066}@supports (color:color-mix(in lab, red, red)){.border-amber-500\/40{border-color:color-mix(in oklab, var(--color-amber-500) 40%, transparent)}}.border-border{border-color:var(--sui-border)}.border-border-strong{border-color:var(--sui-border-strong)}.border-control{border-color:var(--sui-control)}.border-current{border-color:currentColor}.border-destructive\/30{border-color:var(--sui-destructive)}@supports (color:color-mix(in lab, red, red)){.border-destructive\/30{border-color:color-mix(in oklab, var(--sui-destructive) 30%, transparent)}}.border-destructive\/40{border-color:var(--sui-destructive)}@supports (color:color-mix(in lab, red, red)){.border-destructive\/40{border-color:color-mix(in oklab, var(--sui-destructive) 40%, transparent)}}.border-foreground-muted{border-color:var(--sui-foreground-muted)}.border-primary,.border-primary\/30{border-color:var(--sui-primary)}@supports (color:color-mix(in lab, red, red)){.border-primary\/30{border-color:color-mix(in oklab, var(--sui-primary) 30%, transparent)}}.border-primary\/40{border-color:var(--sui-primary)}@supports (color:color-mix(in lab, red, red)){.border-primary\/40{border-color:color-mix(in oklab, var(--sui-primary) 40%, transparent)}}.border-transparent{border-color:#0000}.border-r-transparent{border-right-color:#0000}.bg-accent{background-color:var(--sui-accent)}.bg-background,.bg-background\/80{background-color:var(--sui-background)}@supports (color:color-mix(in lab, red, red)){.bg-background\/80{background-color:color-mix(in oklab, var(--sui-background) 80%, transparent)}}.bg-black\/60{background-color:#0009}@supports (color:color-mix(in lab, red, red)){.bg-black\/60{background-color:color-mix(in oklab, var(--color-black) 60%, transparent)}}.bg-border{background-color:var(--sui-border)}.bg-border-strong{background-color:var(--sui-border-strong)}.bg-card{background-color:var(--sui-card)}.bg-destructive,.bg-destructive\/10{background-color:var(--sui-destructive)}@supports (color:color-mix(in lab, red, red)){.bg-destructive\/10{background-color:color-mix(in oklab, var(--sui-destructive) 10%, transparent)}}.bg-field{background-color:var(--sui-field)}.bg-muted,.bg-muted\/50{background-color:var(--sui-muted)}@supports (color:color-mix(in lab, red, red)){.bg-muted\/50{background-color:color-mix(in oklab, var(--sui-muted) 50%, transparent)}}.bg-popover{background-color:var(--sui-popover)}.bg-primary,.bg-primary\/10{background-color:var(--sui-primary)}@supports (color:color-mix(in lab, red, red)){.bg-primary\/10{background-color:color-mix(in oklab, var(--sui-primary) 10%, transparent)}}.bg-secondary{background-color:var(--sui-secondary)}.bg-transparent{background-color:#0000}.object-cover{object-fit:cover}.p-1{padding:var(--spacing)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-3\.5{padding-inline:calc(var(--spacing) * 3.5)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-10{padding-block:calc(var(--spacing) * 10)}.py-14{padding-block:calc(var(--spacing) * 14)}.py-16{padding-block:calc(var(--spacing) * 16)}.pt-6{padding-top:calc(var(--spacing) * 6)}.pt-px{padding-top:1px}.pr-8{padding-right:calc(var(--spacing) * 8)}.pr-9{padding-right:calc(var(--spacing) * 9)}.pr-10{padding-right:calc(var(--spacing) * 10)}.pl-2\.5{padding-left:calc(var(--spacing) * 2.5)}.pl-3{padding-left:calc(var(--spacing) * 3)}.pl-3\.5{padding-left:calc(var(--spacing) * 3.5)}.pl-9{padding-left:calc(var(--spacing) * 9)}.text-center{text-align:center}.text-left{text-align:left}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[9px\]{font-size:9px}.text-\[10px\]{font-size:10px}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.text-balance{text-wrap:balance}.break-words{overflow-wrap:break-word}.whitespace-nowrap{white-space:nowrap}.text-amber-500{color:var(--color-amber-500)}.text-card-foreground{color:var(--sui-card-foreground)}.text-destructive{color:var(--sui-destructive)}.text-destructive-foreground{color:var(--sui-destructive-foreground)}.text-foreground{color:var(--sui-foreground)}.text-foreground-light{color:var(--sui-foreground-light)}.text-foreground-lighter{color:var(--sui-foreground-lighter)}.text-foreground-muted{color:var(--sui-foreground-muted)}.text-muted-foreground{color:var(--sui-muted-foreground)}.text-primary{color:var(--sui-primary)}.text-primary-foreground{color:var(--sui-primary-foreground)}.text-secondary-foreground{color:var(--sui-secondary-foreground)}.uppercase{text-transform:uppercase}.accent-primary{accent-color:var(--sui-primary)}.opacity-50{opacity:.5}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-\[var\(--sui-button-shadow-raised\)\]{--tw-shadow:var(--sui-button-shadow-raised);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-blur-md{--tw-backdrop-blur:blur(var(--blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-xs{--tw-backdrop-blur:blur(var(--blur-xs));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition-\[background-color\,border-color\,color\,transform\]{transition-property:background-color,border-color,color,transform;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.ease-\[cubic-bezier\(0\.22\,1\,0\.36\,1\)\]{--tw-ease:cubic-bezier(.22,1,.36,1);transition-timing-function:cubic-bezier(.22,1,.36,1)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.outline-none{--tw-outline-style:none;outline-style:none}.file\:border-0::file-selector-button{border-style:var(--tw-border-style);border-width:0}.file\:bg-transparent::file-selector-button{background-color:#0000}.file\:text-sm::file-selector-button{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.file\:font-medium::file-selector-button{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.placeholder\:text-foreground-muted::placeholder{color:var(--sui-foreground-muted)}.read-only\:border-button:read-only{border-color:var(--sui-button)}.read-only\:text-foreground-light:read-only{color:var(--sui-foreground-light)}@media (hover:hover){.hover\:border-border-strong:hover{border-color:var(--sui-border-strong)}.hover\:border-control-hover:hover{border-color:var(--sui-control-hover)}.hover\:bg-accent:hover{background-color:var(--sui-accent)}.hover\:bg-destructive\/90:hover{background-color:var(--sui-destructive)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-destructive\/90:hover{background-color:color-mix(in oklab, var(--sui-destructive) 90%, transparent)}}.hover\:bg-primary\/10:hover{background-color:var(--sui-primary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-primary\/10:hover{background-color:color-mix(in oklab, var(--sui-primary) 10%, transparent)}}.hover\:bg-primary\/90:hover{background-color:var(--sui-primary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-primary\/90:hover{background-color:color-mix(in oklab, var(--sui-primary) 90%, transparent)}}.hover\:bg-secondary:hover{background-color:var(--sui-secondary)}.hover\:text-foreground:hover{color:var(--sui-foreground)}}.focus\:translate-y-\[10px\]:focus{--tw-translate-y:10px;translate:var(--tw-translate-x) var(--tw-translate-y)}.focus\:border-control-hover:focus{border-color:var(--sui-control-hover)}.focus\:bg-accent:focus{background-color:var(--sui-accent)}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:ring-ring:focus-visible{--tw-ring-color:var(--sui-ring)}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus-visible\:ring-offset-background:focus-visible{--tw-ring-offset-color:var(--sui-background)}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.active\:bg-accent:active{background-color:var(--sui-accent)}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:text-foreground-muted:disabled{color:var(--sui-foreground-muted)}.disabled\:opacity-50:disabled{opacity:.5}.aria-invalid\:border-destructive[aria-invalid=true]{border-color:var(--sui-destructive)}.aria-invalid\:focus-visible\:ring-destructive[aria-invalid=true]:focus-visible{--tw-ring-color:var(--sui-destructive)}@media (prefers-reduced-motion:no-preference){.motion-safe\:active\:scale-\[0\.97\]:active{scale:.97}}@media (min-width:40rem){.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}.sm\:justify-between{justify-content:space-between}.sm\:gap-6{gap:calc(var(--spacing) * 6)}.sm\:px-6{padding-inline:calc(var(--spacing) * 6)}}@media (min-width:48rem){.md\:block{display:block}.md\:flex{display:flex}.md\:hidden{display:none}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media (min-width:64rem){.lg\:max-w-64{max-width:calc(var(--spacing) * 64)}.lg\:px-8{padding-inline:calc(var(--spacing) * 8)}}.\[\&_svg\]\:size-3 svg{width:calc(var(--spacing) * 3);height:calc(var(--spacing) * 3)}.\[\&_svg\]\:size-3\.5 svg{width:calc(var(--spacing) * 3.5);height:calc(var(--spacing) * 3.5)}.\[\&_svg\]\:size-4 svg{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.\[\&_svg\]\:size-5 svg{width:calc(var(--spacing) * 5);height:calc(var(--spacing) * 5)}.\[\&_svg\]\:size-8 svg{width:calc(var(--spacing) * 8);height:calc(var(--spacing) * 8)}.\[\&_svg\]\:size-10 svg{width:calc(var(--spacing) * 10);height:calc(var(--spacing) * 10)}.\[\&_svg\]\:size-12 svg{width:calc(var(--spacing) * 12);height:calc(var(--spacing) * 12)}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\&\>span\:last-child\]\:truncate>span:last-child{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.\[\&\>svg\]\:size-4>svg{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.\[\&\>svg\]\:size-5>svg{width:calc(var(--spacing) * 5);height:calc(var(--spacing) * 5)}.\[\&\>svg\]\:shrink-0>svg{flex-shrink:0}}:root{color-scheme:light;--sui-background:#fff;--sui-foreground:#171717;--sui-card:#fff;--sui-card-foreground:#171717;--sui-primary:#3ecf8e;--sui-primary-foreground:#032b1c;--sui-secondary:#f1f5f3;--sui-secondary-foreground:#333d38;--sui-muted:#f5f5f5;--sui-muted-foreground:#737373;--sui-accent:#f0f2f0;--sui-accent-foreground:#171717;--sui-border:#e5e7eb;--sui-field:#fff;--sui-control:#d4d4d4;--sui-control-hover:#a3a3a3;--sui-button:#e5e5e5;--sui-foreground-muted:#737373;--sui-foreground-light:#525252;--sui-input:#e5e7eb;--sui-ring:#3ecf8e;--sui-destructive:#dc2626;--sui-destructive-foreground:#fff;--sui-popover:#fff;--sui-border-strong:#d4d4d4;--sui-foreground-lighter:#525252;--sui-button-shadow-raised:0 1px 2px #0000001a, inset 0 1px 0 #ffffff26}.dark{color-scheme:dark;--sui-field:#1c1c1c;--sui-control:#383838;--sui-control-hover:#525252;--sui-button:#404040;--sui-foreground-muted:#858585;--sui-foreground-light:#b0b0b0;--sui-background:#171717;--sui-foreground:#ededed;--sui-card:#1c1c1c;--sui-card-foreground:#ededed;--sui-primary:#3ecf8e;--sui-primary-foreground:#032b1c;--sui-secondary:#292929;--sui-secondary-foreground:#ededed;--sui-muted:#262626;--sui-muted-foreground:#a3a3a3;--sui-accent:#292929;--sui-accent-foreground:#ededed;--sui-border:#333;--sui-input:#333;--sui-ring:#3ecf8e;--sui-destructive:#ef4444;--sui-destructive-foreground:#fff;--sui-popover:#1c1c1c;--sui-border-strong:#454545;--sui-foreground-lighter:#b0b0b0;--sui-button-shadow-raised:0 1px 2px #0003, inset 0 1px 0 #ffffff14}@keyframes sui-toast-in{0%{opacity:0;transform:translateY(12px)scale(.97)}to{opacity:1;transform:translateY(0)scale(1)}}@keyframes sui-pop-in{0%{opacity:0;transform:scale(.96)}to{opacity:1;transform:scale(1)}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}
@@ -0,0 +1,3 @@
1
+ import * as React from "react";
2
+ import type { ToastlyProviderProps } from "./types";
3
+ export default function ToastlyProvider({ children, position, duration, maxVisible }: ToastlyProviderProps): React.JSX.Element;
@@ -0,0 +1,111 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import Modal from "../ui/modal";
5
+ import Button from "../ui/button";
6
+ import Spinner from "../ui/spinner";
7
+ import { cn } from "../lib/cn";
8
+ import { TOASTLY_EVENTS } from "./toastly";
9
+ const ALIASES = {
10
+ tl: "top-left", tc: "top-center", tr: "top-right",
11
+ lc: "left-center", c: "center", rc: "right-center",
12
+ bl: "bottom-left", bc: "bottom-center", br: "bottom-right"
13
+ };
14
+ const POSITION_CLASSES = {
15
+ "top-left": "top-4 left-4 items-start",
16
+ "top-center": "top-4 left-1/2 -translate-x-1/2 items-center",
17
+ "top-right": "top-4 right-4 items-end",
18
+ "left-center": "top-1/2 left-4 -translate-y-1/2 items-start",
19
+ "center": "top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 items-center",
20
+ "right-center": "top-1/2 right-4 -translate-y-1/2 items-end",
21
+ "bottom-left": "bottom-4 left-4 items-start",
22
+ "bottom-center": "bottom-4 left-1/2 -translate-x-1/2 items-center",
23
+ "bottom-right": "bottom-4 right-4 items-end"
24
+ };
25
+ const VARIANT_STYLES = {
26
+ info: { border: "border-border", icon: "text-foreground-muted" },
27
+ success: { border: "border-primary/40", icon: "text-primary" },
28
+ warning: { border: "border-amber-500/40", icon: "text-amber-500" },
29
+ error: { border: "border-destructive/40", icon: "text-destructive" }
30
+ };
31
+ const ICON_PATHS = {
32
+ info: _jsxs(_Fragment, { children: [_jsx("circle", { cx: "12", cy: "12", r: "9" }), _jsx("path", { d: "M12 11v5M12 8h.01" })] }),
33
+ success: _jsxs(_Fragment, { children: [_jsx("circle", { cx: "12", cy: "12", r: "9" }), _jsx("path", { d: "m8.5 12.5 2.5 2.5 4.5-5" })] }),
34
+ warning: _jsxs(_Fragment, { children: [_jsx("path", { d: "M10.3 4.2 2.5 18a2 2 0 0 0 1.7 3h15.6a2 2 0 0 0 1.7-3L13.7 4.2a2 2 0 0 0-3.4 0Z" }), _jsx("path", { d: "M12 9.5v4M12 17h.01" })] }),
35
+ error: _jsxs(_Fragment, { children: [_jsx("circle", { cx: "12", cy: "12", r: "9" }), _jsx("path", { d: "m9 9 6 6M15 9l-6 6" })] })
36
+ };
37
+ function ToastCard({ toast, onDismiss, className, style }) {
38
+ const variant = toast.variant ?? "info";
39
+ const styles = VARIANT_STYLES[variant];
40
+ return (_jsxs("div", { role: variant === "error" ? "alert" : "status", style: { animation: "sui-toast-in 220ms cubic-bezier(0.22,1,0.36,1)", ...style }, className: cn("pointer-events-auto flex w-80 max-w-[calc(100vw-2rem)] items-start gap-3 rounded-lg border bg-popover p-3 text-sm text-foreground shadow-xl", styles.border, className), children: [_jsx("span", { className: cn("mt-0.5 flex size-5 shrink-0 items-center justify-center", styles.icon), children: toast.loading ? (_jsx(Spinner, { size: "sm" })) : (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", className: "size-5", children: ICON_PATHS[variant] })) }), _jsx("div", { className: "min-w-0 flex-1 break-words pt-px", children: toast.message }), toast.allowDismiss !== false && (_jsx("button", { type: "button", "aria-label": "Dismiss", onClick: () => onDismiss(toast.id), className: "-mr-1 -mt-0.5 inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md text-foreground-muted transition-colors hover:bg-accent hover:text-foreground", children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", className: "size-3.5", children: _jsx("path", { d: "M18 6 6 18M6 6l12 12" }) }) }))] }));
41
+ }
42
+ export default function ToastlyProvider({ children, position = "bottom-right", duration = 4000, maxVisible = 5 }) {
43
+ const [toasts, setToasts] = React.useState([]);
44
+ const [alertState, setAlertState] = React.useState(null);
45
+ const [confirmState, setConfirmState] = React.useState(null);
46
+ const ids = React.useRef(new Set());
47
+ const timers = React.useRef(new Map());
48
+ const durationRef = React.useRef(Number(duration));
49
+ durationRef.current = Number(duration) || 4000;
50
+ const dismiss = React.useCallback((id) => {
51
+ ids.current.delete(id);
52
+ const timer = timers.current.get(id);
53
+ if (timer !== undefined) {
54
+ window.clearTimeout(timer);
55
+ timers.current.delete(id);
56
+ }
57
+ setToasts((current) => current.filter((toast) => toast.id !== id));
58
+ }, []);
59
+ React.useEffect(() => {
60
+ const onAdd = (event) => {
61
+ const detail = event.detail;
62
+ if (!detail?.id || ids.current.has(detail.id))
63
+ return;
64
+ ids.current.add(detail.id);
65
+ setToasts((current) => [detail, ...current]);
66
+ const auto = detail.autoDismiss ?? !detail.loading;
67
+ const delay = detail.autoDismissIn ?? (auto ? durationRef.current : undefined);
68
+ if (delay !== undefined && Number.isFinite(delay) && delay >= 0) {
69
+ timers.current.set(detail.id, window.setTimeout(() => dismiss(detail.id), delay));
70
+ }
71
+ };
72
+ const onDismiss = (event) => dismiss(event.detail.id);
73
+ const onAlert = (event) => setAlertState(event.detail);
74
+ const onConfirm = (event) => setConfirmState(event.detail);
75
+ window.addEventListener(TOASTLY_EVENTS.add, onAdd);
76
+ window.addEventListener(TOASTLY_EVENTS.dismiss, onDismiss);
77
+ window.addEventListener(TOASTLY_EVENTS.alert, onAlert);
78
+ window.addEventListener(TOASTLY_EVENTS.confirm, onConfirm);
79
+ const activeTimers = timers.current;
80
+ const activeIds = ids.current;
81
+ return () => {
82
+ window.removeEventListener(TOASTLY_EVENTS.add, onAdd);
83
+ window.removeEventListener(TOASTLY_EVENTS.dismiss, onDismiss);
84
+ window.removeEventListener(TOASTLY_EVENTS.alert, onAlert);
85
+ window.removeEventListener(TOASTLY_EVENTS.confirm, onConfirm);
86
+ activeTimers.forEach((timer) => window.clearTimeout(timer));
87
+ activeTimers.clear();
88
+ activeIds.clear();
89
+ };
90
+ }, [dismiss]);
91
+ const closeAlert = () => {
92
+ const current = alertState;
93
+ setAlertState(null);
94
+ current?.onClose?.();
95
+ };
96
+ const closeConfirm = (confirmed) => {
97
+ const current = confirmState;
98
+ setConfirmState(null);
99
+ if (!current)
100
+ return;
101
+ if (confirmed)
102
+ current.onConfirm?.();
103
+ else
104
+ current.onCancel?.();
105
+ current.onClose?.();
106
+ };
107
+ const canonical = ALIASES[position] ?? position;
108
+ const stacked = toasts.filter((toast) => !toast.position).slice(0, maxVisible);
109
+ const placed = toasts.filter((toast) => toast.position);
110
+ return (_jsxs(_Fragment, { children: [children, _jsx("div", { "aria-label": "Notifications", className: cn("pointer-events-none fixed z-[80] flex flex-col gap-2", POSITION_CLASSES[canonical] ?? POSITION_CLASSES["bottom-right"]), children: stacked.map((toast) => (_jsx(ToastCard, { toast: toast, onDismiss: dismiss }, toast.id))) }), placed.map((toast) => (_jsx(ToastCard, { toast: toast, onDismiss: dismiss, className: "fixed z-[80]", style: { left: Number(toast.position?.[0] ?? 0), top: Number(toast.position?.[1] ?? 0) } }, toast.id))), _jsx(Modal, { open: alertState !== null, onClose: closeAlert, title: alertState?.title, size: "sm", footer: _jsx(Button, { size: "sm", onClick: closeAlert, children: alertState?.okLabel ?? "OK" }), children: _jsx("div", { className: "text-sm text-foreground-lighter", children: alertState?.message }) }), _jsx(Modal, { open: confirmState !== null, onClose: () => closeConfirm(false), title: confirmState?.title ?? "Confirm", size: "sm", footer: _jsxs(_Fragment, { children: [_jsx(Button, { size: "sm", variant: "outline", onClick: () => closeConfirm(false), children: confirmState?.cancelLabel ?? "Cancel" }), _jsx(Button, { size: "sm", variant: confirmState?.danger ? "danger" : "primary", onClick: () => closeConfirm(true), children: confirmState?.confirmLabel ?? "Confirm" })] }), children: _jsx("div", { className: "text-sm text-foreground-lighter", children: confirmState?.message }) })] }));
111
+ }
@@ -0,0 +1,28 @@
1
+ import type * as React from "react";
2
+ import type { AlertEvent, ConfirmEvent, ToastInput } from "./types";
3
+ export declare const TOASTLY_EVENTS: {
4
+ readonly add: "toastly:add-toast";
5
+ readonly dismiss: "toastly:dismiss-alert";
6
+ readonly alert: "toastly:alert";
7
+ readonly confirm: "toastly:confirm";
8
+ };
9
+ type PromiseMessages<T> = {
10
+ loading: React.ReactNode;
11
+ success: React.ReactNode | ((value: T) => React.ReactNode);
12
+ error: React.ReactNode | ((error: unknown) => React.ReactNode);
13
+ };
14
+ export declare const toastly: {
15
+ add: (input: ToastInput) => string;
16
+ info: (input: ToastInput) => string;
17
+ success: (input: ToastInput) => string;
18
+ warning: (input: ToastInput) => string;
19
+ error: (input: ToastInput) => string;
20
+ dismiss: (id: string) => void;
21
+ promise<T>(promise: Promise<T>, messages: PromiseMessages<T>): Promise<T>;
22
+ alert(event: AlertEvent): Promise<void>;
23
+ confirm(event: ConfirmEvent): Promise<boolean>;
24
+ to(x: number | `${number}`, y: number | `${number}`): {
25
+ add: (input: ToastInput) => string;
26
+ };
27
+ };
28
+ export {};