统一快速编辑与改造生成弹窗底盘

复用 ImageCanvasBasicGenerationComposerView 作为快速编辑面板容器

复用 ImageCanvasBasicGenerationComposerView 作为改造对话框容器

为基础底盘增加可配置化参数,覆盖标题、提示词、按钮、参考图和引用源能力

生成器参数与状态可由外层覆盖,支持快速编辑重绘等场景定制

Quick-edit 参考图使用通用参考图 Slot,不再手写组件
This commit is contained in:
2026-06-25 18:17:41 +08:00
parent e4ac57c97d
commit 3c125ceb86
4 changed files with 458 additions and 690 deletions
@@ -16,14 +16,27 @@ import { PlatformTextField } from '../common/PlatformTextField';
import type {
CharacterReferenceImage,
GenerateDialogState,
UploadTarget,
} from './ImageCanvasEditorTypes';
import { ImageCanvasGenerationImageOptionsView } from './ImageCanvasGenerationImageOptionsView';
import { ImageCanvasReferenceSlot } from './ImageCanvasReferenceSlot';
import {
calculateEditorImageGenerationPrice,
} from './ImageCanvasGenerationModel';
import { calculateEditorImageGenerationPrice } from './ImageCanvasGenerationModel';
import { useImageCanvasFloatingOptionDismiss } from './useImageCanvasFloatingOptionDismiss';
type ReferenceLabelFormatter = (
reference: CharacterReferenceImage,
index: number,
) => string;
type ReferenceTone =
| 'default'
| 'spec'
| 'icon'
| 'ui'
| 'quick-edit'
| 'video'
| 'audio';
type ImageCanvasBasicGenerationComposerViewProps = {
dialog: GenerateDialogState;
style: CSSProperties;
@@ -37,11 +50,41 @@ type ImageCanvasBasicGenerationComposerViewProps = {
anchor: HTMLElement | null,
placement: 'above' | 'below',
) => CSSProperties;
onRequestUpload: (target: 'generation-reference') => void;
onRequestUpload: (target: UploadTarget) => void;
onPickReferenceFromCanvas?: () => void;
onToggleReferenceMenu?: () => void;
onRememberImageModel?: (model: string) => void;
onSubmit: (dialog: GenerateDialogState) => void;
dialogLabel?: string;
includeDimensions?: boolean;
includeModel?: boolean;
includeReferences?: boolean;
promptLabel?: string;
promptPlaceholder?: string;
optionLabelPrefix?: string;
dimensionRatioAriaLabelPrefix?: string;
dimensionSizeAriaLabelPrefix?: string;
referenceUploadTarget?: UploadTarget;
referenceButtonIcon?: ReactNode;
referenceButtonLabel?: string;
referenceButtonAriaLabel?: string;
referenceMenuLabel?: string;
referenceSlotTone?: ReferenceTone;
referenceSlotDisabled?: boolean;
referenceLabelFormatter?: ReferenceLabelFormatter;
referenceAriaLabelFormatter?: ReferenceLabelFormatter;
referenceRemoveLabelFormatter?: ReferenceLabelFormatter;
showReferenceAddButton?: boolean;
formClassName?: string;
footerClassName?: string;
promptClassName?: string;
referenceSlotClassName?: string;
submitButtonClassName?: string;
submitLabel?: string;
submitAriaLabel?: string;
submittingStatusLabel?: string;
};
function resetFailedDialogStatus(dialog: GenerateDialogState) {
return {
...dialog,
@@ -50,31 +93,6 @@ function resetFailedDialogStatus(dialog: GenerateDialogState) {
};
}
function ReferenceChip({
reference,
index,
onRemove,
}: {
reference: CharacterReferenceImage;
index: number;
onRemove: () => void;
}) {
const label = reference.label || `参考图${index + 1}`;
return (
<ImageCanvasReferenceSlot
tone="default"
icon={<ImageIcon className="h-4 w-4" aria-hidden="true" />}
imageSrc={reference.src}
objectKey={reference.objectKey}
label={label}
ariaLabel={label}
title={reference.label}
onRemove={onRemove}
removeLabel={`删除${label}`}
/>
);
}
export function ImageCanvasBasicGenerationComposerView({
dialog,
style,
@@ -86,12 +104,60 @@ export function ImageCanvasBasicGenerationComposerView({
renderEditorPortal = (node) => node,
buildPortalMenuStyle = () => ({}),
onRequestUpload,
onPickReferenceFromCanvas,
onToggleReferenceMenu,
onRememberImageModel = () => {},
onSubmit,
dialogLabel,
includeDimensions = true,
includeModel = true,
includeReferences = true,
promptLabel,
promptPlaceholder,
optionLabelPrefix,
dimensionRatioAriaLabelPrefix,
dimensionSizeAriaLabelPrefix,
referenceUploadTarget = 'generation-reference',
referenceButtonIcon = <ImageIcon className="h-4 w-4" aria-hidden="true" />,
referenceButtonLabel = '参考图',
referenceButtonAriaLabel = '添加参考图',
referenceMenuLabel = '参考图来源',
referenceSlotTone = 'default',
referenceSlotDisabled = false,
referenceLabelFormatter,
referenceAriaLabelFormatter,
referenceRemoveLabelFormatter,
showReferenceAddButton = true,
formClassName,
footerClassName,
promptClassName = 'image-canvas-editor__generation-prompt',
referenceSlotClassName,
submitButtonClassName = 'image-canvas-editor__generation-submit',
submitLabel = '生成',
submitAriaLabel = '生成',
submittingStatusLabel = '生成中',
}: ImageCanvasBasicGenerationComposerViewProps) {
const references = dialog.generationReferences ?? [];
const isQuickEdit = dialog.mode === 'quick-edit';
const resolvedDialogLabel =
dialogLabel ?? optionLabelPrefix ?? (isQuickEdit ? '改造图片' : '生成图片');
const resolvedPromptLabel =
promptLabel ?? (isQuickEdit ? '改造提示词' : '生成提示词');
const resolvedPromptPlaceholder =
promptPlaceholder ??
(isQuickEdit ? '想怎么改造这张图?' : '今天想生成什么画面?');
const resolvedOptionLabelPrefix =
optionLabelPrefix ?? (isQuickEdit ? '改造图片' : '生成图片');
const resolvedReferenceButtonLabel = referenceButtonLabel;
const resolvedReferenceButtonAriaLabel =
referenceButtonAriaLabel ?? `添加${resolvedReferenceButtonLabel}`;
const hasReferenceMenu = includeReferences && onRequestUpload;
const finalFormClassName =
formClassName ??
'image-canvas-editor__generation-composer image-canvas-editor__generation-composer--image';
const finalFooterClassName =
footerClassName ?? 'image-canvas-editor__generation-composer-footer';
useImageCanvasFloatingOptionDismiss({
isOpen: isGenerationReferenceMenuOpen,
boundaryRefs: [generationReferenceButtonRef],
@@ -101,10 +167,10 @@ export function ImageCanvasBasicGenerationComposerView({
return (
<>
<form
className="image-canvas-editor__generation-composer image-canvas-editor__generation-composer--image"
className={finalFormClassName}
style={style}
role="dialog"
aria-label={isQuickEdit ? '改造图片' : '生成图片'}
aria-label={resolvedDialogLabel}
onPointerDown={(event) => event.stopPropagation()}
onSubmit={(event) => {
event.preventDefault();
@@ -113,102 +179,133 @@ export function ImageCanvasBasicGenerationComposerView({
}
}}
>
<div className="image-canvas-editor__reference-strip">
{references.map((reference, index) => (
<ReferenceChip
key={reference.id}
reference={reference}
index={index}
onRemove={() =>
setGenerateDialog((currentDialog) =>
currentDialog
? {
...resetFailedDialogStatus(currentDialog),
generationReferences: (
currentDialog.generationReferences ?? []
).filter((item) => item.id !== reference.id),
}
: currentDialog,
)
}
/>
))}
<ImageCanvasReferenceSlot
buttonRef={generationReferenceButtonRef}
tone="default"
icon={<ImageIcon className="h-4 w-4" aria-hidden="true" />}
label="参考图"
ariaLabel="添加参考图"
disabled={dialog.status === 'generating'}
isAdd
onClick={() => onToggleReferenceMenu?.()}
/>
</div>
<PlatformTextField
variant="textarea"
aria-label={isQuickEdit ? '改造提示词' : '生成提示词'}
value={dialog.prompt}
disabled={dialog.status === 'generating'}
placeholder={isQuickEdit ? '想怎么改造这张图?' : '今天想生成什么画面?'}
size="sm"
density="compact"
className="image-canvas-editor__generation-prompt"
onChange={(event) =>
setGenerateDialog((currentDialog) =>
currentDialog
? {
...resetFailedDialogStatus(currentDialog),
prompt: event.target.value,
{includeReferences ? (
<div className="image-canvas-editor__reference-strip">
{references.map((reference, index) => {
const label =
(referenceLabelFormatter?.(reference, index) ??
reference.label) ||
`参考图${index + 1}`;
const ariaLabel =
referenceAriaLabelFormatter?.(reference, index) ?? label;
const removeLabel =
referenceRemoveLabelFormatter?.(reference, index) ??
`删除${ariaLabel}`;
return (
<ImageCanvasReferenceSlot
key={reference.id}
tone={referenceSlotTone}
icon={<ImageIcon className="h-4 w-4" aria-hidden="true" />}
imageSrc={reference.src}
objectKey={reference.objectKey}
label={label}
ariaLabel={ariaLabel}
title={reference.label}
onRemove={() =>
setGenerateDialog((currentDialog) =>
currentDialog
? {
...resetFailedDialogStatus(currentDialog),
generationReferences: (
currentDialog.generationReferences ?? []
).filter((item) => item.id !== reference.id),
}
: currentDialog,
)
}
removeLabel={removeLabel}
className={referenceSlotClassName}
/>
);
})}
{showReferenceAddButton ? (
<ImageCanvasReferenceSlot
buttonRef={generationReferenceButtonRef}
tone={referenceSlotTone}
icon={referenceButtonIcon}
label={resolvedReferenceButtonLabel}
ariaLabel={resolvedReferenceButtonAriaLabel}
disabled={
dialog.status === 'generating' || referenceSlotDisabled
}
: currentDialog,
)
}
/>
<div className="image-canvas-editor__generation-composer-footer">
<ImageCanvasGenerationImageOptionsView
dialog={dialog}
setGenerateDialog={setGenerateDialog}
includeDimensions
onRememberImageModel={onRememberImageModel}
optionLabelPrefix={isQuickEdit ? '改造图片' : '生成图片'}
cost={calculateEditorImageGenerationPrice({
model: dialog.imageModel,
imageSize: dialog.imageSize,
})}
submitLabel="生成"
submitAriaLabel="生成"
renderEditorPortal={renderEditorPortal}
buildPortalMenuStyle={buildPortalMenuStyle}
isAdd
onClick={() => onToggleReferenceMenu?.()}
/>
) : null}
</div>
) : null}
<PlatformTextField
variant="textarea"
aria-label={resolvedPromptLabel}
value={dialog.prompt}
disabled={dialog.status === 'generating'}
placeholder={resolvedPromptPlaceholder}
size="sm"
density="compact"
className={promptClassName}
onChange={(event) =>
setGenerateDialog((currentDialog) =>
currentDialog
? {
...resetFailedDialogStatus(currentDialog),
prompt: event.target.value,
}
: currentDialog,
)
}
/>
</div>
{dialog.status === 'generating' ? (
<PlatformStatusMessage
tone="info"
surface="platform"
size="xs"
className="image-canvas-editor__generate-status"
role="status"
>
</PlatformStatusMessage>
) : null}
{dialog.status === 'failed' ? (
<PlatformStatusMessage
tone="error"
surface="platform"
size="xs"
className="image-canvas-editor__generate-status"
role="alert"
>
{dialog.errorMessage}
</PlatformStatusMessage>
) : null}
<div className={finalFooterClassName}>
<ImageCanvasGenerationImageOptionsView
dialog={dialog}
setGenerateDialog={setGenerateDialog}
includeDimensions={includeDimensions}
includeModel={includeModel}
onRememberImageModel={onRememberImageModel}
dimensionRatioAriaLabelPrefix={dimensionRatioAriaLabelPrefix}
dimensionSizeAriaLabelPrefix={dimensionSizeAriaLabelPrefix}
optionLabelPrefix={resolvedOptionLabelPrefix}
cost={calculateEditorImageGenerationPrice({
model: dialog.imageModel,
imageSize: dialog.imageSize,
})}
submitLabel={submitLabel}
submitAriaLabel={submitAriaLabel}
submitButtonClassName={submitButtonClassName}
renderEditorPortal={renderEditorPortal}
buildPortalMenuStyle={buildPortalMenuStyle}
/>
</div>
{dialog.status === 'generating' ? (
<PlatformStatusMessage
tone="info"
surface="platform"
size="xs"
className="image-canvas-editor__generate-status"
role="status"
>
{submittingStatusLabel}
</PlatformStatusMessage>
) : null}
{dialog.status === 'failed' ? (
<PlatformStatusMessage
tone="error"
surface="platform"
size="xs"
className="image-canvas-editor__generate-status"
role="alert"
>
{dialog.errorMessage}
</PlatformStatusMessage>
) : null}
</form>
{isGenerationReferenceMenuOpen && generationReferenceButtonRef
{hasReferenceMenu &&
isGenerationReferenceMenuOpen &&
generationReferenceButtonRef
? renderEditorPortal(
<PlatformFloatingMenu
className="image-canvas-editor__spec-menu image-canvas-editor__portal-menu"
label="参考图来源"
label={referenceMenuLabel}
placement="top-start"
style={buildPortalMenuStyle(
generationReferenceButtonRef.current,
@@ -218,7 +315,11 @@ export function ImageCanvasBasicGenerationComposerView({
<PlatformFloatingMenuItem
onClick={() => {
setIsGenerationReferenceMenuOpen?.(false);
setIsPickingGenerationReferenceFromCanvas?.(true);
if (onPickReferenceFromCanvas) {
onPickReferenceFromCanvas();
} else {
setIsPickingGenerationReferenceFromCanvas?.(true);
}
}}
>
@@ -227,7 +328,7 @@ export function ImageCanvasBasicGenerationComposerView({
onClick={() => {
setIsGenerationReferenceMenuOpen?.(false);
setIsPickingGenerationReferenceFromCanvas?.(false);
onRequestUpload('generation-reference');
onRequestUpload(referenceUploadTarget);
}}
>
@@ -1,13 +1,11 @@
import { type Dispatch, type SetStateAction } from 'react';
import { PlatformActionButton } from '../common/PlatformActionButton';
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
import { PlatformTextField } from '../common/PlatformTextField';
import { UnifiedModal } from '../common/UnifiedModal';
import {
calculateEditorImageGenerationPrice,
IMAGE_MODEL_GPT_IMAGE_2,
} from './ImageCanvasGenerationModel';
import { ImageCanvasBasicGenerationComposerView } from './ImageCanvasBasicGenerationComposerView';
import type { GenerateDialogState } from './ImageCanvasEditorTypes';
type ImageCanvasEditGenerationModalViewProps = {
@@ -40,70 +38,32 @@ export function ImageCanvasEditGenerationModalView({
bodyClassName="image-canvas-editor__generate-dialog-body"
>
{dialog?.mode === 'edit' ? (
<form
className="image-canvas-editor__generate-form"
onSubmit={(event) => {
event.preventDefault();
if (dialog.status !== 'generating') {
onSubmit(dialog);
}
<ImageCanvasBasicGenerationComposerView
dialog={{
...dialog,
mode: 'edit',
imageModel: IMAGE_MODEL_GPT_IMAGE_2,
aspectRatio: dialog.aspectRatio ?? '1:1',
imageSize: dialog.imageSize ?? '1K',
generationReferences: [],
}}
>
<div className="image-canvas-editor__generate-body">
<PlatformTextField
variant="textarea"
aria-label="生成提示词"
value={dialog.prompt}
disabled={dialog.status === 'generating'}
size="sm"
density="roomy"
className="image-canvas-editor__generate-prompt"
placeholder="描述你想如何修改这张图片"
onChange={(event) =>
setGenerateDialog((currentDialog) =>
currentDialog
? {
...currentDialog,
prompt: event.target.value,
}
: currentDialog,
)
}
/>
{dialog.status === 'generating' ? (
<PlatformStatusMessage
tone="info"
surface="platform"
size="xs"
className="image-canvas-editor__generate-status"
role="status"
>
</PlatformStatusMessage>
) : null}
{dialog.status === 'failed' ? (
<PlatformStatusMessage
tone="error"
surface="platform"
size="xs"
className="image-canvas-editor__generate-status"
role="alert"
>
{dialog.errorMessage}
</PlatformStatusMessage>
) : null}
<PlatformActionButton
type="submit"
size="sm"
className="image-canvas-editor__generate-submit"
disabled={dialog.status === 'generating'}
>
{dialog.status === 'generating'
? '修改中'
: `修改${editPrice}泥点`}
</PlatformActionButton>
</div>
</form>
style={{}}
setGenerateDialog={setGenerateDialog}
onRequestUpload={() => {}}
onSubmit={onSubmit}
dialogLabel="修改图片"
includeDimensions={false}
includeModel={false}
includeReferences={false}
promptPlaceholder="描述你想如何修改这张图片"
submitLabel="修改"
submitAriaLabel={`修改${editPrice}泥点`}
submittingStatusLabel="修改中"
formClassName="image-canvas-editor__generate-form"
footerClassName="image-canvas-editor__generate-body"
promptClassName="image-canvas-editor__generate-prompt"
submitButtonClassName="image-canvas-editor__generate-submit"
/>
) : null}
</UnifiedModal>
);
@@ -28,8 +28,11 @@ type ImageCanvasGenerationImageOptionsViewProps = {
dialog: GenerateDialogState;
setGenerateDialog: Dispatch<SetStateAction<GenerateDialogState | null>>;
includeDimensions: boolean;
includeModel?: boolean;
onRememberImageModel: (model: string) => void;
optionLabelPrefix?: string;
dimensionRatioAriaLabelPrefix?: string;
dimensionSizeAriaLabelPrefix?: string;
cost?: number;
submitLabel?: string;
submitAriaLabel?: string;
@@ -87,11 +90,13 @@ function OptionChoice({
children,
selected,
className,
ariaLabel,
onClick,
}: {
children: ReactNode;
selected: boolean;
className?: string;
ariaLabel?: string;
onClick: () => void;
}) {
return (
@@ -100,6 +105,7 @@ function OptionChoice({
className={['image-canvas-editor__option-popover-choice', className]
.filter(Boolean)
.join(' ')}
aria-label={ariaLabel}
aria-pressed={selected}
onClick={onClick}
>
@@ -112,8 +118,11 @@ export function ImageCanvasGenerationImageOptionsView({
dialog,
setGenerateDialog,
includeDimensions,
includeModel = true,
onRememberImageModel,
optionLabelPrefix,
dimensionRatioAriaLabelPrefix,
dimensionSizeAriaLabelPrefix,
cost,
submitLabel = '生成',
submitAriaLabel = '生成',
@@ -138,10 +147,15 @@ export function ImageCanvasGenerationImageOptionsView({
imageSize: selection.imageSize,
});
const isGenerating = dialog.status === 'generating';
const isModelLocked = Boolean(normalizedLockedModel);
const isModelLocked = Boolean(normalizedLockedModel) && includeModel;
const labelPrefix = optionLabelPrefix ?? '生成图片';
const ratioAriaLabelPrefix = dimensionRatioAriaLabelPrefix?.trim() ?? '';
const sizeAriaLabelPrefix = dimensionSizeAriaLabelPrefix?.trim() ?? '';
const dismissOpenPanel = useCallback(() => setOpenPanel(null), []);
const formatDimensionOptionLabel = (prefix: string, value: string) =>
prefix ? `${prefix} ${value}` : value;
useImageCanvasFloatingOptionDismiss({
isOpen: openPanel !== null,
boundaryRefs: [dimensionsButtonRef, modelButtonRef],
@@ -236,6 +250,10 @@ export function ImageCanvasGenerationImageOptionsView({
key={aspectRatio}
selected={selection.aspectRatio === aspectRatio}
className="image-canvas-editor__option-popover-choice--ratio"
ariaLabel={formatDimensionOptionLabel(
ratioAriaLabelPrefix,
aspectRatio,
)}
onClick={() => updateDialog({ aspectRatio })}
>
<span
@@ -257,6 +275,10 @@ export function ImageCanvasGenerationImageOptionsView({
<OptionChoice
key={imageSize}
selected={selection.imageSize === imageSize}
ariaLabel={formatDimensionOptionLabel(
sizeAriaLabelPrefix,
imageSize,
)}
onClick={() => updateDialog({ imageSize })}
>
{imageSize}
@@ -270,80 +292,82 @@ export function ImageCanvasGenerationImageOptionsView({
: null}
</div>
) : null}
<div
className={[
'image-canvas-editor__option-popover-anchor image-canvas-editor__option-popover-anchor--model',
isModelLocked
? 'image-canvas-editor__readonly-generation-option'
: '',
]
.filter(Boolean)
.join(' ')}
>
<PlatformInlineOptionButton
ref={modelButtonRef}
className="image-canvas-editor__option-cluster image-canvas-editor__option-cluster--model"
aria-label={`${labelPrefix}模型 ${selectedModelLabel}`}
aria-expanded={isModelLocked ? undefined : openPanel === 'model'}
disabled={isGenerating || isModelLocked}
trailingIcon={
isModelLocked ? undefined : <ChevronDown className="h-3 w-3" />
}
onClick={() => {
if (!isModelLocked) {
togglePanel('model');
}
}}
{includeModel ? (
<div
className={[
'image-canvas-editor__option-popover-anchor image-canvas-editor__option-popover-anchor--model',
isModelLocked
? 'image-canvas-editor__readonly-generation-option'
: '',
]
.filter(Boolean)
.join(' ')}
>
<span className="image-canvas-editor__model-trigger-label">
<span
className="image-canvas-editor__model-icon"
aria-hidden="true"
>
<Cpu />
</span>
<span>{selectedModelLabel}</span>
</span>
</PlatformInlineOptionButton>
{!isModelLocked && openPanel === 'model'
? renderEditorPortal(
<PlatformFloatingMenu
className="image-canvas-editor__option-popover image-canvas-editor__option-popover--model image-canvas-editor__portal-menu"
label={`${labelPrefix}模型选项`}
placement="top-start"
style={buildPortalMenuStyle(modelButtonRef.current, 'above')}
onPointerDown={(event) => event.stopPropagation()}
<PlatformInlineOptionButton
ref={modelButtonRef}
className="image-canvas-editor__option-cluster image-canvas-editor__option-cluster--model"
aria-label={`${labelPrefix}模型 ${selectedModelLabel}`}
aria-expanded={isModelLocked ? undefined : openPanel === 'model'}
disabled={isGenerating || isModelLocked}
trailingIcon={
isModelLocked ? undefined : <ChevronDown className="h-3 w-3" />
}
onClick={() => {
if (!isModelLocked) {
togglePanel('model');
}
}}
>
<span className="image-canvas-editor__model-trigger-label">
<span
className="image-canvas-editor__model-icon"
aria-hidden="true"
>
<div className="image-canvas-editor__option-popover-items image-canvas-editor__option-popover-items--model">
{EDITOR_IMAGE_MODEL_OPTIONS.map((option) => {
const selected = selection.model === option.value;
return (
<OptionChoice
key={option.value}
selected={selected}
className="image-canvas-editor__option-popover-choice--model"
onClick={() => updateImageModel(option.value)}
>
<span
className="image-canvas-editor__model-icon"
aria-hidden="true"
<Cpu />
</span>
<span>{selectedModelLabel}</span>
</span>
</PlatformInlineOptionButton>
{!isModelLocked && openPanel === 'model'
? renderEditorPortal(
<PlatformFloatingMenu
className="image-canvas-editor__option-popover image-canvas-editor__option-popover--model image-canvas-editor__portal-menu"
label={`${labelPrefix}模型选项`}
placement="top-start"
style={buildPortalMenuStyle(modelButtonRef.current, 'above')}
onPointerDown={(event) => event.stopPropagation()}
>
<div className="image-canvas-editor__option-popover-items image-canvas-editor__option-popover-items--model">
{EDITOR_IMAGE_MODEL_OPTIONS.map((option) => {
const selected = selection.model === option.value;
return (
<OptionChoice
key={option.value}
selected={selected}
className="image-canvas-editor__option-popover-choice--model"
onClick={() => updateImageModel(option.value)}
>
<Cpu />
</span>
<span>{option.label}</span>
<Check
className="image-canvas-editor__option-selected-check"
data-visible={selected}
aria-hidden="true"
/>
</OptionChoice>
);
})}
</div>
</PlatformFloatingMenu>,
)
: null}
</div>
<span
className="image-canvas-editor__model-icon"
aria-hidden="true"
>
<Cpu />
</span>
<span>{option.label}</span>
<Check
className="image-canvas-editor__option-selected-check"
data-visible={selected}
aria-hidden="true"
/>
</OptionChoice>
);
})}
</div>
</PlatformFloatingMenu>,
)
: null}
</div>
) : null}
{typeof cost === 'number' ? (
<PlatformActionButton
type="submit"
File diff suppressed because it is too large Load Diff