diff --git a/bun.lock b/bun.lock index b582bdbe5..0c999c2ac 100644 --- a/bun.lock +++ b/bun.lock @@ -26,6 +26,7 @@ "devDependencies": { "@atlaskit/pragmatic-drag-and-drop": "^1.7.10", "@atlaskit/pragmatic-drag-and-drop-react-drop-indicator": "^3.2.15", + "@base-ui/react": "^1.4.1", "@chenglou/pretext": "^0.0.5", "@electron-toolkit/eslint-config-prettier": "^3.0.0", "@electron-toolkit/eslint-config-ts": "^3.1.0", @@ -159,6 +160,10 @@ "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + "@base-ui/react": ["@base-ui/react@1.4.1", "", { "dependencies": { "@babel/runtime": "^7.29.2", "@base-ui/utils": "0.2.8", "@floating-ui/react-dom": "^2.1.8", "@floating-ui/utils": "^0.2.11", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@date-fns/tz": "^1.2.0", "@types/react": "^17 || ^18 || ^19", "date-fns": "^4.0.0", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@date-fns/tz", "@types/react", "date-fns"] }, "sha512-Ab5/LIhcmL8BQcsBUYiOfkSDRdLpvgUBzMK30cu684JPcLclYlztharvCZyNNgzJtbAiREzI9q0pI5erHCMgCw=="], + + "@base-ui/utils": ["@base-ui/utils@0.2.8", "", { "dependencies": { "@babel/runtime": "^7.29.2", "@floating-ui/utils": "^0.2.11", "reselect": "^5.1.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-jvOi+c+ftGlGotNcKnzPVg2IhCaDTB6/6R3JeqdjdXktuAJi3wKH9T7+svuaKh1mmfVU11UWzUZVH74JDfi/wQ=="], + "@borewit/text-codec": ["@borewit/text-codec@0.2.1", "", {}, "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw=="], "@canvas/image-data": ["@canvas/image-data@1.1.0", "", {}, "sha512-QdObRRjRbcXGmM1tmJ+MrHcaz1MftF2+W7YI+MsphnsCrmtyfS0d5qJbk0MeSbUeyM/jCb0hmnkXPsy026L7dA=="], @@ -279,15 +284,15 @@ "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], - "@floating-ui/core": ["@floating-ui/core@1.7.1", "", { "dependencies": { "@floating-ui/utils": "^0.2.9" } }, "sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw=="], + "@floating-ui/core": ["@floating-ui/core@1.7.5", "", { "dependencies": { "@floating-ui/utils": "^0.2.11" } }, "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ=="], - "@floating-ui/dom": ["@floating-ui/dom@1.7.1", "", { "dependencies": { "@floating-ui/core": "^1.7.1", "@floating-ui/utils": "^0.2.9" } }, "sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ=="], + "@floating-ui/dom": ["@floating-ui/dom@1.7.6", "", { "dependencies": { "@floating-ui/core": "^1.7.5", "@floating-ui/utils": "^0.2.11" } }, "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ=="], "@floating-ui/react": ["@floating-ui/react@0.26.28", "", { "dependencies": { "@floating-ui/react-dom": "^2.1.2", "@floating-ui/utils": "^0.2.8", "tabbable": "^6.0.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw=="], - "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.3", "", { "dependencies": { "@floating-ui/dom": "^1.0.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-huMBfiU9UnQ2oBwIhgzyIiSpVgvlDstU8CX0AF+wS+KzmYMs0J2a3GwuFHV1Lz+jlrQGeC1fF+Nv0QoumyV0bA=="], + "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.8", "", { "dependencies": { "@floating-ui/dom": "^1.7.6" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A=="], - "@floating-ui/utils": ["@floating-ui/utils@0.2.9", "", {}, "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="], + "@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="], "@ghostery/adblocker": ["@ghostery/adblocker@2.14.1", "", { "dependencies": { "@ghostery/adblocker-content": "^2.14.1", "@ghostery/adblocker-extended-selectors": "^2.14.1", "@ghostery/url-parser": "^1.3.1", "@remusao/guess-url-type": "^2.1.0", "@remusao/small": "^2.1.0", "@remusao/smaz": "^2.2.0", "tldts-experimental": "^7.0.23" } }, "sha512-/cQTMJRd4/7zpgFHWqe+9wqiRPGTy+BhqfkxplvejPgq8d6spBjC6bSJV61rVHJZw5F4KOO5ReKOvuguaKG28A=="], @@ -1673,6 +1678,8 @@ "resedit": ["resedit@1.7.2", "", { "dependencies": { "pe-library": "^0.4.1" } }, "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA=="], + "reselect": ["reselect@5.1.1", "", {}, "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="], + "resize-observer-polyfill": ["resize-observer-polyfill@1.5.1", "", {}, "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="], "resolve": ["resolve@2.0.0-next.5", "", { "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA=="], @@ -2021,6 +2028,10 @@ "@babel/traverse/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], + "@base-ui/react/@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="], + + "@base-ui/utils/@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="], + "@electron/asar/commander": ["commander@5.1.0", "", {}, "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg=="], "@electron/fuses/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], @@ -2049,6 +2060,10 @@ "@eslint/eslintrc/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + "@floating-ui/react/@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.3", "", { "dependencies": { "@floating-ui/dom": "^1.0.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-huMBfiU9UnQ2oBwIhgzyIiSpVgvlDstU8CX0AF+wS+KzmYMs0J2a3GwuFHV1Lz+jlrQGeC1fF+Nv0QoumyV0bA=="], + + "@floating-ui/react/@floating-ui/utils": ["@floating-ui/utils@0.2.9", "", {}, "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="], + "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="], "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], @@ -2079,6 +2094,8 @@ "@radix-ui/react-dialog/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA=="], + "@radix-ui/react-popper/@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.3", "", { "dependencies": { "@floating-ui/dom": "^1.0.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-huMBfiU9UnQ2oBwIhgzyIiSpVgvlDstU8CX0AF+wS+KzmYMs0J2a3GwuFHV1Lz+jlrQGeC1fF+Nv0QoumyV0bA=="], + "@radix-ui/react-use-is-hydrated/use-sync-external-store": ["use-sync-external-store@1.5.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A=="], "@rolldown/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], @@ -2313,6 +2330,8 @@ "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], + "@floating-ui/react/@floating-ui/react-dom/@floating-ui/dom": ["@floating-ui/dom@1.7.1", "", { "dependencies": { "@floating-ui/core": "^1.7.1", "@floating-ui/utils": "^0.2.9" } }, "sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ=="], + "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], @@ -2323,6 +2342,8 @@ "@jimp/core/file-type/token-types": ["token-types@4.2.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ=="], + "@radix-ui/react-popper/@floating-ui/react-dom/@floating-ui/dom": ["@floating-ui/dom@1.7.1", "", { "dependencies": { "@floating-ui/core": "^1.7.1", "@floating-ui/utils": "^0.2.9" } }, "sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ=="], + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], @@ -2435,6 +2456,12 @@ "@babel/helper-module-imports/@babel/traverse/@babel/generator/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="], + "@floating-ui/react/@floating-ui/react-dom/@floating-ui/dom/@floating-ui/core": ["@floating-ui/core@1.7.1", "", { "dependencies": { "@floating-ui/utils": "^0.2.9" } }, "sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw=="], + + "@radix-ui/react-popper/@floating-ui/react-dom/@floating-ui/dom/@floating-ui/core": ["@floating-ui/core@1.7.1", "", { "dependencies": { "@floating-ui/utils": "^0.2.9" } }, "sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw=="], + + "@radix-ui/react-popper/@floating-ui/react-dom/@floating-ui/dom/@floating-ui/utils": ["@floating-ui/utils@0.2.9", "", {}, "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="], + "app-builder-lib/@electron/get/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], "app-builder-lib/@electron/get/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], diff --git a/docs/contributing/dependencies.md b/docs/contributing/dependencies.md index 39d41476e..56903530b 100644 --- a/docs/contributing/dependencies.md +++ b/docs/contributing/dependencies.md @@ -81,6 +81,7 @@ These dependencies are either used in the build process, or they are only used i - nuqs - Use for managing query parameters in the URL. - @tanstack/react-query - React Query for the frontend. - @chenglou/pretext - Calculate text width for the frontend. +- @base-ui/react - UI Components for the frontend. ## Tailwind CSS Dependencies for the Frontend diff --git a/package.json b/package.json index d51cd2ba8..c0eb8c4e7 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "devDependencies": { "@atlaskit/pragmatic-drag-and-drop": "^1.7.10", "@atlaskit/pragmatic-drag-and-drop-react-drop-indicator": "^3.2.15", + "@base-ui/react": "^1.4.1", "@chenglou/pretext": "^0.0.5", "@electron-toolkit/eslint-config-prettier": "^3.0.0", "@electron-toolkit/eslint-config-ts": "^3.1.0", diff --git a/src/renderer/src/components/browser-ui/browser-sidebar/_components/address-bar.tsx b/src/renderer/src/components/browser-ui/browser-sidebar/_components/address-bar.tsx index 0df03bf08..92592db03 100644 --- a/src/renderer/src/components/browser-ui/browser-sidebar/_components/address-bar.tsx +++ b/src/renderer/src/components/browser-ui/browser-sidebar/_components/address-bar.tsx @@ -4,8 +4,9 @@ import { memo, useCallback, useRef, type MouseEvent } from "react"; import { useAddressUrl, useFocusedTabId } from "@/components/providers/tabs-provider"; import { simplifyUrl } from "@/lib/url"; import { PinnedBrowserActions } from "./pinned-browser-actions"; -import { BrowserActionList } from "@/components/browser-ui/browser-sidebar/_components/browser-action-list"; import { useBrowserSidebar } from "@/components/browser-ui/browser-sidebar/provider"; +import { BrowserActionList } from "@/components/browser-ui/browser-sidebar/_components/browser-action-list"; +// import { SiteControls } from "@/components/browser-ui/browser-sidebar/_components/site-controls"; export const AddressBar = memo(function AddressBar() { const containerRef = useRef(null); @@ -71,6 +72,8 @@ export const AddressBar = memo(function AddressBar() {
+ {/* TODO: Add site controls */} + {/* */}
diff --git a/src/renderer/src/components/browser-ui/browser-sidebar/_components/bottom/bottom-extras-menu.tsx b/src/renderer/src/components/browser-ui/browser-sidebar/_components/bottom/bottom-extras-menu.tsx index cec387f87..0341db603 100644 --- a/src/renderer/src/components/browser-ui/browser-sidebar/_components/bottom/bottom-extras-menu.tsx +++ b/src/renderer/src/components/browser-ui/browser-sidebar/_components/bottom/bottom-extras-menu.tsx @@ -1,57 +1,76 @@ -import { PortalPopover } from "@/components/portal/popover"; +import { BubbleEvent } from "@/components/logic/bubble-event"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/portal/popover"; import { useSpaces } from "@/components/providers/spaces-provider"; import { Button } from "@/components/ui/button"; -import { PopoverListboxItem, PopoverListboxList, usePopoverListbox } from "@/components/ui/popover-listbox"; -import { PopoverTrigger } from "@/components/ui/popover"; +import { Command, CommandItem, CommandList } from "@/components/ui/command"; import { cn } from "@/lib/utils"; -import { ArchiveIcon, HistoryIcon, SettingsIcon } from "lucide-react"; -import { useCallback, useState } from "react"; +import { ArchiveIcon, HistoryIcon, LucideIcon, SettingsIcon } from "lucide-react"; +import { useCallback, useRef, useState } from "react"; -const EXTRA_ITEM_COUNT = 2; +function BottomExtrasMenuItem({ + id, + Icon, + label, + url, + onItemSelected +}: { + id: string; + Icon: LucideIcon; + label: string; + url: string; + onItemSelected: (url: string) => void; +}) { + return ( + onItemSelected(url)} className="text-black dark:text-white"> + + {label} + + ); +} export function BottomExtrasMenu() { const [open, setOpen] = useState(false); + const commandRef = useRef(null); - const { isCurrentSpaceLight } = useSpaces(); - const spaceInjectedClasses = cn(isCurrentSpaceLight ? "" : "dark"); - - const onActivate = useCallback((index: number) => { - if (index === 0) { - flow.tabs.newTab("flow://history", true); - } else if (index === 1) { + const onItemSelected = useCallback((url: string) => { + if (url === "internal://settings") { flow.windows.openSettingsWindow(); + } else { + flow.tabs.newTab(url, true); } setOpen(false); }, []); - const listbox = usePopoverListbox({ - open, - itemCount: EXTRA_ITEM_COUNT, - ariaLabel: "Sidebar extras", - getOptionId: (i) => `bottom-extra-${i}`, - onActivate, - initialHighlightedIndex: EXTRA_ITEM_COUNT - 1 - }); - + const { isCurrentSpaceLight } = useSpaces(); + const spaceInjectedClasses = cn(isCurrentSpaceLight ? "" : "dark"); return ( - - - - - - - - - History - - - - Settings - - - - + + + + + + + + + + + + ); } diff --git a/src/renderer/src/components/browser-ui/browser-sidebar/_components/browser-action-list.tsx b/src/renderer/src/components/browser-ui/browser-sidebar/_components/browser-action-list.tsx index 44df18a83..628506ee0 100644 --- a/src/renderer/src/components/browser-ui/browser-sidebar/_components/browser-action-list.tsx +++ b/src/renderer/src/components/browser-ui/browser-sidebar/_components/browser-action-list.tsx @@ -1,8 +1,7 @@ import { type ActivateEventType, useBrowserAction } from "@/components/providers/browser-action-provider"; import { useExtensions } from "@/components/providers/extensions-provider"; import { useSpaces } from "@/components/providers/spaces-provider"; -import { PortalPopover } from "@/components/portal/popover"; -import { PopoverTrigger } from "@/components/ui/popover"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/portal/popover"; import { Separator } from "@/components/ui/separator"; import { cn } from "@/lib/utils"; import { CogIcon, LayersIcon, PackageXIcon, PinIcon, PinOffIcon, PuzzleIcon } from "lucide-react"; @@ -168,23 +167,21 @@ export function BrowserActionList() { if (!focusedTab) return null; return ( - - - + + { + event.stopPropagation(); + }} + > + - + {!noActiveTab && !noActions && actions.map((action) => ( @@ -216,7 +213,7 @@ export function BrowserActionList() { Manage Extensions - - + + ); } diff --git a/src/renderer/src/components/browser-ui/browser-sidebar/_components/navigation-controls.tsx b/src/renderer/src/components/browser-ui/browser-sidebar/_components/navigation-controls.tsx index a340cc9ba..828291a8f 100644 --- a/src/renderer/src/components/browser-ui/browser-sidebar/_components/navigation-controls.tsx +++ b/src/renderer/src/components/browser-ui/browser-sidebar/_components/navigation-controls.tsx @@ -3,9 +3,9 @@ import { ArrowLeftIcon, ArrowLeftIconHandle } from "@/components/icons/arrow-lef import { ArrowRightIcon, ArrowRightIconHandle } from "@/components/icons/arrow-right"; import { useAddressUrl, useFocusedTabId, useFocusedTabLoading } from "@/components/providers/tabs-provider"; import { useSpaces } from "@/components/providers/spaces-provider"; -import { PortalPopover } from "@/components/portal/popover"; -import { PopoverTrigger } from "@/components/ui/popover"; -import { PopoverListboxItem, PopoverListboxList, usePopoverListbox } from "@/components/ui/popover-listbox"; +import { BubbleEvent } from "@/components/logic/bubble-event"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/portal/popover"; +import { Command, CommandItem, CommandList } from "@/components/ui/command"; import { cn } from "@/lib/utils"; import { XIcon } from "lucide-react"; import { AnimatePresence, motion } from "motion/react"; @@ -99,28 +99,19 @@ function NavigationButton({ }) { const { isCurrentSpaceLight } = useSpaces(); const iconRef = useRef(null); + const commandRef = useRef(null); const [open, setOpen] = useState(false); const { handleMouseDown, handleMouseUp } = usePressAnimation(iconRef); const onActivateHistory = useCallback( - (index: number) => { + (entry: NavigationEntryWithIndex) => { if (!focusedTabId) return; - const entry = entries[index]; - if (!entry) return; flow.navigation.goToNavigationEntry(focusedTabId, entry.index); setOpen(false); }, - [entries, focusedTabId] + [focusedTabId] ); - const listbox = usePopoverListbox({ - open, - itemCount: entries.length, - ariaLabel: direction === "back" ? "Back history" : "Forward history", - getOptionId: (i) => `nav-history-${direction}-${entries[i]!.index}`, - onActivate: onActivateHistory - }); - const navigate = useCallback(() => { if (!focusedTabId || entries.length === 0) return; flow.navigation.goToNavigationEntry(focusedTabId, entries[0].index); @@ -158,21 +149,25 @@ function NavigationButton({ /> {entries.length > 0 && ( - + - - - {entries.map((entry, index) => ( - - {entry.title || entry.url} - - ))} - - - + + + + + {entries.map((entry) => ( + onActivateHistory(entry)} + > + {entry.title || entry.url} + + ))} + + + + )} ); diff --git a/src/renderer/src/components/browser-ui/browser-sidebar/_components/site-controls/extensions.tsx b/src/renderer/src/components/browser-ui/browser-sidebar/_components/site-controls/extensions.tsx new file mode 100644 index 000000000..082301bf4 --- /dev/null +++ b/src/renderer/src/components/browser-ui/browser-sidebar/_components/site-controls/extensions.tsx @@ -0,0 +1,247 @@ +import { type ActivateEventType, useBrowserAction } from "@/components/providers/browser-action-provider"; +import { useExtensions } from "@/components/providers/extensions-provider"; +import { cn } from "@/lib/utils"; +import { CHROME_WEB_STORE_URL } from "@/routes/extensions/page"; +import { PlusIcon, PuzzleIcon } from "lucide-react"; +import { MouseEvent, useCallback, useRef, useState } from "react"; + +interface ExtensionAction { + color?: string; + text?: string; + title?: string; + icon?: chrome.browserAction.TabIconDetails; + popup?: string; + iconModified?: number; +} + +interface Action { + id: string; + title: string; + popup: string; + tabs: Record; +} + +function RotatedPinInCircle({ className }: { className?: string }) { + return ( + + + + + + {/* Pin cutout */} + + + + + + + + + + ); +} + +// Extension icon via crx:// protocol +function BrowserActionIcon({ + action, + activeTabId, + tabInfo, + partitionId +}: { + action: Action; + activeTabId: number; + tabInfo: ExtensionAction | null; + partitionId: string; +}) { + const { iconModified } = { ...action, ...tabInfo }; + const [isError, setIsError] = useState(false); + const iconSize = 32; + const resizeType = 2; + const timeParam = iconModified ? `&t=${iconModified}` : ""; + const iconUrl = `crx://extension-icon/${action.id}/${iconSize}/${resizeType}?tabId=${activeTabId}${timeParam}&partition=${encodeURIComponent(partitionId)}`; + + if (isError) { + return ; + } + + return ( + + {/* eslint-disable-next-line react/no-unknown-property */} + setIsError(true)} /> + + ); +} + +// Badge overlay on extension icon +function Badge({ color, text }: { color?: string; text?: string }) { + if (!text) return null; + + return ( +
+ {text} +
+ ); +} + +function ExtensionButtonContainer({ + children, + className, + ...props +}: { children: React.ReactNode } & React.ComponentProps<"button">) { + return ( + + ); +} + +// Grid tile for a single extension +function ExtensionGridTile({ + action, + alignment, + partition, + activeTabId +}: { + action: Action; + alignment: string; + partition: string; + activeTabId: number; +}) { + const { activate } = useBrowserAction(); + const buttonRef = useRef(null); + + const tabInfo = activeTabId > -1 ? action.tabs[activeTabId] : null; + + const { extensions } = useExtensions(); + const extension = extensions.find((e) => e.id === action.id); + const isPinned = extension?.pinned; + + const onActivated = useCallback( + (eventType: ActivateEventType) => { + if (!buttonRef.current) return; + activate(action.id, activeTabId, buttonRef.current, alignment, eventType); + }, + [action.id, activeTabId, alignment, activate] + ); + + const onClick = useCallback(() => onActivated("click"), [onActivated]); + + const onContextMenu = useCallback( + (event: MouseEvent) => { + event.stopPropagation(); + event.nativeEvent.stopImmediatePropagation(); + return onActivated("contextmenu"); + }, + [onActivated] + ); + + return ( + + + + + + ); +} + +export function ExtensionsList({ setOpen }: { setOpen: (open: boolean) => void }) { + const { actions, activeTabId, partition } = useBrowserAction(); + const alignment = "right bottom" as const; + + const noActions = actions.length === 0; + const noActiveTab = typeof activeTabId !== "number"; + + if (noActiveTab || noActions) return null; + + return ( +
+ {actions.map((action) => ( + + ))} + { + event.stopPropagation(); + flow.tabs.newTab(CHROME_WEB_STORE_URL, true); + setOpen(false); + }} + > + + +
+ ); +} + +export function SiteControlExtensions({ setOpen }: { setOpen: (open: boolean) => void }) { + return ( +
+ {/* Title and Manage button */} +
+ Extensions + +
+ {/* Extensions list */} +
+ +
+
+ ); +} diff --git a/src/renderer/src/components/browser-ui/browser-sidebar/_components/site-controls/index.tsx b/src/renderer/src/components/browser-ui/browser-sidebar/_components/site-controls/index.tsx new file mode 100644 index 000000000..922a4ee03 --- /dev/null +++ b/src/renderer/src/components/browser-ui/browser-sidebar/_components/site-controls/index.tsx @@ -0,0 +1,65 @@ +import { useSpaces } from "@/components/providers/spaces-provider"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/portal/popover"; +import { cn } from "@/lib/utils"; +import { EllipsisIcon, LockIcon, Settings2Icon } from "lucide-react"; +import { useState } from "react"; +import { useFocusedTab } from "@/components/providers/tabs-provider"; +import { SiteControlExtensions } from "@/components/browser-ui/browser-sidebar/_components/site-controls/extensions"; +import { Separator } from "@/components/ui/separator"; +import { Button } from "@/components/ui/button"; + +// Main extensions popover for the new browser UI sidebar +export function SiteControls() { + const { isCurrentSpaceLight } = useSpaces(); + const focusedTab = useFocusedTab(); + const [open, setOpen] = useState(false); + + const spaceInjectedClasses = cn(isCurrentSpaceLight ? "" : "dark"); + + if (!focusedTab) return null; + return ( + + { + event.stopPropagation(); + }} + > +
+ +
+
+ + {/* Extensions Section */} + + + {/* Utilities Section */} +
+ + +
+
+
+ ); +} diff --git a/src/renderer/src/components/logic/bubble-event.tsx b/src/renderer/src/components/logic/bubble-event.tsx new file mode 100644 index 000000000..bd6f8f9ef --- /dev/null +++ b/src/renderer/src/components/logic/bubble-event.tsx @@ -0,0 +1,48 @@ +import { useEffect, type RefObject } from "react"; + +/** + * Forwards document-level events into a target element so that components + * scoped to that subtree receive events that would otherwise only fire on the + * document root. + * + * A listener is attached to the `ownerDocument` of `documentRef` (defaults to + * `targetRef`). When an event of `eventType` fires and its `target` is **not** + * inside `targetRef`, a cloned `KeyboardEvent` is dispatched on `targetRef` so + * the event bubbles through that subtree as if it originated there. + * + * Renders nothing — use it as a logic-only child inside any component tree. + * + * Mostly used for Portal + cmdk (command component) so it can receive up and down arrow keys. + * + * @example + * // Forward keyboard events from the document into a portalled overlay + * + */ +export function BubbleEvent({ + targetRef, + eventType, + documentRef = targetRef +}: { + targetRef: RefObject; + eventType: EventType; + documentRef?: RefObject; +}) { + useEffect(() => { + const document = documentRef.current?.ownerDocument; + if (!document) return; + + const bubbleTarget = targetRef.current; + if (!bubbleTarget) return; + + const handler = (event: DocumentEventMap[EventType]) => { + if (bubbleTarget.contains(event.target as Node)) return; + const cloned = new KeyboardEvent(event.type, event); + bubbleTarget.dispatchEvent(cloned); + }; + + document.addEventListener(eventType, handler); + return () => document.removeEventListener(eventType, handler); + }, [targetRef, eventType, documentRef]); + + return null; +} diff --git a/src/renderer/src/components/portal/popover.tsx b/src/renderer/src/components/portal/popover.tsx index bbb57c56a..f9d18a88d 100644 --- a/src/renderer/src/components/portal/popover.tsx +++ b/src/renderer/src/components/portal/popover.tsx @@ -1,23 +1,22 @@ -import { PortalComponent } from "@/components/portal/portal"; -import { Popover, PopoverContent } from "@/components/ui/popover"; import { useOptionalBrowserSidebar } from "@/components/browser-ui/browser-sidebar/provider"; +import { PortalComponent } from "@/components/portal/portal"; +import { Popover as BasePopover, PopoverContent as BasePopoverContent } from "@/components/ui/popover"; +import { type PopoverRootChangeEventDetails } from "@base-ui/react"; +import { createContext, useContext, useEffect, useId, useRef, useState } from "react"; import { ViewLayer } from "~/layers"; -import { createContext, useContext, useEffect, useId, useState } from "react"; -import { AnimatePresence, motion } from "motion/react"; -import { PopoverArrow } from "@radix-ui/react-popover"; -type PopoverContextType = { - open: boolean; - setOpen: ((open: boolean) => void) | undefined; -}; +export { PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger } from "@/components/ui/popover"; +interface PopoverContextType { + open: boolean; + setOpen: ((open: boolean, eventDetails: PopoverRootChangeEventDetails) => void) | undefined; +} const PopoverContext = createContext(undefined); - -function PortalPopoverRoot({ +export function Popover({ open: userOpen, onOpenChange: userSetOpen, ...props -}: React.ComponentProps) { +}: React.ComponentProps) { const [internalOpen, internalSetOpen] = useState(false); const useUser = userOpen !== undefined; @@ -36,35 +35,58 @@ function PortalPopoverRoot({ return ( - + ); } -function PortalPopoverContent({ children, ...props }: React.ComponentProps) { +/** + * Set open to true instantly, but wait the given delay before setting open to false to delay removing of the popover. + * @param value - The value to delay. + * @param delay - The delay in milliseconds. + * @returns The delayed value. + */ +function useDelayedOpenValue(value: boolean, delay: number): boolean { + const [delayedValue, setDelayedValue] = useState(value); + + useEffect(() => { + if (value === true) { + setDelayedValue(value); + return () => {}; + } else { + const timer = setTimeout(() => { + setDelayedValue(value); + }, delay); + + return () => { + clearTimeout(timer); + }; + } + }, [value, delay]); + + return delayedValue; +} + +export function PopoverContent({ ...props }: Omit, "portalContainer">) { const { open } = usePopover(); + const delayedOpen = useDelayedOpenValue(open, 200); + const portalContainerRef = useRef(null); + if (!delayedOpen) return null; return ( - - {open && ( - - - - - {children} - - - - )} - + <> + + + ); } -export const PortalPopover = { - Root: PortalPopoverRoot, - Content: PortalPopoverContent -}; - // Hook to use the popover context export const usePopover = () => { const context = useContext(PopoverContext); diff --git a/src/renderer/src/components/portal/portal.tsx b/src/renderer/src/components/portal/portal.tsx index 616306b31..6251e66c6 100644 --- a/src/renderer/src/components/portal/portal.tsx +++ b/src/renderer/src/components/portal/portal.tsx @@ -5,10 +5,11 @@ import { useCopyStyles } from "@/hooks/use-copy-styles"; import { mergeRefs } from "@/lib/merge-refs"; import { cn } from "@/lib/utils"; import { ViewLayer } from "~/layers"; -import { createContext, useContext, useEffect, useLayoutEffect, useMemo, useRef } from "react"; +import { createContext, RefObject, useContext, useEffect, useLayoutEffect, useMemo, useRef } from "react"; import { createPortal } from "react-dom"; interface PortalComponentProps extends React.ComponentProps<"div"> { + portalBodyRef?: RefObject; visible?: boolean; zIndex?: number; autoFocus?: boolean; @@ -40,6 +41,7 @@ export function PortalComponent({ className, children, ref, + portalBodyRef, ...args }: PortalComponentProps) { const { usePortal } = usePortalsProvider(); @@ -61,6 +63,13 @@ export function PortalComponent({ // Copy styles from parent window to portal window useCopyStyles(portal?.window ?? null); + // Keep portalBodyRef in sync with the portal window's document body + useEffect(() => { + if (!portal?.window) return; + if (!portalBodyRef) return; + portalBodyRef.current = portal.window.document.body; + }, [portal, portalBodyRef]); + const portalChildren = useMemo(() => { const contextValue: PortalContextValue = { x: bounds.x, diff --git a/src/renderer/src/components/ui/popover-listbox.tsx b/src/renderer/src/components/ui/popover-listbox.tsx deleted file mode 100644 index f1de0b19b..000000000 --- a/src/renderer/src/components/ui/popover-listbox.tsx +++ /dev/null @@ -1,204 +0,0 @@ -import { cn } from "@/lib/utils"; -import { createContext, useCallback, useContext, useEffect, useRef, useState, type HTMLAttributes } from "react"; - -export type UsePopoverListboxOptions = { - open: boolean; - itemCount: number; - ariaLabel: string; - getOptionId: (index: number) => string; - onActivate: (index: number) => void; - /** When true, ArrowDown from last item wraps to first (and vice versa). Default true. */ - wrap?: boolean; - /** Highlighted row when the list opens. Clamped to `itemCount - 1`. Default 0. */ - initialHighlightedIndex?: number; -}; - -export type PopoverListboxOptionProps = Pick< - HTMLAttributes, - "id" | "role" | "aria-selected" | "onMouseEnter" ->; - -export type PopoverListbox = ReturnType; - -const defaultListClassName = - "max-h-64 custom-scrollbar overflow-y-auto rounded-sm outline-none focus:outline-none focus-visible:outline-none"; - -type PopoverListboxContextType = { - highlightedIndex: number; - getOptionProps: (index: number) => PopoverListboxOptionProps; - onActivate: (index: number) => void; -}; - -const PopoverListboxContext = createContext(undefined); - -function usePopoverListboxContext() { - const ctx = useContext(PopoverListboxContext); - if (!ctx) throw new Error("PopoverListboxItem must be used within a PopoverListboxList"); - return ctx; -} - -export function usePopoverListbox({ - open, - itemCount, - ariaLabel, - getOptionId, - onActivate, - wrap = true, - initialHighlightedIndex = 0 -}: UsePopoverListboxOptions) { - const listRef = useRef(null); - const [highlightedIndex, setHighlightedIndex] = useState(0); - - const itemCountRef = useRef(itemCount); - itemCountRef.current = itemCount; - const highlightedIndexRef = useRef(highlightedIndex); - highlightedIndexRef.current = highlightedIndex; - const getOptionIdRef = useRef(getOptionId); - getOptionIdRef.current = getOptionId; - const onActivateRef = useRef(onActivate); - onActivateRef.current = onActivate; - - const wasOpenRef = useRef(false); - useEffect(() => { - if (open && !wasOpenRef.current) { - const n = itemCount; - const i = n <= 0 ? 0 : Math.min(Math.max(0, initialHighlightedIndex), n - 1); - setHighlightedIndex(i); - } - wasOpenRef.current = open; - }, [open, itemCount, initialHighlightedIndex]); - - useEffect(() => { - if (!open) return; - setHighlightedIndex((i) => Math.min(i, Math.max(0, itemCount - 1))); - }, [itemCount, open]); - - useEffect(() => { - if (!open || itemCount === 0 || highlightedIndex < 0 || highlightedIndex >= itemCount) return; - const id = getOptionIdRef.current(highlightedIndex); - const root = listRef.current?.ownerDocument ?? document; - root.getElementById(id)?.scrollIntoView({ block: "nearest" }); - }, [highlightedIndex, itemCount, open]); - - useEffect(() => { - if (!open) return; - const el = listRef.current; - if (!el) return; - const doc = el.ownerDocument; - - el.focus(); - - const onKeyDown = (e: KeyboardEvent) => { - const n = itemCountRef.current; - if (n === 0) return; - - if (e.key === "ArrowDown") { - e.preventDefault(); - if (wrap) { - setHighlightedIndex((i) => (i + 1) % n); - } else { - setHighlightedIndex((i) => Math.min(n - 1, i + 1)); - } - return; - } - if (e.key === "ArrowUp") { - e.preventDefault(); - if (wrap) { - setHighlightedIndex((i) => (i - 1 + n) % n); - } else { - setHighlightedIndex((i) => Math.max(0, i - 1)); - } - return; - } - if (e.key === "Enter") { - e.preventDefault(); - const i = highlightedIndexRef.current; - if (i >= 0 && i < itemCountRef.current) { - onActivateRef.current(i); - } - } - }; - - doc.addEventListener("keydown", onKeyDown); - return () => doc.removeEventListener("keydown", onKeyDown); - }, [open, wrap]); - - const activeDescendant = - itemCount > 0 && highlightedIndex >= 0 && highlightedIndex < itemCount ? getOptionId(highlightedIndex) : undefined; - - const listProps: HTMLAttributes = { - role: "listbox", - tabIndex: -1, - "aria-label": ariaLabel, - "aria-activedescendant": activeDescendant, - className: defaultListClassName - }; - - const getOptionProps = useCallback( - (index: number): PopoverListboxOptionProps => ({ - id: getOptionId(index), - role: "option", - "aria-selected": index === highlightedIndex, - onMouseEnter: () => setHighlightedIndex(index) - }), - [getOptionId, highlightedIndex] - ); - - const contentProps = { - onOpenAutoFocus: (event: Event) => event.preventDefault() - }; - - return { - listRef, - listProps, - highlightedIndex, - getOptionProps, - onActivate, - contentProps - }; -} - -export function PopoverListboxList({ - listbox, - className, - children -}: { - listbox: PopoverListbox; - className?: string; - children: React.ReactNode; -}) { - const { listRef, listProps, highlightedIndex, getOptionProps, onActivate } = listbox; - const { className: listClassName, ...rest } = listProps; - return ( - -
- {children} -
-
- ); -} - -export function PopoverListboxItem({ - index, - className, - children -}: { - index: number; - className?: string; - children: React.ReactNode; -}) { - const { highlightedIndex, getOptionProps, onActivate } = usePopoverListboxContext(); - return ( -
onActivate(index)} - className={cn( - "flex min-w-0 w-full items-center gap-2 truncate px-2 py-1.5 text-sm rounded-sm", - index === highlightedIndex ? "bg-accent" : "hover:bg-accent", - className - )} - > - {children} -
- ); -} diff --git a/src/renderer/src/components/ui/popover.tsx b/src/renderer/src/components/ui/popover.tsx index 11d6de119..6b6279067 100644 --- a/src/renderer/src/components/ui/popover.tsx +++ b/src/renderer/src/components/ui/popover.tsx @@ -1,44 +1,130 @@ +"use client"; + import * as React from "react"; -import { Popover as PopoverPrimitive } from "radix-ui"; +import { Popover as PopoverPrimitive } from "@base-ui/react/popover"; import { cn } from "@/lib/utils"; -import { Fragment } from "react"; -function Popover({ ...props }: React.ComponentProps) { +export type PopoverVariants = "default" | "translucent"; + +export function ArrowSvg({ variant, ...props }: React.ComponentProps<"svg"> & { variant: PopoverVariants }) { + return ( + + + {/* Light mode border */} + + {/* Dark mode border */} + + + ); +} + +function Popover({ ...props }: PopoverPrimitive.Root.Props) { return ; } -function PopoverTrigger({ ...props }: React.ComponentProps) { +function PopoverTrigger({ ...props }: PopoverPrimitive.Trigger.Props) { return ; } function PopoverContent({ className, align = "center", + alignOffset = 0, + side = "bottom", sideOffset = 4, - portal = true, + children, + variant = "default", + positionerClassName, + portalContainer, + arrow = true, ...props -}: React.ComponentProps & { portal?: boolean }) { - const Portal = portal ? PopoverPrimitive.Portal : Fragment; - +}: PopoverPrimitive.Popup.Props & + Pick & { + variant?: PopoverVariants; + positionerClassName?: string; + portalContainer?: React.ComponentProps["container"]; + arrow?: boolean; + }) { return ( - - + - + className={cn("isolate z-50", variant === "translucent" && "dark", positionerClassName)} + > + + {arrow && ( + + + + )} + {children} + + + ); } -function PopoverAnchor({ ...props }: React.ComponentProps) { - return ; +function PopoverHeader({ className, ...props }: React.ComponentProps<"div">) { + return
; +} + +function PopoverTitle({ className, ...props }: PopoverPrimitive.Title.Props) { + return ; +} + +function PopoverDescription({ className, ...props }: PopoverPrimitive.Description.Props) { + return ( + + ); } -export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }; +export { Popover, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger }; diff --git a/src/renderer/src/routes/extensions/page.tsx b/src/renderer/src/routes/extensions/page.tsx index c50eae9e0..e365efe55 100644 --- a/src/renderer/src/routes/extensions/page.tsx +++ b/src/renderer/src/routes/extensions/page.tsx @@ -11,7 +11,7 @@ import ExtensionDetails from "./components/extension-details"; import { ExtensionsProvider, useExtensions } from "@/components/providers/extensions-provider"; import { useQueryState } from "nuqs"; -const CHROME_WEB_STORE_URL = "https://chromewebstore.google.com/category/extensions?utm_source=ext_sidebar"; +export const CHROME_WEB_STORE_URL = "https://chromewebstore.google.com/category/extensions?utm_source=ext_sidebar"; function ExtensionsPage() { const [isDeveloperMode, setIsDeveloperMode] = useState(false);