Design

В наше время, создать собственное приложение уже не кажется чем-то сложным. Ещё несколько лет назад вам пришлось бы освоить две экосистемы, чтобы создать кроссплатформенное приложение. Сегодня, благодаря JavaScript и React Native, вы можете увидеть первый результат на экране смартфона (iOS и Android) уже через несколько минут после начала разработки.

О преимуществах и недостатках react native перед нативным приложением, можно поспорить, но эта статья не о том.

Итак, создать приложение легко (в каком-то смысле), а что насчёт дизайна? Как сделать так, чтобы приложение выглядело привлекательно?

Начнём с пары примеров

Для демонстрации, я подготовил два экрана, которые есть в большинстве приложений.

  • Экран аутентификации
  • Типовая форма

На примере этих экранов я покажу насколько просто переключаться между разными версиями оформления, мы будем использовать библиотеку uilib.

1. Исследование

В начале разработки всегда проводите исследование. В основе каждого удачного приложения лежат: цвет, типографика и сетка.

Стиль очень важен. Здесь я рекомендую сайты, которые всегда помогают мне найти вдохновение и идеи.

Pinterest — здесь всегда можно найти интересные идеи.

Muzli — лучший способ найти ресурсы для вдохновения.

Coolors — генератор цветовых схем.

Google Fonts — бесплатные шрифты.

Ищите подходящие идеи, сохраняйте всё, что вам покажется интересным. Нет смысла изобретать колесо.

2. Основы

После того, как вы определились с идеей и сформулировали чёткое представление того, как должно выглядеть приложение, пора приступать к его созданию.

Я уже упоминал, что в основе любого приложения лежит некая идея, и это выражается через UI. Реализовать вашу идею в коде поможет библиотека uilib.

Пятьдесят оттенков серого

Сперва цвет. У каждого приложения есть уникальная цветовая палитра, которая делает приложение узнаваемым. Например, Spotify — зелёный и черный, Pinterest — красный, Facebook — синий. Google — довольно «простой» (в смысле белый), но всё же, они используют синий, красный, зелёный и жёлтый цвета в своих продуктах.

Необязательно вводить много цвета. Я рекомендую использовать 5–8 оттенков тех цветов, которые вы выбрали. Это понадобится для отображения различных состояний (выключено, нажато и т.д).

Например, мы решили использовать #D64933 в качестве красного, тогда я бы ввёл несколько оттенков и назвал бы их red10, red20, …red50 соответственно (где red30 — основной оттенок #D64933), конечно, вы можете давать имена на своё усмотрение.

Далее, используя Colors.loadColors из библиотеки uilib, пишем такой код:

import {Colors} from 'react-native-ui-lib';

Colors.loadColors({
  red10: '#C37463',
  red20: '#BD6753',
  red30: '#B95A44',
  red40: '#A9523F',
  red50: '#984938',
});

Для вашего удобства, в uilib уже записаны наборы цветов с их оттенками. Кроме того, вам доступна полезная утилита для быстрого переключения оттенков.

Colors.getColorTint(Colors.red30, 50) // --> Colors.red50
Colors.getColorTint(Colors.blue10, 40) // --> Colors.blue40

Подбираем правильный шрифт

С типографикой чуть сложнее, каждый пресет определяет несколько значений: fontSize, fontWeight, fontFamily, lineHeight и другие.

Я рекомендую иметь 5–6 пресетов. Как и в случае с цветами, библиотека uilib, для быстрого старта предлагает набор типографики (text10, text20, …, text100). Вы также можете создать собственные наборы.

Typography.loadTypographies({
  header: {fontSize: 58, fontWeight: '300', lineHeight: 78},
  title: {fontSize: 46, fontWeight: '300', lineHeight: 64},
  body: {fontSize: 18, fontWeight: '400', lineHeight: 22},
});

Все пресеты включённые в библиотеку uilib можно использовать в качестве модификаторов.

3. Собираем всё вместе

Итак, у вас готова идея и определены пресеты, теперь настало время писать код.

Самый интуитивный способ внедрения — передавать значения непосредственно свойствам компонентов. Возьмём, к примеру компонент Button.

import {Button, Colors, BorderRadiuses} from 'react-native-ui-lib';

<Button 
  label="Button"
  backgroundColor={Colors.red30}
  color={Colors.white}
  borderRadius={BorderRadiuses.br20} />

Можно сделать ещё проще, используя модификаторы

<Button label="Button" bg-red30 white br20 />

В результате получим такую же кнопку.

Красная кнопка с белой надписью и радиусом равным 6 

Пока всё идёт гладко. Наша задача создать общий стиль, унифицировать вид и поведение нашего приложения. То есть скорее всего мы будем использовать этот стиль и для других кнопок в приложении. Но передавать эти свойства каждый раз, это не лучший способ. Нам поможет ThemeManager.

В ThemeManager мы собираем всё воедино, он помогает нам создать глобальный стиль или другими словами — «тему».

В нашем случае, код для применения стиля ко всем кнопкам в приложении будет выглядеть так:

import {ThemeManager} from 'react-native-ui-lib`;

ThemeManager.setComponentTheme('Button', {
  backgroundColor: Colors.red30,
  color: Colors.white,
  borderRadius: BorderRadiuses.br20,
});

Раз всё так легко, почему бы нам просто не переписать значения defaultProps в каждом компоненте? Здесь ThemeManager раскрывает нам всю свою мощь.

Давайте рассмотрим более сложный пример. У компонента Button есть свойство fullWidth, как видно по названию, оно позволяет отобразить кнопку в полную ширину экрана, без ограничения по ширине.

Что если мы хотим изменять вид кнопки, но только когда свойство fullWidth активно. Для этого используем ThemeManager.

В нашем примере «full-width кнопка» будет отображаться с белым фоном, красной надписью и красной окантовкой 2pt. Как на картинке:

Итак, мы снова в затруднительном положении. Мы можем начать передавать каждому экземпляру «широкой» кнопки соответствующие свойства, чтобы перезаписать дефолтные значения, но это не лучшее решение.

ThemeManager позволяет настраивать тему компонентов двумя способами:

  • Передавать объект (мы уже видели)
  • Передавать callback (сейчас увидим)

Передавая callback, у вас появляется возможность контролировать свойства, передаваемые компонентам во время выполнения. Callback принимает текущие свойства компонента (и его текущий контекст) и ожидает, что вы вернёте свойства, которые хотите изменить.

ThemeManager.setComponentTheme('Button', (props, context) => {

const themeProps = {
    backgroundColor: Colors.red30,
    color: Colors.white,
    borderRadius: BorderRadiuses.br20,
  };

if (props.fullWidth) {
    themeProps.backgroundColor = Colors.white;
    themeProps.color = Colors.red30;
    themeProps.outlineWidth = 2;
    themeProps.outlineColor = Colors.red30;
  }

return themeProps;
});

Здесь мы проверяем было ли передано свойство fullWidth, если да, то перезаписываем свойства (и добавляем другие) для изменения внешнего вида кнопки.

Вот и всё. Вся логика темы в одном месте. Как только вы настроили свою тему, вы можете легко изменить концепцию или вносить незначительные доработки для всего приложения.

Вернёмся к началу

Мы начали с примеров двух очень распространённых экранов: аутентификации и формы. Теперь, давайте наконец посмотрим на реализацию того, о чём мы говорили, в коде.

Экран формы:

import React, {Component} from 'react';
import {Colors, Card, View, Text, Button, TextField,RadioGroup, RadioButton} from 'react-native-ui-lib';

class FormScreen extends Component {
  render() {
      return (
        <View flex padding-20 centerV bg-screen>
          <Text title marginB-20 center white>EDIT DETAILS</Text>
          <Card padding-20>
            <View>
              <TextField title="FIRST NAME" />
              <TextField title="LAST NAME" />
              <TextField title="EMAIL ADDRESS" />

              <RadioGroup row marginT-10>
                {this.renderRadioButton('girl', 'Girl')}
                {this.renderRadioButton('boy', 'Boy')}
                {this.renderRadioButton('undefined', 'Undefined')}
              </RadioGroup>
            </View>
          </Card>
          <View marginT-20>
            <Button label="SAVE CHANGES" fullWidth />
          </View>
        </View>
      );
  }
}

Экран аутентификации:

import React, {Component} from 'react';
import {Colors, Card, View, Text, Button, TextField,RadioGroup, RadioButton} from 'react-native-ui-lib';

class LoginScreen extends Component {
  render() {
    return (
      <View flex centerV padding-20 bg-screen>
        <View>
          <Text header white center marginB-20>
            WELCOME
          </Text>
          <Card padding-20>
            <TextField title="EMAIL"/>
            <TextField title="PASSWORD" secureTextEntry />
            <Button label="LOGIN" marginT-20 />
            <Button text80 link label="Forgot your password?" marginT-10 />
          </Card>
        </View>
      </View>
    );
  }
}

Настройка Темы:

import {ThemeManager, Colors, Typography, BorderRadiuses} from 'react-native-ui-lib';

// We load here typographies presets
Typography.loadTypographies({
  header: {fontSize: 38, fontWeight: '500', lineHeight: 45},
  title: {fontSize: 26, fontWeight: '400', lineHeight: 36},
  buttonText: {fontSize: 18, fontWeight: '700'},
});

// We load here some color
Colors.loadColors({
  primary: Colors.red30,
  secondary: Colors.yellow30,
  screen: Colors.blue50,
});

// TextField component theme
ThemeManager.setComponentTheme('TextField', {
  underlineColor: Colors.secondary,
  titleColor: {focus: Colors.secondary},
  color: {focus: Colors.dark10, default: Colors.dark30},
});

// Button theme (using callback) 
ThemeManager.setComponentTheme('Button', (props) => {
  return {
    backgroundColor: Colors.primary,
    color: props.link ? Colors.secondary : Colors.white,
    buttonText: true,
  };
});

// RadioButton theme
ThemeManager.setComponentTheme('RadioButton', {
  color: Colors.secondary,
  size: 15,
});

Здесь мы реализовали оба экрана и настройку темы.

Далее, мы можем внести изменения, например сделать приложение монохромным.

Всё что нам нужно, это изменить конфигурацию темы.

import {ThemeManager, Colors, Typography, BorderRadiuses} from 'react-native-ui-lib';

Typography.loadTypographies({
  header: {fontSize: 30, fontWeight: '400', lineHeight: 45},
  title: {fontSize: 18, fontWeight: '500', lineHeight: 36},
  buttonText: {fontSize: 16, fontWeight: '500'},
});

Colors.loadColors({
  primary: Colors.purple30,
  secondary: Colors.purple80,
  title: Colors.purple30,
  header: Colors.purple30,
  screen: Colors.dark80,
});

ThemeManager.setComponentTheme('TextField', (props) => {
  return {
    style: {
      borderWidth: 2,
      borderColor: Colors.primary,
      padding: 8,
      backgroundColor: Colors.secondary,
      borderRadius: BorderRadiuses.br20,
    },
    hideUnderline: true,
    titleColor: {focus: Colors.primary},
    color: {focus: Colors.dark10, default: Colors.dark30},
  };
});

ThemeManager.setComponentTheme('Button', (props) => {
  const defaultProps = {
    backgroundColor: Colors.primary,
    color: props.link ? Colors.primary : Colors.white,
    buttonText: true,
    borderRadius: BorderRadiuses.br20,
  };

  if (props.fullWidth) {
    defaultProps.backgroundColor = Colors.secondary;
    defaultProps.outline = true;
    defaultProps.outlineColor = Colors.primary;
    defaultProps.outlineWidth = 3;
    defaultProps.color = Colors.primary;
  }

  return defaultProps;
});

ThemeManager.setComponentTheme('RadioButton', {
  color: Colors.primary,
  size: 18,
  borderRadius: BorderRadiuses.br10,
});

ThemeManager.setComponentTheme('Card', {
  containerStyle: {borderWidth: 2, borderColor: Colors.primary},
  borderRadius: BorderRadiuses.br20,
});

Вуаля, мы просто изменили несколько свойств и пресетов, но теперь у нас абсолютно другой вид UI.

Перевод статьи Ethan Sharabi: Design System for dummies, create your own flavor of React Native app in 3 easy steps