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 = ({