Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion components/card/card-content-meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class CardContentMeta extends LitElement {
return css`
:host {
box-sizing: border-box;
color: var(--d2l-color-tungsten);
color: var(--d2l-theme-text-color-static-subtle);
display: inline-block;
font-size: 0.7rem;
font-weight: 400;
Expand Down
41 changes: 18 additions & 23 deletions components/card/card-loading-shimmer.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import '../colors/colors.js';
import { css, html, LitElement } from 'lit';
import { SkeletonMixin, skeletonStyles } from '../skeleton/skeleton-mixin.js';

/**
* A card layout component for when the card header is loading.
* @slot - Slot for header content being loaded
*/
class CardLoadingShimmer extends LitElement {
class CardLoadingShimmer extends SkeletonMixin(LitElement) {

static get properties() {
return {
Expand All @@ -18,37 +19,25 @@ class CardLoadingShimmer extends LitElement {
}

static get styles() {
return css`
return [skeletonStyles, css`
:host([hidden]) {
display: none;
}

.d2l-card-loading-indicator {
background-color: var(--d2l-color-regolith);
.d2l-skeletize {
border-radius: 7px 7px 0 0;
box-shadow: inset 0 -1px 0 0 var(--d2l-color-gypsum);
height: inherit;
overflow: hidden;
position: relative;
}

.d2l-card-loading-indicator::after {
animation: loadingShimmer 1.5s ease-in-out infinite;
background: linear-gradient(90deg, rgba(249, 250, 251, 0.1), rgba(114, 119, 122, 0.1), rgba(249, 250, 251, 0.1));
background-color: var(--d2l-color-regolith);
content: "";
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%;
:host([skeleton]) .d2l-skeletize::before {
border-radius: 0;
box-shadow: inset 0 -1px 0 0 var(--d2l-color-gypsum);
}

@keyframes loadingShimmer {
0% { transform: translate3d(-100%, 0, 0); }
100% { transform: translate3d(100%, 0, 0); }
:host([skeleton]) slot {
display: none;
}
`;
`];
}

constructor() {
Expand All @@ -58,11 +47,17 @@ class CardLoadingShimmer extends LitElement {

render() {
return html`
<div ?hidden="${!this.loading}" class="d2l-card-loading-indicator"></div>
<div ?hidden="${this.loading}"><slot></slot></div>
<div class="d2l-skeletize"><slot></slot></div>
`;
}

willUpdate(changedProperties) {
super.willUpdate(changedProperties);
if (changedProperties.has('loading')) {
this.skeleton = this.loading;
}
}

}

customElements.define('d2l-card-loading-shimmer', CardLoadingShimmer);
16 changes: 7 additions & 9 deletions components/card/card.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ class Card extends LitElement {
static get styles() {
return [offscreenStyles, css`
:host {
background-color: #ffffff;
border: 1px solid var(--d2l-color-gypsum);
background-color: var(--d2l-theme-background-color-base);
border: 1px solid var(--d2l-theme-border-color-subtle);
border-radius: 6px;
box-sizing: border-box;
display: inline-block;
Expand Down Expand Up @@ -191,20 +191,18 @@ class Card extends LitElement {
border: none;
}
:host([subtle][href]) {
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.03);
}
:host([href]:not([_active]):hover) {
box-shadow: 0 2px 14px 1px rgba(0, 0, 0, 0.06);
box-shadow: var(--d2l-theme-shadow-attached);
Copy link
Copy Markdown
Contributor

@dbatiste dbatiste May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that "attached" is probably what we want here, at least based on how the shadows are currently defined.

We have subtle card shadow...
from: 0 4px 8px 0 rgba(0, 0, 0, 0.03)
to: 0 2px 4px 0 rgba(0, 0, 0, 0.03) (attached)

hover from: 0 4px 18px 2px rgba(0, 0, 0, 0.06)
to: 0 2px 12px 0 rgba(0, 0, 0, 0.15) (floating)

We have normal card shadow...
hover from: 0 2px 14px 1px rgba(0, 0, 0, 0.06)
to: 0 2px 12px 0 rgba(0, 0, 0, 0.15) (floating)

Do I have that right?

Those seem pretty similar, but the floating semantic doesn't seem right to me, and I'm not sure this is being considered in the shadow design revisions.

}
:host([href]:not([_active]):hover),
:host([subtle][href]:not([_active]):hover) {
box-shadow: 0 4px 18px 2px rgba(0, 0, 0, 0.06);
box-shadow: var(--d2l-theme-shadow-floating);
}
${getFocusRingStyles(() => ':host([_active])', { 'extraStyles': css`border-color: transparent;` })}
/* .d2l-card-link-container-hover is used to only color/underline when
hovering the anchor; these styles are not applied when hovering actions */
:host([href]) .d2l-card-link-container-hover,
:host([href][_active]) .d2l-card-content {
color: var(--d2l-color-celestine);
color: var(--d2l-theme-text-color-interactive-default);
text-decoration: underline;
}
/* this is needed to ensure tooltip is not be clipped by adjacent cards */
Expand All @@ -222,7 +220,7 @@ class Card extends LitElement {
}
@media (prefers-contrast: more) {
:host([subtle]) {
border: 1px solid var(--d2l-color-gypsum);
border: 1px solid var(--d2l-theme-border-color-subtle);
}
}
`];
Expand Down
2 changes: 1 addition & 1 deletion components/card/demo/card.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
gap: 0.6rem;
}
.cards-subtle {
background-color: #f6f7f8;
background-color: var(--d2l-theme-background-color-face);
padding: 20px;
}
d2l-card {
Expand Down
2 changes: 1 addition & 1 deletion components/card/test/card-content.vdiff.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describe('card-content-meta', () => {
</ul>
</d2l-card-content-meta>
`);
await expect(elem).to.be.golden();
await expect(elem).to.be.golden({ allColorModes: true });
});
});

Expand Down
25 changes: 13 additions & 12 deletions components/card/test/card.vdiff.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import '../../tooltip/tooltip.js';
import '../card.js';
import '../card-loading-shimmer.js';
import { clickElem, expect, fixture, focusElem, hoverElem, html, oneEvent } from '@brightspace-ui/testing';

import { _registerCustomSemanticVariableValue } from '../../../helpers/internal/css.js';
_registerCustomSemanticVariableValue('--d2l-subtle-cards-header-background', 'orange', 'darkblue');
function createCardTemplate(opts) {
const { alignCenter, content, subtle } = { alignCenter: false, subtle: false, ...opts };
return html`
Expand All @@ -24,7 +25,7 @@ function createLinkCardTemplate(opts) {
}
function createHeader(text) {
return html`
<div slot="header" style="background-color: orange; height: 95px;">${text}</div>
<div slot="header" style="background-color: var(--d2l-subtle-cards-header-background); height: 95px;">${text}</div>
`;
}
function createMultiLinkCardTemplate(opts) {
Expand Down Expand Up @@ -99,15 +100,15 @@ const badgeSlotContent = html`
</div>
`;

const subtleLinkCardTemplate = html`<div style="background-color: #f6f7f8; padding: 20px; width: 300px">
const subtleLinkCardTemplate = html`<div style="background-color: var(--d2l-theme-background-color-face); padding: 20px; width: 300px">
${createLinkCardTemplate({ content: simpleContent, subtle: true })}
</div>`;

describe('card', () => {
[
{ name: 'header-content', template: createCardTemplate({ content: simpleContent }) },
{ name: 'hover', template: createLinkCardTemplate({ content: simpleContent }), action: elem => hoverElem(elem) },
{ name: 'focus', template: createLinkCardTemplate({ content: simpleContent }), action: elem => focusElem(elem) },
{ name: 'hover', template: createLinkCardTemplate({ content: simpleContent }), action: elem => hoverElem(elem), allColorModes: true },
{ name: 'focus', template: createLinkCardTemplate({ content: simpleContent }), action: elem => focusElem(elem), allColorModes: true },
{ name: 'footer', template: createCardTemplate({ content: simpleContentWithFooter }) },
{ name: 'align-center', template: createCardTemplate({ content: simpleContentWithFooter, alignCenter: true }) },
{ name: 'badge', template: createCardTemplate({ content: html`${simpleContent}${badgeSlotContent}` }) },
Expand All @@ -133,18 +134,18 @@ describe('card', () => {
await oneEvent(elem, 'd2l-tooltip-show');
} },
{ name: 'loading', template: createCardTemplate({ content: html`<d2l-card-loading-shimmer slot="header" loading style="display: block; height: 103.5px; width: 100%;"></d2l-card-loading-shimmer>` }) },
{ name: 'subtle', template: html`<div style="background-color: #f6f7f8; padding: 20px; width: 300px">
{ name: 'subtle', template: html`<div style="background-color: var(--d2l-theme-background-color-face); padding: 20px; width: 300px">
${createCardTemplate({ content: simpleContent, subtle: true })}
</div>`, cardOnly: true },
{ name: 'subtle-link', template: subtleLinkCardTemplate, cardOnly: true },
{ name: 'subtle-link-hover', template: subtleLinkCardTemplate, action: elem => hoverElem(elem), cardOnly: true },
{ name: 'subtle-link-focus', template: subtleLinkCardTemplate, action: elem => focusElem(elem), cardOnly: true },
].forEach(({ name, template, action, rtl, cardOnly }) => {
</div>`, cardOnly: true, allColorModes: true },
{ name: 'subtle-link', template: subtleLinkCardTemplate, cardOnly: true, allColorModes: true },
{ name: 'subtle-link-hover', template: subtleLinkCardTemplate, action: elem => hoverElem(elem), cardOnly: true, allColorModes: true },
{ name: 'subtle-link-focus', template: subtleLinkCardTemplate, action: elem => focusElem(elem), cardOnly: true, allColorModes: true },
].forEach(({ name, template, action, rtl, cardOnly, allColorModes }) => {
it(name, async() => {
let elem = await fixture(template, { rtl });
if (cardOnly) elem = elem.querySelector('d2l-card');
if (action) await action(elem);
await expect(elem).to.be.golden();
await expect(elem).to.be.golden({ allColorModes });
});
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified components/card/test/golden/card/chromium/hover.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified components/card/test/golden/card/chromium/loading.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified components/card/test/golden/card/chromium/subtle-link-focus.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified components/card/test/golden/card/chromium/subtle-link-hover.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified components/card/test/golden/card/chromium/subtle-link.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified components/card/test/golden/card/chromium/subtle.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions components/colors/colors.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ const lightVariables = new Map([
['--d2l-theme-backdrop-background-color', '--d2l-color-regolith'],
['--d2l-theme-backdrop-opacity', '0.7'],
['--d2l-theme-backdrop-dialog-color', '#ffffff'],
['--d2l-theme-background-color-face', 'var(--d2l-color-gypsum)'],
['--d2l-theme-background-color-interactive-faint-disabled', '#f9fbff80'], /* --d2l-theme-background-color-interactive-faint-default at 50% opacity, remove once color-mix is widely supported */
['--d2l-theme-badge-background-color', '--d2l-color-gypsum'],
['--d2l-theme-badge-text-color', '--d2l-theme-text-color-static-standard'],
Expand Down Expand Up @@ -194,6 +195,7 @@ const darkVariables = new Map([
['--d2l-theme-backdrop-background-color', '--d2l-color-ferrite'],
['--d2l-theme-backdrop-opacity', '0.7'],
['--d2l-theme-backdrop-dialog-color', '#ffffff'],
['--d2l-theme-background-color-face', '#303335'],
['--d2l-theme-background-color-interactive-faint-disabled', '#20212280'], /* --d2l-theme-background-color-interactive-faint-default at 50% opacity, remove once color-mix is widely supported */
['--d2l-theme-badge-background-color', '#303335'],
['--d2l-theme-badge-text-color', '--d2l-theme-text-color-static-standard'],
Expand Down
19 changes: 19 additions & 0 deletions helpers/internal/css.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,22 @@ export function _isValidCssSelector(selector) {
const allValid = parts.every(part => partIsValid(part));
return allValid;
}

export function _registerCustomSemanticVariableValue(name, lightValue, darkValue) {
const style = globalThis.document?.head.querySelector('#d2l-colors');
if (!style) throw new Error('Colors stylesheet not found, make sure to properly import colors.js');
if (!name || !_isValidCssValue(lightValue) || !_isValidCssValue(darkValue)) {
throw new TypeError('_registerCustomSemanticVariableValue requires a name, lightValue, and darkValue');
}
const lightRule = style.sheet.cssRules[0];
const darkRule = style.sheet.cssRules[1];
const osRule = style.sheet.cssRules[2].cssRules[0];

lightRule.style.setProperty(name, lightValue);
darkRule.style.setProperty(name, darkValue);
osRule.style.setProperty(name, darkValue);
}

function _isValidCssValue(value) {
return (typeof value === 'string') || typeof (value?.cssText) === 'string';
}