feat: added rtl direction to all of ant-design components (#19380)

* rtl demo change en-us description

* change bundlesize css limit

* RTL: modal component (exclude confirm)

* RTL: table component

* RTL: pagination component

* cleanup rtl demo

* fix pagination.tsx compile error

* RTL: button and button-group

* RTL: Steps component

* fix rtl demo style

* fix input suffix icon alignment

* fix select component arrow issue

* RTL: form component

* add pagination rtl test

* fix test lint error

* RTL: rate component

* RTL: radio and radio group components

* RTL: tree-select component

* some fixes to RTL components

* RTL: badge component

* fix rtl issue in inline form

* fix input component rtl padding issue

* fix switch component text rtl issue

* fix table grouped header text-align

* add rtl support to whole demo with RTL button

* Update rtl demo responsive

* RTL: page-header component

* RTL: typography component

* RTL: Dropdown (Partial)

* update config-provider doc

* RTL: input component

* RTL: select component

* RTL: switch component

* RTL: tree component

* fix rtl demo lint

* rtl demo change en-us description

* RTL: modal component (exclude confirm)

* RTL: table component

* RTL: pagination component

* cleanup rtl demo

* RTL: button and button-group

* RTL: Steps component

* fix rtl demo style

* fix input suffix icon alignment

* RTL: form component

* RTL: rate component

* RTL: radio and radio group components

* RTL: tree-select component

* RTL: badge component

* fix rtl issue in inline form

* fix input component rtl padding issue

* add rtl support to whole demo with RTL button

* fix lost changes after rebase

* fix lint errors

* RTL: Transfer Component

* RTL: upload component

* RTL: update avatar demo

* RTL: comment component

* RTL: collapse component

* RTL: carousel component

* update snapshots

* RTL: Card component

* RTL: descriptions component

* RTL:  Empty component

* RTL: list component

* RTL: slider component

* slider component import/order

* add shared rtlTest

* RTL: Statistic component

* RTL: tooltip components

* RTL: popover component

* RTL: timeline component

* RTL: tag component

* RTL: alert component

* RTL: drawer component

* RTL: Tab component

* change direction definition

* RTL: progress component

* input.tsx, remove duplicate after rebase

* fix demo.less after rebase

* fix ant-row-rtl after rebase

* fix upload issues in rtl

* badge rtl demo margin fix

* fix: tabs with icon margin

* fix: radio-wrapper margin

* fix: table component after rebase

* fix: centered modal text-align

* update slider snapshot

* RTL: popconfirm component

* RTL: back-top component

* RTL: spin component

* RTL: result component

* RTL: skeleton component

* RTL: menu component

* RTL: time-picker component

* RTL: calendar component

* RTL: date-picker component

* RTL: home page

* update snapshots

* test: add auto-complete rtl test

* test: add avatar component rtl tests

* test: add badge component rtl tests

* test: add breadcrumb component rtl tests

* test: add button components rtl tests

* test: add card component rtl tests

* test: add carousel component rtl tests

* test: add cascader component rtl tests

* test: add checkbox component rtl tests

* test: add collapse component rtl tests

* test: add comment component rtl tests

* test: add dropdown component rtl tests

* test: add empty component rtl tests

* test: add form component rtl tests

* test: add grid component rtl tests

* test: add input component rtl tests

* test: add search component rtl tests

* test: add input-number component rtl tests

* test: add layout component rtl tests

* test: add list component rtl tests

* test: add mentions component rtl tests

* test: add modal component rtl tests

* test: add page-header component rtl tests

* test: add pagination component rtl tests

* test: add radio component rtl tests

* test: add rate component rtl tests

* test: add select component rtl tests

* test: add slider component rtl tests

* test: add steps component rtl tests

* test: add switch component rtl tests

* test: add table component rtl tests

* test: add transfer component rtl tests

* test: add tree component rtl tests

* test: add tree-select component rtl tests

* test: add typography component rtl tests

* test: add upload component rtl tests

* test: add affix component rtl tests

* update calendar tests

* increase css file maxSize

* update snapshots

* remove workflows to allow push

* remove duplicate reverse prop from slider

* fix: remove table demo from config-provider

* fix: remove table demo from config-provider

* fix lint error

* Added direction property to ConfigProvider

* cascader rtl tests added

* update config-provider doc

* RTL: grid system

* RTL: input component

* RTL: switch component

* fix rtl demo lint

* RTL: modal component (exclude confirm)

* RTL: table component

* RTL: pagination component

* cleanup rtl demo

* fix pagination.tsx compile error

* RTL: button and button-group

* RTL: Steps component

* fix rtl demo style

* RTL: form component

* add pagination rtl test

* RTL: rate component

* RTL: radio and radio group components

* RTL: tree-select component

* RTL: badge component

* fix rtl issue in inline form

* fix input component rtl padding issue

* add rtl support to whole demo with RTL button

* RTL: input component

* RTL: select component

* RTL: switch component

* RTL: tree component

* fix rtl demo lint

* rtl demo change en-us description

* RTL: modal component (exclude confirm)

* RTL: table component

* RTL: pagination component

* cleanup rtl demo

* RTL: button and button-group

* RTL: Steps component

* fix rtl demo style

* fix input suffix icon alignment

* RTL: form component

* RTL: rate component

* RTL: radio and radio group components

* RTL: tree-select component

* RTL: badge component

* fix rtl issue in inline form

* fix input component rtl padding issue

* add rtl support to whole demo with RTL button

* input.tsx, remove duplicate after rebase

* fix ant-row-rtl after rebase

* update snapshots

* test: add cascader component rtl tests

* test: add pagination component rtl tests

* update calendar tests

* update snapshots

* fix: remove table demo from config-provider

* fix: remove table demo from config-provider

* fix lint error

* update direction.md icons

* dropdown and cascader default placement in rtl

* update snapshots

* fix lint errors

* remove duplicate import

* update snapshots

* update snapshot

* update calendar snapshot

* update snapshots

* integrate with new rc-picker

* update snapshots

* fix lint errors

* update snapshot

* update snapshots

* update snapshots

* update snapshots :|

* update snapshots

* fix compile error.

* fix typo after rebase

* update snapshots

* remove workflows to allow push

* update snapshots

* update snapshots

* fix dist error

* front-page css fix

* update snapshots

* fix lint and test issues

* restore cascader index.less

* update snapshots

* fix logo in rtl and demo controls

* ci errors

* resolve steps/index.tsx conflicts

* tooltip family demo remove inline style

* resolve table/Table.tsx conflicts

* resolve modal/Modal.tsx conflicts

* resolve cascader/index.tsx conflicts

* add workflows from upstream

* update snapshots

* revert logo to default

* fix codebox demo direction of placements

* resolve tooltip overlayClassName conflicts

* update snapshots

* update popover test

* fix: cascader miss popupClassName

* fix: fix select missing dropdownClassName

* chore: Update snapshot

* chore: Adjust menu use rtl logic

* docs: Update demo line color

Co-authored-by: 二货机器人 <smith3816@gmail.com>
This commit is contained in:
Saeed Rahimi 2020-01-02 14:40:16 +03:30 committed by 二货机器人
parent 37ca4f7884
commit 676de29eb4
262 changed files with 7560 additions and 168 deletions

View File

@ -4,6 +4,7 @@ import Affix from '..';
import { getObserverEntities } from '../utils';
import Button from '../../button';
import { spyElementPrototype } from '../../__tests__/util/domHook';
import rtlTest from '../../../tests/shared/rtlTest';
const events = {};
@ -40,6 +41,8 @@ class AffixMounter extends React.Component {
}
describe('Affix Render', () => {
rtlTest(Affix);
let wrapper;
let domMock;

View File

@ -0,0 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Affix Render rtl render component should be rendered correctly in RTL direction 1`] = `
<div>
<div
class=""
/>
</div>
`;

View File

@ -21,3 +21,17 @@ exports[`Alert ErrorBoundary 1`] = `
</span>
</div>
`;
exports[`Alert rtl render component should be rendered correctly in RTL direction 1`] = `
<div
class="ant-alert ant-alert-info ant-alert-no-icon ant-alert-rtl"
data-show="true"
>
<span
class="ant-alert-message"
/>
<span
class="ant-alert-description"
/>
</div>
`;

View File

@ -1,10 +1,13 @@
import React from 'react';
import { mount } from 'enzyme';
import Alert from '..';
import rtlTest from '../../../tests/shared/rtlTest';
const { ErrorBoundary } = Alert;
describe('Alert', () => {
rtlTest(Alert);
beforeAll(() => {
jest.useFakeTimers();
});

View File

@ -95,7 +95,7 @@ export default class Alert extends React.Component<AlertProps, AlertState> {
(this.props.afterClose || noop)();
};
renderAlert = ({ getPrefixCls }: ConfigConsumerProps) => {
renderAlert = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
const {
description,
prefixCls: customizePrefixCls,
@ -133,6 +133,7 @@ export default class Alert extends React.Component<AlertProps, AlertState> {
[`${prefixCls}-no-icon`]: !showIcon,
[`${prefixCls}-banner`]: !!banner,
[`${prefixCls}-closable`]: closable,
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
);

View File

@ -11,18 +11,33 @@
word-wrap: break-word;
border-radius: @border-radius-base;
&-rtl {
padding: 8px 37px 8px 15px;
direction: rtl;
}
&&-no-icon {
padding: 8px 15px;
}
&&-closable {
padding-right: 30px;
.@{alert-prefix-cls}-rtl& {
padding-right: 15px;
padding-left: 30px;
}
}
&-icon {
position: absolute;
top: 8px + @font-size-base * @line-height-base / 2 - @font-size-base / 2;
left: 16px;
.@{alert-prefix-cls}-rtl & {
right: 16px;
left: auto;
}
}
&-description {
@ -81,6 +96,11 @@
outline: none;
cursor: pointer;
.@{alert-prefix-cls}-rtl & {
right: auto;
left: 16px;
}
.@{iconfont-css-prefix}-close {
color: @alert-close-color;
transition: color 0.3s;
@ -104,6 +124,10 @@
color: @alert-text-color;
line-height: @line-height-base;
border-radius: @border-radius-base;
.@{alert-prefix-cls}-rtl& {
padding: 15px 64px 15px 15px;
}
}
&-with-description&-no-icon {
@ -115,6 +139,11 @@
top: 16px;
left: 24px;
font-size: 24px;
.@{alert-prefix-cls}-rtl& {
right: 24px;
left: auto;
}
}
&-with-description &-close-icon {
@ -123,6 +152,11 @@
right: 16px;
font-size: @font-size-base;
cursor: pointer;
.@{alert-prefix-cls}-rtl& {
right: auto;
left: 16px;
}
}
&-with-description &-message {

View File

@ -0,0 +1,30 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AutoComplete with Custom Input Element Render rtl render component should be rendered correctly in RTL direction 1`] = `
<div
class="ant-select ant-select-auto-complete ant-select-rtl ant-select-single ant-select-show-search"
>
<div
class="ant-select-selector"
>
<span
class="ant-select-selection-search"
>
<input
aria-activedescendant="undefined_list_0"
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
role="combobox"
value=""
/>
</span>
<span
class="ant-select-selection-placeholder"
/>
</div>
</div>
`;

View File

@ -2,9 +2,11 @@ import React from 'react';
import { mount } from 'enzyme';
import AutoComplete from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
describe('AutoComplete with Custom Input Element Render', () => {
mountTest(AutoComplete);
rtlTest(AutoComplete);
it('AutoComplete with custom Input render perfectly', () => {
const wrapper = mount(

View File

@ -2,9 +2,11 @@ import React from 'react';
import { mount } from 'enzyme';
import Avatar from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
describe('Avatar Render', () => {
mountTest(Avatar);
rtlTest(Avatar);
let originOffsetWidth;
beforeAll(() => {

View File

@ -0,0 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Avatar Render rtl render component should be rendered correctly in RTL direction 1`] = `
<span
class="ant-avatar ant-avatar-circle"
>
<span
class="ant-avatar-string"
style="opacity:0"
/>
</span>
`;

View File

@ -3,7 +3,7 @@
exports[`renders ./components/avatar/demo/badge.md correctly 1`] = `
<div>
<span
style="margin-right:24px"
class="avatar-item"
>
<span
class="ant-badge"

View File

@ -19,7 +19,7 @@ import { UserOutlined } from '@ant-design/icons';
ReactDOM.render(
<div>
<span style={{ marginRight: 24 }}>
<span className="avatar-item">
<Badge count={1}>
<Avatar shape="square" icon={<UserOutlined />} />
</Badge>
@ -33,3 +33,15 @@ ReactDOM.render(
mountNode,
);
```
```css
/* tile uploaded pictures */
.avatar-item {
margin-right: 24px;
}
[class*='-col-rtl'] .avatar-item {
margin-right: 0;
margin-left: 24px;
}
```

View File

@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`BackTop rtl render component should be rendered correctly in RTL direction 1`] = `null`;

View File

@ -2,10 +2,12 @@ import React from 'react';
import { mount } from 'enzyme';
import { sleep } from '../../../tests/utils';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import BackTop from '..';
describe('BackTop', () => {
mountTest(BackTop);
rtlTest(BackTop);
it('should scroll to top after click it', async () => {
const wrapper = mount(<BackTop visibilityHeight={-1} />);

View File

@ -65,10 +65,12 @@ export default class BackTop extends React.Component<BackTopProps, any> {
});
};
renderBackTop = ({ getPrefixCls }: ConfigConsumerProps) => {
renderBackTop = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
const { prefixCls: customizePrefixCls, className = '', children } = this.props;
const prefixCls = getPrefixCls('back-top', customizePrefixCls);
const classString = classNames(prefixCls, className);
const classString = classNames(prefixCls, className, {
[`${prefixCls}-rtl`]: direction === 'rtl',
});
const defaultElement = (
<div className={`${prefixCls}-content`}>

View File

@ -14,6 +14,11 @@
height: 40px;
cursor: pointer;
&-rtl {
right: auto;
left: 100px;
}
&-content {
width: 40px;
height: 40px;

View File

@ -724,6 +724,12 @@ exports[`Badge render correct with negative number 1`] = `
</div>
`;
exports[`Badge rtl render component should be rendered correctly in RTL direction 1`] = `
<span
class="ant-badge ant-badge-not-a-wrapper ant-badge-rtl"
/>
`;
exports[`Badge should be compatible with borderColor style 1`] = `
<span
class="ant-badge ant-badge-not-a-wrapper"

View File

@ -3,9 +3,11 @@ import { mount, render } from 'enzyme';
import Badge from '../index';
import Tooltip from '../../tooltip';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
describe('Badge', () => {
mountTest(Badge);
rtlTest(Badge);
beforeEach(() => {
jest.useFakeTimers();

View File

@ -37,6 +37,10 @@ ReactDOM.render(
.ant-badge:not(.ant-badge-not-a-wrapper) {
margin-right: 20px;
}
.ant-badge.ant-badge-rtl:not(.ant-badge-not-a-wrapper) {
margin-right: 0;
margin-left: 20px;
}
.head-example {
width: 42px;
height: 42px;

View File

@ -40,6 +40,10 @@ ReactDOM.render(
.ant-badge-not-a-wrapper:not(.ant-badge-status) {
margin-right: 8px;
}
.ant-badge.ant-badge-rtl:not(.ant-badge-not-a-wrapper) {
margin-right: 0;
margin-left: 20px;
}
[data-theme="dark"] .site-badge-count-4 .ant-badge-count {
background-color: #141414;
box-shadow: 0 0 0 1px #434343 inset;

View File

@ -31,6 +31,12 @@ ReactDOM.render(
.ant-badge:not(.ant-badge-not-a-wrapper) {
margin-right: 20px;
}
.ant-badge.ant-badge-rtl:not(.ant-badge-not-a-wrapper) {
margin-right: 0;
margin-left: 20px;
}
.head-example {
width: 42px;
height: 42px;

View File

@ -74,11 +74,12 @@ export default class Badge extends React.Component<BadgeProps, any> {
: style;
}
getBadgeClassName(prefixCls: string) {
getBadgeClassName(prefixCls: string, direction: string = 'ltr') {
const { className, children } = this.props;
return classNames(className, prefixCls, {
[`${prefixCls}-status`]: this.hasStatus(),
[`${prefixCls}-not-a-wrapper`]: !children,
[`${prefixCls}-rtl`]: direction === 'rtl',
}) as string;
}
@ -156,7 +157,7 @@ export default class Badge extends React.Component<BadgeProps, any> {
);
}
renderBadge = ({ getPrefixCls }: ConfigConsumerProps) => {
renderBadge = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
const {
prefixCls: customizePrefixCls,
scrollNumberPrefixCls: customizeScrollNumberPrefixCls,
@ -200,7 +201,7 @@ export default class Badge extends React.Component<BadgeProps, any> {
return (
<span
{...omit(restProps, omitArr)}
className={this.getBadgeClassName(prefixCls)}
className={this.getBadgeClassName(prefixCls, direction)}
style={styleWithOffset}
>
<span className={statusCls} style={statusStyle} />
@ -212,7 +213,7 @@ export default class Badge extends React.Component<BadgeProps, any> {
}
return (
<span {...omit(restProps, omitArr)} className={this.getBadgeClassName(prefixCls)}>
<span {...omit(restProps, omitArr)} className={this.getBadgeClassName(prefixCls, direction)}>
{children}
<Animate
component=""

View File

@ -12,6 +12,10 @@
color: unset;
line-height: 1;
&-rtl {
direction: rtl;
}
&-count {
z-index: @zindex-badge;
min-width: @badge-height;
@ -53,6 +57,20 @@
right: 0;
transform: translate(50%, -50%);
transform-origin: 100% 0%;
.@{badge-prefix-cls}-rtl & {
right: auto;
left: 0;
transform: translate(-50%, -50%);
transform-origin: 0% 0%;
}
}
.@{badge-prefix-cls}-rtl& .@{number-prefix-cls}-custom-component {
right: auto;
left: 0;
transform: translate(-50%, -50%);
transform-origin: 0% 0%;
}
&-status {
@ -111,6 +129,11 @@
margin-left: 8px;
color: @text-color;
font-size: @font-size-base;
.@{badge-prefix-cls}-rtl & {
margin-right: 8px;
margin-left: 0;
}
}
}
@ -118,11 +141,19 @@
&-zoom-enter {
animation: antZoomBadgeIn 0.3s @ease-out-back;
animation-fill-mode: both;
.@{badge-prefix-cls}-rtl & {
animation-name: antZoomBadgeInRtl;
}
}
&-zoom-leave {
animation: antZoomBadgeOut 0.3s @ease-in-back;
animation-fill-mode: both;
.@{badge-prefix-cls}-rtl & {
animation-name: antZoomBadgeOutRtl;
}
}
&-not-a-wrapper {
@ -189,3 +220,23 @@
opacity: 0;
}
}
@keyframes antZoomBadgeInRtl {
0% {
transform: scale(0) translate(-50%, -50%);
opacity: 0;
}
100% {
transform: scale(1) translate(-50%, -50%);
}
}
@keyframes antZoomBadgeOutRtl {
0% {
transform: scale(1) translate(-50%, -50%);
}
100% {
transform: scale(0) translate(-50%, -50%);
opacity: 0;
}
}

View File

@ -2,9 +2,11 @@ import React from 'react';
import { mount, render } from 'enzyme';
import Breadcrumb from '../index';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
describe('Breadcrumb', () => {
mountTest(Breadcrumb);
rtlTest(Breadcrumb);
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

View File

@ -32,6 +32,12 @@ exports[`Breadcrumb filter React.Fragment 1`] = `
</div>
`;
exports[`Breadcrumb rtl render component should be rendered correctly in RTL direction 1`] = `
<div
class="ant-breadcrumb"
/>
`;
exports[`Breadcrumb should allow Breadcrumb.Item is null or undefined 1`] = `
<div
class="ant-breadcrumb"

View File

@ -215,6 +215,45 @@ exports[`Button renders correctly 1`] = `
</button>
`;
exports[`Button rtl render component should be rendered correctly in RTL direction 1`] = `
<button
class="ant-btn ant-btn-rtl"
type="button"
/>
`;
exports[`Button rtl render component should be rendered correctly in RTL direction 2`] = `
<button
class="ant-btn ant-btn-lg ant-btn-rtl"
type="button"
/>
`;
exports[`Button rtl render component should be rendered correctly in RTL direction 3`] = `
<button
class="ant-btn ant-btn-sm ant-btn-rtl"
type="button"
/>
`;
exports[`Button rtl render component should be rendered correctly in RTL direction 4`] = `
<div
class="ant-btn-group ant-btn-group-rtl"
/>
`;
exports[`Button rtl render component should be rendered correctly in RTL direction 5`] = `
<div
class="ant-btn-group ant-btn-group-lg ant-btn-group-rtl"
/>
`;
exports[`Button rtl render component should be rendered correctly in RTL direction 6`] = `
<div
class="ant-btn-group ant-btn-group-sm ant-btn-group-rtl"
/>
`;
exports[`Button should has click wave effect 1`] = `
<button
ant-click-animating-without-extra-node="true"

View File

@ -4,6 +4,7 @@ import renderer from 'react-test-renderer';
import { SearchOutlined } from '@ant-design/icons';
import Button from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { sleep } from '../../../tests/utils';
describe('Button', () => {
@ -14,6 +15,13 @@ describe('Button', () => {
mountTest(() => <Button.Group size="large" />);
mountTest(() => <Button.Group size="small" />);
rtlTest(Button);
rtlTest(() => <Button size="large" />);
rtlTest(() => <Button size="small" />);
rtlTest(Button.Group);
rtlTest(() => <Button.Group size="large" />);
rtlTest(() => <Button.Group size="small" />);
it('renders correctly', () => {
const wrapper = render(<Button>Follow</Button>);
expect(wrapper).toMatchSnapshot();

View File

@ -12,7 +12,7 @@ export interface ButtonGroupProps {
const ButtonGroup: React.SFC<ButtonGroupProps> = props => (
<ConfigConsumer>
{({ getPrefixCls }: ConfigConsumerProps) => {
{({ getPrefixCls, direction }: ConfigConsumerProps) => {
const { prefixCls: customizePrefixCls, size, className, ...others } = props;
const prefixCls = getPrefixCls('btn-group', customizePrefixCls);
@ -34,6 +34,7 @@ const ButtonGroup: React.SFC<ButtonGroupProps> = props => (
prefixCls,
{
[`${prefixCls}-${sizeCls}`]: sizeCls,
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
);

View File

@ -201,7 +201,7 @@ class Button extends React.Component<ButtonProps, ButtonState> {
return React.Children.count(children) === 1 && !icon && type !== 'link';
}
renderButton = ({ getPrefixCls, autoInsertSpaceInButton }: ConfigConsumerProps) => {
renderButton = ({ getPrefixCls, autoInsertSpaceInButton, direction }: ConfigConsumerProps) => {
const {
prefixCls: customizePrefixCls,
type,
@ -252,6 +252,7 @@ class Button extends React.Component<ButtonProps, ButtonState> {
[`${prefixCls}-two-chinese-chars`]: hasTwoCNChar && autoInsertSpace,
[`${prefixCls}-block`]: block,
[`${prefixCls}-dangerous`]: !!danger,
[`${prefixCls}-rtl`]: direction === 'rtl',
});
const iconNode = loading ? <LoadingOutlined /> : icon || null;

View File

@ -24,6 +24,9 @@
.btn;
.btn-default;
&-rtl {
direction: rtl;
}
// Make sure that the target of Button's click event always be `button`
// Ref: https://github.com/ant-design/ant-design/issues/7034
> i,
@ -48,6 +51,7 @@
.@{btn-prefix-cls}-group &:first-child {
&:not(:last-child) {
border-right-color: @btn-group-border;
&[disabled] {
border-right-color: @btn-default-border;
}
@ -57,8 +61,18 @@
.@{btn-prefix-cls}-group &:last-child:not(:first-child),
.@{btn-prefix-cls}-group & + & {
border-left-color: @btn-group-border;
.@{btn-prefix-cls}-group-rtl& {
border-right-color: @btn-group-border;
border-left-color: @btn-default-border;
}
&[disabled] {
border-left-color: @btn-default-border;
.@{btn-prefix-cls}-group-rtl& {
border-right-color: @btn-default-border;
border-left-color: @btn-group-border;
}
}
}
}
@ -155,15 +169,37 @@
&&-loading:not(&-circle):not(&-circle-outline):not(&-icon-only) {
padding-left: 29px;
.@{btn-prefix-cls}-rtl& {
padding-right: 29px;
padding-left: @padding-md - 1px;
}
.@{iconfont-css-prefix}:not(:last-child) {
margin-left: -14px;
.@{btn-prefix-cls}-rtl& {
margin-right: -14px;
margin-left: 0;
}
}
}
&-sm&-loading:not(&-circle):not(&-circle-outline):not(&-icon-only) {
padding-left: 24px;
.@{btn-prefix-cls}-rtl& {
padding-right: 24px;
padding-left: @padding-xs - 1px;
}
.@{iconfont-css-prefix} {
margin-left: -17px;
.@{btn-prefix-cls}-rtl& {
margin-right: -17px;
margin-left: 0;
}
}
}
@ -181,6 +217,11 @@
> .@{iconfont-css-prefix} + span,
> span + .@{iconfont-css-prefix} {
margin-left: 8px;
.@{btn-prefix-cls}-rtl& {
margin-right: 8px;
margin-left: 0;
}
}
&-background-ghost {

View File

@ -400,6 +400,10 @@
& + .@{btnClassName},
& + & {
margin-left: -1px;
.@{btnClassName}-rtl& {
margin-right: -1px;
margin-left: auto;
}
}
.@{btnClassName}-primary + .@{btnClassName}:not(.@{btnClassName}-primary):not([disabled]) {
border-left-color: transparent;
@ -407,6 +411,9 @@
.@{btnClassName} {
border-radius: 0;
}
&.@{btnClassName}-group-rtl {
direction: rtl;
}
> .@{btnClassName}:first-child,
> span:first-child > .@{btnClassName} {
margin-left: 0;
@ -421,11 +428,23 @@
> span:first-child:not(:last-child) > .@{btnClassName} {
border-top-left-radius: @btn-border-radius-base;
border-bottom-left-radius: @btn-border-radius-base;
.@{btnClassName}-group-rtl& {
border-top-left-radius: 0;
border-top-right-radius: @btn-border-radius-base;
border-bottom-right-radius: @btn-border-radius-base;
border-bottom-left-radius: 0;
}
}
> .@{btnClassName}:last-child:not(:first-child),
> span:last-child:not(:first-child) > .@{btnClassName} {
border-top-right-radius: @btn-border-radius-base;
border-bottom-right-radius: @btn-border-radius-base;
.@{btnClassName}-group-rtl& {
border-top-left-radius: @btn-border-radius-base;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-bottom-left-radius: @btn-border-radius-base;
}
}
&-sm {
> .@{btnClassName}:only-child {
@ -438,11 +457,23 @@
> span:first-child:not(:last-child) > .@{btnClassName} {
border-top-left-radius: @btn-border-radius-sm;
border-bottom-left-radius: @btn-border-radius-sm;
.@{btnClassName}-group-rtl& {
border-top-left-radius: 0;
border-top-right-radius: @btn-border-radius-sm;
border-bottom-right-radius: @btn-border-radius-sm;
border-bottom-left-radius: 0;
}
}
> .@{btnClassName}:last-child:not(:first-child),
> span:last-child:not(:first-child) > .@{btnClassName} {
border-top-right-radius: @btn-border-radius-sm;
border-bottom-right-radius: @btn-border-radius-sm;
.@{btnClassName}-group-rtl& {
border-top-left-radius: @btn-border-radius-sm;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-bottom-left-radius: @btn-border-radius-sm;
}
}
}
& > & {

View File

@ -951,3 +951,953 @@ exports[`Calendar Calendar should support locale 1`] = `
</div>
</div>
`;
exports[`Calendar rtl render component should be rendered correctly in RTL direction 1`] = `
<div
class="ant-picker-calendar ant-picker-calendar-full"
>
<div
class="ant-picker-calendar-header"
>
<div
class="ant-select ant-picker-calendar-year-select ant-select-rtl ant-select-single ant-select-show-arrow"
>
<div
class="ant-select-selector"
>
<span
class="ant-select-selection-search"
>
<input
aria-activedescendant="undefined_list_0"
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
readonly=""
role="combobox"
style="opacity:0"
value=""
/>
</span>
<span
class="ant-select-selection-item"
>
2000
</span>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</div>
<div
class="ant-select ant-picker-calendar-month-select ant-select-rtl ant-select-single ant-select-show-arrow"
>
<div
class="ant-select-selector"
>
<span
class="ant-select-selection-search"
>
<input
aria-activedescendant="undefined_list_0"
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
readonly=""
role="combobox"
style="opacity:0"
value=""
/>
</span>
<span
class="ant-select-selection-item"
>
Sep
</span>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</div>
<div
class="ant-radio-group ant-radio-group-outline ant-radio-group-default ant-radio-group-rtl ant-picker-calendar-mode-switch"
>
<label
class="ant-radio-button-wrapper ant-radio-button-wrapper-checked ant-radio-button-wrapper-rtl"
>
<span
class="ant-radio-button ant-radio-button-checked"
>
<input
checked=""
class="ant-radio-button-input"
type="radio"
value="month"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
Month
</span>
</label>
<label
class="ant-radio-button-wrapper ant-radio-button-wrapper-rtl"
>
<span
class="ant-radio-button"
>
<input
class="ant-radio-button-input"
type="radio"
value="year"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
Year
</span>
</label>
</div>
</div>
<div
class="ant-picker-panel"
tabindex="0"
>
<div
class="ant-picker-date-panel"
>
<div
class="ant-picker-body"
>
<table
class="ant-picker-content"
>
<thead>
<tr>
<th>
Su
</th>
<th>
Mo
</th>
<th>
Tu
</th>
<th>
We
</th>
<th>
Th
</th>
<th>
Fr
</th>
<th>
Sa
</th>
</tr>
</thead>
<tbody>
<tr
class=""
>
<td
class="ant-picker-cell"
title="2000-08-27"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
27
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell"
title="2000-08-28"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
28
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell"
title="2000-08-29"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
29
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell"
title="2000-08-30"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
30
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell"
title="2000-08-31"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
31
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-01"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
01
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-02"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
02
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
</tr>
<tr
class=""
>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-03"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
03
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-04"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
04
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-05"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
05
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-06"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
06
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-07"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
07
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-08"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
08
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-09"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
09
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
</tr>
<tr
class=""
>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-10"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
10
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-11"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
11
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-12"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
12
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-13"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
13
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-14"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
14
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-15"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
15
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-16"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
16
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
</tr>
<tr
class=""
>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-17"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
17
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-18"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
18
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-19"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
19
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-20"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
20
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-21"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
21
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-22"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
22
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-23"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
23
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
</tr>
<tr
class=""
>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-24"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
24
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-25"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
25
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-26"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
26
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-27"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
27
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view ant-picker-cell-today ant-picker-cell-selected"
title="2000-09-28"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date ant-picker-calendar-date-today"
>
<div
class="ant-picker-calendar-date-value"
>
28
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-29"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
29
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell ant-picker-cell-in-view"
title="2000-09-30"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
30
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
</tr>
<tr
class=""
>
<td
class="ant-picker-cell"
title="2000-10-01"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
01
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell"
title="2000-10-02"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
02
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell"
title="2000-10-03"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
03
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell"
title="2000-10-04"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
04
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell"
title="2000-10-05"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
05
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell"
title="2000-10-06"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
06
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
<td
class="ant-picker-cell"
title="2000-10-07"
>
<div
class="ant-picker-cell-inner ant-picker-calendar-date"
>
<div
class="ant-picker-calendar-date-value"
>
07
</div>
<div
class="ant-picker-calendar-date-content"
/>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
`;

View File

@ -9,9 +9,11 @@ import Select from '../../select';
import Group from '../../radio/group';
import Button from '../../radio/radioButton';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
describe('Calendar', () => {
mountTest(Calendar);
rtlTest(Calendar, true);
function openSelect(wrapper, className) {
wrapper

View File

@ -1,5 +1,15 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Card rtl render component should be rendered correctly in RTL direction 1`] = `
<div
class="ant-card ant-card-bordered ant-card-rtl"
>
<div
class="ant-card-body"
/>
</div>
`;
exports[`Card should still have padding when card which set padding to 0 is loading 1`] = `
<div
class="ant-card ant-card-loading ant-card-bordered"

View File

@ -3,9 +3,11 @@ import { mount } from 'enzyme';
import Card from '../index';
import Button from '../../button/index';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
describe('Card', () => {
mountTest(Card);
rtlTest(Card);
beforeAll(() => {
jest.useFakeTimers();

View File

@ -76,7 +76,7 @@ export default class Card extends React.Component<CardProps, {}> {
return containGrid;
}
renderCard = ({ getPrefixCls }: ConfigConsumerProps) => {
renderCard = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
const {
prefixCls: customizePrefixCls,
className,
@ -108,6 +108,7 @@ export default class Card extends React.Component<CardProps, {}> {
[`${prefixCls}-contain-tabs`]: tabList && tabList.length,
[`${prefixCls}-${size}`]: size !== 'default',
[`${prefixCls}-type-${type}`]: !!type,
[`${prefixCls}-rtl`]: direction === 'rtl',
});
const loadingBlockStyle =

View File

@ -17,6 +17,10 @@
border-radius: @card-radius;
transition: all 0.3s;
&-rtl {
direction: rtl;
}
&-hoverable {
cursor: pointer;
&:hover {
@ -76,6 +80,11 @@
color: @text-color;
font-weight: normal;
font-size: @font-size-base;
.@{card-prefix-cls}-rtl & {
margin-right: auto;
margin-left: 0;
}
}
&-body {
@ -98,6 +107,11 @@
1px 1px 0 0 @border-color-split, 1px 0 0 0 @border-color-split inset,
0 1px 0 0 @border-color-split inset;
transition: all 0.3s;
.@{card-prefix-cls}-rtl & {
float: right;
}
&-hoverable {
&:hover {
position: relative;
@ -142,6 +156,10 @@
color: @text-color-secondary;
text-align: center;
.@{card-prefix-cls}-rtl & {
float: right;
}
> span {
position: relative;
display: block;
@ -205,6 +223,12 @@
&-avatar {
float: left;
padding-right: 16px;
.@{card-prefix-cls}-rtl & {
float: right;
padding-right: 0;
padding-left: 16px;
}
}
&-detail {

View File

@ -1,5 +1,24 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Carousel rtl render component should be rendered correctly in RTL direction 1`] = `
<div
class="ant-carousel ant-carousel-rtl"
>
<div
class="slick-slider slick-initialized"
>
<div
class="slick-list"
>
<div
class="slick-track"
style="width:0%;left:NaN%"
/>
</div>
</div>
</div>
`;
exports[`Carousel should works for dotPosition bottom 1`] = `
<div
class="ant-carousel"

View File

@ -2,9 +2,11 @@ import React from 'react';
import { mount } from 'enzyme';
import Carousel from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
describe('Carousel', () => {
mountTest(Carousel);
rtlTest(Carousel);
beforeEach(() => {
jest.useFakeTimers();

View File

@ -1,6 +1,7 @@
import * as React from 'react';
import debounce from 'lodash/debounce';
import { Settings } from '@ant-design/react-slick';
import classNames from 'classnames';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
// Use require over import (will be lifted up)
@ -92,7 +93,7 @@ export default class Carousel extends React.Component<CarouselProps, {}> {
this.slick.slickGoTo(slide, dontAnimate);
}
renderCarousel = ({ getPrefixCls }: ConfigConsumerProps) => {
renderCarousel = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
const props = {
...this.props,
};
@ -101,11 +102,15 @@ export default class Carousel extends React.Component<CarouselProps, {}> {
props.fade = true;
}
const className = getPrefixCls('carousel', props.prefixCls);
const prefixCls = getPrefixCls('carousel', props.prefixCls);
const dotsClass = 'slick-dots';
const dotPosition = this.getDotPosition();
props.dotsClass = `${dotsClass} ${dotsClass}-${dotPosition || 'bottom'}`;
const className = classNames(prefixCls, {
[`${prefixCls}-rtl`]: direction === 'rtl',
});
return (
<div className={className}>
<SlickCarousel ref={this.saveSlick} {...props} />

View File

@ -1,9 +1,15 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
.@{ant-prefix}-carousel {
@carousel-prefix-cls: ~'@{ant-prefix}-carousel';
.@{carousel-prefix-cls} {
.reset-component;
&-rtl {
direction: rtl;
}
.slick-slider {
position: relative;
display: block;
@ -47,6 +53,11 @@
left: 0;
display: block;
.@{carousel-prefix-cls}-rtl & {
right: 0;
left: auto;
}
&::before,
&::after {
display: table;
@ -66,9 +77,7 @@
float: left;
height: 100%;
min-height: 1px;
[dir='rtl'] & {
float: right;
}
img {
display: block;
}
@ -131,9 +140,19 @@
.slick-prev {
left: -25px;
&::before {
content: '←';
}
.@{carousel-prefix-cls}-rtl & {
right: -25px;
left: auto;
&::before {
content: '→';
}
}
}
.slick-next {
@ -141,18 +160,34 @@
&::before {
content: '→';
}
.@{carousel-prefix-cls}-rtl & {
right: auto;
left: -25px;
&::before {
content: '←';
}
}
}
// Dots
.slick-dots {
position: absolute;
display: block;
width: 100%;
height: @carousel-dot-height;
margin: 0;
padding: 0;
text-align: center;
right: 0;
bottom: 0;
left: 0;
z-index: 15;
display: flex !important;
justify-content: center;
margin-right: 15%;
margin-left: 15%;
padding-right: 0;
list-style: none;
.@{carousel-prefix-cls}-rtl& {
flex-direction: row-reverse;
}
&-bottom {
bottom: 12px;
}
@ -162,13 +197,21 @@
li {
position: relative;
display: inline-block;
flex: 0 1 auto;
box-sizing: content-box;
width: @carousel-dot-width;
height: @carousel-dot-height;
margin: 0 2px;
margin-right: 3px;
margin-left: 3px;
padding: 0;
text-align: center;
text-indent: -999px;
vertical-align: top;
transition: all 0.5s;
button {
display: block;
width: @carousel-dot-width;
width: 100%;
height: @carousel-dot-height;
padding: 0;
color: transparent;
@ -185,10 +228,12 @@
opacity: 0.75;
}
}
&.slick-active button {
&.slick-active {
width: @carousel-dot-active-width;
background: @component-background;
opacity: 1;
& button {
background: @component-background;
opacity: 1;
}
&:hover,
&:focus {
opacity: 1;
@ -202,25 +247,41 @@
.slick-dots {
top: 50%;
bottom: auto;
flex-direction: column;
width: @carousel-dot-height;
height: auto;
margin: 0;
transform: translateY(-50%);
.@{carousel-prefix-cls}-rtl& {
flex-direction: column;
}
&-left {
right: auto;
left: 12px;
}
&-right {
right: 12px;
left: auto;
}
li {
margin: 0 2px;
width: @carousel-dot-height;
height: @carousel-dot-width;
margin: 4px 2px;
vertical-align: baseline;
button {
width: @carousel-dot-height;
height: @carousel-dot-width;
}
&.slick-active button {
&.slick-active {
width: @carousel-dot-height;
height: @carousel-dot-active-width;
button {
width: @carousel-dot-height;
height: @carousel-dot-active-width;
}
}
}
}

View File

@ -360,6 +360,243 @@ exports[`Cascader can be selected 3`] = `
</div>
`;
exports[`Cascader can be selected in RTL direction 1`] = `
<div>
<div
class="ant-cascader-menus ant-cascader-menu-rtl"
style="opacity:0"
>
<div>
<ul
class="ant-cascader-menu"
>
<li
class="ant-cascader-menu-item ant-cascader-menu-item-expand ant-cascader-menu-item-active"
role="menuitem"
title="Zhejiang"
>
Zhejiang
<span
class="ant-cascader-menu-item-expand-icon"
>
<span
aria-label="left"
class="anticon anticon-left"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="left"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"
/>
</svg>
</span>
</span>
</li>
<li
class="ant-cascader-menu-item ant-cascader-menu-item-expand"
role="menuitem"
title="Jiangsu"
>
Jiangsu
<span
class="ant-cascader-menu-item-expand-icon"
>
<span
aria-label="left"
class="anticon anticon-left"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="left"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"
/>
</svg>
</span>
</span>
</li>
</ul>
<ul
class="ant-cascader-menu"
>
<li
class="ant-cascader-menu-item ant-cascader-menu-item-expand"
role="menuitem"
title="Hangzhou"
>
Hangzhou
<span
class="ant-cascader-menu-item-expand-icon"
>
<span
aria-label="left"
class="anticon anticon-left"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="left"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"
/>
</svg>
</span>
</span>
</li>
</ul>
</div>
</div>
</div>
`;
exports[`Cascader can be selected in RTL direction 2`] = `
<div>
<div
class="ant-cascader-menus ant-cascader-menu-rtl"
style="opacity:0"
>
<div>
<ul
class="ant-cascader-menu"
>
<li
class="ant-cascader-menu-item ant-cascader-menu-item-expand ant-cascader-menu-item-active"
role="menuitem"
title="Zhejiang"
>
Zhejiang
<span
class="ant-cascader-menu-item-expand-icon"
>
<span
aria-label="left"
class="anticon anticon-left"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="left"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"
/>
</svg>
</span>
</span>
</li>
<li
class="ant-cascader-menu-item ant-cascader-menu-item-expand"
role="menuitem"
title="Jiangsu"
>
Jiangsu
<span
class="ant-cascader-menu-item-expand-icon"
>
<span
aria-label="left"
class="anticon anticon-left"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="left"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"
/>
</svg>
</span>
</span>
</li>
</ul>
<ul
class="ant-cascader-menu"
>
<li
class="ant-cascader-menu-item ant-cascader-menu-item-expand ant-cascader-menu-item-active"
role="menuitem"
title="Hangzhou"
>
Hangzhou
<span
class="ant-cascader-menu-item-expand-icon"
>
<span
aria-label="left"
class="anticon anticon-left"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="left"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"
/>
</svg>
</span>
</span>
</li>
</ul>
<ul
class="ant-cascader-menu"
>
<li
class="ant-cascader-menu-item"
role="menuitem"
title="West Lake"
>
West Lake
</li>
</ul>
</div>
</div>
</div>
`;
exports[`Cascader have a notFoundContent that fit trigger input width 1`] = `
<div>
<div
@ -641,6 +878,170 @@ exports[`Cascader popup correctly with defaultValue 1`] = `
</div>
`;
exports[`Cascader popup correctly with defaultValue RTL 1`] = `
<div>
<div
class="ant-cascader-menus ant-cascader-menu-rtl"
style="opacity:0"
>
<div>
<ul
class="ant-cascader-menu"
>
<li
class="ant-cascader-menu-item ant-cascader-menu-item-expand ant-cascader-menu-item-active"
role="menuitem"
title="Zhejiang"
>
Zhejiang
<span
class="ant-cascader-menu-item-expand-icon"
>
<span
aria-label="left"
class="anticon anticon-left"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="left"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"
/>
</svg>
</span>
</span>
</li>
<li
class="ant-cascader-menu-item ant-cascader-menu-item-expand"
role="menuitem"
title="Jiangsu"
>
Jiangsu
<span
class="ant-cascader-menu-item-expand-icon"
>
<span
aria-label="left"
class="anticon anticon-left"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="left"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"
/>
</svg>
</span>
</span>
</li>
</ul>
<ul
class="ant-cascader-menu"
>
<li
class="ant-cascader-menu-item ant-cascader-menu-item-expand ant-cascader-menu-item-active"
role="menuitem"
title="Hangzhou"
>
Hangzhou
<span
class="ant-cascader-menu-item-expand-icon"
>
<span
aria-label="left"
class="anticon anticon-left"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="left"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"
/>
</svg>
</span>
</span>
</li>
</ul>
<ul
class="ant-cascader-menu"
>
<li
class="ant-cascader-menu-item"
role="menuitem"
title="West Lake"
>
West Lake
</li>
</ul>
</div>
</div>
</div>
`;
exports[`Cascader rtl render component should be rendered correctly in RTL direction 1`] = `
<span
class="ant-cascader-picker ant-cascader-picker-rtl"
tabindex="0"
>
<span
class="ant-cascader-picker-label"
/>
<input
autocomplete="off"
class="ant-input ant-input-rtl ant-cascader-input "
placeholder="Please select"
readonly=""
tabindex="-1"
type="text"
value=""
/>
<span
aria-label="down"
class="anticon anticon-down ant-cascader-picker-arrow"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
`;
exports[`Cascader should highlight keyword and filter when search in Cascader 1`] = `
<Popup
align={

View File

@ -2,8 +2,10 @@ import React from 'react';
import { render, mount } from 'enzyme';
import KeyCode from 'rc-util/lib/KeyCode';
import Cascader from '..';
import ConfigProvider from '../../config-provider';
import focusTest from '../../../tests/shared/focusTest';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
const options = [
{
@ -47,6 +49,7 @@ function filter(inputValue, path) {
describe('Cascader', () => {
focusTest(Cascader);
mountTest(Cascader);
rtlTest(Cascader);
it('popup correctly when panel is hidden', () => {
const wrapper = mount(<Cascader options={options} />);
@ -442,4 +445,134 @@ describe('Cascader', () => {
});
expect(wrapper.find('input').prop('placeholder')).toBe(customPlaceholder);
});
it('popup correctly with defaultValue RTL', () => {
const wrapper = mount(
<ConfigProvider direction="rtl">
<Cascader options={options} defaultValue={['zhejiang', 'hangzhou']} />
</ConfigProvider>,
);
wrapper
.find('Cascader')
.find('input')
.simulate('click');
expect(
render(
wrapper
.find('Cascader')
.find('Trigger')
.instance()
.getComponent(),
),
).toMatchSnapshot();
});
it('can be selected in RTL direction', () => {
const options2 = [
{
value: 'zhejiang',
label: 'Zhejiang',
children: [
{
value: 'hangzhou',
label: 'Hangzhou',
children: [
{
value: 'xihu',
label: 'West Lake',
},
],
},
],
},
{
value: 'jiangsu',
label: 'Jiangsu',
children: [
{
value: 'nanjing',
label: 'Nanjing',
children: [
{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
},
],
},
],
},
];
const onChange = jest.fn();
const wrapper = mount(
<ConfigProvider direction="rtl">
<Cascader
options={options2}
defaultValue={['zhejiang', 'hangzhou']}
onChange={onChange}
popupPlacement="bottomRight"
/>
</ConfigProvider>,
);
wrapper
.find('Cascader')
.find('input')
.simulate('click');
let popupWrapper = mount(
wrapper
.find('Cascader')
.find('Trigger')
.instance()
.getComponent(),
);
popupWrapper
.find('.ant-cascader-menu')
.at(0)
.find('.ant-cascader-menu-item')
.at(0)
.simulate('click');
expect(
render(
wrapper
.find('Cascader')
.find('Trigger')
.instance()
.getComponent(),
),
).toMatchSnapshot();
popupWrapper = mount(
wrapper
.find('Cascader')
.find('Trigger')
.instance()
.getComponent(),
);
popupWrapper
.find('.ant-cascader-menu')
.at(1)
.find('.ant-cascader-menu-item')
.at(0)
.simulate('click');
expect(
render(
wrapper
.find('Cascader')
.find('Trigger')
.instance()
.getComponent(),
),
).toMatchSnapshot();
popupWrapper = mount(
wrapper
.find('Cascader')
.find('Trigger')
.instance()
.getComponent(),
);
popupWrapper
.find('.ant-cascader-menu')
.at(2)
.find('.ant-cascader-menu-item')
.at(0)
.simulate('click');
expect(onChange).toHaveBeenCalledWith(['zhejiang', 'hangzhou', 'xihu'], expect.anything());
});
});

View File

@ -4,7 +4,13 @@ import arrayTreeFilter from 'array-tree-filter';
import classNames from 'classnames';
import omit from 'omit.js';
import KeyCode from 'rc-util/lib/KeyCode';
import { CloseCircleFilled, DownOutlined, RightOutlined, RedoOutlined } from '@ant-design/icons';
import {
CloseCircleFilled,
DownOutlined,
RightOutlined,
RedoOutlined,
LeftOutlined,
} from '@ant-design/icons';
import Input from '../input';
import { ConfigConsumer, ConfigConsumerProps, RenderEmptyHandler } from '../config-provider';
@ -211,7 +217,6 @@ function warningValueNotExist(list: CascaderOptionType[], fieldNames: FieldNames
class Cascader extends React.Component<CascaderProps, CascaderState> {
static defaultProps = {
transitionName: 'slide-up',
popupPlacement: 'bottomLeft',
options: [],
disabled: false,
allowClear: true,
@ -414,8 +419,21 @@ class Cascader extends React.Component<CascaderProps, CascaderState> {
this.input.blur();
}
getPopupPlacement(direction: string = 'ltr') {
const { popupPlacement } = this.props;
if (popupPlacement !== undefined) {
return popupPlacement;
}
return direction === 'rtl' ? 'bottomRight' : 'bottomLeft';
}
renderCascader = (
{ getPopupContainer: getContextPopupContainer, getPrefixCls, renderEmpty }: ConfigConsumerProps,
{
getPopupContainer: getContextPopupContainer,
getPrefixCls,
renderEmpty,
direction,
}: ConfigConsumerProps,
locale: CascaderLocale,
) => {
const { props, state } = this;
@ -432,11 +450,14 @@ class Cascader extends React.Component<CascaderProps, CascaderState> {
showSearch = false,
suffixIcon,
notFoundContent,
popupClassName,
...otherProps
} = props;
const { value, inputFocused } = state;
const isRtlLayout = direction === 'rtl';
const prefixCls = getPrefixCls('cascader', customizePrefixCls);
const inputPrefixCls = getPrefixCls('input', customizeInputPrefixCls);
@ -453,6 +474,7 @@ class Cascader extends React.Component<CascaderProps, CascaderState> {
[`${prefixCls}-picker-arrow-expand`]: state.popupVisible,
});
const pickerCls = classNames(className, `${prefixCls}-picker`, {
[`${prefixCls}-picker-rtl`]: isRtlLayout,
[`${prefixCls}-picker-with-value`]: state.inputValue,
[`${prefixCls}-picker-disabled`]: disabled,
[`${prefixCls}-picker-${size}`]: !!size,
@ -552,7 +574,10 @@ class Cascader extends React.Component<CascaderProps, CascaderState> {
</span>
);
const expandIcon = <RightOutlined />;
let expandIcon = <RightOutlined />;
if (isRtlLayout) {
expandIcon = <LeftOutlined />;
}
const loadingIcon = (
<span className={`${prefixCls}-menu-item-loading-icon`}>
@ -562,7 +587,9 @@ class Cascader extends React.Component<CascaderProps, CascaderState> {
const getPopupContainer = props.getPopupContainer || getContextPopupContainer;
const rest = omit(props, ['inputIcon', 'expandIcon', 'loadingIcon']);
const rcCascaderRtlPopupClassName = classNames(popupClassName, {
[`${prefixCls}-menu-${direction}`]: direction === 'rtl',
});
return (
<RcCascader
{...rest}
@ -576,6 +603,8 @@ class Cascader extends React.Component<CascaderProps, CascaderState> {
dropdownMenuColumnStyle={dropdownMenuColumnStyle}
expandIcon={expandIcon}
loadingIcon={loadingIcon}
popupClassName={rcCascaderRtlPopupClassName}
popupPlacement={this.getPopupPlacement(direction)}
>
{input}
</RcCascader>

View File

@ -3,6 +3,8 @@
@import '../../input/style/mixin';
@cascader-prefix-cls: ~'@{ant-prefix}-cascader';
@picker-rtl-cls: ~'@{cascader-prefix-cls}-picker-rtl';
@menu-rtl-cls: ~'@{cascader-prefix-cls}-menu-rtl';
.@{cascader-prefix-cls} {
.reset-component;
@ -17,6 +19,12 @@
// because input.less will compile after cascader.less
background-color: transparent !important;
cursor: pointer;
.@{picker-rtl-cls} & {
padding-right: @input-padding-horizontal-base;
padding-left: 24px;
text-align: right;
}
}
&-picker-show-search &-input.@{ant-prefix}-input {
@ -67,6 +75,11 @@
line-height: 20px;
white-space: nowrap;
text-overflow: ellipsis;
.@{picker-rtl-cls} & {
padding: 0 @control-padding-horizontal 0 20px;
text-align: right;
}
}
&-clear {
@ -87,6 +100,11 @@
&:hover {
color: @text-color-secondary;
}
.@{picker-rtl-cls} & {
right: auto;
left: @control-padding-horizontal;
}
}
&:hover &-clear {
@ -109,6 +127,11 @@
&&-expand {
transform: rotate(180deg);
}
.@{picker-rtl-cls} & {
right: auto;
left: @control-padding-horizontal;
}
}
}
@ -120,6 +143,11 @@
&-picker-small &-picker-clear,
&-picker-small &-picker-arrow {
right: @control-padding-horizontal-sm;
.@{picker-rtl-cls} & {
right: auto;
left: @control-padding-horizontal;
}
}
&-menus {
@ -182,6 +210,17 @@
&:only-child {
border-radius: @border-radius-base;
}
&-rtl {
direction: rtl;
border-right: none;
border-left: @border-width-base @border-style-base @border-color-split;
&:last-child {
margin-right: 0;
margin-left: -1px;
border-left-color: transparent;
border-radius: 0 0 4px 4px;
}
}
}
&-menu-item {
padding: @cascader-dropdown-vertical-padding @control-padding-horizontal;
@ -209,6 +248,11 @@
&-expand {
position: relative;
padding-right: 24px;
.@{menu-rtl-cls} & {
padding-right: @control-padding-horizontal;
padding-left: 24px;
}
}
&-expand &-expand-icon,
@ -218,6 +262,11 @@
position: absolute;
right: @control-padding-horizontal;
color: @text-color-secondary;
.@{menu-rtl-cls} & {
right: auto;
left: @control-padding-horizontal;
}
}
& &-keyword {

View File

@ -0,0 +1,19 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Checkbox rtl render component should be rendered correctly in RTL direction 1`] = `
<label
class="ant-checkbox-wrapper"
>
<span
class="ant-checkbox"
>
<input
class="ant-checkbox-input"
type="checkbox"
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
`;

View File

@ -44,3 +44,9 @@ exports[`CheckboxGroup passes prefixCls down to checkbox 1`] = `
</label>
</div>
`;
exports[`CheckboxGroup rtl render component should be rendered correctly in RTL direction 1`] = `
<div
class="ant-checkbox-group"
/>
`;

View File

@ -4,10 +4,12 @@ import Checkbox from '..';
import focusTest from '../../../tests/shared/focusTest';
import { resetWarned } from '../../_util/warning';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
describe('Checkbox', () => {
focusTest(Checkbox);
mountTest(Checkbox);
rtlTest(Checkbox);
it('responses hover events', () => {
const onMouseEnter = jest.fn();

View File

@ -2,9 +2,11 @@ import React from 'react';
import { mount, render } from 'enzyme';
import Checkbox from '../index';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
describe('CheckboxGroup', () => {
mountTest(Checkbox.Group);
rtlTest(Checkbox.Group);
it('should work basically', () => {
const onChange = jest.fn();

View File

@ -59,7 +59,7 @@ export default class Collapse extends React.Component<CollapseProps, any> {
: icon;
};
renderCollapse = ({ getPrefixCls }: ConfigConsumerProps) => {
renderCollapse = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
const {
prefixCls: customizePrefixCls,
className = '',
@ -71,6 +71,7 @@ export default class Collapse extends React.Component<CollapseProps, any> {
{
[`${prefixCls}-borderless`]: !bordered,
[`${prefixCls}-icon-position-${expandIconPosition}`]: true,
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
);

View File

@ -1,5 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Collapse rtl render component should be rendered correctly in RTL direction 1`] = `
<div
class="ant-collapse ant-collapse-icon-position-left ant-collapse-rtl"
/>
`;
exports[`Collapse should render extra node of panel 1`] = `
<div
class="ant-collapse ant-collapse-icon-position-left"

View File

@ -1,6 +1,7 @@
import React from 'react';
import { mount } from 'enzyme';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
describe('Collapse', () => {
// Fix css-animation deps on these
@ -17,6 +18,7 @@ describe('Collapse', () => {
// eslint-disable-next-line global-require
const Collapse = require('..').default;
mountTest(Collapse);
rtlTest(Collapse);
it('should support remove expandIcon', () => {
const wrapper = mount(

View File

@ -11,6 +11,10 @@
border-bottom: 0;
border-radius: @collapse-panel-border-radius;
&-rtl {
direction: rtl;
}
& > &-item {
border-bottom: @border-width-base @border-style-base @border-color-base;
@ -30,6 +34,11 @@
cursor: pointer;
transition: all 0.3s;
.@{collapse-prefix-cls}-rtl & {
padding: @collapse-header-padding;
padding-right: @collapse-header-padding-extra;
}
.@{collapse-prefix-cls}-arrow {
.iconfont-mixin();
@ -42,11 +51,19 @@
& svg {
transition: transform 0.24s;
.@{collapse-prefix-cls}-rtl& {
transform: rotate(180deg);
}
}
}
.@{collapse-prefix-cls}-extra {
float: right;
.@{collapse-prefix-cls}-rtl& {
float: left;
}
}
&:focus {
@ -57,6 +74,11 @@
&.@{collapse-prefix-cls}-no-arrow {
> .@{collapse-prefix-cls}-header {
padding-left: 12px;
.@{collapse-prefix-cls}-rtl& {
padding-right: 12px;
padding-left: 0;
}
}
}
}

View File

@ -74,7 +74,7 @@ exports[`renders ./components/comment/demo/basic.md correctly 1`] = `
</svg>
</span>
<span
style="padding-left:8px;cursor:auto"
class="comment-action"
>
0
</span>
@ -104,7 +104,7 @@ exports[`renders ./components/comment/demo/basic.md correctly 1`] = `
</svg>
</span>
<span
style="padding-left:8px;cursor:auto"
class="comment-action"
>
0
</span>

View File

@ -0,0 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Comment rtl render component should be rendered correctly in RTL direction 1`] = `
<div
class="ant-comment ant-comment-rtl"
>
<div
class="ant-comment-inner"
>
<div
class="ant-comment-avatar"
/>
<div
class="ant-comment-content"
>
<div
class="ant-comment-content-author"
/>
<div
class="ant-comment-content-detail"
/>
</div>
</div>
</div>
`;

View File

@ -1,6 +1,8 @@
import Comment from '../index';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
describe('Comment', () => {
mountTest(Comment);
rtlTest(Comment);
});

View File

@ -51,7 +51,7 @@ class App extends React.Component {
onClick: this.like,
})}
</Tooltip>
<span style={{ paddingLeft: 8, cursor: 'auto' }}>{likes}</span>
<span className="comment-action">{likes}</span>
</span>,
<span key=' key="comment-basic-dislike"'>
<Tooltip title="Dislike">
@ -59,7 +59,7 @@ class App extends React.Component {
onClick: this.dislike,
})}
</Tooltip>
<span style={{ paddingLeft: 8, cursor: 'auto' }}>{dislikes}</span>
<span className="comment-action">{dislikes}</span>
</span>,
<span key="comment-basic-reply-to">Reply to</span>,
];
@ -93,3 +93,16 @@ class App extends React.Component {
ReactDOM.render(<App />, mountNode);
```
```css
/* tile uploaded pictures */
.comment-action {
padding-left: 8px;
cursor: 'auto';
}
[class*='-col-rtl'] .comment-action {
padding-right: 8px;
padding-left: 0;
}
```

View File

@ -37,7 +37,7 @@ export default class Comment extends React.Component<CommentProps, {}> {
return <div className={classNames(`${prefixCls}-nested`)}>{children}</div>;
};
renderComment = ({ getPrefixCls }: ConfigConsumerProps) => {
renderComment = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
const {
actions,
author,
@ -86,8 +86,11 @@ export default class Comment extends React.Component<CommentProps, {}> {
</div>
);
const cls = classNames(prefixCls, className, {
[`${prefixCls}-rtl`]: direction === 'rtl',
});
return (
<div {...otherProps} className={classNames(prefixCls, className)} style={style}>
<div {...otherProps} className={cls} style={style}>
{comment}
{children ? this.renderNested(prefixCls, children) : null}
</div>

View File

@ -7,6 +7,10 @@
position: relative;
background-color: @comment-bg;
&-rtl {
direction: rtl;
}
&-inner {
display: flex;
padding: @comment-padding-base;
@ -17,6 +21,11 @@
flex-shrink: 0;
margin-right: 12px;
cursor: pointer;
.@{comment-prefix-cls}-rtl & {
margin-right: 0;
margin-left: 12px;
}
img {
width: 32px;
height: 32px;
@ -42,6 +51,11 @@
padding-right: 8px;
font-size: @comment-font-size-sm;
line-height: 18px;
.@{comment-prefix-cls}-rtl & {
padding-right: 0;
padding-left: 8px;
}
}
&-name {
@ -71,6 +85,10 @@
&-actions {
margin-top: 12px;
padding-left: 0;
.@{comment-prefix-cls}-rtl & {
padding-right: 0;
}
> li {
display: inline-block;
color: @comment-action-color;
@ -81,6 +99,12 @@
cursor: pointer;
transition: color 0.3s;
user-select: none;
.@{comment-prefix-cls}-rtl & {
padding-right: 0;
padding-left: 10px;
}
&:hover {
color: @comment-action-hover-color;
}
@ -90,5 +114,10 @@
&-nested {
margin-left: @comment-nest-indent;
.@{comment-prefix-cls}-rtl & {
margin-right: @comment-nest-indent;
margin-left: 0;
}
}
}

View File

@ -17,6 +17,7 @@ export interface ConfigConsumerProps {
pageHeader?: {
ghost: boolean;
};
direction?: 'ltr' | 'rtl';
}
export const ConfigContext = React.createContext<ConfigConsumerProps>({

View File

@ -0,0 +1,613 @@
---
order: 2
title:
zh-CN: 方向
en-US: Direction
---
## zh-CN
这里列出了支持 `rtl` 方向的组件,您可以在演示中切换方向。
## en-US
Components which support rtl direction are listed here, you can toggle the direction in the demo.
```jsx
import {
Input,
Col,
Row,
Select,
InputNumber,
ConfigProvider,
Cascader,
Radio,
Switch,
Tree,
TreeSelect,
Modal,
Button,
Pagination,
Steps,
Rate,
Badge,
} from 'antd';
import {
SearchOutlined as SearchIcon,
SmileOutlined,
DownloadOutlined,
LeftOutlined,
RightOutlined,
MinusOutlined,
PlusOutlined,
} from '@ant-design/icons';
const InputGroup = Input.Group;
const ButtonGroup = Button.Group;
const { Option } = Select;
const { TreeNode } = Tree;
const { Search } = Input;
const { Step } = Steps;
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: 'primadar',
label: 'پیرمادر',
},
],
},
],
},
{
value: 'gilan',
label: 'گیلان',
children: [
{
value: 'rasht',
label: 'رشت',
children: [
{
value: 'district-3',
label: 'منطقه ۳',
},
],
},
],
},
];
class Page extends React.Component {
state = {
currentStep: 0,
modalVisible: false,
badgeCount: 5,
showBadge: true,
};
selectBefore = (
<Select defaultValue="Http://" style={{ width: 90 }}>
<Option value="Http://">Http://</Option>
<Option value="Https://">Https://</Option>
</Select>
);
selectAfter = (
<Select defaultValue=".com" style={{ width: 80 }}>
<Option value=".com">.com</Option>
<Option value=".jp">.jp</Option>
<Option value=".cn">.cn</Option>
<Option value=".org">.org</Option>
</Select>
);
// ==== Cascader ====
cascaderFilter = (inputValue, path) => {
return path.some(option => option.label.toLowerCase().indexOf(inputValue.toLowerCase()) > -1);
};
onCascaderChange = value => {
console.log(value);
};
// ==== End Cascader ====
// ==== Modal ====
showModal = () => {
this.setState({
modalVisible: true,
});
};
handleOk = e => {
console.log(e);
this.setState({
modalVisible: false,
});
};
handleCancel = e => {
console.log(e);
this.setState({
modalVisible: false,
});
};
// ==== End Modal ====
onStepsChange = currentStep => {
console.log('onChange:', currentStep);
this.setState({ currentStep });
};
// ==== Badge ====
increaseBadge = () => {
const badgeCount = this.state.badgeCount + 1;
this.setState({ badgeCount });
};
declineBadge = () => {
let badgeCount = this.state.badgeCount - 1;
if (badgeCount < 0) {
badgeCount = 0;
}
this.setState({ badgeCount });
};
onChangeBadge = showBadge => {
this.setState({ showBadge });
};
// ==== End Badge ====
render() {
const { currentStep } = this.state;
return (
<div className="direction-components example">
<Row>
<Col span={24}>
<h3 className="demo-block-title">Cascader example:</h3>
<Cascader
suffixIcon={<SearchIcon />}
options={cascaderOptions}
onChange={this.onCascaderChange}
placeholder="یک مورد انتخاب کنید"
popupPlacement={this.props.popupPlacement}
/>
&nbsp;&nbsp;&nbsp;&nbsp; With search:
<Cascader
suffixIcon={<SmileOutlined />}
options={cascaderOptions}
onChange={this.onCascaderChange}
placeholder="Select an item"
popupPlacement={this.props.popupPlacement}
showSearch={this.cascaderFilter}
/>
</Col>
</Row>
<br />
<Row>
<Col span={12}>
<h3 className="demo-block-title">Switch example:</h3>
&nbsp;&nbsp;
<Switch defaultChecked />
&nbsp;&nbsp;
<Switch loading defaultChecked />
&nbsp;&nbsp;
<Switch size="small" loading />
</Col>
<Col span={12}>
<h3 className="demo-block-title">Radio Group example:</h3>
<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>
<br />
<Row>
<Col span={12}>
<h3 className="demo-block-title">Button example:</h3>
<div className="button-demo">
<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>
<br />
<Button.Group>
<Button type="primary">
<LeftOutlined />
Backward
</Button>
<Button type="primary">
Forward
<RightOutlined />
</Button>
</Button.Group>
<Button type="primary" loading>
Loading
</Button>
<Button type="primary" size="small" loading>
Loading
</Button>
</div>
</Col>
<Col span={12}>
<h3 className="demo-block-title">Tree example:</h3>
<Tree
showLine
checkable
defaultExpandedKeys={['0-0-0', '0-0-1']}
defaultSelectedKeys={['0-0-0', '0-0-1']}
defaultCheckedKeys={['0-0-0', '0-0-1']}
>
<TreeNode title="parent 1" key="0-0">
<TreeNode title="parent 1-0" key="0-0-0" disabled>
<TreeNode title="leaf" key="0-0-0-0" disableCheckbox />
<TreeNode title="leaf" key="0-0-0-1" />
</TreeNode>
<TreeNode title="parent 1-1" key="0-0-1">
<TreeNode title={<span style={{ color: '#1890ff' }}>sss</span>} key="0-0-1-0" />
</TreeNode>
</TreeNode>
</Tree>
</Col>
</Row>
<br />
<Row>
<Col span={24}>
<h3 className="demo-block-title">Input (Input Group) example:</h3>
<InputGroup size="large">
<Row gutter={8}>
<Col span={5}>
<Input defaultValue="0571" />
</Col>
<Col span={8}>
<Input defaultValue="26888888" />
</Col>
</Row>
</InputGroup>
<br />
<InputGroup compact>
<Input style={{ width: '20%' }} defaultValue="0571" />
<Input style={{ width: '30%' }} defaultValue="26888888" />
</InputGroup>
<br />
<InputGroup compact>
<Select defaultValue="Option1">
<Option value="Option1">Option1</Option>
<Option value="Option2">Option2</Option>
</Select>
<Input style={{ width: '50%' }} defaultValue="input content" />
<InputNumber />
</InputGroup>
<br />
<Search placeholder="input search text" enterButton="Search" size="large" />
<br />
<br />
<div style={{ marginBottom: 16 }}>
<Input
addonBefore={this.selectBefore}
addonAfter={this.selectAfter}
defaultValue="mysite"
/>
</div>
<br />
<Row>
<Col span={12}>
<h3 className="demo-block-title">Select example:</h3>
<Select mode="multiple" defaultValue="مورچه" style={{ width: 120 }}>
<Option value="jack">Jack</Option>
<Option value="مورچه">مورچه</Option>
<Option value="disabled" disabled>
Disabled
</Option>
<Option value="Yiminghe">yiminghe</Option>
</Select>
<Select defaultValue="مورچه" style={{ width: 120 }} disabled>
<Option value="مورچه">مورچه</Option>
</Select>
<Select defaultValue="مورچه" style={{ width: 120 }} loading>
<Option value="مورچه">مورچه</Option>
</Select>
<Select
showSearch
style={{ width: 200 }}
placeholder="Select a person"
optionFilterProp="children"
filterOption={(input, option) =>
option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
<Option value="jack">Jack</Option>
<Option value="سعید">سعید</Option>
<Option value="tom">Tom</Option>
</Select>
</Col>
<Col span={12}>
<h3 className="demo-block-title">TreeSelect example:</h3>
<div>
<TreeSelect
showSearch
style={{ width: '100%' }}
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
placeholder="Please select"
allowClear
treeDefaultExpandAll
>
<TreeNode value="parent 1" title="parent 1" key="0-1">
<TreeNode value="parent 1-0" title="parent 1-0" key="0-1-1">
<TreeNode value="leaf1" title="my leaf" key="random" />
<TreeNode value="leaf2" title="your leaf" key="random1" />
</TreeNode>
<TreeNode value="parent 1-1" title="parent 1-1" key="random2">
<TreeNode
value="sss"
title={<b style={{ color: '#08c' }}>sss</b>}
key="random3"
/>
</TreeNode>
</TreeNode>
</TreeSelect>
</div>
</Col>
</Row>
<br />
<Row>
<Col span={24}>
<h3 className="demo-block-title">Modal example:</h3>
<div>
<Button type="primary" onClick={this.showModal}>
Open Modal
</Button>
<Modal
title="پنچره ساده"
visible={this.state.modalVisible}
onOk={this.handleOk}
onCancel={this.handleCancel}
>
<p>نگاشته‌های خود را اینجا قراردهید</p>
<p>نگاشته‌های خود را اینجا قراردهید</p>
<p>نگاشته‌های خود را اینجا قراردهید</p>
</Modal>
</div>
</Col>
</Row>
<br />
<Row>
<Col span={24}>
<h3 className="demo-block-title">Steps example:</h3>
<div>
<Steps progressDot current={currentStep}>
<Step title="Finished" description="This is a description." />
<Step title="In Progress" description="This is a description." />
<Step title="Waiting" description="This is a description." />
</Steps>
<br />
<Steps current={currentStep} onChange={this.onStepsChange}>
<Step title="Step 1" description="This is a description." />
<Step title="Step 2" description="This is a description." />
<Step title="Step 3" description="This is a description." />
</Steps>
</div>
</Col>
</Row>
<br />
<Row>
<Col span={12}>
<h3 className="demo-block-title">Rate example:</h3>
<div>
<Rate defaultValue={2.5} />
<br />
<strong>* Note:</strong> Half star not implemented in RTL direction, it will be
supported after <a href="https://github.com/react-component/rate">rc-rate</a>{' '}
implement rtl support.
</div>
</Col>
<Col span={12}>
<h3 className="demo-block-title">Badge example:</h3>
<div>
<div>
<Badge count={this.state.badgeCount}>
<a href="#" className="head-example" />
</Badge>
<ButtonGroup>
<Button onClick={this.declineBadge}>
<MinusOutlined />
</Button>
<Button onClick={this.increaseBadge}>
<PlusOutlined />
</Button>
</ButtonGroup>
</div>
<div style={{ marginTop: 10 }}>
<Badge dot={this.state.showBadge}>
<a href="#" className="head-example" />
</Badge>
<Switch onChange={this.onChangeBadge} checked={this.state.showBadge} />
</div>
</div>
</Col>
</Row>
</Col>
</Row>
<br />
<br />
<Row>
<Col span={24}>
<h3 className="demo-block-title">Pagination example:</h3>
<Pagination showSizeChanger defaultCurrent={3} total={500} />
</Col>
</Row>
<br />
<Row>
<Col span={24}>
<h3 className="demo-block-title">Grid System example:</h3>
<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>
</div>
);
}
}
class App extends React.Component {
state = {
direction: 'ltr',
popupPlacement: 'bottomLeft',
};
changeDirection = e => {
const directionValue = e.target.value;
this.setState({ direction: directionValue });
if (directionValue === 'rtl') {
this.setState({ popupPlacement: 'bottomRight' });
} else {
this.setState({ popupPlacement: 'bottomLeft' });
}
};
render() {
const { direction } = this.state;
return (
<div>
<div className="change-direction">
<span style={{ marginRight: 16 }}>Change direction of components: </span>
<Radio.Group defaultValue="ltr" onChange={this.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 className={direction} popupPlacement={this.state.popupPlacement} />
</ConfigProvider>
</div>
);
}
}
ReactDOM.render(<App />, mountNode);
```
```css
.direction-components {
padding-top: 16px;
border-top: 1px solid rgba(150, 150, 150, 0.5);
}
.example {
margin: 16px 0;
}
.example > * {
margin-right: 8px;
}
.change-direction {
margin-bottom: 16px;
}
.demo-block-title {
margin: 0 8px 18px 8px;
border-bottom: 1px solid rgba(150, 150, 150, 0.5);
}
.button-demo .ant-btn,
.button-demo .ant-btn-group {
margin-right: 8px;
margin-bottom: 12px;
}
.button-demo .ant-btn-group > .ant-btn,
.button-demo .ant-btn-group > span > .ant-btn {
margin-right: 0;
margin-left: 0;
}
.head-example {
display: inline-block;
width: 42px;
height: 42px;
vertical-align: middle;
background: #eee;
border-radius: 4px;
}
.ant-badge:not(.ant-badge-not-a-wrapper) {
margin-right: 20px;
}
.ant-badge-rtl:not(.ant-badge-not-a-wrapper) {
margin-right: 0;
margin-left: 20px;
}
```

View File

@ -45,6 +45,7 @@ Some components use dynamic style to support wave effect. You can config `csp` p
| locale | language package setting, you can find the packages in [antd/es/locale](http://unpkg.com/antd/es/locale/) | object | |
| prefixCls | set prefix class | string | ant | |
| pageHeader | Unify the ghost of pageHeader ,Ref [pageHeader](<(/components/page-header)> | { ghost:boolean } | 'true' | |
| direction | set direction of layout. See [demo](#components-config-provider-demo-direction) | string: 'ltr', 'rtl' | ltr | |
## FAQ

View File

@ -36,6 +36,7 @@ export interface ConfigProviderProps {
pageHeader?: {
ghost: boolean;
};
direction?: 'ltr' | 'rtl';
}
class ConfigProvider extends React.Component<ConfigProviderProps> {
@ -57,6 +58,7 @@ class ConfigProvider extends React.Component<ConfigProviderProps> {
form,
locale,
pageHeader,
direction,
} = this.props;
const config: ConfigConsumerProps = {
@ -65,6 +67,7 @@ class ConfigProvider extends React.Component<ConfigProviderProps> {
csp,
autoInsertSpaceInButton,
locale: locale || legacyLocale,
direction,
};
if (getPopupContainer) {

View File

@ -46,6 +46,7 @@ return (
| locale | 语言包配置,语言包可到 [antd/es/locale](http://unpkg.com/antd/es/locale/) 目录下寻找 | object | - | |
| prefixCls | 设置统一样式前缀 | string | ant | |
| pageHeader | 统一设置 pageHeader 的 ghost参考 [pageHeader](<(/components/page-header)>) | { ghost: boolean } | 'true' | |
| direction | 设置文本展示方向。 [示例](#components-config-provider-demo-direction) | string: 'ltr', 'rtl' | ltr | |
## FAQ

View File

@ -0,0 +1,188 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`mount rtl render component should be rendered correctly in RTL direction 1`] = `
<div
class="ant-picker ant-picker-rtl"
>
<div
class="ant-picker-input"
>
<input
placeholder="Select date"
readonly=""
size="12"
value=""
/>
<span
class="ant-picker-suffix"
>
<span
aria-label="calendar"
class="anticon anticon-calendar"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="calendar"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"
/>
</svg>
</span>
</span>
</div>
</div>
`;
exports[`mount rtl render component should be rendered correctly in RTL direction 2`] = `
<div
class="ant-picker ant-picker-rtl"
>
<div
class="ant-picker-input"
>
<input
placeholder="Select month"
readonly=""
size="12"
value=""
/>
<span
class="ant-picker-suffix"
>
<span
aria-label="calendar"
class="anticon anticon-calendar"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="calendar"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"
/>
</svg>
</span>
</span>
</div>
</div>
`;
exports[`mount rtl render component should be rendered correctly in RTL direction 3`] = `
<div
class="ant-picker ant-picker-rtl"
>
<div
class="ant-picker-input"
>
<input
placeholder="Select week"
readonly=""
size="12"
value=""
/>
<span
class="ant-picker-suffix"
>
<span
aria-label="calendar"
class="anticon anticon-calendar"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="calendar"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"
/>
</svg>
</span>
</span>
</div>
</div>
`;
exports[`mount rtl render component should be rendered correctly in RTL direction 4`] = `
<div
class="ant-picker ant-picker-range ant-picker-rtl"
>
<div
class="ant-picker-input ant-picker-input-active"
>
<input
placeholder="Start date"
readonly=""
size="12"
value=""
/>
</div>
<div
class="ant-picker-range-separator"
>
<span
class="ant-picker-separator"
>
</span>
</div>
<div
class="ant-picker-input"
>
<input
placeholder="End date"
readonly=""
size="12"
value=""
/>
</div>
<div
class="ant-picker-active-bar"
style="right:0;width:0;position:absolute"
/>
<span
class="ant-picker-suffix"
>
<span
aria-label="calendar"
class="anticon anticon-calendar"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="calendar"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"
/>
</svg>
</span>
</span>
</div>
`;

View File

@ -1,5 +1,6 @@
import DatePicker from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
const { MonthPicker, WeekPicker, RangePicker } = DatePicker;
@ -8,4 +9,9 @@ describe('mount', () => {
mountTest(MonthPicker);
mountTest(WeekPicker);
mountTest(RangePicker);
rtlTest(DatePicker);
rtlTest(MonthPicker);
rtlTest(WeekPicker);
rtlTest(RangePicker);
});

View File

@ -142,7 +142,7 @@ function generatePicker<DateType>(generateConfig: GenerateConfig<DateType>) {
};
renderPicker = (locale: any) => {
const { getPrefixCls } = this.context;
const { getPrefixCls, direction } = this.context;
const { prefixCls: customizePrefixCls, className, size, ...restProps } = this.props;
const { format, showTime } = this.props as any;
const prefixCls = getPrefixCls('picker', customizePrefixCls);
@ -187,6 +187,7 @@ function generatePicker<DateType>(generateConfig: GenerateConfig<DateType>) {
superPrevIcon={<span className={`${prefixCls}-super-prev-icon`} />}
superNextIcon={<span className={`${prefixCls}-super-next-icon`} />}
components={Components}
direction={direction}
/>
);
};
@ -247,7 +248,7 @@ function generatePicker<DateType>(generateConfig: GenerateConfig<DateType>) {
};
renderPicker = (locale: any) => {
const { getPrefixCls } = this.context;
const { getPrefixCls, direction } = this.context;
const { prefixCls: customizePrefixCls, className, size, ...restProps } = this.props;
const { format, showTime, picker } = this.props as any;
const prefixCls = getPrefixCls('picker', customizePrefixCls);
@ -282,6 +283,7 @@ function generatePicker<DateType>(generateConfig: GenerateConfig<DateType>) {
superPrevIcon={<span className={`${prefixCls}-super-prev-icon`} />}
superNextIcon={<span className={`${prefixCls}-super-next-icon`} />}
components={Components}
direction={direction}
/>
);
};

View File

@ -32,6 +32,10 @@
border-radius: @border-radius-base;
transition: border @animation-duration-slow, box-shadow @animation-duration-slow;
&-rtl {
direction: rtl;
}
&:hover,
&-focused {
.hover();
@ -99,6 +103,11 @@
margin-left: @padding-xs / 2;
color: @disabled-color;
pointer-events: none;
.@{picker-prefix-cls}-rtl & {
margin-right: @padding-xs / 2;
margin-left: 0;
}
}
&-clear {
@ -115,6 +124,11 @@
&:hover {
color: @text-color-secondary;
}
.@{picker-prefix-cls}-rtl & {
right: auto;
left: 0;
}
}
&-separator {
@ -126,6 +140,11 @@
font-size: @font-size-lg;
line-height: @font-size-lg;
text-align: center;
.@{picker-prefix-cls}-rtl & {
transform: rotate(180deg);
transform-origin: 50% 60%;
}
}
// ======================== Range =========================
@ -136,6 +155,11 @@
// Clear
.@{picker-prefix-cls}-clear {
right: @input-padding-horizontal-base;
.@{picker-prefix-cls}-rtl& {
right: auto;
left: @input-padding-horizontal-base;
}
}
&:hover {
@ -211,6 +235,10 @@
text-align: left;
list-style: none;
.@{picker-prefix-cls}-dropdown-rtl & {
text-align: right;
}
> li {
display: inline-block;
}
@ -222,6 +250,12 @@
.@{picker-prefix-cls}-ok {
float: right;
margin-left: @padding-xs;
.@{picker-prefix-cls}-dropdown-rtl & {
float: left;
margin-right: @padding-xs;
margin-left: 0;
}
}
}

View File

@ -16,6 +16,10 @@
border-radius: @border-radius-base;
outline: none;
&-rtl {
direction: rtl;
}
&-focused {
border-color: @primary-color;
}
@ -126,11 +130,19 @@
&-prev-icon,
&-super-prev-icon {
transform: rotate(-45deg);
.@{picker-prefix-cls}-panel-rtl & {
transform: rotate(135deg);
}
}
&-next-icon,
&-super-next-icon {
transform: rotate(135deg);
.@{picker-prefix-cls}-panel-rtl & {
transform: rotate(-45deg);
}
}
// ======================== Body ========================
@ -226,9 +238,19 @@
&-in-view&-range-start::before {
left: 50%;
.@{picker-prefix-cls}-panel-rtl & {
right: 50%;
left: 0;
}
}
&-in-view&-range-end::before {
right: 50%;
.@{picker-prefix-cls}-panel-rtl & {
right: 0;
left: 50%;
}
}
// >>> Range Hover
@ -289,18 +311,38 @@
.@{cellClassName}::after {
right: -6px - @border-width-base;
left: 0;
.@{picker-prefix-cls}-panel-rtl & {
right: 0;
left: -6px - @border-width-base;
}
}
.@{picker-prefix-cls}-date-panel &-in-view&-in-range&-range-hover-end .@{cellClassName}::after {
right: 0;
left: -6px - @border-width-base;
.@{picker-prefix-cls}-panel-rtl & {
right: -6px - @border-width-base;
left: 0;
}
}
// Hover with range start & end
&-range-hover&-range-start::after {
right: 50%;
.@{picker-prefix-cls}-panel-rtl & {
right: 0;
left: 50%;
}
}
&-range-hover&-range-end::after {
left: 50%;
.@{picker-prefix-cls}-panel-rtl & {
right: 50%;
left: 0;
}
}
// Edge start
@ -312,6 +354,15 @@
border-left: @border-width-base dashed @picker-date-hover-range-border-color;
border-top-left-radius: @border-radius-base;
border-bottom-left-radius: @border-radius-base;
.@{picker-prefix-cls}-panel-rtl & {
right: 6px;
left: 0;
border-right: @border-width-base dashed @picker-date-hover-range-border-color;
border-left: none;
border-top-right-radius: @border-radius-base;
border-bottom-right-radius: @border-radius-base;
}
}
// Edge end
@ -323,6 +374,15 @@
border-right: @border-width-base dashed @picker-date-hover-range-border-color;
border-top-right-radius: @border-radius-base;
border-bottom-right-radius: @border-radius-base;
.@{picker-prefix-cls}-panel-rtl & {
right: 0;
left: 6px;
border-right: none;
border-left: @border-width-base dashed @picker-date-hover-range-border-color;
border-top-left-radius: @border-radius-base;
border-bottom-left-radius: @border-radius-base;
}
}
// >>> Disabled
@ -451,11 +511,23 @@
left: @hover-cell-fixed-distance;
border-left: @border-width-base dashed @picker-date-hover-range-border-color;
border-radius: @border-radius-base 0 0 @border-radius-base;
.@{picker-prefix-cls}-panel-rtl & {
right: @hover-cell-fixed-distance;
border-right: @border-width-base dashed @picker-date-hover-range-border-color;
border-radius: 0 @border-radius-base @border-radius-base 0;
}
}
.@{picker-prefix-cls}-cell-range-hover-end::after {
right: @hover-cell-fixed-distance;
border-right: @border-width-base dashed @picker-date-hover-range-border-color;
border-radius: 0 @border-radius-base @border-radius-base 0;
.@{picker-prefix-cls}-panel-rtl & {
left: @hover-cell-fixed-distance;
border-left: @border-width-base dashed @picker-date-hover-range-border-color;
border-radius: @border-radius-base 0 0 @border-radius-base;
}
}
}
@ -548,6 +620,10 @@
width: auto;
min-width: auto;
.@{picker-prefix-cls}-panel-rtl & {
direction: ltr;
}
.@{picker-prefix-cls}-content {
display: flex;
flex: auto;

View File

@ -224,7 +224,7 @@ class Descriptions extends React.Component<
render() {
return (
<ConfigConsumer>
{({ getPrefixCls }: ConfigConsumerProps) => {
{({ getPrefixCls, direction }: ConfigConsumerProps) => {
const {
className,
prefixCls: customizePrefixCls,
@ -258,6 +258,7 @@ class Descriptions extends React.Component<
className={classNames(prefixCls, className, {
[`${prefixCls}-${size}`]: size !== 'default',
[`${prefixCls}-bordered`]: !!bordered,
[`${prefixCls}-rtl`]: direction === 'rtl',
})}
style={style}
>

View File

@ -8,6 +8,10 @@
@descriptions-small-padding: 8px 16px;
.@{descriptions-prefix-cls} {
&-rtl {
direction: rtl;
}
&-title {
margin-bottom: 20px;
color: @heading-color;
@ -48,6 +52,10 @@
top: -0.5px;
margin: 0 8px 0 2px;
content: ' ';
.@{descriptions-prefix-cls}-rtl & {
margin: 0 2px 0 8px;
}
}
}

View File

@ -2,9 +2,11 @@ import React from 'react';
import { render } from 'enzyme';
import Drawer from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
describe('Drawer', () => {
mountTest(Drawer);
rtlTest(Drawer);
it('render correctly', () => {
const wrapper = render(

View File

@ -350,6 +350,8 @@ exports[`Drawer render top drawer 1`] = `
</div>
`;
exports[`Drawer rtl render component should be rendered correctly in RTL direction 1`] = `null`;
exports[`Drawer style/drawerStyle/headerStyle/bodyStyle should work 1`] = `
<div
class=""

View File

@ -69,14 +69,7 @@ class App extends React.Component {
This is two-level drawer
</Drawer>
<div className="site-multi-level-drawer-footer">
<Button
style={{
marginRight: 8,
}}
onClick={this.onClose}
>
Cancel
</Button>
<Button onClick={this.onClose}>Cancel</Button>
<Button onClick={this.onClose} type="primary">
Submit
</Button>
@ -102,6 +95,21 @@ ReactDOM.render(<App />, mountNode);
border-radius: 0 0 4px 4px;
background: #fff;
}
.site-multi-level-drawer-footer button:first-child {
margin-right: 8px;
}
[class*='-drawer-rtl'] .site-multi-level-drawer-footer {
text-align: left;
right: 0;
left: auto;
}
[class*='-drawer-rtl'] .site-multi-level-drawer-footer button:first-child {
margin-right: 0;
margin-left: 8px;
}
```
<style>

View File

@ -206,7 +206,7 @@ class Drawer extends React.Component<DrawerProps & ConfigConsumerProps, IDrawerS
// render Provider for Multi-level drawer
renderProvider = (value: Drawer) => {
const { prefixCls, placement, className, width, height, mask, ...rest } = this.props;
const { prefixCls, placement, className, width, height, mask, direction, ...rest } = this.props;
const haveMask = mask ? '' : 'no-mask';
this.parentDrawer = value;
const offsetStyle: any = {};
@ -215,6 +215,9 @@ class Drawer extends React.Component<DrawerProps & ConfigConsumerProps, IDrawerS
} else {
offsetStyle.height = height;
}
const drawerClassName = classNames(className, haveMask, {
[`${prefixCls}-rtl`]: direction === 'rtl',
});
return (
<DrawerContext.Provider value={this}>
<RcDrawer
@ -244,7 +247,7 @@ class Drawer extends React.Component<DrawerProps & ConfigConsumerProps, IDrawerS
showMask={mask}
placement={placement}
style={this.getRcDrawerStyle()}
className={classNames(className, haveMask)}
className={drawerClassName}
>
{this.renderBody()}
</RcDrawer>

View File

@ -14,6 +14,10 @@
box-shadow @animation-duration-slow @ease-base-out;
}
&-rtl {
direction: rtl;
}
&-content-wrapper {
position: absolute;
}
@ -40,6 +44,14 @@
}
&-left {
left: 0;
.@{drawer-prefix-cls} {
&-content-wrapper {
left: 0;
}
}
&.@{drawer-prefix-cls}-open {
.@{drawer-prefix-cls}-content-wrapper {
box-shadow: @shadow-1-right;
@ -166,6 +178,11 @@
transition: color @animation-duration-slow;
text-rendering: auto;
.@{drawer-prefix-cls}-rtl & {
right: auto;
left: 0;
}
&:focus,
&:hover {
color: @icon-color-hover;

View File

@ -1,5 +1,47 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DropdownButton rtl render component should be rendered correctly in RTL direction 1`] = `
<span
class="ant-dropdown-trigger ant-dropdown-rtl"
/>
`;
exports[`DropdownButton rtl render component should be rendered correctly in RTL direction 2`] = `
<div
class="ant-btn-group ant-btn-group-rtl ant-dropdown-button"
>
<button
class="ant-btn ant-btn-default ant-btn-rtl"
type="button"
/>
<button
class="ant-btn ant-dropdown-trigger ant-dropdown-rtl ant-btn-default ant-btn-rtl"
type="button"
>
<span
aria-label="ellipsis"
class="anticon anticon-ellipsis"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="ellipsis"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z"
/>
</svg>
</span>
</button>
</div>
`;
exports[`DropdownButton should support href like Button 1`] = `
<div
class="ant-btn-group ant-dropdown-button"

View File

@ -3,6 +3,7 @@ import { mount } from 'enzyme';
import Dropdown from '..';
import Menu from '../../menu';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
describe('DropdownButton', () => {
mountTest(() => (
@ -12,6 +13,13 @@ describe('DropdownButton', () => {
));
mountTest(Dropdown.Button);
rtlTest(() => (
<Dropdown menu={<Menu />}>
<span />
</Dropdown>
));
rtlTest(Dropdown.Button);
it('pass appropriate props to Dropdown', () => {
const props = {
align: {

View File

@ -59,7 +59,6 @@ export default class Dropdown extends React.Component<DropDownProps, any> {
static defaultProps = {
mouseEnterDelay: 0.15,
mouseLeaveDelay: 0.1,
placement: 'bottomLeft' as Placement,
};
getTransitionName() {
@ -118,9 +117,18 @@ export default class Dropdown extends React.Component<DropDownProps, any> {
return fixedModeOverlay as React.ReactElement;
};
getPlacement(direction: string = 'ltr') {
const { placement } = this.props;
if (placement !== undefined) {
return placement;
}
return direction === 'rtl' ? ('bottomRight' as Placement) : ('bottomLeft' as Placement);
}
renderDropDown = ({
getPopupContainer: getContextPopupContainer,
getPrefixCls,
direction,
}: ConfigConsumerProps) => {
const {
prefixCls: customizePrefixCls,
@ -128,16 +136,23 @@ export default class Dropdown extends React.Component<DropDownProps, any> {
trigger,
disabled,
getPopupContainer,
overlayClassName,
} = this.props;
const prefixCls = getPrefixCls('dropdown', customizePrefixCls);
const child = React.Children.only(children) as React.ReactElement<any>;
const dropdownTrigger = React.cloneElement(child, {
className: classNames(child.props.className, `${prefixCls}-trigger`),
className: classNames(child.props.className, `${prefixCls}-trigger`, {
[`${prefixCls}-rtl`]: direction === 'rtl',
}),
disabled,
});
const overlayClassNameCustomized = classNames(overlayClassName, {
[`${prefixCls}-rtl`]: direction === 'rtl',
});
const triggerActions = disabled ? [] : trigger;
let alignPoint;
if (triggerActions && triggerActions.indexOf('contextMenu') !== -1) {
@ -148,11 +163,13 @@ export default class Dropdown extends React.Component<DropDownProps, any> {
<RcDropdown
alignPoint={alignPoint}
{...this.props}
overlayClassName={overlayClassNameCustomized}
prefixCls={prefixCls}
getPopupContainer={getPopupContainer || getContextPopupContainer}
transitionName={this.getTransitionName()}
trigger={triggerActions}
overlay={() => this.renderOverlay(prefixCls)}
placement={this.getPlacement(direction)}
>
{dropdownTrigger}
</RcDropdown>

View File

@ -12,6 +12,10 @@
z-index: @zindex-dropdown;
display: block;
&-rtl {
direction: rtl;
}
&::before {
position: absolute;
top: -7px;
@ -21,6 +25,11 @@
z-index: -9999;
opacity: 0.0001;
content: ' ';
.@{dropdown-prefix-cls}-rtl& {
right: -7px;
left: 0;
}
}
&-wrap {
@ -76,6 +85,10 @@
ul,
li {
list-style: none;
.@{dropdown-prefix-cls}-rtl & {
text-align: right;
}
}
ul {
@ -98,11 +111,20 @@
cursor: pointer;
transition: all 0.3s;
.@{dropdown-prefix-cls}-rtl & {
text-align: right;
}
> .anticon:first-child,
> span > .anticon:first-child {
min-width: 12px;
margin-right: 8px;
font-size: @font-size-sm;
.@{dropdown-prefix-cls}-rtl & {
margin-right: 0;
margin-left: 8px;
}
}
> a {
@ -160,6 +182,12 @@
.@{dropdown-prefix-cls}-menu-submenu-arrow {
position: absolute;
right: @padding-xs;
.@{dropdown-prefix-cls}-rtl & {
right: auto;
left: @padding-xs;
}
&-icon {
color: @text-color-secondary;
font-style: normal;
@ -176,6 +204,11 @@
&-submenu-title {
padding-right: 26px;
.@{dropdown-prefix-cls}-rtl & {
padding-right: @control-padding-horizontal;
padding-left: 26px;
}
}
&-submenu-vertical {
@ -189,6 +222,13 @@
min-width: 100%;
margin-left: 4px;
transform-origin: 0 0;
.@{dropdown-prefix-cls}-rtl & {
right: 100%;
left: 0;
margin-right: 4px;
margin-left: 0;
}
}
&-submenu&-submenu-disabled .@{dropdown-prefix-cls}-menu-submenu-title {

View File

@ -0,0 +1,151 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Empty rtl render component should be rendered correctly in RTL direction 1`] = `
<div
class="ant-empty ant-empty-rtl"
>
<div
class="ant-empty-image"
>
<svg
class="ant-empty-img-default"
height="152"
viewBox="0 0 184 152"
width="184"
xmlns="http://www.w3.org/2000/svg"
>
<g
fill="none"
fill-rule="evenodd"
>
<g
transform="translate(24 31.67)"
>
<ellipse
class="ant-empty-img-default-ellipse"
cx="67.797"
cy="106.89"
rx="67.797"
ry="12.668"
/>
<path
class="ant-empty-img-default-path-1"
d="M122.034 69.674L98.109 40.229c-1.148-1.386-2.826-2.225-4.593-2.225h-51.44c-1.766 0-3.444.839-4.592 2.225L13.56 69.674v15.383h108.475V69.674z"
/>
<path
class="ant-empty-img-default-path-2"
d="M101.537 86.214L80.63 61.102c-1.001-1.207-2.507-1.867-4.048-1.867H31.724c-1.54 0-3.047.66-4.048 1.867L6.769 86.214v13.792h94.768V86.214z"
transform="translate(13.56)"
/>
<path
class="ant-empty-img-default-path-3"
d="M33.83 0h67.933a4 4 0 0 1 4 4v93.344a4 4 0 0 1-4 4H33.83a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4z"
/>
<path
class="ant-empty-img-default-path-4"
d="M42.678 9.953h50.237a2 2 0 0 1 2 2V36.91a2 2 0 0 1-2 2H42.678a2 2 0 0 1-2-2V11.953a2 2 0 0 1 2-2zM42.94 49.767h49.713a2.262 2.262 0 1 1 0 4.524H42.94a2.262 2.262 0 0 1 0-4.524zM42.94 61.53h49.713a2.262 2.262 0 1 1 0 4.525H42.94a2.262 2.262 0 0 1 0-4.525zM121.813 105.032c-.775 3.071-3.497 5.36-6.735 5.36H20.515c-3.238 0-5.96-2.29-6.734-5.36a7.309 7.309 0 0 1-.222-1.79V69.675h26.318c2.907 0 5.25 2.448 5.25 5.42v.04c0 2.971 2.37 5.37 5.277 5.37h34.785c2.907 0 5.277-2.421 5.277-5.393V75.1c0-2.972 2.343-5.426 5.25-5.426h26.318v33.569c0 .617-.077 1.216-.221 1.789z"
/>
</g>
<path
class="ant-empty-img-default-path-5"
d="M149.121 33.292l-6.83 2.65a1 1 0 0 1-1.317-1.23l1.937-6.207c-2.589-2.944-4.109-6.534-4.109-10.408C138.802 8.102 148.92 0 161.402 0 173.881 0 184 8.102 184 18.097c0 9.995-10.118 18.097-22.599 18.097-4.528 0-8.744-1.066-12.28-2.902z"
/>
<g
class="ant-empty-img-default-g"
transform="translate(149.65 15.383)"
>
<ellipse
cx="20.654"
cy="3.167"
rx="2.849"
ry="2.815"
/>
<path
d="M5.698 5.63H0L2.898.704zM9.259.704h4.985V5.63H9.259z"
/>
</g>
</g>
</svg>
</div>
<p
class="ant-empty-description"
>
No Data
</p>
</div>
`;
exports[`Empty should render in RTL direction 1`] = `
<div
class="ant-empty ant-empty-rtl"
>
<div
class="ant-empty-image"
>
<svg
class="ant-empty-img-default"
height="152"
viewBox="0 0 184 152"
width="184"
xmlns="http://www.w3.org/2000/svg"
>
<g
fill="none"
fill-rule="evenodd"
>
<g
transform="translate(24 31.67)"
>
<ellipse
class="ant-empty-img-default-ellipse"
cx="67.797"
cy="106.89"
rx="67.797"
ry="12.668"
/>
<path
class="ant-empty-img-default-path-1"
d="M122.034 69.674L98.109 40.229c-1.148-1.386-2.826-2.225-4.593-2.225h-51.44c-1.766 0-3.444.839-4.592 2.225L13.56 69.674v15.383h108.475V69.674z"
/>
<path
class="ant-empty-img-default-path-2"
d="M101.537 86.214L80.63 61.102c-1.001-1.207-2.507-1.867-4.048-1.867H31.724c-1.54 0-3.047.66-4.048 1.867L6.769 86.214v13.792h94.768V86.214z"
transform="translate(13.56)"
/>
<path
class="ant-empty-img-default-path-3"
d="M33.83 0h67.933a4 4 0 0 1 4 4v93.344a4 4 0 0 1-4 4H33.83a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4z"
/>
<path
class="ant-empty-img-default-path-4"
d="M42.678 9.953h50.237a2 2 0 0 1 2 2V36.91a2 2 0 0 1-2 2H42.678a2 2 0 0 1-2-2V11.953a2 2 0 0 1 2-2zM42.94 49.767h49.713a2.262 2.262 0 1 1 0 4.524H42.94a2.262 2.262 0 0 1 0-4.524zM42.94 61.53h49.713a2.262 2.262 0 1 1 0 4.525H42.94a2.262 2.262 0 0 1 0-4.525zM121.813 105.032c-.775 3.071-3.497 5.36-6.735 5.36H20.515c-3.238 0-5.96-2.29-6.734-5.36a7.309 7.309 0 0 1-.222-1.79V69.675h26.318c2.907 0 5.25 2.448 5.25 5.42v.04c0 2.971 2.37 5.37 5.277 5.37h34.785c2.907 0 5.277-2.421 5.277-5.393V75.1c0-2.972 2.343-5.426 5.25-5.426h26.318v33.569c0 .617-.077 1.216-.221 1.789z"
/>
</g>
<path
class="ant-empty-img-default-path-5"
d="M149.121 33.292l-6.83 2.65a1 1 0 0 1-1.317-1.23l1.937-6.207c-2.589-2.944-4.109-6.534-4.109-10.408C138.802 8.102 148.92 0 161.402 0 173.881 0 184 8.102 184 18.097c0 9.995-10.118 18.097-22.599 18.097-4.528 0-8.744-1.066-12.28-2.902z"
/>
<g
class="ant-empty-img-default-g"
transform="translate(149.65 15.383)"
>
<ellipse
cx="20.654"
cy="3.167"
rx="2.849"
ry="2.815"
/>
<path
d="M5.698 5.63H0L2.898.704zM9.259.704h4.985V5.63H9.259z"
/>
</g>
</g>
</svg>
</div>
<p
class="ant-empty-description"
>
No Data
</p>
</div>
`;

View File

@ -1,10 +1,13 @@
import React from 'react';
import { mount } from 'enzyme';
import { render, mount } from 'enzyme';
import Empty from '..';
import ConfigProvider from '../../config-provider';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
describe('Empty', () => {
mountTest(Empty);
rtlTest(Empty);
it('image size should change', () => {
const wrapper = mount(<Empty imageStyle={{ height: 20 }} />);
@ -15,4 +18,13 @@ describe('Empty', () => {
const wrapper = mount(<Empty description={false} />);
expect(wrapper.find('.ant-empty-description').length).toBe(0);
});
it('should render in RTL direction', () => {
const wrapper = mount(
<ConfigProvider direction="rtl">
<Empty />
</ConfigProvider>,
);
expect(render(wrapper)).toMatchSnapshot();
});
});

View File

@ -32,7 +32,7 @@ interface EmptyType extends React.FC<EmptyProps> {
const Empty: EmptyType = (props: EmptyProps) => (
<ConfigConsumer>
{({ getPrefixCls }: ConfigConsumerProps) => {
{({ getPrefixCls, direction }: ConfigConsumerProps) => {
const {
className,
prefixCls: customizePrefixCls,
@ -64,6 +64,7 @@ const Empty: EmptyType = (props: EmptyProps) => (
prefixCls,
{
[`${prefixCls}-normal`]: image === simpleEmptyImg,
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
)}

View File

@ -10,6 +10,10 @@
line-height: 22px;
text-align: center;
&-rtl {
direction: rtl;
}
&-image {
height: 100px;
margin-bottom: 8px;

View File

@ -24,7 +24,7 @@ export interface FormProps extends Omit<RcFormProps, 'form'> {
}
const InternalForm: React.FC<FormProps> = (props, ref) => {
const { getPrefixCls }: ConfigConsumerProps = React.useContext(ConfigContext);
const { getPrefixCls, direction }: ConfigConsumerProps = React.useContext(ConfigContext);
const {
form,
@ -45,6 +45,7 @@ const InternalForm: React.FC<FormProps> = (props, ref) => {
{
[`${prefixCls}-${layout}`]: true,
[`${prefixCls}-hide-required-mark`]: hideRequiredMark,
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
);

View File

@ -22,3 +22,23 @@ exports[`Form Form.Item should support data-*、aria-* and custom attribute 1`]
</div>
</form>
`;
exports[`Form rtl render component should be rendered correctly in RTL direction 1`] = `
<form
class="ant-form ant-form-horizontal ant-form-rtl"
/>
`;
exports[`Form rtl render component should be rendered correctly in RTL direction 2`] = `
<div
class="ant-row ant-row-rtl ant-form-item"
>
<div
class="ant-col ant-form-item-control ant-col-rtl"
>
<div
class="ant-form-item-control-input"
/>
</div>
</div>
`;

View File

@ -5,6 +5,7 @@ import Form from '..';
import Input from '../../input';
import Button from '../../button';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
jest.mock('scroll-into-view-if-needed');
@ -17,6 +18,9 @@ describe('Form', () => {
mountTest(Form);
mountTest(Form.Item);
rtlTest(Form);
rtlTest(Form.Item);
scrollIntoView.mockImplementation(() => {});
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

View File

@ -21,6 +21,10 @@
display: inline-block;
padding-right: 8px;
}
&-rtl {
direction: rtl;
}
}
// ================================================================
@ -140,11 +144,21 @@
.@{ant-prefix}-input-search:not(.@{ant-prefix}-input-search-enter-button) {
.@{ant-prefix}-input-suffix {
right: 28px;
.@{form-prefix-cls}-rtl & {
right: auto;
left: 28px;
}
}
}
.@{ant-prefix}-input-password-icon {
margin-right: 18px;
.@{form-prefix-cls}-rtl & {
margin-right: 0;
margin-left: 18px;
}
}
// ======================== Switch =========================
@ -162,21 +176,41 @@
> .@{ant-prefix}-select
.@{ant-prefix}-select-selection__clear {
right: 28px;
.@{form-prefix-cls}-rtl & {
right: auto;
left: 28px;
}
}
> .@{ant-prefix}-select .@{ant-prefix}-select-selection-selected-value,
:not(.@{ant-prefix}-input-group-addon)
> .@{ant-prefix}-select
.@{ant-prefix}-select-selection-selected-value {
padding-right: 42px;
.@{form-prefix-cls}-rtl & {
padding-right: 0;
padding-left: 42px;
}
}
// ======================= Cascader ========================
.@{ant-prefix}-cascader-picker {
&-arrow {
margin-right: 17px;
.@{form-prefix-cls}-rtl & {
margin-right: 0;
margin-left: 17px;
}
}
&-clear {
right: 28px;
.@{form-prefix-cls}-rtl & {
right: auto;
left: 28px;
}
}
}
@ -216,6 +250,11 @@
animation: zoomIn 0.3s @ease-out-back;
pointer-events: none;
.@{form-prefix-cls}-rtl & {
right: auto;
left: 0;
}
& svg {
position: absolute;
top: 0;

View File

@ -10,6 +10,11 @@
margin-right: 16px;
margin-bottom: 0;
.@{form-prefix-cls}-rtl& {
margin-right: 0;
margin-left: 16px;
}
&-with-help {
margin-bottom: @form-item-margin-bottom;
}

View File

@ -8,6 +8,9 @@
white-space: initial;
text-align: left;
.@{form-prefix-cls}-rtl& {
text-align: right;
}
> label {
margin: 0;

View File

@ -18,6 +18,18 @@ exports[`Grid renders wrapped Col correctly 1`] = `
</div>
`;
exports[`Grid rtl render component should be rendered correctly in RTL direction 1`] = `
<div
class="ant-row ant-row-rtl"
/>
`;
exports[`Grid rtl render component should be rendered correctly in RTL direction 2`] = `
<div
class="ant-col ant-col-rtl"
/>
`;
exports[`Grid should render Col 1`] = `
<div
class="ant-col ant-col-2"

View File

@ -2,11 +2,15 @@ import React from 'react';
import { render, mount } from 'enzyme';
import { Col, Row } from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
describe('Grid', () => {
mountTest(Row);
mountTest(Col);
rtlTest(Row);
rtlTest(Col);
it('should render Col', () => {
const wrapper = render(<Col span={2} />);
expect(wrapper).toMatchSnapshot();

View File

@ -45,7 +45,7 @@ function parseFlex(flex: FlexType): string {
}
export default class Col extends React.Component<ColProps, {}> {
renderCol = ({ getPrefixCls }: ConfigConsumerProps) => {
renderCol = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
const { props } = this;
const {
prefixCls: customizePrefixCls,
@ -81,6 +81,7 @@ export default class Col extends React.Component<ColProps, {}> {
sizeProps.offset || sizeProps.offset === 0,
[`${prefixCls}-${size}-push-${sizeProps.push}`]: sizeProps.push || sizeProps.push === 0,
[`${prefixCls}-${size}-pull-${sizeProps.pull}`]: sizeProps.pull || sizeProps.pull === 0,
[`${prefixCls}-rtl`]: direction === 'rtl',
};
});
const classes = classNames(

View File

@ -72,7 +72,7 @@ export default class Row extends React.Component<RowProps, RowState> {
return results;
}
renderRow = ({ getPrefixCls }: ConfigConsumerProps) => {
renderRow = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
const {
prefixCls: customizePrefixCls,
justify,
@ -89,6 +89,7 @@ export default class Row extends React.Component<RowProps, RowState> {
{
[`${prefixCls}-${justify}`]: justify,
[`${prefixCls}-${align}`]: align,
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
);

View File

@ -11,6 +11,10 @@
&::after {
display: flex;
}
&-rtl {
direction: rtl;
}
}
// x轴原点
@ -59,6 +63,10 @@
max-width: 100%;
// Prevent columns from collapsing when empty
min-height: 1px;
&&-rtl {
float: right;
}
}
.make-grid();

View File

@ -11,12 +11,30 @@
}
.@{ant-prefix}-col@{class}-push-@{index} {
left: percentage((@index / @grid-columns));
// reset property in RTL direction
&.@{ant-prefix}-col-rtl {
right: percentage((@index / @grid-columns));
left: auto;
}
}
.@{ant-prefix}-col@{class}-pull-@{index} {
right: percentage((@index / @grid-columns));
// reset property in RTL direction
&.@{ant-prefix}-col-rtl {
right: auto;
left: percentage((@index / @grid-columns));
}
}
.@{ant-prefix}-col@{class}-offset-@{index} {
margin-left: percentage((@index / @grid-columns));
// reset property in RTL direction
&.@{ant-prefix}-col-rtl {
margin-right: percentage((@index / @grid-columns));
margin-left: 0;
}
}
.@{ant-prefix}-col@{class}-order-@{index} {
order: @index;
@ -30,18 +48,39 @@
}
.@{ant-prefix}-col-push-@{index} {
left: auto;
// reset property in RTL direction
&.@{ant-prefix}-col-rtl {
right: auto;
}
}
.@{ant-prefix}-col-pull-@{index} {
right: auto;
&.@{ant-prefix}-col-rtl {
left: auto;
}
}
.@{ant-prefix}-col@{class}-push-@{index} {
left: auto;
&.@{ant-prefix}-col-rtl {
right: auto;
}
}
.@{ant-prefix}-col@{class}-pull-@{index} {
right: auto;
&.@{ant-prefix}-col-rtl {
left: auto;
}
}
.@{ant-prefix}-col@{class}-offset-@{index} {
margin-left: 0;
&.@{ant-prefix}-col-rtl {
margin-right: 0;
}
}
.@{ant-prefix}-col@{class}-order-@{index} {
order: 0;

View File

@ -0,0 +1,81 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`InputNumber rtl render component should be rendered correctly in RTL direction 1`] = `
<div
class="ant-input-number"
>
<div
class="ant-input-number-handler-wrap"
>
<span
aria-disabled="false"
aria-label="Increase Value"
class="ant-input-number-handler ant-input-number-handler-up "
role="button"
unselectable="unselectable"
>
<span
aria-label="up"
class="anticon anticon-up ant-input-number-handler-up-inner"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="up"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M890.5 755.3L537.9 269.2c-12.8-17.6-39-17.6-51.7 0L133.5 755.3A8 8 0 00140 768h75c5.1 0 9.9-2.5 12.9-6.6L512 369.8l284.1 391.6c3 4.1 7.8 6.6 12.9 6.6h75c6.5 0 10.3-7.4 6.5-12.7z"
/>
</svg>
</span>
</span>
<span
aria-disabled="false"
aria-label="Decrease Value"
class="ant-input-number-handler ant-input-number-handler-down "
role="button"
unselectable="unselectable"
>
<span
aria-label="down"
class="anticon anticon-down ant-input-number-handler-down-inner"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</div>
<div
class="ant-input-number-input-wrap"
>
<input
aria-valuemin="-9007199254740991"
autocomplete="off"
class="ant-input-number-input"
min="-9007199254740991"
role="spinbutton"
step="1"
value=""
/>
</div>
</div>
`;

View File

@ -3,10 +3,12 @@ import { mount } from 'enzyme';
import InputNumber from '..';
import focusTest from '../../../tests/shared/focusTest';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
describe('InputNumber', () => {
focusTest(InputNumber);
mountTest(InputNumber);
rtlTest(InputNumber);
// https://github.com/ant-design/ant-design/issues/13896
it('should return null when blur a empty input number', () => {

View File

@ -24,6 +24,7 @@ interface BasicProps {
className?: string;
style?: object;
disabled?: boolean;
direction?: any;
}
/**
@ -81,6 +82,7 @@ class ClearableLabeledInput extends React.Component<ClearableInputProps> {
[`${prefixCls}-affix-wrapper-lg`]: props.size === 'large',
[`${prefixCls}-affix-wrapper-input-with-clear-btn`]:
props.suffix && props.allowClear && this.props.value,
[`${prefixCls}-affix-wrapper-rtl`]: props.direction === 'rtl',
});
return (
<span className={affixWrapperCls} style={props.style}>
@ -96,7 +98,7 @@ class ClearableLabeledInput extends React.Component<ClearableInputProps> {
}
renderInputWithLabel(prefixCls: string, labeledElement: React.ReactElement<any>) {
const { addonBefore, addonAfter, style, size, className } = this.props;
const { addonBefore, addonAfter, style, size, className, direction } = this.props;
// Not wrap when there is not addons
if (!addonBefore && !addonAfter) {
return labeledElement;
@ -111,11 +113,13 @@ class ClearableLabeledInput extends React.Component<ClearableInputProps> {
const mergedWrapperClassName = classNames(`${prefixCls}-wrapper`, {
[wrapperClassName]: addonBefore || addonAfter,
[`${wrapperClassName}-rtl`]: direction === 'rtl',
});
const mergedGroupClassName = classNames(className, `${prefixCls}-group-wrapper`, {
[`${prefixCls}-group-wrapper-sm`]: size === 'small',
[`${prefixCls}-group-wrapper-lg`]: size === 'large',
[`${prefixCls}-group-wrapper-rtl`]: direction === 'rtl',
});
// Need another wrapper for changing display:table to display:inline-block

View File

@ -17,7 +17,7 @@ export interface GroupProps {
const Group: React.StatelessComponent<GroupProps> = props => (
<ConfigConsumer>
{({ getPrefixCls }: ConfigConsumerProps) => {
{({ getPrefixCls, direction }: ConfigConsumerProps) => {
const { prefixCls: customizePrefixCls, className = '' } = props;
const prefixCls = getPrefixCls('input-group', customizePrefixCls);
const cls = classNames(
@ -26,6 +26,7 @@ const Group: React.StatelessComponent<GroupProps> = props => (
[`${prefixCls}-lg`]: props.size === 'large',
[`${prefixCls}-sm`]: props.size === 'small',
[`${prefixCls}-compact`]: props.compact,
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
);

Some files were not shown because too many files have changed in this diff Show More