NestedFolders: Select style for closed nested folder picker (#71959)

* make unexpanded nested folder picker look like a select

* fix prefix icon

* placeholder style, only show input label if value selected

* aria-label

* text component
This commit is contained in:
Josh Hunt 2023-07-20 14:33:42 +00:00 committed by GitHub
parent 264cbb402c
commit 78869fae7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 117 additions and 18 deletions

View File

@ -1,13 +1,11 @@
import { css } from '@emotion/css';
import React, { useCallback, useId, useMemo, useState } from 'react';
import Skeleton from 'react-loading-skeleton';
import { usePopperTooltip } from 'react-popper-tooltip';
import { useAsync } from 'react-use';
import { GrafanaTheme2 } from '@grafana/data';
import { Alert, Button, Icon, Input, LoadingBar, useStyles2 } from '@grafana/ui';
import { Text } from '@grafana/ui/src/components/Text/Text';
import { t, Trans } from 'app/core/internationalization';
import { Alert, Icon, Input, LoadingBar, useStyles2 } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { skipToken, useGetFolderQuery } from 'app/features/browse-dashboards/api/browseDashboardsAPI';
import { PAGE_SIZE } from 'app/features/browse-dashboards/api/services';
import {
@ -26,6 +24,7 @@ import { DashboardViewItem } from 'app/features/search/types';
import { useDispatch, useSelector } from 'app/types/store';
import { getDOMId, NestedFolderList } from './NestedFolderList';
import Trigger from './Trigger';
import { useTreeInteractions } from './hooks';
import { FolderChange, FolderUID } from './types';
@ -198,22 +197,19 @@ export function NestedFolderPicker({ value, onChange }: NestedFolderPickerProps)
if (!visible) {
return (
<Button
<Trigger
label={label}
isLoading={selectedFolder.isLoading}
autoFocus={autoFocusButton}
className={styles.button}
variant="secondary"
icon={value !== undefined ? 'folder' : undefined}
ref={setTriggerRef}
aria-label={label ? `Select folder: ${label} currently selected` : undefined}
>
{selectedFolder.isLoading ? (
<Skeleton width={100} />
) : (
<Text truncate>
{label ?? <Trans i18nKey="browse-dashboards.folder-picker.button-label">Select folder</Trans>}
</Text>
)}
</Button>
aria-label={
label
? t('browse-dashboards.folder-picker.accessible-label', 'Select folder: {{ label }} currently selected', {
label,
})
: undefined
}
/>
);
}
@ -222,6 +218,7 @@ export function NestedFolderPicker({ value, onChange }: NestedFolderPickerProps)
<Input
ref={setTriggerRef}
autoFocus
prefix={label ? <Icon name="folder" /> : null}
placeholder={label ?? t('browse-dashboards.folder-picker.search-placeholder', 'Search folders')}
value={search}
className={styles.search}

View File

@ -0,0 +1,96 @@
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 { useStyles2, Icon, getInputStyles } from '@grafana/ui';
import { focusCss } from '@grafana/ui/src/themes/mixins';
import { Text } from '@grafana/ui/src/unstable';
import { Trans } from 'app/core/internationalization';
interface TriggerProps extends ButtonHTMLAttributes<HTMLButtonElement> {
isLoading: boolean;
label?: ReactNode;
}
function Trigger({ isLoading, label, ...rest }: TriggerProps, ref: React.ForwardedRef<HTMLButtonElement>) {
const styles = useStyles2(getStyles);
return (
<div className={styles.wrapper}>
<div className={styles.inputWrapper}>
{label ? (
<div className={styles.prefix}>
<Icon name="folder" />
</div>
) : undefined}
<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>
)}
</button>
<div className={styles.suffix}>
<Icon name="angle-down" />
</div>
</div>
</div>
);
}
export default forwardRef(Trigger);
const getStyles = (theme: GrafanaTheme2) => {
const baseStyles = getInputStyles({ theme });
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)}
`,
},
]),
hasPrefix: css({
paddingLeft: 28,
}),
};
};

View File

@ -62,6 +62,7 @@
"move": ""
},
"folder-picker": {
"accessible-label": "",
"button-label": "",
"empty-message": "",
"error-title": "",

View File

@ -62,6 +62,7 @@
"move": "Move"
},
"folder-picker": {
"accessible-label": "Select folder: {{ label }} currently selected",
"button-label": "Select folder",
"empty-message": "No folders found",
"error-title": "Error loading folders",

View File

@ -67,6 +67,7 @@
"move": ""
},
"folder-picker": {
"accessible-label": "",
"button-label": "",
"empty-message": "",
"error-title": "",

View File

@ -67,6 +67,7 @@
"move": ""
},
"folder-picker": {
"accessible-label": "",
"button-label": "",
"empty-message": "",
"error-title": "",

View File

@ -62,6 +62,7 @@
"move": "Mővę"
},
"folder-picker": {
"accessible-label": "Ŝęľęčŧ ƒőľđęř: {{ label }} čūřřęʼnŧľy şęľęčŧęđ",
"button-label": "Ŝęľęčŧ ƒőľđęř",
"empty-message": "Ńő ƒőľđęřş ƒőūʼnđ",
"error-title": "Ēřřőř ľőäđįʼnģ ƒőľđęřş",

View File

@ -57,6 +57,7 @@
"move": ""
},
"folder-picker": {
"accessible-label": "",
"button-label": "",
"empty-message": "",
"error-title": "",