@vamos-ui/react 0.2.0 → 0.4.0

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 (166) hide show
  1. package/dist/blocks/block-image.js +1 -1
  2. package/dist/blocks/block-link.js +1 -1
  3. package/dist/blocks/index.d.ts +2 -2
  4. package/dist/blocks/index.js +2 -2
  5. package/dist/blocks/site/_kit-1.d.ts +1 -0
  6. package/dist/blocks/site/_kit-1.js +3 -1
  7. package/dist/blocks/site/announcement-bar.js +1 -1
  8. package/dist/blocks/site/article-layout.d.ts +1 -0
  9. package/dist/blocks/site/article-layout.js +2 -1
  10. package/dist/blocks/site/blog-list.d.ts +1 -0
  11. package/dist/blocks/site/blog-list.js +3 -2
  12. package/dist/blocks/site/contact-dock.js +3 -1
  13. package/dist/blocks/site/contact-form.js +1 -1
  14. package/dist/blocks/site/cta.d.ts +2 -2
  15. package/dist/blocks/site/cta.js +8 -5
  16. package/dist/blocks/site/faq.js +2 -1
  17. package/dist/blocks/site/feature-grid.d.ts +1 -0
  18. package/dist/blocks/site/feature-grid.js +7 -5
  19. package/dist/blocks/site/footer.d.ts +1 -1
  20. package/dist/blocks/site/footer.js +8 -7
  21. package/dist/blocks/site/gallery-section.d.ts +6 -1
  22. package/dist/blocks/site/gallery-section.js +6 -4
  23. package/dist/blocks/site/hero.d.ts +3 -2
  24. package/dist/blocks/site/hero.js +14 -6
  25. package/dist/blocks/site/logo-strip.d.ts +6 -3
  26. package/dist/blocks/site/logo-strip.js +8 -7
  27. package/dist/blocks/site/map-hours.d.ts +2 -2
  28. package/dist/blocks/site/map-hours.js +4 -2
  29. package/dist/blocks/site/media-text.d.ts +4 -1
  30. package/dist/blocks/site/media-text.js +6 -4
  31. package/dist/blocks/site/menu-list.d.ts +7 -3
  32. package/dist/blocks/site/menu-list.js +34 -7
  33. package/dist/blocks/site/navbar.d.ts +1 -0
  34. package/dist/blocks/site/navbar.js +6 -5
  35. package/dist/blocks/site/not-found.js +2 -1
  36. package/dist/blocks/site/page-intro.d.ts +1 -0
  37. package/dist/blocks/site/page-intro.js +2 -1
  38. package/dist/blocks/site/pricing-table.js +2 -1
  39. package/dist/blocks/site/process.d.ts +1 -0
  40. package/dist/blocks/site/process.js +4 -2
  41. package/dist/blocks/site/registry-1.d.ts +1 -1
  42. package/dist/blocks/site/registry-2.d.ts +5 -0
  43. package/dist/blocks/site/registry-2.js +2 -2
  44. package/dist/blocks/site/rich-text-section.js +2 -1
  45. package/dist/blocks/site/shared/heading-lines.d.ts +9 -0
  46. package/dist/blocks/site/shared/heading-lines.js +32 -0
  47. package/dist/blocks/site/shared/image-sizes.d.ts +25 -0
  48. package/dist/blocks/site/shared/image-sizes.js +37 -0
  49. package/dist/blocks/site/shared/island-keys.d.ts +1 -1
  50. package/dist/blocks/site/shared/island-keys.js +1 -1
  51. package/dist/blocks/site/shared/labels-2.d.ts +9 -1
  52. package/dist/blocks/site/shared/labels-2.js +24 -6
  53. package/dist/blocks/site/shared/open-now.d.ts +1 -1
  54. package/dist/blocks/site/shared/open-now.js +1 -1
  55. package/dist/blocks/site/shared/opening-hours.d.ts +18 -2
  56. package/dist/blocks/site/shared/opening-hours.js +36 -7
  57. package/dist/blocks/site/shared/tap.d.ts +9 -0
  58. package/dist/blocks/site/shared/tap.js +16 -0
  59. package/dist/blocks/site/site-shell.d.ts +11 -0
  60. package/dist/blocks/site/site-shell.js +43 -16
  61. package/dist/blocks/site/stats-band.d.ts +3 -1
  62. package/dist/blocks/site/stats-band.js +11 -7
  63. package/dist/blocks/site/team.d.ts +1 -1
  64. package/dist/blocks/site/team.js +4 -3
  65. package/dist/blocks/site/testimonials.d.ts +2 -2
  66. package/dist/blocks/site/testimonials.js +5 -4
  67. package/dist/business/index.d.ts +1 -0
  68. package/dist/business/index.js +2 -0
  69. package/dist/business/signer-status.d.ts +53 -0
  70. package/dist/business/signer-status.js +76 -0
  71. package/dist/charts/bar-chart.js +6 -2
  72. package/dist/charts/chart-shell.d.ts +9 -2
  73. package/dist/charts/chart-shell.js +10 -4
  74. package/dist/charts/chart.js +1 -1
  75. package/dist/charts/combo-chart.js +5 -7
  76. package/dist/charts/utils.d.ts +10 -0
  77. package/dist/charts/utils.js +46 -0
  78. package/dist/components/_qr-core.d.ts +26 -0
  79. package/dist/components/_qr-core.js +376 -0
  80. package/dist/components/activity-timeline.js +5 -5
  81. package/dist/components/aspect-ratio.d.ts +26 -0
  82. package/dist/components/aspect-ratio.js +42 -0
  83. package/dist/components/backup-codes.d.ts +42 -0
  84. package/dist/components/backup-codes.js +67 -0
  85. package/dist/components/breadcrumb.d.ts +2 -2
  86. package/dist/components/breadcrumb.js +1 -1
  87. package/dist/components/carousel.d.ts +12 -3
  88. package/dist/components/carousel.js +8 -5
  89. package/dist/components/collapsible.d.ts +36 -0
  90. package/dist/components/collapsible.js +37 -0
  91. package/dist/components/command.d.ts +8 -2
  92. package/dist/components/command.js +7 -2
  93. package/dist/components/image.d.ts +18 -2
  94. package/dist/components/image.js +12 -5
  95. package/dist/components/input.d.ts +1 -1
  96. package/dist/components/input.js +1 -1
  97. package/dist/components/menubar.d.ts +96 -0
  98. package/dist/components/menubar.js +123 -0
  99. package/dist/components/pin-map.d.ts +58 -0
  100. package/dist/components/pin-map.js +167 -0
  101. package/dist/components/placeholder-art.d.ts +51 -0
  102. package/dist/components/placeholder-art.js +82 -0
  103. package/dist/components/qr-code.d.ts +36 -0
  104. package/dist/components/qr-code.js +27 -0
  105. package/dist/components/record-detail-layout.d.ts +2 -2
  106. package/dist/components/record-detail-layout.js +1 -1
  107. package/dist/components/sidebar.js +8 -8
  108. package/dist/components/sla-timer.d.ts +75 -0
  109. package/dist/components/sla-timer.js +142 -0
  110. package/dist/components/slug-input.js +1 -1
  111. package/dist/components/tour.d.ts +60 -0
  112. package/dist/components/tour.js +139 -0
  113. package/dist/index.d.ts +9 -0
  114. package/dist/index.js +9 -0
  115. package/dist/lib/public.d.ts +1 -0
  116. package/dist/lib/public.js +1 -0
  117. package/dist/lib/value-format.js +1 -1
  118. package/dist/locale-data/core/holidays.d.ts +1 -1
  119. package/dist/locale-data/core/holidays.js +1 -1
  120. package/dist/locales/ar.d.ts +2087 -0
  121. package/dist/locales/ar.js +2088 -0
  122. package/dist/locales/de.d.ts +2087 -0
  123. package/dist/locales/de.js +2088 -0
  124. package/dist/locales/en.d.ts +1 -0
  125. package/dist/locales/en.js +1 -0
  126. package/dist/locales/es.d.ts +2087 -0
  127. package/dist/locales/es.js +2087 -0
  128. package/dist/locales/fr.d.ts +2087 -0
  129. package/dist/locales/fr.js +2088 -0
  130. package/dist/locales/tr.d.ts +1 -0
  131. package/dist/locales/tr.js +1 -0
  132. package/dist/media/gallery.js +3 -1
  133. package/dist/media/media-library.js +1 -1
  134. package/dist/motion/core.d.ts +32 -0
  135. package/dist/motion/core.js +77 -0
  136. package/dist/motion/count-up.d.ts +29 -0
  137. package/dist/motion/count-up.js +92 -0
  138. package/dist/motion/index.d.ts +11 -0
  139. package/dist/motion/index.js +13 -0
  140. package/dist/motion/motion-scope.d.ts +17 -0
  141. package/dist/motion/motion-scope.js +16 -0
  142. package/dist/motion/page-transition.d.ts +33 -0
  143. package/dist/motion/page-transition.js +61 -0
  144. package/dist/motion/parallax.d.ts +37 -0
  145. package/dist/motion/parallax.js +90 -0
  146. package/dist/motion/pointer.d.ts +38 -0
  147. package/dist/motion/pointer.js +117 -0
  148. package/dist/motion/reveal.d.ts +72 -0
  149. package/dist/motion/reveal.js +133 -0
  150. package/dist/motion/scroll-shrink.d.ts +18 -0
  151. package/dist/motion/scroll-shrink.js +47 -0
  152. package/dist/motion/skeleton-swap.d.ts +17 -0
  153. package/dist/motion/skeleton-swap.js +43 -0
  154. package/dist/motion/text-reveal.d.ts +32 -0
  155. package/dist/motion/text-reveal.js +31 -0
  156. package/dist/motion/ticker.d.ts +35 -0
  157. package/dist/motion/ticker.js +16 -0
  158. package/dist/render/block-renderer.js +1 -1
  159. package/dist/sortable/sortable-tree.d.ts +4 -0
  160. package/dist/sortable/sortable-tree.js +16 -4
  161. package/dist/styles/blocks.css +21 -12
  162. package/dist/styles/editors.css +2 -1
  163. package/dist/styles/media.css +31 -5
  164. package/dist/styles/motion.css +389 -0
  165. package/dist/styles.css +41 -10
  166. package/package.json +5 -1
@@ -27,6 +27,7 @@ const carouselLabels = {
27
27
  thumbs: "Slide thumbnails",
28
28
  empty: "Nothing to show yet",
29
29
  loading: "Loading slides",
30
+ marqueePause: "Pause scrolling",
30
31
  };
31
32
  const CarouselContext = React.createContext(null);
32
33
  function useCarouselContext(part) {
@@ -338,11 +339,13 @@ export const CarouselCounter = React.forwardRef(function CarouselCounter({ class
338
339
  return (_jsxs("span", { ref: ref, "data-slot": "carousel-counter", className: cn("tabular text-[13px] text-fg-muted", className), ...props, children: [_jsx("span", { className: "sr-only", children: c.t("counter", { index: c.nf.format(c.index + 1), total: c.nf.format(c.count) }) }), _jsxs("span", { "aria-hidden": true, children: [_jsx("span", { className: "font-medium text-fg", children: c.nf.format(c.index + 1) }), _jsx("span", { className: "mx-1 text-fg-subtle", children: "/" }), c.nf.format(c.count)] })] }));
339
340
  });
340
341
  /**
341
- * Endless logo band in pure CSS: no JS, renders with SSR. The items are listed once for assistive
342
- * tech; a duplicate track (aria-hidden) closes the loop. Reduced motion: static, scrollable row.
342
+ * Endless logo band in pure CSS: renders with SSR. The items are listed once for assistive tech;
343
+ * a duplicate track (`aria-hidden` + `inert`, so its links never take focus) closes the loop.
344
+ * Pauses on hover/focus and with its pause control. Reduced motion: static, scrollable row.
343
345
  */
344
- export function Marquee({ label, duration = 40, reverse, pauseOnHover = true, fade = true, gap = "lg", className, style, children, ...props }) {
346
+ export function Marquee({ label, duration = 40, reverse, pauseOnHover = true, fade = true, gap = "lg", controls = true, pauseLabel, className, style, children, ...props }) {
347
+ const t = useLabels("carousel", carouselLabels, pauseLabel ? { marqueePause: pauseLabel } : undefined);
345
348
  const items = React.Children.toArray(children);
346
- const track = (hidden) => (_jsx("ul", { "aria-hidden": hidden || undefined, "aria-label": hidden ? undefined : label, className: "vui-marquee-track flex shrink-0 items-center", children: items.map((child, i) => (_jsx("li", { className: "flex shrink-0 items-center", children: child }, i))) }));
347
- return (_jsxs("div", { "data-slot": "marquee", "data-reverse": reverse ? "" : undefined, "data-pause-on-hover": pauseOnHover ? "" : undefined, "data-fade": fade ? "" : undefined, className: cn("vui-marquee flex overflow-hidden", className), style: { "--marquee-duration": `${duration}s`, "--marquee-gap": GAP[gap], ...style }, ...props, children: [track(false), track(true)] }));
349
+ const track = (hidden) => (_jsx("ul", { "aria-hidden": hidden || undefined, inert: hidden || undefined, "aria-label": hidden ? undefined : label, className: "vui-marquee-track flex shrink-0 items-center", children: items.map((child, i) => (_jsx("li", { className: "flex shrink-0 items-center", children: child }, i))) }));
350
+ return (_jsxs("div", { "data-slot": "marquee", "data-reverse": reverse ? "" : undefined, "data-pause-on-hover": pauseOnHover ? "" : undefined, "data-fade": fade ? "" : undefined, "data-controls": controls ? "" : undefined, className: cn("vui-marquee relative", className), style: { "--marquee-duration": `${duration}s`, "--marquee-gap": GAP[gap], ...style }, ...props, children: [_jsxs("div", { className: "vui-marquee-viewport flex overflow-hidden", children: [track(false), track(true)] }), controls ? (_jsxs("label", { className: "vui-marquee-toggle", children: [_jsx("input", { type: "checkbox", className: "vui-marquee-pause sr-only", "aria-label": t("marqueePause") }), _jsx(Pause, { "aria-hidden": true, className: "vui-marquee-icon-pause" }), _jsx(Play, { "aria-hidden": true, className: "vui-marquee-icon-play" })] })) : null] }));
348
351
  }
@@ -0,0 +1,36 @@
1
+ import * as React from "react";
2
+ import { Collapsible as CollapsiblePrimitive } from "radix-ui";
3
+ export interface CollapsibleProps extends React.ComponentPropsWithoutRef<typeof CollapsiblePrimitive.Root> {
4
+ }
5
+ /**
6
+ * One show/hide region with its trigger (WAI-ARIA disclosure). Controlled (`open` / `onOpenChange`)
7
+ * or uncontrolled (`defaultOpen`). For several sections that coordinate, use Accordion.
8
+ *
9
+ * ```tsx
10
+ * <Collapsible>
11
+ * <CollapsibleTrigger>Advanced settings</CollapsibleTrigger>
12
+ * <CollapsibleContent>…</CollapsibleContent>
13
+ * </Collapsible>
14
+ * ```
15
+ */
16
+ export declare const Collapsible: React.ForwardRefExoticComponent<CollapsibleProps & React.RefAttributes<HTMLDivElement>>;
17
+ export interface CollapsibleTriggerProps extends React.ComponentPropsWithoutRef<typeof CollapsiblePrimitive.Trigger> {
18
+ /** Trailing chevron that turns when open. Default `true` (ignored with `asChild`). */
19
+ chevron?: boolean;
20
+ /** Leading icon. */
21
+ icon?: React.ReactNode;
22
+ /** Right-side summary while closed, e.g. "4 fields". Part of the button's name. */
23
+ meta?: React.ReactNode;
24
+ }
25
+ /**
26
+ * The toggle button (`aria-expanded`, `aria-controls`). Styled as a quiet full-width row; pass
27
+ * `asChild` to make any button (a Button, a link-styled text) the trigger instead.
28
+ */
29
+ export declare const CollapsibleTrigger: React.ForwardRefExoticComponent<CollapsibleTriggerProps & React.RefAttributes<HTMLButtonElement>>;
30
+ export interface CollapsibleContentProps extends React.ComponentPropsWithoutRef<typeof CollapsiblePrimitive.Content> {
31
+ }
32
+ /**
33
+ * The hidden region. Animates its height (`ui-collapse`); no animation with
34
+ * `prefers-reduced-motion` or inside `data-motion="off"`. `forceMount` keeps it in the DOM while closed.
35
+ */
36
+ export declare const CollapsibleContent: React.ForwardRefExoticComponent<CollapsibleContentProps & React.RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,37 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Collapsible as CollapsiblePrimitive } from "radix-ui";
5
+ import { ChevronDown } from "lucide-react";
6
+ import { cn } from "../lib/cn.js";
7
+ /**
8
+ * One show/hide region with its trigger (WAI-ARIA disclosure). Controlled (`open` / `onOpenChange`)
9
+ * or uncontrolled (`defaultOpen`). For several sections that coordinate, use Accordion.
10
+ *
11
+ * ```tsx
12
+ * <Collapsible>
13
+ * <CollapsibleTrigger>Advanced settings</CollapsibleTrigger>
14
+ * <CollapsibleContent>…</CollapsibleContent>
15
+ * </Collapsible>
16
+ * ```
17
+ */
18
+ export const Collapsible = React.forwardRef(function Collapsible({ className, ...props }, ref) {
19
+ return _jsx(CollapsiblePrimitive.Root, { ref: ref, "data-slot": "collapsible", className: cn("group/collapsible", className), ...props });
20
+ });
21
+ /**
22
+ * The toggle button (`aria-expanded`, `aria-controls`). Styled as a quiet full-width row; pass
23
+ * `asChild` to make any button (a Button, a link-styled text) the trigger instead.
24
+ */
25
+ export const CollapsibleTrigger = React.forwardRef(function CollapsibleTrigger({ className, chevron = true, icon, meta, asChild, children, ...props }, ref) {
26
+ if (asChild) {
27
+ return (_jsx(CollapsiblePrimitive.Trigger, { ref: ref, "data-slot": "collapsible-trigger", asChild: true, className: className, ...props, children: children }));
28
+ }
29
+ return (_jsxs(CollapsiblePrimitive.Trigger, { ref: ref, "data-slot": "collapsible-trigger", className: cn("group/collapsible-trigger flex w-full min-w-0 items-center gap-2.5 rounded-ui-sm py-2 text-start text-sm font-medium text-fg outline-none transition-colors focus-ring", "hover:[&_[data-slot=collapsible-title]]:underline hover:[&_[data-slot=collapsible-title]]:decoration-line hover:[&_[data-slot=collapsible-title]]:underline-offset-4", "disabled:cursor-not-allowed disabled:opacity-45", "[&_svg]:size-4 [&_svg]:shrink-0", className), ...props, children: [icon ? (_jsx("span", { "aria-hidden": true, className: "inline-flex shrink-0 text-fg-muted", children: icon })) : null, _jsx("span", { "data-slot": "collapsible-title", className: "min-w-0 flex-1 text-pretty", children: children }), meta ? _jsx("span", { className: "sr-only", children: ", " }) : null, meta ? (_jsx("span", { "data-slot": "collapsible-meta", className: "tabular shrink-0 truncate text-[13px] font-normal text-fg-subtle", children: meta })) : null, chevron ? (_jsx(ChevronDown, { "aria-hidden": true, className: "text-fg-subtle transition-transform duration-200 group-data-[state=open]/collapsible-trigger:rotate-180 motion-reduce:transition-none in-data-[motion=off]:transition-none" })) : null] }));
30
+ });
31
+ /**
32
+ * The hidden region. Animates its height (`ui-collapse`); no animation with
33
+ * `prefers-reduced-motion` or inside `data-motion="off"`. `forceMount` keeps it in the DOM while closed.
34
+ */
35
+ export const CollapsibleContent = React.forwardRef(function CollapsibleContent({ className, ...props }, ref) {
36
+ return _jsx(CollapsiblePrimitive.Content, { ref: ref, "data-slot": "collapsible-content", className: cn("ui-collapse in-data-[motion=off]:animate-none", className), ...props });
37
+ });
@@ -102,7 +102,10 @@ export interface CommandItemProps extends React.ComponentPropsWithoutRef<typeof
102
102
  icon?: React.ReactNode;
103
103
  /** Second line under the label. */
104
104
  description?: React.ReactNode;
105
- /** `"mod+shift+n"` or `["mod","shift","n"]` — display only. */
105
+ /**
106
+ * `"mod+shift+n"` or `["mod","shift","n"]` — display only. An array without modifier keys is a
107
+ * key sequence (`["g","o"]` shows G then O).
108
+ */
106
109
  shortcut?: string | string[];
107
110
  /** Trailing meta (an app badge, a count). */
108
111
  badge?: React.ReactNode;
@@ -113,7 +116,10 @@ export interface CommandItemProps extends React.ComponentPropsWithoutRef<typeof
113
116
  }
114
117
  /** One selectable row. Highlighted with the arrow keys; Enter or click runs `onSelect`. */
115
118
  export declare const CommandItem: React.ForwardRefExoticComponent<CommandItemProps & React.RefAttributes<HTMLDivElement>>;
116
- /** Shortcut keycaps at the end of an item. */
119
+ /**
120
+ * Shortcut keycaps at the end of an item. A string or an array with modifiers is one combination
121
+ * (`"mod+k"`, `["mod","k"]`); an array of plain keys is a sequence (`["g","o"]` → G O).
122
+ */
117
123
  export declare function CommandShortcut({ keys, className }: {
118
124
  keys: string | string[];
119
125
  className?: string;
@@ -87,9 +87,14 @@ export const CommandSeparator = React.forwardRef(function CommandSeparator({ cla
87
87
  export const CommandItem = React.forwardRef(function CommandItem({ className, icon, description, shortcut, badge, chevron, loading, children, ...props }, ref) {
88
88
  return (_jsxs(Cmdk.Item, { ref: ref, "data-slot": "command-item", "aria-busy": loading || undefined, className: cn(itemVariants({ size: "md" }), "min-h-[calc(var(--control-h)*0.95)] gap-2.5 px-2.5 data-[selected=true]:bg-selected", className), ...props, children: [loading ? (_jsx(Spinner, { size: "xs", className: "shrink-0" })) : icon ? (_jsx("span", { "aria-hidden": true, className: cn("flex shrink-0 items-center text-fg-muted", description && "self-start pt-0.5"), children: icon })) : null, _jsxs("span", { className: itemSlots.label, children: [children, description ? _jsx("span", { className: cn(itemSlots.description, "mt-0.5 truncate"), children: description }) : null] }), badge != null ? _jsx("span", { className: "ms-auto flex shrink-0 items-center ps-3", children: badge }) : null, shortcut ? _jsx(CommandShortcut, { keys: shortcut, className: badge == null ? "ms-auto" : undefined }) : null, chevron ? _jsx(ChevronRight, { "aria-hidden": true, className: cn("size-3.5! shrink-0 text-fg-subtle rtl:-scale-x-100", !shortcut && badge == null && "ms-auto") }) : null] }));
89
89
  });
90
- /** Shortcut keycaps at the end of an item. */
90
+ const MODIFIER_KEYS = /^(mod|ctrl|control|meta|cmd|command|alt|option|shift)$/i;
91
+ /**
92
+ * Shortcut keycaps at the end of an item. A string or an array with modifiers is one combination
93
+ * (`"mod+k"`, `["mod","k"]`); an array of plain keys is a sequence (`["g","o"]` → G O).
94
+ */
91
95
  export function CommandShortcut({ keys, className }) {
92
- return _jsx(KbdGroup, { keys: Array.isArray(keys) ? keys.join("+") : keys, size: "sm", variant: "ghost", className: cn("shrink-0 ps-3", className) });
96
+ const sequence = Array.isArray(keys) && keys.length > 1 && !keys.some((k) => MODIFIER_KEYS.test(k));
97
+ return (_jsx(KbdGroup, { keys: sequence ? keys : Array.isArray(keys) ? keys.join("+") : keys, size: "sm", variant: "ghost", className: cn("shrink-0 ps-3", className) }));
93
98
  }
94
99
  /**
95
100
  * Command inside a top-anchored dialog (fullscreen on phones). Focus goes to the input; Esc closes
@@ -1,6 +1,7 @@
1
1
  import * as React from "react";
2
2
  import { type VariantProps } from "class-variance-authority";
3
3
  import type { CropRect, ImageLoader, MediaAsset } from "../media/types.js";
4
+ import { type PlaceholderArtPattern } from "./placeholder-art.js";
4
5
  export type ImageAspect = "auto" | "1/1" | "4/3" | "3/2" | "16/9" | "21/9" | "4/5" | "1.91/1" | number;
5
6
  /** Default `srcset` steps. */
6
7
  export declare const IMAGE_WIDTHS: number[];
@@ -57,7 +58,7 @@ declare const imageLabels: {
57
58
  error: string;
58
59
  };
59
60
  export type ImageLabels = typeof imageLabels;
60
- export type ImagePlaceholder = "blur" | "color" | "skeleton" | "none";
61
+ export type ImagePlaceholder = "blur" | "color" | "skeleton" | "art" | "none";
61
62
  export interface ImageBaseProps extends Omit<React.ImgHTMLAttributes<HTMLImageElement>, "src" | "placeholder" | "width" | "height" | "alt">, Omit<VariantProps<typeof imageVariants>, "fit"> {
62
63
  width?: number;
63
64
  height?: number;
@@ -77,8 +78,20 @@ export interface ImageBaseProps extends Omit<React.ImgHTMLAttributes<HTMLImageEl
77
78
  quality?: number;
78
79
  /** Above the fold: eager loading + `fetchpriority="high"`. */
79
80
  priority?: boolean;
80
- /** While loading. Default: `blur` with a blur source, `color` with a dominant colour, else `skeleton`. */
81
+ /**
82
+ * While loading. Default: `blur` with a blur source, `color` with a dominant colour, else `skeleton`.
83
+ * `art` shows a seeded token pattern (PlaceholderArt) while loading and on error. Without a `src`
84
+ * the image always renders as art.
85
+ */
81
86
  placeholder?: ImagePlaceholder;
87
+ /** Seed for the art placeholder. Default: `alt`, then the URL. Same seed → same pattern. */
88
+ seed?: string;
89
+ /** Force the art pattern. Default `"auto"` (from the seed). */
90
+ placeholderPattern?: PlaceholderArtPattern | "auto";
91
+ /** Glyph in the art placeholder. `false` hides it. */
92
+ placeholderIcon?: React.ReactNode | false;
93
+ /** Print `alt` (or this node) as a small caption on the art placeholder. */
94
+ placeholderCaption?: boolean | React.ReactNode;
82
95
  /** Tiny blurred preview (LQIP data URL or a small URL). Defaults to `asset.placeholder`. */
83
96
  blurDataURL?: string;
84
97
  /** Placeholder colour. Defaults to `asset.dominantColor`. */
@@ -103,6 +116,9 @@ export type ImageProps = ImageBaseProps & ({
103
116
  } | {
104
117
  src: MediaAsset;
105
118
  alt?: string;
119
+ } | {
120
+ src?: null | "";
121
+ alt: string;
106
122
  });
107
123
  /**
108
124
  * The image primitive: no layout shift (width/height + `aspect-ratio`), focal point →
@@ -9,6 +9,7 @@ import { DefaultImage, useImage, useMediaAdapter } from "../lib/provider.js";
9
9
  import { useComposedRefs } from "../lib/compose-refs.js";
10
10
  import { useIsomorphicLayoutEffect } from "../lib/use-controllable-state.js";
11
11
  import { Spinner } from "./spinner.js";
12
+ import { PlaceholderArt } from "./placeholder-art.js";
12
13
  /** Default `srcset` steps. */
13
14
  export const IMAGE_WIDTHS = [320, 480, 640, 768, 1024, 1280, 1600, 1920];
14
15
  /** `"16/9"` → 1.777…, `1.5` → 1.5, `"auto"` → the intrinsic ratio (or `undefined`). */
@@ -107,9 +108,10 @@ const FILENAME_ALT = /(^(img|dsc|pxl|screenshot)[-_ ]?\d+)|\.(jpe?g|png|gif|webp
107
108
  * <Image src={asset} alt="Lobby of the Kadıköy office" aspect="16/9" sizes="(min-width: 1024px) 50vw, 100vw" rounded="ui" />
108
109
  * ```
109
110
  */
110
- export const Image = React.forwardRef(function Image({ src: source, alt: altProp, width: widthProp, height: heightProp, aspect = "auto", fit = "cover", focal: focalProp, crop, sizes, widths, quality, priority, placeholder: placeholderProp, blurDataURL, color: colorProp, loader, fallback, processing: processingProp, rounded, frame, className, imgClassName, style, labels, onLoad, onError, loading, decoding, ...props }, forwardedRef) {
111
+ export const Image = React.forwardRef(function Image({ src: source, alt: altProp, width: widthProp, height: heightProp, aspect = "auto", fit = "cover", focal: focalProp, crop, sizes, widths, quality, priority, placeholder: placeholderProp, blurDataURL, seed, placeholderPattern, placeholderIcon, placeholderCaption, color: colorProp, loader, fallback, processing: processingProp, rounded, frame, className, imgClassName, style, labels, onLoad, onError, loading, decoding, ...props }, forwardedRef) {
111
112
  const t = useLabels("image", imageLabels, labels);
112
113
  const Bridge = useImage();
114
+ const empty = !source || (typeof source === "string" && !source.trim());
113
115
  const asset = isAsset(source) ? source : undefined;
114
116
  const alt = altProp ?? asset?.alt ?? "";
115
117
  const width = widthProp ?? asset?.width;
@@ -118,9 +120,10 @@ export const Image = React.forwardRef(function Image({ src: source, alt: altProp
118
120
  const blur = blurDataURL ?? asset?.placeholder;
119
121
  const color = colorProp ?? asset?.dominantColor;
120
122
  const processing = processingProp ?? asset?.status === "processing";
121
- const placeholder = placeholderProp ?? (blur ? "blur" : color ? "color" : "skeleton");
122
- const ratio = aspectRatioOf(aspect, width, height);
123
- const sources = useImageSources(source, { sizes, widths, loader, quality, focal, crop });
123
+ const placeholder = empty ? "art" : placeholderProp ?? (blur ? "blur" : color ? "color" : "skeleton");
124
+ // An empty image has no intrinsic size: fall back to 4:3 so it never collapses.
125
+ const ratio = aspectRatioOf(aspect, width, height) ?? (empty ? 4 / 3 : undefined);
126
+ const sources = useImageSources(source || "", { sizes, widths, loader, quality, focal, crop });
124
127
  const [state, setState] = React.useState("loading");
125
128
  const imgRef = React.useRef(null);
126
129
  const ref = useComposedRefs(forwardedRef, imgRef);
@@ -138,6 +141,10 @@ export const Image = React.forwardRef(function Image({ src: source, alt: altProp
138
141
  if (process.env.NODE_ENV !== "production" && alt && FILENAME_ALT.test(alt.trim())) {
139
142
  console.warn(`Image: alt text "${alt}" looks like a file name. Describe the image instead, or pass alt="" if it is decorative.`);
140
143
  }
144
+ const art = (decorative) => (_jsx(PlaceholderArt, { "data-slot": "image-art", seed: seed ?? (alt || (typeof source === "string" ? source : asset?.id) || ""), pattern: placeholderPattern, icon: placeholderIcon, caption: placeholderCaption === true ? alt || undefined : placeholderCaption, label: decorative ? undefined : alt || undefined, className: "absolute inset-0" }));
145
+ if (empty) {
146
+ return (_jsx("span", { "data-slot": "image", "data-state": "empty", "data-placeholder": "art", className: cn(imageVariants({ rounded, frame }), "bg-transparent", className), style: { aspectRatio: String(ratio), ...style }, children: art(false) }));
147
+ }
141
148
  const objectPosition = focalToObjectPosition(focal);
142
149
  const positioned = ratio !== undefined;
143
150
  const imgClasses = cn("vui-image-img block", positioned ? "absolute inset-0 size-full" : "h-auto w-full", fit === "contain" ? "object-contain" : "object-cover", imgClassName);
@@ -156,7 +163,7 @@ export const Image = React.forwardRef(function Image({ src: source, alt: altProp
156
163
  ...(positioned ? { aspectRatio: String(ratio) } : null),
157
164
  ...(placeholder === "color" && color ? { backgroundColor: color } : null),
158
165
  ...style,
159
- }, children: [placeholder === "blur" && blur && state !== "error" ? (_jsx("img", { "aria-hidden": true, alt: "", src: blur, "data-slot": "image-blur", className: "vui-image-blur pointer-events-none absolute inset-0 size-full object-cover", style: imgStyle })) : null, placeholder === "skeleton" && state === "loading" ? _jsx("span", { "aria-hidden": true, "data-slot": "image-skeleton", className: "ui-shimmer absolute inset-0" }) : null, state === "error" ? (fallback ? (_jsx("span", { "data-slot": "image-fallback", className: cn(positioned ? "absolute inset-0" : "block min-h-24", "flex items-center justify-center"), children: fallback })) : (_jsx("span", { "data-slot": "image-fallback", ...(alt ? { role: "img", "aria-label": alt } : { "aria-hidden": true }), title: t("error"), className: cn(positioned ? "absolute inset-0" : "block min-h-24", "flex items-center justify-center text-fg-subtle [&_svg]:size-6"), children: _jsx(ImageOff, { "aria-hidden": true, strokeWidth: 1.5 }) }))) : (img), processing ? (_jsxs("span", { "data-slot": "image-processing", className: "surface-popover absolute start-2 top-2 z-10 inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-[11px] font-medium", children: [_jsx(Spinner, { size: "xs", tone: "accent" }), t("processing")] })) : null] }));
166
+ }, children: [placeholder === "blur" && blur && state !== "error" ? (_jsx("img", { "aria-hidden": true, alt: "", src: blur, "data-slot": "image-blur", className: "vui-image-blur pointer-events-none absolute inset-0 size-full object-cover", style: imgStyle })) : null, placeholder === "skeleton" && state === "loading" ? _jsx("span", { "aria-hidden": true, "data-slot": "image-skeleton", className: "ui-shimmer absolute inset-0" }) : null, placeholder === "art" && state === "loading" ? art(true) : null, state === "error" ? (placeholder === "art" && !fallback ? (_jsx("span", { "data-slot": "image-fallback", title: t("error"), className: cn(positioned ? "absolute inset-0" : "relative block min-h-24"), children: art(false) })) : fallback ? (_jsx("span", { "data-slot": "image-fallback", className: cn(positioned ? "absolute inset-0" : "block min-h-24", "flex items-center justify-center"), children: fallback })) : (_jsx("span", { "data-slot": "image-fallback", ...(alt ? { role: "img", "aria-label": alt } : { "aria-hidden": true }), title: t("error"), className: cn(positioned ? "absolute inset-0" : "block min-h-24", "flex items-center justify-center text-fg-subtle [&_svg]:size-6"), children: _jsx(ImageOff, { "aria-hidden": true, strokeWidth: 1.5 }) }))) : (img), processing ? (_jsxs("span", { "data-slot": "image-processing", className: "surface-popover absolute start-2 top-2 z-10 inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-[11px] font-medium", children: [_jsx(Spinner, { size: "xs", tone: "accent" }), t("processing")] })) : null] }));
160
167
  });
161
168
  /** `<figure>` for a captioned image (blocks, galleries, articles). */
162
169
  export const Figure = React.forwardRef(function Figure({ gap = "sm", className, ...props }, ref) {
@@ -47,7 +47,7 @@ export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElem
47
47
  * <Input maxLength={60} showCount />
48
48
  * </Field>
49
49
  * <Input leading={<Search />} placeholder="Search contacts" clearable />
50
- * <Input addonStart="https://" addonEnd=".vamos.ist" defaultValue="acme" />
50
+ * <Input addonStart="https://" addonEnd=".vamos.ist" defaultValue="northpoint" />
51
51
  * ```
52
52
  */
53
53
  export declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<HTMLInputElement>>;
@@ -36,7 +36,7 @@ const setNativeValue = (el, value) => {
36
36
  * <Input maxLength={60} showCount />
37
37
  * </Field>
38
38
  * <Input leading={<Search />} placeholder="Search contacts" clearable />
39
- * <Input addonStart="https://" addonEnd=".vamos.ist" defaultValue="acme" />
39
+ * <Input addonStart="https://" addonEnd=".vamos.ist" defaultValue="northpoint" />
40
40
  * ```
41
41
  */
42
42
  export const Input = React.forwardRef(function Input(rawProps, forwardedRef) {
@@ -0,0 +1,96 @@
1
+ import * as React from "react";
2
+ import { Menubar as MenubarPrimitive } from "radix-ui";
3
+ import { type VariantProps } from "class-variance-authority";
4
+ import type { MenuEntry } from "../lib/variants.js";
5
+ export declare const menubarVariants: (props?: ({
6
+ variant?: "ghost" | "bar" | null | undefined;
7
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
8
+ /** One top-level menu in the data-driven form. */
9
+ export interface MenubarMenuEntry {
10
+ id: string;
11
+ /** Trigger text ("File", "Edit", "View"…). */
12
+ label: React.ReactNode;
13
+ /** The menu's entries — the same `MenuEntry` model as Dropdown Menu, Context Menu and Command. */
14
+ items: MenuEntry[];
15
+ disabled?: boolean;
16
+ /** Hidden unless `can(permission)` is true (see `useCan`). */
17
+ permission?: string;
18
+ }
19
+ export interface MenubarProps extends React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>, VariantProps<typeof menubarVariants> {
20
+ /** Data-driven menus. Rendered before `children`. */
21
+ menus?: MenubarMenuEntry[];
22
+ /** Minimum panel width of every menu: `sm` 180px, `md` 220px. Default `md`. */
23
+ size?: "sm" | "md";
24
+ }
25
+ /**
26
+ * A desktop-app menu bar (WAI-ARIA `menubar`): File / Edit / View… One tab stop; ← → move between
27
+ * menus (and keep the open one open), ↓ / Enter / Space open, Esc closes, typeahead inside a menu.
28
+ *
29
+ * ```tsx
30
+ * <Menubar aria-label="Editor" menus={[
31
+ * { id: "file", label: "File", items: [{ id: "new", label: "New document", shortcut: "mod+n", onSelect: create }] },
32
+ * ]} />
33
+ * ```
34
+ */
35
+ export declare const Menubar: React.ForwardRefExoticComponent<MenubarProps & React.RefAttributes<HTMLDivElement>>;
36
+ /** One menu: a `MenubarTrigger` plus its `MenubarContent`. `value` is needed only for a controlled bar. */
37
+ export declare const MenubarMenu: typeof MenubarPrimitive.Menu;
38
+ export declare const MenubarGroup: React.ForwardRefExoticComponent<MenubarPrimitive.MenubarGroupProps & React.RefAttributes<HTMLDivElement>>;
39
+ export declare const MenubarSub: React.FC<MenubarPrimitive.MenubarSubProps>;
40
+ export declare const MenubarRadioGroup: React.ForwardRefExoticComponent<MenubarPrimitive.MenubarRadioGroupProps & React.RefAttributes<HTMLDivElement>>;
41
+ /** The top-level button ("File"). */
42
+ export declare const MenubarTrigger: React.ForwardRefExoticComponent<Omit<MenubarPrimitive.MenubarTriggerProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
43
+ export interface MenubarContentProps extends React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content> {
44
+ /** Overrides the bar's `size` for this menu. */
45
+ size?: "sm" | "md";
46
+ }
47
+ /** A menu panel under its trigger. Portals into the nearest `ThemeScope`; only this first layer blurs in glass mode. */
48
+ export declare const MenubarContent: React.ForwardRefExoticComponent<MenubarContentProps & React.RefAttributes<HTMLDivElement>>;
49
+ export interface MenubarItemProps extends React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> {
50
+ icon?: React.ReactNode;
51
+ description?: React.ReactNode;
52
+ /** Shortcut hint, e.g. `"mod+s"` (rendered with Kbd). Display only — bind it with `useHotkeys`. */
53
+ shortcut?: string | string[];
54
+ badge?: React.ReactNode;
55
+ tone?: "default" | "danger";
56
+ /** Reserve the indicator gutter so labels align with checkbox/radio items. */
57
+ inset?: boolean;
58
+ }
59
+ /** One command. `onSelect` fires on click, Enter and Space, then the menu closes (`event.preventDefault()` keeps it open). */
60
+ export declare const MenubarItem: React.ForwardRefExoticComponent<MenubarItemProps & React.RefAttributes<HTMLDivElement>>;
61
+ export interface MenubarCheckboxItemProps extends React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem> {
62
+ shortcut?: string | string[];
63
+ description?: React.ReactNode;
64
+ }
65
+ /** A toggle (`menuitemcheckbox`) — "Show ruler", "Word wrap". */
66
+ export declare const MenubarCheckboxItem: React.ForwardRefExoticComponent<MenubarCheckboxItemProps & React.RefAttributes<HTMLDivElement>>;
67
+ export interface MenubarRadioItemProps extends React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem> {
68
+ shortcut?: string | string[];
69
+ description?: React.ReactNode;
70
+ }
71
+ /** One choice inside a `MenubarRadioGroup` (`menuitemradio`) — zoom level, sort order. */
72
+ export declare const MenubarRadioItem: React.ForwardRefExoticComponent<MenubarRadioItemProps & React.RefAttributes<HTMLDivElement>>;
73
+ export declare const MenubarLabel: React.ForwardRefExoticComponent<Omit<MenubarPrimitive.MenubarLabelProps & React.RefAttributes<HTMLDivElement>, "ref"> & {
74
+ inset?: boolean;
75
+ } & React.RefAttributes<HTMLDivElement>>;
76
+ export declare const MenubarSeparator: React.ForwardRefExoticComponent<Omit<MenubarPrimitive.MenubarSeparatorProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
77
+ /** Trailing text hint (when you don't want keycaps). */
78
+ export declare function MenubarShortcut({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>): React.JSX.Element;
79
+ export interface MenubarSubTriggerProps extends React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> {
80
+ icon?: React.ReactNode;
81
+ inset?: boolean;
82
+ }
83
+ /** Opens a submenu on hover, Enter, Space or → (← in RTL). */
84
+ export declare const MenubarSubTrigger: React.ForwardRefExoticComponent<MenubarSubTriggerProps & React.RefAttributes<HTMLDivElement>>;
85
+ /** A submenu panel. One layer deeper, so it is opaque in glass mode. */
86
+ export declare const MenubarSubContent: React.ForwardRefExoticComponent<Omit<MenubarPrimitive.MenubarSubContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & {
87
+ size?: "sm" | "md";
88
+ } & React.RefAttributes<HTMLDivElement>>;
89
+ /** Renders `MenuEntry[]` as menubar items (inside your own `MenubarContent`). */
90
+ export declare function MenubarEntries({ items }: {
91
+ items: MenuEntry[];
92
+ }): React.JSX.Element;
93
+ /** Renders `MenubarMenuEntry[]` (what `Menubar menus` uses). Menus with no visible entries are dropped. */
94
+ export declare function MenubarMenus({ menus }: {
95
+ menus: MenubarMenuEntry[];
96
+ }): React.JSX.Element;
@@ -0,0 +1,123 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Menubar as MenubarPrimitive } from "radix-ui";
5
+ import { cva } from "class-variance-authority";
6
+ import { cn } from "../lib/cn.js";
7
+ import { usePortalContainer } from "../lib/theme-scope.js";
8
+ import { Layer } from "../lib/layer.js";
9
+ import { useCan } from "../lib/provider.js";
10
+ import { MenuCheckIcon, MenuEntries, MenuItemContent, MenuRadioDot, MenuSizeContext, menuContentVariants, menuIndicatorClass, menuItemClass, menuLabelClass, menuSeparatorClass, useVisibleEntries, } from "../lib/menu.js";
11
+ /* ------------------------------------------------------------------ */
12
+ /* Bar */
13
+ /* ------------------------------------------------------------------ */
14
+ export const menubarVariants = cva("flex min-w-0 items-center gap-0.5 overflow-x-auto [scrollbar-width:none]", {
15
+ variants: {
16
+ variant: {
17
+ /** A hairline surface strip — the top of an editor or a desktop-style app window. */
18
+ bar: "surface rounded-ui-lg p-1",
19
+ /** No chrome: sits inside a Top bar, toolbar or window header. */
20
+ ghost: "",
21
+ },
22
+ },
23
+ defaultVariants: { variant: "bar" },
24
+ });
25
+ /**
26
+ * A desktop-app menu bar (WAI-ARIA `menubar`): File / Edit / View… One tab stop; ← → move between
27
+ * menus (and keep the open one open), ↓ / Enter / Space open, Esc closes, typeahead inside a menu.
28
+ *
29
+ * ```tsx
30
+ * <Menubar aria-label="Editor" menus={[
31
+ * { id: "file", label: "File", items: [{ id: "new", label: "New document", shortcut: "mod+n", onSelect: create }] },
32
+ * ]} />
33
+ * ```
34
+ */
35
+ export const Menubar = React.forwardRef(function Menubar({ className, variant, menus, size = "md", children, loop = true, ...props }, ref) {
36
+ return (_jsx(MenuSizeContext.Provider, { value: size, children: _jsxs(MenubarPrimitive.Root, { ref: ref, "data-slot": "menubar", "data-variant": variant ?? "bar", loop: loop, className: cn(menubarVariants({ variant }), className), ...props, children: [menus ? _jsx(MenubarMenus, { menus: menus }) : null, children] }) }));
37
+ });
38
+ /** One menu: a `MenubarTrigger` plus its `MenubarContent`. `value` is needed only for a controlled bar. */
39
+ export const MenubarMenu = MenubarPrimitive.Menu;
40
+ export const MenubarGroup = MenubarPrimitive.Group;
41
+ export const MenubarSub = MenubarPrimitive.Sub;
42
+ export const MenubarRadioGroup = MenubarPrimitive.RadioGroup;
43
+ /** The top-level button ("File"). */
44
+ export const MenubarTrigger = React.forwardRef(function MenubarTrigger({ className, ...props }, ref) {
45
+ return (_jsx(MenubarPrimitive.Trigger, { ref: ref, "data-slot": "menubar-trigger", className: cn("inline-flex h-[calc(var(--control-h)-0.5rem)] shrink-0 cursor-default select-none items-center gap-1.5 rounded-ui-sm px-2.5 text-[13px] font-medium text-fg outline-none", "transition-colors duration-100 hover:bg-hover focus-visible:bg-hover focus-ring", "data-highlighted:bg-hover data-[state=open]:bg-selected", "disabled:pointer-events-none disabled:opacity-45 data-disabled:pointer-events-none data-disabled:opacity-45", "[&_svg]:size-3.5 [&_svg]:shrink-0 [&_svg]:text-fg-muted", className), ...props }));
46
+ });
47
+ /** A menu panel under its trigger. Portals into the nearest `ThemeScope`; only this first layer blurs in glass mode. */
48
+ export const MenubarContent = React.forwardRef(function MenubarContent({ className, size: sizeProp, sideOffset = 6, alignOffset = -4, collisionPadding = 12, align = "start", ...props }, ref) {
49
+ const container = usePortalContainer();
50
+ const barSize = React.useContext(MenuSizeContext);
51
+ const size = sizeProp ?? barSize;
52
+ return (_jsx(MenubarPrimitive.Portal, { container: container, children: _jsx(MenuSizeContext.Provider, { value: size, children: _jsx(Layer, { asChild: true, children: _jsx(MenubarPrimitive.Content, { ref: ref, "data-slot": "menubar-content", sideOffset: sideOffset, alignOffset: alignOffset, collisionPadding: collisionPadding, align: align, className: cn(menuContentVariants({ size }), "max-h-(--radix-menubar-content-available-height) origin-(--radix-menubar-content-transform-origin)", className), ...props }) }) }) }));
53
+ });
54
+ /** One command. `onSelect` fires on click, Enter and Space, then the menu closes (`event.preventDefault()` keeps it open). */
55
+ export const MenubarItem = React.forwardRef(function MenubarItem({ className, icon, description, shortcut, badge, tone = "default", inset, asChild, children, ...props }, ref) {
56
+ const size = React.useContext(MenuSizeContext);
57
+ return (_jsx(MenubarPrimitive.Item, { ref: ref, "data-slot": "menubar-item", "data-tone": tone, asChild: asChild, className: menuItemClass({ size, tone, inset, className }), ...props, children: asChild ? (children) : (_jsx(MenuItemContent, { icon: icon, description: description, shortcut: shortcut, badge: badge, children: children })) }));
58
+ });
59
+ /** A toggle (`menuitemcheckbox`) — "Show ruler", "Word wrap". */
60
+ export const MenubarCheckboxItem = React.forwardRef(function MenubarCheckboxItem({ className, shortcut, description, children, ...props }, ref) {
61
+ const size = React.useContext(MenuSizeContext);
62
+ return (_jsxs(MenubarPrimitive.CheckboxItem, { ref: ref, "data-slot": "menubar-checkbox-item", className: menuItemClass({ size, inset: true, className }), ...props, children: [_jsx("span", { className: menuIndicatorClass, children: _jsx(MenubarPrimitive.ItemIndicator, { children: _jsx(MenuCheckIcon, {}) }) }), _jsx(MenuItemContent, { shortcut: shortcut, description: description, children: children })] }));
63
+ });
64
+ /** One choice inside a `MenubarRadioGroup` (`menuitemradio`) — zoom level, sort order. */
65
+ export const MenubarRadioItem = React.forwardRef(function MenubarRadioItem({ className, shortcut, description, children, ...props }, ref) {
66
+ const size = React.useContext(MenuSizeContext);
67
+ return (_jsxs(MenubarPrimitive.RadioItem, { ref: ref, "data-slot": "menubar-radio-item", className: menuItemClass({ size, inset: true, className }), ...props, children: [_jsx("span", { className: menuIndicatorClass, children: _jsx(MenubarPrimitive.ItemIndicator, { children: _jsx(MenuRadioDot, {}) }) }), _jsx(MenuItemContent, { shortcut: shortcut, description: description, children: children })] }));
68
+ });
69
+ export const MenubarLabel = React.forwardRef(function MenubarLabel({ className, inset, ...props }, ref) {
70
+ return _jsx(MenubarPrimitive.Label, { ref: ref, "data-slot": "menubar-label", className: cn(menuLabelClass, inset && "ps-8", className), ...props });
71
+ });
72
+ export const MenubarSeparator = React.forwardRef(function MenubarSeparator({ className, ...props }, ref) {
73
+ return _jsx(MenubarPrimitive.Separator, { ref: ref, "data-slot": "menubar-separator", className: cn(menuSeparatorClass, className), ...props });
74
+ });
75
+ /** Trailing text hint (when you don't want keycaps). */
76
+ export function MenubarShortcut({ className, ...props }) {
77
+ return _jsx("span", { "data-slot": "menubar-shortcut", className: cn("ms-auto ps-4 font-mono text-[11px] tracking-wide text-fg-subtle", className), ...props });
78
+ }
79
+ /** Opens a submenu on hover, Enter, Space or → (← in RTL). */
80
+ export const MenubarSubTrigger = React.forwardRef(function MenubarSubTrigger({ className, icon, inset, children, ...props }, ref) {
81
+ const size = React.useContext(MenuSizeContext);
82
+ return (_jsx(MenubarPrimitive.SubTrigger, { ref: ref, "data-slot": "menubar-sub-trigger", className: menuItemClass({ size, inset, className: cn("data-[state=open]:bg-hover", className) }), ...props, children: _jsx(MenuItemContent, { icon: icon, chevron: true, children: children }) }));
83
+ });
84
+ /** A submenu panel. One layer deeper, so it is opaque in glass mode. */
85
+ export const MenubarSubContent = React.forwardRef(function MenubarSubContent({ className, size: sizeProp, sideOffset = 4, alignOffset = -5, collisionPadding = 12, ...props }, ref) {
86
+ const container = usePortalContainer();
87
+ const parentSize = React.useContext(MenuSizeContext);
88
+ const size = sizeProp ?? parentSize;
89
+ return (_jsx(MenubarPrimitive.Portal, { container: container, children: _jsx(Layer, { asChild: true, children: _jsx(MenubarPrimitive.SubContent, { ref: ref, "data-slot": "menubar-sub-content", sideOffset: sideOffset, alignOffset: alignOffset, collisionPadding: collisionPadding, className: cn(menuContentVariants({ size }), "max-h-(--radix-menubar-content-available-height)", className), ...props }) }) }));
90
+ });
91
+ /* ------------------------------------------------------------------ */
92
+ /* Data-driven */
93
+ /* ------------------------------------------------------------------ */
94
+ const menubarParts = {
95
+ Item: MenubarItem,
96
+ CheckboxItem: MenubarCheckboxItem,
97
+ RadioGroup: MenubarRadioGroup,
98
+ RadioItem: MenubarRadioItem,
99
+ Label: MenubarLabel,
100
+ Separator: MenubarSeparator,
101
+ Group: MenubarGroup,
102
+ Sub: MenubarSub,
103
+ SubTrigger: MenubarSubTrigger,
104
+ SubContent: MenubarSubContent,
105
+ };
106
+ /** Renders `MenuEntry[]` as menubar items (inside your own `MenubarContent`). */
107
+ export function MenubarEntries({ items }) {
108
+ const visible = useVisibleEntries(items);
109
+ return _jsx(MenuEntries, { entries: visible, parts: menubarParts });
110
+ }
111
+ function MenubarDataMenu({ menu }) {
112
+ const visible = useVisibleEntries(menu.items);
113
+ if (visible.length === 0)
114
+ return null;
115
+ return (_jsxs(MenubarMenu, { value: menu.id, children: [_jsx(MenubarTrigger, { disabled: menu.disabled, children: menu.label }), _jsx(MenubarContent, { children: _jsx(MenuEntries, { entries: visible, parts: menubarParts }) })] }));
116
+ }
117
+ /** Renders `MenubarMenuEntry[]` (what `Menubar menus` uses). Menus with no visible entries are dropped. */
118
+ export function MenubarMenus({ menus }) {
119
+ const can = useCan();
120
+ return (_jsx(_Fragment, { children: menus
121
+ .filter((m) => !m.permission || can(m.permission))
122
+ .map((m) => (_jsx(MenubarDataMenu, { menu: m }, m.id))) }));
123
+ }
@@ -0,0 +1,58 @@
1
+ import * as React from "react";
2
+ export type MapPinTone = "accent" | "neutral" | "info" | "success" | "warning" | "danger";
3
+ export interface MapPin {
4
+ id: string;
5
+ lat: number;
6
+ lng: number;
7
+ /** Accessible name and callout title. */
8
+ label: string;
9
+ /** Callout body (address, status, ETA). */
10
+ description?: React.ReactNode;
11
+ tone?: MapPinTone;
12
+ /** Size for `variant="bubble"` (visitors, orders). */
13
+ value?: number;
14
+ /** Live ring — vehicles en route, new visitors. Off under reduced motion. */
15
+ pulse?: boolean;
16
+ /** Small glyph inside the marker (pin variant), e.g. a 12px icon or initials. */
17
+ icon?: React.ReactNode;
18
+ }
19
+ export interface PinMapBounds {
20
+ north: number;
21
+ south: number;
22
+ west: number;
23
+ east: number;
24
+ }
25
+ export interface PinMapProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange"> {
26
+ pins: MapPin[];
27
+ /** Visible area. Omit to fit every pin. Expanded to match `aspectRatio` so nothing distorts. */
28
+ bounds?: PinMapBounds;
29
+ /** Width ÷ height. Default `16 / 10`. */
30
+ aspectRatio?: number;
31
+ /** `"pin"` (default) markers, small `"dot"`s, or value-sized `"bubble"`s. */
32
+ variant?: "pin" | "dot" | "bubble";
33
+ selectedId?: string | null;
34
+ defaultSelectedId?: string | null;
35
+ onSelectedIdChange?: (id: string | null) => void;
36
+ /** Callout for the selected pin. Default: label + description. `false` hides it. */
37
+ callout?: false | ((pin: MapPin) => React.ReactNode);
38
+ /** Always show labels next to markers (few pins only). */
39
+ showLabels?: boolean;
40
+ /** Replace the drawn sheet with your own layer (a static tile image, an SVG of districts). */
41
+ backdrop?: React.ReactNode;
42
+ /** Hide the coordinate ticks and scale bar. */
43
+ bare?: boolean;
44
+ /** Required: what the map shows ("Stores in Istanbul"). */
45
+ "aria-label": string;
46
+ }
47
+ /**
48
+ * Markers on a projected sheet — stores, field jobs, vehicles, live visitors — without a tile
49
+ * provider. Pins are positioned in Web Mercator inside `bounds`; pass a `backdrop` to lay them
50
+ * over your own static map. Selection is controlled or not; arrow keys move to the nearest pin
51
+ * in that direction, Enter selects, Escape clears.
52
+ *
53
+ * ```tsx
54
+ * <PinMap aria-label="Stores" pins={stores.map((s) => ({ id: s.id, lat: s.lat, lng: s.lng, label: s.name }))}
55
+ * selectedId={selected} onSelectedIdChange={setSelected} />
56
+ * ```
57
+ */
58
+ export declare const PinMap: React.ForwardRefExoticComponent<PinMapProps & React.RefAttributes<HTMLDivElement>>;