122 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			122 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import { CaretRight, Check, Circle } from 'phosphor-react'
 | 
						|
import { FC, Fragment, ReactNode } from 'react'
 | 
						|
import { Flex, Text } from '../'
 | 
						|
import {
 | 
						|
  ContextMenuCheckboxItem,
 | 
						|
  ContextMenuContent,
 | 
						|
  ContextMenuItem,
 | 
						|
  ContextMenuItemIndicator,
 | 
						|
  ContextMenuLabel,
 | 
						|
  ContextMenuRadioGroup,
 | 
						|
  ContextMenuRadioItem,
 | 
						|
  ContextMenuRoot,
 | 
						|
  ContextMenuSeparator,
 | 
						|
  ContextMenuTrigger,
 | 
						|
  ContextMenuTriggerItem
 | 
						|
} from './primitive'
 | 
						|
 | 
						|
export type TextOption = {
 | 
						|
  type: 'text'
 | 
						|
  label: ReactNode
 | 
						|
  onSelect?: () => any
 | 
						|
  children?: ContentMenuOption[]
 | 
						|
}
 | 
						|
export type SeparatorOption = { type: 'separator' }
 | 
						|
export type CheckboxOption = {
 | 
						|
  type: 'checkbox'
 | 
						|
  label: ReactNode
 | 
						|
  checked?: boolean
 | 
						|
  onCheckedChange?: (isChecked: boolean) => any
 | 
						|
}
 | 
						|
export type RadioOption<T extends string = string> = {
 | 
						|
  type: 'radio'
 | 
						|
  label: ReactNode
 | 
						|
  onValueChange?: (value: string) => any
 | 
						|
  value: T
 | 
						|
  options?: { value: T; label?: ReactNode }[]
 | 
						|
}
 | 
						|
 | 
						|
type WithCommons = { key: string; disabled?: boolean }
 | 
						|
 | 
						|
export type ContentMenuOption = (TextOption | SeparatorOption | CheckboxOption | RadioOption) &
 | 
						|
  WithCommons
 | 
						|
 | 
						|
export interface IContextMenu {
 | 
						|
  options?: ContentMenuOption[]
 | 
						|
  isNested?: boolean
 | 
						|
}
 | 
						|
export const ContextMenu: FC<IContextMenu> = ({ children, options, isNested }) => {
 | 
						|
  return (
 | 
						|
    <ContextMenuRoot>
 | 
						|
      {isNested ? (
 | 
						|
        <ContextMenuTriggerItem>{children}</ContextMenuTriggerItem>
 | 
						|
      ) : (
 | 
						|
        <ContextMenuTrigger>{children}</ContextMenuTrigger>
 | 
						|
      )}
 | 
						|
      {options && !!options.length && (
 | 
						|
        <ContextMenuContent sideOffset={isNested ? 2 : 5}>
 | 
						|
          {options.map(({ key, ...option }) => {
 | 
						|
            if (option.type === 'text') {
 | 
						|
              const { children, label, onSelect } = option
 | 
						|
              if (children)
 | 
						|
                return (
 | 
						|
                  <ContextMenu isNested key={key} options={children}>
 | 
						|
                    <Flex fluid row justify="space-between" align="center">
 | 
						|
                      <Text>{label}</Text>
 | 
						|
                      <CaretRight />
 | 
						|
                    </Flex>
 | 
						|
                  </ContextMenu>
 | 
						|
                )
 | 
						|
              return (
 | 
						|
                <ContextMenuItem key={key} onSelect={onSelect}>
 | 
						|
                  {label}
 | 
						|
                </ContextMenuItem>
 | 
						|
              )
 | 
						|
            }
 | 
						|
            if (option.type === 'checkbox') {
 | 
						|
              const { label, checked, onCheckedChange } = option
 | 
						|
              return (
 | 
						|
                <ContextMenuCheckboxItem
 | 
						|
                  key={key}
 | 
						|
                  checked={checked}
 | 
						|
                  onCheckedChange={onCheckedChange}
 | 
						|
                >
 | 
						|
                  <Flex row align="center">
 | 
						|
                    <ContextMenuItemIndicator>
 | 
						|
                      <Check />
 | 
						|
                    </ContextMenuItemIndicator>
 | 
						|
                    <Text css={{ ml: checked ? '$4' : undefined }}>{label}</Text>
 | 
						|
                  </Flex>
 | 
						|
                </ContextMenuCheckboxItem>
 | 
						|
              )
 | 
						|
            }
 | 
						|
            if (option.type === 'radio') {
 | 
						|
              const { label, options, onValueChange, value } = option
 | 
						|
              return (
 | 
						|
                <Fragment key={key}>
 | 
						|
                  <ContextMenuLabel>{label}</ContextMenuLabel>
 | 
						|
                  <ContextMenuRadioGroup value={value} onValueChange={onValueChange}>
 | 
						|
                    {options?.map(({ value: v, label }) => {
 | 
						|
                      return (
 | 
						|
                        <ContextMenuRadioItem key={v} value={v}>
 | 
						|
                          <ContextMenuItemIndicator>
 | 
						|
                            <Circle weight="fill" />
 | 
						|
                          </ContextMenuItemIndicator>
 | 
						|
                          <Text css={{ ml: '$4' }}>{label}</Text>
 | 
						|
                        </ContextMenuRadioItem>
 | 
						|
                      )
 | 
						|
                    })}
 | 
						|
                  </ContextMenuRadioGroup>
 | 
						|
                </Fragment>
 | 
						|
              )
 | 
						|
            }
 | 
						|
            return <ContextMenuSeparator key={key} />
 | 
						|
          })}
 | 
						|
        </ContextMenuContent>
 | 
						|
      )}
 | 
						|
    </ContextMenuRoot>
 | 
						|
  )
 | 
						|
}
 | 
						|
 | 
						|
export default ContextMenu
 |