mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 12:56:01 -06:00
improve(ui): reduce startup stylesheet size (#125539)
* perf(ui): lazy-load settings workspace styles Amp-Thread-ID: https://ampcode.com/threads/T-01a01277-59ee-703a-b04d-927a3ba733a1 * fix(ui): keep picker styles route scoped Amp-Thread-ID: https://ampcode.com/threads/T-01a01277-59ee-703a-b04d-927a3ba733a1 --------- Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
committed by
GitHub
parent
d9b69c5b6f
commit
ca8bd59df5
@@ -1,6 +1,6 @@
|
||||
# Settings Design Language
|
||||
|
||||
Every settings surface (the `/settings` takeover pages plus the Plugins/Skills hubs) uses one structural pattern. Styles live in `ui/src/styles/settings.css`; templates are built through the helpers in `ui/src/components/settings-ui.ts`.
|
||||
Every settings surface (the `/settings` takeover pages plus the Plugins/Skills hubs) uses one structural pattern. Workspace styles live in `ui/src/styles/settings.css`, while controls shared with startup surfaces live in `ui/src/styles/settings-controls.css`; templates are built through the helpers in `ui/src/components/settings-ui.ts`.
|
||||
|
||||
## Anatomy
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ function readUiCss(): string {
|
||||
"ui/src/styles/layout.css",
|
||||
"ui/src/styles/layout.mobile.css",
|
||||
"ui/src/styles/components.css",
|
||||
"ui/src/styles/settings-controls.css",
|
||||
"ui/src/styles/settings.css",
|
||||
"ui/src/styles/config.css",
|
||||
"ui/src/styles/usage.css",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { html, nothing } from "lit";
|
||||
import "./web-awesome-select.ts";
|
||||
import "../styles/select-picker.css";
|
||||
|
||||
export type PickerOption = {
|
||||
value: string;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Settings design-language primitives. Every settings surface builds its
|
||||
// layout through these helpers so pages cannot drift back into bespoke
|
||||
// card/pill markup. Styles live in ui/src/styles/settings.css; rules in
|
||||
// ui/docs/settings-design.md.
|
||||
// card/pill markup. Styles live in ui/src/styles/settings.css and the shared
|
||||
// ui/src/styles/settings-controls.css; rules in ui/docs/design-system/settings-design.md.
|
||||
import "@awesome.me/webawesome/dist/components/radio/radio.js";
|
||||
import "@awesome.me/webawesome/dist/components/radio-group/radio-group.js";
|
||||
import "@awesome.me/webawesome/dist/components/switch/switch.js";
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// section navigation lives in the takeover sidebar (settings-sidebar.ts).
|
||||
import { html } from "lit";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import "../styles/settings.css";
|
||||
|
||||
export function renderSettingsWorkspace(
|
||||
body: unknown,
|
||||
|
||||
@@ -930,9 +930,9 @@ describeBrowserLayout.concurrent("chat responsive browser layout", () => {
|
||||
try {
|
||||
const splitViewCss = readStyleSheet("ui/src/styles/chat/split-view.css");
|
||||
const boardCss = readStyleSheet("ui/src/styles/chat/board.css");
|
||||
const settingsCss = readStyleSheet("ui/src/styles/settings.css");
|
||||
const settingsControlsCss = readStyleSheet("ui/src/styles/settings-controls.css");
|
||||
await page.setContent(
|
||||
`<!doctype html><html><head><style>${readUiCss()}\n${settingsCss}\n${splitViewCss}\n${boardCss}</style></head><body>
|
||||
`<!doctype html><html><head><style>${readUiCss()}\n${settingsControlsCss}\n${splitViewCss}\n${boardCss}</style></head><body>
|
||||
<div class="chat-split-view__cell" style="width: 320px;">
|
||||
<div class="chat-pane__header">
|
||||
<button class="btn btn--ghost btn--icon chat-icon-btn chat-pane__nav-toggle" type="button">N</button>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { html } from "lit";
|
||||
import "../../components/web-awesome-select.ts";
|
||||
import "../../styles/select-picker.css";
|
||||
import { SUPPORTED_LOCALES, t, type Locale } from "../../i18n/index.ts";
|
||||
|
||||
export function languageLabel(locale: Locale) {
|
||||
|
||||
@@ -29,6 +29,7 @@ function readUiCss(): string {
|
||||
"ui/src/styles/layout.css",
|
||||
"ui/src/styles/layout.mobile.css",
|
||||
"ui/src/styles/components.css",
|
||||
"ui/src/styles/settings-controls.css",
|
||||
"ui/src/styles/settings.css",
|
||||
"ui/src/styles/sessions.css",
|
||||
];
|
||||
|
||||
@@ -408,6 +408,21 @@ suite.define(() => {
|
||||
.locator(".workboard-toolbar__filters .workboard-select")
|
||||
.nth(1);
|
||||
const priorityCombobox = prioritySelect.getByRole("combobox");
|
||||
const directRoutePickerStyles = await prioritySelect.evaluate((select) => {
|
||||
const label = select.querySelector(".picker-select__label");
|
||||
const copy = select.querySelector(".picker-select__copy");
|
||||
if (!label || !copy) {
|
||||
throw new Error("Workboard picker style probe did not render");
|
||||
}
|
||||
return {
|
||||
copyDisplay: getComputedStyle(copy).display,
|
||||
labelFontWeight: getComputedStyle(label).fontWeight,
|
||||
};
|
||||
});
|
||||
expect(directRoutePickerStyles).toEqual({
|
||||
copyDisplay: "grid",
|
||||
labelFontWeight: "650",
|
||||
});
|
||||
await priorityCombobox.focus();
|
||||
await writable.page.keyboard.press("ArrowDown");
|
||||
await expect.poll(() => priorityCombobox.getAttribute("aria-expanded")).toBe("true");
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
@import "./styles/components.css";
|
||||
@import "./styles/chat/progress-card.css";
|
||||
@import "./styles/approval-boot.css";
|
||||
@import "./styles/settings.css";
|
||||
@import "./styles/settings-controls.css";
|
||||
|
||||
/* Contract read by stale-chunk-reload.ts to detect a failed entry stylesheet load.
|
||||
* Removing it leaves a fully unstyled page undetected. */
|
||||
|
||||
@@ -223,6 +223,7 @@ function readUiCss(): string {
|
||||
"ui/src/styles/layout.css",
|
||||
"ui/src/styles/option-card.css",
|
||||
"ui/src/styles/chat/layout.css",
|
||||
"ui/src/styles/settings-controls.css",
|
||||
"ui/src/styles/settings.css",
|
||||
"ui/src/pages/activity/run-inspector.css",
|
||||
]
|
||||
|
||||
@@ -61,6 +61,7 @@ function readUiCss(): string {
|
||||
"ui/src/styles/components.css",
|
||||
"ui/src/styles/layout.css",
|
||||
"ui/src/styles/sessions.css",
|
||||
"ui/src/styles/settings-controls.css",
|
||||
"ui/src/styles/settings.css",
|
||||
"ui/src/styles/skill-workshop.css",
|
||||
"ui/src/styles/chat/layout.css",
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
wa-select.settings-select {
|
||||
--wa-form-control-activated-color: var(--primary);
|
||||
}
|
||||
|
||||
wa-select.settings-select::part(combobox) {
|
||||
min-height: var(--settings-control-height);
|
||||
padding-inline: 10px;
|
||||
border: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
|
||||
border-radius: var(--radius-md);
|
||||
background: color-mix(in srgb, var(--bg) 80%, var(--bg-elevated) 20%);
|
||||
color: var(--text);
|
||||
cursor: var(--cursor-action);
|
||||
transition: border-color var(--duration-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
/* [open] is required alongside :focus-within: Chromium does not propagate
|
||||
:focus-within to the host while a slotted option holds focus, so the open
|
||||
state must key off the reflected attribute or the stock ring leaks through. */
|
||||
wa-select.settings-select[open]::part(combobox),
|
||||
wa-select.settings-select:focus-within::part(combobox) {
|
||||
outline: none;
|
||||
border-color: color-mix(in srgb, var(--accent) 55%, var(--border) 45%);
|
||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 18%, transparent);
|
||||
}
|
||||
|
||||
wa-select.settings-select::part(expand-icon) {
|
||||
margin-inline-start: var(--space-2);
|
||||
font-size: var(--control-ui-text-sm);
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
wa-select.settings-select::part(listbox) {
|
||||
/* Keep Web Awesome's collision-aware bound in the cap; replacing it outright
|
||||
would let the listbox overflow when the popup has little room to open. */
|
||||
max-height: min(320px, var(--auto-size-available-height, calc(100vh - 48px)));
|
||||
padding: 6px;
|
||||
border: 1px solid color-mix(in srgb, var(--border-strong) 86%, transparent);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--bg-elevated);
|
||||
box-shadow: var(--shadow-lg);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
wa-select.settings-select wa-option {
|
||||
padding: 6px 8px;
|
||||
border-radius: calc(var(--radius-md) - 4px);
|
||||
font-size: var(--control-ui-text-md);
|
||||
color: var(--text);
|
||||
cursor: var(--cursor-action);
|
||||
}
|
||||
|
||||
/* The roving "current" option already carries the accent fill; the global
|
||||
:focus-visible accent outline on top of it is pure noise. */
|
||||
wa-select.settings-select wa-option:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
wa-select.settings-select wa-option:state(current) {
|
||||
color: var(--primary-foreground);
|
||||
}
|
||||
|
||||
.picker-select__option {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.picker-select [slot="start"] {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.picker-select__label {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.picker-select__copy {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.picker-select__description {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: var(--muted);
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.2;
|
||||
text-overflow: ellipsis;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
/* The model picker composes the shared picker and loads this same stylesheet. */
|
||||
.model-picker {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.model-picker__provider-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex: 0 0 20px;
|
||||
}
|
||||
|
||||
.model-picker__provider-icon.provider-brand-icon--fallback {
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.model-picker__custom[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Forced colors suppresses the box-shadow ring and accent fill that stand in
|
||||
for the outlines removed above, so restore system-color outlines there. */
|
||||
@media (forced-colors: active) {
|
||||
wa-select.settings-select[open]::part(combobox),
|
||||
wa-select.settings-select:focus-within::part(combobox) {
|
||||
outline: 2px solid SelectedItem;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
wa-select.settings-select wa-option:focus-visible {
|
||||
outline: 2px solid SelectedItem;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
:root {
|
||||
--settings-control-height: 32px;
|
||||
}
|
||||
|
||||
/* Settings controls also appear outside the settings workspace: the login
|
||||
gate, chat board controls, and the chat MCP form all use these primitives. */
|
||||
|
||||
/* Segmented keeps its 2px track inset; the buttons fill the inner height.
|
||||
min-height (not height) so many-option enums can still wrap to two lines. */
|
||||
.settings-segmented {
|
||||
min-height: var(--settings-control-height);
|
||||
box-sizing: border-box;
|
||||
align-items: stretch;
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 2px;
|
||||
max-width: 100%;
|
||||
background: color-mix(in srgb, var(--bg) 80%, var(--bg-elevated) 20%);
|
||||
border: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.settings-segmented > .settings-segmented__btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding-block: 0;
|
||||
}
|
||||
|
||||
.settings-control__sr-label {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
wa-radio-group.settings-segmented::part(form-control-label) {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
wa-radio-group.settings-segmented::part(radios) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.settings-segmented__btn {
|
||||
font-size: var(--control-ui-text-sm);
|
||||
font-weight: 550;
|
||||
padding: 4px 12px;
|
||||
border: none;
|
||||
border-radius: calc(var(--radius-md) - 3px);
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
white-space: nowrap;
|
||||
cursor: var(--cursor-action);
|
||||
transition:
|
||||
color var(--duration-normal) var(--ease-out),
|
||||
background var(--duration-normal) var(--ease-out),
|
||||
box-shadow var(--duration-normal) var(--ease-out);
|
||||
}
|
||||
|
||||
.settings-segmented__btn:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.settings-segmented__btn:focus-visible {
|
||||
outline: none;
|
||||
color: var(--text);
|
||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 22%, transparent);
|
||||
}
|
||||
|
||||
.settings-segmented__btn--active {
|
||||
background: var(--bg-elevated);
|
||||
color: var(--text-strong);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.settings-segmented__btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
input.settings-input,
|
||||
select.settings-select:not([multiple]) {
|
||||
height: var(--settings-control-height);
|
||||
padding-block: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.settings-input,
|
||||
.settings-select {
|
||||
--control-ui-touch-input-size: var(--control-ui-input-text-size);
|
||||
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
font: inherit;
|
||||
font-size: var(--control-ui-input-text-size);
|
||||
}
|
||||
|
||||
/* Native controls own their box. Web Awesome selects are styled by their
|
||||
component-owned stylesheet. */
|
||||
.settings-input,
|
||||
select.settings-select {
|
||||
border: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
|
||||
border-radius: var(--radius-md);
|
||||
background-color: color-mix(in srgb, var(--bg) 80%, var(--bg-elevated) 20%);
|
||||
color: var(--text);
|
||||
padding: 7px 10px;
|
||||
transition: border-color var(--duration-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
select.settings-select:not([multiple]) {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
padding-right: 36px;
|
||||
background-image: var(--select-chevron);
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 10px center;
|
||||
}
|
||||
|
||||
.settings-input::placeholder {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.settings-input:focus-visible,
|
||||
select.settings-select:focus-visible {
|
||||
outline: none;
|
||||
border-color: color-mix(in srgb, var(--accent) 55%, var(--border) 45%);
|
||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 18%, transparent);
|
||||
}
|
||||
|
||||
/* Secret inputs also appear in the startup login gate. Keep these selectors
|
||||
unscoped so the gate's mobile sizing rules retain higher specificity. */
|
||||
.settings-secret {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.settings-secret > input,
|
||||
.settings-secret > textarea {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
padding-right: 36px;
|
||||
}
|
||||
|
||||
.settings-secret__toggle {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: var(--radius-sm);
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
cursor: var(--cursor-action);
|
||||
transition: color var(--duration-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.settings-secret__toggle:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.settings-secret__toggle:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.settings-secret__toggle:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 22%, transparent);
|
||||
}
|
||||
|
||||
.settings-secret__toggle svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-width: 2px;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
/* Quiet count badge used by settings pages and the Skill Workshop. */
|
||||
.settings-count {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 1px 7px;
|
||||
border-radius: var(--radius-full);
|
||||
background: color-mix(in srgb, var(--bg-elevated) 80%, var(--border) 20%);
|
||||
color: var(--muted);
|
||||
font-size: var(--control-ui-text-xs);
|
||||
font-weight: 600;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
+1
-342
@@ -1,7 +1,3 @@
|
||||
:root {
|
||||
--settings-control-height: 32px;
|
||||
}
|
||||
|
||||
/* ── Settings design language ──────────────────────────────────────────────
|
||||
*
|
||||
* One structural pattern for every settings surface:
|
||||
@@ -441,36 +437,12 @@
|
||||
Multi-select list boxes and textareas keep their natural height. */
|
||||
.settings-row__control .btn,
|
||||
.settings-section__actions .btn,
|
||||
.settings-group .data-table-search input,
|
||||
input.settings-input,
|
||||
select.settings-select:not([multiple]) {
|
||||
.settings-group .data-table-search input {
|
||||
height: var(--settings-control-height);
|
||||
padding-block: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Segmented keeps its 2px track inset; the buttons fill the inner height.
|
||||
min-height (not height) so many-option enums can still wrap to two lines. */
|
||||
.settings-segmented {
|
||||
min-height: var(--settings-control-height);
|
||||
box-sizing: border-box;
|
||||
align-items: stretch;
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 2px;
|
||||
max-width: 100%;
|
||||
background: color-mix(in srgb, var(--bg) 80%, var(--bg-elevated) 20%);
|
||||
border: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.settings-segmented > .settings-segmented__btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding-block: 0;
|
||||
}
|
||||
|
||||
.settings-row__control .btn--icon,
|
||||
.settings-section__actions .btn--icon {
|
||||
width: var(--settings-control-height);
|
||||
@@ -485,18 +457,6 @@ select.settings-select:not([multiple]) {
|
||||
cursor: var(--cursor-action);
|
||||
}
|
||||
|
||||
.settings-control__sr-label {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
wa-switch.settings-toggle {
|
||||
/* Web Awesome defaults to brand blue; settings controls follow the active scene. */
|
||||
--wa-form-control-activated-color: var(--accent);
|
||||
@@ -504,94 +464,6 @@ wa-switch.settings-toggle {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
wa-radio-group.settings-segmented::part(form-control-label) {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
wa-radio-group.settings-segmented::part(radios) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.settings-segmented__btn {
|
||||
font-size: var(--control-ui-text-sm);
|
||||
font-weight: 550;
|
||||
padding: 4px 12px;
|
||||
border: none;
|
||||
border-radius: calc(var(--radius-md) - 3px);
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
white-space: nowrap;
|
||||
cursor: var(--cursor-action);
|
||||
transition:
|
||||
color var(--duration-normal) var(--ease-out),
|
||||
background var(--duration-normal) var(--ease-out),
|
||||
box-shadow var(--duration-normal) var(--ease-out);
|
||||
}
|
||||
|
||||
.settings-segmented__btn:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.settings-segmented__btn:focus-visible {
|
||||
outline: none;
|
||||
color: var(--text);
|
||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 22%, transparent);
|
||||
}
|
||||
|
||||
.settings-segmented__btn--active {
|
||||
background: var(--bg-elevated);
|
||||
color: var(--text-strong);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.settings-segmented__btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.settings-input,
|
||||
.settings-select {
|
||||
--control-ui-touch-input-size: var(--control-ui-input-text-size);
|
||||
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
font: inherit;
|
||||
font-size: var(--control-ui-input-text-size);
|
||||
}
|
||||
|
||||
/* Native controls own their box. wa-select draws the same box on its shadow
|
||||
combobox part below; host chrome would double-border the web component. */
|
||||
.settings-input,
|
||||
select.settings-select {
|
||||
border: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
|
||||
border-radius: var(--radius-md);
|
||||
/* background-color, not the background shorthand: the shorthand resets the
|
||||
.field select chevron longhands (image/repeat/position) and tiles the
|
||||
arrow across Model Providers selects. */
|
||||
background-color: color-mix(in srgb, var(--bg) 80%, var(--bg-elevated) 20%);
|
||||
color: var(--text);
|
||||
padding: 7px 10px;
|
||||
transition: border-color var(--duration-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
/* Single-value selects draw the themed chevron; the engine's stock arrow
|
||||
ignores the 32px control box and renders misaligned. Multi-select list
|
||||
boxes keep native rendering. */
|
||||
select.settings-select:not([multiple]) {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
padding-right: 36px;
|
||||
background-image: var(--select-chevron);
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 10px center;
|
||||
}
|
||||
|
||||
.settings-row:not(.settings-row--stacked) .settings-row__control > .settings-select {
|
||||
width: auto;
|
||||
min-width: min(220px, 100%);
|
||||
@@ -648,22 +520,6 @@ select.settings-select:not([multiple]) {
|
||||
|
||||
/* ── Secret input (inset reveal toggle; no trailing button) ── */
|
||||
|
||||
.settings-secret {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Class-agnostic child selectors: the login gate and config form reuse this
|
||||
wrapper around their own input/textarea styling. */
|
||||
.settings-secret > input,
|
||||
.settings-secret > textarea {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
padding-right: 36px;
|
||||
}
|
||||
|
||||
/* Stacked rows (config form arrays/maps) give the control full width; the
|
||||
wrapper must grow or the inner 100% resolves against fit-content. */
|
||||
.settings-row--stacked .settings-row__control > .settings-secret {
|
||||
@@ -679,189 +535,6 @@ select.settings-select:not([multiple]) {
|
||||
top: 6px;
|
||||
}
|
||||
|
||||
.settings-secret__toggle {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: var(--radius-sm);
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
cursor: var(--cursor-action);
|
||||
transition: color var(--duration-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.settings-secret__toggle:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.settings-secret__toggle:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.settings-secret__toggle:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 22%, transparent);
|
||||
}
|
||||
|
||||
.settings-secret__toggle svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-width: 2px;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
.settings-input::placeholder {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.settings-input:focus-visible,
|
||||
select.settings-select:focus-visible {
|
||||
outline: none;
|
||||
border-color: color-mix(in srgb, var(--accent) 55%, var(--border) 45%);
|
||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 18%, transparent);
|
||||
}
|
||||
|
||||
/* ── Web Awesome select ── */
|
||||
|
||||
wa-select.settings-select {
|
||||
--wa-form-control-activated-color: var(--primary);
|
||||
}
|
||||
|
||||
wa-select.settings-select::part(combobox) {
|
||||
min-height: var(--settings-control-height);
|
||||
padding-inline: 10px;
|
||||
border: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
|
||||
border-radius: var(--radius-md);
|
||||
background: color-mix(in srgb, var(--bg) 80%, var(--bg-elevated) 20%);
|
||||
color: var(--text);
|
||||
cursor: var(--cursor-action);
|
||||
transition: border-color var(--duration-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
/* [open] is required alongside :focus-within: Chromium does not propagate
|
||||
:focus-within to the host while a slotted option holds focus, so the open
|
||||
state must key off the reflected attribute or the stock ring leaks through. */
|
||||
wa-select.settings-select[open]::part(combobox),
|
||||
wa-select.settings-select:focus-within::part(combobox) {
|
||||
outline: none;
|
||||
border-color: color-mix(in srgb, var(--accent) 55%, var(--border) 45%);
|
||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 18%, transparent);
|
||||
}
|
||||
|
||||
wa-select.settings-select::part(expand-icon) {
|
||||
margin-inline-start: var(--space-2);
|
||||
font-size: var(--control-ui-text-sm);
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
wa-select.settings-select::part(listbox) {
|
||||
/* Keep Web Awesome's collision-aware bound in the cap; replacing it outright
|
||||
would let the listbox overflow when the popup has little room to open. */
|
||||
max-height: min(320px, var(--auto-size-available-height, calc(100vh - 48px)));
|
||||
padding: 6px;
|
||||
border: 1px solid color-mix(in srgb, var(--border-strong) 86%, transparent);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--bg-elevated);
|
||||
box-shadow: var(--shadow-lg);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
wa-select.settings-select wa-option {
|
||||
padding: 6px 8px;
|
||||
border-radius: calc(var(--radius-md) - 4px);
|
||||
font-size: var(--control-ui-text-md);
|
||||
color: var(--text);
|
||||
cursor: var(--cursor-action);
|
||||
}
|
||||
|
||||
/* The roving "current" option already carries the accent fill; the global
|
||||
:focus-visible accent outline on top of it is pure noise. */
|
||||
wa-select.settings-select wa-option:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
wa-select.settings-select wa-option:state(current) {
|
||||
color: var(--primary-foreground);
|
||||
}
|
||||
|
||||
.picker-select__option {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.picker-select [slot="start"] {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.picker-select__label {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.picker-select__copy {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.picker-select__description {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: var(--muted);
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.2;
|
||||
text-overflow: ellipsis;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.model-picker {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.model-picker__provider-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex: 0 0 20px;
|
||||
}
|
||||
|
||||
.model-picker__provider-icon.provider-brand-icon--fallback {
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.model-picker__custom[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Forced colors suppresses the box-shadow ring and accent fill that stand in
|
||||
for the outlines removed above, so restore system-color outlines there. */
|
||||
@media (forced-colors: active) {
|
||||
wa-select.settings-select[open]::part(combobox),
|
||||
wa-select.settings-select:focus-within::part(combobox) {
|
||||
outline: 2px solid SelectedItem;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
wa-select.settings-select wa-option:focus-visible {
|
||||
outline: 2px solid SelectedItem;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
textarea.settings-input {
|
||||
min-height: 88px;
|
||||
resize: vertical;
|
||||
@@ -952,20 +625,6 @@ textarea.settings-input {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
/* ── Count badge (quiet; genuine counts/alerts only, not status) ── */
|
||||
|
||||
.settings-count {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 1px 7px;
|
||||
border-radius: var(--radius-full);
|
||||
background: color-mix(in srgb, var(--bg-elevated) 80%, var(--border) 20%);
|
||||
color: var(--muted);
|
||||
font-size: var(--control-ui-text-xs);
|
||||
font-weight: 600;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* ── Empty state ── */
|
||||
|
||||
.settings-empty {
|
||||
|
||||
Reference in New Issue
Block a user