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" + } +}