diff --git a/packages/grafana-ui/src/components/Forms/Checkbox.story.tsx b/packages/grafana-ui/src/components/Forms/Checkbox.story.tsx index fac1bc339c0..3f7229f13c1 100644 --- a/packages/grafana-ui/src/components/Forms/Checkbox.story.tsx +++ b/packages/grafana-ui/src/components/Forms/Checkbox.story.tsx @@ -18,54 +18,62 @@ export const Controlled = () => { const [checked, setChecked] = useState(false); const onChange = useCallback((e) => setChecked(e.currentTarget.checked), [setChecked]); return ( - +
+ +
); }; export const uncontrolled = () => { return ( - - ); -}; - -export const StackedList = () => { - return ( - +
- - - +
+ ); +}; + +export const StackedList = () => { + return ( +
+ + + + + +
); }; export const InAField = () => { return ( - - - +
+ + + +
); }; diff --git a/packages/grafana-ui/src/components/Forms/Checkbox.tsx b/packages/grafana-ui/src/components/Forms/Checkbox.tsx index c0631710e39..c6af215247e 100644 --- a/packages/grafana-ui/src/components/Forms/Checkbox.tsx +++ b/packages/grafana-ui/src/components/Forms/Checkbox.tsx @@ -44,34 +44,21 @@ export const Checkbox = React.forwardRef( export const getCheckboxStyles = stylesFactory((theme: GrafanaTheme2) => { const labelStyles = getLabelStyles(theme); - const checkboxSize = '16px'; + const checkboxSize = 2; + const labelPadding = 1; + return { - label: cx( - labelStyles.label, - css` - padding-left: ${theme.spacing(1)}; - white-space: nowrap; - cursor: pointer; - ` - ), - description: cx( - labelStyles.description, - css` - line-height: ${theme.typography.bodySmall.lineHeight}; - padding-left: ${theme.spacing(1)}; - ` - ), wrapper: css` position: relative; - padding-left: ${checkboxSize}; vertical-align: middle; - min-height: ${theme.spacing(3)}; + font-size: 0; `, input: css` position: absolute; + z-index: 1; top: 0; left: 0; - width: 100%; + width: 100% !important; // global styles unset this height: 100%; opacity: 0; @@ -101,6 +88,7 @@ export const getCheckboxStyles = stylesFactory((theme: GrafanaTheme2) => { &:after { content: ''; position: absolute; + z-index: 2; left: 5px; top: 1px; width: 6px; @@ -114,6 +102,7 @@ export const getCheckboxStyles = stylesFactory((theme: GrafanaTheme2) => { &:disabled + span { background-color: ${theme.colors.action.disabledBackground}; cursor: not-allowed; + &:hover { background-color: ${theme.colors.action.disabledBackground}; } @@ -124,22 +113,40 @@ export const getCheckboxStyles = stylesFactory((theme: GrafanaTheme2) => { } `, checkmark: css` + position: relative; /* Checkbox should be layered on top of the invisible input so it recieves :hover */ + z-index: 2; display: inline-block; - width: ${checkboxSize}; - height: ${checkboxSize}; + width: ${theme.spacing(checkboxSize)}; + height: ${theme.spacing(checkboxSize)}; border-radius: ${theme.shape.borderRadius()}; - margin-right: ${theme.spacing(1)}; background: ${theme.components.input.background}; border: 1px solid ${theme.components.input.borderColor}; - position: absolute; - top: 2px; - left: 0; &:hover { cursor: pointer; border-color: ${theme.components.input.borderHover}; } `, + label: cx( + labelStyles.label, + css` + position: relative; + z-index: 2; + padding-left: ${theme.spacing(labelPadding)}; + white-space: nowrap; + cursor: pointer; + position: relative; + top: -3px; + ` + ), + description: cx( + labelStyles.description, + css` + line-height: ${theme.typography.bodySmall.lineHeight}; + padding-left: ${theme.spacing(checkboxSize + labelPadding)}; + margin-top: 0; /* The margin effectively comes from the top: -2px on the label above it */ + ` + ), }; }); diff --git a/public/app/features/search/components/SearchCheckbox.tsx b/public/app/features/search/components/SearchCheckbox.tsx index 04327c2c842..152090b8dec 100644 --- a/public/app/features/search/components/SearchCheckbox.tsx +++ b/public/app/features/search/components/SearchCheckbox.tsx @@ -1,34 +1,19 @@ import React, { FC, memo } from 'react'; -import { css } from '@emotion/css'; -import { Checkbox, stylesFactory } from '@grafana/ui'; +import { Checkbox } from '@grafana/ui'; interface Props { checked?: boolean; - onClick: any; + onClick?: React.MouseEventHandler; + className?: string; editable?: boolean; } -export const SearchCheckbox: FC = memo(({ onClick, checked = false, editable = false }) => { - const styles = getStyles(); - +export const SearchCheckbox: FC = memo(({ onClick, className, checked = false, editable = false }) => { return editable ? ( -
+
) : null; }); -const getStyles = stylesFactory(() => ({ - wrapper: css` - height: 21px; - & > label { - height: 100%; - - & > input { - position: relative; - } - } - `, -})); - SearchCheckbox.displayName = 'SearchCheckbox'; diff --git a/public/app/features/search/components/SearchItem.test.tsx b/public/app/features/search/components/SearchItem.test.tsx index e8b4ce93945..b1386c754f2 100644 --- a/public/app/features/search/components/SearchItem.test.tsx +++ b/public/app/features/search/components/SearchItem.test.tsx @@ -39,7 +39,7 @@ describe('SearchItem', () => { expect(screen.getAllByText('Test 1')).toHaveLength(1); }); - it('should mark item as checked', () => { + it('should toggle items when checked', () => { const mockedOnToggleChecked = jest.fn(); setup({ editable: true, onToggleChecked: mockedOnToggleChecked }); const checkbox = screen.getByRole('checkbox'); @@ -47,6 +47,10 @@ describe('SearchItem', () => { fireEvent.click(checkbox); expect(mockedOnToggleChecked).toHaveBeenCalledTimes(1); expect(mockedOnToggleChecked).toHaveBeenCalledWith(data); + }); + + it('should mark items as checked', () => { + setup({ editable: true, item: { ...data, checked: true } }); expect(screen.getByRole('checkbox')).toBeChecked(); }); diff --git a/public/app/features/search/components/SearchItem.tsx b/public/app/features/search/components/SearchItem.tsx index e1524c44d05..fc9783a9126 100644 --- a/public/app/features/search/components/SearchItem.tsx +++ b/public/app/features/search/components/SearchItem.tsx @@ -34,9 +34,11 @@ export const SearchItem: FC = ({ item, editable, onToggleChecked, onTagSe [onTagSelected] ); - const toggleItem = useCallback( - (event: React.MouseEvent) => { - event.preventDefault(); + const handleCheckboxClick = useCallback( + (ev: React.MouseEvent) => { + ev.stopPropagation(); + ev.preventDefault(); + if (onToggleChecked) { onToggleChecked(item); } @@ -54,7 +56,7 @@ export const SearchItem: FC = ({ item, editable, onToggleChecked, onTagSe className={styles.container} > - + diff --git a/public/app/features/search/components/SectionHeader.tsx b/public/app/features/search/components/SectionHeader.tsx index bb932ff112e..a0704c7fee1 100644 --- a/public/app/features/search/components/SectionHeader.tsx +++ b/public/app/features/search/components/SectionHeader.tsx @@ -29,10 +29,12 @@ export const SectionHeader: FC = ({ onSectionClick(section); }; - const onSectionChecked = useCallback( - (e: React.MouseEvent) => { - e.preventDefault(); - e.stopPropagation(); + const handleCheckboxClick = useCallback( + (ev: React.MouseEvent) => { + console.log('section header handleCheckboxClick'); + ev.stopPropagation(); + ev.preventDefault(); + if (onToggleChecked) { onToggleChecked(section); } @@ -46,7 +48,12 @@ export const SectionHeader: FC = ({ onClick={onSectionExpand} aria-label={section.expanded ? `Collapse folder ${section.id}` : `Expand folder ${section.id}`} > - +
@@ -90,6 +97,9 @@ const getSectionHeaderStyles = stylesFactory((theme: GrafanaTheme, selected = fa 'pointer', { selected } ), + checkbox: css` + padding: 0 ${sm} 0 0; + `, icon: css` padding: 0 ${sm} 0 ${editable ? 0 : sm}; `,