{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "glance",
  "title": "Glance",
  "author": "@fma-ds/ds",
  "description": "A quick piece of information that opens more context one click away — an account notice, a balance, a count of pending items.\n\n`Glance` wraps react-aria's `DialogTrigger` and shares a `tone` (`default`, `info`, `success`, `warning`, `destructive`) with its parts.\n\n- **`GlanceTrigger`**: the compact button — an icon plus `GlanceText` (a `GlanceLabel` caption over a `GlanceValue`), or icon-only with `size=\"icon\"` and an `aria-label`.\n- **`GlanceIndicator`**: pins a dot or count to the trigger's corner.\n- **`GlanceContent`**: a popover holding a dialog (focus moves in, Escape closes, focus returns), laid out with `GlanceHeader`, `GlanceMedia` (an `IconTile` in the tone), `GlanceTitle`, `GlanceDescription` and `GlanceFooter`. Its children can be a function of `{ close }`, to close it from an action.\n\nTone tints the trigger like `Alert`, with no motion — urgency comes from tone and wording.",
  "dependencies": [
    "class-variance-authority@^0.7.1",
    "cn@^0.5.0",
    "react-aria-components@^1.22.1"
  ],
  "registryDependencies": [
    "@fma-ds/icon-tile",
    "@fma-ds/popover"
  ],
  "files": [
    {
      "path": "src/core/glance/glance.tsx",
      "content": "import { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"cn\";\nimport * as React from \"react\";\nimport {\n  Button as ButtonPrimitive,\n  type ButtonProps as ButtonPrimitiveProps,\n  Dialog,\n  type DialogProps,\n  type DialogTriggerProps,\n  Heading,\n} from \"react-aria-components\";\nimport { IconTile } from \"@/core/icon-tile/icon-tile\";\nimport { Popover, PopoverTrigger } from \"@/core/popover/popover\";\n\ntype GlanceTone = \"default\" | \"info\" | \"success\" | \"warning\" | \"destructive\";\n\nconst GlanceContext = React.createContext<{ tone: GlanceTone }>({ tone: \"default\" });\n\nconst glanceTriggerVariants = cva(\n  \"group/glance-trigger relative inline-flex shrink-0 items-center gap-2 rounded-lg border text-left text-sm whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-5\",\n  {\n    variants: {\n      tone: {\n        default:\n          \"border-border bg-background hover:bg-accent hover:text-accent-foreground aria-expanded:bg-accent aria-expanded:text-accent-foreground dark:border-input dark:bg-input/30 [&>svg]:text-muted-foreground\",\n        info: \"border-info/30 bg-info/4 hover:bg-info/10 aria-expanded:bg-info/10 dark:bg-info/15 dark:hover:bg-info/25 dark:aria-expanded:bg-info/25 [&>svg]:text-info\",\n        success:\n          \"border-success/30 bg-success/4 hover:bg-success/10 aria-expanded:bg-success/10 dark:bg-success/15 dark:hover:bg-success/25 dark:aria-expanded:bg-success/25 [&>svg]:text-success\",\n        warning:\n          \"border-warning/30 bg-warning/4 hover:bg-warning/10 aria-expanded:bg-warning/10 dark:bg-warning/15 dark:hover:bg-warning/25 dark:aria-expanded:bg-warning/25 [&>svg]:text-warning\",\n        destructive:\n          \"border-destructive/30 bg-destructive/4 hover:bg-destructive/10 aria-expanded:bg-destructive/10 dark:bg-destructive/15 dark:hover:bg-destructive/25 dark:aria-expanded:bg-destructive/25 [&>svg]:text-destructive\",\n      },\n      size: {\n        default: \"h-10 px-3\",\n        sm: \"h-8 px-2.5 [&_[data-slot=glance-label]]:sr-only\",\n        icon: \"size-10 justify-center\",\n        \"icon-sm\": \"size-8 justify-center [&_svg:not([class*='size-'])]:size-4\",\n      },\n    },\n    defaultVariants: {\n      tone: \"default\",\n      size: \"default\",\n    },\n  },\n);\n\nconst toneTextClass: Record<GlanceTone, string> = {\n  default: \"text-muted-foreground\",\n  info: \"text-info\",\n  success: \"text-success\",\n  warning: \"text-warning\",\n  destructive: \"text-destructive\",\n};\n\n/**\n * Root: wires the trigger to the panel and shares the `tone` with both.\n * Wraps react-aria's `DialogTrigger`, so `isOpen`/`onOpenChange` control it.\n */\nfunction Glance({\n  tone = \"default\",\n  ...props\n}: DialogTriggerProps & {\n  tone?: GlanceTone;\n}) {\n  const value = React.useMemo(() => ({ tone }), [tone]);\n  return (\n    <GlanceContext.Provider value={value}>\n      <PopoverTrigger {...props} />\n    </GlanceContext.Provider>\n  );\n}\n\nfunction GlanceTrigger({\n  className,\n  size = \"default\",\n  ...props\n}: Omit<ButtonPrimitiveProps, \"className\"> &\n  Pick<VariantProps<typeof glanceTriggerVariants>, \"size\"> & {\n    className?: string;\n  }) {\n  const { tone } = React.useContext(GlanceContext);\n  return (\n    <ButtonPrimitive\n      data-slot=\"glance-trigger\"\n      data-tone={tone}\n      data-size={size}\n      className={cn(glanceTriggerVariants({ tone, size }), className)}\n      {...props}\n    />\n  );\n}\n\n/** Stacks a `GlanceLabel` over a `GlanceValue` inside the trigger. */\nfunction GlanceText({ className, ...props }: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"glance-text\"\n      className={cn(\"flex min-w-0 flex-col items-start leading-tight\", className)}\n      {...props}\n    />\n  );\n}\n\n/** The small caption on top: what the value is about. Visually hidden at `size=\"sm\"`. */\nfunction GlanceLabel({ className, ...props }: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"glance-label\"\n      className={cn(\"text-xs text-muted-foreground\", className)}\n      {...props}\n    />\n  );\n}\n\n/** The quick information itself. */\nfunction GlanceValue({ className, ...props }: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"glance-value\"\n      className={cn(\"max-w-[20ch] truncate font-medium\", className)}\n      {...props}\n    />\n  );\n}\n\n/**\n * A dot (no children) or a count pinned to the trigger's corner. Decorative:\n * put the meaning in the trigger's accessible name (e.g. `aria-label`).\n */\nfunction GlanceIndicator({ className, children, ...props }: React.ComponentProps<\"span\">) {\n  const hasContent = children !== undefined && children !== null && children !== false;\n  return (\n    <span\n      data-slot=\"glance-indicator\"\n      aria-hidden=\"true\"\n      className={cn(\n        \"pointer-events-none absolute -top-1 -right-1 flex items-center justify-center rounded-full bg-primary font-medium text-primary-foreground ring-2 ring-background\",\n        hasContent ? \"h-4 min-w-4 px-1 text-[0.625rem] leading-none tabular-nums\" : \"size-2.5\",\n        className,\n      )}\n      {...props}\n    >\n      {children}\n    </span>\n  );\n}\n\n/**\n * The panel opened by the trigger: a popover holding a dialog, so focus moves\n * in, Escape closes it and focus returns to the trigger. Children can be a\n * function of `{ close }` to close it from an action.\n */\nfunction GlanceContent({\n  className,\n  placement = \"bottom end\",\n  children,\n  \"aria-label\": ariaLabel,\n  ...props\n}: Omit<React.ComponentProps<typeof Popover>, \"children\"> & {\n  children?: DialogProps[\"children\"];\n  \"aria-label\"?: string;\n}) {\n  const { tone } = React.useContext(GlanceContext);\n  return (\n    <Popover\n      data-slot=\"glance-content\"\n      data-tone={tone}\n      placement={placement}\n      className={cn(\"w-80 p-0\", className)}\n      {...props}\n    >\n      <Dialog aria-label={ariaLabel} className=\"flex min-h-0 flex-col gap-3 p-3 outline-none\">\n        {children}\n      </Dialog>\n    </Popover>\n  );\n}\n\nfunction GlanceHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"glance-header\"\n      className={cn(\"flex items-start gap-3 [&>[data-slot=glance-media]+div]:flex-1\", className)}\n      {...props}\n    />\n  );\n}\n\n/** An icon in a tile tinted with the root's `tone`. */\nfunction GlanceMedia({ className, ...props }: React.ComponentProps<typeof IconTile>) {\n  const { tone } = React.useContext(GlanceContext);\n  return (\n    <IconTile\n      data-slot=\"glance-media\"\n      variant=\"soft\"\n      aria-hidden=\"true\"\n      className={cn(toneTextClass[tone], className)}\n      {...props}\n    />\n  );\n}\n\n/** The panel's heading; names the dialog for assistive tech. */\nfunction GlanceTitle({ className, ...props }: React.ComponentProps<typeof Heading>) {\n  return (\n    <Heading\n      slot=\"title\"\n      data-slot=\"glance-title\"\n      className={cn(\"text-base leading-snug font-medium\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction GlanceDescription({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"glance-description\"\n      className={cn(\"text-sm text-muted-foreground [&_p:not(:last-child)]:mb-2\", className)}\n      {...props}\n    />\n  );\n}\n\n/** Actions, set apart at the bottom of the panel. */\nfunction GlanceFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"glance-footer\"\n      className={cn(\n        \"-mx-3 -mb-3 flex items-center justify-end gap-2 border-t p-3 *:[[data-slot=button]]:flex-1\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport type { GlanceTone };\nexport {\n  Glance,\n  GlanceContent,\n  GlanceDescription,\n  GlanceFooter,\n  GlanceHeader,\n  GlanceIndicator,\n  GlanceLabel,\n  GlanceMedia,\n  GlanceText,\n  GlanceTitle,\n  GlanceTrigger,\n  GlanceValue,\n  glanceTriggerVariants,\n};\n",
      "type": "registry:component",
      "target": "components/fma-ds/glance.tsx"
    }
  ],
  "meta": {
    "status": "experimental",
    "source": "original",
    "license": "MIT",
    "tags": [
      "overlay",
      "popover",
      "notice",
      "status",
      "header"
    ],
    "contentHash": "24871faccb10"
  },
  "categories": [
    "overlay"
  ],
  "type": "registry:ui"
}