mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-09 23:20:04 +00:00
App mode - builder toolbar - 6 (#9029)
## Summary A toolbar for builder mode, hides various UI elements when in builder mode ## Screenshots (if applicable) Toolbar <img width="706" height="166" alt="image" src="https://github.com/user-attachments/assets/1f0b08b5-1661-4ed5-96bb-feecc73ca701" /> With disabled save and output required popover <img width="711" height="299" alt="image" src="https://github.com/user-attachments/assets/4a93aaf8-d850-4afe-ab9f-4abd44a25420" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9029-App-mode-builder-toolbar-6-30d6d73d365081e3aef5c90033ba347d) by [Unito](https://www.unito.io)
This commit is contained in:
105
src/components/builder/BuilderToolbar.vue
Normal file
105
src/components/builder/BuilderToolbar.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<nav
|
||||
class="fixed top-[calc(var(--workflow-tabs-height)+var(--spacing)*1.5)] left-1/2 z-[1000] -translate-x-1/2"
|
||||
:aria-label="t('builderToolbar.label')"
|
||||
>
|
||||
<div
|
||||
class="inline-flex items-center gap-1 rounded-2xl border border-border-default bg-base-background p-2 shadow-interface"
|
||||
>
|
||||
<template
|
||||
v-for="(step, index) in [selectStep, arrangeStep]"
|
||||
:key="step.id"
|
||||
>
|
||||
<button
|
||||
:class="
|
||||
cn(
|
||||
stepClasses,
|
||||
activeStep === step.id && 'bg-interface-builder-mode-background',
|
||||
activeStep !== step.id &&
|
||||
'hover:bg-secondary-background bg-transparent'
|
||||
)
|
||||
"
|
||||
:aria-current="activeStep === step.id ? 'step' : undefined"
|
||||
@click="appModeStore.setMode(step.id)"
|
||||
>
|
||||
<StepBadge :step :index :model-value="activeStep" />
|
||||
<StepLabel :step />
|
||||
</button>
|
||||
|
||||
<div class="mx-1 h-px w-4 bg-border-default" role="separator" />
|
||||
</template>
|
||||
|
||||
<!-- Save -->
|
||||
<ConnectOutputPopover
|
||||
v-if="!appModeStore.hasOutputs"
|
||||
:is-select-active="activeStep === 'builder:select'"
|
||||
@switch="appModeStore.setMode('builder:select')"
|
||||
>
|
||||
<button :class="cn(stepClasses, 'opacity-30 bg-transparent')">
|
||||
<StepBadge :step="saveStep" :index="2" :model-value="activeStep" />
|
||||
<StepLabel :step="saveStep" />
|
||||
</button>
|
||||
</ConnectOutputPopover>
|
||||
<button
|
||||
v-else
|
||||
:class="
|
||||
cn(
|
||||
stepClasses,
|
||||
activeStep === 'save'
|
||||
? 'bg-interface-builder-mode-background'
|
||||
: 'hover:bg-secondary-background bg-transparent'
|
||||
)
|
||||
"
|
||||
@click="appModeStore.setBuilderSaving(true)"
|
||||
>
|
||||
<StepBadge :step="saveStep" :index="2" :model-value="activeStep" />
|
||||
<StepLabel :step="saveStep" />
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useAppModeStore } from '@/stores/appModeStore'
|
||||
import type { AppMode } from '@/stores/appModeStore'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
import ConnectOutputPopover from './ConnectOutputPopover.vue'
|
||||
import StepBadge from './StepBadge.vue'
|
||||
import StepLabel from './StepLabel.vue'
|
||||
import type { BuilderToolbarStep } from './types'
|
||||
|
||||
const { t } = useI18n()
|
||||
const appModeStore = useAppModeStore()
|
||||
|
||||
const activeStep = computed(() =>
|
||||
appModeStore.isBuilderSaving ? 'save' : appModeStore.mode
|
||||
)
|
||||
|
||||
const stepClasses =
|
||||
'inline-flex h-14 min-h-8 cursor-pointer items-center gap-3 rounded-lg py-2 pr-4 pl-2 transition-colors border-none'
|
||||
|
||||
const selectStep: BuilderToolbarStep<AppMode> = {
|
||||
id: 'builder:select',
|
||||
title: t('builderToolbar.select'),
|
||||
subtitle: t('builderToolbar.selectDescription'),
|
||||
icon: 'icon-[lucide--mouse-pointer-click]'
|
||||
}
|
||||
|
||||
const arrangeStep: BuilderToolbarStep<AppMode> = {
|
||||
id: 'builder:arrange',
|
||||
title: t('builderToolbar.arrange'),
|
||||
subtitle: t('builderToolbar.arrangeDescription'),
|
||||
icon: 'icon-[lucide--layout-panel-left]'
|
||||
}
|
||||
|
||||
const saveStep: BuilderToolbarStep<'save'> = {
|
||||
id: 'save',
|
||||
title: t('builderToolbar.save'),
|
||||
subtitle: t('builderToolbar.saveDescription'),
|
||||
icon: 'icon-[lucide--cloud-upload]'
|
||||
}
|
||||
</script>
|
||||
78
src/components/builder/ConnectOutputPopover.vue
Normal file
78
src/components/builder/ConnectOutputPopover.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<PopoverRoot>
|
||||
<PopoverTrigger as-child>
|
||||
<slot />
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
side="bottom"
|
||||
:side-offset="8"
|
||||
:collision-padding="10"
|
||||
class="z-[1001] w-80 rounded-xl border border-border-default bg-base-background shadow-interface will-change-[transform,opacity] data-[state=open]:data-[side=bottom]:animate-slideUpAndFade"
|
||||
>
|
||||
<div class="flex h-12 items-center justify-between px-4">
|
||||
<h3 class="text-sm font-medium text-base-foreground">
|
||||
{{ t('builderToolbar.connectOutput') }}
|
||||
</h3>
|
||||
<PopoverClose
|
||||
:aria-label="t('g.close')"
|
||||
class="flex cursor-pointer appearance-none items-center justify-center border-none bg-transparent p-0 text-muted-foreground hover:text-base-foreground"
|
||||
>
|
||||
<i class="icon-[lucide--x] size-4" />
|
||||
</PopoverClose>
|
||||
</div>
|
||||
<div class="border-t border-border-default" />
|
||||
<p class="mt-3 px-4 text-xs text-muted-foreground leading-relaxed">
|
||||
{{ t('builderToolbar.connectOutputBody1') }}
|
||||
</p>
|
||||
<p
|
||||
v-if="!isSelectActive"
|
||||
class="mt-2 px-4 text-xs text-muted-foreground leading-relaxed"
|
||||
>
|
||||
{{ t('builderToolbar.connectOutputBody2') }}
|
||||
</p>
|
||||
<div class="mt-4 flex items-center justify-end gap-2 px-4 pb-4">
|
||||
<template v-if="isSelectActive">
|
||||
<PopoverClose as-child>
|
||||
<Button variant="secondary" size="md">
|
||||
{{ t('g.ok') }}
|
||||
</Button>
|
||||
</PopoverClose>
|
||||
</template>
|
||||
<template v-else>
|
||||
<PopoverClose as-child>
|
||||
<Button variant="muted-textonly" size="md">
|
||||
{{ t('g.cancel') }}
|
||||
</Button>
|
||||
</PopoverClose>
|
||||
<PopoverClose as-child>
|
||||
<Button variant="secondary" size="md" @click="emit('switch')">
|
||||
{{ t('builderToolbar.switchToSelect') }}
|
||||
</Button>
|
||||
</PopoverClose>
|
||||
</template>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</PopoverRoot>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
PopoverClose,
|
||||
PopoverContent,
|
||||
PopoverRoot,
|
||||
PopoverTrigger
|
||||
} from 'reka-ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
|
||||
const { isSelectActive = false } = defineProps<{
|
||||
isSelectActive?: boolean
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const emit = defineEmits<{
|
||||
switch: []
|
||||
}>()
|
||||
</script>
|
||||
22
src/components/builder/StepBadge.vue
Normal file
22
src/components/builder/StepBadge.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex size-10 shrink-0 items-center justify-center rounded-lg bg-interface-builder-mode-button-background text-interface-builder-mode-button-foreground"
|
||||
>
|
||||
<i v-if="modelValue === step.id" :class="cn(step.icon, 'size-5')" />
|
||||
<span v-else class="text-sm font-bold">
|
||||
{{ index + 1 }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
import type { BuilderToolbarStep } from './types'
|
||||
|
||||
defineProps<{
|
||||
step: BuilderToolbarStep
|
||||
index: number
|
||||
modelValue: string
|
||||
}>()
|
||||
</script>
|
||||
20
src/components/builder/StepLabel.vue
Normal file
20
src/components/builder/StepLabel.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-0.5 text-left">
|
||||
<span class="text-sm font-medium text-base-foreground">
|
||||
{{ step.title }}
|
||||
</span>
|
||||
<span
|
||||
class="hidden whitespace-nowrap text-xs text-muted-foreground sm:inline"
|
||||
>
|
||||
{{ step.subtitle }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { BuilderToolbarStep } from './types'
|
||||
|
||||
const { step } = defineProps<{
|
||||
step: BuilderToolbarStep
|
||||
}>()
|
||||
</script>
|
||||
6
src/components/builder/types.ts
Normal file
6
src/components/builder/types.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface BuilderToolbarStep<T extends string = string> {
|
||||
id: T
|
||||
title: string
|
||||
subtitle: string
|
||||
icon: string
|
||||
}
|
||||
Reference in New Issue
Block a user