@apotech-os/ui-sdk 0.10.0 → 1.0.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,6 +1,5 @@
1
1
  import { ClassValue } from 'clsx';
2
2
  import * as React from 'react';
3
- import { ReactNode } from 'react';
4
3
  import { AppErrorCode, AppRunRecord, AppRunStatus } from '@apotech-os/app-kit';
5
4
  export { AppRunRecord, AppRunStatus } from '@apotech-os/app-kit';
6
5
  import * as class_variance_authority_types from 'class-variance-authority/types';
@@ -160,9 +159,7 @@ declare function Badge({ className, variant, dot, children, ...props }: BadgePro
160
159
  declare function Card({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): React.JSX.Element;
161
160
  declare function CardHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): React.JSX.Element;
162
161
  declare function CardTitle({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>): React.JSX.Element;
163
- declare function CardDescription({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>): React.JSX.Element;
164
162
  declare function CardContent({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): React.JSX.Element;
165
- declare function CardFooter({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): React.JSX.Element;
166
163
  declare const Input: React.ForwardRefExoticComponent<React.InputHTMLAttributes<HTMLInputElement> & React.RefAttributes<HTMLInputElement>>;
167
164
  declare function Table({ className, ...props }: React.HTMLAttributes<HTMLTableElement>): React.JSX.Element;
168
165
  declare function TableHeader({ className, ...props }: React.HTMLAttributes<HTMLTableSectionElement>): React.JSX.Element;
@@ -194,7 +191,7 @@ declare function TabsContent({ value, className, children, }: {
194
191
  value: string;
195
192
  className?: string;
196
193
  children: React.ReactNode;
197
- }): React.JSX.Element;
194
+ }): React.JSX.Element | null;
198
195
  interface ModalProps {
199
196
  open: boolean;
200
197
  onClose: () => void;
@@ -202,8 +199,7 @@ interface ModalProps {
202
199
  children: React.ReactNode;
203
200
  className?: string;
204
201
  }
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;
202
+ declare function Modal({ open, onClose, title, children, className }: ModalProps): React.JSX.Element | null;
207
203
  interface SpinnerProps {
208
204
  className?: string;
209
205
  size?: 'sm' | 'md' | 'lg';
@@ -213,10 +209,6 @@ interface LinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
213
209
  variant?: 'default' | 'muted';
214
210
  }
215
211
  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
- */
220
212
  interface SwitchProps {
221
213
  checked?: boolean;
222
214
  onCheckedChange?: (checked: boolean) => void;
@@ -262,10 +254,9 @@ interface SidePanelProps {
262
254
  * Children are laid out in a flex column filling the panel: put the scrollable
263
255
  * region in a `min-h-0 flex-1 overflow-y-auto` child and the footer after it.
264
256
  *
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.
257
+ * NOTE: no focus management. Autofocus the primary action yourself.
267
258
  */
268
- declare function SidePanel({ open, onClose, title, description, children, className, }: SidePanelProps): React.JSX.Element;
259
+ declare function SidePanel({ open, onClose, title, description, children, className, }: SidePanelProps): React.JSX.Element | null;
269
260
  interface CopyButtonProps {
270
261
  value: string;
271
262
  label?: string;
@@ -303,45 +294,9 @@ interface DropdownMenuProps {
303
294
  align?: 'start' | 'end';
304
295
  className?: string;
305
296
  }
306
- /** Uncontrolled menu. Selecting an item closes it; arrow keys move between them. */
297
+ /** Uncontrolled menu built on Popover. Selecting an item closes it. */
307
298
  declare function DropdownMenu({ trigger, items, align, className }: DropdownMenuProps): React.JSX.Element;
308
299
 
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
-
345
300
  /**
346
301
  * Browser helpers for app pages.
347
302
  *
@@ -371,4 +326,4 @@ declare function toCsv(rows: string[][]): string;
371
326
  */
372
327
  declare function downloadCsv(rows: string[][], filename: string): void;
373
328
 
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 };
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 };
package/dist/index.js CHANGED
@@ -2,11 +2,6 @@ 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';
10
5
  import { cva } from 'class-variance-authority';
11
6
  import { jsx, jsxs } from 'react/jsx-runtime';
12
7
 
@@ -294,12 +289,6 @@ function downloadCsv(rows, filename) {
294
289
  const blob = new Blob([`${BOM}${toCsv(rows)}`], { type: "text/csv;charset=utf-8" });
295
290
  downloadBlob(blob, filename);
296
291
  }
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
- }
303
292
  var buttonVariants = cva(
304
293
  "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",
305
294
  {
@@ -363,18 +352,9 @@ function CardHeader({ className, ...props }) {
363
352
  function CardTitle({ className, ...props }) {
364
353
  return /* @__PURE__ */ jsx("h3", { className: cn("text-lg font-semibold leading-none tracking-tight", className), ...props });
365
354
  }
366
- function CardDescription({
367
- className,
368
- ...props
369
- }) {
370
- return /* @__PURE__ */ jsx("p", { className: cn("text-sm text-muted-foreground", className), ...props });
371
- }
372
355
  function CardContent({ className, ...props }) {
373
356
  return /* @__PURE__ */ jsx("div", { className: cn("p-6 pt-0", className), ...props });
374
357
  }
375
- function CardFooter({ className, ...props }) {
376
- return /* @__PURE__ */ jsx("div", { className: cn("flex items-center p-6 pt-0", className), ...props });
377
- }
378
358
  var Input = React.forwardRef(({ className, type, ...props }, ref) => /* @__PURE__ */ jsx(
379
359
  "input",
380
360
  {
@@ -395,13 +375,7 @@ function TableHeader({
395
375
  className,
396
376
  ...props
397
377
  }) {
398
- return /* @__PURE__ */ jsx(
399
- "thead",
400
- {
401
- className: cn("[&_tr]:border-b [&_tr]:hover:bg-transparent", className),
402
- ...props
403
- }
404
- );
378
+ return /* @__PURE__ */ jsx("thead", { className: cn("[&_tr]:border-b", className), ...props });
405
379
  }
406
380
  function TableBody({ className, ...props }) {
407
381
  return /* @__PURE__ */ jsx("tbody", { className: cn("[&_tr:last-child]:border-0", className), ...props });
@@ -423,7 +397,7 @@ function TableHead({ className, ...props }) {
423
397
  "th",
424
398
  {
425
399
  className: cn(
426
- "h-10 px-4 text-left align-middle text-xs font-medium uppercase tracking-wide text-muted-foreground",
400
+ "h-10 px-4 text-left align-middle text-xs font-medium text-muted-foreground",
427
401
  className
428
402
  ),
429
403
  ...props
@@ -448,43 +422,42 @@ var Select = React.forwardRef(
448
422
  )
449
423
  );
450
424
  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
+ }
451
431
  function Tabs({ defaultValue, value, onValueChange, 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
- );
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 }) });
462
439
  }
463
440
  function TabsList({
464
441
  className,
465
442
  children
466
443
  }) {
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
- );
444
+ return /* @__PURE__ */ jsx("div", { className: cn("inline-flex items-center gap-1 rounded-lg bg-muted p-1", className), children });
474
445
  }
475
446
  function TabsTrigger({
476
447
  value,
477
448
  className,
478
449
  children
479
450
  }) {
451
+ const { value: current, setValue } = useTabsContext();
452
+ const active = current === value;
480
453
  return /* @__PURE__ */ jsx(
481
- Tabs$1.Tab,
454
+ "button",
482
455
  {
483
- value,
456
+ type: "button",
457
+ onClick: () => setValue(value),
484
458
  className: cn(
485
459
  "rounded-md px-3 py-1.5 text-sm font-medium transition-colors",
486
- "text-muted-foreground hover:text-foreground",
487
- "data-[active]:bg-card data-[active]:text-foreground data-[active]:shadow-sm",
460
+ active ? "bg-card text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground",
488
461
  className
489
462
  ),
490
463
  children
@@ -496,25 +469,38 @@ function TabsContent({
496
469
  className,
497
470
  children
498
471
  }) {
499
- return /* @__PURE__ */ jsx(Tabs$1.Panel, { value, className, children });
472
+ const { value: current } = useTabsContext();
473
+ if (current !== value) return null;
474
+ return /* @__PURE__ */ jsx("div", { className, children });
500
475
  }
501
476
  function Modal({ open, onClose, title, children, className }) {
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" }),
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
+ ),
504
488
  /* @__PURE__ */ jsxs(
505
- Dialog.Popup,
489
+ "div",
506
490
  {
491
+ role: "dialog",
492
+ "aria-modal": true,
507
493
  className: cn(
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",
494
+ "relative z-10 w-full max-w-md rounded-xl border bg-card p-5 shadow-lg",
509
495
  className
510
496
  ),
511
497
  children: [
512
- title && /* @__PURE__ */ jsx(Dialog.Title, { className: "mb-3 text-base font-semibold", children: title }),
498
+ title && /* @__PURE__ */ jsx("h2", { className: "mb-3 text-base font-semibold", children: title }),
513
499
  children
514
500
  ]
515
501
  }
516
502
  )
517
- ] }) });
503
+ ] });
518
504
  }
519
505
  var spinnerSizes = { sm: "size-4", md: "size-6", lg: "size-8" };
520
506
  function Spinner({ className, size = "md" }) {
@@ -564,19 +550,28 @@ function Switch({
564
550
  ...props
565
551
  }) {
566
552
  return /* @__PURE__ */ jsx(
567
- Switch$1.Root,
553
+ "button",
568
554
  {
569
- checked,
570
- onCheckedChange: (next) => onCheckedChange?.(next),
555
+ type: "button",
556
+ role: "switch",
557
+ "aria-checked": checked,
571
558
  disabled,
559
+ onClick: () => onCheckedChange?.(!checked),
572
560
  className: cn(
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",
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",
576
563
  className
577
564
  ),
578
565
  ...props,
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" })
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
+ )
580
575
  }
581
576
  );
582
577
  }
@@ -705,26 +700,47 @@ function SidePanel({
705
700
  children,
706
701
  className
707
702
  }) {
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" }),
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
+ ),
710
722
  /* @__PURE__ */ jsxs(
711
- Dialog.Popup,
723
+ "aside",
712
724
  {
725
+ role: "dialog",
726
+ "aria-modal": true,
713
727
  className: cn(
714
- "fixed inset-y-0 right-0 z-50 flex w-full max-w-lg flex-col border-l bg-card shadow-xl",
728
+ "absolute inset-y-0 right-0 flex w-full max-w-lg flex-col border-l bg-card shadow-xl",
715
729
  className
716
730
  ),
717
731
  children: [
718
732
  /* @__PURE__ */ jsxs("header", { className: "flex shrink-0 items-start gap-3 border-b px-5 py-4", children: [
719
733
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
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 })
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 })
722
736
  ] }),
723
737
  /* @__PURE__ */ jsx(
724
- Dialog.Close,
738
+ "button",
725
739
  {
740
+ type: "button",
726
741
  "aria-label": "Fermer",
727
742
  className: "-mr-1 shrink-0 rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground",
743
+ onClick: onClose,
728
744
  children: /* @__PURE__ */ jsx(IconX, { className: "size-4" })
729
745
  }
730
746
  )
@@ -733,7 +749,7 @@ function SidePanel({
733
749
  ]
734
750
  }
735
751
  )
736
- ] }) });
752
+ ] });
737
753
  }
738
754
  var COPY_FEEDBACK_MS = 2e3;
739
755
  function CopyButton({
@@ -776,111 +792,69 @@ function Popover({
776
792
  align = "start",
777
793
  className
778
794
  }) {
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(
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(
844
814
  "div",
845
815
  {
846
- role: "status",
847
816
  className: cn(
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],
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",
876
819
  className
877
820
  ),
878
- children: label
821
+ children
879
822
  }
880
823
  )
881
824
  ] });
882
825
  }
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
+ }
883
857
 
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 };
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 };
885
859
  //# sourceMappingURL=index.js.map
886
860
  //# sourceMappingURL=index.js.map