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
19 changes: 19 additions & 0 deletions examples/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@ import React from 'react';
import '../assets/index.less';
import Cascader from '../src';

const testClassNames = {
prefix: 'test-prefix',
suffix: 'test-suffix',
input: 'test-input',
popup: {
list: 'test-popup-list',
listItem: 'test-popup-list-item',
},
};
const testStyles = {
popup: {
list: { background: 'red' },
listItem: { color: 'yellow' },
},
};
const addressOptions = [
{
label: '福建',
Expand Down Expand Up @@ -68,6 +83,10 @@ const addressOptions = [
const Demo = () => {
return (
<Cascader
prefix="prefix"
suffixIcon={() => 'icon'}
classNames={testClassNames}
styles={testStyles}
options={addressOptions}
showSearch
style={{ width: 300 }}
Expand Down
42 changes: 32 additions & 10 deletions src/Cascader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ interface BaseCascaderProps<
OptionType extends DefaultOptionType = DefaultOptionType,
ValueField extends keyof OptionType = keyof OptionType,
> extends Omit<
BaseSelectPropsWithoutPrivate,
'tokenSeparators' | 'labelInValue' | 'mode' | 'showSearch'
> {
BaseSelectPropsWithoutPrivate,
'tokenSeparators' | 'labelInValue' | 'mode' | 'showSearch'
> {
// MISC
id?: string;
prefixCls?: string;
Expand Down Expand Up @@ -129,8 +129,8 @@ export type ValueType<
ValueField extends keyof OptionType = keyof OptionType,
> = keyof OptionType extends ValueField
? unknown extends OptionType['value']
? OptionType[ValueField]
: OptionType['value']
? OptionType[ValueField]
: OptionType['value']
: OptionType[ValueField];

export type GetValueType<
Expand All @@ -146,11 +146,19 @@ export type GetOptionType<
Multiple extends boolean | React.ReactNode = false,
> = false extends Multiple ? OptionType[] : OptionType[][];

type SemanticName = 'input' | 'prefix' | 'suffix';
type PopupSemantic = 'list' | 'listItem';
export interface CascaderProps<
OptionType extends DefaultOptionType = DefaultOptionType,
ValueField extends keyof OptionType = keyof OptionType,
Multiple extends boolean | React.ReactNode = false,
> extends BaseCascaderProps<OptionType, ValueField> {
styles?: Partial<Record<SemanticName, React.CSSProperties>> & {
popup?: Partial<Record<PopupSemantic, React.CSSProperties>>;
};
classNames?: Partial<Record<SemanticName, string>> & {
popup?: Partial<Record<PopupSemantic, string>>;
};
checkable?: Multiple;
value?: GetValueType<OptionType, ValueField, Multiple>;
defaultValue?: GetValueType<OptionType, ValueField, Multiple>;
Expand Down Expand Up @@ -216,6 +224,9 @@ const Cascader = React.forwardRef<CascaderRef, InternalCascaderProps>((props, re
popupMenuColumnStyle,
popupStyle: customPopupStyle,

classNames,
styles,

placement,

onPopupVisibleChange,
Expand Down Expand Up @@ -372,7 +383,6 @@ const Cascader = React.forwardRef<CascaderRef, InternalCascaderProps>((props, re
onPopupVisibleChange?.(nextVisible);
};


// ========================== Warning ===========================
if (process.env.NODE_ENV !== 'production') {
warningNullOptions(mergedOptions, mergedFieldNames);
Expand All @@ -381,6 +391,8 @@ const Cascader = React.forwardRef<CascaderRef, InternalCascaderProps>((props, re
// ========================== Context ===========================
const cascaderContext = React.useMemo(
() => ({
classNames,
styles,
options: mergedOptions,
fieldNames: mergedFieldNames,
values: checkedValues,
Expand Down Expand Up @@ -424,12 +436,12 @@ const Cascader = React.forwardRef<CascaderRef, InternalCascaderProps>((props, re
const popupStyle: React.CSSProperties =
// Search to match width
(mergedSearchValue && searchConfig.matchInputWidth) ||
// Empty keep the width
emptyOptions
// Empty keep the width
emptyOptions
? {}
: {
minWidth: 'auto',
};
minWidth: 'auto',
};

return (
<CascaderContext.Provider value={cascaderContext}>
Expand All @@ -441,6 +453,16 @@ const Cascader = React.forwardRef<CascaderRef, InternalCascaderProps>((props, re
prefixCls={prefixCls}
autoClearSearchValue={autoClearSearchValue}
popupMatchSelectWidth={popupMatchSelectWidth}
classNames={{
prefix: classNames?.prefix,
suffix: classNames?.suffix,
input: classNames?.input,
}}
styles={{
prefix: styles?.prefix,
suffix: styles?.suffix,
input: styles?.input,
}}
popupStyle={{
...popupStyle,
...customPopupStyle,
Expand Down
15 changes: 11 additions & 4 deletions src/OptionList/Column.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import classNames from 'classnames';
import cls from 'classnames';
import * as React from 'react';
import type { DefaultOptionType, SingleValueType } from '../Cascader';
import CascaderContext from '../context';
Expand Down Expand Up @@ -53,6 +53,8 @@ export default function Column<OptionType extends DefaultOptionType = DefaultOpt
loadingIcon,
popupMenuColumnStyle,
optionRender,
classNames,
styles,
} = React.useContext(CascaderContext);

const hoverOpen = expandTrigger === 'hover';
Expand Down Expand Up @@ -117,7 +119,12 @@ export default function Column<OptionType extends DefaultOptionType = DefaultOpt

// ============================ Render ============================
return (
<ul className={menuPrefixCls} ref={menuRef} role="menu">
<ul
className={cls(menuPrefixCls, classNames?.popup?.list)}
style={styles?.popup?.list}
ref={menuRef}
role="menu"
>
{optionInfoList.map(
({
disabled,
Expand Down Expand Up @@ -163,14 +170,14 @@ export default function Column<OptionType extends DefaultOptionType = DefaultOpt
return (
<li
key={fullPathKey}
className={classNames(menuItemPrefixCls, {
className={cls(menuItemPrefixCls, classNames?.popup?.listItem, {
[`${menuItemPrefixCls}-expand`]: !isMergedLeaf,
[`${menuItemPrefixCls}-active`]:
activeValue === value || activeValue === fullPathKey,
[`${menuItemPrefixCls}-disabled`]: isOptionDisabled(disabled),
[`${menuItemPrefixCls}-loading`]: isLoading,
})}
style={popupMenuColumnStyle}
style={{ ...popupMenuColumnStyle, ...styles?.popup?.listItem }}
role="menuitemcheckbox"
title={title}
aria-checked={checked}
Expand Down
2 changes: 2 additions & 0 deletions src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export interface CascaderContextProps {
loadingIcon?: React.ReactNode;
popupMenuColumnStyle?: React.CSSProperties;
optionRender?: CascaderProps['optionRender'];
classNames?: CascaderProps['classNames'];
styles?: CascaderProps['styles'];
}

const CascaderContext = React.createContext<CascaderContextProps>({} as CascaderContextProps);
Expand Down
53 changes: 53 additions & 0 deletions tests/semantic.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { render } from '@testing-library/react';

import React from 'react';
import Cascader from '../src';

describe('Cascader.Search', () => {
it('Should support semantic', () => {
const testClassNames = {
prefix: 'test-prefix',
suffix: 'test-suffix',
input: 'test-input',
popup: {
list: 'test-popup-list',
listItem: 'test-popup-list-item',
},
};
const testStyles = {
prefix: { color: 'green' },
suffix: { color: 'blue' },
input: { color: 'purple' },
popup: {
list: { background: 'red' },
listItem: { color: 'yellow' },
},
};
const { container } = render(
<Cascader
classNames={testClassNames}
styles={testStyles}
prefix="prefix"
suffixIcon={() => 'icon'}
open
options={[{ label: 'bamboo', value: 'bamboo' }]}
optionRender={option => `${option.label} - test`}
/>,
);
const input = container.querySelector('.rc-cascader-selection-search-input');
const prefix = container.querySelector('.rc-cascader-prefix');
const suffix = container.querySelector('.rc-cascader-arrow');
const list = container.querySelector('.rc-cascader-menu');
const listItem = container.querySelector('.rc-cascader-menu-item');
expect(input).toHaveStyle(testStyles.input);
expect(prefix).toHaveStyle(testStyles.prefix);
expect(suffix).toHaveStyle(testStyles.suffix);
expect(list).toHaveStyle(testStyles.popup.list);
expect(listItem).toHaveStyle(testStyles.popup.listItem);
expect(input?.className).toContain(testClassNames.input);
expect(prefix?.className).toContain(testClassNames.prefix);
expect(suffix?.className).toContain(testClassNames.suffix);
expect(list?.className).toContain(testClassNames.popup.list);
expect(listItem?.className).toContain(testClassNames.popup.listItem);
});
});
Loading