Files
kolaytercih/.agents/skills/migrate-radix-to-base/wrapper-shapes.md
bilalgursen 87f265ec15 Build TercihAI MVP: landing page, YÖK Atlas data pipeline, rank-based results
- Add product vision/market research doc (VISION.md)
- Set up shadcn/ui (radix + nova preset) with blue/orange theme, Outfit + Work Sans fonts
- Landing page: hero with rank input, problem/steps sections, comparison table, FAQ
- Data pipeline: CSV archive ingest (2021-2024) + live YÖK Atlas API refresh (2025)
  into SQLite (npm run ingest / refresh); zeros normalized to NULL
- /sonuc page: hayal/dengeli/garanti buckets by COALESCE(sira2025, sira2024),
  score-type switcher, 2024→2025 trend indicators
- Add ui-ux-pro-max and shadcn agent skills, shadcn MCP config, launch.json

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 07:46:53 +03:00

4.6 KiB

Target wrapper shapes (golden-derived specifics)

Facts learned by diffing hand migrations against the shadcn base registry wrappers. These close gaps the mapping tables cannot express: exact classes, defaults, and composition shapes. When migrating shadcn-style wrappers, prefer these shapes.

Conventions

  • Positioner parts get NO data-slot attribute; keep data-slot on the parts the radix wrapper already had.
  • Menu-family Positioner: className="isolate z-50 outline-none"; the Popup KEEPS z-50 and outline-none too. Tooltip: Popup keeps z-50, Positioner gets isolate z-50. Select: isolate z-50 lives on the Popup, Positioner gets no class.
  • The base registry adds cn-<comp>-content-logical (and for tooltip also cn-tooltip-arrow-logical) companion classes next to the existing cn-<comp>-content hooks on popover, tooltip, hover-card, dropdown, context-menu, select, menubar popups. Add them when the source uses cn-* hooks; skip for plain-Tailwind projects.

Button

Base UI HAS a Button primitive: import { Button as ButtonPrimitive } from "@base-ui/react/button". A shadcn button.tsx with the Slot/asChild idiom migrates to <ButtonPrimitive> directly (which supports render), NOT to a hand-rolled useRender wrapper. Reserve useRender + mergeProps for non-button polymorphic components (breadcrumb link, marker).

Tooltip Arrow (literal classes)

<TooltipPrimitive.Arrow
  className={cn(
    "cn-tooltip-arrow cn-tooltip-arrow-logical",
    "data-[side=bottom]:top-1 data-[side=left]:right-[-13px] data-[side=left]:top-1/2! data-[side=left]:-translate-y-1/2 data-[side=right]:left-[-13px] data-[side=right]:top-1/2! data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5",
    className
  )}
/>

Verify against the current base registry tooltip before relying on the exact pixel values; the shape (per-side offsets + translate, no rotation) is the stable part. Golden default: alignOffset = 0, sideOffset = 4.

DropdownMenu / ContextMenu SubContent

Compose the PUBLIC Content wrapper, do not rebuild from primitives:

function DropdownMenuSubContent(props) {
  return (
    <DropdownMenuContent
      align="start"
      alignOffset={-3}
      side="right"
      sideOffset={0}
      className={cn("w-auto", props.className)}
      {...props}
    />
  )
}

The -3 / 0 defaults are load-bearing (visual alignment with the parent menu). NOTE: the live registry shapes SubContent differently per menu: context-menu is a true minimal compose (as above), while dropdown-menu's SubContent duplicates the full content class list (including translucent menu styling) rather than composing. When a golden pair exists, copy the golden shape; this example is the fallback.

DANGER — do not confuse SubContent defaults with main-Content defaults. The values above are ONLY for the submenu wrappers (DropdownMenuSubContent / ContextMenuSubContent). The MAIN ContextMenuContent (the pointer-anchored right-click menu) keeps its own positioning — do NOT apply side="right"/alignOffset to it, or every right-click menu mispositions.

  • ContextMenu SUBContent defaults: align="start" alignOffset={4} side="right" sideOffset={0}.
  • DropdownMenu SUBContent defaults: align="start" alignOffset={-3} side="right" sideOffset={0}.
  • Main Content (either): keep the wrapper's existing align/sideOffset; do not add a side.

SubTrigger open styling

Base wrappers ADD data-popup-open:bg-accent data-popup-open:text-accent-foreground to SubTrigger (no radix equivalent class existed; the open styling was previously data-[state=open]).

Select

  • Bare re-export: const Select = SelectPrimitive.Root (no wrapper function, no data-slot on Root). SelectPrimitive.Root.Props is GENERIC (<Value, Multiple>), which breaks the usual ComponentProps pattern; the bare re-export sidesteps it.
  • Drop the radix position prop entirely; expose alignItemWithTrigger (default true) picked from Positioner.Props, sideOffset = 4.
  • Item anatomy: ItemText FIRST with cn-select-item-text shrink-0 whitespace-nowrap, then ItemIndicator render={<span className="cn-select-item-indicator" />}.
  • Scroll arrows get top-0 w-full / bottom-0 w-full; List has no classes.

Accordion animation placement

h-(--accordion-panel-height), data-starting-style:h-0, and data-ending-style:h-0 all go on the INNER div of the Panel (the element that previously carried the radix height animation), not on the Panel itself.

Tabs

The base registry accepts Base UI's manual-activation default (no activateOnFocus), and does not forward orientation beyond what the radix wrapper did. Match it: flag the behavior delta, do not patch it.