چگونه Nordhealth از ویژگی های سفارشی در اجزای وب استفاده می کند

مزایای استفاده از ویژگی های سفارشی در سیستم های طراحی و کتابخانه های مؤلفه.

دیوید دارنز
David Darnes

من دیو هستم و یک برنامه نویس ارشد Front-end در Nordhealth هستم. من روی طراحی و توسعه سیستم طراحی خود Nord کار می کنم که شامل ساخت اجزای وب برای کتابخانه اجزای ما است. من می‌خواستم به اشتراک بگذارم که چگونه مشکلات مربوط به استایل‌سازی اجزای وب را با استفاده از ویژگی‌های سفارشی CSS و برخی از مزایای دیگر استفاده از ویژگی‌های سفارشی در سیستم‌های طراحی و کتابخانه‌های مؤلفه حل کردیم.

چگونه کامپوننت های وب را می سازیم

برای ساخت اجزای وب خود از Lit استفاده می‌کنیم، کتابخانه‌ای که کدهای دیگ بخار زیادی مانند حالت، سبک‌های محدوده، قالب‌بندی و موارد دیگر را ارائه می‌کند. Lit نه تنها سبک وزن است، بلکه بر روی API های بومی جاوا اسکریپت نیز ساخته شده است، به این معنی که می توانیم یک بسته نرم افزاری ناب از کد ارائه دهیم که از ویژگی هایی که مرورگر قبلاً دارد بهره می برد.


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 نوشته شده است.

اما جذاب‌ترین چیز در مورد کامپوننت‌های وب این است که تقریباً با هر چارچوب جاوا اسکریپت موجود یا حتی بدون هیچ چارچوبی کار می‌کنند. هنگامی که بسته اصلی جاوا اسکریپت در صفحه ارجاع داده می شود، استفاده از یک کامپوننت وب بسیار شبیه به استفاده از یک عنصر HTML بومی است. تنها نشانه واقعی که نشان می‌دهد یک عنصر بومی HTML نیست، خط فاصله درون تگ‌ها است، که استانداردی برای نشان دادن این موضوع به مرورگر است که این یک مؤلفه وب است.


// TODO: DevSite - Code sample removed as it used inline event handlers
با استفاده از Web Component ایجاد شده در بالا در یک صفحه.

کپسوله سازی به سبک Shadow DOM

به همان شکلی که عناصر بومی HTML دارای Shadow DOM هستند، اجزای وب نیز چنین هستند. Shadow DOM یک درخت پنهان از گره ها در یک عنصر است. بهترین راه برای تجسم این موضوع این است که بازرس وب خود را باز کنید و گزینه "Show Shadow DOM tree" را روشن کنید. هنگامی که این کار را انجام دادید، سعی کنید به یک عنصر ورودی بومی در بازرس نگاه کنید—اکنون این گزینه را خواهید داشت که آن ورودی را باز کنید و همه عناصر درون آن را ببینید. حتی می‌توانید این کار را با یکی از مؤلفه‌های وب ما امتحان کنید - سعی کنید مؤلفه ورودی سفارشی ما را بررسی کنید تا Shadow DOM آن را ببینید.

DOM سایه در DevTools بازرسی شده است.
مثالی از Shadow DOM در یک عنصر ورودی متن معمولی و در مؤلفه وب ورودی Nord ما.

یکی از مزایا (یا معایب، بسته به دیدگاه شما) Shadow DOM کپسوله کردن سبک است. اگر CSS را در کامپوننت وب خود بنویسید، آن سبک ها نمی توانند به بیرون درز کنند و بر صفحه اصلی یا سایر عناصر تأثیر بگذارند. آنها به طور کامل در داخل جزء موجود هستند. علاوه بر این، CSS نوشته شده برای صفحه اصلی یا یک مؤلفه وب مادر نمی تواند به مؤلفه وب شما نشت کند.

این کپسوله‌سازی سبک‌ها یک مزیت در کتابخانه مؤلفه ما است. این به ما تضمین بیشتری می‌دهد که وقتی شخصی از یکی از مؤلفه‌های ما استفاده می‌کند، بدون توجه به سبک‌های اعمال شده در صفحه اصلی، همانطور که در نظر داشتیم به نظر می‌رسد. و برای اطمینان بیشتر، all: unset; به ریشه یا "میزبان" همه اجزای وب ما.


:host {
  all: unset;
  display: block;
  box-sizing: border-box;
  text-align: start;
  /* ... */
}
برخی از کدهای مولفه دیگ بخار در حال اعمال به ریشه سایه یا انتخابگر میزبان.

با این حال، اگر شخصی که از کامپوننت وب شما استفاده می کند دلیل موجهی برای تغییر سبک های خاص داشته باشد، چه؟ شاید خطی از متن وجود داشته باشد که به دلیل زمینه‌اش به کنتراست بیشتری نیاز دارد یا حاشیه باید ضخیم‌تر باشد؟ اگر هیچ سبکی نمی تواند وارد مؤلفه شما شود، چگونه می توانید قفل آن گزینه های استایل را باز کنید؟

اینجاست که CSS Custom Properties وارد می شود.

ویژگی های سفارشی CSS

ویژگی های سفارشی بسیار مناسب نام گذاری شده اند - آنها ویژگی های CSS هستند که شما می توانید به طور کامل نام خود را بنویسید و هر مقداری را که لازم است اعمال کنید. تنها شرط لازم این است که آنها را با دو خط فاصله قرار دهید. هنگامی که ویژگی سفارشی خود را اعلام کردید، مقدار را می توان در CSS با استفاده از تابع var() استفاده کرد.


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

.n-color-accent-text {
  color: var(--n-color-accent);
}
نمونه ای از چارچوب CSS ما از یک نشانه طراحی به عنوان یک ویژگی سفارشی و استفاده از آن در یک کلاس کمکی.

وقتی صحبت از وراثت می‌شود، تمام ویژگی‌های سفارشی به ارث می‌رسد که از رفتار معمولی خواص و مقادیر CSS معمولی پیروی می‌کند. هر خاصیت سفارشی که برای یک عنصر والد یا خود عنصر اعمال می شود، می تواند به عنوان یک مقدار در ویژگی های دیگر استفاده شود. ما از ویژگی‌های سفارشی برای توکن‌های طراحی خود با اعمال آن‌ها بر روی عنصر ریشه از طریق چارچوب CSS خود استفاده زیادی می‌کنیم، به این معنی که همه عناصر موجود در صفحه می‌توانند از این مقادیر نشانه استفاده کنند، خواه یک Web Component، کلاس کمکی CSS یا یک توسعه‌دهنده‌ای که می‌خواهد مقداری را از لیست توکن‌های ما حذف کند.

این توانایی به ارث بردن ویژگی‌های سفارشی، با استفاده از تابع var() است که چگونه در Shadow DOM اجزای وب خود نفوذ می‌کنیم و به توسعه‌دهندگان اجازه می‌دهیم هنگام استایل‌سازی اجزای ما، کنترل دقیق‌تری داشته باشند.

ویژگی های سفارشی در یک مؤلفه وب Nord

هر زمان که ما در حال توسعه یک مؤلفه برای سیستم طراحی خود هستیم، یک رویکرد متفکرانه به 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);
}
گروه برگه padding Contextual Custom Property که در چندین مکان در کد جزء استفاده می شود.

و ثانیاً، باعث می‌شود تغییرات وضعیت و تغییرات کامپوننت واقعاً تمیز باشد - فقط ویژگی سفارشی است که باید تغییر کند تا همه آن ویژگی‌ها را به‌روزرسانی کنید، مثلاً زمانی که یک حالت شناور یا حالت فعال یا، در این مورد، یک تغییر را طراحی می‌کنید.


:host([padding="l"]) {
  --n-tab-group-padding: var(--n-space-l);
}
گونه‌ای از مولفه برگه که در آن بالشتک با استفاده از یک به‌روزرسانی ویژگی سفارشی به جای چند به‌روزرسانی تغییر می‌کند.

اما قوی‌ترین مزیت این است که وقتی این ویژگی‌های سفارشی متنی را بر روی یک مؤلفه تعریف می‌کنیم، نوعی CSS API سفارشی برای هر یک از مؤلفه‌های خود ایجاد می‌کنیم که می‌تواند توسط کاربر آن مؤلفه مورد استفاده قرار گیرد.


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

<style>
  nord-tab-group {
    --n-tab-group-padding: var(--n-space-xl);
  }
</style>
استفاده از مولفه گروه برگه در صفحه و به‌روزرسانی ویژگی سفارشی padding به اندازه بزرگتر.

مثال قبلی یکی از اجزای وب ما را با یک ویژگی سفارشی متنی که از طریق یک انتخابگر تغییر کرده است نشان می دهد. نتیجه کل این رویکرد مؤلفه‌ای است که انعطاف‌پذیری ظاهری کافی را برای کاربر فراهم می‌کند و در عین حال اکثر سبک‌های واقعی را کنترل می‌کند. به علاوه، به عنوان یک امتیاز، ما به عنوان توسعه دهندگان کامپوننت توانایی رهگیری آن سبک های اعمال شده توسط کاربر را داریم. اگر بخواهیم یکی از آن ویژگی‌ها را تنظیم یا گسترش دهیم، می‌توانیم بدون اینکه کاربر نیازی به تغییر کد خود داشته باشد.

ما این رویکرد را بسیار قدرتمند می‌دانیم، نه تنها برای ما به‌عنوان خالق اجزای سیستم طراحی‌مان، بلکه برای تیم توسعه‌مان نیز زمانی که از این اجزا در محصولات ما استفاده می‌کنند.

استفاده بیشتر از ویژگی های سفارشی

در زمان نگارش، ما در واقع این ویژگی های سفارشی متنی را در اسناد خود آشکار نمی کنیم. با این حال، ما برنامه‌ریزی می‌کنیم تا تیم توسعه گسترده‌تر ما بتواند این ویژگی‌ها را درک کرده و از آنها استفاده کند. اجزای ما در 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>
دو نمونه از مولفه تقسیم کننده ما که به دو رنگ متفاوت نیاز دارند. یکی در داخل یک بخش قرار دارد که می‌توانیم از آن برای انتخابگر خاص‌تری استفاده کنیم، اما باید تقسیم‌کننده را به طور خاص هدف قرار دهیم.

دلیل اینکه شما باید مقدار Custom Property را مستقیماً روی کامپوننت تنظیم کنید این است که ما آنها را روی همان عنصر از طریق انتخابگر میزبان کامپوننت تعریف می کنیم. توکن‌های طراحی جهانی که مستقیماً در کامپوننت استفاده می‌کنیم، مستقیماً بدون تأثیر این موضوع از بین می‌روند و حتی می‌توانند روی عناصر والد رهگیری شوند. چگونه می توانیم بهترین های هر دو دنیا را بدست آوریم؟

خصوصیات سفارشی خصوصی و عمومی

خصوصیات سفارشی خصوصی چیزی است که توسط Lea Verou گردآوری شده است ، که یک ویژگی سفارشی "خصوصی" متنی بر روی خود مؤلفه است، اما به یک ویژگی سفارشی "عمومی" با یک بازگشت مجدد تنظیم شده است.



: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 تقسیم‌کننده Web Component با ویژگی‌های سفارشی متنی به‌گونه‌ای تنظیم شده است که 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 مفید بوده باشد. نظر خود را با ما در میان بگذارید و اگر تصمیم دارید از هر یک از این روش ها در کار خود استفاده کنید، می توانید من را در توییتر @DavidDarnes پیدا کنید. همچنین می‌توانید Nordhealth @NordhealthHQ را در توییتر بیابید، و همچنین بقیه اعضای تیم من، که برای گردآوری این سیستم طراحی و اجرای ویژگی‌های ذکر شده در این مقاله سخت کار کرده‌اند: @Viljamis ، @WickyNilliams و @eric_habich .

تصویر قهرمان توسط دن کریستین پادورت