Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions apps/docs/pages/docs/v2/Components/Inputs/slider.en-US.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
searchable: true
---

import { CodeEditor } from '@components/code-editor';
import PropsTable from "@components/docs/props-table";

# Slider

Component that is based on `@react-native-community/slider` library.

https://github.com/callstack/react-native-slider

## Import

```js
import { Slider } from "@ficus-ui/native";
```

## Usage

### Default

<CodeEditor code={`<Box>
<Slider />
<Slider colorScheme="green" defaultValue={0.2} />
<Slider colorScheme="red" defaultValue={0.3} />
<Slider colorScheme="orange" defaultValue={0.5} />
<Slider colorScheme="pink" filledTrackColor="pink.100" />
</Box>`} />

### Change value

<CodeEditor code={`const SimpleSlider = () => {
const [value, setValue] = React.useState(0.2);

return (
<Box>
<Text>Slider value : {Math.round(value * 100) / 100}</Text>
<Slider colorScheme="teal" value={value} onValueChange={setValue} />
</Box>
);
}
render(<SimpleSlider />)`} noInline />

### Custom step

<CodeEditor code={`<Slider colorScheme="orange" step={0.2} />`} />

## Props

Extends every `Box` props and `SliderProps` from `@react-native-community/slider`

https://github.com/callstack/react-native-slider/blob/main/package/typings/index.d.ts

### `colorScheme`
<PropsTable
description="The colorScheme property will define the slider's main color."
prop={{ type: "string", required: false }}
/>

### `filledTrackColor`
<PropsTable
description="The filled track color of the slider."
prop={{ type: "string", required: false }}
/>

### `min`
<PropsTable
description="The minimum value of the slider input."
prop={{ type: "number", required: false, default: 0 }}
/>

### `max`
<PropsTable
description="The maximum value of the slider input."
prop={{ type: "number", required: false, default: 1 }}
/>

### `defaultValue`
<PropsTable
description="The default value of the slider input."
prop={{ type: "number", required: false }}
/>
35 changes: 35 additions & 0 deletions apps/examples/app/components-v2/Slider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useState } from "react";
import { SafeAreaView } from "react-native";

import { Slider, Text } from '@ficus-ui/native';
import ExampleSection from "@/src/ExampleSection";

const SliderComponent = () => {
const [value, setValue] = useState(0.2);

return (
<SafeAreaView>
<Text mx="xl" fontSize="4xl">
Slider
</Text>
<ExampleSection name="Slider">
<Slider />
<Slider colorScheme="green" defaultValue={0.2} />
<Slider colorScheme="red" defaultValue={0.3} />
<Slider colorScheme="orange" defaultValue={0.5} />
<Slider colorScheme="pink" filledTrackColor="pink.100" />
</ExampleSection>

<ExampleSection name="Slider value">
<Text>Slider value : {Math.round(value * 100) / 100}</Text>
<Slider colorScheme="teal" value={value} onValueChange={setValue} />
</ExampleSection>

<ExampleSection name="Slider custom step">
<Slider colorScheme="orange" step={0.2} h={40}/>
</ExampleSection>
</SafeAreaView>
);
};

export default SliderComponent;
4 changes: 3 additions & 1 deletion apps/examples/app/items-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import ImageComponent from '@/app/components-v2/Image';
import PressableComponent from './components-v2/Pressable';
import DividerComponent from '@/app/components-v2/Divider';
import SpinnerComponent from '@/app/components-v2/Spinner';
import SliderComponent from '@/app/components-v2/Slider';

type ExampleComponentType = {
onScreenName: string;
Expand All @@ -36,5 +37,6 @@ export const components: ExampleComponentType[] = [
{ navigationPath: 'Image', onScreenName: 'Image', component: ImageComponent },
{ navigationPath: 'Divider', onScreenName: 'Divider', component: DividerComponent },
{ navigationPath: 'Spinner', onScreenName: 'Spinner', component: SpinnerComponent },
{ navigationPath: 'Pressable', onScreenName: 'Pressable', component: SpinnerComponent },
{ navigationPath: 'Pressable', onScreenName: 'Pressable', component: PressableComponent },
{ navigationPath: 'Slider', onScreenName: 'Slider', component: SliderComponent },
];
1 change: 1 addition & 0 deletions packages/components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ export * from './image';
export * from './pressable';
export * from './divider';
export * from './spinner';
export * from './slider';

export { ThemeProvider } from '@ficus-ui/theme';
98 changes: 98 additions & 0 deletions packages/components/src/slider/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { useMemo } from 'react';

import { ThemingProps } from '@ficus-ui/style-system';
import { getColor, useTheme } from '@ficus-ui/theme';

import {
type NativeFicusProps,
ficus,
forwardRef,
useMultiStyleConfig,
} from '../system';

interface SliderOptions {
/**
* The minimum value of the slider.
* @default 0
*/
min?: number;
/**
* The maximum value of the slider.
* @default 100
*/
max?: number;
/**
* The default value of the slider.
*/
defaultValue?: number;
/**
* The color of the track.
*/
trackColor?: string;
/**
* The color of the thumb.
*/
thumbColor?: string;
/**
* The color of the filled track.
*/
filledTrackColor?: string;
}

export interface SliderProps
extends NativeFicusProps<'Slider'>,
SliderOptions,
ThemingProps<'Slider'> {
/**
* @private
* Use `trackColor` instead.
*/
minimumTrackTintColor?: string;
/**
* @private
* Use `filledTrackColor` instead.
*/
maximumTrackTintColor?: string;
}

export const Slider = forwardRef<SliderProps, 'Slider'>(
function Slider(props, ref) {
const styles = useMultiStyleConfig('Slider', props);
const { theme } = useTheme();
const {
min,
max,
defaultValue,
trackColor,
filledTrackColor,
thumbColor,
...rest
} = props;

const sliderColors = useMemo(
() => ({
minimumTrackTintColor: getColor(
trackColor ?? styles.track?.bg,
theme.colors
),
maximumTrackTintColor: getColor(
filledTrackColor ?? styles.filledTrack?.bg,
theme.colors
),
thumbTintColor: getColor(thumbColor ?? styles.thumb?.bg, theme.colors),
}),
[trackColor, filledTrackColor, thumbColor, styles]
);

return (
<ficus.Slider
ref={ref}
minimumValue={min}
maximumValue={max}
value={defaultValue}
{...sliderColors}
{...rest}
/>
);
}
);
61 changes: 61 additions & 0 deletions packages/components/src/slider/slider.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react';

import { fireEvent, render } from '@testing-library/react-native';

import { Slider } from '.';

describe('Slider Component', () => {
it('should render correctly', () => {
const { getByTestId } = render(
<Slider min={0} max={100} defaultValue={50} testID="slider" />
);
expect(getByTestId('slider')).toBeTruthy();
});

it('should have correct default values', () => {
const { getByTestId } = render(
<Slider min={10} max={90} defaultValue={30} testID="slider" />
);
const slider = getByTestId('slider');

expect(slider.props.minimumValue).toBe(10);
expect(slider.props.maximumValue).toBe(90);
expect(slider.props.value).toBe(30);
});

it('should call onValueChange when value changes', () => {
const mockOnValueChange = jest.fn();
const { getByTestId } = render(
<Slider
min={0}
max={100}
defaultValue={50}
onValueChange={mockOnValueChange}
testID="slider"
/>
);

fireEvent(getByTestId('slider'), 'valueChange', 75);

expect(mockOnValueChange).toHaveBeenCalledTimes(1);
expect(mockOnValueChange).toHaveBeenCalledWith(75);
});

it('should apply the correct color scheme', () => {
const { getByTestId } = render(
<Slider colorScheme="blue" testID="slider" />
);
const slider = getByTestId('slider');

expect(slider.props.minimumTrackTintColor).toBe('#3182ce');
});

it('should apply the correct filled track color', () => {
const { getByTestId } = render(
<Slider filledTrackColor="red.500" testID="slider" />
);
const slider = getByTestId('slider');

expect(slider.props.maximumTrackTintColor).toBe('#E53E3E');
});
});
1 change: 1 addition & 0 deletions packages/components/src/spinner/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const BaseSpinner = ficus('ActivityIndicator', {
excludedProps: ['color'],
});

// TODO: Add to theme for default style ?
export const Spinner = forwardRef<SpinnerProps, 'Image'>((props, ref) => {
const { color = 'black', ...rest } = props;

Expand Down
2 changes: 2 additions & 0 deletions packages/components/src/system/base-elements.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import RNSlider from '@react-native-community/slider';
import {
ActivityIndicator as RNActivityIndicator,
Image as RNImage,
Expand Down Expand Up @@ -25,6 +26,7 @@ export const baseRNElements = {
TouchableWithoutFeedback: RNTouchableWithoutFeedback,
Pressable: RNPressable,
ActivityIndicator: RNActivityIndicator,
Slider: RNSlider,
} as const;

export type BaseRNElements = keyof typeof baseRNElements;
3 changes: 3 additions & 0 deletions packages/theme/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { badgeTheme } from './badge';
import { sliderTheme } from './slider';

export { badgeTheme as Badge } from './badge';
export { sliderTheme as Slider } from './slider';

export const components = {
Badge: badgeTheme,
Slider: sliderTheme,
};
59 changes: 59 additions & 0 deletions packages/theme/src/components/slider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {
createMultiStyleConfigHelpers,
defineStyle,
} from '@ficus-ui/style-system';

const sliderParts = ['container', 'track', 'thumb', 'filledTrack'] as const;

const { definePartsStyle, defineMultiStyleConfig } =
createMultiStyleConfigHelpers(sliderParts);

const baseContainerStyle = defineStyle({});

const baseTrackStyle = defineStyle((props) => {
const { colorScheme: c } = props;

return {
bg: `${c}.500`,
};
});

const baseFilledTrackStyle = defineStyle((props) => {
const { colorScheme: c } = props;

if (c === 'gray') {
return {
bg: `${c}.200`,
};
}
return {
bg: `${c}.100`,
};
});

const baseThumbStyle = defineStyle({
bg: 'white',
});

const baseStyle = definePartsStyle((props) => ({
container: baseContainerStyle,
track: baseTrackStyle(props),
thumb: baseThumbStyle,
filledTrack: baseFilledTrackStyle(props),
}));

// TODO
const sizes = {
sm: {},
md: {},
lg: {},
};

export const sliderTheme = defineMultiStyleConfig({
baseStyle,
sizes,
defaultProps: {
size: 'md',
colorScheme: 'gray',
},
});
Loading
Loading