@activecollab/components 2.0.363 → 2.0.365

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 (91) hide show
  1. package/dist/cjs/presentation/shared/index.js +37 -1
  2. package/dist/cjs/presentation/shared/index.js.map +1 -1
  3. package/dist/cjs/presentation/shared/settings.js +91 -0
  4. package/dist/cjs/presentation/shared/settings.js.map +1 -0
  5. package/dist/cjs/presentation/stackedCard/content/ImageCard.js +60 -0
  6. package/dist/cjs/presentation/stackedCard/content/ImageCard.js.map +1 -0
  7. package/dist/cjs/presentation/stackedCard/content/LinkCard.js +348 -0
  8. package/dist/cjs/presentation/stackedCard/content/LinkCard.js.map +1 -0
  9. package/dist/cjs/presentation/stackedCard/content/NoteCard.js +92 -0
  10. package/dist/cjs/presentation/stackedCard/content/NoteCard.js.map +1 -0
  11. package/dist/cjs/presentation/stackedCard/content/SwatchCard.js +74 -0
  12. package/dist/cjs/presentation/stackedCard/content/SwatchCard.js.map +1 -0
  13. package/dist/cjs/presentation/stackedCard/content/TaskCard.js +231 -0
  14. package/dist/cjs/presentation/stackedCard/content/TaskCard.js.map +1 -0
  15. package/dist/cjs/presentation/stackedCard/content/index.js +83 -0
  16. package/dist/cjs/presentation/stackedCard/content/index.js.map +1 -0
  17. package/dist/cjs/presentation/stackedCard/content/shared.js +144 -0
  18. package/dist/cjs/presentation/stackedCard/content/shared.js.map +1 -0
  19. package/dist/cjs/presentation/stackedCard/content/unfurl.js +172 -0
  20. package/dist/cjs/presentation/stackedCard/content/unfurl.js.map +1 -0
  21. package/dist/cjs/presentation/stackedCard/primitives.js +254 -0
  22. package/dist/cjs/presentation/stackedCard/primitives.js.map +1 -0
  23. package/dist/cjs/presentation/stackedCard/resizePolicy.js +183 -0
  24. package/dist/cjs/presentation/stackedCard/resizePolicy.js.map +1 -0
  25. package/dist/cjs/presentation/stackedCard/shell.js +165 -0
  26. package/dist/cjs/presentation/stackedCard/shell.js.map +1 -0
  27. package/dist/cjs/presentation/stackedCard/storyScaffold.js +72 -0
  28. package/dist/cjs/presentation/stackedCard/storyScaffold.js.map +1 -0
  29. package/dist/cjs/presentation/stackedCard/useInlineEdit.js +208 -0
  30. package/dist/cjs/presentation/stackedCard/useInlineEdit.js.map +1 -0
  31. package/dist/esm/presentation/shared/index.d.ts +2 -0
  32. package/dist/esm/presentation/shared/index.d.ts.map +1 -1
  33. package/dist/esm/presentation/shared/index.js +1 -0
  34. package/dist/esm/presentation/shared/index.js.map +1 -1
  35. package/dist/esm/presentation/shared/settings.d.ts +33 -0
  36. package/dist/esm/presentation/shared/settings.d.ts.map +1 -0
  37. package/dist/esm/presentation/shared/settings.js +84 -0
  38. package/dist/esm/presentation/shared/settings.js.map +1 -0
  39. package/dist/esm/presentation/stackedCard/content/ImageCard.d.ts +15 -0
  40. package/dist/esm/presentation/stackedCard/content/ImageCard.d.ts.map +1 -0
  41. package/dist/esm/presentation/stackedCard/content/ImageCard.js +50 -0
  42. package/dist/esm/presentation/stackedCard/content/ImageCard.js.map +1 -0
  43. package/dist/esm/presentation/stackedCard/content/LinkCard.d.ts +28 -0
  44. package/dist/esm/presentation/stackedCard/content/LinkCard.d.ts.map +1 -0
  45. package/dist/esm/presentation/stackedCard/content/LinkCard.js +336 -0
  46. package/dist/esm/presentation/stackedCard/content/LinkCard.js.map +1 -0
  47. package/dist/esm/presentation/stackedCard/content/NoteCard.d.ts +10 -0
  48. package/dist/esm/presentation/stackedCard/content/NoteCard.d.ts.map +1 -0
  49. package/dist/esm/presentation/stackedCard/content/NoteCard.js +82 -0
  50. package/dist/esm/presentation/stackedCard/content/NoteCard.js.map +1 -0
  51. package/dist/esm/presentation/stackedCard/content/SwatchCard.d.ts +14 -0
  52. package/dist/esm/presentation/stackedCard/content/SwatchCard.d.ts.map +1 -0
  53. package/dist/esm/presentation/stackedCard/content/SwatchCard.js +64 -0
  54. package/dist/esm/presentation/stackedCard/content/SwatchCard.js.map +1 -0
  55. package/dist/esm/presentation/stackedCard/content/TaskCard.d.ts +29 -0
  56. package/dist/esm/presentation/stackedCard/content/TaskCard.d.ts.map +1 -0
  57. package/dist/esm/presentation/stackedCard/content/TaskCard.js +218 -0
  58. package/dist/esm/presentation/stackedCard/content/TaskCard.js.map +1 -0
  59. package/dist/esm/presentation/stackedCard/content/index.d.ts +8 -0
  60. package/dist/esm/presentation/stackedCard/content/index.d.ts.map +1 -0
  61. package/dist/esm/presentation/stackedCard/content/index.js +8 -0
  62. package/dist/esm/presentation/stackedCard/content/index.js.map +1 -0
  63. package/dist/esm/presentation/stackedCard/content/shared.d.ts +46 -0
  64. package/dist/esm/presentation/stackedCard/content/shared.d.ts.map +1 -0
  65. package/dist/esm/presentation/stackedCard/content/shared.js +134 -0
  66. package/dist/esm/presentation/stackedCard/content/shared.js.map +1 -0
  67. package/dist/esm/presentation/stackedCard/content/unfurl.d.ts +89 -0
  68. package/dist/esm/presentation/stackedCard/content/unfurl.d.ts.map +1 -0
  69. package/dist/esm/presentation/stackedCard/content/unfurl.js +147 -0
  70. package/dist/esm/presentation/stackedCard/content/unfurl.js.map +1 -0
  71. package/dist/esm/presentation/stackedCard/primitives.d.ts +84 -0
  72. package/dist/esm/presentation/stackedCard/primitives.d.ts.map +1 -0
  73. package/dist/esm/presentation/stackedCard/primitives.js +241 -0
  74. package/dist/esm/presentation/stackedCard/primitives.js.map +1 -0
  75. package/dist/esm/presentation/stackedCard/resizePolicy.d.ts +97 -0
  76. package/dist/esm/presentation/stackedCard/resizePolicy.d.ts.map +1 -0
  77. package/dist/esm/presentation/stackedCard/resizePolicy.js +173 -0
  78. package/dist/esm/presentation/stackedCard/resizePolicy.js.map +1 -0
  79. package/dist/esm/presentation/stackedCard/shell.d.ts +46 -0
  80. package/dist/esm/presentation/stackedCard/shell.d.ts.map +1 -0
  81. package/dist/esm/presentation/stackedCard/shell.js +156 -0
  82. package/dist/esm/presentation/stackedCard/shell.js.map +1 -0
  83. package/dist/esm/presentation/stackedCard/storyScaffold.d.ts +28 -0
  84. package/dist/esm/presentation/stackedCard/storyScaffold.d.ts.map +1 -0
  85. package/dist/esm/presentation/stackedCard/storyScaffold.js +66 -0
  86. package/dist/esm/presentation/stackedCard/storyScaffold.js.map +1 -0
  87. package/dist/esm/presentation/stackedCard/useInlineEdit.d.ts +68 -0
  88. package/dist/esm/presentation/stackedCard/useInlineEdit.d.ts.map +1 -0
  89. package/dist/esm/presentation/stackedCard/useInlineEdit.js +192 -0
  90. package/dist/esm/presentation/stackedCard/useInlineEdit.js.map +1 -0
  91. package/package.json +1 -1
@@ -0,0 +1,134 @@
1
+ import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
2
+ import _extends from "@babel/runtime/helpers/esm/extends";
3
+ const _excluded = ["icon", "label", "height", "dashed"];
4
+ import React from "react";
5
+ import styled, { css } from "styled-components";
6
+ import { PlayIcon, TreeDotsIcon } from "../../../components/Icons";
7
+ import { Body2 } from "../../../components/Typography";
8
+ import { Typography } from "../../../components/Typography/Typography";
9
+ import { CollapseGlyph, CoverButton, PlayBadge, RemoveCoverGlyph } from "../primitives";
10
+
11
+ /**
12
+ * Content-level chrome shared by the typed StackedCard components.
13
+ *
14
+ * These live OUTSIDE the shell on purpose. The shell is hollow; the "…" entity
15
+ * menu, the cover-management controls, the clamp, the favicon, the chips and
16
+ * the placeholder frame are content concerns, so they belong here where the
17
+ * typed cards (TaskCard, LinkCard, …) compose them.
18
+ */
19
+
20
+ /* ------------------------------------------------------------------ */
21
+ /* Cover chrome — media control + cover management */
22
+ /* ------------------------------------------------------------------ */
23
+
24
+ /** Centered media control (play), for a cover that wraps audio/video. */
25
+ export const playControl = /*#__PURE__*/React.createElement(PlayBadge, null, /*#__PURE__*/React.createElement(PlayIcon, null));
26
+
27
+ /**
28
+ * Top-right cover management. Acts on the COVER (collapse it, remove it) — not
29
+ * on the entity. Callers wire the click handlers; the default is inert chrome
30
+ * for the static demos.
31
+ */
32
+ export const coverManagement = function (_temp) {
33
+ let _ref = _temp === void 0 ? {} : _temp,
34
+ _ref$removeLabel = _ref.removeLabel,
35
+ removeLabel = _ref$removeLabel === void 0 ? "Remove cover" : _ref$removeLabel,
36
+ onCollapse = _ref.onCollapse,
37
+ onRemove = _ref.onRemove;
38
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(CoverButton, {
39
+ type: "button",
40
+ "aria-label": "Collapse cover",
41
+ onClick: onCollapse
42
+ }, /*#__PURE__*/React.createElement(CollapseGlyph, null)), /*#__PURE__*/React.createElement(CoverButton, {
43
+ type: "button",
44
+ "aria-label": removeLabel,
45
+ onClick: onRemove
46
+ }, /*#__PURE__*/React.createElement(RemoveCoverGlyph, null)));
47
+ };
48
+
49
+ /* ------------------------------------------------------------------ */
50
+ /* Entity "…" menu — acts on the ENTITY, lives in a content row */
51
+ /* ------------------------------------------------------------------ */
52
+
53
+ const StyledEntityDots = styled.button.withConfig({
54
+ displayName: "shared__StyledEntityDots",
55
+ componentId: "sc-1x5md9a-0"
56
+ })(["display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;padding:0;border:0;border-radius:6px;cursor:pointer;color:var(--color-theme-700);background:var(--color-theme-200);svg{width:16px;height:16px;display:block;}&:hover{background:var(--color-theme-300);}"]);
57
+ export const EntityDots = props => /*#__PURE__*/React.createElement(StyledEntityDots, _extends({
58
+ type: "button",
59
+ "aria-label": "Options",
60
+ "data-fixed": true,
61
+ className: "sc-control"
62
+ }, props), /*#__PURE__*/React.createElement(TreeDotsIcon, null));
63
+
64
+ /* ------------------------------------------------------------------ */
65
+ /* Text + inline bits */
66
+ /* ------------------------------------------------------------------ */
67
+
68
+ /** Two-line title clamp — the card's name row content. */
69
+ export const StyledClamp = styled(Body2).withConfig({
70
+ displayName: "shared__StyledClamp",
71
+ componentId: "sc-1x5md9a-1"
72
+ })(["display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;"]);
73
+
74
+ /** A favicon square — a solid data colour stands in for the real favicon. */
75
+ export const StyledFavicon = styled.span.withConfig({
76
+ displayName: "shared__StyledFavicon",
77
+ componentId: "sc-1x5md9a-2"
78
+ })(["display:inline-flex;width:16px;height:16px;border-radius:4px;background:", ";flex-shrink:0;"], _ref2 => {
79
+ let $bg = _ref2.$bg;
80
+ return $bg;
81
+ });
82
+
83
+ /** A small pill for link metadata (media kind, duration, …). */
84
+ export const StyledChip = styled.span.withConfig({
85
+ displayName: "shared__StyledChip",
86
+ componentId: "sc-1x5md9a-3"
87
+ })(["display:inline-flex;align-items:center;padding:2px 8px;border-radius:10px;background:var(--color-theme-300);color:var(--color-theme-800);font-size:11px;white-space:nowrap;"]);
88
+
89
+ /** A solid-colour cover fill (swatch value, generic link cover, image stand-in). */
90
+ export const StyledColorCover = styled.div.withConfig({
91
+ displayName: "shared__StyledColorCover",
92
+ componentId: "sc-1x5md9a-4"
93
+ })(["width:100%;height:100%;background:", ";"], _ref3 => {
94
+ let $color = _ref3.$color;
95
+ return $color;
96
+ });
97
+
98
+ /* ------------------------------------------------------------------ */
99
+ /* Placeholder frame — the interactive empty state, per type */
100
+ /* ------------------------------------------------------------------ */
101
+
102
+ const StyledPlaceholder = styled.button.withConfig({
103
+ displayName: "shared__StyledPlaceholder",
104
+ componentId: "sc-1x5md9a-5"
105
+ })(["display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;width:100%;height:", "px;padding:0;border:0;background:transparent;color:var(--color-theme-600);text-align:center;cursor:pointer;", " > svg{width:40px;height:40px;color:var(--color-theme-500);}"], _ref4 => {
106
+ let $height = _ref4.$height;
107
+ return $height != null ? $height : 150;
108
+ }, _ref5 => {
109
+ let $dashed = _ref5.$dashed;
110
+ return $dashed && css(["margin:8px;width:calc(100% - 16px);border:1.5px dashed var(--color-theme-400);border-radius:6px;&:hover{border-color:var(--sc-selection-ring);color:var(--color-theme-700);}"]);
111
+ });
112
+
113
+ /**
114
+ * The shared placeholder frame. Each type supplies its own icon and verb-first,
115
+ * sentence-case invitation (ActiveCollab voice). Interactive by design — the
116
+ * whole frame is a button — and distinct from the loading skeleton.
117
+ */
118
+ export const Placeholder = _ref6 => {
119
+ let icon = _ref6.icon,
120
+ label = _ref6.label,
121
+ height = _ref6.height,
122
+ dashed = _ref6.dashed,
123
+ rest = _objectWithoutPropertiesLoose(_ref6, _excluded);
124
+ return /*#__PURE__*/React.createElement(StyledPlaceholder, _extends({
125
+ type: "button",
126
+ $height: height,
127
+ $dashed: dashed
128
+ }, rest), icon, /*#__PURE__*/React.createElement(Typography, {
129
+ as: "span",
130
+ variant: "Body 2",
131
+ color: "secondary"
132
+ }, label));
133
+ };
134
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.js","names":["React","styled","css","PlayIcon","TreeDotsIcon","Body2","Typography","CollapseGlyph","CoverButton","PlayBadge","RemoveCoverGlyph","playControl","createElement","coverManagement","_temp","_ref","_ref$removeLabel","removeLabel","onCollapse","onRemove","Fragment","type","onClick","StyledEntityDots","button","withConfig","displayName","componentId","EntityDots","props","_extends","className","StyledClamp","StyledFavicon","span","_ref2","$bg","StyledChip","StyledColorCover","div","_ref3","$color","StyledPlaceholder","_ref4","$height","_ref5","$dashed","Placeholder","_ref6","icon","label","height","dashed","rest","_objectWithoutPropertiesLoose","_excluded","as","variant","color"],"sources":["../../../../../src/presentation/stackedCard/content/shared.tsx"],"sourcesContent":["import React, { ReactElement, ReactNode } from \"react\";\n\nimport styled, { css } from \"styled-components\";\n\nimport { PlayIcon, TreeDotsIcon } from \"../../../components/Icons\";\nimport { Body2 } from \"../../../components/Typography\";\nimport { Typography } from \"../../../components/Typography/Typography\";\nimport {\n CollapseGlyph,\n CoverButton,\n PlayBadge,\n RemoveCoverGlyph,\n} from \"../primitives\";\n\n/**\n * Content-level chrome shared by the typed StackedCard components.\n *\n * These live OUTSIDE the shell on purpose. The shell is hollow; the \"…\" entity\n * menu, the cover-management controls, the clamp, the favicon, the chips and\n * the placeholder frame are content concerns, so they belong here where the\n * typed cards (TaskCard, LinkCard, …) compose them.\n */\n\n/* ------------------------------------------------------------------ */\n/* Cover chrome — media control + cover management */\n/* ------------------------------------------------------------------ */\n\n/** Centered media control (play), for a cover that wraps audio/video. */\nexport const playControl = (\n <PlayBadge>\n <PlayIcon />\n </PlayBadge>\n);\n\n/**\n * Top-right cover management. Acts on the COVER (collapse it, remove it) — not\n * on the entity. Callers wire the click handlers; the default is inert chrome\n * for the static demos.\n */\nexport const coverManagement = ({\n removeLabel = \"Remove cover\",\n onCollapse,\n onRemove,\n}: {\n removeLabel?: string;\n onCollapse?: () => void;\n onRemove?: () => void;\n} = {}): ReactElement => (\n <>\n <CoverButton type=\"button\" aria-label=\"Collapse cover\" onClick={onCollapse}>\n <CollapseGlyph />\n </CoverButton>\n <CoverButton type=\"button\" aria-label={removeLabel} onClick={onRemove}>\n <RemoveCoverGlyph />\n </CoverButton>\n </>\n);\n\n/* ------------------------------------------------------------------ */\n/* Entity \"…\" menu — acts on the ENTITY, lives in a content row */\n/* ------------------------------------------------------------------ */\n\nconst StyledEntityDots = styled.button`\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n padding: 0;\n border: 0;\n border-radius: 6px;\n cursor: pointer;\n color: var(--color-theme-700);\n background: var(--color-theme-200);\n\n svg {\n width: 16px;\n height: 16px;\n display: block;\n }\n\n &:hover {\n background: var(--color-theme-300);\n }\n`;\n\nexport const EntityDots = (\n props: React.ButtonHTMLAttributes<HTMLButtonElement>\n): ReactElement => (\n <StyledEntityDots\n type=\"button\"\n aria-label=\"Options\"\n data-fixed\n className=\"sc-control\"\n {...props}\n >\n <TreeDotsIcon />\n </StyledEntityDots>\n);\n\n/* ------------------------------------------------------------------ */\n/* Text + inline bits */\n/* ------------------------------------------------------------------ */\n\n/** Two-line title clamp — the card's name row content. */\nexport const StyledClamp = styled(Body2)`\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n`;\n\n/** A favicon square — a solid data colour stands in for the real favicon. */\nexport const StyledFavicon = styled.span<{ $bg: string }>`\n display: inline-flex;\n width: 16px;\n height: 16px;\n border-radius: 4px;\n background: ${({ $bg }) => $bg};\n flex-shrink: 0;\n`;\n\n/** A small pill for link metadata (media kind, duration, …). */\nexport const StyledChip = styled.span`\n display: inline-flex;\n align-items: center;\n padding: 2px 8px;\n border-radius: 10px;\n background: var(--color-theme-300);\n color: var(--color-theme-800);\n font-size: 11px;\n white-space: nowrap;\n`;\n\n/** A solid-colour cover fill (swatch value, generic link cover, image stand-in). */\nexport const StyledColorCover = styled.div<{ $color: string }>`\n width: 100%;\n height: 100%;\n background: ${({ $color }) => $color};\n`;\n\n/* ------------------------------------------------------------------ */\n/* Placeholder frame — the interactive empty state, per type */\n/* ------------------------------------------------------------------ */\n\nconst StyledPlaceholder = styled.button<{\n $height?: number;\n $dashed?: boolean;\n}>`\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 10px;\n width: 100%;\n height: ${({ $height }) => $height ?? 150}px;\n padding: 0;\n border: 0;\n background: transparent;\n color: var(--color-theme-600);\n text-align: center;\n cursor: pointer;\n\n ${({ $dashed }) =>\n $dashed &&\n css`\n margin: 8px;\n width: calc(100% - 16px);\n border: 1.5px dashed var(--color-theme-400);\n border-radius: 6px;\n\n &:hover {\n border-color: var(--sc-selection-ring);\n color: var(--color-theme-700);\n }\n `}\n\n > svg {\n width: 40px;\n height: 40px;\n color: var(--color-theme-500);\n }\n`;\n\n/**\n * The shared placeholder frame. Each type supplies its own icon and verb-first,\n * sentence-case invitation (ActiveCollab voice). Interactive by design — the\n * whole frame is a button — and distinct from the loading skeleton.\n */\nexport const Placeholder = ({\n icon,\n label,\n height,\n dashed,\n ...rest\n}: {\n icon: ReactNode;\n label: ReactNode;\n height?: number;\n dashed?: boolean;\n} & React.ButtonHTMLAttributes<HTMLButtonElement>): ReactElement => (\n <StyledPlaceholder type=\"button\" $height={height} $dashed={dashed} {...rest}>\n {icon}\n <Typography as=\"span\" variant=\"Body 2\" color=\"secondary\">\n {label}\n </Typography>\n </StyledPlaceholder>\n);\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Mock unfurl service for the LinkCard concept.
3
+ *
4
+ * A link preview is a LIFECYCLE, not two static layouts. The real service is
5
+ * server-side (SSRF-guarded, oEmbed → OpenGraph → Twitter Cards → <title>,
6
+ * cached ~24h with a stored `fetchedAt` and a manual re-unfurl affordance — see
7
+ * note 52104 §B3). This is a promise-based fake with a configurable delay and a
8
+ * failure toggle so the story can drive the whole lifecycle without a network.
9
+ *
10
+ * The data arrives on a PROGRESSIVE LADDER of three tiers, each carrying more
11
+ * than the last:
12
+ * url → just the raw URL (nothing fetched yet)
13
+ * basic → favicon + title + domain
14
+ * full → + description, hero image, media kind / duration
15
+ * The card reserves the next tier's space at every tier, so climbing the ladder
16
+ * never reflows the card.
17
+ */
18
+ export type UnfurlTier = "url" | "basic" | "full";
19
+ export type UnfurlStatus = "resolving" | "resolved" | "stale" | "error";
20
+ /** The cumulative data shape. Fields fill in as the ladder climbs. */
21
+ export interface UnfurlData {
22
+ /** Always present — the raw pasted URL. */
23
+ url: string;
24
+ /** Tier `basic` and up: a stand-in favicon colour. */
25
+ faviconColor?: string;
26
+ /** Tier `basic` and up: page title. */
27
+ title?: string;
28
+ /** Tier `basic` and up: the display domain. */
29
+ domain?: string;
30
+ /** Tier `full`: short description / summary. */
31
+ description?: string;
32
+ /** Tier `full`: hero image src (a real <img> in production). */
33
+ heroSrc?: string;
34
+ /** Tier `full`: hero fill colour, used when there is no hero image. */
35
+ heroColor?: string;
36
+ /** Tier `full`: media kind chip (Video, Article, …). */
37
+ kind?: string;
38
+ /** Tier `full`: duration chip for playable media. */
39
+ duration?: string;
40
+ /** Tier `full`: whether the media is playable (shows the cover play badge). */
41
+ playable?: boolean;
42
+ }
43
+ /** A seed link — what the fake "server" would resolve the URL to at tier full. */
44
+ export interface MockLink {
45
+ url: string;
46
+ faviconColor: string;
47
+ title: string;
48
+ domain: string;
49
+ description: string;
50
+ heroColor: string;
51
+ heroSrc?: string;
52
+ kind: string;
53
+ duration?: string;
54
+ playable?: boolean;
55
+ }
56
+ export declare const MOCK_LINKS: Record<string, MockLink>;
57
+ export interface UnfurlConfig {
58
+ /** Which seed link to resolve. */
59
+ link: MockLink;
60
+ /** Per-tier delay in ms (url→basic and basic→full each wait this long). */
61
+ delayMs?: number;
62
+ /** When true, the unfurl fails partway and lands in the error state. */
63
+ shouldFail?: boolean;
64
+ }
65
+ export interface UnfurlController {
66
+ status: UnfurlStatus;
67
+ tier: UnfurlTier;
68
+ data: UnfurlData;
69
+ /** Epoch ms of the last successful resolve (drives the stale affordance). */
70
+ fetchedAt: number | null;
71
+ /** Start (or restart) the unfurl ladder from scratch. */
72
+ start: () => void;
73
+ /** Retry after an error — re-runs the simulation. */
74
+ retry: () => void;
75
+ /** Re-unfurl a resolved/stale card — re-runs the simulation. */
76
+ reunfurl: () => void;
77
+ /** Force the resolved card into the stale state (demo affordance). */
78
+ markStale: () => void;
79
+ }
80
+ /**
81
+ * Drives one link through the unfurl lifecycle. Climbs url → basic → full on a
82
+ * timer, or lands in `error` if `shouldFail`. `retry` / `reunfurl` re-run it;
83
+ * `markStale` moves a resolved card to the stale state so the re-unfurl
84
+ * affordance can be shown.
85
+ */
86
+ export declare const useUnfurl: ({ link, delayMs, shouldFail, }: UnfurlConfig) => UnfurlController;
87
+ /** Human-readable "fetched N ago" for the stale affordance. */
88
+ export declare const formatFetchedAt: (fetchedAt: number | null) => string;
89
+ //# sourceMappingURL=unfurl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unfurl.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/stackedCard/content/unfurl.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,OAAO,GAAG,MAAM,CAAC;AAElD,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,CAAC;AAExE,sEAAsE;AACtE,MAAM,WAAW,UAAU;IACzB,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,kFAAkF;AAClF,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAyB/C,CAAC;AAgBF,MAAM,WAAW,YAAY;IAC3B,kCAAkC;IAClC,IAAI,EAAE,QAAQ,CAAC;IACf,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,YAAY,CAAC;IACrB,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;IACjB,6EAA6E;IAC7E,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,yDAAyD;IACzD,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,qDAAqD;IACrD,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,gEAAgE;IAChE,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,sEAAsE;IACtE,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB;AAED;;;;;GAKG;AACH,eAAO,MAAM,SAAS,GAAI,gCAIvB,YAAY,KAAG,gBA6DjB,CAAC;AAEF,+DAA+D;AAC/D,eAAO,MAAM,eAAe,GAAI,WAAW,MAAM,GAAG,IAAI,KAAG,MAS1D,CAAC"}
@@ -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"}