diff --git a/src/components.d.ts b/src/components.d.ts index d789f00b6aa0865f3b631b78b257d8b520dbd848..082bbc9f5799cd0ea481cb4751ff5dd2cb4e6e26 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -26,6 +26,7 @@ import { LinkBoxVariants, LinkVariants, MenuType, + PersonCardVariants, ProcessFlowBoxStepStates, ProcessFlowBoxVariants, SiteLogoSize, @@ -585,6 +586,22 @@ export namespace Components { placement: string; variant: string; } + interface HyPersonCard { + categoryTitle?: string; + department?: string; + email?: string; + field?: string; + firstName?: string; + imageAlt?: string; + imageUrl?: string; + isExternal: boolean; + lastName?: string; + phone?: string; + positionTitle?: string; + scLabel?: string; + url?: string; + variant: PersonCardVariants; + } interface HyProcess { dataItems: ProcessFlowBoxValue[] | string; numberTerm: string; @@ -1043,6 +1060,11 @@ declare global { prototype: HTMLHyParagraphTextElement; new (): HTMLHyParagraphTextElement; }; + interface HTMLHyPersonCardElement extends Components.HyPersonCard, HTMLStencilElement {} + var HTMLHyPersonCardElement: { + prototype: HTMLHyPersonCardElement; + new (): HTMLHyPersonCardElement; + }; interface HTMLHyProcessElement extends Components.HyProcess, HTMLStencilElement {} var HTMLHyProcessElement: { prototype: HTMLHyProcessElement; @@ -1195,6 +1217,7 @@ declare global { 'hy-menu-mobile-breadcrumb': HTMLHyMenuMobileBreadcrumbElement; 'hy-menu-sidebar': HTMLHyMenuSidebarElement; 'hy-paragraph-text': HTMLHyParagraphTextElement; + 'hy-person-card': HTMLHyPersonCardElement; 'hy-process': HTMLHyProcessElement; 'hy-process-flow-box': HTMLHyProcessFlowBoxElement; 'hy-prominent-image': HTMLHyProminentImageElement; @@ -1763,6 +1786,22 @@ declare namespace LocalJSX { placement?: string; variant?: string; } + interface HyPersonCard { + categoryTitle?: string; + department?: string; + email?: string; + field?: string; + firstName?: string; + imageAlt?: string; + imageUrl?: string; + isExternal?: boolean; + lastName?: string; + phone?: string; + positionTitle?: string; + scLabel?: string; + url?: string; + variant?: PersonCardVariants; + } interface HyProcess { dataItems?: ProcessFlowBoxValue[] | string; numberTerm?: string; @@ -1974,6 +2013,7 @@ declare namespace LocalJSX { 'hy-menu-mobile-breadcrumb': HyMenuMobileBreadcrumb; 'hy-menu-sidebar': HyMenuSidebar; 'hy-paragraph-text': HyParagraphText; + 'hy-person-card': HyPersonCard; 'hy-process': HyProcess; 'hy-process-flow-box': HyProcessFlowBox; 'hy-prominent-image': HyProminentImage; @@ -2066,6 +2106,7 @@ declare module '@stencil/core' { JSXBase.HTMLAttributes<HTMLHyMenuMobileBreadcrumbElement>; 'hy-menu-sidebar': LocalJSX.HyMenuSidebar & JSXBase.HTMLAttributes<HTMLHyMenuSidebarElement>; 'hy-paragraph-text': LocalJSX.HyParagraphText & JSXBase.HTMLAttributes<HTMLHyParagraphTextElement>; + 'hy-person-card': LocalJSX.HyPersonCard & JSXBase.HTMLAttributes<HTMLHyPersonCardElement>; 'hy-process': LocalJSX.HyProcess & JSXBase.HTMLAttributes<HTMLHyProcessElement>; 'hy-process-flow-box': LocalJSX.HyProcessFlowBox & JSXBase.HTMLAttributes<HTMLHyProcessFlowBoxElement>; 'hy-prominent-image': LocalJSX.HyProminentImage & JSXBase.HTMLAttributes<HTMLHyProminentImageElement>; diff --git a/src/components/hy-person-card/hy-person-card.scss b/src/components/hy-person-card/hy-person-card.scss new file mode 100644 index 0000000000000000000000000000000000000000..53e21129282973e85d51c6c3af094acffda2aab1 --- /dev/null +++ b/src/components/hy-person-card/hy-person-card.scss @@ -0,0 +1,294 @@ +:host { + display: block; +} + +.hy-person-card-container { + font-family: var(--main-font-family); + + &__category-title { + @include font-size(12px, 14px); + color: var(--grayscale-dark); + letter-spacing: 0; + margin-bottom: 8px; + + @include breakpoint($narrow) { + @include font-size(14px, 18px); + } + + @include breakpoint($fullhd) { + @include font-size(14px, 18px); //>1920 desktop + } + } +} + +.hy-person-card { + display: flex; + font-family: var(--main-font-family); + margin-bottom: 28px; + text-decoration: none; + + @include breakpoint($wide) { + margin-bottom: 32px; + } + + &__image-container { + margin-right: 12px; + + @include breakpoint($narrow) { + margin-right: 16px; + } + + &__image { + height: 70px; + width: 70px; + + @include breakpoint($narrow) { + height: 112px; + width: 112px; + } + + @include breakpoint($fullhd) { + //1920 + height: 140px; + width: 140px; + } + img { + height: 70px; + width: 70px; + object-fit: cover; + + @include breakpoint($narrow) { + height: 112px; + width: 112px; + } + + @include breakpoint($fullhd) { + height: 140px; + width: 140px; + } + } + } + &__no-image { + height: 70px; + width: 70px; + + align-items: center; + background-color: var(--brand-main-light); + display: flex; + justify-content: center; + + @include breakpoint($narrow) { + height: 112px; + width: 112px; + } + + @include breakpoint($fullhd) { + height: 140px; + width: 140px; + } + + &__initials { + @include font-size(18px, 24px); + @include font-weight($bold); + color: var(--grayscale-white); + letter-spacing: -0.56px; + + @include breakpoint($narrow) { + @include font-size(32px, 32px); + letter-spacing: 0.96px; + } + } + } + } + + &__info-container { + &__full-name { + @include font-size(18px, 24px); + @include font-weight($bold); + + color: var(--brand-main-light); + letter-spacing: -0.56px; + margin-bottom: 4px; + + @include breakpoint($narrow) { + @include font-size(22px, 28px); + letter-spacing: -0.69px; + margin-bottom: 3px; + } + + @include breakpoint($fullhd) { + @include font-size(26px, 28px); + letter-spacing: -0.81px; + margin-bottom: 5px; + } + } + + &__position-title { + @include font-size(14px, 19px); + @include font-weight($bold); + color: var(--brand-main-nearly-black); + letter-spacing: 0; + margin-bottom: 3px; + + @include breakpoint($narrow) { + @include font-size(16px, 20px); + letter-spacing: -0.5px; + margin-bottom: 4px; + } + + @include breakpoint($fullhd) { + @include font-size(18px, 24px); + letter-spacing: -0.56px; + margin-bottom: 8px; + } + } + + &__department { + @include font-size(12px, 17px); + color: var(--brand-main-nearly-black); + letter-spacing: 0; + margin-bottom: 3px; + text-transform: uppercase; + + @include breakpoint($narrow) { + @include font-size(14px, 19px); + margin-bottom: 6px; + } + + @include breakpoint($fullhd) { + @include font-size(14px, 16px); + margin-bottom: 9px; + } + } + + &__field { + @include font-size(12px, 17px); + color: var(--brand-main-nearly-black); + letter-spacing: 0; + margin-bottom: 4px; + + @include breakpoint($narrow) { + @include font-size(14px, 19px); + margin-bottom: 6px; + } + + @include breakpoint($fullhd) { + @include font-size(14px, 16px); + margin-bottom: 9px; + } + } + + &__email-container { + align-items: center; + display: flex; + margin-bottom: 4px; + } + &__email { + @include font-size(12px, 17px); + color: var(--brand-main-nearly-black); + letter-spacing: 0; + margin-left: 11px; + + @include breakpoint($narrow) { + @include font-size(14px, 19px); + margin-left: 8px; + } + + &__icon { + svg { + height: 15px; + width: 16px; + + @include breakpoint($narrow) { + height: 12px; + width: 16px; + } + } + } + } + + &__phone-container { + align-items: center; + display: flex; + } + &__phone { + @include font-size(12px, 17px); + color: var(--brand-main-nearly-black); + letter-spacing: 0; + margin-left: 11px; + + @include breakpoint($narrow) { + @include font-size(14px, 19px); + margin-left: 8px; + } + + &__icon { + svg { + height: 13px; + width: 13px; + + @include breakpoint($narrow) { + height: 14px; + width: 14px; + } + } + } + } + } + + &.search-panel { + margin-bottom: 12px; + + @include breakpoint($narrow) { + margin-bottom: 14px; + } + + .hy-person-card__info-container__full-name { + @include font-size(14px, 20px); + @include font-weight($semibold); + color: var(--grayscale-dark); + letter-spacing: 0; + margin-bottom: 0; + + @include breakpoint($narrow) { + letter-spacing: -0.3px; + } + + @include breakpoint($fullhd) { + @include font-size(16px, 24px); + letter-spacing: 0; + } + } + + .hy-person-card__info-container__position-title { + @include font-size(12px, 16px); + color: var(--grayscale-dark); + letter-spacing: -0.07px; + margin-bottom: 0; + } + + .hy-person-card__image-container { + margin-right: 12px; + + &__image { + height: 60px; + width: 60px; + img { + height: 60px; + width: 60px; + } + } + &__no-image { + height: 60px; + width: 60px; + + &__initials { + color: var(--grayscale-white); + @include font-size(16px, 32px); + @include font-weight($bold); + letter-spacing: 0.48px; + } + } + } + } +} diff --git a/src/components/hy-person-card/hy-person-card.tsx b/src/components/hy-person-card/hy-person-card.tsx new file mode 100644 index 0000000000000000000000000000000000000000..051fcfee538e7c9b23b9915e1314ea034561a17f --- /dev/null +++ b/src/components/hy-person-card/hy-person-card.tsx @@ -0,0 +1,94 @@ +import {Component, h, Prop} from '@stencil/core'; +import {PersonCardVariants} from '../../utils/utils'; + +@Component({ + tag: 'hy-person-card', + styleUrl: 'hy-person-card.scss', + shadow: false, +}) +export class HyPersonCard { + @Prop() variant: PersonCardVariants = PersonCardVariants.default; + @Prop() categoryTitle?: string; + @Prop() imageUrl?: string; + @Prop() imageAlt?: string; + @Prop() firstName?: string; + @Prop() lastName?: string; + @Prop() url?: string; + @Prop() isExternal: boolean = false; + @Prop() scLabel?: string; + @Prop() positionTitle?: string; + @Prop() department?: string; + @Prop() field?: string; + @Prop() email?: string; + @Prop() phone?: string; + private _fullName: string; + private _initials: string; + + componentWillLoad() { + this._fullName = this.firstName && this.lastName ? `${this.firstName} ${this.lastName}` : ''; + this._initials = + this.imageUrl && this.firstName && this.lastName ? '' : `${this.firstName.charAt(0)}${this.lastName.charAt(0)}`; + if (!this.imageAlt) { + this.imageAlt = this._fullName; + } + if (!this.scLabel) { + this.scLabel = 'Link to the person profile'; + } + } + + render() { + const classAttributes = ['hy-person-card', this.variant].join(' '); + const target = this.isExternal ? '_blank' : '_self'; + + return ( + <article class="hy-person-card-container"> + {this.variant == PersonCardVariants.default && ( + <div class="hy-person-card-container__category-title">{this.categoryTitle}</div> + )} + <a class={classAttributes} href={this.url} target={target} aria-label={this.scLabel}> + <div class="hy-person-card__image-container"> + {this.imageUrl ? ( + <div class="hy-person-card__image-container__image"> + <img aria-hidden="true" src={this.imageUrl} alt={this.imageAlt} /> + </div> + ) : ( + <div class="hy-person-card__image-container__no-image"> + <div class="hy-person-card__image-container__no-image__initials">{this._initials}</div> + </div> + )} + </div> + <div class="hy-person-card__info-container"> + <div class="hy-person-card__info-container__full-name">{this._fullName}</div> + {this.positionTitle && ( + <div class="hy-person-card__info-container__position-title">{this.positionTitle}</div> + )} + {this.department && this.variant == PersonCardVariants.default && ( + <div class="hy-person-card__info-container__department">{this.department}</div> + )} + {this.field && this.variant == PersonCardVariants.default && ( + <div class="hy-person-card__info-container__field">{this.field}</div> + )} + {this.email && + this.variant == PersonCardVariants.default && [ + <div class="hy-person-card__info-container__email-container"> + <span class="hy-person-card__info-container__email__icon"> + <hy-icon icon={'hy-icon-email'} size={16} /> + </span> + <span class="hy-person-card__info-container__email">{this.email}</span> + </div>, + ]} + {this.phone && + this.variant == PersonCardVariants.default && [ + <div class="hy-person-card__info-container__phone-container"> + <span class="hy-person-card__info-container__phone__icon"> + <hy-icon icon={'hy-icon-phone'} size={14} /> + </span> + <span class="hy-person-card__info-container__phone">{this.phone}</span> + </div>, + ]} + </div> + </a> + </article> + ); + } +} diff --git a/src/components/hy-person-card/readme.md b/src/components/hy-person-card/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..e89c3058ff5c949ec5f39645353173522b45079f --- /dev/null +++ b/src/components/hy-person-card/readme.md @@ -0,0 +1,40 @@ +# hy-person-card + +<!-- Auto Generated Below --> + +## Properties + +| Property | Attribute | Description | Type | Default | +| --------------- | ---------------- | ----------- | -------------------------------------------------------------- | ---------------------------- | +| `categoryTitle` | `category-title` | | `string` | `undefined` | +| `department` | `department` | | `string` | `undefined` | +| `email` | `email` | | `string` | `undefined` | +| `field` | `field` | | `string` | `undefined` | +| `firstName` | `first-name` | | `string` | `undefined` | +| `imageAlt` | `image-alt` | | `string` | `undefined` | +| `imageUrl` | `image-url` | | `string` | `undefined` | +| `isExternal` | `is-external` | | `boolean` | `false` | +| `lastName` | `last-name` | | `string` | `undefined` | +| `phone` | `phone` | | `string` | `undefined` | +| `positionTitle` | `position-title` | | `string` | `undefined` | +| `scLabel` | `sc-label` | | `string` | `undefined` | +| `url` | `url` | | `string` | `undefined` | +| `variant` | `variant` | | `PersonCardVariants.default \| PersonCardVariants.searchPanel` | `PersonCardVariants.default` | + +## Dependencies + +### Depends on + +- [hy-icon](../icon) + +### Graph + +```mermaid +graph TD; + hy-person-card --> hy-icon + style hy-person-card fill:#f9f,stroke:#333,stroke-width:4px +``` + +--- + +Helsinki University Design System diff --git a/src/components/icon/icon.tsx b/src/components/icon/icon.tsx index 35c38b94740398840c7ef769f9738a8442bf72e1..3d04ef5e3420d1cebaba06fadc1e5b52b2d39262 100644 --- a/src/components/icon/icon.tsx +++ b/src/components/icon/icon.tsx @@ -19,6 +19,8 @@ const iconNames: IconName = { 'hy-icon-globe': (p) => <icons.Globe {...p} />, 'hy-icon-hamburger': (p) => <icons.Hamburger {...p} />, 'hy-icon-home': (p) => <icons.Home {...p} />, + 'hy-icon-email': (p) => <icons.Mail {...p} />, + 'hy-icon-phone': (p) => <icons.Phone {...p} />, 'hy-icon-hy-logo': (p) => <icons.HyLogo {...p} />, 'hy-icon-image-gallery': (p) => <icons.ImageGallery {...p} />, 'hy-icon-link-arrow-down': (p) => <icons.LinkArrowDown {...p} />, diff --git a/src/components/icon/readme.md b/src/components/icon/readme.md index c42057f3becc0d5f442783211c25543c9e74b0f5..7641c398bd65405636605d708b4ebdf88fd25d76 100644 --- a/src/components/icon/readme.md +++ b/src/components/icon/readme.md @@ -38,6 +38,7 @@ - [hy-menu-level-container](../navigation/menu-level-container) - [hy-menu-mobile-breadcrumb](../navigation/menu-mobile-breadcrumb) - [hy-menu-sidebar](../navigation/menu-sidebar) +- [hy-person-card](../hy-person-card) - [hy-quote](../hy-quote) - [hy-search-field](../hy-search-field) - [hy-shortcuts](../hy-shortcuts) @@ -75,6 +76,7 @@ graph TD; hy-menu-level-container --> hy-icon hy-menu-mobile-breadcrumb --> hy-icon hy-menu-sidebar --> hy-icon + hy-person-card --> hy-icon hy-quote --> hy-icon hy-search-field --> hy-icon hy-shortcuts --> hy-icon diff --git a/src/components/navigation/menu-sidebar/menu-sidebar.tsx b/src/components/navigation/menu-sidebar/menu-sidebar.tsx index 8e0dcd02fa6ed7734436053b05b674f67f9b95c7..c643207b9382309dd735d738a5f8a0918259a895 100644 --- a/src/components/navigation/menu-sidebar/menu-sidebar.tsx +++ b/src/components/navigation/menu-sidebar/menu-sidebar.tsx @@ -86,26 +86,32 @@ export class MenuSidebar { componentWillLoad() { const sideBar = document.querySelector('.layout-sidebar-first'); - if (this.menuIsOpen) { - sideBar.classList.add('menu-is-open'); - } else if (sideBar.classList.contains('menu-is-open')) { - sideBar.classList.remove('menu-is-open'); + if (sideBar) { + if (this.menuIsOpen) { + sideBar.classList.add('menu-is-open'); + } else if (sideBar.classList.contains('menu-is-open')) { + sideBar.classList.remove('menu-is-open'); + } } } componentDidLoad() { const sidebarContainer = document.querySelector('.hy-menu-sidebar--container'); - sidebarContainer.addEventListener('click', (e) => { - if ((e.target as HTMLElement).classList.contains('sidebar-open')) { - this.closeWholeTree(); - } - }); + if (sidebarContainer) { + sidebarContainer.addEventListener('click', (e) => { + if ((e.target as HTMLElement).classList.contains('sidebar-open')) { + this.closeWholeTree(); + } + }); + } const sidepanelContainer = document.querySelector('.hy-menu-sidepanel--container'); - sidepanelContainer.addEventListener('click', (e) => { - if ((e.target as HTMLElement).classList.contains('sidepanel-open')) { - this.panelToggle(e); - } - }); + if (sidepanelContainer) { + sidepanelContainer.addEventListener('click', (e) => { + if ((e.target as HTMLElement).classList.contains('sidepanel-open')) { + this.panelToggle(e); + } + }); + } const panels = document.querySelectorAll('.hy-menu-level-container'); if (panels) { diff --git a/src/index.html b/src/index.html index 6d625609c548333d9a40d293dd6cfcceb5b7eaa4..cfb1f1c4bfbb3d1b8caba96d3af40392b62244ea 100644 --- a/src/index.html +++ b/src/index.html @@ -182,6 +182,64 @@ THIS IS MAIN CONTENT </hy-paragraph-text> + <hy-person-card + variant="search-panel" + category-title="People" + image-url="https://flamma.helsinki.fi/documents/20142/1322159/upload_00015721.jpg" + image-alt="professor" + first-name="Dolor" + last-name="Sit-amet" + url="https://www.helsinki.fi/sv/people/people-finder/person-by-id/9038938" + sc-label="Link opens up in a new tab" + position-title="Postdoctoral Researcher" + department="DEPARTMENT OF BIOCHEMISTRY AND DEVELOPMENTAL BIOLOGY" + field="Fields of science: Biochemistry, cell and molecular biology" + email="lorem.ipsumlorem@helsinki.fi" + phone="0212345678" + > + </hy-person-card> + <hy-person-card + variant="search-panel" + category-title="People" + first-name="Paula" + last-name="Elomaa" + url="https://www.helsinki.fi/sv/people/people-finder/person-by-id/9038938" + is-external="true" + position-title="professor" + department="Agrikultur-forstvetenskapliga fakulteten" + email="paula.elomaa@helsinki.fi" + phone="0504480427" + > + </hy-person-card> + + <hy-person-card + category-title="People" + image-url="https://flamma.helsinki.fi/documents/20142/1322159/upload_00015721.jpg" + image-alt="professor" + first-name="Dolor" + last-name="Sit-amet" + url="https://www.helsinki.fi/sv/people/people-finder/person-by-id/9038938" + is-external="true" + sc-label="Link opens up in a new tab" + position-title="Postdoctoral Researcher" + department="DEPARTMENT OF BIOCHEMISTRY AND DEVELOPMENTAL BIOLOGY" + field="Fields of science: Biochemistry, cell and molecular biology" + email="lorem.ipsumlorem@helsinki.fi" + phone="0212345678" + > + </hy-person-card> + <hy-person-card + category-title="People" + first-name="Paula" + last-name="Elomaa" + url="https://www.helsinki.fi/sv/people/people-finder/person-by-id/9038938" + position-title="professor" + department="Agrikultur-forstvetenskapliga fakulteten" + email="paula.elomaa@helsinki.fi" + phone="0504480427" + > + </hy-person-card> + <hy-link-list list-heading="link list heading number one" data-items='[ diff --git a/src/utils/utils.ts b/src/utils/utils.ts index d16c1f46e1710d40d57d6ce41ea2d060ef549021..f4ee84acf452a67a94bcbc7c9ba75be93ffaf0d3 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -73,6 +73,11 @@ export enum CourseVariants { grid = 'grid', } +export enum PersonCardVariants { + default = 'results', + searchPanel = 'search-panel', +} + export enum GridColumnsSm { columnsSm1 = '1', columnsSm2 = '2',