mirror of
https://github.com/langgenius/dify.git
synced 2026-05-25 19:00:43 -04:00
feat(dify-ui): add shared form primitives (#36334)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
@@ -317,6 +317,20 @@ describe('InstallFromGitHub', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should submit the repo URL form from the set URL step', async () => {
|
||||
render(<InstallFromGitHub {...defaultProps} />)
|
||||
|
||||
const input = getRepoUrlInput()
|
||||
const form = input.closest('form')
|
||||
fireEvent.change(input, { target: { value: 'https://github.com/owner/repo' } })
|
||||
|
||||
expect(form).toBeInTheDocument()
|
||||
expect(getNextButton()).toHaveAttribute('type', 'submit')
|
||||
fireEvent.submit(form!)
|
||||
|
||||
expect(await screen.findByRole('button', { name: 'Select Version' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should update selectedVersion when version is selected', async () => {
|
||||
render(<InstallFromGitHub {...defaultProps} updatePayload={createUpdatePayload()} />)
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import type { InstallState } from '@/app/components/plugins/types'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { FieldControl, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { Form } from '@langgenius/dify-ui/form'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
@@ -194,23 +196,24 @@ const InstallFromGitHub: React.FC<InstallFromGitHubProps> = ({ updatePayload, on
|
||||
: (
|
||||
<div className={`flex min-h-0 flex-1 flex-col items-start justify-center self-stretch overflow-y-auto px-6 py-3 ${state.step === InstallStepFromGitHub.installed ? 'gap-2' : 'gap-4'}`}>
|
||||
{state.step === InstallStepFromGitHub.setUrl && (
|
||||
<>
|
||||
<label
|
||||
htmlFor="repoUrl"
|
||||
className="flex flex-col items-start justify-center self-stretch text-text-secondary"
|
||||
>
|
||||
<span className="system-sm-semibold">{t('installFromGitHub.gitHubRepo', { ns: 'plugin' })}</span>
|
||||
</label>
|
||||
<input
|
||||
autoFocus
|
||||
type="url"
|
||||
id="repoUrl"
|
||||
name="repoUrl"
|
||||
value={state.repoUrl}
|
||||
onChange={e => setState(prevState => ({ ...prevState, repoUrl: e.target.value }))}
|
||||
className="flex grow items-center gap-0.5 self-stretch overflow-hidden rounded-lg border border-components-input-border-active bg-components-input-bg-active p-2 system-sm-regular text-ellipsis text-components-input-text-filled outline-hidden"
|
||||
placeholder="Please enter GitHub repo URL"
|
||||
/>
|
||||
<Form
|
||||
onFormSubmit={handleUrlSubmit}
|
||||
className="flex flex-col items-start gap-4 self-stretch"
|
||||
>
|
||||
<FieldRoot name="repoUrl" className="gap-4 self-stretch">
|
||||
<FieldLabel className="flex w-full flex-col items-start justify-center p-0 text-text-secondary">
|
||||
<span className="system-sm-semibold">{t('installFromGitHub.gitHubRepo', { ns: 'plugin' })}</span>
|
||||
</FieldLabel>
|
||||
<FieldControl
|
||||
autoFocus
|
||||
type="text"
|
||||
inputMode="url"
|
||||
value={state.repoUrl}
|
||||
onValueChange={value => setState(prevState => ({ ...prevState, repoUrl: value }))}
|
||||
className="flex grow items-center gap-0.5 self-stretch overflow-hidden rounded-lg border-components-input-border-active bg-components-input-bg-active p-2 text-ellipsis"
|
||||
placeholder="Please enter GitHub repo URL"
|
||||
/>
|
||||
</FieldRoot>
|
||||
<div className="mt-4 flex items-center justify-end gap-2 self-stretch">
|
||||
<Button
|
||||
variant="secondary"
|
||||
@@ -221,14 +224,14 @@ const InstallFromGitHub: React.FC<InstallFromGitHubProps> = ({ updatePayload, on
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
type="submit"
|
||||
className="min-w-18"
|
||||
onClick={handleUrlSubmit}
|
||||
disabled={!state.repoUrl.trim()}
|
||||
>
|
||||
{t('installModal.next', { ns: 'plugin' })}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
</Form>
|
||||
)}
|
||||
{state.step === InstallStepFromGitHub.selectPackage && (
|
||||
<SelectPackage
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import type { PluginDeclaration, UpdateFromGitHubPayload } from '../../../types'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { FieldLabel, FieldRoot } from '@langgenius/dify-ui/field'
|
||||
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectTrigger } from '@langgenius/dify-ui/select'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -77,79 +78,77 @@ const SelectPackage: React.FC<SelectPackageProps> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<label
|
||||
htmlFor="version"
|
||||
className="flex flex-col items-start justify-center self-stretch text-text-secondary"
|
||||
>
|
||||
<span className="system-sm-semibold">{t(`${i18nPrefix}.selectVersion`, { ns: 'plugin' })}</span>
|
||||
</label>
|
||||
<Select
|
||||
value={selectedVersionOption ? String(selectedVersionOption.value) : null}
|
||||
onValueChange={(value) => {
|
||||
if (!value)
|
||||
return
|
||||
const selectedItem = versions.find(item => String(item.value) === value)
|
||||
if (selectedItem)
|
||||
onSelectVersion(selectedItem)
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-9 text-components-input-text-filled">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="truncate">
|
||||
{selectedVersionOption?.name ?? t(`${i18nPrefix}.selectVersionPlaceholder`, { ns: 'plugin' }) ?? ''}
|
||||
</span>
|
||||
{!!(updatePayload?.originalPackageInfo.version && selectedVersionOption && selectedVersionOption.value !== updatePayload.originalPackageInfo.version) && (
|
||||
<Badge>
|
||||
{updatePayload.originalPackageInfo.version}
|
||||
{' '}
|
||||
{'->'}
|
||||
{' '}
|
||||
{selectedVersionOption.value}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</SelectTrigger>
|
||||
<SelectContent popupClassName="w-[512px]">
|
||||
{versions.map(item => (
|
||||
<SelectItem key={item.value} value={String(item.value)}>
|
||||
<SelectItemText>{item.name}</SelectItemText>
|
||||
{item.value === updatePayload?.originalPackageInfo.version && (
|
||||
<Badge uppercase={true} className="ml-1 shrink-0">INSTALLED</Badge>
|
||||
<FieldRoot name="version" className="gap-4 self-stretch">
|
||||
<FieldLabel className="flex w-full flex-col items-start justify-center p-0 text-text-secondary">
|
||||
<span className="system-sm-semibold">{t(`${i18nPrefix}.selectVersion`, { ns: 'plugin' })}</span>
|
||||
</FieldLabel>
|
||||
<Select
|
||||
value={selectedVersionOption ? String(selectedVersionOption.value) : null}
|
||||
onValueChange={(value) => {
|
||||
if (!value)
|
||||
return
|
||||
const selectedItem = versions.find(item => String(item.value) === value)
|
||||
if (selectedItem)
|
||||
onSelectVersion(selectedItem)
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-9 text-components-input-text-filled">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="truncate">
|
||||
{selectedVersionOption?.name ?? t(`${i18nPrefix}.selectVersionPlaceholder`, { ns: 'plugin' }) ?? ''}
|
||||
</span>
|
||||
{!!(updatePayload?.originalPackageInfo.version && selectedVersionOption && selectedVersionOption.value !== updatePayload.originalPackageInfo.version) && (
|
||||
<Badge>
|
||||
{updatePayload.originalPackageInfo.version}
|
||||
{' '}
|
||||
{'->'}
|
||||
{' '}
|
||||
{selectedVersionOption.value}
|
||||
</Badge>
|
||||
)}
|
||||
<SelectItemIndicator />
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<label
|
||||
htmlFor="package"
|
||||
className="flex flex-col items-start justify-center self-stretch text-text-secondary"
|
||||
>
|
||||
<span className="system-sm-semibold">{t(`${i18nPrefix}.selectPackage`, { ns: 'plugin' })}</span>
|
||||
</label>
|
||||
<Select
|
||||
value={selectedPackageOption ? String(selectedPackageOption.value) : null}
|
||||
readOnly={!selectedVersion}
|
||||
onValueChange={(value) => {
|
||||
if (!value)
|
||||
return
|
||||
const selectedItem = packages.find(item => String(item.value) === value)
|
||||
if (selectedItem)
|
||||
onSelectPackage(selectedItem)
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-9 text-components-input-text-filled">
|
||||
{selectedPackageOption?.name ?? t(`${i18nPrefix}.selectPackagePlaceholder`, { ns: 'plugin' }) ?? ''}
|
||||
</SelectTrigger>
|
||||
<SelectContent popupClassName="w-[512px]">
|
||||
{packages.map(item => (
|
||||
<SelectItem key={item.value} value={String(item.value)}>
|
||||
<SelectItemText>{item.name}</SelectItemText>
|
||||
<SelectItemIndicator />
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</SelectTrigger>
|
||||
<SelectContent popupClassName="w-[512px]">
|
||||
{versions.map(item => (
|
||||
<SelectItem key={item.value} value={String(item.value)}>
|
||||
<SelectItemText>{item.name}</SelectItemText>
|
||||
{item.value === updatePayload?.originalPackageInfo.version && (
|
||||
<Badge uppercase={true} className="ml-1 shrink-0">INSTALLED</Badge>
|
||||
)}
|
||||
<SelectItemIndicator />
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FieldRoot>
|
||||
<FieldRoot name="package" className="gap-4 self-stretch">
|
||||
<FieldLabel className="flex w-full flex-col items-start justify-center p-0 text-text-secondary">
|
||||
<span className="system-sm-semibold">{t(`${i18nPrefix}.selectPackage`, { ns: 'plugin' })}</span>
|
||||
</FieldLabel>
|
||||
<Select
|
||||
value={selectedPackageOption ? String(selectedPackageOption.value) : null}
|
||||
readOnly={!selectedVersion}
|
||||
onValueChange={(value) => {
|
||||
if (!value)
|
||||
return
|
||||
const selectedItem = packages.find(item => String(item.value) === value)
|
||||
if (selectedItem)
|
||||
onSelectPackage(selectedItem)
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-9 text-components-input-text-filled">
|
||||
{selectedPackageOption?.name ?? t(`${i18nPrefix}.selectPackagePlaceholder`, { ns: 'plugin' }) ?? ''}
|
||||
</SelectTrigger>
|
||||
<SelectContent popupClassName="w-[512px]">
|
||||
{packages.map(item => (
|
||||
<SelectItem key={item.value} value={String(item.value)}>
|
||||
<SelectItemText>{item.name}</SelectItemText>
|
||||
<SelectItemIndicator />
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FieldRoot>
|
||||
<div className="mt-4 flex items-center justify-end gap-2 self-stretch">
|
||||
{!isEdit
|
||||
&& (
|
||||
|
||||
Reference in New Issue
Block a user