feat: coming soon

This commit is contained in:
Divlo
2021-10-24 05:48:06 +02:00
parent 21123c4477
commit 33bd2bb6bf
176 changed files with 36858 additions and 22133 deletions

View File

@ -1,17 +0,0 @@
import Image, { ImageProps } from 'next/image'
export const Avatar: React.FC<ImageProps> = (props) => {
return (
<>
<Image {...props} className='avatar-image' />
<style jsx>
{`
:global(.avatar-image) {
border-radius: 50%;
}
`}
</style>
</>
)
}

View File

@ -1,40 +0,0 @@
import { forwardRef } from 'react'
interface ButtonProps extends React.ComponentPropsWithRef<'button'> {}
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
(props, ref) => {
const { children, ...rest } = props
return (
<>
<button ref={ref} {...rest} className='button'>
{children}
</button>
<style jsx>{`
.button {
cursor: pointer;
font-size: var(--default-font-size);
font-weight: 400;
letter-spacing: 0.8px;
padding: 1rem 2rem;
transform: translateY(-3px);
background-color: transparent;
border: 1px solid var(--color-primary);
border-radius: 10px;
transition: all 0.3s ease-in;
color: var(--color-primary);
outline: 0;
margin: 0;
}
.button:hover {
background-color: var(--color-primary);
color: #fff;
}
`}
</style>
</>
)
}
)

View File

@ -0,0 +1,15 @@
import { Meta, Story } from '@storybook/react'
import { Button as Component, ButtonProps } from './Button'
const Stories: Meta = {
title: 'Button',
component: Component
}
export default Stories
export const Button: Story<ButtonProps> = (arguments_) => (
<Component {...arguments_} />
)
Button.args = { children: 'Get started' }

View File

@ -1,6 +1,6 @@
import { render } from '@testing-library/react'
import { Button } from '../Button'
import { Button } from './'
describe('<Button />', () => {
it('should render', async () => {

View File

@ -0,0 +1,19 @@
import classNames from 'classnames'
export interface ButtonProps extends React.ComponentPropsWithoutRef<'button'> {}
export const Button: React.FC<ButtonProps> = (props) => {
const { children, className, ...rest } = props
return (
<button
className={classNames(
'py-2 px-6 font-paragraph rounded-lg bg-transparent border border-green-800 dark:border-green-400 text-green-800 dark:text-green-400 hover:bg-green-800 hover:text-white dark:hover:bg-green-400 dark:hover:text-black fill-current stroke-current transform transition-colors duration-300 ease-in-out focus:outline-none focus:bg-green-800 focus:text-white dark:focus:bg-green-400 dark:focus:text-black',
className
)}
{...rest}
>
{children}
</button>
)
}

View File

@ -0,0 +1 @@
export * from './Button'

View File

@ -1,24 +0,0 @@
interface ContainerProps extends React.ComponentPropsWithRef<'div'> {}
export const Container: React.FC<ContainerProps> = (props) => {
const { children, className } = props
return (
<>
<div className={`container ${className ?? ''}`}>
{children}
</div>
<style jsx>
{`
.container {
height: calc(100vh - 110px);
display: flex;
flex-direction: column;
justify-content: center;
}
`}
</style>
</>
)
}

View File

@ -1,38 +0,0 @@
interface DividerProps {
content: string
}
export const Divider: React.FC<DividerProps> = (props) => {
const { content } = props
return (
<>
<div className='text-divider'>{content}</div>
<style jsx>
{`
.text-divider {
--text-divider-gap: 1rem;
--color-divider: #414141;
display: flex;
align-items: center;
letter-spacing: 0.1em;
&::before,
&::after {
content: '';
height: 1px;
background-color: var(--color-divider);
flex-grow: 1;
}
&::before {
margin-right: var(--text-divider-gap);
}
&::after {
margin-left: var(--text-divider-gap);
}
}
`}
</style>
</>
)
}

View File

@ -1,65 +0,0 @@
import { forwardRef, useMemo } from 'react'
export const icons = [
'add',
'delete',
'edit',
'emoji',
'send',
'settings',
'more',
'download'
] as const
export type Icon = typeof icons[number]
interface IconButtonProps extends React.ComponentPropsWithRef<'button'> {
icon: Icon
hasBackground?: boolean
size?: number
}
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
(props, ref) => {
const { icon, hasBackground = false, size = 60, ...rest } = props
const imageSize = useMemo(() => {
return size / 2.6
}, [size])
return (
<>
<button ref={ref} className='button' {...rest}>
<img src={`/images/svg/icons/${icon}.svg`} alt={icon} />
</button>
<style jsx>
{`
.button {
background: ${hasBackground
? 'var(--color-background-secondary)'
: 'none'};
border-radius: ${hasBackground ? '50%' : '0'};
width: ${hasBackground ? `${size}px` : '100%'};
height: ${hasBackground ? `${size}px` : '100%'};
border: none;
outline: none;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.button:hover {
opacity: 0.9;
}
.button > img {
width: ${imageSize}px;
height: ${imageSize}px;
display: block;
}
`}
</style>
</>
)
}
)

View File

@ -1,128 +0,0 @@
import { forwardRef, useState } from 'react'
import Link from 'next/link'
import { ErrorMessage } from '../Authentication/ErrorMessage'
import useTranslation from 'next-translate/useTranslation'
interface InputProps extends React.ComponentPropsWithRef<'input'> {
label: string
errors?: string[]
showForgotPassword?: boolean
}
export const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
const {
label,
name,
type = 'text',
errors = [],
showForgotPassword = false,
...rest
} = props
const { t } = useTranslation()
const [inputType, setInputType] = useState(type)
const handlePassword = (): void => {
const oppositeType = inputType === 'password' ? 'text' : 'password'
setInputType(oppositeType)
}
return (
<>
<div className='container'>
<div className='input-with-label'>
<div className='label-container'>
<label className='label' htmlFor={name}>
{label}
</label>
{type === 'password' && showForgotPassword ? (
<Link href='/authentication/forgot-password'>
<a className='label-forgot-password'>
{t('authentication:forgot-password')}
</a>
</Link>
) : null}
</div>
<div className='input-container'>
<input
data-testid='input'
className='input'
{...rest}
ref={ref}
id={name}
name={name}
type={inputType}
/>
{type === 'password' && (
<div
data-testid='password-eye'
onClick={handlePassword}
className='password-eye'
/>
)}
<ErrorMessage errors={errors} />
</div>
</div>
</div>
<style jsx>
{`
.container {
margin-bottom: 20px;
}
.input-container {
margin-top: 0;
position: relative;
}
.input-with-label {
display: flex;
flex-direction: column;
}
.label-container {
display: flex;
justify-content: space-between;
margin: 5px 0;
}
.label-forgot-password {
font-size: 12px;
}
.label {
color: var(--color-secondary);
font-size: 16px;
font-family: 'Poppins', 'Arial', 'sans-serif';
padding-left: 3px;
}
.input {
background-color: #f1f1f1;
font-family: 'Roboto', 'Arial', 'sans-serif';
width: 100%;
height: 44px;
line-height: 44px;
padding: 0 20px;
color: #2a2a2a;
border: 0;
box-shadow: ${errors.length >= 1
? '0 0 0 2px var(--color-error)'
: 'none'};
border-radius: 10px;
}
.input:focus {
outline: 0;
border-color: var(--color-primary);
box-shadow: 0 0 0 2px var(--color-primary);
}
.password-eye {
position: absolute;
top: 12px;
right: 16px;
z-index: 1;
width: 20px;
height: 20px;
background-image: url(/images/svg/icons/input/${inputType}.svg);
background-size: cover;
cursor: pointer;
}
`}
</style>
</>
)
})

View File

@ -1,80 +0,0 @@
export interface LoaderProps {
width?: number
height?: number
}
export const Loader: React.FC<LoaderProps> = (props) => {
const { width = 50, height = 50 } = props
return (
<>
<div data-testid='progress-spinner' className='progress-spinner'>
<svg className='progress-spinner-svg' viewBox='25 25 50 50'>
<circle
className='progress-spinner-circle'
cx='50'
cy='50'
r='20'
fill='none'
strokeWidth='2'
strokeMiterlimit='10'
/>
</svg>
</div>
<style jsx>{`
.progress-spinner {
position: relative;
margin: 0 auto;
width: ${width}px;
height: ${height}px;
display: inline-block;
}
.progress-spinner::before {
content: '';
display: block;
padding-top: 100%;
}
.progress-spinner-svg {
animation: progress-spinner-rotate 2s linear infinite;
height: 100%;
transform-origin: center center;
width: 100%;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
}
.progress-spinner-circle {
stroke-dasharray: 89, 200;
stroke-dashoffset: 0;
stroke: var(--color-primary);
animation: progress-spinner-dash 1.5s ease-in-out infinite;
stroke-linecap: round;
}
@keyframes progress-spinner-rotate {
100% {
transform: rotate(360deg);
}
}
@keyframes progress-spinner-dash {
0% {
stroke-dasharray: 1, 200;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 89, 200;
stroke-dashoffset: -35px;
}
100% {
stroke-dasharray: 89, 200;
stroke-dashoffset: -124px;
}
}
`}
</style>
</>
)
}

View File

@ -0,0 +1,10 @@
import { render } from '@testing-library/react'
import { Main } from './'
describe('<Main />', () => {
it('should render successfully', () => {
const { baseElement } = render(<Main />)
expect(baseElement).toBeTruthy()
})
})

View File

@ -0,0 +1,20 @@
import classNames from 'classnames'
export interface MainProps {
className?: string
}
export const Main: React.FC<MainProps> = (props) => {
const { children, className } = props
return (
<main
className={classNames(
'flex flex-1 flex-col justify-center items-center py-8',
className
)}
>
{children}
</main>
)
}

View File

@ -0,0 +1 @@
export * from './Main'

View File

@ -1,80 +0,0 @@
import { forwardRef, useMemo } from 'react'
import Image from 'next/image'
export type SocialMedia = 'Discord' | 'GitHub' | 'Google'
type SocialMediaColors = {
[key in SocialMedia]: string
}
interface SocialMediaButtonProps extends React.ComponentPropsWithRef<'button'> {
socialMedia: SocialMedia
}
const socialMediaColors: SocialMediaColors = {
Discord: '#7289DA',
GitHub: '#24292E',
Google: '#FCFCFC'
}
export const SocialMediaButton = forwardRef<
HTMLButtonElement,
SocialMediaButtonProps
>((props, ref) => {
const { socialMedia, className, ...rest } = props
const socialMediaColor = useMemo(() => {
return socialMediaColors[socialMedia]
}, [socialMedia])
return (
<>
<button
data-testid='button'
ref={ref}
{...rest}
className={`button ${className ?? ''}`}
>
<Image
width={20}
height={20}
src={`/images/svg/web/${socialMedia}.svg`}
alt={socialMedia}
/>
<span className='social-media'>{socialMedia}</span>
</button>
<style jsx>
{`
.button {
display: inline-flex;
align-items: center;
outline: none;
font-size: var(--default-font-size);
font-family: 'Roboto', 'Arial', 'sans-serif';
margin: 0;
cursor: pointer;
letter-spacing: 0.8px;
padding: 0.9rem 2.4rem;
border: 1px solid transparent;
border-radius: 10px;
box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.2);
background: ${socialMediaColor};
color: ${socialMedia === 'Google' ? '#000' : '#fff'};
transition: all 0.3s ease-out;
}
.button:hover {
opacity: 0.85;
transition: all 0.3s ease-in;
}
.button:before {
display: none;
}
.social-media {
margin-left: 0.7rem;
}
`}
</style>
</>
)
})

View File

@ -0,0 +1,23 @@
import { Meta, Story } from '@storybook/react'
import { SocialMediaButton, SocialMediaButtonProps } from './SocialMediaButton'
const Stories: Meta = {
title: 'SocialMediaButton',
component: SocialMediaButton
}
export default Stories
const Template: Story<SocialMediaButtonProps> = (arguments_) => (
<SocialMediaButton {...arguments_} />
)
export const Github = Template.bind({})
Github.args = { socialMedia: 'GitHub' }
export const Discord = Template.bind({})
Discord.args = { socialMedia: 'Discord' }
export const Google = Template.bind({})
Google.args = { socialMedia: 'Google' }

View File

@ -1,6 +1,6 @@
import { render } from '@testing-library/react'
import { SocialMedia, SocialMediaButton } from '../SocialMediaButton'
import { SocialMedia, SocialMediaButton } from './'
describe('<SocialMediaButton />', () => {
it('should render the social media', async () => {

View File

@ -0,0 +1,62 @@
import { useMemo } from 'react'
import Image from 'next/image'
import classNames from 'classnames'
export type SocialMedia = 'Discord' | 'GitHub' | 'Google'
type SocialMediaColors = {
[key in SocialMedia]: string
}
export interface SocialMediaButtonProps
extends React.ComponentPropsWithRef<'button'> {
socialMedia: SocialMedia
}
const socialMediaColors: SocialMediaColors = {
Discord: '#404EED',
GitHub: '#24292E',
Google: '#FCFCFC'
}
export const SocialMediaButton: React.FC<SocialMediaButtonProps> = (props) => {
const { socialMedia, className, ...rest } = props
const socialMediaColor = useMemo(() => {
return socialMediaColors[socialMedia]
}, [socialMedia])
return (
<>
<button
data-testid='button'
{...rest}
className={classNames(
`button py-2 px-6 inline-flex outline-none items-center font-paragraph rounded-lg cursor-pointer transition duration-300 ease-in-out hover:opacity-80 focus:outline-none`,
className
)}
>
<Image
width={20}
height={20}
src={`/images/svg/web/${socialMedia}.svg`}
alt={socialMedia}
/>
<span className='ml-2'>{socialMedia}</span>
</button>
<style jsx>
{`
.button {
background: ${socialMediaColor};
color: ${socialMedia === 'Google' ? '#000' : '#fff'};
border: ${socialMedia === 'Google' ? '1px solid #000' : 'none'};
}
.button:focus {
box-shadow: 0 0 0 2px #27b05e;
}
`}
</style>
</>
)
}

View File

@ -0,0 +1 @@
export * from './SocialMediaButton'

View File

@ -1,106 +0,0 @@
import { forwardRef } from 'react'
interface TooltipProps extends React.ComponentPropsWithRef<'div'> {
content: string
direction?: 'top' | 'bottom' | 'right' | 'left'
}
export const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(
(props, ref) => {
const { direction = 'bottom', children, content, ...rest } = props
return (
<>
<div ref={ref} {...rest} className='tooltip-wrapper'>
{children}
<div className={`tooltip ${direction}`}>{content}</div>
</div>
<style jsx>
{`
.tooltip-wrapper {
--tooltip-text-color: white;
--tooltip-background-color: black;
--tooltip-margin: 50px;
--tooltip-arrow-size: 6px;
}
.tooltip-wrapper {
display: inline-block;
}
.tooltip {
position: absolute;
border-radius: 6px;
left: 100%;
top: 50%;
transform: translateY(-50%);
padding: 10px;
color: var(--tooltip-text-color);
background: var(--tooltip-background-color);
font-size: 15px;
font-family: sans-serif;
line-height: 1;
z-index: 100;
white-space: nowrap;
opacity: 0;
visibility: hidden;
transition: all 0.15s ease-in;
}
.tooltip-wrapper ~ .tooltip-wrapper:hover .tooltip,
.tooltip-wrapper:first-child:hover .tooltip {
opacity: 1;
visibility: visible;
transition: all 0.35s ease-out;
margin: 0;
}
.tooltip::before {
content: ' ';
left: 50%;
border: solid transparent;
height: 0;
width: 0;
position: absolute;
pointer-events: none;
border-width: var(--tooltip-arrow-size);
margin-left: calc(var(--tooltip-arrow-size) * -1);
}
.tooltip.top {
top: calc(var(--tooltip-margin) * -1);
}
.tooltip.top::before {
top: 100%;
border-top-color: var(--tooltip-background-color);
}
.tooltip.right {
left: calc(100% + var(--tooltip-margin));
}
.tooltip.right::before {
left: calc(var(--tooltip-arrow-size) * -1);
border-right-color: var(--tooltip-background-color);
}
.tooltip.bottom {
bottom: calc(var(--tooltip-margin) * -1);
}
.tooltip.bottom::before {
bottom: 100%;
border-bottom-color: var(--tooltip-background-color);
}
.tooltip.left {
left: auto;
right: calc(100% + var(--tooltip-margin));
top: 50%;
transform: translateX(0) translateY(-50%);
}
.tooltip.left::before {
left: auto;
right: calc(var(--tooltip-arrow-size) * -2);
top: 50%;
transform: translateX(0) translateY(-50%);
border-left-color: var(--tooltip-background-color);
}
`}
</style>
</>
)
}
)

View File

@ -1,13 +0,0 @@
import { render } from '@testing-library/react'
import { Avatar } from '../Avatar'
describe('<Avatar />', () => {
it('should render', async () => {
const altAttribute = 'avatar'
const { getByAltText } = render(
<Avatar width={50} height={50} src='/avatar.png' alt={altAttribute} />
)
expect(getByAltText(altAttribute)).toBeInTheDocument()
})
})

View File

@ -1,10 +0,0 @@
import { render } from '@testing-library/react'
import { Container } from '../Container'
describe('<Container />', () => {
it('should render', async () => {
const { getByText } = render(<Container>Content</Container>)
expect(getByText('Content')).toBeInTheDocument()
})
})

View File

@ -1,11 +0,0 @@
import { render } from '@testing-library/react'
import { Divider } from '../Divider'
describe('<Divider />', () => {
it('should render with the content', async () => {
const content = 'divider'
const { getByText } = render(<Divider content={content} />)
expect(getByText(content)).toBeInTheDocument()
})
})

View File

@ -1,13 +0,0 @@
import { render } from '@testing-library/react'
import { Icon, IconButton } from '../IconButton'
describe('<IconButton />', () => {
it('should render with the icon', async () => {
const icon: Icon = 'add'
const { getByAltText } = render(<IconButton icon={icon} />)
const iconImage = getByAltText(icon)
expect(iconImage).toBeInTheDocument()
expect(iconImage).toHaveAttribute('src', `/images/svg/icons/${icon}.svg`)
})
})

View File

@ -1,26 +0,0 @@
import { render, fireEvent } from '@testing-library/react'
import { Input } from '../Input'
describe('<Input />', () => {
it('should render the label', async () => {
const labelContent = 'label content'
const { getByText } = render(<Input label={labelContent} />)
expect(getByText(labelContent)).toBeInTheDocument()
})
it('should not render the eye icon if the input is not of type "password"', async () => {
const { queryByTestId } = render(<Input type='text' label='content' />)
const passwordEye = queryByTestId('password-eye')
expect(passwordEye).not.toBeInTheDocument()
})
it('should handlePassword with eye icon', async () => {
const { findByTestId } = render(<Input type='password' label='content' />)
const passwordEye = await findByTestId('password-eye')
const input = await findByTestId('input')
expect(input).toHaveAttribute('type', 'password')
fireEvent.click(passwordEye)
expect(input).toHaveAttribute('type', 'text')
})
})

View File

@ -1,20 +0,0 @@
import { render } from '@testing-library/react'
import { Loader } from '../Loader'
describe('<Loader />', () => {
it('should render with correct width and height', async () => {
const size = 20
const { findByTestId } = render(<Loader width={size} height={size} />)
const progressSpinner = await findByTestId('progress-spinner')
expect(progressSpinner).toHaveStyle(`width: ${size}px`)
expect(progressSpinner).toHaveStyle(`height: ${size}px`)
})
it('should render with default width and height', async () => {
const { findByTestId } = render(<Loader />)
const progressSpinner = await findByTestId('progress-spinner')
expect(progressSpinner).toHaveStyle('width: 50px')
expect(progressSpinner).toHaveStyle('height: 50px')
})
})

View File

@ -1,11 +0,0 @@
import { render } from '@testing-library/react'
import { Tooltip } from '../Tooltip'
describe('<Tooltip />', () => {
it('should render with content', async () => {
const content = 'tooltip content'
const { getByText } = render(<Tooltip content={content} />)
expect(getByText(content)).toBeInTheDocument()
})
})