Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CSS-in-JS] Convert EuiMark #4575

Merged
merged 16 commits into from
Mar 15, 2022
3 changes: 2 additions & 1 deletion .babelrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ module.exports = {
[
"@emotion/babel-preset-css-prop",
{
"labelFormat": "[filename]-[local]"
"autoLabel": "always",
Copy link
Contributor Author

@thompsongl thompsongl Feb 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the long-form class name defined by labelFormat in dev and prod environments (prod would use a short, mangled string otherwise).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A little confused here because you mention "in dev and prod environments" but then say that prod would use the short/uglified string. Just to clarify, the long-form string is in dev only, and the short-form string is prod only?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The properties are confusing, yes. "autoLabel": "always" makes it so that the long-form string is used in both prod and dev environments. We don't want to use the short-form string in any case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh my reading comprehension is at an all time low today 🤦 Sorry, I totally missed the "otherwise" in that parentheses! Alrighty, I get it now, so always a long-form string.

"labelFormat": "[local]"
},
],
],
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

- Removed Legacy theme including compiled CSS ([#5688](https://github.com/elastic/eui/pull/5688))

**CSS-in-JS conversions**

- Converted `EuiMark` to CSS-in-JS styling ([#4575](https://github.com/elastic/eui/pull/4575))

## [`51.1.0`](https://github.com/elastic/eui/tree/v51.1.0)

- Updated `testenv` mock for `EuiFlyout` to include default `aria-label` on the close button ([#5702](https://github.com/elastic/eui/pull/5702))
Expand Down
28 changes: 23 additions & 5 deletions src/components/highlight/__snapshots__/highlight.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`EuiHighlight behavior loose matching matches strings with different casing 1`] = `
.emotion-0 {
background-color: rgba(0,119,204,0.1);
font-weight: 700;
color: #343741;
}
Comment on lines +4 to +8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can come back to this in another PR, but I think we'll find having these styles output into the snapshots are going to weight heavy on larger components.


<span>
different
<mark
class="euiMark"
class="euiMark emotion-0"
>
case
</mark>
Expand All @@ -13,31 +19,43 @@ exports[`EuiHighlight behavior loose matching matches strings with different cas
`;

exports[`EuiHighlight behavior matching applies to all matches 1`] = `
.emotion-0 {
background-color: rgba(0,119,204,0.1);
font-weight: 700;
color: #343741;
}

<span>
<mark
class="euiMark"
class="euiMark emotion-0"
>
match
</mark>

<mark
class="euiMark"
class="euiMark emotion-0"
>
match
</mark>

<mark
class="euiMark"
class="euiMark emotion-0"
>
match
</mark>
</span>
`;

exports[`EuiHighlight behavior matching only applies to first match 1`] = `
.emotion-0 {
background-color: rgba(0,119,204,0.1);
font-weight: 700;
color: #343741;
}

<span>
<mark
class="euiMark"
class="euiMark emotion-0"
>
match
</mark>
Expand Down
1 change: 0 additions & 1 deletion src/components/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
@import 'list_group/index';
@import 'loading/index';
@import 'markdown_editor/index';
@import 'mark/index';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EuiMark now has no legacy styles. Potentially a breaking change.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely seems like a breaking change 🤔

Just to clarify, are we merging all conversions into a feature branch (rather than main) and then flipping the switch on removing the legacy Sass theme all at once? (+ coordinating with Kibana so they remove the option to switch back to the legacy theme?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think only the first few will go into the feature branch while we establish various patterns. After that we can go straight to main.

coordinating with Kibana so they remove the option to switch back to the legacy theme

I think legacy has been removed entirely from Kibana already.

Copy link
Member

@cee-chen cee-chen Feb 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh nice! So it's just a question of removing the legacy theme in our own docs site, then? Should we deprecate the legacy toggle at the same time we start merging these changes into main?

Copy link
Contributor Author

@thompsongl thompsongl Feb 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, we should just open a PR to remove the legacy theme entirely.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest, I think we should just merge this PR into main so that we can evaluate consumer affects before marching onward.

And yes it's a "breaking" change, but only to the Legacy theme that we should be removing very soon, so not all these following conversions will be breaking.

@import 'modal/index';
@import 'notification/index';
@import 'overlay_mask/index';
Expand Down
8 changes: 7 additions & 1 deletion src/components/mark/__snapshots__/mark.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`EuiMark is rendered 1`] = `
.emotion-0 {
background-color: rgba(0,119,204,0.1);
font-weight: 700;
color: #343741;
}

<mark
class="euiMark"
class="euiMark emotion-0"
>
Marked
</mark>
Expand Down
1 change: 0 additions & 1 deletion src/components/mark/_index.scss

This file was deleted.

7 changes: 0 additions & 7 deletions src/components/mark/_mark.scss

This file was deleted.

26 changes: 26 additions & 0 deletions src/components/mark/mark.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* 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, transparentize } from '../../services';

export const euiMarkStyles = ({ euiTheme, colorMode }: UseEuiTheme) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Result of useEuiTheme comes as a param instead of used directly so that the style factory is not dependent on React context.

// TODO: Was $euiFocusBackgroundColor
const transparency = { LIGHT: 0.1, DARK: 0.3 };
cee-chen marked this conversation as resolved.
Show resolved Hide resolved

return css`
background-color: ${transparentize(
euiTheme.colors.primary,
transparency[colorMode]
)};
font-weight: ${euiTheme.font.weight.bold};
// Override the browser's black color.
// Can't use 'inherit' because the text to background color contrast may not be sufficient
color: ${euiTheme.colors.text};
`;
};
8 changes: 6 additions & 2 deletions src/components/mark/mark.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
*/

import React, { HTMLAttributes, FunctionComponent, ReactNode } from 'react';
import { CommonProps } from '../common';
import classNames from 'classnames';
import { CommonProps } from '../common';
import { useEuiTheme } from '../../services';
import { euiMarkStyles } from './mark.styles';
export type EuiMarkProps = HTMLAttributes<HTMLElement> &
CommonProps & {
/**
Expand All @@ -22,10 +24,12 @@ export const EuiMark: FunctionComponent<EuiMarkProps> = ({
className,
...rest
}) => {
const useTheme = useEuiTheme();
const styles = euiMarkStyles(useTheme);
Comment on lines +27 to +28
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DevEx question: why pass a hook into a separate file? Why not just make euiMarkStyles useEuiMarkStyles and call useEuiTheme directly in that file?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, to be clear, I'm proposing this instead:

// mark.tsx
const styles = useEuiMarkStyles();

// mark.styles.ts
import { css } from '@emotion/react';
import { useEuiTheme, transparentize } from '../../services';

export const useEuiMarkStyles = () => {
  const { euiTheme, colorMode } = useEuiTheme();

  return css `// ... etc`;
};

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah sorry, I see you addressed this in https://github.com/elastic/eui/pull/4575/files#r808436110 - I'm still not totally clear on the rationale behind that comment though. Is it a bad thing for a style file to be dependent on React context? TBH, keeping our usage of EUI theming to the style-focused file makes more sense to me from a developer POV.

I'm also very curious what usage would look like with a traditional class component (i.e.: can't use hooks) vs. a functional component. Do we have an example of that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did this so that it's possible to just pass in a theme shaped object and get styles, without needing the full React context. No reason we can't do a two-step thing for internal use. That is, keep euiMarkStyles context-agnostic, but add useEuiMarkStyles that is a convenience useEuiTheme() + euiMarkStyles(euiTheme) wrapper.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In more complex cases, we'll probably need to have useEuiTheme in the component file to do dynamic styles with the style prop. In that case this reduces the number of context calls. (More coming on the dynamic styles bit, likely in the EuiAvatar PR).

Copy link
Member

@cee-chen cee-chen Mar 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha! I think the benefits will be clearer with a dynamic styles example + a class component example (I assume we will need to use a HOC for class components). If we could add those examples to the TBD wiki section along with a basic example that would be amazing!

const classes = classNames('euiMark', className);

return (
<mark className={classes} {...rest}>
<mark css={[styles]} className={classes} {...rest}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok so the output class list is now: euiMark css-1izqibv-euiMarkStyles-EuiMark which is coming from the classes above and the Emotion setting "labelFormat": "[local]" which by using the array format will spit out the component name, in this case -EuiMark and the function(?) name -euiMarkStyles.

Seems like a lot of variations of euiMark. But, this may be better evaluated fully in the avatar PR which has more style variations. So for now, I think it's fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, we can reduce the verbosity if deemed necessary. These are only referential so no changes will ever be breaking.

{children}
</mark>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2127,6 +2127,12 @@ exports[`EuiSelectableListItem props rowHeight 1`] = `
`;

exports[`EuiSelectableListItem props searchValue 1`] = `
.emotion-0 {
background-color: rgba(0,119,204,0.1);
font-weight: 700;
color: #343741;
}

<div
class="euiSelectableList testClass1 testClass2"
data-test-subj="test subject string"
Expand Down Expand Up @@ -2222,7 +2228,7 @@ exports[`EuiSelectableListItem props searchValue 1`] = `
>
<span>
<mark
class="euiMark"
class="euiMark emotion-0"
>
Mi
</mark>
Expand Down Expand Up @@ -2322,6 +2328,12 @@ exports[`EuiSelectableListItem props searchValue 1`] = `
`;

exports[`EuiSelectableListItem props searchValue 2`] = `
.emotion-0 {
background-color: rgba(0,119,204,0.1);
font-weight: 700;
color: #343741;
}

<div
class="euiSelectableList testClass1 testClass2"
data-test-subj="test subject string"
Expand Down Expand Up @@ -2417,7 +2429,7 @@ exports[`EuiSelectableListItem props searchValue 2`] = `
>
<span>
<mark
class="euiMark"
class="euiMark emotion-0"
>
Mi
</mark>
Expand Down
1 change: 0 additions & 1 deletion src/themes/amsterdam/overrides/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
@import 'list_group_item';
@import 'image';
@import 'key_pad_menu';
@import 'mark';
@import 'markdown_editor';
@import 'modal';
@import 'notification_badge';
Expand Down
3 changes: 0 additions & 3 deletions src/themes/amsterdam/overrides/_mark.scss

This file was deleted.

11 changes: 4 additions & 7 deletions wiki/creating-components-manually.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
# Creating components manually

## Create component SCSS files
## Create component style files

1. Create a directory for your component in `src/components`.
2. In this directory, create `_{component name}.scss`.
3. _Optional:_ Create any other components that should be [logically-grouped][docs-logical-group] in this directory.
4. Create an `_index.scss` file in this directory that import all of the new component SCSS files you created.
5. Import the `_index.scss` file into `src/components/index.scss`.
2. Create a `{component name}.styles.ts` file inside the directory

This makes your styles available to your project and to the [EUI documentation][docs].
Refer to the [Writing styles with emotion](wiki/writing-styles-with-emotion.md) guidelines for more instruction.
thompsongl marked this conversation as resolved.
Show resolved Hide resolved

## Create the React component

1. Create the React component(s) (preferably as TypeScript) in the same directory as the related SCSS file(s).
1. Create the React component(s) (preferably as TypeScript) in the same directory as the related style file(s).
2. Export these components from an `index.ts` file.
3. Re-export these components from `src/components/index.ts`.

Expand Down
38 changes: 37 additions & 1 deletion wiki/writing-styles-with-emotion.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,36 @@
EUI uses [`Emotion`](https://emotion.sh/) when writing CSS-in-JS styles.
A general knowledge of writing CSS is enough in most cases, but there are some JavaScript-related differences that can result in unintended output. Similarly, there are feaures that don't exist in CSS of which we like to take advantage.


## File patterns

```ts
/* {component name}.styles.ts */
import { css } from '@emotion/react';
import { UseEuiTheme } from '../../services';

export const euiComponentNameStyles = ({ euiTheme }: UseEuiTheme) => {
return css`
color: ${euiTheme.colors.primary};
`;
};
```

```tsx
/* {component name}.tsx */
import { useEuiTheme } from '../../services';
import { euiComponentNameStyles } from './{component name}.styles.ts';

export const EuiComponent = () => {
const theme = useEuiTheme();
const styles = euiComponentStyles(theme);

return (
<div css={[styles]} />
);
};
```

## Conditional styles

Styles can be added conditionally based on environment variables, such as the active theme, using nested string template literals.
Expand All @@ -22,4 +52,10 @@ Although possible in some contexts, it is not recommended to "shortcut" logic us

## The `css` prop

TBD
_Work in progress_

* Use an array inside of the `css` prop for optimal style composition and class name generation. This is relevant even if only a single style object is passed.

```tsx
<EuiComponent css={[styles.default, styles.stateful]} />
```