איך Nordhealth משתמשת בנכסים מותאמים אישית ברכיבי האינטרנט

יתרונות השימוש במאפיינים מותאמים אישית במערכות עיצוב ובספריות רכיבים.

David Darnes
David Darnes

אני דייב ואני מפתח Front-end בכיר ב-Nordhealth. אני עובד על התכנון והפיתוח של מערכת העיצוב Nord, שכוללת את בניית רכיבי האינטרנט לספריית הרכיבים שלנו. רציתי להסביר איך פתרנו את הבעיות שקשורות לעיצוב רכיבי אינטרנט באמצעות נכסים מותאמים אישית של CSS, וכמה מהיתרונות האחרים של השימוש בנכסים מותאמים אישית במערכות עיצוב ובספריות רכיבים.

איך אנחנו בונים רכיבי אינטרנט

כדי ליצור את רכיבי האינטרנט שלנו, אנחנו משתמשים ב-Lit, ספרייה שמספקת כמות גדולה של קוד סטנדרטי (בוילרפלייט) כמו מצב, סגנונות טווח, יצירת תבניות ועוד. לא רק שהוא קל מאוד, אלא גם מבוסס על ממשקי API מקוריים של JavaScript, כך שאנחנו יכולים לספק חבילה רזה של קוד שמנצלת את התכונות שכבר קיימות בדפדפן.


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);
רכיב אינטרנט שנכתב באמצעות Lit.

אבל הדבר הכי מושך ברכיבי אינטרנט הוא שהם עובדים כמעט עם כל מסגרת JavaScript קיימת, ואפילו בלי מסגרת בכלל. לאחר שבדף יש הפניה לחבילת ה-JavaScript הראשית, השימוש ברכיב אינטרנט דומה מאוד לשימוש ברכיב HTML מקורי. הסימן היחידי האמיתי לכך שלא מדובר ברכיב HTML מקורי הוא המקף העקבי בין התגים, שהוא תקן שמציין לדפדפן שמדובר ברכיב אינטרנט.


// TODO: DevSite - Code sample removed as it used inline event handlers
שימוש ברכיב האינטרנט שנוצר למעלה בדף.

אנקפסולציה בסגנון DOM של צל

בדומה לדומה, לרכיבי HTML מקוריים יש Shadow DOM, כך גם לרכיבי אינטרנט. Shadow DOM הוא עץ מוסתר של צמתים בתוך רכיב. הדרך הטובה ביותר להמחיש זאת היא לפתוח את בודק האינטרנט ולהפעיל את האפשרות 'הצגת עץ DOM של צל'. לאחר מכן, נסו לבחון רכיב קלט מקורי בכלי לבדיקת — תהיה לכם אפשרות לפתוח את הקלט ולראות את כל הרכיבים שבתוכו. תוכלו אפילו לנסות את זה עם אחד מרכיבי האינטרנט שלנו. נסו לבדוק את רכיב הקלט המותאם אישית שלנו כדי לראות את ה-DOM של Shadow.

DOM של הצל שנבדק בכלי הפיתוח.
דוגמה ל-DOM של Shadow ברכיב קלט טקסט רגיל וברכיב האינטרנט של קלט Nord.

אחד היתרונות (או החסרונות, בהתאם להשקפה שלכם) של Shadow DOM הוא אנקפסולציה של סגנון. אם אתה כותב CSS בתוך רכיב האינטרנט שלך, סגנונות אלה לא יכולים לדלף ולהשפיע על הדף הראשי או על רכיבים אחרים. הם כלולים במלואם בתוך הרכיב. בנוסף, CSS שנכתב עבור הדף הראשי או רכיב אינטרנט הורה לא יכול לדלף לתוך רכיב האינטרנט שלך.

סיכום הסגנונות הזה הוא יתרון בספריית הרכיבים שלנו. כך אנחנו יכולים להבטיח שכאשר מישהו ישתמש באחד מהרכיבים שלנו, הוא ייראה כמו שהתכוונתנו, ללא קשר לסגנונות שהוחלו בדף ההורה. כדי לוודא זאת, אנחנו מוסיפים את all: unset; לשורש של כל רכיבי האינטרנט שלנו.


:host {
  all: unset;
  display: block;
  box-sizing: border-box;
  text-align: start;
  /* ... */
}
קוד סטנדרטי של רכיבים שמוחל על שורש הצללית או על בורר המארח.

עם זאת, מה אם למישהו שמשתמש ברכיב האינטרנט שלך יש סיבה לגיטימית לשנות סגנונות מסוימים? אולי יש שורת טקסט שדורשת ניגודיות רבה יותר בגלל ההקשר שלה, או שגבולות צריכים להיות עבה יותר? אם אף סגנון לא נכנס לרכיב, איך אפשר לבטל את הנעילה של אפשרויות הסגנון האלה?

כאן נכנסים לתמונה 'נכסים מותאמים אישית של CSS'.

מאפיינים מותאמים אישית של CSS

נכסים מותאמים אישית מקבלים שמות מאוד מתאימים – אלו הם מאפייני CSS שאתם יכולים לתת להם שם ייחודי ולהחיל כל ערך נחוץ. הדרישה היחידה היא להוסיף לפניהם שני מקפים. אחרי שמצהירים על הנכס המותאם אישית, אפשר להשתמש בערך הזה ב-CSS באמצעות הפונקציה var().


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

.n-color-accent-text {
  color: var(--n-color-accent);
}
דוגמה מ-CSS Framework של אסימון עיצוב כנכס מותאם אישית, שנעשה בו שימוש בכיתת עזרה.

בכל הנוגע לירושה, כל הנכסים המותאמים אישית עוברים בירושה, וזאת בהתאם להתנהגות האופיינית של מאפיינים וערכים רגילים של CSS. כל מאפיין מותאם אישית שמוחל על רכיב הורה, או על הרכיב עצמו, יכול לשמש כערך בנכסים אחרים. אנחנו עושים שימוש רב בנכסים מותאמים אישית עבור אסימוני העיצוב שלנו על ידי החלה שלהם על רכיב הבסיס דרך CSS Framework. כלומר, כל הרכיבים בדף יכולים להשתמש בערכי האסימון האלה, בין אם הם רכיבי אינטרנט, מחלקה של כלי עזר של CSS או מפתח שרוצה לשלוף ערך מרשימת האסימונים שלנו.

היכולת הזו לרשת מאפיינים מותאמים אישית באמצעות הפונקציה var() היא הדרך שבה אנחנו נכנסים דרך DOM DOM של רכיבי האינטרנט, ומאפשרים למפתחים לשלוט בצורה פרטנית יותר בעיצוב הרכיבים שלנו.

נכסים מותאמים אישית ברכיב Nord Web

בכל פעם שאנחנו מפתחים רכיב עבור מערכת העיצוב שלנו, אנחנו נוקטים גישה מתחשבת לגבי ה-CSS שלו - אנחנו שואפים ליצור קוד רזה אך קל לתחזק. אסימוני העיצוב שיש לנו מוגדרים כנכסים מותאמים אישית בתוך מסגרת ה-CSS הראשית שלנו ברכיב הבסיסי.


:root {
  --n-space-m: 16px;
  --n-space-l: 24px;
  /* ... */
  --n-color-background: rgb(255, 255, 255);
  --n-color-border: rgb(216, 222, 228);
  /* ... */
}
הגדרה של נכסים מותאמים אישית של CSS בסלקטור הבסיסי.

הרכיבים שלנו מפנים לערכי האסימון האלה. במקרים מסוימים, נחיל את הערך ישירות על נכס ה-CSS, אבל במקרים אחרים נגדיר למעשה נכס מותאם אישית חדש לפי הקשר ונחיל את הערך עליו.


: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);
  /* ... */
}
מאפיינים מותאמים אישית שמוגדרים בשורש הצללית של הרכיב, ואז נעשה בהם שימוש בסגנונות הרכיבים. גם נכסים מותאמים אישית מרשימת אסימוני העיצוב נמצאים בשימוש.

בנוסף, נחליף מספר ערכים שהם ספציפיים לרכיב אך לא באסימונים שלנו ונהפוך אותם לנכס מותאם אישית לפי הקשר. נכסים מותאמים אישית בעלי הקשר לרכיב מספקים לנו שני יתרונות עיקריים. ראשית, המשמעות היא שאנחנו יכולים להיות "יבשים" יותר עם ה-CSS שלנו, מכיוון שניתן להחיל את הערך הזה על מאפיינים מרובים בתוך הרכיב.


.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);
}
נכס מותאם אישית לפי הקשר בקבוצת כרטיסיות, שנמצא בשימוש בכמה מקומות בקוד הרכיב.

שנית, שינויים במצב הרכיבים ובגרסאות שלה יהיו נקיים לגמרי — רק הנכס המותאם אישית שצריך לשנות כדי לעדכן את כל הנכסים האלה, למשל, כשאתם מעצבים מצב של העברת עכבר או מצב פעיל, או במקרה הזה, גרסה של הנכס.


:host([padding="l"]) {
  --n-tab-group-padding: var(--n-space-l);
}
גרסה של רכיב הכרטיסייה שבה המרווח הפנימי משתנה באמצעות עדכון יחיד של נכס מותאם אישית, במקום מספר עדכונים.

אבל היתרון המשמעותי ביותר הוא שכשאנחנו מגדירים את המאפיינים המותאמים אישית האלה לפי הקשר ברכיב מסוים, אנחנו יוצרים מעין API מותאם אישית של CSS לכל רכיב, שהמשתמש יכול להשתמש בו באותו רכיב.


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

<style>
  nord-tab-group {
    --n-tab-group-padding: var(--n-space-xl);
  }
</style>
שימוש ברכיב קבוצת הכרטיסיות בדף ועדכון המאפיין המותאם אישית עם מרווח פנימי גדול יותר.

הדוגמה הקודמת מראה אחד מרכיבי האינטרנט שלנו עם נכס מותאם אישית לפי הקשר שהשתנה באמצעות בורר. התוצאה של כל הגישה הזו היא רכיב שמספק למשתמש מספיק גמישות עיצוב, תוך שמירה על רוב הסגנונות בפועל בבדיקה. בנוסף, כבונוס, אנחנו, כמפתחי רכיבים, יכולים ליירט את הסגנונות האלה שהוחלו על ידי המשתמש. אם אנחנו רוצים להתאים או להרחיב אחד מהנכסים האלה, אנחנו יכולים לעשות זאת בלי שהמשתמש יצטרך לשנות את הקוד שלו.

הגישה הזו מועילה לנו במיוחד, לא רק עבורנו כיוצרים של רכיבי מערכת העיצוב שלנו, אלא גם עבור צוות הפיתוח שלנו כאשר הוא משתמש ברכיבים אלה במוצרים שלנו.

מקדמים נכסים מותאמים אישית

נכון לרגע הכתיבה, אנחנו לא חושפים בפועל את המאפיינים המותאמים אישית האלה לפי הקשר בתיעוד שלנו. עם זאת, אנחנו מתכננים לעשות זאת כדי שצוות הפיתוח הרחב יותר שלנו יוכל להבין את המאפיינים האלה ולמנף אותם. הרכיבים שלנו ארוזים ב-npm עם קובץ מניפסט, שמכיל את כל מה שאפשר לדעת עליהם. לאחר מכן אנחנו צורכים את קובץ המניפסט כנתונים כשאתר התיעוד שלנו נפרס, באמצעות Eleventy והתכונה Global Data של האתר. אנחנו מתכננים לכלול את המאפיינים המותאמים אישית וההקשריים האלה בקובץ נתוני המניפסט.

תחום נוסף שאנו רוצים לשפר הוא האופן שבו נכסים מותאמים אישית אלו יורשים ערכים. בשלב זה, לדוגמה, אם רוצים לשנות את הצבע של שני רכיבי קו מפריד, צריך לטרגט את שני הרכיבים האלה באופן ספציפי באמצעות בוררים, או להחיל את המאפיין המותאם אישית ישירות על הרכיב עם המאפיין style. זה אולי נשמע בסדר, אבל יעזור יותר אם המפתח יוכל להגדיר את הסגנונות האלה ברכיב מכיל או אפילו ברמת הבסיס.


<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>
שני מופעים של רכיב המחיצה שזקוקים לשני טיפולי צבע שונים. אחת מהן נמצאת בתוך קטע שבו אנחנו יכולים להשתמש לבורר ספציפי יותר, אבל עלינו לטרגט את המחיצה באופן ספציפי.

הסיבה לכך שצריך להגדיר את הערך של המאפיין המותאם אישית ישירות ברכיב היא שאנחנו מגדירים אותם באותו רכיב באמצעות בורר מארח הרכיבים. אסימוני התכנון הגלובליים שבהם אנחנו משתמשים ישירות ברכיב עוברים ישר דרכם, לא מושפעים מבעיה זו ואף יכולים ליירט אותם ברכיבי הורה. איך אפשר להפיק את המקסימום משני העולמות?

נכסים פרטיים וציבוריים בהתאמה אישית

נכסים פרטיים בהתאמה אישית הם נכס שהרכיבה לאה ורו. זהו נכס מותאם אישית 'פרטי' ובעל הקשר, אבל מוגדר כנכס מותאם אישית 'ציבורי' עם חלופה.



: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 של רכיב האינטרנט עם מאפיינים מותאמים אישית לפי הקשר, שעברו התאמה כך שה-CSS הפנימי מסתמך על נכס פרטי מותאם אישית שהוגדר לנכס ציבורי מותאם אישית עם חלופה.

על ידי הגדרת המאפיינים המותאמים אישית לפי הקשר בצורה הזו, אנחנו עדיין יכולים לבצע את כל הדברים שעשינו קודם, כמו ירושה של ערכי אסימונים גלובליים ושימוש חוזר בערכים בקוד הרכיב שלנו. אבל הרכיב גם יירש באלגנטיות הגדרות חדשות של המאפיין הזה על עצמו או על כל רכיב הורה.


<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>
שוב, שני תווי ההפרדה, אבל הפעם אפשר לשנות את הצבע שלהם. כדי לעשות את זה, מוסיפים לבורר הקטעים את הנכס המותאם אישית לפי הקשר. המחיצה תירש אותו וכך תפיק קוד קוד נקי וגמיש יותר.

למרות שניתן לטעון שהשיטה הזו אינה "פרטית" באמת, אנחנו עדיין חושבים שזהו פתרון אלגנטי למדי לבעיה שחששנו ממנה. כשתהיה לנו הזדמנות, נטפל בנושא הזה ברכיבים שלנו כדי שצוות הפיתוח שלנו יוכל לשלוט טוב יותר בשימוש ברכיבים ועדיין ליהנות משכבות ההגנה שקיימות אצלנו.

אני מקווה שהצלחתם להבין את האופן שבו אנחנו משתמשים ברכיבי אינטרנט עם נכסים מותאמים אישית של CSS. ספרו לנו מה דעתכם. אם תחליטו להשתמש באחת מהשיטות האלה בעבודה שלכם, תוכלו למצוא אותי ב-Twitter @DavidDarnes. ניתן גם למצוא את Nordhealth @NordhealthHQ ב-Twitter, וכן את שאר חברי הצוות שלי שעבדו קשה כדי לקרב את מערכת העיצוב הזו וליישם את התכונות המוזכרות במאמר זה: @Viljamis , @WickyNilliams ו-@eric_habich.

תמונה ראשית (Hero) של דן כריסטיאן פדורה