mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
264cbb402c
commit
78869fae7c
@ -1,13 +1,11 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import React, { useCallback, useId, useMemo, useState } from 'react';
|
import React, { useCallback, useId, useMemo, useState } from 'react';
|
||||||
import Skeleton from 'react-loading-skeleton';
|
|
||||||
import { usePopperTooltip } from 'react-popper-tooltip';
|
import { usePopperTooltip } from 'react-popper-tooltip';
|
||||||
import { useAsync } from 'react-use';
|
import { useAsync } from 'react-use';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { Alert, Button, Icon, Input, LoadingBar, useStyles2 } from '@grafana/ui';
|
import { Alert, Icon, Input, LoadingBar, useStyles2 } from '@grafana/ui';
|
||||||
import { Text } from '@grafana/ui/src/components/Text/Text';
|
import { t } from 'app/core/internationalization';
|
||||||
import { t, Trans } from 'app/core/internationalization';
|
|
||||||
import { skipToken, useGetFolderQuery } from 'app/features/browse-dashboards/api/browseDashboardsAPI';
|
import { skipToken, useGetFolderQuery } from 'app/features/browse-dashboards/api/browseDashboardsAPI';
|
||||||
import { PAGE_SIZE } from 'app/features/browse-dashboards/api/services';
|
import { PAGE_SIZE } from 'app/features/browse-dashboards/api/services';
|
||||||
import {
|
import {
|
||||||
@ -26,6 +24,7 @@ import { DashboardViewItem } from 'app/features/search/types';
|
|||||||
import { useDispatch, useSelector } from 'app/types/store';
|
import { useDispatch, useSelector } from 'app/types/store';
|
||||||
|
|
||||||
import { getDOMId, NestedFolderList } from './NestedFolderList';
|
import { getDOMId, NestedFolderList } from './NestedFolderList';
|
||||||
|
import Trigger from './Trigger';
|
||||||
import { useTreeInteractions } from './hooks';
|
import { useTreeInteractions } from './hooks';
|
||||||
import { FolderChange, FolderUID } from './types';
|
import { FolderChange, FolderUID } from './types';
|
||||||
|
|
||||||
@ -198,22 +197,19 @@ export function NestedFolderPicker({ value, onChange }: NestedFolderPickerProps)
|
|||||||
|
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Trigger
|
||||||
|
label={label}
|
||||||
|
isLoading={selectedFolder.isLoading}
|
||||||
autoFocus={autoFocusButton}
|
autoFocus={autoFocusButton}
|
||||||
className={styles.button}
|
|
||||||
variant="secondary"
|
|
||||||
icon={value !== undefined ? 'folder' : undefined}
|
|
||||||
ref={setTriggerRef}
|
ref={setTriggerRef}
|
||||||
aria-label={label ? `Select folder: ${label} currently selected` : undefined}
|
aria-label={
|
||||||
>
|
label
|
||||||
{selectedFolder.isLoading ? (
|
? t('browse-dashboards.folder-picker.accessible-label', 'Select folder: {{ label }} currently selected', {
|
||||||
<Skeleton width={100} />
|
label,
|
||||||
) : (
|
})
|
||||||
<Text truncate>
|
: undefined
|
||||||
{label ?? <Trans i18nKey="browse-dashboards.folder-picker.button-label">Select folder</Trans>}
|
}
|
||||||
</Text>
|
/>
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,6 +218,7 @@ export function NestedFolderPicker({ value, onChange }: NestedFolderPickerProps)
|
|||||||
<Input
|
<Input
|
||||||
ref={setTriggerRef}
|
ref={setTriggerRef}
|
||||||
autoFocus
|
autoFocus
|
||||||
|
prefix={label ? <Icon name="folder" /> : null}
|
||||||
placeholder={label ?? t('browse-dashboards.folder-picker.search-placeholder', 'Search folders')}
|
placeholder={label ?? t('browse-dashboards.folder-picker.search-placeholder', 'Search folders')}
|
||||||
value={search}
|
value={search}
|
||||||
className={styles.search}
|
className={styles.search}
|
||||||
|
96
public/app/core/components/NestedFolderPicker/Trigger.tsx
Normal file
96
public/app/core/components/NestedFolderPicker/Trigger.tsx
Normal 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,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
@ -62,6 +62,7 @@
|
|||||||
"move": ""
|
"move": ""
|
||||||
},
|
},
|
||||||
"folder-picker": {
|
"folder-picker": {
|
||||||
|
"accessible-label": "",
|
||||||
"button-label": "",
|
"button-label": "",
|
||||||
"empty-message": "",
|
"empty-message": "",
|
||||||
"error-title": "",
|
"error-title": "",
|
||||||
|
@ -62,6 +62,7 @@
|
|||||||
"move": "Move"
|
"move": "Move"
|
||||||
},
|
},
|
||||||
"folder-picker": {
|
"folder-picker": {
|
||||||
|
"accessible-label": "Select folder: {{ label }} currently selected",
|
||||||
"button-label": "Select folder",
|
"button-label": "Select folder",
|
||||||
"empty-message": "No folders found",
|
"empty-message": "No folders found",
|
||||||
"error-title": "Error loading folders",
|
"error-title": "Error loading folders",
|
||||||
|
@ -67,6 +67,7 @@
|
|||||||
"move": ""
|
"move": ""
|
||||||
},
|
},
|
||||||
"folder-picker": {
|
"folder-picker": {
|
||||||
|
"accessible-label": "",
|
||||||
"button-label": "",
|
"button-label": "",
|
||||||
"empty-message": "",
|
"empty-message": "",
|
||||||
"error-title": "",
|
"error-title": "",
|
||||||
|
@ -67,6 +67,7 @@
|
|||||||
"move": ""
|
"move": ""
|
||||||
},
|
},
|
||||||
"folder-picker": {
|
"folder-picker": {
|
||||||
|
"accessible-label": "",
|
||||||
"button-label": "",
|
"button-label": "",
|
||||||
"empty-message": "",
|
"empty-message": "",
|
||||||
"error-title": "",
|
"error-title": "",
|
||||||
|
@ -62,6 +62,7 @@
|
|||||||
"move": "Mővę"
|
"move": "Mővę"
|
||||||
},
|
},
|
||||||
"folder-picker": {
|
"folder-picker": {
|
||||||
|
"accessible-label": "Ŝęľęčŧ ƒőľđęř: {{ label }} čūřřęʼnŧľy şęľęčŧęđ",
|
||||||
"button-label": "Ŝęľęčŧ ƒőľđęř",
|
"button-label": "Ŝęľęčŧ ƒőľđęř",
|
||||||
"empty-message": "Ńő ƒőľđęřş ƒőūʼnđ",
|
"empty-message": "Ńő ƒőľđęřş ƒőūʼnđ",
|
||||||
"error-title": "Ēřřőř ľőäđįʼnģ ƒőľđęřş",
|
"error-title": "Ēřřőř ľőäđįʼnģ ƒőľđęřş",
|
||||||
|
@ -57,6 +57,7 @@
|
|||||||
"move": ""
|
"move": ""
|
||||||
},
|
},
|
||||||
"folder-picker": {
|
"folder-picker": {
|
||||||
|
"accessible-label": "",
|
||||||
"button-label": "",
|
"button-label": "",
|
||||||
"empty-message": "",
|
"empty-message": "",
|
||||||
"error-title": "",
|
"error-title": "",
|
||||||
|
Loading…
Reference in New Issue
Block a user