Skip to content

Commit

Permalink
feat(cxl-ui): add cxl-course-card component
Browse files Browse the repository at this point in the history
  • Loading branch information
freudFlintstone committed Jul 24, 2023
1 parent d30c005 commit 816408f
Show file tree
Hide file tree
Showing 14 changed files with 584 additions and 20 deletions.
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
"@commitlint/cli": "^12.0.1",
"@commitlint/config-conventional": "^12.0.1",
"@commitlint/config-lerna-scopes": "^12.1.4",
"@lit-labs/observers": "^2.0.0",
"@open-wc/testing-helpers": "^0.9.5",
"@size-limit/file": "^6.0.3",
"@webcomponents/webcomponentsjs": "^2.2.10",
Expand Down
3 changes: 3 additions & 0 deletions packages/cxl-lumo-styles/src/icons.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

253 changes: 253 additions & 0 deletions packages/cxl-ui/scss/cxl-course-card.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
/* 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";

:host {
position: relative;
box-sizing: border-box;
display: flex;
height: max-content;
max-width: calc(100% - 2 * var(--lumo-space-l));
width: 100%;
min-height: 270px;
padding: var(--lumo-space-m) var(--lumo-space-l);
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

// @see https://github.com/conversionxl/aybolit/pull/293
--video-background: hsla(355.8, 74.7%, 48%, 0.03); // --lumo-primary-color-3pct does not exist
--training-background: hsla(0, 0%, 10%, 0.03); // --lumo-shade-3pct does not exist
--playbook-background: hsla(213, 100%, 62%, 0.03); // No similar base color exists

// Container / Media queries
@media #{mq.$small} {
margin: unset;
max-width: unset;

.container > .attributes {
display: none;
}

header .info .attributes {
display: flex;
}
}

[empty] {
visibility: hidden;
user-select: none;
}
}

:host([hidden]) {
display: none;
}

:host(:first-child) {
margin-top: unset;
}

:host(:last-child) {
margin-bottom: unset;
}

:host([theme~="dark"]) {
background-color: var(--lumo-contrast);
}

:host([theme~="video"]) {
background-color: var(--video-background);
}

:host([theme~="training"]) {
background-color: var(--training-background);
}

:host([theme~="playbook"]) {
background-color: var(--playbook-background);
}

.container {
display: block;
gap: var(--lumo-space-s);
width: 100%;
transition: all 0.3s ease;

> .attributes {
padding-top: 0;
}

.content-wrapper, footer {
margin-top: var(--lumo-space-s);
}
}

.tags {
display: flex;
gap: var(--lumo-space-s);
min-height: 1.6em;
max-width: 100%;
}

.attributes {
display: flex;
align-items: flex-start;
align-self: stretch;
padding: var(--lumo-space-s) 0;
gap: var(--lumo-space-s);
color: var(--lumo-shade-60pct);
}

header {
display: flex;
align-items: start;
justify-content: space-between;
gap: var(--lumo-space-m);

.info {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: var(--lumo-space-xs);
align-self: stretch;
max-width: calc(100% - var(--lumo-space-m) - 80px);
overflow: hidden;

.name {
font-family: Roboto, sans-serif;
font-size: var(--lumo-font-size-xl);
font-style: normal;
font-weight: 700;
line-height: var(--lumo-line-height-xs);
color: #1A1A1A;
}

.attributes {
display: none;
}

.tags {
flex-wrap: nowrap;

::slotted(.tag), .tag:not(:first-child):not(.new) {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.tag {
&:first-child, &.new {
color: var(--lumo-primary-color)
}

&:first-child {
text-transform: capitalize;
}
}
}
}

.instructor-image {
width: 80px;
height: 92px;

img {
height: 80px;
overflow: hidden;
border-radius: 100px;
}
}
}

.content-wrapper {
min-height: calc(var(--lumo-line-height-s) * 6); // Six standard line-heights
line-height: var(--lumo-line-height-s);

.content {
min-height: calc(var(--lumo-line-height-s) * 3 + 1.25em); // Four standard line-heights plus global inherited <p> block margins
}

.tags {
margin-top: var(--lumo-space-s);
transition: height 0.3s ease-in-out;
flex-wrap: wrap;

::slotted(span) {
font-style: italic;
line-height: 1;
}
}
}

footer {
position: relative;

vaadin-details[theme="reverse"] {
&::part(summary) {
justify-content: flex-start;
gap: var(--lumo-space-s);
font-size: var(--lumo-font-size-s);
}

&::part(toggle) {
padding: calc(var(--lumo-space-xs) / 4);
margin-left: initial;
font-size: var(--lumo-font-size-m);
transform: rotate(90deg);
}

&[opened]::part(toggle) {
transform: rotate(-90deg);
}

&::part(summary-content) {
font-weight: 600;
color: var(--lumo-contrast);
}

&::part(content) {
padding-bottom: 0;
font-size: var(--lumo-font-size-s);
line-height: var(--lumo-line-height-s);
}
}

.cta {
position: absolute;
top: -6px;
right: 0;
font-weight: 600;

vaadin-icon {
width: var(--lumo-icon-size-s);
height: var(--lumo-icon-size-s);
padding: calc(var(--lumo-space-xs) / 2);
margin-left: var(--lumo-space-xs);
background: var(--lumo-primary-color-10pct);
border-radius: 100%;
}
}
}

.badge-new {
display: none;
}

:host([new]) {
.badge-new {
position: absolute;
top: calc(-1 * var(--lumo-space-s));
right: calc(-1 * var(--lumo-space-s));
display: block;
width: calc(2 * var(--lumo-space-m));
height: calc(2 * var(--lumo-space-m));
padding: 6px;
color: var(--lumo-primary-contrast-color);
background: var(--lumo-primary-color);
border-radius: 100%;
}
}
109 changes: 109 additions & 0 deletions packages/cxl-ui/src/components/cxl-course-card.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/* eslint-disable import/no-extraneous-dependencies */
import { LitElement, html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import '@vaadin/details';
import '@vaadin/button';
import cxlCourseCardStyles from '../styles/cxl-course-card-css.js';

@customElement('cxl-course-card')
export class CXLCourseCardElement extends LitElement {
static get styles() {
return [cxlCourseCardStyles];
}

separator = html`<span> | </span>`;

@state() _tagsHasChildren = false;

@state() _moreHasChildren = false;

@property({ type: String }) id = '';
@property({ type: String }) theme = 'course';
@property({ type: String }) name = '';
@property({ type: String }) time = '';
@property({ type: String }) instructor = '';
@property({ type: String }) avatar = '';
@property({ type: Boolean, reflect: true }) new = false;
@property({ type: String, attribute: 'cta-label' }) ctaLabel = 'View';
@property({ type: String, attribute: 'cta-url' }) ctaUrl = '';
_slotHasChildren (e) {
const slot = e.target
const { name } = slot
const children = slot.assignedNodes()
this[`_${name}HasChildren`] = !!children.length
}
render() {
return html`
<div class="container">
<header>
<div class="info">
<div class="tags">
${this.theme ? html`<span class="tag">${this.theme}</span>`: ''}
${this.theme && this._tagsHasChildren ? this.separator : ''}
<slot name="tags" @slotchange=${this._slotHasChildren}></slot>
${this.new ? html`${this.theme ? this.separator : ''}<span class="tag new">NEW</span>` : '' }
</div>
<div class="name">
${this.name}
</div>
<div class="attributes">
<div class="time">
${this.theme.toLowerCase() === 'course' ? html`<vaadin-icon icon="lumo:clock"></vaadin-icon>` : ''}
${this.time}
</div>
<div class="instructor">
By: ${this.instructor}
</div>
</div>
</div>
<div class="instructor-image">
<img
src=${this.avatar}
alt="${this.instructor}"
/>
</div>
</header>
<div class="attributes">
<div class="time">
${this.theme.toLowerCase() === 'course' ? html`<vaadin-icon icon="lumo:clock"></vaadin-icon>` : ''}
${this.time}
</div>
<div class="instructor">
By: ${this.instructor}
</div>
</div>
<section class="content-wrapper">
<div class="content">
<slot name="content"></slot>
</div>
<div class="tags" >
<slot name="content-tags"></slot>
</div>
</section>
<footer>
<vaadin-details theme="reverse" ?empty=${!this._moreHasChildren}>
<div slot="summary">Read more</div>
<slot name="more" @slotchange=${this._slotHasChildren}></slot>
</vaadin-details>
<a href=${this.ctaUrl}>
<vaadin-button class="cta" theme="tertiary" @click=${this.callToAction}>
${this.ctaLabel} ${this.theme}
<vaadin-icon icon="lumo:angle-right"></vaadin-icon>
</vaadin-button>
</a>
</footer>
<vaadin-icon class="badge-new" icon="cxl:new"></vaadin-icon>
</div>
`;
}
}
1 change: 1 addition & 0 deletions packages/cxl-ui/src/index-core.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import * as Headroom from 'headroom.js';
// CXL.
export { CXLAppLayoutElement } from './components/cxl-app-layout.js';
export { CXLCardElement } from './components/cxl-card.js';
export { CXLCourseCardElement } from './components/cxl-course-card.js';
export { CXLCredentialElement } from './components/cxl-credential.js'
export { CXLCheckoutDetailsElement } from './components/cxl-checkout-details.js';
export { CXLMarketingNavElement } from './components/cxl-marketing-nav.js';
Expand Down
1 change: 1 addition & 0 deletions packages/cxl-ui/src/index-storybook.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as Headroom from 'headroom.js';

export { CXLAppLayoutElement } from './components/cxl-app-layout.js';
export { CXLCardElement } from './components/cxl-card.js';
export { CXLCourseCardElement } from './components/cxl-course-card.js';
export { CXLMarketingNavElement } from './components/cxl-marketing-nav.js';
export { CXLSectionElement } from './components/cxl-section.js';
export { CXLStatsElement } from './components/cxl-stats.js';
Expand Down
Loading

0 comments on commit 816408f

Please sign in to comment.