Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Global date picker #1026

Merged
merged 9 commits into from
Jul 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you find a changelog? I did some clicking around and stuff to make sure everything still worked proper, but I couldn't find a list of changes in his lib.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just looked here Hacker0x01/react-datepicker@v1.4.1...master up until the 1.5.0. commit

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^^ Ran through this and did some pretty deep manual testing to make sure everything still worked the way we expect.

"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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding ownFocus here will trap the keyboard properly (and allow you to ESC your way out immediately)

ownFocus
>
<div style={{ width: '400px' }}>
{this.renderQuickSelect()}
<EuiHorizontalRule />
{commonlyUsed}
<EuiHorizontalRule />
{recentlyUsed}
</div>
</EuiPopover>
);

return (
<EuiFormControlLayout
prepend={quickSelectPopover}
>
<EuiDatePickerRange
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worthwhile to figure out the clipping.

image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only thing I can do is reduce the side paddings of inputs or increase the max-width of this component. The latter doesn't really help all that much because it also depends on where it's located and what size the browser width is at.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries.

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