{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "select",
  "title": "Select",
  "description": "A component that allows users to select a value from a list of options.",
  "dependencies": [
    "react-native-reanimated"
  ],
  "registryDependencies": [
    "@tetra-ui/action-input",
    "@tetra-ui/bottom-sheet",
    "@tetra-ui/icons",
    "@tetra-ui/input",
    "@tetra-ui/popover",
    "@tetra-ui/slot",
    "@tetra-ui/text"
  ],
  "files": [
    {
      "path": "ui/select.tsx",
      "content": "import {\n  createContext,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\nimport {\n  type GestureResponderEvent,\n  Pressable,\n  type PressableProps,\n} from \"react-native\";\nimport Animated, {\n  Easing,\n  interpolate,\n  useAnimatedStyle,\n  useSharedValue,\n  withTiming,\n} from \"react-native-reanimated\";\nimport { cn, mergeRefs } from \"@/lib/utils\";\nimport { ActionInput } from \"./action-input\";\nimport {\n  BottomSheet,\n  BottomSheetBody,\n  BottomSheetContent,\n  BottomSheetFooter,\n  BottomSheetHeader,\n  BottomSheetTitle,\n} from \"./bottom-sheet\";\nimport { CheckIcon, ChevronDownIcon } from \"./icons\";\nimport { InputAddon, type InputAddonChild, InputAddonIcon } from \"./input\";\nimport {\n  Popover,\n  PopoverContent,\n  PopoverOverlay,\n  PopoverPortal,\n  usePopover,\n} from \"./popover\";\nimport { Slot } from \"./slot\";\nimport { Text } from \"./text\";\n\n// Constants\nconst ANIMATION_DURATION = 280;\nconst ANIMATION_EASING = Easing.out(Easing.cubic);\n\n// Types\ntype LayoutPosition = {\n  pageX: number;\n  pageY: number;\n  width: number;\n  height: number;\n};\n\ntype SelectValueType = number | string;\n\ntype SelectOption<T> = {\n  value: T;\n  label: string;\n};\n\ntype SelectContextProps<T> = {\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n  options: SelectOption<T>[];\n  value?: T;\n  onValueChange: (value: T) => void;\n  selectedValue?: T;\n  setSelectedValue: (value: T) => void;\n  onConfirm: (value?: T) => void;\n  onCancel: () => void;\n  disabled?: boolean;\n  invalid?: boolean;\n  placeholder: string;\n  popoverTriggerPosition?: LayoutPosition;\n  setPopoverTriggerPosition: (position?: LayoutPosition) => void;\n};\n\ntype SelectItemContextProps<T> = {\n  value: T;\n};\n\ntype SelectProps<T extends SelectValueType> = {\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  options: SelectOption<T>[];\n  value?: T;\n  onValueChange?: (value: T) => void;\n  disabled?: boolean;\n  invalid?: boolean;\n  placeholder?: string;\n  children: React.ReactNode;\n};\n\ntype SelectTriggerProps = React.ComponentPropsWithRef<typeof Pressable> & {\n  asChild?: boolean;\n};\n\ntype SelectItemProps<T> = SelectOption<T> & Omit<PressableProps, \"onPress\">;\n\ntype SelectContentProps<T> = {\n  children:\n    | React.ReactElement<SelectItemProps<T>>\n    | React.ReactElement<SelectItemProps<T>>[];\n};\n\ntype SelectContentPopoverProps<T> = React.ComponentProps<\n  typeof PopoverContent\n> &\n  SelectContentProps<T>;\n\ntype SelectContentSheetConfirmProps = PressableProps & {\n  asChild?: boolean;\n};\n\n// Context\nconst SelectContext = createContext<SelectContextProps<SelectValueType> | null>(\n  null\n);\n\nconst useSelect = () => {\n  const context = useContext(SelectContext);\n  if (!context) {\n    throw new Error(\"useSelect must be used within a Select\");\n  }\n  return context;\n};\n\nconst SelectPopoverContext = createContext<boolean | null>(null);\n\nconst useSelectPopover = () => {\n  const context = useContext(SelectPopoverContext);\n  return context ?? false;\n};\n\nconst SelectItemContext =\n  createContext<SelectItemContextProps<SelectValueType> | null>(null);\n\nconst useSelectItem = () => {\n  const context = useContext(SelectItemContext);\n  if (!context) {\n    throw new Error(\"useSelectItem must be used within a SelectItem\");\n  }\n  return context;\n};\n\n// Components\nexport const Select = <T extends SelectValueType>({\n  open: openProp,\n  onOpenChange: onOpenChangeProp,\n  value: valueProp,\n  onValueChange: onValueChangeProp,\n  disabled,\n  invalid,\n  options,\n  placeholder = \"Select...\",\n  children,\n}: SelectProps<T>) => {\n  const [internalOpen, setInternalOpen] = useState(openProp ?? false);\n  const [internalValue, setInternalValue] = useState<T>();\n  const [selectedValue, setSelectedValue] = useState<T>();\n  const [popoverTriggerPosition, setPopoverTriggerPosition] =\n    useState<LayoutPosition>();\n\n  const isOpenControlled = openProp !== undefined;\n  const open = isOpenControlled ? openProp : internalOpen;\n\n  const isValueControlled = valueProp !== undefined;\n  const value = isValueControlled ? valueProp : internalValue;\n\n  useEffect(() => {\n    setSelectedValue(value);\n  }, [value]);\n\n  const onOpenChange = useCallback(\n    (nextOpen: boolean) => {\n      setInternalOpen(nextOpen);\n      onOpenChangeProp?.(nextOpen);\n    },\n    [onOpenChangeProp]\n  );\n\n  const onValueChange = useCallback(\n    (nextValue: T) => {\n      setInternalValue(nextValue);\n      onValueChangeProp?.(nextValue);\n    },\n    [onValueChangeProp]\n  );\n\n  const onConfirm = useCallback(\n    (nextValue?: T) => {\n      const finalValue = nextValue ?? selectedValue;\n\n      if (typeof finalValue !== \"undefined\") {\n        onValueChange(finalValue);\n      }\n\n      onOpenChange(false);\n    },\n    [onOpenChange, onValueChange, selectedValue]\n  );\n\n  const onCancel = useCallback(() => {\n    setSelectedValue(value);\n    onOpenChange(false);\n  }, [onOpenChange, value]);\n\n  const ctx = useMemo(\n    () => ({\n      disabled,\n      invalid,\n      onCancel,\n      onConfirm,\n      onOpenChange,\n      onValueChange,\n      open,\n      options,\n      placeholder,\n      popoverTriggerPosition,\n      selectedValue,\n      setPopoverTriggerPosition,\n      setSelectedValue,\n      value,\n    }),\n    [\n      open,\n      disabled,\n      value,\n      options,\n      selectedValue,\n      invalid,\n      placeholder,\n      onConfirm,\n      onCancel,\n      onOpenChange,\n      onValueChange,\n      popoverTriggerPosition,\n    ]\n  );\n\n  return (\n    <SelectContext.Provider\n      value={ctx as SelectContextProps<T | SelectValueType>}\n    >\n      {children}\n    </SelectContext.Provider>\n  );\n};\n\nexport const SelectTrigger = ({\n  asChild,\n  ref: refProp,\n  onPress: onPressProp,\n  ...props\n}: SelectTriggerProps) => {\n  const { disabled, open, onOpenChange, setPopoverTriggerPosition } =\n    useSelect();\n\n  const ref = useRef<React.ComponentRef<typeof Pressable>>(null);\n\n  const mergedRefs = mergeRefs(ref, refProp);\n\n  const handlePress = useCallback(\n    (e: GestureResponderEvent) => {\n      onPressProp?.(e);\n\n      ref.current?.measure((_x, _y, width, height, pageX, pageY) => {\n        setPopoverTriggerPosition({\n          height,\n          pageX,\n          pageY,\n          width,\n        });\n\n        onOpenChange(!open);\n      });\n    },\n    [onOpenChange, open, onPressProp, setPopoverTriggerPosition]\n  );\n\n  const Comp = asChild ? Slot.Pressable : Pressable;\n\n  return (\n    <Comp\n      {...props}\n      disabled={disabled}\n      onPress={handlePress}\n      ref={mergedRefs}\n    />\n  );\n};\n\nexport const SelectInput = ({\n  children,\n  ...props\n}: Partial<React.ComponentProps<typeof ActionInput>>) => {\n  const { open, placeholder, options, value, invalid, disabled } = useSelect();\n\n  const openSharedValue = useSharedValue(open ? 1 : 0);\n\n  useEffect(() => {\n    openSharedValue.value = withTiming(open ? 1 : 0, {\n      duration: ANIMATION_DURATION,\n      easing: ANIMATION_EASING,\n    });\n  }, [open, openSharedValue]);\n\n  const animatedStyle = useAnimatedStyle(() => {\n    const rotate = interpolate(openSharedValue.value, [0, 1], [0, 180]);\n    return {\n      transform: [{ rotate: `${rotate}deg` }],\n    };\n  });\n\n  const valueLabel = useMemo(() => {\n    return options.find((option) => option.value === value)?.label;\n  }, [options, value]);\n\n  return (\n    <ActionInput\n      {...props}\n      disabled={disabled}\n      focused={open}\n      invalid={invalid}\n      placeholder={placeholder}\n      value={valueLabel}\n    >\n      {children as InputAddonChild}\n\n      <InputAddon align=\"inline-end\">\n        <Animated.View style={animatedStyle}>\n          <InputAddonIcon>\n            <ChevronDownIcon />\n          </InputAddonIcon>\n        </Animated.View>\n      </InputAddon>\n    </ActionInput>\n  );\n};\n\nexport const SelectItemLabel = (props: React.ComponentProps<typeof Text>) => {\n  return (\n    <Text className=\"flex-1 font-medium text-base text-foreground\" {...props} />\n  );\n};\n\nexport const SelectItemIndicator = ({\n  className,\n  ...props\n}: React.ComponentProps<typeof CheckIcon>) => {\n  const { value: itemValue } = useSelectItem();\n  const { selectedValue } = useSelect();\n\n  if (itemValue !== selectedValue) {\n    return null;\n  }\n\n  return (\n    <CheckIcon className={cn(\"size-5 text-primary\", className)} {...props} />\n  );\n};\n\nexport const SelectItem = <T extends SelectValueType>({\n  value,\n  label,\n  children,\n  className,\n  ...props\n}: SelectItemProps<T>) => {\n  const { setSelectedValue, onConfirm } = useSelect();\n  const isSelectPopover = useSelectPopover();\n\n  const onPress = useCallback(() => {\n    setSelectedValue(value);\n\n    if (isSelectPopover) {\n      onConfirm(value);\n    }\n  }, [setSelectedValue, onConfirm, value, isSelectPopover]);\n\n  return (\n    <SelectItemContext.Provider value={{ value }}>\n      <Pressable\n        className={cn(\n          \"flex flex-row items-center rounded-lg px-2 py-2 active:bg-accent/90 dark:active:bg-accent/50\",\n          className\n        )}\n        onPress={onPress}\n        {...props}\n      >\n        {children ?? (\n          <>\n            <SelectItemLabel>{label}</SelectItemLabel>\n\n            <SelectItemIndicator />\n          </>\n        )}\n      </Pressable>\n    </SelectItemContext.Provider>\n  );\n};\n\nexport const SelectContentSheet = ({\n  children,\n}: SelectContentProps<SelectValueType>) => {\n  const ctx = useSelect();\n\n  return (\n    <BottomSheet onOpenChange={ctx.onCancel} open={ctx.open}>\n      <SelectContext.Provider value={ctx}>\n        <BottomSheetContent>{children}</BottomSheetContent>\n      </SelectContext.Provider>\n    </BottomSheet>\n  );\n};\n\nexport const SelectContentSheetHeader = BottomSheetHeader;\nexport const SelectContentSheetTitle = BottomSheetTitle;\nexport const SelectContentSheetBody = BottomSheetBody;\nexport const SelectContentSheetFooter = BottomSheetFooter;\n\nexport const SelectContentSheetConfirm = ({\n  asChild,\n  ...props\n}: SelectContentSheetConfirmProps) => {\n  const { onConfirm } = useSelect();\n\n  const onPress = useCallback(() => {\n    onConfirm();\n  }, [onConfirm]);\n\n  const Comp = asChild ? Slot.Pressable : Pressable;\n\n  return <Comp {...props} onPress={onPress} />;\n};\n\nconst SelectContentPopoverAnchor = () => {\n  const { popoverTriggerPosition } = useSelect();\n  const { setTriggerPosition } = usePopover();\n\n  useEffect(() => {\n    if (popoverTriggerPosition) {\n      setTriggerPosition(popoverTriggerPosition);\n    }\n\n    return () => {\n      setTriggerPosition(undefined);\n    };\n  }, [popoverTriggerPosition, setTriggerPosition]);\n\n  return null;\n};\n\nexport const SelectContentPopover = ({\n  width = \"trigger\",\n  ...props\n}: SelectContentPopoverProps<SelectValueType>) => {\n  const ctx = useSelect();\n\n  return (\n    <Popover onOpenChange={ctx.onOpenChange} open={ctx.open}>\n      <SelectContentPopoverAnchor />\n      <PopoverPortal>\n        <SelectPopoverContext.Provider value={true}>\n          <SelectContext.Provider value={ctx}>\n            <PopoverOverlay className=\"bg-transparent\" />\n            <PopoverContent {...props} width={width} />\n          </SelectContext.Provider>\n        </SelectPopoverContext.Provider>\n      </PopoverPortal>\n    </Popover>\n  );\n};\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}
