Skip to content
Snippets Groups Projects
Commit 2af49821 authored by Tuukka Turu's avatar Tuukka Turu
Browse files

Merge branch 'NXSTAGE-610-breadcrumb' into 'development'

Nxstage 610 breadcrumb

See merge request julkiset-sivut/design-system-lib!80
parents c3db32b8 0a057e38
No related branches found
No related tags found
No related merge requests found
......@@ -5,7 +5,9 @@
* It contains typing information for all components that exist in this project.
*/
import {HTMLStencilElement, JSXBase} from '@stencil/core/internal';
import {Breadcrumb} from './components/hy-breadcrumbs/hy-breadcrumbs';
import {
BreadcrumbVariants,
ButtonVariants,
ColorVariant,
CtaLinkButtonVariants,
......@@ -129,6 +131,11 @@ export namespace Components {
*/
wrap: boolean;
}
interface HyBreadcrumbs {
dataItems: Breadcrumb[] | string;
headerstyle: string;
variant: BreadcrumbVariants;
}
interface HyButton {
/**
* Aria label for the element
......@@ -557,6 +564,11 @@ declare global {
prototype: HTMLHyBoxContainerElement;
new (): HTMLHyBoxContainerElement;
};
interface HTMLHyBreadcrumbsElement extends Components.HyBreadcrumbs, HTMLStencilElement {}
var HTMLHyBreadcrumbsElement: {
prototype: HTMLHyBreadcrumbsElement;
new (): HTMLHyBreadcrumbsElement;
};
interface HTMLHyButtonElement extends Components.HyButton, HTMLStencilElement {}
var HTMLHyButtonElement: {
prototype: HTMLHyButtonElement;
......@@ -827,6 +839,7 @@ declare global {
'hy-baseline': HTMLHyBaselineElement;
'hy-box': HTMLHyBoxElement;
'hy-box-container': HTMLHyBoxContainerElement;
'hy-breadcrumbs': HTMLHyBreadcrumbsElement;
'hy-button': HTMLHyButtonElement;
'hy-cta-button': HTMLHyCtaButtonElement;
'hy-cta-link': HTMLHyCtaLinkElement;
......@@ -971,6 +984,11 @@ declare namespace LocalJSX {
*/
wrap?: boolean;
}
interface HyBreadcrumbs {
dataItems?: Breadcrumb[] | string;
headerstyle?: string;
variant?: BreadcrumbVariants;
}
interface HyButton {
/**
* Aria label for the element
......@@ -1368,6 +1386,7 @@ declare namespace LocalJSX {
'hy-baseline': HyBaseline;
'hy-box': HyBox;
'hy-box-container': HyBoxContainer;
'hy-breadcrumbs': HyBreadcrumbs;
'hy-button': HyButton;
'hy-cta-button': HyCtaButton;
'hy-cta-link': HyCtaLink;
......@@ -1435,6 +1454,7 @@ declare module '@stencil/core' {
'hy-baseline': LocalJSX.HyBaseline & JSXBase.HTMLAttributes<HTMLHyBaselineElement>;
'hy-box': LocalJSX.HyBox & JSXBase.HTMLAttributes<HTMLHyBoxElement>;
'hy-box-container': LocalJSX.HyBoxContainer & JSXBase.HTMLAttributes<HTMLHyBoxContainerElement>;
'hy-breadcrumbs': LocalJSX.HyBreadcrumbs & JSXBase.HTMLAttributes<HTMLHyBreadcrumbsElement>;
'hy-button': LocalJSX.HyButton & JSXBase.HTMLAttributes<HTMLHyButtonElement>;
'hy-cta-button': LocalJSX.HyCtaButton & JSXBase.HTMLAttributes<HTMLHyCtaButtonElement>;
'hy-cta-link': LocalJSX.HyCtaLink & JSXBase.HTMLAttributes<HTMLHyCtaLinkElement>;
......
:host {
display: block;
}
// Default variant
.hy-breadcrumbs {
display: inline-block;
width: auto;
&.is-condensed {
width: 100%;
}
ol {
margin: 0;
padding: 0;
}
.breadcrumb-container {
color: var(--grayscale-dark);
display: flex;
flex-wrap: nowrap;
font-family: var(--main-font-family);
list-style-type: none;
margin: 0;
min-height: 72px;
overflow: hidden;
padding: 0;
@include breakpoint($narrow) {
min-height: 76px;
}
@include breakpoint($wide) {
min-height: 86px;
}
@include breakpoint($extrawide) {
min-height: 94px;
}
}
.breadcrumb-item {
display: flex;
flex-direction: row;
align-items: center;
flex: 0 0 auto;
a {
color: var(--brand-main-light);
display: flex;
flex-direction: row;
align-items: center;
margin-right: 20px;
position: relative;
text-decoration: none;
@include breakpoint($medium) {
margin-right: 28px;
}
@include breakpoint($wide) {
margin-right: 30px;
}
.breadcrumb-item-caret {
position: absolute;
right: -15px;
top: 50%;
transform: translateY(-50%);
@include breakpoint($medium) {
right: -18px;
}
@include breakpoint($wide) {
right: -19px;
}
&:hover {
cursor: default;
}
}
}
a.default {
@include font-size(14px, 20px);
@include breakpoint($narrow) {
@include font-size(16px, 24px);
}
}
&:focus {
outline: auto;
}
&.hidden {
display: none;
}
}
.breadcrumb-item.home {
hy-icon.default {
svg {
fill: var(--brand-main-light);
stroke: var(--brand-main-light);
}
}
}
.breadcrumb-item.main {
min-width: 0;
}
.breadcrumb-item__more {
display: none;
flex-direction: row;
align-items: center;
position: relative;
margin-right: 20px;
@include breakpoint($medium) {
margin-right: 25px;
}
@include breakpoint($wide) {
margin-right: 30px;
}
&.visible {
display: flex;
}
.breadcrumb-item-caret {
position: absolute;
right: -15px;
top: 50%;
transform: translateY(-50%);
@include breakpoint($medium) {
right: -18px;
}
&:hover {
cursor: default;
}
&__drop {
position: absolute;
right: 5.5px;
top: 50%;
transform: translateY(-50%);
}
}
}
.breadcrumb-item-dropdown-button {
display: flex;
flex-direction: row;
color: var(--brand-main-light);
cursor: pointer;
border: 1.5px solid var(--brand-main-light);
font-size: 1.5rem;
line-height: 10px;
border-radius: 3px;
background-color: var(--grayscale-white);
box-shadow: 0 0 10px 0 rgba(14, 104, 139, 0.1);
padding: 0 25px 9px 10px;
position: relative;
hy-icon {
svg {
fill: var(--brand-main-light);
margin: 0 0 -3px 10px;
transform: rotate(90deg);
}
}
}
.breadcrumb-item-dropdown-button.is-open {
background-color: var(--brand-main-light);
color: var(--grayscale-white);
svg {
fill: var(--grayscale-white);
margin: 0 0 -3px 10px;
transform: rotate(270deg);
}
}
.breadcrumb-hidden-items {
display: none;
visibility: hidden;
&__is-open {
background: var(--grayscale-white);
box-shadow: 0 0 10px 0 rgba(14, 104, 139, 0.1);
display: block;
margin-top: -9px;
padding: 32px 16px 6px 16px;
position: absolute;
visibility: visible;
z-index: 5;
@include breakpoint($narrow) {
padding: 32px 64px 6px 32px;
}
a {
margin: 0;
padding-bottom: 26px;
}
}
}
.breadcrumb-item__current {
flex: 0 2 auto;
min-width: 0;
}
.breadcrumb-item__current a {
color: var(--grayscale-dark);
font-family: var(--main-font-family);
text-decoration: none;
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-height: auto;
&:hover {
cursor: default;
}
}
.intermediate {
display: flex;
text-overflow: initial;
}
.intermediate.hidden {
display: none;
visibility: hidden;
}
#more,
.more {
display: none;
visibility: hidden;
}
#more.visible,
.more.visible {
display: flex;
visibility: visible;
}
}
// Large variant. Do not show Breadcrumbs if there is a hero and a sidebar on Large/Medium screens
.hy-breadcrumbs.large.with-sidebar {
display: block;
visibility: visible;
a.large {
@include font-size(26px, 26px);
color: var(--grayscale-black);
font-weight: 700;
//@todo change home icon to be 20px x 20px and be as in the specs
.breadcrumb-item-caret {
// @todo set styles for caret near the home icon
// should be bold
// padding: 0 6px 0 8px; (mobile) && padding: 0 8px 0 10px (tablet + desktop);
}
}
.breadcrumb-container {
color: var(--grayscale-black);
display: flex;
flex-wrap: nowrap;
font-family: var(--main-font-family);
list-style-type: none;
margin: 0;
min-height: 64px;
overflow: hidden;
padding: 0;
@include breakpoint($narrow) {
min-height: 84px;
}
.breadcrumb-item.home {
svg {
fill: var(--grayscale-black);
stroke: var(--grayscale-black);
stroke-width: 30;
}
}
}
@include breakpoint($extrawide) {
display: none;
visibility: hidden;
}
}
export interface Breadcrumb {
url: string;
text: string;
}
let breadcrumbsWidth = null;
import {Component, Element, h, Listen, Prop, State, Watch} from '@stencil/core';
import {BreadcrumbVariants} from '../../utils/utils';
@Component({
tag: 'hy-breadcrumbs',
styleUrl: 'hy-breadcrumbs.scss',
shadow: false,
})
export class HyBreadcrumbs {
private _dataItems: Breadcrumb[];
@Prop() dataItems: Breadcrumb[] | string;
@Prop() variant: BreadcrumbVariants = BreadcrumbVariants.default as any;
@Prop() headerstyle: string = 'with-sidebar';
@State() menuOpen: boolean = false;
@Element() el: HTMLElement;
@Watch('dataItems')
arrayDataWatcher(newValue: Breadcrumb[] | string) {
if (typeof newValue === 'string') {
this._dataItems = JSON.parse(newValue);
} else {
this._dataItems = newValue;
}
}
componentWillLoad() {
this.arrayDataWatcher(this.dataItems);
}
componentDidLoad() {
let hyMainDiv = this.el.closest('.hy-main');
if (hyMainDiv) {
if (!hyMainDiv.classList.contains('with-sidebar')) {
this.headerstyle = 'without-sidebar';
}
}
// Set breadcumbs width + paddings.
breadcrumbsWidth = this.el.offsetWidth + 64;
if (breadcrumbsWidth >= document.body.scrollWidth) {
this.adjustBreadcrumbsMenuVisibility();
}
}
adjustBreadcrumbsMenuVisibility(showMenu = true) {
// Show ... and Hide intermediate links
if (!showMenu) {
this.closeMoreMenu();
}
const crumbContainer = document.querySelectorAll('.hy-breadcrumbs')[0];
const moreDotsItem = document.querySelectorAll('#more')[0];
const moreDotsItemWrapper = document.querySelectorAll('.breadcrumb-item__more')[0];
if (moreDotsItem) {
if (showMenu) {
crumbContainer.classList.add('is-condensed');
moreDotsItem.classList.add('visible');
moreDotsItemWrapper.classList.add('visible');
} else {
crumbContainer.classList.remove('is-condensed');
moreDotsItem.classList.remove('visible');
moreDotsItemWrapper.classList.remove('visible');
}
}
const intermediateItems = document.querySelectorAll('.intermediate');
if (intermediateItems) {
for (let i = 0; i < intermediateItems.length; i++) {
if (showMenu) {
intermediateItems[i].classList.add('hidden');
} else {
intermediateItems[i].classList.remove('hidden');
}
}
}
}
HomeItem(url) {
const homeItemClass = ['hy-icon-wrapper', this.variant].join(' ');
return (
<li class="breadcrumb-item home">
<a href={url} class={homeItemClass}>
<hy-icon icon={'hy-icon-home'} class={`${this.variant}`} size={20} />
<hy-icon icon={'hy-icon-caret-right'} class={'breadcrumb-item-caret'} size={10} />
</a>
</li>
);
}
BreadcrumbItem(label, url, className = '', withCaret = true) {
const breadcrumbClass = ['breadcrumb-item', className].join(' ');
const caretClass = ['breadcrumb-item-caret', this.variant].join(' ');
if (url) {
if (withCaret) {
return (
<li class={breadcrumbClass}>
<a href={url} class={`${this.variant}`}>
{label}
<hy-icon icon={'hy-icon-caret-right'} class={caretClass} size={10} />
</a>
</li>
);
} else {
return (
<li class={breadcrumbClass}>
<a href={url} class={`${this.variant}`}>
{label}
</a>
</li>
);
}
} else {
return (
<li class={`${breadcrumbClass} breadcrumb-item__current`}>
<a aria-current="page" href={url} class={`${this.variant}`}>
{label}
</a>
</li>
);
}
}
BreadcrumbTextItem(label, className = '') {
const breadcrumbClass = ['breadcrumb-item', className].join(' ');
return <li class={breadcrumbClass}>{label}</li>;
}
DropdownMenuItem() {
return (
<li class="breadcrumb-item__more">
<button
type="button"
aria-hidden="true"
aria-expanded="false"
id="more"
key="more"
class="breadcrumb-item-dropdown-button"
>
...
<hy-icon
icon={'hy-icon-caret-right'}
class={'breadcrumb-item-caret__drop breadcrumb-item__more__icon'}
size={10}
/>
</button>
<hy-icon icon={'hy-icon-caret-right'} class={'breadcrumb-item-caret'} size={10} />
</li>
);
}
adjustHiddenMenuWidth() {
// set width to the menu area equal to the widest link + paddings
const moreMenu = document.querySelectorAll('.breadcrumb-hidden-items')[0];
if (moreMenu) {
if (document.body.scrollWidth < 480) {
(moreMenu as HTMLElement).style.width = '100%';
} else {
//maxIntermediateLinkWidth
var maxIntermediateLinkWidth = 0;
const moreMenuLinks = document.querySelectorAll('.breadcrumb-hidden-items .breadcrumb-item a');
if (moreMenuLinks) {
for (let i = 0; i < moreMenuLinks.length; i++) {
if (maxIntermediateLinkWidth < (moreMenuLinks[i] as HTMLElement).offsetWidth) {
maxIntermediateLinkWidth = (moreMenuLinks[i] as HTMLElement).offsetWidth;
}
}
maxIntermediateLinkWidth = maxIntermediateLinkWidth + 32 + 64;
}
(moreMenu as HTMLElement).style.width = maxIntermediateLinkWidth.toString().concat('px');
}
}
}
closeMoreMenu() {
const moreMenu = document.querySelectorAll('.breadcrumb-hidden-items')[0];
if (moreMenu) {
moreMenu.classList.remove('breadcrumb-hidden-items__is-open');
this.menuOpen = false;
}
const moreBreadcrumb = document.querySelectorAll('#more')[0];
if (moreBreadcrumb) {
moreBreadcrumb.classList.remove('is-open');
}
}
// When a ... is clicked, show/hide the Menu with hidden breadcrumbs
@Listen('click')
clickEventListener(event) {
if (!event) return;
const target = event.target;
const moreMenu = document.querySelectorAll('.breadcrumb-hidden-items')[0];
const moreButton = document.querySelectorAll('.breadcrumb-item-dropdown-button')[0];
// Trigger if target is button or svg icon
// TODO: Make this if prettier
if (
target &&
(target.id === 'more' ||
((target.tagName == 'svg' || 'path') &&
target.closest('hy-icon').classList.contains('breadcrumb-item__more__icon')))
) {
//@todo Show the menu on the right place of the screen
if (moreMenu) {
if (this.menuOpen) {
moreMenu.classList.remove('breadcrumb-hidden-items__is-open');
moreButton.classList.remove('is-open');
moreButton.setAttribute('aria-expanded', 'false');
} else {
moreMenu.classList.add('breadcrumb-hidden-items__is-open');
moreButton.classList.add('is-open');
moreButton.setAttribute('aria-expanded', 'true');
if (document.body.scrollWidth < 480) {
(moreMenu as HTMLElement).style.left = '16px';
} else {
var rect = (moreButton as HTMLElement).getBoundingClientRect();
(moreMenu as HTMLElement).style.left = (rect.left - 64).toString().concat('px');
this.adjustHiddenMenuWidth();
}
}
this.menuOpen = !this.menuOpen;
}
} else {
this.closeMoreMenu();
}
event.stopPropagation();
event.stopImmediatePropagation();
}
@Listen('resize', {target: 'window'})
resizeEventListener(event) {
if (!event) return;
const breadcrumbsElement = document.querySelectorAll('.hy-breadcrumbs')[0];
if (breadcrumbsElement) {
if (breadcrumbsWidth + 64 >= document.body.scrollWidth) {
this.adjustBreadcrumbsMenuVisibility(true);
} else {
this.adjustBreadcrumbsMenuVisibility(false);
}
}
}
render() {
//@todo Accesibility
const TOTAL_ITEMS = this._dataItems.length;
const MAX_ITEMS_TO_SHOW = 3;
let isMenuNeeded = TOTAL_ITEMS > MAX_ITEMS_TO_SHOW;
let itemsBreadcrumbs = [];
let itemsToShowInMenu = [];
if (this.variant == BreadcrumbVariants.landingLarge) {
// Landing pages, Large variant
this._dataItems.map((x, index) => {
if (index < 2) {
if (index == 0) {
itemsBreadcrumbs.push(this.HomeItem(x.url));
} else {
//itemsBreadcrumbs.push(this.BreadcrumbTextItem(x.text, 'main'));
itemsBreadcrumbs.push(this.BreadcrumbItem(x.text, '', 'main'));
}
}
});
} else {
// Landing and Content pages, Standard variant
this._dataItems.map((x, index) => {
let breadcrumbEl = this.BreadcrumbItem(x.text, x.url, '', false);
if (isMenuNeeded && index > 1 && index < TOTAL_ITEMS - 1) {
itemsToShowInMenu.push(<div>{breadcrumbEl}</div>);
if (index === 2) {
itemsBreadcrumbs.push(this.DropdownMenuItem());
}
itemsBreadcrumbs.push(this.BreadcrumbItem(x.text, x.url, 'intermediate'));
return;
} else {
if (index == 0) {
itemsBreadcrumbs.push(this.HomeItem(x.url));
} else {
itemsBreadcrumbs.push(this.BreadcrumbItem(x.text, x.url, 'main'));
}
}
});
}
const breadcrumbsClass = ['hy-breadcrumbs', this.variant, this.headerstyle].join(' ');
return (
<nav aria-label="Breadcrumb" role="navigation" aria-labelledby="system-breadcrumb" class={breadcrumbsClass}>
<ol class="breadcrumb-container">{itemsBreadcrumbs}</ol>
{itemsToShowInMenu && <ol class="breadcrumb-hidden-items">{itemsToShowInMenu}</ol>}
</nav>
);
}
}
# hy-breadcrumbs
<!-- Auto Generated Below -->
## Properties
| Property | Attribute | Description | Type | Default |
| ------------- | ------------- | ----------- | --------------------------------------------------------------- | ----------------------------------- |
| `dataItems` | `data-items` | | `Breadcrumb[] \| string` | `undefined` |
| `headerstyle` | `headerstyle` | | `string` | `'with-sidebar'` |
| `variant` | `variant` | | `BreadcrumbVariants.default \| BreadcrumbVariants.landingLarge` | `BreadcrumbVariants.default as any` |
## Dependencies
### Depends on
- [hy-icon](../icon)
### Graph
```mermaid
graph TD;
hy-breadcrumbs --> hy-icon
style hy-breadcrumbs fill:#f9f,stroke:#333,stroke-width:4px
```
---
Helsinki University Design System
......@@ -2,11 +2,11 @@ import {h} from '@stencil/core';
function SvgHome(props) {
return (
<svg viewBox="0 0 1000 1000" {...props}>
<svg viewBox="0 0 1000 1000" {...props} stroke="black" stroke-width="1">
<path
d="M345.3,998.7c-30.3,0-55.4-25.2-55.4-55.6c0-30.4,25.1-55.6,55.4-55.6h479.9l0-509.9L500,114.2L174.8,377.6
v565.5c0,30.4-25.1,55.6-55.4,55.6c-30.3,0-55.4-25.2-55.4-55.6l0-567.6c0-31.5,14.6-61.9,38.7-81.9L434.1,25
c37.7-30.4,93-30.4,130.7,0l332.5,268.6c25.1,19.9,38.7,49.3,38.7,81.9v568.7c0,29.4-25.1,54.6-55.4,54.6L345.3,998.7z"
c37.7-30.4,93-30.4,130.7,0l332.5,268.6c25.1,19.9,38.7,49.3,38.7,81.9v568.7c0,29.4-25.1,54.6-55.4,54.6L345.3,998.7z"
/>
</svg>
);
......
......@@ -15,6 +15,7 @@
### Used by
- [hy-accordion-item](../accordion-item)
- [hy-breadcrumbs](../hy-breadcrumbs)
- [hy-button](../button)
- [hy-cta-button](../cta-button)
- [hy-cta-link](../cta-link)
......@@ -39,6 +40,7 @@
```mermaid
graph TD;
hy-accordion-item --> hy-icon
hy-breadcrumbs --> hy-icon
hy-button --> hy-icon
hy-cta-button --> hy-icon
hy-cta-link --> hy-icon
......
......@@ -12,6 +12,11 @@ export type IconName = {
[key: string]: (props: any) => FunctionalComponent;
};
export enum BreadcrumbVariants {
default = 'default',
landingLarge = 'large',
}
export enum HeadingVarians {
default = 'h1',
h2 = 'h2',
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment