mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
a84edb274b
commit
d405f3a877
@ -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;
|
@ -1,16 +1,39 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import React from 'react';
|
import React, { Children, cloneElement, isValidElement, ReactElement } from 'react';
|
||||||
|
|
||||||
import { useStyles2 } from '../../themes';
|
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);
|
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 = () => ({
|
const getStyles = () => ({
|
||||||
root: css({
|
root: css({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -38,14 +61,20 @@ const getStyles = () => ({
|
|||||||
|
|
||||||
//
|
//
|
||||||
position: 'relative',
|
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': {
|
'&:hover': {
|
||||||
zIndex: 2,
|
zIndex: borderPriority.indexOf('hovered'),
|
||||||
},
|
},
|
||||||
'&:focus-within': {
|
'&:focus-within': {
|
||||||
zIndex: 2,
|
zIndex: borderPriority.indexOf('focused'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
invalidChild: css({
|
||||||
|
zIndex: borderPriority.indexOf('invalid'),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user