{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "card-default",
  "title": "Card Default",
  "description": "Card Default from the Get IT Done design system.",
  "dependencies": [
    "clsx@^2.0.0"
  ],
  "registryDependencies": [
    "https://zero-design.itdone.de/r/labels-context.json",
    "https://zero-design.itdone.de/r/theme.json"
  ],
  "files": [
    {
      "path": "registry/ui/card/Card.tsx",
      "content": "'use client';\n\nimport { clsx } from 'clsx';\nimport { forwardRef, KeyboardEvent, MouseEvent } from 'react';\nimport { CardProps } from './card.interface';\nimport { useLabels, usePreset } from '@/components/ui/labels-context';\n\n// NOTE (MAT-CARD-01): this `CardDefault` (single `children`/`image` prop API, product-tile shaped)\n// and the composable `Card`/`CardHeader`/`CardTitle`/`CardDescription`/`CardContent`/`CardFooter`\n// slot API in `../card.tsx` are two intentionally distinct primitives — not accidental duplicates.\n// They are kept in sync on the things that must never diverge for two same-named-in-spirit\n// surfaces: shape token (`rounded-card`, not `rounded-default`) and the full disabled/invalid/\n// selected state set (see below). They intentionally keep separate anatomy (single free-form\n// `children` slot + optional `image` here vs. composed header/title/description/content/footer\n// slots there) and separate elevation defaults: this Card's `shadow` prop is not a bare elevation\n// toggle — it switches to an entirely different centered/featured layout (`shadow` + `!border-none`\n// + centered text + different padding scale), so unconditionally defaulting it to `shadow-sm` (to\n// match the composable Card's fixed default) would silently change the rendered layout of every\n// existing `shadow`-less consumer. That specific default-elevation alignment is left as a\n// NEEDS_DECISION for design/product rather than folded into this state-parity fix.\nexport const Card = forwardRef<HTMLDivElement, CardProps>(\n  (\n    {\n      className,\n      transparent = false,\n      children,\n      image,\n      isFluid = false,\n      noBorder = false,\n      shadow = false,\n      style,\n      onClick,\n      selected,\n      disabled,\n      invalid,\n      'aria-label': ariaLabel,\n      ...props\n    },\n    ref,\n  ) => {\n    // Interactivity is derived from the presence of a real `onClick` handler (mirrors the\n    // composable Card in `card.tsx`) rather than a separate boolean prop, so a static-content\n    // Card (Default/NoBorder/Transparent/ProductCard, …) never becomes an empty, unlabeled Tab\n    // stop with a suppressed focus outline — it only gains role=\"button\", a Tab stop, keyboard\n    // activation, and a visible focus ring once the consumer actually wires up `onClick`. Disabled\n    // always wins over an `onClick` (mirrors the composable Card's `interactive` computation).\n    const l = useLabels();\n    const zero = usePreset() === 'zero';\n    const interactive = Boolean(onClick) && !disabled;\n\n    const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n      if (!interactive || event.defaultPrevented) return;\n      if (event.key === 'Enter' || event.key === ' ') {\n        event.preventDefault();\n        onClick?.(event as unknown as MouseEvent<HTMLDivElement>);\n      }\n    };\n\n    return (\n      <div\n        className={clsx(\n          'card',\n          'w-full',\n          'h-full',\n          'flex',\n          'flex-col',\n          'flex-nowrap',\n          'items-stretch',\n          // MAT-CARD-02: dedicated `rounded-card` token (borderRadius/card, 8px), not the generic\n          // `rounded-default` (borderRadius/default, 6px) — matches the composable Card in\n          // `card.tsx`, which correctly uses `rounded-card` for the same semantic component.\n          'rounded-surface',\n          // Transparent cards sit on any surface, so they inherit its text colour.\n          transparent ? 'text-inherit' : 'text-foreground',\n          '[&>img]:rounded-tl-surface',\n          '[&>img]:rounded-tr-surface',\n          '[&_p:last-child]:mb-0',\n          { 'max-sm:p-0': noBorder || transparent },\n          // Zero: one fixed 20px card padding instead of the responsive 16/24/32 scale.\n          { 'p-5': zero && !image && !transparent },\n          { 'p-md md:p-lg lg:p-xl': !zero && !image && !transparent },\n          { 'shadow-elevation-surface border-border-default data-[selected=true]:border-border-brand data-[selected=true]:bg-background data-[selected=true]:ring-[3px] data-[selected=true]:ring-background-brand-subtle': zero && !transparent && !noBorder },\n          { 'items-center gap-3 !border-none p-3 md:p-6 lg:p-10 text-center shadow': shadow },\n          // CARD-A11Y-01 TOKEN GAP: `border-form-border-default` against `bg-background` computes\n          // to ~1.23:1 in light mode (needs ≥3:1 for a UI-component boundary, WCAG 1.4.11) — the\n          // resting Card border is effectively edge-less on a white page. Token-value defect (needs\n          // a darker `form-border-default` or a dedicated `card-border-default` token from\n          // design-tokens-maintainer), not a usage defect — this file already consumes the intended\n          // semantic border token rather than hardcoding a color.\n          { 'border border-solid border-border': !zero && !transparent && !noBorder },\n          { 'border border-solid': zero && !transparent && !noBorder },\n          // CARD-TOK-01 / CARD-A11Y-02 TOKEN GAP: plain `bg-background` (and the `card-*`/`primary`/\n          // `ring-default`/`error` tokens used below) are declared once at `:root` with no `.dark`\n          // override in kette-tokens `dist/theme.css` — this Card renders the identical\n          // light-mode surface in dark mode. Same root cause already tracked on the composable Card\n          // (see card.tsx); needs dark-mode-tuned values from design-tokens-maintainer, nothing to\n          // change here once they ship.\n          { 'bg-background': !transparent },\n          // MAT-CARD-03 fix: the clsx object key IS the class name gated on the value's truthiness —\n          // `{ isFluid: '@container' }` had this backwards (the key was the prop name, a permanently\n          // truthy string, so it always emitted the literal class \"isFluid\" and never \"@container\").\n          { '@container': isFluid },\n          {\n            'cursor-pointer hover:bg-background-neutral-default focus-visible:outline-none focus-visible:ring-focus focus-visible:ring-offset-focus focus-visible:ring-offset-background focus-visible:ring-ring-default focus-visible:border-ring active:bg-background-neutral-default active:brightness-95 aria-[invalid=true]:focus-visible:ring-error':\n              interactive,\n          },\n          // Disabled — Card renders a <div>, not a native form control, so (mirrors the composable\n          // Card/Chip/AlertClose) the dimmed/inert look is driven by `aria-disabled`/`data-disabled`\n          // rather than the (inapplicable) `disabled:` pseudo-class. Independent of `interactive`: a\n          // purely informational Card can still be shown as \"unavailable\" (MAT-CARD-04).\n          //\n          // CARD-A11Y-03 fix: `opacity-disabled` is not a defined Tailwind utility anywhere in this\n          // design system (no `disabled` key in the opacity scale of kette-tokens' preset,\n          // built theme, or this repo's tailwind.config.cjs), so it silently compiled to no CSS rule.\n          // Using the real `opacity-50` utility (same interim fallback used in card.tsx/Tabs/\n          // NavigationMenu) until design-tokens-maintainer ships a proper `opacity.disabled` scale.\n          'aria-disabled:pointer-events-none aria-disabled:opacity-50',\n          'data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50',\n          // Invalid — e.g. a form-section Card reporting a validation error. Mirrors the composable\n          // Card's `aria-[invalid=true]:border-error` so the boundary-only (no background refill)\n          // treatment stays one convention across form-adjacent containers (MAT-CARD-04).\n          { 'aria-[invalid=true]:border-error': !zero && !transparent && !noBorder },\n          // Zero: the destructive border role instead of the raw error red.\n          { 'aria-[invalid=true]:border-destructive': zero && !transparent && !noBorder },\n          // Selected — a persistent \"this is the chosen one\" identity for a card among alternatives\n          // (e.g. a plan/tile picker), independent of `interactive`. Mirrors the composable Card's\n          // `data-[selected=true]:border-primary` / `bg-background-accent` (MAT-CARD-04).\n          { 'data-[selected=true]:border-primary': !zero && !transparent && !noBorder },\n          { 'data-[selected=true]:bg-background-accent': !zero },\n          className,\n        )}\n        style={style}\n        role={interactive ? 'button' : undefined}\n        tabIndex={interactive ? 0 : undefined}\n        aria-pressed={interactive && selected != null ? Boolean(selected) : undefined}\n        aria-disabled={disabled || undefined}\n        aria-invalid={invalid || undefined}\n        aria-label={ariaLabel}\n        data-disabled={disabled || undefined}\n        data-invalid={invalid || undefined}\n        data-selected={selected || undefined}\n        onClick={onClick}\n        onKeyDown={handleKeyDown}\n        data-testid=\"card\"\n        {...props}\n        ref={ref}\n      >\n        {image}\n        {image ? (\n          <div\n            className={clsx(\n              'flex',\n              'h-full',\n              'flex-col',\n              'flex-nowrap',\n              'items-start',\n              { 'p-md': !transparent },\n              { 'md:p-lg': !transparent },\n              { 'lg:p-xl': !transparent },\n            )}\n          >\n            {children}\n          </div>\n        ) : (\n          children\n        )}\n        {selected && !interactive && <span className=\"sr-only\">{l.selectedSuffix}</span>}\n        {invalid && <span className=\"sr-only\">{l.invalidSuffix}</span>}\n      </div>\n    );\n  },\n);\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/ui/card/card.interface.ts",
      "content": "import { MouseEventHandler, ReactNode } from 'react';\n\nexport interface CardProps {\n  image?: ReactNode;\n  children: ReactNode;\n  isFluid?: boolean;\n  transparent?: boolean;\n  noBorder?: boolean;\n  shadow?: boolean;\n  className?: string;\n  style?: React.CSSProperties;\n  /**\n   * A real click handler is what turns this Card into a keyboard-operable \"clickable card\" —\n   * mirrors the `interactive` computation in the composable `card.tsx` Card. Omit it for a\n   * purely informational Card, which then renders with no role/tabIndex/focus ring and never\n   * enters the Tab order.\n   */\n  onClick?: MouseEventHandler<HTMLDivElement>;\n  /**\n   * Marks this card as the chosen option among a set of alternatives (e.g. a plan/tile picker).\n   * Independent of `onClick` — a purely informational Card can still indicate \"this is the current\n   * one.\" Sets `data-selected` always, and `aria-pressed` only while interactive (a plain `<div>`\n   * has no ARIA widget role to carry `aria-selected`/`aria-pressed` otherwise). On a non-interactive\n   * selected Card, an sr-only \" (selected)\" suffix carries the state to assistive tech instead.\n   * Mirrors the composable `Card` in `card.tsx` (MAT-CARD-04).\n   */\n  selected?: boolean;\n  /**\n   * Dims the card and blocks pointer interaction/keyboard activation. Mirrors the composable\n   * `Card`'s `aria-disabled`/`data-disabled` approach rather than the HTML `disabled` attribute,\n   * since Card is not a native form control (MAT-CARD-04).\n   */\n  disabled?: boolean;\n  /**\n   * Forces the `error` border/focus-ring treatment and sets `aria-invalid` — e.g. a form-section\n   * Card reporting a validation error. An sr-only \" (invalid)\" suffix mirrors the composable Card's\n   * convention so the state isn't color-only (MAT-CARD-04).\n   */\n  invalid?: boolean;\n  /**\n   * CARD-A11Y-04: for an interactive Card (`onClick` set) whose children are rich nested content,\n   * `role=\"button\"`'s accessible name otherwise defaults to the concatenation of ALL descendant\n   * text/alt (e.g. \"Category I am a h3 Description button\" instead of a concise \"Title\"). Pass a\n   * short, meaningful label so `screen.getByRole('button', { name: '<title>' })` resolves.\n   */\n  'aria-label'?: string;\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/ui/card/index.ts",
      "content": "export { Card } from './Card';\nexport type { CardProps } from './card.interface';\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}