textarea]:h-auto",
+
+ // Variants based on alignment.
+ "has-[>[data-align=inline-start]]:[&>input]:pl-2",
+ "has-[>[data-align=inline-end]]:[&>input]:pr-2",
+ "has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
+ "has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
+
+ // Focus state.
+ "has-[[data-slot=input-group-control]:focus-visible]:ring-ring has-[[data-slot=input-group-control]:focus-visible]:ring-1",
+
+ // Error state.
+ "has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
+
+ className,
+ )}
+ {...props}
+ />
+ );
+}
+
+const inputGroupAddonVariants = cva(
+ "text-muted-foreground flex h-auto cursor-text select-none items-center justify-center gap-2 py-1.5 text-sm font-medium group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",
+ {
+ variants: {
+ align: {
+ "inline-start":
+ "order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]",
+ "inline-end":
+ "order-last pr-3 has-[>button]:mr-[-0.4rem] has-[>kbd]:mr-[-0.35rem]",
+ "block-start":
+ "[.border-b]:pb-3 order-first w-full justify-start px-3 pt-3 group-has-[>input]/input-group:pt-2.5",
+ "block-end":
+ "[.border-t]:pt-3 order-last w-full justify-start px-3 pb-3 group-has-[>input]/input-group:pb-2.5",
+ },
+ },
+ defaultVariants: {
+ align: "inline-start",
+ },
+ },
+);
+
+function InputGroupAddon({
+ className,
+ align = "inline-start",
+ ...props
+}: React.ComponentProps<"div"> & VariantProps
) {
+ return (
+ {
+ if ((e.target as HTMLElement).closest("button")) {
+ return;
+ }
+ e.currentTarget.parentElement?.querySelector("input")?.focus();
+ }}
+ {...props}
+ />
+ );
+}
+
+const inputGroupButtonVariants = cva(
+ "flex items-center gap-2 text-sm shadow-none",
+ {
+ variants: {
+ size: {
+ xs: "h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-2 has-[>svg]:px-2 [&>svg:not([class*='size-'])]:size-3.5",
+ sm: "h-8 gap-1.5 rounded-md px-2.5 has-[>svg]:px-2.5",
+ "icon-xs":
+ "size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
+ "icon-sm": "size-8 p-0 has-[>svg]:p-0",
+ },
+ },
+ defaultVariants: {
+ size: "xs",
+ },
+ },
+);
+
+function InputGroupButton({
+ className,
+ type = "button",
+ variant = "ghost",
+ size = "xs",
+ ...props
+}: Omit, "size"> &
+ VariantProps) {
+ return (
+
+ );
+}
+
+function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
+ return (
+
+ );
+}
+
+function InputGroupInput({
+ className,
+ ...props
+}: React.ComponentProps<"input">) {
+ return (
+
+ );
+}
+
+function InputGroupTextarea({
+ className,
+ ...props
+}: React.ComponentProps<"textarea">) {
+ return (
+
+ );
+}
+
+export {
+ InputGroup,
+ InputGroupAddon,
+ InputGroupButton,
+ InputGroupText,
+ InputGroupInput,
+ InputGroupTextarea,
+};
diff --git a/apps/gui/src/components/ui/menubar.tsx b/apps/gui/src/components/ui/menubar.tsx
new file mode 100644
index 0000000..57ed7f6
--- /dev/null
+++ b/apps/gui/src/components/ui/menubar.tsx
@@ -0,0 +1,292 @@
+"use client";
+
+import * as React from "react";
+import { Menu as MenuPrimitive } from "@base-ui/react/menu";
+import { Menubar as MenubarPrimitive } from "@base-ui/react/menubar";
+import { CheckIcon, ChevronRight } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+
+function Menubar({ className, ...props }: MenubarPrimitive.Props) {
+ return (
+
+ );
+}
+
+function MenubarMenu({ ...props }: MenuPrimitive.Root.Props) {
+ return ;
+}
+
+function MenubarGroup({ ...props }: MenuPrimitive.Group.Props) {
+ return ;
+}
+
+function MenubarPortal({ ...props }: MenuPrimitive.Portal.Props) {
+ return ;
+}
+
+function MenubarTrigger({ className, ...props }: MenuPrimitive.Trigger.Props) {
+ return (
+
+ );
+}
+
+function MenubarContent({
+ className,
+ align = "start",
+ alignOffset = -4,
+ sideOffset = 8,
+ ...props
+}: MenuPrimitive.Popup.Props & {
+ align?: MenuPrimitive.Positioner.Props["align"];
+ alignOffset?: MenuPrimitive.Positioner.Props["alignOffset"];
+ sideOffset?: MenuPrimitive.Positioner.Props["sideOffset"];
+}) {
+ return (
+
+
+
+
+
+ );
+}
+
+function MenubarItem({
+ className,
+ inset,
+ variant = "default",
+ ...props
+}: MenuPrimitive.Item.Props & {
+ inset?: boolean;
+ variant?: "default" | "destructive";
+}) {
+ return (
+
+ );
+}
+
+function MenubarCheckboxItem({
+ className,
+ children,
+ checked,
+ inset,
+ ...props
+}: MenuPrimitive.CheckboxItem.Props & {
+ inset?: boolean;
+}) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ );
+}
+
+function MenubarRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {
+ return (
+
+ );
+}
+
+function MenubarRadioItem({
+ className,
+ children,
+ inset,
+ ...props
+}: MenuPrimitive.RadioItem.Props & {
+ inset?: boolean;
+}) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ );
+}
+
+function MenubarLabel({
+ className,
+ inset,
+ ...props
+}: MenuPrimitive.GroupLabel.Props & {
+ inset?: boolean;
+}) {
+ return (
+
+ );
+}
+
+function MenubarSeparator({
+ className,
+ ...props
+}: MenuPrimitive.Separator.Props) {
+ return (
+
+ );
+}
+
+function MenubarShortcut({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+
+ );
+}
+
+// Submenu components using SubmenuRoot and SubmenuTrigger
+function MenubarSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {
+ return ;
+}
+
+function MenubarSubTrigger({
+ className,
+ inset,
+ children,
+ ...props
+}: MenuPrimitive.SubmenuTrigger.Props & {
+ inset?: boolean;
+}) {
+ return (
+
+ {children}
+
+
+ );
+}
+
+function MenubarSubContent({
+ className,
+ align = "start",
+ alignOffset = -4,
+ sideOffset = 8,
+ ...props
+}: MenuPrimitive.Popup.Props & {
+ align?: MenuPrimitive.Positioner.Props["align"];
+ alignOffset?: MenuPrimitive.Positioner.Props["alignOffset"];
+ sideOffset?: MenuPrimitive.Positioner.Props["sideOffset"];
+}) {
+ return (
+
+
+
+
+
+ );
+}
+
+export {
+ Menubar,
+ MenubarPortal,
+ MenubarMenu,
+ MenubarTrigger,
+ MenubarContent,
+ MenubarGroup,
+ MenubarSeparator,
+ MenubarLabel,
+ MenubarItem,
+ MenubarShortcut,
+ MenubarCheckboxItem,
+ MenubarRadioGroup,
+ MenubarRadioItem,
+ MenubarSub,
+ MenubarSubTrigger,
+ MenubarSubContent,
+};
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 058feeb..eb055c7 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -342,6 +342,9 @@ importers:
"@radix-ui/react-dialog":
specifier: ^1.1.19
version: 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ "@radix-ui/react-dropdown-menu":
+ specifier: ^2.1.20
+ version: 2.1.20(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
"@radix-ui/react-separator":
specifier: ^1.1.11
version: 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)