├── src ├── vite-env.d.ts ├── components │ ├── accordion-2 │ │ ├── 1.jpg │ │ ├── 2.jpg │ │ ├── 3.jpg │ │ ├── 4.jpg │ │ ├── 5.jpg │ │ ├── styles.css │ │ └── Accordion.tsx │ └── accordion-1 │ │ ├── image.jpeg │ │ ├── styles.css │ │ └── Accordion.tsx ├── App.tsx ├── index.css ├── main.tsx ├── App.css └── assets │ └── react.svg ├── vite.config.ts ├── tsconfig.node.json ├── .gitignore ├── .eslintrc.cjs ├── index.html ├── tsconfig.json ├── package.json └── public └── vite.svg /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/components/accordion-2/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontend-joe/react-accordions/HEAD/src/components/accordion-2/1.jpg -------------------------------------------------------------------------------- /src/components/accordion-2/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontend-joe/react-accordions/HEAD/src/components/accordion-2/2.jpg -------------------------------------------------------------------------------- /src/components/accordion-2/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontend-joe/react-accordions/HEAD/src/components/accordion-2/3.jpg -------------------------------------------------------------------------------- /src/components/accordion-2/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontend-joe/react-accordions/HEAD/src/components/accordion-2/4.jpg -------------------------------------------------------------------------------- /src/components/accordion-2/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontend-joe/react-accordions/HEAD/src/components/accordion-2/5.jpg -------------------------------------------------------------------------------- /src/components/accordion-1/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontend-joe/react-accordions/HEAD/src/components/accordion-1/image.jpeg -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Accordion } from "./components/accordion-2/Accordion"; 2 | 3 | function App() { 4 | return ; 5 | } 6 | 7 | export default App; 8 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | display: flex; 4 | justify-content: center; 5 | align-items: center; 6 | height: 100vh; 7 | color: #3e3f5d; 8 | font-family: "Euclid Circular A", "Poppins"; 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.tsx' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { browser: true, es2020: true }, 3 | extends: [ 4 | 'eslint:recommended', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:react-hooks/recommended', 7 | ], 8 | parser: '@typescript-eslint/parser', 9 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 10 | plugins: ['react-refresh'], 11 | rules: { 12 | 'react-refresh/only-export-components': 'warn', 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 5 | "module": "ESNext", 6 | "skipLibCheck": true, 7 | 8 | /* Bundler mode */ 9 | "moduleResolution": "bundler", 10 | "allowImportingTsExtensions": true, 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "noEmit": true, 14 | "jsx": "react-jsx", 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true 21 | }, 22 | "include": ["src"], 23 | "references": [{ "path": "./tsconfig.node.json" }] 24 | } 25 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-accordions", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "react": "^18.2.0", 14 | "react-dom": "^18.2.0" 15 | }, 16 | "devDependencies": { 17 | "@types/react": "^18.0.28", 18 | "@types/react-dom": "^18.0.11", 19 | "@typescript-eslint/eslint-plugin": "^5.57.1", 20 | "@typescript-eslint/parser": "^5.57.1", 21 | "@vitejs/plugin-react": "^4.0.0", 22 | "eslint": "^8.38.0", 23 | "eslint-plugin-react-hooks": "^4.6.0", 24 | "eslint-plugin-react-refresh": "^0.3.4", 25 | "typescript": "^5.0.2", 26 | "vite": "^4.3.2" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/accordion-1/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | display: grid; 4 | place-items: center; 5 | height: 100vh; 6 | color: #3e3f5d; 7 | background: #eff0ff; 8 | font-family: "Euclid Circular A", "Poppins"; 9 | } 10 | 11 | article { 12 | background: #ffffff; 13 | width: 400px; 14 | padding: 20px; 15 | border-radius: 10px; 16 | } 17 | 18 | article header { 19 | display: flex; 20 | align-items: center; 21 | justify-content: space-between; 22 | height: 48px; 23 | padding: 0 10px 0 20px; 24 | border-radius: 6px; 25 | cursor: pointer; 26 | background: #494964; 27 | margin-bottom: 10px; 28 | color: #f8f8f8; 29 | } 30 | 31 | article header.active { 32 | background: #6366f1; 33 | } 34 | 35 | article header.active span { 36 | rotate: -180deg; 37 | } 38 | 39 | article h2 { 40 | margin: 0; 41 | font-size: 16px; 42 | font-weight: 500; 43 | } 44 | 45 | article p { 46 | padding: 0 20px 10px; 47 | line-height: 1.7; 48 | font-size: 14px; 49 | } 50 | 51 | article span { 52 | transition: 0.3s; 53 | } 54 | 55 | .collapse { 56 | position: relative; 57 | height: 0; 58 | overflow: hidden; 59 | transition: height 0.5s ease; 60 | } 61 | 62 | .collapse.show { 63 | height: auto; 64 | } 65 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/accordion-2/styles.css: -------------------------------------------------------------------------------- 1 | section { 2 | display: flex; 3 | gap: 10px; 4 | cursor: pointer; 5 | } 6 | 7 | article { 8 | position: relative; 9 | overflow: hidden; 10 | background: #ffffff; 11 | width: 64px; 12 | height: 500px; 13 | border-radius: 36px; 14 | display: flex; 15 | align-items: flex-end; 16 | opacity: 0.9; 17 | transition: 0.5s; 18 | } 19 | 20 | h2, 21 | p { 22 | margin: 0; 23 | } 24 | 25 | article h2 { 26 | font-size: 24px; 27 | font-weight: 400; 28 | color: rgb(255 255 255 / 96%); 29 | } 30 | 31 | article p { 32 | color: rgb(255 255 255 / 66%); 33 | } 34 | 35 | article.active { 36 | width: 400px; 37 | opacity: 1; 38 | } 39 | 40 | article .material-symbols-outlined { 41 | display: grid; 42 | place-items: center; 43 | width: 50px; 44 | height: 50px; 45 | background: rgb(255 255 255 / 80%); 46 | border-radius: 50%; 47 | font-size: 28px; 48 | } 49 | 50 | article .content { 51 | position: absolute; 52 | bottom: 0; 53 | left: 0; 54 | width: 400px; 55 | z-index: 1; 56 | opacity: 0; 57 | visibility: hidden; 58 | padding: 100px 0 20px 20px; 59 | display: flex; 60 | align-items: center; 61 | gap: 14px; 62 | background: linear-gradient(to bottom, rgb(0 0 0 / 0%), rgb(0 0 0 / 80%)); 63 | transition: 0.25s; 64 | } 65 | 66 | article.active .content { 67 | opacity: 1; 68 | visibility: visible; 69 | } 70 | 71 | article img { 72 | position: absolute; 73 | z-index: 0; 74 | top: 50%; 75 | left: 50%; 76 | translate: -50% -50%; 77 | height: 150%; 78 | filter: grayscale(0.5); 79 | } 80 | -------------------------------------------------------------------------------- /src/components/accordion-2/Accordion.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import image1 from "./1.jpg"; 3 | import image2 from "./2.jpg"; 4 | import image3 from "./3.jpg"; 5 | import image4 from "./4.jpg"; 6 | import image5 from "./5.jpg"; 7 | import "./styles.css"; 8 | 9 | const cards = [ 10 | { 11 | header: "Canada", 12 | image: image2, 13 | text: `Image description here`, 14 | }, 15 | { 16 | header: "Bali", 17 | image: image1, 18 | text: `Image description here`, 19 | }, 20 | { 21 | header: "Spain", 22 | image: image3, 23 | text: `Image description here`, 24 | }, 25 | { 26 | header: "Indonesia", 27 | image: image4, 28 | text: `Image description here`, 29 | }, 30 | { 31 | header: "South Africa", 32 | image: image5, 33 | text: `Image description here`, 34 | }, 35 | ]; 36 | 37 | export const Accordion = () => { 38 | const [active, setActive] = useState(0); 39 | 40 | const handleToggle = (index: number) => setActive(index); 41 | 42 | return ( 43 |
44 | {cards.map((card, index) => { 45 | const isActive = active === index ? "active" : ""; 46 | return ( 47 |
handleToggle(index)} 51 | > 52 | 53 |
54 | photo_camera 55 |
56 |

{card.header}

57 |

{card.text}

58 |
59 |
60 |
61 | ); 62 | })} 63 |
64 | ); 65 | }; 66 | -------------------------------------------------------------------------------- /src/components/accordion-1/Accordion.tsx: -------------------------------------------------------------------------------- 1 | import { useRef, useState } from "react"; 2 | import "./styles.css"; 3 | 4 | const faqs = [ 5 | { 6 | id: 1, 7 | header: "What is Lorem Ipsum?", 8 | text: `Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.`, 9 | }, 10 | { 11 | id: 2, 12 | header: "Where does it come from?", 13 | text: `Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.`, 14 | }, 15 | { 16 | id: 3, 17 | header: "Why do we use it?", 18 | text: `Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.`, 19 | }, 20 | { 21 | id: 4, 22 | header: "Where can I get some?", 23 | text: `Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.`, 24 | }, 25 | ]; 26 | 27 | const AccordionItem = (props: any) => { 28 | const contentEl = useRef(null); 29 | const { handleToggle, active, faq } = props; 30 | const { header, id, text } = faq; 31 | 32 | return ( 33 |
34 |
handleToggle(id)} 37 | > 38 |

{header}

39 | expand_more 40 |
41 |
50 |

{text}

51 |
52 |
53 | ); 54 | }; 55 | 56 | export const Accordion = () => { 57 | const [active, setActive] = useState(null); 58 | 59 | const handleToggle = (index: any) => { 60 | if (active === index) { 61 | setActive(null); 62 | } else { 63 | setActive(index); 64 | } 65 | }; 66 | 67 | return ( 68 |
69 | {faqs.map((faq, index) => { 70 | return ( 71 | 77 | ); 78 | })} 79 |
80 | ); 81 | }; 82 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------