Примечание: авторский перевод статьи Web Content Elements
В HTML разработке мы используем тег в качестве дескриминатора - тег определяет элемент. Мы используем классы, чтобы применять стили к HTML элементам. Разработчик создает структуру и описывает стили руководствуясь правилами конкретного проекта, своим опытом и общепринятыми рекомендациями.
В результате мы получаем проекты состоящие из длинного списка кастомных решений в моменте принятых разработчиком лично, либо в составе команды, например после код ревью.
Проблему можно описать следующим образом: разработка HTML структуры и системы стилей для этой структуры недостаточно формализована для обеспечения единого и независимого от проекта, разработчика или этапа разработки стандарта.
Web Content Elements(WCE) - это концепция, шаблон проектирования, который позволяет описать любые решения подобного рода линейно и однообразно.
Согласно WCE, тег - это переменная для генерации синтаксической структуры элемента разметки.
Основная идея паттерна заключается в группировке элементов по роли, которую они представляют на странице.
? Элементы сгруппированы по их ролям на странице, а не тегам.
Основные роли:
Block (div, section, main, footer, e.t.c.)
Text (p, span, b, e.t.c)
Image (img)
Link (a)
Button (button)
Divider (hr)
List (ul, ol, dl, e.t.c.)
Дополнительные роли(служебные функции для разработчиков):
Custom
Создавать дополнительные HTML структуры согласно WCE паттерна

React Content Elements это библиотека, которая реализует Web Content Elements паттерн с помощью Typescript и React.
Basics
Content Elements(CE) названы по роли, которую они представляют в контексте страницы(DOM дерева).
Примеры использования CE элементов:
import CE from 'react-content-elements'; <CE.Block>Block Content Element</CE.Block> // HTML <div class="ce ce-block">Block Content Element</div> <CE.Text className="class-name">Text Content Element</CE.Text> // HTML <p class="class-name ce ce-text">Text Content Element</p> <CE.Image src="link/to/the-image.jpg" /> // HTML <img class="ce ce-image" src="link/to/the-image.jpg" /> <CE.Link>Link Content Element</CE.Link> // HTML <a class="ce ce-link">Link Content Element</a> <CE.Button>Button Content Element</CE.Button> // HTML <button class="ce ce-link" type="button">Button Content Element</button> <CE.Divider /> // HTML <hr class="ce ce-divider" />
Каждый CE элемент имеет:
дефолтный тег
Определяется по имени элемента, например ‘p’ для Text Content Element
базовый класс: ce ce-[name]
например “ce ce-text” для Text Content Element
Мы определили базовую структуру и теперь можем рассмотреть как применяются стили к нагим элементам.
Как мы уже знаем, каждый CE элемент обладает базовым классом и RCE ****предлагает набор миксинов, для того чтобы просто и понятно применять стили к данным элементам:
@use 'react-content-element/styles/utils' as *; /* by content element */ @include byName { color: red; } // CSS .ce { color: red; } /* by content element name */ @include byName('text') { font-size: 16px; } // CSS .ce-text { font-size: 16px; }
Мы научились генерировать базовые структуры и применять к ним стили, а значит самое время перейти к более сложным и реалистичным кейсам.
Hello, Real Development World!
В данной статье будут рассмотрены следующие вопросы:
Как управлять тегами и получать нестандартные значения (например, 'h1', 'section' и т.д.)?
Как определять стили для конкретных элементов, а не целой группы(например Text или Image)? Просто добавить класс(через свойство JSX элемента ‘className’) и применить БЭМ?
Создание базовых структур достаточно просто и очевидно, но как быть с более сложными, комплексными примерами, такими как списки, макеты, таблицы или любые другие кастомные структуры?
Каждый CE элемент кастомизируется через специальные свойства:
Tag
Modifiers
Content
Также есть дополнительные специальные свойства:
Config
Альтернативная точка входа для любых свойств элемента (CE или JSX), имеет более высокий приоритет.If
Это свойство фильтрует элемент, приводя значение свойства к логическому типу. Если значение равно «false», то разметка не генерируется, а функция возвращает значение null.
Tag
Мы можем определить тэг элемента через свойство ‘tag’
import CEfrom 'react-content-elements'; <CE.Text tag="h1">Rule your mind or it will rule you.</CE.Text> // HTML <h1 class="ce ce-text">Rule your mind or it will rule you.</h1>
Modifiers
Мы можем расширить список классов CE элемента с помощью свойства "modifiers"
import CEfrom 'react-content-elements'; <CE.Link className="nav-link" modifiers={['bold']}>Navigation Link</CE.Text> <CE.Link className="nav-link" modifiers={['bold', 'active']}> Active Navigation Link </CE.Text> // HTML <a class="nav-link ce ce-link ce--bold">Navigation Link</a> <a class="nav-link ce ce-link ce--bold ce--active">Active Navigation Link</a>
К классам, созданным с помощью модификаторов, можно обратиться с использованием следующих SASS-миксинов:
@use 'react-content-element/styles/utils' as *; /* by content element modifier */ @include byModifier('bold') { font-weight: bold; } // CSS .ce--bold { font-weight: bold; } /* by selector with content element modifier */ .nav-link { @include withModifier('active') { color: red; } } // CSS .nav-link.ce--active { color: red; }
Модификаторы могут также использоваться для изменения поведения элементов по умолчанию, например, для переопределения дефолтного тега:
import CEfrom 'react-content-elements'; <CE.Text modifiers={['title']}>Be yourself; everyone else is already taken.</CE.Text> // HTML <h3 class="ce ce-text ce--title">Be yourself; everyone else is already taken.</h3>
Список тегов byName(дефолтные теги) и byModifier(по модификатору) настраивается через конфигурацию CE элемента:
import CEfrom 'react-content-elements'; CE.setup({ tags: { byName: { 'text': 'p', }, byModifier: { 'title': 'h3', }, }, })
Также модификаторы реализуют полезную фичу для создания адаптивных макетов:
Above & Beyond
above-[breakpoint-name]- стили применяются для экранов шириной меньше заданного значения для брейкпойнтаbeyond-[breakpoint-name]- стили применяются для экранов шириной больше или равной заданному значения для брейкпойнта
import CEfrom 'react-content-elements'; <CE.Text modifiers={["title-above-xl", "accent-beyond-sm"]}>Don't be dead serious about your life – it's just a play.</CE.Text> // HTML <p class="ce ce-text ce--title-above-xl ce--accent-beyond-sm">Don't be dead serious about your life – it's just a play.</p> <CE.Block modifiers={["row-above-md", "section-below-xl"]}>The way you speak to yourself matters.</CE.Block> // HTML <div class="ce ce-block ce--row-above-md ce--section-below-xl">The way you speak to yourself matters.</div>
Content
Это свойство определяет HTML содержимое вашего элемента. Оно обладает приоритетом над данными, переданными через свойства "children".
import CEfrom 'react-content-elements'; <CE.Text content="Simplicity is the ultimate sophistication."/> // HTML <p class="ce ce-text">Simplicity is the ultimate sophistication.</p> <CE.Text content="Content by property">Creativity is intelligence having fun.</CE.Text> // HTML <p class="ce ce-text">Content by property</p>
Config
Мы можем определять любые свойства для элемента через “config”. Значения переданные через это свойство будут иметь высший приоритет.
import CEfrom 'react-content-elements'; <CE.Text config={{ modifiers: ['accent'], tag: 'h2' }}>Simple example with config</CE.Text> // HTML <h2 class="ce ce-text ce--accent">Simple example with config</h2> <CE.Text tag="h3" modifiers={['bold']} config={{ modifiers: ['accent'], tag: 'h2', content: '<i>Content by config</i>' }} > Another example with config </CE.Text> // HTML <h2 class="ce ce-text ce--accent"><i>Content by config</i></h2>
If
Булево значение, которое используется для фильтрации элементов по условию. Если передается ложное значение, элемент не будет отображаться.
import CEfrom 'react-content-elements'; <CE.Text if={0}>Nothing is impossible</CE.Text> // HTML // The element is not rendered <CE.Text if={1}>Everything is possible</CE.Text> // HTML <p class="ce ce-text">Everything is possible</p>
Теперь, когда мы узнали основы и методы настройки элементов, давайте перейдем к более сложным структурам. Мы рассмотрим следующие элементы:
List
Custom
List
Структура HTML списка включает в себя два элемента (ul и li). Вот как мы можем воспроизвести это с помощью CE элементов.
import CEfrom 'react-content-elements'; <CE.List /> // HTML <ul class="ce ce-list"></ul> <CE.List> <CE.Text className="first">1st item</CE.Text> <CE.Text modifiers={['bold']}>2nd item</CE.Text> </CE.List> // HTML <ul class="ce ce-list"> <li class="ce ce-item"> <p class="first ce ce-text">1st item</p> </li> <li class="ce ce-item"> <p class="ce ce-text ce--bold">2nd item</p> </li> </ul> <CE.List items={[ { content: '1st item', modifiers: ['accent'] }, { content: '2nd item', tag: 'span' } ]} ItemTemplate={CE.Text} /> // HTML <ul class="ce ce-list"> <li class="ce ce-item"> <p class="ce ce-text ce--accent">1st item</p> </li> <li class="ce ce-item"> <span class="ce ce-text">2nd item</span> </li> </ul>
Custom
В случае, если необходимая структура элемента отсутствует "из коробки", мы можем расширить стандартный список с помощью Custom элемента.
Для генерации базового класса по имени элемента существует вспомогательная функция getCEClassName:
import { getCEClassName } from 'react-content-elements'; getCEClassName('example', ['modifier', false && 'another-modifier']); // 'ce ce-example ce--modifier'
/* custom element */ const CustomTable = ({ className, headerCellModifiers, cellHeaders, rowsData, }) => { const baseClassName = getCEClassName('custom-table'); const trHeaderClassName = getCEClassName('custom-table-header'); const thClassName = getCEClassName('custom-table-cell', headerCellModifiers); const trClassName = getCEClassName('custom-table-row'); const tdClassName = getCEClassName('custom-table-cell'); const TableRow = ({ rowData }: any) => ( <tr className={trClassName}> {rowData.map((rowValue: string, rowID: string) => ( <td key={rowID} className={tdClassName}> {rowValue} </td> ))} </tr> ); return ( <table className={[className, baseClassName].join(' ')}> <thead> <tr className={trHeaderClassName}> {cellHeaders.map((header: string, headID: string) => ( <th key={headID} className={thClassName}> {header} </th> ))} </tr> </thead> <tbody> {rowsData.map((rowData: string, rowID: string) => ( <TableRow rowData={rowData} key={rowID} /> ))} </tbody> </table> ); }; /* usage of custom element */ const headers = ['header 1', 'header 2', 'header 3', 'header 4']; const firstRowData = ['cell 1', 'cell 2', 'cell 3', 'cell 4']; const rowsData = [firstRowData]; const headerCellModifiers = ['bold']; <CE.Custom CustomTemplate={CustomTable} cellHeaders={headers} rowsData={rowsData} headerCellModifiers={headerCellModifiers} /> /* HTML */ <table className="ce ce-custom-table"> <thead> <tr className="ce ce-custom-table-header"> <th className="ce ce-custom-table-cell ce--bold">header 1</th> <th className="ce ce-custom-table-cell ce--bold">header 2</th> <th className="ce ce-custom-table-cell ce--bold">header 3</th> <th className="ce ce-custom-table-cell ce--bold">header 4</th> </tr> </thead> <tbody> <tr className="ce ce-custom-table-row"> <td className="ce ce-custom-table-cell">cell 1</td> <td className="ce ce-custom-table-cell">cell 2</td> <td className="ce ce-custom-table-cell">cell 3</td> <td className="ce ce-custom-table-cell">cell 4</td> </tr> </tbody> </table>
