From 4187c3bd80311cca619d38f3e4781a730486cfbc Mon Sep 17 00:00:00 2001 From: SrJuggernaut Date: Mon, 23 Mar 2026 10:29:16 -0600 Subject: [PATCH] feat: add Menu component with sub-components --- src/components/ui/Menu.tsx | 107 +++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 src/components/ui/Menu.tsx diff --git a/src/components/ui/Menu.tsx b/src/components/ui/Menu.tsx new file mode 100644 index 0000000..8687e0e --- /dev/null +++ b/src/components/ui/Menu.tsx @@ -0,0 +1,107 @@ +import { useRender } from '@base-ui/react/use-render' +import { cx } from '@styled-system/css' +import { type MenuVariantProps, menu } from '@styled-system/recipes/menu' +import type { FC, ReactNode } from 'react' +import type { MergeOmitting } from '@/types/helpers' + +export type MenuProps = MergeOmitting< + useRender.ComponentProps<'div'>, + MenuVariantProps +> + +const Menu: FC = ({ render, className, ...props }) => { + const [menuProps, allOther] = menu.splitVariantProps(props) + return useRender({ + defaultTagName: 'div', + render, + props: { className: cx(menu(menuProps).container, className), ...allOther } + }) +} + +export default Menu + +export type MenuItemProps = MergeOmitting< + useRender.ComponentProps<'button'>, + MenuVariantProps +> + +export const MenuItem: FC = ({ + render, + className, + ...props +}) => { + const [menuProps, allOther] = menu.splitVariantProps(props) + return useRender({ + defaultTagName: 'button', + render, + props: { className: cx(menu(menuProps).item, className), ...allOther } + }) +} + +export type MenuLabelProps = MergeOmitting< + useRender.ComponentProps<'span'>, + MenuVariantProps +> + +export const MenuLabel: FC = ({ + render, + className, + ...props +}) => { + const [menuProps, allOther] = menu.splitVariantProps(props) + + return useRender({ + defaultTagName: 'span', + render, + props: { className: cx(menu(menuProps).label, className), ...allOther } + }) +} + +export type MenuGroupProps = MergeOmitting< + useRender.ComponentProps<'div'>, + MenuVariantProps & { label: ReactNode } +> + +export const MenuGroup: FC = ({ + render, + children, + className, + label, + ...props +}) => { + const [menuProps, allOther] = menu.splitVariantProps(props) + return useRender({ + defaultTagName: 'div', + render, + + props: { + className: cx(menu(menuProps).group, className), + children: ( + <> + {label} + {children} + + ), + ...allOther + } + }) +} + +export type MenuSeparatorProps = MergeOmitting< + Omit, 'children'>, + MenuVariantProps +> + +export const MenuSeparator: FC = ({ + render, + className, + ...props +}) => { + const [menuProps, allOther] = menu.splitVariantProps(props) + + return useRender({ + defaultTagName: 'div', + render, + props: { className: cx(menu(menuProps).separator, className), ...allOther } + }) +}