{"$schemaVersion":1,"name":"button","type":"registry:ui","description":"A versatile button component with multiple variants, sizes, and interactive animations.","dependencies":["expo-haptics","lucide-react-native","react-native-reanimated","react-native-svg","react-native-worklets"],"registryDependencies":["mode-provider","useColorScheme","colors","useColor","useHaptics","globals","text","view","icon","spinner"],"files":[{"type":"registry:theme","path":"src/providers/mode-provider.tsx","target":"providers/mode-provider.tsx","content":"import {\n  createContext,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useState,\n} from 'react';\nimport { Appearance, useColorScheme as useRNColorScheme } from 'react-native';\n\nexport type Mode = 'light' | 'dark' | 'system';\n\n/**\n * The slice of a key/value store this needs. Returns are sync-or-async so a\n * store satisfies it whichever style it exposes — `expo-secure-store` (sync\n * `getItem`/`setItem`) and `AsyncStorage` (promise-returning) both pass\n * unchanged, with no adapter.\n *\n * Keeping it structural is what lets persistence cost an app one prop and this\n * package zero dependencies — nobody installing `useColor` pays for a storage\n * engine they may not want.\n */\nexport type ModeStorage = {\n  getItem: (key: string) => string | null | Promise<string | null>;\n  setItem: (key: string, value: string) => void | Promise<void>;\n};\n\ntype ModeContextValue = {\n  /** What the app was asked for, including the `'system'` passthrough. */\n  mode: Mode;\n  setMode: (mode: Mode) => void;\n  /**\n   * What the app should actually render as. Prefer this over re-deriving it\n   * from `mode` — outside `'system'` there is no meaningful system value to\n   * fall back to, and on native RN's own `useColorScheme()` reports the\n   * override rather than the OS once `setMode` has run.\n   */\n  scheme: 'light' | 'dark';\n};\n\nconst ModeContext = createContext<ModeContextValue | null>(null);\n\nconst isMode = (value: unknown): value is Mode =>\n  value === 'light' || value === 'dark' || value === 'system';\n\n/**\n * Mirrors the override into React Native's global `Appearance` so native chrome\n * follows the toggle too — the status bar, the Android navigation bar, native\n * sheet presentation, and anything reading the OS scheme *above* this provider\n * (root layouts do exactly that to colour the system UI).\n *\n * `Appearance.setColorScheme` landed in React Native 0.73 and react-native-web\n * has never implemented it, so feature-detect rather than assume. On web the\n * context alone drives the theme, which is why the toggle works there without\n * this call.\n */\nfunction syncNativeAppearance(mode: Mode) {\n  if (typeof Appearance.setColorScheme !== 'function') return;\n  // RN 0.86 replaced the old `null` sentinel (\"follow the system\") with\n  // `'unspecified'`.\n  Appearance.setColorScheme(mode === 'system' ? 'unspecified' : mode);\n}\n\ntype Props = {\n  children: React.ReactNode;\n  /** Supply to persist the choice across launches. Omit and it resets. */\n  storage?: ModeStorage;\n  storageKey?: string;\n  defaultMode?: Mode;\n};\n\nexport const ModeProvider = ({\n  children,\n  storage,\n  storageKey = 'bna-ui.mode',\n  defaultMode = 'system',\n}: Props) => {\n  const [mode, setModeState] = useState<Mode>(defaultMode);\n  const systemScheme = useRNColorScheme() === 'dark' ? 'dark' : 'light';\n\n  // Rehydrate once. A missing, malformed or unreadable value leaves the default\n  // in place — persistence is a convenience and must never be able to break boot.\n  //\n  // The `Promise.resolve().then(...)` wrapper is doing real work: it normalises\n  // sync and async stores into one path, and turns a *synchronous* throw into a\n  // rejection `.catch` can see. `expo-secure-store` throws exactly that way on\n  // web, where it is unsupported.\n  useEffect(() => {\n    if (!storage) return;\n    let cancelled = false;\n\n    Promise.resolve()\n      .then(() => storage.getItem(storageKey))\n      .then((saved) => {\n        if (cancelled || !isMode(saved)) return;\n        setModeState(saved);\n        syncNativeAppearance(saved);\n      })\n      .catch(() => {});\n\n    return () => {\n      cancelled = true;\n    };\n  }, [storage, storageKey]);\n\n  const setMode = useCallback(\n    (next: Mode) => {\n      setModeState(next);\n      syncNativeAppearance(next);\n\n      if (storage) {\n        Promise.resolve()\n          .then(() => storage.setItem(storageKey, next))\n          .catch(() => {});\n      }\n    },\n    [storage, storageKey]\n  );\n\n  // This sits at the app root, so an unstable value re-renders every themed\n  // component in the tree on any parent render.\n  const value = useMemo<ModeContextValue>(\n    () => ({\n      mode,\n      setMode,\n      scheme: mode === 'system' ? systemScheme : mode,\n    }),\n    [mode, setMode, systemScheme]\n  );\n\n  return <ModeContext.Provider value={value}>{children}</ModeContext.Provider>;\n};\n\n/**\n * `null` when no `ModeProvider` is mounted, so callers can fall back to the\n * system scheme instead of forcing every app that only wanted `useColor` to\n * mount a provider.\n */\nexport function useModeContext(): ModeContextValue | null {\n  return useContext(ModeContext);\n}\n"},{"type":"registry:hook","path":"src/hooks/useColorScheme.ts","target":"hooks/useColorScheme.ts","content":"import { useColorScheme as useRNColorScheme } from 'react-native';\n\nimport { useModeContext } from '@/providers/mode-provider';\n\n/**\n * The one place the app's colour scheme is decided.\n *\n * A mounted `ModeProvider` wins, so an in-app light/dark toggle works on every\n * platform — including web, where react-native-web has no\n * `Appearance.setColorScheme` for the toggle to write through. With no provider\n * this is just the OS scheme, so installing `useColor` alone still behaves as\n * it always has.\n *\n * React Native 0.86 widened `ColorSchemeName` to `'light' | 'dark' |\n * 'unspecified'`. The theme is binary — `Colors` only has `light` and `dark`\n * keys — so collapse the third value here, once, and let every consumer keep\n * indexing with a two-value union.\n */\nexport function useColorScheme(): 'light' | 'dark' {\n  const system = useRNColorScheme() === 'dark' ? 'dark' : 'light';\n  return useModeContext()?.scheme ?? system;\n}\n"},{"type":"registry:hook","path":"src/hooks/useColorScheme.web.ts","target":"hooks/useColorScheme.web.ts","content":"import { useEffect, useState } from 'react';\nimport { useColorScheme as useRNColorScheme } from 'react-native';\n\nimport { useModeContext } from '@/providers/mode-provider';\n\n/**\n * To support static rendering, this value needs to be re-calculated on the client side for web.\n *\n * Mirrors the native variant: a mounted `ModeProvider` wins, falling back to the\n * OS scheme. The provider is what makes the toggle work here at all —\n * react-native-web's `Appearance` is read-only, exposing `getColorScheme` and\n * `addChangeListener` but no setter, so nothing can push an override into the\n * value `useRNColorScheme()` reports.\n *\n * React Native 0.86's `ColorSchemeName` includes `'unspecified'`, which the\n * binary theme has no slot for, so it collapses here.\n */\nexport function useColorScheme(): 'light' | 'dark' {\n  const [hasHydrated, setHasHydrated] = useState(false);\n\n  useEffect(() => {\n    setHasHydrated(true);\n  }, []);\n\n  const system = useRNColorScheme() === 'dark' ? 'dark' : 'light';\n  const scheme = useModeContext()?.scheme ?? system;\n\n  if (hasHydrated) {\n    return scheme;\n  }\n\n  return 'light';\n}\n"},{"type":"registry:theme","path":"src/theme/colors.ts","target":"theme/colors.ts","content":"const lightColors = {\n  // Base colors\n  background: '#FFFFFF',\n  foreground: '#000000',\n\n  // Card colors\n  card: '#F2F2F7',\n  cardForeground: '#000000',\n\n  // Popover colors\n  popover: '#F2F2F7',\n  popoverForeground: '#000000',\n\n  // Primary colors\n  primary: '#18181b',\n  primaryForeground: '#FFFFFF',\n\n  // Secondary colors\n  secondary: '#F2F2F7',\n  secondaryForeground: '#18181b',\n\n  // Muted colors\n  muted: '#78788033',\n  mutedForeground: '#71717a',\n\n  // Accent colors\n  accent: '#F2F2F7',\n  accentForeground: '#18181b',\n\n  // Destructive colors\n  destructive: '#ef4444',\n  destructiveForeground: '#FFFFFF',\n\n  // Border and input\n  border: '#C6C6C8',\n  input: '#e4e4e7',\n  ring: '#a1a1aa',\n\n  // Text colors\n  text: '#000000',\n  textMuted: '#71717a',\n\n  // Legacy support for existing components\n  tint: '#18181b',\n  icon: '#71717a',\n  tabIconDefault: '#71717a',\n  tabIconSelected: '#18181b',\n\n  // Default buttons, links, Send button, selected tabs\n  blue: '#007AFF',\n\n  // Success states, FaceTime buttons, completed tasks\n  green: '#34C759',\n\n  // Delete buttons, error states, critical alerts\n  red: '#FF3B30',\n\n  // VoiceOver highlights, warning states\n  orange: '#FF9500',\n\n  // Notes app accent, Reminders highlights\n  yellow: '#FFCC00',\n\n  // Pink accent color for various UI elements\n  pink: '#FF2D92',\n\n  // Purple accent for creative apps and features\n  purple: '#AF52DE',\n\n  // Teal accent for communication features\n  teal: '#5AC8FA',\n\n  // Indigo accent for system features\n  indigo: '#5856D6',\n\n  // Semantic states\n  success: '#22c55e',\n  successForeground: '#ffffff',\n  warning: '#f59e0b',\n  warningForeground: '#ffffff',\n  info: '#3b82f6',\n  infoForeground: '#ffffff',\n  error: '#ef4444',\n  errorForeground: '#ffffff',\n};\n\nconst darkColors = {\n  // Base colors\n  background: '#000000',\n  foreground: '#FFFFFF',\n\n  // Card colors\n  card: '#1C1C1E',\n  cardForeground: '#FFFFFF',\n\n  // Popover colors\n  popover: '#18181b',\n  popoverForeground: '#FFFFFF',\n\n  // Primary colors\n  primary: '#e4e4e7',\n  primaryForeground: '#18181b',\n\n  // Secondary colors\n  secondary: '#1C1C1E',\n  secondaryForeground: '#FFFFFF',\n\n  // Muted colors\n  muted: '#78788033',\n  mutedForeground: '#a1a1aa',\n\n  // Accent colors\n  accent: '#1C1C1E',\n  accentForeground: '#FFFFFF',\n\n  // Destructive colors\n  destructive: '#dc2626',\n  destructiveForeground: '#FFFFFF',\n\n  // Border and input - using alpha values for better blending\n  border: '#38383A',\n  input: 'rgba(255, 255, 255, 0.15)',\n  ring: '#71717a',\n\n  // Text colors\n  text: '#FFFFFF',\n  textMuted: '#a1a1aa',\n\n  // Legacy support for existing components\n  tint: '#FFFFFF',\n  icon: '#a1a1aa',\n  tabIconDefault: '#a1a1aa',\n  tabIconSelected: '#FFFFFF',\n\n  // Default buttons, links, Send button, selected tabs\n  blue: '#0A84FF',\n\n  // Success states, FaceTime buttons, completed tasks\n  green: '#30D158',\n\n  // Delete buttons, error states, critical alerts\n  red: '#FF453A',\n\n  // VoiceOver highlights, warning states\n  orange: '#FF9F0A',\n\n  // Notes app accent, Reminders highlights\n  yellow: '#FFD60A',\n\n  // Pink accent color for various UI elements\n  pink: '#FF375F',\n\n  // Purple accent for creative apps and features\n  purple: '#BF5AF2',\n\n  // Teal accent for communication features\n  teal: '#64D2FF',\n\n  // Indigo accent for system features\n  indigo: '#5E5CE6',\n\n  // Semantic states\n  success: '#16a34a',\n  successForeground: '#ffffff',\n  warning: '#d97706',\n  warningForeground: '#ffffff',\n  info: '#2563eb',\n  infoForeground: '#ffffff',\n  error: '#dc2626',\n  errorForeground: '#ffffff',\n};\n\nexport const Colors = {\n  light: lightColors,\n  dark: darkColors,\n};\n\n// Export individual color schemes for easier access\nexport { darkColors, lightColors };\n\n// Utility type for color keys\nexport type ColorKeys = keyof typeof lightColors;\n\n// Helper function to get color with opacity (useful for React Native)\nexport const withOpacity = (color: string, opacity: number) => {\n  // Handle rgba colors\n  if (color.startsWith('rgba')) {\n    return color;\n  }\n\n  // Handle hex colors\n  if (color.startsWith('#')) {\n    const hex = color.replace('#', '');\n    const r = parseInt(hex.substr(0, 2), 16);\n    const g = parseInt(hex.substr(2, 2), 16);\n    const b = parseInt(hex.substr(4, 2), 16);\n    return `rgba(${r}, ${g}, ${b}, ${opacity})`;\n  }\n\n  return color;\n};\n"},{"type":"registry:hook","path":"src/hooks/useColor.ts","target":"hooks/useColor.ts","content":"import { useColorScheme } from '@/hooks/useColorScheme';\nimport { Colors } from '@/theme/colors';\n\nexport function useColor(\n  colorName: keyof typeof Colors.light & keyof typeof Colors.dark,\n  props?: { light?: string; dark?: string }\n) {\n  const theme = useColorScheme() ?? 'light';\n  const colorFromProps = props?.[theme];\n\n  if (colorFromProps) {\n    return colorFromProps;\n  } else {\n    return Colors[theme][colorName];\n  }\n}\n"},{"type":"registry:hook","path":"src/hooks/useHaptics.ts","target":"hooks/useHaptics.ts","content":"import * as Haptics from 'expo-haptics';\nimport { useCallback } from 'react';\nimport { Platform } from 'react-native';\n\n/**\n * What a haptic is *for*, rather than which API to call.\n *\n * iOS and web share one API surface — `impactAsync` / `notificationAsync` /\n * `selectionAsync`. Web is not a no-op: expo-haptics drives `navigator.vibrate`\n * there, falling back to an iOS-Safari switch-element trick.\n *\n * Android must not use those. They are simulated with the raw `Vibrator`\n * service, which Expo explicitly does not recommend: it is a coarse timed buzz\n * and nothing like what native Android controls feel like.\n * `performAndroidHapticsAsync` goes through `View.performHapticFeedback`\n * instead, which is exactly what those controls use.\n *\n * Branching on that is the whole reason this file exists, so that no component\n * has to know about it.\n */\nexport type HapticIntent =\n  | 'selection'\n  | 'tick'\n  | 'toggle-on'\n  | 'toggle-off'\n  | 'impact-light'\n  | 'impact-medium'\n  | 'success'\n  | 'warning'\n  | 'error';\n\nconst ANDROID_HAPTICS: Record<HapticIntent, Haptics.AndroidHaptics> = {\n  selection: Haptics.AndroidHaptics.Segment_Tick,\n  // Clock_Tick rather than Segment_Frequent_Tick: the latter is documented as\n  // possibly producing no vibration at all on devices that cannot make a\n  // suitably soft one, which would silently drop repeated ticks.\n  tick: Haptics.AndroidHaptics.Clock_Tick,\n  'toggle-on': Haptics.AndroidHaptics.Toggle_On,\n  'toggle-off': Haptics.AndroidHaptics.Toggle_Off,\n  'impact-light': Haptics.AndroidHaptics.Virtual_Key,\n  'impact-medium': Haptics.AndroidHaptics.Long_Press,\n  success: Haptics.AndroidHaptics.Confirm,\n  warning: Haptics.AndroidHaptics.Reject,\n  error: Haptics.AndroidHaptics.Reject,\n};\n\nfunction perform(intent: HapticIntent): Promise<void> {\n  if (Platform.OS === 'android') {\n    return Haptics.performAndroidHapticsAsync(ANDROID_HAPTICS[intent]);\n  }\n\n  switch (intent) {\n    case 'selection':\n    case 'tick':\n      return Haptics.selectionAsync();\n    case 'impact-medium':\n      return Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);\n    case 'success':\n      return Haptics.notificationAsync(\n        Haptics.NotificationFeedbackType.Success\n      );\n    case 'warning':\n      return Haptics.notificationAsync(\n        Haptics.NotificationFeedbackType.Warning\n      );\n    case 'error':\n      return Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);\n    default:\n      // toggle-on, toggle-off, impact-light\n      return Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);\n  }\n}\n\n/**\n * Fire and forget. Never throws and never rejects.\n *\n * Haptics are decoration: a device with no taptic engine, a user who turned\n * touch feedback off, or a build where the native module is missing (which\n * makes expo-haptics reject with `UnavailabilityError`) must not be able to\n * take an `onPress` handler down with it.\n *\n * Call this on the JS thread. The expo module is bound to the JS runtime, so\n * invoking it from a Reanimated worklet running on the UI thread will throw —\n * reach for `runOnJS` at those call sites.\n */\nexport function triggerHaptic(intent: HapticIntent = 'impact-light'): void {\n  try {\n    perform(intent).catch(() => {});\n  } catch {\n    // Unreachable today, since every expo-haptics entry point is async. Kept so\n    // that a future version throwing synchronously cannot break a press either.\n  }\n}\n\n/**\n * Returns a stable trigger that no-ops while `enabled` is false — the shape\n * every component's `haptic` prop plugs into:\n *\n * ```tsx\n * const feedback = useHaptics(haptic);\n * feedback('toggle-on');\n * ```\n *\n * Its identity only changes when `enabled` does, so it is safe to list in a\n * `useCallback` dependency array and will not defeat a `React.memo` boundary.\n */\nexport function useHaptics(enabled: boolean = true) {\n  return useCallback(\n    (intent: HapticIntent = 'impact-light') => {\n      if (!enabled) return;\n      triggerHaptic(intent);\n    },\n    [enabled]\n  );\n}\n"},{"type":"registry:theme","path":"src/theme/globals.ts","target":"theme/globals.ts","content":"export const HEIGHT = 48;\nexport const FONT_SIZE = 17;\nexport const BORDER_RADIUS = 26;\nexport const CORNERS = 999;\n\nexport const SPACING = {\n  xs: 4,\n  sm: 8,\n  md: 16,\n  lg: 24,\n  xl: 32,\n};\n"},{"type":"registry:ui","path":"src/components/ui/text.tsx","target":"components/ui/text.tsx","content":"import { useColor } from '@/hooks/useColor';\nimport { FONT_SIZE } from '@/theme/globals';\nimport React, { forwardRef } from 'react';\nimport {\n  Text as RNText,\n  TextProps as RNTextProps,\n  TextStyle,\n} from 'react-native';\n\ntype TextVariant =\n  'body' | 'title' | 'subtitle' | 'caption' | 'heading' | 'link';\n\ninterface TextProps extends RNTextProps {\n  variant?: TextVariant;\n  lightColor?: string;\n  darkColor?: string;\n  children: React.ReactNode;\n}\n\nconst headingVariants: TextVariant[] = ['heading', 'title', 'subtitle'];\n\nexport const Text = React.memo(\n  forwardRef<RNText, TextProps>(\n    (\n      { variant = 'body', lightColor, darkColor, style, children, ...props },\n      ref\n    ) => {\n      const textColor = useColor('text', {\n        light: lightColor,\n        dark: darkColor,\n      });\n      const mutedColor = useColor('textMuted');\n      const defaultAccessibilityRole = headingVariants.includes(variant)\n        ? 'header'\n        : undefined;\n\n      const getTextStyle = (): TextStyle => {\n        const baseStyle: TextStyle = {\n          color: textColor,\n        };\n\n        switch (variant) {\n          case 'heading':\n            return {\n              ...baseStyle,\n              fontSize: 28,\n              fontWeight: '700',\n            };\n          case 'title':\n            return {\n              ...baseStyle,\n              fontSize: 24,\n              fontWeight: '700',\n            };\n          case 'subtitle':\n            return {\n              ...baseStyle,\n              fontSize: 19,\n              fontWeight: '600',\n            };\n          case 'caption':\n            return {\n              ...baseStyle,\n              fontSize: FONT_SIZE,\n              fontWeight: '400',\n              color: mutedColor,\n            };\n          case 'link':\n            return {\n              ...baseStyle,\n              fontSize: FONT_SIZE,\n              fontWeight: '500',\n              textDecorationLine: 'underline',\n            };\n          default: // 'body'\n            return {\n              ...baseStyle,\n              fontSize: FONT_SIZE,\n              fontWeight: '400',\n            };\n        }\n      };\n\n      return (\n        <RNText\n          ref={ref}\n          style={[getTextStyle(), style]}\n          accessibilityRole={defaultAccessibilityRole}\n          {...props}\n        >\n          {children}\n        </RNText>\n      );\n    }\n  )\n);\n\nText.displayName = 'Text';\n"},{"type":"registry:ui","path":"src/components/ui/view.tsx","target":"components/ui/view.tsx","content":"import { forwardRef, memo } from 'react';\nimport { View as RNView, type ViewProps } from 'react-native';\n\nexport const View = memo(\n  forwardRef<RNView, ViewProps>(({ style, ...otherProps }, ref) => {\n    return (\n      <RNView\n        ref={ref}\n        style={[{ backgroundColor: 'transparent' }, style]}\n        {...otherProps}\n      />\n    );\n  })\n);\n\nView.displayName = 'View';\n"},{"type":"registry:ui","path":"src/components/ui/icon.tsx","target":"components/ui/icon.tsx","content":"import { useColor } from '@/hooks/useColor';\nimport { LucideProps } from 'lucide-react-native';\nimport React from 'react';\n\nexport type Props = LucideProps & {\n  lightColor?: string;\n  darkColor?: string;\n  name: React.ComponentType<LucideProps>;\n};\n\nexport function Icon({\n  lightColor,\n  darkColor,\n  name: IconComponent,\n  color,\n  size = 24,\n  strokeWidth = 1.8,\n  accessible = false,\n  ...rest\n}: Props) {\n  const themedColor = useColor('icon', { light: lightColor, dark: darkColor });\n\n  // Use provided color prop if available, otherwise use themed color\n  const iconColor = color || themedColor;\n\n  return (\n    <IconComponent\n      color={iconColor}\n      size={size}\n      strokeWidth={strokeWidth}\n      strokeLinecap='round'\n      accessible={accessible}\n      {...rest}\n    />\n  );\n}\n"},{"type":"registry:ui","path":"src/components/ui/spinner.tsx","target":"components/ui/spinner.tsx","content":"import { Text } from '@/components/ui/text';\nimport { useColor } from '@/hooks/useColor';\nimport { BORDER_RADIUS, CORNERS, FONT_SIZE } from '@/theme/globals';\nimport { Loader2 } from 'lucide-react-native';\nimport React, { useEffect, useMemo } from 'react';\nimport { ActivityIndicator, StyleSheet, View, ViewStyle } from 'react-native';\nimport Animated, {\n  Easing,\n  SharedValue,\n  useAnimatedStyle,\n  useSharedValue,\n  withDelay,\n  withRepeat,\n  withSequence,\n  withTiming,\n} from 'react-native-reanimated';\n\n// Types\ntype SpinnerSize = 'default' | 'sm' | 'lg' | 'icon';\nexport type SpinnerVariant = 'default' | 'circle' | 'dots' | 'pulse' | 'bars';\n\ninterface SpinnerProps {\n  size?: SpinnerSize;\n  variant?: SpinnerVariant;\n  label?: string;\n  showLabel?: boolean;\n  style?: ViewStyle;\n  color?: string;\n  thickness?: number; // Only affects the 'circle' variant's stroke width\n  speed?: 'slow' | 'normal' | 'fast';\n}\n\ninterface LoadingOverlayProps extends SpinnerProps {\n  visible: boolean;\n  backdrop?: boolean;\n  backdropColor?: string;\n  backdropOpacity?: number;\n  onRequestClose?: () => void;\n}\n\ninterface SpinnerConfig {\n  size: number;\n  iconSize: number;\n  fontSize: number;\n  gap: number;\n  thickness: number;\n}\n\n// Configuration\nconst sizeConfig: Record<SpinnerSize, SpinnerConfig> = {\n  sm: { size: 16, iconSize: 16, fontSize: 12, gap: 6, thickness: 2 },\n  default: {\n    size: 24,\n    iconSize: 24,\n    fontSize: FONT_SIZE,\n    gap: 8,\n    thickness: 2,\n  },\n  lg: { size: 32, iconSize: 32, fontSize: 16, gap: 10, thickness: 3 },\n  icon: { size: 24, iconSize: 24, fontSize: FONT_SIZE, gap: 8, thickness: 2 },\n};\n\nconst speedConfig = {\n  slow: 1500,\n  normal: 1000,\n  fast: 500,\n};\n\n// --- Helper Animated Components for Dots and Bars ---\n\ninterface AnimatedShapeProps {\n  anim: SharedValue<number>;\n  color: string;\n  size: number;\n  style: ViewStyle;\n}\n\nconst AnimatedDot = React.memo(\n  ({ anim, color, size, style }: AnimatedShapeProps) => {\n    const animatedStyle = useAnimatedStyle(() => ({\n      opacity: anim.value,\n    }));\n    return (\n      <Animated.View\n        style={[\n          style,\n          { width: size, height: size, backgroundColor: color },\n          animatedStyle,\n        ]}\n      />\n    );\n  }\n);\n\nconst AnimatedBar = React.memo(\n  ({ anim, color, size, style }: AnimatedShapeProps) => {\n    const animatedStyle = useAnimatedStyle(() => ({\n      opacity: anim.value,\n    }));\n    return (\n      <Animated.View\n        style={[\n          style,\n          { width: size / 6, height: size, backgroundColor: color },\n          animatedStyle,\n        ]}\n      />\n    );\n  }\n);\n\n// Main Spinner Component\nexport function Spinner({\n  size = 'default',\n  variant = 'default',\n  label,\n  showLabel = false,\n  style,\n  color,\n  thickness,\n  speed = 'normal',\n}: SpinnerProps) {\n  // Reanimated shared values\n  const rotate = useSharedValue(0);\n  const pulse = useSharedValue(1);\n\n  // --- FIX: Call hooks at the top level ---\n  // 1. Call useSharedValue at the top level for each dot/bar\n  const dotAnim1 = useSharedValue(0.3);\n  const dotAnim2 = useSharedValue(0.3);\n  const dotAnim3 = useSharedValue(0.3);\n\n  const barAnim1 = useSharedValue(0.3);\n  const barAnim2 = useSharedValue(0.3);\n  const barAnim3 = useSharedValue(0.3);\n  const barAnim4 = useSharedValue(0.3);\n\n  // 2. Use useMemo to create a stable array reference from the values\n  const dotsAnims = useMemo(\n    () => [dotAnim1, dotAnim2, dotAnim3],\n    [dotAnim1, dotAnim2, dotAnim3]\n  );\n  const barsAnims = useMemo(\n    () => [barAnim1, barAnim2, barAnim3, barAnim4],\n    [barAnim1, barAnim2, barAnim3, barAnim4]\n  );\n  // --- END FIX ---\n\n  // Theme colors\n  const primaryColor = useColor('text');\n  const textColor = useColor('text');\n\n  const config = sizeConfig[size];\n  const spinnerColor = color || primaryColor;\n  const animationDuration = speedConfig[speed];\n\n  // Rotation animation\n  useEffect(() => {\n    if (variant === 'circle') {\n      rotate.value = withRepeat(\n        withTiming(360, { duration: animationDuration, easing: Easing.linear }),\n        -1\n      );\n    } else {\n      rotate.value = 0; // Reset\n    }\n  }, [rotate, variant, animationDuration]);\n\n  // Pulse animation\n  useEffect(() => {\n    if (variant === 'pulse') {\n      pulse.value = withRepeat(\n        withSequence(\n          withTiming(1.3, { duration: animationDuration / 2 }),\n          withTiming(1, { duration: animationDuration / 2 })\n        ),\n        -1,\n        true\n      );\n    } else {\n      pulse.value = 1; // Reset\n    }\n  }, [pulse, variant, animationDuration]);\n\n  // Dots animation\n  useEffect(() => {\n    if (variant === 'dots') {\n      dotsAnims.forEach((anim, index) => {\n        anim.value = withRepeat(\n          withSequence(\n            withDelay(\n              index * (animationDuration / 6),\n              withTiming(1, { duration: animationDuration / 3 })\n            ),\n            withTiming(0.3, { duration: animationDuration / 3 })\n          ),\n          -1\n        );\n      });\n    } else {\n      dotsAnims.forEach((anim) => (anim.value = 0.3)); // Reset\n    }\n  }, [dotsAnims, variant, animationDuration]);\n\n  // Bars animation\n  useEffect(() => {\n    if (variant === 'bars') {\n      barsAnims.forEach((anim, index) => {\n        anim.value = withRepeat(\n          withSequence(\n            withDelay(\n              index * (animationDuration / 8),\n              withTiming(1, { duration: animationDuration / 4 })\n            ),\n            withTiming(0.3, { duration: animationDuration / 4 })\n          ),\n          -1\n        );\n      });\n    } else {\n      barsAnims.forEach((anim) => (anim.value = 0.3)); // Reset\n    }\n  }, [barsAnims, variant, animationDuration]);\n\n  // Animated styles\n  const animatedCircleStyle = useAnimatedStyle(() => ({\n    transform: [{ rotate: `${rotate.value}deg` }],\n  }));\n\n  const animatedPulseStyle = useAnimatedStyle(() => ({\n    transform: [{ scale: pulse.value }],\n  }));\n\n  const renderSpinner = () => {\n    switch (variant) {\n      case 'default':\n        return (\n          <ActivityIndicator\n            size={config.size}\n            color={spinnerColor}\n            style={styles.spinner}\n          />\n        );\n\n      case 'circle':\n        return (\n          <Animated.View\n            style={[\n              styles.customSpinner,\n              { width: config.size, height: config.size },\n              animatedCircleStyle,\n            ]}\n          >\n            <Loader2\n              size={config.iconSize}\n              color={spinnerColor}\n              strokeWidth={thickness ?? config.thickness}\n            />\n          </Animated.View>\n        );\n\n      case 'pulse':\n        return (\n          <Animated.View\n            style={[\n              styles.pulseSpinner,\n              {\n                width: config.size,\n                height: config.size,\n                backgroundColor: spinnerColor,\n              },\n              animatedPulseStyle,\n            ]}\n          />\n        );\n\n      case 'dots':\n        return (\n          <View style={[styles.dotsContainer, { gap: config.size / 4 }]}>\n            {dotsAnims.map((anim, index) => (\n              <AnimatedDot\n                key={index}\n                anim={anim}\n                color={spinnerColor}\n                size={config.size / 3}\n                style={styles.dot}\n              />\n            ))}\n          </View>\n        );\n\n      case 'bars':\n        return (\n          <View style={[styles.barsContainer, { gap: config.size / 6 }]}>\n            {barsAnims.map((anim, index) => (\n              <AnimatedBar\n                key={index}\n                anim={anim}\n                color={spinnerColor}\n                size={config.size}\n                style={styles.bar}\n              />\n            ))}\n          </View>\n        );\n\n      default:\n        return null;\n    }\n  };\n\n  const containerStyle: ViewStyle = {\n    alignItems: 'center',\n    justifyContent: 'center',\n    gap: config.gap,\n  };\n\n  return (\n    <View\n      style={[containerStyle, style]}\n      accessibilityRole='progressbar'\n      accessibilityLabel={label || 'Loading'}\n    >\n      {renderSpinner()}\n      {(showLabel || label) && (\n        <Text\n          style={[\n            styles.label,\n            {\n              color: textColor,\n              fontSize: config.fontSize,\n            },\n          ]}\n        >\n          {label || 'Loading...'}\n        </Text>\n      )}\n    </View>\n  );\n}\n\n// Loading Overlay Component\nexport function LoadingOverlay({\n  visible,\n  backdrop = true,\n  backdropColor,\n  backdropOpacity = 0.5,\n  ...spinnerProps\n}: LoadingOverlayProps) {\n  const opacity = useSharedValue(0);\n  const backgroundColor = useColor('background');\n  const cardColor = useColor('card');\n\n  useEffect(() => {\n    opacity.value = withTiming(visible ? 1 : 0, {\n      duration: 200,\n    });\n  }, [visible, opacity]);\n\n  const animatedOverlayStyle = useAnimatedStyle(() => ({\n    opacity: opacity.value,\n    // Conditionally render to avoid interaction issues\n    display: opacity.value === 0 ? 'none' : 'flex',\n  }));\n\n  const defaultBackdropColor =\n    backdropColor ||\n    `${backgroundColor}${Math.round(backdropOpacity * 255)\n      .toString(16)\n      .padStart(2, '0')}`;\n\n  return (\n    <Animated.View\n      style={[\n        styles.overlay,\n        { backgroundColor: backdrop ? defaultBackdropColor : 'transparent' },\n        animatedOverlayStyle,\n      ]}\n      pointerEvents={visible ? 'auto' : 'none'}\n    >\n      <View style={[styles.overlayContent, { backgroundColor: cardColor }]}>\n        <Spinner {...spinnerProps} />\n      </View>\n    </Animated.View>\n  );\n}\n\n// Inline Loader Component (for buttons, etc.)\nexport function InlineLoader({\n  size = 'sm',\n  variant = 'default',\n  color,\n}: Omit<SpinnerProps, 'label' | 'showLabel'>) {\n  return (\n    <Spinner\n      size={size}\n      variant={variant}\n      color={color}\n      style={styles.inlineLoader}\n    />\n  );\n}\n\n// Button Spinner Component - optimized for button usage\nexport function ButtonSpinner({\n  size = 'sm',\n  variant = 'default',\n  color,\n}: Omit<SpinnerProps, 'label' | 'showLabel'>) {\n  const primaryForegroundColor = useColor('primaryForeground');\n\n  return (\n    <Spinner\n      size={size}\n      variant={variant}\n      color={color || primaryForegroundColor}\n      style={styles.buttonSpinner}\n    />\n  );\n}\n\nconst styles = StyleSheet.create({\n  spinner: {\n    alignSelf: 'center',\n  },\n  customSpinner: {\n    alignItems: 'center',\n    justifyContent: 'center',\n  },\n  pulseSpinner: {\n    borderRadius: 999,\n  },\n  dotsContainer: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    justifyContent: 'center',\n  },\n  dot: {\n    borderRadius: 999,\n  },\n  barsContainer: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    justifyContent: 'center',\n  },\n  bar: {\n    borderRadius: CORNERS,\n  },\n  label: {\n    textAlign: 'center',\n    fontWeight: '500',\n  },\n  overlay: {\n    ...StyleSheet.absoluteFill,\n    alignItems: 'center',\n    justifyContent: 'center',\n    zIndex: 9999,\n  },\n  overlayContent: {\n    padding: 60,\n    borderRadius: BORDER_RADIUS,\n  },\n  inlineLoader: {\n    minHeight: 0,\n    minWidth: 0,\n  },\n  buttonSpinner: {\n    minHeight: 0,\n    minWidth: 0,\n  },\n});\n"},{"type":"registry:ui","path":"src/components/ui/button.tsx","target":"components/ui/button.tsx","content":"import { Icon } from '@/components/ui/icon';\nimport { ButtonSpinner, SpinnerVariant } from '@/components/ui/spinner';\nimport { Text } from '@/components/ui/text';\nimport { useColor } from '@/hooks/useColor';\nimport { useHaptics } from '@/hooks/useHaptics';\nimport { CORNERS, FONT_SIZE, HEIGHT } from '@/theme/globals';\nimport { LucideProps } from 'lucide-react-native';\nimport { forwardRef } from 'react';\nimport {\n  Pressable,\n  TextStyle,\n  TouchableOpacity,\n  TouchableOpacityProps,\n  View,\n  ViewStyle,\n} from 'react-native';\nimport Animated, {\n  useAnimatedStyle,\n  useSharedValue,\n  withSpring,\n} from 'react-native-reanimated';\n\nexport type ButtonVariant =\n  | 'default'\n  | 'destructive'\n  | 'success'\n  | 'outline'\n  | 'secondary'\n  | 'ghost'\n  | 'link';\n\nexport type ButtonSize = 'default' | 'sm' | 'lg' | 'icon';\n\nexport interface ButtonProps extends Omit<TouchableOpacityProps, 'style'> {\n  label?: string;\n  children?: React.ReactNode;\n  animation?: boolean;\n  haptic?: boolean;\n  icon?: React.ComponentType<LucideProps>;\n  onPress?: () => void;\n  variant?: ButtonVariant;\n  size?: ButtonSize;\n  disabled?: boolean;\n  loading?: boolean;\n  loadingVariant?: SpinnerVariant;\n  style?: ViewStyle | ViewStyle[];\n  textStyle?: TextStyle;\n}\n\nexport const Button = forwardRef<View, ButtonProps>(\n  (\n    {\n      children,\n      icon,\n      onPress,\n      variant = 'default',\n      size = 'default',\n      disabled = false,\n      loading = false,\n      animation = true,\n      haptic = true,\n      loadingVariant = 'default',\n      style,\n      textStyle,\n      label,\n      ...props\n    },\n    ref\n  ) => {\n    const feedback = useHaptics(haptic);\n    const primaryColor = useColor('primary');\n    const primaryForegroundColor = useColor('primaryForeground');\n    const secondaryColor = useColor('secondary');\n    const secondaryForegroundColor = useColor('secondaryForeground');\n    const destructiveColor = useColor('red');\n    const destructiveForegroundColor = useColor('destructiveForeground');\n    const greenColor = useColor('green');\n    const borderColor = useColor('border');\n\n    // Animation values for liquid glass effect\n    const scale = useSharedValue(1);\n    const brightness = useSharedValue(1);\n\n    const getButtonStyle = (): ViewStyle => {\n      const baseStyle: ViewStyle = {\n        borderRadius: CORNERS,\n        flexDirection: 'row',\n        alignItems: 'center',\n        justifyContent: 'center',\n      };\n\n      // Size variants\n      switch (size) {\n        case 'sm':\n          Object.assign(baseStyle, { height: 44, paddingHorizontal: 24 });\n          break;\n        case 'lg':\n          Object.assign(baseStyle, { height: 54, paddingHorizontal: 36 });\n          break;\n        case 'icon':\n          Object.assign(baseStyle, {\n            height: HEIGHT,\n            width: HEIGHT,\n            paddingHorizontal: 0,\n          });\n          break;\n        default:\n          Object.assign(baseStyle, { height: HEIGHT, paddingHorizontal: 32 });\n      }\n\n      // Variant styles\n      switch (variant) {\n        case 'destructive':\n          return { ...baseStyle, backgroundColor: destructiveColor };\n        case 'success':\n          return { ...baseStyle, backgroundColor: greenColor };\n        case 'outline':\n          return {\n            ...baseStyle,\n            backgroundColor: 'transparent',\n            borderWidth: 1,\n            borderColor,\n          };\n        case 'secondary':\n          return { ...baseStyle, backgroundColor: secondaryColor };\n        case 'ghost':\n          return { ...baseStyle, backgroundColor: 'transparent' };\n        case 'link':\n          return {\n            ...baseStyle,\n            backgroundColor: 'transparent',\n            height: 'auto',\n            paddingHorizontal: 0,\n          };\n        default:\n          return { ...baseStyle, backgroundColor: primaryColor };\n      }\n    };\n\n    const getButtonTextStyle = (): TextStyle => {\n      const baseTextStyle: TextStyle = {\n        fontSize: FONT_SIZE,\n        fontWeight: '500',\n      };\n\n      switch (variant) {\n        case 'destructive':\n          return { ...baseTextStyle, color: destructiveForegroundColor };\n        case 'success':\n          return { ...baseTextStyle, color: destructiveForegroundColor };\n        case 'outline':\n          return { ...baseTextStyle, color: primaryColor };\n        case 'secondary':\n          return { ...baseTextStyle, color: secondaryForegroundColor };\n        case 'ghost':\n          return { ...baseTextStyle, color: primaryColor };\n        case 'link':\n          return {\n            ...baseTextStyle,\n            color: primaryColor,\n            textDecorationLine: 'underline',\n          };\n        default:\n          return { ...baseTextStyle, color: primaryForegroundColor };\n      }\n    };\n\n    const getColor = (): string => {\n      switch (variant) {\n        case 'destructive':\n          return destructiveForegroundColor;\n        case 'success':\n          return destructiveForegroundColor;\n        case 'outline':\n          return primaryColor;\n        case 'secondary':\n          return secondaryForegroundColor;\n        case 'ghost':\n          return primaryColor;\n        case 'link':\n          return primaryColor;\n        default:\n          return primaryForegroundColor;\n      }\n    };\n\n    // Helper function to get icon size based on button size\n    const getIconSize = (): number => {\n      switch (size) {\n        case 'sm':\n          return 16;\n        case 'lg':\n          return 24;\n        case 'icon':\n          return 20;\n        default:\n          return 18;\n      }\n    };\n\n    // Trigger haptic feedback\n    const triggerHapticFeedback = () => {\n      if (!disabled && !loading) {\n        feedback('impact-light');\n      }\n    };\n\n    // Improved animation handlers for liquid glass effect.\n    // These are deliberately not worklets: Pressable dispatches them on the JS\n    // thread, and both the haptic call and `props.onPressIn` are JS-only.\n    // Writing to a shared value from JS is fine — Reanimated still runs the\n    // spring on the UI thread.\n    const handlePressIn = (ev?: any) => {\n      // Trigger haptic feedback\n      triggerHapticFeedback();\n\n      // Scale up with bouncy spring animation\n      scale.value = withSpring(1.05, {\n        damping: 15,\n        stiffness: 400,\n        mass: 0.5,\n      });\n\n      // Slight brightness increase for glass effect\n      brightness.value = withSpring(1.1, {\n        damping: 20,\n        stiffness: 300,\n      });\n\n      // Call original onPressIn if provided\n      props.onPressIn?.(ev);\n    };\n\n    const handlePressOut = (ev?: any) => {\n      // Return to original size with smooth spring\n      scale.value = withSpring(1, {\n        damping: 20,\n        stiffness: 400,\n        mass: 0.8,\n        overshootClamping: false,\n      });\n\n      // Return brightness to normal\n      brightness.value = withSpring(1, {\n        damping: 20,\n        stiffness: 300,\n      });\n\n      // Call original onPressOut if provided\n      props.onPressOut?.(ev);\n    };\n\n    // Handle actual press action\n    const handlePress = () => {\n      if (onPress && !disabled && !loading) {\n        onPress();\n      }\n    };\n\n    // Handle press for TouchableOpacity (non-animated version)\n    const handleTouchablePress = () => {\n      triggerHapticFeedback();\n      handlePress();\n    };\n\n    // Animated styles using useAnimatedStyle\n    const animatedStyle = useAnimatedStyle(() => {\n      return {\n        transform: [{ scale: scale.value }],\n        opacity: brightness.value * (disabled ? 0.5 : 1),\n      };\n    });\n\n    // Extract flex value from style prop\n    const getFlexFromStyle = () => {\n      if (!style) return null;\n\n      const styleArray = Array.isArray(style) ? style : [style];\n\n      // Find the last occurrence of flex (in case of multiple styles with flex)\n      for (let i = styleArray.length - 1; i >= 0; i--) {\n        const s = styleArray[i];\n        if (s && typeof s === 'object' && 'flex' in s) {\n          return s.flex;\n        }\n      }\n      return null;\n    };\n\n    // Alternative simpler solution - replace flex with alignSelf\n    const getPressableStyle = (): ViewStyle => {\n      const flexValue = getFlexFromStyle();\n      // If flex: 1 is applied, use alignSelf: 'stretch' instead to only affect width\n      return flexValue === 1\n        ? {\n            flex: 1,\n            alignSelf: 'stretch',\n          }\n        : flexValue !== null\n          ? {\n              flex: flexValue,\n              maxHeight: size === 'sm' ? 44 : size === 'lg' ? 54 : HEIGHT,\n            }\n          : {};\n    };\n\n    // Updated getStyleWithoutFlex function\n    const getStyleWithoutFlex = () => {\n      if (!style) return style;\n\n      const styleArray = Array.isArray(style) ? style : [style];\n      return styleArray.map((s) => {\n        if (s && typeof s === 'object' && 'flex' in s) {\n          const { flex, ...restStyle } = s;\n          return restStyle;\n        }\n        return s;\n      });\n    };\n\n    const buttonStyle = getButtonStyle();\n    const finalTextStyle = getButtonTextStyle();\n    const contentColor = getColor();\n    const iconSize = getIconSize();\n    const styleWithoutFlex = getStyleWithoutFlex();\n\n    return animation ? (\n      <Pressable\n        ref={ref}\n        onPress={handlePress}\n        onPressIn={handlePressIn}\n        onPressOut={handlePressOut}\n        disabled={disabled || loading}\n        style={getPressableStyle()}\n        accessibilityRole='button'\n        accessibilityState={{ busy: loading, disabled: disabled || loading }}\n        accessibilityLabel={label}\n        {...props}\n      >\n        <Animated.View style={[animatedStyle, buttonStyle, styleWithoutFlex]}>\n          {loading ? (\n            <ButtonSpinner\n              size={size}\n              variant={loadingVariant}\n              color={contentColor}\n            />\n          ) : typeof children === 'string' ? (\n            <View\n              style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}\n            >\n              {icon && (\n                <Icon name={icon} color={contentColor} size={iconSize} />\n              )}\n              <Text style={[finalTextStyle, textStyle]}>{children}</Text>\n            </View>\n          ) : (\n            <View\n              style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}\n            >\n              {icon && (\n                <Icon name={icon} color={contentColor} size={iconSize} />\n              )}\n              {children}\n            </View>\n          )}\n        </Animated.View>\n      </Pressable>\n    ) : (\n      <TouchableOpacity\n        ref={ref}\n        style={[buttonStyle, disabled && { opacity: 0.5 }, styleWithoutFlex]}\n        onPress={handleTouchablePress}\n        disabled={disabled || loading}\n        activeOpacity={0.8}\n        accessibilityRole='button'\n        accessibilityState={{ busy: loading, disabled: disabled || loading }}\n        accessibilityLabel={label}\n        {...props}\n      >\n        {loading ? (\n          <ButtonSpinner\n            size={size}\n            variant={loadingVariant}\n            color={contentColor}\n          />\n        ) : typeof children === 'string' ? (\n          <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>\n            {icon && <Icon name={icon} color={contentColor} size={iconSize} />}\n            <Text style={[finalTextStyle, textStyle]}>{children}</Text>\n          </View>\n        ) : (\n          children\n        )}\n      </TouchableOpacity>\n    );\n  }\n);\n\n// Add display name for better debugging\nButton.displayName = 'Button';\n"}],"meta":{"name":"button","types":[{"name":"Button","description":"A pressable button component with multiple variants and states.","props":[{"name":"children","type":"ReactNode","description":"The button content (text or custom elements)."},{"name":"onPress","type":"() => void","description":"Function called when the button is pressed."},{"name":"variant","type":"ButtonVariant","default":"`'default'`","description":"The visual style variant of the button."},{"name":"size","type":"ButtonSize","default":"`'default'`","description":"The size of the button."},{"name":"disabled","type":"boolean","default":"`false`","description":"Whether the button is disabled."},{"name":"loading","type":"boolean","default":"`false`","description":"Whether the button is in loading state."},{"name":"loadingVariant","type":"SpinnerVariant","default":"`'default'`","description":"The variant of the loading spinner."},{"name":"animation","type":"boolean","default":"`true`","description":"Whether to enable press animations."},{"name":"haptic","type":"boolean","default":"`true`","description":"Whether to trigger haptic feedback on press (iOS and Android)."},{"name":"icon","type":"ComponentType<LucideProps>","description":"Icon component to display before the text."},{"name":"label","type":"string","description":"Fallback `accessibilityLabel` for screen readers, useful when `children` is not plain text (e.g. an icon-only button)."},{"name":"style","type":"ViewStyle | ViewStyle[]","description":"Additional styles for the button container."},{"name":"textStyle","type":"TextStyle","description":"Additional styles for the button text."}]}],"variants":[{"name":"ButtonVariant","description":"The available button variants:","values":[{"value":"default","description":"Primary button with solid background"},{"value":"destructive","description":"Red button for destructive actions"},{"value":"success","description":"Green button for success actions"},{"value":"outline","description":"Button with border and transparent background"},{"value":"secondary","description":"Secondary button with muted colors"},{"value":"ghost","description":"Button with no background"},{"value":"link","description":"Text-only button with underline"}]},{"name":"ButtonSize","description":"The available button sizes:","values":[{"value":"default","description":"Standard button size (48px height)"},{"value":"sm","description":"Small button size (44px height)"},{"value":"lg","description":"Large button size (54px height)"},{"value":"icon","description":"Square button for icons only (48x48px)"}]}],"usage":{"import":"import { Button } from '@/components/ui/button';","snippet":"<Button onPress={() => console.log('Button pressed!')}>Click me</Button>"},"accessibility":{"summary":"The Button component is built with accessibility in mind:","items":["Proper touch target size (minimum 44px)","Disabled state prevents interaction and reduces opacity","Loading state shows spinner with appropriate color contrast","Supports screen readers with proper accessibility labels","Responsive to system accessibility settings"]}}}