Last active
March 10, 2019 22:54
-
-
Save alobato/0e326449582b86f8408e20298a0f5efd to your computer and use it in GitHub Desktop.
React Components
This file contains hidden or 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
import React from 'react' | |
import styled, { css } from 'styled-components' | |
import { ExpandLessIcon } from '../components/Icons' | |
const Accordion = ({ open, title, children, onChange, ...rest }) => ( | |
<div onKeyPress={e => (['Enter', ' '].includes(e.key)) && onChange(!open)} {...rest}> | |
<label onClick={() => onChange(!open)}> | |
<span>{title}</span> | |
<i><ExpandLessIcon /></i> | |
</label> | |
<div style={{ maxHeight: open ? 1000 : 0 }}> | |
{children} | |
</div> | |
</div> | |
) | |
const StyledAccordion = styled(Accordion)` | |
outline: 0; | |
user-select: none; | |
& > div { | |
overflow: auto; | |
transition: max-height 100ms cubic-bezier(0.39, 0.58, 0.57, 1); | |
} | |
& > label { | |
display: flex; | |
align-items: center; | |
justify-content: space-between; | |
cursor: pointer; | |
} | |
& > label > i { | |
line-height: 0; | |
transition: transform 100ms cubic-bezier(0.39, 0.58, 0.57, 1); | |
${props => props.open && css` | |
transform: rotate(180deg); | |
`} | |
} | |
` | |
StyledAccordion.defaultProps = { | |
tabIndex: 0, | |
onChange: () => { }, | |
} | |
export default StyledAccordion |
This file contains hidden or 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
import React, { useEffect } from 'react' | |
import Modal from './StyledModalAlert' | |
const Alert = ({ autoHideDuration = 3000, open, message, onClose }) => { | |
if (open) return ( | |
<Modal | |
hasBackdrop={false} | |
onCloseCompleted={() => onClose()} | |
render={({onRequestClose}) => { | |
let timeout | |
useEffect(() => { | |
if (open) { | |
timeout = setTimeout(() => { | |
onRequestClose() | |
}, autoHideDuration) | |
return () => clearTimeout(timeout) | |
} | |
}, [open]) | |
return ( | |
<div onClick={() => onRequestClose()}>{message}</div> | |
) | |
}} | |
/> | |
) | |
return null | |
} | |
export default Alert |
This file contains hidden or 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
import styled from 'styled-components' | |
import { Box } from '../components/FlexBox' | |
const Card = styled(Box)` | |
background: white; | |
box-shadow: 0px 3px 6px 0px rgba(50, 50, 50, 0.15); | |
border-radius: 12px; | |
min-height: 400px; | |
` | |
export default Card |
This file contains hidden or 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
import React, { useRef } from 'react' | |
import styled from 'styled-components' | |
const Checkbox = ({ checked, label, labelStyle, containerStyle, checkAnimation, uncheckAnimation, ...rest }) => { | |
const checkboxRef = useRef() | |
const checkRef = useRef() | |
const handleClick = () => { | |
if (checked) { | |
if (uncheckAnimation) { | |
uncheckAnimation(checkRef, () => rest.onChange(false)) | |
} else { | |
rest.onChange(false) | |
} | |
} else { | |
if (checkAnimation) { | |
checkAnimation(checkRef, () => rest.onChange(true)) | |
} else { | |
rest.onChange(true) | |
} | |
} | |
} | |
const content = ( | |
<div onKeyPress={e => (['Enter', ' '].includes(e.key)) && handleClick()} ref={checkboxRef} onClick={handleClick} {...rest}> | |
<div ref={checkRef} style={{opacity: checked ? 1 : 0}} /> | |
</div> | |
) | |
if (label) return ( | |
<div style={containerStyle}> | |
{content} | |
<label style={labelStyle} onClick={handleClick}>{label}</label> | |
</div> | |
) | |
return content | |
} | |
const checkAnimation = (ref, callback) => { | |
if (!ref.current) return | |
if ('animate' in ref.current) { | |
ref.current.animate([{opacity: 0, transform: 'rotate(45deg) scale(0)'}, {opacity: 1, transform: 'rotate(45deg) scale(1)'}], {duration: 200, easing: 'ease'}) | |
.onfinish = () => { | |
callback() | |
} | |
} else { | |
callback() | |
} | |
} | |
const uncheckAnimation = (ref, callback) => { | |
if (!ref.current) return | |
if ('animate' in ref.current) { | |
ref.current.animate([{opacity: 1, transform: 'rotate(45deg) scale(1)'}, {opacity: 0, transform: 'rotate(45deg) scale(0)'}], {duration: 200, easing: 'ease'}) | |
.onfinish = () => { | |
callback() | |
} | |
} else { | |
callback() | |
} | |
} | |
const StyledCheckbox = styled(Checkbox)` | |
display: inline-block; | |
position: relative; | |
cursor: pointer; | |
width: 18px; | |
height: 18px; | |
background-color: transparent; | |
border-radius: 2px; | |
border: 2px solid hsla(216, 20%, 50%, 0.85); | |
transition: background-color 500ms, border-color 500ms; | |
&:hover { | |
background-color: hsla(216, 20%, 50%, 0.2); | |
border-color: hsla(216, 20%, 50%, 1); | |
} | |
& > div { | |
position: absolute; | |
top: 1px; | |
left: 4px; | |
width: 6px; | |
height: 10px; | |
border-bottom: 2px solid hsla(216, 20%, 50%, 0.85); | |
border-left: none; | |
border-right: 2px solid hsla(216, 20%, 50%, 0.85); | |
border-top: none; | |
transform: rotate(45deg); | |
pointer-events: none; | |
} | |
` | |
const StyledCheckboxWithAnimations = props => ( | |
<StyledCheckbox checkAnimation={checkAnimation} uncheckAnimation={uncheckAnimation} {...props} /> | |
) | |
StyledCheckboxWithAnimations.defaultProps = { | |
tabIndex: 0, | |
onChange: () => {}, | |
containerStyle: { display: 'flex', alignItems: 'center', cursor: 'pointer', userSelect: 'none' }, | |
labelStyle: { cursor: 'pointer', marginLeft: 8 }, | |
} | |
export default StyledCheckboxWithAnimations |
This file contains hidden or 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
import React from 'react' | |
const CircularProgress = props => ( | |
<div style={{width: '100%', textAlign: 'center'}}> | |
<svg | |
height={120} | |
viewBox='0 0 100 100' | |
preserveAspectRatio='xMidYMid' | |
style={{background: 'none'}} | |
{...props} | |
> | |
<circle | |
cx='50' | |
cy='50' | |
fill='none' | |
r='20' | |
stroke='hsla(216, 40%, 55%, 1)' | |
strokeWidth='5' | |
/> | |
<circle | |
cx='50' | |
cy='50' | |
fill='none' | |
r='20' | |
stroke='#ffffff' | |
strokeWidth='4' | |
strokeLinecap='square' | |
transform='rotate(146.131 50 50)' | |
> | |
<animateTransform | |
attributeName='transform' | |
type='rotate' | |
calcMode='linear' | |
values='0 50 50;180 50 50;720 50 50' | |
keyTimes='0;0.5;1' | |
dur='2.9s' | |
begin='0s' | |
repeatCount='indefinite' | |
/> | |
<animate | |
attributeName='stroke-dasharray' | |
calcMode='linear' | |
values='12.566370614359172 113.09733552923255;62.83185307179586 62.83185307179586;12.566370614359172 113.09733552923255' | |
keyTimes='0;0.5;1' | |
dur='2.9' | |
begin='0s' | |
repeatCount='indefinite' | |
/> | |
</circle> | |
</svg> | |
</div> | |
) | |
export default CircularProgress |
This file contains hidden or 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
import React from 'react' | |
import styled from 'styled-components' | |
const Button = props => ( | |
<button {...props}> | |
<i> | |
<svg height={32} viewBox='0 0 24 24' fill='currentColor'> | |
<path d='M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z' /> | |
</svg> | |
</i> | |
</button> | |
) | |
const StyledButton = styled(Button)` | |
cursor: pointer; | |
user-select: none; | |
width: 56px; height: 56px; | |
position: fixed; | |
bottom: 24px; | |
right: 24px; | |
border-radius: 50%; | |
box-shadow: rgba(0, 0, 0, 0.14) 0px 6px 10px 0px, rgba(0, 0, 0, 0.12) 0px 1px 18px 0px, rgba(0, 0, 0, 0.2) 0px 3px 5px -1px; | |
display: flex; justify-content: center; align-items: center; | |
background: hsla(216, 40%, 55%, 1); | |
color: white; | |
&:hover { | |
background: hsla(216, 40%, 45%, 1); | |
} | |
&:active { | |
background: hsla(216, 40%, 35%, 1); | |
} | |
& > i { | |
display: block; | |
line-height: 0; | |
pointer-events: none; | |
} | |
` | |
export default StyledButton |
This file contains hidden or 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
import styled from 'styled-components' | |
import { style, space, color, border, borderRadius, width, height, opacity, fontSize, fontWeight, flex, order, alignSelf, textAlign, lineHeight, top, right, bottom, left, maxWidth, maxHeight, minWidth, minHeight, position, overflow, justifyContent, alignItems, flexDirection, flexWrap } from 'styled-system' | |
const lh = style({prop: 'lh', cssProperty: 'lineHeight'}) | |
const ta = style({prop: 'ta', cssProperty: 'textAlign'}) | |
const t = style({prop: 't', cssProperty: 'top', transformValue: n => /^\d+$/.test(n) ? n + 'px': n}) | |
const r = style({prop: 'r', cssProperty: 'right', transformValue: n => /^\d+$/.test(n) ? n + 'px': n}) | |
const b = style({prop: 'b', cssProperty: 'bottom', transformValue: n => /^\d+$/.test(n) ? n + 'px': n}) | |
const l = style({prop: 'l', cssProperty: 'left', transformValue: n => /^\d+$/.test(n) ? n + 'px': n}) | |
const w = style({prop: 'w', cssProperty: 'width', transformValue: n => /^\d+$/.test(n) ? n + 'px': n}) | |
const h = style({prop: 'h', cssProperty: 'height', transformValue: n => /^\d+$/.test(n) ? n + 'px': n}) | |
const cursor = style({prop: 'cursor', cssProperty: 'cursor'}) | |
export const Box = styled('div')( | |
{boxSizing: 'border-box'}, | |
space, | |
color, | |
border, | |
borderRadius, | |
width, | |
height, | |
opacity, | |
fontSize, | |
fontWeight, | |
flex, | |
order, | |
alignSelf, | |
textAlign, | |
ta, | |
lineHeight, | |
lh, | |
top, right, bottom, left, | |
t, r, b, l, | |
maxWidth, maxHeight, minWidth, minHeight, | |
w, h, | |
cursor, | |
position, | |
overflow, | |
props => props.css | |
) | |
Box.displayName = 'Box' | |
Box.propTypes = { | |
...space.propTypes, | |
...color.propTypes, | |
...width.propTypes, | |
...height.propTypes, | |
...fontSize.propTypes, | |
} | |
const jc = style({prop: 'jc', cssProperty: 'justifyContent'}) | |
const ai = style({prop: 'ai', cssProperty: 'alignItems'}) | |
const fd = style({prop: 'fd', cssProperty: 'flexDirection'}) | |
const fw = style({prop: 'fd', cssProperty: 'flexWrap'}) | |
export const Flex = styled(Box)( | |
{display: 'flex'}, | |
justifyContent, alignItems, flexDirection, flexWrap, | |
jc, ai, fd, fw, | |
props => props.css | |
) | |
Flex.displayName = 'Flex' | |
Flex.propTypes = { | |
...justifyContent.propTypes, | |
...alignItems.propTypes, | |
...flexDirection.propTypes, | |
...flexWrap.propTypes, | |
} |
This file contains hidden or 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
export const FormatCpf = props => { | |
let formatedCpf = props.children | |
if (!formatedCpf) return '' | |
if (formatedCpf.length === 11) | |
formatedCpf = `${formatedCpf.substr(0, 3)}.${formatedCpf.substr(3, 3)}.${formatedCpf.substr(6, 3)}-${formatedCpf.substr(9, 2)}` | |
return formatedCpf | |
} | |
export const FormatCnpj = props => { | |
let formated = props.children | |
if (!formated) return '' | |
if (formated.length === 14) | |
formated = `${formated.substr(0, 2)}.${formated.substr(2, 3)}.${formated.substr(5, 3)}/${formated.substr(8, 4)}-${formated.substr(12, 2)}` | |
return formated | |
} | |
export const FormatCep = props => { | |
let formated = props.children | |
if (!formated) return '' | |
if (formated.length === 8) | |
formated = `${formated.substr(0, 5)}-${formated.substr(5, 3)}` | |
return formated | |
} | |
export const FormatCurrency = props => { | |
if (!props.children || props.children === '') return '' | |
return parseFloat(props.children).toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' }) | |
} | |
export const FormatGender = props => { | |
if (props.children && props.children.toString().toUpperCase() === 'M') return 'Masculino' | |
if (props.children && props.children.toString().toUpperCase() === 'F') return 'Feminino' | |
return '' | |
} |
This file contains hidden or 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
import React from 'react' | |
import styled from 'styled-components' | |
import { Box } from './FlexBox' | |
export const FormError = styled.div` | |
font-size: 10px; | |
line-height: 18px; | |
color: hsla(6, 66%, 47%, 1); | |
text-transform: uppercase; | |
font-weight: 500; | |
` | |
const FormErrorBox = ({ errors, touched, fieldName }) => { | |
return ( | |
<Box minHeight={18}> | |
{errors[fieldName] && touched[fieldName] && <FormError>{errors[fieldName]}</FormError>} | |
</Box> | |
) | |
} | |
export default FormErrorBox |
This file contains hidden or 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
import { Formik } from 'formik' | |
export default Formik |
This file contains hidden or 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
import React from 'react' | |
export const MoreIcon = props => ( | |
<svg viewBox='0 0 24 24' height={24} fill='currentColor' {...props}> | |
<path d='M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z' /> | |
</svg> | |
) | |
export const ExpandLessIcon = props => ( | |
<svg viewBox='0 0 24 24' height={24} fill='currentColor' {...props}> | |
<path d='M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z' /> | |
</svg> | |
) |
This file contains hidden or 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
import React from 'react' | |
import ReactDOM from 'react-dom' | |
import App from './containers/App' | |
import * as serviceWorker from './serviceWorker' | |
import { ApolloClient } from 'apollo-client' | |
import { ApolloProvider } from 'react-apollo' | |
import { InMemoryCache } from 'apollo-cache-inmemory' | |
import { split } from 'apollo-link' | |
import { HttpLink } from 'apollo-link-http' | |
import { WebSocketLink } from 'apollo-link-ws' | |
import { getMainDefinition } from 'apollo-utilities' | |
import { ApolloLink } from 'apollo-link' | |
import { setContext } from 'apollo-link-context' | |
import { onError } from 'apollo-link-error' | |
import { createUploadLink } from 'apollo-upload-client' | |
import { ApolloProvider as ApolloHooksProvider } from './hooks/useApollo' | |
import ApolloLinkTimeout from './utils/apolloLinkTimeout' | |
import BASE_API, { WS_BASE_API } from './constants/baseApi' | |
import AUTH_TOKEN from './constants/authToken' | |
const timeoutLink = new ApolloLinkTimeout(10000) | |
const httpLink = new HttpLink({uri: `${BASE_API}/graphql`}) | |
const uploadLink = createUploadLink({uri: `${BASE_API}/graphql`}) | |
const wsLink = new WebSocketLink({uri: `${WS_BASE_API}/graphql`, options: {reconnect: true}}) | |
const logout = client => { | |
localStorage.removeItem(AUTH_TOKEN) | |
client.resetStore() | |
window.location.href = '/login' | |
} | |
const errorLink = onError(({ graphQLErrors, networkError }) => { | |
if (graphQLErrors) { | |
graphQLErrors.map(({ message, locations, path }) => console.log(`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`)) | |
graphQLErrors.forEach(({ message, locations, path }) => { | |
if (message === 'Context creation failed: Your session expired. Sign in again.') logout(client) | |
if (message === 'Not authenticated as user.') logout(client) | |
}) | |
} | |
if (networkError && networkError.statusCode === 401) { | |
console.log(`[Network error]: ${networkError}`) | |
logout(client) | |
} | |
}) | |
const authLink = setContext((_, { headers }) => { | |
const token = localStorage.getItem(AUTH_TOKEN) | |
return { headers: {...headers, authorization: token ? `Bearer ${token}` : ''} } | |
}) | |
// using the ability to split links, you can send data to each link | |
// depending on what kind of operation is being sent | |
const splitLink = split( | |
// split based on operation type | |
({ query }) => { | |
const { kind, operation } = getMainDefinition(query) | |
return kind === 'OperationDefinition' && operation === 'subscription' | |
}, | |
wsLink, httpLink | |
) | |
const cache = new InMemoryCache() | |
const defaultOptions = {} | |
const link = ApolloLink.from([timeoutLink, authLink, errorLink, uploadLink, splitLink]) | |
const client = new ApolloClient({link, cache, defaultOptions}) | |
ReactDOM.render( | |
<ApolloProvider client={client}> | |
<ApolloHooksProvider client={client}> | |
<App logout={logout} client={client} /> | |
</ApolloHooksProvider> | |
</ApolloProvider>, | |
document.getElementById('root') | |
) | |
// If you want your app to work offline and load faster, you can change | |
// unregister() to register() below. Note this comes with some pitfalls. | |
// Learn more about service workers: http://bit.ly/CRA-PWA | |
serviceWorker.unregister() |
This file contains hidden or 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
import React, { memo, forwardRef } from 'react' | |
import styled, { css } from 'styled-components' | |
import { width, height } from 'styled-system' | |
const MyInput = memo(forwardRef(({withError, ...rest}, ref) => ( | |
<input ref={ref} {...rest} /> | |
))) | |
const StyledInput = styled(MyInput)` | |
border: 1px solid hsla(216, 40%, 90%, 1); | |
border-radius: 3px; | |
font-size: inherit; | |
line-height: 1; | |
padding: 8px; | |
width: 100%; | |
transition: all 0.3s; | |
${width} | |
${height} | |
::placeholder { | |
color: hsla(216, 40%, 55%, 1); | |
} | |
${props => props.withError && css` | |
border-color: hsla(6, 66%, 47%, 1); | |
`} | |
&:focus { | |
outline: 0; | |
box-shadow: 0 0 0 3px hsla(216, 40%, 90%, 0.5); | |
border: 1px solid hsla(216, 40%, 70%, 1); | |
} | |
` | |
export default StyledInput |
This file contains hidden or 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
import React, { memo, useState, useEffect } from 'react' | |
import Input from './Input' | |
const maskValue = (value = '') => { | |
value = value.toString() | |
value = value.replace(/\D/g, '') | |
if (!value) return '' | |
if (value.length === 4 && /\d{4}/.test(value)) return `${value.substr(0, 3)}.${value.substr(3, 1)}` | |
if (value.length === 5 && /\d{5}/.test(value)) return `${value.substr(0, 3)}.${value.substr(3, 2)}` | |
if (value.length === 6 && /\d{6}/.test(value)) return `${value.substr(0, 3)}.${value.substr(3, 3)}` | |
if (value.length === 7 && /\d{7}/.test(value)) return `${value.substr(0, 3)}.${value.substr(3, 3)}.${value.substr(6, 1)}` | |
if (value.length === 8 && /\d{8}/.test(value)) return `${value.substr(0, 3)}.${value.substr(3, 3)}.${value.substr(6, 2)}` | |
if (value.length === 9 && /\d{9}/.test(value)) return `${value.substr(0, 3)}.${value.substr(3, 3)}.${value.substr(6, 3)}` | |
if (value.length === 10 && /\d{10}/.test(value)) return `${value.substr(0, 3)}.${value.substr(3, 3)}.${value.substr(6, 3)}-${value.substr(9, 1)}` | |
if (value.length === 11 && /\d{11}/.test(value)) return `${value.substr(0, 3)}.${value.substr(3, 3)}.${value.substr(6, 3)}-${value.substr(9, 2)}` | |
return value | |
} | |
const unmaskValue = (maskedValue = '') => { | |
if (!maskedValue) return '' | |
if (maskedValue.length === 10) { | |
return maskedValue.replace(/\D/g, '') | |
} else { | |
return '' | |
} | |
} | |
const initialMaskedValue = '' | |
const InputCpf = memo(({ onChange = () => {}, value, defaultValue, ...rest }) => { | |
const [maskedValue, setMaskedValue] = useState(initialMaskedValue) | |
useEffect( | |
() => { | |
setMaskedValue(maskValue(value || defaultValue)) | |
}, | |
[defaultValue] | |
) | |
const handleChange = event => { | |
const { target } = event | |
const { value: inputValue = 0 } = target | |
const value = unmaskValue(inputValue) | |
const maskedValue = maskValue(inputValue) | |
setMaskedValue(maskedValue) | |
if (!onChange || typeof onChange !== 'function') return false | |
return onChange(event, value, maskedValue) | |
} | |
return ( | |
<Input maxLength={14} type='tel' value={maskedValue} onChange={handleChange} {...rest} /> | |
) | |
}) | |
export default InputCpf |
This file contains hidden or 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
import React, { memo, useState, useEffect } from 'react' | |
import Input from './Input' | |
const maskValue = (value = '') => { | |
value = value.toString() | |
value = value.replace(/\D/g, '') | |
if (!value) return '' | |
if (value.length === 1 && /\d/.test(value)) return `(${value}` | |
if (value.length === 2 && /\d{2}/.test(value)) return `(${value}` | |
if (value.length === 3 && /\d{3}/.test(value)) return `(${value.substr(0, 2)}) ${value.substr(2, 1)}` | |
if (value.length === 4 && /\d{4}/.test(value)) return `(${value.substr(0, 2)}) ${value.substr(2, 2)}` | |
if (value.length === 5 && /\d{5}/.test(value)) return `(${value.substr(0, 2)}) ${value.substr(2, 3)}` | |
if (value.length === 6 && /\d{6}/.test(value)) return `(${value.substr(0, 2)}) ${value.substr(2, 4)}` | |
if (value.length === 7 && /\d{7}/.test(value)) return `(${value.substr(0, 2)}) ${value.substr(2, 4)}-${value.substr(6, 1)}` | |
if (value.length === 8 && /\d{8}/.test(value)) return `(${value.substr(0, 2)}) ${value.substr(2, 4)}-${value.substr(6, 2)}` | |
if (value.length === 9 && /\d{9}/.test(value)) return `(${value.substr(0, 2)}) ${value.substr(2, 4)}-${value.substr(6, 3)}` | |
if (value.length === 10 && /\d{10}/.test(value)) return `(${value.substr(0, 2)}) ${value.substr(2, 4)}-${value.substr(6, 4)}` | |
if (value.length === 11 && /\d{11}/.test(value)) return `(${value.substr(0, 2)}) ${value.substr(2, 5)}-${value.substr(7, 4)}` | |
return value | |
} | |
const unmaskValue = (maskedValue = '') => { | |
if (!maskedValue) return '' | |
if (maskedValue.length === 10) { | |
return maskedValue.replace(/\D/g, '') | |
} else { | |
return '' | |
} | |
} | |
const initialMaskedValue = '' | |
const InputPhone = memo(({ onChange = () => {}, value, defaultValue, ...rest }) => { | |
const [maskedValue, setMaskedValue] = useState(initialMaskedValue) | |
useEffect( | |
() => { | |
setMaskedValue(maskValue(value || defaultValue)) | |
}, | |
[defaultValue] | |
) | |
const handleChange = event => { | |
const { target } = event | |
const { value: inputValue = 0 } = target | |
const value = unmaskValue(inputValue) | |
const maskedValue = maskValue(inputValue) | |
setMaskedValue(maskedValue) | |
if (!onChange || typeof onChange !== 'function') return false | |
return onChange(event, value, maskedValue) | |
} | |
return ( | |
<Input type='tel' maxLength={15} value={maskedValue} onChange={handleChange} {...rest} /> | |
) | |
}) | |
export default InputPhone |
This file contains hidden or 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
import styled from 'styled-components' | |
const Label = styled.label` | |
font-weight: 600; | |
line-height: 24px; | |
& > span { | |
color: hsla(216, 40%, 70%, 1); | |
} | |
` | |
export default Label |
This file contains hidden or 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
import React, { forwardRef, useRef, useEffect } from 'react' | |
import { createPortal } from 'react-dom' | |
import useKeyPress from '../hooks/useKeyPress' | |
import useLockBodyScroll from '../hooks/useLockBodyScroll' | |
const Portal = ({children}) => createPortal(children, document.getElementById('modal-root')) | |
const Backdrop = forwardRef(({ onClick, zIndex = 1000, style }, ref) => ( | |
<div ref={ref} style={{position: 'fixed', top: 0, right: 0, bottom: 0, left: 0, backgroundColor: 'black', opacity: 0, zIndex: zIndex, outline: 'none', tabIndex: -1, ...style}} onClick={onClick} /> | |
)) | |
const Modal = ({ render, className, onCloseCompleted = () => {}, zIndex = 1001, hasBackdrop = true, clickOutsideDisabled = false, backdropOpacity = 0.6, exitAnimation, enterAnimation, backdropStyle }) => { | |
if (hasBackdrop) useLockBodyScroll() | |
const modal = useRef() | |
const backdrop = useRef() | |
let initialOpacity = 0 | |
const handleExit = () => { | |
if (exitAnimation) { | |
exitAnimation(modal, backdrop, backdropOpacity, onCloseCompleted) | |
} else { | |
onCloseCompleted() | |
} | |
} | |
useEffect(() => { | |
if (enterAnimation) { | |
enterAnimation(modal, backdrop, backdropOpacity) | |
} else { | |
modal.current.style.opacity = 1 | |
modal.current.style.transform = 'translateY(0)' | |
backdrop.current.style.opacity = backdropOpacity | |
} | |
}, []) | |
const escPress = useKeyPress('Escape') | |
if (escPress) handleExit() | |
return ( | |
<Portal> | |
<div className={className} ref={modal} tabIndex='-1' style={{opacity: initialOpacity, position: 'fixed', top: 0, right: 0, bottom: 0, left: 0, zIndex: zIndex, overflow: 'hidden', pointerEvents: 'none', outline: 'none', display: 'flex', alignItems: 'center', justifyContent: 'center'}}> | |
<div> | |
{render({onRequestClose: handleExit})} | |
</div> | |
</div> | |
{hasBackdrop && | |
<Backdrop | |
style={backdropStyle} | |
ref={backdrop} | |
onClick={() => { | |
if (clickOutsideDisabled) return false | |
handleExit() | |
}} /> | |
} | |
</Portal> | |
) | |
} | |
export default Modal |
This file contains hidden or 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
import React, { memo, useState, useEffect } from 'react' | |
import memoize from 'memoize-one' | |
import isEqual from 'lodash.isequal' | |
const equalFn = (newArg, lastArg) => isEqual(newArg, lastArg) | |
const range = (start, end) => [...Array(end - start).keys()].map(k => k + start) | |
const getPager = memoize((totalItems, currentPage, pageSize) => { | |
// calculate total pages | |
const totalPages = Math.ceil(totalItems / pageSize) | |
let startPage, endPage | |
if (totalPages <= 10) { | |
// less than 10 total pages so show all | |
startPage = 1 | |
endPage = totalPages | |
} else { | |
// more than 10 total pages so calculate start and end pages | |
if (currentPage <= 6) { | |
startPage = 1 | |
endPage = 10 | |
} else if (currentPage + 4 >= totalPages) { | |
startPage = totalPages - 9 | |
endPage = totalPages | |
} else { | |
startPage = currentPage - 5 | |
endPage = currentPage + 4 | |
} | |
} | |
// calculate start and end item indexes | |
const startIndex = (currentPage - 1) * pageSize | |
const endIndex = Math.min(startIndex + pageSize - 1, totalItems - 1) | |
// create an array of pages | |
const pages = range(startPage, endPage + 1) | |
// return object with all pager properties required by the view | |
return { | |
totalItems: totalItems, | |
currentPage: currentPage, | |
pageSize: pageSize, | |
totalPages: totalPages, | |
startPage: startPage, | |
endPage: endPage, | |
startIndex: startIndex, | |
endIndex: endIndex, | |
pages: pages | |
} | |
}) | |
const getPageOfItems = memoize((items, pager) => items.slice(pager.startIndex, pager.endIndex + 1), equalFn) | |
const Pagination = memo(({ initialPage, pageSize, items, previousLabel, nextLabel, fistLabel, lastLabel, onChange, ...rest }) => { | |
const processedItems = () => items | |
const genPager = () => getPager(processedItems().length, initialPage, pageSize) | |
const [pager, setPager] = useState(genPager()) | |
useEffect(() => { | |
const newPager = genPager() | |
setPager(newPager) | |
const pageOfItems = getPageOfItems(processedItems(), newPager) | |
onChange(pageOfItems) | |
}, [JSON.stringify(items)] | |
) | |
const setPage = page => { | |
const newPager = getPager(processedItems().length, page, pageSize) | |
setPager(newPager) | |
const pageOfItems = getPageOfItems(processedItems(), newPager) | |
onChange(pageOfItems) | |
} | |
const isFirstPage = pager.currentPage === 1 | |
const isCurrentPage = page => pager.currentPage === page | |
return ( | |
<div {...rest}> | |
<ul> | |
<li className={isFirstPage ? 'disabled' : ''}> | |
{isFirstPage ? (<span>{fistLabel}</span>) : (<span tabIndex={0} onKeyPress={e => (e.key === 'Enter') && setPage(1)} onClick={() => setPage(1)}>{fistLabel}</span>)} | |
</li> | |
<li className={isFirstPage ? 'disabled' : ''}> | |
{isFirstPage ? (<span>{previousLabel}</span>) : (<span tabIndex={0} onKeyPress={e => (e.key === 'Enter') && setPage(pager.currentPage - 1)} onClick={() => setPage(pager.currentPage - 1)}>{previousLabel}</span>)} | |
</li> | |
{pager.pages.map((page, index) => | |
<li key={index} className={isCurrentPage(page) ? 'active' : ''}><span tabIndex={isCurrentPage(page) ? -1 : 0} onKeyPress={e => (e.key === 'Enter') && setPage(page)} onClick={() => setPage(page)}>{page}</span></li> | |
)} | |
<li className={pager.currentPage === pager.totalPages ? 'disabled' : ''}> | |
{pager.currentPage === pager.totalPages ? (<span>{nextLabel}</span>) : (<span tabIndex={0} onKeyPress={e => (e.key === 'Enter') && setPage(pager.currentPage + 1)} onClick={() => setPage(pager.currentPage + 1)}>{nextLabel}</span>)} | |
</li> | |
<li className={pager.currentPage === pager.totalPages ? 'disabled' : ''}> | |
{pager.currentPage === pager.totalPages ? (<span>{lastLabel}</span>) : (<span tabIndex={0} onKeyPress={e => (e.key === 'Enter') && setPage(pager.totalPages)} onClick={() => setPage(pager.totalPages)}>{lastLabel}</span>)} | |
</li> | |
</ul> | |
</div> | |
) | |
}, equalFn) | |
Pagination.defaultProps = { | |
initialPage: 1, | |
pageSize: 20, | |
items: [], | |
fistLabel: 'Primeira', | |
nextLabel: 'Próxima', | |
previousLabel: 'Anterior', | |
lastLabel: 'Última', | |
onChange: () => {} | |
} | |
export default Pagination |
This file contains hidden or 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
import { createPortal } from 'react-dom' | |
const Portal = ({ children }) => ( | |
createPortal(children, document.getElementById('portal-root')) | |
) | |
export default Portal |
This file contains hidden or 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
import React, { useRef } from 'react' | |
import styled from 'styled-components' | |
const Radio = ({ checked, label, labelStyle, containerStyle, checkAnimation, uncheckAnimation, ...rest }) => { | |
const checkboxRef = useRef() | |
const checkRef = useRef() | |
const handleClick = () => { | |
if (checked) { | |
if (uncheckAnimation) { | |
uncheckAnimation(checkRef, () => rest.onChange(false)) | |
} else { | |
rest.onChange(false) | |
} | |
} else { | |
if (checkAnimation) { | |
checkAnimation(checkRef, () => rest.onChange(true)) | |
} else { | |
rest.onChange(true) | |
} | |
} | |
} | |
const content = ( | |
<div onKeyPress={e => (['Enter', ' '].includes(e.key)) && handleClick()} ref={checkboxRef} onClick={handleClick} {...rest}> | |
<div ref={checkRef} style={{ opacity: checked ? 1 : 0 }} /> | |
</div> | |
) | |
if (label) return ( | |
<div style={containerStyle}> | |
{content} | |
<div style={labelStyle} onClick={handleClick}>{label}</div> | |
</div> | |
) | |
return content | |
} | |
const checkAnimation = (ref, callback) => { | |
if (!ref.current) return | |
if ('animate' in ref.current) { | |
ref.current.animate([{ opacity: 0, transform: 'scale(0)' }, { opacity: 1, transform: 'scale(1)' }], { duration: 200, easing: 'ease' }) | |
.onfinish = () => { | |
callback() | |
} | |
} else { | |
callback() | |
} | |
} | |
const uncheckAnimation = (ref, callback) => { | |
if (!ref.current) return | |
if ('animate' in ref.current) { | |
ref.current.animate([{ opacity: 1, transform: 'scale(1)' }, { opacity: 0, transform: 'scale(0)' }], { duration: 200, easing: 'ease' }) | |
.onfinish = () => { | |
callback() | |
} | |
} else { | |
callback() | |
} | |
} | |
const StyledRadio = styled(Radio)` | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
cursor: pointer; | |
width: 18px; | |
height: 18px; | |
background-color: transparent; | |
border-radius: 50%; | |
border: 2px solid hsla(216, 20%, 50%, 0.85); | |
transition: background-color 500ms, border-color 500ms; | |
&:hover { | |
background-color: hsla(216, 20%, 50%, 0.2); | |
border-color: hsla(216, 20%, 50%, 1); | |
} | |
& > div { | |
width: 10px; | |
height: 10px; | |
border-radius: 50%; | |
background: hsla(216, 20%, 50%, 0.85); | |
pointer-events: none; | |
} | |
` | |
const StyledRadioWithAnimations = props => ( | |
<StyledRadio checkAnimation={checkAnimation} uncheckAnimation={uncheckAnimation} {...props} /> | |
) | |
StyledRadioWithAnimations.defaultProps = { | |
tabIndex: 0, | |
onChange: () => { }, | |
containerStyle: { display: 'flex', alignItems: 'center', cursor: 'pointer', userSelect: 'none' }, | |
labelStyle: { marginLeft: 8 }, | |
} | |
export default StyledRadioWithAnimations |
This file contains hidden or 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
import React from 'react' | |
import Radio from './Radio' | |
const RadioGroup = ({value, options, onChange}) => { | |
const handleChange = optionValue => { | |
if (optionValue === value) return onChange('') | |
return onChange(optionValue) | |
} | |
return ( | |
<> | |
{options.map(option => ( | |
<Radio label={option.label} onChange={() => handleChange(option.value)} checked={option.value === value} /> | |
))} | |
</> | |
) | |
} | |
export default RadioGroup |
This file contains hidden or 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
import { Component } from 'react' | |
import { Router as ReachRouter, Link as ReachLink } from '@reach/router' | |
export const Router = ReachRouter | |
export const Link = ReachLink | |
// https://github.com/reach/router/issues/100 | |
class MyRedirect extends Component { | |
componentDidMount() { | |
setTimeout(() => this.props.navigate(this.props.to, { replace: true }), 0) | |
} | |
render() { | |
return null | |
} | |
} | |
export const Redirect = MyRedirect |
This file contains hidden or 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
import React from 'react' | |
import styled from 'styled-components' | |
import Modal from './Modal' | |
const enterAnimation = (modal, backdrop, backdropOpacity) => { | |
if (!modal.current) return | |
if ('animate' in modal.current) { | |
backdrop.current.animate([{opacity: 0}, {opacity: backdropOpacity}], {duration: 300, easing: 'ease'}) | |
modal.current.animate([{opacity: 0, transform: 'translateY(-100px)'}, {opacity: 1, transform: 'translateY(0)'}], {duration: 300, easing: 'ease'}) | |
.onfinish = () => { | |
if (modal.current) { modal.current.style.opacity = 1 } | |
if (backdrop.current) { backdrop.current.style.opacity = backdropOpacity } | |
} | |
} else { | |
modal.current.style.opacity = 1 | |
modal.current.style.transform = 'translateY(0)' | |
backdrop.current.style.opacity = backdropOpacity | |
} | |
} | |
const exitAnimation = (modal, backdrop, backdropOpacity, callback) => { | |
if (!modal.current) return | |
if ('animate' in modal.current) { | |
backdrop.current.animate([{opacity: backdropOpacity}, {opacity: 0}], {duration: 300, easing: 'ease'}) | |
modal.current.animate([{opacity: 1, transform: 'translateY(0)'}, {opacity: 0, transform: 'translateY(-100px)'}], {duration: 300, easing: 'ease'}) | |
.onfinish = () => { callback() } | |
} else { | |
callback() | |
} | |
} | |
const StyledModal = styled(Modal)` | |
& > div { | |
position: relative; | |
background-color: white; | |
box-shadow: 0 7px 8px -4px rgba(0,0,0,0.2), 0 13px 19px 2px rgba(0,0,0,0.14), 0 5px 24px 4px rgba(0,0,0,0.12); | |
pointer-events: auto; | |
height: calc(100vh); | |
margin: 0; | |
overflow: scroll; | |
width: 100%; | |
@media (min-width: 40em) { | |
margin: 32px auto; | |
width: auto; | |
/* height: min-content; */ | |
height: calc(100vh - 64px); | |
max-height: 800px; | |
min-width: 640px; | |
border-radius: 8px; | |
} | |
} | |
` | |
const StyledModalWithAnimations = props => ( | |
<StyledModal enterAnimation={enterAnimation} exitAnimation={exitAnimation} {...props} /> | |
) | |
export default StyledModalWithAnimations |
This file contains hidden or 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
import React from 'react' | |
import styled from 'styled-components' | |
import Modal from './Modal' | |
const enterAnimation = (modal, backdrop, backdropOpacity) => { | |
if (!modal.current) return | |
if ('animate' in modal.current) { | |
modal.current.animate([{opacity: 0, transform: 'translateY(-100px)'}, {opacity: 1, transform: 'translateY(0)'}], {duration: 300, easing: 'ease'}) | |
.onfinish = () => { | |
if (modal.current) { modal.current.style.opacity = 1 } | |
} | |
} else { | |
modal.current.style.opacity = 1 | |
modal.current.style.transform = 'translateY(0)' | |
} | |
} | |
const exitAnimation = (modal, backdrop, backdropOpacity, callback) => { | |
if (!modal.current) return | |
if ('animate' in modal.current) { | |
modal.current.animate([{opacity: 1, transform: 'translateY(0)'}, {opacity: 0, transform: 'translateY(-100px)'}], {duration: 300, easing: 'ease'}) | |
.onfinish = () => { callback() } | |
} else { | |
callback() | |
} | |
} | |
const StyledModal = styled(Modal)` | |
align-items: stretch !important; | |
& > div { | |
cursor: pointer; | |
margin-top: 8px; | |
pointer-events: auto !important; | |
height: 40px; | |
border-radius: 4px; | |
padding: 0 40px; | |
background-color: hsla(0, 0%, 0%, 0.8); | |
color: hsla(0, 0%, 90%, 1); | |
min-width: 100px; | |
text-align: center; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
} | |
` | |
const StyledModalWithAnimations = props => ( | |
<StyledModal enterAnimation={enterAnimation} exitAnimation={exitAnimation} {...props} /> | |
) | |
export default StyledModalWithAnimations |
This file contains hidden or 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
import styled from 'styled-components' | |
import Pagination from './Pagination' | |
const StyledPagination = styled(Pagination)` | |
display: flex; | |
justify-content: center; | |
& > ul { | |
display: inline-block; | |
padding-left: 0; | |
border-radius: 4px; | |
user-select: none; | |
} | |
& > ul > li { | |
display:inline; | |
} | |
& > ul > li > a { | |
cursor: pointer; | |
} | |
& > ul > li > a, & > ul > li > span { | |
cursor: pointer; | |
position: relative; | |
float: left; | |
padding: 6px 12px; | |
margin-left: -1px; | |
line-height: 1.42857143; | |
color: hsla(216, 40%, 55%, 1); | |
text-decoration: none; | |
background-color: white; | |
border: 1px solid rgb(231, 231, 231); | |
} | |
& > ul > li:first-child > a, & ul > li:first-child > span { | |
margin-left: 0; | |
border-top-left-radius: 4px; | |
border-bottom-left-radius: 4px; | |
} | |
& > ul > li:last-child > a, & > ul > li:last-child > span { | |
border-top-right-radius: 4px; | |
border-bottom-right-radius: 4px; | |
} | |
& > ul > li > a:focus, & > ul > li > a:hover, & > ul > li > span:focus, & > ul > li > span:hover { | |
z-index: 3; | |
color: rgb(66, 133, 244); | |
background-color: rgb(240, 240, 240); | |
border-color: rgb(231, 231, 231); | |
} | |
& > ul > .active > a, | |
& > ul > .active > a:focus, | |
& > ul > .active > a:hover, | |
& > ul > .active > span, | |
& > ul > .active > span:focus, | |
& > ul > .active > span:hover { | |
z-index: 2; | |
color: white; | |
cursor: default; | |
background-color: hsla(216, 40%, 55%, 1); | |
border-color: hsla(216, 40%, 55%, 1); | |
} | |
& > ul > .disabled > a, | |
& > ul > .disabled > a:focus, | |
& > ul > .disabled > a:hover, | |
& > ul > .disabled > span, | |
& > ul > .disabled > span:focus, | |
& > ul > .disabled > span:hover { | |
color: rgb(200, 200, 200); | |
cursor: not-allowed; | |
background-color: white; | |
border-color: rgb(231, 231, 231); | |
} | |
` | |
export default StyledPagination |
This file contains hidden or 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
import styled from 'styled-components' | |
import { width, height, lineHeight } from 'styled-system' | |
export const TableContainer = styled.div` | |
border: none; | |
@media (min-width: 768px) { | |
border: 1px solid hsla(216, 20%, 48%, 0.12); | |
} | |
` | |
export const Table = styled.div` | |
display: block; | |
&:last-child { | |
border-bottom: none; | |
} | |
@media (min-width: 768px) { | |
width: 100%; | |
display: table; | |
color: hsla(0, 0%, 0%, 0.65); | |
} | |
` | |
export const TableHeader = styled.div` | |
display: block; | |
@media (min-width: 768px) { | |
font-weight: 600; | |
color: hsla(0, 0%, 0%, 0.85); | |
background: hsla(216, 20%, 95%, 1); | |
} | |
` | |
export const TableRow = styled.div` | |
display: block; | |
border-bottom: 1px solid hsla(216, 20%, 91%, 1); | |
@media (min-width: 768px) { | |
display: table-row; | |
border-bottom: none; | |
box-shadow: inset 0 -1px 0 0 hsla(216, 20%, 48%, 0.12); | |
&:hover { | |
background-color: hsla(216, 20%, 96%, 1); | |
} | |
} | |
` | |
export const TableHeaderRow = styled(TableRow)` | |
border-bottom: none; | |
@media (min-width: 768px) { | |
font-weight: 600; | |
color: hsla(0, 0%, 0%, 0.85); | |
background: hsla(216, 20%, 96%, 1); | |
} | |
` | |
export const TableCell = styled.div` | |
display: block; | |
margin-bottom: 10px; | |
line-height: 20px; | |
@media (max-width: 767px) { | |
&:before { | |
margin-bottom: 3px; | |
content: attr(data-title); | |
min-width: 98px; | |
font-size: 10px; | |
line-height: 1; | |
font-weight: bold; | |
text-transform: uppercase; | |
color: hsla(0, 0%, 60%, 1); | |
display: block; | |
} | |
} | |
@media (min-width: 768px) { | |
display: table-cell; | |
vertical-align: middle; | |
font-size: inherit; | |
padding-right: 16px; | |
&:first-child { | |
height: 40px; | |
padding-left: 16px; | |
} | |
} | |
${width} | |
${height} | |
${lineHeight} | |
` | |
export const TableHeaderCell = styled(TableCell)` | |
margin-bottom: 0; | |
@media (max-width: 767px) { | |
display: none; | |
} | |
` | |
export default Table |
This file contains hidden or 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
import { ThemeProvider } from 'styled-components' | |
export default ThemeProvider |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment