{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "toast",
  "registryDependencies": [
    "@exabase/button"
  ],
  "files": [
    {
      "path": "components/ui/toast.tsx",
      "content": "// Based on coss ui (https://github.com/cosscom/coss/blob/main/apps/ui/registry/default/ui/toast.tsx)\n// Copyright (c) coss.com - MIT License\n\n\"use client\"\n\nimport type React from \"react\"\nimport { Toast } from \"@base-ui/react/toast\"\nimport {\n  CheckmarkCircleFillIcon,\n  ExclamationmarkCircleFillIcon,\n  ExclamationmarkTriangleFillIcon,\n  InformationmarkCircleFillIcon,\n  XmarkLargeIcon,\n} from \"@exawizards/exabase-design-system-icons-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button, buttonVariants } from \"@/components/ui/button\"\n\nconst TOAST_ICONS: Record<\n  string,\n  React.ComponentType<{ className?: string }>\n> = {\n  success: CheckmarkCircleFillIcon,\n  info: InformationmarkCircleFillIcon,\n  warning: ExclamationmarkTriangleFillIcon,\n  error: ExclamationmarkCircleFillIcon,\n  destructive: ExclamationmarkCircleFillIcon,\n}\n\nconst TOAST_ICON_CLASSES: Record<string, string> = {\n  success: \"text-success\",\n  info: \"text-info\",\n  warning: \"text-warning\",\n  error: \"text-destructive\",\n  destructive: \"text-destructive-foreground\",\n}\n\nconst TOAST_TYPE_CLASSES: Record<string, string> = {\n  default: \"light outline-border\",\n  dark: \"dark outline-border\",\n  info: \"info outline-info-text/10 bg-info-muted\",\n  success: \"success outline-success-text/10 bg-success-muted\",\n  warning: \"warning outline-warning-text/10 bg-warning-muted\",\n  error: \"error outline-destructive-text/10 bg-destructive-muted\",\n  destructive:\n    \"destructive outline-destructive bg-destructive text-destructive-foreground\",\n}\n\nfunction renderToastIcon(\n  customIcon: React.ReactNode | undefined,\n  Icon: React.ComponentType<{ className?: string }> | null,\n  iconClass: string\n): React.ReactNode {\n  // Explicit null means no icon\n  if (customIcon === null) {\n    return null\n  }\n\n  // Custom icon provided\n  if (customIcon !== undefined) {\n    return customIcon\n  }\n\n  // Default icon from type\n  if (Icon) {\n    return (\n      <div\n        className=\"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&>svg]:size-5\"\n        data-slot=\"toast-icon\"\n      >\n        <Icon className={iconClass} />\n      </div>\n    )\n  }\n\n  return null\n}\n\ntype SwipeDirection = \"up\" | \"down\" | \"left\" | \"right\"\n\nfunction getSwipeDirection(position: ToastPosition): SwipeDirection[] {\n  const verticalDirection: SwipeDirection = position.startsWith(\"top\")\n    ? \"up\"\n    : \"down\"\n\n  if (position.includes(\"center\")) {\n    return [verticalDirection]\n  }\n\n  if (position.includes(\"left\")) {\n    return [\"left\", verticalDirection]\n  }\n\n  return [\"right\", verticalDirection]\n}\n\nfunction Toasts({ position }: { position: ToastPosition }): React.ReactElement {\n  const { toasts } = Toast.useToastManager()\n  const swipeDirection = getSwipeDirection(position)\n\n  return (\n    <Toast.Portal data-slot=\"toast-portal\">\n      <Toast.Viewport\n        className={cn(\n          \"fixed z-100 mx-auto flex w-[calc(100%-var(--toast-inset)*2)] max-w-[420px] [--toast-inset:--spacing(4)] sm:[--toast-inset:--spacing(8)]\",\n          \"data-[position*=top]:top-(--toast-inset)\",\n          \"data-[position*=bottom]:bottom-(--toast-inset)\",\n          \"data-[position*=left]:left-(--toast-inset)\",\n          \"data-[position*=right]:right-(--toast-inset)\",\n          \"data-[position*=center]:left-1/2 data-[position*=center]:-translate-x-1/2\"\n        )}\n        data-position={position}\n        data-slot=\"toast-viewport\"\n      >\n        {toasts.map((toast) => {\n          const toastType = (toast.type as string) ?? \"default\"\n          const Icon = TOAST_ICONS[toastType] ?? null\n          const iconClass = TOAST_ICON_CLASSES[toastType] ?? \"\"\n          const typeClass = TOAST_TYPE_CLASSES[toastType] ?? \"\"\n          const customIcon = (toast.data as { icon?: React.ReactNode })?.icon\n\n          return (\n            <Toast.Root\n              className={cn(\n                \"absolute z-[calc(9999-var(--toast-index))] h-(--toast-calc-height) w-full rounded-xl bg-background text-foreground shadow-lg outline [transition:transform_.5s_cubic-bezier(.22,1,.36,1),opacity_.5s,height_.15s,background-color_.5s]\",\n                // Base positioning\n                \"data-[position*=right]:right-0 data-[position*=right]:left-auto\",\n                \"data-[position*=left]:right-auto data-[position*=left]:left-0\",\n                \"data-[position*=center]:right-0 data-[position*=center]:left-0\",\n                \"data-[position*=top]:top-0 data-[position*=top]:bottom-auto data-[position*=top]:origin-top\",\n                \"data-[position*=bottom]:top-auto data-[position*=bottom]:bottom-0 data-[position*=bottom]:origin-bottom\",\n                // Gap fill for hover\n                \"after:absolute after:left-0 after:h-[calc(var(--toast-gap)+1px)] after:w-full\",\n                \"data-[position*=top]:after:top-full\",\n                \"data-[position*=bottom]:after:bottom-full\",\n                // Variables\n                \"[--toast-calc-height:var(--toast-frontmost-height,var(--toast-height))] [--toast-gap:--spacing(3)] [--toast-peek:--spacing(3)] [--toast-scale:calc(max(0,1-(var(--toast-index)*.1)))] [--toast-shrink:calc(1-var(--toast-scale))]\",\n                // Offset-y\n                \"data-[position*=top]:[--toast-calc-offset-y:calc(var(--toast-offset-y)+var(--toast-index)*var(--toast-gap)+var(--toast-swipe-movement-y))]\",\n                \"data-[position*=bottom]:[--toast-calc-offset-y:calc(var(--toast-offset-y)*-1+var(--toast-index)*var(--toast-gap)*-1+var(--toast-swipe-movement-y))]\",\n                // Default transform\n                \"data-[position*=top]:transform-[translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)+(var(--toast-index)*var(--toast-peek))+(var(--toast-shrink)*var(--toast-calc-height))))_scale(var(--toast-scale))]\",\n                \"data-[position*=bottom]:transform-[translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)-(var(--toast-index)*var(--toast-peek))-(var(--toast-shrink)*var(--toast-calc-height))))_scale(var(--toast-scale))]\",\n                // Limited state\n                \"data-limited:opacity-0\",\n                // Expanded state\n                \"data-expanded:h-(--toast-height)\",\n                \"data-position:data-expanded:transform-[translateX(var(--toast-swipe-movement-x))_translateY(var(--toast-calc-offset-y))]\",\n                // Starting and ending animations\n                \"data-[position*=top]:data-starting-style:transform-[translateY(calc(-100%-var(--toast-inset)))]\",\n                \"data-[position*=bottom]:data-starting-style:transform-[translateY(calc(100%+var(--toast-inset)))]\",\n                \"data-ending-style:opacity-0\",\n                // Ending (direction-aware)\n                \"data-ending-style:not-data-limited:not-data-swipe-direction:transform-[translateY(calc(100%+var(--toast-inset)))]\",\n                \"data-ending-style:data-[swipe-direction=left]:transform-[translateX(calc(var(--toast-swipe-movement-x)-100%-var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]\",\n                \"data-ending-style:data-[swipe-direction=right]:transform-[translateX(calc(var(--toast-swipe-movement-x)+100%+var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]\",\n                \"data-ending-style:data-[swipe-direction=up]:transform-[translateY(calc(var(--toast-swipe-movement-y)-100%-var(--toast-inset)))]\",\n                \"data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(var(--toast-swipe-movement-y)+100%+var(--toast-inset)))]\",\n                // Ending (expanded)\n                \"data-expanded:data-ending-style:data-[swipe-direction=left]:transform-[translateX(calc(var(--toast-swipe-movement-x)-100%-var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]\",\n                \"data-expanded:data-ending-style:data-[swipe-direction=right]:transform-[translateX(calc(var(--toast-swipe-movement-x)+100%+var(--toast-inset)))_translateY(var(--toast-calc-offset-y))]\",\n                \"data-expanded:data-ending-style:data-[swipe-direction=up]:transform-[translateY(calc(var(--toast-swipe-movement-y)-100%-var(--toast-inset)))]\",\n                \"data-expanded:data-ending-style:data-[swipe-direction=down]:transform-[translateY(calc(var(--toast-swipe-movement-y)+100%+var(--toast-inset)))]\",\n                typeClass\n              )}\n              data-position={position}\n              key={toast.id}\n              swipeDirection={swipeDirection}\n              toast={toast}\n            >\n              <Toast.Content className=\"pointer-events-auto flex items-center justify-between gap-1.5 overflow-hidden px-4 py-3 text-sm transition-opacity duration-250 data-behind:opacity-0 data-behind:not-data-expanded:pointer-events-none data-expanded:opacity-100\">\n                <div className=\"flex items-center gap-3\">\n                  {renderToastIcon(customIcon, Icon, iconClass)}\n\n                  <div className=\"flex flex-col gap-0.5\">\n                    <Toast.Title\n                      className=\"font-medium\"\n                      data-slot=\"toast-title\"\n                    />\n                    <Toast.Description\n                      className=\"opacity-80\"\n                      data-slot=\"toast-description\"\n                    />\n                  </div>\n                </div>\n                <div className=\"flex items-center gap-1.5\">\n                  {toast.actionProps && (\n                    <Toast.Action\n                      className={buttonVariants({\n                        variant: \"outline\",\n                        size: \"sm\",\n                      })}\n                      data-slot=\"toast-action\"\n                    >\n                      {toast.actionProps.children}\n                    </Toast.Action>\n                  )}\n                  <Toast.Close\n                    data-slot=\"toast-close\"\n                    render={\n                      <Button\n                        variant=\"ghost\"\n                        size=\"icon-sm\"\n                        className={\n                          toastType === \"destructive\" ? \"dark\" : undefined\n                        }\n                      />\n                    }\n                  >\n                    <XmarkLargeIcon />\n                    <span className=\"sr-only\">Close</span>\n                  </Toast.Close>\n                </div>\n              </Toast.Content>\n            </Toast.Root>\n          )\n        })}\n      </Toast.Viewport>\n    </Toast.Portal>\n  )\n}\n\nfunction AnchoredToasts(): React.ReactElement {\n  const { toasts } = Toast.useToastManager()\n\n  return (\n    <Toast.Portal data-slot=\"toast-portal-anchored\">\n      <Toast.Viewport\n        data-slot=\"toast-viewport-anchored\"\n        className=\"outline-none\"\n      >\n        {toasts.map((toast) => {\n          const positionerProps = toast.positionerProps\n\n          if (!positionerProps?.anchor) {\n            return null\n          }\n\n          return (\n            <Toast.Positioner\n              data-slot=\"toast-positioner\"\n              key={toast.id}\n              sideOffset={positionerProps.sideOffset ?? 4}\n              alignOffset={positionerProps.alignOffset ?? 0}\n              collisionPadding={positionerProps.collisionPadding ?? 4}\n              toast={toast}\n              className=\"isolate z-50\"\n            >\n              <Toast.Root\n                data-slot=\"toast-popup\"\n                className={cn(\n                  \"z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-2 py-1.5 text-xs text-background shadow-md data-ending-style:animate-out data-ending-style:fade-out-0 data-ending-style:zoom-out-95 data-starting-style:animate-in data-starting-style:fade-in-0 data-starting-style:zoom-in-95 forced-colors:outline\"\n                )}\n                toast={toast}\n              >\n                <Toast.Content\n                  data-slot=\"toast-content\"\n                  className=\"flex items-center gap-1.5\"\n                >\n                  <Toast.Title data-slot=\"toast-title\" />\n                </Toast.Content>\n                <Toast.Arrow className=\"z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5 forced-colors:hidden\" />\n              </Toast.Root>\n            </Toast.Positioner>\n          )\n        })}\n      </Toast.Viewport>\n    </Toast.Portal>\n  )\n}\n\nexport const toastManager: ReturnType<typeof Toast.createToastManager> =\n  Toast.createToastManager()\nexport const anchoredToastManager: ReturnType<typeof Toast.createToastManager> =\n  Toast.createToastManager()\n\nexport type ToastPosition =\n  | \"top-left\"\n  | \"top-center\"\n  | \"top-right\"\n  | \"bottom-left\"\n  | \"bottom-center\"\n  | \"bottom-right\"\n\nexport interface ToastProviderProps extends Toast.Provider.Props {\n  position?: ToastPosition\n}\n\nexport function ToastProvider({\n  children,\n  position = \"bottom-right\",\n  ...props\n}: ToastProviderProps): React.ReactElement {\n  return (\n    <Toast.Provider toastManager={toastManager} {...props}>\n      {children}\n      <Toasts position={position} />\n    </Toast.Provider>\n  )\n}\n\nexport function AnchoredToastProvider({\n  children,\n  ...props\n}: Toast.Provider.Props): React.ReactElement {\n  return (\n    <Toast.Provider toastManager={anchoredToastManager} {...props}>\n      {children}\n      <AnchoredToasts />\n    </Toast.Provider>\n  )\n}\n\nexport { Toast as ToastPrimitive }\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}