{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "toast",
  "title": "Toast",
  "description": "transient · `ToastProvider` is in the root layout",
  "dependencies": [
    "lucide-react-native@^1.21.0",
    "react-native-safe-area-context@~5.7.0"
  ],
  "registryDependencies": [
    "@kit-native/icon",
    "@kit-native/lib-haptics",
    "@kit-native/lib-reduced-motion",
    "@kit-native/lib-theme",
    "@kit-native/lib-utils",
    "@kit-native/text"
  ],
  "files": [
    {
      "path": "components/kit/toast.tsx",
      "content": "import { Icon } from '@/components/ui/icon';\nimport { Text } from '@/components/ui/text';\nimport { haptic } from '@/lib/haptics';\nimport { TOKENS } from '@/lib/theme';\nimport { cn } from '@/lib/utils';\nimport { useMotion } from '@/lib/reduced-motion';\nimport { CircleAlertIcon, CircleCheckIcon, InfoIcon, TriangleAlertIcon } from 'lucide-react-native';\nimport * as React from 'react';\nimport { Animated, Easing, Pressable, View } from 'react-native';\nimport { useSafeAreaInsets } from 'react-native-safe-area-context';\n\nexport type ToastTone = 'success' | 'error' | 'info' | 'warning';\nexport type ToastOptions = { tone?: ToastTone; /** ms; 0 = sticky */ duration?: number };\ntype ToastState = { id: number; message: string; tone: ToastTone; duration: number };\n\ntype ToastApi = {\n  show: (message: string, options?: ToastOptions) => void;\n  success: (message: string, options?: Omit<ToastOptions, 'tone'>) => void;\n  error: (message: string, options?: Omit<ToastOptions, 'tone'>) => void;\n  info: (message: string, options?: Omit<ToastOptions, 'tone'>) => void;\n  warning: (message: string, options?: Omit<ToastOptions, 'tone'>) => void;\n};\n\nconst ToastContext = React.createContext<ToastApi | null>(null);\n\n/**\n * Imperative toasts: `const toast = useToast(); toast.success('Saved')`.\n * Bottom-anchored, newest last, auto-dismiss 2.4s, tap to dismiss.\n */\nexport function useToast(): ToastApi {\n  const ctx = React.useContext(ToastContext);\n  if (!ctx) throw new Error('useToast must be used inside <ToastProvider>');\n  return ctx;\n}\n\nexport function ToastProvider({ children }: { children: React.ReactNode }) {\n  const [toasts, setToasts] = React.useState<ToastState[]>([]);\n  const idRef = React.useRef(0);\n\n  const dismiss = React.useCallback((id: number) => setToasts((prev) => prev.filter((t) => t.id !== id)), []);\n\n  const show = React.useCallback(\n    (message: string, options: ToastOptions = {}) => {\n      const id = ++idRef.current;\n      const t: ToastState = { id, message, tone: options.tone ?? 'info', duration: options.duration ?? 2400 };\n      haptic(t.tone === 'success' ? 'success' : t.tone === 'error' ? 'error' : t.tone === 'warning' ? 'warning' : 'light');\n      setToasts((prev) => [...prev.slice(-2), t]);\n      if (t.duration > 0) setTimeout(() => dismiss(id), t.duration);\n    },\n    [dismiss]\n  );\n\n  const api = React.useMemo<ToastApi>(\n    () => ({\n      show,\n      success: (m, o) => show(m, { ...o, tone: 'success' }),\n      error: (m, o) => show(m, { ...o, tone: 'error' }),\n      info: (m, o) => show(m, { ...o, tone: 'info' }),\n      warning: (m, o) => show(m, { ...o, tone: 'warning' }),\n    }),\n    [show]\n  );\n\n  return (\n    <ToastContext.Provider value={api}>\n      {children}\n      <ToastViewport toasts={toasts} onDismiss={dismiss} />\n    </ToastContext.Provider>\n  );\n}\n\n/**\n * One shape for every tone: a dark surface with light text, tinted by the tone's icon.\n * A toast is transient and overlays content — inverting it is what separates it from the\n * page, and it keeps all four reading as the same object.\n */\nconst TONE = {\n  success: { bg: 'bg-success', fg: 'text-success-foreground', icon: CircleCheckIcon },\n  error: { bg: 'bg-destructive', fg: 'text-destructive-foreground', icon: CircleAlertIcon },\n  warning: { bg: 'bg-warning', fg: 'text-warning-foreground', icon: TriangleAlertIcon },\n  info: { bg: 'bg-foreground', fg: 'text-background', icon: InfoIcon },\n} as const;\n\nfunction ToastViewport({ toasts, onDismiss }: { toasts: ToastState[]; onDismiss: (id: number) => void }) {\n  const insets = useSafeAreaInsets();\n  if (toasts.length === 0) return null;\n  return (\n    <View\n      pointerEvents=\"box-none\"\n      accessibilityLiveRegion=\"polite\"\n      className=\"absolute left-0 right-0 items-center gap-2\"\n      style={{ bottom: insets.bottom + 24 }}>\n      {toasts.map((t) => (\n        <ToastItem key={t.id} toast={t} onDismiss={() => onDismiss(t.id)} />\n      ))}\n    </View>\n  );\n}\n\nfunction ToastItem({ toast, onDismiss }: { toast: ToastState; onDismiss: () => void }) {\n  const [translateY] = React.useState(() => new Animated.Value(12));\n  const spec = TONE[toast.tone];\n  const motion = useMotion();\n  React.useEffect(() => {\n    Animated.timing(translateY, { toValue: 0, duration: motion(TOKENS.duration.base), easing: Easing.out(Easing.cubic), useNativeDriver: true }).start();\n  }, [translateY, motion]);\n  return (\n    <Animated.View style={{ transform: [{ translateY }] }} className=\"max-w-[90%]\">\n      <Pressable onPress={onDismiss} accessibilityRole=\"alert\" className={cn('flex-row items-center gap-2 rounded-full px-4 py-2.5 shadow-lg shadow-black/20', spec.bg)}>\n        <Icon as={spec.icon} size={18} className={spec.fg} />\n        <Text className={cn('font-semibold', spec.fg)} numberOfLines={2}>\n          {toast.message}\n        </Text>\n      </Pressable>\n    </Animated.View>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/kit/toast.tsx"
    }
  ],
  "meta": {
    "exports": [
      "useToast",
      "ToastProvider"
    ],
    "api": "const toast = useToast(); toast.success(\"Saved\") · .error · .warning · .info"
  },
  "docs": "Run `npx expo install --fix` afterwards so Expo SDK packages match your SDK. Needs the kit theme (`@kit-native/theme`) and `KitThemeProvider` from `@kit-native/lib-theme-context` at the root.",
  "categories": [
    "feedback"
  ],
  "type": "registry:component"
}