update the plugins page

This commit is contained in:
Yi 2024-09-14 17:09:25 +08:00
parent 6613b8f2e0
commit 21193c2fbf
22 changed files with 609 additions and 46 deletions

View File

@ -0,0 +1,94 @@
'use client'
import { useMemo, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import {
RiArrowRightUpLine,
RiBugLine,
RiDragDropLine,
RiEqualizer2Line,
} from '@remixicon/react'
import InstallPluginDropdown from './InstallPluginDropdown'
import { useTabSearchParams } from '@/hooks/use-tab-searchparams'
import Button from '@/app/components/base/button'
import TabSlider from '@/app/components/base/tab-slider'
import Tooltip from '@/app/components/base/tooltip'
const Container = () => {
const { t } = useTranslation()
const options = useMemo(() => {
return [
{ value: 'plugins', text: t('common.menus.plugins') },
{ value: 'discover', text: 'Discover in Marketplace' },
]
}, [t])
const [activeTab, setActiveTab] = useTabSearchParams({
defaultTab: 'plugins',
})
const containerRef = useRef<HTMLDivElement>(null)
return (
<div ref={containerRef} className='grow relative flex flex-col rounded-t-xl bg-components-panel-bg border-t
border-divider-subtle overflow-y-auto'>
<div className='flex min-h-[60px] px-12 pt-4 pb-2 items-center self-stretch gap-1'>
<div className='flex justify-between items-center w-full'>
<div className='flex-1'>
<TabSlider
value={activeTab}
onChange={newActiveTab => setActiveTab(newActiveTab)}
options={options}
/>
</div>
<div className='flex flex-shrink-0 items-center gap-1'>
<InstallPluginDropdown />
<Tooltip
triggerMethod='click'
popupContent={
<>
<div className='flex items-center gap-1 self-stretch'>
<span className='flex flex-col justify-center items-start flex-grow flex-shrink-0 basis-0 text-text-secondary system-sm-semibold'>Debugging</span>
<div className='flex items-center gap-0.5 text-text-accent-light-mode-only cursor-pointer'>
<span className='system-xs-medium'>View docs</span>
<RiArrowRightUpLine className='w-3 h-3' />
</div>
</div>
<div className='flex flex-col items-start gap-0.5 self-stretch'>
<div className='flex items-center gap-1 self-stretch'>
<span className='flex w-10 flex-col justify-center items-start text-text-tertiary system-xs-medium'>Port</span>
</div>
</div>
</>
}
popupClassName='flex flex-col items-start w-[256px] px-4 py-3.5 gap-1 border border-components-panel-border
rounded-xl bg-components-tooltip-bg shadows-shadow-lg'
asChild={false}
position='bottom'
>
<Button className='w-full h-full p-2 text-components-button-secondary-text'>
<RiBugLine className='w-4 h-4' />
</Button>
</Tooltip>
<Button className='w-full h-full p-2 text-components-button-secondary-text'>
<RiEqualizer2Line className='w-4 h-4' />
</Button>
</div>
</div>
</div>
<div className='flex flex-col flex-grow pt-1 pb-3 px-12 justify-center items-start gap-3 self-stretch'>
<div className='h-px self-stretch bg-divider-subtle'></div>
<div className='flex items-center gap-2 self-stretch'>
{/* Content for active tab will go here */}
</div>
</div>
<div className='flex items-center justify-center py-4 gap-2 text-text-quaternary'>
<RiDragDropLine className='w-4 h-4' />
<span className='system-xs-regular'>Drop plugin package here to install</span>
</div>
</div>
)
}
export default Container

View File

@ -0,0 +1,67 @@
'use client'
import { useEffect, useRef, useState } from 'react'
import { RiAddLine, RiArrowDownSLine } from '@remixicon/react'
import Button from '@/app/components/base/button'
import { MagicBox } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices'
import { FileZip } from '@/app/components/base/icons/src/vender/solid/files'
import { Github } from '@/app/components/base/icons/src/vender/solid/general'
const InstallPluginDropdown = () => {
const [isMenuOpen, setIsMenuOpen] = useState(false)
const menuRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (menuRef.current && !menuRef.current.contains(event.target as Node))
setIsMenuOpen(false)
}
document.addEventListener('mousedown', handleClickOutside)
return () => {
document.removeEventListener('mousedown', handleClickOutside)
}
}, [])
return (
<div className="relative" ref={menuRef}>
<Button
className='w-full h-full p-2 text-components-button-secondary-text'
onClick={() => setIsMenuOpen(!isMenuOpen)}
>
<RiAddLine className='w-4 h-4' />
<span className='pl-1'>Install plugin</span>
<RiArrowDownSLine className='w-4 h-4 ml-1' />
</Button>
{isMenuOpen && (
<div className='flex flex-col items-start absolute z-1000 top-full left-0 mt-1 p-1 pb-2
w-[200px] bg-components-panel-bg-blur border border-components-panel-border rounded-xl
shadows-shadow-lg'>
<span className='flex pt-1 pb-0.5 pl-2 pr-3 items-start self-stretch text-text-tertiary
system-xs-medium-uppercase'>
Install Form
</span>
{[
{ icon: MagicBox, text: 'Marketplace', action: 'marketplace' },
{ icon: Github, text: 'GitHub', action: 'github' },
{ icon: FileZip, text: 'Local Package File', action: 'local' },
].map(({ icon: Icon, text, action }) => (
<div
key={action}
className='flex items-center w-full px-2 py-1.5 gap-1 rounded-lg hover:bg-state-base-hover cursor-pointer'
onClick={() => {
console.log(action)
setIsMenuOpen(false)
}}
>
<Icon className="w-4 h-4 text-text-tertiary" />
<span className='px-1 text-text-secondary system-md-regular'>{text}</span>
</div>
))}
</div>
)}
</div>
)
}
export default InstallPluginDropdown

View File

@ -0,0 +1,13 @@
import Container from './Container'
const PluginList = async () => {
return (
<Container />
)
}
export const metadata = {
title: 'Plugins - Dify',
}
export default PluginList

View File

@ -0,0 +1,28 @@
@tailwind components;
@layer components {
.badge {
@apply inline-flex justify-center items-center text-text-tertiary border border-divider-deep
}
.badge-l {
@apply rounded-md gap-1 min-w-6
}
/* m is for the regular button */
.badge-m {
@apply rounded-md gap-[3px] min-w-5
}
.badge-s {
@apply rounded-[5px] gap-0.5 min-w-[18px]
}
.badge.badge-warning {
@apply text-text-warning border border-text-warning
}
.badge.badge-accent {
@apply text-text-accent-secondary border border-text-accent-secondary
}
}

View File

@ -0,0 +1,81 @@
import type { CSSProperties, ReactNode } from 'react'
import React from 'react'
import { type VariantProps, cva } from 'class-variance-authority'
import classNames from '@/utils/classnames'
import './index.css'
enum BadgeState {
Warning = 'warning',
Accent = 'accent',
Default = '',
}
const BadgeVariants = cva(
'badge',
{
variants: {
size: {
s: 'badge-s',
m: 'badge-m',
l: 'badge-l',
},
},
defaultVariants: {
size: 'm',
},
},
)
type BadgeProps = {
size?: 's' | 'm' | 'l'
iconOnly?: boolean
uppercase?: boolean
state?: BadgeState
styleCss?: CSSProperties
children?: ReactNode
} & React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof BadgeVariants>
function getBadgeState(state: BadgeState) {
switch (state) {
case BadgeState.Warning:
return 'badge-warning'
case BadgeState.Accent:
return 'badge-accent'
default:
return ''
}
}
const Badge: React.FC<BadgeProps> = ({
className,
size,
state = BadgeState.Default,
iconOnly = false,
uppercase = false,
styleCss,
children,
...props
}) => {
return (
<div
className={classNames(
BadgeVariants({ size, className }),
getBadgeState(state),
size === 's'
? (iconOnly ? 'p-[3px]' : 'px-[5px] py-[3px]')
: size === 'l'
? (iconOnly ? 'p-1.5' : 'px-2 py-1')
: (iconOnly ? 'p-1' : 'px-[5px] py-[2px]'),
uppercase ? 'system-2xs-medium-uppercase' : 'system-2xs-medium',
)}
style={styleCss}
{...props}
>
{children}
</div>
)
}
Badge.displayName = 'Badge'
export default Badge
export { Badge, BadgeState, BadgeVariants }

View File

@ -0,0 +1,8 @@
<svg width="14" height="16" viewBox="0 0 14 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Group">
<path id="Vector" d="M5.6475 8.0115L0.333496 5.05884V11.3335C0.333491 11.4524 0.365258 11.569 0.425506 11.6715C0.485754 11.7739 0.572294 11.8584 0.676163 11.9162L6.3335 15.0588V9.17684C6.33344 8.93907 6.26981 8.70565 6.14919 8.50075C6.02857 8.29586 5.85536 8.12694 5.6475 8.0115Z" fill="#354052"/>
<path id="Vector_2" d="M7.66699 9.17684V15.0588L13.3243 11.9162C13.4282 11.8584 13.5147 11.7739 13.575 11.6715C13.6352 11.569 13.667 11.4524 13.667 11.3335V5.05884L8.35299 8.0115C8.14513 8.12694 7.97192 8.29586 7.8513 8.50075C7.73068 8.70565 7.66705 8.93907 7.66699 9.17684Z" fill="#676F83"/>
<path id="Vector_3" d="M10.1913 2.34351C9.804 3.33351 8.588 4.00017 7 4.00017C5.412 4.00017 4.196 3.33351 3.80867 2.34351L1 3.90417L6.35267 6.87817C6.5507 6.98815 6.77348 7.04586 7 7.04586C7.22652 7.04586 7.4493 6.98815 7.64733 6.87817L13 3.90417L10.1913 2.34351Z" fill="#676F83"/>
<path id="Vector_4" d="M7 2.66675C8.10457 2.66675 9 2.21903 9 1.66675C9 1.11446 8.10457 0.666748 7 0.666748C5.89543 0.666748 5 1.11446 5 1.66675C5 2.21903 5.89543 2.66675 7 2.66675Z" fill="#354052"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Icon">
<path id="Vector" d="M3.99999 1.33325H7.99999V5.33325C7.99999 6.06963 8.59692 6.66659 9.33332 6.66659H13.3333V13.3333C13.3333 14.0697 12.7364 14.6666 12 14.6666H6.66666V13.3333H7.99999V11.9999H6.66666V10.6666H7.99999V9.33325H6.66666V7.99992H5.33332V9.33325H6.66666V10.6666H5.33332V11.9999H6.66666V13.3333H5.33332V14.6666H3.99999C3.26361 14.6666 2.66666 14.0697 2.66666 13.3333V2.66659C2.66666 1.93021 3.26361 1.33325 3.99999 1.33325Z" fill="#676F83"/>
<path id="Vector_2" opacity="0.5" d="M12.9428 4.99993C13.0415 5.09868 13.1232 5.21133 13.1859 5.33327H9.33334V1.48071C9.45528 1.54338 9.56794 1.62504 9.66668 1.72379L12.9428 4.99993Z" fill="#676F83"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 775 B

View File

@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Icon">
<path id="Vector" d="M8 1C4.1325 1 1 4.1325 1 8C1 11.0975 3.00375 13.7137 5.78625 14.6413C6.13625 14.7025 6.2675 14.4925 6.2675 14.3088C6.2675 14.1425 6.25875 13.5913 6.25875 13.005C4.5 13.3288 4.045 12.5763 3.905 12.1825C3.82625 11.9812 3.485 11.36 3.1875 11.1937C2.9425 11.0625 2.5925 10.7387 3.17875 10.73C3.73 10.7212 4.12375 11.2375 4.255 11.4475C4.885 12.5062 5.89125 12.2088 6.29375 12.025C6.355 11.57 6.53875 11.2638 6.74 11.0887C5.1825 10.9137 3.555 10.31 3.555 7.6325C3.555 6.87125 3.82625 6.24125 4.2725 5.75125C4.2025 5.57625 3.9575 4.85875 4.3425 3.89625C4.3425 3.89625 4.92875 3.7125 6.2675 4.61375C6.8275 4.45625 7.4225 4.3775 8.0175 4.3775C8.6125 4.3775 9.2075 4.45625 9.7675 4.61375C11.1063 3.70375 11.6925 3.89625 11.6925 3.89625C12.0775 4.85875 11.8325 5.57625 11.7625 5.75125C12.2087 6.24125 12.48 6.8625 12.48 7.6325C12.48 10.3187 10.8438 10.9137 9.28625 11.0887C9.54 11.3075 9.75875 11.7275 9.75875 12.3837C9.75875 13.32 9.75 14.0725 9.75 14.3088C9.75 14.4925 9.88125 14.7113 10.2312 14.6413C11.6209 14.1721 12.8284 13.279 13.6839 12.0877C14.5393 10.8963 14.9996 9.46668 15 8C15 4.1325 11.8675 1 8 1Z" fill="#676F83"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,66 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "14",
"height": "16",
"viewBox": "0 0 14 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "Group"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Vector",
"d": "M5.6475 8.0115L0.333496 5.05884V11.3335C0.333491 11.4524 0.365258 11.569 0.425506 11.6715C0.485754 11.7739 0.572294 11.8584 0.676163 11.9162L6.3335 15.0588V9.17684C6.33344 8.93907 6.26981 8.70565 6.14919 8.50075C6.02857 8.29586 5.85536 8.12694 5.6475 8.0115Z",
"fill": "currentColor"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"id": "Vector_2",
"d": "M7.66699 9.17684V15.0588L13.3243 11.9162C13.4282 11.8584 13.5147 11.7739 13.575 11.6715C13.6352 11.569 13.667 11.4524 13.667 11.3335V5.05884L8.35299 8.0115C8.14513 8.12694 7.97192 8.29586 7.8513 8.50075C7.73068 8.70565 7.66705 8.93907 7.66699 9.17684Z",
"fill": "currentColor"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"id": "Vector_3",
"d": "M10.1913 2.34351C9.804 3.33351 8.588 4.00017 7 4.00017C5.412 4.00017 4.196 3.33351 3.80867 2.34351L1 3.90417L6.35267 6.87817C6.5507 6.98815 6.77348 7.04586 7 7.04586C7.22652 7.04586 7.4493 6.98815 7.64733 6.87817L13 3.90417L10.1913 2.34351Z",
"fill": "currentColor"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"id": "Vector_4",
"d": "M7 2.66675C8.10457 2.66675 9 2.21903 9 1.66675C9 1.11446 8.10457 0.666748 7 0.666748C5.89543 0.666748 5 1.11446 5 1.66675C5 2.21903 5.89543 2.66675 7 2.66675Z",
"fill": "currentColor"
},
"children": []
}
]
}
]
},
"name": "Group"
}

View File

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Group.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'Group'
export default Icon

View File

@ -1 +1,2 @@
export { default as Generator } from './Generator'
export { default as Group } from './Group'

View File

@ -0,0 +1,47 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "16",
"height": "16",
"viewBox": "0 0 16 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "Icon"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Vector",
"d": "M3.99999 1.33325H7.99999V5.33325C7.99999 6.06963 8.59692 6.66659 9.33332 6.66659H13.3333V13.3333C13.3333 14.0697 12.7364 14.6666 12 14.6666H6.66666V13.3333H7.99999V11.9999H6.66666V10.6666H7.99999V9.33325H6.66666V7.99992H5.33332V9.33325H6.66666V10.6666H5.33332V11.9999H6.66666V13.3333H5.33332V14.6666H3.99999C3.26361 14.6666 2.66666 14.0697 2.66666 13.3333V2.66659C2.66666 1.93021 3.26361 1.33325 3.99999 1.33325Z",
"fill": "currentColor"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"id": "Vector_2",
"opacity": "0.5",
"d": "M12.9428 4.99993C13.0415 5.09868 13.1232 5.21133 13.1859 5.33327H9.33334V1.48071C9.45528 1.54338 9.56794 1.62504 9.66668 1.72379L12.9428 4.99993Z",
"fill": "currentColor"
},
"children": []
}
]
}
]
},
"name": "FileZip"
}

View File

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './FileZip.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'FileZip'
export default Icon

View File

@ -1,3 +1,4 @@
export { default as File05 } from './File05'
export { default as FileSearch02 } from './FileSearch02'
export { default as FileZip } from './FileZip'
export { default as Folder } from './Folder'

View File

@ -0,0 +1,36 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "16",
"height": "16",
"viewBox": "0 0 16 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "Icon"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Vector",
"d": "M8 1C4.1325 1 1 4.1325 1 8C1 11.0975 3.00375 13.7137 5.78625 14.6413C6.13625 14.7025 6.2675 14.4925 6.2675 14.3088C6.2675 14.1425 6.25875 13.5913 6.25875 13.005C4.5 13.3288 4.045 12.5763 3.905 12.1825C3.82625 11.9812 3.485 11.36 3.1875 11.1937C2.9425 11.0625 2.5925 10.7387 3.17875 10.73C3.73 10.7212 4.12375 11.2375 4.255 11.4475C4.885 12.5062 5.89125 12.2088 6.29375 12.025C6.355 11.57 6.53875 11.2638 6.74 11.0887C5.1825 10.9137 3.555 10.31 3.555 7.6325C3.555 6.87125 3.82625 6.24125 4.2725 5.75125C4.2025 5.57625 3.9575 4.85875 4.3425 3.89625C4.3425 3.89625 4.92875 3.7125 6.2675 4.61375C6.8275 4.45625 7.4225 4.3775 8.0175 4.3775C8.6125 4.3775 9.2075 4.45625 9.7675 4.61375C11.1063 3.70375 11.6925 3.89625 11.6925 3.89625C12.0775 4.85875 11.8325 5.57625 11.7625 5.75125C12.2087 6.24125 12.48 6.8625 12.48 7.6325C12.48 10.3187 10.8438 10.9137 9.28625 11.0887C9.54 11.3075 9.75875 11.7275 9.75875 12.3837C9.75875 13.32 9.75 14.0725 9.75 14.3088C9.75 14.4925 9.88125 14.7113 10.2312 14.6413C11.6209 14.1721 12.8284 13.279 13.6839 12.0877C14.5393 10.8963 14.9996 9.46668 15 8C15 4.1325 11.8675 1 8 1Z",
"fill": "currentColor"
},
"children": []
}
]
}
]
},
"name": "Github"
}

View File

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Github.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'Github'
export default Icon

View File

@ -5,6 +5,7 @@ export { default as Download02 } from './Download02'
export { default as Edit03 } from './Edit03'
export { default as Edit04 } from './Edit04'
export { default as Eye } from './Eye'
export { default as Github } from './Github'
export { default as MessageClockCircle } from './MessageClockCircle'
export { default as PlusCircle } from './PlusCircle'
export { default as QuestionTriangle } from './QuestionTriangle'

View File

@ -1,64 +1,81 @@
import type { FC } from 'react'
import { useEffect, useState } from 'react'
import cn from '@/utils/classnames'
import Badge, { BadgeState } from '@/app/components/base/badge/index'
type Option = {
value: string
text: string
}
type TabSliderProps = {
className?: string
itemWidth?: number
value: string
onChange: (v: string) => void
options: Option[]
}
const TabSlider: FC<TabSliderProps> = ({
className,
itemWidth = 118,
value,
onChange,
options,
}) => {
const currentIndex = options.findIndex(option => option.value === value)
const current = options[currentIndex]
const [activeIndex, setActiveIndex] = useState(options.findIndex(option => option.value === value))
const [sliderStyle, setSliderStyle] = useState({})
const updateSliderStyle = (index: number) => {
const tabElement = document.getElementById(`tab-${index}`)
if (tabElement) {
const { offsetLeft, offsetWidth } = tabElement
setSliderStyle({
transform: `translateX(${offsetLeft}px)`,
width: `${offsetWidth}px`,
})
}
}
useEffect(() => {
const newIndex = options.findIndex(option => option.value === value)
setActiveIndex(newIndex)
updateSliderStyle(newIndex)
}, [value, options])
return (
<div className={cn(className, 'relative flex p-0.5 rounded-lg bg-gray-200')}>
{
options.map((option, index) => (
<div
key={option.value}
className={`
flex justify-center items-center h-7 text-[13px]
font-semibold text-gray-600 rounded-[7px] cursor-pointer
hover:bg-gray-50
${index !== options.length - 1 && 'mr-[1px]'}
`}
style={{
width: itemWidth,
}}
onClick={() => onChange(option.value)}
>
{option.text}
</div>
))
}
{
current && (
<div
className={`
absolute flex justify-center items-center h-7 bg-white text-[13px] font-semibold text-primary-600
border-[0.5px] border-gray-200 rounded-[7px] shadow-xs transition-transform
`}
style={{
width: itemWidth,
transform: `translateX(${currentIndex * itemWidth + 1}px)`,
}}
>
{current.text}
</div>
)
}
<div className={cn(className, 'inline-flex p-0.5 rounded-[10px] bg-components-segmented-control-bg-normal relative items-center justify-center')}>
<div
className="absolute top-0.5 bottom-0.5 left-0 right-0 bg-components-panel-bg rounded-[10px] transition-transform duration-300 ease-in-out shadows-shadow-xs"
style={sliderStyle}
/>
{options.map((option, index) => (
<div
id={`tab-${index}`}
key={option.value}
className={cn(
'relative flex justify-center items-center px-2.5 py-1.5 gap-1 rounded-[10px] transition-colors duration-300 ease-in-out cursor-pointer z-10',
'system-md-semibold',
index === activeIndex
? 'text-text-primary'
: 'text-text-tertiary',
)}
onClick={() => {
if (index !== activeIndex) {
onChange(option.value)
updateSliderStyle(index)
}
}}
>
{option.text}
{option.value === 'plugins'
&& <Badge
size='s'
uppercase={true}
state={BadgeState.Default}
>
6
</Badge>
}
</div>
))}
</div>
)
}

View File

@ -9,6 +9,7 @@ import AccountDropdown from './account-dropdown'
import AppNav from './app-nav'
import DatasetNav from './dataset-nav'
import EnvNav from './env-nav'
import PluginsNav from './plugins-nav'
import ExploreNav from './explore-nav'
import ToolsNav from './tools-nav'
import GithubStar from './github-star'
@ -59,6 +60,11 @@ const Header = () => {
<Link href="/apps" className='flex items-center mr-4'>
<LogoSite className='object-contain' />
</Link>
{enableBilling && (
<div className='select-none'>
<HeaderBillingBtn onClick={handlePlanClick} />
</div>
)}
<GithubStar />
</>}
</div>
@ -67,6 +73,11 @@ const Header = () => {
<Link href="/apps" className='flex items-center mr-4'>
<LogoSite />
</Link>
{enableBilling && (
<div className='select-none'>
<HeaderBillingBtn onClick={handlePlanClick} />
</div>
)}
<GithubStar />
</div>
)}
@ -80,11 +91,9 @@ const Header = () => {
)}
<div className='flex items-center flex-shrink-0'>
<EnvNav />
{enableBilling && (
<div className='mr-3 select-none'>
<HeaderBillingBtn onClick={handlePlanClick} />
</div>
)}
<div className='mr-3'>
<PluginsNav />
</div>
<WorkspaceProvider>
<AccountDropdown isMobile={isMobile} />
</WorkspaceProvider>

View File

@ -0,0 +1,30 @@
'use client'
import { useTranslation } from 'react-i18next'
import Link from 'next/link'
import classNames from '@/utils/classnames'
import { Group } from '@/app/components/base/icons/src/vender/other'
type PluginsNavProps = {
className?: string
}
const PluginsNav = ({
className,
}: PluginsNavProps) => {
const { t } = useTranslation()
return (
<Link href="/plugins" className={classNames(
className, 'group',
)}>
<div className='flex flex-row p-1.5 gap-0.5 items-center justify-center rounded-xl system-xs-medium-uppercase hover:bg-state-base-hover text-text-tertiary hover:text-text-secondary'>
<div className='flex w-4 h-4 justify-center items-center'>
<Group />
</div>
<span className='px-0.5'>{t('common.menus.plugins')}</span>
</div>
</Link>
)
}
export default PluginsNav

View File

@ -0,0 +1,5 @@
const translation = {
}
export default translation

0
web/service/plugins.ts Normal file
View File