Skip to content

Commit

Permalink
Add RangeSlider component (#36)
Browse files Browse the repository at this point in the history
* Add RangeSlider component

* Prepare components release 1.8.0

* Fix RangeSlider tooltip

* Prepare components release 1.8.1
  • Loading branch information
asimonok authored Apr 5, 2024
1 parent b2f23e9 commit 167bd45
Show file tree
Hide file tree
Showing 18 changed files with 545 additions and 11 deletions.
45 changes: 43 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Change Log

## 1.8.1 (2024-04-05)

### Features

- Add RangeSlider component (#36)

## 1.7.0 (2024-03-26)

### Features
Expand Down
1 change: 1 addition & 0 deletions packages/components/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- `NumberInput` allows to check and input numbers.
- `Collapse` allows to display collapsable elements.
- `Slider` allows to enter number values by slider and/or NumberInput.
- `RangeSlider` allows to enter number range values by slider.
- `Form` allows to render form controls from the declarative config generated by FormBuilder.
- `AutosizeCodeEditor` code editor with auto height resizing based on entered content.

Expand Down
6 changes: 4 additions & 2 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
"@emotion/css": "^11.11.2",
"@grafana/data": "^10.2.1",
"@grafana/ui": "^10.2.1",
"rc-slider": "^10.5.0"
"classnames": "^2.5.1",
"rc-slider": "^10.5.0",
"rc-tooltip": "^6.2.0"
},
"description": "UI Components for Grafana",
"devDependencies": {
Expand Down Expand Up @@ -83,5 +85,5 @@
"typecheck": "tsc --emitDeclarationOnly false --noEmit"
},
"types": "dist/index.d.ts",
"version": "1.7.0"
"version": "1.8.1"
}
4 changes: 2 additions & 2 deletions packages/components/rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default [
sourcemap: true,
},
],
external: ['react', '@grafana/ui', 'rc-slider', '@emotion/css', '@emotion/react'],
external: ['react', '@grafana/ui', 'rc-slider', '@emotion/css', '@emotion/react', 'rc-tooltip'],
},
{
input: `src/index.ts`,
Expand All @@ -25,6 +25,6 @@ export default [
file: `${name}.d.ts`,
format: 'es',
},
external: ['react', '@grafana/ui', 'rc-slider', '@emotion/css', '@emotion/react'],
external: ['react', '@grafana/ui', 'rc-slider', '@emotion/css', '@emotion/react', 'rc-tooltip'],
},
];
6 changes: 3 additions & 3 deletions packages/components/src/__mocks__/rc-slider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ import React from 'react';
/**
* Mock RC Slider
*/
const RcSlider: React.FC<any> = ({ onChange, ariaLabelForHandle, value }) => {
const RcSlider: React.FC<any> = ({ onChange, ariaLabelForHandle, value, range = false }) => {

Check warning on line 6 in packages/components/src/__mocks__/rc-slider.tsx

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type
return (
<input
type="number"
onChange={(event) => {
if (onChange) {
onChange(Number(event.target.value));
onChange(range ? (event.target as any).values : Number(event.target.value));

Check warning on line 12 in packages/components/src/__mocks__/rc-slider.tsx

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type
}
}}
aria-label={ariaLabelForHandle}
value={value}
value={range ? value[0] : value}
/>
);
};
Expand Down
16 changes: 16 additions & 0 deletions packages/components/src/components/Form/Form.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const meta = {
const Preview = () => {
const form = useFormBuilder<{
opacity: number;
normalize: [number, number];
custom: string;
color: string;
radio: string;
Expand All @@ -41,6 +42,21 @@ const meta = {
max: 100,
label: 'Opacity',
description: 'Opacity description',
view: {
grow: true,
},
})
.addRangeSlider({
path: 'normalize',
defaultValue: [0, 255],
min: 0,
max: 255,
label: 'Normalize',
description: 'Normalize description',
marks: { 0: '0', 100: '100' },
view: {
grow: true,
},
})
.addColorPicker({
path: 'color',
Expand Down
19 changes: 17 additions & 2 deletions packages/components/src/components/Form/Form.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,14 @@ describe('Form', () => {
newValue: '2',
expectedValue: 2,
},
{
name: 'range slider',
path: 'rangeSlider',
getField: selectors.fieldRangeSlider,
defaultValue: 0,
newValue: [5, 6],
expectedValue: 5,
},
{
name: 'number input',
path: 'number',
Expand All @@ -160,6 +168,7 @@ describe('Form', () => {
select: string;
custom: string;
slider: number;
rangeSlider: [number, number];
number: number;
color: string;
}>({
Expand Down Expand Up @@ -188,6 +197,12 @@ describe('Form', () => {
min: 0,
max: 10,
})
.addRangeSlider({
path: 'rangeSlider',
defaultValue: [0, 10],
min: 0,
max: 10,
})
.addNumberInput({
path: 'number',
defaultValue: path === 'number' ? (defaultValue as any) : 0,
Expand All @@ -207,9 +222,9 @@ describe('Form', () => {
/**
* Change Value
*/
await act(async () => fireEvent.change(getField(false, path), { target: { value: newValue } }));
await act(async () => fireEvent.change(getField(false, path), { target: { value: newValue, values: newValue } }));

expect(getField(false, path)).toHaveValue(expectedValue);
expect(getField(false, path)).toHaveValue(expectedValue as any);
});

it.each([
Expand Down
17 changes: 17 additions & 0 deletions packages/components/src/components/Form/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import React from 'react';
import { TEST_IDS } from '../../constants';
import { FormFieldType, RenderFormField } from '../../types';
import { NumberInput } from '../NumberInput';
import { RangeSlider } from '../RangeSlider';
import { Slider } from '../Slider';
import { getStyles } from './Form.styles';

Expand Down Expand Up @@ -220,6 +221,22 @@ export const Form = <TValue extends object>({
);
}

if (field.type === FormFieldType.RANGE_SLIDER) {
return (
<FieldComponent {...fieldProps}>
<RangeSlider
value={field.value}
onChange={field.onChange}
min={field.min}
max={field.max}
step={field.step}
marks={field.marks}
sliderAriaLabel={TEST_IDS.form.fieldRangeSlider(field.fullPath)}
/>
</FieldComponent>
);
}

if (field.type === FormFieldType.NUMBER_INPUT) {
return (
<FieldComponent {...fieldProps}>
Expand Down
82 changes: 82 additions & 0 deletions packages/components/src/components/RangeSlider/HandleTooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '@grafana/ui';
import type { TooltipRef } from 'rc-tooltip';
import React, { useEffect, useRef } from 'react';

/**
* To make it working with grafana build
*/
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { default: Tooltip } = require('rc-tooltip');

export const HandleTooltip = (props: {
value: number;
children: React.ReactElement;
visible: boolean;
placement: 'bottom' | 'right';
tipFormatter?: () => React.ReactNode;
}) => {
const { value, children, visible, placement, tipFormatter, ...restProps } = props;

const tooltipRef = useRef<TooltipRef>(null);
const rafRef = useRef<number | null>(null);
const styles = useStyles2(tooltipStyles);

function cancelKeepAlign() {
if (rafRef.current !== null) {
cancelAnimationFrame(rafRef.current);
}
}

function keepAlign() {
rafRef.current = requestAnimationFrame(() => {
tooltipRef.current?.forceAlign();
});
}

useEffect(() => {
if (visible) {
keepAlign();
} else {
cancelKeepAlign();
}

return cancelKeepAlign;
}, [value, visible]);

return (
<Tooltip
overlayClassName={styles.tooltip}
placement={placement}
overlay={tipFormatter ?? value}
overlayInnerStyle={{ minHeight: 'auto' }}
ref={tooltipRef}
visible={visible}
align={{
offset: [0, 4],
}}
{...restProps}
>
{children}
</Tooltip>
);
};

const tooltipStyles = (theme: GrafanaTheme2) => {
return {
tooltip: css({
position: 'absolute',
display: 'block',
visibility: 'visible',
fontSize: theme.typography.bodySmall.fontSize,
backgroundColor: theme.colors.background.primary,
opacity: 0.9,
/**
* Should be higher to be visible in Drawers
*/
zIndex: theme.zIndex.portal,
padding: theme.spacing(0, 0.5),
}),
};
};
Loading

0 comments on commit 167bd45

Please sign in to comment.