├── src ├── components │ ├── TreeView │ │ ├── index.jsx │ │ ├── TreeView.jsx │ │ └── TreeItem.jsx │ ├── Tooltip │ │ ├── index.jsx │ │ └── utils.jsx │ ├── CommandPalette │ │ ├── index.jsx │ │ └── utils │ │ │ ├── isVisibleXY.jsx │ │ │ └── useKeyboardShortcut.jsx │ ├── ScrollAnimations │ │ ├── index.jsx │ │ ├── ScrollAnimations.jsx │ │ └── useIntersectionObserver.jsx │ ├── Toast │ │ ├── index.jsx │ │ ├── useToast.jsx │ │ ├── ToastList.jsx │ │ └── Toast.jsx │ ├── DatePicker │ │ ├── index.jsx │ │ ├── DatePickerModal.jsx │ │ └── MonthYearSelection.jsx │ ├── Loader.jsx │ ├── ContentEditable.jsx │ ├── Breadcrumb.jsx │ ├── Chip.jsx │ ├── ScrollIndicator.jsx │ ├── Ripple.jsx │ ├── ScrollToTopBtn.jsx │ ├── Rating.jsx │ ├── Ribbon.jsx │ ├── Masonry.jsx │ ├── SidebarWithSubmenu │ │ ├── SidebarWithSubmenu.jsx │ │ ├── Submenu.jsx │ │ └── data.jsx │ ├── CircularMenu.jsx │ ├── Typewriter.jsx │ ├── Timeline.jsx │ ├── SpeedDial.jsx │ ├── CircularRotatingList.jsx │ ├── StatsSection.jsx │ ├── Progress.jsx │ ├── RadioBtn.jsx │ ├── Faq.jsx │ ├── Sidebar.jsx │ ├── Resizable.jsx │ ├── Drawer.jsx │ ├── Navbar.jsx │ ├── SortableList.jsx │ ├── Autocomplete.jsx │ ├── SelectMenu.jsx │ ├── Switch.jsx │ ├── Footer.jsx │ ├── Checkbox.jsx │ ├── Accordion.jsx │ ├── MegaMenu.jsx │ ├── Carousel.jsx │ ├── MultiselectMenu.jsx │ ├── Popover.jsx │ ├── AnimatedMultilevelDropdown.jsx │ ├── Alert.jsx │ ├── Form.jsx │ ├── Testimonials │ │ └── Testimonials1.jsx │ ├── ImageUploader.jsx │ ├── Modal.jsx │ ├── Dropdown.jsx │ ├── Pricing │ │ └── PricingComparisonTable.jsx │ ├── Tabs.jsx │ ├── Popconfirm.jsx │ └── Table.jsx ├── index.css ├── main.jsx └── pages │ ├── FormDemo.jsx │ ├── MegaMenuDemo.jsx │ ├── MultistepFormDemo.jsx │ ├── StatsSectionDemo.jsx │ ├── ScrollAnimationsDemo.jsx │ ├── LoaderDemo.jsx │ ├── FooterDemo.jsx │ ├── ImageUploaderDemo.jsx │ ├── AnimatedFormDemo.jsx │ ├── SortableListDemo.jsx │ ├── AccordionDemo.jsx │ ├── TypewriterDemo.jsx │ ├── ScrollspyDemo.jsx │ ├── ChipsDemo.jsx │ ├── AnimatedMultilevelDropdownDemo.jsx │ ├── ContentEditableDemo.jsx │ ├── SidebarWithSubmenuDemo.jsx │ ├── NavbarDemo.jsx │ ├── RippleDemo.jsx │ ├── PricingDemo.jsx │ ├── RibbonDemo.jsx │ ├── SidebarDemo.jsx │ ├── SwitchDemo.jsx │ ├── TabsDemo.jsx │ ├── TimelineDemo.jsx │ ├── ScrollToTopDemo.jsx │ ├── ResizableDemo.jsx │ ├── BreadcrumbDemo.jsx │ ├── RatingDemo.jsx │ ├── TooltipDemo.jsx │ ├── PopconfirmDemo.jsx │ ├── CommandPaletteDemo.jsx │ ├── RadioBtnDemo.jsx │ ├── AutocompleteDemo.jsx │ ├── CarouselDemo.jsx │ ├── TableDemo.jsx │ ├── DatePickerDemo.jsx │ ├── ProgressDemo.jsx │ ├── FaqDemo.jsx │ ├── TestimonialsDemo.jsx │ ├── SliderDemo.jsx │ ├── SpeedDialDemo.jsx │ ├── MultiselectMenuDemo.jsx │ ├── ModalDemo.jsx │ ├── ListHoverDemo.jsx │ ├── SelectMenuDemo.jsx │ ├── CircularMenuDemo.jsx │ ├── TreeViewDemo.jsx │ ├── CircularRotatingListDemo.jsx │ ├── MasonryDemo.jsx │ ├── AlertsDemo.jsx │ ├── NotificationBadgeDemo.jsx │ ├── DrawerDemo.jsx │ ├── CheckboxDemo.jsx │ ├── PopoverDemo.jsx │ ├── ToastDemo.jsx │ └── Home.jsx ├── postcss.config.js ├── jsconfig.json ├── vite.config.js ├── .gitignore ├── package.json ├── .eslintrc.json ├── index.html ├── .github └── workflows │ └── main.yml ├── tailwind.config.js └── README.md /src/components/TreeView/index.jsx: -------------------------------------------------------------------------------- 1 | import TreeView from "./TreeView"; 2 | export default TreeView; -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /src/components/Tooltip/index.jsx: -------------------------------------------------------------------------------- 1 | import TooltipProvider from "./TooltipProvider"; 2 | export default TooltipProvider; 3 | -------------------------------------------------------------------------------- /src/components/CommandPalette/index.jsx: -------------------------------------------------------------------------------- 1 | import CommandPalette from "./CommandPalette"; 2 | export default CommandPalette; 3 | -------------------------------------------------------------------------------- /src/components/ScrollAnimations/index.jsx: -------------------------------------------------------------------------------- 1 | import ScrollAnimations from "./ScrollAnimations"; 2 | export default ScrollAnimations; 3 | -------------------------------------------------------------------------------- /src/components/Toast/index.jsx: -------------------------------------------------------------------------------- 1 | import ToastList from "./ToastList"; 2 | import useToast from "./useToast"; 3 | export { ToastList, useToast }; -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body 6 | { 7 | font-family: "Roboto", sans-serif; 8 | } -------------------------------------------------------------------------------- /src/components/DatePicker/index.jsx: -------------------------------------------------------------------------------- 1 | import DatePickerModal from "./DatePickerModal"; 2 | import DatePicker from "./DatePicker"; 3 | export default DatePickerModal; 4 | export { DatePicker }; 5 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2016", 5 | "jsx": "preserve" 6 | }, 7 | "exclude": ["node_modules", "**/node_modules/*"] 8 | } 9 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | base: "/React-tailwind-components/", 7 | plugins: [react()], 8 | }); 9 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import './index.css' 4 | import App from './App' 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ) 12 | -------------------------------------------------------------------------------- /src/pages/FormDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Form from '../components/Form' 3 | 4 | const FormDemo = () => { 5 | return ( 6 | <> 7 |

Forms

8 |
9 | 10 | ) 11 | } 12 | 13 | export default FormDemo -------------------------------------------------------------------------------- /src/pages/MegaMenuDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import MegaMenu from '../components/MegaMenu' 3 | 4 | const MegaMenuDemo = () => { 5 | return ( 6 | <> 7 |
8 | 9 |
10 | 11 | ) 12 | } 13 | 14 | export default MegaMenuDemo -------------------------------------------------------------------------------- /src/pages/MultistepFormDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import MultistepForm from '../components/MultistepForm' 3 | 4 | const MultistepFormDemo = () => { 5 | return ( 6 | <> 7 |

Multistep Form

8 | 9 | 10 | ) 11 | } 12 | 13 | export default MultistepFormDemo -------------------------------------------------------------------------------- /src/pages/StatsSectionDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import StatsSection from "../components/StatsSection"; 3 | 4 | const StatsSectionDemo = () => { 5 | return ( 6 |
7 |
Stats Sections
8 | 9 |
10 | ); 11 | }; 12 | 13 | export default StatsSectionDemo; 14 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /src/pages/ScrollAnimationsDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ScrollAnimations from "../components/ScrollAnimations"; 3 | 4 | const ScrollAnimationsDemo = () => { 5 | return ( 6 |
7 |

Scroll Animations

8 | 9 |
10 | ); 11 | }; 12 | 13 | export default ScrollAnimationsDemo; 14 | -------------------------------------------------------------------------------- /src/pages/LoaderDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Loader from "../components/Loader"; 3 | 4 | const LoaderDemo = () => { 5 | return ( 6 |
7 |

Loader

8 |
9 | 10 |
11 |
12 | ); 13 | }; 14 | 15 | export default LoaderDemo; 16 | -------------------------------------------------------------------------------- /src/components/Loader.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Loader = ({ className = "" }) => { 4 | return ( 5 | <> 6 |
7 |
8 |
9 | 10 | ); 11 | }; 12 | 13 | export default Loader; 14 | -------------------------------------------------------------------------------- /src/pages/FooterDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Footer from "../components/Footer"; 3 | 4 | const FooterDemo = () => { 5 | return ( 6 |
7 |
Footer
8 |
9 |
10 |
11 |
12 | ); 13 | }; 14 | 15 | export default FooterDemo; 16 | -------------------------------------------------------------------------------- /src/pages/ImageUploaderDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ImageUploader from "../components/ImageUploader"; 3 | 4 | const ImageUploaderDemo = () => { 5 | return ( 6 |
7 |
8 |

Image Uploader

9 | 10 |
11 |
12 | ); 13 | }; 14 | 15 | export default ImageUploaderDemo; 16 | -------------------------------------------------------------------------------- /src/pages/AnimatedFormDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import AnimatedForm from "../components/AnimatedForm"; 3 | 4 | const AnimatedFormDemo = () => { 5 | return ( 6 |
7 |

Animated Form

8 |
9 | 10 |
11 |
12 | ); 13 | }; 14 | 15 | export default AnimatedFormDemo; 16 | -------------------------------------------------------------------------------- /src/pages/SortableListDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import SortableList from '../components/SortableList' 3 | 4 | const SortableListDemo = () => { 5 | return ( 6 | <> 7 |

Sortable List

8 |
9 | 10 |
11 | 12 | ) 13 | } 14 | 15 | export default SortableListDemo -------------------------------------------------------------------------------- /src/pages/AccordionDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Accordion from '../components/Accordion' 3 | 4 | const AccordionDemo = () => { 5 | return ( 6 | <> 7 |

Accordion

8 | 9 |
This page is used to show the demo of Accordion.
10 |
Click on any accordion item to see the effect.
11 | 12 | ) 13 | } 14 | 15 | export default AccordionDemo -------------------------------------------------------------------------------- /src/pages/TypewriterDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Typewriter from '../components/Typewriter' 3 | 4 | const TypewriterDemo = () => { 5 | return ( 6 | <> 7 |

Typewriter

8 |
9 | 10 |
11 | 12 | ) 13 | } 14 | 15 | export default TypewriterDemo -------------------------------------------------------------------------------- /src/pages/ScrollspyDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Scrollspy from '../components/Scrollspy' 3 | 4 | const ScrollspyDemo = () => { 5 | return ( 6 | <> 7 |
8 |

Scrollspy

9 |

This scrollspy is responsive as well. So please try resizing the page as well to try this out.

10 |
11 | 12 | 13 | ) 14 | } 15 | 16 | export default ScrollspyDemo -------------------------------------------------------------------------------- /src/pages/ChipsDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Chip from '../components/Chip' 3 | 4 | const ChipsDemo = () => { 5 | return ( 6 | <> 7 |

Chips

8 |
9 | 10 | 11 | 12 |
13 | 14 | ) 15 | } 16 | 17 | export default ChipsDemo -------------------------------------------------------------------------------- /src/pages/AnimatedMultilevelDropdownDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import AnimatedMultilevelDropdown from "../components/AnimatedMultilevelDropdown"; 3 | 4 | const AnimatedMultilevelDropdownDemo = () => { 5 | return ( 6 |
7 |

Animated Multilevel Dropdown

8 |
9 | 10 |
11 |
12 | ); 13 | }; 14 | 15 | export default AnimatedMultilevelDropdownDemo; 16 | -------------------------------------------------------------------------------- /src/pages/ContentEditableDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ContentEditable from "../components/ContentEditable"; 3 | 4 | const ContentEditableDemo = () => { 5 | return ( 6 |
7 |
Content Editable
8 | 9 | 13 |
14 | ); 15 | }; 16 | 17 | export default ContentEditableDemo; 18 | -------------------------------------------------------------------------------- /src/pages/SidebarWithSubmenuDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import SidebarWithSubmenu from '../components/SidebarWithSubmenu/SidebarWithSubmenu' 3 | 4 | const SidebarWithSubmenuDemo = () => { 5 | return ( 6 | <> 7 | 8 |
9 |

Sidebar with submenus

10 |
In this sidebar, Nesting is also possible
11 |
12 | 13 | ) 14 | } 15 | 16 | export default SidebarWithSubmenuDemo -------------------------------------------------------------------------------- /src/pages/NavbarDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Navbar from '../components/Navbar' 3 | 4 | const NavbarDemo = () => { 5 | return ( 6 |
7 | 8 |
9 |

This page is made to show the demo of Responsive Navbar Component.

10 |

Try resizing the page to see the responsive behaviour of Navbar.

11 |

The best part is that everything is done in jsx using tailwind classes.

12 |
13 |
14 | ) 15 | } 16 | 17 | export default NavbarDemo -------------------------------------------------------------------------------- /src/pages/RippleDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Ripple from '../components/Ripple' 3 | 4 | const RippleDemo = () => { 5 | return ( 6 | <> 7 |

Ripple

8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | ) 19 | } 20 | 21 | export default RippleDemo -------------------------------------------------------------------------------- /src/pages/PricingDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Pricing from "../components/Pricing/Pricing"; 3 | import PricingComparisonTable from "../components/Pricing/PricingComparisonTable"; 4 | 5 | const PricingDemo = () => { 6 | return ( 7 |
8 |
Pricing
9 | 10 |
11 |
Pricing Comparison Table
12 | 13 |
14 |
15 | ); 16 | }; 17 | 18 | export default PricingDemo; 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-tailwind-components", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "preview": "vite preview" 9 | }, 10 | "dependencies": { 11 | "eslint": "^8.35.0", 12 | "eslint-config-react-app": "^7.0.1", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-router-dom": "^6.2.2" 16 | }, 17 | "devDependencies": { 18 | "@vitejs/plugin-react": "^1.0.7", 19 | "autoprefixer": "^10.4.2", 20 | "postcss": "^8.4.8", 21 | "tailwindcss": "^3.2.7", 22 | "vite": "^2.8.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/pages/RibbonDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Ribbon from "../components/Ribbon"; 3 | 4 | const RibbonDemo = () => { 5 | return ( 6 |
7 |

Ribbon

8 |
9 | 10 |
11 | 12 |
13 | 14 |
15 |
16 | ); 17 | }; 18 | 19 | export default RibbonDemo; 20 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "node": true 6 | }, 7 | "extends": [ 8 | "react-app", 9 | "eslint:recommended", 10 | "plugin:react/recommended" 11 | ], 12 | "parserOptions": { 13 | "ecmaFeatures": { 14 | "jsx": true 15 | }, 16 | "ecmaVersion": "latest", 17 | "sourceType": "module" 18 | }, 19 | "plugins": [ 20 | "react" 21 | ], 22 | "rules": { 23 | "react/prop-types": 0, 24 | "react/no-unescaped-entities": 0, 25 | "react/no-unknown-property":0, 26 | "no-constant-condition": 0, 27 | "no-unused-vars": "warn" 28 | } 29 | } -------------------------------------------------------------------------------- /src/pages/SidebarDemo.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react' 2 | import Sidebar from '../components/Sidebar' 3 | 4 | const SidebarDemo = () => { 5 | return ( 6 | <> 7 |
8 | 9 |
10 |

This page is made to show the demo of Responsive Sidebar Component.

11 |

Try resizing the page to see the responsive behaviour of Sidebar.

12 |

The best part is that everything is done in jsx using tailwind classes.

13 |
14 |
15 | 16 | 17 | ) 18 | } 19 | 20 | export default SidebarDemo -------------------------------------------------------------------------------- /src/pages/SwitchDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Switch from "../components/Switch"; 3 | 4 | const SwitchDemo = () => { 5 | return ( 6 |
7 |

Switch

8 | 9 |
10 | 11 | 12 | 13 | 14 | 20 |
21 |
22 | ); 23 | }; 24 | 25 | export default SwitchDemo; 26 | -------------------------------------------------------------------------------- /src/pages/TabsDemo.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import TabBar from "../components/Tabs"; 3 | 4 | const tabsData = [ 5 | { tabName: "tab1", tabTitle: "Tab 1" }, 6 | { tabName: "longwordtab2", tabTitle: "Long word Tab 2" }, 7 | { tabName: "tab3", tabTitle: "Tb 3" }, 8 | ]; 9 | const TabsDemo = () => { 10 | const [activeTabName, setActiveTabName] = useState("tab1"); 11 | return ( 12 | <> 13 |

Tabs

14 |
15 | setActiveTabName(t)} /> 16 |
17 | 18 | ); 19 | }; 20 | 21 | export default TabsDemo; 22 | -------------------------------------------------------------------------------- /src/pages/TimelineDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Timeline from '../components/Timeline' 3 | 4 | const TimelineDemo = () => { 5 | return ( 6 | <> 7 |

Timeline

8 | 9 |
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Ipsam velit cum ad nulla odit totam labore quo sit aut voluptas.
10 |
Lorem, ipsum dolor.
11 |
Lorem ipsum dolor sit amet consectetur adipisicing elit. Asperiores, quae?
12 |
13 | 14 | ) 15 | } 16 | 17 | export default TimelineDemo -------------------------------------------------------------------------------- /src/pages/ScrollToTopDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ScrollToTopBtn from '../components/ScrollToTopBtn' 3 | 4 | const ScrollToTopDemo = () => { 5 | return ( 6 | <> 7 | 8 |
9 |

Scroll Back To top Button Demo

10 |

11 | Start scrolling the page and a strong "Back to top" button will appear 12 | in the bottom right corner of the screen. 13 |

14 |

Click this button and you will be taken to the top of the page.

15 |
16 | 17 | ) 18 | } 19 | 20 | export default ScrollToTopDemo -------------------------------------------------------------------------------- /src/pages/ResizableDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Resizable from "../components/Resizable"; 3 | 4 | const ResizableDemo = () => { 5 | return ( 6 |
7 |
Resizable Component
8 | 9 |
10 | Lorem, ipsum dolor sit amet consectetur adipisicing elit. Enim sit, dolorem eligendi culpa numquam 11 | iusto excepturi veniam dolores provident fugit. 12 |
13 |
14 |
15 | ); 16 | }; 17 | 18 | export default ResizableDemo; 19 | -------------------------------------------------------------------------------- /src/components/ContentEditable.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ContentEditable = ({ value = "", onChange = () => {}, onFocus, placeholder = "", className = "" }) => { 4 | return ( 5 |
onFocus?.()} 10 | dangerouslySetInnerHTML={{ __html: value }} 11 | onBlur={onChange} 12 | className={`whitespace-pre border-b border-b-gray-400 bg-gray-50 px-1 py-1 outline-none transition before:cursor-text empty:before:font-normal empty:before:text-gray-500 empty:before:content-[attr(placeholder)] focus:border-b-2 focus:border-b-violet-500 ${className} `} 13 | /> 14 | ); 15 | }; 16 | 17 | export default ContentEditable; 18 | -------------------------------------------------------------------------------- /src/components/Breadcrumb.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Separator = ({ separator }) => { 4 | return ( 5 |
6 | {separator ? {separator} : } 7 |
8 | ); 9 | }; 10 | 11 | const Breadcrumb = ({ elements, separator }) => { 12 | return ( 13 |
14 | {elements.map((elem, idx) => ( 15 |
16 | {idx !== 0 && } 17 | {elem} 18 |
19 | ))} 20 |
21 | ); 22 | }; 23 | 24 | export default Breadcrumb; 25 | -------------------------------------------------------------------------------- /src/pages/BreadcrumbDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Breadcrumb from "../components/Breadcrumb"; 3 | 4 | const elements = [ 5 |
6 | 7 |
, 8 |
Projects
, 9 |
10 | Project 1 11 |
, 12 | ]; 13 | 14 | const BreadcrumbDemo = () => { 15 | return ( 16 |
17 |
Breadcrumbs
18 |
19 | 20 | 21 | 22 |
23 |
24 | ); 25 | }; 26 | 27 | export default BreadcrumbDemo; 28 | -------------------------------------------------------------------------------- /src/components/Chip.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Chip = ({ text = "Dummy text", addCloseBtn = false }) => { 4 | return ( 5 | <> 6 |
10 | {text} 11 | {addCloseBtn && ( 12 | 15 | )} 16 |
17 | 18 | ) 19 | } 20 | 21 | export default Chip -------------------------------------------------------------------------------- /src/components/Toast/useToast.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | 3 | const useToast = () => { 4 | const [toastList, setToastList] = useState([]); 5 | 6 | const addToast = (toast) => { 7 | toast.id = Math.floor(Math.random() * 10000); 8 | toast.position = toast.position || "top-right"; 9 | toast.type = toast.type || "success"; 10 | toast.msg = toast.msg || toast.type; 11 | toast.autoCloseTimeout = toast.autoCloseTimeout || 5000; 12 | 13 | toast.position.startsWith("top") ? ( 14 | setToastList([toast, ...toastList]) 15 | ) : ( 16 | setToastList([...toastList, toast]) 17 | ) 18 | } 19 | 20 | const deleteToast = id => { 21 | setToastList(toastList => toastList.filter(toast => toast.id !== id)); 22 | } 23 | 24 | return [toastList, addToast, deleteToast]; 25 | } 26 | 27 | export default useToast -------------------------------------------------------------------------------- /src/pages/RatingDemo.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import Rating from '../components/Rating' 3 | 4 | const RatingDemo = () => { 5 | const [rating1, setRating1] = useState(0); 6 | const [rating2, setRating2] = useState(0); 7 | const [rating3, setRating3] = useState(0); 8 | const [rating4, setRating4] = useState(0); 9 | 10 | return ( 11 | <> 12 |

Rating

13 |
14 | 15 | 16 | 17 | 18 |
19 | 20 | ) 21 | } 22 | 23 | export default RatingDemo -------------------------------------------------------------------------------- /src/pages/TooltipDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import TooltipProvider from "../components/Tooltip"; 3 | 4 | const TooltipDemo = () => { 5 | return ( 6 | <> 7 | 8 |
9 |

Tooltip

10 |
11 | 17 |
18 |
19 |
20 | 21 | ); 22 | }; 23 | 24 | export default TooltipDemo; 25 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | React Tailwind Components 8 | 15 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/pages/PopconfirmDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Popconfirm from '../components/Popconfirm' 3 | 4 | const PopconfirmDemo = () => { 5 | 6 | const handleConfirm = () => { alert("You confirmed ok") } 7 | const handleCancel = () => { alert("You cancelled") } 8 | 9 | return ( 10 | <> 11 |

Pop confirm

12 | 13 |
14 | 15 | Delete 16 | 17 | 18 |

19 | 20 | 21 | Delete 22 | 23 |
24 | 25 | ) 26 | } 27 | 28 | export default PopconfirmDemo -------------------------------------------------------------------------------- /src/components/ScrollIndicator.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | 3 | const ScrollIndicator = () => { 4 | 5 | const [scrollTopPercent, setScrollTopPercent] = useState(0); 6 | 7 | const handleScroll = () => { 8 | const winScroll = document.documentElement.scrollTop; 9 | const height = document.documentElement.scrollHeight - document.documentElement.clientHeight; 10 | const scrolledPercent = (winScroll / height) * 100; 11 | setScrollTopPercent(scrolledPercent); 12 | } 13 | 14 | useEffect(() => { 15 | window.addEventListener("scroll", handleScroll); 16 | return () => window.removeEventListener("scroll", handleScroll); 17 | }, []); 18 | 19 | return ( 20 | <> 21 |
22 |
23 |
24 | 25 | ) 26 | } 27 | 28 | export default ScrollIndicator -------------------------------------------------------------------------------- /src/pages/CommandPaletteDemo.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import CommandPalette from "../components/CommandPalette"; 3 | 4 | const CommandPaletteDemo = () => { 5 | const commands = Array(20) 6 | .fill(0) 7 | .map((_, i) => ({ text: `Element ${i}` })); 8 | const [open, setOpen] = useState(false); 9 | 10 | return ( 11 |
12 |

Command Palette

13 | 19 |
20 | or use ctrl+shift+k 21 | setOpen(false)} closeOnSelect /> 22 |
23 | ); 24 | }; 25 | 26 | export default CommandPaletteDemo; 27 | -------------------------------------------------------------------------------- /src/components/TreeView/TreeView.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import TreeItem from './TreeItem' 3 | 4 | const TreeView = ({ data, 5 | defaultExpandedKeys = [], 6 | defaultSelectedKey = null, 7 | expandedKeys: expKeys, 8 | selectedKey: selKey, 9 | }) => { 10 | 11 | const [expandedKeys, setExpandedKeys] = useState(expKeys || defaultExpandedKeys); 12 | const [selectedKey, setSelectedKey] = useState(selKey || defaultSelectedKey); 13 | 14 | useEffect(() => { 15 | if (expKeys) setExpandedKeys(expKeys); 16 | }, [expKeys]); 17 | 18 | useEffect(() => { 19 | if (selKey) setSelectedKey(selKey); 20 | }, [selKey]); 21 | 22 | return ( 23 | <> 24 |
    25 | {data.map((item) => ( 26 | 27 | ))} 28 |
29 | 30 | ) 31 | } 32 | 33 | export default TreeView -------------------------------------------------------------------------------- /src/pages/RadioBtnDemo.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import RadioBtn from "../components/RadioBtn"; 3 | 4 | const RadioBtnDemo = () => { 5 | const [fruit, setFruit] = useState("apple"); 6 | const handleChange = (name, value) => { 7 | setFruit(value); 8 | }; 9 | 10 | return ( 11 |
12 |

Radio Button

13 |
14 |
15 | 22 |
23 | 24 |
25 | 32 |
33 |
34 |
35 | ); 36 | }; 37 | 38 | export default RadioBtnDemo; 39 | -------------------------------------------------------------------------------- /src/components/CommandPalette/utils/isVisibleXY.jsx: -------------------------------------------------------------------------------- 1 | const isVisibleXY = (element, minPercentX = 50, minPercentY = 50) => { 2 | if (!element) return false; 3 | const elRect = element.getBoundingClientRect(); 4 | if (elRect.bottom < 0) return false; 5 | if (elRect.top > document.documentElement.clientHeight) return false; 6 | 7 | let parent = element.parentNode; 8 | do { 9 | const parentRect = parent.getBoundingClientRect(); 10 | const visibleX = Math.min(elRect.right, parentRect.right) - Math.max(elRect.left, parentRect.left); 11 | const visibleY = Math.min(elRect.bottom, parentRect.bottom) - Math.max(elRect.top, parentRect.top); 12 | const visiblePercentageX = (visibleX / elRect.width) * 100; 13 | const visiblePercentageY = (visibleY / elRect.height) * 100; 14 | 15 | if (visiblePercentageX < minPercentX || visiblePercentageY < minPercentY) return false; 16 | parent = parent.parentNode; 17 | } while (parent !== document.body); 18 | 19 | return true; 20 | }; 21 | 22 | export default isVisibleXY; 23 | -------------------------------------------------------------------------------- /src/components/Ripple.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | 3 | const Ripple = ({ children }) => { 4 | 5 | const [rippleActive, setRippleActive] = useState(false); 6 | const [left, setLeft] = useState(null); 7 | const [top, setTop] = useState(null); 8 | 9 | const handleMouseDown = e => { 10 | const left = e.clientX - e.target.getBoundingClientRect().left; 11 | const top = e.clientY - e.target.getBoundingClientRect().top; 12 | setLeft(left); 13 | setTop(top); 14 | setRippleActive(true); 15 | setTimeout(() => { 16 | setRippleActive(false); 17 | }, 500); 18 | } 19 | 20 | return ( 21 | <> 22 |
23 | {children} 24 | {rippleActive && ( 25 | 26 | )} 27 |
28 | 29 | ) 30 | } 31 | 32 | export default Ripple -------------------------------------------------------------------------------- /src/pages/AutocompleteDemo.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import Autocomplete from '../components/Autocomplete' 3 | 4 | const list = ["apple", "mango", "banana", "grapes"]; 5 | 6 | 7 | const AutocompleteDemo = () => { 8 | 9 | const [formData, setFormData] = useState({ fruit: "" }); 10 | const handleSelectChange = (item, name) => { 11 | setFormData({ ...formData, [name]: item }); 12 | } 13 | const handleSubmit = e => { 14 | e.preventDefault(); 15 | alert(`Fruit: ${formData.fruit}`); 16 | } 17 | 18 | return ( 19 | <> 20 |

Autocomplete

21 |
22 |
23 | 24 | 25 |
26 | 27 |
28 | 29 | ) 30 | } 31 | 32 | export default AutocompleteDemo -------------------------------------------------------------------------------- /src/pages/CarouselDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Carousel from '../components/Carousel' 3 | 4 | const CarouselDemo = () => { 5 | const carouselImages = [ 6 | { 7 | src: "https://images.unsplash.com/photo-1610878180933-123728745d22?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MXx8Y2FuYWRhJTIwbmF0dXJlfGVufDB8fDB8fA%3D%3D&w=1000&q=80", 8 | label: "First slide label", 9 | text: "this is the first slide", 10 | }, 11 | { 12 | src: "https://cdn.pixabay.com/photo/2018/02/09/10/10/flower-3141276_960_720.jpg", 13 | label: "Second slide label", 14 | text: "this is the second slide", 15 | }, 16 | { 17 | src: "https://image.shutterstock.com/image-photo/3d-wallpaper-design-waterfall-sea-260nw-1380925703.jpg", 18 | label: "Third slide label", 19 | text: "this is the third slide", 20 | }, 21 | ]; 22 | 23 | return ( 24 | <> 25 |

Carousel Demo

26 | 27 | 28 | ) 29 | } 30 | 31 | export default CarouselDemo -------------------------------------------------------------------------------- /src/components/ScrollToTopBtn.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | 3 | const ScrollToTopBtn = () => { 4 | 5 | const [visible, setVisible] = useState(false); 6 | window.onscroll = () => { 7 | if (document.body.scrollTop > 40 || document.documentElement.scrollTop > 40) { 8 | setVisible(true); 9 | } 10 | else { 11 | setVisible(false); 12 | } 13 | } 14 | 15 | const scrolltoTop = () => { 16 | document.body.scrollTop = 0; 17 | document.documentElement.scrollTop = 0; 18 | } 19 | 20 | return ( 21 | <> 22 | 25 | 26 | ) 27 | } 28 | 29 | export default ScrollToTopBtn -------------------------------------------------------------------------------- /src/pages/TableDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Table from '../components/Table' 3 | 4 | const keys = ["name", "email", "roll",]; 5 | const data = [ 6 | { 7 | name: "Rahul", 8 | email: "rahul@gmail.com", 9 | roll: 10, 10 | }, 11 | { 12 | name: "Sohan", 13 | email: "han@gmail.com", 14 | roll: 5 15 | }, 16 | { 17 | name: "Mohan", 18 | email: "mohan@gmail.com", 19 | roll: 6 20 | }, 21 | { 22 | name: "Mohan", 23 | email: "mohan@gmail.com", 24 | roll: 6 25 | }, 26 | { 27 | name: "Mohan", 28 | email: "mohan@gmail.com", 29 | roll: 6 30 | }, 31 | ]; 32 | 33 | 34 | const TableDemo = () => { 35 | return ( 36 | <> 37 |
38 |

Tables

39 |
40 |
41 | 42 | 43 | 44 | 45 | 46 | ) 47 | } 48 | 49 | export default TableDemo -------------------------------------------------------------------------------- /src/components/Rating.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | 3 | const Rating = ({ rating, setRating, count, color, icon }) => { 4 | 5 | const [hoverRating, setHoverRating] = useState(0); 6 | count = count || 5; 7 | color = color || "#facc15"; 8 | icon = icon || "star"; 9 | 10 | 11 | const handleClick = (idx) => setRating(idx); 12 | 13 | const getIconType = (idx) => { 14 | if (hoverRating == 0) { 15 | return (idx <= rating) ? "fa-solid" : "fa-regular" 16 | } 17 | return (idx <= hoverRating) ? "fa-solid" : "fa-regular"; 18 | } 19 | 20 | return ( 21 | <> 22 |
23 | {Array(count).fill(0).map((_, i) => i + 1).map(idx => ( 24 | handleClick(idx)} 29 | onMouseEnter={() => setHoverRating(idx)} 30 | onMouseLeave={() => setHoverRating(0)} 31 | > 32 | 33 | 34 | ))} 35 |
36 | 37 | ) 38 | } 39 | 40 | export default Rating -------------------------------------------------------------------------------- /src/components/Ribbon.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | // Credits: youtube/@OnlineTutorialsYT 4 | const Ribbon = ({ text = "", verticalDistance = "50%", looseness = "6px", thickness }) => { 5 | return ( 6 | <> 7 |
11 |
19 | {text} 20 |
21 | 22 |
26 |
30 |
31 | 32 | ); 33 | }; 34 | 35 | export default Ribbon; 36 | -------------------------------------------------------------------------------- /src/pages/DatePickerDemo.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import DatePickerModal from "../components/DatePicker"; 3 | 4 | const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; 5 | const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; 6 | 7 | const DatePickerDemo = () => { 8 | const [datePickerModal, setDatePickerModal] = useState(false); 9 | const [date, setDate] = useState(new Date()); 10 | 11 | return ( 12 | <> 13 |

Date Picker

14 | 23 | 24 | setDatePickerModal(false)} 28 | onSelect={date => setDate(date)} 29 | /> 30 | 31 | ); 32 | }; 33 | 34 | export default DatePickerDemo; 35 | -------------------------------------------------------------------------------- /src/pages/ProgressDemo.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import Progress from '../components/Progress' 3 | 4 | const ProgressDemo = () => { 5 | const [linearPercent, setLinearPercent] = useState(0); 6 | const [circularPercent, setCircularPercent] = useState(0); 7 | 8 | return ( 9 | <> 10 |

Progress

11 |
12 |
13 | 14 | 15 | 16 |
17 | 18 |
19 | 20 | 21 | 22 |
23 |
24 | 25 | ) 26 | } 27 | 28 | export default ProgressDemo -------------------------------------------------------------------------------- /src/components/Masonry.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | 3 | const Masonry = ({ children, columns, spacing }) => { 4 | 5 | const [items, setItems] = useState([]); 6 | columns = columns || 4; 7 | spacing = spacing || 16; 8 | 9 | useEffect(() => { 10 | 11 | let heights = [...Array(columns)].map((_, i) => ({ column: i + 1, height: 0 })); 12 | let itemsCopy = []; 13 | 14 | children.forEach(element => { 15 | heights.sort((ob1, ob2) => ob1.height - ob2.height); 16 | heights[0].height += element.props.height + spacing; 17 | itemsCopy.push(React.cloneElement(element, { 18 | style: { ...element.props.style, order: heights[0].column, margin: spacing / 2, width: `calc(${100 / columns}% - ${spacing}px)` } 19 | })); 20 | }); 21 | 22 | [...Array(columns)].forEach((_, i) => { 23 | itemsCopy.push( 24 | 25 | ); 26 | }); 27 | 28 | setItems(itemsCopy); 29 | 30 | }, [children, columns, spacing]); 31 | 32 | 33 | 34 | return ( 35 | <> 36 |
37 | {items} 38 |
39 | 40 | ) 41 | } 42 | 43 | export default Masonry -------------------------------------------------------------------------------- /src/components/SidebarWithSubmenu/SidebarWithSubmenu.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { sidebarData } from './data'; 3 | import Submenu from './Submenu'; 4 | 5 | const SidebarWithSubmenu = () => { 6 | const [isSidebarOpen, setIsSidebarOpen] = useState(false); 7 | const toggleSidebar = () => setIsSidebarOpen(state => !state); 8 | 9 | return ( 10 | <> 11 |
12 | 13 |
14 | 15 | {/* Sidebar */} 16 |
17 |
18 | 19 |
20 | 21 |
    22 | {sidebarData.map((item, i) => ( 23 | 24 | ))} 25 |
26 |
27 | 28 | ) 29 | } 30 | 31 | export default SidebarWithSubmenu -------------------------------------------------------------------------------- /src/components/SidebarWithSubmenu/Submenu.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { Link } from 'react-router-dom' 3 | 4 | const Submenu = ({ item, paddingLeft = 10 }) => { 5 | const [isSubnavOpen, setIsSubnavOpen] = useState(false); 6 | const toggleSubnav = () => setIsSubnavOpen(state => !state); 7 | 8 | return ( 9 | <> 10 |
  • 11 | 12 |
    13 | {item.icon} 14 | {item.title} 15 |
    16 | 17 | {item.subNav ? () : null} 18 | 19 | 20 | 21 |
      22 | {item.subNav && item.subNav.map((item, i) => ( 23 | 24 | ))} 25 |
    26 |
  • 27 | 28 | ) 29 | } 30 | 31 | export default Submenu -------------------------------------------------------------------------------- /src/components/CircularMenu.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | 3 | const CircularMenu = ({ children, radius = 70, startAngle = 0, endAngle = 360, endAngleInclusive = false }) => { 4 | 5 | const [show, setShow] = useState(false); 6 | let angle; 7 | if (endAngleInclusive) angle = (endAngle - startAngle) / (children.length - 1); 8 | else angle = (endAngle - startAngle) / (children.length); 9 | 10 | return ( 11 | <> 12 |
    setShow(show => !show)}> 13 | 14 | {show ? : } 15 | 16 | 17 |
      18 | {children.map((item, i) => ( 19 |
    • 20 | {item} 21 |
    • 22 | ))} 23 |
    24 |
    25 | 26 | ) 27 | } 28 | 29 | export default CircularMenu -------------------------------------------------------------------------------- /src/components/DatePicker/DatePickerModal.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Modal from "../Modal"; 3 | import DatePicker from "./DatePicker"; 4 | 5 | const DatePickerModal = ({ 6 | isOpen, 7 | onClose, 8 | defaultValue, 9 | value, 10 | onSelect, 11 | mode, 12 | fromYear, 13 | toYear, 14 | fromDate, 15 | toDate, 16 | hideOutsideDays, 17 | isHidden, 18 | isDateUnselectable, 19 | noSelectionAllowed, 20 | minSelections, 21 | maxSelections, 22 | minRangeLength, 23 | maxRangeLength, 24 | }) => { 25 | return ( 26 | <> 27 | 28 | 50 | 51 | 52 | ); 53 | }; 54 | 55 | export default DatePickerModal; 56 | -------------------------------------------------------------------------------- /src/pages/FaqDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Faq from "../components/Faq"; 3 | 4 | const FaqDemo = () => { 5 | const faqs = [ 6 | { 7 | q: "What does this website do?", 8 | a: "Lorem ipsum dolor sit amet consectetur adipisicing elit. Est, velit temporibus? Placeat, praesentium repellendus quas mollitia soluta error expedita veniam.", 9 | expanded: true, 10 | }, 11 | { 12 | q: "Is there any payment in the website?", 13 | a: "Lorem ipsum, dolor sit amet consectetur adipisicing elit. Velit saepe quas atque nihil officia, accusantium dignissimos in repudiandae eos mollitia? Architecto error mollitia minima beatae facere cupiditate dolorem modi nihil quae. Reprehenderit minima aliquam, esse doloribus deleniti corporis cum consequuntur et aperiam error illo illum commodi necessitatibus repellat similique quaerat? Consectetur quidem quibusdam eligendi voluptatum, voluptates accusamus animi nobis pariatur dolorum quasi veniam nisi vitae deleniti dolore officiis sapiente voluptate quisquam quaerat.", 14 | }, 15 | { 16 | q: "How do I proceed?", 17 | a: "Lorem ipsum dolor sit amet consectetur adipisicing elit. Quaerat, dolores!", 18 | }, 19 | ]; 20 | 21 | return ( 22 |
    23 | 24 |
    25 | ); 26 | }; 27 | 28 | export default FaqDemo; 29 | -------------------------------------------------------------------------------- /src/components/Typewriter.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | 3 | const Typewriter = ({ list }) => { 4 | const [currItem, setCurrItem] = useState(0); 5 | const [currIndex, setCurrIndex] = useState(0); 6 | const [paused, setPaused] = useState(false); 7 | const [showCursor, setShowCursor] = useState(false); 8 | 9 | useEffect(() => { 10 | if (paused) return; 11 | const id = window.setInterval(() => { 12 | if (currIndex < list[currItem].length - 1) { 13 | setCurrIndex(currIndex + 1); 14 | } 15 | else { 16 | setPaused(true); 17 | window.setTimeout(() => { 18 | setCurrIndex(0); 19 | setCurrItem((currItem + 1) % list.length); 20 | setPaused(false); 21 | }, 3000); 22 | } 23 | }, 100); 24 | 25 | return () => window.clearInterval(id); 26 | }); 27 | 28 | useEffect(() => { 29 | const id2 = window.setInterval(() => { 30 | setShowCursor(val => !val); 31 | }, 500); 32 | return () => window.clearInterval(id); 33 | }, []); 34 | 35 | 36 | return ( 37 | <> 38 |
    39 |
    {list[currItem].slice(0, currIndex + 1)}
    40 |
    41 | 42 | ) 43 | } 44 | 45 | export default Typewriter -------------------------------------------------------------------------------- /src/pages/TestimonialsDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Testimonials1 from "../components/Testimonials/Testimonials1"; 3 | 4 | const data = [ 5 | { 6 | personName: "Rakesh Bansal", 7 | profile: "Marketing Manager", 8 | imageUrl: "https://www.ateneo.edu/sites/default/files/2021-11/istockphoto-517998264-612x612.jpeg", 9 | quote: 10 | "Lorem ipsum dolor sit amet consectetur adipisicing elit. Nulla quam ducimus perferendis impedit laborum, facilis dignissimos! Ullam minus cumque quo.", 11 | }, 12 | { 13 | personName: "Vimal Kumar", 14 | profile: "Data Analyst", 15 | imageUrl: "https://www.ateneo.edu/sites/default/files/2021-11/istockphoto-517998264-612x612.jpeg", 16 | quote: "Lorem ipsum dolor sit amet consectetur adipisicing elit. Non, necessitatibus?", 17 | }, 18 | { 19 | personName: "Vineet Singh", 20 | profile: "Product Manager", 21 | imageUrl: "https://www.ateneo.edu/sites/default/files/2021-11/istockphoto-517998264-612x612.jpeg", 22 | quote: 23 | "Lorem, ipsum dolor sit amet consectetur adipisicing elit. Praesentium tenetur nisi minima ipsum, laborum veniam?", 24 | }, 25 | ]; 26 | 27 | const TestimonialsDemo = () => { 28 | return ( 29 |
    30 |
    Testimonials
    31 | 32 |
    33 | ); 34 | }; 35 | 36 | export default TestimonialsDemo; 37 | -------------------------------------------------------------------------------- /src/pages/SliderDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Slider from "../components/Slider"; 3 | 4 | const SliderDemo = () => { 5 | return ( 6 |
    7 |

    Slider

    8 | 9 |
    10 | Simple slider 11 | 12 |
    13 | 14 |
    15 | 0 16 | 17 | 100 18 |
    19 | 20 |
    21 | Negative values 22 | 23 |
    24 | 25 |
    26 | Discrete slider 27 | 28 |
    29 | 30 |
    31 | Display marks 32 | 33 |
    34 | 35 |
    36 | Small steps 37 | 38 |
    39 | 40 |
    41 | Disabled Slider 42 | 43 |
    44 | 45 |
    46 | Always display value label 47 |
    48 | 49 |
    50 |
    51 | ); 52 | }; 53 | 54 | export default SliderDemo; 55 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Deploy static content to Pages 2 | 3 | on: 4 | # Runs on pushes targeting the default branch 5 | push: 6 | branches: ['main'] 7 | 8 | # Allows you to run this workflow manually from the Actions tab 9 | workflow_dispatch: 10 | 11 | # Sets the GITHUB_TOKEN permissions to allow deployment to GitHub Pages 12 | permissions: 13 | contents: read 14 | pages: write 15 | id-token: write 16 | 17 | # Allow one concurrent deployment 18 | concurrency: 19 | group: 'pages' 20 | cancel-in-progress: true 21 | 22 | jobs: 23 | # Single deploy job since we're just deploying 24 | deploy: 25 | environment: 26 | name: github-pages 27 | url: ${{ steps.deployment.outputs.page_url }} 28 | runs-on: ubuntu-latest 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v3 32 | - name: Set up Node 33 | uses: actions/setup-node@v3 34 | with: 35 | node-version: 18 36 | cache: 'npm' 37 | - name: Install dependencies 38 | run: npm install 39 | - name: Build 40 | run: npm run build 41 | - name: Setup Pages 42 | uses: actions/configure-pages@v3 43 | - name: Upload artifact 44 | uses: actions/upload-pages-artifact@v1 45 | with: 46 | # Upload dist repository 47 | path: './dist' 48 | - name: Deploy to GitHub Pages 49 | id: deployment 50 | uses: actions/deploy-pages@v1 51 | -------------------------------------------------------------------------------- /src/components/Timeline.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Timeline = ({ children }) => { 4 | 5 | // connector is from center 6 | const connectorWidth = "2.5rem"; 7 | const connectorStyles = (index) => ({ 8 | position: "absolute", 9 | transform: "translateY(1rem)", 10 | width: connectorWidth, 11 | height: "0.25rem", 12 | background: "#F43F5E", 13 | left: ((index & 1) == 0) ? "50%" : "", 14 | right: ((index & 1) == 1) ? "50%" : "", 15 | }); 16 | 17 | const boxStyles = (index) => ({ 18 | width: "40%", 19 | position: "relative", 20 | left: "50%", 21 | transform: ((index & 1) == 0) ? `translateX(${connectorWidth})` : `translateX(calc(-100% - ${connectorWidth}))`, 22 | }) 23 | 24 | return ( 25 | <> 26 |
    27 |
    28 | 29 | {children.map((child, index) => ( 30 |
    31 |
    32 | {index + 1} 33 |
    34 |
    35 |
    {child}
    36 |
    37 | ))} 38 | 39 |
    40 | 41 | ) 42 | } 43 | 44 | export default Timeline -------------------------------------------------------------------------------- /src/components/SpeedDial.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | 3 | const SpeedDial = ({ children, direction = "up" }) => { 4 | const [show, setShow] = useState(false); 5 | 6 | const getPosition = (i) => { 7 | if (direction == "up") return { top: `${-(i + 1) * 50}px`, left: 0 } 8 | if (direction == "down") return { top: `${(i + 1) * 50}px`, left: 0 } 9 | if (direction == "left") return { left: `${-(i + 1) * 50}px`, top: 0 } 10 | if (direction == "right") return { left: `${(i + 1) * 50}px`, top: 0 } 11 | } 12 | 13 | const getTransitionDelay = (i) => { 14 | if (show) return { transitionDelay: `${i * 100}ms` } 15 | else return { transitionDelay: `${(children.length - i - 1) * 100}ms` } 16 | } 17 | 18 | return ( 19 | <> 20 |
    setShow(show => !show)}> 21 | 22 | 23 | 24 | 25 |
      26 | {children.map((item, i) => ( 27 |
    • 28 | {item} 29 |
    • 30 | ))} 31 |
    32 |
    33 | 34 | ) 35 | } 36 | 37 | export default SpeedDial -------------------------------------------------------------------------------- /src/components/Toast/ToastList.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDom from 'react-dom'; 3 | import Toast from './Toast'; 4 | 5 | const Portal = ({ children }) => { 6 | return ReactDom.createPortal(children, document.body); 7 | } 8 | 9 | 10 | const ToastList = ({ toastList, deleteToast }) => { 11 | const getToast = (toast) => { 12 | return deleteToast(toast.id)} 18 | hasCloseBtn={toast.hasCloseBtn} 19 | autoClose={toast.autoClose} 20 | autoCloseTimeout={toast.autoCloseTimeout} /> 21 | } 22 | 23 | const positions = ["top-right", "top-left", "bottom-right", "bottom-left"]; 24 | const getPositionClasses = (position) => { 25 | switch (position) { 26 | case "top-right": return "top-0 right-0 sm:top-4 sm:right-4"; 27 | case "top-left": return "top-0 left-0 sm:top-4 sm:left-4"; 28 | case "bottom-right": return "right-0 sm:bottom-4 sm:right-4"; 29 | case "bottom-left": return "bottom-0 left-0 sm:bottom-4 sm:left-4"; 30 | } 31 | } 32 | 33 | return ( 34 | <> 35 | 36 | {positions.map(position => ( 37 |
    38 | {toastList.filter(toast => toast.position == position).map((toast) => ( 39 | getToast(toast) 40 | ))} 41 |
    42 | ))} 43 |
    44 | 45 | ) 46 | } 47 | 48 | export default ToastList -------------------------------------------------------------------------------- /src/pages/SpeedDialDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import SpeedDial from '../components/SpeedDial' 3 | 4 | const items = [ 5 | alert("Clicked user")}> 6 | 7 | , 8 | 9 | 10 | 11 | , 12 | 13 | 14 | 15 | , 16 | 17 | 18 | 19 | , 20 | ]; 21 | 22 | const SpeedDialDemo = () => { 23 | return ( 24 | <> 25 |

    Speed Dial

    26 |
    27 | 28 | {items} 29 | 30 | 31 | 32 | {items} 33 | 34 | 35 | 36 | {items} 37 | 38 | 39 | 40 | {items} 41 | 42 |
    43 | 44 | ) 45 | } 46 | 47 | export default SpeedDialDemo -------------------------------------------------------------------------------- /src/pages/MultiselectMenuDemo.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import MultiselectMenu from '../components/MultiselectMenu' 3 | 4 | const list = [ 5 | { 6 | label: "one", 7 | value: 1, 8 | }, 9 | { 10 | label: "two", 11 | value: 2, 12 | }, 13 | { 14 | label: "three", 15 | value: 3, 16 | }, 17 | ]; 18 | 19 | 20 | 21 | const MultiselectMenuDemo = () => { 22 | const [formData, setFormData] = useState({ name: "", nums: [] }); 23 | const handleChange = e => { 24 | setFormData({ ...formData, [e.target.name]: e.target.value }); 25 | } 26 | 27 | const handleSelectChange = (items, name) => { 28 | setFormData(formData => ({ ...formData, [name]: items })); 29 | } 30 | 31 | const handleSubmit = e => { 32 | e.preventDefault(); 33 | alert(`Name: ${formData.name}, Nums: ${formData.nums.join(",")}`); 34 | } 35 | 36 | return ( 37 | <> 38 |

    Custom Multiselect menu

    39 |
    40 |
    41 | 42 |
    43 |
    44 | 45 |
    46 | 47 |
    48 | 49 | ) 50 | } 51 | 52 | export default MultiselectMenuDemo -------------------------------------------------------------------------------- /src/components/CircularRotatingList.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | 3 | const CircularRotatingList = ({ items }) => { 4 | 5 | const [activeItem, setActiveItem] = useState(0); 6 | 7 | const angle = -360 / (items.length); 8 | const radius = 200; 9 | const startAngle = -angle * activeItem; 10 | 11 | const handleClick = (i) => { 12 | setActiveItem(i); 13 | } 14 | 15 | useEffect(() => { 16 | const id = window.setInterval(() => { setActiveItem((activeItem + 1) % items.length) }, 4000); 17 | return () => window.clearInterval(id); 18 | }, [activeItem]); 19 | 20 | 21 | return ( 22 | <> 23 |
    24 |
    {items[activeItem].text}
    25 |
    26 |
      27 | {items.map((item, i) => ( 28 |
    • handleClick(i)}> 29 | 30 | {item.icon} 31 | 32 |
    • 33 | ))} 34 |
    35 |
    36 | 37 | ) 38 | } 39 | 40 | export default CircularRotatingList -------------------------------------------------------------------------------- /src/pages/ModalDemo.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import Modal from "../components/Modal"; 3 | 4 | const ModalDemo = () => { 5 | const [isModalOpen, setIsModalOpen] = useState(false); 6 | 7 | return ( 8 | <> 9 |

    Modal

    10 | 16 | 17 | setIsModalOpen(false)} 20 | className="bg-white rounded-md max-w-[800px]" 21 | > 22 |
    23 |

    This is some sample modal

    24 |

    25 | Lorem ipsum dolor, sit amet consectetur adipisicing elit. Magnam laboriosam vero, iusto at eos 26 | totam! Fugit, vitae ipsum! Officia quod deserunt minus reprehenderit consequatur possimus at 27 | praesentium aliquam, nobis asperiores! Ipsam dicta modi magni earum officiis quibusdam quidem 28 | placeat officia nam vel ducimus tempora laudantium facere eaque ut, voluptatem incidunt? 29 |

    30 |
    31 | 37 |
    38 |
    39 |
    40 | 41 | ); 42 | }; 43 | 44 | export default ModalDemo; 45 | -------------------------------------------------------------------------------- /src/pages/ListHoverDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const ListHoverDemo = () => { 4 | 5 | const listItems = [ 6 | "First item", 7 | "This is second item with large contents.........Just like that", 8 | "This one is third item", 9 | ]; 10 | 11 | return ( 12 | <> 13 | {/* Instead of using group and group-hover class of tailwindcss, we can also use hover events:onMouseEnter and onMouseLeave and maintain one state for current item */} 14 |
    15 |

    List Hover Effects

    16 |
    17 |
      18 | {listItems.map((listItem, index) => ( 19 |
    • 20 | {listItem} 21 | 25 |
    • 26 | ))} 27 |
    28 |
    29 |
    30 | 31 | ) 32 | } 33 | 34 | export default ListHoverDemo -------------------------------------------------------------------------------- /src/components/StatsSection.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | // UI Reference: https://tailwindui.com/components 4 | const stats = [ 5 | { stat: "100%", text: "Trust" }, 6 | { stat: "24/7", text: "Delivery" }, 7 | { stat: "100k", text: "Users" }, 8 | ]; 9 | 10 | const StatsSection = () => { 11 | return ( 12 |
    13 |
    14 |

    15 | Trusted by developers from over 80 planets 16 |

    17 | 18 |
    19 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam veniam voluptates nisi perferendis 20 | minus fuga dolor at quasi reiciendis suscipit. 21 |
    22 | 23 |
    24 | 25 |
    26 | {stats.map(({ stat, text }, idx) => ( 27 |
    31 |
    {stat}
    32 |
    {text}
    33 |
    34 | ))} 35 |
    36 |
    37 |
    38 | ); 39 | }; 40 | 41 | export default StatsSection; 42 | -------------------------------------------------------------------------------- /src/pages/SelectMenuDemo.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import SelectMenu from "../components/SelectMenu"; 3 | 4 | const list = [ 5 | { 6 | label: "one", 7 | value: 1, 8 | }, 9 | { 10 | label: "two", 11 | value: 2, 12 | }, 13 | { 14 | label: "three", 15 | value: 3, 16 | }, 17 | ]; 18 | 19 | const SelectMenuDemo = () => { 20 | const [formData, setFormData] = useState({ name: "", num: "" }); 21 | const handleChange = e => { 22 | setFormData({ ...formData, [e.target.name]: e.target.value }); 23 | }; 24 | const handleSelectChange = (item, name) => { 25 | setFormData({ ...formData, [name]: item }); 26 | }; 27 | const handleSubmit = e => { 28 | e.preventDefault(); 29 | alert(`Name: ${formData.name}, Num: ${formData.num}`); 30 | }; 31 | 32 | return ( 33 | <> 34 |

    Custom select menu

    35 |
    36 |
    37 | 44 |
    45 |
    46 | 47 |
    48 | 51 |
    52 | 53 | ); 54 | }; 55 | 56 | export default SelectMenuDemo; 57 | -------------------------------------------------------------------------------- /src/pages/CircularMenuDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import CircularMenu from '../components/CircularMenu' 3 | 4 | const items = [ 5 | alert("Clicked user")}> 6 | 7 | , 8 | 9 | 10 | 11 | , 12 | 13 | 14 | 15 | , 16 | 17 | 18 | 19 | , 20 | ]; 21 | 22 | const CircularMenuDemo = () => { 23 | return ( 24 | <> 25 |

    Circular Menu

    26 |
    27 | 28 | {items} 29 | 30 | 31 | 32 | {items} 33 | 34 | 35 | 36 | {items} 37 | 38 | 39 | 40 | {items} 41 | 42 |
    43 | 44 | ) 45 | } 46 | 47 | export default CircularMenuDemo -------------------------------------------------------------------------------- /src/pages/TreeViewDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import TreeView from '../components/TreeView' 3 | 4 | const data = [ 5 | { 6 | title: "Item 0", 7 | key: "0", 8 | children: [ 9 | { 10 | title: "Item 00", 11 | key: "0-0" 12 | }, 13 | { 14 | title: "Item 01", 15 | key: "0-1" 16 | }, 17 | ] 18 | }, 19 | 20 | { 21 | title: "Item 1", 22 | key: "1", 23 | children: [ 24 | { 25 | title: "Item 10", 26 | key: "1-0", 27 | children: [ 28 | { 29 | title: "Item 100", 30 | key: "1-0-0" 31 | }, 32 | { 33 | title: "Item 101", 34 | key: "1-0-1" 35 | }, 36 | { 37 | title: "Item 102", 38 | key: "1-0-2" 39 | }, 40 | ] 41 | }, 42 | { 43 | title: "Item 11", 44 | key: "1-1" 45 | }, 46 | ] 47 | }, 48 | 49 | { 50 | title: "Item 2", 51 | key: "2" 52 | } 53 | 54 | ]; 55 | 56 | 57 | 58 | const TreeViewDemo = () => { 59 | return ( 60 | <> 61 |

    Tree View

    62 |
    63 | Example1 64 |
    65 | 66 |
    67 | 68 |
    69 | 70 | Example2 (Expanded Items: 0, 10) 71 |
    72 | 73 |
    74 |
    75 | 76 | ) 77 | } 78 | 79 | export default TreeViewDemo -------------------------------------------------------------------------------- /src/components/SidebarWithSubmenu/data.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | // Modify path,title, icons, subnav according to project 4 | export const sidebarData = [ 5 | { 6 | title: "Overview", 7 | path: "#", 8 | icon: , 9 | subNav: [ 10 | { 11 | title: "Users", 12 | path: "#", 13 | icon: 14 | }, 15 | { 16 | title: "Revenue", 17 | path: "#", 18 | icon: , 19 | }, 20 | ] 21 | }, 22 | { 23 | title: "Bookmarks", 24 | path: "#", 25 | icon: 26 | }, 27 | { 28 | title: "Reports", 29 | path: "#", 30 | icon: , 31 | subNav: [ 32 | { 33 | title: "Type1 Reports", 34 | path: "#", 35 | icon: , 36 | subNav: [ 37 | { 38 | title: "TypeA Reports", 39 | path: "#", 40 | icon: 41 | }, 42 | { 43 | title: "TypeB Reports", 44 | path: "#", 45 | icon: 46 | }, 47 | ] 48 | }, 49 | { 50 | title: "Type2 Reports", 51 | path: "#", 52 | icon: 53 | }, 54 | { 55 | title: "Type3 Reports", 56 | path: "#", 57 | icon: 58 | }, 59 | ] 60 | }, 61 | { 62 | title: "Products", 63 | path: "#", 64 | icon: 65 | }, 66 | ] -------------------------------------------------------------------------------- /src/pages/CircularRotatingListDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import CircularRotatingList from '../components/CircularRotatingList' 3 | 4 | const items = [ 5 | { 6 | text: "User... Lorem ipsum dolor sit amet consectetur adipisicing elit. Qui veritatis quos inventore quisquam laboriosam atque temporibus perferendis. Possimus, molestias ipsum?", 7 | icon: 8 | }, 9 | { 10 | text: "Message... Lorem ipsum dolor sit amet consectetur adipisicing elit. Qui veritatis quos inventore quisquam laboriosam atque temporibus perferendis. Possimus, molestias ipsum?", 11 | icon: 12 | }, 13 | { 14 | text: "Pen... Lorem ipsum dolor sit amet consectetur adipisicing elit. Qui veritatis quos inventore quisquam laboriosam atque temporibus perferendis. Possimus, molestias ipsum?", 15 | icon: 16 | }, 17 | { 18 | text: "Chart... Lorem ipsum dolor sit amet consectetur adipisicing elit. Qui veritatis quos inventore quisquam laboriosam atque temporibus perferendis. Possimus, molestias ipsum?", 19 | icon: 20 | }, 21 | { 22 | text: "Star... Lorem ipsum dolor sit amet consectetur adipisicing elit. Qui veritatis quos inventore quisquam laboriosam atque temporibus perferendis. Possimus, molestias ipsum?", 23 | icon: 24 | }, 25 | ]; 26 | 27 | const CircularRotatingListDemo = () => { 28 | return ( 29 | <> 30 |

    Circular Rotating List

    31 |
    32 | 33 |
    34 | 35 | ) 36 | } 37 | 38 | export default CircularRotatingListDemo -------------------------------------------------------------------------------- /src/components/CommandPalette/utils/useKeyboardShortcut.jsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useState } from "react"; 2 | 3 | const useKeyboardShortcut = (keyCombo, callback) => { 4 | const [pressedKeys, setPressedKeys] = useState([]); 5 | 6 | const onKeyDown = useCallback( 7 | e => { 8 | const { key, repeat } = e; 9 | if (repeat) return; 10 | if (pressedKeys.includes(key)) return; 11 | setPressedKeys([...pressedKeys, key]); 12 | }, 13 | [pressedKeys] 14 | ); 15 | 16 | const onKeyUp = useCallback( 17 | e => { 18 | const { key } = e; 19 | const newPressedKeys = pressedKeys.filter(pressedKey => pressedKey !== key); 20 | setPressedKeys(newPressedKeys); 21 | }, 22 | [pressedKeys] 23 | ); 24 | 25 | useEffect(() => { 26 | window.addEventListener("keydown", onKeyDown); 27 | return () => window.removeEventListener("keydown", onKeyDown); 28 | }, [onKeyDown]); 29 | 30 | useEffect(() => { 31 | window.addEventListener("keyup", onKeyUp); 32 | return () => window.removeEventListener("keyup", onKeyUp); 33 | }, [onKeyUp]); 34 | 35 | const isMatch = (keyCombo, pressedKeys) => { 36 | const wantKeysArray = keyCombo 37 | .toLowerCase() 38 | .split("+") 39 | .map(str => str.trim()) 40 | .map(str => (str === "ctrl" ? "control" : str)); 41 | 42 | const pressedKeysArray = pressedKeys.map(key => key.toLowerCase()); 43 | if (pressedKeysArray.length !== wantKeysArray.length) return false; 44 | if (pressedKeysArray.some(pressedKey => !wantKeysArray.includes(pressedKey))) return false; 45 | return true; 46 | }; 47 | 48 | useEffect(() => { 49 | if (isMatch(keyCombo, pressedKeys)) callback(); 50 | }, [pressedKeys, keyCombo, callback]); 51 | }; 52 | 53 | export default useKeyboardShortcut; 54 | -------------------------------------------------------------------------------- /src/components/Progress.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | // @param type: "linear" or "circular" 4 | const Progress = ({ type = "circular", percent = 70 }) => { 5 | 6 | if (type == "linear") { 7 | return ( 8 | <> 9 |
    10 |
    11 |
    12 |
    13 |
    {percent}%
    14 |
    15 | 16 | ) 17 | } 18 | 19 | else if (type == "circular") { 20 | return ( 21 | <> 22 |
    23 |
    24 |
    25 |
    26 |
    27 |
    28 |
    29 | 30 |
    31 |
    {percent}%
    32 |
    33 | 34 | ) 35 | } 36 | 37 | else { 38 | return null; 39 | } 40 | 41 | } 42 | 43 | export default Progress -------------------------------------------------------------------------------- /src/components/RadioBtn.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | 3 | const RadioBtn = ({ label, id, name, value, checked = false, onChange }) => { 4 | let [isChecked, setIsChecked] = useState(checked); 5 | 6 | const handleRadioBtnClick = () => { 7 | const checkTogged = !isChecked; 8 | setIsChecked(checkTogged); 9 | onChange(name, value, checkTogged); 10 | }; 11 | 12 | useEffect(() => setIsChecked(checked), [checked]); 13 | 14 | return ( 15 | <> 16 | 44 | 45 | ); 46 | }; 47 | 48 | export default RadioBtn; 49 | -------------------------------------------------------------------------------- /src/pages/MasonryDemo.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import Masonry from '../components/Masonry' 3 | 4 | const heights = [150, 30, 90, 70, 110, 150, 130, 80, 50, 90, 100, 150, 30, 50, 80]; 5 | 6 | const MasonryDemo = () => { 7 | 8 | const [columns, setColumns] = useState(5); 9 | const [spacing, setSpacing] = useState(16); 10 | 11 | const [formData, setFormData] = useState({ columns: 5, spacing: 16 }); 12 | const handleSubmit = () => { 13 | if (formData.columns == "") setColumns(0); else setColumns(Number(formData.columns)); 14 | if (formData.spacing == "") setSpacing(0); else setSpacing(Number(formData.spacing)); 15 | } 16 | 17 | return ( 18 | <> 19 |

    Masonry

    20 |
    21 | 22 |
    23 | setFormData({ ...formData, columns: e.target.value })} /> 24 | setFormData({ ...formData, spacing: e.target.value })} /> 25 | 26 |
    27 | 28 |
    29 | 30 | {heights.map((height, index) => ( 31 |
    32 | {index + 1} 33 |
    34 | ))} 35 |
    36 |
    37 | 38 |
    39 | 40 | ) 41 | } 42 | 43 | export default MasonryDemo -------------------------------------------------------------------------------- /src/components/Faq.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from "react"; 2 | 3 | const FaqItem = ({ faq, defaultAllExpanded }) => { 4 | const [isItemOpen, setIsItemOpen] = useState(faq.expanded ?? defaultAllExpanded ?? false); 5 | const ansRef = useRef(); 6 | const [height, setHeight] = useState("auto"); 7 | 8 | const handleClick = () => { 9 | setIsItemOpen(!isItemOpen); 10 | }; 11 | 12 | useEffect(() => { 13 | if (ansRef.current) setHeight(ansRef.current.offsetHeight); 14 | }, []); 15 | 16 | const { q, a } = faq; 17 | 18 | return ( 19 |
    20 |
    25 |
    {q}
    26 | 32 |
    33 | 34 |
    38 |
    39 | {a} 40 |
    41 |
    42 |
    43 | ); 44 | }; 45 | 46 | const Faq = ({ faqs = [], defaultAllExpanded = false }) => { 47 | return ( 48 |
    49 |
    Frequently Asked Questions
    50 | {faqs.map((faq, idx) => ( 51 | 52 | ))} 53 |
    54 | ); 55 | }; 56 | 57 | export default Faq; 58 | -------------------------------------------------------------------------------- /src/components/Sidebar.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | 3 | const Sidebar = () => { 4 | 5 | const liClasses = "px-4 py-2 cursor-pointer hover:bg-slate-100"; 6 | const [isSidebarOpen, setIsSidebarOpen] = useState(window.innerWidth < 730 ? false : true); 7 | const toggleSidebar = () => { 8 | setIsSidebarOpen(!isSidebarOpen); 9 | } 10 | 11 | 12 | return ( 13 | <> 14 |
    15 |
    16 | 17 |
    18 | 19 |
      20 |
    • 21 | 22 | Code 23 |
    • 24 |
    • 25 | 26 | Messages 27 |
    • 28 |
    • 29 | 30 | Write 31 |
    • 32 |
    • 33 | 34 | User 35 |
    • 36 |
    37 |
    38 | 39 | {/* Empty div for pushing main content to right according to the width of the sidebar */} 40 |
    41 |
    42 | 43 | ) 44 | } 45 | 46 | export default Sidebar -------------------------------------------------------------------------------- /src/pages/AlertsDemo.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import Alert from '../components/Alert'; 3 | 4 | const AlertsDemo = () => { 5 | const successMsg = "Congratulations!! Account registered successfully!"; 6 | const infoMsg = "This channel is archived by the owner!"; 7 | const warningMsg = "Your image size is too large!"; 8 | const errorMsg = "This email is already in use!"; 9 | 10 | const [showSuccess, setShowSuccess] = useState(false); 11 | const [showInfo, setShowInfo] = useState(false); 12 | const [showWarning, setShowWarning] = useState(false); 13 | const [showError, setShowError] = useState(false); 14 | 15 | 16 | return ( 17 | <> 18 |

    Alerts

    19 |
    20 | 21 |
    22 | {showSuccess && setShowSuccess(false)} />} 23 | 24 |
    25 | 26 |
    27 | {showInfo && setShowInfo(false)} />} 28 | 29 |
    30 | 31 |
    32 | {showWarning && setShowWarning(false)} />} 33 | 34 |
    35 | 36 |
    37 | {showError && setShowError(false)} />} 38 | 39 |
    40 |
    41 | 42 | 43 | ) 44 | } 45 | 46 | export default AlertsDemo -------------------------------------------------------------------------------- /src/components/Resizable.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from "react"; 2 | 3 | const formatSize = size => { 4 | return typeof size === "string" ? size : `${size}px`; 5 | }; 6 | 7 | const Resizable = ({ 8 | children, 9 | className = "", 10 | style = {}, 11 | defaultSize: { defaultWidth = "fit-content", defaultHeight = "fit-content" } = {}, 12 | minWidth, 13 | maxWidth, 14 | }) => { 15 | const elementRef = useRef(null); 16 | const resizerRef = useRef(null); 17 | 18 | useEffect(() => { 19 | elementRef.current.style.width = formatSize(defaultWidth); 20 | elementRef.current.style.height = formatSize(defaultHeight); 21 | }, [defaultWidth, defaultHeight]); 22 | 23 | useEffect(() => { 24 | const resizerElement = resizerRef.current; 25 | let prevXRight; 26 | 27 | const onPointerMove = e => { 28 | const dx = e.clientX - prevXRight; 29 | const newWidth = elementRef.current.offsetWidth + dx; 30 | elementRef.current.style.width = formatSize(newWidth); 31 | prevXRight = e.clientX; 32 | }; 33 | 34 | const onPointerUp = () => { 35 | document.removeEventListener("pointermove", onPointerMove); 36 | }; 37 | 38 | const onPointerDown = e => { 39 | prevXRight = e.clientX; 40 | document.addEventListener("pointermove", onPointerMove); 41 | document.addEventListener("pointerup", onPointerUp); 42 | }; 43 | 44 | resizerElement.addEventListener("pointerdown", onPointerDown); 45 | return () => { 46 | resizerElement.removeEventListener("pointerdown", onPointerDown); 47 | }; 48 | }, [minWidth, maxWidth]); 49 | 50 | return ( 51 |
    52 | {children} 53 |
    57 |
    58 | ); 59 | }; 60 | 61 | export default Resizable; 62 | -------------------------------------------------------------------------------- /src/pages/NotificationBadgeDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const NotificationBadgeDemo = () => { 4 | return ( 5 | <> 6 |
    7 | 8 | 9 | 10 | 11 |
    12 | 13 |
    14 | 15 | 16 | 17 | 19 | New 20 | 21 |
    22 | 23 |
    24 | 25 | 26 | 27 | 29 | 100+ 30 | 31 |
    32 | 33 |
    34 | 35 | View Emails 36 | 37 | 39 | 10 40 | 41 |
    42 | 43 | ) 44 | } 45 | 46 | export default NotificationBadgeDemo -------------------------------------------------------------------------------- /src/pages/DrawerDemo.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import Drawer from '../components/Drawer' 3 | 4 | const DrawerDemo = () => { 5 | 6 | const [isRightDrawerOpen, setIsRightDrawerOpen] = useState(false); 7 | const [isLeftDrawerOpen, setIsLeftDrawerOpen] = useState(false); 8 | const [isTopDrawerOpen, setIsTopDrawerOpen] = useState(false); 9 | const [isBottomDrawerOpen, setIsBottomDrawerOpen] = useState(false); 10 | 11 | return ( 12 | <> 13 |

    Drawer

    14 |
    15 | 16 | 17 | 18 | 19 | 20 | setIsRightDrawerOpen(false)}> 21 |
    Some dummy content
    22 |
    23 | 24 | setIsLeftDrawerOpen(false)}> 25 |
    Some dummy content
    26 |
    27 | 28 | setIsTopDrawerOpen(false)}> 29 |
    Some dummy content
    30 |
    31 | 32 | setIsBottomDrawerOpen(false)}> 33 |
    Some dummy content
    34 |
    35 |
    36 | 37 | ) 38 | } 39 | 40 | export default DrawerDemo -------------------------------------------------------------------------------- /src/components/TreeView/TreeItem.jsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect, useRef } from 'react' 2 | 3 | const TreeItem = ({ level, treeItem, expandedKeys, setExpandedKeys, selectedKey, setSelectedKey }) => { 4 | 5 | const hasChildren = treeItem.children && treeItem.children?.length > 0; 6 | const expanded = expandedKeys.includes(treeItem.key); 7 | const isSelected = selectedKey === treeItem.key; 8 | 9 | const itemRef = useRef(); 10 | 11 | const toggleExpand = useCallback(() => { 12 | if (!hasChildren) return; 13 | if (!expanded) setExpandedKeys([...expandedKeys, treeItem.key]); 14 | else setExpandedKeys(expandedKeys.filter(key => key !== treeItem.key)); 15 | }, [expandedKeys, setExpandedKeys]); 16 | 17 | useEffect(() => { 18 | const handleKeyPress = e => { 19 | if (e.key === "Enter" || e.key === " ") toggleExpand(); 20 | } 21 | itemRef.current?.addEventListener("keypress", handleKeyPress); 22 | return () => itemRef.current?.removeEventListener("keypress", handleKeyPress); 23 | }, [toggleExpand]); 24 | 25 | 26 | const handleListItemClick = () => { 27 | setSelectedKey(treeItem.key); 28 | toggleExpand(); 29 | } 30 | 31 | 32 | return ( 33 | <> 34 |
  • 35 |
    36 |
    37 |
    38 | {hasChildren && ()} 39 | {treeItem.title} 40 |
    41 |
    42 | 43 | {hasChildren && ( 44 |
      45 | {treeItem.children.map(item => ( 46 | 47 | ))} 48 |
    49 | )} 50 |
  • 51 | 52 | ) 53 | } 54 | 55 | export default TreeItem -------------------------------------------------------------------------------- /src/components/Drawer.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from 'react' 2 | import ReactDom from 'react-dom'; 3 | 4 | const Portal = ({ children }) => { 5 | return ReactDom.createPortal(children, document.body); 6 | } 7 | 8 | 9 | const Drawer = ({ children, title = "Drawer", isOpen, onClose, placement = "right" }) => { 10 | 11 | const drawerRef = useRef(); 12 | 13 | useEffect(() => { 14 | if (!isOpen) return; 15 | document.body.style.overflow = "hidden"; 16 | return () => document.body.style.overflow = "auto"; 17 | }, [isOpen]); 18 | 19 | const checkAndCloseDrawer = e => { 20 | if (drawerRef.current.contains(e.target)) return; 21 | onClose(); 22 | } 23 | 24 | const wrapperClasses = () => { 25 | if (isOpen) return "top-0 bottom-0 left-0 right-0"; 26 | 27 | switch (placement) { 28 | case "right": return "w-0 top-0 bottom-0 right-0"; 29 | case "left": return "w-0 top-0 bottom-0 left-0"; 30 | case "top": return "h-0 left-0 right-0 top-0"; 31 | case "bottom": return "h-0 left-0 right-0 bottom-0"; 32 | } 33 | } 34 | 35 | const drawerClasses = () => { 36 | switch (placement) { 37 | case "right": return "right-0 w-[300px] h-full " + (!isOpen ? " translate-x-full" : ""); 38 | case "left": return "left-0 w-[300px] h-full " + (!isOpen ? " -translate-x-full" : ""); 39 | case "top": return "top-0 h-[300px] w-full " + (!isOpen ? " -translate-y-full" : ""); 40 | case "bottom": return "bottom-0 h-[300px] w-full " + (!isOpen ? " translate-y-full" : ""); 41 | } 42 | } 43 | 44 | return ( 45 | <> 46 | 47 |
    48 |
    49 |
    50 |
    51 | 54 |
    {title}
    55 |
    56 | {children} 57 |
    58 |
    59 |
    60 | 61 | ) 62 | } 63 | 64 | export default Drawer -------------------------------------------------------------------------------- /src/components/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | 3 | const Navbar = () => { 4 | const liClasses = 'py-2 px-3 cursor-pointer hover:bg-[#c1d4da] transition rounded-sm'; 5 | const [isNavbarOpen, setIsNavbarOpen] = useState(false); 6 | const toggleNavbar = () => { 7 | setIsNavbarOpen(!isNavbarOpen); 8 | } 9 | 10 | return ( 11 | <> 12 |
    13 |

    React Tailwind

    14 |
    15 | 16 | 17 |
    18 |
      19 |
    • Home
    • 20 |
    • About
    • 21 |
    • Services
    • 22 |
    • Gallery
    • 23 |
    24 | 25 | 26 | 27 | {/* Navbar displayed as sidebar on smaller screens */} 28 |
    29 |
    30 | 31 |
    32 |
    33 | 34 | 35 |
    36 |
      37 |
    • Home
    • 38 |
    • About
    • 39 |
    • Services
    • 40 |
    • Gallery
    • 41 |
    42 |
    43 |
    44 | 45 | ) 46 | } 47 | 48 | export default Navbar -------------------------------------------------------------------------------- /src/components/SortableList.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | 3 | const SortableList = ({ list }) => { 4 | const [items, setItems] = useState(list); 5 | const [draggedItem, setDraggedItem] = useState(null); 6 | const [overItem, setOverItem] = useState(null); 7 | 8 | const handleDragStart = e => { 9 | // console.log("drag start") 10 | let source = e.target; 11 | setDraggedItem(source); 12 | e.dataTransfer.effectAllowed = "move"; 13 | let dragImageSource = source.cloneNode(true); 14 | dragImageSource.style.position = 'absolute'; 15 | dragImageSource.style.top = '0px'; 16 | dragImageSource.style.left = '-' + String(window.innerWidth) + 'px'; 17 | dragImageSource.style.backgroundColor = "gray"; 18 | document.body.append(dragImageSource); 19 | e.dataTransfer.setDragImage(dragImageSource, 0, 0); 20 | } 21 | 22 | const handleDragEnter = e => { 23 | // console.log("drag enter") 24 | setOverItem(e.target); 25 | } 26 | 27 | const handleDragLeave = e => { 28 | // console.log("drag leave") 29 | if (e.target == overItem) setOverItem(null); 30 | } 31 | 32 | const handleDragOver = e => { 33 | // console.log("drag over") 34 | e.preventDefault(); 35 | } 36 | 37 | const handleDrop = e => { 38 | // console.log("drop") 39 | let data = items; 40 | let from = Number(draggedItem.dataset.index); 41 | let to = Number(overItem.dataset.index); 42 | if (from < to) to--; 43 | data.splice(to, 0, data.splice(from, 1)[0]); 44 | setItems(data); 45 | setDraggedItem(null); 46 | setOverItem(null); 47 | } 48 | 49 | return ( 50 | <> 51 |
    52 |
      53 | {items.map((item, i) => ( 54 |
    • 57 | {item} 58 | 59 |
    • 60 | ))} 61 |
    62 |
    63 | 64 | ) 65 | } 66 | 67 | export default SortableList -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | const plugin = require("tailwindcss/plugin"); 2 | 3 | module.exports = { 4 | content: ["./src/**/*.{js,jsx,ts,tsx}"], 5 | theme: { 6 | extend: { 7 | animation: { 8 | fadeInUp: "fadeInUp 500ms ease-out", 9 | loader: "loader 600ms linear infinite", 10 | }, 11 | keyframes: { 12 | loader: { 13 | "0%": { transform: "rotate(0)" }, 14 | "50%": { transform: "rotate(180deg)" }, 15 | "100%": { transform: "rotate(360deg)" }, 16 | }, 17 | ripple: { 18 | "0%": { width: 0, height: 0, opacity: 1 }, 19 | "100%": { width: "500px", height: "500px", opacity: 0 }, 20 | }, 21 | slideInFromDown: { 22 | from: { transform: "translateY(100vh)" }, 23 | to: { transform: "translateY(0px)" }, 24 | }, 25 | slideInFromUp: { 26 | from: { transform: "translateY(-100vh)" }, 27 | to: { transform: "translateY(0px)" }, 28 | }, 29 | slideInFromLeft: { 30 | from: { transform: "translateX(-100vw)" }, 31 | to: { transform: "translateX(0px)" }, 32 | }, 33 | slideInFromRight: { 34 | from: { transform: "translateX(100vw)" }, 35 | to: { transform: "translateX(0px)" }, 36 | }, 37 | zoomIn: { 38 | from: { transform: "scale(0)" }, 39 | to: { transform: "scale(1)" }, 40 | }, 41 | slideOutToDown: { 42 | from: { transform: "translateY(0px)" }, 43 | to: { transform: "translateY(100vh)" }, 44 | }, 45 | slideOutToUp: { 46 | from: { transform: "translateY(0px)" }, 47 | to: { transform: "translateY(-100vh)" }, 48 | }, 49 | slideOutToLeft: { 50 | from: { transform: "translateX(0px)" }, 51 | to: { transform: "translateX(-100vw)" }, 52 | }, 53 | slideOutToRight: { 54 | from: { transform: "translateX(0px)" }, 55 | to: { transform: "translateX(100vw)" }, 56 | }, 57 | zoomOut: { 58 | from: { transform: "scale(1)" }, 59 | to: { transform: "scale(0)" }, 60 | }, 61 | }, 62 | }, 63 | }, 64 | plugins: [ 65 | plugin(function ({ addUtilities }) { 66 | addUtilities({ 67 | ".rotate-y-90": { 68 | transform: "rotateY(90deg)", 69 | }, 70 | }); 71 | }), 72 | ], 73 | }; 74 | -------------------------------------------------------------------------------- /src/components/Autocomplete.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from 'react' 2 | 3 | const Autocomplete = ({ placeholder, name, onChange, list, isDismissible = true }) => { 4 | 5 | const [open, setOpen] = useState(false); 6 | const [filteredList, setFilteredList] = useState(list); 7 | const dropdownRef = useRef(); 8 | const inputRef = useRef(); 9 | 10 | useEffect(() => { 11 | if (!isDismissible) return; 12 | document.addEventListener("click", checkAndHideDropdown); 13 | return () => document.removeEventListener("click", checkAndHideDropdown); 14 | }, [open]); 15 | 16 | 17 | const toggleDropdownMenu = () => { 18 | setOpen(open => !open); 19 | } 20 | 21 | const handleInputChange = e => { 22 | const val = e.target.value; 23 | setFilteredList(list.filter(option => option.includes(val))); 24 | onChange(val, name); 25 | }; 26 | 27 | const handleSelect = (option) => { 28 | inputRef.current.value = option; 29 | onChange(option, name); 30 | setOpen(false); 31 | } 32 | 33 | const checkAndHideDropdown = e => { 34 | if (dropdownRef.current.contains(e.target)) return; 35 | setOpen(false); 36 | } 37 | 38 | const inputClasses = `w-full min-w-[150px] bg-white border border-gray-300 px-3 py-1.5 text-sm rounded-md shadow-sm whitespace-nowrap hover:shadow-md focus:shadow-md focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 active:shadow-md transition duration-150 ease-in-out sm:text-base`; 39 | const dropdownMenuClasses = `${open ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none -translate-y-2"} 40 | m-0 mt-1 absolute min-w-full max-h-[300px] overflow-auto z-50 bg-white border border-gray-100 transition-all duration-300 cursor-pointer rounded-lg shadow-lg`; 41 | const itemClasses = "text-gray-400 bg-white py-2 px-4 block w-full whitespace-nowrap hover:bg-indigo-500 hover:text-white" 42 | 43 | 44 | return ( 45 | <> 46 |
    47 | 48 | 49 |
    50 |
      51 | {filteredList.map((option, i) => ( 52 |
    • handleSelect(option)}>{option}
    • 53 | ))} 54 |
    55 |
    56 |
    57 | 58 | ) 59 | } 60 | 61 | export default Autocomplete -------------------------------------------------------------------------------- /src/components/SelectMenu.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from 'react' 2 | 3 | /** 4 | * @param list: arr of objs {label, value} 5 | */ 6 | const SelectMenu = ({ title, name, id, value, list, onChange, isDismissible = true }) => { 7 | 8 | const [open, setOpen] = useState(false); 9 | const [selected, setSelected] = useState(null); 10 | const dropdownRef = useRef(); 11 | 12 | useEffect(() => { 13 | if (value) setSelected(list.find(ob => ob.value === value)); 14 | }, [value]); 15 | 16 | useEffect(() => { 17 | if (!isDismissible) return; 18 | document.addEventListener("click", checkAndHideDropdown); 19 | return () => document.removeEventListener("click", checkAndHideDropdown); 20 | }, [open]); 21 | 22 | 23 | const toggleDropdownMenu = () => { 24 | setOpen(open => !open); 25 | } 26 | 27 | const handleSelect = (option) => { 28 | onChange(option.value, name); 29 | setSelected(option); 30 | setOpen(false); 31 | } 32 | 33 | const checkAndHideDropdown = e => { 34 | if (!dropdownRef.current || dropdownRef.current.contains(e.target)) return; 35 | setOpen(false); 36 | } 37 | 38 | const dropdownArrowClasses = `transition ${open ? "-rotate-180" : ""}`; 39 | const dropdownMenuClasses = ` ${open ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none -translate-y-2"}`; 40 | const itemClasses = (option) => ( 41 | `${selected && option.value == selected.value ? "text-sky-600" : "text-gray-600"} px-4 py-2 hover:bg-sky-50 hover:text-sky-600` 42 | ); 43 | 44 | 45 | return ( 46 | <> 47 |
    48 | 52 | 53 |
    54 |
      55 | {list.map((option, i) => ( 56 |
    • handleSelect(option)}>{option.label}
    • 57 | ))} 58 |
    59 |
    60 |
    61 | 62 | ) 63 | } 64 | 65 | export default SelectMenu -------------------------------------------------------------------------------- /src/components/Switch.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | 3 | const Thumb = ({ disabled, disableRipple, active, styles }) => { 4 | return ( 5 | <> 6 |
    11 |
    15 |
    16 | 17 | ); 18 | }; 19 | 20 | const Switch = ({ 21 | defaultChecked = false, 22 | checked = false, 23 | disabled = false, 24 | disableRipple = false, 25 | name = "", 26 | id, 27 | onChange = () => {}, 28 | inputRef, 29 | styles = {}, 30 | }) => { 31 | const [currChecked, setCurrChecked] = useState(defaultChecked); 32 | const [active, setActive] = useState(false); 33 | const handleChange = () => { 34 | setCurrChecked(!currChecked); 35 | onChange(!currChecked); 36 | }; 37 | 38 | useEffect(() => { 39 | setCurrChecked(checked); 40 | }, [checked]); 41 | 42 | return ( 43 | <> 44 | 76 | 77 | ); 78 | }; 79 | 80 | export default Switch; 81 | -------------------------------------------------------------------------------- /src/components/ScrollAnimations/ScrollAnimations.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from "react"; 2 | import useIntersectionObserver from "./useIntersectionObserver"; 3 | 4 | const ScrollAnimations = () => { 5 | const elementRef = useRef(); 6 | const { isVisible } = useIntersectionObserver(elementRef, { threshold: 1 }); 7 | 8 | return ( 9 | <> 10 |
    11 |
    12 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Eius accusantium velit repudiandae? Saepe 13 | minus rem odit labore corporis, tempora, ducimus dolores omnis inventore, similique adipisci! Illum 14 | itaque incidunt eaque minima reprehenderit commodi reiciendis ipsum nobis, voluptas quia earum 15 | consequuntur quod, animi enim nostrum ut. Sequi ad inventore facere. Natus debitis dolorum omnis 16 | voluptatem, possimus explicabo amet temporibus modi! Quidem, aut. A atque doloribus voluptatem 17 | dolores culpa nulla modi sapiente rerum nam tempora dolor maxime, quod nemo molestias magnam iusto 18 | facilis deleniti. Alias, blanditiis veritatis nemo, beatae molestiae id facere et consequuntur 19 | cumque, iste dolor ut! Velit corrupti eum cumque, nam ut consequuntur! Voluptate eos eum cupiditate 20 | harum officia nam magnam atque voluptates laudantium, exercitationem distinctio rerum odit beatae 21 | quos expedita deserunt ipsum numquam laboriosam nemo sapiente vero magni ex quam. Consequuntur 22 | voluptatum totam harum! Quod, dolorum laborum alias repellat in atque saepe? Et tempora deleniti 23 | nulla? Cupiditate autem nisi animi consequatur modi quod alias numquam, aut labore quae 24 | exercitationem molestiae nostrum tempora perferendis asperiores fugit quam placeat unde soluta ipsum 25 | aliquam corporis. Eos blanditiis quas repellendus facilis quasi nihil dolorem ea omnis, natus ut 26 | excepturi sapiente. Excepturi suscipit ea officia eos modi, error rerum at reprehenderit, nesciunt 27 | laborum, magni provident! 28 |
    29 | 30 |
    36 | Lorem, ipsum dolor sit amet consectetur adipisicing elit. Sit sed officiis quas officia facilis 37 | totam architecto magnam ducimus quod itaque. 38 |
    39 |
    40 | 41 | ); 42 | }; 43 | 44 | export default ScrollAnimations; 45 | -------------------------------------------------------------------------------- /src/components/ScrollAnimations/useIntersectionObserver.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | /** 4 | * ARGUMENTS DESC: 5 | * 6 | * elementRef: 7 | * - reference of target element whose visibility has to be observed. 8 | * 9 | * callback: 10 | * - called when element enters into /leaves from screen from top or bottom. 11 | * 12 | * rootRef: 13 | * - Reference of element that is used as viewport for checking visibility of the target. 14 | * - Must be ancestor of the target. 15 | * - Defaults to browser viewport if not specified or null. 16 | * 17 | * rootMargin: 18 | * - Margin around the root. 19 | * - Can have values similar to CSS margin property (E.G., "10px 20px 10px 10px" (top, right, bottom, left)). 20 | * - The values can be percentages. 21 | * - This set of values serves to grow or shrink each side of root element's bounding box before computing intersections. 22 | * - Defaults to all 0s 23 | * 24 | * threshold: 25 | * - Either a single number or an array of numbers. 26 | * - Indicate at what percentage of the target's visibility the observer's callback should be executed. 27 | * - Example: 28 | * - If you only want to detect when visibility passes 50% mark, you can use value of 0.5 29 | * - If you want the callback to run every time visibility passes another 25%, you would specify the array [0,0.25,0.5,0.75,1] 30 | * - A value of 1 means that when 100% of target is visible within the element specified by root option, the callback is invoked. 31 | * - Default is 0. This means as soon as even 1 pixel is visible, the callback will be run. 32 | * 33 | * Source of doc: MDN Web Docs 34 | */ 35 | const useIntersectionObserver = ( 36 | elementRef, 37 | { callback = () => {}, rootRef = null, rootMargin = "0px", threshold = 0, freezeOnceVisible = false } = {} 38 | ) => { 39 | const [entry, setEntry] = useState({ isVisible: false }); 40 | const frozen = freezeOnceVisible && entry?.isIntersecting; 41 | 42 | const handleIntersect = ([entry]) => { 43 | setEntry({ isVisible: entry.isIntersecting }); 44 | console.log(entry); 45 | callback(entry); 46 | }; 47 | 48 | useEffect(() => { 49 | const element = elementRef?.current; 50 | const hasIOSupport = !!window.IntersectionObserver; 51 | if (!hasIOSupport || !element || frozen) return; 52 | 53 | const observerParams = { root: rootRef?.current, rootMargin, threshold }; 54 | const observer = new IntersectionObserver(handleIntersect, observerParams); 55 | observer.observe(element); 56 | 57 | return () => observer.disconnect(); 58 | // eslint-disable-next-line react-hooks/exhaustive-deps 59 | }, [elementRef, rootRef, rootMargin, JSON.stringify(threshold), frozen]); 60 | 61 | return entry; 62 | }; 63 | 64 | export default useIntersectionObserver; 65 | -------------------------------------------------------------------------------- /src/components/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | // UI Reference: https://tailwindui.com/components/marketing/sections/footers 4 | const data = [ 5 | { title: "Solutions", list: ["Marketing", "Analytics", "Commerce", "Insights"] }, 6 | { title: "Support", list: ["Pricing", "Documentation", "Guides", "API Status"] }, 7 | { title: "Company", list: ["About", "Blog", "Jobs", "Press", "Partners"] }, 8 | { title: "Legal", list: ["Claim", "Privacy", "Terms of Use"] }, 9 | ]; 10 | 11 | const Footer = ({ theme = "dark" }) => { 12 | return ( 13 | <> 14 |
    19 |
    20 |
    21 |
    22 | Making the world a better place through constructing elegant hierachies. 23 |
    24 | 25 |
    26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
    42 |
    43 | 44 |
    45 | {data.map(({ title, list }, idx) => ( 46 |
    47 |
    51 | {title} 52 |
    53 |
      54 | {list.map((item, idx) => ( 55 |
    • {item}
    • 56 | ))} 57 |
    58 |
    59 | ))} 60 |
    61 |
    62 | 63 |
    67 | © 2020 Your Company, Inc. All rights reserved 68 |
    69 |
    70 | 71 | ); 72 | }; 73 | 74 | export default Footer; 75 | -------------------------------------------------------------------------------- /src/components/Checkbox.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | 3 | const Checkbox = ({ label, id, name, value, checked = false, indeterminate = false, onChange }) => { 4 | let [isChecked, setIsChecked] = useState(checked); 5 | const [isIndeterminate, setIsIndeterminate] = useState(indeterminate); 6 | const handleCheckboxClick = () => { 7 | setIsIndeterminate(false); 8 | setIsChecked(!isChecked); 9 | }; 10 | 11 | useEffect(() => setIsChecked(checked), [checked]); 12 | useEffect(() => setIsIndeterminate(indeterminate), [indeterminate]); 13 | useEffect(() => { 14 | if (isIndeterminate) return; 15 | onChange(name, value, isChecked); 16 | }, [isChecked, isIndeterminate, name, value, onChange]); 17 | 18 | return ( 19 | <> 20 | 64 | 65 | ); 66 | }; 67 | 68 | export default Checkbox; 69 | -------------------------------------------------------------------------------- /src/components/Accordion.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from "react"; 2 | 3 | const AccordionItem = ({ accordionItem }) => { 4 | const [isItemOpen, setIsItemOpen] = useState(false); 5 | const bodyRef = useRef(); 6 | const toggleAccordionItem = () => { 7 | setIsItemOpen(!isItemOpen); 8 | }; 9 | 10 | return ( 11 |
    12 | 21 | 22 |
    26 |
    27 | {accordionItem.body} 28 |
    29 |
    30 |
    31 | ); 32 | }; 33 | 34 | const Accordion = () => { 35 | const accordionContent = [ 36 | { 37 | header: "Accordion item #1", 38 | body: "Lorem ipsum dolor sit amet consectetur adipisicing elit. Distinctio in a soluta, quos ex architecto officia voluptates atque dolores saepe enim molestias repellat inventore maxime sit. Unde est minus culpa, nisi perspiciatis quasi delectus. Nam ex molestiae animi provident natus voluptate tempora quam, suscipit doloremque quisquam consequuntur esse blanditiis aliquid ea a id ad expedita cumque eos ab distinctio beatae aspernatur! Aliquid, in. Velit hic ad libero culpa iure perspiciatis minima earum obcaecati ullam necessitatibus sunt nemo corporis qui aut id maiores dicta eveniet, assumenda vitae expedita distinctio rem sint? Maxime modi inventore ipsa neque reiciendis facere? Saepe, eum odio!", 39 | }, 40 | { 41 | header: "Accordion item #2", 42 | body: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis consequatur accusamus nesciunt eius molestiae dicta distinctio obcaecati praesentium perspiciatis inventore.", 43 | }, 44 | { 45 | header: "Accordion item #3", 46 | body: "Lorem ipsum dolor sit amet consectetur adipisicing elit. Itaque maxime suscipit, ut aut magnam quo veritatis. Pariatur iste, necessitatibus optio vero hic magni quis similique nisi. Debitis, facilis iste! Sequi, quis quod maiores doloremque corrupti totam aliquam iure! Voluptates excepturi, provident dolore aliquam cumque obcaecati eveniet aspernatur itaque blanditiis rem.", 47 | }, 48 | ]; 49 | 50 | return ( 51 | <> 52 |
    53 | {accordionContent.map((accordionItem, index) => ( 54 | 55 | ))} 56 |
    57 | 58 | ); 59 | }; 60 | 61 | export default Accordion; 62 | -------------------------------------------------------------------------------- /src/components/Toast/Toast.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from 'react' 2 | 3 | const Toast = ({ type, msg, position, onClose, hasCloseBtn, autoClose, autoCloseTimeout, }) => { 4 | const getBgColor = () => { 5 | switch (type) { 6 | case "success": return "bg-green-500"; 7 | case "info": return "bg-blue-500"; 8 | case "warning": return "bg-yellow-400"; 9 | case "error": return "bg-red-500"; 10 | default: return "bg-green-500"; 11 | } 12 | } 13 | 14 | const getTag = () => { 15 | switch (type) { 16 | case "success": return "Success"; 17 | case "info": return "Info"; 18 | case "warning": return "Warning"; 19 | case "error": return "Error"; 20 | default: return "Success"; 21 | } 22 | } 23 | 24 | const getTagColor = () => { 25 | switch (type) { 26 | case "success": return "text-green-500"; 27 | case "info": return "text-blue-500"; 28 | case "warning": return "text-yellow-400"; 29 | case "error": return "text-red-500"; 30 | default: return "text-green-500"; 31 | } 32 | } 33 | 34 | const getIcon = () => { 35 | switch (type) { 36 | case "success": return "fa-circle-check"; 37 | case "info": return "fa-circle-info"; 38 | case "warning": return "fa-triangle-exclamation"; 39 | case "error": return "fa-circle-xmark"; 40 | default: return "fa-circle-check"; 41 | } 42 | } 43 | 44 | const getAnimationClasses = () => { 45 | if (show) return "opacity-1 translate-x-0"; 46 | if (position.endsWith("right")) return "opacity-0 translate-x-[100px]"; 47 | return "opacity-0 -translate-x-[100px]"; 48 | } 49 | 50 | const [show, setShow] = useState(false); 51 | const toastRef = useRef(); 52 | 53 | useEffect(() => { 54 | requestAnimationFrame(() => setShow(true)); 55 | if (autoClose) window.setTimeout(removeToast, autoCloseTimeout); 56 | }, []); 57 | 58 | const removeToast = () => { 59 | setShow(false); 60 | toastRef.current.addEventListener("transitionend", onClose); 61 | } 62 | 63 | 64 | return ( 65 | <> 66 |
    67 |
    68 | 69 |
    70 | 71 |
    72 | {getTag()} 73 |

    74 | {msg} 75 |

    76 | {hasCloseBtn && ( 77 | 78 | )} 79 |
    80 |
    81 | 82 | ) 83 | } 84 | 85 | export default Toast -------------------------------------------------------------------------------- /src/components/MegaMenu.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | 3 | const items = [ 4 | { 5 | key: "mathematics", 6 | title: "Mathematics", 7 | subItems: ["Algebra", "Arithmetic", "Calculus", "Differential Equations", "Discrete Math", "Graph", "Linear Algebra", "Number Theory", "Probability", "statistics", "Trigonometry", "Numerical Analysis"], 8 | }, 9 | { 10 | key: "programming", 11 | title: "Programming", 12 | subItems: [".NET", "C++", "HTML / CSS", "Go", "Java", "Javascript", "Programming", "Python", "Ruby", "Rust", "Swift", "Typescript", "Website Design"], 13 | }, 14 | { 15 | key: "science", 16 | title: "Science", 17 | subItems: ["Agriculture", "Anatomy", "Applied Physics", "Astrobiology", "Astronomy", "Astrophysics", "Biochemistry", "Biology", "Botany", "Chemistry", "Earth and Space Exploration", "Ecology", "Environmental Science", "Geology", "Microbiology", "Physics", "Science", "Zoology"] 18 | }, 19 | { 20 | key: "engineering", 21 | title: "Engineering", 22 | subItems: ["Aeronautical Management", "Aerospace Engineering", "Chemical Enginnering", "Civil Engineering", "Computer Engineering", "Environmental Engineering", "Information Technology", "Mathematics and Computing", "Mechanical Engineering", "Software Engineering",] 23 | }, 24 | 25 | ]; 26 | 27 | const MegaMenu = () => { 28 | 29 | const [activeMenu, setActiveMenu] = useState(false); 30 | const [activeItem, setActiveItem] = useState(null); 31 | 32 | return ( 33 | <> 34 |
    35 |
    36 | 40 | 41 |
    42 |
      43 | {items.map(item => ( 44 |
    • setActiveItem(item.key)} className={`flex justify-between w-52 px-4 py-1 hover:text-blue-500 cursor-pointer ${activeItem == item.key ? "bg-gray-200" : ""}`}> 45 | {item.title} 46 | 47 |
    • 48 | ))} 49 |
    50 | 51 |
      52 | {items.find(item => item.key == activeItem)?.subItems?.map(subItem => ( 53 |
    • {subItem}
    • 54 | ))} 55 |
    56 |
    57 |
    58 |
    59 | 60 | ) 61 | } 62 | 63 | export default MegaMenu -------------------------------------------------------------------------------- /src/components/Carousel.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | 3 | const Carousel = ({ carouselImages }) => { 4 | 5 | const [carouselItem, setCarouselItem] = useState(0); 6 | const numCarouselItems = carouselImages.length; 7 | const changeToPrevious = () => { 8 | setCarouselItem(Math.max(carouselItem - 1, 0)); 9 | } 10 | const changeToNext = () => { 11 | setCarouselItem(Math.min(carouselItem + 1, numCarouselItems - 1)); 12 | } 13 | const changeToGivenItem = (i) => { 14 | setCarouselItem(i); 15 | } 16 | 17 | const indicatorClasses = (i) => ( 18 | `w-8 h-1.5 transition duration-500 ${carouselItem == i ? "bg-white" : "bg-gray-500 scale-75"}` 19 | ) 20 | const imagesContainerStyle = { width: `${numCarouselItems}00%` }; 21 | const imgContainerstyle = (index) => ( 22 | index == 0 ? { 23 | width: `${100 / numCarouselItems}%`, 24 | marginLeft: `-${carouselItem * 100 / numCarouselItems}%` 25 | } : { 26 | width: `${100 / numCarouselItems}%` 27 | } 28 | ); 29 | 30 | useEffect(() => { 31 | const id = window.setInterval(() => { setCarouselItem((carouselItem) => (carouselItem + 1) % numCarouselItems) }, 5000); 32 | return () => window.clearInterval(id); 33 | }, []) 34 | 35 | 36 | ; 37 | 38 | return ( 39 | <> 40 |
    41 | 42 | 45 | 48 | 49 |
    50 | {carouselImages.map((_, i) => ( 51 | 52 | ))} 53 |
    54 | 55 |
    56 | {carouselImages.map((carouselImg, i) => ( 57 |
    58 | ... 63 |
    64 |
    {carouselImg.label}
    65 |

    {carouselImg.text}

    66 |
    67 |
    68 | ))} 69 |
    70 | 71 |
    72 | 73 | ) 74 | } 75 | 76 | export default Carousel -------------------------------------------------------------------------------- /src/components/MultiselectMenu.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from 'react' 2 | 3 | const MultiselectMenu = ({ title, name, onChange, list, isDismissible = true }) => { 4 | 5 | const [open, setOpen] = useState(false); 6 | const [selected, setSelected] = useState([]); 7 | const dropdownRef = useRef(); 8 | 9 | useEffect(() => { 10 | if (!isDismissible) return; 11 | document.addEventListener("click", checkAndHideDropdown); 12 | return () => document.removeEventListener("click", checkAndHideDropdown); 13 | }, [open]); 14 | 15 | 16 | const toggleDropdownMenu = () => { 17 | setOpen(open => !open); 18 | } 19 | 20 | const handleSelect = (option) => { 21 | if (selected.includes(option)) { 22 | setSelected(prevSel => prevSel.filter(selOption => selOption !== option)); 23 | } 24 | else { 25 | setSelected(prevSel => [...prevSel, option]); 26 | } 27 | } 28 | 29 | useEffect(() => { 30 | onChange(selected.map(option => option.value), name); 31 | }, [selected]); 32 | 33 | 34 | const checkAndHideDropdown = e => { 35 | if (dropdownRef.current.contains(e.target)) return; 36 | setOpen(false); 37 | } 38 | 39 | const dropdownButtonClasses = `flex items-center justify-between w-full min-w-[150px] bg-white border px-3 py-2.5 text-sm rounded-md shadow-sm whitespace-nowrap hover:shadow-md focus:shadow-md focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 active:shadow-md transition duration-150 ease-in-out sm:text-base`; 40 | const dropdownArrowClasses = `ml-2 transition ${open ? "-rotate-180" : ""} `; 41 | const dropdownMenuClasses = `${open ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none -translate-y-2"} 42 | m-0 mt-1 absolute min-w-full max-h-[300px] overflow-auto z-50 py-2 bg-white border-[1px] border-gray-100 transition-all duration-300 cursor-pointer rounded-lg shadow-lg`; 43 | const itemClasses = (option) => ( 44 | `${selected.includes(option) ? "text-indigo-600 font-bold" : "text-gray-500"} bg-white py-2 px-3 block w-full whitespace-nowrap hover:bg-indigo-500 hover:text-white group` 45 | ); 46 | 47 | 48 | return ( 49 | <> 50 |
    51 | 55 | 56 |
    57 |
      58 | {list.map((option, i) => ( 59 |
    • handleSelect(option)}> 60 | 61 | {option.label} 62 |
    • 63 | ))} 64 |
    65 |
    66 |
    67 | 68 | ) 69 | } 70 | 71 | export default MultiselectMenu -------------------------------------------------------------------------------- /src/components/Popover.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from 'react' 2 | import ReactDom from 'react-dom'; 3 | 4 | const Portal = ({ children }) => { 5 | return ReactDom.createPortal(children, document.body); 6 | } 7 | 8 | // @param space: it is the dist between the tip and the element 9 | // @param children: It is expected to have only 2 children: first for main element and second for popover element 10 | const Popover = ({ children, position = "bottom", space = 5 }) => { 11 | 12 | const [isOpen, setIsOpen] = useState(false); 13 | const popoverRef = useRef(); 14 | const elementRef = useRef(); 15 | 16 | const handleClick = () => { 17 | setIsOpen(isOpen => !isOpen); 18 | const { x, y } = getPoint(elementRef.current, popoverRef.current, position, space); 19 | popoverRef.current.style.left = `${x}px`; 20 | popoverRef.current.style.top = `${y}px`; 21 | } 22 | 23 | const getPoint = (element, popover, position, space) => { 24 | const eleRect = element.getBoundingClientRect(); 25 | const pt = { x: 0, y: 0 }; 26 | switch (position) { 27 | case "bottom": { 28 | pt.x = eleRect.left + (element.offsetWidth - popover.offsetWidth) / 2; 29 | pt.y = eleRect.bottom + (space + 10); 30 | break; 31 | } 32 | case "left": { 33 | pt.x = eleRect.left - (popover.offsetWidth + (space + 10)); 34 | pt.y = eleRect.top + (element.offsetHeight - popover.offsetHeight) / 2; 35 | break; 36 | } 37 | case "right": { 38 | pt.x = eleRect.right + (space + 10); 39 | pt.y = eleRect.top + (element.offsetHeight - popover.offsetHeight) / 2; 40 | break; 41 | } 42 | case "top": { 43 | pt.x = eleRect.left + (element.offsetWidth - popover.offsetWidth) / 2; 44 | pt.y = eleRect.top - (popover.offsetHeight + (space + 10)); 45 | break; 46 | } 47 | } 48 | return pt; 49 | } 50 | 51 | const popoverClasses = `fixed z-50 border ${isOpen ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none"} transition 52 | ${position == "top" && " after:absolute after:content-[''] after:left-1/2 after:top-full after:-translate-x-1/2 after:border-[10px] after:border-transparent after:border-t-white"} 53 | ${position == "bottom" && " after:absolute after:content-[''] after:left-1/2 after:bottom-full after:-translate-x-1/2 after:border-[10px] after:border-transparent after:border-b-white"} 54 | ${position == "left" && " after:absolute after:content-[''] after:top-1/2 after:left-full after:-translate-y-1/2 after:border-[10px] after:border-transparent after:border-l-white"} 55 | ${position == "right" && " after:absolute after:content-[''] after:top-1/2 after:right-full after:-translate-y-1/2 after:border-[10px] after:border-transparent after:border-r-white"} 56 | `; 57 | 58 | return ( 59 | <> 60 | {React.cloneElement(children[0], { 61 | onClick: handleClick, 62 | ref: elementRef 63 | })} 64 | 65 | 66 |
    {children[1]}
    67 |
    68 | 69 | ) 70 | } 71 | 72 | export default Popover -------------------------------------------------------------------------------- /src/components/AnimatedMultilevelDropdown.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from "react"; 2 | 3 | const AnimatedMultilevelDropdown = () => { 4 | const [open, setOpen] = useState(false); 5 | const [right, setRight] = useState(false); 6 | const ref1 = useRef(); 7 | const ref2 = useRef(); 8 | const [height, setHeight] = useState(0); 9 | 10 | useEffect(() => { 11 | if (!right) { 12 | setHeight(ref1.current.offsetHeight); 13 | } else { 14 | setHeight(ref2.current.offsetHeight); 15 | } 16 | }, [right]); 17 | 18 | return ( 19 | <> 20 |
    21 |
    setOpen(o => !o)} 23 | className="cursor-pointer w-full bg-gray-800 py-1.5 px-2.5 rounded-[4px] hover:bg-gray-900 active:bg-gray-900 transition" 24 | > 25 |
    Select option
    26 |
    27 | 28 |
    38 |
    39 |
    A1
    40 |
    setRight(true)} 43 | > 44 | A2 45 | 46 | 47 | 48 |
    49 |
    A3
    50 |
    51 | 52 |
    53 |
    setRight(false)}> 54 | 55 | 56 | 57 | Go back 58 |
    59 |
    B1
    60 |
    B2
    61 |
    B2
    62 |
    B2
    63 |
    B2
    64 |
    B2
    65 |
    B2
    66 |
    B2
    67 |
    68 |
    69 |
    70 | 71 | ); 72 | }; 73 | 74 | export default AnimatedMultilevelDropdown; 75 | -------------------------------------------------------------------------------- /src/components/Tooltip/utils.jsx: -------------------------------------------------------------------------------- 1 | const getTooltipPoint = (element, tooltip, position, space, arrowLength = 0) => { 2 | const eleRect = element.getBoundingClientRect(); 3 | const pt = { x: 0, y: 0 }; 4 | switch (position) { 5 | case "bottom": { 6 | pt.x = eleRect.left + (element.offsetWidth - tooltip.offsetWidth) / 2; 7 | pt.y = eleRect.bottom + space + arrowLength; 8 | if (pt.x + tooltip.offsetWidth >= window.innerWidth) { 9 | pt.x -= pt.x + tooltip.offsetWidth - window.innerWidth + 5; 10 | } else if (pt.x < 0) { 11 | pt.x = 5; 12 | } 13 | break; 14 | } 15 | case "left": { 16 | pt.x = eleRect.left - space - arrowLength - tooltip.offsetWidth; 17 | pt.y = eleRect.top + (element.offsetHeight - tooltip.offsetHeight) / 2; 18 | if (pt.y + tooltip.offsetHeight >= window.innerHeight) { 19 | pt.y -= pt.y + tooltip.offsetHeight - window.innerHeight + 5; 20 | } else if (pt.y < 0) { 21 | pt.y = 5; 22 | } 23 | break; 24 | } 25 | case "right": { 26 | pt.x = eleRect.right + (space + arrowLength); 27 | pt.y = eleRect.top + (element.offsetHeight - tooltip.offsetHeight) / 2; 28 | if (pt.y + tooltip.offsetHeight >= window.innerHeight) { 29 | pt.y -= pt.y + tooltip.offsetHeight - window.innerHeight + 5; 30 | } else if (pt.y < 0) { 31 | pt.y = 5; 32 | } 33 | break; 34 | } 35 | case "top": { 36 | pt.x = eleRect.left + (element.offsetWidth - tooltip.offsetWidth) / 2; 37 | pt.y = eleRect.top - space - arrowLength - tooltip.offsetHeight; 38 | if (pt.x + tooltip.offsetWidth >= window.innerWidth) { 39 | pt.x -= pt.x + tooltip.offsetWidth - window.innerWidth + 5; 40 | } else if (pt.x < 0) { 41 | pt.x = 5; 42 | } 43 | break; 44 | } 45 | default: { 46 | break; 47 | } 48 | } 49 | return pt; 50 | }; 51 | 52 | const getArrowBoxPoint = (element, tooltip, position, space, arrowLength = 0) => { 53 | const eleRect = element.getBoundingClientRect(); 54 | const pt = { x: 0, y: 0 }; 55 | switch (position) { 56 | case "bottom": { 57 | pt.x = eleRect.left + element.offsetWidth / 2; 58 | pt.y = eleRect.bottom + space - arrowLength; 59 | break; 60 | } 61 | case "left": { 62 | pt.x = eleRect.left - space - arrowLength; 63 | pt.y = eleRect.top + element.offsetHeight / 2; 64 | break; 65 | } 66 | case "right": { 67 | pt.x = eleRect.right + space - arrowLength; 68 | pt.y = eleRect.top + element.offsetHeight / 2; 69 | break; 70 | } 71 | case "top": { 72 | pt.x = eleRect.left + element.offsetWidth / 2; 73 | pt.y = eleRect.top - space - arrowLength; 74 | break; 75 | } 76 | default: { 77 | break; 78 | } 79 | } 80 | return pt; 81 | }; 82 | 83 | const getClosestAncestor = (element, selectorString) => { 84 | if (!document.documentElement.contains(element)) return null; 85 | do { 86 | if (element.matches(selectorString)) return element; 87 | element = element.parentElement; 88 | } while (element !== null); 89 | return null; 90 | }; 91 | 92 | export { getTooltipPoint, getArrowBoxPoint, getClosestAncestor }; 93 | -------------------------------------------------------------------------------- /src/pages/CheckboxDemo.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import Checkbox from '../components/Checkbox' 3 | 4 | const CheckboxDemo = () => { 5 | 6 | const [formData, setFormData] = useState({ fruits: [], colors: [], sendMeEmail: false }); 7 | 8 | const handleCheckboxChange = (name, value, checked) => { 9 | let arr = formData[name]; 10 | const isPresent = arr.includes(value);; 11 | checked && !isPresent && arr.push(value); 12 | !checked && isPresent && (arr = arr.filter(x => x !== value)); 13 | setFormData({ ...formData, [name]: arr }); 14 | } 15 | 16 | 17 | const seeIfAllColorsChecked = () => { 18 | return formData.colors.length == ["green", "blue", "red"].length; 19 | } 20 | const seeIfColorsIndeterminate = () => { 21 | return 0 < formData.colors.length && formData.colors.length < ["green", "blue", "red"].length; 22 | } 23 | const handleAllColorsSelectorCheckboxChange = (name, value, checked) => { 24 | if (checked) setFormData({ ...formData, colors: ["green", "blue", "red"] }); 25 | else setFormData({ ...formData, colors: [] }); 26 | } 27 | 28 | const handleSendMeEmailCheckboxChange = (name, value, checked) => { 29 | setFormData({ ...formData, sendMeEmail: checked }); 30 | } 31 | 32 | return ( 33 | <> 34 |

    Custom Checkboxes

    35 |
    36 |
    37 |

    Select fruit

    38 |
    39 | 40 | 41 | 42 |
    43 | 44 |
    45 | 46 |
    47 |

    Select colors

    48 | 49 |
    50 | 51 | 52 | 53 |
    54 | 55 |
    56 | 57 |
    58 |
    59 | 60 |
    61 | 62 |
    63 |
    64 | 65 | ) 66 | } 67 | 68 | export default CheckboxDemo -------------------------------------------------------------------------------- /src/pages/PopoverDemo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Popover from '../components/Popover' 3 | 4 | const PopoverDemo = () => { 5 | return ( 6 | <> 7 |

    Popover

    8 |
    9 | 10 | 11 | 14 |
    15 |

    Heading

    16 |

    Lorem ipsum dolor sit amet consectetur adipisicing elit. Eum tempora voluptate nam necessitatibus natus nesciunt incidunt molestias sequi autem. Quo!

    17 |
    18 |
    19 | 20 | 21 | 22 | 25 |
    26 |

    Heading

    27 |

    Lorem ipsum dolor sit amet consectetur adipisicing elit. Eum tempora voluptate nam necessitatibus natus nesciunt incidunt molestias sequi autem. Quo!

    28 |
    29 |
    30 | 31 | 32 | 33 | 36 |
    37 |

    Heading

    38 |

    Lorem ipsum dolor sit amet consectetur adipisicing elit. Eum tempora voluptate nam necessitatibus natus nesciunt incidunt molestias sequi autem. Quo!

    39 |
    40 |
    41 | 42 | 43 | 46 |
    47 |

    Heading

    48 |

    Lorem ipsum dolor sit amet consectetur adipisicing elit. Eum tempora voluptate nam necessitatibus natus nesciunt incidunt molestias sequi autem. Quo!

    49 |
    50 |
    51 | 52 |
    53 | 54 | ) 55 | } 56 | 57 | export default PopoverDemo -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Tailwind Components 2 | 3 | A react app used to demonstrate some common components using tailwind css without any external Components library. 4 | 5 | Note: 6 | 7 | - This project uses Vitejs as the front end build tool (i.e., module bundler). 8 | - The project was bootstrapped using the command `npm init vite` 9 | 10 | ## Table of Contents 11 | 12 | - [Components](#components) 13 | - [Tools and Technologies](#tools-and-technologies) 14 | - [Installation and setup](#installation-and-setup) 15 | - [Useful Links](#useful-links) 16 | - [Contact](#contact) 17 | 18 | ## Components 19 | 20 | - Accordion 21 | - Alerts 22 | - Animated Form 23 | - Animated Multilevel Dropdown 24 | - Autocomplete 25 | - Breadcrumbs 26 | - Carousel 27 | - Checkbox 28 | - Chips 29 | - Circular Menu 30 | - Circular Rotating List 31 | - Command Palette 32 | - Content Editable 33 | - Datepicker 34 | - Drawer 35 | - Dropdown 36 | - FAQ 37 | - Footer 38 | - Form 39 | - Image Hover effects 40 | - Image Uploader 41 | - List hover effects 42 | - Loader 43 | - Masonry 44 | - Mega Menu 45 | - Modal 46 | - Multiselect Menu 47 | - Multistep Form 48 | - Navbar 49 | - Notification Badges 50 | - Popconfirm 51 | - Popover 52 | - Pricing 53 | - Progress 54 | - Radio button 55 | - Rating 56 | - Resizable 57 | - Ribbon 58 | - Ripple 59 | - Scroll Indicator 60 | - Scrollspy 61 | - Scroll back to top button 62 | - Select Menu 63 | - Sidebar 64 | - Sidebar with Submenus 65 | - Slider 66 | - Sortable List 67 | - Speed Dial 68 | - Stats Section 69 | - Switch 70 | - Table 71 | - Tabs 72 | - Testimonials 73 | - Timeline 74 | - Toast 75 | - Tooltip 76 | - Tree View 77 | - Typewriter 78 | 79 | ## Tools and Technologies 80 | 81 | - HTML 82 | - CSS 83 | - Javascript 84 | - Tailwind CSS 85 | - Node.js 86 | - React 87 | 88 | ## Installation and Setup 89 | 90 | 1. Install dependencies 91 | ```sh 92 | npm install 93 | ``` 94 | 2. Start the application 95 | ```sh 96 | npm run dev 97 | ``` 98 | 3. Go to http://localhost:3000 99 | 100 | ## Useful Links 101 | 102 | - This project 103 | 104 | - Github Repo: https://github.com/aayush301/React-tailwind-components 105 | 106 | - References 107 | 108 | - Tailwind Elements: https://tailwind-elements.com/ 109 | - Tailwind Components: https://tailwindcomponents.com/ 110 | - Material UI: https://mui.com 111 | - Ant Design: https://ant.design/components/overview/ 112 | 113 | - Official Docs 114 | 115 | - Tailwind CSS docs: https://tailwindcss.com/ 116 | - Reactjs docs: https://reactjs.org/docs/getting-started.html 117 | - npmjs docs: https://docs.npmjs.com/ 118 | - Github docs: https://docs.github.com/en/get-started/quickstart/hello-world 119 | 120 | - Youtube tutorials 121 | 122 | - Tailwind CSS: https://youtu.be/lZp4salRFFc 123 | - React: https://youtu.be/EHTWMpD6S_0 124 | 125 | - Download links 126 | 127 | - Nodejs download: https://nodejs.org/ 128 | - VS Code download: https://code.visualstudio.com/ 129 | 130 | - Cheatsheets 131 | - Git cheatsheet: https://education.github.com/git-cheat-sheet-education.pdf 132 | - VS Code keyboard shortcuts: https://code.visualstudio.com/shortcuts/keyboard-shortcuts-windows.pdf 133 | - CSS Selectors Cheatsheet: https://frontend30.com/css-selectors-cheatsheet/ 134 | 135 | ## Contact 136 | 137 | For any queries or suggestions, contact at: 138 | 139 | - Email: aayush5521186@gmail.com 140 | - Linkedin: https://www.linkedin.com/in/aayush12/ 141 | -------------------------------------------------------------------------------- /src/components/DatePicker/MonthYearSelection.jsx: -------------------------------------------------------------------------------- 1 | import React, { createRef, useEffect, useState } from "react"; 2 | const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; 3 | 4 | const MonthYearSelection = ({ 5 | monthYearSelection, 6 | setMonthYearSelection, 7 | shownMonthObj, 8 | setShownMonthObj, 9 | setMonthYearSelectMode, 10 | selected, 11 | setSelected, 12 | fromYear, 13 | toYear, 14 | fromDate, 15 | toDate, 16 | mode, 17 | }) => { 18 | const [yearsList, setyearsList] = useState([]); 19 | const [refs, setRefs] = useState([]); 20 | 21 | useEffect(() => { 22 | const yearsList = []; 23 | let minYear = 1900, 24 | maxYear = 2100; 25 | if (fromYear || fromDate) minYear = fromYear || fromDate.getFullYear(); 26 | if (toYear || toDate) maxYear = toYear || toDate.getFullYear(); 27 | for (let i = minYear; i <= maxYear; i++) yearsList.push(i); 28 | setyearsList(yearsList); 29 | 30 | const refs = yearsList.reduce((acc, year) => { 31 | acc[year] = createRef(); 32 | return acc; 33 | }, {}); 34 | setRefs(refs); 35 | }, [fromDate, fromYear, toYear, toDate]); 36 | 37 | useEffect(() => { 38 | refs[shownMonthObj.getFullYear()]?.current.scrollIntoView({ inline: "center" }); 39 | }, [refs, shownMonthObj]); 40 | 41 | const handleOKMonthYearSelection = () => { 42 | setMonthYearSelectMode(false); 43 | setShownMonthObj(new Date(monthYearSelection.year, monthYearSelection.month)); 44 | if (mode === "single") { 45 | setSelected(new Date(monthYearSelection.year, monthYearSelection.month, selected.getDate())); 46 | } 47 | }; 48 | 49 | return ( 50 | <> 51 |
    52 |
    53 | {yearsList.map(year => ( 54 |
    setMonthYearSelection({ ...monthYearSelection, year })} 61 | > 62 | {year} 63 |
    64 | ))} 65 |
    66 |
    67 | {months.map((month, i) => ( 68 |
    setMonthYearSelection({ ...monthYearSelection, month: i })} 74 | > 75 | {month} 76 |
    77 | ))} 78 |
    79 | 80 |
    81 | 87 | 93 |
    94 |
    95 | 96 | ); 97 | }; 98 | 99 | export default MonthYearSelection; 100 | -------------------------------------------------------------------------------- /src/components/Alert.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Success = ({ msg, onClose }) => { 4 | return ( 5 | <> 6 |
    7 |
    8 | 9 |
    10 | 11 |
    12 | Success 13 |

    14 | {msg} 15 |

    16 | 17 |
    18 |
    19 | 20 | ) 21 | } 22 | 23 | const Info = ({ msg, onClose }) => { 24 | return ( 25 | <> 26 |
    27 |
    28 | 29 |
    30 | 31 |
    32 | Info 33 |

    34 | {msg} 35 |

    36 | 37 |
    38 |
    39 | 40 | ) 41 | } 42 | 43 | const Warning = ({ msg, onClose }) => { 44 | return ( 45 | <> 46 |
    47 |
    48 | 49 |
    50 | 51 |
    52 | Warning 53 |

    54 | {msg} 55 |

    56 | 57 |
    58 |
    59 | 60 | ) 61 | } 62 | 63 | const Error = ({ msg, onClose }) => { 64 | return ( 65 | <> 66 |
    67 |
    68 | 69 |
    70 | 71 |
    72 | Error 73 |

    74 | {msg} 75 |

    76 | 77 |
    78 |
    79 | 80 | ) 81 | } 82 | 83 | 84 | 85 | const Alert = ({ type, msg = "", onClose }) => { 86 | switch (type) { 87 | case "success": return 88 | case "info": return 89 | case "warning": return 90 | case "error": return 91 | default: return 92 | } 93 | } 94 | 95 | export default Alert -------------------------------------------------------------------------------- /src/components/Form.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | 3 | const isValidEmail = (email) => { 4 | return String(email) 5 | .toLowerCase() 6 | .match( 7 | /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ 8 | ); 9 | }; 10 | const validateData = ({ username, email, password }) => { 11 | const errorObj = {}; 12 | 13 | if (!username) errorObj.username = "Please provide the username"; 14 | 15 | if (!email) { 16 | errorObj.email = "Please provide the email"; 17 | } else if (!isValidEmail(email)) { 18 | errorObj.email = "Please provide a valid email address.."; 19 | } 20 | 21 | if (!password) { 22 | errorObj.password = "Please provide the password"; 23 | } else if (password.length < 4) { 24 | errorObj.password = "Password length should be atleast 4"; 25 | } 26 | return errorObj; 27 | } 28 | 29 | 30 | 31 | const Form = () => { 32 | 33 | const [formData, setFormData] = useState({}); 34 | const [formErrors, setFormErrors] = useState({}); 35 | const handleChange = e => { 36 | setFormData({ ...formData, [e.target.name]: e.target.value }); 37 | } 38 | 39 | const handleSubmit = e => { 40 | e.preventDefault(); 41 | const errorObj = validateData(formData); 42 | setFormErrors(errorObj); 43 | if (Object.keys(errorObj).length == 0) alert("Successful"); 44 | } 45 | 46 | const inputClasses = (field) => ( 47 | `block w-full sm:text-sm px-3 py-2 bg-white border border-slate-300 shadow-sm rounded-md 48 | placeholder-slate-400 49 | focus:outline-none focus:border-sky-500 focus:ring-sky-500 focus:ring-1 50 | disabled:bg-slate-50 disabled:text-slate-500 disabled:border-slate-200 disabled:shadow-none 51 | ${formErrors[field] ? "border-pink-500 text-pink-600 focus:border-pink-500 focus:ring-pink-500" : ""}` 52 | ); 53 | const fieldErrorClasses = (field) => ( 54 | `mt-2 ${formErrors[field] ? "visible" : "invisible"} text-pink-600 text-sm` 55 | ) 56 | 57 | return ( 58 | <> 59 |
    60 | 61 |
    62 | 63 | 64 |

    {formErrors.username}

    65 |
    66 | 67 |
    68 | 69 | 70 |

    {formErrors.email}

    71 |
    72 | 73 |
    74 | 75 | 76 |

    {formErrors.password}

    77 |
    78 | 79 |
    80 | 83 |
    84 | 85 |
    86 | 87 | ) 88 | } 89 | 90 | export default Form -------------------------------------------------------------------------------- /src/components/Testimonials/Testimonials1.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | 3 | // UI Reference: https://dribbble.com/shots/3934425-Testimonial-Widget-UI-Animation 4 | const Testimonials1 = ({ data }) => { 5 | const [activeItem, setActiveItem] = useState(0); 6 | 7 | useEffect(() => { 8 | const intervalId = window.setInterval(() => { 9 | setActiveItem(id => (id + 1) % data.length); 10 | }, 5000); 11 | 12 | return () => { 13 | window.clearInterval(intervalId); 14 | }; 15 | }, [data]); 16 | 17 | return ( 18 | <> 19 |
    20 | {/* Title */} 21 |

    22 | Some words from our happy clients 23 |

    24 | 25 | {/* Subtitle */} 26 |
    27 | Our Lorem ipsum dolor, sit amet consectetur adipisicing elit. Qui quis minus facere maxime 28 | cupiditate dolores! 29 |
    30 | 31 | {/* Some space */} 32 |
    33 | 34 | {/* Quote container along with person details */} 35 |
    36 |
    37 |
    {data[activeItem].personName}
    38 |
    {data[activeItem].profile}
    39 |
    40 |
    41 | 42 |
    43 |
    {data[activeItem].quote}
    44 |
    45 | 46 | {/* Person image */} 47 |
    48 | profile 49 |
    50 | 51 | {/* Left button */} 52 | 59 | 60 | {/* Right button */} 61 | 68 |
    69 | 70 | {/* Item Indicator */} 71 |
    72 | {data.map((item, idx) => ( 73 |
    setActiveItem(idx)} 79 | title={`Move to ${idx + 1}`} 80 | /> 81 | ))} 82 |
    83 |
    84 | 85 | ); 86 | }; 87 | 88 | export default Testimonials1; 89 | -------------------------------------------------------------------------------- /src/components/ImageUploader.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | 3 | /** 4 | * 5 | * @param {number} maxFileSize maximum file size allowed in bytes 6 | * @returns 7 | */ 8 | const ImageUploader = ({ id = "image-input", maxFileSize }) => { 9 | const [images, setImages] = useState([]); 10 | const [imageUrls, setImageUrls] = useState([]); 11 | const [dragover, setDragover] = useState(false); 12 | 13 | const getImageUrl = imageFile => { 14 | // Method-1 15 | return URL.createObjectURL(imageFile); 16 | 17 | // Method-2 18 | // const reader = new FileReader(); 19 | // reader.onload = e => { 20 | // return e.target.result; 21 | // }; 22 | // reader.readAsDataURL(inputFile); 23 | }; 24 | 25 | const handleInputFileChange = e => { 26 | if (!e.target.files) return; 27 | for (const file of e.target.files) { 28 | if (!file.type.startsWith("image")) continue; 29 | if (maxFileSize && file.size > maxFileSize) continue; 30 | const imageUrl = getImageUrl(file); 31 | setImageUrls(imageUrls => [...imageUrls, imageUrl]); 32 | setImages(images => [...images, file]); 33 | } 34 | }; 35 | 36 | const removeImage = idx => { 37 | setImages([...images.slice(0, idx), ...images.slice(idx + 1)]); 38 | setImageUrls([...imageUrls.slice(0, idx), ...imageUrls.slice(idx + 1)]); 39 | }; 40 | 41 | const onDragEnter = () => { 42 | setDragover(true); 43 | }; 44 | 45 | const onDragLeave = () => { 46 | setDragover(false); 47 | }; 48 | 49 | const onDrop = () => { 50 | setDragover(false); 51 | }; 52 | 53 | return ( 54 |
    55 |
    56 | 78 |
    79 | 80 |
    81 | {imageUrls.map((imageUrl, idx) => ( 82 |
    83 | 84 | Uploaded 85 | 86 |
    87 |
    {images[idx]?.name}
    88 |
    {(images[idx]?.size / 1024).toFixed(2)} KB
    89 |
    90 | 96 |
    97 | ))} 98 |
    99 |
    100 | ); 101 | }; 102 | 103 | export default ImageUploader; 104 | -------------------------------------------------------------------------------- /src/pages/ToastDemo.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { ToastList, useToast } from '../components/Toast'; 3 | 4 | const ToastDemo = () => { 5 | 6 | const [toastList, addToast, deleteToast] = useToast([]); 7 | const [formData, setFormData] = useState({ hasCloseBtn: true }); 8 | const handleChange = e => { 9 | if (e.target.type == "checkbox") { 10 | setFormData({ ...formData, [e.target.name]: e.target.checked }); 11 | } 12 | else { 13 | setFormData({ ...formData, [e.target.name]: e.target.value }); 14 | } 15 | }; 16 | 17 | const handleSubmit = e => { 18 | e.preventDefault(); 19 | addToast({ 20 | msg: formData.msg, 21 | type: formData.type, 22 | position: formData.position, 23 | hasCloseBtn: formData.hasCloseBtn, 24 | autoClose: formData.autoClose, 25 | autoCloseTimeout: formData.autoCloseTimeout, 26 | }); 27 | } 28 | 29 | return ( 30 | <> 31 | 32 |

    Toasts

    33 |
    34 |
    35 | 36 | 37 |
    38 | 39 |
    40 | 41 | 42 |
    43 | 44 |
    45 | 46 | 47 |
    48 |
    49 | 50 | 51 |
    52 | 53 |
    54 | 55 |
    56 | 57 | 58 |
    59 |
    60 | 61 | 62 |
    63 |
    64 | 65 | 66 |
    67 |
    68 | 69 | 70 |
    71 |
    72 | 73 |
    74 | 75 |
    76 | 77 | 78 |
    79 |
    80 | 81 | 82 |
    83 |
    84 | 85 | 86 |
    87 |
    88 | 89 | 90 |
    91 |
    92 | 93 | 94 | 95 | 96 | ) 97 | } 98 | 99 | export default ToastDemo -------------------------------------------------------------------------------- /src/components/Modal.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from "react"; 2 | import { useState } from "react"; 3 | import ReactDom from "react-dom"; 4 | 5 | const Portal = ({ children }) => { 6 | return ReactDom.createPortal(children, document.body); 7 | }; 8 | 9 | const Modal = ({ 10 | children, 11 | isOpen, 12 | onClose, 13 | isDismissible = true, 14 | showCloseIcon = true, 15 | toAnimate = true, 16 | animationEnter = "zoomIn", 17 | animationExit = "zoomOut", 18 | className = "", 19 | }) => { 20 | const modalRef = useRef(); 21 | const [mouseDownEv, setMouseDownEv] = useState(null); 22 | 23 | useEffect(() => { 24 | if (!isOpen || !isDismissible) return; 25 | const checkEscAndCloseModal = e => { 26 | if (e.key !== "Escape") return; 27 | onClose(); 28 | }; 29 | document.addEventListener("keydown", checkEscAndCloseModal); 30 | document.body.style.overflow = "hidden"; 31 | 32 | return () => { 33 | document.body.style.overflow = "auto"; 34 | document.removeEventListener("keydown", checkEscAndCloseModal); 35 | }; 36 | }, [isOpen, onClose, isDismissible]); 37 | 38 | const handleMouseDown = e => { 39 | setMouseDownEv({ screenX: e.screenX, screenY: e.screenY }); 40 | }; 41 | 42 | const checkOutsideAndCloseModal = e => { 43 | if (!isDismissible) return; 44 | if ( 45 | modalRef.current.contains(e.target) || 46 | Math.abs(mouseDownEv.screenX - e.screenX) > 15 || 47 | Math.abs(mouseDownEv.screenY - e.screenY) > 15 48 | ) 49 | return; 50 | onClose(); 51 | setMouseDownEv(null); 52 | }; 53 | 54 | const getEnterAnimation = animEnter => { 55 | return { 56 | slideInFromDown: "animate-[slideInFromDown_500ms_forwards]", 57 | slideInFromUp: "animate-[slideInFromUp_500ms_forwards]", 58 | slideInFromLeft: "animate-[slideInFromLeft_500ms_forwards]", 59 | slideInFromRight: "animate-[slideInFromRight_500ms_forwards]", 60 | zoomIn: "animate-[zoomIn_500ms_forwards]", 61 | }[animEnter]; 62 | }; 63 | 64 | const getExitAnimation = animExit => { 65 | return { 66 | slideOutToDown: "animate-[slideOutToDown_500ms_forwards]", 67 | slideOutToUp: "animate-[slideOutToUp_500ms_forwards]", 68 | slideOutToLeft: "animate-[slideOutToLeft_500ms_forwards]", 69 | slideOutToRight: "animate-[slideOutToRight_500ms_forwards]", 70 | zoomOut: "animate-[zoomOut_500ms_forwards]", 71 | }[animExit]; 72 | }; 73 | 74 | return ( 75 | <> 76 | 77 |
    84 |
    93 | {showCloseIcon && ( 94 |
    95 | 103 |
    104 | )} 105 |
    {children}
    106 |
    107 |
    108 |
    109 | 110 | ); 111 | }; 112 | 113 | export default Modal; 114 | -------------------------------------------------------------------------------- /src/components/Dropdown.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from 'react' 2 | import ReactDom from 'react-dom'; 3 | 4 | const classNames = (...arr) => { 5 | return arr.join(" "); 6 | } 7 | 8 | const Portal = ({ children }) => { 9 | return ReactDom.createPortal(children, document.body); 10 | } 11 | 12 | const Dropdown = ({ children, menu, placement = "bottom-left", space = 5, isDismissible = true, trigger = ["click"] }) => { 13 | 14 | const [isOpen, setIsOpen] = useState(false); 15 | const dropdownRef = useRef(); 16 | const elementRef = useRef(); 17 | 18 | useEffect(() => { 19 | if (!isDismissible || !isOpen) return; 20 | document.addEventListener("click", checkAndHideDropdown); 21 | return () => document.removeEventListener("click", checkAndHideDropdown); 22 | }, [isOpen]); 23 | 24 | const checkAndHideDropdown = e => { 25 | if (dropdownRef.current.contains(e.target) || elementRef.current.contains(e.target)) return; 26 | setIsOpen(false); 27 | } 28 | 29 | const handleMouseEnter = () => { 30 | if (!trigger.includes("hover")) return; 31 | setIsOpen(true); 32 | const { x, y } = getPoint(elementRef.current, dropdownRef.current, placement, space); 33 | dropdownRef.current.style.left = `${x}px`; 34 | dropdownRef.current.style.top = `${y}px`; 35 | } 36 | 37 | const handleMouseLeave = () => { 38 | if (!trigger.includes("hover")) return; 39 | setIsOpen(false); 40 | const { x, y } = getPoint(elementRef.current, dropdownRef.current, placement, space); 41 | dropdownRef.current.style.left = `${x}px`; 42 | dropdownRef.current.style.top = `${y}px`; 43 | } 44 | 45 | const handleClick = () => { 46 | if (!trigger.includes("click")) return; 47 | setIsOpen(isOpen => !isOpen); 48 | const { x, y } = getPoint(elementRef.current, dropdownRef.current, placement, space); 49 | dropdownRef.current.style.left = `${x}px`; 50 | dropdownRef.current.style.top = `${y}px`; 51 | } 52 | 53 | const getPoint = (element, dropdown, placement, space) => { 54 | const eleRect = element.getBoundingClientRect(); 55 | const pt = { x: 0, y: 0 }; 56 | switch (placement) { 57 | case "bottom-left": { 58 | pt.x = eleRect.left; 59 | pt.y = eleRect.bottom + space; 60 | break; 61 | } 62 | case "bottom-right": { 63 | pt.x = eleRect.right - dropdown.offsetWidth; 64 | pt.y = eleRect.bottom + space; 65 | break; 66 | } 67 | case "bottom-center": { 68 | pt.x = eleRect.left + (element.offsetWidth - dropdown.offsetWidth) / 2; 69 | pt.y = eleRect.bottom + space; 70 | break; 71 | } 72 | case "top-left": { 73 | pt.x = eleRect.left - dropdown.offsetWidth - space; 74 | pt.y = eleRect.top; 75 | break; 76 | } 77 | case "top-right": { 78 | pt.x = eleRect.right + space; 79 | pt.y = eleRect.top; 80 | break; 81 | } 82 | } 83 | return pt; 84 | } 85 | 86 | const getDropdownClasses = () => { 87 | return classNames( 88 | "fixed z-50 border shadow-xl rounded-md transition", 89 | isOpen ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none", 90 | !isOpen && placement == "bottom-left" ? "-translate-x-2 -translate-y-2" : "", 91 | !isOpen && placement == "bottom-right" ? "translate-x-2 -translate-y-2" : "", 92 | !isOpen && placement == "bottom-center" ? "-translate-y-2" : "", 93 | !isOpen && placement == "top-left" ? "translate-x-2" : "", 94 | !isOpen && placement == "top-right" ? "-translate-x-2" : "", 95 | ) 96 | } 97 | 98 | return ( 99 | <> 100 | {React.cloneElement(children, { 101 | onClick: handleClick, 102 | onMouseEnter: handleMouseEnter, 103 | onMouseLeave: handleMouseLeave, 104 | ref: elementRef, 105 | })} 106 | 107 | 108 |
    109 | {menu} 110 |
    111 |
    112 | 113 | ) 114 | } 115 | 116 | export default Dropdown 117 | -------------------------------------------------------------------------------- /src/pages/Home.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | const Home = () => { 5 | return ( 6 | <> 7 |

    React Tailwind Components

    8 |
      9 | {[ 10 | { path: "/accordion", label: "Accordion" }, 11 | { path: "/alerts", label: "Alerts" }, 12 | { path: "/animated-form", label: "Animated Form" }, 13 | { path: "/animated-multilevel-dropdown", label: "Animated Multilevel Dropdown" }, 14 | { path: "/autocomplete", label: "Autocomplete" }, 15 | { path: "/breadcrumbs", label: "Breadcrumbs" }, 16 | { path: "/carousel", label: "Carousel" }, 17 | { path: "/checkbox", label: "Checkbox" }, 18 | { path: "/chips", label: "Chips" }, 19 | { path: "/circular-menu", label: "Circular Menu" }, 20 | { path: "/circular-rotating-list", label: "Circular Rotating List" }, 21 | { path: "/command-palette", label: "Command Palette" }, 22 | { path: "/content-editable", label: "Content Editable" }, 23 | { path: "/datepicker", label: "Date Picker" }, 24 | { path: "/drawer", label: "Drawer" }, 25 | { path: "/dropdown", label: "Dropdown" }, 26 | { path: "/image-uploader", label: "Image Uploader" }, 27 | { path: "/faq", label: "FAQ" }, 28 | { path: "/footer", label: "Footer" }, 29 | { path: "/form", label: "Form" }, 30 | { path: "/image-hover", label: "Image Hover Effects" }, 31 | { path: "/list-hover", label: "List hover Effects" }, 32 | { path: "/loader", label: "Loader" }, 33 | { path: "/masonry", label: "Masonry" }, 34 | { path: "/mega-menu", label: "Mega menu" }, 35 | { path: "/modal", label: "Modal" }, 36 | { path: "/multiselect-menu", label: "Multiselect Menu" }, 37 | { path: "/multistepform", label: "Multistep form" }, 38 | { path: "/navbar", label: "Navbar" }, 39 | { path: "/notification-badge", label: "Notification Badges" }, 40 | { path: "/popconfirm", label: "Popconfirm" }, 41 | { path: "/popover", label: "Popover" }, 42 | { path: "/pricing", label: "Pricing" }, 43 | { path: "/progress", label: "Progress" }, 44 | { path: "/radio-btn", label: "Radio button" }, 45 | { path: "/rating", label: "Rating" }, 46 | { path: "/resizable", label: "Resizable" }, 47 | { path: "/ribbon", label: "Ribbon" }, 48 | { path: "/ripple", label: "Ripple" }, 49 | { path: "/scroll-animations", label: "Scroll Animations" }, 50 | { path: "/scroll-indicator", label: "Scroll Indicator" }, 51 | { path: "/scrollspy", label: "Scrollspy" }, 52 | { path: "/scroll-back-to-top-btn", label: "Scroll Back To Top Button" }, 53 | { path: "/select-menu", label: "Select Menu" }, 54 | { path: "/sidebar", label: "Sidebar" }, 55 | { path: "/sidebar-with-submenu", label: "Sidebar with sub-menu" }, 56 | { path: "/slider", label: "Slider" }, 57 | { path: "/sortable-list", label: "Sortable List" }, 58 | { path: "/speed-dial", label: "Speed Dial" }, 59 | { path: "/stats-section", label: "Stats Section" }, 60 | { path: "/switch", label: "Switch" }, 61 | { path: "/table", label: "Table" }, 62 | { path: "/tabs", label: "Tabs" }, 63 | { path: "/testimonials", label: "Testimonials" }, 64 | { path: "/timeline", label: "Timeline" }, 65 | { path: "/toast", label: "Toast" }, 66 | { path: "/tooltip", label: "Tooltip" }, 67 | { path: "/tree-view", label: "Tree View" }, 68 | { path: "/typewriter", label: "Typewriter" }, 69 | ].map(({ path, label }) => ( 70 | 71 | {label} 72 | 73 | ))} 74 |
    75 | 76 | ); 77 | }; 78 | 79 | export default Home; 80 | -------------------------------------------------------------------------------- /src/components/Pricing/PricingComparisonTable.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | // UI Reference: https://tailwindui.com/components/marketing/sections/pricing 4 | const data = [ 5 | { 6 | feature: "Tax Savings", 7 | compare: { 8 | Starter: { type: "boolean", value: true }, 9 | Scale: { type: "boolean", value: true }, 10 | Growth: { type: "boolean", value: true }, 11 | }, 12 | }, 13 | { 14 | feature: "Easy to use accounting", 15 | compare: { 16 | Starter: { type: "boolean", value: true }, 17 | Scale: { type: "boolean", value: true }, 18 | Growth: { type: "boolean", value: true }, 19 | }, 20 | }, 21 | { 22 | feature: "Multi-accounts", 23 | compare: { 24 | Starter: { value: "3 accounts" }, 25 | Scale: { value: "unlimited accounts" }, 26 | Growth: { value: "7 accounts" }, 27 | }, 28 | }, 29 | { 30 | feature: "Invoicing", 31 | compare: { 32 | Starter: { value: "3 invoices" }, 33 | Scale: { value: "unlimited invoices" }, 34 | Growth: { value: "7 invoices" }, 35 | }, 36 | }, 37 | { 38 | feature: "Exclusive offers", 39 | compare: { 40 | Starter: { type: "boolean", value: false }, 41 | Scale: { type: "boolean", value: true }, 42 | Growth: { type: "boolean", value: true }, 43 | }, 44 | }, 45 | { 46 | feature: "6 months free advisor", 47 | compare: { 48 | Starter: { type: "boolean", value: false }, 49 | Scale: { type: "boolean", value: true }, 50 | Growth: { type: "boolean", value: true }, 51 | }, 52 | }, 53 | { 54 | feature: "Mobile and web access", 55 | compare: { 56 | Starter: { type: "boolean", value: false }, 57 | Scale: { type: "boolean", value: true }, 58 | Growth: { type: "boolean", value: false }, 59 | }, 60 | }, 61 | ]; 62 | 63 | const PricingComparisonTable = () => { 64 | return ( 65 | <> 66 |
    67 |
    68 |
    Catered for business
    69 | 70 | {[ 71 | { title: "Starter", subtitle: "All your business finances, taken care of." }, 72 | { title: "Scale", subtitle: "The best financial services for your thriving business." }, 73 | { title: "Growth", subtitle: "Convenient features to take your business to the nest level." }, 74 | ].map(({ title, subtitle }, idx) => ( 75 |
    76 |

    {title}

    77 |
    {subtitle}
    78 |
    79 | ))} 80 |
    81 | 82 |
    83 | {data.map(({ feature, compare }, featureIdx) => ( 84 |
    88 |
    {feature}
    89 | 90 | {Object.keys(compare).map((pack, idx) => ( 91 |
    98 | <> 99 | {compare[pack].type === "boolean" ? ( 100 | <> 101 | {compare[pack].value ? ( 102 | 103 | 104 | 105 | ) : ( 106 | 107 | 108 | 109 | )} 110 | 111 | ) : ( 112 | <>{compare[pack].value} 113 | )} 114 | 115 |
    116 | ))} 117 |
    118 | ))} 119 |
    120 |
    121 | 122 | ); 123 | }; 124 | 125 | export default PricingComparisonTable; 126 | -------------------------------------------------------------------------------- /src/components/Tabs.jsx: -------------------------------------------------------------------------------- 1 | import React, { createRef, useEffect, useState } from "react"; 2 | import { useRef } from "react"; 3 | 4 | /** 5 | * @param {({tabName:string, tabTitle:string})[]} tabsData - Array of tabs. tabName: unique tab name to each tab 6 | */ 7 | const TabBar = ({ tabsData, activeTabName, onTabClick }) => { 8 | const tabGroupRef = useRef(); 9 | const sliderRef = useRef(); 10 | const [refs, setRefs] = useState([]); 11 | const [isAtLeftEnd, setIsAtLeftEnd] = useState(true); 12 | const [isAtRightEnd, setIsAtRightEnd] = useState(true); 13 | 14 | // Create ref for each tab 15 | useEffect(() => { 16 | const refs = tabsData.map(() => createRef()); 17 | setRefs(refs); 18 | }, [tabsData]); 19 | 20 | // Slide the slider when active tab changes 21 | useEffect(() => { 22 | const i = tabsData.findIndex(tab => tab.tabName === activeTabName); 23 | if (!sliderRef.current || !refs[i]?.current) return; 24 | const w = refs[i].current.offsetWidth; 25 | const left = refs[i].current.offsetLeft; 26 | sliderRef.current.style.left = `${left}px`; 27 | sliderRef.current.style.width = `${w}px`; 28 | }, [activeTabName, refs, tabsData]); 29 | 30 | // Scroll into view of active tab when active tab changes 31 | useEffect(() => { 32 | const i = tabsData.findIndex(tab => tab.tabName === activeTabName); 33 | if (!refs[i]?.current) return; 34 | refs[i]?.current.scrollIntoView({ block: "nearest", inline: "center" }); 35 | setIsAtLeftEnd(tabGroupRef.current?.scrollLeft === 0); 36 | setIsAtRightEnd( 37 | tabGroupRef.current?.scrollLeft + tabGroupRef.current?.clientWidth === tabGroupRef.current?.scrollWidth 38 | ); 39 | }, [refs, tabsData, activeTabName]); 40 | 41 | // Keep an eye on horizontal scrolling to detect whether we are at left end, at right end, or in between. 42 | const handleScroll = () => { 43 | setIsAtLeftEnd(tabGroupRef.current?.scrollLeft === 0); 44 | setIsAtRightEnd( 45 | tabGroupRef.current?.scrollLeft + tabGroupRef.current?.clientWidth === tabGroupRef.current?.scrollWidth 46 | ); 47 | }; 48 | 49 | return ( 50 |
    51 |
    56 | {tabsData.map((tab, i) => ( 57 | 67 | ))} 68 |
    69 |
    70 | 71 | {/* Show left blurred to indicate that we are not at extreme left, if so is the case */} 72 | {!isAtLeftEnd && ( 73 | 80 | 86 | 87 | )} 88 | 89 | {/* Show right blurred to indicate that we are not at extreme right, if so is the case */} 90 | {!isAtRightEnd && ( 91 | 98 | 104 | 105 | )} 106 |
    107 | ); 108 | }; 109 | 110 | export default TabBar; 111 | -------------------------------------------------------------------------------- /src/components/Popconfirm.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from 'react' 2 | import ReactDom from 'react-dom'; 3 | 4 | const Portal = ({ children }) => { 5 | return ReactDom.createPortal(children, document.body); 6 | } 7 | 8 | const Popconfirm = ({ children, title = "Are you sure?", okText = "Yes", cancelText = "No", onConfirm, onCancel, position = "bottom", isDismissible = true }) => { 9 | 10 | const [isOpen, setIsOpen] = useState(false); 11 | const popoverRef = useRef(); 12 | const elementRef = useRef(); 13 | const space = 5; 14 | 15 | useEffect(() => { 16 | if (!isDismissible || !isOpen) return; 17 | document.addEventListener("click", checkAndHidePopconfirm); 18 | return () => document.removeEventListener("click", checkAndHidePopconfirm); 19 | }, [isOpen]); 20 | 21 | const checkAndHidePopconfirm = e => { 22 | if (popoverRef.current?.contains(e.target) || elementRef.current?.contains(e.target)) return; 23 | setIsOpen(false); 24 | } 25 | 26 | const handleClickElement = e => { 27 | e.preventDefault(); 28 | setIsOpen(isOpen => !isOpen); 29 | const { x, y } = getPoint(elementRef.current, popoverRef.current, position, space); 30 | popoverRef.current.style.left = `${x}px`; 31 | popoverRef.current.style.top = `${y}px`; 32 | } 33 | 34 | const handleClickOk = e => { 35 | setIsOpen(false); 36 | onConfirm(); 37 | } 38 | 39 | const handleClickCancel = e => { 40 | e.preventDefault(); 41 | setIsOpen(false); 42 | onCancel(); 43 | } 44 | 45 | const getPoint = (element, popover, position, space) => { 46 | const eleRect = element.getBoundingClientRect(); 47 | const pt = { x: 0, y: 0 }; 48 | switch (position) { 49 | case "bottom": { 50 | pt.x = eleRect.left + (element.offsetWidth - popover.offsetWidth) / 2; 51 | pt.y = eleRect.bottom + (space + 10); 52 | break; 53 | } 54 | case "left": { 55 | pt.x = eleRect.left - (popover.offsetWidth + (space + 10)); 56 | pt.y = eleRect.top + (element.offsetHeight - popover.offsetHeight) / 2; 57 | break; 58 | } 59 | case "right": { 60 | pt.x = eleRect.right + (space + 10); 61 | pt.y = eleRect.top + (element.offsetHeight - popover.offsetHeight) / 2; 62 | break; 63 | } 64 | case "top": { 65 | pt.x = eleRect.left + (element.offsetWidth - popover.offsetWidth) / 2; 66 | pt.y = eleRect.top - (popover.offsetHeight + (space + 10)); 67 | break; 68 | } 69 | } 70 | return pt; 71 | } 72 | 73 | const posDepClasses = () => { 74 | switch (position) { 75 | case "top": return " after:absolute after:content-[''] after:left-1/2 after:top-full after:-translate-x-1/2 after:border-[10px] after:border-transparent after:border-t-white "; 76 | case "bottom": return " after:absolute after:content-[''] after:left-1/2 after:bottom-full after:-translate-x-1/2 after:border-[10px] after:border-transparent after:border-b-white "; 77 | case "left": return " after:absolute after:content-[''] after:top-1/2 after:left-full after:-translate-y-1/2 after:border-[10px] after:border-transparent after:border-l-white "; 78 | case "right": return " after:absolute after:content-[''] after:top-1/2 after:right-full after:-translate-y-1/2 after:border-[10px] after:border-transparent after:border-r-white "; 79 | } 80 | } 81 | const popoverClasses = `fixed z-50 bg-white border rounded-sm p-5 shadow-[0_3px_6px_-4px_#0000001f,0_6px_16px_#00000014,0_9px_28px_8px_#0000000d] transition ${isOpen ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none"} ${posDepClasses()}`; 82 | 83 | 84 | 85 | return ( 86 | <> 87 | {React.cloneElement(children, { 88 | onClick: handleClickElement, 89 | ref: elementRef 90 | })} 91 | 92 | 93 |
    94 |
    95 | 96 | {title} 97 | 98 |
    99 |
    100 | 101 | 102 |
    103 |
    104 |
    105 | 106 | ) 107 | } 108 | 109 | export default Popconfirm -------------------------------------------------------------------------------- /src/components/Table.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | 3 | const sort = (arr, key, order) => { 4 | if (order == true || order == "asc") { 5 | return [...arr].sort((row1, row2) => (row1[key] < (row2[key]) ? -1 : 1)); 6 | } 7 | return [...arr].sort((row1, row2) => (row1[key] > (row2[key]) ? -1 : 1)) 8 | } 9 | 10 | const Table = ({ data, keys }) => { 11 | const [tableData, setTableData] = useState(data); 12 | const [sortKey, setSortKey] = useState(null); 13 | const [sortOrder, setSortOrder] = useState(true); //true=>asc, false=>desc 14 | const [query, setQuery] = useState(""); 15 | const [loading, setLoading] = useState(false); 16 | 17 | 18 | useEffect(() => { 19 | if (query == "") setTableData(data); 20 | else setTableData(data.filter(row => Object.values(row).some(x => x.toString().toLowerCase().includes(query.toLowerCase())))); 21 | 22 | if (sortKey) setTableData(prevData => sort(prevData, sortKey, sortOrder)); 23 | }, [query]); 24 | 25 | 26 | const sortByKey = (key) => { 27 | if (sortKey === null) { 28 | setSortKey(key); 29 | setTableData(sort(tableData, key, sortOrder)); 30 | } 31 | else if (sortKey === key) { 32 | setSortOrder(!sortOrder); 33 | setTableData(sort(tableData, key, !sortOrder)); 34 | } 35 | else { 36 | setSortKey(key); 37 | setSortOrder(true); 38 | setTableData(sort(tableData, key, true)); 39 | } 40 | } 41 | 42 | 43 | const getArrowClasses = key => { 44 | if (key !== sortKey) return "opacity-0 group-hover:opacity-60"; 45 | if (!sortOrder) return "rotate-180"; 46 | return ""; 47 | } 48 | 49 | return ( 50 | <> 51 |
    52 | 53 | {loading && ( 54 |
    55 |
    Loading..
    56 |
    57 | )} 58 | 59 |
    60 |

    Table Title

    61 |

    Additional Description

    62 |
    63 | 64 |
    65 |
    66 | 67 | setQuery(e.target.value)} placeholder='Search the table..' /> 68 |
    69 | 72 | 75 | 78 | 82 |
    83 | 84 |
    85 | 86 | 87 | {keys.map(key => ( 88 | 96 | ))} 97 | 98 | 99 | 100 | 101 | {tableData.length > 0 && tableData.map((rowData, i) => ( 102 | 103 | {keys.map(key => ( 104 | 105 | ))} 106 | 107 | ))} 108 | 109 | {tableData.length == 0 && ( 110 | 111 | )} 112 | 113 |
    89 | 90 | {key} 91 | 92 | sortByKey(key)}> 93 | 94 | 95 |
    {rowData[key]}
    No results found
    114 | 115 |
    116 | 117 | ) 118 | } 119 | 120 | export default Table --------------------------------------------------------------------------------