Skip to content
Open
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
7 changes: 7 additions & 0 deletions .changeset/empty-suits-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@react-select/docs': minor
'react-select': minor
'storybook': minor
---

Ability to customize the DummyInput component
32 changes: 32 additions & 0 deletions docs/examples/CustomDummyInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import Select, { components, DummyInputProps } from 'react-select';
import { StateOption, stateOptions } from '../data';

const DummyInput = (props: DummyInputProps<StateOption, false>) => {
return (
<div
style={{
alignItems: 'center',
border: '1px dashed #2684FF',
borderRadius: 4,
display: 'inline-flex',
gap: 6,
padding: '2px 6px',
}}
>
<span style={{ color: '#2684FF', fontSize: 11, fontWeight: 600 }}>
Custom Dummy Input
</span>
<components.DummyInput {...props} />
</div>
);
};

export default () => (
<Select
isSearchable={false}
options={stateOptions}
components={{ DummyInput }}
placeholder="Select an option"
/>
);
1 change: 1 addition & 0 deletions docs/examples/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export { default as BasicMulti } from './BasicMulti';
export { default as BasicSingle } from './BasicSingle';
export { default as CustomAriaLive } from './CustomAriaLive';
export { default as CustomControl } from './CustomControl';
export { default as CustomDummyInput } from './CustomDummyInput';
export { default as CreatableAdvanced } from './CreatableAdvanced';
export { default as CreatableInputOnly } from './CreatableInputOnly';
export { default as CreateFilter } from './CreateFilter';
Expand Down
11 changes: 11 additions & 0 deletions docs/pages/advanced/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
CreateFilter,
ControlledMenu,
CustomAriaLive,
CustomDummyInput,
CustomFilterOptions,
CustomGetOptionLabel,
CustomGetOptionValue,
Expand Down Expand Up @@ -164,6 +165,16 @@ export default function Advanced() {
</ExampleWrapper>
)}

${(
<ExampleWrapper
label="Custom DummyInput with non-searchable Select"
urlPath="docs/examples/CustomDummyInput.tsx"
raw={require('!!raw-loader!../../examples/CustomDummyInput.tsx')}
>
<CustomDummyInput />
</ExampleWrapper>
)}

## Methods

These two methods sit as callable methods on the component. They are designed
Expand Down
12 changes: 12 additions & 0 deletions docs/pages/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
CustomMultiValueRemove,
CustomMultiValueLabel,
CustomControl,
CustomDummyInput,
CustomGroup,
CustomInput,
CustomOption,
Expand Down Expand Up @@ -47,6 +48,7 @@ export default function Components() {
The following components are customisable and switchable:
* ClearIndicator
* Control
* DummyInput
* DropdownIndicator
* DownChevron
* CrossIcon
Expand Down Expand Up @@ -339,6 +341,16 @@ export default function Components() {
</ExampleWrapper>
)}

${(
<ExampleWrapper
label="Custom DummyInput Example"
urlPath="docs/examples/CustomDummyInput.tsx"
raw={require('!!raw-loader!../../examples/CustomDummyInput.tsx')}
>
<CustomDummyInput />
</ExampleWrapper>
)}

### LoadingIndicator

Loading indicator to be displayed in the Indicators Container when \`isLoading\`
Expand Down
5 changes: 3 additions & 2 deletions packages/react-select/src/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { MenuPlacer } from './components/Menu';
import LiveRegion from './components/LiveRegion';

import { createFilter, FilterOptionOption } from './filters';
import { DummyInput, ScrollManager, RequiredInput } from './internal/index';
import { ScrollManager, RequiredInput } from './internal/index';
import { AriaLiveMessages, AriaSelection } from './accessibility/index';
import { isAppleDevice } from './accessibility/helpers';

Expand Down Expand Up @@ -1712,7 +1712,7 @@ export default class Select<
menuIsOpen,
required,
} = this.props;
const { Input } = this.getComponents();
const { DummyInput, Input } = this.getComponents();
const { inputIsHidden, ariaSelection } = this.state;
const { commonProps } = this;

Expand Down Expand Up @@ -1752,6 +1752,7 @@ export default class Select<
// use a dummy input to maintain focus/blur functionality
return (
<DummyInput
{...commonProps}
id={id}
innerRef={this.getInputRef}
onBlur={this.onInputBlur}
Expand Down
20 changes: 20 additions & 0 deletions packages/react-select/src/__tests__/Select.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2892,6 +2892,26 @@ test('render custom Input Component', () => {
expect(container.querySelector('.my-input-component')).toBeInTheDocument();
});

test('render custom DummyInput Component', () => {
const DummyInputComponent = () => (
<div className="my-dummy-input-component" />
);
let { container } = render(
<Select
{...BASIC_PROPS}
isSearchable={false}
components={{ DummyInput: DummyInputComponent }}
/>
);

expect(
container.querySelector('input.react-select__dummy-input')
).not.toBeInTheDocument();
expect(
container.querySelector('.my-dummy-input-component')
).toBeInTheDocument();
});

test('render custom Menu Component', () => {
const MenuComponent = () => <div className="my-menu-component" />;
let { container } = render(
Expand Down
91 changes: 91 additions & 0 deletions packages/react-select/src/components/DummyInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/** @jsx jsx */
import { InputHTMLAttributes, Ref } from 'react';
import { jsx } from '@emotion/react';

import {
CommonPropsAndClassName,
CSSObjectWithLabel,
GroupBase,
} from '../types';
import { cleanCommonProps, removeProps } from '../utils';

export interface DummyInputProps<
Option = unknown,
IsMulti extends boolean = boolean,
Group extends GroupBase<Option> = GroupBase<Option>
> extends InputHTMLAttributes<HTMLInputElement>,
CommonPropsAndClassName<Option, IsMulti, Group> {
readonly innerRef: Ref<HTMLInputElement>;
}

export const dummyInputCSS = <
Option,
IsMulti extends boolean,
Group extends GroupBase<Option>
>(
props: DummyInputProps<Option, IsMulti, Group>,
unstyled: boolean
): CSSObjectWithLabel => {
void props;
void unstyled;

return {
label: 'dummyInput',
// get rid of any default styles
background: 0,
border: 0,
// important! this hides the flashing cursor
caretColor: 'transparent',
fontSize: 'inherit',
gridArea: '1 / 1 / 2 / 3',
outline: 0,
padding: 0,
// important! without `width` browsers won't allow focus
width: 1,

// remove cursor on desktop
color: 'transparent',

// remove cursor on mobile whilst maintaining "scroll into view" behaviour
left: -100,
opacity: 0,
position: 'relative',
transform: 'scale(.01)',
};
};

const DummyInput = <
Option,
IsMulti extends boolean,
Group extends GroupBase<Option>
>(
props: DummyInputProps<Option, IsMulti, Group>
) => {
const { cx, getStyles, getClassNames, className } = props;
const { innerRef, ...innerProps } = cleanCommonProps(props);

// Remove animation props not meant for HTML elements
const filteredProps = removeProps(
innerProps,
'onExited',
'in',
'enter',
'exit',
'appear'
);

return (
<input
ref={innerRef}
{...filteredProps}
css={getStyles('dummyInput', props)}
className={cx(
{ 'dummy-input': true },
getClassNames('dummyInput', props),
className
)}
/>
);
};

export default DummyInput;
3 changes: 3 additions & 0 deletions packages/react-select/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
} from './indicators';

import Control, { ControlProps } from './Control';
import DummyInput, { DummyInputProps } from './DummyInput';
import Group, { GroupHeading, GroupHeadingProps, GroupProps } from './Group';
import Input, { InputProps } from './Input';
import Menu, {
Expand Down Expand Up @@ -55,6 +56,7 @@ export interface SelectComponents<
> {
ClearIndicator: ComponentType<ClearIndicatorProps<Option, IsMulti, Group>>;
Control: ComponentType<ControlProps<Option, IsMulti, Group>>;
DummyInput: ComponentType<DummyInputProps<Option, IsMulti, Group>>;
DropdownIndicator: ComponentType<
DropdownIndicatorProps<Option, IsMulti, Group>
> | null;
Expand Down Expand Up @@ -103,6 +105,7 @@ export type SelectComponentsConfig<
export const components = {
ClearIndicator: ClearIndicator,
Control: Control,
DummyInput: DummyInput,
DropdownIndicator: DropdownIndicator,
DownChevron: DownChevron,
CrossIcon: CrossIcon,
Expand Down
1 change: 1 addition & 0 deletions packages/react-select/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export type {
ValueContainerProps,
} from './components/containers';
export type { ControlProps } from './components/Control';
export type { DummyInputProps } from './components/DummyInput';
export type { GroupProps, GroupHeadingProps } from './components/Group';
export type {
ClearIndicatorProps,
Expand Down
51 changes: 0 additions & 51 deletions packages/react-select/src/internal/DummyInput.tsx

This file was deleted.

1 change: 0 additions & 1 deletion packages/react-select/src/internal/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export { default as A11yText } from './A11yText';
export { default as DummyInput } from './DummyInput';
export { default as ScrollManager } from './ScrollManager';
export { default as RequiredInput } from './RequiredInput';
3 changes: 3 additions & 0 deletions packages/react-select/src/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
ValueContainerProps,
} from './components/containers';
import { ControlProps, css as controlCSS } from './components/Control';
import { dummyInputCSS, DummyInputProps } from './components/DummyInput';
import {
groupCSS,
groupHeadingCSS,
Expand Down Expand Up @@ -57,6 +58,7 @@ export interface StylesProps<
clearIndicator: ClearIndicatorProps<Option, IsMulti, Group>;
container: ContainerProps<Option, IsMulti, Group>;
control: ControlProps<Option, IsMulti, Group>;
dummyInput: DummyInputProps<Option, IsMulti, Group>;
dropdownIndicator: DropdownIndicatorProps<Option, IsMulti, Group>;
group: GroupProps<Option, IsMulti, Group>;
groupHeading: GroupHeadingProps<Option, IsMulti, Group>;
Expand Down Expand Up @@ -87,6 +89,7 @@ export const defaultStyles: {
clearIndicator: clearIndicatorCSS,
container: containerCSS,
control: controlCSS,
dummyInput: dummyInputCSS,
dropdownIndicator: dropdownIndicatorCSS,
group: groupCSS,
groupHeading: groupHeadingCSS,
Expand Down