@agent-native/core 0.148.1 → 0.149.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/changelog/2026-08-10-wide-tables-in-analytics-chat-now-scroll-horizontally-within.md +6 -0
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +13 -53
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +11 -70
- package/corpus/templates/calendar/app/lib/event-layout.ts +122 -0
- package/corpus/templates/calendar/app/routes/_app.availability.tsx +1 -1
- package/corpus/templates/calendar/changelog/2026-08-10-opening-availability-settings-from-the-agent-lands-on-the-av.md +6 -0
- package/corpus/templates/calendar/vitest.config.ts +6 -1
- package/corpus/templates/clips/actions/lib/audio-only-transcription.ts +3 -1
- package/corpus/templates/clips/app/components/player/video-player.tsx +44 -4
- package/corpus/templates/clips/changelog/2026-08-10-fixed-transcription-failures-for-recordings-without-captured.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-10-mobile-clips-can-now-enter-fullscreen-on-iphone-safari.md +6 -0
- package/corpus/templates/content/actions/_property-utils.ts +11 -1
- package/corpus/templates/content/actions/view-screen.ts +62 -12
- package/corpus/templates/content/server/plugins/agent-chat.ts +2 -0
- package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +22 -0
- package/corpus/templates/design/AGENTS.md +3 -0
- package/corpus/templates/design/actions/create-design-from-template.ts +19 -0
- package/corpus/templates/design/actions/get-design-snapshot.ts +18 -0
- package/corpus/templates/design/actions/get-design-template.ts +178 -0
- package/corpus/templates/design/actions/list-designs.ts +3 -1
- package/corpus/templates/design/actions/view-screen.ts +31 -0
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +225 -214
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +3 -0
- package/corpus/templates/design/app/i18n-data.ts +78 -0
- package/corpus/templates/design/app/lib/design-authors.ts +64 -0
- package/corpus/templates/design/app/pages/Index.tsx +114 -4
- package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +1 -0
- package/corpus/templates/design/changelog/2026-08-07-alt-dragging-an-element-to-copy-it-now-keeps-the-copy-visibl.md +6 -0
- package/corpus/templates/design/changelog/2026-08-07-design-cards-now-show-who-created-each-design-with-a-filter-.md +6 -0
- package/corpus/templates/design/changelog/2026-08-07-drawing-a-shape-on-the-overview-canvas-now-keeps-the-shape-s.md +6 -0
- package/corpus/templates/design/changelog/2026-08-07-hold-option-alt-while-dragging-a-frame-on-the-overview-canva.md +6 -0
- package/corpus/templates/design/server/lib/design-template-data.ts +143 -0
- package/corpus/templates/design/server/plugins/agent-chat.ts +1 -0
- package/corpus/templates/forms/AGENTS.md +2 -2
- package/corpus/templates/forms/actions/create-form.ts +1 -1
- package/corpus/templates/forms/actions/update-form.ts +5 -5
- package/corpus/templates/forms/changelog/2026-08-10-forms-now-keep-agent-applied-field-changes-visible-after-an-.md +6 -0
- package/corpus/templates/forms/changelog/2026-08-10-published-form-links-always-use-the-working-public-form-url.md +6 -0
- package/corpus/templates/forms/server/plugins/agent-chat.ts +2 -2
- package/corpus/templates/slides/AGENTS.md +4 -3
- package/corpus/templates/slides/actions/import-google-slides-reference.ts +56 -10
- package/corpus/templates/slides/actions/patch-deck.ts +18 -2
- package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +46 -0
- package/corpus/templates/slides/app/components/editor/BlockBubbleMenu.tsx +194 -3
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +22 -75
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +5 -3
- package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +5 -1
- package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +15 -4
- package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +116 -55
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +27 -4
- package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +45 -0
- package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +143 -18
- package/corpus/templates/slides/app/i18n/en-US.ts +6 -0
- package/corpus/templates/slides/app/lib/export-pdf-client.ts +84 -25
- package/corpus/templates/slides/app/lib/generation-state.ts +7 -1
- package/corpus/templates/slides/app/lib/import-uploaded-deck.ts +11 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +44 -14
- package/corpus/templates/slides/app/pages/Index.tsx +124 -135
- package/corpus/templates/slides/changelog/2026-08-08-google-slides-export-now-tells-you-why-a-deck-fell-back-to-a.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-delete-key-now-removes-a-selected-image.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-editing-a-slide-in-an-imported-deck-no-longer-fails-to-save.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-fixed-pdf-and-custom-size-powerpoint-imports-rendering-with-.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-images-from-sites-without-cors-no-longer-export-blank.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-imported-pdf-headings-no-longer-lose-the-space-between-color.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-imported-slide-text-no-longer-renders-too-large-for-its-box.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-large-decks-no-longer-render-slowly-or-glitch-in-the-editor.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-revise-highlighted-slide-text-with-ai.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-the-deck-editor-no-longer-stays-dimmed-after-the-window-narro.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-imported-reference-decks-now-show-clear-success-and-stay-sel.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-pasted-google-slides-links-import-after-reconnecting-google-.md +6 -0
- package/corpus/templates/slides/server/handlers/google-docs-auth.ts +4 -0
- package/corpus/templates/slides/server/handlers/import/html-converter.ts +115 -22
- package/corpus/templates/slides/server/handlers/import/pdf-fidelity-parser.ts +11 -2
- package/corpus/templates/slides/server/lib/fetch-remote-image.ts +192 -0
- package/corpus/templates/slides/server/lib/google-docs-oauth.ts +13 -0
- package/corpus/templates/slides/server/lib/image-proxy-url.ts +192 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/slides/server/routes/api/image-proxy.get.ts +65 -0
- package/dist/agent/engine/registry.d.ts +8 -2
- package/dist/agent/engine/registry.js +63 -29
- package/dist/agent/production-agent.js +7 -0
- package/dist/agent/run-manager.d.ts +5 -4
- package/dist/agent/run-manager.js +6 -5
- package/dist/cli/claude-code-participant.d.ts +2 -1
- package/dist/cli/claude-code-participant.js +19 -0
- package/dist/cli/code-agent-executor.js +223 -0
- package/dist/cli/create-workspace.js +3 -3
- package/dist/cli/create.d.ts +3 -2
- package/dist/cli/create.js +20 -24
- package/dist/client/AgentPanel.js +8 -4
- package/dist/client/AssistantChat.js +34 -1
- package/dist/client/agent-chat-adapter.d.ts +1 -0
- package/dist/client/agent-chat-adapter.js +10 -2
- package/dist/client/chat/markdown-renderer.d.ts +1 -0
- package/dist/client/chat/markdown-renderer.js +4 -0
- package/dist/client/chat/message-components.d.ts +1 -0
- package/dist/client/chat/message-components.js +9 -1
- package/dist/client/chat/tool-call-display.d.ts +8 -0
- package/dist/client/chat/tool-call-display.js +30 -21
- package/dist/client/chat-first/app-pane.js +1 -1
- package/dist/client/chat-first/apps-rail.d.ts +1 -1
- package/dist/client/chat-first/apps-rail.js +11 -11
- package/dist/client/chat-first/chat-history.d.ts +1 -1
- package/dist/client/chat-first/chat-history.js +2 -0
- package/dist/client/chat-first/primary-nav.js +1 -1
- package/dist/client/progress/RunsTray.js +14 -1
- package/dist/client/resources/McpConnectionSuggestion.js +2 -2
- package/dist/client/settings/SettingsTabsPage.d.ts +3 -1
- package/dist/client/settings/SettingsTabsPage.js +2 -2
- package/dist/client/sse-event-processor.d.ts +8 -0
- package/dist/client/sse-event-processor.js +13 -6
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/deploy/build.d.ts +5 -7
- package/dist/deploy/build.js +19 -12
- package/dist/localization/default-messages.js +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +6 -6
- package/dist/provider-api/actions/provider-api.d.ts +2 -2
- package/dist/scripts/call-agent.js +3 -3
- package/dist/scripts/describe-workspace-apps.js +1 -1
- package/dist/server/agent-capabilities.js +0 -0
- package/dist/server/agent-chat/context-tools.js +1 -1
- package/dist/server/agent-chat/prompt-resources.js +1 -1
- package/dist/server/agent-chat-plugin.js +1 -1
- package/dist/server/auth.js +190 -42
- package/dist/server/better-auth-migrations.js +53 -0
- package/dist/server/http-response-telemetry.js +7 -1
- package/dist/server/onboarding-html.js +119 -33
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/password-policy.d.ts +2 -1
- package/dist/shared/password-policy.js +2 -1
- package/dist/styles/agent-conversation.css +20 -3
- package/dist/styles/agent-native.css +14 -2
- package/dist/templates/default/app/i18n/ar-SA.ts +1 -1
- package/dist/templates/default/app/i18n/de-DE.ts +1 -1
- package/dist/templates/default/app/i18n/en-US.ts +1 -1
- package/dist/templates/default/app/i18n/es-ES.ts +1 -1
- package/dist/templates/default/app/i18n/fr-FR.ts +1 -1
- package/dist/templates/default/app/i18n/hi-IN.ts +1 -1
- package/dist/templates/default/app/i18n/ja-JP.ts +1 -1
- package/dist/templates/default/app/i18n/ko-KR.ts +1 -1
- package/dist/templates/default/app/i18n/pt-BR.ts +1 -1
- package/dist/templates/default/app/i18n/zh-CN.ts +1 -1
- package/dist/templates/default/app/i18n/zh-TW.ts +1 -1
- package/dist/templates/default/pnpm-workspace.yaml +1 -0
- package/dist/templates/workspace-root/pnpm-workspace.yaml +2 -0
- package/docs/content/cloneable-saas.mdx +6 -6
- package/docs/content/deployment.mdx +10 -8
- package/docs/content/environment-variables.mdx +4 -3
- package/package.json +2 -2
- package/src/templates/default/app/i18n/ar-SA.ts +1 -1
- package/src/templates/default/app/i18n/de-DE.ts +1 -1
- package/src/templates/default/app/i18n/en-US.ts +1 -1
- package/src/templates/default/app/i18n/es-ES.ts +1 -1
- package/src/templates/default/app/i18n/fr-FR.ts +1 -1
- package/src/templates/default/app/i18n/hi-IN.ts +1 -1
- package/src/templates/default/app/i18n/ja-JP.ts +1 -1
- package/src/templates/default/app/i18n/ko-KR.ts +1 -1
- package/src/templates/default/app/i18n/pt-BR.ts +1 -1
- package/src/templates/default/app/i18n/zh-CN.ts +1 -1
- package/src/templates/default/app/i18n/zh-TW.ts +1 -1
- package/src/templates/default/pnpm-workspace.yaml +1 -0
- package/src/templates/workspace-root/pnpm-workspace.yaml +2 -0
|
@@ -17,12 +17,6 @@ import {
|
|
|
17
17
|
import { CSS } from "@dnd-kit/utilities";
|
|
18
18
|
import { appStateKeyForBrowserTab } from "@shared/app-state-tabs";
|
|
19
19
|
import { hashSlideContent, type DeckFitState } from "@shared/slide-fit";
|
|
20
|
-
import {
|
|
21
|
-
IconGripVertical,
|
|
22
|
-
IconCopy,
|
|
23
|
-
IconTrash,
|
|
24
|
-
IconLoader2,
|
|
25
|
-
} from "@tabler/icons-react";
|
|
26
20
|
import { useRef, useEffect } from "react";
|
|
27
21
|
import { useCallback } from "react";
|
|
28
22
|
|
|
@@ -35,7 +29,7 @@ import {
|
|
|
35
29
|
TooltipTrigger,
|
|
36
30
|
} from "@/components/ui/tooltip";
|
|
37
31
|
import type { Slide } from "@/context/DeckContext";
|
|
38
|
-
import type
|
|
32
|
+
import { getAspectRatioDims, type AspectRatio } from "@/lib/aspect-ratios";
|
|
39
33
|
import { TAB_ID } from "@/lib/tab-id";
|
|
40
34
|
|
|
41
35
|
import type { DesignSystemData } from "../../../shared/api";
|
|
@@ -45,8 +39,6 @@ interface EditorSidebarProps {
|
|
|
45
39
|
activeSlideId: string;
|
|
46
40
|
deckId: string;
|
|
47
41
|
onSelectSlide: (id: string) => void;
|
|
48
|
-
onDuplicateSlide: (id: string) => void;
|
|
49
|
-
onDeleteSlide: (id: string) => void;
|
|
50
42
|
/** Viewer-role decks get thumbnails only: no add, duplicate, or delete. */
|
|
51
43
|
readOnly?: boolean;
|
|
52
44
|
/** Presence map: slideId → list of users currently viewing that slide */
|
|
@@ -140,21 +132,11 @@ function PresenceAvatarTip({
|
|
|
140
132
|
);
|
|
141
133
|
}
|
|
142
134
|
|
|
143
|
-
// Thumbnail overlays sit on top of rendered slide artwork, which is arbitrary
|
|
144
|
-
// user content rather than a themed app surface, so they use a fixed scrim.
|
|
145
|
-
const THUMB_OVERLAY_CLASS =
|
|
146
|
-
// guard:allow-raw-color — matches the duplicate/delete overlays below.
|
|
147
|
-
"absolute top-2 left-7 rounded bg-black/60 p-1 backdrop-blur-sm border border-white/10 cursor-grab active:cursor-grabbing sm:opacity-0 sm:group-hover:opacity-100";
|
|
148
|
-
// guard:allow-raw-color — same fixed scrim as the class above.
|
|
149
|
-
const THUMB_OVERLAY_ICON_CLASS = "w-3 h-3 text-white/60";
|
|
150
|
-
|
|
151
135
|
function SortableSlideThumb({
|
|
152
136
|
slide,
|
|
153
137
|
index,
|
|
154
138
|
isActive,
|
|
155
139
|
onSelect,
|
|
156
|
-
onDuplicate,
|
|
157
|
-
onDelete,
|
|
158
140
|
registerButtonRef,
|
|
159
141
|
presenceUsers = [],
|
|
160
142
|
aspectRatio,
|
|
@@ -166,8 +148,6 @@ function SortableSlideThumb({
|
|
|
166
148
|
index: number;
|
|
167
149
|
isActive: boolean;
|
|
168
150
|
onSelect: () => void;
|
|
169
|
-
onDuplicate: () => void;
|
|
170
|
-
onDelete: () => void;
|
|
171
151
|
readOnly?: boolean;
|
|
172
152
|
registerButtonRef: (slideId: string, node: HTMLButtonElement | null) => void;
|
|
173
153
|
presenceUsers?: CollabUser[];
|
|
@@ -194,18 +174,25 @@ function SortableSlideThumb({
|
|
|
194
174
|
opacity: isDragging ? 0.5 : 1,
|
|
195
175
|
};
|
|
196
176
|
|
|
177
|
+
const thumbDims = getAspectRatioDims(aspectRatio);
|
|
178
|
+
|
|
197
179
|
return (
|
|
198
|
-
<div ref={setNodeRef} style={style}
|
|
180
|
+
<div ref={setNodeRef} style={style}>
|
|
199
181
|
<button
|
|
200
182
|
ref={(node) => registerButtonRef(slide.id, node)}
|
|
183
|
+
type="button"
|
|
184
|
+
{...(readOnly ? {} : attributes)}
|
|
185
|
+
{...(readOnly ? {} : listeners)}
|
|
201
186
|
onClick={onSelect}
|
|
202
187
|
onFocus={onSelect}
|
|
203
188
|
aria-label={t("editorSidebar.selectSlide", { number: index + 1 })}
|
|
204
189
|
aria-current={isActive ? "true" : undefined}
|
|
205
190
|
data-slide-thumbnail-id={slide.id}
|
|
206
191
|
className={`w-full text-left flex items-start gap-1.5 p-1.5 rounded-lg transition-[background-color,box-shadow] duration-150 ${
|
|
207
|
-
isActive ? "bg-accent ring-1 ring-
|
|
208
|
-
}
|
|
192
|
+
isActive ? "bg-accent ring-1 ring-primary/50" : ""
|
|
193
|
+
} ${
|
|
194
|
+
readOnly ? "" : "cursor-grab active:cursor-grabbing"
|
|
195
|
+
} focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1`}
|
|
209
196
|
>
|
|
210
197
|
{/* Index and slide presence share the fixed rail so presence does not resize the row. */}
|
|
211
198
|
<div className="relative flex-shrink-0 w-4 self-stretch">
|
|
@@ -228,6 +215,11 @@ function SortableSlideThumb({
|
|
|
228
215
|
|
|
229
216
|
{/* Thumbnail */}
|
|
230
217
|
<div className="flex-1 min-w-0">
|
|
218
|
+
{/* Each thumbnail paints a full-resolution slide canvas scaled down by
|
|
219
|
+
* transform, so a long rail keeps dozens of large layers live and the
|
|
220
|
+
* browser drops frames or paints stale tiles. `content-visibility`
|
|
221
|
+
* lets it skip everything below the fold; `aspect-ratio` keeps the row
|
|
222
|
+
* the right height while its contents are skipped. */}
|
|
231
223
|
<div
|
|
232
224
|
className="w-full overflow-hidden rounded border"
|
|
233
225
|
style={{
|
|
@@ -235,6 +227,8 @@ function SortableSlideThumb({
|
|
|
235
227
|
presenceUsers.length > 0
|
|
236
228
|
? presenceUsers[0].color + "66"
|
|
237
229
|
: "rgba(255,255,255,0.06)",
|
|
230
|
+
aspectRatio: `${thumbDims.width} / ${thumbDims.height}`,
|
|
231
|
+
contentVisibility: "auto",
|
|
238
232
|
}}
|
|
239
233
|
>
|
|
240
234
|
<SlideRenderer
|
|
@@ -246,50 +240,6 @@ function SortableSlideThumb({
|
|
|
246
240
|
</div>
|
|
247
241
|
</div>
|
|
248
242
|
</button>
|
|
249
|
-
|
|
250
|
-
{/* Drag handle — overlaid on the thumbnail instead of holding its own
|
|
251
|
-
* column, so the rail stays narrow. Mirrors the action buttons opposite. */}
|
|
252
|
-
{!readOnly && (
|
|
253
|
-
<div {...attributes} {...listeners} className={THUMB_OVERLAY_CLASS}>
|
|
254
|
-
<IconGripVertical className={THUMB_OVERLAY_ICON_CLASS} />
|
|
255
|
-
</div>
|
|
256
|
-
)}
|
|
257
|
-
|
|
258
|
-
{/* Actions - always visible on touch devices */}
|
|
259
|
-
{!readOnly && (
|
|
260
|
-
<div className="absolute top-2 right-2 flex gap-0.5 sm:opacity-0 sm:group-hover:opacity-100">
|
|
261
|
-
<Tooltip>
|
|
262
|
-
<TooltipTrigger asChild>
|
|
263
|
-
<button
|
|
264
|
-
onClick={(e) => {
|
|
265
|
-
e.stopPropagation();
|
|
266
|
-
onDuplicate();
|
|
267
|
-
}}
|
|
268
|
-
className="p-1.5 rounded bg-black/60 backdrop-blur-sm border border-white/10 hover:bg-black/80"
|
|
269
|
-
aria-label={t("editorSidebar.duplicateSlide")}
|
|
270
|
-
>
|
|
271
|
-
<IconCopy className="w-3 h-3 text-white/60" />
|
|
272
|
-
</button>
|
|
273
|
-
</TooltipTrigger>
|
|
274
|
-
<TooltipContent>{t("editorSidebar.duplicate")}</TooltipContent>
|
|
275
|
-
</Tooltip>
|
|
276
|
-
<Tooltip>
|
|
277
|
-
<TooltipTrigger asChild>
|
|
278
|
-
<button
|
|
279
|
-
onClick={(e) => {
|
|
280
|
-
e.stopPropagation();
|
|
281
|
-
onDelete();
|
|
282
|
-
}}
|
|
283
|
-
className="p-1.5 rounded bg-black/60 backdrop-blur-sm border border-white/10 hover:bg-red-900/80"
|
|
284
|
-
aria-label={t("editorSidebar.deleteSlide")}
|
|
285
|
-
>
|
|
286
|
-
<IconTrash className="w-3 h-3 text-white/60" />
|
|
287
|
-
</button>
|
|
288
|
-
</TooltipTrigger>
|
|
289
|
-
<TooltipContent>{t("editorSidebar.delete")}</TooltipContent>
|
|
290
|
-
</Tooltip>
|
|
291
|
-
</div>
|
|
292
|
-
)}
|
|
293
243
|
</div>
|
|
294
244
|
);
|
|
295
245
|
}
|
|
@@ -313,14 +263,14 @@ function GeneratingSlideSkeleton({
|
|
|
313
263
|
return (
|
|
314
264
|
<button
|
|
315
265
|
type="button"
|
|
316
|
-
className={`
|
|
317
|
-
selected ? "
|
|
266
|
+
className={`relative block w-full rounded-lg text-left transition-[box-shadow] ${
|
|
267
|
+
selected ? "ring-1 ring-primary" : ""
|
|
318
268
|
}`}
|
|
319
269
|
aria-label={t("editorSidebar.generatingSlide")}
|
|
320
270
|
aria-current={selected ? "true" : undefined}
|
|
321
271
|
onClick={onSelect}
|
|
322
272
|
>
|
|
323
|
-
<div className="w-full
|
|
273
|
+
<div className="flex w-full items-start gap-1.5 rounded-lg p-1.5">
|
|
324
274
|
<span className="flex-shrink-0 w-4 text-center text-[10px] font-medium leading-5 text-muted-foreground/70">
|
|
325
275
|
{index + 1}
|
|
326
276
|
</span>
|
|
@@ -342,8 +292,6 @@ export default function EditorSidebar({
|
|
|
342
292
|
activeSlideId,
|
|
343
293
|
deckId,
|
|
344
294
|
onSelectSlide,
|
|
345
|
-
onDuplicateSlide,
|
|
346
|
-
onDeleteSlide,
|
|
347
295
|
readOnly = false,
|
|
348
296
|
slidePresence,
|
|
349
297
|
recentEdits,
|
|
@@ -513,8 +461,6 @@ export default function EditorSidebar({
|
|
|
513
461
|
index={index}
|
|
514
462
|
isActive={slide.id === activeSlideId}
|
|
515
463
|
onSelect={() => onSelectSlide(slide.id)}
|
|
516
|
-
onDuplicate={() => onDuplicateSlide(slide.id)}
|
|
517
|
-
onDelete={() => onDeleteSlide(slide.id)}
|
|
518
464
|
readOnly={readOnly}
|
|
519
465
|
registerButtonRef={registerSlideButton}
|
|
520
466
|
presenceUsers={slidePresence?.get(slide.id) ?? []}
|
|
@@ -543,6 +489,7 @@ export default function EditorSidebar({
|
|
|
543
489
|
edits={recentEdits}
|
|
544
490
|
resolveRect={resolveThumbRect}
|
|
545
491
|
containerRef={thumbScrollRef}
|
|
492
|
+
outlineOnly
|
|
546
493
|
/>
|
|
547
494
|
)}
|
|
548
495
|
</div>
|
|
@@ -123,11 +123,13 @@ export function ExportMenu({
|
|
|
123
123
|
});
|
|
124
124
|
return;
|
|
125
125
|
}
|
|
126
|
-
console.warn("Google Slides upload unavailable:", result.reason);
|
|
127
126
|
if (target) target.location.href = GOOGLE_SLIDES_IMPORT_URL;
|
|
128
127
|
setGoogleSlidesImportOpen(true);
|
|
129
|
-
|
|
130
|
-
|
|
128
|
+
// The deck did not reach Drive. Saying "success" here is why users read
|
|
129
|
+
// the .pptx download as the intended result and never learn that their
|
|
130
|
+
// Google account is unconnected or that Drive rejected the upload.
|
|
131
|
+
toast.warning(t("editorExport.googleSlidesDownloaded"), {
|
|
132
|
+
description: `${result.reason} ${t("editorExport.googleSlidesImportHint")}`,
|
|
131
133
|
});
|
|
132
134
|
} catch (err) {
|
|
133
135
|
googleSlidesImportTarget.current = null;
|
|
@@ -47,7 +47,7 @@ export default function GeneratingSlidePreview({
|
|
|
47
47
|
return (
|
|
48
48
|
<div
|
|
49
49
|
className={cn(
|
|
50
|
-
"relative w-full overflow-hidden rounded-lg border border-
|
|
50
|
+
"relative w-full overflow-hidden rounded-lg border border-primary/70",
|
|
51
51
|
className,
|
|
52
52
|
)}
|
|
53
53
|
style={{ aspectRatio: cssRatio }}
|
|
@@ -65,6 +65,10 @@ export default function GeneratingSlidePreview({
|
|
|
65
65
|
) : (
|
|
66
66
|
<SlideLoadingArtwork />
|
|
67
67
|
)}
|
|
68
|
+
<div className="pointer-events-none absolute left-2 top-2 inline-flex items-center gap-1.5 rounded-full border border-primary/70 px-2 py-1 text-[11px] font-medium text-primary">
|
|
69
|
+
<span className="size-1.5 rounded-full bg-primary" aria-hidden="true" />
|
|
70
|
+
{t("raw.aiEditing")}
|
|
71
|
+
</div>
|
|
68
72
|
<div className="absolute right-2 top-2 rounded-full bg-background/80 p-1.5 text-muted-foreground shadow-sm backdrop-blur">
|
|
69
73
|
<IconLoader2 className="size-3.5 animate-spin" aria-hidden="true" />
|
|
70
74
|
</div>
|
|
@@ -13,6 +13,7 @@ import { Button } from "../ui/button";
|
|
|
13
13
|
interface GoogleDocsStatus {
|
|
14
14
|
configured: boolean;
|
|
15
15
|
connected: boolean;
|
|
16
|
+
googleSlidesUrlImportReady?: boolean;
|
|
16
17
|
error?: string;
|
|
17
18
|
message?: string;
|
|
18
19
|
}
|
|
@@ -97,6 +98,12 @@ export function GoogleDriveConnectionCta({
|
|
|
97
98
|
void refreshStatus();
|
|
98
99
|
}, [refreshStatus]);
|
|
99
100
|
|
|
101
|
+
const needsReconnect =
|
|
102
|
+
status?.connected === true && status.googleSlidesUrlImportReady === false;
|
|
103
|
+
const requiresUrlImportAccess =
|
|
104
|
+
status?.connected === true ||
|
|
105
|
+
status?.googleSlidesUrlImportReady !== undefined;
|
|
106
|
+
|
|
100
107
|
const connect = useCallback(async () => {
|
|
101
108
|
if (connecting) return;
|
|
102
109
|
setConnecting(true);
|
|
@@ -138,23 +145,27 @@ export function GoogleDriveConnectionCta({
|
|
|
138
145
|
while (Date.now() < deadline && !popup.closed) {
|
|
139
146
|
await new Promise((resolve) => window.setTimeout(resolve, 1_200));
|
|
140
147
|
const next = await refreshStatus();
|
|
141
|
-
|
|
148
|
+
const nextHasUrlImportAccess =
|
|
149
|
+
next?.googleSlidesUrlImportReady ?? !requiresUrlImportAccess;
|
|
150
|
+
if (next?.connected && nextHasUrlImportAccess) {
|
|
142
151
|
popup.close();
|
|
143
152
|
onConnected?.();
|
|
144
153
|
return;
|
|
145
154
|
}
|
|
146
155
|
}
|
|
147
156
|
const next = await refreshStatus();
|
|
148
|
-
|
|
157
|
+
const nextHasUrlImportAccess =
|
|
158
|
+
next?.googleSlidesUrlImportReady ?? !requiresUrlImportAccess;
|
|
159
|
+
if (next?.connected && nextHasUrlImportAccess) onConnected?.();
|
|
149
160
|
} catch (caught) {
|
|
150
161
|
popup?.close();
|
|
151
162
|
setError(caught instanceof Error ? caught.message : String(caught));
|
|
152
163
|
} finally {
|
|
153
164
|
setConnecting(false);
|
|
154
165
|
}
|
|
155
|
-
}, [connecting, onConnected, refreshStatus]);
|
|
166
|
+
}, [connecting, onConnected, refreshStatus, requiresUrlImportAccess]);
|
|
156
167
|
|
|
157
|
-
if (loading || status?.connected) return null;
|
|
168
|
+
if (loading || (status?.connected && !needsReconnect)) return null;
|
|
158
169
|
|
|
159
170
|
const displayStatus = status ?? { configured: false, connected: false };
|
|
160
171
|
|
|
@@ -48,6 +48,17 @@ export interface NewDeckReferenceSelection {
|
|
|
48
48
|
value: string;
|
|
49
49
|
} | null;
|
|
50
50
|
}
|
|
51
|
+
|
|
52
|
+
export type NewDeckReferenceSource = NonNullable<
|
|
53
|
+
NewDeckReferenceSelection["referenceSource"]
|
|
54
|
+
>;
|
|
55
|
+
|
|
56
|
+
export interface ImportedReference {
|
|
57
|
+
id: string;
|
|
58
|
+
title: string;
|
|
59
|
+
source: "pptx" | "pdf" | "google-slides";
|
|
60
|
+
}
|
|
61
|
+
|
|
51
62
|
interface DesignSystemOption {
|
|
52
63
|
id: string;
|
|
53
64
|
title: string;
|
|
@@ -61,9 +72,12 @@ interface NewDeckReferenceStepProps {
|
|
|
61
72
|
defaultDesignSystemId: string | null;
|
|
62
73
|
defaultReferenceDeckId: string | null;
|
|
63
74
|
recentReferences: RecentReference[];
|
|
64
|
-
onSelect: (selection: NewDeckReferenceSelection) => void
|
|
65
|
-
onImport: (files: File[]) => Promise<
|
|
66
|
-
|
|
75
|
+
onSelect: (selection: NewDeckReferenceSelection) => void | Promise<void>;
|
|
76
|
+
onImport: (files: File[]) => Promise<ImportedReference | null>;
|
|
77
|
+
onImportSource: (
|
|
78
|
+
source: NewDeckReferenceSource,
|
|
79
|
+
) => Promise<ImportedReference | null>;
|
|
80
|
+
onSkip: () => void | Promise<void>;
|
|
67
81
|
onOpenChange: (open: boolean) => void;
|
|
68
82
|
importing?: boolean;
|
|
69
83
|
title: string;
|
|
@@ -72,7 +86,6 @@ interface NewDeckReferenceStepProps {
|
|
|
72
86
|
chooseDeckLabel: string;
|
|
73
87
|
importingLabel: string;
|
|
74
88
|
skipLabel: string;
|
|
75
|
-
defaultSuffix: string;
|
|
76
89
|
starredLabel: string;
|
|
77
90
|
otherDecksLabel: string;
|
|
78
91
|
searchDecksLabel: string;
|
|
@@ -88,6 +101,7 @@ export function NewDeckReferenceStep({
|
|
|
88
101
|
recentReferences,
|
|
89
102
|
onSelect,
|
|
90
103
|
onImport,
|
|
104
|
+
onImportSource,
|
|
91
105
|
onSkip,
|
|
92
106
|
onOpenChange,
|
|
93
107
|
importing = false,
|
|
@@ -97,7 +111,6 @@ export function NewDeckReferenceStep({
|
|
|
97
111
|
chooseDeckLabel,
|
|
98
112
|
importingLabel,
|
|
99
113
|
skipLabel,
|
|
100
|
-
defaultSuffix,
|
|
101
114
|
starredLabel,
|
|
102
115
|
otherDecksLabel,
|
|
103
116
|
searchDecksLabel,
|
|
@@ -110,6 +123,8 @@ export function NewDeckReferenceStep({
|
|
|
110
123
|
const [selectedReferenceDeckId, setSelectedReferenceDeckId] = useState<
|
|
111
124
|
string | null
|
|
112
125
|
>(defaultReferenceDeckId);
|
|
126
|
+
const [importedReference, setImportedReference] =
|
|
127
|
+
useState<ImportedReference | null>(null);
|
|
113
128
|
const [selectedSource, setSelectedSource] =
|
|
114
129
|
useState<NewDeckReferenceSelection["referenceSource"]>(null);
|
|
115
130
|
const [referenceDeckSearchOpen, setReferenceDeckSearchOpen] = useState(false);
|
|
@@ -141,9 +156,6 @@ export function NewDeckReferenceStep({
|
|
|
141
156
|
);
|
|
142
157
|
const starredDecks = decks.filter((deck) => deck.starred);
|
|
143
158
|
const otherDecks = decks.filter((deck) => !deck.starred);
|
|
144
|
-
const selectedDesignSystem = selectedDesignSystemId
|
|
145
|
-
? designSystemById.get(selectedDesignSystemId)
|
|
146
|
-
: undefined;
|
|
147
159
|
const selectedReferenceDeck = selectedReferenceDeckId
|
|
148
160
|
? deckById.get(selectedReferenceDeckId)
|
|
149
161
|
: undefined;
|
|
@@ -152,6 +164,7 @@ export function NewDeckReferenceStep({
|
|
|
152
164
|
if (!open) return;
|
|
153
165
|
setSelectedDesignSystemId(defaultDesignSystemId);
|
|
154
166
|
setSelectedReferenceDeckId(defaultReferenceDeckId);
|
|
167
|
+
setImportedReference(null);
|
|
155
168
|
setSelectedSource(null);
|
|
156
169
|
setReferenceDeckSearchOpen(false);
|
|
157
170
|
}, [open, defaultDesignSystemId, defaultReferenceDeckId]);
|
|
@@ -160,17 +173,33 @@ export function NewDeckReferenceStep({
|
|
|
160
173
|
const files = Array.from(event.target.files ?? []);
|
|
161
174
|
event.target.value = "";
|
|
162
175
|
if (files.length === 0) return;
|
|
163
|
-
await onImport(files);
|
|
176
|
+
const imported = await onImport(files);
|
|
177
|
+
if (imported) applyImportedReference(imported);
|
|
164
178
|
};
|
|
165
179
|
|
|
166
|
-
const
|
|
180
|
+
const applyImportedReference = (imported: ImportedReference) => {
|
|
181
|
+
setSelectedDesignSystemId(null);
|
|
182
|
+
setSelectedReferenceDeckId(imported.id);
|
|
183
|
+
setSelectedSource(null);
|
|
184
|
+
setImportedReference(imported);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const handleContinue = async () => {
|
|
188
|
+
const trimmedSource =
|
|
189
|
+
selectedSource && selectedSource.value.trim()
|
|
190
|
+
? { ...selectedSource, value: selectedSource.value.trim() }
|
|
191
|
+
: null;
|
|
192
|
+
|
|
193
|
+
if (trimmedSource?.kind === "google-docs") {
|
|
194
|
+
const imported = await onImportSource(trimmedSource);
|
|
195
|
+
if (imported) applyImportedReference(imported);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
167
199
|
onSelect({
|
|
168
200
|
designSystemId: selectedDesignSystemId,
|
|
169
201
|
referenceDeckId: selectedReferenceDeckId,
|
|
170
|
-
referenceSource:
|
|
171
|
-
selectedSource && selectedSource.value.trim()
|
|
172
|
-
? { ...selectedSource, value: selectedSource.value.trim() }
|
|
173
|
-
: null,
|
|
202
|
+
referenceSource: trimmedSource,
|
|
174
203
|
});
|
|
175
204
|
};
|
|
176
205
|
|
|
@@ -182,23 +211,20 @@ export function NewDeckReferenceStep({
|
|
|
182
211
|
setSelectedDesignSystemId(null);
|
|
183
212
|
} else {
|
|
184
213
|
setSelectedReferenceDeckId(null);
|
|
214
|
+
setImportedReference(null);
|
|
185
215
|
}
|
|
186
216
|
};
|
|
187
217
|
const selectedSourceLabel =
|
|
188
218
|
selectedSource?.kind === "google-docs"
|
|
189
|
-
? "
|
|
219
|
+
? t("home.googleSlidesReferenceTitle")
|
|
190
220
|
: selectedSource?.kind === "website"
|
|
191
221
|
? "Website"
|
|
192
222
|
: "Figma";
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
) : (
|
|
199
|
-
<IconBrandFigma className="size-4" />
|
|
200
|
-
);
|
|
201
|
-
|
|
223
|
+
const selectedReferenceDeckTitle =
|
|
224
|
+
selectedReferenceDeck?.title ??
|
|
225
|
+
(selectedReferenceDeckId === importedReference?.id
|
|
226
|
+
? importedReference.title
|
|
227
|
+
: undefined);
|
|
202
228
|
return open ? (
|
|
203
229
|
<div
|
|
204
230
|
className="fixed inset-0 z-[200] flex min-h-screen flex-col bg-background text-foreground"
|
|
@@ -228,29 +254,6 @@ export function NewDeckReferenceStep({
|
|
|
228
254
|
</p>
|
|
229
255
|
)}
|
|
230
256
|
|
|
231
|
-
{(selectedDesignSystem || selectedSource) && (
|
|
232
|
-
<div className="mt-8 space-y-2">
|
|
233
|
-
{selectedDesignSystem && (
|
|
234
|
-
<ReferenceRow
|
|
235
|
-
icon={<IconPalette className="size-4" />}
|
|
236
|
-
label={selectedDesignSystem.title}
|
|
237
|
-
meta={`${designSystemLabel}${defaultDesignSystemId === selectedDesignSystem.id ? defaultSuffix : ""}`}
|
|
238
|
-
selected
|
|
239
|
-
onClick={() => setSelectedDesignSystemId(null)}
|
|
240
|
-
/>
|
|
241
|
-
)}
|
|
242
|
-
{selectedSource && (
|
|
243
|
-
<ReferenceRow
|
|
244
|
-
icon={selectedSourceIcon}
|
|
245
|
-
label={selectedSourceLabel}
|
|
246
|
-
meta={selectedSource.value || "Add a link below"}
|
|
247
|
-
selected
|
|
248
|
-
onClick={() => setSelectedSource(null)}
|
|
249
|
-
/>
|
|
250
|
-
)}
|
|
251
|
-
</div>
|
|
252
|
-
)}
|
|
253
|
-
|
|
254
257
|
<div className="mt-10 space-y-6">
|
|
255
258
|
<div className="grid gap-2">
|
|
256
259
|
<div className="flex items-center justify-between gap-3">
|
|
@@ -309,7 +312,7 @@ export function NewDeckReferenceStep({
|
|
|
309
312
|
className="flex h-10 w-full items-center justify-between gap-2 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
|
310
313
|
>
|
|
311
314
|
<span className="truncate">
|
|
312
|
-
{
|
|
315
|
+
{selectedReferenceDeckTitle ?? chooseDeckLabel}
|
|
313
316
|
</span>
|
|
314
317
|
<IconChevronDown className="size-4 shrink-0 opacity-50" />
|
|
315
318
|
</button>
|
|
@@ -336,6 +339,7 @@ export function NewDeckReferenceStep({
|
|
|
336
339
|
value={`${deck.title} ${deck.id}`}
|
|
337
340
|
onSelect={() => {
|
|
338
341
|
setSelectedReferenceDeckId(deck.id);
|
|
342
|
+
setImportedReference(null);
|
|
339
343
|
setSelectedSource(null);
|
|
340
344
|
setReferenceDeckSearchOpen(false);
|
|
341
345
|
}}
|
|
@@ -361,6 +365,7 @@ export function NewDeckReferenceStep({
|
|
|
361
365
|
value={`${deck.title} ${deck.id}`}
|
|
362
366
|
onSelect={() => {
|
|
363
367
|
setSelectedReferenceDeckId(deck.id);
|
|
368
|
+
setImportedReference(null);
|
|
364
369
|
setSelectedSource(null);
|
|
365
370
|
setReferenceDeckSearchOpen(false);
|
|
366
371
|
}}
|
|
@@ -430,6 +435,8 @@ export function NewDeckReferenceStep({
|
|
|
430
435
|
accept=".pptx"
|
|
431
436
|
icon={<IconPresentation className="size-4" />}
|
|
432
437
|
label="PPT"
|
|
438
|
+
imported={importedReference?.source === "pptx"}
|
|
439
|
+
importedLabel={t("home.imported")}
|
|
433
440
|
importing={importing}
|
|
434
441
|
importingLabel={importingLabel}
|
|
435
442
|
onChange={handleImport}
|
|
@@ -438,6 +445,8 @@ export function NewDeckReferenceStep({
|
|
|
438
445
|
accept=".pdf"
|
|
439
446
|
icon={<IconFileTypePdf className="size-4" />}
|
|
440
447
|
label="PDF"
|
|
448
|
+
imported={importedReference?.source === "pdf"}
|
|
449
|
+
importedLabel={t("home.imported")}
|
|
441
450
|
importing={importing}
|
|
442
451
|
importingLabel={importingLabel}
|
|
443
452
|
onChange={handleImport}
|
|
@@ -446,25 +455,34 @@ export function NewDeckReferenceStep({
|
|
|
446
455
|
accept=".docx"
|
|
447
456
|
icon={<IconFileText className="size-4" />}
|
|
448
457
|
label="DOCX"
|
|
458
|
+
imported={false}
|
|
459
|
+
importedLabel={t("home.imported")}
|
|
449
460
|
importing={importing}
|
|
450
461
|
importingLabel={importingLabel}
|
|
451
462
|
onChange={handleImport}
|
|
452
463
|
/>
|
|
453
464
|
<ImportOption
|
|
454
465
|
icon={<IconBrandGoogle className="size-4" />}
|
|
455
|
-
label="
|
|
456
|
-
|
|
466
|
+
label={t("home.googleSlidesReferenceTitle")}
|
|
467
|
+
confirmed={importedReference?.source === "google-slides"}
|
|
468
|
+
confirmedLabel={t("home.imported")}
|
|
469
|
+
selected={
|
|
470
|
+
selectedSource?.kind === "google-docs" ||
|
|
471
|
+
importedReference?.source === "google-slides"
|
|
472
|
+
}
|
|
457
473
|
onClick={() => chooseSource("google-docs")}
|
|
458
474
|
/>
|
|
459
475
|
<ImportOption
|
|
460
476
|
icon={<IconWorld className="size-4" />}
|
|
461
477
|
label="Website"
|
|
478
|
+
confirmedLabel={t("home.imported")}
|
|
462
479
|
selected={selectedSource?.kind === "website"}
|
|
463
480
|
onClick={() => chooseSource("website")}
|
|
464
481
|
/>
|
|
465
482
|
<ImportOption
|
|
466
483
|
icon={<IconBrandFigma className="size-4" />}
|
|
467
484
|
label="Figma"
|
|
485
|
+
confirmedLabel={t("home.imported")}
|
|
468
486
|
selected={selectedSource?.kind === "figma"}
|
|
469
487
|
onClick={() => chooseSource("figma")}
|
|
470
488
|
/>
|
|
@@ -484,23 +502,54 @@ export function NewDeckReferenceStep({
|
|
|
484
502
|
}
|
|
485
503
|
/>
|
|
486
504
|
)}
|
|
505
|
+
{importedReference && (
|
|
506
|
+
<div
|
|
507
|
+
className="mt-3 flex items-start gap-3 rounded-lg border border-primary/30 bg-primary/5 px-3 py-2.5 text-sm"
|
|
508
|
+
role="status"
|
|
509
|
+
aria-live="polite"
|
|
510
|
+
>
|
|
511
|
+
<span className="mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground">
|
|
512
|
+
<IconCheck className="size-3.5" />
|
|
513
|
+
</span>
|
|
514
|
+
<div className="min-w-0">
|
|
515
|
+
<p className="font-medium text-foreground">
|
|
516
|
+
{t("home.referenceImportSuccess")}
|
|
517
|
+
</p>
|
|
518
|
+
<p className="truncate text-xs text-muted-foreground">
|
|
519
|
+
{t("home.referenceImportSelected", {
|
|
520
|
+
title: importedReference.title,
|
|
521
|
+
})}
|
|
522
|
+
</p>
|
|
523
|
+
</div>
|
|
524
|
+
</div>
|
|
525
|
+
)}
|
|
487
526
|
</div>
|
|
488
527
|
</div>
|
|
489
528
|
</div>
|
|
490
529
|
</main>
|
|
491
530
|
|
|
492
531
|
<footer className="flex shrink-0 items-center justify-between border-t border-border px-5 py-4 sm:px-8">
|
|
493
|
-
<Button
|
|
532
|
+
<Button
|
|
533
|
+
type="button"
|
|
534
|
+
variant="ghost"
|
|
535
|
+
onClick={onSkip}
|
|
536
|
+
disabled={importing}
|
|
537
|
+
>
|
|
494
538
|
{skipLabel}
|
|
495
539
|
</Button>
|
|
496
540
|
<Button
|
|
497
541
|
type="button"
|
|
498
|
-
onClick={handleContinue}
|
|
542
|
+
onClick={() => void handleContinue()}
|
|
543
|
+
aria-busy={importing}
|
|
499
544
|
disabled={
|
|
500
545
|
importing || Boolean(selectedSource && !selectedSource.value.trim())
|
|
501
546
|
}
|
|
502
547
|
>
|
|
503
|
-
|
|
548
|
+
{importing
|
|
549
|
+
? importingLabel
|
|
550
|
+
: importedReference
|
|
551
|
+
? t("home.continueToGenerate")
|
|
552
|
+
: t("home.continue")}
|
|
504
553
|
<IconCheck className="ms-1.5 size-4" />
|
|
505
554
|
</Button>
|
|
506
555
|
</footer>
|
|
@@ -512,6 +561,8 @@ function FileImportOption({
|
|
|
512
561
|
accept,
|
|
513
562
|
icon,
|
|
514
563
|
label,
|
|
564
|
+
imported = false,
|
|
565
|
+
importedLabel,
|
|
515
566
|
importing,
|
|
516
567
|
importingLabel,
|
|
517
568
|
onChange,
|
|
@@ -519,6 +570,8 @@ function FileImportOption({
|
|
|
519
570
|
accept: string;
|
|
520
571
|
icon: ReactNode;
|
|
521
572
|
label: string;
|
|
573
|
+
imported?: boolean;
|
|
574
|
+
importedLabel: string;
|
|
522
575
|
importing: boolean;
|
|
523
576
|
importingLabel: string;
|
|
524
577
|
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
|
|
@@ -529,8 +582,9 @@ function FileImportOption({
|
|
|
529
582
|
"flex cursor-pointer items-center justify-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-medium transition-colors hover:bg-accent",
|
|
530
583
|
importing && "pointer-events-none opacity-60",
|
|
531
584
|
)}
|
|
585
|
+
aria-label={imported ? `${label} - ${importedLabel}` : label}
|
|
532
586
|
>
|
|
533
|
-
{icon}
|
|
587
|
+
{imported ? <IconCheck className="size-4 text-primary" /> : icon}
|
|
534
588
|
<span>{importing ? importingLabel : label}</span>
|
|
535
589
|
<input
|
|
536
590
|
type="file"
|
|
@@ -547,11 +601,15 @@ function FileImportOption({
|
|
|
547
601
|
function ImportOption({
|
|
548
602
|
icon,
|
|
549
603
|
label,
|
|
604
|
+
confirmed = false,
|
|
605
|
+
confirmedLabel,
|
|
550
606
|
selected = false,
|
|
551
607
|
onClick,
|
|
552
608
|
}: {
|
|
553
609
|
icon: ReactNode;
|
|
554
610
|
label: string;
|
|
611
|
+
confirmed?: boolean;
|
|
612
|
+
confirmedLabel?: string;
|
|
555
613
|
selected?: boolean;
|
|
556
614
|
onClick: () => void;
|
|
557
615
|
}) {
|
|
@@ -565,9 +623,12 @@ function ImportOption({
|
|
|
565
623
|
? "border-primary/50 bg-primary/5 text-primary"
|
|
566
624
|
: "border-border hover:bg-accent",
|
|
567
625
|
)}
|
|
626
|
+
aria-label={
|
|
627
|
+
confirmed && confirmedLabel ? `${label} - ${confirmedLabel}` : label
|
|
628
|
+
}
|
|
568
629
|
aria-pressed={selected}
|
|
569
630
|
>
|
|
570
|
-
{icon}
|
|
631
|
+
{confirmed ? <IconCheck className="size-4 text-primary" /> : icon}
|
|
571
632
|
<span>{label}</span>
|
|
572
633
|
</button>
|
|
573
634
|
);
|