fix(ui): unify composer submit button styles (#48032)

This commit is contained in:
David Hill
2026-09-08 17:07:41 -06:00
committed by GitHub
Unverified
parent 883d16d2ad
commit f3128fa241
11 changed files with 96 additions and 67 deletions
+2 -2
View File
@@ -841,8 +841,8 @@ export function ComposerEditorSubmitButton(props: {
disabled={!props.stopping && props.disabled}
tabIndex={props.mode === "normal" ? undefined : -1}
icon={<Icon name={props.stopping ? "stop" : props.mode === "shell" ? "arrow-undo-down" : "arrow-up"} />}
variant="contrast"
class="size-7 rounded-md p-[6px] disabled:opacity-50"
variant="submit"
class="size-7 rounded-md p-[6px]"
aria-label={props.stopping ? props.stopLabel : props.sendLabel}
onClick={(event) => {
event.preventDefault()
@@ -45,7 +45,7 @@ export function SessionPermissionDock(props: {
>
{language.t("ui.permission.allowAlways")}
</Button>
<Button variant="contrast" size="normal" onClick={() => props.onDecide("once")} disabled={props.responding}>
<Button variant="submit" size="normal" onClick={() => props.onDecide("once")} disabled={props.responding}>
{language.t("ui.permission.allowOnce")}
</Button>
</div>
@@ -523,7 +523,7 @@ export const SessionQuestionDock: Component<{ request: FormInfo; onSubmit: () =>
</Button>
</Show>
<Button
variant={last() ? "contrast" : "neutral"}
variant={last() ? "submit" : "neutral"}
size="large"
disabled={sending()}
onClick={next}
@@ -69,7 +69,7 @@ export function SessionWebSearchDock(props: { model: WebSearchRequestModel; onSu
</Button>
</Show>
<Button
variant="neutral"
variant="submit"
size="small"
onClick={() => {
const selected = props.model.selected()
@@ -1,3 +1,5 @@
@import "../submit.css";
* {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@@ -5,7 +5,8 @@ const docs = `### Overview
Button v2 with visual variants and three sizes.
### API
- \`variant\`: "neutral" | "danger" | "warning" | "contrast" | "ghost" | "ghost-muted" | "ghost-faint" | "loading".
- \`variant\`: "neutral" | "danger" | "warning" | "contrast" | "submit" | "ghost" | "ghost-muted" | "ghost-faint" | "loading".
- \`submit\` shares the brighter contrast icon-button styling for Composer submission actions.
- \`size\`: "small" | "normal" | "large".
- \`icon\`: Optional icon name.
- Inherits Kobalte Button props and native button attributes.
@@ -40,7 +41,7 @@ export default {
},
variant: {
control: "select",
options: ["neutral", "danger", "warning", "contrast", "ghost", "ghost-muted", "ghost-faint", "loading"],
options: ["neutral", "danger", "warning", "contrast", "submit", "ghost", "ghost-muted", "ghost-faint", "loading"],
},
size: {
control: "select",
@@ -65,6 +66,7 @@ export const Variants = {
<Button variant="danger">Danger</Button>
<Button variant="warning">Warning</Button>
<Button variant="contrast">Contrast</Button>
<Button variant="submit">Submit</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="ghost-muted" icon="edit">
Ghost muted
@@ -127,6 +129,7 @@ export const AllStates = {
"danger",
"warning",
"contrast",
"submit",
"ghost",
"ghost-muted",
"ghost-faint",
@@ -13,6 +13,7 @@ export interface ButtonProps
| "warning"
| "outline"
| "contrast"
| "submit"
| "ghost"
| "ghost-muted"
| "ghost-faint"
@@ -1,3 +1,5 @@
@import "../submit.css";
* {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@@ -69,61 +71,6 @@
cursor: not-allowed;
}
/* Contrast */
[data-component="icon-button-v2"][data-variant="contrast"] {
--icon-button-contrast-highlight: var(--v2-alpha-light-20);
--icon-button-contrast-hover: var(--v2-overlay-simple-overlay-contrast-hover);
background-image:
linear-gradient(180deg, var(--icon-button-contrast-highlight) 0%, var(--v2-alpha-light-0) 100%),
linear-gradient(
90deg,
var(--v2-background-bg-icon-button-contrast) 0%,
var(--v2-background-bg-icon-button-contrast) 100%
);
color: var(--v2-text-text-contrast);
text-shadow: 0 0.5px 0.5px rgba(0, 0, 0, 0.3);
box-shadow: var(--v2-elevation-button-contrast);
}
[data-color-scheme="dark"] [data-component="icon-button-v2"][data-variant="contrast"] {
--icon-button-contrast-highlight: var(--v2-alpha-light-60);
--icon-button-contrast-hover: var(--v2-alpha-dark-8);
color: var(--v2-icon-icon-inverse);
}
[data-component="icon-button-v2"][data-variant="contrast"]:is(:hover, [data-state="hover"]):not(:disabled) {
background-image:
linear-gradient(90deg, var(--icon-button-contrast-hover) 0%, var(--icon-button-contrast-hover) 100%),
linear-gradient(180deg, var(--icon-button-contrast-highlight) 0%, var(--v2-alpha-light-0) 100%),
linear-gradient(
90deg,
var(--v2-background-bg-icon-button-contrast) 0%,
var(--v2-background-bg-icon-button-contrast) 100%
);
}
[data-component="icon-button-v2"][data-variant="contrast"]:is(:active, [data-state="pressed"]):not(:disabled) {
background-image:
linear-gradient(
90deg,
var(--v2-overlay-simple-overlay-contrast-pressed) 0%,
var(--v2-overlay-simple-overlay-contrast-pressed) 100%
),
linear-gradient(180deg, var(--icon-button-contrast-highlight) 0%, var(--v2-alpha-light-0) 100%),
linear-gradient(
90deg,
var(--v2-background-bg-icon-button-contrast) 0%,
var(--v2-background-bg-icon-button-contrast) 100%
);
}
[data-component="icon-button-v2"][data-variant="contrast"]:is(:disabled, [data-state="disabled"]) {
opacity: 0.4;
cursor: not-allowed;
}
/* Ghost */
[data-component="icon-button-v2"][data-variant="ghost"] {
background-color: transparent;
@@ -3,11 +3,12 @@ import { IconButton } from "./icon-button"
import { Icon } from "@opencode/ui/icon"
const docs = `### Overview
Square icon-only button with three visual variants and three sizes.
Square icon-only button with visual variants and three sizes.
### API
- \`icon\`: Icon content.
- \`variant\`: "neutral" | "contrast" | "ghost".
- \`variant\`: "neutral" | "contrast" | "submit" | "ghost".
- \`submit\` shares the contrast styling with regular Composer submit buttons.
- \`size\`: "small" | "normal" | "large".
- Inherits Kobalte Button props and native button attributes.
@@ -41,7 +42,7 @@ export default {
},
variant: {
control: "select",
options: ["neutral", "contrast", "ghost"],
options: ["neutral", "contrast", "submit", "ghost"],
},
size: {
control: "select",
@@ -57,6 +58,7 @@ export const Variants = {
<div style={{ display: "flex", gap: "12px", "align-items": "center", "flex-wrap": "wrap" }}>
<IconButton icon={<Icon name="plus" />} variant="neutral" />
<IconButton icon={<Icon name="plus" />} variant="contrast" />
<IconButton icon={<Icon name="arrow-up" />} variant="submit" />
<IconButton icon={<Icon name="plus" />} variant="ghost" />
</div>
),
@@ -74,7 +76,7 @@ export const Sizes = {
export const AllStates = {
render: () => {
const variants = ["neutral", "contrast", "ghost"] as const
const variants = ["neutral", "contrast", "submit", "ghost"] as const
const states = ["default", "hover", "pressed", "focus", "disabled"] as const
return (
@@ -8,7 +8,7 @@ export interface IconButtonProps
Pick<ComponentProps<"button">, "class" | "classList"> {
icon?: JSX.Element
size?: "small" | "normal" | "large"
variant?: "neutral" | "contrast" | "ghost" | "ghost-muted"
variant?: "neutral" | "contrast" | "submit" | "ghost" | "ghost-muted"
state?: "rest" | "hover" | "pressed"
}
+74
View File
@@ -0,0 +1,74 @@
/* Shared by Composer submit actions and contrast icon buttons. */
:is(
[data-component="button-v2"][data-variant="submit"],
[data-component="icon-button-v2"]:is([data-variant="submit"], [data-variant="contrast"])
) {
--submit-highlight: var(--v2-alpha-light-20);
--submit-hover: var(--v2-overlay-simple-overlay-contrast-hover);
background-image:
linear-gradient(180deg, var(--submit-highlight) 0%, var(--v2-alpha-light-0) 100%),
linear-gradient(
90deg,
var(--v2-background-bg-icon-button-contrast) 0%,
var(--v2-background-bg-icon-button-contrast) 100%
);
color: var(--v2-text-text-contrast);
text-shadow: 0 0.5px 0.5px rgba(0, 0, 0, 0.3);
box-shadow: var(--v2-elevation-button-contrast);
}
[data-color-scheme="dark"]
:is(
[data-component="button-v2"][data-variant="submit"],
[data-component="icon-button-v2"]:is([data-variant="submit"], [data-variant="contrast"])
) {
--submit-highlight: var(--v2-alpha-light-60);
--submit-hover: var(--v2-alpha-dark-8);
color: var(--v2-text-text-inverse);
}
[data-color-scheme="dark"] [data-component="icon-button-v2"]:is([data-variant="submit"], [data-variant="contrast"]) {
color: var(--v2-icon-icon-inverse);
}
:is(
[data-component="button-v2"][data-variant="submit"],
[data-component="icon-button-v2"]:is([data-variant="submit"], [data-variant="contrast"])
):is(:hover, [data-state="hover"]):not(:disabled) {
background-image:
linear-gradient(90deg, var(--submit-hover) 0%, var(--submit-hover) 100%),
linear-gradient(180deg, var(--submit-highlight) 0%, var(--v2-alpha-light-0) 100%),
linear-gradient(
90deg,
var(--v2-background-bg-icon-button-contrast) 0%,
var(--v2-background-bg-icon-button-contrast) 100%
);
}
:is(
[data-component="button-v2"][data-variant="submit"],
[data-component="icon-button-v2"]:is([data-variant="submit"], [data-variant="contrast"])
):is(:active, [data-state="pressed"]):not(:disabled) {
background-image:
linear-gradient(
90deg,
var(--v2-overlay-simple-overlay-contrast-pressed) 0%,
var(--v2-overlay-simple-overlay-contrast-pressed) 100%
),
linear-gradient(180deg, var(--submit-highlight) 0%, var(--v2-alpha-light-0) 100%),
linear-gradient(
90deg,
var(--v2-background-bg-icon-button-contrast) 0%,
var(--v2-background-bg-icon-button-contrast) 100%
);
}
:is(
[data-component="button-v2"][data-variant="submit"],
[data-component="icon-button-v2"]:is([data-variant="submit"], [data-variant="contrast"])
):is(:disabled, [data-state="disabled"]) {
opacity: 0.4;
cursor: not-allowed;
}