Browse Source

wip: polish to shortcuts

David Hill 4 months ago
parent
commit
3ff6ce5967

+ 3 - 3
packages/app/src/components/prompt-input.tsx

@@ -44,7 +44,7 @@ import { ImagePreview } from "@opencode-ai/ui/image-preview"
 import { ModelSelectorPopover } from "@/components/dialog-select-model"
 import { DialogSelectModelUnpaid } from "@/components/dialog-select-model-unpaid"
 import { useProviders } from "@/hooks/use-providers"
-import { useCommand } from "@/context/command"
+import { formatKeybind, useCommand } from "@/context/command"
 import { Persist, persisted } from "@/utils/persist"
 import { Identifier } from "@/utils/id"
 import { SessionContextUsage } from "@/components/session-context-usage"
@@ -1400,8 +1400,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                             custom
                           </span>
                         </Show>
-                        <Show when={command.keybind(cmd.id)}>
-                          <span class="text-12-regular text-text-subtle">{command.keybind(cmd.id)}</span>
+                        <Show when={cmd.keybind}>
+                          <span class="text-12-regular text-text-subtle">{formatKeybind(cmd.keybind!)}</span>
                         </Show>
                       </div>
                     </button>

+ 17 - 4
packages/app/src/components/shortcuts-panel.tsx

@@ -3,7 +3,7 @@ import { Tabs } from "@opencode-ai/ui/tabs"
 import { Icon } from "@opencode-ai/ui/icon"
 import { IconButton } from "@opencode-ai/ui/icon-button"
 import { Tooltip } from "@opencode-ai/ui/tooltip"
-import { parseKeybind } from "@/context/command"
+import { parseKeybind, formatKeybind } from "@/context/command"
 
 const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform)
 
@@ -163,10 +163,14 @@ function ShortcutItem(props: { shortcut: Shortcut }) {
             <For each={getKeyChars(props.shortcut.keybind)}>
               {(char) => {
                 const tooltip = SPECIAL_CHAR_NAMES[char]
+                const isSpecial = tooltip && !isLetter(char)
+                const isShift = char === "⇧"
                 return (
-                  <Show when={tooltip && !isLetter(char)} fallback={<kbd class="shortcut-key">{char}</kbd>}>
+                  <Show when={isSpecial} fallback={<kbd class="shortcut-key">{char}</kbd>}>
                     <Tooltip value={tooltip} placement="top">
-                      <kbd class="shortcut-key">{char}</kbd>
+                      <kbd class="shortcut-key shortcut-key-special">
+                        <span classList={{ "shortcut-key-shift": isShift }}>{char}</span>
+                      </kbd>
                     </Tooltip>
                   </Show>
                 )
@@ -197,7 +201,16 @@ export function ShortcutsPanel(props: { onClose: () => void }) {
               {(category) => <Tabs.Trigger value={category.name}>{category.name}</Tabs.Trigger>}
             </For>
           </Tabs.List>
-          <IconButton icon="close" variant="ghost" onClick={props.onClose} />
+          <Tooltip
+            placement="top"
+            value={
+              <span>
+                Close shortcuts <span class="text-text-weak">{formatKeybind("ctrl+/")}</span>
+              </span>
+            }
+          >
+            <IconButton icon="close" variant="ghost" onClick={props.onClose} />
+          </Tooltip>
         </div>
         <For each={SHORTCUT_CATEGORIES}>
           {(category) => (

+ 15 - 1
packages/app/src/context/layout.tsx

@@ -1,5 +1,5 @@
 import { createStore, produce } from "solid-js/store"
-import { batch, createEffect, createMemo, onCleanup, onMount } from "solid-js"
+import { batch, createEffect, createMemo, createSignal, onCleanup, onMount } from "solid-js"
 import { createSimpleContext } from "@opencode-ai/ui/context"
 import { useGlobalSync } from "./global-sync"
 import { useGlobalSDK } from "./global-sdk"
@@ -93,6 +93,8 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
       }),
     )
 
+    const [shortcutsOpened, setShortcutsOpened] = createSignal(false)
+
     const MAX_SESSION_KEYS = 50
     const meta = { active: undefined as string | undefined, pruned: false }
     const used = new Map<string, number>()
@@ -353,6 +355,18 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
           setStore("review", "diffStyle", diffStyle)
         },
       },
+      shortcuts: {
+        opened: shortcutsOpened,
+        open() {
+          setShortcutsOpened(true)
+        },
+        close() {
+          setShortcutsOpened(false)
+        },
+        toggle() {
+          setShortcutsOpened((x) => !x)
+        },
+      },
       session: {
         width: createMemo(() => store.session?.width ?? 600),
         resize(width: number) {

+ 16 - 4
packages/app/src/index.css

@@ -97,7 +97,7 @@
 
 .shortcuts-panel [data-slot="tabs-trigger-wrapper"] {
   background: transparent;
-  border: 0.5px solid transparent;
+  border: none;
   font-weight: var(--font-weight-regular);
   border-radius: 6px;
   color: var(--color-text-weak);
@@ -108,8 +108,7 @@
 }
 
 .shortcuts-panel [data-slot="tabs-trigger-wrapper"]:has([data-selected]) {
-  border: 1px solid var(--color-border-weak-base);
-  background: var(--color-surface-raised-base);
+  background: var(--color-surface-raised-base-active);
   color: var(--color-text-strong);
 }
 
@@ -202,7 +201,20 @@
   white-space: nowrap;
 }
 
-/* Adjust main content when shortcuts panel is open */
+.shortcut-key-special {
+  font-size: 18px;
+}
+
+.shortcut-key-shift {
+  position: relative;
+  top: -2px;
+}
+
+/* Adjust main content and sidebar when shortcuts panel is open */
 main.shortcuts-open {
   padding-bottom: 280px;
 }
+
+.sidebar-shortcuts-open {
+  padding-bottom: 280px;
+}

+ 10 - 9
packages/app/src/pages/layout.tsx

@@ -59,7 +59,7 @@ import { useDialog } from "@opencode-ai/ui/context/dialog"
 import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme"
 import { DialogSelectProvider } from "@/components/dialog-select-provider"
 import { DialogSelectServer } from "@/components/dialog-select-server"
-import { useCommand, type CommandOption } from "@/context/command"
+import { formatKeybind, useCommand, type CommandOption } from "@/context/command"
 import { ConstrainDragXAxis } from "@/utils/solid-dnd"
 import { navStart } from "@/utils/perf"
 import { DialogSelectDirectory } from "@/components/dialog-select-directory"
@@ -81,7 +81,6 @@ export default function Layout(props: ParentProps) {
       workspaceExpanded: {} as Record<string, boolean>,
     }),
   )
-  const [shortcutsOpen, setShortcutsOpen] = createSignal(false)
 
   const pageReady = createMemo(() => ready())
 
@@ -757,7 +756,7 @@ export default function Layout(props: ParentProps) {
         title: "Toggle shortcuts panel",
         category: "View",
         keybind: "ctrl+/",
-        onSelect: () => setShortcutsOpen(!shortcutsOpen()),
+        onSelect: () => layout.shortcuts.toggle(),
       },
       {
         id: "project.open",
@@ -1897,7 +1896,7 @@ export default function Layout(props: ParentProps) {
     const homedir = createMemo(() => sync.data.path.home)
 
     return (
-      <div class="flex h-full w-full overflow-hidden">
+      <div class="flex h-full w-full overflow-hidden" classList={{ "sidebar-shortcuts-open": layout.shortcuts.opened() }}>
         <div class="w-16 shrink-0 bg-background-base flex flex-col items-center overflow-hidden">
           <div class="flex-1 min-h-0 w-full">
             <DragDropProvider
@@ -1937,7 +1936,7 @@ export default function Layout(props: ParentProps) {
             <Tooltip placement={sidebarProps.mobile ? "bottom" : "right"} value="Settings" class="hidden">
               <IconButton disabled icon="settings-gear" variant="ghost" size="large" />
             </Tooltip>
-            <DropdownMenu>
+            <DropdownMenu placement={layout.shortcuts.opened() ? "top-start" : "bottom-start"}>
               <Tooltip placement={sidebarProps.mobile ? "bottom" : "right"} value="Help">
                 <DropdownMenu.Trigger as={IconButton} icon="question-mark" variant="ghost" size="large" />
               </Tooltip>
@@ -1946,7 +1945,9 @@ export default function Layout(props: ParentProps) {
                   <DropdownMenu.Item onSelect={() => platform.openLink("https://opencode.ai/desktop-feedback")}>
                     Submit feedback
                   </DropdownMenu.Item>
-                  <DropdownMenu.Item onSelect={() => setShortcutsOpen(true)}>Keyboard shortcuts</DropdownMenu.Item>
+                  <DropdownMenu.Item class="flex justify-between gap-6" onSelect={() => layout.shortcuts.open()}>
+                    Keyboard shortcuts <span class="text-text-weaker">{formatKeybind("ctrl+/")}</span>
+                  </DropdownMenu.Item>
                 </DropdownMenu.Content>
               </DropdownMenu.Portal>
             </DropdownMenu>
@@ -2160,14 +2161,14 @@ export default function Layout(props: ParentProps) {
           classList={{
             "size-full overflow-x-hidden flex flex-col items-start contain-strict border-t border-border-weak-base": true,
             "xl:border-l xl:rounded-tl-sm": !layout.sidebar.opened(),
-            "shortcuts-open": shortcutsOpen(),
+            "shortcuts-open": layout.shortcuts.opened(),
           }}
         >
           {props.children}
         </main>
       </div>
-      <Show when={shortcutsOpen()}>
-        <ShortcutsPanel onClose={() => setShortcutsOpen(false)} />
+      <Show when={layout.shortcuts.opened()}>
+        <ShortcutsPanel onClose={() => layout.shortcuts.close()} />
       </Show>
       <Toast.Region />
       <ReleaseNotesHandler />

+ 3 - 1
packages/ui/src/components/select.tsx

@@ -15,6 +15,7 @@ export type SelectProps<T> = Omit<ComponentProps<typeof Kobalte<T>>, "value" | "
   class?: ComponentProps<"div">["class"]
   classList?: ComponentProps<"div">["classList"]
   children?: (item: T | undefined) => JSX.Element
+  placement?: "bottom-start" | "bottom-end" | "top-start" | "top-end"
 }
 
 export function Select<T>(props: SelectProps<T> & ButtonProps) {
@@ -29,6 +30,7 @@ export function Select<T>(props: SelectProps<T> & ButtonProps) {
     "groupBy",
     "onSelect",
     "children",
+    "placement",
   ])
   const grouped = createMemo(() => {
     const result = pipe(
@@ -46,7 +48,7 @@ export function Select<T>(props: SelectProps<T> & ButtonProps) {
     <Kobalte<T, { category: string; options: T[] }>
       {...others}
       data-component="select"
-      placement="bottom-start"
+      placement={local.placement ?? "bottom-start"}
       value={local.current}
       options={grouped()}
       optionValue={(x) => (local.value ? local.value(x) : (x as string))}