-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
create Element component + normalize file (#1436)
* create Element component + normalize file * changing Element * wip Element * final Element component changes * Create slimy-bikes-wash.md * remove useless test and rewrite comment
- Loading branch information
Showing
6 changed files
with
442 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@marigold/system": patch | ||
--- | ||
|
||
create Element component + normalize file |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
import React from 'react'; | ||
import { render, screen } from '@testing-library/react'; | ||
import { useStyles } from './useStyles'; | ||
import { ThemeProvider } from './useTheme'; | ||
import { Element } from './Element'; | ||
|
||
const theme = { | ||
text: { | ||
body: { | ||
fontSize: 1, | ||
color: 'black', | ||
marginTop: '2px', | ||
}, | ||
heading: { | ||
fontSize: 3, | ||
color: 'primary', | ||
}, | ||
padding: { | ||
paddingTop: '2px', | ||
}, | ||
}, | ||
}; | ||
|
||
test('renders a <div> by default', () => { | ||
render(<Element>Text</Element>); | ||
const testelem = screen.getByText('Text'); | ||
|
||
expect(testelem instanceof HTMLDivElement).toBeTruthy(); | ||
}); | ||
|
||
test('supports as prop', () => { | ||
render(<Element as="p">Text</Element>); | ||
const testelem = screen.getByText('Text'); | ||
|
||
expect(testelem instanceof HTMLParagraphElement).toBeTruthy(); | ||
}); | ||
|
||
test('supports HTML className attribute', () => { | ||
render(<Element className="my-custom-class">Text</Element>); | ||
const element = screen.getByText('Text'); | ||
|
||
expect(element.getAttribute('class')).toMatch('my-custom-class'); | ||
}); | ||
|
||
test('passes down HTML attributes', () => { | ||
render( | ||
<Element className="my-custom-class" id="element-id" disabled> | ||
Text | ||
</Element> | ||
); | ||
const element = screen.getByText('Text'); | ||
|
||
expect(element.getAttribute('id')).toEqual('element-id'); | ||
expect(element.getAttribute('disabled')).toMatch(''); | ||
expect(element.getAttribute('class')).toMatch('my-custom-class'); | ||
}); | ||
|
||
test('forwards ref', () => { | ||
const ref = React.createRef<HTMLButtonElement>(); | ||
render( | ||
<Element as="button" ref={ref}> | ||
button | ||
</Element> | ||
); | ||
|
||
expect(ref.current instanceof HTMLButtonElement).toBeTruthy(); | ||
}); | ||
|
||
test('base styles first', () => { | ||
const { getByText } = render(<Element as="p">Text</Element>); | ||
const testelem = getByText('Text'); | ||
const style = getComputedStyle(testelem); | ||
|
||
expect(style.marginTop).toEqual('0px'); // 0px come from base | ||
}); | ||
|
||
test('variant styles second', () => { | ||
const TestComponent: React.FC<{ variant?: 'body' }> = ({ | ||
variant = 'body', | ||
children, | ||
...props | ||
}) => { | ||
return ( | ||
<Element as="p" variant={`text.${variant}`} {...props}> | ||
{children} | ||
</Element> | ||
); | ||
}; | ||
|
||
const { getByText } = render( | ||
<ThemeProvider theme={theme}> | ||
<TestComponent>Text</TestComponent> | ||
</ThemeProvider> | ||
); | ||
const testelem = getByText('Text'); | ||
const style = getComputedStyle(testelem); | ||
|
||
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 | ||
}); | ||
|
||
test('array of variant styles', () => { | ||
const TestComponent: React.FC<{ variant?: 'body' }> = ({ | ||
variant = 'body', | ||
children, | ||
...props | ||
}) => { | ||
return ( | ||
<Element as="p" variant={[`text.${variant}`, `text.padding`]} {...props}> | ||
{children} | ||
</Element> | ||
); | ||
}; | ||
|
||
const { getByText } = render( | ||
<ThemeProvider theme={theme}> | ||
<TestComponent>Text</TestComponent> | ||
</ThemeProvider> | ||
); | ||
const testelem = getByText('Text'); | ||
const style = getComputedStyle(testelem); | ||
|
||
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 | ||
}); | ||
|
||
test('custom styles with css prop third', () => { | ||
const TestComponent: React.FC<{ variant?: 'body' }> = ({ | ||
variant = 'body', | ||
children, | ||
...props | ||
}) => { | ||
return ( | ||
<Element | ||
as="p" | ||
variant={`text.${variant}`} | ||
css={{ marginTop: '4px' }} | ||
{...props} | ||
> | ||
{children} | ||
</Element> | ||
); | ||
}; | ||
|
||
const { getByText } = render( | ||
<ThemeProvider theme={theme}> | ||
<TestComponent>Text</TestComponent> | ||
</ThemeProvider> | ||
); | ||
const testelem = getByText('Text'); | ||
const style = getComputedStyle(testelem); | ||
|
||
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 | ||
}); | ||
|
||
test("don't apply the same reset multiple times", () => { | ||
const Button = ({ className }: { className?: string }) => { | ||
const classNames = useStyles({ element: 'button', className }); | ||
return ( | ||
<Element as="button" title="button" className={classNames}> | ||
Click me! | ||
</Element> | ||
); | ||
}; | ||
const Wrapper = () => <Button />; | ||
|
||
render(<Wrapper />); | ||
const button = screen.getByTitle('button'); | ||
const classNames = button.className.split(' ').filter(i => i.length); | ||
|
||
// Test if applied classnames are unique | ||
expect(classNames.length).toEqual([...new Set(classNames)].length); | ||
}); | ||
|
||
test('normalize tag name <a>', () => { | ||
const TestComponent: React.FC<{ variant?: 'body' }> = ({ | ||
variant = 'body', | ||
children, | ||
...props | ||
}) => { | ||
return ( | ||
<Element as="a" variant={`text.${variant}`} {...props}> | ||
{children} | ||
</Element> | ||
); | ||
}; | ||
|
||
const { getByText } = render( | ||
<ThemeProvider theme={theme}> | ||
<TestComponent>Link</TestComponent> | ||
</ThemeProvider> | ||
); | ||
const testelem = getByText('Link'); | ||
const style = getComputedStyle(testelem); | ||
|
||
expect(style.boxSizing).toEqual('border-box'); // from base | ||
expect(style.textDecoration).toEqual('none'); // from a | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { jsx } from '@emotion/react'; | ||
import { forwardRef } from 'react'; | ||
import { | ||
PolymorphicPropsWithRef, | ||
PolymorphicComponentWithRef, | ||
} from '@marigold/types'; | ||
|
||
import { getNormalizedStyles } from './normalize'; | ||
import { CSSObject } from './types'; | ||
import { useTheme } from './useTheme'; | ||
|
||
export type ElementOwnProps = { | ||
css?: CSSObject; | ||
variant?: string | string[]; | ||
}; | ||
|
||
export type ElementProps = PolymorphicPropsWithRef<ElementOwnProps, 'div'>; | ||
|
||
/** | ||
* Function expression to check if there is any falsy value or empty object | ||
*/ | ||
const isNotEmpty = (val: any) => | ||
!(val && Object.keys(val).length === 0 && val.constructor === Object); | ||
|
||
/** | ||
* Get the normalized base styles | ||
*/ | ||
const baseStyles = getNormalizedStyles('base'); | ||
|
||
export const Element: PolymorphicComponentWithRef<ElementOwnProps, 'div'> = | ||
forwardRef( | ||
({ as = 'div', css: styles = {}, variant, children, ...props }, ref) => { | ||
const { css } = useTheme(); | ||
|
||
/** | ||
* Transform variant input for `@theme-ui/css` | ||
*/ | ||
const variants = Array.isArray(variant) | ||
? variant.map(v => ({ variant: v })) | ||
: [{ variant }]; | ||
|
||
return jsx( | ||
as, | ||
{ | ||
...props, | ||
...{ | ||
css: [ | ||
baseStyles, | ||
getNormalizedStyles(as), | ||
...variants.map(v => css(v)), | ||
css(styles), | ||
].filter(isNotEmpty), | ||
}, | ||
ref, | ||
}, | ||
children | ||
); | ||
} | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
export * from './Element'; | ||
export * from './cache'; | ||
export * from './types'; | ||
export * from './useClassname'; | ||
export * from './useStyles'; | ||
export * from './useTheme'; | ||
export * from './useTheme'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { getNormalizedStyles } from './normalize'; | ||
|
||
test('get base styles', () => { | ||
const baseStyles = getNormalizedStyles('base'); | ||
expect(baseStyles).toEqual({ | ||
boxSizing: 'border-box', | ||
margin: 0, | ||
padding: 0, | ||
minWidth: 0, | ||
fontSize: '100%', | ||
fontFamily: 'inherit', | ||
verticalAlign: 'baseline', | ||
WebkitTapHighlightColor: 'transparent', | ||
}); | ||
}); | ||
|
||
test('get reset style by element', () => { | ||
const baseStyles = getNormalizedStyles('a'); | ||
expect(baseStyles).toEqual({ | ||
textDecoration: 'none', | ||
touchAction: 'manipulation', | ||
}); | ||
}); | ||
|
||
test('getNormalizedStyles returns base if input is not a string', () => { | ||
const baseStyles = getNormalizedStyles(undefined); | ||
expect(baseStyles).toEqual({ | ||
boxSizing: 'border-box', | ||
margin: 0, | ||
padding: 0, | ||
minWidth: 0, | ||
fontSize: '100%', | ||
fontFamily: 'inherit', | ||
verticalAlign: 'baseline', | ||
WebkitTapHighlightColor: 'transparent', | ||
}); | ||
}); | ||
|
||
test('getNormalizedStyles returns empty object if input is unknown', () => { | ||
const baseStyles = getNormalizedStyles('p'); | ||
expect(baseStyles).toEqual({}); | ||
}); |
Oops, something went wrong.