-import { useEffect, useState } from "react";
+import { createContext, useEffect, useState } from "react";
+
import "../../../../css/bulma.css";
-import "./App.css";
-import Slider from "./components/Slider";
+import cn from "./App.module.css";
+
+import Color from "./components/Color";
const COLORS = ["primary", "link", "info", "success", "warning", "danger"];
"-gap": "gap",
};
+export const CustomizerContext = createContext({
+ cssvars: {},
+ getVar: () => {},
+ updateVar: () => {},
+});
+
function App() {
- const [vars, setVars] = useState({});
+ const initialContext = {
+ cssvars: {},
+ getVar: (id) => {
+ return context.cssvars[id];
+ },
+ updateVar: (id, newValue) => {
+ setContext((context) => {
+ return {
+ ...context,
+ cssvars: {
+ ...context.cssvars,
+ [id]: {
+ ...context.cssvars[id],
+ value: newValue,
+ },
+ },
+ };
+ });
+ },
+ };
+ const [context, setContext] = useState(initialContext);
+
+ console.log("ZLOG context", context);
useEffect(() => {
const rootStyle = window.getComputedStyle(document.documentElement);
kind: SUFFIX_TO_KIND[suffix] || "any",
original,
unit,
+ current: Number(value),
start: Number(value),
};
});
- setVars(cssvars);
+ setContext((context) => {
+ return {
+ ...context,
+ cssvars,
+ };
+ });
}, []);
+ // useEffect(() => {
+ // Object.values(context.cssvars).forEach((cssvar) => {
+ // const { id, current, unit } = cssvar;
+ // const computedValue = `${current}${unit}`;
+
+ // document.documentElement.style.setProperty(
+ // `--bulma-${id}`,
+ // computedValue,
+ // );
+ // });
+ // }, [context.cssvars]);
+
+ // useEffect(() => {
+ // const computedValue = `${current}${unit}`;
+
+ // if (current === start) {
+ // document.documentElement.style.removeProperty(`--bulma-${id}`);
+ // } else {
+ // document.documentElement.style.setProperty(
+ // `--bulma-${id}`,
+ // computedValue,
+ // );
+ // }
+ // }, [id, start, unit, value]);
+
return (
- <section className="section">
- <div className="card">
- <div className="card-content">
- {COLORS.map((color) => {
- const h = `${color}-h`;
-
- if (!(h in vars)) {
- return;
- }
-
- const s = `${color}-s`;
- const l = `${color}-l`;
-
- return (
- <div key={color} className="block">
- <code>{color}</code>
-
- <Slider
- id={h}
- kind="hue"
- color={color}
- original={vars[h].original}
- start={vars[h].start}
- unit={vars[h].unit}
- />
-
- <Slider
- id={s}
- kind="saturation"
- color={color}
- original={vars[s].original}
- start={vars[s].start}
- unit={vars[s].unit}
- />
-
- <Slider
- id={l}
- kind="lightness"
- color={color}
- original={vars[l].original}
- start={vars[l].start}
- unit={vars[l].unit}
- />
- </div>
- );
- })}
-
- {/* {vars.map((v) => {
- const { id, kind, original, unit, start } = v;
-
- return (
- <div key={id} className="block">
- <code>{id}</code>
- <Slider
- id={id}
- kind={kind}
- original={original}
- start={start}
- unit={unit}
- />
- </div>
- );
- })} */}
-
- <div className="buttons">
- <button className="button">Button</button>
- <button className="button is-primary">Primary</button>
- <button className="button is-link">Link</button>
- <button className="button is-info">Info</button>
- <button className="button is-success">Success</button>
- <button className="button is-warning">Warning</button>
- <button className="button is-danger">Danger</button>
+ <CustomizerContext.Provider value={context}>
+ <section className="section">
+ <div className="card">
+ <div className="card-content">
+ <div className={cn.colors}>
+ {COLORS.map((color) => {
+ return <Color key={color} color={color} />;
+ })}
+ </div>
</div>
</div>
- </div>
- </section>
+ </section>
+ </CustomizerContext.Provider>
);
}
--- /dev/null
+.colors {
+}
--- /dev/null
+import { useContext } from "react";
+import PropTypes from "prop-types";
+
+import Slider from "./Slider";
+
+import cn from "./Color.module.css";
+import { CustomizerContext } from "../App";
+
+function Color({ color }) {
+ // const [hue, setHue] = useState(h.start);
+ // const [saturation, setSaturation] = useState(s.start);
+ // const [lightness, setLightness] = useState(l.start);
+
+ const { cssvars } = useContext(CustomizerContext);
+ const hName = `${color}-h`;
+ const sName = `${color}-s`;
+ const lName = `${color}-l`;
+ const h = cssvars[hName];
+ const s = cssvars[sName];
+ const l = cssvars[lName];
+
+ const mainStyle = {
+ "--h": `var(--bulma-${hName})`,
+ "--s": `var(--bulma-${sName})`,
+ "--l": `var(--bulma-${lName})`,
+ };
+ const name = color.charAt(0).toUpperCase() + color.slice(1);
+
+ const handleReset = (event) => {
+ event.preventDefault();
+ document.documentElement.style.removeProperty(`--bulma-${hName}`);
+ document.documentElement.style.removeProperty(`--bulma-${sName}`);
+ document.documentElement.style.removeProperty(`--bulma-${lName}`);
+ };
+
+ if (!h) {
+ return;
+ }
+
+ return (
+ <div className={cn.main} style={mainStyle}>
+ <div className={cn.side}>
+ <div className={cn.name}>
+ <div className={cn.swatch} />
+ <p>{name}</p>
+ </div>
+
+ <button className="button is-small" onClick={handleReset}>
+ Reset
+ </button>
+ </div>
+
+ <div className={cn.lines}>
+ <div className={cn.line}>
+ <p>Hue</p>
+ <Slider id={hName} kind="hue" color={color} />
+ <p>
+ <code>
+ {h.current}
+ {h.unit}
+ </code>
+ </p>
+ </div>
+
+ <div className={cn.line}>
+ <p>Saturation</p>
+ <Slider id={sName} kind="saturation" color={color} />
+ <p>
+ <code>
+ {s.current}
+ {s.unit}
+ </code>
+ </p>
+ </div>
+
+ <div className={cn.line}>
+ <p>Lightness</p>
+ <Slider id={lName} kind="lightness" color={color} />
+ <p>
+ <code>
+ {l.current}
+ {l.unit}
+ </code>
+ </p>
+ </div>
+ </div>
+
+ <div className={cn.side}>
+ <button className={`button is-${color}`}>{name}</button>
+ </div>
+ </div>
+ );
+}
+
+Color.propTypes = {
+ color: PropTypes.string,
+ h: PropTypes.string,
+ s: PropTypes.string,
+ l: PropTypes.string,
+};
+
+export default Color;
--- /dev/null
+.main {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1.5rem;
+ border-bottom: 1px solid var(--bulma-border);
+ padding: 1.25rem;
+}
+
+.side {
+ width: 15rem;
+}
+
+.swatch {
+ background-color: hsl(var(--h) var(--s) var(--l));
+ height: 1.25rem;
+ width: 1.25rem;
+ border-radius: 0.25rem;
+ flex-shrink: 0;
+}
+
+.name {
+ gap: 1rem;
+ display: flex;
+ align-items: center;
+ margin-bottom: 0.5rem;
+}
+
+.name p {
+ color: var(--bulma-text-strong);
+ font-size: 1.25em;
+ font-weight: 600;
+}
+
+.lines {
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+}
+
+.line {
+ display: flex;
+ align-items: center;
+ gap: 1.5rem;
+}
+
+.line p {
+ color: var(--bulma-text-strong);
+ width: 6rem;
+}
-import { useEffect, useRef, useState } from "react";
+import { useContext, useEffect, useRef, useState } from "react";
import PropTypes from "prop-types";
import classNames from "classnames";
import cn from "./Slider.module.css";
+import { CustomizerContext } from "../App";
const RANGES = {
hue: [0, 360, 1],
return Math.round(newValue);
};
-function Slider({ id, color, kind, start, unit }) {
+function Slider({ id, color, kind }) {
+ const { cssvars, updateVar } = useContext(CustomizerContext);
+ const { start, current, unit } = cssvars[id];
const [min, max] = RANGES[kind];
const sliderRef = useRef(null);
const handleRef = useRef(null);
- const [value, setValue] = useState(start);
const [isMoving, setMoving] = useState(false);
const [x, setX] = useState(valueToX(start, 240, min, max));
};
useEffect(() => {
- const computedValue = `${value}${unit}`;
+ const computedValue = `${current}${unit}`;
- if (value === start) {
+ if (current === start) {
document.documentElement.style.removeProperty(`--bulma-${id}`);
} else {
document.documentElement.style.setProperty(
computedValue,
);
}
- }, [id, start, unit, value]);
+ }, [current, id, start, unit]);
useEffect(() => {
const slider = sliderRef.current;
const sliderRect = slider.getBoundingClientRect();
const final = xToValue(x, sliderRect.width, min, max);
- setValue(final);
- }, [min, max, unit, x]);
+ updateVar(id, final);
+ }, [id, min, max, updateVar, unit, x]);
useEffect(() => {
const docMouseMove = (event) => {
original: PropTypes.string,
start: PropTypes.number,
unit: PropTypes.string,
+ getValue: PropTypes.func,
};
export default Slider;
{
"name": "bulma-docs",
- "version": "1.0.0",
+ "version": "1.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "bulma-docs",
- "version": "1.0.0",
+ "version": "1.0.1",
"license": "MIT",
- "dependencies": {
- "sass": "^1.71.1"
- },
"devDependencies": {
"@shopify/prettier-plugin-liquid": "^1.4.4",
- "prettier": "^3.2.4"
+ "prettier": "^3.2.4",
+ "sass": "^1.71.1"
}
},
"node_modules/@shopify/liquid-html-parser": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
"engines": {
"node": ">=8"
},
}
},
"node_modules/braces": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
- "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
"dependencies": {
- "fill-range": "^7.0.1"
+ "fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
}
},
"node_modules/fill-range": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
- "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
"dependencies": {
"is-glob": "^4.0.1"
},
"node_modules/immutable": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz",
- "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw=="
+ "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==",
+ "dev": true
},
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
"dependencies": {
"binary-extensions": "^2.0.0"
},
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
"engines": {
"node": ">=0.10.0"
}
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
"dependencies": {
"is-extglob": "^2.1.1"
},
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
"engines": {
"node": ">=0.12.0"
}
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
"engines": {
"node": ">=0.10.0"
}
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
"engines": {
"node": ">=8.6"
},
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
"dependencies": {
"picomatch": "^2.2.1"
},
"version": "1.72.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.72.0.tgz",
"integrity": "sha512-Gpczt3WA56Ly0Mn8Sl21Vj94s1axi9hDIzDFn9Ph9x3C3p4nNyvsqJoQyVXKou6cBlfFWEgRW4rT8Tb4i3XnVA==",
+ "dev": true,
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
+ "dev": true,
"engines": {
"node": ">=0.10.0"
}
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
"dependencies": {
"is-number": "^7.0.0"
},