diff --git a/docs/demo/basic.tsx b/docs/demo/basic.tsx index 649a853..4839882 100644 --- a/docs/demo/basic.tsx +++ b/docs/demo/basic.tsx @@ -1,7 +1,7 @@ -import '../../assets/style.less'; -import React from 'react'; import Segmented from 'rc-segmented'; - +import React from 'react'; +import '../../assets/style.less'; +type Options = 'iOS' | 'Android' | 'Web'; export default function App() { return (
@@ -21,7 +21,7 @@ export default function App() {
- options={[ 'iOS', { label: 'Android', value: 'Android', disabled: true }, diff --git a/src/MotionThumb.tsx b/src/MotionThumb.tsx index a2e26cc..1a64822 100644 --- a/src/MotionThumb.tsx +++ b/src/MotionThumb.tsx @@ -3,7 +3,7 @@ import CSSMotion from 'rc-motion'; import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect'; import { composeRef } from 'rc-util/lib/ref'; import * as React from 'react'; -import type { SegmentedValue } from '.'; +import type { SegmentedRawValue, SegmentedValue } from '.'; type ThumbReact = { left: number; @@ -11,10 +11,10 @@ type ThumbReact = { width: number; } | null; -export interface MotionThumbInterface { +export interface MotionThumbInterface { containerRef: React.RefObject; - value: SegmentedValue; - getValueIndex: (value: SegmentedValue) => number; + value: SegmentedValue; + getValueIndex: (value: SegmentedValue) => number; prefixCls: string; motionName: string; onMotionStart: VoidFunction; @@ -39,7 +39,9 @@ const calcThumbStyle = ( const toPX = (value: number) => value !== undefined ? `${value}px` : undefined; -export default function MotionThumb(props: MotionThumbInterface) { +export default function MotionThumb( + props: MotionThumbInterface, +) { const { prefixCls, containerRef, @@ -55,7 +57,7 @@ export default function MotionThumb(props: MotionThumbInterface) { const [prevValue, setPrevValue] = React.useState(value); // =========================== Effect =========================== - const findValueElement = (val: SegmentedValue) => { + const findValueElement = (val: SegmentedValue) => { const index = getValueIndex(val); const ele = containerRef.current?.querySelectorAll( diff --git a/src/index.tsx b/src/index.tsx index 2541a6c..09eab84 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,41 +1,62 @@ -import * as React from 'react'; import classNames from 'classnames'; import useMergedState from 'rc-util/lib/hooks/useMergedState'; -import { composeRef } from 'rc-util/lib/ref'; import omit from 'rc-util/lib/omit'; +import { composeRef } from 'rc-util/lib/ref'; +import * as React from 'react'; import MotionThumb from './MotionThumb'; -export type SegmentedValue = string | number; +export type SegmentedRawValue = string | number; -export type SegmentedRawOption = SegmentedValue; +export type SegmentedValue = T; -export interface SegmentedLabeledOption { +export type SegmentedRawOption = SegmentedValue; + +export interface SegmentedLabeledOption { className?: string; disabled?: boolean; label: React.ReactNode; - value: SegmentedRawOption; + value: SegmentedRawOption; /** * html `title` property for label */ title?: string; } -type SegmentedOptions = (SegmentedRawOption | SegmentedLabeledOption)[]; +type SegmentedOptions = ( + | SegmentedRawOption + | SegmentedLabeledOption +)[]; -export interface SegmentedProps +type InternalSegmentedOptionProps = { + prefixCls: string; + className?: string; + disabled?: boolean; + checked: boolean; + label: React.ReactNode; + title?: string; + value: SegmentedRawOption; + onChange: ( + e: React.ChangeEvent, + value: SegmentedRawOption, + ) => void; +}; + +export interface SegmentedProps extends Omit, 'onChange'> { - options: SegmentedOptions; - defaultValue?: SegmentedValue; - value?: SegmentedValue; - onChange?: (value: SegmentedValue) => void; + options: SegmentedOptions; + defaultValue?: SegmentedValue; + value?: SegmentedValue; + onChange?: (value: SegmentedValue) => void; disabled?: boolean; prefixCls?: string; direction?: 'ltr' | 'rtl'; motionName?: string; } -function getValidTitle(option: SegmentedLabeledOption) { +function getValidTitle( + option: SegmentedLabeledOption, +) { if (typeof option.title !== 'undefined') { return option.title; } @@ -46,7 +67,9 @@ function getValidTitle(option: SegmentedLabeledOption) { } } -function normalizeOptions(options: SegmentedOptions): SegmentedLabeledOption[] { +function normalizeOptions( + options: SegmentedOptions, +): SegmentedLabeledOption[] { return options.map((option) => { if (typeof option === 'object' && option !== null) { const validTitle = getValidTitle(option); @@ -65,19 +88,9 @@ function normalizeOptions(options: SegmentedOptions): SegmentedLabeledOption[] { }); } -const InternalSegmentedOption: React.FC<{ - prefixCls: string; - className?: string; - disabled?: boolean; - checked: boolean; - label: React.ReactNode; - title?: string; - value: SegmentedRawOption; - onChange: ( - e: React.ChangeEvent, - value: SegmentedRawOption, - ) => void; -}> = ({ +const InternalSegmentedOption: React.FC> = < + T extends SegmentedRawValue, +>({ prefixCls, className, disabled, @@ -86,7 +99,7 @@ const InternalSegmentedOption: React.FC<{ title, value, onChange, -}) => { +}: InternalSegmentedOptionProps) => { const handleChange = (event: React.ChangeEvent) => { if (disabled) { return; @@ -115,8 +128,11 @@ const InternalSegmentedOption: React.FC<{ ); }; -const Segmented = React.forwardRef( - (props, ref) => { +const Segmented = React.forwardRef( + ( + props: React.PropsWithChildren>, + ref: React.Ref, + ) => { const { prefixCls = 'rc-segmented', direction, @@ -152,7 +168,7 @@ const Segmented = React.forwardRef( const handleChange = ( event: React.ChangeEvent, - val: SegmentedRawOption, + val: SegmentedRawOption, ) => { if (disabled) { return; @@ -225,4 +241,10 @@ Segmented.defaultProps = { options: [], }; -export default Segmented; +const TypeSegmented = Segmented as unknown as ( + props: React.PropsWithChildren> & { + ref?: React.Ref; + }, +) => React.ReactElement; + +export default TypeSegmented; diff --git a/tests/index.test.tsx b/tests/index.test.tsx index 59fb1b1..94d9c93 100644 --- a/tests/index.test.tsx +++ b/tests/index.test.tsx @@ -2,6 +2,7 @@ import { act, fireEvent, render } from '@testing-library/react'; import * as React from 'react'; import Segmented from '../src'; +type Options = 'iOS' | 'Android' | 'Web'; jest.mock('rc-motion/lib/util/motion', () => { return { ...jest.requireActual('rc-motion/lib/util/motion'), @@ -47,7 +48,7 @@ describe('rc-segmented', () => { it('render segmented ok', () => { const { container, asFragment } = render( - options={[{ label: 'iOS', value: 'iOS' }, 'Android', 'Web']} />, ); @@ -124,7 +125,7 @@ describe('rc-segmented', () => { it('render segmented with options: 2', () => { const handleValueChange = jest.fn(); const { container, asFragment } = render( - options={['iOS', { label: 'Android', value: 'Android' }, 'Web']} onChange={(value) => handleValueChange(value)} />, @@ -140,7 +141,7 @@ describe('rc-segmented', () => { it('render segmented with options: disabled', () => { const handleValueChange = jest.fn(); const { container, asFragment } = render( - options={[ 'iOS', { label: 'Android', value: 'Android', disabled: true }, @@ -474,7 +475,7 @@ describe('rc-segmented', () => { it('render segmented with title', () => { const { asFragment, container } = render( - options={[ 'Web', { @@ -511,7 +512,7 @@ describe('rc-segmented', () => { it('render with rtl', () => { const { container } = render( - direction="rtl" options={[{ label: 'iOS', value: 'iOS' }, 'Android', 'Web']} />, diff --git a/tsconfig.json b/tsconfig.json index 850f783..d652d17 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,5 +19,5 @@ "rc-segmented": ["src/index.tsx"] } }, - "include": [".dumi/**/*", ".dumirc.ts", "src", "tests", "docs/examples"], + "include": [".dumi/**/*", ".dumirc.ts", "src", "tests", "docs/**/*"] }