diff --git a/.eslintrc b/.eslintrc index ba2e2041..a3ce36ee 100644 --- a/.eslintrc +++ b/.eslintrc @@ -84,6 +84,7 @@ "indent": "off", "no-unused-vars": "off", "react/jsx-one-expression-per-line": "off", + "react/jsx-fragments": "off", "semi": "off" } } diff --git a/Makefile b/Makefile index 918723a9..5e3ad19c 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,7 @@ build: .PHONY: build stage: build + rsync -zavP build/ -e 'ssh -p 666' ott-staging@85.10.224.24:/usr/local/www/ott-staging test: npm test diff --git a/package.json b/package.json index 54a5925e..a1076adc 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "lodash": "^4.17.15", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-scripts": "3.4.1" + "react-scripts": "3.4.1", + "styled-components": "^5.1.1" }, "devDependencies": { "@commitlint/cli": "^8.3.5", @@ -36,6 +37,7 @@ "@types/node": "^12.0.0", "@types/react": "^16.9.0", "@types/react-dom": "^16.9.0", + "@types/styled-components": "^5.1.0", "commitizen": "^4.1.2", "eslint": "6.8.0", "eslint-config-airbnb": "18.1.0", diff --git a/public/images/arrowLeft.svg b/public/images/arrowLeft.svg new file mode 100644 index 00000000..dfa7c5d0 --- /dev/null +++ b/public/images/arrowLeft.svg @@ -0,0 +1,6 @@ + + + + + diff --git a/public/images/arrowRight.svg b/public/images/arrowRight.svg new file mode 100644 index 00000000..7875155b --- /dev/null +++ b/public/images/arrowRight.svg @@ -0,0 +1,6 @@ + + + + + diff --git a/public/images/checkboxChecked.svg b/public/images/checkboxChecked.svg new file mode 100644 index 00000000..bcf8ad02 --- /dev/null +++ b/public/images/checkboxChecked.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/images/checkboxUnchecked.svg b/public/images/checkboxUnchecked.svg new file mode 100644 index 00000000..e69c317e --- /dev/null +++ b/public/images/checkboxUnchecked.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + diff --git a/public/images/radioChecked.svg b/public/images/radioChecked.svg new file mode 100644 index 00000000..27cf636c --- /dev/null +++ b/public/images/radioChecked.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/images/radioUnchecked.svg b/public/images/radioUnchecked.svg new file mode 100644 index 00000000..e14a682f --- /dev/null +++ b/public/images/radioUnchecked.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + diff --git a/public/index.html b/public/index.html index 4fb02265..e91d53dd 100644 --- a/public/index.html +++ b/public/index.html @@ -6,6 +6,7 @@ + Instat TV diff --git a/src/features/App/index.tsx b/src/features/App/index.tsx index 9eded704..f9857eb4 100644 --- a/src/features/App/index.tsx +++ b/src/features/App/index.tsx @@ -1,7 +1,9 @@ import React from 'react' +import { Theme } from 'features/Theme' + export const App = () => ( -
+ Instat TV -
+ ) diff --git a/src/features/Button/index.tsx b/src/features/Button/index.tsx deleted file mode 100644 index 3a2a000d..00000000 --- a/src/features/Button/index.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React, { ReactNode } from 'react' - -type ButtonProps = { - children: ReactNode, - - /** - * Simple click handler - */ - onClick?: () => void, -} - -/** - * The world's most _basic_ button - */ -export const Button = ({ children, onClick }: ButtonProps) => ( - -) diff --git a/src/features/Button/stories.tsx b/src/features/Button/stories.tsx deleted file mode 100644 index bce6188c..00000000 --- a/src/features/Button/stories.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react' - -import { action } from '@storybook/addon-actions' - -import { Button } from 'features/Button' - -export default { - component: Button, - title: 'Button', -} - -export const Text = () => - -export const Emoji = () => ( - -) diff --git a/src/features/Common/Arrows/index.tsx b/src/features/Common/Arrows/index.tsx new file mode 100644 index 00000000..4302a0c0 --- /dev/null +++ b/src/features/Common/Arrows/index.tsx @@ -0,0 +1,28 @@ +import styled from 'styled-components/macro' + +const ArrowStyled = styled.button` + width: 40px; + height: 40px; + padding: 0; + border: 1px solid transparent; + border-radius: 50%; + background-color: transparent; + background-position: center; + outline: none; + + :active { + background-color: rgba(117, 117, 117, 1); + } + + :hover, :focus { + background-color: rgba(117, 117, 117, 0.5); + } +` + +export const ArrowLeft = styled(ArrowStyled)` + background-image: url(/images/arrowLeft.svg); +` + +export const ArrowRight = styled(ArrowStyled)` + background-image: url(/images/arrowRight.svg); +` diff --git a/src/features/Common/Arrows/stories.tsx b/src/features/Common/Arrows/stories.tsx new file mode 100644 index 00000000..f2e61f8a --- /dev/null +++ b/src/features/Common/Arrows/stories.tsx @@ -0,0 +1,21 @@ +import React from 'react' + +import { ArrowLeft, ArrowRight } from 'features/Common' + +export default { + component: ArrowLeft, + title: 'Arrows', +} + +const backgroundStyles = { + backgroundColor: '#333', + height: '200px', + padding: '20px', +} + +export const Group = () => ( +
+ + +
+) diff --git a/src/features/Common/Button/index.tsx b/src/features/Common/Button/index.tsx new file mode 100644 index 00000000..9bc65a52 --- /dev/null +++ b/src/features/Common/Button/index.tsx @@ -0,0 +1 @@ +export * from './styled' diff --git a/src/features/Common/Button/stories.tsx b/src/features/Common/Button/stories.tsx new file mode 100644 index 00000000..0c58bfca --- /dev/null +++ b/src/features/Common/Button/stories.tsx @@ -0,0 +1,21 @@ +import React from 'react' + +import { action } from '@storybook/addon-actions' + +import { ButtonOutline, ButtonSolid } from 'features/Common' + +export default { + title: 'Button', +} + +export const Solid = () => ( + + Solid + +) + +export const Outline = () => ( + + Outline + +) diff --git a/src/features/Common/Button/styled.tsx b/src/features/Common/Button/styled.tsx new file mode 100644 index 00000000..04a2c45a --- /dev/null +++ b/src/features/Common/Button/styled.tsx @@ -0,0 +1,44 @@ +import styled, { css } from 'styled-components/macro' + +const baseButtonStyles = css` + width: 272px; + height: 48px; + border-width: 0.7px; + border-style: solid; + border-radius: 2px; + padding: 0 12px; + + font-style: normal; + font-size: 20px; + outline-color: white; +` + +export const outlineButtonStyles = css` + ${baseButtonStyles} + + padding-top: 8.6px; + padding-bottom: 10.6px; + color: ${({ theme: { colors } }) => colors.secondary}; + font-weight: normal; + border-color: ${({ theme: { colors } }) => colors.secondary}; + background: transparent; +` + +export const solidButtonStyles = css` + ${baseButtonStyles} + + padding-top: 18.5px; + padding-bottom: 13px; + color: #FFFFFF; + font-weight: bold; + border-color: transparent; + background: ${({ theme: { colors } }) => colors.primary}; +` + +export const ButtonSolid = styled.button` + ${solidButtonStyles} +` + +export const ButtonOutline = styled.button` + ${outlineButtonStyles} +` diff --git a/src/features/Common/Checkbox/index.tsx b/src/features/Common/Checkbox/index.tsx new file mode 100644 index 00000000..b3c80f67 --- /dev/null +++ b/src/features/Common/Checkbox/index.tsx @@ -0,0 +1,38 @@ +import React, { InputHTMLAttributes } from 'react' + +import { + Wrapper, + Input, + Label, +} from './styled' + +type TCheckbox = Pick, ( + | 'checked' + | 'id' + | 'name' + | 'value' + | 'onChange' +)> & { + label?: string, +} + +export const Checkbox = ({ + checked, + id, + label, + name, + onChange, + value, +}: TCheckbox) => ( + + + + +) diff --git a/src/features/Common/Checkbox/stories.tsx b/src/features/Common/Checkbox/stories.tsx new file mode 100644 index 00000000..0721257d --- /dev/null +++ b/src/features/Common/Checkbox/stories.tsx @@ -0,0 +1,28 @@ +import React from 'react' + +import { Checkbox } from 'features/Common' + +export default { + component: Checkbox, + title: 'Checkbox', +} + +const backgroundStyles = { + backgroundColor: '#333', + height: '200px', + padding: '20px', +} + +export const Group = () => ( +
+ + +
+) diff --git a/src/features/Common/Checkbox/styled.tsx b/src/features/Common/Checkbox/styled.tsx new file mode 100644 index 00000000..6c62dff1 --- /dev/null +++ b/src/features/Common/Checkbox/styled.tsx @@ -0,0 +1,40 @@ +import styled from 'styled-components/macro' + +export const Wrapper = styled.div` + +` + +export const Label = styled.label` + color: ${({ theme: { colors } }) => colors.text}; + font-style: normal; + font-weight: bold; + font-size: 18px; + line-height: 21px; +` + +export const Input = styled.input` + position: absolute; + z-index: -1; + opacity: 0; + + &+${Label} { + display: inline-flex; + align-items: center; + user-select: none; + } + + &+${Label}::before { + content: ''; + display: inline-block; + width: 24px; + height: 24px; + margin-right: 22px; + background-repeat: no-repeat; + background-position: center center; + background-image: url(/images/checkboxUnchecked.svg); + } + + &:checked+${Label}::before { + background-image: url(/images/checkboxChecked.svg); + } +` diff --git a/src/features/Common/Input/index.tsx b/src/features/Common/Input/index.tsx new file mode 100644 index 00000000..55bbbc6b --- /dev/null +++ b/src/features/Common/Input/index.tsx @@ -0,0 +1,58 @@ +import React from 'react' + +import { + TInputWrapper, + InputWrapper, + InputStyled, + Label, +} from './styled' + +type TInput = { + defaultValue?: string, + id: string, + inputWidth?: number, + label: string, + labelWidth?: number, + maxLength?: number, + onChange?: () => void, + required?: boolean, + type?: string, + value?: string, +} & TInputWrapper + +export const Input = ({ + defaultValue, + id, + inputWidth, + label, + labelWidth, + maxLength, + onChange, + paddingX, + required, + type, + value, + wrapperWidth, +}: TInput) => ( + + + + +) diff --git a/src/features/Common/Input/stories.tsx b/src/features/Common/Input/stories.tsx new file mode 100644 index 00000000..51bb517e --- /dev/null +++ b/src/features/Common/Input/stories.tsx @@ -0,0 +1,35 @@ +import React, { Fragment } from 'react' + +import { Input } from 'features/Common' + +export default { + component: Input, + title: 'Input', +} + +export const Empty = () => ( + +) + +export const EmailPassword = () => ( + + + + +) diff --git a/src/features/Common/Input/styled.tsx b/src/features/Common/Input/styled.tsx new file mode 100644 index 00000000..708cd621 --- /dev/null +++ b/src/features/Common/Input/styled.tsx @@ -0,0 +1,70 @@ +import styled from 'styled-components/macro' + +export type TInputWrapper = { + paddingX?: number, + wrapperWidth?: number, +} + +export const InputWrapper = styled.div` + width: ${({ wrapperWidth }) => (wrapperWidth ? `${wrapperWidth}px` : '100%')}; + height: 48px; + margin: 20px 0; + padding-left: ${({ paddingX = 24 }) => (paddingX ? `${paddingX}px` : '')}; + padding-right: ${({ paddingX = 24 }) => (paddingX ? `${paddingX}px` : '')}; + padding-top: 13px; + padding-bottom: 11px; + display: flex; + align-items: center; + background-color: #3F3F3F; + box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3); + border-radius: 2px; +` + +type TLabel = { + labelWidth?: number, +} + +export const Label = styled.label` + font-style: normal; + font-weight: normal; + font-size: 16px; + line-height: 24px; + letter-spacing: -0.01em; + padding-top: 2px; + color: ${({ theme: { colors } }) => colors.secondary}; + width: ${({ labelWidth }) => (labelWidth ? `${labelWidth}px` : '')}; +` + +type TInputStyled = { + inputWidth?: number, +} + +export const InputStyled = styled.input` + flex-grow: 1; + font-weight: bold; + font-size: 20px; + line-height: 24px; + width: ${({ inputWidth }) => (inputWidth ? `${inputWidth}px` : '')}; + background-color: transparent; + border: transparent; + margin-left: 24px; + color: ${({ theme: { colors } }) => colors.text}; + + &[type='password'] { + letter-spacing: 6px; + } + + :focus { + border-color: transparent; + outline: none; + } + + :-webkit-autofill, + :-webkit-autofill:hover, + :-webkit-autofill:focus, + :-webkit-autofill:active { + box-shadow: 0 0 0 30px #3F3F3F inset; + caret-color: ${({ theme: { colors } }) => colors.text}; + -webkit-text-fill-color: ${({ theme: { colors } }) => colors.text}; + } +` diff --git a/src/features/Common/Radio/index.tsx b/src/features/Common/Radio/index.tsx new file mode 100644 index 00000000..7ef499cf --- /dev/null +++ b/src/features/Common/Radio/index.tsx @@ -0,0 +1,38 @@ +import React, { InputHTMLAttributes } from 'react' + +import { + Wrapper, + Input, + Label, +} from './styled' + +type TCheckbox = Pick, ( + | 'checked' + | 'id' + | 'name' + | 'value' + | 'onChange' +)> & { + label?: string, +} + +export const Radio = ({ + checked, + id, + label, + name, + onChange, + value, +}: TCheckbox) => ( + + + + +) diff --git a/src/features/Common/Radio/stories.tsx b/src/features/Common/Radio/stories.tsx new file mode 100644 index 00000000..12d9cc66 --- /dev/null +++ b/src/features/Common/Radio/stories.tsx @@ -0,0 +1,30 @@ +import React from 'react' + +import { Radio } from 'features/Common' + +export default { + component: Radio, + title: 'Radio', +} + +const backgroundStyles = { + backgroundColor: '#333', + height: '200px', + padding: '20px', +} + +export const Group = () => ( +
+ + +
+) diff --git a/src/features/Common/Radio/styled.tsx b/src/features/Common/Radio/styled.tsx new file mode 100644 index 00000000..9a98a8e6 --- /dev/null +++ b/src/features/Common/Radio/styled.tsx @@ -0,0 +1,40 @@ +import styled from 'styled-components/macro' + +export const Wrapper = styled.div` + +` + +export const Label = styled.label` + color: ${({ theme: { colors } }) => colors.text}; + font-style: normal; + font-weight: bold; + font-size: 18px; + line-height: 21px; +` + +export const Input = styled.input` + position: absolute; + z-index: -1; + opacity: 0; + + &+${Label} { + display: inline-flex; + align-items: center; + user-select: none; + } + + &+${Label}::before { + content: ''; + display: inline-block; + width: 26px; + height: 26px; + margin-right: 22px; + background-repeat: no-repeat; + background-position: center center; + background-image: url(/images/radioUnchecked.svg); + } + + &:checked+${Label}::before { + background-image: url(/images/radioChecked.svg); + } +` diff --git a/src/features/Common/index.tsx b/src/features/Common/index.tsx new file mode 100644 index 00000000..7161a69c --- /dev/null +++ b/src/features/Common/index.tsx @@ -0,0 +1,5 @@ +export * from './Input' +export * from './Button' +export * from './Radio' +export * from './Checkbox' +export * from './Arrows' diff --git a/src/features/GlobalStyles/index.tsx b/src/features/GlobalStyles/index.tsx new file mode 100644 index 00000000..b281bcdf --- /dev/null +++ b/src/features/GlobalStyles/index.tsx @@ -0,0 +1,46 @@ +import { createGlobalStyle } from 'styled-components/macro' + +export const GlobalStyles = createGlobalStyle` + *, *:before, *:after { + box-sizing: border-box; + } + + body { + min-height: 100vh; + margin: 0; + padding: 0; + font-family: Montserrat, Tahoma, sans-serif; + font-size: 12px; + line-height: 12px; + color: #000; + } + + h1, h2, h3, h4, h5, h6, p, ul, li { + margin: 0; + padding: 0; + font-size: 12px; + line-height: 12px; + } + + ul, li { + list-style: none; + } + + a { + text-decoration: none; + color: #000; + } + + fieldset { + margin: 0; + min-width: 0; + padding: 0; + border: 0; + } + + button, input, select, textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; + } +` diff --git a/src/features/Theme/config.tsx b/src/features/Theme/config.tsx new file mode 100644 index 00000000..d5c52872 --- /dev/null +++ b/src/features/Theme/config.tsx @@ -0,0 +1,40 @@ +export const lightTheme = { + colors: { + background: '', + primary: '', + secondary: '', + text: '', + }, + name: 'light' as TName, + switchTheme: () => {}, +} + +export const darkTheme = { + colors: { + background: ` + radial-gradient( + 49.07% 49.07% at 50% 29.54%, + rgba(255, 255, 255, 0.14) 0%, + rgba(255, 255, 255, 0) 100% + ), + rgba(0, 0, 0, 0.95) + `, + primary: ` + linear-gradient( + 180deg, + rgba(255, 255, 255, 0) 0%, + rgba(255, 255, 255, 0.1) 0.01%, + rgba(0, 0, 0, 0.1) 99.99% + ), + #0033CC + `, + secondary: '#999999', + text: '#fff', + }, + name: 'dark' as TName, + switchTheme: () => {}, +} + +type TName = 'light' | 'dark' + +export type TCustomTheme = typeof lightTheme diff --git a/src/features/Theme/index.tsx b/src/features/Theme/index.tsx new file mode 100644 index 00000000..ff3afce0 --- /dev/null +++ b/src/features/Theme/index.tsx @@ -0,0 +1,39 @@ +import React, { + useState, + ReactNode, + useCallback, + useMemo, +} from 'react' +import { ThemeProvider } from 'styled-components' + +import { + TCustomTheme, + lightTheme, + darkTheme, +} from './config' + +type TThemeProps = { + children: ReactNode, +} + +export const Theme = ({ children }: TThemeProps) => { + const [theme, setTheme] = useState(darkTheme) + + const switchTheme = useCallback( + () => { + setTheme(theme.name === 'light' ? darkTheme : lightTheme) + }, + [theme], + ) + + const memoTheme = useMemo(() => ({ + ...theme, + switchTheme, + }), [theme, switchTheme]) + + return ( + + {children} + + ) +} diff --git a/src/types/styled-components.d.ts b/src/types/styled-components.d.ts new file mode 100644 index 00000000..19b1878f --- /dev/null +++ b/src/types/styled-components.d.ts @@ -0,0 +1,5 @@ +import { TCustomTheme } from '../features/Theme/config' + +declare module 'styled-components' { + export interface DefaultTheme extends TCustomTheme {} +}