Morphin
Introduction
Button
Chip
Input
Searchbar
Sidebar

Sidebar

A sidebar component
Vaultions
    • Screenless
    • XPass
    • Bluehawk
Vaults
Collapsible Type

Code

import {
  Avatar,
  Button,
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
  Label,
  RadioGroup,
  Separator,
  SizableText,
  SizeTokens,
  XStack,
  YStack,
} from 'tamagui'

import {
  Sidebar,
  SidebarGroup,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarSubMenu,
  SidebarSubMenuButton,
  SidebarSubMenuItem,
} from 'yourpath/ui'

import {
  Bot,
  Building2,
  ChevronRight,
  FolderLock,
  Key,
  Lock,
  Shield,
  ShieldCheck,
  TerminalSquare,
  Vault,
} from '@tamagui/lucide-icons'
import { useState } from 'react'

const navMain = [
  {
    title: 'Vimults',
    url: '#',
    icon: TerminalSquare,
    isActive: true,
    items: [
      {
        title: 'Screenless',
        url: '#',
      },
      {
        title: 'XPass',
        url: '#',
      },
      {
        title: 'Bluehawk',
        url: '#',
      },
    ],
  },
  {
    title: 'Pattern',
    url: '#',
    icon: Bot,
    items: [
      {
        title: '8figure',
        url: '#',
      },
      {
        title: 'Alloptions',
        url: '#',
      },
      {
        title: 'Retina',
        url: '#',
      },
    ],
  },
]

const items = [
  {
    id: '3',
    name: 'Keyflow',
    href: '#',
    IconComponent: Key,
  },
  {
    id: '4',
    name: 'Vaultease',
    href: '#',
    IconComponent: Vault,
  },
  {
    id: '5',
    name: 'Credguard',
    href: '#',
    IconComponent: Shield,
  },
  {
    id: '6',
    name: 'Lockshift',
    href: '#',
    IconComponent: Lock,
  },

  {
    id: '8',
    name: 'Safecore',
    href: '#',
    IconComponent: FolderLock,
  },
  {
    id: '9',
    name: 'Guardstack',
    href: '#',
    IconComponent: ShieldCheck,
  },
]

export function RadioGroupItemWithLabel(props: { size: SizeTokens; value: string; label: string }) {
  const id = `radiogroup-${props.value}`
  return (
    <XStack width={300} alignItems="center" space="$4">
      <RadioGroup.Item value={props.value} id={id} size={props.size}>
        <RadioGroup.Indicator />
      </RadioGroup.Item>

      <Label size={props.size} htmlFor={id}>
        {props.label}
      </Label>
    </XStack>
  )
}

const DemoSidebar = () => {
  const [collapsibleVal, setCollapsibleVal] = useState<'icon' | 'offcanvas' | 'none'>('icon')

  return (
    <>
      <YStack
        minHeight={100}
        overflow="hidden"
        margin="$3"
        padding="$2"
        justifyContent="space-between"
      >
        <XStack gap={'$4'}>
          <Sidebar
            collapsible={collapsibleVal}
            borderColor={'aliceblue'}
            borderStyle="solid"
            //@ts-ignore
            maxHeight={'70svh'}
            //@ts-ignore
            minHeight={'70svh'}
          >
            <Sidebar.Header>
              <SidebarMenu>
                <SidebarMenuItem>
                  <SidebarMenuButton
                    className="collapsible-icon-pl-4"
                    transition="padding-left 0.3s ease"
                    tooltipProps={{
                      title: 'Armac LLC',
                      placement: 'right-start',
                      delay: {
                        open: 0,
                        close: 0.5,
                      },
                    }}
                  >
                    <XStack padding="$1" bg={'$purple7'} borderRadius={'$1'}>
                      <SidebarMenuButton.Icon>
                        <Building2 />
                      </SidebarMenuButton.Icon>
                    </XStack>
                    <SidebarMenuButton.Text
                      whiteSpace="nowrap"
                      lineHeight={'unset'}
                      fontSize={14}
                      fontWeight={'bold'}
                    >
                      Armac LLC
                    </SidebarMenuButton.Text>
                  </SidebarMenuButton>
                </SidebarMenuItem>
              </SidebarMenu>
            </Sidebar.Header>
            <Sidebar.Content>
              <SidebarGroup>
                <SidebarGroupLabel fontSize={12} color={'$color.gray10Light'}>
                  Vaultions
                </SidebarGroupLabel>
                <SidebarGroupContent>
                  <SidebarMenu>
                    {navMain.map((item) => (
                      <Collapsible
                        key={item.title}
                        asChild
                        defaultOpen={item.isActive}
                        className="group/collapsible"
                      >
                        <SidebarMenuItem>
                          <CollapsibleTrigger asChild transition="height 3s ease">
                            <SidebarMenuButton
                              tooltipProps={{
                                title: `${item.title}`,
                                placement: 'right-start',
                                delay: {
                                  open: 0,
                                  close: 0.5,
                                },
                              }}
                            >
                              {item.icon && (
                                <SidebarMenuButton.Icon>
                                  <item.icon flexShrink={0} />
                                </SidebarMenuButton.Icon>
                              )}
                              <SidebarMenuButton.Text fontSize={14}>
                                {item.title}
                              </SidebarMenuButton.Text>
                              <SidebarMenuButton.Icon className="rotate-90deg">
                                <ChevronRight flexShrink={0} style={{ marginLeft: 'auto' }} />
                              </SidebarMenuButton.Icon>
                            </SidebarMenuButton>
                          </CollapsibleTrigger>
                          <CollapsibleContent>
                            <SidebarSubMenu>
                              {item.items?.map((subItem) => (
                                <SidebarSubMenuItem key={subItem.title}>
                                  <SidebarSubMenuButton asChild>
                                    <a href={subItem.url} style={{ textDecoration: 'none' }}>
                                      <SizableText whiteSpace="nowrap" textOverflow="ellipsis">
                                        {subItem.title}
                                      </SizableText>
                                    </a>
                                  </SidebarSubMenuButton>
                                </SidebarSubMenuItem>
                              ))}
                            </SidebarSubMenu>
                          </CollapsibleContent>
                        </SidebarMenuItem>
                      </Collapsible>
                    ))}
                  </SidebarMenu>
                </SidebarGroupContent>
              </SidebarGroup>
              <SidebarGroup>
                <SidebarGroupLabel fontSize={12} color={'$color.gray10Light'}>
                  Vaults
                </SidebarGroupLabel>
                <SidebarGroupContent>
                  <SidebarMenu>
                    {items.map(({ id, name, IconComponent }) => {
                      return (
                        <SidebarMenuItem key={id}>
                          <SidebarMenuButton>
                            <SidebarMenuButton.Icon>
                              <IconComponent flexShrink={0} />
                            </SidebarMenuButton.Icon>
                            <SidebarMenuButton.Text lineHeight={'unset'} fontSize={14}>
                              {name}
                            </SidebarMenuButton.Text>
                          </SidebarMenuButton>
                        </SidebarMenuItem>
                      )
                    })}
                  </SidebarMenu>
                </SidebarGroupContent>
              </SidebarGroup>
            </Sidebar.Content>
            <Separator />
            <Sidebar.Footer>
              <SidebarMenuButton
                className="collapsible-icon-pl-0"
                transition="padding-left 0.3s ease"
              >
                <Avatar circular size="$2">
                  <Avatar.Image
                    accessibilityLabel="Nate Wienert"
                    src="https://images.unsplash.com/photo-1531384441138-2736e62e0919?&w=100&h=100&dpr=2&q=80"
                  />
                  <Avatar.Fallback delayMs={600} backgroundColor="$blue10" />
                </Avatar>
                <YStack>
                  <SidebarMenuButton.Text
                    lineHeight={'unset'}
                    fontSize={14}
                    fontWeight={'bold'}
                    color={'$gray11Light'}
                  >
                    Someone
                  </SidebarMenuButton.Text>
                  <SidebarMenuButton.Text lineHeight={'unset'} fontSize={10}>
                    someone@com.in
                  </SidebarMenuButton.Text>
                </YStack>
              </SidebarMenuButton>
            </Sidebar.Footer>
          </Sidebar>
          <YStack>
            <Sidebar.Trigger asChild>
              <Button>Toggle Sidebar</Button>
            </Sidebar.Trigger>
            <YStack>
              <Separator marginVertical={15} />
              <SizableText>Collapsible Type</SizableText>
              <RadioGroup
                onValueChange={(v) => {
                  // @ts-ignore
                  setCollapsibleVal(v)
                }}
                gap="$2"
                name="form"
                value={collapsibleVal}
              >
                <YStack>
                  <XStack width={300} alignItems="center" space="$4">
                    <RadioGroup.Item value="icon" id="icon-radio-item">
                      <RadioGroup.Indicator />
                    </RadioGroup.Item>
                    <Label size={3} htmlFor={'icon-radio-item'}>
                      Icon
                    </Label>
                  </XStack>
                  <XStack width={300} alignItems="center" space="$4">
                    <RadioGroup.Item value="offcanvas" id="offcanvas-radio-item">
                      <RadioGroup.Indicator />
                    </RadioGroup.Item>
                    <Label size={3} htmlFor={'offcanvas-radio-item'}>
                      Offcanvas
                    </Label>
                  </XStack>
                  <XStack width={300} alignItems="center" space="$4">
                    <RadioGroup.Item value="none" id="none-radio-item">
                      <RadioGroup.Indicator />
                    </RadioGroup.Item>
                    <Label size={3} htmlFor={'none-radio-item'}>
                      None
                    </Label>
                  </XStack>
                </YStack>
              </RadioGroup>
            </YStack>
          </YStack>
        </XStack>
      </YStack>
    </>
  )
}

export default DemoSidebar

Props

Sidebar
Prop NameTypeDescription
collapsible'offcanvas' | 'icon' | 'none'Render a sidebar, when none then it is non-collapsible, otherwise full collapsible (width 0) and width equal to icon size

Sidebar Code

import { getSize, getSpace } from '@tamagui/get-token'
import { composeEventHandlers, withStaticProperties } from '@tamagui/helpers'
import {
  GetProps,
  SizeTokens,
  Stack,
  StackProps,
  VariantSpreadExtras,
  createStyledContext,
  styled,
  useTheme,
} from '@tamagui/web'
import * as React from 'react'
import {
  Button,
  SizableText,
  Text,
  ThemeableStack,
  ThemeableStackProps,
  Tooltip,
  TooltipProps,
  YStack,
} from 'tamagui'
import './sidebar.css'

/**
 * when size is undefined or not explicitly passed by user
 * Font size h,w = 1 rem
 *
 */

/** TODO MERGE CLASSNAMES */

const getButtonSized = (val: SizeTokens | number, { tokens, props }: VariantSpreadExtras<any>) => {
  if (!val || props.circular) {
    return
  }
  if (typeof val === 'number') {
    return {
      padding: val * 0.25,
    }
  }

  const xSize = getSpace(val, {
    shift: -4,
  })
  return {
    padding: xSize,
    gap: xSize,
  }
}

/* -------------------------------------------------------------------------------------------------
 * Sidebar Constants
 * -----------------------------------------------------------------------------------------------*/

const SIDEBAR_NAME = 'Sidebar'
const SIDEBAR_WIDTH = '16rem'
// const SIDEBAR_WIDTH_MOBILE = '18rem'
const SIDEBAR_WIDTH_ICON = '3rem'
const SIDEBAR_KEYBOARD_SHORTCUT = 'b'

/* -------------------------------------------------------------------------------------------------
 * Sidebar Context
 * -----------------------------------------------------------------------------------------------*/

type ScopedProps<P> = P & { __scopeSidebar?: string }

type SidebarContextValue = {
  contentId: string
  state: 'expanded' | 'collapsed'
  open: boolean
  setOpen: (open: boolean) => void
  openMobile: boolean
  setOpenMobile: (open: boolean) => void
  isMobile: boolean
  toggleSidebar: () => void
  disabled?: boolean
  size?: SizeTokens
}

const SidebarContext = createStyledContext<SidebarContextValue>()

/* -------------------------------------------------------------------------------------------------
 * Sidebar Component
 * -----------------------------------------------------------------------------------------------*/

interface SidebarProps extends StackProps {
  side?: 'left'
  collapsible?: 'offcanvas' | 'icon' | 'none'
}

interface SidebarProviderProps {
  defaultOpen?: boolean
  open?: boolean
  onOpenChange?: (open: boolean) => void
  children?: React.ReactNode
}

const SidebarProvider: React.FC<SidebarProviderProps> = ({ children }) => {
  const [open, setOpen] = React.useState(true)
  const toggleSidebar = React.useCallback(() => setOpen((prev) => !prev), [])

  React.useEffect(() => {
    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
        event.preventDefault()
        toggleSidebar()
      }
    }

    window.addEventListener('keydown', handleKeyDown)
    return () => window.removeEventListener('keydown', handleKeyDown)
  }, [toggleSidebar])

  const state = open ? 'expanded' : 'collapsed'

  return (
    <SidebarContext.Provider open={open} toggleSidebar={toggleSidebar} state={state}>
      {children}
    </SidebarContext.Provider>
  )
}

const _Sidebar = React.forwardRef<Stack, ScopedProps<SidebarProps>>((props, forwardedRef) => {
  const { __scopeSidebar, collapsible = 'icon', ...sidebarProps } = props
  const { useStyledContext: useSidebarContext } = SidebarContext

  const { open, state } = useSidebarContext() // Get open state and toggle function from context

  const width = open ? SIDEBAR_WIDTH : collapsible === 'offcanvas' ? 0 : SIDEBAR_WIDTH_ICON

  if (collapsible === 'none') {
    return (
      <Stack
        ref={forwardedRef}
        className="sidebar group peer"
        style={{
          // @ts-ignore
          '--sidebar-width': SIDEBAR_WIDTH,
          width: 'var(--sidebar-width)',
        }}
        {...sidebarProps}
      />
    )
  }

  return (
    <Stack
      data-state={state}
      data-disabled={false}
      data-collapsible={state === 'collapsed' ? collapsible : ''}
      width={width}
      ref={forwardedRef}
      className="sidebar group peer w-0"
      $sm={{
        display: 'none',
      }}
      overflow="hidden"
      transition="width 0.3s ease"
      {...sidebarProps}
    />
  )
})

_Sidebar.displayName = SIDEBAR_NAME

/* -------------------------------------------------------------------------------------------------
 * SidebarTrigger Component
 * -----------------------------------------------------------------------------------------------*/

const TRIGGER_NAME = 'SidebarTrigger'

type SidebarTriggerProps = GetProps<typeof Stack>

const SidebarTriggerFrame = styled(Stack, {
  name: TRIGGER_NAME,
  tag: 'button',
})

const SidebarTrigger = SidebarTriggerFrame.styleable(
  (props: ScopedProps<SidebarTriggerProps>, forwardedRef) => {
    const { __scopeSidebar, children, ...triggerProps } = props
    const { useStyledContext: useSidebarContext } = SidebarContext
    const context = useSidebarContext(__scopeSidebar)

    return (
      <SidebarTriggerFrame
        aria-controls={context.contentId}
        aria-expanded={context.open}
        data-state={getState(context.open)}
        data-disabled={context.disabled ? '' : undefined}
        disabled={context.disabled}
        ref={forwardedRef}
        onPress={composeEventHandlers(props.onPress as any, context.toggleSidebar)}
        {...(triggerProps as any)}
      >
        {typeof children === 'function' ? children({ open: context.open }) : children}
      </SidebarTriggerFrame>
    )
  }
)

SidebarTrigger.displayName = TRIGGER_NAME

/* -------------------------------------------------------------------------------------------------
 * SidebarHeader Component
 * -----------------------------------------------------------------------------------------------*/

const HEADER_NAME = 'SidebarHeader'

type SidebarHeaderProps = GetProps<typeof Stack>

const SidebarHeaderFrame = styled(Stack, {
  name: HEADER_NAME,
  padding: '0.5rem',
})

const SidebarHeader = SidebarHeaderFrame.styleable<SidebarHeaderProps>((props, forwardedRef) => {
  const { children, ...headerProps } = props

  return (
    <SidebarHeaderFrame className="sidebar-header" {...headerProps} ref={forwardedRef}>
      {children}
    </SidebarHeaderFrame>
  )
})

SidebarHeader.displayName = HEADER_NAME

/* -------------------------------------------------------------------------------------------------
 * SidebarFooter Component
 * -----------------------------------------------------------------------------------------------*/

const FOOTER_NAME = 'SidebarFooter'

type SidebarFooterProps = GetProps<typeof Stack>

const SidebarFooterFrame = styled(Stack, {
  name: FOOTER_NAME,
  padding: '0.5rem',
})

const SidebarFooter = SidebarFooterFrame.styleable<SidebarFooterProps>((props, forwardedRef) => {
  const { children, ...footerProps } = props

  return (
    <SidebarFooterFrame className="sidebar-footer" ref={forwardedRef} {...footerProps}>
      {children}
    </SidebarFooterFrame>
  )
})

SidebarFooter.displayName = FOOTER_NAME

/* -------------------------------------------------------------------------------------------------
 * SidebarContent Component
 * -----------------------------------------------------------------------------------------------*/

interface SidebarContentProps extends StackProps {
  forceMount?: true
}

const CONTENT_NAME = 'SidebarContent'

const SidebarContentFrame = styled(Stack, {
  name: CONTENT_NAME,
  // @ts-ignore
  overflowY: 'auto',
  flex: 1,
})

const SidebarContent = SidebarContentFrame.styleable<ScopedProps<SidebarContentProps>>(
  (props, forwardedRef) => {
    const { forceMount, children, __scopeSidebar, ...contentProps } = props

    return (
      <SidebarContentFrame
        ref={forwardedRef}
        className="sidebar-content sidebar-content-overflow-hidden"
        {...contentProps}
      >
        {children}
      </SidebarContentFrame>
    )
  }
)

SidebarContent.displayName = CONTENT_NAME

const SidebarButtonBase = styled(ThemeableStack, {
  tag: 'button',
  role: 'button',
  display: 'flex',
  alignItems: 'center',
  flexDirection: 'row',
  width: '100%',
  cursor: 'pointer',
  focusable: true,
  variants: {
    unstyled: {
      false: {
        size: '$true',
        pressStyle: {
          borderColor: 'transparent',
        },
        hoverStyle: {
          backgroundColor: '$color4',
          borderColor: 'transparent',
        },

        // Shows focus when we click tab button
        focusVisibleStyle: {
          outlineColor: '$outlineColor',
          outlineStyle: 'solid',
          outlineWidth: 2,
        },
      },
    },
    size: {
      '...size': getButtonSized,
      ':number': getButtonSized,
    },
  },

  justifyContent: 'flex-start',
  overflow: 'hidden',

  defaultVariants: {
    unstyled: process.env.TAMAGUI_HEADLESS === '1',
  },
})

/* -------------------------------------------------------------------------------------------------
 * SidebarGroup Component
 * -----------------------------------------------------------------------------------------------*/

const SIDEBAR_GROUP = 'SidebarGroup'

const SidebarGroupFrame = styled(YStack, {
  name: SIDEBAR_GROUP,
  position: 'relative',
  transition: 'padding 0.3s ease',
  padding: '0.5rem',
  //@ts-ignore
  overflowX: 'hidden',
})

const SidebarGroup = SidebarGroupFrame.styleable((props, forwardedRef) => {
  const { children, ...contentProps } = props

  return (
    <SidebarGroupFrame
      data-sidebar="group"
      className={'sidebar-group'}
      ref={forwardedRef}
      {...contentProps}
    >
      {children}
    </SidebarGroupFrame>
  )
})

SidebarGroup.displayName = SIDEBAR_GROUP

/* -------------------------------------------------------------------------------------------------
 * SidebarGroupLabel Component
 * -----------------------------------------------------------------------------------------------*/

const SidebarGroupLabelFrame = styled(SizableText, {
  whiteSpace: 'nowrap',
  transition: 'margin 0.3s ease',
})

const SidebarGroupLabel = SidebarGroupLabelFrame.styleable((props, forwardedRef) => {
  const { children, ...restProps } = props
  return (
    <SidebarGroupLabelFrame
      ref={forwardedRef}
      data-sidebar="group-label"
      className="sidebar-group-label group-data-[collapsible=icon]:opacity-0"
      {...restProps}
    >
      {children}
    </SidebarGroupLabelFrame>
  )
})

SidebarGroupLabel.displayName = 'SidebarGroupLabel'

/* -------------------------------------------------------------------------------------------------
 * SidebarGroupAction Component
 * -----------------------------------------------------------------------------------------------*/

/** Maybe use button base ? */
const SidebarGroupActionFrame = styled(Button, {
  position: 'absolute',
  top: '50%',
  right: '1.5px',
  transform: 'translateY(-50%)',
  pressStyle: {
    borderColor: 'transparent',
  },
  circular: true,
  chromeless: true,
})

const SidebarGroupAction = SidebarGroupActionFrame.styleable((props, forwardedRef) => {
  return (
    <SidebarGroupActionFrame
      className="sidebar-group-action group-data-[collapsible=icon]:opacity-0"
      ref={forwardedRef}
      data-sidebar="group-action"
      {...props}
    />
  )
})
SidebarGroupAction.displayName = 'SidebarGroupAction'

/* -------------------------------------------------------------------------------------------------
 * SidebarGroupContent Component
 * -----------------------------------------------------------------------------------------------*/

const SidebarGroupContentFrame = styled(ThemeableStack, {})

const SidebarGroupContent = SidebarGroupContentFrame.styleable((props, forwardedRef) => (
  <SidebarGroupContentFrame ref={forwardedRef} data-sidebar="group-content" {...props} />
))
SidebarGroupContent.displayName = 'SidebarGroupContent'

/* -------------------------------------------------------------------------------------------------
 * SidebarMenu Component
 * -----------------------------------------------------------------------------------------------*/
const SIDEBAR_MENU = 'SidebarMenu'

const SidebarMenuFrame = styled(YStack, {
  name: SIDEBAR_MENU,
  tag: 'ul',
  style: {
    listStyle: 'none',
  },
  //@ts-ignore
})

const SidebarMenu = SidebarMenuFrame.styleable((props, forwardedRef) => (
  <SidebarMenuFrame ref={forwardedRef} data-sidebar="menu" {...props} />
))
SidebarMenu.displayName = 'SidebarMenu'

/* -------------------------------------------------------------------------------------------------
 * SidebarMenuAction Component
 * -----------------------------------------------------------------------------------------------*/
const SIDEBAR_MENU_ACTION = 'SidebarMenuAction'

const SidebarMenuActionFrame = styled(SidebarButtonBase, {
  name: SIDEBAR_MENU_ACTION,
  position: 'absolute',
  top: '50%',
  transform: 'translateY(-50%)',
  circular: true,
  width: 'unset',
})

const SidebarMenuAction = SidebarMenuActionFrame.styleable((props, forwardedRef) => (
  <SidebarMenuActionFrame
    ref={forwardedRef}
    data-sidebar="sidebar-menu-action"
    className="group-data-[collapsible=icon]:opacity-0"
    {...props}
  />
))

SidebarMenuAction.displayName = SIDEBAR_MENU_ACTION

/* -------------------------------------------------------------------------------------------------
 * SidebarMenuItem Component
 * -----------------------------------------------------------------------------------------------*/
const SIDEBAR_MENUITEM = 'SidebarMenuItem'

const SidebarMenuItemFrame = styled(YStack, {
  name: SIDEBAR_MENUITEM,
  tag: 'li',
  // @ts-ignore˝
  display: 'list-item',
  position: 'relative',
})

const SidebarMenuItem = SidebarMenuItemFrame.styleable((props, forwardedRef) => (
  <SidebarMenuItemFrame ref={forwardedRef} data-sidebar="menu-item" {...props} />
))
SidebarMenuItemFrame.displayName = 'SidebarMenuItem'

/* -------------------------------------------------------------------------------------------------
 * SidebarMenuButton Component
 * -----------------------------------------------------------------------------------------------*/
const SIDEBAR_MENUBUTTON = 'SidebarMenuButton'

const SidebarMenuButtonFrame = styled(SidebarButtonBase, {
  name: SIDEBAR_MENUBUTTON,
  tag: 'button',
  role: 'button',
  display: 'flex',
  alignItems: 'center',
  flexDirection: 'row',
  width: '100%',
  cursor: 'pointer',
  focusable: true,

  variants: {
    unstyled: {
      false: {
        size: '$true',
        pressStyle: {
          borderColor: 'transparent',
        },
        hoverStyle: {
          backgroundColor: '$color4',
          borderColor: 'transparent',
        },

        // Shows focus when we click tab button
        focusVisibleStyle: {
          outlineColor: '$outlineColor',
          outlineStyle: 'solid',
          outlineWidth: 2,
        },
      },
    },
    size: {
      '...size': getButtonSized,
      ':number': getButtonSized,
    },
  },

  justifyContent: 'flex-start',
  overflow: 'hidden',

  defaultVariants: {
    unstyled: process.env.TAMAGUI_HEADLESS === '1',
  },
})

type SidebarMenuButtonProps = ThemeableStackProps & {
  tooltipProps?: TooltipProps & { title?: string }
}

const SidebarMenuButtonC = SidebarMenuButtonFrame.styleable<SidebarMenuButtonProps>(
  (props, forwardedRef) => {
    const { tooltipProps, className, ...restProps } = props

    if (tooltipProps) {
      const { title, ...restTooltipProps } = tooltipProps
      return (
        <Tooltip {...restTooltipProps}>
          <Tooltip.Trigger>
            <SidebarMenuButtonFrame
              ref={forwardedRef}
              data-sidebar="menu-button"
              className={`${className} sidebar-menu-btn`}
              {...restProps}
            />
          </Tooltip.Trigger>
          <Tooltip.Content>
            <Tooltip.Arrow />
            <SizableText>{title}</SizableText>
          </Tooltip.Content>
        </Tooltip>
      )
    }

    return (
      <SidebarMenuButtonFrame
        ref={forwardedRef}
        data-sidebar="menu-button"
        className={`${props.className} sidebar-menu-btn`}
        {...props}
      />
    )
  }
)

export const SidebarMenuButtonText = styled(Text, {
  name: 'ButtonText',
  color: '$color',
  userSelect: 'none',
  lineHeight: 'unset',

  variants: {
    size: {
      '...fontSize': (name, { font }) => {
        return {
          fontSize: font?.size[name],
        }
      },
    },
  } as const,
})

const SidebarMenuButtonIcon = (props: { children: any; className?: string }) => {
  const { size = '$true' } = React.useContext(SidebarContext)
  const smaller = getSize(size, {
    // default size is 1 rem
    shift: -3,
  })

  const theme = useTheme()
  return React.isValidElement(props.children)
    ? React.cloneElement(props.children, {
        // @ts-ignore
        size: smaller.val * 0.5,
        color: theme?.color?.get(),
        // @ts-ignore
        className: props?.className,
      })
    : null
}

const SidebarMenuButton = withStaticProperties(SidebarMenuButtonC, {
  Text: SidebarMenuButtonText,
  Icon: SidebarMenuButtonIcon,
})

SidebarMenuButton.displayName = SIDEBAR_MENUBUTTON

/* -------------------------------------------------------------------------------------------------
 * SidebarSubMenu Component
 * -----------------------------------------------------------------------------------------------*/
const SIDEBAR_SUB_MENU = 'SidebarSubMenu'

const SidebarSubMenuFrame = styled(YStack, {
  name: SIDEBAR_MENU,
  tag: 'ul',
  marginHorizontal: '$3',
  paddingHorizontal: '$2.5',
  borderLeftWidth: '1px',
  borderLeftColor: '$gray4Light',
})

const SidebarSubMenu = SidebarSubMenuFrame.styleable((props, forwardedRef) => (
  <SidebarSubMenuFrame
    ref={forwardedRef}
    data-sidebar="sub-menu"
    className="group-data-[collapsible=icon]:hidden"
    style={{ listStyle: 'none' }}
    {...props}
  />
))
SidebarSubMenu.displayName = SIDEBAR_SUB_MENU

/* -------------------------------------------------------------------------------------------------
 * SidebarSubMenuItem Component
 * -----------------------------------------------------------------------------------------------*/
const SIDEBAR_SUB_MENU_ITEM = 'SidebarSubMenuItem'

const SidebarSubMenuItemFrame = styled(YStack, {
  name: SIDEBAR_MENUITEM,
  tag: 'li',
  // @ts-ignore˝
  display: 'list-item',
  position: 'relative',
})

const SidebarSubMenuItem = SidebarSubMenuItemFrame.styleable((props, forwardedRef) => (
  <SidebarSubMenuItemFrame ref={forwardedRef} data-sidebar="menu-item" {...props} />
))
SidebarSubMenuItemFrame.displayName = SIDEBAR_SUB_MENU_ITEM

/* -------------------------------------------------------------------------------------------------
 * SidebarSubMenuButton Component
 * -----------------------------------------------------------------------------------------------*/
const SIDEBAR_SUB_MENU_BUTTON = 'SidebarSubMenuButton'

const SidebarSubMenuButtonFrame = styled(SidebarMenuButtonFrame, {
  name: SIDEBAR_SUB_MENU_BUTTON,
})

/***
 * Tooltip support add
 *
 */

const SidebarSubMenuButtonC = SidebarSubMenuButtonFrame.styleable((props, forwardedRef) => {
  return (
    <SidebarSubMenuButtonFrame
      ref={forwardedRef}
      data-sidebar="menu-button"
      className="sidebar-menu-btn"
      {...props}
    />
  )
})

const SidebarSubMenuButtonText = SidebarMenuButtonText
const SidebarSubMenuButtonIcon = SidebarMenuButtonIcon
const SidebarSubMenuButton = withStaticProperties(SidebarSubMenuButtonC, {
  Text: SidebarSubMenuButtonText,
  Icon: SidebarSubMenuButtonIcon,
})

SidebarSubMenuButton.displayName = SIDEBAR_SUB_MENU_BUTTON

/* -----------------------------------------------------------------------------------------------*/

function getState(open?: boolean) {
  return open ? 'open' : 'closed'
}

/* -----------------------------------------------------------------------------------------------*/

const Sidebar = withStaticProperties(_Sidebar, {
  Trigger: SidebarTrigger,
  Header: SidebarHeader,
  Footer: SidebarFooter,
  Content: SidebarContent,
})

export {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarGroupAction,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarHeader,
  SidebarMenu,
  SidebarMenuAction,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarProvider,
  SidebarSubMenu,
  SidebarSubMenuButton,
  SidebarSubMenuItem,
  SidebarTrigger,
}

export type {
  SidebarContentProps,
  SidebarFooterProps,
  SidebarHeaderProps,
  SidebarProps,
  SidebarTriggerProps,
}