mirror of https://github.com/buster-so/buster.git
slider updates
This commit is contained in:
parent
d8aabfba1b
commit
ce4f4f705e
|
@ -371,7 +371,6 @@ export const useUpdateMetric = (params?: {
|
|||
});
|
||||
|
||||
if (prevMetric && newMetric) {
|
||||
console.log(newMetric.chart_config.yAxisScaleType);
|
||||
queryClient.setQueryData(options.queryKey, newMetric);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Slider } from './Slider';
|
||||
|
||||
const meta: Meta<typeof Slider> = {
|
||||
title: 'UI/Slider/Slider',
|
||||
component: Slider,
|
||||
tags: ['autodocs'],
|
||||
args: {
|
||||
defaultValue: [50],
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
showTooltip: true
|
||||
},
|
||||
argTypes: {
|
||||
min: {
|
||||
control: 'number',
|
||||
description: 'Minimum value of the slider'
|
||||
},
|
||||
max: {
|
||||
control: 'number',
|
||||
description: 'Maximum value of the slider'
|
||||
},
|
||||
step: {
|
||||
control: 'number',
|
||||
description: 'Step size for slider increments'
|
||||
},
|
||||
defaultValue: {
|
||||
control: 'object',
|
||||
description: 'Default value of the slider (array of numbers)'
|
||||
},
|
||||
value: {
|
||||
control: 'object',
|
||||
description: 'Current value of the slider (array of numbers)'
|
||||
},
|
||||
showTooltip: {
|
||||
control: 'boolean',
|
||||
description: 'Whether to show tooltip while sliding or hovering'
|
||||
},
|
||||
disabled: {
|
||||
control: 'boolean',
|
||||
description: 'Whether the slider is disabled'
|
||||
},
|
||||
onValueChange: {
|
||||
action: 'valueChanged',
|
||||
description: 'Callback when value changes'
|
||||
},
|
||||
onValueCommit: {
|
||||
action: 'valueCommitted',
|
||||
description: 'Callback when value is committed'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof Slider>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
defaultValue: [50]
|
||||
}
|
||||
};
|
||||
|
||||
export const WithRange: Story = {
|
||||
args: {
|
||||
defaultValue: [25, 75]
|
||||
}
|
||||
};
|
||||
|
||||
export const WithCustomMinMax: Story = {
|
||||
args: {
|
||||
min: 1000,
|
||||
max: 5000,
|
||||
defaultValue: [2500]
|
||||
}
|
||||
};
|
||||
|
||||
export const WithCustomStep: Story = {
|
||||
args: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 10,
|
||||
defaultValue: [40]
|
||||
}
|
||||
};
|
||||
|
||||
export const WithoutTooltip: Story = {
|
||||
args: {
|
||||
defaultValue: [50],
|
||||
showTooltip: false
|
||||
}
|
||||
};
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
defaultValue: [50],
|
||||
disabled: true
|
||||
}
|
||||
};
|
|
@ -2,7 +2,12 @@
|
|||
|
||||
import * as React from 'react';
|
||||
import * as SliderPrimitive from '@radix-ui/react-slider';
|
||||
import { Tooltip } from '@/components/ui/tooltip';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
TooltipContent
|
||||
} from '@/components/ui/tooltip/TooltipBase';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
|
@ -28,42 +33,24 @@ const Slider = React.forwardRef<React.ElementRef<typeof SliderPrimitive.Root>, S
|
|||
},
|
||||
ref
|
||||
) => {
|
||||
const [isDragging, setIsDragging] = React.useState(false);
|
||||
const [isHovered, setIsHovered] = React.useState(false);
|
||||
const [useTooltip, setUseTooltip] = React.useState(false);
|
||||
const [internalValues, setInternalValues] = React.useState(value || defaultValue || [min]);
|
||||
const currentValue = value || defaultValue || [min];
|
||||
|
||||
const handleValueChange = React.useCallback(
|
||||
(newValue: number[]) => {
|
||||
setIsDragging(true);
|
||||
onValueChange?.(newValue);
|
||||
setInternalValues(newValue);
|
||||
setUseTooltip(true);
|
||||
},
|
||||
[onValueChange]
|
||||
);
|
||||
|
||||
const handleValueCommit = React.useCallback(() => {
|
||||
setIsDragging(false);
|
||||
setUseTooltip(false);
|
||||
setInternalValues(currentValue);
|
||||
}, []);
|
||||
|
||||
const renderThumb = (index: number) => {
|
||||
const thumbValue = currentValue[index];
|
||||
const shouldShowTooltip = showTooltip && (isDragging || isHovered);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
key={index}
|
||||
title={shouldShowTooltip ? String(thumbValue) : undefined}
|
||||
open={shouldShowTooltip}
|
||||
side="top"
|
||||
sideOffset={5}>
|
||||
<SliderPrimitive.Thumb
|
||||
className="border-primary/50 bg-background focus-visible:ring-ring block h-4 w-4 rounded-full border shadow transition-colors focus-visible:ring-1 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50"
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<SliderPrimitive.Root
|
||||
ref={ref}
|
||||
|
@ -79,7 +66,21 @@ const Slider = React.forwardRef<React.ElementRef<typeof SliderPrimitive.Root>, S
|
|||
<SliderPrimitive.Track className="bg-primary/20 relative h-1.5 w-full grow overflow-hidden rounded-full">
|
||||
<SliderPrimitive.Range className="bg-primary absolute h-full" />
|
||||
</SliderPrimitive.Track>
|
||||
{Array.from({ length: currentValue.length }).map((_, index) => renderThumb(index))}
|
||||
|
||||
<TooltipProvider>
|
||||
<Tooltip open={useTooltip}>
|
||||
<TooltipTrigger asChild>
|
||||
<SliderPrimitive.Thumb
|
||||
onMouseEnter={() => setUseTooltip(true)}
|
||||
onMouseLeave={() => setUseTooltip(false)}
|
||||
className="border-primary bg-background block h-4 w-4 rounded-full border-2 shadow transition-all hover:scale-110 focus:outline-0 disabled:pointer-events-none disabled:opacity-50"
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={5}>
|
||||
{internalValues[0]}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</SliderPrimitive.Root>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { SliderWithInputNumber } from './SliderWithInputNumber';
|
||||
import { fn } from '@storybook/test';
|
||||
|
||||
const meta: Meta<typeof SliderWithInputNumber> = {
|
||||
title: 'UI/Slider/SliderWithInputNumber',
|
||||
component: SliderWithInputNumber,
|
||||
tags: ['autodocs'],
|
||||
args: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
value: 50,
|
||||
onChange: fn()
|
||||
},
|
||||
argTypes: {
|
||||
min: {
|
||||
control: 'number',
|
||||
description: 'Minimum value of the slider'
|
||||
},
|
||||
max: {
|
||||
control: 'number',
|
||||
description: 'Maximum value of the slider'
|
||||
},
|
||||
value: {
|
||||
control: 'number',
|
||||
description: 'Current value of the slider'
|
||||
},
|
||||
onChange: {
|
||||
action: 'changed',
|
||||
description: 'Callback when value changes'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof SliderWithInputNumber>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
value: 50
|
||||
}
|
||||
};
|
||||
|
||||
export const WithCustomMinMax: Story = {
|
||||
args: {
|
||||
min: 1000,
|
||||
max: 5000,
|
||||
value: 2500
|
||||
}
|
||||
};
|
||||
|
||||
export const WithLowValue: Story = {
|
||||
args: {
|
||||
value: 10
|
||||
}
|
||||
};
|
||||
|
||||
export const WithHighValue: Story = {
|
||||
args: {
|
||||
value: 90
|
||||
}
|
||||
};
|
|
@ -0,0 +1,54 @@
|
|||
import { InputNumber } from '../inputs';
|
||||
import { Slider } from './Slider';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useMemoizedFn } from '@/hooks/useMemoizedFn';
|
||||
|
||||
interface SliderWithInputNumberProps {
|
||||
min: number;
|
||||
max: number;
|
||||
value: number;
|
||||
onChange: (value: number) => void;
|
||||
}
|
||||
|
||||
export const SliderWithInputNumber: React.FC<SliderWithInputNumberProps> = ({
|
||||
min,
|
||||
max,
|
||||
value,
|
||||
onChange
|
||||
}) => {
|
||||
const [internalValue, setInternalValue] = React.useState(value);
|
||||
|
||||
const onChangeSlider = useMemoizedFn((value: number[]) => {
|
||||
setInternalValue(value[0]);
|
||||
onChange(value[0]);
|
||||
});
|
||||
|
||||
const onChangeInputNumber = useMemoizedFn((value: number) => {
|
||||
setInternalValue(value);
|
||||
onChange(value);
|
||||
});
|
||||
|
||||
const styleOfInputNumber = useMemo(() => {
|
||||
return { width: `${internalValue.toString().length * 17}px` };
|
||||
}, [internalValue]);
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-3">
|
||||
<InputNumber
|
||||
className="min-w-[50px]"
|
||||
style={styleOfInputNumber}
|
||||
min={min}
|
||||
max={max}
|
||||
value={internalValue}
|
||||
onChange={onChangeInputNumber}
|
||||
/>
|
||||
<Slider
|
||||
className="w-full"
|
||||
min={min}
|
||||
max={max}
|
||||
value={[internalValue]}
|
||||
onValueChange={onChangeSlider}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1 +1,2 @@
|
|||
export { Slider } from './Slider';
|
||||
export { SliderWithInputNumber } from './SliderWithInputNumber';
|
||||
|
|
|
@ -41,7 +41,6 @@ export const useUpdateMetricChart = (props?: { metricId?: string }) => {
|
|||
...chartConfig
|
||||
};
|
||||
|
||||
console.log(newChartConfig.yAxisScaleType);
|
||||
onUpdateMetricDebounced({
|
||||
id: metricId,
|
||||
chart_config: newChartConfig
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
MIN_DONUT_WIDTH
|
||||
} from '@/api/asset_interfaces';
|
||||
import { InputNumber } from '@/components/ui/inputs';
|
||||
import { Slider } from '@/components/ui/slider';
|
||||
import { Slider, SliderWithInputNumber } from '@/components/ui/slider';
|
||||
import { useMemoizedFn } from '@/hooks';
|
||||
import { AppSegmented, SegmentedItem } from '@/components/ui/segmented';
|
||||
|
||||
|
@ -71,22 +71,12 @@ export const EditPieAppearance = React.memo(
|
|||
|
||||
{showDonutWidthSelector && !hasMultipleYAxis && (
|
||||
<LabelAndInput label="Donut width">
|
||||
<div className="flex items-center space-x-3">
|
||||
<InputNumber
|
||||
className="max-w-[50px]"
|
||||
min={DONUT_WIDTH_MIN}
|
||||
max={DONUT_WIDTH_MAX}
|
||||
value={value}
|
||||
onChange={setPieDonutWidth}
|
||||
/>
|
||||
<Slider
|
||||
className="w-full"
|
||||
min={DONUT_WIDTH_MIN}
|
||||
max={DONUT_WIDTH_MAX}
|
||||
defaultValue={[value]}
|
||||
onValueChange={onChangeSlider}
|
||||
/>
|
||||
</div>
|
||||
<SliderWithInputNumber
|
||||
min={DONUT_WIDTH_MIN}
|
||||
max={DONUT_WIDTH_MAX}
|
||||
value={value}
|
||||
onChange={setPieDonutWidth}
|
||||
/>
|
||||
</LabelAndInput>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useState } from 'react';
|
||||
import { LabelAndInput } from '../Common';
|
||||
import { InputNumber } from '@/components/ui/inputs';
|
||||
import { Slider } from '@/components/ui/slider';
|
||||
import { Slider, SliderWithInputNumber } from '@/components/ui/slider';
|
||||
import { IBusterMetricChartConfig } from '@/api/asset_interfaces';
|
||||
import { useMemoizedFn } from '@/hooks';
|
||||
|
||||
|
@ -26,23 +26,12 @@ export const EditPieMinimumSlicePercentage = React.memo(
|
|||
|
||||
return (
|
||||
<LabelAndInput label="Minimum slice %">
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<InputNumber
|
||||
min={0}
|
||||
max={100}
|
||||
placeholder="2.5"
|
||||
className="max-w-[50px]"
|
||||
value={pieMinimumSlicePercentage}
|
||||
onChange={(value) => onChange(value || 0)}
|
||||
/>
|
||||
<Slider
|
||||
className="w-full"
|
||||
min={0}
|
||||
max={100}
|
||||
value={[pieMinimumSlicePercentage]}
|
||||
onValueChange={onChangeSlider}
|
||||
/>
|
||||
</div>
|
||||
<SliderWithInputNumber
|
||||
min={0}
|
||||
max={100}
|
||||
value={pieMinimumSlicePercentage}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</LabelAndInput>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useState } from 'react';
|
||||
import { LabelAndInput } from '../../../Common/LabelAndInput';
|
||||
import { InputNumber } from '@/components/ui/inputs';
|
||||
import { Slider } from '@/components/ui/slider';
|
||||
import { Slider, SliderWithInputNumber } from '@/components/ui/slider';
|
||||
import { ColumnSettings } from '@/api/asset_interfaces/metric/charts';
|
||||
import { useMemoizedFn } from '@/hooks';
|
||||
|
||||
|
@ -22,28 +22,14 @@ export const EditBarRoundness: React.FC<{
|
|||
setValue(v || 0);
|
||||
});
|
||||
|
||||
const onUpdateBarRoundnessSlider = useMemoizedFn((v: [number]) => {
|
||||
if (v) onUpdateBarRoundness(v?.[0]);
|
||||
});
|
||||
|
||||
return (
|
||||
<LabelAndInput label="Bar roundness">
|
||||
<div className="flex items-center space-x-3">
|
||||
<InputNumber
|
||||
className="max-w-[40px]"
|
||||
min={BAR_ROUNDNESS_MIN}
|
||||
max={BAR_ROUNDNESS_MAX}
|
||||
value={value}
|
||||
onChange={onUpdateBarRoundness}
|
||||
/>
|
||||
<Slider
|
||||
className="w-full"
|
||||
min={BAR_ROUNDNESS_MIN}
|
||||
max={BAR_ROUNDNESS_MAX}
|
||||
defaultValue={[value]}
|
||||
onValueChange={onUpdateBarRoundnessSlider}
|
||||
/>
|
||||
</div>
|
||||
<SliderWithInputNumber
|
||||
min={BAR_ROUNDNESS_MIN}
|
||||
max={BAR_ROUNDNESS_MAX}
|
||||
value={value}
|
||||
onChange={onUpdateBarRoundness}
|
||||
/>
|
||||
</LabelAndInput>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -25,8 +25,6 @@ export const EditAxisLabelRotation: React.FC<{
|
|||
onChangeLabelRotation(value.value as IBusterMetricChartConfig['xAxisLabelRotation']);
|
||||
});
|
||||
|
||||
console.log(selectedOption, xAxisLabelRotation);
|
||||
|
||||
return (
|
||||
<LabelAndInput label="Axis orientation">
|
||||
<AppSegmented
|
||||
|
|
|
@ -14,7 +14,6 @@ export const EditAxisScale: React.FC<{
|
|||
| IBusterMetricChartConfig['y2AxisScaleType'];
|
||||
onChangeAxisScale: (value: IBusterMetricChartConfig['yAxisScaleType']) => void;
|
||||
}> = React.memo(({ scaleType, onChangeAxisScale }) => {
|
||||
console.log(scaleType);
|
||||
return (
|
||||
<LabelAndInput label="Scale">
|
||||
<Select items={options} value={scaleType} onChange={onChangeAxisScale} />
|
||||
|
|
|
@ -24,7 +24,6 @@ export const EditShowAxisTitle: React.FC<{
|
|||
const [show, setShow] = useState(showAxisTitle);
|
||||
|
||||
const onToggleAxisTitle = useMemoizedFn((show: boolean) => {
|
||||
console.log(show);
|
||||
setShow(show);
|
||||
onChangeShowAxisTitle(show);
|
||||
|
||||
|
|
Loading…
Reference in New Issue