Skip to content

Commit

Permalink
Special case Thumb render fn
Browse files Browse the repository at this point in the history
  • Loading branch information
mj12albert committed Jun 7, 2024
1 parent 1addb17 commit 3f1b132
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 27 deletions.
4 changes: 2 additions & 2 deletions docs/pages/base-ui/api/slider-thumb.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
"describedArgs": ["value", "index"]
}
},
"render": { "type": { "name": "union", "description": "element<br>&#124;&nbsp;func" } }
"render": { "type": { "name": "func" } }
},
"name": "SliderThumb",
"imports": [
"import * as Slider from '@base_ui/react/Slider';\nconst SliderThumb = Slider.Thumb;"
],
"classes": [],
"spread": false,
"spread": true,
"themeDefaultProps": true,
"muiName": "SliderThumb",
"forwardsRefTo": "HTMLSpanElement",
Expand Down
67 changes: 57 additions & 10 deletions packages/mui-base/src/Slider/SliderThumb/SliderThumb.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as React from 'react';
import { createRenderer } from '@mui/internal-test-utils';
import { expect } from 'chai';
import { createRenderer, randomStringValue } from '@mui/internal-test-utils';
import * as Slider from '@base_ui/react/Slider';
import { SliderProvider, type SliderProviderValue } from '@base_ui/react/Slider';
import { describeConformance } from '../../../test/describeConformance';
Expand Down Expand Up @@ -58,20 +59,66 @@ describe('<Slider.Thumb />', () => {
values: [0],
};

const renderTest = (node) => {
const { container, ...other } = render(
<SliderProvider value={testProviderValue}>{node}</SliderProvider>,
);

return { container, ...other };
};

describeConformance(<Slider.Thumb />, () => ({
inheritComponent: 'span',
render: (node) => {
const { container, ...other } = render(
<SliderProvider value={testProviderValue}>{node}</SliderProvider>,
);

return { container, ...other };
},
render: renderTest,
refInstanceof: window.HTMLSpanElement,
skip: [
'reactTestRenderer', // Need to be wrapped with SliderProvider
'propsSpread', // TODO: fix after relocating the <input/>
'renderProp', // TODO: fix after relocating the <input/>
'renderProp', // Manually implemented below
],
}));

// the Thumb's render fn is a special case that includes inputProps to render a "hidden" input element
describe('prop: render', () => {
const Wrapper = React.forwardRef<any, { children?: React.ReactNode }>(
function Wrapper(props, forwardedRef) {
return (
<div data-testid="base-ui-wrapper">
{/* @ts-ignore */}
<div ref={forwardedRef} {...props} data-testid="wrapped" />
</div>
);
},
);

it('renders a customized root element with a function', async () => {
const testValue = randomStringValue();
const { queryByTestId } = await renderTest(
React.cloneElement(<Slider.Thumb />, {
render: (props: {}) => <Wrapper {...props} data-test-value={testValue} />,
}),
);

expect(queryByTestId('base-ui-wrapper')).not.to.equal(null);
expect(queryByTestId('wrapped')).not.to.equal(null);
expect(queryByTestId('wrapped')).to.have.attribute('data-test-value', testValue);
});

it('should pass the ref to the custom component', () => {
let instanceFromRef = null;

function Test() {
return React.cloneElement(<Slider.Thumb />, {
ref: (el: HTMLElement | null) => {
instanceFromRef = el;
},
render: (props: {}) => <Wrapper {...props} />,
'data-testid': 'wrapped',
});
}

renderTest(<Test />);
expect(instanceFromRef!.tagName).to.equal('DIV');
expect(instanceFromRef!).to.have.attribute('data-testid', 'wrapped');
});
});
});
28 changes: 14 additions & 14 deletions packages/mui-base/src/Slider/SliderThumb/SliderThumb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,21 @@ import * as React from 'react';
import PropTypes from 'prop-types';
import { getStyleHookProps } from '../../utils/getStyleHookProps';
import { resolveClassName } from '../../utils/resolveClassName';
import { useRenderPropForkRef } from '../../utils/useRenderPropForkRef';
import { useSliderContext } from '../Root/SliderProvider';
import { SliderThumbProps } from './SliderThumb.types';
import { useSliderThumb } from './useSliderThumb';

function defaultRender(props: React.ComponentPropsWithRef<'span'>) {
return <span {...props} />;
function defaultRender(
props: React.ComponentPropsWithRef<'span'>,
inputProps: React.ComponentPropsWithRef<'input'>,
) {
const { children, ...thumbProps } = props;
return (
<span {...thumbProps}>
{children}
<input {...inputProps} />
</span>
);
}

const SliderThumb = React.forwardRef(function SliderThumb(
Expand All @@ -21,7 +29,6 @@ const SliderThumb = React.forwardRef(function SliderThumb(
'aria-label': ariaLabel,
'aria-valuetext': ariaValuetext,
className,
children,
disabled: disabledProp = false,
getAriaLabel,
getAriaValueText,
Expand All @@ -31,8 +38,6 @@ const SliderThumb = React.forwardRef(function SliderThumb(

const render = renderProp ?? defaultRender;

const mergedRef = useRenderPropForkRef(render, forwardedRef);

const {
active: activeIndex,
'aria-labelledby': ariaLabelledby,
Expand Down Expand Up @@ -74,7 +79,7 @@ const SliderThumb = React.forwardRef(function SliderThumb(
name,
orientation,
percentageValues,
rootRef: mergedRef,
rootRef: forwardedRef,
scale,
setOpen,
step,
Expand All @@ -95,12 +100,7 @@ const SliderThumb = React.forwardRef(function SliderThumb(

const inputProps = getThumbInputProps({ disabled });

return (
<span {...thumbProps}>
{children}
<input {...inputProps} />
</span>
);
return render(thumbProps, inputProps, ownerState);
});

SliderThumb.propTypes /* remove-proptypes */ = {
Expand Down Expand Up @@ -169,7 +169,7 @@ SliderThumb.propTypes /* remove-proptypes */ = {
/**
* A function to customize rendering of the component.
*/
render: PropTypes.oneOfType([PropTypes.element, PropTypes.func]),
render: PropTypes.func,
} as any;

export { SliderThumb };
10 changes: 9 additions & 1 deletion packages/mui-base/src/Slider/SliderThumb/SliderThumb.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,20 @@ export interface SliderThumbOwnerState extends SliderRootOwnerState {}

export interface SliderThumbProps
extends Partial<Omit<UseSliderThumbParameters, 'rootRef'>>,
BaseUIComponentProps<'span', SliderThumbOwnerState> {
Omit<BaseUIComponentProps<'span', SliderThumbOwnerState>, 'render'> {
onPointerLeave?: React.PointerEventHandler;
onPointerOver?: React.PointerEventHandler;
onBlur?: React.FocusEventHandler;
onFocus?: React.FocusEventHandler;
onKeyDown?: React.KeyboardEventHandler;
/**
* A function to customize rendering of the component.
*/
render?: (
props: React.ComponentPropsWithRef<'span'>,
inputProps: React.ComponentPropsWithRef<'input'>,
state: SliderThumbOwnerState,
) => React.ReactElement;
}

export interface UseSliderThumbParameters
Expand Down

0 comments on commit 3f1b132

Please sign in to comment.