Skip to content

Commit

Permalink
refactor(empty-state): update to match the design system (#279)
Browse files Browse the repository at this point in the history
  • Loading branch information
mimshins authored Nov 23, 2024
1 parent 2f7f5c4 commit 5c1cad8
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 116 deletions.
4 changes: 2 additions & 2 deletions docs/dev/components/elements-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@
"icon": {
"simple icon": "<tap-icon-target-slash slot=\"icon\" width=\"120\" height=\"120\" color=\"#ff7733\"></tap-icon-map>"
},
"actions": {
"multiple action": "<tap-button slot=\"actions\" variant=\"primary\">روشن کن</tap-button>"
"action": {
"button": "<tap-button slot=\"action\">روشن کن</tap-button>"
}
}
},
Expand Down
4 changes: 4 additions & 0 deletions packages/web-components/src/empty-state/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const Slots = {
ICON: "icon",
ACTION: "action",
} as const;
114 changes: 54 additions & 60 deletions packages/web-components/src/empty-state/empty-state.style.ts
Original file line number Diff line number Diff line change
@@ -1,88 +1,82 @@
import { css } from "lit";

export default css`
:host {
*,
*::before,
*::after {
box-sizing: border-box;
}
:host *,
:host *::before,
:host *::after {
box-sizing: inherit;
[hidden] {
display: none !important;
}
.container {
.root.auto {
--empty-state-alignment: start;
}
.root.center {
--empty-state-alignment: center;
}
.root {
--empty-state-alignment: start;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-family: var(--tap-font-family, var(--tap-sys-font-family)), serif;
font-family: var(--tap-sys-font-family);
padding: var(--tap-sys-spacing-6) var(--tap-sys-spacing-8);
}
.icon {
margin: var(
--tap-empty-state-content-vertical-margin,
var(--tap-sys-spacing-6)
)
var(--tap-empty-state-content-horizontal-margin, 0);
color: var(--tap-sys-color-content-secondary);
display: flex;
align-items: center;
justify-items: center;
width: 4rem;
height: 4rem;
max-width: 4rem;
max-height: 4rem;
font-size: 4rem;
}
.icon ::slotted(svg) {
width: 4rem;
height: 4rem;
max-width: 4rem;
max-height: 4rem;
}
.content {
text-align: center;
padding: var(
--tap-empty-state-content-vertical-padding,
var(--tap-sys-spacing-4)
)
var(
--tap-empty-state-content-horizontal-padding,
var(--tap-sys-spacing-6)
);
text-align: var(--empty-state-alignment);
margin-top: var(--tap-sys-spacing-8);
margin-bottom: var(--tap-sys-spacing-8);
}
.title {
font-size: var(
--tap-empty-state-title-font-size,
var(--tap-sys-typography-headline-sm-size)
);
font-weight: var(
tap-empty-state-title-font-weight,
var(--tap-sys-typography-headline-sm-weight)
);
line-height: var(
--tap-empty-state-title-line-height,
var(--tap-sys-typography-headline-sm-height)
);
color: var(--tap-sys-color-content-primary);
font-size: var(--tap-sys-typography-headline-sm-size);
font-weight: var(--tap-sys-typography-headline-sm-weight);
line-height: var(--tap-sys-typography-headline-sm-height);
}
.description {
margin: var(
--tap-empty-state-description-top-margin,
var(--tap-sys-spacing-4)
)
0 0 0;
font-size: var(
--tap-empty-state-description-font-size,
var(--tap-sys-typography-body-md-size)
);
font-weight: var(
--tap-empty-state-description-font-weight,
var(--tap-sys-typography-body-md-weight)
);
line-height: var(
--tap-empty-state-description-line-height,
var(--tap-sys-typography-body-md-height)
);
color: var(
--tap-empty-state-description-color,
var(--tap-palette-gray-600)
);
margin-top: var(--tap-sys-spacing-4);
color: var(--tap-sys-color-content-secondary);
font-size: var(--tap-sys-typography-body-md-size);
font-weight: var(--tap-sys-typography-body-md-weight);
line-height: var(--tap-sys-typography-body-md-height);
}
.actions {
margin: var(
--tap-empty-state-content-vertical-margin,
var(--tap-sys-spacing-6)
)
var(--tap-empty-state-content-horizontal-margin, 0);
.title + .description {
margin-top: var(--tap-sys-spacing-4);
}
`;
101 changes: 74 additions & 27 deletions packages/web-components/src/empty-state/empty-state.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,97 @@
import { html, LitElement, nothing } from "lit";
import { property } from "lit/decorators.js";
import { html, LitElement, type PropertyValues } from "lit";
import { property, state } from "lit/decorators.js";
import { getRenderRootSlot, runAfterRepaint } from "../utils";
import { Slots } from "./constants";

export class EmptyState extends LitElement {
/**
* The title of the empty state.
*/
@property({ type: String })
public override title = "";

/**
* The description of the empty state.
*/
@property({ type: String })
public description = "";

/**
* The alignment of the content.
*/
@property({ type: String, attribute: "content-alignment" })
public contentAlignment: "center" | "auto" = "auto";

@state()
private _hasIcon = false;

@state()
private _hasAction = false;

protected override updated(changed: PropertyValues<this>): void {
super.updated(changed);

runAfterRepaint(() => {
const iconSlot = getRenderRootSlot(this.renderRoot, Slots.ICON);
const actionSlot = getRenderRootSlot(this.renderRoot, Slots.ACTION);

if (!iconSlot || !actionSlot) return;

this._hasIcon = iconSlot.assignedNodes().length > 0;
this._hasAction = actionSlot.assignedNodes().length > 0;
});
}

private _renderTitle() {
if (!this.title) return null;

return html`
<span
part="title"
class="title"
>${this.title}
</span>
`;
}

private _renderDescription() {
if (!this.description) return null;

return html`
<p
part="description"
class="description"
>
${this.description}
</p>
`;
}

protected override render() {
return html`
<div
class="container"
part="container"
class="root ${this.contentAlignment}"
part="root"
>
<span
class="icon"
part="icon"
<div
class=${Slots.ICON}
part=${Slots.ICON}
?hidden=${!this._hasIcon}
>
<slot name="icon"></slot>
</span>
<slot name=${Slots.ICON}></slot>
</div>
<div
class="content"
part="content"
>
${this.title
? html`<span
part="title"
class="title"
>${this.title}</span
>`
: nothing}
${this.description
? html`<p
part="description"
class="description"
>
${this.description}
</p>`
: nothing}
${this._renderTitle()}${this._renderDescription()}
</div>
<div
class="actions"
part="actions"
class=${Slots.ACTION}
part=${Slots.ACTION}
?hidden=${!this._hasAction}
>
<slot name="actions"></slot>
<slot name=${Slots.ACTION}></slot>
</div>
</div>
`;
Expand Down
34 changes: 7 additions & 27 deletions packages/web-components/src/empty-state/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,17 @@ import { customElement } from "lit/decorators.js";
import { EmptyState } from "./empty-state";
import styles from "./empty-state.style";

export { Slots } from "./constants";

/**
* @summary An empty state component with icon and actions slots.
*
* @cssprop [--tap-font-family=--tap-sys-font-family] - The font family used in the empty state.
* @cssprop [--tap-empty-state-icon-horizontal-margin] - The horizontal margin for the icon slot.
* @cssprop [--tap-empty-state-icon-vertical-margin=--tap-sys-spacing-6] - The vertical margin for the icon slot.
* @cssprop [--tap-empty-state-actions-horizontal-margin] - The horizontal margin for the actions slot.
* @cssprop [--tap-empty-state-actions-vertical-margin=--tap-sys-spacing-6] - The vertical margin for the actions slot.
* @cssprop [--tap-empty-state-content-horizontal-padding=--tap-sys-spacing-6] - The horizontal padding for the content slot.
* @cssprop [--tap-empty-state-content-vertical-padding=--tap-sys-spacing-4] - The vertical padding for the content slot.
* @cssprop [--tap-empty-state-title-font-size=--tap-sys-typography-headline-sm-size] - The font size used for the title in the empty state.
* @cssprop [--tap-empty-state-title-font-weight=--tap-sys-typography-headline-sm-weight] - The font weight used for the title in the empty state.
* @cssprop [--tap-empty-state-title-line-height=--tap-sys-typography-headline-sm-height] - The line height used for the title in the empty state.
* @cssprop [--tap-empty-state-description-font-size=--tap-sys-typography-headline-sm-size] - The font size used for the description in the empty state.
* @cssprop [--tap-empty-state-description-font-weight=--tap-sys-typography-headline-sm-size] - The font weight used for the description in the empty state.
* @cssprop [--tap-empty-state-description-line-height=--tap-sys-typography-headline-sm-size] - The line height used for the description in the empty state.
* @cssprop [--tap-empty-state-description-color=--tap-sys-typography-headline-sm-size] - The color used for the description in the empty state.
* @cssprop [--tap-empty-state-description-top-margin=--tap-sys-spacing-4] - The top margin used for the description in the empty state.
*
* @csspart [container] - The root container of the empty state component a `div`.
* @csspart [icon] - The container of the icon slot, a `span`.
* @csspart [content] - The wrapper around heading and description, a `div`.
* @csspart [heading] - The heading element, a `span`.
* @csspart [description] - The description element, a `p`.
* @csspart [actions] - The container of the actions slot, a `div`.
* @summary An empty state component with icon and action slots.
*
* @slot [icon] - the icon slot of the empty state.
* @slot [actions] - the actions slot of the empty state.
* @slot icon - The slot for icon element.
* @slot action - The slot for action element.
*
* @prop {string} [title=''] - The title of the empty state.
* @prop {string} [description=''] - The description for the empty state.
* @prop {string} [description=''] - The description of the empty state.
* @prop {'auto' | 'center'} [content-alignment='auto'] - The alignment of the content.
*/

@customElement("tap-empty-state")
Expand Down

0 comments on commit 5c1cad8

Please sign in to comment.