@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.
Files changed (93) hide show
  1. package/dist/chunk-3LTHHL5C.js +11 -0
  2. package/dist/chunk-3LTHHL5C.js.map +1 -0
  3. package/dist/chunk-5KN5TPGG.js +30 -0
  4. package/dist/chunk-5KN5TPGG.js.map +1 -0
  5. package/dist/chunk-AYAMNHNF.js +1 -0
  6. package/dist/chunk-AYAMNHNF.js.map +1 -0
  7. package/dist/chunk-BOPVPQLB.js +25 -0
  8. package/dist/chunk-BOPVPQLB.js.map +1 -0
  9. package/dist/chunk-EW3S3GK6.js +41 -0
  10. package/dist/chunk-EW3S3GK6.js.map +1 -0
  11. package/dist/chunk-NE25337V.js +1 -0
  12. package/dist/chunk-NE25337V.js.map +1 -0
  13. package/dist/chunk-SFECTSQP.js +68 -0
  14. package/dist/chunk-SFECTSQP.js.map +1 -0
  15. package/dist/chunk-TNWQQ56F.js +19 -0
  16. package/dist/chunk-TNWQQ56F.js.map +1 -0
  17. package/dist/chunk-U6JTDANG.js +22 -0
  18. package/dist/chunk-U6JTDANG.js.map +1 -0
  19. package/dist/chunk-YCZXDQJZ.js +41 -0
  20. package/dist/chunk-YCZXDQJZ.js.map +1 -0
  21. package/{src/components/Button/Button.css → dist/components/Button/index.css} +2 -11
  22. package/dist/components/Button/index.css.map +1 -0
  23. package/dist/components/Button/index.js +7 -0
  24. package/dist/components/Button/index.js.map +1 -0
  25. package/{src/components/Card/Card.css → dist/components/Card/index.css} +3 -3
  26. package/dist/components/Card/index.css.map +1 -0
  27. package/dist/components/Card/index.js +7 -0
  28. package/dist/components/Card/index.js.map +1 -0
  29. package/{src/components/Footer/Footer.css → dist/components/Footer/index.css} +28 -7
  30. package/dist/components/Footer/index.css.map +1 -0
  31. package/dist/components/Footer/index.js +8 -0
  32. package/dist/components/Footer/index.js.map +1 -0
  33. package/dist/components/Header/index.css +98 -0
  34. package/dist/components/Header/index.css.map +1 -0
  35. package/dist/components/Header/index.js +8 -0
  36. package/dist/components/Header/index.js.map +1 -0
  37. package/{src/components/Layout/Layout.css → dist/components/Layout/index.css} +2 -4
  38. package/dist/components/Layout/index.css.map +1 -0
  39. package/dist/components/Layout/index.js +11 -0
  40. package/dist/components/Layout/index.js.map +1 -0
  41. package/{src/components/Nav/Nav.css → dist/components/Nav/index.css} +2 -8
  42. package/dist/components/Nav/index.css.map +1 -0
  43. package/dist/components/Nav/index.js +8 -0
  44. package/dist/components/Nav/index.js.map +1 -0
  45. package/{src/components/ThemeSwitcher/ThemeSwitcher.css → dist/components/ThemeSwitcher/index.css} +3 -7
  46. package/dist/components/ThemeSwitcher/index.css.map +1 -0
  47. package/dist/components/ThemeSwitcher/index.js +8 -0
  48. package/dist/components/ThemeSwitcher/index.js.map +1 -0
  49. package/dist/components/Typography/index.css +50 -0
  50. package/dist/components/Typography/index.css.map +1 -0
  51. package/dist/components/Typography/index.js +9 -0
  52. package/dist/components/Typography/index.js.map +1 -0
  53. package/dist/index.css +544 -0
  54. package/dist/index.css.map +1 -0
  55. package/dist/index.js +116 -0
  56. package/dist/index.js.map +1 -0
  57. package/package.json +19 -6
  58. package/vite-plugin.js +13 -10
  59. package/src/components/Button/Button.jsx +0 -20
  60. package/src/components/Button/index.js +0 -1
  61. package/src/components/Card/Card.jsx +0 -6
  62. package/src/components/Card/index.js +0 -1
  63. package/src/components/Footer/Footer.jsx +0 -37
  64. package/src/components/Footer/index.js +0 -1
  65. package/src/components/Header/Header.css +0 -47
  66. package/src/components/Header/Header.jsx +0 -30
  67. package/src/components/Header/index.js +0 -1
  68. package/src/components/Layout/Container.jsx +0 -10
  69. package/src/components/Layout/Grid.jsx +0 -22
  70. package/src/components/Layout/Stack.jsx +0 -24
  71. package/src/components/Layout/index.js +0 -3
  72. package/src/components/Nav/Nav.jsx +0 -29
  73. package/src/components/Nav/index.js +0 -1
  74. package/src/components/ThemeSwitcher/ThemeSwitcher.jsx +0 -26
  75. package/src/components/ThemeSwitcher/index.js +0 -1
  76. package/src/components/Typography/Heading.jsx +0 -29
  77. package/src/components/Typography/Text.jsx +0 -20
  78. package/src/components/Typography/Typography.css +0 -35
  79. package/src/components/Typography/index.js +0 -2
  80. package/src/index.js +0 -12
  81. package/src/theme/ThemeContext.js +0 -3
  82. package/src/theme/ThemeProvider.jsx +0 -66
  83. package/src/theme/themeConstants.js +0 -12
  84. package/src/theme/useTheme.js +0 -10
  85. package/src/tokens/base.css +0 -21
  86. package/src/tokens/colors.css +0 -29
  87. package/src/tokens/elevation.css +0 -10
  88. package/src/tokens/index.css +0 -8
  89. package/src/tokens/motion.css +0 -6
  90. package/src/tokens/radius.css +0 -6
  91. package/src/tokens/spacing.css +0 -10
  92. package/src/tokens/themes.css +0 -156
  93. 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.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
- "main": "./src/index.js",
5
+ "main": "./dist/index.js",
6
6
  "exports": {
7
- ".": "./src/index.js",
8
- "./styles.css": "./src/tokens/index.css",
9
- "./components/*": "./src/components/*/index.js",
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
- "src",
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 designSystemSrc = path.join(packageRoot, "src");
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 made through the symlinked file: dependency (which lives outside
10
- * that root) are invisible to HMR no matter what `server.watch.ignored`
11
- * patterns say. This plugin explicitly adds the design system's real source
12
- * directory to the watcher and forces a full reload when anything in it
13
- * changes. Add it to every site's vite.config.js alongside the file:
14
- * dependency on @5voltfx/design-system.
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(designSystemSrc);
23
+ server.watcher.add(designSystemDist);
21
24
  server.watcher.on("change", (file) => {
22
- if (file.startsWith(designSystemSrc)) {
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 DESIGN_SYSTEM_SRC = designSystemSrc;
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,6 +0,0 @@
1
- import "./Card.css";
2
-
3
- export function Card({ as: Component = "div", className = "", ...rest }) {
4
- const classes = ["ds-card", className].filter(Boolean).join(" ");
5
- return <Component className={classes} {...rest} />;
6
- }
@@ -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,10 +0,0 @@
1
- import "./Layout.css";
2
-
3
- export function Container({ as: Component = "div", className = "", ...rest }) {
4
- return (
5
- <Component
6
- className={["ds-container", className].filter(Boolean).join(" ")}
7
- {...rest}
8
- />
9
- );
10
- }
@@ -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,3 +0,0 @@
1
- export { Container } from "./Container.jsx";
2
- export { Stack } from "./Stack.jsx";
3
- export { Grid } from "./Grid.jsx";
@@ -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
- }
@@ -1,2 +0,0 @@
1
- export { Heading } from "./Heading.jsx";
2
- export { Text } from "./Text.jsx";
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,3 +0,0 @@
1
- import { createContext } from "react";
2
-
3
- export const ThemeContext = createContext(null);
@@ -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";
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -1,8 +0,0 @@
1
- @import "./colors.css";
2
- @import "./themes.css";
3
- @import "./base.css";
4
- @import "./typography.css";
5
- @import "./spacing.css";
6
- @import "./radius.css";
7
- @import "./elevation.css";
8
- @import "./motion.css";