├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── package.config.ts ├── package.json ├── sanity.json ├── src ├── extends.d.ts ├── globals.d.ts ├── index.ts ├── note-field.tsx ├── note-input.tsx ├── types │ └── index.ts └── v2-incompatible.js ├── tsconfig.dist.json ├── tsconfig.json ├── tsconfig.settings.json ├── v2-incompatible.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | ; editorconfig.org 2 | root = true 3 | charset= utf8 4 | 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | indent_style = space 10 | indent_size = 2 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.js 2 | .eslintrc.js 3 | commitlint.config.js 4 | dist 5 | lint-staged.config.js 6 | package.config.ts 7 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "node": true, 5 | "browser": true 6 | }, 7 | "extends": [ 8 | "sanity", 9 | "sanity/typescript", 10 | "sanity/react", 11 | "plugin:react-hooks/recommended", 12 | "plugin:prettier/recommended", 13 | "plugin:react/jsx-runtime" 14 | ], 15 | "rules": { 16 | "react/jsx-no-bind": "off", 17 | "no-nested-ternary": "off", 18 | "react/require-default-props": "off" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-error.log* 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules 32 | jspm_packages 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | 40 | # macOS finder cache file 41 | .DS_Store 42 | 43 | # VS Code settings 44 | .vscode 45 | 46 | # IntelliJ 47 | .idea 48 | *.iml 49 | 50 | # Cache 51 | .cache 52 | 53 | # Yalc 54 | .yalc 55 | yalc.lock 56 | 57 | # npm package zips 58 | *.tgz 59 | 60 | # Compiled plugin 61 | dist 62 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .prettierrc -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | pnpm-lock.yaml 3 | yarn.lock 4 | package-lock.json 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "semi": false, 4 | "singleQuote": true, 5 | "tabWidth": 2 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Nick DiMatteo 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 |

2 | Note Fields for Sanity 3 |

4 |

5 | Display inline notes within your schemas
6 | ✨ uses Sanity UI ✨ multiple styles ✨ dark mode compatible ✨ 7 |

8 | 9 | ![note-field-v3](https://user-images.githubusercontent.com/737188/219781860-0e43a189-3fce-48e6-8440-f70908deba54.png) 10 | 11 |
12 | 13 | ## 🔌 Install 14 | 15 | ```sh 16 | yarn add sanity-plugin-note-field 17 | # or npm 18 | npm i sanity-plugin-note-field 19 | ``` 20 | 21 | > **Warning**
This is a **Sanity Studio V3** plugin. For the V2 version, please refer to the [studio-v2 branch](https://github.com/ndimatteo/sanity-plugin-note-field/tree/studio-v2). 22 | 23 |
24 | 25 | ## ⚙️ Configure 26 | 27 | ```ts 28 | // `sanity.config.ts` / `sanity.config.js`: 29 | import { defineConfig } from 'sanity' 30 | import { noteField } from 'sanity-plugin-note-field' 31 | 32 | export default defineConfig({ 33 | // ... 34 | plugins: [ 35 | // ... 36 | noteField(), 37 | ], 38 | }) 39 | ``` 40 | 41 |
42 | 43 | ## 🗒️ Usage 44 | 45 | ```js 46 | defineField({ 47 | title: 'Important!', 48 | description: 'a custom Message...', 49 | name: 'myCustomNote', 50 | type: 'note', 51 | options: { 52 | icon: () => , 53 | tone: 'caution', 54 | }, 55 | }) 56 | ``` 57 | 58 | ### Properties 59 | | Name | Type | Description | 60 | | -------- | ------------------------- | ---------------------------------------------------------------------------- | 61 | | `type` | string | **(Required)** Value must be set to `note`. | 62 | | `name` | string | **(Required)** The field name. This will be the key in the data record. | 63 | | `title` | string | **(Optional)** Short title, appears in bold above the optional description. | 64 | | `description` | string / React.Component | **(Optional)** Long form message, displayed under the title. | 65 | 66 | ### Options 67 | | Name | Type | Description | 68 | | -------- | --------------------- | ---------------------------------------------------------------------------- | 69 | | `icon` | React.Component | **(Optional)** Display an icon alongside your note's title/message.
*Just remember that any schema file with icons in them should have a .jsx or .tsx extension.* | 70 | | `tone` | string | **(Optional)** The color of the note.
*Accepts any of the [Sanity UI Card](https://www.sanity.io/ui/docs/primitive/card#properties) tone values. Defaults to `primary`.* | 71 | 72 |
73 | 74 | ## 🧪 Develop & test 75 | 76 | This plugin uses [@sanity/plugin-kit](https://github.com/sanity-io/plugin-kit) 77 | with default configuration for build & watch scripts. 78 | 79 | See [Testing a plugin in Sanity Studio](https://github.com/sanity-io/plugin-kit#testing-a-plugin-in-sanity-studio) 80 | on how to run this plugin with hotreload in the studio. 81 | 82 |
83 | 84 | ## 🤝 License 85 | 86 | ### MIT 87 | 88 | > [nickdimatteo.com](https://nickdimatteo.com)  ·  89 | > Github [@ndimatteo](https://github.com/ndimatteo)  ·  90 | > Instagram [@ndimatteo](https://instagram.com/ndimatteo) 91 | -------------------------------------------------------------------------------- /package.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@sanity/pkg-utils' 2 | 3 | export default defineConfig({ 4 | legacyExports: true, 5 | dist: 'dist', 6 | tsconfig: 'tsconfig.dist.json', 7 | 8 | // Remove this block to enable strict export validation 9 | extract: { 10 | rules: { 11 | 'ae-forgotten-export': 'off', 12 | 'ae-incompatible-release-tags': 'off', 13 | 'ae-internal-missing-underscore': 'off', 14 | 'ae-missing-release-tag': 'off', 15 | }, 16 | }, 17 | }) 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sanity-plugin-note-field", 3 | "version": "2.0.2", 4 | "description": "Display inline notes within your schemas", 5 | "keywords": [ 6 | "sanity", 7 | "sanity-plugin", 8 | "note", 9 | "input", 10 | "custom field" 11 | ], 12 | "homepage": "https://github.com/ndimatteo/sanity-plugin-note-field#readme", 13 | "bugs": { 14 | "url": "https://github.com/ndimatteo/sanity-plugin-note-field/issues" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+ssh://git@github.com/ndimatteo/sanity-plugin-note-field.git" 19 | }, 20 | "license": "MIT", 21 | "author": "Nick DiMatteo ", 22 | "exports": { 23 | ".": { 24 | "types": "./dist/index.d.ts", 25 | "source": "./src/index.ts", 26 | "require": "./dist/index.js", 27 | "import": "./dist/index.esm.js", 28 | "default": "./dist/index.esm.js" 29 | }, 30 | "./package.json": "./package.json" 31 | }, 32 | "main": "./dist/index.js", 33 | "module": "./dist/index.esm.js", 34 | "source": "./src/index.ts", 35 | "types": "./dist/index.d.ts", 36 | "files": [ 37 | "dist", 38 | "sanity.json", 39 | "src", 40 | "v2-incompatible.js" 41 | ], 42 | "scripts": { 43 | "build": "run-s clean && plugin-kit verify-package --silent && pkg-utils build --strict && pkg-utils --strict", 44 | "clean": "rimraf dist", 45 | "format": "prettier --write --cache --ignore-unknown .", 46 | "link-watch": "plugin-kit link-watch", 47 | "lint": "eslint '**/*.+(ts|js|tsx|jsx)'", 48 | "prepublishOnly": "run-s build", 49 | "watch": "pkg-utils watch --strict" 50 | }, 51 | "dependencies": { 52 | "@sanity/incompatible-plugin": "^1.0.4", 53 | "@sanity/ui": "^1.0.14", 54 | "lodash": "^4.17.21", 55 | "prop-types": "^15.7.2" 56 | }, 57 | "devDependencies": { 58 | "@sanity/pkg-utils": "^2.2.3", 59 | "@sanity/plugin-kit": "^3.1.4", 60 | "@types/react": "^18.0.27", 61 | "@typescript-eslint/eslint-plugin": "^5.49.0", 62 | "@typescript-eslint/parser": "^5.49.0", 63 | "eslint": "^8.32.0", 64 | "eslint-config-prettier": "^8.6.0", 65 | "eslint-config-sanity": "^6.0.0", 66 | "eslint-plugin-prettier": "^4.2.1", 67 | "eslint-plugin-react": "^7.32.1", 68 | "eslint-plugin-react-hooks": "^4.6.0", 69 | "npm-run-all": "^4.1.5", 70 | "prettier": "^2.8.3", 71 | "prettier-plugin-packagejson": "^2.4.0", 72 | "react": "^18.2.0", 73 | "react-dom": "^18.2.0", 74 | "react-is": "^18.2.0", 75 | "rimraf": "^4.1.2", 76 | "sanity": "^3.2.5", 77 | "styled-components": "^5.3.6", 78 | "typescript": "^4.9.4" 79 | }, 80 | "peerDependencies": { 81 | "react": "^18", 82 | "sanity": "^3" 83 | }, 84 | "engines": { 85 | "node": ">=14" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /sanity.json: -------------------------------------------------------------------------------- 1 | { 2 | "parts": [ 3 | { 4 | "implements": "part:@sanity/base/sanity-root", 5 | "path": "./v2-incompatible.js" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /src/extends.d.ts: -------------------------------------------------------------------------------- 1 | // this should not be flagged as this is a d.ts file 2 | /* eslint-disable no-unused-vars */ 3 | 4 | import '@sanity/ui' 5 | import React from 'react' 6 | declare module '@sanity/ui' { 7 | interface InlineProps { 8 | children: React.ReactNode | React.ReactNode[] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/globals.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.css' 2 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { noteField } from './note-field' 2 | -------------------------------------------------------------------------------- /src/note-field.tsx: -------------------------------------------------------------------------------- 1 | import { definePlugin, defineType } from 'sanity' 2 | 3 | import NoteInput from './note-input' 4 | 5 | export const noteField = definePlugin(() => { 6 | return { 7 | name: 'sanity-plugin-note-field', 8 | schema: { 9 | types: [ 10 | defineType({ 11 | title: 'Note', 12 | name: 'note', 13 | type: 'string', 14 | components: { 15 | input: NoteInput, 16 | field: (props) => <>{props.children}, 17 | }, 18 | }), 19 | ], 20 | }, 21 | } 22 | }) 23 | -------------------------------------------------------------------------------- /src/note-input.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import startCase from 'lodash/startCase' 3 | import { Card, Flex, Box, Inline, Heading, Text } from '@sanity/ui' 4 | 5 | import type { noteInputProps } from './types' 6 | 7 | const NoteInput = React.forwardRef((args: noteInputProps, ref: any) => { 8 | const { title, description, options } = args.schemaType 9 | 10 | // get last item in args.path array 11 | const pathId = args.path[args.path?.length - 1] as any as string 12 | 13 | const displayTitle = startCase(pathId) === title ? null : title 14 | const icon = options?.icon 15 | const tone = options?.tone ?? 'primary' 16 | 17 | // bail if nothing was set 18 | if (!displayTitle && !description) return null 19 | 20 | const CustomIcon = icon as React.ElementType 21 | 22 | return ( 23 | 24 | {displayTitle && ( 25 | 26 | 27 | {icon && } 28 | {displayTitle} 29 | 30 | 31 | )} 32 | 33 | {description && ( 34 | 35 | 36 | {icon && !displayTitle && } 37 | 38 | 42 | {description} 43 | 44 | 45 | )} 46 | 47 | ) 48 | }) 49 | 50 | NoteInput.displayName = 'NoteInput' 51 | 52 | export default NoteInput 53 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { ObjectSchemaType, ObjectInputProps } from 'sanity' 3 | 4 | export type ThemeColorToneKey = 5 | | 'default' 6 | | 'transparent' 7 | | 'primary' 8 | | 'positive' 9 | | 'caution' 10 | | 'critical' 11 | 12 | export type noteSchemaType = Omit & { 13 | options?: { 14 | icon?: React.ReactNode 15 | headline?: string 16 | message: any 17 | tone?: ThemeColorToneKey 18 | } 19 | } 20 | 21 | export interface noteInputValue { 22 | _type?: 'note' 23 | } 24 | 25 | export type noteInputProps = ObjectInputProps 26 | -------------------------------------------------------------------------------- /src/v2-incompatible.js: -------------------------------------------------------------------------------- 1 | const { showIncompatiblePluginDialog } = require('@sanity/incompatible-plugin') 2 | const { name, version } = require('./package.json') 3 | 4 | export default showIncompatiblePluginDialog({ 5 | name: name, 6 | versions: { 7 | v3: version, 8 | // Optional: If there is not v2 version of your plugin, v2 can be omitted 9 | v2: '^2.1.6', 10 | }, 11 | }) 12 | -------------------------------------------------------------------------------- /tsconfig.dist.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings", 3 | "include": ["./src"], 4 | "exclude": [ 5 | "./src/**/__fixtures__", 6 | "./src/**/__mocks__", 7 | "./src/**/*.test.ts", 8 | "./src/**/*.test.tsx" 9 | ], 10 | "compilerOptions": { 11 | "rootDir": ".", 12 | "outDir": "./dist", 13 | "jsx": "react-jsx", 14 | "emitDeclarationOnly": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings", 3 | "include": ["./src", "./package.config.ts"], 4 | "compilerOptions": { 5 | "rootDir": ".", 6 | "jsx": "react-jsx", 7 | "noEmit": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "target": "esnext", 5 | "module": "esnext", 6 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 7 | "esModuleInterop": true, 8 | "strict": true, 9 | "downlevelIteration": true, 10 | "declaration": true, 11 | "allowSyntheticDefaultImports": true, 12 | "skipLibCheck": true, 13 | "isolatedModules": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /v2-incompatible.js: -------------------------------------------------------------------------------- 1 | const { showIncompatiblePluginDialog } = require('@sanity/incompatible-plugin') 2 | const { name, version, sanityExchangeUrl } = require('./package.json') 3 | 4 | export default showIncompatiblePluginDialog({ 5 | name: name, 6 | versions: { 7 | v3: version, 8 | v2: '^2.1.6', 9 | }, 10 | sanityExchangeUrl, 11 | }) 12 | --------------------------------------------------------------------------------