{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "alert",
  "title": "Alert",
  "description": "Alert from the Get IT Done design system.",
  "dependencies": [
    "class-variance-authority@^0.7.0",
    "lucide-react@^0.379.0"
  ],
  "registryDependencies": [
    "https://zero-design.itdone.de/r/heading-tag.json",
    "https://zero-design.itdone.de/r/labels-context.json",
    "https://zero-design.itdone.de/r/utils.json",
    "https://zero-design.itdone.de/r/theme.json"
  ],
  "files": [
    {
      "path": "registry/ui/alert.tsx",
      "content": "'use client';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { X } from 'lucide-react';\nimport * as React from 'react';\n\nimport { cn } from '@/lib/utils';\nimport type { HeadingTag } from '@/components/ui/heading-tag';\nimport { useLabels, usePreset } from '@/components/ui/labels-context';\n\n// Every severity variant is built exclusively from the `general-{severity}-*` tokens (already\n// shipped, dark-mode-aware via the `.dark` CSS-var scope — see theme.css) — never a raw Tailwind\n// palette class. The soft \"container\" background is the SAME token family consumed through\n// Tailwind's CSS-var opacity syntax (`/10`) rather than a new token, matching the MD3\n// alert/container role (soft tint + saturated on-container text) without inventing a value.\n//\n// KNOWN TOKEN GAP (ALERT-TOK-01): the token repo's JSON source (`figma-tokens/default_theme.json`\n// + `default_theme_dark.json`) already defines dedicated `general.{info,success,warning,error}\n// .container` / `.on-container` pairs, which would be the more semantically exact tokens for this\n// role — but they were never carried into the built CSS vars / Tailwind theme (`dist/theme.css`\n// has no `--colors-general-*-container` entries at all). Until `kette-tokens` regenerates\n// its CSS-var build to include them, this file intentionally falls back to the opacity-modified\n// `-default` token above rather than hardcoding a value or fabricating a token name.\n//\n// KNOWN TOKEN GAP (ALERT-TOK-02): there is no dedicated light-tint ramp for `info` at all\n// (`--semantic-error-50..900` and `--semantic-warning-50..900` exist; `--semantic-success-50..900`\n// exists; no `--semantic-info-*` ramp is shipped) — another reason the ramp-based tints were not\n// used here (would have left `info` inconsistent with the other three severities).\n//\n// KNOWN TOKEN GAP (ALERT-TOK-03): where the `--semantic-{error,success,warning}-*` ramps DO exist,\n// they are declared once at `:root` and never redefined under `.dark` — the same very-light tint\n// would render in dark mode. This is a second, independent reason those ramps were not used here.\n//\n// KNOWN TOKEN GAP (ALERT-TOK-05, tracks ALERT-A11Y-06 / ALERT-A11Y-05 — same root cause already\n// flagged on Chip as CHIP-MAT-01 / CHIP-A11Y-05, see chip.tsx): the `default`/`decent` variants'\n// boundary is carried entirely by `border-general-border-default` (there is no `/10` tint or any\n// other secondary fill-color cue distinguishing these two variants' backgrounds from the page in\n// dark mode — `background-default`/`background-subtle`/`background-muted` differ from each other by\n// only 1.13-1.38:1 there). In dark mode `--colors-general-border-default` (rgb(51 65 85)) is\n// byte-identical to `--semantic-border-default` and computes to only ~1.95:1 against\n// `--semantic-background-default` (rgb(2 6 23)) — under the WCAG 1.4.11 non-text 3:1 minimum (light\n// mode is fine at ~17.85:1, same token pair). Unlike the four severity variants below, the\n// `general-border-*` family ships ONLY a `-default` shade in `dist/theme.css` — no `-hover`/\n// `-active`/`-strong` step exists to borrow the way ALERT-TOK-04 borrows `general-{severity}-hover`\n// — so there is no already-shipped token this file can re-point to without fabricating a value.\n// Needs a dedicated, more-separated `general-border-default` (or a new `general-border-strong`)\n// dark-mode value from design-tokens-maintainer, tuned to clear 3:1 against\n// `semantic-background-default`; re-verify via the pinned regression assertions in alert.test.tsx\n// (`describe('WCAG 1.4.11 non-text contrast (ALERT-A11Y-06)')`) once it ships, and update those\n// assertions to require >=3:1 to close this gap.\n//\n// KNOWN TOKEN GAP (ALERT-TOK-04, closes ALERT-A11Y-03 / ALERT-A11Y-04): the `-default` shade alone\n// does not clear WCAG 1.4.3 (text) or 1.4.11 (non-text) contrast against the `/10` tint + page\n// background in light mode for 3 of 4 severities (measured: text 3.15-4.14:1 against the tinted\n// container; border/40 1.6-2.1:1 against the page). `-active` is bitwise identical to `-default`\n// in the current build (no headroom), but `-hover` IS a distinct, more-saturated step in both\n// themes, so text/icon use `-hover` (verified ≥4.58:1 in light, ≥4.86:1 in dark against the tinted\n// container) and the border uses `-hover` at 70% opacity (verified ≥3.11:1 in light, ≥3.95:1 in\n// dark against the page). This is an interim reuse of an interaction-scoped token as a static\n// on-container/border color, not a new value — the correct long-term fix is the dedicated\n// `-on-container` / `-container` pair from ALERT-TOK-01, flagged to design-tokens-maintainer.\n// Inline links (Button variant=\"link\") inside a severity alert take the alert text colour: the link blue measured 4.57:1 on the warning tint and 4.05:1 on the destructive tint. The underline stays as the cue that the text is a link (not colour alone, WCAG 1.4.1), hover and pressed keep the colour and only thicken the underline.\nconst ALERT_LINK = [\n  '[&_[data-slot=button][data-variant=link]]:text-inherit',\n  '[&_[data-slot=button][data-variant=link]:is(:hover,:active)]:text-inherit',\n  '[&_[data-slot=button][data-variant=link]]:underline',\n  '[&_[data-slot=button][data-variant=link]]:underline-offset-4',\n  '[&_[data-slot=button][data-variant=link]:hover]:decoration-2',\n];\n\nconst alertVariants = cva(['relative w-full border border-solid', '[&>svg]:absolute'], {\n  variants: {\n    variant: {\n      default: 'border-general-border-default bg-surface-canvas text-text-default [&>svg]:text-text-default',\n      decent: 'border-general-border-default bg-background-neutral-subtle text-text-default [&>svg]:text-text-default',\n      info: [\n        'border-general-info-hover/70 bg-general-info-default/10',\n        'text-general-info-hover [&>svg]:text-general-info-hover',\n        ...ALERT_LINK,\n      ],\n      success: [\n        'border-general-success-hover/70 bg-general-success-default/10',\n        'text-general-success-hover [&>svg]:text-general-success-hover',\n        ...ALERT_LINK,\n      ],\n      warning: [\n        'border-general-warning-hover/70 bg-general-warning-default/10',\n        'text-general-warning-hover [&>svg]:text-general-warning-hover',\n        ...ALERT_LINK,\n      ],\n      destructive: [\n        'border-general-error-hover/70 bg-general-error-default/10',\n        // On-subtle text role: 5.0:1 or better on the tint in every theme and mode (the hover role fell to 3.9:1 in windows-95).\n        'text-text-destructive-on-subtle [&>svg]:text-text-destructive-on-subtle',\n        ...ALERT_LINK,\n      ],\n    },\n    // `default` is the established layout. `compact` is a single-line note: no border, severity tint\n    // fill, surface radius, `ui` type role, and an `icon-md` glyph sized from the control tokens.\n    size: {\n      default: [\n        'rounded-card p-4 text-sm leading-3',\n        '[&>svg~*:not([data-slot=alert-close])]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:left-4 [&>svg]:top-[1.2rem] [&>svg]:h-4 [&>svg]:w-4',\n      ],\n      compact: [\n        'rounded-surface border-transparent px-control-px-lg py-control-py-md typography-ui text-text-default',\n        '[&>svg]:left-control-px-lg [&>svg]:top-control-py-md [&>svg]:h-icon-md [&>svg]:w-icon-md [&>svg]:text-icon-default',\n        '[&>svg~*:not([data-slot=alert-close])]:pl-[calc(theme(spacing.icon-md)+theme(spacing.gap-control))]',\n      ],\n    },\n    // Where an AlertActions row sits. `below` (default) stacks it under the text. `end` puts it on the right, vertically centred, once the alert itself is at least 36rem wide (container query on a wrapper, not the viewport, so an alert in a narrow column or next to a sidebar still stacks). Compose AlertActions as a direct child of Alert.\n    actionsPlacement: {\n      below: '',\n      end: [\n        '@xl:grid @xl:grid-cols-[minmax(0,1fr)_auto] @xl:gap-x-4',\n        // The icon is absolutely positioned: keep it out of the column rule so it stays anchored to the padding box at every breakpoint.\n        '@xl:[&>*:not(svg)]:col-start-1',\n        '@xl:[&>[data-slot=alert-actions]]:col-start-2 @xl:[&>[data-slot=alert-actions]]:row-span-3 @xl:[&>[data-slot=alert-actions]]:row-start-1',\n        '@xl:[&>[data-slot=alert-actions]]:mt-0 @xl:[&>[data-slot=alert-actions]]:self-center @xl:[&>[data-slot=alert-actions]]:pl-0',\n      ],\n    },\n    // Product preset (DesignSystemProvider `preset`). `zero` uses 12/16 padding and the brand tint for the info tone in the compact (soft note) size.\n    preset: { default: '', zero: '' },\n  },\n  compoundVariants: [\n    { preset: 'zero', size: 'compact', className: 'px-4 py-3 [&>svg]:left-4 [&>svg]:top-3 [&>svg~*:not([data-slot=alert-close])]:pl-[calc(theme(spacing.icon-md)+theme(spacing.gap-control))]' },\n    { preset: 'zero', size: 'compact', variant: 'info', className: 'bg-background-brand-subtle' },\n    { variant: 'default', size: 'compact', className: 'bg-muted' },\n    { variant: 'decent', size: 'compact', className: 'bg-muted' },\n    { variant: 'info', size: 'compact', className: 'bg-background-info-subtle' },\n    { variant: 'success', size: 'compact', className: 'bg-background-success-subtle' },\n    { variant: 'warning', size: 'compact', className: 'bg-background-warning-subtle' },\n    { variant: 'destructive', size: 'compact', className: 'bg-background-destructive-subtle' },\n  ],\n  defaultVariants: {\n    variant: 'default',\n    size: 'default',\n    actionsPlacement: 'below',\n    preset: 'default',\n  },\n});\n\n// ALERT-A11Y-05: role/aria-live is derived from severity per the WAI-ARIA Alert authoring\n// pattern — assertive `role=\"alert\"` is reserved for time-sensitive, interrupting messages\n// (warning/destructive); non-urgent variants use the polite `role=\"status\"` so they don't forcibly\n// interrupt a screen-reader user's current reading. Explicit `role`/`aria-live` props passed by a\n// consumer still win (see the spread order in `Alert` below).\nconst alertRoleByVariant: Record<NonNullable<VariantProps<typeof alertVariants>['variant']>, 'alert' | 'status'> = {\n  default: 'status',\n  decent: 'status',\n  info: 'status',\n  success: 'status',\n  warning: 'alert',\n  destructive: 'alert',\n};\n\nconst Alert = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement> & Omit<VariantProps<typeof alertVariants>, 'preset'>\n>(({ className, variant, size, actionsPlacement, role, 'aria-live': ariaLive, ...props }, ref) => {\n  const zero = usePreset() === 'zero';\n  const resolvedVariant = variant ?? 'default';\n  const resolvedRole = role ?? alertRoleByVariant[resolvedVariant];\n  const resolvedAriaLive = ariaLive ?? (resolvedRole === 'alert' ? 'assertive' : 'polite');\n  const alert = (\n    <div\n      data-slot=\"alert\"\n      data-variant={resolvedVariant}\n      ref={ref}\n      role={resolvedRole}\n      aria-live={resolvedAriaLive}\n      data-actions-placement={actionsPlacement ?? undefined}\n      className={cn(alertVariants({ variant, size: size ?? (zero ? 'compact' : 'default'), actionsPlacement, preset: zero ? 'zero' : 'default' }), 'shadow-[shadow:var(--bevel-alert)]', className)}\n      {...props}\n    />\n  );\n  // A container query styles descendants only, so the alert gets a measuring frame when the actions move to the end.\n  return actionsPlacement === 'end' ? (\n    <div data-slot=\"alert-frame\" className=\"w-full min-w-0 @container\">\n      {alert}\n    </div>\n  ) : (\n    alert\n  );\n});\nAlert.displayName = 'Alert';\n\nexport interface AlertTitleProps extends React.HTMLAttributes<HTMLElement> {\n  // Optional heading element (h1-h6). Without it the title is a plain paragraph, so alerts do not add heading-order skips to the page.\n  titleAs?: HeadingTag;\n}\n\nconst AlertTitle = React.forwardRef<HTMLElement, AlertTitleProps>(({ className, titleAs, ...props }, ref) => {\n  const Title = (titleAs ?? 'p') as React.ElementType;\n  return (\n    // The rule can't see through the `{...props}` spread; every consumer passes visible title text as children (see stories/tests).\n    // eslint-disable-next-line jsx-a11y/heading-has-content\n    <Title ref={ref} className={cn('font-semibold leading-none', className)} {...props} />\n  );\n});\nAlertTitle.displayName = 'AlertTitle';\n\nconst AlertDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(\n  ({ className, ...props }, ref) => {\n    // Zero reads the description as the ui type role (13/20), the default theme keeps text-sm.\n    const zero = usePreset() === 'zero';\n    return <div ref={ref} className={cn(zero ? 'typography-ui' : 'text-sm', '[&_p]:leading-base mt-1', className)} {...props} />;\n  },\n);\nAlertDescription.displayName = 'AlertDescription';\n\n// Row of action buttons or links under the title and description. Composed manually like AlertTitle,\n// so it sits in the text column next to the icon (the `[&>svg~*]` indent of the Alert applies).\nconst AlertActions = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (\n  <div ref={ref} data-slot=\"alert-actions\" className={cn('mt-3 flex flex-wrap items-center gap-2', className)} {...props} />\n));\nAlertActions.displayName = 'AlertActions';\n\n// Optional dismiss affordance. Alert itself stays a passive live region (`role=\"alert\"` or\n// `role=\"status\"`, see `alertRoleByVariant` above) — it must never carry a focus ring or tabIndex\n// of its own (there is nothing to \"activate\" on the container; only a real nested control, like\n// this one, is keyboard-operable) — so hover /\n// focus-visible / active / disabled all live here, on a genuine `<button>`, rather than being\n// simulated on the container the way Chip has to (Chip's root is a `<div>`; this is a real button,\n// so the native `:hover` / `:focus-visible` / `:active` / `:disabled` pseudo-classes apply with no\n// aria-disabled workaround needed). Composed manually by the consumer, exactly like AlertTitle/\n// AlertDescription, so Alert's own props stay untouched — when composing it, add e.g.\n// `className=\"pr-8\"` to <Alert> to reserve room so title/description text doesn't run under it.\nconst AlertClose = React.forwardRef<HTMLButtonElement, React.ButtonHTMLAttributes<HTMLButtonElement>>(\n  ({ className, type = 'button', 'aria-label': ariaLabelProp, ...props }, ref) => {\n    const l = useLabels();\n    const ariaLabel = ariaLabelProp ?? l.dismiss;\n    return (\n    <button\n      ref={ref}\n      type={type}\n      data-slot=\"alert-close\"\n      aria-label={ariaLabel}\n      className={cn(\n        'absolute right-3 top-3 inline-flex h-6 w-6 items-center justify-center rounded-default text-text-subtle transition-colors',\n        'hover:bg-background-neutral-default hover:text-text-default',\n        'focus-visible:outline-none focus-visible:ring-focus focus-visible:ring-ring-default focus-visible:ring-offset-focus',\n        'active:bg-background-neutral-default active:brightness-95',\n        'disabled:pointer-events-none disabled:opacity-disabled',\n        className,\n      )}\n      {...props}\n    >\n      <X className=\"h-icon-md w-icon-md\" aria-hidden=\"true\" />\n    </button>\n  );\n  },\n);\nAlertClose.displayName = 'AlertClose';\n\nexport { Alert, AlertActions, AlertClose, AlertDescription, AlertTitle };\n\n// --- Deliberately NOT added (documented rather than silently omitted) ---\n//\n// `selected`: Alert has no resolvable Figma spec to confirm this against (see alert.figma.tsx —\n// the \"Alert\" component_set is indexed in the published \"🚧 GetITDone Basic\" library but is not\n// present on any current page of this file; same orphaned-publish issue already documented on\n// Chip). MD3's Alert/banner role has no native \"selected\" state (that is a Chip/List-item/Card\n// concept), and nothing in this codebase currently needs a selectable Alert. Fabricating one here\n// would violate the \"never fabricate a spec that isn't in Figma\" rule — flagged as NEEDS_DECISION\n// instead of guessed.\n//\n// `invalid`: unlike Chip (which has an independent `variant` axis that can be forced to the\n// error treatment regardless of what it was), Alert's severity variant IS its only axis, and\n// `variant=\"destructive\"` already means \"this is the invalid/error message\" (see the `Error`\n// story). A separate `invalid` boolean would just duplicate `variant=\"destructive\"` with no new\n// information — the kind of redundant, ambiguous API this maintainer role is meant to avoid.\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}