@chronogrove/ui 0.79.0 → 0.81.0
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/README.md +40 -19
- package/package.json +73 -6
- package/src/__snapshots__/header.spec.js.snap +8 -8
- package/src/__snapshots__/theme.spec.js.snap +39 -20
- package/src/action-button.js +6 -6
- package/src/action-button.spec.js +14 -2
- package/src/action-card-layout.js +13 -0
- package/src/action-card-layout.spec.js +13 -0
- package/src/animated-page-background/ChronogroveAnimatedPageBackground.js +153 -0
- package/src/animated-page-background/ChronogroveAnimatedPageBackground.spec.js +189 -0
- package/src/animated-page-background/ColorBends.js +309 -0
- package/src/animated-page-background/color-bends.css +13 -0
- package/src/animated-page-background/index.js +2 -0
- package/src/animated-page-background/index.spec.js +18 -0
- package/src/button.js +4 -3
- package/src/category-label.js +23 -0
- package/src/category-label.spec.js +24 -0
- package/src/chevron-icons.js +37 -0
- package/src/chronogrove-theme-surface-colors.js +22 -0
- package/src/color-mode/browser-sync.js +7 -0
- package/src/color-mode/browser-sync.spec.js +7 -0
- package/src/color-mode/chronogrove-head-theme.js +22 -0
- package/src/color-mode/head-inline.js +40 -5
- package/src/color-mode/head-inline.spec.js +29 -0
- package/src/color-mode/index.js +3 -0
- package/src/color-mode/resolve-theme-colors.js +18 -6
- package/src/color-mode/resolve-theme-colors.spec.js +13 -3
- package/src/color-mode/spa-navigation.js +14 -0
- package/src/color-mode/spa-navigation.spec.js +25 -0
- package/src/color-mode/use-document-color-mode-surface.js +52 -0
- package/src/color-mode/use-document-color-mode-surface.node.spec.js +12 -0
- package/src/color-mode/use-document-color-mode-surface.spec.js +154 -0
- package/src/color-toggle-styles.css +10 -0
- package/src/color-toggle.js +11 -2
- package/src/emotion-cache.node.spec.js +13 -0
- package/src/emotion-cache.spec.js +12 -0
- package/src/external-link-icon.js +30 -0
- package/src/external-link-icon.spec.js +16 -0
- package/src/gatsby/build-theme-ui-color-mode-head-components.js +7 -1
- package/src/gatsby/index.spec.js +42 -0
- package/src/gatsby/on-route-update-color-mode.js +1 -14
- package/src/header.js +4 -16
- package/src/lazy-load.js +30 -11
- package/src/lazy-load.spec.js +9 -5
- package/src/metric-badge.js +10 -0
- package/src/metric-badge.spec.js +15 -0
- package/src/metric-card.js +95 -0
- package/src/metric-card.spec.js +60 -0
- package/src/muted-card-footer.js +22 -0
- package/src/muted-card-footer.spec.js +25 -0
- package/src/next/app-shell.js +34 -0
- package/src/next/emotion-registry.js +68 -0
- package/src/next/emotion-registry.spec.js +99 -0
- package/src/next/index.js +4 -0
- package/src/next/root-layout-head.js +42 -0
- package/src/next/root-layout-head.spec.js +17 -0
- package/src/next/theme-ui-color-mode-route-sync.js +32 -0
- package/src/page-backdrop.js +42 -0
- package/src/page-backdrop.spec.js +41 -0
- package/src/pagination-button.js +4 -4
- package/src/pagination-button.spec.js +26 -2
- package/src/pagination.js +198 -0
- package/src/pagination.spec.js +281 -0
- package/src/skip-nav/SkipNavLink.js +6 -5
- package/src/skip-nav/SkipNavLink.spec.js +11 -0
- package/src/status-card.js +18 -0
- package/src/status-card.spec.js +38 -0
- package/src/theme.js +27 -20
- package/src/widget-call-to-action.js +106 -0
- package/src/widget-call-to-action.spec.js +115 -0
- package/src/widget-section.js +83 -0
- package/src/widget-section.spec.js +59 -0
package/README.md
CHANGED
|
@@ -18,28 +18,49 @@ Use **`pnpm publish`** for releases so `workspace:` dependencies in dependents a
|
|
|
18
18
|
|
|
19
19
|
Prefer deep imports so bundles stay lean:
|
|
20
20
|
|
|
21
|
-
| Import path
|
|
22
|
-
|
|
|
23
|
-
| `@chronogrove/ui`
|
|
24
|
-
| `@chronogrove/ui/theme`
|
|
25
|
-
| `@chronogrove/ui/provider`
|
|
26
|
-
| `@chronogrove/ui/color-mode`
|
|
27
|
-
| `@chronogrove/ui/
|
|
28
|
-
| `@chronogrove/ui/
|
|
29
|
-
| `@chronogrove/ui/
|
|
30
|
-
| `@chronogrove/ui/
|
|
31
|
-
| `@chronogrove/ui/
|
|
32
|
-
| `@chronogrove/ui/
|
|
33
|
-
| `@chronogrove/ui/
|
|
34
|
-
| `@chronogrove/ui/
|
|
35
|
-
| `@chronogrove/ui/
|
|
36
|
-
| `@chronogrove/ui/
|
|
37
|
-
| `@chronogrove/ui/
|
|
38
|
-
| `@chronogrove/ui/
|
|
21
|
+
| Import path | Contents |
|
|
22
|
+
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
23
|
+
| `@chronogrove/ui` | `ChronogroveThemeProvider` |
|
|
24
|
+
| `@chronogrove/ui/theme` | Default Theme UI theme object + named exports |
|
|
25
|
+
| `@chronogrove/ui/provider` | `ChronogroveThemeProvider` |
|
|
26
|
+
| `@chronogrove/ui/color-mode` | Storage key, reconcile event, SSR inline builders, `chronogroveHeadTheme` (RSC-safe), `resolveChronogroveSurfaceColors`, `useDocumentColorModeSurface`, browser sync, `reconcileThemeUiColorModeOnNavigation` |
|
|
27
|
+
| `@chronogrove/ui/animated-page-background` | **`ChronogroveAnimatedPageBackground`** — same stack as the Gatsby home: fixed `z-index: 0`, light = solid theme background, dark = **three.js** Color Bends + scroll-linked gradient overlay and parallax (`three` is a dependency of this package). **Not** the same as `page-backdrop`. |
|
|
28
|
+
| `@chronogrove/ui/color-bends` | **`ColorBends`** — lower-level three.js gradient background used inside `ChronogroveAnimatedPageBackground`. Prefer the full **`animated-page-background`** or **`@chronogrove/ui/next`** shell unless you need the raw component. |
|
|
29
|
+
| `@chronogrove/ui/page-backdrop` | **`ChronogrovePageBackdrop`** — lightweight alternative: fixed `z-index: 0` fill without WebGL (CSS gradients in dark mode). Use when you cannot ship `three`. |
|
|
30
|
+
| `@chronogrove/ui/next` | **Next.js App Router helpers:** `ChronogroveNextRootLayoutHead` (RSC `<head>` injections), `ChronogroveNextEmotionRegistry`, `ChronogroveNextAppShell` (theme + three.js background + surface sync + soft-nav reconcile), `ChronogroveNextThemeUiColorModeRouteSync` (standalone). Requires `next` (peer, optional for the rest of the package). |
|
|
31
|
+
| `@chronogrove/ui/action-card-layout` | **`actionCardPinnedLayoutSx`** — layout `sx` for `Card variant="actionCard"` (matches GitHub pinned cards). |
|
|
32
|
+
| `@chronogrove/ui/emotion-cache` | `createChronogroveEmotionCache`, `getChronogroveEmotionCache` |
|
|
33
|
+
| `@chronogrove/ui/button` | Theme UI `components` button |
|
|
34
|
+
| `@chronogrove/ui/color-toggle` | Theme UI + `@theme-toggles/react` `Expand` |
|
|
35
|
+
| `@chronogrove/ui/color-toggle-styles` | CSS for the toggle (`Expand.css` + sizing); `@import` once in global CSS (see `examples/chronogrove-next/app/globals.css`) |
|
|
36
|
+
| `@chronogrove/ui/skip-nav` | `SkipNavLink`, `SkipNavContent` |
|
|
37
|
+
| `@chronogrove/ui/is-dark-mode` | `colorMode === 'dark'` helper |
|
|
38
|
+
| `@chronogrove/ui/color-utils` | `hexToRgb`, `hexToRgba`, `BUTTON_PRIMARY_COLORS` |
|
|
39
|
+
| `@chronogrove/ui/action-button` | Outline CTA as `<button>` or `<a>` |
|
|
40
|
+
| `@chronogrove/ui/pagination-button` | Compact paginator control |
|
|
41
|
+
| `@chronogrove/ui/lazy-load` | Defer children until in viewport (`react-intersection-observer`) |
|
|
42
|
+
| `@chronogrove/ui/header` | Masthead shell (`variant: styles.Header`) |
|
|
43
|
+
| `@chronogrove/ui/page-header` | Blog-style `h1` heading (`p-name`) |
|
|
44
|
+
| `@chronogrove/ui/gatsby` | Color-mode Gatsby SSR/browser helpers |
|
|
39
45
|
|
|
40
46
|
## Next.js (App Router)
|
|
41
47
|
|
|
42
|
-
|
|
48
|
+
**Reference app:** [`examples/chronogrove-next`](../../examples/chronogrove-next) (`chronogrove-next`). Run `pnpm --filter chronogrove-next dev` from the repo root.
|
|
49
|
+
|
|
50
|
+
**Prefer `@chronogrove/ui/next`** for the standard wiring: `ChronogroveNextRootLayoutHead` in `<head>`, `ChronogroveNextEmotionRegistry` wrapping the body tree, and `ChronogroveNextAppShell` (or compose the lower-level exports yourself).
|
|
51
|
+
|
|
52
|
+
**Server vs client**
|
|
53
|
+
|
|
54
|
+
- **Server `layout`:** Keep the root layout a Server Component. **Do not** import `@chronogrove/ui/theme` here—it loads `theme-ui`’s `merge` and triggers React `createContext`, which Next.js disallows in RSC. Use **`ChronogroveNextRootLayoutHead`** from `@chronogrove/ui/next`, or manually pass **`chronogroveHeadTheme`** from `@chronogrove/ui/color-mode` to `resolveChronogroveSurfaceColors` and emit **in order**: `<meta name="emotion-insertion-point" content="" />`, then the Theme UI no-flash script, HTML background script, and fallback CSS (same composition as [`buildThemeUiColorModeHeadComponents`](./src/gatsby/build-theme-ui-color-mode-head-components.js) in `@chronogrove/ui/gatsby`).
|
|
55
|
+
- **Client Emotion registry + theme:** Use **`ChronogroveNextEmotionRegistry`** from `@chronogrove/ui/next`, or wrap the app in Emotion’s `CacheProvider` with a **per-request** cache with `key: 'css'` and Next’s `useServerInsertedHTML` ([Next.js: CSS-in-JS](https://nextjs.org/docs/app/building-your-application/styling/css-in-js)). **Do not** rely on `getChronogroveEmotionCache()` for SSR—it is a browser-oriented singleton. Import the **full** theme from `@chronogrove/ui/theme` only inside client components. Anything using `useColorMode`, `useThemeUI`, or `ChronogroveThemeProvider` must be `'use client'`. **Order:** `CacheProvider` (registry) **outside** `ChronogroveThemeProvider` **inside** `<body>`.
|
|
56
|
+
- **Document surface (match Gatsby `RootWrapper`):** **`ChronogroveNextAppShell`** calls **`useDocumentColorModeSurface`** from `@chronogrove/ui/color-mode` once. It syncs `document.documentElement`’s `theme-ui-*` class, `data-theme-ui-color-mode`, and inline page background from the **resolved** Theme UI theme (`rawColors` / `colors.background`). Without it, Emotion can win the cascade over head fallback CSS and panel tokens (`bg: 'panel-background'`) may not update in dark mode after hydration.
|
|
57
|
+
- **Animated page background (match Gatsby home):** **`ChronogroveNextAppShell`** includes **`ChronogroveAnimatedPageBackground`** (three.js Color Bends). For a **CSS-only** backdrop without `three`, use **`ChronogrovePageBackdrop`** from `@chronogrove/ui/page-backdrop` instead and compose your own shell. Content should sit in a **`position: relative; z-index: 1`** wrapper.
|
|
58
|
+
- **Soft navigations:** **`ChronogroveNextAppShell`** includes **`ChronogroveNextThemeUiColorModeRouteSync`**, which calls `reconcileThemeUiColorModeOnNavigation` after pathname changes (not on initial mount). Running reconcile on mount can fight `useColorMode` toggles. Gatsby’s equivalent is `onRouteUpdateThemeUiColorMode` (no initial `onRouteUpdate`).
|
|
59
|
+
- **Hydration:** Set `suppressHydrationWarning` on `<html>` and `<body>`. The inline no-flash / background scripts run before React hydrates and update `<html>` (`theme-ui-*` classes, `data-theme-ui-color-mode`, background), so the DOM no longer matches the server-rendered markup—React would warn without this flag (similar to [next-themes](https://github.com/pacocoursey/next-themes) on Next.js App Router).
|
|
60
|
+
|
|
61
|
+
**Imports:** Prefer `@chronogrove/ui/color-mode` for head builders and SPA reconcile. Reserve `@chronogrove/ui/gatsby` for Gatsby hooks (`onPreRenderHTML`, `onRenderBody` helpers).
|
|
62
|
+
|
|
63
|
+
**JSX + bundlers:** Primitives such as [`button`](./src/button.js) use [`Box`](https://theme-ui.com/components/box) from `@theme-ui/components` with an `as` prop for native elements, so `sx` works with Gatsby’s classic JSX runtime and Next’s SWC without a Theme UI file pragma. Jest uses [`babel.config.cjs`](./babel.config.cjs) (automatic JSX).
|
|
43
64
|
|
|
44
65
|
## Changelog
|
|
45
66
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chronogrove/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.81.0",
|
|
4
4
|
"description": "Chronogrove Theme UI theme, color mode helpers, and shared UI primitives",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
"url": "https://github.com/chrisvogt/gatsby-theme-chronogrove.git",
|
|
14
14
|
"directory": "packages/ui"
|
|
15
15
|
},
|
|
16
|
-
"sideEffects":
|
|
16
|
+
"sideEffects": [
|
|
17
|
+
"**/*.css"
|
|
18
|
+
],
|
|
17
19
|
"exports": {
|
|
18
20
|
".": {
|
|
19
21
|
"import": "./src/index.js",
|
|
@@ -35,6 +37,7 @@
|
|
|
35
37
|
"import": "./src/color-toggle.js",
|
|
36
38
|
"default": "./src/color-toggle.js"
|
|
37
39
|
},
|
|
40
|
+
"./color-toggle-styles": "./src/color-toggle-styles.css",
|
|
38
41
|
"./skip-nav": {
|
|
39
42
|
"import": "./src/skip-nav/index.js",
|
|
40
43
|
"default": "./src/skip-nav/index.js"
|
|
@@ -63,10 +66,50 @@
|
|
|
63
66
|
"import": "./src/pagination-button.js",
|
|
64
67
|
"default": "./src/pagination-button.js"
|
|
65
68
|
},
|
|
69
|
+
"./pagination": {
|
|
70
|
+
"import": "./src/pagination.js",
|
|
71
|
+
"default": "./src/pagination.js"
|
|
72
|
+
},
|
|
73
|
+
"./category-label": {
|
|
74
|
+
"import": "./src/category-label.js",
|
|
75
|
+
"default": "./src/category-label.js"
|
|
76
|
+
},
|
|
77
|
+
"./external-link-icon": {
|
|
78
|
+
"import": "./src/external-link-icon.js",
|
|
79
|
+
"default": "./src/external-link-icon.js"
|
|
80
|
+
},
|
|
81
|
+
"./metric-badge": {
|
|
82
|
+
"import": "./src/metric-badge.js",
|
|
83
|
+
"default": "./src/metric-badge.js"
|
|
84
|
+
},
|
|
85
|
+
"./metric-card": {
|
|
86
|
+
"import": "./src/metric-card.js",
|
|
87
|
+
"default": "./src/metric-card.js"
|
|
88
|
+
},
|
|
89
|
+
"./muted-card-footer": {
|
|
90
|
+
"import": "./src/muted-card-footer.js",
|
|
91
|
+
"default": "./src/muted-card-footer.js"
|
|
92
|
+
},
|
|
93
|
+
"./status-card": {
|
|
94
|
+
"import": "./src/status-card.js",
|
|
95
|
+
"default": "./src/status-card.js"
|
|
96
|
+
},
|
|
97
|
+
"./widget-section": {
|
|
98
|
+
"import": "./src/widget-section.js",
|
|
99
|
+
"default": "./src/widget-section.js"
|
|
100
|
+
},
|
|
101
|
+
"./widget-call-to-action": {
|
|
102
|
+
"import": "./src/widget-call-to-action.js",
|
|
103
|
+
"default": "./src/widget-call-to-action.js"
|
|
104
|
+
},
|
|
66
105
|
"./lazy-load": {
|
|
67
106
|
"import": "./src/lazy-load.js",
|
|
68
107
|
"default": "./src/lazy-load.js"
|
|
69
108
|
},
|
|
109
|
+
"./next": {
|
|
110
|
+
"import": "./src/next/index.js",
|
|
111
|
+
"default": "./src/next/index.js"
|
|
112
|
+
},
|
|
70
113
|
"./header": {
|
|
71
114
|
"import": "./src/header.js",
|
|
72
115
|
"default": "./src/header.js"
|
|
@@ -75,15 +118,37 @@
|
|
|
75
118
|
"import": "./src/page-header.js",
|
|
76
119
|
"default": "./src/page-header.js"
|
|
77
120
|
},
|
|
121
|
+
"./page-backdrop": {
|
|
122
|
+
"import": "./src/page-backdrop.js",
|
|
123
|
+
"default": "./src/page-backdrop.js"
|
|
124
|
+
},
|
|
125
|
+
"./animated-page-background": {
|
|
126
|
+
"import": "./src/animated-page-background/index.js",
|
|
127
|
+
"default": "./src/animated-page-background/index.js"
|
|
128
|
+
},
|
|
129
|
+
"./color-bends": {
|
|
130
|
+
"import": "./src/animated-page-background/ColorBends.js",
|
|
131
|
+
"default": "./src/animated-page-background/ColorBends.js"
|
|
132
|
+
},
|
|
133
|
+
"./action-card-layout": {
|
|
134
|
+
"import": "./src/action-card-layout.js",
|
|
135
|
+
"default": "./src/action-card-layout.js"
|
|
136
|
+
},
|
|
78
137
|
"./gatsby": {
|
|
79
138
|
"import": "./src/gatsby/index.js",
|
|
80
139
|
"default": "./src/gatsby/index.js"
|
|
81
140
|
}
|
|
82
141
|
},
|
|
83
142
|
"peerDependencies": {
|
|
143
|
+
"next": "^14.0.0 || ^15.0.0",
|
|
84
144
|
"react": "^18.0.0 || ^19.0.0",
|
|
85
145
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
86
146
|
},
|
|
147
|
+
"peerDependenciesMeta": {
|
|
148
|
+
"next": {
|
|
149
|
+
"optional": true
|
|
150
|
+
}
|
|
151
|
+
},
|
|
87
152
|
"dependencies": {
|
|
88
153
|
"@emotion/cache": "^11.14.0",
|
|
89
154
|
"@emotion/react": "^11.14.0",
|
|
@@ -91,17 +156,19 @@
|
|
|
91
156
|
"@theme-ui/components": "^0.17.4",
|
|
92
157
|
"@theme-ui/presets": "^0.17.4",
|
|
93
158
|
"react-intersection-observer": "^10.0.3",
|
|
94
|
-
"theme-ui": "^0.17.4"
|
|
159
|
+
"theme-ui": "^0.17.4",
|
|
160
|
+
"three": "^0.183.2"
|
|
95
161
|
},
|
|
96
162
|
"devDependencies": {
|
|
97
|
-
"@babel/core": "^7.
|
|
98
|
-
"@babel/preset-env": "^7.
|
|
163
|
+
"@babel/core": "^7.29.0",
|
|
164
|
+
"@babel/preset-env": "^7.29.2",
|
|
99
165
|
"@babel/preset-react": "^7.28.5",
|
|
100
166
|
"@testing-library/jest-dom": "^6.9.1",
|
|
101
167
|
"@testing-library/react": "^16.3.2",
|
|
102
|
-
"babel-jest": "^30.0
|
|
168
|
+
"babel-jest": "^30.3.0",
|
|
103
169
|
"jest": "^30.3.0",
|
|
104
170
|
"jest-environment-jsdom": "^30.3.0",
|
|
171
|
+
"next": "^15.1.0",
|
|
105
172
|
"react": "^19.2.5",
|
|
106
173
|
"react-dom": "^19.2.5"
|
|
107
174
|
},
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
exports[`Header renders with children 1`] = `
|
|
4
4
|
<DocumentFragment>
|
|
5
5
|
<header
|
|
6
|
-
class="css-
|
|
6
|
+
class="css-1yz7e9k"
|
|
7
7
|
role="banner"
|
|
8
8
|
>
|
|
9
9
|
<div
|
|
10
|
-
class="css-
|
|
10
|
+
class="css-1yz7e9k"
|
|
11
11
|
>
|
|
12
12
|
<h1>
|
|
13
13
|
Test Header
|
|
@@ -20,11 +20,11 @@ exports[`Header renders with children 1`] = `
|
|
|
20
20
|
exports[`Header renders with custom styles 1`] = `
|
|
21
21
|
<DocumentFragment>
|
|
22
22
|
<header
|
|
23
|
-
class="css-
|
|
23
|
+
class="css-1yz7e9k"
|
|
24
24
|
role="banner"
|
|
25
25
|
>
|
|
26
26
|
<div
|
|
27
|
-
class="css-
|
|
27
|
+
class="css-1g5wp3y"
|
|
28
28
|
>
|
|
29
29
|
<h1>
|
|
30
30
|
Test Header with Styles
|
|
@@ -37,11 +37,11 @@ exports[`Header renders with custom styles 1`] = `
|
|
|
37
37
|
exports[`Header renders with empty styles object 1`] = `
|
|
38
38
|
<DocumentFragment>
|
|
39
39
|
<header
|
|
40
|
-
class="css-
|
|
40
|
+
class="css-1yz7e9k"
|
|
41
41
|
role="banner"
|
|
42
42
|
>
|
|
43
43
|
<div
|
|
44
|
-
class="css-
|
|
44
|
+
class="css-1yz7e9k"
|
|
45
45
|
>
|
|
46
46
|
<h1>
|
|
47
47
|
Test Header with Empty Styles
|
|
@@ -54,11 +54,11 @@ exports[`Header renders with empty styles object 1`] = `
|
|
|
54
54
|
exports[`Header renders without styles prop 1`] = `
|
|
55
55
|
<DocumentFragment>
|
|
56
56
|
<header
|
|
57
|
-
class="css-
|
|
57
|
+
class="css-1yz7e9k"
|
|
58
58
|
role="banner"
|
|
59
59
|
>
|
|
60
60
|
<div
|
|
61
|
-
class="css-
|
|
61
|
+
class="css-1yz7e9k"
|
|
62
62
|
>
|
|
63
63
|
<h1>
|
|
64
64
|
Test Header without Styles
|
|
@@ -130,12 +130,11 @@ exports[`Theme Configuration a snapshot of the configuration matches the snapsho
|
|
|
130
130
|
},
|
|
131
131
|
"WebkitBackdropFilter": "blur(10px)",
|
|
132
132
|
"backdropFilter": "blur(10px)",
|
|
133
|
-
"
|
|
134
|
-
"backgroundColor": "var(--theme-ui-colors-panel-background)",
|
|
133
|
+
"bg": "panel-background",
|
|
135
134
|
"border": "1px solid rgba(255, 255, 255, 0.15)",
|
|
136
135
|
"borderRadius": "10px",
|
|
137
136
|
"boxShadow": "0 4px 6px rgba(0, 0, 0, 0.1)",
|
|
138
|
-
"color": "
|
|
137
|
+
"color": "text",
|
|
139
138
|
"display": "flex",
|
|
140
139
|
"flexDirection": "column",
|
|
141
140
|
"flexGrow": 1,
|
|
@@ -160,8 +159,8 @@ exports[`Theme Configuration a snapshot of the configuration matches the snapsho
|
|
|
160
159
|
"UserProfileDark": {
|
|
161
160
|
"WebkitBackdropFilter": "blur(12px) saturate(150%)",
|
|
162
161
|
"backdropFilter": "blur(12px) saturate(150%)",
|
|
163
|
-
"background": "var(--theme-ui-colors-panel-background)",
|
|
164
162
|
"backgroundColor": "none",
|
|
163
|
+
"bg": "panel-background",
|
|
165
164
|
"borderBottom": "none",
|
|
166
165
|
"borderRadius": "card",
|
|
167
166
|
"boxShadow": "none",
|
|
@@ -184,12 +183,12 @@ exports[`Theme Configuration a snapshot of the configuration matches the snapsho
|
|
|
184
183
|
":hover": "pointer",
|
|
185
184
|
},
|
|
186
185
|
"backdropFilter": "blur(10px)",
|
|
187
|
-
"
|
|
186
|
+
"bg": "panel-background",
|
|
188
187
|
"border": "1px solid rgba(255, 255, 255, 0.15)",
|
|
189
188
|
"borderLeft": [Function],
|
|
190
189
|
"borderRadius": "10px",
|
|
191
190
|
"boxShadow": "0 4px 6px rgba(0, 0, 0, 0.1)",
|
|
192
|
-
"color": "
|
|
191
|
+
"color": "text",
|
|
193
192
|
"flexGrow": 1,
|
|
194
193
|
"fontSize": [
|
|
195
194
|
1,
|
|
@@ -285,12 +284,12 @@ exports[`Theme Configuration a snapshot of the configuration matches the snapsho
|
|
|
285
284
|
},
|
|
286
285
|
"WebkitBackdropFilter": "blur(10px)",
|
|
287
286
|
"backdropFilter": "blur(10px)",
|
|
288
|
-
"
|
|
287
|
+
"bg": "panel-background",
|
|
289
288
|
"border": "1px solid rgba(255, 255, 255, 0.15)",
|
|
290
289
|
"borderLeft": [Function],
|
|
291
290
|
"borderRadius": "10px",
|
|
292
291
|
"boxShadow": "0 4px 6px rgba(0, 0, 0, 0.1)",
|
|
293
|
-
"color": "
|
|
292
|
+
"color": "text",
|
|
294
293
|
"flexGrow": 1,
|
|
295
294
|
"fontSize": [
|
|
296
295
|
1,
|
|
@@ -302,33 +301,48 @@ exports[`Theme Configuration a snapshot of the configuration matches the snapsho
|
|
|
302
301
|
"metricCard": {
|
|
303
302
|
"WebkitBackdropFilter": "blur(12px) saturate(150%)",
|
|
304
303
|
"backdropFilter": "blur(12px) saturate(150%)",
|
|
305
|
-
"
|
|
306
|
-
"
|
|
304
|
+
"backgroundColor": "panel-background",
|
|
305
|
+
"bg": "panel-background",
|
|
306
|
+
"border": "1px solid",
|
|
307
|
+
"borderColor": "panel-divider",
|
|
307
308
|
"borderRadius": "card",
|
|
308
309
|
"boxShadow": "none",
|
|
309
|
-
"color": "
|
|
310
|
+
"color": "text",
|
|
311
|
+
"flexGrow": 1,
|
|
312
|
+
"fontSize": [
|
|
313
|
+
1,
|
|
314
|
+
2,
|
|
315
|
+
],
|
|
316
|
+
"padding": 3,
|
|
317
|
+
"textDecoration": "none",
|
|
318
|
+
},
|
|
319
|
+
"metricCardDark": {
|
|
320
|
+
"WebkitBackdropFilter": "blur(12px) saturate(150%)",
|
|
321
|
+
"backdropFilter": "blur(12px) saturate(150%)",
|
|
322
|
+
"backgroundColor": "#1e2530",
|
|
323
|
+
"bg": "panel-background",
|
|
324
|
+
"border": "1px solid",
|
|
325
|
+
"borderColor": "panel-divider",
|
|
326
|
+
"borderRadius": "card",
|
|
327
|
+
"boxShadow": "none",
|
|
328
|
+
"color": "text",
|
|
310
329
|
"flexGrow": 1,
|
|
311
330
|
"fontSize": [
|
|
312
331
|
1,
|
|
313
332
|
2,
|
|
314
333
|
],
|
|
315
334
|
"padding": 3,
|
|
316
|
-
"span": {
|
|
317
|
-
"fontFamily": "heading",
|
|
318
|
-
"fontWeight": "bold",
|
|
319
|
-
"padding": 2,
|
|
320
|
-
},
|
|
321
335
|
"textDecoration": "none",
|
|
322
336
|
},
|
|
323
337
|
"presentationalCard": {
|
|
324
338
|
"WebkitBackdropFilter": "blur(10px)",
|
|
325
339
|
"backdropFilter": "blur(10px)",
|
|
326
|
-
"
|
|
340
|
+
"bg": "panel-background",
|
|
327
341
|
"border": "1px solid rgba(255, 255, 255, 0.15)",
|
|
328
342
|
"borderLeft": [Function],
|
|
329
343
|
"borderRadius": "10px",
|
|
330
344
|
"boxShadow": "0 4px 6px rgba(0, 0, 0, 0.1)",
|
|
331
|
-
"color": "
|
|
345
|
+
"color": "text",
|
|
332
346
|
"flexGrow": 1,
|
|
333
347
|
"fontSize": [
|
|
334
348
|
1,
|
|
@@ -340,10 +354,10 @@ exports[`Theme Configuration a snapshot of the configuration matches the snapsho
|
|
|
340
354
|
"primary": {
|
|
341
355
|
"WebkitBackdropFilter": "blur(12px) saturate(150%)",
|
|
342
356
|
"backdropFilter": "blur(12px) saturate(150%)",
|
|
343
|
-
"
|
|
357
|
+
"bg": "panel-background",
|
|
344
358
|
"borderRadius": "card",
|
|
345
359
|
"boxShadow": "default",
|
|
346
|
-
"color": "
|
|
360
|
+
"color": "text",
|
|
347
361
|
"flexGrow": 1,
|
|
348
362
|
"fontSize": [
|
|
349
363
|
1,
|
|
@@ -806,6 +820,11 @@ exports[`Theme Configuration a snapshot of the configuration matches the snapsho
|
|
|
806
820
|
"right",
|
|
807
821
|
],
|
|
808
822
|
},
|
|
823
|
+
"mutedCardFooter": {
|
|
824
|
+
"display": "flex",
|
|
825
|
+
"justifyContent": "space-between",
|
|
826
|
+
"mt": 2,
|
|
827
|
+
},
|
|
809
828
|
"outlined": {
|
|
810
829
|
"border": "4px solid #efefef",
|
|
811
830
|
},
|
package/src/action-button.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
/** @jsx jsx */
|
|
2
1
|
import React from 'react'
|
|
3
|
-
import {
|
|
2
|
+
import { Box } from '@theme-ui/components'
|
|
3
|
+
import { useThemeUI } from 'theme-ui'
|
|
4
4
|
import isDarkMode from './helpers/isDarkMode.js'
|
|
5
5
|
import { hexToRgb } from './color-utils.js'
|
|
6
6
|
|
|
@@ -67,16 +67,16 @@ const ActionButton = ({ children, href, onClick, variant = 'primary', size = 'me
|
|
|
67
67
|
|
|
68
68
|
if (href) {
|
|
69
69
|
return (
|
|
70
|
-
<a href={href} sx={baseStyles} {...props}>
|
|
70
|
+
<Box as='a' href={href} sx={baseStyles} {...props}>
|
|
71
71
|
{content}
|
|
72
|
-
</
|
|
72
|
+
</Box>
|
|
73
73
|
)
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
return (
|
|
77
|
-
<button type='button' onClick={onClick} sx={baseStyles} {...props}>
|
|
77
|
+
<Box as='button' type='button' onClick={onClick} sx={baseStyles} {...props}>
|
|
78
78
|
{content}
|
|
79
|
-
</
|
|
79
|
+
</Box>
|
|
80
80
|
)
|
|
81
81
|
}
|
|
82
82
|
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/** @jsx jsx */
|
|
2
|
-
import { jsx } from 'theme-ui'
|
|
3
1
|
import { render, screen, fireEvent } from '@testing-library/react'
|
|
4
2
|
import { ThemeUIProvider } from 'theme-ui'
|
|
5
3
|
|
|
@@ -84,6 +82,20 @@ describe('ActionButton', () => {
|
|
|
84
82
|
})
|
|
85
83
|
})
|
|
86
84
|
|
|
85
|
+
it('uses dark secondary palette when color mode is dark', () => {
|
|
86
|
+
mockUseThemeUI.mockReturnValueOnce({
|
|
87
|
+
colorMode: 'dark',
|
|
88
|
+
theme: {
|
|
89
|
+
colors: {
|
|
90
|
+
primary: BUTTON_PRIMARY_COLORS.light,
|
|
91
|
+
primaryRgb: '66, 46, 163'
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
renderWithProviders(<ActionButton variant='secondary'>Secondary Dark</ActionButton>)
|
|
96
|
+
expect(screen.getByRole('button', { name: /secondary dark/i })).toHaveStyle({ color: '#888' })
|
|
97
|
+
})
|
|
98
|
+
|
|
87
99
|
it('applies small size styles', () => {
|
|
88
100
|
renderWithProviders(<ActionButton size='small'>Small Button</ActionButton>)
|
|
89
101
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout `sx` aligned with GitHub pinned cards — pair with `Card variant="actionCard"`.
|
|
3
|
+
* @see theme/src/components/widgets/github/pinned-item-card.js
|
|
4
|
+
*/
|
|
5
|
+
export const actionCardPinnedLayoutSx = {
|
|
6
|
+
height: '100%',
|
|
7
|
+
display: 'flex',
|
|
8
|
+
flexDirection: 'column',
|
|
9
|
+
transition: 'transform 0.2s ease-in-out',
|
|
10
|
+
'&:hover': {
|
|
11
|
+
transform: 'translateY(-4px)'
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { actionCardPinnedLayoutSx } from './action-card-layout.js'
|
|
2
|
+
|
|
3
|
+
describe('actionCardPinnedLayoutSx', () => {
|
|
4
|
+
it('exports a Theme UI sx object for action-card hover layout', () => {
|
|
5
|
+
expect(actionCardPinnedLayoutSx).toMatchObject({
|
|
6
|
+
height: '100%',
|
|
7
|
+
display: 'flex',
|
|
8
|
+
flexDirection: 'column',
|
|
9
|
+
transition: 'transform 0.2s ease-in-out'
|
|
10
|
+
})
|
|
11
|
+
expect(actionCardPinnedLayoutSx['&:hover']).toEqual({ transform: 'translateY(-4px)' })
|
|
12
|
+
})
|
|
13
|
+
})
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React, { useMemo, useEffect, useState } from 'react'
|
|
4
|
+
import { Box } from '@theme-ui/components'
|
|
5
|
+
import { useColorMode, useThemeUI } from 'theme-ui'
|
|
6
|
+
|
|
7
|
+
import ColorBends from './ColorBends.js'
|
|
8
|
+
import {
|
|
9
|
+
chronogroveThemeSurfaceColorsDark,
|
|
10
|
+
chronogroveThemeSurfaceColorsLight
|
|
11
|
+
} from '../chronogrove-theme-surface-colors.js'
|
|
12
|
+
import { hexToRgba } from '../color-utils.js'
|
|
13
|
+
|
|
14
|
+
// Based on Starry Banner SVG colors: purple #800080 and gold #FFD700
|
|
15
|
+
const COLOR_BENDS_COLORS = ['#800080', '#6B2F6B', '#FFD700', '#A855A8']
|
|
16
|
+
const COLOR_BENDS_STYLE = { width: '100%', height: '100%' }
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Fixed viewport background: solid in light mode, Color Bends (three.js) in dark mode,
|
|
20
|
+
* plus scroll-linked gradient overlay and parallax — same behavior as the Gatsby home.
|
|
21
|
+
*/
|
|
22
|
+
export default function ChronogroveAnimatedPageBackground({
|
|
23
|
+
overlayHeight = 'min(112.5vh, 1500px)',
|
|
24
|
+
darkOpacity = 0.12,
|
|
25
|
+
fadeDistance = 700,
|
|
26
|
+
maxParallaxOffset = 150
|
|
27
|
+
}) {
|
|
28
|
+
const [colorMode] = useColorMode()
|
|
29
|
+
const { theme } = useThemeUI()
|
|
30
|
+
const isDark = colorMode === 'dark'
|
|
31
|
+
const [overlayOpacity, setOverlayOpacity] = useState(1)
|
|
32
|
+
const [parallaxOffset, setParallaxOffset] = useState(0)
|
|
33
|
+
const [maxScrollDistance, setMaxScrollDistance] = useState(1)
|
|
34
|
+
const [mounted, setMounted] = useState(false)
|
|
35
|
+
|
|
36
|
+
const bgColorRaw =
|
|
37
|
+
theme?.rawColors?.background ||
|
|
38
|
+
theme?.colors?.background ||
|
|
39
|
+
(isDark ? chronogroveThemeSurfaceColorsDark.background : chronogroveThemeSurfaceColorsLight.background)
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
setMounted(true)
|
|
43
|
+
}, [])
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
const updateMaxScroll = () => {
|
|
47
|
+
const maxScroll = Math.max(1, document.documentElement.scrollHeight - window.innerHeight)
|
|
48
|
+
setMaxScrollDistance(maxScroll)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
updateMaxScroll()
|
|
52
|
+
|
|
53
|
+
window.addEventListener('resize', updateMaxScroll, { passive: true })
|
|
54
|
+
return () => window.removeEventListener('resize', updateMaxScroll)
|
|
55
|
+
}, [])
|
|
56
|
+
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
const handleScroll = () => {
|
|
59
|
+
const scrollY = window.scrollY
|
|
60
|
+
const opacity = Math.max(0, 1 - scrollY / fadeDistance)
|
|
61
|
+
setOverlayOpacity(opacity)
|
|
62
|
+
|
|
63
|
+
const scrollProgress = Math.min(scrollY / maxScrollDistance, 1)
|
|
64
|
+
const offset = scrollProgress * maxParallaxOffset
|
|
65
|
+
setParallaxOffset(offset)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
handleScroll()
|
|
69
|
+
|
|
70
|
+
window.addEventListener('scroll', handleScroll, { passive: true })
|
|
71
|
+
return () => window.removeEventListener('scroll', handleScroll)
|
|
72
|
+
}, [fadeDistance, maxParallaxOffset, maxScrollDistance])
|
|
73
|
+
|
|
74
|
+
const backgroundAnimation = useMemo(
|
|
75
|
+
() =>
|
|
76
|
+
isDark ? (
|
|
77
|
+
<ColorBends
|
|
78
|
+
colors={COLOR_BENDS_COLORS}
|
|
79
|
+
rotation={30}
|
|
80
|
+
speed={0.1}
|
|
81
|
+
scale={1}
|
|
82
|
+
frequency={1}
|
|
83
|
+
warpStrength={1}
|
|
84
|
+
mouseInfluence={1}
|
|
85
|
+
parallax={1}
|
|
86
|
+
noise={0.1}
|
|
87
|
+
transparent
|
|
88
|
+
style={COLOR_BENDS_STYLE}
|
|
89
|
+
/>
|
|
90
|
+
) : null,
|
|
91
|
+
[isDark]
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
if (!mounted) {
|
|
95
|
+
return null
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const toRgba = (color, alpha) => {
|
|
99
|
+
if (typeof color === 'string' && color.startsWith('var(')) {
|
|
100
|
+
const fallbackHex = isDark
|
|
101
|
+
? chronogroveThemeSurfaceColorsDark.background
|
|
102
|
+
: chronogroveThemeSurfaceColorsLight.background
|
|
103
|
+
return hexToRgba(fallbackHex, alpha)
|
|
104
|
+
}
|
|
105
|
+
return hexToRgba(color, alpha)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const gradientOverlay = `linear-gradient(to bottom, ${bgColorRaw} 0%, ${bgColorRaw} 30%, ${toRgba(bgColorRaw, 0.6)} 65%, ${toRgba(bgColorRaw, 0.2)} 85%, transparent 100%)`
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<>
|
|
112
|
+
<Box
|
|
113
|
+
key={`bg-${colorMode}`}
|
|
114
|
+
aria-hidden='true'
|
|
115
|
+
sx={{
|
|
116
|
+
position: 'fixed',
|
|
117
|
+
top: 0,
|
|
118
|
+
left: 0,
|
|
119
|
+
right: 0,
|
|
120
|
+
width: '100vw',
|
|
121
|
+
height: `calc(100vh + ${maxParallaxOffset}px)`,
|
|
122
|
+
zIndex: 0,
|
|
123
|
+
overflow: 'hidden',
|
|
124
|
+
opacity: isDark ? darkOpacity : 1,
|
|
125
|
+
pointerEvents: 'none',
|
|
126
|
+
backgroundColor: bgColorRaw,
|
|
127
|
+
transform: `translateY(-${parallaxOffset}px)`,
|
|
128
|
+
willChange: 'transform'
|
|
129
|
+
}}
|
|
130
|
+
>
|
|
131
|
+
{backgroundAnimation}
|
|
132
|
+
</Box>
|
|
133
|
+
|
|
134
|
+
<Box
|
|
135
|
+
key={`overlay-${colorMode}`}
|
|
136
|
+
aria-hidden='true'
|
|
137
|
+
sx={{
|
|
138
|
+
position: 'absolute',
|
|
139
|
+
top: 0,
|
|
140
|
+
left: 0,
|
|
141
|
+
right: 0,
|
|
142
|
+
width: '100%',
|
|
143
|
+
height: overlayHeight,
|
|
144
|
+
zIndex: 0,
|
|
145
|
+
pointerEvents: 'none',
|
|
146
|
+
opacity: overlayOpacity,
|
|
147
|
+
transition: 'opacity 0.1s ease-out',
|
|
148
|
+
background: gradientOverlay
|
|
149
|
+
}}
|
|
150
|
+
/>
|
|
151
|
+
</>
|
|
152
|
+
)
|
|
153
|
+
}
|