eidotter 0.20.1 → 0.22.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/LICENSE.md +8 -2
- package/README.md +3 -3
- package/dist/components/LegalPage/components/LegalPage.d.ts +31 -0
- package/dist/components/LegalPage/components/clauses.d.ts +54 -0
- package/dist/components/LegalPage/components/index.d.ts +4 -0
- package/dist/components/LegalPage/index.d.ts +2 -0
- package/dist/components/Lightbox/components/Lightbox.d.ts +23 -0
- package/dist/components/Lightbox/components/index.d.ts +2 -0
- package/dist/components/Lightbox/index.d.ts +2 -0
- package/dist/components/TimelineContainer/components/TimelineContainer.d.ts +27 -3
- package/dist/components/TimelineContainer/components/TimelineContent.d.ts +2 -1
- package/dist/components/TimelineContainer/components/TimelineEntryCard.d.ts +9 -5
- package/dist/components/TimelineContainer/components/index.d.ts +1 -1
- package/dist/components/TimelineContainer/components/timelineUtils.d.ts +2 -2
- package/dist/components/TimelineContainer/components/types.d.ts +63 -6
- package/dist/components/TimelineContainer/components/variants/TimelineEntryCardGallery.d.ts +18 -0
- package/dist/components/TimelineContainer/components/variants/TimelineEntryCardImage.d.ts +21 -0
- package/dist/components/TimelineContainer/index.d.ts +1 -1
- package/dist/components/registry.d.ts +1 -1
- package/dist/eidotter.css +2 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.es.js +11118 -12699
- package/dist/index.umd.js +4 -21
- package/guidelines/README.md +2 -2
- package/package.json +4 -2
- package/src/styles/fonts/Perfect_DOS_VGA_437.ttf +0 -0
- package/src/styles/fonts/Perfect_DOS_VGA_437_LICENSE.txt +104 -0
- package/src/styles/fonts.css +10 -4
- package/src/styles/theme.amber-mono.css +4 -2
- package/src/styles/theme.cga-amber.css +4 -2
- package/src/styles/theme.cga-mode4-p0.css +4 -2
- package/src/styles/theme.cga-mode4-p1.css +4 -2
- package/src/styles/theme.cga-mode5.css +4 -2
- package/src/styles/tokens.css +4 -2
- package/tailwind.preset.cjs +2 -1
package/LICENSE.md
CHANGED
|
@@ -100,8 +100,14 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
100
100
|
SOFTWARE.
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
##
|
|
103
|
+
## Perfect DOS VGA 437
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
Perfect DOS VGA 437 by [Zeh Fernando](https://zehfernando.com) (2002, updated 2008) — a pixel-perfect-vector TrueType font emulating the MS-DOS/Text mode VGA font with the 437 codepage. Every glyph outline is axis-aligned (no curves); scales cleanly at any size.
|
|
106
|
+
|
|
107
|
+
Bundled as `src/styles/fonts/Perfect_DOS_VGA_437.ttf` and loaded via `src/styles/fonts.css` (eiDotter v0.22.0 onwards). Free to redistribute and use in any project per the author's license: `src/styles/fonts/Perfect_DOS_VGA_437_LICENSE.txt`. Selling the font as a standalone is not permitted (eiDotter does not — it is bundled as part of the free design system).
|
|
108
|
+
|
|
109
|
+
## Flexi IBM VGA True (legacy, bundled until v0.22.x)
|
|
110
|
+
|
|
111
|
+
Flexi IBM VGA True v2 — bitmap-style remake of the IBM VGA BIOS font by VileR (int10h.org). Bundled as `src/styles/fonts/Flexi_IBM_VGA_True.ttf` (no longer loaded by default after v0.22.0; superseded by Perfect DOS VGA 437).
|
|
106
112
|
|
|
107
113
|
Licensed under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/). Full license text and attribution notice: `src/styles/fonts/Flexi_IBM_VGA_LICENSE.txt`.
|
package/README.md
CHANGED
|
@@ -86,7 +86,7 @@ The preset auto-registers `tailwindcss-react-aria-components` and `tailwindcss-a
|
|
|
86
86
|
| **Live docs** | [Storybook](https://cinimody.github.io/eiDotter/) — every component with stories, controls, and source |
|
|
87
87
|
| **Design tokens** | `src/tokens/*.json` (DTCG format) → `tokens.css` via Style Dictionary |
|
|
88
88
|
| **Themes** | `amber-mono` (default), `cga-amber`, `cga-mode4-p0/p1`, `cga-mode5` |
|
|
89
|
-
| **Font** | [
|
|
89
|
+
| **Font** | [Perfect DOS VGA 437](https://www.dafont.com/perfect-dos-vga-437.font) by Zeh Fernando — pixel-perfect-vector TTF (every glyph outline axis-aligned). Free-to-redistribute per bundled license. Single-weight; all `fontWeight` tokens resolve to 400 |
|
|
90
90
|
| **Icons** | [pixelarticons](https://github.com/halfmage/pixelarticons) (MIT) — DOS pixel-art glyphs |
|
|
91
91
|
| **Accessibility** | WCAG AA, React Aria primitives under interactive components, `prefers-reduced-motion` + `prefers-contrast` honored throughout |
|
|
92
92
|
|
|
@@ -206,7 +206,7 @@ npm run build-tokens # Rebuild tokens from src/tokens/*.json
|
|
|
206
206
|
eiDotter commits to the constraints that other "DOS-inspired" systems relax:
|
|
207
207
|
|
|
208
208
|
- **16 colors, no more.** The palette is a historical fact, not a starting point. Extensions (amber, green, phosphor variants) are declared themes, not ad-hoc hex codes.
|
|
209
|
-
- **Single-weight typography.**
|
|
209
|
+
- **Single-weight typography.** Perfect DOS VGA 437 has no bold variant — authentic DOS never had one. All `fontWeight` tokens resolve to `400` and `font-synthesis: none` blocks browser faux-bold. Emphasis comes from color, uppercase on labels, or inverse video — never weight.
|
|
210
210
|
- **Phosphor as physics, not decoration.** Warmup animations, glow layers, and scanline effects are wired into component states; consumers don't opt in per-component.
|
|
211
211
|
- **Keyboard-first.** React Aria powers every interactive component. Focus rings are loud amber on amber-dim. `prefers-reduced-motion` and `prefers-contrast` are honored across the board.
|
|
212
212
|
- **Tree-shakable, no CSS-in-JS.** Runtime deps are minimal and on-purpose: [React Aria](https://react-spectrum.adobe.com/react-aria/) + [pixelarticons](https://github.com/halfmage/pixelarticons), both MIT. Component CSS ships compiled; you pay no CSS-in-JS cost at runtime.
|
|
@@ -215,4 +215,4 @@ The aesthetic is a constraint the system holds itself to — not a theme that ca
|
|
|
215
215
|
|
|
216
216
|
## License
|
|
217
217
|
|
|
218
|
-
[CC-BY-NC-4.0](./LICENSE.md) — Creative Commons Attribution-NonCommercial 4.0 International. Bundled third-party licenses (pixelarticons MIT, Flexi IBM VGA True CC BY-SA 4.0) attributed in `LICENSE.md`.
|
|
218
|
+
[CC-BY-NC-4.0](./LICENSE.md) — Creative Commons Attribution-NonCommercial 4.0 International. Bundled third-party licenses (pixelarticons MIT, Perfect DOS VGA 437 free-redistribution by Zeh Fernando, Flexi IBM VGA True CC BY-SA 4.0 — kept for legacy consumers) attributed in `LICENSE.md`.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import './LegalPage.css';
|
|
3
|
+
export interface LegalPageProps {
|
|
4
|
+
/** Heading shown in the hero (e.g., "Impressum", "Datenschutzerklärung") */
|
|
5
|
+
title: string;
|
|
6
|
+
/** "Stand: …" date stamp shown above the heading */
|
|
7
|
+
date: string;
|
|
8
|
+
/** Optional intro paragraph rendered inside the hero, below the heading.
|
|
9
|
+
* Wrapped in a `<div>` (not `<p>`) so consumers can pass block-level
|
|
10
|
+
* children like fragments or multiple paragraphs without invalid HTML. */
|
|
11
|
+
intro?: React.ReactNode;
|
|
12
|
+
/** Optional back-link slot rendered above the article. Should be a single
|
|
13
|
+
* `<a>` or `<button>` element so the styling selectors can reach it —
|
|
14
|
+
* react-router `<Link>` and react-router `<NavLink>` (without a function
|
|
15
|
+
* child) both render a single `<a>`. Wrapping the link in extra elements
|
|
16
|
+
* will leave it unstyled. */
|
|
17
|
+
home?: React.ReactNode;
|
|
18
|
+
/** Body content — `<h2>`, `<p>`, `<ul>`, `<address>` children styled by the wrapper. */
|
|
19
|
+
children: React.ReactNode;
|
|
20
|
+
/** Extra class on the outer wrapper */
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Layout primitive for Impressum / Datenschutz / other compact legal pages.
|
|
25
|
+
*
|
|
26
|
+
* Wraps the `.legal-hero` + `.legal-body` chrome that previously lived
|
|
27
|
+
* duplicated across portfolio surfaces. Consumers compose freeform
|
|
28
|
+
* `<h2>`/`<p>`/`<ul>` body content (or pre-built clauses from this module)
|
|
29
|
+
* and the wrapper handles spacing, type scale, and the `>` list bullet.
|
|
30
|
+
*/
|
|
31
|
+
export declare const LegalPage: React.FC<LegalPageProps & React.HTMLAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface AddressBlockProps {
|
|
3
|
+
/** Optional email shown after the address. When provided, rendered as a mailto link. */
|
|
4
|
+
email?: string;
|
|
5
|
+
/** Override the postal address. Defaults to the canonical Düsseldorf address. */
|
|
6
|
+
address?: React.ReactNode;
|
|
7
|
+
/** Override the legal name. Defaults to "Dominic Kennedy". */
|
|
8
|
+
name?: string;
|
|
9
|
+
}
|
|
10
|
+
/** Standalone postal address block — wrapped in `<address>`. */
|
|
11
|
+
export declare const AddressBlock: React.FC<AddressBlockProps>;
|
|
12
|
+
/** § 5 DDG header + address. */
|
|
13
|
+
export declare const ImpressumAddress: React.FC<Omit<AddressBlockProps, 'email'>>;
|
|
14
|
+
/** "Kontakt" header + mailto link. */
|
|
15
|
+
export declare const ImpressumContact: React.FC<{
|
|
16
|
+
email: string;
|
|
17
|
+
}>;
|
|
18
|
+
/** § 18 Abs. 2 MStV responsibility block — short address (no country line). */
|
|
19
|
+
export declare const ImpressumResponsible: React.FC<{
|
|
20
|
+
name?: string;
|
|
21
|
+
}>;
|
|
22
|
+
/** §§ 7-10 DDG content liability disclaimer. */
|
|
23
|
+
export declare const ImpressumLiabilityContent: React.FC;
|
|
24
|
+
/** External-link liability disclaimer. */
|
|
25
|
+
export declare const ImpressumLiabilityLinks: React.FC;
|
|
26
|
+
export interface DatenschutzControllerProps {
|
|
27
|
+
/** Email address rendered as a mailto link inside the address block. */
|
|
28
|
+
email: string;
|
|
29
|
+
/** Override the legal name. Defaults to "Dominic Kennedy". */
|
|
30
|
+
name?: string;
|
|
31
|
+
}
|
|
32
|
+
/** "Verantwortlicher" header + address with email. */
|
|
33
|
+
export declare const DatenschutzController: React.FC<DatenschutzControllerProps>;
|
|
34
|
+
/** Cloudflare hosting clause + Art. 6 Abs. 1 lit. f legal basis. */
|
|
35
|
+
export declare const DatenschutzHosting: React.FC;
|
|
36
|
+
export interface DatenschutzPostHogProps {
|
|
37
|
+
/** Domain hosting the PostHog reverse-proxy (without protocol),
|
|
38
|
+
* e.g. `e.dmnc.tech`. Defaults to `e.dmnc.tech`. */
|
|
39
|
+
proxyHost?: string;
|
|
40
|
+
}
|
|
41
|
+
/** PostHog webanalytics clause — EU servers, reverse-proxied, no cookies. */
|
|
42
|
+
export declare const DatenschutzPostHog: React.FC<DatenschutzPostHogProps>;
|
|
43
|
+
/** Local Perfect DOS VGA 437 font clause. */
|
|
44
|
+
export declare const DatenschutzFonts: React.FC;
|
|
45
|
+
/** SSL/TLS encryption clause. */
|
|
46
|
+
export declare const DatenschutzEncryption: React.FC;
|
|
47
|
+
export interface DatenschutzRightsProps {
|
|
48
|
+
/** Email for exercising data-subject rights. */
|
|
49
|
+
email?: string;
|
|
50
|
+
/** Override the supervisory authority. Defaults to NRW. */
|
|
51
|
+
supervisoryAuthority?: React.ReactNode;
|
|
52
|
+
}
|
|
53
|
+
/** DSGVO Art. 15-21 rights list + Art. 77 supervisory-authority complaint clause. */
|
|
54
|
+
export declare const DatenschutzRights: React.FC<DatenschutzRightsProps>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { LegalPage } from './LegalPage';
|
|
2
|
+
export type { LegalPageProps } from './LegalPage';
|
|
3
|
+
export { AddressBlock, ImpressumAddress, ImpressumContact, ImpressumResponsible, ImpressumLiabilityContent, ImpressumLiabilityLinks, DatenschutzController, DatenschutzHosting, DatenschutzPostHog, DatenschutzFonts, DatenschutzEncryption, DatenschutzRights, } from './clauses';
|
|
4
|
+
export type { AddressBlockProps, DatenschutzControllerProps, DatenschutzPostHogProps, DatenschutzRightsProps, } from './clauses';
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { LegalPage, AddressBlock, ImpressumAddress, ImpressumContact, ImpressumResponsible, ImpressumLiabilityContent, ImpressumLiabilityLinks, DatenschutzController, DatenschutzHosting, DatenschutzPostHog, DatenschutzFonts, DatenschutzEncryption, DatenschutzRights, } from './components';
|
|
2
|
+
export type { LegalPageProps, AddressBlockProps, DatenschutzControllerProps, DatenschutzPostHogProps, DatenschutzRightsProps, } from './components';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { TimelineImage } from '../../TimelineContainer/components/types';
|
|
3
|
+
import './Lightbox.css';
|
|
4
|
+
export interface LightboxProps {
|
|
5
|
+
/** Images to navigate between. */
|
|
6
|
+
images: TimelineImage[];
|
|
7
|
+
/** Index to start on when the lightbox opens. Defaults to 0. */
|
|
8
|
+
initialIndex?: number;
|
|
9
|
+
/** Whether the lightbox is open. */
|
|
10
|
+
isOpen: boolean;
|
|
11
|
+
/** Called when the lightbox should close (close button, backdrop, Esc). */
|
|
12
|
+
onClose: () => void;
|
|
13
|
+
/** Optional callback fired when navigation moves to a new image. */
|
|
14
|
+
onIndexChange?: (index: number) => void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Fullscreen image viewer built on the same React Aria primitives as Modal.
|
|
18
|
+
*
|
|
19
|
+
* Reuses Modal's CRT phosphor enter/exit animations, overlay tokens, and close
|
|
20
|
+
* button styling. Adds prev/next, counter, caption, keyboard nav, and touch
|
|
21
|
+
* swipe — see Tasks 4 and 5.
|
|
22
|
+
*/
|
|
23
|
+
export declare const Lightbox: React.FC<LightboxProps>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { TimelineEntryData, ZoomLevel } from './types';
|
|
2
|
+
import type { TimelineEntryData, TimelineRenderEntry, ZoomLevel } from './types';
|
|
3
3
|
import './TimelineContainer.css';
|
|
4
4
|
import './views/views.css';
|
|
5
5
|
export interface TimelineContainerProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
@@ -8,10 +8,13 @@ export interface TimelineContainerProps extends React.HTMLAttributes<HTMLDivElem
|
|
|
8
8
|
/**
|
|
9
9
|
* Display mode
|
|
10
10
|
* - "interactive" (default): zoom controls, selection, keyboard shortcuts
|
|
11
|
-
* - "static": read-only vertical feed of
|
|
11
|
+
* - "static": read-only vertical feed of always-expanded entries (replaces TimelineList)
|
|
12
|
+
* - "feed": paginated vertical list with collapsed-by-default entries that
|
|
13
|
+
* expand on selection. Renders a DOS-style "LOAD MORE..." button while
|
|
14
|
+
* more entries are available. No zoom controls.
|
|
12
15
|
* @default 'interactive'
|
|
13
16
|
*/
|
|
14
|
-
mode?: 'interactive' | 'static';
|
|
17
|
+
mode?: 'interactive' | 'static' | 'feed';
|
|
15
18
|
/**
|
|
16
19
|
* Controlled zoom level — overrides internal state when provided.
|
|
17
20
|
* Use with `onZoomChange` for full control. Disables drill-down navigation.
|
|
@@ -51,6 +54,27 @@ export interface TimelineContainerProps extends React.HTMLAttributes<HTMLDivElem
|
|
|
51
54
|
* @default true
|
|
52
55
|
*/
|
|
53
56
|
keyboardShortcuts?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Pluggable entry renderer. When provided, this is called for every entry
|
|
59
|
+
* in every zoom-level view (and in static / feed modes) instead of rendering
|
|
60
|
+
* the built-in `TimelineEntryCard`. Return `context.defaultRender()` to keep
|
|
61
|
+
* the default card for some entries while customising others.
|
|
62
|
+
*
|
|
63
|
+
* Use this to render different card UIs per entry type — blog posts,
|
|
64
|
+
* photos, financial records, etc.
|
|
65
|
+
*/
|
|
66
|
+
renderEntry?: TimelineRenderEntry;
|
|
67
|
+
/**
|
|
68
|
+
* Number of entries to show per page in feed mode. Ignored for other modes.
|
|
69
|
+
* @default 10
|
|
70
|
+
*/
|
|
71
|
+
pageSize?: number;
|
|
72
|
+
/**
|
|
73
|
+
* Fired in feed mode after the user clicks "LOAD MORE…", with the new total
|
|
74
|
+
* number of visible entries. Use this to fetch the next batch from a backend
|
|
75
|
+
* and append to `entries`, or for analytics.
|
|
76
|
+
*/
|
|
77
|
+
onLoadMore?: (visibleCount: number) => void;
|
|
54
78
|
}
|
|
55
79
|
/**
|
|
56
80
|
* TimelineContainer - Interactive multi-level zoom timeline
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { ZoomLevel, DateBucket } from './types';
|
|
2
|
+
import type { ZoomLevel, DateBucket, TimelineRenderEntry } from './types';
|
|
3
3
|
export interface TimelineContentProps {
|
|
4
4
|
zoomLevel: ZoomLevel;
|
|
5
5
|
buckets: readonly DateBucket[];
|
|
6
6
|
selectedEntryId: string | null;
|
|
7
7
|
onEntrySelect?: (entryId: string) => void;
|
|
8
8
|
onBucketClick?: (bucket: DateBucket) => void;
|
|
9
|
+
renderEntry?: TimelineRenderEntry;
|
|
9
10
|
}
|
|
10
11
|
/**
|
|
11
12
|
* View switcher that renders the appropriate zoom level view.
|
|
@@ -10,11 +10,15 @@ export interface TimelineEntryCardProps {
|
|
|
10
10
|
children?: React.ReactNode;
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* Dispatcher for content-aware timeline entry rendering.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* Branches on `entry.kind`:
|
|
16
|
+
* - `text` — existing trigger+panel rendering with `entry.content`.
|
|
17
|
+
* - `image` — placeholder for now; filled in by Task 6.
|
|
18
|
+
* - `gallery` — placeholder for now; filled in by Tasks 7–8.
|
|
19
|
+
*
|
|
20
|
+
* The card chrome (border, hover slide+glow, selected/expanded states)
|
|
21
|
+
* lives on the outer `.eidotter-timeline-card` element and applies to all
|
|
22
|
+
* kinds.
|
|
19
23
|
*/
|
|
20
24
|
export declare const TimelineEntryCard: React.NamedExoticComponent<TimelineEntryCardProps>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { TimelineContainer } from './TimelineContainer';
|
|
2
2
|
export type { TimelineContainerProps } from './TimelineContainer';
|
|
3
3
|
export { ZOOM_LEVELS } from './types';
|
|
4
|
-
export type { TimelineEntry,
|
|
4
|
+
export type { TimelineEntryData, TimelineEntry, TimelineEntryRenderContext, TimelineRenderEntry, TimelineImage, DateBucket, ZoomLevel, } from './types';
|
|
5
5
|
export type { DrillDownEntry, UseDrillDownReturn } from './useDrillDown';
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Timeline utility functions for grouping entries and formatting dates.
|
|
3
3
|
* Pure functions with zero external dependencies — uses only native Date/Intl.
|
|
4
4
|
*/
|
|
5
|
-
import type {
|
|
5
|
+
import type { TimelineEntryData, DateBucket, ZoomLevel } from './types';
|
|
6
6
|
/** Format a full timestamp: "Mar 15, 2024 at 2:30pm" */
|
|
7
7
|
export declare function formatTimestamp(iso: string): string;
|
|
8
8
|
/**
|
|
@@ -19,4 +19,4 @@ export declare function filterBucketsByPeriod(buckets: readonly DateBucket[], pe
|
|
|
19
19
|
/**
|
|
20
20
|
* Group timeline entries into DateBuckets based on the current zoom level.
|
|
21
21
|
*/
|
|
22
|
-
export declare function groupEntriesByZoom(entries:
|
|
22
|
+
export declare function groupEntriesByZoom(entries: TimelineEntryData[], zoomLevel: ZoomLevel, sortOrder?: 'asc' | 'desc'): DateBucket[];
|
|
@@ -8,25 +8,79 @@ import type { ReactNode } from 'react';
|
|
|
8
8
|
export declare const ZOOM_LEVELS: readonly ["year", "month", "day", "hour"];
|
|
9
9
|
/** Timeline zoom level */
|
|
10
10
|
export type ZoomLevel = (typeof ZOOM_LEVELS)[number];
|
|
11
|
-
/**
|
|
12
|
-
export interface
|
|
11
|
+
/** A single image used by the `image` and `gallery` entry kinds. */
|
|
12
|
+
export interface TimelineImage {
|
|
13
|
+
/** Full-resolution URL. */
|
|
14
|
+
src: string;
|
|
15
|
+
/** Required alt text. Pass an empty string for purely decorative images. */
|
|
16
|
+
alt: string;
|
|
17
|
+
/** Optional separate low-resolution URL used in grids/thumbnails. */
|
|
18
|
+
thumbnail?: string;
|
|
19
|
+
/** Optional intrinsic width (px) for layout reservation. */
|
|
20
|
+
width?: number;
|
|
21
|
+
/** Optional intrinsic height (px) for layout reservation. */
|
|
22
|
+
height?: number;
|
|
23
|
+
/** Optional caption rendered in the lightbox. */
|
|
24
|
+
caption?: string;
|
|
25
|
+
/** Optional ISO date — overrides parent entry date for ordering/display in mixed galleries. */
|
|
26
|
+
date?: string;
|
|
27
|
+
/** Optional per-image tags. */
|
|
28
|
+
tags?: string[];
|
|
29
|
+
/**
|
|
30
|
+
* Optional URL. When present, the thumbnail wraps `<a href={link}>`
|
|
31
|
+
* and clicks navigate out — the grow-in-place + lightbox interactions
|
|
32
|
+
* are skipped entirely.
|
|
33
|
+
*/
|
|
34
|
+
link?: string;
|
|
35
|
+
}
|
|
36
|
+
/** Fields common to every timeline entry kind. */
|
|
37
|
+
interface TimelineEntryBase {
|
|
13
38
|
/** Unique identifier for this entry */
|
|
14
39
|
id: string;
|
|
15
40
|
/** ISO 8601 date string or display date */
|
|
16
41
|
date: string;
|
|
17
42
|
/** Display title */
|
|
18
43
|
title: string;
|
|
19
|
-
/**
|
|
20
|
-
content?: ReactNode;
|
|
21
|
-
/** Entry category — influences node shape/color */
|
|
44
|
+
/** Semantic entry category — influences node shape/color. Independent of `kind`. */
|
|
22
45
|
type?: 'event' | 'project' | 'milestone';
|
|
23
46
|
/** Categorization tags */
|
|
24
47
|
tags?: string[];
|
|
25
48
|
}
|
|
49
|
+
/** Discriminated union: `kind` determines what other fields are present. */
|
|
50
|
+
export type TimelineEntryData = (TimelineEntryBase & {
|
|
51
|
+
kind: 'text';
|
|
52
|
+
content?: ReactNode;
|
|
53
|
+
}) | (TimelineEntryBase & {
|
|
54
|
+
kind: 'image';
|
|
55
|
+
image: TimelineImage;
|
|
56
|
+
}) | (TimelineEntryBase & {
|
|
57
|
+
kind: 'gallery';
|
|
58
|
+
images: TimelineImage[];
|
|
59
|
+
});
|
|
26
60
|
/**
|
|
27
|
-
* @deprecated Use TimelineEntryData instead.
|
|
61
|
+
* @deprecated Use `TimelineEntryData` instead. Existing entries must add a
|
|
62
|
+
* `kind` field — `kind: 'text'` is the equivalent of pre-DMNC-877 behavior.
|
|
63
|
+
* This alias will be removed in the next major release.
|
|
28
64
|
*/
|
|
29
65
|
export type TimelineEntry = TimelineEntryData;
|
|
66
|
+
/**
|
|
67
|
+
* Context passed to a `renderEntry` function so consumers can decide how
|
|
68
|
+
* to render their custom entry, while still being able to fall back to
|
|
69
|
+
* the built-in `TimelineEntryCard` rendering when needed.
|
|
70
|
+
*/
|
|
71
|
+
export interface TimelineEntryRenderContext {
|
|
72
|
+
/** True when this entry is currently expanded (its content is visible). */
|
|
73
|
+
isExpanded: boolean;
|
|
74
|
+
/** True when this entry is the currently selected entry. */
|
|
75
|
+
isSelected: boolean;
|
|
76
|
+
/** Render the built-in `TimelineEntryCard` for this entry. */
|
|
77
|
+
defaultRender: () => ReactNode;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Pluggable entry renderer. Return `defaultRender()` to delegate back to the
|
|
81
|
+
* built-in card, or any custom ReactNode to take over the entry slot.
|
|
82
|
+
*/
|
|
83
|
+
export type TimelineRenderEntry = (entry: TimelineEntryData, context: TimelineEntryRenderContext) => ReactNode;
|
|
30
84
|
export interface DateBucket {
|
|
31
85
|
/** Human-readable label, e.g. "2024", "March 2024", "Mar 15" */
|
|
32
86
|
label: string;
|
|
@@ -45,4 +99,7 @@ export interface TimelineViewProps {
|
|
|
45
99
|
selectedEntryId?: string | null;
|
|
46
100
|
/** Callback when a bucket header is clicked (for drill-down) */
|
|
47
101
|
onBucketClick?: (bucket: DateBucket) => void;
|
|
102
|
+
/** Optional pluggable entry renderer */
|
|
103
|
+
renderEntry?: TimelineRenderEntry;
|
|
48
104
|
}
|
|
105
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { TimelineEntryData } from '../types';
|
|
3
|
+
import './TimelineEntryCardGallery.css';
|
|
4
|
+
type GalleryEntry = Extract<TimelineEntryData, {
|
|
5
|
+
kind: 'gallery';
|
|
6
|
+
}>;
|
|
7
|
+
export interface TimelineEntryCardGalleryProps {
|
|
8
|
+
entry: GalleryEntry;
|
|
9
|
+
isExpanded: boolean;
|
|
10
|
+
onSelect?: (id: string) => void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Gallery variant of TimelineEntryCard. iOS Photos two-stage interaction:
|
|
14
|
+
* grid → focused (grow-in-place) → lightbox. Resets to grid on parent
|
|
15
|
+
* collapse via useEffect on `isExpanded`.
|
|
16
|
+
*/
|
|
17
|
+
export declare const TimelineEntryCardGallery: React.FC<TimelineEntryCardGalleryProps>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { TimelineEntryData } from '../types';
|
|
3
|
+
import './TimelineEntryCardImage.css';
|
|
4
|
+
type ImageEntry = Extract<TimelineEntryData, {
|
|
5
|
+
kind: 'image';
|
|
6
|
+
}>;
|
|
7
|
+
export interface TimelineEntryCardImageProps {
|
|
8
|
+
entry: ImageEntry;
|
|
9
|
+
isExpanded: boolean;
|
|
10
|
+
onSelect?: (id: string) => void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Image variant of TimelineEntryCard.
|
|
14
|
+
*
|
|
15
|
+
* Collapsed: title + thumbnail. Expanded: title + full-width image. Click on
|
|
16
|
+
* the expanded image opens a Lightbox. If `entry.image.link` is set and uses
|
|
17
|
+
* a safe scheme (http/https/mailto/relative), the thumbnail is wrapped in a
|
|
18
|
+
* plain anchor and clicks navigate out — no expansion, no lightbox.
|
|
19
|
+
*/
|
|
20
|
+
export declare const TimelineEntryCardImage: React.FC<TimelineEntryCardImageProps>;
|
|
21
|
+
export {};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { TimelineContainer } from './components';
|
|
2
|
-
export type { TimelineContainerProps, TimelineEntry,
|
|
2
|
+
export type { TimelineContainerProps, TimelineEntryData, TimelineEntry, TimelineEntryRenderContext, TimelineRenderEntry, TimelineImage, DateBucket, ZoomLevel, } from './components';
|
|
3
3
|
export { ZOOM_LEVELS } from './components';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type ProjectId = 'eidotter' | 'spacewar' | 'rizomorf' | 'pomodoke-calendar' | 'keepcoin' | 'steuerdash' | 'sella' | 'lifelines' | 'betamorf';
|
|
1
|
+
export type ProjectId = 'eidotter' | 'spacewar' | 'rizomorf' | 'pomodoke-calendar' | 'keepcoin' | 'steuerdash' | 'sella' | 'lifelines' | 'betamorf' | 'dmnctech';
|
|
2
2
|
export interface ProjectInfo {
|
|
3
3
|
displayName: string;
|
|
4
4
|
url?: string;
|