You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
217 lines
5.3 KiB
217 lines
5.3 KiB
'use client';
|
|
import React from 'react';
|
|
import { createMenu } from '@gluestack-ui/menu';
|
|
import { tva } from '@gluestack-ui/nativewind-utils/tva';
|
|
import { cssInterop } from 'nativewind';
|
|
import { Pressable, Text, View, ViewStyle } from 'react-native';
|
|
import {
|
|
Motion,
|
|
AnimatePresence,
|
|
MotionComponentProps,
|
|
} from '@legendapp/motion';
|
|
import type { VariantProps } from '@gluestack-ui/nativewind-utils';
|
|
|
|
type IMotionViewProps = React.ComponentProps<typeof View> &
|
|
MotionComponentProps<typeof View, ViewStyle, unknown, unknown, unknown>;
|
|
|
|
const MotionView = Motion.View as React.ComponentType<IMotionViewProps>;
|
|
|
|
const menuStyle = tva({
|
|
base: 'rounded-md bg-background-0 border border-outline-100 p-1 shadow-hard-5',
|
|
});
|
|
|
|
const menuItemStyle = tva({
|
|
base: 'min-w-[200px] p-3 flex-row items-center rounded data-[hover=true]:bg-background-50 data-[active=true]:bg-background-100 data-[focus=true]:bg-background-50 data-[focus=true]:web:outline-none data-[focus=true]:web:outline-0 data-[disabled=true]:opacity-40 data-[disabled=true]:web:cursor-not-allowed data-[focus-visible=true]:web:outline-2 data-[focus-visible=true]:web:outline-primary-700 data-[focus-visible=true]:web:outline data-[focus-visible=true]:web:cursor-pointer data-[disabled=true]:data-[focus=true]:bg-transparent',
|
|
});
|
|
|
|
const menuBackdropStyle = tva({
|
|
base: 'absolute top-0 bottom-0 left-0 right-0 web:cursor-default',
|
|
// add this classnames if you want to give background color to backdrop
|
|
// opacity-50 bg-background-500,
|
|
});
|
|
|
|
const menuSeparatorStyle = tva({
|
|
base: 'bg-background-200 h-px w-full',
|
|
});
|
|
|
|
const menuItemLabelStyle = tva({
|
|
base: 'text-typography-700 font-normal font-body',
|
|
|
|
variants: {
|
|
isTruncated: {
|
|
true: 'web:truncate',
|
|
},
|
|
bold: {
|
|
true: 'font-bold',
|
|
},
|
|
underline: {
|
|
true: 'underline',
|
|
},
|
|
strikeThrough: {
|
|
true: 'line-through',
|
|
},
|
|
size: {
|
|
'2xs': 'text-2xs',
|
|
'xs': 'text-xs',
|
|
'sm': 'text-sm',
|
|
'md': 'text-base',
|
|
'lg': 'text-lg',
|
|
'xl': 'text-xl',
|
|
'2xl': 'text-2xl',
|
|
'3xl': 'text-3xl',
|
|
'4xl': 'text-4xl',
|
|
'5xl': 'text-5xl',
|
|
'6xl': 'text-6xl',
|
|
},
|
|
sub: {
|
|
true: 'text-xs',
|
|
},
|
|
italic: {
|
|
true: 'italic',
|
|
},
|
|
highlight: {
|
|
true: 'bg-yellow-500',
|
|
},
|
|
},
|
|
});
|
|
|
|
const BackdropPressable = React.forwardRef<
|
|
React.ComponentRef<typeof Pressable>,
|
|
React.ComponentPropsWithoutRef<typeof Pressable> &
|
|
VariantProps<typeof menuBackdropStyle>
|
|
>(function BackdropPressable({ className, ...props }, ref) {
|
|
return (
|
|
<Pressable
|
|
ref={ref}
|
|
className={menuBackdropStyle({
|
|
class: className,
|
|
})}
|
|
{...props}
|
|
/>
|
|
);
|
|
});
|
|
|
|
type IMenuItemProps = VariantProps<typeof menuItemStyle> & {
|
|
className?: string;
|
|
} & React.ComponentPropsWithoutRef<typeof Pressable>;
|
|
|
|
const Item = React.forwardRef<
|
|
React.ComponentRef<typeof Pressable>,
|
|
IMenuItemProps
|
|
>(function Item({ className, ...props }, ref) {
|
|
return (
|
|
<Pressable
|
|
ref={ref}
|
|
className={menuItemStyle({
|
|
class: className,
|
|
})}
|
|
{...props}
|
|
/>
|
|
);
|
|
});
|
|
|
|
const Separator = React.forwardRef<
|
|
React.ComponentRef<typeof View>,
|
|
React.ComponentPropsWithoutRef<typeof View> &
|
|
VariantProps<typeof menuSeparatorStyle>
|
|
>(function Separator({ className, ...props }, ref) {
|
|
return (
|
|
<View
|
|
ref={ref}
|
|
className={menuSeparatorStyle({ class: className })}
|
|
{...props}
|
|
/>
|
|
);
|
|
});
|
|
export const UIMenu = createMenu({
|
|
Root: MotionView,
|
|
Item: Item,
|
|
Label: Text,
|
|
Backdrop: BackdropPressable,
|
|
AnimatePresence: AnimatePresence,
|
|
Separator: Separator,
|
|
});
|
|
|
|
cssInterop(MotionView, { className: 'style' });
|
|
|
|
type IMenuProps = React.ComponentProps<typeof UIMenu> &
|
|
VariantProps<typeof menuStyle> & { className?: string };
|
|
type IMenuItemLabelProps = React.ComponentProps<typeof UIMenu.ItemLabel> &
|
|
VariantProps<typeof menuItemLabelStyle> & { className?: string };
|
|
|
|
const Menu = React.forwardRef<React.ComponentRef<typeof UIMenu>, IMenuProps>(
|
|
function Menu({ className, ...props }, ref) {
|
|
return (
|
|
<UIMenu
|
|
ref={ref}
|
|
initial={{
|
|
opacity: 0,
|
|
scale: 0.8,
|
|
}}
|
|
animate={{
|
|
opacity: 1,
|
|
scale: 1,
|
|
}}
|
|
exit={{
|
|
opacity: 0,
|
|
scale: 0.8,
|
|
}}
|
|
transition={{
|
|
type: 'timing',
|
|
duration: 100,
|
|
}}
|
|
className={menuStyle({
|
|
class: className,
|
|
})}
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
);
|
|
|
|
const MenuItem = UIMenu.Item;
|
|
|
|
const MenuItemLabel = React.forwardRef<
|
|
React.ComponentRef<typeof UIMenu.ItemLabel>,
|
|
IMenuItemLabelProps
|
|
>(function MenuItemLabel(
|
|
{
|
|
className,
|
|
isTruncated,
|
|
bold,
|
|
underline,
|
|
strikeThrough,
|
|
size = 'md',
|
|
sub,
|
|
italic,
|
|
highlight,
|
|
...props
|
|
},
|
|
ref
|
|
) {
|
|
return (
|
|
<UIMenu.ItemLabel
|
|
ref={ref}
|
|
className={menuItemLabelStyle({
|
|
isTruncated,
|
|
bold,
|
|
underline,
|
|
strikeThrough,
|
|
size,
|
|
sub,
|
|
italic,
|
|
highlight,
|
|
class: className,
|
|
})}
|
|
{...props}
|
|
/>
|
|
);
|
|
});
|
|
|
|
const MenuSeparator = UIMenu.Separator;
|
|
|
|
Menu.displayName = 'Menu';
|
|
MenuItem.displayName = 'MenuItem';
|
|
MenuItemLabel.displayName = 'MenuItemLabel';
|
|
MenuSeparator.displayName = 'MenuSeparator';
|
|
export { Menu, MenuItem, MenuItemLabel, MenuSeparator };
|