Skip to content

Основное

Гайдлайн

Значения атомов для передачи в атрибуты компонент Дизайн-Системы считываются с макетов в Figma. Визуализация гайдлайна.

Гайдлайн предоставляет типографику, скругления, непрозрачности, палитру и тени. Объявляет точки перехода типоразмеров и отступы в сетках, скорости анимаций.

Перечни токенов, атомов гайдлайна (кроме имен и допустимых для них размеров пиктограмм), можно найти в файле src/models/models.ts проекта ДС.

ts
// Atoms

// Типографика
enum Fonts {
  // Заголовки
  primo = 'primo',
  // остальные токены ...
}

// Скругления
enum BorderRadiuses {
  jade = 'jade',
  // остальные токены ...
}

// Непрозрачности
enum Opacities {
  odo = 'odo',
  // остальные токены ...
}

// Тени
enum Shadows {
  bigShadow = 'bigShadow',
  // остальные токены ...
}

// Палитра
enum Colors {
  harakiri = 'harakiri',
  // остальные токены ...
}

Значения точек перехода типоразмеров, а также перечни цветов для каждой из тем - в константе DESIGN.

Если вы передадите в компонент значение атрибута не соответсвующее гайдлайну - ДС покажет сообщение об ошибке в консоли и установит дефолтное значение.

Компоненты

Библиотека предоставляет несколько важных компонент, которые сейчас не перечислены именно как отдельные компоненты в гайдлайне. Это:

  • Компонент Text надежно предоставляющий типографику.

  • Компонент Theme со слотом предоставляющий переключение цветовых тем.

  • Компонент Wrapper предоставляющий адаптивную обертку над контентом "по всей доступной ширине".

  • Компонент Grid предоставляющий возможность создавать адаптивные сетки с простыми ритмами.

Почему важно использовать, например, компоненты Text или Grid при разметке шаблонов? Ведь вместо первого, по сути, достаточно скопировать набор "готового CSS" из Фигма в селектор стиля, а второе, тоже самое, что и простая нативная сетка, селектор с несколькими правилами и медиа-запросами?

Используя поставленные компоненты ДС, вы поступаете правильно, потому что:

  • Ваши коды разметки остаются максимально выразительными и консистентными. Все только в одном месте, легче искать.

  • Компоненты ДС используют не деревянные магические значения, а атомы гайдлайна - цвета, шрифты, брекпоинты, скорости анимаций. Вы можете спокойно полностью перестать уделять внимание таким важным, крайне важным, для качества дизайна, подробностям и деталям, занявшись на своих проектах архитектурой и бизнес-логикой, а не копированием невыразительных кусков стилей с неизбежными ошибками.

  • Кроме того, всегда остается вероятной ситуация, что дизайнеры опять захотят что-либо изменить или добавить. Любые изменения в код аккуратно отверстанный с помощью компонентов ДС - придут "по умолчанию" при обновлении библиотеки.

Атрибуты

В Ангуляр вам следует указзывать составные имена атрибутов в верблюжьей нотации (camelCase), а не в шашлычной (kebab-case):

html
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';

@Component({
  ...
  template: '
    <ds-grid
      [columnsWidest]="columns"
      [gapWidest]="gap"
    >
      ...
    </ds-grid>',
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class SomeComponent {
  columns: string;
  gap: string;
  ...
}

Привязывание логических атрибутов

Входящие параметры компонент логического типа можно разделить на два условно разных подтипа. Это статические значения, которые никогда не изменятся, и динамические, которые введены именно для того, чтобы отобразить определенное состояние. Так, если кнопка Button должна нести атрибут rounded - он может быть просто обозначен на элементе, без значения. А вот loading если будет указан, вне зависимости от значения, всегда будет отображать кнопку в которой крутиться лоадер.

HTML, Vue, Angular

html
<!-- 1. -->
<ds-button
  rounded
  text="Эта кнопка всегда будет крупной скругленной кнопкой!"
></ds-button>

<!-- 2. Все эти кнопки будут с лоадером: -->
<ds-button loading="true" text="..."></ds-button>
<ds-button loading="false" text="..."></ds-button>
<ds-button loading text="..."></ds-button>
  1. Здесь вам на помощь придет или дублирование разметки по условию, например на Vue:
vue
<ds-button v-if="loading" loading text="..."></ds-button>
<ds-button v-else text="..."></ds-button>
  1. Или "джаваскрипт на элементе по айди":
html
<ds-button id="button" text="Я твоя кнопка!"></ds-button>

<script>
  setTimeout(() => {
    const button = document.getElementById('button');
    if (button) {
      button.setAttribute('loading', 'true');

      setTimeout(() => {
        const button = document.getElementById('button');
        if (button) button.removeAttribute('loading'); // Вы должны именно удалить атрибут
      }, 1000);
    }
  }, 1000);
</script>

Во Вью второй способ будет работать без какой-либо дополнительной магии и принидительных перерендеров, даже в самых редких случаях - когда на элементе меняются сразу несколько логических параметров. В Агуляр, вам, вероятно, придется использовать какую-то свою привычную магию для перерендера, если не хотите дублировать разметку в условиях. Если логических атрибутов, которые могут часто динамически менятся, несколько, то вам следует построить небольшой стор, в виде хеш-таблицы в родительском компоненте. И каждый раз устанавливать все логические атрибуты при изменении какого-нибудь одного из них. Пример на Вью:

ts
import { defineComponent, ref } from 'vue';

export default defineComponent({
  setup() {
    // Стор
    const attrs = ref<{ [key: string]: boolean }>({});

    // Метод устанавливающий все динамические boolean атрибуты
    const setAttrs = () => {
      const button = document.getElementById('button');
      if (button) {
        for (const prop in attrs.value) {
          if (attrs.value[prop]) button.setAttribute(prop, `true`);
          else button.removeAttribute(prop);
        }
      }
    };

    // Метод устанавливающий атрибут loading
    const setLoading = (value: boolean) => {
      loading.value = value;
      attrs.value.loading = loading.value;
      setAttrs();
    };

    // Метод устанавливающий еще какой-нибудь атрибут
    const setProperty = (value: boolean) => {
      property.value = value;
      attrs.value.property = property.value;
      setAttrs();
    };
  },
});

React

В Реакт можно привязывать стор вокруг значения логического типа, но, в случае, если никакой другой входящий параметр отличного от логического типа не поменялся, а последний поменялся на false - элемент не будет отрендерен заново. Используйте динамический ключ:

js
import { useState } from 'react';

// React Components
import { Button } from '@rir/ds-library';

function Component() {
  const [key, setKey] = useState(0);
  const [loading, setLoading] = useState(false);

  // Устанавливаем значение, предположим, из строки в логический, который привязывается
  // И дергаем динамический ключ на комопоненте
  const setLoadingValue = (value: string) => {
    setLoading(value === 'true');
    setKey(key + 1);
  };

  return (
    <>
      <Button loading={loading} text="Текст кнопки" key={key}></Button>
    </>
  );
}

export default Component;

Дублирующие динамические логические литеральные атрибуты

Как видно из примеров выше - привязка булеан-атрибутов во фреймворках Реакт и Ангуляр может вызвать определенные затруднения (некоторые способы решения таких проблем - показаны). Но поэтому, Дизайн-Система также предоставляет простой альтернативный способ для того, чтобы быстро, удобно и надежно привязать логическое значение к пользовательскому элементу. Каждый динамический логический пропс можно использовать с подчеркиванием вначале, для того чтобы обвязать логический входящий тип через строковые литералы 'true' | 'false'. (Если дополнительный атрибут указан - его значение окажется в приоритете.)

В Реакт:

js
<Button _loading={loading ? 'true' : 'false'}></Button>

Ангуляр:

html
<ds-button [_loading]="loading ? 'true' : 'false'"></ds-button>

Это будет работать как простая привязка строкового типа, и способно экономить ваше время. Пользуйтесь документацией по компонентам чтобы точно определить возможность использования подобного дублирующего атрибута.

Атрибуты - динамические ключи

Иногда нам нужно вызвать определенное действие в компоненте, которое не связано ни с каким значением, напрмиер - установить фокус в поле ввода. Дизайн-Система позволяет сделать это с помощью изменения значения динамического ключа, в виде передачи в него новой строки (отличной от предыдущей):

html
<ds-input id="input" placeholder="placeholder"></ds-input>

<script>
  // Через 1 секунду устанавливаем фокус на поле ввода
  setTimeout(() => {
    const input = document.getElementById('input');
    if (input) input.setAttribute('focus', '1');
  }, 1000);
</script>

Адаптивность

Областью верстки, которая часто вызывает затруднения и приводит к ошибкам, является реализация адаптивного поведения шаблонов интерфейса. Гайдлайн и реализованные на нем графические прототипы обьявляют четыре различных типоразмера, описывающих разные размеры и форматы экранов:

  • Widest - большие мониторы - от 1360 пикселей шириной
  • Wide - небольшие мониторы - от 1024 до 1359 пикселей шириной
  • Middle - планшеты, "таблетки" - от 600 до 1023 пикселей шириной
  • Narrow - мобильные устройства, смартфоны, "наладонники" - до 599 пикселей

Дизайн-Система предоставляет следующие способы для обработки подобных требований:

  • Экспортирует простую утилитарную IIFE-функцию Screen.

  • Предоставляет компоненты Wrapper и Grid для создания стандартной обертки для контента ("тело сайта") и простых адаптивных сеток.

  • Предоставляет CSS с набором простых стилевых классов для быстрого адаптива через дублирование частей разметки. Описано дальше, в разделе CSS.

Функция Screen использует стандартный интерфейс для определения соответствия документа переданной строке медиавыражения в соответсвии с точками перехода типоразмеров, "брекпоинтами" обьявленными гайдлайном:

js
// Точки перехода в src/models/models.ts
const DESIGN = {
  BREAKPOINTS: {
    middle: 600,
    wide: 1024,
    widest: 1360,
  },
  // Остальные константы дизайна ...
};

// Экранный помощник в src/utils/screen.ts
export const Screen = (() => {
  const WIDESTS = DESIGN.BREAKPOINTS.widest;
  const WIDE = DESIGN.BREAKPOINTS.wide;
  const MIDDLE = DESIGN.BREAKPOINTS.middle;

  // Это любые мониторы?
  const isNotGadgets = () => {
    return window.matchMedia(`(min-width: ${WIDE}px)`).matches;
  };

  // Это большие мониторы?
  const isWidest = () => {
    return window.matchMedia(`(min-width: ${WIDESTS}px)`).matches;
  };

  // Это небольшие мониторы?
  const isWide = () => {
    return window.matchMedia(
      `(min-width: ${MIDDLE}px) and (min-width: ${WIDESTS - 1}px)`,
    ).matches;
  };

  // Это любые гаджеты?
  const isGadgets = () => {
    return window.matchMedia(`(max-width: ${WIDE - 1}px)`).matches;
  };

  // Это планшеты?
  const isMiddle = () => {
    return window.matchMedia(
      `(min-width: ${MIDDLE}px) and (min-width: ${WIDE - 1}px)`,
    ).matches;
  };

  // Это мобилки?
  const isNarrow = () => {
    return window.matchMedia(`(max-width: ${MIDDLE - 1}px)`).matches;
  };

  // Это любые экраны кроме мобилок?
  const isNotNarrow = () => {
    return window.matchMedia(`(min-width: ${MIDDLE}px)`).matches;
  };

  return {
    isNotGadgets,
    isWidest,
    isWide,
    isGadgets,
    isMiddle,
    isNarrow,
    isNotNarrow,
  };
})();

Давайте посмотрим на абстрактном примере, хорошо демонстрирующем, когда и как вам может быть полезна такая функция. Предположим, вы строите фронтенд на Vue3, и вам необходимо закрыть типоразмеры для планшетов и мобильных временной заглушкой, "пока макеты для них еще не готовы"))), временно показать верстку "только для декстопных браузеров". В компоненте "главного лейаута":

vue
<script lang="ts">
import { defineComponent, onMounted, Ref, ref } from 'vue';
import { Screen } from '@rir/ds-library';

export default defineComponent({
  setup() {
    const onWindowResize: () => void;
    const isDesktops: Ref<boolean> = ref(false);

    onMounted(() => {
      onWindowResize();
      window.addEventListener('resize', onWindowResize, false);
    });

    onWindowResize = () => {
      // Вызов функции Экранного Помощника
      // в обработчике повешенном на событие изменения размеров вьюпорта браузера
      isDesktops.value = Screen.isNotGadgets();
    };

    return {
      isDesktops,
    };
  },
});
</script>

<template>
  <div class="layout">
    <div v-if="isDesktops">
      <!-- Верстка ... -->
    </div>

    <!-- Заглушка -->
    <div v-else class="layout__gate">
      <div>Мобильная и планшетная версии в разработке!</div>
    </div>
  </div>
</template>

<style lang="stylus" scoped>
.layout
  // ...

  &__gate
    position fixed
    top 0
    left 0
    right 0
    bottom 0
    width 100vw
    height 100vh
    background #000
    display flex
    align-items center
    justify-content center
    text-aling center
</style>

Состояние

ts
// Состояние ДС
type TState = {
  theme: Themes.default | Themes.dark; // Цветовые темы оформления
};

В некоторых особенных случаях, вам может потребоваться состояние вашего экземпляра Дизайн-Системы. Например для того, чтобы показать некоторую модификацию разметки в зависимости от текущей темы, или даже от того - открыто или закрыто меню компонента Лейаута-дашборда (реальный пример требований от дизайна с одного из проектов). Сделать это можно тремя способами:

  • Перенеся инициализацию экземпляра ДС из точки входа в некий "верхний компонент", "лейаут", и вызваз на нем метод getState. Так вы получите сразу все поля состояния.

  • Наблюдая входной параметр на соответсвующем компоненте вы можете получить необходимый отдельный параметр. Например, для того чтобы получить актуальную тему, вам достаточно посмотреть значение пропса value на компоненте Theme.

  • Вы всегда можете сделать "ход конем", просто прочитав нужное вам поле из localStorage - состояние ДС - прокидывается через него:

js
// В переменную будет записано актуальное значение цветовой темы
const theme = localStorage.getItem('state:theme');

Пример первого варианта на Vue3:

vue
<script lang="ts">
import { defineComponent, onMounted } from 'vue';
import DS from '@rir/ds-library';

export default defineComponent({
  setup() {
    let ds: DS;

    onMounted(() => {
      ds = new DS({ isWC: true, isNormalize: true });
    });

    const getState = () => {
      console.log('Текущее состояние Дизайн-Системы: ', ds.getState());
    };
  },
});
</script>