diff --git a/.babelrc.js b/.babelrc.js deleted file mode 100644 index 8ea7566..0000000 --- a/.babelrc.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - presets: ['@babel/preset-react', ['@babel/preset-env', { modules: false }]], - - plugins: [ - '@babel/plugin-proposal-export-namespace-from', - '@babel/plugin-proposal-export-default-from', - '@babel/plugin-proposal-class-properties', - ], -}; diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 8e81b08..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,20 +0,0 @@ -module.exports = { - parser: 'babel-eslint', - extends: ['@rtivital/eslint-config'], - env: { - browser: true, - node: true, - }, - - rules: { - 'jsx-a11y/no-static-element-interactions': 'off', - 'jsx-a11y/click-events-have-key-events': 'off', - }, - - settings: { - 'import/resolver': { - node: {}, - webpack: {}, - }, - }, -}; diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml new file mode 100644 index 0000000..39b6c56 --- /dev/null +++ b/.github/workflows/pr-test.yml @@ -0,0 +1,29 @@ +name: PR Test + +on: + pull_request: + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 24 + + - name: Enable Corepack + run: | + corepack enable + corepack prepare yarn@4.12.0 --activate + + - name: Install dependencies + run: yarn install --immutable + + - name: Run tests + run: npm test diff --git a/.gitignore b/.gitignore index 8986ea0..0304689 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules *.log dist .DS_Store +.yarn/install-state.gz diff --git a/.prettierrc.js b/.prettierrc.js index 2f14513..0722390 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -1 +1,6 @@ -module.exports = require('@rtivital/eslint-config/.prettierrc'); +module.exports = { + singleQuote: true, + trailingComma: 'es5', + printWidth: 100, + semi: true, +}; diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 0000000..3186f3f --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..c16e7b7 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,22 @@ +import mantine from 'eslint-config-mantine'; +import { defineConfig } from 'eslint/config'; +import tseslint from 'typescript-eslint'; + +// @ts-check +export default defineConfig( + tseslint.configs.recommended, + ...mantine, + { ignores: ['**/*.{mjs,cjs,js,d.ts,d.mts}'] }, + { + files: ['**/*.story.tsx'], + rules: { 'no-console': 'off' }, + }, + { + languageOptions: { + parserOptions: { + tsconfigRootDir: process.cwd(), + project: ['./tsconfig.json'], + }, + }, + } +); diff --git a/package.json b/package.json index 3eb634e..a4afa28 100644 --- a/package.json +++ b/package.json @@ -1,89 +1,85 @@ { - "name": "Omatsuri", + "name": "omatsuri", "version": "1.0.0", "description": "Open source frontend focused tools", "repository": "https://github.com/rtivital/tools.git", "author": "Vitaly Rtishchev ", "license": "MIT", "private": true, + "packageManager": "yarn@4.12.0", + "engines": { + "node": ">=24.0.0" + }, "scripts": { - "start": "npm run clean && webpack-dev-server", - "build": "npm run clean && cross-env NODE_ENV=production webpack --progress", + "start": "yarn clean && webpack serve --mode development --open", + "build": "yarn clean && cross-env NODE_ENV=production webpack --mode production --progress", "clean": "rimraf ./dist", - "deploy": "npm run build && gh-pages -d dist", - "lint": "eslint src --ext jsx --ext js", - "serve": "npm run build && http-server dist -p 6002", - "analyze": "npm run build -- --analyze" + "deploy": "yarn build && gh-pages -d dist", + "test": "npm run lint && prettier --check . && npm run typecheck", + "lint": "eslint src --ext .js,.jsx,.ts,.tsx", + "typecheck": "tsc --noEmit", + "serve": "yarn build && http-server dist -p 6002", + "analyze": "yarn build -- --analyze" }, "browserslist": [ "> 1%", "last 2 versions" ], "devDependencies": { - "@babel/core": "^7.11.6", - "@babel/plugin-proposal-class-properties": "^7.10.4", - "@babel/plugin-proposal-export-default-from": "^7.10.4", - "@babel/plugin-proposal-export-namespace-from": "^7.10.4", - "@babel/preset-env": "^7.11.5", - "@babel/preset-react": "^7.10.4", - "@rtivital/eslint-config": "1.0.1", - "autoprefixer": "^9.8.6", - "babel-eslint": "^10.1.0", - "babel-loader": "^8.1.0", + "@eslint/js": "^9.39.1", + "@pmmmwh/react-refresh-webpack-plugin": "^0.6.1", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "autoprefixer": "^10.4.20", "cname-webpack-plugin": "^2.0.1", - "cross-env": "^7.0.2", - "css-loader": "^4.3.0", - "eslint": "^7.9.0", - "eslint-config-airbnb": "^18.2.0", - "eslint-import-resolver-webpack": "^0.12.2", - "eslint-plugin-import": "^2.22.0", - "eslint-plugin-jsx-a11y": "^6.3.1", - "eslint-plugin-react": "^7.20.6", - "eslint-plugin-react-hooks": "^4.1.2", - "favicons-webpack-plugin": "^4.2.0", - "file-loader": "^6.1.0", - "gh-pages": "^3.1.0", - "html-webpack-plugin": "^4.4.1", - "http-server": "^0.12.3", - "less": "^3.12.2", - "less-loader": "^7.0.1", - "mini-css-extract-plugin": "^0.11.2", - "open-browser-webpack-plugin": "^0.0.5", - "optimize-css-assets-webpack-plugin": "^5.0.4", - "postcss": "^7.0.32", - "postcss-loader": "^4.0.1", - "rimraf": "^3.0.2", - "style-loader": "^1.2.1", - "terser-webpack-plugin": "^4.2.0", - "webpack": "^4.44.1", - "webpack-bundle-analyzer": "^3.8.0", - "webpack-cli": "^3.3.12", - "webpack-dev-server": "^3.11.0", - "worker-loader": "^3.0.2", - "yargs": "^16.0.3" + "cross-env": "^7.0.3", + "css-loader": "^7.1.2", + "css-minimizer-webpack-plugin": "^7.0.2", + "eslint": "^9.39.1", + "eslint-config-mantine": "^4.0.2", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^5.2.0", + "favicons": "^7.2.0", + "favicons-webpack-plugin": "^6.0.1", + "gh-pages": "^6.3.0", + "html-webpack-plugin": "^5.6.3", + "http-server": "^14.1.1", + "less": "^4.2.2", + "less-loader": "^12.2.0", + "mini-css-extract-plugin": "^2.9.2", + "postcss": "^8.5.3", + "postcss-loader": "^8.1.1", + "prettier": "^3.5.3", + "react-refresh": "^0.16.0", + "rimraf": "^6.0.1", + "style-loader": "^4.0.0", + "terser-webpack-plugin": "^5.3.14", + "ts-loader": "^9.5.2", + "typescript": "^5.8.2", + "typescript-eslint": "^8.46.2", + "webpack": "^5.98.0", + "webpack-bundle-analyzer": "^4.10.2", + "webpack-cli": "^6.0.1", + "webpack-dev-server": "^5.2.0" }, "dependencies": { - "classnames": "^2.2.6", - "color": "^3.1.2", - "faker": "^4.1.0", + "@faker-js/faker": "^9.5.0", + "@mantine/hooks": "^7.17.8", + "clsx": "^2.1.1", + "color": "^5.0.2", "fuse.js": "^6.4.1", - "lorem-ipsum": "^2.0.3", + "lorem-ipsum": "^2.0.8", "normalize.css": "^8.0.1", - "offline-plugin": "^5.0.7", "open-color": "^1.7.0", "pokeipsum": "^1.1.0", - "prettier": "^2.1.1", "prop-types": "^15.7.2", - "react": "^16.13.1", - "react-colorful": "^4.0.4", - "react-custom-scrollbars": "^4.2.1", - "react-dom": "^16.13.1", - "react-hot-loader": "^4.12.21", - "react-router-dom": "^5.2.0", + "react": "^18.3.1", + "react-colorful": "^5.6.1", + "react-custom-scrollbars-2": "^4.5.0", + "react-dom": "^18.3.1", + "react-router-dom": "^6.30.0", "samuel-ipsum": "^1.0.11", - "svg-to-jsx": "^1.0.2", - "svgo-browser": "^1.3.7", - "uuid": "^8.3.0", - "xooks": "^2.2.6" + "uuid": "^11.1.0" } } diff --git a/src/App.jsx b/src/App.jsx deleted file mode 100644 index bb9fc61..0000000 --- a/src/App.jsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react'; -import { BrowserRouter, Route, Switch } from 'react-router-dom'; -import { hot } from 'react-hot-loader'; - -import ThemeProvider from './ThemeProvider'; -import AppContainer from './components/AppContainer/AppContainer'; - -import Index from './pages/index/Index'; -import About from './pages/about/About'; -import NotFound from './pages/not-found/NotFound'; - -import TriangleGenerator from './apps/triangle-generator/TriangleGenerator'; -import LoremIpsum from './apps/lorem-ipsum/LoremIpsum'; -import SvgCompressor from './apps/svg-compressor/SvgCompressor'; -import SvgToJsx from './apps/svg-to-jsx/SvgToJsx'; -import HtmlSymbols from './apps/html-symbols/HtmlSymbols'; -import Base64Encoding from './apps/b64-encoding/B64Encoding'; -import ColorShadesGenerator from './apps/color-shades-generator/ColorShadesGenerator'; -import PageDividers from './apps/page-dividers/PageDividers'; -import FakeDataGenerator from './apps/fake-data-generator/FakeDataGenerator'; -import CssCursors from './apps/css-cursors/CssCursors'; -import EventsKeycode from './apps/events-keycode/EventsKeycode'; -import GradientGenerator from './apps/gradient-generator/GradientGenerator'; - -function App() { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -} - -export default hot(module)(App); diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..a7c737c --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,50 @@ +import type { ReactElement } from 'react'; +import { BrowserRouter, Route, Routes } from 'react-router-dom'; + +import ThemeProvider from './ThemeProvider'; +import AppContainer from './components/AppContainer/AppContainer'; + +import Index from './pages/index/Index'; +import About from './pages/about/About'; +import NotFound from './pages/not-found/NotFound'; + +import TriangleGenerator from './apps/triangle-generator/TriangleGenerator'; +import LoremIpsum from './apps/lorem-ipsum/LoremIpsum'; +import SvgCompressor from './apps/svg-compressor/SvgCompressor'; +import SvgToJsx from './apps/svg-to-jsx/SvgToJsx'; +import HtmlSymbols from './apps/html-symbols/HtmlSymbols'; +import Base64Encoding from './apps/b64-encoding/B64Encoding'; +import ColorShadesGenerator from './apps/color-shades-generator/ColorShadesGenerator'; +import PageDividers from './apps/page-dividers/PageDividers'; +import FakeDataGenerator from './apps/fake-data-generator/FakeDataGenerator'; +import CssCursors from './apps/css-cursors/CssCursors'; +import EventsKeycode from './apps/events-keycode/EventsKeycode'; +import GradientGenerator from './apps/gradient-generator/GradientGenerator'; + +const withContainer = (component: ReactElement) => {component}; + +export default function App() { + return ( + + + + } /> + } /> + )} /> + )} /> + )} /> + )} /> + )} /> + )} /> + )} /> + )} /> + )} /> + )} /> + )} /> + )} /> + )} /> + + + + ); +} diff --git a/src/ThemeProvider.jsx b/src/ThemeProvider.tsx similarity index 50% rename from src/ThemeProvider.jsx rename to src/ThemeProvider.tsx index 6395ef4..0ec0e26 100644 --- a/src/ThemeProvider.jsx +++ b/src/ThemeProvider.tsx @@ -1,8 +1,14 @@ import React, { createContext, useState, useContext } from 'react'; -import PropTypes from 'prop-types'; -import { useColorScheme, useLocalStorage } from 'xooks'; +import { useColorScheme, useLocalStorage } from '@hooks'; -export const ThemeContext = createContext(); +type Theme = 'light' | 'dark'; + +interface ThemeContextValue { + theme: Theme; + setTheme: (theme: Theme) => void; +} + +export const ThemeContext = createContext(null); export function useTheme() { const context = useContext(ThemeContext); @@ -11,16 +17,20 @@ export function useTheme() { throw new Error('ThemeProvider was not found'); } - return [context.theme, context.setTheme]; + return [context.theme, context.setTheme] as const; } -export default function ThemeProvider({ children }) { +interface ThemeProviderProps { + children: React.ReactNode; +} + +export default function ThemeProvider({ children }: ThemeProviderProps) { const ls = useLocalStorage({ key: '@omatsuri/theme', delay: 10 }); const systemTheme = useColorScheme(); - const [userTheme, setUserTheme] = useState(ls.retrieve() || null); + const [userTheme, setUserTheme] = useState(ls.retrieve() || null); - const handleUserThemeChange = (theme) => { + const handleUserThemeChange = (theme: Theme) => { ls.save(theme); setUserTheme(theme); }; @@ -36,7 +46,3 @@ export default function ThemeProvider({ children }) { ); } - -ThemeProvider.propTypes = { - children: PropTypes.node.isRequired, -}; diff --git a/src/apps/b64-encoding/B64Encoding.jsx b/src/apps/b64-encoding/B64Encoding.tsx similarity index 65% rename from src/apps/b64-encoding/B64Encoding.jsx rename to src/apps/b64-encoding/B64Encoding.tsx index 1c1f9c9..4213636 100644 --- a/src/apps/b64-encoding/B64Encoding.jsx +++ b/src/apps/b64-encoding/B64Encoding.tsx @@ -1,28 +1,37 @@ -import React, { useState, useLayoutEffect } from 'react'; -import cx from 'classnames'; -import { useDocumentTitle, useLocalStorage } from 'xooks'; +import React, { useState, useLayoutEffect, useRef } from 'react'; +import cx from 'clsx'; +import { useDocumentTitle, useLocalStorage } from '@hooks'; import Highlight from '../../components/Highlight/Highlight'; import Background from '../../components/Background/Background'; import SettingsLabel from '../../components/SettingsLabel/SettingsLabel'; import DropPlaceholder from '../../components/DropPlaceholder/DropPlaceholder'; import Dropzone from '../../components/Dropzone/Dropzone'; -import B64Worker from '../../workers/b64.worker'; import classes from './B64Encoding.styles.less'; -const b64 = new B64Worker(); - -function generateCssExample(content) { +function generateCssExample(content: string) { return `.element {\n background-image: url(${content});\n}`; } export default function B64Encoding() { useDocumentTitle('Base64 encoding'); + const b64 = useRef(null); + if (!b64.current) { + b64.current = new Worker(new URL('../../workers/b64.worker.js', import.meta.url)); + } const ls = useLocalStorage({ key: '@omatsuri/b64-encoding', delay: 500 }); const transmittedValue = useLocalStorage({ key: '@omatsuri/conversion-after-compression/b64' }); - const [result, setResult] = useState({ loading: false, error: null, content: ls.retrieve() }); + const [result, setResult] = useState<{ + loading: boolean; + error: boolean | null; + content: string | null; + }>({ + loading: false, + error: null, + content: ls.retrieve() || null, + }); - const handleMessage = (event) => { + const handleMessage = (event: MessageEvent) => { const error = event.data instanceof Error; if (!error) { ls.save(event.data); @@ -34,22 +43,30 @@ export default function B64Encoding() { }); }; - const postMessage = (file) => { - b64.postMessage({ file }); + const postMessage = (file: File) => { + b64.current?.postMessage({ file }); }; useLayoutEffect(() => { - b64.addEventListener('message', handleMessage); + const worker = b64.current; + if (!worker) { + return undefined; + } + + worker.addEventListener('message', handleMessage); const transmittedContent = transmittedValue.retrieveAndClean(); if (transmittedContent) { - b64.postMessage({ content: transmittedContent }); + worker.postMessage({ content: transmittedContent }); } - return () => b64.removeEventListener('message', handleMessage); + return () => { + worker.removeEventListener('message', handleMessage); + worker.terminate(); + }; }, []); - const handleFilesDrop = (files) => { + const handleFilesDrop = (files: File[]) => { if (files.length > 0) { postMessage(files[0]); } @@ -61,7 +78,6 @@ export default function B64Encoding() { handleFilesDrop([file])} - accepts={undefined} > Drop a file to the browser window to convert it to base64 format diff --git a/src/apps/color-shades-generator/ColorShadesGenerator.jsx b/src/apps/color-shades-generator/ColorShadesGenerator.tsx similarity index 97% rename from src/apps/color-shades-generator/ColorShadesGenerator.jsx rename to src/apps/color-shades-generator/ColorShadesGenerator.tsx index 6ee46b3..9cc65b5 100644 --- a/src/apps/color-shades-generator/ColorShadesGenerator.jsx +++ b/src/apps/color-shades-generator/ColorShadesGenerator.tsx @@ -1,7 +1,7 @@ import oc from 'open-color'; import React, { useState, useEffect } from 'react'; import { v4 } from 'uuid'; -import { useDocumentTitle, useLocalStorage } from 'xooks'; +import { useDocumentTitle, useLocalStorage } from '@hooks'; import Button from '../../components/Button/Button'; import ColorShadesList from './ColorShadesList/ColorShadesList'; import Settings from './Settings/Settings'; diff --git a/src/apps/color-shades-generator/ColorShadesList/ColorShadesList.jsx b/src/apps/color-shades-generator/ColorShadesList/ColorShadesList.tsx similarity index 84% rename from src/apps/color-shades-generator/ColorShadesList/ColorShadesList.jsx rename to src/apps/color-shades-generator/ColorShadesList/ColorShadesList.tsx index fcc6b42..69d0b7a 100644 --- a/src/apps/color-shades-generator/ColorShadesList/ColorShadesList.jsx +++ b/src/apps/color-shades-generator/ColorShadesList/ColorShadesList.tsx @@ -1,9 +1,8 @@ import React from 'react'; -import PropTypes from 'prop-types'; import Color from 'color'; -import cx from 'classnames'; -import { useClipboard } from 'xooks'; -import { Scrollbars } from 'react-custom-scrollbars'; +import cx from 'clsx'; +import { useClipboard } from '@hooks'; +import Scrollbars from 'react-custom-scrollbars-2'; import { useTheme } from '../../../ThemeProvider'; import HexInput from '../../../components/HexInput/HexInput'; import Background from '../../../components/Background/Background'; @@ -82,12 +81,3 @@ export default function ColorShadesList({ ); } - -ColorShadesList.propTypes = { - value: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired, - onDelete: PropTypes.func.isRequired, - canDelete: PropTypes.bool.isRequired, - saturation: PropTypes.number.isRequired, - darken: PropTypes.number.isRequired, -}; diff --git a/src/apps/color-shades-generator/Settings/Settings.jsx b/src/apps/color-shades-generator/Settings/Settings.tsx similarity index 80% rename from src/apps/color-shades-generator/Settings/Settings.jsx rename to src/apps/color-shades-generator/Settings/Settings.tsx index 7473f5b..8ba1772 100644 --- a/src/apps/color-shades-generator/Settings/Settings.jsx +++ b/src/apps/color-shades-generator/Settings/Settings.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; +import cx from 'clsx'; import { useTheme } from '../../../ThemeProvider'; import SliderInput from '../../../components/SliderInput/SliderInput'; import Background from '../../../components/Background/Background'; @@ -54,13 +53,3 @@ export default function Settings({ ); } - -Settings.propTypes = { - darken: PropTypes.number.isRequired, - onDarkenChange: PropTypes.func.isRequired, - saturation: PropTypes.number.isRequired, - onSaturationChange: PropTypes.func.isRequired, - onPaletteLoad: PropTypes.func.isRequired, - onAllRemove: PropTypes.func.isRequired, - canRemove: PropTypes.bool.isRequired, -}; diff --git a/src/apps/css-cursors/CssCursors.jsx b/src/apps/css-cursors/CssCursors.tsx similarity index 95% rename from src/apps/css-cursors/CssCursors.jsx rename to src/apps/css-cursors/CssCursors.tsx index b5e38f0..5dbeeea 100644 --- a/src/apps/css-cursors/CssCursors.jsx +++ b/src/apps/css-cursors/CssCursors.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useDocumentTitle } from 'xooks'; +import { useDocumentTitle } from '@hooks'; import Background from '../../components/Background/Background'; import SettingsLabel from '../../components/SettingsLabel/SettingsLabel'; import CursorControl from './CursorControl/CursorControl'; diff --git a/src/apps/css-cursors/CursorControl/CursorControl.jsx b/src/apps/css-cursors/CursorControl/CursorControl.tsx similarity index 74% rename from src/apps/css-cursors/CursorControl/CursorControl.jsx rename to src/apps/css-cursors/CursorControl/CursorControl.tsx index 659e9b4..925cbbd 100644 --- a/src/apps/css-cursors/CursorControl/CursorControl.jsx +++ b/src/apps/css-cursors/CursorControl/CursorControl.tsx @@ -1,7 +1,6 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; -import { useClipboard } from 'xooks'; +import cx from 'clsx'; +import { useClipboard } from '@hooks'; import { useTheme } from '../../../ThemeProvider'; import classes from './CursorControl.styles.less'; @@ -26,8 +25,3 @@ export default function CursorControl({ className, value }) { ); } - -CursorControl.propTypes = { - className: PropTypes.string, - value: PropTypes.string.isRequired, -}; diff --git a/src/apps/events-keycode/EventInfo/EventInfo.jsx b/src/apps/events-keycode/EventInfo/EventInfo.tsx similarity index 68% rename from src/apps/events-keycode/EventInfo/EventInfo.jsx rename to src/apps/events-keycode/EventInfo/EventInfo.tsx index 481a022..021af44 100644 --- a/src/apps/events-keycode/EventInfo/EventInfo.jsx +++ b/src/apps/events-keycode/EventInfo/EventInfo.tsx @@ -1,10 +1,23 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; +import cx from 'clsx'; import { useTheme } from '../../../ThemeProvider'; import classes from './EventInfo.styles.less'; -export default function EventInfo({ className, title, kbd, value, deprecation = false }) { +interface EventInfoProps { + className?: string; + title: string; + kbd?: string; + value: React.ReactNode | boolean; + deprecation?: boolean; +} + +export default function EventInfo({ + className, + title, + kbd, + value, + deprecation = false, +}: EventInfoProps) { const [theme] = useTheme(); return ( @@ -30,11 +43,3 @@ export default function EventInfo({ className, title, kbd, value, deprecation = ); } - -EventInfo.propTypes = { - className: PropTypes.string, - title: PropTypes.string.isRequired, - kbd: PropTypes.string, - value: PropTypes.oneOfType([PropTypes.node, PropTypes.bool]).isRequired, - deprecation: PropTypes.bool, -}; diff --git a/src/apps/events-keycode/EventsKeycode.jsx b/src/apps/events-keycode/EventsKeycode.tsx similarity index 98% rename from src/apps/events-keycode/EventsKeycode.jsx rename to src/apps/events-keycode/EventsKeycode.tsx index 8414b59..06c67a3 100644 --- a/src/apps/events-keycode/EventsKeycode.jsx +++ b/src/apps/events-keycode/EventsKeycode.tsx @@ -1,5 +1,5 @@ import React, { useState, useLayoutEffect } from 'react'; -import { useDocumentTitle } from 'xooks'; +import { useDocumentTitle } from '@hooks'; import Background from '../../components/Background/Background'; import SettingsLabel from '../../components/SettingsLabel/SettingsLabel'; import Highlight from '../../components/Highlight/Highlight'; diff --git a/src/apps/events-keycode/Header/Header.jsx b/src/apps/events-keycode/Header/Header.tsx similarity index 93% rename from src/apps/events-keycode/Header/Header.jsx rename to src/apps/events-keycode/Header/Header.tsx index 1911ed2..aa8f752 100644 --- a/src/apps/events-keycode/Header/Header.jsx +++ b/src/apps/events-keycode/Header/Header.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; +import cx from 'clsx'; import { useTheme } from '../../../ThemeProvider'; import Background from '../../../components/Background/Background'; import generateEventData from '../generate-event-data'; @@ -54,7 +53,3 @@ export default function Header({ event }) { ); } - -Header.propTypes = { - event: PropTypes.object, -}; diff --git a/src/apps/events-keycode/Header/Keycap/Keycap.jsx b/src/apps/events-keycode/Header/Keycap/Keycap.tsx similarity index 79% rename from src/apps/events-keycode/Header/Keycap/Keycap.jsx rename to src/apps/events-keycode/Header/Keycap/Keycap.tsx index 07d043e..9369d1a 100644 --- a/src/apps/events-keycode/Header/Keycap/Keycap.jsx +++ b/src/apps/events-keycode/Header/Keycap/Keycap.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; +import cx from 'clsx'; import { useTheme } from '../../../../ThemeProvider'; import classes from './Keycap.styles.less'; @@ -16,7 +15,12 @@ const PREDEFINED_VALUES = { Cmd: null, }; -export default function Keycap({ value, className }) { +interface KeycapProps { + value: string; + className?: string; +} + +export default function Keycap({ value, className }: KeycapProps) { const [theme] = useTheme(); return ( @@ -39,8 +43,3 @@ export default function Keycap({ value, className }) { ); } - -Keycap.propTypes = { - value: PropTypes.string.isRequired, - className: PropTypes.string, -}; diff --git a/src/apps/fake-data-generator/FakeDataGenerator.jsx b/src/apps/fake-data-generator/FakeDataGenerator.tsx similarity index 97% rename from src/apps/fake-data-generator/FakeDataGenerator.jsx rename to src/apps/fake-data-generator/FakeDataGenerator.tsx index 3c0746e..e915604 100644 --- a/src/apps/fake-data-generator/FakeDataGenerator.jsx +++ b/src/apps/fake-data-generator/FakeDataGenerator.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { v4 } from 'uuid'; -import { useDocumentTitle, useLocalStorage } from 'xooks'; +import { useDocumentTitle, useLocalStorage } from '@hooks'; import Settings from './Settings/Settings'; import Output from './Output/Output'; diff --git a/src/apps/fake-data-generator/Output/Output.jsx b/src/apps/fake-data-generator/Output/Output.tsx similarity index 75% rename from src/apps/fake-data-generator/Output/Output.jsx rename to src/apps/fake-data-generator/Output/Output.tsx index fa7dce3..9c619cb 100644 --- a/src/apps/fake-data-generator/Output/Output.jsx +++ b/src/apps/fake-data-generator/Output/Output.tsx @@ -1,6 +1,5 @@ import React, { useState, useEffect } from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; +import cx from 'clsx'; import { useTheme } from '../../../ThemeProvider'; import Background from '../../../components/Background/Background'; import Highlight from '../../../components/Highlight/Highlight'; @@ -34,16 +33,3 @@ export default function Output({ type, fields, amount, seed = null }) { ); } - -Output.propTypes = { - type: PropTypes.oneOf(['default', 'json']).isRequired, - fields: PropTypes.arrayOf( - PropTypes.shape({ - name: PropTypes.string.isRequired, - type: PropTypes.string.isRequired, - key: PropTypes.string.isRequired, - }) - ).isRequired, - amount: PropTypes.number.isRequired, - seed: PropTypes.string, -}; diff --git a/src/apps/fake-data-generator/Settings/Settings.jsx b/src/apps/fake-data-generator/Settings/Settings.tsx similarity index 80% rename from src/apps/fake-data-generator/Settings/Settings.jsx rename to src/apps/fake-data-generator/Settings/Settings.tsx index 04fe305..6c36f08 100644 --- a/src/apps/fake-data-generator/Settings/Settings.jsx +++ b/src/apps/fake-data-generator/Settings/Settings.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; +import cx from 'clsx'; import { useTheme } from '../../../ThemeProvider'; import Tabs from '../../../components/Tabs/Tabs'; import Background from '../../../components/Background/Background'; @@ -88,23 +87,3 @@ export default function Settings({ ); } - -Settings.propTypes = { - type: PropTypes.oneOf(['default', 'json']).isRequired, - amount: PropTypes.number.isRequired, - - fields: PropTypes.arrayOf( - PropTypes.shape({ - name: PropTypes.string.isRequired, - type: PropTypes.string.isRequired, - key: PropTypes.string.isRequired, - }) - ), - - onAmountChange: PropTypes.func.isRequired, - onFieldAdd: PropTypes.func.isRequired, - onFieldRemove: PropTypes.func.isRequired, - onFieldPropChange: PropTypes.func.isRequired, - onTypeChange: PropTypes.func.isRequired, - onRegenerate: PropTypes.func.isRequired, -}; diff --git a/src/apps/fake-data-generator/generator.js b/src/apps/fake-data-generator/generator.js index d22c29e..d802118 100644 --- a/src/apps/fake-data-generator/generator.js +++ b/src/apps/fake-data-generator/generator.js @@ -1,22 +1,19 @@ -import faker from 'faker/locale/en'; +import { fakerEN as faker } from '@faker-js/faker'; const generators = { - name: faker.name.findName, + name: faker.person.fullName, email: faker.internet.email, - avatar: faker.internet.avatar, - username: faker.internet.userName, + avatar: faker.image.avatar, + username: faker.internet.username, password: faker.internet.password, - job_title: faker.name.jobTitle, - phone: faker.phone.phoneNumber, + job_title: faker.person.jobTitle, + phone: faker.phone.number, bitcoin_address: faker.finance.bitcoinAddress, - company: faker.company.companyName, - zip: faker.address.zipCode, - address: () => - faker.fake( - '{{address.cityPrefix}} {{address.city}}, {{address.streetName}}, {{random.number}}' - ), + company: faker.company.name, + zip: faker.location.zipCode, + address: () => `${faker.location.city()}, ${faker.location.street()}, ${faker.number.int(999)}`, date: () => faker.date.past().toISOString(), - city: faker.address.city, + city: faker.location.city, }; export const generatorsData = Object.keys(generators).map((key) => ({ diff --git a/src/apps/gradient-generator/GradientCode/GradientCode.jsx b/src/apps/gradient-generator/GradientCode/GradientCode.tsx similarity index 66% rename from src/apps/gradient-generator/GradientCode/GradientCode.jsx rename to src/apps/gradient-generator/GradientCode/GradientCode.tsx index 52c2be3..588fce9 100644 --- a/src/apps/gradient-generator/GradientCode/GradientCode.jsx +++ b/src/apps/gradient-generator/GradientCode/GradientCode.tsx @@ -1,12 +1,24 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; +import cx from 'clsx'; import Highlight from '../../../components/Highlight/Highlight'; import Background from '../../../components/Background/Background'; import generateGradientValue from '../generate-gradient-value'; import classes from './GradientCode.styles.less'; -export default function GradientCode({ className, values, angle, type }) { +interface GradientValue { + color: string; + position: number; + opacity: number; +} + +interface GradientCodeProps { + className?: string; + values: GradientValue[]; + angle: number; + type: 'linear' | 'radial'; +} + +export default function GradientCode({ className, values, angle, type }: GradientCodeProps) { const gradient = generateGradientValue({ values, angle, type }); return ( @@ -23,16 +35,3 @@ export default function GradientCode({ className, values, angle, type }) { ); } - -GradientCode.propTypes = { - className: PropTypes.string, - angle: PropTypes.number.isRequired, - type: PropTypes.oneOf(['radial', 'linear']).isRequired, - values: PropTypes.arrayOf( - PropTypes.shape({ - color: PropTypes.string.isRequired, - position: PropTypes.number.isRequired, - opacity: PropTypes.number.isRequired, - }) - ).isRequired, -}; diff --git a/src/apps/gradient-generator/GradientColors/GradientColors.jsx b/src/apps/gradient-generator/GradientColors/GradientColors.tsx similarity index 77% rename from src/apps/gradient-generator/GradientColors/GradientColors.jsx rename to src/apps/gradient-generator/GradientColors/GradientColors.tsx index 56ff9c3..d9c8aa1 100644 --- a/src/apps/gradient-generator/GradientColors/GradientColors.jsx +++ b/src/apps/gradient-generator/GradientColors/GradientColors.tsx @@ -1,13 +1,30 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; +import cx from 'clsx'; import { useTheme } from '../../../ThemeProvider'; import HexInput from '../../../components/HexInput/HexInput'; import SliderInput from '../../../components/SliderInput/SliderInput'; import X from './X'; import classes from './GradientColors.styles.less'; -export default function GradientColors({ className, values, handlers }) { +interface GradientValue { + key: string; + color: string; + position: number; + opacity: number; +} + +interface GradientHandlers { + setItemProp: (index: number, prop: string, value: any) => void; + remove: (index: number) => void; +} + +interface GradientColorsProps { + className?: string; + values: GradientValue[]; + handlers: GradientHandlers; +} + +export default function GradientColors({ className, values, handlers }: GradientColorsProps) { const [theme] = useTheme(); const items = values.map((value, index) => ( @@ -54,19 +71,3 @@ export default function GradientColors({ className, values, handlers }) { ); } - -GradientColors.propTypes = { - className: PropTypes.string, - values: PropTypes.arrayOf( - PropTypes.shape({ - color: PropTypes.string.isRequired, - position: PropTypes.number.isRequired, - opacity: PropTypes.number.isRequired, - }) - ).isRequired, - - handlers: PropTypes.shape({ - setItemProp: PropTypes.func.isRequired, - remove: PropTypes.func.isRequired, - }).isRequired, -}; diff --git a/src/apps/gradient-generator/GradientColors/X.jsx b/src/apps/gradient-generator/GradientColors/X.tsx similarity index 100% rename from src/apps/gradient-generator/GradientColors/X.jsx rename to src/apps/gradient-generator/GradientColors/X.tsx diff --git a/src/apps/gradient-generator/GradientGenerator.jsx b/src/apps/gradient-generator/GradientGenerator.tsx similarity index 99% rename from src/apps/gradient-generator/GradientGenerator.jsx rename to src/apps/gradient-generator/GradientGenerator.tsx index 93451b0..94e64ba 100644 --- a/src/apps/gradient-generator/GradientGenerator.jsx +++ b/src/apps/gradient-generator/GradientGenerator.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { useDocumentTitle, useListState, useLocalStorage } from 'xooks'; +import { useDocumentTitle, useListState, useLocalStorage } from '@hooks'; import { v4 } from 'uuid'; import Background from '../../components/Background/Background'; import SettingsLabel from '../../components/SettingsLabel/SettingsLabel'; diff --git a/src/apps/gradient-generator/GradientLine/ColorStop/ColorStop.jsx b/src/apps/gradient-generator/GradientLine/ColorStop/ColorStop.tsx similarity index 63% rename from src/apps/gradient-generator/GradientLine/ColorStop/ColorStop.jsx rename to src/apps/gradient-generator/GradientLine/ColorStop/ColorStop.tsx index c887c85..ddc9dc8 100644 --- a/src/apps/gradient-generator/GradientLine/ColorStop/ColorStop.jsx +++ b/src/apps/gradient-generator/GradientLine/ColorStop/ColorStop.tsx @@ -1,22 +1,54 @@ import React, { useRef } from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; +import cx from 'clsx'; import classes from './ColorStop.styles.less'; -export default function ColorStop({ className, value, values, handlers, lineRect, index }) { - const handle = useRef(null); - const start = useRef({}); - const offset = useRef({}); +interface GradientValue { + key: string; + color: string; + position: number; + opacity: number; +} + +interface ColorStopProps { + className?: string; + value: GradientValue; + values: GradientValue[]; + handlers: { + setState: (values: GradientValue[]) => void; + remove: (index: number) => void; + }; + lineRect: { left: number; top: number; width: number } | null; + index: number; +} + +export default function ColorStop({ + className, + value, + values, + handlers, + lineRect, + index, +}: ColorStopProps) { + const handle = useRef(null); + const start = useRef(0); + const offset = useRef(0); const removeColorStop = () => handlers.remove(index); const handleChange = (val) => { + if (!lineRect) { + return; + } let left = val; const { width } = lineRect; let position = 0; - if (left < 0) left = 0; - if (left > width) left = width; - position = parseInt((left / width) * 100, 10); + if (left < 0) { + left = 0; + } + if (left > width) { + left = width; + } + position = Math.round((left / width) * 100); const newValues = [...values]; newValues[index] = { ...newValues[index], position }; @@ -47,6 +79,9 @@ export default function ColorStop({ className, value, values, handlers, lineRect event.preventDefault(); event.stopPropagation(); event.nativeEvent.stopImmediatePropagation(); + if (!lineRect) { + return; + } const clientPos = event.clientX; start.current = clientPos - lineRect.left; @@ -68,40 +103,14 @@ export default function ColorStop({ className, value, values, handlers, lineRect e.stopPropagation(); e.nativeEvent.stopImmediatePropagation(); }} + onKeyDown={(e) => { + e.stopPropagation(); + }} + role="button" + tabIndex={0} >
); } - -ColorStop.propTypes = { - className: PropTypes.string, - - index: PropTypes.number.isRequired, - - values: PropTypes.arrayOf( - PropTypes.shape({ - color: PropTypes.string.isRequired, - position: PropTypes.number.isRequired, - opacity: PropTypes.number.isRequired, - }) - ).isRequired, - - value: PropTypes.shape({ - color: PropTypes.string.isRequired, - position: PropTypes.number.isRequired, - opacity: PropTypes.number.isRequired, - }).isRequired, - - handlers: PropTypes.shape({ - setState: PropTypes.func.isRequired, - remove: PropTypes.func.isRequired, - }).isRequired, - - lineRect: PropTypes.shape({ - left: PropTypes.number.isRequired, - top: PropTypes.number.isRequired, - width: PropTypes.number.isRequired, - }), -}; diff --git a/src/apps/gradient-generator/GradientLine/GradientLine.jsx b/src/apps/gradient-generator/GradientLine/GradientLine.tsx similarity index 68% rename from src/apps/gradient-generator/GradientLine/GradientLine.jsx rename to src/apps/gradient-generator/GradientLine/GradientLine.tsx index 4abd5cb..16f2a1d 100644 --- a/src/apps/gradient-generator/GradientLine/GradientLine.jsx +++ b/src/apps/gradient-generator/GradientLine/GradientLine.tsx @@ -1,13 +1,31 @@ import React, { useMemo, useState } from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; +import cx from 'clsx'; import { v4 } from 'uuid'; import { generateGradientColorValues } from '../generate-gradient-value'; import getRandomColor from './get-random-color'; import ColorStop from './ColorStop/ColorStop'; import classes from './GradientLine.styles.less'; -export default function GradientLine({ values, handlers, className }) { +interface GradientValue { + key: string; + color: string; + position: number; + opacity: number; +} + +interface GradientHandlers { + setState: (values: GradientValue[]) => void; + remove: (index: number) => void; + setItemProp: (index: number, prop: string, value: any) => void; +} + +interface GradientLineProps { + values: GradientValue[]; + handlers: GradientHandlers; + className?: string; +} + +export default function GradientLine({ values, handlers, className }: GradientLineProps) { const [lineProps, setLineProps] = useState(null); const parsedLineRect = useMemo(() => (lineProps ? JSON.parse(lineProps) : null), [lineProps]); const gradient = `linear-gradient(to right, ${generateGradientColorValues(values)})`; @@ -15,7 +33,7 @@ export default function GradientLine({ values, handlers, className }) { const handleColorAdd = (event) => { const rect = event.target.getBoundingClientRect(); const clickPosition = event.clientX - rect.left; - const position = parseInt((clickPosition / rect.width) * 100, 10); + const position = Math.round((clickPosition / rect.width) * 100); handlers.setState( values .concat({ color: getRandomColor(), position, opacity: 100, key: v4() }) @@ -41,6 +59,13 @@ export default function GradientLine({ values, handlers, className }) { className={classes.line} style={{ backgroundImage: gradient }} onClick={handleColorAdd} + onKeyDown={(event) => { + if (event.key === 'Enter' || event.key === ' ') { + handleColorAdd(event); + } + }} + role="button" + tabIndex={0} ref={(node) => node && setLineProps(JSON.stringify(node.getBoundingClientRect()))} /> @@ -48,18 +73,3 @@ export default function GradientLine({ values, handlers, className }) { ); } - -GradientLine.propTypes = { - className: PropTypes.string, - values: PropTypes.arrayOf( - PropTypes.shape({ - color: PropTypes.string.isRequired, - position: PropTypes.number.isRequired, - opacity: PropTypes.number.isRequired, - }) - ).isRequired, - - handlers: PropTypes.shape({ - setState: PropTypes.func.isRequired, - }).isRequired, -}; diff --git a/src/apps/gradient-generator/GradientPreview/GradientPreview.styles.less b/src/apps/gradient-generator/GradientPreview/GradientPreview.styles.less index 296028c..67b88d2 100644 --- a/src/apps/gradient-generator/GradientPreview/GradientPreview.styles.less +++ b/src/apps/gradient-generator/GradientPreview/GradientPreview.styles.less @@ -2,12 +2,17 @@ height: 220px; border-radius: 15px; background-color: @oc-white; - background-image: linear-gradient(45deg, @oc-gray-3 25%, transparent 25%), + background-image: + linear-gradient(45deg, @oc-gray-3 25%, transparent 25%), linear-gradient(-45deg, @oc-gray-3 25%, transparent 25%), linear-gradient(45deg, transparent 75%, @oc-gray-3 75%), linear-gradient(-45deg, transparent 75%, @oc-gray-3 75%); background-size: 16px 16px; - background-position: 0 0, 0 8px, 8px -8px, -8px 0px; + background-position: + 0 0, + 0 8px, + 8px -8px, + -8px 0px; overflow: hidden; } diff --git a/src/apps/gradient-generator/GradientPreview/GradientPreview.jsx b/src/apps/gradient-generator/GradientPreview/GradientPreview.tsx similarity index 50% rename from src/apps/gradient-generator/GradientPreview/GradientPreview.jsx rename to src/apps/gradient-generator/GradientPreview/GradientPreview.tsx index 55f478b..463493d 100644 --- a/src/apps/gradient-generator/GradientPreview/GradientPreview.jsx +++ b/src/apps/gradient-generator/GradientPreview/GradientPreview.tsx @@ -1,10 +1,22 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; +import cx from 'clsx'; import generateGradientValue from '../generate-gradient-value'; import classes from './GradientPreview.styles.less'; -export default function GradientPreview({ values, angle, className, type }) { +interface GradientValue { + color: string; + position: number; + opacity: number; +} + +interface GradientPreviewProps { + values: GradientValue[]; + angle: number; + className?: string; + type: 'linear' | 'radial'; +} + +export default function GradientPreview({ values, angle, className, type }: GradientPreviewProps) { const gradient = generateGradientValue({ angle, values, type }); return ( @@ -13,16 +25,3 @@ export default function GradientPreview({ values, angle, className, type }) { ); } - -GradientPreview.propTypes = { - className: PropTypes.string, - angle: PropTypes.number, - type: PropTypes.oneOf(['linear', 'radial']).isRequired, - values: PropTypes.arrayOf( - PropTypes.shape({ - color: PropTypes.string.isRequired, - position: PropTypes.number.isRequired, - opacity: PropTypes.number.isRequired, - }) - ).isRequired, -}; diff --git a/src/apps/gradient-generator/GradientSettings/GradientSettings.jsx b/src/apps/gradient-generator/GradientSettings/GradientSettings.tsx similarity index 80% rename from src/apps/gradient-generator/GradientSettings/GradientSettings.jsx rename to src/apps/gradient-generator/GradientSettings/GradientSettings.tsx index 96f1de5..ed84a8d 100644 --- a/src/apps/gradient-generator/GradientSettings/GradientSettings.jsx +++ b/src/apps/gradient-generator/GradientSettings/GradientSettings.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; +import cx from 'clsx'; import { useTheme } from '../../../ThemeProvider'; import DirectionPicker from '../../triangle-generator/DirectionPicker/DirectionPicker'; import SliderInput from '../../../components/SliderInput/SliderInput'; @@ -18,7 +17,21 @@ const DIRECTIONS = { 'bottom-right': 135, }; -export default function GradientSettings({ className, type, onTypeChange, angle, onAngleChange }) { +interface GradientSettingsProps { + className?: string; + type: 'linear' | 'radial'; + onTypeChange: (type: 'linear' | 'radial') => void; + angle: number; + onAngleChange: (value: number) => void; +} + +export default function GradientSettings({ + className, + type, + onTypeChange, + angle, + onAngleChange, +}: GradientSettingsProps) { const [theme] = useTheme(); const activeDirection = @@ -59,11 +72,3 @@ export default function GradientSettings({ className, type, onTypeChange, angle, ); } - -GradientSettings.propTypes = { - className: PropTypes.string, - type: PropTypes.oneOf(['linear', 'radial']).isRequired, - onTypeChange: PropTypes.func.isRequired, - angle: PropTypes.number, - onAngleChange: PropTypes.func.isRequired, -}; diff --git a/src/apps/gradient-generator/GradientsGallery/GradientGalleryItem/GradientGalleryItem.jsx b/src/apps/gradient-generator/GradientsGallery/GradientGalleryItem/GradientGalleryItem.tsx similarity index 72% rename from src/apps/gradient-generator/GradientsGallery/GradientGalleryItem/GradientGalleryItem.jsx rename to src/apps/gradient-generator/GradientsGallery/GradientGalleryItem/GradientGalleryItem.tsx index 56ec9e9..504a35c 100644 --- a/src/apps/gradient-generator/GradientsGallery/GradientGalleryItem/GradientGalleryItem.jsx +++ b/src/apps/gradient-generator/GradientsGallery/GradientGalleryItem/GradientGalleryItem.tsx @@ -1,7 +1,6 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; -import { useClipboard } from 'xooks'; +import cx from 'clsx'; +import { useClipboard } from '@hooks'; import { useTheme } from '../../../../ThemeProvider'; import Background from '../../../../components/Background/Background'; import { generateGradientColorValues } from '../../generate-gradient-value'; @@ -32,16 +31,3 @@ export default function GradientGalleryItem({ className, values, name, onEditorO ); } - -GradientGalleryItem.propTypes = { - className: PropTypes.string, - name: PropTypes.string.isRequired, - onEditorOpen: PropTypes.func.isRequired, - values: PropTypes.arrayOf( - PropTypes.shape({ - color: PropTypes.string.isRequired, - position: PropTypes.number.isRequired, - opacity: PropTypes.number.isRequired, - }) - ).isRequired, -}; diff --git a/src/apps/gradient-generator/GradientsGallery/GradientsGallery.jsx b/src/apps/gradient-generator/GradientsGallery/GradientsGallery.tsx similarity index 81% rename from src/apps/gradient-generator/GradientsGallery/GradientsGallery.jsx rename to src/apps/gradient-generator/GradientsGallery/GradientsGallery.tsx index 99a0be2..5123043 100644 --- a/src/apps/gradient-generator/GradientsGallery/GradientsGallery.jsx +++ b/src/apps/gradient-generator/GradientsGallery/GradientsGallery.tsx @@ -1,13 +1,19 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; +import cx from 'clsx'; import { v4 } from 'uuid'; import data from './data'; import SettingsLabel from '../../../components/SettingsLabel/SettingsLabel'; import GradientGalleryItem from './GradientGalleryItem/GradientGalleryItem'; import classes from './GradientsGallery.styles.less'; -export default function GradientsGallery({ className, handlers }) { +interface GradientsGalleryProps { + className?: string; + handlers: { + setState: (values: any[]) => void; + }; +} + +export default function GradientsGallery({ className, handlers }: GradientsGalleryProps) { const handleGradientPick = (values) => { window.scrollTo({ top: 0, behavior: 'smooth' }); handlers.setState(values.map((value) => ({ ...value, key: v4() }))); @@ -30,10 +36,3 @@ export default function GradientsGallery({ className, handlers }) { ); } - -GradientsGallery.propTypes = { - className: PropTypes.string, - handlers: PropTypes.shape({ - setState: PropTypes.func.isRequired, - }).isRequired, -}; diff --git a/src/apps/html-symbols/HtmlSymbols.styles.less b/src/apps/html-symbols/HtmlSymbols.styles.less index 8282d75..e178c7d 100644 --- a/src/apps/html-symbols/HtmlSymbols.styles.less +++ b/src/apps/html-symbols/HtmlSymbols.styles.less @@ -60,7 +60,9 @@ font-family: 'Source Sans Pro', sans-serif; color: @oc-white; opacity: 0; - transition: opacity 150ms ease, transform 150ms ease; + transition: + opacity 150ms ease, + transform 150ms ease; pointer-events: none; } } @@ -139,4 +141,4 @@ background-color: @oc-green-9; } } -} \ No newline at end of file +} diff --git a/src/apps/html-symbols/HtmlSymbols.jsx b/src/apps/html-symbols/HtmlSymbols.tsx similarity index 83% rename from src/apps/html-symbols/HtmlSymbols.jsx rename to src/apps/html-symbols/HtmlSymbols.tsx index d985bc1..ea9e397 100644 --- a/src/apps/html-symbols/HtmlSymbols.jsx +++ b/src/apps/html-symbols/HtmlSymbols.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; -import cx from 'classnames'; -import { useDocumentTitle, useLocalStorage, useClipboard } from 'xooks'; +import cx from 'clsx'; +import { useDocumentTitle, useLocalStorage, useClipboard } from '@hooks'; import { useTheme } from '../../ThemeProvider'; import Background from '../../components/Background/Background'; import Tabs from '../../components/Tabs/Tabs'; @@ -45,15 +45,17 @@ export default function HtmlSymbols() { const handleCopy = (value) => { setCopiedValue(value); - clipboard.copy(value) - } + clipboard.copy(value); + }; const results = searchSymbols(query, type).map((item) => ( {item.name} diff --git a/src/apps/lorem-ipsum/LoremIpsum.jsx b/src/apps/lorem-ipsum/LoremIpsum.tsx similarity index 99% rename from src/apps/lorem-ipsum/LoremIpsum.jsx rename to src/apps/lorem-ipsum/LoremIpsum.tsx index 1559381..1e06c2c 100644 --- a/src/apps/lorem-ipsum/LoremIpsum.jsx +++ b/src/apps/lorem-ipsum/LoremIpsum.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { useDocumentTitle, useClipboard, useLocalStorage } from 'xooks'; +import { useDocumentTitle, useClipboard, useLocalStorage } from '@hooks'; import generateText from './generate-text'; import Settings from './Settings/Settings'; import Preview from './Preview/Preview'; diff --git a/src/apps/lorem-ipsum/Preview/Preview.jsx b/src/apps/lorem-ipsum/Preview/Preview.tsx similarity index 78% rename from src/apps/lorem-ipsum/Preview/Preview.jsx rename to src/apps/lorem-ipsum/Preview/Preview.tsx index a0ea2c1..d813112 100644 --- a/src/apps/lorem-ipsum/Preview/Preview.jsx +++ b/src/apps/lorem-ipsum/Preview/Preview.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; +import cx from 'clsx'; import { useTheme } from '../../../ThemeProvider'; import Background from '../../../components/Background/Background'; import classes from './Preview.styles.less'; @@ -15,7 +14,3 @@ export default function Preview({ text }) { return {nodes}; } - -Preview.propTypes = { - text: PropTypes.string.isRequired, -}; diff --git a/src/apps/lorem-ipsum/Settings/Settings.jsx b/src/apps/lorem-ipsum/Settings/Settings.tsx similarity index 80% rename from src/apps/lorem-ipsum/Settings/Settings.jsx rename to src/apps/lorem-ipsum/Settings/Settings.tsx index c66a827..7b6c05e 100644 --- a/src/apps/lorem-ipsum/Settings/Settings.jsx +++ b/src/apps/lorem-ipsum/Settings/Settings.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import SliderInput from '../../../components/SliderInput/SliderInput'; import Button from '../../../components/Button/Button'; import Tabs from '../../../components/Tabs/Tabs'; @@ -34,12 +33,3 @@ export default function Settings({ onTypeChange, type, length, onLengthChange, o ); } - -Settings.propTypes = { - onTypeChange: PropTypes.func.isRequired, - onLengthChange: PropTypes.func.isRequired, - onSubmit: PropTypes.func.isRequired, - copied: PropTypes.bool.isRequired, - type: PropTypes.oneOf(AVAILABLE_GENERATORS).isRequired, - length: PropTypes.number.isRequired, -}; diff --git a/src/apps/lorem-ipsum/generate-text.js b/src/apps/lorem-ipsum/generate-text.js index 954f53b..04ad568 100644 --- a/src/apps/lorem-ipsum/generate-text.js +++ b/src/apps/lorem-ipsum/generate-text.js @@ -3,9 +3,9 @@ import pokeipsum from 'pokeipsum'; import samuelIpsum from 'samuel-ipsum'; const generators = { - lorem: length => loremIpsum({ count: length, units: 'paragraphs' }), + lorem: (length) => loremIpsum({ count: length, units: 'paragraphs' }), pokemon: pokeipsum.paragraphs, - samuel: length => samuelIpsum.generateParagraphs(length).join('\n'), + samuel: (length) => samuelIpsum.generateParagraphs(length).join('\n'), }; export const AVAILABLE_GENERATORS = Object.keys(generators); diff --git a/src/apps/page-dividers/Output/Output.jsx b/src/apps/page-dividers/Output/Output.tsx similarity index 71% rename from src/apps/page-dividers/Output/Output.jsx rename to src/apps/page-dividers/Output/Output.tsx index 2c6f5c8..6ccc0e2 100644 --- a/src/apps/page-dividers/Output/Output.jsx +++ b/src/apps/page-dividers/Output/Output.tsx @@ -1,11 +1,9 @@ import React from 'react'; -import PropTypes from 'prop-types'; import Button from '../../../components/Button/Button'; import Background from '../../../components/Background/Background'; import Highlight from '../../../components/Highlight/Highlight'; import SettingsLabel from '../../../components/SettingsLabel/SettingsLabel'; import generateExample from './generate-example'; -import { shapes } from '../Shape/Shape'; import * as assets from '../assets'; import classes from './Output.styles.less'; @@ -29,14 +27,3 @@ export default function Output({ values }) { ); } - -Output.propTypes = { - values: PropTypes.shape({ - position: PropTypes.oneOf(['top', 'bottom']).isRequired, - direction: PropTypes.oneOf(['normal', 'reverse']).isRequired, - color: PropTypes.string.isRequired, - type: PropTypes.oneOf(Object.keys(shapes)).isRequired, - width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, - }), -}; diff --git a/src/apps/page-dividers/PageDividers.jsx b/src/apps/page-dividers/PageDividers.tsx similarity index 96% rename from src/apps/page-dividers/PageDividers.jsx rename to src/apps/page-dividers/PageDividers.tsx index 2408109..a3104d5 100644 --- a/src/apps/page-dividers/PageDividers.jsx +++ b/src/apps/page-dividers/PageDividers.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { useDocumentTitle, useLocalStorage } from 'xooks'; +import { useDocumentTitle, useLocalStorage } from '@hooks'; import Settings from './Settings/Settings'; import Output from './Output/Output'; import Preview from './Preview/Preview'; diff --git a/src/apps/page-dividers/Preview/Preview.styles.less b/src/apps/page-dividers/Preview/Preview.styles.less index d976af9..ee24d34 100644 --- a/src/apps/page-dividers/Preview/Preview.styles.less +++ b/src/apps/page-dividers/Preview/Preview.styles.less @@ -14,7 +14,10 @@ overflow: hidden; border-top-right-radius: 14px; border-top-left-radius: 14px; - transition: transform 250ms ease, top 250ms ease, bottom 250ms ease; + transition: + transform 250ms ease, + top 250ms ease, + bottom 250ms ease; } .top { diff --git a/src/apps/page-dividers/Preview/Preview.jsx b/src/apps/page-dividers/Preview/Preview.tsx similarity index 51% rename from src/apps/page-dividers/Preview/Preview.jsx rename to src/apps/page-dividers/Preview/Preview.tsx index bd54aba..f6d3e6d 100644 --- a/src/apps/page-dividers/Preview/Preview.jsx +++ b/src/apps/page-dividers/Preview/Preview.tsx @@ -1,7 +1,6 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; -import Shape, { shapes } from '../Shape/Shape'; +import cx from 'clsx'; +import Shape from '../Shape/Shape'; import classes from './Preview.styles.less'; export default function Preview({ values }) { @@ -22,14 +21,3 @@ export default function Preview({ values }) { ); } - -Preview.propTypes = { - values: PropTypes.shape({ - position: PropTypes.oneOf(['top', 'bottom']).isRequired, - direction: PropTypes.oneOf(['normal', 'reverse']).isRequired, - color: PropTypes.string.isRequired, - type: PropTypes.oneOf(Object.keys(shapes)).isRequired, - width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, - }), -}; diff --git a/src/apps/page-dividers/Settings/Settings.jsx b/src/apps/page-dividers/Settings/Settings.tsx similarity index 79% rename from src/apps/page-dividers/Settings/Settings.jsx rename to src/apps/page-dividers/Settings/Settings.tsx index 396e02d..5f98343 100644 --- a/src/apps/page-dividers/Settings/Settings.jsx +++ b/src/apps/page-dividers/Settings/Settings.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; +import cx from 'clsx'; import { useTheme } from '../../../ThemeProvider'; import Tabs from '../../../components/Tabs/Tabs'; import SliderInput from '../../../components/SliderInput/SliderInput'; @@ -94,23 +93,3 @@ export default function Settings({ values, handlers }) { ); } - -Settings.propTypes = { - values: PropTypes.shape({ - position: PropTypes.oneOf(['top', 'bottom']).isRequired, - direction: PropTypes.oneOf(['normal', 'reverse']).isRequired, - color: PropTypes.string.isRequired, - type: PropTypes.oneOf(Object.keys(shapes)).isRequired, - width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, - }), - - handlers: PropTypes.shape({ - onTypeChange: PropTypes.func.isRequired, - onColorChange: PropTypes.func.isRequired, - onWidthChange: PropTypes.func.isRequired, - onHeightChange: PropTypes.func.isRequired, - onPositionChange: PropTypes.func.isRequired, - onDirectionChange: PropTypes.func.isRequired, - }).isRequired, -}; diff --git a/src/apps/page-dividers/Shape/Shape.jsx b/src/apps/page-dividers/Shape/Shape.tsx similarity index 79% rename from src/apps/page-dividers/Shape/Shape.jsx rename to src/apps/page-dividers/Shape/Shape.tsx index 05d1425..2d8f172 100644 --- a/src/apps/page-dividers/Shape/Shape.jsx +++ b/src/apps/page-dividers/Shape/Shape.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import Waves from './Waves'; import WavesOpaque from './WavesOpaque'; @@ -21,7 +20,3 @@ export default function Shape({ shape, ...others }) { return null; } - -Shape.propTypes = { - shape: PropTypes.oneOf(Object.keys(shapes)).isRequired, -}; diff --git a/src/apps/page-dividers/Shape/Tilt.jsx b/src/apps/page-dividers/Shape/Tilt.tsx similarity index 100% rename from src/apps/page-dividers/Shape/Tilt.jsx rename to src/apps/page-dividers/Shape/Tilt.tsx diff --git a/src/apps/page-dividers/Shape/Triangles.jsx b/src/apps/page-dividers/Shape/Triangles.tsx similarity index 100% rename from src/apps/page-dividers/Shape/Triangles.jsx rename to src/apps/page-dividers/Shape/Triangles.tsx diff --git a/src/apps/page-dividers/Shape/Waves.jsx b/src/apps/page-dividers/Shape/Waves.tsx similarity index 100% rename from src/apps/page-dividers/Shape/Waves.jsx rename to src/apps/page-dividers/Shape/Waves.tsx diff --git a/src/apps/page-dividers/Shape/WavesOpaque.jsx b/src/apps/page-dividers/Shape/WavesOpaque.tsx similarity index 100% rename from src/apps/page-dividers/Shape/WavesOpaque.jsx rename to src/apps/page-dividers/Shape/WavesOpaque.tsx diff --git a/src/apps/page-dividers/assets/index.js b/src/apps/page-dividers/assets/index.js index 37f345d..dfccc01 100644 --- a/src/apps/page-dividers/assets/index.js +++ b/src/apps/page-dividers/assets/index.js @@ -1,4 +1,4 @@ -export tilt from './tilt.svg'; -export waves_opaque from './waves-opaque.svg'; -export waves from './waves.svg'; -export triangles from './triangles.svg'; +export { default as tilt } from './tilt.svg'; +export { default as waves_opaque } from './waves-opaque.svg'; +export { default as waves } from './waves.svg'; +export { default as triangles } from './triangles.svg'; diff --git a/src/apps/svg-compressor/CompressedResult/CompressedResult.jsx b/src/apps/svg-compressor/CompressedResult/CompressedResult.tsx similarity index 87% rename from src/apps/svg-compressor/CompressedResult/CompressedResult.jsx rename to src/apps/svg-compressor/CompressedResult/CompressedResult.tsx index b8e05df..68f96be 100644 --- a/src/apps/svg-compressor/CompressedResult/CompressedResult.jsx +++ b/src/apps/svg-compressor/CompressedResult/CompressedResult.tsx @@ -1,8 +1,7 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; -import { useHistory } from 'react-router-dom'; -import { useClipboard } from 'xooks'; +import cx from 'clsx'; +import { useNavigate } from 'react-router-dom'; +import { useClipboard } from '@hooks'; import { useTheme } from '../../../ThemeProvider'; import SettingsLabel from '../../../components/SettingsLabel/SettingsLabel'; import Background from '../../../components/Background/Background'; @@ -13,17 +12,17 @@ import classes from './CompressedResult.styles.less'; export default function CompressedResult({ content, fileKey }) { const [theme] = useTheme(); - const history = useHistory(); + const navigate = useNavigate(); const { copied, copy } = useClipboard(); const convertToJsx = () => { localStorage.setItem('@omatsuri/conversion-after-compression/jsx', JSON.stringify(content)); - history.push('/svg-to-jsx'); + navigate('/svg-to-jsx'); }; const convertToB64 = () => { localStorage.setItem('@omatsuri/conversion-after-compression/b64', JSON.stringify(content)); - history.push('/b64-encoding'); + navigate('/b64-encoding'); }; return ( @@ -70,8 +69,3 @@ export default function CompressedResult({ content, fileKey }) { ); } - -CompressedResult.propTypes = { - content: PropTypes.string, - fileKey: PropTypes.string, -}; diff --git a/src/apps/svg-compressor/Output/Output.jsx b/src/apps/svg-compressor/Output/Output.tsx similarity index 75% rename from src/apps/svg-compressor/Output/Output.jsx rename to src/apps/svg-compressor/Output/Output.tsx index 5ad1c1a..44a0612 100644 --- a/src/apps/svg-compressor/Output/Output.jsx +++ b/src/apps/svg-compressor/Output/Output.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import Background from '../../../components/Background/Background'; import CompressedResult from '../CompressedResult/CompressedResult'; import classes from './Output.styles.less'; @@ -29,14 +28,3 @@ export default function Output({ results }) { return items; } - -Output.propTypes = { - results: PropTypes.objectOf( - PropTypes.shape({ - content: PropTypes.string, - error: PropTypes.string, - loading: PropTypes.bool, - queue: PropTypes.number.isRequired, - }) - ), -}; diff --git a/src/apps/svg-compressor/SvgCompressor.jsx b/src/apps/svg-compressor/SvgCompressor.tsx similarity index 73% rename from src/apps/svg-compressor/SvgCompressor.jsx rename to src/apps/svg-compressor/SvgCompressor.tsx index cfa210e..d07c23c 100644 --- a/src/apps/svg-compressor/SvgCompressor.jsx +++ b/src/apps/svg-compressor/SvgCompressor.tsx @@ -1,13 +1,10 @@ import React, { useState, useLayoutEffect, useRef } from 'react'; -import { useLocalStorage, useDocumentTitle } from 'xooks'; +import { useLocalStorage, useDocumentTitle } from '@hooks'; import SvgInput from '../../components/SvgInput/SvgInput'; -import SvgoWorker from '../../workers/svgo.worker'; import processSvgFile from '../../utils/process-svg-file'; import formatFileName from './format-file-name'; import Output from './Output/Output'; -const svgo = new SvgoWorker(); - const INITIAL_PROGRESS_STATE = { loading: false, output: null, @@ -16,22 +13,26 @@ const INITIAL_PROGRESS_STATE = { export default function SvgCompressor() { useDocumentTitle('SVG compressor'); + const svgo = useRef(null); + if (!svgo.current) { + svgo.current = new Worker(new URL('../../workers/svgo.worker.js', import.meta.url)); + } const ls = useLocalStorage({ key: '@omatsuri/svg-compressor', delay: 500 }); const [value, setValue] = useState(ls.retrieve() || ''); - const [results, setResults] = useState({}); + const [results, setResults] = useState>({}); const queue = useRef(0); const incrementQueue = () => { queue.current += 1; }; - const postTextValue = (text) => - svgo.postMessage({ + const postTextValue = (text: string) => + svgo.current?.postMessage({ content: text, payload: { name: 'file', index: 'input', queue: queue.current }, }); - const handleSvgoMessage = (event) => { + const handleSvgoMessage = (event: MessageEvent) => { const { index, name, queue: q } = event.data.payload; setResults((current) => ({ ...current, @@ -45,16 +46,24 @@ export default function SvgCompressor() { }; useLayoutEffect(() => { - svgo.addEventListener('message', handleSvgoMessage); + const worker = svgo.current; + if (!worker) { + return undefined; + } + + worker.addEventListener('message', handleSvgoMessage); if (value.trim().length > 0) { postTextValue(value); } - return () => svgo.removeEventListener('message', handleSvgoMessage); + return () => { + worker.removeEventListener('message', handleSvgoMessage); + worker.terminate(); + }; }, []); - const handleFilesDrop = (files) => { + const handleFilesDrop = (files: File[]) => { incrementQueue(); Promise.all(files.map((file) => processSvgFile(file))).then((filesData) => { setResults((current) => @@ -71,7 +80,7 @@ export default function SvgCompressor() { ); filesData.forEach((fileData, index) => { - svgo.postMessage({ + svgo.current?.postMessage({ content: fileData.text, payload: { name: fileData.file.name, index, queue: queue.current }, }); @@ -79,7 +88,7 @@ export default function SvgCompressor() { }); }; - const handleChange = (text) => { + const handleChange = (text: string) => { setValue(text); incrementQueue(); ls.save(text); diff --git a/src/apps/svg-compressor/format-file-name.js b/src/apps/svg-compressor/format-file-name.js index 1e40e0a..40e7b58 100644 --- a/src/apps/svg-compressor/format-file-name.js +++ b/src/apps/svg-compressor/format-file-name.js @@ -4,10 +4,7 @@ export default function formatFileName(key) { } if (key !== 'input_file') { - return key - .split('_') - .slice(1) - .join('_'); + return key.split('_').slice(1).join('_'); } return 'from input'; diff --git a/src/apps/svg-to-jsx/Output/Output.jsx b/src/apps/svg-to-jsx/Output/Output.tsx similarity index 77% rename from src/apps/svg-to-jsx/Output/Output.jsx rename to src/apps/svg-to-jsx/Output/Output.tsx index bb1f0a2..7810f98 100644 --- a/src/apps/svg-to-jsx/Output/Output.jsx +++ b/src/apps/svg-to-jsx/Output/Output.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import Background from '../../../components/Background/Background'; import Highlight from '../../../components/Highlight/Highlight'; import classes from './Output.styles.less'; @@ -21,10 +20,3 @@ export default function Output({ data }) { return null; } - -Output.propTypes = { - data: PropTypes.shape({ - content: PropTypes.string, - loading: PropTypes.bool.isRequired, - }), -}; diff --git a/src/apps/svg-to-jsx/SvgToJsx.jsx b/src/apps/svg-to-jsx/SvgToJsx.tsx similarity index 60% rename from src/apps/svg-to-jsx/SvgToJsx.jsx rename to src/apps/svg-to-jsx/SvgToJsx.tsx index d76bdd1..1c3e05a 100644 --- a/src/apps/svg-to-jsx/SvgToJsx.jsx +++ b/src/apps/svg-to-jsx/SvgToJsx.tsx @@ -1,45 +1,60 @@ -import React, { useState, useLayoutEffect } from 'react'; -import { useDocumentTitle, useLocalStorage } from 'xooks'; +import React, { useState, useLayoutEffect, useRef } from 'react'; +import { useDocumentTitle, useLocalStorage } from '@hooks'; import SvgInput from '../../components/SvgInput/SvgInput'; -import Svg2jsxWorker from '../../workers/svg-to-jsx.worker'; import processSvgFile from '../../utils/process-svg-file'; import Output from './Output/Output'; -const svg2jsx = new Svg2jsxWorker(); - export default function SvgToJsx() { useDocumentTitle('SVG to JSX'); + const svg2jsx = useRef(null); + if (!svg2jsx.current) { + svg2jsx.current = new Worker(new URL('../../workers/svg-to-jsx.worker.js', import.meta.url)); + } const ls = useLocalStorage({ key: '@omatsuri/svg-to-jsx', delay: 1000 }); const transmittedValue = useLocalStorage({ key: '@omatsuri/conversion-after-compression/jsx' }); const [value, setValue] = useState(transmittedValue.retrieveAndClean() || ls.retrieve() || ''); - const [result, setResult] = useState({ loading: false, error: null, content: null }); + const [result, setResult] = useState({ + loading: false, + error: null, + content: null as string | null, + }); - const handleMessage = (event) => { + const handleMessage = (event: MessageEvent) => { setResult({ loading: false, error: event.data.error, content: event.data.code }); }; - const postMessage = (text) => svg2jsx.postMessage({ content: text }); + const postMessage = (text: string) => { + svg2jsx.current?.postMessage({ content: text }); + }; useLayoutEffect(() => { - svg2jsx.addEventListener('message', handleMessage); + const worker = svg2jsx.current; + if (!worker) { + return undefined; + } + + worker.addEventListener('message', handleMessage); if (value.trim().length > 0) { setResult({ loading: true, content: null, error: null }); postMessage(value); } - return () => svg2jsx.removeEventListener('message', handleMessage); + return () => { + worker.removeEventListener('message', handleMessage); + worker.terminate(); + }; }, []); - const handleChange = (text) => { + const handleChange = (text: string) => { setValue(text); ls.save(text); setResult({ loading: true, content: null, error: null }); postMessage(text); }; - const handleFilesDrop = (files) => { + const handleFilesDrop = (files: File[]) => { if (files.length > 0) { processSvgFile(files[0]).then((file) => handleChange(file.text)); } diff --git a/src/apps/triangle-generator/Code/Code.jsx b/src/apps/triangle-generator/Code/Code.tsx similarity index 78% rename from src/apps/triangle-generator/Code/Code.jsx rename to src/apps/triangle-generator/Code/Code.tsx index b5a20c1..748024b 100644 --- a/src/apps/triangle-generator/Code/Code.jsx +++ b/src/apps/triangle-generator/Code/Code.tsx @@ -1,10 +1,8 @@ import React, { useState } from 'react'; -import PropTypes from 'prop-types'; -import { useLocalStorage } from 'xooks'; +import { useLocalStorage } from '@hooks'; import Tabs from '../../../components/Tabs/Tabs'; import Background from '../../../components/Background/Background'; import Highlight from '../../../components/Highlight/Highlight'; -import directions from '../directions'; import generateExample from './generate-example'; import classes from './Code.styles.less'; @@ -40,12 +38,3 @@ export default function Code({ values }) { ); } - -Code.propTypes = { - values: PropTypes.shape({ - direction: PropTypes.oneOf(directions).isRequired, - height: PropTypes.number.isRequired, - width: PropTypes.number.isRequired, - color: PropTypes.string.isRequired, - }).isRequired, -}; diff --git a/src/apps/triangle-generator/ColorPicker/ColorPicker.jsx b/src/apps/triangle-generator/ColorPicker/ColorPicker.tsx similarity index 84% rename from src/apps/triangle-generator/ColorPicker/ColorPicker.jsx rename to src/apps/triangle-generator/ColorPicker/ColorPicker.tsx index 6658077..3d57bf9 100644 --- a/src/apps/triangle-generator/ColorPicker/ColorPicker.jsx +++ b/src/apps/triangle-generator/ColorPicker/ColorPicker.tsx @@ -1,6 +1,5 @@ import oc from 'open-color'; import React from 'react'; -import PropTypes from 'prop-types'; import Swatch from './Swatch/Swatch'; import HexInput from '../../../components/HexInput/HexInput'; import classes from './ColorPicker.styles.less'; @@ -32,8 +31,3 @@ export default function ColorPicker({ value, onChange }) { ); } - -ColorPicker.propTypes = { - value: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired, -}; diff --git a/src/apps/triangle-generator/ColorPicker/Swatch/Swatch.styles.less b/src/apps/triangle-generator/ColorPicker/Swatch/Swatch.styles.less index e5546f9..09c1043 100644 --- a/src/apps/triangle-generator/ColorPicker/Swatch/Swatch.styles.less +++ b/src/apps/triangle-generator/ColorPicker/Swatch/Swatch.styles.less @@ -5,7 +5,9 @@ width: 18px; height: 18px; opacity: 0.8; - transition: opacity 150ms ease, transform 150ms ease; + transition: + opacity 150ms ease, + transform 150ms ease; outline: 0; cursor: pointer; diff --git a/src/apps/triangle-generator/ColorPicker/Swatch/Swatch.jsx b/src/apps/triangle-generator/ColorPicker/Swatch/Swatch.tsx similarity index 65% rename from src/apps/triangle-generator/ColorPicker/Swatch/Swatch.jsx rename to src/apps/triangle-generator/ColorPicker/Swatch/Swatch.tsx index c2e4771..3cafd6b 100644 --- a/src/apps/triangle-generator/ColorPicker/Swatch/Swatch.jsx +++ b/src/apps/triangle-generator/ColorPicker/Swatch/Swatch.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; +import cx from 'clsx'; import classes from './Swatch.styles.less'; export default function Swatch({ className, value, ...others }) { @@ -13,8 +12,3 @@ export default function Swatch({ className, value, ...others }) { /> ); } - -Swatch.propTypes = { - className: PropTypes.string, - value: PropTypes.string.isRequired, -}; diff --git a/src/apps/triangle-generator/DirectionPicker/Chevron.jsx b/src/apps/triangle-generator/DirectionPicker/Chevron.tsx similarity index 86% rename from src/apps/triangle-generator/DirectionPicker/Chevron.jsx rename to src/apps/triangle-generator/DirectionPicker/Chevron.tsx index a9e6f3b..c788e49 100644 --- a/src/apps/triangle-generator/DirectionPicker/Chevron.jsx +++ b/src/apps/triangle-generator/DirectionPicker/Chevron.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; const angles = { top: 180, @@ -37,7 +36,3 @@ export default function Chevron({ direction }) { ); } - -Chevron.propTypes = { - direction: PropTypes.oneOf(Object.keys(angles)).isRequired, -}; diff --git a/src/apps/triangle-generator/DirectionPicker/DirectionPicker.jsx b/src/apps/triangle-generator/DirectionPicker/DirectionPicker.tsx similarity index 77% rename from src/apps/triangle-generator/DirectionPicker/DirectionPicker.jsx rename to src/apps/triangle-generator/DirectionPicker/DirectionPicker.tsx index 13edb1c..9a2f0aa 100644 --- a/src/apps/triangle-generator/DirectionPicker/DirectionPicker.jsx +++ b/src/apps/triangle-generator/DirectionPicker/DirectionPicker.tsx @@ -1,12 +1,17 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; +import cx from 'clsx'; import { useTheme } from '../../../ThemeProvider'; import directions from '../directions'; import Chevron from './Chevron'; import classes from './DirectionPicker.styles.less'; -export default function DirectionPicker({ className, value, onChange }) { +interface DirectionPickerProps { + className?: string; + value?: string; + onChange: (value: string) => void; +} + +export default function DirectionPicker({ className, value, onChange }: DirectionPickerProps) { const [theme] = useTheme(); const contols = directions.map((direction) => ( @@ -24,9 +29,3 @@ export default function DirectionPicker({ className, value, onChange }) { return
{contols}
; } - -DirectionPicker.propTypes = { - className: PropTypes.string, - onChange: PropTypes.func.isRequired, - value: PropTypes.oneOf(directions), -}; diff --git a/src/apps/triangle-generator/Settings/Settings.jsx b/src/apps/triangle-generator/Settings/Settings.tsx similarity index 67% rename from src/apps/triangle-generator/Settings/Settings.jsx rename to src/apps/triangle-generator/Settings/Settings.tsx index 1c4b167..48a1e64 100644 --- a/src/apps/triangle-generator/Settings/Settings.jsx +++ b/src/apps/triangle-generator/Settings/Settings.tsx @@ -1,8 +1,6 @@ import React from 'react'; -import PropTypes from 'prop-types'; import Background from '../../../components/Background/Background'; import SettingsLabel from '../../../components/SettingsLabel/SettingsLabel'; -import directions from '../directions'; import DirectionPicker from '../DirectionPicker/DirectionPicker'; import SizePicker from '../SizePicker/SizePicker'; import ColorPicker from '../ColorPicker/ColorPicker'; @@ -37,22 +35,3 @@ export default function Settings({ values, handlers }) { ); } - -Settings.propTypes = { - values: PropTypes.shape({ - predefinedSizes: PropTypes.object, - activePredefinedSize: PropTypes.string, - direction: PropTypes.oneOf(directions).isRequired, - height: PropTypes.number.isRequired, - width: PropTypes.number.isRequired, - color: PropTypes.string.isRequired, - }).isRequired, - - handlers: PropTypes.shape({ - onDirectionChange: PropTypes.func.isRequired, - onWidthChange: PropTypes.func.isRequired, - onHeightChange: PropTypes.func.isRequired, - onColorChange: PropTypes.func.isRequired, - setPredefinedSize: PropTypes.func.isRequired, - }), -}; diff --git a/src/apps/triangle-generator/SizePicker/SizePicker.jsx b/src/apps/triangle-generator/SizePicker/SizePicker.tsx similarity index 71% rename from src/apps/triangle-generator/SizePicker/SizePicker.jsx rename to src/apps/triangle-generator/SizePicker/SizePicker.tsx index 8f88051..7f86aac 100644 --- a/src/apps/triangle-generator/SizePicker/SizePicker.jsx +++ b/src/apps/triangle-generator/SizePicker/SizePicker.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { useTheme } from '../../../ThemeProvider'; import Tabs from '../../../components/Tabs/Tabs'; import SliderInput from '../../../components/SliderInput/SliderInput'; @@ -36,15 +35,3 @@ export default function SizePicker({ ); } - -SizePicker.propTypes = { - setPredefinedSize: PropTypes.func.isRequired, - activePredefinedSize: PropTypes.oneOf(['sm', 'md', 'lg', 'xl']), - predefinedSizes: PropTypes.object.isRequired, - value: PropTypes.shape({ - width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, - }).isRequired, - onWidthChange: PropTypes.func.isRequired, - onHeightChange: PropTypes.func.isRequired, -}; diff --git a/src/apps/triangle-generator/Triangle/Triangle.jsx b/src/apps/triangle-generator/Triangle/Triangle.tsx similarity index 54% rename from src/apps/triangle-generator/Triangle/Triangle.jsx rename to src/apps/triangle-generator/Triangle/Triangle.tsx index 7d9b89f..a1f9d5e 100644 --- a/src/apps/triangle-generator/Triangle/Triangle.jsx +++ b/src/apps/triangle-generator/Triangle/Triangle.tsx @@ -1,6 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import directions from '../directions'; import getTriangleStyles from '../get-triangle-styles'; export default function Triangle({ width, height, color, direction }) { @@ -13,10 +11,3 @@ export default function Triangle({ width, height, color, direction }) { /> ); } - -Triangle.propTypes = { - direction: PropTypes.oneOf(directions).isRequired, - height: PropTypes.number.isRequired, - width: PropTypes.number.isRequired, - color: PropTypes.string.isRequired, -}; diff --git a/src/apps/triangle-generator/TriangleGenerator.jsx b/src/apps/triangle-generator/TriangleGenerator.tsx similarity index 97% rename from src/apps/triangle-generator/TriangleGenerator.jsx rename to src/apps/triangle-generator/TriangleGenerator.tsx index c070cca..01aee6e 100644 --- a/src/apps/triangle-generator/TriangleGenerator.jsx +++ b/src/apps/triangle-generator/TriangleGenerator.tsx @@ -1,7 +1,7 @@ import oc from 'open-color'; import React, { useState, useEffect } from 'react'; import Color from 'color'; -import { useDocumentTitle, useLocalStorage } from 'xooks'; +import { useDocumentTitle, useLocalStorage } from '@hooks'; import Settings from './Settings/Settings'; import TrianglePreview from './TrianglePreview/TrianglePreview'; import Code from './Code/Code'; diff --git a/src/apps/triangle-generator/TrianglePreview/ThemeToggle/ThemeToggle.jsx b/src/apps/triangle-generator/TrianglePreview/ThemeToggle/ThemeToggle.tsx similarity index 70% rename from src/apps/triangle-generator/TrianglePreview/ThemeToggle/ThemeToggle.jsx rename to src/apps/triangle-generator/TrianglePreview/ThemeToggle/ThemeToggle.tsx index 96bdbc6..6d3cbeb 100644 --- a/src/apps/triangle-generator/TrianglePreview/ThemeToggle/ThemeToggle.jsx +++ b/src/apps/triangle-generator/TrianglePreview/ThemeToggle/ThemeToggle.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; +import cx from 'clsx'; import ThemeControl from '../../../../components/ThemeControl/ThemeIcon'; import classes from './ThemeToggle.styles.less'; @@ -18,10 +17,3 @@ export default function ThemeToggle({ className, theme, onToggle, label = 'mode' ); } - -ThemeToggle.propTypes = { - className: PropTypes.string, - theme: PropTypes.oneOf(['light', 'dark']).isRequired, - onToggle: PropTypes.func.isRequired, - label: PropTypes.string, -}; diff --git a/src/apps/triangle-generator/TrianglePreview/TrianglePreview.jsx b/src/apps/triangle-generator/TrianglePreview/TrianglePreview.tsx similarity index 58% rename from src/apps/triangle-generator/TrianglePreview/TrianglePreview.jsx rename to src/apps/triangle-generator/TrianglePreview/TrianglePreview.tsx index 3858ee8..4a2de79 100644 --- a/src/apps/triangle-generator/TrianglePreview/TrianglePreview.jsx +++ b/src/apps/triangle-generator/TrianglePreview/TrianglePreview.tsx @@ -1,10 +1,8 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; +import cx from 'clsx'; import Triangle from '../Triangle/Triangle'; import Background from '../../../components/Background/Background'; import ThemeToggle from './ThemeToggle/ThemeToggle'; -import directions from '../directions'; import classes from './TrianglePreview.styles.less'; export default function TrianglePreview({ values, theme, onThemeToggle }) { @@ -22,14 +20,3 @@ export default function TrianglePreview({ values, theme, onThemeToggle }) { ); } - -TrianglePreview.propTypes = { - onThemeToggle: PropTypes.func.isRequired, - theme: PropTypes.oneOf(['light', 'dark']).isRequired, - values: PropTypes.shape({ - direction: PropTypes.oneOf(directions).isRequired, - height: PropTypes.number.isRequired, - width: PropTypes.number.isRequired, - color: PropTypes.string.isRequired, - }).isRequired, -}; diff --git a/src/components/AppContainer/AppContainer.jsx b/src/components/AppContainer/AppContainer.tsx similarity index 65% rename from src/components/AppContainer/AppContainer.jsx rename to src/components/AppContainer/AppContainer.tsx index 750a63f..8e2bf36 100644 --- a/src/components/AppContainer/AppContainer.jsx +++ b/src/components/AppContainer/AppContainer.tsx @@ -1,11 +1,14 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; +import cx from 'clsx'; import { useTheme } from '../../ThemeProvider'; import Navbar from '../Navbar/Navbar'; import classes from './AppContainer.styles.less'; -export default function AppContainer({ children }) { +interface AppContainerProps { + children: React.ReactNode; +} + +export default function AppContainer({ children }: AppContainerProps) { const [theme] = useTheme(); return ( @@ -15,7 +18,3 @@ export default function AppContainer({ children }) { ); } - -AppContainer.propTypes = { - children: PropTypes.node.isRequired, -}; diff --git a/src/components/Background/Background.jsx b/src/components/Background/Background.jsx deleted file mode 100644 index 93af40d..0000000 --- a/src/components/Background/Background.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; -import { useTheme } from '../../ThemeProvider'; -import classes from './Background.styles.less'; - -export default function Background({ className, component: Element = 'div', ...others }) { - const [theme] = useTheme(); - return ; -} - -Background.propTypes = { - className: PropTypes.string, - component: PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.func]), -}; diff --git a/src/components/Background/Background.tsx b/src/components/Background/Background.tsx new file mode 100644 index 0000000..d1cb874 --- /dev/null +++ b/src/components/Background/Background.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import cx from 'clsx'; +import { useTheme } from '../../ThemeProvider'; +import classes from './Background.styles.less'; + +interface BackgroundProps extends React.HTMLAttributes { + className?: string; + component?: React.ElementType; +} + +export default function Background({ + className, + component: Element = 'div', + ...others +}: BackgroundProps) { + const [theme] = useTheme(); + return ; +} diff --git a/src/components/Button/Button.styles.less b/src/components/Button/Button.styles.less index 94fd127..d9e82e1 100644 --- a/src/components/Button/Button.styles.less +++ b/src/components/Button/Button.styles.less @@ -14,7 +14,9 @@ font-weight: bold; cursor: pointer; outline: 0; - transition: transform 150ms ease, color 150ms ease; + transition: + transform 150ms ease, + color 150ms ease; &:hover { transform: scale(1.05); diff --git a/src/components/Button/Button.jsx b/src/components/Button/Button.tsx similarity index 54% rename from src/components/Button/Button.jsx rename to src/components/Button/Button.tsx index 584f331..0ef7705 100644 --- a/src/components/Button/Button.jsx +++ b/src/components/Button/Button.tsx @@ -1,9 +1,17 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; +import cx from 'clsx'; import { useTheme } from '../../ThemeProvider'; import classes from './Button.styles.less'; +interface ButtonProps { + className?: string | null; + component?: React.ElementType; + theme?: 'primary' | 'success' | 'secondary' | 'blue' | 'red'; + elementRef?: React.Ref | null; + children?: React.ReactNode; + [key: string]: any; +} + export default function Button({ className = null, component: Element = 'button', @@ -12,7 +20,7 @@ export default function Button({ elementRef = null, children = null, ...others -}) { +}: ButtonProps) { const [appTheme] = useTheme(); return ( @@ -27,14 +35,3 @@ export default function Button({ ); } - -Button.propTypes = { - component: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.object]), - type: PropTypes.oneOf(['button', 'submit', 'reset']), - theme: PropTypes.oneOf(['primary', 'success', 'secondary', 'blue', 'red']), - elementRef: PropTypes.func, - className: PropTypes.string, - children: PropTypes.node, - maxWidth: PropTypes.number, - style: PropTypes.object, -}; diff --git a/src/components/CopyCodeButton/CopyCodeButton.jsx b/src/components/CopyCodeButton/CopyCodeButton.tsx similarity index 54% rename from src/components/CopyCodeButton/CopyCodeButton.jsx rename to src/components/CopyCodeButton/CopyCodeButton.tsx index 451412a..138a51d 100644 --- a/src/components/CopyCodeButton/CopyCodeButton.jsx +++ b/src/components/CopyCodeButton/CopyCodeButton.tsx @@ -1,16 +1,14 @@ import React from 'react'; -import PropTypes from 'prop-types'; import Button from '../Button/Button'; -export default function CopyCodeButton({ copied, ...others }) { +interface CopyCodeButtonProps extends React.ButtonHTMLAttributes { + copied: boolean; +} + +export default function CopyCodeButton({ copied, ...others }: CopyCodeButtonProps) { return ( ); } - -CopyCodeButton.propTypes = { - className: PropTypes.string, - copied: PropTypes.bool.isRequired, -}; diff --git a/src/components/DropPlaceholder/DropPlaceholder.styles.less b/src/components/DropPlaceholder/DropPlaceholder.styles.less index b9f3520..793d0fb 100644 --- a/src/components/DropPlaceholder/DropPlaceholder.styles.less +++ b/src/components/DropPlaceholder/DropPlaceholder.styles.less @@ -30,12 +30,18 @@ background-color: @oc-white; display: block; cursor: pointer; - transition: transform 150ms ease, box-shadow 150ms ease; + transition: + transform 150ms ease, + box-shadow 150ms ease; &:hover { transform: scale(1.01) translateY(-1px); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03), 0 2px 4px rgba(0, 0, 0, 0.03), - 0 4px 8px rgba(0, 0, 0, 0.03), 0 8px 16px rgba(0, 0, 0, 0.03), 0 16px 32px rgba(0, 0, 0, 0.03), + box-shadow: + 0 1px 2px rgba(0, 0, 0, 0.03), + 0 2px 4px rgba(0, 0, 0, 0.03), + 0 4px 8px rgba(0, 0, 0, 0.03), + 0 8px 16px rgba(0, 0, 0, 0.03), + 0 16px 32px rgba(0, 0, 0, 0.03), 0 32px 64px rgba(0, 0, 0, 0.03); } } diff --git a/src/components/DropPlaceholder/DropPlaceholder.jsx b/src/components/DropPlaceholder/DropPlaceholder.tsx similarity index 73% rename from src/components/DropPlaceholder/DropPlaceholder.jsx rename to src/components/DropPlaceholder/DropPlaceholder.tsx index 3561eb0..e203434 100644 --- a/src/components/DropPlaceholder/DropPlaceholder.jsx +++ b/src/components/DropPlaceholder/DropPlaceholder.tsx @@ -1,15 +1,21 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; +import cx from 'clsx'; import { useTheme } from '../../ThemeProvider'; import classes from './DropPlaceholder.styles.less'; +interface DropPlaceholderProps { + className?: string; + children: React.ReactNode; + onFileAdd: (file: File) => void; + accepts?: string; +} + export default function DropPlaceholder({ className, children, onFileAdd, accepts = 'image/svg+xml', -}) { +}: DropPlaceholderProps) { const [theme] = useTheme(); return ( @@ -21,17 +27,10 @@ export default function DropPlaceholder({ className={classes.input} type="file" id="file-browse" - accepts={accepts} + accept={accepts} onChange={(event) => event.target.files[0] && onFileAdd(event.target.files[0])} /> ); } - -DropPlaceholder.propTypes = { - className: PropTypes.string, - children: PropTypes.string.isRequired, - onFileAdd: PropTypes.func.isRequired, - accepts: PropTypes.string, -}; diff --git a/src/components/Dropzone/Dropzone.styles.less b/src/components/Dropzone/Dropzone.styles.less index 4516d5c..450cabd 100644 --- a/src/components/Dropzone/Dropzone.styles.less +++ b/src/components/Dropzone/Dropzone.styles.less @@ -1,5 +1,7 @@ .wrapper { - transition: opacity 250ms ease, transform 250ms ease; + transition: + opacity 250ms ease, + transform 250ms ease; position: fixed; top: 0; left: 0; diff --git a/src/components/Dropzone/Dropzone.jsx b/src/components/Dropzone/Dropzone.tsx similarity index 68% rename from src/components/Dropzone/Dropzone.jsx rename to src/components/Dropzone/Dropzone.tsx index 3c8c9f6..6ff7f97 100644 --- a/src/components/Dropzone/Dropzone.jsx +++ b/src/components/Dropzone/Dropzone.tsx @@ -1,24 +1,33 @@ import React, { useLayoutEffect, useState, useRef } from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; +import cx from 'clsx'; import { useTheme } from '../../ThemeProvider'; import classes from './Dropzone.styles.less'; const preventDefault = (event) => event.preventDefault(); -export default function Dropzone({ onDrop, accepts = ['image/svg+xml'] }) { +interface DropzoneProps { + onDrop: (files: File[]) => void; + accepts?: string[] | '*'; +} + +export default function Dropzone({ onDrop, accepts = ['image/svg+xml'] }: DropzoneProps) { const [theme] = useTheme(); const [dragOver, setDragOver] = useState(false); - const dragLeaveTimeout = useRef(); + const dragLeaveTimeout = useRef | null>(null); const allowAll = accepts === '*'; + const acceptedTypes = allowAll ? [] : accepts; const onDragOver = () => { - clearTimeout(dragLeaveTimeout.current); + if (dragLeaveTimeout.current) { + clearTimeout(dragLeaveTimeout.current); + } setDragOver(true); }; const onDragLeave = () => { - clearTimeout(dragLeaveTimeout.current); + if (dragLeaveTimeout.current) { + clearTimeout(dragLeaveTimeout.current); + } dragLeaveTimeout.current = setTimeout(() => { setDragOver(false); }, 20); @@ -26,9 +35,13 @@ export default function Dropzone({ onDrop, accepts = ['image/svg+xml'] }) { const handleDrop = (event) => { event.preventDefault(); - clearTimeout(dragLeaveTimeout.current); + if (dragLeaveTimeout.current) { + clearTimeout(dragLeaveTimeout.current); + } setDragOver(false); - onDrop([...event.dataTransfer.files].filter((file) => allowAll || accepts.includes(file.type))); + onDrop( + [...event.dataTransfer.files].filter((file) => allowAll || acceptedTypes.includes(file.type)) + ); }; useLayoutEffect(() => { @@ -51,8 +64,3 @@ export default function Dropzone({ onDrop, accepts = ['image/svg+xml'] }) { ) : null; } - -Dropzone.propTypes = { - onDrop: PropTypes.func.isRequired, - accepts: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.string), PropTypes.string]), -}; diff --git a/src/components/GithubButton/GithubButton.styles.less b/src/components/GithubButton/GithubButton.styles.less index 035733a..2dcd854 100644 --- a/src/components/GithubButton/GithubButton.styles.less +++ b/src/components/GithubButton/GithubButton.styles.less @@ -10,12 +10,18 @@ color: @oc-gray-8; border: 1px solid @oc-gray-2; - transition: transform 150ms ease, box-shadow 150ms ease; + transition: + transform 150ms ease, + box-shadow 150ms ease; &:hover { transform: scale(1.01) translateY(-1px); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03), 0 2px 4px rgba(0, 0, 0, 0.03), - 0 4px 8px rgba(0, 0, 0, 0.03), 0 8px 16px rgba(0, 0, 0, 0.03), 0 16px 32px rgba(0, 0, 0, 0.03), + box-shadow: + 0 1px 2px rgba(0, 0, 0, 0.03), + 0 2px 4px rgba(0, 0, 0, 0.03), + 0 4px 8px rgba(0, 0, 0, 0.03), + 0 8px 16px rgba(0, 0, 0, 0.03), + 0 16px 32px rgba(0, 0, 0, 0.03), 0 32px 64px rgba(0, 0, 0, 0.03); } } diff --git a/src/components/GithubButton/GithubButton.jsx b/src/components/GithubButton/GithubButton.tsx similarity index 88% rename from src/components/GithubButton/GithubButton.jsx rename to src/components/GithubButton/GithubButton.tsx index 867063f..886c5d4 100644 --- a/src/components/GithubButton/GithubButton.jsx +++ b/src/components/GithubButton/GithubButton.tsx @@ -1,11 +1,14 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; +import cx from 'clsx'; import { useTheme } from '../../ThemeProvider'; import settings from '../../settings'; import classes from './GithubButton.styles.less'; -export default function GithubButton({ className }) { +interface GithubButtonProps { + className?: string; +} + +export default function GithubButton({ className }: GithubButtonProps) { const [theme] = useTheme(); return ( @@ -30,7 +33,3 @@ export default function GithubButton({ className }) { ); } - -GithubButton.propTypes = { - className: PropTypes.string, -}; diff --git a/src/components/HexInput/HexInput.styles.less b/src/components/HexInput/HexInput.styles.less index 3e4d542..77e286e 100644 --- a/src/components/HexInput/HexInput.styles.less +++ b/src/components/HexInput/HexInput.styles.less @@ -48,8 +48,12 @@ left: 0; z-index: 10; border-radius: 9px; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03), 0 2px 4px rgba(0, 0, 0, 0.03), - 0 4px 8px rgba(0, 0, 0, 0.03), 0 8px 16px rgba(0, 0, 0, 0.03), 0 16px 32px rgba(0, 0, 0, 0.03), + box-shadow: + 0 1px 2px rgba(0, 0, 0, 0.03), + 0 2px 4px rgba(0, 0, 0, 0.03), + 0 4px 8px rgba(0, 0, 0, 0.03), + 0 8px 16px rgba(0, 0, 0, 0.03), + 0 16px 32px rgba(0, 0, 0, 0.03), 0 32px 64px rgba(0, 0, 0, 0.03); } diff --git a/src/components/HexInput/HexInput.jsx b/src/components/HexInput/HexInput.tsx similarity index 83% rename from src/components/HexInput/HexInput.jsx rename to src/components/HexInput/HexInput.tsx index a51a076..5b167f2 100644 --- a/src/components/HexInput/HexInput.jsx +++ b/src/components/HexInput/HexInput.tsx @@ -1,12 +1,18 @@ import React, { useState, useLayoutEffect, useRef } from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; -import { useClickOutside } from 'xooks'; +import cx from 'clsx'; +import { useClickOutside } from '@hooks'; import { HexColorPicker, HexColorInput } from 'react-colorful'; import { useTheme } from '../../ThemeProvider'; import classes from './HexInput.styles.less'; -export default function HexInput({ className, value, onChange, ...others }) { +interface HexInputProps { + className?: string; + value: string; + onChange: (value: string) => void; + [key: string]: any; +} + +export default function HexInput({ className, value, onChange, ...others }: HexInputProps) { const [theme] = useTheme(); const ref = useRef(null); const [opened, setOpened] = useState(false); @@ -36,9 +42,3 @@ export default function HexInput({ className, value, onChange, ...others }) { ); } - -HexInput.propTypes = { - className: PropTypes.string, - value: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired, -}; diff --git a/src/components/Highlight/Highlight.jsx b/src/components/Highlight/Highlight.tsx similarity index 74% rename from src/components/Highlight/Highlight.jsx rename to src/components/Highlight/Highlight.tsx index 6487c7e..2fdc03b 100644 --- a/src/components/Highlight/Highlight.jsx +++ b/src/components/Highlight/Highlight.tsx @@ -1,12 +1,15 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; -import { useClipboard } from 'xooks'; +import cx from 'clsx'; +import { useClipboard } from '@hooks'; import { useTheme } from '../../ThemeProvider'; import CopyCodeButton from '../CopyCodeButton/CopyCodeButton'; import classes from './Highlight.styles.less'; -export default function Highlight({ children }) { +interface HighlightProps { + children: string; +} + +export default function Highlight({ children }: HighlightProps) { const { copy, copied } = useClipboard(); const [theme] = useTheme(); @@ -26,7 +29,3 @@ export default function Highlight({ children }) { ); } - -Highlight.propTypes = { - children: PropTypes.string.isRequired, -}; diff --git a/src/components/Input/Input.jsx b/src/components/Input/Input.tsx similarity index 63% rename from src/components/Input/Input.jsx rename to src/components/Input/Input.tsx index 8308993..fef552d 100644 --- a/src/components/Input/Input.jsx +++ b/src/components/Input/Input.tsx @@ -1,15 +1,20 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; +import cx from 'clsx'; import { useTheme } from '../../ThemeProvider'; import classes from './Input.styles.less'; +interface InputProps extends React.InputHTMLAttributes { + className?: string; + invalid?: boolean; + component?: React.ElementType; +} + export default function Input({ className, invalid = false, component: Element = 'input', ...others -}) { +}: InputProps) { const [theme] = useTheme(); return ( ); } - -Input.propTypes = { - component: PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.func]), - className: PropTypes.string, - invalid: PropTypes.bool, -}; diff --git a/src/components/Navbar/Navbar.jsx b/src/components/Navbar/Navbar.tsx similarity index 79% rename from src/components/Navbar/Navbar.jsx rename to src/components/Navbar/Navbar.tsx index 6d19b05..549efc3 100644 --- a/src/components/Navbar/Navbar.jsx +++ b/src/components/Navbar/Navbar.tsx @@ -1,8 +1,7 @@ import React, { useState, useEffect, useRef } from 'react'; import { Link, NavLink, useLocation } from 'react-router-dom'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; -import Scrollbars from 'react-custom-scrollbars'; +import cx from 'clsx'; +import Scrollbars from 'react-custom-scrollbars-2'; import { useTheme } from '../../ThemeProvider'; import Background from '../Background/Background'; import GithubButton from '../GithubButton/GithubButton'; @@ -17,27 +16,30 @@ import classes from './Navbar.styles.less'; const LINK_HEIGHT = 72; const removeTrailingSlash = (path) => - (path.slice(path.length - 1) === '/' ? path.slice(0, path.length - 1) : path); - -const isActive = (path, match, location) => - !!(match || removeTrailingSlash(path) === removeTrailingSlash(location.pathname)); + path.slice(path.length - 1) === '/' ? path.slice(0, path.length - 1) : path; const findCurrentIndex = (pathname) => settings.tools.findIndex( (tool) => removeTrailingSlash(pathname) === removeTrailingSlash(tool.link) ); -export default function Navbar({ className }) { +interface NavbarProps { + className?: string; +} + +export default function Navbar({ className }: NavbarProps) { const [theme] = useTheme(); const { pathname } = useLocation(); const [current, setCurrent] = useState(findCurrentIndex(pathname)); const [offline, setOffline] = useState({ ready: false, error: false }); - const scrollbars = useRef(); + const scrollbars = useRef(null); useEffect(() => { const currentIndex = findCurrentIndex(pathname); setCurrent(currentIndex); - scrollbars.current && scrollbars.current.scrollTop(currentIndex * LINK_HEIGHT); + if (scrollbars.current) { + scrollbars.current.scrollTop(currentIndex * LINK_HEIGHT); + } }, [pathname]); useEffect(() => { @@ -52,9 +54,12 @@ export default function Navbar({ className }) { + cx(classes.link, { + [classes.linkActive]: + routeIsActive || removeTrailingSlash(pathname) === removeTrailingSlash(tool.link), + }) + } title={tool.name} > {tool.name} @@ -64,17 +69,25 @@ export default function Navbar({ className }) { const links = settings.meta.map((link, index) => { const { internal, label, ...linkProps } = link; - const Component = internal ? Link : 'a'; + + if (internal) { + return ( + + {label} + + ); + } return ( - {label} - + ); }); @@ -119,7 +132,3 @@ export default function Navbar({ className }) { ); } - -Navbar.propTypes = { - className: PropTypes.string, -}; diff --git a/src/components/NumberInput/NumberInput.jsx b/src/components/NumberInput/NumberInput.tsx similarity index 61% rename from src/components/NumberInput/NumberInput.jsx rename to src/components/NumberInput/NumberInput.tsx index 53cdb41..2a0e8de 100644 --- a/src/components/NumberInput/NumberInput.jsx +++ b/src/components/NumberInput/NumberInput.tsx @@ -1,9 +1,23 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import { useIntermediateValue } from 'xooks'; +import { useIntermediateValue } from '@hooks'; import Input from '../Input/Input'; -export default function NumberInput({ value, onChange, min = 0, max = 100, ...others }) { +interface NumberInputProps { + className?: string; + value: number; + onChange: (value: number) => void; + min?: number; + max?: number; + [key: string]: any; +} + +export default function NumberInput({ + value, + onChange, + min = 0, + max = 100, + ...others +}: NumberInputProps) { const { intermediateValue, valid, handleChange, handleSubmit } = useIntermediateValue({ value, onChange, @@ -22,10 +36,3 @@ export default function NumberInput({ value, onChange, min = 0, max = 100, ...ot /> ); } - -NumberInput.propTypes = { - value: PropTypes.number.isRequired, - onChange: PropTypes.func.isRequired, - min: PropTypes.number, - max: PropTypes.number, -}; diff --git a/src/components/PageBase/Footer/Footer.jsx b/src/components/PageBase/Footer/Footer.tsx similarity index 75% rename from src/components/PageBase/Footer/Footer.jsx rename to src/components/PageBase/Footer/Footer.tsx index 47785ff..177c013 100644 --- a/src/components/PageBase/Footer/Footer.jsx +++ b/src/components/PageBase/Footer/Footer.tsx @@ -1,13 +1,16 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { Link } from 'react-router-dom'; -import cx from 'classnames'; +import cx from 'clsx'; import { useTheme } from '../../../ThemeProvider'; import GithubButton from '../../GithubButton/GithubButton'; import settings from '../../../settings'; import classes from './Footer.styles.less'; -export default function Footer({ className }) { +interface FooterProps { + className?: string; +} + +export default function Footer({ className }: FooterProps) { const [theme] = useTheme(); const tools = settings.tools.map((tool) => ( @@ -18,18 +21,25 @@ export default function Footer({ className }) { const links = settings.meta.map((link, index) => { const { internal, label, ...linkProps } = link; - const Component = internal ? Link : 'a'; + + if (internal) { + return ( + + {label} + + ); + } return ( - {label} - + ); }); @@ -55,7 +65,3 @@ export default function Footer({ className }) { ); } - -Footer.propTypes = { - className: PropTypes.string, -}; diff --git a/src/components/PageBase/Header/Header.jsx b/src/components/PageBase/Header/Header.tsx similarity index 96% rename from src/components/PageBase/Header/Header.jsx rename to src/components/PageBase/Header/Header.tsx index 68cd751..c28da3b 100644 --- a/src/components/PageBase/Header/Header.jsx +++ b/src/components/PageBase/Header/Header.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Link } from 'react-router-dom'; -import cx from 'classnames'; +import cx from 'clsx'; import { useTheme } from '../../../ThemeProvider'; import GithubButton from '../../GithubButton/GithubButton'; import logoText from '../../../assets/logo-text.svg'; diff --git a/src/components/PageBase/PageBase.jsx b/src/components/PageBase/PageBase.tsx similarity index 70% rename from src/components/PageBase/PageBase.jsx rename to src/components/PageBase/PageBase.tsx index 9081e4a..1b81f2a 100644 --- a/src/components/PageBase/PageBase.jsx +++ b/src/components/PageBase/PageBase.tsx @@ -1,12 +1,15 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; +import cx from 'clsx'; import { useTheme } from '../../ThemeProvider'; import Footer from './Footer/Footer'; import Header from './Header/Header'; import classes from './PageBase.styles.less'; -export default function PageBase({ children }) { +interface PageBaseProps { + children: React.ReactNode; +} + +export default function PageBase({ children }: PageBaseProps) { const [theme] = useTheme(); return ( @@ -19,7 +22,3 @@ export default function PageBase({ children }) { ); } - -PageBase.propTypes = { - children: PropTypes.node.isRequired, -}; diff --git a/src/components/Select/Select.jsx b/src/components/Select/Select.tsx similarity index 75% rename from src/components/Select/Select.jsx rename to src/components/Select/Select.tsx index 35c57b3..2644171 100644 --- a/src/components/Select/Select.jsx +++ b/src/components/Select/Select.tsx @@ -1,11 +1,24 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; +import cx from 'clsx'; import { useTheme } from '../../ThemeProvider'; import Input from '../Input/Input'; import classes from './Select.styles.less'; -export default function Select({ className, data, value, onChange, id, label }) { +interface SelectItem { + value: string; + label: string; +} + +interface SelectProps { + className?: string; + data: SelectItem[]; + value: string; + onChange: (value: string) => void; + id: string; + label: string; +} + +export default function Select({ className, data, value, onChange, id, label }: SelectProps) { const [theme] = useTheme(); const options = data.map((option) => (