Merge pull request #3848 from jgthms/customizer

Add Bulma Customizer
This commit is contained in:
Jeremy Thomas 2024-06-27 04:21:44 +01:00 committed by GitHub
commit bbd8d21bf2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
72 changed files with 8872 additions and 22 deletions

1
.gitignore vendored
View File

@ -17,5 +17,6 @@ test.css.map
_gh_pages
_site
dev
dist
node_modules
test/output/

View File

@ -0,0 +1,39 @@
<div id="bulma-customizer-app" class="is-hidden-mobile"></div>
<div id="scope" class="is-hidden">
<div class="breadcrumb"></div>
<div class="card"></div>
<div class="dropdown"></div>
<div class="menu"></div>
<div class="message"></div>
<div class="modal"></div>
<div class="navbar"></div>
<div class="pagination"></div>
<div class="panel"></div>
<div class="tabs"></div>
<div class="box"></div>
<div class="content"></div>
<div class="delete"></div>
<div class="icon"></div>
<div class="notification"></div>
<div class="progress"></div>
<div class="table"></div>
<div class="tag"></div>
<div class="title"></div>
<input class="input" />
<div class="file"></div>
<div class="columns"></div>
<div class="grid"></div>
<div class="footer"></div>
<div class="hero"></div>
<div class="media"></div>
<div class="section"></div>
</div>
<link
rel="stylesheet"
href="{{ site.url }}/assets/javascript/bulma-customizer/index.css"
/>
<script
defer
src="{{ site.url }}/assets/javascript/bulma-customizer/index.js"
></script>

View File

@ -29,5 +29,6 @@
});
</script>
<script src="{{ site.url }}/assets/javascript/main.js"></script>
{% include global/customizer.html %}
</body>
</html>

View File

@ -0,0 +1,21 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
settings: { react: { version: '18.2' } },
plugins: ['react-refresh'],
rules: {
'react/jsx-no-target-blank': 'off',
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}

24
docs/_react/bulma-customizer/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -0,0 +1,8 @@
# React + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

View File

@ -0,0 +1,46 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
<style type="text/css">
#bulma-customizer-app {
}
</style>
</head>
<body>
<div id="bulma-customizer-app"></div>
<div id="scope" class="is-hidden">
<div class="breadcrumb"></div>
<div class="card"></div>
<div class="dropdown"></div>
<div class="menu"></div>
<div class="message"></div>
<div class="modal"></div>
<div class="navbar"></div>
<div class="pagination"></div>
<div class="panel"></div>
<div class="tabs"></div>
<div class="box"></div>
<div class="content"></div>
<div class="delete"></div>
<div class="icon"></div>
<div class="notification"></div>
<div class="progress"></div>
<table class="table"></table>
<div class="tag"></div>
<div class="title"></div>
<input class="input" />
<div class="file"></div>
<div class="columns"></div>
<div class="grid"></div>
<div class="footer"></div>
<div class="hero"></div>
<div class="media"></div>
<div class="section"></div>
</div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
{
"name": "bulma-customizer",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"classnames": "^2.5.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-syntax-highlighter": "^15.5.0"
},
"devDependencies": {
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
"eslint": "^8.57.0",
"eslint-plugin-react": "^7.34.2",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.7",
"vite": "^5.3.1"
}
}

View File

@ -0,0 +1,26 @@
:root {
--gh-dark: #25292e;
--gh-dimmed: #6a737d;
--gh-text: #e1e4e8;
--gh-red: #f97583;
--gh-orange: #ffab70;
--gh-yellow: #ffea7f;
--gh-green: #85e89d;
--gh-blue: #79b8ff;
--gh-blue-light: #9ecbff;
--gh-purple: #b392f0;
--gh-pink: #f692ce;
/*
"black": "#1b1f23",
"white": "#fff",
"gray": ["#fafbfc", "#f6f8fa", "#e1e4e8", "#d1d5da", "#959da5", "#6a737d", "#586069", "#444d56", "#2f363d", "#24292e"],
"blue": ["#f1f8ff", "#dbedff", "#c8e1ff", "#79b8ff", "#2188ff", "#0366d6", "#005cc5", "#044289", "#032f62", "#05264c"],
"green": ["#f0fff4", "#dcffe4", "#bef5cb", "#85e89d", "#34d058", "#28a745", "#22863a", "#176f2c", "#165c26", "#144620"],
"yellow": ["#fffdef", "#fffbdd", "#fff5b1", "#ffea7f", "#ffdf5d", "#ffd33d", "#f9c513", "#dbab09", "#b08800", "#735c0f"],
"orange": ["#fff8f2", "#ffebda", "#ffd1ac", "#ffab70", "#fb8532", "#f66a0a", "#e36209", "#d15704", "#c24e00", "#a04100"],
"red": ["#ffeef0", "#ffdce0", "#fdaeb7", "#f97583", "#ea4a5a", "#d73a49", "#cb2431", "#b31d28", "#9e1c23", "#86181d"],
"purple": ["#f5f0ff", "#e6dcfd", "#d1bcf9", "#b392f0", "#8a63d2", "#6f42c1", "#5a32a3", "#4c2889", "#3a1d6e", "#29134e"],
"pink": ["#ffeef8", "#fedbf0", "#f9b3dd", "#f692ce", "#ec6cb9", "#ea4aaa", "#d03592", "#b93a86", "#99306f", "#6d224f"]
*/
}

View File

@ -0,0 +1,592 @@
import { createContext, useEffect, useRef, useState } from "react";
import classNames from "classnames";
// import "../../../../css/bulma.css";
import "./App.css";
import cn from "./App.module.css";
import { CSSVAR_KEYS } from "./constants";
import { unslug } from "./utils";
import Colors from "./pages/Colors";
import Scheme from "./pages/Scheme";
import Typography from "./pages/Typography";
import Other from "./pages/Other";
import Generic from "./pages/Generic";
import Skeleton from "./pages/Skeleton";
import Breadcrumb from "./pages/components/Breadcrumb";
import Card from "./pages/components/Card";
import Dropdown from "./pages/components/Dropdown";
import Menu from "./pages/components/Menu";
import Message from "./pages/components/Message";
import Modal from "./pages/components/Modal";
import Navbar from "./pages/components/Navbar";
import Pagination from "./pages/components/Pagination";
import Panel from "./pages/components/Panel";
import Tabs from "./pages/components/Tabs";
import Box from "./pages/elements/Box";
import Content from "./pages/elements/Content";
import Delete from "./pages/elements/Delete";
import Icon from "./pages/elements/Icon";
import Notification from "./pages/elements/Notification";
import Progress from "./pages/elements/Progress";
import Table from "./pages/elements/Table";
import Tag from "./pages/elements/Tag";
import Title from "./pages/elements/Title";
import Control from "./pages/form/Control";
import Input from "./pages/form/Input";
import File from "./pages/form/File";
import Columns from "./pages/grid/Columns";
import Grid from "./pages/grid/Grid";
import Footer from "./pages/layout/Footer";
import Hero from "./pages/layout/Hero";
import Media from "./pages/layout/Media";
import Section from "./pages/layout/Section";
import Export from "./components/Export";
const SUFFIX_TO_KIND = {
"-h": "hue",
"-s": "saturation",
"-l": "lightness",
"-delta": "delta",
"-color": "color",
};
const UNITS = ["deg", "%"];
const PAGE_TO_COMPONENT = {
colors: <Colors />,
scheme: <Scheme />,
typography: <Typography />,
other: <Other />,
generic: <Generic />,
skeleton: <Skeleton />,
// Components
breadcrumb: <Breadcrumb />,
card: <Card />,
dropdown: <Dropdown />,
menu: <Menu />,
message: <Message />,
modal: <Modal />,
navbar: <Navbar />,
pagination: <Pagination />,
panel: <Panel />,
tabs: <Tabs />,
// Elements
box: <Box />,
content: <Content />,
delete: <Delete />,
icon: <Icon />,
notification: <Notification />,
progress: <Progress />,
table: <Table />,
tag: <Tag />,
title: <Title />,
// Form
control: <Control />,
input: <Input />,
file: <File />,
// Grid
columns: <Columns />,
grid: <Grid />,
// Layout
footer: <Footer />,
hero: <Hero />,
media: <Media />,
section: <Section />,
// Export
export: <Export />,
};
const TAB_IDS = [
"Global Variables",
"Components",
"Elements",
"Form",
"Grid",
"Layout",
"Export",
];
const PAGE_IDS = {
"Global Variables": [
"colors",
"scheme",
"typography",
"other",
"generic",
"skeleton",
],
Components: [
"breadcrumb",
"card",
"dropdown",
"menu",
"message",
"modal",
"navbar",
"pagination",
"panel",
"tabs",
],
Elements: [
"box",
"content",
"delete",
"icon",
"notification",
"progress",
"table",
"tag",
"title",
],
Form: ["control", "input", "file"],
Grid: ["columns", "grid"],
Layout: ["footer", "hero", "media", "section"],
Export: ["export"],
};
const LOCAL_STORAGE_KEY = "bulma-customizer-vars";
export const CustomizerContext = createContext({
isOpen: false,
showExport: false,
cssvars: {},
saved: {},
currentTab: "",
currentPage: "",
getVar: () => {},
changeTab: () => {},
changePage: () => {},
updateVar: () => {},
hideExport: () => {},
});
function App() {
const styleRef = useRef();
const initialContext = {
isOpen: false,
showExport: false,
cssvars: {},
saved: {},
currentTab: "Global Variables",
currentPage: "colors",
getVar: (id) => {
return context.cssvars[id];
},
changeTab: (tabId) => {
setContext((context) => {
return {
...context,
currentTab: tabId,
};
});
},
changePage: (pageId) => {
setContext((context) => {
return {
...context,
currentPage: pageId,
};
});
},
updateVar: (id, newValue) => {
setContext((context) => {
return {
...context,
cssvars: {
...context.cssvars,
[id]: {
...context.cssvars[id],
current: newValue,
},
},
};
});
},
resetVars: () => {
setContext((context) => {
const cssvars = {};
for (const [key, value] of Object.entries(context.cssvars)) {
const item = { ...value, current: value.start };
cssvars[key] = item;
}
return {
...context,
cssvars,
};
});
},
hideExport: () => {
setContext((context) => {
return {
...context,
showExport: false,
};
});
},
};
const [context, setContext] = useState(() => {
const saved = localStorage.getItem(LOCAL_STORAGE_KEY);
if (saved) {
const initialValue = JSON.parse(saved);
initialContext.cssvars = initialValue;
initialContext.saved = initialValue;
}
return initialContext;
});
const handleTabChange = (event) => {
event.preventDefault();
const tabId = event.target.value;
context.changeTab(tabId);
context.changePage(PAGE_IDS[tabId][0]);
};
const handlePageChange = (event, pageId) => {
event.preventDefault();
context.changePage(pageId);
};
const handleOpening = (event) => {
event.preventDefault();
setContext((context) => {
return {
...context,
isOpen: !context.isOpen,
};
});
};
const handleExport = (event) => {
event.preventDefault();
setContext((context) => {
return {
...context,
showExport: !context.showExport,
};
});
};
// Get the computed styles of all cssvars
useEffect(() => {
const styles = {
root: window.getComputedStyle(document.documentElement),
breadcrumb: window.getComputedStyle(
document.querySelector(".breadcrumb"),
),
card: window.getComputedStyle(document.querySelector(".card")),
dropdown: window.getComputedStyle(document.querySelector(".dropdown")),
menu: window.getComputedStyle(document.querySelector(".menu")),
message: window.getComputedStyle(document.querySelector(".message")),
modal: window.getComputedStyle(document.querySelector(".modal")),
navbar: window.getComputedStyle(document.querySelector(".navbar")),
pagination: window.getComputedStyle(
document.querySelector(".pagination"),
),
panel: window.getComputedStyle(document.querySelector(".panel")),
tabs: window.getComputedStyle(document.querySelector(".tabs")),
box: window.getComputedStyle(document.querySelector(".box")),
content: window.getComputedStyle(document.querySelector(".content")),
delete: window.getComputedStyle(document.querySelector(".delete")),
icon: window.getComputedStyle(document.querySelector(".icon")),
notification: window.getComputedStyle(
document.querySelector(".notification"),
),
progress: window.getComputedStyle(document.querySelector(".progress")),
table: window.getComputedStyle(document.querySelector(".table")),
tag: window.getComputedStyle(document.querySelector(".tag")),
title: window.getComputedStyle(document.querySelector(".title")),
file: window.getComputedStyle(document.querySelector(".file")),
input: window.getComputedStyle(document.querySelector(".input")),
columns: window.getComputedStyle(document.querySelector(".columns")),
grid: window.getComputedStyle(document.querySelector(".grid")),
footer: window.getComputedStyle(document.querySelector(".footer")),
hero: window.getComputedStyle(document.querySelector(".hero")),
media: window.getComputedStyle(document.querySelector(".media")),
section: window.getComputedStyle(document.querySelector(".section")),
};
const cssvars = {};
const allKeys = Object.values(PAGE_IDS)
.flat()
.map((pageId) => {
if (!(pageId in CSSVAR_KEYS)) {
return;
}
return CSSVAR_KEYS[pageId];
})
.flat();
const allKeyIds = allKeys.map((i) => {
if (!i) {
return;
}
return i.id;
});
allKeyIds.map((key) => {
if (!key) {
return;
}
// Keep the value saved in localStorage
if (key in context.saved) {
cssvars[key] = context.saved[key];
return;
}
let original;
let scope = ":root";
if (key.startsWith("breadcrumb")) {
scope = ".breadcrumb";
original = styles.breadcrumb.getPropertyValue(`--bulma-${key}`);
} else if (key.startsWith("card")) {
scope = ".card";
original = styles.card.getPropertyValue(`--bulma-${key}`);
} else if (key.startsWith("dropdown")) {
scope = ".dropdown";
original = styles.dropdown.getPropertyValue(`--bulma-${key}`);
} else if (key.startsWith("menu")) {
scope = ".menu";
original = styles.menu.getPropertyValue(`--bulma-${key}`);
} else if (key.startsWith("message")) {
scope = ".message";
original = styles.message.getPropertyValue(`--bulma-${key}`);
} else if (key.startsWith("modal")) {
scope = ".modal";
original = styles.modal.getPropertyValue(`--bulma-${key}`);
} else if (key.startsWith("navbar")) {
scope = ".navbar";
original = styles.navbar.getPropertyValue(`--bulma-${key}`);
} else if (key.startsWith("pagination")) {
scope = ".pagination";
original = styles.pagination.getPropertyValue(`--bulma-${key}`);
} else if (key.startsWith("panel")) {
scope = ".panel";
original = styles.panel.getPropertyValue(`--bulma-${key}`);
} else if (key.startsWith("tabs")) {
scope = ".tabs";
original = styles.tabs.getPropertyValue(`--bulma-${key}`);
} else if (key.startsWith("box")) {
scope = ".box";
original = styles.box.getPropertyValue(`--bulma-${key}`);
} else if (key.startsWith("content")) {
scope = ".content";
original = styles.content.getPropertyValue(`--bulma-${key}`);
} else if (key.startsWith("delete")) {
scope = ".delete";
original = styles.delete.getPropertyValue(`--bulma-${key}`);
} else if (key.startsWith("icon")) {
scope = ".icon";
original = styles.icon.getPropertyValue(`--bulma-${key}`);
} else if (key.startsWith("notification")) {
scope = ".notification";
original = styles.notification.getPropertyValue(`--bulma-${key}`);
} else if (key.startsWith("progress")) {
scope = ".progress";
original = styles.progress.getPropertyValue(`--bulma-${key}`);
} else if (key.startsWith("table")) {
scope = ".table";
original = styles.table.getPropertyValue(`--bulma-${key}`);
} else if (key.startsWith("tag")) {
scope = ".tag";
original = styles.tag.getPropertyValue(`--bulma-${key}`);
} else if (key.startsWith("title")) {
scope = ".title";
original = styles.title.getPropertyValue(`--bulma-${key}`);
} else if (key.startsWith("file")) {
scope = ".file";
original = styles.file.getPropertyValue(`--bulma-${key}`);
} else if (key.startsWith("input")) {
scope = ".input";
original = styles.input.getPropertyValue(`--bulma-${key}`);
} else if (key.startsWith("columns")) {
scope = ".columns";
original = styles.columns.getPropertyValue(`--bulma-${key}`);
} else if (key.startsWith("grid")) {
scope = ".grid";
original = styles.grid.getPropertyValue(`--bulma-${key}`);
} else if (key.startsWith("footer")) {
scope = ".footer";
original = styles.footer.getPropertyValue(`--bulma-${key}`);
} else if (key.startsWith("hero")) {
scope = ".hero";
original = styles.hero.getPropertyValue(`--bulma-${key}`);
} else if (key.startsWith("media")) {
scope = ".media";
original = styles.media.getPropertyValue(`--bulma-${key}`);
} else if (key.startsWith("section")) {
scope = ".section";
original = styles.section.getPropertyValue(`--bulma-${key}`);
} else {
original = styles.root.getPropertyValue(`--bulma-${key}`);
}
const suffix = Object.keys(SUFFIX_TO_KIND).find((kind) =>
key.endsWith(kind),
);
const unit = UNITS.find((unit) => original.endsWith(unit)) || "";
const value = unit !== "" ? original.split(unit)[0] : original;
const description =
allKeys.find((el) => el.id === key)?.description || "None";
cssvars[key] = {
id: key,
kind: SUFFIX_TO_KIND[suffix] || "any",
original,
unit,
current: value,
start: value,
description,
scope,
};
});
setContext((context) => {
return {
...context,
cssvars,
};
});
}, [context.saved]);
// Update the styling when the cssvars change
useEffect(() => {
const rules = {};
const storedVars = {};
Object.values(context.cssvars).forEach((cssvar) => {
const { id, current, start, scope, unit } = cssvar;
if (current == start) {
return;
}
storedVars[id] = cssvar;
const computedValue = `${current}${unit}`;
const declaration = `--bulma-${id}: ${computedValue} !important;`;
if (scope in rules) {
rules[scope].push(declaration);
} else {
rules[scope] = [declaration];
}
});
let content = "";
for (const [key, arr] of Object.entries(rules)) {
content += `${key} {`;
arr.forEach((item) => (content += item));
content += `}`;
}
if (styleRef) {
styleRef.current.innerHTML = content;
}
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(storedVars));
}, [context.cssvars]);
// Computed values
// const isExportAvailable = Object.values(context.cssvars).some(
// (item) => item.current != item.start,
// );
// Styles
const tabsStyle = {
"--bulma-tabs-link-active-color": "var(--bulma-primary)",
};
const customizerClass = classNames({
[cn.customizer]: true,
[cn.open]: context.isOpen,
});
const controlsClass = classNames({
[cn.controls]: true,
});
const exportClass = classNames({
[cn.button]: true,
"is-hidden": !context.isOpen,
button: !context.showExport,
"button is-primary": context.showExport,
});
const buttonClass = classNames({
[cn.button]: true,
"button is-primary": !context.isOpen,
button: context.isOpen,
});
return (
<CustomizerContext.Provider value={context}>
<div className={cn.app}>
<style ref={styleRef} />
<div className={customizerClass}>
{context.showExport ? (
PAGE_TO_COMPONENT.export
) : (
<>
{" "}
<div className={controlsClass}>
<div className="select" style={tabsStyle}>
<select onChange={handleTabChange} value={context.currentTab}>
{TAB_IDS.map((tabId) => {
return (
<option key={tabId} value={tabId}>
{unslug(tabId)}
</option>
);
})}
</select>
</div>
{PAGE_IDS[context.currentTab].map((pageId) => {
const buttonClass = classNames({
button: true,
"is-primary": pageId === context.currentPage,
});
return (
<button
className={buttonClass}
key={pageId}
onClick={(e) => handlePageChange(e, pageId)}
>
{unslug(pageId)}
</button>
);
})}
</div>
{PAGE_TO_COMPONENT[context.currentPage]}
</>
)}
</div>
<div className={cn.buttons}>
<button className={exportClass} onClick={handleExport}>
<i className="fa-solid fa-code"></i>
<span>Export</span>
</button>
<button className={buttonClass} onClick={handleOpening}>
<i className="fa-solid fa-palette"></i>
<span>
{context.isOpen ? "Close Customizer" : "Open Customizer"}
</span>
</button>
</div>
</div>
</CustomizerContext.Provider>
);
}
export default App;

View File

@ -0,0 +1,55 @@
.app {
background-color: red;
--var-item-side-width: 12rem;
--var-item-slider-width: 30rem;
--var-item-padding: 1rem;
--var-item-gap: 1rem;
bottom: 1rem;
overflow: hidden;
position: fixed;
right: 1rem;
z-index: 100;
}
.customizer {
background: var(--bulma-scheme-main);
box-shadow: var(--bulma-shadow);
border-radius: 0.5rem;
transform-origin: bottom right;
transition-duration: 300ms;
transition-property: opacity, transform;
transform: scale(0.98) translate(2px, 2px);
opacity: 0;
pointer-events: none;
width: 33rem;
top: 1rem;
position: fixed;
bottom: 4.5rem;
right: 1rem;
overflow-y: auto;
}
.open {
opacity: 1;
pointer-events: auto;
transform: none;
}
.controls {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
padding: 0.5rem;
}
.buttons {
display: flex;
gap: 0.5rem;
bottom: 1rem;
right: 1rem;
position: fixed;
}
.button {
gap: 0.5rem;
}

View File

@ -0,0 +1,264 @@
import { useContext } from "react";
import PropTypes from "prop-types";
import Slider from "./Slider";
import cn from "./Color.module.css";
import { CustomizerContext } from "../App";
import classNames from "classnames";
export function hslToHex(h, s, l) {
s /= 100;
l /= 100;
let c = (1 - Math.abs(2 * l - 1)) * s;
let x = c * (1 - Math.abs(((h / 60) % 2) - 1));
let m = l - c / 2;
let r = 0,
g = 0,
b = 0;
if (0 <= h && h < 60) {
r = c;
g = x;
b = 0;
} else if (60 <= h && h < 120) {
r = x;
g = c;
b = 0;
} else if (120 <= h && h < 180) {
r = 0;
g = c;
b = x;
} else if (180 <= h && h < 240) {
r = 0;
g = x;
b = c;
} else if (240 <= h && h < 300) {
r = x;
g = 0;
b = c;
} else if (300 <= h && h < 360) {
r = c;
g = 0;
b = x;
}
r = Math.round((r + m) * 255);
g = Math.round((g + m) * 255);
b = Math.round((b + m) * 255);
return `${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase()}`;
}
function hexToHsl(hex) {
// Remove the hash at the start if it's there
hex = hex.replace(/^#/, "");
// Parse the hex values
let r = parseInt(hex.slice(0, 2), 16);
let g = parseInt(hex.slice(2, 4), 16);
let b = parseInt(hex.slice(4, 6), 16);
// Convert the RGB values to the range [0, 1]
r /= 255;
g /= 255;
b /= 255;
// Find the maximum and minimum values to get the lightness
let max = Math.max(r, g, b);
let min = Math.min(r, g, b);
let h,
s,
l = (max + min) / 2;
if (max === min) {
h = s = 0; // Achromatic
} else {
let d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / d + 2;
break;
case b:
h = (r - g) / d + 4;
break;
}
h *= 60;
}
return {
hue: Math.round(h),
saturation: Math.round(s * 100),
lightness: Math.round(l * 100),
};
}
function Color({ color }) {
const { cssvars, updateVar } = 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();
updateVar(h.id, h.start);
updateVar(s.id, s.start);
updateVar(l.id, l.start);
};
const handleHexInput = (event) => {
event.preventDefault();
let value = window.prompt("Enter a Hexadecimal value (e.g. 00d1b2)");
if (value.startsWith("#")) {
value = value.replace(/^#/, "");
}
const hexPattern = /^([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$/;
if (!hexPattern.test(value) || value.length > 6) {
window.prompt("That is not a valid Hexadecimal value. Please try again.");
return;
}
if (value.length === 3) {
value = value[0] + value[0] + value[1] + value[1] + value[2] + value[2];
}
const { hue, saturation, lightness } = hexToHsl(value);
updateVar(h.id, hue);
updateVar(s.id, saturation);
updateVar(l.id, lightness);
};
const handleInputChange = (event, cssvar) => {
let value = event.target.value;
updateVar(cssvar.id, value);
};
if (!h || !s || !l) {
return;
}
const isDisabled =
Number(h.current) === Number(h.start) &&
Number(s.current) === Number(s.start) &&
Number(l.current) === Number(l.start);
const resetClass = classNames({
button: true,
"is-danger is-outlined": !isDisabled,
});
const resetStyle = isDisabled ? { opacity: 0.1 } : {};
return (
<div className={cn.main} style={mainStyle}>
<div className={cn.side}>
<div className="buttons are-small is-float-right ml-2">
<button className="button" onClick={handleHexInput}>
Enter a Hex code
</button>
<button
className={resetClass}
style={resetStyle}
onClick={handleReset}
disabled={isDisabled}
>
Reset
</button>
</div>
<div className={cn.name}>
<div className={cn.swatch} />
<p>{name}</p>
</div>
</div>
<div className={cn.lines}>
<div className={cn.line}>
<p className={cn.label}>Hue</p>
<div className={cn.slider}>
<Slider id={hName} kind="hue" color={color} />
<p className={cn.form}>
<input
type="text"
className="input"
value={Number(h.current)}
onChange={(e) => handleInputChange(e, h)}
size="3"
/>
<span>{h.unit}</span>
</p>
</div>
</div>
<div className={cn.line}>
<p className={cn.label}>Saturation</p>
<div className={cn.slider}>
<Slider id={sName} kind="saturation" color={color} />
<p className={cn.form}>
<input
type="text"
className="input"
value={Number(s.current)}
onChange={(e) => handleInputChange(e, s)}
size="3"
/>
<span>{s.unit}</span>
</p>
</div>
</div>
<div className={cn.line}>
<p className={cn.label}>Lightness</p>
<div className={cn.slider}>
<Slider id={lName} kind="lightness" color={color} />
<p className={cn.form}>
<input
type="text"
className="input"
value={Number(l.current)}
onChange={(e) => handleInputChange(e, l)}
size="3"
/>
<span>{l.unit}</span>
</p>
</div>
</div>
</div>
<div className={cn.example}>
<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;

View File

@ -0,0 +1,80 @@
.main {
border-top: 1px solid var(--bulma-border);
padding: var(--var-item-padding);
justify-content: center;
}
.swatch {
background-color: hsl(var(--h) var(--s) var(--l));
height: 1.25rem;
width: 1.25rem;
border-radius: 0.25rem;
flex-shrink: 0;
}
.name {
color: var(--bulma-primary);
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;
flex-wrap: wrap;
align-items: center;
gap: 0 1.5rem;
}
.line p {
color: var(--bulma-text-strong);
}
.label {
width: 100%;
}
.slider {
display: flex;
align-items: center;
gap: 0 1.5rem;
}
.form {
display: flex;
align-items: center;
font-family: var(--bulma-family-code);
gap: 0.25em;
width: 6rem;
}
.form input {
font-family: inherit;
font-size: inherit;
padding: 0.25em;
height: auto;
border-radius: 0.25em;
width: 3em;
padding: 0 0.25em;
}
.form span {
opacity: 0.5;
}
.example {
margin-top: 0.5rem;
}

View File

@ -0,0 +1,120 @@
import { useContext, useEffect, useState } from "react";
import { CustomizerContext } from "../App";
import cn from "./Export.module.css";
function Export() {
const { cssvars, resetVars, hideExport } = useContext(CustomizerContext);
const [css, setCSS] = useState("");
const [copied, setCopied] = useState(false);
const handleReset = (event) => {
event.preventDefault();
if (window.confirm("Are you sure?")) {
resetVars();
}
};
const handleGo = (event) => {
event.preventDefault();
hideExport();
};
const copyToClipboard = async (event) => {
event.preventDefault();
try {
await navigator.clipboard.writeText(css);
setCopied(true);
window.setTimeout(() => {
setCopied(false);
}, 500);
} catch (err) {
console.error("Failed to copy!", err);
}
};
useEffect(() => {
const rules = {};
Object.values(cssvars).forEach((cssvar) => {
const { id, current, start, scope, unit } = cssvar;
if (current == start) {
return;
}
const computedValue = `${current}${unit}`;
const declaration = `--bulma-${id}: ${computedValue};\n`;
if (scope in rules) {
rules[scope].push(declaration);
} else {
rules[scope] = [declaration];
}
});
let content = "";
for (const [key, arr] of Object.entries(rules)) {
content += `${key} {\n`;
arr.forEach((item) => (content += ` ${item}`));
content += `}\n\n`;
}
setCSS(content);
}, [cssvars]);
return (
<div className={cn.main}>
<div className={cn.body}>
<p className="title is-5">Export</p>
{css ? (
<div className={cn.explanation}>
<p>
Insert this CSS <em>after</em> importing Bulma.
</p>
<div className="buttons are-small">
{copied ? (
<span className="button is-success">Copied!</span>
) : (
<button onClick={copyToClipboard} className="button is-primary">
Copy
</button>
)}
<button
onClick={handleReset}
className="button is-danger is-outlined"
>
Reset
</button>
</div>
</div>
) : (
<>
<div className={cn.explanation}>
<p>
Customize CSS variables in the other pages and come back here to
find the generated CSS.
</p>
</div>
<div className={cn.go}>
<button className="button is-primary" onClick={handleGo}>
Let&apos;s go!
</button>
</div>
</>
)}
</div>
{css && <pre className={cn.pre}>{css.trim()}</pre>}
</div>
);
}
export default Export;

View File

@ -0,0 +1,36 @@
.main {
padding: 0.5rem;
}
.body {
padding: 0.5rem 1rem;
}
.body :global(.title) {
margin-bottom: 0.25rem;
}
.explanation {
display: flex;
align-items: center;
justify-content: space-between;
p {
padding: 3px 0;
}
div {
flex-wrap: nowrap;
}
}
.go {
margin-top: 0.5rem;
}
.pre {
background-color: var(--gh-dark);
margin: 0.5rem -0.5rem;
color: var(--gh-text);
font-size: 1rem;
}

View File

@ -0,0 +1,15 @@
import PropTypes from "prop-types";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { atomOneDark } from "react-syntax-highlighter/dist/esm/styles/hljs";
const Highlighter = ({ ...rest }) => {
return (
<SyntaxHighlighter style={atomOneDark} useInlineStyles={false} {...rest} />
);
};
Highlighter.propTypes = {
code: PropTypes.string,
};
export default Highlighter;

View File

@ -0,0 +1,80 @@
import { useEffect, useState } from "react";
import PropTypes from "prop-types";
import classNames from "classnames";
import cn from "./Slider.module.css";
const RANGES = {
deg: [0, 360, 1],
"%": [0, 100, 1],
};
function Picker({ id, kind, start, unit }) {
const [value, setValue] = useState(start);
let min = 0;
let max = 360;
let step = 1;
if (unit in RANGES) {
[min, max, step] = RANGES[unit];
}
const handleChange = (event) => {
setValue(event.target.value);
};
const handleReset = () => {
setValue(start);
};
useEffect(() => {
const computedValue = `${value}${unit}`;
if (value === start) {
document.documentElement.style.removeProperty(`--bulma-${id}`);
} else {
document.documentElement.style.setProperty(
`--bulma-${id}`,
computedValue,
);
}
}, [id, start, unit, value]);
const mainCN = classNames({
[cn.main]: true,
[cn[kind]]: kind,
});
return (
<div className={mainCN}>
<code>{id}</code>
<input
type="range"
min={min}
max={max}
step={step}
value={value}
onChange={handleChange}
/>
<code>
{value}
{unit}
</code>
<button className="button is-small" onClick={handleReset}>
Reset
</button>
</div>
);
}
Picker.propTypes = {
id: PropTypes.string,
kind: PropTypes.string,
original: PropTypes.string,
start: PropTypes.number,
unit: PropTypes.string,
};
export default Picker;

View File

@ -0,0 +1,167 @@
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],
saturation: [0, 100, 1],
lightness: [0, 100, 1],
gap: [0, 100, 1],
delta: [0, 100, 1],
any: [0, 100, 1],
};
const xToValue = (x, width, min, max) => {
const decimal = x / width;
const newValue = decimal * (max - min);
return Math.round(newValue);
};
const valueToX = (value, width, min, max) => {
const decimal = Number(value) / (max - min);
const newValue = decimal * width;
return Math.round(newValue);
};
function Slider({ id, color }) {
const { cssvars, updateVar } = useContext(CustomizerContext);
const { start, current, kind } = cssvars[id];
const [min, max] = kind ? RANGES[kind] : RANGES.any;
const sliderRef = useRef(null);
const handleRef = useRef(null);
const [isMoving, setMoving] = useState(false);
const [x, setX] = useState(valueToX(start, 360, min, max));
const handleMouseDown = (event) => {
setMoving(true);
const slider = sliderRef.current;
const sliderRect = slider.getBoundingClientRect();
const target = event.clientX - sliderRect.left;
setX(Math.round(target));
};
const docMouseLeave = () => {
setMoving(false);
};
const docMouseUp = () => {
setMoving(false);
};
// useEffect(() => {
// const computedValue = `${current}${unit}`;
// if (current === start) {
// document.documentElement.style.removeProperty(`--bulma-${id}`);
// } else {
// document.documentElement.style.setProperty(
// `--bulma-${id}`,
// computedValue,
// );
// }
// }, [current, id, start, unit]);
useEffect(() => {
if (!isMoving) {
return;
}
const slider = sliderRef.current;
const sliderRect = slider.getBoundingClientRect();
const final = xToValue(x, sliderRect.width, min, max);
updateVar(id, final);
}, [id, isMoving, min, max, updateVar, x]);
useEffect(() => {
const newX = valueToX(current, 360, min, max);
setX(Math.round(newX));
}, [min, max, current]);
useEffect(() => {
const docMouseMove = (event) => {
if (!isMoving || !sliderRef.current || !handleRef.current) {
return;
}
const slider = sliderRef.current;
const sliderRect = slider.getBoundingClientRect();
let target = event.clientX - sliderRect.left;
if (target < 0) {
target = 0;
} else if (target > sliderRect.width) {
target = sliderRect.width;
}
setX(Math.round(target));
};
window.addEventListener("mousemove", docMouseMove);
return () => {
window.removeEventListener("mousemove", docMouseMove);
};
}, [isMoving, min, max, x]);
useEffect(() => {
window.addEventListener("mouseleave", docMouseLeave);
return () => {
window.removeEventListener("mouseleave", docMouseLeave);
};
}, []);
useEffect(() => {
window.addEventListener("mouseup", docMouseUp);
return () => {
window.removeEventListener("mouseup", docMouseUp);
};
}, []);
const mainCN = classNames({
[cn.main]: true,
[cn.moving]: isMoving,
});
const backgroundCN = classNames({
[cn.background]: true,
[cn[kind]]: true,
});
const mainStyle = color
? {
"--h": `var(--bulma-${color}-h)`,
"--s": `var(--bulma-${color}-s)`,
"--l": `var(--bulma-${color}-l)`,
}
: {};
const handleStyle = {
transform: `translateX(${x}px)`,
};
return (
<div
className={mainCN}
ref={sliderRef}
style={mainStyle}
onMouseDown={handleMouseDown}
>
<div className={backgroundCN}>
<span ref={handleRef} className={cn.handle} style={handleStyle} />
</div>
</div>
);
}
Slider.propTypes = {
id: PropTypes.string,
color: PropTypes.string,
};
export default Slider;

View File

@ -0,0 +1,68 @@
.main {
position: relative;
width: 360px;
flex-shrink: 0;
padding: 0.375rem 0;
cursor: grab;
}
.moving {
cursor: grabbing;
}
.handle {
background-color: rgba(255, 255, 255, 0.05);
box-shadow:
rgba(0, 0, 0, 0.15) 0 0 0 1px,
rgba(0, 0, 0, 0.05) 0 10px 10px -5px;
height: 1.25rem;
width: 1.25rem;
border-radius: 9999px;
position: absolute;
top: 0rem;
cursor: grab;
border: 0.25rem solid white;
left: -0.625rem;
}
.moving .handle,
.handle:active {
cursor: grabbing;
}
.background {
border-radius: 0.25rem;
background-color: var(--bulma-border);
height: 0.5rem;
}
.hue {
background-image: linear-gradient(
to right,
rgb(255, 0, 0),
rgb(255, 255, 0),
rgb(0, 255, 0),
rgb(0, 255, 255),
rgb(0, 0, 255),
rgb(255, 0, 255),
rgb(255, 0, 0)
);
}
.saturation {
background-image: linear-gradient(
to right,
hsl(var(--h), 0%, var(--l)),
hsl(var(--h), var(--s), var(--l)),
hsl(var(--h), 100%, var(--l))
);
}
.lightness {
background-image: linear-gradient(
to right,
hsl(var(--h), var(--s), 0%),
hsl(var(--h), var(--s), 50%),
hsl(var(--h), var(--s), 100%)
);
}

View File

@ -0,0 +1,3 @@
function VarInput() {}
export default VarInput;

View File

@ -0,0 +1,102 @@
import { useContext } from "react";
import PropTypes from "prop-types";
import Slider from "./Slider";
import { CustomizerContext } from "../App";
import cn from "./VarItem.module.css";
import classNames from "classnames";
function VarItem({ id }) {
const { cssvars, updateVar } = useContext(CustomizerContext);
const cssvar = cssvars[id];
if (!cssvar) {
return;
}
const handleReset = (event) => {
event.preventDefault();
updateVar(cssvar.id, cssvar.start);
};
const handleInputChange = (event, cssvar) => {
let value = event.target.value;
updateVar(cssvar.id, value);
};
const isDisabled = cssvar.current == cssvar.start;
const resetClass = classNames({
"button is-small": true,
"is-float-right": true,
"ml-2": true,
"is-danger is-outlined": !isDisabled,
});
const resetStyle = isDisabled ? { opacity: 0.1 } : {};
let control;
switch (cssvar.kind) {
case "hue":
case "saturation":
case "lightness":
case "delta":
control = (
<>
<Slider id={cssvar.id} />
<p className={cn.form}>
<input
type="text"
className="input"
value={cssvar.current}
onChange={(e) => handleInputChange(e, cssvar)}
size="3"
/>
<span>{cssvar.unit}</span>
</p>
</>
);
break;
default:
control = (
<input
className="input"
type="text"
value={cssvar.current}
onChange={(e) => handleInputChange(e, cssvar)}
/>
);
break;
}
return (
<div className={cn.main}>
<div className={cn.side}>
<button
className={resetClass}
onClick={handleReset}
disabled={isDisabled}
style={resetStyle}
>
Reset
</button>
<div className={cn.name}>
<code>{cssvar.id}</code>
</div>
<div className={cn.description}>{cssvar.description}</div>
</div>
<div className={cn.slider}>{control}</div>
</div>
);
}
VarItem.propTypes = {
id: PropTypes.string,
};
export default VarItem;

View File

@ -0,0 +1,67 @@
.main {
gap: var(--var-item-gap);
border-top: 1px solid var(--bulma-border);
padding: var(--var-item-padding);
transition-property: background-color;
transition-duration: var(--bulma-duration);
}
.main:hover {
background-color: var(--bulma-background);
}
.side {
flex-shrink: 0;
}
.name {
gap: 1rem;
display: flex;
align-items: center;
margin-bottom: 0.5rem;
}
.name code {
color: var(--bulma-primary);
font-size: 1em;
font-weight: 400;
padding: 0;
background: none;
}
.slider {
align-items: center;
display: flex;
gap: 1.5rem;
/* padding: 2px 0; */
flex-shrink: 0;
width: var(--var-item-slider-width);
}
.form {
display: flex;
align-items: center;
font-family: var(--bulma-family-code);
gap: 0.25em;
}
.form input {
font-family: inherit;
font-size: inherit;
padding: 0.25em;
height: auto;
border-radius: 0.25em;
width: 3em;
padding: 0 0.25em;
}
.form span {
opacity: 0.5;
}
.description {
flex-shrink: 1;
flex-grow: 1;
margin: -0.25rem 0 0.5rem;
font-size: 0.875em;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,68 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

View File

@ -0,0 +1,9 @@
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
ReactDOM.createRoot(document.getElementById("bulma-customizer-app")).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);

View File

@ -0,0 +1,17 @@
import Color from "../components/Color";
const COLORS = ["primary", "link", "info", "success", "warning", "danger"];
import cn from "root/App.module.css";
function Colors() {
return (
<div className={cn.colors}>
{COLORS.map((color) => {
return <Color key={color} color={color} />;
})}
</div>
);
}
export default Colors;

View File

@ -0,0 +1,18 @@
import VarItem from "../components/VarItem";
import { CSSVAR_KEYS } from "../constants";
import cn from "root/App.module.css";
function Generic() {
const ids = CSSVAR_KEYS.generic.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Generic;

View File

@ -0,0 +1,18 @@
import VarItem from "../components/VarItem";
import { CSSVAR_KEYS } from "../constants";
import cn from "root/App.module.css";
function Other() {
const ids = CSSVAR_KEYS.other.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Other;

View File

@ -0,0 +1,18 @@
import VarItem from "../components/VarItem";
import { CSSVAR_KEYS } from "../constants";
import cn from "root/App.module.css";
function Scheme() {
const schemeIds = CSSVAR_KEYS.scheme.map((i) => i.id);
return (
<div className={cn.items}>
{schemeIds.map((schemeId) => {
return <VarItem key={schemeId} id={schemeId} />;
})}
</div>
);
}
export default Scheme;

View File

@ -0,0 +1,18 @@
import VarItem from "../components/VarItem";
import { CSSVAR_KEYS } from "../constants";
import cn from "root/App.module.css";
function Skeleton() {
const ids = CSSVAR_KEYS.skeleton.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Skeleton;

View File

@ -0,0 +1,18 @@
import VarItem from "../components/VarItem";
import { CSSVAR_KEYS } from "../constants";
import cn from "root/App.module.css";
function Typography() {
const ids = CSSVAR_KEYS.typography.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Typography;

View File

@ -0,0 +1,18 @@
import VarItem from "components/VarItem";
import { CSSVAR_KEYS } from "root/constants";
import cn from "root/App.module.css";
function Breadcrumb() {
const ids = CSSVAR_KEYS.breadcrumb.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Breadcrumb;

View File

@ -0,0 +1,18 @@
import VarItem from "components/VarItem";
import { CSSVAR_KEYS } from "root/constants";
import cn from "root/App.module.css";
function Card() {
const ids = CSSVAR_KEYS.card.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Card;

View File

@ -0,0 +1,18 @@
import VarItem from "components/VarItem";
import { CSSVAR_KEYS } from "root/constants";
import cn from "root/App.module.css";
function Dropdown() {
const ids = CSSVAR_KEYS.dropdown.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Dropdown;

View File

@ -0,0 +1,18 @@
import VarItem from "components/VarItem";
import { CSSVAR_KEYS } from "root/constants";
import cn from "root/App.module.css";
function Menu() {
const ids = CSSVAR_KEYS.menu.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Menu;

View File

@ -0,0 +1,18 @@
import VarItem from "components/VarItem";
import { CSSVAR_KEYS } from "root/constants";
import cn from "root/App.module.css";
function Message() {
const ids = CSSVAR_KEYS.message.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Message;

View File

@ -0,0 +1,18 @@
import VarItem from "components/VarItem";
import { CSSVAR_KEYS } from "root/constants";
import cn from "root/App.module.css";
function Modal() {
const ids = CSSVAR_KEYS.modal.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Modal;

View File

@ -0,0 +1,18 @@
import VarItem from "components/VarItem";
import { CSSVAR_KEYS } from "root/constants";
import cn from "root/App.module.css";
function Navbar() {
const ids = CSSVAR_KEYS.navbar.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Navbar;

View File

@ -0,0 +1,18 @@
import VarItem from "components/VarItem";
import { CSSVAR_KEYS } from "root/constants";
import cn from "root/App.module.css";
function Pagination() {
const ids = CSSVAR_KEYS.pagination.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Pagination;

View File

@ -0,0 +1,18 @@
import VarItem from "components/VarItem";
import { CSSVAR_KEYS } from "root/constants";
import cn from "root/App.module.css";
function Panel() {
const ids = CSSVAR_KEYS.panel.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Panel;

View File

@ -0,0 +1,18 @@
import VarItem from "components/VarItem";
import { CSSVAR_KEYS } from "root/constants";
import cn from "root/App.module.css";
function Tabs() {
const ids = CSSVAR_KEYS.tabs.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Tabs;

View File

@ -0,0 +1,18 @@
import VarItem from "components/VarItem";
import { CSSVAR_KEYS } from "root/constants";
import cn from "root/App.module.css";
function Box() {
const ids = CSSVAR_KEYS.box.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Box;

View File

@ -0,0 +1,18 @@
import VarItem from "components/VarItem";
import { CSSVAR_KEYS } from "root/constants";
import cn from "root/App.module.css";
function Content() {
const ids = CSSVAR_KEYS.content.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Content;

View File

@ -0,0 +1,18 @@
import VarItem from "components/VarItem";
import { CSSVAR_KEYS } from "root/constants";
import cn from "root/App.module.css";
function Delete() {
const ids = CSSVAR_KEYS.delete.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Delete;

View File

@ -0,0 +1,18 @@
import VarItem from "components/VarItem";
import { CSSVAR_KEYS } from "root/constants";
import cn from "root/App.module.css";
function Icon() {
const ids = CSSVAR_KEYS.icon.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Icon;

View File

@ -0,0 +1,18 @@
import VarItem from "components/VarItem";
import { CSSVAR_KEYS } from "root/constants";
import cn from "root/App.module.css";
function Notification() {
const ids = CSSVAR_KEYS.notification.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Notification;

View File

@ -0,0 +1,18 @@
import VarItem from "components/VarItem";
import { CSSVAR_KEYS } from "root/constants";
import cn from "root/App.module.css";
function Progress() {
const ids = CSSVAR_KEYS.progress.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Progress;

View File

@ -0,0 +1,18 @@
import VarItem from "components/VarItem";
import { CSSVAR_KEYS } from "root/constants";
import cn from "root/App.module.css";
function Table() {
const ids = CSSVAR_KEYS.table.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Table;

View File

@ -0,0 +1,18 @@
import VarItem from "components/VarItem";
import { CSSVAR_KEYS } from "root/constants";
import cn from "root/App.module.css";
function Tag() {
const ids = CSSVAR_KEYS.tag.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Tag;

View File

@ -0,0 +1,18 @@
import VarItem from "components/VarItem";
import { CSSVAR_KEYS } from "root/constants";
import cn from "root/App.module.css";
function Title() {
const ids = CSSVAR_KEYS.title.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Title;

View File

@ -0,0 +1,18 @@
import VarItem from "components/VarItem";
import { CSSVAR_KEYS } from "root/constants";
import cn from "root/App.module.css";
function Control() {
const ids = CSSVAR_KEYS.control.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Control;

View File

@ -0,0 +1,18 @@
import VarItem from "components/VarItem";
import { CSSVAR_KEYS } from "root/constants";
import cn from "root/App.module.css";
function File() {
const ids = CSSVAR_KEYS.file.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default File;

View File

@ -0,0 +1,18 @@
import VarItem from "components/VarItem";
import { CSSVAR_KEYS } from "root/constants";
import cn from "root/App.module.css";
function Input() {
const ids = CSSVAR_KEYS.input.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Input;

View File

@ -0,0 +1,18 @@
import VarItem from "components/VarItem";
import { CSSVAR_KEYS } from "root/constants";
import cn from "root/App.module.css";
function Columns() {
const ids = CSSVAR_KEYS.columns.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Columns;

View File

@ -0,0 +1,18 @@
import VarItem from "components/VarItem";
import { CSSVAR_KEYS } from "root/constants";
import cn from "root/App.module.css";
function Grid() {
const ids = CSSVAR_KEYS.grid.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Grid;

View File

@ -0,0 +1,18 @@
import VarItem from "components/VarItem";
import { CSSVAR_KEYS } from "root/constants";
import cn from "root/App.module.css";
function Footer() {
const ids = CSSVAR_KEYS.footer.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Footer;

View File

@ -0,0 +1,18 @@
import VarItem from "components/VarItem";
import { CSSVAR_KEYS } from "root/constants";
import cn from "root/App.module.css";
function Hero() {
const ids = CSSVAR_KEYS.hero.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Hero;

View File

@ -0,0 +1,18 @@
import VarItem from "components/VarItem";
import { CSSVAR_KEYS } from "root/constants";
import cn from "root/App.module.css";
function Media() {
const ids = CSSVAR_KEYS.media.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Media;

View File

@ -0,0 +1,18 @@
import VarItem from "components/VarItem";
import { CSSVAR_KEYS } from "root/constants";
import cn from "root/App.module.css";
function Section() {
const ids = CSSVAR_KEYS.section.map((i) => i.id);
return (
<div className={cn.items}>
{ids.map((id) => {
return <VarItem key={id} id={id} />;
})}
</div>
);
}
export default Section;

View File

@ -0,0 +1,9 @@
export function unslug(slug) {
// Replace hyphens and underscores with spaces
let result = slug.replace(/[-_]/g, " ");
// Capitalize the first letter of each word
return result.replace(/\b\w/g, function (char) {
return char.toUpperCase();
});
}

View File

@ -0,0 +1,23 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
// https://vitejs.dev/config/
export default defineConfig({
build: {
emptyOutDir: true,
outDir: "../../assets/javascript/bulma-customizer",
rollupOptions: {
output: {
assetFileNames: "[name].css",
entryFileNames: "[name].js",
},
},
},
plugins: [react()],
resolve: {
alias: {
root: "/src",
components: "/src/components",
},
},
});

View File

@ -10,6 +10,7 @@
padding: calc(var(--scale) * 3rem);
text-align: center;
&.is-horizontal,
&.is-docs {
align-items: center;
flex-direction: row;
@ -30,6 +31,10 @@
justify-content: flex-start;
}
}
&.is-horizontal {
padding: calc(var(--scale) * 3rem);
}
}
@media screen and (min-width: 1000px) {

View File

@ -36819,23 +36819,26 @@ has-background-moon.is-hoverable:active {
padding: calc(var(--scale) * 3rem);
text-align: center;
}
.bd-hero.is-docs {
.bd-hero.is-horizontal, .bd-hero.is-docs {
align-items: center;
flex-direction: row;
justify-content: flex-start;
padding: 0;
text-align: left;
}
.bd-hero.is-docs .bd-hero-body {
.bd-hero.is-horizontal .bd-hero-body, .bd-hero.is-docs .bd-hero-body {
flex-basis: 20em;
flex-grow: 1;
}
.bd-hero.is-docs .bd-hr {
.bd-hero.is-horizontal .bd-hr, .bd-hero.is-docs .bd-hr {
margin-left: 0;
}
.bd-hero.is-docs .bd-hero-prints {
.bd-hero.is-horizontal .bd-hero-prints, .bd-hero.is-docs .bd-hero-prints {
justify-content: flex-start;
}
.bd-hero.is-horizontal {
padding: calc(var(--scale) * 3rem);
}
@media screen and (min-width: 1000px) {
.bd-hero.is-docs {

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
:root{--gh-dark: #25292e;--gh-dimmed: #6a737d;--gh-text: #e1e4e8;--gh-red: #f97583;--gh-orange: #ffab70;--gh-yellow: #ffea7f;--gh-green: #85e89d;--gh-blue: #79b8ff;--gh-blue-light: #9ecbff;--gh-purple: #b392f0;--gh-pink: #f692ce}._app_1oamh_1{background-color:red;--var-item-side-width: 12rem;--var-item-slider-width: 30rem;--var-item-padding: 1rem;--var-item-gap: 1rem;bottom:1rem;overflow:hidden;position:fixed;right:1rem;z-index:100}._customizer_1oamh_14{background:var(--bulma-scheme-main);box-shadow:var(--bulma-shadow);border-radius:.5rem;transform-origin:bottom right;transition-duration:.3s;transition-property:opacity,transform;transform:scale(.98) translate(2px,2px);opacity:0;pointer-events:none;width:33rem;top:1rem;position:fixed;bottom:4.5rem;right:1rem;overflow-y:auto}._open_1oamh_32{opacity:1;pointer-events:auto;transform:none}._controls_1oamh_38{display:flex;flex-wrap:wrap;gap:.5rem;padding:.5rem}._buttons_1oamh_45{display:flex;gap:.5rem;bottom:1rem;right:1rem;position:fixed}._button_1oamh_45{gap:.5rem}._main_1gut5_1{position:relative;width:360px;flex-shrink:0;padding:.375rem 0;cursor:grab}._moving_1gut5_9{cursor:grabbing}._handle_1gut5_13{background-color:#ffffff0d;box-shadow:#00000026 0 0 0 1px,#0000000d 0 10px 10px -5px;height:1.25rem;width:1.25rem;border-radius:9999px;position:absolute;top:0rem;cursor:grab;border:.25rem solid white;left:-.625rem}._moving_1gut5_9 ._handle_1gut5_13,._handle_1gut5_13:active{cursor:grabbing}._background_1gut5_33{border-radius:.25rem;background-color:var(--bulma-border);height:.5rem}._hue_1gut5_39{background-image:linear-gradient(to right,red,#ff0,#0f0,#0ff,#00f,#f0f,red)}._saturation_1gut5_52{background-image:linear-gradient(to right,hsl(var(--h),0%,var(--l)),hsl(var(--h),var(--s),var(--l)),hsl(var(--h),100%,var(--l)))}._lightness_1gut5_61{background-image:linear-gradient(to right,hsl(var(--h),var(--s),0%),hsl(var(--h),var(--s),50%),hsl(var(--h),var(--s),100%))}._main_17ce3_1{border-top:1px solid var(--bulma-border);padding:var(--var-item-padding);justify-content:center}._swatch_17ce3_7{background-color:hsl(var(--h) var(--s) var(--l));height:1.25rem;width:1.25rem;border-radius:.25rem;flex-shrink:0}._name_17ce3_15{color:var(--bulma-primary);gap:1rem;display:flex;align-items:center;margin-bottom:.5rem}._name_17ce3_15 p{color:var(--bulma-text-strong);font-size:1.25em;font-weight:600}._lines_17ce3_29{display:flex;flex-direction:column;gap:.25rem}._line_17ce3_29{display:flex;flex-wrap:wrap;align-items:center;gap:0 1.5rem}._line_17ce3_29 p{color:var(--bulma-text-strong)}._label_17ce3_46{width:100%}._slider_17ce3_50{display:flex;align-items:center;gap:0 1.5rem}._form_17ce3_56{display:flex;align-items:center;font-family:var(--bulma-family-code);gap:.25em;width:6rem}._form_17ce3_56 input{font-family:inherit;font-size:inherit;height:auto;border-radius:.25em;width:3em;padding:0 .25em}._form_17ce3_56 span{opacity:.5}._example_17ce3_78{margin-top:.5rem}._main_1itpo_1{gap:var(--var-item-gap);border-top:1px solid var(--bulma-border);padding:var(--var-item-padding);transition-property:background-color;transition-duration:var(--bulma-duration)}._main_1itpo_1:hover{background-color:var(--bulma-background)}._side_1itpo_13{flex-shrink:0}._name_1itpo_17{gap:1rem;display:flex;align-items:center;margin-bottom:.5rem}._name_1itpo_17 code{color:var(--bulma-primary);font-size:1em;font-weight:400;padding:0;background:none}._slider_1itpo_32{align-items:center;display:flex;gap:1.5rem;flex-shrink:0;width:var(--var-item-slider-width)}._form_1itpo_41{display:flex;align-items:center;font-family:var(--bulma-family-code);gap:.25em}._form_1itpo_41 input{font-family:inherit;font-size:inherit;height:auto;border-radius:.25em;width:3em;padding:0 .25em}._form_1itpo_41 span{opacity:.5}._description_1itpo_62{flex-shrink:1;flex-grow:1;margin:-.25rem 0 .5rem;font-size:.875em}._main_uds4w_1{padding:.5rem}._body_uds4w_5{padding:.5rem 1rem}._body_uds4w_5 .title{margin-bottom:.25rem}._explanation_uds4w_13{display:flex;align-items:center;justify-content:space-between}._explanation_uds4w_13 p{padding:3px 0}._explanation_uds4w_13 div{flex-wrap:nowrap}._go_uds4w_27{margin-top:.5rem}._pre_uds4w_31{background-color:var(--gh-dark);margin:.5rem -.5rem;color:var(--gh-text);font-size:1rem}

View File

@ -0,0 +1,47 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
<style type="text/css">
#bulma-customizer-app {
}
</style>
<script type="module" crossorigin src="/index.js"></script>
<link rel="stylesheet" crossorigin href="/index.css">
</head>
<body>
<div id="bulma-customizer-app"></div>
<div id="scope" class="is-hidden">
<div class="breadcrumb"></div>
<div class="card"></div>
<div class="dropdown"></div>
<div class="menu"></div>
<div class="message"></div>
<div class="modal"></div>
<div class="navbar"></div>
<div class="pagination"></div>
<div class="panel"></div>
<div class="tabs"></div>
<div class="box"></div>
<div class="content"></div>
<div class="delete"></div>
<div class="icon"></div>
<div class="notification"></div>
<div class="progress"></div>
<table class="table"></table>
<div class="tag"></div>
<div class="title"></div>
<input class="input" />
<div class="file"></div>
<div class="columns"></div>
<div class="grid"></div>
<div class="footer"></div>
<div class="hero"></div>
<div class="media"></div>
<div class="section"></div>
</div>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -155,6 +155,10 @@ document.addEventListener("DOMContentLoaded", () => {
if ($clickableDropdowns.length > 0) {
$clickableDropdowns.forEach(($dropdown) => {
if (!$dropdown.querySelector("button")) {
return;
}
$dropdown.querySelector("button").addEventListener("click", (event) => {
event.stopPropagation();
$dropdown.classList.toggle("is-active");

9
docs/customizer.html Normal file
View File

@ -0,0 +1,9 @@
---
layout: default
theme: customizer
route: customizer
---
{% include global/header.html %} {% include docs/hero.html variant="horizontal"
title="The Bulma Customizer" subtitle="Easily customize Bulma with your own CSS
variables." %}

View File

@ -31,9 +31,9 @@ breadcrumb:
</div>
<div class="media-content">
<p class="title is-5">Use the <strong>HTML5 doctype</strong></p>
{% highlight html %}
<!doctype html>
{% endhighlight %}
{%- highlight html -%}
<!DOCTYPE html>
{%- endhighlight -%}
</div>
</article>

44
docs/package-lock.json generated
View File

@ -1,19 +1,17 @@
{
"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": {
@ -43,6 +41,7 @@
"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"
@ -55,6 +54,7 @@
"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"
},
@ -63,11 +63,12 @@
}
},
"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"
@ -77,6 +78,7 @@
"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",
@ -97,9 +99,10 @@
}
},
"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"
},
@ -111,6 +114,7 @@
"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": [
@ -124,6 +128,7 @@
"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"
},
@ -140,12 +145,14 @@
"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"
},
@ -157,6 +164,7 @@
"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"
}
@ -165,6 +173,7 @@
"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"
},
@ -176,6 +185,7 @@
"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"
}
@ -212,6 +222,7 @@
"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"
}
@ -229,6 +240,7 @@
"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"
},
@ -255,6 +267,7 @@
"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"
},
@ -266,6 +279,7 @@
"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",
@ -282,6 +296,7 @@
"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"
}
@ -290,6 +305,7 @@
"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"
},