Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Как создать мультиплатформенную дизайн-систему на React

0a7aaf2d496a00240d39d877ab8dbb5e?s=47 Ilya Lesik
November 26, 2020
160

Как создать мультиплатформенную дизайн-систему на React

Доклад на HolyJS 2020 Moscow

Материалы

0a7aaf2d496a00240d39d877ab8dbb5e?s=128

Ilya Lesik

November 26, 2020
Tweet

Transcript

  1. Илья Лесик Как создать мультиплатформенную дизайн-систему на React 1 3FBDU

    EFTJHOTZTUFN
  2. 2 Software Engineer & Co-founder github.com/lessmess-dev @lessmessdev MFTTNFTTBHFODZ dev.to/lessmess

  3. 3 tver.io Community Leader _tverio TverIO

  4. React Figma react-figma.dev github.com/react-figma PO & Main contributor 4

  5. 5 5-%3 5-%3

  6. 6

  7. Веб Мобайл 7

  8. 8 Проблема Написали кучу кода на React под веб Понадобилось

    мобильное приложение
  9. 9 Сделали кучу экранов и компонентов на дизайне Трудно обеспечить

    консистентность Проблема
  10. 10 План доклада • Кроссплатформенность • Пример • Дизайн-системы •

    Процесс + Рекомендации • Будущие планы
  11. 11 Инструменты • Figma • React • React Native /

    react-native-web • React Figma • Storybook
  12. 12 Кроссплатформенность $SPTTQMBUGPSN

  13. 13

  14. 14 Можем использовать JS-код для хранения элементов дизайн-системы +4DPEF %FTJHO4ZTUFN

  15. 15 const designSystem = { fontSizes: [ 12, 14, 16,

    20, 24, 32 ], colors: { blue: '#07c', green:'#0fa', } }
  16. 16 Неплохо, но хотим переиспользовать элементы более высокого уровня IJHIMFWFM

  17. Кросс-платформенный React 17 3FBDU

  18. 18 Веб 8FC

  19. React DOM const Component = () => { return <div>Hello</div>

    } 19
  20. 20 Все остальное

  21. 21 • react-native

  22. • react-360 • react-tree-fiber 22 • Ink • react-hardware

  23. 23 • react-sketchapp • react-figma • react-xd • …

  24. 24 Почему React хорош для кросс-платформы? Правильные абстракции!

  25. const Reconciler = require('react-reconciler'); const HostConfig = { // You'll

    need to implement some methods here. // See below for more information and examples. }; const MyRenderer = Reconciler(HostConfig); const RendererPublicAPI = { render(element, container, callback) { // Call MyRenderer.updateContainer() to schedule changes on the roots. // See ReactDOM, React Native, or React ART for practical examples. } }; module.exports = RendererPublicAPI; react-reconciler 25
  26. 26

  27. 27

  28. 28 Кроссплатформенность возможна за счет: • JavaScript (почти везде есть

    интерпретатор) • React (react-reconciler) $SPTTQMBUGPSN
  29. Дизайн-системы 29 %FTJHO4ZTUFN

  30. Atomic Design by Brad Frost 30 От простого к сложному

  31. 31 Стайл-гайд + Визуальные элементы

  32. 32 Стайл-гайд 4UZMFHVJEF

  33. 33 Стайл-гайд — это набор базовых значений • Цвета •

    Типографика • Размеры/отступы 4UZMFHVJEF
  34. 34 gambit.gg - стайл-гайд

  35. 35 UI-кит 6*LJU

  36. 36 UI-кит — это набор визуальных элементов • Иконки •

    Кнопки • Инпуты • Тултипы • … 6*LJU
  37. 37 UI-кит использует стайл-гайд gambit.gg - UI-kit

  38. 38 UI-кит содержит разные состояния элементов

  39. 39 Setproduct Design System 2.0 - UI kit - Buttons

  40. 40 Setproduct Design System 2.0 - UI kit - Checkboxes

  41. 41 Картина целиком

  42. Каталог компонентов Шаблоны для Figma UI-кит Базовые компоненты 42 Стайл-гайд

  43. 43 Все это может быть в коде! $PEF По аналогии

    с Infrastructure as Code (IaC) Design System as Code
  44. 44 Но как отобразить дизайн-систему в Figma? 'JHNB

  45. Источник: figma.com/developers 45 Только на чтение Встраивание в iframe Обертки

    над REST API и Эмбедами
  46. 46 Figma Plugins API

  47. 47

  48. { "name": “Our design-system", "id": “…”, "api": "1.0.0", "main": "code.js",

    "ui": "ui.html" } 48 { "name": “Our design-system", "id": “…”, "api": "1.0.0", "main": "code.js", "ui": "ui.html" } manifest.json "name": “Our design-system"
  49. 49 Две точки входа

  50. 50 main Управление содержимым документа ui Интерфейс плагина Сообщения

  51. 51 В main-треде доступно Figma Plugins API Код можно писать

    на ES6 'JHNB1MVHJOT "1*
  52. 52 const page = figma.createPage(); const rect = figma.createRectangle(); rect.x

    = 10; rect.y = 10; rect.resize(50, 50); page.appendChild(rect);
  53. 53 Код можно бандлить — Webpack 8FCQBDL И использовать react-figma

    вместо чистого API
  54. 54 <Page> <Rectangle x={10} y={10} width={50} height={50} /> </Page> Тот

    же самый код, только на react-figma:
  55. 55 Преимущества react-figma перед Figma Plugins API • Декларативный подход

    • Кроссплатформенность + Yoga Layout • Все фичи React: хуки, стейт • DX: Гидратация, HMR, React DevTools • Интегрируется с множеством других инструментов
  56. 56 Примитивы react-figma SFBDUpHNB

  57. 57 <Page /> figma.createPage()

  58. 58 <Frame /> figma.createFrame() Основной блок Figma

  59. 59 <Rectangle /> figma.createRectangle()

  60. 60 <Component> … </Component> figma.createComponent()

  61. 61 <Star /> figma.createStar()

  62. 62 UI примитивы Совместимы с react-native / react-native-web / …

  63. 63 View — фундаментальный компонент для построения UI import React

    from "react"; import { View } from "react-native"; const ViewBoxesWithColor = () => { return ( <View style={{ flexDirection: "row", height: 100, padding: 20 }} > <View style={{ backgroundColor: "blue", flex: 0.3 }} /> <View style={{ backgroundColor: "red", flex: 0.5 }} /> </View> ); }; export default ViewBoxesWithColor;
  64. 64 Text — отображение текста import React from "react"; import

    { Text } from "react-native"; const SomeText = () => { return ( <Text> Hello, World! </Text> ); }; export default SomeText; Hello, World!
  65. 65 const App = () => ( <View style={styles.container}> <Text

    style={styles.title}>React Native</Text> </View> ); const styles = StyleSheet.create({ container: { flex: 1, padding: 24, backgroundColor: "#eaeaea" }, title: { marginTop: 16, paddingVertical: 8, borderWidth: 4, borderColor: "#20232a", borderRadius: 6, backgroundColor: "#61dafb", color: "#20232a", textAlign: "center", fontSize: 30, fontWeight: "bold" } }); StyleSheet — абстракция, похожая на CSS StyleSheets
  66. 66 Почему не Веб-based? Хотим писать <div className="alert" />

  67. 67 Проблемы: CSS ::after, ::before CSS ::hover, ::focus CSS Grid,

    etc. Обработка CSS
  68. 68 const SwatchTile = styled.View` height: 250px; width: 250px; border-radius:

    4px; margin: 4px; background-color: ${props => props.hex}; justify-content: center; align-items: center; ${props => (props.hex === '#ffffff' ? `border-width: 2px;\nborder-color: black;\nborder-style: solid;` : '')} `; Поддерживается styled-components github.com/react-figma/react-figma/blob/master/examples/styled-components/
  69. 69 • Стайлгайд — JS-конфиг • Базовые элементы — React

    (в react-native стиле) • Шаблоны для Figma и каталог - тоже в коде!
  70. 70 Пример? Primer! 1SJNFS

  71. 71

  72. 72 Интерфейс GitHub — на Primer

  73. 73 • UI-kit под Веб • Шаблоны Sketch/Figma • Octicons

    В Primer есть:
  74. 74 • UI-kit на react-native • Консистентности* Нет:

  75. 75 Нет задачи повторить полностью Сделаем кросс-платфороменный вариант

  76. 76 Создадим react-native проект + react-figma-boilerplate

  77. 77 ├── android - gradle project for React Native app

    ├── assets - React Native app assets ├── ios - iOS project for React Native app ├── src │ ├── components │ ├── App.tsx - React Figma app │ ├── code.tsx - entty point for Figma plugin Main-thead │ ├── ui.html - entty point for Figma plugin UI-thead │ └── ui.tsx ├── app.json - react-native config file ├── babel.config.js - babel config for react-native ├── figma.d.ts - figma plugin typings ├── figma.webpack.confgi.js - Webpack config for react-figma ├── manifest.json - Figma plugin manifest ├── metro.config.js - config for Metro bundler ├── package.json ├── tsconfig.json └── yarn.lock После генерации
  78. 78 Цвета $PMPST

  79. 79

  80. 80 export const colors = { gray: "#6a737d", blue: "#0366d6",

    green: "#28a745", purple: "#6f42c1", yellow: "#ffd33d", orange: "#f66a0a", red: "#d73a49", pink: "#ea4aaa", black: "#24292e", white: "#ffffff" };
  81. 81 Отобразим цвета в Figma

  82. import * as React from "react"; import {StyleSheet, Text, View}

    from "react-figma"; const styles = StyleSheet.create({ container: { alignItems: "center" }, rect: { width: 100, height: 100 }, text: { marginTop: 10 } }); import * as React from "react"; import {StyleSheet, Text, View} from "react-figma"; const styles = StyleSheet.create({ container: { alignItems: "center" }, rect: { width: 100, height: 100 }, text: { marginTop: 10 } }); 82 Стили rect: { width: 100, height: 100 },
  83. … export const StyleguideColor = (props) => { const {color,

    withBorder, name} = props; return <View name="Color container" style={[styles.container, props.style]}> <View style={[ styles.rect, {backgroundColor: color}, withBorder && {borderColor: "#c8c8c8", borderWidth: 1} ]} /> </View> }; 83 Компонент … export const StyleguideColor = (props) => { const {color, withBorder, name} = props; return <View name="Color container" style={[styles.container, props.style]}> <View style={[ styles.rect, {backgroundColor: color}, withBorder && {borderColor: "#c8c8c8", borderWidth: 1} ]} /> </View> }; {backgroundColor: color},
  84. import * as React from "react"; import {StyleSheet, Frame, View,

    Text} from "react-figma"; import {colors} from "../../tokens/colors"; import {StyleguideColor} from "../../components/styleguide-color/StyleguideColor"; export const Styleguide10 = (props) => { return <Frame name="Styleguide 1-0" style={[styles.frame, props.style]}> <View> <View style={styles.colorsContainer}> <StyleguideColor name="gray" color={colors.gray} style={styles.colorWrapper}/> <StyleguideColor name="blue" color={colors.blue} style={styles.colorWrapper}/> <StyleguideColor name="green" color={colors.green} style={styles.colorWrapper}/> <StyleguideColor name="purple" color={colors.purple} style={styles.colorWrapper}/> <StyleguideColor name="yellow" color={colors.yellow} style={styles.colorWrapper}/> <StyleguideColor name="orange" color={colors.orange} style={styles.colorWrapper}/> <StyleguideColor name="red" color={colors.red} style={styles.colorWrapper}/> <StyleguideColor name="pink" color={colors.pink} style={styles.colorWrapper}/> <StyleguideColor name="black" color={colors.black} style={styles.colorWrapper}/> <StyleguideColor name="white" color={colors.white} style={styles.colorWrapper}/> </View> </View> </Frame> } 84 import * as React from "react"; import {StyleSheet, Frame, View, Text} from "react-figma"; import {colors} from "../../tokens/colors"; import {StyleguideColor} from "../../components/styleguide-color/StyleguideColor"; export const Styleguide10 = (props) => { return <Frame name="Styleguide 1-0" style={[styles.frame, props.style]}> <View> <View style={styles.colorsContainer}> <StyleguideColor name="gray" color={colors.gray} style={styles.colorWrapper}/> <StyleguideColor name="blue" color={colors.blue} style={styles.colorWrapper}/> <StyleguideColor name="green" color={colors.green} style={styles.colorWrapper}/> <StyleguideColor name="purple" color={colors.purple} style={styles.colorWrapper}/> <StyleguideColor name="yellow" color={colors.yellow} style={styles.colorWrapper}/> <StyleguideColor name="orange" color={colors.orange} style={styles.colorWrapper}/> <StyleguideColor name="red" color={colors.red} style={styles.colorWrapper}/> <StyleguideColor name="pink" color={colors.pink} style={styles.colorWrapper}/> <StyleguideColor name="black" color={colors.black} style={styles.colorWrapper}/> <StyleguideColor name="white" color={colors.white} style={styles.colorWrapper}/> </View> </View> </Frame> } Собираем цвета на одном фрейме color={colors.gray} color={colors.blue} color={colors.green} color={colors.purple} color={colors.yellow} color={colors.orange} color={colors.red} color={colors.pink} color={colors.black} color={colors.white}
  85. 85 После того как добавим Figma плагин

  86. 86 Выбор из меню плагинов

  87. 87

  88. 88

  89. 89

  90. 90 export const colors = { // Gray gray: "#6a737d",

    gray000: "#6a737d", gray100: "#f6f8fa", gray200: "#e1e4e8", gray300: "#d1d5da", gray400: "#959da5", gray500: "#6a737d", gray600: "#586069", gray700: "#444d56", gray800: "#2f363d", gray900: "#24292e", ... }
  91. 91 Стайл-гайд Web CSS-in-JS react-native-web Mobile react-native Figma react-figma

  92. 92 Код становится single source of truth для дизайн-системы

  93. 93 0DUJDPOT 0DUJDPOT

  94. 94

  95. 95 Создадим компонент <Icon /> для иконки *DPO

  96. import * as React from "react"; import { SvgXml }

    from 'react-native-svg'; export const Icon = (props) => { const {src: source, height = 16, width, ratio = 1, ...otherProps} = props; return <SvgXml xml={source} height={height} width={width || Math.round(height * ratio)} {...otherProps} />; }; 96 import * as React from "react"; import { SvgXml } from 'react-native-svg'; export const Icon = (props) => { const {src: source, height = 16, width, ratio = 1, ...otherProps} = props; return <SvgXml xml={source} height={height} width={width || Math.round(height * ratio)} {...otherProps} />; }; iOS + Android react-native-svg
  97. import * as React from "react"; export const Icon =

    (props) => { const {src, height = 16, width, ratio = 1, ...otherProps} = props; return <img src={src} style={{height, width: width || ratio * height}} {...otherProps} /> }; 97 Web import * as React from "react"; export const Icon = (props) => { const {src, height = 16, width, ratio = 1, ...otherProps} = props; return <img src={src} style={{height, width: width || ratio * height}} {...otherProps} /> }; <img src={src}
  98. import * as React from "react"; import {Svg} from "react-figma";

    export const Icon = (props) => { const {src: source, height = 16, width, ratio = 1, ...otherProps} = props; return <Svg source={source} height={height} width={width || Math.round(height * ratio)} {...otherProps} /> }; 98 import * as React from "react"; import {Svg} from "react-figma"; export const Icon = (props) => { const {src: source, height = 16, width, ratio = 1, ...otherProps} = props; return <Svg source={source} height={height} width={width || Math.round(height * ratio)} {...otherProps} /> }; Figma import {Svg} from "react-figma"; figma.createNodeFromSvg(svg: string)
  99. 99 Создадим компонент под каждую иконку $PNQPOFOUT

  100. 100 icons/logo-github.svg

  101. import * as React from "react"; import icon from "./icons/logo-github.svg"

    import {Icon} from "../../wrappers/icon/Icon"; export const GitHubLogo = (props) => { return <Icon ratio={45.0/16} src={icon} {...props} /> }; 101 import * as React from "react"; import icon from "./icons/logo-github.svg" import {Icon} from "../../wrappers/icon/Icon"; export const GitHubLogo = (props) => { return <Icon ratio={45.0/16} src={icon} {...props} /> }; import icon from "./icons/logo-github.svg" src={icon}
  102. 102 Покажем все иконки на фрейме *DPOT 'SBNF

  103. <View style={{marginTop: 69}}> <StyleguideSeparatorWrapper> <StyleguideLabel text="Logos" /> </StyleguideSeparatorWrapper> <View style={styles.iconsLine}>

    <Component name="logo-gist-5" style={styles.iconComponent5}> <GistLogo height={spacer5} /> </Component> <Component name="logo-github-5" style={styles.iconComponent5}> <GitHubLogo height={spacer5} /> </Component> <Component name="logo-github-mark-5" style={styles.iconComponent5}> <GitHubMark height={spacer5} /> </Component> <Component name="logo-markdown-5" style={styles.iconComponent5}> <Markdown height={spacer5} /> </Component> <Component name="logo-octoface-5" style={styles.iconComponent5}> <Octoface height={spacer5} /> </Component> <Component name="logo-paintcan-5" style={styles.iconComponent5}> <Paintcan height={spacer5} /> </Component> </View> <View style={styles.iconsLine}> <Component name="logo-gist-3" style={styles.iconComponent3}> <GistLogo height={spacer3} /> </Component> <Component name="logo-github-3" style={styles.iconComponent3}> <GitHubLogo height={spacer3} /> 103 <View style={{marginTop: 69}}> <StyleguideSeparatorWrapper> <StyleguideLabel text="Logos" /> </StyleguideSeparatorWrapper> <View style={styles.iconsLine}> <Component name="logo-gist-5" style={styles.iconComponent5}> <GistLogo height={spacer5} /> </Component> <Component name="logo-github-5" style={styles.iconComponent5}> <GitHubLogo height={spacer5} /> </Component> <Component name="logo-github-mark-5" style={styles.iconComponent5}> <GitHubMark height={spacer5} /> </Component> <Component name="logo-markdown-5" style={styles.iconComponent5}> <Markdown height={spacer5} /> </Component> <Component name="logo-octoface-5" style={styles.iconComponent5}> <Octoface height={spacer5} /> </Component> <Component name="logo-paintcan-5" style={styles.iconComponent5}> <Paintcan height={spacer5} /> </Component> </View> <View style={styles.iconsLine}> <Component name="logo-gist-3" style={styles.iconComponent3}> <GistLogo height={spacer3} /> </Component> <Component name="logo-github-3" style={styles.iconComponent3}> <GitHubLogo height={spacer3} /> <Component name="logo-octoface-5" style={styles.iconComponent5}> <Octoface height={spacer5} /> </Component>
  104. 104

  105. 105

  106. 106 Что-то более сложное? Кнопка! #VUUPO #VUUPO

  107. 107

  108. 108

  109. 109

  110. 110 Удобнее всего начать писать код на react-native API react-native

    поддерживается на остальных платформах Но не наоборот!
  111. 111 Базовые стили для кнопки CBTFTUZMFT

  112. 112 export const commonButtonStyle = { container: { justifyContent: "center",

    alignItems: "center", height: 32, borderRadius: 3, borderWidth: 1, borderColor: "rgba(27,31,35,0.2)", }, text: { fontFamily: "SF Pro Text", fontWeight: "bold", fontSize: typeScale.size5, textAlign: "center", zIndex: 1, marginLeft: 12 + borderSize, marginRight: 12 + borderSize } };
  113. 113 export const commonButtonSmallStyle = StyleSheet.create({ container: { height: 28

    }, text: { fontSize: typeScale.size6, lineHeight: 20, marginLeft: 10 + borderSize, marginRight: 10 + borderSize } });
  114. 114 Default Button %FGBVMU #VUUPO

  115. const styles = StyleSheet.create({ container: { backgroundColor: colors.gray100, backgroundImage: `linear-gradient(-180deg,

    ${colors.gray100} 0%, ${colors.gray300} 90%)`, }, text: { color: colors.gray900, } }); 115 const styles = StyleSheet.create({ container: { backgroundColor: colors.gray100, backgroundImage: `linear-gradient(-180deg, ${colors.gray100} 0%, ${colors.gray300} 90%)`, }, text: { color: colors.gray900, } }); colors.gray100 colors.gray100 colors.gray300 colors.gray900 Пользуемся значениями из стайл-гайда
  116. export const DefaultButton = (props) => { const { style,

    children, isHover, isFocus, isSmall } = props; return ( <View style={[ commonButtonStyle.container, styles.container, isHover && hoverStyles.container, isFocus && focusStyles.container, isSmall && commonButtonSmallStyle.container, style, ]} > <Text style={[ commonButtonStyle.text, styles.text, isSmall && commonButtonSmallStyle.text, ]} > {children} </Text> </View> ); }; 116 export const DefaultButton = (props) => { const { style, children, isHover, isFocus, isSmall } = props; return ( <View style={[ commonButtonStyle.container, styles.container, isHover && hoverStyles.container, isFocus && focusStyles.container, isSmall && commonButtonSmallStyle.container, style, ]} > <Text style={[ commonButtonStyle.text, styles.text, isSmall && commonButtonSmallStyle.text, ]} > {children} </Text> </View> ); }; isHover, isFocus, isSmall isHover && hoverStyles.container, isFocus && focusStyles.container, isSmall && commonButtonSmallStyle.container,
  117. 117 Покажем разные состояния кнопки на фрейме

  118. <View style={styles.buttonsLine}> <Component name="button-default"> <DefaultButton>Button</DefaultButton> </Component> <Component name="button-default-hover" style={styles.buttonMargin}> <DefaultButton

    isHover >Hovered button</DefaultButton> </Component> <Component name="button-default-focus" style={styles.buttonMargin}> <DefaultButton isFocus >Focused button</DefaultButton> </Component> <Component name="button-default-small" style={styles.buttonMargin}> <DefaultButton isSmall >Small button</DefaultButton> </Component> </View> 118 <View style={styles.buttonsLine}> <Component name="button-default"> <DefaultButton>Button</DefaultButton> </Component> <Component name="button-default-hover" style={styles.buttonMargin}> <DefaultButton isHover >Hovered button</DefaultButton> </Component> <Component name="button-default-focus" style={styles.buttonMargin}> <DefaultButton isFocus >Focused button</DefaultButton> </Component> <Component name="button-default-small" style={styles.buttonMargin}> <DefaultButton isSmall >Small button</DefaultButton> </Component> </View> isHover isFocus isSmall <Component <Component <Component <Component
  119. 119

  120. 120

  121. 121 Можем использовать ту же самую кнопку в вебе! И

    в мобильных приложениях
  122. 122 Но: • Нужна обработка нажатия - она разная на

    разных платформах • Нужно обеспечить доступность (a11y) Для этого сделаем компонент-обертку!
  123. 123 const style = { background: "none", border: "none", padding:

    0 }; export default function Button(props: ButtonProps) { const {children, onClick} = props; const [isHover, hoverHandlers] = useHover(); const [isFocus, focusHandlers] = useFocus(); return <button style={style} onClick={onClick} {...hoverHandlers} {...focusHandlers}> {children({isHover, isFocus})} </button> } Веб
  124. 124 export default function Button(props: ButtonProps) { const {children, onClick}

    = props; const [isFocus, focusHandlers] = useFocus(); return <TouchableHighlight onPress={onClick} {...focusHandlers}> {children({isFocus})} </TouchableHighlight>; } iOS
  125. 125 export default function Button(props: ButtonProps) { const {children, onClick}

    = props; const [isFocus, focusHandlers] = useFocus(); return <TouchableNativeFeedback onPress={onClick} {...focusHandlers}> {children({isFocus})} </TouchableNativeFeedback>; } Android
  126. 126 Хотим оборачивать удобно

  127. 127

  128. 128 iOS и Android Это работает с коробки!

  129. 129 Для всего остального - Вебпак-алиасинг!

  130. 130 Веб module.exports = configure({ resolve: { extensions: ['.web.tsx', ‘.web.ts','.tsx',

    '.ts', '.jsx', '.js'], alias: { 'react-native$': ‘react-native-web’ } } });
  131. 131 module.exports = configure({ resolve: { extensions: ['.figma.tsx', '.figma.ts','.tsx', '.ts',

    '.jsx', '.js'], alias: { 'react-native$': 'react-figma' } } }); Figma
  132. import Button from "../../wrappers/button/Button"; export default (props: { onClick?: ()

    => void }) => ( <Button onClick={props.onClick}> {({ isHover, isFocus }) => ( <DefaultButton {...props} isFocus={isFocus} isHover={isHover} /> )} </Button> ); 132 import Button from "../../wrappers/button/Button"; export default (props: { onClick?: () => void }) => ( <Button onClick={props.onClick}> {({ isHover, isFocus }) => ( <DefaultButton {...props} isFocus={isFocus} isHover={isHover} /> )} </Button> ); import Button from "../../wrappers/button/Button"; <Button onClick={props.onClick}> </Button>
  133. 133 Каталог компонентов

  134. 134

  135. 135 • @storybook/react • @storybook/react-native • @storybook/addons

  136. 136 ├── .storybook - @storybook/react configuration ├── .storybook-native - @storybook/react-native

    configuration ├── android - gradle project for React Native app ├── assets - React Native app assets ├── ios - iOS project for React Native app ├── src │ ├── components │ ├── App.tsx - React Figma app │ ├── code.tsx - entty point for Figma plugin Main-thead │ ├── ui.html - entty point for Figma plugin UI-thead │ └── ui.tsx ├── app.json - react-native config file ├── babel.config.js - babel config for react-native ├── figma.d.ts - figma plugin typings ├── figma.webpack.confgi.js - Webpack config for react-figma ├── manifest.json - Figma plugin manifest ├── metro.config.js - config for Metro bundler ├── package.json ├── tsconfig.json └── yarn.lock ├── .storybook - @storybook/react configuration ├── .storybook-native - @storybook/react-native configuration ├── android - gradle project for React Native app ├── assets - React Native app assets ├── ios - iOS project for React Native app ├── src │ ├── components │ ├── App.tsx - React Figma app │ ├── code.tsx - entty point for Figma plugin Main-thead │ ├── ui.html - entty point for Figma plugin UI-thead │ └── ui.tsx ├── app.json - react-native config file ├── babel.config.js - babel config for react-native ├── figma.d.ts - figma plugin typings ├── figma.webpack.confgi.js - Webpack config for react-figma ├── manifest.json - Figma plugin manifest ├── metro.config.js - config for Metro bundler ├── package.json ├── tsconfig.json └── yarn.lock ├── .storybook - @storybook/react configuration ├── .storybook-native - @storybook/react-native configuration
  137. 137 import * as React from 'react'; import {storiesOf} from

    "@storybook/react-native"; import {boolean, withKnobs, text} from "@storybook/addon-knobs"; import {defaultBackground} from "../storybook-decorators/DefaultBackground"; import { action } from '@storybook/addon-actions'; import DefaultButton from "../default-button/DefaultButton"; storiesOf('Button', module) .addDecorator(withKnobs) .addDecorator(defaultBackground) .add('Default Button', () => <DefaultButton onClick={action('click')} isSmall={boolean("isSmall", false)}> {text("children", "Danger Button")} </DefaultButton> );
  138. 138

  139. 139

  140. 140 Тот же самый код будет работать и на react-native

  141. 141 iOS-симулятор

  142. 142 Композиция элементов DPNQPTJUJPO FMFNFOUT

  143. 143

  144. 144 export const DefaultButton = (props: IDefaultButton) => { const

    { style, children, isHover, isFocus, isSmall, icon } = props; return ( <View style={[ styles.container, isHover && hoverStyles.container, isFocus && focusStyles.container, isSmall && commonButtonSmallStyle.container, style, ]} > {icon} <Text style={[styles.text, isSmall && commonButtonSmallStyle.text]}> {children} </Text> </View> ); }; export const DefaultButton = (props: IDefaultButton) => { const { style, children, isHover, isFocus, isSmall, icon } = props; return ( <View style={[ styles.container, isHover && hoverStyles.container, isFocus && focusStyles.container, isSmall && commonButtonSmallStyle.container, style, ]} > {icon} <Text style={[styles.text, isSmall && commonButtonSmallStyle.text]}> {children} </Text> </View> ); }; icon {icon}
  145. 145 import {DefaultButton} from "./components/default-button/DefaultButton"; import {RepoForked} from "./components/icons/RepoForked"; const

    App = () => { return ( <Page> <DefaultButton icon={<RepoForked style={{marginRight: 6}} />}> Fork </DefaultButton> </Page> ); };
  146. 146 Отобразим в Figma:

  147. 147 Отобразим в Storybook .add('Fork Button', () => <DefaultButton icon={<RepoForked

    style={{marginRight: 6}} />}> Fork </DefaultButton>);
  148. 148 Отобразим в Storybook

  149. 149 Таким образом можно собирать сколь угодно сложные элементы

  150. 150 Полный код примера есть на GitHub: github.com/react-figma/PrimerDemo

  151. 151 Сделали пример мульти-платформенной дизайн системы • Стайл-гайд в коде

    • UI-кит в коде • Отображение в Figma • Каталог компонентов в Storybook
  152. 152 Процесс QSPDFTT

  153. 153 Интуитивный процесс JOUVJUJWF QSPDFTT

  154. 154

  155. 155 Хочется автоматизировать процесс

  156. 156

  157. 157 Но у нас другой подход BQQSPBDI

  158. 158

  159. 159

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

    чуда
  161. 161 • Генераторы кода пока слишком не совершенны • Лучший

    код можно написать только вручную • Можно положить дизайн-систему в VCS • Консистентность
  162. 162 Но подход усложняется

  163. 163

  164. 164 А если хотим внести изменения в дизайн-систему?

  165. 165 Рисует новый вариант Вносит изменения Используют исправленную дизайн-систему

  166. 166 При этом возможно совмещение роли дизайнера и разработчика

  167. 167 Код дизайн-системы понятный Дизайнеры могут делать в нее MR

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

  169. 169 Дизайн-система … Продукт 1 Продукт 2 Продукт 3 Продукт

    4
  170. 170 Свои преимущества: • Проще управлять процессом внесения изменений в

    дизайн-систему • Безопасность: код находится у вас • Проще запускать новые продукты
  171. 171 Рекомендации SFDPNNFOEBUJPO

  172. 172 Для стайл-гайда лучше использовать System UI Theme Specification system-ui.com/theme

  173. 173 Универсальный формат конфига стайл-гайда

  174. 174 const theme = { breakpoints: ['40em', '52em', '64em'], fontSizes:

    [ 12, 14, 16, 20, 24, 32, 48, 64 ], colors: { blue: '#07c', lightgray: '#f6f6ff' }, space: [ 0, 4, 8, 16, 32, 64, 128, 256 ], fonts: { body: 'system-ui, sans-serif', heading: 'inherit', monospace: 'Menlo, monospace', }, fontWeights: { body: 400, heading: 700, bold: 700, }, buttons: { primary: { color: 'white', bg: 'primary', } } } Определяем тему
  175. 175 <Box p={5} fontSize={4} width={[ 1, 1, 1/2 ]} color='white'

    bg='primary'> Box </Box> Пользуемся значениями темы
  176. 176 Theme Specification совместим со множеством библиотек

  177. 177 + Темизация из коробки

  178. 178 • react-primitives-box — универсальная реализация

  179. 179 const theme = { colors: { text: "#000", background:

    "#fff", primary: "#07c", }, space: [0, 4, 8, 16, 32, 64, 128, 256], fonts: { roboto: "Roboto", }, fontSizes: [12, 16, 18, 20, 24, 32, 40, 64, 96], text: { heading1: { fontFamily: "roboto", fontSize: 5, color: "background", }, }, };
  180. <ThemeProvider theme={theme}> <Box sx={{ p: 4, bg: "primary", }} >

    <Text variant={"heading1"}>Hello</Text> </Box> </ThemeProvider> 180 <ThemeProvider theme={theme}> <Box sx={{ p: 4, bg: "primary", }} > <Text variant={"heading1"}>Hello</Text> </Box> </ThemeProvider> theme={theme}
  181. 181

  182. 182 $PNQPOFOU7BSJBOUT $PNQPOFOU 7BSJBOUT

  183. 183 Разные состояния можно объединять во варианты

  184. 184

  185. 185 .%9 .%9 .%9

  186. 186

  187. 187 # Hello, world! MDX is an authorable format that

    lets you seamlessly write JSX in your Markdown documents. It allows for displaying something unique from your design system. E.g., colors: <Row> <Color value="#6A737D" /> <Color value="#0366D6" /> <Color value="#28A745" /> <Color value="#6F42C1" /> <Color value="#FFD33D" /> <Color value="#F66A0A" /> <Color value="#D73A49" /> </Row>
  188. 188 В Figma:

  189. 189 Дальнейшие планы QMBOT

  190. 190 Набор универсальных компонентов Button SVG … DPNQPOFOUT Для стандартных

    вещей
  191. 191 Адаптер react-figma для Storybook SFBDUpHNB 4UPSZCPPL

  192. storiesOf('Button', module) .addDecorator(withKnobs) .addDecorator(defaultBackground) .add('Default Button', () => <DefaultButton onClick={action('click')}

    isSmall={boolean("isSmall", false)}> {text("children", "Danger Button")} </DefaultButton> ); 192 storiesOf('Button', module) .addDecorator(withKnobs) .addDecorator(defaultBackground) .add('Default Button', () => <DefaultButton onClick={action('click')} isSmall={boolean("isSmall", false)}> {text("children", "Danger Button")} </DefaultButton> ); storiesOf('Button', module) .addDecorator(defaultBackground) Добавить фрейм
  193. storiesOf('Button', module) .addDecorator(withKnobs) .addDecorator(defaultBackground) .add('Default Button', () => <DefaultButton onClick={action('click')}

    isSmall={boolean("isSmall", false)}> {text("children", "Danger Button")} </DefaultButton> ); 193 storiesOf('Button', module) .addDecorator(withKnobs) .addDecorator(defaultBackground) .add('Default Button', () => <DefaultButton onClick={action('click')} isSmall={boolean("isSmall", false)}> {text("children", "Danger Button")} </DefaultButton> ); Добавить в UI плагина контролы action('click') boolean("isSmall", false)
  194. 194 Респонсив SFTQPOTJWF SFTQPOTJWF

  195. 195 В react-native есть Dimensions API Все тянется за счет

    Yoga, но
  196. 196

  197. 197

  198. 198 Кодогенерация DPEF HFOFSBUJPO

  199. 199 github.com/react-figma/code-generator

  200. 200

  201. 201 github.com/bernaferrari/FigmaToCode

  202. 202

  203. 203

  204. 204 Сложности Этап конвертации Figma-дерева в AST Как работать с

    изменениями (например, props у компонентов) Все хотят разного
  205. 205 0QFO"*(15 (15 (15

  206. 206

  207. 207 Мы открыты для: Новых контрибуторов Для early adopters 8FMDPNF

  208. 208 Выводы Пишите кроссплатформенно и процветайте! У хранения дизайн-системы в

    коде есть преимущества Принцип от простого - к сложному
  209. 209 Спасибо за внимание! 5IBOLT

  210. 210 Особая благодарность ,VEPT

  211. 211 MFTJLEFW @ilialesik @ilyalesik ilyalesik/multiplatform-design-systems-talk