@activecollab/components 2.0.362 → 2.0.364

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 (79) hide show
  1. package/dist/cjs/presentation/stackedCard/content/ImageCard.js +60 -0
  2. package/dist/cjs/presentation/stackedCard/content/ImageCard.js.map +1 -0
  3. package/dist/cjs/presentation/stackedCard/content/LinkCard.js +348 -0
  4. package/dist/cjs/presentation/stackedCard/content/LinkCard.js.map +1 -0
  5. package/dist/cjs/presentation/stackedCard/content/NoteCard.js +92 -0
  6. package/dist/cjs/presentation/stackedCard/content/NoteCard.js.map +1 -0
  7. package/dist/cjs/presentation/stackedCard/content/SwatchCard.js +74 -0
  8. package/dist/cjs/presentation/stackedCard/content/SwatchCard.js.map +1 -0
  9. package/dist/cjs/presentation/stackedCard/content/TaskCard.js +231 -0
  10. package/dist/cjs/presentation/stackedCard/content/TaskCard.js.map +1 -0
  11. package/dist/cjs/presentation/stackedCard/content/index.js +83 -0
  12. package/dist/cjs/presentation/stackedCard/content/index.js.map +1 -0
  13. package/dist/cjs/presentation/stackedCard/content/shared.js +144 -0
  14. package/dist/cjs/presentation/stackedCard/content/shared.js.map +1 -0
  15. package/dist/cjs/presentation/stackedCard/content/unfurl.js +172 -0
  16. package/dist/cjs/presentation/stackedCard/content/unfurl.js.map +1 -0
  17. package/dist/cjs/presentation/stackedCard/primitives.js +254 -0
  18. package/dist/cjs/presentation/stackedCard/primitives.js.map +1 -0
  19. package/dist/cjs/presentation/stackedCard/resizePolicy.js +183 -0
  20. package/dist/cjs/presentation/stackedCard/resizePolicy.js.map +1 -0
  21. package/dist/cjs/presentation/stackedCard/shell.js +165 -0
  22. package/dist/cjs/presentation/stackedCard/shell.js.map +1 -0
  23. package/dist/cjs/presentation/stackedCard/storyScaffold.js +72 -0
  24. package/dist/cjs/presentation/stackedCard/storyScaffold.js.map +1 -0
  25. package/dist/cjs/presentation/stackedCard/useInlineEdit.js +208 -0
  26. package/dist/cjs/presentation/stackedCard/useInlineEdit.js.map +1 -0
  27. package/dist/esm/presentation/stackedCard/content/ImageCard.d.ts +15 -0
  28. package/dist/esm/presentation/stackedCard/content/ImageCard.d.ts.map +1 -0
  29. package/dist/esm/presentation/stackedCard/content/ImageCard.js +50 -0
  30. package/dist/esm/presentation/stackedCard/content/ImageCard.js.map +1 -0
  31. package/dist/esm/presentation/stackedCard/content/LinkCard.d.ts +28 -0
  32. package/dist/esm/presentation/stackedCard/content/LinkCard.d.ts.map +1 -0
  33. package/dist/esm/presentation/stackedCard/content/LinkCard.js +336 -0
  34. package/dist/esm/presentation/stackedCard/content/LinkCard.js.map +1 -0
  35. package/dist/esm/presentation/stackedCard/content/NoteCard.d.ts +10 -0
  36. package/dist/esm/presentation/stackedCard/content/NoteCard.d.ts.map +1 -0
  37. package/dist/esm/presentation/stackedCard/content/NoteCard.js +82 -0
  38. package/dist/esm/presentation/stackedCard/content/NoteCard.js.map +1 -0
  39. package/dist/esm/presentation/stackedCard/content/SwatchCard.d.ts +14 -0
  40. package/dist/esm/presentation/stackedCard/content/SwatchCard.d.ts.map +1 -0
  41. package/dist/esm/presentation/stackedCard/content/SwatchCard.js +64 -0
  42. package/dist/esm/presentation/stackedCard/content/SwatchCard.js.map +1 -0
  43. package/dist/esm/presentation/stackedCard/content/TaskCard.d.ts +29 -0
  44. package/dist/esm/presentation/stackedCard/content/TaskCard.d.ts.map +1 -0
  45. package/dist/esm/presentation/stackedCard/content/TaskCard.js +218 -0
  46. package/dist/esm/presentation/stackedCard/content/TaskCard.js.map +1 -0
  47. package/dist/esm/presentation/stackedCard/content/index.d.ts +8 -0
  48. package/dist/esm/presentation/stackedCard/content/index.d.ts.map +1 -0
  49. package/dist/esm/presentation/stackedCard/content/index.js +8 -0
  50. package/dist/esm/presentation/stackedCard/content/index.js.map +1 -0
  51. package/dist/esm/presentation/stackedCard/content/shared.d.ts +46 -0
  52. package/dist/esm/presentation/stackedCard/content/shared.d.ts.map +1 -0
  53. package/dist/esm/presentation/stackedCard/content/shared.js +134 -0
  54. package/dist/esm/presentation/stackedCard/content/shared.js.map +1 -0
  55. package/dist/esm/presentation/stackedCard/content/unfurl.d.ts +89 -0
  56. package/dist/esm/presentation/stackedCard/content/unfurl.d.ts.map +1 -0
  57. package/dist/esm/presentation/stackedCard/content/unfurl.js +147 -0
  58. package/dist/esm/presentation/stackedCard/content/unfurl.js.map +1 -0
  59. package/dist/esm/presentation/stackedCard/primitives.d.ts +84 -0
  60. package/dist/esm/presentation/stackedCard/primitives.d.ts.map +1 -0
  61. package/dist/esm/presentation/stackedCard/primitives.js +241 -0
  62. package/dist/esm/presentation/stackedCard/primitives.js.map +1 -0
  63. package/dist/esm/presentation/stackedCard/resizePolicy.d.ts +97 -0
  64. package/dist/esm/presentation/stackedCard/resizePolicy.d.ts.map +1 -0
  65. package/dist/esm/presentation/stackedCard/resizePolicy.js +173 -0
  66. package/dist/esm/presentation/stackedCard/resizePolicy.js.map +1 -0
  67. package/dist/esm/presentation/stackedCard/shell.d.ts +46 -0
  68. package/dist/esm/presentation/stackedCard/shell.d.ts.map +1 -0
  69. package/dist/esm/presentation/stackedCard/shell.js +156 -0
  70. package/dist/esm/presentation/stackedCard/shell.js.map +1 -0
  71. package/dist/esm/presentation/stackedCard/storyScaffold.d.ts +28 -0
  72. package/dist/esm/presentation/stackedCard/storyScaffold.d.ts.map +1 -0
  73. package/dist/esm/presentation/stackedCard/storyScaffold.js +66 -0
  74. package/dist/esm/presentation/stackedCard/storyScaffold.js.map +1 -0
  75. package/dist/esm/presentation/stackedCard/useInlineEdit.d.ts +68 -0
  76. package/dist/esm/presentation/stackedCard/useInlineEdit.d.ts.map +1 -0
  77. package/dist/esm/presentation/stackedCard/useInlineEdit.js +192 -0
  78. package/dist/esm/presentation/stackedCard/useInlineEdit.js.map +1 -0
  79. package/package.json +1 -1
@@ -0,0 +1,147 @@
1
+ import _extends from "@babel/runtime/helpers/esm/extends";
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
+
4
+ /**
5
+ * Mock unfurl service for the LinkCard concept.
6
+ *
7
+ * A link preview is a LIFECYCLE, not two static layouts. The real service is
8
+ * server-side (SSRF-guarded, oEmbed → OpenGraph → Twitter Cards → <title>,
9
+ * cached ~24h with a stored `fetchedAt` and a manual re-unfurl affordance — see
10
+ * note 52104 §B3). This is a promise-based fake with a configurable delay and a
11
+ * failure toggle so the story can drive the whole lifecycle without a network.
12
+ *
13
+ * The data arrives on a PROGRESSIVE LADDER of three tiers, each carrying more
14
+ * than the last:
15
+ * url → just the raw URL (nothing fetched yet)
16
+ * basic → favicon + title + domain
17
+ * full → + description, hero image, media kind / duration
18
+ * The card reserves the next tier's space at every tier, so climbing the ladder
19
+ * never reflows the card.
20
+ */
21
+
22
+ /** The cumulative data shape. Fields fill in as the ladder climbs. */
23
+
24
+ /** A seed link — what the fake "server" would resolve the URL to at tier full. */
25
+
26
+ export const MOCK_LINKS = {
27
+ vimeo: {
28
+ url: "https://vimeo.com/876631121",
29
+ faviconColor: "#0F6E56",
30
+ title: "Brand refresh — motion explorations for the spring launch",
31
+ domain: "vimeo.com",
32
+ description: "A reel of motion studies exploring the refreshed brand system: type in motion, the new mark, and transitions for the marketing site.",
33
+ heroColor: "#1D9E75",
34
+ kind: "Video",
35
+ duration: "4:32",
36
+ playable: true
37
+ },
38
+ history: {
39
+ url: "https://ww2history.org/timeline",
40
+ faviconColor: "#8C5A2B",
41
+ title: "The Second World War • Timeline, key battles, and firsthand accounts",
42
+ domain: "ww2history.org",
43
+ description: "An illustrated timeline of the Second World War with primary sources, maps, and firsthand accounts from each theatre of the war.",
44
+ heroColor: "#B5793B",
45
+ kind: "Article",
46
+ playable: false
47
+ }
48
+ };
49
+
50
+ /** Progressive tier data derived from a seed link. */
51
+ const tierData = (link, tier) => {
52
+ if (tier === "url") return {
53
+ url: link.url
54
+ };
55
+ if (tier === "basic") {
56
+ return {
57
+ url: link.url,
58
+ faviconColor: link.faviconColor,
59
+ title: link.title,
60
+ domain: link.domain
61
+ };
62
+ }
63
+ return _extends({}, link);
64
+ };
65
+ /**
66
+ * Drives one link through the unfurl lifecycle. Climbs url → basic → full on a
67
+ * timer, or lands in `error` if `shouldFail`. `retry` / `reunfurl` re-run it;
68
+ * `markStale` moves a resolved card to the stale state so the re-unfurl
69
+ * affordance can be shown.
70
+ */
71
+ export const useUnfurl = _ref => {
72
+ let link = _ref.link,
73
+ _ref$delayMs = _ref.delayMs,
74
+ delayMs = _ref$delayMs === void 0 ? 900 : _ref$delayMs,
75
+ _ref$shouldFail = _ref.shouldFail,
76
+ shouldFail = _ref$shouldFail === void 0 ? false : _ref$shouldFail;
77
+ const _useState = useState("resolving"),
78
+ status = _useState[0],
79
+ setStatus = _useState[1];
80
+ const _useState2 = useState("url"),
81
+ tier = _useState2[0],
82
+ setTier = _useState2[1];
83
+ const _useState3 = useState(() => tierData(link, "url")),
84
+ data = _useState3[0],
85
+ setData = _useState3[1];
86
+ const _useState4 = useState(null),
87
+ fetchedAt = _useState4[0],
88
+ setFetchedAt = _useState4[1];
89
+ const timers = useRef([]);
90
+ const clearTimers = useCallback(() => {
91
+ timers.current.forEach(clearTimeout);
92
+ timers.current = [];
93
+ }, []);
94
+ const start = useCallback(() => {
95
+ clearTimers();
96
+ setStatus("resolving");
97
+ setTier("url");
98
+ setData(tierData(link, "url"));
99
+ if (shouldFail) {
100
+ // Fail partway: the favicon + title never arrive; we keep the raw URL.
101
+ timers.current.push(setTimeout(() => setStatus("error"), delayMs));
102
+ return;
103
+ }
104
+ timers.current.push(setTimeout(() => {
105
+ setTier("basic");
106
+ setData(tierData(link, "basic"));
107
+ }, delayMs));
108
+ timers.current.push(setTimeout(() => {
109
+ setTier("full");
110
+ setData(tierData(link, "full"));
111
+ setStatus("resolved");
112
+ setFetchedAt(Date.now());
113
+ }, delayMs * 2));
114
+ }, [clearTimers, link, delayMs, shouldFail]);
115
+ const markStale = useCallback(() => {
116
+ setStatus(prev => prev === "resolved" ? "stale" : prev);
117
+ }, []);
118
+
119
+ // Kick off on mount and whenever the config that defines the run changes.
120
+ useEffect(() => {
121
+ start();
122
+ return clearTimers;
123
+ }, [start, clearTimers]);
124
+ return {
125
+ status,
126
+ tier,
127
+ data,
128
+ fetchedAt,
129
+ start,
130
+ retry: start,
131
+ reunfurl: start,
132
+ markStale
133
+ };
134
+ };
135
+
136
+ /** Human-readable "fetched N ago" for the stale affordance. */
137
+ export const formatFetchedAt = fetchedAt => {
138
+ if (!fetchedAt) return "";
139
+ const seconds = Math.max(1, Math.round((Date.now() - fetchedAt) / 1000));
140
+ if (seconds < 60) return seconds + "s ago";
141
+ const minutes = Math.round(seconds / 60);
142
+ if (minutes < 60) return minutes + "m ago";
143
+ const hours = Math.round(minutes / 60);
144
+ if (hours < 24) return hours + "h ago";
145
+ return Math.round(hours / 24) + "d ago";
146
+ };
147
+ //# sourceMappingURL=unfurl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unfurl.js","names":["useCallback","useEffect","useRef","useState","MOCK_LINKS","vimeo","url","faviconColor","title","domain","description","heroColor","kind","duration","playable","history","tierData","link","tier","_extends","useUnfurl","_ref","_ref$delayMs","delayMs","_ref$shouldFail","shouldFail","_useState","status","setStatus","_useState2","setTier","_useState3","data","setData","_useState4","fetchedAt","setFetchedAt","timers","clearTimers","current","forEach","clearTimeout","start","push","setTimeout","Date","now","markStale","prev","retry","reunfurl","formatFetchedAt","seconds","Math","max","round","minutes","hours"],"sources":["../../../../../src/presentation/stackedCard/content/unfurl.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\n\n/**\n * Mock unfurl service for the LinkCard concept.\n *\n * A link preview is a LIFECYCLE, not two static layouts. The real service is\n * server-side (SSRF-guarded, oEmbed → OpenGraph → Twitter Cards → <title>,\n * cached ~24h with a stored `fetchedAt` and a manual re-unfurl affordance — see\n * note 52104 §B3). This is a promise-based fake with a configurable delay and a\n * failure toggle so the story can drive the whole lifecycle without a network.\n *\n * The data arrives on a PROGRESSIVE LADDER of three tiers, each carrying more\n * than the last:\n * url → just the raw URL (nothing fetched yet)\n * basic → favicon + title + domain\n * full → + description, hero image, media kind / duration\n * The card reserves the next tier's space at every tier, so climbing the ladder\n * never reflows the card.\n */\n\nexport type UnfurlTier = \"url\" | \"basic\" | \"full\";\n\nexport type UnfurlStatus = \"resolving\" | \"resolved\" | \"stale\" | \"error\";\n\n/** The cumulative data shape. Fields fill in as the ladder climbs. */\nexport interface UnfurlData {\n /** Always present — the raw pasted URL. */\n url: string;\n /** Tier `basic` and up: a stand-in favicon colour. */\n faviconColor?: string;\n /** Tier `basic` and up: page title. */\n title?: string;\n /** Tier `basic` and up: the display domain. */\n domain?: string;\n /** Tier `full`: short description / summary. */\n description?: string;\n /** Tier `full`: hero image src (a real <img> in production). */\n heroSrc?: string;\n /** Tier `full`: hero fill colour, used when there is no hero image. */\n heroColor?: string;\n /** Tier `full`: media kind chip (Video, Article, …). */\n kind?: string;\n /** Tier `full`: duration chip for playable media. */\n duration?: string;\n /** Tier `full`: whether the media is playable (shows the cover play badge). */\n playable?: boolean;\n}\n\n/** A seed link — what the fake \"server\" would resolve the URL to at tier full. */\nexport interface MockLink {\n url: string;\n faviconColor: string;\n title: string;\n domain: string;\n description: string;\n heroColor: string;\n heroSrc?: string;\n kind: string;\n duration?: string;\n playable?: boolean;\n}\n\nexport const MOCK_LINKS: Record<string, MockLink> = {\n vimeo: {\n url: \"https://vimeo.com/876631121\",\n faviconColor: \"#0F6E56\",\n title: \"Brand refresh — motion explorations for the spring launch\",\n domain: \"vimeo.com\",\n description:\n \"A reel of motion studies exploring the refreshed brand system: type in motion, the new mark, and transitions for the marketing site.\",\n heroColor: \"#1D9E75\",\n kind: \"Video\",\n duration: \"4:32\",\n playable: true,\n },\n history: {\n url: \"https://ww2history.org/timeline\",\n faviconColor: \"#8C5A2B\",\n title:\n \"The Second World War • Timeline, key battles, and firsthand accounts\",\n domain: \"ww2history.org\",\n description:\n \"An illustrated timeline of the Second World War with primary sources, maps, and firsthand accounts from each theatre of the war.\",\n heroColor: \"#B5793B\",\n kind: \"Article\",\n playable: false,\n },\n};\n\n/** Progressive tier data derived from a seed link. */\nconst tierData = (link: MockLink, tier: UnfurlTier): UnfurlData => {\n if (tier === \"url\") return { url: link.url };\n if (tier === \"basic\") {\n return {\n url: link.url,\n faviconColor: link.faviconColor,\n title: link.title,\n domain: link.domain,\n };\n }\n return { ...link };\n};\n\nexport interface UnfurlConfig {\n /** Which seed link to resolve. */\n link: MockLink;\n /** Per-tier delay in ms (url→basic and basic→full each wait this long). */\n delayMs?: number;\n /** When true, the unfurl fails partway and lands in the error state. */\n shouldFail?: boolean;\n}\n\nexport interface UnfurlController {\n status: UnfurlStatus;\n tier: UnfurlTier;\n data: UnfurlData;\n /** Epoch ms of the last successful resolve (drives the stale affordance). */\n fetchedAt: number | null;\n /** Start (or restart) the unfurl ladder from scratch. */\n start: () => void;\n /** Retry after an error — re-runs the simulation. */\n retry: () => void;\n /** Re-unfurl a resolved/stale card — re-runs the simulation. */\n reunfurl: () => void;\n /** Force the resolved card into the stale state (demo affordance). */\n markStale: () => void;\n}\n\n/**\n * Drives one link through the unfurl lifecycle. Climbs url → basic → full on a\n * timer, or lands in `error` if `shouldFail`. `retry` / `reunfurl` re-run it;\n * `markStale` moves a resolved card to the stale state so the re-unfurl\n * affordance can be shown.\n */\nexport const useUnfurl = ({\n link,\n delayMs = 900,\n shouldFail = false,\n}: UnfurlConfig): UnfurlController => {\n const [status, setStatus] = useState<UnfurlStatus>(\"resolving\");\n const [tier, setTier] = useState<UnfurlTier>(\"url\");\n const [data, setData] = useState<UnfurlData>(() => tierData(link, \"url\"));\n const [fetchedAt, setFetchedAt] = useState<number | null>(null);\n\n const timers = useRef<ReturnType<typeof setTimeout>[]>([]);\n\n const clearTimers = useCallback(() => {\n timers.current.forEach(clearTimeout);\n timers.current = [];\n }, []);\n\n const start = useCallback(() => {\n clearTimers();\n setStatus(\"resolving\");\n setTier(\"url\");\n setData(tierData(link, \"url\"));\n\n if (shouldFail) {\n // Fail partway: the favicon + title never arrive; we keep the raw URL.\n timers.current.push(setTimeout(() => setStatus(\"error\"), delayMs));\n return;\n }\n\n timers.current.push(\n setTimeout(() => {\n setTier(\"basic\");\n setData(tierData(link, \"basic\"));\n }, delayMs)\n );\n timers.current.push(\n setTimeout(() => {\n setTier(\"full\");\n setData(tierData(link, \"full\"));\n setStatus(\"resolved\");\n setFetchedAt(Date.now());\n }, delayMs * 2)\n );\n }, [clearTimers, link, delayMs, shouldFail]);\n\n const markStale = useCallback(() => {\n setStatus((prev) => (prev === \"resolved\" ? \"stale\" : prev));\n }, []);\n\n // Kick off on mount and whenever the config that defines the run changes.\n useEffect(() => {\n start();\n return clearTimers;\n }, [start, clearTimers]);\n\n return {\n status,\n tier,\n data,\n fetchedAt,\n start,\n retry: start,\n reunfurl: start,\n markStale,\n };\n};\n\n/** Human-readable \"fetched N ago\" for the stale affordance. */\nexport const formatFetchedAt = (fetchedAt: number | null): string => {\n if (!fetchedAt) return \"\";\n const seconds = Math.max(1, Math.round((Date.now() - fetchedAt) / 1000));\n if (seconds < 60) return `${seconds}s ago`;\n const minutes = Math.round(seconds / 60);\n if (minutes < 60) return `${minutes}m ago`;\n const hours = Math.round(minutes / 60);\n if (hours < 24) return `${hours}h ago`;\n return `${Math.round(hours / 24)}d ago`;\n};\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,84 @@
1
+ import { ReactElement, ReactNode } from "react";
2
+ /**
3
+ * StackedCard layout primitives (story-local, presentation-only).
4
+ *
5
+ * These are the documented foundation of the StackedCard subsystem — the
6
+ * building blocks that every StackedCard concept story in this task set
7
+ * imports from. They are NOT exported from the design-system barrel; they
8
+ * live here so the concept stories share one source of truth instead of
9
+ * re-declaring one-off styled blocks.
10
+ *
11
+ * Three primitives:
12
+ * - Stack — the vertical column. A card is a Stack of StackRows.
13
+ * - StackRow — the one layout primitive. Everything (cover, title, meta,
14
+ * footer) is a row; a `role` sets the default padding and the
15
+ * allowed direction so the anatomy stays predictable.
16
+ * - Cover — sugar for a bleed row that holds media, reserves its height
17
+ * with `aspect-ratio` (so a late image never reflows), and
18
+ * carries two control slots (center + top-right).
19
+ *
20
+ * Colours here come exclusively from CSS variables — the dark scrim behind
21
+ * cover controls is `--sc-scrim` / `--sc-scrim-strong` / `--sc-scrim-fg`, all
22
+ * declared story-side as DS token candidates. No rgba/hex is hardcoded in a
23
+ * primitive; only data colours (a swatch value, a label dot) are ever literal,
24
+ * and those are passed in by the content, never baked in here.
25
+ */
26
+ export declare const Stack: import("styled-components").StyledComponent<"div", any, {}, never>;
27
+ export type StackRowRole = "cover" | "title" | "meta" | "footer" | "custom";
28
+ export type StackRowDirection = "row" | "column";
29
+ /**
30
+ * Role → default padding. The row is the increment: title/footer carry the
31
+ * system padding, meta is a tighter properties line, cover bleeds to the edge.
32
+ * `custom` starts from the system padding and expects the caller to adjust.
33
+ */
34
+ export declare const ROLE_PADDING: Record<StackRowRole, string>;
35
+ export interface StackRowProps {
36
+ /** Named role → default padding + allowed direction. Defaults to `custom`. */
37
+ role?: StackRowRole;
38
+ /** Zero the padding, overriding the role (media rows, edge-to-edge bands). */
39
+ bleed?: boolean;
40
+ /** Footer bands lay their children out vertically. Defaults to `row`. */
41
+ direction?: StackRowDirection;
42
+ /** A row can carry its own background (the tinted link-footer band). */
43
+ background?: string;
44
+ className?: string;
45
+ children: ReactNode;
46
+ }
47
+ export declare const StackRow: ({ role, bleed, direction, background, className, children, }: StackRowProps) => ReactElement;
48
+ export interface CoverProps {
49
+ /**
50
+ * CSS `aspect-ratio` value that reserves the cover's height, e.g.
51
+ * `"260 / 132"`. Kept as a ratio (not a pixel height) so the cover scales
52
+ * with the card and holds its space before media loads.
53
+ */
54
+ aspectRatio?: string;
55
+ /** Center slot — the media control (a play button over audio/video). */
56
+ centerSlot?: ReactNode;
57
+ /** Top-right slot — cover management (collapse, remove). */
58
+ topRightSlot?: ReactNode;
59
+ /**
60
+ * Collapsed cover: render the thin strip instead of the media box. The
61
+ * collapse *control* lives in the caller's `topRightSlot`; this flag is the
62
+ * resulting visual state. See the "Collapsed cover" story section.
63
+ */
64
+ collapsed?: boolean;
65
+ /**
66
+ * What the collapsed strip shows — an ~8px colour bar, or a favicon + domain
67
+ * row for links. Content-specific, so the caller supplies it.
68
+ */
69
+ collapsedStrip?: ReactNode;
70
+ /** Fired when the collapsed strip's expand affordance is activated. */
71
+ onExpand?: () => void;
72
+ /** Accessible label for the expand affordance. */
73
+ expandLabel?: string;
74
+ className?: string;
75
+ /** The fill: an `<img>`, an inline `<svg>`, or a solid-colour box. */
76
+ children: ReactNode;
77
+ }
78
+ export declare const Cover: ({ aspectRatio, centerSlot, topRightSlot, collapsed, collapsedStrip, onExpand, expandLabel, className, children, }: CoverProps) => ReactElement;
79
+ export declare const PlayBadge: import("styled-components").StyledComponent<"span", any, {}, never>;
80
+ export declare const CoverButton: import("styled-components").StyledComponent<"button", any, {}, never>;
81
+ export declare const CollapseGlyph: () => ReactElement;
82
+ export declare const ExpandGlyph: () => ReactElement;
83
+ export declare const RemoveCoverGlyph: () => ReactElement;
84
+ //# sourceMappingURL=primitives.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../../../../src/presentation/stackedCard/primitives.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIvD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAMH,eAAO,MAAM,KAAK,oEAIjB,CAAC;AAMF,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE5E,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,QAAQ,CAAC;AAEjD;;;;GAIG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAMrD,CAAC;AA0CF,MAAM,WAAW,aAAa;IAC5B,8EAA8E;IAC9E,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,yEAAyE;IACzE,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,eAAO,MAAM,QAAQ,GAAI,8DAOtB,aAAa,KAAG,YAUlB,CAAC;AAuHF,MAAM,WAAW,UAAU;IACzB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,4DAA4D;IAC5D,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,cAAc,CAAC,EAAE,SAAS,CAAC;IAC3B,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sEAAsE;IACtE,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,eAAO,MAAM,KAAK,GAAI,mHAUnB,UAAU,KAAG,YA6Bf,CAAC;AAOF,eAAO,MAAM,SAAS,qEAgBrB,CAAC;AAGF,eAAO,MAAM,WAAW,uEAsBvB,CAAC;AAGF,eAAO,MAAM,aAAa,QAAO,YAUhC,CAAC;AAGF,eAAO,MAAM,WAAW,QAAO,YAU9B,CAAC;AAEF,eAAO,MAAM,gBAAgB,QAAO,YAkBnC,CAAC"}
@@ -0,0 +1,241 @@
1
+ import React from "react";
2
+ import styled, { css } from "styled-components";
3
+
4
+ /**
5
+ * StackedCard layout primitives (story-local, presentation-only).
6
+ *
7
+ * These are the documented foundation of the StackedCard subsystem — the
8
+ * building blocks that every StackedCard concept story in this task set
9
+ * imports from. They are NOT exported from the design-system barrel; they
10
+ * live here so the concept stories share one source of truth instead of
11
+ * re-declaring one-off styled blocks.
12
+ *
13
+ * Three primitives:
14
+ * - Stack — the vertical column. A card is a Stack of StackRows.
15
+ * - StackRow — the one layout primitive. Everything (cover, title, meta,
16
+ * footer) is a row; a `role` sets the default padding and the
17
+ * allowed direction so the anatomy stays predictable.
18
+ * - Cover — sugar for a bleed row that holds media, reserves its height
19
+ * with `aspect-ratio` (so a late image never reflows), and
20
+ * carries two control slots (center + top-right).
21
+ *
22
+ * Colours here come exclusively from CSS variables — the dark scrim behind
23
+ * cover controls is `--sc-scrim` / `--sc-scrim-strong` / `--sc-scrim-fg`, all
24
+ * declared story-side as DS token candidates. No rgba/hex is hardcoded in a
25
+ * primitive; only data colours (a swatch value, a label dot) are ever literal,
26
+ * and those are passed in by the content, never baked in here.
27
+ */
28
+
29
+ /* ------------------------------------------------------------------ */
30
+ /* Stack — vertical flex column. No own padding. */
31
+ /* ------------------------------------------------------------------ */
32
+
33
+ export const Stack = styled.div.withConfig({
34
+ displayName: "primitives__Stack",
35
+ componentId: "sc-1ax68l9-0"
36
+ })(["position:relative;display:flex;flex-direction:column;"]);
37
+
38
+ /* ------------------------------------------------------------------ */
39
+ /* StackRow — the only layout primitive, differentiated by `role`. */
40
+ /* ------------------------------------------------------------------ */
41
+
42
+ /**
43
+ * Role → default padding. The row is the increment: title/footer carry the
44
+ * system padding, meta is a tighter properties line, cover bleeds to the edge.
45
+ * `custom` starts from the system padding and expects the caller to adjust.
46
+ */
47
+ export const ROLE_PADDING = {
48
+ cover: "0",
49
+ title: "8px 12px",
50
+ meta: "2px 12px 4px",
51
+ footer: "8px 12px",
52
+ custom: "8px 12px"
53
+ };
54
+ const StyledRow = styled.div.withConfig({
55
+ displayName: "primitives__StyledRow",
56
+ componentId: "sc-1ax68l9-1"
57
+ })(["position:relative;display:flex;flex-direction:", ";align-items:", ";gap:", ";padding:", ";", " ", ""], _ref => {
58
+ let $direction = _ref.$direction;
59
+ return $direction;
60
+ }, _ref2 => {
61
+ let $direction = _ref2.$direction;
62
+ return $direction === "column" ? "stretch" : "center";
63
+ }, _ref3 => {
64
+ let $direction = _ref3.$direction;
65
+ return $direction === "column" ? "4px" : "8px";
66
+ }, _ref4 => {
67
+ let $role = _ref4.$role,
68
+ $bleed = _ref4.$bleed;
69
+ return $bleed ? "0" : ROLE_PADDING[$role];
70
+ }, _ref5 => {
71
+ let $background = _ref5.$background;
72
+ return $background && css(["background:", ";"], $background);
73
+ }, _ref6 => {
74
+ let $direction = _ref6.$direction;
75
+ return $direction === "row" && css(["> *{flex:1 1 0;min-width:0;}> [data-fixed]{flex:0 0 auto;}"]);
76
+ });
77
+ export const StackRow = _ref7 => {
78
+ let _ref7$role = _ref7.role,
79
+ role = _ref7$role === void 0 ? "custom" : _ref7$role,
80
+ bleed = _ref7.bleed,
81
+ _ref7$direction = _ref7.direction,
82
+ direction = _ref7$direction === void 0 ? "row" : _ref7$direction,
83
+ background = _ref7.background,
84
+ className = _ref7.className,
85
+ children = _ref7.children;
86
+ return /*#__PURE__*/React.createElement(StyledRow, {
87
+ $role: role,
88
+ $bleed: bleed,
89
+ $direction: direction,
90
+ $background: background,
91
+ className: className
92
+ }, children);
93
+ };
94
+
95
+ /* ------------------------------------------------------------------ */
96
+ /* Cover — sugar for a bleed row that holds media. */
97
+ /* ------------------------------------------------------------------ */
98
+
99
+ /**
100
+ * The media box reserves its height with `aspect-ratio` so the image (or the
101
+ * skeleton that stands in for it) never reflows the card when it arrives.
102
+ * Controls carry the `sc-control` class and are hidden until the cover is
103
+ * hovered OR holds focus (`:focus-within`) — the focus path keeps the cover
104
+ * controls reachable by keyboard, not mouse-only.
105
+ */
106
+ const StyledCoverMedia = styled.div.withConfig({
107
+ displayName: "primitives__StyledCoverMedia",
108
+ componentId: "sc-1ax68l9-2"
109
+ })(["position:relative;width:100%;aspect-ratio:", ";overflow:hidden;.sc-control{opacity:0;transition:opacity 120ms ease;}&:hover .sc-control,&:focus-within .sc-control{opacity:1;}"], _ref8 => {
110
+ let $aspectRatio = _ref8.$aspectRatio;
111
+ return $aspectRatio;
112
+ });
113
+
114
+ /* The fill (an <img>, an inline <svg> wrapper, or a solid-colour box) is laid
115
+ over the reserved box and made to cover it. */
116
+ const StyledCoverFill = styled.div.withConfig({
117
+ displayName: "primitives__StyledCoverFill",
118
+ componentId: "sc-1ax68l9-3"
119
+ })(["position:absolute;inset:0;> *{width:100%;height:100%;}> img{display:block;object-fit:cover;}"]);
120
+ const StyledCoverCenter = styled.div.withConfig({
121
+ displayName: "primitives__StyledCoverCenter",
122
+ componentId: "sc-1ax68l9-4"
123
+ })(["position:absolute;inset:0;display:flex;align-items:center;justify-content:center;"]);
124
+ const StyledCoverTopRight = styled.div.withConfig({
125
+ displayName: "primitives__StyledCoverTopRight",
126
+ componentId: "sc-1ax68l9-5"
127
+ })(["position:absolute;top:8px;right:8px;display:flex;gap:6px;"]);
128
+
129
+ /**
130
+ * Collapsed cover — the media box gives way to a thin strip. The strip is the
131
+ * expand affordance itself: the whole bar is a button, and a chevron hint is
132
+ * revealed on hover OR focus (`.sc-control`, same reveal rule as every other
133
+ * cover control) so it is reachable by keyboard, not mouse-only. The strip
134
+ * holds no reserved height of its own — the caller's `collapsedStrip` (an ~8px
135
+ * colour bar, or a favicon + domain row for links) sets how tall it reads.
136
+ */
137
+ const StyledCollapsedStrip = styled.button.withConfig({
138
+ displayName: "primitives__StyledCollapsedStrip",
139
+ componentId: "sc-1ax68l9-6"
140
+ })(["position:relative;display:flex;align-items:center;width:100%;min-height:16px;padding:0;border:0;background:transparent;cursor:pointer;text-align:left;.sc-control{opacity:0;transition:opacity 120ms ease;}&:hover .sc-control,&:focus-visible .sc-control{opacity:1;}&:focus-visible{outline:2px solid var(--sc-selection-ring);outline-offset:-2px;}"]);
141
+
142
+ /* The expand chevron sits at the right, vertically centred over the strip, so
143
+ it reads the same whether the strip is a hairline colour bar or a taller
144
+ favicon row. Small + theme-coloured so it fits a thin strip without a scrim
145
+ button (the whole strip is the button). */
146
+ const StyledStripExpand = styled.span.withConfig({
147
+ displayName: "primitives__StyledStripExpand",
148
+ componentId: "sc-1ax68l9-7"
149
+ })(["position:absolute;top:50%;right:8px;transform:translateY(-50%);display:inline-flex;width:16px;height:16px;color:var(--color-theme-600);svg{width:14px;height:14px;}"]);
150
+ export const Cover = _ref9 => {
151
+ let _ref9$aspectRatio = _ref9.aspectRatio,
152
+ aspectRatio = _ref9$aspectRatio === void 0 ? "260 / 132" : _ref9$aspectRatio,
153
+ centerSlot = _ref9.centerSlot,
154
+ topRightSlot = _ref9.topRightSlot,
155
+ collapsed = _ref9.collapsed,
156
+ collapsedStrip = _ref9.collapsedStrip,
157
+ onExpand = _ref9.onExpand,
158
+ _ref9$expandLabel = _ref9.expandLabel,
159
+ expandLabel = _ref9$expandLabel === void 0 ? "Expand cover" : _ref9$expandLabel,
160
+ className = _ref9.className,
161
+ children = _ref9.children;
162
+ return /*#__PURE__*/React.createElement(StackRow, {
163
+ role: "cover",
164
+ bleed: true,
165
+ className: className
166
+ }, collapsed ? /*#__PURE__*/React.createElement(StyledCollapsedStrip, {
167
+ type: "button",
168
+ "aria-label": expandLabel,
169
+ onClick: onExpand
170
+ }, collapsedStrip, /*#__PURE__*/React.createElement(StyledStripExpand, {
171
+ className: "sc-control",
172
+ "aria-hidden": "true"
173
+ }, /*#__PURE__*/React.createElement(ExpandGlyph, null))) : /*#__PURE__*/React.createElement(StyledCoverMedia, {
174
+ $aspectRatio: aspectRatio
175
+ }, /*#__PURE__*/React.createElement(StyledCoverFill, null, children), centerSlot ? /*#__PURE__*/React.createElement(StyledCoverCenter, {
176
+ className: "sc-control"
177
+ }, centerSlot) : null, topRightSlot ? /*#__PURE__*/React.createElement(StyledCoverTopRight, {
178
+ className: "sc-control"
179
+ }, topRightSlot) : null));
180
+ };
181
+
182
+ /* ------------------------------------------------------------------ */
183
+ /* Cover controls — dark, scrim-backed chrome shared by every story. */
184
+ /* ------------------------------------------------------------------ */
185
+
186
+ /* Centered media control (a play button). The caller drops the glyph inside. */
187
+ export const PlayBadge = styled.span.withConfig({
188
+ displayName: "primitives__PlayBadge",
189
+ componentId: "sc-1ax68l9-8"
190
+ })(["display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;border-radius:12px;background:var(--sc-scrim);color:var(--sc-scrim-fg);svg{fill:currentColor;width:20px;height:20px;margin-left:2px;}"]);
191
+
192
+ /* Top-right cover-management button (collapse, remove …). */
193
+ export const CoverButton = styled.button.withConfig({
194
+ displayName: "primitives__CoverButton",
195
+ componentId: "sc-1ax68l9-9"
196
+ })(["display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;padding:0;border:0;border-radius:8px;cursor:pointer;color:var(--sc-scrim-fg);background:var(--sc-scrim);svg{width:16px;height:16px;display:block;}&:hover{background:var(--sc-scrim-strong);}"]);
197
+
198
+ /* Cover-management glyphs (act on the cover itself, not the entity). */
199
+ export const CollapseGlyph = () => /*#__PURE__*/React.createElement("svg", {
200
+ viewBox: "0 0 16 16",
201
+ fill: "none",
202
+ "aria-hidden": "true"
203
+ }, /*#__PURE__*/React.createElement("path", {
204
+ d: "M6.5 2.5v4h-4M9.5 13.5v-4h4",
205
+ stroke: "currentColor",
206
+ strokeWidth: "1.5",
207
+ strokeLinecap: "round",
208
+ strokeLinejoin: "round"
209
+ }));
210
+
211
+ /* Expand glyph — the inverse of collapse; shown on the collapsed strip. */
212
+ export const ExpandGlyph = () => /*#__PURE__*/React.createElement("svg", {
213
+ viewBox: "0 0 16 16",
214
+ fill: "none",
215
+ "aria-hidden": "true"
216
+ }, /*#__PURE__*/React.createElement("path", {
217
+ d: "M2.5 6.5v-4h4M13.5 9.5v4h-4",
218
+ stroke: "currentColor",
219
+ strokeWidth: "1.5",
220
+ strokeLinecap: "round",
221
+ strokeLinejoin: "round"
222
+ }));
223
+ export const RemoveCoverGlyph = () => /*#__PURE__*/React.createElement("svg", {
224
+ viewBox: "0 0 16 16",
225
+ fill: "none",
226
+ "aria-hidden": "true"
227
+ }, /*#__PURE__*/React.createElement("rect", {
228
+ x: "2.25",
229
+ y: "3.75",
230
+ width: "11.5",
231
+ height: "8.5",
232
+ rx: "1.5",
233
+ stroke: "currentColor",
234
+ strokeWidth: "1.5"
235
+ }), /*#__PURE__*/React.createElement("path", {
236
+ d: "M6 9.5L10 6M10 9.5L6 6",
237
+ stroke: "currentColor",
238
+ strokeWidth: "1.5",
239
+ strokeLinecap: "round"
240
+ }));
241
+ //# sourceMappingURL=primitives.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"primitives.js","names":["React","styled","css","Stack","div","withConfig","displayName","componentId","ROLE_PADDING","cover","title","meta","footer","custom","StyledRow","_ref","$direction","_ref2","_ref3","_ref4","$role","$bleed","_ref5","$background","_ref6","StackRow","_ref7","_ref7$role","role","bleed","_ref7$direction","direction","background","className","children","createElement","StyledCoverMedia","_ref8","$aspectRatio","StyledCoverFill","StyledCoverCenter","StyledCoverTopRight","StyledCollapsedStrip","button","StyledStripExpand","span","Cover","_ref9","_ref9$aspectRatio","aspectRatio","centerSlot","topRightSlot","collapsed","collapsedStrip","onExpand","_ref9$expandLabel","expandLabel","type","onClick","ExpandGlyph","PlayBadge","CoverButton","CollapseGlyph","viewBox","fill","d","stroke","strokeWidth","strokeLinecap","strokeLinejoin","RemoveCoverGlyph","x","y","width","height","rx"],"sources":["../../../../src/presentation/stackedCard/primitives.tsx"],"sourcesContent":["import React, { ReactElement, ReactNode } from \"react\";\n\nimport styled, { css } from \"styled-components\";\n\n/**\n * StackedCard layout primitives (story-local, presentation-only).\n *\n * These are the documented foundation of the StackedCard subsystem — the\n * building blocks that every StackedCard concept story in this task set\n * imports from. They are NOT exported from the design-system barrel; they\n * live here so the concept stories share one source of truth instead of\n * re-declaring one-off styled blocks.\n *\n * Three primitives:\n * - Stack — the vertical column. A card is a Stack of StackRows.\n * - StackRow — the one layout primitive. Everything (cover, title, meta,\n * footer) is a row; a `role` sets the default padding and the\n * allowed direction so the anatomy stays predictable.\n * - Cover — sugar for a bleed row that holds media, reserves its height\n * with `aspect-ratio` (so a late image never reflows), and\n * carries two control slots (center + top-right).\n *\n * Colours here come exclusively from CSS variables — the dark scrim behind\n * cover controls is `--sc-scrim` / `--sc-scrim-strong` / `--sc-scrim-fg`, all\n * declared story-side as DS token candidates. No rgba/hex is hardcoded in a\n * primitive; only data colours (a swatch value, a label dot) are ever literal,\n * and those are passed in by the content, never baked in here.\n */\n\n/* ------------------------------------------------------------------ */\n/* Stack — vertical flex column. No own padding. */\n/* ------------------------------------------------------------------ */\n\nexport const Stack = styled.div`\n position: relative;\n display: flex;\n flex-direction: column;\n`;\n\n/* ------------------------------------------------------------------ */\n/* StackRow — the only layout primitive, differentiated by `role`. */\n/* ------------------------------------------------------------------ */\n\nexport type StackRowRole = \"cover\" | \"title\" | \"meta\" | \"footer\" | \"custom\";\n\nexport type StackRowDirection = \"row\" | \"column\";\n\n/**\n * Role → default padding. The row is the increment: title/footer carry the\n * system padding, meta is a tighter properties line, cover bleeds to the edge.\n * `custom` starts from the system padding and expects the caller to adjust.\n */\nexport const ROLE_PADDING: Record<StackRowRole, string> = {\n cover: \"0\",\n title: \"8px 12px\",\n meta: \"2px 12px 4px\",\n footer: \"8px 12px\",\n custom: \"8px 12px\",\n};\n\ninterface StyledRowProps {\n $role: StackRowRole;\n $bleed?: boolean;\n $direction: StackRowDirection;\n $background?: string;\n}\n\nconst StyledRow = styled.div<StyledRowProps>`\n position: relative;\n display: flex;\n flex-direction: ${({ $direction }) => $direction};\n align-items: ${({ $direction }) =>\n $direction === \"column\" ? \"stretch\" : \"center\"};\n gap: ${({ $direction }) => ($direction === \"column\" ? \"4px\" : \"8px\")};\n\n /* bleed overrides any role and runs the row edge to edge. */\n padding: ${({ $role, $bleed }) => ($bleed ? \"0\" : ROLE_PADDING[$role])};\n\n ${({ $background }) =>\n $background &&\n css`\n background: ${$background};\n `}\n\n /* Distribution (row direction only): fixed-width children keep their width,\n the rest share what remains evenly; drop an empty spacer to push items. */\n ${({ $direction }) =>\n $direction === \"row\" &&\n css`\n > * {\n flex: 1 1 0;\n min-width: 0;\n }\n\n > [data-fixed] {\n flex: 0 0 auto;\n }\n `}\n`;\n\nexport interface StackRowProps {\n /** Named role → default padding + allowed direction. Defaults to `custom`. */\n role?: StackRowRole;\n /** Zero the padding, overriding the role (media rows, edge-to-edge bands). */\n bleed?: boolean;\n /** Footer bands lay their children out vertically. Defaults to `row`. */\n direction?: StackRowDirection;\n /** A row can carry its own background (the tinted link-footer band). */\n background?: string;\n className?: string;\n children: ReactNode;\n}\n\nexport const StackRow = ({\n role = \"custom\",\n bleed,\n direction = \"row\",\n background,\n className,\n children,\n}: StackRowProps): ReactElement => (\n <StyledRow\n $role={role}\n $bleed={bleed}\n $direction={direction}\n $background={background}\n className={className}\n >\n {children}\n </StyledRow>\n);\n\n/* ------------------------------------------------------------------ */\n/* Cover — sugar for a bleed row that holds media. */\n/* ------------------------------------------------------------------ */\n\n/**\n * The media box reserves its height with `aspect-ratio` so the image (or the\n * skeleton that stands in for it) never reflows the card when it arrives.\n * Controls carry the `sc-control` class and are hidden until the cover is\n * hovered OR holds focus (`:focus-within`) — the focus path keeps the cover\n * controls reachable by keyboard, not mouse-only.\n */\nconst StyledCoverMedia = styled.div<{ $aspectRatio: string }>`\n position: relative;\n width: 100%;\n aspect-ratio: ${({ $aspectRatio }) => $aspectRatio};\n overflow: hidden;\n\n .sc-control {\n opacity: 0;\n transition: opacity 120ms ease;\n }\n\n &:hover .sc-control,\n &:focus-within .sc-control {\n opacity: 1;\n }\n`;\n\n/* The fill (an <img>, an inline <svg> wrapper, or a solid-colour box) is laid\n over the reserved box and made to cover it. */\nconst StyledCoverFill = styled.div`\n position: absolute;\n inset: 0;\n\n > * {\n width: 100%;\n height: 100%;\n }\n\n > img {\n display: block;\n object-fit: cover;\n }\n`;\n\nconst StyledCoverCenter = styled.div`\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n`;\n\nconst StyledCoverTopRight = styled.div`\n position: absolute;\n top: 8px;\n right: 8px;\n display: flex;\n gap: 6px;\n`;\n\n/**\n * Collapsed cover — the media box gives way to a thin strip. The strip is the\n * expand affordance itself: the whole bar is a button, and a chevron hint is\n * revealed on hover OR focus (`.sc-control`, same reveal rule as every other\n * cover control) so it is reachable by keyboard, not mouse-only. The strip\n * holds no reserved height of its own — the caller's `collapsedStrip` (an ~8px\n * colour bar, or a favicon + domain row for links) sets how tall it reads.\n */\nconst StyledCollapsedStrip = styled.button`\n position: relative;\n display: flex;\n align-items: center;\n width: 100%;\n min-height: 16px;\n padding: 0;\n border: 0;\n background: transparent;\n cursor: pointer;\n text-align: left;\n\n .sc-control {\n opacity: 0;\n transition: opacity 120ms ease;\n }\n\n &:hover .sc-control,\n &:focus-visible .sc-control {\n opacity: 1;\n }\n\n &:focus-visible {\n outline: 2px solid var(--sc-selection-ring);\n outline-offset: -2px;\n }\n`;\n\n/* The expand chevron sits at the right, vertically centred over the strip, so\n it reads the same whether the strip is a hairline colour bar or a taller\n favicon row. Small + theme-coloured so it fits a thin strip without a scrim\n button (the whole strip is the button). */\nconst StyledStripExpand = styled.span`\n position: absolute;\n top: 50%;\n right: 8px;\n transform: translateY(-50%);\n display: inline-flex;\n width: 16px;\n height: 16px;\n color: var(--color-theme-600);\n\n svg {\n width: 14px;\n height: 14px;\n }\n`;\n\nexport interface CoverProps {\n /**\n * CSS `aspect-ratio` value that reserves the cover's height, e.g.\n * `\"260 / 132\"`. Kept as a ratio (not a pixel height) so the cover scales\n * with the card and holds its space before media loads.\n */\n aspectRatio?: string;\n /** Center slot — the media control (a play button over audio/video). */\n centerSlot?: ReactNode;\n /** Top-right slot — cover management (collapse, remove). */\n topRightSlot?: ReactNode;\n /**\n * Collapsed cover: render the thin strip instead of the media box. The\n * collapse *control* lives in the caller's `topRightSlot`; this flag is the\n * resulting visual state. See the \"Collapsed cover\" story section.\n */\n collapsed?: boolean;\n /**\n * What the collapsed strip shows — an ~8px colour bar, or a favicon + domain\n * row for links. Content-specific, so the caller supplies it.\n */\n collapsedStrip?: ReactNode;\n /** Fired when the collapsed strip's expand affordance is activated. */\n onExpand?: () => void;\n /** Accessible label for the expand affordance. */\n expandLabel?: string;\n className?: string;\n /** The fill: an `<img>`, an inline `<svg>`, or a solid-colour box. */\n children: ReactNode;\n}\n\nexport const Cover = ({\n aspectRatio = \"260 / 132\",\n centerSlot,\n topRightSlot,\n collapsed,\n collapsedStrip,\n onExpand,\n expandLabel = \"Expand cover\",\n className,\n children,\n}: CoverProps): ReactElement => (\n <StackRow role=\"cover\" bleed className={className}>\n {collapsed ? (\n <StyledCollapsedStrip\n type=\"button\"\n aria-label={expandLabel}\n onClick={onExpand}\n >\n {collapsedStrip}\n <StyledStripExpand className=\"sc-control\" aria-hidden=\"true\">\n <ExpandGlyph />\n </StyledStripExpand>\n </StyledCollapsedStrip>\n ) : (\n <StyledCoverMedia $aspectRatio={aspectRatio}>\n <StyledCoverFill>{children}</StyledCoverFill>\n {centerSlot ? (\n <StyledCoverCenter className=\"sc-control\">\n {centerSlot}\n </StyledCoverCenter>\n ) : null}\n {topRightSlot ? (\n <StyledCoverTopRight className=\"sc-control\">\n {topRightSlot}\n </StyledCoverTopRight>\n ) : null}\n </StyledCoverMedia>\n )}\n </StackRow>\n);\n\n/* ------------------------------------------------------------------ */\n/* Cover controls — dark, scrim-backed chrome shared by every story. */\n/* ------------------------------------------------------------------ */\n\n/* Centered media control (a play button). The caller drops the glyph inside. */\nexport const PlayBadge = styled.span`\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n height: 44px;\n border-radius: 12px;\n background: var(--sc-scrim);\n color: var(--sc-scrim-fg);\n\n svg {\n fill: currentColor;\n width: 20px;\n height: 20px;\n margin-left: 2px;\n }\n`;\n\n/* Top-right cover-management button (collapse, remove …). */\nexport const CoverButton = styled.button`\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 30px;\n height: 30px;\n padding: 0;\n border: 0;\n border-radius: 8px;\n cursor: pointer;\n color: var(--sc-scrim-fg);\n background: var(--sc-scrim);\n\n svg {\n width: 16px;\n height: 16px;\n display: block;\n }\n\n &:hover {\n background: var(--sc-scrim-strong);\n }\n`;\n\n/* Cover-management glyphs (act on the cover itself, not the entity). */\nexport const CollapseGlyph = (): ReactElement => (\n <svg viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\">\n <path\n d=\"M6.5 2.5v4h-4M9.5 13.5v-4h4\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n);\n\n/* Expand glyph — the inverse of collapse; shown on the collapsed strip. */\nexport const ExpandGlyph = (): ReactElement => (\n <svg viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\">\n <path\n d=\"M2.5 6.5v-4h4M13.5 9.5v4h-4\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n);\n\nexport const RemoveCoverGlyph = (): ReactElement => (\n <svg viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\">\n <rect\n x=\"2.25\"\n y=\"3.75\"\n width=\"11.5\"\n height=\"8.5\"\n rx=\"1.5\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n />\n <path\n d=\"M6 9.5L10 6M10 9.5L6 6\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n </svg>\n);\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * StackedCard resize policy — pure math (story-local, presentation-only).
3
+ *
4
+ * The card only reports deltas; the HOST applies a resize policy (note 52097
5
+ * §7). This module is that policy, packaged as pure functions so the SAME code
6
+ * path serves both the pointer drag and the keyboard resize (task 5/8). Nothing
7
+ * here touches the DOM, React, or the content — it takes a start size, a delta,
8
+ * and a policy, and returns a clamped size. The story wires events to it; the
9
+ * card's intrinsic content minimum is measured story-side and passed in as
10
+ * `contentMin`, so this module knows nothing about heroes or footers.
11
+ *
12
+ * The policy has four parts, all preserved from the concept:
13
+ * - bounds — optional min/max per axis; unset means unbounded.
14
+ * - proportional — lock the aspect ratio captured at the start of the gesture;
15
+ * the scale is clamped by the BINDING axis so the ratio never
16
+ * distorts at a bound.
17
+ * - step — optional grid snap; proportion wins over the grid.
18
+ * - content min — the effective min is max(policyMin, contentMin) per axis,
19
+ * so the card can never be crushed below what its content needs.
20
+ */
21
+ export interface Size {
22
+ w: number;
23
+ h: number;
24
+ }
25
+ /** The card's intrinsic minimum, measured from its real content. */
26
+ export interface ContentMin {
27
+ w: number;
28
+ h: number;
29
+ }
30
+ export interface Policy {
31
+ minW?: number;
32
+ maxW?: number;
33
+ minH?: number;
34
+ maxH?: number;
35
+ /** Lock the aspect ratio for the whole gesture. */
36
+ proportional: boolean;
37
+ /** Grid snap increment in px; omitted / 0 means continuous. */
38
+ step?: number;
39
+ }
40
+ /** The size captured at the start of a gesture (pointer down or a key press). */
41
+ export interface ResizeStart {
42
+ w: number;
43
+ h: number;
44
+ contentMin: ContentMin;
45
+ }
46
+ export interface ResizeDelta {
47
+ dx: number;
48
+ dy: number;
49
+ }
50
+ export interface EffectiveBounds {
51
+ wMin: number;
52
+ wMax: number;
53
+ hMin: number;
54
+ hMax: number;
55
+ }
56
+ /** Keyboard resize nudges by the policy step, or this when no step is set. */
57
+ export declare const DEFAULT_KEYBOARD_STEP = 8;
58
+ /** Shift + arrow resizes by a larger increment (the familiar coarse nudge). */
59
+ export declare const SHIFT_STEP_MULTIPLIER = 4;
60
+ /** Keys the separator handles; everything else falls through to the browser. */
61
+ export declare const RESIZE_KEYS: ReadonlyArray<string>;
62
+ export declare const isResizeKey: (key: string) => boolean;
63
+ export declare const clamp: (v: number, lo: number, hi?: number) => number;
64
+ export declare const snap: (v: number, step?: number) => number;
65
+ export declare const makeStart: (size: Size, contentMin: ContentMin) => ResizeStart;
66
+ /**
67
+ * The effective bounds a size is clamped to: the policy min floored by the
68
+ * content min, and the policy max (unbounded when unset). Independent of any
69
+ * live gesture, so the story can also use it for the aria value and Home/End.
70
+ */
71
+ export declare const effectiveBounds: (policy: Policy, contentMin: ContentMin) => EffectiveBounds;
72
+ /**
73
+ * Apply a delta to the start size under the policy — the single path for both
74
+ * inputs. `constrain` is the transient Shift-to-constrain lock: a free-form
75
+ * policy behaves proportionally for that one gesture; a proportional policy is
76
+ * already locked and ignores it.
77
+ */
78
+ export declare const applyResize: (start: ResizeStart, delta: ResizeDelta, policy: Policy, opts?: {
79
+ constrain?: boolean;
80
+ }) => Size;
81
+ /**
82
+ * Translate a resize key into the next size, reusing `applyResize` so the
83
+ * keyboard clamps exactly like the pointer. Arrows nudge by the step (Shift ×4);
84
+ * Home/End jump to the min/max of the allowed range. Returns null for any key
85
+ * the handle does not own.
86
+ */
87
+ export declare const keyboardResize: (key: string, size: Size, policy: Policy, contentMin: ContentMin, opts?: {
88
+ shiftKey?: boolean;
89
+ }) => Size | null;
90
+ /**
91
+ * The handle's `aria-valuenow`: the current size as a percent (0–100) of its
92
+ * allowed range. The corner resizes both axes, but the value must be a single
93
+ * number, so it reports the WIDTH axis — the one axis both the fixed-box and the
94
+ * width-only cards share. Returns 0 when the range isn't finite (no max bound).
95
+ */
96
+ export declare const measurePercent: (size: Size, policy: Policy, contentMin: ContentMin) => number;
97
+ //# sourceMappingURL=resizePolicy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resizePolicy.d.ts","sourceRoot":"","sources":["../../../../src/presentation/stackedCard/resizePolicy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,WAAW,IAAI;IACnB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,oEAAoE;AACpE,MAAM,WAAW,UAAU;IACzB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,YAAY,EAAE,OAAO,CAAC;IACtB,+DAA+D;IAC/D,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,iFAAiF;AACjF,MAAM,WAAW,WAAW;IAC1B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,UAAU,EAAE,UAAU,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,8EAA8E;AAC9E,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAEvC,+EAA+E;AAC/E,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAEvC,gFAAgF;AAChF,eAAO,MAAM,WAAW,EAAE,aAAa,CAAC,MAAM,CAO7C,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,KAAG,OAAoC,CAAC;AAE/E,eAAO,MAAM,KAAK,GAAI,GAAG,MAAM,EAAE,IAAI,MAAM,EAAE,KAAK,MAAM,KAAG,MACA,CAAC;AAE5D,eAAO,MAAM,IAAI,GAAI,GAAG,MAAM,EAAE,OAAO,MAAM,KAAG,MACI,CAAC;AAErD,eAAO,MAAM,SAAS,GAAI,MAAM,IAAI,EAAE,YAAY,UAAU,KAAG,WAI7D,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAC1B,QAAQ,MAAM,EACd,YAAY,UAAU,KACrB,eAKD,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GACtB,OAAO,WAAW,EAClB,OAAO,WAAW,EAClB,QAAQ,MAAM,EACd,OAAM;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAO,KACjC,IAoCF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GACzB,KAAK,MAAM,EACX,MAAM,IAAI,EACV,QAAQ,MAAM,EACd,YAAY,UAAU,EACtB,OAAM;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAO,KAChC,IAAI,GAAG,IAoCT,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GACzB,MAAM,IAAI,EACV,QAAQ,MAAM,EACd,YAAY,UAAU,KACrB,MAIF,CAAC"}