Основное
Гайдлайн
Значения атомов для передачи в атрибуты компонент Дизайн-Системы считываются с макетов в Figma. Визуализация гайдлайна.
Гайдлайн предоставляет типографику, скругления, непрозрачности, палитру и тени. Объявляет точки перехода типоразмеров и отступы в сетках, скорости анимаций.
Перечни токенов, атомов гайдлайна (кроме имен и допустимых для них размеров пиктограмм), можно найти в файле src/models/models.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):
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
<!-- 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>- Здесь вам на помощь придет или дублирование разметки по условию, например на Vue:
<ds-button v-if="loading" loading text="..."></ds-button>
<ds-button v-else text="..."></ds-button>- Или "джаваскрипт на элементе по айди":
<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>Во Вью второй способ будет работать без какой-либо дополнительной магии и принидительных перерендеров, даже в самых редких случаях - когда на элементе меняются сразу несколько логических параметров. В Агуляр, вам, вероятно, придется использовать какую-то свою привычную магию для перерендера, если не хотите дублировать разметку в условиях. Если логических атрибутов, которые могут часто динамически менятся, несколько, то вам следует построить небольшой стор, в виде хеш-таблицы в родительском компоненте. И каждый раз устанавливать все логические атрибуты при изменении какого-нибудь одного из них. Пример на Вью:
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 - элемент не будет отрендерен заново. Используйте динамический ключ:
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'. (Если дополнительный атрибут указан - его значение окажется в приоритете.)
В Реакт:
<Button _loading={loading ? 'true' : 'false'}></Button>Ангуляр:
<ds-button [_loading]="loading ? 'true' : 'false'"></ds-button>Это будет работать как простая привязка строкового типа, и способно экономить ваше время. Пользуйтесь документацией по компонентам чтобы точно определить возможность использования подобного дублирующего атрибута.
Атрибуты - динамические ключи
Иногда нам нужно вызвать определенное действие в компоненте, которое не связано ни с каким значением, напрмиер - установить фокус в поле ввода. Дизайн-Система позволяет сделать это с помощью изменения значения динамического ключа, в виде передачи в него новой строки (отличной от предыдущей):
<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 использует стандартный интерфейс для определения соответствия документа переданной строке медиавыражения в соответсвии с точками перехода типоразмеров, "брекпоинтами" обьявленными гайдлайном:
// Точки перехода в 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, и вам необходимо закрыть типоразмеры для планшетов и мобильных временной заглушкой, "пока макеты для них еще не готовы"))), временно показать верстку "только для декстопных браузеров". В компоненте "главного лейаута":
<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>Состояние
// Состояние ДС
type TState = {
theme: Themes.default | Themes.dark; // Цветовые темы оформления
};В некоторых особенных случаях, вам может потребоваться состояние вашего экземпляра Дизайн-Системы. Например для того, чтобы показать некоторую модификацию разметки в зависимости от текущей темы, или даже от того - открыто или закрыто меню компонента Лейаута-дашборда (реальный пример требований от дизайна с одного из проектов). Сделать это можно тремя способами:
Перенеся инициализацию экземпляра ДС из точки входа в некий "верхний компонент", "лейаут", и вызваз на нем метод
getState. Так вы получите сразу все поля состояния.Наблюдая входной параметр на соответсвующем компоненте вы можете получить необходимый отдельный параметр. Например, для того чтобы получить актуальную тему, вам достаточно посмотреть значение пропса
valueна компоненте Theme.Вы всегда можете сделать "ход конем", просто прочитав нужное вам поле из localStorage - состояние ДС - прокидывается через него:
// В переменную будет записано актуальное значение цветовой темы
const theme = localStorage.getItem('state:theme');Пример первого варианта на Vue3:
<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>