import { forwardRef, useEffect, useMemo, type ReactNode } from 'react'; import { Text, View, type TextProps } from 'react-native'; import { ChipRoot as ChipRootPrimitive, Slot, dataAttributes, mergeDataAttributes, type IChipProps, } from '@cdx-ui/primitives'; import { cn, ParentContext, useParentContext, useStyleContext } from '@cdx-ui/utils'; import { Avatar, type AvatarProps } from '../Avatar'; import { Icon, type IconProps } from '../Icon'; import { positionToDataIcon, type IconSlotPosition } from '../../utils/positionToDataIcon'; import { chipAvatarVariants, chipIconVariants, chipLabelVariants, chipRootVariants, type ChipVariantProps, } from './styles'; const SCOPE = 'CHIP'; const useChipStyleContext = () => useStyleContext(SCOPE) as ChipVariantProps; // ============================================================================= // ROOT // ============================================================================= export type ChipVariant = 'subtle' | 'outline'; /** @deprecated Use {@link ChipVariant} (`'subtle'`) instead. */ export type ChipVariantTint = 'tint'; export type ChipColor = 'neutral' | 'action' | 'danger' | 'warning' | 'success' | 'info'; /** @deprecated Use {@link ChipColor} (`'neutral'`) instead. */ export type ChipColorDefault = 'default'; let hasWarnedChipTintVariant = false; let hasWarnedChipDefaultColor = false; export interface ChipProps extends IChipProps, Omit { /** * Visual style variant. * @default 'subtle' */ variant?: | ChipVariant /** @deprecated Use `'subtle'` instead. */ | 'tint'; /** * Color theme applied to the chip surface and content. * @default 'neutral' */ color?: | ChipColor /** @deprecated Use `'neutral'` instead. */ | 'default'; className?: string; children?: ReactNode; } const ChipRoot = forwardRef( ( { children, variant: variantProp = 'subtle', size = 'default', color: colorProp = 'neutral', className, style, ...props }, ref, ) => { useEffect(() => { if (__DEV__ && variantProp === 'tint' && !hasWarnedChipTintVariant) { hasWarnedChipTintVariant = true; console.warn( '[Chip] The `tint` variant is deprecated and will be removed in a future major release. Use `subtle` instead.', ); } }, [variantProp]); useEffect(() => { if (__DEV__ && colorProp === 'default' && !hasWarnedChipDefaultColor) { hasWarnedChipDefaultColor = true; console.warn( '[Chip] The `default` color is deprecated and will be removed in a future major release. Use `neutral` instead.', ); } }, [colorProp]); const variant = variantProp === 'tint' ? 'subtle' : variantProp; const color = colorProp === 'default' ? 'neutral' : colorProp; const parent = useParentContext(); const ctx = useMemo( () => ({ ...parent, [SCOPE]: { variant, size, color } }), [parent, variant, size, color], ); const computedClassName = cn(chipRootVariants({ variant, size, color }), className); return ( {children} ); }, ); ChipRoot.displayName = 'Chip'; // ============================================================================= // LABEL // ============================================================================= export interface ChipLabelProps extends TextProps { asChild?: boolean; className?: string; children?: ReactNode; } const ChipLabel = forwardRef( ({ asChild, className, children, style, ...props }, ref) => { const { size, variant, color } = useChipStyleContext(); const computedClassName = cn(chipLabelVariants({ size, variant, color }), className); const Comp = asChild ? Slot : Text; return ( {children} ); }, ); ChipLabel.displayName = 'Chip.Label'; // ============================================================================= // ICON // ============================================================================= export interface ChipIconProps extends Omit { asChild?: boolean; /** * Forge icon component to render. Required unless `asChild` is set, in which * case the consumer's substituted element is rendered instead. */ as?: IconProps['as']; /** * Position hint that emits `data-icon="inline-start"` or `data-icon="inline-end"`. * CVA uses this attribute to apply leading/trailing margin offsets logically * (RTL-aware). Omit when the icon is freely positioned within a custom layout. */ position?: IconSlotPosition; } const ChipIcon = ({ asChild, className, style, as, position, ...props }: ChipIconProps) => { const { size, color } = useChipStyleContext(); const computedClassName = cn(chipIconVariants({ size, color }), className); if (asChild) { return ( ); } if (!as) { return null; } return ( ); }; ChipIcon.displayName = 'Chip.Icon'; // ============================================================================= // AVATAR // ============================================================================= export interface ChipAvatarProps extends Omit {} const ChipAvatar = ({ className, style, children, ...props }: ChipAvatarProps) => { const { size } = useChipStyleContext(); // chipAvatarVariants sets w/h and the leading-edge negative margin. // Avatar renders with size="sm" (its smallest named size) but chipAvatarVariants // overrides the w/h via tailwind-merge so the chip size wins. const computedClassName = cn(chipAvatarVariants({ size }), className); return ( {children} ); }; ChipAvatar.displayName = 'Chip.Avatar'; // ============================================================================= // COMPOUND EXPORT // ============================================================================= type ChipCompoundComponent = typeof ChipRoot & { Label: typeof ChipLabel; Icon: typeof ChipIcon; Avatar: typeof ChipAvatar; }; export const Chip = Object.assign(ChipRoot, { Label: ChipLabel, Icon: ChipIcon, Avatar: ChipAvatar, }) as ChipCompoundComponent; export { type ChipVariantProps } from './styles';