diff --git a/packages/eui/.loki/reference/chrome_desktop_Editors_Syntax_EuiMarkdownFormat_Playground.png b/packages/eui/.loki/reference/chrome_desktop_Editors_Syntax_EuiMarkdownFormat_Playground.png index 0d70b990e44..09d83378cff 100644 Binary files a/packages/eui/.loki/reference/chrome_desktop_Editors_Syntax_EuiMarkdownFormat_Playground.png and b/packages/eui/.loki/reference/chrome_desktop_Editors_Syntax_EuiMarkdownFormat_Playground.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiCheckboxGroup_Playground.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiCheckboxGroup_Playground.png index 6b9e9b52822..8993825b30e 100644 Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiCheckboxGroup_Playground.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiCheckboxGroup_Playground.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiCheckbox_Playground.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiCheckbox_Playground.png index d49d36d2309..ed522a8a334 100644 Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiCheckbox_Playground.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiCheckbox_Playground.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiRadioGroup_Playground.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiRadioGroup_Playground.png index 6beb887b5ec..8fc344b6542 100644 Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiRadioGroup_Playground.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiRadioGroup_Playground.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiRadio_Playground.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiRadio_Playground.png index dc6d6148072..62c4d8e920d 100644 Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiRadio_Playground.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiRadio_Playground.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSwitch_Kitchen_Sink.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSwitch_Kitchen_Sink.png new file mode 100644 index 00000000000..5d07ea70a4b Binary files /dev/null and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSwitch_Kitchen_Sink.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSwitch_Playground.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSwitch_Playground.png index df76bf3bfa6..c7048826c82 100644 Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSwitch_Playground.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiSwitch_Playground.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Editors_Syntax_EuiMarkdownFormat_Playground.png b/packages/eui/.loki/reference/chrome_mobile_Editors_Syntax_EuiMarkdownFormat_Playground.png index c81fbf045dc..fcc5adc6a60 100644 Binary files a/packages/eui/.loki/reference/chrome_mobile_Editors_Syntax_EuiMarkdownFormat_Playground.png and b/packages/eui/.loki/reference/chrome_mobile_Editors_Syntax_EuiMarkdownFormat_Playground.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiCheckboxGroup_Playground.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiCheckboxGroup_Playground.png index 2d684c11940..5321036ba3c 100644 Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiCheckboxGroup_Playground.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiCheckboxGroup_Playground.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiCheckbox_Playground.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiCheckbox_Playground.png index 7f0285f8dcf..5bf1e090823 100644 Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiCheckbox_Playground.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiCheckbox_Playground.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiRadioGroup_Playground.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiRadioGroup_Playground.png index 5eb3f20d0ec..86ac51459b6 100644 Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiRadioGroup_Playground.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiRadioGroup_Playground.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiRadio_Playground.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiRadio_Playground.png index 6a1906478c0..02cf0e6a0c4 100644 Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiRadio_Playground.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiRadio_Playground.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSwitch_Kitchen_Sink.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSwitch_Kitchen_Sink.png new file mode 100644 index 00000000000..66275ef3c65 Binary files /dev/null and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSwitch_Kitchen_Sink.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSwitch_Playground.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSwitch_Playground.png index a72980a7909..6941fed5635 100644 Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSwitch_Playground.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiSwitch_Playground.png differ diff --git a/packages/eui/changelogs/upcoming/7969.md b/packages/eui/changelogs/upcoming/7969.md new file mode 100644 index 00000000000..dd1e80736ea --- /dev/null +++ b/packages/eui/changelogs/upcoming/7969.md @@ -0,0 +1,22 @@ +**CSS-in-JS conversions** + +- Converted `EuiCheckbox` to Emotion +- Converted `EuiRadio` to Emotion +- Converted `EuiSwitch` to Emotion +- Removed the following Sass variables: + - `$euiFormCustomControlDisabledIconColor` + - `$euiFormCustomControlBorderColor` + - `$euiRadioSize` + - `$euiCheckBoxSize` + - `$euiCheckboxBorderRadius` + - `$euiSwitchHeight` (and compressed/mini variants) + - `$euiSwitchWidth` (and compressed/mini variants) + - `$euiSwitchThumbSize` (and compressed/mini variants) + - `$euiSwitchIconHeight` + - `$euiSwitchOffColor` +- Removed the following Sass mixins: + - `euiIconBackground` + - `euiCustomControl` + - `euiCustomControlSelected` + - `euiCustomControlDisabled` + - `euiCustomControlFocused` diff --git a/packages/eui/src-docs/src/views/selection_controls/checkbox.js b/packages/eui/src-docs/src/views/selection_controls/checkbox.js index 715cfe54c0e..cc045ecb747 100644 --- a/packages/eui/src-docs/src/views/selection_controls/checkbox.js +++ b/packages/eui/src-docs/src/views/selection_controls/checkbox.js @@ -1,4 +1,4 @@ -import React, { useState, Fragment } from 'react'; +import React, { useState } from 'react'; import { EuiCheckbox, EuiSpacer } from '../../../../src/components'; @@ -26,7 +26,7 @@ export default () => { }; return ( - + <> { id={compressedCheckboxId} label="I am a readonly checkbox" checked={checked} - onChange={(e) => onChange(e)} + onChange={() => {}} readOnly /> - + ); }; diff --git a/packages/eui/src/components/basic_table/__snapshots__/basic_table.test.tsx.snap b/packages/eui/src/components/basic_table/__snapshots__/basic_table.test.tsx.snap index 2334b0aee41..09520756ab1 100644 --- a/packages/eui/src/components/basic_table/__snapshots__/basic_table.test.tsx.snap +++ b/packages/eui/src/components/basic_table/__snapshots__/basic_table.test.tsx.snap @@ -145,19 +145,24 @@ exports[`EuiBasicTable renders (kitchen sink) with pagination, selection, sortin class="euiTableCellContent" >
-
+ class="euiCheckbox__square emotion-euiCheckbox__square-unselected" + > + + +
@@ -269,19 +274,24 @@ exports[`EuiBasicTable renders (kitchen sink) with pagination, selection, sortin class="euiTableCellContent" >
-
+ class="euiCheckbox__square emotion-euiCheckbox__square-unselected" + > + + +
@@ -375,19 +385,24 @@ exports[`EuiBasicTable renders (kitchen sink) with pagination, selection, sortin class="euiTableCellContent" >
-
+ class="euiCheckbox__square emotion-euiCheckbox__square-unselected" + > + + +
@@ -481,19 +496,24 @@ exports[`EuiBasicTable renders (kitchen sink) with pagination, selection, sortin class="euiTableCellContent" >
-
+ class="euiCheckbox__square emotion-euiCheckbox__square-unselected" + > + + +
diff --git a/packages/eui/src/components/basic_table/__snapshots__/in_memory_table.test.tsx.snap b/packages/eui/src/components/basic_table/__snapshots__/in_memory_table.test.tsx.snap index dd93726382c..91bc4241c1d 100644 --- a/packages/eui/src/components/basic_table/__snapshots__/in_memory_table.test.tsx.snap +++ b/packages/eui/src/components/basic_table/__snapshots__/in_memory_table.test.tsx.snap @@ -7,19 +7,24 @@ exports[`EuiInMemoryTable behavior mobile header 1`] = `
-
+ class="euiCheckbox__square emotion-euiCheckbox__square-unselected" + > + + +
-
+ class="euiCheckbox__square emotion-euiCheckbox__square-unselected" + > + + +
-
+ class="euiRadio__circle emotion-euiRadio__circle-unselected" + > + + +
-
+ class="euiRadio__circle emotion-euiRadio__circle-disabled-unselected" + > + + +
-
+ class="euiRadio__circle emotion-euiRadio__circle-unselected" + > + + +
@@ -179,12 +175,12 @@ exports[`useDataGridColumnSelector columnSelector [React 16] renders a toolbar b class="euiFlexItem emotion-euiFlexItem-growZero" >
@@ -394,12 +386,12 @@ exports[`useDataGridColumnSelector columnSelector [React 17] renders a toolbar b class="euiFlexItem emotion-euiFlexItem-growZero" >
@@ -471,12 +459,12 @@ exports[`useDataGridColumnSelector columnSelector [React 17] renders a toolbar b class="euiFlexItem emotion-euiFlexItem-growZero" >
@@ -686,12 +670,12 @@ exports[`useDataGridColumnSelector columnSelector [React 18] renders a toolbar b class="euiFlexItem emotion-euiFlexItem-growZero" >
@@ -763,12 +743,12 @@ exports[`useDataGridColumnSelector columnSelector [React 18] renders a toolbar b class="euiFlexItem emotion-euiFlexItem-growZero" >
diff --git a/packages/eui/src/components/datagrid/controls/_data_grid_toolbar.scss b/packages/eui/src/components/datagrid/controls/_data_grid_toolbar.scss index 19b42fa54e3..2e4085ffe6e 100644 --- a/packages/eui/src/components/datagrid/controls/_data_grid_toolbar.scss +++ b/packages/eui/src/components/datagrid/controls/_data_grid_toolbar.scss @@ -48,7 +48,7 @@ } .euiDataGrid__controlScroll { - @include euiYScrollWithShadows; + @include euiYScroll; max-height: $euiDataGridPopoverMaxHeight; padding: $euiSizeS; margin: -$euiSizeS; // Offset against the panel to make the scrollbar flush scrollbars diff --git a/packages/eui/src/components/datagrid/controls/column_selector.tsx b/packages/eui/src/components/datagrid/controls/column_selector.tsx index 3c1719b0fff..71df716795e 100644 --- a/packages/eui/src/components/datagrid/controls/column_selector.tsx +++ b/packages/eui/src/components/datagrid/controls/column_selector.tsx @@ -216,8 +216,7 @@ export const useDataGridColumnSelector = ( label={displayValues[id] || id} showLabel={false} checked={visibleColumnIds.has(id)} - compressed - className="euiSwitch--mini" + mini onChange={(event) => { const { target: { checked }, diff --git a/packages/eui/src/components/datagrid/data_grid.a11y.tsx b/packages/eui/src/components/datagrid/data_grid.a11y.tsx index cc2d7d7e65a..fdf9a7124ca 100644 --- a/packages/eui/src/components/datagrid/data_grid.a11y.tsx +++ b/packages/eui/src/components/datagrid/data_grid.a11y.tsx @@ -190,7 +190,7 @@ describe('EuiDataGrid', () => { 'button[data-test-subj="dataGridColumnSelectorButton"]' ).realClick(); cy.get('input[data-test-subj="dataGridColumnSelectorSearch"]').type('a'); - cy.get('div.euiSwitch--compressed').should(($s) => { + cy.get('.euiSwitch').should(($s) => { expect($s).to.have.length(5); }); cy.checkAxe(); @@ -203,7 +203,7 @@ describe('EuiDataGrid', () => { cy.get('input[data-test-subj="dataGridColumnSelectorSearch"]').type( 'favorite' ); - cy.get('div.euiSwitch--compressed').should(($s) => { + cy.get('.euiSwitch').should(($s) => { expect($s).to.have.length(1); }); cy.checkAxe(); diff --git a/packages/eui/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select_popover.test.tsx.snap b/packages/eui/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select_popover.test.tsx.snap index 8e11651e26b..64b9fbf5011 100644 --- a/packages/eui/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select_popover.test.tsx.snap +++ b/packages/eui/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select_popover.test.tsx.snap @@ -288,31 +288,27 @@ exports[`EuiQuickSelectPanels customQuickSelectPanels should render custom panel class="euiFlexItem emotion-euiFlexItem-growZero" >
`; -exports[`EuiCheckbox props check is rendered 1`] = ` +exports[`EuiCheckbox renders props: checked 1`] = `
-
+ class="euiCheckbox__square emotion-euiCheckbox__square-selected" + > + + +
`; -exports[`EuiCheckbox props disabled disabled is rendered 1`] = ` +exports[`EuiCheckbox renders props: disabled 1`] = `
-
+ class="euiCheckbox__square emotion-euiCheckbox__square-disabled-unselected" + > + + +
`; -exports[`EuiCheckbox props label is rendered 1`] = ` +exports[`EuiCheckbox renders props: label 1`] = `
-
+ class="euiCheckbox__square emotion-euiCheckbox__square-unselected" + > + + +
`; -exports[`EuiCheckbox props labelProps is rendered 1`] = ` +exports[`EuiCheckbox renders props: labelProps 1`] = `
-
+ class="euiCheckbox__square emotion-euiCheckbox__square-unselected" + > + + +
`; + +exports[`EuiCheckbox renders props: readOnly 1`] = ` +
+
+ + +
+ +
+`; diff --git a/packages/eui/src/components/form/checkbox/_checkbox.scss b/packages/eui/src/components/form/checkbox/_checkbox.scss deleted file mode 100644 index 8c9e051c72b..00000000000 --- a/packages/eui/src/components/form/checkbox/_checkbox.scss +++ /dev/null @@ -1,115 +0,0 @@ -// TODO: Address nesting during Emotion conversion, if possible -// stylelint-disable max-nesting-depth - -/** - * 1. Float above the visual radio and match its dimension, so that when users try to click it - * they actually click this input. - */ - -.euiCheckbox { - position: relative; - - // Set a top offset for the real input and faux input to align better with the text - $topOffset: (($euiSizeL - $euiCheckBoxSize) / 2) - 1px; - - .euiCheckbox__input { - @include size($euiCheckBoxSize); - top: $topOffset; - cursor: pointer; - position: absolute; /* 1 */ - opacity: 0; /* 1 */ - z-index: 1; /* 1 */ - - ~ .euiCheckbox__label { - display: inline-block; - padding-left: ($euiCheckBoxSize * 1.5); - line-height: $euiSizeL; - font-size: $euiFontSizeS; - position: relative; - z-index: 2; - cursor: pointer; - } - - + .euiCheckbox__square { - @include euiCustomControl($type: 'square', $size: $euiCheckBoxSize); - display: inline-block; - position: absolute; - left: 0; - top: $topOffset; - } - - &:checked { - + .euiCheckbox__square { - @include euiCustomControlSelected($type: 'check'); - } - } - - &:indeterminate { - + .euiCheckbox__square { - @include euiCustomControlSelected($type: 'square'); - } - } - - &:focus { - + .euiCheckbox__square { - @include euiCustomControlFocused; - } - } - - // Readonly checkboxes are used by EuiMarkdownEditor - &[readonly] { - cursor: default !important; // stylelint-disable-line declaration-no-important - - ~ .euiCheckbox__label { - cursor: default !important; // stylelint-disable-line declaration-no-important - } - - // maintain the initial color to enforce that clicks are not doing anything - &:focus { - + .euiCheckbox__square { - outline-color: $euiFormCustomControlBorderColor !important; // stylelint-disable-line declaration-no-important - border-color: $euiFormCustomControlBorderColor; - } - } - } - - &[disabled] { - cursor: not-allowed !important; // stylelint-disable-line declaration-no-important - - ~ .euiCheckbox__label { - color: $euiFormControlDisabledColor; - cursor: not-allowed !important; // stylelint-disable-line declaration-no-important - } - - + .euiCheckbox__square { - @include euiCustomControlDisabled; - } - } - - &:checked[disabled] { - + .euiCheckbox__square { - @include euiCustomControlDisabled($type: 'check'); - } - } - - &:indeterminate[disabled] { - + .euiCheckbox__square { - @include euiCustomControlDisabled($type: 'square'); - } - } - } - - &.euiCheckbox--noLabel { - min-height: $euiCheckBoxSize; - min-width: $euiCheckBoxSize; - - .euiCheckbox__input, - .euiCheckbox__square { - top: 0; - } - - .euiCheckbox__input { - margin: 0; - } - } -} diff --git a/packages/eui/src/components/form/checkbox/_index.scss b/packages/eui/src/components/form/checkbox/_index.scss deleted file mode 100644 index e6e53eb7128..00000000000 --- a/packages/eui/src/components/form/checkbox/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'checkbox'; diff --git a/packages/eui/src/components/form/checkbox/checkbox.styles.ts b/packages/eui/src/components/form/checkbox/checkbox.styles.ts new file mode 100644 index 00000000000..77d713ba1d1 --- /dev/null +++ b/packages/eui/src/components/form/checkbox/checkbox.styles.ts @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { css } from '@emotion/react'; + +import { UseEuiTheme } from '../../../services'; +import { + euiFormCustomControlStyles, + euiFormCustomControlVariables, +} from '../form.styles'; + +export const euiCheckboxStyles = (euiThemeContext: UseEuiTheme) => { + const { euiTheme } = euiThemeContext; + const controlStyles = euiFormCustomControlStyles(euiThemeContext); + const { + colors: { unselectedBorder }, + } = euiFormCustomControlVariables(euiThemeContext); + + return { + euiCheckbox: css(controlStyles.wrapper), + + input: { + euiCheckbox__square: css` + ${controlStyles.input.fauxInput} + border-radius: ${euiTheme.border.radius.small}; + `, + enabled: { + selected: css(controlStyles.input.enabled.selected), + unselected: css(controlStyles.input.enabled.unselected), + }, + disabled: { + selected: css(controlStyles.input.disabled.selected), + unselected: css(controlStyles.input.disabled.unselected), + }, + // Readonly checkboxes are used by EuiMarkdownEditor + // Maintain the initial color to enforce that clicks are not doing anything + readOnly: css` + &:has(input:focus-visible) { + outline: ${euiTheme.focus.width} solid ${unselectedBorder}; + } + + &:has(input:focus) { + border-color: ${unselectedBorder}; + } + `, + + icon: { + euiCheckbox__icon: css``, + check: css` + ${controlStyles.input.icon} + stroke: currentColor; + `, + indeterminate: css` + transform: scale(0.5); + `, + }, + + euiCheckbox__input: css` + ${controlStyles.input.hiddenInput} + + &[readonly] { + cursor: default; + } + `, + }, + + label: { + euiCheckbox__label: css(controlStyles.label.label), + enabled: controlStyles.label.enabled, + disabled: css(controlStyles.label.disabled), + readOnly: css` + cursor: default; + `, + }, + }; +}; diff --git a/packages/eui/src/components/form/checkbox/checkbox.test.tsx b/packages/eui/src/components/form/checkbox/checkbox.test.tsx index 2474d3c3314..6766f668c44 100644 --- a/packages/eui/src/components/form/checkbox/checkbox.test.tsx +++ b/packages/eui/src/components/form/checkbox/checkbox.test.tsx @@ -42,7 +42,7 @@ describe('EuiCheckbox', () => { } ); - test('is rendered', () => { + it('renders', () => { const { container } = render( {}} {...requiredProps} /> ); @@ -50,8 +50,8 @@ describe('EuiCheckbox', () => { expect(container.firstChild).toMatchSnapshot(); }); - describe('props', () => { - test('check is rendered', () => { + describe('renders props:', () => { + test('checked', () => { const { container } = render( ); @@ -59,7 +59,7 @@ describe('EuiCheckbox', () => { expect(container.firstChild).toMatchSnapshot(); }); - test('label is rendered', () => { + test('label', () => { const { container } = render( Label} /> ); @@ -67,7 +67,7 @@ describe('EuiCheckbox', () => { expect(container.firstChild).toMatchSnapshot(); }); - test('labelProps is rendered', () => { + test('labelProps', () => { const { container } = render( { expect(container.firstChild).toMatchSnapshot(); }); - describe('disabled', () => { - test('disabled is rendered', () => { - const { container } = render( - - ); + test('disabled', () => { + const { container } = render( + + ); + + expect(container.firstChild).toMatchSnapshot(); + }); - expect(container.firstChild).toMatchSnapshot(); - }); + test('readOnly', () => { + const { container } = render( + + ); + + expect(container.firstChild).toMatchSnapshot(); }); }); }); diff --git a/packages/eui/src/components/form/checkbox/checkbox.tsx b/packages/eui/src/components/form/checkbox/checkbox.tsx index 496472a6020..8e5ee9cefca 100644 --- a/packages/eui/src/components/form/checkbox/checkbox.tsx +++ b/packages/eui/src/components/form/checkbox/checkbox.tsx @@ -13,13 +13,14 @@ import React, { InputHTMLAttributes, LabelHTMLAttributes, useCallback, - useMemo, } from 'react'; -import { css } from '@emotion/react'; import classNames from 'classnames'; -import { useCombinedRefs } from '../../../services'; +import { useCombinedRefs, useEuiMemoizedStyles } from '../../../services'; import { CommonProps } from '../../common'; +import { EuiIcon } from '../../icon'; + +import { euiCheckboxStyles } from './checkbox.styles'; export interface EuiCheckboxProps extends CommonProps, @@ -39,43 +40,45 @@ export interface EuiCheckboxProps export const EuiCheckbox: FunctionComponent = ({ className, - css: customCss, id, checked = false, label, onChange, type, disabled = false, + readOnly = false, indeterminate = false, inputRef, labelProps, ...rest }) => { - const classes = classNames( - 'euiCheckbox', - { - 'euiCheckbox--noLabel': !label, - }, - className - ); + const classes = classNames('euiCheckbox', className); - const styles = { euiCheckbox: css`` }; // TODO: Emotion conversion - const cssStyles = [styles.euiCheckbox, customCss]; + const styles = useEuiMemoizedStyles(euiCheckboxStyles); + const inputStyles = [ + styles.input.euiCheckbox__square, + disabled + ? checked || indeterminate + ? styles.input.disabled.selected + : styles.input.disabled.unselected + : checked || indeterminate + ? styles.input.enabled.selected + : styles.input.enabled.unselected, + readOnly && styles.input.readOnly, + ]; - const optionalLabel = useMemo(() => { - if (!label) return; + const labelClasses = classNames('euiCheckbox__label', labelProps?.className); + const labelStyles = [ + styles.label.euiCheckbox__label, + disabled ? styles.label.disabled : styles.label.enabled, + readOnly && styles.label.readOnly, + labelProps?.css, + ]; - const labelClasses = classNames( - 'euiCheckbox__label', - labelProps?.className - ); - - return ( - - ); - }, [label, labelProps, id]); + const iconStyles = [ + styles.input.icon.euiCheckbox__icon, + indeterminate ? styles.input.icon.indeterminate : styles.input.icon.check, + ]; // @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#indeterminate_state_checkboxes const setIndeterminateState = useCallback( @@ -87,21 +90,36 @@ export const EuiCheckbox: FunctionComponent = ({ const refs = useCombinedRefs([inputRef, setIndeterminateState]); return ( -
- - -
+
+
+ + +
- {optionalLabel} + {label && ( + + )}
); }; diff --git a/packages/eui/src/components/form/checkbox/checkbox_group.stories.tsx b/packages/eui/src/components/form/checkbox/checkbox_group.stories.tsx index 41c4f4e261e..28b8a177620 100644 --- a/packages/eui/src/components/form/checkbox/checkbox_group.stories.tsx +++ b/packages/eui/src/components/form/checkbox/checkbox_group.stories.tsx @@ -30,9 +30,11 @@ export const Playground: Story = { { id: 'checkbox-1', label: 'Checkbox 1' }, { id: 'checkbox-2', label: 'Checkbox 2' }, { id: 'checkbox-3', label: 'Checkbox 3', disabled: true }, + { id: 'checkbox-4', label: 'Checkbox 4', disabled: true }, ], idToSelectedMap: { 'checkbox-2': true, + 'checkbox-4': true, }, // set up for easier testing/QA legend: { diff --git a/packages/eui/src/components/form/form.styles.test.tsx b/packages/eui/src/components/form/form.styles.test.tsx index d6a9f011ecc..4fd5cddcb96 100644 --- a/packages/eui/src/components/form/form.styles.test.tsx +++ b/packages/eui/src/components/form/form.styles.test.tsx @@ -14,7 +14,7 @@ import { EuiProvider } from '../provider'; import { euiFormVariables, euiFormControlStyles, - euiCustomControl, + euiFormCustomControlStyles, } from './form.styles'; const darkModeWrapper: FunctionComponent = ({ @@ -51,8 +51,6 @@ describe('euiFormVariables', () => { "controlLayoutGroupInputHeight": "38px", "controlPadding": "12px", "controlPlaceholderText": "#646a77", - "customControlBorderColor": "#f5f7fc", - "customControlDisabledIconColor": "#cacfd8", "iconAffordance": "24px", "iconCompressedAffordance": "18px", "maxWidth": "400px", @@ -68,8 +66,6 @@ describe('euiFormVariables', () => { // Check custom dark-mode logic expect(result.current.backgroundColor).toEqual('#16171c'); expect(result.current.controlPlaceholderText).toEqual('#878b95'); - expect(result.current.customControlDisabledIconColor).toEqual('#33373f'); - expect(result.current.customControlBorderColor).toEqual('#16171c'); }); }); @@ -218,97 +214,99 @@ describe('euiFormControlStyles', () => { }); }); -describe('euiCustomControl', () => { - it('returns CSS styles for padding, borders, backgrounds', () => { - const { result } = renderHook(() => euiCustomControl(useEuiTheme())); - expect(result.current).toMatchInlineSnapshot(` - " - padding: 7px; - - border: 1px solid #f5f7fc; - background: #FFF no-repeat center; - - @media screen and (prefers-reduced-motion: no-preference) { - transition: background-color 150ms ease-in, - border-color 150ms ease-in; - } - " - `); - }); - - it('allows passing custom padding size', () => { +describe('euiFormCustomControlStyles', () => { + it('outputs an object of styles and child element styles', () => { const { result } = renderHook(() => - euiCustomControl(useEuiTheme(), { size: '32px' }) + euiFormCustomControlStyles(useEuiTheme()) ); expect(result.current).toMatchInlineSnapshot(` - " - padding: 15px; - - border: 1px solid #f5f7fc; - background: #FFF no-repeat center; + { + "input": { + "disabled": { + "selected": " + label: disabled; + color: #69707D; + background-color: #D3DAE6; + ", + "unselected": " + label: disabled; + color: #D3DAE6; + background-color: #D3DAE6; + cursor: not-allowed; + ", + }, + "enabled": { + "selected": " + color: #FFF; + background-color: #07C; + ", + "unselected": " + color: transparent; + background-color: #FFF; + border: 1px solid #919296; - @media screen and (prefers-reduced-motion: no-preference) { - transition: background-color 150ms ease-in, - border-color 150ms ease-in; - } - " - `); - }); + &:has(input:focus) { + border-color: #07C; + } + ", + }, + "fauxInput": " + position: relative; + block-size: 16px; + inline-size: 16px; + display: flex; + justify-content: center; + align-items: center; - test('type.round', () => { - const { result } = renderHook(() => - euiCustomControl(useEuiTheme(), { type: 'round' }) - ); - expect(result.current).toMatchInlineSnapshot(` - " - padding: 7px; - border-radius: 16px; - border: 1px solid #f5f7fc; - background: #FFF no-repeat center; + &:has(+ label) { + margin-block-start: 4px; + } - @media screen and (prefers-reduced-motion: no-preference) { - transition: background-color 150ms ease-in, - border-color 150ms ease-in; - } - " - `); - }); - - test('size and type.round changes both padding and border-radius', () => { - const { result } = renderHook(() => - euiCustomControl(useEuiTheme(), { size: '6px', type: 'round' }) - ); - expect(result.current).toMatchInlineSnapshot(` - " - padding: 2px; - border-radius: 6px; - border: 1px solid #f5f7fc; - background: #FFF no-repeat center; + &:has(input:focus-visible) { + outline: 2px solid #07C; + outline-offset: 2px; + } - @media screen and (prefers-reduced-motion: no-preference) { - transition: background-color 150ms ease-in, - border-color 150ms ease-in; - } - " - `); - }); + @media screen and (prefers-reduced-motion: no-preference) { + transition-property: background-color, color; + transition-duration: 150ms; + transition-timing-function: ease-in; + } + ", + "hiddenInput": " + position: absolute; + inset: 0; + opacity: 0 !important; + cursor: pointer; - test('type.square', () => { - const { result } = renderHook(() => - euiCustomControl(useEuiTheme(), { type: 'square' }) - ); - expect(result.current).toMatchInlineSnapshot(` - " - padding: 7px; - border-radius: 4px; - border: 1px solid #f5f7fc; - background: #FFF no-repeat center; - - @media screen and (prefers-reduced-motion: no-preference) { - transition: background-color 150ms ease-in, - border-color 150ms ease-in; - } - " + &:disabled { + cursor: not-allowed; + } + ", + "icon": " + transform: scale(0.75); + ", + }, + "label": { + "disabled": " + cursor: not-allowed; + color: #a2abba; + ", + "enabled": " + cursor: pointer; + ", + "label": " + /* Needs to use padding and not flex gap for extra mouse click area */ + padding-inline-start: 8px; + line-height: 24px; + font-size: 1.0000rem; + ", + }, + "wrapper": " + display: flex; + align-items: flex-start; + ", + } `); }); }); diff --git a/packages/eui/src/components/form/form.styles.ts b/packages/eui/src/components/form/form.styles.ts index 677a90b70c5..3fb08bd23ce 100644 --- a/packages/eui/src/components/form/form.styles.ts +++ b/packages/eui/src/components/form/form.styles.ts @@ -71,16 +71,6 @@ export const euiFormVariables = (euiThemeContext: UseEuiTheme) => { : tint(euiTheme.colors.lightShade, 0.5), }; - // Colors - specific to checkboxes, radios, switches, and range thumbs - const customControlColors = { - customControlDisabledIconColor: isColorDark - ? shade(euiTheme.colors.mediumShade, 0.38) - : tint(euiTheme.colors.mediumShade, 0.485), - customControlBorderColor: isColorDark - ? shade(euiTheme.colors.lightestShade, 0.4) - : tint(euiTheme.colors.lightestShade, 0.31), - }; - const controlLayout = { controlLayoutGroupInputHeight: mathWithUnits(controlHeight, (x) => x - 2), controlLayoutGroupInputCompressedHeight: mathWithUnits( @@ -103,7 +93,6 @@ export const euiFormVariables = (euiThemeContext: UseEuiTheme) => { return { ...sizes, ...colors, - ...customControlColors, ...iconSizes, ...controlLayout, animationTiming: `${euiTheme.animation.fast} ease-in`, @@ -173,45 +162,6 @@ export const euiFormControlStyles = (euiThemeContext: UseEuiTheme) => { }; }; -export const euiCustomControl = ( - euiThemeContext: UseEuiTheme, - options: { - type?: 'round' | 'square'; - size?: string; - } = {} -) => { - const euiTheme = euiThemeContext.euiTheme; - const form = euiFormVariables(euiThemeContext); - const { type, size = euiTheme.size.base } = options; - - let padddingStyle = ''; - let borderRadiusStyle = ''; - - if (size) { - const borderSize = parseFloat(String(euiTheme.border.width.thin)); - const paddingSize = mathWithUnits(size, (x) => (x - borderSize * 2) / 2); - padddingStyle = `padding: ${paddingSize};`; - } - - if (type === 'round') { - borderRadiusStyle = `border-radius: ${size};`; - } else if (type === 'square') { - borderRadiusStyle = `border-radius: ${form.controlCompressedBorderRadius};`; - } - - return ` - ${padddingStyle} - ${borderRadiusStyle} - border: ${euiTheme.border.width.thin} solid ${form.customControlBorderColor}; - background: ${euiTheme.colors.emptyShade} no-repeat center; - - ${euiCanAnimate} { - transition: background-color ${form.animationTiming}, - border-color ${form.animationTiming}; - } - `; -}; - export const euiFormControlText = (euiThemeContext: UseEuiTheme) => { const { euiTheme } = euiThemeContext; const { fontSize } = euiFontSize(euiThemeContext, 's'); @@ -376,3 +326,143 @@ const euiPlaceholderPerBrowser = (content: string) => ` &:-moz-placeholder { ${content} } &::placeholder { ${content} } `; + +/** + * Selection custom controls - checkboxes, radios, and switches + */ + +export const euiFormCustomControlVariables = (euiThemeContext: UseEuiTheme) => { + const { euiTheme, colorMode } = euiThemeContext; + + const sizes = { + control: euiTheme.size.base, + lineHeight: euiTheme.size.l, + labelGap: euiTheme.size.s, + }; + + const colors = { + unselected: euiTheme.colors.emptyShade, + unselectedBorder: + colorMode === 'DARK' + ? tint(euiTheme.colors.lightestShade, 0.31) // WCAG AA requirements + : shade(euiTheme.colors.lightestShade, 0.4), + selected: euiTheme.colors.primary, + selectedIcon: euiTheme.colors.emptyShade, + disabled: euiTheme.colors.lightShade, + disabledIcon: euiTheme.colors.darkShade, + disabledLabel: euiTheme.colors.disabledText, // Lighter than formVars.disabledColor because it typically doesn't have as dark a background + }; + + const animation = { + speed: euiTheme.animation.fast, + easing: 'ease-in', + }; + + return { + sizes, + colors, + animation, + }; +}; + +export const euiFormCustomControlStyles = (euiThemeContext: UseEuiTheme) => { + const { euiTheme } = euiThemeContext; + const controlVars = euiFormCustomControlVariables(euiThemeContext); + + const centerWithLabel = mathWithUnits( + [controlVars.sizes.lineHeight, controlVars.sizes.control], + (x, y) => (x - y) / 2 + ); + + return { + wrapper: ` + display: flex; + align-items: flex-start; + `, + input: { + fauxInput: ` + position: relative; + ${logicalCSS('height', controlVars.sizes.control)} + ${logicalCSS('width', controlVars.sizes.control)} + display: flex; + justify-content: center; + align-items: center; + + &:has(+ label) { + ${logicalCSS('margin-top', centerWithLabel)} + } + + &:has(input:focus-visible) { + outline: ${euiTheme.focus.width} solid ${controlVars.colors.selected}; + outline-offset: ${euiTheme.focus.width}; + } + + ${euiCanAnimate} { + transition-property: background-color, color; + transition-duration: ${controlVars.animation.speed}; + transition-timing-function: ${controlVars.animation.easing}; + } + `, + enabled: { + selected: ` + color: ${controlVars.colors.selectedIcon}; + background-color: ${controlVars.colors.selected}; + `, + unselected: ` + color: transparent; + background-color: ${controlVars.colors.unselected}; + border: ${euiTheme.border.width.thin} solid ${controlVars.colors.unselectedBorder}; + + &:has(input:focus) { + border-color: ${controlVars.colors.selected}; + } + `, + }, + disabled: { + selected: ` + label: disabled; + color: ${controlVars.colors.disabledIcon}; + background-color: ${controlVars.colors.disabled}; + `, + unselected: ` + label: disabled; + color: ${controlVars.colors.disabled}; + background-color: ${controlVars.colors.disabled}; + cursor: not-allowed; + `, + }, + + // Looks better centered at different zoom levels than just + icon: ` + transform: scale(0.75); + `, + + // Hidden input sits on top of the visible element + hiddenInput: ` + position: absolute; + inset: 0; + opacity: 0 !important; + cursor: pointer; + + &:disabled { + cursor: not-allowed; + } + `, + }, + label: { + label: ` + /* Needs to use padding and not flex gap for extra mouse click area */ + ${logicalCSS('padding-left', controlVars.sizes.labelGap)} + line-height: ${controlVars.sizes.lineHeight}; + font-size: ${euiFontSize(euiThemeContext, 's').fontSize}; + `, + enabled: ` + cursor: pointer; + `, + disabled: ` + cursor: not-allowed; + color: ${controlVars.colors.disabledLabel}; + `, + }, + }; +}; diff --git a/packages/eui/src/components/form/radio/__snapshots__/radio.test.tsx.snap b/packages/eui/src/components/form/radio/__snapshots__/radio.test.tsx.snap index 818a48f4fab..c9700ec7d5b 100644 --- a/packages/eui/src/components/form/radio/__snapshots__/radio.test.tsx.snap +++ b/packages/eui/src/components/form/radio/__snapshots__/radio.test.tsx.snap @@ -3,70 +3,90 @@ exports[`EuiRadio is rendered 1`] = `
-
+ class="euiRadio__circle emotion-euiRadio__circle-unselected" + > + + +
`; exports[`EuiRadio props checked is rendered 1`] = `
-
+ class="euiRadio__circle emotion-euiRadio__circle-selected" + > + + +
`; exports[`EuiRadio props disabled is rendered 1`] = `
-
+ class="euiRadio__circle emotion-euiRadio__circle-disabled-unselected" + > + + +
`; exports[`EuiRadio props label is rendered 1`] = `
-
+ class="euiRadio__circle emotion-euiRadio__circle-unselected" + > + + +