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

Zalety używania właściwości niestandardowych w systemach projektowania i bibliotekach komponentów.

David Darnes
David Darnes

Mam na imię Dave i jestem starszym programistą frontendu w firmie Nordhealth. Zajmuję się projektowaniem i rozwojem naszego systemu projektowania Nord, który obejmuje m.in. tworzenie komponentów sieciowych do naszej biblioteki komponentów. Chcę opowiedzieć o tym, jak rozwiązaliśmy problemy związane ze stylem komponentów internetowych za pomocą właściwości niestandardowych CSS, a także inne korzyści płynące z używania właściwości niestandardowych w systemach projektowania i bibliotekach komponentów.

Jak tworzymy komponenty sieciowe

Do tworzenia komponentów sieciowych używamy Lit – biblioteki, która udostępnia wiele stałych elementów kodu, takich jak stan, style zakresu czy szablony. Jest ona nie tylko lekka, lecz oparta na natywnych interfejsach API JavaScript, dzięki czemu możemy dostarczać nieduży pakiet kodu wykorzystujący funkcje dostępne 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 angielskim.

Najbardziej atrakcyjną cechą komponentów sieciowych jest to, że współpracują z prawie każdą istniejącą platformą JavaScript lub nawet nie obsługują żadnej. Gdy na stronie pojawi się odwołanie do głównego pakietu JavaScript, korzystanie z komponentu internetowego przypomina używanie natywnego elementu HTML. Jedynym rzeczywistym znakiem, który sygnalizuje, że nie jest to natywny element HTML, jest spójny łącznik w tagach, który jest standardem wskazującym przeglądarce, że jest to komponent internetowy.


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

Herbata cienia w stylu DOM

W podobny sposób natywne elementy HTML mają model Shadow DOM, podobnie jak komponenty sieciowe. Shadow DOM to ukryte drzewo węzłów w elemencie. Aby to zwizualizować, najlepiej otwórz inspektora sieci i włącz opcję „Pokaż drzewo DOM DOM”. Gdy to zrobisz, przyjrzyj się natywnym elementom wejściowym w inspektorze – pojawi się możliwość otworzenia tych danych wejściowych i obejrzenia wszystkich zawartych w nich elementów. Możesz nawet wypróbować ten komponent z jednym z naszych komponentów sieciowych – sprawdź nasz niestandardowy komponent do wprowadzania danych, aby zobaczyć model DOM.

DOM DOM sprawdzony w Narzędziach deweloperskich.
Przykład modelu Shadow DOM w zwykłym elemencie wprowadzania tekstu i w komponencie sieciowym dane wejściowego Nord.

Jedną z zalet (lub wad – w zależności od wizji) architekturze Shadow DOM jest hermetyzacja stylów. Jeśli tworzysz kod CSS w komponencie internetowym, style te nie mogą wyciekać ani wpływać na stronę główną ani inne elementy. Są one w całości zawarte w komponencie. Kod CSS napisany dla strony głównej lub nadrzędnego komponentu internetowego nie może również wyciekać do komponentu internetowego.

Takie zakończenie stylów jest jedną z korzyści w naszej bibliotece komponentów. Daje nam to większą pewność, że gdy ktoś użyje jednego z naszych komponentów, będzie on wyglądał zgodnie z oczekiwaniami, niezależnie od stylów zastosowanych na stronie nadrzędnej. Aby jeszcze bardziej zyskać pewność, dodajemy atrybut all: unset; do katalogu głównego, czyli „hosta” wszystkich naszych komponentów sieciowych.


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

Co jednak zrobić, jeśli osoba korzystająca z Twojego komponentu internetowego ma uzasadniony powód, by zmienić niektóre style? Może jeden wiersz tekstu wymaga większego kontrastu ze względu na kontekst lub obramowanie trzeba zwiększyć? Jak możesz odblokować te opcje stylu, jeśli do komponentu nie dostają się żadne style?

Właśnie tu do akcji wkraczają właściwości niestandardowe CSS.

Właściwości niestandardowe CSS

Właściwości niestandardowe mają bardzo odpowiednią nazwę – są to właściwości CSS, które możesz samodzielnie nazwać i zastosować dowolną wartość. Jedynym wymaganiem jest dodanie do nich dwóch łączników. Po zadeklarowaniu właściwości niestandardowej możesz jej używać 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 naszej struktury CSS przedstawiającej token projektowy jako właściwość niestandardową, który jest używany w klasie pomocniczej.

W przypadku dziedziczenia wszystkie właściwości niestandardowe są dziedziczone zgodnie z typowym zachowaniem zwykłych właściwości i wartości CSS. Dowolna niestandardowa właściwość zastosowana do elementu nadrzędnego lub samego elementu może być używana jako wartość w innych usługach. Właściwości niestandardowe wykorzystujemy w naszych tokenach projektowych do elementu głównego za pomocą naszej struktury CSS. Oznacza to, że wszystkie elementy na stronie mogą korzystać z tych wartości, niezależnie od tego, czy jest to komponent internetowy, klasa pomocnicza CSS, czy programista, który chce pobrać wartość z naszej listy tokenów.

Dzięki tej możliwości dziedziczenia właściwości niestandardowych przy użyciu funkcji var() przebijamy się przez model DOM dla komponentów sieciowych, umożliwiając programistom większą kontrolę nad stylem komponentów.

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

Za każdym razem, gdy opracowujemy komponent naszego systemu projektowania, uważnie podchodzimy do jego stylu CSS – zależy nam na tym, by kod był jak najjaśniejszy, ale łatwy w użyciu. Tokeny projektowe są zdefiniowane jako właściwości niestandardowe w głównej strukturze 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 CSS zdefiniowane w selektorze głównym.

Wartości tokena są przywoływane w komponentach. W niektórych przypadkach zastosujemy wartość bezpośrednio w usłudze CSS, a w innych zdefiniujemy nową kontekstową usługę niestandardową 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 są definiowane w pierwiastku cienia komponentu i używane w stylach komponentu. Używane są również właściwości niestandardowe z listy tokenów projektowych.

Wyodrębnimy też niektóre wartości charakterystyczne dla komponentu, ale nie w tokenach, i przekształcimy je w kontekstową usługę niestandardową. Właściwości niestandardowe związane z kontekstem komponentu mają 2 główne zalety. Po pierwsze, nasz kod CSS może zapewnić większą „suchość”, ponieważ tę 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);
}
Kontekstowa dopełnienie grupy kart właściwość niestandardowa używana w wielu miejscach w kodzie komponentu.

Po drugie, stan komponentu i odmiana są bardzo przejrzyste – aby zaktualizować wszystkie te właściwości, trzeba na przykład zmienić tylko właściwość niestandardową, gdy np. określisz styl stanu najechania, stanu aktywności lub, w tym przypadku odmiany.


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

Największą zaletą jest to, że kiedy zdefiniujemy kontekstowe właściwości niestandardowe w komponencie, tworzymy dla każdego z naszych komponentów niestandardowy interfejs CSS API, z którego może skorzystać użytkownik danego 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 aktualizowanie dopełnienia właściwości niestandardowej przez zwiększenie rozmiaru.

Poprzedni przykład przedstawia jeden z naszych komponentów sieciowych z kontekstową usługą niestandardową zmienioną za pomocą selektora. W efekcie takie podejście zapewnia użytkownikom wystarczającą elastyczność w określaniu stylu, a jednocześnie pozwala zachować zgodność z większością rzeczywistych stylów. Dodatkowo jako deweloperzy komponentów możemy przechwytywać style zastosowane 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.

Takie podejście jest niezwykle skuteczne nie tylko dla nas, jako twórców komponentów systemu projektowania, ale także dla naszego zespołu programistów, który wykorzystuje je w naszych produktach i usługach.

Ulepszamy właściwości niestandardowe

Obecnie nie ujawniamy w naszej dokumentacji kontekstowych właściwości niestandardowych, ale planujemy tak, aby nasz większy zespół programistów mógł je zrozumieć i wykorzystać. Nasze komponenty są spakowane do npm w pliku manifestu, który zawiera wszystkie informacje na ich temat. Plik manifestu jest następnie przetwarzany jako dane po wdrożeniu naszej witryny z dokumentacją. Można to zrobić przy użyciu usługi Eleventy i jej funkcji danych globalnych. Planujemy uwzględnić te kontekstowe właściwości niestandardowe w tym pliku danych manifestu.

Kolejnym obszarem, który chcemy poprawić, jest sposób, w jaki kontekstowe właściwości niestandardowe dziedziczą wartości. Jeśli na przykład chcesz zmienić kolor dwóch komponentów rozdzielających, musisz zastosować kierowanie na oba komponenty za pomocą selektorów lub zastosować właściwość niestandardową bezpośrednio do elementu z atrybutem stylu. Może się to wydawać normalne, ale lepiej byłoby, gdyby deweloper mógł definiować te style na poziomie elementu nadrzędnego lub nawet na poziomie głównym.


<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 naszego separatora wymagają 2 różnych kolorów. Pierwszy z nich jest zagnieżdżony w sekcji, której możemy użyć do uzyskania bardziej szczegółowego selektora, ale musimy kierować reklamy konkretnie na separator.

Musisz ustawić wartość właściwości niestandardowej bezpośrednio w komponencie, ponieważ definiujemy ją w tym samym elemencie za pomocą selektora hosta komponentu. Globalne tokeny projektowe, których używamy bezpośrednio w komponencie, przechodzą bezpośrednio, ten problem nie ma na niego wpływu, a nawet mogą być przechwytywane w elementach nadrzędnych. Jak możemy połączyć to, co najlepsze?

Prywatne i publiczne właściwości niestandardowe

Prywatne właściwości niestandardowe utworzyła Lea Verou. Są one kontekstową „prywatną” właściwością niestandardową w samym komponencie, ale ustawioną jako „publiczną” właściwość niestandardową z zastępczą wartością.



: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);
  /* ... */
}
Plik CSS dzielnika z kontekstowymi właściwościami niestandardowymi został dostosowany tak, aby wewnętrzna usługa CSS korzystała z prywatnej usługi niestandardowej, która została ustawiona jako publiczna usługa niestandardowa z zastępczą wartością.

Jeśli skonfigurujesz kontekstowe właściwości niestandardowe w taki sposób, nadal możemy wykonywać te same czynności, co do tej pory, na przykład dziedziczyć wartości tokenów globalnych i ponownie ich używać w całym kodzie komponentu. Będzie też płynnie dziedziczyć nowe definicje właściwości samej lub dowolnego elementu nadrzędnego.


<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 2 rozgraniczenia można zmienić, dodając do selektora sekcji kontekstową właściwość niestandardową separatora. Separator odziedziczy go, tworząc czystszy i bardziej elastyczny fragment kodu.

Chociaż można argumentować, że ta metoda nie jest w pełni „prywatna”, to wciąż sądzimy, że jest to dość eleganckie rozwiązanie problemu, którego się martwiliśmy. Gdy pojawi się taka możliwość, zajmiemy się tą sprawą w naszych komponentach, tak aby nasz zespół programistów miał większą kontrolę nad ich wykorzystywaniem, a jednocześnie nadal korzystał z naszych barier.

Mam nadzieję, że udało mi się zrozumieć, jak korzystamy z komponentów sieciowych z właściwościami niestandardowymi CSS. Jeśli zdecydujesz się wykorzystać którąś z tych metod w swojej pracy, daj nam znać, co o tym myślisz. Możesz mnie też znaleźć na Twitterze: @DavidDarnes. Możesz też odwiedzić konto Nordhealth @NordhealthHQ na Twitterze oraz inne osoby z mojego zespołu, które ciężko pracowały nad połączeniem tego systemu projektowania i wdrożeniem funkcji opisanych w tym artykule: @Viljamis, @WickyNilliams i @eric_habich.

Baner powitalny: Dan Cristian Pădure Reklamy