Shoelace vorstellen, eine Framework-unabhängige komponentenbasiert UX Bibliothek

Avatar of Adam Rackis
Adam Rackis am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Dies ist ein Beitrag über Shoelace, eine Komponentenbibliothek von Cory LaViska, aber mit einem Twist. Sie definiert alle Ihre Standard-UX-Komponenten: Tabs, Modals, Akkordeons, Autocompletes und vieles, vieles mehr. Sie sehen von Haus aus gut aus, sind zugänglich und voll anpassbar. Aber anstatt diese Komponenten in React, Solid, Svelte usw. zu erstellen, werden sie mit Web Components erstellt; das bedeutet, dass Sie sie mit *jedem* Framework verwenden können.

Einige vorbereitende Dinge

Web Components sind großartig, aber es gibt derzeit ein paar kleine Hürden, die man beachten sollte.

React

Ich sagte, sie funktionieren in jedem JavaScript-Framework, aber wie ich schon früher geschrieben habe, ist die Unterstützung von React für Web Components derzeit schlecht. Um dem abzuhelfen, hat Shoelace tatsächlich Wrapper nur für React erstellt.

Eine andere Option, die ich persönlich mag, ist die Erstellung einer dünnen React-Komponente, die den Tag-Namen einer Web Component und all ihre Attribute und Eigenschaften akzeptiert, und dann die schmutzige Arbeit der Handhabung von Reacts Unzulänglichkeiten übernimmt. Ich habe diese Option in einem früheren Beitrag besprochen. Ich mag diese Lösung, weil sie dazu gedacht ist, gelöscht zu werden. Das Problem der Interoperabilität von Web Components ist derzeit im experimentellen Branch von React behoben, so dass, sobald dies veröffentlicht wird, jede dünne, Web Component-interoperable Komponente, die Sie verwenden, durchsucht und entfernt werden kann, so dass Sie direkte Web Component-Verwendungen ohne React-Wrapper haben.

Server-Side Rendering (SSR)

Die Unterstützung für SSR ist zum Zeitpunkt der Erstellung dieses Beitrags ebenfalls schlecht. Theoretisch gibt es etwas namens Declarative Shadow DOM (DSD), das SSR ermöglichen würde. Aber die Browserunterstützung ist minimal, und in jedem Fall erfordert DSD *Serverunterstützung*, um richtig zu funktionieren, was bedeutet, dass Next, Remix oder was auch immer Sie auf dem Server verwenden, in der Lage sein muss, spezielle Handhabung zu betreiben.

Das heißt, es gibt andere Wege, Web Components dazu zu bringen, mit einer Web-App, die mit etwas wie Next SSR'd wird, „einfach zu funktionieren“. Die Kurzfassung ist, dass die Skripte, die Ihre Web Components registrieren, in einem blockierenden Skript ausgeführt werden müssen, bevor Ihr Markup geparst wird. Aber das ist ein Thema für einen anderen Beitrag.

Natürlich ist dies kein Problem, wenn Sie irgendeine Art von clientseitig gerendertem SPA erstellen. Damit werden wir uns in diesem Beitrag beschäftigen.

Legen wir los

Da ich möchte, dass sich dieser Beitrag auf Shoelace und seine Natur als Web Component konzentriert, werde ich alles mit Svelte machen. Ich werde auch dieses Stackblitz-Projekt zur Demonstration verwenden. Wir werden diese Demo Schritt für Schritt aufbauen, aber Sie können diesen REPL jederzeit öffnen, um das Endergebnis zu sehen.

Ich werde Ihnen zeigen, wie Sie Shoelace verwenden und, was noch wichtiger ist, wie Sie es anpassen. Wir werden über Shadow DOMs und welche Stile sie von der Außenwelt blockieren (und welche nicht) sprechen. Wir werden auch über den CSS-Selektor ::part sprechen, der für Sie vielleicht ganz neu ist, und wir werden sogar sehen, wie Shoelace es uns ermöglicht, seine verschiedenen Animationen zu überschreiben und anzupassen.

Wenn Sie Shoelace nach dem Lesen dieses Beitrags mögen und es in einem React-Projekt ausprobieren möchten, rate ich Ihnen, einen Wrapper wie in der Einleitung erwähnt zu verwenden. Dies ermöglicht Ihnen die Verwendung aller Shoelace-Komponenten, und er kann vollständig entfernt werden, sobald React die bereits vorhandenen Web Component-Fixes veröffentlicht (achten Sie auf Version 19).

Shoelace vorstellen

Shoelace hat recht detaillierte Installationsanweisungen. Im einfachsten Fall können Sie <script> und <style> Tags in Ihr HTML-Dokument einfügen, und das war's. Für jede Produktionsanwendung möchten Sie jedoch wahrscheinlich nur das importieren, was Sie benötigen, und dafür gibt es auch Anleitungen.

Nach der Installation von Shoelace erstellen wir eine Svelte-Komponente, um einige Inhalte zu rendern, und gehen dann die Schritte durch, um sie vollständig anzupassen. Um etwas ziemlich nicht-triviales auszuwählen, habe ich die Tabs und die Dialog-Komponente (oft als Modal bezeichnet) gewählt. Hier ist etwas Markup, das weitgehend aus den Docs übernommen wurde

<sl-tab-group>
  <sl-tab slot="nav" panel="general">General</sl-tab>
  <sl-tab slot="nav" panel="custom">Custom</sl-tab>
  <sl-tab slot="nav" panel="advanced">Advanced</sl-tab>
  <sl-tab slot="nav" panel="disabled" disabled>Disabled</sl-tab>

  <sl-tab-panel name="general">This is the general tab panel.</sl-tab-panel>
  <sl-tab-panel name="custom">This is the custom tab panel.</sl-tab-panel>
  <sl-tab-panel name="advanced">This is the advanced tab panel.</sl-tab-panel>
  <sl-tab-panel name="disabled">This is a disabled tab panel.</sl-tab-panel>
</sl-tab-group>

<sl-dialog no-header label="Dialog">
  Hello World!
  <button slot="footer" variant="primary">Close</button>
</sl-dialog>

<br />
<button>Open Dialog</button>

Dies rendert einige schöne, stilisierte Tabs. Die Unterstreichung des aktiven Tabs animiert sich sogar schön und gleitet vom einen aktiven Tab zum nächsten.

Four horizontal tab headings with the first active in blue with placeholder content contained in a panel below.
Standard-Tabs in Shoelace

Ich werde Ihre Zeit nicht damit verschwenden, jeden Winkel der APIs durchzugehen, die bereits gut auf der Shoelace-Website dokumentiert sind. Stattdessen schauen wir uns an, wie wir am besten mit diesen Web Components interagieren und sie vollständig anpassen können.

Interaktion mit der API: Methoden und Events

Das Aufrufen von Methoden und das Abonnieren von Events auf einer Web Component kann sich leicht von dem unterscheiden, was Sie von Ihrem üblichen Framework gewohnt sind, aber es ist nicht zu kompliziert. Sehen wir uns an, wie.

Tabs

Die Tab-Komponente (<sl-tab-group>) hat eine show Methode, die einen bestimmten Tab manuell anzeigt. Um diese aufzurufen, benötigen wir Zugriff auf das zugrunde liegende DOM-Element unserer Tabs. In Svelte bedeutet das die Verwendung von bind:this. In React wäre es ein ref. Und so weiter. Da wir Svelte verwenden, deklarieren wir eine Variable für unsere tabs-Instanz

<script>
  let tabs;
</script>

…und binden sie

<sl-tab-group bind:this="{tabs}"></sl-tab-group>

Jetzt können wir einen Button hinzufügen, um sie aufzurufen

<button on:click={() => tabs.show("custom")}>Show custom</button>

Bei Events ist es das Gleiche. Es gibt ein sl-tab-show-Event, das ausgelöst wird, wenn ein neuer Tab angezeigt wird. Wir könnten addEventListener auf unserer tabs-Variable verwenden, oder wir können die on:event-name-Kurzform von Svelte verwenden.

<sl-tab-group bind:this={tabs} on:sl-tab-show={e => console.log(e)}>

Das funktioniert und protokolliert die Event-Objekte, während Sie verschiedene Tabs anzeigen.

Event object meta shown in DevTools.

Typischerweise rendern wir Tabs und lassen den Benutzer dazwischen klicken, sodass diese Arbeit normalerweise nicht einmal notwendig ist, aber sie ist da, wenn Sie sie brauchen. Lassen Sie uns jetzt die Dialog-Komponente interaktiv gestalten.

Dialog

Die Dialog-Komponente (<sl-dialog>) nimmt eine open Prop entgegen, die steuert, ob der Dialog geöffnet ist. Lassen Sie uns sie in unserer Svelte-Komponente deklarieren

<script>
  let tabs;
  let open = false;
</script>

Sie hat auch ein sl-hide-Event, wenn der Dialog versteckt wird. Lassen Sie uns unsere open Prop übergeben und an das hide-Event binden, damit wir sie zurücksetzen können, wenn der Benutzer außerhalb des Dialoginhalts klickt, um ihn zu schließen. Und fügen wir dem Schließen-Button einen Click-Handler hinzu, um unsere open Prop auf false zu setzen, was den Dialog ebenfalls schließen würde.

<sl-dialog no-header {open} label="Dialog" on:sl-hide={() => open = false}>
  Hello World!
  <button slot="footer" variant="primary" on:click={() => open = false}>Close</button>
</sl-dialog>

Zuletzt verbinden wir unseren Öffnen-Dialog-Button

<button on:click={() => (open = true)}>Open Dialog</button>

Und das war's. Die Interaktion mit der API einer Komponentenbibliothek ist mehr oder weniger unkompliziert. Wenn das alles wäre, was dieser Beitrag tut, wäre er ziemlich langweilig.

Aber Shoelace – da es mit Web Components erstellt wurde – bedeutet, dass einige Dinge, insbesondere Stile, etwas anders funktionieren werden, als wir es vielleicht gewohnt sind.

Alle Stile anpassen!

Zum Zeitpunkt der Erstellung dieses Beitrags befindet sich Shoelace noch in der Beta-Phase und der Entwickler überlegt, einige Standardstile zu ändern, möglicherweise sogar einige Standards ganz zu entfernen, damit sie nicht mehr die Stile Ihrer Host-Anwendung überschreiben. Die Konzepte, die wir behandeln werden, sind in jedem Fall relevant, aber wundern Sie sich nicht, wenn einige der spezifischen Shoelace-Details, die ich erwähne, anders sind, wenn Sie es verwenden.

So gut die Standardstile von Shoelace auch sind, wir haben vielleicht unsere eigenen Designs in unserer Web-App und möchten, dass unsere UX-Komponenten dazu passen. Sehen wir uns an, wie wir das in einer Web Components-Welt tun würden.

Wir werden nicht versuchen, etwas zu *verbessern*. Der Shoelace-Entwickler ist ein weit besserer Designer, als ich es jemals sein werde. Stattdessen schauen wir uns nur an, wie man Dinge *ändert*, damit Sie sich an Ihre eigenen Web-Apps anpassen können.

Eine schnelle Tour durch Shadow DOMs

Schauen Sie sich einen dieser Tab-Header in Ihren DevTools an; er sollte ungefähr so aussehen

The tabs component markup shown in DevTools.

Unser Tab-Element hat einen div-Container mit den Klassen .tab und .tab--active sowie einem tabindex erstellt und dabei den von uns eingegebenen Text für diesen Tab angezeigt. Aber bemerken Sie, dass er sich innerhalb eines *Shadow Root* befindet. Dies ermöglicht es Web Component-Autoren, ihr eigenes Markup zum Web Component hinzuzufügen und gleichzeitig einen Platz für den Inhalt zu bieten, den *wir* bereitstellen. Beachten Sie das <slot>-Element? Das bedeutet im Grunde "füge hier allen Inhalt ein, den der Benutzer zwischen den Web Component-Tags gerendert hat".

Das <sl-tab>-Element erstellt also einen Shadow Root, fügt ihm Inhalt hinzu, um den schön gestalteten Tab-Header zusammen mit einem Platzhalter (<slot>) zu rendern, der unseren Inhalt darin rendert.

Eingekapselte Stile

Eines der klassischen, frustrierendsten Probleme in der Webentwicklung waren schon immer Stile, die kaskadierten und an Stellen gelangten, wo wir sie nicht haben wollen. Sie könnten befürchten, dass jede Stilregel in unserer Anwendung, die etwas wie div.tab angibt, diese Tabs beeinträchtigt. Es stellt sich heraus, dass dies kein Problem ist; Shadow Roots kapseln Stile ein. Stile von außerhalb des Shadow Roots beeinflussen nicht, was sich im Shadow Root befindet (mit einigen Ausnahmen, über die wir sprechen werden), und umgekehrt.

Ausnahmen sind erbbare Stile. Sie müssen natürlich nicht für jedes Element in Ihrer Web-App eine font-family-Style anwenden. Stattdessen können Sie Ihre font-family einmal auf :root oder html festlegen und sie überall darunter erben lassen. Diese Vererbung wird sogar den Shadow Root durchdringen.

CSS Custom Properties (oft als „CSS-Variablen“ bezeichnet) sind eine verwandte Ausnahme. Ein Shadow Root kann absolut eine CSS-Eigenschaft lesen, die außerhalb des Shadow Roots definiert ist; dies wird gleich relevant.

Der ::part Selektor

Was ist mit Stilen, die sich *nicht* vererben. Was ist, wenn wir etwas wie den cursor anpassen wollen, der sich nicht vererbt, auf etwas innerhalb des Shadow Roots. Haben wir Pech gehabt? Es stellt sich heraus, dass wir das nicht haben. Werfen Sie noch einmal einen Blick auf das Tab-Element-Bild oben und seinen Shadow Root. Beachten Sie das part-Attribut am div? Das erlaubt es Ihnen, dieses Element von außerhalb des Shadow Roots mit dem ::part Selektor anzusprechen und zu stylen. Wir werden das gleich anhand eines Beispiels durchgehen.

Shoelace-Stile überschreiben

Sehen wir uns jeden dieser Ansätze in Aktion an. Derzeit erhalten *viele* Shoelace-Stile, einschließlich Schriftarten, Standardwerte von CSS Custom Properties. Um diese Schriftarten mit den Stilen Ihrer Anwendung abzugleichen, überschreiben Sie die betreffenden Custom Props. Im Docs finden Sie Informationen dazu, welche CSS-Variablen Shoelace verwendet, oder Sie können die Stile in jedem beliebigen Element in den DevTools inspizieren.

Stile durch den Shadow Root erben

Öffnen Sie die Datei app.css im src-Verzeichnis des StackBlitz-Projekts. Im :root-Abschnitt am Ende sollten Sie eine Deklaration letter-spacing: normal; sehen. Da die letter-spacing-Eigenschaft erblich ist, versuchen Sie, einen neuen Wert festzulegen, z. B. 2px. Nach dem Speichern passt sich der gesamte Inhalt, einschließlich der Tab-Header im Shadow Root, entsprechend an.

Four horizontal tab headers with the first active in blue with plqceholder content contained in a panel below. The text is slightly stretched with letter spacing.

Shoelace CSS-Variablen überschreiben

Die <sl-tab-group>-Komponente liest eine --indicator-color CSS Custom Property für die Unterstreichung des aktiven Tabs. Wir können dies mit einfachem CSS überschreiben

sl-tab-group {
  --indicator-color: green;
}

Und schon haben wir einen grünen Indikator!

Four horizontal tab headers with the first active with blue text and a green underline.

Teile abfragen

In der Version von Shoelace, die ich gerade verwende (2.0.0-beta.83), hat jeder nicht deaktivierte Tab einen pointer-Cursor. Ändern wir das zu einem Standard-Cursor für den aktiven (ausgewählten) Tab. Wir haben bereits gesehen, dass das <sl-tab>-Element ein part="base"-Attribut am Container für den Tab-Header hinzufügt. Außerdem erhält der aktuell ausgewählte Tab ein active-Attribut. Nutzen wir diese Fakten, um den aktiven Tab anzusprechen und den Cursor zu ändern

sl-tab[active]::part(base) {
  cursor: default;
}

Und das war's!

Animationen anpassen

Als sozusagen Kirsche auf dem metaphorischen Kuchen, sehen wir uns an, wie Shoelace uns erlaubt, Animationen anzupassen. Shoelace verwendet die Web Animations API und stellt eine setDefaultAnimation API zur Verfügung, um zu steuern, wie verschiedene Elemente ihre verschiedenen Interaktionen animieren. Sehen Sie die Docs für Einzelheiten, aber als Beispiel, hier ist, wie Sie die Standard-Dialoganimation von Shoelace ändern könnten, von der Ausdehnung nach außen und dem Zusammenziehen nach innen, zu stattdessen dem Animieren von oben, und dem Herunterfallen beim Verstecken.

import { setDefaultAnimation } from "@shoelace-style/shoelace/dist/utilities/animation-registry";

setDefaultAnimation("dialog.show", {
  keyframes: [
    { opacity: 0, transform: "translate3d(0px, -20px, 0px)" },
    { opacity: 1, transform: "translate3d(0px, 0px, 0px)" },
  ],
  options: { duration: 250, easing: "cubic-bezier(0.785, 0.135, 0.150, 0.860)" },
});
setDefaultAnimation("dialog.hide", {
  keyframes: [
    { opacity: 1, transform: "translate3d(0px, 0px, 0px)" },
    { opacity: 0, transform: "translate3d(0px, 20px, 0px)" },
  ],
  options: { duration: 200, easing: "cubic-bezier(0.785, 0.135, 0.150, 0.860)" },
});

Dieser Code befindet sich in der Datei App.svelte. Kommentieren Sie ihn aus, um die ursprüngliche, Standardanimation zu sehen.

Zusammenfassung

Shoelace ist eine unglaublich ehrgeizige Komponentenbibliothek, die mit Web Components erstellt wurde. Da Web Components Framework-unabhängig sind, können sie in jedem Projekt mit jedem Framework verwendet werden. Da neue Frameworks mit erstaunlichen Leistungseigenschaften und auch Benutzerfreundlichkeit auf den Markt kommen, ist die Möglichkeit, hochwertige User Experience-Widgets zu verwenden, die nicht an ein einzelnes Framework gebunden sind, noch nie überzeugender gewesen.