From 3f1e97cb070e92d6e21f65c3a25c66be9a6d9f57 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Wed, 24 Jan 2024 13:18:01 +0000 Subject: [PATCH] NestedFolderPicker: Add `clearable` prop (#81114) * add clearable prop to NestedFolderPicker * update types --- .../NestedFolderPicker.test.tsx | 7 +++ .../NestedFolderPicker/NestedFolderPicker.tsx | 18 +++++++- .../components/NestedFolderPicker/Trigger.tsx | 43 +++++++++++++++++-- .../settings/GeneralSettingsEditView.tsx | 2 +- .../DashboardSettings/GeneralSettings.tsx | 2 +- .../forms/SaveDashboardAsForm.tsx | 2 +- public/app/plugins/panel/dashlist/module.tsx | 2 +- public/locales/de-DE/grafana.json | 1 + public/locales/en-US/grafana.json | 1 + public/locales/es-ES/grafana.json | 1 + public/locales/fr-FR/grafana.json | 1 + public/locales/pseudo-LOCALE/grafana.json | 1 + public/locales/zh-Hans/grafana.json | 1 + 13 files changed, 74 insertions(+), 8 deletions(-) diff --git a/public/app/core/components/NestedFolderPicker/NestedFolderPicker.test.tsx b/public/app/core/components/NestedFolderPicker/NestedFolderPicker.test.tsx index 3c24d81440b..f5ed2595d83 100644 --- a/public/app/core/components/NestedFolderPicker/NestedFolderPicker.test.tsx +++ b/public/app/core/components/NestedFolderPicker/NestedFolderPicker.test.tsx @@ -113,6 +113,13 @@ describe('NestedFolderPicker', () => { expect(mockOnChange).toHaveBeenCalledWith(folderA.item.uid, folderA.item.title); }); + it('can clear a selection if clearable is specified', async () => { + render(); + + await userEvent.click(await screen.findByRole('button', { name: 'Clear selection' })); + expect(mockOnChange).toHaveBeenCalledWith(undefined, undefined); + }); + it('can select a folder from the picker with the keyboard', async () => { render(); const button = await screen.findByRole('button', { name: 'Select folder' }); diff --git a/public/app/core/components/NestedFolderPicker/NestedFolderPicker.tsx b/public/app/core/components/NestedFolderPicker/NestedFolderPicker.tsx index 888ea40f01f..7dd455e0780 100644 --- a/public/app/core/components/NestedFolderPicker/NestedFolderPicker.tsx +++ b/public/app/core/components/NestedFolderPicker/NestedFolderPicker.tsx @@ -42,7 +42,10 @@ export interface NestedFolderPickerProps { excludeUIDs?: string[]; /* Callback for when the user selects a folder */ - onChange?: (folderUID: string, folderName: string) => void; + onChange?: (folderUID: string | undefined, folderName: string | undefined) => void; + + /* Whether the picker should be clearable */ + clearable?: boolean; } const EXCLUDED_KINDS = ['empty-folder' as const, 'dashboard' as const]; @@ -64,6 +67,7 @@ export function NestedFolderPicker({ value, invalid, showRootFolder = true, + clearable = false, excludeUIDs, onChange, }: NestedFolderPickerProps) { @@ -158,6 +162,17 @@ export function NestedFolderPicker({ [onChange] ); + const handleClearSelection = useCallback( + (event: React.MouseEvent | React.KeyboardEvent) => { + event.preventDefault(); + event.stopPropagation(); + if (onChange) { + onChange(undefined, undefined); + } + }, + [onChange] + ); + const handleCloseOverlay = useCallback(() => setOverlayOpen(false), [setOverlayOpen]); const baseHandleLoadMore = useLoadNextChildrenPage(EXCLUDED_KINDS); @@ -259,6 +274,7 @@ export function NestedFolderPicker({ return ( { isLoading: boolean; + handleClearSelection?: (event: React.MouseEvent | React.KeyboardEvent) => void; invalid?: boolean; label?: ReactNode; } -function Trigger({ isLoading, invalid, label, ...rest }: TriggerProps, ref: React.ForwardedRef) { +function Trigger( + { handleClearSelection, isLoading, invalid, label, ...rest }: TriggerProps, + ref: React.ForwardedRef +) { const theme = useTheme2(); const styles = getStyles(theme, invalid); + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Enter' || event.key === ' ') { + handleClearSelection?.(event); + } + }; + return (
@@ -41,6 +51,18 @@ function Trigger({ isLoading, invalid, label, ...rest }: TriggerProps, ref: Reac Select folder )} + + {!isLoading && handleClearSelection && ( + + )}
@@ -92,11 +114,26 @@ const getStyles = (theme: GrafanaTheme2, invalid = false) => { '&: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), + }), }; }; diff --git a/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx b/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx index b86749abcbb..b6fca750747 100644 --- a/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx +++ b/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx @@ -90,7 +90,7 @@ export class GeneralSettingsEditView this._dashboard.setState({ tags: value }); }; - public onFolderChange = (newUID: string, newTitle: string) => { + public onFolderChange = (newUID: string | undefined, newTitle: string | undefined) => { const newMeta = { ...this._dashboard.state.meta, folderUid: newUID || this._dashboard.state.meta.folderUid, diff --git a/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx index c2025c81754..59795f86727 100644 --- a/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.tsx @@ -45,7 +45,7 @@ export function GeneralSettingsUnconnected({ const [dashboardDescription, setDashboardDescription] = useState(dashboard.description); const pageNav = config.featureToggles.dockedMegaMenu ? sectionNav.node.parentItem : undefined; - const onFolderChange = (newUID: string, newTitle: string) => { + const onFolderChange = (newUID: string | undefined, newTitle: string | undefined) => { dashboard.meta.folderUid = newUID; dashboard.meta.folderTitle = newTitle; dashboard.meta.hasUnsavedFolderChange = true; diff --git a/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardAsForm.tsx b/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardAsForm.tsx index 0e86fa849d3..8e435c158b8 100644 --- a/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardAsForm.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardAsForm.tsx @@ -167,7 +167,7 @@ export const SaveDashboardAsForm = ({ render={({ field: { ref, ...field } }) => ( field.onChange({ uid, title })} + onChange={(uid: string | undefined, title: string | undefined) => field.onChange({ uid, title })} value={field.value?.uid} // Old folder picker fields initialTitle={dashboard.meta.folderTitle} diff --git a/public/app/plugins/panel/dashlist/module.tsx b/public/app/plugins/panel/dashlist/module.tsx index 6cbc76f964d..c9fe6d12654 100644 --- a/public/app/plugins/panel/dashlist/module.tsx +++ b/public/app/plugins/panel/dashlist/module.tsx @@ -57,7 +57,7 @@ export const plugin = new PanelPlugin(DashList) id: 'folderUID', defaultValue: undefined, editor: function RenderFolderPicker({ value, onChange }) { - return onChange(folderUID)} />; + return onChange(folderUID)} />; }, }) .addCustomEditor({ diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index 80d9534dce4..64078c4ac5e 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -76,6 +76,7 @@ "folder-picker": { "accessible-label": "Ordner auswählen: {{ label }} aktuell ausgewählt", "button-label": "Ordner auswählen", + "clear-selection": "", "empty-message": "Keine Ordner gefunden", "error-title": "Fehler beim Laden der Ordner", "search-placeholder": "Ordner suchen", diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index d7dc250426c..23fb2ae1e95 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -76,6 +76,7 @@ "folder-picker": { "accessible-label": "Select folder: {{ label }} currently selected", "button-label": "Select folder", + "clear-selection": "Clear selection", "empty-message": "No folders found", "error-title": "Error loading folders", "search-placeholder": "Search folders", diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index 047df60e828..738854ff300 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -81,6 +81,7 @@ "folder-picker": { "accessible-label": "Seleccionar carpeta: {{ label }} seleccionada actualmente", "button-label": "Seleccionar carpeta", + "clear-selection": "", "empty-message": "No se ha encontrado ninguna carpeta", "error-title": "Error al cargar las carpetas", "search-placeholder": "Buscar carpetas", diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 88f75d17d44..1a0ba83c858 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -81,6 +81,7 @@ "folder-picker": { "accessible-label": "Sélectionnez un dossier : {{ label }} est sélectionné actuellement", "button-label": "Sélectionner un dossier", + "clear-selection": "", "empty-message": "Aucun dossier trouvé", "error-title": "Impossible de charger les dossiers", "search-placeholder": "Rechercher dans les dossiers", diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index e57f0726aa4..c96f41250c0 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -76,6 +76,7 @@ "folder-picker": { "accessible-label": "Ŝęľęčŧ ƒőľđęř: {{ label }} čūřřęʼnŧľy şęľęčŧęđ", "button-label": "Ŝęľęčŧ ƒőľđęř", + "clear-selection": "Cľęäř şęľęčŧįőʼn", "empty-message": "Ńő ƒőľđęřş ƒőūʼnđ", "error-title": "Ēřřőř ľőäđįʼnģ ƒőľđęřş", "search-placeholder": "Ŝęäřčĥ ƒőľđęřş", diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index 94100e46fd0..7d42394d0f6 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -71,6 +71,7 @@ "folder-picker": { "accessible-label": "选择文件夹:{{ label }} 当前已选择", "button-label": "选择文件夹", + "clear-selection": "", "empty-message": "未找到文件夹", "error-title": "加载文件夹时出错", "search-placeholder": "搜索文件夹",