mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-09 22:28:30 +00:00
feat(app): refactor help button and add tabs info popup persistence (#36066)
Co-authored-by: Aarav Sareen <96787824+arvsrn@users.noreply.github.com>
This commit is contained in:
parent
6b41ae910c
commit
c6c599b872
10 changed files with 287 additions and 46 deletions
16
bun.lock
16
bun.lock
|
|
@ -31,6 +31,7 @@
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.17.16",
|
"version": "1.17.16",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@corvu/drawer": "catalog:",
|
||||||
"@dnd-kit/abstract": "0.5.0",
|
"@dnd-kit/abstract": "0.5.0",
|
||||||
"@dnd-kit/dom": "0.5.0",
|
"@dnd-kit/dom": "0.5.0",
|
||||||
"@dnd-kit/helpers": "0.5.0",
|
"@dnd-kit/helpers": "0.5.0",
|
||||||
|
|
@ -1087,6 +1088,7 @@
|
||||||
},
|
},
|
||||||
"catalog": {
|
"catalog": {
|
||||||
"@cloudflare/workers-types": "4.20251008.0",
|
"@cloudflare/workers-types": "4.20251008.0",
|
||||||
|
"@corvu/drawer": "0.2.4",
|
||||||
"@effect/opentelemetry": "4.0.0-beta.83",
|
"@effect/opentelemetry": "4.0.0-beta.83",
|
||||||
"@effect/platform-node": "4.0.0-beta.83",
|
"@effect/platform-node": "4.0.0-beta.83",
|
||||||
"@effect/sql-sqlite-bun": "4.0.0-beta.83",
|
"@effect/sql-sqlite-bun": "4.0.0-beta.83",
|
||||||
|
|
@ -1468,6 +1470,10 @@
|
||||||
|
|
||||||
"@cloudflare/workers-types": ["@cloudflare/workers-types@4.20251008.0", "", {}, "sha512-dZLkO4PbCL0qcCSKzuW7KE4GYe49lI12LCfQ5y9XeSwgYBoAUbwH4gmJ6A0qUIURiTJTkGkRkhVPqpq2XNgYRA=="],
|
"@cloudflare/workers-types": ["@cloudflare/workers-types@4.20251008.0", "", {}, "sha512-dZLkO4PbCL0qcCSKzuW7KE4GYe49lI12LCfQ5y9XeSwgYBoAUbwH4gmJ6A0qUIURiTJTkGkRkhVPqpq2XNgYRA=="],
|
||||||
|
|
||||||
|
"@corvu/dialog": ["@corvu/dialog@0.2.4", "", { "dependencies": { "@corvu/utils": "~0.4.2", "solid-dismissible": "~0.1.1", "solid-focus-trap": "~0.1.8", "solid-presence": "~0.2.0", "solid-prevent-scroll": "~0.1.10" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-n54vJq+fOy8GVrnYBdJpD6JXNuyx7LOeMrRxwzAvZnYGpW8+AA12tnb/P/2emJj/HjOO5otheGKb0breshdFlA=="],
|
||||||
|
|
||||||
|
"@corvu/drawer": ["@corvu/drawer@0.2.4", "", { "dependencies": { "@corvu/dialog": "~0.2.4", "@corvu/utils": "~0.4.2", "@solid-primitives/memo": "^1.4.1", "solid-transition-size": "~0.1.4" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-7jQoGZ8ROB9CmXam2nMY2wEskU3IoFwZQywkF/7vrBc/edGsPv7mOVQ1GN6G+4nd7nrMZ3UtqHAhmRh9V0azlw=="],
|
||||||
|
|
||||||
"@corvu/utils": ["@corvu/utils@0.4.2", "", { "dependencies": { "@floating-ui/dom": "^1.6.11" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-Ox2kYyxy7NoXdKWdHeDEjZxClwzO4SKM8plAaVwmAJPxHMqA0rLOoAsa+hBDwRLpctf+ZRnAd/ykguuJidnaTA=="],
|
"@corvu/utils": ["@corvu/utils@0.4.2", "", { "dependencies": { "@floating-ui/dom": "^1.6.11" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-Ox2kYyxy7NoXdKWdHeDEjZxClwzO4SKM8plAaVwmAJPxHMqA0rLOoAsa+hBDwRLpctf+ZRnAd/ykguuJidnaTA=="],
|
||||||
|
|
||||||
"@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
|
"@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
|
||||||
|
|
@ -2668,6 +2674,8 @@
|
||||||
|
|
||||||
"@solid-primitives/media": ["@solid-primitives/media@2.3.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-hQ4hLOGvfbugQi5Eu1BFWAIJGIAzztq9x0h02xgBGl2l0Jaa3h7tg6bz5tV1NSuNYVGio4rPoa7zVQQLkkx9dA=="],
|
"@solid-primitives/media": ["@solid-primitives/media@2.3.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-hQ4hLOGvfbugQi5Eu1BFWAIJGIAzztq9x0h02xgBGl2l0Jaa3h7tg6bz5tV1NSuNYVGio4rPoa7zVQQLkkx9dA=="],
|
||||||
|
|
||||||
|
"@solid-primitives/memo": ["@solid-primitives/memo@1.5.0", "", { "dependencies": { "@solid-primitives/scheduled": "^1.5.3", "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-nSMHpFdnOMP88t7lqtktUXJlhQdJk0BQs2v3jUqcn+OtFwUm27Oa/coKl1BHDxL/65jR5drjqxxvuhaIpiUi0w=="],
|
||||||
|
|
||||||
"@solid-primitives/props": ["@solid-primitives/props@3.2.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-XzG6en9gSFwmvbKcATm2BxL63HegZ+BAG5fmHi8jyBppQHcaths7ffz+6vYvwYy3nlgLa20ufJLj7tst+PcHFA=="],
|
"@solid-primitives/props": ["@solid-primitives/props@3.2.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-XzG6en9gSFwmvbKcATm2BxL63HegZ+BAG5fmHi8jyBppQHcaths7ffz+6vYvwYy3nlgLa20ufJLj7tst+PcHFA=="],
|
||||||
|
|
||||||
"@solid-primitives/refs": ["@solid-primitives/refs@1.1.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-aam02fjNKpBteewF/UliPSQCVJsIIGOLEWQOh+ll6R/QePzBOOBMcC4G+5jTaO75JuUS1d/14Q1YXT3X0Ow6iA=="],
|
"@solid-primitives/refs": ["@solid-primitives/refs@1.1.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-aam02fjNKpBteewF/UliPSQCVJsIIGOLEWQOh+ll6R/QePzBOOBMcC4G+5jTaO75JuUS1d/14Q1YXT3X0Ow6iA=="],
|
||||||
|
|
@ -5100,6 +5108,10 @@
|
||||||
|
|
||||||
"socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="],
|
"socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="],
|
||||||
|
|
||||||
|
"solid-dismissible": ["solid-dismissible@0.1.1", "", { "dependencies": { "@corvu/utils": "~0.4.1" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-9kcKBJIMdS+586cA1g63HYWxKh3h89leeNHbPZ1csYjuni+NvPBtNr11l0iEX2AKKEt6FHk6qNhc/gjoYAW1pA=="],
|
||||||
|
|
||||||
|
"solid-focus-trap": ["solid-focus-trap@0.1.9", "", { "dependencies": { "@corvu/utils": "~0.4.2" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-LTyNki6GUJPRLXV5uMWPkYClB07SUMubbr2EkAddiR0CJCF/I283txilMU9RURSr/P8EewMfXWu2o3aWrK7A5A=="],
|
||||||
|
|
||||||
"solid-js": ["solid-js@1.9.10", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", "seroval-plugins": "~1.3.0" } }, "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew=="],
|
"solid-js": ["solid-js@1.9.10", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", "seroval-plugins": "~1.3.0" } }, "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew=="],
|
||||||
|
|
||||||
"solid-list": ["solid-list@0.3.0", "", { "dependencies": { "@corvu/utils": "~0.4.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-t4hx/F/l8Vmq+ib9HtZYl7Z9F1eKxq3eKJTXlvcm7P7yI4Z8O7QSOOEVHb/K6DD7M0RxzVRobK/BS5aSfLRwKg=="],
|
"solid-list": ["solid-list@0.3.0", "", { "dependencies": { "@corvu/utils": "~0.4.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-t4hx/F/l8Vmq+ib9HtZYl7Z9F1eKxq3eKJTXlvcm7P7yI4Z8O7QSOOEVHb/K6DD7M0RxzVRobK/BS5aSfLRwKg=="],
|
||||||
|
|
@ -5112,6 +5124,8 @@
|
||||||
|
|
||||||
"solid-stripe": ["solid-stripe@0.8.1", "", { "peerDependencies": { "@stripe/stripe-js": ">=1.44.1 <8.0.0", "solid-js": "^1.6.0" } }, "sha512-l2SkWoe51rsvk9u1ILBRWyCHODZebChSGMR6zHYJTivTRC0XWrRnNNKs5x1PYXsaIU71KYI6ov5CZB5cOtGLWw=="],
|
"solid-stripe": ["solid-stripe@0.8.1", "", { "peerDependencies": { "@stripe/stripe-js": ">=1.44.1 <8.0.0", "solid-js": "^1.6.0" } }, "sha512-l2SkWoe51rsvk9u1ILBRWyCHODZebChSGMR6zHYJTivTRC0XWrRnNNKs5x1PYXsaIU71KYI6ov5CZB5cOtGLWw=="],
|
||||||
|
|
||||||
|
"solid-transition-size": ["solid-transition-size@0.1.4", "", { "dependencies": { "@corvu/utils": "~0.3.2" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-ocHVnbfy23CgfaH4cEUR/AFg0Y3CEL8Oh3n9Qv8OHFJgPh+zkmERKZQfi/xH5XvxDCizg8VjPrVUhiHB1Gza8g=="],
|
||||||
|
|
||||||
"solid-use": ["solid-use@0.9.1", "", { "peerDependencies": { "solid-js": "^1.7" } }, "sha512-UwvXDVPlrrbj/9ewG9ys5uL2IO4jSiwys2KPzK4zsnAcmEl7iDafZWW1Mo4BSEWOmQCGK6IvpmGHo1aou8iOFw=="],
|
"solid-use": ["solid-use@0.9.1", "", { "peerDependencies": { "solid-js": "^1.7" } }, "sha512-UwvXDVPlrrbj/9ewG9ys5uL2IO4jSiwys2KPzK4zsnAcmEl7iDafZWW1Mo4BSEWOmQCGK6IvpmGHo1aou8iOFw=="],
|
||||||
|
|
||||||
"sonic-boom": ["sonic-boom@4.2.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="],
|
"sonic-boom": ["sonic-boom@4.2.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="],
|
||||||
|
|
@ -6450,6 +6464,8 @@
|
||||||
|
|
||||||
"slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="],
|
"slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="],
|
||||||
|
|
||||||
|
"solid-transition-size/@corvu/utils": ["@corvu/utils@0.3.2", "", { "dependencies": { "@floating-ui/dom": "^1.6.7" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-ZWlyWEE8qV9+CB9OAyo2bTrZGXQN9ZeM+JfYv89zoR+lRACKTDuoOZEdiyL8Uc7U5dUSH1uTqKhTTnaHWb+wZA=="],
|
||||||
|
|
||||||
"sort-keys/is-plain-obj": ["is-plain-obj@1.1.0", "", {}, "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg=="],
|
"sort-keys/is-plain-obj": ["is-plain-obj@1.1.0", "", {}, "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg=="],
|
||||||
|
|
||||||
"sst/aws4fetch": ["aws4fetch@1.0.18", "", {}, "sha512-3Cf+YaUl07p24MoQ46rFwulAmiyCwH2+1zw1ZyPAX5OtJ34Hh185DwB8y/qRLb6cYYYtSFJ9pthyLc0MD4e8sQ=="],
|
"sst/aws4fetch": ["aws4fetch@1.0.18", "", {}, "sha512-3Cf+YaUl07p24MoQ46rFwulAmiyCwH2+1zw1ZyPAX5OtJ34Hh185DwB8y/qRLb6cYYYtSFJ9pthyLc0MD4e8sQ=="],
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@
|
||||||
"@shikijs/stream": "4.2.0",
|
"@shikijs/stream": "4.2.0",
|
||||||
"ulid": "3.0.1",
|
"ulid": "3.0.1",
|
||||||
"@kobalte/core": "0.13.11",
|
"@kobalte/core": "0.13.11",
|
||||||
|
"@corvu/drawer": "0.2.4",
|
||||||
"@types/luxon": "3.7.1",
|
"@types/luxon": "3.7.1",
|
||||||
"@types/node": "24.12.2",
|
"@types/node": "24.12.2",
|
||||||
"@types/semver": "7.7.1",
|
"@types/semver": "7.7.1",
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@
|
||||||
"vite-plugin-solid": "catalog:"
|
"vite-plugin-solid": "catalog:"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@corvu/drawer": "catalog:",
|
||||||
"@dnd-kit/abstract": "0.5.0",
|
"@dnd-kit/abstract": "0.5.0",
|
||||||
"@dnd-kit/dom": "0.5.0",
|
"@dnd-kit/dom": "0.5.0",
|
||||||
"@dnd-kit/helpers": "0.5.0",
|
"@dnd-kit/helpers": "0.5.0",
|
||||||
|
|
|
||||||
BIN
packages/app/src/assets/help/introducing-tabs.mp4
Normal file
BIN
packages/app/src/assets/help/introducing-tabs.mp4
Normal file
Binary file not shown.
BIN
packages/app/src/assets/help/placeholder.png
Normal file
BIN
packages/app/src/assets/help/placeholder.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
|
|
@ -1,54 +1,144 @@
|
||||||
import { Icon } from "@opencode-ai/ui/v2/icon"
|
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
||||||
import { Popover } from "@opencode-ai/ui/popover"
|
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
||||||
import { createSignal, Show } from "solid-js"
|
import { createSignal, Show } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
|
import { Drawer, DrawerClose, DrawerContent } from "@/components/ui/drawer"
|
||||||
|
import { usePlatform } from "@/context/platform"
|
||||||
|
import introducingTabsVideo from "@/assets/help/introducing-tabs.mp4"
|
||||||
|
import { Persist, persisted } from "@/utils/persist"
|
||||||
|
|
||||||
|
const helpIcon = (
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
aria-hidden="true"
|
||||||
|
data-slot="icon-svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M6.94235 10.5714V10.4854C6.94617 9.76302 7.01879 9.18777 7.16022 8.75968C7.30546 8.33158 7.50804 7.98567 7.76796 7.72193C8.02787 7.45819 8.34321 7.21548 8.71397 6.99379C8.93948 6.85619 9.14206 6.69374 9.32171 6.50645C9.50518 6.31916 9.64851 6.10511 9.75171 5.86431C9.85874 5.62351 9.91225 5.35404 9.91225 5.0559C9.91225 4.69661 9.82625 4.38509 9.65424 4.12136C9.48607 3.85762 9.26055 3.65504 8.9777 3.51362C8.69486 3.36837 8.38143 3.29575 8.03743 3.29575C7.73165 3.29575 7.43733 3.35882 7.15448 3.48495C6.87546 3.61108 6.6423 3.80984 6.45501 4.08122C6.26772 4.3526 6.15878 4.70425 6.12821 5.13617H4.56299C4.59357 4.47109 4.76557 3.9054 5.07899 3.43908C5.39242 2.96894 5.80522 2.61156 6.31741 2.36694C6.83341 2.12231 7.40675 2 8.03743 2C8.72161 2 9.31789 2.13378 9.82625 2.40134C10.3384 2.66507 10.734 3.0301 11.0131 3.49642C11.2959 3.96273 11.4373 4.49976 11.4373 5.1075C11.4373 5.53177 11.3724 5.914 11.2424 6.25418C11.1124 6.59436 10.9251 6.89823 10.6805 7.16579C10.4397 7.43335 10.1492 7.67033 9.80905 7.87673C9.48033 8.08313 9.21468 8.301 9.0121 8.53034C8.80952 8.75585 8.66237 9.02341 8.57063 9.33302C8.4789 9.64262 8.42921 10.0268 8.42156 10.4854V10.5714H6.94235ZM7.72782 14C7.43351 14 7.17933 13.8949 6.96528 13.6847C6.75506 13.4744 6.64994 13.2203 6.64994 12.9221C6.64994 12.6278 6.75506 12.3755 6.96528 12.1653C7.17933 11.9551 7.43351 11.85 7.72782 11.85C8.02214 11.85 8.27441 11.9551 8.48463 12.1653C8.69868 12.3755 8.8057 12.6278 8.8057 12.9221C8.8057 13.1209 8.75601 13.3024 8.65663 13.4668C8.55726 13.6273 8.4273 13.7573 8.26676 13.8567C8.10623 13.9522 7.92658 14 7.72782 14Z"
|
||||||
|
fill="var(--v2-icon-icon-base)"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
|
||||||
|
const triggerClass =
|
||||||
|
"size-7 !rounded-full shrink-0 bg-v2-background-bg-base shadow-[var(--v2-elevation-button-neutral)]"
|
||||||
|
|
||||||
|
// TODO: wire to changelog / seen-state when available
|
||||||
|
const showPopover = () => true
|
||||||
|
|
||||||
export function HelpButton() {
|
export function HelpButton() {
|
||||||
if (import.meta.env.VITE_OPENCODE_CHANNEL !== "dev") return null
|
if (import.meta.env.VITE_OPENCODE_CHANNEL !== "dev") return null
|
||||||
|
|
||||||
const [state, setState] = /* persisted(Persist.global("help-button"), */ createStore({ dismissed: false }) /* ) */
|
const platform = usePlatform()
|
||||||
const [shown, setShown] = createSignal(false)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Show when={!state.dismissed}>
|
<a
|
||||||
<div class="fixed bottom-4 right-4 z-50 hidden md:block">
|
href="https://opencode.ai"
|
||||||
<Popover
|
aria-label="Open the OpenCode website"
|
||||||
open={shown()}
|
data-component="icon-button-v2"
|
||||||
onOpenChange={setShown}
|
data-size="large"
|
||||||
triggerAs="button"
|
class={`${triggerClass} fixed bottom-5 right-5 z-50 flex items-center justify-center`}
|
||||||
triggerProps={{
|
onClick={(event) => {
|
||||||
type: "button",
|
event.preventDefault()
|
||||||
"aria-label": "Help",
|
platform.openLink(event.currentTarget.href)
|
||||||
class:
|
|
||||||
"size-7 rounded-full bg-background-base shadow-[var(--shadow-lg-border-base)] flex items-center justify-center text-text-base hover:text-text-strong transition-colors",
|
|
||||||
}}
|
|
||||||
trigger={<span aria-hidden="true">?</span>}
|
|
||||||
class="[&_[data-slot=popover-body]]:p-0 w-[320px] max-w-[calc(100vw-40px)] bg-transparent border-0 shadow-none rounded-xl"
|
|
||||||
gutter={8}
|
|
||||||
placement="top-end"
|
|
||||||
>
|
|
||||||
<Show when={shown()}>
|
|
||||||
<div class="relative flex flex-col gap-1 w-[320px] p-4 rounded-xl bg-background-strong shadow-[var(--shadow-lg-border-base)]">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
aria-label="Close"
|
|
||||||
class="absolute top-3.5 right-3.5 size-6 rounded-md flex items-center justify-center text-text-base hover:text-text-strong hover:bg-surface-raised-base-hover transition-colors"
|
|
||||||
onClick={() => {
|
|
||||||
setShown(false)
|
|
||||||
setState("dismissed", true)
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon name="xmark-small" />
|
{helpIcon}
|
||||||
</button>
|
</a>
|
||||||
<span class="text-14-regular text-text-strong">Lorem ipsum dolor sit amet</span>
|
)
|
||||||
<p class="text-12-regular text-text-weak">
|
}
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et
|
|
||||||
dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation.
|
// can remove this after the tabs rollout has been out for a while
|
||||||
</p>
|
export function TabsInfoPopup() {
|
||||||
</div>
|
if (import.meta.env.VITE_OPENCODE_CHANNEL !== "dev") return null
|
||||||
</Show>
|
|
||||||
</Popover>
|
const [state, setState] = persisted(Persist.global("tabsInfoPopup"), createStore({ dismissed: false }))
|
||||||
</div>
|
// setState({ dismissed: false }) // for testing
|
||||||
</Show>
|
const [drawerOpen, setDrawerOpen] = createSignal(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer open={drawerOpen()} onOpenChange={setDrawerOpen} side="right">
|
||||||
|
<Show when={!state.dismissed}>
|
||||||
|
<div
|
||||||
|
class="fixed bottom-14 right-5 z-50 h-[240px] w-[192px] rounded-[8px] bg-v2-background-bg-base p-1 shadow-[var(--v2-elevation-floating)]"
|
||||||
|
aria-label="Introducing Tabs. A faster, more intuitive way to work."
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label="Dismiss Tabs information"
|
||||||
|
class="absolute top-3 right-3 z-10 size-5 flex items-center justify-center rounded-[4px] bg-[rgba(0,0,0,0.4)]"
|
||||||
|
onClick={() => setState("dismissed", true)}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path d="M4.25 11.75L11.75 4.25M11.75 11.75L4.25 4.25" stroke="white" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="relative block h-[232px] w-[184px] cursor-pointer overflow-hidden rounded-[4px] text-left"
|
||||||
|
onClick={() => {
|
||||||
|
setState("dismissed", true)
|
||||||
|
setDrawerOpen(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<video
|
||||||
|
src={introducingTabsVideo}
|
||||||
|
class="absolute inset-0 h-full w-full object-cover"
|
||||||
|
loop
|
||||||
|
muted
|
||||||
|
autoplay
|
||||||
|
playsinline
|
||||||
|
aria-hidden="true"
|
||||||
|
onContextMenu={(event) => event.preventDefault()}
|
||||||
|
/>
|
||||||
|
<div class="absolute inset-x-0 bottom-0 flex w-full flex-col items-start gap-1.5 bg-[linear-gradient(180deg,rgba(0,0,0,0)_0%,#000000_100%)] px-3 py-5">
|
||||||
|
<p class="w-full select-none text-[13px] font-[530] leading-none tracking-[-0.04px] text-[#FFFFFF]">
|
||||||
|
Introducing Tabs
|
||||||
|
</p>
|
||||||
|
<p class="w-full select-none text-[13px] font-[440] leading-[140%] tracking-[-0.04px] text-[#808080]">
|
||||||
|
A faster, more intuitive way to work.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
<DrawerContent>
|
||||||
|
<div class="flex h-[52px] w-full shrink-0 items-center gap-4 self-stretch border-b border-v2-border-border-muted p-4">
|
||||||
|
<p class="min-h-0 min-w-0 flex-1 text-[13px] font-[530] leading-5 tracking-[-0.04px] tabular-nums text-v2-text-text-muted">
|
||||||
|
June 16
|
||||||
|
</p>
|
||||||
|
<DrawerClose
|
||||||
|
as={IconButtonV2}
|
||||||
|
type="button"
|
||||||
|
size="small"
|
||||||
|
variant="ghost-muted"
|
||||||
|
aria-label="Close"
|
||||||
|
icon={<IconV2 name="xmark-small" />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="relative flex w-full flex-col items-start gap-6 p-8">
|
||||||
|
<p class="w-full shrink-0 self-stretch text-[21px] font-[610] leading-6 tracking-[-0.37px] tabular-nums text-v2-text-text-base">
|
||||||
|
Introducing Tabs Navigation.
|
||||||
|
</p>
|
||||||
|
<p class="w-full flex-1 text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-base">
|
||||||
|
We've introduced tabs as the primary navigation in OpenCode. Your most important session are now pinned at
|
||||||
|
the top of your screen at all times. No more hunting through menus or losing your place mid-session. Switch
|
||||||
|
contexts instantly, pick up exactly where you left off, and keep your focus where it belongs: on the
|
||||||
|
sessions.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</DrawerContent>
|
||||||
|
</Drawer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
121
packages/app/src/components/ui/drawer.tsx
Normal file
121
packages/app/src/components/ui/drawer.tsx
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
/**
|
||||||
|
* Taken from https://www.solid-ui.com/docs/components/drawer
|
||||||
|
* Only used in one place hence not a v2 component yet... can be promoted to ui/v2 later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Component, ComponentProps, JSX, ValidComponent } from "solid-js"
|
||||||
|
import { splitProps } from "solid-js"
|
||||||
|
import type { ContentProps, DescriptionProps, DynamicProps, LabelProps, OverlayProps } from "@corvu/drawer"
|
||||||
|
import DrawerPrimitive from "@corvu/drawer"
|
||||||
|
|
||||||
|
const Drawer = DrawerPrimitive
|
||||||
|
|
||||||
|
const DrawerTrigger = DrawerPrimitive.Trigger
|
||||||
|
|
||||||
|
const DrawerPortal = DrawerPrimitive.Portal
|
||||||
|
|
||||||
|
const DrawerClose = DrawerPrimitive.Close
|
||||||
|
|
||||||
|
type DrawerOverlayProps<T extends ValidComponent = "div"> = OverlayProps<T> & { class?: string }
|
||||||
|
|
||||||
|
const DrawerOverlay = <T extends ValidComponent = "div">(props: DynamicProps<T, DrawerOverlayProps<T>>) => {
|
||||||
|
const [, rest] = splitProps(props as DrawerOverlayProps, ["class"])
|
||||||
|
const drawerContext = DrawerPrimitive.useContext()
|
||||||
|
const overlayStyle = () => {
|
||||||
|
const state = drawerContext.transitionState()
|
||||||
|
if (state === "opening" || state === "closing") return undefined
|
||||||
|
const open = drawerContext.openPercentage()
|
||||||
|
return {
|
||||||
|
opacity: open,
|
||||||
|
"backdrop-filter": `blur(${4 * open}px)`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<DrawerPrimitive.Overlay
|
||||||
|
class={props.class}
|
||||||
|
classList={{
|
||||||
|
"fixed inset-0 z-[100] bg-v2-overlay-simple-overlay-scrim opacity-0 backdrop-blur-none transition-[opacity,backdrop-filter] duration-300 data-[opening]:opacity-100 data-[opening]:backdrop-blur-[4px] data-[closing]:opacity-0 data-[closing]:backdrop-blur-none": true,
|
||||||
|
}}
|
||||||
|
style={overlayStyle()}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DrawerContentProps<T extends ValidComponent = "div"> = ContentProps<T> & {
|
||||||
|
class?: string
|
||||||
|
children?: JSX.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
const DrawerContent = <T extends ValidComponent = "div">(props: DynamicProps<T, DrawerContentProps<T>>) => {
|
||||||
|
const [, rest] = splitProps(props as DrawerContentProps, ["class", "children"])
|
||||||
|
return (
|
||||||
|
<DrawerPortal>
|
||||||
|
<DrawerOverlay />
|
||||||
|
<DrawerPrimitive.Content
|
||||||
|
class={props.class}
|
||||||
|
classList={{
|
||||||
|
"group/drawer-content fixed inset-y-[6px] right-[6px] left-auto z-[100] flex h-auto max-h-[calc(100vh-12px)] w-[560px] max-w-[calc(100vw-12px)] flex-col items-start rounded-[8px] bg-v2-background-bg-base p-0 shadow-[var(--v2-elevation-overlay)] data-[transitioning]:transition-transform data-[transitioning]:duration-300 md:select-none": true,
|
||||||
|
}}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</DrawerPrimitive.Content>
|
||||||
|
</DrawerPortal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const DrawerHeader: Component<ComponentProps<"div">> = (props) => {
|
||||||
|
const [, rest] = splitProps(props, ["class"])
|
||||||
|
return <div class={props.class} classList={{ "grid gap-1.5 p-4 text-center sm:text-left": true }} {...rest} />
|
||||||
|
}
|
||||||
|
|
||||||
|
const DrawerFooter: Component<ComponentProps<"div">> = (props) => {
|
||||||
|
const [, rest] = splitProps(props, ["class"])
|
||||||
|
return <div class={props.class} classList={{ "mt-auto flex flex-col gap-2 p-4": true }} {...rest} />
|
||||||
|
}
|
||||||
|
|
||||||
|
type DrawerTitleProps<T extends ValidComponent = "div"> = LabelProps<T> & { class?: string }
|
||||||
|
|
||||||
|
const DrawerTitle = <T extends ValidComponent = "div">(props: DynamicProps<T, DrawerTitleProps<T>>) => {
|
||||||
|
const [, rest] = splitProps(props as DrawerTitleProps, ["class"])
|
||||||
|
return (
|
||||||
|
<DrawerPrimitive.Label
|
||||||
|
class={props.class}
|
||||||
|
classList={{ "text-base font-[530] leading-none tracking-[-0.04px] text-v2-text-text-base": true }}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DrawerDescriptionProps<T extends ValidComponent = "div"> = DescriptionProps<T> & {
|
||||||
|
class?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const DrawerDescription = <T extends ValidComponent = "div">(
|
||||||
|
props: DynamicProps<T, DrawerDescriptionProps<T>>,
|
||||||
|
) => {
|
||||||
|
const [, rest] = splitProps(props as DrawerDescriptionProps, ["class"])
|
||||||
|
return (
|
||||||
|
<DrawerPrimitive.Description
|
||||||
|
class={props.class}
|
||||||
|
classList={{
|
||||||
|
"text-[13px] font-[440] leading-[140%] tracking-[-0.04px] text-v2-text-text-muted": true,
|
||||||
|
}}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Drawer,
|
||||||
|
DrawerPortal,
|
||||||
|
DrawerOverlay,
|
||||||
|
DrawerTrigger,
|
||||||
|
DrawerClose,
|
||||||
|
DrawerContent,
|
||||||
|
DrawerHeader,
|
||||||
|
DrawerFooter,
|
||||||
|
DrawerTitle,
|
||||||
|
DrawerDescription,
|
||||||
|
}
|
||||||
10
packages/app/src/env.d.ts
vendored
10
packages/app/src/env.d.ts
vendored
|
|
@ -12,6 +12,16 @@ interface ImportMeta {
|
||||||
readonly env: ImportMetaEnv
|
readonly env: ImportMetaEnv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module "*.png" {
|
||||||
|
const src: string
|
||||||
|
export default src
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "*.mp4" {
|
||||||
|
const src: string
|
||||||
|
export default src
|
||||||
|
}
|
||||||
|
|
||||||
export declare module "solid-js" {
|
export declare module "solid-js" {
|
||||||
namespace JSX {
|
namespace JSX {
|
||||||
interface Directives {
|
interface Directives {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { createEffect, Suspense, type ParentProps } from "solid-js"
|
import { createEffect, Suspense, type ParentProps } from "solid-js"
|
||||||
import { useNavigate } from "@solidjs/router"
|
import { useNavigate } from "@solidjs/router"
|
||||||
import { DebugBar } from "@/components/debug-bar"
|
import { DebugBar } from "@/components/debug-bar"
|
||||||
import { HelpButton } from "@/components/help-button"
|
import { HelpButton, TabsInfoPopup } from "@/components/help-button"
|
||||||
import { Titlebar, type TitlebarUpdate } from "@/components/titlebar"
|
import { Titlebar, type TitlebarUpdate } from "@/components/titlebar"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
import { setNavigate } from "@/utils/notification-click"
|
import { setNavigate } from "@/utils/notification-click"
|
||||||
|
|
@ -39,6 +39,7 @@ export default function NewLayout(props: ParentProps) {
|
||||||
<Suspense>{props.children}</Suspense>
|
<Suspense>{props.children}</Suspense>
|
||||||
</main>
|
</main>
|
||||||
{import.meta.env.DEV && <DebugBar inline />}
|
{import.meta.env.DEV && <DebugBar inline />}
|
||||||
|
<TabsInfoPopup />
|
||||||
<HelpButton />
|
<HelpButton />
|
||||||
<ToastRegion v2 />
|
<ToastRegion v2 />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme/context"
|
||||||
import { useCommand, type CommandOption } from "@/context/command"
|
import { useCommand, type CommandOption } from "@/context/command"
|
||||||
import { ConstrainDragXAxis, getDraggableId } from "@/utils/solid-dnd"
|
import { ConstrainDragXAxis, getDraggableId } from "@/utils/solid-dnd"
|
||||||
import { DebugBar } from "@/components/debug-bar"
|
import { DebugBar } from "@/components/debug-bar"
|
||||||
import { HelpButton } from "@/components/help-button"
|
import { HelpButton, TabsInfoPopup } from "@/components/help-button"
|
||||||
import { Titlebar, type TitlebarUpdate } from "@/components/titlebar"
|
import { Titlebar, type TitlebarUpdate } from "@/components/titlebar"
|
||||||
import { useDirectoryPicker } from "@/components/directory-picker"
|
import { useDirectoryPicker } from "@/components/directory-picker"
|
||||||
import { ServerConnection, useServer } from "@/context/server"
|
import { ServerConnection, useServer } from "@/context/server"
|
||||||
|
|
@ -2395,6 +2395,7 @@ export default function LegacyLayout(props: ParentProps) {
|
||||||
</div>
|
</div>
|
||||||
{import.meta.env.DEV && import.meta.env.VITE_DISABLE_DEBUG_BAR !== "1" && <DebugBar />}
|
{import.meta.env.DEV && import.meta.env.VITE_DISABLE_DEBUG_BAR !== "1" && <DebugBar />}
|
||||||
</div>
|
</div>
|
||||||
|
<TabsInfoPopup />
|
||||||
<HelpButton />
|
<HelpButton />
|
||||||
<ToastRegion v2={false} />
|
<ToastRegion v2={false} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue