├── .gitignore ├── .husky └── .gitignore ├── .vscode └── settings.json ├── package-lock.json ├── package.json ├── readme.md ├── studio ├── .gitignore ├── README.md ├── config │ ├── .checksums │ └── @sanity │ │ ├── data-aspects.json │ │ ├── default-layout.json │ │ ├── default-login.json │ │ └── form-builder.json ├── package-lock.json ├── package.json ├── plugins │ └── .gitkeep ├── sanity.json ├── schemas │ ├── documents │ │ ├── artist.js │ │ └── artwork.js │ └── schema.js ├── static │ ├── .gitkeep │ └── favicon.ico ├── tsconfig.json └── yarn.lock └── table ├── .env.template ├── .gitignore ├── index.html ├── lib └── client.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── src ├── App.css ├── App.jsx ├── components │ ├── Query.jsx │ ├── Table.jsx │ ├── buttons │ │ ├── AddImages.jsx │ │ ├── AddNew.jsx │ │ ├── AddTitles.jsx │ │ ├── Delete.jsx │ │ ├── Publish.jsx │ │ └── Undo.jsx │ └── cells │ │ ├── DeleteId.jsx │ │ ├── Price.jsx │ │ ├── SanityImage.jsx │ │ ├── Select.jsx │ │ ├── Text.jsx │ │ └── Toggle.jsx ├── favicon.svg ├── helpers │ └── index.js ├── hooks │ └── useStore.js └── main.jsx ├── tailwind.config.js └── vite.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Env 2 | .env* 3 | !.env.template 4 | 5 | # Key 6 | *.pem 7 | 8 | # Logs 9 | logs 10 | *.log 11 | npm-debug.log* 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | 24 | # nyc test coverage 25 | .nyc_output 26 | 27 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # Compiled binary addons (http://nodejs.org/api/addons.html) 34 | build/Release 35 | 36 | # Dependency directories 37 | node_modules 38 | jspm_packages 39 | 40 | # Optional npm cache directory 41 | .npm 42 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoreLimitWarning": true 3 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "powertynan", 3 | "version": "1.0.0", 4 | "description": "Sanity/Next.js Website", 5 | "author": "Simeon Griggs", 6 | "license": "UNLICENSED", 7 | "scripts": { 8 | "lint": "eslint .", 9 | "lint:fix": "eslint . --fix", 10 | "postinstall": "husky install" 11 | }, 12 | "husky": { 13 | "hooks": { 14 | "pre-commit": "npm run lint:fix" 15 | } 16 | }, 17 | "dependencies": { 18 | "husky": "^5.0.9" 19 | }, 20 | "devDependencies": { 21 | "autoprefixer": "^10.2.4", 22 | "babel-eslint": "^10.1.0", 23 | "eslint": "^7.17.0", 24 | "eslint-config-airbnb": "^18.2.0", 25 | "eslint-config-prettier": "^6.15.0", 26 | "eslint-config-wesbos": "^1.0.1", 27 | "eslint-plugin-html": "^6.1.1", 28 | "eslint-plugin-import": "^2.22.1", 29 | "eslint-plugin-jsx-a11y": "^6.4.1", 30 | "eslint-plugin-prettier": "^3.3.1", 31 | "eslint-plugin-react": "^7.22.0", 32 | "eslint-plugin-react-hooks": "^4.2.0" 33 | }, 34 | "eslintConfig": { 35 | "root": true, 36 | "extends": [ 37 | "wesbos" 38 | ], 39 | "rules": { 40 | "no-console": 2, 41 | "react/jsx-props-no-spreading": 0, 42 | "prettier/prettier": [ 43 | "error", 44 | { 45 | "trailingComma": "es5", 46 | "singleQuote": true, 47 | "printWidth": 80, 48 | "semi": false 49 | } 50 | ] 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Sanity × React Table Demo 2 | 3 | The bulk content editing story in Sanity is almost non-existent. From a user interface, anyway. [React Table](http://react-table.tanstack.com) gives you most of what you'd want from a table view out of the box. All this project aims to do is to wire that up to a Sanity dataset. 4 | 5 | Currently this is a rapidly-produced proof-of-concept. The `./table` directory is a Vite JS project which combines [Sanity Client](https://www.npmjs.com/package/@sanity/client) with [React Query](https://react-query.tanstack.com) to fetch data and update the UI. 6 | 7 | Finally, [Zustand](https://github.com/pmndrs/zustand) tracks the client state's staged mutations in preparation to post back to Sanity. 8 | 9 | 📼 [Walkthrough Video II](https://www.loom.com/share/09d40289961f4d07939993c931e5877b) 10 | 11 | This demo now uses a custom schema. 12 | 13 | ## Roadmap of this project if I actually took the time to do it properly 14 | 15 | - Build the whole thing inside a new [Sanity Plugin](https://www.sanity.io/plugins/sanipack) so it actually lives in the dashboard (though I might move it when closer to final because Vite's DX is just too good.) 16 | - Leverage [Sanity UI](https://www.sanity.io/ui) throughout. One of my big gripes from WordPress is that every plugin has to inject its own **brand**. Ugh. It should look as close to native as possible. (It's currently using Tailwind and Headless UI). 17 | - Add a bunch more React Table features like searching, filtering and pagination 18 | - **Done!** Optimistic UI. Currently if you toggle multiple rows, the other toggles don't update until the patch request completes and the query is invalidated and refetched. It might look nicer if they all changed immediately, and then changed back if there was an error. That said... 19 | - **Done!** Move to a 'Commit' based editing with confirmation, instead of real-time. Presently it just publishes edits as soon as you make them. Which, if taken to its logical conclusion, could be the world's biggest footgun. You could annihilate so mmany rows of data so easily! Instead I think the Table should track all the cells of each row that have changed (perhaps highlighting the modified cells) and popup a button at the bottom to say _"Publish X Changes to Y Documents"_. 20 | - THEN I'd also like to implement a history panel. So you could `cmd+z` your way back through bulk edits. Again, safety measures for the footgun-ish nature of this project. Also because I tried implementing a History in an app once and it seemed like a really neat programming challenge... 21 | 22 | ## Original Video 23 | 24 | Super basic version [Walkthrough Video](https://www.loom.com/share/d058ef2851a245098078cc883115b5ec) 25 | -------------------------------------------------------------------------------- /studio/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | 4 | # studio 5 | dist 6 | 7 | # misc 8 | .DS_Store 9 | *.pem 10 | 11 | # debug 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | 16 | # local env files 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | # vercel 23 | .vercel 24 | -------------------------------------------------------------------------------- /studio/README.md: -------------------------------------------------------------------------------- 1 | # Sanity Clean Content Studio 2 | 3 | Congratulations, you have now installed the Sanity Content Studio, an open source real-time content editing environment connected to the Sanity backend. 4 | 5 | Now you can do the following things: 6 | 7 | - [Read “getting started” in the docs](https://www.sanity.io/docs/introduction/getting-started?utm_source=readme) 8 | - [Join the community Slack](https://slack.sanity.io/?utm_source=readme) 9 | - [Extend and build plugins](https://www.sanity.io/docs/content-studio/extending?utm_source=readme) 10 | -------------------------------------------------------------------------------- /studio/config/.checksums: -------------------------------------------------------------------------------- 1 | { 2 | "#": "Used by Sanity to keep track of configuration file checksums, do not delete or modify!", 3 | "@sanity/default-layout": "bb034f391ba508a6ca8cd971967cbedeb131c4d19b17b28a0895f32db5d568ea", 4 | "@sanity/default-login": "6fb6d3800aa71346e1b84d95bbcaa287879456f2922372bb0294e30b968cd37f", 5 | "@sanity/form-builder": "b38478227ba5e22c91981da4b53436df22e48ff25238a55a973ed620be5068aa", 6 | "@sanity/data-aspects": "d199e2c199b3e26cd28b68dc84d7fc01c9186bf5089580f2e2446994d36b3cb6" 7 | } 8 | -------------------------------------------------------------------------------- /studio/config/@sanity/data-aspects.json: -------------------------------------------------------------------------------- 1 | { 2 | "listOptions": {} 3 | } 4 | -------------------------------------------------------------------------------- /studio/config/@sanity/default-layout.json: -------------------------------------------------------------------------------- 1 | { 2 | "toolSwitcher": { 3 | "order": [], 4 | "hidden": [] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /studio/config/@sanity/default-login.json: -------------------------------------------------------------------------------- 1 | { 2 | "providers": { 3 | "mode": "append", 4 | "redirectOnSingle": false, 5 | "entries": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /studio/config/@sanity/form-builder.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": { 3 | "directUploads": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /studio/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "galleryeditions", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "", 6 | "main": "package.json", 7 | "author": "Simeon Griggs ", 8 | "license": "UNLICENSED", 9 | "scripts": { 10 | "start": "sanity start", 11 | "build": "sanity build" 12 | }, 13 | "keywords": [ 14 | "sanity" 15 | ], 16 | "dependencies": { 17 | "@sanity/base": "^2.6.2", 18 | "@sanity/components": "^2.2.6", 19 | "@sanity/core": "^2.6.2", 20 | "@sanity/default-layout": "^2.6.2", 21 | "@sanity/default-login": "^2.2.6", 22 | "@sanity/desk-tool": "^2.6.3", 23 | "@sanity/vision": "^2.2.6", 24 | "prop-types": "^15.7", 25 | "react": "^17.0", 26 | "react-dom": "^17.0" 27 | }, 28 | "devDependencies": {} 29 | } 30 | -------------------------------------------------------------------------------- /studio/plugins/.gitkeep: -------------------------------------------------------------------------------- 1 | User-specific packages can be placed here 2 | -------------------------------------------------------------------------------- /studio/sanity.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "project": { 4 | "name": "Gallery" 5 | }, 6 | "api": { 7 | "projectId": "0p3ccz99", 8 | "dataset": "production" 9 | }, 10 | "plugins": [ 11 | "@sanity/base", 12 | "@sanity/components", 13 | "@sanity/default-layout", 14 | "@sanity/default-login", 15 | "@sanity/desk-tool" 16 | ], 17 | "env": { 18 | "development": { 19 | "plugins": ["@sanity/vision"] 20 | } 21 | }, 22 | "parts": [ 23 | { 24 | "name": "part:@sanity/base/schema", 25 | "path": "./schemas/schema" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /studio/schemas/documents/artist.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'artist', 3 | type: 'document', 4 | fields: [ 5 | { 6 | name: 'name', 7 | type: 'string', 8 | }, 9 | ], 10 | preview: { 11 | select: { 12 | title: 'name', 13 | }, 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /studio/schemas/documents/artwork.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'artwork', 3 | type: 'document', 4 | fields: [ 5 | { 6 | name: 'visible', 7 | type: 'boolean', 8 | }, 9 | { 10 | name: 'title', 11 | type: 'string', 12 | }, 13 | { 14 | name: 'price', 15 | type: 'number', 16 | }, 17 | { 18 | name: 'artist', 19 | type: 'reference', 20 | to: [{ type: 'artist' }], 21 | }, 22 | { 23 | name: 'image', 24 | type: 'image', 25 | }, 26 | ], 27 | preview: { 28 | select: { 29 | title: 'title', 30 | visible: 'visible', 31 | price: 'price', 32 | artist: 'artist.name', 33 | media: 'image', 34 | }, 35 | prepare: (selection) => { 36 | const { title, visible, price, artist, media } = selection 37 | const priceFormatted = price 38 | ? Intl.NumberFormat('en-gb', { 39 | style: 'currency', 40 | currency: 'GBP', 41 | }).format(price) 42 | : '' 43 | const subtitle = [priceFormatted, artist || ''] 44 | .filter((part) => part) 45 | .join(' | ') 46 | 47 | return { 48 | title: visible ? `${title} 🟢` : `${title} ❌`, 49 | subtitle, 50 | media, 51 | } 52 | }, 53 | }, 54 | } 55 | -------------------------------------------------------------------------------- /studio/schemas/schema.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import createSchema from 'part:@sanity/base/schema-creator' 3 | import schemaTypes from 'all:part:@sanity/base/schema-type' 4 | /* eslint-enable */ 5 | 6 | import artwork from './documents/artwork' 7 | import artist from './documents/artist' 8 | 9 | export default createSchema({ 10 | name: 'default', 11 | types: schemaTypes.concat([artwork, artist]), 12 | }) 13 | -------------------------------------------------------------------------------- /studio/static/.gitkeep: -------------------------------------------------------------------------------- 1 | Files placed here will be served by the Sanity server under the `/static`-prefix 2 | -------------------------------------------------------------------------------- /studio/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimeonGriggs/sanity-react-table/d0cb99878250c5c31a09e675e1b38475cdb5915b/studio/static/favicon.ico -------------------------------------------------------------------------------- /studio/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // Note: This config is only used to help editors like VS Code understand/resolve 3 | // parts, the actual transpilation is done by babel. Any compiler configuration in 4 | // here will be ignored. 5 | "include": ["./node_modules/@sanity/base/types/**/*.ts", "./**/*.ts", "./**/*.tsx"] 6 | } 7 | -------------------------------------------------------------------------------- /table/.env.template: -------------------------------------------------------------------------------- 1 | VITE_DATASET='' 2 | VITE_PROJECT_ID='' 3 | VITE_SANITY_API_KEY='' -------------------------------------------------------------------------------- /table/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local -------------------------------------------------------------------------------- /table/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Sanity × React Query/Table × Zustand 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /table/lib/client.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import sanityClient from '@sanity/client' 3 | 4 | const client = sanityClient({ 5 | projectId: import.meta.env?.VITE_PROJECT_ID, 6 | dataset: import.meta.env?.VITE_DATASET, 7 | token: import.meta.env?.VITE_SANITY_API_KEY, 8 | useCdn: true, 9 | }) 10 | 11 | export default client 12 | -------------------------------------------------------------------------------- /table/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sanity-react-table", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build", 7 | "serve": "vite preview", 8 | "lint": "eslint .", 9 | "lint:fix": "eslint . --fix" 10 | }, 11 | "dependencies": { 12 | "@graywolfai/react-heroicons": "^1.2.3", 13 | "@headlessui/react": "^0.3.1-d519f7d", 14 | "@sanity/client": "^2.2.6", 15 | "@sanity/image-url": "^0.140.22", 16 | "@types/react-table": "7.0.28", 17 | "@vitejs/plugin-react-refresh": "^1.1.0", 18 | "date-fns": "^2.18.0", 19 | "dset": "^3.1.0", 20 | "postcss": "^8.2.6", 21 | "prettier": "^2.2.1", 22 | "prop-types": "^15.7.2", 23 | "react": "^17.0.0", 24 | "react-dom": "^17.0.0", 25 | "react-dropzone": "^11.3.1", 26 | "react-query": "^3.12.0", 27 | "react-table": "^7.6.3", 28 | "tailwindcss": "^2.0.3", 29 | "uuid": "^8.3.2", 30 | "vite": "^2.0.1", 31 | "zustand": "^3.3.3" 32 | }, 33 | "devDependencies": { 34 | "autoprefixer": "^10.2.4", 35 | "babel-eslint": "^10.1.0", 36 | "eslint": "^7.17.0", 37 | "eslint-config-airbnb": "^18.2.0", 38 | "eslint-config-prettier": "^6.15.0", 39 | "eslint-config-wesbos": "^1.0.1", 40 | "eslint-plugin-html": "^6.1.1", 41 | "eslint-plugin-import": "^2.22.1", 42 | "eslint-plugin-jsx-a11y": "^6.4.1", 43 | "eslint-plugin-prettier": "^3.3.1", 44 | "eslint-plugin-react": "^7.22.0", 45 | "eslint-plugin-react-hooks": "^4.2.0" 46 | }, 47 | "eslintConfig": { 48 | "root": true, 49 | "extends": [ 50 | "wesbos" 51 | ], 52 | "rules": { 53 | "no-console": 2, 54 | "react/jsx-props-no-spreading": 0, 55 | "prettier/prettier": [ 56 | "error", 57 | { 58 | "trailingComma": "es5", 59 | "singleQuote": true, 60 | "printWidth": 80, 61 | "semi": false 62 | } 63 | ] 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /table/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /table/src/App.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /table/src/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { QueryClient, QueryClientProvider } from 'react-query' 3 | 4 | import './App.css' 5 | import Publish from './components/buttons/Publish' 6 | import Query from './components/Query' 7 | import useStore from './hooks/useStore' 8 | 9 | const queryClient = new QueryClient() 10 | 11 | function App() { 12 | const updates = useStore((state) => state.updates) 13 | 14 | return ( 15 |
16 | {updates && ( 17 |
18 |
19 |             {JSON.stringify(updates, null, 1)}
20 |           
21 |
22 | )} 23 | 24 | 25 |
26 | Sanity × React Query/Table × Zustand 27 |
28 | 29 |
30 |
31 | ) 32 | } 33 | 34 | export default App 35 | -------------------------------------------------------------------------------- /table/src/components/Query.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import { useQuery } from 'react-query' 3 | 4 | import client from '../../lib/client' 5 | import useStore from '../hooks/useStore' 6 | import { stubNewDocument } from '../helpers' 7 | 8 | import Table from './Table' 9 | import AddNew from './buttons/AddNew' 10 | import AddTitles from './buttons/AddTitles' 11 | import AddImages from './buttons/AddImages' 12 | 13 | function Query() { 14 | const setArtists = useStore((state) => state.setArtists) 15 | const addUpdate = useStore((state) => state.addUpdate) 16 | const extendedData = useStore((state) => state.extendedData) 17 | const setExtendedData = useStore((state) => state.setExtendedData) 18 | const appendToExtendedData = useStore((state) => state.appendToExtendedData) 19 | 20 | const query = `{ 21 | "artworks": *[_type == "artwork"]{_id,visible,title,price,artist,image} | order(_createdAt asc), 22 | "artists": *[_type == "artist"]{_id,name} 23 | }` 24 | 25 | const { isLoading, error, data } = useQuery('artworkQuery', () => 26 | client.fetch(query).then((allScreening) => allScreening) 27 | ) 28 | 29 | useEffect(() => { 30 | if (data?.artists?.length) setArtists(data.artists) 31 | if (data?.artworks?.length) setExtendedData(data.artworks) 32 | }, [data]) 33 | 34 | if (isLoading) return
Loading...
35 | if (error) 36 | return ( 37 |
38 | An error has occurred: ${error?.message} 39 |
40 | ) 41 | 42 | if (!data) { 43 | return
Query did not return data
44 | } 45 | 46 | // Handlers for creating new documents 47 | function handleNewClick() { 48 | const newDoc = stubNewDocument() 49 | addUpdate(newDoc) 50 | appendToExtendedData([newDoc]) 51 | } 52 | 53 | function handleNewLines(lines) { 54 | const linesIntoDocs = lines.split('\n').map((title) => ({ 55 | ...stubNewDocument(), 56 | title, 57 | })) 58 | linesIntoDocs.forEach((doc) => 59 | addUpdate({ 60 | _id: doc._id, 61 | _action: doc._action, 62 | key: `title`, 63 | value: doc.title, 64 | }) 65 | ) 66 | appendToExtendedData(linesIntoDocs) 67 | } 68 | 69 | return ( 70 | <> 71 | 72 |
73 | 74 | 75 | 76 |
77 | 78 | ) 79 | } 80 | 81 | export default Query 82 | -------------------------------------------------------------------------------- /table/src/components/Table.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/display-name */ 2 | /* eslint-disable no-nested-ternary */ 3 | import PropTypes from 'prop-types' 4 | import React, { useEffect } from 'react' 5 | import { useRowSelect, useSortBy, useTable } from 'react-table' 6 | 7 | import useStore from '../hooks/useStore' 8 | 9 | import Toggle from './cells/Toggle' 10 | import SanityImage from './cells/SanityImage' 11 | import Text from './cells/Text' 12 | import Price from './cells/Price' 13 | import Select from './cells/Select' 14 | import DeleteId from './cells/DeleteId' 15 | 16 | function Table({ data }) { 17 | const columns = React.useMemo( 18 | () => [ 19 | { 20 | Header: 'Visible', 21 | accessor: 'visible', 22 | Cell: ({ row, cell }) => ( 23 | 29 | ), 30 | }, 31 | { 32 | Header: 'Image', 33 | accessor: 'image', 34 | Cell: ({ row, cell }) => ( 35 | 41 | ), 42 | }, 43 | { 44 | Header: 'Title', 45 | accessor: 'title', 46 | Cell: ({ row, cell }) => ( 47 | 53 | ), 54 | }, 55 | { 56 | Header: 'Price', 57 | accessor: 'price', 58 | Cell: ({ row, cell }) => ( 59 | 65 | ), 66 | }, 67 | { 68 | Header: 'Artist', 69 | accessor: 'artist', 70 | Cell: ({ row, cell }) => ( 71 |
133 | 134 | { 135 | // Loop over the header rows 136 | headerGroups.map((headerGroup) => ( 137 | // Apply the header row props 138 | 139 | { 140 | // Loop over the headers in each row 141 | headerGroup.headers.map((column) => ( 142 | // Apply the header cell props 143 | 156 | )) 157 | } 158 | 159 | )) 160 | } 161 | 162 | 163 | { 164 | // Loop over the table rows 165 | rows.map((row) => { 166 | // Prepare the row for display 167 | prepareRow(row) 168 | return ( 169 | // Apply the row props 170 | 171 | { 172 | // Loop over the rows cells 173 | row.cells.map((cell) => ( 174 | // Apply the cell props 175 | 188 | )) 189 | } 190 | 191 | ) 192 | }) 193 | } 194 | 195 |
147 | {column.render('Header')} 148 | 149 | {column.isSorted 150 | ? column.isSortedDesc 151 | ? ' 🔽' 152 | : ' 🔼' 153 | : ''} 154 | 155 |
183 | { 184 | // Render the cell contents 185 | cell.render('Cell') 186 | } 187 |
196 | 197 | ) 198 | } 199 | 200 | Table.propTypes = { 201 | data: PropTypes.array, 202 | } 203 | 204 | const IndeterminateCheckbox = React.forwardRef( 205 | ({ indeterminate, ...rest }, ref) => { 206 | const defaultRef = React.useRef() 207 | const resolvedRef = ref || defaultRef 208 | 209 | React.useEffect(() => { 210 | resolvedRef.current.indeterminate = indeterminate 211 | }, [resolvedRef, indeterminate]) 212 | 213 | return ( 214 | <> 215 | 221 | 222 | ) 223 | } 224 | ) 225 | 226 | export default Table 227 | -------------------------------------------------------------------------------- /table/src/components/buttons/AddImages.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Dialog } from '@headlessui/react' 4 | // eslint-disable-next-line import/no-unresolved 5 | import { ViewGridAddSolid } from '@graywolfai/react-heroicons' 6 | 7 | import SanityImage from '../cells/SanityImage' 8 | 9 | export default function AddImages({ onClick }) { 10 | const [isOpen, setIsOpen] = useState(false) 11 | 12 | return ( 13 | <> 14 | 19 | 20 | 21 |
22 | Add New Artworks, one per image 23 | 24 |
25 | 26 |
27 | 28 |
29 | 36 |
37 |
38 |
39 | 49 | 50 | ) 51 | } 52 | 53 | AddImages.propTypes = { 54 | onClick: PropTypes.func, 55 | } 56 | -------------------------------------------------------------------------------- /table/src/components/buttons/AddNew.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | // eslint-disable-next-line import/no-unresolved 4 | import { PlusSolid } from '@graywolfai/react-heroicons' 5 | 6 | export default function AddNew({ onClick }) { 7 | return ( 8 | 16 | ) 17 | } 18 | 19 | AddNew.propTypes = { 20 | onClick: PropTypes.func, 21 | } 22 | -------------------------------------------------------------------------------- /table/src/components/buttons/AddTitles.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Dialog } from '@headlessui/react' 4 | // eslint-disable-next-line import/no-unresolved 5 | import { MenuAlt2Solid } from '@graywolfai/react-heroicons' 6 | 7 | export default function AddTitles({ onClick }) { 8 | const [isOpen, setIsOpen] = useState(false) 9 | const [lines, setLines] = useState('') 10 | 11 | return ( 12 | <> 13 | 18 | 19 | 20 |
21 | Add New Artworks, one per line 22 | 23 |