// Copyright (c) Meta Platforms, Inc. and affiliates. 'use client'; /** * @file StatusDot.tsx * @input Uses React * @output Exports StatusDot component, StatusDotProps, StatusDotVariant types * @position Core implementation; consumed by index.ts * * SYNC: When modified, update these files to stay in sync: * - /packages/core/src/StatusDot/StatusDot.doc.mjs (props table, features, implementation notes) * - /packages/core/src/StatusDot/StatusDot.test.tsx (tests for new/changed behavior) * - /packages/core/src/StatusDot/index.ts (exports if types change) * - /apps/storybook/stories/StatusDot.stories.tsx (storybook stories) * - /packages/cli/assets/templates/blocks/components/StatusDot/ (showcase blocks) */ import type { ReactNode } from 'react'; import * as stylex from "../style-runtime.js"; import "../theme/tokens.stylex.js"; import { colorVars } from "../theme/tokens.stylex.js"; import type { BaseProps } from "../BaseProps.js"; import { Tooltip } from "../Tooltip/Tooltip.js"; import { isRenderable, mergeProps } from "../utils/index.js"; import { themeProps } from "../utils/themeProps.js"; import type { StatusDotVariantMap } from "./index.js"; /** * Pulse animation keyframes */ const pulseKeyframes = "xtut5q7-B"; /** Fixed dot size in px. A user-supplied icon is drawn into this same field. */ const DOT_SIZE = 8; /** * Base styles */ const styles = { base: { k1xSpc: "x3nfvp2", kGNEyG: "x6s0dn4", kjj79g: "xl56j7k", kaIpWk: "x16rqkct", kmuXW: "x2lah0s", kzqmXN: "x1xc55vz", kZKoxP: "xdk7pt", $$css: true as const }, pulsing: { kKVMdj: "x1mta51n", k44tkh: "x1c74tu6", kyAemX: "x4hg4is", ko0y90: "xa4qsjk", $$css: true as const }, reducedMotion: { kjO2d4: "x1aquc0h", $$css: true as const } }; /** * Variant styles mapping to theme color tokens. * * Each variant sets both the plate colour and an ink colour: a user-supplied * `icon` paints from `currentColor`, so plate and ink can never drift out of * contrast (same contract as AvatarStatusDot). The ink is each plate's * dedicated `--color-on-*` pairing (the Badge precedent), not the surface * colour: on-warning is a fixed dark ink, which keeps an icon legible on the * yellow plate (~9.6:1) where a light surface ink lands near 2:1. Neutral has * no `--color-on-*` token; its mid-grey plate takes the surface ink. */ const variants = { 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 }, accent: { kWkggS: "x1ewilqj", kMwMTN: "x17wrial", $$css: true as const }, neutral: { kWkggS: "x1q5y3ey", kMwMTN: "xrkvqaz", $$css: true as const } }; /** * Status dot variant type derived from StatusDotVariantMap. * Extensible via module augmentation of StatusDotVariantMap. */ export type StatusDotVariant = keyof StatusDotVariantMap; export interface StatusDotProps extends BaseProps { /** Ref forwarded to the root element */ ref?: React.Ref; /** * The semantic color variant. */ variant: StatusDotVariant; /** * Accessible label describing the status. Required for a11y — it is the * dot's `aria-label`, so the status reaches screen readers without hover. */ label: string; /** * Whether the dot should pulse to indicate activity. * Respects `prefers-reduced-motion`. * @default false */ isPulsing?: boolean; /** * Tooltip text shown on hover to explain the status meaning. * When omitted, no tooltip is rendered. */ tooltip?: string; /** * Optional icon to render centered inside the dot. Accepts any ReactNode * (typically an SVG icon), painted in the dot's `currentColor` ink. Same * contract as `AvatarStatusDot`'s `icon`. * * By default the dot is a colour-only signal; an icon gives the status a * non-colour mark, so use a different icon per status — the same icon on * every variant leaves the statuses distinguishable by colour alone * (WCAG 2.1 SC 1.4.1). Booleans and empty strings are ignored (safe for * `cond && `). * * @example * ``` * } /> * ``` */ icon?: ReactNode; } /** * A small colored dot indicator for status display (online/offline, severity, etc). * * Fixed 8px size. By default the dot conveys status by colour only, which is * not accessible in isolation (WCAG 2.1 SC 1.4.1) — treat it as a signal the * surrounding UI must make accessible: use it as a binary present/absent * signal, pair it with a visible label, pass `icon` so the status carries a * non-colour mark, or convey the status accessibly elsewhere. See the usage * guidance in the component docs. * * Renders as a non-focusable `` with `role="img"` and * `aria-label` for accessibility. Styles use Astryx theme tokens via StyleX. * Wrap your app in `` to apply a theme. * * @example * ``` * * * * * } /> * ``` */ export function StatusDot({ variant, label, isPulsing = false, tooltip, icon, xstyle, className, style, ref, ...props }: StatusDotProps) { // Booleans and empty renders don't count (safe for `cond && `) — // same contract as AvatarStatusDot. const showsIcon = isRenderable(icon); const dot = {showsIcon && } ; if (tooltip) { return {dot}; } return dot; } StatusDot.displayName = 'StatusDot';