@apotech-os/ui-sdk 0.9.0 → 0.10.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/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { ClassValue } from 'clsx';
2
2
  import * as React from 'react';
3
+ import { ReactNode } from 'react';
3
4
  import { AppErrorCode, AppRunRecord, AppRunStatus } from '@apotech-os/app-kit';
4
5
  export { AppRunRecord, AppRunStatus } from '@apotech-os/app-kit';
5
6
  import * as class_variance_authority_types from 'class-variance-authority/types';
@@ -159,7 +160,9 @@ declare function Badge({ className, variant, dot, children, ...props }: BadgePro
159
160
  declare function Card({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): React.JSX.Element;
160
161
  declare function CardHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): React.JSX.Element;
161
162
  declare function CardTitle({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>): React.JSX.Element;
163
+ declare function CardDescription({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>): React.JSX.Element;
162
164
  declare function CardContent({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): React.JSX.Element;
165
+ declare function CardFooter({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): React.JSX.Element;
163
166
  declare const Input: React.ForwardRefExoticComponent<React.InputHTMLAttributes<HTMLInputElement> & React.RefAttributes<HTMLInputElement>>;
164
167
  declare function Table({ className, ...props }: React.HTMLAttributes<HTMLTableElement>): React.JSX.Element;
165
168
  declare function TableHeader({ className, ...props }: React.HTMLAttributes<HTMLTableSectionElement>): React.JSX.Element;
@@ -191,7 +194,7 @@ declare function TabsContent({ value, className, children, }: {
191
194
  value: string;
192
195
  className?: string;
193
196
  children: React.ReactNode;
194
- }): React.JSX.Element | null;
197
+ }): React.JSX.Element;
195
198
  interface ModalProps {
196
199
  open: boolean;
197
200
  onClose: () => void;
@@ -199,7 +202,8 @@ interface ModalProps {
199
202
  children: React.ReactNode;
200
203
  className?: string;
201
204
  }
202
- declare function Modal({ open, onClose, title, children, className }: ModalProps): React.JSX.Element | null;
205
+ /** Centred blocking dialog. Base UI owns the focus trap, focus restore and Escape. */
206
+ declare function Modal({ open, onClose, title, children, className }: ModalProps): React.JSX.Element;
203
207
  interface SpinnerProps {
204
208
  className?: string;
205
209
  size?: 'sm' | 'md' | 'lg';
@@ -209,6 +213,10 @@ interface LinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
209
213
  variant?: 'default' | 'muted';
210
214
  }
211
215
  declare const Link: React.ForwardRefExoticComponent<LinkProps & React.RefAttributes<HTMLAnchorElement>>;
216
+ /**
217
+ * Do not pass `aria-label` when the switch sits inside a `<label>`: the label
218
+ * already names it, and the two naming sources cancel out into no name at all.
219
+ */
212
220
  interface SwitchProps {
213
221
  checked?: boolean;
214
222
  onCheckedChange?: (checked: boolean) => void;
@@ -254,9 +262,10 @@ interface SidePanelProps {
254
262
  * Children are laid out in a flex column filling the panel: put the scrollable
255
263
  * region in a `min-h-0 flex-1 overflow-y-auto` child and the footer after it.
256
264
  *
257
- * NOTE: no focus management. Autofocus the primary action yourself.
265
+ * Focus is trapped inside while open and restored to the opener on close — Base
266
+ * UI's dialog does both, which is what the hand-rolled version never did.
258
267
  */
259
- declare function SidePanel({ open, onClose, title, description, children, className, }: SidePanelProps): React.JSX.Element | null;
268
+ declare function SidePanel({ open, onClose, title, description, children, className, }: SidePanelProps): React.JSX.Element;
260
269
  interface CopyButtonProps {
261
270
  value: string;
262
271
  label?: string;
@@ -294,9 +303,45 @@ interface DropdownMenuProps {
294
303
  align?: 'start' | 'end';
295
304
  className?: string;
296
305
  }
297
- /** Uncontrolled menu built on Popover. Selecting an item closes it. */
306
+ /** Uncontrolled menu. Selecting an item closes it; arrow keys move between them. */
298
307
  declare function DropdownMenu({ trigger, items, align, className }: DropdownMenuProps): React.JSX.Element;
299
308
 
309
+ interface ToastInput {
310
+ title: string;
311
+ description?: string;
312
+ /** `info` exists because Pilotage's own stack had it — a neutral outcome is not a success. */
313
+ variant?: 'success' | 'error' | 'info';
314
+ }
315
+ /** Returns a `toast({ title, description?, variant? })` function. */
316
+ declare function useToast(): (toast: ToastInput) => void;
317
+ /** Fixed bottom-right stack, auto-dismissing. */
318
+ declare function ToastProvider({ children }: {
319
+ children: ReactNode;
320
+ }): React.JSX.Element;
321
+
322
+ /**
323
+ * Hover tooltip, lifted from the shell.
324
+ *
325
+ * Deliberately CSS-only (`group-hover`) rather than another Base UI surface: it
326
+ * carries no focus, no dismissal and no state, so a portal and a positioner would
327
+ * be weight for nothing. If a tooltip ever needs to be keyboard-reachable or to
328
+ * survive a scroll container, that is the moment to move it onto Base UI's.
329
+ */
330
+
331
+ declare const SIDES: {
332
+ readonly top: "bottom-full left-1/2 -translate-x-1/2 mb-1.5";
333
+ readonly right: "left-full top-1/2 -translate-y-1/2 ml-1.5";
334
+ readonly bottom: "top-full left-1/2 -translate-x-1/2 mt-1.5";
335
+ readonly left: "right-full top-1/2 -translate-y-1/2 mr-1.5";
336
+ };
337
+ interface TooltipProps {
338
+ label: React.ReactNode;
339
+ children: React.ReactNode;
340
+ side?: keyof typeof SIDES;
341
+ className?: string;
342
+ }
343
+ declare function Tooltip({ label, children, side, className }: TooltipProps): React.JSX.Element;
344
+
300
345
  /**
301
346
  * Browser helpers for app pages.
302
347
  *
@@ -326,4 +371,4 @@ declare function toCsv(rows: string[][]): string;
326
371
  */
327
372
  declare function downloadCsv(rows: string[][], filename: string): void;
328
373
 
329
- export { type AppApiClient, AppApiProvider, AppContextProvider, type AppContextValue, AppInvokeError, Badge, type BadgeProps, Button, type ButtonProps, Card, CardContent, CardHeader, CardTitle, Checkbox, type CheckboxProps, CopyButton, type CopyButtonProps, type DateRange, DateRangePicker, type DateRangePickerProps, DropdownMenu, type DropdownMenuItem, type DropdownMenuProps, Input, Link, type LinkProps, Modal, type ModalProps, Popover, type PopoverProps, Select, type SelectProps, SidePanel, type SidePanelProps, Spinner, type SpinnerProps, Switch, type SwitchProps, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, type TabsProps, TabsTrigger, Textarea, type TextareaProps, cn, copyToClipboard, downloadBlob, downloadCsv, toCsv, useAppApi, useAppContext, useAppRun, useAppRuns, useAppSettings };
374
+ export { type AppApiClient, AppApiProvider, AppContextProvider, type AppContextValue, AppInvokeError, Badge, type BadgeProps, Button, type ButtonProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, type CheckboxProps, CopyButton, type CopyButtonProps, type DateRange, DateRangePicker, type DateRangePickerProps, DropdownMenu, type DropdownMenuItem, type DropdownMenuProps, Input, Link, type LinkProps, Modal, type ModalProps, Popover, type PopoverProps, Select, type SelectProps, SidePanel, type SidePanelProps, Spinner, type SpinnerProps, Switch, type SwitchProps, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, type TabsProps, TabsTrigger, Textarea, type TextareaProps, type ToastInput, ToastProvider, Tooltip, type TooltipProps, badgeVariants, buttonVariants, cn, copyToClipboard, downloadBlob, downloadCsv, toCsv, useAppApi, useAppContext, useAppRun, useAppRuns, useAppSettings, useToast };
package/dist/index.js CHANGED
@@ -2,6 +2,11 @@ import { clsx } from 'clsx';
2
2
  import { twMerge } from 'tailwind-merge';
3
3
  import * as React from 'react';
4
4
  import { createContext, useContext, useState, useRef, useCallback, useEffect } from 'react';
5
+ import { Dialog } from '@base-ui/react/dialog';
6
+ import { Menu } from '@base-ui/react/menu';
7
+ import { Popover as Popover$1 } from '@base-ui/react/popover';
8
+ import { Switch as Switch$1 } from '@base-ui/react/switch';
9
+ import { Tabs as Tabs$1 } from '@base-ui/react/tabs';
5
10
  import { cva } from 'class-variance-authority';
6
11
  import { jsx, jsxs } from 'react/jsx-runtime';
7
12
 
@@ -289,6 +294,12 @@ function downloadCsv(rows, filename) {
289
294
  const blob = new Blob([`${BOM}${toCsv(rows)}`], { type: "text/csv;charset=utf-8" });
290
295
  downloadBlob(blob, filename);
291
296
  }
297
+ function triggerProps(trigger) {
298
+ if (React.isValidElement(trigger)) {
299
+ return { render: trigger, nativeButton: trigger.type === "button" || trigger.type === Button };
300
+ }
301
+ return { render: /* @__PURE__ */ jsx("span", { children: trigger }), nativeButton: false };
302
+ }
292
303
  var buttonVariants = cva(
293
304
  "inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
294
305
  {
@@ -352,9 +363,18 @@ function CardHeader({ className, ...props }) {
352
363
  function CardTitle({ className, ...props }) {
353
364
  return /* @__PURE__ */ jsx("h3", { className: cn("text-lg font-semibold leading-none tracking-tight", className), ...props });
354
365
  }
366
+ function CardDescription({
367
+ className,
368
+ ...props
369
+ }) {
370
+ return /* @__PURE__ */ jsx("p", { className: cn("text-sm text-muted-foreground", className), ...props });
371
+ }
355
372
  function CardContent({ className, ...props }) {
356
373
  return /* @__PURE__ */ jsx("div", { className: cn("p-6 pt-0", className), ...props });
357
374
  }
375
+ function CardFooter({ className, ...props }) {
376
+ return /* @__PURE__ */ jsx("div", { className: cn("flex items-center p-6 pt-0", className), ...props });
377
+ }
358
378
  var Input = React.forwardRef(({ className, type, ...props }, ref) => /* @__PURE__ */ jsx(
359
379
  "input",
360
380
  {
@@ -375,7 +395,13 @@ function TableHeader({
375
395
  className,
376
396
  ...props
377
397
  }) {
378
- return /* @__PURE__ */ jsx("thead", { className: cn("[&_tr]:border-b", className), ...props });
398
+ return /* @__PURE__ */ jsx(
399
+ "thead",
400
+ {
401
+ className: cn("[&_tr]:border-b [&_tr]:hover:bg-transparent", className),
402
+ ...props
403
+ }
404
+ );
379
405
  }
380
406
  function TableBody({ className, ...props }) {
381
407
  return /* @__PURE__ */ jsx("tbody", { className: cn("[&_tr:last-child]:border-0", className), ...props });
@@ -397,7 +423,7 @@ function TableHead({ className, ...props }) {
397
423
  "th",
398
424
  {
399
425
  className: cn(
400
- "h-10 px-4 text-left align-middle text-xs font-medium text-muted-foreground",
426
+ "h-10 px-4 text-left align-middle text-xs font-medium uppercase tracking-wide text-muted-foreground",
401
427
  className
402
428
  ),
403
429
  ...props
@@ -422,42 +448,43 @@ var Select = React.forwardRef(
422
448
  )
423
449
  );
424
450
  Select.displayName = "Select";
425
- var TabsContext = React.createContext(null);
426
- function useTabsContext() {
427
- const ctx = React.useContext(TabsContext);
428
- if (!ctx) throw new Error("Tabs sub-component must be used within <Tabs>");
429
- return ctx;
430
- }
431
451
  function Tabs({ defaultValue, value, onValueChange, className, children }) {
432
- const [internal, setInternal] = React.useState(defaultValue ?? "");
433
- const current = value ?? internal;
434
- const setValue = (v) => {
435
- setInternal(v);
436
- onValueChange?.(v);
437
- };
438
- return /* @__PURE__ */ jsx(TabsContext.Provider, { value: { value: current, setValue }, children: /* @__PURE__ */ jsx("div", { className, children }) });
452
+ return /* @__PURE__ */ jsx(
453
+ Tabs$1.Root,
454
+ {
455
+ className,
456
+ defaultValue,
457
+ value,
458
+ onValueChange: (next) => onValueChange?.(String(next)),
459
+ children
460
+ }
461
+ );
439
462
  }
440
463
  function TabsList({
441
464
  className,
442
465
  children
443
466
  }) {
444
- return /* @__PURE__ */ jsx("div", { className: cn("inline-flex items-center gap-1 rounded-lg bg-muted p-1", className), children });
467
+ return /* @__PURE__ */ jsx(
468
+ Tabs$1.List,
469
+ {
470
+ className: cn("inline-flex items-center gap-1 rounded-lg bg-muted p-1", className),
471
+ children
472
+ }
473
+ );
445
474
  }
446
475
  function TabsTrigger({
447
476
  value,
448
477
  className,
449
478
  children
450
479
  }) {
451
- const { value: current, setValue } = useTabsContext();
452
- const active = current === value;
453
480
  return /* @__PURE__ */ jsx(
454
- "button",
481
+ Tabs$1.Tab,
455
482
  {
456
- type: "button",
457
- onClick: () => setValue(value),
483
+ value,
458
484
  className: cn(
459
485
  "rounded-md px-3 py-1.5 text-sm font-medium transition-colors",
460
- active ? "bg-card text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground",
486
+ "text-muted-foreground hover:text-foreground",
487
+ "data-[active]:bg-card data-[active]:text-foreground data-[active]:shadow-sm",
461
488
  className
462
489
  ),
463
490
  children
@@ -469,38 +496,25 @@ function TabsContent({
469
496
  className,
470
497
  children
471
498
  }) {
472
- const { value: current } = useTabsContext();
473
- if (current !== value) return null;
474
- return /* @__PURE__ */ jsx("div", { className, children });
499
+ return /* @__PURE__ */ jsx(Tabs$1.Panel, { value, className, children });
475
500
  }
476
501
  function Modal({ open, onClose, title, children, className }) {
477
- if (!open) return null;
478
- return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center p-4", children: [
479
- /* @__PURE__ */ jsx(
480
- "button",
481
- {
482
- type: "button",
483
- "aria-label": "Close",
484
- className: "absolute inset-0 cursor-default bg-foreground/40",
485
- onClick: onClose
486
- }
487
- ),
502
+ return /* @__PURE__ */ jsx(Dialog.Root, { open, onOpenChange: (next) => !next && onClose(), children: /* @__PURE__ */ jsxs(Dialog.Portal, { children: [
503
+ /* @__PURE__ */ jsx(Dialog.Backdrop, { "aria-label": "Close", className: "fixed inset-0 z-50 bg-foreground/40" }),
488
504
  /* @__PURE__ */ jsxs(
489
- "div",
505
+ Dialog.Popup,
490
506
  {
491
- role: "dialog",
492
- "aria-modal": true,
493
507
  className: cn(
494
- "relative z-10 w-full max-w-md rounded-xl border bg-card p-5 shadow-lg",
508
+ "fixed left-1/2 top-1/2 z-50 w-[calc(100%-2rem)] max-w-md -translate-x-1/2 -translate-y-1/2 rounded-xl border bg-card p-5 shadow-lg",
495
509
  className
496
510
  ),
497
511
  children: [
498
- title && /* @__PURE__ */ jsx("h2", { className: "mb-3 text-base font-semibold", children: title }),
512
+ title && /* @__PURE__ */ jsx(Dialog.Title, { className: "mb-3 text-base font-semibold", children: title }),
499
513
  children
500
514
  ]
501
515
  }
502
516
  )
503
- ] });
517
+ ] }) });
504
518
  }
505
519
  var spinnerSizes = { sm: "size-4", md: "size-6", lg: "size-8" };
506
520
  function Spinner({ className, size = "md" }) {
@@ -550,28 +564,19 @@ function Switch({
550
564
  ...props
551
565
  }) {
552
566
  return /* @__PURE__ */ jsx(
553
- "button",
567
+ Switch$1.Root,
554
568
  {
555
- type: "button",
556
- role: "switch",
557
- "aria-checked": checked,
569
+ checked,
570
+ onCheckedChange: (next) => onCheckedChange?.(next),
558
571
  disabled,
559
- onClick: () => onCheckedChange?.(!checked),
560
572
  className: cn(
561
- "relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
562
- checked ? "bg-primary" : "bg-input",
573
+ // `data-[disabled]`, not `disabled:` the root is a span, so `:disabled`
574
+ // never matches it and the dimming would silently stop happening.
575
+ "relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full bg-input transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring data-[checked]:bg-primary data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50",
563
576
  className
564
577
  ),
565
578
  ...props,
566
- children: /* @__PURE__ */ jsx(
567
- "span",
568
- {
569
- className: cn(
570
- "inline-block size-4 transform rounded-full bg-white shadow transition-transform",
571
- checked ? "translate-x-4" : "translate-x-0.5"
572
- )
573
- }
574
- )
579
+ children: /* @__PURE__ */ jsx(Switch$1.Thumb, { className: "inline-block size-4 translate-x-0.5 rounded-full bg-white shadow transition-transform data-[checked]:translate-x-4" })
575
580
  }
576
581
  );
577
582
  }
@@ -700,47 +705,26 @@ function SidePanel({
700
705
  children,
701
706
  className
702
707
  }) {
703
- React.useEffect(() => {
704
- if (!open) return;
705
- const onKeyDown = (e) => {
706
- if (e.key === "Escape") onClose();
707
- };
708
- window.addEventListener("keydown", onKeyDown);
709
- return () => window.removeEventListener("keydown", onKeyDown);
710
- }, [open, onClose]);
711
- if (!open) return null;
712
- return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50", children: [
713
- /* @__PURE__ */ jsx(
714
- "button",
715
- {
716
- type: "button",
717
- "aria-label": "Fermer",
718
- className: "absolute inset-0 cursor-default bg-foreground/40",
719
- onClick: onClose
720
- }
721
- ),
708
+ return /* @__PURE__ */ jsx(Dialog.Root, { open, onOpenChange: (next) => !next && onClose(), children: /* @__PURE__ */ jsxs(Dialog.Portal, { children: [
709
+ /* @__PURE__ */ jsx(Dialog.Backdrop, { "aria-label": "Fermer", className: "fixed inset-0 z-50 bg-foreground/40" }),
722
710
  /* @__PURE__ */ jsxs(
723
- "aside",
711
+ Dialog.Popup,
724
712
  {
725
- role: "dialog",
726
- "aria-modal": true,
727
713
  className: cn(
728
- "absolute inset-y-0 right-0 flex w-full max-w-lg flex-col border-l bg-card shadow-xl",
714
+ "fixed inset-y-0 right-0 z-50 flex w-full max-w-lg flex-col border-l bg-card shadow-xl",
729
715
  className
730
716
  ),
731
717
  children: [
732
718
  /* @__PURE__ */ jsxs("header", { className: "flex shrink-0 items-start gap-3 border-b px-5 py-4", children: [
733
719
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
734
- title && /* @__PURE__ */ jsx("h2", { className: "text-base font-semibold", children: title }),
735
- description && /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: description })
720
+ title && /* @__PURE__ */ jsx(Dialog.Title, { className: "text-base font-semibold", children: title }),
721
+ description && /* @__PURE__ */ jsx(Dialog.Description, { className: "mt-0.5 text-xs text-muted-foreground", children: description })
736
722
  ] }),
737
723
  /* @__PURE__ */ jsx(
738
- "button",
724
+ Dialog.Close,
739
725
  {
740
- type: "button",
741
726
  "aria-label": "Fermer",
742
727
  className: "-mr-1 shrink-0 rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground",
743
- onClick: onClose,
744
728
  children: /* @__PURE__ */ jsx(IconX, { className: "size-4" })
745
729
  }
746
730
  )
@@ -749,7 +733,7 @@ function SidePanel({
749
733
  ]
750
734
  }
751
735
  )
752
- ] });
736
+ ] }) });
753
737
  }
754
738
  var COPY_FEEDBACK_MS = 2e3;
755
739
  function CopyButton({
@@ -792,69 +776,111 @@ function Popover({
792
776
  align = "start",
793
777
  className
794
778
  }) {
795
- const root = React.useRef(null);
796
- React.useEffect(() => {
797
- if (!open) return;
798
- const onPointerDown = (e) => {
799
- if (!root.current?.contains(e.target)) onOpenChange(false);
800
- };
801
- const onKeyDown = (e) => {
802
- if (e.key === "Escape") onOpenChange(false);
803
- };
804
- document.addEventListener("mousedown", onPointerDown);
805
- document.addEventListener("keydown", onKeyDown);
806
- return () => {
807
- document.removeEventListener("mousedown", onPointerDown);
808
- document.removeEventListener("keydown", onKeyDown);
809
- };
810
- }, [open, onOpenChange]);
811
- return /* @__PURE__ */ jsxs("div", { ref: root, className: "relative inline-block", children: [
812
- /* @__PURE__ */ jsx("button", { type: "button", "aria-expanded": open, onClick: () => onOpenChange(!open), children: trigger }),
813
- open && /* @__PURE__ */ jsx(
779
+ return (
780
+ // The callback is re-wrapped so Base UI's event-details argument stops at the
781
+ // SDK boundary: the engine is an implementation detail, not part of the API.
782
+ /* @__PURE__ */ jsxs(Popover$1.Root, { open, onOpenChange: (next) => onOpenChange(next), children: [
783
+ /* @__PURE__ */ jsx("span", { className: "inline-block", children: /* @__PURE__ */ jsx(Popover$1.Trigger, { ...triggerProps(trigger) }) }),
784
+ /* @__PURE__ */ jsx(Popover$1.Portal, { children: /* @__PURE__ */ jsx(Popover$1.Positioner, { side: "bottom", align, sideOffset: 4, className: "z-50", children: /* @__PURE__ */ jsx(
785
+ Popover$1.Popup,
786
+ {
787
+ className: cn("min-w-48 rounded-md border bg-card p-2 shadow-lg", className),
788
+ children
789
+ }
790
+ ) }) })
791
+ ] })
792
+ );
793
+ }
794
+ function DropdownMenu({ trigger, items, align = "end", className }) {
795
+ return /* @__PURE__ */ jsxs(Menu.Root, { children: [
796
+ /* @__PURE__ */ jsx("span", { className: "inline-block", children: /* @__PURE__ */ jsx(Menu.Trigger, { ...triggerProps(trigger) }) }),
797
+ /* @__PURE__ */ jsx(Menu.Portal, { children: /* @__PURE__ */ jsx(Menu.Positioner, { side: "bottom", align, sideOffset: 4, className: "z-50", children: /* @__PURE__ */ jsx(
798
+ Menu.Popup,
799
+ {
800
+ className: cn(
801
+ "flex min-w-48 flex-col rounded-md border bg-card p-1 shadow-lg",
802
+ className
803
+ ),
804
+ children: items.map((item, i) => /* @__PURE__ */ jsx(
805
+ Menu.Item,
806
+ {
807
+ disabled: item.disabled,
808
+ onClick: item.onSelect,
809
+ className: cn(
810
+ "cursor-pointer rounded px-2 py-1.5 text-left text-sm outline-none data-[highlighted]:bg-accent data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
811
+ item.destructive && "text-destructive"
812
+ ),
813
+ children: item.label
814
+ },
815
+ i
816
+ ))
817
+ }
818
+ ) }) })
819
+ ] });
820
+ }
821
+ var ToastContext = createContext(null);
822
+ function useToast() {
823
+ const toast = useContext(ToastContext);
824
+ if (!toast) throw new Error("useToast must be used within ToastProvider");
825
+ return toast;
826
+ }
827
+ var TOAST_TTL_MS = 4e3;
828
+ var TONE = {
829
+ success: "border-l-status-success",
830
+ error: "border-l-status-blocked",
831
+ info: "border-l-status-info"
832
+ };
833
+ function ToastProvider({ children }) {
834
+ const [toasts, setToasts] = useState([]);
835
+ const nextId = useRef(0);
836
+ const toast = useCallback((input) => {
837
+ const id = nextId.current++;
838
+ setToasts((prev) => [...prev, { id, ...input }]);
839
+ setTimeout(() => setToasts((prev) => prev.filter((t) => t.id !== id)), TOAST_TTL_MS);
840
+ }, []);
841
+ return /* @__PURE__ */ jsxs(ToastContext.Provider, { value: toast, children: [
842
+ children,
843
+ /* @__PURE__ */ jsx("div", { className: "pointer-events-none fixed bottom-4 right-4 z-[60] flex w-80 flex-col gap-2", children: toasts.map((t) => /* @__PURE__ */ jsxs(
814
844
  "div",
815
845
  {
846
+ role: "status",
816
847
  className: cn(
817
- "absolute z-40 mt-1 min-w-48 rounded-md border bg-card p-2 shadow-lg",
818
- align === "end" ? "right-0" : "left-0",
848
+ "pointer-events-auto rounded-lg border border-l-4 bg-card px-4 py-3 text-foreground shadow-md",
849
+ TONE[t.variant ?? "success"]
850
+ ),
851
+ children: [
852
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t.title }),
853
+ t.description && /* @__PURE__ */ jsx("p", { className: "mt-0.5 break-all text-xs text-muted-foreground", children: t.description })
854
+ ]
855
+ },
856
+ t.id
857
+ )) })
858
+ ] });
859
+ }
860
+ var SIDES = {
861
+ top: "bottom-full left-1/2 -translate-x-1/2 mb-1.5",
862
+ right: "left-full top-1/2 -translate-y-1/2 ml-1.5",
863
+ bottom: "top-full left-1/2 -translate-x-1/2 mt-1.5",
864
+ left: "right-full top-1/2 -translate-y-1/2 mr-1.5"
865
+ };
866
+ function Tooltip({ label, children, side = "top", className }) {
867
+ return /* @__PURE__ */ jsxs("span", { className: "group/tt relative inline-flex", children: [
868
+ children,
869
+ /* @__PURE__ */ jsx(
870
+ "span",
871
+ {
872
+ role: "tooltip",
873
+ className: cn(
874
+ "pointer-events-none absolute z-50 whitespace-nowrap rounded-md bg-foreground px-2 py-1 text-xs font-medium text-background opacity-0 shadow-md transition-opacity group-hover/tt:opacity-100",
875
+ SIDES[side],
819
876
  className
820
877
  ),
821
- children
878
+ children: label
822
879
  }
823
880
  )
824
881
  ] });
825
882
  }
826
- function DropdownMenu({ trigger, items, align = "end", className }) {
827
- const [open, setOpen] = React.useState(false);
828
- return /* @__PURE__ */ jsx(
829
- Popover,
830
- {
831
- open,
832
- onOpenChange: setOpen,
833
- trigger,
834
- align,
835
- className: cn("p-1", className),
836
- children: /* @__PURE__ */ jsx("div", { role: "menu", className: "flex flex-col", children: items.map((item, i) => /* @__PURE__ */ jsx(
837
- "button",
838
- {
839
- type: "button",
840
- role: "menuitem",
841
- disabled: item.disabled,
842
- className: cn(
843
- "cursor-pointer rounded px-2 py-1.5 text-left text-sm hover:bg-accent disabled:pointer-events-none disabled:opacity-50",
844
- item.destructive && "text-destructive"
845
- ),
846
- onClick: () => {
847
- setOpen(false);
848
- item.onSelect();
849
- },
850
- children: item.label
851
- },
852
- i
853
- )) })
854
- }
855
- );
856
- }
857
883
 
858
- export { AppApiProvider, AppContextProvider, AppInvokeError, Badge, Button, Card, CardContent, CardHeader, CardTitle, Checkbox, CopyButton, DateRangePicker, DropdownMenu, Input, Link, Modal, Popover, Select, SidePanel, Spinner, Switch, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, cn, copyToClipboard, downloadBlob, downloadCsv, toCsv, useAppApi, useAppContext, useAppRun, useAppRuns, useAppSettings };
884
+ export { AppApiProvider, AppContextProvider, AppInvokeError, Badge, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, CopyButton, DateRangePicker, DropdownMenu, Input, Link, Modal, Popover, Select, SidePanel, Spinner, Switch, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, ToastProvider, Tooltip, badgeVariants, buttonVariants, cn, copyToClipboard, downloadBlob, downloadCsv, toCsv, useAppApi, useAppContext, useAppRun, useAppRuns, useAppSettings, useToast };
859
885
  //# sourceMappingURL=index.js.map
860
886
  //# sourceMappingURL=index.js.map