Skip to content

Commit

Permalink
Side Navigation: support a leading icon for the secondary dropdown in…
Browse files Browse the repository at this point in the history
… the navbar (#3089)

* test side nav with header

* demo

* expanded code

* update logic

* 2927 - add support to the left nav header

* 2927 - setting popper width

* 2992 - buld and test case fixes

* 2927 - side navigation storybook docs and canvas update

* 3066- Add support for icon for side nav header dropdown

* 3006 - code fixes

* 3006 - storybook update

* 3066 - storybook control fixes

* 3066 - css fixes for side nav

* 3066- build fixes

* 3006- fix blue theme and dark mode styles

* 3066 - handling blur and css fixes

* 3066 - revert handle blur

* 3066 - handle blur to popper and storybook fixes

* 3066 - storybook fixes

* 3066 - side nav selected state fix for header dropdown

* 3066 - storybook update for dropdown dynamic content update

* 3066 - storybook fixes

* 3066 - eslint  fix

---------

Co-authored-by: Elisha Sam Peter Prabhu <[email protected]>
  • Loading branch information
prashanthr6383 and ElishaSamPeterPrabhu authored Jan 23, 2025
1 parent 6526acf commit adb8441
Show file tree
Hide file tree
Showing 8 changed files with 244 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ describe('modus-list-item', () => {
expect(root).toEqualHtml(`
<modus-list-item>
<mock:shadow-root>
<li class='standard' tabindex="0">
<li class='standard' part="list-item-li" tabindex="0">
<div class="text-container">
<span class='slot'>
<slot></slot>
</span>
</div>
</div>
</li>
</mock:shadow-root>
</modus-list-item>
Expand All @@ -30,12 +30,12 @@ describe('modus-list-item', () => {
expect(root).toEqualHtml(`
<modus-list-item>
<mock:shadow-root>
<li class='standard' tabindex="0">
<li class='standard'part="list-item-li" tabindex="0">
<div class="text-container">
<span class='slot'>
<slot></slot>
</span>
</div>
</div>
</li>
</mock:shadow-root>
List Item
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export class ModusListItem {
return (
<li
ref={(el) => (this.listItemRef = el)}
part="list-item-li"
class={containerClass}
tabIndex={this.disabled ? -1 : 0}
onClick={() => (!this.disabled ? this.itemClick.emit() : null)}
Expand Down
7 changes: 7 additions & 0 deletions stencil-workspace/src/components/modus-list-item/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ Type: `Promise<void>`



## Shadow Parts

| Part | Description |
| ---------------- | ----------- |
| `"list-item-li"` | |


## Dependencies

### Used by
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@ $modus-dropdown-list-border-color: var(--modus-dropdown-list-border-color, #6a6e
border-radius: $rem-4px;
display: none;
max-height: 200px;
max-width: 298px;
opacity: 1;
overflow-x: auto;
overflow-y: auto;
Expand Down Expand Up @@ -164,4 +163,30 @@ $modus-dropdown-list-border-color: var(--modus-dropdown-list-border-color, #6a6e
right: 0;
top: 0;
}

.list-item::part(list-item-li) {
background-color: $modus-side-navigation-item-bg;
border-radius: unset;
color: $modus-side-navigation-item-color;
}

.list-item::part(list-item-li):hover {
background-color: $modus-side-navigation-item-hover-bg;
}
}

.dropdown-item {
align-items: center;
cursor: pointer;
display: flex;
font-family: $primary-font;
font-size: $rem-14px;
gap: 1rem;
padding: 0.4rem;

.menu-item-text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// eslint-disable-next-line
import { h, Component, Prop, Element, Event, Watch, EventEmitter, Method, State, Host } from '@stencil/core';
import { createPopper, Instance as PopperInstance } from '@popperjs/core';
import { ModusIconMap } from '../../../icons/ModusIconMap';
import { ModusHeaderNavigationItemInfo } from '../modus-side-navigation.models';
import { ModusHeaderNavigationItemInfo, ModusHeaderNavigationItems } from '../modus-side-navigation.models';

@Component({
tag: 'modus-side-navigation-item',
Expand Down Expand Up @@ -84,27 +85,50 @@ export class ModusSideNavigationItem {
if (this.isHeader?.enabled) {
this.showExpandIcon = true;
}
document.addEventListener('mousedown', this.handleDocumentClick.bind(this));
}

disconnectedCallback() {
this._sideNavItemRemoved.emit(this.element);
this.destroyPopper();
document.removeEventListener('mousedown', this.handleDocumentClick.bind(this));
}

handleListItemClick(itemId: string): void {
this.sideNavListItemClicked.emit({ id: itemId });
handleListItemClick(itemId: ModusHeaderNavigationItems): void {
this.sideNavListItemClicked.emit({ id: itemId?.id });
this.dropdownVisible = false; // Close the dropdown
this.label = itemId;
this.label = itemId?.label;

this.menuIcon = itemId?.icon || this.menuIcon;
// this.selected = this.disableSelection ? this.selected : !this.selected;
this.sideNavItemHeaderClicked?.emit({
id: itemId,
id: itemId?.id,
selected: this?.selected,
});
this.destroyPopper(); // Destroy the popper to reset the positioning
}

findParentReference(element: HTMLElement): HTMLElement | null {
let currentNode: HTMLElement | ShadowRoot | null = element;

while (currentNode) {
if (currentNode instanceof HTMLElement && currentNode.matches('nav')) {
return currentNode;
}

if (currentNode instanceof ShadowRoot) {
currentNode = currentNode.host as HTMLElement;
} else {
currentNode = currentNode.parentElement || (currentNode.getRootNode() as HTMLElement);
}
}

return null;
}

setupPopper(expanded: boolean): void {
if (!this.referenceRef || !this.dropdownRef) return;
const parentReference = this.findParentReference(this.element)?.clientWidth;
this.popperInstance = createPopper(this.referenceRef, this.dropdownRef, {
placement: expanded ? 'bottom-start' : 'right',
modifiers: [
Expand All @@ -121,9 +145,9 @@ export class ModusSideNavigationItem {
phase: 'write',
fn: ({ state }) => {
if (state.placement.startsWith('bottom')) {
state.elements.popper.style.width = '300px';
state.elements.popper.style.width = `${parentReference - 2}px` || '300px';
} else if (state.placement.startsWith('right')) {
state.elements.popper.style.width = '150px';
state.elements.popper.style.width = `250px`;
}
},
},
Expand All @@ -141,6 +165,12 @@ export class ModusSideNavigationItem {
handleClick(): void {
if (this.disabled) return;

this.selected = this.disableSelection ? this.selected : this.isHeader?.enabled ? false : !this.selected;
this.sideNavItemClicked?.emit({
id: this.element.id,
selected: this?.selected,
});

if (this.isHeader?.enabled) {
this.dropdownVisible = !this.dropdownVisible;

Expand All @@ -151,12 +181,6 @@ export class ModusSideNavigationItem {
}
return;
}

this.selected = this.disableSelection ? this.selected : !this.selected;
this.sideNavItemClicked?.emit({
id: this.element.id,
selected: this?.selected,
});
}

handleKeyDown(e: KeyboardEvent): void {
Expand All @@ -165,15 +189,34 @@ export class ModusSideNavigationItem {
}
}

handleDocumentClick(event: MouseEvent): void {
const target = event.target as HTMLElement;

if (this.dropdownRef?.contains(target) || this.referenceRef?.contains(target)) {
return;
}

this.dropdownVisible = false;
this.destroyPopper();
}

renderDropdown() {
return (
<div
class={`dropdown-list ${this.dropdownVisible ? 'visible' : 'hidden'} list-border animate-list`}
ref={(el) => (this.dropdownRef = el)}>
<modus-list slot="dropdownList">
{this.isHeader?.items?.map((item) => (
<modus-list-item size="large" borderless onClick={() => this.handleListItemClick(item)}>
{item}
{this.isHeader?.items?.map((item: ModusHeaderNavigationItems) => (
<modus-list-item
key={item.id}
class="list-item"
borderless
onClick={() => this.handleListItemClick(item)}
onMouseDown={(e) => e.stopPropagation()}>
<span class="dropdown-item">
<ModusIconMap icon={item?.icon} size="24" />
<span class="menu-item-text">{item?.label}</span>
</span>
</modus-list-item>
))}
</modus-list>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { ModusSideNavigationItemCustomEvent } from '../../components';

export interface ModusHeaderNavigationItems {
id: string;
label: string;
icon?: string;
}

export interface ModusHeaderNavigationItemInfo {
enabled: boolean;
items: string[];
items: ModusHeaderNavigationItems[];
}

export interface ModusSideNavigationItemInfo {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,35 +262,20 @@ Modus side navigation uses CSS variables for theming ex:`--modus-side-navigation
The `isHeader` property enables a dropdown menu in side navigation items when set to true and configured with menu items. This allows dynamic updates to the menu, navigation, or other interactive features.
#
<Story of={SideNavigation.SideNavigationWithHeader} />
```html
<div id="dataTemplate">
<modus-switch id="switch-theme" label="Enable blue theme"></modus-switch>
<br />
<modus-switch id="switch-mode" label="Enable Push Side Navigation"></modus-switch>
<div
style="
width: 100%;
align-items: center;
height: 56px;
box-shadow: 0 0 2px var(--modus-secondary) !important;
margin-top: 10px;
">
style="width: 100%;align-items: center;height: 56px;box-shadow: 0 0 2px var(--modus-secondary)!important; margin-top: 10px;">
<modus-navbar id="navbar" show-apps-menu show-help show-main-menu show-notifications> </modus-navbar>
</div>
<div
id="container"
style="
display: flex;
min-height: 500px;
overflow-y: auto;
position: relative;
box-shadow: 0 0 2px var(--modus-secondary) !important;
">
style="display:flex; min-height:500px; overflow-y: auto; position: relative;box-shadow: 0 0 2px var(--modus-secondary)!important;">
<modus-side-navigation max-width="300px" id="sideNav" target-content="#dataTemplate #panelcontent" mode="overlay">
</modus-side-navigation>
Expand Down Expand Up @@ -353,20 +338,36 @@ The `isHeader` property enables a dropdown menu in side navigation items when se
label: 'Home',
isHeader: {
enabled: true,
items: ['Home', 'Charts', 'Maps'],
items: [
{
id: 'Home',
label: 'Home',
icon: 'home',
},
{
id: 'Charts',
label: 'Charts',
icon: 'bar_graph',
},
{
id: 'Maps',
label: 'Maps',
icon: 'location_arrow',
},
],
},
onSideNavItemHeaderClicked: selectionHeaderHandler,
},
{
id: 'usage-menu',
menuIcon: usageIcon,
label: newItems[0],
menuIcon: newItems[0].icon,
label: newItems[0].label,
onSideNavItemClicked: selectionHandler,
},
{
id: 'styles-menu',
menuIcon: stylesIcon,
label: newItems[1],
menuIcon: newItems[1].icon,
label: newItems[1].label,
onSideNavItemClicked: selectionHandler,
},
];
Expand All @@ -377,13 +378,37 @@ The `isHeader` property enables a dropdown menu in side navigation items when se
let newItems = [];
if (headerLabel === 'Charts') {
newItems = ['Pie Chart', 'Bar Chart'];
newItems = [
{ label: 'Bar graph square', icon: 'bar_graph_square' },
{ label: 'Gantt chart', icon: 'gantt_chart' },
];
getLabel(newItems);
const simulatedEvent = {
detail: { selected: true, id: 'Charts' },
target: { data: [{ id: 'Charts', label: 'Charts' }] },
};
selectionHandler(simulatedEvent);
} else if (headerLabel === 'Maps') {
newItems = ['World Map', 'Region Map'];
newItems = [
{ label: 'World', icon: 'web' },
{ label: 'Region', icon: 'map_poi' },
];
getLabel(newItems);
const simulatedEvent = {
detail: { selected: true, id: 'Maps' },
target: { data: [{ id: 'Maps', label: 'Maps' }] },
};
selectionHandler(simulatedEvent);
} else {
initialize();
const simulatedEvent = {
detail: { selected: true, id: 'Home' },
target: { data: [{ id: 'Home', label: 'Home' }] },
};
selectionHandler(simulatedEvent);
}
};
Expand All @@ -397,7 +422,23 @@ The `isHeader` property enables a dropdown menu in side navigation items when se
label: 'Home',
isHeader: {
enabled: true,
items: ['Home', 'Charts', 'Maps'],
items: [
{
id: 'Home',
label: 'Home',
icon: 'home',
},
{
id: 'Charts',
label: 'Charts',
icon: 'bar_graph',
},
{
id: 'Maps',
label: 'Maps',
icon: 'location_arrow',
},
],
},
onSideNavItemHeaderClicked: selectionHeaderHandler,
},
Expand Down
Loading

0 comments on commit adb8441

Please sign in to comment.