diff --git a/.changeset/empty-bikes-pay.md b/.changeset/empty-bikes-pay.md new file mode 100644 index 0000000000..0e2c0e6bc1 --- /dev/null +++ b/.changeset/empty-bikes-pay.md @@ -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. \ No newline at end of file diff --git a/.changeset/rich-cases-sleep.md b/.changeset/rich-cases-sleep.md new file mode 100644 index 0000000000..58c947e0c8 --- /dev/null +++ b/.changeset/rich-cases-sleep.md @@ -0,0 +1,5 @@ +--- +'@solid-design-system/docs': minor +--- + +Added documentation for the new `stacked` attribute for the `sd-navigation-item` component. \ No newline at end of file diff --git a/packages/components/src/components/navigation-item/navigation-item.ts b/packages/components/src/components/navigation-item/navigation-item.ts index caec64efb9..1ea7ba8a4c 100644 --- a/packages/components/src/components/navigation-item/navigation-item.ts +++ b/packages/components/src/components/navigation-item/navigation-item.ts @@ -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; @@ -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(); @@ -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 */ @@ -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} @@ -230,18 +244,25 @@ export default class SdNavigationItem extends SolidElement {
-
+
` : '' } - - + + ${ this.chevron || (slots['children'] && this.vertical && !this.separated) @@ -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))); diff --git a/packages/docs/src/stories/components/navigation-item.mdx b/packages/docs/src/stories/components/navigation-item.mdx index d01f912b4a..f6da494809 100644 --- a/packages/docs/src/stories/components/navigation-item.mdx +++ b/packages/docs/src/stories/components/navigation-item.mdx @@ -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 diff --git a/packages/docs/src/stories/components/navigation-item.stories.ts b/packages/docs/src/stories/components/navigation-item.stories.ts index 724381d9f8..01d5a571ec 100644 --- a/packages/docs/src/stories/components/navigation-item.stories.ts +++ b/packages/docs/src/stories/components/navigation-item.stories.ts @@ -84,6 +84,19 @@ export const Disabled = { render: () => html` Disabled Navigation ` }; +/** + * Use the ”stacked” attribute to create a stacked layout of the navigation item with icon. + */ + +export const Stacked = { + render: () => html` + + + Navigation + + ` +}; + /** * Use the `divider` attribute to add a divider above the navigation item. * diff --git a/packages/docs/src/stories/components/navigation-item.test.stories.ts b/packages/docs/src/stories/components/navigation-item.test.stories.ts index e945de363f..12759672ea 100644 --- a/packages/docs/src/stories/components/navigation-item.test.stories.ts +++ b/packages/docs/src/stories/components/navigation-item.test.stories.ts @@ -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: 'Navigation' } + ], + args + })} + `; + } }; -export const Variants = { - name: 'Variant × Size', +export const OrientationAndSize = { + name: 'Orientation × Size', render: (args: any) => { return html` ${generateTemplate({ @@ -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: 'Navigation' } + ], + args + })} + `; + } +}; + export const Disabled = { name: 'Disabled', render: (args: any) => { @@ -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: 'Navigation' } + ], args })} `; } }; -export const VerticalAndLink = { - name: 'Vertical × Link', +export const Stacked = { + name: 'Stacked', render: (args: any) => { + const cases = [ + { + title: 'Default', + slot: 'Navigation' + }, + { + title: 'Short Navigation', + slot: 'Nav' + }, + { + title: 'Large Navigation', + slot: 'Large Navigation' + } + ]; + 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 + })} + ` + )} `; } }; @@ -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