eui
eui copied to clipboard
[Meta][CSS-in-JS] Component conversions
Wiki Doc for How to Write Styles with Emotion
Completed
- [x] Accordion (#5826) @1Copenut
- [x] Bottom bar @breehall #5823
- [x] Flex @constancecchen #6270
- [x] Flyout @breehall #6213
- [x] Horizontal rule (#5815)
- [x] Page @cchaos
- [x] Page header @cchaos
- [x] Panel @cchaos #5891
- [x] Popover @cchaos #5977
- [x] Spacer (#5812)
- [x] Button
- #5989
- #6150
- [x] Facet @miukimiu (#5878)
- [x] Link (#5856) @MichaelMarcialis
- [x] Pagination #6109
- [x] Tabs #6311
- [x] Aspect ratio (#5818)
- [x] Avatar (#5670)
- [x] Badge (#6224, #6258)
- [x] Callout @cchaos #5870
- [x] Card #6110
- [x] Comment list @miukimiu (#5692)
- [x] Description list @breehall #5971
- [x] Health @constancecchen #5832
- [x] Icons @miukimiu #5967
- [x] Token @miukimiu #6067
- [x] Image @miukimiu #5969
- [x] List group @miukimiu #6207
- [x] Loading @cchaos #5845
- [x] Loading Chart (#5821) @cchaos
- [x] Progress @constancecchen #5986
- [x] Range sliders @miukimiu #6092
- [x] Stat @constancecchen #5968
- [x] Text @constancecchen #5895
- [x] Title @constancecchen #5842
- [x] Toast #6068
- [x] Tooltip #6104
- [x] Tour #6087
- [x] Code @miukimiu #6263
- [x] #6403 @miukimiu (#6321)
Utilities
- [x] Accessibility
- [x] Screen reader only #5846
- [x] Skip link #5851
- [x] Beacon @miukimiu (#5814)
- [x] CSS utility classes #6124
- [x] Error boundary #6053
- [x] Highlight and mark (#4575)
- [x] Overlay mask #6090
- [x] Portal #6075
- [x] Text diff #6056
## Partially complete
- [x] https://github.com/elastic/eui/issues/6388
- [ ] https://github.com/elastic/eui/issues/6653
- [ ] #6386
- [ ] #6408
- [ ] https://github.com/elastic/eui/issues/6406
## Display
- [ ] #6404
- [ ] #6397
- [ ] #6396
## Navigation
- [ ] #6389
- [ ] #6392
- [ ] #6393
- [ ] #6412
- [ ] #6416
- [ ] #6401
- [x] #6413
- [x] #6418
## Layout
- [ ] #6417
- [ ] https://github.com/elastic/eui/issues/6405
## Tables
- [ ] #6415
- [ ] #6387
## Forms
- [x] https://github.com/elastic/eui/issues/6398
- [ ] https://github.com/elastic/eui/issues/6410
- [ ] https://github.com/elastic/eui/issues/6411
- [ ] https://github.com/elastic/eui/issues/6391
- [ ] https://github.com/elastic/eui/issues/6390
- [ ] #6400
## Low priority (3rd party components or components that may move out of EUI)
- [ ] #6395
- [ ] #6402
- [ ] #6394
- [ ] #6414
## Docs
- [ ] Guide page
- [ ] Guide rule
- [ ] Guide section
Component Conversion Process
Converting the Sass styles to Emotion
1. New file structure
- [ ] In the component directory (in
src/components/{component}), create a new file for Emotion styling. Files should follow the{component}.styles.tsnaming structure (i.eavatar.styles.ts). In this file, convert the existing Sass styles to Emotion. For detailed information on converting styles, formatting styles, and creating style helpers, see writing styles with Emotion. For more detailed conversion checklist see comment below. - [ ] Import the new Emotion style file into the component (
{component}.tsx). ImportuseEuiTheme()from the services directory and configure the component to use Emotion styling.
Example
//imports
import { useEuiTheme } from '../../services';
import { euiAvatarStyles } from './avatar.styles';
//set the theme and configure Emotion styling
const euiTheme = useEuiTheme();
const styles = euiAvatarStyles(euiTheme);
- [ ] Downstream components that take mounted snapshots including the converted component may see some
<Insertion>component shenanigans. These snapshots should be addressed by one of the following options:- Converting
mount()torender() - Converting
mount()tomount().render() - Avoid taking a snapshot and instead using a more specific assertion, depending on the test (e.g. if a specific component is supposed to be present)
- Converting
2a. Convert & Remove Sass component styles
- [ ] Move the Sass styles into the newly created
{component}.styles.ts - [ ] Remove the Sass modules and stylesheets found in the component directory (in
src/components/{component}). This should include the stylesheets and the_index.scssfile found in the component folder. Insrc/components/index.scss, remove the import for the component Sass module
2b. Update style props types by removing classNameMaps where possible
See https://github.com/elastic/eui/pull/5889 for examples.
If the modifier piece of the class name --{modifier} is the same string as the possible prop values, switch to applying the modifier piece of the class name directly as they key. Remove the manual maps of property value to class name maps and change the array of values as an exported array as const.
Example:
const sizeToClassNameMap = {
s: 'euiAvatar--s',
m: 'euiAvatar--m',
l: 'euiAvatar--l',
xl: 'euiAvatar--xl',
};
export const SIZES = keysOf(sizeToClassNameMap);
export type EuiAvatarSize = keyof typeof sizeToClassNameMap;
const classes = classNames(
'euiAvatar',
sizeToClassNameMap[size],
);
Becomes:
export const SIZES = ['s', 'm', 'l', 'xl'] as const;
export type EuiAvatarSize = typeof SIZES[number];
const classes = classNames(
'euiAvatar',
{
[`euiAvatar--${size}`]: size,
},
);
If they don't match completely 1:1, you will need to keep the map, but remove the euiComponentName-- part of the value and still apply the className directly in the function:
export const MARGINS = ['none', 'xs', 's', 'm', 'l', 'xl', 'xxl'] as const;
export type EuiHorizontalRuleMargin = typeof MARGINS[number];
const marginToClassNameMap: {
[value in EuiHorizontalRuleMargin]: string | null;
} = {
none: null,
xs: 'marginXSmall',
...
xxl: 'marginXXLarge',
};
const classes = classNames(
'euiHorizontalRule',
{
[`euiHorizontalRule--${marginToClassNameMap[margin]}`]: margin && margin !== 'none',
},
);
2c. Removing vs. keeping classNames
In the above scenarios, removing the -- modifier classNames/maps may be a perfectly valid choice as well to clean up our classNames logic. Be sure to search through Kibana first for the modifier(s) classes:
- If no frequent Kibana usages come up (e.g. in CSS overrides or test selectors) outside of snapshots, fantastic! Go ahead and remove the classes.
- If there are minor usages, consider replacing the modifier class with a
[data]attribute instead that consumers can target instead of a class.
NOTE: Generally, do not remove the top level className (e.g. .euiComponent) or child element classNames (e.g. .euiComponent__child). These classes serve as useful hooks/markers for consumers to use (both for CSS overrides and test selectors).
3. Move and remove style Amsterdam overrides (if present)
Check src/themes/amsterdam/overrides for component style overrides. If overrides are present:
- [ ] Find the Sass module containing the override styles in
src/themes/amsterdam/overrides/. The file will be named_{component}.scss. - [ ] Prioritize/move these styles into the new
{component}.styles.tsfile - [ ] Delete the old Sass file
- [ ] In
src/themes/amsterdam/overrides/_index.scss, remove the component Sass module import
4. Update component tests
- [ ] Once styling changes have been tested and verified in browser, update component snapshots
- [ ] Update the component test file (
{component}.test.tsx) to use theshouldRenderCustomStylesutility:
Example
import { shouldRenderCustomStyles } from '../../test/internal';
describe('EuiAvatar', () => {
test('it renders', () => { ... });
shouldRenderCustomStyles(<EuiAvatar name="name" />);
// more tests
}
5. Remove styling variables used by the EUI documentation site (if present)
- [ ] Remove any styling variables related to the component from the rendered
.jsonfiles by running the commandyarn compile-scss
6. Deprecate any component-specific mixins
- [ ] Add an
@warnmessage within theglobal_styling/mixins/{component name}.scssfile providing a recommendation if possible
Example if the recommendation is to use the actual React component
@mixin componentMixin(){
...
@warn 'componentMixin() has been deprecated. Wrap your usage with <EuiComponentName /> instead.';
}
7. Update the documentation site playground (if using withEuiTheme HOC)
- [ ] Import and use the component class that is wrapped by
withEuiTheme() - [ ] Pass
trueas the second argument topropUtilityForPlayground
- const docgenInfo = Array.isArray(EuiAccordion.__docgenInfo)
- ? EuiAccordion.__docgenInfo[0]
- : EuiAccordion.__docgenInfo;
- const propsToUse = propUtilityForPlayground(docgenInfo.props);
+ const docgenInfo = Array.isArray(EuiAccordionClass.__docgenInfo)
+ ? EuiAccordionClass.__docgenInfo[0]
+ : EuiAccordionClass.__docgenInfo;
+ const propsToUse = propUtilityForPlayground(docgenInfo.props, true);
8. Changelog
Be sure to add the following header:
**CSS-in-JS conversions**
- Converted ___ to Emotion
If any Sass variables or mixins were removed, append them to the line item use a semi-colon separator
- Converted __ to Emotion; Removed `$___`
EDIT: We've moved this conversion checklist to our GitHub PR template instead. See https://github.com/elastic/eui/pull/6294
Do/When:
If a prop/value pair maps 1:1 to the CSS property: value, pass the value straight through
position?: CSSProperties['position'];
const cssStyles = [
{ position }
];
If a prop's value renders no styles
A. If it's necessary to still know the prop value while debugging, create an empty css`` map for that value
paddingSize = 'none';
const euiComponentStyles = ({
none: css``
})
B. If it's mostly just an empty default state, check for that prop before grabbing the css value
paddingSize = 'none';
const cssStyles = [
paddingSize === 'none' ? undefined : styles[paddingSize]
]