u
This commit is contained in:
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 }
|
||||
Reference in new issue
Block a user