diff --git a/web/app/(commonLayout)/datasets/Container.tsx b/web/app/(commonLayout)/datasets/Container.tsx
index 771fa7c3f9..2a3efbea98 100644
--- a/web/app/(commonLayout)/datasets/Container.tsx
+++ b/web/app/(commonLayout)/datasets/Container.tsx
@@ -13,7 +13,8 @@ import Datasets from './Datasets'
import DatasetFooter from './DatasetFooter'
import ApiServer from '../../components/develop/ApiServer'
import Doc from './Doc'
-import TabSliderNew from '@/app/components/base/tab-slider-new'
+import SegmentedControl from '@/app/components/base/segmented-control'
+import { RiBook2Line, RiTerminalBoxLine } from '@remixicon/react'
import TagManagementModal from '@/app/components/base/tag-management'
import TagFilter from '@/app/components/base/tag-management/filter'
import Button from '@/app/components/base/button'
@@ -42,8 +43,10 @@ const Container = () => {
const options = useMemo(() => {
return [
- { value: 'dataset', text: t('dataset.datasets') },
- ...(currentWorkspace.role === 'dataset_operator' ? [] : [{ value: 'api', text: t('dataset.datasetsApi') }]),
+ { value: 'dataset', text: t('dataset.datasets'), Icon: RiBook2Line, count: 1 },
+ ...(currentWorkspace.role === 'dataset_operator' ? [] : [{
+ value: 'api', text: t('dataset.datasetsApi'), Icon: RiTerminalBoxLine, count: 5,
+ }]),
]
}, [currentWorkspace.role, t])
@@ -85,11 +88,13 @@ const Container = () => {
return (
-
-
+ setActiveTab(newActiveTab)}
+ onChange={newActiveTab => setActiveTab(newActiveTab as string)}
options={options}
+ size='large'
+ activeClassName='text-text-primary'
/>
{activeTab === 'dataset' && (
diff --git a/web/app/components/base/segmented-control/index.css b/web/app/components/base/segmented-control/index.css
new file mode 100644
index 0000000000..45c10389cf
--- /dev/null
+++ b/web/app/components/base/segmented-control/index.css
@@ -0,0 +1,78 @@
+@tailwind components;
+
+@layer components {
+ .segmented-control {
+ @apply flex items-center bg-components-segmented-control-bg-normal gap-x-px
+ }
+
+ .segmented-control-regular,
+ .segmented-control-large {
+ @apply rounded-lg
+ }
+
+ .segmented-control-large.padding,
+ .segmented-control-regular.padding {
+ @apply p-0.5
+ }
+
+ .segmented-control-small {
+ @apply rounded-md
+ }
+
+ .segmented-control-small.padding {
+ @apply p-px
+ }
+
+ .no-padding {
+ @apply border-[0.5px] border-divider-subtle
+ }
+
+ .segmented-control-item {
+ @apply flex items-center justify-center relative border-[0.5px] border-transparent
+ }
+
+ .segmented-control-item-regular {
+ @apply px-2 h-7 gap-x-0.5 rounded-lg
+ }
+
+ .segmented-control-item-small {
+ @apply p-px h-[22px] rounded-md
+ }
+
+ .segmented-control-item-large {
+ @apply px-2.5 h-8 gap-x-0.5 rounded-lg
+ }
+
+ .segmented-control-item-disabled {
+ @apply cursor-not-allowed text-text-disabled
+ }
+
+ .default {
+ @apply hover:bg-state-base-hover text-text-tertiary hover:text-text-secondary
+ }
+
+ .active {
+ @apply border-components-segmented-control-item-active-border bg-components-segmented-control-item-active-bg shadow-xs shadow-shadow-shadow-3
+ text-text-secondary
+ }
+
+ .active.accent {
+ @apply text-text-accent
+ }
+
+ .active.accent-light {
+ @apply text-text-accent-light-mode-only
+ }
+
+ .item-text-regular {
+ @apply p-0.5
+ }
+
+ .item-text-small {
+ @apply p-0.5 pr-1
+ }
+
+ .item-text-large {
+ @apply px-0.5
+ }
+}
\ No newline at end of file
diff --git a/web/app/components/base/segmented-control/index.tsx b/web/app/components/base/segmented-control/index.tsx
index bd921e4243..7bb3aa78a3 100644
--- a/web/app/components/base/segmented-control/index.tsx
+++ b/web/app/components/base/segmented-control/index.tsx
@@ -1,31 +1,107 @@
import React from 'react'
-import classNames from '@/utils/classnames'
+import cn from '@/utils/classnames'
import type { RemixiconComponentType } from '@remixicon/react'
import Divider from '../divider'
+import type { VariantProps } from 'class-variance-authority'
+import { cva } from 'class-variance-authority'
+import './index.css'
+
+type SegmentedControlOption
= {
+ value: T
+ text?: string
+ Icon: RemixiconComponentType
+ count?: number
+}
-// Updated generic type to allow enum values
type SegmentedControlProps = {
- options: { Icon: RemixiconComponentType, text: string, value: T }[]
+ options: SegmentedControlOption[]
value: T
onChange: (value: T) => void
className?: string
+ activeClassName?: string
}
+const SegmentedControlVariants = cva(
+ 'segmented-control',
+ {
+ variants: {
+ size: {
+ regular: 'segmented-control-regular',
+ small: 'segmented-control-small',
+ large: 'segmented-control-large',
+ },
+ padding: {
+ none: 'no-padding',
+ with: 'padding',
+ },
+ },
+ defaultVariants: {
+ size: 'regular',
+ padding: 'with',
+ },
+ },
+)
+
+const SegmentedControlItemVariants = cva(
+ 'segmented-control-item disabled:segmented-control-item-disabled',
+ {
+ variants: {
+ size: {
+ regular: ['segmented-control-item-regular', 'system-sm-medium'],
+ small: ['segmented-control-item-small', 'system-xs-medium'],
+ large: ['segmented-control-item-large', 'system-md-semibold'],
+ },
+ activeState: {
+ default: '',
+ accent: 'accent',
+ accentLight: 'accent-light',
+ },
+ },
+ defaultVariants: {
+ size: 'regular',
+ activeState: 'default',
+ },
+ },
+)
+
+const ItemTextWrapperVariants = cva(
+ 'item-text',
+ {
+ variants: {
+ size: {
+ regular: 'item-text-regular',
+ small: 'item-text-small',
+ large: 'item-text-large',
+ },
+ },
+ defaultVariants: {
+ size: 'regular',
+ },
+ },
+)
+
export const SegmentedControl = ({
options,
value,
onChange,
className,
-}: SegmentedControlProps): JSX.Element => {
+ size,
+ padding,
+ activeState,
+ activeClassName,
+}: SegmentedControlProps
+& VariantProps
+& VariantProps
+& VariantProps) => {
const selectedOptionIndex = options.findIndex(option => option.value === value)
return (
-
{options.map((option, index) => {
- const { Icon } = option
+ const { Icon, text, count } = option
const isSelected = index === selectedOptionIndex
const isNextSelected = index === selectedOptionIndex - 1
const isLast = index === options.length - 1
@@ -33,26 +109,24 @@ export const SegmentedControl =
({