diff --git a/CHANGELOG.md b/CHANGELOG.md
index b3c6b4fe414..741e6513cef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,9 @@
## [`master`](https://github.com/elastic/eui/tree/master)
- Added support for nodes as "Action" column headers in `EuiBasicTable`, which was overlooked in the original change in `4.5.0` ([#1312](https://github.com/elastic/eui/pull/1312))
+- Updated `GlobalDatePicker` example to include all Kibana features ([#1219](https://github.com/elastic/eui/pull/1219))
+- Adjusted `EuiDatePickerRange` to allow for deeper customization ([#1219](https://github.com/elastic/eui/pull/1219))
+- Added `contentProps` and `textProps` to `EuiButton` and `EuiButtonEmpty` ([#1219](https://github.com/elastic/eui/pull/1219))
**Bug fixes**
diff --git a/src-docs/src/theme_dark.scss b/src-docs/src/theme_dark.scss
index f09a1aede5c..97d4ef0207c 100644
--- a/src-docs/src/theme_dark.scss
+++ b/src-docs/src/theme_dark.scss
@@ -1,3 +1,4 @@
@import '../../src/theme_dark';
@import './components/guide_components';
@import './views/header/global_filter_group';
+@import './views/date_picker/global_date_picker';
diff --git a/src-docs/src/theme_k6_dark.scss b/src-docs/src/theme_k6_dark.scss
index 99e708a00f1..a3738416b1d 100644
--- a/src-docs/src/theme_k6_dark.scss
+++ b/src-docs/src/theme_k6_dark.scss
@@ -1,3 +1,4 @@
@import '../../src/theme_k6_dark';
@import './components/guide_components';
@import './views/header/global_filter_group';
+@import './views/date_picker/global_date_picker';
diff --git a/src-docs/src/theme_k6_light.scss b/src-docs/src/theme_k6_light.scss
index 544617f335d..6fe9d55fe4b 100644
--- a/src-docs/src/theme_k6_light.scss
+++ b/src-docs/src/theme_k6_light.scss
@@ -1,3 +1,4 @@
@import '../../src/theme_k6_light';
@import './components/guide_components';
@import './views/header/global_filter_group';
+@import './views/date_picker/global_date_picker';
diff --git a/src-docs/src/theme_light.scss b/src-docs/src/theme_light.scss
index 9a8ac0e7586..bd607c9114f 100644
--- a/src-docs/src/theme_light.scss
+++ b/src-docs/src/theme_light.scss
@@ -1,4 +1,4 @@
@import '../../src/theme_light';
@import './components/guide_components';
@import './views/header/global_filter_group';
-
+@import './views/date_picker/global_date_picker';
diff --git a/src-docs/src/views/date_picker/_global_date_picker.scss b/src-docs/src/views/date_picker/_global_date_picker.scss
new file mode 100644
index 00000000000..d522ce46bcb
--- /dev/null
+++ b/src-docs/src/views/date_picker/_global_date_picker.scss
@@ -0,0 +1,97 @@
+//// GLOBAL Date picker
+
+// sass-lint:disable no-important
+.euiGlobalDatePicker__quickSelectButton {
+ // Override prepend border since button already lives inside another prepend
+ border-right: none !important;
+
+ .euiGlobalDatePicker__quickSelectButtonText {
+ // Override specificity from universal and sibiling selectors
+ margin-right: $euiSizeXS !important;
+ }
+}
+
+.euiGlobalDatePicker.euiFormControlLayout {
+ max-width: 480px;
+
+ > .euiFormControlLayout__childrenWrapper {
+ flex: 1 1 100%;
+ overflow: hidden;
+
+ > .euiDatePickerRange {
+ max-width: none;
+ width: auto;
+
+ // sass-lint:disable nesting-depth
+ .euiPopover__anchor {
+ display: block;
+ }
+ }
+ }
+}
+
+.euiGlobalDatePicker__dateButton {
+ @include euiFormControlText;
+ display: block;
+ width: 100%;
+ padding: 0 $euiSizeS;
+ line-height: $euiFormControlHeight - 2px;
+ height: $euiFormControlHeight - 2px;
+ word-break: break-all;
+ transition: background $euiAnimSpeedFast ease-in;
+
+ $backgroundColor: tintOrShade($euiColorSuccess, 90%, 70%);
+ $textColor: makeHighContrastColor($euiColorSuccess, $backgroundColor);
+
+ &-isSelected,
+ &-needsUpdating,
+ &:hover,
+ &:focus {
+ background-color: $backgroundColor;
+ }
+
+ &-needsUpdating {
+ color: $textColor;
+ }
+
+ &-isInvalid {
+ $backgroundColor: tintOrShade($euiColorDanger, 90%, 70%);
+ $textColor: makeHighContrastColor($euiColorDanger, $backgroundColor);
+ background-color: $backgroundColor;
+ color: $textColor;
+ }
+
+ .euiFormControlLayout__prepend {
+ border: none;
+ }
+}
+
+.euiGlobalDatePicker__dateButton--start {
+ text-align: right;
+}
+
+.euiGlobalDatePicker__dateButton--end {
+ text-align: left;
+}
+
+.euiGlobalDatePicker__updateButton {
+ // Just wide enough for all 3 states
+ min-width: $euiButtonMinWidth + ($euiSizeXS * 1.5);
+}
+
+.euiGlobalDatePicker__popoverSection {
+ @include euiScrollBar;
+ max-height: $euiSizeM * 11;
+ overflow: hidden;
+ overflow-y: auto;
+}
+
+@include euiBreakpoint('xs', 's') {
+ .euiGlobalDatePicker__updateButton {
+ min-width: 0;
+
+ .euiGlobalDatePicker__updateButtonText {
+ display: none;
+ }
+ }
+}
diff --git a/src-docs/src/views/date_picker/date_picker_example.js b/src-docs/src/views/date_picker/date_picker_example.js
index e40743e58bf..38dcff6a3f9 100644
--- a/src-docs/src/views/date_picker/date_picker_example.js
+++ b/src-docs/src/views/date_picker/date_picker_example.js
@@ -279,9 +279,7 @@ export const DatePickerExample = {
This documents a visual pattern for the eventual replacement of Kibana's
- global date/time picker. It uses all EUI components without any custom styles. However, it
- currently depends strongly on react-datepicker's calendarContainer
option
- which has it's own problems and limitations (like auto-focus on input stealing focus from inputs inside of popover).
+ global date/time picker. It uses all EUI components with some custom styles.
diff --git a/src-docs/src/views/date_picker/global_date_picker.js b/src-docs/src/views/date_picker/global_date_picker.js
index e45e76e2404..13930214175 100644
--- a/src-docs/src/views/date_picker/global_date_picker.js
+++ b/src-docs/src/views/date_picker/global_date_picker.js
@@ -2,9 +2,10 @@
import React, {
Component, Fragment,
} from 'react';
+import PropTypes from 'prop-types';
import moment from 'moment';
-import { CalendarContainer } from 'react-datepicker';
+import classNames from 'classnames';
import {
EuiDatePicker,
@@ -25,7 +26,9 @@ import {
EuiTabbedContent,
EuiForm,
EuiSwitch,
- EuiTextColor,
+ EuiToolTip,
+ EuiFieldText,
+ EuiButtonIcon,
} from '../../../../src/components';
const commonDates = [
@@ -57,9 +60,16 @@ class GlobalDatePopover extends Component {
id: 'absolute',
name: 'Absolute',
content: (
-
- {props.children}
-
+
+
+
+
+
+
),
}, {
id: 'relative',
@@ -79,7 +89,7 @@ class GlobalDatePopover extends Component {
-
+
@@ -90,14 +100,11 @@ class GlobalDatePopover extends Component {
id: 'now',
name: 'Now',
content: (
-
- {moment().format('MMMM Do YYYY')}
-
-
-
- {moment().format('h:mm:ss a')}
-
-
+
+
+ Setting the time to "Now" means that on every refresh
+ this time will be set to the time of the refresh.
+
),
}];
@@ -117,21 +124,115 @@ class GlobalDatePopover extends Component {
tabs={this.tabs}
selectedTab={this.state.selectedTab}
onTabClick={this.onTabClick}
+ size="s"
expand
/>
);
}
}
+// eslint-disable-next-line react/no-multi-comp
+class GlobalDateButton extends Component {
+ static propTypes = {
+ position: PropTypes.oneOf(['start', 'end']),
+ isInvalid: PropTypes.bool,
+ needsUpdating: PropTypes.bool,
+ buttonOnly: PropTypes.bool,
+ date: PropTypes.string,
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ isPopoverOpen: false,
+ };
+ }
+
+ togglePopover = () => {
+ this.setState({
+ isPopoverOpen: !this.state.isPopoverOpen,
+ });
+ }
+
+ closePopover = () => {
+ this.setState({
+ isPopoverOpen: false,
+ });
+ }
+
+ render() {
+ const {
+ position,
+ isInvalid,
+ needsUpdating,
+ date,
+ buttonProps,
+ buttonOnly,
+ ...rest
+ } = this.props;
+
+ const {
+ isPopoverOpen,
+ } = this.state;
+
+ const classes = classNames([
+ 'euiGlobalDatePicker__dateButton',
+ `euiGlobalDatePicker__dateButton--${position}`,
+ {
+ 'euiGlobalDatePicker__dateButton-isSelected': isPopoverOpen,
+ 'euiGlobalDatePicker__dateButton-isInvalid': isInvalid,
+ 'euiGlobalDatePicker__dateButton-needsUpdating': needsUpdating
+ }
+ ]);
+
+ let title = date;
+ if (isInvalid) {
+ title = `Invalid date: ${title}`;
+ } else if (needsUpdating) {
+ title = `Update needed: ${title}`;
+ }
+
+ const button = (
+
+ {date}
+
+ );
+
+ return buttonOnly ? button : (
+
+
+
+ );
+ }
+}
+
// 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'),
+ startDate: moment().format('MMM DD YYYY h:mm:ss.SSS'),
+ endDate: moment().add(11, 'd').format('MMM DD YYYY hh:mm:ss.SSS'),
isPopoverOpen: false,
+ showPrettyFormat: false,
+ showNeedsUpdate: false,
+ isUpdating: false,
+ timerIsOn: false,
recentlyUsed: [
['11/25/2017 00:00 AM', '11/25/2017 11:59 PM'],
['3 hours ago', '4 minutes ago'],
@@ -141,21 +242,44 @@ export default class extends Component {
};
}
- handleChangeStart = (date) => {
- this.setState({
- startDate: date
- });
+ setTootipRef = node => (this.tooltip = node);
+
+ showTooltip = () => this.tooltip.showToolTip();
+ hideTooltip = () => this.tooltip.hideToolTip();
+
+ togglePopover = (e) => {
+ // HACK TODO:
+ // this works because react listens to all events at the
+ // document level, and you need to interact with the native
+ // event's propagation to short-circuit outside click handler
+ // see also: https://stackoverflow.com/a/24421834
+ e.nativeEvent.stopImmediatePropagation();
+
+ this.setState(prevState => ({
+ isPopoverOpen: !prevState.isPopoverOpen,
+ }));
}
- handleChangeEnd = (date) => {
- this.setState({
- endDate: date
- });
+ togglePrettyFormat = () => {
+ this.setState(prevState => ({
+ showPrettyFormat: !prevState.showPrettyFormat,
+ }));
}
- onButtonClick = () => {
- this.setState({
- isPopoverOpen: !this.state.isPopoverOpen,
+ toggleNeedsUpdate = () => {
+ this.setState(prevState => {
+
+ if (!prevState.showNeedsUpdate) {
+ clearTimeout(this.tooltipTimeout);
+ this.showTooltip();
+ this.tooltipTimeout = setTimeout(() => {
+ this.hideTooltip();
+ }, 10000);
+ }
+
+ return ({
+ showNeedsUpdate: !prevState.showNeedsUpdate,
+ });
});
}
@@ -165,18 +289,31 @@ export default class extends Component {
});
}
+ toggleTimer = () => {
+ this.setState(prevState => ({
+ timerIsOn: !prevState.timerIsOn,
+ }));
+ }
+
+ toggleIsUpdating = () => {
+ this.setState(prevState => ({
+ isUpdating: !prevState.isUpdating,
+ }));
+ }
+
+
render() {
const quickSelectButton = (
-
+
);
@@ -190,51 +327,96 @@ export default class extends Component {
isOpen={this.state.isPopoverOpen}
closePopover={this.closePopover.bind(this)}
anchorPosition="downLeft"
- ownFocus
>
-
+
{this.renderQuickSelect()}
-
+
{commonlyUsed}
-
+
{recentlyUsed}
+
+ {this.renderTimer()}
);
return (
-
- this.state.endDate}
- aria-label="Start date"
- calendarContainer={GlobalDatePopover}
- showTimeSelect
- />
- }
- endDateControl={
- this.state.endDate}
- aria-label="End date"
- calendarContainer={GlobalDatePopover}
- showTimeSelect
- />
- }
- />
-
+
+
+
+
+
+
+
+
+
+
+ }
+ endDateControl={
+
+ }
+ >
+ {this.state.showPrettyFormat &&
+
+
+ Show dates
+
+ }
+
+
+
+
+ {this.renderUpdateButton()}
+
+
+
+ );
+ }
+
+ renderUpdateButton = () => {
+ const color = this.state.showNeedsUpdate ? 'secondary' : 'primary';
+ const icon = this.state.showNeedsUpdate ? 'kqlFunction' : 'refresh';
+ let text = this.state.showNeedsUpdate ? 'Update' : 'Refresh';
+
+ if (this.state.isUpdating) {
+ text = 'Updating';
+ }
+
+ return (
+
+
+ {text}
+
+
);
}
@@ -256,27 +438,41 @@ export default class extends Component {
return (
- Quick select
+
+
+ Quick select
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
-
+
- Apply
+ Apply
@@ -295,7 +491,7 @@ export default class extends Component {
Commonly used
-
+
{links}
@@ -320,7 +516,7 @@ export default class extends Component {
Recently used date ranges
-
+
{links}
@@ -328,4 +524,43 @@ export default class extends Component {
);
}
+
+ renderTimer = () => {
+ const lastOptions = [
+ { value: 'minutes', text: 'minutes' },
+ { value: 'hours', text: 'hours' },
+ ];
+
+ return (
+
+ Refresh every
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {this.state.timerIsOn ? 'Stop' : 'Start'}
+
+
+
+
+
+ );
+ }
+
}
diff --git a/src/components/button/button.js b/src/components/button/button.js
index 9a4875d7b25..9d5fbf68b56 100644
--- a/src/components/button/button.js
+++ b/src/components/button/button.js
@@ -53,6 +53,8 @@ export const EuiButton = ({
rel,
type,
buttonRef,
+ contentProps,
+ textProps,
...rest
}) => {
@@ -105,9 +107,9 @@ export const EuiButton = ({
ref={buttonRef}
{...rest}
>
-
+
{buttonIcon}
- {children}
+ {children}
);
@@ -120,9 +122,9 @@ export const EuiButton = ({
ref={buttonRef}
{...rest}
>
-
+
{buttonIcon}
- {children}
+ {children}
);
@@ -165,6 +167,16 @@ EuiButton.propTypes = {
*/
type: PropTypes.string,
buttonRef: PropTypes.func,
+
+ /**
+ * Passes props to `euiButton__content` span
+ */
+ contentProps: PropTypes.object,
+
+ /**
+ * Passes props to `euiButton__text` span
+ */
+ textProps: PropTypes.object,
};
EuiButton.defaultProps = {
diff --git a/src/components/button/button_empty/button_empty.js b/src/components/button/button_empty/button_empty.js
index 799db99bf2a..20c57e60f06 100644
--- a/src/components/button/button_empty/button_empty.js
+++ b/src/components/button/button_empty/button_empty.js
@@ -60,6 +60,8 @@ export const EuiButtonEmpty = ({
rel,
type,
buttonRef,
+ contentProps,
+ textProps,
...rest
}) => {
@@ -110,9 +112,9 @@ export const EuiButtonEmpty = ({
ref={buttonRef}
{...rest}
>
-
+
{buttonIcon}
- {children}
+ {children}
);
@@ -125,9 +127,9 @@ export const EuiButtonEmpty = ({
ref={buttonRef}
{...rest}
>
-
+
{buttonIcon}
- {children}
+ {children}
);
@@ -155,6 +157,16 @@ EuiButtonEmpty.propTypes = {
type: PropTypes.string,
buttonRef: PropTypes.func,
+
+ /**
+ * Passes props to `euiButton__content` span
+ */
+ contentProps: PropTypes.object,
+
+ /**
+ * Passes props to `euiButton__text` span
+ */
+ textProps: PropTypes.object,
};
EuiButtonEmpty.defaultProps = {
diff --git a/src/components/button/index.d.ts b/src/components/button/index.d.ts
index fe10d678721..fd3096cee25 100644
--- a/src/components/button/index.d.ts
+++ b/src/components/button/index.d.ts
@@ -1,7 +1,7 @@
///
///
-import { SFC, ButtonHTMLAttributes, AnchorHTMLAttributes, MouseEventHandler } from 'react';
+import { SFC, ButtonHTMLAttributes, AnchorHTMLAttributes, MouseEventHandler, HTMLAttributes } from 'react';
declare module '@elastic/eui' {
type EuiButtonPropsForButtonOrLink = (
@@ -33,6 +33,8 @@ declare module '@elastic/eui' {
size?: ButtonSize;
isLoading?: boolean;
isDisabled?: boolean;
+ contentProps?: HTMLAttributes;
+ textProps?: HTMLAttributes;
}
export const EuiButton: SFC<
EuiButtonPropsForButtonOrLink
@@ -87,6 +89,8 @@ declare module '@elastic/eui' {
flush?: EmptyButtonFlush;
isLoading?: boolean;
isDisabled?: boolean;
+ contentProps?: HTMLAttributes;
+ textProps?: HTMLAttributes;
}
export const EuiButtonEmpty: SFC<
diff --git a/src/components/date_picker/_date_picker_range.scss b/src/components/date_picker/_date_picker_range.scss
index 593f2085fe7..a88f2331894 100644
--- a/src/components/date_picker/_date_picker_range.scss
+++ b/src/components/date_picker/_date_picker_range.scss
@@ -5,7 +5,7 @@
* 1. Account for inner box-shadow style border
*/
- .euiDatePickerRange {
+.euiDatePickerRange {
@include euiFormControlSize(auto, $includeAlternates: true);
// Match just the regular drop shadow of inputs
@include euiFormControlDefaultShadow();
@@ -13,8 +13,9 @@
align-items: center;
padding: 1px; /* 1 */
- > span {
- flex: 1 1 0%; // All values necessary for IE support
+ // Allow any child to fill entire range container
+ > * {
+ flex-grow: 1;
}
.euiFieldText.euiDatePicker {
@@ -36,14 +37,19 @@
height: $euiFormControlHeight - 2px;
}
}
-}
-.euiDatePickerRange__icon {
- padding-left: $euiFormControlPadding;
- padding-right: $euiFormControlPadding;
-}
+ // Direct descendent selectors to override `> span`
-.euiDatePickerRange__delimeter {
- padding-left: $euiFormControlPadding/2;
- padding-right: $euiFormControlPadding/2;
+ > .euiDatePickerRange__icon {
+ flex: 0 0 auto;
+ padding-left: $euiFormControlPadding;
+ padding-right: $euiFormControlPadding;
+ }
+
+ > .euiDatePickerRange__delimeter {
+ line-height: 1 !important;
+ flex: 0 0 auto;
+ padding-left: $euiFormControlPadding / 2;
+ padding-right: $euiFormControlPadding / 2;
+ }
}
diff --git a/src/components/date_picker/date_picker_range.js b/src/components/date_picker/date_picker_range.js
index a3177121091..8dc3b5e5fd0 100644
--- a/src/components/date_picker/date_picker_range.js
+++ b/src/components/date_picker/date_picker_range.js
@@ -1,5 +1,5 @@
import React, {
- cloneElement,
+ cloneElement, Fragment,
} from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
@@ -11,11 +11,13 @@ import {
} from '../icon';
export const EuiDatePickerRange = ({
+ children,
className,
startDateControl,
endDateControl,
iconType,
fullWidth,
+ isCustom,
...rest
}) => {
@@ -38,25 +40,35 @@ export const EuiDatePickerRange = ({
optionalIcon = null;
}
- const clonedStartDate = cloneElement(startDateControl, {
- showIcon: false,
- fullWidth: fullWidth,
- });
+ let startControl = startDateControl;
+ let endControl = endDateControl;
+
+ if (!isCustom) {
+ startControl = cloneElement(startDateControl, {
+ showIcon: false,
+ fullWidth: fullWidth,
+ });
+
+ endControl = cloneElement(endDateControl, {
+ showIcon: false,
+ fullWidth: fullWidth,
+ });
+ }
- const clonedEndDate = cloneElement(endDateControl, {
- showIcon: false,
- fullWidth: fullWidth,
- });
return (
- {optionalIcon}
- {clonedStartDate}
- →
- {clonedEndDate}
+ {children ? (children) : (
+
+ {optionalIcon}
+ {startControl}
+ →
+ {endControl}
+
+ )}
);
};
@@ -78,6 +90,14 @@ EuiDatePickerRange.propTypes = {
PropTypes.oneOf(ICON_TYPES),
]),
fullWidth: PropTypes.bool,
+ /**
+ * Won't apply any additional props to start and end date components
+ */
+ isCustom: PropTypes.bool,
+ /**
+ * Including any children will replace all innerds with the provided children
+ */
+ children: PropTypes.node,
};
EuiDatePickerRange.defaultProps = {
diff --git a/src/components/form/_mixins.scss b/src/components/form/_mixins.scss
index 66c65d74f84..f5ecd320de9 100644
--- a/src/components/form/_mixins.scss
+++ b/src/components/form/_mixins.scss
@@ -13,6 +13,13 @@
* 3. Must supply both values to background-size or some browsers apply the single value to both directions
*/
+@mixin euiFormControlText {
+ font-size: $euiFontSizeS;
+ font-family: $euiFontFamily;
+ line-height: 1em; // fixes text alignment in IE
+ color: $euiTextColor;
+}
+
@mixin euiFormControlSize(
$height: $euiFormControlHeight,
$includeAlternates: false
@@ -134,14 +141,11 @@
@mixin euiFormControlStyle($borderOnly: false, $includeStates: true, $includeSizes: true) {
@include euiFormControlSize($includeAlternates: $includeSizes);
@include euiFormControlDefaultShadow;
+ @include euiFormControlText;
border: none;
- font-size: $euiFontSizeS;
- font-family: $euiFontFamily;
- padding: $euiFormControlPadding;
- line-height: 1em; // fixes text alignment in IE
- color: $euiTextColor;
border-radius: 0;
+ padding: $euiFormControlPadding;
@if ($includeSizes) {
&--compressed {