import type { Snippet } from 'svelte'; import type { HTMLAttributes } from 'svelte/elements'; import type { MintProp } from '../../mint/index.js'; import type { BadgePlacement, BadgeSlots, BadgeVariants } from './badge.variants.js'; /** * Shared fields that apply to every Badge arm. `variant`, `purpose`, and the * label-only props (`children` / `counter` / `removable` / `interactive` / * `onRemove`) are declared per-arm instead — that split is what lets *both* * dot spellings (`variant="dot"` and the canonical `purpose="dot"`) forbid the * label-only props at the type level. */ interface BadgeBaseProps extends Omit, Omit, 'children'> { /** Add a pulsing animation to draw attention (e.g. for live indicators). Stilled under `prefers-reduced-motion`. */ pulse?: boolean; /** Visually disable the badge (reduced opacity, no pointer events). */ disabled?: boolean; /** Add a ring outline in the page background color — useful to visually separate overlapping or positioned badges from their parent. */ border?: boolean; /** Anchor the badge absolutely within a `position: relative` parent. */ placement?: BadgePlacement; /** Click handler. Makes the badge operable: interactive styles, a tab stop, Enter/Space activation and `role="button"`. */ onclick?: (event: MouseEvent) => void; /** Called when the hover state changes. */ onHover?: (hovered: boolean) => void; /** Extra classes merged onto the root element. */ class?: string; /** Remove all default tv classes. */ unstyled?: boolean; /** Per-slot class overrides merged with tv styles. */ slotClasses?: Partial>; /** * Apply a named preset registered via ``. * Prefer this over `class` overrides when the requested look falls outside the * semantic intent palette — presets keep hover/active/dark-mode logic coherent * and make the custom look reusable across the project. */ preset?: string; /** * ARIA role. Derived from `purpose` when unset: `status` and `dot` render * `"status"` — a polite live region, right for a state marker; `tag`, * `counter` and `chip` render no role — a category, a count or a filter chip * announces nothing when it changes, so it stays a plain `span`: a label * inside a link rather than a live region inside one. A badge with an * `onclick` handler (when not `disabled`) is a `"button"` whatever its * purpose, so assistive tech announces its activation semantics; * `purpose="chip"` or `interactive` alone change only the look — without a * handler there is nothing to activate, so no button is announced and the * badge stays outside the tab order. Without `purpose` a static badge keeps * `"status"`, and so does the deprecated `counter` boolean. Set explicitly to * override: `"status"` for a count that must be announced when it changes, * `"alert"` for a time-sensitive notification. An explicit value always wins * over the derived default. * @summary Announced role; follows purpose (status for a state marker, none for a tag or count) unless set. */ role?: 'status' | 'alert' | 'button'; /** * Micro-interaction preset applied to the badge. Only applies while * interactive (`purpose="chip"`, `interactive`, or `onclick`) and not * disabled. * @default 'none' */ mint?: MintProp; } /** * The label-only props a pure-indicator dot forbids. An invisible remove * button, counter shape, or hover-scale on a 2.5 × 2.5 px dot is never the * intended UI, so every dot arm excludes them at the type level. */ interface BadgeDotForbiddenProps { children?: never; counter?: never; removable?: never; interactive?: never; onRemove?: never; } /** * Dot badge selected by the canonical `purpose="dot"` — a pure indicator whose * content is hidden. `variant` is inert here (the dot look always wins), and * the label-only props are excluded by {@link BadgeDotForbiddenProps}. */ interface BadgeDotByPurposeProps extends BadgeBaseProps, BadgeDotForbiddenProps { /** * The canonical dot spelling — forces the pure-indicator look regardless of * `variant`. For the label roles use `status` / `tag` / `counter` / `chip` * (their own arm). */ purpose: 'dot'; /** Inert under `purpose="dot"`; accepted only so a leftover `variant` compiles. */ variant?: 'filled' | 'outlined' | 'soft' | 'dot'; } /** * Dot badge selected by the deprecated `variant="dot"`. Prefer `purpose="dot"`. * Same exclusions as {@link BadgeDotByPurposeProps}. */ interface BadgeDotProps extends BadgeBaseProps, BadgeDotForbiddenProps { /** Visual variant. `dot` renders a pure indicator (content hidden); the label variants accept the full surface. */ variant: 'dot'; /** Only the dot purpose is non-contradictory with `variant="dot"`. */ purpose?: 'dot'; } /** * Label-style badge — accepts content, counter shape, remove button, etc. */ interface BadgeStandardProps extends BadgeBaseProps { /** * The badge's semantic purpose — the canonical axis that resolves what the * badge *is*, since a bare Badge served overlapping roles. Orchestrates the * low-level visual props so you rarely set them directly: * - `status` — a state marker (Active, Failed); pairs with `intent`. * - `tag` — a neutral inline label (category, version). The one purpose that * also picks the intent: it defaults to `neutral`, because a category carries * no severity and painting it as one is the most common colour defect there * is. Pass `intent` explicitly to override. * - `counter` — a compact numeric pill (replaces the `counter` boolean). * - `chip` — a removable, interactive filter chip; pair with `removable`. * * For a pure indicator use `purpose="dot"` (its own arm — it forbids * content / counter / remove). Leave unset to drive the badge purely by the * low-level props (back-compat); when set, `purpose` wins over the `counter` * boolean and picks the default `role` (see there). */ purpose?: 'status' | 'tag' | 'counter' | 'chip'; /** Visual variant. `dot` renders a pure indicator (content hidden); the label variants accept the full surface. @default 'filled' */ variant?: 'filled' | 'outlined' | 'soft'; /** Badge content (text, icons, numbers). */ children?: Snippet; /** * Display as a compact pill for numeric counts (tightens padding, tabular-nums). * Keeps the pre-`purpose` ARIA default with the rest of the low-level props: a * badge driven by this boolean stays a `"status"` live region, where * `purpose="counter"` carries no role. A count that must be announced when it * changes sets `role="status"` explicitly. * @summary Compact numeric pill for counts. Unlike purpose="counter", a badge driven by this boolean stays a status region. * @deprecated Prefer `purpose="counter"` — the canonical semantic axis. Kept for back-compat. */ counter?: boolean; /** Show a remove (×) button. */ removable?: boolean; /** * Enable the interactive look (pointer cursor, hover/press scale, mint). * Automatically enabled when `onclick` is provided — and only the handler * makes the badge a tab stop with Enter/Space activation; `interactive` * alone never creates a focus stop that answers no key. * @summary Interactive look (cursor, hover scale); a keyboard tab stop only together with a click handler. */ interactive?: boolean; /** Fired when the remove button is clicked (only when `removable` is true). */ onRemove?: () => void; } /** * @summary A small label for a status, a category or a count. * @description Compact label for status, categories, counters, and notifications. * * Badge props are a discriminated union. The pure-indicator dot — spelled * canonically as `purpose="dot"` or via the deprecated `variant="dot"` — * forbids `children` / `counter` / `removable` / `interactive` / `onRemove` * at the type level, while the label arms (`filled` / `outlined` / `soft`; * `purpose` `status` / `tag` / `counter` / `chip`) accept the full surface. * A badge with an `onclick` handler is announced as a `button` and joins the * tab order. A static one takes its role from `purpose`: `status` and `dot` * are a `status` live region; `tag`, `counter` and `chip` carry no role and * render a plain span, so a tag inside a link is a label and not a live region; * without `purpose` it stays a `status`. Override via `role`. Its accessible * name is always the visible label; a removable badge's ✕ control names itself * ("Remove badge"). * * @tag feedback * @related Alert * @related Toast * * @example Purpose-driven (canonical) — the intent reads from `purpose` * ```svelte * Active * 5 * removeTag('react')}>React * * ``` * * @example Notification counter anchored to a trigger * ```svelte *
* * 5 *
* ``` */ export type BadgeProps = BadgeDotByPurposeProps | BadgeDotProps | BadgeStandardProps; export { default as Badge } from './Badge.svelte'; export { type BadgePlacement, type BadgeVariants, badgeVariants, PLACEMENT_VALUES } from './badge.variants.js';