Remove source app left border highlight

This commit is contained in:
Stephen Zhou
2026-05-27 20:18:13 +08:00
parent ae66ec279f
commit f30e4fa7cb
6 changed files with 122 additions and 205 deletions

View File

@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next'
import Link from '@/next/link'
import { DoneStep } from './done-step'
import { GuideActions, GuideCard, GuideFrame } from './layout'
import { DeploymentSummaryPreview, ReviewStep } from './review-step'
import { CreationSections } from './source-release-sections'
import { TargetReviewSections } from './target-step'
import { useCreateDeploymentGuide } from './use-create-deployment-guide'
@ -15,7 +14,6 @@ export function CreateDeploymentGuide() {
canContinue,
creationSectionsProps,
deployedEnvironmentName,
deploymentPreviewProps,
handleBack,
handlePrimaryAction,
isDeploying,
@ -25,29 +23,21 @@ export function CreateDeploymentGuide() {
targetReviewSectionsProps,
} = useCreateDeploymentGuide()
const deploymentPreview = (
<DeploymentSummaryPreview {...deploymentPreviewProps} />
)
const guideContent = (
<>
{step === 'done'
? (
<DoneStep environmentName={deployedEnvironmentName || selectedTargetEnvironmentName} />
)
: step === 'review'
: showTargetConfiguration
? (
<ReviewStep preview={deploymentPreview} />
<div className="flex flex-col gap-7 pb-4">
<TargetReviewSections {...targetReviewSectionsProps} />
</div>
)
: showTargetConfiguration
? (
<div className="flex flex-col gap-7 pb-4">
<TargetReviewSections {...targetReviewSectionsProps} />
</div>
)
: (
<CreationSections {...creationSectionsProps} />
)}
: (
<CreationSections {...creationSectionsProps} />
)}
</>
)

View File

@ -6,46 +6,71 @@ import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { useTranslation } from 'react-i18next'
const GUIDE_PROGRESS_STEPS: GuideStep[] = ['source', 'release', 'target', 'review']
const GUIDE_PROGRESS_STEPS: GuideStep[] = ['source', 'release', 'target']
function GuideStepIntro({ activeStep }: {
activeStep: GuideStep
}) {
const { t } = useTranslation('deployments')
let title: string
let description: string
if (activeStep === 'source') {
title = t('createGuide.source.title')
description = t('createGuide.method.description')
}
else if (activeStep === 'release') {
title = t('createGuide.release.title')
description = t('createGuide.release.description')
}
else if (activeStep === 'target') {
title = t('createGuide.target.title')
description = t('createGuide.target.description')
}
else {
return null
}
return (
<div className="pb-4">
<h2 className="system-md-semibold text-text-primary">{title}</h2>
<p className="mt-1 max-w-150 system-sm-regular text-text-tertiary">{description}</p>
</div>
)
}
function GuideProgress({ activeStep }: {
activeStep: GuideStep
}) {
const { t } = useTranslation('deployments')
const activeIndex = GUIDE_PROGRESS_STEPS.indexOf(activeStep)
return (
<ol className="mb-6 grid grid-cols-2 gap-2 sm:grid-cols-4">
{GUIDE_PROGRESS_STEPS.map((step, index) => {
<ol className="grid grid-cols-2 gap-1.5 sm:grid-cols-4">
{GUIDE_PROGRESS_STEPS.map((step) => {
const isActive = step === activeStep
const isComplete = activeIndex > index || activeStep === 'done'
const label = t(`createGuide.steps.${step}`)
return (
<li
key={step}
aria-current={isActive ? 'step' : undefined}
title={label}
className={cn(
'flex min-w-0 items-center gap-2 rounded-lg border px-3 py-2 system-xs-medium',
isActive
? 'border-state-accent-solid bg-state-accent-hover text-text-accent'
: isComplete
? 'border-util-colors-green-green-200 bg-util-colors-green-green-50 text-util-colors-green-green-700'
: 'border-divider-subtle bg-background-default text-text-tertiary',
'flex min-w-0 items-center gap-2 px-2 py-1.5 system-xs-medium',
isActive ? 'text-text-primary' : 'text-text-quaternary',
)}
>
<span
aria-hidden
className={cn(
'flex size-5 shrink-0 items-center justify-center rounded-full system-2xs-medium',
isComplete
? 'bg-util-colors-green-green-600 text-text-primary-on-surface'
: isActive
? 'bg-primary-600 text-text-primary-on-surface'
: 'bg-background-section-burn text-text-tertiary',
'size-2 shrink-0 rounded-full border-[1.5px]',
isActive
? 'border-text-primary bg-text-primary'
: 'border-text-quaternary bg-transparent',
)}
>
{isComplete ? <span className="i-ri-check-line size-3.5" /> : index + 1}
</span>
<span className="truncate">{t(`createGuide.steps.${step}`)}</span>
/>
<span className="truncate">{label}</span>
</li>
)
})}
@ -53,6 +78,51 @@ function GuideProgress({ activeStep }: {
)
}
function GuideProgressSummary({ activeStep }: {
activeStep: GuideStep
}) {
const { t } = useTranslation('deployments')
const activeIndex = GUIDE_PROGRESS_STEPS.indexOf(activeStep)
const activeStepNumber = activeIndex + 1
let activeStepLabel: string
if (activeStep === 'source')
activeStepLabel = t('createGuide.steps.source')
else if (activeStep === 'release')
activeStepLabel = t('createGuide.steps.release')
else if (activeStep === 'target')
activeStepLabel = t('createGuide.steps.target')
else
return null
if (activeIndex < 0)
return null
return (
<div className="flex w-full min-w-0 flex-col gap-2">
<div className="flex min-w-0 items-baseline justify-between gap-3">
<span className="truncate system-sm-medium text-text-secondary">{activeStepLabel}</span>
<span className="shrink-0 system-xs-regular text-text-quaternary">
{activeStepNumber}
/
{GUIDE_PROGRESS_STEPS.length}
</span>
</div>
<div className="grid grid-cols-3 gap-1" aria-hidden="true">
{GUIDE_PROGRESS_STEPS.map((step, index) => (
<span
key={step}
className={cn(
'h-1 rounded-full',
index <= activeIndex ? 'bg-text-primary' : 'bg-divider-subtle',
)}
/>
))}
</div>
</div>
)
}
export function StepShell({ title, description, descriptionClassName, hideHeader, children }: {
title: string
description: string
@ -94,23 +164,30 @@ export function GuideFrame({ activeStep, children }: {
const { t } = useTranslation('deployments')
return (
<div className="flex h-full min-h-0 overflow-hidden bg-background-default-subtle">
<div className="relative flex h-full min-h-0 overflow-hidden bg-background-default-subtle">
<div className="flex min-w-0 flex-1 shrink-0 justify-center overflow-y-auto">
<section
aria-label={t('createGuide.title')}
className="w-full max-w-[840px] px-5 sm:px-8 lg:px-10"
>
<div className="h-5 sm:h-8 lg:h-12" />
<div className="pt-1 pb-5">
<div className="pt-1 pb-4">
<h1 className="title-2xl-semi-bold text-text-primary">{t('createGuide.title')}</h1>
<p className="mt-1 max-w-150 system-sm-regular text-text-tertiary">
{t('createGuide.review.description')}
</p>
</div>
{activeStep !== 'done' && <GuideProgress activeStep={activeStep} />}
<GuideStepIntro activeStep={activeStep} />
{activeStep !== 'done' && (
<div className="mb-6 lg:hidden">
<GuideProgress activeStep={activeStep} />
</div>
)}
{children}
</section>
</div>
{activeStep !== 'done' && (
<aside className="pointer-events-none absolute top-24 left-[calc(50%+390px)] hidden w-[148px] min-[1120px]:block">
<GuideProgressSummary activeStep={activeStep} />
</aside>
)}
</div>
)
}
@ -130,19 +207,17 @@ export function GuideActions({
}) {
const { t } = useTranslation('deployments')
const primaryLabel = step === 'target'
? t('createGuide.actions.review')
: step === 'review'
? isDeploying ? t('createGuide.actions.deploying') : t('createGuide.actions.deploy')
: step === 'release' && isDeploying
? t('createGuide.actions.creating')
: t('createGuide.actions.next')
? isDeploying ? t('createGuide.actions.deploying') : t('createGuide.actions.deploy')
: step === 'release' && isDeploying
? t('createGuide.actions.creating')
: t('createGuide.actions.next')
if (step === 'method' || step === 'done')
return null
return (
<div className="flex items-center justify-end gap-2 pt-5 pb-10">
{(step === 'release' || step === 'target' || step === 'review') && (
<div className="flex items-center justify-end gap-2 pt-4 pb-8">
{(step === 'release' || step === 'target') && (
<Button type="button" variant="secondary" onClick={onBack} disabled={isDeploying}>
{t('createGuide.actions.back')}
</Button>

View File

@ -1,122 +0,0 @@
'use client'
import type {
CredentialSlot,
} from '@dify/contracts/enterprise/types.gen'
import type { ReactNode } from 'react'
import type { BindingSelections } from './types'
import { cn } from '@langgenius/dify-ui/cn'
import { useTranslation } from 'react-i18next'
import {
runtimeCredentialCandidateOptions,
runtimeCredentialSlotKey,
} from '../components/runtime-credential-bindings-utils'
import { StepShell } from './layout'
export function DeploymentSummaryPreview({
sourceName,
instanceName,
releaseName,
releaseDescription,
targetEnvironmentName,
bindingSlots,
bindingSelections,
}: {
sourceName: string
instanceName: string
releaseName: string
releaseDescription: string
targetEnvironmentName: string
bindingSlots: CredentialSlot[]
bindingSelections: BindingSelections
}) {
const { t } = useTranslation('deployments')
const displayValue = (value: string) => value || '—'
const sourceDisplayName = displayValue(sourceName)
const instanceDisplayName = displayValue(instanceName)
const releaseDisplayName = displayValue(releaseName)
const environmentDisplayName = displayValue(targetEnvironmentName)
const routeItems = [
{
icon: 'i-ri-apps-2-line',
label: t('createGuide.review.source'),
meta: `${t('createGuide.review.instance')} ${instanceDisplayName}`,
value: sourceDisplayName,
},
{
icon: 'i-ri-price-tag-3-line',
label: t('createGuide.review.release'),
value: releaseDisplayName,
},
{
icon: 'i-ri-cloud-line',
label: t('createGuide.review.environment'),
value: environmentDisplayName,
},
]
return (
<div className="flex min-w-0 flex-col gap-5 rounded-xl border border-components-panel-border bg-components-panel-bg p-4 sm:p-5">
<div className="flex flex-col">
{routeItems.map((item, index) => (
<div key={item.label} className="flex min-w-0 gap-3">
<div className="flex w-8 shrink-0 flex-col items-center">
<span className="flex size-8 items-center justify-center rounded-lg border border-divider-subtle bg-background-default-subtle">
<span className={cn('size-4 text-text-tertiary', item.icon)} aria-hidden="true" />
</span>
{index < routeItems.length - 1 && <span className="my-1 h-5 w-px bg-divider-subtle" aria-hidden="true" />}
</div>
<div className="min-w-0 flex-1 pb-3">
<div className="system-2xs-medium-uppercase text-text-tertiary">{item.label}</div>
<div className="system-sm-semibold break-words text-text-primary" title={item.value}>{item.value}</div>
{item.meta && <div className="mt-0.5 system-xs-regular break-words text-text-tertiary" title={item.meta}>{item.meta}</div>}
</div>
</div>
))}
</div>
<div>
<div className="system-xs-medium-uppercase text-text-tertiary">{t('createGuide.review.bindings')}</div>
<div className="mt-2 flex flex-col gap-1.5">
{bindingSlots.length === 0
? (
<div className="rounded-lg bg-background-default-subtle px-3 py-2 system-xs-regular text-text-tertiary">
{t('createGuide.target.noBindingRequired')}
</div>
)
: bindingSlots.map((slot) => {
const slotKey = runtimeCredentialSlotKey(slot)
const selectedValue = bindingSelections[slotKey] ?? ''
const selectedCandidate = runtimeCredentialCandidateOptions(slot).find(candidate => candidate.value === selectedValue)
return (
<div key={slotKey} className="grid min-w-0 grid-cols-1 gap-1 rounded-lg bg-background-default-subtle px-3 py-2 sm:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)] sm:gap-2">
<span className="system-xs-medium break-words text-text-secondary">{slot.providerId || slotKey}</span>
<span className="system-xs-regular break-words text-text-tertiary sm:text-right">{selectedCandidate?.label || '—'}</span>
</div>
)
})}
</div>
</div>
<div>
<div className="system-xs-medium-uppercase text-text-tertiary">{t('createGuide.review.releaseNote')}</div>
<div className="mt-1 line-clamp-3 system-xs-regular whitespace-pre-wrap text-text-secondary">{releaseDescription || '—'}</div>
</div>
</div>
)
}
export function ReviewStep({
preview,
}: {
preview: ReactNode
}) {
const { t } = useTranslation('deployments')
return (
<StepShell
title={t('createGuide.review.title')}
description={t('createGuide.review.description')}
>
{preview}
</StepShell>
)
}

View File

@ -42,10 +42,10 @@ function SourceAppOption({ app, selected, onSelect }: {
return (
<label
className={cn(
'group flex min-h-14 cursor-pointer items-center gap-3 border-b border-l-2 border-b-divider-subtle px-3 py-2 transition-colors first:rounded-t-lg last:rounded-b-lg last:border-b-0',
'group flex min-h-14 cursor-pointer items-center gap-3 border-b border-b-divider-subtle px-3 py-2 transition-colors first:rounded-t-lg last:rounded-b-lg last:border-b-0',
selected
? 'border-l-state-accent-solid bg-state-accent-hover hover:bg-state-accent-hover'
: 'border-l-transparent bg-background-default hover:bg-state-base-hover',
? 'bg-state-accent-hover hover:bg-state-accent-hover'
: 'bg-background-default hover:bg-state-base-hover',
)}
>
<AppIcon

View File

@ -4,6 +4,6 @@ import type {
import type { RuntimeCredentialBindingSelections } from '../components/runtime-credential-bindings-utils'
export type GuideMethod = 'bindApp' | 'importDsl'
export type GuideStep = 'method' | 'source' | 'release' | 'target' | 'review' | 'done'
export type GuideStep = 'method' | 'source' | 'release' | 'target' | 'done'
export type EnvironmentOption = Environment & { id: string }
export type BindingSelections = RuntimeCredentialBindingSelections

View File

@ -102,7 +102,7 @@ export function useCreateDeploymentGuide() {
const effectiveSelectedApp = selectedApp ?? sourceApps[0]
const hasDslContent = Boolean(dslContent.trim())
const encodedDslContent = hasDslContent ? encodeUtf8Base64(dslContent) : ''
const shouldResolveDeploymentTarget = step === 'target' || step === 'review'
const shouldResolveDeploymentTarget = step === 'target'
const shouldLoadSourceDeploymentTarget = method === 'bindApp' && Boolean(effectiveSelectedApp?.id) && shouldResolveDeploymentTarget
const shouldLoadDslDeploymentTarget = method === 'importDsl' && hasDslContent && shouldResolveDeploymentTarget
const shouldLoadDeploymentTarget = shouldLoadSourceDeploymentTarget || shouldLoadDslDeploymentTarget
@ -234,17 +234,6 @@ export function useCreateDeploymentGuide() {
&& requiredBindingsReady,
)
}
if (step === 'review') {
return Boolean(
selectedEnvironment?.id
&& shouldLoadDeploymentTarget
&& !isEnvironmentLoading
&& !deployableEnvironmentsQuery.isError
&& !isBindingLoading
&& !deploymentOptionsQuery.isError
&& requiredBindingsReady,
)
}
return false
}
@ -255,8 +244,6 @@ export function useCreateDeploymentGuide() {
setStep('source')
else if (step === 'target')
setStep('release')
else if (step === 'review')
setStep('target')
}
async function createReleaseArtifactsAndContinue() {
@ -344,10 +331,6 @@ export function useCreateDeploymentGuide() {
return
}
if (step === 'target') {
setStep('review')
return
}
if (step === 'review') {
void handleDeploy()
}
}
@ -399,15 +382,6 @@ export function useCreateDeploymentGuide() {
stage: step === 'release' ? 'release' as const : 'source' as const,
},
deployedEnvironmentName,
deploymentPreviewProps: {
bindingSelections,
bindingSlots,
instanceName: displayedInstanceName,
releaseDescription: displayedReleaseDescription,
releaseName: displayedReleaseName,
sourceName,
targetEnvironmentName: selectedTargetEnvironmentName,
},
handleBack,
handlePrimaryAction,
isDeploying,