From bbebec0543e95edee7c14279f66a1c4ba9b5684d Mon Sep 17 00:00:00 2001 From: Hener Hoop Date: Wed, 18 Oct 2023 21:26:10 +0300 Subject: [PATCH] feat(cxl-ui): refactor light-card https://app.clickup.com/t/86ayhhg16 --- packages/cxl-lumo-styles/scss/badge.scss | 5 + packages/cxl-lumo-styles/scss/global.scss | 1 + packages/cxl-ui/scss/cxl-light-card.scss | 196 ++++++++++++++---- .../cxl-ui/src/components/cxl-light-card.js | 148 +++++++++++-- .../cxl-ui/cxl-light-card/default.stories.js | 48 ++++- .../light-card-slider.stories.js | 15 +- .../cxl-ui/cxl-light-card/template.js | 5 +- 7 files changed, 338 insertions(+), 80 deletions(-) diff --git a/packages/cxl-lumo-styles/scss/badge.scss b/packages/cxl-lumo-styles/scss/badge.scss index 083ce7ae2..a2aa47ae0 100644 --- a/packages/cxl-lumo-styles/scss/badge.scss +++ b/packages/cxl-lumo-styles/scss/badge.scss @@ -12,6 +12,11 @@ background-color: var(--cxl-color-brand-blue); } +[theme~="badge"][theme~="tertiary"] { + color: var(--lumo-primary-contrast-color); + background-color: var(--cxl-color-dark-green); +} + .course-skills { [theme~="badge"] { margin-right: var(--lumo-space-s); diff --git a/packages/cxl-lumo-styles/scss/global.scss b/packages/cxl-lumo-styles/scss/global.scss index ae9549991..a40c0b6ee 100644 --- a/packages/cxl-lumo-styles/scss/global.scss +++ b/packages/cxl-lumo-styles/scss/global.scss @@ -13,6 +13,7 @@ html { --cxl-color-black-50pct: hsla(0, 0%, 0%, 0.5); --cxl-color-brand-blue: hsla(214, 61%, 25%, 1); --cxl-color-light-pink: hsl(353, 73%, 96%, 1); + --cxl-color-dark-green: hsl(148, 57%, 24%, 1); /** * Lumo Icons have a documented 4px "safe area" around them. Vaadin Icons don't, for unknown reasons. diff --git a/packages/cxl-ui/scss/cxl-light-card.scss b/packages/cxl-ui/scss/cxl-light-card.scss index 1dea00503..8d56abff9 100644 --- a/packages/cxl-ui/scss/cxl-light-card.scss +++ b/packages/cxl-ui/scss/cxl-light-card.scss @@ -1,68 +1,174 @@ +/* stylelint-disable value-no-vendor-prefix, property-no-vendor-prefix -- some of these are necessary for line-clamp implementation */ +@use "~@conversionxl/cxl-lumo-styles/scss/mq"; +@use "~@conversionxl/cxl-ui/scss/mixins"; + :host { - min-width: 267px; // 3col widths on 1400px - max-width: 300px; - height: auto; - min-height: calc(3 * var(--lumo-space-xl)); - padding: var(--lumo-space-m); - - .container > .attributes { - display: none; + position: relative; + box-sizing: border-box; + display: flex; + flex-direction: column; + justify-content: space-between; + width: 100%; + height: 100%; + min-height: 122px; + min-width: 267px; + padding: var(--lumo-space-m) var(--lumo-space-s) var(--lumo-space-s) var(--lumo-space-m); + font-size: var(--lumo-font-size-s); + background: var(--lumo-tint); + border: 1px solid var(--lumo-contrast-10pct); + border-radius: var(--lumo-border-radius-l); + box-shadow: var(--lumo-box-shadow-xs); + break-inside: avoid; + transform: translateZ(0); // CSS columns @see https://stackoverflow.com/a/55110789/35946 + overflow: hidden; +} + +:host([hidden]) { + display: none; +} + +:host(:hover) { + border-color: var(--lumo-primary-color); +} + +:host([portrait]) { + display: block; + width: 175px; + min-width: 0; + padding: 0; +} + +:host([completed]) { + opacity: 0.6; +} + +:host([theme~="minidegree"]) { + background-color: var(--lumo-contrast); +} + +.container { + display: flex; + justify-content: space-between; +} + +.badge-new { + position: absolute; + right: var(--lumo-space-s); + bottom: var(--lumo-space-s); + display: none; + padding: calc(var(--lumo-space-xs) / 2) var(--lumo-space-s); + font-size: var(--lumo-font-size-s); + font-weight: 700; + line-height: var(--lumo-line-height-s); + color: var(--lumo-primary-contrast-color); + border-radius: calc(var(--lumo-border-radius-l) * 6); + background: var(--lumo-primary-color); + z-index: 2; + + :host([new]) & { + display: block; } +} - header { - .info { - .name { - font-size: var(--lumo-font-size-m); - font-weight: 600; - } +header { + display: flex; + align-items: start; + flex-direction: column; + flex-grow: 1; + justify-content: space-between; + padding-right: 112px; // avatar width - .attributes { - display: flex; - flex-direction: column; - align-items: flex-start; - padding-top: var(--lumo-space-xs); - padding-bottom: 0; - gap: var(--lumo-space-xs); - } + :host([portrait]) & { + padding: var(--lumo-space-m) calc(var(--lumo-space-xs) * 3) 0 calc(var(--lumo-space-xs) * 3); + } - cxl-time { - margin-left: -3px; // to align with the instructor text - } + :host([theme~="minidegree"]) & { + justify-content: flex-start; + } + + .info { + display: flex; + flex-direction: column; + gap: var(--lumo-space-xs); + } + + .tag { + text-transform: capitalize; + + &:first-child { + color: var(--lumo-primary-color); } + } + + .name { + font-family: var(--lumo-font-family); + font-size: var(--lumo-font-size-m); + font-style: normal; + font-weight: 600; + line-height: var(--lumo-line-height-xs); + @include mixins.ellipsis-for-lines(2); - .instructor-image { - height: 80px; + :host([theme~="minidegree"]) & { + color: var(--lumo-tint); + } + + a { + color: var(--lumo-body-text-color); + text-decoration: none; + + &:hover { + text-decoration: underline; + } } } -} -:host([completed]) { - opacity: 0.6; + cxl-time { + margin-left: -3px; // to align with the instructor text + color: var(--lumo-shade); + opacity: .6; + + :host([theme~="minidegree"]) & { + color: var(--lumo-tint-40pct); + opacity: 1; + } + } [icon="lumo:checkmark"] { display: inline-block; margin-top: calc(var(--lumo-space-xs) * -1); color: var(--lumo-primary-color); } -} -::slotted(footer) { - display: flex; - flex-wrap: wrap; - gap: var(--lumo-space-xs); -} - -:host([theme~="minidegree"]) { - .name { - color: var(--lumo-primary-contrast-color); + .progress { + display: flex; + flex-direction: column; + justify-content: center; + font-size: var(--lumo-font-size-xs); + line-height: var(--lumo-line-height-s); + color: var(--lumo-body-text-color); } - .attributes { - color: var(--lumo-tint-40pct); + .instructor { + color: var(--lumo-shade); + opacity: .6; } +} - cxl-time { - margin-left: -3px; // to align with the instructor text - color: var(--lumo-tint-40pct); +::slotted([slot="badges"]) { + position: relative; + z-index: 1; +} + +.avatar { + position: absolute; + right: 0; + bottom: 0; + display: block; + width: 112px; + + :host([portrait]) & { + position: relative; + width: 100%; + margin-top: var(--lumo-space-s); } } diff --git a/packages/cxl-ui/src/components/cxl-light-card.js b/packages/cxl-ui/src/components/cxl-light-card.js index b916a8e0c..1fde425f2 100644 --- a/packages/cxl-ui/src/components/cxl-light-card.js +++ b/packages/cxl-ui/src/components/cxl-light-card.js @@ -1,17 +1,44 @@ /* eslint-disable import/no-extraneous-dependencies */ -import { html, nothing } from 'lit'; +import { html, LitElement, nothing } from 'lit'; import { unsafeHTML } from 'lit/directives/unsafe-html.js'; -import { customElement, property } from 'lit/decorators.js'; -import '@conversionxl/cxl-lumo-styles'; +import { customElement, property, state } from 'lit/decorators.js'; + +import '@vaadin/progress-bar'; + import cxlLightCardStyles from '../styles/cxl-light-card-css.js'; -import { CXLBaseCardElement } from './cxl-base-card.js'; @customElement('cxl-light-card') -export class CXLLightCardElement extends CXLBaseCardElement { +export class CXLLightCardElement extends LitElement { static get styles() { - return [...super.styles, cxlLightCardStyles]; + return [cxlLightCardStyles]; } + separator = html` | `; + + @state() _tagsHasChildren = false; + + @property({ type: String }) id = ''; + + @property({ type: String }) theme = ''; + + @property({ type: String }) name = ''; + + @property({ type: String }) avatar = ''; + + @property({ type: String }) time = ''; + + @property({ type: String }) instructor = ''; + + @property({ type: Number }) progress = 0; + + @property({ type: Number }) lessons = 0; + + @property({ type: Boolean, reflect: true }) new = false; + + @property({ type: Boolean, reflect: true }) portrait = false; + + @property({ type: Boolean, reflect: true }) completed = false; + constructor() { super(); this.showTimeIcon = true; @@ -28,23 +55,116 @@ export class CXLLightCardElement extends CXLBaseCardElement { this.requestUpdate('showTags', this._showTags); } - _renderHeaderName() { + _slotHasChildren(e) { + const slot = e.target; + const { name } = slot; + const children = slot.assignedNodes(); + this[`_${name}HasChildren`] = !!children.length; + } + + _renderNew() { + return this.new ? html`NEW` : nothing; + } + + _renderTags() { + if (this.showTags) { + return html` +
+ ${this.theme ? html`${this.theme}` : ''} + ${this.theme && this._tagsHasChildren ? this.separator : ''} + +
+ `; + } + + return nothing; + } + + _renderName() { return html`
- ${unsafeHTML(this.name)}${ - this.completed ? html`` : nothing + ${unsafeHTML(this.name)} + ${this.completed ? html`` : nothing} +
+ `; + } + + _renderTime() { + if ( this.time ) { + return html` + + `; } + + return nothing; + } + + _renderInfo() { + return html` +
+ ${this._renderTags()} + ${this._renderName()} + ${this._renderTime()}
`; } + _renderProgress() { + if ( this.progress && this.lessons ) { + return html` +
+ + Completed ${this.progress} of ${this.lessons} + + + Completed ${this.progress} of ${this.lessons} + +
+ `; + } + + return nothing; + } + + _renderInstructor() { + if ( this.instructor ) { + return html` +
By: ${this.instructor}
+ `; + } + + return nothing; + } + + /** + * Renders the header of the component. + * @return {string} The rendered header HTML. + */ + _renderHeader() { + return html` +
+ ${this._renderInfo()} + ${this._renderProgress()} + ${this._renderInstructor()} +
+ `; + } + + _renderAvatar() { + return this.avatar ? html`${this.instructor}` : nothing; + } + + // eslint-disable-next-line class-methods-use-this + _renderBadges() { + return html``; + } + render() { return html` -
- ${this._renderHeader()} - - -
+ ${this._renderNew()} + ${this._renderHeader()} + ${this._renderBadges()} + ${this._renderAvatar()} `; } } diff --git a/packages/storybook/cxl-ui/cxl-light-card/default.stories.js b/packages/storybook/cxl-ui/cxl-light-card/default.stories.js index 4a24cdc81..c56281d37 100644 --- a/packages/storybook/cxl-ui/cxl-light-card/default.stories.js +++ b/packages/storybook/cxl-ui/cxl-light-card/default.stories.js @@ -10,19 +10,40 @@ export default { }; export const CXLLightCard = Template.bind({}); +export const CXLLightCardProgress = Template.bind({}); +export const CXLLightCardPortrait = Template.bind({}); export const CXLLightCardMinidegree = Template.bind({}); -export const CXLLightCardFooter = Template.bind({}); +export const CXLLightCardBadges = Template.bind({}); CXLLightCard.args = { - theme: 'light-card', + theme: 'new-card', + name: 'Account based marketing', + time: '43h 00min', + instructor: 'Ton Wesseling', + avatar: 'https://cxl.com/institute/wp-content/uploads/2020/07/ben-labay_team_headshot-1x1-bw-min-150x150.png', + completed: false, + portrait: false, new: false, +}; + +CXLLightCardProgress.args = { + theme: 'new-card', + name: 'Account based marketing', + progress: 2, + lessons: 6, + avatar: 'https://cxl.com/institute/wp-content/uploads/2020/07/ben-labay_team_headshot-1x1-bw-min-150x150.png', completed: false, + portrait: false, + new: false, +}; + +CXLLightCardPortrait.args = { + theme: 'new-card', name: 'Account based marketing', - time: '3h 00min', - instructor: 'Ton Wesseling', - avatar: - 'https://cxl.com/institute/wp-content/uploads/2020/05/48192546_10156982340630746_8127333122065825792_n-wpv_400pxx400px_center_center.jpg', - footer: '', + avatar: 'https://cxl.com/institute/wp-content/uploads/2020/07/ben-labay_team_headshot-1x1-bw-min-150x150.png', + completed: false, + portrait: true, + new: false, }; CXLLightCardMinidegree.args = { @@ -33,8 +54,13 @@ CXLLightCardMinidegree.args = { time: '43h 00min', }; -CXLLightCardFooter.args = { - ...CXLLightCard.args, - footer: - 'Team Roadmap Personal Roadmap', +CXLLightCardBadges.args = { + theme: 'new-card', + name: 'Account based marketing', + instructor: 'Ton Wesseling', + avatar: 'https://cxl.com/institute/wp-content/uploads/2020/07/ben-labay_team_headshot-1x1-bw-min-150x150.png', + completed: false, + new: false, + badges: + 'Team Personal', }; diff --git a/packages/storybook/cxl-ui/cxl-light-card/light-card-slider.stories.js b/packages/storybook/cxl-ui/cxl-light-card/light-card-slider.stories.js index 88c67a5da..d0b106527 100644 --- a/packages/storybook/cxl-ui/cxl-light-card/light-card-slider.stories.js +++ b/packages/storybook/cxl-ui/cxl-light-card/light-card-slider.stories.js @@ -12,28 +12,25 @@ const CXLLightCard = Template.bind({}); CXLLightCard.args = { theme: 'light-card', name: 'Account based marketing', - time: '3h 00min', - instructor: 'Ton Wesseling', - avatar: - 'https://cxl.com/institute/wp-content/uploads/2020/05/48192546_10156982340630746_8127333122065825792_n-wpv_400pxx400px_center_center.jpg', + avatar: 'https://cxl.com/institute/wp-content/uploads/2020/05/48192546_10156982340630746_8127333122065825792_n-wpv_400pxx400px_center_center.jpg', + progress: 3, + lessons: 12 }; const CXLLightCard2 = Template.bind({}); CXLLightCard2.args = { ...CXLLightCard.args, name: 'Basics of Casual Inference', - time: '', - instructor: 'Georgi Georgiev', avatar: '', + progress: 7, + lessons: 9 }; const CXLLightCard3 = Template.bind({}); CXLLightCard3.args = { ...CXLLightCard.args, name: 'Best Practices', - time: '1h 30min', - instructor: 'Peep Laja', - avatar: 'https://cxl.com/institute/wp-content/uploads/2019/09/peep-1x1-transparent-150x150.png', + avatar: 'https://cxl.com/institute/wp-content/uploads/2019/09/peep-1x1-transparent-150x150.png' }; export const CXLLightCardSlider = ({ numberOfCards, theme }) => html` diff --git a/packages/storybook/cxl-ui/cxl-light-card/template.js b/packages/storybook/cxl-ui/cxl-light-card/template.js index a55816cd8..961182152 100644 --- a/packages/storybook/cxl-ui/cxl-light-card/template.js +++ b/packages/storybook/cxl-ui/cxl-light-card/template.js @@ -6,11 +6,14 @@ export const Template = (card) => html` theme="${card.theme}" name="${card.name}" time="${card.time}" + progress="${card.progress}" + lessons="${card.lessons}" instructor="${card.instructor}" avatar="${card.avatar}" .new="${card.new}" .completed="${card.completed}" + .portrait="${card.portrait}" > - ${card.footer ? html` ` : nothing} + ${card.badges ? html`
${unsafeHTML(card.badges)}
` : nothing} `;