Skip to content

Commit

Permalink
refactor(skeleton): rewrite the skeleton to follow best practices and…
Browse files Browse the repository at this point in the history
… improve dx (#295)
  • Loading branch information
mimshins authored Dec 9, 2024
1 parent df3f6a4 commit 82e144d
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 89 deletions.
3 changes: 3 additions & 0 deletions packages/web-components/src/skeleton/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const Slots = {
DEFAULT: "",
} as const;
26 changes: 12 additions & 14 deletions packages/web-components/src/skeleton/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,22 @@ import { customElement } from "lit/decorators.js";
import { Skeleton } from "./skeleton";
import styles from "./skeleton.style";

export { Slots } from "./constants";

/**
* @summary Display Skeleton component with different styles and types.
* @summary The skeleton component.
*
* @prop {"line" | "rect" | "circle"} [variant="line"] - The variant of the skeleton.
* @prop {"progress" | "pulse" | "none"} [animation-mode="progress"] - The animation mode of the skeleton.
* @prop {string} [width="100%"] - The width value of the skeleton.
* @prop {string} [height="20px"] - The height value of the skeleton.
* @tag tap-skeleton
*
* @csspart [skeleton] - The skeleton element
* @prop {"rectangular" | "circular" | "pill" | "text"} [variant="rectangular"] -
* The type of content that will be rendered.
* @prop {string} [width=""] - Width of the skeleton.
* @prop {string} [height=""] - Height of the skeleton.
* @prop {number} [ratio=NaN] -
* The ratio of the width to the height.
* If the value is invalid, it will default to 1.
*
* @cssprop [--tap-skeleton-background=--tap-sys-color-surface-tertiary] - Background color of the skeleton
* @cssprop [--tap-skeleton-radius=--tap-sys-radius-2] - Border radius of the skeleton
* @cssprop [--tap-skeleton-width=100%] - Width of the skeleton
* @cssprop [--tap-skeleton-height=--tap-sys-spacing-8] - Height of the skeleton
* @cssprop [--tap-skeleton-rect-radius=--tap-sys-spacing-0] - Border radius of the skeleton variant rect
* @cssprop [--tap-skeleton-circle-radius=--tap-sys-radius-full] - Border radius of the skeleton variant circle
* @cssprop [--tap-skeleton-circle-width=--tap-sys-spacing-10] - Width of the skeleton variant circle
* @cssprop [--tap-skeleton-circle-height=--tap-sys-spacing-10] - Height of the skeleton variant circle
* Only works when `variant="rectangular"`.
*/

@customElement("tap-skeleton")
Expand Down
97 changes: 42 additions & 55 deletions packages/web-components/src/skeleton/skeleton.style.ts
Original file line number Diff line number Diff line change
@@ -1,70 +1,60 @@
import { css } from "lit";

export default css`
:host {
const styles = css`
*,
*::before,
*::after {
box-sizing: border-box;
}
:host *,
:host *::before,
:host *::after {
box-sizing: inherit;
:host {
display: inline-block;
vertical-align: middle;
}
[hidden] {
display: none;
.root {
display: block;
background-color: var(--tap-sys-color-surface-tertiary);
height: 1.2em;
animation-name: pulse;
animation-duration: 1.5s;
animation-timing-function: ease-in-out;
animation-delay: 0.5s;
animation-iteration-count: infinite;
}
.skeleton {
position: relative;
overflow: hidden;
outline: none;
background: var(
--tap-skeleton-background,
var(--tap-sys-color-surface-tertiary)
)
no-repeat;
border-radius: var(--tap-skeleton-radius, var(--tap-sys-radius-2));
width: var(--tap-skeleton-width, 100%);
height: var(--tap-skeleton-height, var(--tap-sys-spacing-8));
display: inline-block;
will-change: transform;
.root.text {
margin: 0;
height: auto;
transform-origin: 0 55%;
transform: scale(1, 0.6);
border-radius: var(--tap-sys-radius-2);
}
:host([variant="rect"]) .skeleton {
border-radius: var(--tap-skeleton-rect-radius, var(--tap-sys-spacing-0));
.root.text::before {
content: " ";
}
:host([variant="circle"]) .skeleton {
width: var(--tap-skeleton-circle-width, var(--tap-sys-spacing-10));
height: var(--tap-skeleton-circle-height, var(--tap-sys-spacing-10));
border-radius: var(
--tap-skeleton-circle-radius,
var(--tap-sys-radius-full)
);
.root.circular {
border-radius: 50%;
}
:host([animation-mode="progress"]) .skeleton {
animation: progress 2s cubic-bezier(0.4, 0, 0.2, 1) infinite;
background-size: 200px 100%;
background-image: linear-gradient(
90deg,
color-mix(in srgb, var(--tap-sys-color-surface-white), transparent 100%),
color-mix(in srgb, var(--tap-sys-color-surface-white), transparent 40%),
color-mix(in srgb, var(--tap-sys-color-surface-white), transparent 100%)
);
.root.pill {
border-radius: var(--tap-sys-radius-full);
}
:host([animation-mode="pulse"]) .skeleton {
animation: pulse 1.5s cubic-bezier(0.4, 0, 0.2, 1) 0.5s infinite;
.root.rectangular {
border-radius: var(--tap-sys-radius-3);
}
@keyframes progress {
from {
background-position: -200px 0%;
}
to {
background-position: calc(200px + 100%) 0;
}
.root ::slotted(*) {
visibility: hidden;
}
@keyframes pulse {
Expand All @@ -83,13 +73,10 @@ export default css`
// Removing animation if user enabled the 'Reduce Motion' option.
@media (prefers-reduced-motion: reduce) {
:host([animation-mode="progress"]) .skeleton,
:host([animation-mode="pulse"]) .skeleton {
.root {
animation: none;
}
:host([animation-mode="progress"]) .skeleton {
background: none;
}
}
`;

export default styles;
85 changes: 67 additions & 18 deletions packages/web-components/src/skeleton/skeleton.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,77 @@
import { html, LitElement, nothing } from "lit";
import { html, LitElement } from "lit";
import { property } from "lit/decorators.js";
import { type SkeletonAnimation, type SkeletonVariant } from "./types";
import { classMap } from "lit/directives/class-map.js";
import { styleMap, type StyleInfo } from "lit/directives/style-map.js";
import { logger } from "../utils";

export class Skeleton extends LitElement {
@property({ reflect: true })
public variant?: SkeletonVariant = "line";
/**
* The type of content that will be rendered.
*/
@property()
public variant: "rectangular" | "circular" | "pill" | "text" = "rectangular";

@property({ reflect: true, attribute: "animation-mode" })
public animationMode?: SkeletonAnimation = "progress";
/**
* Width of the skeleton.
*/
@property()
public width = "";

@property({ reflect: true })
public width?: string = "100%";
/**
* Height of the skeleton.
*/
@property()
public height = "";

@property({ reflect: true })
public height?: string = "20px";
/**
* The ratio of the width to the height.
* If the value is invalid, it will default to 1.
*
* Only works when `variant="rectangular"`.
*/
@property({ type: Number })
public ratio = NaN;

protected override render() {
return html`<div
part="skeleton"
class="skeleton"
aria-label="Loading"
aria-labeledby=${nothing}
aria-describedby=${nothing}
style="height: ${this.height}; width: ${this.width};"
></div>`;
const styleInfo: StyleInfo = {};

if (this.width) styleInfo.width = this.width;
if (this.height) styleInfo.height = this.height;

if (this.ratio && this.variant === "rectangular") {
const ratio = Number.isNaN(Number(this.ratio)) ? 1 : Number(this.ratio);

styleInfo.height = 0;
styleInfo.paddingTop = `${100 / ratio}%`;
} else if (this.ratio && this.variant !== "rectangular") {
logger(
'You can only use `ratio` when `variant="rectangular"`.',
"skeleton",
"error",
);
}

if (this.variant === "text") {
styleInfo.width = "fit-content";
styleInfo.height = "auto";
}

const rootStyles = styleMap(styleInfo);

const rootClasses = classMap({
root: true,
[this.variant]: true,
});

return html`
<div
id="root"
class=${rootClasses}
style=${rootStyles}
part="root"
>
<slot></slot>
</div>
`;
}
}
2 changes: 0 additions & 2 deletions packages/web-components/src/skeleton/types.ts

This file was deleted.

0 comments on commit 82e144d

Please sign in to comment.