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

[Input Controls] Options List Embeddable, Factory & Frame #106877

Merged
merged 27 commits into from
Aug 10, 2021
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a982f35
Option 1a and 1b
clintandrewhall May 2, 2021
ada1576
Merge branch 'input_controls/options' of github.com:clintandrewhall/k…
ThomThomson Jun 4, 2021
026d8c4
Remove some demo data. Create options list components
ThomThomson Jun 22, 2021
450076c
Merge branch 'master' of github.com:elastic/kibana into controls/init…
ThomThomson Jun 22, 2021
a64cf4a
Merge branch 'master' of github.com:elastic/kibana into controls/init…
ThomThomson Jun 28, 2021
e73028c
Remove commented code
ThomThomson Jun 28, 2021
5a005fd
First pass creating input controls embeddables
ThomThomson Jul 20, 2021
b3f5e9c
Merge branch 'master' of github.com:elastic/kibana into controls/cont…
ThomThomson Jul 20, 2021
9b61889
slightly refined embeddables. Before rewrite
ThomThomson Jul 20, 2021
beec344
niave implementation of diffing for embeddable data fetching
ThomThomson Jul 21, 2021
a70eba5
with data fetching done in component, exhaustive deps disable needed
ThomThomson Jul 27, 2021
d015ff2
used useRef for selectedOptions
ThomThomson Jul 27, 2021
2ca7bad
Merge branch 'master' of github.com:elastic/kibana into controls/cont…
ThomThomson Jul 27, 2021
afbb045
Fix types
ThomThomson Jul 27, 2021
3caa0e6
remove some demo data, use defined flight field labels
ThomThomson Jul 28, 2021
99f020a
Merge branch 'master' into controls/controlFrame
kibanamachine Jul 28, 2021
131e8e0
about to clean up css
andreadelrio Jul 28, 2021
c063199
cleaning up css
andreadelrio Jul 28, 2021
e6a93df
placeholder color
andreadelrio Jul 28, 2021
6b7ad1c
remove more css
andreadelrio Jul 28, 2021
1605c24
fixing conflicts
andreadelrio Jul 28, 2021
ba7559f
prevent resizing of control upon selecting items
andreadelrio Jul 29, 2021
87b410f
feedback
andreadelrio Jul 30, 2021
be63947
Merge pull request #9 from andreadelrio/controls-dsgn-ii
ThomThomson Jul 30, 2021
a3578dc
fix scss lint error
ThomThomson Jul 30, 2021
60c9d56
Merge branch 'master' into controls/controlFrame
kibanamachine Aug 9, 2021
606555f
review feedback
ThomThomson Aug 10, 2021
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
1,953 changes: 1,953 additions & 0 deletions src/plugins/presentation_util/public/components/fixtures/flights.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { Story } from '@storybook/react';

const bar = '#c5ced8';
const panel = '#f7f9fa';
const background = '#e0e6ec';
const minHeight = 60;

const panelStyle = {
height: 165,
width: 400,
background: panel,
};

const kqlBarStyle = { background: bar, padding: 16, minHeight, fontStyle: 'italic' };

const inputBarStyle = { background: '#fff', padding: 4, minHeight };

const layout = (OptionStory: Story) => (
<EuiFlexGroup style={{ background }} direction="column">
<EuiFlexItem style={kqlBarStyle}>KQL Bar</EuiFlexItem>
<EuiFlexItem style={inputBarStyle}>
<OptionStory />
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup>
<EuiFlexItem style={panelStyle} />
<EuiFlexItem style={panelStyle} />
<EuiFlexItem style={panelStyle} />
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup>
<EuiFlexItem style={panelStyle} />
<EuiFlexItem style={panelStyle} />
<EuiFlexItem style={panelStyle} />
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
);

export const decorators = [layout];
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { map, uniq } from 'lodash';
import { EuiSelectableOption } from '@elastic/eui';

import { flights } from '../../fixtures/flights';

export type Flight = typeof flights[number];
export type FlightField = keyof Flight;

export const getOptions = (field: string) => uniq(map(flights, field)).sort();

export const getEuiSelectableOptions = (field: string, search?: string): EuiSelectableOption[] => {
const options = getOptions(field)
.map((option) => ({
label: option + '',
searchableLabel: option + '',
}))
.filter((option) => !search || option.label.toLowerCase().includes(search.toLowerCase()));
if (options.length > 10) options.length = 10;
return options;
};

export const flightFieldLabels: Record<FlightField, string> = {
AvgTicketPrice: 'Average Ticket Price',
Cancelled: 'Cancelled',
Carrier: 'Carrier',
dayOfWeek: 'Day of Week',
Dest: 'Destination',
DestAirportID: 'Destination Airport ID',
DestCityName: 'Destination City',
DestCountry: 'Destination Country',
DestLocation: 'Destination Location',
DestRegion: 'Destination Region',
DestWeather: 'Destination Weather',
DistanceKilometers: 'Distance (km)',
DistanceMiles: 'Distance (mi)',
FlightDelay: 'Flight Delay',
FlightDelayMin: 'Flight Delay (min)',
FlightDelayType: 'Flight Delay Type',
FlightNum: 'Flight Number',
FlightTimeHour: 'Flight Time (hr)',
FlightTimeMin: 'Flight Time (min)',
Origin: 'Origin',
OriginAirportID: 'Origin Airport ID',
OriginCityName: 'Origin City',
OriginCountry: 'Origin Country',
OriginLocation: 'Origin Location',
OriginRegion: 'Origin Region',
OriginWeather: 'Origin Weather',
timestamp: 'Timestamp',
};

export const flightFields = Object.keys(flightFieldLabels) as FlightField[];
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React, { useEffect, useMemo, useState } from 'react';

import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';

import { decorators } from './decorators';
import { getEuiSelectableOptions, flightFields, flightFieldLabels, FlightField } from './flights';
import { OptionsListEmbeddableFactory, OptionsListEmbeddable } from '../control_types/options_list';
import { ControlFrame } from '../control_frame/control_frame';

export default {
title: 'Input Controls',
description: '',
decorators,
};

interface OptionsListStorybookArgs {
fields: string[];
twoLine: boolean;
}

const storybookArgs = {
twoLine: false,
fields: ['OriginCityName', 'OriginWeather', 'DestCityName', 'DestWeather'],
};

const storybookArgTypes = {
fields: {
twoLine: {
control: { type: 'bool' },
},
control: {
type: 'check',
options: flightFields,
},
},
};

const OptionsListStoryComponent = ({ fields, twoLine }: OptionsListStorybookArgs) => {
const [embeddables, setEmbeddables] = useState<OptionsListEmbeddable[]>([]);

const optionsListEmbeddableFactory = useMemo(
() =>
new OptionsListEmbeddableFactory(
({ field, search }) =>
new Promise((r) => setTimeout(() => r(getEuiSelectableOptions(field, search)), 500))
),
[]
);

useEffect(() => {
const embeddableCreatePromises = fields.map((field) => {
return optionsListEmbeddableFactory.create({
field,
id: '',
indexPattern: '',
multiSelect: true,
twoLineLayout: twoLine,
title: flightFieldLabels[field as FlightField],
});
});
Promise.all(embeddableCreatePromises).then((newEmbeddables) => setEmbeddables(newEmbeddables));
}, [fields, optionsListEmbeddableFactory, twoLine]);

return (
<EuiFlexGroup alignItems="center" wrap={true} gutterSize={'s'}>
{embeddables.map((embeddable) => (
<EuiFlexItem key={embeddable.getInput().field}>
<ControlFrame twoLine={twoLine} embeddable={embeddable} />
</EuiFlexItem>
))}
</EuiFlexGroup>
);
};

export const OptionsListStory = ({ fields, twoLine }: OptionsListStorybookArgs) => (
<OptionsListStoryComponent fields={fields} twoLine={twoLine} />
);

OptionsListStory.args = storybookArgs;
OptionsListStory.argTypes = storybookArgTypes;
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.controlFrame--formControlLayout {
width: 100%;
min-width: $euiSize * 12.5;
}

.controlFrame--control {
&.optionsList--filterBtnSingle {
height: 100%;
}
}

.optionsList--filterBtnTwoLine {
width: 100%;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React, { useMemo } from 'react';
import useMount from 'react-use/lib/useMount';
import classNames from 'classnames';
import { EuiFormControlLayout, EuiFormLabel, EuiFormRow } from '@elastic/eui';

import { InputControlEmbeddable } from '../embeddable/types';

import './control_frame.scss';

interface ControlFrameProps {
embeddable: InputControlEmbeddable;
twoLine?: boolean;
}

export const ControlFrame = ({ twoLine, embeddable }: ControlFrameProps) => {
const embeddableRoot: React.RefObject<HTMLDivElement> = useMemo(() => React.createRef(), []);

useMount(() => {
if (embeddableRoot.current && embeddable) embeddable.render(embeddableRoot.current);
});

const form = (
<EuiFormControlLayout
className="controlFrame--formControlLayout"
fullWidth
prepend={
twoLine ? undefined : (
<EuiFormLabel htmlFor={embeddable.id}>{embeddable.getInput().title}</EuiFormLabel>
)
}
>
<div
className={classNames('controlFrame--control', {
'optionsList--filterBtnTwoLine': twoLine,
'optionsList--filterBtnSingle': !twoLine,
})}
id={embeddable.id}
ref={embeddableRoot}
/>
</EuiFormControlLayout>
);

return twoLine ? (
<EuiFormRow fullWidth label={embeddable.getInput().title}>
{form}
</EuiFormRow>
) : (
form
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

export { OptionsListEmbeddableFactory } from './options_list_embeddable_factory';
export { OptionsListEmbeddable } from './options_list_embeddable';
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
.optionsList--anchorOverride {
display:block;
}

.optionsList--popoverOverride {
width: 100%;
height: 100%;
}

.optionsList--items {
@include euiScrollBar;

overflow-y: auto;
max-height: $euiSize * 30;
width: $euiSize * 25;
max-width: 100%;
}

.optionsList--filterBtn {
.euiFilterButton__text-hasNotification {
flex-grow: 1;
justify-content: space-between;
width: 0;
}
&.optionsList--filterBtnSingle {
width: 100%;
}
&.optionsList--filterBtnPlaceholder {
.euiFilterButton__textShift {
color: $euiTextSubduedColor;
}
}
}

.optionsList--filterGroupSingle {
box-shadow: none;
height: 100%;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-top-right-radius: $euiBorderRadius - 1px;
border-bottom-right-radius: $euiBorderRadius - 1px;
}

.optionsList--filterGroup {
width: 100%;
}
Loading