@dataimago/ui 0.1.0-alpha.3 → 0.1.0-alpha.35
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 +19 -5
- package/dist/index.cjs +431 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +251 -9
- package/dist/index.d.ts +251 -9
- package/dist/index.js +422 -3
- package/dist/index.js.map +1 -1
- package/package.json +12 -9
package/README.md
CHANGED
|
@@ -2,27 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
React components for the dataimago ethical AI design system.
|
|
4
4
|
|
|
5
|
-
> **Registry:** published to **public npm** (`registry.npmjs.org`) under the `@dataimago` scope alongside
|
|
5
|
+
> **Registry:** published to **public npm** (`registry.npmjs.org`) under the `@dataimago` scope alongside the rest of the design-system packages. No scoped `.npmrc` or `GITHUB_PACKAGES_TOKEN` is required — a plain `pnpm install @dataimago/ui` works.
|
|
6
6
|
|
|
7
7
|
## Peer dependencies
|
|
8
8
|
|
|
9
9
|
- `react` ≥ 18
|
|
10
10
|
- `react-dom` ≥ 18
|
|
11
|
+
- `@dataimago/brand` ≥ 0.1.0-alpha.0
|
|
11
12
|
- `@dataimago/tokens` ≥ 0.1.0-alpha.0
|
|
12
13
|
- `@dataimago/css` ≥ 0.1.0-alpha.0 (visual styling consumes the semantic CSS variables emitted by this package)
|
|
13
14
|
|
|
14
|
-
## Alpha scope
|
|
15
|
+
## Alpha scope and exports
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
Current public exports:
|
|
18
|
+
|
|
19
|
+
- `<Button>` — smoke-level button using semantic CSS variables.
|
|
20
|
+
- `<LogoMonogram>`, `<LogoMonogramBg>`, `<LogoWordmark>` — legacy outlined SVG brand marks.
|
|
21
|
+
- `<Logo>` — manifest-driven mark renderer backed by `@dataimago/brand`.
|
|
22
|
+
- `<BrandAnimation>` — decorative brand animation with reduced-motion poster fallback.
|
|
23
|
+
- `<NavbarLogo>` — animated navbar mark with reduced-motion poster fallback.
|
|
17
24
|
|
|
18
25
|
```tsx
|
|
19
|
-
import { Button } from '@dataimago/ui';
|
|
26
|
+
import { BrandAnimation, Button, Logo, NavbarLogo } from '@dataimago/ui';
|
|
20
27
|
|
|
21
28
|
<Button variant="primary">Generate</Button>
|
|
22
29
|
<Button variant="secondary" size="sm">Cancel</Button>
|
|
30
|
+
<Logo name="dataimago_dash_ai" variant="PATH" title="dataimago-ai" />
|
|
31
|
+
<NavbarLogo name="dataimago-ai" />
|
|
32
|
+
<BrandAnimation name="dataimago-ai" />
|
|
23
33
|
```
|
|
24
34
|
|
|
25
|
-
Additional components get harvested from consumer prototypes (`dataimago-ai`, `dissertation
|
|
35
|
+
Additional components get harvested from consumer prototypes (`dataimago-ai`, `dissertation-ai`, etc.) via the prototype-in-consumer workflow. Broad new primitives are deferred until the substrate is stable and L1/L2 usage identifies reusable candidates.
|
|
26
36
|
|
|
27
37
|
## Building
|
|
28
38
|
|
|
@@ -46,3 +56,7 @@ use semantic CSS variables rather than app-local Tailwind classes. When the dev
|
|
|
46
56
|
server is running, the Storybook MCP addon is available at
|
|
47
57
|
`http://localhost:6006/mcp` for AI-assisted component discovery and story
|
|
48
58
|
testing.
|
|
59
|
+
|
|
60
|
+
Accessibility checks are progressively hardened: current stories surface a11y
|
|
61
|
+
findings for review, and the gate should move to blocking only after the existing
|
|
62
|
+
story set is clean.
|
package/dist/index.cjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
"use strict";
|
|
2
3
|
var __create = Object.create;
|
|
3
4
|
var __defProp = Object.defineProperty;
|
|
@@ -30,10 +31,25 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
31
|
// src/index.ts
|
|
31
32
|
var index_exports = {};
|
|
32
33
|
__export(index_exports, {
|
|
34
|
+
BrandAnimation: () => BrandAnimation,
|
|
33
35
|
Button: () => Button,
|
|
36
|
+
CopulaLockup: () => CopulaLockup,
|
|
37
|
+
Logo: () => Logo,
|
|
34
38
|
LogoMonogram: () => LogoMonogram,
|
|
35
39
|
LogoMonogramBg: () => LogoMonogramBg,
|
|
36
|
-
LogoWordmark: () => LogoWordmark
|
|
40
|
+
LogoWordmark: () => LogoWordmark,
|
|
41
|
+
NavbarLogo: () => NavbarLogo,
|
|
42
|
+
THEME_ATTRIBUTE: () => THEME_ATTRIBUTE,
|
|
43
|
+
THEME_STORAGE_KEY: () => THEME_STORAGE_KEY,
|
|
44
|
+
ThemeScript: () => ThemeScript,
|
|
45
|
+
ThemeToggle: () => ThemeToggle,
|
|
46
|
+
nextThemePreference: () => nextThemePreference,
|
|
47
|
+
readThemePreference: () => readThemePreference,
|
|
48
|
+
resolveTheme: () => resolveTheme,
|
|
49
|
+
setThemePreference: () => setThemePreference,
|
|
50
|
+
themeInitScript: () => themeInitScript,
|
|
51
|
+
useResolvedTheme: () => useResolvedTheme,
|
|
52
|
+
useThemePreference: () => useThemePreference
|
|
37
53
|
});
|
|
38
54
|
module.exports = __toCommonJS(index_exports);
|
|
39
55
|
|
|
@@ -180,7 +196,7 @@ function LogoMonogramBg({
|
|
|
180
196
|
height: "100%",
|
|
181
197
|
rx: "40",
|
|
182
198
|
ry: "40",
|
|
183
|
-
fill: "var(--color-interactive-accent, #
|
|
199
|
+
fill: "var(--color-interactive-accent, #1f618d)"
|
|
184
200
|
}
|
|
185
201
|
),
|
|
186
202
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
@@ -228,7 +244,7 @@ function LogoWordmark({
|
|
|
228
244
|
height: "100%",
|
|
229
245
|
rx: "5",
|
|
230
246
|
ry: "5",
|
|
231
|
-
fill: "var(--color-interactive-accent, #
|
|
247
|
+
fill: "var(--color-interactive-accent, #1f618d)"
|
|
232
248
|
}
|
|
233
249
|
),
|
|
234
250
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
@@ -249,11 +265,422 @@ function LogoWordmark({
|
|
|
249
265
|
}
|
|
250
266
|
);
|
|
251
267
|
}
|
|
268
|
+
|
|
269
|
+
// src/brand/Logo.tsx
|
|
270
|
+
var import_brand = require("@dataimago/brand");
|
|
271
|
+
|
|
272
|
+
// src/brand/decorative.ts
|
|
273
|
+
function silenceSvg(markup) {
|
|
274
|
+
return markup.replace(/<title\b[^>]*>[\s\S]*?<\/title>/gi, "").replace(/<svg\b([^>]*)>/i, (_m, attrs) => {
|
|
275
|
+
const kept = attrs.replace(/\srole="[^"]*"/i, "").replace(/\saria-label="[^"]*"/i, "").replace(/\saria-labelledby="[^"]*"/i, "");
|
|
276
|
+
return `<svg${kept} aria-hidden="true" focusable="false">`;
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
function isDecorative(ariaHidden) {
|
|
280
|
+
return ariaHidden === true || ariaHidden === "true";
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// src/brand/Logo.tsx
|
|
284
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
285
|
+
function Logo({ name, variant = "PATH", width, style, ...rest }) {
|
|
286
|
+
const key = `${name}_${variant}`;
|
|
287
|
+
let markup = import_brand.svgs[key];
|
|
288
|
+
if (!markup) {
|
|
289
|
+
console.warn(`[@dataimago/ui] <Logo>: unknown mark "${key}".`);
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
if (width != null) {
|
|
293
|
+
markup = markup.replace(/\swidth="[^"]*"/, ` width="${width}"`).replace(/\sheight="[^"]*"/, "");
|
|
294
|
+
}
|
|
295
|
+
if (isDecorative(rest["aria-hidden"])) markup = silenceSvg(markup);
|
|
296
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
297
|
+
"span",
|
|
298
|
+
{
|
|
299
|
+
style: { display: "inline-flex", ...style },
|
|
300
|
+
dangerouslySetInnerHTML: { __html: markup },
|
|
301
|
+
...rest
|
|
302
|
+
}
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// src/brand/CopulaLockup.tsx
|
|
307
|
+
var import_brand2 = require("@dataimago/brand");
|
|
308
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
309
|
+
var BADGE = 16 / 32;
|
|
310
|
+
var GAP = 19.7 / 32;
|
|
311
|
+
var CROP = 10 / 32;
|
|
312
|
+
var VISUALLY_HIDDEN = {
|
|
313
|
+
border: 0,
|
|
314
|
+
clip: "rect(0 0 0 0)",
|
|
315
|
+
height: 1,
|
|
316
|
+
margin: -1,
|
|
317
|
+
overflow: "hidden",
|
|
318
|
+
padding: 0,
|
|
319
|
+
position: "absolute",
|
|
320
|
+
whiteSpace: "nowrap",
|
|
321
|
+
width: 1
|
|
322
|
+
};
|
|
323
|
+
var round = (n) => Math.round(n * 100) / 100;
|
|
324
|
+
function pathEntry(id) {
|
|
325
|
+
return import_brand2.manifest.find((e) => e.id === id && e.variant === "PATH");
|
|
326
|
+
}
|
|
327
|
+
var plainLink = ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("a", { ...props, children });
|
|
328
|
+
function CopulaLockup({
|
|
329
|
+
property,
|
|
330
|
+
propertyLabel,
|
|
331
|
+
propertyHref = "/",
|
|
332
|
+
dataimago = "shown",
|
|
333
|
+
brandHref = "https://dataimago.ai",
|
|
334
|
+
height = 32,
|
|
335
|
+
linkClassName,
|
|
336
|
+
badgeClassName,
|
|
337
|
+
renderLink = plainLink,
|
|
338
|
+
style,
|
|
339
|
+
...rest
|
|
340
|
+
}) {
|
|
341
|
+
const prop = pathEntry(property);
|
|
342
|
+
const brand = pathEntry("dataimago");
|
|
343
|
+
if (!prop || !brand) {
|
|
344
|
+
console.warn(`[@dataimago/ui] <CopulaLockup>: unknown mark "${property}".`);
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
const shown = dataimago === "shown";
|
|
348
|
+
const badge = BADGE * height;
|
|
349
|
+
const reach = GAP * height - badge / 2;
|
|
350
|
+
const crop = -CROP * height;
|
|
351
|
+
const width = (e) => round(e.w * height / e.h);
|
|
352
|
+
const trailing = (prop.w - prop.ink.x2) * height / prop.h;
|
|
353
|
+
const leading = brand.ink.x1 * height / brand.h;
|
|
354
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
355
|
+
"span",
|
|
356
|
+
{
|
|
357
|
+
style: { alignItems: "center", display: "inline-flex", flexWrap: "wrap", rowGap: 4, ...style },
|
|
358
|
+
...rest,
|
|
359
|
+
children: [
|
|
360
|
+
renderLink({
|
|
361
|
+
href: propertyHref,
|
|
362
|
+
"aria-label": propertyLabel,
|
|
363
|
+
className: linkClassName,
|
|
364
|
+
// The crop only centres the cap-height band on the badge; alone (hidden),
|
|
365
|
+
// the mark keeps its full box so nothing next to it can overlap it.
|
|
366
|
+
style: {
|
|
367
|
+
display: "inline-flex",
|
|
368
|
+
marginBlock: shown ? crop : 0,
|
|
369
|
+
marginRight: shown ? round(reach - trailing) : 0
|
|
370
|
+
},
|
|
371
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Logo, { name: property, "aria-hidden": true, width: width(prop) })
|
|
372
|
+
}),
|
|
373
|
+
shown ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
374
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: badgeClassName, style: { display: "inline-flex" }, children: [
|
|
375
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Logo, { name: "is_badge", variant: "CUTOUT", "aria-hidden": true, width: round(badge) }),
|
|
376
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: VISUALLY_HIDDEN, children: "is" })
|
|
377
|
+
] }),
|
|
378
|
+
renderLink({
|
|
379
|
+
href: brandHref,
|
|
380
|
+
"aria-label": "dataimago",
|
|
381
|
+
className: linkClassName,
|
|
382
|
+
style: { display: "inline-flex", marginBlock: crop, marginLeft: round(reach - leading) },
|
|
383
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Logo, { name: "dataimago", "aria-hidden": true, width: width(brand) })
|
|
384
|
+
})
|
|
385
|
+
] }) : null
|
|
386
|
+
]
|
|
387
|
+
}
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// src/brand/BrandAnimation.tsx
|
|
392
|
+
var React2 = __toESM(require("react"), 1);
|
|
393
|
+
var import_brand3 = require("@dataimago/brand");
|
|
394
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
395
|
+
function usePrefersReducedMotion() {
|
|
396
|
+
const [reduced, setReduced] = React2.useState(true);
|
|
397
|
+
React2.useEffect(() => {
|
|
398
|
+
const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
399
|
+
setReduced(mq.matches);
|
|
400
|
+
const onChange = (e) => setReduced(e.matches);
|
|
401
|
+
mq.addEventListener("change", onChange);
|
|
402
|
+
return () => mq.removeEventListener("change", onChange);
|
|
403
|
+
}, []);
|
|
404
|
+
return reduced;
|
|
405
|
+
}
|
|
406
|
+
function BrandAnimation({
|
|
407
|
+
name,
|
|
408
|
+
width,
|
|
409
|
+
style,
|
|
410
|
+
...rest
|
|
411
|
+
}) {
|
|
412
|
+
const reduced = usePrefersReducedMotion();
|
|
413
|
+
const entry = import_brand3.animationManifest.find((a) => a.id === name);
|
|
414
|
+
let markup = import_brand3.animations[name];
|
|
415
|
+
if (!entry || !markup) {
|
|
416
|
+
console.warn(`[@dataimago/ui] <BrandAnimation>: unknown animation "${name}".`);
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
if (reduced) {
|
|
420
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
421
|
+
Logo,
|
|
422
|
+
{
|
|
423
|
+
name: entry.poster.id,
|
|
424
|
+
variant: entry.poster.variant,
|
|
425
|
+
width,
|
|
426
|
+
style,
|
|
427
|
+
...rest
|
|
428
|
+
}
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
if (width != null) {
|
|
432
|
+
markup = markup.replace(/\swidth="[^"]*"/, ` width="${width}"`).replace(/\sheight="[^"]*"/, "");
|
|
433
|
+
}
|
|
434
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
435
|
+
"span",
|
|
436
|
+
{
|
|
437
|
+
style: { display: "inline-flex", ...style },
|
|
438
|
+
dangerouslySetInnerHTML: { __html: markup },
|
|
439
|
+
...rest
|
|
440
|
+
}
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// src/brand/NavbarLogo.tsx
|
|
445
|
+
var React3 = __toESM(require("react"), 1);
|
|
446
|
+
var import_brand4 = require("@dataimago/brand");
|
|
447
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
448
|
+
function usePrefersReducedMotion2() {
|
|
449
|
+
const [reduced, setReduced] = React3.useState(true);
|
|
450
|
+
React3.useEffect(() => {
|
|
451
|
+
const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
452
|
+
setReduced(mq.matches);
|
|
453
|
+
const onChange = (e) => setReduced(e.matches);
|
|
454
|
+
mq.addEventListener("change", onChange);
|
|
455
|
+
return () => mq.removeEventListener("change", onChange);
|
|
456
|
+
}, []);
|
|
457
|
+
return reduced;
|
|
458
|
+
}
|
|
459
|
+
function NavbarLogo({ name, width, style, ...rest }) {
|
|
460
|
+
const reduced = usePrefersReducedMotion2();
|
|
461
|
+
const entry = import_brand4.navbarManifest.find((n) => n.id === name);
|
|
462
|
+
let markup = import_brand4.navbarLogos[name];
|
|
463
|
+
if (!entry || !markup) {
|
|
464
|
+
console.warn(`[@dataimago/ui] <NavbarLogo>: unknown navbar mark "${name}".`);
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
if (reduced) {
|
|
468
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
469
|
+
Logo,
|
|
470
|
+
{
|
|
471
|
+
name: entry.poster.id,
|
|
472
|
+
variant: entry.poster.variant,
|
|
473
|
+
width,
|
|
474
|
+
style,
|
|
475
|
+
...rest
|
|
476
|
+
}
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
if (width != null) {
|
|
480
|
+
markup = markup.replace(/\swidth="[^"]*"/, ` width="${width}"`).replace(/\sheight="[^"]*"/, "");
|
|
481
|
+
}
|
|
482
|
+
if (isDecorative(rest["aria-hidden"])) markup = silenceSvg(markup);
|
|
483
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
484
|
+
"span",
|
|
485
|
+
{
|
|
486
|
+
style: { display: "inline-flex", ...style },
|
|
487
|
+
dangerouslySetInnerHTML: { __html: markup },
|
|
488
|
+
...rest
|
|
489
|
+
}
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// src/theme/ThemeToggle.tsx
|
|
494
|
+
var React5 = __toESM(require("react"), 1);
|
|
495
|
+
|
|
496
|
+
// src/theme/theme.ts
|
|
497
|
+
var React4 = __toESM(require("react"), 1);
|
|
498
|
+
var THEME_STORAGE_KEY = "dataimago-theme";
|
|
499
|
+
var THEME_ATTRIBUTE = "data-bs-theme";
|
|
500
|
+
var CHANGE_EVENT = "dataimago:themechange";
|
|
501
|
+
var themeInitScript = `(function(){try{var p=localStorage.getItem(${JSON.stringify(THEME_STORAGE_KEY)});if(p==='light'||p==='dark'){document.documentElement.setAttribute(${JSON.stringify(THEME_ATTRIBUTE)},p);}}catch(e){}})();`;
|
|
502
|
+
function isPreference(value) {
|
|
503
|
+
return value === "system" || value === "light" || value === "dark";
|
|
504
|
+
}
|
|
505
|
+
function readThemePreference() {
|
|
506
|
+
if (typeof window === "undefined") return "system";
|
|
507
|
+
try {
|
|
508
|
+
const stored = window.localStorage.getItem(THEME_STORAGE_KEY);
|
|
509
|
+
return isPreference(stored) ? stored : "system";
|
|
510
|
+
} catch {
|
|
511
|
+
return "system";
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
function applyAttribute(preference) {
|
|
515
|
+
const root = document.documentElement;
|
|
516
|
+
if (preference === "system") root.removeAttribute(THEME_ATTRIBUTE);
|
|
517
|
+
else root.setAttribute(THEME_ATTRIBUTE, preference);
|
|
518
|
+
}
|
|
519
|
+
function setThemePreference(preference) {
|
|
520
|
+
if (typeof window === "undefined") return;
|
|
521
|
+
try {
|
|
522
|
+
if (preference === "system") window.localStorage.removeItem(THEME_STORAGE_KEY);
|
|
523
|
+
else window.localStorage.setItem(THEME_STORAGE_KEY, preference);
|
|
524
|
+
} catch {
|
|
525
|
+
}
|
|
526
|
+
applyAttribute(preference);
|
|
527
|
+
window.dispatchEvent(new Event(CHANGE_EVENT));
|
|
528
|
+
}
|
|
529
|
+
function resolveTheme() {
|
|
530
|
+
if (typeof document === "undefined") return "light";
|
|
531
|
+
const forced = document.documentElement.getAttribute(THEME_ATTRIBUTE);
|
|
532
|
+
if (forced === "light" || forced === "dark") return forced;
|
|
533
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
534
|
+
}
|
|
535
|
+
function syncAttribute() {
|
|
536
|
+
applyAttribute(readThemePreference());
|
|
537
|
+
}
|
|
538
|
+
function subscribePreference(onChange) {
|
|
539
|
+
syncAttribute();
|
|
540
|
+
const onStorage = (e) => {
|
|
541
|
+
if (e.key !== null && e.key !== THEME_STORAGE_KEY) return;
|
|
542
|
+
applyAttribute(readThemePreference());
|
|
543
|
+
onChange();
|
|
544
|
+
};
|
|
545
|
+
window.addEventListener(CHANGE_EVENT, onChange);
|
|
546
|
+
window.addEventListener("storage", onStorage);
|
|
547
|
+
return () => {
|
|
548
|
+
window.removeEventListener(CHANGE_EVENT, onChange);
|
|
549
|
+
window.removeEventListener("storage", onStorage);
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
function subscribeResolved(onChange) {
|
|
553
|
+
const observer = new MutationObserver(onChange);
|
|
554
|
+
observer.observe(document.documentElement, {
|
|
555
|
+
attributes: true,
|
|
556
|
+
attributeFilter: [THEME_ATTRIBUTE]
|
|
557
|
+
});
|
|
558
|
+
const media = window.matchMedia("(prefers-color-scheme: dark)");
|
|
559
|
+
media.addEventListener("change", onChange);
|
|
560
|
+
window.addEventListener(CHANGE_EVENT, onChange);
|
|
561
|
+
syncAttribute();
|
|
562
|
+
return () => {
|
|
563
|
+
observer.disconnect();
|
|
564
|
+
media.removeEventListener("change", onChange);
|
|
565
|
+
window.removeEventListener(CHANGE_EVENT, onChange);
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
function useThemePreference() {
|
|
569
|
+
const preference = React4.useSyncExternalStore(
|
|
570
|
+
subscribePreference,
|
|
571
|
+
readThemePreference,
|
|
572
|
+
() => "system"
|
|
573
|
+
);
|
|
574
|
+
return [preference, setThemePreference];
|
|
575
|
+
}
|
|
576
|
+
function useResolvedTheme() {
|
|
577
|
+
return React4.useSyncExternalStore(subscribeResolved, resolveTheme, () => "light");
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// src/theme/ThemeToggle.tsx
|
|
581
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
582
|
+
var DEFAULT_LABELS = {
|
|
583
|
+
name: "Theme",
|
|
584
|
+
system: "System",
|
|
585
|
+
light: "Light",
|
|
586
|
+
dark: "Dark"
|
|
587
|
+
};
|
|
588
|
+
function systemTheme() {
|
|
589
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
590
|
+
}
|
|
591
|
+
function nextThemePreference(shown, system) {
|
|
592
|
+
const next = shown === "dark" ? "light" : "dark";
|
|
593
|
+
return next === system ? "system" : next;
|
|
594
|
+
}
|
|
595
|
+
var noSubscribe = () => () => {
|
|
596
|
+
};
|
|
597
|
+
function useHydrated() {
|
|
598
|
+
return React5.useSyncExternalStore(noSubscribe, () => true, () => false);
|
|
599
|
+
}
|
|
600
|
+
function Icon({ theme }) {
|
|
601
|
+
const common = {
|
|
602
|
+
width: 20,
|
|
603
|
+
height: 20,
|
|
604
|
+
viewBox: "0 0 24 24",
|
|
605
|
+
fill: "none",
|
|
606
|
+
stroke: "currentColor",
|
|
607
|
+
strokeWidth: 1.75,
|
|
608
|
+
strokeLinecap: "round",
|
|
609
|
+
strokeLinejoin: "round",
|
|
610
|
+
"aria-hidden": true,
|
|
611
|
+
focusable: false
|
|
612
|
+
};
|
|
613
|
+
if (theme === "dark") {
|
|
614
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("svg", { ...common, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" }) });
|
|
615
|
+
}
|
|
616
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("svg", { ...common, children: [
|
|
617
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("circle", { cx: "12", cy: "12", r: "4" }),
|
|
618
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41" })
|
|
619
|
+
] });
|
|
620
|
+
}
|
|
621
|
+
function ThemeToggle({ labels, className = "", style, ...rest }) {
|
|
622
|
+
const [preference, setPreference] = useThemePreference();
|
|
623
|
+
const resolved = useResolvedTheme();
|
|
624
|
+
const hydrated = useHydrated();
|
|
625
|
+
const shown = hydrated ? resolved : void 0;
|
|
626
|
+
const text = { ...DEFAULT_LABELS, ...labels };
|
|
627
|
+
const label = shown ? `${text.name}: ${text[shown]}${preference === "system" ? ` (${text.system})` : ""}` : text.name;
|
|
628
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
629
|
+
"button",
|
|
630
|
+
{
|
|
631
|
+
...rest,
|
|
632
|
+
className: ["di-ui-theme-toggle", className].filter(Boolean).join(" "),
|
|
633
|
+
"aria-label": label,
|
|
634
|
+
title: label,
|
|
635
|
+
"data-theme-preference": preference,
|
|
636
|
+
"data-theme": shown,
|
|
637
|
+
style: {
|
|
638
|
+
alignItems: "center",
|
|
639
|
+
background: "var(--di-theme-toggle-bg, transparent)",
|
|
640
|
+
border: 0,
|
|
641
|
+
borderRadius: "var(--radius-md, 0.5rem)",
|
|
642
|
+
color: "var(--di-theme-toggle-fg, inherit)",
|
|
643
|
+
cursor: "pointer",
|
|
644
|
+
display: "inline-flex",
|
|
645
|
+
height: "48px",
|
|
646
|
+
justifyContent: "center",
|
|
647
|
+
padding: 0,
|
|
648
|
+
transition: "background-color 150ms ease",
|
|
649
|
+
width: "48px",
|
|
650
|
+
...style
|
|
651
|
+
},
|
|
652
|
+
type: "button",
|
|
653
|
+
onClick: () => setPreference(nextThemePreference(resolveTheme(), systemTheme())),
|
|
654
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { display: "inline-flex", visibility: shown ? "visible" : "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Icon, { theme: shown ?? "light" }) })
|
|
655
|
+
}
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// src/theme/ThemeScript.tsx
|
|
660
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
661
|
+
function ThemeScript({ nonce }) {
|
|
662
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("script", { nonce, dangerouslySetInnerHTML: { __html: themeInitScript } });
|
|
663
|
+
}
|
|
252
664
|
// Annotate the CommonJS export names for ESM import in node:
|
|
253
665
|
0 && (module.exports = {
|
|
666
|
+
BrandAnimation,
|
|
254
667
|
Button,
|
|
668
|
+
CopulaLockup,
|
|
669
|
+
Logo,
|
|
255
670
|
LogoMonogram,
|
|
256
671
|
LogoMonogramBg,
|
|
257
|
-
LogoWordmark
|
|
672
|
+
LogoWordmark,
|
|
673
|
+
NavbarLogo,
|
|
674
|
+
THEME_ATTRIBUTE,
|
|
675
|
+
THEME_STORAGE_KEY,
|
|
676
|
+
ThemeScript,
|
|
677
|
+
ThemeToggle,
|
|
678
|
+
nextThemePreference,
|
|
679
|
+
readThemePreference,
|
|
680
|
+
resolveTheme,
|
|
681
|
+
setThemePreference,
|
|
682
|
+
themeInitScript,
|
|
683
|
+
useResolvedTheme,
|
|
684
|
+
useThemePreference
|
|
258
685
|
});
|
|
259
686
|
//# sourceMappingURL=index.cjs.map
|