@shiftbloom-studio/circadian-ui 0.2.0 → 0.2.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/README.md +97 -37
- package/dist/index.cjs +86 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +84 -40
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +15 -8
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +2 -1
- package/dist/server.d.ts +2 -1
- package/dist/server.js +15 -8
- package/dist/server.js.map +1 -1
- package/package.json +14 -14
package/README.md
CHANGED
|
@@ -4,13 +4,17 @@
|
|
|
4
4
|
[](https://github.com/shiftbloom-studio/circadian-ui/actions/workflows/ci.yml)
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**Circadian UI** is a production‑ready, time‑aware theming engine for React and Tailwind. It adapts your design tokens across dawn/day/dusk/night based on local time, optional sunrise/sunset data, system preferences, and user overrides — while enforcing accessible contrast.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## What makes it special
|
|
10
10
|
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
11
|
+
- **Zero‑config start** — install, wrap your app, and you’re done.
|
|
12
|
+
- **Circadian magic** — automatic phase shifts based on time or sun data.
|
|
13
|
+
- **Accessible by default** — WCAG‑conscious contrast adjustments.
|
|
14
|
+
- **Framework‑friendly** — Next.js (App/Pages), Vite, SSR or CSR.
|
|
15
|
+
- **Tailwind‑native** — tokens exposed as CSS variables + preset/plugin.
|
|
16
|
+
|
|
17
|
+
---
|
|
14
18
|
|
|
15
19
|
## Install
|
|
16
20
|
|
|
@@ -18,7 +22,9 @@ Automatic, accessible, Tailwind-friendly time-of-day theming for React and Next.
|
|
|
18
22
|
npm install @shiftbloom-studio/circadian-ui
|
|
19
23
|
```
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 30‑second quickstart (React)
|
|
22
28
|
|
|
23
29
|
```tsx
|
|
24
30
|
import { CircadianProvider, CircadianScript } from "@shiftbloom-studio/circadian-ui";
|
|
@@ -33,7 +39,13 @@ export function App({ children }: { children: React.ReactNode }) {
|
|
|
33
39
|
}
|
|
34
40
|
```
|
|
35
41
|
|
|
36
|
-
|
|
42
|
+
> `CircadianScript` prevents theme flash by setting the initial phase before hydration.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Next.js
|
|
47
|
+
|
|
48
|
+
### App Router
|
|
37
49
|
|
|
38
50
|
```tsx
|
|
39
51
|
// app/layout.tsx
|
|
@@ -52,7 +64,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|
|
52
64
|
}
|
|
53
65
|
```
|
|
54
66
|
|
|
55
|
-
|
|
67
|
+
### Pages Router
|
|
56
68
|
|
|
57
69
|
```tsx
|
|
58
70
|
// pages/_document.tsx
|
|
@@ -76,9 +88,30 @@ export default class MyDocument extends Document {
|
|
|
76
88
|
}
|
|
77
89
|
```
|
|
78
90
|
|
|
79
|
-
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Vite / CSR apps
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
import { createRoot } from "react-dom/client";
|
|
97
|
+
import { CircadianProvider, CircadianScript } from "@shiftbloom-studio/circadian-ui";
|
|
98
|
+
import App from "./App";
|
|
99
|
+
|
|
100
|
+
createRoot(document.getElementById("root")!).render(
|
|
101
|
+
<>
|
|
102
|
+
<CircadianScript />
|
|
103
|
+
<CircadianProvider>
|
|
104
|
+
<App />
|
|
105
|
+
</CircadianProvider>
|
|
106
|
+
</>
|
|
107
|
+
);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
80
111
|
|
|
81
|
-
|
|
112
|
+
## Tailwind integration
|
|
113
|
+
|
|
114
|
+
### Option A — CSS variables (recommended)
|
|
82
115
|
|
|
83
116
|
```ts
|
|
84
117
|
// tailwind.config.ts
|
|
@@ -109,7 +142,7 @@ const config: Config = {
|
|
|
109
142
|
export default config;
|
|
110
143
|
```
|
|
111
144
|
|
|
112
|
-
###
|
|
145
|
+
### Option B — Preset + Plugin
|
|
113
146
|
|
|
114
147
|
```ts
|
|
115
148
|
// tailwind.config.ts
|
|
@@ -125,9 +158,11 @@ const config: Config = {
|
|
|
125
158
|
export default config;
|
|
126
159
|
```
|
|
127
160
|
|
|
161
|
+
---
|
|
162
|
+
|
|
128
163
|
## Configuration examples
|
|
129
164
|
|
|
130
|
-
### Custom
|
|
165
|
+
### 1) Custom schedule windows
|
|
131
166
|
|
|
132
167
|
```tsx
|
|
133
168
|
<CircadianProvider
|
|
@@ -144,46 +179,60 @@ export default config;
|
|
|
144
179
|
</CircadianProvider>
|
|
145
180
|
```
|
|
146
181
|
|
|
147
|
-
###
|
|
182
|
+
### 2) Sun‑aware schedule
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
import type { SunTimesProvider } from "@shiftbloom-studio/circadian-ui";
|
|
186
|
+
|
|
187
|
+
const provider: SunTimesProvider = (date) => {
|
|
188
|
+
return {
|
|
189
|
+
sunrise: new Date(date.getFullYear(), date.getMonth(), date.getDate(), 6, 12),
|
|
190
|
+
sunset: new Date(date.getFullYear(), date.getMonth(), date.getDate(), 19, 48)
|
|
191
|
+
};
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
<CircadianProvider config={{ mode: "sun", sunTimesProvider: provider }} />;
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### 3) Auto mode (recommended)
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
<CircadianProvider config={{ mode: "auto", sunTimesProvider: provider }} />
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
If sun data is available, it uses `sun`; otherwise it falls back to `time`.
|
|
204
|
+
|
|
205
|
+
### 4) Manual override UI
|
|
148
206
|
|
|
149
207
|
```tsx
|
|
150
208
|
import { useCircadian } from "@shiftbloom-studio/circadian-ui";
|
|
151
209
|
|
|
152
210
|
const ModeToggle = () => {
|
|
153
|
-
const { mode, setMode, setPhaseOverride } = useCircadian();
|
|
211
|
+
const { mode, resolvedMode, setMode, setPhaseOverride } = useCircadian();
|
|
154
212
|
return (
|
|
155
213
|
<div>
|
|
156
|
-
<button onClick={() => setMode("
|
|
214
|
+
<button onClick={() => setMode("auto")}>Auto</button>
|
|
157
215
|
<button onClick={() => setPhaseOverride("night")}>Night</button>
|
|
158
|
-
<
|
|
216
|
+
<p>Requested: {mode}</p>
|
|
217
|
+
<p>Resolved: {resolvedMode}</p>
|
|
159
218
|
</div>
|
|
160
219
|
);
|
|
161
220
|
};
|
|
162
221
|
```
|
|
163
222
|
|
|
164
|
-
###
|
|
165
|
-
|
|
166
|
-
```ts
|
|
167
|
-
import type { SunTimesProvider } from "@shiftbloom-studio/circadian-ui";
|
|
168
|
-
|
|
169
|
-
const provider: SunTimesProvider = (date) => {
|
|
170
|
-
// Plug in your own sunrise/sunset provider
|
|
171
|
-
return {
|
|
172
|
-
sunrise: new Date(date.getFullYear(), date.getMonth(), date.getDate(), 6, 12),
|
|
173
|
-
sunset: new Date(date.getFullYear(), date.getMonth(), date.getDate(), 19, 48)
|
|
174
|
-
};
|
|
175
|
-
};
|
|
223
|
+
### 5) Initial phase hint (SSR)
|
|
176
224
|
|
|
177
|
-
|
|
225
|
+
```tsx
|
|
226
|
+
<CircadianScript config={{ initialPhase: "night" }} />
|
|
178
227
|
```
|
|
179
228
|
|
|
180
|
-
### Disable persistence
|
|
229
|
+
### 6) Disable persistence
|
|
181
230
|
|
|
182
231
|
```tsx
|
|
183
232
|
<CircadianProvider config={{ persist: false }} />
|
|
184
233
|
```
|
|
185
234
|
|
|
186
|
-
### Strict contrast
|
|
235
|
+
### 7) Strict contrast tuning
|
|
187
236
|
|
|
188
237
|
```tsx
|
|
189
238
|
<CircadianProvider
|
|
@@ -196,9 +245,7 @@ const provider: SunTimesProvider = (date) => {
|
|
|
196
245
|
/>
|
|
197
246
|
```
|
|
198
247
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
Circadian UI enforces WCAG-conscious contrast by default. Foreground tokens are nudged in lightness until they meet the configured ratio. You can tune ratios for normal and large text via `accessibility.minimumRatio` and `accessibility.largeTextRatio`.
|
|
248
|
+
---
|
|
202
249
|
|
|
203
250
|
## Design tokens
|
|
204
251
|
|
|
@@ -217,15 +264,17 @@ Circadian UI enforces WCAG-conscious contrast by default. Foreground tokens are
|
|
|
217
264
|
| Destructive | `--cui-destructive` |
|
|
218
265
|
| Destructive Foreground | `--cui-destructive-fg` |
|
|
219
266
|
|
|
267
|
+
---
|
|
268
|
+
|
|
220
269
|
## API reference
|
|
221
270
|
|
|
222
271
|
### React
|
|
223
272
|
|
|
224
273
|
- `CircadianProvider`
|
|
225
274
|
- Props: `{ config?: CircadianConfig; children: React.ReactNode }`
|
|
226
|
-
-
|
|
275
|
+
- Applies phase + tokens to the document root (or body).
|
|
227
276
|
- `useCircadian()`
|
|
228
|
-
- Returns `{ phase, mode, setMode, setPhaseOverride, clearOverride, tokens, isAuto, nextChangeAt }`.
|
|
277
|
+
- Returns `{ phase, mode, resolvedMode, setMode, setPhaseOverride, clearOverride, tokens, isAuto, nextChangeAt }`.
|
|
229
278
|
- `useCircadianTokens()`
|
|
230
279
|
- Returns `{ tokens, cssVars, applyToStyle }` for inline usage.
|
|
231
280
|
- `CircadianScript`
|
|
@@ -245,15 +294,18 @@ Circadian UI enforces WCAG-conscious contrast by default. Foreground tokens are
|
|
|
245
294
|
- `circadianTailwindPreset()`
|
|
246
295
|
- `circadianPlugin()` (wrap with `tailwindcss/plugin`)
|
|
247
296
|
|
|
297
|
+
---
|
|
298
|
+
|
|
248
299
|
## Configuration schema
|
|
249
300
|
|
|
250
301
|
```ts
|
|
251
302
|
interface CircadianConfig {
|
|
252
303
|
schedule?: Partial<CircadianSchedule>;
|
|
253
304
|
tokens?: Partial<Record<Phase, Partial<CircadianTokens>>>;
|
|
254
|
-
mode?: "time" | "sun" | "manual";
|
|
305
|
+
mode?: "time" | "sun" | "manual" | "auto";
|
|
255
306
|
sunTimesProvider?: SunTimesProvider;
|
|
256
307
|
sunSchedule?: Partial<SunScheduleOptions>;
|
|
308
|
+
initialPhase?: Phase;
|
|
257
309
|
persist?: boolean;
|
|
258
310
|
storageKey?: string;
|
|
259
311
|
accessibility?: Partial<AccessibilityOptions>;
|
|
@@ -264,6 +316,14 @@ interface CircadianConfig {
|
|
|
264
316
|
}
|
|
265
317
|
```
|
|
266
318
|
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## Accessibility notes
|
|
322
|
+
|
|
323
|
+
Circadian UI nudges foreground tokens until they meet your configured contrast ratio. You can tune ratios for normal and large text via `accessibility.minimumRatio` and `accessibility.largeTextRatio`.
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
267
327
|
## Development
|
|
268
328
|
|
|
269
329
|
```bash
|
package/dist/index.cjs
CHANGED
|
@@ -56,21 +56,24 @@ var isWithinRange = (minutes, start, end) => {
|
|
|
56
56
|
}
|
|
57
57
|
return minutes >= start || minutes < end;
|
|
58
58
|
};
|
|
59
|
-
var
|
|
60
|
-
const minutes = getMinutesFromDate(date);
|
|
61
|
-
const normalized = normalizeSchedule(schedule);
|
|
59
|
+
var getPhaseFromMinutes = (minutes, schedule) => {
|
|
62
60
|
const phases = ["dawn", "day", "dusk", "night"];
|
|
63
61
|
for (const phase of phases) {
|
|
64
|
-
const window2 =
|
|
62
|
+
const window2 = schedule[phase];
|
|
65
63
|
if (isWithinRange(minutes, window2.start, window2.end)) {
|
|
66
64
|
return phase;
|
|
67
65
|
}
|
|
68
66
|
}
|
|
69
67
|
return "night";
|
|
70
68
|
};
|
|
69
|
+
var getPhaseFromTime = (date, schedule) => {
|
|
70
|
+
const minutes = getMinutesFromDate(date);
|
|
71
|
+
const normalized = normalizeSchedule(schedule);
|
|
72
|
+
return getPhaseFromMinutes(minutes, normalized);
|
|
73
|
+
};
|
|
71
74
|
var computeNextTransition = (date, schedule) => {
|
|
72
75
|
const normalized = normalizeSchedule(schedule);
|
|
73
|
-
const currentPhase =
|
|
76
|
+
const currentPhase = getPhaseFromMinutes(getMinutesFromDate(date), normalized);
|
|
74
77
|
const minutes = getMinutesFromDate(date);
|
|
75
78
|
const endMinutes = normalized[currentPhase].end;
|
|
76
79
|
let delta = endMinutes - minutes;
|
|
@@ -79,6 +82,16 @@ var computeNextTransition = (date, schedule) => {
|
|
|
79
82
|
}
|
|
80
83
|
return new Date(date.getTime() + delta * 60 * 1e3);
|
|
81
84
|
};
|
|
85
|
+
var computeNextTransitionFromMinutes = (date, schedule) => {
|
|
86
|
+
const currentPhase = getPhaseFromMinutes(getMinutesFromDate(date), schedule);
|
|
87
|
+
const minutes = getMinutesFromDate(date);
|
|
88
|
+
const endMinutes = schedule[currentPhase].end;
|
|
89
|
+
let delta = endMinutes - minutes;
|
|
90
|
+
if (delta <= 0) {
|
|
91
|
+
delta += minutesInDay;
|
|
92
|
+
}
|
|
93
|
+
return new Date(date.getTime() + delta * 60 * 1e3);
|
|
94
|
+
};
|
|
82
95
|
|
|
83
96
|
// src/core/sun.ts
|
|
84
97
|
var minutesInDay2 = 24 * 60;
|
|
@@ -124,6 +137,10 @@ var getPhaseFromSunTimes = (date, sunTimes, options) => {
|
|
|
124
137
|
}
|
|
125
138
|
return "night";
|
|
126
139
|
};
|
|
140
|
+
var computeNextSunTransition = (date, sunTimes, options) => {
|
|
141
|
+
const schedule = deriveSunSchedule(date, sunTimes, options);
|
|
142
|
+
return computeNextTransitionFromMinutes(date, schedule);
|
|
143
|
+
};
|
|
127
144
|
var getScheduleFromProvider = (date, provider, options) => {
|
|
128
145
|
if (!provider) {
|
|
129
146
|
return null;
|
|
@@ -471,7 +488,7 @@ var defaultTransition = {
|
|
|
471
488
|
durationMs: 200
|
|
472
489
|
};
|
|
473
490
|
var resolveMode = (userMode, system, config) => {
|
|
474
|
-
return userMode ?? config?.mode ?? "
|
|
491
|
+
return userMode ?? config?.mode ?? "auto";
|
|
475
492
|
};
|
|
476
493
|
var resolveAccessibility = (system, config) => {
|
|
477
494
|
const base = { ...defaultAccessibility, ...config?.accessibility };
|
|
@@ -496,11 +513,13 @@ var getMergedSchedule = (schedule) => ({
|
|
|
496
513
|
dusk: { ...defaultSchedule.dusk, ...schedule?.dusk },
|
|
497
514
|
night: { ...defaultSchedule.night, ...schedule?.night }
|
|
498
515
|
});
|
|
499
|
-
var getMode = (mode) => mode ?? "
|
|
516
|
+
var getMode = (mode) => mode ?? "auto";
|
|
500
517
|
var createInlineScript = (config) => {
|
|
501
518
|
const schedule = getMergedSchedule(config?.schedule);
|
|
502
519
|
const storageKey = config?.storageKey ?? defaultStorageKey;
|
|
503
520
|
const persist = config?.persist !== false;
|
|
521
|
+
const initialPhase = config?.initialPhase ?? null;
|
|
522
|
+
const setAttributeOn = config?.setAttributeOn ?? "html";
|
|
504
523
|
const tokens = {
|
|
505
524
|
dawn: tokensToCssVars({
|
|
506
525
|
...defaultTokens.dawn,
|
|
@@ -526,6 +545,8 @@ var createInlineScript = (config) => {
|
|
|
526
545
|
const storageKey = ${serialize(storageKey)};
|
|
527
546
|
const persist = ${serialize(persist)};
|
|
528
547
|
const fallbackMode = ${serialize(getMode(config?.mode))};
|
|
548
|
+
const initialPhase = ${serialize(initialPhase)};
|
|
549
|
+
const setAttributeOn = ${serialize(setAttributeOn)};
|
|
529
550
|
const now = new Date();
|
|
530
551
|
const minutes = now.getHours() * 60 + now.getMinutes();
|
|
531
552
|
|
|
@@ -548,12 +569,14 @@ var createInlineScript = (config) => {
|
|
|
548
569
|
};
|
|
549
570
|
|
|
550
571
|
const order = ["dawn", "day", "dusk", "night"];
|
|
551
|
-
let phase = "night";
|
|
552
|
-
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
572
|
+
let phase = initialPhase || "night";
|
|
573
|
+
if (!initialPhase) {
|
|
574
|
+
for (const key of order) {
|
|
575
|
+
const window = normalized[key];
|
|
576
|
+
if (isWithin(minutes, window.start, window.end)) {
|
|
577
|
+
phase = key;
|
|
578
|
+
break;
|
|
579
|
+
}
|
|
557
580
|
}
|
|
558
581
|
}
|
|
559
582
|
|
|
@@ -569,7 +592,8 @@ var createInlineScript = (config) => {
|
|
|
569
592
|
}
|
|
570
593
|
}
|
|
571
594
|
|
|
572
|
-
const root =
|
|
595
|
+
const root =
|
|
596
|
+
setAttributeOn === "body" && document.body ? document.body : document.documentElement;
|
|
573
597
|
root.setAttribute("data-cui-phase", phase);
|
|
574
598
|
const vars = tokens[phase] || tokens.night;
|
|
575
599
|
for (const key in vars) {
|
|
@@ -618,27 +642,21 @@ var getInitialSystemPreferences = () => {
|
|
|
618
642
|
}
|
|
619
643
|
return getSystemPreferences();
|
|
620
644
|
};
|
|
621
|
-
var computePhase = (date, mode, config, phaseOverride) => {
|
|
645
|
+
var computePhase = (date, mode, config, phaseOverride, sunTimes) => {
|
|
622
646
|
if (mode === "manual" && phaseOverride) {
|
|
623
647
|
return phaseOverride;
|
|
624
648
|
}
|
|
625
|
-
if (mode === "sun") {
|
|
626
|
-
|
|
627
|
-
if (sunTimes) {
|
|
628
|
-
return getPhaseFromSunTimes(date, sunTimes, config.sunSchedule);
|
|
629
|
-
}
|
|
649
|
+
if (mode === "sun" && sunTimes) {
|
|
650
|
+
return getPhaseFromSunTimes(date, sunTimes, config.sunSchedule);
|
|
630
651
|
}
|
|
631
652
|
return getPhaseFromTime(date, config.schedule);
|
|
632
653
|
};
|
|
633
|
-
var computeNextChange = (date, mode, config) => {
|
|
654
|
+
var computeNextChange = (date, mode, config, sunTimes) => {
|
|
634
655
|
if (mode === "manual") {
|
|
635
656
|
return null;
|
|
636
657
|
}
|
|
637
|
-
if (mode === "sun") {
|
|
638
|
-
|
|
639
|
-
if (schedule) {
|
|
640
|
-
return computeNextTransition(date, schedule);
|
|
641
|
-
}
|
|
658
|
+
if (mode === "sun" && sunTimes) {
|
|
659
|
+
return computeNextSunTransition(date, sunTimes, config.sunSchedule);
|
|
642
660
|
}
|
|
643
661
|
return computeNextTransition(date, config.schedule);
|
|
644
662
|
};
|
|
@@ -648,17 +666,18 @@ var CircadianProvider = ({ config = {}, children }) => {
|
|
|
648
666
|
const [systemPrefs, setSystemPrefs] = react.useState(getInitialSystemPreferences);
|
|
649
667
|
const initialPersisted = typeof window !== "undefined" && shouldPersist ? loadPersistedState(storageKey) : null;
|
|
650
668
|
const [mode, setModeState] = react.useState(
|
|
651
|
-
initialPersisted?.mode ?? config.mode ?? "
|
|
669
|
+
initialPersisted?.mode ?? config.mode ?? "auto"
|
|
652
670
|
);
|
|
653
671
|
const [phaseOverride, setPhaseOverrideState] = react.useState(
|
|
654
672
|
initialPersisted?.phase ?? null
|
|
655
673
|
);
|
|
656
674
|
const [phase, setPhase] = react.useState(
|
|
657
|
-
() => computePhase(/* @__PURE__ */ new Date(), mode, config, phaseOverride)
|
|
675
|
+
() => config.initialPhase ?? computePhase(/* @__PURE__ */ new Date(), mode, config, phaseOverride, null)
|
|
658
676
|
);
|
|
659
677
|
const [nextChangeAt, setNextChangeAt] = react.useState(
|
|
660
|
-
() => computeNextChange(/* @__PURE__ */ new Date(), mode, config)
|
|
678
|
+
() => computeNextChange(/* @__PURE__ */ new Date(), mode, config, null)
|
|
661
679
|
);
|
|
680
|
+
const [resolvedMode, setResolvedMode] = react.useState(mode);
|
|
662
681
|
const timerRef = react.useRef(null);
|
|
663
682
|
const accessibility = react.useMemo(
|
|
664
683
|
() => resolveAccessibility(systemPrefs, config),
|
|
@@ -684,18 +703,42 @@ var CircadianProvider = ({ config = {}, children }) => {
|
|
|
684
703
|
systemOptions.respectColorScheme,
|
|
685
704
|
colorBias
|
|
686
705
|
]);
|
|
706
|
+
const resolveModeWithSun = react.useCallback(
|
|
707
|
+
(date) => {
|
|
708
|
+
const desiredMode = resolveMode(mode, systemPrefs, config);
|
|
709
|
+
if (desiredMode === "auto") {
|
|
710
|
+
if (config.sunTimesProvider) {
|
|
711
|
+
const sunTimes = config.sunTimesProvider(date);
|
|
712
|
+
if (sunTimes) {
|
|
713
|
+
return { resolvedMode: "sun", sunTimes };
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
return { resolvedMode: "time", sunTimes: null };
|
|
717
|
+
}
|
|
718
|
+
if (desiredMode === "sun") {
|
|
719
|
+
const sunTimes = config.sunTimesProvider?.(date) ?? null;
|
|
720
|
+
if (!sunTimes) {
|
|
721
|
+
return { resolvedMode: "time", sunTimes: null };
|
|
722
|
+
}
|
|
723
|
+
return { resolvedMode: "sun", sunTimes };
|
|
724
|
+
}
|
|
725
|
+
return { resolvedMode: desiredMode, sunTimes: null };
|
|
726
|
+
},
|
|
727
|
+
[mode, systemPrefs, config]
|
|
728
|
+
);
|
|
687
729
|
const updatePhase = react.useCallback(() => {
|
|
688
730
|
const now = /* @__PURE__ */ new Date();
|
|
689
|
-
const resolvedMode =
|
|
690
|
-
const nextPhase = computePhase(now,
|
|
731
|
+
const { resolvedMode: resolvedMode2, sunTimes } = resolveModeWithSun(now);
|
|
732
|
+
const nextPhase = computePhase(now, resolvedMode2, config, phaseOverride, sunTimes);
|
|
733
|
+
setResolvedMode(resolvedMode2);
|
|
691
734
|
setPhase(nextPhase);
|
|
692
|
-
setNextChangeAt(computeNextChange(now,
|
|
693
|
-
}, [
|
|
735
|
+
setNextChangeAt(computeNextChange(now, resolvedMode2, config, sunTimes));
|
|
736
|
+
}, [config, phaseOverride, resolveModeWithSun]);
|
|
694
737
|
react.useEffect(() => {
|
|
695
738
|
updatePhase();
|
|
696
739
|
}, [updatePhase]);
|
|
697
740
|
react.useEffect(() => {
|
|
698
|
-
if (
|
|
741
|
+
if (resolvedMode === "manual") {
|
|
699
742
|
return;
|
|
700
743
|
}
|
|
701
744
|
if (timerRef.current) {
|
|
@@ -713,7 +756,7 @@ var CircadianProvider = ({ config = {}, children }) => {
|
|
|
713
756
|
window.clearTimeout(timerRef.current);
|
|
714
757
|
}
|
|
715
758
|
};
|
|
716
|
-
}, [
|
|
759
|
+
}, [resolvedMode, nextChangeAt, updatePhase]);
|
|
717
760
|
react.useEffect(() => {
|
|
718
761
|
const root = getRootElement(config.setAttributeOn);
|
|
719
762
|
if (!root || !root.style) {
|
|
@@ -768,8 +811,8 @@ var CircadianProvider = ({ config = {}, children }) => {
|
|
|
768
811
|
}, []);
|
|
769
812
|
const clearOverride = react.useCallback(() => {
|
|
770
813
|
setPhaseOverrideState(null);
|
|
771
|
-
setModeState("
|
|
772
|
-
}, []);
|
|
814
|
+
setModeState(config.mode ?? "auto");
|
|
815
|
+
}, [config.mode]);
|
|
773
816
|
const contextValue = react.useMemo(
|
|
774
817
|
() => ({
|
|
775
818
|
phase,
|
|
@@ -779,9 +822,10 @@ var CircadianProvider = ({ config = {}, children }) => {
|
|
|
779
822
|
setMode,
|
|
780
823
|
setPhaseOverride,
|
|
781
824
|
clearOverride,
|
|
782
|
-
isAuto: mode !== "manual"
|
|
825
|
+
isAuto: mode !== "manual",
|
|
826
|
+
resolvedMode
|
|
783
827
|
}),
|
|
784
|
-
[phase, mode, tokens, nextChangeAt, setMode, setPhaseOverride, clearOverride]
|
|
828
|
+
[phase, mode, tokens, nextChangeAt, setMode, setPhaseOverride, clearOverride, resolvedMode]
|
|
785
829
|
);
|
|
786
830
|
return /* @__PURE__ */ jsxRuntime.jsx(CircadianContext.Provider, { value: contextValue, children });
|
|
787
831
|
};
|
|
@@ -854,7 +898,9 @@ exports.applyTokensToElement = applyTokensToElement;
|
|
|
854
898
|
exports.circadianPlugin = circadianPlugin;
|
|
855
899
|
exports.circadianTailwindPreset = circadianTailwindPreset;
|
|
856
900
|
exports.clearPersistedState = clearPersistedState;
|
|
901
|
+
exports.computeNextSunTransition = computeNextSunTransition;
|
|
857
902
|
exports.computeNextTransition = computeNextTransition;
|
|
903
|
+
exports.computeNextTransitionFromMinutes = computeNextTransitionFromMinutes;
|
|
858
904
|
exports.contrastRatio = contrastRatio;
|
|
859
905
|
exports.createInlineScript = createInlineScript;
|
|
860
906
|
exports.cssVarMap = cssVarMap;
|
|
@@ -869,6 +915,7 @@ exports.defaultTransition = defaultTransition;
|
|
|
869
915
|
exports.deriveSunSchedule = deriveSunSchedule;
|
|
870
916
|
exports.ensureContrast = ensureContrast;
|
|
871
917
|
exports.getMinutesFromDate = getMinutesFromDate;
|
|
918
|
+
exports.getPhaseFromMinutes = getPhaseFromMinutes;
|
|
872
919
|
exports.getPhaseFromSunTimes = getPhaseFromSunTimes;
|
|
873
920
|
exports.getPhaseFromTime = getPhaseFromTime;
|
|
874
921
|
exports.getScheduleFromProvider = getScheduleFromProvider;
|