Skip to content

Commit

Permalink
Global date picker (#1026)
Browse files Browse the repository at this point in the history
  • Loading branch information
cchaos authored Jul 19, 2018
1 parent cc97c4c commit 567a9ed
Show file tree
Hide file tree
Showing 18 changed files with 498 additions and 20 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## [`master`](https://github.com/elastic/eui/tree/master)

- Added a visual pattern for Kibana's Global Date Picker ([#1026](https://github.com/elastic/eui/pull/1026))
- Added `responsive` prop to `EuiFlexGrid` ([#1026](https://github.com/elastic/eui/pull/1026))
- Added `expand` prop to `EuiTabs` and `EuiTabbedContent` ([#1026](https://github.com/elastic/eui/pull/1026))

**Bug fixes**

- Fixed `EuiContextMenuPanel` calling `ref` after being unmounted ([#1038](https://github.com/elastic/eui/pull/1038))
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"prop-types": "^15.6.0",
"react-ace": "^5.5.0",
"react-color": "^2.13.8",
"react-datepicker": "v1.4.1",
"react-datepicker": "v1.5.0",
"react-input-autosize": "^2.2.1",
"react-virtualized": "^9.18.5",
"react-vis": "1.10.2",
Expand Down
27 changes: 27 additions & 0 deletions src-docs/src/views/date_picker/date_picker_example.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
EuiLink,
EuiDatePicker,
EuiDatePickerRange,
EuiCallOut,
} from '../../../../src/components';

import DatePicker from './date_picker';
Expand Down Expand Up @@ -57,6 +58,10 @@ import Utc from './utc';
const utcSource = require('!!raw-loader!./utc');
const utcHtml = renderToHtml(Utc);

import GlobalDatePicker from './global_date_picker';
const globalDatePickerSource = require('!!raw-loader!./global_date_picker');
const globalDatePickerHtml = renderToHtml(GlobalDatePicker);

export const DatePickerExample = {
title: 'DatePicker',
sections: [{
Expand Down Expand Up @@ -260,5 +265,27 @@ export const DatePickerExample = {
</div>
),
demo: <Classes />,
}, {
title: 'Global date picker',
source: [{
type: GuideSectionTypes.JS,
code: globalDatePickerSource,
}, {
type: GuideSectionTypes.HTML,
code: globalDatePickerHtml,
}],
text: (
<div>
<EuiCallOut color="warning" title="Demo of visual pattern only">
<p>
This documents a <strong>visual</strong> pattern for the eventual replacement of Kibana&apos;s
global date/time picker. It uses all EUI components without any custom styles. However, it
currently depends strongly on <EuiLink href="https://reactdatepicker.com/#example-45">react-datepicker&apos;s <code>calendarContainer</code></EuiLink> option
which has it&apos;s own problems and limitations (like auto-focus on input stealing focus from inputs inside of popover).
</p>
</EuiCallOut>
</div>
),
demo: <GlobalDatePicker />,
}],
};
331 changes: 331 additions & 0 deletions src-docs/src/views/date_picker/global_date_picker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,331 @@

import React, {
Component, Fragment,
} from 'react';

import moment from 'moment';
import { CalendarContainer } from 'react-datepicker';

import {
EuiDatePicker,
EuiDatePickerRange,
EuiFormControlLayout,
EuiButtonEmpty,
EuiIcon,
EuiLink, EuiTitle, EuiFlexGrid, EuiFlexItem,
EuiPopover,
EuiSpacer,
EuiText,
EuiHorizontalRule,
EuiFlexGroup,
EuiFormRow,
EuiSelect,
EuiFieldNumber,
EuiButton,
EuiTabbedContent,
EuiForm,
EuiSwitch,
EuiTextColor,
} from '../../../../src/components';

const commonDates = [
'Today', 'Yesterday', 'This week', 'Week to date', 'This month', 'Month to date', 'This year', 'Year to date',
];

const relativeSelectOptions = [
{ text: 'Seconds ago', value: 'string:s' },
{ text: 'Minutes ago', value: 'string:m' },
{ text: 'Hours ago', value: 'string:h' },
{ text: 'Days ago', value: 'string:d' },
{ text: 'Weeks ago', value: 'string:w' },
{ text: 'Months ago', value: 'string:M' },
{ text: 'Years ago', value: 'string:y' },
{ text: 'Seconds from now', value: 'string:s+' },
{ text: 'Minutes from now', value: 'string:m+' },
{ text: 'Hours from now', value: 'string:h+' },
{ text: 'Days from now', value: 'string:d+' },
{ text: 'Weeks from now', value: 'string:w+' },
{ text: 'Months from now', value: 'string:M+' },
{ text: 'Years from now', value: 'string:y+' },
];

class GlobalDatePopover extends Component {
constructor(props) {
super(props);

this.tabs = [{
id: 'absolute',
name: 'Absolute',
content: (
<CalendarContainer className={props.className} style={{ width: 390 }}>
{props.children}
</CalendarContainer>
),
}, {
id: 'relative',
name: 'Relative',
content: (
<EuiForm style={{ width: 390, padding: 16 }}>
<EuiFlexGroup gutterSize="s" responsive={false}>
<EuiFlexItem>
<EuiFormRow>
<EuiFieldNumber aria-label="Count of" defaultValue="3" />
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow>
<EuiSelect options={relativeSelectOptions} defaultValue="string:d" />
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFormRow>
<EuiDatePicker selected={moment().subtract(3, 'day')} disabled />
</EuiFormRow>
<EuiFormRow>
<EuiSwitch label="Round to the day" />
</EuiFormRow>
</EuiForm>
),
}, {
id: 'now',
name: 'Now',
content: (
<EuiText textAlign="center" style={{ width: 390, padding: 16 }}>
<EuiTitle size="m"><span>{moment().format('MMMM Do YYYY')}</span></EuiTitle>
<EuiSpacer size="s" />
<EuiTitle size="m">
<EuiTextColor color="subdued">
<span>{moment().format('h:mm:ss a')}</span>
</EuiTextColor>
</EuiTitle>
</EuiText>
),
}];

this.state = {
selectedTab: this.tabs[0],
};
}

onTabClick = (selectedTab) => {
this.setState({ selectedTab });
};

render() {
return (
<EuiTabbedContent
tabs={this.tabs}
selectedTab={this.state.selectedTab}
onTabClick={this.onTabClick}
expand
/>
);
}
}

// eslint-disable-next-line react/no-multi-comp
export default class extends Component {
constructor(props) {
super(props);

this.state = {
startDate: moment(),
endDate: moment().add(11, 'd'),
isPopoverOpen: false,
recentlyUsed: [
['11/25/2017 00:00 AM', '11/25/2017 11:59 PM'],
['3 hours ago', '4 minutes ago'],
'Last 6 months',
['06/11/2017 06:11 AM', '06/11/2017 06:11 PM'],
],
};
}

handleChangeStart = (date) => {
this.setState({
startDate: date
});
}

handleChangeEnd = (date) => {
this.setState({
endDate: date
});
}

onButtonClick = () => {
this.setState({
isPopoverOpen: !this.state.isPopoverOpen,
});
}

closePopover = () => {
this.setState({
isPopoverOpen: false,
});
}

render() {
const quickSelectButton = (
<EuiButtonEmpty
className="euiFormControlLayout__prepend"
style={{ borderRight: 'none' }}
onClick={this.onButtonClick}
aria-label="Date quick select"
size="xs"
iconType="arrowDown"
iconSide="right"
>
<EuiIcon type="calendar" />
</EuiButtonEmpty>
);

const commonlyUsed = this.renderCommonlyUsed(commonDates);
const recentlyUsed = this.renderRecentlyUsed(this.state.recentlyUsed);

const quickSelectPopover = (
<EuiPopover
id="QuickSelectPopover"
button={quickSelectButton}
isOpen={this.state.isPopoverOpen}
closePopover={this.closePopover.bind(this)}
anchorPosition="downLeft"
ownFocus
>
<div style={{ width: '400px' }}>
{this.renderQuickSelect()}
<EuiHorizontalRule />
{commonlyUsed}
<EuiHorizontalRule />
{recentlyUsed}
</div>
</EuiPopover>
);

return (
<EuiFormControlLayout
prepend={quickSelectPopover}
>
<EuiDatePickerRange
className="euiDatePickerRange--inGroup"
iconType={false}
startDateControl={
<EuiDatePicker
selected={this.state.startDate}
onChange={this.handleChangeStart}
startDate={this.state.startDate}
endDate={this.state.endDate}
isInvalid={this.state.startDate > this.state.endDate}
aria-label="Start date"
calendarContainer={GlobalDatePopover}
showTimeSelect
/>
}
endDateControl={
<EuiDatePicker
selected={this.state.endDate}
onChange={this.handleChangeEnd}
startDate={this.state.startDate}
endDate={this.state.endDate}
isInvalid={this.state.startDate > this.state.endDate}
aria-label="End date"
calendarContainer={GlobalDatePopover}
showTimeSelect
/>
}
/>
</EuiFormControlLayout>
);
}

renderQuickSelect = () => {
const firstOptions = [
{ value: 'last', text: 'Last' },
{ value: 'previous', text: 'Previous' },
];

const lastOptions = [
{ value: 'seconds', text: 'seconds' },
{ value: 'minutes', text: 'minutes' },
{ value: 'hours', text: 'hours' },
{ value: 'days', text: 'days' },
{ value: 'weeks', text: 'weeks' },
{ value: 'months', text: 'months' },
{ value: 'years', text: 'years' },
];

return (
<Fragment>
<EuiTitle size="xxxs"><span>Quick select</span></EuiTitle>
<EuiSpacer size="s" />
<EuiFlexGroup gutterSize="s" responsive={false}>
<EuiFlexItem>
<EuiFormRow>
<EuiSelect options={firstOptions} />
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow>
<EuiFieldNumber aria-label="Count of" defaultValue="256" />
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow>
<EuiSelect options={lastOptions} />
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFormRow>
<EuiButton onClick={this.closePopover} style={{ minWidth: 0 }}>Apply</EuiButton>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
</Fragment>
);
}

renderCommonlyUsed = (commonDates) => {
const links = commonDates.map((date) => {
return (
<EuiFlexItem key={date}><EuiLink onClick={this.closePopover}>{date}</EuiLink></EuiFlexItem>
);
});

return (
<Fragment>
<EuiTitle size="xxxs"><span>Commonly used</span></EuiTitle>
<EuiSpacer size="s" />
<EuiText size="s">
<EuiFlexGrid gutterSize="s" columns={2} responsive={false}>
{links}
</EuiFlexGrid>
</EuiText>
</Fragment>
);
}

renderRecentlyUsed = (recentDates) => {
const links = recentDates.map((date) => {
let dateRange;
if (typeof date !== 'string') {
dateRange = `${date[0]}${date[1]}`;
}

return (
<EuiFlexItem key={date}><EuiLink onClick={this.closePopover}>{dateRange || date}</EuiLink></EuiFlexItem>
);
});

return (
<Fragment>
<EuiTitle size="xxxs"><span>Recently used date ranges</span></EuiTitle>
<EuiSpacer size="s" />
<EuiText size="s">
<EuiFlexGroup gutterSize="s" direction="column">
{links}
</EuiFlexGroup>
</EuiText>
</Fragment>
);
}
}
Loading

0 comments on commit 567a9ed

Please sign in to comment.