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:
pythongosssss
2026-02-23 18:48:07 +00:00
committed by GitHub
parent 19dc48b69a
commit b3a5317500
11 changed files with 291 additions and 9 deletions

View 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>

View 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>

View 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>

View 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>

View File

@@ -0,0 +1,6 @@
export interface BuilderToolbarStep<T extends string = string> {
id: T
title: string
subtitle: string
icon: string
}