{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sidebar",
  "title": "Sidebar",
  "description": "shadcn/ui(new-york-v4) sidebar의 유니버설(웹+네이티브 한 벌 소스) 포팅. 포팅 판단(원본 대비 차이·이유): 관리한다(open·openMobile·toggleSidebar). 제어형(open/onOpenChange) 유지.",
  "dependencies": [
    "@unikit-bridge/ui",
    "class-variance-authority"
  ],
  "registryDependencies": [
    "sheet",
    "tooltip",
    "utils"
  ],
  "files": [
    {
      "path": "registry/ui/sidebar.tsx",
      "content": "// Sidebar — shadcn/ui（new-york-v4） sidebar.tsx 유니버설 포팅 （사이클 40, T-3b）.\n// 원본: shadcn-ref/ui/sidebar.tsx\n//\n// 포팅 판단（원본 대비 차이이유）:\n// - 쿠키 지속화（document.cookie） 제거 — 열림 상태는 React state + context로만\n//   관리한다（openopenMobiletoggleSidebar）. 제어형（open/onOpenChange） 유지.\n// - useIsMobile 훅 대신 SidebarProvider의 isMobile prop（기본 false）으로 전환\n//   — 유니버설 환경에 matchMedia 계약이 없어 소비자가 결정한다. isMobile이면\n//   원본과 동일하게 Sheet（side 전달）로 전환, 아니면 데스크톱 레이아웃.\n// - 데스크톱 배치: 원본의 fixed 컨테이너 + gap peer 구조（화면 밖 슬라이드\n//   transition）는 문서 흐름 참여형 단일 View（폭 토큰 전환）로 근사 — 즉시\n//   전환 관행（다른 오버레이 계열과 동일）, offcanvas 접힘은 w-0 + 경계 제거로\n//   표현한다. md: 미디어쿼리 토큰은 isMobile prop 분기가 대체했다.\n//   높이는 웹에서 web:sticky top-0 + web:h-svh（원본 fixed inset-y-0의 근사 —\n//   스크롤 문서에서도 뷰포트 높이를 유지）, 네이티브는 Provider flex-row의\n//   교차축 stretch. h-full은 Provider（min-h, 높이 부정） 안에서 콘텐츠 높이로\n//   붕괴해 제거했다（2026-11-12 블록 검증에서 발견）.\n// - 폭 CSS 변수（--sidebar-width 16rem 등）는 표준 계단 토큰으로 치환:\n//   w-64（16rem）w-72（18rem 모바일）w-12（3rem 아이콘）w-14（아이콘+패딩 3.5rem,\n//   원본 calc（3rem+1rem）의 임의값 근사）.\n// - sidebar-* 색 팔레트는 UniKit UI 토큰 20종에 없어（check-consumer-theme가 소비자\n//   자산으로 분류） 다음 매핑으로 근사: bg-sidebar→bg-background,\n//   text-sidebar-foreground→text-foreground, bg-sidebar-accent→bg-accent,\n//   *-accent-foreground→*-accent-foreground, sidebar-border→border,\n//   sidebar-ring→ring. 소비자가 sidebar 팔레트를 쓰려면 className 오버라이드.\n// - data-slotdata-sidebar 속성과 group-data-［］peer-data-［］ 그룹 셀렉터는\n//   네이티브에 data 속성이 없어 SidebarContext 확장 필드（variantcollapsible\n//   side） + cva 변형 + 조건부 클래스로 근사한다（attachmentalert-dialog 관행）.\n//   icon 접힘（size-8! p-2! 등 important 토큰）은 cn 병합 순서로 대체.\n// - 키보드 단축키（Ctrl/Cmd+B）는 window keydown 리스너（typeof window 가드）로\n//   유지 — 웹 전용, 네이티브는 no-op.\n// - radix Slot（asChild） 미지원 — GroupLabelMenuButtonMenuActionSubButton은\n//   Pressable/View 렌더 고정（문자열 children은 내부 Text로 감싼다）.\n// - 툴팁 연동: 기존 \"./tooltip\"을 소비. 원본은 항상 Tooltip으로 감싸고\n//   hidden 제어하지만, 우리 툴팁은 press-to-toggle 방식이라 \"아이콘 접힘\n//   상태에서만\" TooltipTrigger로 전환한다（expanded에선 라벨이 이미 보이므로\n//   원본 hidden과 동일 효과 — 판단 기록）.\n// - lucide PanelLeftIcon은 브리지 primitives 경유（직접 lucide import 금지）.\n//   sr-only는 accessibilityLabel로 대체（dialogsheet 관행）. 모바일 시트의\n//   sr-only 헤더는 시각 숨김 근사（absolute opacity-0）로 렌더해 스크린리더\n//   이름은 유지한다.\n// - 원본 임의값가상요소 토큰 근사: after:w-［2px］ 레일 바 제거（커서\n//   w/e-resize와 함께 — 웹 전용 셀렉터）, shadow-［0_0_0_1px_var（）］ outline\n//   변형은 border border-border로, skeleton 랜덤 폭은 style maxWidth 직접 값으로.\n// - SidebarContent는 div overflow-auto 대신 브리지 ScrollView를 소비（네이티브\n//   스크롤 보장 — scroll-area 관행）.\n// - ［&>svg］:size-4 등 자식 svg 규격 셀렉터는 제거 — 아이콘 크기는 소비자\n//   className 안내로 남긴다. hover 전경색 전환（peer-hover）도 셀렉터 불가로\n//   배경 전환만 근사했다.\nimport * as React from \"react\";\nimport {\n  PanelLeftIcon,\n  Pressable,\n  ScrollView,\n  Text,\n  View,\n  type BridgePressableRef,\n  type BridgeScrollViewRef,\n  type BridgeTextRef,\n  type BridgeViewRef,\n  type PressableProps,\n  type ScrollViewProps,\n  type TextProps,\n  type TextInputProps,\n  type ViewProps,\n  type BridgeTextInputRef,\n} from \"@unikit-bridge/ui/primitives\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { Button } from \"./button\";\nimport { Input } from \"./input\";\nimport { Separator } from \"./separator\";\nimport {\n  Sheet,\n  SheetContent,\n  SheetDescription,\n  SheetHeader,\n  SheetTitle,\n} from \"./sheet\";\nimport { Skeleton } from \"./skeleton\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"./tooltip\";\nimport { cn } from \"@/lib/utils\";\n\nconst SIDEBAR_WIDTH = \"w-64\"; // 원본 16rem\nconst SIDEBAR_WIDTH_MOBILE = \"w-72\"; // 원본 18rem\nconst SIDEBAR_WIDTH_ICON = \"w-12\"; // 원본 3rem\nconst SIDEBAR_WIDTH_ICON_PADDED = \"w-14\"; // 원본 calc(3rem + 1rem) 근사\nconst SIDEBAR_KEYBOARD_SHORTCUT = \"b\";\n\ntype SidebarContextProps = {\n  state: \"expanded\" | \"collapsed\";\n  open: boolean;\n  setOpen: (open: boolean) => void;\n  openMobile: boolean;\n  setOpenMobile: (open: boolean) => void;\n  isMobile: boolean;\n  toggleSidebar: () => void;\n  /** 포팅 확장 — 원본 data-variant/collapsible/side 속성그룹 셀렉터 근사 */\n  variant: \"sidebar\" | \"floating\" | \"inset\";\n  collapsible: \"offcanvas\" | \"icon\" | \"none\";\n  side: \"left\" | \"right\";\n};\n\nconst SidebarContext = React.createContext<SidebarContextProps | null>(null);\n\nexport function useSidebar(): SidebarContextProps {\n  const context = React.useContext(SidebarContext);\n  if (!context) {\n    throw new Error(\"useSidebar는 <SidebarProvider> 안에서 사용해야 합니다.\");\n  }\n  return context;\n}\n\n// 문자열 children을 스타일 지정된 내부 Text로 감싼다（이중 지정 원칙）.\nfunction serializableChildren(\n  children: React.ReactNode,\n  textClassName: string,\n): React.ReactNode {\n  if (typeof children === \"string\" || typeof children === \"number\") {\n    return <Text className={textClassName}>{children}</Text>;\n  }\n  return children;\n}\n\n// ---------------------------------------------------------------------------\n// SidebarProvider\n// ---------------------------------------------------------------------------\n\nexport type SidebarProviderProps = ViewProps & {\n  defaultOpen?: boolean;\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  /** 모바일 여부 — true면 Sidebar가 Sheet로 전환（기본 false 데스크톱） */\n  isMobile?: boolean;\n};\n\nconst SidebarProvider = React.forwardRef<\n  BridgeViewRef,\n  SidebarProviderProps\n>(\n  (\n    {\n      defaultOpen = true,\n      open: openProp,\n      onOpenChange: setOpenProp,\n      isMobile = false,\n      className,\n      children,\n      ...props\n    },\n    ref,\n  ) => {\n    const [openMobile, setOpenMobile] = React.useState(false);\n\n    // 내부 상태 — openProp 제어형이면 무시된다（tabsdialog 관행）.\n    const [_open, _setOpen] = React.useState(defaultOpen);\n    const open = openProp ?? _open;\n    const setOpen = React.useCallback(\n      (value: boolean | ((value: boolean) => boolean)) => {\n        const openState = typeof value === \"function\" ? value(open) : value;\n        if (setOpenProp) {\n          setOpenProp(openState);\n        } else {\n          _setOpen(openState);\n        }\n      },\n      [setOpenProp, open],\n    );\n\n    const toggleSidebar = React.useCallback(() => {\n      return isMobile\n        ? setOpenMobile((open) => !open)\n        : setOpen((open) => !open);\n    }, [isMobile, setOpen]);\n\n    // Ctrl/Cmd+B 단축키 — 웹 window 리스너（네이티브 no-op）.\n    // RN(Hermes)은 window 전역이 global의 별칭이라 존재하지만 DOM API가 없다 —\n    // 존재 여부가 아니라 addEventListener 함수 여부로 가드해야 한다(실증 2026-11-10).\n    React.useEffect(() => {\n      if (typeof window?.addEventListener !== \"function\") return;\n      const handleKeyDown = (event: Event) => {\n        const keyboardEvent = event as {\n          key?: string;\n          metaKey?: boolean;\n          ctrlKey?: boolean;\n          preventDefault?: () => void;\n        };\n        if (\n          keyboardEvent.key === SIDEBAR_KEYBOARD_SHORTCUT &&\n          (keyboardEvent.metaKey || keyboardEvent.ctrlKey)\n        ) {\n          keyboardEvent.preventDefault?.();\n          toggleSidebar();\n        }\n      };\n      window.addEventListener(\"keydown\", handleKeyDown);\n      return () => window.removeEventListener(\"keydown\", handleKeyDown);\n    }, [toggleSidebar]);\n\n    const state = open ? \"expanded\" : \"collapsed\";\n\n    const contextValue = React.useMemo<SidebarContextProps>(\n      () => ({\n        state,\n        open,\n        setOpen,\n        isMobile,\n        openMobile,\n        setOpenMobile,\n        toggleSidebar,\n        // Sidebar가 실제 값으로 재제공하기 전의 기본값\n        variant: \"sidebar\",\n        collapsible: \"offcanvas\",\n        side: \"left\",\n      }),\n      [state, open, setOpen, isMobile, openMobile, toggleSidebar],\n    );\n\n    return (\n      <SidebarContext.Provider value={contextValue}>\n        <TooltipProvider delayDuration={0}>\n          <View\n            ref={ref}\n            className={cn(\"w-full flex-row web:min-h-svh\", className)}\n            {...props}\n          >\n            {children}\n          </View>\n        </TooltipProvider>\n      </SidebarContext.Provider>\n    );\n  },\n);\nSidebarProvider.displayName = \"SidebarProvider\";\n\n// ---------------------------------------------------------------------------\n// Sidebar 루트 — isMobile이면 Sheet, 아니면 흐름 참여형 고정 열\n// ---------------------------------------------------------------------------\n\nexport type SidebarProps = ViewProps & {\n  side?: \"left\" | \"right\";\n  variant?: \"sidebar\" | \"floating\" | \"inset\";\n  collapsible?: \"offcanvas\" | \"icon\" | \"none\";\n};\n\nconst Sidebar = React.forwardRef<BridgeViewRef, SidebarProps>(\n  (\n    {\n      side = \"left\",\n      variant = \"sidebar\",\n      collapsible = \"offcanvas\",\n      className,\n      children,\n      ...props\n    },\n    ref,\n  ) => {\n    const sidebar = useSidebar();\n    const { isMobile, state, openMobile, setOpenMobile } = sidebar;\n\n    // 아이콘 접힘 여부 — 자식 토큰 근사의 공통 조건.\n    const collapsed = state === \"collapsed\";\n    const iconCollapsed = collapsed && collapsible === \"icon\";\n\n    // 루트 context 재제공 — data-* 속성그룹 셀렉터 근사（attachment 관행）.\n    const contextValue = React.useMemo<SidebarContextProps>(\n      () => ({ ...sidebar, variant, collapsible, side }),\n      [sidebar, variant, collapsible, side],\n    );\n\n    if (collapsible === \"none\") {\n      return (\n        <SidebarContext.Provider value={contextValue}>\n          <View\n            ref={ref}\n            className={cn(\n              // h-full 제거 — Provider(높이 부정 min-h) 안에서 100%가 콘텐츠\n              // 높이로 붕괴한다. 네이티브는 flex-row 교차축 stretch, 웹은\n              // sticky+h-svh(원본 fixed inset-y-0의 흐름 참여형 근사)로 채운다.\n              \"web:sticky web:top-0 web:h-svh flex-col bg-background text-foreground\",\n              SIDEBAR_WIDTH,\n              className,\n            )}\n            {...props}\n          >\n            {children}\n          </View>\n        </SidebarContext.Provider>\n      );\n    }\n\n    if (isMobile) {\n      return (\n        <SidebarContext.Provider value={contextValue}>\n          <Sheet open={openMobile} onOpenChange={setOpenMobile}>\n            <SheetContent\n              side={side}\n              showCloseButton={false}\n              className={cn(\n                \"gap-0 bg-background p-0 text-foreground\",\n                SIDEBAR_WIDTH_MOBILE,\n                className,\n              )}\n            >\n              {/* 시각 숨김 근사（sr-only 대체） — 스크린리더 이름 유지 */}\n              <SheetHeader className=\"absolute inset-0 gap-0 p-0 opacity-0\">\n                <SheetTitle>Sidebar</SheetTitle>\n                <SheetDescription>\n                  모바일 사이드바를 표시합니다.\n                </SheetDescription>\n              </SheetHeader>\n              <View className=\"h-full w-full flex-col\">{children}</View>\n            </SheetContent>\n          </Sheet>\n        </SidebarContext.Provider>\n      );\n    }\n\n    // offcanvas 접힘 — 원본 화면 밖 슬라이드 대신 폭 0경계 제거로 즉시 전환.\n    const widthClass = collapsed\n      ? collapsible === \"icon\"\n        ? variant === \"sidebar\"\n          ? SIDEBAR_WIDTH_ICON\n          : SIDEBAR_WIDTH_ICON_PADDED\n        : \"w-0\"\n      : SIDEBAR_WIDTH;\n\n    const inner = (\n      <View className=\"h-full w-full flex-col bg-background text-foreground\">\n        {children}\n      </View>\n    );\n\n    return (\n      <SidebarContext.Provider value={contextValue}>\n        <View\n          ref={ref}\n          className={cn(\n            // h-full 제거 근거는 collapsible=none 분기 주석과 동일 —\n            // 웹은 sticky+h-svh로 뷰포트 높이 고정(스크롤 문서에서도 잔류),\n            // 네이티브는 교차축 stretch로 부모를 채운다.\n            \"relative web:sticky web:top-0 web:h-svh overflow-hidden\",\n            widthClass,\n            variant === \"sidebar\" &&\n              !collapsed &&\n              (side === \"left\" ? \"border-r\" : \"border-l\"),\n            variant === \"floating\" || variant === \"inset\" ? \"p-2\" : \"\",\n            className,\n          )}\n          {...props}\n        >\n          {variant === \"floating\" ? (\n            <View className=\"h-full w-full flex-col rounded-lg border bg-background text-foreground shadow-sm\">\n              {children}\n            </View>\n          ) : (\n            inner\n          )}\n        </View>\n      </SidebarContext.Provider>\n    );\n  },\n);\nSidebar.displayName = \"Sidebar\";\n\n// ---------------------------------------------------------------------------\n// Trigger / Rail / Inset\n// ---------------------------------------------------------------------------\n\nexport type SidebarTriggerProps = PressableProps;\n\nconst SidebarTrigger = React.forwardRef<\n  BridgePressableRef,\n  SidebarTriggerProps\n>(({ className, onPress, ...props }, ref) => {\n  const { toggleSidebar } = useSidebar();\n  return (\n    <Button\n      ref={ref}\n      variant=\"ghost\"\n      size=\"icon\"\n      accessibilityLabel=\"Toggle Sidebar\"\n      className={cn(\"size-7\", className)}\n      onPress={() => {\n        onPress?.();\n        toggleSidebar();\n      }}\n      {...props}\n    >\n      <PanelLeftIcon className=\"size-4\" />\n    </Button>\n  );\n});\nSidebarTrigger.displayName = \"SidebarTrigger\";\n\nexport type SidebarRailProps = PressableProps;\n\nconst SidebarRail = React.forwardRef<BridgePressableRef, SidebarRailProps>(\n  ({ className, onPress, ...props }, ref) => {\n    const { toggleSidebar, isMobile, side } = useSidebar();\n    // 모바일（시트）에서는 레일이 무의미 — 렌더하지 않는다（원본 hidden sm:flex）.\n    if (isMobile) return null;\n    return (\n      <Pressable\n        ref={ref}\n        accessibilityLabel=\"Toggle Sidebar\"\n        onPress={() => {\n          onPress?.();\n          toggleSidebar();\n        }}\n        className={cn(\n          // 원본: -translate-x-1/2 + side=left -right-4 / side=right left-0.\n          \"absolute inset-y-0 z-20 w-4 -translate-x-1/2\",\n          side === \"left\" ? \"-right-4\" : \"left-0\",\n          className,\n        )}\n        {...props}\n      />\n    );\n  },\n);\nSidebarRail.displayName = \"SidebarRail\";\n\nexport type SidebarInsetProps = ViewProps;\n\nconst SidebarInset = React.forwardRef<BridgeViewRef, SidebarInsetProps>(\n  ({ className, ...props }, ref) => {\n    // 원본 md:peer-data-［variant=inset］:* 토큰 근사 — variantstate context 판독.\n    const { variant, state } = useSidebar();\n    const inset = variant === \"inset\";\n    return (\n      <View\n        ref={ref}\n        className={cn(\n          \"relative flex w-full flex-1 flex-col bg-background\",\n          inset && \"m-2 ml-0 rounded-xl shadow-sm\",\n          inset && state === \"collapsed\" && \"ml-2\",\n          className,\n        )}\n        {...props}\n      />\n    );\n  },\n);\nSidebarInset.displayName = \"SidebarInset\";\n\n// ---------------------------------------------------------------------------\n// Input / Header / Footer / Separator / Content\n// ---------------------------------------------------------------------------\n\nexport type SidebarInputProps = TextInputProps;\n\nconst SidebarInput = React.forwardRef<BridgeTextInputRef, SidebarInputProps>(\n  ({ className, ...props }, ref) => (\n    <Input\n      ref={ref}\n      className={cn(\"h-8 w-full bg-background shadow-none\", className)}\n      {...props}\n    />\n  ),\n);\nSidebarInput.displayName = \"SidebarInput\";\n\nexport type SidebarHeaderProps = ViewProps;\n\nconst SidebarHeader = React.forwardRef<BridgeViewRef, SidebarHeaderProps>(\n  ({ className, ...props }, ref) => (\n    <View ref={ref} className={cn(\"flex-col gap-2 p-2\", className)} {...props} />\n  ),\n);\nSidebarHeader.displayName = \"SidebarHeader\";\n\nexport type SidebarFooterProps = ViewProps;\n\nconst SidebarFooter = React.forwardRef<BridgeViewRef, SidebarFooterProps>(\n  ({ className, ...props }, ref) => (\n    <View ref={ref} className={cn(\"flex-col gap-2 p-2\", className)} {...props} />\n  ),\n);\nSidebarFooter.displayName = \"SidebarFooter\";\n\nexport type SidebarSeparatorProps = ViewProps;\n\nconst SidebarSeparator = React.forwardRef<BridgeViewRef, SidebarSeparatorProps>(\n  ({ className, ...props }, ref) => (\n    <Separator\n      ref={ref}\n      className={cn(\"mx-2 w-auto bg-border\", className)}\n      {...props}\n    />\n  ),\n);\nSidebarSeparator.displayName = \"SidebarSeparator\";\n\nexport type SidebarContentProps = ScrollViewProps;\n\nconst SidebarContent = React.forwardRef<\n  BridgeScrollViewRef,\n  SidebarContentProps\n>(({ className, ...props }, ref) => {\n  const { state, collapsible } = useSidebar();\n  const iconCollapsed = state === \"collapsed\" && collapsible === \"icon\";\n  return (\n    <ScrollView\n      ref={ref}\n      contentContainerClassName=\"flex-col gap-2\"\n      className={cn(\n        \"min-h-0 flex-1\",\n        iconCollapsed && \"overflow-hidden\",\n        className,\n      )}\n      {...props}\n    />\n  );\n});\nSidebarContent.displayName = \"SidebarContent\";\n\n// ---------------------------------------------------------------------------\n// Group 계열\n// ---------------------------------------------------------------------------\n\nexport type SidebarGroupProps = ViewProps;\n\nconst SidebarGroup = React.forwardRef<BridgeViewRef, SidebarGroupProps>(\n  ({ className, ...props }, ref) => (\n    <View\n      ref={ref}\n      className={cn(\"relative w-full min-w-0 flex-col p-2\", className)}\n      {...props}\n    />\n  ),\n);\nSidebarGroup.displayName = \"SidebarGroup\";\n\nexport type SidebarGroupLabelProps = TextProps;\n\nconst SidebarGroupLabel = React.forwardRef<\n  BridgeTextRef,\n  SidebarGroupLabelProps\n>(({ className, children, ...props }, ref) => {\n  const { state, collapsible } = useSidebar();\n  const iconCollapsed = state === \"collapsed\" && collapsible === \"icon\";\n  return (\n    <View\n      className={cn(\n        \"h-8 shrink-0 flex-row items-center rounded-md px-2\",\n        \"web:focus-visible:ring-2 web:focus-visible:ring-ring\",\n        iconCollapsed && \"-mt-8 opacity-0\",\n        className,\n      )}\n    >\n      <Text\n        ref={ref}\n        className=\"font-sans text-xs font-medium text-foreground/70\"\n        {...props}\n      >\n        {children}\n      </Text>\n    </View>\n  );\n});\nSidebarGroupLabel.displayName = \"SidebarGroupLabel\";\n\nexport type SidebarGroupActionProps = PressableProps;\n\nconst SidebarGroupAction = React.forwardRef<\n  BridgePressableRef,\n  SidebarGroupActionProps\n>(({ className, children, onPress, ...props }, ref) => {\n  const { state, collapsible } = useSidebar();\n  const iconCollapsed = state === \"collapsed\" && collapsible === \"icon\";\n  // 아이콘 접힘 시 비활성（원본 group-data-［collapsible=icon］:hidden）.\n  if (iconCollapsed) return null;\n  return (\n    <Pressable\n      ref={ref}\n      accessibilityRole=\"button\"\n      // after:-inset-2（8px 히트영역 확장）의 근사 — hitSlop.\n      hitSlop={8}\n      className={cn(\n        \"absolute top-3.5 right-3 aspect-square w-5 items-center justify-center rounded-md p-0\",\n        \"web:focus-visible:ring-2 web:focus-visible:ring-ring active:bg-accent\",\n        className,\n      )}\n      onPress={onPress}\n      {...props}\n    >\n      {serializableChildren(\n        children,\n        \"font-sans text-sm text-foreground active:text-accent-foreground\",\n      )}\n    </Pressable>\n  );\n});\nSidebarGroupAction.displayName = \"SidebarGroupAction\";\n\nexport type SidebarGroupContentProps = ViewProps;\n\nconst SidebarGroupContent = React.forwardRef<\n  BridgeViewRef,\n  SidebarGroupContentProps\n>(({ className, ...props }, ref) => (\n  <View ref={ref} className={cn(\"w-full\", className)} {...props} />\n));\nSidebarGroupContent.displayName = \"SidebarGroupContent\";\n\n// ---------------------------------------------------------------------------\n// Menu 계열\n// ---------------------------------------------------------------------------\n\nexport type SidebarMenuProps = ViewProps;\n\nconst SidebarMenu = React.forwardRef<BridgeViewRef, SidebarMenuProps>(\n  ({ className, ...props }, ref) => (\n    <View\n      ref={ref}\n      accessibilityRole=\"list\"\n      className={cn(\"w-full min-w-0 flex-col gap-1\", className)}\n      {...props}\n    />\n  ),\n);\nSidebarMenu.displayName = \"SidebarMenu\";\n\nexport type SidebarMenuItemProps = ViewProps;\n\nconst SidebarMenuItem = React.forwardRef<BridgeViewRef, SidebarMenuItemProps>(\n  ({ className, ...props }, ref) => (\n    <View\n      ref={ref}\n      accessibilityRole=\"listitem\"\n      className={cn(\"relative\", className)}\n      {...props}\n    />\n  ),\n);\nSidebarMenuItem.displayName = \"SidebarMenuItem\";\n\nconst sidebarMenuButtonVariants = cva(\n  // 원본 base의 hover·active 피드백（hover:bg-sidebar-accent hover:text-sidebar-accent-foreground\n  // active:bg-sidebar-accent active:text-sidebar-accent-foreground）를 토큰 대응(bg-accent 등)으로\n  // 복원 — hover 전경색은 RN 미상속이라 내부 Text에도 web:hover 이중 지정한다.\n  // flex-row 필수: 브리지 Pressable 기본이 flex-col이라(아이콘+라벨이 세로로\n  // 쌓이며 h-8에서 라벨이 밀려 보임) 여기서 방향을 덮어쓴다.\n  \"flex flex-row w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm web:hover:bg-accent active:bg-accent web:hover:text-accent-foreground active:text-accent-foreground web:focus-visible:ring-2 web:focus-visible:ring-ring disabled:opacity-50 disabled:web:pointer-events-none\",\n  {\n    variants: {\n      variant: {\n        default: \"\",\n        // 원본 shadow-［0_0_0_1px_var（--sidebar-border）］ → border 근사.\n        // hover 링 색 전환（shadow→sidebar-accent）은 border-accent로 대응.\n        outline: \"border border-border bg-background web:hover:border-accent\",\n      },\n      size: {\n        default: \"h-8 text-sm\",\n        sm: \"h-7 text-xs\",\n        lg: \"h-12 text-sm\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  },\n);\n\nexport type SidebarMenuButtonProps = PressableProps & {\n  isActive?: boolean;\n  /** 접힘 시 툴팁 문구（TooltipContentProps 전달도 지원） */\n  tooltip?: string | React.ComponentProps<typeof TooltipContent>;\n} & VariantProps<typeof sidebarMenuButtonVariants>;\n\nconst SidebarMenuButton = React.forwardRef<\n  BridgePressableRef,\n  SidebarMenuButtonProps\n>(\n  (\n    {\n      isActive = false,\n      variant = \"default\",\n      size = \"default\",\n      tooltip,\n      className,\n      children,\n      onPress,\n      ...props\n    },\n    ref,\n  ) => {\n    const { state, collapsible, isMobile } = useSidebar();\n    const iconCollapsed = state === \"collapsed\" && collapsible === \"icon\";\n\n    const classes = cn(\n      sidebarMenuButtonVariants({ variant, size }),\n      isActive && \"bg-accent font-medium\",\n      iconCollapsed && \"size-8 p-2\",\n      className,\n    );\n    const content = serializableChildren(\n      children,\n      cn(\n        // hover·active 전경색 이중 지정 — 컨테이너 클래스만으론 RN에서 자식이\n        // 물려받지 못하고, 웹에서는 Text 고유 클래스가 상속보다 우선한다.\n        \"font-sans text-sm truncate web:hover:text-accent-foreground active:text-accent-foreground\",\n        isActive ? \"text-accent-foreground\" : \"text-foreground\",\n      ),\n    );\n\n    // 툴팁 없음 또는 전개 상태（라벨이 보임）모바일이면 일반 버튼.\n    if (!tooltip || !iconCollapsed || isMobile) {\n      return (\n        <Pressable\n          ref={ref}\n          accessibilityRole=\"button\"\n          className={classes}\n          onPress={onPress}\n          {...props}\n        >\n          {content}\n        </Pressable>\n      );\n    }\n\n    const tooltipOptions =\n      typeof tooltip === \"string\" ? { children: tooltip } : tooltip;\n\n    // 접힘 상태에서만 툴팁 — press-to-toggle 툴팁을 소비（머리주석 참조）.\n    return (\n      <Tooltip>\n        <TooltipTrigger\n          ref={ref}\n          accessibilityRole=\"button\"\n          className={classes}\n          onPress={onPress}\n          {...props}\n        >\n          {content}\n        </TooltipTrigger>\n        <TooltipContent side=\"right\" align=\"center\" {...tooltipOptions} />\n      </Tooltip>\n    );\n  },\n);\nSidebarMenuButton.displayName = \"SidebarMenuButton\";\n\nexport type SidebarMenuActionProps = PressableProps & {\n  /** 원본 showOnHover 표시 제어는 그룹 셀렉터 의존이라 미지원（항상 표시） */\n  showOnHover?: boolean;\n};\n\nconst SidebarMenuAction = React.forwardRef<\n  BridgePressableRef,\n  SidebarMenuActionProps\n>(({ className, children, onPress, showOnHover: _showOnHover, ...props }, ref) => {\n  const { state, collapsible } = useSidebar();\n  const iconCollapsed = state === \"collapsed\" && collapsible === \"icon\";\n  if (iconCollapsed) return null;\n  return (\n    <Pressable\n      ref={ref}\n      accessibilityRole=\"button\"\n      hitSlop={8}\n      className={cn(\n        \"absolute top-1.5 right-1 aspect-square w-5 items-center justify-center rounded-md p-0\",\n        \"web:focus-visible:ring-2 web:focus-visible:ring-ring active:bg-accent\",\n        className,\n      )}\n      onPress={onPress}\n      {...props}\n    >\n      {serializableChildren(\n        children,\n        \"font-sans text-sm text-foreground active:text-accent-foreground\",\n      )}\n    </Pressable>\n  );\n});\nSidebarMenuAction.displayName = \"SidebarMenuAction\";\n\nexport type SidebarMenuBadgeProps = ViewProps;\n\nconst SidebarMenuBadge = React.forwardRef<\n  BridgeViewRef,\n  SidebarMenuBadgeProps\n>(({ className, children, ...props }, ref) => {\n  const { state, collapsible } = useSidebar();\n  const iconCollapsed = state === \"collapsed\" && collapsible === \"icon\";\n  if (iconCollapsed) return null;\n  return (\n    <View\n      ref={ref}\n      className={cn(\n        \"absolute top-1.5 right-1 h-5 min-w-5 flex-row items-center justify-center rounded-md px-1\",\n        \"web:select-none web:tabular-nums\",\n        className,\n      )}\n      {...props}\n    >\n      {serializableChildren(children, \"font-sans text-xs font-medium text-foreground\")}\n    </View>\n  );\n});\nSidebarMenuBadge.displayName = \"SidebarMenuBadge\";\n\nexport type SidebarMenuSkeletonProps = ViewProps & {\n  showIcon?: boolean;\n};\n\nconst SidebarMenuSkeleton = React.forwardRef<\n  BridgeViewRef,\n  SidebarMenuSkeletonProps\n>(({ className, showIcon = false, ...props }, ref) => {\n  // 50~90% 랜덤 폭 — 원본 CSS 변수 대신 style maxWidth 직접 값.\n  const width = React.useMemo(() => {\n    return `${Math.floor(Math.random() * 40) + 50}%`;\n  }, []);\n  return (\n    <View\n      ref={ref}\n      className={cn(\"h-8 flex-row items-center gap-2 rounded-md px-2\", className)}\n      {...props}\n    >\n      {showIcon && <Skeleton className=\"size-4 rounded-md\" />}\n      <Skeleton className=\"h-4 flex-1 rounded-md\" style={{ maxWidth: width }} />\n    </View>\n  );\n});\nSidebarMenuSkeleton.displayName = \"SidebarMenuSkeleton\";\n\nexport type SidebarMenuSubProps = ViewProps;\n\nconst SidebarMenuSub = React.forwardRef<BridgeViewRef, SidebarMenuSubProps>(\n  ({ className, ...props }, ref) => {\n    const { state, collapsible } = useSidebar();\n    const iconCollapsed = state === \"collapsed\" && collapsible === \"icon\";\n    if (iconCollapsed) return null;\n    return (\n      <View\n        ref={ref}\n        accessibilityRole=\"list\"\n        className={cn(\n          \"mx-3.5 min-w-0 flex-col gap-1 border-l border-border px-2.5 py-0.5\",\n          className,\n        )}\n        {...props}\n      />\n    );\n  },\n);\nSidebarMenuSub.displayName = \"SidebarMenuSub\";\n\nexport type SidebarMenuSubItemProps = ViewProps;\n\nconst SidebarMenuSubItem = React.forwardRef<\n  BridgeViewRef,\n  SidebarMenuSubItemProps\n>(({ className, ...props }, ref) => (\n  <View\n    ref={ref}\n    accessibilityRole=\"listitem\"\n    className={cn(\"relative\", className)}\n    {...props}\n  />\n));\nSidebarMenuSubItem.displayName = \"SidebarMenuSubItem\";\n\nexport type SidebarMenuSubButtonProps = PressableProps & {\n  size?: \"sm\" | \"md\";\n  isActive?: boolean;\n};\n\nconst SidebarMenuSubButton = React.forwardRef<\n  BridgePressableRef,\n  SidebarMenuSubButtonProps\n>(\n  (\n    { size = \"md\", isActive = false, className, children, onPress, ...props },\n    ref,\n  ) => {\n    const { state, collapsible } = useSidebar();\n    const iconCollapsed = state === \"collapsed\" && collapsible === \"icon\";\n    if (iconCollapsed) return null;\n    return (\n      <Pressable\n        ref={ref}\n        // 원본이 <a> 기반이라 링크 역할로 근사（href 계약은 부재）.\n        accessibilityRole=\"link\"\n        className={cn(\n          // 원본 hover·active 피드백 복원（전경색은 내부 Text 이중 지정）.\n          \"h-7 min-w-0 w-full flex-row items-center gap-2 overflow-hidden rounded-md px-2 web:hover:bg-accent active:bg-accent web:hover:text-accent-foreground active:text-accent-foreground\",\n          isActive && \"bg-accent\",\n          className,\n        )}\n        onPress={onPress}\n        {...props}\n      >\n        {serializableChildren(\n          children,\n          cn(\n            \"font-sans truncate web:hover:text-accent-foreground active:text-accent-foreground\",\n            size === \"sm\" ? \"text-xs\" : \"text-sm\",\n            isActive ? \"text-accent-foreground\" : \"text-foreground\",\n          ),\n        )}\n      </Pressable>\n    );\n  },\n);\nSidebarMenuSubButton.displayName = \"SidebarMenuSubButton\";\n\nexport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarGroup,\n  SidebarGroupAction,\n  SidebarGroupContent,\n  SidebarGroupLabel,\n  SidebarHeader,\n  SidebarInput,\n  SidebarInset,\n  SidebarMenu,\n  SidebarMenuAction,\n  SidebarMenuBadge,\n  SidebarMenuButton,\n  sidebarMenuButtonVariants,\n  SidebarMenuItem,\n  SidebarMenuSkeleton,\n  SidebarMenuSub,\n  SidebarMenuSubButton,\n  SidebarMenuSubItem,\n  SidebarProvider,\n  SidebarRail,\n  SidebarSeparator,\n  SidebarTrigger,\n};\n",
      "type": "registry:ui",
      "target": "components/ui/sidebar.tsx"
    }
  ],
  "type": "registry:ui"
}
