diff --git a/src/components.d.ts b/src/components.d.ts index 89ecacca85a5c7ed536212956839ae86512d2c36..61b2d9fb39f94d71d5738f5efa0ecfbc228ce563 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -356,6 +356,10 @@ export namespace Components { stepState: ProcessFlowBoxStepStates; variant: ProcessFlowBoxVariants; } + interface HyProminentImage { + imageUrl?: string; + textTitle?: string; + } interface HyRow { /** * justify-content property. @@ -396,6 +400,13 @@ export namespace Components { showLabel: boolean; size: number; } + interface HyTabs { + tabId?: string; + tabListLabel: string; + } + interface HyTabsItem { + tabTitle?: string; + } interface HyTinyText {} interface HyTwoColumns { reversed: boolean; @@ -621,6 +632,11 @@ declare global { prototype: HTMLHyProcessFlowBoxElement; new (): HTMLHyProcessFlowBoxElement; }; + interface HTMLHyProminentImageElement extends Components.HyProminentImage, HTMLStencilElement {} + var HTMLHyProminentImageElement: { + prototype: HTMLHyProminentImageElement; + new (): HTMLHyProminentImageElement; + }; interface HTMLHyRowElement extends Components.HyRow, HTMLStencilElement {} var HTMLHyRowElement: { prototype: HTMLHyRowElement; @@ -651,6 +667,16 @@ declare global { prototype: HTMLHySiteSearchElement; new (): HTMLHySiteSearchElement; }; + interface HTMLHyTabsElement extends Components.HyTabs, HTMLStencilElement {} + var HTMLHyTabsElement: { + prototype: HTMLHyTabsElement; + new (): HTMLHyTabsElement; + }; + interface HTMLHyTabsItemElement extends Components.HyTabsItem, HTMLStencilElement {} + var HTMLHyTabsItemElement: { + prototype: HTMLHyTabsItemElement; + new (): HTMLHyTabsItemElement; + }; interface HTMLHyTinyTextElement extends Components.HyTinyText, HTMLStencilElement {} var HTMLHyTinyTextElement: { prototype: HTMLHyTinyTextElement; @@ -710,12 +736,15 @@ declare global { 'hy-paragraph-text': HTMLHyParagraphTextElement; 'hy-process': HTMLHyProcessElement; 'hy-process-flow-box': HTMLHyProcessFlowBoxElement; + 'hy-prominent-image': HTMLHyProminentImageElement; 'hy-row': HTMLHyRowElement; 'hy-section-container': HTMLHySectionContainerElement; 'hy-shortcuts': HTMLHyShortcutsElement; 'hy-site-header': HTMLHySiteHeaderElement; 'hy-site-logo': HTMLHySiteLogoElement; 'hy-site-search': HTMLHySiteSearchElement; + 'hy-tabs': HTMLHyTabsElement; + 'hy-tabs-item': HTMLHyTabsItemElement; 'hy-tiny-text': HTMLHyTinyTextElement; 'hy-two-columns': HTMLHyTwoColumnsElement; 'hy-user-login-form': HTMLHyUserLoginFormElement; @@ -1047,6 +1076,10 @@ declare namespace LocalJSX { stepState?: ProcessFlowBoxStepStates; variant?: ProcessFlowBoxVariants; } + interface HyProminentImage { + imageUrl?: string; + textTitle?: string; + } interface HyRow { /** * justify-content property. @@ -1087,6 +1120,13 @@ declare namespace LocalJSX { showLabel?: boolean; size?: number; } + interface HyTabs { + tabId?: string; + tabListLabel?: string; + } + interface HyTabsItem { + tabTitle?: string; + } interface HyTinyText {} interface HyTwoColumns { reversed?: boolean; @@ -1139,12 +1179,15 @@ declare namespace LocalJSX { 'hy-paragraph-text': HyParagraphText; 'hy-process': HyProcess; 'hy-process-flow-box': HyProcessFlowBox; + 'hy-prominent-image': HyProminentImage; 'hy-row': HyRow; 'hy-section-container': HySectionContainer; 'hy-shortcuts': HyShortcuts; 'hy-site-header': HySiteHeader; 'hy-site-logo': HySiteLogo; 'hy-site-search': HySiteSearch; + 'hy-tabs': HyTabs; + 'hy-tabs-item': HyTabsItem; 'hy-tiny-text': HyTinyText; 'hy-two-columns': HyTwoColumns; 'hy-user-login-form': HyUserLoginForm; @@ -1201,12 +1244,15 @@ declare module '@stencil/core' { 'hy-paragraph-text': LocalJSX.HyParagraphText & JSXBase.HTMLAttributes<HTMLHyParagraphTextElement>; 'hy-process': LocalJSX.HyProcess & JSXBase.HTMLAttributes<HTMLHyProcessElement>; 'hy-process-flow-box': LocalJSX.HyProcessFlowBox & JSXBase.HTMLAttributes<HTMLHyProcessFlowBoxElement>; + 'hy-prominent-image': LocalJSX.HyProminentImage & JSXBase.HTMLAttributes<HTMLHyProminentImageElement>; 'hy-row': LocalJSX.HyRow & JSXBase.HTMLAttributes<HTMLHyRowElement>; 'hy-section-container': LocalJSX.HySectionContainer & JSXBase.HTMLAttributes<HTMLHySectionContainerElement>; 'hy-shortcuts': LocalJSX.HyShortcuts & JSXBase.HTMLAttributes<HTMLHyShortcutsElement>; 'hy-site-header': LocalJSX.HySiteHeader & JSXBase.HTMLAttributes<HTMLHySiteHeaderElement>; 'hy-site-logo': LocalJSX.HySiteLogo & JSXBase.HTMLAttributes<HTMLHySiteLogoElement>; 'hy-site-search': LocalJSX.HySiteSearch & JSXBase.HTMLAttributes<HTMLHySiteSearchElement>; + 'hy-tabs': LocalJSX.HyTabs & JSXBase.HTMLAttributes<HTMLHyTabsElement>; + 'hy-tabs-item': LocalJSX.HyTabsItem & JSXBase.HTMLAttributes<HTMLHyTabsItemElement>; 'hy-tiny-text': LocalJSX.HyTinyText & JSXBase.HTMLAttributes<HTMLHyTinyTextElement>; 'hy-two-columns': LocalJSX.HyTwoColumns & JSXBase.HTMLAttributes<HTMLHyTwoColumnsElement>; 'hy-user-login-form': LocalJSX.HyUserLoginForm & JSXBase.HTMLAttributes<HTMLHyUserLoginFormElement>; diff --git a/src/components/hy-main-content-wrapper/hy-main-content-wrapper.scss b/src/components/hy-main-content-wrapper/hy-main-content-wrapper.scss index bf2aa0eb0c5634d3c87a28a7758ae5049732e2d2..42aa09b56d202a1e5d3a0056aa9405416801ec0b 100644 --- a/src/components/hy-main-content-wrapper/hy-main-content-wrapper.scss +++ b/src/components/hy-main-content-wrapper/hy-main-content-wrapper.scss @@ -4,29 +4,19 @@ .with-sidebar { .hy-main-content-wrapper { - margin: 0 auto; // Layout Mobile 320-479px - max-width: 22.5rem; // 328px + margin: 0 auto; + max-width: 100%; padding: 0 1rem; position: relative; // Layout Tablet 480-959px + // Side navigation disappears to hamburger when width < 1200. @include breakpoint($narrow) { - max-width: 48rem; // 704px + // the rule is applied to screens >=480px: width 100% - side margins (32px) padding: 0 2rem; } - // Layout Small Desktop 960-1200px - // Side navigation disappears to hamburger when width < 1200. - @include breakpoint($wide) { - max-width: 64rem; // 960px - } - - // > 1200px - @include breakpoint($extrawide) { - max-width: 64rem; // 960px - } - // Large desktop Layout >1441px @include breakpoint($overwide) { max-width: 80rem; // 1216px + (32px + 32px side margins) @@ -36,28 +26,17 @@ .without-sidebar { .hy-main-content-wrapper { - margin: 0 auto; // Layout Mobile 320-479px - max-width: 22.5rem; // 328px + (16px + 16px side margins) + margin: 0 auto; + max-width: 100%; padding: 0 1rem; position: relative; // Layout Tablet 480-959px @include breakpoint($narrow) { - max-width: 48rem; // 704px + (32px + 32px side margins) padding: 0 2rem; } - // Layout Small Desktop 960-1200px - @include breakpoint($wide) { - max-width: 64rem; // 960px + (32px + 32px side margins) - } - - // > 1200px - @include breakpoint($extrawide) { - max-width: 80rem; // 1216px + (32px + 32px side margins) - } - // Large desktop Layout >1441px @include breakpoint($overwide) { max-width: 94rem; // 1440px + (32px + 32px side margins) diff --git a/src/components/hy-main/hy-main.scss b/src/components/hy-main/hy-main.scss index aa78774a44f4ddae1200d227f9a8adcfe2b625c1..d4563441f5704031f5f79a624dc62ec53a8a86b4 100644 --- a/src/components/hy-main/hy-main.scss +++ b/src/components/hy-main/hy-main.scss @@ -2,15 +2,11 @@ display: flex; margin: var(--gutter-mobile) auto; max-width: $fullhd; - padding: 0 var(--gutter-mobile) var(--gutter-mobile); + padding: 0; width: 100%; - @include breakpoint($medium) { - padding: var(--gutter-mobile) 0; - } - .layout-content { - padding: var(--gutter-mobile) var(--gutter-medium); + padding: 0; width: 100%; } @@ -26,17 +22,17 @@ &.with-sidebar { .layout-content { @include breakpoint($medium) { - //padding: var(--gutter-mobile) var(--gutter-medium); margin: 0 auto; - padding: var(--gutter-medium); width: 100%; } @include breakpoint($extrawide) { margin: 0; order: 2; - padding: var(--gutter-extrawide); width: 80%; } + @include breakpoint($overwide) { + padding: 0 2rem; + } } .layout-sidebar-first { @@ -53,6 +49,11 @@ &.without-sidebar { .layout-content { width: 100%; + + padding: 0; + @include breakpoint($overwide) { + padding: 0 2rem; + } } } } diff --git a/src/components/hy-prominent-image/hy-prominent-image.scss b/src/components/hy-prominent-image/hy-prominent-image.scss new file mode 100644 index 0000000000000000000000000000000000000000..f38955a23baa999af29728c2acb4ce37039eaa9a --- /dev/null +++ b/src/components/hy-prominent-image/hy-prominent-image.scss @@ -0,0 +1,56 @@ +:host { + display: block; +} + +.hy-prominent-image { + position: relative; + + &__text { + // font: h5 small size + @include font-size(16px, 20px); + background-color: var(--grayscale-black); + bottom: 12px; + color: var(--grayscale-white); + font-family: var(--main-font-family); + font-weight: bold; + left: -8px; + letter-spacing: -0.5px; + max-width: 46%; // 5.5 columns + padding: 6px 12px; + position: absolute; + text-transform: uppercase; + + @include breakpoint($narrow) { + // >=480px + // font: h4 small size + @include font-size(18px, 24px); + bottom: 20px; + left: -16px; + letter-spacing: -0.56px; + max-width: 58%; // 7 columns + padding: 12px 24px; + position: absolute; + } + + @include breakpoint($wide) { + // Applies both to 960px-1200px and 1201-1440px + // font: h4 medium size + @include font-size(22px, 28px); + bottom: 24px; + left: -16px; + letter-spacing: -0.69px; + padding: 16px 24px; + position: absolute; + } + @include breakpoint($overwide) { + // >= 1441px + // font: h3 medium size + @include font-size(26px, 32px); + bottom: 28px; + left: -16px; + letter-spacing: -0.8px; + padding: 20px 24px; + position: absolute; + } + } +} diff --git a/src/components/hy-prominent-image/hy-prominent-image.tsx b/src/components/hy-prominent-image/hy-prominent-image.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b0b945917b4bedd5b234c843c67d0fea3a275c5f --- /dev/null +++ b/src/components/hy-prominent-image/hy-prominent-image.tsx @@ -0,0 +1,24 @@ +import {Component, Host, h, Prop} from '@stencil/core'; + +@Component({ + tag: 'hy-prominent-image', + styleUrl: 'hy-prominent-image.scss', + shadow: true, +}) +export class HyProminentImage { + @Prop() textTitle?: string; + @Prop() imageUrl?: string; + + render() { + return ( + <Host> + <div class="hy-prominent-image"> + <div class="hy-prominent-image__image"> + <hy-image image-url={this.imageUrl} aspectRatioWidth={2} aspectRatioHeight={1} /> + </div> + {this.textTitle && <div class="hy-prominent-image__text">{this.textTitle}</div>} + </div> + </Host> + ); + } +} diff --git a/src/components/hy-prominent-image/readme.md b/src/components/hy-prominent-image/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..1f050f149e308c359280c72cd0f44b7075a878aa --- /dev/null +++ b/src/components/hy-prominent-image/readme.md @@ -0,0 +1,28 @@ +# hy-prominent-image + +<!-- Auto Generated Below --> + +## Properties + +| Property | Attribute | Description | Type | Default | +| ----------- | ------------ | ----------- | -------- | ----------- | +| `imageUrl` | `image-url` | | `string` | `undefined` | +| `textTitle` | `text-title` | | `string` | `undefined` | + +## Dependencies + +### Depends on + +- [hy-image](../image) + +### Graph + +```mermaid +graph TD; + hy-prominent-image --> hy-image + style hy-prominent-image fill:#f9f,stroke:#333,stroke-width:4px +``` + +--- + +Helsinki University Design System diff --git a/src/components/hy-tabs-item/hy-tabs-item.scss b/src/components/hy-tabs-item/hy-tabs-item.scss new file mode 100644 index 0000000000000000000000000000000000000000..5d4e87f30f6362b8597dbe54a44aadaffa915763 --- /dev/null +++ b/src/components/hy-tabs-item/hy-tabs-item.scss @@ -0,0 +1,3 @@ +:host { + display: block; +} diff --git a/src/components/hy-tabs-item/hy-tabs-item.tsx b/src/components/hy-tabs-item/hy-tabs-item.tsx new file mode 100644 index 0000000000000000000000000000000000000000..dd206b337cdbb9a664625e0a1d7a000ca410d41a --- /dev/null +++ b/src/components/hy-tabs-item/hy-tabs-item.tsx @@ -0,0 +1,20 @@ +import {Component, h, Prop} from '@stencil/core'; + +@Component({ + tag: 'hy-tabs-item', + styleUrl: 'hy-tabs-item.scss', + shadow: false, +}) +export class HyTabsItem { + @Prop() tabTitle?: string; + + render() { + const id = this.tabTitle.toLowerCase().replace(/\W/g, '-'); + + return ( + <div tabindex="0" role="tabpanel" id={`${id}-tab`} aria-labelledby={id} hidden> + <slot></slot> + </div> + ); + } +} diff --git a/src/components/hy-tabs-item/readme.md b/src/components/hy-tabs-item/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..6bfeaaf3ee96fd2d5c4a40d9c775306469aaad9e --- /dev/null +++ b/src/components/hy-tabs-item/readme.md @@ -0,0 +1,13 @@ +# hy-tabs-item + +<!-- Auto Generated Below --> + +## Properties + +| Property | Attribute | Description | Type | Default | +| ---------- | ----------- | ----------- | -------- | ----------- | +| `tabTitle` | `tab-title` | | `string` | `undefined` | + +--- + +Helsinki University Design System diff --git a/src/components/hy-tabs/hy-tabs.scss b/src/components/hy-tabs/hy-tabs.scss new file mode 100644 index 0000000000000000000000000000000000000000..1b4066c833692178adfe93dc4519132aec7e48a4 --- /dev/null +++ b/src/components/hy-tabs/hy-tabs.scss @@ -0,0 +1,170 @@ +:host { + display: block; +} + +.hy-tabs__container { + .hy-tablist-container { + border-bottom: 1px solid var(--grayscale-tabs-border); + position: relative; + } + + .hy-tab-scroll { + background-color: var(--brand-main-light); + border-radius: 50%; + border: 0; + box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1); + padding: 0; + position: absolute; + top: 50%; + transform: translateY(-50%); + z-index: 3; + + &.is-hidden { + display: none; + visibility: hidden; + } + &.is-disabled { + background-color: var(--grayscale-medium); + + &:hover { + cursor: none; + } + } + + &:focus, + &:hover { + outline: none; + cursor: pointer; + } + + svg { + fill: var(--grayscale-white); + height: 16px; + margin: 8px; + position: relative; + width: 16px; + + @include breakpoint($narrow) { + height: 18px; + margin: 9px; //36x36 + width: 18px; + } + + @include breakpoint($wide) { + height: 24px; + margin: 10px; //44x44 + width: 24px; + } + } + + &__left { + left: 0; + + svg { + left: -1.5px; + } + } + + &__right { + right: 0; + + svg { + left: 1.5px; + } + } + } + + [role='tablist'] { + display: flex; + flex-direction: row; + margin: 0 0 -0.1em; + overflow: scroll; + position: relative; + white-space: nowrap; + + &::-webkit-scrollbar { + display: none; + } + } + + [role='tab'] { + @include font-size(14px, 16px); + + align-items: center; + background-color: var(--grayscale-background-box); + border: 0; + color: var(--grayscale-black); + display: flex; + flex-direction: row; + flex-shrink: 0; + font-family: var(--main-font-family); + font-weight: 600; + justify-content: center; + letter-spacing: -0.44px; + margin: 0 8px 0 0; + max-width: calc(100% / 12 * 3); + min-height: 44px; + min-width: 100px; + padding: 8px; + position: relative; + white-space: normal; + + span.tab-title { + padding: 8px; + } + + @include breakpoint($narrow) { + @include font-size(18px, 24px); + letter-spacing: -0.56px; + padding: 8px 12px; + + span.tab-title { + padding: 8px 12px; + } + } + @include breakpoint($medium) { + padding: 8px 16px; + + span.tab-title { + padding: 8px 16px; + } + } + @include breakpoint($overwide) { + margin-right: 12px; + } + } + + [role='tab'][aria-selected='true'] { + background: var(--grayscale-white); + border: 1px solid var(--grayscale-tabs-border); + border-bottom-width: 0; + box-shadow: inset 0 1px 0 0 var(--grayscale-medium), inset -1px 1px 0 0 var(--grayscale-medium), + inset 1px 1px 0 0 var(--grayscale-medium), 0 -4px 8px -4px rgba(0, 0, 0, 0.1); + font-weight: bold; + margin-bottom: -1px; + padding-bottom: 9px; + } + + [role='tab']:hover, + [role='tab']:focus, + [role='tab']:active { + border-radius: 0; + color: inherit; + cursor: pointer; + text-decoration: none; + } + + [role='tab']:hover::before, + [role='tab']:focus::before { + } + + [role='tabpanel'] { + padding: 1.5em 0.5em 0.7em; + position: relative; + z-index: 2; + } + + [role='tabpanel']:focus { + outline: 0; + } +} diff --git a/src/components/hy-tabs/hy-tabs.tsx b/src/components/hy-tabs/hy-tabs.tsx new file mode 100644 index 0000000000000000000000000000000000000000..620bce61ea0c7c4af483f41561d47596e8cd1d78 --- /dev/null +++ b/src/components/hy-tabs/hy-tabs.tsx @@ -0,0 +1,321 @@ +import {Component, ComponentInterface, h, Prop, State, Listen, Element} from '@stencil/core'; +// For easy reference +let keys = { + end: 35, + home: 36, + left: 37, + up: 38, + right: 39, + down: 40, + delete: 46, +}; + +// Add or substract depending on key pressed +let direction = { + 37: -1, + 38: -1, + 39: 1, + 40: 1, +}; +let checkTimeout; +let focusTimeout; + +@Component({ + tag: 'hy-tabs', + styleUrl: 'hy-tabs.scss', + shadow: false, +}) +export class HyTabs implements ComponentInterface { + @Prop() tabId?: string; + @Prop() tabListLabel: string = ''; + @State() focusTimeoutCleared: boolean = true; + @State() tabButtonTitles: object[]; + @State() tabPanelsState: NodeListOf<Element>[]; + @State() tabButtons: NodeListOf<Element>[]; + @State() tabList: NodeListOf<Element>[]; + @Element() el: HTMLElement; + + componentWillLoad() { + const tabItems = this.el.querySelectorAll('hy-tabs-item'); + if (tabItems) { + this.tabButtonTitles = Array.from(tabItems).map((panel) => { + return { + title: panel.getAttribute('tab-title'), + id: panel.getAttribute('tab-title').toLowerCase().replace(/\W/g, '-'), + }; + }); + } + } + + componentDidLoad() { + const tabContainer = this.el.children[0]; + if (tabContainer) { + this.generateArrays(tabContainer); + + const leftButton = this.el.querySelectorAll('.hy-tab-scroll__left')[0]; + const rightButton = this.el.querySelectorAll('.hy-tab-scroll__right')[0]; + const tabList = this.tabList as any; + this.checkScrollHidden(tabList, leftButton, rightButton); + + if (tabList.offSetWidth <= document.body.scrollWidth) { + leftButton.classList.add('is-hidden'); + rightButton.classList.add('is-hidden'); + } + + const oneTabWidth = 250; + rightButton.addEventListener('click', (e) => { + e.preventDefault(); + rightButton.classList.add('is-disabled'); + tabList.scrollBy({ + top: 0, + left: +oneTabWidth, + behavior: 'smooth', + }); + checkTimeout = window.setTimeout(() => { + this.checkScrollHidden(tabList, leftButton, rightButton); + rightButton.classList.remove('is-disabled'); + }, 250); + }); + + leftButton.addEventListener('click', (e) => { + e.preventDefault(); + leftButton.classList.add('is-disabled'); + tabList.scrollBy({ + top: 0, + left: -oneTabWidth, + behavior: 'smooth', + }); + checkTimeout = window.setTimeout(() => { + this.checkScrollHidden(tabList, leftButton, rightButton); + leftButton.classList.remove('is-disabled'); + }, 250); + }); + } + } + + generateArrays(tc) { + this.tabList = tc.querySelectorAll('[role="tablist"]')[0] as any; + this.tabButtons = tc.querySelectorAll('[role="tab"]') as any; + this.tabPanelsState = [tc.querySelectorAll('[role="tabpanel"]')] as any; + + if (this.tabButtons.length > 0) { + this.addListeners(this.tabButtons, 1); + this.activateTab(this.tabButtons[0], true); + } + } + + addListeners(tabs, index) { + if (tabs.length > 1) { + for (let i = 0; i < tabs.length; ++i) { + tabs[index].addEventListener('click', this.clickEventListener); + tabs[index].addEventListener('keydown', this.keydownEventListener); + //tabs[index].addEventListener('keyup', this.keyupEventListener); + tabs[index].index = index; + } + } + } + + checkScrollHidden(tablist, leftButton, rightButton) { + if (tablist.scrollLeft === 0) { + leftButton.classList.add('is-hidden'); + } else { + leftButton.classList.remove('is-hidden'); + } + if (tablist.scrollLeft + tablist.clientWidth >= tablist.scrollWidth - 1) { + rightButton.classList.add('is-hidden'); + } else { + rightButton.classList.remove('is-hidden'); + } + window.clearTimeout(checkTimeout); + } + + // When a tab is clicked, activateTab is fired to activate it + @Listen('click') + clickEventListener(event) { + if (event) { + const target = event.target; + const tabs = this.tabButtonTitles; + if (tabs) { + tabs.forEach((tab) => { + const id = Object.values(tab)[1]; + if (id === target.id) { + this.activateTab(target, true); + } + }); + event.stopPropagation(); + event.stopImmediatePropagation(); + } + } + } + + @Listen('keydown') + keydownEventListener(event) { + const key = event.keyCode; + const tabs = this.tabButtonTitles as any; + switch (key) { + case keys.end: + event.preventDefault(); + // Activate last tab + this.activateTab(tabs[tabs.length - 1], true); + break; + case keys.home: + event.preventDefault(); + // Activate first tab + this.activateTab(tabs[0], true); + break; + + // Up and down are in keydown + // because we need to prevent page scroll >:) + case keys.up: + case keys.down: + if (this.focusTimeoutCleared) { + this.determineOrientation(event); + } + break; + } + } + + @Listen('keydown') + keyupEventListener(event) { + const key = event.keyCode; + + switch (key) { + case keys.left: + case keys.right: + if (this.focusTimeoutCleared) { + this.determineOrientation(event); + } + break; + } + event.stopPropagation(); + event.stopImmediatePropagation(); + } + + determineOrientation(event) { + const leftButton = this.el.querySelectorAll('.hy-tab-scroll__left')[0]; + const rightButton = this.el.querySelectorAll('.hy-tab-scroll__right')[0]; + const tabList = this.tabList as any; + + this.checkScrollHidden(tabList, leftButton, rightButton); + + const key = event.keyCode; + const vertical = tabList.getAttribute('aria-orientation') == 'vertical'; + let proceed = false; + if (vertical) { + if (key === keys.up || key === keys.down) { + event.preventDefault(); + proceed = true; + } + } else { + if (key === keys.left || key === keys.right) { + proceed = true; + } + } + + if (proceed) { + this.switchTabOnArrowPress(event); + } + } + + switchTabOnArrowPress(event) { + const pressed = event.keyCode; + + if (direction[pressed]) { + const target = event.target; + const tabs = this.tabButtons as any; + + for (let i = 0; i < tabs.length; i++) { + if (tabs[i].id === target.id) { + if (i > 0) { + if (direction[pressed] === -1) { + tabs[i - 1].focus(); + this.focusEventHandler(tabs[i - 1]); + break; + } + } + if (i < tabs.length - 1) { + if (direction[pressed] === 1) { + tabs[i + 1].focus(); + this.focusEventHandler(tabs[i + 1]); + break; + } + } + } + } + } + } + + // Activates any given tab panel + activateTab(tab, setFocus) { + setFocus = setFocus || true; + // Deactivate all other tabs + this.deactivateTabs(this.tabButtons); + // Remove tabindex attribute + tab.removeAttribute('tabindex'); + tab.setAttribute('aria-selected', 'true'); + const controls = tab.getAttribute('aria-controls'); + this.el.querySelector(`#${controls}`).removeAttribute('hidden'); + + if (setFocus) { + tab.focus(); + } + window.clearTimeout(focusTimeout); + this.focusTimeoutCleared = true; + } + + // Deactivate all tabs and tab panels + deactivateTabs(tabs) { + for (let t = 0; t < tabs.length; t++) { + tabs[t].setAttribute('tabindex', '-1'); + tabs[t].setAttribute('aria-selected', 'false'); + tabs[t].removeEventListener('focus', this.focusEventHandler); + } + const panels = this.tabPanelsState[0]; + for (let p = 0; p < panels.length; p++) { + panels[p].setAttribute('hidden', 'hidden'); + } + } + + @Listen('focus') + focusEventHandler(tab) { + const target = tab; + this.focusTimeoutCleared = false; + focusTimeout = window.setTimeout(() => { + const focused = document.activeElement; + if (target === focused) { + this.activateTab(target, false); + } + }, 300); + } + + render() { + const classComponentAttributes = ['hy-tabs__container'].join(' '); + const id = this.tabId.toLowerCase().replace(/\W/g, '-'); + + return ( + <div id={id} class={classComponentAttributes}> + <div class="hy-tablist-container"> + <button class="hy-tab-scroll hy-tab-scroll__left is-hidden" aria-hidden="true"> + <hy-icon icon={'hy-icon-caret-left'} size={16} /> + </button> + <div role="tablist" aria-label={this.tabListLabel}> + {this.tabButtonTitles && + this.tabButtonTitles.map((item) => { + const title = Object.values(item)[0]; + const id = title.toLowerCase().replace(/\W/g, '-'); + return ( + <button role="tab" aria-selected="false" aria-controls={`${id}-tab`} id={id}> + {title} + </button> + ); + })} + </div> + <button class="hy-tab-scroll hy-tab-scroll__right" aria-hidden="true"> + <hy-icon icon={'hy-icon-caret-right'} size={16} /> + </button> + </div> + <slot></slot> + </div> + ); + } +} diff --git a/src/components/hy-tabs/readme.md b/src/components/hy-tabs/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..a44b7a525147022f09654e7ddb6432068dc0e6b4 --- /dev/null +++ b/src/components/hy-tabs/readme.md @@ -0,0 +1,28 @@ +# hy-tabs + +<!-- Auto Generated Below --> + +## Properties + +| Property | Attribute | Description | Type | Default | +| -------------- | ---------------- | ----------- | -------- | ----------- | +| `tabId` | `tab-id` | | `string` | `undefined` | +| `tabListLabel` | `tab-list-label` | | `string` | `''` | + +## Dependencies + +### Depends on + +- [hy-icon](../icon) + +### Graph + +```mermaid +graph TD; + hy-tabs --> hy-icon + style hy-tabs fill:#f9f,stroke:#333,stroke-width:4px +``` + +--- + +Helsinki University Design System diff --git a/src/components/icon/readme.md b/src/components/icon/readme.md index 2c3987cc2925e20fecab80740e32c1598937172e..3c1937f7e6e12131ccfaf394e37887e0792a2869 100644 --- a/src/components/icon/readme.md +++ b/src/components/icon/readme.md @@ -28,6 +28,7 @@ - [hy-site-header](../site-header) - [hy-site-logo](../site-header/site-logo) - [hy-site-search](../site-header/site-search) +- [hy-tabs](../hy-tabs) ### Graph @@ -47,6 +48,7 @@ graph TD; hy-site-header --> hy-icon hy-site-logo --> hy-icon hy-site-search --> hy-icon + hy-tabs --> hy-icon style hy-icon fill:#f9f,stroke:#333,stroke-width:4px ``` diff --git a/src/components/image/readme.md b/src/components/image/readme.md index 5e3aca605c385108db79f05e58388f7bad5ab5f1..e1e51c7f4b8fa7c263ab8790db0225b71f536e07 100644 --- a/src/components/image/readme.md +++ b/src/components/image/readme.md @@ -39,12 +39,14 @@ Defaults to 16/9 aspect ratio if none is provided. Caption is optional. ### Used by - [hy-introduction](../hy-introduction) +- [hy-prominent-image](../hy-prominent-image) ### Graph ```mermaid graph TD; hy-introduction --> hy-image + hy-prominent-image --> hy-image style hy-image fill:#f9f,stroke:#333,stroke-width:4px ``` diff --git a/src/global/_breakpoints.scss b/src/global/_breakpoints.scss index 231510a1061a4a18076d3151e4b392e98f1ecb54..176840554fc6799beef7b2af651ed40e4da3b1e3 100644 --- a/src/global/_breakpoints.scss +++ b/src/global/_breakpoints.scss @@ -1,16 +1,16 @@ :root { - --breakpoint-extrawide: 75rem; // 1200px - --breakpoint-max-width: 75rem; // 1200px + --breakpoint-extrawide: 75.0625rem; // 1201px + --breakpoint-max-width: 75.0625rem; // 1201px --breakpoint-medium: 48rem; // 768px --breakpoint-narrow: 30rem; // 480px - --breakpoint-overwide: 90rem; // 1440px + --breakpoint-overwide: 90.0625rem; // 1441px --breakpoint-wide: 60rem; // 960px } -$narrow: 30em; // 480px -$medium: 48em; // 768px -$wide: 60em; // 960px -$extrawide: 75em; // 1200px -$overwide: 90.0625rem; // 1441px +$narrow: 30em; // 480px 480-767 +$medium: 48em; // 768px 768-959 +$wide: 60em; // 960px 960-1200 //small desktop +$extrawide: 75.0625em; // 1201px //mid-size desktop full width lauout +$overwide: 90.0625rem; // >=1441px $fullhd: 120em; // 1920px $max-width: $overwide; diff --git a/src/global/_colors.scss b/src/global/_colors.scss index f860e0058b810a89331fe5c8b6e39ce5cd93984c..e0b53e7264f4574b2d34e66a8789839513a9f625 100644 --- a/src/global/_colors.scss +++ b/src/global/_colors.scss @@ -8,6 +8,7 @@ --grayscale-light: #f8f8f8; --grayscale-medium: #d2d2d2; --grayscale-background-box: #f5f5f5; + --grayscale-tabs-border: #e6e6e6; --grayscale-background-arrow: #dfdfdf; --grayscale-medium-dark: #979797; --grayscale-dark: #555555; diff --git a/src/index.html b/src/index.html index 20ff06d0af42fc18046b847797bfc20540a08ce8..a3b571872bf884839fa3d77915a2241a914d34b1 100644 --- a/src/index.html +++ b/src/index.html @@ -13,6 +13,13 @@ <body class="full-width" style="padding: 0; margin: 0;"> <hy-main has-sidebar="false"> <div class="layout-content"> + <hy-main-content-wrapper> + <hy-prominent-image + text-title="Making waves with research" + image-url="https://www.helsinki.fi/sites/default/files/styles/16_9_huge/public/kukkataedit_ja_-sedaet-6_0.jpg" + /> + </hy-main-content-wrapper> + <!-- <hy-introduction reversed text-title="This is an introduction" @@ -56,6 +63,8 @@ </hy-accordion-item> </hy-accordion-container> + --> + <!-- <hy-main-content-wrapper> <hy-heading heading="h2" section="contentsection"> A process illustrated with steps @@ -91,7 +100,7 @@ > </hy-large-process-flow> </hy-main-content-wrapper> - </div> + --></div> </hy-main> <!-- diff --git a/tsconfig.json b/tsconfig.json index 751e368d3e6ed526c6abeb0b141da3b361940d09..0789b5364aca50460789841a4f9a7a9b372fbac2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,10 +4,7 @@ "allowUnreachableCode": false, "declaration": false, "experimentalDecorators": true, - "lib": [ - "dom", - "es2017" - ], + "lib": ["dom", "es2017", "dom.iterable"], "moduleResolution": "node", "module": "esnext", "target": "es2017", @@ -16,11 +13,6 @@ "jsx": "react", "jsxFactory": "h" }, - "include": [ - "src", - "types/jsx.d.ts" - ], - "exclude": [ - "node_modules" - ] + "include": ["src", "types/jsx.d.ts"], + "exclude": ["node_modules"] }