├── .editorconfig ├── .eslintrc.js ├── .github └── workflows │ └── test.yml ├── .gitignore ├── .prettierrc.js ├── LICENSE ├── README.md ├── dev ├── .gitignore ├── Dockerfile ├── docker-compose.yml ├── jest.config.js ├── nodemon.json ├── package.json ├── plugin.spec.ts ├── src │ ├── collections │ │ ├── Examples.ts │ │ └── Users.ts │ ├── mocks │ │ ├── fileStub.js │ │ └── mockData.ts │ ├── payload.config.ts │ └── server.ts └── tsconfig.json ├── eslint-config ├── index.js └── rules │ ├── import.js │ ├── prettier.js │ ├── style.js │ └── typescript.js ├── example.png ├── package.json ├── src ├── LabelPopover.tsx ├── index.ts ├── onInitExtension.ts └── plugin.ts ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | end_of_line = lf 10 | max_line_length = null 11 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['./eslint-config'], 4 | overrides: [ 5 | // Temporary overrides 6 | { 7 | files: ['dev/**/*.ts'], 8 | rules: { 9 | 'import/no-relative-packages': 'off', 10 | 'no-process-env': 'off', 11 | }, 12 | }, 13 | ], 14 | excludes: [ 15 | 'dev/plugin.spec.ts', 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | pull_request: 5 | types: [ opened, reopened, synchronize ] 6 | push: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-node@v2 15 | with: 16 | node-version: '14.x' 17 | registry-url: 'https://registry.npmjs.org' 18 | - run: yarn install 19 | - run: yarn build 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dev/tmp 2 | dev/yarn.lock 3 | 4 | # Created by https://www.gitignore.io/api/node,macos,windows,webstorm,sublimetext,visualstudiocode 5 | 6 | ### macOS ### 7 | *.DS_Store 8 | .AppleDouble 9 | .LSOverride 10 | 11 | # Thumbnails 12 | ._* 13 | 14 | # Files that might appear in the root of a volume 15 | .DocumentRevisions-V100 16 | .fseventsd 17 | .Spotlight-V100 18 | .TemporaryItems 19 | .Trashes 20 | .VolumeIcon.icns 21 | .com.apple.timemachine.donotpresent 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | 30 | ### Node ### 31 | # Logs 32 | logs 33 | *.log 34 | npm-debug.log* 35 | yarn-debug.log* 36 | yarn-error.log* 37 | 38 | # Runtime data 39 | pids 40 | *.pid 41 | *.seed 42 | *.pid.lock 43 | 44 | # Directory for instrumented libs generated by jscoverage/JSCover 45 | lib-cov 46 | 47 | # Coverage directory used by tools like istanbul 48 | coverage 49 | 50 | # nyc test coverage 51 | .nyc_output 52 | 53 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 54 | .grunt 55 | 56 | # Bower dependency directory (https://bower.io/) 57 | bower_components 58 | 59 | # node-waf configuration 60 | .lock-wscript 61 | 62 | # Compiled binary addons (http://nodejs.org/api/addons.html) 63 | build/Release 64 | 65 | # Dependency directories 66 | node_modules/ 67 | jspm_packages/ 68 | 69 | # Typescript v1 declaration files 70 | typings/ 71 | 72 | # Optional npm cache directory 73 | .npm 74 | 75 | # Optional eslint cache 76 | .eslintcache 77 | 78 | # Optional REPL history 79 | .node_repl_history 80 | 81 | # Output of 'npm pack' 82 | *.tgz 83 | 84 | # Yarn Integrity file 85 | .yarn-integrity 86 | 87 | # Yarn Berry 88 | .yarn/* 89 | !.yarn/patches 90 | !.yarn/plugins 91 | !.yarn/releases 92 | !.yarn/sdks 93 | !.yarn/versions 94 | .pnp.* 95 | 96 | # dotenv environment variables file 97 | .env 98 | 99 | 100 | ### SublimeText ### 101 | # cache files for sublime text 102 | *.tmlanguage.cache 103 | *.tmPreferences.cache 104 | *.stTheme.cache 105 | 106 | # workspace files are user-specific 107 | *.sublime-workspace 108 | 109 | # project files should be checked into the repository, unless a significant 110 | # proportion of contributors will probably not be using SublimeText 111 | # *.sublime-project 112 | 113 | # sftp configuration file 114 | sftp-config.json 115 | 116 | # Package control specific files 117 | Package Control.last-run 118 | Package Control.ca-list 119 | Package Control.ca-bundle 120 | Package Control.system-ca-bundle 121 | Package Control.cache/ 122 | Package Control.ca-certs/ 123 | Package Control.merged-ca-bundle 124 | Package Control.user-ca-bundle 125 | oscrypto-ca-bundle.crt 126 | bh_unicode_properties.cache 127 | 128 | # Sublime-github package stores a github token in this file 129 | # https://packagecontrol.io/packages/sublime-github 130 | GitHub.sublime-settings 131 | 132 | ### VisualStudioCode ### 133 | .vscode/* 134 | !.vscode/tasks.json 135 | !.vscode/launch.json 136 | !.vscode/extensions.json 137 | .history 138 | 139 | ### WebStorm ### 140 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 141 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 142 | 143 | .idea/* 144 | # User-specific stuff: 145 | .idea/**/workspace.xml 146 | .idea/**/tasks.xml 147 | .idea/dictionaries 148 | 149 | # Sensitive or high-churn files: 150 | .idea/**/dataSources/ 151 | .idea/**/dataSources.ids 152 | .idea/**/dataSources.xml 153 | .idea/**/dataSources.local.xml 154 | .idea/**/sqlDataSources.xml 155 | .idea/**/dynamic.xml 156 | .idea/**/uiDesigner.xml 157 | 158 | # Gradle: 159 | .idea/**/gradle.xml 160 | .idea/**/libraries 161 | 162 | # CMake 163 | cmake-build-debug/ 164 | 165 | # Mongo Explorer plugin: 166 | .idea/**/mongoSettings.xml 167 | 168 | ## File-based project format: 169 | *.iws 170 | 171 | ## Plugin-specific files: 172 | 173 | # IntelliJ 174 | /out/ 175 | 176 | # mpeltonen/sbt-idea plugin 177 | .idea_modules/ 178 | 179 | # JIRA plugin 180 | atlassian-ide-plugin.xml 181 | 182 | # Cursive Clojure plugin 183 | .idea/replstate.xml 184 | 185 | # Ruby plugin and RubyMine 186 | /.rakeTasks 187 | 188 | # Crashlytics plugin (for Android Studio and IntelliJ) 189 | com_crashlytics_export_strings.xml 190 | crashlytics.properties 191 | crashlytics-build.properties 192 | fabric.properties 193 | 194 | ### WebStorm Patch ### 195 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 196 | 197 | # *.iml 198 | # modules.xml 199 | # .idea/misc.xml 200 | # *.ipr 201 | 202 | # Sonarlint plugin 203 | .idea/sonarlint 204 | 205 | ### Windows ### 206 | # Windows thumbnail cache files 207 | Thumbs.db 208 | ehthumbs.db 209 | ehthumbs_vista.db 210 | 211 | # Folder config file 212 | Desktop.ini 213 | 214 | # Recycle Bin used on file shares 215 | $RECYCLE.BIN/ 216 | 217 | # Windows Installer files 218 | *.cab 219 | *.msi 220 | *.msm 221 | *.msp 222 | 223 | # Windows shortcuts 224 | *.lnk 225 | 226 | # End of https://www.gitignore.io/api/node,macos,windows,webstorm,sublimetext,visualstudiocode 227 | 228 | # Ignore all uploads 229 | demo/upload 230 | demo/media 231 | demo/files 232 | 233 | # Ignore build folder 234 | build 235 | 236 | # Ignore built components 237 | components/index.js 238 | components/styles.css 239 | 240 | # Ignore generated 241 | dev/generated-types.ts 242 | dev/generated-schema.graphql 243 | 244 | # Ignore dist, no need for git 245 | dist 246 | 247 | dev/src/uploads 248 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | parser: "typescript", 4 | semi: false, 5 | singleQuote: true, 6 | trailingComma: "all", 7 | arrowParens: "avoid", 8 | }; 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Payload 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Payload Label Popover Plugin 2 | #### Adds a descriptive popover to [Payload](https://payloadcms.com/) field labels (using [React Tiny Popover](https://github.com/alexkatz/react-tiny-popover)). 3 | 4 | ![image](https://github.com/notchris/payload-label-popover/blob/main/example.png?raw=true) 5 | 6 | 7 | ## Installation 8 | 9 | ```bash 10 | yarn add payload-label-popover 11 | #OR 12 | npm i payload-label-popover 13 | ``` 14 | 15 | ## Basic Usage 16 | 17 | Import the plugin and add it to your payload configuration file. 18 | 19 | ```ts 20 | // Add the plugin to the "plugins" array in your payload config 21 | { 22 | // ... Rest of payload config 23 | plugins: [labelPopoverPlugin({})] 24 | } 25 | ``` 26 | 27 | ```ts 28 | // Enable a popover on a field using the `custom` object 29 | const Examples: CollectionConfig = { 30 | slug: 'examples', 31 | admin: { 32 | useAsTitle: 'title', 33 | }, 34 | fields: [ 35 | { 36 | type: 'text', 37 | name: 'title', 38 | label: 'Hello World', 39 | custom: { 40 | labelPopover: 'This is a test to see if this popover will work and wrap correctly.', 41 | showLabelPopover: true, 42 | }, 43 | }, 44 | ], 45 | } 46 | export default Examples 47 | ``` 48 | 49 | ### Custom Component for Popover Content 50 | 51 | You can also use a custom component to render content inside of the popover. 52 | 53 | ```tsx 54 | // CustomPopoverContent.tsx 55 | import React from 'react' 56 | 57 | type Props = { 58 | data: string 59 | } 60 | 61 | export const CustomPopoverContent: React.FC = props => { 62 | const { data } = props 63 | return
{data}
64 | } 65 | ``` 66 | 67 | Then import and pass the component to the 'labelPopover' property in your field config. 68 | 69 | ```ts 70 | custom: { 71 | labelPopover: LabelContent({ data: 'HELLO WORLD!!!' }), 72 | showLabelPopover: true, 73 | } 74 | ``` 75 | 76 | ## Contributing 77 | 78 | Pull requests are welcome. For major changes, please open an issue first 79 | to discuss what you would like to change. -------------------------------------------------------------------------------- /dev/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | dist 3 | /media 4 | node_modules 5 | .DS_Store 6 | .env 7 | -------------------------------------------------------------------------------- /dev/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18.8-alpine as base 2 | 3 | FROM base as builder 4 | 5 | WORKDIR /home/node/app 6 | COPY package*.json ./ 7 | 8 | COPY . . 9 | RUN yarn install 10 | RUN yarn build 11 | 12 | FROM base as runtime 13 | 14 | ENV NODE_ENV=production 15 | ENV PAYLOAD_CONFIG_PATH=dist/payload.config.js 16 | 17 | WORKDIR /home/node/app 18 | COPY package*.json ./ 19 | COPY yarn.lock ./ 20 | 21 | RUN yarn install --production 22 | COPY --from=builder /home/node/app/dist ./dist 23 | COPY --from=builder /home/node/app/build ./build 24 | 25 | EXPOSE 3000 26 | 27 | CMD ["node", "dist/server.js"] 28 | -------------------------------------------------------------------------------- /dev/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | payload: 5 | image: node:18-alpine 6 | ports: 7 | - '3000:3000' 8 | volumes: 9 | - .:/home/node/app 10 | - node_modules:/home/node/app/node_modules 11 | working_dir: /home/node/app/ 12 | command: sh -c "yarn install && yarn dev" 13 | depends_on: 14 | - mongo 15 | env_file: 16 | - .env 17 | 18 | mongo: 19 | image: mongo:latest 20 | ports: 21 | - '27017:27017' 22 | command: 23 | - --storageEngine=wiredTiger 24 | volumes: 25 | - data:/data/db 26 | logging: 27 | driver: none 28 | 29 | volumes: 30 | data: 31 | node_modules: 32 | -------------------------------------------------------------------------------- /dev/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true, 3 | testEnvironment: 'node', 4 | transform: { 5 | '^.+\\.(t|j)sx?$': '/node_modules/@swc/jest', 6 | }, 7 | moduleNameMapper: { 8 | '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 9 | '/src/mocks/fileStub.js', 10 | '\\.(css|scss)$': '/src/mocks/fileStub.js', 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /dev/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nodemon.json", 3 | "ext": "ts", 4 | "exec": "ts-node src/server.ts -- -I", 5 | "stdin": false 6 | } -------------------------------------------------------------------------------- /dev/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Payload2Blank", 3 | "description": "A blank template to get started with Payload", 4 | "version": "1.0.0", 5 | "main": "dist/server.js", 6 | "license": "MIT", 7 | "scripts": { 8 | "dev": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts nodemon", 9 | "build:payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload build", 10 | "build:server": "tsc", 11 | "build": "yarn copyfiles && yarn build:payload && yarn build:server", 12 | "serve": "cross-env PAYLOAD_CONFIG_PATH=dist/payload.config.js NODE_ENV=production node dist/server.js", 13 | "copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png}\" dist/", 14 | "generate:types": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types", 15 | "generate:graphQLSchema": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:graphQLSchema", 16 | "payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload" 17 | }, 18 | "dependencies": { 19 | "@payloadcms/bundler-webpack": "^1.0.0", 20 | "@payloadcms/db-mongodb": "^1.0.0", 21 | "@payloadcms/plugin-cloud": "^2.0.0", 22 | "@payloadcms/richtext-slate": "^1.0.0", 23 | "cross-env": "^7.0.3", 24 | "dotenv": "^8.2.0", 25 | "express": "^4.17.1", 26 | "payload": "^2.0.0" 27 | }, 28 | "devDependencies": { 29 | "@types/express": "^4.17.9", 30 | "copyfiles": "^2.4.1", 31 | "nodemon": "^2.0.6", 32 | "ts-node": "^9.1.1", 33 | "typescript": "^4.8.4" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /dev/plugin.spec.ts: -------------------------------------------------------------------------------- 1 | import type { Server } from 'http' 2 | import mongoose from 'mongoose' 3 | import payload from 'payload' 4 | import { start } from './src/server' 5 | 6 | describe('Plugin tests', () => { 7 | let server: Server 8 | 9 | beforeAll(async () => { 10 | await start({ local: true }) 11 | }) 12 | 13 | afterAll(async () => { 14 | await mongoose.connection.dropDatabase() 15 | await mongoose.connection.close() 16 | server.close() 17 | }) 18 | 19 | // Add tests to ensure that the plugin works as expected 20 | 21 | // Example test to check for seeded data 22 | it('seeds data accordingly', async () => { 23 | const newCollectionQuery = await payload.find({ 24 | collection: 'new-collection', 25 | sort: 'createdAt', 26 | }) 27 | 28 | expect(newCollectionQuery.totalDocs).toEqual(1) 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /dev/src/collections/Examples.ts: -------------------------------------------------------------------------------- 1 | import { CollectionConfig } from 'payload/types' 2 | 3 | // Example Collection - For reference only, this must be added to payload.config.ts to be used. 4 | 5 | const Examples: CollectionConfig = { 6 | slug: 'examples', 7 | admin: { 8 | useAsTitle: 'title', 9 | }, 10 | fields: [ 11 | { 12 | type: 'text', 13 | name: 'title', 14 | label: 'Hello World', 15 | custom: { 16 | labelPopover: 'The quick brown fox jumps over the lazy dog.', 17 | showLabelPopover: true, 18 | }, 19 | }, 20 | ], 21 | } 22 | 23 | export default Examples 24 | -------------------------------------------------------------------------------- /dev/src/collections/Users.ts: -------------------------------------------------------------------------------- 1 | import { CollectionConfig } from 'payload/types'; 2 | 3 | const Users: CollectionConfig = { 4 | slug: 'users', 5 | auth: true, 6 | admin: { 7 | useAsTitle: 'email', 8 | }, 9 | fields: [ 10 | // Email added by default 11 | // Add more fields as needed 12 | ], 13 | }; 14 | 15 | export default Users; -------------------------------------------------------------------------------- /dev/src/mocks/fileStub.js: -------------------------------------------------------------------------------- 1 | export default 'file-stub' 2 | -------------------------------------------------------------------------------- /dev/src/mocks/mockData.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | id: 1, 4 | title: 'Beetlejuice', 5 | year: '1988', 6 | }, 7 | { 8 | id: 2, 9 | title: 'Ghostbusters', 10 | year: '1984', 11 | }, 12 | { 13 | id: 3, 14 | title: 'The Godfather', 15 | year: '1972', 16 | }, 17 | { 18 | id: 4, 19 | title: 'Casablanca', 20 | year: '1942', 21 | }, 22 | { 23 | id: 5, 24 | title: 'Gone with the Wind', 25 | year: '1939', 26 | }, 27 | { 28 | id: 6, 29 | title: 'The Shawshank Redemption', 30 | year: '1994', 31 | }, 32 | { 33 | id: 7, 34 | title: 'Pulp Fiction', 35 | year: '1994', 36 | }, 37 | { 38 | id: 8, 39 | title: 'The Dark Knight', 40 | year: '2008', 41 | }, 42 | { 43 | id: 9, 44 | title: "Schindler's List", 45 | year: '1993', 46 | }, 47 | { 48 | id: 10, 49 | title: 'Citizen Kane', 50 | year: '1941', 51 | }, 52 | { 53 | id: 11, 54 | title: 'The Wizard of Oz', 55 | year: '1939', 56 | }, 57 | { 58 | id: 12, 59 | title: 'Forrest Gump', 60 | year: '1994', 61 | }, 62 | { 63 | id: 13, 64 | title: 'The Matrix', 65 | year: '1999', 66 | }, 67 | { 68 | id: 14, 69 | title: 'The Silence of the Lambs', 70 | year: '1991', 71 | }, 72 | { 73 | id: 15, 74 | title: "One Flew Over the Cuckoo's Nest", 75 | year: '1975', 76 | }, 77 | { 78 | id: 16, 79 | title: 'The Godfather: Part II', 80 | year: '1974', 81 | }, 82 | { 83 | id: 17, 84 | title: 'Goodfellas', 85 | year: '1990', 86 | }, 87 | { 88 | id: 18, 89 | title: 'Lawrence of Arabia', 90 | year: '1962', 91 | }, 92 | { 93 | id: 19, 94 | title: 'Seven Samurai', 95 | year: '1954', 96 | }, 97 | { 98 | id: 20, 99 | title: 'The Shawshank Redemption', 100 | year: '1994', 101 | }, 102 | { 103 | id: 21, 104 | title: 'The Godfather', 105 | year: '1972', 106 | }, 107 | { 108 | id: 22, 109 | title: 'The Dark Knight', 110 | year: '2008', 111 | }, 112 | { 113 | id: 23, 114 | title: 'The Lord of the Rings: The Fellowship of the Ring', 115 | year: '2001', 116 | }, 117 | { 118 | id: 24, 119 | title: 'Inception', 120 | year: '2010', 121 | }, 122 | { 123 | id: 25, 124 | title: 'The Empire Strikes Back', 125 | year: '1980', 126 | }, 127 | { 128 | id: 26, 129 | title: 'The Shawshank Redemption', 130 | year: '1994', 131 | }, 132 | { 133 | id: 27, 134 | title: 'The Godfather', 135 | year: '1972', 136 | }, 137 | { 138 | id: 28, 139 | title: 'The Dark Knight', 140 | year: '2008', 141 | }, 142 | { 143 | id: 29, 144 | title: 'The Lord of the Rings: The Fellowship of the Ring', 145 | year: '2001', 146 | }, 147 | { 148 | id: 30, 149 | title: 'Inception', 150 | year: '2010', 151 | }, 152 | ] 153 | -------------------------------------------------------------------------------- /dev/src/payload.config.ts: -------------------------------------------------------------------------------- 1 | import { buildConfig } from 'payload/config' 2 | import { webpackBundler } from '@payloadcms/bundler-webpack' 3 | import { slateEditor } from '@payloadcms/richtext-slate' 4 | import Examples from './collections/Examples' 5 | import Users from './collections/Users' 6 | import path from 'path' 7 | import { mongooseAdapter } from '@payloadcms/db-mongodb' 8 | //@ts-ignore 9 | import { labelPopoverPlugin } from '../../src/index' 10 | 11 | export default buildConfig({ 12 | admin: { 13 | user: 'users', 14 | bundler: webpackBundler(), 15 | webpack: config => { 16 | const newConfig = { 17 | ...config, 18 | resolve: { 19 | ...config.resolve, 20 | alias: { 21 | ...(config?.resolve?.alias || {}), 22 | react: path.join(__dirname, '../node_modules/react'), 23 | 'react-dom': path.join(__dirname, '../node_modules/react-dom'), 24 | payload: path.join(__dirname, '../node_modules/payload'), 25 | }, 26 | }, 27 | } 28 | return newConfig 29 | }, 30 | }, 31 | editor: slateEditor({}), 32 | collections: [Examples, Users], 33 | typescript: { 34 | outputFile: path.resolve(__dirname, 'payload-types.ts'), 35 | }, 36 | graphQL: { 37 | schemaOutputFile: path.resolve(__dirname, 'generated-schema.graphql'), 38 | }, 39 | //@ts-ignore 40 | plugins: [labelPopoverPlugin({})], 41 | db: mongooseAdapter({ 42 | url: process.env.DATABASE_URI, 43 | }), 44 | }) 45 | -------------------------------------------------------------------------------- /dev/src/server.ts: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import payload from 'payload' 3 | import { InitOptions } from 'payload/config' 4 | 5 | require('dotenv').config() 6 | const app = express() 7 | 8 | // Redirect root to Admin panel 9 | app.get('/', (_, res) => { 10 | res.redirect('/admin') 11 | }) 12 | 13 | export const start = async (args?: Partial) => { 14 | // Initialize Payload 15 | await payload.init({ 16 | secret: process.env.PAYLOAD_SECRET, 17 | express: app, 18 | onInit: async () => { 19 | payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`) 20 | }, 21 | ...(args || {}), 22 | }) 23 | 24 | // Add your own express routes here 25 | 26 | app.listen(3000) 27 | } 28 | 29 | start() 30 | -------------------------------------------------------------------------------- /dev/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "strict": false, 11 | "esModuleInterop": true, 12 | "skipLibCheck": true, 13 | "outDir": "./dist", 14 | "rootDir": "./src", 15 | "jsx": "react", 16 | "paths": { 17 | "payload/generated-types": [ 18 | "./src/payload-types.ts" 19 | ] 20 | } 21 | }, 22 | "include": [ 23 | "src" 24 | ], 25 | "exclude": [ 26 | "node_modules", 27 | "dist", 28 | "build" 29 | ], 30 | "ts-node": { 31 | "transpileOnly": true, 32 | "swc": true 33 | } 34 | } -------------------------------------------------------------------------------- /eslint-config/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | extends: [ 4 | 'airbnb-base', 5 | require.resolve('./rules/style.js'), 6 | require.resolve('./rules/import.js'), 7 | require.resolve('./rules/typescript.js'), 8 | require.resolve('./rules/prettier.js'), 9 | ], 10 | env: { 11 | es6: true, 12 | browser: true, 13 | node: true, 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /eslint-config/rules/import.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | es6: true, 4 | }, 5 | extends: ['plugin:import/errors', 'plugin:import/warnings', 'plugin:import/typescript'], 6 | plugins: ['import'], 7 | settings: { 8 | 'import/parsers': { 9 | '@typescript-eslint/parser': ['.ts'], 10 | }, 11 | }, 12 | rules: { 13 | /** 14 | * https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-unresolved.md 15 | */ 16 | 'import/no-unresolved': ['error', { commonjs: true, caseSensitive: true }], 17 | 'import/no-default-export': 'off', 18 | 'import/prefer-default-export': 'off', 19 | 'import/extensions': [ 20 | 'error', 21 | 'ignorePackages', 22 | { 23 | ts: 'never', 24 | tsx: 'never', 25 | js: 'never', 26 | jsx: 'never', 27 | }, 28 | ], 29 | 'import/no-extraneous-dependencies': 'off', 30 | /** 31 | * https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/named.md#when-not-to-use-it 32 | */ 33 | 'import/named': 'error', 34 | 'import/no-relative-packages': 'warn', 35 | 'import/no-import-module-exports': 'warn', 36 | 'import/no-cycle': 'warn', 37 | }, 38 | } 39 | -------------------------------------------------------------------------------- /eslint-config/rules/prettier.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['prettier'], 3 | extends: ['plugin:prettier/recommended'], 4 | rules: { 5 | 'prettier/prettier': 'error', 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /eslint-config/rules/style.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | 'prefer-named-exports': 'off', 4 | 5 | 'prefer-destructuring': 'off', 6 | // 'prefer-destructuring': ['warn', { object: true, array: true }], 7 | // ensure all object/arrays end with a comma 8 | 'comma-dangle': ['error', 'always-multiline'], 9 | 'class-methods-use-this': 'off', 10 | // consistent new lines 11 | 'function-paren-newline': ['error', 'consistent'], 12 | 'eol-last': ['error', 'always'], 13 | // allow restricted syntax like for...of loops 14 | 'no-restricted-syntax': 'off', 15 | 'no-await-in-loop': 'off', 16 | 'no-console': 'error', 17 | // 'no-floating-promises': true, 18 | // do not allow process.env access in files 19 | 'no-process-env': 'warn', 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /eslint-config/rules/typescript.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['@typescript-eslint'], 3 | overrides: [ 4 | { 5 | files: ['**/**.ts', '**/**.d.ts'], 6 | rules: { 7 | 'no-undef': 'off', 8 | camelcase: 'off', 9 | '@typescript-eslint/adjacent-overload-signatures': 'error', 10 | /** 11 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/array-type.md 12 | */ 13 | '@typescript-eslint/array-type': ['error', { default: 'array-simple' }], 14 | /** 15 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/await-thenable.md 16 | */ 17 | '@typescript-eslint/await-thenable': 'off', 18 | /** 19 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/consistent-type-assertions.md 20 | */ 21 | '@typescript-eslint/consistent-type-assertions': [ 22 | 'error', 23 | { assertionStyle: 'as', objectLiteralTypeAssertions: 'allow-as-parameter' }, 24 | ], 25 | /** 26 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/consistent-type-definitions.md 27 | */ 28 | '@typescript-eslint/consistent-type-definitions': ['error', 'interface'], 29 | /** 30 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/consistent-type-imports.md 31 | */ 32 | '@typescript-eslint/consistent-type-imports': 'warn', 33 | /** 34 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/explicit-function-return-type.md 35 | */ 36 | '@typescript-eslint/explicit-function-return-type': [ 37 | 'error', 38 | { 39 | // TODO: come back and check if we need those 40 | allowExpressions: true, 41 | allowTypedFunctionExpressions: true, 42 | allowHigherOrderFunctions: true, 43 | allowConciseArrowFunctionExpressionsStartingWithVoid: false, 44 | }, 45 | ], 46 | /** 47 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md 48 | */ 49 | '@typescript-eslint/explicit-member-accessibility': [ 50 | 'error', 51 | { accessibility: 'no-public' }, 52 | ], 53 | /** 54 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/member-delimiter-style.md 55 | */ 56 | '@typescript-eslint/member-delimiter-style': [ 57 | 'error', 58 | { 59 | multiline: { 60 | delimiter: 'none', 61 | requireLast: true, 62 | }, 63 | singleline: { 64 | delimiter: 'semi', 65 | requireLast: false, 66 | }, 67 | }, 68 | ], 69 | /** 70 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/method-signature-style.md 71 | */ 72 | '@typescript-eslint/method-signature-style': 'off', 73 | /** 74 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/naming-convention.md 75 | */ 76 | '@typescript-eslint/naming-convention': [ 77 | 'off', 78 | { 79 | selector: 'default', 80 | format: ['camelCase'], 81 | leadingUnderscore: 'forbid', 82 | trailingUnderscore: 'forbid', 83 | }, 84 | { 85 | selector: 'variable', 86 | format: ['camelCase', 'UPPER_CASE'], 87 | leadingUnderscore: 'forbid', 88 | trailingUnderscore: 'forbid', 89 | }, 90 | // Enforce that type parameters (generics) are prefixed with T or U 91 | { 92 | selector: 'typeParameter', 93 | format: ['PascalCase'], 94 | prefix: ['T', 'U'], 95 | }, 96 | // enforce boolean variables to start with proper prefix. 97 | { 98 | selector: 'variable', 99 | types: ['boolean'], 100 | format: ['PascalCase'], 101 | prefix: ['is', 'should', 'has', 'can', 'did', 'will'], 102 | }, 103 | // Enforce that interface names do not begin with an I 104 | { 105 | selector: 'interface', 106 | format: ['PascalCase'], 107 | custom: { 108 | regex: '^I[A-Z]', 109 | match: false, 110 | }, 111 | }, 112 | { 113 | selector: [ 114 | 'function', 115 | 'parameter', 116 | 'property', 117 | 'parameterProperty', 118 | 'method', 119 | 'accessor', 120 | ], 121 | format: ['camelCase'], 122 | leadingUnderscore: 'forbid', 123 | trailingUnderscore: 'forbid', 124 | }, 125 | { 126 | selector: ['class', 'interface', 'typeAlias', 'enum', 'typeParameter'], 127 | format: ['PascalCase'], 128 | leadingUnderscore: 'forbid', 129 | trailingUnderscore: 'forbid', 130 | }, 131 | ], 132 | /** 133 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-base-to-string.md 134 | */ 135 | '@typescript-eslint/no-base-to-string': 'off', 136 | /** 137 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-confusing-non-null-assertion.md 138 | */ 139 | '@typescript-eslint/no-confusing-non-null-assertion': 'error', 140 | /** 141 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-dynamic-delete.md 142 | */ 143 | '@typescript-eslint/no-dynamic-delete': 'error', 144 | /** 145 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-empty-interface.md 146 | */ 147 | '@typescript-eslint/no-empty-interface': 'off', 148 | /** 149 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-explicit-any.md 150 | */ 151 | '@typescript-eslint/no-explicit-any': [ 152 | 'warn', 153 | { 154 | ignoreRestArgs: true, 155 | // enable later 156 | fixToUnknown: false, 157 | }, 158 | ], 159 | /** 160 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-extra-non-null-assertion.md 161 | */ 162 | '@typescript-eslint/no-extra-non-null-assertion': 'error', 163 | /** 164 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-extraneous-class.md 165 | */ 166 | '@typescript-eslint/no-extraneous-class': [ 167 | 'error', 168 | { 169 | allowConstructorOnly: false, 170 | allowEmpty: false, 171 | allowStaticOnly: false, 172 | allowWithDecorator: false, 173 | }, 174 | ], 175 | /** 176 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-floating-promises.md 177 | */ 178 | '@typescript-eslint/no-floating-promises': 'off', 179 | /** 180 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-for-in-array.md 181 | */ 182 | '@typescript-eslint/no-for-in-array': 'off', 183 | /** 184 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-implicit-any-catch.md 185 | */ 186 | '@typescript-eslint/no-implicit-any-catch': [ 187 | 'error', 188 | { 189 | allowExplicitAny: false, 190 | }, 191 | ], 192 | /** 193 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-implied-eval.md 194 | */ 195 | '@typescript-eslint/no-implied-eval': 'off', 196 | /** 197 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-inferrable-types.md 198 | */ 199 | '@typescript-eslint/no-inferrable-types': [ 200 | 'error', 201 | { 202 | ignoreParameters: false, 203 | ignoreProperties: false, 204 | }, 205 | ], 206 | /** 207 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-invalid-void-type.md 208 | */ 209 | '@typescript-eslint/no-invalid-void-type': [ 210 | 'off', 211 | { 212 | allowInGenericTypeArguments: true, 213 | }, 214 | ], 215 | /** 216 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-misused-new.md 217 | */ 218 | '@typescript-eslint/no-misused-new': 'error', 219 | /** 220 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-misused-promises.md 221 | */ 222 | '@typescript-eslint/no-misused-promises': 'off', 223 | /** 224 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-namespace.md 225 | */ 226 | '@typescript-eslint/no-namespace': 'off', 227 | /** 228 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-non-null-asserted-optional-chain.md 229 | */ 230 | '@typescript-eslint/no-non-null-asserted-optional-chain': 'error', 231 | /** 232 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-non-null-assertion.md 233 | */ 234 | '@typescript-eslint/no-non-null-assertion': 'warn', 235 | /** 236 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-parameter-properties.md 237 | */ 238 | '@typescript-eslint/no-parameter-properties': 'error', 239 | /** 240 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-require-imports.md 241 | */ 242 | '@typescript-eslint/no-require-imports': 'error', 243 | /** 244 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-this-alias.md 245 | */ 246 | '@typescript-eslint/no-this-alias': 'error', 247 | /** 248 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-throw-literal.md 249 | */ 250 | '@typescript-eslint/no-throw-literal': 'off', 251 | /** 252 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-type-alias.md 253 | */ 254 | '@typescript-eslint/no-type-alias': [ 255 | 'off', 256 | { 257 | allowAliases: 'always', 258 | allowCallbacks: 'always', 259 | allowConditionalTypes: 'always', 260 | allowConstructors: 'never', 261 | allowLiterals: 'in-unions-and-intersections', 262 | allowMappedTypes: 'always', 263 | allowTupleTypes: 'always', 264 | }, 265 | ], 266 | /** 267 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-boolean-literal-compare.md 268 | */ 269 | '@typescript-eslint/no-unnecessary-boolean-literal-compare': 'off', 270 | /** 271 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-condition.md 272 | */ 273 | '@typescript-eslint/no-unnecessary-condition': 'off', 274 | /** 275 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-qualifier.md 276 | */ 277 | '@typescript-eslint/no-unnecessary-qualifier': 'off', 278 | /** 279 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-type-arguments.md 280 | */ 281 | '@typescript-eslint/no-unnecessary-type-arguments': 'off', 282 | /** 283 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.md 284 | */ 285 | '@typescript-eslint/no-unnecessary-type-assertion': 'off', 286 | /** 287 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unsafe-assignment.md 288 | */ 289 | '@typescript-eslint/no-unsafe-assignment': 'off', 290 | /** 291 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unsafe-call.md 292 | */ 293 | '@typescript-eslint/no-unsafe-call': 'off', 294 | /** 295 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unsafe-member-access.md 296 | */ 297 | '@typescript-eslint/no-unsafe-member-access': 'off', 298 | /** 299 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unsafe-return.md 300 | */ 301 | '@typescript-eslint/no-unsafe-return': 'off', 302 | /** 303 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-var-requires.md 304 | */ 305 | '@typescript-eslint/no-var-requires': 'error', 306 | /** 307 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-as-const.md 308 | */ 309 | '@typescript-eslint/prefer-as-const': 'error', 310 | /** 311 | * We don't care about enums having implicit values. 312 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-enum-initializers.md 313 | */ 314 | '@typescript-eslint/prefer-enum-initializers': 'off', 315 | /** 316 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-for-of.md 317 | */ 318 | '@typescript-eslint/prefer-for-of': 'error', 319 | /** 320 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-includes.md 321 | */ 322 | '@typescript-eslint/prefer-includes': 'off', 323 | /** 324 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-literal-enum-member.md 325 | */ 326 | '@typescript-eslint/prefer-literal-enum-member': 'error', 327 | /** 328 | * using ES2015 syntax so this rule can be safetly turned off 329 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-namespace-keyword.md 330 | */ 331 | '@typescript-eslint/prefer-namespace-keyword': 'off', 332 | /** 333 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md 334 | */ 335 | '@typescript-eslint/prefer-nullish-coalescing': 'off', 336 | /** 337 | * only set to warn because there are some cases this behavior doesnt work because 338 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-optional-chain.md 339 | */ 340 | '@typescript-eslint/prefer-optional-chain': 'warn', 341 | /** 342 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-readonly.md 343 | */ 344 | '@typescript-eslint/prefer-readonly': 'off', 345 | /** 346 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.md 347 | */ 348 | '@typescript-eslint/prefer-readonly-parameter-types': 'off', 349 | /** 350 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-reduce-type-parameter.md 351 | */ 352 | '@typescript-eslint/prefer-reduce-type-parameter': 'off', 353 | /** 354 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-regexp-exec.md 355 | */ 356 | '@typescript-eslint/prefer-regexp-exec': 'off', 357 | /** 358 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-string-starts-ends-with.md 359 | */ 360 | '@typescript-eslint/prefer-string-starts-ends-with': 'off', 361 | /** 362 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-ts-expect-error.md 363 | */ 364 | '@typescript-eslint/prefer-ts-expect-error': 'warn', 365 | /** 366 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/promise-function-async.md 367 | */ 368 | '@typescript-eslint/promise-function-async': 'off', 369 | /** 370 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/require-array-sort-compare.md 371 | */ 372 | '@typescript-eslint/require-array-sort-compare': 'off', 373 | /** 374 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/restrict-plus-operands.md 375 | */ 376 | '@typescript-eslint/restrict-plus-operands': 'off', 377 | /** 378 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/restrict-template-expressions.md 379 | */ 380 | '@typescript-eslint/restrict-template-expressions': 'off', 381 | /** 382 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/strict-boolean-expressions.md 383 | */ 384 | '@typescript-eslint/strict-boolean-expressions': 'off', 385 | /** 386 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.md 387 | */ 388 | '@typescript-eslint/switch-exhaustiveness-check': 'off', 389 | /** 390 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/triple-slash-reference.md 391 | */ 392 | '@typescript-eslint/triple-slash-reference': 'error', 393 | /** 394 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/type-annotation-spacing.md 395 | */ 396 | '@typescript-eslint/type-annotation-spacing': [ 397 | 'error', 398 | { 399 | before: false, 400 | after: true, 401 | overrides: { 402 | arrow: { 403 | before: true, 404 | after: true, 405 | }, 406 | }, 407 | }, 408 | ], 409 | /** 410 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/typedef.md 411 | */ 412 | '@typescript-eslint/typedef': [ 413 | 'error', 414 | { 415 | arrayDestructuring: false, 416 | arrowParameter: false, 417 | memberVariableDeclaration: false, 418 | objectDestructuring: false, 419 | parameter: false, 420 | propertyDeclaration: true, 421 | variableDeclaration: false, 422 | variableDeclarationIgnoreFunction: false, 423 | }, 424 | ], 425 | /** 426 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/unbound-method.md 427 | */ 428 | '@typescript-eslint/unbound-method': 'off', 429 | /** 430 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/unified-signatures.md 431 | */ 432 | '@typescript-eslint/unified-signatures': 'off', 433 | 434 | // @typescript-eslint Extension Rules 435 | // ================================================================================== 436 | /** 437 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/brace-style.md 438 | */ 439 | 'brace-style': 'off', 440 | '@typescript-eslint/brace-style': 'error', 441 | /** 442 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/comma-spacing.md 443 | */ 444 | 'comma-spacing': 'off', 445 | '@typescript-eslint/comma-spacing': 'error', 446 | /** 447 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/default-param-last.md 448 | */ 449 | 'default-param-last': 'off', 450 | '@typescript-eslint/default-param-last': 'error', 451 | /** 452 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/dot-notation.md 453 | */ 454 | 'dot-notation': 'error', 455 | '@typescript-eslint/dot-notation': 'off', 456 | /** 457 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/func-call-spacing.md 458 | */ 459 | 'func-call-spacing': 'off', 460 | '@typescript-eslint/func-call-spacing': 'error', 461 | /** 462 | * use prettier instead 463 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/indent.md 464 | */ 465 | indent: 'off', 466 | '@typescript-eslint/indent': 'off', 467 | /** 468 | * Allow a mix between the two 469 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/init-declarations.md 470 | */ 471 | '@typescript-eslint/init-declarations': 'off', 472 | /** 473 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/keyword-spacing.md 474 | */ 475 | 'keyword-spacing': 'off', 476 | '@typescript-eslint/keyword-spacing': 'error', 477 | /** 478 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/lines-between-class-members.md 479 | */ 480 | 'lines-between-class-members': 'off', 481 | '@typescript-eslint/lines-between-class-members': [ 482 | 'error', 483 | 'always', 484 | { 485 | // base eslint config 486 | exceptAfterSingleLine: true, 487 | // typescript specific 488 | exceptAfterOverload: true, 489 | }, 490 | ], 491 | /** 492 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-array-constructor.md 493 | */ 494 | 'no-array-constructor': 'off', 495 | '@typescript-eslint/no-array-constructor': 'error', 496 | /** 497 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-dupe-class-members.md 498 | */ 499 | 'no-dupe-class-members': 'off', 500 | '@typescript-eslint/no-dupe-class-members': 'error', 501 | /** 502 | * Use prettier 503 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-extra-parens.md 504 | */ 505 | 'no-extra-parens': 'off', 506 | '@typescript-eslint/no-extra-parens': 'off', 507 | /** 508 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-extra-semi.md 509 | */ 510 | 'no-extra-semi': 'off', 511 | '@typescript-eslint/no-extra-semi': 'error', 512 | /** 513 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-invalid-this.md 514 | */ 515 | 'no-invalid-this': 'off', 516 | '@typescript-eslint/no-invalid-this': 'error', 517 | /** 518 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-loss-of-precision.md 519 | */ 520 | 'no-loss-of-precision': 'off', 521 | '@typescript-eslint/no-loss-of-precision': 'error', 522 | /** 523 | * https://eslint.org/docs/rules/no-magic-numbers 524 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-magic-numbers.md 525 | */ 526 | 'no-magic-numbers': 'off', 527 | '@typescript-eslint/no-magic-numbers': [ 528 | 'off', 529 | { 530 | // base eslint configs 531 | ignoreArrayIndexes: true, 532 | ignoreDefaultValues: true, 533 | enforceConst: true, 534 | // typescript specific configs 535 | ignoreEnums: true, 536 | ignoreNumericLiteralTypes: true, 537 | ignoreReadonlyClassProperties: true, 538 | }, 539 | ], 540 | /** 541 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-redeclare.md 542 | */ 543 | 'no-redeclare': 'off', 544 | '@typescript-eslint/no-redeclare': [ 545 | 'error', 546 | { 547 | // prevents variables from being created with global variable naming 548 | builtinGlobals: true, 549 | }, 550 | ], 551 | /** 552 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-shadow.md 553 | */ 554 | 'no-shadow': 'off', 555 | '@typescript-eslint/no-shadow': [ 556 | 'error', 557 | { 558 | // No variables + types with same naming 559 | ignoreTypeValueShadow: false, 560 | }, 561 | ], 562 | /** 563 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unused-expressions.md 564 | */ 565 | 'no-unused-expressions': 'off', 566 | '@typescript-eslint/no-unused-expressions': 'error', 567 | /** 568 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars.md 569 | */ 570 | 'no-unused-vars': 'off', 571 | '@typescript-eslint/no-unused-vars': [ 572 | 'error', 573 | { 574 | ignoreRestSiblings: true, 575 | }, 576 | ], 577 | /** 578 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-use-before-define.md 579 | */ 580 | 'no-use-before-define': 'off', 581 | '@typescript-eslint/no-use-before-define': 'off', 582 | /** 583 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-useless-constructor.md 584 | */ 585 | 'no-useless-constructor': 'off', 586 | '@typescript-eslint/no-useless-constructor': 'error', 587 | /** 588 | * https://eslint.org/docs/rules/quotes 589 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/quotes.md 590 | */ 591 | quotes: 'off', 592 | '@typescript-eslint/quotes': [ 593 | 'error', 594 | 'single', 595 | { 596 | avoidEscape: true, 597 | allowTemplateLiterals: true, 598 | }, 599 | ], 600 | /** 601 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/require-await.md 602 | */ 603 | '@typescript-eslint/require-await': 'off', 604 | /** 605 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/return-await.md 606 | */ 607 | '@typescript-eslint/return-await': 'off', 608 | /** 609 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/semi.md 610 | */ 611 | semi: 'off', 612 | '@typescript-eslint/semi': ['error', 'never'], 613 | /** 614 | * https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/space-before-function-paren.md 615 | */ 616 | 'space-before-function-paren': 'off', 617 | '@typescript-eslint/space-before-function-paren': [ 618 | 'error', 619 | { 620 | anonymous: 'never', 621 | named: 'never', 622 | asyncArrow: 'always', 623 | }, 624 | ], 625 | }, 626 | }, 627 | ], 628 | } 629 | -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notchris/payload-label-popover/eee44a9d6c280371ed551f2feb09d7dac730d18f/example.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "payload-label-popover", 3 | "version": "1.0.1", 4 | "description": "A plugin to add descriptive popovers to field labels in Payload.", 5 | "main": "src/index.ts", 6 | "peerDependencies": { 7 | "payload": "^2.0.0" 8 | }, 9 | "publishConfig": { 10 | "registry": "https://registry.npmjs.org/" 11 | }, 12 | "devDependencies": { 13 | "@payloadcms/eslint-config": "^0.0.1", 14 | "@swc/jest": "^0.2.28", 15 | "@types/jest": "^29.5.11", 16 | "@typescript-eslint/eslint-plugin": "5.12.1", 17 | "@typescript-eslint/parser": "5.12.1", 18 | "dotenv": "^8.2.0", 19 | "eslint": "^8.19.0", 20 | "eslint-config-airbnb-base": "^14.2.1", 21 | "eslint-config-prettier": "^8.5.0", 22 | "eslint-plugin-import": "2.25.4", 23 | "eslint-plugin-prettier": "^4.0.0", 24 | "jest": "^29.7.0", 25 | "payload": "^2.0.14", 26 | "prettier": "^2.7.1", 27 | "react": "^18.0.0", 28 | "typescript": "^4.8.4", 29 | "webpack": "^5.90.3" 30 | }, 31 | "scripts": { 32 | "test": "echo \"Error: no test specified\" && exit 1" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "git+ssh://git@github.com/notchris/payload-label-popover.git" 37 | }, 38 | "keywords": [ 39 | "payload", 40 | "react-tiny-popover", 41 | "payload-plugin" 42 | ], 43 | "author": "notchris ", 44 | "license": "MIT", 45 | "bugs": { 46 | "url": "https://github.com/notchris/payload-label-popover/issues" 47 | }, 48 | "homepage": "https://github.com/notchris/payload-label-popover#readme", 49 | "dependencies": { 50 | "react-tiny-popover": "^8.0.4" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/LabelPopover.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef } from 'react' 2 | import { useTranslation } from 'react-i18next' 3 | import { ArrowContainer, Popover } from 'react-tiny-popover' 4 | 5 | import { getTranslation } from 'payload/utilities' 6 | 7 | type Props = { 8 | htmlFor?: string 9 | label?: Record | false | string 10 | required?: boolean 11 | labelPopover: React.FC | string 12 | showLabelPopover: boolean 13 | } 14 | 15 | export const LabelPopover: React.FC = props => { 16 | const { label, required = false, labelPopover, showLabelPopover } = props 17 | const { t, i18n } = useTranslation() 18 | const [isPopoverOpen, setIsPopoverOpen] = useState(false) 19 | 20 | if (label) { 21 | return ( 22 |
23 | {getTranslation(label, i18n)} 24 | {required && *} 25 | {showLabelPopover && ( 26 | setIsPopoverOpen(false)} 31 | content={({ position, childRect, popoverRect }) => ( 32 | 41 |
setIsPopoverOpen(!isPopoverOpen)} 50 | > 51 | {labelPopover} 52 |
53 |
54 | )} 55 | > 56 | 80 |
81 | )} 82 |
83 | ) 84 | } 85 | 86 | return null 87 | } 88 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { labelPopoverPlugin } from './plugin' 2 | -------------------------------------------------------------------------------- /src/onInitExtension.ts: -------------------------------------------------------------------------------- 1 | import type { Payload } from 'payload/dist/payload' 2 | 3 | export const onInitExtension = (pluginOptions: {}, payload: Payload): void => { 4 | const { express: app } = payload 5 | 6 | if (!app) return 7 | 8 | try { 9 | } catch (err: unknown) { 10 | payload.logger.error({ msg: 'Error in onInitExtension', err }) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/plugin.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from 'payload/config' 2 | 3 | import { onInitExtension } from './onInitExtension' 4 | import { LabelPopover } from './LabelPopover' 5 | import { CollectionConfig, Field } from 'payload/dist/exports/types' 6 | 7 | const addCustomLabelToFields = (collection: CollectionConfig) => { 8 | const traverseFields = (fields: Field[]) => { 9 | fields.forEach(field => { 10 | field.admin = field.admin ?? {} 11 | field.admin.components = { 12 | ...(field.admin?.components ?? {}), 13 | //@ts-ignore 14 | Label: props => 15 | LabelPopover({ 16 | ...props, 17 | showLabelPopover: field.custom?.showLabelPopover, 18 | labelPopover: field.custom?.labelPopover, 19 | }), 20 | } 21 | if ('fields' in field) { 22 | traverseFields(field.fields) 23 | } 24 | 25 | if ('blocks' in field) { 26 | field.blocks.forEach(block => traverseFields(block.fields)) 27 | } 28 | 29 | if ('tabs' in field) { 30 | field.tabs.forEach(tab => traverseFields(tab.fields)) 31 | } 32 | }) 33 | } 34 | 35 | traverseFields(collection.fields) 36 | } 37 | 38 | export const labelPopoverPlugin = 39 | (pluginOptions: {}): Plugin => 40 | incomingConfig => { 41 | let config = { ...incomingConfig } 42 | 43 | config.admin = { 44 | ...(config.admin || {}), 45 | components: { 46 | ...(config.admin?.components || {}), 47 | }, 48 | } 49 | 50 | config.collections = [...(config.collections || [])] 51 | 52 | if (config.collections !== undefined) { 53 | config.collections.forEach(collection => { 54 | addCustomLabelToFields(collection) 55 | }) 56 | } 57 | 58 | config.onInit = async payload => { 59 | if (incomingConfig.onInit) await incomingConfig.onInit(payload) 60 | onInitExtension(pluginOptions, payload) 61 | } 62 | 63 | return config 64 | } 65 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "dom", 5 | "dom.iterable", 6 | "esnext" 7 | ], 8 | "target": "es5", 9 | "outDir": "./dist", 10 | "allowJs": true, 11 | "module": "commonjs", 12 | "sourceMap": true, 13 | "jsx": "react", 14 | "esModuleInterop": true, 15 | "declaration": true, 16 | "declarationDir": "./dist", 17 | "skipLibCheck": true, 18 | "strict": true, 19 | }, 20 | "include": [ 21 | "src/**/*", 22 | ], 23 | } --------------------------------------------------------------------------------