diff --git a/.github/Examples/resources/CustomStepMarker-Component.png b/.github/Examples/resources/CustomStepMarker-Component.png new file mode 100644 index 00000000..4a1d5d11 Binary files /dev/null and b/.github/Examples/resources/CustomStepMarker-Component.png differ diff --git a/.github/Examples/resources/CustomStepMarker-Default.png b/.github/Examples/resources/CustomStepMarker-Default.png new file mode 100644 index 00000000..d8f67716 Binary files /dev/null and b/.github/Examples/resources/CustomStepMarker-Default.png differ diff --git a/.github/Examples/resources/CustomStepMarker-StepnNumber.png b/.github/Examples/resources/CustomStepMarker-StepnNumber.png new file mode 100644 index 00000000..30341cb8 Binary files /dev/null and b/.github/Examples/resources/CustomStepMarker-StepnNumber.png differ diff --git a/README.md b/README.md index 1035d099..d40eebc3 100644 --- a/README.md +++ b/README.md @@ -107,11 +107,102 @@ To use this library you need to ensure you are using the correct version of Reac | `minimumTrackImage` | Assigns a minimum track image. Only static images are supported. The rightmost pixel of the image will be stretched to fill the track. | Image
.propTypes
.source | No | iOS | | `thumbImage` | Sets an image for the thumb. Only static images are supported. Needs to be a URI of a local or network image; base64-encoded SVG is not supported. | Image
.propTypes
.source | No | | | `trackImage` | Assigns a single image for the track. Only static images are supported. The center pixel of the image will be stretched to fill the track. | Image
.propTypes
.source | No | iOS | | -| ⚠️ **Experimental:**
`StepMarker` | Component to be rendered for each step on the track,
with the possibility to change the styling, when thumb is at that given step | `FC`,
where
`MarkerProps`: `{stepMarked: boolean}` | No | iOS, Android, Windows | -| ⚠️ **Experimental:**
`renderStepNumber` | Turns on the displaying of numbers of steps.
Numbers of steps are displayed under the track | bool | No | iOS, Android, Windows | +| [`StepMarker`](#stepmarker) | Component to be rendered for each step on the track,
with the possibility to change the styling, when thumb is at that given step | `FC` | No | iOS, Android, Windows | +| [`renderStepNumber`](#renderstepnumber) | Turns on the displaying of numbers of steps.
Numbers of steps are displayed under the track | bool | No | iOS, Android, Windows | | `ref` | Reference object. | MutableRefObject | No | web | | `View` | [Inherited `View` props...](https://github.com/facebook/react-native-website/blob/master/docs/view.md#props) | | | | +## Custom step marker and step numbers + +It is possible to render default step numbers under your slider and to render custom step component and step marker. +
This can be achieved by using: + +### `renderStepNumber` + +Turns on the displaying of numbers of steps.
Numbers of steps are displayed under the track. +
Two font sizes are available and they will be selected automatically depending on the overall number of steps. + +### `StepMarker` + +Your custom component rendered for every step on the Slider, both the thumb and the rest of steps along the Slider's whole length. +
This `StepMarker` prop accepts your custom component and provides it with the following parameters: + + + + + + + + + + + + + + + + + + + + + + +
+ +```typescript + stepMarked: boolean; +``` + + + +Indicates whether that custom step is the one that the thum is currently on. +
If user drags or clicks on that step, thumb will be moved there and the `stepMarked` parameter will be set to `true`. +
Use it, to differentiate between rendering your custom thumb component, or your custom step marker. + +
+ +```typescript + currentValue: number; +``` + + +Contains the `number` type saying at which value of the Slider the thumb currently is. +
Use it, for example, to render the Slider value on every step marker, or to render different step marker's variant depending on the Thumb position. + +
+ +```typescript + index: number; +``` + + + +Contains the index at which this exact instantiation of your custom step marker was rendered at. +
Use it if you would like to render step number within the step marker, or, for example, if you want to render many variants of step marker depending on their positions along the Slider's width. + +
+ +```typescript + min: number; +``` + + + +Minimum value of the Slider. It is equal to `minimumValue` and has the same default if not set. + +
+ +```typescript + max: number; +``` + + + +Maximum value of the Slider. It is equal to `maximumValue` and has the same default if not set. + +
+ ## Roadmap and Progress There's a Project board available [here](https://github.com/callstack/react-native-slider/projects/1) which contains all reported issues organized into columns regarding their status. diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index bf740a51..cd43d463 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1806,10 +1806,10 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 1dca942403ed9342f98334bf4c3621f011aa7946 - DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953 + DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385 FBLazyVector: be7314029d6ec6b90f0f75ce1195b8130ed9ac4f fmt: 10c6e61f4be25dc963c36bd73fc7b1705fe975be - glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 + glog: 08b301085f15bcbb6ff8632a8ebaf239aae04e6a hermes-engine: 0555a84ea495e8e3b4bde71b597cd87fbb382888 RCT-Folly: bf5c0376ffe4dd2cf438dcf86db385df9fdce648 RCTDeprecation: 2c5e1000b04ab70b53956aa498bf7442c3c6e497 @@ -1874,4 +1874,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 1a52083da6c006694389963dc5ddd0deab10abc9 -COCOAPODS: 1.15.2 +COCOAPODS: 1.14.2 diff --git a/example/package-lock.json b/example/package-lock.json index 38be536d..3656f6cb 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -49,6 +49,7 @@ } }, "../package": { + "name": "@react-native-community/slider", "version": "4.5.5", "license": "MIT", "devDependencies": { diff --git a/example/src/Examples.tsx b/example/src/Examples.tsx index d0448042..9f67eea6 100644 --- a/example/src/Examples.tsx +++ b/example/src/Examples.tsx @@ -1,4 +1,4 @@ -import React, {FC, useState} from 'react'; +import React, {FC, useCallback, useState} from 'react'; import {Text, View, StyleSheet, Image} from 'react-native'; import Slider, {MarkerProps, SliderProps} from '@react-native-community/slider'; @@ -70,6 +70,18 @@ const SlidingCompleteExample = (props: SliderProps) => { }; const SlidingStepsExample = (props: SliderProps) => { + const renderStepMarker = useCallback(({stepMarked}: MarkerProps) => { + return stepMarked ? ( + + + + ) : ( + + + + ); + }, []); + return ( { maximumValue={4} step={1} tapToSeek - StepMarker={({stepMarked}) => { - return stepMarked ? ( - - - - ) : ( - - - - ); - }} + StepMarker={renderStepMarker} minimumTrackTintColor={'#112233'} maximumTrackTintColor={'#00FF00'} /> @@ -97,6 +99,18 @@ const SlidingStepsExample = (props: SliderProps) => { }; const SlidingStepsNumbersExample = (props: SliderProps) => { + const renderStepMarker = useCallback(({stepMarked}: MarkerProps) => { + return stepMarked ? ( + + + + ) : ( + + + + ); + }, []); + return ( { step={1} tapToSeek renderStepNumber - StepMarker={({stepMarked}) => { - return stepMarked ? ( - - - - ) : ( - - - - ); - }} + StepMarker={renderStepMarker} minimumTrackTintColor={'#123456'} maximumTrackTintColor={'#00FF00'} /> @@ -125,6 +129,18 @@ const SlidingStepsNumbersExample = (props: SliderProps) => { }; const SlidingStepsSmallNumbersExample = (props: SliderProps) => { + const renderStepMarker = useCallback(({stepMarked}: MarkerProps) => { + return stepMarked ? ( + + + + ) : ( + + + + ); + }, []); + return ( { step={1} tapToSeek renderStepNumber - StepMarker={({stepMarked}) => { - return stepMarked ? ( - - - - ) : ( - - - - ); - }} + StepMarker={renderStepMarker} minimumTrackTintColor={'#123456'} maximumTrackTintColor={'#00FF00'} /> @@ -153,6 +159,17 @@ const SlidingStepsSmallNumbersExample = (props: SliderProps) => { }; const SlidingCustomStepsThumbImageNumbersExample = (props: SliderProps) => { + const renderStepMarker = useCallback(({stepMarked}: MarkerProps) => { + return stepMarked ? ( + + + + ) : ( + + + + ); + }, []); return ( { tapToSeek renderStepNumber thumbImage={require('./resources/ck-icon.png')} - StepMarker={({stepMarked}) => { - return stepMarked ? ( - - - - ) : ( - - - - ); - }} + StepMarker={renderStepMarker} minimumTrackTintColor={'#123456'} maximumTrackTintColor={'#654321'} /> @@ -184,6 +191,18 @@ const SlidingCustomStepsThumbImageNumbersExample = (props: SliderProps) => { const SlidingCustomStepsAnotherThumbImageNumbersExample = ( props: SliderProps, ) => { + const renderStepMarker = useCallback(({stepMarked}: MarkerProps) => { + return stepMarked ? ( + + + + ) : ( + + + + ); + }, []); + return ( { - return stepMarked ? ( - - - - ) : ( - - - - ); - }} + StepMarker={renderStepMarker} minimumTrackTintColor={'#123456'} maximumTrackTintColor={'#654321'} /> @@ -213,6 +222,18 @@ const SlidingCustomStepsAnotherThumbImageNumbersExample = ( }; const InvertedSliderWithStepMarker = (props: SliderProps) => { + const renderStepMarker = useCallback(({stepMarked}: MarkerProps) => { + return stepMarked ? ( + + + + ) : ( + + + + ); + }, []); + return ( { tapToSeek renderStepNumber thumbImage={require('./resources/twitter-small.png')} - StepMarker={({stepMarked}) => { - return stepMarked ? ( - - - - ) : ( - - - - ); - }} + StepMarker={renderStepMarker} inverted minimumTrackTintColor={'#123456'} maximumTrackTintColor={'#654321'} @@ -245,6 +256,14 @@ const InvertedSliderWithStepMarker = (props: SliderProps) => { const SlidingCustomStepsThumbImageWithNumbersAndDifferentWidth = ( props: SliderProps, ) => { + const renderStepMarker = useCallback(({stepMarked}: MarkerProps) => { + return stepMarked ? ( + + ) : ( + + ); + }, []); + return ( { - return stepMarked ? ( - - ) : ( - - ); - }} + StepMarker={renderStepMarker} minimumTrackTintColor={'#ABCDEF'} maximumTrackTintColor={'#001122'} /> @@ -294,6 +307,34 @@ const MyStepMarker: FC = ({stepMarked, currentValue}) => { ); }; +const CustomComponent: FC = ({ + stepMarked, + currentValue, + index, + max, +}) => { + if (stepMarked) { + return ( + + + {index} + + + {max} + + + / + + + ); + } + return currentValue > index ? ( + + ) : ( + + ); +}; + const SliderExampleWithCustomMarker = (props: SliderProps) => { const [value, setValue] = useState(props.value ?? CONSTANTS.MIN_VALUE); @@ -318,21 +359,21 @@ const SliderExampleWithCustomMarker = (props: SliderProps) => { ); }; - -const SliderExampleWithCustomMarkerWithDefaultProps = (props: SliderProps) => { - const [value, setValue] = useState(props.value ?? CONSTANTS.MIN_VALUE); +const SliderExampleWithCustomComponentAndFilledSteps = (props: SliderProps) => { + const [value, setValue] = useState(props.value || 50); return ( - + {value && +value.toFixed(3)} @@ -349,6 +390,43 @@ const styles = StyleSheet.create({ fontWeight: '500', margin: 0, }, + trackText: { + color: '#FFFFFF', + fontSize: 10, + justifyContent: 'center', + alignSelf: 'center', + top: 12, + }, + trackDot: { + width: 10, + height: 10, + borderRadius: 10, + top: 4, + }, + empty: { + backgroundColor: '#B3BFC9', + }, + filled: { + backgroundColor: '#00629A', + }, + customComponentFrame: { + flex: 1, + flexDirection: 'row', + top: -10, + opacity: 0.95, + }, + customComponentLeftFrame: { + height: 40, + width: 20, + borderTopLeftRadius: 40, + borderBottomLeftRadius: 40, + }, + customComponentRightFrame: { + height: 40, + width: 20, + borderTopRightRadius: 40, + borderBottomRightRadius: 40, + }, divider: { width: 2, height: 20, @@ -635,9 +713,9 @@ export const examples: Props[] = [ }, }, { - title: 'Custom step marker but default step and min/max values', + title: 'Custom component with steps filled when passed', render() { - return ; + return ; }, }, { diff --git a/package/src/Slider.tsx b/package/src/Slider.tsx index 9c1b8fa4..1e6d3ae7 100644 --- a/package/src/Slider.tsx +++ b/package/src/Slider.tsx @@ -211,7 +211,7 @@ const SliderComponent = ( ...localProps } = props; const [currentValue, setCurrentValue] = useState( - props.value ?? props.minimumValue, + props.value || props.minimumValue || constants.SLIDER_DEFAULT_INITIAL_VALUE, ); const [width, setWidth] = useState(0); @@ -350,7 +350,7 @@ const SliderComponent = ( const SliderWithRef = React.forwardRef(SliderComponent); SliderWithRef.defaultProps = { - value: 0, + value: constants.SLIDER_DEFAULT_INITIAL_VALUE, minimumValue: 0, maximumValue: 1, step: 0, diff --git a/package/src/components/StepsIndicator.tsx b/package/src/components/StepsIndicator.tsx index a28b12c6..be3b0eaf 100644 --- a/package/src/components/StepsIndicator.tsx +++ b/package/src/components/StepsIndicator.tsx @@ -1,4 +1,4 @@ -import React, {FC, Fragment} from 'react'; +import React, {FC, Fragment, useCallback, useMemo} from 'react'; import {View} from 'react-native'; import {StepNumber} from './StepNumber'; import {MarkerProps, SliderTrackMark} from './TrackMark'; @@ -18,19 +18,58 @@ export const StepsIndicator = ({ }: { options: number[]; sliderWidth: number; - currentValue?: number; + currentValue: number; StepMarker?: FC; renderStepNumber?: boolean; thumbImage?: ImageSource; isLTR?: boolean; }) => { - const stepNumberFontStyle = { - fontSize: - options.length > 9 - ? constants.STEP_NUMBER_TEXT_FONT_SMALL - : constants.STEP_NUMBER_TEXT_FONT_BIG, - }; + const stepNumberFontStyle = useMemo(() => { + return { + fontSize: + options.length > 9 + ? constants.STEP_NUMBER_TEXT_FONT_SMALL + : constants.STEP_NUMBER_TEXT_FONT_BIG, + }; + }, [options.length]); const values = isLTR ? options.reverse() : options; + + const renderStepIndicator = useCallback( + (i: number, index: number) => { + return ( + + + + {renderStepNumber ? ( + + ) : null} + + + ); + }, + [ + currentValue, + StepMarker, + options, + thumbImage, + renderStepNumber, + stepNumberFontStyle, + ], + ); + return ( - {values.map((i, index) => { - return ( - - - - {renderStepNumber ? ( - - ) : null} - - - ); - })} + {values.map((i, index) => renderStepIndicator(i, index))} ); }; diff --git a/package/src/components/TrackMark.tsx b/package/src/components/TrackMark.tsx index bd87b237..e9e4c52d 100644 --- a/package/src/components/TrackMark.tsx +++ b/package/src/components/TrackMark.tsx @@ -4,26 +4,41 @@ import {styles} from '../utils/styles'; export type MarkerProps = { stepMarked: boolean; - currentValue?: number; + currentValue: number; + index: number; + min: number; + max: number; }; export type TrackMarksProps = { isTrue: boolean; + index: number; thumbImage?: ImageURISource; StepMarker?: FC; - currentValue?: number; + currentValue: number; + min: number; + max: number; }; export const SliderTrackMark = ({ isTrue, + index, thumbImage, StepMarker, currentValue, + min, + max, }: TrackMarksProps) => { return ( {StepMarker ? ( - + ) : null} {thumbImage && isTrue ? ( diff --git a/package/src/utils/constants.ts b/package/src/utils/constants.ts index 6dd27a09..9cc7d4c1 100644 --- a/package/src/utils/constants.ts +++ b/package/src/utils/constants.ts @@ -1,6 +1,7 @@ import {Platform} from 'react-native'; export const constants = { + SLIDER_DEFAULT_INITIAL_VALUE: 0, MARGIN_HORIZONTAL_PADDING: 0.05, STEP_NUMBER_TEXT_FONT_SMALL: 8, STEP_NUMBER_TEXT_FONT_BIG: 12, diff --git a/package/typings/index.d.ts b/package/typings/index.d.ts index abae2dcb..0b548440 100644 --- a/package/typings/index.d.ts +++ b/package/typings/index.d.ts @@ -22,14 +22,18 @@ export interface SliderRef { export type TrackMarksProps = { isTrue: boolean; + index: number; thumbImage?: ImageURISource; StepMarker?: FC | boolean; - currentValue?: number; + currentValue: number; }; export type MarkerProps = { stepMarked: boolean; - currentValue?: number; + currentValue: number; + index: number; + min: number; + max: number; }; export interface SliderPropsIOS extends ReactNative.ViewProps {