@owlmeans/web-panel 0.1.18-rc.52 → 0.1.18-rc.53
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 +27 -3
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/web-panel/SKILL.md +64 -5
- package/build/components/footer/component.d.ts +6 -1
- package/build/components/footer/component.d.ts.map +1 -1
- package/build/components/footer/component.js +15 -4
- package/build/components/footer/component.js.map +1 -1
- package/build/components/footer/types.d.ts +10 -0
- package/build/components/footer/types.d.ts.map +1 -1
- package/build/components/index.d.ts +1 -0
- package/build/components/index.d.ts.map +1 -1
- package/build/components/index.js +1 -0
- package/build/components/index.js.map +1 -1
- package/build/components/nav/layout.d.ts.map +1 -1
- package/build/components/nav/layout.js +15 -4
- package/build/components/nav/layout.js.map +1 -1
- package/build/components/nav/types.d.ts +8 -0
- package/build/components/nav/types.d.ts.map +1 -1
- package/build/components/scheme/hook.d.ts +16 -0
- package/build/components/scheme/hook.d.ts.map +1 -0
- package/build/components/scheme/hook.js +51 -0
- package/build/components/scheme/hook.js.map +1 -0
- package/build/components/scheme/index.d.ts +4 -0
- package/build/components/scheme/index.d.ts.map +1 -0
- package/build/components/scheme/index.js +3 -0
- package/build/components/scheme/index.js.map +1 -0
- package/build/components/scheme/toggle.d.ts +14 -0
- package/build/components/scheme/toggle.d.ts.map +1 -0
- package/build/components/scheme/toggle.js +34 -0
- package/build/components/scheme/toggle.js.map +1 -0
- package/build/components/scheme/types.d.ts +21 -0
- package/build/components/scheme/types.d.ts.map +1 -0
- package/build/components/scheme/types.js +2 -0
- package/build/components/scheme/types.js.map +1 -0
- package/build/scheme/index.d.ts +3 -0
- package/build/scheme/index.d.ts.map +1 -0
- package/build/scheme/index.js +2 -0
- package/build/scheme/index.js.map +1 -0
- package/build/scheme/scheme.d.ts +44 -0
- package/build/scheme/scheme.d.ts.map +1 -0
- package/build/scheme/scheme.js +78 -0
- package/build/scheme/scheme.js.map +1 -0
- package/package.json +9 -2
- package/src/components/footer/component.tsx +24 -4
- package/src/components/footer/types.ts +9 -0
- package/src/components/index.ts +1 -0
- package/src/components/nav/layout.tsx +16 -4
- package/src/components/nav/types.ts +8 -0
- package/src/components/scheme/hook.ts +64 -0
- package/src/components/scheme/index.ts +4 -0
- package/src/components/scheme/toggle.tsx +43 -0
- package/src/components/scheme/types.ts +23 -0
- package/src/scheme/index.ts +4 -0
- package/src/scheme/scheme.ts +86 -0
- package/tests/harness/mount.tsx +3 -0
- package/tests/scheme.spec.ts +149 -0
- package/zz-tw.d.mts +2 -0
- package/zz-tw.d.mts.map +1 -0
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ but never started.
|
|
|
11
11
|
## Installation
|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
|
-
bun add @owlmeans/web-panel@^0.1.18-rc.
|
|
14
|
+
bun add @owlmeans/web-panel@^0.1.18-rc.53
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
Peer requirements (the consuming app provides these): `react`, `react-dom`,
|
|
@@ -224,11 +224,12 @@ alias. Default keys are `nav.<section>` and `modules.<alias>`.
|
|
|
224
224
|
|
|
225
225
|
| Component | Props |
|
|
226
226
|
|---|---|
|
|
227
|
-
| `NavLayout` | `nav`, `translate?`, `title?`, `home?` (brand target; defaults to the first section's first item), `actions?`, `mobileMenu?` (below `md`, a menu button and a sheet replace the section menu and the screen strip; `actions` stay), `skipLinkLabel?` (the first-on-page skip link to `<main id="main">`; default `shell.skip` → "Skip to content"; `false` renders no link and no `#main`), `footer?` (`PanelNavLink[]` renders the centred link row; a node renders as a full-width block above the credit), `headerClassName?`, `contentClassName?`, `containerClassName?`, `className?`, `style?` |
|
|
227
|
+
| `NavLayout` | `nav`, `translate?`, `title?`, `home?` (brand target; defaults to the first section's first item), `actions?`, `mobileMenu?` (below `md`, a menu button and a sheet replace the section menu and the screen strip; `actions` stay), `skipLinkLabel?` (the first-on-page skip link to `<main id="main">`; default `shell.skip` → "Skip to content"; `false` renders no link and no `#main`), `themeToggle?` (`true` or `{ labels? }`: the light/dark switcher in the footer's bottom row beside the credit; names via `shell.toLight` / `shell.toDark`), `footer?` (`PanelNavLink[]` renders the centred link row; a node renders as a full-width block above the credit), `headerClassName?`, `contentClassName?`, `containerClassName?`, `className?`, `style?` |
|
|
228
228
|
| `TopNav` | `config`, `translate?`, `ariaLabel?`, `className?`, `style?` |
|
|
229
229
|
| `SideNav` | the same, plus `variant?: 'side' \| 'bar'` |
|
|
230
230
|
| `MobileNav` | the same as `TopNav`; the trigger's name and the sheet title resolve `shell.menu` ("Menu"), its close button `shell.close` ("Close"); `className`/`style` land on the trigger |
|
|
231
|
-
| `Footer` | `links?`, `content?` (full-width block, `data-footer-content`), `translate?`, `containerClassName?`, `children?` (join the link row), `className?`, `style?` — always renders the platform/owner credit line last, via `ShellCredit` |
|
|
231
|
+
| `Footer` | `links?`, `content?` (full-width block, `data-footer-content`), `translate?`, `containerClassName?`, `children?` (join the link row), `themeToggle?`, `className?`, `style?` — always renders the platform/owner credit line last, via `ShellCredit` |
|
|
232
|
+
| `ThemeToggle` | `labels?: { toLight?, toDark? }` (English defaults), `className?`, `style?` — a 44px `<button data-theme-toggle>` flipping the resolved scheme |
|
|
232
233
|
| `ShellCredit`, `useShellCredit` | `className?` — "Powered by OwlMeans" plus the owner's copyright, resolved the same way the sign-in screen's credit is |
|
|
233
234
|
|
|
234
235
|
### 4. Forms, panels and status
|
|
@@ -345,6 +346,8 @@ The dialog's strings are the lib-tier `socket` namespace (`reload.title`, `reloa
|
|
|
345
346
|
| `BlockProps`, `TextProps`, `LinkProps`, `StatusProps`, `StyledProps`, `TextVariant` | type | Their props |
|
|
346
347
|
| `ImageUploader`, `ImageUploaderProps` | component, type | Drop target with a `previewUrl` |
|
|
347
348
|
| `Toaster` | component | `sonner` surface themed from app tokens; follows `.dark` on the document element |
|
|
349
|
+
| `ThemeToggle`, `ThemeToggleProps`, `ThemeToggleLabels` | component, type | The light/dark switcher |
|
|
350
|
+
| `useColorScheme()`, `ColorSchemeModel` | hook, type | `{ scheme, choice, setChoice }` — the resolved scheme, the stored choice (`null` = follow the OS), and the setter |
|
|
348
351
|
| `SocketReloadDialog` | component | Blocking reload prompt for `'lost'` sockets |
|
|
349
352
|
| `LoginScreen`, `LocalizedLoginScreen` | component | Identity-provider choice screen; the localized one binds `translate` to the app's resources |
|
|
350
353
|
| `appendLoginScreen(ctx, setup?)`, `LoginScreenSetup` | function, type | Register the screen on the login service, with `Logo` and other `LoginScreenProps` |
|
|
@@ -388,6 +391,24 @@ relying party.
|
|
|
388
391
|
`@owlmeans/web-panel/auth/entrypoints` exports `entrypoints`: `@owlmeans/client-auth/manager`'s
|
|
389
392
|
bindings plus `@owlmeans/api-config-client`'s.
|
|
390
393
|
|
|
394
|
+
### `@owlmeans/web-panel/scheme`
|
|
395
|
+
|
|
396
|
+
React-free — it imports nothing, so a Node build script can load it.
|
|
397
|
+
|
|
398
|
+
| Symbol | Kind | Purpose |
|
|
399
|
+
|---|---|---|
|
|
400
|
+
| `COLOR_SCHEME_KEY` | const | `'owlmeans:color-scheme'`, the `localStorage` key |
|
|
401
|
+
| `COLOR_SCHEME_EVENT` | const | The `window` event every change dispatches |
|
|
402
|
+
| `ColorSchemeChoice` | type | `'light' \| 'dark'` |
|
|
403
|
+
| `readColorScheme()` | function | The stored choice or `null` (storage errors swallowed) |
|
|
404
|
+
| `applyColorScheme(choice \| null)` | function | Sets the `light`/`dark` class on `<html>`, stores or clears the key, dispatches the event |
|
|
405
|
+
| `colorSchemeBootstrapScript()` | function | The inline head script that applies a stored choice before first paint |
|
|
406
|
+
|
|
407
|
+
The class contract: `.dark` = chosen dark, `.light` = chosen light, neither = follow the OS. A
|
|
408
|
+
consumer's CSS puts dark tokens under `.dark` AND under
|
|
409
|
+
`@media (prefers-color-scheme: dark) { :root:not(.light) { … } }`, and inlines
|
|
410
|
+
`colorSchemeBootstrapScript()` in the document head.
|
|
411
|
+
|
|
391
412
|
### `@owlmeans/web-panel/consent`
|
|
392
413
|
|
|
393
414
|
| Symbol | Kind | Purpose |
|
|
@@ -445,6 +466,9 @@ The public names match the MUI implementation; these props and types differ:
|
|
|
445
466
|
does nothing and `PanelCookieConsent` keeps its floating button.
|
|
446
467
|
- **Pass an application footer layout as a node `footer`.** It renders full-width above the credit;
|
|
447
468
|
an array renders the centred link row. Neither removes the credit.
|
|
469
|
+
- **A theme for `ThemeToggle` reads both classes.** Dark tokens under `.dark` only never follow
|
|
470
|
+
the OS; under the media query only, they ignore an explicit light choice. Inline the head
|
|
471
|
+
bootstrap or a stored dark choice flashes light for a frame.
|
|
448
472
|
- **The shell already renders the skip link and `<main id="main">`.** A screen never renders a
|
|
449
473
|
second `<main>` or `#main`; an app with its own skip link passes `skipLinkLabel={false}`.
|
|
450
474
|
- **`mobileMenu` is opt-in.** Without it a narrow viewport keeps the section menu and the screen
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/web-panel",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-09-
|
|
4
|
+
"version": "0.1.18-rc.53",
|
|
5
|
+
"generatedAt": "2026-09-22T09:20:40.388Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -8,7 +8,7 @@ user-invocable: false
|
|
|
8
8
|
# @owlmeans/web-panel
|
|
9
9
|
|
|
10
10
|
**Layer:** Web (React)
|
|
11
|
-
**Install:** `"@owlmeans/web-panel": "^0.1.18-rc.
|
|
11
|
+
**Install:** `"@owlmeans/web-panel": "^0.1.18-rc.53"` in `dependencies`
|
|
12
12
|
|
|
13
13
|
## Key Exports
|
|
14
14
|
|
|
@@ -18,6 +18,7 @@ user-invocable: false
|
|
|
18
18
|
| `NavLayout` | The application shell — header, section menu, screen menu, content, footer |
|
|
19
19
|
| `TopNav` / `SideNav` / `MobileNav` / `Footer` | The shell's pieces, mountable on their own |
|
|
20
20
|
| `Toaster` | The application's toast surface — mounted once, in the layout |
|
|
21
|
+
| `ThemeToggle` / `useColorScheme` | The light/dark switcher (`ThemeToggleProps`, `ThemeToggleLabels`) and the hook behind it (`ColorSchemeModel`) — see *Light and dark* below |
|
|
21
22
|
| `SocketReloadDialog` | The global "reload the page" prompt (opt in via `cfg.socket.reloadDialog`) `PanelApp` mounts automatically — see below. The hook it reads, `useSocketStatus`, is imported from `@owlmeans/client-socket`, not from this package |
|
|
22
23
|
| `Link` | An `<a>` addressing an entrypoint alias (or a literal `src`), with the label taken from i18n |
|
|
23
24
|
| `LoginScreen` / `LocalizedLoginScreen` / `appendLoginScreen` | The identity-provider choice screen — see `login-methods` |
|
|
@@ -42,6 +43,9 @@ user-invocable: false
|
|
|
42
43
|
- `./consent` — the cookie consent dialog and policy, bound to this app's i18n, plus a menu-row
|
|
43
44
|
widget and a presence service so a host's own collapsed menu can take over the floating button's
|
|
44
45
|
job
|
|
46
|
+
- `./scheme` — the React-free colour-scheme half: `COLOR_SCHEME_KEY`, `COLOR_SCHEME_EVENT`,
|
|
47
|
+
`ColorSchemeChoice`, `readColorScheme`, `applyColorScheme`, `colorSchemeBootstrapScript` — safe
|
|
48
|
+
to import from a Node build script
|
|
45
49
|
|
|
46
50
|
## Usage
|
|
47
51
|
|
|
@@ -104,11 +108,11 @@ header. Both render the same items; only one is visible at a time.
|
|
|
104
108
|
|
|
105
109
|
| Component | Props |
|
|
106
110
|
|---|---|
|
|
107
|
-
| `NavLayout` | `nav: PanelNavConfig`, `translate?`, `title?: ReactNode`, `home?: string` (brand target — defaults to the first section's first item), `actions?: ReactNode`, `mobileMenu?: boolean`, `skipLinkLabel?: string \| false`, `footer?: PanelNavLink[] \| ReactNode`, `headerClassName?`, `contentClassName?`, `containerClassName?`, `className?`, `style?` |
|
|
111
|
+
| `NavLayout` | `nav: PanelNavConfig`, `translate?`, `title?: ReactNode`, `home?: string` (brand target — defaults to the first section's first item), `actions?: ReactNode`, `mobileMenu?: boolean`, `skipLinkLabel?: string \| false`, `themeToggle?: boolean \| { labels? }`, `footer?: PanelNavLink[] \| ReactNode`, `headerClassName?`, `contentClassName?`, `containerClassName?`, `className?`, `style?` |
|
|
108
112
|
| `TopNav` | `config: PanelNavConfig`, `translate?`, `ariaLabel?`, `className?`, `style?` |
|
|
109
113
|
| `SideNav` | the same, plus `variant?: 'side' \| 'bar'` |
|
|
110
114
|
| `MobileNav` | the same as `TopNav`; `ariaLabel` names the landmark inside the sheet, `className`/`style` land on the trigger button |
|
|
111
|
-
| `Footer` | `links?: PanelNavLink[]`, `content?: ReactNode` (a full-width block — see below), `translate?`, `containerClassName?` (the shell's rhythm, so the footer row lines up with the header and content), `children?` (joins the centred link row), `className?`, `style?` |
|
|
115
|
+
| `Footer` | `links?: PanelNavLink[]`, `content?: ReactNode` (a full-width block — see below), `translate?`, `containerClassName?` (the shell's rhythm, so the footer row lines up with the header and content), `children?` (joins the centred link row), `themeToggle?: boolean \| { labels? }` (the switcher beside the credit), `className?`, `style?` |
|
|
112
116
|
| `ShellCredit` / `useShellCredit` | `className?`; the platform/owner credit line `Footer` always renders — see below |
|
|
113
117
|
|
|
114
118
|
**The narrow-viewport menu is opt-in: `mobileMenu`.** Off (the default) the shell is exactly the
|
|
@@ -153,8 +157,9 @@ row. A node — an application's own footer layout: a brand, a description, link
|
|
|
153
157
|
`Footer`'s `content` and renders as a `w-full self-stretch text-start` block (`data-footer-content`)
|
|
154
158
|
inside the footer's container, above the credit. The container keeps `items-center` for the link
|
|
155
159
|
row and the credit; only the block stretches. It used to land INSIDE the centred link row, where
|
|
156
|
-
it shrink-wrapped to its content in the middle of the page. `ShellCredit` stays the
|
|
157
|
-
|
|
160
|
+
it shrink-wrapped to its content in the middle of the page. `ShellCredit` stays the last thing the
|
|
161
|
+
footer renders in every case — the container's own last child, or, with `themeToggle`, first in the
|
|
162
|
+
bottom row `[data-footer-bottom]` with the switcher after it. Pinned by `nav.spec.ts` → "a node footer" (`?footer=node`).
|
|
158
163
|
|
|
159
164
|
**The style slots are REGIONS, and each region is its own SURFACE.** `className` is the root —
|
|
160
165
|
the full-height page *behind* the header, side menu and footer. `headerClassName` is the sticky
|
|
@@ -244,6 +249,60 @@ Rules that make the shell behave:
|
|
|
244
249
|
uses the package-local shadcn `navigation-menu` primitive and `MobileNav` the package-local
|
|
245
250
|
`sheet` (over `@radix-ui/react-dialog`, a peer) — see the package-boundary rule below.
|
|
246
251
|
|
|
252
|
+
### Light and dark — `ThemeToggle`, `useColorScheme`, `./scheme`
|
|
253
|
+
|
|
254
|
+
The standard switcher, with no theme library behind it. `NavLayout themeToggle` (or `Footer
|
|
255
|
+
themeToggle`) puts it in the footer's bottom row beside the credit, whatever `footer` is; absent or
|
|
256
|
+
`false`, the footer's DOM is what it is without one. It is also mountable on its own.
|
|
257
|
+
|
|
258
|
+
**The contract is two classes on `<html>`.** `dark` = the visitor chose dark, `light` = chose light,
|
|
259
|
+
neither = follow the operating system. The choice is stored under `COLOR_SCHEME_KEY`
|
|
260
|
+
(`owlmeans:color-scheme`, `localStorage`, every access in `try`). `light` exists as a class of its
|
|
261
|
+
own because a visitor choosing light on a dark system has no other way to say so to a stylesheet
|
|
262
|
+
that paints dark under `prefers-color-scheme: dark`. So a consumer's CSS must read BOTH:
|
|
263
|
+
|
|
264
|
+
```css
|
|
265
|
+
:root { color-scheme: light; /* light tokens */ }
|
|
266
|
+
.dark { color-scheme: dark; /* dark tokens */ }
|
|
267
|
+
@media (prefers-color-scheme: dark) {
|
|
268
|
+
:root:not(.light) { color-scheme: dark; /* the same dark tokens */ }
|
|
269
|
+
}
|
|
270
|
+
/* Tailwind v4: a `dark:` variant that follows the same rule */
|
|
271
|
+
@custom-variant dark {
|
|
272
|
+
&:where(.dark, .dark *) { @slot; }
|
|
273
|
+
@media (prefers-color-scheme: dark) { &:where(:root:not(.light), :root:not(.light) *) { @slot; } }
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
A theme with dark tokens under `.dark` alone never follows the OS; one with them under the media
|
|
278
|
+
query alone ignores an explicit light choice.
|
|
279
|
+
|
|
280
|
+
**The head bootstrap goes before any stylesheet paints.** `colorSchemeBootstrapScript()` returns a
|
|
281
|
+
self-contained inline script that puts the stored choice's class on `<html>` before first paint —
|
|
282
|
+
the only moment it can; a component doing it after mount shows a visitor who chose dark one frame
|
|
283
|
+
of the light page. A build step inlines it into `index.html` (import it from
|
|
284
|
+
`@owlmeans/web-panel/scheme` — that module imports nothing, React included) and a CSP that hashes
|
|
285
|
+
inline scripts must hash it. Without the bootstrap, `useColorScheme` re-applies the stored class on
|
|
286
|
+
mount: correct, one frame late.
|
|
287
|
+
|
|
288
|
+
Rules the pieces own:
|
|
289
|
+
|
|
290
|
+
- **The toggle flips the RESOLVED scheme** (`choice ?? system`), never the stored choice alone: on
|
|
291
|
+
a dark system with nothing stored the first press means light. The icon is the scheme the page
|
|
292
|
+
is in (sun while light, moon while dark), chosen in script, not by a `dark:` variant.
|
|
293
|
+
- **The accessible name says what pressing does** — `labels.toLight` / `labels.toDark`, English
|
|
294
|
+
defaults; `NavLayout` resolves them through `translate` (`shell.toLight`, `shell.toDark`).
|
|
295
|
+
- **One choice per document.** `applyColorScheme(choice | null)` sets the class, stores or clears
|
|
296
|
+
the key and dispatches `COLOR_SCHEME_EVENT`, so every mounted `useColorScheme` agrees at once;
|
|
297
|
+
another tab's change arrives through `storage`, and an OS switch through `matchMedia` while
|
|
298
|
+
nothing is stored. `setChoice(null)` hands the page back to the OS.
|
|
299
|
+
- **Look:** a 44px round target, `text-muted-foreground` → `hover:text-foreground`, the 3px
|
|
300
|
+
`--ring` focus ring, no fill, border, gradient or shadow.
|
|
301
|
+
|
|
302
|
+
Pinned by `tests/scheme.spec.ts` (`?themeToggle=1`): the module's imports, the bootstrap string,
|
|
303
|
+
the class flip and persistence, the resolved flip under an emulated dark system, a node footer,
|
|
304
|
+
and the absent case.
|
|
305
|
+
|
|
247
306
|
### Toasts — `Toaster`
|
|
248
307
|
|
|
249
308
|
An action that succeeded or failed says so in a toast. The surface is `Toaster`; the messages are
|
|
@@ -19,7 +19,12 @@ import type { FooterProps } from './types.js';
|
|
|
19
19
|
* The credit is rendered unconditionally — never behind a prop, so a restyle cannot delete it.
|
|
20
20
|
* `useShellCredit` decides emptiness here (rather than letting `ShellCredit` render null inside
|
|
21
21
|
* an otherwise-empty bordered strip): the footer as a whole returns `null` only when content,
|
|
22
|
-
* links, children AND the resolved credit are all
|
|
22
|
+
* links, children, the theme switcher AND the resolved credit are all absent.
|
|
23
|
+
*
|
|
24
|
+
* `themeToggle` puts the light/dark `ThemeToggle` in the bottom row beside the credit. It is the
|
|
25
|
+
* footer's own row rather than something an application adds through `content`, so every area of
|
|
26
|
+
* an application offers it in the same place and the credit keeps its position whatever the
|
|
27
|
+
* application's own footer layout does.
|
|
23
28
|
*/
|
|
24
29
|
export declare const Footer: FC<FooterProps>;
|
|
25
30
|
//# sourceMappingURL=component.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../../src/components/footer/component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../../src/components/footer/component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;AAO/B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE7C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,MAAM,EAAE,EAAE,CAAC,WAAW,CAyClC,CAAA"}
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { defaultNavTranslate, resolveNavLabel } from '@owlmeans/client-panel';
|
|
3
3
|
import { cn } from '../../@/lib/utils.js';
|
|
4
4
|
import { Link } from '../link.js';
|
|
5
|
+
import { ThemeToggle } from '../scheme/toggle.js';
|
|
5
6
|
import { ShellCredit, useShellCredit } from './credit.js';
|
|
6
7
|
/**
|
|
7
8
|
* The standard footer — an optional full-width `content` block, a centred row of links plus
|
|
@@ -22,12 +23,20 @@ import { ShellCredit, useShellCredit } from './credit.js';
|
|
|
22
23
|
* The credit is rendered unconditionally — never behind a prop, so a restyle cannot delete it.
|
|
23
24
|
* `useShellCredit` decides emptiness here (rather than letting `ShellCredit` render null inside
|
|
24
25
|
* an otherwise-empty bordered strip): the footer as a whole returns `null` only when content,
|
|
25
|
-
* links, children AND the resolved credit are all
|
|
26
|
+
* links, children, the theme switcher AND the resolved credit are all absent.
|
|
27
|
+
*
|
|
28
|
+
* `themeToggle` puts the light/dark `ThemeToggle` in the bottom row beside the credit. It is the
|
|
29
|
+
* footer's own row rather than something an application adds through `content`, so every area of
|
|
30
|
+
* an application offers it in the same place and the credit keeps its position whatever the
|
|
31
|
+
* application's own footer layout does.
|
|
26
32
|
*/
|
|
27
|
-
export const Footer = ({ links, translate = defaultNavTranslate, children, content, className, style, containerClassName }) => {
|
|
33
|
+
export const Footer = ({ links, translate = defaultNavTranslate, children, content, className, style, containerClassName, themeToggle }) => {
|
|
28
34
|
const credit = useShellCredit();
|
|
29
35
|
const hasLinks = (links != null && links.length > 0) || children != null;
|
|
30
|
-
|
|
36
|
+
const toggle = themeToggle == null || themeToggle === false
|
|
37
|
+
? null
|
|
38
|
+
: themeToggle === true ? {} : themeToggle;
|
|
39
|
+
if (content == null && !hasLinks && toggle == null && !credit.poweredBy && credit.line == null) {
|
|
31
40
|
return null;
|
|
32
41
|
}
|
|
33
42
|
return _jsx("footer", { className: cn('border-t py-6', className), style: style, children: _jsxs("div", { className: cn('flex flex-col items-center gap-3 text-center', containerClassName), children: [content != null && _jsx("div", { "data-footer-content": true, className: "w-full self-stretch text-start", children: content }), hasLinks && _jsxs("div", { className: "flex flex-wrap items-center justify-center gap-4 text-sm", children: [links?.map((link, idx) => {
|
|
@@ -35,6 +44,8 @@ export const Footer = ({ links, translate = defaultNavTranslate, children, conte
|
|
|
35
44
|
return link.href != null
|
|
36
45
|
? _jsx(Link, { src: link.href, open: link.open, children: label }, `${link.href}:${idx}`)
|
|
37
46
|
: _jsx(Link, { module: link.alias, open: link.open, children: label }, `${link.alias}:${idx}`);
|
|
38
|
-
}), children] }),
|
|
47
|
+
}), children] }), toggle != null
|
|
48
|
+
? _jsxs("div", { "data-footer-bottom": true, className: "flex flex-wrap items-center justify-center gap-2", children: [_jsx(ShellCredit, {}), _jsx(ThemeToggle, { ...(toggle.labels != null ? { labels: toggle.labels } : {}) })] })
|
|
49
|
+
: _jsx(ShellCredit, {})] }) });
|
|
39
50
|
};
|
|
40
51
|
//# sourceMappingURL=component.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component.js","sourceRoot":"","sources":["../../../src/components/footer/component.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAC7E,OAAO,EAAE,EAAE,EAAE,MAAM,sBAAsB,CAAA;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACjC,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAIzD
|
|
1
|
+
{"version":3,"file":"component.js","sourceRoot":"","sources":["../../../src/components/footer/component.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAC7E,OAAO,EAAE,EAAE,EAAE,MAAM,sBAAsB,CAAA;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACjD,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAIzD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,CAAC,MAAM,MAAM,GAAoB,CAAC,EACtC,KAAK,EAAE,SAAS,GAAG,mBAAmB,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,kBAAkB,EAC/F,WAAW,EACZ,EAAE,EAAE;IACH,MAAM,MAAM,GAAG,cAAc,EAAE,CAAA;IAC/B,MAAM,QAAQ,GAAG,CAAC,KAAK,IAAI,IAAI,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,QAAQ,IAAI,IAAI,CAAA;IACxE,MAAM,MAAM,GAAG,WAAW,IAAI,IAAI,IAAI,WAAW,KAAK,KAAK;QACzD,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,WAAW,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,CAAA;IAC3C,IAAI,OAAO,IAAI,IAAI,IAAI,CAAC,QAAQ,IAAI,MAAM,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC;QAC/F,OAAO,IAAI,CAAA;IACb,CAAC;IAED,OAAO,iBAAQ,SAAS,EAAE,EAAE,CAAC,eAAe,EAAE,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,YACpE,eAAK,SAAS,EAAE,EAAE,CAAC,8CAA8C,EAAE,kBAAkB,CAAC,aACnF,OAAO,IAAI,IAAI,IAAI,2CAAyB,SAAS,EAAC,gCAAgC,YAAE,OAAO,GAAO,EACtG,QAAQ,IAAI,eAAK,SAAS,EAAC,0DAA0D,aACnF,KAAK,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;4BACxB,MAAM,KAAK,GAAG,eAAe,CAC3B,SAAS,EAAE,IAAI,CAAC,KAAK,EAAE,WAAW,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,IAAI,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,IAAI,CAC3F,CAAA;4BAED,OAAO,IAAI,CAAC,IAAI,IAAI,IAAI;gCACtB,CAAC,CAAC,KAAC,IAAI,IAA6B,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,YAAG,KAAK,IAA9D,GAAG,IAAI,CAAC,IAAI,IAAI,GAAG,EAAE,CAAiD;gCACnF,CAAC,CAAC,KAAC,IAAI,IAA8B,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,YAAG,KAAK,IAAnE,GAAG,IAAI,CAAC,KAAK,IAAI,GAAG,EAAE,CAAqD,CAAA;wBAC5F,CAAC,CAAC,EACD,QAAQ,IACL,EAML,MAAM,IAAI,IAAI;oBACb,CAAC,CAAC,2CAAwB,SAAS,EAAC,kDAAkD,aACpF,KAAC,WAAW,KAAG,EACf,KAAC,WAAW,OAAK,CAAC,MAAM,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,GAAI,IACzE;oBACN,CAAC,CAAC,KAAC,WAAW,KAAG,IACf,GACC,CAAA;AACX,CAAC,CAAA"}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { PropsWithChildren, ReactNode } from 'react';
|
|
2
2
|
import type { NavTranslate, PanelNavLink } from '@owlmeans/client-panel';
|
|
3
|
+
import type { ThemeToggleLabels } from '../scheme/types.js';
|
|
3
4
|
import type { StyledProps } from '../types.js';
|
|
5
|
+
/** `true` for the English labels; an object to pass the application's own. */
|
|
6
|
+
export type FooterThemeToggle = boolean | {
|
|
7
|
+
labels?: ThemeToggleLabels;
|
|
8
|
+
};
|
|
4
9
|
export interface FooterProps extends PropsWithChildren<StyledProps> {
|
|
5
10
|
links?: PanelNavLink[];
|
|
6
11
|
/**
|
|
@@ -9,6 +14,11 @@ export interface FooterProps extends PropsWithChildren<StyledProps> {
|
|
|
9
14
|
* the link row and the credit line. `children`, by contrast, join the centred link row.
|
|
10
15
|
*/
|
|
11
16
|
content?: ReactNode;
|
|
17
|
+
/**
|
|
18
|
+
* Render the light/dark `ThemeToggle` in the bottom row, beside the credit line. Absent or
|
|
19
|
+
* `false`, the footer is exactly what it is without a switcher.
|
|
20
|
+
*/
|
|
21
|
+
themeToggle?: FooterThemeToggle;
|
|
12
22
|
/** The shell's horizontal rhythm, so the footer row lines up with the header and the content. */
|
|
13
23
|
containerClassName?: string;
|
|
14
24
|
/** See {@link NavTranslate} — omitted, literal labels and humanized aliases are used. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/footer/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AACxE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAE9C,MAAM,WAAW,WAAY,SAAQ,iBAAiB,CAAC,WAAW,CAAC;IACjE,KAAK,CAAC,EAAE,YAAY,EAAE,CAAA;IACtB;;;;OAIG;IACH,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,iGAAiG;IACjG,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,yFAAyF;IACzF,SAAS,CAAC,EAAE,YAAY,CAAA;CACzB"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/footer/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AACxE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAC3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAE9C,8EAA8E;AAC9E,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG;IAAE,MAAM,CAAC,EAAE,iBAAiB,CAAA;CAAE,CAAA;AAExE,MAAM,WAAW,WAAY,SAAQ,iBAAiB,CAAC,WAAW,CAAC;IACjE,KAAK,CAAC,EAAE,YAAY,EAAE,CAAA;IACtB;;;;OAIG;IACH,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB;;;OAGG;IACH,WAAW,CAAC,EAAE,iBAAiB,CAAA;IAC/B,iGAAiG;IACjG,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,yFAAyF;IACzF,SAAS,CAAC,EAAE,YAAY,CAAA;CACzB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,YAAY,CAAA;AAE/B,cAAc,sBAAsB,CAAA;AACpC,cAAc,mBAAmB,CAAA;AACjC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAEhC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,sBAAsB,CAAA;AACpC,cAAc,wBAAwB,CAAA;AACtC,cAAc,qBAAqB,CAAA;AAEnC,cAAc,mBAAmB,CAAA;AAEjC,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,mBAAmB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,YAAY,CAAA;AAE/B,cAAc,sBAAsB,CAAA;AACpC,cAAc,mBAAmB,CAAA;AACjC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAEhC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,sBAAsB,CAAA;AACpC,cAAc,wBAAwB,CAAA;AACtC,cAAc,qBAAqB,CAAA;AAEnC,cAAc,mBAAmB,CAAA;AAEjC,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,mBAAmB,CAAA;AACjC,cAAc,mBAAmB,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAEA,cAAc,sBAAsB,CAAA;AACpC,cAAc,mBAAmB,CAAA;AACjC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAEhC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,sBAAsB,CAAA;AACpC,cAAc,wBAAwB,CAAA;AACtC,cAAc,qBAAqB,CAAA;AAEnC,cAAc,mBAAmB,CAAA;AAEjC,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,mBAAmB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAEA,cAAc,sBAAsB,CAAA;AACpC,cAAc,mBAAmB,CAAA;AACjC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAEhC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,sBAAsB,CAAA;AACpC,cAAc,wBAAwB,CAAA;AACtC,cAAc,qBAAqB,CAAA;AAEnC,cAAc,mBAAmB,CAAA;AAEjC,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,mBAAmB,CAAA;AACjC,cAAc,mBAAmB,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../../src/components/nav/layout.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAc,MAAM,OAAO,CAAA;AAS3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AA2BhD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,SAAS,EAAE,EAAE,CAAC,cAAc,
|
|
1
|
+
{"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../../src/components/nav/layout.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAc,MAAM,OAAO,CAAA;AAS3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AA2BhD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,SAAS,EAAE,EAAE,CAAC,cAAc,CAsIxC,CAAA"}
|
|
@@ -45,8 +45,19 @@ const SKIP_LINK = [
|
|
|
45
45
|
* The skip link comes FIRST in the root, ahead of the header — the first thing a keyboard reaches
|
|
46
46
|
* — and moves focus to `<main id="main">`, past the brand, the menus and the actions.
|
|
47
47
|
*/
|
|
48
|
-
export const NavLayout = ({ nav, translate = defaultNavTranslate, title, home, actions, footer, children, className, style, headerClassName, contentClassName, containerClassName, mobileMenu = false, skipLinkLabel }) => {
|
|
48
|
+
export const NavLayout = ({ nav, translate = defaultNavTranslate, title, home, actions, footer, children, className, style, headerClassName, contentClassName, containerClassName, mobileMenu = false, skipLinkLabel, themeToggle }) => {
|
|
49
49
|
const navigator = useNavigate();
|
|
50
|
+
// The switcher's names go through `translate` like every other shell string, with the caller's
|
|
51
|
+
// own labels winning; `false`/absent stays absent, so the footer renders exactly as it did.
|
|
52
|
+
const toggle = themeToggle == null || themeToggle === false ? undefined : {
|
|
53
|
+
labels: {
|
|
54
|
+
toLight: (themeToggle === true ? undefined : themeToggle.labels?.toLight)
|
|
55
|
+
?? translate('shell.toLight', 'Switch to light mode'),
|
|
56
|
+
toDark: (themeToggle === true ? undefined : themeToggle.labels?.toDark)
|
|
57
|
+
?? translate('shell.toDark', 'Switch to dark mode'),
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
const toggleProp = toggle != null ? { themeToggle: toggle } : {};
|
|
50
61
|
const main = useRef(null);
|
|
51
62
|
const skipLabel = skipLinkLabel === false
|
|
52
63
|
? null
|
|
@@ -68,9 +79,9 @@ export const NavLayout = ({ nav, translate = defaultNavTranslate, title, home, a
|
|
|
68
79
|
// value stays a width-only change; `px-8` still overrides the padding when that is the intent.
|
|
69
80
|
const container = cn(CONTAINER, containerClassName);
|
|
70
81
|
return _jsxs("div", { className: cn('flex min-h-screen flex-col bg-background text-foreground', className), style: style, children: [skipLabel != null && _jsx("a", { href: "#main", onClick: skip, "data-skip-link": true, className: SKIP_LINK, children: skipLabel }), _jsxs("header", { className: cn('sticky top-0 z-40 isolate border-b bg-background text-foreground', headerClassName), children: [_jsxs("div", { className: cn('flex h-14 items-center gap-6', container), children: [title != null ? _jsx("a", { onClick: brandAlias != null ? navigator.press(brandAlias) : undefined, className: cn('brand flex items-center gap-2 text-lg font-semibold', brandAlias != null && 'cursor-pointer'), children: title }) : null, _jsx(TopNav, { config: nav, translate: translate, ariaLabel: "Sections", className: mobileMenu ? 'hidden md:flex' : undefined }), _jsxs("div", { className: "ml-auto flex items-center gap-2", children: [actions, mobileMenu && _jsx(MobileNav, { config: nav, translate: translate, ariaLabel: "Sections", className: "md:hidden" })] })] }), !mobileMenu && _jsx(SideNav, { config: nav, translate: translate, variant: "bar", ariaLabel: "Screens", className: "md:hidden" }), _jsx("div", { "aria-hidden": "true", "data-nav-backdrop": true, className: "absolute inset-0 -z-10 bg-background pointer-events-none" })] }), _jsxs("div", { className: "flex flex-1", children: [_jsx(SideNav, { config: nav, translate: translate, variant: "side", ariaLabel: "Screens", className: "hidden md:block" }), _jsx("main", { ref: main, ...(skipLabel != null ? { id: 'main', tabIndex: -1 } : {}), className: cn('flex-1 py-8 outline-none', contentClassName), children: _jsx("div", { className: cn(container), children: children }) })] }), Array.isArray(footer)
|
|
71
|
-
? _jsx(Footer, { links: footer, translate: translate, containerClassName: container })
|
|
82
|
+
? _jsx(Footer, { links: footer, translate: translate, containerClassName: container, ...toggleProp })
|
|
72
83
|
: footer != null
|
|
73
|
-
? _jsx(Footer, { containerClassName: container, content: footer })
|
|
74
|
-
: _jsx(Footer, { containerClassName: container })] });
|
|
84
|
+
? _jsx(Footer, { containerClassName: container, content: footer, ...toggleProp })
|
|
85
|
+
: _jsx(Footer, { containerClassName: container, ...toggleProp })] });
|
|
75
86
|
};
|
|
76
87
|
//# sourceMappingURL=layout.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../../../src/components/nav/layout.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAC5D,OAAO,EAAE,EAAE,EAAE,MAAM,sBAAsB,CAAA;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAA;AAE/C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAGjC;;;;;;;;GAQG;AACH,MAAM,SAAS,GAAG,+BAA+B,CAAA;AAEjD;;;;;;GAMG;AACH,MAAM,SAAS,GAAG;IAChB,iGAAiG;IACjG,kEAAkE;IAClE,2FAA2F;IAC3F,sCAAsC;CACvC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAEX;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,SAAS,GAAuB,CAAC,EAC5C,GAAG,EAAE,SAAS,GAAG,mBAAmB,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAC9F,eAAe,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,UAAU,GAAG,KAAK,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../../../src/components/nav/layout.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { PropsWithChildren, ReactNode } from 'react';
|
|
2
2
|
import type { NavTranslate, PanelNavConfig, PanelNavLink } from '@owlmeans/client-panel';
|
|
3
|
+
import type { FooterThemeToggle } from '../footer/types.js';
|
|
3
4
|
import type { StyledProps } from '../types.js';
|
|
4
5
|
interface NavCommonProps extends StyledProps {
|
|
5
6
|
config: PanelNavConfig;
|
|
@@ -48,6 +49,13 @@ export interface NavLayoutProps extends PropsWithChildren<StyledProps> {
|
|
|
48
49
|
* without the `main` id, for an application that renders its own skip link and target.
|
|
49
50
|
*/
|
|
50
51
|
skipLinkLabel?: string | false;
|
|
52
|
+
/**
|
|
53
|
+
* The light/dark switcher in the footer's bottom row, beside the credit — forwarded to
|
|
54
|
+
* `Footer`, whether `footer` is links, a node or absent. The accessible names resolve
|
|
55
|
+
* `shell.toLight` / `shell.toDark` through `translate` unless `labels` gives them. Absent or
|
|
56
|
+
* `false`, the footer renders exactly as it does without one.
|
|
57
|
+
*/
|
|
58
|
+
themeToggle?: FooterThemeToggle;
|
|
51
59
|
/**
|
|
52
60
|
* An array renders the standard footer: a centred row of links above the credit line.
|
|
53
61
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/nav/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AACxF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAE9C,UAAU,cAAe,SAAQ,WAAW;IAC1C,MAAM,EAAE,cAAc,CAAA;IACtB,yFAAyF;IACzF,SAAS,CAAC,EAAE,YAAY,CAAA;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,WAAY,SAAQ,cAAc;CAAI;AAEvD,MAAM,WAAW,YAAa,SAAQ,cAAc;IAClD;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAe,SAAQ,cAAc;CAAI;AAE1D,MAAM,WAAW,cAAe,SAAQ,iBAAiB,CAAC,WAAW,CAAC;IACpE,GAAG,EAAE,cAAc,CAAA;IACnB,SAAS,CAAC,EAAE,YAAY,CAAA;IACxB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,gFAAgF;IAChF,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,yFAAyF;IACzF,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IAC9B;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,YAAY,EAAE,GAAG,SAAS,CAAA;IACnC;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,yEAAyE;IACzE,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;;;;;;;OASG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/nav/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AACxF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAC3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAE9C,UAAU,cAAe,SAAQ,WAAW;IAC1C,MAAM,EAAE,cAAc,CAAA;IACtB,yFAAyF;IACzF,SAAS,CAAC,EAAE,YAAY,CAAA;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,WAAY,SAAQ,cAAc;CAAI;AAEvD,MAAM,WAAW,YAAa,SAAQ,cAAc;IAClD;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAe,SAAQ,cAAc;CAAI;AAE1D,MAAM,WAAW,cAAe,SAAQ,iBAAiB,CAAC,WAAW,CAAC;IACpE,GAAG,EAAE,cAAc,CAAA;IACnB,SAAS,CAAC,EAAE,YAAY,CAAA;IACxB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,gFAAgF;IAChF,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,yFAAyF;IACzF,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IAC9B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,iBAAiB,CAAA;IAC/B;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,YAAY,EAAE,GAAG,SAAS,CAAA;IACnC;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,yEAAyE;IACzE,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;;;;;;;OASG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ColorSchemeModel } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The page's colour scheme, and the way to choose one.
|
|
4
|
+
*
|
|
5
|
+
* Both halves are read synchronously on the first render — the stored choice from storage, the
|
|
6
|
+
* system preference from `matchMedia` — so a toggle never renders the wrong icon for a frame.
|
|
7
|
+
* Afterwards it follows three sources: `applyColorScheme` anywhere in this document (its event),
|
|
8
|
+
* another tab changing the stored key (`storage`), and the operating system switching while no
|
|
9
|
+
* choice is stored.
|
|
10
|
+
*
|
|
11
|
+
* Mounting also re-applies a stored choice's class. The head bootstrap has normally done that
|
|
12
|
+
* before the first paint; an application without it still ends up on the right scheme, one frame
|
|
13
|
+
* late rather than never.
|
|
14
|
+
*/
|
|
15
|
+
export declare const useColorScheme: () => ColorSchemeModel;
|
|
16
|
+
//# sourceMappingURL=hook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook.d.ts","sourceRoot":"","sources":["../../../src/components/scheme/hook.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAQlD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,cAAc,QAAO,gBAqCjC,CAAA"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { COLOR_SCHEME_EVENT, COLOR_SCHEME_KEY, applyColorScheme, readColorScheme, setColorSchemeClass, } from '../../scheme/scheme.js';
|
|
3
|
+
const DARK_QUERY = '(prefers-color-scheme: dark)';
|
|
4
|
+
const systemDark = () => typeof window !== 'undefined' && typeof window.matchMedia === 'function'
|
|
5
|
+
&& window.matchMedia(DARK_QUERY).matches;
|
|
6
|
+
/**
|
|
7
|
+
* The page's colour scheme, and the way to choose one.
|
|
8
|
+
*
|
|
9
|
+
* Both halves are read synchronously on the first render — the stored choice from storage, the
|
|
10
|
+
* system preference from `matchMedia` — so a toggle never renders the wrong icon for a frame.
|
|
11
|
+
* Afterwards it follows three sources: `applyColorScheme` anywhere in this document (its event),
|
|
12
|
+
* another tab changing the stored key (`storage`), and the operating system switching while no
|
|
13
|
+
* choice is stored.
|
|
14
|
+
*
|
|
15
|
+
* Mounting also re-applies a stored choice's class. The head bootstrap has normally done that
|
|
16
|
+
* before the first paint; an application without it still ends up on the right scheme, one frame
|
|
17
|
+
* late rather than never.
|
|
18
|
+
*/
|
|
19
|
+
export const useColorScheme = () => {
|
|
20
|
+
const [choice, setState] = useState(readColorScheme);
|
|
21
|
+
const [dark, setDark] = useState(systemDark);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const stored = readColorScheme();
|
|
24
|
+
if (stored != null) {
|
|
25
|
+
setColorSchemeClass(stored);
|
|
26
|
+
}
|
|
27
|
+
setState(stored);
|
|
28
|
+
const onChoice = () => setState(readColorScheme());
|
|
29
|
+
const onStorage = (event) => {
|
|
30
|
+
if (event.key === COLOR_SCHEME_KEY || event.key == null) {
|
|
31
|
+
const next = readColorScheme();
|
|
32
|
+
setColorSchemeClass(next);
|
|
33
|
+
setState(next);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
window.addEventListener(COLOR_SCHEME_EVENT, onChoice);
|
|
37
|
+
window.addEventListener('storage', onStorage);
|
|
38
|
+
const media = typeof window.matchMedia === 'function' ? window.matchMedia(DARK_QUERY) : null;
|
|
39
|
+
const onSystem = () => setDark(media?.matches === true);
|
|
40
|
+
media?.addEventListener('change', onSystem);
|
|
41
|
+
onSystem();
|
|
42
|
+
return () => {
|
|
43
|
+
window.removeEventListener(COLOR_SCHEME_EVENT, onChoice);
|
|
44
|
+
window.removeEventListener('storage', onStorage);
|
|
45
|
+
media?.removeEventListener('change', onSystem);
|
|
46
|
+
};
|
|
47
|
+
}, []);
|
|
48
|
+
const setChoice = useCallback((next) => applyColorScheme(next), []);
|
|
49
|
+
return { scheme: choice ?? (dark ? 'dark' : 'light'), choice, setChoice };
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=hook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook.js","sourceRoot":"","sources":["../../../src/components/scheme/hook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACxD,OAAO,EACL,kBAAkB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,eAAe,EAAE,mBAAmB,GAC7F,MAAM,wBAAwB,CAAA;AAI/B,MAAM,UAAU,GAAG,8BAA8B,CAAA;AAEjD,MAAM,UAAU,GAAG,GAAY,EAAE,CAC/B,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,UAAU;OACnE,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,OAAO,CAAA;AAE5C;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,GAAqB,EAAE;IACnD,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAA2B,eAAe,CAAC,CAAA;IAC9E,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAU,UAAU,CAAC,CAAA;IAErD,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,MAAM,GAAG,eAAe,EAAE,CAAA;QAChC,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC;YACnB,mBAAmB,CAAC,MAAM,CAAC,CAAA;QAC7B,CAAC;QACD,QAAQ,CAAC,MAAM,CAAC,CAAA;QAEhB,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAC,QAAQ,CAAC,eAAe,EAAE,CAAC,CAAA;QAClD,MAAM,SAAS,GAAG,CAAC,KAAmB,EAAE,EAAE;YACxC,IAAI,KAAK,CAAC,GAAG,KAAK,gBAAgB,IAAI,KAAK,CAAC,GAAG,IAAI,IAAI,EAAE,CAAC;gBACxD,MAAM,IAAI,GAAG,eAAe,EAAE,CAAA;gBAC9B,mBAAmB,CAAC,IAAI,CAAC,CAAA;gBACzB,QAAQ,CAAC,IAAI,CAAC,CAAA;YAChB,CAAC;QACH,CAAC,CAAA;QACD,MAAM,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAA;QACrD,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;QAE7C,MAAM,KAAK,GAAG,OAAO,MAAM,CAAC,UAAU,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QAC5F,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC,CAAA;QACvD,KAAK,EAAE,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;QAC3C,QAAQ,EAAE,CAAA;QAEV,OAAO,GAAG,EAAE;YACV,MAAM,CAAC,mBAAmB,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAA;YACxD,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;YAChD,KAAK,EAAE,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;QAChD,CAAC,CAAA;IACH,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,MAAM,SAAS,GAAG,WAAW,CAAC,CAAC,IAA8B,EAAE,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAA;IAE7F,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAA;AAC3E,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/scheme/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,YAAY,CAAA;AAE/B,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/scheme/index.ts"],"names":[],"mappings":"AAEA,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
import type { ThemeToggleProps } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* The light/dark switcher.
|
|
5
|
+
*
|
|
6
|
+
* It flips the RESOLVED scheme, not the stored choice: on a system that prefers dark and with no
|
|
7
|
+
* choice stored, the page is dark, so the first press means "light" — a toggle keyed on the
|
|
8
|
+
* choice alone would answer that press with "dark", which changes nothing the visitor can see.
|
|
9
|
+
* The icon is the scheme the page is in (a sun while light, a moon while dark), chosen in script
|
|
10
|
+
* from the same state rather than by a `dark:` variant, so it is right whatever the app's CSS
|
|
11
|
+
* calls dark. The accessible name says what pressing does, from `labels` or the English default.
|
|
12
|
+
*/
|
|
13
|
+
export declare const ThemeToggle: FC<ThemeToggleProps>;
|
|
14
|
+
//# sourceMappingURL=toggle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toggle.d.ts","sourceRoot":"","sources":["../../../src/components/scheme/toggle.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;AAI/B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAclD;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CAc5C,CAAA"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Moon, Sun } from 'lucide-react';
|
|
3
|
+
import { cn } from '../../@/lib/utils.js';
|
|
4
|
+
import { useColorScheme } from './hook.js';
|
|
5
|
+
/**
|
|
6
|
+
* A 44px round target in the page's own tokens — muted at rest, the foreground on hover — with
|
|
7
|
+
* the 3px ring every interactive element shows on keyboard focus. No fill, border, gradient or
|
|
8
|
+
* shadow: it sits in a footer's bottom row beside the credit and must read as part of it.
|
|
9
|
+
*/
|
|
10
|
+
const TOGGLE = [
|
|
11
|
+
'inline-flex size-11 shrink-0 cursor-pointer items-center justify-center rounded-full',
|
|
12
|
+
'bg-transparent text-muted-foreground transition-colors hover:text-foreground',
|
|
13
|
+
'outline-none focus-visible:ring-[3px] focus-visible:ring-ring focus-visible:ring-offset-2',
|
|
14
|
+
'focus-visible:ring-offset-background',
|
|
15
|
+
].join(' ');
|
|
16
|
+
/**
|
|
17
|
+
* The light/dark switcher.
|
|
18
|
+
*
|
|
19
|
+
* It flips the RESOLVED scheme, not the stored choice: on a system that prefers dark and with no
|
|
20
|
+
* choice stored, the page is dark, so the first press means "light" — a toggle keyed on the
|
|
21
|
+
* choice alone would answer that press with "dark", which changes nothing the visitor can see.
|
|
22
|
+
* The icon is the scheme the page is in (a sun while light, a moon while dark), chosen in script
|
|
23
|
+
* from the same state rather than by a `dark:` variant, so it is right whatever the app's CSS
|
|
24
|
+
* calls dark. The accessible name says what pressing does, from `labels` or the English default.
|
|
25
|
+
*/
|
|
26
|
+
export const ThemeToggle = ({ labels, className, style }) => {
|
|
27
|
+
const { scheme, setChoice } = useColorScheme();
|
|
28
|
+
const dark = scheme === 'dark';
|
|
29
|
+
const label = dark
|
|
30
|
+
? labels?.toLight ?? 'Switch to light mode'
|
|
31
|
+
: labels?.toDark ?? 'Switch to dark mode';
|
|
32
|
+
return _jsx("button", { type: "button", "data-theme-toggle": true, "data-scheme": scheme, "aria-label": label, title: label, onClick: () => setChoice(dark ? 'light' : 'dark'), className: cn(TOGGLE, className), style: style, children: dark ? _jsx(Moon, { className: "size-5", "aria-hidden": "true" }) : _jsx(Sun, { className: "size-5", "aria-hidden": "true" }) });
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=toggle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toggle.js","sourceRoot":"","sources":["../../../src/components/scheme/toggle.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EAAE,EAAE,EAAE,MAAM,sBAAsB,CAAA;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAG1C;;;;GAIG;AACH,MAAM,MAAM,GAAG;IACb,sFAAsF;IACtF,8EAA8E;IAC9E,2FAA2F;IAC3F,sCAAsC;CACvC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAEX;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,WAAW,GAAyB,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE;IAChF,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,cAAc,EAAE,CAAA;IAC9C,MAAM,IAAI,GAAG,MAAM,KAAK,MAAM,CAAA;IAC9B,MAAM,KAAK,GAAG,IAAI;QAChB,CAAC,CAAC,MAAM,EAAE,OAAO,IAAI,sBAAsB;QAC3C,CAAC,CAAC,MAAM,EAAE,MAAM,IAAI,qBAAqB,CAAA;IAE3C,OAAO,iBACL,IAAI,EAAC,QAAQ,4CAAgC,MAAM,gBAAc,KAAK,EAAE,KAAK,EAAE,KAAK,EACpF,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,EACjD,SAAS,EAAE,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,YAE7C,IAAI,CAAC,CAAC,CAAC,KAAC,IAAI,IAAC,SAAS,EAAC,QAAQ,iBAAa,MAAM,GAAG,CAAC,CAAC,CAAC,KAAC,GAAG,IAAC,SAAS,EAAC,QAAQ,iBAAa,MAAM,GAAG,GAC/F,CAAA;AACX,CAAC,CAAA"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ColorSchemeChoice } from '../../scheme/scheme.js';
|
|
2
|
+
import type { StyledProps } from '../types.js';
|
|
3
|
+
export interface ColorSchemeModel {
|
|
4
|
+
/** What the page is showing: the stored choice, or the operating system's preference. */
|
|
5
|
+
scheme: 'light' | 'dark';
|
|
6
|
+
/** The visitor's explicit choice, or `null` while the page follows the operating system. */
|
|
7
|
+
choice: ColorSchemeChoice | null;
|
|
8
|
+
/** Choose — `null` clears the choice and hands the page back to the operating system. */
|
|
9
|
+
setChoice: (choice: ColorSchemeChoice | null) => void;
|
|
10
|
+
}
|
|
11
|
+
/** The toggle's accessible names — what pressing it DOES. English defaults when absent. */
|
|
12
|
+
export interface ThemeToggleLabels {
|
|
13
|
+
/** Shown while the page is dark. Default "Switch to light mode". */
|
|
14
|
+
toLight?: string;
|
|
15
|
+
/** Shown while the page is light. Default "Switch to dark mode". */
|
|
16
|
+
toDark?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ThemeToggleProps extends StyledProps {
|
|
19
|
+
labels?: ThemeToggleLabels;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=types.d.ts.map
|