{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "concentric-frame",
  "title": "Concentric Frame",
  "description": "A layout primitive that exposes outer, padding and inner radius as CSS variables so nested corners stay concentric.",
  "registryDependencies": [
    "https://eased.dev/r/eased-tokens.json"
  ],
  "files": [
    {
      "path": "src/registry/eased/concentric-frame/concentric-frame.tsx",
      "content": "\"use client\";\n\nimport {\n  createContext,\n  useContext,\n  useMemo,\n  type ComponentProps,\n  type CSSProperties,\n  type ElementType,\n  type ReactNode,\n} from \"react\";\n\n/** A px number, or any CSS length (`\"var(--eased-radius-panel)\"`, `\"1rem\"`). */\nexport type FrameLength = number | string;\n\nexport type FrameMetrics = {\n  /** CSS lengths, safe to drop into `style` or `calc()`. */\n  radius: string;\n  /** The larger of the two paddings: the one the inner radius is cut by. */\n  pad: string;\n  padX: string;\n  padY: string;\n  border: string;\n  /** The smallest inner radius, so a deep nest rounds at this instead of squaring off (R5). */\n  min: string;\n  inner: string;\n  /** The same values in px, or null when any input is a CSS string. */\n  px: { radius: number; pad: number; border: number; min: number; inner: number } | null;\n  concentric: boolean;\n};\n\n// ComponentProps, not ComponentPropsWithoutRef: React 19 passes `ref` as a prop, and ...rest forwards it.\ntype Poly<T extends ElementType, P> = P & { as?: T } & Omit<ComponentProps<T>, keyof P | \"as\">;\n\ntype FrameOwnProps = {\n  /** Outer corner radius. Defaults to the parent frame's inner radius, else `--eased-radius-panel`. */\n  radius?: FrameLength;\n  /** Padding on every side, or `{ x, y }`. Default 12. The inner radius is cut by the larger one. */\n  pad?: FrameLength | { x: FrameLength; y: FrameLength };\n  /** Border width, subtracted from the inner radius. Default 1; colour it with a class. */\n  border?: FrameLength;\n  /** Floor for the inner radius. Default 2: below that a corner reads as a mistake, so it stays slightly round. */\n  min?: FrameLength;\n  /** False hands children the outer radius unchanged: the naive look, for comparisons. Inherited. */\n  concentric?: boolean;\n  style?: CSSProperties;\n  children?: ReactNode;\n};\n\nexport type FrameProps<T extends ElementType = \"div\"> = Poly<T, FrameOwnProps>;\nexport type FrameInnerProps<T extends ElementType = \"div\"> = Poly<T, { style?: CSSProperties; children?: ReactNode }>;\n\nconst FrameContext = createContext<FrameMetrics | null>(null);\n\nconst toCss = (v: FrameLength) => (typeof v === \"number\" ? `${v}px` : v);\n\nconst cssVars = (m: FrameMetrics) =>\n  ({\n    \"--radius-outer\": m.radius,\n    \"--pad\": m.pad,\n    \"--pad-x\": m.padX,\n    \"--pad-y\": m.padY,\n    \"--border-w\": m.border,\n    \"--radius-inner\": m.inner,\n  }) as CSSProperties;\n\n/**\n * A layout primitive that keeps nested curves parallel. It exposes `--radius-outer`,\n * `--pad`, `--border-w` and `--radius-inner` on its root, so any descendant can round\n * itself with `var(--radius-inner)` (or `<FrameInner>`, or `radius-nested`) without props.\n * Padding, radius and border (width and style) are owned here: Tailwind `p-*`, `rounded-*`\n * and `border-*` width classes on the root are overridden. Style only the border colour.\n */\nexport function Frame<T extends ElementType = \"div\">({\n  as,\n  radius,\n  pad = 12,\n  border = 1,\n  min = 2,\n  concentric,\n  style,\n  ...rest\n}: FrameProps<T>) {\n  const parent = useContext(FrameContext);\n\n  const metrics = useMemo<FrameMetrics>(() => {\n    const isConcentric = concentric ?? parent?.concentric ?? true;\n    // A nested frame with no radius continues the parent's curve.\n    const r: FrameLength = radius ?? (parent ? (parent.px?.inner ?? parent.inner) : \"var(--eased-radius-panel)\");\n\n    const { x, y } = typeof pad === \"object\" ? pad : { x: pad, y: pad };\n\n    if ([r, x, y, border, min].every((v) => typeof v === \"number\")) {\n      const [R, X, Y, B, M] = [r, x, y, border, min] as number[];\n      const P = Math.max(X, Y);\n      // Never rounder than the outer corner, never squarer than the floor.\n      const inner = isConcentric ? Math.min(R, Math.max(M, R - P - B)) : R;\n      return {\n        radius: toCss(R),\n        pad: toCss(P),\n        padX: toCss(X),\n        padY: toCss(Y),\n        border: toCss(B),\n        min: toCss(M),\n        inner: toCss(inner),\n        px: { radius: R, pad: P, border: B, min: M, inner },\n        concentric: isConcentric,\n      };\n    }\n\n    const [R, X, Y, B, M] = [toCss(r), toCss(x), toCss(y), toCss(border), toCss(min)];\n    const P = X === Y ? X : `max(${X}, ${Y})`;\n    return {\n      radius: R,\n      pad: P,\n      padX: X,\n      padY: Y,\n      border: B,\n      min: M,\n      inner: isConcentric ? `min(${R}, max(${M}, calc(${R} - ${P} - ${B})))` : R,\n      px: null,\n      concentric: isConcentric,\n    };\n  }, [radius, pad, border, min, concentric, parent]);\n\n  const Comp: ElementType = as ?? \"div\";\n  const vars = {\n    ...cssVars(metrics),\n    borderRadius: \"var(--radius-outer)\",\n    padding: \"var(--pad-y) var(--pad-x)\",\n    borderWidth: \"var(--border-w)\",\n    // Set with the width so the border never depends on Tailwind's preflight.\n    borderStyle: \"solid\",\n    ...style,\n  } as CSSProperties;\n\n  return (\n    <FrameContext value={metrics}>\n      <Comp {...rest} style={vars} />\n    </FrameContext>\n  );\n}\n\n/** A direct child that takes the frame's concentric inner radius. Images inside should use `rounded-[inherit]`. */\nexport function FrameInner<T extends ElementType = \"div\">({ as, style, ...rest }: FrameInnerProps<T>) {\n  const Comp: ElementType = as ?? \"div\";\n  return <Comp {...rest} style={{ borderRadius: \"var(--radius-inner)\", ...style }} />;\n}\n\n/** The nearest frame's metrics, or null outside one. Use `px` for geometry (guides, canvases). */\nexport function useFrame() {\n  return useContext(FrameContext);\n}\n\n/**\n * The nearest frame's CSS variables as a style object, empty outside one. Spread it onto\n * anything that cannot inherit them: a portal, or an SVG or canvas wrapper rendered elsewhere.\n */\nexport function useFrameVar(): CSSProperties {\n  const metrics = useContext(FrameContext);\n  return metrics ? cssVars(metrics) : {};\n}\n",
      "type": "registry:ui",
      "target": "@components/eased/concentric-frame/concentric-frame.tsx"
    }
  ],
  "docs": "Import Frame, FrameInner and useFrame from components/eased/concentric-frame/concentric-frame. The Eased tokens were merged into your global CSS; nothing to import. Dark mode follows .dark or data-theme=\"dark\".",
  "type": "registry:ui"
}