@seekalfred/primitives 0.7.1 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/README.md +255 -5
  2. package/dist/alert/alert.d.ts +49 -4
  3. package/dist/alert/alert.js +66 -38
  4. package/dist/alert/alert.js.map +1 -1
  5. package/dist/alert/index.d.ts +2 -2
  6. package/dist/alert/index.js +2 -2
  7. package/dist/alert-dialog/alert-dialog.d.ts +31 -4
  8. package/dist/alert-dialog/alert-dialog.js +75 -26
  9. package/dist/alert-dialog/alert-dialog.js.map +1 -1
  10. package/dist/announcement-bar/announcement-bar.d.ts +15 -4
  11. package/dist/announcement-bar/announcement-bar.js +31 -6
  12. package/dist/announcement-bar/announcement-bar.js.map +1 -1
  13. package/dist/avatar/avatar.d.ts +7 -0
  14. package/dist/avatar/avatar.js +33 -12
  15. package/dist/avatar/avatar.js.map +1 -1
  16. package/dist/badge/badge.d.ts +1 -1
  17. package/dist/bar-chart/bar-chart-series.d.ts +75 -0
  18. package/dist/bar-chart/bar-chart-series.js +401 -0
  19. package/dist/bar-chart/bar-chart-series.js.map +1 -0
  20. package/dist/bar-chart/bar-chart.d.ts +25 -1
  21. package/dist/bar-chart/bar-chart.js +26 -1
  22. package/dist/bar-chart/bar-chart.js.map +1 -1
  23. package/dist/bar-chart/index.d.ts +2 -1
  24. package/dist/button/button.d.ts +1 -1
  25. package/dist/calendar/calendar.d.ts +1 -1
  26. package/dist/card/card.d.ts +22 -1
  27. package/dist/card/card.js +60 -7
  28. package/dist/card/card.js.map +1 -1
  29. package/dist/carousel/carousel.d.ts +21 -0
  30. package/dist/carousel/carousel.js +34 -8
  31. package/dist/carousel/carousel.js.map +1 -1
  32. package/dist/chip-group/chip-group.d.ts +11 -0
  33. package/dist/chip-group/chip-group.js +50 -34
  34. package/dist/chip-group/chip-group.js.map +1 -1
  35. package/dist/chip-group/index.d.ts +2 -2
  36. package/dist/copy-button/copy-button.d.ts +37 -5
  37. package/dist/copy-button/copy-button.js +35 -11
  38. package/dist/copy-button/copy-button.js.map +1 -1
  39. package/dist/donut-chart/donut-chart.d.ts +22 -0
  40. package/dist/donut-chart/donut-chart.js +35 -18
  41. package/dist/donut-chart/donut-chart.js.map +1 -1
  42. package/dist/donut-chart/index.d.ts +2 -2
  43. package/dist/empty-state/empty-state.d.ts +21 -0
  44. package/dist/empty-state/empty-state.js +16 -4
  45. package/dist/empty-state/empty-state.js.map +1 -1
  46. package/dist/empty-state/index.d.ts +2 -2
  47. package/dist/empty-state/index.js +2 -2
  48. package/dist/file-drop-zone/file-drop-zone.d.ts +29 -3
  49. package/dist/file-drop-zone/file-drop-zone.js +26 -8
  50. package/dist/file-drop-zone/file-drop-zone.js.map +1 -1
  51. package/dist/filter-bar/filter-bar.d.ts +4 -1
  52. package/dist/filter-bar/filter-bar.js +53 -9
  53. package/dist/filter-bar/filter-bar.js.map +1 -1
  54. package/dist/gauge/gauge.d.ts +18 -1
  55. package/dist/gauge/gauge.js +35 -21
  56. package/dist/gauge/gauge.js.map +1 -1
  57. package/dist/gauge/index.d.ts +2 -2
  58. package/dist/index.d.ts +15 -14
  59. package/dist/index.js +9 -9
  60. package/dist/input/input.d.ts +7 -0
  61. package/dist/input/input.js +7 -5
  62. package/dist/input/input.js.map +1 -1
  63. package/dist/lib/field-shell.js +6 -3
  64. package/dist/lib/field-shell.js.map +1 -1
  65. package/dist/lib/field-surface.js +22 -1
  66. package/dist/lib/field-surface.js.map +1 -1
  67. package/dist/lib/required-marker.js +20 -0
  68. package/dist/lib/required-marker.js.map +1 -0
  69. package/dist/lib/use-copy-feedback.js +17 -3
  70. package/dist/lib/use-copy-feedback.js.map +1 -1
  71. package/dist/line-chart/index.d.ts +2 -2
  72. package/dist/line-chart/line-chart.d.ts +144 -2
  73. package/dist/line-chart/line-chart.js +314 -46
  74. package/dist/line-chart/line-chart.js.map +1 -1
  75. package/dist/money-input/index.d.ts +2 -2
  76. package/dist/money-input/money-input.d.ts +36 -1
  77. package/dist/money-input/money-input.js +30 -14
  78. package/dist/money-input/money-input.js.map +1 -1
  79. package/dist/morph-select/morph-select.d.ts +26 -1
  80. package/dist/morph-select/morph-select.js +28 -6
  81. package/dist/morph-select/morph-select.js.map +1 -1
  82. package/dist/multi-select/index.d.ts +2 -2
  83. package/dist/multi-select/index.js +2 -2
  84. package/dist/multi-select/multi-select.d.ts +74 -2
  85. package/dist/multi-select/multi-select.js +213 -39
  86. package/dist/multi-select/multi-select.js.map +1 -1
  87. package/dist/password-field/password-field.d.ts +16 -0
  88. package/dist/password-field/password-field.js +37 -8
  89. package/dist/password-field/password-field.js.map +1 -1
  90. package/dist/popover/index.d.ts +2 -2
  91. package/dist/popover/index.js +2 -2
  92. package/dist/popover/popover.d.ts +39 -2
  93. package/dist/popover/popover.js +29 -2
  94. package/dist/popover/popover.js.map +1 -1
  95. package/dist/progress/index.d.ts +2 -2
  96. package/dist/progress/index.js +2 -2
  97. package/dist/progress/progress.d.ts +68 -3
  98. package/dist/progress/progress.js +156 -39
  99. package/dist/progress/progress.js.map +1 -1
  100. package/dist/radio-group/index.d.ts +2 -2
  101. package/dist/radio-group/index.js +2 -2
  102. package/dist/radio-group/radio-group.d.ts +33 -3
  103. package/dist/radio-group/radio-group.js +109 -9
  104. package/dist/radio-group/radio-group.js.map +1 -1
  105. package/dist/scroll-area/scroll-area.js +4 -3
  106. package/dist/scroll-area/scroll-area.js.map +1 -1
  107. package/dist/scrub-input/scrub-input.d.ts +11 -1
  108. package/dist/scrub-input/scrub-input.js +22 -10
  109. package/dist/scrub-input/scrub-input.js.map +1 -1
  110. package/dist/search-field/search-field.d.ts +22 -1
  111. package/dist/search-field/search-field.js +22 -7
  112. package/dist/search-field/search-field.js.map +1 -1
  113. package/dist/select/select.d.ts +10 -1
  114. package/dist/select/select.js +11 -8
  115. package/dist/select/select.js.map +1 -1
  116. package/dist/separator/separator.d.ts +21 -9
  117. package/dist/separator/separator.js +33 -11
  118. package/dist/separator/separator.js.map +1 -1
  119. package/dist/sparkline/sparkline.d.ts +27 -2
  120. package/dist/sparkline/sparkline.js +115 -18
  121. package/dist/sparkline/sparkline.js.map +1 -1
  122. package/dist/stat-card/index.d.ts +2 -2
  123. package/dist/stat-card/index.js +2 -2
  124. package/dist/stat-card/stat-card.d.ts +65 -1
  125. package/dist/stat-card/stat-card.js +98 -25
  126. package/dist/stat-card/stat-card.js.map +1 -1
  127. package/dist/styles.css +1 -1
  128. package/dist/table/index.d.ts +2 -2
  129. package/dist/table/index.js +2 -2
  130. package/dist/table/table.d.ts +115 -5
  131. package/dist/table/table.js +374 -94
  132. package/dist/table/table.js.map +1 -1
  133. package/dist/text-shimmer/text-shimmer.d.ts +18 -2
  134. package/dist/text-shimmer/text-shimmer.js +5 -2
  135. package/dist/text-shimmer/text-shimmer.js.map +1 -1
  136. package/dist/textarea/textarea.d.ts +7 -0
  137. package/dist/textarea/textarea.js +3 -1
  138. package/dist/textarea/textarea.js.map +1 -1
  139. package/dist/time-picker/time-picker.d.ts +36 -1
  140. package/dist/time-picker/time-picker.js +33 -5
  141. package/dist/time-picker/time-picker.js.map +1 -1
  142. package/dist/toast/toast.d.ts +2 -2
  143. package/dist/usage-meter/index.d.ts +2 -2
  144. package/dist/usage-meter/usage-meter.d.ts +30 -0
  145. package/dist/usage-meter/usage-meter.js +56 -38
  146. package/dist/usage-meter/usage-meter.js.map +1 -1
  147. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"alert.js","names":["AriaButton"],"sources":["../../src/alert/alert.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport {\n forwardRef,\n isValidElement,\n useCallback,\n useEffect,\n useRef,\n useState,\n type ComponentPropsWithoutRef,\n type ReactNode,\n} from \"react\";\n\nimport { Button as AriaButton } from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { useHeightFollow } from \"../lib/height-follow.js\";\nimport { prefersReducedMotion, useExitBackstop, useIsomorphicLayoutEffect } from \"../lib/motion.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\n\n/**\n * Alert, ported from the legacy shadcn component.\n *\n * There is no React Aria base: RAC ships no static inline alert (its Toast\n * API is transient and queue-driven), so this is a styled div with\n * role=\"alert\". Because it is not RAC-backed, its props follow\n * ARCHITECTURE section 7 rule 1 -- VariantProps & ComponentPropsWithoutRef --\n * rather than the Button's Omit-from-React-Aria departure, which exists only\n * to stop RAC-ignored props being advertised.\n *\n * Every string below is a STATIC literal. Tailwind scans source text, so a\n * class assembled at runtime is invisible to it and would never be emitted.\n *\n * THE LAYOUT IS THE BIGGEST CHANGE. The legacy component positioned its icon\n * with `[&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4`, pushed the text over\n * with `[&>svg~*]:pl-7`, and nudged it back with `[&>svg+div]:translate-y-[-3px]`.\n * All three are physical properties or magic numbers: `left` and `pl` do not\n * mirror in RTL, so the icon stayed left while the text indented the wrong\n * side, and `translate-y-[-3px]` is a bracket value that `check:hardcoding`\n * rejects outright. Flex with an explicit `icon` prop deletes all three\n * problems rather than porting them.\n */\nexport const alertVariants = cva(\n [\n // box-border is not boilerplate. Tailwind Preflight sets box-sizing\n // globally and the legacy app had it; this library's reset is OPT-IN, so\n // a consumer may load none and the browser default content-box applies.\n // w-full is width:100%, so the padding and border are then ADDED to it\n // and the alert overflows its container by exactly 46px at comfortable\n // density. Measured, not theorised: 346px inside a 300px parent.\n \"box-border flex w-full items-start gap-(--density-control-gap)\",\n \"rounded-panel border-(length:--border-width) p-(--density-control-pad-md)\",\n // font-sans is load-bearing. The library does not style the consumer's\n // page, so anything that does not set its own face inherits the host's\n // default -- which is a serif in an unstyled document. The accordion\n // panel shipped exactly that way and it was caught by eye, not by a test.\n \"font-sans text-sm leading-(--leading-normal)\",\n ],\n {\n variants: {\n variant: {\n default: \"bg-surface text-foreground border-border\",\n // The status variants paint only their icon; the card stays neutral\n // (dark title, grey description, light border), like the reference\n // Sync status alert. See alertIconVariants.\n success: \"bg-surface text-foreground border-border\",\n warning: \"bg-surface text-foreground border-border\",\n // destructive-STRONG, not destructive. The fill token is 4.04:1 on\n // the dark page; this role exists so the text and border clear the\n // 4.5 floor in both themes. The legacy `border-destructive/50` was\n // also an opacity modifier, which cannot be contrast-checked at all\n // because the result depends on whatever sits behind it.\n destructive: \"bg-surface text-destructive-strong border-destructive-strong\",\n },\n },\n defaultVariants: { variant: \"default\" },\n },\n);\n\n/** The icon's colour: the state's own hue, on the status variants. Destructive keeps its whole-alert red. */\nconst alertIconVariants = cva(\n \"flex h-lh shrink-0 items-center [&_svg]:block [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-(--icon-sm)\",\n {\n variants: {\n variant: {\n default: \"text-info-strong\",\n success: \"text-success-strong\",\n warning: \"text-warning-strong\",\n destructive: \"\",\n },\n },\n defaultVariants: { variant: \"default\" },\n },\n);\n\n/**\n * The built-in glyph of each variant, drawn with currentColor so the icon\n * wrapper's colour paints it. Each is its own component so a variant change\n * crossfades the icon (see iconKey).\n */\nfunction StatusGlyph({ children }: { children: ReactNode }) {\n return (\n <svg\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n {children}\n </svg>\n );\n}\nfunction InfoGlyph() {\n return (\n <StatusGlyph>\n <circle cx=\"8\" cy=\"8\" r=\"6.25\" />\n <path d=\"M8 7.25v3.5M8 5h.01\" />\n </StatusGlyph>\n );\n}\nfunction SuccessGlyph() {\n return (\n <StatusGlyph>\n <circle cx=\"8\" cy=\"8\" r=\"6.25\" />\n <path d=\"m5.25 8.25 1.9 1.9 3.6-3.9\" />\n </StatusGlyph>\n );\n}\nfunction WarningGlyph() {\n return (\n <StatusGlyph>\n <path d=\"M6.9 2.6 1.9 11.4a1.2 1.2 0 0 0 1 1.8h10.2a1.2 1.2 0 0 0 1-1.8L9.1 2.6a1.2 1.2 0 0 0-2.2 0Z\" />\n <path d=\"M8 6.4v2.6M8 11.2h.01\" />\n </StatusGlyph>\n );\n}\nfunction DestructiveGlyph() {\n return (\n <StatusGlyph>\n <circle cx=\"8\" cy=\"8\" r=\"6.25\" />\n <path d=\"m6 6 4 4M10 6l-4 4\" />\n </StatusGlyph>\n );\n}\nconst statusGlyphs = {\n default: <InfoGlyph />,\n success: <SuccessGlyph />,\n warning: <WarningGlyph />,\n destructive: <DestructiveGlyph />,\n} as const;\n\n/**\n * An inline status message.\n *\n * Announced by assistive technology the moment it renders, via `role=\"alert\"`.\n * That makes it the wrong component for content that is present on load and\n * merely styled as a notice -- use a plain region for those.\n *\n * @example\n * <Alert variant=\"destructive\" icon={<WarningIcon aria-hidden />} title=\"Save failed\">\n * Your changes were not saved.\n * </Alert>\n */\nexport interface AlertProps\n // The native `title` attribute is omitted so this component can use the\n // name for its heading. That attribute renders a browser tooltip, which is\n // not keyboard reachable and is not announced reliably -- on a role=\"alert\"\n // it is worth nothing, and silently shadowing it with a ReactNode would be\n // a type error waiting for the first consumer who tried both.\n extends Omit<ComponentPropsWithoutRef<\"div\">, \"title\">, VariantProps<typeof alertVariants> {\n /**\n * Leading glyph. Mark it `aria-hidden` -- the message text is the\n * announcement, and an unhidden graphic makes the alert announce twice.\n *\n * Left out, the alert shows its variant's own icon (info, check, warning\n * triangle, cross), coloured for the state and centred on the first line of\n * text. Pass `null` for no icon, or your own glyph to replace it.\n *\n * A prop rather than a positioned child: the legacy `[&>svg]` selectors\n * only worked when the icon was the first element, and broke in RTL.\n */\n icon?: ReactNode;\n /**\n * Optional heading. Replaces the legacy `AlertTitle` part.\n *\n * Omit it for a single-line alert; most call sites do.\n */\n title?: ReactNode;\n /**\n * Heading level for {@link AlertProps.title}.\n *\n * The legacy `AlertTitle` was always an `<h5>`, which breaks the document\n * outline anywhere the alert is not nested five deep.\n *\n * @default 3\n */\n headingLevel?: 2 | 3 | 4 | 5 | 6;\n /**\n * Whether the alert is shown. Controlled: hiding it collapses its height and\n * fades it out, and showing it again grows it back. An alert that is open on\n * first render does not animate. Leave it out for an alert that is always there.\n */\n open?: boolean;\n /**\n * Adds a Dismiss button. Pressed, a controlled alert (`open` given) only calls\n * this and waits to be told `open={false}`; an uncontrolled alert (no `open`)\n * collapses first and then calls it.\n */\n onDismiss?: () => void;\n /** The Dismiss button's accessible name. @default \"Dismiss\" */\n dismissLabel?: string;\n}\n\n/** A string title or description swaps as a whole when its text changes. */\nfunction Swappable({ value, children }: { value: ReactNode; children: ReactNode }) {\n if (typeof value !== \"string\" && typeof value !== \"number\") return <>{children}</>;\n return (\n <SwapText swapKey={String(value)} className=\"block\">\n {children}\n </SwapText>\n );\n}\n\n/** The name an icon swaps under: a different icon component crossfades; the same one re-rendering stays still. */\nfunction iconKey(icon: ReactNode) {\n if (!isValidElement(icon)) return \"icon\";\n const type = icon.type as string | { displayName?: string; name?: string };\n return typeof type === \"string\" ? type : (type.displayName ?? type.name ?? \"icon\");\n}\n\nfunction DismissGlyph() {\n return (\n <svg\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n aria-hidden=\"true\"\n className=\"size-4\"\n >\n <path d=\"M4 4l8 8M12 4l-8 8\" />\n </svg>\n );\n}\n\nexport const Alert = forwardRef<HTMLDivElement, AlertProps>(function Alert(\n {\n className,\n variant,\n icon: iconProp,\n title,\n headingLevel = 3,\n children,\n open,\n onDismiss,\n dismissLabel = \"Dismiss\",\n ...props\n },\n ref,\n) {\n const Title = `h${headingLevel}` as const;\n const icon = iconProp === undefined ? statusGlyphs[variant ?? \"default\"] : iconProp;\n const frame = useRef<HTMLDivElement>(null);\n const content = useRef<HTMLDivElement>(null);\n const text = typeof children === \"string\" || typeof children === \"number\" ? String(children) : \"\";\n useHeightFollow(\n frame,\n content,\n `${typeof title === \"string\" ? title : \"\"}\\n${children === undefined ? \"\" : \"details\"}\\n${text}`,\n );\n\n // Presence. Without `open` or `onDismiss` the alert is simply there.\n const managed = open !== undefined || onDismiss !== undefined;\n const [dismissed, setDismissed] = useState(false);\n const wanted = open ?? !dismissed;\n const [rendered, setRendered] = useState(wanted);\n const [shown, setShown] = useState(wanted);\n if (wanted && !rendered) setRendered(true);\n if (!wanted && shown) setShown(false);\n useEffect(() => {\n // A newly shown alert mounts collapsed and opens a frame later, so it grows rather than appears.\n if (wanted && rendered && !shown) {\n const frameId = requestAnimationFrame(() => setShown(true));\n return () => cancelAnimationFrame(frameId);\n }\n }, [wanted, rendered, shown]);\n const leaving = rendered && !wanted;\n const finish = useCallback(() => {\n setRendered(false);\n if (open === undefined) onDismiss?.();\n }, [onDismiss, open]);\n useExitBackstop(leaving, finish, 900);\n useIsomorphicLayoutEffect(() => {\n if (leaving && prefersReducedMotion()) finish();\n }, [leaving, finish]);\n if (managed && !rendered) return null;\n\n const alert = (\n <div\n ref={ref}\n {...props}\n role=\"alert\"\n data-slot=\"alert\"\n className={cn(alertVariants({ variant }), className)}\n >\n {/*\n The icon is decorative or labelled by the consumer; this wrapper adds\n no name. It keeps the icon from shrinking and sizes a bare svg to the\n icon scale, unless the consumer sized it themselves.\n */}\n {icon === null || icon === false ? null : (\n <span data-slot=\"alert-icon\" className={alertIconVariants({ variant })}>\n <SwapText swapKey={iconKey(icon)} motion=\"glyph\" className=\"grid place-items-center\">\n {icon}\n </SwapText>\n </span>\n )}\n {/* The copy column follows its content's height, so longer or shorter text never snaps the alert. */}\n <div ref={frame} data-slot=\"alert-body\" className=\"height-follow min-w-0 flex-1\">\n <div ref={content}>\n {title === undefined ? null : (\n <Title\n data-slot=\"alert-title\"\n className=\"m-0 mb-1 text-sm leading-(--leading-normal) font-medium\"\n >\n <Swappable value={title}>{title}</Swappable>\n </Title>\n )}\n {children === undefined ? null : (\n <div\n data-slot=\"alert-description\"\n className={title === undefined ? undefined : \"text-xs leading-(--leading-normal) text-text-secondary\"}\n >\n <Swappable value={children}>{children}</Swappable>\n </div>\n )}\n </div>\n </div>\n {onDismiss ? (\n <AriaButton\n data-slot=\"alert-dismiss\"\n aria-label={typeof title === \"string\" ? `${dismissLabel}: ${title}` : dismissLabel}\n onPress={() => (open === undefined ? setDismissed(true) : onDismiss())}\n className=\"calendar-nav-motion -my-1 -me-1.5 grid size-7 shrink-0 cursor-pointer place-items-center rounded-full border-0 bg-transparent text-muted-foreground outline-none data-[hovered]:bg-muted data-[hovered]:text-foreground data-[focus-visible]:outline-solid data-[focus-visible]:outline-(length:--focus-ring-width) data-[focus-visible]:outline-offset-(--focus-ring-offset) data-[focus-visible]:outline-(--color-ring)\"\n >\n <DismissGlyph />\n </AriaButton>\n ) : null}\n </div>\n );\n if (!managed) return alert;\n return (\n <div\n data-slot=\"alert-presence\"\n data-shown={shown || undefined}\n onTransitionEnd={(event) => {\n if (leaving && event.target === event.currentTarget && event.propertyName === \"grid-template-rows\")\n finish();\n }}\n className=\"alert-presence\"\n >\n <div>{alert}</div>\n </div>\n );\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"alert.js","names":["AriaButton"],"sources":["../../src/alert/alert.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport {\n forwardRef,\n isValidElement,\n useCallback,\n useEffect,\n useRef,\n useState,\n type AriaRole,\n type ComponentPropsWithoutRef,\n type ReactNode,\n type SVGProps,\n} from \"react\";\n\nimport { Button as AriaButton } from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { useHeightFollow } from \"../lib/height-follow.js\";\nimport { prefersReducedMotion, useExitBackstop, useIsomorphicLayoutEffect } from \"../lib/motion.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\n\n/**\n * `info` is `default` said by name: a call site that means \"information\" should\n * not depend on `default` happening to look like it. Every per-variant map below\n * derives `info` here, from its `default`, so the two cannot drift.\n */\nfunction withInfo<T extends { default: unknown }>(map: T): T & { info: T[\"default\"] } {\n return { ...map, info: map.default };\n}\n\n/**\n * Alert, ported from the legacy shadcn component.\n *\n * There is no React Aria base: RAC ships no static inline alert (its Toast\n * API is transient and queue-driven), so this is a styled div with\n * role=\"alert\". Because it is not RAC-backed, its props follow\n * ARCHITECTURE section 7 rule 1 -- VariantProps & ComponentPropsWithoutRef --\n * rather than the Button's Omit-from-React-Aria departure, which exists only\n * to stop RAC-ignored props being advertised.\n *\n * Every string below is a STATIC literal. Tailwind scans source text, so a\n * class assembled at runtime is invisible to it and would never be emitted.\n *\n * THE LAYOUT IS THE BIGGEST CHANGE. The legacy component positioned its icon\n * with `[&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4`, pushed the text over\n * with `[&>svg~*]:pl-7`, and nudged it back with `[&>svg+div]:translate-y-[-3px]`.\n * All three are physical properties or magic numbers: `left` and `pl` do not\n * mirror in RTL, so the icon stayed left while the text indented the wrong\n * side, and `translate-y-[-3px]` is a bracket value that `check:hardcoding`\n * rejects outright. Flex with an explicit `icon` prop deletes all three\n * problems rather than porting them.\n */\nexport const alertVariants = cva(\n [\n // box-border is not boilerplate. Tailwind Preflight sets box-sizing\n // globally and the legacy app had it; this library's reset is OPT-IN, so\n // a consumer may load none and the browser default content-box applies.\n // w-full is width:100%, so the padding and border are then ADDED to it\n // and the alert overflows its container by exactly 46px at comfortable\n // density. Measured, not theorised: 346px inside a 300px parent.\n \"box-border flex w-full items-start gap-(--density-control-gap)\",\n \"rounded-panel border-(length:--border-width) p-(--density-control-pad-md)\",\n // font-sans is load-bearing. The library does not style the consumer's\n // page, so anything that does not set its own face inherits the host's\n // default -- which is a serif in an unstyled document. The accordion\n // panel shipped exactly that way and it was caught by eye, not by a test.\n \"font-sans text-sm leading-(--leading-normal)\",\n ],\n {\n variants: {\n variant: withInfo({\n default: \"bg-surface text-foreground border-border\",\n // The status variants paint only their icon; the card stays neutral\n // (dark title, grey description, light border), like the reference\n // Sync status alert. See alertIconVariants.\n success: \"bg-surface text-foreground border-border\",\n warning: \"bg-surface text-foreground border-border\",\n // destructive-STRONG, not destructive. The fill token is 4.04:1 on\n // the dark page; this role exists so the text and border clear the\n // 4.5 floor in both themes. The legacy `border-destructive/50` was\n // also an opacity modifier, which cannot be contrast-checked at all\n // because the result depends on whatever sits behind it.\n destructive: \"bg-surface text-destructive-strong border-destructive-strong\",\n }),\n },\n defaultVariants: { variant: \"default\" },\n },\n);\n\n/**\n * The icon's colour: the state's own hue on every variant. Inside an Alert the\n * destructive red is also the whole alert's text colour, so it changes nothing\n * there; outside one it is what colours the icon.\n *\n * Exported with the status icons so a notice that is not an `Alert` (a row, a\n * banner of the app's own) can show a tone the same way the alert does.\n */\nexport const alertIconVariants = cva(\n \"flex h-lh shrink-0 items-center [&_svg]:block [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-(--icon-sm)\",\n {\n variants: {\n variant: withInfo({\n default: \"text-info-strong\",\n success: \"text-success-strong\",\n warning: \"text-warning-strong\",\n destructive: \"text-destructive-strong\",\n }),\n },\n defaultVariants: { variant: \"default\" },\n },\n);\n\n/**\n * The built-in glyph of each variant, drawn with currentColor so the icon\n * wrapper's colour paints it. Each is its own component so a variant change\n * crossfades the icon (see iconKey).\n *\n * Exported as AlertInfoIcon, AlertSuccessIcon, AlertWarningIcon and\n * AlertDestructiveIcon. They are decorative (`aria-hidden`) and `--icon-sm`\n * by default; extra props, such as a `className` with a size, land on the svg.\n * Given an `aria-label` or `aria-labelledby` (with `role=\"img\"`), an icon is\n * no longer hidden, and an explicit `aria-hidden` always wins.\n */\ntype StatusIconProps = Omit<SVGProps<SVGSVGElement>, \"children\">;\nfunction StatusGlyph({\n children,\n className,\n ...props\n}: StatusIconProps & { children: ReactNode }) {\n const labelled = props[\"aria-label\"] !== undefined || props[\"aria-labelledby\"] !== undefined;\n return (\n <svg\n // Before the spread, so a caller's own aria-hidden wins; a labelled icon\n // is meant to be read, so it is not hidden by default.\n aria-hidden={labelled ? undefined : \"true\"}\n {...props}\n // Sized on its own, so an exported icon outside an alert is not 0x0; the\n // same --icon-sm the alert's icon slot gives an unsized svg. A caller's\n // size class wins.\n className={cn(\"size-(--icon-sm)\", className)}\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n {children}\n </svg>\n );\n}\nfunction InfoGlyph(props: StatusIconProps) {\n return (\n <StatusGlyph {...props}>\n <circle cx=\"8\" cy=\"8\" r=\"6.25\" />\n <path d=\"M8 7.25v3.5M8 5h.01\" />\n </StatusGlyph>\n );\n}\nfunction SuccessGlyph(props: StatusIconProps) {\n return (\n <StatusGlyph {...props}>\n <circle cx=\"8\" cy=\"8\" r=\"6.25\" />\n <path d=\"m5.25 8.25 1.9 1.9 3.6-3.9\" />\n </StatusGlyph>\n );\n}\nfunction WarningGlyph(props: StatusIconProps) {\n return (\n <StatusGlyph {...props}>\n <path d=\"M6.9 2.6 1.9 11.4a1.2 1.2 0 0 0 1 1.8h10.2a1.2 1.2 0 0 0 1-1.8L9.1 2.6a1.2 1.2 0 0 0-2.2 0Z\" />\n <path d=\"M8 6.4v2.6M8 11.2h.01\" />\n </StatusGlyph>\n );\n}\nfunction DestructiveGlyph(props: StatusIconProps) {\n return (\n <StatusGlyph {...props}>\n <circle cx=\"8\" cy=\"8\" r=\"6.25\" />\n <path d=\"m6 6 4 4M10 6l-4 4\" />\n </StatusGlyph>\n );\n}\nexport {\n InfoGlyph as AlertInfoIcon,\n SuccessGlyph as AlertSuccessIcon,\n WarningGlyph as AlertWarningIcon,\n DestructiveGlyph as AlertDestructiveIcon,\n};\nexport type { StatusIconProps as AlertStatusIconProps };\n\nconst statusGlyphs = withInfo({\n default: <InfoGlyph />,\n success: <SuccessGlyph />,\n warning: <WarningGlyph />,\n destructive: <DestructiveGlyph />,\n});\n\n/**\n * An inline status message.\n *\n * Announced by assistive technology the moment it renders, via `role=\"alert\"`.\n * That makes it the wrong default for content that is present on load and\n * merely styled as a notice -- pass `role=\"note\"` for those, or\n * `role=\"status\"` for an outcome that should be announced politely.\n *\n * @example\n * <Alert variant=\"destructive\" icon={<WarningIcon aria-hidden />} title=\"Save failed\">\n * Your changes were not saved.\n * </Alert>\n */\nexport interface AlertProps\n // The native `title` attribute is omitted so this component can use the\n // name for its heading. That attribute renders a browser tooltip, which is\n // not keyboard reachable and is not announced reliably -- on a role=\"alert\"\n // it is worth nothing, and silently shadowing it with a ReactNode would be\n // a type error waiting for the first consumer who tried both.\n extends Omit<ComponentPropsWithoutRef<\"div\">, \"title\">, VariantProps<typeof alertVariants> {\n /**\n * Leading glyph. Mark it `aria-hidden` -- the message text is the\n * announcement, and an unhidden graphic makes the alert announce twice.\n *\n * Left out, the alert shows its variant's own icon (info, check, warning\n * triangle, cross), coloured for the state and centred on the first line of\n * text. Pass `null` for no icon, or your own glyph to replace it.\n *\n * A prop rather than a positioned child: the legacy `[&>svg]` selectors\n * only worked when the icon was the first element, and broke in RTL.\n */\n icon?: ReactNode;\n /**\n * Optional heading. Replaces the legacy `AlertTitle` part.\n *\n * Omit it for a single-line alert; most call sites do.\n */\n title?: ReactNode;\n /**\n * Heading level for {@link AlertProps.title}.\n *\n * The legacy `AlertTitle` was always an `<h5>`, which breaks the document\n * outline anywhere the alert is not nested five deep.\n *\n * @default 3\n */\n headingLevel?: 2 | 3 | 4 | 5 | 6;\n /**\n * Whether the alert is shown. Controlled: hiding it collapses its height and\n * fades it out, and showing it again grows it back. An alert that is open on\n * first render does not animate. Leave it out for an alert that is always there.\n */\n open?: boolean;\n /**\n * Adds a Dismiss button. Pressed, a controlled alert (`open` given) only calls\n * this and waits to be told `open={false}`; an uncontrolled alert (no `open`)\n * collapses first and then calls it.\n */\n onDismiss?: () => void;\n /** The Dismiss button's accessible name. @default \"Dismiss\" */\n dismissLabel?: string;\n /**\n * How assistive technology treats the message.\n *\n * - `alert`: announced assertively the moment it renders. For an error or a\n * warning that appears after the user did something.\n * - `status`: a live region announced politely, after what the user is\n * hearing. For an outcome reported after an action (\"Saved\").\n * - `note`: not live. For a notice that is simply there when the page loads;\n * an alert there would interrupt the page's own announcement.\n *\n * Typed as any ARIA role, as the inherited `role` was, so existing callers\n * still compile; those three are the ones an inline notice should use.\n *\n * @default \"alert\"\n */\n role?: \"alert\" | \"status\" | \"note\" | AriaRole;\n}\n\n/** A string title or description swaps as a whole when its text changes. */\nfunction Swappable({ value, children }: { value: ReactNode; children: ReactNode }) {\n if (typeof value !== \"string\" && typeof value !== \"number\") return <>{children}</>;\n return (\n <SwapText swapKey={String(value)} className=\"block\">\n {children}\n </SwapText>\n );\n}\n\n/** The name an icon swaps under: a different icon component crossfades; the same one re-rendering stays still. */\nfunction iconKey(icon: ReactNode) {\n if (!isValidElement(icon)) return \"icon\";\n const type = icon.type as string | { displayName?: string; name?: string };\n return typeof type === \"string\" ? type : (type.displayName ?? type.name ?? \"icon\");\n}\n\nfunction DismissGlyph() {\n return (\n <svg\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n aria-hidden=\"true\"\n className=\"size-4\"\n >\n <path d=\"M4 4l8 8M12 4l-8 8\" />\n </svg>\n );\n}\n\nexport const Alert = forwardRef<HTMLDivElement, AlertProps>(function Alert(\n {\n className,\n variant,\n icon: iconProp,\n title,\n headingLevel = 3,\n children,\n open,\n onDismiss,\n dismissLabel = \"Dismiss\",\n role = \"alert\",\n ...props\n },\n ref,\n) {\n const Title = `h${headingLevel}` as const;\n const icon = iconProp === undefined ? statusGlyphs[variant ?? \"default\"] : iconProp;\n const frame = useRef<HTMLDivElement>(null);\n const content = useRef<HTMLDivElement>(null);\n const text = typeof children === \"string\" || typeof children === \"number\" ? String(children) : \"\";\n useHeightFollow(\n frame,\n content,\n `${typeof title === \"string\" ? title : \"\"}\\n${children === undefined ? \"\" : \"details\"}\\n${text}`,\n );\n\n // Presence. Without `open` or `onDismiss` the alert is simply there.\n const managed = open !== undefined || onDismiss !== undefined;\n const [dismissed, setDismissed] = useState(false);\n const wanted = open ?? !dismissed;\n const [rendered, setRendered] = useState(wanted);\n const [shown, setShown] = useState(wanted);\n if (wanted && !rendered) setRendered(true);\n if (!wanted && shown) setShown(false);\n useEffect(() => {\n // A newly shown alert mounts collapsed and opens a frame later, so it grows rather than appears.\n if (wanted && rendered && !shown) {\n const frameId = requestAnimationFrame(() => setShown(true));\n return () => cancelAnimationFrame(frameId);\n }\n }, [wanted, rendered, shown]);\n const leaving = rendered && !wanted;\n const finish = useCallback(() => {\n setRendered(false);\n if (open === undefined) onDismiss?.();\n }, [onDismiss, open]);\n useExitBackstop(leaving, finish, 900);\n useIsomorphicLayoutEffect(() => {\n if (leaving && prefersReducedMotion()) finish();\n }, [leaving, finish]);\n if (managed && !rendered) return null;\n\n const alert = (\n <div\n ref={ref}\n {...props}\n role={role}\n data-slot=\"alert\"\n className={cn(alertVariants({ variant }), className)}\n >\n {/*\n The icon is decorative or labelled by the consumer; this wrapper adds\n no name. It keeps the icon from shrinking and sizes a bare svg to the\n icon scale, unless the consumer sized it themselves.\n */}\n {icon === null || icon === false ? null : (\n <span data-slot=\"alert-icon\" className={alertIconVariants({ variant })}>\n <SwapText swapKey={iconKey(icon)} motion=\"glyph\" className=\"grid place-items-center\">\n {icon}\n </SwapText>\n </span>\n )}\n {/* The copy column follows its content's height, so longer or shorter text never snaps the alert. */}\n <div ref={frame} data-slot=\"alert-body\" className=\"height-follow min-w-0 flex-1\">\n <div ref={content}>\n {title === undefined ? null : (\n <Title\n data-slot=\"alert-title\"\n className=\"m-0 mb-1 text-sm leading-(--leading-normal) font-medium\"\n >\n <Swappable value={title}>{title}</Swappable>\n </Title>\n )}\n {children === undefined ? null : (\n <div\n data-slot=\"alert-description\"\n className={title === undefined ? undefined : \"text-xs leading-(--leading-normal) text-text-secondary\"}\n >\n <Swappable value={children}>{children}</Swappable>\n </div>\n )}\n </div>\n </div>\n {onDismiss ? (\n <AriaButton\n data-slot=\"alert-dismiss\"\n aria-label={typeof title === \"string\" ? `${dismissLabel}: ${title}` : dismissLabel}\n onPress={() => (open === undefined ? setDismissed(true) : onDismiss())}\n className=\"calendar-nav-motion -my-1 -me-1.5 grid size-7 shrink-0 cursor-pointer place-items-center rounded-full border-0 bg-transparent text-muted-foreground outline-none data-[hovered]:bg-muted data-[hovered]:text-foreground data-[focus-visible]:outline-solid data-[focus-visible]:outline-(length:--focus-ring-width) data-[focus-visible]:outline-offset-(--focus-ring-offset) data-[focus-visible]:outline-(--color-ring)\"\n >\n <DismissGlyph />\n </AriaButton>\n ) : null}\n </div>\n );\n if (!managed) return alert;\n return (\n <div\n data-slot=\"alert-presence\"\n data-shown={shown || undefined}\n onTransitionEnd={(event) => {\n if (leaving && event.target === event.currentTarget && event.propertyName === \"grid-template-rows\")\n finish();\n }}\n className=\"alert-presence\"\n >\n <div>{alert}</div>\n </div>\n );\n});\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import { Alert, AlertProps, alertVariants } from "./alert.js";
2
- export { Alert, type AlertProps, alertVariants };
1
+ import { Alert, AlertDestructiveIcon as DestructiveGlyph, AlertInfoIcon as InfoGlyph, AlertProps, AlertStatusIconProps as StatusIconProps, AlertSuccessIcon as SuccessGlyph, AlertWarningIcon as WarningGlyph, alertIconVariants, alertVariants } from "./alert.js";
2
+ export { Alert, DestructiveGlyph as AlertDestructiveIcon, InfoGlyph as AlertInfoIcon, type AlertProps, type StatusIconProps as AlertStatusIconProps, SuccessGlyph as AlertSuccessIcon, WarningGlyph as AlertWarningIcon, alertIconVariants, alertVariants };
@@ -1,2 +1,2 @@
1
- import { Alert, alertVariants } from "./alert.js";
2
- export { Alert, alertVariants };
1
+ import { Alert, AlertDestructiveIcon as DestructiveGlyph, AlertInfoIcon as InfoGlyph, AlertSuccessIcon as SuccessGlyph, AlertWarningIcon as WarningGlyph, alertIconVariants, alertVariants } from "./alert.js";
2
+ export { Alert, DestructiveGlyph as AlertDestructiveIcon, InfoGlyph as AlertInfoIcon, SuccessGlyph as AlertSuccessIcon, WarningGlyph as AlertWarningIcon, alertIconVariants, alertVariants };
@@ -7,7 +7,8 @@ import { ReactNode } from "react";
7
7
  *
8
8
  * Built from `Dialog`'s own overlay and panel styles and the same React Aria
9
9
  * parts, but with its own layout: a header (title, a grey subtitle and a
10
- * circular close button), a thin divider, the body text, then the actions.
10
+ * circular close button), a thin divider, the description sentence, an
11
+ * optional `body` of rich content, then the actions.
11
12
  * The legacy `alert-dialog.tsx` was a 157-line clone of `dialog.tsx`; this
12
13
  * shares the tokens and the motion classes instead, so the two cannot drift
13
14
  * on radius, scrim or entrance.
@@ -30,7 +31,7 @@ import { ReactNode } from "react";
30
31
  * header's copy of the second one.
31
32
  * - `description` is REQUIRED. A confirmation with no explanation is the
32
33
  * thing this component exists to prevent, and the role only associates it
33
- * when it is there. It is the body text above the actions.
34
+ * when it is there. It is the sentence under the divider.
34
35
  *
35
36
  * THE TWO ACTIONS MUST NEVER LOOK ALIKE. `AlertDialogCancel` is the secondary
36
37
  * button (white, light border) and cancels through `slot="close"`;
@@ -43,8 +44,8 @@ import { ReactNode } from "react";
43
44
  */
44
45
  export interface AlertDialogProps extends Omit<DialogProps, "role" | "closeButton" | "isDismissable"> {
45
46
  /**
46
- * What is about to happen, and whether it can be undone. Rendered as the body text
47
- * between the divider and the actions.
47
+ * What is about to happen, and whether it can be undone. Rendered as the
48
+ * sentence under the divider, above the `body` slot (if any) and the actions.
48
49
  *
49
50
  * **Required**, unlike on `Dialog`. `role="alertdialog"` is what makes
50
51
  * React Aria wire this to `aria-describedby`, so it is both the sentence
@@ -53,6 +54,32 @@ export interface AlertDialogProps extends Omit<DialogProps, "role" | "closeButto
53
54
  description: ReactNode;
54
55
  /** A short grey line under the title, in the header. */
55
56
  subtitle?: ReactNode;
57
+ /**
58
+ * Rich content between the description and the actions: a plan comparison,
59
+ * a before/after diff, a quoted reply, a form field or a choice to make.
60
+ *
61
+ * It is NOT part of the accessible description: the screen reader announces
62
+ * the one `description` sentence with the name, and the body is read in
63
+ * place or as focus reaches its controls (each with its own label). So the
64
+ * `description` must say everything needed to answer, including that a
65
+ * choice or field below is required ("Pick what happens to your data.").
66
+ * `false`, `true` and `""` (as from `cond && <X />`) render no body.
67
+ */
68
+ body?: ReactNode;
69
+ /**
70
+ * A decorative icon at the start of the header, in a round tile beside the
71
+ * title: a warning triangle before a wide-reaching switch, a trash can before
72
+ * a delete. Hidden from assistive technology, so the title must still say
73
+ * what is at stake. Leave it out and the header is as before.
74
+ */
75
+ icon?: ReactNode;
76
+ /**
77
+ * The colour of the icon's tile, from the status tokens: `neutral` (the
78
+ * default), `warning`, `destructive`, `info` or `success`. It paints the tile
79
+ * only, never the actions. Without an `icon` it does nothing. Mirrored to
80
+ * `data-tone` on the tile.
81
+ */
82
+ tone?: "neutral" | "warning" | "destructive" | "info" | "success";
56
83
  /** The actions. Give the cancel button `slot="close"`. */
57
84
  children?: ReactNode;
58
85
  }
@@ -10,6 +10,11 @@ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
10
10
  //#region src/alert-dialog/alert-dialog.tsx
11
11
  const alertDialogActionsVariants = cva("flex flex-wrap items-center justify-end gap-2 px-8 pb-8");
12
12
  /**
13
+ * The body slot: the description's side padding, so the two share an edge,
14
+ * and the description's own bottom space repeated below it before the actions.
15
+ */
16
+ const alertDialogBodyVariants = cva("min-w-0 px-8 pb-6 text-sm leading-(--leading-normal) text-foreground");
17
+ /**
13
18
  * The cancel action: the secondary button, wired to dismiss the dialog.
14
19
  *
15
20
  * @example
@@ -45,6 +50,31 @@ const AlertDialogAction = forwardRef(function AlertDialogAction({ className, ...
45
50
  });
46
51
  });
47
52
  const alertDialogHeaderVariants = cva("flex items-start gap-4 border-b-(length:--border-width) border-border px-8 pt-8 pb-6");
53
+ /**
54
+ * The header icon's tile: the close button's size, so a one-line title centres
55
+ * on both. Each tone is a subtle tint with the ink the token contrast contract
56
+ * grades on it.
57
+ */
58
+ const alertDialogIconVariants = cva("inline-flex size-9 shrink-0 items-center justify-center rounded-circle [&_svg]:pointer-events-none [&_svg]:size-5", {
59
+ variants: { tone: {
60
+ neutral: "bg-muted text-foreground",
61
+ warning: "bg-warning-subtle text-warning-foreground",
62
+ destructive: "bg-destructive-subtle text-destructive-subtle-foreground",
63
+ info: "bg-info-subtle text-info-foreground",
64
+ success: "bg-success-subtle text-success-foreground"
65
+ } },
66
+ defaultVariants: { tone: "neutral" }
67
+ });
68
+ /**
69
+ * The title column. With no subtitle it is at least the close button's height
70
+ * and centres the title in it, so a one-line title sits level with the close;
71
+ * a longer title grows the column from the top as before. With a subtitle the
72
+ * two lines start at the top of the button, as they always have.
73
+ */
74
+ const alertDialogTitleBlockVariants = cva("min-w-0 flex-1", { variants: { subtitle: {
75
+ true: "",
76
+ false: "flex min-h-9 flex-col justify-center"
77
+ } } });
48
78
  const alertDialogTitleVariants = cva("m-0 text-lg leading-(--leading-tight) font-semibold text-foreground");
49
79
  const alertDialogCloseVariants = cva([
50
80
  "inline-flex size-9 shrink-0 items-center justify-center",
@@ -68,7 +98,11 @@ function Swappable({ value }) {
68
98
  children: value
69
99
  });
70
100
  }
71
- const AlertDialog = forwardRef(function AlertDialog({ title, subtitle, beforeTitle, description, children, className, closeLabel = "Close", overlayClassName, entrance = "rise", ...props }, ref) {
101
+ /** `cond && <X />` hands over `false`; React renders nothing for it, so neither does the slot. */
102
+ function isEmpty(node) {
103
+ return node == null || typeof node === "boolean" || node === "";
104
+ }
105
+ const AlertDialog = forwardRef(function AlertDialog({ title, subtitle, beforeTitle, description, body, icon, tone = "neutral", children, className, closeLabel = "Close", overlayClassName, entrance = "rise", ...props }, ref) {
72
106
  return /* @__PURE__ */ jsx(ModalOverlay, {
73
107
  ...props,
74
108
  isDismissable: false,
@@ -86,33 +120,43 @@ const AlertDialog = forwardRef(function AlertDialog({ title, subtitle, beforeTit
86
120
  /* @__PURE__ */ jsxs("div", {
87
121
  "data-slot": "alert-dialog-header",
88
122
  className: alertDialogHeaderVariants(),
89
- children: [/* @__PURE__ */ jsxs("div", {
90
- className: "min-w-0 flex-1",
91
- children: [/* @__PURE__ */ jsx(Heading, {
92
- slot: "title",
93
- "data-slot": "dialog-title",
94
- className: alertDialogTitleVariants(),
95
- children: /* @__PURE__ */ jsx(Swappable, { value: title })
96
- }), subtitle == null ? null : /* @__PURE__ */ jsx("p", {
97
- "data-slot": "alert-dialog-subtitle",
98
- className: "m-0 mt-1 text-sm leading-(--leading-normal) text-text-secondary",
99
- children: /* @__PURE__ */ jsx(Swappable, { value: subtitle })
100
- })]
101
- }), /* @__PURE__ */ jsx(Button, {
102
- slot: "close",
103
- "aria-label": closeLabel,
104
- "data-slot": "alert-dialog-close",
105
- className: alertDialogCloseVariants(),
106
- children: /* @__PURE__ */ jsx("svg", {
123
+ children: [
124
+ isEmpty(icon) ? null : /* @__PURE__ */ jsx("span", {
107
125
  "aria-hidden": "true",
108
- viewBox: "0 0 16 16",
109
- fill: "none",
110
- stroke: "currentColor",
111
- strokeWidth: "1.75",
112
- strokeLinecap: "round",
113
- children: /* @__PURE__ */ jsx("path", { d: "m4 4 8 8M12 4l-8 8" })
126
+ "data-slot": "alert-dialog-icon",
127
+ "data-tone": tone,
128
+ className: alertDialogIconVariants({ tone }),
129
+ children: icon
130
+ }),
131
+ /* @__PURE__ */ jsxs("div", {
132
+ className: alertDialogTitleBlockVariants({ subtitle: subtitle != null }),
133
+ children: [/* @__PURE__ */ jsx(Heading, {
134
+ slot: "title",
135
+ "data-slot": "dialog-title",
136
+ className: alertDialogTitleVariants(),
137
+ children: /* @__PURE__ */ jsx(Swappable, { value: title })
138
+ }), subtitle == null ? null : /* @__PURE__ */ jsx("p", {
139
+ "data-slot": "alert-dialog-subtitle",
140
+ className: "m-0 mt-1 text-sm leading-(--leading-normal) text-text-secondary",
141
+ children: /* @__PURE__ */ jsx(Swappable, { value: subtitle })
142
+ })]
143
+ }),
144
+ /* @__PURE__ */ jsx(Button, {
145
+ slot: "close",
146
+ "aria-label": closeLabel,
147
+ "data-slot": "alert-dialog-close",
148
+ className: alertDialogCloseVariants(),
149
+ children: /* @__PURE__ */ jsx("svg", {
150
+ "aria-hidden": "true",
151
+ viewBox: "0 0 16 16",
152
+ fill: "none",
153
+ stroke: "currentColor",
154
+ strokeWidth: "1.75",
155
+ strokeLinecap: "round",
156
+ children: /* @__PURE__ */ jsx("path", { d: "m4 4 8 8M12 4l-8 8" })
157
+ })
114
158
  })
115
- })]
159
+ ]
116
160
  }),
117
161
  /* @__PURE__ */ jsx(Text, {
118
162
  slot: "description",
@@ -121,6 +165,11 @@ const AlertDialog = forwardRef(function AlertDialog({ title, subtitle, beforeTit
121
165
  className: "m-0 block px-8 py-6 text-sm leading-(--leading-normal) text-foreground",
122
166
  children: /* @__PURE__ */ jsx(Swappable, { value: description })
123
167
  }),
168
+ isEmpty(body) ? null : /* @__PURE__ */ jsx("div", {
169
+ "data-slot": "alert-dialog-body",
170
+ className: alertDialogBodyVariants(),
171
+ children: body
172
+ }),
124
173
  children == null ? null : /* @__PURE__ */ jsx("div", {
125
174
  "data-slot": "alert-dialog-actions",
126
175
  className: alertDialogActionsVariants(),
@@ -1 +1 @@
1
- {"version":3,"file":"alert-dialog.js","names":["Button","AriaModalOverlay","AriaModal","AriaDialog","AriaHeading","AriaButton","AriaText"],"sources":["../../src/alert-dialog/alert-dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, type ReactNode } from \"react\";\nimport {\n Button as AriaButton,\n Dialog as AriaDialog,\n Heading as AriaHeading,\n Modal as AriaModal,\n ModalOverlay as AriaModalOverlay,\n Text as AriaText,\n} from \"react-aria-components\";\n\nimport { Button, type ButtonProps } from \"../button/button.js\";\nimport { cnAll } from \"../lib/cn.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\nimport { dialogOverlayVariants, dialogPanelVariants, type DialogProps } from \"../dialog/dialog.js\";\n\n/**\n * AlertDialog: a confirmation that must be answered.\n *\n * Built from `Dialog`'s own overlay and panel styles and the same React Aria\n * parts, but with its own layout: a header (title, a grey subtitle and a\n * circular close button), a thin divider, the body text, then the actions.\n * The legacy `alert-dialog.tsx` was a 157-line clone of `dialog.tsx`; this\n * shares the tokens and the motion classes instead, so the two cannot drift\n * on radius, scrim or entrance.\n *\n * Census: 2 files, all eight parts used twice each, `AlertDialogPortal`,\n * `AlertDialogOverlay` and `AlertDialogTrigger` never rendered externally.\n *\n * What the preset forces, and why each one is a defect if forgotten:\n *\n * - `role=\"alertdialog\"`. React Aria associates the description with\n * `aria-describedby` ONLY for this role (useDialog.mjs:29), so a\n * confirmation built from a plain Dialog loses the sentence explaining\n * what is about to happen.\n * - `isDismissable={false}`. Clicking the page must not resolve a\n * destructive confirmation. Escape still cancels, which is the APG\n * behaviour and what Radix does.\n * - THE CLOSE (X) IS CANCEL. It is `slot=\"close\"`, the same dismiss wiring as\n * a Cancel button, so it goes through the same `onOpenChange(false)` and\n * focus returns to the trigger. It is not a third answer: it is the\n * header's copy of the second one.\n * - `description` is REQUIRED. A confirmation with no explanation is the\n * thing this component exists to prevent, and the role only associates it\n * when it is there. It is the body text above the actions.\n *\n * THE TWO ACTIONS MUST NEVER LOOK ALIKE. `AlertDialogCancel` is the secondary\n * button (white, light border) and cancels through `slot=\"close\"`;\n * `AlertDialogAction` is the primary button painted destructive red, because a\n * confirmation here is a destructive one. The legacy `AlertDialogAction`\n * defaulted to the gradient primary and BOTH call sites overrode it back to\n * destructive by hand (`data-table.tsx:146`, `calendar-view.tsx:638`); the red\n * is the default now, so nobody fights it. Plain `Button`s still work in the\n * actions slot for anything else.\n */\nexport interface AlertDialogProps extends Omit<\n DialogProps,\n \"role\" | \"closeButton\" | \"isDismissable\"\n> {\n /**\n * What is about to happen, and whether it can be undone. Rendered as the body text\n * between the divider and the actions.\n *\n * **Required**, unlike on `Dialog`. `role=\"alertdialog\"` is what makes\n * React Aria wire this to `aria-describedby`, so it is both the sentence\n * the user needs and the one the screen reader announces with the name.\n */\n description: ReactNode;\n /** A short grey line under the title, in the header. */\n subtitle?: ReactNode;\n /** The actions. Give the cancel button `slot=\"close\"`. */\n children?: ReactNode;\n}\n\nconst alertDialogActionsVariants = cva(\"flex flex-wrap items-center justify-end gap-2 px-8 pb-8\");\n\n/**\n * The cancel action: the secondary button, wired to dismiss the dialog.\n *\n * @example\n * <AlertDialogCancel>Cancel</AlertDialogCancel>\n */\nexport const AlertDialogCancel = forwardRef<HTMLButtonElement, ButtonProps>(\n function AlertDialogCancel(props, ref) {\n return <Button ref={ref} slot=\"close\" variant=\"secondary\" size=\"sm\" {...props} />;\n },\n);\n\n/**\n * The confirm action: the primary button in the destructive red.\n *\n * It is the primary button with its fill, hover, pressed and label roles\n * pointed at the destructive tokens, so it keeps the primary's press motion\n * and shadow. Variables on the element, rather than a competing background\n * class, so no stylesheet order decides which colour wins.\n *\n * @example\n * <AlertDialogAction onPress={remove}>Delete</AlertDialogAction>\n */\nexport const AlertDialogAction = forwardRef<HTMLButtonElement, ButtonProps>(\n function AlertDialogAction({ className, ...props }, ref) {\n return (\n <Button\n ref={ref}\n variant=\"primary\"\n size=\"sm\"\n className={cnAll(\n \"[--color-primary:var(--color-destructive)] [--color-primary-hover:var(--color-destructive-hover)] [--color-primary-pressed:var(--color-destructive-pressed)] [--color-primary-foreground:var(--color-destructive-foreground)]\",\n className,\n )}\n {...props}\n />\n );\n },\n);\n\nconst alertDialogHeaderVariants = cva(\n \"flex items-start gap-4 border-b-(length:--border-width) border-border px-8 pt-8 pb-6\",\n);\n\nconst alertDialogTitleVariants = cva(\n \"m-0 text-lg leading-(--leading-tight) font-semibold text-foreground\",\n);\n\nconst alertDialogCloseVariants = cva([\n \"inline-flex size-9 shrink-0 items-center justify-center\",\n \"rounded-circle box-border cursor-pointer\",\n \"border-(length:--border-width) border-border bg-surface p-0 text-text-secondary\",\n // Fill on hover, a quick press to .96 and a spring release (see calendar-nav-motion).\n \"calendar-nav-motion\",\n \"data-[hovered]:bg-muted data-[hovered]:text-foreground\",\n \"outline-none\",\n \"data-[focus-visible]:outline-solid\",\n \"data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"data-[focus-visible]:outline-offset-(--focus-ring-offset)\",\n \"data-[focus-visible]:outline-(--color-ring)\",\n \"[&_svg]:pointer-events-none [&_svg]:size-4\",\n]);\n\n/** A string swaps as a whole when its text changes while the dialog is open. */\nfunction Swappable({ value }: { value: ReactNode }) {\n if (typeof value !== \"string\") return <>{value}</>;\n return (\n <SwapText swapKey={value} className=\"block\">\n {value}\n </SwapText>\n );\n}\n\nexport const AlertDialog = forwardRef<HTMLElement, AlertDialogProps>(function AlertDialog(\n {\n title,\n subtitle,\n beforeTitle,\n description,\n children,\n className,\n closeLabel = \"Close\",\n overlayClassName,\n entrance = \"rise\",\n ...props\n },\n ref,\n) {\n return (\n <AriaModalOverlay\n {...props}\n isDismissable={false}\n data-slot=\"dialog-overlay\"\n className={cnAll(\n dialogOverlayVariants(),\n entrance === \"rise\" && \"dialog-overlay-motion\",\n overlayClassName,\n )}\n >\n <AriaModal className=\"contents\">\n <AriaDialog\n ref={ref}\n role=\"alertdialog\"\n data-slot=\"dialog\"\n className={cnAll(\n dialogPanelVariants(),\n // The panel has no padding of its own: the header, body and actions pad themselves,\n // so the divider can run edge to edge.\n \"gap-0 p-0\",\n entrance === \"rise\" && \"dialog-panel-motion\",\n className,\n )}\n >\n {beforeTitle}\n <div data-slot=\"alert-dialog-header\" className={alertDialogHeaderVariants()}>\n <div className=\"min-w-0 flex-1\">\n {/* slot=\"title\" is what React Aria wires aria-labelledby to. */}\n <AriaHeading\n slot=\"title\"\n data-slot=\"dialog-title\"\n className={alertDialogTitleVariants()}\n >\n <Swappable value={title} />\n </AriaHeading>\n {subtitle == null ? null : (\n <p\n data-slot=\"alert-dialog-subtitle\"\n className=\"m-0 mt-1 text-sm leading-(--leading-normal) text-text-secondary\"\n >\n <Swappable value={subtitle} />\n </p>\n )}\n </div>\n {/* slot=\"close\" is the same dismiss wiring as a Cancel button: one handler, focus back to the trigger. */}\n <AriaButton\n slot=\"close\"\n aria-label={closeLabel}\n data-slot=\"alert-dialog-close\"\n className={alertDialogCloseVariants()}\n >\n <svg\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n >\n <path d=\"m4 4 8 8M12 4l-8 8\" />\n </svg>\n </AriaButton>\n </div>\n <AriaText\n slot=\"description\"\n elementType=\"p\"\n data-slot=\"dialog-description\"\n className=\"m-0 block px-8 py-6 text-sm leading-(--leading-normal) text-foreground\"\n >\n <Swappable value={description} />\n </AriaText>\n {children == null ? null : (\n <div data-slot=\"alert-dialog-actions\" className={alertDialogActionsVariants()}>\n {children}\n </div>\n )}\n </AriaDialog>\n </AriaModal>\n </AriaModalOverlay>\n );\n});\n"],"mappings":";;;;;;;;;;AA4EA,MAAM,6BAA6B,IAAI,yDAAyD;;;;;;;AAQhG,MAAa,oBAAoB,WAC/B,SAAS,kBAAkB,OAAO,KAAK;CACrC,OAAO,oBAACA,UAAD;EAAa;EAAK,MAAK;EAAQ,SAAQ;EAAY,MAAK;EAAK,GAAI;CAAQ,CAAA;AAClF,CACF;;;;;;;;;;;;AAaA,MAAa,oBAAoB,WAC/B,SAAS,kBAAkB,EAAE,WAAW,GAAG,SAAS,KAAK;CACvD,OACE,oBAACA,UAAD;EACO;EACL,SAAQ;EACR,MAAK;EACL,WAAW,MACT,iOACA,SACF;EACA,GAAI;CACL,CAAA;AAEL,CACF;AAEA,MAAM,4BAA4B,IAChC,sFACF;AAEA,MAAM,2BAA2B,IAC/B,qEACF;AAEA,MAAM,2BAA2B,IAAI;CACnC;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;AAGD,SAAS,UAAU,EAAE,SAA+B;CAClD,IAAI,OAAO,UAAU,UAAU,OAAO,oBAAA,YAAA,EAAA,UAAG,MAAQ,CAAA;CACjD,OACE,oBAAC,UAAD;EAAU,SAAS;EAAO,WAAU;EACjC,UAAA;CACO,CAAA;AAEd;AAEA,MAAa,cAAc,WAA0C,SAAS,YAC5E,EACE,OACA,UACA,aACA,aACA,UACA,WACA,aAAa,SACb,kBACA,WAAW,QACX,GAAG,SAEL,KACA;CACA,OACE,oBAACC,cAAD;EACE,GAAI;EACJ,eAAe;EACf,aAAU;EACV,WAAW,MACT,sBAAsB,GACtB,aAAa,UAAU,yBACvB,gBACF;EAEA,UAAA,oBAACC,OAAD;GAAW,WAAU;GACnB,UAAA,qBAACC,QAAD;IACO;IACL,MAAK;IACL,aAAU;IACV,WAAW,MACT,oBAAoB,GAGpB,aACA,aAAa,UAAU,uBACvB,SACF;IAXF,UAAA;KAaG;KACD,qBAAC,OAAD;MAAK,aAAU;MAAsB,WAAW,0BAA0B;MAA1E,UAAA,CACE,qBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CAEE,oBAACC,SAAD;QACE,MAAK;QACL,aAAU;QACV,WAAW,yBAAyB;QAEpC,UAAA,oBAAC,WAAD,EAAW,OAAO,MAAQ,CAAA;OACf,CAAA,GACZ,YAAY,OAAO,OAClB,oBAAC,KAAD;QACE,aAAU;QACV,WAAU;QAEV,UAAA,oBAAC,WAAD,EAAW,OAAO,SAAW,CAAA;OAC5B,CAAA,CAEF;MAEL,CAAA,GAAA,oBAACC,QAAD;OACE,MAAK;OACL,cAAY;OACZ,aAAU;OACV,WAAW,yBAAyB;OAEpC,UAAA,oBAAC,OAAD;QACE,eAAY;QACZ,SAAQ;QACR,MAAK;QACL,QAAO;QACP,aAAY;QACZ,eAAc;QAEd,UAAA,oBAAC,QAAD,EAAM,GAAE,qBAAsB,CAAA;OAC3B,CAAA;MACK,CAAA,CACT;;KACL,oBAACC,MAAD;MACE,MAAK;MACL,aAAY;MACZ,aAAU;MACV,WAAU;MAEV,UAAA,oBAAC,WAAD,EAAW,OAAO,YAAc,CAAA;KACxB,CAAA;KACT,YAAY,OAAO,OAClB,oBAAC,OAAD;MAAK,aAAU;MAAuB,WAAW,2BAA2B;MACzE;KACE,CAAA;IAEG;;EACH,CAAA;CACK,CAAA;AAEtB,CAAC"}
1
+ {"version":3,"file":"alert-dialog.js","names":["Button","AriaModalOverlay","AriaModal","AriaDialog","AriaHeading","AriaButton","AriaText"],"sources":["../../src/alert-dialog/alert-dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, type ReactNode } from \"react\";\nimport {\n Button as AriaButton,\n Dialog as AriaDialog,\n Heading as AriaHeading,\n Modal as AriaModal,\n ModalOverlay as AriaModalOverlay,\n Text as AriaText,\n} from \"react-aria-components\";\n\nimport { Button, type ButtonProps } from \"../button/button.js\";\nimport { cnAll } from \"../lib/cn.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\nimport { dialogOverlayVariants, dialogPanelVariants, type DialogProps } from \"../dialog/dialog.js\";\n\n/**\n * AlertDialog: a confirmation that must be answered.\n *\n * Built from `Dialog`'s own overlay and panel styles and the same React Aria\n * parts, but with its own layout: a header (title, a grey subtitle and a\n * circular close button), a thin divider, the description sentence, an\n * optional `body` of rich content, then the actions.\n * The legacy `alert-dialog.tsx` was a 157-line clone of `dialog.tsx`; this\n * shares the tokens and the motion classes instead, so the two cannot drift\n * on radius, scrim or entrance.\n *\n * Census: 2 files, all eight parts used twice each, `AlertDialogPortal`,\n * `AlertDialogOverlay` and `AlertDialogTrigger` never rendered externally.\n *\n * What the preset forces, and why each one is a defect if forgotten:\n *\n * - `role=\"alertdialog\"`. React Aria associates the description with\n * `aria-describedby` ONLY for this role (useDialog.mjs:29), so a\n * confirmation built from a plain Dialog loses the sentence explaining\n * what is about to happen.\n * - `isDismissable={false}`. Clicking the page must not resolve a\n * destructive confirmation. Escape still cancels, which is the APG\n * behaviour and what Radix does.\n * - THE CLOSE (X) IS CANCEL. It is `slot=\"close\"`, the same dismiss wiring as\n * a Cancel button, so it goes through the same `onOpenChange(false)` and\n * focus returns to the trigger. It is not a third answer: it is the\n * header's copy of the second one.\n * - `description` is REQUIRED. A confirmation with no explanation is the\n * thing this component exists to prevent, and the role only associates it\n * when it is there. It is the sentence under the divider.\n *\n * THE TWO ACTIONS MUST NEVER LOOK ALIKE. `AlertDialogCancel` is the secondary\n * button (white, light border) and cancels through `slot=\"close\"`;\n * `AlertDialogAction` is the primary button painted destructive red, because a\n * confirmation here is a destructive one. The legacy `AlertDialogAction`\n * defaulted to the gradient primary and BOTH call sites overrode it back to\n * destructive by hand (`data-table.tsx:146`, `calendar-view.tsx:638`); the red\n * is the default now, so nobody fights it. Plain `Button`s still work in the\n * actions slot for anything else.\n */\nexport interface AlertDialogProps extends Omit<\n DialogProps,\n \"role\" | \"closeButton\" | \"isDismissable\"\n> {\n /**\n * What is about to happen, and whether it can be undone. Rendered as the\n * sentence under the divider, above the `body` slot (if any) and the actions.\n *\n * **Required**, unlike on `Dialog`. `role=\"alertdialog\"` is what makes\n * React Aria wire this to `aria-describedby`, so it is both the sentence\n * the user needs and the one the screen reader announces with the name.\n */\n description: ReactNode;\n /** A short grey line under the title, in the header. */\n subtitle?: ReactNode;\n /**\n * Rich content between the description and the actions: a plan comparison,\n * a before/after diff, a quoted reply, a form field or a choice to make.\n *\n * It is NOT part of the accessible description: the screen reader announces\n * the one `description` sentence with the name, and the body is read in\n * place or as focus reaches its controls (each with its own label). So the\n * `description` must say everything needed to answer, including that a\n * choice or field below is required (\"Pick what happens to your data.\").\n * `false`, `true` and `\"\"` (as from `cond && <X />`) render no body.\n */\n body?: ReactNode;\n /**\n * A decorative icon at the start of the header, in a round tile beside the\n * title: a warning triangle before a wide-reaching switch, a trash can before\n * a delete. Hidden from assistive technology, so the title must still say\n * what is at stake. Leave it out and the header is as before.\n */\n icon?: ReactNode;\n /**\n * The colour of the icon's tile, from the status tokens: `neutral` (the\n * default), `warning`, `destructive`, `info` or `success`. It paints the tile\n * only, never the actions. Without an `icon` it does nothing. Mirrored to\n * `data-tone` on the tile.\n */\n tone?: \"neutral\" | \"warning\" | \"destructive\" | \"info\" | \"success\";\n /** The actions. Give the cancel button `slot=\"close\"`. */\n children?: ReactNode;\n}\n\nconst alertDialogActionsVariants = cva(\"flex flex-wrap items-center justify-end gap-2 px-8 pb-8\");\n\n/**\n * The body slot: the description's side padding, so the two share an edge,\n * and the description's own bottom space repeated below it before the actions.\n */\nconst alertDialogBodyVariants = cva(\n \"min-w-0 px-8 pb-6 text-sm leading-(--leading-normal) text-foreground\",\n);\n\n/**\n * The cancel action: the secondary button, wired to dismiss the dialog.\n *\n * @example\n * <AlertDialogCancel>Cancel</AlertDialogCancel>\n */\nexport const AlertDialogCancel = forwardRef<HTMLButtonElement, ButtonProps>(\n function AlertDialogCancel(props, ref) {\n return <Button ref={ref} slot=\"close\" variant=\"secondary\" size=\"sm\" {...props} />;\n },\n);\n\n/**\n * The confirm action: the primary button in the destructive red.\n *\n * It is the primary button with its fill, hover, pressed and label roles\n * pointed at the destructive tokens, so it keeps the primary's press motion\n * and shadow. Variables on the element, rather than a competing background\n * class, so no stylesheet order decides which colour wins.\n *\n * @example\n * <AlertDialogAction onPress={remove}>Delete</AlertDialogAction>\n */\nexport const AlertDialogAction = forwardRef<HTMLButtonElement, ButtonProps>(\n function AlertDialogAction({ className, ...props }, ref) {\n return (\n <Button\n ref={ref}\n variant=\"primary\"\n size=\"sm\"\n className={cnAll(\n \"[--color-primary:var(--color-destructive)] [--color-primary-hover:var(--color-destructive-hover)] [--color-primary-pressed:var(--color-destructive-pressed)] [--color-primary-foreground:var(--color-destructive-foreground)]\",\n className,\n )}\n {...props}\n />\n );\n },\n);\n\nconst alertDialogHeaderVariants = cva(\n \"flex items-start gap-4 border-b-(length:--border-width) border-border px-8 pt-8 pb-6\",\n);\n\n/**\n * The header icon's tile: the close button's size, so a one-line title centres\n * on both. Each tone is a subtle tint with the ink the token contrast contract\n * grades on it.\n */\nconst alertDialogIconVariants = cva(\n \"inline-flex size-9 shrink-0 items-center justify-center rounded-circle [&_svg]:pointer-events-none [&_svg]:size-5\",\n {\n variants: {\n tone: {\n neutral: \"bg-muted text-foreground\",\n warning: \"bg-warning-subtle text-warning-foreground\",\n destructive: \"bg-destructive-subtle text-destructive-subtle-foreground\",\n info: \"bg-info-subtle text-info-foreground\",\n success: \"bg-success-subtle text-success-foreground\",\n },\n },\n defaultVariants: { tone: \"neutral\" },\n },\n);\n\n/**\n * The title column. With no subtitle it is at least the close button's height\n * and centres the title in it, so a one-line title sits level with the close;\n * a longer title grows the column from the top as before. With a subtitle the\n * two lines start at the top of the button, as they always have.\n */\nconst alertDialogTitleBlockVariants = cva(\"min-w-0 flex-1\", {\n variants: {\n subtitle: {\n true: \"\",\n false: \"flex min-h-9 flex-col justify-center\",\n },\n },\n});\n\nconst alertDialogTitleVariants = cva(\n \"m-0 text-lg leading-(--leading-tight) font-semibold text-foreground\",\n);\n\nconst alertDialogCloseVariants = cva([\n \"inline-flex size-9 shrink-0 items-center justify-center\",\n \"rounded-circle box-border cursor-pointer\",\n \"border-(length:--border-width) border-border bg-surface p-0 text-text-secondary\",\n // Fill on hover, a quick press to .96 and a spring release (see calendar-nav-motion).\n \"calendar-nav-motion\",\n \"data-[hovered]:bg-muted data-[hovered]:text-foreground\",\n \"outline-none\",\n \"data-[focus-visible]:outline-solid\",\n \"data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"data-[focus-visible]:outline-offset-(--focus-ring-offset)\",\n \"data-[focus-visible]:outline-(--color-ring)\",\n \"[&_svg]:pointer-events-none [&_svg]:size-4\",\n]);\n\n/** A string swaps as a whole when its text changes while the dialog is open. */\nfunction Swappable({ value }: { value: ReactNode }) {\n if (typeof value !== \"string\") return <>{value}</>;\n return (\n <SwapText swapKey={value} className=\"block\">\n {value}\n </SwapText>\n );\n}\n\n/** `cond && <X />` hands over `false`; React renders nothing for it, so neither does the slot. */\nfunction isEmpty(node: ReactNode) {\n return node == null || typeof node === \"boolean\" || node === \"\";\n}\n\nexport const AlertDialog = forwardRef<HTMLElement, AlertDialogProps>(function AlertDialog(\n {\n title,\n subtitle,\n beforeTitle,\n description,\n body,\n icon,\n tone = \"neutral\",\n children,\n className,\n closeLabel = \"Close\",\n overlayClassName,\n entrance = \"rise\",\n ...props\n },\n ref,\n) {\n return (\n <AriaModalOverlay\n {...props}\n isDismissable={false}\n data-slot=\"dialog-overlay\"\n className={cnAll(\n dialogOverlayVariants(),\n entrance === \"rise\" && \"dialog-overlay-motion\",\n overlayClassName,\n )}\n >\n <AriaModal className=\"contents\">\n <AriaDialog\n ref={ref}\n role=\"alertdialog\"\n data-slot=\"dialog\"\n className={cnAll(\n dialogPanelVariants(),\n // The panel has no padding of its own: the header, body and actions pad themselves,\n // so the divider can run edge to edge.\n \"gap-0 p-0\",\n entrance === \"rise\" && \"dialog-panel-motion\",\n className,\n )}\n >\n {beforeTitle}\n <div data-slot=\"alert-dialog-header\" className={alertDialogHeaderVariants()}>\n {isEmpty(icon) ? null : (\n <span\n aria-hidden=\"true\"\n data-slot=\"alert-dialog-icon\"\n data-tone={tone}\n className={alertDialogIconVariants({ tone })}\n >\n {icon}\n </span>\n )}\n <div className={alertDialogTitleBlockVariants({ subtitle: subtitle != null })}>\n {/* slot=\"title\" is what React Aria wires aria-labelledby to. */}\n <AriaHeading\n slot=\"title\"\n data-slot=\"dialog-title\"\n className={alertDialogTitleVariants()}\n >\n <Swappable value={title} />\n </AriaHeading>\n {subtitle == null ? null : (\n <p\n data-slot=\"alert-dialog-subtitle\"\n className=\"m-0 mt-1 text-sm leading-(--leading-normal) text-text-secondary\"\n >\n <Swappable value={subtitle} />\n </p>\n )}\n </div>\n {/* slot=\"close\" is the same dismiss wiring as a Cancel button: one handler, focus back to the trigger. */}\n <AriaButton\n slot=\"close\"\n aria-label={closeLabel}\n data-slot=\"alert-dialog-close\"\n className={alertDialogCloseVariants()}\n >\n <svg\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n >\n <path d=\"m4 4 8 8M12 4l-8 8\" />\n </svg>\n </AriaButton>\n </div>\n <AriaText\n slot=\"description\"\n elementType=\"p\"\n data-slot=\"dialog-description\"\n className=\"m-0 block px-8 py-6 text-sm leading-(--leading-normal) text-foreground\"\n >\n <Swappable value={description} />\n </AriaText>\n {isEmpty(body) ? null : (\n <div data-slot=\"alert-dialog-body\" className={alertDialogBodyVariants()}>\n {body}\n </div>\n )}\n {children == null ? null : (\n <div data-slot=\"alert-dialog-actions\" className={alertDialogActionsVariants()}>\n {children}\n </div>\n )}\n </AriaDialog>\n </AriaModal>\n </AriaModalOverlay>\n );\n});\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { ReactNode } from "react";
1
+ import { ComponentPropsWithoutRef, ReactNode } from "react";
2
2
  //#region src/announcement-bar/announcement-bar.d.ts
3
3
  export interface AnnouncementAction {
4
4
  label: string;
@@ -28,8 +28,15 @@ export interface Announcement {
28
28
  * collapses the bar's height so the page below eases up, and with an `id` the
29
29
  * dismissal is remembered in `localStorage`. While rotation runs the message is
30
30
  * not announced on each change; when it holds, changes are announced politely.
31
+ *
32
+ * Other attributes (`data-*`, `role`, `aria-*`, event handlers, `style`) reach
33
+ * the bar's `<section>`. `role` lets a consumer make the bar a live `alert`
34
+ * or `status`; without it the bar stays a labelled region. `label` remains the
35
+ * accessible name, and `id` is not passed through: it names the remembered
36
+ * dismissal, as before. Handlers for pointer enter and leave, focus and blur
37
+ * run before the bar's own rotation hold.
31
38
  */
32
- export interface AnnouncementBarProps {
39
+ export interface AnnouncementBarProps extends Omit<ComponentPropsWithoutRef<"section">, "id" | "children" | "className"> {
33
40
  messages: Announcement[];
34
41
  /** Remembers dismissal under this id. Change the id to show a new campaign to everyone again. */
35
42
  id?: string;
@@ -51,8 +58,12 @@ export interface AnnouncementBarProps {
51
58
  controls?: boolean;
52
59
  /** @default true */
53
60
  dismissible?: boolean;
54
- /** `inverted` is the foreground colour as the fill. @default "neutral" */
55
- tone?: "neutral" | "inverted";
61
+ /**
62
+ * `inverted` is the foreground colour as the fill. `warning` is the warning
63
+ * tint with its ink and a decorative warning triangle before the message, so
64
+ * the tone is not carried by colour alone. @default "neutral"
65
+ */
66
+ tone?: "neutral" | "inverted" | "warning";
56
67
  onAction?: (announcement: Announcement) => void;
57
68
  onCountdownEnd?: (announcement: Announcement) => void;
58
69
  /** Accessible name of the region. @default "Announcements" */
@@ -123,7 +123,7 @@ const Glyph = ({ path, size = 16 }) => /* @__PURE__ */ jsx("svg", {
123
123
  "aria-hidden": "true",
124
124
  children: /* @__PURE__ */ jsx("path", { d: path })
125
125
  });
126
- const AnnouncementBar = forwardRef(function AnnouncementBar({ messages, id, open: openProp, defaultOpen = true, onOpenChange, index: indexProp, defaultIndex = 0, onIndexChange, interval = 6e3, autoPlay = true, controls = false, dismissible = true, tone = "neutral", onAction, onCountdownEnd, label = "Announcements", className }, ref) {
126
+ const AnnouncementBar = forwardRef(function AnnouncementBar({ messages, id, open: openProp, defaultOpen = true, onOpenChange, index: indexProp, defaultIndex = 0, onIndexChange, interval = 6e3, autoPlay = true, controls = false, dismissible = true, tone = "neutral", onAction, onCountdownEnd, label = "Announcements", className, style, onPointerEnter, onPointerLeave, onFocus, onBlur, ...rest }, ref) {
127
127
  const uid = useId();
128
128
  const reduced = useReducedMotion();
129
129
  const total = messages.length;
@@ -238,6 +238,7 @@ const AnnouncementBar = forwardRef(function AnnouncementBar({ messages, id, open
238
238
  },
239
239
  className: "alert-presence announcement-frame",
240
240
  children: /* @__PURE__ */ jsxs("section", {
241
+ ...rest,
241
242
  ref: barRef,
242
243
  "data-slot": "announcement-bar",
243
244
  "data-tone": tone,
@@ -245,11 +246,24 @@ const AnnouncementBar = forwardRef(function AnnouncementBar({ messages, id, open
245
246
  "aria-roledescription": total > 1 ? "carousel" : void 0,
246
247
  "aria-hidden": leaving || void 0,
247
248
  className: cn("announcement-bar", className),
248
- style: { "--controls": controlCount },
249
- onPointerEnter: (event) => event.pointerType === "mouse" && setHovered(true),
250
- onPointerLeave: () => setHovered(false),
251
- onFocus: (event) => setFocused(event.target.matches(":focus-visible") && !event.target.closest("[data-pause]")),
249
+ style: {
250
+ ...style,
251
+ "--controls": controlCount
252
+ },
253
+ onPointerEnter: (event) => {
254
+ onPointerEnter?.(event);
255
+ if (event.pointerType === "mouse") setHovered(true);
256
+ },
257
+ onPointerLeave: (event) => {
258
+ onPointerLeave?.(event);
259
+ setHovered(false);
260
+ },
261
+ onFocus: (event) => {
262
+ onFocus?.(event);
263
+ setFocused(event.target.matches(":focus-visible") && !event.target.closest("[data-pause]"));
264
+ },
252
265
  onBlur: (event) => {
266
+ onBlur?.(event);
253
267
  if (!event.currentTarget.contains(event.relatedTarget)) setFocused(false);
254
268
  },
255
269
  children: [
@@ -274,7 +288,18 @@ const AnnouncementBar = forwardRef(function AnnouncementBar({ messages, id, open
274
288
  children: /* @__PURE__ */ jsxs("p", {
275
289
  className: "announcement-message",
276
290
  children: [
277
- /* @__PURE__ */ jsx("span", { children: announcement.message }),
291
+ /* @__PURE__ */ jsxs("span", { children: [tone === "warning" ? /* @__PURE__ */ jsx("svg", {
292
+ "data-slot": "announcement-tone-icon",
293
+ viewBox: "0 0 16 16",
294
+ fill: "none",
295
+ stroke: "currentColor",
296
+ strokeWidth: 1.75,
297
+ strokeLinecap: "round",
298
+ strokeLinejoin: "round",
299
+ "aria-hidden": "true",
300
+ className: "announcement-tone-icon",
301
+ children: /* @__PURE__ */ jsx("path", { d: "M8 2.5 14.5 13.5h-13ZM8 6.5v3M8 11.5h.01" })
302
+ }) : null, announcement.message] }),
278
303
  countdown ? /* @__PURE__ */ jsx(Countdown, {
279
304
  to: countdown.to,
280
305
  label: countdown.label,