@olympusoss/canvas 2.6.19

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 (128) hide show
  1. package/package.json +179 -0
  2. package/src/components/atoms/README.md +11 -0
  3. package/src/components/atoms/aspect-ratio.tsx +32 -0
  4. package/src/components/atoms/avatar.tsx +98 -0
  5. package/src/components/atoms/badge.tsx +44 -0
  6. package/src/components/atoms/brand-mark.tsx +74 -0
  7. package/src/components/atoms/button.tsx +104 -0
  8. package/src/components/atoms/checkbox.tsx +63 -0
  9. package/src/components/atoms/flex-box.tsx +105 -0
  10. package/src/components/atoms/icon.tsx +34 -0
  11. package/src/components/atoms/input.tsx +91 -0
  12. package/src/components/atoms/label.tsx +41 -0
  13. package/src/components/atoms/logo.tsx +89 -0
  14. package/src/components/atoms/progress.tsx +55 -0
  15. package/src/components/atoms/radio-group.tsx +122 -0
  16. package/src/components/atoms/scroll-area.tsx +106 -0
  17. package/src/components/atoms/section.tsx +48 -0
  18. package/src/components/atoms/separator.tsx +45 -0
  19. package/src/components/atoms/skeleton.tsx +17 -0
  20. package/src/components/atoms/slider.tsx +93 -0
  21. package/src/components/atoms/switch.tsx +60 -0
  22. package/src/components/atoms/textarea.tsx +78 -0
  23. package/src/components/atoms/toggle.tsx +80 -0
  24. package/src/components/charts/activity-heatmap.tsx +96 -0
  25. package/src/components/charts/axes.tsx +21 -0
  26. package/src/components/charts/chart-container.tsx +195 -0
  27. package/src/components/charts/chart-legend.tsx +67 -0
  28. package/src/components/charts/chart-tooltip.tsx +161 -0
  29. package/src/components/charts/chart-types.tsx +49 -0
  30. package/src/components/charts/containers.tsx +11 -0
  31. package/src/components/charts/data.tsx +16 -0
  32. package/src/components/charts/details.tsx +25 -0
  33. package/src/components/charts/gauge.tsx +106 -0
  34. package/src/components/charts/grids.tsx +8 -0
  35. package/src/components/charts/index.ts +62 -0
  36. package/src/components/charts/labeled-bar-list.tsx +85 -0
  37. package/src/components/charts/references.tsx +8 -0
  38. package/src/components/charts/service-health-list.tsx +73 -0
  39. package/src/components/charts/sparkline.tsx +52 -0
  40. package/src/components/charts/stacked-bar.tsx +104 -0
  41. package/src/components/charts/text.tsx +10 -0
  42. package/src/components/charts/world-heat-map-inner.tsx +317 -0
  43. package/src/components/charts/world-heat-map.tsx +184 -0
  44. package/src/components/molecules/README.md +12 -0
  45. package/src/components/molecules/action-bar.tsx +73 -0
  46. package/src/components/molecules/activity-item.tsx +74 -0
  47. package/src/components/molecules/alert.tsx +80 -0
  48. package/src/components/molecules/animated-background.tsx +92 -0
  49. package/src/components/molecules/brand-lockup.tsx +48 -0
  50. package/src/components/molecules/breadcrumb.tsx +161 -0
  51. package/src/components/molecules/button-group.tsx +104 -0
  52. package/src/components/molecules/calendar.tsx +216 -0
  53. package/src/components/molecules/card.tsx +101 -0
  54. package/src/components/molecules/code-block.tsx +48 -0
  55. package/src/components/molecules/empty-state.tsx +55 -0
  56. package/src/components/molecules/error-state.tsx +42 -0
  57. package/src/components/molecules/field-display.tsx +35 -0
  58. package/src/components/molecules/input-otp.tsx +74 -0
  59. package/src/components/molecules/loading-state.tsx +36 -0
  60. package/src/components/molecules/notification-item.tsx +67 -0
  61. package/src/components/molecules/notification-list.tsx +45 -0
  62. package/src/components/molecules/number-badge.tsx +53 -0
  63. package/src/components/molecules/page-header.tsx +88 -0
  64. package/src/components/molecules/page-tabs.tsx +94 -0
  65. package/src/components/molecules/pagination.tsx +150 -0
  66. package/src/components/molecules/phone-input.tsx +200 -0
  67. package/src/components/molecules/search-bar.tsx +64 -0
  68. package/src/components/molecules/secret-field.tsx +158 -0
  69. package/src/components/molecules/section-card.tsx +91 -0
  70. package/src/components/molecules/stat-card.tsx +96 -0
  71. package/src/components/molecules/status-badge.tsx +42 -0
  72. package/src/components/molecules/stepper.tsx +96 -0
  73. package/src/components/molecules/table.tsx +157 -0
  74. package/src/components/molecules/toggle-group.tsx +145 -0
  75. package/src/components/molecules/tooltip.tsx +150 -0
  76. package/src/components/molecules/user-avatar-chip.tsx +71 -0
  77. package/src/components/organisms/README.md +14 -0
  78. package/src/components/organisms/accordion.tsx +149 -0
  79. package/src/components/organisms/alert-dialog.tsx +269 -0
  80. package/src/components/organisms/carousel.tsx +244 -0
  81. package/src/components/organisms/collapsible.tsx +69 -0
  82. package/src/components/organisms/command.tsx +143 -0
  83. package/src/components/organisms/context-menu.tsx +333 -0
  84. package/src/components/organisms/dashboard-grid.tsx +360 -0
  85. package/src/components/organisms/data-table.tsx +330 -0
  86. package/src/components/organisms/dialog.tsx +304 -0
  87. package/src/components/organisms/drawer.tsx +100 -0
  88. package/src/components/organisms/dropdown-menu.tsx +434 -0
  89. package/src/components/organisms/editors/code-editor.tsx +144 -0
  90. package/src/components/organisms/editors/index.ts +4 -0
  91. package/src/components/organisms/editors/markdown-editor.tsx +153 -0
  92. package/src/components/organisms/editors/markdown-renderer.ts +27 -0
  93. package/src/components/organisms/editors/prose-canvas-classes.ts +45 -0
  94. package/src/components/organisms/editors/rich-text-editor.tsx +126 -0
  95. package/src/components/organisms/editors/toolbar/md-toolbar.tsx +129 -0
  96. package/src/components/organisms/editors/toolbar/rte-toolbar.tsx +211 -0
  97. package/src/components/organisms/editors/toolbar/toolbar-shell.tsx +45 -0
  98. package/src/components/organisms/editors/use-codemirror-theme.ts +61 -0
  99. package/src/components/organisms/error-boundary.tsx +61 -0
  100. package/src/components/organisms/form.tsx +174 -0
  101. package/src/components/organisms/hover-card.tsx +114 -0
  102. package/src/components/organisms/menubar.tsx +491 -0
  103. package/src/components/organisms/navbar.tsx +101 -0
  104. package/src/components/organisms/navigation-menu.tsx +234 -0
  105. package/src/components/organisms/popover.tsx +144 -0
  106. package/src/components/organisms/resizable.tsx +39 -0
  107. package/src/components/organisms/schema-form.tsx +232 -0
  108. package/src/components/organisms/select.tsx +303 -0
  109. package/src/components/organisms/sheet.tsx +256 -0
  110. package/src/components/organisms/sidebar.tsx +1037 -0
  111. package/src/components/organisms/sonner.tsx +96 -0
  112. package/src/components/organisms/tabs.tsx +132 -0
  113. package/src/components/organisms/theme-provider.tsx +101 -0
  114. package/src/hooks/use-mobile.tsx +19 -0
  115. package/src/index.ts +547 -0
  116. package/src/lib/portal-container.tsx +35 -0
  117. package/src/lib/utils.ts +6 -0
  118. package/src/native.ts +23 -0
  119. package/src/tokens/colors.ts +91 -0
  120. package/src/tokens/index.ts +3 -0
  121. package/src/tokens/spacing.ts +55 -0
  122. package/src/tokens/typography.ts +27 -0
  123. package/styles/canvas.css +55 -0
  124. package/styles/dashboard-grid.css +47 -0
  125. package/styles/leaflet.css +13 -0
  126. package/styles/tokens.css +234 -0
  127. package/tailwind.config.ts +70 -0
  128. package/tsconfig.json +23 -0
@@ -0,0 +1,491 @@
1
+ "use client";
2
+
3
+ import * as MenubarPrimitive from "@radix-ui/react-menubar";
4
+ import { Check, ChevronRight, Circle } from "lucide-react";
5
+ import * as React from "react";
6
+
7
+ import { cn } from "../../lib/utils";
8
+
9
+ export interface MenubarProps extends React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root> {
10
+ /** Controlled active menu value (the `value` of the open `<MenubarMenu>`). */
11
+ value?: string;
12
+ /** Initial active menu value for uncontrolled usage. */
13
+ defaultValue?: string;
14
+ /** Fires when the open menu changes. */
15
+ onValueChange?: (value: string) => void;
16
+ /**
17
+ * Reading direction. Affects keyboard arrow navigation.
18
+ * @default "ltr"
19
+ */
20
+ dir?: "ltr" | "rtl";
21
+ /**
22
+ * Loop arrow-key navigation across the top-level menus.
23
+ * @default false
24
+ */
25
+ loop?: boolean;
26
+ /** A list of `<MenubarMenu>`s. */
27
+ children?: React.ReactNode;
28
+ className?: string;
29
+ }
30
+
31
+ const Menubar = React.forwardRef<React.ElementRef<typeof MenubarPrimitive.Root>, MenubarProps>(
32
+ ({ className, ...props }, ref) => (
33
+ <MenubarPrimitive.Root
34
+ ref={ref}
35
+ className={cn(
36
+ "flex h-9 items-center space-x-1 rounded-md border bg-background p-1 shadow-sm",
37
+ className,
38
+ )}
39
+ {...props}
40
+ />
41
+ ),
42
+ );
43
+ Menubar.displayName = MenubarPrimitive.Root.displayName;
44
+
45
+ export interface MenubarMenuProps extends React.ComponentProps<typeof MenubarPrimitive.Menu> {
46
+ /** Required for controlled menus — unique id of this menu. */
47
+ value?: string;
48
+ /** Trigger + Content (sub-menus and items). */
49
+ children?: React.ReactNode;
50
+ }
51
+
52
+ function MenubarMenu({ ...props }: MenubarMenuProps) {
53
+ return <MenubarPrimitive.Menu {...props} />;
54
+ }
55
+
56
+ export interface MenubarGroupProps
57
+ extends React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Group> {
58
+ /** Items grouped together for screen-reader semantics. */
59
+ children?: React.ReactNode;
60
+ }
61
+
62
+ function MenubarGroup({ ...props }: MenubarGroupProps) {
63
+ return <MenubarPrimitive.Group {...props} />;
64
+ }
65
+
66
+ export interface MenubarPortalProps
67
+ extends React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Portal> {
68
+ /** DOM element to portal into. Defaults to `document.body`. */
69
+ container?: HTMLElement | null;
70
+ /**
71
+ * Force the portal to mount even when the menu is closed.
72
+ * @default false
73
+ */
74
+ forceMount?: true;
75
+ children?: React.ReactNode;
76
+ }
77
+
78
+ function MenubarPortal({ ...props }: MenubarPortalProps) {
79
+ return <MenubarPrimitive.Portal {...props} />;
80
+ }
81
+
82
+ export interface MenubarRadioGroupProps
83
+ extends React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioGroup> {
84
+ /** Currently selected value (the `value` of the active RadioItem). */
85
+ value?: string;
86
+ /** Fires when the user picks a different RadioItem. */
87
+ onValueChange?: (value: string) => void;
88
+ /** A list of `<MenubarRadioItem>`s. */
89
+ children?: React.ReactNode;
90
+ }
91
+
92
+ function MenubarRadioGroup({ ...props }: MenubarRadioGroupProps) {
93
+ return <MenubarPrimitive.RadioGroup {...props} />;
94
+ }
95
+
96
+ export interface MenubarSubProps extends React.ComponentProps<typeof MenubarPrimitive.Sub> {
97
+ /** Controlled open state of the sub-menu. */
98
+ open?: boolean;
99
+ /**
100
+ * Initial open state for uncontrolled usage.
101
+ * @default false
102
+ */
103
+ defaultOpen?: boolean;
104
+ /** Fires whenever the sub-menu opens or closes. */
105
+ onOpenChange?: (open: boolean) => void;
106
+ /** SubTrigger + SubContent. */
107
+ children?: React.ReactNode;
108
+ }
109
+
110
+ function MenubarSub({ ...props }: MenubarSubProps) {
111
+ return <MenubarPrimitive.Sub data-slot="menubar-sub" {...props} />;
112
+ }
113
+
114
+ export interface MenubarTriggerProps
115
+ extends React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger> {
116
+ /**
117
+ * Disable this top-level trigger.
118
+ * @default false
119
+ */
120
+ disabled?: boolean;
121
+ /**
122
+ * Render as a Radix Slot.
123
+ * @default false
124
+ */
125
+ asChild?: boolean;
126
+ /** Trigger label (typically a top-level menu name). */
127
+ children?: React.ReactNode;
128
+ className?: string;
129
+ }
130
+
131
+ const MenubarTrigger = React.forwardRef<
132
+ React.ElementRef<typeof MenubarPrimitive.Trigger>,
133
+ MenubarTriggerProps
134
+ >(({ className, ...props }, ref) => (
135
+ <MenubarPrimitive.Trigger
136
+ ref={ref}
137
+ className={cn(
138
+ "flex cursor-default select-none items-center rounded-sm px-3 py-1 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
139
+ className,
140
+ )}
141
+ {...props}
142
+ />
143
+ ));
144
+ MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName;
145
+
146
+ export interface MenubarSubTriggerProps
147
+ extends React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> {
148
+ /**
149
+ * Add left padding so this row aligns with sibling checkbox/radio
150
+ * items that have leading indicators.
151
+ * @default false
152
+ */
153
+ inset?: boolean;
154
+ /**
155
+ * Disable the sub-trigger.
156
+ * @default false
157
+ */
158
+ disabled?: boolean;
159
+ /**
160
+ * Render as a Radix Slot.
161
+ * @default false
162
+ */
163
+ asChild?: boolean;
164
+ /** Sub-trigger label. */
165
+ children?: React.ReactNode;
166
+ className?: string;
167
+ }
168
+
169
+ const MenubarSubTrigger = React.forwardRef<
170
+ React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
171
+ MenubarSubTriggerProps
172
+ >(({ className, inset, children, ...props }, ref) => (
173
+ <MenubarPrimitive.SubTrigger
174
+ ref={ref}
175
+ className={cn(
176
+ "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
177
+ inset && "pl-8",
178
+ className,
179
+ )}
180
+ {...props}
181
+ >
182
+ {children}
183
+ <ChevronRight className="ml-auto h-4 w-4" />
184
+ </MenubarPrimitive.SubTrigger>
185
+ ));
186
+ MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName;
187
+
188
+ export interface MenubarSubContentProps
189
+ extends React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent> {
190
+ /** Distance from the parent menu (px). */
191
+ sideOffset?: number;
192
+ /** Distance from the alignment edge (px). */
193
+ alignOffset?: number;
194
+ /**
195
+ * Avoid colliding with viewport edges.
196
+ * @default true
197
+ */
198
+ avoidCollisions?: boolean;
199
+ /** Force the sub-content to mount even when closed. */
200
+ forceMount?: true;
201
+ /**
202
+ * Loop arrow-key navigation through items.
203
+ * @default false
204
+ */
205
+ loop?: boolean;
206
+ asChild?: boolean;
207
+ /** Items + groups + separators. */
208
+ children?: React.ReactNode;
209
+ className?: string;
210
+ }
211
+
212
+ const MenubarSubContent = React.forwardRef<
213
+ React.ElementRef<typeof MenubarPrimitive.SubContent>,
214
+ MenubarSubContentProps
215
+ >(({ className, ...props }, ref) => (
216
+ <MenubarPrimitive.SubContent
217
+ ref={ref}
218
+ className={cn(
219
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[var(--radix-menubar-content-transform-origin)]",
220
+ className,
221
+ )}
222
+ {...props}
223
+ />
224
+ ));
225
+ MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName;
226
+
227
+ export interface MenubarContentProps
228
+ extends React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content> {
229
+ /**
230
+ * Distance from the trigger (px).
231
+ * @default 8
232
+ */
233
+ sideOffset?: number;
234
+ /**
235
+ * Distance from the alignment edge (px).
236
+ * @default -4
237
+ */
238
+ alignOffset?: number;
239
+ /**
240
+ * Preferred side of the trigger to render on.
241
+ * @default "bottom"
242
+ */
243
+ side?: "top" | "right" | "bottom" | "left";
244
+ /**
245
+ * Alignment along the chosen side.
246
+ * @default "start"
247
+ */
248
+ align?: "start" | "center" | "end";
249
+ /**
250
+ * Avoid colliding with the viewport edges.
251
+ * @default true
252
+ */
253
+ avoidCollisions?: boolean;
254
+ /** Padding kept from collision boundaries. */
255
+ collisionPadding?: number | { top?: number; right?: number; bottom?: number; left?: number };
256
+ /** Loop arrow-key navigation through items. */
257
+ loop?: boolean;
258
+ /** Force the content to mount even when closed. */
259
+ forceMount?: true;
260
+ asChild?: boolean;
261
+ /** Fires when the Escape key is pressed. */
262
+ onEscapeKeyDown?: (event: KeyboardEvent) => void;
263
+ /** Fires on pointer event outside the menu. */
264
+ onPointerDownOutside?: (event: CustomEvent<{ originalEvent: PointerEvent }>) => void;
265
+ /** Fires on any interaction outside (focus + pointer). */
266
+ onInteractOutside?: (event: Event) => void;
267
+ /** Items + groups + separators + sub-menus. */
268
+ children?: React.ReactNode;
269
+ className?: string;
270
+ }
271
+
272
+ const MenubarContent = React.forwardRef<
273
+ React.ElementRef<typeof MenubarPrimitive.Content>,
274
+ MenubarContentProps
275
+ >(({ className, align = "start", alignOffset = -4, sideOffset = 8, ...props }, ref) => (
276
+ <MenubarPrimitive.Portal>
277
+ <MenubarPrimitive.Content
278
+ ref={ref}
279
+ align={align}
280
+ alignOffset={alignOffset}
281
+ sideOffset={sideOffset}
282
+ className={cn(
283
+ "z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[var(--radix-menubar-content-transform-origin)]",
284
+ className,
285
+ )}
286
+ {...props}
287
+ />
288
+ </MenubarPrimitive.Portal>
289
+ ));
290
+ MenubarContent.displayName = MenubarPrimitive.Content.displayName;
291
+
292
+ export interface MenubarItemProps
293
+ extends React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> {
294
+ /**
295
+ * Add left padding so this row aligns with sibling checkbox/radio
296
+ * items that have leading indicators.
297
+ * @default false
298
+ */
299
+ inset?: boolean;
300
+ /**
301
+ * Disable the item.
302
+ * @default false
303
+ */
304
+ disabled?: boolean;
305
+ /** Fires when the item is activated (click, Enter, Space). */
306
+ onSelect?: (event: Event) => void;
307
+ /**
308
+ * Render as a Radix Slot — wrap a router `<Link>` to use the item as
309
+ * navigation.
310
+ * @default false
311
+ */
312
+ asChild?: boolean;
313
+ /** Item label or any nested elements. */
314
+ children?: React.ReactNode;
315
+ className?: string;
316
+ }
317
+
318
+ const MenubarItem = React.forwardRef<
319
+ React.ElementRef<typeof MenubarPrimitive.Item>,
320
+ MenubarItemProps
321
+ >(({ className, inset, ...props }, ref) => (
322
+ <MenubarPrimitive.Item
323
+ ref={ref}
324
+ className={cn(
325
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
326
+ inset && "pl-8",
327
+ className,
328
+ )}
329
+ {...props}
330
+ />
331
+ ));
332
+ MenubarItem.displayName = MenubarPrimitive.Item.displayName;
333
+
334
+ export interface MenubarCheckboxItemProps
335
+ extends React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem> {
336
+ /** Controlled checked state. */
337
+ checked?: boolean | "indeterminate";
338
+ /** Fires when the user toggles the item. */
339
+ onCheckedChange?: (checked: boolean) => void;
340
+ /**
341
+ * Disable the item.
342
+ * @default false
343
+ */
344
+ disabled?: boolean;
345
+ /** Fires when the item is activated. */
346
+ onSelect?: (event: Event) => void;
347
+ asChild?: boolean;
348
+ /** Item label. */
349
+ children?: React.ReactNode;
350
+ className?: string;
351
+ }
352
+
353
+ const MenubarCheckboxItem = React.forwardRef<
354
+ React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
355
+ MenubarCheckboxItemProps
356
+ >(({ className, children, checked, ...props }, ref) => (
357
+ <MenubarPrimitive.CheckboxItem
358
+ ref={ref}
359
+ className={cn(
360
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
361
+ className,
362
+ )}
363
+ checked={checked}
364
+ {...props}
365
+ >
366
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
367
+ <MenubarPrimitive.ItemIndicator>
368
+ <Check className="h-4 w-4" />
369
+ </MenubarPrimitive.ItemIndicator>
370
+ </span>
371
+ {children}
372
+ </MenubarPrimitive.CheckboxItem>
373
+ ));
374
+ MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName;
375
+
376
+ export interface MenubarRadioItemProps
377
+ extends React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem> {
378
+ /** Required — value reported when this item is selected. Match parent `<RadioGroup>`'s value. */
379
+ value: string;
380
+ /**
381
+ * Disable the item.
382
+ * @default false
383
+ */
384
+ disabled?: boolean;
385
+ /** Fires when the item is activated. */
386
+ onSelect?: (event: Event) => void;
387
+ asChild?: boolean;
388
+ children?: React.ReactNode;
389
+ className?: string;
390
+ }
391
+
392
+ const MenubarRadioItem = React.forwardRef<
393
+ React.ElementRef<typeof MenubarPrimitive.RadioItem>,
394
+ MenubarRadioItemProps
395
+ >(({ className, children, ...props }, ref) => (
396
+ <MenubarPrimitive.RadioItem
397
+ ref={ref}
398
+ className={cn(
399
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
400
+ className,
401
+ )}
402
+ {...props}
403
+ >
404
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
405
+ <MenubarPrimitive.ItemIndicator>
406
+ <Circle className="h-4 w-4 fill-current" />
407
+ </MenubarPrimitive.ItemIndicator>
408
+ </span>
409
+ {children}
410
+ </MenubarPrimitive.RadioItem>
411
+ ));
412
+ MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName;
413
+
414
+ export interface MenubarLabelProps
415
+ extends React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> {
416
+ /**
417
+ * Add left padding so this row aligns with sibling checkbox/radio
418
+ * items.
419
+ * @default false
420
+ */
421
+ inset?: boolean;
422
+ asChild?: boolean;
423
+ /** Section heading text. */
424
+ children?: React.ReactNode;
425
+ className?: string;
426
+ }
427
+
428
+ const MenubarLabel = React.forwardRef<
429
+ React.ElementRef<typeof MenubarPrimitive.Label>,
430
+ MenubarLabelProps
431
+ >(({ className, inset, ...props }, ref) => (
432
+ <MenubarPrimitive.Label
433
+ ref={ref}
434
+ className={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
435
+ {...props}
436
+ />
437
+ ));
438
+ MenubarLabel.displayName = MenubarPrimitive.Label.displayName;
439
+
440
+ export interface MenubarSeparatorProps
441
+ extends React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator> {
442
+ asChild?: boolean;
443
+ className?: string;
444
+ }
445
+
446
+ const MenubarSeparator = React.forwardRef<
447
+ React.ElementRef<typeof MenubarPrimitive.Separator>,
448
+ MenubarSeparatorProps
449
+ >(({ className, ...props }, ref) => (
450
+ <MenubarPrimitive.Separator
451
+ ref={ref}
452
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
453
+ {...props}
454
+ />
455
+ ));
456
+ MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName;
457
+
458
+ export interface MenubarShortcutProps extends React.HTMLAttributes<HTMLSpanElement> {
459
+ /** Keyboard shortcut text (e.g. "⌘K", "⌃Z"). */
460
+ children?: React.ReactNode;
461
+ className?: string;
462
+ }
463
+
464
+ const MenubarShortcut = ({ className, ...props }: MenubarShortcutProps) => {
465
+ return (
466
+ <span
467
+ className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)}
468
+ {...props}
469
+ />
470
+ );
471
+ };
472
+ MenubarShortcut.displayName = "MenubarShortcut";
473
+
474
+ export {
475
+ Menubar,
476
+ MenubarCheckboxItem,
477
+ MenubarContent,
478
+ MenubarGroup,
479
+ MenubarItem,
480
+ MenubarLabel,
481
+ MenubarMenu,
482
+ MenubarPortal,
483
+ MenubarRadioGroup,
484
+ MenubarRadioItem,
485
+ MenubarSeparator,
486
+ MenubarShortcut,
487
+ MenubarSub,
488
+ MenubarSubContent,
489
+ MenubarSubTrigger,
490
+ MenubarTrigger,
491
+ };
@@ -0,0 +1,101 @@
1
+ "use client";
2
+
3
+ import { Menu, X } from "lucide-react";
4
+ import * as React from "react";
5
+
6
+ import { cn } from "../../lib/utils";
7
+ import { Button } from "../atoms/button";
8
+
9
+ export interface NavLink {
10
+ label: string;
11
+ href: string;
12
+ external?: boolean;
13
+ }
14
+
15
+ export interface NavBarProps extends React.HTMLAttributes<HTMLElement> {
16
+ logo?: React.ReactNode;
17
+ links?: NavLink[];
18
+ actions?: React.ReactNode;
19
+ sticky?: boolean;
20
+ /** Custom link component (e.g. Next.js Link) for client-side navigation */
21
+ linkComponent?: React.ElementType;
22
+ }
23
+
24
+ const NavBar = React.forwardRef<HTMLElement, NavBarProps>(
25
+ ({ logo, links = [], actions, sticky = true, linkComponent, className, ...props }, ref) => {
26
+ const [mobileOpen, setMobileOpen] = React.useState(false);
27
+ const LinkEl = linkComponent || "a";
28
+
29
+ return (
30
+ <>
31
+ <nav
32
+ ref={ref}
33
+ className={cn(
34
+ "left-0 right-0 top-0 z-50 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60",
35
+ sticky && "fixed",
36
+ className,
37
+ )}
38
+ {...props}
39
+ >
40
+ <div className="mx-auto flex h-14 max-w-6xl items-center justify-between px-4 sm:px-6">
41
+ {/* Logo */}
42
+ {logo && <div className="shrink-0">{logo}</div>}
43
+
44
+ {/* Desktop links */}
45
+ <div className="hidden items-center gap-6 md:flex">
46
+ {links.map((link) => (
47
+ <LinkEl
48
+ key={link.href}
49
+ href={link.href}
50
+ className="text-sm font-medium text-muted-foreground no-underline transition-colors hover:text-foreground"
51
+ {...(link.external ? { target: "_blank", rel: "noopener noreferrer" } : {})}
52
+ >
53
+ {link.label}
54
+ </LinkEl>
55
+ ))}
56
+ {actions}
57
+ </div>
58
+
59
+ {/* Mobile hamburger */}
60
+ {(links.length > 0 || actions) && (
61
+ <Button
62
+ variant="ghost"
63
+ size="icon"
64
+ className="md:hidden"
65
+ onClick={() => setMobileOpen((v) => !v)}
66
+ aria-label={mobileOpen ? "Close menu" : "Open menu"}
67
+ >
68
+ {mobileOpen ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
69
+ </Button>
70
+ )}
71
+ </div>
72
+
73
+ {/* Mobile dropdown */}
74
+ {mobileOpen && (
75
+ <div className="border-t md:hidden">
76
+ <div className="flex flex-col gap-1 px-4 py-3 sm:px-6">
77
+ {links.map((link) => (
78
+ <LinkEl
79
+ key={link.href}
80
+ href={link.href}
81
+ onClick={() => setMobileOpen(false)}
82
+ className="rounded-md px-3 py-2.5 text-sm font-medium text-muted-foreground no-underline transition-colors hover:bg-accent hover:text-foreground"
83
+ {...(link.external ? { target: "_blank", rel: "noopener noreferrer" } : {})}
84
+ >
85
+ {link.label}
86
+ </LinkEl>
87
+ ))}
88
+ {actions && <div className="px-3 py-2.5">{actions}</div>}
89
+ </div>
90
+ </div>
91
+ )}
92
+ </nav>
93
+ {/* Spacer when sticky to prevent content from hiding behind navbar */}
94
+ {sticky && <div className="h-14" />}
95
+ </>
96
+ );
97
+ },
98
+ );
99
+ NavBar.displayName = "NavBar";
100
+
101
+ export { NavBar };