{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "data-tile",
  "title": "Data tile",
  "description": "The tile a figure or chart sits in: name, a control on the right, the figure, the fine print under a line; inverted to lift the one that matters most, compact for dense dashboards.",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/manniche/data-tile/data-tile.tsx",
      "content": "// The tile every data primitive sits in: a quiet card with a name, an optional control on the right, the figure,\n// the chart and a footer with the fine print. One tile per question. `inverted` turns one tile dark on a light page\n// (or light on a dark one) to mark the figure that matters most; every token inside it follows, charts included.\nimport { useId, type CSSProperties, type ReactNode } from 'react'\nimport { cn } from '@/lib/utils'\n\nexport type DataTileProps = {\n  /** The question the tile answers, as a short name: \"Revenue\", \"Sleep\". */\n  title?: ReactNode\n  /** A control set at the right of the title row, such as a period switch. */\n  action?: ReactNode\n  /** The fine print under a line: what is counted, from when, by whom. */\n  footer?: ReactNode\n  /** Swap the tile to the opposite theme, to lift the one figure that matters most on a screen. */\n  inverted?: boolean\n  /** \"compact\" tightens the padding and gaps for dense dashboards. Default \"comfortable\". */\n  density?: 'comfortable' | 'compact'\n  className?: string\n  children?: ReactNode\n}\n\n// Snapshots of the page's tokens, taken on the outer element so the inner one can swap them without a cycle.\nconst INVERT_OUTER = {\n  '--tile-bg': 'var(--foreground)',\n  '--tile-fg': 'var(--background)',\n  '--tile-primary': 'var(--primary)',\n  '--tile-success': 'var(--success)',\n  '--tile-destructive': 'var(--destructive)',\n  '--tile-chart-1': 'var(--chart-1)',\n  '--tile-chart-2': 'var(--chart-2)',\n  '--tile-chart-3': 'var(--chart-3)',\n  '--tile-chart-4': 'var(--chart-4)',\n  '--tile-chart-5': 'var(--chart-5)',\n} as CSSProperties\n\n// Colours are pulled a little towards the tile's text colour, so they keep their contrast on the swapped surface.\nconst toward = (name: string, amount: number) => `color-mix(in oklab, var(--tile-${name}) ${amount}%, var(--tile-fg))`\nconst INVERT_INNER = {\n  '--card': 'var(--tile-bg)',\n  '--card-foreground': 'var(--tile-fg)',\n  '--foreground': 'var(--tile-fg)',\n  '--background': 'var(--tile-bg)',\n  '--muted': 'color-mix(in oklab, var(--tile-fg) 10%, var(--tile-bg))',\n  '--muted-foreground': 'color-mix(in oklab, var(--tile-fg) 64%, var(--tile-bg))',\n  '--border': 'color-mix(in oklab, var(--tile-fg) 15%, var(--tile-bg))',\n  '--primary': toward('primary', 80),\n  '--ring': toward('primary', 80),\n  '--success': toward('success', 80),\n  '--destructive': toward('destructive', 80),\n  '--chart-1': toward('chart-1', 85),\n  '--chart-2': toward('chart-2', 85),\n  '--chart-3': toward('chart-3', 85),\n  '--chart-4': toward('chart-4', 85),\n  '--chart-5': toward('chart-5', 85),\n} as CSSProperties\n\nconst DENSITY = {\n  comfortable: { tile: 'px-6 pt-[18px]', body: 'mt-[22px]', foot: 'mt-[22px] pt-3.5 pb-[18px]', end: 'pb-6' },\n  compact: { tile: 'px-[18px] pt-3', body: 'mt-3.5', foot: 'mt-3.5 pt-2.5 pb-3.5', end: 'pb-[18px]' },\n}\n\nexport function DataTile({ title, action, footer, inverted = false, density = 'comfortable', className, children }: DataTileProps) {\n  const titleId = useId()\n  const d = DENSITY[density]\n  return (\n    <section\n      aria-labelledby={title ? titleId : undefined}\n      data-density={density}\n      data-inverted={inverted || undefined}\n      className={cn(\n        'group/tile relative flex min-w-0 flex-col rounded-[calc(var(--radius)*2+2px)]',\n        inverted\n          ? 'bg-foreground text-background'\n          : 'bg-card text-card-foreground shadow-[0_0_0_1px_color-mix(in_oklab,var(--foreground)_9%,transparent),0_1px_2px_rgba(0,0,0,0.03)]',\n        className,\n      )}\n      style={inverted ? INVERT_OUTER : undefined}\n    >\n      <div\n        className={cn('flex min-w-0 flex-1 flex-col', d.tile, !footer && d.end)}\n        style={inverted ? INVERT_INNER : undefined}\n      >\n        {(title || action) && (\n          <div className=\"flex min-h-11 flex-wrap items-center gap-3\">\n            {title && (\n              <h3 id={titleId} className=\"text-[15px] leading-[1.2] font-medium tracking-[-0.01em]\">\n                {title}\n              </h3>\n            )}\n            {action && <div className=\"ml-auto flex flex-wrap items-center gap-2\">{action}</div>}\n          </div>\n        )}\n        <div className={cn('relative min-w-0 flex-1', (title || action) && d.body)}>{children}</div>\n        {footer && (\n          <div\n            className={cn(\n              'flex flex-wrap items-baseline gap-x-4 gap-y-1.5 border-t border-border text-[13.5px] leading-normal text-pretty text-muted-foreground',\n              d.foot,\n            )}\n          >\n            {footer}\n          </div>\n        )}\n      </div>\n    </section>\n  )\n}\n\n/** The figure block at the top of a tile: a small label over the big number, with a change pill or note beside it. */\nexport function TileFact({ label, children, aside }: { label?: ReactNode; children: ReactNode; aside?: ReactNode }) {\n  return (\n    <div className=\"grid gap-1.5\">\n      {label && <span className=\"text-sm leading-[1.3] font-medium text-muted-foreground\">{label}</span>}\n      <div className=\"flex flex-wrap items-end gap-x-4 gap-y-3\">\n        {children}\n        {aside && <span className=\"mb-[0.35em] inline-flex flex-wrap items-center gap-2\">{aside}</span>}\n      </div>\n    </div>\n  )\n}\n\n/** A key for charts where two or more series share a plot. Never needed for one series. */\nexport function TileLegend({ items, className }: { items: { label: string; color: string; shape?: 'square' | 'dot' | 'line' | 'dashed' }[]; className?: string }) {\n  return (\n    <ul className={cn('flex flex-wrap gap-x-4 gap-y-1.5 text-[13px] text-muted-foreground', className)}>\n      {items.map((it) => (\n        <li key={it.label} className=\"inline-flex items-center gap-[7px]\">\n          <i\n            aria-hidden\n            className={cn(\n              'flex-none',\n              it.shape === 'line' || it.shape === 'dashed' ? 'h-0 w-4 border-t-2' : 'size-2.5',\n              it.shape === 'dashed' && 'border-dashed',\n              it.shape === 'dot' ? 'rounded-full' : it.shape === 'square' || !it.shape ? 'rounded-[3px]' : '',\n            )}\n            style={it.shape === 'line' || it.shape === 'dashed' ? { borderColor: it.color } : { background: it.color }}\n          />\n          {it.label}\n        </li>\n      ))}\n    </ul>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "categories": [
    "surfaces"
  ],
  "type": "registry:component"
}