// Copyright (c) Meta Platforms, Inc. and affiliates. 'use client'; /** * @file Token.tsx * @input Uses React, ReactNode, StyleXStyles * @output Exports Token component, TokenProps, TokenColor, TokenColorMap types * @position Core implementation; consumed by index.ts, tested by Token.test.tsx * * SYNC: When modified, update these files to stay in sync: * - /packages/core/src/Token/TokenLink.tsx (interactive wrapper for href+onRemove) * - /packages/core/src/Token/Token.doc.mjs (props table, features, implementation notes) * - /packages/core/src/Token/Token.test.tsx (tests for new/changed behavior) * - /packages/core/src/Token/index.ts (exports if types change) * - /apps/storybook/stories/Token.stories.tsx (storybook stories) * - /packages/cli/assets/templates/blocks/components/Token/ (showcase blocks) */ import type { ReactNode } from 'react'; import * as stylex from "../style-runtime.js"; import "../theme/tokens.stylex.js"; import { colorVars, spacingVars, radiusVars, sizeVars, durationVars, easeVars, fontWeightVars, typeScaleVars } from "../theme/tokens.stylex.js"; import { Icon } from "../Icon/index.js"; import { mergeProps } from "../utils/index.js"; import { useLinkComponent } from "../Link/useLinkComponent.js"; import { useInteractiveRole } from "../hooks/useInteractiveRole.js"; import { TokenLink } from "./TokenLink.js"; import type { BaseProps } from "../BaseProps.js"; import { themeProps } from "../utils/themeProps.js"; import { focusOutlineProps } from "../utils/focusOutline.stylex.js"; import { interactionOverlayStyles } from "../utils/interactionOverlay.stylex.js"; import { useTranslator } from "../i18n/index.js"; import type { TokenColorMap } from "./index.js"; // ============================================================================= // Types // ============================================================================= /** * Token color type derived from TokenColorMap. * Extensible via module augmentation of TokenColorMap. */ export type TokenColor = keyof TokenColorMap; export type TokenSize = 'sm' | 'md' | 'lg'; export interface TokenProps extends BaseProps { /** Ref forwarded to the root element */ ref?: React.Ref; /** * The text label displayed in the token. */ label: string; /** * The size of the token. * @default 'md' */ size?: TokenSize; /** * The color variant of the token. * @default 'default' */ color?: TokenColor; /** * Optional icon to display before the label. */ icon?: ReactNode; /** * Whether the token is disabled. * @default false */ isDisabled?: boolean; /** * Callback when the remove (X) button is clicked. * When provided, a remove button is rendered. */ onRemove?: (e: React.MouseEvent) => void; /** * Click handler. When provided, the token renders as a `` container * with an invisible `; const content = <> {icon} {label} {endContent} {removeButton} ; const sharedProps = { 'data-testid': testId, ...(isLabelHidden ? { 'aria-label': label } : {}), ...(description != null ? { 'aria-description': description } : {}) }; if (role === 'link') { if (onRemove == null) { return } href={href as string} {...mergeProps(themeProps('token', { color, size }), focusOutlineProps.focusVisible(styles.base, sizeStyles[size], colorStyles[color], styles.interactive, interactionOverlayStyles.backgroundImage, isDisabled && styles.disabled, xstyle), className, style)} {...rest} {...isDisabled ? { 'aria-disabled': true } : {}} {...sharedProps}> {content} ; } // With a remove button, the link cannot be the root — nesting a {endContent} {removeButton} ; } return {content} ; } Token.displayName = 'Token';