mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Chore: Add new CloudWatch ui components (#41484)
* add new ui components * pr feedback * remove leftover comment
This commit is contained in:
@@ -0,0 +1,23 @@
|
|||||||
|
import { css, cx } from '@emotion/css';
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { stylesFactory, useTheme2 } from '@grafana/ui';
|
||||||
|
import { Button, ButtonProps } from '@grafana/ui/src/components/Button';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface AccessoryButtonProps extends ButtonProps {}
|
||||||
|
|
||||||
|
const AccessoryButton: React.FC<AccessoryButtonProps> = ({ className, ...props }) => {
|
||||||
|
const theme = useTheme2();
|
||||||
|
const styles = getButtonStyles(theme);
|
||||||
|
|
||||||
|
return <Button {...props} className={cx(className, styles.button)} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getButtonStyles = stylesFactory((theme: GrafanaTheme2) => ({
|
||||||
|
button: css({
|
||||||
|
paddingLeft: theme.spacing(3 / 2),
|
||||||
|
paddingRight: theme.spacing(3 / 2),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default AccessoryButton;
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { Field, Icon, PopoverContent, stylesFactory, Tooltip, useTheme2 } from '@grafana/ui';
|
||||||
|
import { getChildId } from '@grafana/ui/src/utils/children';
|
||||||
|
import { Space } from 'app/plugins/datasource/grafana-azure-monitor-datasource/components/Space';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface EditorFieldProps {
|
||||||
|
label: string;
|
||||||
|
children: React.ReactElement;
|
||||||
|
width?: number;
|
||||||
|
optional?: boolean;
|
||||||
|
tooltip?: PopoverContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EditorField: React.FC<EditorFieldProps> = (props) => {
|
||||||
|
const { label, optional, tooltip, children } = props;
|
||||||
|
|
||||||
|
const theme = useTheme2();
|
||||||
|
const styles = getStyles(theme, props);
|
||||||
|
const childInputId = getChildId(children);
|
||||||
|
|
||||||
|
const labelEl = (
|
||||||
|
<>
|
||||||
|
<label className={styles.label} htmlFor={childInputId}>
|
||||||
|
{label}
|
||||||
|
{optional && <span className={styles.optional}> - optional</span>}
|
||||||
|
{tooltip && (
|
||||||
|
<Tooltip placement="top" content={tooltip} theme="info">
|
||||||
|
<Icon name="info-circle" size="sm" className={styles.icon} />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
<Space v={0.5} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.root}>
|
||||||
|
<Field className={styles.field} label={labelEl}>
|
||||||
|
<div className={styles.child}>{children}</div>
|
||||||
|
</Field>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditorField;
|
||||||
|
|
||||||
|
const getStyles = stylesFactory((theme: GrafanaTheme2, props: EditorFieldProps) => {
|
||||||
|
return {
|
||||||
|
root: css({
|
||||||
|
minWidth: theme.spacing(props.width ?? 0),
|
||||||
|
}),
|
||||||
|
label: css({
|
||||||
|
fontSize: 12,
|
||||||
|
}),
|
||||||
|
optional: css({
|
||||||
|
fontStyle: 'italic',
|
||||||
|
color: theme.colors.text.secondary,
|
||||||
|
}),
|
||||||
|
field: css({
|
||||||
|
marginBottom: 0, // GrafanaUI/Field has a bottom margin which we must remove
|
||||||
|
}),
|
||||||
|
|
||||||
|
// TODO: really poor hack to align the switch
|
||||||
|
// Find a better solution to this
|
||||||
|
child: css({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
minHeight: 30,
|
||||||
|
}),
|
||||||
|
icon: css({
|
||||||
|
color: theme.colors.text.secondary,
|
||||||
|
marginLeft: theme.spacing(1),
|
||||||
|
':hover': {
|
||||||
|
color: theme.colors.text.primary,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Stack from './Stack';
|
||||||
|
|
||||||
|
interface EditorFieldGroupProps {}
|
||||||
|
|
||||||
|
const EditorFieldGroup: React.FC<EditorFieldGroupProps> = ({ children }) => {
|
||||||
|
return <Stack gap={1}>{children}</Stack>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditorFieldGroup;
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { stylesFactory, useTheme2 } from '@grafana/ui';
|
||||||
|
import React from 'react';
|
||||||
|
import Stack from './Stack';
|
||||||
|
|
||||||
|
interface EditorHeaderProps {}
|
||||||
|
|
||||||
|
const EditorHeader: React.FC<EditorHeaderProps> = ({ children }) => {
|
||||||
|
const theme = useTheme2();
|
||||||
|
const styles = getStyles(theme);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.root}>
|
||||||
|
<Stack gap={3} alignItems="center">
|
||||||
|
{children}
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditorHeader;
|
||||||
|
|
||||||
|
const getStyles = stylesFactory((theme: GrafanaTheme2) => ({
|
||||||
|
root: css({
|
||||||
|
padding: theme.spacing(0, 1),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { Button } from '@grafana/ui';
|
||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import Stack from './Stack';
|
||||||
|
|
||||||
|
interface EditorListProps<T> {
|
||||||
|
items: T[];
|
||||||
|
renderItem: (item: Partial<T>, onChangeItem: (item: T) => void, onDeleteItem: () => void) => React.ReactElement;
|
||||||
|
onChange: (items: Array<Partial<T>>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function EditorList<T>({ items, renderItem, onChange }: EditorListProps<T>) {
|
||||||
|
const onAddItem = () => {
|
||||||
|
const newItems = [...items, {}];
|
||||||
|
|
||||||
|
onChange(newItems);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onChangeItem = (itemIndex: number, newItem: T) => {
|
||||||
|
const newItems = [...items];
|
||||||
|
newItems[itemIndex] = newItem;
|
||||||
|
onChange(newItems);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDeleteItem = (itemIndex: number) => {
|
||||||
|
const newItems = [...items];
|
||||||
|
newItems.splice(itemIndex, 1);
|
||||||
|
onChange(newItems);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
{items.map((item, index) => (
|
||||||
|
<Fragment key={index}>
|
||||||
|
{renderItem(
|
||||||
|
item,
|
||||||
|
(newItem) => onChangeItem(index, newItem),
|
||||||
|
() => onDeleteItem(index)
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
<Button onClick={onAddItem} variant="secondary" size="md" icon="plus" aria-label="Add" />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditorList;
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { stylesFactory, useTheme2 } from '@grafana/ui';
|
||||||
|
import React from 'react';
|
||||||
|
import Stack from './Stack';
|
||||||
|
|
||||||
|
interface EditorRowProps {}
|
||||||
|
|
||||||
|
const EditorRow: React.FC<EditorRowProps> = ({ children }) => {
|
||||||
|
const theme = useTheme2();
|
||||||
|
const styles = getStyles(theme);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.root}>
|
||||||
|
<Stack gap={4}>{children}</Stack>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditorRow;
|
||||||
|
|
||||||
|
const getStyles = stylesFactory((theme: GrafanaTheme2) => {
|
||||||
|
return {
|
||||||
|
root: css({
|
||||||
|
padding: theme.spacing(1),
|
||||||
|
backgroundColor: theme.colors.background.secondary,
|
||||||
|
borderRadius: theme.shape.borderRadius(1),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Stack from './Stack';
|
||||||
|
|
||||||
|
interface EditorRowsProps {}
|
||||||
|
|
||||||
|
const EditorRows: React.FC<EditorRowsProps> = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<Stack gap={0.5} direction="column">
|
||||||
|
{children}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditorRows;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface FlexItemProps {
|
||||||
|
grow?: number;
|
||||||
|
shrink?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FlexItem: React.FC<FlexItemProps> = ({ grow, shrink }) => {
|
||||||
|
return <div style={{ display: 'block', flexGrow: grow, flexShrink: shrink }} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FlexItem;
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { css, cx } from '@emotion/css';
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { Select, stylesFactory, useTheme2 } from '@grafana/ui';
|
||||||
|
import {
|
||||||
|
ContainerProps,
|
||||||
|
SelectContainer as BaseSelectContainer,
|
||||||
|
} from '@grafana/ui/src/components/Select/SelectContainer';
|
||||||
|
import { SelectCommonProps } from '@grafana/ui/src/components/Select/types';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { GroupTypeBase } from 'react-select';
|
||||||
|
|
||||||
|
interface InlineSelectProps<T> extends SelectCommonProps<T> {
|
||||||
|
label?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function InlineSelect<T>({ label: labelProp, ...props }: InlineSelectProps<T>) {
|
||||||
|
const theme = useTheme2();
|
||||||
|
const [id] = useState(() => Math.random().toString(16).slice(2));
|
||||||
|
const styles = getSelectStyles(theme);
|
||||||
|
const components = {
|
||||||
|
SelectContainer,
|
||||||
|
ValueContainer,
|
||||||
|
SingleValue: ValueContainer,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.root}>
|
||||||
|
{labelProp && (
|
||||||
|
<label className={styles.label} htmlFor={id}>
|
||||||
|
{labelProp}
|
||||||
|
{':'}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
<Select openMenuOnFocus inputId={id} {...props} width="auto" components={components} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InlineSelect;
|
||||||
|
|
||||||
|
const SelectContainer = <Option, isMulti extends boolean, Group extends GroupTypeBase<Option>>(
|
||||||
|
props: ContainerProps<Option, isMulti, Group>
|
||||||
|
) => {
|
||||||
|
const { children } = props;
|
||||||
|
|
||||||
|
const theme = useTheme2();
|
||||||
|
const styles = getSelectStyles(theme);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseSelectContainer {...props} className={cx(props.className, styles.container)}>
|
||||||
|
{children}
|
||||||
|
</BaseSelectContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ValueContainer = <Option, isMulti extends boolean, Group extends GroupTypeBase<Option>>(
|
||||||
|
props: ContainerProps<Option, isMulti, Group>
|
||||||
|
) => {
|
||||||
|
const { className, children } = props;
|
||||||
|
const theme = useTheme2();
|
||||||
|
const styles = getSelectStyles(theme);
|
||||||
|
|
||||||
|
return <div className={cx(className, styles.valueContainer)}>{children}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSelectStyles = stylesFactory((theme: GrafanaTheme2) => ({
|
||||||
|
root: css({
|
||||||
|
display: 'flex',
|
||||||
|
fontSize: 12,
|
||||||
|
alignItems: 'center',
|
||||||
|
}),
|
||||||
|
|
||||||
|
label: css({
|
||||||
|
color: theme.colors.text.secondary,
|
||||||
|
}),
|
||||||
|
|
||||||
|
container: css({
|
||||||
|
background: 'none',
|
||||||
|
borderColor: 'transparent',
|
||||||
|
}),
|
||||||
|
|
||||||
|
valueContainer: css({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
flex: 'initial',
|
||||||
|
color: theme.colors.text.secondary,
|
||||||
|
fontSize: 12,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { stylesFactory, useTheme2 } from '@grafana/ui';
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
|
||||||
|
interface InputGroupProps {}
|
||||||
|
|
||||||
|
const InputGroup: React.FC<InputGroupProps> = ({ children }) => {
|
||||||
|
const theme = useTheme2();
|
||||||
|
const styles = useStyles(theme);
|
||||||
|
|
||||||
|
return <div className={styles.root}>{children}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useStyles = stylesFactory((theme: GrafanaTheme2) => ({
|
||||||
|
root: css({
|
||||||
|
display: 'flex',
|
||||||
|
|
||||||
|
// Style the direct children of the component
|
||||||
|
'> *': {
|
||||||
|
'&:not(:first-child)': {
|
||||||
|
// Negative margin hides the double-border on adjacent selects
|
||||||
|
marginLeft: -1,
|
||||||
|
},
|
||||||
|
|
||||||
|
'&:first-child': {
|
||||||
|
borderTopRightRadius: 0,
|
||||||
|
borderBottomRightRadius: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
'&:last-child': {
|
||||||
|
borderTopLeftRadius: 0,
|
||||||
|
borderBottomLeftRadius: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
'&:not(:first-child):not(:last-child)': {
|
||||||
|
borderRadius: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
position: 'relative',
|
||||||
|
zIndex: 1,
|
||||||
|
|
||||||
|
'&:hover': {
|
||||||
|
zIndex: 2,
|
||||||
|
},
|
||||||
|
'&:focus-within': {
|
||||||
|
zIndex: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default InputGroup;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { Space } from '../../../grafana-azure-monitor-datasource/components/Space';
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import React, { CSSProperties } from 'react';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { stylesFactory, useTheme2 } from '@grafana/ui';
|
||||||
|
|
||||||
|
interface StackProps {
|
||||||
|
direction?: CSSProperties['flexDirection'];
|
||||||
|
alignItems?: CSSProperties['alignItems'];
|
||||||
|
wrap?: boolean;
|
||||||
|
gap?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Stack: React.FC<StackProps> = ({ children, ...props }) => {
|
||||||
|
const theme = useTheme2();
|
||||||
|
const styles = useStyles(theme, props);
|
||||||
|
|
||||||
|
return <div className={styles.root}>{children}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useStyles = stylesFactory((theme: GrafanaTheme2, props: StackProps) => ({
|
||||||
|
root: css({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: props.direction ?? 'row',
|
||||||
|
flexWrap: props.wrap ?? true ? 'wrap' : undefined,
|
||||||
|
alignItems: props.alignItems,
|
||||||
|
gap: theme.spacing(props.gap ?? 2),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default Stack;
|
||||||
Reference in New Issue
Block a user