Glrk UI

Menubar

A simplified wrapper for Shadcn Menubar with flexible option handling and automatic type conversion

Installation

npx shadcn@latest add @glrk-ui/menubar

If you haven't set up the prerequisites yet, check out Prerequest section.

Update following code in ui/menubar.tsx

function MenubarCheckboxItem({
  className,
  children,
  checked,
  indicatorAt = "right",
  ...props
}: React.ComponentProps<typeof MenubarPrimitive.CheckboxItem> & { indicatorAt?: indicatorAtT }) {
  return (
    <MenubarPrimitive.CheckboxItem
      data-slot="menubar-checkbox-item"
      className={cn(
        "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
        className,
        indicatorAt === "right" ? "pr-8 pl-2" : "pr-2 pl-8",
      )}
      checked={checked}
      {...props}
    >
      <span className={cn("pointer-events-none absolute flex size-3.5 items-center justify-center", indicatorAt === "right" ? "right-2" : "left-2")}>
        <MenubarPrimitive.ItemIndicator>
          <CheckIcon className="size-4" />
        </MenubarPrimitive.ItemIndicator>
      </span>
      {children}
    </MenubarPrimitive.CheckboxItem>
  )
}

function MenubarRadioItem({
  className,
  children,
  indicatorAt = "right",
  ...props
}: React.ComponentProps<typeof MenubarPrimitive.RadioItem> & { indicatorAt?: indicatorAtT }) {
  return (
    <MenubarPrimitive.RadioItem
      data-slot="menubar-radio-item"
      className={cn(
        "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
        className,
        indicatorAt === "right" ? "pr-8 pl-2" : "pr-2 pl-8",
      )}
      {...props}
    >
      <span className={cn("pointer-events-none absolute flex size-3.5 items-center justify-center", indicatorAt === "right" ? "right-2" : "left-2")}>
        <MenubarPrimitive.ItemIndicator>
          <CircleIcon className="size-2 fill-current" />
        </MenubarPrimitive.ItemIndicator>
      </span>
      {children}
    </MenubarPrimitive.RadioItem>
  )
}

Create new file name types/menu.d.ts.

types/menu.d.ts
type menuOptionT = allowedPrimitiveT | optionT | (optionT & {
  variant?: "default" | "destructive"
  shortcut?: string
  disabled?: boolean
})

type menuGroupT = {
  group: string
  options: menuOptionT[]
  className?: string
  groupLabelCls?: string
}

type subMenuT = {
  submenu: string
  options: (menuOptionT | menuGroupT)[]
  triggerCls?: string
  contentCls?: string
}

type menuOptionsT = (menuOptionT | menuGroupT | subMenuT)[]

type menuInputOptionT = allowedPrimitiveT | optionT | (optionT & {
  disabled?: boolean
})

type menuInputGroupT = {
  group: string
  options: menuInputOptionT[]
  className?: string
  groupLabelCls?: string
}

type inputSubMenuT = {
  submenu: string
  options: (menuInputOptionT | menuInputGroupT)[]
  triggerCls?: string
  contentCls?: string
}

type menuInputOptionsT = (menuInputOptionT | menuInputGroupT | inputSubMenuT)[]

Add following lines to lib/utils.ts

export const isGroupMenu = optionTypeChecker<menuGroupT>("group")
export const isSubMenu = optionTypeChecker<subMenuT>("submenu")
export const isInputGroupMenu = optionTypeChecker<menuInputGroupT>("group")
export const isInputSubMenu = optionTypeChecker<inputSubMenuT>("submenu")

Create new file name ui/menubar-wrapper.tsx.

ui/menubar-wrapper.tsx
"use client"

import { useState } from "react"
import {
  cn, getKey, getLabel, getValue,
  isSeparator,
  isSubMenu,
  isGroupMenu,
  isInputSubMenu,
  isInputGroupMenu,
  parseAllowedPrimitive,
} from "@/lib/utils"

import {
  Menubar,
  MenubarTrigger,
  MenubarContent,
  MenubarGroup,
  MenubarLabel,
  MenubarItem,
  MenubarCheckboxItem,
  MenubarRadioGroup,
  MenubarRadioItem,
  MenubarSeparator,
  MenubarShortcut,
  MenubarSub,
  MenubarSubTrigger,
  MenubarSubContent,
  MenubarMenu,
} from "@/components/ui/menubar"

type commomClsT = {
  itemCls?: string
  groupCls?: string
  groupLabelCls?: string
}

type commonCheckboxProps = {
  checked?: allowedPrimitiveT[]
  indicatorAt?: indicatorAtT
  onCheckedChange?: (value: allowedPrimitiveT, checked: boolean) => void
}

type commonRadioProps = {
  value?: allowedPrimitiveT
  indicatorAt?: indicatorAtT
  onValueChange?: (value: allowedPrimitiveT) => void
}

type commonInner = commomClsT & {
  trigger: React.ReactNode
  contentProps?: React.ComponentProps<typeof MenubarContent>
}

type menubarBaseT = commomClsT & {
  key: string
  trigger: React.ReactNode
  contentProps?: React.ComponentProps<typeof MenubarContent>
}

type menubarOptionsT = (menubarBaseT & {
  options: menuOptionsT
  onSelect?: (value: allowedPrimitiveT) => void
})[]

type menubarInputOptionT = menubarBaseT & {
  options: menuInputOptionsT
}

type menubarCheckboxOptionsT = (menubarInputOptionT & commonCheckboxProps)[]
type menubarRadioOptionsT = (menubarInputOptionT & commonRadioProps)[]

type commonWrapT = commomClsT & Omit<React.ComponentProps<typeof Menubar>, "children" | "asChild" | "value"> & {
  contentProps?: React.ComponentProps<typeof MenubarContent>
}

// -------

type itemProps = {
  option: menuOptionT
  className?: string
  onSelect?: () => void
}
function Item({
  option,
  className,
  onSelect
}: itemProps) {
  const value = getValue(option)

  if (isSeparator(value)) return <MenubarSeparator className={className} />

  const label = getLabel(option)
  const opt: any = typeof option === "object" ? option : {}
  const shortcut = opt?.shortcut

  return (
    <MenubarItem
      {...opt}
      onSelect={onSelect}
      className={cn(className, opt?.className)}
    >
      {label}
      {shortcut && <MenubarShortcut>{shortcut}</MenubarShortcut>}
    </MenubarItem>
  )
}

type checkboxItemProps = {
  option: menuInputOptionT
  className?: string
  checked?: boolean
  onCheckedChange?: (checked: boolean) => void
  indicatorAt?: indicatorAtT
}
function CheckboxItem({
  option,
  className,
  checked = false,
  indicatorAt,
  onCheckedChange = () => { }
}: checkboxItemProps) {
  const value = getValue(option)

  if (isSeparator(value)) return <MenubarSeparator className={className} />

  const label = getLabel(option)
  const disabled = (option as any)?.disabled

  return (
    <MenubarCheckboxItem
      checked={checked}
      disabled={disabled}
      className={className}
      indicatorAt={indicatorAt}
      onCheckedChange={onCheckedChange}
    >
      {label}
    </MenubarCheckboxItem>
  )
}

type radioItemProps = {
  option: menuInputOptionT
  className?: string
  indicatorAt?: indicatorAtT
}
function RadioItem({
  option,
  className,
  indicatorAt
}: radioItemProps) {
  const value = getValue(option)

  if (isSeparator(value)) return <MenubarSeparator className={className} />

  const label = getLabel(option)
  const disabled = (option as any)?.disabled

  return (
    <MenubarRadioItem
      value={`${value}`}
      disabled={disabled}
      className={className}
      indicatorAt={indicatorAt}
    >
      {label}
    </MenubarRadioItem>
  )
}

type SubMenuProps = commomClsT & {
  submenu: subMenuT
  onSelect?: (value: allowedPrimitiveT) => void
}
function SubMenu({
  submenu,
  itemCls,
  groupCls,
  groupLabelCls,
  onSelect
}: SubMenuProps) {
  return (
    <MenubarSub>
      <MenubarSubTrigger className={submenu.triggerCls}>
        {submenu.submenu}
      </MenubarSubTrigger>

      <MenubarSubContent className={submenu.contentCls}>
        {submenu.options.map((option, i) => {
          if (isGroupMenu(option)) {
            return (
              <MenubarGroup key={option.group} className={cn(groupCls, option.className)}>
                <MenubarLabel className={cn("pb-0.5 text-xs text-muted-foreground font-normal", groupLabelCls, option.groupLabelCls)}>
                  {option.group}
                </MenubarLabel>

                {option.options.map((grOpt, j) => (
                  <Item
                    key={getKey(grOpt, j)}
                    option={grOpt}
                    className={itemCls}
                    onSelect={() => onSelect?.(getValue(grOpt))}
                  />
                ))}
              </MenubarGroup>
            )
          }

          if (isSubMenu(option)) {
            return (
              <SubMenu
                key={getKey(option, i)}
                submenu={option}
                itemCls={itemCls}
                groupCls={groupCls}
                onSelect={onSelect}
              />
            )
          }

          return (
            <Item
              key={getKey(option, i)}
              option={option}
              className={itemCls}
              onSelect={() => onSelect?.(getValue(option))}
            />
          )
        })}
      </MenubarSubContent>
    </MenubarSub>
  )
}

type CheckboxSubMenuProps = commomClsT & commonCheckboxProps & {
  submenu: inputSubMenuT
}
function CheckboxSubMenu({
  submenu,
  itemCls,
  groupCls,
  groupLabelCls,
  checked = [],
  indicatorAt,
  onCheckedChange = () => { }
}: CheckboxSubMenuProps) {
  return (
    <MenubarSub>
      <MenubarSubTrigger className={submenu.triggerCls}>
        {submenu.submenu}
      </MenubarSubTrigger>

      <MenubarSubContent className={submenu.contentCls}>
        {submenu.options.map((option, i) => {
          if (isInputGroupMenu(option)) {
            return (
              <MenubarGroup key={option.group} className={cn(groupCls, option.className)}>
                <MenubarLabel className={cn("pb-0.5 text-xs text-muted-foreground font-normal", groupLabelCls, option.groupLabelCls)}>
                  {option.group}
                </MenubarLabel>

                {option.options.map((grOpt, j) => {
                  const v = getValue(grOpt)
                  return (
                    <CheckboxItem
                      key={getKey(grOpt, j)}
                      option={grOpt}
                      checked={checked.includes(v)}
                      className={itemCls}
                      indicatorAt={indicatorAt}
                      onCheckedChange={(checked) => onCheckedChange?.(v, checked)}
                    />
                  )
                })}
              </MenubarGroup>
            )
          }

          if (isInputSubMenu(option)) {
            return (
              <CheckboxSubMenu
                key={option.submenu}
                submenu={option}
                checked={checked}
                itemCls={itemCls}
                groupCls={groupCls}
                indicatorAt={indicatorAt}
                groupLabelCls={groupLabelCls}
                onCheckedChange={onCheckedChange}
              />
            )
          }

          const v = getValue(option)
          return (
            <CheckboxItem
              key={getKey(option, i)}
              option={option}
              checked={checked.includes(v)}
              className={itemCls}
              indicatorAt={indicatorAt}
              onCheckedChange={(checked) => onCheckedChange?.(v, checked)}
            />
          )
        })}
      </MenubarSubContent>
    </MenubarSub>
  )
}

type RadioSubMenuProps = commomClsT & commonRadioProps & {
  submenu: inputSubMenuT
}
function RadioSubMenu({
  submenu,
  itemCls,
  groupCls,
  groupLabelCls,
  value = "",
  indicatorAt,
  onValueChange = () => { }
}: RadioSubMenuProps) {
  return (
    <MenubarSub>
      <MenubarSubTrigger className={submenu.triggerCls}>
        {submenu.submenu}
      </MenubarSubTrigger>

      <MenubarSubContent className={submenu.contentCls}>
        <MenubarRadioGroup value={`${value}`} onValueChange={onValueChange}>
          {submenu.options.map((option, i) => {
            if (isInputGroupMenu(option)) {
              return (
                <MenubarGroup key={option.group} className={cn(groupCls, option.className)}>
                  <MenubarLabel className={cn("pb-0.5 text-xs text-muted-foreground font-normal", groupLabelCls, option.groupLabelCls)}>
                    {option.group}
                  </MenubarLabel>

                  {option.options.map((grOpt, j) => (
                    <RadioItem
                      key={getKey(grOpt, j)}
                      option={grOpt}
                      className={itemCls}
                      indicatorAt={indicatorAt}
                    />
                  ))}
                </MenubarGroup>
              )
            }

            if (isInputSubMenu(option)) {
              return (
                <RadioSubMenu
                  key={option.submenu}
                  value={value}
                  submenu={option}
                  itemCls={itemCls}
                  groupCls={groupCls}
                  indicatorAt={indicatorAt}
                  groupLabelCls={groupLabelCls}
                  onValueChange={onValueChange}
                />
              )
            }

            return (
              <RadioItem
                key={getKey(option, i)}
                option={option}
                className={itemCls}
                indicatorAt={indicatorAt}
              />
            )
          })}
        </MenubarRadioGroup>
      </MenubarSubContent>
    </MenubarSub>
  )
}

type wrapperInner = commonInner & {
  options: menuOptionsT
  onSelect?: (value: allowedPrimitiveT) => void
}
function MenubarWrapperInner({
  trigger,
  options,
  itemCls,
  groupCls,
  groupLabelCls,
  contentProps,
  onSelect
}: wrapperInner) {
  return (
    <MenubarMenu>
      <MenubarTrigger asChild={typeof trigger !== "string"}>
        {trigger}
      </MenubarTrigger>

      <MenubarContent {...contentProps}>
        {options.map((option, i) => {
          if (isGroupMenu(option)) {
            return (
              <MenubarGroup key={option.group} className={cn(groupCls, option.className)}>
                <MenubarLabel className={cn("pb-0.5 text-xs text-muted-foreground font-normal", groupLabelCls, option.groupLabelCls)}>
                  {option.group}
                </MenubarLabel>

                {option.options.map((grOpt, j) => (
                  <Item
                    key={getKey(grOpt, j)}
                    option={grOpt}
                    className={itemCls}
                    onSelect={() => onSelect?.(getValue(grOpt))}
                  />
                ))}
              </MenubarGroup>
            )
          }

          if (isSubMenu(option)) {
            return (
              <SubMenu
                key={option.submenu}
                submenu={option}
                itemCls={itemCls}
                groupCls={groupCls}
                groupLabelCls={groupLabelCls}
                onSelect={onSelect}
              />
            )
          }

          return (
            <Item
              key={getKey(option, i)}
              option={option}
              className={itemCls}
              onSelect={() => onSelect?.(getValue(option))}
            />
          )
        })}
      </MenubarContent>
    </MenubarMenu>
  )
}

type checkboxWrapperInner = commonInner & commonCheckboxProps & {
  options: menuInputOptionsT
  label?: string
}
function MenubarCheckboxWrapperInner({
  trigger,
  options,

  label,
  contentProps,
  itemCls,
  groupCls,
  groupLabelCls,

  checked: o_checked,
  onCheckedChange: o_onCheckedChange,

  indicatorAt,
}: checkboxWrapperInner) {
  const [i_checked, setIChecked] = useState<allowedPrimitiveT[]>([])

  function i_Checked(v: allowedPrimitiveT, c: boolean) {
    setIChecked(prev => !c ? prev.filter(p => p !== v) : [...prev, v])
  }

  const checked = o_checked ?? i_checked
  const onCheckedChange = o_onCheckedChange ?? i_Checked

  return (
    <MenubarMenu>
      <MenubarTrigger asChild={typeof trigger !== "string"}>
        {trigger}
      </MenubarTrigger>

      <MenubarContent {...contentProps}>
        {label && (
          <>
            <MenubarLabel>{label}</MenubarLabel>
            <MenubarSeparator />
          </>
        )}
        {options.map((option, i) => {
          if (isInputGroupMenu(option)) {
            return (
              <MenubarGroup key={option.group} className={cn(groupCls, option.className)}>
                <MenubarLabel className={cn("pb-0.5 text-xs text-muted-foreground font-normal", groupLabelCls, option.groupLabelCls)}>
                  {option.group}
                </MenubarLabel>

                {option.options.map((grOpt, j) => {
                  const v = getValue(grOpt)
                  return (
                    <CheckboxItem
                      key={getKey(grOpt, j)}
                      option={grOpt}
                      checked={checked.includes(v)}
                      className={itemCls}
                      indicatorAt={indicatorAt}
                      onCheckedChange={(checked) => onCheckedChange?.(v, checked)}
                    />
                  )
                })}
              </MenubarGroup>
            )
          }

          if (isInputSubMenu(option)) {
            return (
              <CheckboxSubMenu
                key={option.submenu}
                submenu={option}
                checked={checked}
                itemCls={itemCls}
                groupCls={groupCls}
                indicatorAt={indicatorAt}
                groupLabelCls={groupLabelCls}
                onCheckedChange={onCheckedChange}
              />
            )
          }

          const v = getValue(option)
          return (
            <CheckboxItem
              key={getKey(option, i)}
              option={option}
              checked={checked.includes(v)}
              className={itemCls}
              indicatorAt={indicatorAt}
              onCheckedChange={(checked) => onCheckedChange?.(v, checked)}
            />
          )
        })}
      </MenubarContent>
    </MenubarMenu>
  )
}

type radioWrapperInner = commonInner & commonRadioProps & {
  options: menuInputOptionsT
  label?: string
}
function MenubarRadioWrapperInner({
  trigger,
  options,

  label,
  itemCls,
  groupCls,
  groupLabelCls,
  contentProps,

  value: o_value,
  onValueChange: o_onValueChange,

  indicatorAt,
}: radioWrapperInner) {
  const [i_value, setIValue] = useState<allowedPrimitiveT>("")

  const value = o_value ?? i_value
  const onValueChange = o_onValueChange ?? setIValue

  return (
    <MenubarMenu>
      <MenubarTrigger asChild={typeof trigger !== "string"}>
        {trigger}
      </MenubarTrigger>

      <MenubarContent {...contentProps}>
        {label && (
          <>
            <MenubarLabel>{label}</MenubarLabel>
            <MenubarSeparator />
          </>
        )}
        <MenubarRadioGroup value={`${value}`} onValueChange={v => onValueChange(parseAllowedPrimitive(v))}>
          {options.map((option, i) => {
            if (isInputGroupMenu(option)) {
              return (
                <MenubarGroup key={option.group} className={cn(groupCls, option.className)}>
                  <MenubarLabel className={cn("pb-0.5 text-xs text-muted-foreground font-normal", groupLabelCls, option.groupLabelCls)}>
                    {option.group}
                  </MenubarLabel>

                  {option.options.map((grOpt, j) => (
                    <RadioItem
                      key={getKey(grOpt, j)}
                      option={grOpt}
                      className={itemCls}
                      indicatorAt={indicatorAt}
                    />
                  ))}
                </MenubarGroup>
              )
            }

            if (isInputSubMenu(option)) {
              return (
                <RadioSubMenu
                  key={option.submenu}
                  value={value}
                  submenu={option}
                  itemCls={itemCls}
                  groupCls={groupCls}
                  indicatorAt={indicatorAt}
                  groupLabelCls={groupLabelCls}
                  onValueChange={v => onValueChange(parseAllowedPrimitive(v))}
                />
              )
            }

            return (
              <RadioItem
                key={getKey(option, i)}
                option={option}
                className={itemCls}
                indicatorAt={indicatorAt}
              />
            )
          })}
        </MenubarRadioGroup>
      </MenubarContent>
    </MenubarMenu>
  )
}

type wrap = commonWrapT & {
  options: menubarOptionsT
  onSelect?: (value: allowedPrimitiveT) => void
}
function MenubarWrapper({
  options,
  itemCls,
  groupCls,
  groupLabelCls,
  contentProps,
  onSelect,
  ...props
}: wrap) {
  return (
    <Menubar {...props}>
      {
        options.map(op => (
          <MenubarWrapperInner
            key={op.key}
            trigger={op.trigger}
            options={op.options}
            itemCls={cn(itemCls, op.itemCls)}
            groupCls={cn(groupCls, op.groupCls)}
            groupLabelCls={cn(groupLabelCls, op.groupLabelCls)}
            contentProps={{ ...contentProps, ...op?.contentProps }}
            onSelect={op?.onSelect || onSelect}
          />
        ))
      }
    </Menubar>
  )
}

type wrapCheckboxT = commonWrapT & commonCheckboxProps & {
  options: menubarCheckboxOptionsT
}
function MenubarCheckboxWrapper({
  options,

  contentProps,
  itemCls,
  groupCls,
  groupLabelCls,

  checked,
  onCheckedChange,

  indicatorAt,
  ...props
}: wrapCheckboxT) {
  return (
    <Menubar {...props}>
      {options.map(op => (
        <MenubarCheckboxWrapperInner
          key={op.key}
          trigger={op.trigger}
          options={op.options}
          itemCls={cn(itemCls, op.itemCls)}
          groupCls={cn(groupCls, op.groupCls)}
          groupLabelCls={cn(groupLabelCls, op.groupLabelCls)}
          contentProps={{ ...contentProps, ...op?.contentProps }}
          onCheckedChange={op.onCheckedChange ?? onCheckedChange}
          indicatorAt={op.indicatorAt ?? indicatorAt}
          checked={op.checked ?? checked}
        />
      ))}
    </Menubar>
  )
}

type wrapRadioT = commonWrapT & commonRadioProps & {
  options: menubarRadioOptionsT
}
function MenubarRadioWrapper({
  options,

  contentProps,
  itemCls,
  groupCls,
  groupLabelCls,

  value,
  onValueChange,

  indicatorAt,
  ...props
}: wrapRadioT) {
  return (
    <Menubar {...props}>
      {options.map(op => (
        <MenubarRadioWrapperInner
          key={op.key}
          trigger={op.trigger}
          options={op.options}
          itemCls={cn(itemCls, op.itemCls)}
          groupCls={cn(groupCls, op.groupCls)}
          groupLabelCls={cn(groupLabelCls, op.groupLabelCls)}
          contentProps={{ ...contentProps, ...op?.contentProps }}
          onValueChange={op.onValueChange ?? onValueChange}
          indicatorAt={op.indicatorAt ?? indicatorAt}
          value={op.value ?? value}
        />
      ))}
    </Menubar>
  )
}

export {
  MenubarWrapper,
  MenubarCheckboxWrapper,
  MenubarRadioWrapper,
  type menubarOptionsT,
  type menubarCheckboxOptionsT,
  type menubarRadioOptionsT,
}

Installation

npm install @radix-ui/react-menubar

Copy and paste the following code into your project.

general.d.ts

types/general.d.ts
type readOnlyChildren = Readonly<{
  children: React.ReactNode;
}>

type allowedPrimitiveT = string | number | boolean

type optionT = {
  label: React.ReactNode
  value: allowedPrimitiveT
  className?: string
}

type groupT = {
  group: string
  options: (allowedPrimitiveT | optionT)[]
  className?: string
}

type optionsT = (allowedPrimitiveT | optionT | groupT)[]

type indicatorAtT = "right" | "left"
types/menu.d.ts
type menuOptionT = allowedPrimitiveT | optionT | (optionT & {
  variant?: "default" | "destructive"
  shortcut?: string
  disabled?: boolean
})

type menuGroupT = {
  group: string
  options: menuOptionT[]
  className?: string
  groupLabelCls?: string
}

type subMenuT = {
  submenu: string
  options: (menuOptionT | menuGroupT)[]
  triggerCls?: string
  contentCls?: string
}

type menuOptionsT = (menuOptionT | menuGroupT | subMenuT)[]

type menuInputOptionT = allowedPrimitiveT | optionT | (optionT & {
  disabled?: boolean
})

type menuInputGroupT = {
  group: string
  options: menuInputOptionT[]
  className?: string
  groupLabelCls?: string
}

type inputSubMenuT = {
  submenu: string
  options: (menuInputOptionT | menuInputGroupT)[]
  triggerCls?: string
  contentCls?: string
}

type menuInputOptionsT = (menuInputOptionT | menuInputGroupT | inputSubMenuT)[]

utils.ts

lib/utils.ts
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

export function isAllowedPrimitive(value: unknown): value is allowedPrimitiveT {
  return ["string", "number", "boolean"].includes(typeof value)
}

export function parseAllowedPrimitive(value: allowedPrimitiveT): allowedPrimitiveT {
  if (typeof value !== "string") return value

  const trimmed = value.trim()

  if (trimmed === "true") return true
  if (trimmed === "false") return false
  if (/^-?\d+(\.\d+)?$/.test(trimmed)) return Number(trimmed)

  return trimmed
}

export function optionTypeChecker<T>(key: keyof T) {
  return (option: any): option is T => !!option && typeof option === "object" && key in option
}

export const isSeparator = (item: any) => item === "---"
export const isOption = optionTypeChecker<optionT>("value")
export const isGroup = optionTypeChecker<groupT>("group")

export const getValue = (item: allowedPrimitiveT | optionT) => typeof item === "object" ? item.value : item
export const getLabel = (item: allowedPrimitiveT | optionT) => typeof item === "object" ? item.label : `${item}`

export function getKey(item: allowedPrimitiveT | optionT, i: number): string {
  const val = getValue(item)
  if (typeof val === "boolean") return `key-${val}`
  if (val === "---") return `---${i}`
  return `${val}`
}

export const isSubMenu = optionTypeChecker<subMenuT>("submenu")
export const isGroupMenu = optionTypeChecker<menuGroupT>("group")
export const isInputSubMenu = optionTypeChecker<inputSubMenuT>("submenu")
export const isInputGroupMenu = optionTypeChecker<menuInputGroupT>("group")
ui/menubar.tsx
"use client"

import * as React from "react"
import * as MenubarPrimitive from "@radix-ui/react-menubar"
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"

import { cn } from "@/lib/utils"

function Menubar({
  className,
  ...props
}: React.ComponentProps<typeof MenubarPrimitive.Root>) {
  return (
    <MenubarPrimitive.Root
      data-slot="menubar"
      className={cn(
        "bg-background flex h-9 items-center gap-1 rounded-md border p-1 shadow-xs",
        className
      )}
      {...props}
    />
  )
}

function MenubarMenu(props: React.ComponentProps<typeof MenubarPrimitive.Menu>) {
  return <MenubarPrimitive.Menu data-slot="menubar-menu" {...props} />
}

function MenubarGroup(props: React.ComponentProps<typeof MenubarPrimitive.Group>) {
  return <MenubarPrimitive.Group data-slot="menubar-group" {...props} />
}

function MenubarPortal(props: React.ComponentProps<typeof MenubarPrimitive.Portal>) {
  return <MenubarPrimitive.Portal data-slot="menubar-portal" {...props} />
}

function MenubarRadioGroup(props: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) {
  return (
    <MenubarPrimitive.RadioGroup data-slot="menubar-radio-group" {...props} />
  )
}

function MenubarTrigger({
  className,
  ...props
}: React.ComponentProps<typeof MenubarPrimitive.Trigger>) {
  return (
    <MenubarPrimitive.Trigger
      data-slot="menubar-trigger"
      className={cn(
        "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex items-center rounded-sm px-2 py-1 text-sm font-medium outline-hidden select-none",
        className
      )}
      {...props}
    />
  )
}

function MenubarContent({
  className,
  align = "start",
  alignOffset = -4,
  sideOffset = 8,
  ...props
}: React.ComponentProps<typeof MenubarPrimitive.Content>) {
  return (
    <MenubarPortal>
      <MenubarPrimitive.Content
        data-slot="menubar-content"
        align={align}
        alignOffset={alignOffset}
        sideOffset={sideOffset}
        className={cn(
          "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[12rem] origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-md",
          className
        )}
        {...props}
      />
    </MenubarPortal>
  )
}

function MenubarItem({
  className,
  inset,
  variant = "default",
  ...props
}: React.ComponentProps<typeof MenubarPrimitive.Item> & {
  inset?: boolean
  variant?: "default" | "destructive"
}) {
  return (
    <MenubarPrimitive.Item
      data-slot="menubar-item"
      data-inset={inset}
      data-variant={variant}
      className={cn(
        "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
        className
      )}
      {...props}
    />
  )
}

function MenubarCheckboxItem({
  className,
  children,
  checked,
  indicatorAt = "right",
  ...props
}: React.ComponentProps<typeof MenubarPrimitive.CheckboxItem> & { indicatorAt?: indicatorAtT }) {
  return (
    <MenubarPrimitive.CheckboxItem
      data-slot="menubar-checkbox-item"
      className={cn(
        "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
        className,
        indicatorAt === "right" ? "pr-8 pl-2" : "pr-2 pl-8",
      )}
      checked={checked}
      {...props}
    >
      <span className={cn("pointer-events-none absolute flex size-3.5 items-center justify-center", indicatorAt === "right" ? "right-2" : "left-2")}>
        <MenubarPrimitive.ItemIndicator>
          <CheckIcon className="size-4" />
        </MenubarPrimitive.ItemIndicator>
      </span>
      {children}
    </MenubarPrimitive.CheckboxItem>
  )
}

function MenubarRadioItem({
  className,
  children,
  indicatorAt = "right",
  ...props
}: React.ComponentProps<typeof MenubarPrimitive.RadioItem> & { indicatorAt?: indicatorAtT }) {
  return (
    <MenubarPrimitive.RadioItem
      data-slot="menubar-radio-item"
      className={cn(
        "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
        className,
        indicatorAt === "right" ? "pr-8 pl-2" : "pr-2 pl-8",
      )}
      {...props}
    >
      <span className={cn("pointer-events-none absolute flex size-3.5 items-center justify-center", indicatorAt === "right" ? "right-2" : "left-2")}>
        <MenubarPrimitive.ItemIndicator>
          <CircleIcon className="size-2 fill-current" />
        </MenubarPrimitive.ItemIndicator>
      </span>
      {children}
    </MenubarPrimitive.RadioItem>
  )
}

function MenubarLabel({
  className,
  inset,
  ...props
}: React.ComponentProps<typeof MenubarPrimitive.Label> & {
  inset?: boolean
}) {
  return (
    <MenubarPrimitive.Label
      data-slot="menubar-label"
      data-inset={inset}
      className={cn(
        "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
        className
      )}
      {...props}
    />
  )
}

function MenubarSeparator({
  className,
  ...props
}: React.ComponentProps<typeof MenubarPrimitive.Separator>) {
  return (
    <MenubarPrimitive.Separator
      data-slot="menubar-separator"
      className={cn("bg-border -mx-1 my-1 h-px", className)}
      {...props}
    />
  )
}

function MenubarShortcut({
  className,
  ...props
}: React.ComponentProps<"span">) {
  return (
    <span
      data-slot="menubar-shortcut"
      className={cn(
        "text-muted-foreground ml-auto text-xs tracking-widest",
        className
      )}
      {...props}
    />
  )
}

function MenubarSub(props: React.ComponentProps<typeof MenubarPrimitive.Sub>) {
  return <MenubarPrimitive.Sub data-slot="menubar-sub" {...props} />
}

function MenubarSubTrigger({
  className,
  inset,
  children,
  ...props
}: React.ComponentProps<typeof MenubarPrimitive.SubTrigger> & {
  inset?: boolean
}) {
  return (
    <MenubarPrimitive.SubTrigger
      data-slot="menubar-sub-trigger"
      data-inset={inset}
      className={cn(
        "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-none select-none data-[inset]:pl-8",
        className
      )}
      {...props}
    >
      {children}
      <ChevronRightIcon className="ml-auto h-4 w-4" />
    </MenubarPrimitive.SubTrigger>
  )
}

function MenubarSubContent({
  className,
  ...props
}: React.ComponentProps<typeof MenubarPrimitive.SubContent>) {
  return (
    <MenubarPrimitive.SubContent
      data-slot="menubar-sub-content"
      className={cn(
        "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
        className
      )}
      {...props}
    />
  )
}

export {
  Menubar,
  MenubarPortal,
  MenubarMenu,
  MenubarTrigger,
  MenubarContent,
  MenubarGroup,
  MenubarSeparator,
  MenubarLabel,
  MenubarItem,
  MenubarShortcut,
  MenubarCheckboxItem,
  MenubarRadioGroup,
  MenubarRadioItem,
  MenubarSub,
  MenubarSubTrigger,
  MenubarSubContent,
}
ui/menubar-wrapper.tsx
"use client"

import { useState } from "react"
import {
  cn, getKey, getLabel, getValue,
  isSeparator,
  isSubMenu,
  isGroupMenu,
  isInputSubMenu,
  isInputGroupMenu,
  parseAllowedPrimitive,
} from "@/lib/utils"

import {
  Menubar,
  MenubarTrigger,
  MenubarContent,
  MenubarGroup,
  MenubarLabel,
  MenubarItem,
  MenubarCheckboxItem,
  MenubarRadioGroup,
  MenubarRadioItem,
  MenubarSeparator,
  MenubarShortcut,
  MenubarSub,
  MenubarSubTrigger,
  MenubarSubContent,
  MenubarMenu,
} from "@/components/ui/menubar"

type commomClsT = {
  itemCls?: string
  groupCls?: string
  groupLabelCls?: string
}

type commonCheckboxProps = {
  checked?: allowedPrimitiveT[]
  indicatorAt?: indicatorAtT
  onCheckedChange?: (value: allowedPrimitiveT, checked: boolean) => void
}

type commonRadioProps = {
  value?: allowedPrimitiveT
  indicatorAt?: indicatorAtT
  onValueChange?: (value: allowedPrimitiveT) => void
}

type commonInner = commomClsT & {
  trigger: React.ReactNode
  contentProps?: React.ComponentProps<typeof MenubarContent>
}

type menubarBaseT = commomClsT & {
  key: string
  trigger: React.ReactNode
  contentProps?: React.ComponentProps<typeof MenubarContent>
}

type menubarOptionsT = (menubarBaseT & {
  options: menuOptionsT
  onSelect?: (value: allowedPrimitiveT) => void
})[]

type menubarInputOptionT = menubarBaseT & {
  options: menuInputOptionsT
}

type menubarCheckboxOptionsT = (menubarInputOptionT & commonCheckboxProps)[]
type menubarRadioOptionsT = (menubarInputOptionT & commonRadioProps)[]

type commonWrapT = commomClsT & Omit<React.ComponentProps<typeof Menubar>, "children" | "asChild" | "value"> & {
  contentProps?: React.ComponentProps<typeof MenubarContent>
}

// -------

type itemProps = {
  option: menuOptionT
  className?: string
  onSelect?: () => void
}
function Item({
  option,
  className,
  onSelect
}: itemProps) {
  const value = getValue(option)

  if (isSeparator(value)) return <MenubarSeparator className={className} />

  const label = getLabel(option)
  const opt: any = typeof option === "object" ? option : {}
  const shortcut = opt?.shortcut

  return (
    <MenubarItem
      {...opt}
      onSelect={onSelect}
      className={cn(className, opt?.className)}
    >
      {label}
      {shortcut && <MenubarShortcut>{shortcut}</MenubarShortcut>}
    </MenubarItem>
  )
}

type checkboxItemProps = {
  option: menuInputOptionT
  className?: string
  checked?: boolean
  onCheckedChange?: (checked: boolean) => void
  indicatorAt?: indicatorAtT
}
function CheckboxItem({
  option,
  className,
  checked = false,
  indicatorAt,
  onCheckedChange = () => { }
}: checkboxItemProps) {
  const value = getValue(option)

  if (isSeparator(value)) return <MenubarSeparator className={className} />

  const label = getLabel(option)
  const disabled = (option as any)?.disabled

  return (
    <MenubarCheckboxItem
      checked={checked}
      disabled={disabled}
      className={className}
      indicatorAt={indicatorAt}
      onCheckedChange={onCheckedChange}
    >
      {label}
    </MenubarCheckboxItem>
  )
}

type radioItemProps = {
  option: menuInputOptionT
  className?: string
  indicatorAt?: indicatorAtT
}
function RadioItem({
  option,
  className,
  indicatorAt
}: radioItemProps) {
  const value = getValue(option)

  if (isSeparator(value)) return <MenubarSeparator className={className} />

  const label = getLabel(option)
  const disabled = (option as any)?.disabled

  return (
    <MenubarRadioItem
      value={`${value}`}
      disabled={disabled}
      className={className}
      indicatorAt={indicatorAt}
    >
      {label}
    </MenubarRadioItem>
  )
}

type SubMenuProps = commomClsT & {
  submenu: subMenuT
  onSelect?: (value: allowedPrimitiveT) => void
}
function SubMenu({
  submenu,
  itemCls,
  groupCls,
  groupLabelCls,
  onSelect
}: SubMenuProps) {
  return (
    <MenubarSub>
      <MenubarSubTrigger className={submenu.triggerCls}>
        {submenu.submenu}
      </MenubarSubTrigger>

      <MenubarSubContent className={submenu.contentCls}>
        {submenu.options.map((option, i) => {
          if (isGroupMenu(option)) {
            return (
              <MenubarGroup key={option.group} className={cn(groupCls, option.className)}>
                <MenubarLabel className={cn("pb-0.5 text-xs text-muted-foreground font-normal", groupLabelCls, option.groupLabelCls)}>
                  {option.group}
                </MenubarLabel>

                {option.options.map((grOpt, j) => (
                  <Item
                    key={getKey(grOpt, j)}
                    option={grOpt}
                    className={itemCls}
                    onSelect={() => onSelect?.(getValue(grOpt))}
                  />
                ))}
              </MenubarGroup>
            )
          }

          if (isSubMenu(option)) {
            return (
              <SubMenu
                key={getKey(option, i)}
                submenu={option}
                itemCls={itemCls}
                groupCls={groupCls}
                onSelect={onSelect}
              />
            )
          }

          return (
            <Item
              key={getKey(option, i)}
              option={option}
              className={itemCls}
              onSelect={() => onSelect?.(getValue(option))}
            />
          )
        })}
      </MenubarSubContent>
    </MenubarSub>
  )
}

type CheckboxSubMenuProps = commomClsT & commonCheckboxProps & {
  submenu: inputSubMenuT
}
function CheckboxSubMenu({
  submenu,
  itemCls,
  groupCls,
  groupLabelCls,
  checked = [],
  indicatorAt,
  onCheckedChange = () => { }
}: CheckboxSubMenuProps) {
  return (
    <MenubarSub>
      <MenubarSubTrigger className={submenu.triggerCls}>
        {submenu.submenu}
      </MenubarSubTrigger>

      <MenubarSubContent className={submenu.contentCls}>
        {submenu.options.map((option, i) => {
          if (isInputGroupMenu(option)) {
            return (
              <MenubarGroup key={option.group} className={cn(groupCls, option.className)}>
                <MenubarLabel className={cn("pb-0.5 text-xs text-muted-foreground font-normal", groupLabelCls, option.groupLabelCls)}>
                  {option.group}
                </MenubarLabel>

                {option.options.map((grOpt, j) => {
                  const v = getValue(grOpt)
                  return (
                    <CheckboxItem
                      key={getKey(grOpt, j)}
                      option={grOpt}
                      checked={checked.includes(v)}
                      className={itemCls}
                      indicatorAt={indicatorAt}
                      onCheckedChange={(checked) => onCheckedChange?.(v, checked)}
                    />
                  )
                })}
              </MenubarGroup>
            )
          }

          if (isInputSubMenu(option)) {
            return (
              <CheckboxSubMenu
                key={option.submenu}
                submenu={option}
                checked={checked}
                itemCls={itemCls}
                groupCls={groupCls}
                indicatorAt={indicatorAt}
                groupLabelCls={groupLabelCls}
                onCheckedChange={onCheckedChange}
              />
            )
          }

          const v = getValue(option)
          return (
            <CheckboxItem
              key={getKey(option, i)}
              option={option}
              checked={checked.includes(v)}
              className={itemCls}
              indicatorAt={indicatorAt}
              onCheckedChange={(checked) => onCheckedChange?.(v, checked)}
            />
          )
        })}
      </MenubarSubContent>
    </MenubarSub>
  )
}

type RadioSubMenuProps = commomClsT & commonRadioProps & {
  submenu: inputSubMenuT
}
function RadioSubMenu({
  submenu,
  itemCls,
  groupCls,
  groupLabelCls,
  value = "",
  indicatorAt,
  onValueChange = () => { }
}: RadioSubMenuProps) {
  return (
    <MenubarSub>
      <MenubarSubTrigger className={submenu.triggerCls}>
        {submenu.submenu}
      </MenubarSubTrigger>

      <MenubarSubContent className={submenu.contentCls}>
        <MenubarRadioGroup value={`${value}`} onValueChange={onValueChange}>
          {submenu.options.map((option, i) => {
            if (isInputGroupMenu(option)) {
              return (
                <MenubarGroup key={option.group} className={cn(groupCls, option.className)}>
                  <MenubarLabel className={cn("pb-0.5 text-xs text-muted-foreground font-normal", groupLabelCls, option.groupLabelCls)}>
                    {option.group}
                  </MenubarLabel>

                  {option.options.map((grOpt, j) => (
                    <RadioItem
                      key={getKey(grOpt, j)}
                      option={grOpt}
                      className={itemCls}
                      indicatorAt={indicatorAt}
                    />
                  ))}
                </MenubarGroup>
              )
            }

            if (isInputSubMenu(option)) {
              return (
                <RadioSubMenu
                  key={option.submenu}
                  value={value}
                  submenu={option}
                  itemCls={itemCls}
                  groupCls={groupCls}
                  indicatorAt={indicatorAt}
                  groupLabelCls={groupLabelCls}
                  onValueChange={onValueChange}
                />
              )
            }

            return (
              <RadioItem
                key={getKey(option, i)}
                option={option}
                className={itemCls}
                indicatorAt={indicatorAt}
              />
            )
          })}
        </MenubarRadioGroup>
      </MenubarSubContent>
    </MenubarSub>
  )
}

type wrapperInner = commonInner & {
  options: menuOptionsT
  onSelect?: (value: allowedPrimitiveT) => void
}
function MenubarWrapperInner({
  trigger,
  options,
  itemCls,
  groupCls,
  groupLabelCls,
  contentProps,
  onSelect
}: wrapperInner) {
  return (
    <MenubarMenu>
      <MenubarTrigger asChild={typeof trigger !== "string"}>
        {trigger}
      </MenubarTrigger>

      <MenubarContent {...contentProps}>
        {options.map((option, i) => {
          if (isGroupMenu(option)) {
            return (
              <MenubarGroup key={option.group} className={cn(groupCls, option.className)}>
                <MenubarLabel className={cn("pb-0.5 text-xs text-muted-foreground font-normal", groupLabelCls, option.groupLabelCls)}>
                  {option.group}
                </MenubarLabel>

                {option.options.map((grOpt, j) => (
                  <Item
                    key={getKey(grOpt, j)}
                    option={grOpt}
                    className={itemCls}
                    onSelect={() => onSelect?.(getValue(grOpt))}
                  />
                ))}
              </MenubarGroup>
            )
          }

          if (isSubMenu(option)) {
            return (
              <SubMenu
                key={option.submenu}
                submenu={option}
                itemCls={itemCls}
                groupCls={groupCls}
                groupLabelCls={groupLabelCls}
                onSelect={onSelect}
              />
            )
          }

          return (
            <Item
              key={getKey(option, i)}
              option={option}
              className={itemCls}
              onSelect={() => onSelect?.(getValue(option))}
            />
          )
        })}
      </MenubarContent>
    </MenubarMenu>
  )
}

type checkboxWrapperInner = commonInner & commonCheckboxProps & {
  options: menuInputOptionsT
  label?: string
}
function MenubarCheckboxWrapperInner({
  trigger,
  options,

  label,
  contentProps,
  itemCls,
  groupCls,
  groupLabelCls,

  checked: o_checked,
  onCheckedChange: o_onCheckedChange,

  indicatorAt,
}: checkboxWrapperInner) {
  const [i_checked, setIChecked] = useState<allowedPrimitiveT[]>([])

  function i_Checked(v: allowedPrimitiveT, c: boolean) {
    setIChecked(prev => !c ? prev.filter(p => p !== v) : [...prev, v])
  }

  const checked = o_checked ?? i_checked
  const onCheckedChange = o_onCheckedChange ?? i_Checked

  return (
    <MenubarMenu>
      <MenubarTrigger asChild={typeof trigger !== "string"}>
        {trigger}
      </MenubarTrigger>

      <MenubarContent {...contentProps}>
        {label && (
          <>
            <MenubarLabel>{label}</MenubarLabel>
            <MenubarSeparator />
          </>
        )}
        {options.map((option, i) => {
          if (isInputGroupMenu(option)) {
            return (
              <MenubarGroup key={option.group} className={cn(groupCls, option.className)}>
                <MenubarLabel className={cn("pb-0.5 text-xs text-muted-foreground font-normal", groupLabelCls, option.groupLabelCls)}>
                  {option.group}
                </MenubarLabel>

                {option.options.map((grOpt, j) => {
                  const v = getValue(grOpt)
                  return (
                    <CheckboxItem
                      key={getKey(grOpt, j)}
                      option={grOpt}
                      checked={checked.includes(v)}
                      className={itemCls}
                      indicatorAt={indicatorAt}
                      onCheckedChange={(checked) => onCheckedChange?.(v, checked)}
                    />
                  )
                })}
              </MenubarGroup>
            )
          }

          if (isInputSubMenu(option)) {
            return (
              <CheckboxSubMenu
                key={option.submenu}
                submenu={option}
                checked={checked}
                itemCls={itemCls}
                groupCls={groupCls}
                indicatorAt={indicatorAt}
                groupLabelCls={groupLabelCls}
                onCheckedChange={onCheckedChange}
              />
            )
          }

          const v = getValue(option)
          return (
            <CheckboxItem
              key={getKey(option, i)}
              option={option}
              checked={checked.includes(v)}
              className={itemCls}
              indicatorAt={indicatorAt}
              onCheckedChange={(checked) => onCheckedChange?.(v, checked)}
            />
          )
        })}
      </MenubarContent>
    </MenubarMenu>
  )
}

type radioWrapperInner = commonInner & commonRadioProps & {
  options: menuInputOptionsT
  label?: string
}
function MenubarRadioWrapperInner({
  trigger,
  options,

  label,
  itemCls,
  groupCls,
  groupLabelCls,
  contentProps,

  value: o_value,
  onValueChange: o_onValueChange,

  indicatorAt,
}: radioWrapperInner) {
  const [i_value, setIValue] = useState<allowedPrimitiveT>("")

  const value = o_value ?? i_value
  const onValueChange = o_onValueChange ?? setIValue

  return (
    <MenubarMenu>
      <MenubarTrigger asChild={typeof trigger !== "string"}>
        {trigger}
      </MenubarTrigger>

      <MenubarContent {...contentProps}>
        {label && (
          <>
            <MenubarLabel>{label}</MenubarLabel>
            <MenubarSeparator />
          </>
        )}
        <MenubarRadioGroup value={`${value}`} onValueChange={v => onValueChange(parseAllowedPrimitive(v))}>
          {options.map((option, i) => {
            if (isInputGroupMenu(option)) {
              return (
                <MenubarGroup key={option.group} className={cn(groupCls, option.className)}>
                  <MenubarLabel className={cn("pb-0.5 text-xs text-muted-foreground font-normal", groupLabelCls, option.groupLabelCls)}>
                    {option.group}
                  </MenubarLabel>

                  {option.options.map((grOpt, j) => (
                    <RadioItem
                      key={getKey(grOpt, j)}
                      option={grOpt}
                      className={itemCls}
                      indicatorAt={indicatorAt}
                    />
                  ))}
                </MenubarGroup>
              )
            }

            if (isInputSubMenu(option)) {
              return (
                <RadioSubMenu
                  key={option.submenu}
                  value={value}
                  submenu={option}
                  itemCls={itemCls}
                  groupCls={groupCls}
                  indicatorAt={indicatorAt}
                  groupLabelCls={groupLabelCls}
                  onValueChange={v => onValueChange(parseAllowedPrimitive(v))}
                />
              )
            }

            return (
              <RadioItem
                key={getKey(option, i)}
                option={option}
                className={itemCls}
                indicatorAt={indicatorAt}
              />
            )
          })}
        </MenubarRadioGroup>
      </MenubarContent>
    </MenubarMenu>
  )
}

type wrap = commonWrapT & {
  options: menubarOptionsT
  onSelect?: (value: allowedPrimitiveT) => void
}
function MenubarWrapper({
  options,
  itemCls,
  groupCls,
  groupLabelCls,
  contentProps,
  onSelect,
  ...props
}: wrap) {
  return (
    <Menubar {...props}>
      {
        options.map(op => (
          <MenubarWrapperInner
            key={op.key}
            trigger={op.trigger}
            options={op.options}
            itemCls={cn(itemCls, op.itemCls)}
            groupCls={cn(groupCls, op.groupCls)}
            groupLabelCls={cn(groupLabelCls, op.groupLabelCls)}
            contentProps={{ ...contentProps, ...op?.contentProps }}
            onSelect={op?.onSelect || onSelect}
          />
        ))
      }
    </Menubar>
  )
}

type wrapCheckboxT = commonWrapT & commonCheckboxProps & {
  options: menubarCheckboxOptionsT
}
function MenubarCheckboxWrapper({
  options,

  contentProps,
  itemCls,
  groupCls,
  groupLabelCls,

  checked,
  onCheckedChange,

  indicatorAt,
  ...props
}: wrapCheckboxT) {
  return (
    <Menubar {...props}>
      {options.map(op => (
        <MenubarCheckboxWrapperInner
          key={op.key}
          trigger={op.trigger}
          options={op.options}
          itemCls={cn(itemCls, op.itemCls)}
          groupCls={cn(groupCls, op.groupCls)}
          groupLabelCls={cn(groupLabelCls, op.groupLabelCls)}
          contentProps={{ ...contentProps, ...op?.contentProps }}
          onCheckedChange={op.onCheckedChange ?? onCheckedChange}
          indicatorAt={op.indicatorAt ?? indicatorAt}
          checked={op.checked ?? checked}
        />
      ))}
    </Menubar>
  )
}

type wrapRadioT = commonWrapT & commonRadioProps & {
  options: menubarRadioOptionsT
}
function MenubarRadioWrapper({
  options,

  contentProps,
  itemCls,
  groupCls,
  groupLabelCls,

  value,
  onValueChange,

  indicatorAt,
  ...props
}: wrapRadioT) {
  return (
    <Menubar {...props}>
      {options.map(op => (
        <MenubarRadioWrapperInner
          key={op.key}
          trigger={op.trigger}
          options={op.options}
          itemCls={cn(itemCls, op.itemCls)}
          groupCls={cn(groupCls, op.groupCls)}
          groupLabelCls={cn(groupLabelCls, op.groupLabelCls)}
          contentProps={{ ...contentProps, ...op?.contentProps }}
          onValueChange={op.onValueChange ?? onValueChange}
          indicatorAt={op.indicatorAt ?? indicatorAt}
          value={op.value ?? value}
        />
      ))}
    </Menubar>
  )
}

export {
  MenubarWrapper,
  MenubarCheckboxWrapper,
  MenubarRadioWrapper,
  type menubarOptionsT,
  type menubarCheckboxOptionsT,
  type menubarRadioOptionsT,
}

Done

You can now use MenubarCheckboxWrapper, MenubarRadioWrapper, and MenubarWrapper.

Usage

Basic

import { type menubarOptionsT, MenubarCheckboxWrapper, MenubarRadioWrapper, MenubarWrapper } from "@/components/ui/menubar-wrapper"

export function Basic() {
    const opts: menubarOptionsT = [
    {
      key: "1",
      trigger: "Option 1",
      options: dropdownOptions
    },
    {
      key: "2",
      trigger: "Option 2",
      options: dropdownOptions,
    }
  ]

  return (
    <>
      <MenubarWrapper
        options={opts}
        contentProps={{ align: "end" }}
      />

      <MenubarCheckboxWrapper 
        options={opts}
      />


      <MenubarRadioWrapper 
        options={opts}
      />
    </>
  )
}

Controlled

import { useState } from "react"
import { type menubarOptionsT, MenubarCheckboxWrapper, MenubarRadioWrapper, MenubarWrapper } from "@/components/ui/menubar-wrapper"
import { Button } from "@/components/ui/button"

export function Controlled() {
  const [checked, setChecked] = useState<allowedPrimitiveT[]>([])
  const [val, setVal] = useState<allowedPrimitiveT>(true)

  const opts: menubarOptionsT = [
    {
      key: "1",
      trigger: "Option 1",
      options: dropdownOptions
    },
    {
      key: "2",
      trigger: "Option 2",
      options: dropdownOptions,
    }
  ]

  return (
    <>
      <MenubarWrapper
        options={opts}
        onSelect={v => console.log(v)}
      />

      <MenubarCheckboxWrapper
        checked={checked}
        options={opts}
        onCheckedChange={(value, isChecked) => {
          setChecked((prev) =>
            isChecked
              ? [...prev, value]
              : prev.filter((x) => x !== value)
          )
        }}
      />

      <MenubarRadioWrapper
        value={val}
        options={opts}
        onValueChange={setVal}
      />
    </>
  )
}

Nested Complex Data

const dropdownOptions = [
  { label: "New File", value: "new", shortcut: "Ctrl+N" },
  "Save",
  12,
  { label: <><Banana /> Banana</>, value: "banana" },
  "---",
  {
    group: "Settings",
    options: [
      { label: "Appearance", value: "appearance" },
      22,
      true
    ],
  },

  {
    submenu: "More",
    options: [
      { label: <><Apple /> Apple</>, value: "apple" },
      {
        group: "Tools",
        options: [
          { label: "Formatter", value: "formatter" },
          false,
        ],
      },
    ],
  },
]

export function Complex() {
  const opts: menubarOptionsT = [
    {
      key: "1",
      trigger: "Option 1",
      options: dropdownOptions
    }
  ]

  return (
    <MenubarWrapper
      options={opts}
    />
  )
}

Reference

Prop

Type

Prop

Type

Prop

Type

Prop

Type

Prop

Type

Prop

Type