Commit 4483bcad by kay delaney Committed by GitHub

Chore: Move and wrap Cascader component to @grafana/ui (#20246)

* Chore: Move and wrap Cascader component to @grafana/ui
Closes #19042

* Removes unneeded props from interface and removes rc-trigger

* Removes more unneeded props
parent 2ca1cc56
...@@ -39,6 +39,7 @@ ...@@ -39,6 +39,7 @@
"lodash": "4.17.15", "lodash": "4.17.15",
"moment": "2.24.0", "moment": "2.24.0",
"papaparse": "4.6.3", "papaparse": "4.6.3",
"rc-cascader": "0.17.5",
"rc-drawer": "3.0.2", "rc-drawer": "3.0.2",
"rc-time-picker": "^3.7.2", "rc-time-picker": "^3.7.2",
"react": "16.8.6", "react": "16.8.6",
......
import React from 'react';
import { storiesOf } from '@storybook/react';
import { text, boolean, object } from '@storybook/addon-knobs';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { Cascader } from './Cascader';
const getKnobs = () => {
return {
disabled: boolean('Disabled', false),
text: text('Button Text', 'Click me!'),
options: object('Options', [
{ label: 'A', value: 'A', children: [{ label: 'B', value: 'B' }, { label: 'C', value: 'C' }] },
{ label: 'D', value: 'D' },
]),
};
};
const CascaderStories = storiesOf('UI/Cascader', module);
CascaderStories.addDecorator(withCenteredStory);
CascaderStories.add('default', () => {
const { disabled, text, options } = getKnobs();
return <Cascader disabled={disabled} options={options} value={['A']} expandIcon={null} buttonText={text} />;
});
import React from 'react';
// @ts-ignore
import RCCascader from 'rc-cascader';
export interface CascaderOption {
label: string;
value: string;
children?: CascaderOption[];
disabled?: boolean;
}
export interface CascaderProps {
options: CascaderOption[];
buttonText: string;
disabled?: boolean;
expandIcon?: React.ReactNode;
value?: string[];
loadData?: (selectedOptions: CascaderOption[]) => void;
onChange?: (value: string[], selectedOptions: CascaderOption[]) => void;
onPopupVisibleChange?: (visible: boolean) => void;
}
export const Cascader: React.FC<CascaderProps> = props => (
<RCCascader {...props}>
<button className="gf-form-label gf-form-label--btn" disabled={props.disabled}>
{props.buttonText} <i className="fa fa-caret-down" />
</button>
</RCCascader>
);
.rc-cascader { .rc-cascader {
font-size: 12px; font-size: 12px;
&-menus {
font-size: 12px;
overflow: hidden;
background: $panel-bg;
position: absolute;
border: $panel-border;
border-radius: $border-radius;
box-shadow: $typeahead-shadow;
white-space: nowrap;
&-hidden {
display: none;
}
&.slide-up-enter,
&.slide-up-appear {
animation-duration: 0.3s;
animation-fill-mode: both;
transform-origin: 0 0;
opacity: 0;
animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1);
animation-play-state: paused;
}
&.slide-up-enter.slide-up-enter-active.rc-cascader-menus-placement,
&.slide-up-appear.slide-up-appear-active.rc-cascader-menus-placement {
&-bottomLeft {
animation-name: SlideUpIn;
animation-play-state: running;
}
&-topLeft {
animation-name: SlideDownIn;
animation-play-state: running;
}
}
&.slide-up-leave {
animation-duration: 0.3s;
animation-fill-mode: both;
transform-origin: 0 0;
opacity: 1;
animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34);
animation-play-state: paused;
&.slide-up-leave-active.rc-cascader-menus-placement {
&-bottomLeft {
animation-name: SlideUpOut;
animation-play-state: running;
}
&-topLeft {
animation-name: SlideDownOut;
animation-play-state: running;
}
}
}
}
&-menu {
display: inline-block;
/* width: 100px; */
max-width: 50vw;
height: 192px;
list-style: none;
margin: 0;
padding: 0;
border-right: $panel-border;
overflow: auto;
&:last-child {
border-right: 0;
}
&-item {
height: 32px;
line-height: 32px;
padding: 0 2.5em 0 16px;
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: all 0.3s ease;
position: relative;
&:hover {
background: $typeahead-selected-bg;
}
&-disabled {
cursor: not-allowed;
color: $text-color-weak;
&:hover {
background: transparent;
}
&:after {
position: absolute;
right: 12px;
content: 'loading';
color: $text-color-weak;
font-style: italic;
}
}
&-active {
color: $typeahead-selected-color;
background: $typeahead-selected-bg;
&:hover {
color: $typeahead-selected-color;
background: $typeahead-selected-bg;
}
}
&-expand {
position: relative;
&:after {
content: '>';
font-size: 12px;
color: $text-color-weak;
position: absolute;
right: 16px;
line-height: 32px;
}
}
}
}
} }
.rc-cascader-menus {
font-size: 12px;
overflow: hidden;
background: $panel-bg;
position: absolute;
border: $panel-border;
border-radius: $border-radius;
box-shadow: $typeahead-shadow;
white-space: nowrap;
}
.rc-cascader-menus-hidden {
display: none;
}
.rc-cascader-menus.slide-up-enter,
.rc-cascader-menus.slide-up-appear {
animation-duration: 0.3s;
animation-fill-mode: both;
transform-origin: 0 0;
opacity: 0;
animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1);
animation-play-state: paused;
}
.rc-cascader-menus.slide-up-leave {
animation-duration: 0.3s;
animation-fill-mode: both;
transform-origin: 0 0;
opacity: 1;
animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34);
animation-play-state: paused;
}
.rc-cascader-menus.slide-up-enter.slide-up-enter-active.rc-cascader-menus-placement-bottomLeft,
.rc-cascader-menus.slide-up-appear.slide-up-appear-active.rc-cascader-menus-placement-bottomLeft {
animation-name: SlideUpIn;
animation-play-state: running;
}
.rc-cascader-menus.slide-up-enter.slide-up-enter-active.rc-cascader-menus-placement-topLeft,
.rc-cascader-menus.slide-up-appear.slide-up-appear-active.rc-cascader-menus-placement-topLeft {
animation-name: SlideDownIn;
animation-play-state: running;
}
.rc-cascader-menus.slide-up-leave.slide-up-leave-active.rc-cascader-menus-placement-bottomLeft {
animation-name: SlideUpOut;
animation-play-state: running;
}
.rc-cascader-menus.slide-up-leave.slide-up-leave-active.rc-cascader-menus-placement-topLeft {
animation-name: SlideDownOut;
animation-play-state: running;
}
.rc-cascader-menu {
display: inline-block;
/* width: 100px; */
max-width: 50vw;
height: 192px;
list-style: none;
margin: 0;
padding: 0;
border-right: $panel-border;
overflow: auto;
}
.rc-cascader-menu:last-child {
border-right: 0;
}
.rc-cascader-menu-item {
height: 32px;
line-height: 32px;
padding: 0 2.5em 0 16px;
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: all 0.3s ease;
position: relative;
}
.rc-cascader-menu-item:hover {
background: $typeahead-selected-bg;
}
.rc-cascader-menu-item-disabled {
cursor: not-allowed;
color: $text-color-weak;
}
.rc-cascader-menu-item-disabled:hover {
background: transparent;
}
.rc-cascader-menu-item-loading:after {
position: absolute;
right: 12px;
content: 'loading';
color: $text-color-weak;
font-style: italic;
}
.rc-cascader-menu-item-active {
color: $typeahead-selected-color;
background: $typeahead-selected-bg;
}
.rc-cascader-menu-item-active:hover {
color: $typeahead-selected-color;
background: $typeahead-selected-bg;
}
.rc-cascader-menu-item-expand {
position: relative;
}
.rc-cascader-menu-item-expand:after {
content: '>';
font-size: 12px;
color: $text-color-weak;
position: absolute;
right: 16px;
line-height: 32px;
}
@keyframes SlideUpIn { @keyframes SlideUpIn {
0% { 0% {
opacity: 0; opacity: 0;
transform-origin: 0% 0%; transform-origin: 0% 0%;
transform: scaleY(0.8); transform: scaleY(0.8);
} }
100% { 100% {
opacity: 1; opacity: 1;
transform-origin: 0% 0%; transform-origin: 0% 0%;
transform: scaleY(1); transform: scaleY(1);
} }
} }
@keyframes SlideUpOut { @keyframes SlideUpOut {
0% { 0% {
opacity: 1; opacity: 1;
transform-origin: 0% 0%; transform-origin: 0% 0%;
transform: scaleY(1); transform: scaleY(1);
} }
100% { 100% {
opacity: 0; opacity: 0;
transform-origin: 0% 0%; transform-origin: 0% 0%;
transform: scaleY(0.8); transform: scaleY(0.8);
} }
} }
@keyframes SlideDownIn { @keyframes SlideDownIn {
0% { 0% {
opacity: 0; opacity: 0;
transform-origin: 0% 100%; transform-origin: 0% 100%;
transform: scaleY(0.8); transform: scaleY(0.8);
} }
100% { 100% {
opacity: 1; opacity: 1;
transform-origin: 0% 100%; transform-origin: 0% 100%;
transform: scaleY(1); transform: scaleY(1);
} }
} }
@keyframes SlideDownOut { @keyframes SlideDownOut {
0% { 0% {
opacity: 1; opacity: 1;
transform-origin: 0% 100%; transform-origin: 0% 100%;
transform: scaleY(1); transform: scaleY(1);
} }
100% { 100% {
opacity: 0; opacity: 0;
transform-origin: 0% 100%; transform-origin: 0% 100%;
......
@import 'BarGauge/BarGauge';
@import 'Cascader/Cascader';
@import 'ColorPicker/ColorPicker';
@import 'CustomScrollbar/CustomScrollbar'; @import 'CustomScrollbar/CustomScrollbar';
@import 'DeleteButton/DeleteButton'; @import 'DeleteButton/DeleteButton';
@import 'ThresholdsEditor/ThresholdsEditor'; @import 'Drawer/Drawer';
@import 'Table/Table';
@import 'Table/TableInputCSV';
@import 'Tooltip/Tooltip';
@import 'Select/Select';
@import 'PanelOptionsGroup/PanelOptionsGroup';
@import 'PanelOptionsGrid/PanelOptionsGrid';
@import 'ColorPicker/ColorPicker';
@import 'ValueMappingsEditor/ValueMappingsEditor';
@import 'EmptySearchResult/EmptySearchResult'; @import 'EmptySearchResult/EmptySearchResult';
@import 'FormField/FormField'; @import 'FormField/FormField';
@import 'BarGauge/BarGauge'; @import 'PanelOptionsGrid/PanelOptionsGrid';
@import 'PanelOptionsGroup/PanelOptionsGroup';
@import 'RefreshPicker/RefreshPicker'; @import 'RefreshPicker/RefreshPicker';
@import 'TimePicker/TimePicker'; @import 'Select/Select';
@import 'Table/Table';
@import 'Table/TableInputCSV';
@import 'ThresholdsEditor/ThresholdsEditor';
@import 'TimePicker/TimeOfDayPicker'; @import 'TimePicker/TimeOfDayPicker';
@import 'Drawer/Drawer'; @import 'TimePicker/TimePicker';
@import 'Tooltip/Tooltip';
@import 'ValueMappingsEditor/ValueMappingsEditor';
...@@ -13,6 +13,7 @@ export { IndicatorsContainer } from './Select/IndicatorsContainer'; ...@@ -13,6 +13,7 @@ export { IndicatorsContainer } from './Select/IndicatorsContainer';
export { NoOptionsMessage } from './Select/NoOptionsMessage'; export { NoOptionsMessage } from './Select/NoOptionsMessage';
export { default as resetSelectStyles } from './Select/resetSelectStyles'; export { default as resetSelectStyles } from './Select/resetSelectStyles';
export { ButtonSelect } from './Select/ButtonSelect'; export { ButtonSelect } from './Select/ButtonSelect';
export { Cascader, CascaderOption } from './Cascader/Cascader';
// Forms // Forms
export { FormLabel } from './FormLabel/FormLabel'; export { FormLabel } from './FormLabel/FormLabel';
......
import React from 'react'; import React from 'react';
import { ExploreQueryFieldProps } from '@grafana/data'; import { ExploreQueryFieldProps } from '@grafana/data';
// @ts-ignore import { Cascader, CascaderOption } from '@grafana/ui';
import Cascader from 'rc-cascader';
import InfluxQueryModel from '../influx_query_model'; import InfluxQueryModel from '../influx_query_model';
import { AdHocFilterField, KeyValuePair } from 'app/features/explore/AdHocFilterField'; import { AdHocFilterField, KeyValuePair } from 'app/features/explore/AdHocFilterField';
...@@ -9,7 +8,6 @@ import { TemplateSrv } from 'app/features/templating/template_srv'; ...@@ -9,7 +8,6 @@ import { TemplateSrv } from 'app/features/templating/template_srv';
import InfluxDatasource from '../datasource'; import InfluxDatasource from '../datasource';
import { InfluxQueryBuilder } from '../query_builder'; import { InfluxQueryBuilder } from '../query_builder';
import { InfluxQuery, InfluxOptions } from '../types'; import { InfluxQuery, InfluxOptions } from '../types';
import { CascaderOption } from '../../loki/components/LokiQueryFieldForm';
export interface Props extends ExploreQueryFieldProps<InfluxDatasource, InfluxQuery, InfluxOptions> {} export interface Props extends ExploreQueryFieldProps<InfluxDatasource, InfluxQuery, InfluxOptions> {}
...@@ -139,15 +137,13 @@ export class InfluxLogsQueryField extends React.PureComponent<Props, State> { ...@@ -139,15 +137,13 @@ export class InfluxLogsQueryField extends React.PureComponent<Props, State> {
<div className="gf-form-inline gf-form-inline--nowrap"> <div className="gf-form-inline gf-form-inline--nowrap">
<div className="gf-form flex-shrink-0"> <div className="gf-form flex-shrink-0">
<Cascader <Cascader
buttonText={cascadeText}
options={measurements} options={measurements}
disabled={!hasMeasurement}
value={[measurement, field]} value={[measurement, field]}
onChange={this.onMeasurementsChange} onChange={this.onMeasurementsChange}
expandIcon={null} expandIcon={null}
> />
<button className="gf-form-label gf-form-label--btn" disabled={!hasMeasurement}>
{cascadeText} <i className="fa fa-caret-down" />
</button>
</Cascader>
</div> </div>
<div className="flex-shrink-1 flex-flow-column-nowrap"> <div className="flex-shrink-1 flex-flow-column-nowrap">
{measurement && ( {measurement && (
......
// Libraries // Libraries
import React from 'react'; import React from 'react';
// @ts-ignore
import Cascader from 'rc-cascader';
import { SlatePrism, TypeaheadOutput, SuggestionsState, QueryField, TypeaheadInput, BracesPlugin } from '@grafana/ui'; import {
Cascader,
CascaderOption,
SlatePrism,
TypeaheadOutput,
SuggestionsState,
QueryField,
TypeaheadInput,
BracesPlugin,
} from '@grafana/ui';
// Utils & Services // Utils & Services
// dom also includes Element polyfills // dom also includes Element polyfills
...@@ -54,17 +61,10 @@ function willApplySuggestion(suggestion: string, { typeaheadContext, typeaheadTe ...@@ -54,17 +61,10 @@ function willApplySuggestion(suggestion: string, { typeaheadContext, typeaheadTe
return suggestion; return suggestion;
} }
export interface CascaderOption {
label: string;
value: string;
children?: CascaderOption[];
disabled?: boolean;
}
export interface LokiQueryFieldFormProps extends ExploreQueryFieldProps<LokiDatasource, LokiQuery> { export interface LokiQueryFieldFormProps extends ExploreQueryFieldProps<LokiDatasource, LokiQuery> {
history: LokiHistoryItem[]; history: LokiHistoryItem[];
syntax: Grammar; syntax: Grammar;
logLabelOptions: any[]; logLabelOptions: CascaderOption[];
syntaxLoaded: boolean; syntaxLoaded: boolean;
absoluteRange: AbsoluteTimeRange; absoluteRange: AbsoluteTimeRange;
onLoadOptions: (selectedOptions: CascaderOption[]) => void; onLoadOptions: (selectedOptions: CascaderOption[]) => void;
...@@ -150,19 +150,13 @@ export class LokiQueryFieldForm extends React.PureComponent<LokiQueryFieldFormPr ...@@ -150,19 +150,13 @@ export class LokiQueryFieldForm extends React.PureComponent<LokiQueryFieldFormPr
<div className="gf-form"> <div className="gf-form">
<Cascader <Cascader
options={logLabelOptions || []} options={logLabelOptions || []}
disabled={buttonDisabled}
buttonText={chooserText}
onChange={this.onChangeLogLabels} onChange={this.onChangeLogLabels}
loadData={onLoadOptions} loadData={onLoadOptions}
expandIcon={null} expandIcon={null}
onPopupVisibleChange={(isVisible: boolean) => { onPopupVisibleChange={isVisible => isVisible && onLabelsRefresh && onLabelsRefresh()}
if (isVisible && onLabelsRefresh) { />
onLabelsRefresh();
}
}}
>
<button className="gf-form-label gf-form-label--btn" disabled={buttonDisabled}>
{chooserText} <i className="fa fa-caret-down" />
</button>
</Cascader>
</div> </div>
<div className="gf-form gf-form--grow"> <div className="gf-form gf-form--grow">
<QueryField <QueryField
......
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { AbsoluteTimeRange } from '@grafana/data'; import { AbsoluteTimeRange } from '@grafana/data';
import { CascaderOption } from '@grafana/ui';
import LokiLanguageProvider from 'app/plugins/datasource/loki/language_provider'; import LokiLanguageProvider from 'app/plugins/datasource/loki/language_provider';
import { CascaderOption } from 'app/plugins/datasource/loki/components/LokiQueryFieldForm';
import { useRefMounted } from 'app/core/hooks/useRefMounted'; import { useRefMounted } from 'app/core/hooks/useRefMounted';
/** /**
......
import { renderHook, act } from 'react-hooks-testing-library'; import { renderHook, act } from 'react-hooks-testing-library';
import { AbsoluteTimeRange } from '@grafana/data'; import { AbsoluteTimeRange } from '@grafana/data';
import { CascaderOption } from '@grafana/ui';
import LanguageProvider from 'app/plugins/datasource/loki/language_provider'; import LanguageProvider from 'app/plugins/datasource/loki/language_provider';
import { useLokiSyntax } from './useLokiSyntax'; import { useLokiSyntax } from './useLokiSyntax';
import { CascaderOption } from 'app/plugins/datasource/loki/components/LokiQueryFieldForm';
import { makeMockLokiDatasource } from '../mocks'; import { makeMockLokiDatasource } from '../mocks';
describe('useLokiSyntax hook', () => { describe('useLokiSyntax hook', () => {
......
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import Prism from 'prismjs'; import Prism from 'prismjs';
import { AbsoluteTimeRange } from '@grafana/data'; import { AbsoluteTimeRange } from '@grafana/data';
import { CascaderOption } from '@grafana/ui';
import LokiLanguageProvider from 'app/plugins/datasource/loki/language_provider'; import LokiLanguageProvider from 'app/plugins/datasource/loki/language_provider';
import { useLokiLabels } from 'app/plugins/datasource/loki/components/useLokiLabels'; import { useLokiLabels } from 'app/plugins/datasource/loki/components/useLokiLabels';
import { CascaderOption } from 'app/plugins/datasource/loki/components/LokiQueryFieldForm';
import { useRefMounted } from 'app/core/hooks/useRefMounted'; import { useRefMounted } from 'app/core/hooks/useRefMounted';
const PRISM_SYNTAX = 'promql'; const PRISM_SYNTAX = 'promql';
......
import _ from 'lodash'; import _ from 'lodash';
import React from 'react'; import React from 'react';
// @ts-ignore
import Cascader from 'rc-cascader';
import { Plugin } from 'slate'; import { Plugin } from 'slate';
import { SlatePrism, TypeaheadInput, TypeaheadOutput, QueryField, BracesPlugin } from '@grafana/ui'; import {
Cascader,
CascaderOption,
SlatePrism,
TypeaheadInput,
TypeaheadOutput,
QueryField,
BracesPlugin,
} from '@grafana/ui';
import Prism from 'prismjs'; import Prism from 'prismjs';
...@@ -93,13 +99,6 @@ export function willApplySuggestion(suggestion: string, { typeaheadContext, type ...@@ -93,13 +99,6 @@ export function willApplySuggestion(suggestion: string, { typeaheadContext, type
return suggestion; return suggestion;
} }
interface CascaderOption {
label: string;
value: string;
children?: CascaderOption[];
disabled?: boolean;
}
interface PromQueryFieldProps extends ExploreQueryFieldProps<PrometheusDatasource, PromQuery, PromOptions> { interface PromQueryFieldProps extends ExploreQueryFieldProps<PrometheusDatasource, PromQuery, PromOptions> {
history: Array<HistoryItem<PromQuery>>; history: Array<HistoryItem<PromQuery>>;
} }
...@@ -284,11 +283,13 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF ...@@ -284,11 +283,13 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
<> <>
<div className="gf-form-inline gf-form-inline--nowrap"> <div className="gf-form-inline gf-form-inline--nowrap">
<div className="gf-form flex-shrink-0"> <div className="gf-form flex-shrink-0">
<Cascader options={metricsOptions} onChange={this.onChangeMetrics} expandIcon={null}> <Cascader
<button className="gf-form-label gf-form-label--btn" disabled={buttonDisabled}> options={metricsOptions}
{chooserText} <i className="fa fa-caret-down" /> buttonText={chooserText}
</button> disabled={buttonDisabled}
</Cascader> onChange={this.onChangeMetrics}
expandIcon={null}
/>
</div> </div>
<div className="gf-form gf-form--grow flex-shrink-1"> <div className="gf-form gf-form--grow flex-shrink-1">
<QueryField <QueryField
......
// DEPENDENCIES // DEPENDENCIES
@import '../../node_modules/react-table/react-table.css'; @import '../../node_modules/react-table/react-table.css';
// VENDOR
@import '../vendor/css/rc-cascader.scss';
// MIXINS // MIXINS
@import 'mixins/mixins'; @import 'mixins/mixins';
@import 'mixins/animations'; @import 'mixins/animations';
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment