diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 0e22849..3c65b4d 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -86,6 +86,7 @@ jobs: run: | echo "🔨 Generating UI assets..." npm install + npm run lint npm run build cp ./build/injectNav.iife.js ../quantinuum_sphinx/static/injectNav.iife.js cp ./build/syncTheme.iife.js ../quantinuum_sphinx/static/syncTheme.iife.js diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 300c0a4..b60423d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -147,6 +147,7 @@ jobs: run: | echo "🔨 Generating UI assets..." npm install + npm run lint npm run build cp ./build/injectNav.iife.js ../quantinuum_sphinx/static/injectNav.iife.js cp ./build/syncTheme.iife.js ../quantinuum_sphinx/static/syncTheme.iife.js diff --git a/sphinx-ui/react/package-lock.json b/sphinx-ui/react/package-lock.json index 9de780d..4674423 100644 --- a/sphinx-ui/react/package-lock.json +++ b/sphinx-ui/react/package-lock.json @@ -30,7 +30,6 @@ "prettier-plugin-tailwindcss": "^0.6.6", "serve": "^14.2.3", "tailwindcss": "^3.4.4", - "tsc": "^2.0.4", "tsdown": "^0.21.7", "typescript": "^6.0.3" }, @@ -9909,15 +9908,6 @@ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" }, - "node_modules/tsc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/tsc/-/tsc-2.0.4.tgz", - "integrity": "sha512-fzoSieZI5KKJVBYGvwbVZs/J5za84f2lSTLPYf6AGiIf43tZ3GNrI1QzTLcjtyDDP4aLxd46RTZq1nQxe7+k5Q==", - "dev": true, - "bin": { - "tsc": "bin/tsc" - } - }, "node_modules/tsdown": { "version": "0.21.10", "resolved": "https://registry.npmjs.org/tsdown/-/tsdown-0.21.10.tgz", diff --git a/sphinx-ui/react/package.json b/sphinx-ui/react/package.json index 27bb73a..0e4921e 100644 --- a/sphinx-ui/react/package.json +++ b/sphinx-ui/react/package.json @@ -5,6 +5,7 @@ "main": "index.js", "scripts": { "build": "npx tsdown", + "lint": "tsc --noEmit", "format": "prettier --write --config='../../libraries/ui/.prettierrc' --ignore-path='.prettierignore' ." }, "author": "", @@ -23,7 +24,6 @@ "prettier-plugin-tailwindcss": "^0.6.6", "serve": "^14.2.3", "tailwindcss": "^3.4.4", - "tsc": "^2.0.4", "tsdown": "^0.21.7", "typescript": "^6.0.3" }, diff --git a/sphinx-ui/react/postcss.config.mjs b/sphinx-ui/react/postcss.config.mjs index 45cb478..d54a93b 100644 --- a/sphinx-ui/react/postcss.config.mjs +++ b/sphinx-ui/react/postcss.config.mjs @@ -5,13 +5,34 @@ import autoprefixer from 'autoprefixer' import prefixSelector from 'postcss-prefix-selector' import failOnWarn from 'postcss-fail-on-warn' +const tailwindScopeSelector = '.use-tailwind' + +function scopeTailwindSelector(prefix, selector, prefixedSelector) { + if (selector === '*') { + return `${prefix}, ${prefixedSelector}` + } + + if (selector.startsWith('::')) { + return `${prefix}${selector}, ${prefixedSelector}` + } + + if (selector.startsWith('.')) { + return `${prefixedSelector}, ${prefix}${selector}` + } + + return prefixedSelector +} + export default { plugins: [ postcssImport, tailwindcssNesting, tailwindcss, autoprefixer, - prefixSelector({ prefix: '.use-tailwind' }), + prefixSelector({ + prefix: tailwindScopeSelector, + transform: scopeTailwindSelector, + }), failOnWarn, ], } diff --git a/sphinx-ui/react/src/injectNav.tsx b/sphinx-ui/react/src/injectNav.tsx index 1c73bf8..71a0fed 100644 --- a/sphinx-ui/react/src/injectNav.tsx +++ b/sphinx-ui/react/src/injectNav.tsx @@ -8,12 +8,53 @@ const GA_ID = __NEXT_PUBLIC_GA_ID__; const tailwindScopeClassName = 'use-tailwind'; -(() => { - document.body.classList.add(tailwindScopeClassName) +const isTailwindDialogPortalElement = (element: Element): element is HTMLElement => { + if (!(element instanceof HTMLElement)) return false + + const { classList } = element + return ( + classList.contains('fixed') && + classList.contains('z-50') && + (classList.contains('inset-0') || element.getAttribute('role') === 'dialog') + ) +} + +const scopeTailwindDialogPortalElements = (root: ParentNode = document.body) => { + const scopeElement = (element: Element) => { + if (isTailwindDialogPortalElement(element)) { + element.classList.add(tailwindScopeClassName) + } + } + + if (root instanceof Element) { + scopeElement(root) + } + + root.querySelectorAll('[class][data-state], [class][role="dialog"]').forEach(scopeElement) +} +const observeTailwindDialogPortalElements = () => { + scopeTailwindDialogPortalElements() + + const observer = new MutationObserver((records) => { + records.forEach((record) => { + record.addedNodes.forEach((node) => { + if (node instanceof Element) { + scopeTailwindDialogPortalElements(node) + } + }) + }) + }) + + observer.observe(document.body, { childList: true, subtree: true }) +} + +(() => { const mountElement = document.querySelector('.nexus-nav') if (!mountElement) return + observeTailwindDialogPortalElements() + const renderIn = document.createElement('div') mountElement.appendChild(renderIn) diff --git a/sphinx-ui/react/tailwind.config.ts b/sphinx-ui/react/tailwind.config.ts index ec3aedb..1b41934 100644 --- a/sphinx-ui/react/tailwind.config.ts +++ b/sphinx-ui/react/tailwind.config.ts @@ -1,3 +1,5 @@ +/// + import path from 'path' import { Config } from 'tailwindcss' import plugin from 'tailwindcss/plugin' diff --git a/sphinx-ui/react/tsdown.config.ts b/sphinx-ui/react/tsdown.config.ts index 5422f5b..dd89be8 100644 --- a/sphinx-ui/react/tsdown.config.ts +++ b/sphinx-ui/react/tsdown.config.ts @@ -1,6 +1,8 @@ +/// + import { defineConfig } from 'tsdown' -const NEXT_PUBLIC_GA_ID = JSON.stringify(process.env.NEXT_PUBLIC_GA_ID || '') +const NEXT_PUBLIC_GA_ID = JSON.stringify(process.env['NEXT_PUBLIC_GA_ID'] || '') export default defineConfig([ // Script for injecting nav into sphinx build. diff --git a/sphinx-ui/react/tsup.config.ts b/sphinx-ui/react/tsup.config.ts deleted file mode 100644 index 3dd401e..0000000 --- a/sphinx-ui/react/tsup.config.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { defineConfig } from 'tsup' - -export default defineConfig([ - // Script for injecting nav into sphinx build. - { - entry: ['./src/injectNav.tsx'], - outDir: 'build', - minify: true, - skipNodeModulesBundle: false, - target: "es2015", - platform: "browser", - format: ["iife"], - clean: true, -}, -// Script for syncing dark mode preference -{ - entry: ['./src/syncTheme.ts'], - outDir: 'build', - minify: true, - skipNodeModulesBundle: false, - target: "es2015", - platform: "browser", - format: ["iife"], - clean: true, -}]) diff --git a/sphinx-ui/uv.lock b/sphinx-ui/uv.lock index 5a811ac..f1238b6 100644 --- a/sphinx-ui/uv.lock +++ b/sphinx-ui/uv.lock @@ -38,24 +38,24 @@ wheels = [ [[package]] name = "beautifulsoup4" -version = "4.14.3" +version = "4.15.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "soupsieve" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/65/318323f98dbee45d42dff61d8f047181bc6f2268a9068cfad035a46be5af/beautifulsoup4-4.15.0.tar.gz", hash = "sha256:288e3ca7d54b06f2ac191970bc275c1939cb46d450b255bf6718b04aa37ab4f7", size = 632571, upload-time = "2026-06-07T16:44:20.453Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, + { url = "https://files.pythonhosted.org/packages/88/c6/92fcd42f1ba33e1184263f25bfabf3d27c383410470f169e4b8163bf9c17/beautifulsoup4-4.15.0-py3-none-any.whl", hash = "sha256:d6f88de62e1d4e38ecb1077eb9724cd0eff29d2a08ca16a401e9b9e93f117cf9", size = 109924, upload-time = "2026-06-07T16:44:21.566Z" }, ] [[package]] name = "certifi" -version = "2026.4.22" +version = "2026.5.20" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" }, + { url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" }, ] [[package]] @@ -184,11 +184,11 @@ wheels = [ [[package]] name = "idna" -version = "3.15" +version = "3.18" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, ] [[package]] @@ -340,20 +340,20 @@ wheels = [ [[package]] name = "snowballstemmer" -version = "3.0.1" +version = "3.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/f8/0a71edf031f03c40db17503cb8ca78a69a171254e568e7db241b0ab57ea1/snowballstemmer-3.1.1.tar.gz", hash = "sha256:e07bbc54a0d798fe6010a12398422e62a8bfbba95c394fd0956ef58cb4d3e260", size = 123314, upload-time = "2026-06-03T00:56:40.194Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/4c/07/2ebca9b11fb9be7340a818d8d6f63feaebb146be2c4afbd6061701d6df6e/snowballstemmer-3.1.1-py3-none-any.whl", hash = "sha256:7e207fa178741da09cdee59d3ecec3827ad5f92b1fc5c9ff3755b639f71f5752", size = 104164, upload-time = "2026-06-03T00:56:38.614Z" }, ] [[package]] name = "soupsieve" -version = "2.8.3" +version = "2.8.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } +sdist = { url = "https://files.pythonhosted.org/packages/47/2c/0a5f6f8ee0d5589e48c7640213ed5175d52cf540a06725b628cc1a45d6ce/soupsieve-2.8.4.tar.gz", hash = "sha256:e121fd02e975c695e4e9e8774a5ee35d74714b59307868dcc5319ad2d9e3328e", size = 121110, upload-time = "2026-05-24T13:55:57.154Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, + { url = "https://files.pythonhosted.org/packages/5e/f5/0c41cb68dcae6b7de4fac4188a3a9589e21fb31df21ea3a2e888db95e6c9/soupsieve-2.8.4-py3-none-any.whl", hash = "sha256:e7e6b0769c8f51ed59acab6e994b00621096cfb1c640a7509295987388fbaf65", size = 37304, upload-time = "2026-05-24T13:55:55.406Z" }, ] [[package]]