diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFilePicker_Non_Large_Display.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFilePicker_Non_Large_Display.png new file mode 100644 index 00000000000..5a129a1b698 Binary files /dev/null and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFilePicker_Non_Large_Display.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFilePicker_Playground.png b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFilePicker_Playground.png index 45181358c68..da37f816858 100644 Binary files a/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFilePicker_Playground.png and b/packages/eui/.loki/reference/chrome_desktop_Forms_EuiFilePicker_Playground.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFilePicker_Non_Large_Display.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFilePicker_Non_Large_Display.png new file mode 100644 index 00000000000..d952c07e21d Binary files /dev/null and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFilePicker_Non_Large_Display.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFilePicker_Playground.png b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFilePicker_Playground.png index a5eb1a89a61..d95495ed78c 100644 Binary files a/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFilePicker_Playground.png and b/packages/eui/.loki/reference/chrome_mobile_Forms_EuiFilePicker_Playground.png differ diff --git a/packages/eui/changelogs/upcoming/7833.md b/packages/eui/changelogs/upcoming/7833.md new file mode 100644 index 00000000000..14de1c2a968 --- /dev/null +++ b/packages/eui/changelogs/upcoming/7833.md @@ -0,0 +1,3 @@ +**CSS-in-JS conversions** + +- Converted `EuiFilePicker` to Emotion; Removed `$euiFilePickerTallHeight` diff --git a/packages/eui/src/components/form/_index.scss b/packages/eui/src/components/form/_index.scss index 83c850af420..b4c9f0dd4e2 100644 --- a/packages/eui/src/components/form/_index.scss +++ b/packages/eui/src/components/form/_index.scss @@ -1,6 +1,5 @@ @import 'checkbox/index'; @import 'described_form_group/index'; -@import 'file_picker/index'; @import 'form'; @import 'form_control_layout/index'; @import 'form_error_text/index'; diff --git a/packages/eui/src/components/form/file_picker/__snapshots__/file_picker.test.tsx.snap b/packages/eui/src/components/form/file_picker/__snapshots__/file_picker.test.tsx.snap index a3c6c2b9311..58022a1b3d9 100644 --- a/packages/eui/src/components/form/file_picker/__snapshots__/file_picker.test.tsx.snap +++ b/packages/eui/src/components/form/file_picker/__snapshots__/file_picker.test.tsx.snap @@ -2,34 +2,30 @@ exports[`EuiFilePicker is rendered 1`] = `
+
- -
-
+ Select or drag and drop a file +
`; diff --git a/packages/eui/src/components/form/file_picker/_file_picker.scss b/packages/eui/src/components/form/file_picker/_file_picker.scss deleted file mode 100644 index 19324ad7122..00000000000 --- a/packages/eui/src/components/form/file_picker/_file_picker.scss +++ /dev/null @@ -1,212 +0,0 @@ -/** - * REMEMBER: --large modifiers must come last to override --compressed - */ - -.euiFilePicker { - @include euiFormControlSize($includeAlternates: true); - position: relative; - - &.euiFilePicker--large { - border-radius: $euiFormControlBorderRadius; - overflow: hidden; - height: auto; - } - - &.euiFilePicker--large.euiFilePicker--compressed { - border-radius: $euiFormControlCompressedBorderRadius; - } -} - -// The input is an invisible dropzone / button -.euiFilePicker__input { - position: absolute; - left: 0; - top: 0; - width: 100%; - height: 100%; - opacity: 0; - overflow: hidden; - - &:hover { - cursor: pointer; - } - - &:hover:disabled { - cursor: not-allowed; - } - - &:disabled { - opacity: 0; - } - - &:disabled ~ .euiFilePicker__prompt { - color: $euiColorMediumShade; - } -} - -.euiFilePicker__icon { - position: absolute; - left: $euiSizeM; - top: $euiSizeM; - transition: transform $euiAnimSpeedFast $euiAnimSlightResistance; - - .euiFilePicker--compressed & { - top: $euiSizeS; - left: $euiSizeS; - } - - .euiFilePicker--large & { - position: static; - margin-bottom: $euiSize; - } -} - -/** - * 1. Don't block the user from dropping files onto the filepicker. - * 2. Ensure space for import icon, loading spinner, and clear button (only if it has files) - * 4. Static height so that it doesn't shift its surrounding contents around - */ -.euiFilePicker__prompt { - @include euiFormControlWithIcon; /* 2 */ - height: $euiFormControlHeight; - padding-top: $euiFormControlPadding; - padding-right: $euiFormControlPadding; - padding-bottom: $euiFormControlPadding; - pointer-events: none; /* 1 */ - border: $euiBorderWidthThick dashed $euiColorLightShade; - border-radius: $euiFormControlBorderRadius; - - transition: - border-color $euiAnimSpeedFast ease-in, - background-color $euiAnimSpeedFast ease-in; - - .euiFilePicker--compressed & { - @include euiFormControlStyleCompressed($includeStates: false); - @include euiFormControlWithIcon($compressed: true); /* 2 */ - height: $euiFormControlCompressedHeight; - border-radius: $euiFormControlCompressedBorderRadius; - box-shadow: none; - } - - .euiFilePicker--large & { - height: $euiFilePickerTallHeight; /* 4 */ - padding: 0 $euiSizeL; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - } - - .euiFilePicker--large.euiFilePicker--compressed & { - height: $euiFilePickerTallHeight - $euiSizeL; /* 4 */ - } - - // To match other EUI form controls, do not color the border red if focused or disabled - .euiFilePicker-isInvalid:not(.euiFilePicker__showDrop) .euiFilePicker__input:not(:disabled):not(:focus) + & { - border-color: $euiColorDanger; - } -} - -.euiFilePicker__promptText { - @include euiFontSizeS; - @include euiTextTruncate; - line-height: $euiSize; - - // Make normal sized prompt stand out a bit more - on the large size we don't need this as it's already identifiable - .euiFilePicker:not(.euiFilePicker--large):not(.euiFilePicker-hasFiles) & { - color: $euiColorPrimaryText; - } - - // Offset/center prompt text for non-large file-pickers - .euiFilePicker:not(.euiFilePicker--large) & { - margin-top: $euiSizeXS / -2; - } -} - -.euiFilePicker__clearButton, -.euiFilePicker__loadingSpinner { - position: absolute; - right: $euiSizeM; - top: $euiSizeM; - - .euiFilePicker--compressed & { - top: $euiSizeS; - } -} - -/** - * 1. Undo the pointer-events: none applied to the enclosing prompt. - */ -.euiFilePicker__clearButton { - pointer-events: auto; /* 1 */ - - .euiFilePicker:not(.euiFilePicker--large) & { - @include euiFormControlLayoutClearIcon('.euiFilePicker__clearIcon'); - } - - .euiFilePicker--large & { - position: relative; - top: 0; - right: 0; - } -} - -// Focus -.euiFilePicker__showDrop .euiFilePicker__prompt, -.euiFilePicker__input:focus + .euiFilePicker__prompt { - border-color: $euiColorPrimary; -} - -// Disabled -.euiFilePicker__input:disabled + .euiFilePicker__prompt { - @include euiFormControlDisabledStyle; - box-shadow: none; -} - -// Make space for the icons on the right -.euiFilePicker:not(.euiFilePicker--large) { - &.euiFilePicker-isLoading .euiFilePicker__prompt, - &.euiFilePicker-hasFiles .euiFilePicker__prompt { - @include euiFormControlWithIcon(false, 'right'); /* 2 */ - } -} - -// When the filepicker has files in it -.euiFilePicker-hasFiles .euiFilePicker__promptText { - color: $euiTextColor; -} - -// Large styles only -.euiFilePicker--large { - // stylelint-disable max-nesting-depth - // Hover and focus - .euiFilePicker__input:hover:not(:disabled), - .euiFilePicker__input:focus { - + .euiFilePicker__prompt { - .euiFilePicker__promptText { - // Adding the underline to the prompt text ensures the underline - // color is the same as the current text color - text-decoration: underline; - } - - .euiFilePicker__icon { - transform: scale(1.1); - } - } - } - - // While dragging files over the dropzone - &.euiFilePicker__showDrop .euiFilePicker__prompt { - .euiFilePicker__promptText { - text-decoration: underline; - } - - .euiFilePicker__icon { - transform: scale(1.1); - } - } - - &.euiFilePicker-hasFiles .euiFilePicker__promptText { - font-weight: $euiFontWeightBold; - } -} diff --git a/packages/eui/src/components/form/file_picker/_index.scss b/packages/eui/src/components/form/file_picker/_index.scss deleted file mode 100644 index a9f4c98bd34..00000000000 --- a/packages/eui/src/components/form/file_picker/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import 'variables'; -@import 'file_picker'; diff --git a/packages/eui/src/components/form/file_picker/_variables.scss b/packages/eui/src/components/form/file_picker/_variables.scss deleted file mode 100644 index 17c8f9058a0..00000000000 --- a/packages/eui/src/components/form/file_picker/_variables.scss +++ /dev/null @@ -1 +0,0 @@ -$euiFilePickerTallHeight: $euiSize * 8; diff --git a/packages/eui/src/components/form/file_picker/file_picker.stories.tsx b/packages/eui/src/components/form/file_picker/file_picker.stories.tsx index cc0cf14e867..484df762442 100644 --- a/packages/eui/src/components/form/file_picker/file_picker.stories.tsx +++ b/packages/eui/src/components/form/file_picker/file_picker.stories.tsx @@ -34,3 +34,7 @@ export default meta; type Story = StoryObj; export const Playground: Story = {}; + +export const NonLargeDisplay: Story = { + args: { display: 'default' }, +}; diff --git a/packages/eui/src/components/form/file_picker/file_picker.styles.ts b/packages/eui/src/components/form/file_picker/file_picker.styles.ts new file mode 100644 index 00000000000..59a9b08cf29 --- /dev/null +++ b/packages/eui/src/components/form/file_picker/file_picker.styles.ts @@ -0,0 +1,195 @@ +/* + * 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 { + euiCanAnimate, + euiFontSize, + euiTextTruncate, + logicalCSS, + mathWithUnits, +} from '../../../global_styling'; +import { euiFormControlStyles, euiFormVariables } from '../form.styles'; + +export const euiFilePickerStyles = (euiThemeContext: UseEuiTheme) => { + const { euiTheme } = euiThemeContext; + const formStyles = euiFormControlStyles(euiThemeContext); + const formVariables = euiFormVariables(euiThemeContext); + const { fontSize, lineHeight } = euiFontSize(euiThemeContext, 's'); + + return { + euiFilePicker: css` + --euiFormControlLeftIconsCount: 1; /* Manually account for .euiFilePicker__icon */ + position: relative; + + &:has(input:focus) { + --euiFormControlStateColor: ${euiTheme.colors.primary}; + } + `, + isDroppingFile: css` + --euiFormControlStateColor: ${euiTheme.colors.primary}; + `, + invalid: css` + --euiFormControlStateColor: ${euiTheme.colors.danger}; + `, + hasFiles: css` + --euiFormControlRightIconsCount: 1; + font-weight: ${euiTheme.font.weight.bold}; + `, + loading: css` + --euiFormControlRightIconsCount: 1; + + /* Clip EuiProgress loading indicator that renders for large displays */ + border-radius: ${formVariables.controlCompressedBorderRadius}; + overflow: hidden; + `, + + // Skip the css() on the default width to avoid generating a className + formWidth: formStyles.formWidth, + fullWidth: css(formStyles.fullWidth), + + // The input is an invisible dropzone / button + input: { + euiFilePicker__input: css` + position: absolute; + inset: 0; + opacity: 0; + + &:hover { + cursor: pointer; + } + + &:hover:disabled { + cursor: not-allowed; + } + + &:disabled { + opacity: 0; + } + `, + largeInteractive: css` + &:hover, + &:focus, + .euiFilePicker-isDroppingFile & { + & + .euiFilePicker__prompt { + .euiFilePicker__promptText { + text-decoration: underline; + } + + .euiFilePicker__icon { + transform: scale(1.1); + } + } + } + `, + }, + + euiFilePicker__prompt: css` + pointer-events: none; /* Don't block the user from dropping files onto the filepicker */ + font-size: ${fontSize}; + line-height: 1; /* Vertically centers default display text */ + ${euiTextTruncate()} + color: ${euiTheme.colors.text}; + border: ${euiTheme.border.width.thick} dashed + var(--euiFormControlStateColor, ${euiTheme.colors.lightShade}); + + ${euiCanAnimate} { + transition: border-color ${euiTheme.animation.fast} ease-in, + background-color ${euiTheme.animation.fast} ease-in; + } + `, + disabled: css(formStyles.disabled), + + // Skip the css() on the default height to avoid generating a className + uncompressed: formStyles.uncompressed, + compressed: css(formStyles.compressed), + + // Completely different rendering style from the normal form controls + large: { + large: css` + padding-inline: ${euiTheme.size.l}; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + /* Child text truncation needed on prompt text due to flex display */ + .euiFilePicker__promptText { + ${euiTextTruncate()} + line-height: ${lineHeight}; /* Fix descenders getting cut off */ + } + `, + // Static heights so that surrounding contents don't shift around + uncompressed: ` + ${logicalCSS( + 'height', + mathWithUnits(euiTheme.size.base, (x) => x * 8) + )} + border-radius: ${formVariables.controlBorderRadius}; + `, + compressed: css` + ${logicalCSS( + 'height', + mathWithUnits(euiTheme.size.base, (x) => x * 6.5) + )} + border-radius: ${formVariables.controlCompressedBorderRadius}; + `, + }, + + icon: { + euiFilePicker__icon: css``, + + normal: css` + position: absolute; + ${logicalCSS('top', 0)} + ${logicalCSS('height', '100%')} + `, + uncompressed: ` + ${logicalCSS('left', euiTheme.size.m)} + `, + compresssed: css` + ${logicalCSS('left', euiTheme.size.s)} + ${logicalCSS('width', euiTheme.size.m)} + `, + + large: css` + ${logicalCSS('margin-bottom', euiTheme.size.base)} + + ${euiCanAnimate} { + transition: transform ${euiTheme.animation.fast} + ${euiTheme.animation.resistance}; + } + `, + }, + + rightIcon: { + euiFilePicker__rightIcon: css` + position: absolute; + `, + uncompressed: ` + ${logicalCSS('top', euiTheme.size.m)} + ${logicalCSS('right', euiTheme.size.m)} + `, + compressed: css` + ${logicalCSS('right', euiTheme.size.s)} + ${logicalCSS('top', '50%')} + ${logicalCSS( + 'margin-top', + mathWithUnits(euiTheme.size.m, (x) => x / -2) + )} + `, + }, + + euiFilePicker__clearButton: css` + pointer-events: auto; /* Undo the pointer-events: none applied to the enclosing prompt */ + position: relative; /* Required to sit above hidden input */ + `, + }; +}; diff --git a/packages/eui/src/components/form/file_picker/file_picker.test.tsx b/packages/eui/src/components/form/file_picker/file_picker.test.tsx index 6d6a372b2f7..39ce2db5ce1 100644 --- a/packages/eui/src/components/form/file_picker/file_picker.test.tsx +++ b/packages/eui/src/components/form/file_picker/file_picker.test.tsx @@ -9,11 +9,20 @@ import React from 'react'; import { requiredProps } from '../../../test'; import { render } from '../../../test/rtl'; +import { shouldRenderCustomStyles } from '../../../test/internal'; import { EuiForm } from '../form'; import { EuiFilePicker } from './file_picker'; describe('EuiFilePicker', () => { + shouldRenderCustomStyles(, { skip: { style: true } }); + + shouldRenderCustomStyles(, { + // inline styles are applied to input instead of wrapper + skip: { className: true, css: true }, + targetSelector: 'input', + }); + test('is rendered', () => { const { container } = render(); @@ -29,7 +38,7 @@ describe('EuiFilePicker', () => { ); const filePicker = container.querySelector('.euiFilePicker'); - expect(filePicker).toHaveClass('euiFilePicker--fullWidth'); + expect(filePicker!.className).toContain('fullWidth'); }); }); }); diff --git a/packages/eui/src/components/form/file_picker/file_picker.tsx b/packages/eui/src/components/form/file_picker/file_picker.tsx index f65127353b2..38b530fb34d 100644 --- a/packages/eui/src/components/form/file_picker/file_picker.tsx +++ b/packages/eui/src/components/form/file_picker/file_picker.tsx @@ -9,25 +9,24 @@ import React, { Component, InputHTMLAttributes, ReactNode } from 'react'; import classNames from 'classnames'; -import { CommonProps, keysOf } from '../../common'; +import { + withEuiStylesMemoizer, + WithEuiStylesMemoizerProps, + htmlIdGenerator, +} from '../../../services'; +import { CommonProps } from '../../common'; -import { EuiValidatableControl } from '../validatable_control'; import { EuiButtonEmpty } from '../../button'; import { EuiProgress } from '../../progress'; import { EuiIcon } from '../../icon'; import { EuiI18n } from '../../i18n'; import { EuiLoadingSpinner } from '../../loading'; -import { htmlIdGenerator } from '../../../services/accessibility'; -import { FormContext, FormContextValue } from '../eui_form_context'; -const displayToClassNameMap = { - default: null, - large: 'euiFilePicker--large', -}; - -export const DISPLAYS = keysOf(displayToClassNameMap); +import { FormContext, FormContextValue } from '../eui_form_context'; +import { EuiValidatableControl } from '../validatable_control'; +import { EuiFormControlLayoutClearButton } from '../form_control_layout/form_control_layout_clear_button'; -export type EuiFilePickerDisplay = keyof typeof displayToClassNameMap; +import { euiFilePickerStyles } from './file_picker.styles'; export interface EuiFilePickerProps extends CommonProps, @@ -37,6 +36,7 @@ export interface EuiFilePickerProps className?: string; /** * The content that appears in the dropzone if no file is attached + * @default 'Select or drag and drop a file' */ initialPromptText?: ReactNode; /** @@ -45,14 +45,16 @@ export interface EuiFilePickerProps onChange?: (files: FileList | null) => void; /** * Reduces the size to a typical (compressed) input + * @default false */ compressed?: boolean; /** * Size or type of display; * `default` for normal height, similar to other controls; * `large` for taller size + * @default large */ - display?: EuiFilePickerDisplay; + display?: 'default' | 'large'; /** * Expand to fill 100% of the parent. * Defaults to `fullWidth` prop of ``. @@ -64,7 +66,9 @@ export interface EuiFilePickerProps disabled?: boolean; } -export class EuiFilePicker extends Component { +export class EuiFilePickerClass extends Component< + EuiFilePickerProps & WithEuiStylesMemoizerProps +> { static contextType = FormContext; static defaultProps: Partial = { @@ -145,6 +149,7 @@ export class EuiFilePicker extends Component { > {(clearSelectedFiles: string) => { const { + stylesMemoizer, id, name, initialPromptText, @@ -167,11 +172,8 @@ export class EuiFilePicker extends Component { const classes = classNames( 'euiFilePicker', - displayToClassNameMap[display!], { - euiFilePicker__showDrop: this.state.isHoveringDrop, - 'euiFilePicker--compressed': compressed, - 'euiFilePicker--fullWidth': fullWidth, + 'euiFilePicker-isDroppingFile': this.state.isHoveringDrop, 'euiFilePicker-isInvalid': isInvalid, 'euiFilePicker-isLoading': isLoading, 'euiFilePicker-hasFiles': isOverridingInitialPrompt, @@ -179,28 +181,81 @@ export class EuiFilePicker extends Component { className ); + const styles = stylesMemoizer(euiFilePickerStyles); + const cssStyles = [ + styles.euiFilePicker, + fullWidth ? styles.fullWidth : styles.formWidth, + this.state.isHoveringDrop && styles.isDroppingFile, + isInvalid && !disabled && styles.invalid, + isOverridingInitialPrompt && !disabled && styles.hasFiles, + isLoading && styles.loading, + ]; + + const inputStyles = [ + styles.input.euiFilePicker__input, + !normalFormControl && !disabled && styles.input.largeInteractive, + ]; + + const promptStyles = [ + styles.euiFilePicker__prompt, + disabled && styles.disabled, + ...(normalFormControl + ? [compressed ? styles.compressed : styles.uncompressed] + : [ + styles.large.large, + compressed + ? styles.large.compressed + : styles.large.uncompressed, + ]), + ]; + + const iconStyles = [ + styles.icon.euiFilePicker__icon, + ...(normalFormControl + ? [ + styles.icon.normal, + compressed + ? styles.icon.compresssed + : styles.icon.uncompressed, + ] + : [styles.icon.large]), + ]; + + const rightIconStyles = normalFormControl + ? [ + styles.rightIcon.euiFilePicker__rightIcon, + compressed + ? styles.rightIcon.compressed + : styles.rightIcon.uncompressed, + ] + : undefined; + let clearButton; if (isLoading && normalFormControl) { // Override clear button with loading spinner if it is in loading state clearButton = ( - + ); } else if (isOverridingInitialPrompt && !disabled) { if (normalFormControl) { clearButton = ( - + size={compressed ? 's' : 'm'} + /> ); } else { clearButton = ( { ); return ( -
-
- - { - this.fileInput = input; - }} - onDragOver={this.showDrop} - onDragLeave={this.hideDrop} - onDrop={this.hideDrop} - disabled={disabled} - aria-describedby={promptId} - {...rest} - /> - -
-
+
+ + { + this.fileInput = input; + }} + onDragOver={this.showDrop} + onDragLeave={this.hideDrop} + onDrop={this.hideDrop} + disabled={disabled} + aria-describedby={promptId} + {...rest} + /> + +
+
); @@ -265,3 +324,6 @@ export class EuiFilePicker extends Component { ); } } + +export const EuiFilePicker = + withEuiStylesMemoizer(EuiFilePickerClass);