@treasuryspatial/prompt-ui-kit 0.1.14 → 0.1.21

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 (44) hide show
  1. package/dist/FeedbackPanel.d.ts +20 -1
  2. package/dist/FeedbackPanel.d.ts.map +1 -1
  3. package/dist/FeedbackPanel.js +251 -25
  4. package/dist/FullscreenImage.d.ts +12 -1
  5. package/dist/FullscreenImage.d.ts.map +1 -1
  6. package/dist/FullscreenImage.js +319 -41
  7. package/dist/GalleryThumbCard.d.ts +2 -1
  8. package/dist/GalleryThumbCard.d.ts.map +1 -1
  9. package/dist/GalleryThumbCard.js +121 -3
  10. package/dist/ImageReferenceStrip.d.ts +9 -0
  11. package/dist/ImageReferenceStrip.d.ts.map +1 -0
  12. package/dist/ImageReferenceStrip.js +47 -0
  13. package/dist/PromptDebugPanel.d.ts +1 -2
  14. package/dist/PromptDebugPanel.d.ts.map +1 -1
  15. package/dist/PromptDebugPanel.js +74 -2
  16. package/dist/RenderHistoryPanel.d.ts +6 -1
  17. package/dist/RenderHistoryPanel.d.ts.map +1 -1
  18. package/dist/RenderHistoryPanel.js +108 -17
  19. package/dist/RenderImageStage.d.ts +10 -0
  20. package/dist/RenderImageStage.d.ts.map +1 -0
  21. package/dist/RenderImageStage.js +90 -0
  22. package/dist/RenderLightboxModal.d.ts +4 -2
  23. package/dist/RenderLightboxModal.d.ts.map +1 -1
  24. package/dist/RenderLightboxModal.js +54 -18
  25. package/dist/RenderPanel.d.ts +17 -16
  26. package/dist/RenderPanel.d.ts.map +1 -1
  27. package/dist/RenderPanel.js +178 -204
  28. package/dist/createRenderHistoryActions.d.ts +11 -0
  29. package/dist/createRenderHistoryActions.d.ts.map +1 -0
  30. package/dist/createRenderHistoryActions.js +63 -0
  31. package/dist/imageDetailPanels.d.ts +14 -0
  32. package/dist/imageDetailPanels.d.ts.map +1 -0
  33. package/dist/imageDetailPanels.js +128 -0
  34. package/dist/index.d.ts +7 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +4 -0
  37. package/dist/tsconfig.tsbuildinfo +1 -1
  38. package/dist/useRenderImageFlow.d.ts +2 -2
  39. package/dist/useRenderImageFlow.d.ts.map +1 -1
  40. package/dist/useRenderImageFlow.js +1 -1
  41. package/dist/useRenderLightbox.d.ts +14 -2
  42. package/dist/useRenderLightbox.d.ts.map +1 -1
  43. package/dist/useRenderLightbox.js +333 -34
  44. package/package.json +7 -5
@@ -3,7 +3,26 @@ type FeedbackPanelProps = {
3
3
  page?: string;
4
4
  title?: string;
5
5
  description?: string;
6
+ roadmap?: FeedbackRoadmapConfig | null;
6
7
  };
7
- export default function FeedbackPanel({ toolId, page, title, description, }: FeedbackPanelProps): import("react/jsx-runtime").JSX.Element;
8
+ type FeedbackRoadmapItem = {
9
+ id: string;
10
+ name: string;
11
+ description?: string;
12
+ category?: string;
13
+ status?: string;
14
+ enabled?: boolean;
15
+ sortOrder?: number;
16
+ rank?: number;
17
+ score?: number;
18
+ };
19
+ type FeedbackRoadmapConfig = {
20
+ enabled?: boolean;
21
+ title?: string;
22
+ description?: string;
23
+ rankingMode?: string;
24
+ items?: FeedbackRoadmapItem[];
25
+ };
26
+ export default function FeedbackPanel({ toolId, page, title, description, roadmap, }: FeedbackPanelProps): import("react/jsx-runtime").JSX.Element;
8
27
  export {};
9
28
  //# sourceMappingURL=FeedbackPanel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"FeedbackPanel.d.ts","sourceRoot":"","sources":["../src/FeedbackPanel.tsx"],"names":[],"mappings":"AAIA,KAAK,kBAAkB,GAAG;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAKF,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,EACpC,MAAW,EACX,IAAS,EACT,KAAkB,EAClB,WAA+C,GAChD,EAAE,kBAAkB,2CAiJpB"}
1
+ {"version":3,"file":"FeedbackPanel.d.ts","sourceRoot":"","sources":["../src/FeedbackPanel.tsx"],"names":[],"mappings":"AAKA,KAAK,kBAAkB,GAAG;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;CACxC,CAAC;AAEF,KAAK,mBAAmB,GAAG;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,KAAK,qBAAqB,GAAG;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,mBAAmB,EAAE,CAAC;CAC/B,CAAC;AAyIF,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,EACpC,MAAW,EACX,IAAS,EACT,KAAoC,EACpC,WAAwH,EACxH,OAAc,GACf,EAAE,kBAAkB,2CAuSpB"}
@@ -1,20 +1,180 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useRef, useState } from 'react';
3
+ import { useEffect, useRef, useState } from 'react';
4
+ import styled from 'styled-components';
4
5
  const FEEDBACK_IMAGE_LIMIT = 3;
5
6
  const FEEDBACK_IMAGE_MAX_BYTES = 5 * 1024 * 1024;
6
- export default function FeedbackPanel({ toolId = '', page = '', title = 'feedback', description = 'Share bugs, requests, or notes.', }) {
7
+ const TELEMETRY_SESSION_STORAGE_KEY = 'telemetry:session-id';
8
+ const Root = styled.div.attrs({ className: 'viewer-ui-panel-sections' }) `
9
+ font-family: var(--viewer-ui-font-body, var(--ui-font-family, 'Aenik', 'Aeonik', system-ui, sans-serif));
10
+ `;
11
+ const Section = styled.section.attrs({ className: 'panel-section' }) ``;
12
+ const SectionHeader = styled.div.attrs({ className: 'panel-section-header' }) ``;
13
+ const SectionHeaderCopy = styled.div `
14
+ min-width: 0;
15
+ `;
16
+ const SectionFields = styled.div.attrs({ className: 'panel-section-fields' }) ``;
17
+ const Textarea = styled.textarea.attrs({ className: 'cfg-textarea' }) `
18
+ width: 100%;
19
+ min-height: 136px;
20
+ resize: vertical;
21
+ font-family: var(--viewer-ui-font-body, var(--ui-font-family, 'Aenik', 'Aeonik', system-ui, sans-serif));
22
+
23
+ &::placeholder {
24
+ font-family: var(--viewer-ui-font-body, var(--ui-font-family, 'Aenik', 'Aeonik', system-ui, sans-serif));
25
+ letter-spacing: 0;
26
+ color: color-mix(in srgb, var(--brand-text-secondary) 72%, white 28%);
27
+ }
28
+ `;
29
+ const Note = styled.div.attrs({ className: 'panel-section-note' }) ``;
30
+ const DropZone = styled.div `
31
+ margin-top: 8px;
32
+ border-radius: 18px;
33
+ border: 1px dashed ${(props) => (props.$dragActive ? 'var(--brand-primary)' : 'var(--brand-panel-border)')};
34
+ background: ${(props) => props.$dragActive
35
+ ? 'color-mix(in srgb, var(--brand-primary) 10%, var(--brand-background) 90%)'
36
+ : 'var(--brand-background)'};
37
+ padding: 18px 14px;
38
+ text-align: center;
39
+ transition: border-color 0.16s ease, background-color 0.16s ease;
40
+ `;
41
+ const DropButton = styled.button `
42
+ border: 0;
43
+ background: transparent;
44
+ color: var(--brand-text-primary);
45
+ cursor: pointer;
46
+ font-family: inherit;
47
+ font-size: 11px;
48
+ font-weight: 600;
49
+ letter-spacing: 0.08em;
50
+ text-transform: var(--viewer-ui-action-text-case, lowercase);
51
+ `;
52
+ const HiddenFileInput = styled.input `
53
+ position: absolute;
54
+ width: 1px;
55
+ height: 1px;
56
+ padding: 0;
57
+ margin: -1px;
58
+ overflow: hidden;
59
+ clip: rect(0 0 0 0);
60
+ white-space: nowrap;
61
+ border: 0;
62
+ `;
63
+ const FileList = styled.div.attrs({ className: 'panel-section-list' }) `
64
+ margin-top: 10px;
65
+ `;
66
+ const FileRow = styled.div.attrs({ className: 'panel-section-row' }) `
67
+ font-size: 11px;
68
+ color: var(--brand-text-secondary);
69
+ `;
70
+ const RemoveButton = styled.button `
71
+ border: 0;
72
+ background: transparent;
73
+ color: var(--brand-primary);
74
+ cursor: pointer;
75
+ font-size: 11px;
76
+ text-transform: uppercase;
77
+ letter-spacing: 0.16em;
78
+ `;
79
+ const Status = styled.div `
80
+ font-size: 12px;
81
+ color: ${(props) => props.$tone === 'success' ? 'var(--brand-text-primary)' : 'var(--viewer-ui-color-text-error, #b91c1c)'};
82
+ `;
83
+ const SubmitButton = styled.button.attrs({ className: 'cfg-button cfg-button--primary' }) `
84
+ width: 100%;
85
+ font-family: inherit;
86
+ `;
87
+ const RoadmapSection = styled.section.attrs({ className: 'panel-section' }) ``;
88
+ const RoadmapList = styled.div.attrs({ className: 'panel-section-stack' }) ``;
89
+ const RoadmapItem = styled.div.attrs({ className: 'panel-section-group' }) ``;
90
+ const RoadmapItemHeader = styled.div `
91
+ display: flex;
92
+ align-items: flex-start;
93
+ justify-content: space-between;
94
+ gap: 12px;
95
+ `;
96
+ const RoadmapItemTitle = styled.h4 ``;
97
+ const RoadmapItemDescription = styled.p ``;
98
+ const RoadmapMeta = styled.div.attrs({ className: 'panel-chip-row' }) ``;
99
+ const RoadmapTag = styled.span `
100
+ border-radius: 999px;
101
+ border: 1px solid var(--brand-panel-border);
102
+ padding: 3px 8px;
103
+ font-size: 9px;
104
+ line-height: 1;
105
+ color: var(--brand-text-secondary);
106
+ text-transform: uppercase;
107
+ letter-spacing: 0.1em;
108
+ `;
109
+ const VoteButton = styled.button.attrs({ className: 'cfg-button cfg-button--secondary cfg-button--compact' }) `
110
+ flex: 0 0 auto;
111
+ font-family: inherit;
112
+ `;
113
+ export default function FeedbackPanel({ toolId = '', page = '', title = 'design requests + feedback', description = 'Request new design types, plugins, tools, workflows, features, bug fixes, or visual/render improvements.', roadmap = null, }) {
7
114
  const [message, setMessage] = useState('');
8
115
  const [status, setStatus] = useState('idle');
9
116
  const [error, setError] = useState('');
10
117
  const [images, setImages] = useState([]);
11
118
  const [imageError, setImageError] = useState('');
12
119
  const [dragActive, setDragActive] = useState(false);
120
+ const [roadmapItems, setRoadmapItems] = useState([]);
121
+ const [roadmapStatus, setRoadmapStatus] = useState('idle');
122
+ const [roadmapError, setRoadmapError] = useState('');
123
+ const [votedFeatureIds, setVotedFeatureIds] = useState(() => new Set());
13
124
  const inputRef = useRef(null);
125
+ const configuredRoadmapItems = (roadmap?.items ?? [])
126
+ .filter((item) => item && item.enabled !== false && item.id && item.name)
127
+ .sort((a, b) => {
128
+ const aOrder = Number.isFinite(Number(a.sortOrder)) ? Number(a.sortOrder) : 9999;
129
+ const bOrder = Number.isFinite(Number(b.sortOrder)) ? Number(b.sortOrder) : 9999;
130
+ return aOrder - bOrder || a.name.localeCompare(b.name);
131
+ });
132
+ const visibleRoadmapItems = roadmapItems.length > 0 ? roadmapItems : configuredRoadmapItems;
133
+ useEffect(() => {
134
+ if (roadmap?.enabled === false || configuredRoadmapItems.length === 0) {
135
+ setRoadmapItems([]);
136
+ return;
137
+ }
138
+ let cancelled = false;
139
+ const loadRoadmap = async () => {
140
+ setRoadmapStatus('loading');
141
+ setRoadmapError('');
142
+ try {
143
+ const response = await fetch('/api/feedback/roadmap', { cache: 'no-store' });
144
+ const payload = await response.json().catch(() => null);
145
+ if (!response.ok || !payload?.success) {
146
+ throw new Error(payload?.error || 'Failed to load roadmap ranking');
147
+ }
148
+ if (cancelled)
149
+ return;
150
+ const rankedItems = Array.isArray(payload?.data?.items) ? payload.data.items : payload?.items;
151
+ if (Array.isArray(rankedItems) && rankedItems.length > 0) {
152
+ setRoadmapItems(rankedItems.filter((item) => item?.enabled !== false && item?.id && item?.name));
153
+ }
154
+ else {
155
+ setRoadmapItems([]);
156
+ }
157
+ setRoadmapStatus('idle');
158
+ }
159
+ catch (nextError) {
160
+ if (cancelled)
161
+ return;
162
+ setRoadmapItems([]);
163
+ setRoadmapError(nextError?.message || 'Failed to load roadmap ranking');
164
+ setRoadmapStatus('error');
165
+ }
166
+ };
167
+ void loadRoadmap();
168
+ return () => {
169
+ cancelled = true;
170
+ };
171
+ }, [roadmap?.enabled, configuredRoadmapItems.map((item) => item.id).join('|')]);
14
172
  const addFiles = (fileList) => {
15
173
  const incoming = Array.from(fileList);
16
174
  const remaining = Math.max(0, FEEDBACK_IMAGE_LIMIT - images.length);
17
- const accepted = incoming.filter((file) => file.type.startsWith('image/') && file.size <= FEEDBACK_IMAGE_MAX_BYTES).slice(0, remaining);
175
+ const accepted = incoming
176
+ .filter((file) => file.type.startsWith('image/') && file.size <= FEEDBACK_IMAGE_MAX_BYTES)
177
+ .slice(0, remaining);
18
178
  if (accepted.length === 0) {
19
179
  setImageError(`Add up to ${FEEDBACK_IMAGE_LIMIT} images under ${Math.round(FEEDBACK_IMAGE_MAX_BYTES / 1024 / 1024)}MB each.`);
20
180
  return;
@@ -30,6 +190,55 @@ export default function FeedbackPanel({ toolId = '', page = '', title = 'feedbac
30
190
  ]);
31
191
  };
32
192
  const removeFile = (id) => setImages((prev) => prev.filter((item) => item.id !== id));
193
+ const voteForRoadmapItem = async (featureId) => {
194
+ setRoadmapStatus('voting');
195
+ setRoadmapError('');
196
+ try {
197
+ const submissionId = (typeof crypto !== 'undefined' && 'randomUUID' in crypto && crypto.randomUUID()) ||
198
+ `${Date.now()}-${Math.random().toString(16).slice(2, 10)}`;
199
+ let telemetrySessionId = '';
200
+ try {
201
+ telemetrySessionId = window.sessionStorage.getItem(TELEMETRY_SESSION_STORAGE_KEY) || '';
202
+ }
203
+ catch {
204
+ telemetrySessionId = '';
205
+ }
206
+ const response = await fetch('/api/feedback/roadmap', {
207
+ method: 'POST',
208
+ headers: {
209
+ 'Content-Type': 'application/json',
210
+ 'x-submission-id': submissionId,
211
+ },
212
+ body: JSON.stringify({
213
+ featureId,
214
+ submissionId,
215
+ telemetrySessionId,
216
+ toolId,
217
+ page,
218
+ }),
219
+ });
220
+ const payload = await response.json().catch(() => null);
221
+ if (!response.ok || !payload?.success) {
222
+ throw new Error(payload?.error || 'Failed to record roadmap vote');
223
+ }
224
+ setVotedFeatureIds((current) => {
225
+ const next = new Set(current);
226
+ next.add(featureId);
227
+ return next;
228
+ });
229
+ setRoadmapStatus('idle');
230
+ const reload = await fetch('/api/feedback/roadmap', { cache: 'no-store' });
231
+ const nextPayload = await reload.json().catch(() => null);
232
+ const rankedItems = Array.isArray(nextPayload?.data?.items) ? nextPayload.data.items : nextPayload?.items;
233
+ if (reload.ok && Array.isArray(rankedItems)) {
234
+ setRoadmapItems(rankedItems.filter((item) => item?.enabled !== false && item?.id && item?.name));
235
+ }
236
+ }
237
+ catch (nextError) {
238
+ setRoadmapStatus('error');
239
+ setRoadmapError(nextError?.message || 'Failed to record roadmap vote');
240
+ }
241
+ };
33
242
  const submit = async () => {
34
243
  const trimmed = message.trim();
35
244
  if (!trimmed) {
@@ -41,13 +250,29 @@ export default function FeedbackPanel({ toolId = '', page = '', title = 'feedbac
41
250
  setError('');
42
251
  try {
43
252
  const form = new FormData();
253
+ const submissionId = (typeof crypto !== 'undefined' && 'randomUUID' in crypto && crypto.randomUUID()) ||
254
+ `${Date.now()}-${Math.random().toString(16).slice(2, 10)}`;
44
255
  form.set('message', trimmed);
256
+ form.set('submissionId', submissionId);
257
+ form.set('category', 'design-type-feature-feedback');
45
258
  if (toolId)
46
259
  form.set('toolId', toolId);
47
260
  if (page)
48
261
  form.set('page', page);
262
+ try {
263
+ const telemetrySessionId = window.sessionStorage.getItem(TELEMETRY_SESSION_STORAGE_KEY);
264
+ if (telemetrySessionId)
265
+ form.set('telemetrySessionId', telemetrySessionId);
266
+ }
267
+ catch {
268
+ // Ignore storage failures; the server still writes feedback.
269
+ }
49
270
  images.forEach((item) => form.append('images', item.file));
50
- const response = await fetch('/api/feedback', { method: 'POST', body: form });
271
+ const response = await fetch('/api/feedback', {
272
+ method: 'POST',
273
+ body: form,
274
+ headers: { 'x-submission-id': submissionId },
275
+ });
51
276
  const payload = await response.json().catch(() => null);
52
277
  if (!response.ok || !payload?.success)
53
278
  throw new Error(payload?.error || 'Failed to send feedback');
@@ -60,26 +285,27 @@ export default function FeedbackPanel({ toolId = '', page = '', title = 'feedbac
60
285
  setError(nextError?.message || 'Failed to send feedback');
61
286
  }
62
287
  };
63
- return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { children: [_jsx("div", { className: "text-[16px] font-semibold tracking-[0.01em] text-[var(--viewer-ui-color-text-strong)]", children: title }), _jsx("p", { className: "mt-1 text-[12px] text-[var(--viewer-ui-color-text-muted)] leading-relaxed", children: description })] }), _jsx("textarea", { value: message, onChange: (event) => {
64
- setMessage(event.target.value);
65
- if (status !== 'idle') {
66
- setStatus('idle');
67
- setError('');
68
- }
69
- }, rows: 6, placeholder: "Share feedback, feature requests, or issues.", className: "w-full rounded-xl border border-[var(--viewer-ui-color-button-secondary)] bg-[var(--viewer-ui-color-panel-bg)] text-[var(--viewer-ui-color-text-strong)] px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--viewer-ui-color-focus)]" }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "text-xs text-[var(--viewer-ui-color-text-muted)] normal-case", children: ["Add up to ", FEEDBACK_IMAGE_LIMIT, " screenshots (max ", Math.round(FEEDBACK_IMAGE_MAX_BYTES / 1024 / 1024), "MB each)."] }), _jsxs("div", { onDragOver: (event) => {
70
- event.preventDefault();
71
- setDragActive(true);
72
- }, onDragLeave: () => setDragActive(false), onDrop: (event) => {
73
- event.preventDefault();
74
- setDragActive(false);
75
- if (event.dataTransfer?.files?.length)
76
- addFiles(event.dataTransfer.files);
77
- }, className: `rounded-xl border border-dashed px-4 py-4 text-center text-sm transition ${dragActive
78
- ? 'border-[var(--viewer-ui-color-button-primary)] bg-[var(--viewer-ui-color-button-primary)]/10 text-[var(--viewer-ui-color-text-strong)]'
79
- : 'border-[var(--viewer-ui-color-button-secondary)] text-[var(--viewer-ui-color-text-muted)]'}`, children: [_jsx("input", { ref: inputRef, type: "file", accept: "image/*", multiple: true, className: "hidden", onChange: (event) => {
80
- if (event.target.files?.length) {
81
- addFiles(event.target.files);
82
- event.target.value = '';
288
+ return (_jsxs(Root, { children: [_jsxs(Section, { "data-panel-id": "feedback-request", children: [_jsx(SectionHeader, { children: _jsxs(SectionHeaderCopy, { children: [_jsx("h3", { children: title }), _jsx("p", { children: description })] }) }), _jsxs(SectionFields, { children: [_jsx(Textarea, { value: message, onChange: (event) => {
289
+ setMessage(event.target.value);
290
+ if (status !== 'idle') {
291
+ setStatus('idle');
292
+ setError('');
83
293
  }
84
- } }), _jsx("button", { type: "button", onClick: () => inputRef.current?.click(), className: "text-[12px] font-semibold uppercase tracking-[0.08em] text-[var(--viewer-ui-color-text-strong)]", children: "drop screenshots here or click to upload" })] }), imageError ? _jsx("div", { className: "text-xs text-[var(--viewer-ui-color-button-primary)]", children: imageError }) : null, images.length > 0 ? (_jsx("div", { className: "space-y-1", children: images.map((item) => (_jsxs("div", { className: "flex items-center justify-between rounded-lg border viewer-ui-kit-border-subtle viewer-ui-kit-surface px-3 py-2 text-xs viewer-ui-kit-text-muted", children: [_jsx("span", { className: "truncate", children: item.file.name }), _jsx("button", { type: "button", onClick: () => removeFile(item.id), className: "text-[11px] uppercase tracking-[0.2em] text-[var(--viewer-ui-color-button-primary)] hover:text-[var(--viewer-ui-color-button-secondary)]", children: "remove" })] }, item.id))) })) : null] }), status === 'error' ? _jsx("div", { className: "text-xs text-[var(--viewer-ui-color-button-primary)]", children: error || 'Failed to send feedback.' }) : null, status === 'success' ? _jsx("div", { className: "text-xs text-[var(--viewer-ui-color-button-secondary)]", children: "Thanks \u2014 we received your feedback." }) : null, _jsx("button", { type: "button", onClick: () => void submit(), disabled: status === 'sending', className: "w-full rounded-full px-4 py-2.5 text-[12px] font-semibold uppercase tracking-[0.08em] bg-[var(--viewer-ui-color-button-secondary)] text-[var(--viewer-ui-color-text-strong)] hover:bg-[var(--viewer-ui-color-button-primary)] disabled:opacity-60 disabled:cursor-not-allowed transition-colors", children: status === 'sending' ? 'sending…' : 'send feedback' })] }));
294
+ }, rows: 6, placeholder: "Ask for a new design type, plugin, workflow, feature, or fix. Include what you want to make and what output should change." }), _jsxs("div", { children: [_jsxs(Note, { children: ["Add up to ", FEEDBACK_IMAGE_LIMIT, " screenshots (max ", Math.round(FEEDBACK_IMAGE_MAX_BYTES / 1024 / 1024), "MB each)."] }), _jsxs(DropZone, { "$dragActive": dragActive, onDragOver: (event) => {
295
+ event.preventDefault();
296
+ setDragActive(true);
297
+ }, onDragLeave: () => setDragActive(false), onDrop: (event) => {
298
+ event.preventDefault();
299
+ setDragActive(false);
300
+ if (event.dataTransfer?.files?.length)
301
+ addFiles(event.dataTransfer.files);
302
+ }, children: [_jsx(HiddenFileInput, { ref: inputRef, type: "file", accept: "image/*", multiple: true, onChange: (event) => {
303
+ if (event.target.files?.length) {
304
+ addFiles(event.target.files);
305
+ event.target.value = '';
306
+ }
307
+ } }), _jsx(DropButton, { type: "button", onClick: () => inputRef.current?.click(), children: "drop screenshots here or click to upload" })] }), imageError ? _jsx(Status, { "$tone": "error", style: { marginTop: '8px' }, children: imageError }) : null, images.length > 0 ? (_jsx(FileList, { children: images.map((item) => (_jsxs(FileRow, { children: [_jsx("span", { className: "truncate", children: item.file.name }), _jsx(RemoveButton, { type: "button", onClick: () => removeFile(item.id), children: "remove" })] }, item.id))) })) : null] }), status === 'error' ? _jsx(Status, { "$tone": "error", children: error || 'Failed to send feedback.' }) : null, status === 'success' ? _jsx(Status, { "$tone": "success", children: "Thanks \u2014 we received your feedback." }) : null, _jsx(SubmitButton, { type: "button", onClick: () => void submit(), disabled: status === 'sending', children: status === 'sending' ? 'sending...' : 'send request' })] })] }), roadmap?.enabled !== false && visibleRoadmapItems.length > 0 ? (_jsxs(RoadmapSection, { "data-panel-id": "feedback-roadmap", children: [_jsx(SectionHeader, { children: _jsxs(SectionHeaderCopy, { children: [_jsx("h3", { children: roadmap?.title || 'roadmap priorities' }), roadmap?.description ? _jsx("p", { children: roadmap.description }) : null] }) }), roadmapStatus === 'error' && roadmapError ? _jsx(Status, { "$tone": "error", children: roadmapError }) : null, _jsx(RoadmapList, { children: visibleRoadmapItems.map((item) => {
308
+ const voted = votedFeatureIds.has(item.id);
309
+ return (_jsxs(RoadmapItem, { children: [_jsxs(RoadmapItemHeader, { children: [_jsxs("div", { children: [_jsx(RoadmapItemTitle, { children: item.name }), item.description ? _jsx(RoadmapItemDescription, { children: item.description }) : null] }), _jsx(VoteButton, { type: "button", onClick: () => void voteForRoadmapItem(item.id), disabled: roadmapStatus === 'voting' || voted, children: voted ? 'noted' : 'vote' })] }), item.category || item.status ? (_jsxs(RoadmapMeta, { children: [item.category ? _jsx(RoadmapTag, { children: item.category }) : null, item.status ? _jsx(RoadmapTag, { children: item.status }) : null] })) : null] }, item.id));
310
+ }) })] })) : null] }));
85
311
  }
@@ -1,3 +1,4 @@
1
+ import { type ReactNode } from 'react';
1
2
  type ActionConfig = {
2
3
  label: string;
3
4
  onClick?: () => void;
@@ -11,6 +12,13 @@ type EditableTitleConfig = {
11
12
  onSave: () => void;
12
13
  status?: 'idle' | 'saving' | 'saved' | 'error';
13
14
  };
15
+ export type FullscreenDetailPanelConfig = {
16
+ id: string;
17
+ label: string;
18
+ description?: string;
19
+ content: ReactNode;
20
+ defaultOpen?: boolean;
21
+ };
14
22
  type FullscreenImageProps = {
15
23
  open: boolean;
16
24
  src: string;
@@ -26,8 +34,11 @@ type FullscreenImageProps = {
26
34
  onClose: () => void;
27
35
  primaryAction?: ActionConfig | null;
28
36
  secondaryAction?: ActionConfig | null;
37
+ extraActions?: ActionConfig[];
29
38
  editableTitle?: EditableTitleConfig | null;
39
+ detailPanels?: FullscreenDetailPanelConfig[];
40
+ statusText?: string | null;
30
41
  };
31
- export default function FullscreenImage({ open, src, alt, title, subtitle, modeLabel, prompt, promptManifest, createdAt, publicUrl, copyUrl, onClose, primaryAction, secondaryAction, editableTitle, }: FullscreenImageProps): import("react/jsx-runtime").JSX.Element | null;
42
+ export default function FullscreenImage({ open, src, alt, title, subtitle, modeLabel, createdAt, publicUrl, copyUrl, onClose, primaryAction, secondaryAction, extraActions, editableTitle, detailPanels, statusText, }: FullscreenImageProps): import("react/jsx-runtime").JSX.Element | null;
32
43
  export {};
33
44
  //# sourceMappingURL=FullscreenImage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"FullscreenImage.d.ts","sourceRoot":"","sources":["../src/FullscreenImage.tsx"],"names":[],"mappings":"AAIA,KAAK,YAAY,GAAG;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpE,OAAO,CAAC,EAAE,SAAS,GAAG,WAAW,CAAC;CACnC,CAAC;AAEF,KAAK,mBAAmB,GAAG;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;CAChD,CAAC;AAEF,KAAK,oBAAoB,GAAG;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,aAAa,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IACpC,eAAe,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IACtC,aAAa,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;CAC5C,CAAC;AASF,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EACtC,IAAI,EACJ,GAAG,EACH,GAAa,EACb,KAAK,EACL,QAAQ,EACR,SAAS,EACT,MAAM,EACN,cAAc,EACd,SAAS,EACT,SAAS,EACT,OAAO,EACP,OAAO,EACP,aAAa,EACb,eAAe,EACf,aAAa,GACd,EAAE,oBAAoB,kDAoItB"}
1
+ {"version":3,"file":"FullscreenImage.d.ts","sourceRoot":"","sources":["../src/FullscreenImage.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAgC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGrE,KAAK,YAAY,GAAG;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpE,OAAO,CAAC,EAAE,SAAS,GAAG,WAAW,CAAC;CACnC,CAAC;AAEF,KAAK,mBAAmB,GAAG;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;CAChD,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,SAAS,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,KAAK,oBAAoB,GAAG;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,aAAa,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IACpC,eAAe,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IACtC,YAAY,CAAC,EAAE,YAAY,EAAE,CAAC;IAC9B,aAAa,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC3C,YAAY,CAAC,EAAE,2BAA2B,EAAE,CAAC;IAC7C,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC;AAoRF,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EACtC,IAAI,EACJ,GAAG,EACH,GAAa,EACb,KAAK,EACL,QAAQ,EACR,SAAS,EACT,SAAS,EACT,SAAS,EACT,OAAO,EACP,OAAO,EACP,aAAa,EACb,eAAe,EACf,YAAiB,EACjB,aAAa,EACb,YAAiB,EACjB,UAAU,GACX,EAAE,oBAAoB,kDAiNtB"}