diff --git a/src/components/image-editor/ImageCanvasEditorPrimitives.tsx b/src/components/image-editor/ImageCanvasEditorPrimitives.tsx
new file mode 100644
index 00000000..c84fd5a8
--- /dev/null
+++ b/src/components/image-editor/ImageCanvasEditorPrimitives.tsx
@@ -0,0 +1,96 @@
+import type { ComponentType, ReactNode } from 'react';
+
+type IconComponent = ComponentType<{ className?: string }>;
+
+export type EditorIconButtonProps = {
+ label: string;
+ title?: string;
+ icon: IconComponent;
+ className?: string;
+ type?: 'button' | 'submit';
+ disabled?: boolean;
+ pressed?: boolean;
+ expanded?: boolean;
+ onClick?: () => void;
+};
+
+export function EditorIconButton({
+ label,
+ title = label,
+ icon: Icon,
+ className,
+ type = 'button',
+ disabled,
+ pressed,
+ expanded,
+ onClick,
+}: EditorIconButtonProps) {
+ return (
+
+ );
+}
+
+export type SidebarMediaItemProps = {
+ title: string;
+ detail: string;
+ imageSrc: string;
+ imageAlt: string;
+ selected?: boolean;
+ primaryLabel: string;
+ onPrimaryClick: () => void;
+ thumbnailClassName: string;
+ metaClassName: string;
+ rowClassName: string;
+ primaryClassName?: string;
+ actions?: ReactNode;
+ titleNode?: ReactNode;
+};
+
+export function SidebarMediaItem({
+ title,
+ detail,
+ imageSrc,
+ imageAlt,
+ selected = false,
+ primaryLabel,
+ onPrimaryClick,
+ thumbnailClassName,
+ metaClassName,
+ rowClassName,
+ primaryClassName,
+ actions,
+ titleNode,
+}: SidebarMediaItemProps) {
+ return (
+
+
+
+ {titleNode ?? {title}}
+ {detail}
+
+ {actions}
+
+ );
+}
diff --git a/src/components/image-editor/ImageCanvasEditorView.tsx b/src/components/image-editor/ImageCanvasEditorView.tsx
index 4be77503..4f01071c 100644
--- a/src/components/image-editor/ImageCanvasEditorView.tsx
+++ b/src/components/image-editor/ImageCanvasEditorView.tsx
@@ -26,10 +26,8 @@ import {
X,
} from 'lucide-react';
import {
- type ComponentType,
type KeyboardEvent as ReactKeyboardEvent,
type PointerEvent as ReactPointerEvent,
- type ReactNode,
useCallback,
useEffect,
useMemo,
@@ -48,6 +46,10 @@ import {
saveEditorProjectLayout,
} from '../../services/image-editor/editorProjectClient';
import { ApiClientError } from '../../services/apiClient';
+import {
+ EditorIconButton,
+ SidebarMediaItem,
+} from './ImageCanvasEditorPrimitives';
type EditorAsset = {
id: string;
@@ -141,34 +143,6 @@ type SnapCandidate = {
distance: number;
};
-type EditorIconButtonProps = {
- label: string;
- title?: string;
- icon: ComponentType<{ className?: string }>;
- className?: string;
- type?: 'button' | 'submit';
- disabled?: boolean;
- pressed?: boolean;
- expanded?: boolean;
- onClick?: () => void;
-};
-
-type SidebarMediaItemProps = {
- title: string;
- detail: string;
- imageSrc: string;
- imageAlt: string;
- selected?: boolean;
- primaryLabel: string;
- onPrimaryClick: () => void;
- thumbnailClassName: string;
- metaClassName: string;
- rowClassName: string;
- primaryClassName?: string;
- actions?: ReactNode;
- titleNode?: ReactNode;
-};
-
type DragState =
| {
kind: 'pan';
@@ -304,71 +278,6 @@ const CANVAS_BACKGROUND_OPTIONS = [
{ label: '冷蓝', value: '#eef6ff' },
];
-function EditorIconButton({
- label,
- title = label,
- icon: Icon,
- className,
- type = 'button',
- disabled,
- pressed,
- expanded,
- onClick,
-}: EditorIconButtonProps) {
- return (
-
- );
-}
-
-function SidebarMediaItem({
- title,
- detail,
- imageSrc,
- imageAlt,
- selected = false,
- primaryLabel,
- onPrimaryClick,
- thumbnailClassName,
- metaClassName,
- rowClassName,
- primaryClassName,
- actions,
- titleNode,
-}: SidebarMediaItemProps) {
- return (
-
-
-
- {titleNode ?? {title}}
- {detail}
-
- {actions}
-
- );
-}
-
function clamp(value: number, min: number, max: number) {
return Math.min(max, Math.max(min, value));
}
@@ -1604,32 +1513,25 @@ export function ImageCanvasEditorView() {
aria-label={folder.label}
>
-
+ />
{folder.label}
{folder.assets.length}
-
+ />
) : null}
-
+ />
) : null}