Skip to content
Merged
5 changes: 5 additions & 0 deletions .changeset/empty-bikes-pay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@solid-design-system/components': minor
---

Added a new attribute `stacked` to the `sd-navigation-item` component that enables a new vertical stacked variant to the horizontal layout.
5 changes: 5 additions & 0 deletions .changeset/rich-cases-sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@solid-design-system/docs': minor
---

Added documentation for the new `stacked` attribute for the `sd-navigation-item` component.
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ export default class SdNavigationItem extends SolidElement {
/** Disables the navigation item. */
@property({ type: Boolean, reflect: true }) disabled = false;

/** Stacks the navigation-item in a vertical layout. Only used if `vertical` is false. */
@property({ type: Boolean, reflect: true }) stacked = false;

/** Appends a chevron to the right side of a navigation item. Only used if `vertical` is true. */
@property({ type: Boolean, reflect: true }) chevron = false;

Expand Down Expand Up @@ -122,6 +125,10 @@ export default class SdNavigationItem extends SolidElement {
);
}

private get isStackedHorizontal(): boolean {
return !this.vertical && this.stacked;
}

private handleClickButton(event: MouseEvent) {
if (this.disabled) {
event.preventDefault();
Expand Down Expand Up @@ -197,7 +204,7 @@ export default class SdNavigationItem extends SolidElement {
children: this.hasSlotController.test('children')
};

const horizontalPadding = this.vertical ? 'py-3' : 'py-2';
const horizontalPadding = this.vertical ? 'py-3' : this.isStackedHorizontal ? 'pt-1 pb-1' : 'py-2';

/* eslint-disable lit/no-invalid-html */
/* eslint-disable lit/binding-positions */
Expand All @@ -206,14 +213,21 @@ export default class SdNavigationItem extends SolidElement {
part="base"
class=${cx(
'flex items-center cursor-pointer relative focus-visible:focus-outline group hover:bg-neutral-200 transition-colors duration-fast ease-in-out min-h-[48px] navigable-border-radius',
{ md: 'navigable-font-size', lg: 'text-lg', sm: 'text-[14px]' }[this.size],
this.isStackedHorizontal
? 'text-xs leading-4.5'
: { md: 'navigable-font-size', lg: 'text-lg', sm: 'text-[14px]' }[this.size],
this.disabled ? 'text-neutral-500 pointer-events-none' : 'sd-navigation-item-color-text',
this.current ? 'font-bold sd-navigation-item--current-color-text' : 'choice-control-font-weight',
this.current && !this.disabled && 'sd-navigation-item--current-color-text',
this.current && !this.isStackedHorizontal && 'font-bold',
!this.current && !this.isStackedHorizontal && 'choice-control-font-weight',
!isAccordion && 'w-full',
this.divider && this.vertical && 'mt-0.25',
!this.vertical && 'inline-flex items-center',
!this.vertical &&
(this.isStackedHorizontal
? 'inline-flex flex-col items-center justify-center text-center rounded-full wrap'
: 'inline-flex items-center'),
!this.separated && 'hover:bg-neutral-200 group transition-colors duration-fast ease-in-out min-h-[48px]',
isIconOnly ? 'justify-center aspect-square p-3' : 'px-4'
isIconOnly ? 'justify-center aspect-square p-3' : this.isStackedHorizontal ? 'px-8' : 'px-4'
)}
aria-current=${ifDefined(this.current ? 'page' : undefined)}
aria-disabled=${this.disabled}
Expand All @@ -230,18 +244,25 @@ export default class SdNavigationItem extends SolidElement {
<div
part="current-indicator"
class=${cx(
'absolute bg-accent left-0 pointer-events-none navigable__current-indicator-border-radius',
'absolute bg-accent pointer-events-none navigable__current-indicator-border-radius',
this.vertical
? 'navigable__current-indicator-width h-[calc(100%-16px)] top-2 group-hover:h-full group-hover:top-0'
: 'navigable__current-indicator-height w-[calc(100%-16px)] bottom-0 left-2 group-hover:w-full group-hover:left-0 transition-all',
? 'navigable__current-indicator-width h-[calc(100%-16px)] top-2 left-0 group-hover:h-full group-hover:top-0'
: this.isStackedHorizontal
? 'h-[2px] left-6 right-6 bottom-0'
: 'navigable__current-indicator-height w-[calc(100%-16px)] left-2 bottom-0 group-hover:w-full group-hover:left-0 transition-all',
this.disabled && 'bg-neutral-500'
)}></div>
<div class='inline-flex flex-col justify-center gap-1 w-full h-full'>
<div
class=${cx(
'inline-flex justify-center gap-1 w-full h-full',
this.isStackedHorizontal ? 'flex-col items-center' : 'flex-col'
)}>
<span
part="content-area"
class=${cx(
'relative inline-flex justify-between items-center',
isAccordion ? 'grow' : 'w-full',
this.isStackedHorizontal && 'justify-center',
slots['description'] && 'pt-3',
!slots['description'] && !this.separated && horizontalPadding,
this.calculatePaddingX
Expand All @@ -254,8 +275,25 @@ export default class SdNavigationItem extends SolidElement {
></sd-divider>`
: ''
}
<span id="content" part="content-container" class="inline-flex items-center flex-auto">
<slot part="content" class="inline-flex text-start"></slot>
<span
id="content"
part="content-container"
class=${cx(
this.vertical
? 'inline-flex flex-auto items-center gap-2'
: this.isStackedHorizontal
? 'inline-flex items-center text-center'
: 'inline-flex flex-auto flex-col items-center justify-center gap-1 text-center'
)}
>
<slot
part="content"
class=${cx(
this.vertical
? 'inline-flex items-center gap-2'
: 'inline-flex flex-col items-center justify-center gap-1 text-center'
)}
></slot>
</span>
${
this.chevron || (slots['children'] && this.vertical && !this.separated)
Expand Down Expand Up @@ -352,6 +390,10 @@ export default class SdNavigationItem extends SolidElement {
@apply block;
}

:host([stacked]:not([vertical])) ::slotted(sd-icon) {
@apply w-6 h-6;
}

/* TODO clean sd-navigation-item--current-color-text and delete this class from line 210 (breaking change) */
.sd-navigation-item--current-color-text {
color: rgb(var(--sd-navigation-item--current-color-text, var(--sd-navigation-item-color-text)));
Expand Down
1 change: 1 addition & 0 deletions packages/docs/src/stories/components/navigation-item.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Used to facilitate seamless page transitions and help users orient themselves wi
- Populate an [sd-header](./?path=/docs/components-sd-header--docs) navigation bar at the top of a page, helping users easily access different sections.
- Implement navigation items in a sidebar for a more detailed and hierarchical navigation structure.
- Add stacked navigation items to a bottom bar used in mobile or web apps.
### Usage Guidelines
Expand Down
13 changes: 13 additions & 0 deletions packages/docs/src/stories/components/navigation-item.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,19 @@ export const Disabled = {
render: () => html` <sd-navigation-item disabled>Disabled Navigation</sd-navigation-item> `
};

/**
* Use the ”stacked” attribute to create a stacked layout of the navigation item with icon.
*/

export const Stacked = {
render: () => html`
<sd-navigation-item stacked>
<sd-icon name="system/image"></sd-icon>
<span>Navigation</span>
</sd-navigation-item>
`
};

/**
* Use the `divider` attribute to add a divider above the navigation item.
*
Expand Down
134 changes: 100 additions & 34 deletions packages/docs/src/stories/components/navigation-item.test.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,33 @@ export const Default = {
}
};

export const Current = {
name: 'Variant x Current',
render: (args: any) =>
generateTemplate({
args,
axis: {
x: { type: 'attribute', name: 'vertical' },
y: { type: 'attribute', name: 'current' }
}
})
export const OrientationAndCurrent = {
name: 'Orientation x Current',
render: (args: any) => {
return html`
${generateTemplate({
args,
axis: {
x: { type: 'attribute', name: 'vertical' },
y: { type: 'attribute', name: 'current' }
}
})}
${generateTemplate({
axis: {
y: { type: 'attribute', name: 'current' }
},
constants: [
{ type: 'attribute', name: 'stacked', value: true },
{ type: 'slot', name: 'default', value: '<sd-icon name="system/image"></sd-icon><span>Navigation</span>' }
],
args
})}
`;
}
};

export const Variants = {
name: 'Variant × Size',
export const OrientationAndSize = {
name: 'Orientation × Size',
render: (args: any) => {
return html`
${generateTemplate({
Expand Down Expand Up @@ -110,6 +123,31 @@ export const Variants = {
}
};

export const OrientationAndLink = {
name: 'Orientation × Link',
render: (args: any) => {
return html`
${generateTemplate({
axis: {
x: { type: 'attribute', name: 'href', values: ['', '#'] },
y: { type: 'attribute', name: 'vertical' }
},
args
})}
${generateTemplate({
axis: {
y: { type: 'attribute', name: 'href', values: ['', '#'] }
},
constants: [
{ type: 'attribute', name: 'stacked', value: true },
{ type: 'slot', name: 'default', value: '<sd-icon name="system/image"></sd-icon><span>Navigation</span>' }
],
args
})}
`;
}
};

export const Disabled = {
name: 'Disabled',
render: (args: any) => {
Expand Down Expand Up @@ -143,36 +181,64 @@ export const Disabled = {
],
args
})}
`;
}
};

export const VerticalAndCurrent = {
name: 'Vertical × Current',
render: (args: any) => {
return html`
${generateTemplate({
axis: {
x: { type: 'attribute', name: 'vertical' },
y: { type: 'attribute', name: 'current' }
},
options: {
title: 'Stacked (Horizontal)'
},
constants: [
{ type: 'attribute', name: 'disabled', value: true },
{ type: 'attribute', name: 'stacked', value: true },
{ type: 'slot', name: 'default', value: '<sd-icon name="system/image"></sd-icon><span>Navigation</span>' }
],
args
})}
`;
}
};

export const VerticalAndLink = {
name: 'Vertical × Link',
export const Stacked = {
name: 'Stacked',
render: (args: any) => {
const cases = [
{
title: 'Default',
slot: '<sd-icon name="system/image"></sd-icon><span>Navigation</span>'
},
{
title: 'Short Navigation',
slot: '<sd-icon name="system/image"></sd-icon><span>Nav</span>'
},
{
title: 'Large Navigation',
slot: '<sd-icon name="system/image"></sd-icon><span>Large Navigation</span>'
}
];

return html`
${generateTemplate({
axis: {
x: { type: 'attribute', name: 'href', values: ['', '#'] },
y: { type: 'attribute', name: 'vertical' }
},
args
})}
${cases.map(
c => html`
${generateTemplate({
axis: {
y: { type: 'attribute', name: 'current' }
},
options: {
title: c.title
},
constants: [
{ type: 'attribute', name: 'stacked', value: true },
{
type: 'slot',
name: 'default',
value: c.slot
}
],
args
})}
`
)}
`;
}
};
Expand Down Expand Up @@ -420,14 +486,14 @@ export const Mouseless = {

export const Combination = generateScreenshotStory([
Default,
Current,
Variants,
OrientationAndCurrent,
OrientationAndSize,
OrientationAndLink,
Disabled,
Stacked,
Parts,
Chevron,
IndentedRelaxed,
VerticalAndCurrent,
VerticalAndLink,
Separated,
Slots,
Mouseless
Expand Down
Loading