From a1ef2108dd6c8e6838b517dd58c82d38e71dae2b Mon Sep 17 00:00:00 2001 From: Sebastian Sebald Date: Thu, 2 Dec 2021 11:04:09 +0100 Subject: [PATCH] feat: Add default styling to `` (#1508) Co-authored-by: Timo Zehnle <59875255+ti10le@users.noreply.github.com> --- .changeset/slow-dolphins-search.md | 5 + packages/system/src/Element.test.tsx | 229 +++++++++++++++------------ packages/system/src/Element.tsx | 11 +- packages/system/src/normalize.ts | 2 +- 4 files changed, 143 insertions(+), 104 deletions(-) create mode 100644 .changeset/slow-dolphins-search.md diff --git a/.changeset/slow-dolphins-search.md b/.changeset/slow-dolphins-search.md new file mode 100644 index 0000000000..82e6ffe1e8 --- /dev/null +++ b/.changeset/slow-dolphins-search.md @@ -0,0 +1,5 @@ +--- +"@marigold/system": patch +--- + +feat: Add default styling to `` diff --git a/packages/system/src/Element.test.tsx b/packages/system/src/Element.test.tsx index 0e9adfde7f..74d7056d83 100644 --- a/packages/system/src/Element.test.tsx +++ b/packages/system/src/Element.test.tsx @@ -2,41 +2,62 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import { ThemeProvider } from './useTheme'; import { Element } from './Element'; +import { normalize } from './normalize'; const theme = { + colors: { + primary: 'black', + secondary: 'hotpink', + }, + fontSizes: { + body: 16, + small: 12, + large: 24, + }, + space: { + none: 0, + small: 4, + medium: 8, + large: 16, + }, text: { body: { - fontSize: 1, - color: 'black', - marginTop: '2px', + fontSize: 'body', + color: 'primary', }, heading: { - fontSize: 3, - color: 'primary', + fontSize: 'large', + color: 'secondary', }, - padding: { - paddingTop: '2px', + whitespace: { + p: 'medium', + }, + }, + variant: { + spacing: { + m: 'large', + p: 'large', }, }, }; test('renders a
by default', () => { - render(Text); - const testelem = screen.getByText('Text'); + render(Test); + const testelem = screen.getByText('Test'); expect(testelem instanceof HTMLDivElement).toBeTruthy(); }); -test('supports as prop', () => { - render(Text); - const testelem = screen.getByText('Text'); +test('supports "as" prop', () => { + render(Test); + const testelem = screen.getByText('Test'); expect(testelem instanceof HTMLParagraphElement).toBeTruthy(); }); test('supports HTML className attribute', () => { - render(Text); - const element = screen.getByText('Text'); + render(Test); + const element = screen.getByText('Test'); expect(element.getAttribute('class')).toMatch('my-custom-class'); }); @@ -44,10 +65,10 @@ test('supports HTML className attribute', () => { test('passes down HTML attributes', () => { render( - Text + Test ); - const element = screen.getByText('Text'); + const element = screen.getByText('Test'); expect(element.getAttribute('id')).toEqual('element-id'); expect(element.getAttribute('disabled')).toMatch(''); @@ -65,119 +86,123 @@ test('forwards ref', () => { expect(ref.current instanceof HTMLButtonElement).toBeTruthy(); }); -test('base styles first', () => { - const { getByText } = render(Text); - const testelem = getByText('Text'); - const style = getComputedStyle(testelem); +test('apply normalized styles', () => { + render(Test); + const element = screen.getByText('Test'); + const { base } = normalize; - expect(style.marginTop).toEqual('0px'); // 0px come from base + // Smoketest + expect(element).toHaveStyle(`box-sizing: ${base.boxSizing}`); + expect(element).toHaveStyle(`margin: ${base.margin}px`); + expect(element).toHaveStyle(`min-width: ${base.minWidth}`); }); -test('variant styles second', () => { - const TestComponent: React.FC<{ variant?: 'body' }> = ({ - variant = 'body', - children, - ...props - }) => { - return ( - - {children} - - ); - }; +test('base normalization is always applied', () => { + render(Test); + const element = screen.getByText('Test'); + const { base } = normalize; + + expect(element).toHaveStyle(`box-sizing: ${base.boxSizing}`); + expect(element).toHaveStyle(`margin: ${base.margin}px`); + expect(element).toHaveStyle(`min-width: ${base.minWidth}`); +}); + +test('apply normalized styles based on element', () => { + render(Test); + const element = screen.getByText('Test'); + const { h1 } = normalize; - const { getByText } = render( + expect(element).toHaveStyle(`overflow-wrap: ${h1.overflowWrap}`); +}); + +test('accepts default styling via "__baseCSS" prop', () => { + render(Test); + const element = screen.getByText('Test'); + + expect(element).toHaveStyle('color: hotpink'); +}); + +test('default styling overrides normalization', () => { + render( - Text + Test ); - const testelem = getByText('Text'); - const style = getComputedStyle(testelem); + const element = screen.getByText('Test'); - expect(style.marginTop).not.toEqual('0px'); // 0px come from base - expect(style.marginBottom).toEqual('0px'); // 0px still come from base - expect(style.marginTop).toEqual('2px'); // 2px come from variant + expect(element).toHaveStyle(`margin: ${theme.space.medium}px`); }); -test('array of variant styles', () => { - const TestComponent: React.FC<{ variant?: 'body' }> = ({ - variant = 'body', - children, - ...props - }) => { - return ( - - {children} - - ); - }; - - const { getByText } = render( +test('variants are applied correctly', () => { + render( - Text + Test ); - const testelem = getByText('Text'); - const style = getComputedStyle(testelem); + const element = screen.getByText('Test'); - expect(style.marginTop).not.toEqual('0px'); // 0px come from base - expect(style.marginBottom).toEqual('0px'); // 0px still come from base - expect(style.marginTop).toEqual('2px'); // 2px marginTop come from variant - expect(style.paddingTop).toEqual('2px'); // 2px paddingTop come from variant + expect(element).toHaveStyle(`font-size: ${theme.fontSizes.body}px`); + expect(element).toHaveStyle(`color: ${theme.colors.primary}`); }); -test('custom styles with css prop third', () => { - const TestComponent: React.FC<{ variant?: 'body' }> = ({ - variant = 'body', - children, - ...props - }) => { - return ( - - {children} +test('accept an array of variants', () => { + render( + + + Test - ); - }; + + ); + const element = screen.getByText('Test'); - const { getByText } = render( + expect(element).toHaveStyle(`font-size: ${theme.fontSizes.large}px`); + expect(element).toHaveStyle(`color: ${theme.colors.secondary}`); + expect(element).toHaveStyle(`padding: ${theme.space.medium}px`); +}); + +test('variants override normalization and default styles', () => { + render( - Text + + Test + ); - const testelem = getByText('Text'); - const style = getComputedStyle(testelem); + const element = screen.getByText('Test'); - expect(style.marginTop).not.toEqual('0px'); // do not apply 0px from base - expect(style.marginTop).not.toEqual('2px'); // do not apply 2px from variant - expect(style.marginTop).toEqual('4px'); // apply 4px from custom styles + expect(element).toHaveStyle(`margin: ${theme.space.large}px`); + expect(element).toHaveStyle(`padding: ${theme.space.large}px`); }); -test('normalize tag name ', () => { - const TestComponent: React.FC<{ variant?: 'body' }> = ({ - variant = 'body', - children, - ...props - }) => { - return ( - - {children} - - ); - }; +test('apply custom styling via css prop', () => { + render( + + Test + + ); + const element = screen.getByText('Test'); + + expect(element).toHaveStyle(`padding: 1rem`); + expect(element).toHaveStyle(`color: ${theme.colors.secondary}`); +}); - const { getByText } = render( +test('custom styling overrides normalization, defaults and variants', () => { + render( - Link + + Test + ); - const testelem = getByText('Link'); - const style = getComputedStyle(testelem); + const element = screen.getByText('Test'); + + expect(element).toHaveStyle(`margin: ${theme.space.small}px`); // overrides normalization + expect(element).toHaveStyle(`padding: ${theme.space.large}px`); // overrides default + expect(element).toHaveStyle(`font-size: ${theme.fontSizes.large}px`); // overrides variant - expect(style.boxSizing).toEqual('border-box'); // from base - expect(style.textDecoration).toEqual('none'); // from a + expect(element).not.toHaveStyle(`color: ${theme.colors.primary}px`); // variant part that is not overriden }); diff --git a/packages/system/src/Element.tsx b/packages/system/src/Element.tsx index fa64968b10..329476438c 100644 --- a/packages/system/src/Element.tsx +++ b/packages/system/src/Element.tsx @@ -12,6 +12,11 @@ import { useTheme } from './useTheme'; export type ElementOwnProps = { css?: CSSObject; variant?: string | string[]; + /** + * Use to set base styles for the component + * @private - **use with caution ... please!** + */ + __baseCSS?: CSSObject; }; export type ElementProps = PolymorphicPropsWithRef; @@ -24,7 +29,10 @@ const isNotEmpty = (val: any) => export const Element: PolymorphicComponentWithRef = forwardRef( - ({ as = 'div', css: styles = {}, variant, children, ...props }, ref) => { + ( + { as = 'div', __baseCSS, css: styles = {}, variant, children, ...props }, + ref + ) => { const { css } = useTheme(); /** @@ -41,6 +49,7 @@ export const Element: PolymorphicComponentWithRef = ...{ css: [ getNormalizedStyles(as), + css(__baseCSS), ...variants.map(v => css(v)), css(styles), ].filter(isNotEmpty), diff --git a/packages/system/src/normalize.ts b/packages/system/src/normalize.ts index 419e3c53f0..54b6a1b431 100644 --- a/packages/system/src/normalize.ts +++ b/packages/system/src/normalize.ts @@ -24,7 +24,7 @@ const media = { ...base, display: 'block', maxWidth: '100%', -}; +} as const; const button = { ...base,