UI-kit в дизайн-системе
Компоненты дизайн-системы Apple
Реализация принципа модульности в дизайн-системе
Структура UI-kit WebValley Studio
Кнопки дизайн-системы WebValley Studio
Типографика дизайн-системы WebValley Studio
Отличие цветовой палитры в UI-Kit и дизайн-системе
Вариативность текстовых блоков дизайн-системы WebValley Studio
Иконки. Дизайн-система WebValley Studio
Поля ввода. Дизайн-система WebValley Studio
Валидация полей ввода. Дизайн-система WebValley Studio
Сетка и отступы дизайн-системы WebValley Studio
Ant Design. Еженедельно эту библиотеку скачивают 1,3 млн раз
Material UI. Еженедельных скачиваний — 3,2 млн
Chakra UI. 0,5 млн еженедельных скачиваний
Mantine UI. 235 тыс еженедельных скачиваний
Semantic UI React. 215 тыс еженедельных скачиваний
React Bootstrap. 2.3 млн. еженедельных скачиваний
Evergreen. 9 тыс еженедельных скачиваний
const path = require("path");
module.exports = {
mode: "production",
entry: "./src/index.ts",
output: {
filename: "index.js",
path: path.resolve(__dirname, "dist"),
libraryTarget: "umd",
clean: true,
},
resolve: {
extensions: [".ts", ".tsx"],
},
externals: {
react: "react",
},
module: {
rules: [
{
test: /\.css/,
use: ["style-loader", "css-loader"],
},
{
test: /\.(ts|tsx)?$/,
use: ["ts-loader"],
exclude: /node_modules/,
},
],
},
};
SSR не позволял нам отображать компоненты из дизайн системы, собранной webpack’ом. Решили попробовать использовать компилятор typescript, но столкнулись с проблемами подключения плагинов для SCSS и прочих хотелок. В итоге проблему удалось решить с использованием rollup. Он позволяет подключать все необходимые плагины точно также, как и webpack, но не конфликтует с Next и его SSR.
const typescript = require("@rollup/plugin-typescript");
const postcss = require("rollup-plugin-postcss");
const url = require("@rollup/plugin-url");
const svgr = require("@svgr/rollup");
const terser = require("@rollup/plugin-terser");
const dts = require("rollup-plugin-dts");
const packageJson = require("./package.json");
const peerDepsExternal = require("rollup-plugin-peer-deps-external");
const autoprefixer = require("autoprefixer");
const resolve = require("@rollup/plugin-node-resolve");
const commonjs = require("@rollup/plugin-commonjs");
module.exports = [
{
input: "src/index.ts",
output: [
{
file: packageJson.module,
format: "cjs",
sourcemap: true,
},
{
file: packageJson.main,
format: "esm",
sourcemap: true,
},
],
external: ["react-dom"],
plugins: [
pierDes External(),
resolve(),
commonjs(),
typescript({
tsconfig: "./tsconfig.json",
exclude: ["**/*.stories.tsx"],
}),
postcss({
extract: "index.css",
modules: true,
use: ["sass"],
minimize: true,
plugins: [autoprefixer()],
}),
url(),
svgr({ icon: true }),
terser(),
],
},
{
input: "dist/esm/types/index.d.ts",
output: [{ file: packageJson.types, format: "esm" }],
external: [/\.(css|scss)$/],
plugins: [dts.default()],
},
];
import React, { useState } from "react";
import { type StoryFn, type Meta } from "@storybook/react";
import { type IInputProps, Input } from "./Input";
const meta: Meta<typeof Input> = {
component: Input,
tags: ["autodocs"],
argTypes: {
value: {
control: {
disable: true,
},
},
},
};
export default meta;
const Template: StoryFun<Input Props> = (args) => {
const [localValue, setValue] = useState<string>(args.value ?? "");
return (
<Input
{...args}
onChange={(e) => {
args.onChange(e);
setValue(e.target.value);
}}
value={localValue}
/>
);
};
export const Default: StoryFn<IInputProps> = Template.bind({});
Default.args = {
name: "story_input",
value: "",
icon: null,
placeholder: "Placeholder*",
required: false,
success: false,
successMsg: "successMsg",
error: false,
errorMsg: "errorMsg",
disabled: false,
label: "inscription",
caption: "caption",
};
Типографика дизайн-системы WebValley Studio
Пример текста в Storybook. Дизайн система WebValley Studio
// Colors
//gray
$gray50: #f4f4f0;
$gray100: #bebe 6;
$gray200: #dbdbd7;
…
//yellow
$yellow50: #fff7d9;
$yellow100: #fff0ba;
$yellow200: #ffeba1;
…
//blue
$blue50: #ebf1fa;
$blue100: #c3d9fa;
$blue200: #a5c8fa;
…
src/
components/
[Название_компонента]/
[Название_компонента].ts
[Название_компонента].module.scss
[Название_компонента].stories.tsx
core/
assets/
fonts/
icons/
styles/
consts.scss
fonts.scss
…
import clsx from "clsx";
import block from "module-clsx";
import React, { type SyntheticEvent } from "react";
import style from "./LinkButton.module.scss";
import * as Icons from "../Icons";
export interface ILinkButtonProps
extends React.PropsWithChildren<
Omit<React.HTMLProps<HTMLAnchorElement>, "type" | "size">
> {
link: string;
icon?: keyof Icons.IIcons;
size?: "small" | "large";
underline?: boolean;
color?: "dark" | "light" | "yellow";
onClick?: (() => void) | ((e: SyntheticEvent) => void);
extraClass?: string;
}
export const LinkButton: React.FC<ILinkButtonProps> = ({
children,
icon,
link,
size = "large",
underline = false,
color = "dark",
onClick,
extraClass = "",
...rest
}) => {
const Icon = Icons[icon];
const type = icon && children ? "text-icon" : icon ? "only-icon" : "text";
const addHash = block(style);
const className = clsx(
addHash("button"),
`${size === "large" ? "button-m" : "button-s"}`,
{
[addHash(`button_color_${color}`)]: color,
},
{
[addHash(`button_underline_${underline}`)]: underline,
},
"manrope",
"fw-600",
"caps",
);
return (
<a href={link} className={className} {...rest}>
{type !== "only-icon" && <span>{children}</span>}
{type !== "text" ? (
size === "large" ? (
<Icon size="20" />
) : (
<Icon size="16" />
)
) : null}
</a>
);
};
children - текст кнопки;
icon - иконка, как ранее созданный компонент;
link - ссылка на какой-то адрес страницы для перехода;
size - размер;
underline - подчеркивание;
color - цвет;
onClick - обработчик клика;
extraClass - экстра класс, данный пропс есть в каждом нашем компоненте,
на случай назначения дополнительных стилей.
@import "../../core/styles";
//// ОБЩИЕ СТИЛИ НАЧАЛО
.button {
cursor: pointer;
text-decoration: none;
display: flex;
align-items: center;
width: fit-content;
@include allMediaRez("column-gap", 12);
transition: opacity 0.5s;
position: relative;
& span {
pointer-events: none;
}
& svg {
pointer-events: none;
}
&::before {
pointer-events: none;
content: "";
position: absolute;
left: 0;
@include allMediaRez("bottom", -8);
width: 100%;
@include allMediaRez("height", 3);
opacity: 0;
transition: opacity 0.5s;
}
&:hover {
span {
animation-name: btntext-in;
animation-duration: 0.5s;
}
}
&:not(:hover:not(:disabled)) {
span {
animation-name: btntext-out;
animation-duration: 0.5s;
}
}
}
//// ОБЩИЕ СТИЛИ КОНЕЦ
//// ЦВЕТА НАЧАЛО
//Темная
.button_color_dark {
color: $gray800;
& svg g path {
fill: $gray800;
}
&:focus {
&::before {
opacity: 1;
background-color: $yellow200;
}
}
&:active {
color: $gray600;
svg g path {
fill: $gray600;
}
}
}
// Светлая
.button_color_light {
color: $gray50;
& svg g path {
fill: $gray50;
}
&:focus {
&::before {
opacity: 1;
background-color: $yellow500;
}
}
&:active {
color: $yellow50;
svg g path {
fill: $yellow50;
}
}
}
// Желтая
.button_color_yellow {
color: $yellow500;
& svg g path {
fill: $yellow500;
}
&:focus {
&::before {
opacity: 1;
background-color: $yellow700;
}
}
&:active {
color: $yellow400;
svg g path {
fill: $yellow400;
}
}
}
//// ЦВЕТА КОНЕЦ
//// ПОДЧЕРКИВАНИЕ СТАРТ
.button_underline_true {
span {
position: relative;
&::after {
pointer-events: none;
content: "";
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 100%;
border-bottom-style: solid;
@include allMediaRez("border-bottom-width", 1);
transition: width 0.6s ease-in-out;
}
}
&:hover:not(:disabled) {
span {
&::after {
width: 0;
}
}
}
.button_color_dark {
span {
border-color: $gray800;
}
}
.button_color_light {
span {
border-color: $gray50;
}
}
.button_color_yellow {
span {
border-color: $yellow500;
}
}
}
//// Подчеркивание конец
Пример кнопки в Storybook. Дизайн система WebValley Studio.
"name": "@YOUR_GITHUB_USERNAME/YOUR_REPOSITORY_NAME",
"publishConfig": {
"registry": "https://npm.pkg.github.com/YOUR_GITHUB_USERNAME"
},
"main": "dist/esm/index.js",
registry=https://registry.npmjs.org/
@YOUR_GITHUB_USERNAME:registry=https://npm.pkg.github.com/
//npm.pkg.github.com/:_authToken=YOUR_AUTH_TOKEN