diff --git a/public/app/core/components/NestedFolderPicker/NestedFolderPicker.tsx b/public/app/core/components/NestedFolderPicker/NestedFolderPicker.tsx index 78e11c61ed5..baff647e306 100644 --- a/public/app/core/components/NestedFolderPicker/NestedFolderPicker.tsx +++ b/public/app/core/components/NestedFolderPicker/NestedFolderPicker.tsx @@ -31,6 +31,9 @@ export interface NestedFolderPickerProps { /* Folder UID to show as selected */ value?: string; + /** Show an invalid state around the folder picker */ + invalid?: boolean; + /* Whether to show the root 'Dashboards' (formally General) folder as selectable */ showRootFolder?: boolean; @@ -43,7 +46,13 @@ export interface NestedFolderPickerProps { const EXCLUDED_KINDS = ['empty-folder' as const, 'dashboard' as const]; -export function NestedFolderPicker({ value, showRootFolder = true, excludeUIDs, onChange }: NestedFolderPickerProps) { +export function NestedFolderPicker({ + value, + invalid, + showRootFolder = true, + excludeUIDs, + onChange, +}: NestedFolderPickerProps) { const styles = useStyles2(getStyles); const dispatch = useDispatch(); const selectedFolder = useGetFolderQuery(value || skipToken); @@ -212,6 +221,7 @@ export function NestedFolderPicker({ value, showRootFolder = true, excludeUIDs, return ( : null} placeholder={label ?? t('browse-dashboards.folder-picker.search-placeholder', 'Search folders')} value={search} + invalid={invalid} className={styles.search} onKeyDown={handleKeyDown} onChange={(e) => setSearch(e.currentTarget.value)} diff --git a/public/app/core/components/NestedFolderPicker/Trigger.tsx b/public/app/core/components/NestedFolderPicker/Trigger.tsx index 5814f7e307d..fb3a8581ca2 100644 --- a/public/app/core/components/NestedFolderPicker/Trigger.tsx +++ b/public/app/core/components/NestedFolderPicker/Trigger.tsx @@ -3,18 +3,20 @@ 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 { Icon, getInputStyles, useTheme2 } 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; + invalid?: boolean; label?: ReactNode; } -function Trigger({ isLoading, label, ...rest }: TriggerProps, ref: React.ForwardedRef) { - const styles = useStyles2(getStyles); +function Trigger({ isLoading, invalid, label, ...rest }: TriggerProps, ref: React.ForwardedRef) { + const theme = useTheme2(); + const styles = getStyles(theme, invalid); return (
@@ -52,8 +54,8 @@ function Trigger({ isLoading, label, ...rest }: TriggerProps, ref: React.Forward export default forwardRef(Trigger); -const getStyles = (theme: GrafanaTheme2) => { - const baseStyles = getInputStyles({ theme }); +const getStyles = (theme: GrafanaTheme2, invalid = false) => { + const baseStyles = getInputStyles({ theme, invalid }); return { wrapper: baseStyles.wrapper,