// Copyright (c) Meta Platforms, Inc. and affiliates. /** * @file Badge.tsx * @input Uses React, HTMLAttributes * @output Exports Badge component, BadgeProps, BadgeVariant types * @position Core implementation; consumed by index.ts * * SYNC: When modified, update these files to stay in sync: * - /packages/core/src/Badge/Badge.doc.mjs (props table, features, implementation notes) * - /packages/core/src/Badge/Badge.test.tsx (tests for new/changed behavior) * - /packages/core/src/Badge/index.ts (exports if types change) * - /apps/storybook/stories/Badge.stories.tsx (storybook stories) * - /packages/cli/assets/templates/blocks/components/Badge/ (showcase blocks) */ import type { ReactNode } from 'react'; import type { BaseProps } from "../BaseProps.js"; import * as stylex from "../style-runtime.js"; import "../theme/tokens.stylex.js"; import { colorVars, spacingVars, radiusVars, fontWeightVars, typeScaleVars } from "../theme/tokens.stylex.js"; import { mergeProps } from "../utils/index.js"; import { themeProps } from "../utils/themeProps.js"; import type { BadgeVariantMap } from "./index.js"; /** * Base badge styles */ const styles = { base: { k1xSpc: "x3nfvp2", kGNEyG: "x6s0dn4", kjj79g: "xl56j7k", kOIVth: "xzye2dw", kZKoxP: "x1grt7ep", k8WAf4: "xt970qd", kg3NbH: "xf314gf", kaIpWk: "xjspbzw", kMv6JI: "xjb2p0i", kGuDYH: "x141an7d", kLWn49: "x1ltkj2j", k63SB2: "x1e4wzip", khDVqt: "xuxw1ft", ks0D6T: "x193iq5w", k7Eaqz: "xeuugli", $$css: true as const } }; /** * Variant styles for different badge appearances. * Semantic variants use solid backgrounds; non-semantic use tinted backgrounds. */ const variants = { neutral: { kWkggS: "x17x4s8c", kMwMTN: "x1tgivj0", $$css: true as const }, info: { kWkggS: "x1ewilqj", kMwMTN: "x17wrial", $$css: true as const }, success: { kWkggS: "xdsz4j9", kMwMTN: "xri61p4", $$css: true as const }, warning: { kWkggS: "x1q8g9m5", kMwMTN: "xrebv38", $$css: true as const }, error: { kWkggS: "x1pjz0fi", kMwMTN: "x1m024r3", $$css: true as const }, blue: { kWkggS: "x1o0wnni", kMwMTN: "x1vvqiwl", $$css: true as const }, cyan: { kWkggS: "x1rgj867", kMwMTN: "x1txnczv", $$css: true as const }, green: { kWkggS: "x1sqjeoo", kMwMTN: "xltfdvo", $$css: true as const }, orange: { kWkggS: "x1e9xt6e", kMwMTN: "xm47u9q", $$css: true as const }, pink: { kWkggS: "xnpoty2", kMwMTN: "xiuofww", $$css: true as const }, purple: { kWkggS: "x16i6n6f", kMwMTN: "x1m9wyeb", $$css: true as const }, red: { kWkggS: "x1cibrc5", kMwMTN: "x1joocv1", $$css: true as const }, teal: { kWkggS: "x1jtji5o", kMwMTN: "x9x0lbs", $$css: true as const }, yellow: { kWkggS: "x1bo7t0x", kMwMTN: "xdhq94a", $$css: true as const } }; /** * Badge variant type derived from BadgeVariantMap. * Extensible via module augmentation of BadgeVariantMap. */ export type BadgeVariant = keyof BadgeVariantMap; export interface BadgeProps extends BaseProps { /** Ref forwarded to the root element */ ref?: React.Ref; /** * The visual style variant of the badge. * @default 'neutral' */ variant?: BadgeVariant; /** * The badge label text. */ label: ReactNode; /** * Optional icon to display before the label. */ icon?: ReactNode; } /** * A badge component for displaying status indicators, counts, or labels. * * Styles use Astryx theme tokens via StyleX. * Wrap your app in to apply a theme. * * @example * ``` * * * * * ``` */ export function Badge({ variant = 'neutral', label, icon, xstyle, className, style, ref, ...props }: BadgeProps) { // Clipping a label makes its tail unrecoverable, so the full text has to // stay reachable somewhere. `title` is the half of that answer which costs // nothing: no measurement, no hook, so `Badge` renders the same on the // server and stays usable in a server component. It follows the shape // `BaseTable` already uses for a truncated header cell (BaseTable.tsx) — // string content only, and only when there is something to show. // // A rich label is left alone: it is a subtree whose text would have to be // flattened to a string, and flattening renders a guess — an icon, a // ``, a nested element all read differently. // // It is set whether or not the label actually fits, because knowing that // requires measuring. The refinement — a tooltip only when the text is // really cut, reachable by hover and by focus — needs that measurement and // a client component, so it is tracked in #5585. const labelTitle = typeof label === 'number' ? String(label) : typeof label === 'string' && label.length > 0 ? label : undefined; return {icon} {label} ; } Badge.displayName = 'Badge';