Это серия статей, в которых я представляю, как я считаю, отличный способ потреблять и поддерживать токены дизайна. Примеры написаны на React и styled-components, но идеи в этой статье могут быть адаптированы к любым фреймворкам.
Вступление
В Части 1 этой серии мы придумали набор желаемых интерфейсов для использования токенов дизайна. В этой статье мы реализуем токены для соответствия этим интерфейсам. В части 3 мы рассмотрим, как сделать токены более удобными в обслуживании.
Процесс
В этой статье не будет много чтения / рассуждений. Я прослежу каждую итерацию, как определено в предыдущей статье, и покажу вам, как токены дизайна могут выглядеть на каждом этапе. Чтобы увидеть обоснование каждой итерации, см. Часть 1.
Начнем с предположения, что наши токены изначально определены так:
const tokens = { orange: "#ff9b00", purple: "#5f259f", blue: "#0096e6", red: "#ff0000", white: "#ffffff", lighter: "#e8e8e8", lightest: "#b2b2b2", dark: "#999999", darker: "#666666", darkest: "#333333", black: "#000000", };
Итерация 1 - Группировка цвета по использованию
В этой итерации мы хотим сгруппировать токены по типу; будь то цвет текста, цвета фона или цвет значка.
Желаемый интерфейс:
const MyBanner = styled.div` color: ${tokens.text.darkGray}; //color: #333333 background-color: ${tokens.background.purple}; `;
Новое определение токена:
const tokens = { text: { orange: "#ff9b00", purple: "#5f259f", blue: "#0096e6", red: "#ff0000", white: "#ffffff", grayDarkest: "#333333", }, background: { orange: "#ff9b00", purple: "#5f259f", blue: "#0096e6", white: "#ffffff", grayLightest: "#b2b2b2", black: "#000000", }, icon: { white: "#ffffff", grayDark: "#999999", }, };
Итерация 2 - Группировка токенов по типу
Затем мы представляем другие категории токенов, такие как размеры (размеры шрифта, высота строк) и макеты (медиа-запросы, z-индексы).
Желаемый интерфейс:
const MyBanner = styled.div` color: ${tokens.color.text.darkGray}; //color: #333333 background-color: ${tokens.color.background.purple}; font-size: ${tokens.size.font.sm1}; line-height: ${tokens.size.lineHeight.small}; @media (min-width: ${tokens.layout.media.tablet}) { font-size: ${tokens.size.font.lg1}; z-index: ${tokens.layout.zIndex.bottomlessPit}; } `;
Новое определение токена:
const tokens = { color: { text: { orange: "#ff9b00", purple: "#5f259f", ... }, background: { orange: "#ff9b00", purple: "#5f259f", ... }, icon: { orange: "#ff9b00", purple: "#5f259f", ... }, }, layout: { media: { tablet: "640px", ... }, zIndex: { bottomlessPit: "-9999", ... }, }, size: { border: { small: "solid 1px", ... }, font: { sm1: "12px", ... }, lineHeight: { small: "1", ... }, radius: { small: "4px", ... }, }, };
Итерация 3 - объединение ключа токена со значением токена
В этой итерации мы хотим упростить интерфейс, включив имя свойства CSS как часть токенов.
Желаемый интерфейс:
const {color, size, layout} = tokens; const MyBanner = styled.div` ${color.text.darkGray}; //color: #333333 ${color.background.purple}; ${size.font.sm1}; ${size.lineHeight.small}; ${layout.media.tablet} { ${size.font.lg1}; ${layout.zIndex.bottomlessPit}; } `;
Новые токены:
const tokens = { color: { text: { orange: "color: #ff9b00", purple: "color: #5f259f", ... }, background: { orange: "background-color: #ff9b00", purple: "background-color: #5f259f", ... }, }, layout: { media: { tablet: "@media (min-width: 640px)", ... }, zIndex: { bottomlessPit: "z-index: -9999", ... }, }, size: { border: { small: "border: solid 1px", ... }, font: { sm1: "font-size: 12px", ... }, lineHeight: { small: "line-height: 1", ... }, radius: { small: "border-radius: 4px", ... }, }
Итерация 4 - Назначение цветов по функциональности
Далее мы хотим называть цвета не их названиями цветов, а в соответствии с целями, которым они служат на странице.
Желаемый интерфейс:
const MyBanner = styled.div` ${color.text.alt}; //color: #999999 OR color: #FFC0CB ${color.background.main1}; ${color.border.alt}; `;
Новые жетоны:
const tokens = { color: { text: { brand1: "color: #ff9b00", brand2: "color: #5f259f", interaction: "color: #0096e6", error: "color: #ff0000", inverse: "color: #ffffff", default: "color: #666666", alt: "color: #333333", }, background: { brand1: "background-color: #ff9b00", brand2: "background-color: #5f259f", interaction: "background-color: #0096e6", default: "background-color: #ffffff", alt: "background-color: #b2b2b2", inverse: "background-color: #000000", }, }, ... }
Итерация 5 - Intellisense
В итерации 5 мы хотим, чтобы intellisense предоставил нам больше информации об используемых нами токенах. Сам интерфейс не меняется, но нам нужно добавить комментарии в определения токенов:
Новые жетоны:
const tokens = { color: { text: { /** Orange #ff9b0 */ brand1: "color: #ff9b00", /** Purple #5f259f */ brand2: "color: #5f259f", /** Blue #0096e6 */ interaction: "color: #0096e6", ... }, layout: { media: { /** min-width: 640px */ tablet: "@media (min-width: 640px)", /** min-width: 940px */ desktop: "@media (min-width: 940px)", /** min-width: 1200px */ largeDesktop: "@media (min-width: 1200px)", }, zIndex: { /** -9999 */ bottomlessPit: "z-index: -9999", /** 1*/ default: "z-index: 1", /** 800 */ overlay: "z-index: 800", /** 9999 */ overTheMoon: "z-index: 9999", }, }, ... };
Вы уже можете видеть, что это становится довольно сложным. Мы поговорим о том, как упростить вещи, чтобы их было легче поддерживать в Части 3. А пока продолжим.
Итерация 6. Отображение имени токена как интерфейса Typescript.
Наконец, мы хотим предоставить интерфейсы машинописного текста с ключами токенов. Для этого мы бы написали что-то вроде:
export interface ColorTextKey = "default" | "inverse" | "error" export interface BgTextKey = "defualt | "inverse" | "alt" ...
Заключение
Вот как выглядят наши последние токены:
const tokens = { color: { text: { /** Orange #ff9b0 */ brand1: "color: #ff9b00", /** Purple #5f259f */ brand2: "color: #5f259f", /** Blue #0096e6 */ interaction: "color: #0096e6", /** Red #ff0000 */ error: "color: #ff0000", /** White #ffffff */ inverse: "color: #ffffff", /** Darkest Gray #333333 */ default: "color: #333333", }, background: { /** Orange #ff9b00 */ brand1: "background-color: #ff9b00", /** Purple #5f259f */ brand2: "background-color: #5f259f", /** Blue #0096e6 */ interaction: "background-color: #0096e6", /** White #ffffff */ default: "background-color: #ffffff", /** Gray #b2b2b2 */ alt: "background-color: #b2b2b2", /** Black #000000 */ inverse: "background-color: #000000", }, icon: { /** Black #ffffff */ white: "color: #ffffff", /** Black #999999 */ grayDark: "color: #999999", /** Blue #0096e6 */ interaction: "color: #0096e6", }, }, layout: { media: { /** min-width: 640px */ tablet: "@media (min-width: 640px)", /** min-width: 940px */ desktop: "@media (min-width: 940px)", /** min-width: 1200px */ largeDesktop: "@media (min-width: 1200px)", }, zIndex: { /** -9999 */ bottomlessPit: "z-index: -9999", /** 200 */ dropdown: "z-index: 200", /** 400 */ sticky: "z-index: 400", /** 600 */ popover: "z-index: 600", /** 1*/ default: "z-index: 1", /** 800 */ overlay: "z-index: 800", /** 1000 */ modal: "z-index: 1000", /** 1200 */ snackbar: "z-index: 1200", /** 1400 */ spinner: "z-index: 1400", /** 9999 */ overTheMoon: "z-index: 9999", }, size: { font: { /** 12px - Small fonts starts at 12px and increments by 2 */ sm1: "font-size: 12px", /** 14px - Small fonts starts at 12px and increments by 2 */ sm2: "font-size: 14px", /** 16px - Small fonts starts at 12px and increments by 2 */ sm3: "font-size: 16px", /** 18px - Small fonts starts at 12px and increments by 2 */ sm4: "font-size: 18px", /** 24px - Large fonts starts at 24px and increments by 8 */ lg1: "font-size: 24px", /** 32px - Large fonts starts at 24px and increments by 8 */ lg2: "font-size: 32px", /** 40px - Large fonts starts at 24px and increments by 8 */ lg3: "font-size: 40px", /** 48px - Large fonts starts at 24px and increments by 8 */ lg4: "font-size: 48px", }, } }, }; type ColorBackgroundKeys = keyof typeof tokens.color.background type ColorTextKeys = keyof typeof tokens.color.text type ColorIconKeys = keyof typeof tokens.color.icon type LayoutMediaKeys = keyof typeof tokens.layout.media type LayoutZIndexKeys = keyof typeof tokens.layout.zIndex type SizeFontKeys = keyof typeof tokens.size.font
Это работает и удовлетворяет всем требованиям, которые мы выдвинули в Части 1. В дальнейшем мы будем называть эту структуру удобным для потребления форматом.
Однако это не идеально. Это не только выглядит устрашающе, но и может быть немного сложным в обслуживании. В Части 3 мы рассмотрим некоторые из проблем, которые имеет удобный для потребления формат, и представим удобный для обслуживания формат .