├── .env ├── .gitignore ├── .idea ├── .gitignore ├── inspectionProfiles │ └── Project_Default.xml ├── jsLinters │ └── eslint.xml ├── modules.xml ├── react-concepts.iml └── vcs.xml ├── README.md ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── App.test.tsx ├── App.tsx ├── Theme │ ├── ThemeReducer.ts │ ├── global.tsx │ └── theme.tsx ├── components │ ├── ClickOutsideWrapper │ │ ├── ClickOutsideWrapper.tsx │ │ └── useClickOutside.ts │ ├── Form │ │ ├── FormComponent.tsx │ │ ├── Input.tsx │ │ ├── form.types.ts │ │ └── hook │ │ │ ├── useForm.tsx │ │ │ └── useInput.ts │ ├── Header │ │ ├── Header.styles.tsx │ │ └── Header.tsx │ ├── Layout │ │ └── ScreenLayout.tsx │ ├── Selection │ │ └── Selection.tsx │ ├── TreeView │ │ ├── TreeView.tsx │ │ └── data.ts │ └── styles │ │ ├── Animations.tsx │ │ ├── Button.tsx │ │ ├── LinkCustom.tsx │ │ └── Main.tsx ├── hooks │ ├── useClickOutside.ts │ ├── useDomRef.ts │ ├── useTheme.ts │ ├── useToggle.ts │ └── useUpdateEffect.ts ├── index.tsx ├── react-app-env.d.ts ├── reportWebVitals.ts ├── routes │ └── routes.tsx ├── screens │ ├── ClickOutside │ │ └── ClickOutside.tsx │ ├── FormScreen │ │ └── FormExampleScreen.tsx │ ├── Home │ │ └── HomeScreen.tsx │ ├── Me │ │ └── Me.tsx │ ├── StepMachine │ │ └── StepExample.tsx │ └── TreeFile │ │ └── TreeViewScreen.tsx ├── setupTests.ts ├── static │ ├── enums.ts │ ├── me.ts │ └── url.ts ├── store │ ├── StoreProvider.tsx │ └── makeStore.tsx └── utils │ ├── _.ts │ └── misc.ts ├── talk.md ├── tsconfig.json └── yarn.lock /.env: -------------------------------------------------------------------------------- 1 | REACT_APP_GITHUB_ACCESS_TOKEN=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 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | .vercel 26 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/jsLinters/eslint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/react-concepts.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-concepts", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.14.1", 7 | "@testing-library/react": "^11.2.7", 8 | "@testing-library/user-event": "^12.8.3", 9 | "graphql": "^16.6.0", 10 | "graphql-request": "^5.0.0", 11 | "react": "^16.13.1", 12 | "react-dom": "^16.13.1", 13 | "react-helmet": "^6.1.0", 14 | "react-icons": "^4.2.0", 15 | "react-query": "^3.26.0", 16 | "react-router-dom": "^6.0.0", 17 | "react-scripts": "4.0.3", 18 | "react-step-machine": "^1.0.1", 19 | "react-store-maker": "^1.0.5", 20 | "styled-components": "^5.3.0", 21 | "swr": "^1.3.0", 22 | "typescript": "^4.3.5", 23 | "web-vitals": "^1.1.2" 24 | }, 25 | "scripts": { 26 | "start": "react-scripts start", 27 | "build": "react-scripts build", 28 | "test": "react-scripts test", 29 | "eject": "react-scripts eject" 30 | }, 31 | "eslintConfig": { 32 | "extends": [ 33 | "react-app", 34 | "react-app/jest" 35 | ] 36 | }, 37 | "browserslist": { 38 | "production": [ 39 | ">0.2%", 40 | "not dead", 41 | "not op_mini all" 42 | ], 43 | "development": [ 44 | "last 1 chrome version", 45 | "last 1 firefox version", 46 | "last 1 safari version" 47 | ] 48 | }, 49 | "devDependencies": { 50 | "@types/jest": "^26.0.24", 51 | "@types/node": "^12.20.17", 52 | "@types/react": "^17.0.18", 53 | "@types/react-dom": "^17.0.9", 54 | "@types/react-helmet": "^6.1.2", 55 | "@types/react-router-dom": "^5.3.2", 56 | "@types/styled-components": "^5.1.11" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adiathasan/react-concepts/c1198b519a0ecdcb99e6a7d6e0ade4560558948b/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 24 | ADIAT HASAN 25 | 26 | 27 | 28 |
29 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adiathasan/react-concepts/c1198b519a0ecdcb99e6a7d6e0ade4560558948b/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adiathasan/react-concepts/c1198b519a0ecdcb99e6a7d6e0ade4560558948b/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "⚛️ patterns with 💙", 3 | "name": "__PEACE__", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | 27 | 28 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | render(); 7 | const linkElement = screen.getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled, { ThemeProvider } from 'styled-components'; 3 | import { Routes, BrowserRouter, Route } from 'react-router-dom'; 4 | 5 | import Header from './components/Header/Header'; 6 | import { themes } from './Theme/theme'; 7 | import { useTheme } from './hooks/useTheme'; 8 | import { GlobalStyles } from './Theme/global'; 9 | 10 | /* screens */ 11 | import { routes } from './routes/routes'; 12 | 13 | 14 | const App: React.FC = () => { 15 | const { theme } = useTheme(); 16 | 17 | return ( 18 | 19 | 20 | 21 |
22 | 23 | 24 | {routes.map((route, i) => ( 25 | 26 | ))} 27 | 28 | 29 | 30 | 31 | ); 32 | }; 33 | 34 | export default App; 35 | 36 | const AppStyled = styled.div` 37 | min-height: 80vh; 38 | display: flex; 39 | align-items: center; 40 | justify-content: center; 41 | padding: 0 14px; 42 | `; 43 | -------------------------------------------------------------------------------- /src/Theme/ThemeReducer.ts: -------------------------------------------------------------------------------- 1 | import { Theme } from '../hooks/useTheme'; 2 | import { createStore } from 'react-store-maker'; 3 | 4 | export type ThemeActions = 5 | | { type: 'TOGGLE_THEME_LIGHT'; payload: 'light' } 6 | | { type: 'TOGGLE_THEME_DARK'; payload: 'dark' }; 7 | 8 | export const themeReducer = (state: Theme, action: ThemeActions): Theme => { 9 | switch (action.type) { 10 | case 'TOGGLE_THEME_LIGHT': 11 | return action.payload; 12 | case 'TOGGLE_THEME_DARK': 13 | return action.payload; 14 | default: 15 | return state; 16 | } 17 | }; 18 | 19 | const [AppThemeProvider, useAppThemeValue, useAppThemeDispatch] = createStore( 20 | 'dark', 21 | themeReducer 22 | ); 23 | 24 | export { AppThemeProvider, useAppThemeValue, useAppThemeDispatch }; 25 | -------------------------------------------------------------------------------- /src/Theme/global.tsx: -------------------------------------------------------------------------------- 1 | import { createGlobalStyle } from 'styled-components'; 2 | 3 | import { IThemes } from './theme'; 4 | 5 | export const GlobalStyles = createGlobalStyle` 6 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@200&display=swap'); 7 | 8 | *, 9 | ::after, 10 | ::before { 11 | margin: 0; 12 | padding: 0; 13 | box-sizing: border-box; 14 | } 15 | 16 | body { 17 | margin: 0; 18 | font-family: 'Roboto', sans-serif; 19 | -webkit-font-smoothing: antialiased; 20 | -moz-osx-font-smoothing: grayscale; 21 | scroll-behavior: smooth; 22 | background-color: ${({theme}) => theme['bg-primary']}; 23 | color: ${({theme}) => theme['text-primary']}; 24 | transition: .2s ease-in-out all; 25 | overflow-x: hidden; 26 | min-height: 100vh; 27 | min-width: 100vw; 28 | } 29 | 30 | code { 31 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 32 | monospace; 33 | } 34 | 35 | a { 36 | text-decoration: none; 37 | color: hsl(222, 100%, 65%); 38 | } 39 | `; 40 | -------------------------------------------------------------------------------- /src/Theme/theme.tsx: -------------------------------------------------------------------------------- 1 | export enum EThemes { 2 | BG_PRIMARY = 'bg-primary', 3 | BG_SECONDARY = 'bg-secondary', 4 | TEXT_PRIMARY = 'text-primary', 5 | BTN_PRIMARY = 'btn-primary', 6 | LINK_PRIMARY = 'link-primary', 7 | } 8 | 9 | export const common = {}; 10 | 11 | export const themes = { 12 | light: { 13 | ...common, 14 | [EThemes.LINK_PRIMARY]: 'green', 15 | [EThemes.BTN_PRIMARY]: 'hsl(0, 0%, 10%)', 16 | [EThemes.BG_PRIMARY]: 'hsl(0, 0%,91%)', 17 | [EThemes.BG_SECONDARY]: 'hsl(0, 0%, 95%)', 18 | [EThemes.TEXT_PRIMARY]: 'hsl(0, 0%, 33%)', 19 | }, 20 | dark: { 21 | ...common, 22 | [EThemes.LINK_PRIMARY]: 'limegreen', 23 | [EThemes.BTN_PRIMARY]: 'hsl(0, 0%, 90%)', 24 | [EThemes.BG_PRIMARY]: 'hsl(0, 0%, 13%)', 25 | [EThemes.BG_SECONDARY]: 'hsl(0, 0%, 18%)', 26 | [EThemes.TEXT_PRIMARY]: 'hsl(0, 0%, 80%)', 27 | }, 28 | }; 29 | 30 | export interface IThemes { 31 | theme: typeof themes.light; 32 | } 33 | -------------------------------------------------------------------------------- /src/components/ClickOutsideWrapper/ClickOutsideWrapper.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | interface Props { 5 | isOpen: boolean; 6 | isUnmounted: boolean; 7 | } 8 | 9 | const ClickOutsideWrapper: React.FC = React.forwardRef< 10 | HTMLDivElement, 11 | Props 12 | >((props, ref) => { 13 | const { children, isOpen, isUnmounted } = props; 14 | 15 | if (!isOpen) return null; 16 | 17 | return ( 18 | 19 | {children} 20 | 21 | ); 22 | }); 23 | 24 | export default ClickOutsideWrapper; 25 | 26 | const StyledClickWrapper = styled.div<{ done: boolean }>` 27 | z-index: 100; 28 | animation: ${({ done }) => (done ? 'unmount' : 'mount')} 0.3s ease-out 29 | forwards; 30 | 31 | @keyframes mount { 32 | 0% { 33 | opacity: 0; 34 | transform: translateY(-10px); 35 | } 36 | 100% { 37 | opacity: 1; 38 | transform: translateY(0); 39 | } 40 | } 41 | 42 | @keyframes unmount { 43 | 0% { 44 | opacity: 1; 45 | transform: translateY(0); 46 | } 47 | 100% { 48 | opacity: 0; 49 | transform: translateY(-10px); 50 | } 51 | } 52 | `; 53 | -------------------------------------------------------------------------------- /src/components/ClickOutsideWrapper/useClickOutside.ts: -------------------------------------------------------------------------------- 1 | import { createRef, useCallback, useEffect, useRef, useState } from 'react'; 2 | 3 | const TIME_UNMOUT = 400; 4 | 5 | const useClickOutside = (clickOutsideCb?: () => void) => { 6 | const [isOpen, setIsOpen] = useState(false); 7 | 8 | const [isUnmounted, setIsUnmounted] = useState(false); 9 | 10 | const toggle = useCallback(() => { 11 | let timeFn; 12 | 13 | if (timeFn) clearTimeout(timeFn); 14 | 15 | if (!isOpen) { 16 | setIsOpen(true); 17 | return; 18 | } 19 | 20 | setIsUnmounted(true); 21 | 22 | timeFn = setTimeout(() => { 23 | setIsOpen(false); 24 | setIsUnmounted(false); 25 | }, TIME_UNMOUT); 26 | }, [isOpen]); 27 | 28 | /* 29 | *logic outside click 30 | */ 31 | 32 | const ref = createRef(); 33 | 34 | const cbRef = useRef(); 35 | 36 | cbRef.current = clickOutsideCb; 37 | 38 | useEffect(() => { 39 | const onCllickOutside = (e: MouseEvent) => { 40 | if ((e.target as Node)?.contains(ref.current) && isOpen) { 41 | cbRef.current?.(); 42 | toggle(); 43 | } 44 | }; 45 | 46 | document.addEventListener('click', onCllickOutside, true); 47 | 48 | return () => { 49 | document.removeEventListener('click', onCllickOutside, true); 50 | }; 51 | }, [ref, toggle, cbRef, isOpen]); 52 | 53 | return { ref, toggle, isOpen, register: { isOpen, isUnmounted, ref } }; 54 | }; 55 | 56 | export default useClickOutside; 57 | -------------------------------------------------------------------------------- /src/components/Form/FormComponent.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | import { EThemes } from '../../Theme/theme'; 5 | import { Api } from './form.types'; 6 | 7 | interface FormProps extends React.FormHTMLAttributes { 8 | children: React.ReactNode; 9 | onSubmit?: (values: any) => void; 10 | // formProps: React.FormHTMLAttributes; 11 | } 12 | 13 | export type SForm = ((c: FormProps) => JSX.Element | null) & { 14 | api?: Api; 15 | }; 16 | 17 | const FormComponent = (api: Api) => { 18 | const Form: SForm = useMemo( 19 | () => 20 | ({ children, onSubmit, ...all }) => { 21 | if (!Form.api) return null; 22 | 23 | const { formContext, ...rest } = Form.api; 24 | 25 | return ( 26 | 27 | { 30 | e.preventDefault(); 31 | onSubmit?.(rest.values); 32 | }}> 33 | {children} 34 | 35 | 36 | ); 37 | }, 38 | [] 39 | ); 40 | 41 | Form.api = api; 42 | 43 | return React.memo(Form); 44 | }; 45 | 46 | export default FormComponent; 47 | 48 | export const FormStyled = styled.form` 49 | background-color: ${({ theme }) => theme[EThemes.BG_SECONDARY]}; 50 | color: ${({ theme }) => theme[EThemes.TEXT_PRIMARY]}; 51 | display: grid; 52 | grid-template-columns: 1fr; 53 | gap: 1rem; 54 | padding: 2rem; 55 | border-radius: 6px; 56 | text-align: center; 57 | min-width: 300px; 58 | `; 59 | -------------------------------------------------------------------------------- /src/components/Form/Input.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useMemo, useState } from 'react'; 2 | import styled, { css } from 'styled-components'; 3 | 4 | import useInput from './hook/useInput'; 5 | import { EThemes } from '../../Theme/theme'; 6 | import { useFormContext } from './hook/useForm'; 7 | 8 | export interface Props { 9 | name: string; 10 | placeholder?: string; 11 | initValue?: string; 12 | type?: string; 13 | required?: boolean; 14 | validator?: { 15 | regex: RegExp; 16 | message: string; 17 | }; 18 | } 19 | 20 | const Input: React.FC = (props) => { 21 | const { name, validator, placeholder, type, required, initValue } = props; 22 | 23 | const input = useInput(initValue); 24 | 25 | const { setValues } = useFormContext(); 26 | 27 | const [touched, setTouched] = useState(false); 28 | 29 | const [error, setError] = useState(''); 30 | 31 | const inputMethods = useMemo( 32 | () => ({ 33 | onBlur: () => setValues({ [name]: input.value }), 34 | 35 | onFocus: () => { 36 | if (!touched) setTouched(true); 37 | }, 38 | }), 39 | [input.value, name, setValues, touched] 40 | ); 41 | 42 | useEffect(() => { 43 | if (validator && touched) { 44 | const isValid = validator.regex.test(input.value); 45 | 46 | if (!isValid) setError(validator.message); 47 | else setError(''); 48 | } 49 | }, [input.value, touched, validator]); 50 | 51 | return ( 52 | 53 | 62 | {error} 63 | 64 | ); 65 | }; 66 | 67 | export default Input; 68 | 69 | export const InputStyled = styled.input` 70 | padding: 0.5rem 0.2rem; 71 | width: 100%; 72 | border-radius: 6px; 73 | border-color: transparent; 74 | outline: none; 75 | transition: 0.3s ease; 76 | background-color: ${({ theme }) => theme[EThemes.BG_PRIMARY]}; 77 | color: ${({ theme }) => theme[EThemes.TEXT_PRIMARY]}; 78 | &:focus { 79 | border-color: limegreen; 80 | } 81 | `; 82 | 83 | export const Error = styled.div<{ error: string }>` 84 | color: orangered; 85 | min-width: 10px; 86 | padding: 0.1rem; 87 | text-align: start; 88 | font-size: small; 89 | letter-spacing: 0.1rem; 90 | opacity: 0; 91 | width: 0; 92 | transition: 0.2s ease; 93 | 94 | ${({ error }) => 95 | !!error && 96 | css` 97 | opacity: 1; 98 | width: auto; 99 | `} 100 | `; 101 | 102 | export const Wrapper = styled.div``; 103 | -------------------------------------------------------------------------------- /src/components/Form/form.types.ts: -------------------------------------------------------------------------------- 1 | import { Dispatch, Context } from 'react'; 2 | 3 | export type Values = Record; 4 | 5 | export interface InitState { 6 | values: Record; 7 | setValues: Dispatch; 8 | } 9 | 10 | export interface Api extends InitState { 11 | formContext: Context; 12 | } 13 | -------------------------------------------------------------------------------- /src/components/Form/hook/useForm.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext, useMemo, useReducer } from 'react'; 2 | 3 | import { InitState, Values } from '../form.types'; 4 | import FormComponent from '../FormComponent'; 5 | 6 | const initState = { 7 | values: {}, 8 | setValues: () => {}, 9 | }; 10 | 11 | const formContext = createContext(initState); 12 | 13 | export const useForm = () => { 14 | const [values, setValues] = useReducer( 15 | (state: Values, newState: Values) => ({ ...state, ...newState }), 16 | {} 17 | ); 18 | 19 | const api = useMemo( 20 | () => ({ 21 | values, 22 | setValues, 23 | formContext, 24 | }), 25 | [values] 26 | ); 27 | 28 | const Form = FormComponent(api); 29 | 30 | return { ...api, Form }; 31 | }; 32 | 33 | export const useFormContext = () => useContext(formContext); 34 | -------------------------------------------------------------------------------- /src/components/Form/hook/useInput.ts: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useState } from 'react'; 2 | 3 | const useInput = (init: string = '') => { 4 | const [value, setValue] = useState(() => init); 5 | 6 | const onChange = useCallback( 7 | (e: React.ChangeEvent) => setValue(e.target.value), 8 | [] 9 | ); 10 | 11 | return { value, onChange }; 12 | }; 13 | 14 | export default useInput; 15 | -------------------------------------------------------------------------------- /src/components/Header/Header.styles.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { IThemes } from '../../Theme/theme'; 3 | 4 | export const HeaderStyled = styled.header` 5 | position: relative; 6 | padding: 1rem 1rem; 7 | border-bottom: 1px solid hsla(0, 0%, 50%, 0.432); 8 | display: flex; 9 | `; 10 | 11 | export const Nav = styled.nav` 12 | flex: 1; 13 | display: flex; 14 | `; 15 | 16 | export const NavItem = styled.div` 17 | position: relative; 18 | font-size: 1.4rem; 19 | cursor: pointer; 20 | 21 | &::before { 22 | content: ''; 23 | position: absolute; 24 | top: 30px; 25 | height: 2px; 26 | width: 0; 27 | opacity: 0; 28 | transition: 0.3s ease; 29 | background: ${({ theme }) => theme['text-primary']}; 30 | } 31 | 32 | &:hover { 33 | &::before { 34 | width: 146px; 35 | opacity: 1; 36 | } 37 | } 38 | `; 39 | 40 | export const ToggleButton = styled.button` 41 | position: absolute; 42 | right: 14px; 43 | top: 4px; 44 | background: transparent; 45 | display: grid; 46 | place-items: center; 47 | border: none; 48 | height: 50px; 49 | width: 50px; 50 | cursor: pointer; 51 | transition: 0.1s ease-out; 52 | opacity: 0.85; 53 | border-radius: 50%; 54 | outline: none; 55 | 56 | @media print { 57 | display: none; 58 | } 59 | 60 | &::after { 61 | content: attr(aria-label); 62 | background: ${({ theme }) => theme['text-primary']}; 63 | color: ${({ theme }) => theme['bg-primary']}; 64 | position: absolute; 65 | top: 10px; 66 | left: -80px; 67 | transform: scale(0); 68 | opacity: 0; 69 | width: max-content; 70 | border-radius: 6px; 71 | transition: 0.3s ease; 72 | padding: 0.5rem 1rem; 73 | } 74 | 75 | &:hover { 76 | &::after { 77 | left: -160px; 78 | opacity: 0.8; 79 | transform: scale(1); 80 | } 81 | } 82 | 83 | &:hover, 84 | &:focus { 85 | opacity: 1; 86 | background: hsla(0, 0%, 50%, 0.5); 87 | } 88 | `; 89 | 90 | export const SvgToggleDark = styled.svg` 91 | width: 33px; 92 | height: 33px; 93 | 94 | .sun, 95 | .circle { 96 | fill: ${({ theme }) => theme['text-primary']}; 97 | } 98 | 99 | .sun { 100 | transform-origin: center center; 101 | transition: transform 750ms cubic-bezier(0.11, 0.14, 0.29, 1.32); 102 | } 103 | 104 | .circle { 105 | transition: 0.25s ease-out; 106 | } 107 | 108 | .sun.dark { 109 | transform: rotate(0.5turn); 110 | } 111 | 112 | .circle.dark { 113 | transform: translateX(-80px); 114 | } 115 | `; 116 | -------------------------------------------------------------------------------- /src/components/Header/Header.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useNavigate } from 'react-router-dom'; 3 | 4 | import { HeaderStyled, Nav, NavItem, SvgToggleDark, ToggleButton } from './Header.styles'; 5 | import { Theme, useTheme } from '../../hooks/useTheme'; 6 | import { ERoutes } from '../../static/enums'; 7 | 8 | const inverseTheme = (theme: Theme) => (theme === 'light' ? 'dark' : 'light'); 9 | 10 | interface Props {} 11 | 12 | export const LOGO = `ADIAT HASAN`; 13 | 14 | const Header: React.FC = () => { 15 | const navigate = useNavigate(); 16 | 17 | const { theme, toggleTheme } = useTheme(); 18 | 19 | return ( 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | ); 36 | }; 37 | 38 | export default Header; 39 | -------------------------------------------------------------------------------- /src/components/Layout/ScreenLayout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import styled from 'styled-components'; 4 | import {Helmet} from 'react-helmet'; 5 | 6 | interface Props { 7 | title?: string; 8 | } 9 | 10 | const ScreenLayout: React.FC = ({title = '', children}) => { 11 | return ( 12 | 13 | 14 | ADIAT HASAN {'| ' + title} 15 | 16 | {children} 17 | 18 | ); 19 | }; 20 | 21 | export default ScreenLayout; 22 | 23 | const ScreenLayoutStyled = styled.div` 24 | min-height: 400px; 25 | min-width: 340px; 26 | `; 27 | -------------------------------------------------------------------------------- /src/components/Selection/Selection.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | import { FaCheck } from 'react-icons/fa'; 4 | import { pop } from '../styles/Animations'; 5 | 6 | interface Props { 7 | selected: boolean; 8 | label: any; 9 | } 10 | 11 | const Selection: React.FC = (props) => { 12 | const { selected, label } = props; 13 | 14 | return ( 15 | 16 | 17 | {selected && } 18 | 19 |
{label}
20 |
21 | ); 22 | }; 23 | 24 | export default Selection; 25 | 26 | const SelectionContainer = styled.div` 27 | display: flex; 28 | align-items: center; 29 | 30 | .flex-1 { 31 | flex: 1; 32 | } 33 | `; 34 | 35 | const CheckBox = styled.div` 36 | padding: 1px; 37 | width: 20px; 38 | height: 20px; 39 | border-radius: 4px; 40 | border: 1px solid limegreen; 41 | margin-right: 0.5rem; 42 | `; 43 | 44 | const CheckIcon = styled(FaCheck)` 45 | border-radius: inherit; 46 | 47 | animation: ${pop} 0.3s ease forwards; 48 | `; 49 | -------------------------------------------------------------------------------- /src/components/TreeView/TreeView.tsx: -------------------------------------------------------------------------------- 1 | import React, {HTMLAttributes} from 'react'; 2 | import {useLocation, useNavigate} from 'react-router-dom'; 3 | import {RiArrowRightSFill} from 'react-icons/ri'; 4 | import styled from 'styled-components'; 5 | 6 | import {_} from '../../utils/_'; 7 | import {EUrl} from '../../static/url'; 8 | import {childData, viewData} from './data'; 9 | import {EThemes} from '../../Theme/theme'; 10 | import {getUrlParam} from '../../utils/misc'; 11 | import Selection from '../Selection/Selection'; 12 | 13 | interface Props { 14 | data: typeof viewData; 15 | onToggle?: (index: number, checked: boolean) => void; 16 | } 17 | 18 | const TreeView: React.FC = (props) => { 19 | const navigate = useNavigate(); 20 | const location = useLocation(); 21 | 22 | const {data, onToggle} = props; 23 | 24 | /** 25 | * convert it to hook 26 | */ 27 | const attributes = getUrlParam(location, EUrl.Attributes)?.split(','); 28 | 29 | const urlPusher = (name: string) => { 30 | if (!attributes) { 31 | navigate(location.pathname + `?${EUrl.Attributes}=${name}`); 32 | return; 33 | } 34 | 35 | let ref = [...attributes]; 36 | 37 | if (attributes.includes(name)) { 38 | ref = ref.filter((attr) => attr !== name); 39 | } else { 40 | ref.push(name); 41 | } 42 | 43 | if (_.isArrayEmpty(ref)) { 44 | navigate(location.pathname); 45 | return; 46 | } 47 | 48 | navigate(location.pathname + `?${EUrl.Attributes}=${ref.join(',')}`); 49 | }; 50 | 51 | return ( 52 |
53 | {data.map((child, i) => { 54 | const {children, name, show} = child; 55 | const selected = !!attributes?.includes(name); 56 | 57 | return ( 58 |
59 | {_.isArrayEmpty(children) ? ( 60 | 67 | ) : ( 68 | onToggle?.(i, !child.show)}> 72 | 73 | {name} 74 | 75 | )} 76 | 77 | {show && 78 | children.map((child, i) => { 79 | const selected = !!attributes?.includes(child.name); 80 | return ( 81 | 87 | ); 88 | })} 89 |
90 | ); 91 | })} 92 |
93 | ); 94 | }; 95 | 96 | export default TreeView; 97 | 98 | interface ChildProps { 99 | child: typeof childData; 100 | selected: boolean; 101 | urlPusher: (name: string) => void; 102 | } 103 | 104 | let ChildComponent: React.FC> = (props) => { 105 | const {selected, urlPusher, child, ...rest} = props; 106 | 107 | return ( 108 | urlPusher(child.name)} index={0}> 109 | 113 |

{child.name}

114 |

{child.value}

115 | 116 | } 117 | /> 118 |
119 | ); 120 | }; 121 | 122 | ChildComponent = React.memo(ChildComponent) 123 | 124 | const ParentNode = styled.a<{ active: boolean }>` 125 | &::selection { 126 | color: transparent; 127 | } 128 | 129 | display: flex; 130 | align-items: center; 131 | justify-content: flex-start; 132 | font-weight: bold; 133 | letter-spacing: 0.1rem; 134 | font-size: large; 135 | margin: 0.45rem 0; 136 | cursor: pointer; 137 | padding: 0.5rem; 138 | border-radius: 6px; 139 | transition: 0.3s ease all; 140 | background-color: ${({theme}) => theme[EThemes.BG_PRIMARY]}; 141 | 142 | border: 1px solid ${({active}) => (active ? 'limegreen' : 'transparent')}; 143 | 144 | &:hover { 145 | filter: brightness(120%); 146 | } 147 | `; 148 | 149 | const ArrowIndicator = styled(RiArrowRightSFill)<{ active: boolean }>` 150 | margin-right: 0.25rem; 151 | transition: 0.3s ease all; 152 | transform: rotate(${({active}) => (active ? '90deg' : '0deg')}); 153 | `; 154 | 155 | const Label = styled.div` 156 | display: grid; 157 | grid-template-columns: 1fr 1fr; 158 | 159 | p { 160 | &:last-child { 161 | background-color: ${({theme}) => theme[EThemes.BG_SECONDARY]}; 162 | border-radius: 4px; 163 | padding: 0.1rem; 164 | text-align: center; 165 | } 166 | } 167 | `; 168 | 169 | const ChildNode = styled.div<{ index: number }>` 170 | margin: 0.25rem 0; 171 | cursor: pointer; 172 | padding: 0.25rem 0.5rem; 173 | border-radius: 4px; 174 | margin-left: 1rem; 175 | filter: brightness(100%); 176 | background-color: ${({theme}) => theme[EThemes.BG_PRIMARY]}; 177 | animation: mount 0.3s ease-out forwards; 178 | 179 | &:hover { 180 | filter: brightness(120%); 181 | } 182 | 183 | @keyframes mount { 184 | 0% { 185 | transform: translateY(-10px); 186 | } 187 | 100% { 188 | transform: translateY(0); 189 | } 190 | } 191 | `; 192 | -------------------------------------------------------------------------------- /src/components/TreeView/data.ts: -------------------------------------------------------------------------------- 1 | export const viewData = [ 2 | { 3 | name: 'Main', 4 | value: null, 5 | show: false, 6 | selected: null, 7 | count: null, 8 | children: [ 9 | { 10 | name: 'MotherBoard', 11 | children: [], 12 | value: 'm4 ', 13 | selected: false, 14 | count: 29, 15 | }, 16 | { 17 | name: 'Cpu', 18 | children: [], 19 | value: 'm4 k4', 20 | selected: false, 21 | count: 9, 22 | }, 23 | { 24 | name: 'Ram', 25 | children: [], 26 | value: 'm4 ', 27 | selected: false, 28 | count: 2, 29 | }, 30 | ], 31 | }, 32 | { 33 | name: 'Decode', 34 | show: false, 35 | children: [ 36 | { 37 | name: 'Dope', 38 | children: [], 39 | value: 'm4 d9', 40 | selected: false, 41 | count: 101, 42 | }, 43 | { 44 | name: 'Dam', 45 | children: [], 46 | value: 'socket', 47 | selected: false, 48 | count: 33, 49 | }, 50 | ], 51 | }, 52 | { 53 | name: 'No child', 54 | value: 'boket', 55 | show: false, 56 | selected: false, 57 | children: [], 58 | count: 89, 59 | }, 60 | ]; 61 | 62 | export const childData = { 63 | name: 'MotherBoard', 64 | children: [], 65 | value: 'm4 ', 66 | selected: false, 67 | }; 68 | -------------------------------------------------------------------------------- /src/components/styles/Animations.tsx: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components'; 2 | 3 | export const pop = keyframes` 4 | 0% { 5 | opacity: 0; 6 | transform: scale(0); 7 | } 8 | 75% { 9 | opacity: 1; 10 | transform: scale(1.1); 11 | } 12 | 100% { 13 | opacity: 1; 14 | transform: scale(1); 15 | } 16 | `; 17 | -------------------------------------------------------------------------------- /src/components/styles/Button.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | import { EThemes } from '../../Theme/theme'; 4 | 5 | export const Button = styled.button` 6 | padding: 0.5rem 0.3rem; 7 | outline: none; 8 | border-color: transparent; 9 | border-radius: 6px; 10 | width: 100%; 11 | transition: 0.3s ease; 12 | cursor: pointer; 13 | &:focus { 14 | border-color: limegreen; 15 | } 16 | `; 17 | 18 | export const ButtonPrimary = styled(Button)` 19 | background-color: ${({ theme }) => theme[EThemes.BTN_PRIMARY]}; 20 | color: ${({ theme }) => theme[EThemes.BG_PRIMARY]}; 21 | `; 22 | -------------------------------------------------------------------------------- /src/components/styles/LinkCustom.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | import styled from 'styled-components'; 3 | import { EThemes } from '../../Theme/theme'; 4 | 5 | export const LinkCustom = styled(Link)` 6 | text-decoration: none; 7 | color: ${({ theme }) => theme[EThemes.LINK_PRIMARY]}; 8 | font-size: large; 9 | font-weight: 600; 10 | `; 11 | 12 | export const LinkBlank = styled.a` 13 | text-decoration: none; 14 | color: ${({ theme }) => theme[EThemes.LINK_PRIMARY]}; 15 | font-size: large; 16 | font-weight: 600; 17 | `; 18 | -------------------------------------------------------------------------------- /src/components/styles/Main.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | import { EThemes } from '../../Theme/theme'; 4 | 5 | export const MainWrapper = styled.main` 6 | position: relative; 7 | background-color: ${({ theme }) => theme[EThemes.BG_SECONDARY]}; 8 | color: ${({ theme }) => theme[EThemes.TEXT_PRIMARY]}; 9 | padding: 2rem; 10 | border-radius: 2px; 11 | min-width: 300px; 12 | box-shadow: 0 0 15px -2px rgba(0, 0, 0, 0.1); 13 | `; 14 | -------------------------------------------------------------------------------- /src/hooks/useClickOutside.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export function useOnClickOutside( 4 | ref: React.MutableRefObject, 5 | handler: () => void 6 | ) { 7 | const cbRef = React.useRef(handler); 8 | 9 | React.useEffect(() => { 10 | cbRef.current = handler; 11 | }); 12 | 13 | React.useEffect( 14 | () => { 15 | const listener = (event: MouseEvent | TouchEvent) => { 16 | // Do nothing if clicking ref's element or descendent elements 17 | if (!ref.current || ref.current.contains(event.target)) { 18 | return; 19 | } 20 | 21 | cbRef.current(); 22 | }; 23 | 24 | document.addEventListener('mousedown', listener); 25 | document.addEventListener('touchstart', listener); 26 | 27 | return () => { 28 | document.removeEventListener('mousedown', listener); 29 | document.removeEventListener('touchstart', listener); 30 | }; 31 | }, 32 | // Add ref and handler to effect dependencies 33 | // It's worth noting that because passed in handler is a new ... 34 | // ... function on every render that will cause this effect ... 35 | // ... callback/cleanup to run every render. It's not a big deal ... 36 | // ... but to optimize you can wrap handler in useCallback before ... 37 | // ... passing it into this hook. 38 | [ref, handler] 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/hooks/useDomRef.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export const useDomRef = () => { 4 | return React.useRef(null); 5 | }; 6 | -------------------------------------------------------------------------------- /src/hooks/useTheme.ts: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react'; 2 | import { useAppThemeDispatch, useAppThemeValue } from '../Theme/ThemeReducer'; 3 | 4 | export type Theme = 'light' | 'dark'; 5 | 6 | export const useTheme = () => { 7 | const theme = useAppThemeValue(); 8 | 9 | const themeDispatch = useAppThemeDispatch(); 10 | 11 | const toggleTheme = useCallback(() => { 12 | if (theme === 'light') { 13 | themeDispatch({ type: 'TOGGLE_THEME_DARK', payload: 'dark' }); 14 | return; 15 | } 16 | 17 | themeDispatch({ 18 | type: 'TOGGLE_THEME_LIGHT', 19 | payload: 'light', 20 | }); 21 | }, [theme, themeDispatch]); 22 | 23 | return { themeDispatch, theme, toggleTheme, isDark: theme === 'dark' }; 24 | }; 25 | -------------------------------------------------------------------------------- /src/hooks/useToggle.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export const useToggle = (init = false) => { 4 | const [value, setValue] = React.useState(init); 5 | 6 | const toggleValue = (given?: boolean) => 7 | typeof given !== 'undefined' ? setValue(given) : setValue((o) => !o); 8 | 9 | return [value, toggleValue] as const; 10 | }; 11 | -------------------------------------------------------------------------------- /src/hooks/useUpdateEffect.ts: -------------------------------------------------------------------------------- 1 | import { DependencyList, EffectCallback, useEffect, useRef } from 'react'; 2 | 3 | export const useUpdateEffect = (cb: EffectCallback, deps?: DependencyList) => { 4 | const cbRef = useRef(cb); 5 | 6 | const isMounted = useRef(false); 7 | 8 | useEffect(() => { 9 | if (isMounted.current) { 10 | cbRef.current(); 11 | return; 12 | } 13 | 14 | isMounted.current = true; 15 | // eslint-disable-next-line react-hooks/exhaustive-deps 16 | }, deps); 17 | 18 | return { isMounted }; 19 | }; 20 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | import { AppThemeProvider } from './Theme/ThemeReducer'; 7 | 8 | ReactDOM.render( 9 | 10 | 11 | 12 | 13 | , 14 | document.getElementById('root') 15 | ); 16 | 17 | // If you want to start measuring performance in your app, pass a function 18 | // to log results (for example: reportWebVitals(console.log)) 19 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 20 | reportWebVitals(); 21 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /src/routes/routes.tsx: -------------------------------------------------------------------------------- 1 | import {ERoutes} from '../static/enums'; 2 | 3 | /** 4 | * Screens 5 | */ 6 | import FormExampleScreen from '../screens/FormScreen/FormExampleScreen'; 7 | import ClickOutside from '../screens/ClickOutside/ClickOutside'; 8 | import HomeScreen from '../screens/Home/HomeScreen'; 9 | import TreeFile from '../screens/TreeFile/TreeViewScreen'; 10 | import StepExample from '../screens/StepMachine/StepExample'; 11 | import Me from "../screens/Me/Me"; 12 | 13 | export const routes = [ 14 | { 15 | element: , 16 | path: ERoutes.Home, 17 | }, 18 | { 19 | element: , 20 | path: ERoutes.Me, 21 | }, 22 | { 23 | element: , 24 | path: ERoutes.Form, 25 | }, 26 | { 27 | element: , 28 | path: ERoutes.ClickOutside, 29 | }, 30 | { 31 | element: , 32 | path: ERoutes.ReactStepMachine, 33 | }, 34 | { 35 | element: , 36 | path: ERoutes.TreeView, 37 | }, 38 | ]; 39 | -------------------------------------------------------------------------------- /src/screens/ClickOutside/ClickOutside.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | import { ImProfile } from 'react-icons/im'; 4 | import { CgLogOut, CgProfile, CgArrowDown } from 'react-icons/cg'; 5 | 6 | import { EThemes } from '../../Theme/theme'; 7 | import { MainWrapper } from '../../components/styles/Main'; 8 | import { ButtonPrimary } from '../../components/styles/Button'; 9 | import ScreenLayout from '../../components/Layout/ScreenLayout'; 10 | import useClickOutside from '../../components/ClickOutsideWrapper/useClickOutside'; 11 | import ClickOutsideWrapper from '../../components/ClickOutsideWrapper/ClickOutsideWrapper'; 12 | 13 | const ClickOutside: React.FC = () => { 14 | const { toggle, register } = useClickOutside(); 15 | 16 | return ( 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | Dashboard 32 | 33 | 34 | 35 | 36 | 39 | 40 | Logout 41 | 42 | 43 | 44 | 45 | 46 | 47 | ); 48 | }; 49 | 50 | export default ClickOutside; 51 | 52 | export const FloatingMenu = styled.div` 53 | position: absolute; 54 | right: 0px; 55 | top: 110%; 56 | border-radius: 6px; 57 | min-width: 100px; 58 | padding: 0.3rem; 59 | background-color: ${({ theme }) => theme[EThemes.BG_SECONDARY]}; 60 | border: 1px solid ${({ theme }) => theme[EThemes.TEXT_PRIMARY]}; 61 | box-shadow: 0 0 15px -2px rgba(0, 0, 0, 0.1); 62 | transform: translateY(10px); 63 | `; 64 | 65 | export const BtnRounded = styled.div` 66 | border-radius: 50%; 67 | margin-left: auto; 68 | height: 80px; 69 | width: 80px; 70 | display: flex; 71 | align-items: center; 72 | justify-content: center; 73 | cursor: pointer; 74 | background-color: ${({ theme }) => theme[EThemes.BG_PRIMARY]}; 75 | `; 76 | 77 | export const BtnItem = styled(ButtonPrimary)` 78 | background-color: ${({ theme }) => theme[EThemes.BG_PRIMARY]}; 79 | color: ${({ theme }) => theme[EThemes.TEXT_PRIMARY]}; 80 | `; 81 | 82 | export const Item = styled.section` 83 | margin-bottom: 0.2rem; 84 | `; 85 | 86 | export const AnimArrow = styled(CgArrowDown)` 87 | transform: translateY(-10px); 88 | position: absolute; 89 | bottom: 100%; 90 | z-index: 1; 91 | right: 10%; 92 | animation: bounce 2s ease-in-out infinite alternate; 93 | 94 | @keyframes bounce { 95 | 0% { 96 | transform: translateY(-10px); 97 | } 98 | 100% { 99 | transform: translateY(20px); 100 | } 101 | } 102 | `; 103 | -------------------------------------------------------------------------------- /src/screens/FormScreen/FormExampleScreen.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import Input from '../../components/Form/Input'; 4 | import ScreenLayout from '../../components/Layout/ScreenLayout'; 5 | import {useForm} from '../../components/Form/hook/useForm'; 6 | import {ButtonPrimary} from '../../components/styles/Button'; 7 | 8 | const FormExampleScreen: React.FC = () => { 9 | const {Form} = useForm(); 10 | 11 | const handleSubmit = (values: any) => { 12 | alert(JSON.stringify(values, null, 2)); 13 | }; 14 | 15 | return ( 16 | 17 |
18 |

SIGN UP

19 | 26 | 36 | SUBMIT 37 |
38 |
39 | ); 40 | }; 41 | 42 | export default FormExampleScreen; 43 | -------------------------------------------------------------------------------- /src/screens/Home/HomeScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | import { LinkBlank, LinkCustom } from '../../components/styles/LinkCustom'; 5 | import { patternLinks } from '../../static/me'; 6 | import { EThemes } from '../../Theme/theme'; 7 | 8 | const HomeScreen: React.FC = () => { 9 | return ( 10 |
11 | {patternLinks 12 | .filter((link) => link.enabled) 13 | .map((data, i) => ( 14 | 15 |

{data.title}

16 | 17 | Demo! 18 | 19 | Code! 20 | 21 | 22 |
23 | ))} 24 |
25 | ); 26 | }; 27 | 28 | export default HomeScreen; 29 | 30 | export const LinkContainer = styled.div` 31 | display: flex; 32 | align-items: center; 33 | 34 | & > * { 35 | margin-right: 0.8rem; 36 | } 37 | `; 38 | 39 | export const H2 = styled.h2` 40 | margin-bottom: 1rem; 41 | `; 42 | 43 | export const Main = styled.main` 44 | padding: 1rem; 45 | width: 1200px; 46 | max-width: 100%; 47 | display: grid; 48 | grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); 49 | gap: 2rem; 50 | perspective: 800px; 51 | `; 52 | 53 | export const Each = styled.div` 54 | background-color: ${({ theme }) => theme[EThemes.BG_SECONDARY]}; 55 | color: ${({ theme }) => theme[EThemes.TEXT_PRIMARY]}; 56 | padding: 2rem; 57 | border-radius: 6px; 58 | box-shadow: 0 5px 10px rgba(0, 0, 0, 0.12); 59 | transition: 0.3s ease; 60 | 61 | &:hover { 62 | transform: scale(1.05); 63 | filter: brightness(1.3); 64 | } 65 | `; 66 | -------------------------------------------------------------------------------- /src/screens/Me/Me.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { BiLinkExternal } from 'react-icons/bi'; 3 | import { IoLogoStackoverflow } from 'react-icons/io5'; 4 | import { AiFillGithub, AiFillLinkedin } from 'react-icons/ai'; 5 | 6 | import { EThemes } from '../../Theme/theme'; 7 | import { useTheme } from '../../hooks/useTheme'; 8 | import ScreenLayout from '../../components/Layout/ScreenLayout'; 9 | 10 | const Me = () => { 11 | const { isDark } = useTheme(); 12 | 13 | return ( 14 | 15 |
16 | 17 | 18 |
19 | Senior Software Engineer | Fullstack 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 | 33 | Highly motivated and experienced Fullstack Software Developer with a passion for frontend engineering. 34 |
35 |
36 | My expertise mainly lies within Typescript, React.js, Node.js & Nest.js to create clean, reusable, and 37 | maintainable code. But this doesn't stop me from learning new technologies and languages and adopting them 38 | when needed. 39 |
40 |
41 | Currently I'm focusing on open source projects as it leads to a better understanding of certain concepts 42 | and also helps me to further sharpen my skills. 43 |
44 |
45 | 46 | Work Experience 47 | 48 | 49 | Senior Frontend Engineer{' '} 50 | 51 | Giraffe360 52 | {' '} 53 | (December 2023 - present) 54 | 55 | 56 | 1. Developing Video Editor with WebGL.
57 |
58 | 59 | Senior Software Engineer{' '} 60 | 61 | Blue Tech 62 | {' '} 63 | (October 2021 - November 2023) 64 | 65 | 66 | 1. Architected the frontend for bdtickets.com and a few internal apps.
67 | 2. Implemented scalable design patterns in client and server side
68 | 3. Reactive development for the type-safe data layer with React Query.
69 | 4. Adapted advanced patterns for effortless integration of api's with autogenerated type-safe hooks.{' '} 70 |
71 |
72 | 73 | Typescript 74 | React 75 | React-native 76 | Next 77 | Node 78 | Nest 79 | SSR 80 | SSG 81 | React-query 82 | Antd 83 | 84 | 85 | Frontend Engineer{' '} 86 | 87 | Xen Works 88 | {' '} 89 | (August 2021 - November 202) 90 | 91 | 92 | 1. Responsible for developing scalable UIs with solid principles.
93 | 2. Introducing effortlessly scalable type-safe integrations with material UI inputs and react-hook-form.{' '} 94 |
95 | 3. Adapting React Query while replacing redux for server-side type-safe data management through apis.{' '} 96 |
97 | 4. Introducing the pattern for autogenerated custom hooks for type-safe data fetching and mutation.
98 | 5. Providing solutions to team members for better optimization and maintainability of code.
99 | 6. Decision-making to further improvement of code quality and performance. 100 |
101 |
102 | 103 | Typescript 104 | React 105 | Redux 106 | Redux-logics 107 | React-query 108 | Tailwind 109 | Styled-components 110 | 111 | 112 | Frontend Engineer{' '} 113 | 114 | Now 115 | {' '} 116 | (January 2020 - July 2021) 117 | 118 | 119 | 1. Was in charge of developing the client side of now.com.bd from start to finish.
120 | 2. Technology used: React, Next.js, Typescript, React Query, Custom Hooks, Graphql, Sass, Tailwind.
121 | 3. Made the technical decisions by consulting with our tech lead to improve the projects. 122 |
123 | 4. Worked on Rider tracking mobile app [React Native] 124 |
125 |
126 | 127 | Typescript 128 | React 129 | React-native 130 | Next 131 | SSG 132 | React-query 133 | Graphql 134 | Parse Server 135 | Tailwind 136 | Styled-components 137 | 138 |
139 | 140 | Open Source Contributions 141 | 142 | <a target='_blank' rel='noreferrer' href='https://github.com/adiathasan/mui-react-hook-form-plus'> 143 | Mui React Hook Form + 144 | <ExternalIcon style={{ marginLeft: 4, marginBottom: 1 }} /> 145 | </a> 146 | <img 147 | alt='stars' 148 | src='https://badgen.net/github/stars/adiathasan/mui-react-hook-form-plus' 149 | style={{ transform: 'translateY(5px) translateX(16px)', width: 60, float: 'right' }} 150 | /> 151 | 152 | 153 | The perfect recipe with material-ui 💙 TS 💙 react-hook-form & 154 | more. The complete type-safe material-ui and react-hook-form combo and beyond with simple api. Highly 155 | Customizable and supports 99% use-cases. 156 |
157 |
158 | Material ui is effective for building UI's of great UX & DX in a React project. 159 | To make it stateful, we have pay the price of sacrificing DX & sometimes lack of performance and 160 | no-scalability.{' '} 161 |
162 |
163 |
164 | 165 | <a target='_blank' rel='noreferrer' href='https://github.com/adiathasan/react-store-maker'> 166 | React Store Maker <ExternalIcon style={{ marginLeft: 4 }} /> 167 | </a> 168 | <img 169 | alt='stars' 170 | src='https://badgen.net/github/stars/adiathasan/react-store-maker' 171 | style={{ transform: 'translateY(5px) translateX(16px)', width: 60, float: 'right' }} 172 | /> 173 | 174 | 175 | It is a utility function for creating stores for global / local state management with the{' '} 176 | Context API approach in React.js applications. This simplifies contexts and reduces boiler 177 | plate code and has some built in optimization. 178 |
179 |
180 | While building the architecture BD tickets app I didn't want to use redux or other heavy state management 181 | library. I was using Contexts for small things and react-query for data fetching. Then I felt the 182 | necessity of writing a function that would just give the context instead of writing a lot of boiler plate 183 | code.{' '} 184 |
185 |
186 |
187 | 188 | <a target='_blank' rel='noreferrer' href='https://github.com/adiathasan/react-step-machine'> 189 | React Step Machine <ExternalIcon /> 190 | </a> 191 | <img 192 | alt='stars' 193 | src='https://badgen.net/github/stars/adiathasan/react-step-machine' 194 | style={{ transform: 'translateY(5px) translateX(16px)', width: 60, float: 'right' }} 195 | /> 196 | 197 | 198 | A hook-based multi-step wizard library made for React.js apps with vast control over the logic of the user 199 | as per use-case. It's API is much simpler than other step wizards out there. 200 |
201 |
202 | The inspiration came form{' '} 203 | 204 | react-step-wizard 205 | 206 | . It didn't support my use-cases that I needed for my project. Hence, I built my own step wizard with 207 | recent react concepts.{' '} 208 |
209 | 210 |
211 |
212 | 213 | <a target='_blank' rel='noreferrer' href='https://adiathasan.vercel.app'> 214 | React Concepts <ExternalIcon style={{ marginLeft: 4, marginBottom: 0 }} /> 215 | </a> 216 | <img 217 | alt='stars' 218 | src='https://badgen.net/github/stars/adiathasan/react-concepts' 219 | style={{ transform: 'translateY(5px) translateX(16px)', width: 60, float: 'right' }} 220 | /> 221 | 222 | 223 | Advanced concepts of react such as hooks, contexts, reusable components, custom hooks, prop getters, theme 224 | with styled-components, animations and more. 225 |
226 |
227 | When I learn a new concept in react I tend to practice it and share my work so that other developers can 228 | grasp the concepts and remain upto date with the industry standard.{' '} 229 |
230 |
231 |
232 | 233 | 234 | General Information's 235 | London 🇬🇧 United Kingdom 236 | 237 | +44 7432 113152 238 | 239 | 240 | adiathasan.me@gmail.com 241 | 242 | 243 | 244 | Skills 245 | 246 | Javascript 247 | Typescript 248 | Css 249 | Node 250 | Express 251 | Nest 252 | Parse Server 253 | Monorepo 254 | React 255 | React-native 256 | Next 257 | Svelte 258 | Solid.js 259 | React-query 260 | React-hook-form 261 | Material-ui 262 | React-testing-library 263 | Jest 264 | Redux 265 | Graphql 266 | Tailwind 267 | Antd 268 | Styled-components 269 | 270 | 271 | 272 | Related Links 273 | 274 | github.com/adiathasan 275 | 276 | 277 | linkedin.com/adiathasan 278 | 279 | 280 | leetcode.com/adiathasan 281 | 282 | 283 | stackoverflow.com/adiat-hasan 284 | 285 | 286 | adiathasan.medium.com 287 | 288 | 289 | 290 | Blogs 291 | 1.{' '} 292 | 293 | React Form made simple 🚀 294 | 295 |
296 |
297 | 2.{' '} 298 | 299 | Ditching Redux in React — Part 1 🏎️ 300 | 301 |
302 |
303 | 3.{' '} 304 | 305 | Typesafe routes in Next.js 📘 306 | 307 |
308 | 309 | Education 310 | BSc in Computer Science & Engineering 311 |
312 |
313 | University of The People (2020 - present) 314 |
315 | 316 | Certifications 317 |
318 | 319 | Epic React by kent C. Dodds | Advanced React Patterns (01/2022 - Present){' '} 320 | 324 | 325 | 326 | 327 |
328 | 329 | HTML, CSS, and Javascript for Web Developers(coursera) (08/2020 - 08/2020){' '} 330 | 334 | 335 | 336 | 337 |
338 | 339 | Machine-learning(coursera) (05/2020 - 06/2020){' '} 340 | 344 | 345 | 346 | 347 |
348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 |
357 |
358 |
359 | ); 360 | }; 361 | 362 | export default Me; 363 | 364 | export const Main = styled.main` 365 | width: 1400px; 366 | max-width: 100%; 367 | display: grid; 368 | grid-template-columns: repeat(12, 1fr); 369 | grid-gap: 1.4rem; 370 | min-height: 90vh; 371 | margin-top: 20px; 372 | margin-bottom: 60px; 373 | 374 | @media (max-width: 780px) { 375 | grid-template-columns: 1fr; 376 | } 377 | `; 378 | 379 | export const Each = styled.section<{ colSpan?: string; rowSpan?: string; hide?: boolean }>` 380 | background-color: ${({ theme }) => theme[EThemes.BG_SECONDARY]}; 381 | color: ${({ theme }) => theme[EThemes.TEXT_PRIMARY]}; 382 | padding: 1rem 1.5rem; 383 | grid-column: ${({ colSpan }) => colSpan}; 384 | grid-row: ${({ rowSpan }) => rowSpan}; 385 | opacity: ${({ hide }) => (hide ? 0 : 1)}; 386 | 387 | @media (max-width: 780px) { 388 | grid-column: span 12; 389 | } 390 | `; 391 | 392 | export const Sides = styled.section<{ colSpan?: string; rowSpan?: string }>` 393 | grid-column: ${({ colSpan }) => colSpan}; 394 | grid-row: ${({ rowSpan }) => rowSpan}; 395 | display: grid; 396 | grid-template-columns: 1fr; 397 | grid-gap: 1.4rem; 398 | 399 | @media (max-width: 780px) { 400 | grid-column: span 12; 401 | grid-gap: 1rem; 402 | } 403 | `; 404 | 405 | export const Title = styled.h2` 406 | font-weight: bold; 407 | margin-bottom: 20px; 408 | 409 | @media (max-width: 780px) { 410 | font-size: 1.2rem; 411 | } 412 | `; 413 | 414 | export const ExternalIcon = styled(BiLinkExternal)` 415 | transform: translateY(5px); 416 | `; 417 | 418 | export const Chip = styled.span` 419 | border-radius: 4px; 420 | padding: 0.3rem 0.5rem; 421 | background-color: ${({ theme }) => theme[EThemes.BG_PRIMARY]}; 422 | font-size: smaller; 423 | `; 424 | 425 | export const ChipContainer = styled.div` 426 | margin-top: 10px; 427 | margin-bottom: 30px; 428 | display: flex; 429 | gap: 6px; 430 | flex-wrap: wrap; 431 | `; 432 | 433 | export const Subtitle = styled.h4` 434 | font-weight: 600; 435 | margin-bottom: 14px; 436 | letter-spacing: 0.03rem; 437 | line-height: 25px; 438 | 439 | @media (max-width: 780px) { 440 | font-size: 0.9rem; 441 | } 442 | `; 443 | 444 | export const Text = styled.p` 445 | font-weight: 380; 446 | line-height: 25px; 447 | letter-spacing: 0.03rem; 448 | margin-bottom: 0.2rem; 449 | 450 | @media (max-width: 780px) { 451 | font-size: 0.9rem; 452 | } 453 | `; 454 | -------------------------------------------------------------------------------- /src/screens/StepMachine/StepExample.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { StepMachine, Step, useStepActions, StepContainer } from 'react-step-machine'; 3 | 4 | import Input from '../../components/Form/Input'; 5 | import ScreenLayout from '../../components/Layout/ScreenLayout'; 6 | import { useForm } from '../../components/Form/hook/useForm'; 7 | import { MainWrapper } from '../../components/styles/Main'; 8 | import { ButtonPrimary } from '../../components/styles/Button'; 9 | 10 | interface FormValue { 11 | username: string; 12 | email: string; 13 | } 14 | 15 | const StepExample = () => { 16 | const { Form } = useForm(); 17 | 18 | const handleSubmit = (values: FormValue) => { 19 | alert(JSON.stringify(values, null, 2)); 20 | }; 21 | 22 | return ( 23 | 24 | 25 | 26 | 27 | 28 |
29 |

SIGN UP

30 | 37 | 47 |
48 |
49 | 50 |
51 |

Password

52 | 59 | 60 | 67 |
68 |
69 | 70 |

You are awesome

71 |
72 |
73 | 74 |
75 |
76 |
77 | ); 78 | }; 79 | 80 | export default StepExample; 81 | 82 | export const ToggleBtn = () => { 83 | const { nextStep, previousStep } = useStepActions(); 84 | 85 | return ( 86 |
87 | previousStep()}>Prev 88 | nextStep()}>Next 89 |
90 | ); 91 | }; 92 | -------------------------------------------------------------------------------- /src/screens/TreeFile/TreeViewScreen.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | import ScreenLayout from '../../components/Layout/ScreenLayout'; 5 | import TreeView from '../../components/TreeView/TreeView'; 6 | import { viewData } from '../../components/TreeView/data'; 7 | import { MainWrapper } from '../../components/styles/Main'; 8 | 9 | const TreeViewScreen: React.FC = () => { 10 | const [state, setState] = useState(viewData); 11 | 12 | return ( 13 | 14 | 15 | 18 | setState((old) => { 19 | const ref = [...old]; 20 | 21 | ref[i].show = checked; 22 | 23 | return [...ref]; 24 | }) 25 | } 26 | /> 27 | 28 | 29 | ); 30 | }; 31 | 32 | export default TreeViewScreen; 33 | 34 | const TreeViewContainer = styled(MainWrapper)` 35 | min-height: 38vh; 36 | `; 37 | -------------------------------------------------------------------------------- /src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /src/static/enums.ts: -------------------------------------------------------------------------------- 1 | export enum ERoutes { 2 | Home = '/', 3 | Me = '/me', 4 | Form = '/form-hook-pattern', 5 | ClickOutside = '/click-outside', 6 | TreeView = '/tree-view', 7 | ReactStepMachine = '/react-step-machine', 8 | } 9 | 10 | export enum EComponentNames { 11 | ReactStepMachine = 'react-step-machine', 12 | ClickOutside = 'click-outside', 13 | Form = 'form-hook-pattern', 14 | TreeView = 'tree-view', 15 | } 16 | 17 | export enum ECodeLinks { 18 | ReactStepMachine = 'react-step-machine', 19 | Form = 'components/Form/hook/useForm.tsx', 20 | ClickOutside = 'components/ClickOutsideWrapper', 21 | TreeView = 'components/TreeView', 22 | } 23 | -------------------------------------------------------------------------------- /src/static/me.ts: -------------------------------------------------------------------------------- 1 | import { joinRepo } from '../utils/misc'; 2 | import { ECodeLinks, EComponentNames, ERoutes } from './enums'; 3 | 4 | export const GITHUB = 'https://github.com/adiathasan'; 5 | 6 | export const REPO = 'https://github.com/adiathasan/react-concepts/blob/master/src'; 7 | 8 | export const patternLinks = [ 9 | { 10 | title: EComponentNames.Form, 11 | code: joinRepo(REPO, ECodeLinks.Form), 12 | link: ERoutes.Form, 13 | isExternal: false, 14 | enabled: true, 15 | }, 16 | { 17 | title: EComponentNames.ClickOutside, 18 | code: joinRepo(REPO, ECodeLinks.ClickOutside), 19 | link: ERoutes.ClickOutside, 20 | isExternal: false, 21 | enabled: true, 22 | }, 23 | { 24 | title: EComponentNames.TreeView, 25 | code: joinRepo(REPO, ECodeLinks.TreeView), 26 | link: ERoutes.TreeView, 27 | isExternal: false, 28 | enabled: true, 29 | }, 30 | { 31 | title: EComponentNames.ReactStepMachine, 32 | code: joinRepo(GITHUB, ECodeLinks.ReactStepMachine), 33 | link: ERoutes.ReactStepMachine, 34 | isExternal: false, 35 | enabled: true, 36 | }, 37 | { 38 | title: 'more-pattern (coming...)', 39 | code: GITHUB, 40 | link: '', 41 | isExternal: true, 42 | enabled: true, 43 | }, 44 | ]; 45 | -------------------------------------------------------------------------------- /src/static/url.ts: -------------------------------------------------------------------------------- 1 | export enum EUrl { 2 | Attributes = 'attributes', 3 | } 4 | -------------------------------------------------------------------------------- /src/store/StoreProvider.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const StoreContext = React.createContext(null); 4 | const StoreDispatchContext = React.createContext(null); 5 | 6 | interface StoreProviderProps { 7 | init: S; 8 | reducer: (state: S, action: A) => S; 9 | children?: React.ReactNode; 10 | } 11 | 12 | const StoreProvider = (props: StoreProviderProps) => { 13 | const { children, init, reducer } = props; 14 | 15 | const [state, dispatch] = React.useReducer(reducer, init); 16 | 17 | return ( 18 | 19 | 20 | {children} 21 | 22 | 23 | ); 24 | }; 25 | 26 | export default StoreProvider; 27 | 28 | export const useStore = () => { 29 | const store = React.useContext(StoreContext); 30 | 31 | if (!store) { 32 | throw new Error('StoreProvider not found'); 33 | } 34 | 35 | return store; 36 | }; 37 | 38 | export const useStoreDispatch = () => { 39 | const dispatch = React.useContext>(StoreDispatchContext); 40 | 41 | if (!dispatch) { 42 | throw new Error('StoreDispatchProvider not found'); 43 | } 44 | 45 | return dispatch; 46 | }; 47 | -------------------------------------------------------------------------------- /src/store/makeStore.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | interface StoreProviderProps { 4 | children?: React.ReactNode; 5 | } 6 | 7 | const makeStore = (init: S, reducer: (state: S, action: A) => S) => { 8 | const StoreContext = React.createContext(null); 9 | const StoreDispatchContext = React.createContext(null); 10 | 11 | const StoreProvider = (props: StoreProviderProps) => { 12 | const { children } = props; 13 | 14 | const [state, dispatch] = React.useReducer(reducer, init); 15 | 16 | return ( 17 | 18 | {children} 19 | 20 | ); 21 | }; 22 | 23 | const useStore = () => { 24 | const store = React.useContext(StoreContext); 25 | 26 | if (!store) { 27 | throw new Error('makestore->fn: useStore must be used within a Provider'); 28 | } 29 | 30 | return store; 31 | }; 32 | 33 | const useDispatch = () => { 34 | const dispatch = React.useContext>(StoreDispatchContext); 35 | 36 | if (!dispatch) { 37 | throw new Error('makestore->fn: useDispatch must be used within a Provider'); 38 | } 39 | 40 | return dispatch; 41 | }; 42 | 43 | return [StoreProvider, useStore, useDispatch] as const; 44 | }; 45 | 46 | export default makeStore; 47 | -------------------------------------------------------------------------------- /src/utils/_.ts: -------------------------------------------------------------------------------- 1 | const isArrayEmpty = (arr: T[]) => arr.length === 0; 2 | 3 | const isFirstItem = (_arr: T[], i: number) => i === 0; 4 | 5 | const isLastItem = (arr: T[], i: number) => arr.length - 1 === i; 6 | 7 | export const _ = { isArrayEmpty, isFirstItem, isLastItem }; 8 | -------------------------------------------------------------------------------- /src/utils/misc.ts: -------------------------------------------------------------------------------- 1 | export const joinRepo = (repo: string, file: string) => `${repo}/${file}`; 2 | 3 | export const getUrlParam = (location: any, name: string) => 4 | new URLSearchParams(location.search).get(name); 5 | 6 | export const callAll = (...fns: Function[]) => (...values: any) => fns.map(fn => fn?.(...values)) -------------------------------------------------------------------------------- /talk.md: -------------------------------------------------------------------------------- 1 | REACT -> 2 | 3 | Provides-> 4 | 5 | 0. 6 | 1. SPA -> . 7 | 2. Why FC replaces CC. 8 | 3. HOOKS are the future + awesome. 9 | 4. Why I love hooks. 10 | 5. Context API Can Replace Redux + React query. 11 | 6. Bad Idea to use CDN libraries. As react has it's own Virtual dom. Also we can't bundle the code and tree shake if we do so. 12 | 7. Modular CSS for better file structure. 13 | 8. Folder Structure [x] . 14 | 9. Why I never going back to JS -> TS is super. 15 | 16 | NEXT -> React With Superpower 17 | -> 12 18 | Provides -> 19 | 20 | 0. 21 | 1. SWC build-in. 22 | 2. Page base routing. 23 | 3. CSR -> Client -> React. 24 | 4. SSR -> Server -> React (hydrate). 25 | 5. SSG -> Static -> \*\*\* . 26 | 6. Auto Performance Optimization. 27 | 7. Ts support out of the box. 28 | 8. Server-less approach [x]. 29 | 9. JAM-stack. 30 | 10. Easy deployment with Vercel. 31 | 32 | github repo -> https://github.com/adiathasan/react-concepts 33 | 34 | live demo -> https://adiathasan.vercel.app 35 | 36 | Theme -> VS.Code dark 37 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | --------------------------------------------------------------------------------