├── .gitignore
├── README.md
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
├── robots.txt
├── shaper-editing.gif
├── shot.png
└── shot2.png
├── src
├── App.js
├── App.test.js
├── common.css
├── components
│ ├── demo-email
│ │ ├── demo-email.css
│ │ └── demo-email.js
│ ├── setting.js
│ ├── settings.js
│ └── specs
│ │ ├── specs.css
│ │ └── specs.js
├── demo.css
├── icons
│ ├── frame-ratio.svg
│ ├── frame-y.svg
│ ├── space.svg
│ ├── text-increment.svg
│ └── unit.svg
├── index.css
├── index.js
├── logo.svg
├── serviceWorker.js
├── settings.css
├── setupTests.js
├── slider.css
├── utilities.css
├── utilities.js
└── variables.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [SHAPER](https://hihayk.github.io/shaper/) — interface styles shaper
2 |
3 | 
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "homepage": "https://hihayk.github.io/shaper",
3 | "name": "shaper",
4 | "version": "0.1.0",
5 | "private": true,
6 | "dependencies": {
7 | "@testing-library/jest-dom": "^4.2.4",
8 | "@testing-library/react": "^9.3.2",
9 | "@testing-library/user-event": "^7.1.2",
10 | "gh-pages": "^3.1.0",
11 | "react": "^16.13.1",
12 | "react-dom": "^16.13.1",
13 | "react-scripts": "3.4.1",
14 | "tinycolor2": "^1.4.1"
15 | },
16 | "scripts": {
17 | "start": "react-scripts start",
18 | "build": "react-scripts build",
19 | "test": "react-scripts test",
20 | "eject": "react-scripts eject",
21 | "predeploy": "npm run build",
22 | "deploy": "gh-pages -d build"
23 | },
24 | "eslintConfig": {
25 | "extends": "react-app"
26 | },
27 | "browserslist": {
28 | "production": [
29 | ">0.2%",
30 | "not dead",
31 | "not op_mini all"
32 | ],
33 | "development": [
34 | "last 1 chrome version",
35 | "last 1 firefox version",
36 | "last 1 safari version"
37 | ]
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hihayk/shaper/24be3cb723c5d785bfac570dbdd948331624be42/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 |
28 | shaper — interface styles shaper
29 |
30 |
31 |
38 |
39 |
40 |
41 |
42 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hihayk/shaper/24be3cb723c5d785bfac570dbdd948331624be42/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hihayk/shaper/24be3cb723c5d785bfac570dbdd948331624be42/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/public/shaper-editing.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hihayk/shaper/24be3cb723c5d785bfac570dbdd948331624be42/public/shaper-editing.gif
--------------------------------------------------------------------------------
/public/shot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hihayk/shaper/24be3cb723c5d785bfac570dbdd948331624be42/public/shot.png
--------------------------------------------------------------------------------
/public/shot2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hihayk/shaper/24be3cb723c5d785bfac570dbdd948331624be42/public/shot2.png
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef, useLayoutEffect } from 'react';
2 | import './utilities.css';
3 | import './common.css';
4 | import Specs from './components/specs/specs';
5 | import { getRandomObject, setProperty, numberToUnit, fonts } from './utilities';
6 | import Settings from './components/settings'
7 | import DemoEmail from './components/demo-email/demo-email';
8 | import { getVariables, darkModeStyles } from './variables'
9 |
10 | const defaultState = {
11 | fontFamily: fonts[0],
12 | textSizeIncrement: 1.23,
13 | baseTextSize: 0.9,
14 | textFrameRatio: 2.37,
15 | textFrameY: 0.8,
16 | spaceIncrement: 1.65,
17 | unit: 0.5,
18 | accentHue: 254,
19 | accentSaturation: 31,
20 | accentLightness: 50,
21 | greySaturation: 5,
22 | radius: 0.4,
23 | fieldBorderWidth: 2,
24 | buttonRound: false,
25 | }
26 |
27 | function App() {
28 |
29 | const getHash = () => {
30 | const hash = decodeURI(window.location.hash)
31 |
32 | if (hash) {
33 | const stateKeysArray = Object.keys(defaultState)
34 | const hashValuesArray = hash.substr(1, hash.length).split(['/'])
35 |
36 | const getHashObject = () => {
37 | var hashObject = {}
38 | stateKeysArray.forEach((key, i) => {
39 | const valueWithBoolean = () => {
40 | if(hashValuesArray[i] === 'true') { return true }
41 | if(hashValuesArray[i] === 'false') { return false }
42 | return hashValuesArray[i]
43 | }
44 |
45 | return hashObject[key] = valueWithBoolean()
46 | })
47 |
48 | return hashObject
49 | }
50 |
51 | return getHashObject()
52 | }
53 |
54 | return null
55 | }
56 |
57 | const initialState = getHash() || defaultState
58 |
59 | const [fontFamily, setFontFamily] = useState(initialState.fontFamily)
60 | const [textSizeIncrement, setTextSizeIncrement] = useState(initialState.textSizeIncrement)
61 | const [baseTextSize, setBaseTextSize] = useState(initialState.baseTextSize)
62 | const [textFrameRatio, setTextFrameRatio] = useState(initialState.textFrameRatio)
63 | const [textFrameY, setTextFrameY] = useState(initialState.textFrameY)
64 | const [spaceIncrement, setSpaceIncrement] = useState(initialState.spaceIncrement)
65 | const [unit, setUnit] = useState(0.5)
66 | const [accentHue, setAccentHue] = useState(initialState.accentHue)
67 | const [accentSaturation, setAccentSaturation] = useState(initialState.accentSaturation)
68 | const [accentLightness, setAccentLightness] = useState(initialState.accentLightness)
69 | const [greySaturation, setGreySaturation] = useState(initialState.greySaturation)
70 | const [radius, setRadius] = useState(initialState.radius)
71 | const [fieldBorderWidth, setFieldBorderWidth] = useState(initialState.fieldBorderWidth)
72 | const [buttonRound, setButtonRound] = useState(initialState.buttonRound)
73 | const [darkMode, setDarkMode] = useState(false)
74 | const [preview, setPreview] = useState('demo')
75 |
76 | const variables = getVariables({
77 | baseTextSize,
78 | textSizeIncrement,
79 | fontFamily,
80 | unit,
81 | spaceIncrement,
82 | textFrameRatio,
83 | textFrameY,
84 | accentHue,
85 | accentSaturation,
86 | accentLightness,
87 | greySaturation,
88 | radius,
89 | fieldBorderWidth,
90 | })
91 |
92 | const styleRef = useRef()
93 |
94 | useLayoutEffect(() => {
95 | // Only create style element once
96 | if (!styleRef.current) {
97 | const style = document.createElement('style')
98 | document.head.appendChild(style)
99 | styleRef.current = style;
100 | }
101 |
102 | styleRef.current.innerHTML = `
103 | :root{
104 | ${Object.values(variables).join('')}
105 | }
106 | ${darkModeStyles}
107 | `
108 | }, [variables])
109 |
110 | const handleRandomize = () => {
111 | setFontFamily(getRandomObject().fontFamily)
112 | setTextSizeIncrement(getRandomObject().textSizeIncrement)
113 | setBaseTextSize(getRandomObject().baseTextSize)
114 | setTextFrameRatio(getRandomObject().textFrameRatio)
115 | setTextFrameY(getRandomObject().textFrameY)
116 | setSpaceIncrement(getRandomObject().spaceIncrement)
117 | setAccentHue(getRandomObject().accentHue)
118 | setAccentSaturation(getRandomObject().accentSaturation)
119 | setAccentLightness(getRandomObject().accentLightness)
120 | setGreySaturation(getRandomObject().greySaturation)
121 | setRadius(getRandomObject().radius)
122 | setFieldBorderWidth(getRandomObject().fieldBorderWidth)
123 | setButtonRound(getRandomObject().buttonRound)
124 | }
125 |
126 | const bodyClassList = document.querySelector('body').classList
127 |
128 | useEffect(() => {
129 | setProperty('fontFamily', fontFamily)
130 | setProperty('textSizeIncrement', textSizeIncrement)
131 | setProperty('baseTextSize', numberToUnit(baseTextSize, 'rem'))
132 | setProperty('textFrameRatio', textFrameRatio)
133 | setProperty('textFrameY', numberToUnit(textFrameY, 'em'))
134 | setProperty('spaceIncrement', spaceIncrement)
135 | setProperty('unit', numberToUnit(unit, 'rem'))
136 | setProperty('accentH', accentHue)
137 | setProperty('accentS', numberToUnit(accentSaturation, '%'))
138 | setProperty('accentL', numberToUnit(accentLightness, '%'))
139 | setProperty('greyS', numberToUnit(greySaturation, '%'))
140 | setProperty('radius', numberToUnit(radius, 'rem'))
141 | setProperty('fieldBorderWidth', numberToUnit(fieldBorderWidth, 'px'))
142 |
143 | if(buttonRound) {
144 | bodyClassList.add('roundButtons');
145 | } else {
146 | bodyClassList.remove('roundButtons');
147 | }
148 |
149 | if(darkMode) {
150 | bodyClassList.add('darkMode');
151 | } else {
152 | bodyClassList.remove('darkMode');
153 | }
154 |
155 | }, [accentHue, accentLightness, accentSaturation, baseTextSize, bodyClassList, buttonRound, darkMode, fieldBorderWidth, fontFamily, greySaturation, radius, spaceIncrement, textFrameRatio, textFrameY, textSizeIncrement, unit, variables.type])
156 |
157 | const currentState = {
158 | fontFamily,
159 | textSizeIncrement,
160 | baseTextSize,
161 | textFrameRatio,
162 | textFrameY,
163 | spaceIncrement,
164 | unit,
165 | accentHue,
166 | accentSaturation,
167 | accentLightness,
168 | greySaturation,
169 | radius,
170 | fieldBorderWidth,
171 | buttonRound,
172 | }
173 |
174 | if(getHash()) {
175 | window.location.hash = encodeURI(Object.values(getHash()).join('/'))
176 | }
177 |
178 | const updateHash = () => {
179 | window.location.hash = encodeURI(Object.values(currentState).join('/'))
180 | }
181 |
182 | const isInitialMount = useRef(true);
183 |
184 | useEffect(() => {
185 | if (isInitialMount.current) {
186 | isInitialMount.current = false;
187 | } else {
188 | updateHash()
189 | }
190 | });
191 |
192 | return (
193 |
194 |
230 | {preview === 'specs' ? (
231 |
242 | ) : (
243 |
244 | )}
245 |
246 | );
247 | }
248 |
249 | export default App;
250 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | const { getByText } = render();
7 | const linkElement = getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/src/common.css:
--------------------------------------------------------------------------------
1 | *,
2 | *:before,
3 | *:after {
4 | box-sizing: border-box;
5 | }
6 |
7 | body {
8 | --settingsBg: #1B1B1B;
9 |
10 | margin: 0;
11 | background-color: var(--settingsBg);
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | }
15 |
16 | .textInput {
17 | padding: calc(var(--textFrameY) - var(--fieldBorderWidth)) var(--textFrameY);
18 | font: inherit;
19 | color: inherit;
20 | border: var(--fieldBorderWidth) solid var(--c-fieldBorder);
21 | appearance: none;
22 | border-radius: var(--radius);
23 | width: 100%;
24 | background-color: transparent;
25 | }
26 |
27 | .button {
28 | padding: var(--textFrameY) var(--textFrameX);
29 | font: inherit;
30 | border: none;
31 | background-color: var(--c-buttonBg);
32 | appearance: none;
33 | border-radius: var(--radius);
34 | font-weight: 500;
35 | cursor: pointer;
36 | color: var(--c-body);
37 | letter-spacing: 0.03em;
38 | transition: 0.2s filter;
39 | }
40 |
41 | .button:hover {
42 | filter: brightness(1.1);
43 | }
44 |
45 | .button:active {
46 | filter: brightness(0.9);
47 | }
48 |
49 | .buttonSmall {
50 | font-size: var(--text-xs);
51 | }
52 |
53 | .roundButtons .button {
54 | border-radius: 1000rem;
55 | }
56 |
57 | .button-accent {
58 | background-color: var(--c-accent);
59 | color: var(--c-accentContrasted);
60 | }
61 |
62 | .button-accent span {
63 | color: black;
64 | mix-blend-mode: exclusion;
65 | }
66 |
67 | .card {
68 | padding: var(--space-l);
69 | background-color: white;
70 | border-radius: var(--radius);
71 | }
72 |
73 | .detailsActions {
74 | display: flex;
75 | }
--------------------------------------------------------------------------------
/src/components/demo-email/demo-email.css:
--------------------------------------------------------------------------------
1 | .emailWrapper, .emailWrapper * {
2 | line-height: var(--globalLineHeight);
3 | }
4 |
5 | .emailWrapper {
6 | font-size: var(--text-s);
7 | color: var(--c-body);
8 | font-family: var(--fontFamily);
9 | display: grid;
10 | grid-template-columns: 17% 52% calc(100% - 17% - 52%);
11 | min-height: 100vh;
12 | background: var(--c-background);
13 | }
14 |
15 | .sidebar {
16 | border-right: 1px solid var(--c-border);
17 | padding: var(--space-l);
18 | }
19 |
20 | .detailsPanel {
21 | border-left: 1px solid var(--c-border);
22 | padding: var(--space-xl);
23 | background-color: var(--c-overlay);
24 | }
25 |
26 | main {
27 | padding: var(--space-xl);
28 | }
29 |
30 | .emails {
31 | border-top: 1px solid var(--c-border);
32 | }
33 |
34 | .email {
35 | display: grid;
36 | grid-template-columns: 1fr 4fr;
37 | padding: var(--space-m) 0;
38 | border-bottom: 1px solid var(--c-border);
39 | min-width: 0;
40 | min-width: 100%;
41 | }
42 |
43 | .listCell + .listCell {
44 | padding-left: var(--space-l);
45 | }
46 |
47 | .listCell {
48 | overflow: hidden;
49 | text-overflow: ellipsis;
50 | width: 100%;
51 | white-space: nowrap;
52 | min-width: 0;
53 | }
54 |
55 | .searchSection {
56 | margin-bottom: var(--space-xl);
57 | }
58 |
59 | .logo {
60 | width: 2rem;
61 | height: 2rem;
62 | border-radius: 100%;
63 | background-color: var(--c-accent);
64 | }
65 |
66 | .postAvatar {
67 | width: 3rem;
68 | height: 3rem;
69 | border-radius: 100%;
70 | background-color: var(--c-accent);
71 | }
72 |
73 | .post {
74 | display: grid;
75 | grid-template-columns: 3rem 1fr;
76 | grid-gap: var(--space-l);
77 | padding: var(--space-l);
78 | border-radius: var(--radius);
79 | background-color: var(--c-overlay);
80 | }
81 |
82 | .detailsCard {
83 | padding: var(--space-l);
84 | border-radius: var(--radius);
85 | background-color: var(--c-background);
86 | }
87 |
88 | .menuItem {
89 | padding: var(--space-s);
90 | border-radius: var(--radius);
91 | }
92 |
93 | .menuItem.active {
94 | background-color: var(--c-overlay);
95 | font-weight: bold;
96 | }
97 |
98 | .menuItem:not(.active) {
99 | cursor: pointer;
100 | color: var(--c-bodyDimmed);
101 | }
102 |
103 | .userCard {
104 | display: grid;
105 | grid-template-columns: auto 1fr auto;
106 | grid-gap: var(--space-m);
107 | padding: var(--space-m);
108 | border-radius: var(--radius);
109 | background-color: var(--c-background);
110 | align-items: center;
111 | }
112 |
113 | .userCardAvatar {
114 | width: 2rem;
115 | height: 2rem;
116 | border-radius: 100%;
117 | background-color: var(--c-accent);
118 | }
119 |
120 | .formGrid {
121 | display: grid;
122 | grid-template-columns: 1fr 1fr auto;
123 | grid-gap: var(--space-s);
124 | align-items: end;
125 | }
126 |
127 | .tabs {
128 | display: flex;
129 | }
130 |
131 | .tab {
132 | font-size: var(--text-m);
133 | border-bottom: 2px solid transparent;
134 | padding-bottom: 0.2em;
135 | }
136 |
137 | .tab:not(.active) {
138 | cursor: pointer;
139 | color: var(--c-bodyDimmed);
140 | }
141 |
142 | .tab + .tab {
143 | margin-left: var(--space-l);
144 | }
145 |
146 | .tab.active {
147 | border-color: var(--c-accent);
148 | }
149 |
150 | .messageBox {
151 | padding: var(--space-m);
152 | border-radius: var(--radius);
153 | background-color: var(--c-primary-3xlight);
154 | color: var(--c-primary-xdark);
155 | }
156 |
157 | .chartCard {
158 | display: grid;
159 | grid-template-columns: 50% 50%;
160 | padding: var(--space-l);
161 | border-radius: var(--radius);
162 | background-color: var(--c-background);
163 | align-items: center;
164 | }
165 |
166 | .chartCard .chart:first-child {
167 | padding-right: var(--space-m);
168 | }
169 |
170 | .chartCard .chart + .chart {
171 | padding-left: var(--space-m);
172 | border-left: 1px solid var(--c-border);
173 | }
174 |
175 | .chartCard svg {
176 | display: block;
177 | }
178 |
179 | .highlightCard {
180 | background-color: var(--c-accent);
181 | display: grid;
182 | grid-template-columns: 1fr auto;
183 | grid-gap: var(--space-m);
184 | padding: var(--space-l);
185 | border-radius: var(--radius);
186 | align-items: center;
187 | color: var(--c-accentContrasted);
188 | }
189 |
190 | .highlightCardAvatar {
191 | background-color: white;
192 | width: 3rem;
193 | height: 3rem;
194 | border-radius: 100%;
195 | }
196 |
197 | .highlightCardAvatars {
198 | display: flex;
199 | }
200 |
201 | .highlightCardAvatar + .highlightCardAvatar {
202 | margin-left: -2.2rem;
203 | }
204 |
205 | .highlightCardAvatar:first-child {
206 | z-index: 1;
207 | opacity: 0.2;
208 | }
209 |
210 | .highlightCardAvatar:nth-child(2) {
211 | z-index: 2;
212 | opacity: 0.5;
213 | }
214 |
215 | .highlightCardAvatar:nth-child(3) {
216 | z-index: 3;
217 | }
218 |
219 | @media (max-width: 1100px) {
220 | .emailWrapper {
221 | grid-template-columns: 1fr;
222 | }
223 | .sidebar {
224 | display: none;
225 | }
226 | .detailsPanel {
227 | width: 100%;
228 | }
229 | body {
230 | overflow-x: hidden;
231 | }
232 | .mainLogoSection {
233 | margin-right: 2rem;
234 | }
235 |
236 | .snippetSection {
237 | display: block;
238 | }
239 |
240 | .previewSection, .codeSection {
241 | width: 100%;
242 | }
243 | }
244 |
245 | .dataNumber {
246 | text-overflow: ellipsis;
247 | overflow: hidden;
248 | }
249 |
--------------------------------------------------------------------------------
/src/components/demo-email/demo-email.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './demo-email.css'
3 |
4 | const contents = [
5 | `Nylon is a chord generator that emulates chord strokes of a guitar. Play up to 6 strings per chord, add dynamics by controlling stroke speed, acceleration and velocity, and humanize each stroke by adding randomness.`,
6 | `Alternative graphical interface for Robert Henke's ml.Distance but with a twist. Distance and panning values are interpolated, for avoiding clicks in the sound. The added smooth parameter controls the interpolation time.`,
7 | `As suggested this version should keep correct timings when changing looping lengths.`,
8 | `The Interpolation Time`,
9 | `Added a preset system; now you can store/recall settings within a live set`,
10 | ]
11 |
12 | const emails = [
13 | { name: 'Koji Akane', handle: '@koji', content: contents[0] },
14 | { name: 'Kiyoko Riku', handle: '@kiyoko', content: contents[1] },
15 | { name: 'Ichirō Nori', handle: '@nori', content: contents[2] },
16 | ]
17 | const menuItems = [
18 | 'First Section', 'Second Section', 'Third Section', 'Fourth Section'
19 | ]
20 |
21 | const DemoEmail = () => (
22 |
23 |
32 |
33 |
34 | {contents[3]}
35 |
36 |
37 |
First
38 |
Second
39 |
Third
40 |
41 |
42 |
43 |
44 |
45 |
48 |
49 |
{emails[2].name}
50 |
{emails[2].content}
51 |
6:51 p. m. · 11 jun. 2020
52 |
53 |
54 |
55 | {emails.map((email, index) => (
56 |
57 |
{email.name}
58 |
{email.content}
59 |
60 | ))}
61 |
62 |
63 |
64 |
65 |
66 |
67 | {}} type="text" className="textInput mt-s" value="email@example.com" />
68 |
69 |
70 |
71 | {}} type="password" className="textInput mt-s" value="123456" />
72 |
73 |
74 |
75 |
76 |
77 |
78 |
146 |
147 | )
148 |
149 | export default DemoEmail
--------------------------------------------------------------------------------
/src/components/setting.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Setting = ({
4 | label,
5 | id,
6 | illustration,
7 | settingType,
8 | inputType,
9 | value,
10 | onChange,
11 | valueUnit,
12 | step,
13 | children,
14 | switchIsActive,
15 | ...props
16 | }) => {
17 | return (
18 |
23 | {/* {illustration && (
24 |
25 |

26 |
27 | )} */}
28 |
29 | {settingType === "input" && (
30 | <>
31 |
34 |
43 | {inputType === "range" && (
44 |
45 |
53 |
54 | {valueUnit}
55 |
56 |
57 | )}
58 | >
59 | )}
60 | {settingType === "switch" && (
61 |
62 |
66 |
69 |
70 | )}
71 |
72 | )
73 | }
74 |
75 | export default Setting
--------------------------------------------------------------------------------
/src/components/settings.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Setting from './setting';
3 | import '../settings.css';
4 | import '../slider.css';
5 | import space from '../icons/space.svg'
6 | import frameRatio from '../icons/frame-ratio.svg'
7 | import frameY from '../icons/frame-y.svg'
8 | import textIncrement from '../icons/text-increment.svg'
9 | import unitSvg from '../icons/unit.svg'
10 |
11 | const Settings = ({
12 | fontFamily,
13 | setFontFamily,
14 | textSizeIncrement,
15 | setTextSizeIncrement,
16 | baseTextSize,
17 | setBaseTextSize,
18 | textFrameRatio,
19 | setTextFrameRatio,
20 | textFrameY,
21 | setTextFrameY,
22 | spaceIncrement,
23 | unit,
24 | setUnit,
25 | setSpaceIncrement,
26 | accentHue,
27 | setAccentHue,
28 | accentSaturation,
29 | setAccentSaturation,
30 | accentLightness,
31 | setAccentLightness,
32 | greySaturation,
33 | setGreySaturation,
34 | radius,
35 | setRadius,
36 | fieldBorderWidth,
37 | setFieldBorderWidth,
38 | buttonRound,
39 | setButtonRound,
40 | darkMode,
41 | setDarkMode,
42 | preview,
43 | setPreview,
44 |
45 | handleRandomize,
46 | }) => {
47 |
48 | return (
49 |
50 |
51 |
52 |
53 | Typography
54 |
55 | setTextSizeIncrement(e.target.value)}
63 | min={1}
64 | max={2}
65 | step={0.01}
66 | valueUnit="ratio"
67 | />
68 | setBaseTextSize(e.target.value)}
75 | min={0.8}
76 | max={2}
77 | step={0.01}
78 | valueUnit="rem"
79 | />
80 | setFontFamily(e.target.value)}
87 | />
88 |
89 |
90 |
91 | Spacing
92 |
93 | setUnit(e.target.value)}
101 | min={0.4}
102 | max={0.8}
103 | step={0.01}
104 | valueUnit="rem"
105 | />
106 | setSpaceIncrement(e.target.value)}
114 | min={1}
115 | max={2}
116 | step={0.01}
117 | valueUnit="ratio"
118 | />
119 |
120 |
121 |
122 | -
123 |
124 | setTextFrameY(e.target.value)}
132 | min={0}
133 | max={1}
134 | step={0.01}
135 | valueUnit="em"
136 | />
137 | setTextFrameRatio(e.target.value)}
145 | min={1}
146 | max={3}
147 | step={0.01}
148 | valueUnit="ratio"
149 | />
150 |
151 |
152 |
153 | Color
154 |
155 | setAccentHue(e.target.value)}
162 | min={0}
163 | max={360}
164 | step={1}
165 | valueUnit="°"
166 | />
167 | setAccentSaturation(e.target.value)}
174 | min={0}
175 | max={100}
176 | step={1}
177 | valueUnit="%"
178 | />
179 | setAccentLightness(e.target.value)}
186 | min={0}
187 | max={100}
188 | step={1}
189 | valueUnit="%"
190 | />
191 |
192 |
193 |
194 | -
195 |
196 | setGreySaturation(e.target.value)}
203 | min={0}
204 | max={100}
205 | step={1}
206 | valueUnit="%"
207 | />
208 |
209 |
210 |
211 | Layer
212 |
213 | setRadius(e.target.value)}
220 | min={0}
221 | max={1}
222 | step={0.01}
223 | valueUnit="rem"
224 | />
225 | setFieldBorderWidth(e.target.value)}
232 | min={1}
233 | max={3}
234 | step={1}
235 | valueUnit="px"
236 | />
237 | setButtonRound(!buttonRound)}
243 | />
244 |
245 |
246 |
247 |
248 |
254 |
255 |
setDarkMode(!darkMode)}
261 | />
262 |
263 |
264 |
270 |
276 |
277 |
278 |
279 |
280 |
281 |
282 | S
283 | H
284 | A
285 | P
286 | E
287 | R
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 | )
297 | }
298 |
299 | export default Settings
--------------------------------------------------------------------------------
/src/components/specs/specs.css:
--------------------------------------------------------------------------------
1 | .specsWrapper {
2 | padding: 2rem;
3 | background-color: var(--c-background);
4 | color: var(--c-body);
5 | font-family: var(--fontFamily);
6 | }
7 |
8 | .snippetsSection {
9 | display: grid;
10 | grid-gap: var(--space-l);
11 | }
12 |
13 | .snippetSection {
14 | display: flex;
15 | margin-bottom: var(--space-xl);
16 | }
17 |
18 | .previewSection {
19 | border: 1px solid var(--c-border);
20 | padding: var(--space-l);
21 | width: 50%;
22 | }
23 |
24 | .codeSection {
25 | background-color: var(--c-overlay);
26 | padding: var(--space-l);
27 | overflow: auto;
28 | width: 100%;
29 | }
30 |
31 | .snippetSection.withPreview .codeSection {
32 | width: 50%;
33 | }
34 |
35 | .codeSection code {
36 | font-family: 'Roboto Mono';
37 | }
38 |
39 | .demoSpace {
40 | background-color: currentColor;
41 | }
42 |
43 | .demoSpace + .demoSpace {
44 | margin-top: var(--space-s);
45 | }
46 |
47 | .demoSpace-s { width: var(--space-s); height: var(--space-s); }
48 | .demoSpace-m { width: var(--space-m); height: var(--space-m); }
49 | .demoSpace-l { width: var(--space-l); height: var(--space-l); }
50 | .demoSpace-xl { width: var(--space-xl); height: var(--space-xl); }
51 | .demoSpace-2xl { width: var(--space-2xl); height: var(--space-2xl); }
52 | .demoSpace-3xl { width: var(--space-3xl); height: var(--space-3xl); }
53 | .demoSpace-4xl { width: var(--space-4xl); height: var(--space-4xl); }
54 |
55 | .colorBox {
56 | width: 2rem;
57 | height: 2rem;
58 | border-radius: 100%;
59 | display: block;
60 | border: 1px solid hsla(0, 0%, 100%, 0.2);
61 | }
62 |
63 | .colorsRow {
64 | display: grid;
65 | grid-gap: var(--space-s);
66 | }
67 |
68 | .colorBoxWrapper {
69 | display: inline-grid;
70 | grid-template-columns: auto 1fr;
71 | grid-gap: var(--space-s);
72 | align-items: center;
73 | }
--------------------------------------------------------------------------------
/src/components/specs/specs.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './specs.css';
3 |
4 | const Snippet = ({preview, code}) => (
5 |
6 | {preview && (
7 |
8 | {preview}
9 |
10 | )}
11 |
12 |
13 |
14 | {code}
15 |
16 |
17 |
18 | )
19 |
20 | const Specs = ({
21 | variables,
22 | darkModeStyles,
23 | }) => {
24 | return (
25 |
26 |
27 |
Typography
28 |
31 | {['xl','l','m','s','xs'].map((item, index) => (
32 |
33 |
Aa123 — {item}
34 |
Handgloves
35 |
36 | ))}
37 | >
38 | )}
39 | code={variables.type}
40 | />
41 | Spacing
42 |
45 | {['s','m','l','xl','2xl','3xl','4xl'].map((item, index) => (
46 |
47 | ))}
48 | >
49 | )}
50 | code={variables.space}
51 | />
52 |
53 |
Text frame
54 |
Use textFrame variables to set the padding of buttons and text inputs.
55 |
56 |
59 | padding: var(--textFrameY)
66 | padding: var(--textFrameY) var(--textFrameX)
72 | >
73 | )}
74 | code={variables.textFrame}
75 | />
76 | Color
77 |
80 |
81 | {[1,2,3,4,5,6,7,8].map((item, index) => (
82 |
83 |
84 |
grey{item}
85 |
86 | ))}
87 |
88 |
89 |
90 | {['accent','accentContrasted'].map((item, index) => (
91 |
95 | ))}
96 |
97 | >
98 | )}
99 | code={variables.color}
100 | />
101 | Layer
102 |
110 | )}
111 | code={variables.layer}
112 | />
113 | Dark mode styles
114 |
117 |
118 |
119 | )
120 | }
121 |
122 | export default Specs
--------------------------------------------------------------------------------
/src/demo.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hihayk/shaper/24be3cb723c5d785bfac570dbdd948331624be42/src/demo.css
--------------------------------------------------------------------------------
/src/icons/frame-ratio.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/icons/frame-y.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/icons/space.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/icons/text-increment.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/icons/unit.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hihayk/shaper/24be3cb723c5d785bfac570dbdd948331624be42/src/index.css
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want your app to work offline and load faster, you can change
15 | // unregister() to register() below. Note this comes with some pitfalls.
16 | // Learn more about service workers: https://bit.ly/CRA-PWA
17 | serviceWorker.unregister();
18 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl, {
104 | headers: { 'Service-Worker': 'script' },
105 | })
106 | .then(response => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get('content-type');
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf('javascript') === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then(registration => {
115 | registration.unregister().then(() => {
116 | window.location.reload();
117 | });
118 | });
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config);
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | 'No internet connection found. App is running in offline mode.'
127 | );
128 | });
129 | }
130 |
131 | export function unregister() {
132 | if ('serviceWorker' in navigator) {
133 | navigator.serviceWorker.ready
134 | .then(registration => {
135 | registration.unregister();
136 | })
137 | .catch(error => {
138 | console.error(error.message);
139 | });
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/settings.css:
--------------------------------------------------------------------------------
1 | input::-webkit-outer-spin-button,
2 | input::-webkit-inner-spin-button {
3 | -webkit-appearance: none;
4 | margin: 0;
5 | }
6 |
7 | input[type=number] {
8 | -moz-appearance: textfield;
9 | }
10 |
11 | .settingsWrapper {
12 | color: white;
13 | padding: 2rem;
14 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
15 | overflow: auto;
16 | }
17 |
18 | .settingsGrid {
19 | min-width: 1280px;
20 | display: grid;
21 | grid-gap: 2rem;
22 | grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr auto;
23 | }
24 |
25 | .settingsGridColumnDouble {
26 | grid-column: 2 / span 2;
27 | }
28 |
29 | .settingTextInput {
30 | background-color: transparent;
31 | border: none;
32 | color: inherit;
33 | padding: 0.25rem;
34 | left: -0.25rem;
35 | position: relative;
36 | font: inherit;
37 | width: 100%;
38 | transition: 0.1s 0.2s;
39 | }
40 |
41 | .settingTextInput:hover {
42 | background-color: hsla(0, 0%, 100%, 0.1);
43 | }
44 |
45 | .settingTextInputSmall {
46 | max-width: 4rem;
47 | }
48 |
49 | .settingsGridColumnTitle {
50 | margin: 0;
51 | font-size: 1rem;
52 | }
53 |
54 | .mainLogoSection {
55 | display: flex;
56 | flex-direction: column;
57 | align-items: center;
58 | justify-content: space-between;
59 | }
60 |
61 | .mainLogo {
62 | font-size: 0.8rem;
63 | font-weight: bold;
64 | display: flex;
65 | flex-direction: column;
66 | justify-content: center;
67 | align-content: center;
68 | text-align: center;
69 | }
70 |
71 | .mainLogo span {
72 | margin-bottom: 0.7rem;
73 | }
74 |
75 | .ghIcon {
76 | width: 1.5rem;
77 | height: 1.5rem;
78 | cursor: pointer;
79 | text-decoration: none;
80 | color: inherit;
81 | opacity: 0.5;
82 | }
83 |
84 | .ghIcon svg {
85 | display: block;
86 | width: 100%;
87 | }
88 |
89 | .aboutSection {
90 | font-size: 0.8rem;
91 | }
92 |
93 | .aboutSection a, .aboutSection a:visited {
94 | color: inherit;
95 | text-underline-position: under;
96 | opacity: 0.5;
97 | text-decoration-color: hsla(0, 0%, 100%, 0.5);
98 | }
99 |
100 | .triggersColumn {
101 | border-left: 1px solid hsla(0, 0%, 100%, 0.1);
102 | border-right: 1px solid hsla(0, 0%, 100%, 0.1);
103 | display: grid;
104 | justify-items: center;
105 | align-items: center;
106 | padding: 0 3.5rem;
107 | }
108 |
109 | .randomButtonSection {
110 | border-bottom: 1px solid hsla(0, 0%, 100%, 0.1);
111 | }
112 |
113 | .triggerButton {
114 | background-color: white;
115 | border: none;
116 | font: inherit;
117 | color: var(--settingsBg);
118 | padding: 0.8em 1em;
119 | cursor: pointer;
120 | }
121 |
122 | .xBorder {
123 | height: 1px;
124 | width: 100%;
125 | background-color: hsla(0, 0%, 100%, 0.1);
126 | }
127 |
128 | .settingLabel {
129 | display: block;
130 | }
131 |
132 | .settingLabel, .settingNumberUnit {
133 | opacity: 0.5;
134 | }
135 |
136 | .settingWrapper {
137 | margin: 1.2rem 0 0 0;
138 | }
139 |
140 | .settingNumberInputWrapper {
141 | display: flex;
142 | justify-content: space-between;
143 | align-items: center;
144 | }
145 |
146 | .settingIllustration {
147 | display: block;
148 | }
149 |
150 | input[type=range] {
151 | display: block;
152 | }
153 |
154 | .settingIllustrationSection {
155 | height: 35px;
156 | display: flex;
157 | align-items: flex-end;
158 | margin-bottom: 0.5rem;
159 | }
160 |
161 | .switchSettingWrapper {
162 | display: flex;
163 | align-items: center;
164 | }
165 |
166 | .switchSettingWrapper .settingLabel {
167 | margin-bottom: 0;
168 | padding-left: 0.5rem;
169 | }
170 |
171 | .switchTrack {
172 | width: 2rem;
173 | height: 1rem;
174 | display: block;
175 | border-radius: 1rem;
176 | cursor: pointer;
177 | box-shadow: inset 0 0 0 1px hsla(0, 0%, 100%, 0.5);;
178 | transition: 0.2s;
179 | overflow: hidden;
180 | }
181 |
182 | .switchTrack input {
183 | opacity: 0;
184 | position: absolute;
185 | cursor: pointer;
186 | }
187 |
188 | .switchKnob {
189 | cursor: pointer;
190 | width: 1rem;
191 | height: 1rem;
192 | border-radius: 100%;
193 | display: block;
194 | box-shadow: inset 0 0 0 1px white, -1rem 0 0 10px white;
195 | transition: 0.2s;
196 | background-color: var(--settingsBg);
197 | position: relative;
198 | }
199 |
200 | .switchTrack input:checked + .switchKnob {
201 | transform: translate(1rem);
202 | }
203 |
204 | .settingButtonGroup {
205 | display: flex;
206 | font-size: 0.8rem;
207 | border: 1px solid hsla(0, 0%, 100%, 0.2);
208 | padding: 0.25rem;
209 | }
210 |
211 | .settingButton {
212 | appearance: none;
213 | border-radius: 0;
214 | border: none;
215 | font: inherit;
216 | font-size: 0.8rem;
217 | color: hsla(0, 0%, 100%, 0.5);
218 | padding: 0.4em 0.8em;
219 | cursor: pointer;
220 | background-color: transparent;
221 | }
222 |
223 | .settingButton.active {
224 | color: var(--settingsBg);
225 | background-color: white;
226 | }
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/src/slider.css:
--------------------------------------------------------------------------------
1 | input[type=range] {
2 | -webkit-appearance: none;
3 | width: 100%;
4 | background: transparent;
5 | height: 1.5rem;
6 | cursor: pointer;
7 | }
8 |
9 | input[type=range]::-webkit-slider-thumb {
10 | -webkit-appearance: none;
11 | }
12 |
13 | input[type=range]:focus {
14 | outline: none;
15 | }
16 |
17 | input[type=range]:hover::-webkit-slider-thumb,
18 | input[type=range]:focus::-webkit-slider-thumb {
19 | transform: scale(1.6);
20 | }
21 | input[type=range]:hover::-moz-range-thumb,
22 | input[type=range]:focus::-moz-range-thumb {
23 | transform: scale(1.6);
24 | }
25 |
26 | input[type=range]::-webkit-slider-thumb {
27 | -webkit-appearance: none;
28 | margin-top: -3px;
29 | height: 0.5rem;
30 | width: 0.5rem;
31 | background: white;
32 | border-radius: 100%;
33 | transition: 0.2s;
34 | }
35 |
36 | input[type=range]::-moz-range-thumb {
37 | margin-top: -3px;
38 | height: 0.5rem;
39 | width: 0.5rem;
40 | background: white;
41 | border-radius: 100%;
42 | transition: 0.2s;
43 | }
44 |
45 | input[type=range]::-webkit-slider-runnable-track {
46 | width: 100%;
47 | height: 2px;
48 | cursor: pointer;
49 | background: linear-gradient(90deg, hsla(0,0%,100%,1) 0%, hsla(0,0%,100%,1) var(--value), hsla(0,0%,100%,0.2) var(--value));;
50 | border: none;
51 | }
52 |
53 | input[type=range]::-moz-range-track {
54 | width: 100%;
55 | height: 2px;
56 | cursor: pointer;
57 | background: hsla(0,0%,100%,0.2);
58 | border: none;
59 | }
--------------------------------------------------------------------------------
/src/utilities.css:
--------------------------------------------------------------------------------
1 | .limitLineLength {
2 | max-width: 30em;
3 | }
4 |
5 | .c-bodyDimmed {
6 | color: var(--c-bodyDimmed);
7 | }
8 |
9 | .bgc-grey1 { background-color: var(--c-grey1) }
10 | .bgc-grey2 { background-color: var(--c-grey2) }
11 | .bgc-grey3 { background-color: var(--c-grey3) }
12 | .bgc-grey4 { background-color: var(--c-grey4) }
13 | .bgc-grey5 { background-color: var(--c-grey5) }
14 | .bgc-grey6 { background-color: var(--c-grey6) }
15 | .bgc-grey7 { background-color: var(--c-grey7) }
16 | .bgc-grey8 { background-color: var(--c-grey8) }
17 |
18 | .bgc-accent { background-color: var(--c-accent) }
19 | .bgc-accentContrasted { background-color: var(--c-accentContrasted) }
20 |
21 | .text-xs { font-size: var(--text-xs) }
22 | .text-s { font-size: var(--text-s) }
23 | .text-m { font-size: var(--text-m) }
24 | .text-l { font-size: var(--text-l) }
25 | .text-xl { font-size: var(--text-xl) }
26 |
27 | .mt-s { margin-top: var(--space-s) }
28 | .mt-m { margin-top: var(--space-m) }
29 | .mt-l { margin-top: var(--space-l) }
30 | .mt-xl { margin-top: var(--space-xl) }
31 | .mt-2xl { margin-top: var(--space-2xl) }
32 | .mt-3xl { margin-top: var(--space-3xl) }
33 | .mt-4xl { margin-top: var(--space-4xl) }
34 |
35 | .mb-s { margin-bottom: var(--space-s) }
36 | .mb-m { margin-bottom: var(--space-m) }
37 | .mb-l { margin-bottom: var(--space-l) }
38 | .mb-xl { margin-bottom: var(--space-xl) }
39 | .mb-2xl { margin-bottom: var(--space-2xl) }
40 | .mb-3xl { margin-bottom: var(--space-3xl) }
41 | .mb-4xl { margin-bottom: var(--space-4xl) }
42 |
43 | .ml-s { margin-left: var(--space-s) }
44 | .ml-m { margin-left: var(--space-m) }
45 | .ml-l { margin-left: var(--space-l) }
46 | .ml-xl { margin-left: var(--space-xl) }
47 | .ml-2xl { margin-left: var(--space-2xl) }
48 | .ml-3xl { margin-left: var(--space-3xl) }
49 | .ml-4xl { margin-left: var(--space-4xl) }
50 |
51 | .mr-s { margin-right: var(--space-s) }
52 | .mr-m { margin-right: var(--space-m) }
53 | .mr-l { margin-right: var(--space-l) }
54 | .mr-xl { margin-right: var(--space-xl) }
55 | .mr-2xl { margin-right: var(--space-2xl) }
56 | .mr-3xl { margin-right: var(--space-3xl) }
57 | .mr-4xl { margin-right: var(--space-4xl) }
58 |
59 | .pt-s { padding-top: var(--space-s) }
60 | .pt-m { padding-top: var(--space-m) }
61 | .pt-l { padding-top: var(--space-l) }
62 | .pt-xl { padding-top: var(--space-xl) }
63 | .pt-2xl { padding-top: var(--space-2xl) }
64 | .pt-3xl { padding-top: var(--space-3xl) }
65 | .pt-4xl { padding-top: var(--space-4xl) }
66 |
67 | .pb-s { padding-bottom: var(--space-s) }
68 | .pb-m { padding-bottom: var(--space-m) }
69 | .pb-l { padding-bottom: var(--space-l) }
70 | .pb-xl { padding-bottom: var(--space-xl) }
71 | .pb-2xl { padding-bottom: var(--space-2xl) }
72 | .pb-3xl { padding-bottom: var(--space-3xl) }
73 | .pb-4xl { padding-bottom: var(--space-4xl) }
74 |
75 | .pl-s { padding-left: var(--space-s) }
76 | .pl-m { padding-left: var(--space-m) }
77 | .pl-l { padding-left: var(--space-l) }
78 | .pl-xl { padding-left: var(--space-xl) }
79 | .pl-2xl { padding-left: var(--space-2xl) }
80 | .pl-3xl { padding-left: var(--space-3xl) }
81 | .pl-4xl { padding-left: var(--space-4xl) }
82 |
83 | .pr-s { padding-right: var(--space-s) }
84 | .pr-m { padding-right: var(--space-m) }
85 | .pr-l { padding-right: var(--space-l) }
86 | .pr-xl { padding-right: var(--space-xl) }
87 | .pr-2xl { padding-right: var(--space-2xl) }
88 | .pr-3xl { padding-right: var(--space-3xl) }
89 | .pr-4xl { padding-right: var(--space-4xl) }
--------------------------------------------------------------------------------
/src/utilities.js:
--------------------------------------------------------------------------------
1 | export const randomNumber = (min, max, full) => {
2 | const num = Math.random() * (max - min) + min
3 | let rand = min + Math.random() * (max - min);
4 | if (full === 'full') {
5 | return Math.round(rand)
6 | }
7 | return Math.round((num + Number.EPSILON) * 100) / 100
8 | }
9 |
10 | export const setProperty = (property, value) => {
11 | document.documentElement.style.setProperty(`--${property}`, value);
12 | }
13 |
14 | export const getProperty = (property) => {
15 | getComputedStyle(document.documentElement).getPropertyValue(`--${property}`);
16 | }
17 |
18 | export const fonts = [
19 | 'system-ui, sans-serif',
20 | 'IBM Plex Sans',
21 | 'Futura, sans-serif',
22 | 'Roboto Mono',
23 | 'Nunito',
24 | 'Helvetica, sans-serif',
25 | 'Merriweather',
26 | 'Work Sans',
27 | ]
28 |
29 | const buttonIsRound = [true, false, false, false]
30 |
31 | export const getRandomObject = () => {
32 | const randomFontsPosition = randomNumber(0, fonts.length-1, 'full')
33 | return {
34 | fontFamily: fonts[randomFontsPosition],
35 | textSizeIncrement: randomNumber(1.2, 1.4),
36 | baseTextSize: randomNumber(0.875, 1.1),
37 | textFrameRatio: randomNumber(1.2, 3),
38 | textFrameY: randomNumber(0.4, 1),
39 | unit: randomNumber(0.4, 0.8),
40 | spaceIncrement: randomNumber(1.5, 1.8),
41 | accentHue: randomNumber(0, 360, 'full'),
42 | accentSaturation: randomNumber(0, 100, 'full'),
43 | accentLightness: randomNumber(30, 60, 'full'),
44 | greySaturation: randomNumber(0, 10, 'full'),
45 | radius: randomNumber(0, 0.4),
46 | fieldBorderWidth: randomNumber(1, 3, 'full'),
47 | buttonRound: buttonIsRound[randomNumber(0, 3, 'full')],
48 | }
49 | }
50 |
51 | export const numberToUnit = (number, unit) => `${number}${unit}`
--------------------------------------------------------------------------------
/src/variables.js:
--------------------------------------------------------------------------------
1 | import tinycolor from 'tinycolor2'
2 |
3 | export const getVariables = ({
4 | baseTextSize,
5 | textSizeIncrement,
6 | fontFamily,
7 | unit,
8 | spaceIncrement,
9 | textFrameRatio,
10 | textFrameY,
11 | accentHue,
12 | accentSaturation,
13 | accentLightness,
14 | greySaturation,
15 | radius,
16 | fieldBorderWidth,
17 | }) => {
18 | const getAccentButtonColor = () => {
19 | const accentButtonBgLuminance = tinycolor(`hsl(${accentHue} ${accentSaturation} ${accentLightness})`).getLuminance()
20 | const lightness = accentButtonBgLuminance < 0.3 ? 95 : 15
21 |
22 | return `hsl(var(--accentH), var(--accentS), ${lightness}%)`
23 | }
24 |
25 | return {
26 | type:
27 | `--baseTextSize: ${baseTextSize}rem;
28 | --textSizeIncrement: ${textSizeIncrement};
29 |
30 | --text-xs: calc(var(--baseTextSize) / var(--textSizeIncrement));
31 | --text-s: var(--baseTextSize);
32 | --text-m: calc(var(--text-s) * var(--textSizeIncrement));
33 | --text-l: calc(var(--text-m) * var(--textSizeIncrement));
34 | --text-xl: calc(var(--text-l) * var(--textSizeIncrement));
35 |
36 | --lineHeightFixedAmount: 0.35rem;
37 | --lineHeightRelativeAmount: 1.1em;
38 | --globalLineHeight: calc(var(--lineHeightFixedAmount) + var(--lineHeightRelativeAmount));
39 |
40 | --fontFamily: ${fontFamily};`
41 | ,
42 | space:
43 | `--unit: ${unit}rem;
44 | --spaceIncrement: ${spaceIncrement};
45 |
46 | --space-s: var(--unit);
47 | --space-m: calc(var(--space-s) * var(--spaceIncrement));
48 | --space-l: calc(var(--space-m) * var(--spaceIncrement));
49 | --space-xl: calc(var(--space-l) * var(--spaceIncrement));
50 | --space-2xl: calc(var(--space-xl) * var(--spaceIncrement));
51 | --space-3xl: calc(var(--space-2xl) * var(--spaceIncrement));
52 | --space-4xl: calc(var(--space-3xl) * var(--spaceIncrement));`
53 | ,
54 | textFrame:
55 | `--textFrameRatio: ${textFrameRatio};
56 | --textFrameY: ${textFrameY}em;
57 | --textFrameX: calc(var(--textFrameY) * var(--textFrameRatio));`
58 | ,
59 | color:
60 | `--accentH: ${accentHue};
61 | --accentS: ${accentSaturation}%;
62 | --accentL: ${accentLightness}%;
63 | --c-accent: hsl(var(--accentH), var(--accentS), var(--accentL));
64 | --c-accentContrasted: ${getAccentButtonColor()};
65 |
66 | --greyH: var(--accentH);
67 | --greyS: ${greySaturation}%;
68 | --initialGreyLightness: 93%;
69 | --greyscaleLightnessIncrement: 11.3%;
70 |
71 | --grey1L: var(--initialGreyLightness);
72 | --grey2L: calc(var(--initialGreyLightness) - var(--greyscaleLightnessIncrement) * 1);
73 | --grey3L: calc(var(--initialGreyLightness) - var(--greyscaleLightnessIncrement) * 2);
74 | --grey4L: calc(var(--initialGreyLightness) - var(--greyscaleLightnessIncrement) * 3);
75 | --grey5L: calc(var(--initialGreyLightness) - var(--greyscaleLightnessIncrement) * 4);
76 | --grey6L: calc(var(--initialGreyLightness) - var(--greyscaleLightnessIncrement) * 5);
77 | --grey7L: calc(var(--initialGreyLightness) - var(--greyscaleLightnessIncrement) * 6);
78 | --grey8L: calc(var(--initialGreyLightness) - var(--greyscaleLightnessIncrement) * 7);
79 |
80 | --c-grey1: hsl(var(--greyH), var(--greyS), var(--grey1L));
81 | --c-grey2: hsl(var(--greyH), var(--greyS), var(--grey2L));
82 | --c-grey3: hsl(var(--greyH), var(--greyS), var(--grey3L));
83 | --c-grey4: hsl(var(--greyH), var(--greyS), var(--grey4L));
84 | --c-grey5: hsl(var(--greyH), var(--greyS), var(--grey5L));
85 | --c-grey6: hsl(var(--greyH), var(--greyS), var(--grey6L));
86 | --c-grey7: hsl(var(--greyH), var(--greyS), var(--grey7L));
87 | --c-grey8: hsl(var(--greyH), var(--greyS), var(--grey8L));
88 |
89 | --c-border: hsla(var(--greyH), var(--greyS), var(--grey8L), 0.1);
90 | --c-overlay: hsla(var(--greyH), var(--greyS), var(--grey8L), 0.07);
91 | --c-background: white;
92 | --c-body: var(--c-grey8);
93 | --c-bodyDimmed: hsla(var(--greyH), var(--greyS), var(--grey8L), 0.5);
94 | --c-fieldBorder: var(--c-grey2);
95 | --c-buttonBg: var(--c-grey2);`
96 | ,
97 | layer:
98 | `--radius: ${radius}rem;
99 |
100 | --fieldBorderWidth: ${fieldBorderWidth}px;`
101 | ,
102 | }
103 | }
104 |
105 | export const darkModeStyles =
106 | `body.darkMode {
107 | --c-border: hsla(var(--greyH), var(--greyS), var(--grey1L), 0.1);
108 | --c-overlay: hsla(var(--greyH), var(--greyS), var(--grey1L), 0.07);
109 | --c-background: var(--c-grey8);
110 | --c-body: var(--c-grey1);
111 | --c-bodyDimmed: hsla(var(--greyH), var(--greyS), var(--grey1L), 0.5);
112 | --c-fieldBorder: var(--c-grey6);
113 | --c-buttonBg: var(--c-grey6);
114 | }`
--------------------------------------------------------------------------------