# Библиотека UI-компонентов

В StartupJs уже встроена полноценная UI-библиотека компонентов **@startupjs/ui**, которая основана на компонентах React Native. У вас есть возможность: использовать готовые компоненты или создавать свои (в том числе на основе готовых компонентов)


## Ваше первое полноценное приложение на StartupJs

Далее в этом разделе туториала мы будем шаг за шагом создавать полноценное приложение **TODO List**. Посмотрим как добавлять готовые компоненты на страницу, стилизовать их, создадим запрос в БД, получим документы из БД и будем их выводить на странице, научимся добавлять и удалять документы в БД и все это будет реактивно!

## Основные компоненты обёртки

В обычном HTML чаще всего мы используем тег `div` для обертки других компонентов в единую группу. В нашей библиотеке компонентов для этого используется компонент `Div`.

Т.к. под капотом **StartupJS** основан на React Native, то и все элементы по умолчанию имеют некоторые отличия от обычного web\`a. Среди них:
1. Все элементы изначально имеют css свойство `display: flex`
2. Все элементы `position: relative`
3. Выравнивание элементов во flex-контейнере по умолчанию `flex-direction: column`

> Вы можете не переопределять свойство `flex-direction` у компонента `Div` на `flex-direction: row`, а просто использовать готовый компонент `Row`


## Добавляем первый компонент

Давайте добавим первый компонент на страницу.
Это будет компонент `Content` из нашей UI-библиотеки.
`Content` используется в качестве родительского контейнера для элементов.

Сперва необходимо его импортировать в наш файл.
Введите в поле справа вместо строчки 111 следующий код:

```js
import { Content } from '@startupjs/ui'
```

и следующий код вместо строчки 888:

```js
<Content></Content>
```

> Прошу заметить, что UI-компоненты именуются с большой буквы, чтобы их можно было отличить от обычных HTML-тегов
> span !== Span

Далее добавим на страницу заголовок с названием нашего будущего приложения.
Для заголовка первого уровня используем компонент `H1`.

Сперва добавляем его в импорт:
```js
import { Content, H1 } from '@startupjs/ui'
```
затем добавляем заголовок в ранее добавленный `Content`
```js
<Content>
  <H1>TODO List</H1>
</Content>
```

После этого справа в поле предпросмотра вы увидите заголовок первого уровня.

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

Взглянем на [документацию компонента Content](/docs/components/Content#вертикальные-отступы). Видим, что для добавления отступов со всех четырех сторон, необходимо добавить свойство `padding` в значении `true`.

Добавим:
```js
<Content padding>
  <H1>TODO List</H1>
</Content>
```
Теперь появились отступы со всех сторон. Продолжаем...

---

## Добавляем стили

Как и в обычном HTML, в компоненте можно использовать атрибут `style`. Не стоит бояться использовать одинаковые классы в компонентах, потому что все компоненты компилируются независимо, используя `css-modules`.

Давайте добавим несколько стилей к элементу `<H1>`:

```js
<H1 style={{color: 'purple'}}>TODO List</H1>
```

Данный способ вполне легален, но далеко не предпочтителен.
В StartupJS для стилизации компонент используются классы, like [classnames](https://github.com/JedWatson/classnames).

Уберите проп `style` у компонента `H1` и добавьте вместо него проп `styleName`
```js
<H1 styleName={'title'}>TODO List</H1>
```
Таким образом вы дали заголовку H1 класс `title` и теперь в css-файле `index.css` вы можете обратиться к нему по селектору `.title` добавив ему нужные стили

```css
.title {
  color: purple;
}
```


> Среди прочего `styleName` принимает не только строку, но и массив или объект значений. Например:

```js
 const variant = 'primary'
 const color = 'warning'
 const disabled = true
 const active = false

 return (
   <Button styleName={[variant, color, { disabled, active }]} />
  )
```

## Добавляем компонент карточки

Далее нам необходимо нарисовать компонент карточки, в котором мы будем отображать одну нашу задачу.

Добавим к нашему импорту еще и `Card`:

```jsx
import { Content, H1, Card } from '@startupjs/ui'
```

И поместим его под наш заголовок, добавив в него случайный текст:

```jsx
<Content padding>
  <H1>TODO List</H1>
  <Card>Выучить StartupJS</Card>
</Content>
```
Как вы видите, данный код вызывает ошибку:
> Unexpected text node: Выучить StartupJS. A text node cannot be a child of a `<View>`.

Все из-за того, что в React Native нельзя просто помещать текст в компоненты, не обернув его в текстовый компонент `<Text>` или производных от него(напр. `<H1>`). В данном случае мы воспользуемся компонентом `Span`, он позволяет отображать текст(аналог `Text` из React Native)

```js
<Content padding>
  <H1>TODO List</H1>
  <Card>
    <Span>Выучить StartupJS</Span>
  </Card>
</Content>
```

> Не забудьте импортировать `Span` из `@startupjs/ui`


## Отображаем лист с TODO

Сейчас в нашей базе данных есть коллекция `todos` с несколькими готовыми задачами, давайте получим их из базы и выведем на странице, обернув каждую `todo` в компонент `Card`, используя для этого `Array.map()`

Сперва получим данные из базы данных. Для этого мы можем использовать кастомный хук `useQuery`, о нем подробно поговорим в углубленном курсе данного туториала. Если вкратце, то он первым аргументом принимает строку с именем коллекции, а вторым аргументом [mongodb like запрос](https://derbyjs.com/docs/derby-0.10/models/queries#mongodb-query-format) в виде объекта. Возвращает же он массив из двух элементов, первый геттер, второй сеттер.
Добавим в начало следующий код:

```jsx
const [todos, $todos] = useQuery('todos', {})
```
> Пустой объекта `{}` позволяет вернуть нам все документы из коллекции

Теперь у нас есть переменная `todos`, в которой хранится массив из `todo` в формате:
```js
[{
  id: 1,
  title: 'Изучить StartupJS',
  completed: false
}]
```

Осталось только вывести их на странице:

```jsx
const [todos, $todos] = useQuery('todos', {})

<Content padding>
  <H1>TODO List</H1>
    {todos.map(todo => (
      <Card key={todo.id}>
        <Span>{todo.title}</Span>
      </Card>
    ))}
</Content>
```

## Управление задачей

Теперь у нас на странице отображается список задач. Далее необходимо дать возможность отмечать задачи выполненными и возможность удалять их. Для этой цели нам понадобятся два новых частоиспользуемых компонента: `Checkbox` и `Button`, добавим их:
> Думаю, про импорт вам напоминать не надо

```jsx
const [todos, $todos] = useQuery('todos', {})

<Content padding>
  <H1>TODO List</H1>
    {todos.map(todo => (
      <Card key={todo.id}>
        <Span>{todo.title}</Span>
        <Checkbox />
        <Button />
      </Card>
    ))}
</Content>
```

Как вы можете заметить, теперь чекбокс и кнопка появились на странице, но разместились снизу под текстом нашей задачи. Давайте с помощью обёртки `Row` разместим текст, чекбокс и кнопку в один ряд и объединим чекбокс с кнопкой в одну обертку по аналогии:

```jsx
const [todos, $todos] = useQuery('todos', {})

<Content padding>
  <H1>TODO List</H1>
    {todos.map(todo => (
      <Card key={todo.id}>
        <Row align='between' vAlign='center'>
          <Span>{todo.title}</Span>
            <Row align='between' vAlign='center'>
              <Checkbox />
              <Button />
            </Row>
        </Row>
      </Card>
    ))}
</Content>
```

> `align='between' vAlign='center'` - пропсы из [документации компонента Row](/docs/components/Row), аналог `justify-content: space-between; align-items: center;`


## Управление состоянием компонента

Как и в обычном JS и React приложении вы можете управлять состоянием своих компонентов, но в **SturtupJS/UI** у вас для этого будут более удобные инструменты(API).

Начнем с ранее добавленого компонента `Checkbox`.

У чекбокса по [документации](/docs/forms/Checkbox) есть два основных пропса `value` и `onChange`.

`value` получает текущее булевое значение чекбокса, его мы можем сразу брать из массива полученного из базы.
`onChange` принимает коллбэк-функцию, которая вызывается при нажатии на чекбокс и возвращает его новое значение.

Давайте создадим функцию-коллбэк и добавим эти два пропса в наш чекбокс
> В примере ниже `$todos` является моделью коллекции todos, но сейчас вдаваться в эти подробности не имеет смысла. О том что такое модель и как с ней работать мы изучим далее в нашем курсе.

коллбэк принимает `id` нашей задачи и её новое значение, а затем сеттит значение в базу по `id` в поле `completed`

В `value` мы сразу прокидываем значение `completed`

```jsx
const changeStatus = (todoId, value) => $todos.at(todoId).set('completed', value)

<Checkbox
  value={todo.completed}
  onChange={value => changeStatus(todo.id, value)}
/>
```

Теперь клик по чекбоксу изменяет значение `completed` у задачи в нашей базе.

## Удаление задач
Как управлять состоянием мы узнали и чекбокс у нас закончен, осталось доделать кнопку удаления задачи из базы данных.

Добавим иконку нашей кнопке, покрасим её в красный цвет и сделаем небольшого размера.
Иконку необходимо заранее импортировать:

```jsx
import { faTimes } from '@fortawesome/free-solid-svg-icons'

<Button
  icon={faTimes}
  iconColor='error'
  size='s'
/>
```
> Про эти и другие пропсы компонента `Button` можете подробнее узнать в [документации](/docs/components/Button)


У `Button` также есть проп `onPress`. Он принимает коллбэк-функцию, которая вызывается при нажатии на кнопку. Здесь нам необходимо написать функцию, которая будет удалять определенную задачу из базы.

```js
const removeTodo = todoId => $todos.at(todoId).del()
```

> Ищем в моделе $todos задачу с нужным айди, а затем вызываем у нее метод удаления

```jsx
<Button
  icon={faTimes}
  iconColor='error'
  size='s'
  onPress={() => removeTodo(todo.id)}
/>
```

Теперь кнопка удаления задачи полностью функционирует.

## Форма добавления новой задачи

Наш задачник уже почти доделан, для полноценной работы не хватает только функционала добавления новых задач в список.
Для этого нам понадобится новый компонент `TextInput` и уже знакомый `Button`.

В `TextInput` мы будем вводить текст новой задачи, а `Button` будет добавлять задачу в базу данных.

Добавим два компонента к нам в файл:

```jsx
const [todos, $todos] = useQuery('todos', {})

<Content padding>
  <H1>TODO List</H1>
    {todos.map(todo => (
      <Card key={todo.id}>
        <Row align='between' vAlign='center'>
          <Span>{todo.title}</Span>
            <Row align='between' vAlign='center'>
              <Checkbox
                value={todo.completed}
                onChange={value => changeStatus(todo.id, value)}
              />
              <Button
                icon={faTimes}
                iconColor='error'
                size='s'
                onPress={() => removeTodo(todo.id)}
              />
            </Row>
        </Row>
      </Card>
    ))}
    <Row align='between' vAlign='center'>
      <TextInput />
      <Button>Add</Button>
    </Row>
</Content>
```

## Текстовое поле ввода

Согласно [документации](/docs/forms/TextInput) у TextInput для работы нам понадобятся два пропса: `value` и `onChangeText`.

И для работы с ним нам понадобится создать стейт с начальной пустой строкой:
```js
const [inputValue, setInputValue] = useState('')
```

Пропс `value` - будет принимать геттер стейта(`inputValue`)
Пропс `onChangeText` - принимает коллбэк-функцию, которую вызывает при изменении содержимого инпута. В нашем случае он будет просто принимать сеттер стейта (`setInputValue`)

```js
const [inputValue, setInputValue] = useState('')

<TextInput
  value={inputValue}
  onChangeText={setInputValue}
/>
```

Таким образом мы связали `TextInput` и локальный стейт

## Кнопка добавления новой задачи

Как мы уже знаем из предыдущих уроков, компонент `Button` принимает проп `onPress` в виде функции-коллбэка. В данном случае нам необходимо на кнопку повесить обработчик, который добавит новую задачу в базу данных. Если вкратце, функция должна взять `inputValue`, проверить его на пустоту, если поле пустое - выйти из функции, иначе добавить в `$todos` новую задачу и "очистить" инпут.

Вот как это будет выглядеть:

```jsx
const [inputValue, setInputValue] = useState('')

const handleSubmit = async () => {
  if (!inputValue) return
  await $todos.add({
       title: inputValue,
       completed: false
  })
  setInputValue('')
}

<Button onPress={handleSubmit}>Add</Button>
```

## TODO List готов!

Поздравляем, вы написали своё первое полноценное приложение с помощью возможностей StartupJS.

В дальнейших разделах туториала вы глубже познакомитесь с возможностями StartupJS.
