update segmented components

This commit is contained in:
Nate Kelley 2025-03-01 15:21:58 -07:00
parent e053f68391
commit d53e003ce2
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
17 changed files with 336 additions and 326 deletions

View File

@ -3,4 +3,7 @@ description: Rules for new stories
globs: src/components/**/*.stories.tsx
alwaysApply: false
---
All new stories title with "UI/directory/{componentName}" unless specified otherwise.
- All new stories title with "UI/directory/{componentName}" unless specified otherwise.
- Instead of console log for click events, you should do native storybook actions
- If a new story is made in the controller directory, I would like it title "Controllers/{controller name or parent controller name}/{componentName}"
- If a new story is found in the features directory, I would like it titled "Features/{featureName or parent feature name}/{componentName}"

View File

@ -1,12 +1,11 @@
'use client';
import { AppSegmented } from '@/components/ui';
import { AppSegmented, type SegmentedItem } from '@/components/ui/segmented';
import { useRouter } from 'next/navigation';
import React, { useMemo } from 'react';
import { DatasetApps, DataSetAppText } from '../config';
import { createBusterRoute, BusterRoutes } from '@/routes';
import { useMemoizedFn } from 'ahooks';
import { SegmentedValue } from 'antd/es/segmented';
export const DatasetsHeaderOptions: React.FC<{
selectedApp: DatasetApps;
@ -22,7 +21,7 @@ export const DatasetsHeaderOptions: React.FC<{
[isAdmin]
);
const options = useMemo(
const options: SegmentedItem<DatasetApps>[] = useMemo(
() =>
optionsItems.map((key) => ({
label: DataSetAppText[key as DatasetApps],
@ -32,8 +31,8 @@ export const DatasetsHeaderOptions: React.FC<{
[datasetId, optionsItems]
);
const onChangeSegment = useMemoizedFn((value: SegmentedValue) => {
if (datasetId) push(keyToRoute(datasetId, value as DatasetApps));
const onChangeSegment = useMemoizedFn((value: SegmentedItem<DatasetApps>) => {
if (datasetId) push(keyToRoute(datasetId, value.value));
});
return <AppSegmented options={options} value={selectedApp} onChange={onChangeSegment} />;

View File

@ -1,7 +1,7 @@
import { AppSegmented } from '@/components/ui';
import { useMemoizedFn } from 'ahooks';
import { createStyles } from 'antd-style';
import { SegmentedValue } from 'antd/es/segmented';
import { type SegmentedItem } from '@/components/ui/segmented';
import React from 'react';
export enum EditorApps {
@ -20,8 +20,8 @@ export const EditorContainerSubHeader: React.FC<{
}> = React.memo(({ selectedApp, setSelectedApp }) => {
const { styles, cx } = useStyles();
const onSegmentedChange = useMemoizedFn((value: SegmentedValue) => {
setSelectedApp(value as EditorApps);
const onSegmentedChange = useMemoizedFn((value: SegmentedItem<EditorApps>) => {
setSelectedApp(value.value);
});
return (

View File

@ -1,7 +1,7 @@
import React, { useMemo } from 'react';
import { AppSegmented } from '@/components/ui/segmented';
import { useMemoizedFn } from 'ahooks';
import { SegmentedValue } from 'antd/es/segmented';
import { type SegmentedItem } from '@/components/ui/segmented';
import { Divider } from 'antd';
import { createBusterRoute, BusterRoutes } from '@/routes';
@ -30,7 +30,7 @@ export const UserSegments: React.FC<{
onSelectApp: (app: UserSegmentsApps) => void;
userId: string;
}> = React.memo(({ isAdmin, selectedApp, onSelectApp, userId }) => {
const onChange = useMemoizedFn((value: SegmentedValue) => {
const onChange = useMemoizedFn((value: SegmentedItem) => {
onSelectApp(value as UserSegmentsApps);
});
const options = useMemo(

View File

@ -4,7 +4,7 @@ import { CopyLinkButton } from './CopyLinkButton';
import { ShareAssetType } from '@/api/asset_interfaces';
import { ShareRole } from '@/api/asset_interfaces';
import { useMemoizedFn } from 'ahooks';
import { SegmentedValue } from 'antd/es/segmented';
import { type SegmentedItem } from '@/components/ui/segmented';
export enum ShareMenuTopBarOptions {
Share = 'Share',
@ -45,7 +45,7 @@ export const ShareMenuTopBar: React.FC<{
.map((o) => ({ ...o, show: undefined }));
}, [assetType, isOwner]);
const onChange = useMemoizedFn((v: SegmentedValue) => {
const onChange = useMemoizedFn((v: SegmentedItem) => {
onChangeSelectedOption(v as ShareMenuTopBarOptions);
});

View File

@ -15,7 +15,7 @@ import { useBusterSearchContextSelector } from '@/context/Search';
import isEmpty from 'lodash/isEmpty';
import { useBusterDashboardContextSelector } from '@/context/Dashboards';
import { useBusterCollectionIndividualContextSelector } from '@/context/Collections';
import { SegmentedValue } from 'antd/es/segmented';
import { type SegmentedItem } from '@/components/ui/segmented';
import { BusterSearchRequest } from '@/api/buster_socket/search';
import { busterAppStyleConfig } from '@/styles/busterAntDStyleConfig';
@ -350,7 +350,7 @@ const ModalContent: React.FC<{
onSelectChange,
onClose
}) => {
const onSetSelectedFiltersPreflight = useMemoizedFn((value: SegmentedValue) => {
const onSetSelectedFiltersPreflight = useMemoizedFn((value: SegmentedItem) => {
onSetSelectedFilter(value as string);
});

View File

@ -1,10 +1,10 @@
import type { Meta, StoryObj } from '@storybook/react';
import { Segmented } from './Segmented';
import { AppSegmented } from './AppSegmented';
import { BottleChampagne, Grid, HouseModern, PaintRoller } from '../icons';
const meta: Meta<typeof Segmented> = {
title: 'UI/Segmented',
component: Segmented,
const meta: Meta<typeof AppSegmented> = {
title: 'UI/Segmented/AppSegmented',
component: AppSegmented,
parameters: {
layout: 'centered'
},
@ -28,14 +28,14 @@ const meta: Meta<typeof Segmented> = {
render: (args) => {
return (
<div className="flex w-full min-w-[500px] flex-col items-center justify-center gap-4">
<Segmented {...args} />
<AppSegmented {...args} />
</div>
);
}
};
export default meta;
type Story = StoryObj<typeof Segmented>;
type Story = StoryObj<typeof AppSegmented>;
const defaultItems = [
{ value: 'tab1', label: 'Tab 1', icon: <HouseModern /> },
@ -45,20 +45,20 @@ const defaultItems = [
export const Default: Story = {
args: {
items: defaultItems
options: defaultItems
}
};
export const Large: Story = {
args: {
items: defaultItems,
options: defaultItems,
size: 'large'
}
};
export const Block: Story = {
args: {
items: defaultItems,
options: defaultItems,
block: true
},
parameters: {
@ -68,7 +68,7 @@ export const Block: Story = {
export const LargeBlock: Story = {
args: {
items: defaultItems,
options: defaultItems,
size: 'large',
block: true
},
@ -79,7 +79,7 @@ export const LargeBlock: Story = {
export const WithIcons: Story = {
args: {
items: [
options: [
{ value: 'list', label: 'List', icon: <PaintRoller /> },
{ value: 'grid', label: 'Grid', icon: <Grid /> },
{ value: 'gallery', label: 'Gallery', icon: <BottleChampagne /> }
@ -89,14 +89,14 @@ export const WithIcons: Story = {
export const Controlled: Story = {
args: {
items: defaultItems,
options: defaultItems,
value: 'tab2'
}
};
export const WithDisabledItems: Story = {
args: {
items: [
options: [
{ value: 'tab1', label: 'Enabled' },
{ value: 'tab2', label: 'Disabled', disabled: true },
{ value: 'tab3', label: 'Enabled' }
@ -106,7 +106,7 @@ export const WithDisabledItems: Story = {
export const CustomStyling: Story = {
args: {
items: defaultItems,
options: defaultItems,
className: 'bg-blue-100 [&_[data-state=active]]:text-blue-700'
}
};

View File

@ -1,108 +1,211 @@
'use client';
import React, { useMemo } from 'react';
import { ConfigProvider, Segmented, SegmentedProps, ThemeConfig } from 'antd';
import { createStyles } from 'antd-style';
import { busterAppStyleConfig } from '@/styles/busterAntDStyleConfig';
import Link from 'next/link';
import * as React from 'react';
import * as Tabs from '@radix-ui/react-tabs';
import { motion } from 'framer-motion';
import { cn } from '@/lib/classMerge';
import { useEffect, useState } from 'react';
import { cva } from 'class-variance-authority';
import { useMemoizedFn } from 'ahooks';
import { useRouter } from 'next/navigation';
import { AppTooltip } from '@/components/ui/tooltip';
const token = busterAppStyleConfig.token!;
type SegmentedOption = {
value: string;
label?: string;
link?: string;
onHover?: () => void;
export interface SegmentedItem<T extends string = string> {
value: T;
label: React.ReactNode;
icon?: React.ReactNode;
tooltip?: string;
};
export interface AppSegmentedProps extends Omit<SegmentedProps, 'options'> {
bordered?: boolean;
options: SegmentedOption[];
disabled?: boolean;
}
const useStyles = createStyles(({ css, token }) => {
return {
segmented: css``
};
});
interface AppSegmentedProps<T extends string = string> {
options: SegmentedItem<T>[];
value?: T;
onChange?: (value: SegmentedItem<T>) => void;
className?: string;
size?: 'default' | 'large';
block?: boolean;
type?: 'button' | 'track';
}
const THEME_CONFIG: ThemeConfig = {
components: {
Segmented: {
itemColor: token.colorTextDescription,
trackBg: 'transparent',
itemSelectedColor: token.colorTextBase,
itemSelectedBg: token.controlItemBgActive,
colorBorder: token.colorBorder,
boxShadowTertiary: 'none'
const segmentedVariants = cva('relative inline-flex items-center rounded-md', {
variants: {
block: {
true: 'w-full',
false: ''
},
size: {
default: '',
large: ''
},
type: {
button: 'bg-transparent',
track: 'bg-item-select'
}
}
});
const triggerVariants = cva(
'relative z-10 flex items-center justify-center gap-x-1.5 gap-y-1 rounded-md transition-colors',
{
variants: {
size: {
default: 'px-2.5 flex-row',
large: 'px-3 flex-col'
},
block: {
true: 'flex-1',
false: ''
},
disabled: {
true: '!text-foreground/30 !hover:text-foreground/30 cursor-not-allowed',
false: 'cursor-pointer'
},
selected: {
true: 'text-foreground',
false: 'text-gray-dark hover:text-foreground'
}
}
}
);
const gliderVariants = cva('absolute border-border rounded-md border', {
variants: {
type: {
button: 'bg-item-select',
track: 'bg-background'
}
}
});
// Create a type for the forwardRef component that includes displayName
type AppSegmentedComponent = (<T extends string = string>(
props: AppSegmentedProps<T> & { ref?: React.ForwardedRef<HTMLDivElement> }
) => React.ReactElement) & {
displayName?: string;
};
export const AppSegmented = React.memo<AppSegmentedProps>(
({ size = 'small', bordered = true, onChange, options: optionsProps, ...props }) => {
const { cx, styles } = useStyles();
const router = useRouter();
// Update the component definition to properly handle generics
export const AppSegmented: AppSegmentedComponent = React.forwardRef(
<T extends string = string>(
{
options,
type = 'track',
value,
onChange,
className,
size = 'default',
block = false
}: AppSegmentedProps<T>,
ref: React.ForwardedRef<HTMLDivElement>
) => {
const tabRefs = React.useRef<Map<string, HTMLButtonElement>>(new Map());
const [selectedValue, setSelectedValue] = useState(value || options[0]?.value);
const [gliderStyle, setGliderStyle] = useState({
width: 0,
transform: 'translateX(0)'
});
const options = useMemo(() => {
return optionsProps.map((option) => ({
value: option.value,
label: <SegmentedItem option={option} />
}));
}, [optionsProps]);
const height = size === 'default' ? 'h-[28px]' : 'h-[50px]';
const onChangePreflight = useMemoizedFn((value: string | number) => {
const link = optionsProps.find((option) => option.value === value)?.link;
if (link) {
router.push(link);
useEffect(() => {
if (value !== undefined && value !== selectedValue) {
setSelectedValue(value);
}
}, [value]);
useEffect(() => {
const selectedTab = tabRefs.current.get(selectedValue);
if (selectedTab) {
const { offsetWidth, offsetLeft } = selectedTab;
setGliderStyle({
width: offsetWidth,
transform: `translateX(${offsetLeft}px)`
});
}
}, [selectedValue]);
const handleTabClick = useMemoizedFn((value: string) => {
const item = options.find((item) => item.value === value);
if (item && !item.disabled) {
setSelectedValue(item.value);
onChange?.(item);
}
onChange?.(value);
});
return (
<ConfigProvider theme={THEME_CONFIG}>
<Segmented
{...props}
onChange={onChangePreflight}
options={options}
size={size}
className={cx(
styles.segmented,
props.className,
'shadow-none!',
!bordered && 'no-border'
)}
<Tabs.Root
ref={ref}
value={selectedValue}
onValueChange={handleTabClick}
className={cn(segmentedVariants({ block, type }), height, className)}>
<motion.div
className={cn(gliderVariants({ type }), height)}
initial={false}
animate={{
width: gliderStyle.width,
x: parseInt(gliderStyle.transform.replace('translateX(', '').replace('px)', ''))
}}
transition={{
type: 'spring',
stiffness: 400,
damping: 35
}}
/>
</ConfigProvider>
<Tabs.List
className="relative z-10 flex w-full items-center gap-1"
aria-label="Segmented Control">
{options.map((item) => (
<SegmentedTrigger
key={item.value}
item={item}
selectedValue={selectedValue}
size={size}
block={block}
tabRefs={tabRefs}
/>
))}
</Tabs.List>
</Tabs.Root>
);
}
);
) as AppSegmentedComponent;
AppSegmented.displayName = 'AppSegmented';
const SegmentedItem: React.FC<{ option: SegmentedOption }> = ({ option }) => {
return (
<AppTooltip mouseEnterDelay={0.75} title={option.tooltip}>
<SegmentedItemLink href={option.link}>
<div className="flex items-center gap-0.5" onClick={option.onHover}>
{option.icon}
{option.label}
</div>
</SegmentedItemLink>
</AppTooltip>
);
};
interface SegmentedTriggerProps<T extends string = string> {
item: SegmentedItem<T>;
selectedValue: string;
size: AppSegmentedProps<T>['size'];
block: AppSegmentedProps<T>['block'];
tabRefs: React.MutableRefObject<Map<string, HTMLButtonElement>>;
}
const SegmentedItemLink: React.FC<{ href?: string; children: React.ReactNode }> = ({
href,
children
}) => {
if (!href) return children;
function SegmentedTriggerComponent<T extends string = string>(
props: SegmentedTriggerProps<T>
): JSX.Element {
const { item, selectedValue, size, block, tabRefs } = props;
return (
<Link prefetch={true} href={href}>
{children}
</Link>
<Tabs.Trigger
key={item.value}
value={item.value}
disabled={item.disabled}
ref={(el) => {
if (el) tabRefs.current.set(item.value, el);
}}
className={cn(
'focus-visible:ring-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
triggerVariants({
size,
block,
disabled: item.disabled,
selected: selectedValue === item.value
})
)}>
{item.icon && <span className={cn('flex items-center text-sm')}>{item.icon}</span>}
<span className={cn('text-sm')}>{item.label}</span>
</Tabs.Trigger>
);
};
}
SegmentedTriggerComponent.displayName = 'SegmentedTrigger';
const SegmentedTrigger = React.memo(SegmentedTriggerComponent) as typeof SegmentedTriggerComponent;
SegmentedTrigger.displayName = 'SegmentedTrigger';

View File

@ -1,183 +0,0 @@
'use client';
import * as React from 'react';
import * as Tabs from '@radix-ui/react-tabs';
import { motion } from 'framer-motion';
import { cn } from '@/lib/classMerge';
import { useEffect, useState } from 'react';
import { cva } from 'class-variance-authority';
import { useMemoizedFn } from 'ahooks';
export interface SegmentedItem {
value: string;
label: React.ReactNode;
icon?: React.ReactNode;
disabled?: boolean;
}
interface SegmentedProps {
items: SegmentedItem[];
value?: string;
onChange?: (value: SegmentedItem) => void;
className?: string;
size?: 'default' | 'large';
block?: boolean;
type?: 'button' | 'track';
}
const segmentedVariants = cva('relative inline-flex items-center rounded-md', {
variants: {
block: {
true: 'w-full',
false: ''
},
size: {
default: '',
large: ''
},
type: {
button: 'bg-transparent',
track: 'bg-item-select'
}
}
});
const triggerVariants = cva(
'relative z-10 flex items-center justify-center gap-x-1.5 gap-y-1 rounded-md transition-colors',
{
variants: {
size: {
default: 'px-2.5 flex-row',
large: 'px-3 flex-col'
},
block: {
true: 'flex-1',
false: ''
},
disabled: {
true: '!text-foreground/30 !hover:text-foreground/30 cursor-not-allowed',
false: 'cursor-pointer'
},
selected: {
true: 'text-foreground',
false: 'text-gray-dark hover:text-foreground'
}
}
}
);
const gliderVariants = cva('absolute border-border rounded-md border', {
variants: {
type: {
button: 'bg-item-select',
track: 'bg-background'
}
}
});
export const Segmented = React.forwardRef<HTMLDivElement, SegmentedProps>(
({ items, type = 'track', value, onChange, className, size = 'default', block = false }, ref) => {
const tabRefs = React.useRef<Map<string, HTMLButtonElement>>(new Map());
const [selectedValue, setSelectedValue] = useState(value || items[0]?.value);
const [gliderStyle, setGliderStyle] = useState({
width: 0,
transform: 'translateX(0)'
});
const height = size === 'default' ? 'h-[28px]' : 'h-[50px]';
useEffect(() => {
if (value !== undefined && value !== selectedValue) {
setSelectedValue(value);
}
}, [value]);
useEffect(() => {
const selectedTab = tabRefs.current.get(selectedValue);
if (selectedTab) {
const { offsetWidth, offsetLeft } = selectedTab;
setGliderStyle({
width: offsetWidth,
transform: `translateX(${offsetLeft}px)`
});
}
}, [selectedValue]);
const handleTabClick = useMemoizedFn((value: string) => {
const item = items.find((item) => item.value === value);
if (item && !item.disabled) {
setSelectedValue(item.value);
onChange?.(item);
}
});
return (
<Tabs.Root
ref={ref}
value={selectedValue}
onValueChange={handleTabClick}
className={cn(segmentedVariants({ block, type }), height, className)}>
<motion.div
className={cn(gliderVariants({ type }), height)}
initial={false}
animate={{
width: gliderStyle.width,
x: parseInt(gliderStyle.transform.replace('translateX(', '').replace('px)', ''))
}}
transition={{
type: 'spring',
stiffness: 400,
damping: 35
}}
/>
<Tabs.List
className="relative z-10 flex w-full items-center gap-1"
aria-label="Segmented Control">
{items.map((item) => (
<SegmentedTrigger
key={item.value}
item={item}
selectedValue={selectedValue}
size={size}
block={block}
tabRefs={tabRefs}
/>
))}
</Tabs.List>
</Tabs.Root>
);
}
);
Segmented.displayName = 'Segmented';
const SegmentedTrigger = React.memo<{
item: SegmentedItem;
selectedValue: string;
size: SegmentedProps['size'];
block: SegmentedProps['block'];
tabRefs: React.MutableRefObject<Map<string, HTMLButtonElement>>;
}>(({ item, selectedValue, size, block, tabRefs }) => {
return (
<Tabs.Trigger
key={item.value}
value={item.value}
disabled={item.disabled}
ref={(el) => {
if (el) tabRefs.current.set(item.value, el);
}}
className={cn(
'focus-visible:ring-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
triggerVariants({
size,
block,
disabled: item.disabled,
selected: selectedValue === item.value
})
)}>
{item.icon && <span className={cn('flex items-center text-sm')}>{item.icon}</span>}
<span className={cn('text-sm')}>{item.label}</span>
</Tabs.Trigger>
);
});
SegmentedTrigger.displayName = 'SegmentedTrigger';

View File

@ -15,7 +15,7 @@ import { CollectionsListEmit } from '@/api/buster_socket/collections';
import isEmpty from 'lodash/isEmpty';
import omit from 'lodash/omit';
import { useMemoizedFn } from 'ahooks';
import { SegmentedValue } from 'antd/es/segmented';
import { type SegmentedItem } from '@/components/ui/segmented';
export const CollectionListHeader: React.FC<{
collectionId?: string;
@ -122,7 +122,7 @@ const CollectionFilters: React.FC<{
return filters.find((f) => f.value === activeFiltersValue)?.value || filters[0].value;
}, [filters, collectionListFilters]);
const onChangeFilter = useMemoizedFn((v: SegmentedValue) => {
const onChangeFilter = useMemoizedFn((v: SegmentedItem) => {
let parsedValue;
try {
parsedValue = JSON.parse(v as string);

View File

@ -0,0 +1,89 @@
import React from 'react';
import { Meta, StoryObj } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { MetricStylingAppSegment } from './MetricStylingAppSegment';
import { MetricStylingAppSegments } from './config';
import { ChartType } from '@/components/ui/charts/interfaces/enum';
const meta: Meta<typeof MetricStylingAppSegment> = {
title: 'Controllers/EditMetricController/MetricStylingAppSegment',
component: MetricStylingAppSegment,
parameters: {
layout: 'centered'
},
tags: ['autodocs'],
argTypes: {
segment: {
control: 'select',
options: Object.values(MetricStylingAppSegments),
description: 'The currently selected segment'
},
setSegment: {
action: 'setSegment',
description: 'Function called when segment changes'
},
selectedChartType: {
control: 'select',
options: Object.values(ChartType),
description: 'The type of chart currently selected'
},
className: {
control: 'text',
description: 'Additional CSS classes to apply'
}
},
decorators: [
(Story) => (
<div className="w-full min-w-[330px]">
<Story />
</div>
)
]
};
export default meta;
type Story = StoryObj<typeof MetricStylingAppSegment>;
// Create a reusable action handler
const handleSetSegment = action('setSegment');
export const Default: Story = {
args: {
segment: MetricStylingAppSegments.VISUALIZE,
setSegment: handleSetSegment,
selectedChartType: ChartType.Line,
className: ''
}
};
export const WithTableChart: Story = {
args: {
segment: MetricStylingAppSegments.VISUALIZE,
setSegment: handleSetSegment,
selectedChartType: ChartType.Table,
className: ''
},
parameters: {
docs: {
description: {
story: 'When table chart type is selected, Styling and Colors segments are disabled'
}
}
}
};
export const WithMetricChart: Story = {
args: {
segment: MetricStylingAppSegments.VISUALIZE,
setSegment: handleSetSegment,
selectedChartType: ChartType.Metric,
className: ''
},
parameters: {
docs: {
description: {
story: 'When metric chart type is selected, Styling and Colors segments are disabled'
}
}
}
};

View File

@ -1,16 +1,9 @@
import React, { useMemo } from 'react';
import { MetricStylingAppSegments } from './config';
import { SegmentedOptions, SegmentedValue } from 'antd/es/segmented';
import { useMemoizedFn } from 'ahooks';
import { Segmented } from 'antd';
import { createStyles } from 'antd-style';
import { IBusterMetricChartConfig } from '@/api/asset_interfaces';
const useStyles = createStyles(({ css, token }) => ({
container: css`
border-bottom: 0.5px solid ${token.colorBorder};
`
}));
import { AppSegmented, type SegmentedItem } from '@/components/ui/segmented';
import { cn } from '@/lib/utils';
export const MetricStylingAppSegment: React.FC<{
segment: MetricStylingAppSegments;
@ -18,13 +11,12 @@ export const MetricStylingAppSegment: React.FC<{
selectedChartType: IBusterMetricChartConfig['selectedChartType'];
className?: string;
}> = React.memo(({ segment, setSegment, selectedChartType, className = '' }) => {
const { cx, styles } = useStyles();
const isTable = selectedChartType === 'table';
const isMetric = selectedChartType === 'metric';
const disableColors = isTable || isMetric;
const disableStyling = isTable || isMetric;
const options: SegmentedOptions = useMemo(
const options: SegmentedItem<MetricStylingAppSegments>[] = useMemo(
() => [
{
label: MetricStylingAppSegments.VISUALIZE,
@ -44,14 +36,21 @@ export const MetricStylingAppSegment: React.FC<{
[disableColors, disableStyling]
);
const onChangeSegment = useMemoizedFn((value: SegmentedValue) => {
setSegment(value as MetricStylingAppSegments);
const onChangeSegment = useMemoizedFn((value: SegmentedItem<MetricStylingAppSegments>) => {
setSegment(value.value);
});
return (
<div className={cx(styles.container)}>
<div className={cx('pb-3', className)}>
<Segmented block options={options} value={segment} onChange={onChangeSegment} />
<div className={cn('border-b')}>
<div className={cn('pb-3', className)}>
<AppSegmented
type="track"
size="default"
block
options={options}
value={segment}
onChange={onChangeSegment}
/>
</div>
</div>
);

View File

@ -5,7 +5,7 @@ import { AppMaterialIcons, AppSegmented, AppTooltip } from '@/components/ui';
import { useEditAppSegmented } from './useEditAppSegmented';
import { ENABLED_DOTS_ON_LINE_SIZE } from '@/api/asset_interfaces';
import { useMemoizedFn } from 'ahooks';
import { SegmentedValue } from 'antd/es/segmented';
import { type SegmentedItem } from '@/components/ui/segmented';
const options: { icon: React.ReactNode; value: LineValue }[] = [
{
@ -106,7 +106,7 @@ export const EditLineStyle: React.FC<{
methodRecord[lineValue]();
});
const onChangeValue = useMemoizedFn((value: SegmentedValue) => {
const onChangeValue = useMemoizedFn((value: SegmentedItem) => {
if (value) onClickValue(value as string);
});

View File

@ -6,7 +6,7 @@ import { AppSegmented } from '@/components/ui';
import { VerificationStatus } from '@/api/asset_interfaces';
import { Text } from '@/components/ui';
import { useMemoizedFn } from 'ahooks';
import { SegmentedValue } from 'antd/lib/segmented';
import { type SegmentedItem } from '@/components/ui/segmented';
export const MetricListHeader: React.FC<{
type: 'logs' | 'metrics';
@ -30,7 +30,7 @@ export const MetricListHeader: React.FC<{
);
};
const options = [
const options: SegmentedItem<VerificationStatus | 'all'>[] = [
{
label: 'All',
value: 'all'
@ -50,7 +50,7 @@ const MetricsFilters: React.FC<{
filters: VerificationStatus[];
onSetFilters: (filters: VerificationStatus[]) => void;
}> = React.memo(({ type, filters, onSetFilters }) => {
const selectedOption = useMemo(() => {
const selectedOption: SegmentedItem<VerificationStatus | 'all'> | undefined = useMemo(() => {
return (
options.find((option) => {
return filters.includes(option.value as VerificationStatus);
@ -58,11 +58,11 @@ const MetricsFilters: React.FC<{
);
}, [filters]);
const onChange = useMemoizedFn((v: SegmentedValue) => {
if (v === 'all') {
const onChange = useMemoizedFn((v: SegmentedItem<VerificationStatus | 'all'>) => {
if (v.value === 'all') {
onSetFilters([]);
} else {
onSetFilters([v as VerificationStatus]);
onSetFilters([v.value as VerificationStatus]);
}
});

View File

@ -4,7 +4,7 @@ import type { DashboardFileView, FileView } from '../../ChatLayoutContext/useCha
import { AppSegmented } from '@/components/ui/segmented';
import { useChatLayoutContextSelector } from '../../ChatLayoutContext';
import { useMemoizedFn } from 'ahooks';
import { SegmentedValue } from 'antd/es/segmented';
import { type SegmentedItem } from '@/components/ui/segmented';
const segmentOptions: { label: string; value: DashboardFileView }[] = [
{ label: 'Dashboard', value: 'dashboard' },
@ -15,7 +15,7 @@ export const DashboardContainerHeaderSegment: React.FC<FileContainerSegmentProps
({ selectedFileView }) => {
const onSetFileView = useChatLayoutContextSelector((x) => x.onSetFileView);
const onChange = useMemoizedFn((fileView: SegmentedValue) => {
const onChange = useMemoizedFn((fileView: SegmentedItem) => {
onSetFileView({ fileView: fileView as FileView });
});

View File

@ -3,7 +3,7 @@ import { FileContainerSegmentProps } from './interfaces';
import { AppSegmented } from '@/components/ui/segmented';
import { useChatLayoutContextSelector } from '../../ChatLayoutContext';
import type { FileView, MetricFileView } from '../../ChatLayoutContext/useChatFileLayout';
import { SegmentedValue } from 'antd/es/segmented';
import { type SegmentedItem } from '@/components/ui/segmented';
import { useMemoizedFn } from 'ahooks';
const segmentOptions: { label: string; value: MetricFileView }[] = [
@ -16,7 +16,7 @@ export const MetricContainerHeaderSegment: React.FC<FileContainerSegmentProps> =
({ selectedFileView }) => {
const onSetFileView = useChatLayoutContextSelector((x) => x.onSetFileView);
const onChange = useMemoizedFn((fileView: SegmentedValue) => {
const onChange = useMemoizedFn((fileView: SegmentedItem) => {
onSetFileView({ fileView: fileView as FileView });
});

View File

@ -3,7 +3,7 @@ import { FileContainerSegmentProps } from './interfaces';
import { AppSegmented } from '@/components/ui/segmented';
import { useChatLayoutContextSelector } from '../../ChatLayoutContext';
import type { FileView, ReasoningFileView } from '../../ChatLayoutContext/useChatFileLayout';
import { SegmentedValue } from 'antd/es/segmented';
import { type SegmentedItem } from '@/components/ui/segmented';
import { useMemoizedFn } from 'ahooks';
const segmentOptions: { label: string; value: ReasoningFileView }[] = [
@ -14,7 +14,7 @@ export const ReasoningContainerHeaderSegment: React.FC<FileContainerSegmentProps
({ selectedFileView }) => {
const onSetFileView = useChatLayoutContextSelector((x) => x.onSetFileView);
const onChange = useMemoizedFn((fileView: SegmentedValue) => {
const onChange = useMemoizedFn((fileView: SegmentedItem) => {
onSetFileView({ fileView: fileView as FileView });
});