From 51996f371a925865295bcecfae104e00a5b365ac Mon Sep 17 00:00:00 2001 From: Hener Hoop Date: Mon, 20 Nov 2023 19:15:42 +0200 Subject: [PATCH] feat(cxl-ui): add cxl-certificate-header component https://app.clickup.com/t/86ayn4zg9 --- packages/cxl-lumo-styles/scss/global.scss | 2 + .../scss/themes/cxl-tabs-slider.scss | 3 +- .../cxl-ui/scss/cxl-app-layout/_layout.scss | 13 ++ .../cxl-ui/scss/cxl-certificate-header.scss | 116 ++++++++++++++++++ .../cxl-ui/scss/cxl-featured-course-card.scss | 2 +- packages/cxl-ui/scss/cxl-section.scss | 28 ++++- .../scss/global/cxl-certificate-header.scss | 5 + packages/cxl-ui/scss/global/cxl-stats.scss | 16 +++ .../src/components/cxl-certificate-header.js | 94 ++++++++++++++ .../cxl-ui/src/components/cxl-credential.js | 10 +- packages/cxl-ui/src/index-core.js | 1 + .../certificate-header.stories.js | 16 +++ .../cxl-ui/cxl-certificate-header/template.js | 45 +++++++ .../cxl-ui/cxl-credential/default.stories.js | 40 ++---- .../cxl-ui/cxl-stats/default.stories.js | 19 ++- 15 files changed, 366 insertions(+), 44 deletions(-) create mode 100644 packages/cxl-ui/scss/cxl-certificate-header.scss create mode 100644 packages/cxl-ui/scss/global/cxl-certificate-header.scss create mode 100644 packages/cxl-ui/src/components/cxl-certificate-header.js create mode 100644 packages/storybook/cxl-ui/cxl-certificate-header/certificate-header.stories.js create mode 100644 packages/storybook/cxl-ui/cxl-certificate-header/template.js diff --git a/packages/cxl-lumo-styles/scss/global.scss b/packages/cxl-lumo-styles/scss/global.scss index 1f4affe40..de8f4913d 100644 --- a/packages/cxl-lumo-styles/scss/global.scss +++ b/packages/cxl-lumo-styles/scss/global.scss @@ -6,6 +6,8 @@ html { --cxl-content-max-width: 48em; --cxl-content-max-width-wide: calc(var(--cxl-content-max-width) * 2); --cxl-wrap-padding: var(--lumo-space-m); + --cxl-hero-content-max-width: 650px; + --cxl-hero-certificate-max-width: 480px; --cxl-space-sm: 12px; --cxl-color-light-gray: hsla(0, 0%, 96%, 1); --cxl-color-medium-gray: hsla(210, 20%, 96%, 1); diff --git a/packages/cxl-lumo-styles/scss/themes/cxl-tabs-slider.scss b/packages/cxl-lumo-styles/scss/themes/cxl-tabs-slider.scss index 1ea708a98..869a5289b 100644 --- a/packages/cxl-lumo-styles/scss/themes/cxl-tabs-slider.scss +++ b/packages/cxl-lumo-styles/scss/themes/cxl-tabs-slider.scss @@ -237,7 +237,8 @@ * Theme "cxl-course-slider" and "cxl-category-accordion" */ -:host([theme~="cxl-course-slider"][theme~="cxl-category-accordion"][theme~="minimal"]) { +:host([theme~="cxl-course-slider"][theme~="minimal"][theme~="cxl-category-accordion"]), +:host([theme~="cxl-course-slider"][theme~="minimal"][theme~="cxl-category-slider"]) { margin-left: calc(-1 * var(--lumo-space-m)); @media #{mq.$small} { diff --git a/packages/cxl-ui/scss/cxl-app-layout/_layout.scss b/packages/cxl-ui/scss/cxl-app-layout/_layout.scss index a2947ff76..40c5a3ab7 100644 --- a/packages/cxl-ui/scss/cxl-app-layout/_layout.scss +++ b/packages/cxl-ui/scss/cxl-app-layout/_layout.scss @@ -112,3 +112,16 @@ slot[name="action-bar"]::slotted(div) { } } } + +/** + * Certificate layout + */ +:host([layout^="1c"][theme~="cxl-certificate"]:not([layout="1c"])) { + overflow: hidden; + + main { + > slot { + padding: 0; + } + } +} diff --git a/packages/cxl-ui/scss/cxl-certificate-header.scss b/packages/cxl-ui/scss/cxl-certificate-header.scss new file mode 100644 index 000000000..8c56a5254 --- /dev/null +++ b/packages/cxl-ui/scss/cxl-certificate-header.scss @@ -0,0 +1,116 @@ +@use "~@conversionxl/cxl-lumo-styles/scss/mq"; +@use "./mixins"; + +:host { + color: var(--lumo-tint); + background-color: var(--lumo-shade); + overflow: hidden; + @include mixins.pesudo-element-full-width(var(--lumo-shade)); + + @media #{mq.$medium} { + overflow: visible; + } + + .container { + position: relative; + box-sizing: border-box; + width: 100%; + max-width: var(--cxl-content-max-width-wide); + padding: var(--lumo-space-l) 0; + + @media #{mq.$small} { + display: flex; + justify-content: space-between; + gap: var(--lumo-space-m); + } + + @media #{mq.$large} { + padding: var(--lumo-space-xl) 0; + } + } + + .content { + max-width: var(--cxl-hero-content-max-width); + } + + .title { + margin: 0; + font-family: var(--cxl-lumo-font-secondary); + } + + .completed { + display: flex; + margin-top: var(--lumo-space-l); + font-size: var(--lumo-font-size-xl); + font-weight: 700; + color: var(--lumo-primary-color); + } + + .completed-icon { + display: inline-flex; + width: 32px; + height: 32px; + align-items: center; + justify-content: center; + margin-right: var(--lumo-space-m); + color: var(--lumo-primary-color); + background-color: var(--lumo-primary-color-10pct); + border-radius: 100%; + } + + .description { + margin-top: var(--lumo-space-l); + font-family: var(--cxl-lumo-font-secondary); + line-height: var(--lumo-line-height-m); + } + + .stats { + margin-top: var(--lumo-space-l); + + @media #{mq.$medium} { + margin-top: calc(4 * var(--lumo-space-m)); + } + } + + .actions { + display: flex; + flex-direction: column; + gap: var(--lumo-space-s); + margin-top: var(--lumo-space-m); + + @media #{mq.$medium} { + flex-direction: row; + gap: var(--lumo-space-m); + margin-top: var(--lumo-space-xl); + } + } + + .credential { + width: 100%; + max-width: 100%; + margin-top: var(--lumo-space-l); + padding-top: var(--lumo-space-l); + border-top: solid 1px var(--cxl-color-dark-gray); + + @media #{mq.$medium} { + max-width: var(--cxl-hero-certificate-max-width); + margin-top: 0; + padding-top: 0; + border-top: none; + } + } +} + +:host([theme~="completed"]) { + color: var(--lumo-shade); + background-color: var(--lumo-tint); + + @media #{mq.$medium} { + @include mixins.pesudo-element-full-width(var(--lumo-tint)); + } + + .credential { + padding-top: 0; + border-top: none; + } +} diff --git a/packages/cxl-ui/scss/cxl-featured-course-card.scss b/packages/cxl-ui/scss/cxl-featured-course-card.scss index 6897e8ec1..3faf94152 100644 --- a/packages/cxl-ui/scss/cxl-featured-course-card.scss +++ b/packages/cxl-ui/scss/cxl-featured-course-card.scss @@ -111,7 +111,7 @@ position: relative; z-index: 0; display: block; - max-width: 650px; + max-width: var(--cxl-hero-content-max-width); padding: 0 var(--lumo-space-m); @media #{mq.$large} { diff --git a/packages/cxl-ui/scss/cxl-section.scss b/packages/cxl-ui/scss/cxl-section.scss index e2f615e47..57fd6c5b5 100644 --- a/packages/cxl-ui/scss/cxl-section.scss +++ b/packages/cxl-ui/scss/cxl-section.scss @@ -1,7 +1,9 @@ -@use "~@conversionxl/cxl-lumo-styles/scss/mixins"; @use "~@conversionxl/cxl-lumo-styles/scss/mq"; +@use "~@conversionxl/cxl-lumo-styles/scss/mixins"; +@use "./mixins" as cxl-mixins; :host { + position: relative; display: block; --figure-height-multiplier: 0.10; --figure-height-factor: calc(var(--figure-height-multiplier) * var(--cxl-content-max-width-wide)); @@ -13,6 +15,12 @@ } } +:host([theme~="cxl-section-certificate-unfinished"]) { + @media #{mq.$medium} { + @include cxl-mixins.pesudo-element-full-width(var(--cxl-color-medium-gray)); + } +} + /** * Avoid margin collapse with background. */ @@ -31,19 +39,33 @@ /** * Spacious section spacing. */ - margin-top: var(--cxl-section-wrap-margin-top, var(--lumo-space-xl)); - margin-bottom: var(--cxl-section-wrap-margin-bottom, var(--lumo-space-xl)); + margin-top: var(--cxl-section-wrap-margin-top, var(--lumo-space-l)); + margin-bottom: var(--cxl-section-wrap-margin-bottom, var(--lumo-space-l)); /** * Block editor integration. */ + :host([theme~="full"]) > &, :host(.alignfull) > & { max-width: unset; } + :host([theme~="wide"]) > &, :host(.alignwide) > & { max-width: var(--cxl-content-max-width-wide); } + + :host([theme~="cxl-certificate-header"]) > & { + margin-top: 0; + margin-bottom: 0; + } + + :host([theme~="cxl-section-certificate-unfinished"]) > & { + margin-top: 0; + margin-bottom: 0; + padding-top: var(--lumo-space-m); + padding-bottom: var(--lumo-space-xl); + } } :host(.has-gray-background-color) { diff --git a/packages/cxl-ui/scss/global/cxl-certificate-header.scss b/packages/cxl-ui/scss/global/cxl-certificate-header.scss new file mode 100644 index 000000000..7db7b1a3b --- /dev/null +++ b/packages/cxl-ui/scss/global/cxl-certificate-header.scss @@ -0,0 +1,5 @@ +cxl-certificate-header { + position: relative; + display: block; + z-index: 1; +} diff --git a/packages/cxl-ui/scss/global/cxl-stats.scss b/packages/cxl-ui/scss/global/cxl-stats.scss index 7e38845df..224cab4cd 100644 --- a/packages/cxl-ui/scss/global/cxl-stats.scss +++ b/packages/cxl-ui/scss/global/cxl-stats.scss @@ -25,6 +25,10 @@ cxl-stats { } } + .stat-item.featured .stat-count { + color: var(--lumo-primary-color); + } + a.stat-count { color: var(--cxl-color-black); text-decoration: underline; @@ -33,4 +37,16 @@ cxl-stats { color: var(--lumo-primary-color); } } + + &[theme~="dark"] { + color: var(--lumo-tint); + + .stat-title { + color: var(--lumo-tint); + } + + a.stat-count { + color: var(--lumo-tint); + } + } } diff --git a/packages/cxl-ui/src/components/cxl-certificate-header.js b/packages/cxl-ui/src/components/cxl-certificate-header.js new file mode 100644 index 000000000..c43fe04fa --- /dev/null +++ b/packages/cxl-ui/src/components/cxl-certificate-header.js @@ -0,0 +1,94 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import { LitElement, html, nothing } from 'lit'; +import { unsafeHTML } from 'lit/directives/unsafe-html.js'; +import { customElement, property } from 'lit/decorators.js'; +import '@conversionxl/cxl-lumo-styles'; +import '@conversionxl/cxl-ui/src/components/cxl-section.js'; +import { registerGlobalStyles } from '@conversionxl/cxl-lumo-styles/src/utils'; + +import cxlCertificateHeaderStyles from '../styles/cxl-certificate-header-css.js'; +import cxlCertificateHeaderGlobalStyles from '../styles/global/cxl-certificate-header-css.js'; + +@customElement('cxl-certificate-header') +export class CXLCertificateHeaderElement extends LitElement { + static get styles() { + return [cxlCertificateHeaderStyles]; + } + + @property({ type: String }) theme = ''; + + @property({ type: String }) title = ''; + + @property({ type: String }) description = ''; + + @property({ type: Boolean, attribute: 'hide-actions' }) hideActions = false; + + @property({ type: Boolean, attribute: 'hide-stats' }) hideStats = false; + + @property({ type: Boolean, attribute: 'hide-credential' }) hideCredential = false; + + // eslint-disable-next-line class-methods-use-this + _renderCompletedEmblem() { + if (!this.theme.includes('completed')) { + return nothing; + } + + return html`
+ + Course completed +
`; + } + + // eslint-disable-next-line class-methods-use-this + _renderActions() { + if (this.hideActions) { + return nothing; + } + + return html`
`; + } + + // eslint-disable-next-line class-methods-use-this + _renderStats() { + if (this.hideStats) { + return nothing; + } + + return html`
`; + } + + // eslint-disable-next-line class-methods-use-this + _renderCredential() { + if (this.hideCredential) { + return nothing; + } + + return html`
`; + } + + render() { + return html` + +
+
+

${this.title}

+ ${this._renderCompletedEmblem()} +
${unsafeHTML(this.description)}
+ ${this._renderStats()} + ${this._renderActions()} +
+ ${this._renderCredential()} +
+
+ `; + } + + firstUpdated(_changedProperties) { + super.firstUpdated(_changedProperties); + + // Global styles. + registerGlobalStyles(cxlCertificateHeaderGlobalStyles, { + moduleId: 'cxl-certificate-header-global', + }); + } +} diff --git a/packages/cxl-ui/src/components/cxl-credential.js b/packages/cxl-ui/src/components/cxl-credential.js index f6bf0059e..6a6727573 100644 --- a/packages/cxl-ui/src/components/cxl-credential.js +++ b/packages/cxl-ui/src/components/cxl-credential.js @@ -21,6 +21,8 @@ export class CXLCredentialElement extends LitElement { @property({ type: String, attribute: 'full-name' }) fullName = ''; + @property({ type: String }) theme = ''; + @property({ type: String }) title = ''; /** @@ -45,7 +47,7 @@ export class CXLCredentialElement extends LitElement { @query('#qr-code') qrCodeCanvas = null; defaultUrl ='https://cxl.com/institute/credential' - + get url() { return `${this.credentialUrl || this.defaultUrl}/${this.credentialId}`; } @@ -134,12 +136,16 @@ export class CXLCredentialElement extends LitElement { } if (changes.has('type')) { - this.setAttribute('theme', this.type === 'minidegree' ? 'dark' : 'light'); + this.theme += ` ${this.type === 'minidegree' ? 'dark' : 'light'}`; } if (changes.has('scale') || changes.has('correctionFactor') || changes.has('codeScale')) { requestAnimationFrame(() => this._handleScaling()); } + + if (this.theme) { + this.setAttribute('theme', this.theme); + } } renderCredential() { diff --git a/packages/cxl-ui/src/index-core.js b/packages/cxl-ui/src/index-core.js index c62791bde..110555d1c 100644 --- a/packages/cxl-ui/src/index-core.js +++ b/packages/cxl-ui/src/index-core.js @@ -22,6 +22,7 @@ export { CXLCourseCardElement } from './components/cxl-course-card.js'; export { CXLCourseDialogElement } from './components/cxl-course-dialog.js'; export { CXLCheckoutDetailsElement } from './components/cxl-checkout-details.js'; export { CXLDashboardHeaderElement } from './components/cxl-dashboard-header.js'; +export { CXLCertificateHeaderElement } from './components/cxl-certificate-header.js'; export { CxlDashboardSectionElement } from './components/cxl-dashboard-section.js'; export { CXLDashboardNotificationElement } from './components/cxl-dashboard-notification.js'; export { CxlDashboardTeamHeaderElement } from './components/cxl-dashboard-team-header.js'; diff --git a/packages/storybook/cxl-ui/cxl-certificate-header/certificate-header.stories.js b/packages/storybook/cxl-ui/cxl-certificate-header/certificate-header.stories.js new file mode 100644 index 000000000..5cb8d778c --- /dev/null +++ b/packages/storybook/cxl-ui/cxl-certificate-header/certificate-header.stories.js @@ -0,0 +1,16 @@ +import { CertificateHeaderTemplate, ArgTypes } from './template'; + +export default { + title: 'CXL UI/cxl-certificate-header', +}; + +export const CXLCertificateHeader = CertificateHeaderTemplate.bind({}); + +CXLCertificateHeader.argTypes = { + ...ArgTypes, +}; + +CXLCertificateHeader.args = { + title: 'Growth', + description: '

Designed to help marketing professionals create and refine a strategic brand identity, this advanced certification program will give you deep marketing knowledge across key brand disciplines.

At the end of the Brand Marketing Category, you’ll have the processes, tools, and transferrable techniques to level up your brand — no matter your industry or role.

', +}; diff --git a/packages/storybook/cxl-ui/cxl-certificate-header/template.js b/packages/storybook/cxl-ui/cxl-certificate-header/template.js new file mode 100644 index 000000000..c1b2b7a65 --- /dev/null +++ b/packages/storybook/cxl-ui/cxl-certificate-header/template.js @@ -0,0 +1,45 @@ +import { html } from 'lit'; +import '@conversionxl/cxl-lumo-styles'; +import '@vaadin/button'; +import '@conversionxl/cxl-ui/src/components/cxl-certificate-header.js'; + +import { CXLStats } from '../cxl-stats/default.stories'; + +export const CertificateHeaderTemplate = (header) => html` + +
+ ${CXLStats({ + theme: `cxl-certificate-header ${!header.completed ? 'dark' : ''}`, + statsCount: 4 + })} +
+ + Research exam + + + + Marketing Strategy exam + + +
+ Minidegree certificate +
Buttons will come from WPS
+
+
+`; + +export const ArgTypes = { + completed: { control: 'boolean' }, + title: { control: 'text' }, + description: { control: 'text' }, + hideActions: { control: 'boolean' }, + hideStats: { control: 'boolean' }, + hideCredential: { control: 'boolean' }, +}; diff --git a/packages/storybook/cxl-ui/cxl-credential/default.stories.js b/packages/storybook/cxl-ui/cxl-credential/default.stories.js index b360036b8..4083014ba 100644 --- a/packages/storybook/cxl-ui/cxl-credential/default.stories.js +++ b/packages/storybook/cxl-ui/cxl-credential/default.stories.js @@ -5,32 +5,20 @@ export default { title: 'CXL UI/cxl-credential', }; -const Template = ({ credentialId, fullName, title, type, date, credentialUrl }) => html` -
- -
+const Template = (args) => html` + `; export const CertificateShort = Template.bind({}); -export const CertificateLongTitle = Template.bind({}); -export const CertificateLongName = Template.bind({}); - -CertificateShort.argTypes = { - credentialId: { control: 'number' }, - fullName: { control: 'text' }, - title: { control: 'text' }, - type: { control: 'select', options: ['course', 'minidegree'] }, - date: { control: 'date' }, - credentialUrl: { control: 'text' }, -} - CertificateShort.args = { credentialId: 7858542, fullName: 'First Last', @@ -39,17 +27,15 @@ CertificateShort.args = { date: '2023-06-16', // YYYY-MM-DD }; +export const CertificateLongTitle = Template.bind({}); CertificateLongTitle.args = { ...CertificateShort.args, title: 'Psychology and behaviour for digital marketing', }; -CertificateLongTitle.argTypes = { ...CertificateShort.argTypes } - +export const CertificateLongName = Template.bind({}); CertificateLongName.args = { ...CertificateShort.args, fullName: 'Francisco Guillermo Ochoa Magaña', title: 'Psychology and behaviour for digital marketing longer title', }; - -CertificateLongName.argTypes = { ...CertificateShort.argTypes } diff --git a/packages/storybook/cxl-ui/cxl-stats/default.stories.js b/packages/storybook/cxl-ui/cxl-stats/default.stories.js index f3a4f742f..f67d432cd 100644 --- a/packages/storybook/cxl-ui/cxl-stats/default.stories.js +++ b/packages/storybook/cxl-ui/cxl-stats/default.stories.js @@ -13,6 +13,7 @@ export const CXLStats = ({ theme, statsCount }) => { const newItem = { title: 'Complete
library', count: '12%', + featured: true, }; statsData.push(newItem); @@ -20,16 +21,14 @@ export const CXLStats = ({ theme, statsCount }) => { return html` - ${statsData.slice(0, statsCount).map( - (el) => html` -
-

${unsafeHTML(el.title)}

- ${el.link - ? html`${el.count}` - : html`

${el.count}

`} -
- ` - )} + ${statsData.slice(0, statsCount).map((el) => html` +
+

${unsafeHTML(el.title)}

+ ${el.link + ? html`${el.count}` + : html`

${el.count}

`} +
+ `)}
`; };