This commit is contained in:
oneao committed 2025-11-17 17:30:20 +08:00
1 parent 18e9d94b77
commit 1f82f1dd1f
16 files changed
+1048 -297

No files matched your search

@@ -0,0 +1,214 @@
'use client';
import React from 'react';
import { Text, View } from 'react-native';
import { PrimitiveIcon, UIIcon } from '@gluestack-ui/core/icon/creator';
import { tva } from '@gluestack-ui/utils/nativewind-utils';
import {
withStyleContext,
useStyleContext,
} from '@gluestack-ui/utils/nativewind-utils';
import { cssInterop } from 'nativewind';
import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils';
import { Svg } from 'react-native-svg';
const SCOPE = 'BADGE';
const badgeStyle = tva({
base: 'flex-row items-center rounded-sm data-[disabled=true]:opacity-50 px-2 py-1',
variants: {
action: {
error: 'bg-background-error border-error-300',
warning: 'bg-background-warning border-warning-300',
success: 'bg-background-success border-success-300',
info: 'bg-background-info border-info-300',
muted: 'bg-background-muted border-background-300',
},
variant: {
solid: '',
outline: 'border',
},
size: {
sm: '',
md: '',
lg: '',
},
},
});
const badgeTextStyle = tva({
base: 'text-typography-700 font-body font-normal tracking-normal uppercase',
parentVariants: {
action: {
error: 'text-error-600',
warning: 'text-warning-600',
success: 'text-success-600',
info: 'text-info-600',
muted: 'text-background-800',
},
size: {
sm: 'text-2xs',
md: 'text-xs',
lg: 'text-sm',
},
},
variants: {
isTruncated: {
true: 'web:truncate',
},
bold: {
true: 'font-bold',
},
underline: {
true: 'underline',
},
strikeThrough: {
true: 'line-through',
},
sub: {
true: 'text-xs',
},
italic: {
true: 'italic',
},
highlight: {
true: 'bg-yellow-500',
},
},
});
const badgeIconStyle = tva({
base: 'fill-none',
parentVariants: {
action: {
error: 'text-error-600',
warning: 'text-warning-600',
success: 'text-success-600',
info: 'text-info-600',
muted: 'text-background-800',
},
size: {
sm: 'h-3 w-3',
md: 'h-3.5 w-3.5',
lg: 'h-4 w-4',
},
},
});
const ContextView = withStyleContext(View, SCOPE);
cssInterop(PrimitiveIcon, {
className: {
target: 'style',
nativeStyleToProp: {
height: true,
width: true,
fill: true,
color: 'classNameColor',
stroke: true,
},
},
});
type IBadgeProps = React.ComponentPropsWithoutRef<typeof ContextView> &
VariantProps<typeof badgeStyle>;
function Badge({
children,
action = 'muted',
variant = 'solid',
size = 'md',
className,
...props
}: { className?: string } & IBadgeProps) {
return (
<ContextView
className={badgeStyle({ action, variant, class: className })}
{...props}
context={{
action,
variant,
size,
}}
>
{children}
</ContextView>
);
}
type IBadgeTextProps = React.ComponentPropsWithoutRef<typeof Text> &
VariantProps<typeof badgeTextStyle>;
const BadgeText = React.forwardRef<
React.ComponentRef<typeof Text>,
IBadgeTextProps
>(function BadgeText({ children, className, size, ...props }, ref) {
const { size: parentSize, action: parentAction } = useStyleContext(SCOPE);
return (
<Text
ref={ref}
className={badgeTextStyle({
parentVariants: {
size: parentSize,
action: parentAction,
},
size,
class: className,
})}
{...props}
>
{children}
</Text>
);
});
type IBadgeIconProps = React.ComponentPropsWithoutRef<typeof PrimitiveIcon> &
VariantProps<typeof badgeIconStyle>;
const BadgeIcon = React.forwardRef<
React.ComponentRef<typeof Svg>,
IBadgeIconProps
>(function BadgeIcon({ className, size, ...props }, ref) {
const { size: parentSize, action: parentAction } = useStyleContext(SCOPE);
if (typeof size === 'number') {
return (
<UIIcon
ref={ref}
{...props}
className={badgeIconStyle({ class: className })}
size={size}
/>
);
} else if (
(props?.height !== undefined || props?.width !== undefined) &&
size === undefined
) {
return (
<UIIcon
ref={ref}
{...props}
className={badgeIconStyle({ class: className })}
/>
);
}
return (
<UIIcon
className={badgeIconStyle({
parentVariants: {
size: parentSize,
action: parentAction,
},
size,
class: className,
})}
{...props}
ref={ref}
/>
);
});
Badge.displayName = 'Badge';
BadgeText.displayName = 'BadgeText';
BadgeIcon.displayName = 'BadgeIcon';
export { Badge, BadgeIcon, BadgeText };
@@ -0,0 +1,220 @@
'use client'
import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils'
import type {
MotionComponentProps,
} from '@legendapp/motion'
import type { ViewStyle } from 'react-native'
import { createMenu } from '@gluestack-ui/core/menu/creator'
import { tva } from '@gluestack-ui/utils/nativewind-utils'
import {
AnimatePresence,
Motion,
} from '@legendapp/motion'
import { cssInterop } from 'nativewind'
import React from 'react'
import { Pressable, Text, View } from 'react-native'
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>
>(({ 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
>(({ 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>
>(({ className, ...props }, ref) => {
return (
<View
ref={ref}
className={menuSeparatorStyle({ class: className })}
{...props}
/>
)
})
export const UIMenu = createMenu({
Root: MotionView,
Item,
Label: Text,
Backdrop: BackdropPressable,
AnimatePresence,
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>(
({ 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
>((
{
className,
isTruncated,
bold,
underline,
strikeThrough,
size = 'md',
sub,
italic,
highlight,
...props
},
ref,
) => {
return (
<UIMenu.ItemLabel
ref={ref}
className={menuItemLabelStyle({
isTruncated: isTruncated as boolean,
bold: bold as boolean,
underline: underline as boolean,
strikeThrough: strikeThrough as boolean,
size,
sub: sub as boolean,
italic: italic as boolean,
highlight: highlight as boolean,
class: className,
})}
{...props}
/>
)
})
const MenuSeparator = UIMenu.Separator
Menu.displayName = 'Menu'
MenuItem.displayName = 'MenuItem'
MenuItemLabel.displayName = 'MenuItemLabel'
MenuSeparator.displayName = 'MenuSeparator'
export { Menu, MenuItem, MenuItemLabel, MenuSeparator }