├── src ├── vite-env.d.ts ├── components │ ├── shared │ │ ├── Divider │ │ │ ├── Divider.tsx │ │ │ └── divider.module.scss │ │ ├── InputField │ │ │ └── InputField.tsx │ │ └── Modal │ │ │ ├── modal.module.scss │ │ │ └── Modal.tsx │ ├── TopBar │ │ ├── topBar.module.scss │ │ ├── clock.module.scss │ │ ├── TopBar.tsx │ │ ├── Clock.tsx │ │ └── Search │ │ │ ├── search.module.scss │ │ │ └── Search.tsx │ ├── NotepadWidgetModal │ │ ├── notepadWidgetModal.module.scss │ │ └── NotepadWidgetModal.tsx │ ├── widgets │ │ ├── BookmarkWidget │ │ │ ├── bookmarkUtils.ts │ │ │ ├── bookmarkWidget.module.scss │ │ │ └── BookmarkWidget.tsx │ │ ├── AddNewWidgetBtn │ │ │ ├── addNewWidgetBtn.module.scss │ │ │ └── AddNewWidgetBtn.tsx │ │ └── NotepadWidget │ │ │ ├── notepadWidget.module.scss │ │ │ └── NotepadWidget.tsx │ ├── WidgetHolder │ │ ├── widgetHolder.module.scss │ │ ├── DraggableWidget.tsx │ │ └── WidgetHolder.tsx │ ├── AddEditWidgetModal │ │ ├── addEditWidgetModal.module.scss │ │ └── AddWidgetModal.tsx │ └── Footer │ │ ├── footer.module.scss │ │ └── Footer.tsx ├── main.tsx ├── app.module.scss ├── utils │ ├── notepadUtils.ts │ ├── clockUtils.ts │ └── widgetsUtils.ts ├── styles │ ├── mixins.scss │ └── variables.scss ├── App.tsx ├── index.scss ├── hooks │ └── useHover.tsx └── assets │ └── react.svg ├── .gitattributes ├── public ├── icons │ ├── icon128.png │ ├── icon16.png │ ├── icon32.png │ └── icon48.png ├── manifest.json └── vite.svg ├── .prettierrc ├── tsconfig.node.json ├── vite.config.ts ├── .gitignore ├── .eslintrc.cjs ├── .github └── workflows │ ├── stale.yml │ └── deploy.yml ├── tsconfig.json ├── index.html ├── LICENSE.md ├── package.json ├── README.md └── yarn.lock /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /public/icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashchaudhari008/minime/HEAD/public/icons/icon128.png -------------------------------------------------------------------------------- /public/icons/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashchaudhari008/minime/HEAD/public/icons/icon16.png -------------------------------------------------------------------------------- /public/icons/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashchaudhari008/minime/HEAD/public/icons/icon32.png -------------------------------------------------------------------------------- /public/icons/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashchaudhari008/minime/HEAD/public/icons/icon48.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": true, 4 | "semi": true, 5 | "printWidth": 90, 6 | "bracketSpacing": true, 7 | "arrowParens": "always" 8 | } 9 | -------------------------------------------------------------------------------- /src/components/shared/Divider/Divider.tsx: -------------------------------------------------------------------------------- 1 | import styles from './divider.module.scss'; 2 | 3 | const Divider = () => { 4 | return
5 | } 6 | 7 | export default Divider; 8 | -------------------------------------------------------------------------------- /src/components/shared/Divider/divider.module.scss: -------------------------------------------------------------------------------- 1 | @import "/src/styles/variables.scss"; 2 | 3 | .divider { 4 | width: 100%; 5 | border-bottom: 5px solid rgba($primaryColor, 0.05); 6 | border-radius: 10px; 7 | } 8 | -------------------------------------------------------------------------------- /src/components/TopBar/topBar.module.scss: -------------------------------------------------------------------------------- 1 | @import "/src/styles/variables.scss"; 2 | @import "/src/styles/mixins.scss"; 3 | 4 | .topBar { 5 | @include font-poppins; 6 | width: 100%; 7 | display: flex; 8 | justify-content: space-between; 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App.tsx"; 4 | import "./index.scss"; 5 | 6 | ReactDOM.createRoot(document.getElementById("root")!).render( 7 | 8 | 9 | 10 | ); 11 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import dotenv from "dotenv"; 3 | import react from "@vitejs/plugin-react"; 4 | 5 | dotenv.config(); 6 | 7 | // https://vitejs.dev/config/ 8 | export default defineConfig({ 9 | base: `${process.env.BASE_URL || "/"}`, 10 | plugins: [react()], 11 | }); 12 | -------------------------------------------------------------------------------- /src/components/NotepadWidgetModal/notepadWidgetModal.module.scss: -------------------------------------------------------------------------------- 1 | @import "/src/styles/variables.scss"; 2 | 3 | .notes { 4 | resize: none; 5 | height: 100%; 6 | padding: 16px; 7 | background-color: $modalTextColor; 8 | border-radius: $widgetBorderRadiusOnHover; 9 | border: none; 10 | &::placeholder { 11 | color: rgba(#000, 0.7); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "minime", 4 | "description": "Minimal Homepage", 5 | "version": "0.0.1", 6 | "chrome_url_overrides": { 7 | "newtab": "./index.html" 8 | }, 9 | "icons": { 10 | "16": "icons/icon16.png", 11 | "32": "icons/icon32.png", 12 | "48": "icons/icon48.png", 13 | "128": "icons/icon128.png" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/app.module.scss: -------------------------------------------------------------------------------- 1 | .app { 2 | height: 100%; 3 | display: flex; 4 | gap: 16px; 5 | padding: 16px; 6 | padding-bottom: 0px; 7 | flex-direction: column; 8 | 9 | * { 10 | transition: border-radius 0.2s ease; 11 | } 12 | } 13 | 14 | .header { 15 | display: flex; 16 | align-items: center; 17 | justify-content: space-between; 18 | width: 100%; 19 | gap: 3rem; 20 | } 21 | -------------------------------------------------------------------------------- /.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 | .env 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/recommended", 7 | "plugin:react-hooks/recommended", 8 | ], 9 | ignorePatterns: ["dist", ".eslintrc.cjs"], 10 | parser: "@typescript-eslint/parser", 11 | plugins: ["react-refresh"], 12 | rules: { 13 | "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/widgets/BookmarkWidget/bookmarkUtils.ts: -------------------------------------------------------------------------------- 1 | export const getFaviconLink = (bookmarkLink: string) => { 2 | try { 3 | const linkHost = new URL(bookmarkLink).hostname; 4 | return `https://icons.duckduckgo.com/ip3/${linkHost}.ico`; 5 | } catch { 6 | return "https://placehold.co/24/202124/FFF?text=>"; 7 | } 8 | }; 9 | 10 | export const getConfirmDialogMessage = (link: string, name?: string) => { 11 | return `${name && `${name}\n`}${link}\n\nAre you sure you want to delete bookmark?`; 12 | }; 13 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Unassign inactive contributors 2 | permissions: 3 | issues: write 4 | on: 5 | schedule: 6 | - cron: '0 1 * * 1' # Weekly at 1am UTC 7 | 8 | jobs: 9 | unassign-inactive: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: boundfoxstudios/action-unassign-contributor-after-days-of-inactivity@main 13 | with: 14 | token: ${{ secrets.GITHUB_TOKEN }} 15 | last-activity: 90 16 | message: 'Unassigned due to 3 months inactivity.' 17 | -------------------------------------------------------------------------------- /src/components/TopBar/clock.module.scss: -------------------------------------------------------------------------------- 1 | @import "/src/styles/variables.scss"; 2 | @import "/src/styles/mixins.scss"; 3 | 4 | .clock { 5 | @include font-poppins; 6 | 7 | color: $clockColor; 8 | 9 | font-size: 64px; 10 | font-weight: 400; 11 | width: min-content; 12 | 13 | padding-inline: 64px; 14 | border-radius: 64px; 15 | 16 | background-color: $clockTextColor; 17 | 18 | .meridiem { 19 | @include font-inter; 20 | 21 | margin-left: 4px; 22 | 23 | font-weight: 800; 24 | font-size: 16px; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/components/widgets/AddNewWidgetBtn/addNewWidgetBtn.module.scss: -------------------------------------------------------------------------------- 1 | @import "/src/styles/variables.scss"; 2 | .addNewWidget { 3 | margin-left: 8px; 4 | padding: 16px 18px; 5 | display: flex; 6 | align-items: center; 7 | justify-content: center; 8 | 9 | border-radius: 50%; 10 | background-color: $addNewWidgetButtonColor; 11 | 12 | &:hover { 13 | border-radius: $widgetBorderRadiusOnHover; 14 | } 15 | 16 | cursor: pointer; 17 | .icon { 18 | color: $addNewWidgetButtonIconColor; 19 | font-size: 24px; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/components/widgets/NotepadWidget/notepadWidget.module.scss: -------------------------------------------------------------------------------- 1 | @import "/src/styles/variables.scss"; 2 | @import "/src/styles/mixins.scss"; 3 | 4 | .notepadWidget { 5 | padding: 16px 18px; 6 | display: flex; 7 | align-items: center; 8 | justify-content: center; 9 | 10 | border-radius: 50%; 11 | border: 5px solid $addNewWidgetButtonColor; 12 | 13 | &:hover { 14 | border-radius: $widgetBorderRadiusOnHover; 15 | } 16 | 17 | cursor: pointer; 18 | .icon { 19 | color: $addNewWidgetButtonIconColor; 20 | font-size: 24px; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/components/WidgetHolder/widgetHolder.module.scss: -------------------------------------------------------------------------------- 1 | @import "/src/styles/mixins.scss"; 2 | @import "/src/styles/variables.scss"; 3 | 4 | .widgetHolderWrapper { 5 | padding: 8px; 6 | flex: 1; 7 | overflow: auto; 8 | 9 | h2 { 10 | font-weight: 400; 11 | color: rgba($primaryColor, 0.5); 12 | margin-left: 20px; 13 | } 14 | 15 | .widgetHolder { 16 | display: flex; 17 | align-items: center; 18 | gap: 16px; 19 | flex-wrap: wrap; 20 | 21 | .widget { 22 | flex: 1; 23 | max-width: 350px; 24 | } 25 | 26 | .fixedSizeWidget { 27 | flex: 0; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/components/widgets/AddNewWidgetBtn/AddNewWidgetBtn.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { faPlus } from "@fortawesome/free-solid-svg-icons"; 3 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 4 | import styles from "./addNewWidgetBtn.module.scss"; 5 | 6 | const AddNewWidgetBtn = ({ 7 | onClick: onClickHandler, 8 | }: React.HTMLProps) => { 9 | return ( 10 | 13 | ); 14 | }; 15 | 16 | export default AddNewWidgetBtn; 17 | -------------------------------------------------------------------------------- /src/components/TopBar/TopBar.tsx: -------------------------------------------------------------------------------- 1 | import type { Dispatch, SetStateAction } from "react"; 2 | import Search from "./Search/Search"; 3 | import Clock from "./Clock"; 4 | import styles from "./topBar.module.scss"; 5 | 6 | type TopBarProps = { 7 | searchValue: string; 8 | setSearchValue: Dispatch>; 9 | }; 10 | 11 | const TopBar = ({ searchValue, setSearchValue }: TopBarProps) => { 12 | return ( 13 | <> 14 |
15 | 16 | 17 |
18 | 19 | ); 20 | }; 21 | 22 | export default TopBar; 23 | -------------------------------------------------------------------------------- /src/components/widgets/NotepadWidget/NotepadWidget.tsx: -------------------------------------------------------------------------------- 1 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 2 | import styles from './notepadWidget.module.scss'; 3 | import { faNoteSticky } from '@fortawesome/free-regular-svg-icons'; 4 | 5 | const NotepadWidget = ({ 6 | onClick: onClickHandler, 7 | }: React.HTMLProps) => { 8 | return ( 9 | 12 | ) 13 | } 14 | 15 | export default NotepadWidget -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/notepadUtils.ts: -------------------------------------------------------------------------------- 1 | const ls_notepad_key = "minime/notepadContent"; 2 | 3 | export const getNotepadContent = () => { 4 | const localStorageNotepadContent = localStorage.getItem(ls_notepad_key); 5 | 6 | try { 7 | if (!localStorageNotepadContent) { 8 | console.log("No Notepad Content Found!"); 9 | return ""; 10 | } 11 | return localStorageNotepadContent; 12 | } catch (e) { 13 | console.error("Invalid Data:", e); 14 | return ""; 15 | } 16 | }; 17 | 18 | export const saveNotepadContent = (newContent: string) => { 19 | try { 20 | localStorage.setItem(ls_notepad_key, newContent); 21 | } catch (e) { 22 | console.error("Failed to save new content", e); 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/styles/mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin font-poppins { 2 | font-family: "Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 3 | Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 4 | } 5 | 6 | @mixin font-inter { 7 | font-family: "Inter", system-ui, Avenir, Helvetica, Arial, sans-serif; 8 | } 9 | 10 | @mixin respond-to($breakpoint) { 11 | @if $breakpoint == 'small' { 12 | @media (max-width: 600px) { 13 | @content; 14 | } 15 | } @else if $breakpoint == 'medium' { 16 | @media (min-width: 601px) and (max-width: 1024px) { 17 | @content; 18 | } 19 | } @else if $breakpoint == 'large' { 20 | @media (min-width: 1025px) { 21 | @content; 22 | } 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /src/components/shared/InputField/InputField.tsx: -------------------------------------------------------------------------------- 1 | type InputFieldProps = { 2 | id: string; 3 | type?: string; 4 | required?: boolean; 5 | label?: string; 6 | defaultValue?: string; 7 | }; 8 | 9 | const InputField = ({ 10 | id, 11 | type = "text", 12 | required = false, 13 | label, 14 | defaultValue = undefined, 15 | }: InputFieldProps) => { 16 | return ( 17 | <> 18 | 21 | 28 | 29 | ); 30 | }; 31 | 32 | export default InputField; 33 | -------------------------------------------------------------------------------- /src/components/TopBar/Clock.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { getTime } from "../../utils/clockUtils"; 3 | import styles from "./clock.module.scss"; 4 | 5 | const Clock = () => { 6 | const [currentTime, setCurrentTime] = useState(getTime(new Date())); 7 | 8 | useEffect(() => { 9 | let ref = setInterval(() => { 10 | let currentTimeObj = new Date(); 11 | setCurrentTime(getTime(currentTimeObj)); 12 | }, 1000); 13 | return () => clearInterval(ref); 14 | }, []); 15 | 16 | return ( 17 |
18 | {currentTime.time} 19 | {currentTime.meridiem} 20 |
21 | ); 22 | }; 23 | 24 | export default Clock; 25 | -------------------------------------------------------------------------------- /src/utils/clockUtils.ts: -------------------------------------------------------------------------------- 1 | export const getTime = (currentDateObj: Date, is12HourFormat = true) => { 2 | let hours = currentDateObj.getHours(); 3 | const minutes = currentDateObj.getMinutes(); 4 | 5 | const meridiem = hours < 12 ? "AM" : "PM"; 6 | 7 | // Converts hours to 12-hour format 8 | if (is12HourFormat) { 9 | hours = hours % 12; 10 | hours = hours ? hours : 12; // If hours is 0, set it to 12 11 | } 12 | 13 | // Ensure two-digit format for hours and minutes 14 | const formattedHours = hours < 10 ? "0" + hours : hours; 15 | const formattedMinutes = minutes < 10 ? "0" + minutes : minutes; 16 | 17 | return { 18 | time: `${formattedHours}:${formattedMinutes}`, 19 | meridiem, 20 | is12HourFormat, 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import TopBar from "./components/TopBar/TopBar"; 3 | import WidgetHolder from "./components/WidgetHolder/WidgetHolder"; 4 | import Footer from "./components/Footer/Footer"; 5 | import styles from "./app.module.scss"; 6 | import Divider from "./components/shared/Divider/Divider"; 7 | 8 | function App() { 9 | const [searchValue, setSearchValue] = useState(""); 10 | 11 | return ( 12 | <> 13 |
14 |
15 | 16 |
17 | 18 | 19 |
20 |
21 | 22 | ); 23 | } 24 | 25 | export default App; 26 | -------------------------------------------------------------------------------- /src/index.scss: -------------------------------------------------------------------------------- 1 | @import "/src/styles/variables.scss"; 2 | @import "/src/styles/mixins.scss"; 3 | 4 | :root { 5 | @include font-inter; 6 | line-height: 1.5; 7 | font-weight: 400; 8 | 9 | background-color: $appBackground; 10 | 11 | font-synthesis: none; 12 | text-rendering: optimizeLegibility; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | } 16 | 17 | html, 18 | body, 19 | #root { 20 | margin: 0; 21 | padding: 0; 22 | 23 | height: 100vh; 24 | } 25 | 26 | button { 27 | padding: 0; 28 | border: 0; 29 | background: none; 30 | box-shadow: none; 31 | } 32 | button:focus-visible { 33 | outline: 4px solid $selectionColor; 34 | } 35 | button:active { 36 | outline: none; // to prevent outline while hovering 37 | } 38 | 39 | * { 40 | box-sizing: border-box; 41 | } 42 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | Minime 14 | 15 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/hooks/useHover.tsx: -------------------------------------------------------------------------------- 1 | import { useRef, useState, useEffect } from "react"; 2 | 3 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 4 | const useHover = (): [React.RefObject, boolean] => { 5 | const ref = useRef(null); 6 | 7 | const [isHovered, setIsHovered] = useState(false); 8 | 9 | const enterHandler = () => setIsHovered(true); 10 | const leaveHandler = () => setIsHovered(false); 11 | 12 | useEffect(() => { 13 | const currentElement = ref.current; 14 | currentElement?.addEventListener("mouseenter", enterHandler); 15 | currentElement?.addEventListener("mouseleave", leaveHandler); 16 | return () => { 17 | currentElement?.removeEventListener("mouseenter", enterHandler); 18 | currentElement?.removeEventListener("mouseleave", leaveHandler); 19 | }; 20 | }, []); 21 | 22 | return [ref, isHovered]; 23 | }; 24 | 25 | export default useHover; 26 | -------------------------------------------------------------------------------- /src/styles/variables.scss: -------------------------------------------------------------------------------- 1 | // Colors 2 | $appBackground: #181818; 3 | $backdrop: rgba(0, 0, 0, 0.5); 4 | 5 | $primaryColor: #9c988d; 6 | $secondaryColor: #1e1e1e; 7 | 8 | $appFooterTextColor: $primaryColor; 9 | $clockColor: $secondaryColor; 10 | $clockTextColor: $primaryColor; 11 | $selectionColor: $primaryColor; 12 | 13 | // Modal Colors 14 | $modalBackgroundColor: $secondaryColor; 15 | $modalHeaderColor: $primaryColor; 16 | $modalTextColor: $primaryColor; 17 | 18 | $modalButtonColor: $primaryColor; 19 | $modalButtonTextColor: $appBackground; 20 | 21 | // Widget Defaults 22 | $widgetBackground: $secondaryColor; 23 | $widgetTextColor: $primaryColor; 24 | $widgetImageHolderColor: $appBackground; 25 | $widgetBorderRadius: 32px; 26 | $widgetBorderRadiusOnHover: 12px; 27 | $widgetHeight: 52px; 28 | 29 | $addNewWidgetButtonColor: $secondaryColor; 30 | $addNewWidgetButtonIconColor: $primaryColor; 31 | -------------------------------------------------------------------------------- /src/components/AddEditWidgetModal/addEditWidgetModal.module.scss: -------------------------------------------------------------------------------- 1 | @import "/src/styles/mixins.scss"; 2 | @import "/src/styles/variables.scss"; 3 | 4 | .modalContent { 5 | flex: 1; 6 | display: flex; 7 | flex-direction: column; 8 | color: $modalTextColor; 9 | gap: 12px; 10 | padding-top: 14px; 11 | .formInputHolder { 12 | display: inline-flex; 13 | align-items: end; 14 | 15 | font-weight: 500; 16 | label { 17 | width: 90px; 18 | } 19 | input { 20 | flex: 1; 21 | height: 30px; 22 | border-radius: 8px; 23 | border: none; 24 | } 25 | } 26 | .modalFooter { 27 | width: 100%; 28 | padding-top: 24px; 29 | display: inline-flex; 30 | justify-content: end; 31 | gap: 10px; 32 | button { 33 | @include font-inter; 34 | font-weight: 800; 35 | background-color: $modalButtonColor; 36 | color: $modalButtonTextColor; 37 | padding: 8px 16px; 38 | border-radius: 6px; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/components/shared/Modal/modal.module.scss: -------------------------------------------------------------------------------- 1 | @import "/src/styles/variables.scss"; 2 | @import "/src/styles/mixins.scss"; 3 | 4 | .overlay { 5 | background-color: $backdrop; 6 | 7 | height: 100%; 8 | width: 100%; 9 | 10 | position: absolute; 11 | top: 0; 12 | left: 0; 13 | 14 | display: grid; 15 | place-items: center; 16 | .modal { 17 | width: 400px; 18 | 19 | background-color: $modalBackgroundColor; 20 | border-radius: 24px; 21 | padding: 32px; 22 | 23 | display: flex; 24 | flex-direction: column; 25 | .header { 26 | @include font-poppins; 27 | font-size: 24px; 28 | color: $modalHeaderColor; 29 | padding-bottom: 16px; 30 | margin-bottom: 16px; 31 | border-bottom: 2px solid $modalHeaderColor; 32 | } 33 | } 34 | } 35 | .rightSideModal { 36 | display: flex; 37 | flex-direction: column; 38 | align-items: end; 39 | padding: 12px; 40 | .modal { 41 | flex: 1; 42 | width: 60vw; 43 | min-width: 400px; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/components/WidgetHolder/DraggableWidget.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useDrag, useDrop } from "react-dnd"; 3 | 4 | type DraggableWidgetProps = React.PropsWithChildren & { 5 | index: number; 6 | onDrop: (fromIndex: number, toIndex: number) => void; 7 | className: string; 8 | }; 9 | 10 | const DraggableWidget = ({ 11 | index, 12 | onDrop, 13 | children, 14 | className, 15 | }: DraggableWidgetProps) => { 16 | const [{ isDragging }, ref] = useDrag({ 17 | type: "_WIDGET", 18 | item: { index }, 19 | collect: (monitor) => ({ 20 | isDragging: monitor.isDragging(), 21 | }), 22 | }); 23 | 24 | const [, drop] = useDrop({ 25 | accept: "_WIDGET", 26 | drop: (droppedItem: { index: number }) => onDrop(droppedItem.index, index), 27 | }); 28 | 29 | return ( 30 |
ref(drop(node))} 32 | style={{ opacity: isDragging ? 0.3 : 1 }} 33 | className={className} 34 | > 35 | {children} 36 |
37 | ); 38 | }; 39 | 40 | export default DraggableWidget; 41 | -------------------------------------------------------------------------------- /src/components/Footer/footer.module.scss: -------------------------------------------------------------------------------- 1 | @import "/src/styles/mixins.scss"; 2 | @import "/src/styles/variables.scss"; 3 | 4 | .footer { 5 | width: 100%; 6 | display: flex; 7 | justify-content: flex-end; 8 | align-items: center; 9 | padding-right: 24px; 10 | p, 11 | a { 12 | @include font-poppins; 13 | font-size: 14px; 14 | font-weight: 400; 15 | color: $appFooterTextColor; 16 | text-decoration: none; 17 | } 18 | a { 19 | &:hover { 20 | text-decoration: underline; 21 | } 22 | } 23 | .githubContainer { 24 | display: inline-block; 25 | vertical-align: middle; 26 | margin-left: 8px; 27 | .github { 28 | font-size: 20px; 29 | font-weight: 600; 30 | margin-left: 2px; 31 | .icon { 32 | margin-right: 4px; 33 | } 34 | } 35 | } 36 | 37 | @include respond-to("small") { 38 | flex-direction: column; 39 | text-align: center; 40 | padding-right: 0; 41 | font-size: calc(14px + 0.5vw); 42 | .creditInfo { 43 | margin-bottom: 0; 44 | } 45 | .githubContainer { 46 | margin-top: 4px; 47 | } 48 | .seperator { 49 | display: none; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Yash Chaudhari 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/components/TopBar/Search/search.module.scss: -------------------------------------------------------------------------------- 1 | @import "/src/styles/mixins.scss"; 2 | @import "/src/styles/variables.scss"; 3 | 4 | .searchForm { 5 | display: flex; 6 | flex-direction: column; 7 | gap: 6px; 8 | color: $primaryColor; 9 | 10 | input[type="search"]::-webkit-search-decoration, 11 | input[type="search"]::-webkit-search-cancel-button, 12 | input[type="search"]::-webkit-search-results-button, 13 | input[type="search"]::-webkit-search-results-decoration { 14 | display: none; 15 | } 16 | .searchBox { 17 | position: relative; 18 | display: inline-flex; 19 | align-items: center; 20 | input { 21 | @include font-poppins; 22 | 23 | width: 600px; 24 | height: 50px; 25 | 26 | font-size: 18px; 27 | background: none; 28 | color: $primaryColor; 29 | 30 | border: 1px solid rgba($primaryColor, 0.5); 31 | border-radius: 64px; 32 | 33 | padding-left: 50px; 34 | outline: none; 35 | } 36 | label { 37 | position: absolute; 38 | left: 20px; 39 | font-size: 20px; 40 | } 41 | } 42 | 43 | span { 44 | color: rgba($primaryColor, 0.5); 45 | text-align: end; 46 | padding-right: 8px; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/components/shared/Modal/Modal.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styles from "./modal.module.scss"; 3 | import classNames from "classnames"; 4 | 5 | type ModalProps = { 6 | showModal: boolean; 7 | onCloseClick?: () => void; 8 | headerText?: string; 9 | rightSideModal?: boolean; 10 | }; 11 | 12 | const Modal = ({ 13 | children: modalContent, 14 | showModal, 15 | headerText, 16 | rightSideModal = false, 17 | onCloseClick 18 | }: React.PropsWithChildren) => { 19 | const overlayClass = classNames(styles.overlay, { 20 | [styles.rightSideModal]: rightSideModal, 21 | }); 22 | 23 | const onKeyDownHandler = (e: React.KeyboardEvent) => { 24 | if (e.key === "Escape" && onCloseClick) { 25 | onCloseClick(); 26 | } 27 | } 28 | 29 | return ( 30 | showModal && ( 31 |
32 |
{ 33 | e.stopPropagation(); 34 | }}> 35 | {headerText &&
{headerText}
} 36 | {modalContent} 37 |
38 |
39 | ) 40 | ); 41 | }; 42 | 43 | export default Modal; 44 | -------------------------------------------------------------------------------- /src/components/Footer/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 2 | import { faGithub } from "@fortawesome/free-brands-svg-icons"; 3 | import { faHeart } from "@fortawesome/free-solid-svg-icons"; 4 | import styles from "./footer.module.scss"; 5 | 6 | const Footer = () => { 7 | return ( 8 | 34 | ); 35 | }; 36 | 37 | export default Footer; 38 | -------------------------------------------------------------------------------- /src/components/TopBar/Search/Search.tsx: -------------------------------------------------------------------------------- 1 | import type { Dispatch, FormEvent, SetStateAction } from 'react'; 2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 3 | import { faSearch } from '@fortawesome/free-solid-svg-icons'; 4 | import styles from './search.module.scss'; 5 | 6 | type SearchProps = { 7 | searchValue: string; 8 | setSearchValue: Dispatch>; 9 | } 10 | 11 | const Search = ({ searchValue, setSearchValue }: SearchProps) => { 12 | const onSearch = (e: FormEvent) => { 13 | e.preventDefault(); 14 | 15 | let url = "https://www.google.com/search?q="; 16 | window.open(url + searchValue); 17 | } 18 | 19 | return ( 20 |
21 |
22 | 23 | setSearchValue(e.target.value)} 28 | /> 29 |
30 | Press ENTER to search on google 31 |
32 | ) 33 | } 34 | 35 | export default Search; 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "minime", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@fortawesome/fontawesome-svg-core": "^6.5.1", 14 | "@fortawesome/free-brands-svg-icons": "^6.5.1", 15 | "@fortawesome/free-regular-svg-icons": "^6.5.1", 16 | "@fortawesome/free-solid-svg-icons": "^6.5.1", 17 | "@fortawesome/react-fontawesome": "^0.2.0", 18 | "@types/node": "^20.14.10", 19 | "classnames": "^2.5.1", 20 | "dotenv": "^16.4.5", 21 | "react": "^18.2.0", 22 | "react-dnd": "^16.0.1", 23 | "react-dnd-html5-backend": "^16.0.1", 24 | "react-dom": "^18.2.0" 25 | }, 26 | "devDependencies": { 27 | "@types/react": "^18.2.43", 28 | "@types/react-dnd-html5-backend": "^3.0.2", 29 | "@types/react-dom": "^18.2.17", 30 | "@typescript-eslint/eslint-plugin": "^6.14.0", 31 | "@typescript-eslint/parser": "^6.14.0", 32 | "@vitejs/plugin-react": "^4.2.1", 33 | "eslint": "^8.55.0", 34 | "eslint-plugin-react-hooks": "^4.6.0", 35 | "eslint-plugin-react-refresh": "^0.4.5", 36 | "sass": "^1.70.0", 37 | "typescript": "^5.2.2", 38 | "vite": "^5.4.21" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/components/NotepadWidgetModal/NotepadWidgetModal.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { getNotepadContent, saveNotepadContent } from "../../utils/notepadUtils"; 3 | import Modal from "../shared/Modal/Modal"; 4 | import styles from "./notepadWidgetModal.module.scss"; 5 | 6 | type NotepadWidgetModalProps = { 7 | show: boolean; 8 | onClose: () => void; 9 | }; 10 | 11 | const NotepadWidgetModal = ({ 12 | show, 13 | onClose, 14 | }: NotepadWidgetModalProps) => { 15 | const [notepadContent, setNotepadContent] = useState(""); 16 | 17 | useEffect(() => { 18 | const ls_NotepadContent = getNotepadContent(); 19 | if (ls_NotepadContent) { 20 | setNotepadContent(ls_NotepadContent); 21 | } 22 | }, []) 23 | 24 | useEffect(() => { 25 | const autoSaveTimer = setTimeout(() => { 26 | saveNotepadContent(notepadContent); 27 | }, 500); 28 | 29 | return () => clearTimeout(autoSaveTimer); 30 | }, [notepadContent]) 31 | 32 | const onChangeHandler = (e: React.ChangeEvent) => { 33 | setNotepadContent(e.target.value); 34 | } 35 | const onFocusHandler = (e: React.FocusEvent) => { 36 | // https://gist.github.com/piyonishi/409ecbd07f7b86b7da205ad61210a275 37 | const tempContent = e.target.value; 38 | e.target.value = ""; 39 | e.target.value = tempContent; 40 | } 41 | 42 | return ( 43 | 44 |