Skip to content

Commit

Permalink
feat: allow adding custom data to menu bar item types (#300)
Browse files Browse the repository at this point in the history
  • Loading branch information
sissbruecker authored Feb 25, 2025
1 parent 7398a5b commit 9b03316
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 12 deletions.
26 changes: 14 additions & 12 deletions packages/react-components/src/MenuBar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type ForwardedRef, forwardRef, type ReactElement } from 'react';
import { type ForwardedRef, forwardRef, type ReactElement, type RefAttributes } from 'react';
import {
MenuBar as _MenuBar,
type MenuBarElement,
Expand All @@ -10,28 +10,28 @@ import { getOriginalItem, mapItemsWithComponents } from './utils/mapItemsWithCom

export * from './generated/MenuBar.js';

export type SubMenuItem = Omit<_SubMenuItem, 'component' | 'children'> & {
export type SubMenuItem<TItemData extends object = object> = Omit<_SubMenuItem<TItemData>, 'component' | 'children'> & {
component?: ReactElement | string;

children?: Array<SubMenuItem>;
children?: Array<SubMenuItem<TItemData>>;
};

export type MenuBarItem = Omit<_MenuBarItem, 'component' | 'children'> & {
export type MenuBarItem<TItemData extends object = object> = Omit<_MenuBarItem<TItemData>, 'component' | 'children'> & {
component?: ReactElement | string;

children?: Array<SubMenuItem>;
children?: Array<SubMenuItem<TItemData>>;
};

export type MenuBarItemSelectedEvent = CustomEvent<{ value: MenuBarItem }>;
export type MenuBarItemSelectedEvent<TItem extends MenuBarItem = MenuBarItem> = CustomEvent<{ value: TItem }>;

export type MenuBarProps = Partial<Omit<_MenuBarProps, 'items' | 'onItemSelected'>> &
export type MenuBarProps<TItem extends MenuBarItem = MenuBarItem> = Partial<Omit<_MenuBarProps, 'items' | 'onItemSelected'>> &
Readonly<{
items?: Array<MenuBarItem>;
items?: Array<TItem>;

onItemSelected?: (event: MenuBarItemSelectedEvent) => void;
onItemSelected?: (event: MenuBarItemSelectedEvent<TItem>) => void;
}>;

function MenuBar(props: MenuBarProps, ref: ForwardedRef<MenuBarElement>): ReactElement | null {
function MenuBar<TItem extends MenuBarItem = MenuBarItem>(props: MenuBarProps<TItem>, ref: ForwardedRef<MenuBarElement>): ReactElement | null {
const [itemPortals, webComponentItems] = mapItemsWithComponents(props.items, 'vaadin-menu-bar-item');

const onItemSelected = props.onItemSelected;
Expand All @@ -42,7 +42,7 @@ function MenuBar(props: MenuBarProps, ref: ForwardedRef<MenuBarElement>): ReactE
value: getOriginalItem(event.detail.value),
});

onItemSelected(event as CustomEvent<{ value: MenuBarItem }>);
onItemSelected(event as MenuBarItemSelectedEvent<TItem>);
}
: undefined;

Expand All @@ -54,6 +54,8 @@ function MenuBar(props: MenuBarProps, ref: ForwardedRef<MenuBarElement>): ReactE
);
}

const ForwardedMenuBar = forwardRef(MenuBar);
const ForwardedMenuBar = forwardRef(MenuBar) as <TItem extends MenuBarItem = MenuBarItem>(
props: MenuBarProps<TItem> & RefAttributes<MenuBarElement>,
) => ReactElement | null;

export { ForwardedMenuBar as MenuBar };
17 changes: 17 additions & 0 deletions test/typings/MenuBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { MenuBar, type MenuBarItem } from '../../packages/react-components/src/MenuBar.js';

const assertType = function <TExpected>(value: TExpected) {
return value;
};

type CustomMenuBarItem = MenuBarItem<{ value: string }>;

const items: CustomMenuBarItem[] = [{ text: 'View', value: 'view' }];

<MenuBar
items={items}
onItemSelected={(e) => {
// Event item type should be inferred from items
assertType<CustomMenuBarItem>(e.detail.value);
}}
/>;
11 changes: 11 additions & 0 deletions test/typings/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,17 @@ const menuBarOnItemSelected: typeof menuBarProps.onItemSelected = (event) => {
};
assertType<typeof menuBarProps.onItemSelected>(menuBarOnItemSelected);

type CustomMenuBarItem = MenuBarItem<{ value: string }>;

const narrowedMenuBarProps = React.createElement(MenuBar<CustomMenuBarItem>, {}).props;
assertType<CustomMenuBarItem[] | undefined>(narrowedMenuBarProps.items);
assertType<CustomMenuBarItem[] | undefined>(narrowedMenuBarProps.items![0].children);

const narrowedContextMenuOnItemSelected: typeof narrowedMenuBarProps.onItemSelected = (event) => {
assertType<CustomMenuBarItem>(event.detail.value);
};
assertType<typeof narrowedMenuBarProps.onItemSelected>(narrowedContextMenuOnItemSelected);

const popoverProps = React.createElement(Popover, {}).props;
type PopoverProps = typeof popoverProps;

Expand Down

0 comments on commit 9b03316

Please sign in to comment.