├── .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 | 
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 |
--------------------------------------------------------------------------------