From d405f3a8774600243d3b29d4fd7055243b3aa475 Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Mon, 10 Oct 2022 11:43:29 +0100 Subject: [PATCH] GrafanaUI: InputGroup: Fix invalid children borders (#56169) * GrafanaUI: InputGroup: Fix invalid children borders * tidy story * zindex priority stack * clarify comment * fix inputgroup children type --- .../QueryEditor/InputGroup.story.internal.tsx | 65 +++++++++++++++++++ .../src/components/QueryEditor/InputGroup.tsx | 45 ++++++++++--- 2 files changed, 102 insertions(+), 8 deletions(-) create mode 100644 packages/grafana-ui/src/components/QueryEditor/InputGroup.story.internal.tsx diff --git a/packages/grafana-ui/src/components/QueryEditor/InputGroup.story.internal.tsx b/packages/grafana-ui/src/components/QueryEditor/InputGroup.story.internal.tsx new file mode 100644 index 00000000000..b3ddbeeeefc --- /dev/null +++ b/packages/grafana-ui/src/components/QueryEditor/InputGroup.story.internal.tsx @@ -0,0 +1,65 @@ +import { ComponentMeta } from '@storybook/react'; +import React from 'react'; + +import { Stack } from '..'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; +import { Input } from '../Input/Input'; +import { Select } from '../Select/Select'; + +import { AccessoryButton } from './AccessoryButton'; +import { InputGroup } from './InputGroup'; + +const meta: ComponentMeta = { + title: 'Experimental/InputGroup', + component: InputGroup, + decorators: [withCenteredStory], +}; + +export function WithTextInputs() { + return ( + + + + + ); +} + +export function WithAccessoryButton() { + return ( + + + + + + + + + + + + + + ); +} + +const selectOptions = [{ label: 'Prometheus', value: 1 }]; +const comparitorOptions = [ + { label: '=', value: 1 }, + { label: '<', value: 2 }, + { label: '>', value: 3 }, +]; + +export default meta; diff --git a/packages/grafana-ui/src/components/QueryEditor/InputGroup.tsx b/packages/grafana-ui/src/components/QueryEditor/InputGroup.tsx index b90b5b4b0d1..04dc192e9f9 100644 --- a/packages/grafana-ui/src/components/QueryEditor/InputGroup.tsx +++ b/packages/grafana-ui/src/components/QueryEditor/InputGroup.tsx @@ -1,16 +1,39 @@ -import { css } from '@emotion/css'; -import React from 'react'; +import { css, cx } from '@emotion/css'; +import React, { Children, cloneElement, isValidElement, ReactElement } from 'react'; import { useStyles2 } from '../../themes'; -interface InputGroupProps {} +type Child = string | undefined | ReactElement<{ className?: string; invalid?: unknown }>; +interface InputGroupProps { + // we type the children props so we can test them later on + children: Child | Child[]; +} -export const InputGroup: React.FC = ({ children }) => { +export const InputGroup = ({ children }: InputGroupProps) => { const styles = useStyles2(getStyles); - return
{children}
; + // Find children with an invalid prop, and set a class name to raise their z-index so all + // of the invalid border is visible + const modifiedChildren = Children.map(children, (child) => { + if (isValidElement(child) && child.props.invalid) { + return cloneElement(child, { className: cx(child.props.className, styles.invalidChild) }); + } + + return child; + }); + + return
{modifiedChildren}
; }; +// The later in the array the higher the priority for showing that element's border +const borderPriority = [ + '' as const, // lowest priority + 'base' as const, + 'hovered' as const, + 'invalid' as const, + 'focused' as const, // highest priority +]; + const getStyles = () => ({ root: css({ display: 'flex', @@ -38,14 +61,20 @@ const getStyles = () => ({ // position: 'relative', - zIndex: 1, + zIndex: borderPriority.indexOf('base'), + // Adjacent borders are overlapping, so raise children up when hovering etc + // so all that child's borders are visible. '&:hover': { - zIndex: 2, + zIndex: borderPriority.indexOf('hovered'), }, '&:focus-within': { - zIndex: 2, + zIndex: borderPriority.indexOf('focused'), }, }, }), + + invalidChild: css({ + zIndex: borderPriority.indexOf('invalid'), + }), });