Jak Nordhealth korzysta z właściwości niestandardowych w komponentach sieciowych

Zalety korzystania z właściwości niestandardowych w systemach projektowych i bibliotekach komponentów.

David Darnes
David Darnes

Nazywam się Dave i pracuję jako starszy programista front-endu w firmie Nordhealth. Pracuję nad projektem i rozwojem naszego systemu projektowego Nord, który obejmuje tworzenie komponentów internetowych do naszej biblioteki komponentów. Chcę opowiedzieć, jak rozwiązaliśmy problemy związane ze stylizacją komponentów internetowych, używając właściwości niestandardowych CSS, oraz o niektórych innych zaletach korzystania z właściwości niestandardowych w systemach projektowania i bibliotekach komponentów.

Jak tworzymy komponenty sieciowe

Do tworzenia naszych komponentów sieciowych używamy biblioteki Lit, która zawiera wiele gotowych fragmentów kodu, takich jak stan, style zakresu czy szablony. Jest nie tylko uproszczony, ale jest też oparty na natywnych interfejsach API JavaScriptu, co oznacza, że możemy dostarczyć nieskomplikowany pakiet kodu, który wykorzystuje funkcje, które już mamy w przeglądarce.


import {html, css, LitElement} from 'lit';

export class SimpleGreeting extends LitElement {
  static styles = css`:host { color: blue; font-family: sans-serif; }`;

  static properties = {
    name: {type: String},
  };

  constructor() {
    super();
    this.name = 'there';
  }

  render() {
    return html`

Hey ${this.name}, welcome to Web Components!

`
; } } customElements.define('simple-greeting', SimpleGreeting);
Komponent internetowy napisany w języku Lit.

Największą zaletą komponentów internetowych jest jednak to, że działają one z prawie każdym istniejącym frameworkiem JavaScriptu, a nawet bez frameworka. Kiedy na stronie jest odwołanie do głównego pakietu JavaScriptu, używanie komponentu internetowego przypomina używanie natywnego elementu HTML. Jedynym prawdziwym znakiem, że nie jest to natywny element HTML, jest konsekwentne używanie myślnika w tagach, co jest standardem oznaczającym dla przeglądarki, że jest to komponent internetowy.


// TODO: DevSite - Code sample removed as it used inline event handlers
Korzystanie z komponentu sieciowego utworzonego powyżej na stronie.

Kodowanie stylu Shadow DOM

Podobnie jak natywne elementy HTML mają model Shadow DOM, tak samo jest w przypadku komponentów internetowych. Shadow DOM to ukryte drzewo węzłów w elemencie. Najlepszym sposobem, aby to zobaczyć, jest otwarcie inspektora sieci i włączenie opcji „Pokaż drzewo DOM Shadow”. Następnie w inspektorze sprawdź element domyślnego wejścia. Teraz możesz otworzyć to wejście i zobaczyć wszystkie jego elementy. Możesz to też wypróbować z jednym z naszych komponentów sieciowych. Sprawdź nasz niestandardowy komponent do wprowadzania danych, aby zobaczyć jego DOM.

Shadow DOM w Narzędziach deweloperskich.
Przykład modelu Shadow DOM w zwykłym elemencie do wprowadzania tekstu i w naszym komponencie Web w standardzie Nord.

Jedną z zalet (lub wad – zależnie od programu) funkcji Shadow DOM jest hermetyzacja stylu. Jeśli napiszesz kod CSS w komponencie internetowym, te style nie będą mogły przenikać na stronę główną ani inne elementy. Będą one całkowicie zawarte w komponencie. Oprócz tego kod CSS napisany dla strony głównej lub nadrzędnego komponentu internetowego nie może wyciekać do komponentu internetowego.

To uwzględnienie stylów to jedna z korzyści dostępnych w naszej bibliotece komponentów. Dzięki temu mamy większą pewność, że gdy ktoś użyje jednego z naszych komponentów, będzie on wyglądał tak, jak chcieliśmy, niezależnie od stylów zastosowanych na stronie nadrzędnej. Aby jeszcze bardziej to ułatwić, dodaliśmy all: unset; do katalogu głównego (czyli „hosta”) wszystkich naszych komponentów internetowych.


:host {
  all: unset;
  display: block;
  box-sizing: border-box;
  text-align: start;
  /* ... */
}
Niektóre elementy szablonowego kodu komponentu są stosowane do katalogu głównego cienia lub selektora hosta.

Co jednak w sytuacji, gdy użytkownik korzystający z komponentu internetowego ma uzasadniony powód, by zmienić określone style? Może jakiś wiersz tekstu wymaga większego kontrastu ze względu na kontekst lub obramowanie musi być grubsze? Jeśli żadne style nie mogą zostać zastosowane do Twojego komponentu, jak możesz odblokować te opcje stylizacji?

Dlatego do akcji wkraczają niestandardowe właściwości CSS.

Właściwości niestandardowe w CSS

Właściwości niestandardowe mają bardzo prawidłową nazwę – są to właściwości CSS, które możesz nazwać samodzielnie i zastosować dowolną potrzebną wartość. Wymaga to tylko dodania 2 łączników. Po zadeklarowaniu właściwości niestandardowej możesz używać jej wartości w CSS za pomocą funkcji var().


:root {
  --n-color-accent: rgb(53, 89, 199);
  /* ... */
}

.n-color-accent-text {
  color: var(--n-color-accent);
}
Przykład z platformy CSS Framework tokena projektu jako właściwości niestandardowej, który jest używany w klasie pomocniczej.

Jeśli chodzi o dziedziczenie, wszystkie właściwości niestandardowe są dziedziczone, co jest zgodne z typowym zachowaniem zwykłych właściwości i wartości CSS. Niestandardowych właściwości zastosowanych do elementu nadrzędnego lub samego elementu można używać jako wartości w innych właściwościach. Bardzo intensywnie korzystamy z właściwości niestandardowych w przypadku tokenów projektowych, stosując je do elementu głównego za pomocą platformy CSS Framework. Oznacza to, że wszystkie elementy na stronie mogą ich używać, niezależnie od tego, czy jest to komponent internetowy, klasa pomocnicza CSS czy programista chcący uzyskać wartość z naszej listy tokenów.

Ta zdolność do dziedziczenia właściwości niestandardowych za pomocą funkcji var() pozwala nam omijać DOM cieniowy naszych komponentów internetowych i umożliwia deweloperom bardziej szczegółową kontrolę nad stylami komponentów.

Właściwości niestandardowe w komponencie Nord Web

Kiedy opracowujemy komponent do naszego systemu projektowego, stosujemy przemyślane podejście do jego CSS – staramy się stworzyć kod łatwy w użyciu, ale przy tym łatwy w utrzymaniu. Nasze tokeny projektowe są zdefiniowane jako właściwości niestandardowe w głównym interfejsie CSS w elemencie głównym.


:root {
  --n-space-m: 16px;
  --n-space-l: 24px;
  /* ... */
  --n-color-background: rgb(255, 255, 255);
  --n-color-border: rgb(216, 222, 228);
  /* ... */
}
Właściwości niestandardowe w arkuszu CSS zdefiniowane w selektorze głównym.

Wartości tokenów są następnie przywoływane w naszych komponentach. W niektórych przypadkach zastosujemy wartość bezpośrednio w usłudze porównywania cen, ale w innych przypadkach zdefiniujemy nową kontekstową usługę spersonalizowaną i zastosujemy do niej wartość.


:host {
  --n-tab-group-padding: 0;
  --n-tab-list-background: var(--n-color-background);
  --n-tab-list-border: inset 0 -1px 0 0 var(--n-color-border);
  /* ... */
}

.n-tab-group-list {
  box-shadow: var(--n-tab-list-border);
  background-color: var(--n-tab-list-background);
  gap: var(--n-space-s);
  /* ... */
}
Właściwości niestandardowe zdefiniowane w głównym cieniu komponentu i używane w stylach komponentu. Używane są też usługi niestandardowe z listy tokenów projektu.

Utworzymy też abstrakcyjne wartości, które są specyficzne dla danego komponentu, ale nie występują w naszych tokenach, i przekształcimy je w kontekstualną właściwość niestandardową. Właściwości niestandardowe, które odnoszą się do komponentu, mają 2 główne zalety. Po pierwsze, oznacza to, że możemy bardziej „wyschnąć” z CSS, ponieważ wartość można zastosować do wielu właściwości w komponencie.


.n-tab-group-list::before {
  /* ... */
  padding-inline-start: var(--n-tab-group-padding);
}

.n-tab-group-list::after {
  /* ... */
  padding-inline-end: var(--n-tab-group-padding);
}
Dopełnienie kontekstowej właściwości niestandardowej grupy kart używanej w wielu miejscach w kodzie komponentu.

Po drugie, ułatwia to wprowadzanie zmian w stanie komponentu i wariantach – wystarczy zmienić tylko właściwość niestandardową, aby zaktualizować wszystkie te właściwości, gdy na przykład stylizujesz stan najechania kursorem lub stan aktywny albo w tym przypadku wariant.


:host([padding="l"]) {
  --n-tab-group-padding: var(--n-space-l);
}
Wariant komponentu karty, w którym wypełnienie jest zmieniane za pomocą pojedynczej aktualizacji właściwości niestandardowej zamiast wielu aktualizacji.

Największą zaletą jest jednak to, że gdy definiujemy te kontekstowe właściwości niestandardowe w komponencie, tworzymy dla każdego z naszych komponentów rodzaj niestandardowego interfejsu API CSS, z którego może korzystać użytkownik tego komponentu.


<nord-tab-group label="Title">
  <!-- ... -->
</nord-tab-group>

<style>
  nord-tab-group {
    --n-tab-group-padding: var(--n-space-xl);
  }
</style>
Użycie komponentu grupy kart na stronie i zmiana dopełnienia właściwości niestandardowej na większy rozmiar.

Poprzedni przykład przedstawia jeden z naszych Komponentów sieciowych z kontekstową właściwością niestandardową zmienioną za pomocą selektora. Efektem tego podejścia jest komponent, który zapewnia użytkownikowi wystarczającą elastyczność stylizacji, zachowując przy tym kontrolę nad większością rzeczywistych stylów. Dodatkowo, jako deweloperzy komponentów mamy możliwość przechwytywania stylów zastosowanych przez użytkownika. Jeśli chcemy dostosować lub rozszerzyć jedną z tych właściwości, możemy to zrobić bez konieczności zmiany kodu przez użytkownika.

Uważamy, że to podejście jest bardzo skuteczne nie tylko dla nas jako twórców komponentów systemu projektowania, ale też dla naszego zespołu programistów, który używa tych komponentów w naszych usługach.

Rozszerzanie właściwości niestandardowych

W momencie pisania tego artykułu nie ujawniamy tych kontekstowych usług w dokumentacji, ale planujemy to zrobić, aby nasz szerszy zespół programistów mógł je zrozumieć i wykorzystać. Nasze komponenty są spakowane w npm w pliku manifestu, który zawiera wszystkie potrzebne informacje na ich temat. Następnie używamy pliku manifestu jako danych po wdrożeniu naszej witryny z dokumentacją, co odbywa się za pomocą usługi Eleventy i jej funkcji Dane globalne. Planujemy uwzględnić te kontekstowe właściwości niestandardowe w tym pliku danych manifestu.

Innym obszarem, który chcemy ulepszyć, jest sposób, w jaki te kontekstowe usługi niestandardowe dziedziczą wartości. Jeśli na przykład chcesz dostosować kolor dwóch komponentów separatora, musisz ustawić kierowanie na oba komponenty za pomocą selektorów lub zastosować właściwość niestandardową bezpośrednio do elementu z atrybutem style. Może się to wydawać w porządku, ale lepiej byłoby, gdyby deweloper mógł zdefiniować te style w elemencie zawierającym lub nawet na poziomie katalogu głównego.


<nord-divider></nord-divider>

<section>
  <nord-divider></nord-divider>
   <!-- ... -->
</section>

<style>
  nord-divider {
    --n-divider-color: var(--n-color-status-danger);
  }

  section {
    padding: var(--n-space-s);
    background: var(--n-color-surface-raised);
  }
  
  section nord-divider {
    --n-divider-color: var(--n-color-status-success);
  }
</style>
2 wystąpienia elementu separatora, który wymaga 2 różnych oznaczeń kolorów. Jeden z nich jest zagnieżdżony w sekcji, którą możemy wykorzystać do bardziej szczegółowego selektora, ale musimy kierować reklamy na samą linię rozdzielającą.

Wartość właściwości niestandardowej musisz ustawić bezpośrednio w komponencie, ponieważ definiujemy je w tym samym elemencie za pomocą selektora hosta komponentu. Globalne tokeny projektu, których używamy bezpośrednio w komponencie, przechodzą od razu, nie mają na nie wpływu, a nawet mogą być przechwytywane w elementach nadrzędnych. Jak możemy korzystać z możliwości obu tych usług?

Prywatne i publiczne usługi niestandardowe

Prywatne właściwości niestandardowe to usługa, która została ustanowiona przez Lea Verou. Jest to kontekstowa „prywatna” właściwość niestandardowa w samym komponencie, ale ustawiona jako „publiczna” właściwość niestandardowa z wartością zastępczą.



:host {
  --_n-divider-color: var(--n-divider-color, var(--n-color-border));
  --_n-divider-size: var(--n-divider-size, 1px);
}

.n-divider {
  border-block-start: solid var(--_n-divider-size) var(--_n-divider-color);
  /* ... */
}
CSS komponentu internetowego z elementem Property Custom Properties zmodyfikowanym tak, aby wewnętrzny CSS korzystał z właściwości Property Custom Properties, która została ustawiona jako publiczna z elementem zastępczym.

Definiowanie kontekstowych właściwości niestandardowych w taki sposób oznacza, że nadal możemy wykonywać wszystkie czynności, które wykonywaliśmy wcześniej, np. dziedziczyć wartości globalnych tokenów i wykorzystywać wartości w kodzie komponentu. Komponent będzie też płynnie dziedziczyć nowe definicje tej właściwości w sobie lub w dowolnym elemencie nadrzędnym.


<nord-divider></nord-divider>

<section>
  <nord-divider></nord-divider>
   <!-- ... -->
</section>

<style>
  nord-divider {
    --n-divider-color: var(--n-color-status-danger);
  }

  section {
    padding: var(--n-space-s);
    background: var(--n-color-surface-raised);
    --n-divider-color: var(--n-color-status-success);
  }
</style>
Ponownie te dwa separatory, ale tym razem można je zmienić kolorem, dodając do selektora sekcji kontekstową właściwość niestandardową separatora. Odziedziczy go separator, co pozwoli uzyskać bardziej przejrzysty i elastyczny fragment kodu.

Chociaż można argumentować, że ta metoda nie jest naprawdę „prywatna”, uważamy, że jest to dość eleganckie rozwiązanie problemu, który nas niepokoił. Gdy tylko będzie to możliwe, rozwiążemy ten problem w naszych komponentach, aby nasz zespół programistów miał większą kontrolę nad ich używaniem, a jednocześnie mógł korzystać z dostępnych zabezpieczeń.

Mam nadzieję, że te informacje o tym, jak używamy komponentów internetowych z właściwościami niestandardowymi w CSS, były dla Ciebie przydatne. Daj nam znać, co o tym myślisz. Jeśli zdecydujesz się użyć którejś z tych metod w swojej pracy, możesz mnie znaleźć na Twitterze pod adresem @DavidDarnes. Na Twitterze znajdziesz także Nordhealth @NordhealthHQ na Twitterze, a także innych członków mojego zespołu, którzy ciężko pracują nad połączeniem tego systemu projektowania i wykonaniem funkcji opisanych w tym artykule: @Viljamis, @WickyNilliams i @eric_habich.

Baner powitalny: Dan Cristian Pădureț