{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "glass-button",
  "title": "Glass Button",
  "description": "A glass button that sets down under a press and lights up from the point you press.",
  "registryDependencies": [
    "@glassyui-native/glass-provider",
    "@glassyui-native/glass-surface"
  ],
  "files": [
    {
      "path": "registry/glassyui-native/glass-button.tsx",
      "content": "import { Children, forwardRef, isValidElement, useCallback, useState, type ReactNode } from 'react';\nimport {\n  Animated,\n  Easing,\n  Pressable,\n  StyleSheet,\n  Text,\n  View,\n  type GestureResponderEvent,\n  type LayoutChangeEvent,\n  type PressableProps,\n  type StyleProp,\n  type ViewStyle,\n} from 'react-native';\nimport { useGlassTheme } from './glass-provider';\nimport { GlassSurface } from './glass-surface';\nimport { GLASS_MOTION, GLASS_RADIUS } from './lib/tokens';\n\nexport type ButtonVariant = 'primary' | 'prominent' | 'ghost' | 'soft';\n\nexport type ButtonSize = 'sm' | 'md' | 'lg';\n\nexport type GlassButtonProps = Omit<PressableProps, 'children' | 'style'> & {\n  children?: ReactNode;\n  size?: ButtonSize;\n  style?: StyleProp<ViewStyle>;\n  variant?: ButtonVariant;\n};\n\nconst SIZE_STYLE = {\n  sm: { gap: 6, minHeight: 32, paddingHorizontal: 13 },\n  md: { gap: 8, minHeight: 44, paddingHorizontal: 22 },\n  lg: { gap: 10, minHeight: 52, paddingHorizontal: 32 },\n} as const;\n\nconst LABEL_SIZE = { sm: 13, md: 15, lg: 17 } as const;\n\n// A small button still answers a 44-point touch.\nconst HIT_SLOP = { sm: 6, md: 0, lg: 0 } as const;\n\n/**\n * A glass button that rests as a slab just above the page. A press sets it down, its shadow\n * drawing in, and lights the glass from the point it lands. Reduce Motion keeps the pressed\n * state and drops the movement.\n */\nexport const GlassButton = forwardRef<View, GlassButtonProps>(\n  (\n    {\n      children,\n      disabled = false,\n      onLayout,\n      onPressIn,\n      onPressOut,\n      size = 'md',\n      style,\n      variant = 'primary',\n      ...rest\n    },\n    ref,\n  ) => {\n    const { colors, preferences } = useGlassTheme();\n    const [press] = useState(() => new Animated.Value(0));\n    const [bloom] = useState(() => new Animated.Value(0));\n    const [bloomOrigin] = useState(() => new Animated.ValueXY({ x: 0, y: 0 }));\n    const [bloomSize, setBloomSize] = useState(0);\n    const reduceMotion = preferences.reduceMotion;\n    const label = variant === 'prominent' ? colors.accentForeground : colors.foreground;\n\n    const handleLayout = useCallback(\n      (event: LayoutChangeEvent) => {\n        onLayout?.(event);\n        const { height, width } = event.nativeEvent.layout;\n\n        setBloomSize(Math.max(width, height) * 2.4);\n      },\n      [onLayout],\n    );\n\n    const handlePressIn = useCallback(\n      (event: GestureResponderEvent) => {\n        onPressIn?.(event);\n\n        if (reduceMotion) {\n          press.setValue(1);\n\n          return;\n        }\n\n        Animated.timing(press, {\n          duration: 90,\n          easing: Easing.out(Easing.quad),\n          toValue: 1,\n          useNativeDriver: GLASS_MOTION.nativeDriver,\n        }).start();\n\n        // The light starts under the finger and spreads through the glass.\n        bloomOrigin.setValue({ x: event.nativeEvent.locationX, y: event.nativeEvent.locationY });\n        bloom.setValue(0);\n        Animated.timing(bloom, {\n          duration: 720,\n          easing: Easing.bezier(0.22, 0.8, 0.3, 1),\n          toValue: 1,\n          useNativeDriver: GLASS_MOTION.nativeDriver,\n        }).start();\n      },\n      [bloom, bloomOrigin, onPressIn, press, reduceMotion],\n    );\n\n    const handlePressOut = useCallback(\n      (event: GestureResponderEvent) => {\n        onPressOut?.(event);\n\n        if (reduceMotion) {\n          press.setValue(0);\n\n          return;\n        }\n\n        Animated.spring(press, {\n          ...GLASS_MOTION.spring,\n          toValue: 0,\n          useNativeDriver: GLASS_MOTION.nativeDriver,\n        }).start();\n      },\n      [onPressOut, press, reduceMotion],\n    );\n\n    const renderLabel = (text: ReactNode) => (\n      <Text numberOfLines={1} style={[styles.label, { color: label, fontSize: LABEL_SIZE[size] }]}>\n        {text}\n      </Text>\n    );\n\n    const content = (\n      <>\n        <View style={[StyleSheet.absoluteFill, styles.clip]}>\n          <Animated.View\n            style={[\n              StyleSheet.absoluteFill,\n              {\n                backgroundColor: variant === 'ghost' ? colors.controlSurface : colors.rim,\n                opacity: press,\n              },\n            ]}\n          />\n          {bloomSize > 0 ? (\n            <Animated.View\n              style={[\n                styles.bloom,\n                {\n                  borderRadius: bloomSize / 2,\n                  height: bloomSize,\n                  left: -bloomSize / 2,\n                  top: -bloomSize / 2,\n                  width: bloomSize,\n                  opacity: bloom.interpolate({\n                    inputRange: [0, 0.16, 1],\n                    outputRange: [0, 1, 0],\n                  }),\n                  transform: [\n                    { translateX: bloomOrigin.x },\n                    { translateY: bloomOrigin.y },\n                    { scale: bloom.interpolate({ inputRange: [0, 1], outputRange: [0.08, 1] }) },\n                  ],\n                },\n              ]}\n            />\n          ) : null}\n        </View>\n        {Children.toArray(children).every((child) => !isValidElement(child))\n          ? renderLabel(children)\n          : // Beside an icon, each run of text still needs a Text to render in.\n            Children.map(children, (child) => (isValidElement(child) ? child : renderLabel(child)))}\n      </>\n    );\n\n    return (\n      <Pressable\n        ref={ref}\n        accessibilityRole=\"button\"\n        accessibilityState={{ disabled: Boolean(disabled) }}\n        disabled={disabled}\n        hitSlop={HIT_SLOP[size]}\n        onLayout={handleLayout}\n        onPressIn={handlePressIn}\n        onPressOut={handlePressOut}\n        style={[styles.root, disabled && styles.disabled, style]}\n        {...rest}\n      >\n        <Animated.View\n          style={{\n            transform: [\n              { translateY: press.interpolate({ inputRange: [0, 1], outputRange: [0, 1.5] }) },\n            ],\n          }}\n        >\n          {variant === 'ghost' ? (\n            <View style={[styles.body, SIZE_STYLE[size]]}>{content}</View>\n          ) : (\n            <GlassSurface\n              elevation={variant === 'soft' ? 'none' : 'base'}\n              preset={variant === 'soft' ? 'subtle' : 'control'}\n              tint={variant === 'prominent' ? colors.accent : undefined}\n              style={[styles.body, SIZE_STYLE[size]]}\n            >\n              {content}\n            </GlassSurface>\n          )}\n        </Animated.View>\n      </Pressable>\n    );\n  },\n);\n\nGlassButton.displayName = 'GlassButton';\n\nconst styles = StyleSheet.create({\n  bloom: {\n    experimental_backgroundImage:\n      'radial-gradient(circle, rgba(255, 255, 255, 0.55) 0%, rgba(255, 255, 255, 0) 70%)',\n    position: 'absolute',\n  },\n  body: {\n    alignItems: 'center',\n    borderCurve: 'continuous',\n    borderRadius: GLASS_RADIUS.capsule,\n    flexDirection: 'row',\n    justifyContent: 'center',\n  },\n  clip: { borderRadius: GLASS_RADIUS.capsule, overflow: 'hidden', pointerEvents: 'none' },\n  disabled: { opacity: 0.45 },\n  label: { fontWeight: '600', letterSpacing: -0.1 },\n  root: { alignSelf: 'flex-start' },\n});\n",
      "type": "registry:ui",
      "target": "components/glassyui/glass-button.tsx"
    }
  ],
  "type": "registry:ui"
}