Commit 07bb6b8d by Oscar Kilhed Committed by GitHub

Grafana/UI: Add disable prop to Segment (#30539)

* Grafana/UI: Add disable prop to Segment

SegmentSync, SegmentAsync and SegmentInput had the disable prop inherited from HTMLProp but it did not disable the component. The disable prop should now disable the component.

* Use InlineLabel instead of span and remove some sass-classes in Segments

* Change MetricsQueryEditor test to reflect new layout of AsyncSegment

* Use useStyles hook to get themed styles for segment inputs

Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com>
parent fad81e16
...@@ -3,6 +3,9 @@ import { cx } from 'emotion'; ...@@ -3,6 +3,9 @@ import { cx } from 'emotion';
import _ from 'lodash'; import _ from 'lodash';
import { SelectableValue } from '@grafana/data'; import { SelectableValue } from '@grafana/data';
import { SegmentSelect, useExpandableLabel, SegmentProps } from './'; import { SegmentSelect, useExpandableLabel, SegmentProps } from './';
import { getSegmentStyles } from './styles';
import { InlineLabel } from '../Forms/InlineLabel';
import { useStyles } from '../../themes';
export interface SegmentSyncProps<T> extends SegmentProps<T>, Omit<HTMLProps<HTMLDivElement>, 'value' | 'onChange'> { export interface SegmentSyncProps<T> extends SegmentProps<T>, Omit<HTMLProps<HTMLDivElement>, 'value' | 'onChange'> {
value?: T | SelectableValue<T>; value?: T | SelectableValue<T>;
...@@ -18,19 +21,32 @@ export function Segment<T>({ ...@@ -18,19 +21,32 @@ export function Segment<T>({
className, className,
allowCustomValue, allowCustomValue,
placeholder, placeholder,
disabled,
...rest ...rest
}: React.PropsWithChildren<SegmentSyncProps<T>>) { }: React.PropsWithChildren<SegmentSyncProps<T>>) {
const [Label, width, expanded, setExpanded] = useExpandableLabel(false); const [Label, width, expanded, setExpanded] = useExpandableLabel(false);
const styles = useStyles(getSegmentStyles);
if (!expanded) { if (!expanded) {
const label = _.isObject(value) ? value.label : value; const label = _.isObject(value) ? value.label : value;
return ( return (
<Label <Label
disabled={disabled}
Component={ Component={
Component || ( Component || (
<a className={cx('gf-form-label', 'query-part', !value && placeholder && 'query-placeholder', className)}> <InlineLabel
className={cx(
styles.segment,
{
[styles.queryPlaceholder]: placeholder !== undefined && !value,
[styles.disabled]: disabled,
},
className
)}
>
{label || placeholder} {label || placeholder}
</a> </InlineLabel>
) )
} }
/> />
......
...@@ -6,6 +6,9 @@ import { SelectableValue } from '@grafana/data'; ...@@ -6,6 +6,9 @@ import { SelectableValue } from '@grafana/data';
import { useExpandableLabel, SegmentProps } from '.'; import { useExpandableLabel, SegmentProps } from '.';
import { useAsyncFn } from 'react-use'; import { useAsyncFn } from 'react-use';
import { AsyncState } from 'react-use/lib/useAsync'; import { AsyncState } from 'react-use/lib/useAsync';
import { getSegmentStyles } from './styles';
import { InlineLabel } from '../Forms/InlineLabel';
import { useStyles } from '../../themes';
export interface SegmentAsyncProps<T> extends SegmentProps<T>, Omit<HTMLProps<HTMLDivElement>, 'value' | 'onChange'> { export interface SegmentAsyncProps<T> extends SegmentProps<T>, Omit<HTMLProps<HTMLDivElement>, 'value' | 'onChange'> {
value?: T | SelectableValue<T>; value?: T | SelectableValue<T>;
...@@ -20,22 +23,35 @@ export function SegmentAsync<T>({ ...@@ -20,22 +23,35 @@ export function SegmentAsync<T>({
Component, Component,
className, className,
allowCustomValue, allowCustomValue,
disabled,
placeholder, placeholder,
...rest ...rest
}: React.PropsWithChildren<SegmentAsyncProps<T>>) { }: React.PropsWithChildren<SegmentAsyncProps<T>>) {
const [state, fetchOptions] = useAsyncFn(loadOptions, [loadOptions]); const [state, fetchOptions] = useAsyncFn(loadOptions, [loadOptions]);
const [Label, width, expanded, setExpanded] = useExpandableLabel(false); const [Label, width, expanded, setExpanded] = useExpandableLabel(false);
const styles = useStyles(getSegmentStyles);
if (!expanded) { if (!expanded) {
const label = _.isObject(value) ? value.label : value; const label = _.isObject(value) ? value.label : value;
return ( return (
<Label <Label
onClick={fetchOptions} onClick={fetchOptions}
disabled={disabled}
Component={ Component={
Component || ( Component || (
<a className={cx('gf-form-label', 'query-part', !value && placeholder && 'query-placeholder', className)}> <InlineLabel
className={cx(
styles.segment,
{
[styles.queryPlaceholder]: placeholder !== undefined && !value,
[styles.disabled]: disabled,
},
className
)}
>
{label || placeholder} {label || placeholder}
</a> </InlineLabel>
) )
} }
/> />
......
...@@ -3,6 +3,9 @@ import { cx, css } from 'emotion'; ...@@ -3,6 +3,9 @@ import { cx, css } from 'emotion';
import useClickAway from 'react-use/lib/useClickAway'; import useClickAway from 'react-use/lib/useClickAway';
import { measureText } from '../../utils/measureText'; import { measureText } from '../../utils/measureText';
import { useExpandableLabel, SegmentProps } from '.'; import { useExpandableLabel, SegmentProps } from '.';
import { getSegmentStyles } from './styles';
import { InlineLabel } from '../Forms/InlineLabel';
import { useStyles } from '../../themes';
export interface SegmentInputProps<T> extends SegmentProps<T>, Omit<HTMLProps<HTMLInputElement>, 'value' | 'onChange'> { export interface SegmentInputProps<T> extends SegmentProps<T>, Omit<HTMLProps<HTMLInputElement>, 'value' | 'onChange'> {
value: string | number; value: string | number;
...@@ -18,6 +21,7 @@ export function SegmentInput<T>({ ...@@ -18,6 +21,7 @@ export function SegmentInput<T>({
Component, Component,
className, className,
placeholder, placeholder,
disabled,
autofocus = false, autofocus = false,
...rest ...rest
}: React.PropsWithChildren<SegmentInputProps<T>>) { }: React.PropsWithChildren<SegmentInputProps<T>>) {
...@@ -25,6 +29,7 @@ export function SegmentInput<T>({ ...@@ -25,6 +29,7 @@ export function SegmentInput<T>({
const [value, setValue] = useState<number | string>(initialValue); const [value, setValue] = useState<number | string>(initialValue);
const [inputWidth, setInputWidth] = useState<number>(measureText((initialValue || '').toString(), FONT_SIZE).width); const [inputWidth, setInputWidth] = useState<number>(measureText((initialValue || '').toString(), FONT_SIZE).width);
const [Label, , expanded, setExpanded] = useExpandableLabel(autofocus); const [Label, , expanded, setExpanded] = useExpandableLabel(autofocus);
const styles = useStyles(getSegmentStyles);
useClickAway(ref, () => { useClickAway(ref, () => {
setExpanded(false); setExpanded(false);
...@@ -34,11 +39,21 @@ export function SegmentInput<T>({ ...@@ -34,11 +39,21 @@ export function SegmentInput<T>({
if (!expanded) { if (!expanded) {
return ( return (
<Label <Label
disabled={disabled}
Component={ Component={
Component || ( Component || (
<a className={cx('gf-form-label', 'query-part', !value && placeholder && 'query-placeholder', className)}> <InlineLabel
className={cx(
styles.segment,
{
[styles.queryPlaceholder]: placeholder !== undefined && !value,
[styles.disabled]: disabled,
},
className
)}
>
{initialValue || placeholder} {initialValue || placeholder}
</a> </InlineLabel>
) )
} }
/> />
......
import { GrafanaTheme } from '@grafana/data';
import { css } from 'emotion';
export const getSegmentStyles = (theme: GrafanaTheme) => {
return {
segment: css`
cursor: pointer;
width: auto;
`,
queryPlaceholder: css`
color: ${theme.palette.gray2};
`,
disabled: css`
cursor: not-allowed;
opacity: 0.65;
box-shadow: none;
`,
};
};
...@@ -5,4 +5,5 @@ export interface SegmentProps<T> { ...@@ -5,4 +5,5 @@ export interface SegmentProps<T> {
className?: string; className?: string;
allowCustomValue?: boolean; allowCustomValue?: boolean;
placeholder?: string; placeholder?: string;
disabled?: boolean;
} }
...@@ -3,6 +3,7 @@ import React, { useState, useRef, ReactElement } from 'react'; ...@@ -3,6 +3,7 @@ import React, { useState, useRef, ReactElement } from 'react';
interface LabelProps { interface LabelProps {
Component: ReactElement; Component: ReactElement;
onClick?: () => void; onClick?: () => void;
disabled?: boolean;
} }
export const useExpandableLabel = ( export const useExpandableLabel = (
...@@ -12,10 +13,13 @@ export const useExpandableLabel = ( ...@@ -12,10 +13,13 @@ export const useExpandableLabel = (
const [expanded, setExpanded] = useState<boolean>(initialExpanded); const [expanded, setExpanded] = useState<boolean>(initialExpanded);
const [width, setWidth] = useState(0); const [width, setWidth] = useState(0);
const Label: React.FC<LabelProps> = ({ Component, onClick }) => ( const Label: React.FC<LabelProps> = ({ Component, onClick, disabled }) => (
<div <div
ref={ref} ref={ref}
onClick={() => { onClick={
disabled
? undefined
: () => {
setExpanded(true); setExpanded(true);
if (ref && ref.current) { if (ref && ref.current) {
setWidth(ref.current.clientWidth * 1.25); setWidth(ref.current.clientWidth * 1.25);
...@@ -23,7 +27,8 @@ export const useExpandableLabel = ( ...@@ -23,7 +27,8 @@ export const useExpandableLabel = (
if (onClick) { if (onClick) {
onClick(); onClick();
} }
}} }
}
> >
{Component} {Component}
</div> </div>
......
...@@ -107,9 +107,9 @@ describe('QueryEditor', () => { ...@@ -107,9 +107,9 @@ describe('QueryEditor', () => {
const props = setup(); const props = setup();
props.query.region = (null as unknown) as string; props.query.region = (null as unknown) as string;
const wrapper = mount(<MetricsQueryEditor {...props} />); const wrapper = mount(<MetricsQueryEditor {...props} />);
expect(wrapper.find('.gf-form-inline').first().find('.gf-form-label.query-part').first().text()).toEqual( expect(
'default' wrapper.find('.gf-form-inline').first().find('Segment').find('InlineLabel').find('label').text()
); ).toEqual('default');
}); });
}); });
......
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