ConfigProvider
ConfigProvider (전역 설정)
ConfigProvider는 모든 컴포넌트에 통일된 설정을 제공하는 컴포넌트예요.
출처: 문서
본문
사용법 (Usage)
이 컴포넌트는 context API를 통해 자신 아래의 모든 React 컴포넌트에 설정을 제공해요. 렌더 트리에 있는 모든 컴포넌트가 제공된 설정에 접근할 수 있죠.
import React from 'react';
import { ConfigProvider } from 'antd';
// ...
const Demo: React.FC = () => (
<ConfigProvider direction="rtl">
<App />
</ConfigProvider>
);
export default Demo;
Content Security Policy {#csp}
일부 컴포넌트는 웨이브 효과를 위해 동적 스타일을 사용해요. CSP(Content Security Policy)가 활성화되어 있다면 csp prop을 설정할 수 있어요.
<ConfigProvider csp={{ nonce: 'YourNonceCode' }}>
<Button>My Button</Button>
</ConfigProvider>
예제 (Examples)
Locale
로컬라이제이션이 필요한 컴포넌트들을 여기 나열해요. 데모에서 언어를 전환할 수 있어요.
import React, { useState } from 'react';
import { EllipsisOutlined } from '@ant-design/icons';
import type {
ConfigProviderProps,
RadioChangeEvent,
TableProps,
TourProps,
UploadFile,
} from 'antd';
import {
Button,
Calendar,
ConfigProvider,
DatePicker,
Divider,
Form,
Image,
Input,
InputNumber,
Modal,
Pagination,
Popconfirm,
QRCode,
Radio,
Select,
Space,
Table,
theme,
TimePicker,
Tour,
Transfer,
Upload,
} from 'antd';
import enUS from 'antd/locale/en_US';
import zhCN from 'antd/locale/zh_CN';
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
type Locale = ConfigProviderProps['locale'];
dayjs.locale('en');
const { RangePicker } = DatePicker;
const columns: TableProps['columns'] = [
{
title: 'Name',
dataIndex: 'name',
filters: [{ text: 'filter1', value: 'filter1' }],
},
{
title: 'Age',
dataIndex: 'age',
},
];
const Page: React.FC = () => {
const { token } = theme.useToken();
const [open, setOpen] = useState(false);
const [tourOpen, setTourOpen] = useState(false);
const tourRefs = React.useRef<HTMLElement[]>([]);
const showModal = () => {
setOpen(true);
};
const hideModal = () => {
setOpen(false);
};
const info = () => {
Modal.info({
title: 'some info',
content: 'some info',
});
};
const confirm = () => {
Modal.confirm({
title: 'some info',
content: 'some info',
});
};
const steps: TourProps['steps'] = [
{
title: 'Upload File',
description: 'Put your files here.',
target: () => tourRefs.current[0],
},
{
title: 'Save',
description: 'Save your changes.',
target: () => tourRefs.current[1],
},
{
title: 'Other Actions',
description: 'Click to see other actions.',
target: () => tourRefs.current[2],
},
];
const fileList: UploadFile[] = [
{
uid: '-1',
name: 'image.png',
status: 'done',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
},
{
uid: '-2',
percent: 50,
name: 'image.png',
status: 'uploading',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
},
{
uid: '-3',
name: 'image.png',
status: 'error',
},
];
return (
<Space
vertical
size={[0, 16]}
style={{
width: '100%',
paddingTop: token.padding,
borderTop: `${token.lineWidth}px ${token.lineType} ${token.colorBorder}`,
}}
>
<Pagination defaultCurrent={1} total={50} showSizeChanger />
<Space wrap>
<Select
showSearch
style={{ width: 200 }}
options={[
{ label: 'jack', value: 'jack' },
{ label: 'lucy', value: 'lucy' },
]}
/>
<DatePicker />
<TimePicker />
<RangePicker />
</Space>
<Space wrap>
<Button type="primary" onClick={showModal}>
Show Modal
</Button>
<Button onClick={info}>Show info</Button>
<Button onClick={confirm}>Show confirm</Button>
<Popconfirm title="Question?">
<a href="#">Click to confirm</a>
</Popconfirm>
</Space>
<Transfer dataSource={[]} showSearch targetKeys={[]} />
<div
style={{
width: 320,
border: `${token.lineWidth}px ${token.lineType} ${token.colorBorder}`,
borderRadius: token.borderRadiusLG,
}}
>
<Calendar fullscreen={false} value={dayjs()} />
</div>
<Form name="basic" autoComplete="off" labelCol={{ sm: { span: 4 } }} wrapperCol={{ span: 6 }}>
<Form.Item label="Username" name="username" rules={[{ required: true }]}>
<Input width={200} />
</Form.Item>
<Form.Item
label="Age"
name="age"
rules={[{ type: 'number', min: 0, max: 99 }]}
initialValue={100}
>
<InputNumber width={200} />
</Form.Item>
<Form.Item wrapperCol={{ offset: 2, span: 6 }}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
<Table dataSource={[]} columns={columns} />
<Modal title="Locale Modal" open={open} onCancel={hideModal}>
<p>Locale Modal</p>
</Modal>
<Space wrap size={80}>
<QRCode
value="https://ant.design/"
status="expired"
onRefresh={() => console.log('refresh')}
/>
<Image
width={160}
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
/>
</Space>
<Upload listType="picture-card" fileList={fileList} />
<Divider titlePlacement="start">Tour</Divider>
<Button type="primary" onClick={() => setTourOpen(true)}>
Begin Tour
</Button>
<Space>
<Button
ref={(node) => {
node && tourRefs.current.splice(0, 0, node);
}}
>
{' '}
Upload
</Button>
<Button
ref={(node) => {
node && tourRefs.current.splice(1, 0, node);
}}
type="primary"
>
Save
</Button>
<Button
ref={(node) => {
node && tourRefs.current.splice(2, 0, node);
}}
icon={<EllipsisOutlined />}
/>
</Space>
<Tour open={tourOpen} steps={steps} onClose={() => setTourOpen(false)} />
</Space>
);
};
const App: React.FC = () => {
const [locale, setLocale] = useState<Locale>(enUS);
const changeLocale = (e: RadioChangeEvent) => {
const localeValue = e.target.value;
setLocale(localeValue);
if (!localeValue) {
dayjs.locale('en');
} else {
dayjs.locale('zh-cn');
}
};
return (
<>
<div style={{ marginBottom: 16 }}>
<span style={{ marginInlineEnd: 16 }}>Change locale of components:</span>
<Radio.Group value={locale} onChange={changeLocale}>
<Radio.Button key="en" value={enUS}>
English
</Radio.Button>
<Radio.Button key="cn" value={zhCN}>
中文
</Radio.Button>
</Radio.Group>
</div>
<ConfigProvider locale={locale}>
<Page />
</ConfigProvider>
</>
);
};
Direction
rtl 방향을 지원하는 컴포넌트들이에요. 데모에서 방향을 전환할 수 있어요.
import React, { useState } from 'react';
import {
DownloadOutlined,
LeftOutlined,
MinusOutlined,
PlusOutlined,
RightOutlined,
SearchOutlined as SearchIcon,
SmileOutlined,
} from '@ant-design/icons';
import type { ConfigProviderProps, RadioChangeEvent } from 'antd';
import {
Badge,
Button,
Cascader,
Col,
ConfigProvider,
Divider,
Flex,
Input,
InputNumber,
Modal,
Pagination,
Radio,
Rate,
Row,
Select,
Space,
Steps,
Switch,
Tree,
TreeSelect,
} from 'antd';
import { createStyles } from 'antd-style';
type DirectionType = ConfigProviderProps['direction'];
const useStyles = createStyles((props) => {
const { css } = props;
return {
headerExample: css`
display: inline-block;
width: 42px;
height: 42px;
vertical-align: middle;
background-color: #eee;
border-radius: 4px;
`,
};
});
const { Search } = Input;
const treeData = [
{
title: 'parent 1',
key: '0-0',
children: [
{
title: 'parent 1-0',
key: '0-0-0',
disabled: true,
children: [
{ title: 'leaf', key: '0-0-0-0', disableCheckbox: true },
{ title: 'leaf', key: '0-0-0-1' },
],
},
{
title: 'parent 1-1',
key: '0-0-1',
children: [{ title: <span style={{ color: '#1677ff' }}>sss</span>, key: '0-0-1-0' }],
},
],
},
];
const treeSelectData = [
{
title: 'parent 1',
value: '0-1',
children: [
{
title: 'parent 1-0',
value: '0-1-1',
children: [
{ title: 'my leaf', value: 'random' },
{ title: 'your leaf', value: 'random1' },
],
},
{
title: 'parent 1-1',
value: 'random2',
children: [{ title: <b style={{ color: '#08c' }}>sss</b>, value: 'random3' }],
},
],
},
];
const cascaderOptions = [
{
value: 'tehran',
label: 'تهران',
children: [
{
value: 'tehran-c',
label: 'تهران',
children: [
{
value: 'saadat-abad',
label: 'سعادت آباد',
},
],
},
],
},
{
value: 'ardabil',
label: 'اردبیل',
children: [
{
value: 'ardabil-c',
label: 'اردبیل',
children: [
{
value: 'pirmadar',
label: 'پیرمادر',
},
],
},
],
},
{
value: 'gilan',
label: 'گیلان',
children: [
{
value: 'rasht',
label: 'رشت',
children: [
{
value: 'district-3',
label: 'منطقه ۳',
},
],
},
],
},
];
type Placement = 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topRight';
const Page: React.FC<{ placement: Placement }> = (props) => {
const { placement } = props;
const { styles } = useStyles();
const [currentStep, setCurrentStep] = useState(0);
const [modalOpen, setModalOpen] = useState(false);
const [badgeCount, setBadgeCount] = useState(5);
const [showBadge, setShowBadge] = useState(true);
const selectBefore = (
<Select
defaultValue="Http://"
style={{ width: 90 }}
options={[
{ label: 'Http://', value: 'Http://' },
{ label: 'Https://', value: 'Https://' },
]}
/>
);
const selectAfter = (
<Select
defaultValue=".com"
style={{ width: 80 }}
options={[
{ label: '.com', value: '.com' },
{ label: '.jp', value: '.jp' },
{ label: '.cn', value: '.cn' },
{ label: '.org', value: '.org' },
]}
/>
);
// ==== Cascader ====
const cascaderFilter = (inputValue: string, path: { label: string }[]) =>
path.some((option) => option.label.toLowerCase().includes(inputValue.toLowerCase()));
const onCascaderChange = (value: any) => {
console.log(value);
};
// ==== End Cascader ====
// ==== Modal ====
const showModal = () => {
setModalOpen(true);
};
const handleOk = () => {
setModalOpen(false);
};
const handleCancel = () => {
setModalOpen(false);
};
// ==== End Modal ====
const onStepsChange = (newCurrentStep: number) => {
console.log('onChange:', newCurrentStep);
setCurrentStep(newCurrentStep);
};
// ==== Badge ====
const increaseBadge = () => {
setBadgeCount(badgeCount + 1);
};
const declineBadge = () => {
setBadgeCount((prev) => (prev - 1 < 0 ? 0 : prev - 1));
};
const onChangeBadge = (checked: boolean) => {
setShowBadge(checked);
};
// ==== End Badge ====
return (
<Flex className="direction-components" vertical gap="large">
<Row>
<Col span={24}>
<Divider titlePlacement="start">Cascader example</Divider>
<Cascader
suffixIcon={<SearchIcon />}
options={cascaderOptions}
onChange={onCascaderChange}
placeholder="یک مورد انتخاب کنید"
placement={placement}
/>
With search:
<Cascader
suffixIcon={<SmileOutlined />}
options={cascaderOptions}
onChange={onCascaderChange}
placeholder="Select an item"
placement={placement}
showSearch={{ filter: cascaderFilter }}
/>
</Col>
</Row>
<Row>
<Col span={12}>
<Divider titlePlacement="start">Switch example</Divider>
<Switch defaultChecked />
<Switch loading defaultChecked />
<Switch size="small" loading />
</Col>
<Col span={12}>
<Divider titlePlacement="start">Radio Group example</Divider>
<Radio.Group defaultValue="c" buttonStyle="solid">
<Radio.Button value="a">تهران</Radio.Button>
<Radio.Button value="b" disabled>
اصفهان
</Radio.Button>
<Radio.Button value="c">فارس</Radio.Button>
<Radio.Button value="d">خوزستان</Radio.Button>
</Radio.Group>
</Col>
</Row>
<Row>
<Col span={12}>
<Divider titlePlacement="start">Button example</Divider>
<Flex wrap gap="small">
<Button type="primary" icon={<DownloadOutlined />} />
<Button type="primary" shape="circle" icon={<DownloadOutlined />} />
<Button type="primary" shape="round" icon={<DownloadOutlined />} />
<Button type="primary" shape="round" icon={<DownloadOutlined />}>
Download
</Button>
<Button type="primary" icon={<DownloadOutlined />}>
Download
</Button>
<Space.Compact>
<Button type="primary" icon={<LeftOutlined />}>
Backward
</Button>
<Button type="primary" icon={<RightOutlined />} iconPlacement="end">
Forward
</Button>
</Space.Compact>
<Button type="primary" loading>
Loading
</Button>
<Button type="primary" size="small" loading>
Loading
</Button>
</Flex>
</Col>
<Col span={12}>
<Divider titlePlacement="start">Tree example</Divider>
<Tree
showLine
checkable
defaultExpandedKeys={['0-0-0', '0-0-1']}
defaultSelectedKeys={['0-0-0', '0-0-1']}
defaultCheckedKeys={['0-0-0', '0-0-1']}
treeData={treeData}
/>
</Col>
</Row>
<Row>
<Col span={24}>
<Divider titlePlacement="start">Input (Input Group) example</Divider>
<Flex vertical gap="large">
<Flex vertical gap="middle">
<Row gutter={8}>
<Col span={5}>
<Input size="large" defaultValue="0571" />
</Col>
<Col span={8}>
<Input size="large" defaultValue="26888888" />
</Col>
</Row>
<Space.Compact>
<Input style={{ width: '20%' }} defaultValue="0571" />
<Input style={{ width: '30%' }} defaultValue="26888888" />
</Space.Compact>
<Space.Compact>
<Select
defaultValue="Option1"
options={[
{ label: 'Option1', value: 'Option1' },
{ label: 'Option2', value: 'Option2' },
]}
/>
<Input style={{ width: '50%' }} defaultValue="input content" />
<InputNumber />
</Space.Compact>
<Search placeholder="input search text" enterButton="Search" size="large" />
<Space.Compact>
{selectBefore}
<Input defaultValue="mysite" />
{selectAfter}
</Space.Compact>
</Flex>
<Row>
<Col span={12}>
<Divider titlePlacement="start">Select example</Divider>
<Space wrap>
<Select
mode="multiple"
defaultValue="مورچه"
style={{ width: 120 }}
options={[
{ label: 'jack', value: 'jack' },
{ label: 'مورچه', value: 'مورچه' },
{ label: 'disabled', value: 'disabled', disabled: true },
{ label: 'yiminghe', value: 'Yiminghe' },
]}
/>
<Select
disabled
defaultValue="مورچه"
style={{ width: 120 }}
options={[{ label: 'مورچه', value: 'مورچه' }]}
/>
<Select
loading
defaultValue="مورچه"
style={{ width: 120 }}
options={[{ label: 'مورچه', value: 'مورچه' }]}
/>
<Select
showSearch
style={{ width: 200 }}
placeholder="Select a person"
options={[
{ label: 'jack', value: 'jack' },
{ label: 'سعید', value: 'سعید' },
{ label: 'Tom', value: 'tom' },
]}
/>
</Space>
</Col>
<Col span={12}>
<Divider titlePlacement="start">TreeSelect example</Divider>
<TreeSelect
showSearch
style={{ width: '100%' }}
styles={{
popup: {
root: { maxHeight: 400, overflow: 'auto' },
},
}}
placeholder="Please select"
allowClear
treeDefaultExpandAll
treeData={treeSelectData}
/>
</Col>
</Row>
<Row>
<Col span={24}>
<Divider titlePlacement="start">Modal example</Divider>
<Button type="primary" onClick={showModal}>
Open Modal
</Button>
<Modal title="پنچره ساده" open={modalOpen} onOk={handleOk} onCancel={handleCancel}>
<p>نگاشتههای خود را اینجا قراردهید</p>
<p>نگاشتههای خود را اینجا قراردهید</p>
<p>نگاشتههای خود را اینجا قراردهید</p>
</Modal>
</Col>
</Row>
<Row>
<Col span={24}>
<Divider titlePlacement="start">Steps example</Divider>
<Flex vertical gap="middle">
<Steps
progressDot
current={currentStep}
items={[
{
title: 'Finished',
description: 'This is a description.',
},
{
title: 'In Progress',
description: 'This is a description.',
},
{
title: 'Waiting',
description: 'This is a description.',
},
]}
/>
<Steps
current={currentStep}
onChange={onStepsChange}
items={[
{
title: 'Step 1',
description: 'This is a description.',
},
{
title: 'Step 2',
description: 'This is a description.',
},
{
title: 'Step 3',
description: 'This is a description.',
},
]}
/>
</Flex>
</Col>
</Row>
<Row>
<Col span={12}>
<Divider titlePlacement="start">Rate example</Divider>
<Flex vertical gap="small">
<Rate defaultValue={2.5} />
<div>
<strong>* Note:</strong> Half star not implemented in RTL direction, it will be
supported after{' '}
<a
href="https://github.com/react-component/rate"
target="_blank"
rel="noopener noreferrer"
>
rc-rate
</a>{' '}
implement rtl support.
</div>
</Flex>
</Col>
<Col span={12}>
<Divider titlePlacement="start">Badge example</Divider>
<Flex align="center" gap="middle">
<Badge count={badgeCount}>
<a href="#" className={styles.headerExample} />
</Badge>
<Space.Compact>
<Button icon={<MinusOutlined />} onClick={declineBadge} />
<Button icon={<PlusOutlined />} onClick={increaseBadge} />
</Space.Compact>
</Flex>
<Flex align="center" gap="middle" style={{ marginTop: 12 }}>
<Badge dot={showBadge}>
<a href="#" className={styles.headerExample} />
</Badge>
<Switch onChange={onChangeBadge} checked={showBadge} />
</Flex>
</Col>
</Row>
</Flex>
</Col>
</Row>
<Row>
<Col span={24}>
<Divider titlePlacement="start">Pagination example</Divider>
<Pagination showSizeChanger defaultCurrent={3} total={500} />
</Col>
</Row>
<Row>
<Col span={24}>
<Divider titlePlacement="start">Grid System example</Divider>
<div className="grid-demo">
<div className="code-box-demo">
<p>
<strong>* Note:</strong> Every calculation in RTL grid system is from right side
(offset, push, etc.)
</p>
<Row>
<Col span={8}>col-8</Col>
<Col span={8} offset={8}>
col-8
</Col>
</Row>
<Row>
<Col span={6} offset={6}>
col-6 col-offset-6
</Col>
<Col span={6} offset={6}>
col-6 col-offset-6
</Col>
</Row>
<Row>
<Col span={12} offset={6}>
col-12 col-offset-6
</Col>
</Row>
<Row>
<Col span={18} push={6}>
col-18 col-push-6
</Col>
<Col span={6} pull={18}>
col-6 col-pull-18
</Col>
</Row>
</div>
</div>
</Col>
</Row>
</Flex>
);
};
const App: React.FC = () => {
const [direction, setDirection] = useState<DirectionType>('ltr');
const [placement, setPlacement] = useState<Placement>('bottomLeft');
const changeDirection = (e: RadioChangeEvent) => {
const directionValue = e.target.value;
setDirection(directionValue);
setPlacement(directionValue === 'rtl' ? 'bottomRight' : 'bottomLeft');
};
return (
<>
<div style={{ marginBottom: 16 }}>
<span style={{ marginInlineEnd: 16 }}>Change direction of components:</span>
<Radio.Group defaultValue="ltr" onChange={changeDirection}>
<Radio.Button key="ltr" value="ltr">
LTR
</Radio.Button>
<Radio.Button key="rtl" value="rtl">
RTL
</Radio.Button>
</Radio.Group>
</div>
<ConfigProvider direction={direction}>
<Page placement={placement} />
</ConfigProvider>
</>
);
};
export default App;
컴포넌트 크기 (Component size)
컴포넌트 기본 크기를 설정해요.
import React, { useState } from 'react';
import {
Button,
Card,
ConfigProvider,
DatePicker,
Divider,
Input,
Radio,
Select,
Space,
Table,
Tabs,
} from 'antd';
import type { ConfigProviderProps } from 'antd';
type SizeType = ConfigProviderProps['componentSize'];
const App: React.FC = () => {
const [componentSize, setComponentSize] = useState<SizeType>('small');
return (
<>
<Radio.Group
value={componentSize}
onChange={(e) => {
setComponentSize(e.target.value);
}}
>
<Radio.Button value="small">Small</Radio.Button>
<Radio.Button value="medium">Medium</Radio.Button>
<Radio.Button value="large">Large</Radio.Button>
</Radio.Group>
<Divider />
<ConfigProvider componentSize={componentSize}>
<Space size={[0, 16]} style={{ width: '100%' }} vertical>
<Input />
<Tabs
defaultActiveKey="1"
items={[
{
label: 'Tab 1',
key: '1',
children: 'Content of Tab Pane 1',
},
{
label: 'Tab 2',
key: '2',
children: 'Content of Tab Pane 2',
},
{
label: 'Tab 3',
key: '3',
children: 'Content of Tab Pane 3',
},
]}
/>
<Input.Search allowClear />
<Input.TextArea allowClear />
<Select defaultValue="demo" options={[{ value: 'demo' }]} />
<DatePicker />
<DatePicker.RangePicker />
<Button>Button</Button>
<Card title="Card">
<Table
columns={[
{ title: 'Name', dataIndex: 'name' },
{ title: 'Age', dataIndex: 'age' },
]}
dataSource={[
{ key: '1', name: 'John Brown', age: 32 },
{ key: '2', name: 'Jim Green', age: 42 },
{ key: '3', name: 'Joe Black', age: 32 },
]}
/>
</Card>
</Space>
</ConfigProvider>
</>
);
};
export default App;
테마 (Theme)
theme prop으로 테마를 수정해요.
import React from 'react';
import {
Button,
ColorPicker,
ConfigProvider,
Divider,
Form,
Input,
InputNumber,
Space,
Switch,
} from 'antd';
import type { ColorPickerProps, GetProp } from 'antd';
type Color = Extract<GetProp<ColorPickerProps, 'value'>, { cleared: any }>;
type ThemeData = {
borderRadius: number;
colorPrimary: string;
Button?: {
colorPrimary: string;
algorithm?: boolean;
};
};
const defaultData: ThemeData = {
borderRadius: 6,
colorPrimary: '#1677ff',
Button: {
colorPrimary: '#00B96B',
},
};
export default () => {
const [form] = Form.useForm();
const [data, setData] = React.useState<ThemeData>(defaultData);
return (
<div>
<ConfigProvider
theme={{
token: {
colorPrimary: data.colorPrimary,
borderRadius: data.borderRadius,
},
components: {
Button: {
colorPrimary: data.Button?.colorPrimary,
algorithm: data.Button?.algorithm,
},
},
}}
>
<Space>
<Input />
<Button type="primary">Button</Button>
</Space>
</ConfigProvider>
<Divider />
<Form
form={form}
onValuesChange={(_, allValues) => {
setData({
...allValues,
});
}}
name="theme"
initialValues={defaultData}
labelCol={{ span: 4 }}
wrapperCol={{ span: 20 }}
>
<Form.Item
name="colorPrimary"
label="Primary Color"
trigger="onChangeComplete"
getValueFromEvent={(color: Color) => color.toHexString()}
>
<ColorPicker />
</Form.Item>
<Form.Item name="borderRadius" label="Border Radius">
<InputNumber />
</Form.Item>
<Form.Item label="Button">
<Form.Item name={['Button', 'algorithm']} valuePropName="checked" label="algorithm">
<Switch />
</Form.Item>
<Form.Item
name={['Button', 'colorPrimary']}
label="Primary Color"
trigger="onChangeComplete"
getValueFromEvent={(color: Color) => color.toHexString()}
>
<ColorPicker />
</Form.Item>
</Form.Item>
<Form.Item name="submit" wrapperCol={{ offset: 4, span: 20 }}>
<Button type="primary">Submit</Button>
</Form.Item>
</Form>
</div>
);
};
커스텀 웨이브 (Custom Wave)
웨이브 효과가 역동성을 더해요. component로 어떤 컴포넌트가 사용하는지 결정할 수 있어요. @ant-design/happy-work-theme의 HappyProvider를 사용해 동적 웨이브 효과를 구현할 수도 있어요.
import React from 'react';
import { HappyProvider } from '@ant-design/happy-work-theme';
import { Button, ConfigProvider, Flex } from 'antd';
import type { ConfigProviderProps, GetProp } from 'antd';
type WaveConfig = GetProp<ConfigProviderProps, 'wave'>;
// Prepare effect holder
const createHolder = (node: HTMLElement) => {
const { borderWidth } = getComputedStyle(node);
const borderWidthNum = Number.parseInt(borderWidth, 10);
const div = document.createElement('div');
div.style.position = 'absolute';
div.style.inset = `-${borderWidthNum}px`;
div.style.borderRadius = 'inherit';
div.style.background = 'transparent';
div.style.zIndex = '999';
div.style.pointerEvents = 'none';
div.style.overflow = 'hidden';
node.appendChild(div);
return div;
};
const createDot = (holder: HTMLElement, color: string, left: number, top: number, size = 0) => {
const dot = document.createElement('div');
dot.style.position = 'absolute';
dot.style.insetInlineStart = `${left}px`;
dot.style.top = `${top}px`;
dot.style.width = `${size}px`;
dot.style.height = `${size}px`;
dot.style.borderRadius = '50%';
dot.style.background = color;
dot.style.transform = 'translate3d(-50%, -50%, 0)';
dot.style.transition = 'all 1s ease-out';
holder.appendChild(dot);
return dot;
};
// Inset Effect
const showInsetEffect: WaveConfig['showEffect'] = (node, { event, component }) => {
if (component !== 'Button') {
return;
}
const holder = createHolder(node);
const rect = holder.getBoundingClientRect();
const left = event.clientX - rect.left;
const top = event.clientY - rect.top;
const dot = createDot(holder, 'rgba(255, 255, 255, 0.65)', left, top);
// Motion
requestAnimationFrame(() => {
dot.ontransitionend = () => {
holder.remove();
};
dot.style.width = '200px';
dot.style.height = '200px';
dot.style.opacity = '0';
});
};
// Shake Effect
const showShakeEffect: WaveConfig['showEffect'] = (node, { component }) => {
if (component !== 'Button') {
return;
}
const seq = [0, -15, 15, -5, 5, 0];
const itv = 10;
let steps = 0;
const loop = () => {
cancelAnimationFrame((node as any).effectTimeout);
(node as any).effectTimeout = requestAnimationFrame(() => {
const currentStep = Math.floor(steps / itv);
const current = seq[currentStep];
const next = seq[currentStep + 1];
if (next === undefined || next === null) {
node.style.transform = '';
node.style.transition = '';
return;
}
// Trans from current to next by itv
const angle = current + ((next - current) / itv) * (steps % itv);
node.style.transform = `rotate(${angle}deg)`;
node.style.transition = 'none';
steps += 1;
loop();
});
};
loop();
};
// Component
const Wrapper: React.FC<WaveConfig & { name: string }> = ({ name, ...wave }) => (
<ConfigProvider wave={wave}>
<Button type="primary">{name}</Button>
</ConfigProvider>
);
const Demo: React.FC = () => (
<Flex gap="large" wrap>
<Wrapper name="Disabled" disabled />
<Wrapper name="Default" />
<Wrapper name="Inset" showEffect={showInsetEffect} />
<Wrapper name="Shake" showEffect={showShakeEffect} />
<HappyProvider>
<Button type="primary">Happy Work</Button>
</HappyProvider>
</Flex>
);
export default Demo;
정적 함수 (Static function)
holderRender를 사용해 정적 메서드 message, modal, notification의 Provider를 설정해요.
import React, { useContext, useLayoutEffect } from 'react';
import { StyleProvider } from '@ant-design/cssinjs';
import { ExclamationCircleFilled } from '@ant-design/icons';
import { App, Button, ConfigProvider, message, Modal, notification, Space } from 'antd';
const Demo: React.FC = () => {
const { locale, theme } = useContext(ConfigProvider.ConfigContext);
useLayoutEffect(() => {
ConfigProvider.config({
holderRender: (children) => (
<StyleProvider hashPriority="high">
<ConfigProvider componentSize="small" locale={locale} theme={theme}>
<App message={{ maxCount: 1 }} notification={{ maxCount: 1 }}>
{children}
</App>
</ConfigProvider>
</StyleProvider>
),
});
}, [locale, theme]);
return (
<div>
<Space>
<Button
type="primary"
onClick={() => {
message.info('This is a normal message');
}}
>
message
</Button>
<Button
type="primary"
onClick={() => {
notification.open({
title: 'Notification Title',
description:
'This is the content of the notification. This is the content of the notification. This is the content of the notification.',
});
}}
>
notification
</Button>
<Button
type="primary"
onClick={() => {
Modal.confirm({
title: 'Do you want to delete these items?',
icon: <ExclamationCircleFilled />,
content: 'Some descriptions',
});
}}
>
Modal
</Button>
</Space>
</div>
);
};
export default Demo;
API
| 속성 | 설명 | 타입 | 기본값 | 버전 |
|---|---|---|---|---|
| componentDisabled | antd 컴포넌트 disabled 설정 |
boolean | - | 4.21.0 |
| componentSize | antd 컴포넌트 크기 설정 | small | medium | large |
- | |
| csp | Content Security Policy config 설정 | { nonce: string } | - | |
| direction | 레이아웃 방향 설정. 데모 참고 | ltr | rtl |
ltr |
|
| getPopupContainer | 팝업 요소의 컨테이너 설정. 기본은 body에 div 요소를 만듦 |
(trigger?: HTMLElement) => HTMLElement | ShadowRoot |
() => document.body | |
| getTargetContainer | Affix, Anchor의 스크롤 대상 컨테이너 설정 | () => HTMLElement | Window | ShadowRoot |
() => window | 4.2.0 |
| iconPrefixCls | 아이콘 prefix className 설정 | string | anticon |
4.11.0 |
| locale | 언어 패키지 설정, antd/locale에서 패키지 확인 | object | - | |
| popupMatchSelectWidth | 드롭다운 메뉴와 select 입력이 같은 너비인지 결정. 기본은 min-width를 입력과 같게 설정. 값이 select 너비보다 작으면 무시. false는 가상 스크롤 비활성화 |
boolean | number | - | 5.5.0 |
| popupOverflow | Select류 컴포넌트 팝업 로직. 뷰포트에 표시하거나 윈도우 스크롤을 따르도록 설정 가능 | 'viewport' | 'scroll' | 'viewport' | 5.5.0 |
| prefixCls | prefix className 설정 | string | ant |
|
| renderEmpty | 컴포넌트의 빈 콘텐츠 설정. Empty 참고 | function(componentName: string): ReactNode | - | |
| theme | 테마 설정, 테마 커스터마이즈 참고 | Theme | - | 5.0.0 |
| variant | 데이터 입력 컴포넌트의 변형 설정 | outlined | filled | borderless |
- | 5.19.0 |
| virtual | false로 설정하면 가상 스크롤 비활성화 |
boolean | - | 4.3.0 |
| warning | 경고 레벨 설정, strict가 false면 폐기 정보를 단일 메시지로 모아 표시 |
{ strict: boolean } | - | 5.10.0 |
Button 자동 간격 설정, button={{ autoInsertSpace: boolean }}을 사용하세요 |
boolean | - | - | |
드롭다운 메뉴와 select 입력이 같은 너비인지, popupMatchSelectWidth를 사용하세요 |
boolean | - | - |
ConfigProvider.config() {#config}
Modal, Message, Notification 정적 설정을 지정해요. hooks에는 동작하지 않아요.
ConfigProvider.config({
// 5.13.0+
holderRender: (children) => (
<ConfigProvider
prefixCls="ant"
iconPrefixCls="anticon"
theme={{ token: { colorPrimary: 'red' } }}
>
{children}
</ConfigProvider>
),
});
ConfigProvider.useConfig() 5.3.0+ {#useconfig}
부모 Provider의 값을 가져와요. 예: DisabledContextProvider, SizeContextProvider.
const {
componentDisabled, // 5.3.0+
componentSize, // 5.3.0+
} = ConfigProvider.useConfig();
| 속성 | 설명 | 타입 | 기본값 | 버전 |
|---|---|---|---|---|
| componentDisabled | antd 컴포넌트 비활성 상태 | boolean | - | 5.3.0 |
| componentSize | antd 컴포넌트 크기 상태 | small | medium | large |
- | 5.3.0 |
컴포넌트 설정 (Component Config)
다음 설정 키들은 해당 컴포넌트의 공통 props 또는 전역 효과를 설정해요. 자세한 내용은 관련 API를 참고하세요.
affix: Affix (6.0.0부터 지원)alert: Alert (5.7.0부터 지원)anchor: Anchor (6.0.0부터 지원)app: App (6.3.0부터 지원)avatar: Avatar (5.7.0부터 지원)badge: Badge (5.7.0부터 지원)borderBeam: BorderBeam (6.4.0부터 지원)breadcrumb: Breadcrumb (5.7.0부터 지원)button: Button (5.6.0부터 지원)card: Card (5.14.0부터 지원)cardMeta: Card.Meta (6.0.0부터 지원)calendar: Calendar (6.0.0부터 지원)carousel: Carousel (5.7.0부터 지원)cascader: Cascader (5.13.0부터 지원)checkbox: Checkbox (6.0.0부터 지원)collapse: Collapse (5.15.0부터 지원)colorPicker: ColorPicker (6.3.0부터 지원)datePicker: DatePicker (5.7.0부터 지원)rangePicker: RangePicker (5.11.0부터 지원)descriptions: Descriptions (5.23.0부터 지원)divider: Divider (5.10.0부터 지원)drawer: Drawer (5.10.0부터 지원)dropdown: Dropdown (5.11.0부터 지원)empty: Empty (5.23.0부터 지원)flex: Flex (5.10.0부터 지원)floatButton: FloatButton (6.0.0부터 지원)floatButtonGroup: FloatButton.Group (5.16.0부터 지원)form: Form (4.8.0부터 지원)image: Image (5.14.0부터 지원)input: Input (4.2.0부터 지원)inputNumber: InputNumber (5.19.0부터 지원)otp: Input.OTP (6.0.0부터 지원)inputPassword: Input.Password (6.4.0부터 지원)inputSearch: Input.Search (6.4.0부터 지원)textArea: Input.TextArea (5.15.0부터 지원)layout: Layout (5.7.0부터 지원)list: List (5.7.0부터 지원)listy: Listy (6.6.0부터 지원)masonry: Masonry (6.0.0부터 지원)menu: Menu (5.15.0부터 지원)mentions: Mentions (5.13.0부터 지원)message: Message (5.7.0부터 지원)modal: Modal (5.10.0부터 지원)notification: Notification (5.14.0부터 지원)pagination: Pagination (6.0.0부터 지원)progress: Progress (5.7.0부터 지원)radio: Radio (6.0.0부터 지원)rate: Rate (5.7.0부터 지원)result: Result (6.0.0부터 지원)ribbon: Badge.Ribbon (6.0.0부터 지원)skeleton: Skeleton (6.0.0부터 지원)segmented: Segmented (6.0.0부터 지원)select: Select (5.13.0부터 지원)slider: Slider (5.23.0부터 지원)switch: Switch (6.0.0부터 지원)space: Space (5.6.0부터 지원)splitter: Splitter (5.21.0부터 지원)spin: Spin (5.20.0부터 지원)statistic: Statistic (6.0.0부터 지원)steps: Steps (5.10.0부터 지원)table: Table (6.2.0부터 지원)tabs: Tabs (5.14.0부터 지원)tag: Tag (5.14.0부터 지원)timeline: Timeline (6.0.0부터 지원)timePicker: TimePicker (5.13.0부터 지원)tour: Tour (5.14.0부터 지원)tooltip: Tooltip (6.1.0부터 지원)popover: Popover (5.23.0부터 지원)popconfirm: Popconfirm (5.23.0부터 지원)qrcode: QRCode (6.0.0부터 지원)transfer: Transfer (5.7.0부터 지원)tree: Tree (6.0.0부터 지원)treeSelect: TreeSelect (5.19.0부터 지원)typography: Typography (6.4.0부터 지원)upload: Upload (5.27.0부터 지원)watermark: Watermark (6.0.0부터 지원)wave: WaveConfig (5.8.0부터 지원)
WaveConfig
| 속성 | 설명 | 타입 | 기본값 | 버전 |
|---|---|---|---|---|
| disabled | 웨이브 효과 비활성화 여부 | boolean | false | |
| showEffect | 커스터마이즈된 웨이브 효과 | (node: HTMLElement, info: { className, token, component }) => void | - | |
| triggerType | 웨이브 효과를 트리거하는 이벤트 | click | pointerdown | pointerup | mousedown | mouseup |
click |
6.4.0 |
FAQ
새 언어를 어떻게 기여하나요? {#faq-add-locale}
<새 언어 추가> 참고.
날짜 관련 컴포넌트 locale이 동작하지 않아요? {#faq-locale-not-work}
FAQ 날짜 관련 컴포넌트 locale이 동작하지 않아요? 참고
getPopupContainer를 설정하면 Modal이 오류를 던져요? {#faq-get-popup-container}
관련 이슈: https://github.com/ant-design/ant-design/issues/19974
getPopupContainer를 전역으로 parentNode에 설정하면 Modal은 triggerNode가 없어서 triggerNode is undefined 오류를 던져요. 아래 수정을 시도해 볼 수 있어요.
<ConfigProvider
- getPopupContainer={triggerNode => triggerNode.parentNode}
+ getPopupContainer={node => {
+ if (node) {
+ return node.parentNode;
+ }
+ return document.body;
+ }}
>
<App />
</ConfigProvider>
왜 ConfigProvider props(prefixCls, theme 등)가 message.info, notification.open, Modal.confirm 안의 ReactNode에 영향을 주지 못하나요? {#faq-message-inherit}
antd는 message 메서드를 호출할 때 ReactDOM.render로 동적으로 React 인스턴스를 만들어요. 그 context는 원본 코드가 위치한 context와 달라요. message/notification/Modal에서 온 메서드는 5.x에서 폐기(deprecated)되었으니 useMessage, useNotification, useModal을 권장해요.
Vite 프로덕션 모드에서 locale이 동작하지 않아요? {#faq-vite-locale-not-work}
관련 이슈: #39045
Vite 프로덕션 모드에서 cjs 파일의 default export는 enUS.default처럼 사용해야 해요. 그래서 dev와 production이 같은 동작을 하게 하려면 import enUS from 'antd/es/locale/en_US'처럼 es/ 디렉토리에서 locale을 직접 import할 수 있어요.
prefixCls 우선순위(앞선 것이 뒤에 덮임) {#faq-prefixcls-priority}
ConfigProvider.config({ prefixCls: 'prefix-1' })ConfigProvider.config({ holderRender: (children) => <ConfigProvider prefixCls="prefix-2">{children}</ConfigProvider> })message.config({ prefixCls: 'prefix-3' })
더 알아보기 (Learn more)
- 테마 커스터마이즈 — ConfigProvider 테마 설정
- i18n — 언어 설정
- Common props — 공통 props
- Ant Design 시작하기 — 프로젝트 설정