@5voltfx/design-system 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-3LTHHL5C.js +11 -0
- package/dist/chunk-3LTHHL5C.js.map +1 -0
- package/dist/chunk-5KN5TPGG.js +30 -0
- package/dist/chunk-5KN5TPGG.js.map +1 -0
- package/dist/chunk-AYAMNHNF.js +1 -0
- package/dist/chunk-AYAMNHNF.js.map +1 -0
- package/dist/chunk-BOPVPQLB.js +25 -0
- package/dist/chunk-BOPVPQLB.js.map +1 -0
- package/dist/chunk-EW3S3GK6.js +41 -0
- package/dist/chunk-EW3S3GK6.js.map +1 -0
- package/dist/chunk-NE25337V.js +1 -0
- package/dist/chunk-NE25337V.js.map +1 -0
- package/dist/chunk-SFECTSQP.js +68 -0
- package/dist/chunk-SFECTSQP.js.map +1 -0
- package/dist/chunk-TNWQQ56F.js +19 -0
- package/dist/chunk-TNWQQ56F.js.map +1 -0
- package/dist/chunk-U6JTDANG.js +22 -0
- package/dist/chunk-U6JTDANG.js.map +1 -0
- package/dist/chunk-YCZXDQJZ.js +41 -0
- package/dist/chunk-YCZXDQJZ.js.map +1 -0
- package/{src/components/Button/Button.css → dist/components/Button/index.css} +2 -11
- package/dist/components/Button/index.css.map +1 -0
- package/dist/components/Button/index.js +7 -0
- package/dist/components/Button/index.js.map +1 -0
- package/{src/components/Card/Card.css → dist/components/Card/index.css} +3 -3
- package/dist/components/Card/index.css.map +1 -0
- package/dist/components/Card/index.js +7 -0
- package/dist/components/Card/index.js.map +1 -0
- package/{src/components/Footer/Footer.css → dist/components/Footer/index.css} +28 -7
- package/dist/components/Footer/index.css.map +1 -0
- package/dist/components/Footer/index.js +8 -0
- package/dist/components/Footer/index.js.map +1 -0
- package/dist/components/Header/index.css +98 -0
- package/dist/components/Header/index.css.map +1 -0
- package/dist/components/Header/index.js +8 -0
- package/dist/components/Header/index.js.map +1 -0
- package/{src/components/Layout/Layout.css → dist/components/Layout/index.css} +2 -4
- package/dist/components/Layout/index.css.map +1 -0
- package/dist/components/Layout/index.js +11 -0
- package/dist/components/Layout/index.js.map +1 -0
- package/{src/components/Nav/Nav.css → dist/components/Nav/index.css} +2 -8
- package/dist/components/Nav/index.css.map +1 -0
- package/dist/components/Nav/index.js +8 -0
- package/dist/components/Nav/index.js.map +1 -0
- package/{src/components/ThemeSwitcher/ThemeSwitcher.css → dist/components/ThemeSwitcher/index.css} +3 -7
- package/dist/components/ThemeSwitcher/index.css.map +1 -0
- package/dist/components/ThemeSwitcher/index.js +8 -0
- package/dist/components/ThemeSwitcher/index.js.map +1 -0
- package/dist/components/Typography/index.css +50 -0
- package/dist/components/Typography/index.css.map +1 -0
- package/dist/components/Typography/index.js +9 -0
- package/dist/components/Typography/index.js.map +1 -0
- package/dist/index.css +544 -0
- package/dist/index.css.map +1 -0
- package/dist/index.js +116 -0
- package/dist/index.js.map +1 -0
- package/package.json +19 -6
- package/vite-plugin.js +13 -10
- package/src/components/Button/Button.jsx +0 -20
- package/src/components/Button/index.js +0 -1
- package/src/components/Card/Card.jsx +0 -6
- package/src/components/Card/index.js +0 -1
- package/src/components/Footer/Footer.jsx +0 -37
- package/src/components/Footer/index.js +0 -1
- package/src/components/Header/Header.css +0 -47
- package/src/components/Header/Header.jsx +0 -30
- package/src/components/Header/index.js +0 -1
- package/src/components/Layout/Container.jsx +0 -10
- package/src/components/Layout/Grid.jsx +0 -22
- package/src/components/Layout/Stack.jsx +0 -24
- package/src/components/Layout/index.js +0 -3
- package/src/components/Nav/Nav.jsx +0 -29
- package/src/components/Nav/index.js +0 -1
- package/src/components/ThemeSwitcher/ThemeSwitcher.jsx +0 -26
- package/src/components/ThemeSwitcher/index.js +0 -1
- package/src/components/Typography/Heading.jsx +0 -29
- package/src/components/Typography/Text.jsx +0 -20
- package/src/components/Typography/Typography.css +0 -35
- package/src/components/Typography/index.js +0 -2
- package/src/index.js +0 -12
- package/src/theme/ThemeContext.js +0 -3
- package/src/theme/ThemeProvider.jsx +0 -66
- package/src/theme/themeConstants.js +0 -12
- package/src/theme/useTheme.js +0 -10
- package/src/tokens/base.css +0 -21
- package/src/tokens/colors.css +0 -29
- package/src/tokens/elevation.css +0 -10
- package/src/tokens/index.css +0 -8
- package/src/tokens/motion.css +0 -6
- package/src/tokens/radius.css +0 -6
- package/src/tokens/spacing.css +0 -10
- package/src/tokens/themes.css +0 -156
- package/src/tokens/typography.css +0 -29
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/theme/ThemeProvider.jsx","../src/theme/themeConstants.js"],"sourcesContent":["import { useCallback, useLayoutEffect, useMemo, useState } from \"react\";\nimport { ThemeContext } from \"./ThemeContext.js\";\nimport { THEMES, THEME_IDS, DEFAULT_THEME } from \"./themeConstants.js\";\n\nfunction readStoredTheme(storageKey, fallback) {\n if (typeof window === \"undefined\") return fallback;\n try {\n const stored = window.localStorage.getItem(storageKey);\n return THEME_IDS.includes(stored) ? stored : fallback;\n } catch {\n return fallback;\n }\n}\n\n/**\n * data-theme lives on <html> rather than a class or a wrapper div so every\n * consuming site (song-chart used a class on <html>, mattwelker.com used a\n * class on a wrapper div) lands on one shared convention.\n */\nexport function ThemeProvider({\n children,\n storageKey = \"ds.theme\",\n defaultTheme = DEFAULT_THEME,\n onThemeChange,\n}) {\n const [theme, setThemeState] = useState(() =>\n readStoredTheme(storageKey, defaultTheme)\n );\n\n useLayoutEffect(() => {\n document.documentElement.setAttribute(\"data-theme\", theme);\n try {\n window.localStorage.setItem(storageKey, theme);\n } catch {\n // localStorage unavailable (private browsing, etc.) — theme still applies for this session\n }\n }, [theme, storageKey]);\n\n useLayoutEffect(() => {\n function handleStorage(event) {\n if (event.key === storageKey && THEME_IDS.includes(event.newValue)) {\n setThemeState(event.newValue);\n }\n }\n window.addEventListener(\"storage\", handleStorage);\n return () => window.removeEventListener(\"storage\", handleStorage);\n }, [storageKey]);\n\n const setTheme = useCallback(\n (id) => {\n if (!THEME_IDS.includes(id)) return;\n setThemeState(id);\n onThemeChange?.(id);\n },\n [onThemeChange]\n );\n\n const value = useMemo(\n () => ({ theme, setTheme, themes: THEMES }),\n [theme, setTheme]\n );\n\n return (\n <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>\n );\n}\n","export const THEMES = [\n { id: \"coral\", label: \"Coral — creative warm\", swatch: \"#ff6b6b\" },\n { id: \"ink\", label: \"Ink — dark indigo\", swatch: \"#6366f1\" },\n { id: \"sage\", label: \"Sage — muted olive\", swatch: \"#6b7568\" },\n { id: \"midnight\", label: \"Midnight — high contrast\", swatch: \"#00d4ff\" },\n { id: \"amber\", label: \"Amber — golden\", swatch: \"#b8652a\" },\n { id: \"harvest\", label: \"Harvest — earthy warm\", swatch: \"#9f5508\" },\n];\n\nexport const THEME_IDS = THEMES.map((theme) => theme.id);\n\nexport const DEFAULT_THEME = \"coral\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,aAAa,iBAAiB,SAAS,gBAAgB;;;ACAzD,IAAM,SAAS;AAAA,EACpB,EAAE,IAAI,SAAS,OAAO,8BAAyB,QAAQ,UAAU;AAAA,EACjE,EAAE,IAAI,OAAO,OAAO,0BAAqB,QAAQ,UAAU;AAAA,EAC3D,EAAE,IAAI,QAAQ,OAAO,2BAAsB,QAAQ,UAAU;AAAA,EAC7D,EAAE,IAAI,YAAY,OAAO,iCAA4B,QAAQ,UAAU;AAAA,EACvE,EAAE,IAAI,SAAS,OAAO,uBAAkB,QAAQ,UAAU;AAAA,EAC1D,EAAE,IAAI,WAAW,OAAO,8BAAyB,QAAQ,UAAU;AACrE;AAEO,IAAM,YAAY,OAAO,IAAI,CAAC,UAAU,MAAM,EAAE;AAEhD,IAAM,gBAAgB;;;ADoDzB;AA3DJ,SAAS,gBAAgB,YAAY,UAAU;AAC7C,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,MAAI;AACF,UAAM,SAAS,OAAO,aAAa,QAAQ,UAAU;AACrD,WAAO,UAAU,SAAS,MAAM,IAAI,SAAS;AAAA,EAC/C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA,aAAa;AAAA,EACb,eAAe;AAAA,EACf;AACF,GAAG;AACD,QAAM,CAAC,OAAO,aAAa,IAAI;AAAA,IAAS,MACtC,gBAAgB,YAAY,YAAY;AAAA,EAC1C;AAEA,kBAAgB,MAAM;AACpB,aAAS,gBAAgB,aAAa,cAAc,KAAK;AACzD,QAAI;AACF,aAAO,aAAa,QAAQ,YAAY,KAAK;AAAA,IAC/C,QAAQ;AAAA,IAER;AAAA,EACF,GAAG,CAAC,OAAO,UAAU,CAAC;AAEtB,kBAAgB,MAAM;AACpB,aAAS,cAAc,OAAO;AAC5B,UAAI,MAAM,QAAQ,cAAc,UAAU,SAAS,MAAM,QAAQ,GAAG;AAClE,sBAAc,MAAM,QAAQ;AAAA,MAC9B;AAAA,IACF;AACA,WAAO,iBAAiB,WAAW,aAAa;AAChD,WAAO,MAAM,OAAO,oBAAoB,WAAW,aAAa;AAAA,EAClE,GAAG,CAAC,UAAU,CAAC;AAEf,QAAM,WAAW;AAAA,IACf,CAAC,OAAO;AACN,UAAI,CAAC,UAAU,SAAS,EAAE,EAAG;AAC7B,oBAAc,EAAE;AAChB,sBAAgB,EAAE;AAAA,IACpB;AAAA,IACA,CAAC,aAAa;AAAA,EAChB;AAEA,QAAM,QAAQ;AAAA,IACZ,OAAO,EAAE,OAAO,UAAU,QAAQ,OAAO;AAAA,IACzC,CAAC,OAAO,QAAQ;AAAA,EAClB;AAEA,SACE,oBAAC,aAAa,UAAb,EAAsB,OAAe,UAAS;AAEnD;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@5voltfx/design-system",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"main": "./
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
6
|
"exports": {
|
|
7
|
-
".": "./
|
|
8
|
-
"./styles.css": "./
|
|
9
|
-
"./components/*": "./
|
|
7
|
+
".": "./dist/index.js",
|
|
8
|
+
"./styles.css": "./dist/index.css",
|
|
9
|
+
"./components/*": "./dist/components/*/index.js",
|
|
10
10
|
"./vite-plugin": "./vite-plugin.js"
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
|
-
"
|
|
13
|
+
"dist",
|
|
14
14
|
"vite-plugin.js"
|
|
15
15
|
],
|
|
16
|
+
"sideEffects": [
|
|
17
|
+
"**/*.css"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"dev": "tsup --watch",
|
|
22
|
+
"prepublishOnly": "npm run build",
|
|
23
|
+
"prepare": "npm run build"
|
|
24
|
+
},
|
|
16
25
|
"publishConfig": {
|
|
17
26
|
"access": "public"
|
|
18
27
|
},
|
|
@@ -20,5 +29,9 @@
|
|
|
20
29
|
"react": "^19.0.0",
|
|
21
30
|
"react-dom": "^19.0.0",
|
|
22
31
|
"react-router-dom": "^7.0.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"tsup": "^8.5.0",
|
|
35
|
+
"typescript": "^5.0.0"
|
|
23
36
|
}
|
|
24
37
|
}
|
package/vite-plugin.js
CHANGED
|
@@ -2,24 +2,27 @@ import path from "node:path";
|
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
|
|
4
4
|
const packageRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
5
|
-
const
|
|
5
|
+
const designSystemDist = path.join(packageRoot, "dist");
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Vite only watches files inside the consuming project's root by default, so
|
|
9
|
-
* edits
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
9
|
+
* edits to the symlinked file: dependency (which lives outside that root) are
|
|
10
|
+
* invisible to HMR no matter what `server.watch.ignored` patterns say. This
|
|
11
|
+
* plugin explicitly adds the design system's built output directory to the
|
|
12
|
+
* watcher and forces a full reload when anything in it changes.
|
|
13
|
+
*
|
|
14
|
+
* Consumers now resolve to `dist`, not `src`, so pair this with the design
|
|
15
|
+
* system running `npm run dev` (tsup --watch): editing a source file rebuilds
|
|
16
|
+
* dist, which trips this watcher. Add the plugin to every site's
|
|
17
|
+
* vite.config.js alongside the file: dependency on @5voltfx/design-system.
|
|
15
18
|
*/
|
|
16
19
|
export function watchDesignSystem() {
|
|
17
20
|
return {
|
|
18
21
|
name: "watch-design-system",
|
|
19
22
|
configureServer(server) {
|
|
20
|
-
server.watcher.add(
|
|
23
|
+
server.watcher.add(designSystemDist);
|
|
21
24
|
server.watcher.on("change", (file) => {
|
|
22
|
-
if (file.startsWith(
|
|
25
|
+
if (file.startsWith(designSystemDist)) {
|
|
23
26
|
server.ws.send({ type: "full-reload" });
|
|
24
27
|
}
|
|
25
28
|
});
|
|
@@ -27,4 +30,4 @@ export function watchDesignSystem() {
|
|
|
27
30
|
};
|
|
28
31
|
}
|
|
29
32
|
|
|
30
|
-
export const
|
|
33
|
+
export const DESIGN_SYSTEM_DIST = designSystemDist;
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import "./Button.css";
|
|
2
|
-
|
|
3
|
-
export function Button({
|
|
4
|
-
as: Component = "button",
|
|
5
|
-
variant = "primary",
|
|
6
|
-
size = "md",
|
|
7
|
-
className = "",
|
|
8
|
-
...rest
|
|
9
|
-
}) {
|
|
10
|
-
const classes = [
|
|
11
|
-
"ds-button",
|
|
12
|
-
`ds-button--${variant}`,
|
|
13
|
-
`ds-button--${size}`,
|
|
14
|
-
className,
|
|
15
|
-
]
|
|
16
|
-
.filter(Boolean)
|
|
17
|
-
.join(" ");
|
|
18
|
-
|
|
19
|
-
return <Component className={classes} {...rest} />;
|
|
20
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { Button } from "./Button.jsx";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { Card } from "./Card.jsx";
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { ThemeSwitcher } from "../ThemeSwitcher/ThemeSwitcher.jsx";
|
|
2
|
-
import "./Footer.css";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Generalizes the 3 existing footers: 5voltfx.com's simple centered
|
|
6
|
-
* copyright, song-chart's minimal bar, and mattwelker.com's 3-column
|
|
7
|
-
* layout (auth links / theme swatches / copyright). `left` and `right`
|
|
8
|
-
* default to nothing and a copyright string respectively, so a single
|
|
9
|
-
* centered ThemeSwitcher is the only thing rendered out of the box.
|
|
10
|
-
*/
|
|
11
|
-
export function Footer({
|
|
12
|
-
brand = "5VoltFX/Matt Welker",
|
|
13
|
-
showThemeSwitcher = true,
|
|
14
|
-
left,
|
|
15
|
-
right,
|
|
16
|
-
className = "",
|
|
17
|
-
}) {
|
|
18
|
-
const year = new Date().getFullYear();
|
|
19
|
-
|
|
20
|
-
return (
|
|
21
|
-
<footer className={["ds-footer", className].filter(Boolean).join(" ")}>
|
|
22
|
-
<div className="ds-footer__inner">
|
|
23
|
-
<div className="ds-footer__left">{left}</div>
|
|
24
|
-
<div className="ds-footer__center">
|
|
25
|
-
{showThemeSwitcher && <ThemeSwitcher />}
|
|
26
|
-
</div>
|
|
27
|
-
<div className="ds-footer__right">
|
|
28
|
-
{right ?? (
|
|
29
|
-
<span className="ds-footer__copyright">
|
|
30
|
-
© {year} {brand}
|
|
31
|
-
</span>
|
|
32
|
-
)}
|
|
33
|
-
</div>
|
|
34
|
-
</div>
|
|
35
|
-
</footer>
|
|
36
|
-
);
|
|
37
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { Footer } from "./Footer.jsx";
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
.ds-header {
|
|
2
|
-
background-color: var(--ds-color-bg-0);
|
|
3
|
-
color: var(--ds-color-font-0);
|
|
4
|
-
border-bottom: 1px solid var(--ds-color-bw-1);
|
|
5
|
-
font-family: var(--ds-font-body);
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
.ds-header__inner {
|
|
9
|
-
max-width: 1200px;
|
|
10
|
-
margin: 0 auto;
|
|
11
|
-
padding: var(--ds-space-4) var(--ds-space-6);
|
|
12
|
-
display: flex;
|
|
13
|
-
align-items: center;
|
|
14
|
-
gap: var(--ds-space-6);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
.ds-header__brand {
|
|
18
|
-
display: flex;
|
|
19
|
-
align-items: center;
|
|
20
|
-
color: var(--ds-color-font-0);
|
|
21
|
-
text-decoration: none;
|
|
22
|
-
font-weight: var(--ds-font-weight-bold);
|
|
23
|
-
font-size: var(--ds-font-size-300);
|
|
24
|
-
letter-spacing: 0.02em;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
.ds-header__nav {
|
|
28
|
-
flex: 1;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
.ds-header__actions {
|
|
32
|
-
display: flex;
|
|
33
|
-
align-items: center;
|
|
34
|
-
gap: var(--ds-space-3);
|
|
35
|
-
margin-left: auto;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
@media (width <= 480px) {
|
|
39
|
-
.ds-header__inner {
|
|
40
|
-
padding: var(--ds-space-3) var(--ds-space-4);
|
|
41
|
-
gap: var(--ds-space-4);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.ds-header__brand {
|
|
45
|
-
font-size: var(--ds-font-size-200);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { Link } from "react-router-dom";
|
|
2
|
-
import { Nav } from "../Nav/Nav.jsx";
|
|
3
|
-
import "./Header.css";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Generic header shell with brand/nav/actions slots, so each site's
|
|
7
|
-
* bespoke chrome (song-chart's auth-aware menu + fullscreen toggle,
|
|
8
|
-
* mattwelker.com's audio-loop toggle + Venmo link, 5voltfx.com's plain
|
|
9
|
-
* logo-with-text-fallback) stays app-specific and is passed in rather
|
|
10
|
-
* than encoded into this component.
|
|
11
|
-
*/
|
|
12
|
-
export function Header({
|
|
13
|
-
brand,
|
|
14
|
-
brandHref = "/",
|
|
15
|
-
links = [],
|
|
16
|
-
actions,
|
|
17
|
-
className = "",
|
|
18
|
-
}) {
|
|
19
|
-
return (
|
|
20
|
-
<header className={["ds-header", className].filter(Boolean).join(" ")}>
|
|
21
|
-
<div className="ds-header__inner">
|
|
22
|
-
<Link to={brandHref} className="ds-header__brand">
|
|
23
|
-
{brand}
|
|
24
|
-
</Link>
|
|
25
|
-
{links.length > 0 && <Nav links={links} className="ds-header__nav" />}
|
|
26
|
-
{actions && <div className="ds-header__actions">{actions}</div>}
|
|
27
|
-
</div>
|
|
28
|
-
</header>
|
|
29
|
-
);
|
|
30
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { Header } from "./Header.jsx";
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import "./Layout.css";
|
|
2
|
-
|
|
3
|
-
export function Grid({
|
|
4
|
-
as: Component = "div",
|
|
5
|
-
columns = 2,
|
|
6
|
-
gap = "4",
|
|
7
|
-
className = "",
|
|
8
|
-
style,
|
|
9
|
-
...rest
|
|
10
|
-
}) {
|
|
11
|
-
return (
|
|
12
|
-
<Component
|
|
13
|
-
className={["ds-grid", className].filter(Boolean).join(" ")}
|
|
14
|
-
style={{
|
|
15
|
-
gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
|
|
16
|
-
gap: `var(--ds-space-${gap})`,
|
|
17
|
-
...style,
|
|
18
|
-
}}
|
|
19
|
-
{...rest}
|
|
20
|
-
/>
|
|
21
|
-
);
|
|
22
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import "./Layout.css";
|
|
2
|
-
|
|
3
|
-
export function Stack({
|
|
4
|
-
as: Component = "div",
|
|
5
|
-
gap = "4",
|
|
6
|
-
direction = "column",
|
|
7
|
-
align,
|
|
8
|
-
className = "",
|
|
9
|
-
style,
|
|
10
|
-
...rest
|
|
11
|
-
}) {
|
|
12
|
-
return (
|
|
13
|
-
<Component
|
|
14
|
-
className={["ds-stack", className].filter(Boolean).join(" ")}
|
|
15
|
-
style={{
|
|
16
|
-
"--ds-stack-gap": `var(--ds-space-${gap})`,
|
|
17
|
-
flexDirection: direction,
|
|
18
|
-
alignItems: align,
|
|
19
|
-
...style,
|
|
20
|
-
}}
|
|
21
|
-
{...rest}
|
|
22
|
-
/>
|
|
23
|
-
);
|
|
24
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { NavLink } from "react-router-dom";
|
|
2
|
-
import "./Nav.css";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Generic nav-links list. Generalizes 5voltfx.com's Navbar.jsx (active
|
|
6
|
-
* underline via NavLink), song-chart's SiteHeader nav, and mattwelker.com's
|
|
7
|
-
* PageNav link list into one component driven by a `links` array.
|
|
8
|
-
*/
|
|
9
|
-
export function Nav({ links = [], className = "" }) {
|
|
10
|
-
return (
|
|
11
|
-
<nav className={["ds-nav", className].filter(Boolean).join(" ")}>
|
|
12
|
-
<ul className="ds-nav__list">
|
|
13
|
-
{links.map((link) => (
|
|
14
|
-
<li key={link.to}>
|
|
15
|
-
<NavLink
|
|
16
|
-
to={link.to}
|
|
17
|
-
end={link.end}
|
|
18
|
-
className={({ isActive }) =>
|
|
19
|
-
"ds-nav__link" + (isActive ? " ds-nav__link--active" : "")
|
|
20
|
-
}
|
|
21
|
-
>
|
|
22
|
-
{link.label}
|
|
23
|
-
</NavLink>
|
|
24
|
-
</li>
|
|
25
|
-
))}
|
|
26
|
-
</ul>
|
|
27
|
-
</nav>
|
|
28
|
-
);
|
|
29
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { Nav } from "./Nav.jsx";
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { useTheme } from "../../theme/useTheme.js";
|
|
2
|
-
import "./ThemeSwitcher.css";
|
|
3
|
-
|
|
4
|
-
export function ThemeSwitcher({ className = "" }) {
|
|
5
|
-
const { theme, setTheme, themes } = useTheme();
|
|
6
|
-
|
|
7
|
-
return (
|
|
8
|
-
<div className={["ds-theme-switcher", className].filter(Boolean).join(" ")}>
|
|
9
|
-
{themes.map((option) => (
|
|
10
|
-
<button
|
|
11
|
-
key={option.id}
|
|
12
|
-
type="button"
|
|
13
|
-
className={
|
|
14
|
-
"ds-theme-switcher__swatch" +
|
|
15
|
-
(option.id === theme ? " ds-theme-switcher__swatch--active" : "")
|
|
16
|
-
}
|
|
17
|
-
style={{ backgroundColor: option.swatch }}
|
|
18
|
-
aria-label={option.label}
|
|
19
|
-
aria-pressed={option.id === theme}
|
|
20
|
-
title={option.label}
|
|
21
|
-
onClick={() => setTheme(option.id)}
|
|
22
|
-
/>
|
|
23
|
-
))}
|
|
24
|
-
</div>
|
|
25
|
-
);
|
|
26
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { ThemeSwitcher } from "./ThemeSwitcher.jsx";
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import "./Typography.css";
|
|
2
|
-
|
|
3
|
-
const SCALE_BY_LEVEL = { 1: "700", 2: "600", 3: "500", 4: "400", 5: "300", 6: "200" };
|
|
4
|
-
|
|
5
|
-
export function Heading({
|
|
6
|
-
level = 1,
|
|
7
|
-
size,
|
|
8
|
-
decorative = false,
|
|
9
|
-
className = "",
|
|
10
|
-
children,
|
|
11
|
-
...rest
|
|
12
|
-
}) {
|
|
13
|
-
const Component = `h${level}`;
|
|
14
|
-
const scale = size ?? SCALE_BY_LEVEL[level] ?? "300";
|
|
15
|
-
const classes = [
|
|
16
|
-
"ds-heading",
|
|
17
|
-
`ds-heading--${scale}`,
|
|
18
|
-
decorative ? "ds-heading--decorative" : "",
|
|
19
|
-
className,
|
|
20
|
-
]
|
|
21
|
-
.filter(Boolean)
|
|
22
|
-
.join(" ");
|
|
23
|
-
|
|
24
|
-
return (
|
|
25
|
-
<Component className={classes} {...rest}>
|
|
26
|
-
{children}
|
|
27
|
-
</Component>
|
|
28
|
-
);
|
|
29
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import "./Typography.css";
|
|
2
|
-
|
|
3
|
-
export function Text({
|
|
4
|
-
as: Component = "p",
|
|
5
|
-
size = "200",
|
|
6
|
-
tone = "default",
|
|
7
|
-
className = "",
|
|
8
|
-
children,
|
|
9
|
-
...rest
|
|
10
|
-
}) {
|
|
11
|
-
const classes = ["ds-text", `ds-text--${size}`, `ds-text--${tone}`, className]
|
|
12
|
-
.filter(Boolean)
|
|
13
|
-
.join(" ");
|
|
14
|
-
|
|
15
|
-
return (
|
|
16
|
-
<Component className={classes} {...rest}>
|
|
17
|
-
{children}
|
|
18
|
-
</Component>
|
|
19
|
-
);
|
|
20
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
.ds-heading {
|
|
2
|
-
margin: 0;
|
|
3
|
-
font-family: var(--ds-font-body);
|
|
4
|
-
font-weight: var(--ds-font-weight-bold);
|
|
5
|
-
line-height: var(--ds-line-height-tight);
|
|
6
|
-
color: var(--ds-color-font-0);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
.ds-heading--decorative {
|
|
10
|
-
font-family: var(--ds-font-decorative);
|
|
11
|
-
font-weight: var(--ds-font-weight-regular);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
.ds-heading--700 { font-size: var(--ds-font-size-700); }
|
|
15
|
-
.ds-heading--600 { font-size: var(--ds-font-size-600); }
|
|
16
|
-
.ds-heading--500 { font-size: var(--ds-font-size-500); }
|
|
17
|
-
.ds-heading--400 { font-size: var(--ds-font-size-400); }
|
|
18
|
-
.ds-heading--300 { font-size: var(--ds-font-size-300); }
|
|
19
|
-
.ds-heading--200 { font-size: var(--ds-font-size-200); }
|
|
20
|
-
|
|
21
|
-
.ds-text {
|
|
22
|
-
margin: 0;
|
|
23
|
-
font-family: var(--ds-font-body);
|
|
24
|
-
font-weight: var(--ds-font-weight-regular);
|
|
25
|
-
line-height: var(--ds-line-height-normal);
|
|
26
|
-
color: var(--ds-color-font-0);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.ds-text--100 { font-size: var(--ds-font-size-100); }
|
|
30
|
-
.ds-text--200 { font-size: var(--ds-font-size-200); }
|
|
31
|
-
.ds-text--300 { font-size: var(--ds-font-size-300); }
|
|
32
|
-
|
|
33
|
-
.ds-text--muted {
|
|
34
|
-
color: var(--ds-color-font-1);
|
|
35
|
-
}
|
package/src/index.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export { ThemeProvider } from "./theme/ThemeProvider.jsx";
|
|
2
|
-
export { useTheme } from "./theme/useTheme.js";
|
|
3
|
-
export { THEMES, THEME_IDS, DEFAULT_THEME } from "./theme/themeConstants.js";
|
|
4
|
-
|
|
5
|
-
export { Button } from "./components/Button/index.js";
|
|
6
|
-
export { Card } from "./components/Card/index.js";
|
|
7
|
-
export { Header } from "./components/Header/index.js";
|
|
8
|
-
export { Nav } from "./components/Nav/index.js";
|
|
9
|
-
export { Footer } from "./components/Footer/index.js";
|
|
10
|
-
export { ThemeSwitcher } from "./components/ThemeSwitcher/index.js";
|
|
11
|
-
export { Heading, Text } from "./components/Typography/index.js";
|
|
12
|
-
export { Container, Stack, Grid } from "./components/Layout/index.js";
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { useCallback, useLayoutEffect, useMemo, useState } from "react";
|
|
2
|
-
import { ThemeContext } from "./ThemeContext.js";
|
|
3
|
-
import { THEMES, THEME_IDS, DEFAULT_THEME } from "./themeConstants.js";
|
|
4
|
-
|
|
5
|
-
function readStoredTheme(storageKey, fallback) {
|
|
6
|
-
if (typeof window === "undefined") return fallback;
|
|
7
|
-
try {
|
|
8
|
-
const stored = window.localStorage.getItem(storageKey);
|
|
9
|
-
return THEME_IDS.includes(stored) ? stored : fallback;
|
|
10
|
-
} catch {
|
|
11
|
-
return fallback;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* data-theme lives on <html> rather than a class or a wrapper div so every
|
|
17
|
-
* consuming site (song-chart used a class on <html>, mattwelker.com used a
|
|
18
|
-
* class on a wrapper div) lands on one shared convention.
|
|
19
|
-
*/
|
|
20
|
-
export function ThemeProvider({
|
|
21
|
-
children,
|
|
22
|
-
storageKey = "ds.theme",
|
|
23
|
-
defaultTheme = DEFAULT_THEME,
|
|
24
|
-
onThemeChange,
|
|
25
|
-
}) {
|
|
26
|
-
const [theme, setThemeState] = useState(() =>
|
|
27
|
-
readStoredTheme(storageKey, defaultTheme)
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
useLayoutEffect(() => {
|
|
31
|
-
document.documentElement.setAttribute("data-theme", theme);
|
|
32
|
-
try {
|
|
33
|
-
window.localStorage.setItem(storageKey, theme);
|
|
34
|
-
} catch {
|
|
35
|
-
// localStorage unavailable (private browsing, etc.) — theme still applies for this session
|
|
36
|
-
}
|
|
37
|
-
}, [theme, storageKey]);
|
|
38
|
-
|
|
39
|
-
useLayoutEffect(() => {
|
|
40
|
-
function handleStorage(event) {
|
|
41
|
-
if (event.key === storageKey && THEME_IDS.includes(event.newValue)) {
|
|
42
|
-
setThemeState(event.newValue);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
window.addEventListener("storage", handleStorage);
|
|
46
|
-
return () => window.removeEventListener("storage", handleStorage);
|
|
47
|
-
}, [storageKey]);
|
|
48
|
-
|
|
49
|
-
const setTheme = useCallback(
|
|
50
|
-
(id) => {
|
|
51
|
-
if (!THEME_IDS.includes(id)) return;
|
|
52
|
-
setThemeState(id);
|
|
53
|
-
onThemeChange?.(id);
|
|
54
|
-
},
|
|
55
|
-
[onThemeChange]
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
const value = useMemo(
|
|
59
|
-
() => ({ theme, setTheme, themes: THEMES }),
|
|
60
|
-
[theme, setTheme]
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
return (
|
|
64
|
-
<ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
|
|
65
|
-
);
|
|
66
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export const THEMES = [
|
|
2
|
-
{ id: "coral", label: "Coral — creative warm", swatch: "#ff6b6b" },
|
|
3
|
-
{ id: "ink", label: "Ink — dark indigo", swatch: "#6366f1" },
|
|
4
|
-
{ id: "sage", label: "Sage — muted olive", swatch: "#6b7568" },
|
|
5
|
-
{ id: "midnight", label: "Midnight — high contrast", swatch: "#00d4ff" },
|
|
6
|
-
{ id: "amber", label: "Amber — golden", swatch: "#b8652a" },
|
|
7
|
-
{ id: "harvest", label: "Harvest — earthy warm", swatch: "#9f5508" },
|
|
8
|
-
];
|
|
9
|
-
|
|
10
|
-
export const THEME_IDS = THEMES.map((theme) => theme.id);
|
|
11
|
-
|
|
12
|
-
export const DEFAULT_THEME = "coral";
|
package/src/theme/useTheme.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { useContext } from "react";
|
|
2
|
-
import { ThemeContext } from "./ThemeContext.js";
|
|
3
|
-
|
|
4
|
-
export function useTheme() {
|
|
5
|
-
const context = useContext(ThemeContext);
|
|
6
|
-
if (!context) {
|
|
7
|
-
throw new Error("useTheme must be used within a ThemeProvider");
|
|
8
|
-
}
|
|
9
|
-
return context;
|
|
10
|
-
}
|
package/src/tokens/base.css
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Every theme pairs bg-0 (page surface) with font-0 (on-surface text) —
|
|
3
|
-
without this, the page background stays the browser default (white) while
|
|
4
|
-
text/components assume a themed surface behind them, e.g. font-0 is
|
|
5
|
-
near-white in every theme because it's meant to sit on the dark bg-0.
|
|
6
|
-
*/
|
|
7
|
-
html,
|
|
8
|
-
body {
|
|
9
|
-
background-color: var(--ds-color-bg-0);
|
|
10
|
-
color: var(--ds-color-font-0);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
body {
|
|
14
|
-
margin: 0;
|
|
15
|
-
min-height: 100vh;
|
|
16
|
-
font-family: var(--ds-font-body);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
* {
|
|
20
|
-
box-sizing: border-box;
|
|
21
|
-
}
|
package/src/tokens/colors.css
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Base color token shape. Every theme in themes.css overrides this full set
|
|
3
|
-
via [data-theme="<id>"]. These :root values are the default (coral) theme,
|
|
4
|
-
so components render sensibly even before ThemeProvider sets data-theme.
|
|
5
|
-
*/
|
|
6
|
-
:root {
|
|
7
|
-
--ds-color-url: #ff8787;
|
|
8
|
-
--ds-color-visited: #ffb4a2;
|
|
9
|
-
|
|
10
|
-
--ds-color-bg-0: #1a2f2f;
|
|
11
|
-
--ds-color-bg-1: #2d4a4a;
|
|
12
|
-
--ds-color-bg-2: #ff6b6b;
|
|
13
|
-
--ds-color-bg-3: #5c8a8a;
|
|
14
|
-
--ds-color-bg-4: #f7f0eb;
|
|
15
|
-
|
|
16
|
-
--ds-color-font-0: #fff5f0;
|
|
17
|
-
--ds-color-font-1: #ffb4a2;
|
|
18
|
-
--ds-color-font-2: #ff6b6b;
|
|
19
|
-
--ds-color-font-3: #2d4a4a;
|
|
20
|
-
--ds-color-font-4: #1a2f2f;
|
|
21
|
-
|
|
22
|
-
--ds-color-bw-0: #142424;
|
|
23
|
-
--ds-color-bw-1: #2d4a4a;
|
|
24
|
-
--ds-color-bw-2: #5c8a8a;
|
|
25
|
-
--ds-color-bw-3: #c5d5d5;
|
|
26
|
-
--ds-color-bw-4: #fff;
|
|
27
|
-
|
|
28
|
-
--ds-color-highlight: #7c3aed;
|
|
29
|
-
}
|
package/src/tokens/elevation.css
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Shadows are tinted with the active theme's accent (--ds-color-bg-2) at low
|
|
3
|
-
opacity rather than flat black, since none of the 3 sites uses any shadow
|
|
4
|
-
today and a flat-black shadow would read as generic rather than "on-brand".
|
|
5
|
-
*/
|
|
6
|
-
:root {
|
|
7
|
-
--ds-shadow-sm: 0 1px 3px color-mix(in srgb, var(--ds-color-bg-2) 18%, transparent);
|
|
8
|
-
--ds-shadow-md: 0 6px 16px color-mix(in srgb, var(--ds-color-bg-2) 20%, transparent);
|
|
9
|
-
--ds-shadow-lg: 0 16px 40px color-mix(in srgb, var(--ds-color-bg-2) 24%, transparent);
|
|
10
|
-
}
|