Skip to content

Commit

Permalink
feat(menu): support sub items
Browse files Browse the repository at this point in the history
  • Loading branch information
TommyLindh2 committed Sep 14, 2023
1 parent 663b367 commit 2b3c8d8
Show file tree
Hide file tree
Showing 13 changed files with 881 additions and 24 deletions.
1 change: 1 addition & 0 deletions src/components/menu-list/menu-list-renderer-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export interface MenuListRendererConfig {
badgeIcons?: boolean;
type?: MenuListType;
iconSize?: IconSize;
lazyLoadItems?: boolean;
}
16 changes: 16 additions & 0 deletions src/components/menu-list/menu-list-renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ export class MenuListRenderer {
>
{item.icon ? this.renderIcon(this.config, item) : null}
{this.renderText(item)}
{this.renderSubMenuIcon(item)}
{this.renderNotification(item)}
{this.twoLines && this.avatarList ? this.renderDivider() : null}
</li>
Expand Down Expand Up @@ -167,6 +168,14 @@ export class MenuListRenderer {
);
};

private renderSubMenuIcon = (item: MenuItem) => {
if (!this.hasSubItems(item)) {
return;
}

return <limel-icon class="sub-menu-icon" name="-lime-caret-right" />;
};

private renderCommandText = (item: MenuItem) => {
if (!('commandText' in item)) {
return;
Expand Down Expand Up @@ -231,4 +240,11 @@ export class MenuListRenderer {

return <hr class={classes} />;
};

private hasSubItems = (item: MenuItem): boolean => {
return (
!item.isLeafNode &&
(this.config.lazyLoadItems || item.subItems?.length)
);
};
}
5 changes: 5 additions & 0 deletions src/components/menu-list/menu-list.scss
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,8 @@
limel-badge {
transform: translateX(0.75rem);
}

.sub-menu-icon {
width: 1rem;
transform: translateX(0.75rem);
}
4 changes: 4 additions & 0 deletions src/components/menu-list/menu-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ export class MenuList {
@Prop()
public type: MenuListType;

@Prop()
public lazyLoadItems: boolean;

/**
* By default, lists will display 3 lines of text, and then truncate the rest.
* Consumers can increase or decrease this number by specifying
Expand Down Expand Up @@ -99,6 +102,7 @@ export class MenuList {
badgeIcons: this.badgeIcons,
type: this.type,
iconSize: this.iconSize,
lazyLoadItems: this.lazyLoadItems,
};

const html = this.MenuListRenderer.render(this.items, this.config);
Expand Down
2 changes: 2 additions & 0 deletions src/components/menu/examples/menu-composite.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ export class MenuCompositeExample {
};

delete this.schema.properties.label;
delete this.schema.properties.searcher;
delete this.schema.properties.loadSubItems;
}

public render() {
Expand Down
107 changes: 107 additions & 0 deletions src/components/menu/examples/menu-searchable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import {
MenuItem,
ListSeparator,
LimelMenuCustomEvent,
} from '@limetech/lime-elements';
import { Component, State, h } from '@stencil/core';
import { SearchMenuItems } from './subitems-search';

const longSubList: MenuItem[] = [];
for (let index = 1; index <= 50; index++) {
longSubList.push({
text: `Item ${index}`,
});
}

/**
* Searchable items
* @link subitems-search.ts
*/
@Component({
tag: 'limel-example-menu-searchable',
shadow: true,
})
export class MenuSubItemsExample {
private items: Array<MenuItem | ListSeparator> = [
{
text: 'Format',
subItems: [
{
text: 'Bold',
icon: 'bold',
},
{
text: 'Italic',
icon: 'italic',
},
{
text: 'Bullets and numbering',
icon: 'bulleted_list',
subItems: [
{
text: 'Numbered list',
icon: 'numbered_list',
},
{
text: 'Bullet list',
icon: 'bulleted_list',
},
{
text: 'Checklist',
icon: 'todo_list',
},
],
},
],
},
{
text: 'Edit',
subItems: [
{
text: 'Copy',
icon: 'copy',
},
{
text: 'Cut',
icon: 'cut',
},
{ separator: true },
{
text: 'Paste',
icon: 'paste',
},
],
},
{
text: 'Long sub list',
subItems: longSubList,
},
];

@State()
private lastSelectedItem: MenuItem;

public render() {
return [
<limel-menu
items={this.items}
searcher={this.handleSearch}
onSelect={this.handleSelect}
>
<limel-button label="Menu" slot="trigger" />
</limel-menu>,
<limel-example-value
label="Last selected item"
value={this.lastSelectedItem?.text ?? ''}
/>,
];
}

private handleSearch = async (queryString: string) => {
return SearchMenuItems(queryString, this.items);
};

private handleSelect = (event: LimelMenuCustomEvent<MenuItem>) => {
this.lastSelectedItem = event.detail;
};
}
8 changes: 8 additions & 0 deletions src/components/menu/examples/menu-size.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
:host(limel-example-menu-size) {
display: flex;
flex-direction: column;
}

// limel-button {
// flex-grow: 1;
// }
118 changes: 118 additions & 0 deletions src/components/menu/examples/menu-size.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { MenuItem, ListSeparator } from '@limetech/lime-elements';
import { Component, Element, Host, State, h } from '@stencil/core';

/**
* Resize the menu drop-down
*/
@Component({
tag: 'limel-example-menu-size',
styleUrl: 'menu-size.scss',
shadow: true,
})
export class MenuSubItemsExample {
private items: Array<MenuItem | ListSeparator> = [
{
text: 'Real super heroes',
subItems: [
{ text: 'Superman' },
{ text: 'Iron Man' },
{ text: 'Spider-Man' },
{ text: 'The Flash' },
{ text: 'Wonder Woman' },
{ text: 'Hulk' },
{ text: 'Captain America' },
{ text: 'Batman' },
{ text: 'Thor' },
{ text: 'Green Lantern (Hal Jordan)' },
],
},
{
text: 'Fake super heroes',
subItems: [
{ text: 'Tech Titan' },
{ text: 'Data Dynamo' },
{ text: 'Code Crusader' },
{ text: 'Cyber Avenger' },
{ text: 'Robot Wrangler' },
{ text: 'Pixel Protector' },
{ text: 'Bit Byte Blaster' },
{ text: 'Gadget Guru' },
{ text: 'Nerd Ninja' },
{ text: 'Geek Guardian' },
{ text: 'Binary Bandit' },
{ text: 'Web Wizard' },
{ text: 'Logic Lord' },
{ text: 'Captain Compiler' },
{ text: 'Mega Miner' },
{ text: 'Virus Vanquisher' },
{ text: 'Data Duchess' },
{ text: 'Hacker Hero' },
{ text: 'AI Aviator' },
{ text: 'Gizmo Gladiator' },
{ text: 'Pixel Punisher' },
{ text: 'Quantum Quasar' },
{ text: 'Crypto Crusader' },
{ text: 'Nanotech Ninja' },
{ text: 'Debugger Demon' },
{ text: 'Byte Brawler' },
{ text: 'Robot Ruler' },
{ text: 'Space Syntax' },
{ text: 'Infinite Infiltrator' },
{ text: 'Syntax Sorcerer' },
{ text: 'Genius Guardian' },
{ text: 'Data Druid' },
{ text: 'Virtual Vigilante' },
{ text: 'Quantum Quencher' },
{ text: 'Cyber Sentinel' },
{ text: 'Nanobot Ninja' },
{ text: 'Circuit Sage' },
{ text: 'Binary Blitzer' },
{ text: 'Pixel Phantom' },
{ text: 'Hologram Hero' },
{ text: 'Logic Luminator' },
{ text: 'AI Alchemist' },
{ text: 'Tech Trebuchet' },
{ text: 'Data Duchess' },
{ text: 'Virtual Voyager' },
{ text: 'Syntax Sorcerer' },
{ text: 'Genius Guardian' },
],
},
];

@Element()
public host: HTMLElement;

@State()
private isMenuOpen: boolean = false;

public render() {
const width = this.host?.offsetWidth;

return (
<Host>
<limel-button
label="Open the wide menu"
onClick={this.onButtonClick}
/>
<limel-menu
open={this.isMenuOpen}
style={{
'--menu-surface-width': width ? `${width}px` : '',
}}
items={this.items}
onSelect={this.closeMenu}
onCancel={this.closeMenu}
/>
</Host>
);
}

private onButtonClick = () => {
this.isMenuOpen = true;
};

private closeMenu = () => {
this.isMenuOpen = false;
};
}
74 changes: 74 additions & 0 deletions src/components/menu/examples/menu-subitems-lazy-loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {
MenuItem,
ListSeparator,
LimelMenuCustomEvent,
} from '@limetech/lime-elements';
import { Component, State, h } from '@stencil/core';

/**
* Lazy loading infinite amount of sub menu items.
*/
@Component({
tag: 'limel-example-menu-subitems-lazy-loading',
shadow: true,
})
export class MenuSubItemsLazyLoadingExample {
private items: Array<MenuItem | ListSeparator> = [
{
text: 'Item 1',
},
{
text: 'Item 2',
},
{
text: 'Item 3',
},
{
text: 'Item 4',
},
{
text: 'Item 5',
},
];

@State()
private lastSelectedItem: string;

public render() {
return [
<limel-menu
lazyLoadItems
loadSubItems={this.handleLoadSubItems}
items={this.items}
onSelect={this.handleSelect}
>
<limel-button label="Menu" slot="trigger" />
</limel-menu>,
<limel-example-value
label="Last selected item"
value={this.lastSelectedItem}
/>,
];
}

private handleLoadSubItems = async (
item: MenuItem
): Promise<MenuItem[]> => {
return new Promise<MenuItem[]>((resolve) => {
setTimeout(() => {
const subItems = [];
for (let i = 1; i < 6; i++) {
subItems.push({
text: `${item.text}.${i}`,
});
}

resolve(subItems);
}, 1000);
});
};

private handleSelect = (event: LimelMenuCustomEvent<MenuItem>) => {
this.lastSelectedItem = event.detail.text;
};
}
Loading

0 comments on commit 2b3c8d8

Please sign in to comment.