Skip to content

Commit dc9ac0f

Browse files
saeedrahimizombieJ
authored andcommitted
Add RTL support (#6)
* add rtl direction to picker * add rtl demo * add rtl tests * update readme * updateSn * fix overwriten property * fix breaking changes * update tests * pass direction context to Panel from RangePicker * default popupPlacement on rtl direction
1 parent 5b1061c commit dc9ac0f

File tree

13 files changed

+925
-24
lines changed

13 files changed

+925
-24
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ render(<Picker />, mountNode);
7575
| onOpenChange | Function(open:boolean) | | called when open/close picker |
7676
| onFocus | (evnet:React.FocusEventHandler<HTMLInputElement>) => void | | called like input's on focus |
7777
| onBlur | (evnet:React.FocusEventHandler<HTMLInputElement>) => void | | called like input's on blur |
78+
| direction | String: ltr or rtl | | Layout direction of picker component, it supports RTL direction too. |
7879

7980
### PickerPanel
8081

@@ -99,6 +100,7 @@ render(<Picker />, mountNode);
99100
| onSelect | Function(date: moment) | | a callback function, can be executed when the selected time |
100101
| onPanelChange | Function(value: moment, mode) | | callback when picker panel mode is changed |
101102
| onMouseDown | (evnet:React.MouseEventHandler<HTMLInputElement>) => void | | callback when executed onMouseDown evnent |
103+
| direction | String: ltr or rtl | | Layout direction of picker component, it supports RTL direction too. |
102104

103105
### RangePicker
104106

@@ -125,6 +127,7 @@ render(<Picker />, mountNode);
125127
| disabled | Boolean | false | whether the range picker is disabled |
126128
| onChange | Function(value:[moment], formatString: [string, string]) | | a callback function, can be executed when the selected time is changing |
127129
| onCalendarChange | Function(value:[moment], formatString: [string, string]) | | a callback function, can be executed when the start time or the end time of the range is changing |
130+
| direction | String: ltr or rtl | | Layout direction of picker component, it supports RTL direction too. |
128131

129132
### showTime-options
130133

assets/index.less

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55
.@{prefix-cls} {
66
display: inline-flex;
77

8+
&-rtl {
9+
direction: rtl;
10+
}
11+
812
&-focused {
913
border: 1px solid blue;
1014
}
11-
1215
&-panel {
1316
border: 1px solid #666;
1417
background: @background-color;
@@ -18,6 +21,10 @@
1821
&-focused {
1922
border-color: blue;
2023
}
24+
25+
&-rtl {
26+
direction: rtl;
27+
}
2128
}
2229

2330
// ===================== Shared Panel =====================
@@ -237,6 +244,11 @@
237244
display: block;
238245
width: 100%;
239246
text-align: left;
247+
248+
.@{prefix-cls}-panel-rtl & {
249+
padding: 0 12px 0 0;
250+
text-align: right;
251+
}
240252
}
241253
}
242254
}
@@ -274,6 +286,10 @@
274286
display: inline-flex;
275287
width: 100%;
276288

289+
.@{prefix-cls}-rtl & {
290+
text-align: right;
291+
}
292+
277293
> input {
278294
width: 100%;
279295
}
@@ -285,6 +301,11 @@
285301
top: 0;
286302
cursor: pointer;
287303

304+
.@{prefix-cls}-rtl & {
305+
right: auto;
306+
left: 4px;
307+
}
308+
288309
&-btn::after {
289310
content: '×';
290311
}
@@ -306,13 +327,15 @@
306327
// Panel
307328
@arrow-size: 10px;
308329

309-
&-placement-topLeft {
330+
&-placement-topLeft,
331+
&-placement-topRight {
310332
.@{prefix-cls}-range-arrow {
311333
bottom: @arrow-size / 2 + 1px;
312334
transform: rotate(135deg);
313335
}
314336
}
315-
&-placement-bottomLeft {
337+
&-placement-bottomLeft,
338+
&-placement-bottomright {
316339
.@{prefix-cls}-range-arrow {
317340
top: @arrow-size / 2 + 1px;
318341
transform: rotate(-45deg);
@@ -326,7 +349,14 @@
326349
z-index: 1;
327350
left: @arrow-size;
328351
margin-left: 10px;
329-
transition: left 0.3s;
352+
transition: all 0.3s;
353+
354+
.@{prefix-cls}-dropdown-rtl& {
355+
right: @arrow-size;
356+
left: auto;
357+
margin-left: 0;
358+
margin-right: 10px;
359+
}
330360

331361
&::before,
332362
&::after {
@@ -336,6 +366,12 @@
336366
top: 50%;
337367
left: 50%;
338368
transform: translate(-50%, -50%);
369+
370+
.@{prefix-cls}-dropdown-rtl& {
371+
right: 50%;
372+
left: auto;
373+
transform: translate(50%, -50%);
374+
}
339375
}
340376

341377
&::before {

examples/rtl.tsx

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
import React from 'react';
2+
import moment, { Moment } from 'moment';
3+
import Picker from '../src/Picker';
4+
import RangePicker from '../src/RangePicker';
5+
import PickerPanel from '../src/PickerPanel';
6+
import momentGenerateConfig from '../src/generate/moment';
7+
import zhCN from '../src/locale/zh_CN';
8+
import enUS from '../src/locale/en_US';
9+
import jaJP from '../src/locale/ja_JP';
10+
import '../assets/index.less';
11+
12+
const defaultValue = moment('2019-11-28 01:02:03');
13+
14+
function formatDate(date: Moment | null) {
15+
return date ? date.format('YYYY-MM-DD HH:mm:ss') : 'null';
16+
}
17+
18+
export default () => {
19+
const [value, setValue] = React.useState<Moment | null>(defaultValue);
20+
21+
const weekRef = React.useRef<Picker<Moment>>(null);
22+
23+
const onSelect = (newValue: Moment) => {
24+
console.log('Select:', newValue);
25+
};
26+
27+
const onChange = (newValue: Moment | null, formatString?: string) => {
28+
console.log('Change:', newValue, formatString);
29+
setValue(newValue);
30+
};
31+
32+
const sharedProps = {
33+
generateConfig: momentGenerateConfig,
34+
value,
35+
onSelect,
36+
onChange,
37+
direction: 'rtl',
38+
};
39+
40+
const rangePickerRef = React.useRef<RangePicker<Moment>>(null);
41+
42+
return (
43+
<div dir="rtl">
44+
<h2>
45+
Value:{' '}
46+
{value ? `${formatDate(value[0])} ~ ${formatDate(value[1])}` : 'null'}
47+
</h2>
48+
49+
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
50+
<div style={{ margin: '0 8px' }}>
51+
<h3>Basic</h3>
52+
<PickerPanel<Moment> {...sharedProps} locale={zhCN} />
53+
</div>
54+
55+
<div style={{ margin: '0 8px' }}>
56+
<h3>Uncontrolled</h3>
57+
<PickerPanel<Moment>
58+
generateConfig={momentGenerateConfig}
59+
locale={zhCN}
60+
onChange={onChange}
61+
defaultValue={moment('2000-01-01', 'YYYY-MM-DD')}
62+
/>
63+
</div>
64+
65+
<div style={{ margin: '0 8px' }}>
66+
<h3>1 Month earlier</h3>
67+
<PickerPanel<Moment>
68+
{...sharedProps}
69+
defaultPickerValue={defaultValue.clone().subtract(1, 'month')}
70+
locale={enUS}
71+
/>
72+
</div>
73+
74+
<div style={{ margin: '0 8px' }}>
75+
<h3>Week Picker CN</h3>
76+
<PickerPanel<Moment> {...sharedProps} locale={zhCN} picker="week" />
77+
</div>
78+
79+
<div style={{ margin: '0 8px' }}>
80+
<h3>Month Picker</h3>
81+
<PickerPanel<Moment> {...sharedProps} locale={zhCN} picker="month" />
82+
</div>
83+
84+
<div style={{ margin: '0 8px' }}>
85+
<h3>Week Picker US</h3>
86+
<PickerPanel<Moment> {...sharedProps} locale={enUS} picker="week" />
87+
</div>
88+
89+
<div style={{ margin: '0 8px' }}>
90+
<h3>Time</h3>
91+
<PickerPanel<Moment> {...sharedProps} locale={jaJP} mode="time" />
92+
</div>
93+
<div style={{ margin: '0 8px' }}>
94+
<h3>Time AM/PM</h3>
95+
<PickerPanel<Moment>
96+
{...sharedProps}
97+
locale={jaJP}
98+
mode="time"
99+
showTime={{
100+
use12Hours: true,
101+
showSecond: false,
102+
format: 'hh:mm A',
103+
}}
104+
/>
105+
</div>
106+
<div style={{ margin: '0 8px' }}>
107+
<h3>Datetime</h3>
108+
<PickerPanel<Moment> {...sharedProps} locale={zhCN} showTime />
109+
</div>
110+
</div>
111+
112+
<div style={{ display: 'flex' }}>
113+
<div style={{ margin: '0 8px' }}>
114+
<h3>Basic</h3>
115+
<Picker<Moment> {...sharedProps} locale={zhCN} />
116+
</div>
117+
<div style={{ margin: '0 8px' }}>
118+
<h3>Uncontrolled</h3>
119+
<Picker<Moment>
120+
generateConfig={momentGenerateConfig}
121+
locale={zhCN}
122+
allowClear
123+
/>
124+
</div>
125+
<div style={{ margin: '0 8px' }}>
126+
<h3>Datetime</h3>
127+
<Picker<Moment>
128+
{...sharedProps}
129+
locale={zhCN}
130+
defaultPickerValue={defaultValue.clone().subtract(1, 'month')}
131+
showTime={{
132+
showSecond: false,
133+
defaultValue: moment('11:28:39', 'HH:mm:ss'),
134+
}}
135+
showToday
136+
disabledTime={date => {
137+
if (date && date.isSame(defaultValue, 'date')) {
138+
return {
139+
disabledHours: () => [1, 3, 5, 7, 9, 11],
140+
};
141+
}
142+
return {};
143+
}}
144+
/>
145+
</div>
146+
<div style={{ margin: '0 8px' }}>
147+
<h3>Uncontrolled Datetime</h3>
148+
<Picker<Moment> generateConfig={momentGenerateConfig} locale={zhCN} />
149+
</div>
150+
<div style={{ margin: '0 8px' }}>
151+
<h3>Week</h3>
152+
<Picker<Moment>
153+
{...sharedProps}
154+
locale={zhCN}
155+
format="YYYY-Wo"
156+
allowClear
157+
picker="week"
158+
renderExtraFooter={() => 'I am footer!!!'}
159+
ref={weekRef}
160+
/>
161+
162+
<button
163+
type="button"
164+
onClick={() => {
165+
if (weekRef.current) {
166+
weekRef.current.focus();
167+
}
168+
}}
169+
>
170+
Focus
171+
</button>
172+
</div>
173+
<div style={{ margin: '0 8px' }}>
174+
<h3>Week</h3>
175+
<Picker<Moment>
176+
generateConfig={momentGenerateConfig}
177+
locale={zhCN}
178+
picker="week"
179+
/>
180+
</div>
181+
<div style={{ margin: '0 8px' }}>
182+
<h3>Time</h3>
183+
<Picker<Moment> {...sharedProps} locale={zhCN} picker="time" />
184+
</div>
185+
<div style={{ margin: '0 8px' }}>
186+
<h3>Time 12</h3>
187+
<Picker<Moment>
188+
{...sharedProps}
189+
locale={zhCN}
190+
picker="time"
191+
use12Hours
192+
/>
193+
</div>
194+
<div style={{ margin: '0 8px' }}>
195+
<h3>Year</h3>
196+
<Picker<Moment> {...sharedProps} locale={zhCN} picker="year" />
197+
</div>
198+
</div>
199+
200+
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
201+
<div style={{ margin: '0 8px' }}>
202+
<h3>Basic RangePicker</h3>
203+
<RangePicker<Moment>
204+
{...sharedProps}
205+
value={undefined}
206+
locale={zhCN}
207+
allowClear
208+
ref={rangePickerRef}
209+
defaultValue={[moment('1990-09-03'), moment('1989-11-28')]}
210+
placeholder={['start...', 'end...']}
211+
/>
212+
</div>
213+
</div>
214+
</div>
215+
);
216+
};

src/Picker.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ export interface PickerSharedProps<DateType> extends React.AriaAttributes {
8585
// WAI-ARIA
8686
role?: string;
8787
name?: string;
88+
89+
direction?: 'ltr' | 'rtl';
8890
}
8991

9092
type OmitPanelProps<Props> = Omit<
@@ -158,6 +160,7 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
158160
onMouseLeave,
159161
onContextMenu,
160162
onClick,
163+
direction,
161164
} = props as MergedPickerProps<DateType>;
162165

163166
const inputRef = React.useRef<HTMLInputElement>(null);
@@ -357,6 +360,7 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
357360
locale={locale}
358361
tabIndex={-1}
359362
onChange={setSelectedValue}
363+
direction={direction}
360364
/>
361365
</div>
362366
);
@@ -394,6 +398,7 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
394398
triggerOpen(false, true);
395399
}
396400
};
401+
const popupPlacement = direction === 'rtl' ? 'bottomRight' : 'bottomLeft';
397402

398403
return (
399404
<PanelContext.Provider
@@ -414,11 +419,14 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
414419
dropdownAlign={dropdownAlign}
415420
getPopupContainer={getPopupContainer}
416421
transitionName={transitionName}
422+
popupPlacement={popupPlacement}
423+
direction={direction}
417424
>
418425
<div
419426
className={classNames(prefixCls, className, {
420427
[`${prefixCls}-disabled`]: disabled,
421428
[`${prefixCls}-focused`]: focused,
429+
[`${prefixCls}-rtl`]: direction === 'rtl',
422430
})}
423431
style={style}
424432
onMouseDown={onMouseDown}

0 commit comments

Comments
 (0)