eidotter 0.40.2 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -5
- package/dist/components/Accordion/components/AccordionFill.js +1 -1
- package/dist/components/Accordion/components/Section.js +1 -1
- package/dist/components/Alert/components/Alert.js +3 -2
- package/dist/components/Brand/components/Logo.d.ts +7 -7
- package/dist/components/Brand/components/Logo.js +10 -15
- package/dist/components/Brand/components/LogoGeometry.d.ts +28 -0
- package/dist/components/Brand/components/LogoGeometry.js +78 -0
- package/dist/components/Brand/components/Mark.d.ts +13 -6
- package/dist/components/Brand/components/Mark.js +14 -10
- package/dist/components/Brand/components/Wordmark.js +1 -1
- package/dist/components/Brand/components/markGeometry.d.ts +15 -0
- package/dist/components/Brand/components/markGeometry.js +20 -7
- package/dist/components/Breadcrumb/components/Breadcrumb.js +2 -2
- package/dist/components/Button/components/Button.js +1 -1
- package/dist/components/Card/components/Card.d.ts +5 -0
- package/dist/components/Card/components/Card.js +8 -2
- package/dist/components/CmdPalette/components/CmdPalette.d.ts +4 -3
- package/dist/components/CmdPalette/components/CmdPalette.js +17 -12
- package/dist/components/DecayStrip/components/DecayStrip.js +6 -1
- package/dist/components/DosDialog/components/DosDialog.d.ts +77 -0
- package/dist/components/DosDialog/components/DosDialog.js +59 -0
- package/dist/components/DosDialog/components/index.d.ts +2 -0
- package/dist/components/DosDialog/components/index.js +1 -0
- package/dist/components/DosDialog/index.d.ts +2 -0
- package/dist/components/DosDialog/index.js +1 -0
- package/dist/components/DosFigure/components/DosFigure.d.ts +2 -2
- package/dist/components/DosFigure/components/DosFigure.js +14 -7
- package/dist/components/DosWindow/components/DosWindow.d.ts +39 -0
- package/dist/components/DosWindow/components/DosWindow.js +23 -0
- package/dist/components/DosWindow/components/index.d.ts +2 -0
- package/dist/components/DosWindow/components/index.js +1 -0
- package/dist/components/DosWindow/index.d.ts +2 -0
- package/dist/components/DosWindow/index.js +1 -0
- package/dist/components/FilterBar/components/FilterBar.d.ts +7 -4
- package/dist/components/Footer/components/Footer.js +1 -1
- package/dist/components/Gauge/components/Gauge.js +19 -2
- package/dist/components/Header/components/Header.d.ts +29 -1
- package/dist/components/Header/components/Header.js +20 -5
- package/dist/components/Icon/components/Icon.d.ts +2 -0
- package/dist/components/Icon/components/Icon.js +5 -1
- package/dist/components/IconButton/components/IconButton.d.ts +44 -0
- package/dist/components/IconButton/components/IconButton.js +19 -0
- package/dist/components/IconButton/components/index.d.ts +2 -0
- package/dist/components/IconButton/components/index.js +1 -0
- package/dist/components/IconButton/index.d.ts +2 -0
- package/dist/components/IconButton/index.js +1 -0
- package/dist/components/Input/components/Input.js +1 -1
- package/dist/components/Lightbox/components/Lightbox.d.ts +10 -4
- package/dist/components/Lightbox/components/Lightbox.js +19 -11
- package/dist/components/Meter/components/Meter.js +7 -3
- package/dist/components/Meter/components/MeterGroup.js +3 -3
- package/dist/components/Modal/components/Modal.d.ts +18 -6
- package/dist/components/Modal/components/Modal.js +18 -9
- package/dist/components/Nav/components/Nav.js +5 -4
- package/dist/components/Notification/components/Notification.js +3 -2
- package/dist/components/Progress/components/Progress.js +8 -6
- package/dist/components/SeriesBars/components/SeriesBars.js +9 -8
- package/dist/components/Tag/components/Tag.d.ts +8 -1
- package/dist/components/Tag/components/Tag.js +5 -5
- package/dist/components/VitalsStrip/components/VitalsStrip.js +10 -7
- package/dist/components/registry.js +221 -19
- package/dist/eidotter.css +1 -1
- package/dist/hooks/useAnimatedDismiss.js +5 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.es.js +2174 -1600
- package/dist/index.umd.js +1 -1
- package/llms.txt +15 -7
- package/migrations/0.41.0.md +881 -0
- package/migrations/README.md +46 -0
- package/package.json +10 -2
- package/public/favicon.svg +1 -1
- package/src/styles/dos-utilities.css +6 -1
- package/src/styles/shadcn.css +52 -17
- package/src/styles/theme.amber-mono.css +4 -2
- package/src/styles/theme.amber-paper.css +106 -0
- package/src/styles/theme.cga-amber.css +3 -1
- package/src/styles/theme.cga-mode4-p0.css +3 -1
- package/src/styles/theme.cga-mode4-p1.css +3 -1
- package/src/styles/theme.cga-mode5.css +3 -1
- package/src/styles/theme.light.css +3 -1
- package/src/styles/tokens.css +5 -1
- package/tailwind.preset.cjs +23 -1
- package/dist/components/Brand/components/markGeometry.test.d.ts +0 -1
- package/dist/components/Connector/components/connectorGeometry.test.d.ts +0 -1
- package/dist/components/DecayStrip/components/resample.test.d.ts +0 -1
- package/dist/components/TimelineContainer/components/timelineUtils.test.d.ts +0 -1
- package/dist/components/TimelineContainer/components/useDrillDown.test.d.ts +0 -1
- package/dist/components/TimelineContainer/components/useSelection.test.d.ts +0 -1
- package/dist/components/TimelineContainer/components/useZoom.test.d.ts +0 -1
- package/dist/hooks/useTextScramble.test.d.ts +0 -1
- package/dist/hooks/useThemePortal.test.d.ts +0 -1
- package/dist/utils/health/formatValue.test.d.ts +0 -1
- package/dist/utils/health/healthPatterns.test.d.ts +0 -1
- package/dist/utils/health/resolveBand.test.d.ts +0 -1
- package/dist/utils/isSafeHref.test.d.ts +0 -1
- package/dist/utils/isSafeUrl.test.d.ts +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { forwardRef, useCallback, useEffect, useId, useMemo, useRef, useState, } from 'react';
|
|
4
|
-
import {
|
|
4
|
+
import { DosDialog } from '../../DosDialog/components/DosDialog.js';
|
|
5
5
|
import { cn } from '../../../utils/cn.js';
|
|
6
6
|
const defaultFooterHint = (_jsx(_Fragment, { children: "\u2191/\u2193 NAVIGATE \u00B7 \u21B5 OPEN \u00B7 ESC CLOSE" }));
|
|
7
7
|
/** Split hotkey string like "mod+shift+p" into a predicate. */
|
|
@@ -50,9 +50,10 @@ function scoreItem(item, query) {
|
|
|
50
50
|
* Controlled component. Parent owns `open` and item list; CmdPalette handles
|
|
51
51
|
* search, keyboard navigation, focus management, and hotkey binding.
|
|
52
52
|
*
|
|
53
|
-
* Built on
|
|
54
|
-
*
|
|
55
|
-
*
|
|
53
|
+
* Built on the DosDialog shell (DMNC-1592), top placement, `lg` width: the
|
|
54
|
+
* shell supplies focus trap, Esc-to-close, backdrop dismissal, chrome, motion
|
|
55
|
+
* and the amber `▸ JUMP TO` strip (decorative; the dialog is named by
|
|
56
|
+
* `aria-label`).
|
|
56
57
|
*/
|
|
57
58
|
export const CmdPalette = forwardRef(({ open, onOpenChange, items, placeholder = 'SEARCH · TYPE · ▸', hotkey = 'mod+k', emptyMessage = 'NO MATCHES', footerHint = defaultFooterHint, renderItem, maxResults = 20, className, 'aria-label': ariaLabel = 'Command palette', }, ref) => {
|
|
58
59
|
const baseId = useId();
|
|
@@ -157,13 +158,17 @@ export const CmdPalette = forwardRef(({ open, onOpenChange, items, placeholder =
|
|
|
157
158
|
}
|
|
158
159
|
}
|
|
159
160
|
};
|
|
160
|
-
return (
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
161
|
+
return (_jsxs(DosDialog, { ref: ref, open: open, onOpenChange: onOpenChange, placement: "top", size: "lg", "aria-label": ariaLabel, className: cn('eidotter-cmdpal__container', className), head: {
|
|
162
|
+
title: 'JUMP TO',
|
|
163
|
+
end: _jsx("span", { className: "eidotter-cmdpal__blink", children: "_" }),
|
|
164
|
+
decorative: true,
|
|
165
|
+
}, footer: footerHint ? (_jsx("div", { className: "eidotter-cmdpal__footer", "aria-hidden": "true", children: footerHint })) : undefined, children: [_jsx("input", { ref: inputRef, type: "text", role: "combobox", "aria-controls": listboxId, "aria-expanded": true, "aria-autocomplete": "list", "aria-activedescendant": results[selected] ? `${baseId}-item-${results[selected].id}` : undefined, className: "eidotter-cmdpal__input", value: query, placeholder: placeholder, onChange: e => setQuery(e.target.value), onKeyDown: handleKeyDown }), _jsxs("ul", { ref: listRef, id: listboxId, role: "listbox", "aria-label": ariaLabel, className: "eidotter-cmdpal__results", children: [results.length === 0 && (_jsx("li", { className: "eidotter-cmdpal__empty", role: "presentation", children: emptyMessage })), results.map((item, i) => {
|
|
166
|
+
const isSelected = i === selected;
|
|
167
|
+
return (_jsx("li", { id: `${baseId}-item-${item.id}`, role: "option", "aria-selected": isSelected, "data-index": i, className: cn('eidotter-cmdpal__item', isSelected && 'eidotter-cmdpal__item--selected'), onMouseMove: () => {
|
|
168
|
+
if (suppressHoverRef.current)
|
|
169
|
+
return;
|
|
170
|
+
setSelected(i);
|
|
171
|
+
}, onClick: () => handleSelect(item), children: renderItem ? renderItem(item, isSelected) : (_jsxs(_Fragment, { children: [_jsxs("span", { className: cn('eidotter-cmdpal__item-label', isSelected && 'eidotter-cmdpal__text--bright'), children: [_jsx("span", { className: "eidotter-cmdpal__item-caret", "aria-hidden": "true", children: isSelected ? '▸' : ' ' }), item.label] }), item.hint !== undefined && (_jsx("span", { className: cn('eidotter-cmdpal__item-hint', isSelected ? 'eidotter-cmdpal__text--bright' : 'eidotter-cmdpal__text--muted'), children: item.hint }))] })) }, item.id));
|
|
172
|
+
})] })] }));
|
|
168
173
|
});
|
|
169
174
|
CmdPalette.displayName = 'CmdPalette';
|
|
@@ -88,7 +88,12 @@ export const DecayStrip = forwardRef(({ items, samples, now, window, provenance,
|
|
|
88
88
|
.filter(Boolean)
|
|
89
89
|
.join('; ');
|
|
90
90
|
const compact = size === 'sm';
|
|
91
|
-
return (_jsxs("div", { ref: ref, className:
|
|
91
|
+
return (_jsxs("div", { ref: ref, className: [
|
|
92
|
+
// The size class (a text-* font size) is joined outside cn(): twMerge
|
|
93
|
+
// reads it as a colour and would drop text-dos-text-brand.
|
|
94
|
+
cn('eidotter-decay-strip eidotter-health flex flex-col gap-2 w-full min-w-0 font-dos text-dos-text-brand', toneClasses[tone], className),
|
|
95
|
+
sizeClasses[size] ?? sizeClasses.md,
|
|
96
|
+
].join(' '), ...props, children: [items.length === 0 ? (_jsx(EmptyState, { title: `${zeroPercent} ${labels.remaining}`, className: "py-2" })) : (_jsx("ul", { className: cn('flex gap-2 min-w-0', compact ? 'flex-col' : 'flex-wrap'), children: rows.map((r) => (_jsx("li", { className: cn('eidotter-decay-strip__item min-w-0', compact ? 'flex flex-wrap items-baseline gap-2 tabular-nums' : 'flex-1'), children: compact ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "truncate text-dos-text-accent", title: r.item.label, children: r.item.label }), _jsx("span", { children: r.amountText }), _jsx("span", { children: r.shareText }), r.tagText && (_jsx("span", { className: "eidotter-decay-strip__item-provenance border border-dos-border-brand px-1", children: r.tagText }))] })) : (_jsx(Gauge, { label: r.item.label, value: r.share === null ? null : r.share * 100, secondary: r.item.amount === null ? undefined : r.amountText, size: "sm", tone: tone, provenance: r.item.provenance, locale: locale, labels: labelOverrides })) }, r.item.id))) })), _jsxs("div", { className: "eidotter-decay-strip__strip flex items-center gap-2 min-w-0 tabular-nums", role: "img", "aria-label": summary, children: [_jsx("span", { className: "eidotter-decay-strip__from shrink-0", children: fromText }), _jsx("span", { className: "eidotter-decay-strip__cells flex flex-1 min-w-0 overflow-hidden whitespace-nowrap leading-none", style: tone === 'neutral' ? undefined : { color: 'var(--eidotter-fill)' }, children: cells.map((cell, i) => {
|
|
92
97
|
const last = i === cells.length - 1;
|
|
93
98
|
const isNow = nowMs >= cell.start && (nowMs < cell.end || (last && nowMs === cell.end));
|
|
94
99
|
const future = !isNow && nowMs < cell.start && nowMs >= cells[0].start;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type DosDialogPlacement = 'center' | 'top';
|
|
3
|
+
export type DosDialogSize = 'sm' | 'md' | 'lg' | 'full';
|
|
4
|
+
/** Structured head for the title strip. */
|
|
5
|
+
export interface DosDialogHead {
|
|
6
|
+
/** Title text. Rendered as the dialog's `<h2>` unless `decorative` is set. */
|
|
7
|
+
title: React.ReactNode;
|
|
8
|
+
/** Trailing slot on the right of the strip: close button, status, controls. */
|
|
9
|
+
end?: React.ReactNode;
|
|
10
|
+
/** Show the leading `▸` marker. Defaults to `true`. */
|
|
11
|
+
marker?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Visual-only strip: hidden from assistive tech, and the title renders as a
|
|
14
|
+
* `<span>` instead of an `<h2>`. Use when the dialog is named by `aria-label`
|
|
15
|
+
* and the strip repeats nothing a screen reader needs (CmdPalette's
|
|
16
|
+
* `JUMP TO` banner). Do not put focusable controls in `end` when this is set.
|
|
17
|
+
*/
|
|
18
|
+
decorative?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface DosDialogProps {
|
|
21
|
+
/** Controlled open state. */
|
|
22
|
+
open: boolean;
|
|
23
|
+
/** Called when the dialog requests open/close (Escape, backdrop click). */
|
|
24
|
+
onOpenChange?: (open: boolean) => void;
|
|
25
|
+
/** Vertical anchor: `center` (default) or `top` (~18vh, CmdPalette's anchor). */
|
|
26
|
+
placement?: DosDialogPlacement;
|
|
27
|
+
/**
|
|
28
|
+
* Width step, shared by every dialog: `sm` 24rem, `md` 30rem (default),
|
|
29
|
+
* `lg` 40rem, `full` = viewport width minus 2 × spacing-4. Every step is
|
|
30
|
+
* capped at the viewport minus 32px.
|
|
31
|
+
*/
|
|
32
|
+
size?: DosDialogSize;
|
|
33
|
+
/**
|
|
34
|
+
* The title strip (dark, yellow title, dim-amber rule). Pass `{ title, end?, marker?, decorative? }` for the
|
|
35
|
+
* standard strip, or any ReactNode to fill the strip yourself. Omit for no strip.
|
|
36
|
+
*/
|
|
37
|
+
head?: React.ReactNode | DosDialogHead;
|
|
38
|
+
/** Footer row, separated from the body by a 1px rule. Rendered in a `<div>` (no contentinfo landmark). */
|
|
39
|
+
footer?: React.ReactNode;
|
|
40
|
+
/** Close on backdrop click / outside interaction. Escape always closes. Defaults to `true`. */
|
|
41
|
+
isDismissable?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Accessible name. When omitted, a non-decorative `head.title` names the dialog.
|
|
44
|
+
* A ReactNode head, a `decorative` head or no head names nothing: pass
|
|
45
|
+
* `aria-label` or `aria-labelledby` then, or the dialog has no accessible
|
|
46
|
+
* name (outside production builds, an open unnamed dialog logs a `console.warn`).
|
|
47
|
+
*/
|
|
48
|
+
'aria-label'?: string;
|
|
49
|
+
/** Id of the element that names the dialog. */
|
|
50
|
+
'aria-labelledby'?: string;
|
|
51
|
+
/** Extra classes merged onto the visible container (`.eidotter-dialog__container`). */
|
|
52
|
+
className?: string;
|
|
53
|
+
/** Dialog body. */
|
|
54
|
+
children?: React.ReactNode;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* DosDialog: the one overlay shell (DMNC-1592).
|
|
58
|
+
*
|
|
59
|
+
* The CmdPalette chrome, generalised: brand-coloured medium border with an
|
|
60
|
+
* amber phosphor glow, square corners, a blurred scrim, and a dark title
|
|
61
|
+
* strip (yellow title, `▸` marker, dim-amber rule below). Modal, Lightbox and
|
|
62
|
+
* CmdPalette are built on it.
|
|
63
|
+
*
|
|
64
|
+
* React Aria `ModalOverlay > Modal > Dialog` provides the focus trap, scroll
|
|
65
|
+
* lock, Escape and backdrop dismissal. The panel pops in/out with the shared
|
|
66
|
+
* `eidotter-pop-in/out` keyframes (scale 0.8 to 1, the CRT turn-on) and the
|
|
67
|
+
* scrim fades; under reduced motion the entering/exiting classes are not applied at all.
|
|
68
|
+
*
|
|
69
|
+
* Accessible name: a non-decorative structured `head.title` names the dialog;
|
|
70
|
+
* otherwise pass `aria-label` / `aria-labelledby`. Outside production builds
|
|
71
|
+
* an open dialog with no name logs a `console.warn`.
|
|
72
|
+
*
|
|
73
|
+
* The strip title is one line with an ellipsis by default; Modal lets it wrap.
|
|
74
|
+
*
|
|
75
|
+
* Ref forwards to the dialog element (`.eidotter-dialog__container`).
|
|
76
|
+
*/
|
|
77
|
+
export declare const DosDialog: React.ForwardRefExoticComponent<DosDialogProps & React.RefAttributes<HTMLElement>>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef, isValidElement, useEffect, useId } from 'react';
|
|
4
|
+
import { ModalOverlay as AriaModalOverlay, Modal as AriaModal, Dialog as AriaDialog, } from 'react-aria-components';
|
|
5
|
+
import { cn } from '../../../utils/cn.js';
|
|
6
|
+
import { prefersReducedMotion } from '../../../utils/prefersReducedMotion.js';
|
|
7
|
+
function isHeadObject(head) {
|
|
8
|
+
return (typeof head === 'object' &&
|
|
9
|
+
head !== null &&
|
|
10
|
+
!isValidElement(head) &&
|
|
11
|
+
!Array.isArray(head) &&
|
|
12
|
+
'title' in head);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* DosDialog: the one overlay shell (DMNC-1592).
|
|
16
|
+
*
|
|
17
|
+
* The CmdPalette chrome, generalised: brand-coloured medium border with an
|
|
18
|
+
* amber phosphor glow, square corners, a blurred scrim, and a dark title
|
|
19
|
+
* strip (yellow title, `▸` marker, dim-amber rule below). Modal, Lightbox and
|
|
20
|
+
* CmdPalette are built on it.
|
|
21
|
+
*
|
|
22
|
+
* React Aria `ModalOverlay > Modal > Dialog` provides the focus trap, scroll
|
|
23
|
+
* lock, Escape and backdrop dismissal. The panel pops in/out with the shared
|
|
24
|
+
* `eidotter-pop-in/out` keyframes (scale 0.8 to 1, the CRT turn-on) and the
|
|
25
|
+
* scrim fades; under reduced motion the entering/exiting classes are not applied at all.
|
|
26
|
+
*
|
|
27
|
+
* Accessible name: a non-decorative structured `head.title` names the dialog;
|
|
28
|
+
* otherwise pass `aria-label` / `aria-labelledby`. Outside production builds
|
|
29
|
+
* an open dialog with no name logs a `console.warn`.
|
|
30
|
+
*
|
|
31
|
+
* The strip title is one line with an ellipsis by default; Modal lets it wrap.
|
|
32
|
+
*
|
|
33
|
+
* Ref forwards to the dialog element (`.eidotter-dialog__container`).
|
|
34
|
+
*/
|
|
35
|
+
export const DosDialog = forwardRef(({ open, onOpenChange, placement = 'center', size = 'md', head, footer, isDismissable = true, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, className, children, }, ref) => {
|
|
36
|
+
const titleId = useId();
|
|
37
|
+
const reduced = prefersReducedMotion();
|
|
38
|
+
const structured = isHeadObject(head) ? head : null;
|
|
39
|
+
const labelledBy = ariaLabelledBy ??
|
|
40
|
+
(!ariaLabel && structured && !structured.decorative ? titleId : undefined);
|
|
41
|
+
const unnamed = open && !ariaLabel && !labelledBy;
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (unnamed && process.env.NODE_ENV !== 'production') {
|
|
44
|
+
console.warn('[eidotter] DosDialog has no accessible name. A ReactNode or decorative head ' +
|
|
45
|
+
'does not name the dialog: pass aria-label or aria-labelledby.');
|
|
46
|
+
}
|
|
47
|
+
}, [unnamed]);
|
|
48
|
+
let strip = null;
|
|
49
|
+
if (structured) {
|
|
50
|
+
const { title, end, marker = true, decorative = false } = structured;
|
|
51
|
+
const TitleTag = decorative ? 'span' : 'h2';
|
|
52
|
+
strip = (_jsxs("div", { className: "eidotter-dialog__head", "aria-hidden": decorative || undefined, children: [_jsxs("div", { className: "eidotter-dialog__lead", children: [marker && (_jsx("span", { className: "eidotter-dialog__marker", "aria-hidden": "true", children: "\u25B8" })), _jsx(TitleTag, { id: titleId, className: "eidotter-dialog__title", children: title })] }), end != null && end !== false && (_jsx("div", { className: "eidotter-dialog__end", children: end }))] }));
|
|
53
|
+
}
|
|
54
|
+
else if (head != null && head !== false) {
|
|
55
|
+
strip = _jsx("div", { className: "eidotter-dialog__head", children: head });
|
|
56
|
+
}
|
|
57
|
+
return (_jsx(AriaModalOverlay, { isOpen: open, onOpenChange: onOpenChange, isDismissable: isDismissable, className: ({ isEntering, isExiting }) => cn('eidotter-dialog__overlay', `eidotter-dialog__overlay--${placement}`, !reduced && isEntering && 'eidotter-dialog__overlay--entering', !reduced && isExiting && 'eidotter-dialog__overlay--exiting'), children: _jsx(AriaModal, { className: ({ isEntering, isExiting }) => cn('eidotter-dialog', `eidotter-dialog--${size}`, `eidotter-dialog--${placement}`, !reduced && isEntering && 'eidotter-dialog--entering', !reduced && isExiting && 'eidotter-dialog--exiting'), children: _jsxs(AriaDialog, { ref: ref, "aria-label": ariaLabel, "aria-labelledby": labelledBy, className: cn('eidotter-dialog__container outline-none', className), children: [strip, _jsx("div", { className: "eidotter-dialog__body", children: children }), footer != null && footer !== false && (_jsx("div", { className: "eidotter-dialog__footer", children: footer }))] }) }) }));
|
|
58
|
+
});
|
|
59
|
+
DosDialog.displayName = 'DosDialog';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DosDialog } from './DosDialog.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DosDialog } from './components/index.js';
|
|
@@ -30,8 +30,8 @@ export interface DosFigureProps {
|
|
|
30
30
|
*
|
|
31
31
|
* Sierra / LucasArts / demoscene title cards painted the screen with a
|
|
32
32
|
* limited palette, dithering, and annotated frames. DosFigure recreates
|
|
33
|
-
* that aesthetic:
|
|
34
|
-
* resolution tag. Use as a lead-in for blog articles or as a placeholder
|
|
33
|
+
* that aesthetic: the DosWindow chrome (title strip, brand border, drop
|
|
34
|
+
* shadow), scanline sweep, optional pins, and a resolution tag. Use as a lead-in for blog articles or as a placeholder
|
|
35
35
|
* where a photograph would otherwise go.
|
|
36
36
|
*
|
|
37
37
|
* Not intended as a replacement for semantic imagery — pass `aria-label`
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { forwardRef, useId } from 'react';
|
|
4
4
|
import { cn } from '../../../utils/cn.js';
|
|
5
|
+
import { DosWindow } from '../../DosWindow/components/DosWindow.js';
|
|
5
6
|
/**
|
|
6
7
|
* DosFigure — demoscene-style placeholder for media.
|
|
7
8
|
*
|
|
8
9
|
* Sierra / LucasArts / demoscene title cards painted the screen with a
|
|
9
10
|
* limited palette, dithering, and annotated frames. DosFigure recreates
|
|
10
|
-
* that aesthetic:
|
|
11
|
-
* resolution tag. Use as a lead-in for blog articles or as a placeholder
|
|
11
|
+
* that aesthetic: the DosWindow chrome (title strip, brand border, drop
|
|
12
|
+
* shadow), scanline sweep, optional pins, and a resolution tag. Use as a lead-in for blog articles or as a placeholder
|
|
12
13
|
* where a photograph would otherwise go.
|
|
13
14
|
*
|
|
14
15
|
* Not intended as a replacement for semantic imagery — pass `aria-label`
|
|
@@ -17,10 +18,16 @@ import { cn } from '../../../utils/cn.js';
|
|
|
17
18
|
export const DosFigure = forwardRef(({ title, resolution, subject, caption, pins = [], animated = true, 'aria-label': ariaLabel, className, }, ref) => {
|
|
18
19
|
const captionId = useId();
|
|
19
20
|
const describedById = caption ? captionId : undefined;
|
|
20
|
-
return (_jsxs("figure", { ref: ref, className: cn('eidotter-dos-figure', animated && 'eidotter-dos-figure--animated', className), role: "img", "aria-label": ariaLabel, "aria-describedby": describedById, children: [
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
return (_jsxs("figure", { ref: ref, className: cn('eidotter-dos-figure', animated && 'eidotter-dos-figure--animated', className), role: "img", "aria-label": ariaLabel, "aria-describedby": describedById, children: [_jsx(DosWindow
|
|
22
|
+
// Decorative chrome: the figure is named by its aria-label, so the
|
|
23
|
+
// strip text stays out of the accessible content.
|
|
24
|
+
, {
|
|
25
|
+
// Decorative chrome: the figure is named by its aria-label, so the
|
|
26
|
+
// strip text stays out of the accessible content.
|
|
27
|
+
title: title && _jsx("span", { "aria-hidden": "true", children: title }), className: "eidotter-dos-figure__window", children: _jsxs("div", { className: "eidotter-dos-figure__frame", children: [_jsx("div", { className: "eidotter-dos-figure__subject", children: subject }), pins.length > 0 && (_jsx("ul", { className: "eidotter-dos-figure__pins", role: "list", children: pins.map((pin, idx) => {
|
|
28
|
+
const x = Number.isFinite(pin.x) ? Math.max(0, Math.min(100, pin.x)) : 0;
|
|
29
|
+
const y = Number.isFinite(pin.y) ? Math.max(0, Math.min(100, pin.y)) : 0;
|
|
30
|
+
return (_jsxs("li", { className: "eidotter-dos-figure__pin", style: { left: `${x}%`, top: `${y}%` }, children: [_jsx("span", { className: "eidotter-dos-figure__pin-dot", "aria-hidden": "true" }), _jsx("span", { className: "eidotter-dos-figure__pin-label", children: pin.label })] }, `${x}-${y}-${idx}`));
|
|
31
|
+
}) })), _jsx("span", { className: "eidotter-dos-figure__scanline", "aria-hidden": "true" }), resolution && (_jsx("span", { className: "eidotter-dos-figure__resolution", "aria-hidden": "true", children: resolution }))] }) }), caption && (_jsx("figcaption", { id: captionId, className: "eidotter-dos-figure__caption", children: caption }))] }));
|
|
25
32
|
});
|
|
26
33
|
DosFigure.displayName = 'DosFigure';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface DosWindowProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {
|
|
3
|
+
/** Strip title. Omit (with no `controls`) for a frame without a strip. */
|
|
4
|
+
title?: React.ReactNode;
|
|
5
|
+
/** Show the leading `▸` marker in the strip. Defaults to `true`. */
|
|
6
|
+
marker?: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Active window: strip title in the active colour. Inactive dims the
|
|
9
|
+
* strip title to `text-muted`. Defaults to `true`.
|
|
10
|
+
*/
|
|
11
|
+
active?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Window controls for the strip's end slot, usually `IconButton`s at the
|
|
14
|
+
* default `md` size (36px; they overhang the strip padding so the strip
|
|
15
|
+
* keeps its height). Consumer-supplied so DosWindow stays presentational.
|
|
16
|
+
*/
|
|
17
|
+
controls?: React.ReactNode;
|
|
18
|
+
/** Footer row, separated from the body by a 1px rule. */
|
|
19
|
+
footer?: React.ReactNode;
|
|
20
|
+
/** Extra classes merged onto the root (`.eidotter-window`). */
|
|
21
|
+
className?: string;
|
|
22
|
+
/** Window body. */
|
|
23
|
+
children?: React.ReactNode;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* DosWindow: the window chrome for in-page panels (DMNC-1592).
|
|
27
|
+
*
|
|
28
|
+
* The same title strip as DosDialog (dark, uppercase xs title, `▸` marker,
|
|
29
|
+
* 1px dim-amber rule) on a frame of brand border plus `--shadow-drop`, with
|
|
30
|
+
* no glow and no backdrop. DosFigure and Card are built on it.
|
|
31
|
+
*
|
|
32
|
+
* Server-safe and presentational: no hooks, no handlers. Controls are passed
|
|
33
|
+
* in by the consumer. The strip title is a plain `<span>`; it is not a
|
|
34
|
+
* heading, so pass your own heading element as `title` if the window needs
|
|
35
|
+
* one in the document outline.
|
|
36
|
+
*
|
|
37
|
+
* Ref forwards to the root `<div>`.
|
|
38
|
+
*/
|
|
39
|
+
export declare const DosWindow: React.ForwardRefExoticComponent<DosWindowProps & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from 'react';
|
|
3
|
+
import { cn } from '../../../utils/cn.js';
|
|
4
|
+
/**
|
|
5
|
+
* DosWindow: the window chrome for in-page panels (DMNC-1592).
|
|
6
|
+
*
|
|
7
|
+
* The same title strip as DosDialog (dark, uppercase xs title, `▸` marker,
|
|
8
|
+
* 1px dim-amber rule) on a frame of brand border plus `--shadow-drop`, with
|
|
9
|
+
* no glow and no backdrop. DosFigure and Card are built on it.
|
|
10
|
+
*
|
|
11
|
+
* Server-safe and presentational: no hooks, no handlers. Controls are passed
|
|
12
|
+
* in by the consumer. The strip title is a plain `<span>`; it is not a
|
|
13
|
+
* heading, so pass your own heading element as `title` if the window needs
|
|
14
|
+
* one in the document outline.
|
|
15
|
+
*
|
|
16
|
+
* Ref forwards to the root `<div>`.
|
|
17
|
+
*/
|
|
18
|
+
export const DosWindow = forwardRef(({ title, marker = true, active = true, controls, footer, className, children, ...rest }, ref) => {
|
|
19
|
+
const hasTitle = title != null && title !== false && title !== '';
|
|
20
|
+
const hasControls = controls != null && controls !== false;
|
|
21
|
+
return (_jsxs("div", { ref: ref, className: cn('eidotter-window', !active && 'eidotter-window--inactive', className), "data-active": active ? 'true' : 'false', ...rest, children: [(hasTitle || hasControls) && (_jsxs("div", { className: "eidotter-window__strip", children: [_jsxs("div", { className: "eidotter-window__lead", children: [marker && hasTitle && (_jsx("span", { className: "eidotter-window__marker", "aria-hidden": "true", children: "\u25B8" })), hasTitle && _jsx("span", { className: "eidotter-window__title", children: title })] }), hasControls && _jsx("div", { className: "eidotter-window__controls", children: controls })] })), _jsx("div", { className: "eidotter-window__body", children: children }), footer != null && footer !== false && (_jsx("div", { className: "eidotter-window__footer", children: footer }))] }));
|
|
22
|
+
});
|
|
23
|
+
DosWindow.displayName = 'DosWindow';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DosWindow } from './DosWindow.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DosWindow } from './components/index.js';
|
|
@@ -9,10 +9,13 @@ export interface FilterBarItem {
|
|
|
9
9
|
/** Whether this item is disabled */
|
|
10
10
|
disabled?: boolean;
|
|
11
11
|
/**
|
|
12
|
-
* Optional
|
|
13
|
-
* property.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* Optional fill token for the active state, applied as a CSS custom
|
|
13
|
+
* property. The active label is drawn in `text-on-brand` ink, so pass a
|
|
14
|
+
* bright fill: `--color-semantic-background-brand` (follows the theme), or a
|
|
15
|
+
* bright CGA primitive (`--color-cga-bright-cyan`, `-bright-green`,
|
|
16
|
+
* `-yellow`, `-light-gray`). Each clears 4.5:1 in every theme. Text roles
|
|
17
|
+
* such as `--color-semantic-text-accent` are dark in the light theme and
|
|
18
|
+
* drop to about 3:1 as a fill. (A themeable per-category palette is
|
|
16
19
|
* tracked with the token rationalization, DMNC-922/1059.)
|
|
17
20
|
*/
|
|
18
21
|
color?: string;
|
|
@@ -15,7 +15,7 @@ export const Footer = ({ copyright, links, linkComponent, children, className, .
|
|
|
15
15
|
const resolvedLinks = links ?? defaultLegalLinks;
|
|
16
16
|
const LinkTag = linkComponent ?? 'a';
|
|
17
17
|
const linkClassName = 'eidotter-footer__link text-dos-text-brand no-underline';
|
|
18
|
-
return (_jsxs("footer", { className: cn('font-dos text-
|
|
18
|
+
return (_jsxs("footer", { className: cn('font-dos text-dos-text-base py-4 px-2 text-center', 'eidotter-footer', className), ...props, children: [_jsx("div", { className: "h-px bg-dos-border-default mb-4", role: "separator" }), children && _jsx("div", { className: "mb-3", children: children }), resolvedLinks.length > 0 && (_jsx("nav", { className: "flex justify-center items-center flex-wrap gap-2 mb-2", "aria-label": "Footer links", children: resolvedLinks.map((link, index) => {
|
|
19
19
|
// Unsafe hrefs (javascript, data, vbscript, …) render the label without an anchor.
|
|
20
20
|
const safeHref = isSafeHref(link.href) ? link.href : undefined;
|
|
21
21
|
return (_jsxs(React.Fragment, { children: [index > 0 && _jsx("span", { className: "text-dos-text-muted select-none eidotter-footer__dot", "aria-hidden": "true", children: "\u00B7" }), !safeHref ? (_jsx("span", { className: linkClassName, children: link.label })) : link.external ? (_jsx("a", { className: linkClassName, href: safeHref, target: "_blank", rel: "noopener noreferrer", children: link.label })) : (_jsx(LinkTag, { className: linkClassName, href: safeHref, children: link.label }))] }, link.href));
|
|
@@ -15,6 +15,11 @@ const valueSizeClasses = {
|
|
|
15
15
|
lg: 'text-dos-display-sm',
|
|
16
16
|
};
|
|
17
17
|
const ringSizes = { sm: 72, md: 104, lg: 136 };
|
|
18
|
+
/** Splits "720 units" at the first space into the reading and the rest, so the rest can wrap. */
|
|
19
|
+
function splitSecondary(text) {
|
|
20
|
+
const i = text.indexOf(' ');
|
|
21
|
+
return i > 0 ? [text.slice(0, i), text.slice(i + 1)] : [text, null];
|
|
22
|
+
}
|
|
18
23
|
const toneClasses = {
|
|
19
24
|
neutral: 'eidotter-tone--neutral',
|
|
20
25
|
info: 'eidotter-tone--info',
|
|
@@ -63,8 +68,20 @@ export const Gauge = forwardRef(({ label, shortLabel, value, max = 100, unit, se
|
|
|
63
68
|
.filter(Boolean)
|
|
64
69
|
.join(', ');
|
|
65
70
|
const name = `${ariaLabel ?? label}: ${valuetext}`;
|
|
66
|
-
const
|
|
71
|
+
const sizeKey = size in ringSizes ? size : 'md';
|
|
72
|
+
const diameter = ringSizes[sizeKey];
|
|
73
|
+
const thickness = Math.round(diameter / 8);
|
|
74
|
+
// The small ring's hole cannot hold a secondary line, so it sits under the ring.
|
|
75
|
+
const secondaryBelow = sizeKey === 'sm';
|
|
76
|
+
const [secondaryHead, secondaryTail] = splitSecondary(secondary ?? '');
|
|
77
|
+
const secondaryPart = secondary && !unknown && !loading ? (_jsxs("span", { className: "eidotter-gauge__secondary text-dos-text-brand text-dos-text-xs tabular-nums leading-none", children: [_jsx("span", { className: "eidotter-gauge__secondary-head", children: secondaryHead }), secondaryTail !== null && (_jsxs(_Fragment, { children: [' ', _jsx("span", { className: "eidotter-gauge__secondary-tail", children: secondaryTail })] }))] })) : null;
|
|
67
78
|
const tagText = provenanceLabel(labels, provenance);
|
|
68
|
-
return (_jsxs("div", { ref: ref, className: cn('eidotter-gauge eidotter-health flex flex-col items-center gap-1 w-full font-dos', sizeClasses[size] ?? sizeClasses.md, toneClasses[effectiveTone], statusClasses[status], className), style: { minWidth: diameter }, role: "meter", "aria-valuemin": 0, "aria-valuemax": unknown ? 100 : max, "aria-valuenow": unknown || loading ? 0 : Math.max(0, Math.min(value, max)), "aria-valuetext": valuetext, "aria-label": name, "aria-busy": loading || undefined, ...props, children: [_jsxs("div", { className: "relative inline-flex items-center justify-center", children: [_jsx(ProgressRing, { decorative: true, value: loading ? 0 : pct, max: 100, size: diameter, thickness:
|
|
79
|
+
return (_jsxs("div", { ref: ref, className: cn('eidotter-gauge eidotter-health flex flex-col items-center gap-1 w-full font-dos', sizeClasses[size] ?? sizeClasses.md, toneClasses[effectiveTone], statusClasses[status], className), style: { minWidth: diameter }, "data-size": sizeKey, "data-has-short": shortLabel ? 'true' : 'false', role: "meter", "aria-valuemin": 0, "aria-valuemax": unknown ? 100 : max, "aria-valuenow": unknown || loading ? 0 : Math.max(0, Math.min(value, max)), "aria-valuetext": valuetext, "aria-label": name, "aria-busy": loading || undefined, ...props, children: [_jsxs("div", { className: "relative inline-flex items-center justify-center", children: [_jsx(ProgressRing, { decorative: true, value: loading ? 0 : pct, max: 100, size: diameter, thickness: thickness, segments: toneSegments[effectiveTone] ?? 16, color: effectiveTone === 'neutral' ? undefined : 'var(--eidotter-fill)', glow: false, className: cn(glow && 'eidotter-health-glow') }), _jsx("div", { className: "eidotter-gauge__center absolute flex flex-col items-center justify-center min-w-0 text-center leading-none", style: { inset: thickness + 2 }, children: loading ? (_jsx("div", { "aria-hidden": "true", className: "eidotter-gauge__skeleton w-full overflow-hidden", children: _jsx(Skeleton, { variant: "figure", lines: 2, animated: false }) })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: [
|
|
80
|
+
'eidotter-gauge__value text-dos-text-accent tabular-nums',
|
|
81
|
+
valueSizeClasses[sizeKey],
|
|
82
|
+
stale && 'opacity-60',
|
|
83
|
+
]
|
|
84
|
+
.filter(Boolean)
|
|
85
|
+
.join(' '), children: valueText }), !secondaryBelow && secondaryPart] })) })] }), secondaryBelow && secondaryPart, _jsxs("div", { className: "flex flex-wrap items-center justify-center gap-1 text-dos-text-brand uppercase tracking-wider leading-tight max-w-full", children: [effectiveTone === 'critical' && (_jsx("span", { "aria-hidden": "true", className: "eidotter-gauge__glyph text-dos-text-error", children: "\u25B2" })), _jsxs("span", { className: "eidotter-gauge__label truncate", title: label, children: [_jsx("span", { className: "eidotter-gauge__label--full", children: label }), shortLabel && _jsx("span", { className: "eidotter-gauge__label--short", children: shortLabel })] }), overflow && (_jsx("span", { "aria-hidden": "true", className: "eidotter-gauge__overflow text-dos-text-warning", children: "+" })), tagText !== null && (_jsx("span", { className: "eidotter-gauge__provenance border border-dos-border-brand px-1 normal-case", children: tagText })), stale && _jsx("span", { className: "eidotter-gauge__stale normal-case", children: labels.stale })] }), resolved.label && (_jsx("span", { className: "eidotter-gauge__band text-dos-text-brand normal-case", children: resolved.label }))] }));
|
|
69
86
|
});
|
|
70
87
|
Gauge.displayName = 'Gauge';
|
|
@@ -46,7 +46,35 @@ export interface HeaderProps extends React.HTMLAttributes<HTMLElement> {
|
|
|
46
46
|
* is identical to a header without this prop.
|
|
47
47
|
*/
|
|
48
48
|
context?: HeaderContext;
|
|
49
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* How `brandName` is presented when no `children` are passed:
|
|
51
|
+
* - `'lockup'` (default): the eiDotter Logo (28px, glow) + the name in
|
|
52
|
+
* uppercase, wide-tracked DOS text.
|
|
53
|
+
* - `'mark'`: the morphing `Mark` (28px) + the same name text.
|
|
54
|
+
* - `'text'`: the name as plain text (the pre-0.41 behaviour).
|
|
55
|
+
* The icon is decorative (the name is the link's accessible name). Ignored
|
|
56
|
+
* when `children` is provided, or when `brandName` is empty.
|
|
57
|
+
*/
|
|
58
|
+
brand?: 'text' | 'lockup' | 'mark';
|
|
59
|
+
/**
|
|
60
|
+
* Trailing slot (theme toggle, ⌘K hint, …). Rendered once: after the desktop
|
|
61
|
+
* nav on wide screens, and between the brand and the MENU trigger on mobile
|
|
62
|
+
* (the desktop nav is hidden below 768px, the MENU trigger above it, so DOM
|
|
63
|
+
* order gives both placements without duplicating the node). Keep end
|
|
64
|
+
* content compact on mobile; it clips before the brand name does.
|
|
65
|
+
* The slot is `overflow: hidden` at every width, so dropdowns, menus and
|
|
66
|
+
* tooltips opened from `end` must render in a portal (React Aria popovers,
|
|
67
|
+
* e.g. `Select`, already do); an in-flow popover is clipped.
|
|
68
|
+
*/
|
|
69
|
+
end?: React.ReactNode;
|
|
70
|
+
/**
|
|
71
|
+
* When set (e.g. `'#main'`), renders a "SKIP TO CONTENT" anchor as the
|
|
72
|
+
* header's first child, visually hidden until keyboard-focused (WCAG 2.4.1).
|
|
73
|
+
* Off by default — sites with their own skip link leave it unset. Unsafe
|
|
74
|
+
* schemes render no anchor.
|
|
75
|
+
*/
|
|
76
|
+
skipLinkTarget?: string;
|
|
77
|
+
/** Custom branding content — replaces brandName (and `brand`) when provided */
|
|
50
78
|
children?: React.ReactNode;
|
|
51
79
|
/** Additional CSS class name */
|
|
52
80
|
className?: string;
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { forwardRef } from 'react';
|
|
2
|
+
import React, { forwardRef } from 'react';
|
|
3
3
|
import { cn } from '../../../utils/cn.js';
|
|
4
4
|
import { isSafeHref } from '../../../utils/isSafeHref.js';
|
|
5
5
|
import { DesktopNav, MobileNav } from '../../Nav/index.js';
|
|
6
|
+
import { Logo, Mark } from '../../Brand/index.js';
|
|
6
7
|
import { HeaderContextRow } from './HeaderContextRow.js';
|
|
7
8
|
const variantClasses = {
|
|
8
9
|
retro: 'eidotter-header--retro',
|
|
9
10
|
modern: 'eidotter-header--modern',
|
|
10
11
|
};
|
|
11
|
-
export const Header = forwardRef(({ brandName, brandHref = '/', items, activeHref, variant = 'retro', sticky = true, linkComponent, context, children, className, ...rest }, ref) => {
|
|
12
|
+
export const Header = forwardRef(({ brandName, brandHref = '/', items, activeHref, variant = 'retro', sticky = true, linkComponent, context, brand = 'lockup', end, skipLinkTarget, children, className, ...rest }, ref) => {
|
|
12
13
|
const LinkTag = linkComponent ?? 'a';
|
|
13
14
|
// The brand link is structural — an unsafe brandHref falls back to '/' rather
|
|
14
15
|
// than unmounting the branding anchor.
|
|
@@ -16,9 +17,23 @@ export const Header = forwardRef(({ brandName, brandHref = '/', items, activeHre
|
|
|
16
17
|
// An empty context (no categories, no returnTo) is treated as absent so the
|
|
17
18
|
// rendered DOM stays byte-identical to a header without the prop.
|
|
18
19
|
const hasContext = !!context && (context.categories.length > 0 || !!context.returnTo);
|
|
19
|
-
const
|
|
20
|
-
|
|
20
|
+
const safeSkipTarget = skipLinkTarget && isSafeHref(skipLinkTarget) ? skipLinkTarget : undefined;
|
|
21
|
+
// children always win; the icon variants only apply to a non-empty brandName.
|
|
22
|
+
const iconBrand = !children && !!brandName && brand !== 'text';
|
|
23
|
+
const brandContent = children || (iconBrand ? (_jsxs(_Fragment, { children: [brand === 'mark'
|
|
24
|
+
? _jsx(Mark, { size: 28, label: "", className: "eidotter-header__brand-icon" })
|
|
25
|
+
: _jsx(Logo, { size: 28, glow: true, title: "", className: "eidotter-header__brand-icon" }), _jsx("span", { className: "eidotter-header__brand-name uppercase tracking-[0.3em]", children: brandName })] })) : brandName);
|
|
26
|
+
// `end` is rendered once: DesktopNav is hidden <768px and the MENU trigger
|
|
27
|
+
// ≥768px, so this DOM order gives "nav · end" on desktop and "end · MENU" on
|
|
28
|
+
// mobile. The right-hand group wrapper only exists when `end` is passed, so
|
|
29
|
+
// headers without it keep their previous DOM.
|
|
30
|
+
const hasEnd = end != null && end !== false && end !== '';
|
|
31
|
+
const ActionsGroup = hasEnd ? 'div' : React.Fragment;
|
|
32
|
+
const actionsProps = hasEnd ? { className: 'eidotter-header__actions flex items-center gap-4' } : {};
|
|
33
|
+
const main = (_jsxs(_Fragment, { children: [_jsx(LinkTag, { href: safeBrandHref, className: cn('no-underline text-dos-text-base font-bold tracking-wide', 'eidotter-header__branding', iconBrand && 'eidotter-header__branding--icon inline-flex items-center gap-3'), children: brandContent }), _jsxs(ActionsGroup, { ...actionsProps, children: [_jsx(DesktopNav, { items: items, activeHref: activeHref, variant: variant, linkComponent: linkComponent }), hasEnd && _jsx("div", { className: "eidotter-header__end flex items-center", children: end }), _jsx(MobileNav, { items: items, activeHref: activeHref, variant: variant, linkComponent: linkComponent })] })] }));
|
|
34
|
+
const skipLink = safeSkipTarget ? (_jsx("a", { href: safeSkipTarget, className: "eidotter-header__skip", children: "Skip to content" })) : null;
|
|
35
|
+
return (_jsxs("header", { ref: ref, className: cn(hasContext
|
|
21
36
|
? 'flex flex-col gap-2 px-4 py-3 font-dos'
|
|
22
|
-
: 'flex items-center justify-between px-4 py-3 font-dos', 'eidotter-header', variantClasses[variant], sticky && 'sticky top-0 z-50 bg-dos-bg-primary', className), ...rest, children: hasContext ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "eidotter-header__main flex w-full items-center justify-between", children: main }), _jsx(HeaderContextRow, { context: context, linkComponent: linkComponent, activeHref: activeHref })] })) : (main) }));
|
|
37
|
+
: 'flex items-center justify-between px-4 py-3 font-dos', 'eidotter-header', variantClasses[variant], sticky && 'sticky top-0 z-50 bg-dos-bg-primary', className), ...rest, children: [skipLink, hasContext ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "eidotter-header__main flex w-full items-center justify-between", children: main }), _jsx(HeaderContextRow, { context: context, linkComponent: linkComponent, activeHref: activeHref })] })) : (main)] }));
|
|
23
38
|
});
|
|
24
39
|
Header.displayName = 'Header';
|
|
@@ -9,6 +9,8 @@ declare const ICON_MAP: {
|
|
|
9
9
|
Close: PixelIcon;
|
|
10
10
|
'Chevron Up': (props: React.SVGProps<SVGSVGElement>) => JSX.Element;
|
|
11
11
|
'Chevron Down': (props: React.SVGProps<SVGSVGElement>) => JSX.Element;
|
|
12
|
+
'Chevron Left': (props: React.SVGProps<SVGSVGElement>) => JSX.Element;
|
|
13
|
+
'Chevron Right': (props: React.SVGProps<SVGSVGElement>) => JSX.Element;
|
|
12
14
|
App: (props: React.SVGProps<SVGSVGElement>) => JSX.Element;
|
|
13
15
|
Cancel: (props: React.SVGProps<SVGSVGElement>) => JSX.Element;
|
|
14
16
|
Fullscreen: (props: React.SVGProps<SVGSVGElement>) => JSX.Element;
|
|
@@ -6,6 +6,8 @@ import { SquareAlert } from 'pixelarticons/react/SquareAlert';
|
|
|
6
6
|
import { Check } from 'pixelarticons/react/Check';
|
|
7
7
|
import { ChevronUp } from 'pixelarticons/react/ChevronUp';
|
|
8
8
|
import { ChevronDown } from 'pixelarticons/react/ChevronDown';
|
|
9
|
+
import { ChevronLeft } from 'pixelarticons/react/ChevronLeft';
|
|
10
|
+
import { ChevronRight } from 'pixelarticons/react/ChevronRight';
|
|
9
11
|
import { WindowFrame } from 'pixelarticons/react/WindowFrame';
|
|
10
12
|
import { Minus } from 'pixelarticons/react/Minus';
|
|
11
13
|
import { Expand } from 'pixelarticons/react/Expand';
|
|
@@ -17,7 +19,7 @@ import { cn } from '../../../utils/cn.js';
|
|
|
17
19
|
* currentColor inheritance, 2×2 unit "pixels" on the grid.
|
|
18
20
|
*/
|
|
19
21
|
const PixelX = (props) => (_jsx("svg", { viewBox: "0 0 24 24", width: "24", height: "24", fill: "currentColor", xmlns: "http://www.w3.org/2000/svg", ...props, children: _jsx("path", { d: "M5 5h2v2H5zM7 7h2v2H7zM9 9h2v2H9zM11 11h2v2h-2zM13 13h2v2h-2zM15 15h2v2h-2zM17 17h2v2h-2zM17 5h2v2h-2zM15 7h2v2h-2zM13 9h2v2h-2zM9 13h2v2H9zM7 15h2v2H7zM5 17h2v2H5z" }) }));
|
|
20
|
-
//
|
|
22
|
+
// 14 public icon names mapping to 13 unique glyphs (12 pixelarticons + PixelX).
|
|
21
23
|
// Done and Check both map to the Check component intentionally — consumers
|
|
22
24
|
// can use either semantic name for a checkmark glyph.
|
|
23
25
|
//
|
|
@@ -38,6 +40,8 @@ const ICON_MAP = {
|
|
|
38
40
|
'Close': PixelX, // custom pixel-art X mark (pixelarticons has no X glyph)
|
|
39
41
|
'Chevron Up': ChevronUp,
|
|
40
42
|
'Chevron Down': ChevronDown,
|
|
43
|
+
'Chevron Left': ChevronLeft, // Lightbox prev (IconButton, DMNC-1592)
|
|
44
|
+
'Chevron Right': ChevronRight, // Lightbox next
|
|
41
45
|
'App': WindowFrame,
|
|
42
46
|
'Cancel': Minus, // window minimize control, not abort
|
|
43
47
|
'Fullscreen': Expand,
|