{"$schema":"https://ui.shadcn.com/schema/registry-item.json","author":"Eduardo Calvo <educlopez93@gmail.com>","css":{},"dependencies":[],"description":"A Squircle component for SmoothUI.","devDependencies":[],"files":[{"content":"\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport {\n  type CSSProperties,\n  type ElementType,\n  type ReactNode,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\n\nconst DEFAULT_RADIUS = 24;\nconst DEFAULT_SMOOTHING = 0.6;\nconst EXPONENT_BASE = 2;\nconst EXPONENT_RANGE = 4;\nconst MIN_RATIO = 0;\nconst MAX_RATIO = 1;\n/**\n * Corners are traced as polylines, so the sample count has to grow with the\n * corner: 12 samples read as smooth on a 24px radius and visibly faceted on a\n * 96px one.\n */\nconst MIN_SAMPLES = 12;\nconst MAX_SAMPLES = 48;\nconst SAMPLES_PER_PX = 0.5;\nconst QUARTER_TURN = Math.PI / 2;\n\nexport interface SquircleProps {\n  as?: ElementType;\n  border?: boolean;\n  children?: ReactNode;\n  className?: string;\n  radius?: number;\n  smoothing?: number;\n}\n\ninterface Point {\n  x: number;\n  y: number;\n}\n\nconst clamp = (value: number, min: number, max: number) =>\n  Math.min(max, Math.max(min, value));\n\nconst superellipseExponent = (smoothing: number) =>\n  EXPONENT_BASE + clamp(smoothing, MIN_RATIO, MAX_RATIO) * EXPONENT_RANGE;\n\nconst sampleCount = (radius: number) =>\n  Math.round(clamp(radius * SAMPLES_PER_PX, MIN_SAMPLES, MAX_SAMPLES));\n\nconst quadrantPoints = (radius: number, exponent: number): Point[] => {\n  const samples = sampleCount(radius);\n  const points: Point[] = [];\n  for (let i = 1; i <= samples; i++) {\n    const t = QUARTER_TURN * (i / samples);\n    const x = radius * Math.cos(t) ** (2 / exponent);\n    const y = radius * Math.sin(t) ** (2 / exponent);\n    points.push({ x, y });\n  }\n  return points;\n};\n\nconst formatPoint = ({ x, y }: Point) => `${x.toFixed(2)},${y.toFixed(2)}`;\n\n/**\n * Builds an SVG path (for use with `clip-path: path(...)`) that traces a\n * rectangle whose four corners follow a superellipse (squircle) curve\n * instead of a plain circular arc, giving the smoother, curvature-continuous\n * look Apple's icons use. `smoothing` (0..1) maps to the superellipse\n * exponent: 0 is exactly a circular corner — identical to `border-radius` —\n * and 1 pushes the curve out into the flat edges.\n */\nexport const squirclePath = (\n  width: number,\n  height: number,\n  radius: number = DEFAULT_RADIUS,\n  smoothing: number = DEFAULT_SMOOTHING\n): string => {\n  if (width <= 0 || height <= 0) {\n    return \"\";\n  }\n\n  const r = clamp(radius, 0, Math.min(width, height) / 2);\n\n  if (r <= 0) {\n    return `M0,0 L${width},0 L${width},${height} L0,${height} Z`;\n  }\n\n  const exponent = superellipseExponent(smoothing);\n  const quadrant = quadrantPoints(r, exponent);\n\n  const topRight = quadrant.map(({ x, y }) => ({ x: width - r + y, y: r - x }));\n  const bottomRight = quadrant.map(({ x, y }) => ({\n    x: width - r + x,\n    y: height - r + y,\n  }));\n  const bottomLeft = quadrant.map(({ x, y }) => ({\n    x: r - y,\n    y: height - r + x,\n  }));\n  const topLeft = quadrant.map(({ x, y }) => ({ x: r - x, y: r - y }));\n\n  const segments = [\n    `M${r.toFixed(2)},0`,\n    `L${(width - r).toFixed(2)},0`,\n    ...topRight.map((point) => `L${formatPoint(point)}`),\n    `L${width.toFixed(2)},${(height - r).toFixed(2)}`,\n    ...bottomRight.map((point) => `L${formatPoint(point)}`),\n    `L${r.toFixed(2)},${height.toFixed(2)}`,\n    ...bottomLeft.map((point) => `L${formatPoint(point)}`),\n    `L0,${r.toFixed(2)}`,\n    ...topLeft.map((point) => `L${formatPoint(point)}`),\n    \"Z\",\n  ];\n\n  return segments.join(\" \");\n};\n\nexport default function Squircle({\n  as,\n  border = false,\n  children,\n  className,\n  radius = DEFAULT_RADIUS,\n  smoothing = DEFAULT_SMOOTHING,\n}: SquircleProps) {\n  const Wrapper = as ?? \"div\";\n  const elementRef = useRef<HTMLElement | null>(null);\n  const [size, setSize] = useState({ height: 0, width: 0 });\n\n  useEffect(() => {\n    const element = elementRef.current;\n    if (!element) {\n      return;\n    }\n\n    const updateSize = () => {\n      const rect = element.getBoundingClientRect();\n      setSize({ height: rect.height, width: rect.width });\n    };\n\n    updateSize();\n\n    const observer = new ResizeObserver(updateSize);\n    observer.observe(element);\n\n    return () => observer.disconnect();\n  }, []);\n\n  // The generated path is the only code path on purpose: CSS\n  // `corner-shape: squircle` is a single fixed shape that silently ignores\n  // `smoothing`, so switching to it where supported made the same props render\n  // two different corners.\n  const clipPath = useMemo(() => {\n    if (size.width === 0 || size.height === 0) {\n      return;\n    }\n    const path = squirclePath(size.width, size.height, radius, smoothing);\n    return path ? `path('${path}')` : undefined;\n  }, [radius, smoothing, size.height, size.width]);\n\n  const style: CSSProperties = { borderRadius: radius, clipPath };\n\n  return (\n    <Wrapper\n      className={cn(border && \"border border-foreground/20\", className)}\n      ref={elementRef}\n      style={style}\n    >\n      {children}\n    </Wrapper>\n  );\n}\n","path":"index.tsx","target":"components/smoothui/squircle/index.tsx","type":"registry:ui"}],"name":"squircle","registryDependencies":[],"title":"Squircle","type":"registry:ui"}