From b3632d9248a95ab4d14885e416334fd9877d1a92 Mon Sep 17 00:00:00 2001 From: Rohan Chakraborty Date: Tue, 10 Mar 2026 17:41:12 +0530 Subject: [PATCH] feat: menubar component --- apps/www/src/components/playground/index.ts | 1 + .../playground/menubar-examples.tsx | 44 ++++++ .../content/docs/components/menubar/demo.ts | 98 ++++++++++++++ .../content/docs/components/menubar/index.mdx | 64 +++++++++ .../content/docs/components/menubar/props.ts | 33 +++++ .../raystack/components/menu/menu-trigger.tsx | 16 ++- .../raystack/components/menu/menu.module.css | 4 + .../menubar/__tests__/menubar.test.tsx | 128 ++++++++++++++++++ packages/raystack/components/menubar/index.ts | 1 + .../components/menubar/menubar.module.css | 14 ++ .../raystack/components/menubar/menubar.tsx | 28 ++++ packages/raystack/index.tsx | 1 + 12 files changed, 431 insertions(+), 1 deletion(-) create mode 100644 apps/www/src/components/playground/menubar-examples.tsx create mode 100644 apps/www/src/content/docs/components/menubar/demo.ts create mode 100644 apps/www/src/content/docs/components/menubar/index.mdx create mode 100644 apps/www/src/content/docs/components/menubar/props.ts create mode 100644 packages/raystack/components/menubar/__tests__/menubar.test.tsx create mode 100644 packages/raystack/components/menubar/index.ts create mode 100644 packages/raystack/components/menubar/menubar.module.css create mode 100644 packages/raystack/components/menubar/menubar.tsx diff --git a/apps/www/src/components/playground/index.ts b/apps/www/src/components/playground/index.ts index 62ec07833..a5053af8f 100644 --- a/apps/www/src/components/playground/index.ts +++ b/apps/www/src/components/playground/index.ts @@ -28,6 +28,7 @@ export * from './label-examples'; export * from './link-examples'; export * from './list-examples'; export * from './menu-examples'; +export * from './menubar-examples'; export * from './popover-examples'; export * from './preview-card-examples'; export * from './radio-examples'; diff --git a/apps/www/src/components/playground/menubar-examples.tsx b/apps/www/src/components/playground/menubar-examples.tsx new file mode 100644 index 000000000..8f1c7a882 --- /dev/null +++ b/apps/www/src/components/playground/menubar-examples.tsx @@ -0,0 +1,44 @@ +'use client'; + +import { Flex, Menu, Menubar } from '@raystack/apsara'; +import PlaygroundLayout from './playground-layout'; + +export function MenubarExamples() { + return ( + + + + + File + + New Tab + New Window + + Print + + + + Edit + + Undo + Redo + + Cut + Copy + Paste + + + + View + + Zoom In + Zoom Out + + Fullscreen + + + + + + ); +} diff --git a/apps/www/src/content/docs/components/menubar/demo.ts b/apps/www/src/content/docs/components/menubar/demo.ts new file mode 100644 index 000000000..d32483435 --- /dev/null +++ b/apps/www/src/content/docs/components/menubar/demo.ts @@ -0,0 +1,98 @@ +'use client'; + +export const preview = { + type: 'code', + code: ` + + + File + + New Tab + New Window + + + Share + + Email Link + Messages + + + + Print + + + + Edit Action + + Undo Action + Redo Action + + Cut + Copy + Paste + + + + View + + Zoom In + Zoom Out + + Fullscreen + + + ` +}; + +export const verticalDemo = { + type: 'code', + code: ` + + + File + + New + Open + Save + + + + Edit + + Undo + Redo + + + ` +}; + +export const autocompleteDemo = { + type: 'code', + code: ` + + + Settings + + Preferences + Themes + + + + Actions + + + General + Assign member... + Subscribe... + Rename... + + + + Editing + Copy + Delete... + + + + ` +}; diff --git a/apps/www/src/content/docs/components/menubar/index.mdx b/apps/www/src/content/docs/components/menubar/index.mdx new file mode 100644 index 000000000..e5c99f07d --- /dev/null +++ b/apps/www/src/content/docs/components/menubar/index.mdx @@ -0,0 +1,64 @@ +--- +title: Menubar +description: A horizontal menu bar providing commands and options for your application, typically used at the top of a window. +source: packages/raystack/components/menubar +tag: new +--- + +import { preview, verticalDemo, autocompleteDemo } from "./demo.ts"; + + + +## Usage + +```tsx +import { Menu, Menubar } from '@raystack/apsara' + + + + File + + New + Open + + + + Edit + + Undo + Redo + + + +``` + +The `Menubar` component wraps multiple `Menu` instances. Menu triggers are automatically styled when inside a Menubar. If you pass a `render` prop to `Menu.Trigger`, the default menubar trigger styling is skipped, allowing full custom rendering. + +## API Reference + +### Root + +The container element for the menubar. + + + +## Examples + +### Vertical + +Use the `orientation` prop to render a vertical menubar. + + + +### Autocomplete + +Use the `autocomplete` prop on `Menu` to enable search filtering within a menubar menu. + + + +## Accessibility + +- Uses `role="menubar"` for proper ARIA semantics +- Supports keyboard navigation between menu triggers using arrow keys +- `loopFocus` cycles focus from last to first trigger (enabled by default) +- Each menu trigger opens its dropdown on click or Enter/Space diff --git a/apps/www/src/content/docs/components/menubar/props.ts b/apps/www/src/content/docs/components/menubar/props.ts new file mode 100644 index 000000000..f4a4ce26e --- /dev/null +++ b/apps/www/src/content/docs/components/menubar/props.ts @@ -0,0 +1,33 @@ +import { ReactElement } from 'react'; + +export interface MenubarProps { + /** + * Whether the menubar is modal. + * @defaultValue true + */ + modal?: boolean; + + /** + * Whether the whole menubar is disabled. + * @defaultValue false + */ + disabled?: boolean; + + /** + * The orientation of the menubar. + * @defaultValue "horizontal" + */ + orientation?: 'horizontal' | 'vertical'; + + /** + * Whether to loop keyboard focus back to the first item when the end of the list is reached. + * @defaultValue true + */ + loopFocus?: boolean; + + /** Additional CSS class names */ + className?: string; + + /** Replaces the rendered element with a custom one via Base UI's render prop pattern */ + render?: ReactElement; +} diff --git a/packages/raystack/components/menu/menu-trigger.tsx b/packages/raystack/components/menu/menu-trigger.tsx index a6ef279e2..05830054c 100644 --- a/packages/raystack/components/menu/menu-trigger.tsx +++ b/packages/raystack/components/menu/menu-trigger.tsx @@ -4,7 +4,10 @@ import { Autocomplete as AutocompletePrimitive } from '@base-ui/react'; import { Menu as MenuPrimitive } from '@base-ui/react/menu'; import { forwardRef } from 'react'; import { TriangleRightIcon } from '~/icons'; +import { Button } from '../button'; +import { useMenubarContext } from '../menubar/menubar'; import { Cell, CellBaseProps } from './cell'; +import styles from './menu.module.css'; import { useMenuContext } from './menu-root'; import { getMatch } from './utils'; @@ -13,10 +16,21 @@ export interface MenuTriggerProps extends MenuPrimitive.Trigger.Props { } export const MenuTrigger = forwardRef( - ({ children, stopPropagation = true, onClick, ...props }, ref) => { + ({ children, stopPropagation = true, onClick, render, ...props }, ref) => { + const inMenubarContext = useMenubarContext(); + const menubarRender = inMenubarContext ? ( +