The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/public/app/core/components/NestedFolderPicker/Trigger.tsx

139 lines
3.7 KiB

import { css, cx } from '@emotion/css';
import React, { forwardRef, ReactNode, ButtonHTMLAttributes } from 'react';
import Skeleton from 'react-loading-skeleton';
import { GrafanaTheme2 } from '@grafana/data';
import { Icon, getInputStyles, useTheme2, Text } from '@grafana/ui';
import { focusCss, getFocusStyles, getMouseFocusStyles } from '@grafana/ui/src/themes/mixins';
import { Trans, t } from 'app/core/internationalization';
interface TriggerProps extends ButtonHTMLAttributes<HTMLButtonElement> {
isLoading: boolean;
handleClearSelection?: (event: React.MouseEvent<SVGElement> | React.KeyboardEvent<SVGElement>) => void;
invalid?: boolean;
label?: ReactNode;
}
function Trigger(
{ handleClearSelection, isLoading, invalid, label, ...rest }: TriggerProps,
ref: React.ForwardedRef<HTMLButtonElement>
) {
const theme = useTheme2();
const styles = getStyles(theme, invalid);
const handleKeyDown = (event: React.KeyboardEvent<SVGElement>) => {
if (event.key === 'Enter' || event.key === ' ') {
handleClearSelection?.(event);
}
};
return (
<div className={styles.wrapper}>
<div className={styles.inputWrapper}>
{label ? (
<div className={styles.prefix}>
<Icon name="folder" />
</div>
) : undefined}
<button
type="button"
className={cx(styles.fakeInput, label ? styles.hasPrefix : undefined)}
{...rest}
ref={ref}
>
{isLoading ? (
<Skeleton width={100} />
) : label ? (
<Text truncate>{label}</Text>
) : (
<Text truncate color="secondary">
<Trans i18nKey="browse-dashboards.folder-picker.button-label">Select folder</Trans>
</Text>
)}
{!isLoading && handleClearSelection && (
<Icon
role="button"
tabIndex={0}
aria-label={t('browse-dashboards.folder-picker.clear-selection', 'Clear selection')}
className={styles.clearIcon}
name="times"
onClick={handleClearSelection}
onKeyDown={handleKeyDown}
/>
)}
</button>
<div className={styles.suffix}>
<Icon name="angle-down" />
</div>
</div>
</div>
);
}
export default forwardRef(Trigger);
const getStyles = (theme: GrafanaTheme2, invalid = false) => {
const baseStyles = getInputStyles({ theme, invalid });
return {
wrapper: baseStyles.wrapper,
inputWrapper: baseStyles.inputWrapper,
prefix: css([
baseStyles.prefix,
{
pointerEvents: 'none',
color: theme.colors.text.primary,
},
]),
suffix: css([
baseStyles.suffix,
{
pointerEvents: 'none',
},
]),
fakeInput: css([
baseStyles.input,
{
textAlign: 'left',
letterSpacing: 'normal',
// We want the focus styles to appear only when tabbing through, not when clicking the button
// (and when focus is restored after command palette closes)
'&:focus': {
outline: 'unset',
boxShadow: 'unset',
},
'&:focus-visible': css`
${focusCss(theme)}
`,
alignItems: 'center',
display: 'flex',
flexWrap: 'nowrap',
justifyContent: 'space-between',
paddingRight: 28,
},
]),
hasPrefix: css({
paddingLeft: 28,
}),
clearIcon: css({
color: theme.colors.text.secondary,
cursor: 'pointer',
'&:hover': {
color: theme.colors.text.primary,
},
'&:focus:not(:focus-visible)': getMouseFocusStyles(theme),
'&:focus-visible': getFocusStyles(theme),
}),
};
};