@hjmds/react 1.46.1 → 1.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/actions.d.ts +2 -2
  2. package/dist/activity-chip.d.ts +1 -1
  3. package/dist/advanced-display.d.ts +6 -0
  4. package/dist/advanced-display.d.ts.map +1 -1
  5. package/dist/advanced-display.js +4 -2
  6. package/dist/advanced-display.js.map +1 -1
  7. package/dist/advanced-forms.d.ts +2 -2
  8. package/dist/auth-screen.d.ts +1 -1
  9. package/dist/bottom-navigation.d.ts.map +1 -1
  10. package/dist/bottom-navigation.js +1 -1
  11. package/dist/bottom-navigation.js.map +1 -1
  12. package/dist/calendar.d.ts.map +1 -1
  13. package/dist/calendar.js +31 -24
  14. package/dist/calendar.js.map +1 -1
  15. package/dist/carousel.d.ts +5 -1
  16. package/dist/carousel.d.ts.map +1 -1
  17. package/dist/carousel.js +2 -2
  18. package/dist/carousel.js.map +1 -1
  19. package/dist/clipboard.d.ts +2 -2
  20. package/dist/color-picker.d.ts +33 -3
  21. package/dist/color-picker.d.ts.map +1 -1
  22. package/dist/color-picker.js +73 -10
  23. package/dist/color-picker.js.map +1 -1
  24. package/dist/communication.d.ts +6 -1
  25. package/dist/communication.d.ts.map +1 -1
  26. package/dist/communication.js +2 -2
  27. package/dist/communication.js.map +1 -1
  28. package/dist/conversation-row.d.ts +7 -1
  29. package/dist/conversation-row.d.ts.map +1 -1
  30. package/dist/conversation-row.js +4 -2
  31. package/dist/conversation-row.js.map +1 -1
  32. package/dist/date-range.d.ts +24 -3
  33. package/dist/date-range.d.ts.map +1 -1
  34. package/dist/date-range.js +38 -17
  35. package/dist/date-range.js.map +1 -1
  36. package/dist/display.d.ts +28 -0
  37. package/dist/display.d.ts.map +1 -1
  38. package/dist/display.js +11 -9
  39. package/dist/display.js.map +1 -1
  40. package/dist/evidence.d.ts +1 -1
  41. package/dist/evidence.js +1 -1
  42. package/dist/evidence.js.map +1 -1
  43. package/dist/feedback.d.ts +1 -1
  44. package/dist/floating-action-button.d.ts +1 -1
  45. package/dist/follow-list.d.ts +58 -0
  46. package/dist/follow-list.d.ts.map +1 -0
  47. package/dist/follow-list.js +25 -0
  48. package/dist/follow-list.js.map +1 -0
  49. package/dist/forms.d.ts +3 -3
  50. package/dist/image-viewer.d.ts +1 -1
  51. package/dist/index.d.ts +1 -1
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js.map +1 -1
  54. package/dist/internal/calendar-paint.d.ts +12 -0
  55. package/dist/internal/calendar-paint.d.ts.map +1 -0
  56. package/dist/internal/calendar-paint.js +3 -0
  57. package/dist/internal/calendar-paint.js.map +1 -0
  58. package/dist/internal/identity-action.d.ts +12 -0
  59. package/dist/internal/identity-action.d.ts.map +1 -0
  60. package/dist/internal/identity-action.js +10 -0
  61. package/dist/internal/identity-action.js.map +1 -0
  62. package/dist/internal.d.ts +1 -1
  63. package/dist/layout.d.ts +1 -1
  64. package/dist/overlays.d.ts +4 -0
  65. package/dist/overlays.d.ts.map +1 -1
  66. package/dist/overlays.js +3 -3
  67. package/dist/overlays.js.map +1 -1
  68. package/dist/provider-button.d.ts +1 -1
  69. package/dist/public-exports.generated.d.ts +4 -0
  70. package/dist/public-exports.generated.d.ts.map +1 -1
  71. package/dist/public-exports.generated.js +12 -0
  72. package/dist/public-exports.generated.js.map +1 -1
  73. package/dist/screen-flows.d.ts +33 -4
  74. package/dist/screen-flows.d.ts.map +1 -1
  75. package/dist/screen-flows.js +25 -10
  76. package/dist/screen-flows.js.map +1 -1
  77. package/dist/screens.d.ts +38 -6
  78. package/dist/screens.d.ts.map +1 -1
  79. package/dist/screens.js +26 -12
  80. package/dist/screens.js.map +1 -1
  81. package/dist/selection.d.ts +1 -1
  82. package/dist/share-sheet.d.ts +65 -0
  83. package/dist/share-sheet.d.ts.map +1 -0
  84. package/dist/share-sheet.js +145 -0
  85. package/dist/share-sheet.js.map +1 -0
  86. package/dist/social-compositions.d.ts +10 -0
  87. package/dist/social-compositions.d.ts.map +1 -1
  88. package/dist/social-compositions.js +3 -2
  89. package/dist/social-compositions.js.map +1 -1
  90. package/dist/styles.css +122 -48
  91. package/dist/styles.layered.css +122 -48
  92. package/dist/table-family.js +1 -1
  93. package/dist/table-family.js.map +1 -1
  94. package/dist/toast.d.ts +1 -1
  95. package/dist/top-bar.d.ts +1 -1
  96. package/package.json +13 -3
@@ -0,0 +1,145 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState, useSyncExternalStore } from "react";
4
+ import { filterShareSheetRecipients, resolveShareSheetPickerValue, resolveShareSheetSendOutcome, shareSheetRecipe, validateShareSheet, validateShareSheetRecipients, } from "@hjmds/design-contracts/share-sheet";
5
+ import { Button, IconButton } from "./actions.js";
6
+ import { EmptyState, Notice } from "./feedback.js";
7
+ import { SearchField } from "./forms.js";
8
+ import { ImageChoiceGroup } from "./image-choice.js";
9
+ import { Spinner } from "./internal/spinner.js";
10
+ import { Stack, Text } from "./layout.js";
11
+ import { Sheet } from "./overlays.js";
12
+ import { Icon } from "./supplemental-display.js";
13
+ const subscribeNever = () => () => { };
14
+ const browserShareAvailable = () => typeof navigator !== "undefined" && typeof navigator.share === "function";
15
+ const serverShareAvailable = () => false;
16
+ function isAbort(error) {
17
+ return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
18
+ }
19
+ /**
20
+ * One Sheet for sharing a link (2026-10-26, generalized from BurnTok's share sheets): optional in-app recipients with a
21
+ * batch send, then copy link and the OS share sheet as two round actions in the footer.
22
+ */
23
+ export function ShareSheet({ open, onOpenChange, title, labels, link, shareTitle, shareText, copyLink, systemShare, onLinkCopied, onSystemShare, icons, recipients, notice, }) {
24
+ validateShareSheet({ title, link, labels });
25
+ if (recipients)
26
+ validateShareSheetRecipients(recipients.items);
27
+ // navigator.share exists only in some browsers and never on the server; read it after hydration.
28
+ const browserShare = useSyncExternalStore(subscribeNever, browserShareAvailable, serverShareAvailable);
29
+ const copy = copyLink === false ? null : copyLink ?? ((value) => navigator.clipboard.writeText(value));
30
+ const share = systemShare === false ? null : systemShare ?? (browserShare ? (content) => navigator.share(content) : null);
31
+ const [externalAction, setExternalAction] = useState(null);
32
+ const [feedback, setFeedback] = useState(null);
33
+ const [selected, setSelected] = useState([]);
34
+ const [sentHere, setSentHere] = useState(() => new Set());
35
+ const [sending, setSending] = useState(false);
36
+ const [sendError, setSendError] = useState("");
37
+ const [localQuery, setLocalQuery] = useState("");
38
+ // Refs close the gap before React re-renders the busy state, so a double tap cannot start a second request.
39
+ const externalInFlight = useRef(false);
40
+ const batchInFlight = useRef(false);
41
+ // Bumped on every close: results of requests started in an earlier visit must not report into the next one.
42
+ const visit = useRef(0);
43
+ const mounted = useRef(false);
44
+ useEffect(() => {
45
+ mounted.current = true;
46
+ return () => { mounted.current = false; visit.current += 1; };
47
+ }, []);
48
+ useEffect(() => {
49
+ if (open)
50
+ return;
51
+ visit.current += 1;
52
+ setFeedback(null);
53
+ setSelected([]);
54
+ setSentHere(new Set());
55
+ setSendError("");
56
+ setLocalQuery("");
57
+ }, [open]);
58
+ useEffect(() => {
59
+ // A different link makes a "copied" claim about the old one false.
60
+ setFeedback(null);
61
+ }, [link]);
62
+ async function runExternal(action) {
63
+ if (externalInFlight.current)
64
+ return;
65
+ const run = action === "copy" ? copy : share;
66
+ if (!run)
67
+ return;
68
+ externalInFlight.current = true;
69
+ const current = visit.current;
70
+ setExternalAction(action);
71
+ setFeedback(null);
72
+ try {
73
+ if (action === "copy") {
74
+ await copy(link);
75
+ if (current !== visit.current)
76
+ return;
77
+ setFeedback("copied");
78
+ onLinkCopied?.();
79
+ }
80
+ else {
81
+ await share({ url: link, ...(shareTitle ? { title: shareTitle } : {}), ...(shareText ? { text: shareText } : {}) });
82
+ // Resolving only means the OS sheet closed on a target; never announce delivery.
83
+ if (current === visit.current)
84
+ onSystemShare?.("completed");
85
+ }
86
+ }
87
+ catch (error) {
88
+ if (current !== visit.current)
89
+ return;
90
+ if (action === "share" && isAbort(error))
91
+ onSystemShare?.("dismissed");
92
+ else
93
+ setFeedback("failed");
94
+ }
95
+ finally {
96
+ externalInFlight.current = false;
97
+ if (mounted.current)
98
+ setExternalAction(null);
99
+ }
100
+ }
101
+ const sent = new Set([...(recipients?.sentIds ?? []), ...sentHere]);
102
+ const pending = selected.filter(id => !sent.has(id));
103
+ async function sendSelected() {
104
+ if (!recipients || batchInFlight.current || pending.length === 0)
105
+ return;
106
+ batchInFlight.current = true;
107
+ const current = visit.current, requested = pending;
108
+ setSending(true);
109
+ setSendError("");
110
+ try {
111
+ const result = await recipients.onSend(requested);
112
+ if (current !== visit.current)
113
+ return;
114
+ const outcome = resolveShareSheetSendOutcome(requested, result);
115
+ const delivered = new Set(outcome.sent);
116
+ setSentHere(previous => new Set([...previous, ...outcome.sent]));
117
+ // Failed recipients stay selected, so the next press retries only them.
118
+ setSelected(previous => previous.filter(id => !delivered.has(id)));
119
+ if (outcome.failed.length)
120
+ setSendError(result.error ?? recipients.labels.sendFailed);
121
+ }
122
+ catch {
123
+ if (current === visit.current)
124
+ setSendError(recipients.labels.sendFailed);
125
+ }
126
+ finally {
127
+ batchInFlight.current = false;
128
+ if (mounted.current)
129
+ setSending(false);
130
+ }
131
+ }
132
+ const remoteSearch = recipients?.search?.onQueryChange !== undefined;
133
+ const query = remoteSearch ? recipients?.search?.query ?? "" : localQuery;
134
+ const visible = recipients ? (remoteSearch ? recipients.items : filterShareSheetRecipients(recipients.items, query)) : [];
135
+ const status = recipients?.status ?? "ready";
136
+ const actionButton = (action, label, icon) => _jsxs(Stack, { align: "center", gap: shareSheetRecipe.actionCaptionGap, children: [_jsx(IconButton, { label: label, tone: shareSheetRecipe.actionButton.tone, size: shareSheetRecipe.actionButton.size, loading: externalAction === action, disabled: externalAction !== null, onClick: () => void runExternal(action), children: icon }), _jsx(Text, { variant: shareSheetRecipe.actionCaptionVariant, "aria-hidden": "true", children: label })] });
137
+ const actions = copy || share ? _jsxs(Stack, { axis: "inline", justify: "center", gap: shareSheetRecipe.actionGap, children: [copy ? actionButton("copy", labels.copyLink, icons?.copyLink ?? _jsx(Icon, { name: "copy" })) : null, share ? actionButton("share", labels.systemShare, icons?.systemShare ?? _jsx(Icon, { name: "share" })) : null] }) : null;
138
+ const footer = pending.length > 0 || actions ? _jsxs(Stack, { gap: shareSheetRecipe.footerGap, layoutStyle: { width: "100%" }, children: [recipients && pending.length > 0 ? _jsx(Button, { fullWidth: true, tone: shareSheetRecipe.sendButton.tone, size: shareSheetRecipe.sendButton.size, loading: sending, onClick: () => void sendSelected(), children: recipients.labels.send(pending.length) }) : null, actions] }) : undefined;
139
+ return _jsx(Sheet, { open: open, onOpenChange: onOpenChange, title: title, closeLabel: labels.close, busy: sending, size: recipients ? shareSheetRecipe.size.recipients : shareSheetRecipe.size.externalOnly, footer: footer, children: _jsxs(Stack, { gap: shareSheetRecipe.bodyGap, children: [notice, sendError ? _jsx(Notice, { tone: "danger", title: sendError }) : null, feedback === "copied" ? _jsx(Notice, { tone: "success", appearance: "status", title: labels.linkCopied }) : null, feedback === "failed" ? _jsx(Notice, { tone: "danger", title: labels.actionFailed }) : null, recipients ? _jsxs(_Fragment, { children: [_jsx(SearchField, { "aria-label": recipients.labels.search, placeholder: recipients.labels.search, clearLabel: recipients.labels.clearSearch, loading: recipients.search?.searching === true, value: query, onValueChange: remoteSearch ? recipients.search.onQueryChange : setLocalQuery }), status === "error" ? _jsx(Notice, { tone: "danger", title: recipients.labels.loadFailed, action: recipients.onRetry ?
140
+ _jsx(IconButton, { label: recipients.labels.retry, tone: "secondary", size: "small", loading: recipients.retrying === true, onClick: recipients.onRetry, children: _jsx(Icon, { name: "refresh" }) }) : undefined })
141
+ : status === "loading" ? _jsx(Spinner, { label: recipients.labels.loading })
142
+ : visible.length === 0 ? _jsx(EmptyState, { density: "compact", title: query.trim() ? recipients.labels.noResults : recipients.labels.empty })
143
+ : _jsx(ImageChoiceGroup, { label: recipients.labels.group, selectionMode: "multiple", showItemLabels: shareSheetRecipe.picker.showItemLabels, itemLabelLines: shareSheetRecipe.picker.itemLabelLines, disabled: sending, value: resolveShareSheetPickerValue(selected, sent, visible.map(item => item.id)), onValueChange: next => setSelected(next.filter(id => !sent.has(id))), items: visible.map(item => sent.has(item.id) ? { ...item, description: recipients.labels.sent, disabled: true } : item) })] }) : null] }) });
144
+ }
145
+ //# sourceMappingURL=share-sheet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"share-sheet.js","sourceRoot":"","sources":["../src/share-sheet.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState, useSyncExternalStore, type ReactNode } from \"react\";\nimport type { SheetOpenChangeDetails } from \"@hjmds/design-contracts/components/sheet\";\nimport {\n filterShareSheetRecipients, resolveShareSheetPickerValue, resolveShareSheetSendOutcome, shareSheetRecipe, validateShareSheet, validateShareSheetRecipients,\n type ShareSheetLabels, type ShareSheetRecipientDescriptor, type ShareSheetRecipientLabels, type ShareSheetRecipientStatus, type ShareSheetSendResult,\n type ShareSheetShareContent, type ShareSheetSystemShareOutcome,\n} from \"@hjmds/design-contracts/share-sheet\";\nimport { Button, IconButton } from \"./actions.js\";\nimport { EmptyState, Notice } from \"./feedback.js\";\nimport { SearchField } from \"./forms.js\";\nimport { ImageChoiceGroup } from \"./image-choice.js\";\nimport { Spinner } from \"./internal/spinner.js\";\nimport { Stack, Text } from \"./layout.js\";\nimport { Sheet } from \"./overlays.js\";\nimport { Icon } from \"./supplemental-display.js\";\n\nexport type {\n ShareSheetLabels, ShareSheetRecipientLabels, ShareSheetRecipientStatus, ShareSheetSendResult, ShareSheetShareContent, ShareSheetSystemShareOutcome,\n} from \"@hjmds/design-contracts/share-sheet\";\n\nexport type ShareSheetRecipient = ShareSheetRecipientDescriptor & Readonly<{ image: ReactNode }>;\n\nexport type ShareSheetRecipientSearch = Readonly<{\n /** Controlled query. With `onQueryChange` the product owns the query and `items` is already its result. */\n query?: string;\n onQueryChange?: (query: string) => void;\n /** A server search is running. */\n searching?: boolean;\n}>;\n\nexport type ShareSheetRecipients = Readonly<{\n items: readonly ShareSheetRecipient[];\n labels: ShareSheetRecipientLabels;\n /** Default `ready`. */\n status?: ShareSheetRecipientStatus;\n /** Shown with the `error` state. */\n onRetry?: () => void;\n retrying?: boolean;\n /** Omit to filter `items` by label here; pass `onQueryChange` to search on the server. */\n search?: ShareSheetRecipientSearch;\n /** Recipients that already received this link (before the sheet opened). They stay checked and locked. */\n sentIds?: readonly string[];\n /** Sends the link to the selected ids. Delivered ids lock as sent; the rest stay selected for a retry. */\n onSend: (ids: readonly string[]) => Promise<ShareSheetSendResult>;\n}>;\n\nexport type ShareSheetProps = Readonly<{\n open: boolean;\n onOpenChange: (open: boolean, detail: SheetOpenChangeDetails) => void;\n title: string;\n labels: ShareSheetLabels;\n /** The URL that is copied and shared. */\n link: string;\n /** Title and text handed to the OS share sheet with the link. */\n shareTitle?: string;\n shareText?: string;\n /** Writes the link to the clipboard. Default `navigator.clipboard.writeText`; `false` hides the action. */\n copyLink?: ((link: string) => Promise<void>) | false;\n /**\n * Opens the OS share sheet. Default `navigator.share` (the action is hidden where the browser has none); `false`\n * hides the action. Reject with an `AbortError` when the user backs out.\n */\n systemShare?: ((content: ShareSheetShareContent) => Promise<void>) | false;\n onLinkCopied?: () => void;\n /** The OS share sheet closed. `completed` is not a delivery receipt. */\n onSystemShare?: (outcome: ShareSheetSystemShareOutcome) => void;\n /** Product artwork for the two actions. Defaults to the HJM `copy` and `share` icons. */\n icons?: Readonly<{ copyLink?: ReactNode; systemShare?: ReactNode }>;\n /** In-app recipients (group chats, followers). Omit for external sharing only. */\n recipients?: ShareSheetRecipients;\n /** Product feedback above the body, such as a reward earned by sending. */\n notice?: ReactNode;\n}>;\n\ntype ExternalAction = \"copy\" | \"share\";\ntype ExternalFeedback = \"copied\" | \"failed\";\n\nconst subscribeNever = () => () => {};\nconst browserShareAvailable = () => typeof navigator !== \"undefined\" && typeof navigator.share === \"function\";\nconst serverShareAvailable = () => false;\n\nfunction isAbort(error: unknown): boolean {\n return typeof error === \"object\" && error !== null && \"name\" in error && (error as { name: unknown }).name === \"AbortError\";\n}\n\n/**\n * One Sheet for sharing a link (2026-10-26, generalized from BurnTok's share sheets): optional in-app recipients with a\n * batch send, then copy link and the OS share sheet as two round actions in the footer.\n */\nexport function ShareSheet({\n open, onOpenChange, title, labels, link, shareTitle, shareText, copyLink, systemShare, onLinkCopied, onSystemShare, icons, recipients, notice,\n}: ShareSheetProps) {\n validateShareSheet({ title, link, labels });\n if (recipients) validateShareSheetRecipients(recipients.items);\n // navigator.share exists only in some browsers and never on the server; read it after hydration.\n const browserShare = useSyncExternalStore(subscribeNever, browserShareAvailable, serverShareAvailable);\n const copy = copyLink === false ? null : copyLink ?? ((value: string) => navigator.clipboard.writeText(value));\n const share = systemShare === false ? null : systemShare ?? (browserShare ? (content: ShareSheetShareContent) => navigator.share(content) : null);\n\n const [externalAction, setExternalAction] = useState<ExternalAction | null>(null);\n const [feedback, setFeedback] = useState<ExternalFeedback | null>(null);\n const [selected, setSelected] = useState<readonly string[]>([]);\n const [sentHere, setSentHere] = useState<ReadonlySet<string>>(() => new Set());\n const [sending, setSending] = useState(false);\n const [sendError, setSendError] = useState(\"\");\n const [localQuery, setLocalQuery] = useState(\"\");\n // Refs close the gap before React re-renders the busy state, so a double tap cannot start a second request.\n const externalInFlight = useRef(false);\n const batchInFlight = useRef(false);\n // Bumped on every close: results of requests started in an earlier visit must not report into the next one.\n const visit = useRef(0);\n const mounted = useRef(false);\n useEffect(() => {\n mounted.current = true;\n return () => { mounted.current = false; visit.current += 1; };\n }, []);\n useEffect(() => {\n if (open) return;\n visit.current += 1;\n setFeedback(null); setSelected([]); setSentHere(new Set()); setSendError(\"\"); setLocalQuery(\"\");\n }, [open]);\n useEffect(() => {\n // A different link makes a \"copied\" claim about the old one false.\n setFeedback(null);\n }, [link]);\n\n async function runExternal(action: ExternalAction) {\n if (externalInFlight.current) return;\n const run = action === \"copy\" ? copy : share;\n if (!run) return;\n externalInFlight.current = true;\n const current = visit.current;\n setExternalAction(action);\n setFeedback(null);\n try {\n if (action === \"copy\") {\n await copy!(link);\n if (current !== visit.current) return;\n setFeedback(\"copied\");\n onLinkCopied?.();\n } else {\n await share!({ url: link, ...(shareTitle ? { title: shareTitle } : {}), ...(shareText ? { text: shareText } : {}) });\n // Resolving only means the OS sheet closed on a target; never announce delivery.\n if (current === visit.current) onSystemShare?.(\"completed\");\n }\n } catch (error) {\n if (current !== visit.current) return;\n if (action === \"share\" && isAbort(error)) onSystemShare?.(\"dismissed\");\n else setFeedback(\"failed\");\n } finally {\n externalInFlight.current = false;\n if (mounted.current) setExternalAction(null);\n }\n }\n\n const sent = new Set([...(recipients?.sentIds ?? []), ...sentHere]);\n const pending = selected.filter(id => !sent.has(id));\n\n async function sendSelected() {\n if (!recipients || batchInFlight.current || pending.length === 0) return;\n batchInFlight.current = true;\n const current = visit.current, requested = pending;\n setSending(true);\n setSendError(\"\");\n try {\n const result = await recipients.onSend(requested);\n if (current !== visit.current) return;\n const outcome = resolveShareSheetSendOutcome(requested, result);\n const delivered = new Set(outcome.sent);\n setSentHere(previous => new Set([...previous, ...outcome.sent]));\n // Failed recipients stay selected, so the next press retries only them.\n setSelected(previous => previous.filter(id => !delivered.has(id)));\n if (outcome.failed.length) setSendError(result.error ?? recipients.labels.sendFailed);\n } catch {\n if (current === visit.current) setSendError(recipients.labels.sendFailed);\n } finally {\n batchInFlight.current = false;\n if (mounted.current) setSending(false);\n }\n }\n\n const remoteSearch = recipients?.search?.onQueryChange !== undefined;\n const query = remoteSearch ? recipients?.search?.query ?? \"\" : localQuery;\n const visible = recipients ? (remoteSearch ? recipients.items : filterShareSheetRecipients(recipients.items, query)) : [];\n const status = recipients?.status ?? \"ready\";\n\n const actionButton = (action: ExternalAction, label: string, icon: ReactNode) =>\n <Stack align=\"center\" gap={shareSheetRecipe.actionCaptionGap}>\n <IconButton label={label} tone={shareSheetRecipe.actionButton.tone} size={shareSheetRecipe.actionButton.size}\n loading={externalAction === action} disabled={externalAction !== null} onClick={() => void runExternal(action)}>{icon}</IconButton>\n {/* The button already carries the name; the caption is the visible copy of it. */}\n <Text variant={shareSheetRecipe.actionCaptionVariant} aria-hidden=\"true\">{label}</Text>\n </Stack>;\n const actions = copy || share ? <Stack axis=\"inline\" justify=\"center\" gap={shareSheetRecipe.actionGap}>\n {copy ? actionButton(\"copy\", labels.copyLink, icons?.copyLink ?? <Icon name=\"copy\" />) : null}\n {share ? actionButton(\"share\", labels.systemShare, icons?.systemShare ?? <Icon name=\"share\" />) : null}\n </Stack> : null;\n const footer = pending.length > 0 || actions ? <Stack gap={shareSheetRecipe.footerGap} layoutStyle={{ width: \"100%\" }}>\n {recipients && pending.length > 0 ? <Button fullWidth tone={shareSheetRecipe.sendButton.tone} size={shareSheetRecipe.sendButton.size} loading={sending}\n onClick={() => void sendSelected()}>{recipients.labels.send(pending.length)}</Button> : null}\n {actions}\n </Stack> : undefined;\n\n return <Sheet open={open} onOpenChange={onOpenChange} title={title} closeLabel={labels.close} busy={sending}\n size={recipients ? shareSheetRecipe.size.recipients : shareSheetRecipe.size.externalOnly} footer={footer}>\n <Stack gap={shareSheetRecipe.bodyGap}>\n {notice}\n {sendError ? <Notice tone=\"danger\" title={sendError} /> : null}\n {feedback === \"copied\" ? <Notice tone=\"success\" appearance=\"status\" title={labels.linkCopied} /> : null}\n {feedback === \"failed\" ? <Notice tone=\"danger\" title={labels.actionFailed} /> : null}\n {recipients ? <>\n <SearchField aria-label={recipients.labels.search} placeholder={recipients.labels.search} clearLabel={recipients.labels.clearSearch}\n loading={recipients.search?.searching === true} value={query} onValueChange={remoteSearch ? recipients.search!.onQueryChange! : setLocalQuery} />\n {status === \"error\" ? <Notice tone=\"danger\" title={recipients.labels.loadFailed} action={recipients.onRetry ?\n <IconButton label={recipients.labels.retry} tone=\"secondary\" size=\"small\" loading={recipients.retrying === true} onClick={recipients.onRetry}><Icon name=\"refresh\" /></IconButton> : undefined} />\n : status === \"loading\" ? <Spinner label={recipients.labels.loading} />\n : visible.length === 0 ? <EmptyState density=\"compact\" title={query.trim() ? recipients.labels.noResults : recipients.labels.empty} />\n : <ImageChoiceGroup label={recipients.labels.group} selectionMode=\"multiple\" showItemLabels={shareSheetRecipe.picker.showItemLabels}\n itemLabelLines={shareSheetRecipe.picker.itemLabelLines} disabled={sending}\n value={resolveShareSheetPickerValue(selected, sent, visible.map(item => item.id))}\n onValueChange={next => setSelected(next.filter(id => !sent.has(id)))}\n items={visible.map(item => sent.has(item.id) ? { ...item, description: recipients.labels.sent, disabled: true } : item)} />}\n </> : null}\n </Stack>\n </Sheet>;\n}\n"]}
@@ -4,6 +4,11 @@ type Person = Readonly<{
4
4
  id: string;
5
5
  name: string;
6
6
  avatar?: ReactNode;
7
+ avatarAction?: Readonly<{
8
+ label: string;
9
+ onPress(): void;
10
+ disabled?: boolean;
11
+ }>;
7
12
  }>;
8
13
  export type FollowProfileCardProps = Readonly<{
9
14
  /** Changing entities remounts the action scope and detaches old responses. */
@@ -11,6 +16,11 @@ export type FollowProfileCardProps = Readonly<{
11
16
  name: string;
12
17
  description?: ReactNode;
13
18
  avatar?: ReactNode;
19
+ avatarAction?: Readonly<{
20
+ label: string;
21
+ onPress(): void;
22
+ disabled?: boolean;
23
+ }>;
14
24
  state: FollowProfileState;
15
25
  locale: string;
16
26
  labels: Readonly<{
@@ -1 +1 @@
1
- {"version":3,"file":"social-compositions.d.ts","sourceRoot":"","sources":["../src/social-compositions.tsx"],"names":[],"mappings":"AACA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACjD,OAAO,EAAoD,KAAK,kBAAkB,EAAE,KAAK,gBAAgB,EAAE,KAAK,oBAAoB,EAAE,MAAM,6CAA6C,CAAC;AAS1L,KAAK,MAAM,GAAG,QAAQ,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,SAAS,CAAA;CAAE,CAAC,CAAC;AAIzE,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,8EAA8E;IAC9E,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,SAAS,CAAC;IAC7E,KAAK,EAAE,kBAAkB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAC1C,MAAM,EAAE,QAAQ,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvG,+FAA+F;IAC/F,cAAc,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAClF,CAAC,CAAC;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,+BAG9D;AAgBD,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC;IAC9C,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAC3E,KAAK,EAAE,oBAAoB,CAAC;IAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IACnH,MAAM,EAAE,QAAQ,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/H,gBAAgB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,0FAA0F;IAC1F,eAAe,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACjE,CAAC,CAAC;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,wBAAwB,+BAGlE"}
1
+ {"version":3,"file":"social-compositions.d.ts","sourceRoot":"","sources":["../src/social-compositions.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACjD,OAAO,EAAoD,KAAK,kBAAkB,EAAE,KAAK,gBAAgB,EAAE,KAAK,oBAAoB,EAAE,MAAM,6CAA6C,CAAC;AAS1L,KAAK,MAAM,GAAG,QAAQ,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,SAAS,CAAC;IAAC,YAAY,CAAC,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAC,MAAM,CAAC;QAAA,OAAO,IAAG,IAAI,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;CAAE,CAAC,CAAC;AAItJ,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,8EAA8E;IAC9E,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,SAAS,CAAC;IAAC,YAAY,CAAC,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAC,MAAM,CAAC;QAAA,OAAO,IAAG,IAAI,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC1J,KAAK,EAAE,kBAAkB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAC1C,MAAM,EAAE,QAAQ,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvG,+FAA+F;IAC/F,cAAc,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAClF,CAAC,CAAC;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,+BAG9D;AAgBD,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC;IAC9C,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAC3E,KAAK,EAAE,oBAAoB,CAAC;IAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IACnH,MAAM,EAAE,QAAQ,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/H,gBAAgB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,0FAA0F;IAC1F,eAAe,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACjE,CAAC,CAAC;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,wBAAwB,+BAGlE"}
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { IdentityAction } from "./internal/identity-action.js";
3
4
  import { useState } from "react";
4
5
  import { resolveFollowProposal, resolveAttendanceProposal } from "@hjmds/design-contracts/social-compositions";
5
6
  import { Button } from "./actions.js";
@@ -22,7 +23,7 @@ function FollowProfileBody(props) {
22
23
  const action = useCompositionAction(props.disabled);
23
24
  // Capture the proposal once; changing server counts during the request must not add the delta twice.
24
25
  const displayed = action.active ?? props.state;
25
- return _jsx(Card, { title: props.name, description: props.description, leading: props.avatar ?? _jsx(Avatar, { name: props.name, "aria-hidden": true }), children: _jsxs(Stack, { gap: "md", children: [_jsx(AnimatedStatistic, { descriptor: { id: "social-count", label: props.labels.count }, value: displayed.count, locale: props.locale }), _jsx(Button, { selected: displayed.following, loading: action.pending, "aria-label": action.pending ? props.labels.pending : undefined, disabled: props.disabled === true, onClick: () => { const proposal = resolveFollowProposal(props.state, !props.state.following); void action.run(proposal, () => props.onFollowChange(proposal.following)); }, children: displayed.following ? props.labels.following : props.labels.follow }), _jsx(Failure, { failed: action.failed, label: props.labels.error })] }) });
26
+ return _jsx(Card, { title: props.name, description: props.description, leading: props.avatarAction ? _jsx(IdentityAction, { action: props.avatarAction, children: props.avatar ?? _jsx(Avatar, { name: props.name, "aria-hidden": true }) }) : props.avatar ?? _jsx(Avatar, { name: props.name, "aria-hidden": true }), children: _jsxs(Stack, { gap: "md", children: [_jsx(AnimatedStatistic, { descriptor: { id: "social-count", label: props.labels.count }, value: displayed.count, locale: props.locale }), _jsx(Button, { selected: displayed.following, loading: action.pending, "aria-label": action.pending ? props.labels.pending : undefined, disabled: props.disabled === true, onClick: () => { const proposal = resolveFollowProposal(props.state, !props.state.following); void action.run(proposal, () => props.onFollowChange(proposal.following)); }, children: displayed.following ? props.labels.following : props.labels.follow }), _jsx(Failure, { failed: action.failed, label: props.labels.error })] }) });
26
27
  }
27
28
  export function EventAttendanceCard(props) {
28
29
  if (!props.eventId.trim())
@@ -34,7 +35,7 @@ function EventAttendanceBody(props) {
34
35
  const action = useCompositionAction(props.disabled), [calendarAdded, setCalendarAdded] = useState(false);
35
36
  const displayed = action.active === null || action.active === "calendar" ? props.state : action.active;
36
37
  const locked = props.disabled === true || action.pending;
37
- return _jsx(Card, { title: props.title, description: props.description, children: _jsxs(Stack, { gap: "md", children: [_jsx(Text, { children: props.dateLabel }), _jsx(AnimatedStatistic, { descriptor: { id: "social-count", label: props.labels.count }, value: displayed.count, locale: props.locale }), _jsx(Stack, { axis: "inline", gap: "xs", wrap: true, children: props.participants.map(person => _jsxs(Stack, { axis: "inline", gap: "xs", align: "center", children: [person.avatar ?? _jsx(Avatar, { name: person.name, "aria-hidden": true }), _jsx(Text, { children: person.name })] }, person.id)) }), _jsx(RadioGroup, { label: props.labels.response, orientation: "horizontal", presentation: "plain", value: displayed.responseId, items: props.choices.map(choice => ({ value: choice.id, label: choice.label, ...(choice.disabled ? { disabled: true } : {}) })), disabled: locked, ...(action.pending && action.active !== "calendar" ? { description: props.labels.pending } : {}), onValueChange: id => { const choice = props.choices.find(item => item.id === id); if (!choice || choice.disabled || choice.id === props.state.responseId || locked)
38
+ return _jsx(Card, { title: props.title, description: props.description, children: _jsxs(Stack, { gap: "md", children: [_jsx(Text, { children: props.dateLabel }), _jsx(AnimatedStatistic, { descriptor: { id: "social-count", label: props.labels.count }, value: displayed.count, locale: props.locale }), _jsx(Stack, { axis: "inline", gap: "xs", wrap: true, children: props.participants.map(person => _jsxs(Stack, { axis: "inline", gap: "xs", align: "center", children: [person.avatarAction ? _jsx(IdentityAction, { action: person.avatarAction, children: person.avatar ?? _jsx(Avatar, { name: person.name, "aria-hidden": true }) }) : person.avatar ?? _jsx(Avatar, { name: person.name, "aria-hidden": true }), _jsx(Text, { children: person.name })] }, person.id)) }), _jsx(RadioGroup, { label: props.labels.response, orientation: "horizontal", presentation: "plain", value: displayed.responseId, items: props.choices.map(choice => ({ value: choice.id, label: choice.label, ...(choice.disabled ? { disabled: true } : {}) })), disabled: locked, ...(action.pending && action.active !== "calendar" ? { description: props.labels.pending } : {}), onValueChange: id => { const choice = props.choices.find(item => item.id === id); if (!choice || choice.disabled || choice.id === props.state.responseId || locked)
38
39
  return; const proposal = resolveAttendanceProposal(props.state, props.choices, choice.id); void action.run(proposal, () => props.onResponseChange(choice.id)); } }), _jsx(Button, { tone: "secondary", disabled: locked || calendarAdded, loading: action.active === "calendar", "aria-label": action.pending ? props.labels.pending : undefined, onClick: () => { if (calendarAdded)
39
40
  return; void action.run("calendar", props.onAddToCalendar).then(outcome => { if (outcome.status === "success")
40
41
  setCalendarAdded(true); }); }, children: calendarAdded ? props.labels.calendarAdded : props.labels.calendar }), _jsx(Failure, { failed: action.failed, label: props.labels.error })] }) });
@@ -1 +1 @@
1
- {"version":3,"file":"social-compositions.js","sourceRoot":"","sources":["../src/social-compositions.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\nimport { useState, type ReactNode } from \"react\";\nimport { resolveFollowProposal, resolveAttendanceProposal, type FollowProfileState, type AttendanceChoice, type EventAttendanceState } from \"@hjmds/design-contracts/social-compositions\";\nimport { Button } from \"./actions.js\";\nimport { AnimatedStatistic } from \"./statistic-motion.js\";\nimport { Avatar } from \"./advanced-display.js\";\nimport { Card } from \"./display.js\";\nimport { Stack, Text } from \"./layout.js\";\nimport { RadioGroup } from \"./selection.js\";\nimport { useCompositionAction } from \"./internal/use-composition-action.js\";\n\ntype Person = Readonly<{ id: string; name: string; avatar?: ReactNode }>;\nfunction Failure({ failed, label }: Readonly<{ failed: boolean; label: string }>) {\n return failed ? <Text role=\"alert\" style={{ textAlign: \"center\" }} tone=\"danger\">{label}</Text> : null;\n}\nexport type FollowProfileCardProps = Readonly<{\n /** Changing entities remounts the action scope and detaches old responses. */\n profileId: string; name: string; description?: ReactNode; avatar?: ReactNode;\n state: FollowProfileState; locale: string;\n labels: Readonly<{ follow: string; following: string; pending: string; count: string; error: string }>;\n /** Resolve only after the real mutation; update state with the server's confirmed snapshot. */\n onFollowChange: (following: boolean) => void | Promise<void>; disabled?: boolean;\n}>;\nexport function FollowProfileCard(props: FollowProfileCardProps) {\n if (!props.profileId.trim()) throw new TypeError(\"Follow profile requires a stable id\");\n return <FollowProfileBody key={props.profileId} {...props} />;\n}\nfunction FollowProfileBody(props: FollowProfileCardProps) {\n resolveFollowProposal(props.state, props.state.following);\n const action = useCompositionAction<FollowProfileState>(props.disabled);\n // Capture the proposal once; changing server counts during the request must not add the delta twice.\n const displayed: FollowProfileState = action.active ?? props.state;\n return <Card title={props.name} description={props.description} leading={props.avatar ?? <Avatar name={props.name} aria-hidden />}>\n <Stack gap=\"md\"><AnimatedStatistic descriptor={{ id: \"social-count\", label: props.labels.count }} value={displayed.count} locale={props.locale} />\n <Button selected={displayed.following} loading={action.pending} aria-label={action.pending ? props.labels.pending : undefined} disabled={props.disabled === true}\n onClick={() => { const proposal = resolveFollowProposal(props.state, !props.state.following); void action.run(proposal, () => props.onFollowChange(proposal.following)); }}>\n {displayed.following ? props.labels.following : props.labels.follow}\n </Button><Failure failed={action.failed} label={props.labels.error} />\n </Stack>\n </Card>;\n}\n\nexport type EventAttendanceCardProps = Readonly<{\n eventId: string; title: string; description?: ReactNode; dateLabel: string;\n state: EventAttendanceState; choices: readonly AttendanceChoice[]; participants: readonly Person[]; locale: string;\n labels: Readonly<{ response: string; pending: string; count: string; calendar: string; calendarAdded: string; error: string }>;\n onResponseChange: (responseId: string) => void | Promise<void>;\n /** Actual calendar adapter, including platform permissions; rejection stays retryable. */\n onAddToCalendar: () => void | Promise<void>; disabled?: boolean;\n}>;\nexport function EventAttendanceCard(props: EventAttendanceCardProps) {\n if (!props.eventId.trim()) throw new TypeError(\"Event attendance requires a stable id\");\n return <EventAttendanceBody key={props.eventId} {...props} />;\n}\nfunction EventAttendanceBody(props: EventAttendanceCardProps) {\n resolveAttendanceProposal(props.state, props.choices, props.state.responseId);\n const action = useCompositionAction<EventAttendanceState | \"calendar\">(props.disabled), [calendarAdded, setCalendarAdded] = useState(false);\n const displayed: EventAttendanceState = action.active === null || action.active === \"calendar\" ? props.state : action.active;\n const locked = props.disabled === true || action.pending;\n return <Card title={props.title} description={props.description}><Stack gap=\"md\">\n <Text>{props.dateLabel}</Text>\n <AnimatedStatistic descriptor={{ id: \"social-count\", label: props.labels.count }} value={displayed.count} locale={props.locale} />\n <Stack axis=\"inline\" gap=\"xs\" wrap>{props.participants.map(person => <Stack key={person.id} axis=\"inline\" gap=\"xs\" align=\"center\">{person.avatar ?? <Avatar name={person.name} aria-hidden />}<Text>{person.name}</Text></Stack>)}</Stack>\n {/* One answer out of several is a radio choice; selected primary Buttons gave the card three primaries.\n RadioGroup (not SegmentedControl) keeps the \"no answer yet\" state that responseId=null needs; plain keeps\n three short answers on one line inside the card. */}\n <RadioGroup label={props.labels.response} orientation=\"horizontal\" presentation=\"plain\" value={displayed.responseId}\n items={props.choices.map(choice => ({ value: choice.id, label: choice.label, ...(choice.disabled ? { disabled: true } : {}) }))}\n disabled={locked} {...(action.pending && action.active !== \"calendar\" ? { description: props.labels.pending } : {})}\n onValueChange={id => { const choice = props.choices.find(item => item.id === id); if (!choice || choice.disabled || choice.id === props.state.responseId || locked) return; const proposal = resolveAttendanceProposal(props.state, props.choices, choice.id); void action.run(proposal, () => props.onResponseChange(choice.id)); }} />\n <Button tone=\"secondary\" disabled={locked || calendarAdded} loading={action.active === \"calendar\"} aria-label={action.pending ? props.labels.pending : undefined}\n onClick={() => { if (calendarAdded) return; void action.run(\"calendar\", props.onAddToCalendar).then(outcome => { if (outcome.status === \"success\") setCalendarAdded(true); }); }}>{calendarAdded ? props.labels.calendarAdded : props.labels.calendar}</Button>\n <Failure failed={action.failed} label={props.labels.error} />\n </Stack></Card>;\n}\n"]}
1
+ {"version":3,"file":"social-compositions.js","sourceRoot":"","sources":["../src/social-compositions.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\nimport { IdentityAction } from \"./internal/identity-action.js\";\nimport { useState, type ReactNode } from \"react\";\nimport { resolveFollowProposal, resolveAttendanceProposal, type FollowProfileState, type AttendanceChoice, type EventAttendanceState } from \"@hjmds/design-contracts/social-compositions\";\nimport { Button } from \"./actions.js\";\nimport { AnimatedStatistic } from \"./statistic-motion.js\";\nimport { Avatar } from \"./advanced-display.js\";\nimport { Card } from \"./display.js\";\nimport { Stack, Text } from \"./layout.js\";\nimport { RadioGroup } from \"./selection.js\";\nimport { useCompositionAction } from \"./internal/use-composition-action.js\";\n\ntype Person = Readonly<{ id: string; name: string; avatar?: ReactNode; avatarAction?: Readonly<{label:string;onPress():void; disabled?: boolean }> }>;\nfunction Failure({ failed, label }: Readonly<{ failed: boolean; label: string }>) {\n return failed ? <Text role=\"alert\" style={{ textAlign: \"center\" }} tone=\"danger\">{label}</Text> : null;\n}\nexport type FollowProfileCardProps = Readonly<{\n /** Changing entities remounts the action scope and detaches old responses. */\n profileId: string; name: string; description?: ReactNode; avatar?: ReactNode; avatarAction?: Readonly<{label:string;onPress():void; disabled?: boolean }>;\n state: FollowProfileState; locale: string;\n labels: Readonly<{ follow: string; following: string; pending: string; count: string; error: string }>;\n /** Resolve only after the real mutation; update state with the server's confirmed snapshot. */\n onFollowChange: (following: boolean) => void | Promise<void>; disabled?: boolean;\n}>;\nexport function FollowProfileCard(props: FollowProfileCardProps) {\n if (!props.profileId.trim()) throw new TypeError(\"Follow profile requires a stable id\");\n return <FollowProfileBody key={props.profileId} {...props} />;\n}\nfunction FollowProfileBody(props: FollowProfileCardProps) {\n resolveFollowProposal(props.state, props.state.following);\n const action = useCompositionAction<FollowProfileState>(props.disabled);\n // Capture the proposal once; changing server counts during the request must not add the delta twice.\n const displayed: FollowProfileState = action.active ?? props.state;\n return <Card title={props.name} description={props.description} leading={props.avatarAction?<IdentityAction action={props.avatarAction}>{props.avatar ?? <Avatar name={props.name} aria-hidden />}</IdentityAction>:props.avatar ?? <Avatar name={props.name} aria-hidden />}>\n <Stack gap=\"md\"><AnimatedStatistic descriptor={{ id: \"social-count\", label: props.labels.count }} value={displayed.count} locale={props.locale} />\n <Button selected={displayed.following} loading={action.pending} aria-label={action.pending ? props.labels.pending : undefined} disabled={props.disabled === true}\n onClick={() => { const proposal = resolveFollowProposal(props.state, !props.state.following); void action.run(proposal, () => props.onFollowChange(proposal.following)); }}>\n {displayed.following ? props.labels.following : props.labels.follow}\n </Button><Failure failed={action.failed} label={props.labels.error} />\n </Stack>\n </Card>;\n}\n\nexport type EventAttendanceCardProps = Readonly<{\n eventId: string; title: string; description?: ReactNode; dateLabel: string;\n state: EventAttendanceState; choices: readonly AttendanceChoice[]; participants: readonly Person[]; locale: string;\n labels: Readonly<{ response: string; pending: string; count: string; calendar: string; calendarAdded: string; error: string }>;\n onResponseChange: (responseId: string) => void | Promise<void>;\n /** Actual calendar adapter, including platform permissions; rejection stays retryable. */\n onAddToCalendar: () => void | Promise<void>; disabled?: boolean;\n}>;\nexport function EventAttendanceCard(props: EventAttendanceCardProps) {\n if (!props.eventId.trim()) throw new TypeError(\"Event attendance requires a stable id\");\n return <EventAttendanceBody key={props.eventId} {...props} />;\n}\nfunction EventAttendanceBody(props: EventAttendanceCardProps) {\n resolveAttendanceProposal(props.state, props.choices, props.state.responseId);\n const action = useCompositionAction<EventAttendanceState | \"calendar\">(props.disabled), [calendarAdded, setCalendarAdded] = useState(false);\n const displayed: EventAttendanceState = action.active === null || action.active === \"calendar\" ? props.state : action.active;\n const locked = props.disabled === true || action.pending;\n return <Card title={props.title} description={props.description}><Stack gap=\"md\">\n <Text>{props.dateLabel}</Text>\n <AnimatedStatistic descriptor={{ id: \"social-count\", label: props.labels.count }} value={displayed.count} locale={props.locale} />\n <Stack axis=\"inline\" gap=\"xs\" wrap>{props.participants.map(person => <Stack key={person.id} axis=\"inline\" gap=\"xs\" align=\"center\">{person.avatarAction?<IdentityAction action={person.avatarAction}>{person.avatar ?? <Avatar name={person.name} aria-hidden />}</IdentityAction>:person.avatar ?? <Avatar name={person.name} aria-hidden />}<Text>{person.name}</Text></Stack>)}</Stack>\n {/* One answer out of several is a radio choice; selected primary Buttons gave the card three primaries.\n RadioGroup (not SegmentedControl) keeps the \"no answer yet\" state that responseId=null needs; plain keeps\n three short answers on one line inside the card. */}\n <RadioGroup label={props.labels.response} orientation=\"horizontal\" presentation=\"plain\" value={displayed.responseId}\n items={props.choices.map(choice => ({ value: choice.id, label: choice.label, ...(choice.disabled ? { disabled: true } : {}) }))}\n disabled={locked} {...(action.pending && action.active !== \"calendar\" ? { description: props.labels.pending } : {})}\n onValueChange={id => { const choice = props.choices.find(item => item.id === id); if (!choice || choice.disabled || choice.id === props.state.responseId || locked) return; const proposal = resolveAttendanceProposal(props.state, props.choices, choice.id); void action.run(proposal, () => props.onResponseChange(choice.id)); }} />\n <Button tone=\"secondary\" disabled={locked || calendarAdded} loading={action.active === \"calendar\"} aria-label={action.pending ? props.labels.pending : undefined}\n onClick={() => { if (calendarAdded) return; void action.run(\"calendar\", props.onAddToCalendar).then(outcome => { if (outcome.status === \"success\") setCalendarAdded(true); }); }}>{calendarAdded ? props.labels.calendarAdded : props.labels.calendar}</Button>\n <Failure failed={action.failed} label={props.labels.error} />\n </Stack></Card>;\n}\n"]}