@open-slide/core 1.6.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{build-tLrkKUHr.js → build-CCZDC8eF.js} +1 -1
- package/dist/cli/bin.js +3 -3
- package/dist/{config-PwUHqZ_X.js → config-C7sZtiY2.js} +45 -18
- package/dist/{config-CfMThYN9.d.ts → config-D1bANimZ.d.ts} +1 -1
- package/dist/{dev-DpCIRbhT.js → dev-kLS_4CAI.js} +1 -1
- package/dist/{en-BDnM5zKJ.js → en-hyGpmL1O.js} +1 -4
- package/dist/index.d.ts +22 -4
- package/dist/index.js +1 -1
- package/dist/locale/index.d.ts +1 -1
- package/dist/locale/index.js +4 -13
- package/dist/{preview-BSGlM6Se.js → preview-DUkOjOx8.js} +1 -1
- package/dist/{types-B-KrjgX8.d.ts → types-Bvk1pM70.d.ts} +1 -4
- package/dist/vite/index.d.ts +2 -2
- package/dist/vite/index.js +1 -1
- package/package.json +1 -1
- package/skills/create-theme/SKILL.md +1 -1
- package/skills/slide-authoring/SKILL.md +169 -0
- package/src/app/components/inspector/asset-picker-dialog.tsx +196 -0
- package/src/app/components/inspector/comment-widget.tsx +16 -2
- package/src/app/components/inspector/inspect-overlay.tsx +132 -35
- package/src/app/components/inspector/inspector-panel.tsx +19 -256
- package/src/app/components/inspector/inspector-provider.tsx +102 -1
- package/src/app/components/panel/save-card.tsx +4 -4
- package/src/app/components/player.tsx +25 -25
- package/src/app/components/sidebar/folder-item.tsx +7 -2
- package/src/app/components/sidebar/sidebar.tsx +87 -16
- package/src/app/components/slide-transition-layer.tsx +154 -0
- package/src/app/components/style-panel/style-panel.tsx +3 -0
- package/src/app/lib/folders.ts +28 -0
- package/src/app/lib/inspector/fiber.test.ts +154 -0
- package/src/app/lib/inspector/fiber.ts +12 -1
- package/src/app/lib/sdk.ts +3 -1
- package/src/app/lib/transition.ts +23 -0
- package/src/app/lib/use-click-page-navigation.ts +52 -0
- package/src/app/lib/use-is-mobile.ts +21 -0
- package/src/app/lib/use-prefers-reduced-motion.ts +19 -0
- package/src/app/routes/home-shell.tsx +8 -0
- package/src/app/routes/home.tsx +1 -1
- package/src/app/routes/slide.tsx +92 -60
- package/src/locale/en.ts +1 -5
- package/src/locale/ja.ts +1 -5
- package/src/locale/types.ts +1 -5
- package/src/locale/zh-cn.ts +1 -5
- package/src/locale/zh-tw.ts +1 -5
- package/src/app/components/click-nav-zones.tsx +0 -36
|
@@ -39,6 +39,7 @@ export function HomeShell() {
|
|
|
39
39
|
create,
|
|
40
40
|
update,
|
|
41
41
|
remove,
|
|
42
|
+
reorder,
|
|
42
43
|
assign,
|
|
43
44
|
renameSlide,
|
|
44
45
|
duplicateSlide,
|
|
@@ -147,6 +148,13 @@ export function HomeShell() {
|
|
|
147
148
|
}}
|
|
148
149
|
onDropToFolder={(folderId, slideId) => moveSlideWithToast(slideId, folderId)}
|
|
149
150
|
onDropToDraft={(slideId) => moveSlideWithToast(slideId, null)}
|
|
151
|
+
onReorder={async (ids) => {
|
|
152
|
+
try {
|
|
153
|
+
await reorder(ids);
|
|
154
|
+
} catch {
|
|
155
|
+
toast.error(t.home.toastFolderReorderFailed);
|
|
156
|
+
}
|
|
157
|
+
}}
|
|
150
158
|
/>
|
|
151
159
|
</div>
|
|
152
160
|
|
package/src/app/routes/home.tsx
CHANGED
|
@@ -242,7 +242,7 @@ function SortControl({ value, onChange }: { value: SortKey; onChange: (next: Sor
|
|
|
242
242
|
<button
|
|
243
243
|
type="button"
|
|
244
244
|
aria-label={`${t.home.sortLabel}: ${labels[value]}`}
|
|
245
|
-
className="flex h-8 items-center gap-1.5 rounded-[6px] border border-border bg-background pl-2 pr-1.5 text-[12.5px] font-medium text-foreground outline-none hover:bg-muted focus-visible:border-foreground/40 focus-visible:ring-2 focus-visible:ring-ring/30"
|
|
245
|
+
className="flex h-8 shrink-0 items-center gap-1.5 whitespace-nowrap rounded-[6px] border border-border bg-background pl-2 pr-1.5 text-[12.5px] font-medium text-foreground outline-none hover:bg-muted focus-visible:border-foreground/40 focus-visible:ring-2 focus-visible:ring-ring/30"
|
|
246
246
|
>
|
|
247
247
|
<FieldIcon k={value} className="size-3.5 text-muted-foreground" />
|
|
248
248
|
<span>{labels[value]}</span>
|
package/src/app/routes/slide.tsx
CHANGED
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
Loader2,
|
|
11
11
|
Maximize,
|
|
12
12
|
MonitorSpeaker,
|
|
13
|
-
Pencil,
|
|
14
13
|
Play,
|
|
15
14
|
} from 'lucide-react';
|
|
16
15
|
import { type RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
@@ -40,20 +39,22 @@ import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
|
40
39
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
|
41
40
|
import { useFolders } from '@/lib/folders';
|
|
42
41
|
import { useAgentSocketConnected } from '@/lib/use-agent-socket';
|
|
42
|
+
import { useClickPageNavigation } from '@/lib/use-click-page-navigation';
|
|
43
|
+
import { useIsMobile } from '@/lib/use-is-mobile';
|
|
43
44
|
import { format, useLocale } from '@/lib/use-locale';
|
|
44
45
|
import { useWheelPageNavigation } from '@/lib/use-wheel-page-navigation';
|
|
45
46
|
import { cn } from '@/lib/utils';
|
|
46
|
-
import { ClickNavZones } from '../components/click-nav-zones';
|
|
47
47
|
import { NotesDrawer } from '../components/notes-drawer';
|
|
48
48
|
import { PdfProgressToast } from '../components/pdf-progress-toast';
|
|
49
49
|
import { openPresenterWindow, Player } from '../components/player';
|
|
50
50
|
import { SlideCanvas } from '../components/slide-canvas';
|
|
51
|
+
import { SlideTransitionLayer } from '../components/slide-transition-layer';
|
|
51
52
|
import { type ThumbnailActions, ThumbnailRail } from '../components/thumbnail-rail';
|
|
52
53
|
import { exportSlideAsHtml } from '../lib/export-html';
|
|
53
54
|
import { exportSlideAsPdf, isSafari } from '../lib/export-pdf';
|
|
54
55
|
import { remapNotesSessionCacheAfterReorder } from '../lib/inspector/use-notes';
|
|
55
|
-
import { SlidePageProvider } from '../lib/page-context';
|
|
56
56
|
import type { SlideModule } from '../lib/sdk';
|
|
57
|
+
import { usePrefersReducedMotion } from '../lib/use-prefers-reduced-motion';
|
|
57
58
|
import { useSlideModule } from '../lib/use-slide-module';
|
|
58
59
|
|
|
59
60
|
const { showSlideUi, showSlideBrowser, allowHtmlDownload } = config.build;
|
|
@@ -76,6 +77,7 @@ export function Slide() {
|
|
|
76
77
|
const { renameSlide } = useFolders();
|
|
77
78
|
const slideViewportRef = useRef<HTMLElement>(null);
|
|
78
79
|
const t = useLocale();
|
|
80
|
+
const prefersReducedMotion = usePrefersReducedMotion();
|
|
79
81
|
|
|
80
82
|
const modulePages = useMemo(() => slide?.default ?? [], [slide]);
|
|
81
83
|
const [pages, setPages] = useState<typeof modulePages>(modulePages);
|
|
@@ -237,6 +239,8 @@ export function Slide() {
|
|
|
237
239
|
goTo(index - 1);
|
|
238
240
|
} else if (e.key === 'f' || e.key === 'F') {
|
|
239
241
|
setPlayMode('fullscreen');
|
|
242
|
+
} else if (import.meta.env.DEV && (e.key === 'd' || e.key === 'D')) {
|
|
243
|
+
setDesignOpen((v) => !v);
|
|
240
244
|
}
|
|
241
245
|
};
|
|
242
246
|
window.addEventListener('keydown', onKey);
|
|
@@ -325,6 +329,7 @@ export function Slide() {
|
|
|
325
329
|
<Player
|
|
326
330
|
pages={pages}
|
|
327
331
|
design={slide.design}
|
|
332
|
+
transition={slide.transition}
|
|
328
333
|
index={index}
|
|
329
334
|
onIndexChange={goTo}
|
|
330
335
|
onExit={() => setPlayMode(null)}
|
|
@@ -335,17 +340,16 @@ export function Slide() {
|
|
|
335
340
|
);
|
|
336
341
|
}
|
|
337
342
|
|
|
338
|
-
const CurrentPage = pages[index];
|
|
339
343
|
const title = slide.meta?.title ?? slideId;
|
|
340
344
|
|
|
341
345
|
return (
|
|
342
346
|
<HistoryProvider>
|
|
343
|
-
<InspectorProvider slideId={slideId}>
|
|
347
|
+
<InspectorProvider slideId={slideId} pageIndex={index}>
|
|
344
348
|
<SelectionReporter />
|
|
345
349
|
<div className="flex h-dvh flex-col overflow-hidden bg-background text-foreground">
|
|
346
350
|
{/* Editorial toolbar — three zones, hairline separators, mono-folio center */}
|
|
347
|
-
<header className="relative flex h-12 shrink-0 items-center
|
|
348
|
-
<div className="flex items-center gap-1.5 md:gap-2">
|
|
351
|
+
<header className="relative flex h-12 shrink-0 items-center gap-2 border-b border-hairline bg-sidebar/85 px-2 backdrop-blur-md md:px-3">
|
|
352
|
+
<div className="flex shrink-0 items-center gap-1.5 md:gap-2">
|
|
349
353
|
{showSlideBrowser && (
|
|
350
354
|
<Button asChild variant="ghost" size="icon-sm" title={t.slide.home}>
|
|
351
355
|
<Link to="/" aria-label={t.slide.backToHome}>
|
|
@@ -378,14 +382,14 @@ export function Slide() {
|
|
|
378
382
|
{import.meta.env.DEV && <AgentConnectedBadge />}
|
|
379
383
|
</div>
|
|
380
384
|
|
|
381
|
-
{/*
|
|
382
|
-
<div className="pointer-events-none absolute inset-x-0
|
|
383
|
-
<div className="pointer-events-auto min-w-0 max-w-[
|
|
385
|
+
{/* Title centered to the viewport, not the leftover space between the side groups. */}
|
|
386
|
+
<div className="pointer-events-none absolute inset-x-0 flex justify-center px-2">
|
|
387
|
+
<div className="pointer-events-auto min-w-0 max-w-[34rem]">
|
|
384
388
|
<InlineTitleEditor title={title} onSubmit={(next) => renameSlide(slideId, next)} />
|
|
385
389
|
</div>
|
|
386
390
|
</div>
|
|
387
391
|
|
|
388
|
-
<div className="flex items-center gap-1">
|
|
392
|
+
<div className="ml-auto flex shrink-0 items-center gap-1">
|
|
389
393
|
{view === 'slides' && (
|
|
390
394
|
<button
|
|
391
395
|
type="button"
|
|
@@ -577,7 +581,7 @@ export function Slide() {
|
|
|
577
581
|
data-slide-id={slideId}
|
|
578
582
|
className="paper relative min-h-0 min-w-0 flex-1 bg-canvas p-2 md:p-10"
|
|
579
583
|
>
|
|
580
|
-
<
|
|
584
|
+
<SlideViewportNavigation
|
|
581
585
|
targetRef={slideViewportRef}
|
|
582
586
|
onPrev={() => goTo(index - 1)}
|
|
583
587
|
onNext={() => goTo(index + 1)}
|
|
@@ -585,16 +589,14 @@ export function Slide() {
|
|
|
585
589
|
canNext={index < pageCount - 1}
|
|
586
590
|
/>
|
|
587
591
|
<SlideCanvas design={slide.design}>
|
|
588
|
-
<
|
|
589
|
-
|
|
590
|
-
|
|
592
|
+
<SlideTransitionLayer
|
|
593
|
+
pages={pages}
|
|
594
|
+
index={index}
|
|
595
|
+
total={pageCount}
|
|
596
|
+
moduleTransition={slide.transition}
|
|
597
|
+
disabled={prefersReducedMotion}
|
|
598
|
+
/>
|
|
591
599
|
</SlideCanvas>
|
|
592
|
-
<ClickNavZones
|
|
593
|
-
onPrev={() => goTo(index - 1)}
|
|
594
|
-
onNext={() => goTo(index + 1)}
|
|
595
|
-
canPrev={index > 0}
|
|
596
|
-
canNext={index < pageCount - 1}
|
|
597
|
-
/>
|
|
598
600
|
<InspectOverlay />
|
|
599
601
|
<SaveBar />
|
|
600
602
|
{import.meta.env.DEV && <CommentWidget />}
|
|
@@ -806,7 +808,7 @@ function SelectionReporter() {
|
|
|
806
808
|
return null;
|
|
807
809
|
}
|
|
808
810
|
|
|
809
|
-
function
|
|
811
|
+
function SlideViewportNavigation({
|
|
810
812
|
targetRef,
|
|
811
813
|
onPrev,
|
|
812
814
|
onNext,
|
|
@@ -820,6 +822,7 @@ function SlideWheelNavigation({
|
|
|
820
822
|
canNext: boolean;
|
|
821
823
|
}) {
|
|
822
824
|
const { active } = useInspector();
|
|
825
|
+
const isMobile = useIsMobile();
|
|
823
826
|
|
|
824
827
|
useWheelPageNavigation({
|
|
825
828
|
ref: targetRef,
|
|
@@ -830,6 +833,19 @@ function SlideWheelNavigation({
|
|
|
830
833
|
onNext,
|
|
831
834
|
});
|
|
832
835
|
|
|
836
|
+
// Tap-to-navigate is a touch affordance — desktop has visible prev/next
|
|
837
|
+
// chrome, so it stays edge-only on small screens (matches the old md:hidden
|
|
838
|
+
// zones). Interactive slide content keeps its tap via the hook's passthrough.
|
|
839
|
+
useClickPageNavigation({
|
|
840
|
+
ref: targetRef,
|
|
841
|
+
enabled: isMobile && !active,
|
|
842
|
+
edgeRatio: 0.18,
|
|
843
|
+
canPrev,
|
|
844
|
+
canNext,
|
|
845
|
+
onPrev,
|
|
846
|
+
onNext,
|
|
847
|
+
});
|
|
848
|
+
|
|
833
849
|
return null;
|
|
834
850
|
}
|
|
835
851
|
|
|
@@ -882,49 +898,65 @@ function InlineTitleEditor({
|
|
|
882
898
|
|
|
883
899
|
if (editing) {
|
|
884
900
|
return (
|
|
885
|
-
<div className="flex flex-1 items-center justify-center">
|
|
886
|
-
<
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
901
|
+
<div className="flex min-w-0 flex-1 items-center justify-center">
|
|
902
|
+
<div className="inline-grid max-w-full items-center">
|
|
903
|
+
<span
|
|
904
|
+
aria-hidden
|
|
905
|
+
className="invisible col-start-1 row-start-1 overflow-hidden whitespace-pre border border-transparent px-2 py-0.5 font-heading text-[13.5px] font-semibold tracking-[-0.01em]"
|
|
906
|
+
>
|
|
907
|
+
{value || ' '}
|
|
908
|
+
</span>
|
|
909
|
+
<input
|
|
910
|
+
ref={inputRef}
|
|
911
|
+
size={1}
|
|
912
|
+
value={value}
|
|
913
|
+
disabled={saving}
|
|
914
|
+
onChange={(e) => setValue(e.target.value)}
|
|
915
|
+
onBlur={() => {
|
|
916
|
+
if (!saving) commit();
|
|
917
|
+
}}
|
|
918
|
+
onKeyDown={(e) => {
|
|
919
|
+
if (e.key === 'Enter') {
|
|
920
|
+
e.preventDefault();
|
|
921
|
+
commit();
|
|
922
|
+
} else if (e.key === 'Escape') {
|
|
923
|
+
e.preventDefault();
|
|
924
|
+
cancel();
|
|
925
|
+
}
|
|
926
|
+
}}
|
|
927
|
+
maxLength={80}
|
|
928
|
+
className="col-start-1 row-start-1 w-full min-w-0 rounded-[5px] border border-foreground/30 bg-card px-2 py-0.5 text-center font-heading text-[13.5px] font-semibold tracking-[-0.01em] outline-none"
|
|
929
|
+
/>
|
|
930
|
+
</div>
|
|
931
|
+
</div>
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
if (!import.meta.env.DEV) {
|
|
936
|
+
return (
|
|
937
|
+
<div className="flex min-w-0 items-baseline justify-center">
|
|
938
|
+
<h1 className="truncate font-heading text-[13.5px] font-semibold tracking-[-0.01em]">
|
|
939
|
+
{title}
|
|
940
|
+
</h1>
|
|
906
941
|
</div>
|
|
907
942
|
);
|
|
908
943
|
}
|
|
909
944
|
|
|
910
945
|
return (
|
|
911
|
-
<div className="
|
|
912
|
-
<
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
<Pencil className="size-3" />
|
|
926
|
-
</button>
|
|
927
|
-
)}
|
|
946
|
+
<div className="flex min-w-0 items-center justify-center">
|
|
947
|
+
<button
|
|
948
|
+
type="button"
|
|
949
|
+
onClick={() => setEditing(true)}
|
|
950
|
+
aria-label={t.slide.renameSlide}
|
|
951
|
+
className={cn(
|
|
952
|
+
'min-w-0 max-w-full cursor-text rounded-[5px] border border-transparent px-2 py-0.5 transition-colors',
|
|
953
|
+
'hover:border-foreground/30 hover:bg-card focus-visible:border-foreground/30 focus-visible:bg-card focus-visible:outline-none',
|
|
954
|
+
)}
|
|
955
|
+
>
|
|
956
|
+
<h1 className="truncate font-heading text-[13.5px] font-semibold tracking-[-0.01em]">
|
|
957
|
+
{title}
|
|
958
|
+
</h1>
|
|
959
|
+
</button>
|
|
928
960
|
</div>
|
|
929
961
|
);
|
|
930
962
|
}
|
package/src/locale/en.ts
CHANGED
|
@@ -86,6 +86,7 @@ export const en: Locale = {
|
|
|
86
86
|
toastSlideMoveFailed: 'Failed to move slide',
|
|
87
87
|
toastFolderDeleted: 'Deleted folder “{name}”',
|
|
88
88
|
toastFolderDeleteFailed: 'Failed to delete folder',
|
|
89
|
+
toastFolderReorderFailed: 'Failed to reorder folders',
|
|
89
90
|
pickIcon: 'Pick icon',
|
|
90
91
|
},
|
|
91
92
|
|
|
@@ -352,11 +353,6 @@ export const en: Locale = {
|
|
|
352
353
|
system: 'System',
|
|
353
354
|
},
|
|
354
355
|
|
|
355
|
-
clickNav: {
|
|
356
|
-
prevAria: 'Previous page',
|
|
357
|
-
nextAria: 'Next page',
|
|
358
|
-
},
|
|
359
|
-
|
|
360
356
|
imagePlaceholder: {
|
|
361
357
|
dropOverlay: 'Drop image to use here',
|
|
362
358
|
uploading: 'Uploading…',
|
package/src/locale/ja.ts
CHANGED
|
@@ -86,6 +86,7 @@ export const ja: Locale = {
|
|
|
86
86
|
toastSlideMoveFailed: 'スライドの移動に失敗しました',
|
|
87
87
|
toastFolderDeleted: 'フォルダ「{name}」を削除しました',
|
|
88
88
|
toastFolderDeleteFailed: 'フォルダの削除に失敗しました',
|
|
89
|
+
toastFolderReorderFailed: 'フォルダの並び替えに失敗しました',
|
|
89
90
|
pickIcon: 'アイコンを選択',
|
|
90
91
|
},
|
|
91
92
|
|
|
@@ -356,11 +357,6 @@ export const ja: Locale = {
|
|
|
356
357
|
system: 'システム',
|
|
357
358
|
},
|
|
358
359
|
|
|
359
|
-
clickNav: {
|
|
360
|
-
prevAria: '前のページ',
|
|
361
|
-
nextAria: '次のページ',
|
|
362
|
-
},
|
|
363
|
-
|
|
364
360
|
imagePlaceholder: {
|
|
365
361
|
dropOverlay: 'ここにドロップして使用',
|
|
366
362
|
uploading: 'アップロード中…',
|
package/src/locale/types.ts
CHANGED
|
@@ -90,6 +90,7 @@ export type Locale = {
|
|
|
90
90
|
/** template: "Deleted folder “{name}”" */
|
|
91
91
|
toastFolderDeleted: string;
|
|
92
92
|
toastFolderDeleteFailed: string;
|
|
93
|
+
toastFolderReorderFailed: string;
|
|
93
94
|
pickIcon: string;
|
|
94
95
|
};
|
|
95
96
|
|
|
@@ -375,11 +376,6 @@ export type Locale = {
|
|
|
375
376
|
system: string;
|
|
376
377
|
};
|
|
377
378
|
|
|
378
|
-
clickNav: {
|
|
379
|
-
prevAria: string;
|
|
380
|
-
nextAria: string;
|
|
381
|
-
};
|
|
382
|
-
|
|
383
379
|
imagePlaceholder: {
|
|
384
380
|
dropOverlay: string;
|
|
385
381
|
uploading: string;
|
package/src/locale/zh-cn.ts
CHANGED
|
@@ -86,6 +86,7 @@ export const zhCN: Locale = {
|
|
|
86
86
|
toastSlideMoveFailed: '移动幻灯片失败',
|
|
87
87
|
toastFolderDeleted: '已删除文件夹"{name}"',
|
|
88
88
|
toastFolderDeleteFailed: '删除文件夹失败',
|
|
89
|
+
toastFolderReorderFailed: '文件夹排序失败',
|
|
89
90
|
pickIcon: '选择图标',
|
|
90
91
|
},
|
|
91
92
|
|
|
@@ -351,11 +352,6 @@ export const zhCN: Locale = {
|
|
|
351
352
|
system: '系统',
|
|
352
353
|
},
|
|
353
354
|
|
|
354
|
-
clickNav: {
|
|
355
|
-
prevAria: '上一页',
|
|
356
|
-
nextAria: '下一页',
|
|
357
|
-
},
|
|
358
|
-
|
|
359
355
|
imagePlaceholder: {
|
|
360
356
|
dropOverlay: '拖入图片以使用',
|
|
361
357
|
uploading: '上传中…',
|
package/src/locale/zh-tw.ts
CHANGED
|
@@ -86,6 +86,7 @@ export const zhTW: Locale = {
|
|
|
86
86
|
toastSlideMoveFailed: '移動投影片失敗',
|
|
87
87
|
toastFolderDeleted: '已刪除資料夾「{name}」',
|
|
88
88
|
toastFolderDeleteFailed: '刪除資料夾失敗',
|
|
89
|
+
toastFolderReorderFailed: '資料夾排序失敗',
|
|
89
90
|
pickIcon: '選擇圖示',
|
|
90
91
|
},
|
|
91
92
|
|
|
@@ -351,11 +352,6 @@ export const zhTW: Locale = {
|
|
|
351
352
|
system: '系統',
|
|
352
353
|
},
|
|
353
354
|
|
|
354
|
-
clickNav: {
|
|
355
|
-
prevAria: '上一頁',
|
|
356
|
-
nextAria: '下一頁',
|
|
357
|
-
},
|
|
358
|
-
|
|
359
355
|
imagePlaceholder: {
|
|
360
356
|
dropOverlay: '拖入圖片以使用',
|
|
361
357
|
uploading: '上傳中…',
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { useLocale } from '@/lib/use-locale';
|
|
2
|
-
import { useInspector } from './inspector/inspector-provider';
|
|
3
|
-
|
|
4
|
-
type Props = {
|
|
5
|
-
onPrev: () => void;
|
|
6
|
-
onNext: () => void;
|
|
7
|
-
canPrev: boolean;
|
|
8
|
-
canNext: boolean;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export function ClickNavZones({ onPrev, onNext, canPrev, canNext }: Props) {
|
|
12
|
-
const { active } = useInspector();
|
|
13
|
-
const t = useLocale();
|
|
14
|
-
if (active) return null;
|
|
15
|
-
|
|
16
|
-
return (
|
|
17
|
-
<>
|
|
18
|
-
<button
|
|
19
|
-
type="button"
|
|
20
|
-
aria-label={t.clickNav.prevAria}
|
|
21
|
-
onClick={onPrev}
|
|
22
|
-
disabled={!canPrev}
|
|
23
|
-
data-inspector-ui
|
|
24
|
-
className="absolute inset-y-0 left-0 z-20 w-[18%] min-w-12 md:hidden"
|
|
25
|
-
/>
|
|
26
|
-
<button
|
|
27
|
-
type="button"
|
|
28
|
-
aria-label={t.clickNav.nextAria}
|
|
29
|
-
onClick={onNext}
|
|
30
|
-
disabled={!canNext}
|
|
31
|
-
data-inspector-ui
|
|
32
|
-
className="absolute inset-y-0 right-0 z-20 w-[18%] min-w-12 md:hidden"
|
|
33
|
-
/>
|
|
34
|
-
</>
|
|
35
|
-
);
|
|
36
|
-
}
|