Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
"changelog": ["@changesets/changelog-github", {"repo": "sanity-io/plugins"}],
"access": "public",
"baseBranch": "main",
"format": "oxfmt",
"privatePackages": false,
"bumpVersionsWithWorkspaceProtocolOnly": true,
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
"updateInternalDependents": "always",
"onlyUpdatePeerDependentsWhenOutOfRange": true
Expand Down
5 changes: 5 additions & 0 deletions .changeset/dashboard-widget-vercel-xstate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"sanity-plugin-dashboard-widget-vercel": patch
---

Update `xstate` to ^5.33.2
47 changes: 47 additions & 0 deletions .changeset/shared-deps-sanity-ui-icons-motion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
"@sanity/assist": patch
"@sanity/block-insert-picker": patch
"@sanity/code-input": patch
"@sanity/color-input": patch
"@sanity/cross-dataset-duplicator": patch
"@sanity/dashboard": patch
"@sanity/debug-live-sync-tags": patch
"@sanity/debug-preview-url-secret-plugin": patch
"@sanity/document-internationalization": patch
"@sanity/embeddings-index-ui": patch
"@sanity/form-toolkit": patch
"@sanity/google-maps-input": patch
"@sanity/hierarchical-document-list": patch
"@sanity/language-filter": patch
"@sanity/orderable-document-list": patch
"@sanity/personalization-plugin": patch
"@sanity/rich-date-input": patch
"@sanity/sanity-plugin-async-list": patch
"@sanity/sfcc": patch
"@sanity/studio-secrets": patch
"@sanity/table": patch
"@sanity/vercel-protection-bypass": patch
"sanity-plugin-aprimo": patch
"sanity-plugin-asset-source-unsplash": patch
"sanity-plugin-bynder-input": patch
"sanity-plugin-cloudinary": patch
"sanity-plugin-dashboard-widget-document-list": patch
"sanity-plugin-dashboard-widget-netlify": patch
"sanity-plugin-dashboard-widget-vercel": patch
"sanity-plugin-documents-pane": patch
"sanity-plugin-google-translate": patch
"sanity-plugin-graph-view": patch
"sanity-plugin-hotspot-array": patch
"sanity-plugin-iframe-pane": patch
"sanity-plugin-internationalized-array": patch
"sanity-plugin-markdown": patch
"sanity-plugin-media": patch
"sanity-plugin-mux-input": patch
"sanity-plugin-shopify-assets": patch
"sanity-plugin-utils": patch
"sanity-plugin-workflow": patch
"sanity-plugin-workspace-home": patch
"sanity-translations-tab": patch
---

Raise the minimum versions of the `@sanity/ui` (4.2.7), `@sanity/icons` (5.2.2) and `motion` (13.4.4) dependencies the plugin uses
7 changes: 7 additions & 0 deletions .changeset/themer-plugins-monorepo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@sanity/themer": patch
---

author: @stipsan

Move `@sanity/themer` from the sanity-io/ui monorepo to the Sanity plugins monorepo. The package builds to the same JavaScript and CSS, with the same exports and dependency ranges; issues and pull requests now go to [sanity-io/plugins](https://github.com/sanity-io/plugins).
6 changes: 3 additions & 3 deletions .github/workflows/changesets-from-conventional-commits.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: Add changeset to Renovate updates
name: Add changeset from conventional commits

on:
pull_request_target: # zizmor: ignore[dangerous-triggers] gated to renovate[bot]/squiggler-app[bot]; no PR code is checked out or executed in the reusable workflow
pull_request_target: # zizmor: ignore[dangerous-triggers] gated to renovate[bot]/squiggler-legacy[bot]/squiggler-app[bot]; no PR code is checked out or executed in the reusable workflow
types: [opened, synchronize]

concurrency: ${{ github.workflow }}-${{ github.ref }}
Expand All @@ -12,7 +12,7 @@ permissions:
jobs:
call:
uses: sanity-io/.github/.github/workflows/changesets-from-conventional-commits.yml@main
if: github.event.pull_request.user.login == 'renovate[bot]' || github.event.pull_request.user.login == 'squiggler-app[bot]'
if: github.event.pull_request.user.login == 'renovate[bot]' || github.event.pull_request.user.login == 'squiggler-legacy[bot]' || github.event.pull_request.user.login == 'squiggler-app[bot]'
secrets:
ECOSPARK_APP_ID: ${{ secrets.ECOSPARK_APP_ID }}
ECOSPARK_APP_PRIVATE_KEY: ${{ secrets.ECOSPARK_APP_PRIVATE_KEY }}
31 changes: 31 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,37 @@ jobs:
- name: Run Knip
run: pnpm knip --reporter github-actions

storybook-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: ./.github/actions/setup
- name: Store Playwright's version
id: playwright-version
run: |
PLAYWRIGHT_VERSION=$(pnpm --filter plugins-storybook exec playwright --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
echo "Playwright's version: $PLAYWRIGHT_VERSION"
echo "version=${PLAYWRIGHT_VERSION}" >> "$GITHUB_OUTPUT"
- name: Cache Playwright browsers for Playwright's version
id: cache-playwright-browsers
uses: actions/cache@v6
with:
path: ~/.cache/ms-playwright
key: ${{ steps.playwright-version.outputs.version }}-playwright-browsers-chromium
- name: Install Playwright browsers
if: steps.cache-playwright-browsers.outputs.cache-hit != 'true'
run: pnpm --filter plugins-storybook exec playwright install --with-deps chromium
- name: Install Playwright system deps (cache hit)
if: steps.cache-playwright-browsers.outputs.cache-hit == 'true'
run: pnpm --filter plugins-storybook exec playwright install-deps chromium
- run: pnpm test:browser
Comment thread
cursor[bot] marked this conversation as resolved.
- uses: actions/upload-artifact@v7
if: failure()
with:
name: vitest-failure-screenshots
path: dev/storybook/**/__screenshots__/
if-no-files-found: ignore

test_shards:
name: Test (${{ matrix.shardIndex }}/${{ matrix.shardTotal }})
runs-on: ubuntu-latest
Expand Down
3 changes: 0 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,6 @@ jobs:
cache: pnpm
check-latest: true
node-version: lts/*
- name: Update npm to use trusted publishing (OIDC)
# npm 12 wraps `pnpm info --json`; @changesets/cli 3.0.1 does not unwrap it (changesets/changesets#2274)
run: npm install -g npm@11
- run: pnpm install
- name: Create Release Pull Request or Publish to npm
uses: changesets/action@ae32849d5ba541f9ae29e40e22a623bc13562f51 # v2.1.2
Expand Down
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,13 @@ yalc.lock
# Sanity Studio
.sanity

# Storybook
storybook-static

# Vitest browser mode failure screenshots and attachments
__screenshots__
.vitest-attachments

# pnpm
.pnpm-store

Expand Down
29 changes: 20 additions & 9 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ This guide is for AI agents working on this codebase. Follow these instructions
| Cleanup e2e datasets | `pnpm e2e:cleanup` |
| Add changeset | `pnpm changeset add` |
| Start dev server | `pnpm dev` |
| Start Storybook | `pnpm dev:storybook` |
| Run Storybook tests | `pnpm test:browser` |

## Environment Setup

Expand Down Expand Up @@ -74,6 +76,8 @@ pnpm test

Every PR that changes published packages **must** include changesets. **Important:** Create a separate changeset file for each plugin you modify.

The Changesets setup (`.changeset/config.json`, the `@changesets/*` versions, the `changeset`/`release` scripts and `.github/workflows/release.yml`) matches sanity-io/ui's; only the changelog `repo` differs. `"format": "oxfmt"` makes Changesets format the changesets and changelogs it writes with the repo's oxfmt config, and `"bumpVersionsWithWorkspaceProtocolOnly": true` keeps it from rewriting internal ranges that don't use `workspace:` (like the intentionally wide `sanity-plugin-internationalized-array` peer of `@sanity/sfcc`).

#### Why Separate Changesets?

Each plugin has its own changelog that consumers read. A combined changeset would pollute individual plugin changelogs with irrelevant information. For example, `@sanity/code-input`'s changelog should not mention workflow-specific changes.
Expand Down Expand Up @@ -210,12 +214,13 @@ Two things are required:

The CI pipeline runs on every PR:

| Job | What it checks |
| --------- | ------------------------------------------------------ |
| **build** | `pnpm build` - All packages compile successfully |
| **lint** | `pnpm lint --format github` - Code passes oxlint |
| **knip** | `pnpm knip` - No unused files, dependencies or exports |
| **test** | `pnpm test` - All tests pass (runs after build + lint) |
| Job | What it checks |
| ------------------ | -------------------------------------------------------------- |
| **build** | `pnpm build` - All packages compile successfully |
| **lint** | `pnpm lint --format github` - Code passes oxlint |
| **knip** | `pnpm knip` - No unused files, dependencies or exports |
| **test** | `pnpm test` - All tests pass (runs after build + lint) |
| **storybook-test** | `pnpm test:browser` - Every story renders in headless Chromium |

Note on **knip**: in-file usage keeps an exported type "used" (`ignoreExportsUsedInFile`), so removing a type assertion or annotation that was the last reference to an exported type will make knip start flagging that export. Run `pnpm knip` after refactors that remove type references.

Expand Down Expand Up @@ -376,6 +381,10 @@ How it works:

React profiling is already available in the development build from `pnpm dev`. `VERCEL_ENV` is listed in `dev/test-studio/turbo.jsonc` so Turbo cache keys differ between Vercel and non-Vercel builds.

### Storybook

`dev/storybook` is a React Storybook set up like sanity-io/ui's `apps/storybook`, next to the other dev apps, with stories in `dev/storybook/stories/<plugin>/` (see its `README.md`). `pnpm dev:storybook` serves it at `http://localhost:6006` without Sanity authentication, `pnpm test:browser` renders every story in headless Chromium through the Storybook Vitest addon (the `storybook-test` CI job), and `pnpm storybook:build` writes the static build to `dev/storybook/storybook-static`. Install the browser once with `pnpm --filter plugins-storybook exec playwright install chromium`. The preview imports `@sanity/ui/styles.css` because the published `@sanity/ui` dist doesn't import its own CSS, and it keeps the vanilla-extract Vite plugin because workspace plugins resolve to their `.css.ts` source. Like the other `dev/*` apps, it's skipped by `pretest` and the CI build job.

## Creating a New Plugin

Use the generator:
Expand Down Expand Up @@ -558,6 +567,7 @@ The shared rules (plugins, options, categories, rules) live in the `@sanity/plug

```
plugins/
├── dev/storybook/ # React Storybook for plugin components (localhost:6006)
├── dev/test-studio/ # Test Sanity Studio (localhost:3333)
├── packages/@repo/ # Internal shared packages
├── packages/@sanity/ # Published tooling packages (e.g. @sanity/plugin-kit)
Expand Down Expand Up @@ -600,9 +610,10 @@ corepack enable

### Services

| Service | Port | Purpose |
| ------------------------ | ---- | --------------------------------------------- |
| Test Studio (`pnpm dev`) | 3333 | Local Sanity Studio for manual plugin testing |
| Service | Port | Purpose |
| -------------------------------- | ---- | ----------------------------------------------- |
| Test Studio (`pnpm dev`) | 3333 | Local Sanity Studio for manual plugin testing |
| Storybook (`pnpm dev:storybook`) | 6006 | Plugin component stories, no Sanity auth needed |

No Docker, databases, or other local services are required. CI-style verification (`pnpm lint`, `pnpm build`, `pnpm test run`) runs entirely in-process.

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ Sessions can be compared in the DevTools UI to diff bundle changes between build
| [`@sanity/sfcc`](./plugins/@sanity/sfcc) | Salesforce Commerce Cloud integration with synced product data |
| [`@sanity/studio-secrets`](./plugins/@sanity/studio-secrets) | Manage Studio secrets at runtime |
| [`@sanity/table`](./plugins/@sanity/table) | Table schema type and input component for Sanity Studio |
| [`@sanity/themer`](./plugins/@sanity/themer) | Generate Studio themes from a few colors, with a themer Studio tool |
| [`@sanity/vercel-protection-bypass`](./plugins/@sanity/vercel-protection-bypass) | Setup tool for Vercel Deployment Protection in previews |
| [`sanity-plugin-aprimo`](./plugins/sanity-plugin-aprimo) | Aprimo asset selector integration |
| [`sanity-plugin-asset-source-unsplash`](./plugins/sanity-plugin-asset-source-unsplash) | Use Unsplash images directly in Sanity Studio |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import {buildTheme, presets} from '@sanity/themer'
import {Card, ThemeProvider} from '@sanity/ui'
import type {RootTheme, ThemeColorSchemeKey} from '@sanity/ui/theme'
import {DecoratorHelpers} from '@storybook/addon-themes'
import type {Decorator} from '@storybook/react-vite'
import {createGlobalStyle} from 'styled-components'

const {initializeThemeState, pluckThemeFromContext} = DecoratorHelpers

const GlobalStyle = createGlobalStyle`
body,
.docs-story {
background-color: ${
// oxlint-disable-next-line no-deprecated
({theme}) => theme.sanity.color.base.bg
};
}
`

const presetThemes = new Map<string, RootTheme>()

/** The theme `@sanity/themer` builds from the preset with the given slug, built once */
function getPresetTheme(slug: string): RootTheme {
const cached = presetThemes.get(slug)
if (cached) return cached

const preset = presets.find((candidate) => candidate.slug === slug)
const theme = buildTheme(preset?.options)
presetThemes.set(slug, theme)

return theme
}

/**
* Story decorator which wraps all stories in a Sanity <ThemeProvider> with the theme that
* `buildTheme` from `@sanity/themer` generates from the preset picked in the toolbar, in the
* color scheme picked next to it.
*
* Stories are also wrapped in a <Card> for layout. Set the `padding` parameter to change (or
* remove, with `padding: 0`) the default padding – e.g. for stories that depend on exact
* viewport dimensions.
*/
export const withSanityTheme = ({
themes,
defaultTheme,
}: {
themes: Record<string, string>
defaultTheme: string
}): Decorator => {
initializeThemeState(Object.keys(themes), defaultTheme)

return (Story, context) => {
const selectedTheme = pluckThemeFromContext(context)
const {themeOverride} = context.parameters['themes'] ?? {}
const {padding = 4} = context.parameters
const preset = String(context.globals['preset'] ?? 'studio')

// oxlint-disable-next-line no-unsafe-type-assertion
const selected = (themeOverride || selectedTheme || defaultTheme) as ThemeColorSchemeKey

return (
<ThemeProvider scheme={selected} theme={getPresetTheme(preset)}>
<GlobalStyle />
<Card padding={padding}>
<Story />
</Card>
</ThemeProvider>
)
}
}
32 changes: 32 additions & 0 deletions dev/storybook/.storybook/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {vanillaExtractPlugin} from '@sanity/vanilla-extract-vite-plugin'
import type {StorybookConfig} from '@storybook/react-vite'
import viteReact from '@vitejs/plugin-react'
import {mergeConfig} from 'vite'

const config: StorybookConfig = {
stories: ['../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: [
'@storybook/addon-a11y',
'@storybook/addon-docs',
'@storybook/addon-links',
'@storybook/addon-themes',
'@storybook/addon-vitest',
],
framework: {
name: '@storybook/react-vite',
options: {},
},
viteFinal(config) {
return mergeConfig(config, {
plugins: [
// `compiler` runs the React Compiler natively via `oxc-transform-react`
// (the Rust port) in the same pass as TypeScript/JSX — no babel
viteReact({compiler: {target: '19'}}),
// Workspace plugins resolve to their TypeScript source (dev `exports`),
// so their vanilla-extract `.css.ts` modules must be compiled here
vanillaExtractPlugin(),
],
})
},
}
export default config
Loading
Loading