Skip to content

Commit

Permalink
Create SliderControl relocating Slider as internal only component
Browse files Browse the repository at this point in the history
Update snapshots

Remove comments
  • Loading branch information
aaronrobertshaw committed Sep 23, 2022
1 parent d8af9c2 commit 3f5f8b4
Show file tree
Hide file tree
Showing 25 changed files with 2,611 additions and 1,019 deletions.
2 changes: 2 additions & 0 deletions packages/components/src/slider-control/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as SliderControl } from './slider-control/component';
export { useSliderControl } from './slider-control/hook';
48 changes: 48 additions & 0 deletions packages/components/src/slider-control/mark/component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Internal dependencies
*/
import { contextConnect, WordPressComponentProps } from '../../ui/context';
import { useMark } from './hook';
import { View } from '../../view';

import type { MarkProps } from '../types';

const UnconnectedMark = (
props: WordPressComponentProps< MarkProps, 'span' >,
forwardedRef: React.ForwardedRef< any >
) => {
const {
className,
isFilled = false,
label,
labelClassName,
style = {},
...otherProps
} = useMark( props );

return (
<>
<View
as="span"
{ ...otherProps }
aria-hidden="true"
className={ className }
style={ style }
ref={ forwardedRef }
/>
{ label && (
<View
as="span"
aria-hidden="true"
className={ labelClassName }
style={ style }
>
{ label }
</View>
) }
</>
);
};

export const Mark = contextConnect( UnconnectedMark, 'Mark' );
export default Mark;
38 changes: 38 additions & 0 deletions packages/components/src/slider-control/mark/hook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* WordPress dependencies
*/
import { useMemo } from '@wordpress/element';

/**
* Internal dependencies
*/
import * as styles from '../styles';
import { useContextSystem, WordPressComponentProps } from '../../ui/context';
import { useCx } from '../../utils/hooks';

import type { MarkProps } from '../types';

export function useMark( props: WordPressComponentProps< MarkProps, 'span' > ) {
const { className, disabled, isFilled, ...otherProps } = useContextSystem(
props,
'Mark'
);

// Generate dynamic class names.
const cx = useCx();
const classes = useMemo( () => {
return cx( styles.mark( { isFilled, disabled } ), className );
}, [ className, cx, disabled, isFilled ] );

const labelClassName = useMemo( () => {
return cx( styles.markLabel( { isFilled } ) );
}, [ className, cx, isFilled ] );

return {
...otherProps,
className: classes,
disabled,
isFilled,
labelClassName,
};
}
36 changes: 36 additions & 0 deletions packages/components/src/slider-control/marks/component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Internal dependencies
*/
import { contextConnect, WordPressComponentProps } from '../../ui/context';
import { useMarks } from './hook';
import { View } from '../../view';
import { Mark } from '../mark/component';

import type { MarksProps } from '../types';

const UnconnectedMarks = (
props: WordPressComponentProps< MarksProps, 'input', false >,
forwardedRef: React.ForwardedRef< any >
) => {
const { className, disabled = false, marksData } = useMarks( props );
return (
<View
as="span"
aria-hidden="true"
className={ className }
ref={ forwardedRef }
>
{ marksData.map( ( mark ) => (
<Mark
{ ...mark }
key={ mark.key }
aria-hidden="true"
disabled={ disabled }
/>
) ) }
</View>
);
};

export const Marks = contextConnect( UnconnectedMarks, 'Marks' );
export default Marks;
43 changes: 43 additions & 0 deletions packages/components/src/slider-control/marks/hook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* WordPress dependencies
*/
import { useMemo } from '@wordpress/element';

/**
* Internal dependencies
*/
import * as styles from '../styles';
import { useContextSystem, WordPressComponentProps } from '../../ui/context';
import { useCx } from '../../utils/hooks';
import useMarksData from './use-marks-data';

import type { MarksProps } from '../types';

export function useMarks(
props: WordPressComponentProps< MarksProps, 'input', false >
) {
const {
className,
marks = false,
min = 0,
max = 100,
step: stepProp = 1,
value = 0,
...otherProps
} = useContextSystem( props, 'Marks' );

const step = stepProp === 'any' ? 1 : stepProp;
const marksData = useMarksData( { marks, min, max, step, value } );

// Generate dynamic class names.
const cx = useCx();
const classes = useMemo( () => {
return cx( styles.marks, className );
}, [ className, cx ] );

return {
...otherProps,
className: classes,
marksData,
};
}
53 changes: 53 additions & 0 deletions packages/components/src/slider-control/marks/use-marks-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* WordPress dependencies
*/
import { isRTL } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import type { MarkProps, useMarksDataArgs } from '../types';

const useMarksData = ( {
marks,
min = 0,
max = 100,
step = 1,
value = 0,
}: useMarksDataArgs ) => {
if ( ! marks || step === 0 ) {
return [];
}

const range = max - min;
if ( ! Array.isArray( marks ) ) {
marks = [];
const count = 1 + Math.round( range / step );
while ( count > marks.push( { value: step * marks.length + min } ) );
}

const placedMarks: MarkProps[] = [];
marks.forEach( ( mark, index ) => {
if ( mark.value < min || mark.value > max ) {
return;
}
const key = `mark-${ index }`;
const isFilled = mark.value <= value;
const offset = `${ ( ( mark.value - min ) / range ) * 100 }%`;

const offsetStyle = {
[ isRTL() ? 'right' : 'left' ]: offset,
};

placedMarks.push( {
...mark,
isFilled,
key,
style: offsetStyle,
} );
} );

return placedMarks;
};

export default useMarksData;
130 changes: 130 additions & 0 deletions packages/components/src/slider-control/slider-control/component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/**
* WordPress dependencies
*/
import { useInstanceId, useMergeRefs } from '@wordpress/compose';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import BaseControl from '../../base-control';
import Marks from '../marks/component';
import Slider from '../slider/component';
import Tooltip from '../tooltip/component';
import { contextConnect, WordPressComponentProps } from '../../ui/context';
import { useSliderControl } from './hook';
import { VStack } from '../../v-stack';
import { clamp } from '../../utils/math';

import type { SliderControlProps } from '../types';

const noop = () => {};

const UnconnectedSliderControl = (
props: WordPressComponentProps< SliderControlProps, 'input', false >,
forwardedRef: React.ForwardedRef< any >
) => {
const {
className,
disabled,
enableTooltip,
help,
hideLabelFromVision = false,
inputRef,
label,
marks = false,
max = 100,
min = 0,
onBlur = noop,
onChange = noop,
onFocus = noop,
onMouseLeave = noop,
onMouseMove = noop,
renderTooltipContent = ( v ) => v,
showTooltip,
step = 1,
value: valueProp,
wrapperClassName,
...otherProps
} = useSliderControl( props );

const id = useInstanceId( UnconnectedSliderControl, 'slider-control' );
const describedBy = !! help ? `${ id }__help` : undefined;

const value = valueProp;
const isValueReset = value === null;
const rangeFillValue = isValueReset ? ( max - min ) / 2 + min : value;
const fillValue = isValueReset
? 50
: ( ( value - min ) / ( max - min ) ) * 100;
const fillPercentage = clamp( fillValue, 0, 100 );

return (
<BaseControl
className={ className }
label={ label }
hideLabelFromVision={ hideLabelFromVision }
id={ `${ id }` }
help={ help }
>
<VStack className={ wrapperClassName }>
<Slider
aria-describedby={ describedBy }
className="components-range-control__slider"
disabled={ disabled }
id={ `${ id }` }
label={ label }
max={ max }
min={ min }
onBlur={ onBlur }
onChange={ onChange }
onFocus={ onFocus }
onMouseLeave={ onMouseLeave }
onMouseMove={ onMouseMove }
ref={ useMergeRefs( [ inputRef, forwardedRef ] ) }
step={ step }
value={ isValueReset ? undefined : value }
{ ...otherProps }
/>
<Marks
disabled={ disabled }
marks={ marks }
max={ max }
min={ min }
step={ step }
value={ rangeFillValue }
/>
{ enableTooltip && (
<Tooltip
inputRef={ inputRef }
renderTooltipContent={ renderTooltipContent }
show={ showTooltip }
fillPercentage={ fillPercentage }
value={ value }
/>
) }
</VStack>
</BaseControl>
);
};

/**
* `SliderControl` is a form component that lets users choose a value within a
* range.
*
* @example
* ```jsx
* import { SliderControl } from `@wordpress/components`
*
* function Example() {
* return (
* <SliderControl />
* );
* }
* ```
*/
export const SliderControl = contextConnect(
UnconnectedSliderControl,
'SliderControl'
);
export default SliderControl;
Loading

0 comments on commit 3f5f8b4

Please sign in to comment.