eidotter 0.36.1 → 0.37.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 +19 -0
- package/dist/components/AIText/components/AIText.d.ts +0 -1
- package/dist/components/AIText/components/AIText.js +23 -0
- package/dist/components/AIText/components/index.d.ts +2 -2
- package/dist/components/AIText/components/index.js +1 -0
- package/dist/components/AIText/index.d.ts +2 -2
- package/dist/components/AIText/index.js +1 -0
- package/dist/components/Accordion/components/AccordionFill.d.ts +0 -1
- package/dist/components/Accordion/components/AccordionFill.js +12 -0
- package/dist/components/Accordion/components/Section.d.ts +0 -1
- package/dist/components/Accordion/components/Section.js +22 -0
- package/dist/components/Accordion/components/index.d.ts +4 -4
- package/dist/components/Accordion/components/index.js +2 -0
- package/dist/components/Accordion/index.d.ts +1 -1
- package/dist/components/Accordion/index.js +1 -0
- package/dist/components/Alert/components/Alert.d.ts +0 -1
- package/dist/components/Alert/components/Alert.js +51 -0
- package/dist/components/Alert/components/index.d.ts +2 -2
- package/dist/components/Alert/components/index.js +1 -0
- package/dist/components/Alert/index.d.ts +1 -1
- package/dist/components/Alert/index.js +1 -0
- package/dist/components/Badge/components/Badge.d.ts +0 -1
- package/dist/components/Badge/components/Badge.js +31 -0
- package/dist/components/Badge/components/index.d.ts +2 -2
- package/dist/components/Badge/components/index.js +1 -0
- package/dist/components/Badge/index.d.ts +2 -2
- package/dist/components/Badge/index.js +1 -0
- package/dist/components/Brand/components/BrandLockup.js +14 -0
- package/dist/components/Brand/components/Logo.d.ts +0 -1
- package/dist/components/Brand/components/Logo.js +22 -0
- package/dist/components/Brand/components/Wordmark.d.ts +0 -1
- package/dist/components/Brand/components/Wordmark.js +13 -0
- package/dist/components/Brand/components/index.d.ts +6 -6
- package/dist/components/Brand/components/index.js +3 -0
- package/dist/components/Brand/index.d.ts +1 -1
- package/dist/components/Brand/index.js +1 -0
- package/dist/components/Breadcrumb/components/Breadcrumb.d.ts +0 -1
- package/dist/components/Breadcrumb/components/Breadcrumb.js +29 -0
- package/dist/components/Breadcrumb/components/index.d.ts +2 -2
- package/dist/components/Breadcrumb/components/index.js +1 -0
- package/dist/components/Breadcrumb/index.d.ts +2 -2
- package/dist/components/Breadcrumb/index.js +1 -0
- package/dist/components/Button/components/Button.d.ts +0 -2
- package/dist/components/Button/components/Button.js +49 -0
- package/dist/components/Button/components/index.d.ts +2 -2
- package/dist/components/Button/components/index.js +1 -0
- package/dist/components/Button/index.d.ts +2 -2
- package/dist/components/Button/index.js +1 -0
- package/dist/components/Card/components/Card.d.ts +0 -1
- package/dist/components/Card/components/Card.js +16 -0
- package/dist/components/Card/components/index.d.ts +2 -2
- package/dist/components/Card/components/index.js +1 -0
- package/dist/components/Card/index.d.ts +2 -2
- package/dist/components/Card/index.js +1 -0
- package/dist/components/Chat/components/ChatContainer.d.ts +1 -1
- package/dist/components/Chat/components/ChatContainer.js +20 -0
- package/dist/components/Chat/components/ChatHistory.d.ts +0 -1
- package/dist/components/Chat/components/ChatHistory.js +27 -0
- package/dist/components/Chat/components/ChatInput.d.ts +0 -2
- package/dist/components/Chat/components/ChatInput.js +37 -0
- package/dist/components/Chat/components/ChatMessage.d.ts +0 -2
- package/dist/components/Chat/components/ChatMessage.js +18 -0
- package/dist/components/Chat/components/index.d.ts +8 -8
- package/dist/components/Chat/components/index.js +4 -0
- package/dist/components/Chat/index.d.ts +2 -2
- package/dist/components/Chat/index.js +1 -0
- package/dist/components/Checkbox/components/Checkbox.d.ts +0 -2
- package/dist/components/Checkbox/components/Checkbox.js +11 -0
- package/dist/components/Checkbox/components/index.d.ts +2 -2
- package/dist/components/Checkbox/components/index.js +1 -0
- package/dist/components/Checkbox/index.d.ts +2 -2
- package/dist/components/Checkbox/index.js +1 -0
- package/dist/components/ChecklistRow/components/ChecklistRow.js +18 -0
- package/dist/components/ChecklistRow/components/index.d.ts +2 -2
- package/dist/components/ChecklistRow/components/index.js +1 -0
- package/dist/components/ChecklistRow/index.d.ts +2 -2
- package/dist/components/ChecklistRow/index.js +1 -0
- package/dist/components/CmdPalette/components/CmdPalette.d.ts +0 -2
- package/dist/components/CmdPalette/components/CmdPalette.js +169 -0
- package/dist/components/CmdPalette/components/index.d.ts +2 -2
- package/dist/components/CmdPalette/components/index.js +1 -0
- package/dist/components/CmdPalette/index.d.ts +2 -2
- package/dist/components/CmdPalette/index.js +1 -0
- package/dist/components/CommandPrompt/components/CommandPrompt.d.ts +0 -2
- package/dist/components/CommandPrompt/components/CommandPrompt.js +34 -0
- package/dist/components/CommandPrompt/components/index.d.ts +2 -2
- package/dist/components/CommandPrompt/components/index.js +1 -0
- package/dist/components/CommandPrompt/index.d.ts +1 -1
- package/dist/components/CommandPrompt/index.js +1 -0
- package/dist/components/CopyButton/components/CopyButton.js +50 -0
- package/dist/components/CopyButton/components/index.d.ts +2 -2
- package/dist/components/CopyButton/components/index.js +1 -0
- package/dist/components/CopyButton/index.d.ts +2 -2
- package/dist/components/CopyButton/index.js +1 -0
- package/dist/components/DosFigure/components/DosFigure.d.ts +0 -1
- package/dist/components/DosFigure/components/DosFigure.js +26 -0
- package/dist/components/DosFigure/components/index.d.ts +2 -2
- package/dist/components/DosFigure/components/index.js +1 -0
- package/dist/components/DosFigure/index.d.ts +2 -2
- package/dist/components/DosFigure/index.js +1 -0
- package/dist/components/EmptyState/components/EmptyState.js +18 -0
- package/dist/components/EmptyState/components/index.d.ts +2 -2
- package/dist/components/EmptyState/components/index.js +1 -0
- package/dist/components/EmptyState/index.d.ts +2 -2
- package/dist/components/EmptyState/index.js +1 -0
- package/dist/components/FilterBar/components/FilterBar.d.ts +0 -1
- package/dist/components/FilterBar/components/FilterBar.js +120 -0
- package/dist/components/FilterBar/components/index.d.ts +2 -2
- package/dist/components/FilterBar/components/index.js +1 -0
- package/dist/components/FilterBar/index.d.ts +2 -2
- package/dist/components/FilterBar/index.js +1 -0
- package/dist/components/Footer/components/Footer.d.ts +0 -1
- package/dist/components/Footer/components/Footer.js +18 -0
- package/dist/components/Footer/components/index.d.ts +2 -2
- package/dist/components/Footer/components/index.js +1 -0
- package/dist/components/Footer/index.d.ts +2 -2
- package/dist/components/Footer/index.js +1 -0
- package/dist/components/Header/components/Header.d.ts +1 -2
- package/dist/components/Header/components/Header.js +13 -0
- package/dist/components/Header/components/index.d.ts +2 -2
- package/dist/components/Header/components/index.js +1 -0
- package/dist/components/Header/index.d.ts +2 -2
- package/dist/components/Header/index.js +1 -0
- package/dist/components/Icon/components/Icon.d.ts +0 -1
- package/dist/components/Icon/components/Icon.js +78 -0
- package/dist/components/Icon/components/index.d.ts +2 -2
- package/dist/components/Icon/components/index.js +1 -0
- package/dist/components/Icon/index.d.ts +1 -1
- package/dist/components/Icon/index.js +1 -0
- package/dist/components/InlineExpand/components/InlineExpand.d.ts +0 -1
- package/dist/components/InlineExpand/components/InlineExpand.js +50 -0
- package/dist/components/InlineExpand/components/index.d.ts +2 -2
- package/dist/components/InlineExpand/components/index.js +1 -0
- package/dist/components/InlineExpand/index.d.ts +2 -2
- package/dist/components/InlineExpand/index.js +1 -0
- package/dist/components/InlineLink/components/InlineLink.d.ts +0 -1
- package/dist/components/InlineLink/components/InlineLink.js +61 -0
- package/dist/components/InlineLink/components/index.d.ts +2 -2
- package/dist/components/InlineLink/components/index.js +1 -0
- package/dist/components/InlineLink/index.d.ts +2 -2
- package/dist/components/InlineLink/index.js +1 -0
- package/dist/components/Input/components/Input.d.ts +0 -1
- package/dist/components/Input/components/Input.js +18 -0
- package/dist/components/Input/components/index.d.ts +2 -2
- package/dist/components/Input/components/index.js +1 -0
- package/dist/components/Input/index.d.ts +1 -1
- package/dist/components/Input/index.js +1 -0
- package/dist/components/LabeledProgress/components/LabeledProgress.d.ts +0 -1
- package/dist/components/LabeledProgress/components/LabeledProgress.js +16 -0
- package/dist/components/LabeledProgress/components/index.d.ts +2 -2
- package/dist/components/LabeledProgress/components/index.js +1 -0
- package/dist/components/LabeledProgress/index.d.ts +2 -2
- package/dist/components/LabeledProgress/index.js +1 -0
- package/dist/components/LedgerRow/components/LedgerRow.js +15 -0
- package/dist/components/LedgerRow/components/index.d.ts +2 -2
- package/dist/components/LedgerRow/components/index.js +1 -0
- package/dist/components/LedgerRow/index.d.ts +2 -2
- package/dist/components/LedgerRow/index.js +1 -0
- package/dist/components/LegalPage/components/LegalPage.d.ts +0 -1
- package/dist/components/LegalPage/components/LegalPage.js +11 -0
- package/dist/components/LegalPage/components/clauses.js +28 -0
- package/dist/components/LegalPage/components/index.d.ts +4 -4
- package/dist/components/LegalPage/components/index.js +2 -0
- package/dist/components/LegalPage/index.d.ts +2 -2
- package/dist/components/LegalPage/index.js +1 -0
- package/dist/components/Lightbox/components/Lightbox.d.ts +1 -3
- package/dist/components/Lightbox/components/Lightbox.js +97 -0
- package/dist/components/Lightbox/components/index.d.ts +2 -2
- package/dist/components/Lightbox/components/index.js +1 -0
- package/dist/components/Lightbox/index.d.ts +2 -2
- package/dist/components/Lightbox/index.js +1 -0
- package/dist/components/Modal/components/Modal.d.ts +0 -2
- package/dist/components/Modal/components/Modal.js +37 -0
- package/dist/components/Modal/components/index.d.ts +2 -2
- package/dist/components/Modal/components/index.js +1 -0
- package/dist/components/Modal/index.d.ts +2 -2
- package/dist/components/Modal/index.js +1 -0
- package/dist/components/Nav/components/Nav.d.ts +0 -1
- package/dist/components/Nav/components/Nav.js +69 -0
- package/dist/components/Nav/components/index.d.ts +2 -2
- package/dist/components/Nav/components/index.js +1 -0
- package/dist/components/Nav/index.d.ts +2 -2
- package/dist/components/Nav/index.js +1 -0
- package/dist/components/Notification/components/Notification.d.ts +1 -2
- package/dist/components/Notification/components/Notification.js +34 -0
- package/dist/components/Notification/components/index.d.ts +2 -2
- package/dist/components/Notification/components/index.js +1 -0
- package/dist/components/Notification/index.d.ts +1 -1
- package/dist/components/Notification/index.js +1 -0
- package/dist/components/Progress/components/Progress.d.ts +0 -1
- package/dist/components/Progress/components/Progress.js +72 -0
- package/dist/components/Progress/components/index.d.ts +2 -2
- package/dist/components/Progress/components/index.js +1 -0
- package/dist/components/Progress/index.d.ts +2 -2
- package/dist/components/Progress/index.js +1 -0
- package/dist/components/RetroEffects/components/RetroEffects.d.ts +0 -1
- package/dist/components/RetroEffects/components/RetroEffects.js +163 -0
- package/dist/components/RetroEffects/components/index.d.ts +2 -2
- package/dist/components/RetroEffects/components/index.js +1 -0
- package/dist/components/RetroEffects/index.d.ts +2 -2
- package/dist/components/RetroEffects/index.js +1 -0
- package/dist/components/SectionHeading/components/SectionHeading.js +18 -0
- package/dist/components/SectionHeading/components/index.d.ts +2 -2
- package/dist/components/SectionHeading/components/index.js +1 -0
- package/dist/components/SectionHeading/index.d.ts +2 -2
- package/dist/components/SectionHeading/index.js +1 -0
- package/dist/components/Select/components/Select.js +14 -0
- package/dist/components/Select/components/index.d.ts +2 -2
- package/dist/components/Select/components/index.js +1 -0
- package/dist/components/Select/index.d.ts +2 -2
- package/dist/components/Select/index.js +1 -0
- package/dist/components/Separator/components/Separator.d.ts +0 -1
- package/dist/components/Separator/components/Separator.js +11 -0
- package/dist/components/Separator/components/index.d.ts +2 -2
- package/dist/components/Separator/components/index.js +1 -0
- package/dist/components/Separator/index.d.ts +2 -2
- package/dist/components/Separator/index.js +1 -0
- package/dist/components/Skeleton/components/Skeleton.d.ts +0 -1
- package/dist/components/Skeleton/components/Skeleton.js +52 -0
- package/dist/components/Skeleton/components/index.d.ts +2 -2
- package/dist/components/Skeleton/components/index.js +1 -0
- package/dist/components/Skeleton/index.d.ts +2 -2
- package/dist/components/Skeleton/index.js +1 -0
- package/dist/components/Stat/components/Stat.d.ts +0 -1
- package/dist/components/Stat/components/Stat.js +42 -0
- package/dist/components/Stat/components/index.d.ts +2 -2
- package/dist/components/Stat/components/index.js +1 -0
- package/dist/components/Stat/index.d.ts +2 -2
- package/dist/components/Stat/index.js +1 -0
- package/dist/components/Switch/components/Switch.d.ts +0 -1
- package/dist/components/Switch/components/Switch.js +11 -0
- package/dist/components/Switch/components/index.d.ts +2 -2
- package/dist/components/Switch/components/index.js +1 -0
- package/dist/components/Switch/index.d.ts +2 -2
- package/dist/components/Switch/index.js +1 -0
- package/dist/components/Tabs/components/Tabs.d.ts +0 -1
- package/dist/components/Tabs/components/Tabs.js +54 -0
- package/dist/components/Tabs/components/index.d.ts +2 -2
- package/dist/components/Tabs/components/index.js +1 -0
- package/dist/components/Tabs/index.d.ts +2 -2
- package/dist/components/Tabs/index.js +1 -0
- package/dist/components/Tag/components/Tag.d.ts +0 -1
- package/dist/components/Tag/components/Tag.js +46 -0
- package/dist/components/Tag/components/TagGroup.js +15 -0
- package/dist/components/Tag/components/index.d.ts +4 -4
- package/dist/components/Tag/components/index.js +2 -0
- package/dist/components/Tag/index.d.ts +2 -2
- package/dist/components/Tag/index.js +1 -0
- package/dist/components/Terminal/components/Terminal.d.ts +0 -2
- package/dist/components/Terminal/components/Terminal.js +43 -0
- package/dist/components/Terminal/components/index.d.ts +2 -2
- package/dist/components/Terminal/components/index.js +1 -0
- package/dist/components/Terminal/index.d.ts +1 -1
- package/dist/components/Terminal/index.js +1 -0
- package/dist/components/TextScramble/components/TextScramble.d.ts +0 -1
- package/dist/components/TextScramble/components/TextScramble.js +14 -0
- package/dist/components/TextScramble/components/index.d.ts +2 -2
- package/dist/components/TextScramble/components/index.js +1 -0
- package/dist/components/TextScramble/index.d.ts +1 -1
- package/dist/components/TextScramble/index.js +1 -0
- package/dist/components/TimelineContainer/components/MasterDetailLayout.d.ts +1 -3
- package/dist/components/TimelineContainer/components/MasterDetailLayout.js +77 -0
- package/dist/components/TimelineContainer/components/TimelineAxis.d.ts +0 -1
- package/dist/components/TimelineContainer/components/TimelineAxis.js +8 -0
- package/dist/components/TimelineContainer/components/TimelineContainer.d.ts +1 -3
- package/dist/components/TimelineContainer/components/TimelineContainer.js +218 -0
- package/dist/components/TimelineContainer/components/TimelineContent.d.ts +1 -1
- package/dist/components/TimelineContainer/components/TimelineContent.js +23 -0
- package/dist/components/TimelineContainer/components/TimelineEntryCard.d.ts +1 -2
- package/dist/components/TimelineContainer/components/TimelineEntryCard.js +50 -0
- package/dist/components/TimelineContainer/components/ZoomControls.d.ts +2 -3
- package/dist/components/TimelineContainer/components/ZoomControls.js +16 -0
- package/dist/components/TimelineContainer/components/index.d.ts +5 -5
- package/dist/components/TimelineContainer/components/index.js +2 -0
- package/dist/components/TimelineContainer/components/timelineUtils.d.ts +1 -1
- package/dist/components/TimelineContainer/components/timelineUtils.js +185 -0
- package/dist/components/TimelineContainer/components/types.js +7 -0
- package/dist/components/TimelineContainer/components/useDrillDown.d.ts +1 -1
- package/dist/components/TimelineContainer/components/useDrillDown.js +83 -0
- package/dist/components/TimelineContainer/components/useSelection.js +51 -0
- package/dist/components/TimelineContainer/components/useZoom.d.ts +1 -1
- package/dist/components/TimelineContainer/components/useZoom.js +54 -0
- package/dist/components/TimelineContainer/components/variants/TimelineEntryCardGallery.d.ts +1 -2
- package/dist/components/TimelineContainer/components/variants/TimelineEntryCardGallery.js +67 -0
- package/dist/components/TimelineContainer/components/variants/TimelineEntryCardImage.d.ts +1 -2
- package/dist/components/TimelineContainer/components/variants/TimelineEntryCardImage.js +31 -0
- package/dist/components/TimelineContainer/components/views/DayView.d.ts +1 -1
- package/dist/components/TimelineContainer/components/views/DayView.js +20 -0
- package/dist/components/TimelineContainer/components/views/HourView.d.ts +1 -1
- package/dist/components/TimelineContainer/components/views/HourView.js +22 -0
- package/dist/components/TimelineContainer/components/views/MonthView.d.ts +1 -1
- package/dist/components/TimelineContainer/components/views/MonthView.js +18 -0
- package/dist/components/TimelineContainer/components/views/YearView.d.ts +1 -1
- package/dist/components/TimelineContainer/components/views/YearView.js +13 -0
- package/dist/components/TimelineContainer/components/views/index.d.ts +4 -4
- package/dist/components/TimelineContainer/components/views/index.js +4 -0
- package/dist/components/TimelineContainer/index.d.ts +3 -3
- package/dist/components/TimelineContainer/index.js +2 -0
- package/dist/components/TimelineNode/components/TimelineNode.d.ts +0 -1
- package/dist/components/TimelineNode/components/TimelineNode.js +49 -0
- package/dist/components/TimelineNode/components/index.d.ts +2 -2
- package/dist/components/TimelineNode/components/index.js +1 -0
- package/dist/components/TimelineNode/index.d.ts +2 -2
- package/dist/components/TimelineNode/index.js +1 -0
- package/dist/components/registry.js +247 -0
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/index.js +3 -0
- package/dist/hooks/useAnimatedDismiss.d.ts +15 -0
- package/dist/hooks/useAnimatedDismiss.js +29 -0
- package/dist/hooks/useTextScramble.d.ts +21 -0
- package/dist/hooks/useTextScramble.js +91 -0
- package/dist/hooks/useTextScramble.test.d.ts +1 -0
- package/dist/hooks/useThemePortal.d.ts +12 -0
- package/dist/hooks/useThemePortal.js +62 -0
- package/dist/hooks/useThemePortal.test.d.ts +1 -0
- package/dist/utils/cn.d.ts +9 -0
- package/dist/utils/cn.js +12 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +2 -0
- package/dist/utils/isSafeHref.d.ts +1 -0
- package/dist/utils/isSafeHref.js +22 -0
- package/dist/utils/isSafeUrl.d.ts +8 -0
- package/dist/utils/isSafeUrl.js +18 -0
- package/dist/utils/isSafeUrl.test.d.ts +1 -0
- package/dist/utils/prefersReducedMotion.d.ts +5 -0
- package/dist/utils/prefersReducedMotion.js +8 -0
- package/package.json +9 -2
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Button as AriaButton } from 'react-aria-components';
|
|
4
|
+
import { cn } from '../../../utils/cn.js';
|
|
5
|
+
import { useAnimatedDismiss } from '../../../hooks/useAnimatedDismiss.js';
|
|
6
|
+
const sizeClasses = {
|
|
7
|
+
sm: 'text-dos-text-xs px-2 py-0.5 min-h-6 gap-1',
|
|
8
|
+
md: 'text-dos-text-sm px-2.5 py-1 min-h-7 gap-1.5',
|
|
9
|
+
lg: 'text-dos-text-md px-3 py-1.5 min-h-8 gap-1.5',
|
|
10
|
+
small: 'text-dos-text-xs px-2 py-0.5 min-h-6 gap-1',
|
|
11
|
+
medium: 'text-dos-text-sm px-2.5 py-1 min-h-7 gap-1.5',
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* DOS-styled Tag for interactive labels with click, close, and selection.
|
|
15
|
+
* Extends Badge with interaction behaviors. Uses useAnimatedDismiss for phosphor exit.
|
|
16
|
+
*/
|
|
17
|
+
export const Tag = ({ children, variant = 'default', size = 'md', color, selected = false, closeable = false, disabled = false, onClick, onClose, className, 'aria-label': ariaLabel, ...props }) => {
|
|
18
|
+
const isInteractive = !!onClick && !disabled;
|
|
19
|
+
const { isClosing, triggerClose, handleAnimationEnd } = useAnimatedDismiss('tag-exit', onClose);
|
|
20
|
+
const handleKeyDown = (event) => {
|
|
21
|
+
if (disabled)
|
|
22
|
+
return;
|
|
23
|
+
if ((event.key === 'Enter' || event.key === ' ') && onClick) {
|
|
24
|
+
event.preventDefault();
|
|
25
|
+
onClick(event);
|
|
26
|
+
}
|
|
27
|
+
if ((event.key === 'Delete' || event.key === 'Backspace') && closeable && onClose) {
|
|
28
|
+
event.preventDefault();
|
|
29
|
+
triggerClose();
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
// AriaButton's usePress isolates the press event — it calls stopPropagation
|
|
33
|
+
// internally, preventing the close from triggering the parent Tag's onClick.
|
|
34
|
+
const handleClosePress = () => {
|
|
35
|
+
if (!disabled)
|
|
36
|
+
triggerClose();
|
|
37
|
+
};
|
|
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'),
|
|
40
|
+
// Keep the close button out of the tab order only when the body is
|
|
41
|
+
// itself focusable (interactive) and already offers Delete/Backspace
|
|
42
|
+
// dismissal. For a closeable, non-interactive tag the body is not
|
|
43
|
+
// focusable, so the close button must stay tabbable to give keyboard
|
|
44
|
+
// users any dismiss path at all.
|
|
45
|
+
excludeFromTabOrder: isInteractive, onPress: handleClosePress, isDisabled: disabled, children: "[x]" }))] }));
|
|
46
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from '../../../utils/cn.js';
|
|
3
|
+
const gapClasses = {
|
|
4
|
+
tight: 'gap-1',
|
|
5
|
+
normal: 'gap-2',
|
|
6
|
+
loose: 'gap-3',
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Wrapper for rendering multiple Tag components with consistent spacing
|
|
10
|
+
*
|
|
11
|
+
* Provides flex layout with configurable gap and optional wrapping.
|
|
12
|
+
*/
|
|
13
|
+
export const TagGroup = ({ children, gap = 'normal', wrap = true, className, 'aria-label': ariaLabel, ...props }) => {
|
|
14
|
+
return (_jsx("div", { className: cn('flex', wrap ? 'flex-wrap' : 'flex-nowrap', gapClasses[gap] || gapClasses.normal, className), role: "group", "aria-label": ariaLabel, ...props, children: children }));
|
|
15
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { Tag } from './Tag';
|
|
2
|
-
export type { TagProps } from './Tag';
|
|
3
|
-
export { TagGroup } from './TagGroup';
|
|
4
|
-
export type { TagGroupProps } from './TagGroup';
|
|
1
|
+
export { Tag } from './Tag.js';
|
|
2
|
+
export type { TagProps } from './Tag.js';
|
|
3
|
+
export { TagGroup } from './TagGroup.js';
|
|
4
|
+
export type { TagGroupProps } from './TagGroup.js';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { Tag, TagGroup } from './components';
|
|
2
|
-
export type { TagProps, TagGroupProps } from './components';
|
|
1
|
+
export { Tag, TagGroup } from './components/index.js';
|
|
2
|
+
export type { TagProps, TagGroupProps } from './components/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Tag, TagGroup } from './components/index.js';
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import '../../../styles/keyframes.css';
|
|
3
|
-
import './Terminal.css';
|
|
4
2
|
export interface TerminalProps {
|
|
5
3
|
/** Size variant. Use sm/md/lg — small/medium/large are @deprecated aliases. */
|
|
6
4
|
size?: 'sm' | 'md' | 'lg' | 'small' | 'medium' | 'large';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useRef, useEffect } from 'react';
|
|
4
|
+
import { Button as AriaButton } from 'react-aria-components';
|
|
5
|
+
import { Icon } from '../../Icon/components/Icon.js';
|
|
6
|
+
export const Terminal = ({ size = 'md', title = 'MS-DOS Terminal', state = 'active', resizable = true, minimizable = false, maximizable = false, closeable = false, children, onMinimize, onMaximize, onClose, onFocus, className = '', autoFocus = false, }) => {
|
|
7
|
+
const sizeMap = { sm: 'small', md: 'medium', lg: 'large' };
|
|
8
|
+
const normalizedSize = sizeMap[size] ?? size;
|
|
9
|
+
const [windowState, setWindowState] = useState(state);
|
|
10
|
+
const [isMaximized, setIsMaximized] = useState(false);
|
|
11
|
+
const terminalRef = useRef(null);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (autoFocus && terminalRef.current) {
|
|
14
|
+
terminalRef.current.focus();
|
|
15
|
+
}
|
|
16
|
+
}, [autoFocus]);
|
|
17
|
+
const handleMinimize = () => {
|
|
18
|
+
setWindowState('minimized');
|
|
19
|
+
onMinimize?.();
|
|
20
|
+
};
|
|
21
|
+
const handleMaximize = () => {
|
|
22
|
+
setIsMaximized(!isMaximized);
|
|
23
|
+
onMaximize?.();
|
|
24
|
+
};
|
|
25
|
+
const handleClose = () => {
|
|
26
|
+
onClose?.();
|
|
27
|
+
};
|
|
28
|
+
const handleFocus = () => {
|
|
29
|
+
setWindowState('active');
|
|
30
|
+
onFocus?.();
|
|
31
|
+
};
|
|
32
|
+
const handleKeyDown = (event) => {
|
|
33
|
+
// Handle Alt+F4 for closing (DOS convention)
|
|
34
|
+
if (event.altKey && event.key === 'F4' && closeable) {
|
|
35
|
+
event.preventDefault();
|
|
36
|
+
handleClose();
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
if (windowState === 'minimized') {
|
|
40
|
+
return (_jsx(AriaButton, { className: `terminal terminal--minimized ${className}`.trim(), onPress: handleFocus, "aria-label": `Restore ${title} window`, children: _jsxs("div", { className: "terminal__taskbar-item", children: [_jsx(Icon, { name: "App", size: "S" }), _jsx("span", { className: "terminal__taskbar-title", children: title })] }) }));
|
|
41
|
+
}
|
|
42
|
+
return (_jsxs("div", { ref: terminalRef, className: `terminal terminal--${normalizedSize} terminal--${windowState} ${isMaximized ? 'terminal--maximized' : ''} ${className}`.trim(), tabIndex: 0, role: "dialog", "aria-label": `${title} terminal window`, onFocus: handleFocus, onKeyDown: handleKeyDown, children: [_jsxs("div", { className: "terminal__title-bar", children: [_jsxs("div", { className: "terminal__title", children: [_jsx(Icon, { name: "App", size: "S", "aria-hidden": "true" }), _jsx("span", { className: "terminal__title-text", children: title })] }), (minimizable || maximizable || closeable) && (_jsxs("div", { className: "terminal__controls", children: [minimizable && (_jsx(AriaButton, { className: "terminal__control terminal__control--minimize", onPress: handleMinimize, "aria-label": "Minimize window", children: _jsx(Icon, { name: "Cancel", size: "S" }) })), maximizable && (_jsx(AriaButton, { className: "terminal__control terminal__control--maximize", onPress: handleMaximize, "aria-label": isMaximized ? "Restore window" : "Maximize window", children: _jsx(Icon, { name: isMaximized ? "Fullscreen" : "Add", size: "S" }) })), closeable && (_jsx(AriaButton, { className: "terminal__control terminal__control--close", onPress: handleClose, "aria-label": "Close window", children: _jsx(Icon, { name: "Close", size: "S" }) }))] }))] }), _jsx("div", { className: "terminal__content", role: "main", children: children || (_jsx("div", { className: "terminal__default-content", children: _jsxs("div", { className: "terminal__prompt", children: [_jsx("span", { className: "terminal__path", children: "C:\\\\>" }), _jsx("span", { className: "terminal__cursor", children: "\u2588" })] }) })) }), resizable && (_jsx("div", { className: "terminal__resize-handle", "aria-hidden": "true" }))] }));
|
|
43
|
+
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { Terminal } from './Terminal';
|
|
2
|
-
export type { TerminalProps } from './Terminal';
|
|
1
|
+
export { Terminal } from './Terminal.js';
|
|
2
|
+
export type { TerminalProps } from './Terminal.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Terminal } from './Terminal.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from './components';
|
|
1
|
+
export * from './components/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './components/index.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef } from 'react';
|
|
4
|
+
import { cn } from '../../../utils/cn.js';
|
|
5
|
+
import { useTextScramble } from '../../../hooks/useTextScramble.js';
|
|
6
|
+
export const TextScramble = forwardRef(({ children, speed, characters, delay, className, ...props }, ref) => {
|
|
7
|
+
const { text, isScrambling } = useTextScramble(children, {
|
|
8
|
+
speed,
|
|
9
|
+
characters,
|
|
10
|
+
delay,
|
|
11
|
+
});
|
|
12
|
+
return (_jsx("span", { ref: ref, className: cn('eidotter-text-scramble', isScrambling && 'eidotter-text-scramble--scrambling', className), ...props, children: text }));
|
|
13
|
+
});
|
|
14
|
+
TextScramble.displayName = 'TextScramble';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { TextScramble } from './TextScramble';
|
|
2
|
-
export type { TextScrambleProps } from './TextScramble';
|
|
1
|
+
export { TextScramble } from './TextScramble.js';
|
|
2
|
+
export type { TextScrambleProps } from './TextScramble.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { TextScramble } from './TextScramble.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from './components';
|
|
1
|
+
export * from './components/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './components/index.js';
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { TimelineEntryData, TimelineRenderEntry } from './types';
|
|
3
|
-
import '../../../styles/keyframes.css';
|
|
4
|
-
import './MasterDetailLayout.css';
|
|
2
|
+
import type { TimelineEntryData, TimelineRenderEntry } from './types.js';
|
|
5
3
|
export interface MasterDetailLayoutProps {
|
|
6
4
|
/** Pre-sorted entries (the orchestrator sorts; the rail renders in order). */
|
|
7
5
|
entries: TimelineEntryData[];
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
4
|
+
import { cn } from '../../../utils/cn.js';
|
|
5
|
+
import { TimelineNode } from '../../TimelineNode/components/TimelineNode.js';
|
|
6
|
+
import { TimelineEntryCard } from './TimelineEntryCard.js';
|
|
7
|
+
/**
|
|
8
|
+
* Level-3 "master-detail" timeline layout (DMNC-878).
|
|
9
|
+
*
|
|
10
|
+
* A split view: a navigable timeline rail (left) plus a detail pane (right)
|
|
11
|
+
* that renders the selected entry in full. Switching entries swaps the detail
|
|
12
|
+
* pane without remounting the rail, so rail scroll position is preserved.
|
|
13
|
+
*
|
|
14
|
+
* Selection is owned by the orchestrator (controlled/uncontrolled via
|
|
15
|
+
* `useSelection`), so the open entry is deep-linkable by consumers. The rail
|
|
16
|
+
* uses navigation semantics (`aria-current`), arrow keys walk entries, and
|
|
17
|
+
* focus moves into the pane on open and back to the rail on close — without a
|
|
18
|
+
* focus trap, so both panes stay reachable.
|
|
19
|
+
*/
|
|
20
|
+
export const MasterDetailLayout = ({ entries, selectedEntryId, onSelect, onDeselect, renderEntry, formatDate, }) => {
|
|
21
|
+
const selectedEntry = entries.find((e) => e.id === selectedEntryId) ?? null;
|
|
22
|
+
const railRef = useRef(null);
|
|
23
|
+
const detailRef = useRef(null);
|
|
24
|
+
// Focus management (DMNC-878): move focus into the detail pane when an entry
|
|
25
|
+
// opens, and restore focus to the active rail row when it closes. Mirrors the
|
|
26
|
+
// requestAnimationFrame focus-move pattern used for drill-down transitions.
|
|
27
|
+
const prevSelectedRef = useRef(selectedEntryId);
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
const prev = prevSelectedRef.current;
|
|
30
|
+
prevSelectedRef.current = selectedEntryId;
|
|
31
|
+
if (selectedEntryId && selectedEntryId !== prev) {
|
|
32
|
+
requestAnimationFrame(() => detailRef.current?.focus());
|
|
33
|
+
}
|
|
34
|
+
else if (!selectedEntryId && prev) {
|
|
35
|
+
requestAnimationFrame(() => {
|
|
36
|
+
railRef.current
|
|
37
|
+
?.querySelector(`[data-entry-id="${prev}"]`)
|
|
38
|
+
?.focus();
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}, [selectedEntryId]);
|
|
42
|
+
// Arrow-key walk over the flat sorted entries (mirrors CmdPalette). With
|
|
43
|
+
// nothing selected, ArrowDown opens the first entry and ArrowUp the last.
|
|
44
|
+
const moveSelection = useCallback((delta) => {
|
|
45
|
+
if (entries.length === 0)
|
|
46
|
+
return;
|
|
47
|
+
const idx = entries.findIndex((e) => e.id === selectedEntryId);
|
|
48
|
+
const base = idx === -1 ? (delta > 0 ? -1 : entries.length) : idx;
|
|
49
|
+
const next = Math.max(0, Math.min(entries.length - 1, base + delta));
|
|
50
|
+
onSelect(entries[next].id);
|
|
51
|
+
}, [entries, selectedEntryId, onSelect]);
|
|
52
|
+
const handleRailKeyDown = useCallback((e) => {
|
|
53
|
+
if (e.key === 'ArrowDown') {
|
|
54
|
+
e.preventDefault();
|
|
55
|
+
moveSelection(1);
|
|
56
|
+
}
|
|
57
|
+
else if (e.key === 'ArrowUp') {
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
moveSelection(-1);
|
|
60
|
+
}
|
|
61
|
+
else if (e.key === 'Escape' && selectedEntryId) {
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
onDeselect();
|
|
64
|
+
}
|
|
65
|
+
}, [moveSelection, onDeselect, selectedEntryId]);
|
|
66
|
+
const renderDetailBody = (entry) => {
|
|
67
|
+
const defaultRender = () => (_jsx(TimelineEntryCard, { entry: entry, isSelected: true, isExpanded: true }));
|
|
68
|
+
return renderEntry
|
|
69
|
+
? renderEntry(entry, { isExpanded: true, isSelected: true, defaultRender })
|
|
70
|
+
: defaultRender();
|
|
71
|
+
};
|
|
72
|
+
return (_jsxs("div", { className: cn('eidotter-timeline-md', selectedEntry && 'eidotter-timeline-md--detail-open'), children: [_jsx("nav", { ref: railRef, className: "eidotter-timeline-md__rail", "aria-label": "Timeline entries", onKeyDown: handleRailKeyDown, children: _jsx("ul", { className: "eidotter-timeline-md__rail-list", children: entries.map((entry) => {
|
|
73
|
+
const isActive = entry.id === selectedEntryId;
|
|
74
|
+
return (_jsx("li", { className: "eidotter-timeline-md__rail-item", children: _jsxs("button", { type: "button", "data-entry-id": entry.id, className: cn('eidotter-timeline-md__rail-button', isActive && 'eidotter-timeline-md__rail-button--active'), "aria-current": isActive ? 'true' : undefined, onClick: () => onSelect(entry.id), children: [_jsx("span", { className: "eidotter-timeline-md__rail-node", "aria-hidden": "true", children: _jsx(TimelineNode, { shape: "circle", size: "sm", variant: "default", isActive: isActive }) }), _jsxs("span", { className: "eidotter-timeline-md__rail-text", children: [_jsx("span", { className: "eidotter-timeline-md__rail-date", children: formatDate(entry.date) }), _jsx("span", { className: "eidotter-timeline-md__rail-title", children: entry.title })] })] }) }, entry.id));
|
|
75
|
+
}) }) }), _jsx("div", { ref: detailRef, className: "eidotter-timeline-md__detail", role: "region", "aria-label": "Entry detail", tabIndex: -1, children: selectedEntry ? (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "eidotter-timeline-md__back", onClick: onDeselect, children: "\u2039 BACK" }), _jsx("div", { className: "eidotter-timeline-md__detail-body", children: renderDetailBody(selectedEntry) }, selectedEntry.id)] })) : (_jsxs("div", { className: "eidotter-timeline-md__detail-empty", role: "status", children: [_jsx("p", { children: "C:\\TIMELINE> Select an entry to read." }), _jsx("p", { children: "_" })] })) })] }));
|
|
76
|
+
};
|
|
77
|
+
MasterDetailLayout.displayName = 'MasterDetailLayout';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Vertical spine connecting timeline nodes.
|
|
4
|
+
* Wraps content with a continuous vertical line on the left side.
|
|
5
|
+
*/
|
|
6
|
+
export const TimelineAxis = ({ children }) => {
|
|
7
|
+
return (_jsxs("div", { className: "timeline-axis", children: [_jsx("div", { className: "timeline-axis__line", "aria-hidden": "true" }), _jsx("div", { className: "timeline-axis__content", children: children })] }));
|
|
8
|
+
};
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { TimelineEntryData, TimelineRenderEntry, ZoomLevel } from './types';
|
|
3
|
-
import './TimelineContainer.css';
|
|
4
|
-
import './views/views.css';
|
|
2
|
+
import type { TimelineEntryData, TimelineRenderEntry, ZoomLevel } from './types.js';
|
|
5
3
|
export interface TimelineContainerProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
6
4
|
/** Timeline entries to display */
|
|
7
5
|
entries: TimelineEntryData[];
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { cn } from '../../../utils/cn.js';
|
|
5
|
+
import { useDrillDown } from './useDrillDown.js';
|
|
6
|
+
import { useSelection } from './useSelection.js';
|
|
7
|
+
import { groupEntriesByZoom, filterBucketsByPeriod } from './timelineUtils.js';
|
|
8
|
+
import { ZoomControls } from './ZoomControls.js';
|
|
9
|
+
import { TimelineAxis } from './TimelineAxis.js';
|
|
10
|
+
import { TimelineContent } from './TimelineContent.js';
|
|
11
|
+
import { TimelineNode } from '../../TimelineNode/components/TimelineNode.js';
|
|
12
|
+
import { TimelineEntryCard } from './TimelineEntryCard.js';
|
|
13
|
+
import { MasterDetailLayout } from './MasterDetailLayout.js';
|
|
14
|
+
/**
|
|
15
|
+
* TimelineContainer - Interactive multi-level zoom timeline
|
|
16
|
+
*
|
|
17
|
+
* A composite timeline component with 4 zoom levels (year, month, day, hour),
|
|
18
|
+
* entry selection, keyboard shortcuts, scroll-to-zoom, and drill-down navigation.
|
|
19
|
+
* Uses DOS/CGA aesthetic with eidotter primitives.
|
|
20
|
+
*
|
|
21
|
+
* Supports both controlled and uncontrolled patterns for zoom and selection.
|
|
22
|
+
* When zoom is uncontrolled, clicking a bucket drills down into that time period.
|
|
23
|
+
*/
|
|
24
|
+
export const TimelineContainer = ({ entries, mode = 'interactive', zoomLevel: controlledZoom, defaultZoomLevel = 'month', onZoomChange, selectedEntryId: controlledSelection, defaultSelectedEntryId, onSelectEntry, sortOrder = 'desc', scrollToZoom = true, keyboardShortcuts = true, renderEntry, pageSize = 10, onLoadMore, ...props }) => {
|
|
25
|
+
const isStatic = mode === 'static';
|
|
26
|
+
const isFeed = mode === 'feed';
|
|
27
|
+
const isVerticalFeed = isStatic || isFeed;
|
|
28
|
+
// Master-detail (DMNC-878) is NOT a vertical feed — it has its own split
|
|
29
|
+
// layout, sort path, focus model, and keyboard handling — but it shares the
|
|
30
|
+
// feed modes' suppression of zoom controls / scroll-to-zoom / drill-down.
|
|
31
|
+
// Each `isVerticalFeed` gate is audited individually rather than widened.
|
|
32
|
+
const isMasterDetail = mode === 'master-detail';
|
|
33
|
+
const containerRef = useRef(null);
|
|
34
|
+
const contentRef = useRef(null);
|
|
35
|
+
// Drill-down announcement for screen readers
|
|
36
|
+
const [announcement, setAnnouncement] = useState('');
|
|
37
|
+
const { zoomLevel, canZoomIn, canZoomOut, breadcrumbs, currentPeriod, isDrillDownEnabled, drillDown, drillUp, reset, } = useDrillDown({
|
|
38
|
+
zoomLevel: controlledZoom,
|
|
39
|
+
defaultZoomLevel,
|
|
40
|
+
onZoomChange,
|
|
41
|
+
});
|
|
42
|
+
const { selectedEntryId, select, toggle, deselect, } = useSelection({
|
|
43
|
+
selectedEntryId: controlledSelection,
|
|
44
|
+
defaultSelectedEntryId,
|
|
45
|
+
onSelectEntry,
|
|
46
|
+
});
|
|
47
|
+
// Group entries by zoom level, then filter by drill-down period
|
|
48
|
+
const allBuckets = useMemo(() => groupEntriesByZoom(entries, zoomLevel, sortOrder), [entries, zoomLevel, sortOrder]);
|
|
49
|
+
// Determine parent zoom level for filtering
|
|
50
|
+
const parentZoomLevel = breadcrumbs.length > 0
|
|
51
|
+
? ['year', 'month', 'day', 'hour'][breadcrumbs.length - 1]
|
|
52
|
+
: null;
|
|
53
|
+
const buckets = useMemo(() => currentPeriod && parentZoomLevel
|
|
54
|
+
? filterBucketsByPeriod(allBuckets, currentPeriod, parentZoomLevel)
|
|
55
|
+
: allBuckets, [allBuckets, currentPeriod, parentZoomLevel]);
|
|
56
|
+
// Drill into a specific bucket — shared by bucket clicks, the [+] zoom-in
|
|
57
|
+
// control, and the Ctrl/Cmd+= shortcut.
|
|
58
|
+
const drillIntoBucket = useCallback((bucket) => {
|
|
59
|
+
if (!canZoomIn)
|
|
60
|
+
return;
|
|
61
|
+
drillDown(bucket.periodStart, bucket.label);
|
|
62
|
+
setAnnouncement(`Showing ${zoomLevel === 'year' ? 'months' : zoomLevel === 'month' ? 'days' : 'hours'} in ${bucket.label}`);
|
|
63
|
+
}, [canZoomIn, drillDown, zoomLevel]);
|
|
64
|
+
// Generic zoom-in has no inherent target (unlike drill-up, which pops the
|
|
65
|
+
// stack), so the [+] control and Ctrl/Cmd+= drill into the first bucket of the
|
|
66
|
+
// current view — the most recent period under the default sortOrder. Clicking
|
|
67
|
+
// a specific bucket still drills into that exact period.
|
|
68
|
+
const handleZoomIn = useCallback(() => {
|
|
69
|
+
if (buckets.length > 0)
|
|
70
|
+
drillIntoBucket(buckets[0]);
|
|
71
|
+
}, [buckets, drillIntoBucket]);
|
|
72
|
+
// Scroll-to-zoom: Ctrl/Cmd + wheel (interactive mode only)
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (isVerticalFeed || isMasterDetail || !scrollToZoom)
|
|
75
|
+
return;
|
|
76
|
+
const el = containerRef.current;
|
|
77
|
+
if (!el)
|
|
78
|
+
return;
|
|
79
|
+
let rafId = null;
|
|
80
|
+
const handleWheel = (e) => {
|
|
81
|
+
if (!e.ctrlKey && !e.metaKey)
|
|
82
|
+
return;
|
|
83
|
+
e.preventDefault();
|
|
84
|
+
if (rafId !== null)
|
|
85
|
+
return;
|
|
86
|
+
rafId = requestAnimationFrame(() => {
|
|
87
|
+
// Scroll-down zooms out (pops drill-down stack).
|
|
88
|
+
// Scroll-up is a no-op — drill-down requires clicking a specific
|
|
89
|
+
// bucket to know which period to enter. Scroll has no period context.
|
|
90
|
+
if (e.deltaY > 0) {
|
|
91
|
+
drillUp();
|
|
92
|
+
}
|
|
93
|
+
rafId = null;
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
el.addEventListener('wheel', handleWheel, { passive: false });
|
|
97
|
+
return () => {
|
|
98
|
+
el.removeEventListener('wheel', handleWheel);
|
|
99
|
+
if (rafId !== null)
|
|
100
|
+
cancelAnimationFrame(rafId);
|
|
101
|
+
};
|
|
102
|
+
}, [isVerticalFeed, isMasterDetail, scrollToZoom, isDrillDownEnabled, canZoomIn, drillUp]);
|
|
103
|
+
// Keyboard shortcuts (interactive mode only). Master-detail has its own
|
|
104
|
+
// keyboard model (arrow-walk + Escape) inside MasterDetailLayout, so the
|
|
105
|
+
// zoom-shortcut handler is suppressed for it as well.
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
if (isVerticalFeed || isMasterDetail || !keyboardShortcuts)
|
|
108
|
+
return;
|
|
109
|
+
const handleKeyDown = (e) => {
|
|
110
|
+
if (!containerRef.current?.contains(document.activeElement) &&
|
|
111
|
+
document.activeElement !== containerRef.current) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const isMod = e.ctrlKey || e.metaKey;
|
|
115
|
+
if (isMod && (e.key === '=' || e.key === '+')) {
|
|
116
|
+
e.preventDefault();
|
|
117
|
+
handleZoomIn();
|
|
118
|
+
}
|
|
119
|
+
else if (isMod && e.key === '-') {
|
|
120
|
+
e.preventDefault();
|
|
121
|
+
drillUp();
|
|
122
|
+
}
|
|
123
|
+
else if (isMod && e.key === '0') {
|
|
124
|
+
e.preventDefault();
|
|
125
|
+
reset();
|
|
126
|
+
setAnnouncement('Showing all years');
|
|
127
|
+
}
|
|
128
|
+
else if (e.key === 'Escape') {
|
|
129
|
+
e.preventDefault();
|
|
130
|
+
deselect();
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
134
|
+
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
135
|
+
}, [isVerticalFeed, isMasterDetail, keyboardShortcuts, handleZoomIn, drillUp, reset, deselect]);
|
|
136
|
+
// Bucket click triggers drill-down
|
|
137
|
+
const handleBucketClick = useCallback((bucket) => drillIntoBucket(bucket), [drillIntoBucket]);
|
|
138
|
+
// Focus management: move focus after drill-down transitions
|
|
139
|
+
const prevBreadcrumbLengthRef = useRef(breadcrumbs.length);
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
if (breadcrumbs.length !== prevBreadcrumbLengthRef.current) {
|
|
142
|
+
prevBreadcrumbLengthRef.current = breadcrumbs.length;
|
|
143
|
+
// Focus first interactive element in the content area
|
|
144
|
+
const firstTrigger = contentRef.current?.querySelector('.eidotter-timeline-card__trigger, .timeline-view__bucket-button, .eidotter-timeline-node[role="button"]');
|
|
145
|
+
if (firstTrigger) {
|
|
146
|
+
requestAnimationFrame(() => firstTrigger.focus());
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}, [breadcrumbs.length]);
|
|
150
|
+
// Sort entries for the flat-list modes (static, feed, and master-detail —
|
|
151
|
+
// the rail and arrow-walk need a stable sorted order).
|
|
152
|
+
const sortedEntries = useMemo(() => {
|
|
153
|
+
if (!isVerticalFeed && !isMasterDetail)
|
|
154
|
+
return entries;
|
|
155
|
+
return [...entries].sort((a, b) => {
|
|
156
|
+
const cmp = a.date.localeCompare(b.date);
|
|
157
|
+
return sortOrder === 'desc' ? -cmp : cmp;
|
|
158
|
+
});
|
|
159
|
+
}, [isVerticalFeed, isMasterDetail, entries, sortOrder]);
|
|
160
|
+
// Pagination for feed mode. Clamp pageSize at the boundary — pageSize=0 or
|
|
161
|
+
// negative would otherwise produce a permanent LOAD MORE button that never
|
|
162
|
+
// advances. Step is always ≥1.
|
|
163
|
+
const safePageSize = Math.max(1, pageSize);
|
|
164
|
+
const [visibleCount, setVisibleCount] = useState(safePageSize);
|
|
165
|
+
// Clamp visibleCount when entries shrink. Crucially, do NOT reset to
|
|
166
|
+
// pageSize when entries grow — consumers using `onLoadMore` to fetch and
|
|
167
|
+
// append the next batch (the documented backend-pagination flow) need
|
|
168
|
+
// visibleCount to stay where the user clicked. Reset to pageSize only
|
|
169
|
+
// happens when the new entries length is below the current visibleCount.
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
if (!isFeed)
|
|
172
|
+
return;
|
|
173
|
+
setVisibleCount((prev) => {
|
|
174
|
+
const max = sortedEntries.length;
|
|
175
|
+
if (max === 0)
|
|
176
|
+
return safePageSize;
|
|
177
|
+
if (prev > max)
|
|
178
|
+
return Math.max(safePageSize, max);
|
|
179
|
+
return prev;
|
|
180
|
+
});
|
|
181
|
+
}, [isFeed, safePageSize, sortedEntries.length]);
|
|
182
|
+
const visibleEntries = useMemo(() => (isFeed ? sortedEntries.slice(0, visibleCount) : sortedEntries), [isFeed, sortedEntries, visibleCount]);
|
|
183
|
+
const hasMore = isFeed && visibleCount < sortedEntries.length;
|
|
184
|
+
const handleLoadMore = useCallback(() => {
|
|
185
|
+
const next = Math.min(visibleCount + safePageSize, sortedEntries.length);
|
|
186
|
+
if (next <= visibleCount)
|
|
187
|
+
return; // already at cap — no-op, no callback
|
|
188
|
+
setVisibleCount(next);
|
|
189
|
+
onLoadMore?.(next);
|
|
190
|
+
}, [visibleCount, safePageSize, sortedEntries.length, onLoadMore]);
|
|
191
|
+
const dateFormatter = useMemo(() => new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'short', day: 'numeric' }), []);
|
|
192
|
+
const formatDate = (iso) => {
|
|
193
|
+
try {
|
|
194
|
+
return dateFormatter.format(new Date(iso));
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return iso;
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
return (_jsxs("div", { ref: containerRef, ...props, className: cn('font-dos text-dos-text-brand p-4 min-h-[200px]', 'eidotter-timeline-container', isStatic && 'eidotter-timeline-container--static', isFeed && 'eidotter-timeline-container--feed', isMasterDetail && 'eidotter-timeline-container--master-detail', props.className), role: "region", "aria-label": props['aria-label'] ?? 'Timeline', tabIndex: isVerticalFeed || isMasterDetail ? undefined : 0, children: [_jsx("div", { className: "sr-only", role: "status", "aria-live": "polite", "aria-atomic": "true", children: announcement }), !isVerticalFeed && !isMasterDetail && (_jsx(ZoomControls, { zoomLevel: zoomLevel, canZoomIn: canZoomIn, canZoomOut: canZoomOut, onZoomIn: handleZoomIn, onZoomOut: drillUp, onReset: reset, breadcrumbs: isDrillDownEnabled ? breadcrumbs : [], onBreadcrumbClick: (index) => {
|
|
201
|
+
// Navigate to a specific breadcrumb level
|
|
202
|
+
const stepsBack = breadcrumbs.length - 1 - index;
|
|
203
|
+
for (let i = 0; i < stepsBack; i++) {
|
|
204
|
+
drillUp();
|
|
205
|
+
}
|
|
206
|
+
} })), _jsx("div", { ref: contentRef, children: entries.length === 0 ? (_jsxs("div", { className: "eidotter-timeline-container__empty", role: "status", children: [_jsx("p", { children: "C:\\TIMELINE> No entries found." }), _jsx("p", { children: "_" })] })) : isStatic ? (_jsx(TimelineAxis, { children: _jsx("div", { className: "eidotter-timeline-container__static", role: "list", "aria-label": "Timeline", children: sortedEntries.map((entry) => {
|
|
207
|
+
const defaultRender = () => (_jsx(TimelineEntryCard, { entry: entry, isSelected: false, isExpanded: true }));
|
|
208
|
+
return (_jsxs("div", { className: "eidotter-timeline-container__static-entry", role: "listitem", children: [_jsx("div", { className: "timeline-view__node", children: _jsx(TimelineNode, { shape: "circle", size: "medium", variant: "default", label: formatDate(entry.date), labelPosition: "right" }) }), renderEntry
|
|
209
|
+
? renderEntry(entry, { isExpanded: true, isSelected: false, defaultRender })
|
|
210
|
+
: defaultRender()] }, entry.id));
|
|
211
|
+
}) }) })) : isFeed ? (_jsxs(TimelineAxis, { children: [_jsx("div", { className: "eidotter-timeline-container__feed", role: "list", "aria-label": "Timeline", children: visibleEntries.map((entry) => {
|
|
212
|
+
const isSelected = selectedEntryId === entry.id;
|
|
213
|
+
const defaultRender = () => (_jsx(TimelineEntryCard, { entry: entry, isSelected: isSelected, isExpanded: isSelected, onSelect: toggle }));
|
|
214
|
+
return (_jsxs("div", { className: "eidotter-timeline-container__feed-entry", role: "listitem", children: [_jsx("div", { className: "timeline-view__node", children: _jsx(TimelineNode, { shape: "circle", size: "medium", variant: "default", label: formatDate(entry.date), labelPosition: "right" }) }), renderEntry
|
|
215
|
+
? renderEntry(entry, { isExpanded: isSelected, isSelected, defaultRender })
|
|
216
|
+
: defaultRender()] }, entry.id));
|
|
217
|
+
}) }), hasMore && (_jsx("button", { type: "button", className: "eidotter-timeline-container__load-more", onClick: handleLoadMore, "aria-label": `Load more entries (showing ${visibleCount} of ${sortedEntries.length})`, children: "LOAD MORE..." }))] })) : isMasterDetail ? (_jsx(MasterDetailLayout, { entries: sortedEntries, selectedEntryId: selectedEntryId, onSelect: select, onDeselect: deselect, renderEntry: renderEntry, formatDate: formatDate })) : buckets.length === 0 && currentPeriod ? (_jsx(TimelineAxis, { children: _jsxs("div", { className: "eidotter-timeline-container__empty", role: "status", children: [_jsxs("p", { children: ["C:\\TIMELINE> No entries in ", breadcrumbs[breadcrumbs.length - 1]?.label ?? 'this period', "."] }), _jsx("p", { children: "_" })] }) })) : (_jsx(TimelineAxis, { children: _jsx(TimelineContent, { zoomLevel: zoomLevel, buckets: buckets, selectedEntryId: selectedEntryId, onEntrySelect: toggle, onBucketClick: handleBucketClick, renderEntry: renderEntry }) })) })] }));
|
|
218
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { ZoomLevel, DateBucket, TimelineRenderEntry } from './types';
|
|
2
|
+
import type { ZoomLevel, DateBucket, TimelineRenderEntry } from './types.js';
|
|
3
3
|
export interface TimelineContentProps {
|
|
4
4
|
zoomLevel: ZoomLevel;
|
|
5
5
|
buckets: readonly DateBucket[];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { YearView, MonthView, DayView, HourView } from './views/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* View switcher that renders the appropriate zoom level view.
|
|
6
|
+
*/
|
|
7
|
+
export const TimelineContent = ({ zoomLevel, buckets, selectedEntryId, onEntrySelect, onBucketClick, renderEntry, }) => {
|
|
8
|
+
switch (zoomLevel) {
|
|
9
|
+
case 'year':
|
|
10
|
+
// Year view shows bucket counts, not individual entries — renderEntry not applicable.
|
|
11
|
+
return _jsx(YearView, { buckets: buckets, onBucketClick: onBucketClick });
|
|
12
|
+
case 'month':
|
|
13
|
+
return (_jsx(MonthView, { buckets: buckets, selectedEntryId: selectedEntryId, onEntrySelect: onEntrySelect, onBucketClick: onBucketClick, renderEntry: renderEntry }));
|
|
14
|
+
case 'day':
|
|
15
|
+
return (_jsx(DayView, { buckets: buckets, selectedEntryId: selectedEntryId, onEntrySelect: onEntrySelect, renderEntry: renderEntry }));
|
|
16
|
+
case 'hour':
|
|
17
|
+
return (_jsx(HourView, { buckets: buckets, selectedEntryId: selectedEntryId, onEntrySelect: onEntrySelect, renderEntry: renderEntry }));
|
|
18
|
+
default: {
|
|
19
|
+
const _exhaustive = zoomLevel;
|
|
20
|
+
return _exhaustive;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
};
|