├── .gitignore ├── src ├── index.ts ├── components │ ├── Leaderboard │ │ ├── index.ts │ │ └── Leaderboard.tsx │ └── index.ts └── styles.css ├── postcss.config.js ├── tailwind.config.js ├── .github └── workflows │ └── npm-publish.yml ├── README.md ├── rollup.config.mjs ├── package.json └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .npmrc -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components'; -------------------------------------------------------------------------------- /src/components/Leaderboard/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './Leaderboard'; -------------------------------------------------------------------------------- /src/components/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Leaderboard } from './Leaderboard'; -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./src/**/*.{js,jsx,ts,tsx}", 5 | 6 | // Path to the tremor module 7 | "./node_modules/@tremor/**/*.{js,ts,jsx,tsx}", 8 | ], 9 | theme: { 10 | extend: {}, 11 | }, 12 | plugins: [], 13 | } 14 | 15 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: 16 18 | - run: npm ci 19 | - run: npm test 20 | 21 | publish-npm: 22 | needs: build 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v3 26 | - uses: actions/setup-node@v3 27 | with: 28 | node-version: 16 29 | registry-url: https://registry.npmjs.org/ 30 | - run: npm ci 31 | - run: npm publish 32 | env: 33 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flywheel-leaderboard 2 | flywheel is a react component library for adding dynamic leaderboards to your app. Thanks for checking it out :wave: 3 | 4 | ### Getting Started 5 | To install the library simply run `npm i flywheel-leaderboard` or copy the command from [npm](https://www.npmjs.com/package/flywheel-leaderboard). 6 | 7 | **Don't install the github package - it will ask you for a personal access token when deploying to production. Instead install the npm package linked above.** 8 | 9 | Note that this library only works with tailwind css (I mean if you don't get tailwind it'll still work but it'll look ugly lol) and that typescript is recommended. 10 | 11 | I actually wrote up some sweet [docs](https://docs.myflywheel.app/) to guide you as you're getting started with flywheel and I made a quick demo if you're more of a visual learner: 12 | 13 | https://user-images.githubusercontent.com/86082012/236715511-56716c09-60cf-4192-9039-c5e67be9bd95.mp4 14 | 15 | **Note that the vid above is a little outdated - as of `v0.0.2` you should install the package from npm instead of from github.** 16 | 17 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import resolve from "@rollup/plugin-node-resolve"; 2 | import commonjs from "@rollup/plugin-commonjs"; 3 | import typescript from "@rollup/plugin-typescript"; 4 | import dts from "rollup-plugin-dts"; 5 | 6 | import peerDepsExternal from 'rollup-plugin-peer-deps-external'; 7 | import packageJson from "./package.json" assert { type: "json" }; 8 | 9 | export default [ 10 | { 11 | input: "src/index.ts", 12 | output: [ 13 | { 14 | file: packageJson.main, 15 | format: "cjs", 16 | sourcemap: true, 17 | }, 18 | { 19 | file: packageJson.module, 20 | format: "esm", 21 | sourcemap: true, 22 | }, 23 | ], 24 | plugins: [ 25 | peerDepsExternal(), 26 | resolve(), 27 | commonjs(), 28 | typescript({ tsconfig: "./tsconfig.json", exclude: ["./styles.css"] }), 29 | ], 30 | }, 31 | { 32 | input: "dist/esm/types/index.d.ts", 33 | output: [{ file: "dist/index.d.ts", format: "esm" }], 34 | plugins: [dts()], 35 | external: ['react', '@tremor/react', 'tailwindcss', /\.css$/], 36 | }, 37 | ]; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flywheel-leaderboard", 3 | "version": "0.0.2", 4 | "description": "a react component library for creating pretty, dynamic leaderboards", 5 | "scripts": { 6 | "rollup": "rollup -c" 7 | }, 8 | "author": "dylan", 9 | "license": "ISC", 10 | "devDependencies": { 11 | "@rollup/plugin-commonjs": "^24.1.0", 12 | "@rollup/plugin-node-resolve": "^15.0.2", 13 | "@rollup/plugin-typescript": "^11.1.0", 14 | "@types/react": "^18.2.6", 15 | "autoprefixer": "^10.4.14", 16 | "postcss": "^8.4.23", 17 | "rollup": "^3.21.5", 18 | "rollup-plugin-dts": "^5.3.0", 19 | "rollup-plugin-peer-deps-external": "^2.2.4", 20 | "tailwindcss": "^3.3.2", 21 | "tslib": "^2.5.0", 22 | "typescript": "^5.0.4" 23 | }, 24 | "peerDependencies": { 25 | "react": "^18.2.0", 26 | "@tremor/react": "^2.4.0", 27 | "tailwindcss": "^3.3.2" 28 | }, 29 | 30 | "main": "dist/cjs/index.js", 31 | "module": "dist/esm/index.js", 32 | "files": [ 33 | "dist" 34 | ], 35 | 36 | "types": "dist/index.d.ts", 37 | "publishConfig": { 38 | "registry": "https://registry.npmjs.org/", 39 | "access": "public" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/components/Leaderboard/Leaderboard.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { LineChart, Card, AreaChart, BadgeDelta, Button, Callout, Icon, Toggle, ToggleItem, Table, TableHead, TableHeaderCell,TableBody,TableRow,TableCell, Badge, Text, Metric, Title, TextInput, Dropdown, DropdownItem} from "@tremor/react"; 3 | 4 | interface LeaderboardProps { 5 | scoringMetric: string; 6 | id: string; 7 | cell1?: string; 8 | cell2?: string; 9 | cell3?: string; 10 | cell4?: string; 11 | cell5?: string; 12 | items: Record[]; 13 | theme?: "amber" | "slate" | "stone" | "gray" | "zinc" | "neutral" | "red" | "orange" | "yellow" | "lime" | "green" | "emerald" | "teal" | "cyan" | "sky" | "blue" | "indigo" | "violet" | "purple" | "fuchsia" | "pink" | "rose" | undefined; 14 | className?: string; 15 | } 16 | 17 | const Leaderboard = (props: LeaderboardProps) => { 18 | 19 | function findRank(array: Record[], element: Record): number { 20 | for (let i = 0; i < array.length; i++) { 21 | if (array[i] === element) { 22 | return i + 1; 23 | } 24 | } 25 | return -1; 26 | } 27 | 28 | const getBadgeColor = (role: Number) => { 29 | if (role === 1) { 30 | return "amber"; 31 | } else if (role === 2) { 32 | return "slate"; 33 | } else if (role === 3) { 34 | return "stone"; 35 | } else { 36 | return "gray"; 37 | } 38 | } 39 | 40 | return ( 41 | <> 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | {props.cell1 && ( 53 | 54 | {props.cell1} 55 | )} 56 | {props.cell2 && ( 57 | 58 | {props.cell2} 59 | )} 60 | {props.cell3 && ( 61 | 62 | {props.cell3} 63 | )} 64 | {props.cell4 && ( 65 | 66 | {props.cell4} 67 | )} 68 | {props.cell5 && ( 69 | 70 | {props.cell5} 71 | )} 72 | 73 | 74 | 75 | {props.items.sort((a,b) => b[props.scoringMetric] - a[props.scoringMetric]).map((item) => ( 76 | 77 | 78 | b[props.scoringMetric] - a[props.scoringMetric]), item))}>{findRank(props.items.sort((a,b) => b[props.scoringMetric] - a[props.scoringMetric]), item) } 79 | 80 | 81 | {item[props.id]} 82 | 83 | {props.cell1 && ( 84 | {item[props.cell1]} 85 | )} 86 | {props.cell2 && ( 87 | {item[props.cell2]} 88 | )} 89 | {props.cell3 && 90 | {item[props.cell3]} 91 | } 92 | {props.cell4 && 93 | {item[props.cell4]} 94 | } 95 | {props.cell5 && ( 96 | {item[props.cell5]} 97 | )} 98 | 99 | ))} 100 | 101 |
102 |

powered by flywheel

103 |
104 | 105 | ) 106 | } 107 | 108 | export default Leaderboard; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | // "module": "commonjs", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 39 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 40 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 41 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 42 | // "resolveJsonModule": true, /* Enable importing .json files. */ 43 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 44 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 45 | 46 | /* JavaScript Support */ 47 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 48 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 49 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 50 | 51 | /* Emit */ 52 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 53 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 54 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 55 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 56 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 57 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 58 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 59 | // "removeComments": true, /* Disable emitting comments. */ 60 | // "noEmit": true, /* Disable emitting files from a compilation. */ 61 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 62 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 63 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 64 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 65 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 66 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 67 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 68 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 69 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 70 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 71 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 72 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 73 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 74 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 75 | 76 | /* Interop Constraints */ 77 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 78 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 79 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 80 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 81 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 82 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 83 | 84 | /* Type Checking */ 85 | "strict": true, /* Enable all strict type-checking options. */ 86 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 87 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 88 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 89 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 90 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 91 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 92 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 93 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 94 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 95 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 96 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 97 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 98 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 99 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 100 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 101 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 102 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 103 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 104 | 105 | /* Completeness */ 106 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 107 | "skipLibCheck": true /* Skip type checking all .d.ts files. */, 108 | "jsx": "react", 109 | "module": "ESNext", 110 | "declaration": true, 111 | "declarationDir": "types", 112 | "sourceMap": true, 113 | "outDir": "dist", 114 | "moduleResolution": "node", 115 | "allowSyntheticDefaultImports": true, 116 | "emitDeclarationOnly": true, 117 | } 118 | } 119 | --------------------------------------------------------------------------------