@treasuryspatial/prompt-ui-kit 0.1.15 → 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 +204 -95
  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 +55 -55
  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 +3 -3
@@ -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":"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;CACtB,CAAC;AAwIF,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,EACpC,MAAW,EACX,IAAS,EACT,KAAkB,EAClB,WAA+C,GAChD,EAAE,kBAAkB,2CAuIpB"}
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,64 +1,40 @@
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
4
  import styled from 'styled-components';
5
5
  const FEEDBACK_IMAGE_LIMIT = 3;
6
6
  const FEEDBACK_IMAGE_MAX_BYTES = 5 * 1024 * 1024;
7
- const Root = styled.div `
8
- display: flex;
9
- flex-direction: column;
10
- gap: 16px;
11
- `;
12
- const Header = styled.div `
13
- display: flex;
14
- flex-direction: column;
15
- gap: 6px;
16
- `;
17
- const Title = styled.div `
18
- font-size: 17px;
19
- font-weight: 600;
20
- letter-spacing: 0.01em;
21
- color: var(--brand-text-primary);
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));
22
10
  `;
23
- const Description = styled.p `
24
- font-size: 13px;
25
- line-height: 1.6;
26
- color: var(--brand-text-secondary);
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;
27
15
  `;
28
- const Textarea = styled.textarea `
16
+ const SectionFields = styled.div.attrs({ className: 'panel-section-fields' }) ``;
17
+ const Textarea = styled.textarea.attrs({ className: 'cfg-textarea' }) `
29
18
  width: 100%;
30
- min-height: 170px;
19
+ min-height: 136px;
31
20
  resize: vertical;
32
- border-radius: 24px;
33
- border: 1px solid var(--brand-text-primary);
34
- background: var(--brand-background);
35
- padding: 18px 20px;
36
- font-size: 13px;
37
- line-height: 1.6;
38
- color: var(--brand-text-primary);
39
- outline: none;
21
+ font-family: var(--viewer-ui-font-body, var(--ui-font-family, 'Aenik', 'Aeonik', system-ui, sans-serif));
40
22
 
41
23
  &::placeholder {
24
+ font-family: var(--viewer-ui-font-body, var(--ui-font-family, 'Aenik', 'Aeonik', system-ui, sans-serif));
25
+ letter-spacing: 0;
42
26
  color: color-mix(in srgb, var(--brand-text-secondary) 72%, white 28%);
43
27
  }
44
-
45
- &:focus {
46
- border-color: var(--brand-primary);
47
- }
48
- `;
49
- const Note = styled.div `
50
- font-size: 12px;
51
- line-height: 1.5;
52
- color: var(--brand-text-secondary);
53
28
  `;
29
+ const Note = styled.div.attrs({ className: 'panel-section-note' }) ``;
54
30
  const DropZone = styled.div `
55
- margin-top: 10px;
56
- border-radius: 24px;
57
- border: 2px dashed ${(props) => (props.$dragActive ? 'var(--brand-primary)' : 'var(--brand-text-primary)')};
31
+ margin-top: 8px;
32
+ border-radius: 18px;
33
+ border: 1px dashed ${(props) => (props.$dragActive ? 'var(--brand-primary)' : 'var(--brand-panel-border)')};
58
34
  background: ${(props) => props.$dragActive
59
35
  ? 'color-mix(in srgb, var(--brand-primary) 10%, var(--brand-background) 90%)'
60
36
  : 'var(--brand-background)'};
61
- padding: 28px 20px;
37
+ padding: 18px 14px;
62
38
  text-align: center;
63
39
  transition: border-color 0.16s ease, background-color 0.16s ease;
64
40
  `;
@@ -67,26 +43,28 @@ const DropButton = styled.button `
67
43
  background: transparent;
68
44
  color: var(--brand-text-primary);
69
45
  cursor: pointer;
70
- font-size: 13px;
46
+ font-family: inherit;
47
+ font-size: 11px;
71
48
  font-weight: 600;
72
- letter-spacing: 0.01em;
49
+ letter-spacing: 0.08em;
50
+ text-transform: var(--viewer-ui-action-text-case, lowercase);
73
51
  `;
74
- const FileList = styled.div `
75
- display: flex;
76
- flex-direction: column;
77
- gap: 8px;
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' }) `
78
64
  margin-top: 10px;
79
65
  `;
80
- const FileRow = styled.div `
81
- display: flex;
82
- align-items: center;
83
- justify-content: space-between;
84
- gap: 12px;
85
- border-radius: 18px;
86
- border: 1px solid var(--brand-panel-border);
87
- background: var(--brand-background);
88
- padding: 12px 14px;
89
- font-size: 12px;
66
+ const FileRow = styled.div.attrs({ className: 'panel-section-row' }) `
67
+ font-size: 11px;
90
68
  color: var(--brand-text-secondary);
91
69
  `;
92
70
  const RemoveButton = styled.button `
@@ -102,32 +80,95 @@ const Status = styled.div `
102
80
  font-size: 12px;
103
81
  color: ${(props) => props.$tone === 'success' ? 'var(--brand-text-primary)' : 'var(--viewer-ui-color-text-error, #b91c1c)'};
104
82
  `;
105
- const SubmitButton = styled.button `
83
+ const SubmitButton = styled.button.attrs({ className: 'cfg-button cfg-button--primary' }) `
106
84
  width: 100%;
107
- min-height: 56px;
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 `
108
100
  border-radius: 999px;
109
- border: 0;
110
- background: var(--brand-text-primary);
111
- color: var(--brand-background);
112
- cursor: pointer;
113
- font-size: 13px;
114
- font-weight: 600;
115
- letter-spacing: 0.01em;
116
- transition: opacity 0.16s ease;
117
-
118
- &:disabled {
119
- opacity: 0.5;
120
- cursor: not-allowed;
121
- }
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;
122
108
  `;
123
- export default function FeedbackPanel({ toolId = '', page = '', title = 'feedback', description = 'Share bugs, requests, or notes.', }) {
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, }) {
124
114
  const [message, setMessage] = useState('');
125
115
  const [status, setStatus] = useState('idle');
126
116
  const [error, setError] = useState('');
127
117
  const [images, setImages] = useState([]);
128
118
  const [imageError, setImageError] = useState('');
129
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());
130
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('|')]);
131
172
  const addFiles = (fileList) => {
132
173
  const incoming = Array.from(fileList);
133
174
  const remaining = Math.max(0, FEEDBACK_IMAGE_LIMIT - images.length);
@@ -149,6 +190,55 @@ export default function FeedbackPanel({ toolId = '', page = '', title = 'feedbac
149
190
  ]);
150
191
  };
151
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
+ };
152
242
  const submit = async () => {
153
243
  const trimmed = message.trim();
154
244
  if (!trimmed) {
@@ -160,13 +250,29 @@ export default function FeedbackPanel({ toolId = '', page = '', title = 'feedbac
160
250
  setError('');
161
251
  try {
162
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)}`;
163
255
  form.set('message', trimmed);
256
+ form.set('submissionId', submissionId);
257
+ form.set('category', 'design-type-feature-feedback');
164
258
  if (toolId)
165
259
  form.set('toolId', toolId);
166
260
  if (page)
167
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
+ }
168
270
  images.forEach((item) => form.append('images', item.file));
169
- 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
+ });
170
276
  const payload = await response.json().catch(() => null);
171
277
  if (!response.ok || !payload?.success)
172
278
  throw new Error(payload?.error || 'Failed to send feedback');
@@ -179,24 +285,27 @@ export default function FeedbackPanel({ toolId = '', page = '', title = 'feedbac
179
285
  setError(nextError?.message || 'Failed to send feedback');
180
286
  }
181
287
  };
182
- return (_jsxs(Root, { children: [_jsxs(Header, { children: [_jsx(Title, { children: title }), _jsx(Description, { children: description })] }), _jsx(Textarea, { value: message, onChange: (event) => {
183
- setMessage(event.target.value);
184
- if (status !== 'idle') {
185
- setStatus('idle');
186
- setError('');
187
- }
188
- }, rows: 6, placeholder: "Share feedback, feature requests, or issues." }), _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) => {
189
- event.preventDefault();
190
- setDragActive(true);
191
- }, onDragLeave: () => setDragActive(false), onDrop: (event) => {
192
- event.preventDefault();
193
- setDragActive(false);
194
- if (event.dataTransfer?.files?.length)
195
- addFiles(event.dataTransfer.files);
196
- }, children: [_jsx("input", { ref: inputRef, type: "file", accept: "image/*", multiple: true, className: "hidden", onChange: (event) => {
197
- if (event.target.files?.length) {
198
- addFiles(event.target.files);
199
- 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('');
200
293
  }
201
- } }), _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 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] }));
202
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"}