diff --git a/apps/web-client/package.json b/apps/web-client/package.json
index e528298..e70dcf6 100644
--- a/apps/web-client/package.json
+++ b/apps/web-client/package.json
@@ -15,6 +15,7 @@
"@linaria/core": "^3.0.0-beta.13",
"@loadable/component": "^5.15.0",
"@witb/webpack-config": "0.1.0",
+ "@witb/uikit": "0.1.0",
"prop-types": "^15.7.2",
"react": "^18.0.0-beta-96ca8d915-20211115",
"react-router-dom": "^6.0.1"
diff --git a/apps/web-client/src/App.js b/apps/web-client/src/App.js
index 3a714de..eba5fee 100644
--- a/apps/web-client/src/App.js
+++ b/apps/web-client/src/App.js
@@ -3,6 +3,8 @@ import { ResponsePropType } from "@witb/webpack-config/utils";
import loadable from "@loadable/component";
import { Routes, Route } from "react-router-dom";
+import "./global.css";
+
const Error404Page = loadable(() => import("./pages/Error404Page"));
const HomePage = loadable(() => import("./pages/HomePage"));
diff --git a/apps/web-client/src/components/Container.js b/apps/web-client/src/components/Container.js
new file mode 100644
index 0000000..0d63d51
--- /dev/null
+++ b/apps/web-client/src/components/Container.js
@@ -0,0 +1,28 @@
+import React from "react";
+import PropTypes from "prop-types";
+import { css } from "@linaria/core";
+import { variables, apply, dark } from "../theme/schemes.js";
+
+const Container = ({ children }) =>
{children}
;
+
+Container.defaultProps = {
+ children: null,
+};
+
+Container.propTypes = {
+ children: PropTypes.node,
+};
+
+export default Container;
+
+const className = css`
+ align-self: center;
+ margin: 0 auto;
+ max-width: 600px;
+ width: calc(100% - 24px);
+
+ ${apply(dark)}
+
+ background: ${variables.background};
+ color: ${variables.text};
+`;
diff --git a/apps/web-client/src/components/Grid.js b/apps/web-client/src/components/Grid.js
new file mode 100644
index 0000000..37655bd
--- /dev/null
+++ b/apps/web-client/src/components/Grid.js
@@ -0,0 +1,66 @@
+import React from "react";
+import PropTypes from "prop-types";
+import { css, cx } from "@linaria/core";
+import { createModifiers, modifierPropType } from "../theme/breakpoints.js";
+
+const Grid = ({ children, direction, size, spacing }) => (
+
+ {children}
+
+);
+
+Grid.defaultProps = {
+ children: null,
+ direction: "vertical",
+ size: null,
+};
+
+Grid.propTypes = {
+ children: PropTypes.node,
+ direction: modifierPropType(PropTypes.oneOf(["horizontal", "vertical"])),
+ size: modifierPropType(
+ PropTypes.oneOf([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]),
+ ),
+};
+
+export default Grid;
+
+const modifiers = createModifiers({
+ // Direction
+ direction: {
+ horizontal: `flex-direction: row`,
+ vertical: `flex-direction: column`,
+ },
+
+ // Sizing
+ size: {
+ 1: `width: ${(100 / 12).toFixed(3)}%`,
+ 2: `width: ${((100 / 12) * 2).toFixed(3)}%`,
+ 3: `width: 25%`,
+ 4: `width: ${((100 / 12) * 4).toFixed(3)}%`,
+ 5: `width: ${((100 / 12) * 5).toFixed(3)}%`,
+ 6: `width: 50%`,
+ 7: `width: ${((100 / 12) * 7).toFixed(3)}%`,
+ 8: `width: ${((100 / 12) * 8).toFixed(3)}%`,
+ 9: `width: 75%`,
+ 10: `width: ${((100 / 12) * 10).toFixed(3)}%`,
+ 11: `width: ${((100 / 12) * 11).toFixed(3)}%`,
+ 12: `width: 100%`,
+ },
+
+ // Spacing
+ spacing: {
+ XXS: `gap: 2px`,
+ XS: `gap: 4px`,
+ S: `gap: 8px`,
+ M: `gap: 16px`,
+ L: `gap: 24px`,
+ XL: `gap: 32px`,
+ XXL: `gap: 64px`,
+ },
+});
+
+const styles = css`
+ display: flex;
+ ${modifiers.style}
+`;
diff --git a/apps/web-client/src/global.css b/apps/web-client/src/global.css
new file mode 100644
index 0000000..e087cb0
--- /dev/null
+++ b/apps/web-client/src/global.css
@@ -0,0 +1,29 @@
+html {
+ font-size: 62.5%;
+ font-size: calc(1em * 0.625);
+ height: 100%;
+}
+
+body {
+ font-size: 1.6rem;
+ margin: 0;
+}
+
+html,
+body,
+#app {
+ display: flex;
+ flex-direction: column;
+ flex-grow: 1;
+}
+
+*,
+*:before,
+*:after {
+ box-sizing: border-box;
+ outline-color: inherit;
+}
+
+:root {
+ --root-client-width: 100%;
+}
diff --git a/apps/web-client/src/pages/HomePage.js b/apps/web-client/src/pages/HomePage.js
index 023ef70..2117811 100644
--- a/apps/web-client/src/pages/HomePage.js
+++ b/apps/web-client/src/pages/HomePage.js
@@ -1,12 +1,33 @@
import React from "react";
import { css } from "@linaria/core";
+import Container from "../components/Container";
+import Grid from "../components/Grid";
import reactIcon from "../images/react-icon.svg";
const HomePage = () => (
-
- Hello
World!
-

-
+ <>
+
+ Hello World!
+
+
+
+ A
+ A
+ A
+ A
+ A
+ A
+ A
+ A
+ A
+ A
+ A
+ A
+
+ >
);
export default HomePage;
diff --git a/apps/web-client/src/theme/breakpoints.js b/apps/web-client/src/theme/breakpoints.js
new file mode 100644
index 0000000..0163ce4
--- /dev/null
+++ b/apps/web-client/src/theme/breakpoints.js
@@ -0,0 +1,131 @@
+import PropTypes from "prop-types";
+
+export const BREAKPOINTS = {
+ xs: 0,
+ s: 600,
+ m: 900,
+ l: 1200,
+ xl: 1536,
+};
+
+export const isValidBreakpoint = (breakpoint) =>
+ Object.prototype.hasOwnProperty.call(BREAKPOINTS, breakpoint);
+
+export const mediaQuery = (breakpoint, style) => {
+ if (!breakpoint) {
+ return style;
+ }
+
+ if (!isValidBreakpoint(breakpoint)) {
+ throw new Error(`Unknown breakpoint "${breakpoint}"`);
+ }
+
+ return `
+ @media screen and (min-width: ${BREAKPOINTS[breakpoint]}px) {
+ ${style}
+ }
+ `;
+};
+
+export const resolveModifiers = (name, values, value) => {
+ if (value && typeof value === "object") {
+ return Object.entries(value)
+ .reduce((acc, [breakpoint, bpValue]) => {
+ if (
+ !isValidBreakpoint(breakpoint) ||
+ !Object.prototype.hasOwnProperty.call(values, bpValue)
+ ) {
+ return acc;
+ }
+
+ acc.push(`${name}--${bpValue}--${breakpoint}`);
+ return acc;
+ }, [])
+ .join(" ");
+ }
+ if (Object.prototype.hasOwnProperty.call(values, value)) {
+ return `${name}--${value}`;
+ }
+
+ return null;
+};
+
+export const modifierPropType = (type) =>
+ PropTypes.oneOfType([
+ type.isRequired,
+ PropTypes.shape(
+ Object.keys(BREAKPOINTS).reduce((acc, breakpoint) => {
+ acc[breakpoint] = type;
+ return acc;
+ }, {}),
+ ).isRequired,
+ ]);
+
+export const createModifierValueRule = (breakpoint, name, value, rule) =>
+ `&.${name}--${value}${breakpoint ? `--${breakpoint}` : ""} {${rule}}`;
+
+export const createModifierRules = (breakpoint, name, values) =>
+ Object.entries(values).reduce(
+ (rules, [value, rule]) =>
+ `${rules}${createModifierValueRule(breakpoint, name, value, rule)}`,
+ "",
+ );
+
+export const createModifiersRules = (breakpoint, modifiers) =>
+ Object.entries(modifiers).reduce(
+ (rules, [name, values]) =>
+ `${rules}${createModifierRules(breakpoint, name, values)}`,
+ "",
+ );
+
+export const createModifiersMedias = (modifiers) =>
+ [null]
+ .concat(Object.keys(BREAKPOINTS))
+ .reduce(
+ (queries, breakpoint) =>
+ `${queries}${mediaQuery(
+ breakpoint,
+ createModifiersRules(breakpoint, modifiers),
+ )}`,
+ "",
+ );
+
+export const createModifierResolvers = (modifiers) => {
+ const resolvers = Object.entries(modifiers).reduce((acc, [name, values]) => {
+ acc[name] = (value) => resolveModifiers(name, values, value);
+ return acc;
+ }, {});
+
+ const resolve = (properties) => {
+ const classNames = Object.entries(properties).reduce(
+ (acc, [name, value]) => {
+ const className = resolvers[name]?.(value);
+
+ if (className) {
+ acc.push(className);
+ }
+
+ return acc;
+ },
+ [],
+ );
+
+ return classNames.length ? classNames.join(" ") : null;
+ };
+
+ return {
+ resolve,
+ resolvers,
+ };
+};
+
+export const createModifiers = (modifiers) => {
+ const style = createModifiersMedias(modifiers);
+ const { resolve, resolvers } = createModifierResolvers(modifiers);
+
+ return {
+ style,
+ resolve,
+ resolvers,
+ };
+};
diff --git a/apps/web-client/src/theme/colors.js b/apps/web-client/src/theme/colors.js
new file mode 100644
index 0000000..65d41ce
--- /dev/null
+++ b/apps/web-client/src/theme/colors.js
@@ -0,0 +1,147 @@
+// https://github.com/yeun/open-color/
+export const white = "#fff";
+export const black = "#000";
+
+export const gray00 = "#f8f9fa";
+export const gray10 = "#f1f3f5";
+export const gray20 = "#e9ecef";
+export const gray30 = "#dee2e6";
+export const gray40 = "#ced4da";
+export const gray50 = "#adb5bd";
+export const gray60 = "#868e96";
+export const gray70 = "#495057";
+export const gray80 = "#343a40";
+export const gray90 = "#212529";
+
+export const red00 = "#fff5f5";
+export const red10 = "#ffe3e3";
+export const red20 = "#ffc9c9";
+export const red30 = "#ffa8a8";
+export const red40 = "#ff8787";
+export const red50 = "#ff6b6b";
+export const red60 = "#fa5252";
+export const red70 = "#f03e3e";
+export const red80 = "#e03131";
+export const red90 = "#c92a2a";
+
+export const pink00 = "#fff0f6";
+export const pink10 = "#ffdeeb";
+export const pink20 = "#fcc2d7";
+export const pink30 = "#faa2c1";
+export const pink40 = "#f783ac";
+export const pink50 = "#f06595";
+export const pink60 = "#e64980";
+export const pink70 = "#d6336c";
+export const pink80 = "#c2255c";
+export const pink90 = "#a61e4d";
+
+export const grape00 = "#f8f0fc";
+export const grape10 = "#f3d9fa";
+export const grape20 = "#eebefa";
+export const grape30 = "#e599f7";
+export const grape40 = "#da77f2";
+export const grape50 = "#cc5de8";
+export const grape60 = "#be4bdb";
+export const grape70 = "#ae3ec9";
+export const grape80 = "#9c36b5";
+export const grape90 = "#862e9c";
+
+export const violet00 = "#f3f0ff";
+export const violet10 = "#e5dbff";
+export const violet20 = "#d0bfff";
+export const violet30 = "#b197fc";
+export const violet40 = "#9775fa";
+export const violet50 = "#845ef7";
+export const violet60 = "#7950f2";
+export const violet70 = "#7048e8";
+export const violet80 = "#6741d9";
+export const violet90 = "#5f3dc4";
+
+export const indigo00 = "#edf2ff";
+export const indigo10 = "#dbe4ff";
+export const indigo20 = "#bac8ff";
+export const indigo30 = "#91a7ff";
+export const indigo40 = "#748ffc";
+export const indigo50 = "#5c7cfa";
+export const indigo60 = "#4c6ef5";
+export const indigo70 = "#4263eb";
+export const indigo80 = "#3b5bdb";
+export const indigo90 = "#364fc7";
+export const indigo96 = "#121A42";
+
+export const blue00 = "#e7f5ff";
+export const blue10 = "#d0ebff";
+export const blue20 = "#a5d8ff";
+export const blue30 = "#74c0fc";
+export const blue40 = "#4dabf7";
+export const blue50 = "#339af0";
+export const blue60 = "#228be6";
+export const blue70 = "#1c7ed6";
+export const blue80 = "#1971c2";
+export const blue90 = "#1864ab";
+
+export const cyan00 = "#e3fafc";
+export const cyan10 = "#c5f6fa";
+export const cyan20 = "#99e9f2";
+export const cyan30 = "#66d9e8";
+export const cyan40 = "#3bc9db";
+export const cyan50 = "#22b8cf";
+export const cyan60 = "#15aabf";
+export const cyan70 = "#1098ad";
+export const cyan80 = "#0c8599";
+export const cyan90 = "#0b7285";
+
+export const teal00 = "#e6fcf5";
+export const teal10 = "#c3fae8";
+export const teal20 = "#96f2d7";
+export const teal30 = "#63e6be";
+export const teal40 = "#38d9a9";
+export const teal50 = "#20c997";
+export const teal60 = "#12b886";
+export const teal70 = "#0ca678";
+export const teal80 = "#099268";
+export const teal90 = "#087f5b";
+
+export const green00 = "#ebfbee";
+export const green10 = "#d3f9d8";
+export const green20 = "#b2f2bb";
+export const green30 = "#8ce99a";
+export const green40 = "#69db7c";
+export const green50 = "#51cf66";
+export const green60 = "#40c057";
+export const green70 = "#37b24d";
+export const green80 = "#2f9e44";
+export const green90 = "#2b8a3e";
+
+export const lime00 = "#f4fce3";
+export const lime10 = "#e9fac8";
+export const lime20 = "#d8f5a2";
+export const lime30 = "#c0eb75";
+export const lime40 = "#a9e34b";
+export const lime50 = "#94d82d";
+export const lime60 = "#82c91e";
+export const lime70 = "#74b816";
+export const lime80 = "#66a80f";
+export const lime90 = "#5c940d";
+
+export const yellow00 = "#fff9db";
+export const yellow10 = "#fff3bf";
+export const yellow20 = "#ffec99";
+export const yellow30 = "#ffe066";
+export const yellow40 = "#ffd43b";
+export const yellow50 = "#fcc419";
+export const yellow60 = "#fab005";
+export const yellow70 = "#f59f00";
+export const yellow80 = "#f08c00";
+export const yellow90 = "#e67700";
+
+export const orange00 = "#fff4e6";
+export const orange10 = "#ffe8cc";
+export const orange20 = "#ffd8a8";
+export const orange30 = "#ffc078";
+export const orange40 = "#ffa94d";
+export const orange50 = "#ff922b";
+export const orange60 = "#fd7e14";
+export const orange70 = "#f76707";
+export const orange80 = "#e8590c";
+export const orange90 = "#d9480f";
diff --git a/apps/web-client/src/theme/schemes.js b/apps/web-client/src/theme/schemes.js
new file mode 100644
index 0000000..6d7af30
--- /dev/null
+++ b/apps/web-client/src/theme/schemes.js
@@ -0,0 +1,31 @@
+import { indigo10, indigo70, gray10, gray90 } from "./colors.js";
+
+export const light = {
+ background: gray10,
+ border: indigo10,
+ text: gray90,
+};
+
+export const dark = {
+ background: gray90,
+ border: indigo70,
+ text: gray10,
+};
+
+export const properties = {
+ background: "--theme-scheme-background",
+ border: "--theme-scheme-border",
+ text: "--theme-scheme-text",
+};
+
+export const variables = {
+ background: `var(${properties.background})`,
+ border: `var(${properties.border})`,
+ text: `var(${properties.text})`,
+};
+
+export const apply = (scheme) => `
+ --theme-scheme-background: ${scheme.background};
+ --theme-scheme-border: ${scheme.border};
+ --theme-scheme-text: ${scheme.text};
+`;
diff --git a/apps/web-client/src/theme/spacing.js b/apps/web-client/src/theme/spacing.js
new file mode 100644
index 0000000..db686ee
--- /dev/null
+++ b/apps/web-client/src/theme/spacing.js
@@ -0,0 +1,2 @@
+export const space0 = 0;
+export const space4 = 4;
diff --git a/libs/uikit/.eslintrc b/libs/uikit/.eslintrc
new file mode 100644
index 0000000..7eb4dbf
--- /dev/null
+++ b/libs/uikit/.eslintrc
@@ -0,0 +1,4 @@
+{
+ "root": true,
+ "extends": ["@witb/eslint-config/esm"]
+}
diff --git a/libs/uikit/.prettierrc b/libs/uikit/.prettierrc
new file mode 100644
index 0000000..a883480
--- /dev/null
+++ b/libs/uikit/.prettierrc
@@ -0,0 +1 @@
+"@witb/prettier-config"
\ No newline at end of file
diff --git a/libs/uikit/Container.js b/libs/uikit/Container.js
new file mode 100644
index 0000000..1390fb9
--- /dev/null
+++ b/libs/uikit/Container.js
@@ -0,0 +1,20 @@
+import React from "react";
+import PropTypes from "prop-types";
+import { css } from "@linaria/core";
+
+const Container = ({ children }) => {children}
;
+
+Container.defaultProps = {
+ children: null,
+};
+
+Container.propTypes = {
+ children: PropTypes.node,
+};
+
+export default Container;
+
+const className = css`
+ max-width: 1080px;
+ width: 100%;
+`;
diff --git a/libs/uikit/index.js b/libs/uikit/index.js
new file mode 100644
index 0000000..a03eec9
--- /dev/null
+++ b/libs/uikit/index.js
@@ -0,0 +1,3 @@
+import Container from "./Container.js";
+
+export { Container };
diff --git a/libs/uikit/package.json b/libs/uikit/package.json
new file mode 100644
index 0000000..9e526ee
--- /dev/null
+++ b/libs/uikit/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "@witb/uikit",
+ "version": "0.1.0",
+ "main": "index.js",
+ "license": "MIT",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "lint": "eslint ."
+ },
+ "dependencies": {
+ "@linaria/core": "^3.0.0-beta.13",
+ "prop-types": "^15.7.2",
+ "react": "^18.0.0-beta-96ca8d915-20211115",
+ "react-router-dom": "^6.0.1"
+ },
+ "devDependencies": {
+ "@witb/babel-config": "0.1.0",
+ "@witb/eslint-config": "0.1.0",
+ "@witb/prettier-config": "0.1.0",
+ "eslint": "^8.2.0",
+ "prettier": "^2.4.1"
+ }
+}