GrafanaUI: InputGroup: Fix invalid children borders (#56169)

* GrafanaUI: InputGroup: Fix invalid children borders

* tidy story

* zindex priority stack

* clarify comment

* fix inputgroup children type
This commit is contained in:
Josh Hunt 2022-10-10 11:43:29 +01:00 committed by GitHub
parent a84edb274b
commit d405f3a877
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 102 additions and 8 deletions

View File

@ -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<typeof InputGroup> = {
title: 'Experimental/InputGroup',
component: InputGroup,
decorators: [withCenteredStory],
};
export function WithTextInputs() {
return (
<InputGroup>
<Input placeholder="One" />
<Input placeholder="Two" />
</InputGroup>
);
}
export function WithAccessoryButton() {
return (
<InputGroup>
<Select value={selectOptions[0]} options={selectOptions} onChange={() => {}} />
<AccessoryButton aria-label="Remove group by column" icon="times" variant="secondary" />
</InputGroup>
);
}
export function WithSelectsAndInput() {
return (
<Stack direction="column">
<InputGroup>
<Input invalid placeholder="LHS" />
<Select value={comparitorOptions[0]} options={comparitorOptions} onChange={() => {}} />
<Input placeholder="RHS" />
</InputGroup>
<InputGroup>
<Input placeholder="LHS" />
<Select invalid value={comparitorOptions[0]} options={comparitorOptions} onChange={() => {}} />
<Input placeholder="RHS" />
</InputGroup>
<InputGroup>
<Input placeholder="LHS" />
<Select value={comparitorOptions[0]} options={comparitorOptions} onChange={() => {}} />
<Input invalid placeholder="RHS" />
</InputGroup>
</Stack>
);
}
const selectOptions = [{ label: 'Prometheus', value: 1 }];
const comparitorOptions = [
{ label: '=', value: 1 },
{ label: '<', value: 2 },
{ label: '>', value: 3 },
];
export default meta;

View File

@ -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<InputGroupProps> = ({ children }) => {
export const InputGroup = ({ children }: InputGroupProps) => {
const styles = useStyles2(getStyles);
return <div className={styles.root}>{children}</div>;
// 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 <div className={styles.root}>{modifiedChildren}</div>;
};
// 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'),
}),
});