Skip to content

Instantly share code, notes, and snippets.

@cchaos
Last active July 30, 2020 18:23
Show Gist options
  • Save cchaos/6f0da941c831632b91f679675f278bd8 to your computer and use it in GitHub Desktop.
Save cchaos/6f0da941c831632b91f679675f278bd8 to your computer and use it in GitHub Desktop.
Emotion in EUI - Concept A
import chroma from 'chroma-js';
import { Theme, StyleConfig } from '../../services/propagate/create_style';
const $euiCallOutTypes = {
primary: 'euiColorPrimary',
success: 'euiColorSuccess',
warning: 'euiColorWarning',
danger: 'euiColorDanger',
};
function euiCallOutColor(
colors: any,
type: keyof typeof $euiCallOutTypes = 'primary',
returnBackgroundOrForeground: string = 'background'
) {
const $color = colors[$euiCallOutTypes[type]];
// const $backgroundColor = tintOrShade($color, 90%, 70%);
const $backgroundColor = chroma($color)
.luminance(0.9)
.hex();
// const $foregroundColor = shadeOrTint(makeHighContrastColor($color, $backgroundColor), 0, 20%);
const $foregroundColor = $color;
if (returnBackgroundOrForeground === 'background') {
return $backgroundColor;
} else if (returnBackgroundOrForeground === 'foreground') {
return $foregroundColor;
}
}
export const EuiCallOutStyle = ({
colors,
sizes,
borders,
}: Theme): StyleConfig => {
return {
base: {
borderLeft: `${borders.euiBorderWidthThick} solid transparent`,
},
size: {
s: {
padding: sizes.euiSizeS,
},
m: {
padding: sizes.euiSize,
},
},
color: Object.keys($euiCallOutTypes).reduce((styles, color) => {
return {
...styles,
[color]: {
borderColor: colors[$euiCallOutTypes[color]],
backgroundColor: euiCallOutColor(colors, color, 'background'),
},
};
}, {}),
};
};
export const EuiCallOutAmsterdamStyle = ({
theme,
borders,
}: Theme): StyleConfig | undefined => {
if (!theme.includes('amsterdam')) return;
return {
base: {
borderRadius: borders.euiBorderRadius,
borderLeftWidth: 0,
},
};
};
/**
* 1. Align icon with first line of title text if it wraps.
* 2. If content exists under the header, space it appropriately.
* 3. Apply margin to all but last item in the flex.
*/
export const EuiCallOutHeader = ({ sizes }: Theme): StyleConfig => {
return {
base: {
display: 'flex',
alignItems: 'baseline' /* 1 */,
'+ *': {
marginTop: sizes.euiSize /* 2 */,
},
'> * + *': {
marginLeft: sizes.euiSize /* 3 */,
},
},
};
};
export const EuiCallOutHeaderIcon = ({ colors }): StyleConfig => {
return {
base: {
flex: '0 0 auto',
/* Vertically center icon with first line of title */
transform: 'translateY(2px)',
},
color: Object.keys($euiCallOutTypes).reduce((styles, color) => {
return {
...styles,
[color]: {
fill: euiCallOutColor(colors, color, 'foreground'),
},
};
}, {}),
};
};
export const euiCallOutTitleStyles = ({
theme,
colors,
typography,
}: Theme): StyleConfig => {
return {
base: {
fontWeight: theme.includes('amsterdam')
? typography.euiFontWeightMedium
: typography.euiFontWeightRegular,
marginBottom: 0,
},
size: {
// The following won't work because it returns a string
s: typography.euiTitleXXS,
m: typography.euiTitleXS,
},
color: Object.keys($euiCallOutTypes).reduce((styles, color) => {
return {
...styles,
[color]: {
color: euiCallOutColor(colors, color, 'foreground'),
},
};
}, {}),
};
};
import usePropagate from '../../services/propagate/use_propagate';
import { css, SerializedStyles } from '@emotion/core';
export type Theme = {
colorMode: 'light' | 'dark';
theme: string;
colors: any;
sizes: any;
borders: any;
typography: any;
};
export interface StyleConfig {
/**
* Base applies styles to the element that aren't prop dependent
* These `any`s would ideally match Emotion's accepted types
*/
base?: any;
/**
* This generic key/value combos sets up styles for prop dependencies
* Like `size: { s: smallStyles, m: mediumStyles }`
*/
[propName: string]: { [propValue: string]: any }; // Anything props
}
export const createStyle = (
/**
* Required to append a particular label (class name reference) for easy debugging
*/
label: string,
/**
* The actual StyleConfig function that accepts a Theme
*/
style: ({ }: Theme) => StyleConfig | undefined,
/**
* The optional props that the StyleConfig relies on/manipulates styles of
*/
props?: { [key: string]: any }
): SerializedStyles | undefined => {
// Ideally the theme hook just passes back an object of all the globals to pass on to the StyleConfig
const [themeName, colors, sizes, borders, typography] = usePropagate([
'name',
'colors',
'sizes',
'borders',
'typography',
]);
const theme: Theme = {
colorMode: themeName.includes('light') ? 'light' : 'dark',
theme: themeName,
colors,
sizes,
borders,
typography,
};
// Pass the current theme to the style function to return the interpolated StyleConfig object
const computedStyle = style(theme);
if (!computedStyle) return;
// Create a consistent class naming structure for easy debugging
// The `autoLabel` option in babel has been set to `false`
let computedLabel = `label:-${label}`;
// Loop through the passed props object and apply the correct styles
// Depending on the state of the props
const propStyles = props
? Object.keys(props).reduce((styles, propName) => {
// Append the style modifier to the label as well
computedLabel += `-${props[propName]}`;
return {
...styles,
...computedStyle[propName][props[propName]],
};
}, {})
: undefined;
// Return the SerializedStyle with the custom label to append to the class
return css(
{
...computedStyle.base,
...propStyles,
},
computedLabel
);
};
import React, { HTMLAttributes, FunctionComponent } from 'react';
import { CommonProps } from '../common';
import classNames from 'classnames';
import { EuiMarkAmsterdamStyle, EuiMarkStyle } from './mark_style';
import { createStyle } from './create_style';
export type EuiMarkProps = HTMLAttributes<HTMLElement> &
CommonProps & {
children: string;
size?: 's' | 'm';
};
export const EuiMark: FunctionComponent<EuiMarkProps> = ({
children,
className,
size = 's',
...rest
}) => {
const classes = classNames('euiMark', className);
return (
<mark
css={[
createStyle('euiMark', EuiMarkStyle, { size }), // Ex. of composed class is `css-hash--euiMark-s`
// Each style appends it's custom label to the previous style
createStyle('amsterdam', EuiMarkAmsterdamStyle), // Ex. of composed class is `css-hash--euiMark-s--amsterdam`
]}
className={classes}
{...rest}>
{children}
</mark>
);
};
import { euiSize } from '../../global_styling/variables/sizes';
import { isColorDark, hexToRgb } from '../../services';
import { Theme, StyleConfig } from './create_style';
// The unfortunate thing of how this is currently setup is that
// it must be style objects and therefore, we can't use the typical
// CSS format of snake-case with interpolated vars
export const EuiMarkStyle = ({ colors, sizes }: Theme): StyleConfig => {
return {
base: {
margin: euiSize(0.25),
backgroundColor: colors.euiColorHighlight,
color: isColorDark(...hexToRgb(colors.euiColorHighlight))
? colors.euiColorGhost
: colors.euiTextColor,
// Test for handling nesting/pseudo-selectors
':hover': {
textDecoration: 'underline',
},
},
// Test for handling props
size: {
s: {
padding: sizes.euiSizeXS,
},
m: {
padding: sizes.euiSizeXL,
},
},
};
};
export const EuiMarkAmsterdamStyle = ({
theme,
borders,
}: Theme): StyleConfig | undefined => {
if (!theme.includes('amsterdam')) return;
return {
base: {
borderRadius: borders.euiBorderRadius,
},
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment