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.
Files changed (97) hide show
  1. package/README.md +13 -5
  2. package/dist/components/Accordion/components/AccordionFill.js +1 -1
  3. package/dist/components/Accordion/components/Section.js +1 -1
  4. package/dist/components/Alert/components/Alert.js +3 -2
  5. package/dist/components/Brand/components/Logo.d.ts +7 -7
  6. package/dist/components/Brand/components/Logo.js +10 -15
  7. package/dist/components/Brand/components/LogoGeometry.d.ts +28 -0
  8. package/dist/components/Brand/components/LogoGeometry.js +78 -0
  9. package/dist/components/Brand/components/Mark.d.ts +13 -6
  10. package/dist/components/Brand/components/Mark.js +14 -10
  11. package/dist/components/Brand/components/Wordmark.js +1 -1
  12. package/dist/components/Brand/components/markGeometry.d.ts +15 -0
  13. package/dist/components/Brand/components/markGeometry.js +20 -7
  14. package/dist/components/Breadcrumb/components/Breadcrumb.js +2 -2
  15. package/dist/components/Button/components/Button.js +1 -1
  16. package/dist/components/Card/components/Card.d.ts +5 -0
  17. package/dist/components/Card/components/Card.js +8 -2
  18. package/dist/components/CmdPalette/components/CmdPalette.d.ts +4 -3
  19. package/dist/components/CmdPalette/components/CmdPalette.js +17 -12
  20. package/dist/components/DecayStrip/components/DecayStrip.js +6 -1
  21. package/dist/components/DosDialog/components/DosDialog.d.ts +77 -0
  22. package/dist/components/DosDialog/components/DosDialog.js +59 -0
  23. package/dist/components/DosDialog/components/index.d.ts +2 -0
  24. package/dist/components/DosDialog/components/index.js +1 -0
  25. package/dist/components/DosDialog/index.d.ts +2 -0
  26. package/dist/components/DosDialog/index.js +1 -0
  27. package/dist/components/DosFigure/components/DosFigure.d.ts +2 -2
  28. package/dist/components/DosFigure/components/DosFigure.js +14 -7
  29. package/dist/components/DosWindow/components/DosWindow.d.ts +39 -0
  30. package/dist/components/DosWindow/components/DosWindow.js +23 -0
  31. package/dist/components/DosWindow/components/index.d.ts +2 -0
  32. package/dist/components/DosWindow/components/index.js +1 -0
  33. package/dist/components/DosWindow/index.d.ts +2 -0
  34. package/dist/components/DosWindow/index.js +1 -0
  35. package/dist/components/FilterBar/components/FilterBar.d.ts +7 -4
  36. package/dist/components/Footer/components/Footer.js +1 -1
  37. package/dist/components/Gauge/components/Gauge.js +19 -2
  38. package/dist/components/Header/components/Header.d.ts +29 -1
  39. package/dist/components/Header/components/Header.js +20 -5
  40. package/dist/components/Icon/components/Icon.d.ts +2 -0
  41. package/dist/components/Icon/components/Icon.js +5 -1
  42. package/dist/components/IconButton/components/IconButton.d.ts +44 -0
  43. package/dist/components/IconButton/components/IconButton.js +19 -0
  44. package/dist/components/IconButton/components/index.d.ts +2 -0
  45. package/dist/components/IconButton/components/index.js +1 -0
  46. package/dist/components/IconButton/index.d.ts +2 -0
  47. package/dist/components/IconButton/index.js +1 -0
  48. package/dist/components/Input/components/Input.js +1 -1
  49. package/dist/components/Lightbox/components/Lightbox.d.ts +10 -4
  50. package/dist/components/Lightbox/components/Lightbox.js +19 -11
  51. package/dist/components/Meter/components/Meter.js +7 -3
  52. package/dist/components/Meter/components/MeterGroup.js +3 -3
  53. package/dist/components/Modal/components/Modal.d.ts +18 -6
  54. package/dist/components/Modal/components/Modal.js +18 -9
  55. package/dist/components/Nav/components/Nav.js +5 -4
  56. package/dist/components/Notification/components/Notification.js +3 -2
  57. package/dist/components/Progress/components/Progress.js +8 -6
  58. package/dist/components/SeriesBars/components/SeriesBars.js +9 -8
  59. package/dist/components/Tag/components/Tag.d.ts +8 -1
  60. package/dist/components/Tag/components/Tag.js +5 -5
  61. package/dist/components/VitalsStrip/components/VitalsStrip.js +10 -7
  62. package/dist/components/registry.js +221 -19
  63. package/dist/eidotter.css +1 -1
  64. package/dist/hooks/useAnimatedDismiss.js +5 -0
  65. package/dist/index.d.ts +8 -1
  66. package/dist/index.es.js +2174 -1600
  67. package/dist/index.umd.js +1 -1
  68. package/llms.txt +15 -7
  69. package/migrations/0.41.0.md +881 -0
  70. package/migrations/README.md +46 -0
  71. package/package.json +10 -2
  72. package/public/favicon.svg +1 -1
  73. package/src/styles/dos-utilities.css +6 -1
  74. package/src/styles/shadcn.css +52 -17
  75. package/src/styles/theme.amber-mono.css +4 -2
  76. package/src/styles/theme.amber-paper.css +106 -0
  77. package/src/styles/theme.cga-amber.css +3 -1
  78. package/src/styles/theme.cga-mode4-p0.css +3 -1
  79. package/src/styles/theme.cga-mode4-p1.css +3 -1
  80. package/src/styles/theme.cga-mode5.css +3 -1
  81. package/src/styles/theme.light.css +3 -1
  82. package/src/styles/tokens.css +5 -1
  83. package/tailwind.preset.cjs +23 -1
  84. package/dist/components/Brand/components/markGeometry.test.d.ts +0 -1
  85. package/dist/components/Connector/components/connectorGeometry.test.d.ts +0 -1
  86. package/dist/components/DecayStrip/components/resample.test.d.ts +0 -1
  87. package/dist/components/TimelineContainer/components/timelineUtils.test.d.ts +0 -1
  88. package/dist/components/TimelineContainer/components/useDrillDown.test.d.ts +0 -1
  89. package/dist/components/TimelineContainer/components/useSelection.test.d.ts +0 -1
  90. package/dist/components/TimelineContainer/components/useZoom.test.d.ts +0 -1
  91. package/dist/hooks/useTextScramble.test.d.ts +0 -1
  92. package/dist/hooks/useThemePortal.test.d.ts +0 -1
  93. package/dist/utils/health/formatValue.test.d.ts +0 -1
  94. package/dist/utils/health/healthPatterns.test.d.ts +0 -1
  95. package/dist/utils/health/resolveBand.test.d.ts +0 -1
  96. package/dist/utils/isSafeHref.test.d.ts +0 -1
  97. package/dist/utils/isSafeUrl.test.d.ts +0 -1
@@ -0,0 +1,44 @@
1
+ import React from 'react';
2
+ import type { PressEvent } from 'react-aria-components';
3
+ import type { IconName } from '../../Icon/components/Icon.js';
4
+ /** `md` = 36×36 hit area, 20px glyph (every built-in adopter). `window` = 28×28, 16px glyph, for dense chrome. */
5
+ export type IconButtonSize = 'md' | 'window';
6
+ /** `outline` = 1px square outline at rest (default). `bare` = no resting outline. */
7
+ export type IconButtonVariant = 'outline' | 'bare';
8
+ export interface IconButtonProps {
9
+ /** Glyph from the `<Icon>` set: `Close` (X), `Cancel` (minus, minimise), `Fullscreen` (maximise), `Chevron Left/Right/Up/Down`. */
10
+ icon: IconName;
11
+ /** Accessible name. Required: the button has no visible text. Rendered as `aria-label`. */
12
+ label: string;
13
+ /** Hit-area size. Defaults to `md` (36×36). */
14
+ size?: IconButtonSize;
15
+ /**
16
+ * `outline` (default) shows the thin square outline at rest. `bare` drops
17
+ * it, for places where an outline would fight a neighbouring rule.
18
+ */
19
+ variant?: IconButtonVariant;
20
+ /** Disables the button (opacity 0.5, no hover invert). */
21
+ isDisabled?: boolean;
22
+ /** Called on press (mouse, touch, Enter, Space). */
23
+ onPress?: (e: PressEvent) => void;
24
+ /**
25
+ * Keep the button out of the Tab order. Only for controls whose action is
26
+ * reachable another way (Tag's remove, reachable via Delete/Backspace on
27
+ * the focused tag).
28
+ */
29
+ excludeFromTabOrder?: boolean;
30
+ /** Extra classes (placement) merged onto the button. */
31
+ className?: string;
32
+ }
33
+ /**
34
+ * IconButton: the one icon-only control (DMNC-1592).
35
+ *
36
+ * A pixel glyph from `<Icon>` inside a thin square outline: transparent fill,
37
+ * `text-accent` glyph at rest; hover inverts (amber fill, dark glyph); focus
38
+ * is a 2px brand outline offset 2px. Every close, prev/next and window
39
+ * control in the library uses it, so the glyph and states match everywhere.
40
+ *
41
+ * React Aria `Button` handles press, keyboard and focus-visible. Ref forwards
42
+ * to the `<button>`.
43
+ */
44
+ export declare const IconButton: React.ForwardRefExoticComponent<IconButtonProps & React.RefAttributes<HTMLButtonElement>>;
@@ -0,0 +1,19 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { forwardRef } from 'react';
4
+ import { Button as AriaButton } from 'react-aria-components';
5
+ import { Icon } from '../../Icon/components/Icon.js';
6
+ import { cn } from '../../../utils/cn.js';
7
+ /**
8
+ * IconButton: the one icon-only control (DMNC-1592).
9
+ *
10
+ * A pixel glyph from `<Icon>` inside a thin square outline: transparent fill,
11
+ * `text-accent` glyph at rest; hover inverts (amber fill, dark glyph); focus
12
+ * is a 2px brand outline offset 2px. Every close, prev/next and window
13
+ * control in the library uses it, so the glyph and states match everywhere.
14
+ *
15
+ * React Aria `Button` handles press, keyboard and focus-visible. Ref forwards
16
+ * to the `<button>`.
17
+ */
18
+ export const IconButton = forwardRef(({ icon, label, size = 'md', variant = 'outline', isDisabled, onPress, excludeFromTabOrder, className, }, ref) => (_jsx(AriaButton, { ref: ref, "aria-label": label, isDisabled: isDisabled, onPress: onPress, excludeFromTabOrder: excludeFromTabOrder, className: cn('inline-flex items-center justify-center shrink-0', 'eidotter-icon-button', size === 'window' && 'eidotter-icon-button--window', variant === 'bare' && 'eidotter-icon-button--bare', className), children: _jsx("span", { className: "eidotter-icon-button__glyph", "aria-hidden": "true", children: _jsx(Icon, { name: icon, size: "S" }) }) })));
19
+ IconButton.displayName = 'IconButton';
@@ -0,0 +1,2 @@
1
+ export { IconButton } from './IconButton.js';
2
+ export type { IconButtonProps, IconButtonSize, IconButtonVariant } from './IconButton.js';
@@ -0,0 +1 @@
1
+ export { IconButton } from './IconButton.js';
@@ -0,0 +1,2 @@
1
+ export { IconButton } from './components/index.js';
2
+ export type { IconButtonProps, IconButtonSize, IconButtonVariant } from './components/index.js';
@@ -0,0 +1 @@
1
+ export { IconButton } from './components/index.js';
@@ -13,6 +13,6 @@ const variantClasses = {
13
13
  */
14
14
  export const Input = forwardRef(({ variant = 'default', label, description, errorMessage, disabled, className, onChange, isRequired, ...props }, ref) => {
15
15
  const isInvalid = variant === 'error';
16
- return (_jsxs(AriaTextField, { isDisabled: disabled, isInvalid: isInvalid, isRequired: isRequired, className: cn('eidotter-text-field', 'flex flex-col gap-1', className), children: [label && (_jsx(AriaLabel, { className: "text-dos-text-brand font-dos text-xs uppercase tracking-wider", children: label })), _jsx(AriaInput, { ref: ref, className: cn('w-full bg-transparent text-dos-text-brand font-dos text-base p-2', 'border-2 border-dos-border-default outline-none box-border', 'eidotter-input', variantClasses[variant] || variantClasses.default, disabled && 'eidotter-input--disabled'), onChange: onChange, ...props }), description && !isInvalid && (_jsx(AriaText, { slot: "description", className: "text-dos-text-muted font-dos text-xs", children: description })), errorMessage && isInvalid && (_jsx(AriaText, { slot: "errorMessage", className: "text-dos-text-error font-dos text-xs", children: errorMessage }))] }));
16
+ return (_jsxs(AriaTextField, { isDisabled: disabled, isInvalid: isInvalid, isRequired: isRequired, className: cn('eidotter-text-field', 'flex flex-col gap-1', className), children: [label && (_jsx(AriaLabel, { className: "text-dos-text-brand font-dos text-dos-text-base uppercase tracking-wider", children: label })), _jsx(AriaInput, { ref: ref, className: cn('w-full bg-transparent text-dos-text-brand font-dos text-dos-text-base p-2', 'border-2 border-dos-border-default outline-none box-border', 'eidotter-input', variantClasses[variant] || variantClasses.default, disabled && 'eidotter-input--disabled'), onChange: onChange, ...props }), description && !isInvalid && (_jsx(AriaText, { slot: "description", className: "text-dos-text-muted font-dos text-dos-text-base", children: description })), errorMessage && isInvalid && (_jsx(AriaText, { slot: "errorMessage", className: "text-dos-text-error font-dos text-dos-text-base", children: errorMessage }))] }));
17
17
  });
18
18
  Input.displayName = 'Input';
@@ -13,10 +13,16 @@ export interface LightboxProps {
13
13
  onIndexChange?: (index: number) => void;
14
14
  }
15
15
  /**
16
- * Fullscreen image viewer built on the same React Aria primitives as Modal.
16
+ * Fullscreen image viewer on the DosDialog shell (DMNC-1592).
17
17
  *
18
- * Reuses Modal's CRT phosphor enter/exit animations, overlay tokens, and close
19
- * button styling. Adds prev/next, counter, caption, keyboard nav, and touch
20
- * swipe — see Tasks 4 and 5.
18
+ * The shell supplies the overlay, chrome, motion and the title strip;
19
+ * the `[ n / N ]` counter is the strip's title and the close control sits at
20
+ * its end (an IconButton). Lightbox adds prev/next (IconButtons with the pixel
21
+ * chevrons), caption, keyboard nav, and touch swipe.
22
+ * It shares the shell's z-index (`--z-index-modal`) with Modal; stacking a
23
+ * Lightbox over an open Modal is not a supported case.
24
+ *
25
+ * The close IconButton carries `eidotter-lightbox__close`. eidotter ships no
26
+ * CSS for it; it is kept as a stable consumer hook for targeting the control.
21
27
  */
22
28
  export declare const Lightbox: React.FC<LightboxProps>;
@@ -1,15 +1,20 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import React, { useCallback, useEffect, useState } from 'react';
4
- import { Button as AriaButton, ModalOverlay as AriaModalOverlay, Modal as AriaModal, Dialog as AriaDialog, } from 'react-aria-components';
5
- import { Icon } from '../../Icon/components/Icon.js';
6
- import { cn } from '../../../utils/cn.js';
4
+ import { IconButton } from '../../IconButton/components/IconButton.js';
5
+ import { DosDialog } from '../../DosDialog/components/DosDialog.js';
7
6
  /**
8
- * Fullscreen image viewer built on the same React Aria primitives as Modal.
7
+ * Fullscreen image viewer on the DosDialog shell (DMNC-1592).
9
8
  *
10
- * Reuses Modal's CRT phosphor enter/exit animations, overlay tokens, and close
11
- * button styling. Adds prev/next, counter, caption, keyboard nav, and touch
12
- * swipe — see Tasks 4 and 5.
9
+ * The shell supplies the overlay, chrome, motion and the title strip;
10
+ * the `[ n / N ]` counter is the strip's title and the close control sits at
11
+ * its end (an IconButton). Lightbox adds prev/next (IconButtons with the pixel
12
+ * chevrons), caption, keyboard nav, and touch swipe.
13
+ * It shares the shell's z-index (`--z-index-modal`) with Modal; stacking a
14
+ * Lightbox over an open Modal is not a supported case.
15
+ *
16
+ * The close IconButton carries `eidotter-lightbox__close`. eidotter ships no
17
+ * CSS for it; it is kept as a stable consumer hook for targeting the control.
13
18
  */
14
19
  export const Lightbox = ({ images, initialIndex = 0, isOpen, onClose, onIndexChange, }) => {
15
20
  // Clamp the requested initial index so callers passing stale indices
@@ -78,8 +83,8 @@ export const Lightbox = ({ images, initialIndex = 0, isOpen, onClose, onIndexCha
78
83
  return () => window.removeEventListener('keydown', onKeyDown);
79
84
  }, [isOpen, goPrev, goNext]);
80
85
  // Empty array: nothing to show. (Note: do NOT early-return on !isOpen here —
81
- // React Aria's AriaModalOverlay needs to stay mounted across isOpen
82
- // transitions so it can play modal-crt-exit. The overlay handles its own
86
+ // the DosDialog overlay needs to stay mounted across isOpen transitions so
87
+ // it can play its exit animation. The overlay handles its own
83
88
  // visibility based on the `isOpen` prop.)
84
89
  if (images.length === 0) {
85
90
  return null;
@@ -91,7 +96,10 @@ export const Lightbox = ({ images, initialIndex = 0, isOpen, onClose, onIndexCha
91
96
  const current = images[safeIndex];
92
97
  const hasPrev = safeIndex > 0;
93
98
  const hasNext = safeIndex < images.length - 1;
94
- return (_jsx(AriaModalOverlay, { isOpen: isOpen, onOpenChange: (open) => { if (!open)
95
- onClose(); }, isDismissable: true, className: ({ isEntering, isExiting }) => cn('eidotter-lightbox-overlay', isEntering && 'eidotter-lightbox-overlay--entering', isExiting && 'eidotter-lightbox-overlay--exiting'), children: _jsx(AriaModal, { className: ({ isEntering, isExiting }) => cn('eidotter-lightbox', isEntering && 'eidotter-lightbox--entering', isExiting && 'eidotter-lightbox--exiting'), children: _jsxs(AriaDialog, { "aria-label": current.alt || 'Image viewer', className: "eidotter-lightbox__container outline-none", children: [_jsx(AriaButton, { className: "eidotter-lightbox__close", onPress: onClose, "aria-label": "Close lightbox", children: _jsx(Icon, { name: "Close", size: "S" }) }), _jsx(AriaButton, { className: "eidotter-lightbox__nav eidotter-lightbox__nav--prev", onPress: goPrev, isDisabled: !hasPrev, "aria-label": "Previous image", children: "\u25C0" }), _jsx(AriaButton, { className: "eidotter-lightbox__nav eidotter-lightbox__nav--next", onPress: goNext, isDisabled: !hasNext, "aria-label": "Next image", children: "\u25B6" }), _jsxs("figure", { className: "eidotter-lightbox__figure", children: [_jsx("img", { className: "eidotter-lightbox__image", src: current.src, alt: current.alt, width: current.width, height: current.height, onTouchStart: onTouchStart, onTouchEnd: onTouchEnd }), current.caption && (_jsx("figcaption", { className: "eidotter-lightbox__caption", children: current.caption }))] }), _jsxs("span", { className: "eidotter-lightbox__counter", "aria-live": "polite", children: ["[ ", safeIndex + 1, " / ", images.length, " ]"] })] }) }) }));
99
+ return (_jsx(DosDialog, { open: isOpen, onOpenChange: (open) => { if (!open)
100
+ onClose(); }, placement: "center", size: "full", "aria-label": current.alt || 'Image viewer', className: "eidotter-lightbox__container", head: {
101
+ title: (_jsxs("span", { className: "eidotter-lightbox__counter", "aria-live": "polite", children: ["[ ", safeIndex + 1, " / ", images.length, " ]"] })),
102
+ end: (_jsx(IconButton, { icon: "Close", label: "Close lightbox", className: "eidotter-lightbox__close", onPress: onClose })),
103
+ }, children: _jsxs("figure", { className: "eidotter-lightbox__figure", children: [_jsx("img", { className: "eidotter-lightbox__image", src: current.src, alt: current.alt, width: current.width, height: current.height, onTouchStart: onTouchStart, onTouchEnd: onTouchEnd }), _jsx(IconButton, { icon: "Chevron Left", label: "Previous image", className: "eidotter-lightbox__nav eidotter-lightbox__nav--prev", onPress: goPrev, isDisabled: !hasPrev }), _jsx(IconButton, { icon: "Chevron Right", label: "Next image", className: "eidotter-lightbox__nav eidotter-lightbox__nav--next", onPress: goNext, isDisabled: !hasNext }), current.caption && (_jsx("figcaption", { className: "eidotter-lightbox__caption", children: current.caption }))] }) }));
96
104
  };
97
105
  Lightbox.displayName = 'Lightbox';
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef } from 'react';
3
3
  import { cn } from '../../../utils/cn.js';
4
4
  import { formatValue, isKnown, resolveBand, resolveLabels, PLACEHOLDER } from '../../../utils/health/index.js';
@@ -65,15 +65,19 @@ export const Meter = forwardRef(({ label, shortLabel, value, max = 100, unit, op
65
65
  const effectivePattern = pattern ?? tonePatterns[effectiveTone] ?? 'solid';
66
66
  const fillClass = patternClasses[effectivePattern] ?? patternClasses.solid;
67
67
  const valueText = unknown ? PLACEHOLDER : formatValue(value, { locale, unit }).text;
68
+ // Number and unit render in two spans so a narrow MeterGroup can drop the
69
+ // unit onto its own line; the accessible value text stays one string.
70
+ const numberText = unknown ? PLACEHOLDER : formatValue(value, { locale }).text;
71
+ const unitText = unknown ? undefined : unit || undefined;
68
72
  const valuetext = unknown ? labels.unknown : valueText;
69
73
  const name = `${ariaLabel ?? label}: ${valuetext}`;
70
74
  const isRange = Array.isArray(optimal);
71
75
  const optimalProps = maxValid ? optimalStyle(optimal, max) : undefined;
72
76
  const blockCount = segments !== undefined ? Math.max(0, Math.floor(segments)) : 0;
73
77
  const filledBlocks = Math.round(ratio * blockCount);
74
- const valuePart = showValue || overflow ? (_jsxs("div", { className: "eidotter-meter__readout flex items-center gap-1 leading-none", children: [showValue && (_jsx("span", { className: "eidotter-meter__value text-dos-text-accent tabular-nums", children: valueText })), overflow && (_jsx("span", { "aria-hidden": "true", className: "eidotter-meter__overflow text-dos-text-warning", children: "+" }))] })) : null;
78
+ const valuePart = showValue || overflow ? (_jsxs("div", { className: "eidotter-meter__readout flex items-center gap-1 leading-none", children: [showValue && (_jsxs("span", { className: "eidotter-meter__value text-dos-text-accent tabular-nums text-center", children: [_jsx("span", { className: "eidotter-meter__number", children: numberText }), unitText && (_jsxs(_Fragment, { children: [' ', _jsx("span", { className: "eidotter-meter__unit", children: unitText })] }))] })), overflow && (_jsx("span", { "aria-hidden": "true", className: "eidotter-meter__overflow text-dos-text-warning", children: "+" }))] })) : null;
75
79
  const trackPart = (_jsxs("div", { className: "eidotter-meter__track relative bg-dos-bg-primary border border-dos-border-disabled", children: [blockCount > 0 ? (_jsx("div", { className: "eidotter-meter__segments", "aria-hidden": "true", children: Array.from({ length: blockCount }, (_, i) => (_jsx("span", { className: cn('eidotter-meter__segment', i < filledBlocks && `eidotter-meter__segment--filled ${fillClass}`) }, i))) })) : (_jsx("div", { className: cn('eidotter-meter__fill', fillClass), style: { '--eidotter-meter-fill': String(round4(ratio)) } })), optimalProps && (_jsx("div", { "aria-hidden": "true", className: cn('eidotter-meter__optimal', isRange && 'eidotter-meter__optimal--range'), style: optimalProps }))] }));
76
80
  const labelPart = (_jsxs("div", { className: "eidotter-meter__caption flex flex-wrap items-center justify-center gap-1 text-dos-text-brand uppercase tracking-wider leading-tight min-w-0 max-w-full", children: [effectiveTone === 'critical' && (_jsx("span", { "aria-hidden": "true", className: "eidotter-meter__glyph text-dos-text-error", children: "\u25B2" })), _jsxs("span", { className: "eidotter-meter__label truncate", title: label, children: [_jsx("span", { className: "eidotter-meter__label--full", children: label }), shortLabel && _jsx("span", { className: "eidotter-meter__label--short", children: shortLabel })] }), resolved.label && (_jsx("span", { className: "eidotter-meter__band normal-case", children: resolved.label }))] }));
77
- return (_jsx("div", { ref: ref, className: cn('eidotter-meter eidotter-health flex font-dos', orientationClasses[orientation] ?? orientationClasses.vertical, sizeClasses[size] ?? sizeClasses.sm, toneClasses[effectiveTone], className), role: "meter", "aria-valuemin": 0, "aria-valuemax": maxValid ? max : 100, "aria-valuenow": unknown ? 0 : Math.max(0, Math.min(value, max)), "aria-valuetext": valuetext, "aria-label": name, ...props, children: orientation === 'horizontal' ? (_jsxs(_Fragment, { children: [labelPart, trackPart, valuePart] })) : (_jsxs(_Fragment, { children: [valuePart, trackPart, labelPart] })) }));
81
+ return (_jsx("div", { ref: ref, className: cn('eidotter-meter eidotter-health flex font-dos', orientationClasses[orientation] ?? orientationClasses.vertical, sizeClasses[size] ?? sizeClasses.sm, toneClasses[effectiveTone], className), "data-has-short": shortLabel ? 'true' : 'false', role: "meter", "aria-valuemin": 0, "aria-valuemax": maxValid ? max : 100, "aria-valuenow": unknown ? 0 : Math.max(0, Math.min(value, max)), "aria-valuetext": valuetext, "aria-label": name, ...props, children: orientation === 'horizontal' ? (_jsxs(_Fragment, { children: [labelPart, trackPart, valuePart] })) : (_jsxs(_Fragment, { children: [valuePart, trackPart, labelPart] })) }));
78
82
  });
79
83
  Meter.displayName = 'Meter';
@@ -21,8 +21,8 @@ export const MeterGroup = forwardRef(({ layout = 'row', columns, sharedMax = fal
21
21
  const fixedColumns = layout === 'grid' && columns !== undefined;
22
22
  return (_jsx("div", { ref: ref, role: "group", className: cn('eidotter-meter-group', layoutClasses[layout] ?? layoutClasses.row, fixedColumns && 'eidotter-meter-group--columns', className), style: fixedColumns
23
23
  ? { '--eidotter-meter-columns': String(columns) }
24
- : undefined, ...props, children: items
25
- ? items.map((item, i) => (_jsx(Meter, { ...item, ...(scale !== undefined && { max: scale }) }, `${item.label}-${i}`)))
26
- : children }));
24
+ : undefined, ...props, children: _jsx("div", { className: "eidotter-meter-group__layout", children: items
25
+ ? items.map((item, i) => (_jsx(Meter, { ...item, ...(scale !== undefined && { max: scale }) }, `${item.label}-${i}`)))
26
+ : children }) }));
27
27
  });
28
28
  MeterGroup.displayName = 'MeterGroup';
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import type { DosDialogSize } from '../../DosDialog/components/DosDialog.js';
2
3
  export interface ModalProps {
3
4
  /** Whether the modal is open */
4
5
  isOpen: boolean;
@@ -6,19 +7,30 @@ export interface ModalProps {
6
7
  onOpenChange?: (isOpen: boolean) => void;
7
8
  /** @deprecated Use `onOpenChange` instead. Called when modal should close. */
8
9
  onClose?: () => void;
9
- /** Modal title (required for accessibility) */
10
+ /** Modal title (required for accessibility). Rendered in the title strip. */
10
11
  title: string;
11
12
  /** Modal body content */
12
13
  children: React.ReactNode;
13
14
  /** Footer content, typically action buttons */
14
15
  footer?: React.ReactNode;
15
- /** Optional CSS class name */
16
+ /**
17
+ * Width step from the shared dialog map (DosDialog): `sm` 24rem,
18
+ * `md` 30rem (default, the historical 480px), `lg` 40rem, `full`.
19
+ */
20
+ size?: DosDialogSize;
21
+ /** Close on backdrop click. Escape and the close button always close. Defaults to `true`. */
22
+ isDismissable?: boolean;
23
+ /** Optional CSS class name, merged onto the dialog container */
16
24
  className?: string;
17
25
  }
18
26
  /**
19
- * DOS-styled Modal with React Aria Dialog.
20
- * React Aria provides focus trapping, scroll lock, backdrop dismiss, and escape handling.
21
- * CRT phosphor enter/exit animations via isEntering/isExiting render props.
22
- * Ref forwards to the inner AriaDialog element (not the overlay).
27
+ * DOS-styled Modal on the DosDialog shell (DMNC-1592).
28
+ * The title sits in the title strip with the close control at its end; the
29
+ * footer keeps its 1px top rule. React Aria provides focus trapping, scroll
30
+ * lock, backdrop dismiss, and escape handling.
31
+ * Ref forwards to the dialog element (not the overlay).
32
+ *
33
+ * The close IconButton carries `eidotter-modal__close`. eidotter ships no CSS
34
+ * for it; it is kept as a stable consumer hook for targeting the control.
23
35
  */
24
36
  export declare const Modal: React.ForwardRefExoticComponent<ModalProps & React.RefAttributes<HTMLElement>>;
@@ -1,16 +1,20 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { useEffect, useRef, forwardRef } from 'react';
4
- import { Button as AriaButton, ModalOverlay as AriaModalOverlay, Modal as AriaModal, Dialog as AriaDialog, } from 'react-aria-components';
5
- import { Icon } from '../../Icon/components/Icon.js';
4
+ import { IconButton } from '../../IconButton/components/IconButton.js';
5
+ import { DosDialog } from '../../DosDialog/components/DosDialog.js';
6
6
  import { cn } from '../../../utils/cn.js';
7
7
  /**
8
- * DOS-styled Modal with React Aria Dialog.
9
- * React Aria provides focus trapping, scroll lock, backdrop dismiss, and escape handling.
10
- * CRT phosphor enter/exit animations via isEntering/isExiting render props.
11
- * Ref forwards to the inner AriaDialog element (not the overlay).
8
+ * DOS-styled Modal on the DosDialog shell (DMNC-1592).
9
+ * The title sits in the title strip with the close control at its end; the
10
+ * footer keeps its 1px top rule. React Aria provides focus trapping, scroll
11
+ * lock, backdrop dismiss, and escape handling.
12
+ * Ref forwards to the dialog element (not the overlay).
13
+ *
14
+ * The close IconButton carries `eidotter-modal__close`. eidotter ships no CSS
15
+ * for it; it is kept as a stable consumer hook for targeting the control.
12
16
  */
13
- export const Modal = forwardRef(({ isOpen, onOpenChange, onClose, title, children, footer, className, }, ref) => {
17
+ export const Modal = forwardRef(({ isOpen, onOpenChange, onClose, title, children, footer, size = 'md', isDismissable = true, className, }, ref) => {
14
18
  const prevOpenRef = useRef(isOpen);
15
19
  const interactionRef = useRef(false);
16
20
  // Notify onOpenChange on prop-driven state changes only (observer pattern for agents).
@@ -32,6 +36,11 @@ export const Modal = forwardRef(({ isOpen, onOpenChange, onClose, title, childre
32
36
  if (!open)
33
37
  onClose?.();
34
38
  };
35
- return (_jsx(AriaModalOverlay, { isOpen: isOpen, onOpenChange: handleOpenChange, isDismissable: true, className: ({ isEntering, isExiting }) => cn('eidotter-modal-overlay', isEntering && 'eidotter-modal-overlay--entering', isExiting && 'eidotter-modal-overlay--exiting'), children: _jsx(AriaModal, { className: ({ isEntering, isExiting }) => cn('eidotter-modal', isEntering && 'eidotter-modal--entering', isExiting && 'eidotter-modal--exiting'), children: _jsxs(AriaDialog, { ref: ref, "aria-label": title, className: cn('eidotter-modal__container outline-none', className), children: [_jsxs("header", { className: "eidotter-modal__header", children: [_jsx("h2", { className: "eidotter-modal__title", children: title }), _jsx(AriaButton, { className: "eidotter-modal__close", onPress: () => handleOpenChange(false), "aria-label": "Close modal", children: _jsx(Icon, { name: "Close", size: "S" }) })] }), _jsx("div", { className: "eidotter-modal__body", children: children }), footer && (_jsx("footer", { className: "eidotter-modal__footer", children: footer }))] }) }) }));
39
+ return (_jsx(DosDialog, { ref: ref, open: isOpen, onOpenChange: handleOpenChange, isDismissable: isDismissable, size: size, "aria-label": title, className: cn('eidotter-modal__container', className), head: {
40
+ title,
41
+ end: (_jsx(IconButton, { icon: "Close", label: "Close modal", className: "eidotter-modal__close", onPress: () => handleOpenChange(false) })),
42
+ },
43
+ /* Modal keeps its <footer> element (role=contentinfo) inside the shell slot. */
44
+ footer: footer ? _jsx("footer", { className: "eidotter-modal__footer", children: footer }) : undefined, children: _jsx("div", { className: "eidotter-modal__body", children: children }) }));
36
45
  });
37
46
  Modal.displayName = 'Modal';
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { useState, useCallback, useEffect, useRef } from 'react';
4
4
  import { cn } from '../../../utils/cn.js';
5
5
  import { isSafeHref } from '../../../utils/isSafeHref.js';
6
- import { Icon } from '../../Icon/index.js';
6
+ import { IconButton } from '../../IconButton/components/IconButton.js';
7
7
  const variantClasses = {
8
8
  retro: 'eidotter-nav--retro',
9
9
  modern: 'eidotter-nav--modern',
@@ -14,7 +14,8 @@ export const DesktopNav = ({ items, activeHref, variant = 'retro', linkComponent
14
14
  // Unsafe hrefs (javascript, data, vbscript, …) render the label as a
15
15
  // plain span instead of an anchor.
16
16
  const safeHref = isSafeHref(item.href) ? item.href : undefined;
17
- const linkClassName = cn('eidotter-nav__link', activeHref === item.href && 'eidotter-nav__link--active');
17
+ // Retro labels render uppercase DOS, like the MENU trigger (DMNC-1584).
18
+ const linkClassName = cn('eidotter-nav__link', variant === 'retro' && 'uppercase', activeHref === item.href && 'eidotter-nav__link--active');
18
19
  return (_jsx("li", { className: "eidotter-nav__desktop-item", children: safeHref ? (_jsx(LinkTag, { href: safeHref, className: linkClassName, "aria-current": activeHref === item.href ? 'page' : undefined, children: item.label })) : (_jsx("span", { className: linkClassName, children: item.label })) }, item.href));
19
20
  }) }) }));
20
21
  };
@@ -69,10 +70,10 @@ export const MobileNav = ({ items, activeHref, variant = 'retro', linkComponent,
69
70
  first.focus();
70
71
  }
71
72
  };
72
- return (_jsxs("div", { className: cn('eidotter-nav eidotter-nav--mobile', variantClasses[variant], className), children: [_jsx("button", { ref: triggerRef, onClick: toggle, className: "eidotter-nav__menu-trigger", "aria-label": isOpen ? 'Close menu' : 'Open menu', "aria-expanded": isOpen, "aria-controls": "eidotter-mobile-nav-panel", children: "MENU" }), isOpen && (_jsx("div", { className: "eidotter-nav__overlay", onClick: close, "aria-hidden": "true" })), _jsxs("nav", { ref: panelRef, id: "eidotter-mobile-nav-panel", className: cn('eidotter-nav__panel', isOpen && 'eidotter-nav__panel--open'), "aria-label": "Mobile navigation", inert: !isOpen, onKeyDown: handlePanelKeyDown, children: [_jsx("div", { className: "eidotter-nav__panel-header", children: _jsx("button", { ref: closeButtonRef, onClick: close, className: "eidotter-nav__close", "aria-label": "Close menu", children: _jsx(Icon, { name: "Close", size: "S" }) }) }), _jsx("ul", { className: "eidotter-nav__list", children: items.map((item) => {
73
+ return (_jsxs("div", { className: cn('eidotter-nav eidotter-nav--mobile', variantClasses[variant], className), children: [_jsx("button", { ref: triggerRef, onClick: toggle, className: "eidotter-nav__menu-trigger", "aria-label": isOpen ? 'Close menu' : 'Open menu', "aria-expanded": isOpen, "aria-controls": "eidotter-mobile-nav-panel", children: "MENU" }), isOpen && (_jsx("div", { className: "eidotter-nav__overlay", onClick: close, "aria-hidden": "true" })), _jsxs("nav", { ref: panelRef, id: "eidotter-mobile-nav-panel", className: cn('eidotter-nav__panel', isOpen && 'eidotter-nav__panel--open'), "aria-label": "Mobile navigation", inert: !isOpen, onKeyDown: handlePanelKeyDown, children: [_jsx("div", { className: "eidotter-nav__panel-header", children: _jsx(IconButton, { ref: closeButtonRef, icon: "Close", label: "Close menu", className: "eidotter-nav__close", onPress: close }) }), _jsx("ul", { className: "eidotter-nav__list", children: items.map((item) => {
73
74
  // Same guard as desktop — mobile panel is a second render path.
74
75
  const safeHref = isSafeHref(item.href) ? item.href : undefined;
75
- const linkClassName = cn('eidotter-nav__link', activeHref === item.href && 'eidotter-nav__link--active');
76
+ const linkClassName = cn('eidotter-nav__link', variant === 'retro' && 'uppercase', activeHref === item.href && 'eidotter-nav__link--active');
76
77
  return (_jsx("li", { className: "eidotter-nav__item", children: safeHref ? (_jsx(LinkTag, { href: safeHref, className: linkClassName, "aria-current": activeHref === item.href ? 'page' : undefined, onClick: close, children: item.label })) : (_jsx("span", { className: linkClassName, children: item.label })) }, item.href));
77
78
  }) })] })] }));
78
79
  };
@@ -4,6 +4,7 @@ import { useEffect } from 'react';
4
4
  import { Button as AriaButton } from 'react-aria-components';
5
5
  import { cn } from '../../../utils/cn.js';
6
6
  import { Icon } from '../../Icon/components/Icon.js';
7
+ import { IconButton } from '../../IconButton/components/IconButton.js';
7
8
  import { Progress } from '../../Progress/components/Progress.js';
8
9
  import { useAnimatedDismiss } from '../../../hooks/useAnimatedDismiss.js';
9
10
  const TYPE_ICONS = {
@@ -20,7 +21,7 @@ const TYPE_ICONS = {
20
21
  * Purely presentational — consumers handle positioning.
21
22
  */
22
23
  export const Notification = ({ type = 'primary', title, children, actions, onClose, avatar, image, progress, progressLabel, duration, showIcon = true, className, }) => {
23
- const { isClosing, triggerClose, handleAnimationEnd } = useAnimatedDismiss('notification-exit', onClose);
24
+ const { isClosing, triggerClose, handleAnimationEnd } = useAnimatedDismiss('eidotter-pop-out', onClose);
24
25
  // Auto-dismiss timer
25
26
  useEffect(() => {
26
27
  if (duration == null || !onClose)
@@ -30,5 +31,5 @@ export const Notification = ({ type = 'primary', title, children, actions, onClo
30
31
  }, [duration, onClose, triggerClose]);
31
32
  const hasIcon = showIcon && !avatar;
32
33
  const hasProgress = progress != null;
33
- return (_jsxs("div", { className: cn('w-full max-w-[400px]', 'font-dos text-dos-text-sm', 'border border-dos-border-default rounded-dos-base', 'eidotter-notification', `eidotter-notification--${type}`, isClosing && 'eidotter-notification--closing', className), onAnimationEnd: handleAnimationEnd, role: type === 'error' ? 'alert' : 'status', "data-type": type, children: [image && (_jsx("div", { className: "eidotter-notification__image", children: image })), _jsxs("div", { className: "flex flex-row gap-4 p-4", children: [avatar && (_jsx("div", { className: "eidotter-notification__avatar flex-shrink-0", children: avatar })), hasIcon && (_jsx("div", { className: "eidotter-notification__icon", children: _jsx(Icon, { name: TYPE_ICONS[type] || 'Info', size: "S", "aria-label": `${type} notification` }) })), _jsxs("div", { className: cn('flex-1 flex flex-col gap-3 min-w-0', onClose && !hasIcon && !avatar && 'pr-8'), children: [(title || children) && (_jsxs("div", { className: "flex flex-col gap-1", children: [title && (_jsx("div", { className: "eidotter-notification__title font-dos leading-[140%]", children: title })), children && (_jsx("div", { className: "eidotter-notification__text font-dos leading-[140%]", children: children }))] })), hasProgress && (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Progress, { value: progress, max: 100, size: "sm", fullWidth: true, showLabel: false }), progressLabel && (_jsx("div", { className: "eidotter-notification__text font-dos text-dos-text-xs", children: progressLabel }))] })), actions && actions.length > 0 && (_jsx("div", { className: "flex flex-row gap-3", children: actions.map((action, i) => (_jsxs(AriaButton, { className: "eidotter-notification__action", onPress: action.onClick, children: [action.label, action.icon] }, i))) }))] }), onClose && (_jsx(AriaButton, { className: "eidotter-notification__close", onPress: triggerClose, "aria-label": "Close notification", children: _jsx(Icon, { name: "Close", size: "S" }) }))] })] }));
34
+ return (_jsxs("div", { className: cn('w-full max-w-[400px]', 'font-dos text-dos-text-sm', 'border border-dos-border-default', 'eidotter-notification', `eidotter-notification--${type}`, isClosing && 'eidotter-notification--closing', className), onAnimationEnd: handleAnimationEnd, role: type === 'error' ? 'alert' : 'status', "data-type": type, children: [image && (_jsx("div", { className: "eidotter-notification__image", children: image })), _jsxs("div", { className: "flex flex-row gap-4 p-4", children: [avatar && (_jsx("div", { className: "eidotter-notification__avatar flex-shrink-0", children: avatar })), hasIcon && (_jsx("div", { className: "eidotter-notification__icon", children: _jsx(Icon, { name: TYPE_ICONS[type] || 'Info', size: "S", "aria-label": `${type} notification` }) })), _jsxs("div", { className: cn('flex-1 flex flex-col gap-3 min-w-0', onClose && !hasIcon && !avatar && 'pr-8'), children: [(title || children) && (_jsxs("div", { className: "flex flex-col gap-1", children: [title && (_jsx("div", { className: "eidotter-notification__title font-dos leading-[140%]", children: title })), children && (_jsx("div", { className: "eidotter-notification__text font-dos leading-[140%]", children: children }))] })), hasProgress && (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Progress, { value: progress, max: 100, size: "sm", fullWidth: true, showLabel: false }), progressLabel && (_jsx("div", { className: "eidotter-notification__text font-dos text-dos-text-xs", children: progressLabel }))] })), actions && actions.length > 0 && (_jsx("div", { className: "flex flex-row gap-3", children: actions.map((action, i) => (_jsxs(AriaButton, { className: "eidotter-notification__action", onPress: action.onClick, children: [action.label, action.icon] }, i))) }))] }), onClose && (_jsx(IconButton, { icon: "Close", label: "Close notification", className: "eidotter-notification__close", onPress: triggerClose }))] })] }));
34
35
  };
@@ -5,13 +5,15 @@ const FILLED = '\u2588'; // █
5
5
  const EMPTY = '\u2591'; // ░
6
6
  const DARK_SHADE = '\u2593'; // ▓
7
7
  const MED_SHADE = '\u2592'; // ▒
8
+ // eidotter tokens only: raw Tailwind sizes (text-xs/base/lg) would compile
9
+ // into dist/eidotter.css and override consumers' own utilities (DMNC-1579).
8
10
  const sizeClasses = {
9
- sm: 'text-xs',
10
- md: 'text-base',
11
- lg: 'text-lg',
12
- small: 'text-xs',
13
- medium: 'text-base',
14
- large: 'text-lg',
11
+ sm: 'text-dos-text-base', // 16px
12
+ md: 'text-dos-text-sm', // 20px
13
+ lg: 'text-dos-text-md', // 22px
14
+ small: 'text-dos-text-base',
15
+ medium: 'text-dos-text-sm',
16
+ large: 'text-dos-text-md',
15
17
  };
16
18
  const variantClasses = {
17
19
  default: 'eidotter-progress--default',
@@ -47,18 +47,19 @@ export const SeriesBars = forwardRef(({ points, max, goal, goalLabel, toneFor, t
47
47
  const toneOf = (v) => thresholds
48
48
  .filter((t) => v >= t.from)
49
49
  .reduce((best, t) => (!best || t.from > best.from ? t : best), undefined)?.tone ?? 'neutral';
50
+ const goalBand = goal !== undefined && !!goalLabel;
50
51
  const text = (v) => (isKnown(v) ? formatValue(v, { locale }).text : labels.unknown);
51
52
  const summary = [
52
53
  ...points.map((p) => `${p.label} ${text(p.value)}`),
53
54
  ...(goal !== undefined ? [`${goalLabel ?? ''} ${text(goal)}`.trim()] : []),
54
55
  ].join(', ');
55
- return (_jsxs("div", { ref: ref, className: cn('eidotter-series-bars eidotter-health relative font-dos', sizeClasses[size] ?? sizeClasses.md, className), ...props, children: [_jsxs("div", { role: "img", "aria-label": summary, children: [_jsxs("div", { className: cn('eidotter-series-bars__plot relative', plotClasses[size] ?? plotClasses.md), children: [goal !== undefined && (_jsx("div", { className: "eidotter-series-bars__goal absolute inset-x-0", style: { bottom: `${(Math.min(goal, scale) / scale) * 100}%` }, children: goalLabel && (_jsx("span", { className: "eidotter-series-bars__goal-tag text-dos-text-brand", children: goalLabel })) })), _jsx("div", { className: "eidotter-series-bars__cols flex items-end gap-1 h-full", children: points.map((p, i) => {
56
- const v = p.value;
57
- const tone = isKnown(v) ? toneOf(v) : 'neutral';
58
- return (_jsx("div", { className: "eidotter-series-bars__col flex flex-col justify-end flex-1 min-w-0 h-full", title: `${p.label}: ${text(v)}`, children: isKnown(v) ? (_jsx("div", { className: cn('eidotter-series-bars__bar w-full', toneClasses[tone], patternClasses[tone]), style: {
59
- height: `${(Math.min(Math.max(v, 0), scale) / scale) * 100}%`,
60
- ...(tone === 'neutral' && { '--eidotter-fill': NEUTRAL_FILL }),
61
- } })) : (_jsx("span", { className: "eidotter-series-bars__gap text-dos-text-muted text-center", children: PLACEHOLDER })) }, i));
62
- }) })] }), _jsx("div", { "aria-hidden": "true", className: "flex gap-1 text-dos-text-muted", children: points.map((p, i) => (_jsx("span", { className: "eidotter-series-bars__tick flex-1 min-w-0 truncate text-center tabular-nums", children: i % Math.max(tickEvery, 1) === 0 ? p.label : '' }, i))) })] }), _jsx("table", { className: "sr-only", children: _jsxs("tbody", { children: [points.map((p, i) => (_jsxs("tr", { children: [_jsx("th", { scope: "row", children: p.label }), _jsx("td", { children: text(p.value) })] }, i))), goal !== undefined && (_jsxs("tr", { children: [_jsx("th", { scope: "row", children: goalLabel }), _jsx("td", { children: text(goal) })] }))] }) })] }));
56
+ return (_jsxs("div", { ref: ref, className: cn('eidotter-series-bars eidotter-health relative font-dos', sizeClasses[size] ?? sizeClasses.md, className), ...props, children: [_jsxs("div", { role: "img", "aria-label": summary, children: [_jsxs("div", { className: cn('eidotter-series-bars__frame relative', goalBand && 'eidotter-series-bars__frame--goal-band'), children: [goalBand && (_jsx("span", { className: "eidotter-series-bars__goal-tag text-dos-text-brand", children: goalLabel })), _jsxs("div", { className: cn('eidotter-series-bars__plot relative', plotClasses[size] ?? plotClasses.md), children: [goal !== undefined && (_jsx("div", { className: "eidotter-series-bars__goal absolute inset-x-0", style: { bottom: `${(Math.min(goal, scale) / scale) * 100}%` } })), _jsx("div", { className: "eidotter-series-bars__cols flex items-end gap-1 h-full", children: points.map((p, i) => {
57
+ const v = p.value;
58
+ const tone = isKnown(v) ? toneOf(v) : 'neutral';
59
+ return (_jsx("div", { className: "eidotter-series-bars__col flex flex-col justify-end flex-1 min-w-0 h-full", title: `${p.label}: ${text(v)}`, children: isKnown(v) ? (_jsx("div", { className: cn('eidotter-series-bars__bar w-full', toneClasses[tone], patternClasses[tone]), style: {
60
+ height: `${(Math.min(Math.max(v, 0), scale) / scale) * 100}%`,
61
+ ...(tone === 'neutral' && { '--eidotter-fill': NEUTRAL_FILL }),
62
+ } })) : (_jsx("span", { className: "eidotter-series-bars__gap text-dos-text-muted text-center", children: PLACEHOLDER })) }, i));
63
+ }) })] })] }), _jsx("div", { "aria-hidden": "true", className: "flex gap-1 text-dos-text-muted", children: points.map((p, i) => (_jsx("span", { className: "eidotter-series-bars__tick flex-1 min-w-0 truncate text-center tabular-nums", children: i % Math.max(tickEvery, 1) === 0 ? p.label : '' }, i))) })] }), _jsx("table", { className: "sr-only", children: _jsxs("tbody", { children: [points.map((p, i) => (_jsxs("tr", { children: [_jsx("th", { scope: "row", children: p.label }), _jsx("td", { children: text(p.value) })] }, i))), goal !== undefined && (_jsxs("tr", { children: [_jsx("th", { scope: "row", children: goalLabel }), _jsx("td", { children: text(goal) })] }))] }) })] }));
63
64
  });
64
65
  SeriesBars.displayName = 'SeriesBars';
@@ -6,7 +6,14 @@ export interface TagProps {
6
6
  variant?: 'default' | 'outlined' | 'filled';
7
7
  /** Size variant. Use sm/md/lg — small/medium are @deprecated aliases. */
8
8
  size?: 'sm' | 'md' | 'lg' | 'small' | 'medium';
9
- /** CGA color token for border and text */
9
+ /**
10
+ * CGA color token for border and text (`default`, `outlined`), or the fill
11
+ * (`filled`, drawn with `text-on-brand` ink). For `filled`, pass a bright
12
+ * fill: a CGA primitive such as `--color-cga-bright-cyan`, `-bright-green`,
13
+ * `-yellow`, `-light-gray`, or `--color-semantic-background-brand`. Each
14
+ * clears 4.5:1 in every theme; text roles such as
15
+ * `--color-semantic-text-accent` drop to about 3:1 as a fill in the light theme.
16
+ */
10
17
  color?: string;
11
18
  /** Whether the tag is selected/active */
12
19
  selected?: boolean;
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Button as AriaButton } from 'react-aria-components';
3
+ import { IconButton } from '../../IconButton/components/IconButton.js';
4
4
  import { cn } from '../../../utils/cn.js';
5
5
  import { useAnimatedDismiss } from '../../../hooks/useAnimatedDismiss.js';
6
6
  const sizeClasses = {
@@ -16,7 +16,7 @@ const sizeClasses = {
16
16
  */
17
17
  export const Tag = ({ children, variant = 'default', size = 'md', color, selected = false, closeable = false, disabled = false, onClick, onClose, className, 'aria-label': ariaLabel, ...props }) => {
18
18
  const isInteractive = !!onClick && !disabled;
19
- const { isClosing, triggerClose, handleAnimationEnd } = useAnimatedDismiss('tag-exit', onClose);
19
+ const { isClosing, triggerClose, handleAnimationEnd } = useAnimatedDismiss('eidotter-pop-out', onClose);
20
20
  const handleKeyDown = (event) => {
21
21
  if (disabled)
22
22
  return;
@@ -29,18 +29,18 @@ export const Tag = ({ children, variant = 'default', size = 'md', color, selecte
29
29
  triggerClose();
30
30
  }
31
31
  };
32
- // AriaButton's usePress isolates the press event — it calls stopPropagation
32
+ // IconButton (React Aria Button) usePress isolates the press event — it calls stopPropagation
33
33
  // internally, preventing the close from triggering the parent Tag's onClick.
34
34
  const handleClosePress = () => {
35
35
  if (!disabled)
36
36
  triggerClose();
37
37
  };
38
38
  const style = color ? { '--tag-color': 'var(' + color + ')' } : undefined;
39
- return (_jsxs("span", { className: cn('inline-flex items-center justify-center', 'font-dos leading-none whitespace-nowrap select-none', 'rounded-dos-sm uppercase tracking-wider', sizeClasses[size] || sizeClasses.md, 'eidotter-tag', 'eidotter-tag--' + variant, selected && 'eidotter-tag--selected', disabled && 'opacity-50 cursor-not-allowed', isClosing && 'eidotter-tag--closing', isInteractive && 'cursor-pointer', className), style: style, onClick: isInteractive ? (e) => !disabled && onClick?.(e) : undefined, onKeyDown: handleKeyDown, onAnimationEnd: handleAnimationEnd, "aria-label": ariaLabel, "aria-pressed": isInteractive ? (selected || false) : undefined, "aria-disabled": disabled || undefined, "data-variant": variant, ...(isInteractive ? { role: 'button', tabIndex: 0 } : {}), ...props, children: [_jsx("span", { className: "eidotter-tag__content", children: children }), closeable && (_jsx(AriaButton, { className: "eidotter-tag__close", "aria-label": 'Remove ' + (typeof children === 'string' ? children : 'tag'),
39
+ return (_jsxs("span", { className: cn('inline-flex items-center justify-center', 'font-dos leading-none whitespace-nowrap select-none', 'rounded-dos-sm uppercase tracking-wider', sizeClasses[size] || sizeClasses.md, 'eidotter-tag', 'eidotter-tag--' + variant, selected && 'eidotter-tag--selected', disabled && 'opacity-50 cursor-not-allowed', isClosing && 'eidotter-tag--closing', isInteractive && 'cursor-pointer', className), style: style, onClick: isInteractive ? (e) => !disabled && onClick?.(e) : undefined, onKeyDown: handleKeyDown, onAnimationEnd: handleAnimationEnd, "aria-label": ariaLabel, "aria-pressed": isInteractive ? (selected || false) : undefined, "aria-disabled": disabled || undefined, "data-variant": variant, ...(isInteractive ? { role: 'button', tabIndex: 0 } : {}), ...props, children: [_jsx("span", { className: "eidotter-tag__content", children: children }), closeable && (_jsx(IconButton, { icon: "Close", label: 'Remove ' + (typeof children === 'string' ? children : 'tag'), variant: "bare", className: "eidotter-tag__close",
40
40
  // Keep the close button out of the tab order only when the body is
41
41
  // itself focusable (interactive) and already offers Delete/Backspace
42
42
  // dismissal. For a closeable, non-interactive tag the body is not
43
43
  // focusable, so the close button must stay tabbable to give keyboard
44
44
  // users any dismiss path at all.
45
- excludeFromTabOrder: isInteractive, onPress: handleClosePress, isDisabled: disabled, children: "[x]" }))] }));
45
+ excludeFromTabOrder: isInteractive, onPress: handleClosePress, isDisabled: disabled }))] }));
46
46
  };
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { forwardRef } from 'react';
3
3
  import { cn } from '../../../utils/cn.js';
4
4
  import { Skeleton } from '../../Skeleton/index.js';
@@ -8,11 +8,6 @@ const sizeClasses = {
8
8
  md: 'eidotter-vitals-strip--md gap-2 text-dos-text-sm',
9
9
  lg: 'eidotter-vitals-strip--lg gap-2 text-dos-text-md',
10
10
  };
11
- const valueSizeClasses = {
12
- sm: 'text-dos-display-xs',
13
- md: 'text-dos-display-sm',
14
- lg: 'text-dos-display-md',
15
- };
16
11
  const toneClasses = {
17
12
  neutral: 'eidotter-tone--neutral',
18
13
  info: 'eidotter-tone--info',
@@ -67,7 +62,15 @@ export const VitalsStrip = forwardRef(({ tiles, size = 'md', locale = 'en', labe
67
62
  .filter(Boolean)
68
63
  .join(', ');
69
64
  return (_jsxs("div", { role: "group", "aria-label": name, "aria-busy": loading || undefined, className: cn('eidotter-vitals-strip__tile flex flex-col gap-0.5 border border-dos-border-default p-2', toneClasses[tone], statusClasses[status], tone === 'critical' &&
70
- 'eidotter-vitals-strip__marker border-l-4 border-dos-border-error'), children: [_jsxs("span", { className: "flex items-center gap-1 text-dos-text-brand uppercase tracking-wider leading-tight", children: [tone === 'critical' && (_jsx("span", { "aria-hidden": "true", className: "text-dos-text-error", children: "\u25B2" })), _jsx("span", { className: "truncate", title: t.label, children: t.label })] }), loading ? (_jsx("div", { "aria-hidden": "true", className: "w-full overflow-hidden", children: _jsx(Skeleton, { variant: "figure", lines: 1, animated: false }) })) : (_jsxs("span", { className: cn('eidotter-vitals-strip__value text-dos-text-accent leading-none tabular-nums', valueSizeClasses[size] ?? valueSizeClasses.md), children: [stale ? _jsx("span", { className: "opacity-60", children: valueText }) : (stateText ?? valueText), valueText && t.unit && (_jsx("span", { className: "eidotter-vitals-strip__unit ml-1 text-[0.5em] text-dos-text-brand", children: t.unit }))] })), stale && _jsx("span", { className: "eidotter-vitals-strip__stale", children: labels.stale }), t.secondary && !unknown && (_jsx("span", { className: "eidotter-vitals-strip__secondary text-dos-text-muted leading-tight", children: t.secondary })), resolved.label && (_jsx("span", { className: "eidotter-vitals-strip__band text-dos-text-brand leading-tight", children: resolved.label }))] }, t.id));
65
+ 'eidotter-vitals-strip__marker border-l-4 border-dos-border-error'), children: [_jsxs("span", { className: "flex items-center gap-1 text-dos-text-brand uppercase tracking-wider leading-tight", children: [tone === 'critical' && (_jsx("span", { "aria-hidden": "true", className: "text-dos-text-error", children: "\u25B2" })), _jsx("span", { className: "truncate", title: t.label, children: t.label })] }), loading ? (_jsx("div", { "aria-hidden": "true", className: "w-full overflow-hidden", children: _jsx(Skeleton, { variant: "figure", lines: 1, animated: false }) })) : (
66
+ /* Display size comes from VitalsStrip.css (a per-size custom property that
67
+ steps down in narrow strips), not a Tailwind text-* utility: twMerge
68
+ would treat a second text-* class as a colour conflict and drop the
69
+ accent colour. The reading never wraps or widens the tile; the unit
70
+ moves under it when both do not fit on one line (DMNC-1573). */
71
+ _jsxs("span", { className: "eidotter-vitals-strip__value text-dos-text-accent leading-none tabular-nums flex flex-wrap items-baseline gap-x-1 min-w-0", children: [_jsx("span", { className: "eidotter-vitals-strip__reading min-w-0 max-w-full whitespace-nowrap overflow-hidden text-ellipsis", children: stale ? (_jsx("span", { className: "opacity-60", children: valueText })) : stateText !== null ? (
72
+ // State words are prose, not a reading: body size, not display size.
73
+ _jsx("span", { className: "eidotter-vitals-strip__state text-dos-text-sm", children: stateText })) : (valueText) }), valueText && t.unit && (_jsxs(_Fragment, { children: [' ', _jsx("span", { className: "eidotter-vitals-strip__unit whitespace-nowrap text-[0.5em] text-dos-text-brand", children: t.unit })] }))] })), stale && _jsx("span", { className: "eidotter-vitals-strip__stale", children: labels.stale }), t.secondary && !unknown && (_jsx("span", { className: "eidotter-vitals-strip__secondary text-dos-text-muted leading-tight", children: t.secondary })), resolved.label && (_jsx("span", { className: "eidotter-vitals-strip__band text-dos-text-brand leading-tight", children: resolved.label }))] }, t.id));
71
74
  }) }));
72
75
  });
73
76
  VitalsStrip.displayName = 'VitalsStrip';