diff --git a/public/app/core/components/NestedFolderPicker/NestedFolderPicker.tsx b/public/app/core/components/NestedFolderPicker/NestedFolderPicker.tsx index eaa737f69e6..a08ccf7f096 100644 --- a/public/app/core/components/NestedFolderPicker/NestedFolderPicker.tsx +++ b/public/app/core/components/NestedFolderPicker/NestedFolderPicker.tsx @@ -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 ( - + 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) : null} placeholder={label ?? t('browse-dashboards.folder-picker.search-placeholder', 'Search folders')} value={search} className={styles.search} diff --git a/public/app/core/components/NestedFolderPicker/Trigger.tsx b/public/app/core/components/NestedFolderPicker/Trigger.tsx new file mode 100644 index 00000000000..623fe81c49b --- /dev/null +++ b/public/app/core/components/NestedFolderPicker/Trigger.tsx @@ -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 { + isLoading: boolean; + label?: ReactNode; +} + +function Trigger({ isLoading, label, ...rest }: TriggerProps, ref: React.ForwardedRef) { + const styles = useStyles2(getStyles); + + return ( +
+
+ {label ? ( +
+ +
+ ) : undefined} + + + +
+ +
+
+
+ ); +} + +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, + }), + }; +}; diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index 00a747069c7..701777cf7f8 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -62,6 +62,7 @@ "move": "" }, "folder-picker": { + "accessible-label": "", "button-label": "", "empty-message": "", "error-title": "", diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index c659816e5bb..cccb1961dcd 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -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", diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index fd6e4413b81..19fd1905aac 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -67,6 +67,7 @@ "move": "" }, "folder-picker": { + "accessible-label": "", "button-label": "", "empty-message": "", "error-title": "", diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 953a7b92a56..1c7adcf59d6 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -67,6 +67,7 @@ "move": "" }, "folder-picker": { + "accessible-label": "", "button-label": "", "empty-message": "", "error-title": "", diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index b3f4ee574ed..85f021ff763 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -62,6 +62,7 @@ "move": "Mővę" }, "folder-picker": { + "accessible-label": "Ŝęľęčŧ ƒőľđęř: {{ label }} čūřřęʼnŧľy şęľęčŧęđ", "button-label": "Ŝęľęčŧ ƒőľđęř", "empty-message": "Ńő ƒőľđęřş ƒőūʼnđ", "error-title": "Ēřřőř ľőäđįʼnģ ƒőľđęřş", diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index 5af9c0ff9ac..24577491dab 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -57,6 +57,7 @@ "move": "" }, "folder-picker": { + "accessible-label": "", "button-label": "", "empty-message": "", "error-title": "",