@estiva-app/ui 0.71.0 → 0.72.0-widgets2.1

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.
@@ -0,0 +1,584 @@
1
+ // src/widgets-2/proposed.tsx
2
+ import { IconCircleCheck, IconCircleDashed, IconCircleHalf2, IconCircleX, IconFileText, IconMessage2, IconPaperclip } from "@tabler/icons-react";
3
+ import { Avatar } from "@estiva-app/ui";
4
+ import { AvatarGroup } from "@estiva-app/ui";
5
+ import { Card } from "@estiva-app/ui";
6
+ import { cn } from "@estiva-app/ui";
7
+ import { InlineChip } from "@estiva-app/ui";
8
+ import { KindIcon } from "@estiva-app/ui";
9
+ import { Link } from "@estiva-app/ui";
10
+ import { MenuPanel } from "@estiva-app/ui";
11
+ import { PreviewCard } from "@estiva-app/ui";
12
+ import { Select } from "@estiva-app/ui";
13
+ import { SkeletonBar } from "@estiva-app/ui";
14
+ import { UnreadDot } from "@estiva-app/ui";
15
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
16
+ var HREF = "#";
17
+ var noop = () => {
18
+ };
19
+ var STATUS_ICON = {
20
+ neutral: { Icon: IconCircleDashed, tone: "text-text-secondary" },
21
+ blue: { Icon: IconCircleHalf2, tone: "text-info-default" },
22
+ green: { Icon: IconCircleCheck, tone: "text-success-default" },
23
+ muted: { Icon: IconCircleX, tone: "text-text-muted" }
24
+ };
25
+ function StatusMark({ status }) {
26
+ return /* @__PURE__ */ jsxs("span", { className: "inline-flex shrink-0 items-center", title: status.label, children: [
27
+ /* @__PURE__ */ jsx(StatusIcon, { status }),
28
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: status.label })
29
+ ] });
30
+ }
31
+ function StatusIcon({ status }) {
32
+ const { Icon, tone } = STATUS_ICON[status.colour];
33
+ return /* @__PURE__ */ jsx(Icon, { size: 14, stroke: 1.5, className: cn("shrink-0", tone), "aria-hidden": true });
34
+ }
35
+ function ThingIcon({ kind, size = 16, tone = "secondary" }) {
36
+ if (kind === "page") return /* @__PURE__ */ jsx(IconFileText, { size, stroke: 1.5, className: cn("shrink-0", tone === "secondary" && "text-text-secondary"), "aria-hidden": true });
37
+ return /* @__PURE__ */ jsx(KindIcon, { kind, size, tone });
38
+ }
39
+ function Key({ children }) {
40
+ return /* @__PURE__ */ jsx("span", { className: "shrink-0 text-caption tabular-nums text-text-muted", children });
41
+ }
42
+ function ProgressRing({ done, total }) {
43
+ const size = 16;
44
+ const stroke = 2;
45
+ const r = (size - stroke) / 2;
46
+ const c = 2 * Math.PI * r;
47
+ return /* @__PURE__ */ jsxs("span", { className: "inline-flex shrink-0 items-center gap-1 text-caption tabular-nums text-text-secondary", title: `${done} of ${total} done`, children: [
48
+ /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, className: "shrink-0 -rotate-90", "aria-hidden": true, children: [
49
+ /* @__PURE__ */ jsx("circle", { cx: size / 2, cy: size / 2, r, fill: "none", strokeWidth: stroke, className: "stroke-border-default" }),
50
+ /* @__PURE__ */ jsx("circle", { cx: size / 2, cy: size / 2, r, fill: "none", strokeWidth: stroke, strokeLinecap: "round", strokeDasharray: `${c * done / total} ${c}`, className: "stroke-success-default" })
51
+ ] }),
52
+ done,
53
+ "/",
54
+ total
55
+ ] });
56
+ }
57
+ var CONVERSATION_HREF = "#conversation";
58
+ function Comments({ count, unread = false, noun = "comments" }) {
59
+ return /* @__PURE__ */ jsxs(
60
+ Link,
61
+ {
62
+ href: CONVERSATION_HREF,
63
+ variant: "plain",
64
+ className: cn(
65
+ "relative z-10 -mx-1 inline-flex shrink-0 items-center gap-1 rounded-sm px-1 text-caption tabular-nums hover:bg-bg-active",
66
+ unread ? "font-medium text-text-primary" : "text-text-secondary"
67
+ ),
68
+ children: [
69
+ /* @__PURE__ */ jsx(IconMessage2, { size: 14, stroke: 1.5, "aria-hidden": true }),
70
+ count,
71
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: unread ? `${noun}, unread \u2014 open the conversation` : `${noun} \u2014 open the conversation` }),
72
+ unread && /* @__PURE__ */ jsx(UnreadDot, { className: "h-auto w-auto" })
73
+ ]
74
+ }
75
+ );
76
+ }
77
+ function Conversations({ thing }) {
78
+ return /* @__PURE__ */ jsxs("span", { className: "inline-flex shrink-0 items-center gap-2", children: [
79
+ /* @__PURE__ */ jsx("span", { className: "text-caption text-text-secondary", children: thing.updated }),
80
+ /* @__PURE__ */ jsx(Comments, { count: thing.conversations ?? 0, unread: thing.unread, noun: "conversations" })
81
+ ] });
82
+ }
83
+ var people = (names = []) => names.map((name) => ({ name }));
84
+ function Pickers({ thing, readOnly = false }) {
85
+ if (!thing.status) return null;
86
+ if (readOnly) {
87
+ return /* @__PURE__ */ jsxs("span", { className: "flex h-6 min-w-0 items-center gap-3 text-caption text-text-secondary", children: [
88
+ /* @__PURE__ */ jsxs("span", { className: "inline-flex shrink-0 items-center gap-1", children: [
89
+ /* @__PURE__ */ jsx(StatusIcon, { status: thing.status }),
90
+ thing.status.label
91
+ ] }),
92
+ /* @__PURE__ */ jsxs("span", { className: "inline-flex min-w-0 items-center gap-1", children: [
93
+ /* @__PURE__ */ jsx(Avatar, { name: thing.assignee, size: 16 }),
94
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: thing.assignee ?? "Unassigned" })
95
+ ] })
96
+ ] });
97
+ }
98
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
99
+ /* @__PURE__ */ jsx(Select, { size: "small", className: "w-auto", value: "s", onChange: noop, ariaLabel: "Status", options: [{ value: "s", label: thing.status.label, leading: /* @__PURE__ */ jsx(StatusIcon, { status: thing.status }) }] }),
100
+ /* @__PURE__ */ jsx(
101
+ Select,
102
+ {
103
+ size: "small",
104
+ className: "w-auto",
105
+ value: "a",
106
+ onChange: noop,
107
+ ariaLabel: thing.kind === "project" ? "Lead" : "Assignee",
108
+ options: [{ value: "a", label: thing.assignee ?? "Unassigned", leading: /* @__PURE__ */ jsx(Avatar, { name: thing.assignee, size: 16 }) }]
109
+ }
110
+ )
111
+ ] });
112
+ }
113
+ function Lead({ thing, chip = false }) {
114
+ if (thing.kind === "message") return /* @__PURE__ */ jsx(Avatar, { name: thing.author, size: 16 });
115
+ return /* @__PURE__ */ jsx(ThingIcon, { kind: thing.kind, size: chip ? 14 : 16, tone: chip ? "inherit" : "secondary" });
116
+ }
117
+ function ReferenceChip({ thing, state = "ready", preview = false }) {
118
+ if (state === "loading") {
119
+ return /* @__PURE__ */ jsx(InlineChip, { icon: /* @__PURE__ */ jsx(ThingIcon, { kind: thing.kind, size: 14, tone: "inherit" }), children: /* @__PURE__ */ jsx(SkeletonBar, { className: "h-2.5 w-16" }) });
120
+ }
121
+ if (state === "unavailable") {
122
+ return /* @__PURE__ */ jsxs(InlineChip, { icon: /* @__PURE__ */ jsx(ThingIcon, { kind: thing.kind, size: 14, tone: "inherit" }), className: "text-text-muted", children: [
123
+ thing.noun,
124
+ ", not available"
125
+ ] });
126
+ }
127
+ const chip = /* @__PURE__ */ jsxs(InlineChip, { icon: /* @__PURE__ */ jsx(Lead, { thing, chip: true }), href: HREF, className: "max-w-[28ch]", children: [
128
+ thing.key && /* @__PURE__ */ jsx(Key, { children: thing.key }),
129
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: thing.kind === "message" ? `${thing.author}: ${thing.body}` : thing.title }),
130
+ thing.status && /* @__PURE__ */ jsx(StatusMark, { status: thing.status })
131
+ ] });
132
+ return preview ? /* @__PURE__ */ jsx(HoverPreview, { thing, children: chip }) : chip;
133
+ }
134
+ function CompactCard({ thing, preview = false }) {
135
+ const card = /* @__PURE__ */ jsxs(Card, { fill: "inset", hover: "hairline", className: "relative flex h-9 items-center gap-2 px-3", children: [
136
+ /* @__PURE__ */ jsx(Lead, { thing }),
137
+ thing.kind === "message" ? /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-1 items-baseline gap-2 text-body-2", children: [
138
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-body-2-strong text-text-primary", children: thing.author }),
139
+ /* @__PURE__ */ jsx(Link, { href: HREF, variant: "quiet", cover: true, truncate: true, className: "text-text-secondary", children: thing.body })
140
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
141
+ thing.key && /* @__PURE__ */ jsx(Key, { children: thing.key }),
142
+ /* @__PURE__ */ jsx("span", { className: "contents text-body-2-strong text-text-primary", children: /* @__PURE__ */ jsx(Link, { href: HREF, variant: "quiet", cover: true, truncate: true, className: "min-w-0 flex-1", children: thing.title }) })
143
+ ] }),
144
+ thing.kind === "topic" && /* @__PURE__ */ jsx(Conversations, { thing }),
145
+ thing.kind === "page" && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-caption text-text-secondary", children: thing.updated }),
146
+ thing.kind === "message" && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-caption text-text-muted", children: thing.updated }),
147
+ thing.unread && thing.comments !== void 0 && /* @__PURE__ */ jsx(Comments, { count: thing.comments, unread: true }),
148
+ thing.status && thing.kind === "issue" && /* @__PURE__ */ jsx(StatusMark, { status: thing.status }),
149
+ thing.done && /* @__PURE__ */ jsx(ProgressRing, { done: thing.done.done, total: thing.done.total }),
150
+ thing.assignee && /* @__PURE__ */ jsx(Avatar, { name: thing.assignee, size: 16 }),
151
+ thing.members && /* @__PURE__ */ jsx(AvatarGroup, { members: people(thing.members), size: 16 }),
152
+ thing.authors && /* @__PURE__ */ jsx(AvatarGroup, { members: people(thing.authors), size: 16 })
153
+ ] });
154
+ return preview ? /* @__PURE__ */ jsx(HoverPreview, { thing, block: true, children: card }) : card;
155
+ }
156
+ function DetailedCard({ thing, bare = false, readOnly = bare }) {
157
+ const body = /* @__PURE__ */ jsxs(Fragment, { children: [
158
+ /* @__PURE__ */ jsxs("div", { className: "flex min-h-[19.6px] items-center gap-2", children: [
159
+ /* @__PURE__ */ jsxs(Link, { href: HREF, variant: "quiet", className: "flex min-w-0 flex-1 items-center gap-2 text-body-2-strong text-text-primary", children: [
160
+ /* @__PURE__ */ jsx(ThingIcon, { kind: thing.kind }),
161
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: thing.title })
162
+ ] }),
163
+ thing.done && /* @__PURE__ */ jsx(ProgressRing, { done: thing.done.done, total: thing.done.total }),
164
+ thing.members && /* @__PURE__ */ jsx(AvatarGroup, { members: people(thing.members), size: 16 })
165
+ ] }),
166
+ (thing.key || thing.subtitle) && /* @__PURE__ */ jsx("p", { className: "-mt-1 pl-6 text-caption text-text-secondary", children: [thing.key, thing.subtitle].filter(Boolean).join(" \xB7 ") }),
167
+ thing.summary && /* @__PURE__ */ jsx("p", { className: "line-clamp-2 text-body-2 text-text-secondary", children: thing.summary }),
168
+ thing.children && /* @__PURE__ */ jsx("div", { className: "flex flex-col divide-y divide-border-subtle rounded-md border border-border-subtle", children: thing.children.map((child) => /* @__PURE__ */ jsxs("div", { className: "relative flex h-8 items-center gap-2 px-2 hover:bg-bg-active", children: [
169
+ /* @__PURE__ */ jsx(StatusMark, { status: child.status }),
170
+ /* @__PURE__ */ jsx(Key, { children: child.key }),
171
+ /* @__PURE__ */ jsx("span", { className: "contents text-body-2 text-text-primary", children: /* @__PURE__ */ jsx(Link, { href: HREF, variant: "quiet", cover: true, truncate: true, className: "min-w-0 flex-1", children: child.title }) }),
172
+ child.assignee ? /* @__PURE__ */ jsx(Avatar, { name: child.assignee, size: 16 }) : /* @__PURE__ */ jsx("span", { className: "size-4 shrink-0", "aria-hidden": true })
173
+ ] }, child.key)) }),
174
+ thing.latest && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
175
+ /* @__PURE__ */ jsx("span", { className: "text-caption text-text-muted", children: "Latest conversation" }),
176
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2 rounded-md border border-border-subtle p-2", children: [
177
+ /* @__PURE__ */ jsx(Avatar, { name: thing.latest.author, size: 16 }),
178
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
179
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 text-caption", children: [
180
+ /* @__PURE__ */ jsx("span", { className: "text-text-primary", children: thing.latest.author }),
181
+ /* @__PURE__ */ jsx("span", { className: "text-text-muted", children: thing.latest.at })
182
+ ] }),
183
+ /* @__PURE__ */ jsx("p", { className: "line-clamp-2 text-body-2 text-text-secondary", children: thing.latest.body })
184
+ ] })
185
+ ] })
186
+ ] }),
187
+ /* @__PURE__ */ jsxs("div", { className: "flex min-h-6 items-center gap-2 border-t border-border-subtle pt-2", children: [
188
+ (thing.kind === "issue" || thing.kind === "project") && /* @__PURE__ */ jsx(Pickers, { thing, readOnly }),
189
+ thing.kind === "topic" && /* @__PURE__ */ jsx("span", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxs("span", { className: "text-caption text-text-secondary", children: [
190
+ "Updated ",
191
+ thing.updated
192
+ ] }) }),
193
+ thing.authors && /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-2 text-caption text-text-secondary", children: [
194
+ /* @__PURE__ */ jsx(AvatarGroup, { members: people(thing.authors), size: 16 }),
195
+ /* @__PURE__ */ jsx("span", { className: "shrink-0", children: thing.updated })
196
+ ] }),
197
+ thing.kind === "topic" && /* @__PURE__ */ jsx("span", { className: "ml-auto flex", children: /* @__PURE__ */ jsx(Comments, { count: thing.conversations ?? 0, unread: thing.unread, noun: "conversations" }) }),
198
+ thing.comments !== void 0 && /* @__PURE__ */ jsx("span", { className: "ml-auto flex", children: /* @__PURE__ */ jsx(Comments, { count: thing.comments, unread: thing.unread }) })
199
+ ] })
200
+ ] });
201
+ if (bare) return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 p-3", children: body });
202
+ return /* @__PURE__ */ jsx(Card, { fill: "inset", "data-card": "detailed", className: "flex flex-col gap-2 p-3", children: body });
203
+ }
204
+ function HoverPreview({ thing, block = false, children }) {
205
+ return /* @__PURE__ */ jsx(PreviewCard, { side: "bottom", content: /* @__PURE__ */ jsx(DetailedCard, { thing, bare: true }), wrapperClassName: block ? "grid w-full min-w-0 grid-cols-[minmax(0,1fr)]" : "max-w-full", children });
206
+ }
207
+ function HoverPreviewOpen({ thing }) {
208
+ return /* @__PURE__ */ jsx(MenuPanel, { className: "w-[360px] p-0", children: /* @__PURE__ */ jsx(DetailedCard, { thing, bare: true }) });
209
+ }
210
+ function MessageCard({ thing }) {
211
+ return /* @__PURE__ */ jsxs(Card, { fill: "inset", className: "flex flex-col gap-2 p-3", children: [
212
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
213
+ /* @__PURE__ */ jsx(Avatar, { name: thing.author, size: 24 }),
214
+ /* @__PURE__ */ jsx("span", { className: "text-body-2-strong text-text-primary", children: thing.author }),
215
+ /* @__PURE__ */ jsx(Link, { href: HREF, variant: "quiet", className: "text-caption text-text-muted", children: thing.updated })
216
+ ] }),
217
+ /* @__PURE__ */ jsx("p", { className: "line-clamp-4 text-body-2 text-text-primary", children: thing.body })
218
+ ] });
219
+ }
220
+ function loadingShapeOf(thing) {
221
+ return {
222
+ subtitle: !!(thing.subtitle || thing.key),
223
+ summary: !!thing.summary,
224
+ rows: thing.children?.length ?? 0,
225
+ preview: !!thing.latest,
226
+ ring: !!thing.done,
227
+ footer: thing.kind === "issue" || thing.kind === "project" ? "pickers" : thing.authors ? "people" : "line"
228
+ };
229
+ }
230
+ function Bar({ className }) {
231
+ return /* @__PURE__ */ jsx(SkeletonBar, { className: cn("bg-bg-active", className) });
232
+ }
233
+ function CardLoading({ kind, layout, shape = {} }) {
234
+ if (layout === "compact") {
235
+ return /* @__PURE__ */ jsxs(Card, { fill: "inset", "aria-hidden": true, "data-card": "loading", className: "flex h-9 items-center gap-2 px-3 animate-skeleton-in", children: [
236
+ /* @__PURE__ */ jsx(ThingIcon, { kind }),
237
+ /* @__PURE__ */ jsx(Bar, { className: "h-3 w-2/5" }),
238
+ /* @__PURE__ */ jsx(Bar, { className: "ml-auto size-4 rounded-full" })
239
+ ] });
240
+ }
241
+ const { subtitle = false, summary = true, rows = 0, preview = false, ring = false, footer = "line" } = shape;
242
+ return /* @__PURE__ */ jsxs(Card, { fill: "inset", "aria-hidden": true, "data-card": "loading", className: "flex flex-col gap-2 p-3 animate-skeleton-in", children: [
243
+ /* @__PURE__ */ jsxs("div", { className: "flex h-[19.6px] items-center gap-2", children: [
244
+ /* @__PURE__ */ jsx(ThingIcon, { kind }),
245
+ /* @__PURE__ */ jsx(Bar, { className: "h-3.5 w-1/2" }),
246
+ ring && /* @__PURE__ */ jsx(Bar, { className: "ml-auto size-4 rounded-full" })
247
+ ] }),
248
+ subtitle && /* @__PURE__ */ jsx("div", { className: "-mt-1 flex h-[14.4px] items-center pl-6", children: /* @__PURE__ */ jsx(Bar, { className: "h-2.5 w-20" }) }),
249
+ summary && /* @__PURE__ */ jsx("div", { className: "flex h-[19.6px] items-center", children: /* @__PURE__ */ jsx(Bar, { className: "h-3 w-4/5" }) }),
250
+ rows > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-col divide-y divide-border-subtle rounded-md border border-border-subtle", children: Array.from({ length: rows }, (_, i) => /* @__PURE__ */ jsx("div", { className: "flex h-8 items-center px-2", children: /* @__PURE__ */ jsx(Bar, { className: "h-3 w-1/3" }) }, i)) }),
251
+ preview && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
252
+ /* @__PURE__ */ jsx("div", { className: "flex h-[14.4px] items-center", children: /* @__PURE__ */ jsx(Bar, { className: "h-2.5 w-28" }) }),
253
+ /* @__PURE__ */ jsxs("div", { className: "flex h-[71.6px] flex-col justify-center gap-2 rounded-md border border-border-subtle p-2", children: [
254
+ /* @__PURE__ */ jsx(Bar, { className: "h-2.5 w-24" }),
255
+ /* @__PURE__ */ jsx(Bar, { className: "h-3 w-full" }),
256
+ /* @__PURE__ */ jsx(Bar, { className: "h-3 w-3/5" })
257
+ ] })
258
+ ] }),
259
+ footer !== "none" && /* @__PURE__ */ jsx("div", { className: "flex min-h-6 items-center gap-2 border-t border-border-subtle pt-2", children: footer === "pickers" ? /* @__PURE__ */ jsxs(Fragment, { children: [
260
+ /* @__PURE__ */ jsx(Bar, { className: "h-6 w-24 rounded-md" }),
261
+ /* @__PURE__ */ jsx(Bar, { className: "h-6 w-28 rounded-md" })
262
+ ] }) : footer === "people" ? /* @__PURE__ */ jsxs(Fragment, { children: [
263
+ /* @__PURE__ */ jsx(Bar, { className: "size-4 rounded-full" }),
264
+ /* @__PURE__ */ jsx(Bar, { className: "h-3 w-40" })
265
+ ] }) : /* @__PURE__ */ jsx(Bar, { className: "h-3 w-40" }) })
266
+ ] });
267
+ }
268
+ function CardUnavailable({ kind, noun, app }) {
269
+ return /* @__PURE__ */ jsxs(Card, { fill: "none", unreadable: true, className: "flex h-9 items-center gap-2 px-3", children: [
270
+ /* @__PURE__ */ jsx(ThingIcon, { kind }),
271
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1 truncate text-body-2 text-text-muted", children: [
272
+ noun,
273
+ " \u2014 not available."
274
+ ] }),
275
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-caption", children: /* @__PURE__ */ jsxs(Link, { href: HREF, variant: "quiet", external: true, className: "text-text-secondary", children: [
276
+ "Open in ",
277
+ app,
278
+ " \u2197"
279
+ ] }) })
280
+ ] });
281
+ }
282
+ function PartCard({ kind, heading, partKey, children, state = "ready", noun, app }) {
283
+ if (state === "loading") return /* @__PURE__ */ jsx(CardLoading, { kind, layout: "detailed", shape: { summary: true, rows: 0, footer: "none" } });
284
+ if (state === "unavailable") return /* @__PURE__ */ jsx(CardUnavailable, { kind, noun: `A part of this ${noun.toLowerCase()}`, app });
285
+ return /* @__PURE__ */ jsxs(Card, { fill: "inset", className: "flex flex-col gap-2 p-3", children: [
286
+ /* @__PURE__ */ jsxs(Link, { href: HREF, variant: "quiet", className: "flex min-w-0 items-center gap-2 text-body-2-strong text-text-primary", children: [
287
+ /* @__PURE__ */ jsx(ThingIcon, { kind }),
288
+ partKey && /* @__PURE__ */ jsx(Key, { children: partKey }),
289
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: heading })
290
+ ] }),
291
+ /* @__PURE__ */ jsx("div", { className: "border-l-2 border-border-default pl-3", children })
292
+ ] });
293
+ }
294
+ function CommentAnchor({ text }) {
295
+ if (text === void 0) return /* @__PURE__ */ jsx("div", { className: "border-l-2 border-border-default pl-2 text-caption text-text-muted", children: "The part this is about was deleted." });
296
+ return /* @__PURE__ */ jsx("div", { className: "border-l-2 border-border-strong pl-2 text-caption text-text-muted", children: /* @__PURE__ */ jsx("span", { className: "line-clamp-2 italic", children: text }) });
297
+ }
298
+ function AttachmentChip({ name, state = "ready" }) {
299
+ if (state === "loading") {
300
+ return /* @__PURE__ */ jsx(InlineChip, { icon: /* @__PURE__ */ jsx(IconPaperclip, { size: 14, stroke: 1.5, "aria-hidden": true }), children: /* @__PURE__ */ jsx(SkeletonBar, { className: "h-2.5 w-16" }) });
301
+ }
302
+ if (state === "unavailable") {
303
+ return /* @__PURE__ */ jsx(InlineChip, { icon: /* @__PURE__ */ jsx(IconPaperclip, { size: 14, stroke: 1.5, "aria-hidden": true }), className: "text-text-muted", children: "File, not available" });
304
+ }
305
+ return /* @__PURE__ */ jsx(InlineChip, { icon: /* @__PURE__ */ jsx(IconPaperclip, { size: 14, stroke: 1.5, "aria-hidden": true }), href: HREF, className: "max-w-[24ch]", children: /* @__PURE__ */ jsx("span", { className: "truncate", children: name }) });
306
+ }
307
+
308
+ // src/widgets-2/fixtures.ts
309
+ var TODO = { label: "Todo", colour: "neutral" };
310
+ var DOING = { label: "In progress", colour: "blue" };
311
+ var DONE = { label: "Done", colour: "green" };
312
+ var PARAGRAPH = "A description long enough to need more than one line, so the card has to choose how much of it to show and where to cut it short. The rest is there when you open it.";
313
+ var SUMMARY = "A one-sentence summary of what this is for.";
314
+ var ISSUE = {
315
+ kind: "issue",
316
+ noun: "Issue",
317
+ app: "Ship",
318
+ title: "Item one, with a title long enough to be cut short",
319
+ key: "ABC-2",
320
+ subtitle: "Group one",
321
+ body: PARAGRAPH,
322
+ summary: SUMMARY,
323
+ status: DOING,
324
+ assignee: "Person One",
325
+ comments: 4,
326
+ unread: true
327
+ };
328
+ var PROJECT = {
329
+ kind: "project",
330
+ noun: "Project",
331
+ app: "Ship",
332
+ title: "Group one",
333
+ body: PARAGRAPH,
334
+ summary: SUMMARY,
335
+ status: DOING,
336
+ assignee: "Person Two",
337
+ progress: "3 of 8 done",
338
+ done: { done: 3, total: 8 },
339
+ comments: 6,
340
+ children: [
341
+ { title: "Item one", key: "ABC-1", status: DONE, assignee: "Person One" },
342
+ { title: "Item two", key: "ABC-2", status: DOING, assignee: "Person Two" },
343
+ { title: "Item three", key: "ABC-3", status: TODO }
344
+ ]
345
+ };
346
+ var TOPIC = {
347
+ kind: "topic",
348
+ noun: "Topic",
349
+ app: "Peek",
350
+ title: "Place one",
351
+ conversations: 12,
352
+ unread: true,
353
+ members: ["Person One", "Person Two", "Person Three"],
354
+ updated: "2h ago",
355
+ latest: { author: "Person Three", body: "The latest conversation in the topic, as its first message reads. It may run to a second line before it is cut short.", at: "2h ago" }
356
+ };
357
+ var MESSAGE = {
358
+ kind: "message",
359
+ noun: "Message",
360
+ app: "Peek",
361
+ title: "Person Two",
362
+ author: "Person Two",
363
+ body: "A message of two sentences, written by someone in a conversation. It is the words the link points to.",
364
+ updated: "10:42"
365
+ };
366
+ var PAGE = {
367
+ kind: "page",
368
+ noun: "Page",
369
+ app: "Leaf",
370
+ title: "Page one",
371
+ subtitle: "Folder one",
372
+ body: PARAGRAPH,
373
+ summary: SUMMARY,
374
+ author: "Person One",
375
+ authors: ["Person One", "Person Two"],
376
+ comments: 2,
377
+ updated: "Edited yesterday"
378
+ };
379
+ var PART = {
380
+ heading: "Item one",
381
+ noun: "Issue",
382
+ app: "Ship",
383
+ blocks: ["Heading of the part", "A paragraph of the part, as it reads in the description right now.", "A point in a list", "Another point"]
384
+ };
385
+ var ATTACHMENT = { name: "picture-one.png", size: 182400, contentType: "image/png" };
386
+ var ATTACHMENT_FILE = { name: "document-one.pdf", size: 124e4, contentType: "application/pdf" };
387
+ var UNREADABLE = { kind: "issue", noun: "Issue", app: "Ship", address: "naddr1qqjxgdmzxy6r2vfs95cryvmp956x2etp94sns" };
388
+ var PICTURE = "data:image/svg+xml;utf8," + encodeURIComponent(
389
+ '<svg xmlns="http://www.w3.org/2000/svg" width="480" height="300"><rect width="480" height="300" fill="#8a94a6"/><circle cx="360" cy="90" r="40" fill="#d8dde6"/><path d="M0 300 L170 120 L300 260 L380 190 L480 300 Z" fill="#5d6778"/></svg>'
390
+ );
391
+
392
+ // src/widgets-2/places.tsx
393
+ import {
394
+ IconCheck,
395
+ IconExternalLink,
396
+ IconH1,
397
+ IconLetterT,
398
+ IconLink,
399
+ IconListCheck,
400
+ IconPaperclip as IconPaperclip2,
401
+ IconTable,
402
+ IconX
403
+ } from "@tabler/icons-react";
404
+ import { useState } from "react";
405
+ import { cn as cn2 } from "@estiva-app/ui";
406
+ import { IconButton } from "@estiva-app/ui";
407
+ import { MenuItem, MenuPanel as MenuPanel2, MenuSection, MenuSeparator } from "@estiva-app/ui";
408
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
409
+ var isLink = (piece) => typeof piece === "object" && !Array.isArray(piece);
410
+ function linksIn(lines) {
411
+ return [...new Set(lines.flatMap((line) => isLink(line) ? [line] : line.filter(isLink)))];
412
+ }
413
+ function cardFileOf(lines) {
414
+ const files = linksIn(lines);
415
+ return files.length === 1 ? files[0] : void 0;
416
+ }
417
+ function isOnlyOneLink(lines) {
418
+ return lines.length === 1 && isLink(lines[0]);
419
+ }
420
+ function CardOf({ thing }) {
421
+ if (thing.state === "loading") return /* @__PURE__ */ jsx2(CardLoading, { kind: thing.kind, layout: "detailed", shape: loadingShapeOf(thing) });
422
+ if (thing.state === "unavailable") return /* @__PURE__ */ jsx2(CardUnavailable, { kind: thing.kind, noun: thing.noun, app: thing.app });
423
+ return thing.kind === "message" ? /* @__PURE__ */ jsx2(MessageCard, { thing }) : /* @__PURE__ */ jsx2(DetailedCard, { thing });
424
+ }
425
+ function Row({ thing, preview = false }) {
426
+ if (thing.state === "loading") return /* @__PURE__ */ jsx2(CardLoading, { kind: thing.kind, layout: "compact" });
427
+ if (thing.state === "unavailable") return /* @__PURE__ */ jsx2(CardUnavailable, { kind: thing.kind, noun: thing.noun, app: thing.app });
428
+ return /* @__PURE__ */ jsx2(CompactCard, { thing, preview });
429
+ }
430
+ function Sentence({ pieces }) {
431
+ return /* @__PURE__ */ jsx2("p", { className: "text-body-2 text-text-primary", children: pieces.map((piece, i) => typeof piece === "string" ? /* @__PURE__ */ jsx2("span", { children: piece }, i) : /* @__PURE__ */ jsx2(ReferenceChip, { thing: piece, state: piece.state, preview: !piece.state }, i)) });
432
+ }
433
+ function PlacedLines({ lines, cards = [], onLink, under, className }) {
434
+ return /* @__PURE__ */ jsx2("div", { className: cn2("flex flex-col gap-2", className), children: lines.map((line, i) => /* @__PURE__ */ jsxs2("div", { className: "relative flex flex-col", children: [
435
+ isLink(line) ? onLink ? (
436
+ // While writing, a click on the link opens its menu instead of following it (the record clicking-a-chip-while-editing-opens-a-menu).
437
+ /* @__PURE__ */ jsx2(
438
+ "div",
439
+ {
440
+ role: "button",
441
+ tabIndex: 0,
442
+ "aria-haspopup": "menu",
443
+ className: "w-full min-w-0 cursor-pointer",
444
+ onClickCapture: (event) => {
445
+ event.preventDefault();
446
+ event.stopPropagation();
447
+ onLink(i);
448
+ },
449
+ onKeyDown: (event) => {
450
+ if (event.key === "Enter") onLink(i);
451
+ },
452
+ children: cards.includes(i) ? /* @__PURE__ */ jsx2(CardOf, { thing: line }) : /* @__PURE__ */ jsx2(Row, { thing: line })
453
+ }
454
+ )
455
+ ) : cards.includes(i) ? /* @__PURE__ */ jsx2(CardOf, { thing: line }) : /* @__PURE__ */ jsx2(Row, { thing: line, preview: true }) : /* @__PURE__ */ jsx2(Sentence, { pieces: line }),
456
+ under?.(i)
457
+ ] }, i)) });
458
+ }
459
+ function CardUnder({ thing, onRemove, removeOnHover = false, className }) {
460
+ return /* @__PURE__ */ jsxs2("div", { className: cn2("group/card flex items-start gap-1", className), children: [
461
+ /* @__PURE__ */ jsx2("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx2(CardOf, { thing }) }),
462
+ onRemove && // At the card's top corner, not its middle: a tall card's ✕ halfway down reads as belonging to nothing.
463
+ /* @__PURE__ */ jsx2("div", { className: "flex shrink-0 self-start pt-2", children: /* @__PURE__ */ jsx2(IconButton, { "aria-label": "Remove card", tooltip: "Remove card", onClick: onRemove, className: cn2(removeOnHover && "opacity-0 group-hover/card:opacity-100 focus-visible:opacity-100"), children: /* @__PURE__ */ jsx2(IconX, { size: 16 }) }) })
464
+ ] });
465
+ }
466
+ function PlacedMessage({ lines, removed = false, onRemove, removeOnHover, oldCompact, className }) {
467
+ const file = removed || oldCompact ? void 0 : cardFileOf(lines);
468
+ const shown = file ? lines.filter((line) => line !== file) : lines;
469
+ return /* @__PURE__ */ jsxs2("div", { className: cn2("flex flex-col gap-2", className), children: [
470
+ shown.length > 0 && /* @__PURE__ */ jsx2(PlacedLines, { lines: shown }),
471
+ file && /* @__PURE__ */ jsx2(CardUnder, { thing: file, onRemove, removeOnHover, className: "max-w-[480px]" }),
472
+ oldCompact?.map((thing, i) => /* @__PURE__ */ jsx2(CompactCard, { thing, preview: true }, i))
473
+ ] });
474
+ }
475
+ function SentMessage({ lines, author = true, className }) {
476
+ const [removed, setRemoved] = useState(false);
477
+ return /* @__PURE__ */ jsx2(PlacedMessage, { lines, removed, onRemove: author ? () => setRemoved(true) : void 0, removeOnHover: true, className });
478
+ }
479
+ function ChipMenu({ thing, alone = true, shownAs = "row", onShowAs, onOpen }) {
480
+ return /* @__PURE__ */ jsx2(MenuPanel2, { className: "w-[200px]", children: /* @__PURE__ */ jsxs2("div", { role: "menu", "aria-label": "Link", className: "flex flex-col gap-0.5", children: [
481
+ /* @__PURE__ */ jsx2(MenuItem, { role: "menuitem", label: "Open", leading: /* @__PURE__ */ jsx2(IconExternalLink, { size: 16 }), onClick: onOpen }),
482
+ alone && /* @__PURE__ */ jsxs2(Fragment2, { children: [
483
+ /* @__PURE__ */ jsx2(MenuSeparator, {}),
484
+ /* @__PURE__ */ jsx2("div", { role: "group", "aria-label": "Show as", children: /* @__PURE__ */ jsx2(MenuSection, { label: "Show as", children: ["row", "card"].map((as) => /* @__PURE__ */ jsx2(
485
+ MenuItem,
486
+ {
487
+ role: "menuitemradio",
488
+ "aria-checked": shownAs === as,
489
+ label: as === "row" ? "Row" : "Card",
490
+ trailing: shownAs === as ? /* @__PURE__ */ jsx2(IconCheck, { size: 16, "aria-hidden": true }) : void 0,
491
+ onClick: () => onShowAs?.(as)
492
+ },
493
+ as
494
+ )) }) })
495
+ ] })
496
+ ] }) });
497
+ }
498
+ function PlacedDocument({ lines, initialCards = [], className }) {
499
+ const [cards, setCards] = useState(initialCards);
500
+ const [open, setOpen] = useState(null);
501
+ return /* @__PURE__ */ jsx2(
502
+ PlacedLines,
503
+ {
504
+ lines,
505
+ cards,
506
+ className,
507
+ onLink: (i) => setOpen(open === i ? null : i),
508
+ under: (i) => {
509
+ const line = lines[i];
510
+ if (open !== i || !isLink(line)) return null;
511
+ return /* @__PURE__ */ jsx2("div", { className: "absolute left-0 top-full z-10 mt-1", children: /* @__PURE__ */ jsx2(
512
+ ChipMenu,
513
+ {
514
+ thing: line,
515
+ shownAs: cards.includes(i) ? "card" : "row",
516
+ onOpen: () => setOpen(null),
517
+ onShowAs: (as) => {
518
+ setCards(as === "card" ? [...cards, i] : cards.filter((c) => c !== i));
519
+ setOpen(null);
520
+ }
521
+ }
522
+ ) });
523
+ }
524
+ }
525
+ );
526
+ }
527
+ function SlashMenuWithLink({ file = true, table = true }) {
528
+ return /* @__PURE__ */ jsx2(MenuPanel2, { className: "w-[260px]", children: /* @__PURE__ */ jsxs2("div", { role: "menu", "aria-label": "Insert", className: "flex flex-col gap-0.5", children: [
529
+ /* @__PURE__ */ jsxs2(MenuSection, { label: "Format", children: [
530
+ /* @__PURE__ */ jsx2(MenuItem, { label: "Text", leading: /* @__PURE__ */ jsx2(IconLetterT, { size: 16 }) }),
531
+ /* @__PURE__ */ jsx2(MenuItem, { label: "Heading", leading: /* @__PURE__ */ jsx2(IconH1, { size: 16 }), shortcut: "#" }),
532
+ /* @__PURE__ */ jsx2(MenuItem, { label: "Checklist", leading: /* @__PURE__ */ jsx2(IconListCheck, { size: 16 }), shortcut: "[]" })
533
+ ] }),
534
+ /* @__PURE__ */ jsx2(MenuSeparator, {}),
535
+ /* @__PURE__ */ jsxs2(MenuSection, { label: "Insert", children: [
536
+ table && /* @__PURE__ */ jsx2(MenuItem, { label: "Table", leading: /* @__PURE__ */ jsx2(IconTable, { size: 16 }) }),
537
+ file && /* @__PURE__ */ jsx2(MenuItem, { label: "File", leading: /* @__PURE__ */ jsx2(IconPaperclip2, { size: 16 }) }),
538
+ /* @__PURE__ */ jsx2(MenuItem, { label: "Link to a file", leading: /* @__PURE__ */ jsx2(IconLink, { size: 16 }), shortcut: "[", selected: true })
539
+ ] })
540
+ ] }) });
541
+ }
542
+ function LinkPicker({ files, query = "" }) {
543
+ return /* @__PURE__ */ jsx2(MenuPanel2, { className: "w-[320px]", children: /* @__PURE__ */ jsx2("div", { role: "listbox", "aria-label": "Link to a file", className: "flex flex-col gap-0.5", children: /* @__PURE__ */ jsx2(MenuSection, { label: query ? `Files matching \u201C${query}\u201D` : "Recent files", children: files.map((thing, i) => /* @__PURE__ */ jsx2(MenuItem, { role: "option", label: thing.kind === "message" ? `${thing.author}: ${thing.body}` : thing.title, leading: /* @__PURE__ */ jsx2(ThingIcon, { kind: thing.kind }), hint: thing.key && /* @__PURE__ */ jsx2("span", { className: "text-caption text-text-secondary", children: thing.key }), selected: i === 0 }, i)) }) }) });
544
+ }
545
+ export {
546
+ ATTACHMENT,
547
+ ATTACHMENT_FILE,
548
+ AttachmentChip,
549
+ CardLoading,
550
+ CardOf,
551
+ CardUnavailable,
552
+ CardUnder,
553
+ ChipMenu,
554
+ CommentAnchor,
555
+ CompactCard,
556
+ DetailedCard,
557
+ HoverPreview,
558
+ HoverPreviewOpen,
559
+ ISSUE,
560
+ LinkPicker,
561
+ MESSAGE,
562
+ MessageCard,
563
+ PAGE,
564
+ PART,
565
+ PICTURE,
566
+ PROJECT,
567
+ PartCard,
568
+ PlacedDocument,
569
+ PlacedLines,
570
+ PlacedMessage,
571
+ ProgressRing,
572
+ ReferenceChip,
573
+ SentMessage,
574
+ SlashMenuWithLink,
575
+ StatusMark,
576
+ TOPIC,
577
+ ThingIcon,
578
+ UNREADABLE,
579
+ cardFileOf,
580
+ isOnlyOneLink,
581
+ linksIn,
582
+ loadingShapeOf
583
+ };
584
+ //# sourceMappingURL=widgets-2.js.map