├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github ├── CODEOWNERS ├── renovate.json └── workflows │ └── main.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .npmignore ├── .npmrc ├── .prettierrc.json ├── .releaserc.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── commitlint.config.cjs ├── lint-staged.config.cjs ├── package-lock.json ├── package.config.ts ├── package.json ├── sanity.json ├── src ├── DocumentList.tsx ├── index.ts ├── plugin.tsx └── sanityConnector.ts ├── tsconfig.json └── v2-incompatible.js /.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 | .eslintrc.js 2 | commitlint.config.js 3 | dist 4 | lint-staged.config.js 5 | package.config.ts 6 | *.js -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": false 5 | }, 6 | "extends": [ 7 | "sanity/react", 8 | "sanity/typescript", 9 | "plugin:prettier/recommended", 10 | "plugin:react-hooks/recommended" 11 | ], 12 | "overrides": [ 13 | { 14 | "files": ["*.{ts,tsx}"] 15 | } 16 | ], 17 | "parser": "@typescript-eslint/parser", 18 | "parserOptions": { 19 | "ecmaFeatures": { 20 | "jsx": true 21 | }, 22 | "project": "./tsconfig.json" 23 | }, 24 | "plugins": ["prettier"], 25 | "rules": { 26 | "@typescript-eslint/explicit-function-return-type": 0, 27 | "@typescript-eslint/no-shadow": "error", 28 | "@typescript-eslint/no-unused-vars": 1, 29 | "no-shadow": "off", 30 | "react/display-name": 0, 31 | "react/jsx-no-bind": 0, 32 | "react/no-unused-prop-types": 0 33 | }, 34 | "settings": { 35 | "import/ignore": ["\\.css$", ".*node_modules.*", ".*:.*"], 36 | "import/resolver": { 37 | "node": { 38 | "paths": ["src"], 39 | "extensions": [".js", ".jsx", ".ts", ".tsx"] 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @sanity-io/ecosystem 2 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "github>sanity-io/renovate-config", 5 | "github>sanity-io/renovate-config:studio-v3" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI & Release 3 | 4 | # Workflow name based on selected inputs. Fallback to default Github naming when expression evaluates to empty string 5 | run-name: >- 6 | ${{ 7 | inputs.release && inputs.test && 'Build ➤ Test ➤ Publish to NPM' || 8 | inputs.release && !inputs.test && 'Build ➤ Skip Tests ➤ Publish to NPM' || 9 | github.event_name == 'workflow_dispatch' && inputs.test && 'Build ➤ Test' || 10 | github.event_name == 'workflow_dispatch' && !inputs.test && 'Build ➤ Skip Tests' || 11 | '' 12 | }} 13 | 14 | on: 15 | # Build on pushes branches that have a PR (including drafts) 16 | pull_request: 17 | # Build on commits pushed to branches without a PR if it's in the allowlist 18 | push: 19 | branches: [main, studio-v2] 20 | # https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow 21 | workflow_dispatch: 22 | inputs: 23 | test: 24 | description: Run tests 25 | required: true 26 | default: true 27 | type: boolean 28 | release: 29 | description: Release new version 30 | required: true 31 | default: false 32 | type: boolean 33 | 34 | concurrency: 35 | # On PRs builds will cancel if new pushes happen before the CI completes, as it defines `github.head_ref` and gives it the name of the branch the PR wants to merge into 36 | # Otherwise `github.run_id` ensures that you can quickly merge a queue of PRs without causing tests to auto cancel on any of the commits pushed to main. 37 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 38 | cancel-in-progress: true 39 | 40 | jobs: 41 | log-the-inputs: 42 | name: Log inputs 43 | runs-on: ubuntu-latest 44 | steps: 45 | - run: | 46 | echo "Inputs: $INPUTS" 47 | env: 48 | INPUTS: ${{ toJSON(inputs) }} 49 | 50 | build: 51 | runs-on: ubuntu-latest 52 | name: Lint & Build 53 | steps: 54 | - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3 55 | - uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # tag=v3 56 | with: 57 | cache: npm 58 | node-version: lts/* 59 | - run: npm ci 60 | # Linting can be skipped 61 | - run: npm run lint --if-present 62 | if: github.event.inputs.test != 'false' 63 | # But not the build script, as semantic-release will crash if this command fails so it makes sense to test it early 64 | - run: npm run prepublishOnly --if-present 65 | 66 | test: 67 | needs: build 68 | # The test matrix can be skipped, in case a new release needs to be fast-tracked and tests are already passing on main 69 | if: github.event.inputs.test != 'false' 70 | runs-on: ${{ matrix.os }} 71 | name: Node.js ${{ matrix.node }} / ${{ matrix.os }} 72 | strategy: 73 | # A test failing on windows doesn't mean it'll fail on macos. It's useful to let all tests run to its completion to get the full picture 74 | fail-fast: false 75 | matrix: 76 | # Run the testing suite on each major OS with the latest LTS release of Node.js 77 | os: [macos-latest, ubuntu-latest, windows-latest] 78 | node: [lts/*] 79 | # It makes sense to also test the oldest, and latest, versions of Node.js, on ubuntu-only since it's the fastest CI runner 80 | include: 81 | - os: ubuntu-latest 82 | # Test the oldest LTS release of Node that's still receiving bugfixes and security patches, versions older than that have reached End-of-Life 83 | node: lts/-2 84 | - os: ubuntu-latest 85 | # Test the actively developed version that will become the latest LTS release next October 86 | node: current 87 | steps: 88 | # It's only necessary to do this for windows, as mac and ubuntu are sane OS's that already use LF 89 | - name: Set git to use LF 90 | if: matrix.os == 'windows-latest' 91 | run: | 92 | git config --global core.autocrlf false 93 | git config --global core.eol lf 94 | - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3 95 | - uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # tag=v3 96 | with: 97 | cache: npm 98 | node-version: ${{ matrix.node }} 99 | - run: npm i 100 | - run: npm test --if-present 101 | 102 | release: 103 | needs: [build, test] 104 | # only run if opt-in during workflow_dispatch 105 | if: always() && github.event.inputs.release == 'true' && needs.build.result != 'failure' && needs.test.result != 'failure' && needs.test.result != 'cancelled' 106 | runs-on: ubuntu-latest 107 | name: Semantic release 108 | steps: 109 | - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3 110 | with: 111 | # Need to fetch entire commit history to 112 | # analyze every commit since last release 113 | fetch-depth: 0 114 | - uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # tag=v3 115 | with: 116 | cache: npm 117 | node-version: lts/* 118 | - run: npm ci 119 | # Branches that will release new versions are defined in .releaserc.json 120 | - run: npx semantic-release 121 | # Don't allow interrupting the release step if the job is cancelled, as it can lead to an inconsistent state 122 | # e.g. git tags were pushed but it exited before `npm publish` 123 | if: always() 124 | env: 125 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 126 | NPM_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} 127 | # Re-run semantic release with rich logs if it failed to publish for easier debugging 128 | - run: npx semantic-release --dry-run --debug 129 | if: failure() 130 | env: 131 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 132 | NPM_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} 133 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # macOS finder cache file 40 | .DS_Store 41 | 42 | # VS Code settings 43 | .vscode 44 | 45 | # IntelliJ 46 | .idea 47 | *.iml 48 | 49 | # Cache 50 | .cache 51 | 52 | # Yalc 53 | .yalc 54 | yalc.lock 55 | 56 | # Compiled plugin 57 | dist 58 | 59 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | npx --no -- commitlint --edit "" 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npx lint-staged 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /test 2 | /coverage 3 | .editorconfig 4 | .eslintrc 5 | .gitignore 6 | .github 7 | .prettierrc 8 | .travis.yml 9 | .nyc_output 10 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "printWidth": 100, 4 | "bracketSpacing": false, 5 | "singleQuote": true, 6 | "useTabs": false, 7 | "endOfLine": "lf" 8 | } 9 | -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sanity/semantic-release-preset", 3 | "branches": ["main", {"name": "studio-v2", "channel": "studio-v2", "range": "0.x.x"}] 4 | } 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 📓 Changelog 4 | 5 | All notable changes to this project will be documented in this file. See 6 | [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 7 | 8 | ## [2.1.0](https://github.com/sanity-io/dashboard-widget-document-list/compare/v2.0.0...v2.1.0) (2025-03-07) 9 | 10 | ### Features 11 | 12 | - add react 19 to peer deps ([7a2a8d2](https://github.com/sanity-io/dashboard-widget-document-list/commit/7a2a8d23efeb3b2de51b6c0b50f5d34ac77fbe26)) 13 | 14 | ## [2.0.0](https://github.com/sanity-io/dashboard-widget-document-list/compare/v1.0.1...v2.0.0) (2024-08-01) 15 | 16 | ### ⚠ BREAKING CHANGES 17 | 18 | - upgrade dependencies (#24) 19 | 20 | ### Bug Fixes 21 | 22 | - upgrade dependencies ([#24](https://github.com/sanity-io/dashboard-widget-document-list/issues/24)) ([2d1563f](https://github.com/sanity-io/dashboard-widget-document-list/commit/2d1563f8f6f319c55e1ba58dfe6534c3da03828e)) 23 | 24 | ## [1.0.1](https://github.com/sanity-io/dashboard-widget-document-list/compare/v1.0.0...v1.0.1) (2022-11-25) 25 | 26 | ### Bug Fixes 27 | 28 | - **deps:** sanity 3.0.0-rc.3 ([9f54f74](https://github.com/sanity-io/dashboard-widget-document-list/commit/9f54f74d4888a2e1f3b95f71f8cd70353b96713a)) 29 | 30 | ## [1.0.0](https://github.com/sanity-io/dashboard-widget-document-list/compare/v0.0.13...v1.0.0) (2022-11-17) 31 | 32 | ### ⚠ BREAKING CHANGES 33 | 34 | - this version does not work in Sanity Studio v2 35 | - this version no longer works in Studio V2 36 | 37 | ### Features 38 | 39 | - **deps:** @sanity/dashboard ([5b80892](https://github.com/sanity-io/dashboard-widget-document-list/commit/5b808927e18323eea17f3f5f0f8dcd8a14c961fd)) 40 | - **deps:** sanity 3.0.0-dev-preview.17 and ui 0.38 ([aefbd25](https://github.com/sanity-io/dashboard-widget-document-list/commit/aefbd250b024a1ffc9d339f48c332bbb2332e9b4)) 41 | - initial Sanity Studio v3 ([bf8683a](https://github.com/sanity-io/dashboard-widget-document-list/commit/bf8683aa4c84d7c6415101766b7466b99a8fd5b1)) 42 | - initial studio v3 version ([6ae5a46](https://github.com/sanity-io/dashboard-widget-document-list/commit/6ae5a4644ec8444ec2738bdad07cc350e0363a82)) 43 | 44 | ### Bug Fixes 45 | 46 | - added workflow so it can be triggered from v3 ([1fcacd7](https://github.com/sanity-io/dashboard-widget-document-list/commit/1fcacd76267754c7a8c8480ceb04d10c3fda981b)) 47 | - compiled for sanity 3.0.0-rc.0 ([52525df](https://github.com/sanity-io/dashboard-widget-document-list/commit/52525dfc2cc946e321a7e521dbb25a5e5b3a267a)) 48 | - **deps:** dev-preview.21 ([c62accc](https://github.com/sanity-io/dashboard-widget-document-list/commit/c62accca57576d879d06d9def967e7b3d6d6a0f3)) 49 | - **deps:** dev-preview.22 ([77386ce](https://github.com/sanity-io/dashboard-widget-document-list/commit/77386ce9ec3d9b9de0f52bd3fc30e695e51c329f)) 50 | - **deps:** pkg-utils & @sanity/plugin-kit ([67c68e1](https://github.com/sanity-io/dashboard-widget-document-list/commit/67c68e1d9f15a34d3f63ffc5647da0515a6a4cac)) 51 | - **deps:** update deps and added semver workflow ([4e5a57b](https://github.com/sanity-io/dashboard-widget-document-list/commit/4e5a57b0559be121261194e89f69862134e6bf49)) 52 | - typo ([3929617](https://github.com/sanity-io/dashboard-widget-document-list/commit/39296177e02b56c4748eff1fcaabcd275e746c06)) 53 | 54 | ## [3.0.0-v3-studio.7](https://github.com/sanity-io/dashboard-widget-document-list/compare/v3.0.0-v3-studio.6...v3.0.0-v3-studio.7) (2022-11-04) 55 | 56 | ### Bug Fixes 57 | 58 | - **deps:** pkg-utils & @sanity/plugin-kit ([67c68e1](https://github.com/sanity-io/dashboard-widget-document-list/commit/67c68e1d9f15a34d3f63ffc5647da0515a6a4cac)) 59 | 60 | ## [3.0.0-v3-studio.6](https://github.com/sanity-io/dashboard-widget-document-list/compare/v3.0.0-v3-studio.5...v3.0.0-v3-studio.6) (2022-11-02) 61 | 62 | ### Bug Fixes 63 | 64 | - compiled for sanity 3.0.0-rc.0 ([52525df](https://github.com/sanity-io/dashboard-widget-document-list/commit/52525dfc2cc946e321a7e521dbb25a5e5b3a267a)) 65 | 66 | ## [3.0.0-v3-studio.5](https://github.com/sanity-io/dashboard-widget-document-list/compare/v3.0.0-v3-studio.4...v3.0.0-v3-studio.5) (2022-10-27) 67 | 68 | ### Bug Fixes 69 | 70 | - **deps:** dev-preview.22 ([77386ce](https://github.com/sanity-io/dashboard-widget-document-list/commit/77386ce9ec3d9b9de0f52bd3fc30e695e51c329f)) 71 | 72 | ## [3.0.0-v3-studio.4](https://github.com/sanity-io/dashboard-widget-document-list/compare/v3.0.0-v3-studio.3...v3.0.0-v3-studio.4) (2022-10-07) 73 | 74 | ### Bug Fixes 75 | 76 | - **deps:** dev-preview.21 ([c62accc](https://github.com/sanity-io/dashboard-widget-document-list/commit/c62accca57576d879d06d9def967e7b3d6d6a0f3)) 77 | 78 | ## [3.0.0-v3-studio.3](https://github.com/sanity-io/dashboard-widget-document-list/compare/v3.0.0-v3-studio.2...v3.0.0-v3-studio.3) (2022-09-15) 79 | 80 | ### Features 81 | 82 | - **deps:** @sanity/dashboard ([5b80892](https://github.com/sanity-io/dashboard-widget-document-list/commit/5b808927e18323eea17f3f5f0f8dcd8a14c961fd)) 83 | - **deps:** sanity 3.0.0-dev-preview.17 and ui 0.38 ([aefbd25](https://github.com/sanity-io/dashboard-widget-document-list/commit/aefbd250b024a1ffc9d339f48c332bbb2332e9b4)) 84 | 85 | ## [3.0.0-v3-studio.2](https://github.com/sanity-io/dashboard-widget-document-list/compare/v3.0.0-v3-studio.1...v3.0.0-v3-studio.2) (2022-09-14) 86 | 87 | ### Bug Fixes 88 | 89 | - **deps:** update deps and added semver workflow ([4e5a57b](https://github.com/sanity-io/dashboard-widget-document-list/commit/4e5a57b0559be121261194e89f69862134e6bf49)) 90 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Sanity.io 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 | # sanity-plugin-dashboard-widget-document-list 2 | 3 | > This is a **Sanity Studio v3** plugin. 4 | > For the v2 version, please refer to the [v2-branch](https://github.com/sanity-io/dashboard-widget-document-list/tree/studio-v2). 5 | 6 | ## What is it? 7 | 8 | Dashboard widget for the Sanity Content Studio which displays a list of documents. 9 | 10 | ## Install 11 | 12 | ``` 13 | npm install --save sanity-plugin-dashboard-widget-document-list 14 | ``` 15 | 16 | or 17 | 18 | ``` 19 | yarn add sanity-plugin-dashboard-widget-document-list 20 | ``` 21 | 22 | Ensure that you have followed install and usage instructions for @sanity/dashboard. 23 | 24 | ## Usage 25 | 26 | Add dashboard-widget-document-list as a widget to @sanity/dashboard plugin in sanity.config.ts (or .js): 27 | 28 | ```js 29 | import {dashboardTool} from '@sanity/dashboard' 30 | import {documentListWidget} from 'sanity-plugin-dashboard-widget-document-list' 31 | 32 | export default defineConfig({ 33 | // ... 34 | plugins: [ 35 | dashboardTool({ 36 | widgets: [documentListWidget()], 37 | }), 38 | ], 39 | }) 40 | ``` 41 | 42 | _Note_: If a document in the result (as returned by the backend) has a draft, that draft is rendered instead of the published document. 43 | 44 | ## Options 45 | 46 | There are some options available, as specified by [DocumentListConfig](src/DocumentList.tsx): 47 | 48 | ### `title` (string) 49 | 50 | Widget title 51 | 52 | ```js 53 | documentListWidget({ 54 | title: 'Some documents', 55 | }) 56 | ``` 57 | 58 | ### `order` (string) 59 | 60 | Field and direction to order by when docs are rendered 61 | 62 | ```js 63 | documentListWidget({ 64 | title: 'Last edited', 65 | order: '_updatedAt desc', 66 | }) 67 | ``` 68 | 69 | ### `limit` (number) 70 | 71 | Number of docs rendered 72 | 73 | ```js 74 | documentListWidget({ 75 | title: 'Last edited', 76 | order: '_updatedAt desc', 77 | limit: 3, 78 | }) 79 | ``` 80 | 81 | ### `types` (array) 82 | 83 | Array of strings signifying which document (schema) types are fetched 84 | 85 | ```js 86 | documentListWidget({ 87 | title: 'Last edited', 88 | order: '_updatedAt desc', 89 | types: ['book', 'author'], 90 | }) 91 | ``` 92 | 93 | ### `query` (string) and `params` (object) 94 | 95 | Customized GROQ query with params for maximum control. If you use the query option, the `types`, `order`, and `limit` options will cease to function. You're on your own. 96 | 97 | ```js 98 | documentListWidget({ 99 | title: 'Published books by title', 100 | query: '*[_type == "book" && published == true] | order(title asc) [0...10]', 101 | }) 102 | ``` 103 | 104 | ```js 105 | documentListWidget({ 106 | title: 'My favorite documents', 107 | query: '*[_id in $ids]', 108 | params: { 109 | ids: ['ab2', 'c5z', '654'], 110 | }, 111 | }) 112 | ``` 113 | 114 | ### `createButtonText` (string) 115 | 116 | You can override the button default button text (`Create new ${types[0]}`) by setting `createButtonText` to a string of your choice. This doesn't support dynamic variables. 117 | 118 | ```js 119 | documentListWidget({ 120 | title: 'Blog posts', 121 | query: '*[_type == "post"]', 122 | createButtonText: 'Create new blog post', 123 | }) 124 | ``` 125 | 126 | ### `showCreateButton` (boolean) 127 | 128 | You can disable the create button altogether by passing a `showCreateButton` boolean: 129 | 130 | ```js 131 | documentListWidget({ 132 | showCreateButton: false, 133 | }) 134 | ``` 135 | 136 | ### Widget size 137 | 138 | You can change the width of the plugin using `layout.width`: 139 | 140 | ```js 141 | documentListWidget({ 142 | layout: {width: 'small'}, 143 | }) 144 | ``` 145 | 146 | ## License 147 | 148 | MIT-licensed. See LICENSE. 149 | 150 | ## Develop & test 151 | 152 | This plugin uses [@sanity/plugin-kit](https://github.com/sanity-io/plugin-kit) 153 | with default configuration for build & watch scripts. 154 | 155 | See [Testing a plugin in Sanity Studio](https://github.com/sanity-io/plugin-kit#testing-a-plugin-in-sanity-studio) 156 | on how to run this plugin with hotreload in the studio. 157 | 158 | ### Release new version 159 | 160 | Run ["CI & Release" workflow](https://github.com/sanity-io/dashboard-widget-document-list/actions/workflows/main.yml). 161 | Make sure to select the main branch and check "Release new version". 162 | 163 | Semantic release will only release on configured branches, so it is safe to run release on any branch. 164 | -------------------------------------------------------------------------------- /commitlint.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | } 4 | -------------------------------------------------------------------------------- /lint-staged.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '**/*.{js,jsx}': ['eslint'], 3 | '**/*.{ts,tsx}': ['eslint', () => 'tsc --noEmit'], 4 | } 5 | -------------------------------------------------------------------------------- /package.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from '@sanity/pkg-utils' 2 | 3 | export default defineConfig({ 4 | minify: true, 5 | // Remove this block to enable strict export validation 6 | extract: { 7 | rules: { 8 | 'ae-forgotten-export': 'off', 9 | 'ae-incompatible-release-tags': 'off', 10 | 'ae-internal-missing-underscore': 'off', 11 | 'ae-missing-release-tag': 'off', 12 | }, 13 | }, 14 | }) 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sanity-plugin-dashboard-widget-document-list", 3 | "version": "2.1.0", 4 | "description": "> **NOTE**", 5 | "keywords": [ 6 | "sanity", 7 | "plugin", 8 | "part", 9 | "dashboard", 10 | "widget", 11 | "documents" 12 | ], 13 | "homepage": "https://github.com/sanity-io/dashboard-widget-document-list#readme", 14 | "bugs": { 15 | "url": "https://github.com/sanity-io/dashboard-widget-document-list/issues" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git@github.com:sanity-io/dashboard-widget-document-list.git" 20 | }, 21 | "license": "MIT", 22 | "author": "Sanity.io ", 23 | "exports": { 24 | ".": { 25 | "source": "./src/index.ts", 26 | "import": "./dist/index.js", 27 | "require": "./dist/index.cjs", 28 | "default": "./dist/index.js" 29 | }, 30 | "./package.json": "./package.json" 31 | }, 32 | "main": "./dist/index.cjs", 33 | "module": "./dist/index.js", 34 | "types": "./dist/index.d.ts", 35 | "files": [ 36 | "src", 37 | "dist", 38 | "v2-incompatible.js", 39 | "sanity.json" 40 | ], 41 | "scripts": { 42 | "build": "plugin-kit verify-package --silent && pkg-utils build --strict --check --clean", 43 | "compile": "tsc --noEmit", 44 | "link-watch": "plugin-kit link-watch", 45 | "lint": "eslint .", 46 | "prepare": "husky", 47 | "prepublishOnly": "npm run build", 48 | "watch": "pkg-utils watch --strict" 49 | }, 50 | "dependencies": { 51 | "@sanity/incompatible-plugin": "^1.0.5", 52 | "@sanity/ui": "^2.15.2", 53 | "lodash": "^4.17.21", 54 | "rxjs": "^7.8.1" 55 | }, 56 | "devDependencies": { 57 | "@commitlint/cli": "^19.3.0", 58 | "@commitlint/config-conventional": "^19.2.2", 59 | "@sanity/dashboard": "^4.0.0", 60 | "@sanity/pkg-utils": "^6.10.7", 61 | "@sanity/plugin-kit": "^4.0.17", 62 | "@sanity/semantic-release-preset": "^5.0.0", 63 | "@types/react": "^18", 64 | "@typescript-eslint/eslint-plugin": "^7.17.0", 65 | "@typescript-eslint/parser": "^7.17.0", 66 | "eslint": "^8.57.0", 67 | "eslint-config-prettier": "^9.1.0", 68 | "eslint-config-sanity": "^7.1.2", 69 | "eslint-plugin-prettier": "^5.2.1", 70 | "eslint-plugin-react": "^7.35.0", 71 | "eslint-plugin-react-hooks": "^4.6.2", 72 | "husky": "^9.1.3", 73 | "lint-staged": "^15.2.7", 74 | "prettier": "^3.3.3", 75 | "prettier-plugin-packagejson": "^2.5.1", 76 | "react": "^18", 77 | "sanity": "^3.52.2", 78 | "semantic-release": "^24.0.0", 79 | "typescript": "5.5.4" 80 | }, 81 | "peerDependencies": { 82 | "@sanity/dashboard": "^4", 83 | "react": "^18.3 || ^19", 84 | "sanity": "^3" 85 | }, 86 | "engines": { 87 | "node": ">=18" 88 | }, 89 | "public": true, 90 | "sanityExchangeUrl": "https://www.sanity.io/plugins/sanity-plugin-dashboard-widget-document-list", 91 | "browserslist": "extends @sanity/browserslist-config", 92 | "sideEffects": false, 93 | "type": "module", 94 | "overrides": { 95 | "conventional-changelog-conventionalcommits": ">= 8.0.0" 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /sanity.json: -------------------------------------------------------------------------------- 1 | { 2 | "parts": [ 3 | { 4 | "implements": "part:@sanity/base/sanity-root", 5 | "path": "./v2-incompatible.js" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /src/DocumentList.tsx: -------------------------------------------------------------------------------- 1 | import {DashboardWidgetContainer} from '@sanity/dashboard' 2 | import {Card, Flex, Spinner, Stack} from '@sanity/ui' 3 | import {intersection} from 'lodash' 4 | import {type ReactNode, useEffect, useMemo, useState} from 'react' 5 | import { 6 | getPublishedId, 7 | IntentButton, 8 | Preview, 9 | type SanityDocument, 10 | useClient, 11 | useSchema, 12 | } from 'sanity' 13 | 14 | import {getSubscription} from './sanityConnector' 15 | 16 | export interface DocumentListConfig { 17 | title?: string 18 | types?: string[] 19 | query?: string 20 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 21 | queryParams?: Record 22 | order?: string 23 | limit?: number 24 | showCreateButton?: boolean 25 | createButtonText?: string 26 | apiVersion?: string 27 | } 28 | 29 | const defaultProps = { 30 | title: 'Last created', 31 | order: '_createdAt desc', 32 | limit: 10, 33 | queryParams: {}, 34 | showCreateButton: true, 35 | apiVersion: 'v1', 36 | } 37 | 38 | export function DocumentList(props: DocumentListConfig): ReactNode { 39 | const { 40 | query, 41 | limit, 42 | apiVersion, 43 | queryParams, 44 | types, 45 | order, 46 | title, 47 | showCreateButton, 48 | createButtonText, 49 | } = { 50 | ...defaultProps, 51 | ...props, 52 | } 53 | 54 | const [documents, setDocuments] = useState() 55 | const [loading, setLoading] = useState(true) 56 | const [error, setError] = useState() 57 | 58 | const versionedClient = useClient({apiVersion}) 59 | const schema = useSchema() 60 | 61 | const {assembledQuery, params} = useMemo(() => { 62 | if (query) { 63 | return {assembledQuery: query, params: queryParams} 64 | } 65 | 66 | const documentTypes = schema.getTypeNames().filter((typeName) => { 67 | const schemaType = schema.get(typeName) 68 | return schemaType?.type?.name === 'document' 69 | }) 70 | 71 | return { 72 | assembledQuery: `*[_type in $types] | order(${order}) [0...${limit * 2}]`, 73 | params: {types: types ? intersection(types, documentTypes) : documentTypes}, 74 | } 75 | }, [schema, query, queryParams, order, limit, types]) 76 | 77 | useEffect(() => { 78 | if (!assembledQuery) { 79 | return 80 | } 81 | 82 | const subscription = getSubscription(assembledQuery, params, versionedClient).subscribe({ 83 | next: (d) => { 84 | setDocuments(d.slice(0, limit)) 85 | setLoading(false) 86 | }, 87 | error: (e) => { 88 | setError(e) 89 | setLoading(false) 90 | }, 91 | }) 92 | // eslint-disable-next-line consistent-return 93 | return () => { 94 | subscription.unsubscribe() 95 | } 96 | }, [limit, versionedClient, assembledQuery, params]) 97 | 98 | return ( 99 | 116 | ) 117 | } 118 | > 119 | 120 | {error &&
{error.message}
} 121 | {!error && loading && ( 122 | 123 | 124 | 125 | 126 | 127 | )} 128 | {!error && !documents && !loading &&
Could not locate any documents :/
} 129 | 130 | {documents && documents.map((doc) => )} 131 | 132 |
133 |
134 | ) 135 | } 136 | 137 | function MenuEntry({doc}: {doc: SanityDocument}) { 138 | const schema = useSchema() 139 | const type = schema.get(doc._type) 140 | return ( 141 | 142 | 154 | {type ? ( 155 | 156 | ) : ( 157 | 'Schema-type missing' 158 | )} 159 | 160 | 161 | ) 162 | } 163 | 164 | export default DocumentList 165 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export {documentListWidget, type DocumentListWidgetConfig} from './plugin' 2 | -------------------------------------------------------------------------------- /src/plugin.tsx: -------------------------------------------------------------------------------- 1 | import type {DashboardWidget, LayoutConfig} from '@sanity/dashboard' 2 | 3 | import DocumentList, {type DocumentListConfig} from './DocumentList' 4 | 5 | export interface DocumentListWidgetConfig extends DocumentListConfig { 6 | layout?: LayoutConfig 7 | } 8 | 9 | export function documentListWidget(config: DocumentListWidgetConfig): DashboardWidget { 10 | return { 11 | name: 'document-list-widget', 12 | component: function component() { 13 | return 14 | }, 15 | layout: config.layout, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/sanityConnector.ts: -------------------------------------------------------------------------------- 1 | import type {SanityClient} from '@sanity/client' 2 | import uniqBy from 'lodash/uniqBy' 3 | import {type Observable, of as observableOf} from 'rxjs' 4 | import {delay, mergeMap, switchMap, tap} from 'rxjs/operators' 5 | import type {SanityDocument} from 'sanity' 6 | 7 | const draftId = (nonDraftDoc: SanityDocument) => `drafts.${nonDraftDoc._id}` 8 | 9 | function prepareDocumentList( 10 | incoming: SanityDocument | SanityDocument[], 11 | client: SanityClient, 12 | ): Promise { 13 | if (!incoming) { 14 | return Promise.resolve([]) 15 | } 16 | const documents = Array.isArray(incoming) ? incoming : [incoming] 17 | 18 | const ids = documents.filter((doc) => !doc._id.startsWith('drafts.')).map(draftId) 19 | 20 | return client 21 | .fetch('*[_id in $ids]', {ids}) 22 | .then((drafts) => { 23 | const outgoing = documents.map((doc) => { 24 | const foundDraft = drafts.find((draft) => draft._id === draftId(doc)) 25 | return foundDraft || doc 26 | }) 27 | return uniqBy(outgoing, '_id') 28 | }) 29 | .catch((error) => { 30 | throw new Error(`Problems fetching docs ${ids}. Error: ${error.message}`) 31 | }) 32 | } 33 | 34 | export function getSubscription( 35 | query: string, 36 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 37 | params: Record, 38 | client: SanityClient, 39 | ): Observable { 40 | return client 41 | .listen(query, params, { 42 | events: ['welcome', 'mutation'], 43 | includeResult: false, 44 | visibility: 'query', 45 | }) 46 | .pipe( 47 | switchMap((event) => { 48 | return observableOf(1).pipe( 49 | event.type === 'welcome' ? tap() : delay(1000), 50 | mergeMap(() => 51 | client 52 | .fetch(query, params) 53 | .then((incoming) => { 54 | return prepareDocumentList(incoming, client) 55 | }) 56 | .catch((error) => { 57 | if (error.message.startsWith('Problems fetching docs')) { 58 | throw error 59 | } 60 | throw new Error( 61 | `Query failed ${query} and ${JSON.stringify(params)}. Error: ${error.message}`, 62 | ) 63 | }), 64 | ), 65 | ) 66 | }), 67 | ) 68 | } 69 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "preserve", 4 | "moduleResolution": "node", 5 | "target": "esnext", 6 | "module": "preserve", 7 | "esModuleInterop": true, 8 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 9 | "strict": true, 10 | "sourceMap": false, 11 | "inlineSourceMap": false, 12 | "downlevelIteration": true, 13 | "declaration": true, 14 | "allowSyntheticDefaultImports": true, 15 | "experimentalDecorators": true, 16 | "emitDecoratorMetadata": true, 17 | "outDir": "dist", 18 | "skipLibCheck": true, 19 | "isolatedModules": true, 20 | "checkJs": false, 21 | "rootDir": ".", 22 | "noEmit": true 23 | }, 24 | "include": ["src/**/*", ".eslintrc.cjs"] 25 | } 26 | -------------------------------------------------------------------------------- /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: '^0.0.13', 9 | }, 10 | sanityExchangeUrl, 11 | }) 12 | --------------------------------------------------------------------------------