@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.
- package/dist/cjs/presentation/shared/index.js +37 -1
- package/dist/cjs/presentation/shared/index.js.map +1 -1
- package/dist/cjs/presentation/shared/settings.js +91 -0
- package/dist/cjs/presentation/shared/settings.js.map +1 -0
- package/dist/cjs/presentation/stackedCard/content/ImageCard.js +60 -0
- package/dist/cjs/presentation/stackedCard/content/ImageCard.js.map +1 -0
- package/dist/cjs/presentation/stackedCard/content/LinkCard.js +348 -0
- package/dist/cjs/presentation/stackedCard/content/LinkCard.js.map +1 -0
- package/dist/cjs/presentation/stackedCard/content/NoteCard.js +92 -0
- package/dist/cjs/presentation/stackedCard/content/NoteCard.js.map +1 -0
- package/dist/cjs/presentation/stackedCard/content/SwatchCard.js +74 -0
- package/dist/cjs/presentation/stackedCard/content/SwatchCard.js.map +1 -0
- package/dist/cjs/presentation/stackedCard/content/TaskCard.js +231 -0
- package/dist/cjs/presentation/stackedCard/content/TaskCard.js.map +1 -0
- package/dist/cjs/presentation/stackedCard/content/index.js +83 -0
- package/dist/cjs/presentation/stackedCard/content/index.js.map +1 -0
- package/dist/cjs/presentation/stackedCard/content/shared.js +144 -0
- package/dist/cjs/presentation/stackedCard/content/shared.js.map +1 -0
- package/dist/cjs/presentation/stackedCard/content/unfurl.js +172 -0
- package/dist/cjs/presentation/stackedCard/content/unfurl.js.map +1 -0
- package/dist/cjs/presentation/stackedCard/primitives.js +254 -0
- package/dist/cjs/presentation/stackedCard/primitives.js.map +1 -0
- package/dist/cjs/presentation/stackedCard/resizePolicy.js +183 -0
- package/dist/cjs/presentation/stackedCard/resizePolicy.js.map +1 -0
- package/dist/cjs/presentation/stackedCard/shell.js +165 -0
- package/dist/cjs/presentation/stackedCard/shell.js.map +1 -0
- package/dist/cjs/presentation/stackedCard/storyScaffold.js +72 -0
- package/dist/cjs/presentation/stackedCard/storyScaffold.js.map +1 -0
- package/dist/cjs/presentation/stackedCard/useInlineEdit.js +208 -0
- package/dist/cjs/presentation/stackedCard/useInlineEdit.js.map +1 -0
- package/dist/esm/presentation/shared/index.d.ts +2 -0
- package/dist/esm/presentation/shared/index.d.ts.map +1 -1
- package/dist/esm/presentation/shared/index.js +1 -0
- package/dist/esm/presentation/shared/index.js.map +1 -1
- package/dist/esm/presentation/shared/settings.d.ts +33 -0
- package/dist/esm/presentation/shared/settings.d.ts.map +1 -0
- package/dist/esm/presentation/shared/settings.js +84 -0
- package/dist/esm/presentation/shared/settings.js.map +1 -0
- package/dist/esm/presentation/stackedCard/content/ImageCard.d.ts +15 -0
- package/dist/esm/presentation/stackedCard/content/ImageCard.d.ts.map +1 -0
- package/dist/esm/presentation/stackedCard/content/ImageCard.js +50 -0
- package/dist/esm/presentation/stackedCard/content/ImageCard.js.map +1 -0
- package/dist/esm/presentation/stackedCard/content/LinkCard.d.ts +28 -0
- package/dist/esm/presentation/stackedCard/content/LinkCard.d.ts.map +1 -0
- package/dist/esm/presentation/stackedCard/content/LinkCard.js +336 -0
- package/dist/esm/presentation/stackedCard/content/LinkCard.js.map +1 -0
- package/dist/esm/presentation/stackedCard/content/NoteCard.d.ts +10 -0
- package/dist/esm/presentation/stackedCard/content/NoteCard.d.ts.map +1 -0
- package/dist/esm/presentation/stackedCard/content/NoteCard.js +82 -0
- package/dist/esm/presentation/stackedCard/content/NoteCard.js.map +1 -0
- package/dist/esm/presentation/stackedCard/content/SwatchCard.d.ts +14 -0
- package/dist/esm/presentation/stackedCard/content/SwatchCard.d.ts.map +1 -0
- package/dist/esm/presentation/stackedCard/content/SwatchCard.js +64 -0
- package/dist/esm/presentation/stackedCard/content/SwatchCard.js.map +1 -0
- package/dist/esm/presentation/stackedCard/content/TaskCard.d.ts +29 -0
- package/dist/esm/presentation/stackedCard/content/TaskCard.d.ts.map +1 -0
- package/dist/esm/presentation/stackedCard/content/TaskCard.js +218 -0
- package/dist/esm/presentation/stackedCard/content/TaskCard.js.map +1 -0
- package/dist/esm/presentation/stackedCard/content/index.d.ts +8 -0
- package/dist/esm/presentation/stackedCard/content/index.d.ts.map +1 -0
- package/dist/esm/presentation/stackedCard/content/index.js +8 -0
- package/dist/esm/presentation/stackedCard/content/index.js.map +1 -0
- package/dist/esm/presentation/stackedCard/content/shared.d.ts +46 -0
- package/dist/esm/presentation/stackedCard/content/shared.d.ts.map +1 -0
- package/dist/esm/presentation/stackedCard/content/shared.js +134 -0
- package/dist/esm/presentation/stackedCard/content/shared.js.map +1 -0
- package/dist/esm/presentation/stackedCard/content/unfurl.d.ts +89 -0
- package/dist/esm/presentation/stackedCard/content/unfurl.d.ts.map +1 -0
- package/dist/esm/presentation/stackedCard/content/unfurl.js +147 -0
- package/dist/esm/presentation/stackedCard/content/unfurl.js.map +1 -0
- package/dist/esm/presentation/stackedCard/primitives.d.ts +84 -0
- package/dist/esm/presentation/stackedCard/primitives.d.ts.map +1 -0
- package/dist/esm/presentation/stackedCard/primitives.js +241 -0
- package/dist/esm/presentation/stackedCard/primitives.js.map +1 -0
- package/dist/esm/presentation/stackedCard/resizePolicy.d.ts +97 -0
- package/dist/esm/presentation/stackedCard/resizePolicy.d.ts.map +1 -0
- package/dist/esm/presentation/stackedCard/resizePolicy.js +173 -0
- package/dist/esm/presentation/stackedCard/resizePolicy.js.map +1 -0
- package/dist/esm/presentation/stackedCard/shell.d.ts +46 -0
- package/dist/esm/presentation/stackedCard/shell.d.ts.map +1 -0
- package/dist/esm/presentation/stackedCard/shell.js +156 -0
- package/dist/esm/presentation/stackedCard/shell.js.map +1 -0
- package/dist/esm/presentation/stackedCard/storyScaffold.d.ts +28 -0
- package/dist/esm/presentation/stackedCard/storyScaffold.d.ts.map +1 -0
- package/dist/esm/presentation/stackedCard/storyScaffold.js +66 -0
- package/dist/esm/presentation/stackedCard/storyScaffold.js.map +1 -0
- package/dist/esm/presentation/stackedCard/useInlineEdit.d.ts +68 -0
- package/dist/esm/presentation/stackedCard/useInlineEdit.d.ts.map +1 -0
- package/dist/esm/presentation/stackedCard/useInlineEdit.js +192 -0
- package/dist/esm/presentation/stackedCard/useInlineEdit.js.map +1 -0
- package/package.json +1 -1
|
@@ -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"}
|
|
@@ -0,0 +1,173 @@
|
|
|
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
|
+
|
|
22
|
+
/** The card's intrinsic minimum, measured from its real content. */
|
|
23
|
+
|
|
24
|
+
/** The size captured at the start of a gesture (pointer down or a key press). */
|
|
25
|
+
|
|
26
|
+
/** Keyboard resize nudges by the policy step, or this when no step is set. */
|
|
27
|
+
export const DEFAULT_KEYBOARD_STEP = 8;
|
|
28
|
+
|
|
29
|
+
/** Shift + arrow resizes by a larger increment (the familiar coarse nudge). */
|
|
30
|
+
export const SHIFT_STEP_MULTIPLIER = 4;
|
|
31
|
+
|
|
32
|
+
/** Keys the separator handles; everything else falls through to the browser. */
|
|
33
|
+
export const RESIZE_KEYS = ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"];
|
|
34
|
+
export const isResizeKey = key => RESIZE_KEYS.includes(key);
|
|
35
|
+
export const clamp = (v, lo, hi) => Math.min(hi != null ? hi : Number.POSITIVE_INFINITY, Math.max(lo, v));
|
|
36
|
+
export const snap = (v, step) => step && step > 0 ? Math.round(v / step) * step : v;
|
|
37
|
+
export const makeStart = (size, contentMin) => ({
|
|
38
|
+
w: size.w,
|
|
39
|
+
h: size.h,
|
|
40
|
+
contentMin
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The effective bounds a size is clamped to: the policy min floored by the
|
|
45
|
+
* content min, and the policy max (unbounded when unset). Independent of any
|
|
46
|
+
* live gesture, so the story can also use it for the aria value and Home/End.
|
|
47
|
+
*/
|
|
48
|
+
export const effectiveBounds = (policy, contentMin) => {
|
|
49
|
+
var _policy$minW, _policy$minH, _policy$maxW, _policy$maxH;
|
|
50
|
+
return {
|
|
51
|
+
wMin: Math.max((_policy$minW = policy.minW) != null ? _policy$minW : 0, contentMin.w),
|
|
52
|
+
hMin: Math.max((_policy$minH = policy.minH) != null ? _policy$minH : 0, contentMin.h),
|
|
53
|
+
wMax: (_policy$maxW = policy.maxW) != null ? _policy$maxW : Number.POSITIVE_INFINITY,
|
|
54
|
+
hMax: (_policy$maxH = policy.maxH) != null ? _policy$maxH : Number.POSITIVE_INFINITY
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Apply a delta to the start size under the policy — the single path for both
|
|
60
|
+
* inputs. `constrain` is the transient Shift-to-constrain lock: a free-form
|
|
61
|
+
* policy behaves proportionally for that one gesture; a proportional policy is
|
|
62
|
+
* already locked and ignores it.
|
|
63
|
+
*/
|
|
64
|
+
export const applyResize = function (start, delta, policy, opts) {
|
|
65
|
+
if (opts === void 0) {
|
|
66
|
+
opts = {};
|
|
67
|
+
}
|
|
68
|
+
const _effectiveBounds = effectiveBounds(policy, start.contentMin),
|
|
69
|
+
wMin = _effectiveBounds.wMin,
|
|
70
|
+
hMin = _effectiveBounds.hMin,
|
|
71
|
+
wMax = _effectiveBounds.wMax,
|
|
72
|
+
hMax = _effectiveBounds.hMax;
|
|
73
|
+
const proportional = policy.proportional || opts.constrain === true;
|
|
74
|
+
let w;
|
|
75
|
+
let h;
|
|
76
|
+
if (proportional) {
|
|
77
|
+
// one scale drives both axes; the corner's dominant direction wins, so a
|
|
78
|
+
// vertical drag is as effective as a horizontal one
|
|
79
|
+
const sW = (start.w + delta.dx) / start.w;
|
|
80
|
+
const sH = (start.h + delta.dy) / start.h;
|
|
81
|
+
let scale = Math.abs(sW - 1) >= Math.abs(sH - 1) ? sW : sH;
|
|
82
|
+
|
|
83
|
+
// clamp the SCALE so both axes stay in bounds (binding constraint): the
|
|
84
|
+
// ratio can never break at a bound — whichever edge is limiting stops both
|
|
85
|
+
const scaleMin = Math.max(wMin / start.w, hMin / start.h);
|
|
86
|
+
const scaleMax = Math.min(wMax / start.w, hMax / start.h);
|
|
87
|
+
scale = clamp(scale, scaleMin, Math.max(scaleMin, scaleMax));
|
|
88
|
+
|
|
89
|
+
// step: proportion wins. Snap via the scale (driven off the width edge) and
|
|
90
|
+
// re-clamp, so ratio and bounds always hold; the grid yields when honoring
|
|
91
|
+
// it would cost the ratio
|
|
92
|
+
if (policy.step) {
|
|
93
|
+
const snappedScale = snap(start.w * scale, policy.step) / start.w;
|
|
94
|
+
scale = clamp(snappedScale, scaleMin, Math.max(scaleMin, scaleMax));
|
|
95
|
+
}
|
|
96
|
+
w = start.w * scale;
|
|
97
|
+
h = start.h * scale;
|
|
98
|
+
} else {
|
|
99
|
+
w = clamp(snap(start.w + delta.dx, policy.step), wMin, wMax);
|
|
100
|
+
h = clamp(snap(start.h + delta.dy, policy.step), hMin, hMax);
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
w: Math.round(w),
|
|
104
|
+
h: Math.round(h)
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Translate a resize key into the next size, reusing `applyResize` so the
|
|
110
|
+
* keyboard clamps exactly like the pointer. Arrows nudge by the step (Shift ×4);
|
|
111
|
+
* Home/End jump to the min/max of the allowed range. Returns null for any key
|
|
112
|
+
* the handle does not own.
|
|
113
|
+
*/
|
|
114
|
+
export const keyboardResize = function (key, size, policy, contentMin, opts) {
|
|
115
|
+
if (opts === void 0) {
|
|
116
|
+
opts = {};
|
|
117
|
+
}
|
|
118
|
+
const base = policy.step && policy.step > 0 ? policy.step : DEFAULT_KEYBOARD_STEP;
|
|
119
|
+
const step = base * (opts.shiftKey ? SHIFT_STEP_MULTIPLIER : 1);
|
|
120
|
+
const start = makeStart(size, contentMin);
|
|
121
|
+
const b = effectiveBounds(policy, contentMin);
|
|
122
|
+
switch (key) {
|
|
123
|
+
case "ArrowRight":
|
|
124
|
+
return applyResize(start, {
|
|
125
|
+
dx: step,
|
|
126
|
+
dy: 0
|
|
127
|
+
}, policy);
|
|
128
|
+
case "ArrowLeft":
|
|
129
|
+
return applyResize(start, {
|
|
130
|
+
dx: -step,
|
|
131
|
+
dy: 0
|
|
132
|
+
}, policy);
|
|
133
|
+
case "ArrowDown":
|
|
134
|
+
return applyResize(start, {
|
|
135
|
+
dx: 0,
|
|
136
|
+
dy: step
|
|
137
|
+
}, policy);
|
|
138
|
+
case "ArrowUp":
|
|
139
|
+
return applyResize(start, {
|
|
140
|
+
dx: 0,
|
|
141
|
+
dy: -step
|
|
142
|
+
}, policy);
|
|
143
|
+
case "Home":
|
|
144
|
+
// jump to the min corner (a huge negative delta clamps to the floor)
|
|
145
|
+
return applyResize(start, {
|
|
146
|
+
dx: b.wMin - size.w,
|
|
147
|
+
dy: b.hMin - size.h
|
|
148
|
+
}, policy);
|
|
149
|
+
case "End":
|
|
150
|
+
// jump to the max corner; an unbounded axis simply doesn't move
|
|
151
|
+
return applyResize(start, {
|
|
152
|
+
dx: Number.isFinite(b.wMax) ? b.wMax - size.w : 0,
|
|
153
|
+
dy: Number.isFinite(b.hMax) ? b.hMax - size.h : 0
|
|
154
|
+
}, policy);
|
|
155
|
+
default:
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The handle's `aria-valuenow`: the current size as a percent (0–100) of its
|
|
162
|
+
* allowed range. The corner resizes both axes, but the value must be a single
|
|
163
|
+
* number, so it reports the WIDTH axis — the one axis both the fixed-box and the
|
|
164
|
+
* width-only cards share. Returns 0 when the range isn't finite (no max bound).
|
|
165
|
+
*/
|
|
166
|
+
export const measurePercent = (size, policy, contentMin) => {
|
|
167
|
+
const _effectiveBounds2 = effectiveBounds(policy, contentMin),
|
|
168
|
+
wMin = _effectiveBounds2.wMin,
|
|
169
|
+
wMax = _effectiveBounds2.wMax;
|
|
170
|
+
if (!Number.isFinite(wMax) || wMax <= wMin) return 0;
|
|
171
|
+
return Math.round(clamp((size.w - wMin) / (wMax - wMin) * 100, 0, 100));
|
|
172
|
+
};
|
|
173
|
+
//# sourceMappingURL=resizePolicy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resizePolicy.js","names":["DEFAULT_KEYBOARD_STEP","SHIFT_STEP_MULTIPLIER","RESIZE_KEYS","isResizeKey","key","includes","clamp","v","lo","hi","Math","min","Number","POSITIVE_INFINITY","max","snap","step","round","makeStart","size","contentMin","w","h","effectiveBounds","policy","_policy$minW","_policy$minH","_policy$maxW","_policy$maxH","wMin","minW","hMin","minH","wMax","maxW","hMax","maxH","applyResize","start","delta","opts","_effectiveBounds","proportional","constrain","sW","dx","sH","dy","scale","abs","scaleMin","scaleMax","snappedScale","keyboardResize","base","shiftKey","b","isFinite","measurePercent","_effectiveBounds2"],"sources":["../../../../src/presentation/stackedCard/resizePolicy.ts"],"sourcesContent":["/**\n * StackedCard resize policy — pure math (story-local, presentation-only).\n *\n * The card only reports deltas; the HOST applies a resize policy (note 52097\n * §7). This module is that policy, packaged as pure functions so the SAME code\n * path serves both the pointer drag and the keyboard resize (task 5/8). Nothing\n * here touches the DOM, React, or the content — it takes a start size, a delta,\n * and a policy, and returns a clamped size. The story wires events to it; the\n * card's intrinsic content minimum is measured story-side and passed in as\n * `contentMin`, so this module knows nothing about heroes or footers.\n *\n * The policy has four parts, all preserved from the concept:\n * - bounds — optional min/max per axis; unset means unbounded.\n * - proportional — lock the aspect ratio captured at the start of the gesture;\n * the scale is clamped by the BINDING axis so the ratio never\n * distorts at a bound.\n * - step — optional grid snap; proportion wins over the grid.\n * - content min — the effective min is max(policyMin, contentMin) per axis,\n * so the card can never be crushed below what its content needs.\n */\n\nexport interface Size {\n w: number;\n h: number;\n}\n\n/** The card's intrinsic minimum, measured from its real content. */\nexport interface ContentMin {\n w: number;\n h: number;\n}\n\nexport interface Policy {\n minW?: number;\n maxW?: number;\n minH?: number;\n maxH?: number;\n /** Lock the aspect ratio for the whole gesture. */\n proportional: boolean;\n /** Grid snap increment in px; omitted / 0 means continuous. */\n step?: number;\n}\n\n/** The size captured at the start of a gesture (pointer down or a key press). */\nexport interface ResizeStart {\n w: number;\n h: number;\n contentMin: ContentMin;\n}\n\nexport interface ResizeDelta {\n dx: number;\n dy: number;\n}\n\nexport interface EffectiveBounds {\n wMin: number;\n wMax: number;\n hMin: number;\n hMax: number;\n}\n\n/** Keyboard resize nudges by the policy step, or this when no step is set. */\nexport const DEFAULT_KEYBOARD_STEP = 8;\n\n/** Shift + arrow resizes by a larger increment (the familiar coarse nudge). */\nexport const SHIFT_STEP_MULTIPLIER = 4;\n\n/** Keys the separator handles; everything else falls through to the browser. */\nexport const RESIZE_KEYS: ReadonlyArray<string> = [\n \"ArrowLeft\",\n \"ArrowRight\",\n \"ArrowUp\",\n \"ArrowDown\",\n \"Home\",\n \"End\",\n];\n\nexport const isResizeKey = (key: string): boolean => RESIZE_KEYS.includes(key);\n\nexport const clamp = (v: number, lo: number, hi?: number): number =>\n Math.min(hi ?? Number.POSITIVE_INFINITY, Math.max(lo, v));\n\nexport const snap = (v: number, step?: number): number =>\n step && step > 0 ? Math.round(v / step) * step : v;\n\nexport const makeStart = (size: Size, contentMin: ContentMin): ResizeStart => ({\n w: size.w,\n h: size.h,\n contentMin,\n});\n\n/**\n * The effective bounds a size is clamped to: the policy min floored by the\n * content min, and the policy max (unbounded when unset). Independent of any\n * live gesture, so the story can also use it for the aria value and Home/End.\n */\nexport const effectiveBounds = (\n policy: Policy,\n contentMin: ContentMin\n): EffectiveBounds => ({\n wMin: Math.max(policy.minW ?? 0, contentMin.w),\n hMin: Math.max(policy.minH ?? 0, contentMin.h),\n wMax: policy.maxW ?? Number.POSITIVE_INFINITY,\n hMax: policy.maxH ?? Number.POSITIVE_INFINITY,\n});\n\n/**\n * Apply a delta to the start size under the policy — the single path for both\n * inputs. `constrain` is the transient Shift-to-constrain lock: a free-form\n * policy behaves proportionally for that one gesture; a proportional policy is\n * already locked and ignores it.\n */\nexport const applyResize = (\n start: ResizeStart,\n delta: ResizeDelta,\n policy: Policy,\n opts: { constrain?: boolean } = {}\n): Size => {\n const { wMin, hMin, wMax, hMax } = effectiveBounds(policy, start.contentMin);\n const proportional = policy.proportional || opts.constrain === true;\n\n let w: number;\n let h: number;\n\n if (proportional) {\n // one scale drives both axes; the corner's dominant direction wins, so a\n // vertical drag is as effective as a horizontal one\n const sW = (start.w + delta.dx) / start.w;\n const sH = (start.h + delta.dy) / start.h;\n let scale = Math.abs(sW - 1) >= Math.abs(sH - 1) ? sW : sH;\n\n // clamp the SCALE so both axes stay in bounds (binding constraint): the\n // ratio can never break at a bound — whichever edge is limiting stops both\n const scaleMin = Math.max(wMin / start.w, hMin / start.h);\n const scaleMax = Math.min(wMax / start.w, hMax / start.h);\n scale = clamp(scale, scaleMin, Math.max(scaleMin, scaleMax));\n\n // step: proportion wins. Snap via the scale (driven off the width edge) and\n // re-clamp, so ratio and bounds always hold; the grid yields when honoring\n // it would cost the ratio\n if (policy.step) {\n const snappedScale = snap(start.w * scale, policy.step) / start.w;\n scale = clamp(snappedScale, scaleMin, Math.max(scaleMin, scaleMax));\n }\n\n w = start.w * scale;\n h = start.h * scale;\n } else {\n w = clamp(snap(start.w + delta.dx, policy.step), wMin, wMax);\n h = clamp(snap(start.h + delta.dy, policy.step), hMin, hMax);\n }\n\n return { w: Math.round(w), h: Math.round(h) };\n};\n\n/**\n * Translate a resize key into the next size, reusing `applyResize` so the\n * keyboard clamps exactly like the pointer. Arrows nudge by the step (Shift ×4);\n * Home/End jump to the min/max of the allowed range. Returns null for any key\n * the handle does not own.\n */\nexport const keyboardResize = (\n key: string,\n size: Size,\n policy: Policy,\n contentMin: ContentMin,\n opts: { shiftKey?: boolean } = {}\n): Size | null => {\n const base =\n policy.step && policy.step > 0 ? policy.step : DEFAULT_KEYBOARD_STEP;\n const step = base * (opts.shiftKey ? SHIFT_STEP_MULTIPLIER : 1);\n const start = makeStart(size, contentMin);\n const b = effectiveBounds(policy, contentMin);\n\n switch (key) {\n case \"ArrowRight\":\n return applyResize(start, { dx: step, dy: 0 }, policy);\n case \"ArrowLeft\":\n return applyResize(start, { dx: -step, dy: 0 }, policy);\n case \"ArrowDown\":\n return applyResize(start, { dx: 0, dy: step }, policy);\n case \"ArrowUp\":\n return applyResize(start, { dx: 0, dy: -step }, policy);\n case \"Home\":\n // jump to the min corner (a huge negative delta clamps to the floor)\n return applyResize(\n start,\n { dx: b.wMin - size.w, dy: b.hMin - size.h },\n policy\n );\n case \"End\":\n // jump to the max corner; an unbounded axis simply doesn't move\n return applyResize(\n start,\n {\n dx: Number.isFinite(b.wMax) ? b.wMax - size.w : 0,\n dy: Number.isFinite(b.hMax) ? b.hMax - size.h : 0,\n },\n policy\n );\n default:\n return null;\n }\n};\n\n/**\n * The handle's `aria-valuenow`: the current size as a percent (0–100) of its\n * allowed range. The corner resizes both axes, but the value must be a single\n * number, so it reports the WIDTH axis — the one axis both the fixed-box and the\n * width-only cards share. Returns 0 when the range isn't finite (no max bound).\n */\nexport const measurePercent = (\n size: Size,\n policy: Policy,\n contentMin: ContentMin\n): number => {\n const { wMin, wMax } = effectiveBounds(policy, contentMin);\n if (!Number.isFinite(wMax) || wMax <= wMin) return 0;\n return Math.round(clamp(((size.w - wMin) / (wMax - wMin)) * 100, 0, 100));\n};\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAOA;;AAiBA;;AAmBA;AACA,OAAO,MAAMA,qBAAqB,GAAG,CAAC;;AAEtC;AACA,OAAO,MAAMC,qBAAqB,GAAG,CAAC;;AAEtC;AACA,OAAO,MAAMC,WAAkC,GAAG,CAChD,WAAW,EACX,YAAY,EACZ,SAAS,EACT,WAAW,EACX,MAAM,EACN,KAAK,CACN;AAED,OAAO,MAAMC,WAAW,GAAIC,GAAW,IAAcF,WAAW,CAACG,QAAQ,CAACD,GAAG,CAAC;AAE9E,OAAO,MAAME,KAAK,GAAGA,CAACC,CAAS,EAAEC,EAAU,EAAEC,EAAW,KACtDC,IAAI,CAACC,GAAG,CAACF,EAAE,WAAFA,EAAE,GAAIG,MAAM,CAACC,iBAAiB,EAAEH,IAAI,CAACI,GAAG,CAACN,EAAE,EAAED,CAAC,CAAC,CAAC;AAE3D,OAAO,MAAMQ,IAAI,GAAGA,CAACR,CAAS,EAAES,IAAa,KAC3CA,IAAI,IAAIA,IAAI,GAAG,CAAC,GAAGN,IAAI,CAACO,KAAK,CAACV,CAAC,GAAGS,IAAI,CAAC,GAAGA,IAAI,GAAGT,CAAC;AAEpD,OAAO,MAAMW,SAAS,GAAGA,CAACC,IAAU,EAAEC,UAAsB,MAAmB;EAC7EC,CAAC,EAAEF,IAAI,CAACE,CAAC;EACTC,CAAC,EAAEH,IAAI,CAACG,CAAC;EACTF;AACF,CAAC,CAAC;;AAEF;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMG,eAAe,GAAGA,CAC7BC,MAAc,EACdJ,UAAsB;EAAA,IAAAK,YAAA,EAAAC,YAAA,EAAAC,YAAA,EAAAC,YAAA;EAAA,OACD;IACrBC,IAAI,EAAEnB,IAAI,CAACI,GAAG,EAAAW,YAAA,GAACD,MAAM,CAACM,IAAI,YAAAL,YAAA,GAAI,CAAC,EAAEL,UAAU,CAACC,CAAC,CAAC;IAC9CU,IAAI,EAAErB,IAAI,CAACI,GAAG,EAAAY,YAAA,GAACF,MAAM,CAACQ,IAAI,YAAAN,YAAA,GAAI,CAAC,EAAEN,UAAU,CAACE,CAAC,CAAC;IAC9CW,IAAI,GAAAN,YAAA,GAAEH,MAAM,CAACU,IAAI,YAAAP,YAAA,GAAIf,MAAM,CAACC,iBAAiB;IAC7CsB,IAAI,GAAAP,YAAA,GAAEJ,MAAM,CAACY,IAAI,YAAAR,YAAA,GAAIhB,MAAM,CAACC;EAC9B,CAAC;AAAA,CAAC;;AAEF;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMwB,WAAW,GAAG,SAAAA,CACzBC,KAAkB,EAClBC,KAAkB,EAClBf,MAAc,EACdgB,IAA6B,EACpB;EAAA,IADTA,IAA6B;IAA7BA,IAA6B,GAAG,CAAC,CAAC;EAAA;EAElC,MAAAC,gBAAA,GAAmClB,eAAe,CAACC,MAAM,EAAEc,KAAK,CAAClB,UAAU,CAAC;IAApES,IAAI,GAAAY,gBAAA,CAAJZ,IAAI;IAAEE,IAAI,GAAAU,gBAAA,CAAJV,IAAI;IAAEE,IAAI,GAAAQ,gBAAA,CAAJR,IAAI;IAAEE,IAAI,GAAAM,gBAAA,CAAJN,IAAI;EAC9B,MAAMO,YAAY,GAAGlB,MAAM,CAACkB,YAAY,IAAIF,IAAI,CAACG,SAAS,KAAK,IAAI;EAEnE,IAAItB,CAAS;EACb,IAAIC,CAAS;EAEb,IAAIoB,YAAY,EAAE;IAChB;IACA;IACA,MAAME,EAAE,GAAG,CAACN,KAAK,CAACjB,CAAC,GAAGkB,KAAK,CAACM,EAAE,IAAIP,KAAK,CAACjB,CAAC;IACzC,MAAMyB,EAAE,GAAG,CAACR,KAAK,CAAChB,CAAC,GAAGiB,KAAK,CAACQ,EAAE,IAAIT,KAAK,CAAChB,CAAC;IACzC,IAAI0B,KAAK,GAAGtC,IAAI,CAACuC,GAAG,CAACL,EAAE,GAAG,CAAC,CAAC,IAAIlC,IAAI,CAACuC,GAAG,CAACH,EAAE,GAAG,CAAC,CAAC,GAAGF,EAAE,GAAGE,EAAE;;IAE1D;IACA;IACA,MAAMI,QAAQ,GAAGxC,IAAI,CAACI,GAAG,CAACe,IAAI,GAAGS,KAAK,CAACjB,CAAC,EAAEU,IAAI,GAAGO,KAAK,CAAChB,CAAC,CAAC;IACzD,MAAM6B,QAAQ,GAAGzC,IAAI,CAACC,GAAG,CAACsB,IAAI,GAAGK,KAAK,CAACjB,CAAC,EAAEc,IAAI,GAAGG,KAAK,CAAChB,CAAC,CAAC;IACzD0B,KAAK,GAAG1C,KAAK,CAAC0C,KAAK,EAAEE,QAAQ,EAAExC,IAAI,CAACI,GAAG,CAACoC,QAAQ,EAAEC,QAAQ,CAAC,CAAC;;IAE5D;IACA;IACA;IACA,IAAI3B,MAAM,CAACR,IAAI,EAAE;MACf,MAAMoC,YAAY,GAAGrC,IAAI,CAACuB,KAAK,CAACjB,CAAC,GAAG2B,KAAK,EAAExB,MAAM,CAACR,IAAI,CAAC,GAAGsB,KAAK,CAACjB,CAAC;MACjE2B,KAAK,GAAG1C,KAAK,CAAC8C,YAAY,EAAEF,QAAQ,EAAExC,IAAI,CAACI,GAAG,CAACoC,QAAQ,EAAEC,QAAQ,CAAC,CAAC;IACrE;IAEA9B,CAAC,GAAGiB,KAAK,CAACjB,CAAC,GAAG2B,KAAK;IACnB1B,CAAC,GAAGgB,KAAK,CAAChB,CAAC,GAAG0B,KAAK;EACrB,CAAC,MAAM;IACL3B,CAAC,GAAGf,KAAK,CAACS,IAAI,CAACuB,KAAK,CAACjB,CAAC,GAAGkB,KAAK,CAACM,EAAE,EAAErB,MAAM,CAACR,IAAI,CAAC,EAAEa,IAAI,EAAEI,IAAI,CAAC;IAC5DX,CAAC,GAAGhB,KAAK,CAACS,IAAI,CAACuB,KAAK,CAAChB,CAAC,GAAGiB,KAAK,CAACQ,EAAE,EAAEvB,MAAM,CAACR,IAAI,CAAC,EAAEe,IAAI,EAAEI,IAAI,CAAC;EAC9D;EAEA,OAAO;IAAEd,CAAC,EAAEX,IAAI,CAACO,KAAK,CAACI,CAAC,CAAC;IAAEC,CAAC,EAAEZ,IAAI,CAACO,KAAK,CAACK,CAAC;EAAE,CAAC;AAC/C,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAM+B,cAAc,GAAG,SAAAA,CAC5BjD,GAAW,EACXe,IAAU,EACVK,MAAc,EACdJ,UAAsB,EACtBoB,IAA4B,EACZ;EAAA,IADhBA,IAA4B;IAA5BA,IAA4B,GAAG,CAAC,CAAC;EAAA;EAEjC,MAAMc,IAAI,GACR9B,MAAM,CAACR,IAAI,IAAIQ,MAAM,CAACR,IAAI,GAAG,CAAC,GAAGQ,MAAM,CAACR,IAAI,GAAGhB,qBAAqB;EACtE,MAAMgB,IAAI,GAAGsC,IAAI,IAAId,IAAI,CAACe,QAAQ,GAAGtD,qBAAqB,GAAG,CAAC,CAAC;EAC/D,MAAMqC,KAAK,GAAGpB,SAAS,CAACC,IAAI,EAAEC,UAAU,CAAC;EACzC,MAAMoC,CAAC,GAAGjC,eAAe,CAACC,MAAM,EAAEJ,UAAU,CAAC;EAE7C,QAAQhB,GAAG;IACT,KAAK,YAAY;MACf,OAAOiC,WAAW,CAACC,KAAK,EAAE;QAAEO,EAAE,EAAE7B,IAAI;QAAE+B,EAAE,EAAE;MAAE,CAAC,EAAEvB,MAAM,CAAC;IACxD,KAAK,WAAW;MACd,OAAOa,WAAW,CAACC,KAAK,EAAE;QAAEO,EAAE,EAAE,CAAC7B,IAAI;QAAE+B,EAAE,EAAE;MAAE,CAAC,EAAEvB,MAAM,CAAC;IACzD,KAAK,WAAW;MACd,OAAOa,WAAW,CAACC,KAAK,EAAE;QAAEO,EAAE,EAAE,CAAC;QAAEE,EAAE,EAAE/B;MAAK,CAAC,EAAEQ,MAAM,CAAC;IACxD,KAAK,SAAS;MACZ,OAAOa,WAAW,CAACC,KAAK,EAAE;QAAEO,EAAE,EAAE,CAAC;QAAEE,EAAE,EAAE,CAAC/B;MAAK,CAAC,EAAEQ,MAAM,CAAC;IACzD,KAAK,MAAM;MACT;MACA,OAAOa,WAAW,CAChBC,KAAK,EACL;QAAEO,EAAE,EAAEW,CAAC,CAAC3B,IAAI,GAAGV,IAAI,CAACE,CAAC;QAAE0B,EAAE,EAAES,CAAC,CAACzB,IAAI,GAAGZ,IAAI,CAACG;MAAE,CAAC,EAC5CE,MACF,CAAC;IACH,KAAK,KAAK;MACR;MACA,OAAOa,WAAW,CAChBC,KAAK,EACL;QACEO,EAAE,EAAEjC,MAAM,CAAC6C,QAAQ,CAACD,CAAC,CAACvB,IAAI,CAAC,GAAGuB,CAAC,CAACvB,IAAI,GAAGd,IAAI,CAACE,CAAC,GAAG,CAAC;QACjD0B,EAAE,EAAEnC,MAAM,CAAC6C,QAAQ,CAACD,CAAC,CAACrB,IAAI,CAAC,GAAGqB,CAAC,CAACrB,IAAI,GAAGhB,IAAI,CAACG,CAAC,GAAG;MAClD,CAAC,EACDE,MACF,CAAC;IACH;MACE,OAAO,IAAI;EACf;AACF,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMkC,cAAc,GAAGA,CAC5BvC,IAAU,EACVK,MAAc,EACdJ,UAAsB,KACX;EACX,MAAAuC,iBAAA,GAAuBpC,eAAe,CAACC,MAAM,EAAEJ,UAAU,CAAC;IAAlDS,IAAI,GAAA8B,iBAAA,CAAJ9B,IAAI;IAAEI,IAAI,GAAA0B,iBAAA,CAAJ1B,IAAI;EAClB,IAAI,CAACrB,MAAM,CAAC6C,QAAQ,CAACxB,IAAI,CAAC,IAAIA,IAAI,IAAIJ,IAAI,EAAE,OAAO,CAAC;EACpD,OAAOnB,IAAI,CAACO,KAAK,CAACX,KAAK,CAAE,CAACa,IAAI,CAACE,CAAC,GAAGQ,IAAI,KAAKI,IAAI,GAAGJ,IAAI,CAAC,GAAI,GAAG,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;AAC3E,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React, { ReactElement, ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* StackedCard shell (story-local, presentation-only).
|
|
4
|
+
*
|
|
5
|
+
* The hollow frame every typed content component composes. Extracted from the
|
|
6
|
+
* concept spec story (task 2/8) so the StackedCard concept stories share one
|
|
7
|
+
* shell instead of re-declaring it. The rule the whole subsystem hangs on:
|
|
8
|
+
*
|
|
9
|
+
* THE SHELL STAYS HOLLOW.
|
|
10
|
+
*
|
|
11
|
+
* There is no `contentType` enum here, no per-type prop soup, no `if task …`.
|
|
12
|
+
* The shell owns only VISUAL STATES (selected, disabled, pending, priority,
|
|
13
|
+
* drag source/preview, entered) and the intent surface (`onResizeDelta`, the
|
|
14
|
+
* DOM props it forwards). Content — a task, a link, an image, a swatch, a note
|
|
15
|
+
* — is passed in as `children` and composes the layout primitives. A new
|
|
16
|
+
* content type later is a new wrapper, with ZERO changes in this file.
|
|
17
|
+
*
|
|
18
|
+
* Not exported from the design-system barrel; it lives here so the concept
|
|
19
|
+
* stories share one source of truth.
|
|
20
|
+
*/
|
|
21
|
+
export declare const CARD_WIDTH = 260;
|
|
22
|
+
export declare const CARD_RADIUS = 8;
|
|
23
|
+
export declare const COVER_RATIO = "260 / 132";
|
|
24
|
+
export declare const COVER_RATIO_HERO = "320 / 190";
|
|
25
|
+
export declare const COVER_RATIO_IMAGE = "260 / 168";
|
|
26
|
+
export declare const COVER_RATIO_SWATCH = "260 / 104";
|
|
27
|
+
export declare const ResizeHandleGlyph: () => ReactElement;
|
|
28
|
+
export interface StackedCardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onResize"> {
|
|
29
|
+
selected?: boolean;
|
|
30
|
+
disabled?: boolean;
|
|
31
|
+
/** Optimistic in-flight state — dimmed, non-interactive, not draggable. */
|
|
32
|
+
pending?: boolean;
|
|
33
|
+
/** Full-size outline placeholder left in the source position while dragging. */
|
|
34
|
+
isDragSource?: boolean;
|
|
35
|
+
/** The 1:1 elevated copy the host floats in the drag layer. */
|
|
36
|
+
isDragPreview?: boolean;
|
|
37
|
+
/** Focus model: the card has been entered and focus is on an inner control. */
|
|
38
|
+
entered?: boolean;
|
|
39
|
+
forceHover?: boolean;
|
|
40
|
+
resizable?: boolean;
|
|
41
|
+
priorityColor?: string;
|
|
42
|
+
onResizeDelta?: (dx: number, dy: number) => void;
|
|
43
|
+
children: ReactNode;
|
|
44
|
+
}
|
|
45
|
+
export declare const StackedCard: React.ForwardRefExoticComponent<StackedCardProps & React.RefAttributes<HTMLDivElement>>;
|
|
46
|
+
//# sourceMappingURL=shell.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../../../src/presentation/stackedCard/shell.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,YAAY,EAAE,SAAS,EAAuB,MAAM,OAAO,CAAC;AAI5E;;;;;;;;;;;;;;;;;;GAkBG;AAMH,eAAO,MAAM,UAAU,MAAM,CAAC;AAC9B,eAAO,MAAM,WAAW,IAAI,CAAC;AAK7B,eAAO,MAAM,WAAW,cAAc,CAAC;AACvC,eAAO,MAAM,gBAAgB,cAAc,CAAC;AAC5C,eAAO,MAAM,iBAAiB,cAAc,CAAC;AAC7C,eAAO,MAAM,kBAAkB,cAAc,CAAC;AA2K9C,eAAO,MAAM,iBAAiB,QAAO,YAepC,CAAC;AAEF,MAAM,WAAW,gBACf,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC;IAC9D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gFAAgF;IAChF,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,+DAA+D;IAC/D,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,QAAQ,EAAE,SAAS,CAAC;CACrB;AAKD,eAAO,MAAM,WAAW,yFAqEvB,CAAC"}
|