Compare commits
3 Commits
dev
...
nxl/shell-
| Author | SHA1 | Date |
|---|---|---|
|
|
01d80f37dd | |
|
|
f714300e9a | |
|
|
30c4ccbfee |
|
|
@ -19,6 +19,7 @@ import { useSDK } from "@/context/sdk"
|
||||||
import { useSync } from "@/context/sync"
|
import { useSync } from "@/context/sync"
|
||||||
import { useComments } from "@/context/comments"
|
import { useComments } from "@/context/comments"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
|
import { ButtonShortcut } from "@opencode-ai/ui/button-shortcut"
|
||||||
import { DockShellForm, DockTray } from "@opencode-ai/ui/dock-surface"
|
import { DockShellForm, DockTray } from "@opencode-ai/ui/dock-surface"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
||||||
|
|
@ -1450,17 +1451,17 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||||
<Show when={store.mode === "normal" || store.mode === "shell"}>
|
<Show when={store.mode === "normal" || store.mode === "shell"}>
|
||||||
<DockTray attach="top">
|
<DockTray attach="top">
|
||||||
<div class="px-1.75 pt-5.5 pb-2 flex items-center gap-2 min-w-0">
|
<div class="px-1.75 pt-5.5 pb-2 flex items-center gap-2 min-w-0">
|
||||||
<div class="flex items-center gap-1.5 min-w-0 flex-1 relative">
|
<div class="flex h-7 items-center gap-1.5 min-w-0 flex-1 relative">
|
||||||
<div
|
<div
|
||||||
class="h-7 flex items-center gap-1.5 max-w-[160px] min-w-0 absolute inset-y-0 left-0"
|
class="flex items-center max-w-[160px] min-w-0 absolute inset-y-0 left-0"
|
||||||
style={{
|
style={{
|
||||||
padding: "0 4px 0 8px",
|
padding: "0 8px",
|
||||||
...shell(),
|
...shell(),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span class="truncate text-13-medium text-text-strong">{language.t("prompt.mode.shell")}</span>
|
<span class="truncate text-13-regular text-text-strong">{language.t("prompt.mode.shell")}</span>
|
||||||
<div class="size-4 shrink-0" />
|
|
||||||
</div>
|
</div>
|
||||||
|
<Show when={store.mode !== "shell"}>
|
||||||
<div class="flex items-center gap-1.5 min-w-0 flex-1">
|
<div class="flex items-center gap-1.5 min-w-0 flex-1">
|
||||||
<div data-component="prompt-agent-control">
|
<div data-component="prompt-agent-control">
|
||||||
<TooltipKeybind
|
<TooltipKeybind
|
||||||
|
|
@ -1485,7 +1486,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||||
/>
|
/>
|
||||||
</TooltipKeybind>
|
</TooltipKeybind>
|
||||||
</div>
|
</div>
|
||||||
<Show when={store.mode !== "shell"}>
|
|
||||||
<div data-component="prompt-model-control">
|
<div data-component="prompt-model-control">
|
||||||
<Show
|
<Show
|
||||||
when={providers.paid().length > 0}
|
when={providers.paid().length > 0}
|
||||||
|
|
@ -1581,7 +1581,22 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||||
/>
|
/>
|
||||||
</TooltipKeybind>
|
</TooltipKeybind>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
<div class="absolute inset-y-0 right-0 flex items-center" style={shell()}>
|
||||||
|
<ButtonShortcut
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="small"
|
||||||
|
shortcut="Esc"
|
||||||
|
shortcutAria="Escape"
|
||||||
|
class="h-6 gap-2 rounded-[6px] border-none px-0 py-0 pl-3 pr-0.75 text-13-medium text-text-base shadow-none"
|
||||||
|
tabIndex={store.mode === "shell" ? undefined : -1}
|
||||||
|
onClick={() => setMode("normal")}
|
||||||
|
aria-label={language.t("common.cancel")}
|
||||||
|
>
|
||||||
|
{language.t("common.cancel")}
|
||||||
|
</ButtonShortcut>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
[data-component="button"][data-button-shortcut] {
|
||||||
|
[data-slot="button-shortcut-label"] {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="button-shortcut-key"] {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="button-shortcut-key"] [data-component="keybind"] {
|
||||||
|
box-shadow: none;
|
||||||
|
background: var(--surface-raised-base);
|
||||||
|
color: var(--text-weak);
|
||||||
|
font-family: var(--font-family-sans);
|
||||||
|
font-weight: var(--font-weight-regular);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-size="small"] [data-slot="button-shortcut-key"] [data-component="keybind"],
|
||||||
|
&[data-size="normal"] [data-slot="button-shortcut-key"] [data-component="keybind"] {
|
||||||
|
height: 18px;
|
||||||
|
padding: 0 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-size="large"] [data-slot="button-shortcut-key"] [data-component="keybind"] {
|
||||||
|
height: 20px;
|
||||||
|
padding: 0 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
// @ts-nocheck
|
||||||
|
import { ButtonShortcut } from "./button-shortcut"
|
||||||
|
|
||||||
|
const docs = `### Overview
|
||||||
|
Button with a trailing shortcut keycap.
|
||||||
|
|
||||||
|
Use this when the action label and shortcut should be taught together at the control level.
|
||||||
|
|
||||||
|
### API
|
||||||
|
- Inherits Button props.
|
||||||
|
- \`shortcut\`: visible keycap text.
|
||||||
|
- \`shortcutAria\`: semantic shortcut string for \`aria-keyshortcuts\`.
|
||||||
|
- \`shortcutClass\`: optional class override for the keycap.
|
||||||
|
|
||||||
|
### Variants and states
|
||||||
|
- Uses the same \`variant\` and \`size\` options as \`Button\`.
|
||||||
|
- Supports disabled state.
|
||||||
|
|
||||||
|
### Accessibility
|
||||||
|
- Keep the visible shortcut concise.
|
||||||
|
- Use \`shortcutAria\` for the canonical key sequence when it differs from the visible label.
|
||||||
|
|
||||||
|
### Theming/tokens
|
||||||
|
- Extends \`Button\` and composes \`Keybind\`.
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "UI/ButtonShortcut",
|
||||||
|
id: "components-button-shortcut",
|
||||||
|
component: ButtonShortcut,
|
||||||
|
tags: ["autodocs"],
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component: docs,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
children: "Cancel",
|
||||||
|
shortcut: "Esc",
|
||||||
|
shortcutAria: "Escape",
|
||||||
|
variant: "ghost",
|
||||||
|
size: "small",
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
variant: {
|
||||||
|
control: "select",
|
||||||
|
options: ["primary", "secondary", "ghost"],
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
control: "select",
|
||||||
|
options: ["small", "normal", "large"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Basic = {}
|
||||||
|
|
||||||
|
export const Sizes = {
|
||||||
|
render: () => (
|
||||||
|
<div style={{ display: "flex", gap: "12px", "align-items": "center" }}>
|
||||||
|
<ButtonShortcut size="small" variant="ghost" shortcut="Esc" shortcutAria="Escape">
|
||||||
|
Cancel
|
||||||
|
</ButtonShortcut>
|
||||||
|
<ButtonShortcut size="normal" variant="secondary" shortcut="Tab" shortcutAria="Tab">
|
||||||
|
Focus
|
||||||
|
</ButtonShortcut>
|
||||||
|
<ButtonShortcut size="large" variant="primary" shortcut="Enter" shortcutAria="Enter">
|
||||||
|
Submit
|
||||||
|
</ButtonShortcut>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Shell = {
|
||||||
|
args: {
|
||||||
|
children: "Cancel",
|
||||||
|
shortcut: "Esc",
|
||||||
|
shortcutAria: "Escape",
|
||||||
|
variant: "ghost",
|
||||||
|
size: "small",
|
||||||
|
class: "h-6 gap-2 rounded-[6px] border-none px-0 py-0 pl-3 pr-0.75 text-13-medium text-text-base shadow-none",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { type ComponentProps, Show, splitProps } from "solid-js"
|
||||||
|
import { Button, type ButtonProps } from "./button"
|
||||||
|
import { Keybind } from "./keybind"
|
||||||
|
|
||||||
|
export interface ButtonShortcutProps extends ButtonProps {
|
||||||
|
shortcut?: string
|
||||||
|
shortcutAria?: string
|
||||||
|
shortcutClass?: string
|
||||||
|
shortcutClassList?: ComponentProps<"span">["classList"]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ButtonShortcut(props: ButtonShortcutProps) {
|
||||||
|
const [split, rest] = splitProps(props, [
|
||||||
|
"children",
|
||||||
|
"shortcut",
|
||||||
|
"shortcutAria",
|
||||||
|
"shortcutClass",
|
||||||
|
"shortcutClassList",
|
||||||
|
])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button {...rest} aria-keyshortcuts={split.shortcutAria} data-button-shortcut={split.shortcut ? "true" : undefined}>
|
||||||
|
<span data-slot="button-shortcut-label">{split.children}</span>
|
||||||
|
<Show when={split.shortcut}>
|
||||||
|
<span data-slot="button-shortcut-key">
|
||||||
|
<Keybind class={split.shortcutClass} classList={split.shortcutClassList}>
|
||||||
|
{split.shortcut}
|
||||||
|
</Keybind>
|
||||||
|
</span>
|
||||||
|
</Show>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
@import "../components/avatar.css" layer(components);
|
@import "../components/avatar.css" layer(components);
|
||||||
@import "../components/basic-tool.css" layer(components);
|
@import "../components/basic-tool.css" layer(components);
|
||||||
@import "../components/button.css" layer(components);
|
@import "../components/button.css" layer(components);
|
||||||
|
@import "../components/button-shortcut.css" layer(components);
|
||||||
@import "../components/card.css" layer(components);
|
@import "../components/card.css" layer(components);
|
||||||
@import "../components/tool-error-card.css" layer(components);
|
@import "../components/tool-error-card.css" layer(components);
|
||||||
@import "../components/checkbox.css" layer(components);
|
@import "../components/checkbox.css" layer(components);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue