├── .gitattributes ├── tsconfig.json ├── .editorconfig ├── LICENSE ├── package.json ├── .github └── workflows │ └── test.yml ├── .npmignore ├── README.md ├── .gitignore └── src └── index.ts /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["es2015", "dom"] 4 | }, 5 | "exclude": ["node_modules", "**/node_modules/*"], 6 | "include": ["src/**/*"] 7 | } 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root=true 2 | 3 | [*.{js,jsx,ts,tsx,scss,sass,css}] 4 | indent_style = space 5 | indent_size = 4 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | 9 | [*.{md,json,yml,yaml}] 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_size = 4 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Frank Mayer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@frank-mayer/react-tag-cloud", 3 | "version": "1.2.1", 4 | "author": "Frank Mayer", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/Frank-Mayer/react-tag-cloud.git" 9 | }, 10 | "keywords": [ 11 | "react", 12 | "tag", 13 | "cloud", 14 | "component", 15 | "react-component", 16 | "react-component-library" 17 | ], 18 | "issues": { 19 | "url": "https://github.com/Frank-Mayer/react-tag-cloud/issues" 20 | }, 21 | "homepage": "https://github.com/Frank-Mayer/react-tag-cloud#readme", 22 | "isLibrary": true, 23 | "description": "React component for Cong Mins TagCloud", 24 | "source": "src/index.ts", 25 | "main": "main.js", 26 | "types": "types.d.ts", 27 | "module": "module.js", 28 | "scripts": { 29 | "watch": "parcel watch", 30 | "build": "parcel build --no-source-maps" 31 | }, 32 | "dependencies": { 33 | "TagCloud": "^2.3.2" 34 | }, 35 | "devDependencies": { 36 | "@parcel/packager-ts": "^2.8.3", 37 | "@parcel/transformer-typescript-types": "^2.8.3", 38 | "@types/react": "^18.0.31", 39 | "parcel": "^2.8.3", 40 | "react": "^18.2.0", 41 | "typescript": "^5.0.3" 42 | }, 43 | "peerDependencies": { 44 | "react": "^18.2.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | push: 4 | branches: ["main"] 5 | pull_request: 6 | branches: ["main"] 7 | jobs: 8 | Node: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Git Checkout 12 | uses: actions/checkout@v3 13 | - name: Setup Node.js 14 | uses: actions/setup-node@v3 15 | with: 16 | node-version: latest 17 | - name: Install Dependencies 18 | run: npm install 19 | - name: Build 20 | run: npm run build 21 | Bun: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Git Checkout 25 | uses: actions/checkout@v3 26 | - name: Setup Bun 27 | uses: oven-sh/setup-bun@v1 28 | with: 29 | bun-version: latest 30 | - name: Install Dependencies 31 | run: bun install 32 | - name: Build 33 | run: bun run build 34 | Yarn: 35 | runs-on: ubuntu-latest 36 | steps: 37 | - name: Git Checkout 38 | uses: actions/checkout@v3 39 | - name: Setup Node.js 40 | uses: actions/setup-node@v3 41 | with: 42 | node-version: latest 43 | - name: Install Dependencies 44 | run: yarn install 45 | - name: Build 46 | run: yarn run build 47 | PNPM: 48 | runs-on: ubuntu-latest 49 | steps: 50 | - name: Git Checkout 51 | uses: actions/checkout@v3 52 | - name: Setup Node.js 53 | uses: actions/setup-node@v3 54 | with: 55 | node-version: latest 56 | - name: Install PNPM 57 | uses: pnpm/action-setup@v2 58 | with: 59 | version: latest 60 | - name: Install Dependencies 61 | run: pnpm install 62 | - name: Build 63 | run: pnpm run build 64 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Source Files 2 | src/ 3 | 4 | # Git 5 | .github/ 6 | .gitattributes 7 | .gitignore 8 | 9 | # Tests 10 | *.test.js 11 | *.test.ts 12 | coverage/ 13 | 14 | # Source Maps 15 | *.map 16 | 17 | # Logs 18 | logs 19 | *.log 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | lerna-debug.log* 24 | .pnpm-debug.log* 25 | 26 | # IDE 27 | .idea 28 | .vscode 29 | .vs 30 | .editorconfig 31 | 32 | # Diagnostic reports (https://nodejs.org/api/report.html) 33 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 34 | 35 | # Runtime data 36 | pids 37 | *.pid 38 | *.seed 39 | *.pid.lock 40 | 41 | # Directory for instrumented libs generated by jscoverage/JSCover 42 | lib-cov 43 | 44 | # Coverage directory used by tools like istanbul 45 | coverage 46 | *.lcov 47 | 48 | # nyc test coverage 49 | .nyc_output 50 | 51 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 52 | .grunt 53 | 54 | # Bower dependency directory (https://bower.io/) 55 | bower_components 56 | 57 | # node-waf configuration 58 | .lock-wscript 59 | 60 | # Compiled binary addons (https://nodejs.org/api/addons.html) 61 | build/Release 62 | 63 | # Dependency directories 64 | node_modules/ 65 | jspm_packages/ 66 | 67 | # Snowpack dependency directory (https://snowpack.dev/) 68 | web_modules/ 69 | 70 | # TypeScript 71 | *.tsbuildinfo 72 | tsconfig.json 73 | 74 | # Optional npm cache directory 75 | .npm 76 | 77 | # Optional eslint cache 78 | .eslintcache 79 | 80 | # Optional stylelint cache 81 | .stylelintcache 82 | 83 | # Microbundle cache 84 | .rpt2_cache/ 85 | .rts2_cache_cjs/ 86 | .rts2_cache_es/ 87 | .rts2_cache_umd/ 88 | 89 | # Optional REPL history 90 | .node_repl_history 91 | 92 | # Output of 'npm pack' 93 | *.tgz 94 | 95 | # Yarn Integrity file 96 | .yarn-integrity 97 | 98 | # dotenv environment variable files 99 | .env 100 | .env.development.local 101 | .env.test.local 102 | .env.production.local 103 | .env.local 104 | 105 | # parcel-bundler cache (https://parceljs.org/) 106 | .cache 107 | .parcel-cache 108 | 109 | # yarn v2 110 | .yarn/cache 111 | .yarn/unplugged 112 | .yarn/build-state.yml 113 | .yarn/install-state.gz 114 | .pnp.* 115 | 116 | # lock files 117 | yarn.lock 118 | package-lock.json 119 | bun.lockb 120 | shrinkwrap.yaml 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

React Tag Cloud

6 | 7 | Port of [Cong Min](https://github.com/cong-min)s [TagCloud](https://github.com/cong-min/TagCloud) as a React Component 8 | 9 | [![this product is ai free](https://this-product-is-ai-free.github.io/badge.svg)](https://this-product-is-ai-free.github.io) 10 | 11 | [![Test](https://github.com/Frank-Mayer/react-tag-cloud/actions/workflows/test.yml/badge.svg)](https://github.com/Frank-Mayer/react-tag-cloud/actions/workflows/test.yml) 12 | 13 | ## Installation 14 | 15 | ### npm 16 | 17 | ```bash 18 | npm install @frank-mayer/react-tag-cloud 19 | ``` 20 | 21 | ### yarn 22 | 23 | ```bash 24 | yarn add @frank-mayer/react-tag-cloud 25 | ``` 26 | 27 | ### pnpm 28 | 29 | ```bash 30 | pnpm add @frank-mayer/react-tag-cloud 31 | ``` 32 | 33 | ### bun 34 | 35 | ```bash 36 | bun add @frank-mayer/react-tag-cloud 37 | ``` 38 | 39 | ## Usage 40 | 41 | Since this component is based on a non-React library, it uses the `useEffect` hook and cannot be rendered on the server. 42 | 43 | The component can be imported as a named export or as a default export (useful for lazy loaded components). 44 | 45 | The `options` property can modify the behaviour of the component. You can provide the options as a `object` or as a `function` that returns this `object`. 46 | If you provide a `function`, the `window` object will be passed as an argument. 47 | This is useful if you want to use the window size to calculate the radius of the tag cloud. 48 | 49 | Pass an `Array` of `string`s as children to the component. This will be used as the tags. 50 | 51 | ```tsx 52 | import React from "react"; 53 | import { TagCloud } from "@frank-mayer/react-tag-cloud"; 54 | // same as: import TagCloud from "@frank-mayer/react-tag-cloud" 55 | 56 | const App = () => ( 57 | ({ 59 | radius: Math.min(500, w.innerWidth, w.innerHeight) / 2, 60 | maxSpeed: "fast", 61 | })} 62 | onClick={(tag: string, ev: MouseEvent) => alert(tag)} 63 | onClickOptions={{ passive: true }} 64 | > 65 | {[ 66 | "VSCode", 67 | "TypeScript", 68 | "React", 69 | "Preact", 70 | "Parcel", 71 | "Jest", 72 | "Next", 73 | "ESLint", 74 | "Framer Motion", 75 | "Three.js", 76 | ]} 77 | 78 | ); 79 | ``` 80 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Output 2 | /*.js 3 | /*.d.ts 4 | *.map 5 | !.eslintrc.js 6 | 7 | # Logs 8 | logs 9 | *.log 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | .pnpm-debug.log* 15 | 16 | # IDE 17 | .idea 18 | .vscode 19 | .vs 20 | 21 | # Diagnostic reports (https://nodejs.org/api/report.html) 22 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 23 | 24 | # Runtime data 25 | pids 26 | *.pid 27 | *.seed 28 | *.pid.lock 29 | 30 | # Directory for instrumented libs generated by jscoverage/JSCover 31 | lib-cov 32 | 33 | # Coverage directory used by tools like istanbul 34 | coverage 35 | *.lcov 36 | 37 | # nyc test coverage 38 | .nyc_output 39 | 40 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 41 | .grunt 42 | 43 | # Bower dependency directory (https://bower.io/) 44 | bower_components 45 | 46 | # node-waf configuration 47 | .lock-wscript 48 | 49 | # Compiled binary addons (https://nodejs.org/api/addons.html) 50 | build/Release 51 | 52 | # Dependency directories 53 | node_modules/ 54 | jspm_packages/ 55 | 56 | # Snowpack dependency directory (https://snowpack.dev/) 57 | web_modules/ 58 | 59 | # TypeScript cache 60 | *.tsbuildinfo 61 | 62 | # Optional npm cache directory 63 | .npm 64 | 65 | # Optional eslint cache 66 | .eslintcache 67 | 68 | # Optional stylelint cache 69 | .stylelintcache 70 | 71 | # Microbundle cache 72 | .rpt2_cache/ 73 | .rts2_cache_cjs/ 74 | .rts2_cache_es/ 75 | .rts2_cache_umd/ 76 | 77 | # Optional REPL history 78 | .node_repl_history 79 | 80 | # Output of 'npm pack' 81 | *.tgz 82 | 83 | # Yarn Integrity file 84 | .yarn-integrity 85 | 86 | # dotenv environment variable files 87 | .env 88 | .env.development.local 89 | .env.test.local 90 | .env.production.local 91 | .env.local 92 | 93 | # parcel-bundler cache (https://parceljs.org/) 94 | .cache 95 | .parcel-cache 96 | 97 | # Next.js build output 98 | .next 99 | out 100 | 101 | # Nuxt.js build / generate output 102 | .nuxt 103 | dist 104 | 105 | # Gatsby files 106 | .cache/ 107 | # Comment in the public line in if your project uses Gatsby and not Next.js 108 | # https://nextjs.org/blog/next-9-1#public-directory-support 109 | # public 110 | 111 | # vuepress build output 112 | .vuepress/dist 113 | 114 | # vuepress v2.x temp and cache directory 115 | .temp 116 | .cache 117 | 118 | # Serverless directories 119 | .serverless/ 120 | 121 | # FuseBox cache 122 | .fusebox/ 123 | 124 | # DynamoDB Local files 125 | .dynamodb/ 126 | 127 | # TernJS port file 128 | .tern-port 129 | 130 | # Stores VSCode versions used for testing VSCode extensions 131 | .vscode-test 132 | 133 | # yarn v2 134 | .yarn/cache 135 | .yarn/unplugged 136 | .yarn/build-state.yml 137 | .yarn/install-state.gz 138 | .pnp.* 139 | 140 | # lock files 141 | yarn.lock 142 | package-lock.json 143 | bun.lockb 144 | shrinkwrap.yaml 145 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import createTagCloud from "TagCloud"; 2 | import type { TagCloudOptions } from "TagCloud"; 3 | import { useEffect, createElement, useRef } from "react"; 4 | import type { CSSProperties } from "react"; 5 | 6 | export type { TagCloudOptions }; 7 | 8 | type Props = { 9 | id?: string; 10 | children: Array; 11 | className?: string; 12 | options?: 13 | | TagCloudOptions 14 | | ((window: Window & typeof globalThis) => TagCloudOptions); 15 | style?: CSSProperties; 16 | onClick?: (tag: T, event: MouseEvent) => void; 17 | onClickOptions?: AddEventListenerOptions; 18 | }; 19 | 20 | const getIsInitialized = (id: string) => { 21 | const key = "__tag-cloud-" + id; 22 | 23 | if (key in window) { 24 | return Boolean((window as any)[key]); 25 | } 26 | 27 | return ((window as any)[key] = false); 28 | }; 29 | 30 | const setIsInitialized = (id: string, value: boolean) => { 31 | const key = "__tag-cloud-" + id; 32 | 33 | (window as any)[key] = value; 34 | }; 35 | 36 | export const TagCloud = (props: Props) => { 37 | const ref = useRef(null); 38 | const key = [ 39 | props.id, 40 | props.className, 41 | JSON.stringify(props.children), 42 | ].join("-"); 43 | 44 | useEffect(() => { 45 | if (getIsInitialized(key) || !ref.current) { 46 | return; 47 | } 48 | 49 | if (props.children.length === 0) { 50 | console.error("TagCloud: No children provided."); 51 | return; 52 | } 53 | 54 | const options = props.options 55 | ? typeof props.options == "function" 56 | ? props.options(window) 57 | : props.options 58 | : {}; 59 | 60 | const tagCloud = createTagCloud( 61 | ref.current as any, 62 | props.children, 63 | options 64 | ); 65 | 66 | setIsInitialized(key, true); 67 | 68 | if (props.onClick) { 69 | const elements = Array.from( 70 | ref.current.getElementsByClassName( 71 | options.itemClass ?? "tagcloud--item" 72 | ) 73 | ) as Array; 74 | 75 | if (props.onClick) { 76 | for (const el of elements) { 77 | el.addEventListener( 78 | "click", 79 | (event) => { 80 | props.onClick(el.innerText as T, event); 81 | }, 82 | props.onClickOptions 83 | ); 84 | } 85 | } 86 | } 87 | 88 | return () => { 89 | try { 90 | tagCloud?.destroy(); 91 | } finally { 92 | setIsInitialized(key, false); 93 | } 94 | }; 95 | }, [ref, key, props]); 96 | 97 | return createElement("div", { 98 | id: props.id, 99 | className: props.className, 100 | style: props.style, 101 | ref, 102 | }); 103 | }; 104 | 105 | export default TagCloud; 106 | --------------------------------------------------------------------------------