Это серия статей, в которых я представляю, как я считаю, отличный способ потреблять и поддерживать токены дизайна. Примеры написаны на 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 мы рассмотрим некоторые из проблем, которые имеет удобный для потребления формат, и представим удобный для обслуживания формат .