* feat(editor): file tree 'open with' submenu (external editors, pin, SSH-remote, custom editors) The file tree right-click menu gains an 'Open with >' submenu with the resource manager (reveal in file manager), VS Code, Cursor and Zed, plus user-defined custom editors (name + URL template + VSCode-family flag). Each submenu row carries a pushpin that promotes the target to a direct top-level menu item. A configurable SSH host (settings: Files card gear) switches VSCode-family openers to <scheme>://vscode-remote/ssh-remote+<host>/<path> and hides host-local targets (file manager / Zed / non-VSCode custom editors). External opens run through the new fenced /sidebar/api/open.external route which spawns the platform opener with an argv array (no shell); the client never navigates raw scheme URLs itself. The settings seam's render panel now appends after the declarative rows instead of replacing them. * feat(editor): polish open-with menu UI — brand icons, pushpin, chevron, row alignment - Submenu rows: the custom label is now a block-level flex row filling the primitives itemLabel, so the pushpin docks at the row's far right (matching the trailing check of every other menu); menu row icons normalized to 16px (DSH native menus use 16, ours were 14). - The submenu parent row gains a right-aligned chevron (the primitives Menu renders no arrow for submenu parents). - Icons via react-icons: codicons VscPin/VscPinned pushpins (replacing the hand-drawn pin), VscFile/VscFolder/VscFolderOpened tree glyphs (replacing the </> file mark), VscLinkExternal for the open-with parent and the browser tab's open-external button (globally), SiCursor/SiZedindustries brand silhouettes; the VS Code mark is inlined from simple-icons@11.0.0 (CC0) as IconVscode16 since later simple-icons releases dropped it. All monochrome currentColor to follow the active skin. - tsdown: pin react-icons si/vsc to their ESM entries via resolve.alias — the package's exports map lists require before import, so the shared conditionNames picked the unshakeable CJS entry and bloated the core bundle by ~6.4 MB; tree-shaken it now adds only ~12 KB.
961 lines
25 KiB
CSS
961 lines
25 KiB
CSS
/**
|
|
* Side card settings section chrome (DSH-native tokens throughout):
|
|
* - the section is a column of GROUP CONTAINER cards (the PluginCard recipe:
|
|
* l2 hairline, 16px radius, layer-3 fill) with a heading + inventory count
|
|
* badge (the settings catalogHeading recipe), opening with a one-line
|
|
* intro (the DSH section heading+intro recipe);
|
|
* - 常规 holds full-width settings rows (the DSH settings-row recipe:
|
|
* title/desc left + control right, hairline separators) whose toggles are
|
|
* custom switches — a real checkbox (native semantics and focus) driving
|
|
* a styled track/thumb;
|
|
* - the feature inventories are responsive GRIDS of small toggle cards
|
|
* (auto-fill columns — several cards per row, wrapping): icon chip +
|
|
* title + type id, the card's on/off state IS its visual state
|
|
* (enabled = softened brand border + tinted fill + a compact brand
|
|
* switch knob at the far right — the card itself is the toggle;
|
|
* disabled = neutral hairline + dimmed, no knob); every card shares a
|
|
* uniform min-height so rows stay equal whether or not they carry a
|
|
* settings strip;
|
|
* - features that declare related settings carry a labeled SETTINGS STRIP
|
|
* across the card's bottom edge (gear icon + text, hairline-separated)
|
|
* opening a native Modal (wider than the primitive default) whose rows
|
|
* follow the same row recipe with switch controls and a Done footer;
|
|
* - the width input stays a native settings row (a value, not a switch).
|
|
*/
|
|
|
|
.section {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
width: 100%;
|
|
max-width: 760px;
|
|
/* The DSH settings shell's `.options` area owns vertical scrolling. Keep
|
|
this page a plain content column like Plugins → Plugin list so its
|
|
scrollbar stays at the shell edge instead of being inset by the options
|
|
area's right padding. */
|
|
}
|
|
|
|
/* One-line section intro (the DSH section heading+intro recipe). */
|
|
.intro {
|
|
margin: 0;
|
|
padding: 0 2px;
|
|
font-size: 13px;
|
|
line-height: 20px;
|
|
color: var(--dsw-alias-label-tertiary);
|
|
}
|
|
|
|
/* Group container: the DSH card recipe (PluginCard) — l2 hairline, r16,
|
|
layer-3 fill; holds its own heading + stacked rows / card grid. */
|
|
.group {
|
|
flex: none;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
padding: 20px;
|
|
box-sizing: border-box;
|
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
border-radius: 16px;
|
|
background: var(--dsw-alias-bg-layer-3);
|
|
}
|
|
|
|
/* Group heading: 13/600 + the inventory count badge (the settings
|
|
catalogHeading recipe: count in tabular-nums, tertiary). */
|
|
.groupHeading {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 7px;
|
|
padding: 0 2px 6px;
|
|
font-size: 13px;
|
|
line-height: 20px;
|
|
font-weight: 600;
|
|
color: var(--dsw-alias-label-primary);
|
|
}
|
|
|
|
/* The inventory count: a soft-accent pill (the versionBadgeTag token recipe)
|
|
so the count reads as metadata, not as part of the heading text. */
|
|
.count {
|
|
padding: 1px 8px;
|
|
border-radius: 999px;
|
|
background: var(--dsw-alias-accent-soft, var(--dsw-alias-bg-layer-2));
|
|
font-size: 11px;
|
|
line-height: 16px;
|
|
font-weight: 500;
|
|
color: var(--dsw-alias-label-secondary);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
/* Responsive inventory: as many small cards per row as the width allows. */
|
|
.grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
|
gap: 12px;
|
|
}
|
|
|
|
/* One small toggle card (visual chrome; the interactive part is .cardMain).
|
|
A uniform min-height keeps every inventory card the same height across
|
|
rows whether or not it carries a settings strip (strip cards are
|
|
naturally taller); overflow:hidden clips the strip to the card's radius. */
|
|
.card {
|
|
position: relative;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 106px;
|
|
overflow: hidden;
|
|
border-radius: 12px;
|
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
background: transparent;
|
|
font: inherit;
|
|
color: inherit;
|
|
cursor: pointer;
|
|
transition: background 0.12s ease, border-color 0.12s ease;
|
|
}
|
|
|
|
/* Hover affordance on disabled cards only (enabled cards are already lit). */
|
|
.card:not(.cardOn):hover {
|
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
border-color: var(--dsw-alias-label-dimmed);
|
|
}
|
|
|
|
/* Enabled = highlighted: a SOFTENED brand border (color-mix, the icon-chip
|
|
precedent) + tinted fill — full-strength brand reads harsh at card scale. */
|
|
.cardOn {
|
|
border-color: color-mix(in srgb, var(--dsw-alias-button-primary-fill) 45%, transparent);
|
|
background: var(--dsw-alias-interactive-bg-active);
|
|
}
|
|
|
|
/* The card's switch: the whole main area (top row + desc) flips the feature.
|
|
flex:1 lets the main area absorb the extra height of the uniform card
|
|
(cards without a settings strip stretch to match the strip cards). */
|
|
.cardMain {
|
|
display: flex;
|
|
flex: 1;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
width: 100%;
|
|
padding: 12px;
|
|
border: 0;
|
|
border-radius: inherit;
|
|
background: transparent;
|
|
font: inherit;
|
|
color: inherit;
|
|
text-align: left;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.cardMain:focus-visible,
|
|
.cardSettings:focus-visible,
|
|
.rowGear:focus-visible {
|
|
outline: 2px solid var(--dsw-alias-border-l4);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
.cardTop {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
min-width: 0;
|
|
min-height: 28px;
|
|
}
|
|
|
|
/* The icon sits in a rounded chip: brand-tinted while the card is on,
|
|
neutral (layer-2 fill + hairline) while off. */
|
|
.cardIconChip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex: none;
|
|
width: 28px;
|
|
height: 28px;
|
|
border-radius: 8px;
|
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
background: var(--dsw-alias-bg-layer-2);
|
|
color: var(--dsw-alias-label-tertiary);
|
|
}
|
|
|
|
.cardOn .cardIconChip {
|
|
border-color: color-mix(in srgb, var(--dsw-alias-button-primary-fill) 35%, transparent);
|
|
background: color-mix(in srgb, var(--dsw-alias-button-primary-fill) 12%, transparent);
|
|
color: var(--dsw-alias-button-primary-fill);
|
|
}
|
|
|
|
.cardTitle {
|
|
flex: 1;
|
|
min-width: 0;
|
|
font-size: 13px;
|
|
line-height: 20px;
|
|
font-weight: 600;
|
|
color: var(--dsw-alias-label-secondary);
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.cardOn .cardTitle {
|
|
color: var(--dsw-alias-label-primary);
|
|
}
|
|
|
|
/* The compact on/off switch knob at the card's far right (enabled only):
|
|
the card itself is the toggle (aria-pressed on .cardMain), so this is a
|
|
PURELY VISUAL switch — same track/thumb geometry as the row switches,
|
|
one size down. Off cards show no knob (neutral card = off). */
|
|
.cardSwitch {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
flex: none;
|
|
}
|
|
|
|
.cardSwitchTrack {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
width: 30px;
|
|
height: 16px;
|
|
padding: 2px;
|
|
box-sizing: border-box;
|
|
border-radius: 8px;
|
|
background: var(--dsw-alias-bg-layer-2);
|
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
transition: background 0.15s ease, border-color 0.15s ease;
|
|
}
|
|
|
|
.cardSwitchThumb {
|
|
display: block;
|
|
width: 10px;
|
|
height: 10px;
|
|
border-radius: 50%;
|
|
background: var(--dsw-alias-label-tertiary);
|
|
transition: transform 0.15s ease, background 0.15s ease;
|
|
}
|
|
|
|
.cardOn .cardSwitchTrack {
|
|
border-color: var(--dsw-alias-button-primary-fill);
|
|
background: var(--dsw-alias-button-primary-fill);
|
|
}
|
|
|
|
.cardOn .cardSwitchThumb {
|
|
transform: translateX(14px);
|
|
background: var(--dsw-alias-bg-layer-3);
|
|
}
|
|
|
|
.cardDesc {
|
|
font-size: 11px;
|
|
line-height: 16px;
|
|
color: var(--dsw-alias-label-tertiary);
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
/* The "add plugin" card: same grid cell / chrome as the inventory cards
|
|
(it shares .card), but a DASHED border — the repo's first dashed surface;
|
|
the hover accent follows the existing drag-drop dashed outlines
|
|
(sidebar.module.css .dropCenter / .tabBarDrop). */
|
|
.addCard {
|
|
align-items: flex-start;
|
|
padding: 12px;
|
|
border-style: dashed;
|
|
border-color: var(--dsw-alias-border-l2);
|
|
text-align: left;
|
|
}
|
|
|
|
.addCard:hover {
|
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
border-color: var(--dsw-alias-interactive-bg-hover-accent);
|
|
color: var(--dsw-alias-label-primary);
|
|
}
|
|
|
|
.addCard:hover .cardTitle {
|
|
color: var(--dsw-alias-label-primary);
|
|
}
|
|
|
|
.addCard:hover .cardIconChip {
|
|
border-color: color-mix(in srgb, var(--dsw-alias-button-primary-fill) 35%, transparent);
|
|
color: var(--dsw-alias-button-primary-fill);
|
|
}
|
|
|
|
.addCard:focus-visible {
|
|
outline: 2px solid var(--dsw-alias-border-l4);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
.cardOn .cardDesc {
|
|
color: var(--dsw-alias-label-secondary);
|
|
}
|
|
|
|
/* The settings strip: a labeled full-width footer across the card's bottom
|
|
edge (gear icon + "功能设置" text), hairline-separated from the toggle
|
|
area. This is the SECONDARY settings entry — it must be discoverable at
|
|
rest (not a hover-only ghost), so it keeps a steady secondary-ink label
|
|
and answers hover with the brand-accent tint + brand ink. */
|
|
.cardSettings {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
width: 100%;
|
|
padding: 6px 12px;
|
|
border: 0;
|
|
border-top: 1px solid var(--dsw-alias-border-l1);
|
|
background: transparent;
|
|
color: var(--dsw-alias-label-secondary);
|
|
font: inherit;
|
|
font-size: 11px;
|
|
line-height: 16px;
|
|
font-weight: 500;
|
|
text-align: left;
|
|
cursor: pointer;
|
|
transition: background 0.12s ease, color 0.12s ease;
|
|
}
|
|
|
|
.cardOn .cardSettings {
|
|
border-top-color: color-mix(in srgb, var(--dsw-alias-button-primary-fill) 18%, transparent);
|
|
}
|
|
|
|
.cardSettings:hover {
|
|
background: var(--dsw-alias-interactive-bg-hover-accent);
|
|
color: var(--dsw-alias-brand-primary);
|
|
}
|
|
|
|
/* The gear icon button on a general settings ROW (the position-compat
|
|
row's "customize the strip height" entry): a bordered chip (not a ghost)
|
|
so it reads as a control at rest; hover answers with the accent tint. */
|
|
.rowGear {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 22px;
|
|
height: 22px;
|
|
padding: 0;
|
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
border-radius: 6px;
|
|
background: transparent;
|
|
color: var(--dsw-alias-label-secondary);
|
|
cursor: pointer;
|
|
flex: none;
|
|
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
|
|
}
|
|
|
|
.rowGear:hover {
|
|
border-color: var(--dsw-alias-interactive-bg-hover-accent);
|
|
background: var(--dsw-alias-interactive-bg-hover-accent);
|
|
color: var(--dsw-alias-brand-primary);
|
|
}
|
|
|
|
/* Settings-row recipe (the DSH General section rows): title/desc left,
|
|
control right, hairline separators (stripped on the last row). */
|
|
.row {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 16px;
|
|
padding: 12px 2px;
|
|
border-bottom: 1px solid var(--dsw-alias-border-l2);
|
|
}
|
|
|
|
.row:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.rowText {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
min-width: 0;
|
|
}
|
|
|
|
.title {
|
|
font-size: 14px;
|
|
line-height: 22px;
|
|
color: var(--dsw-alias-label-primary);
|
|
}
|
|
|
|
.desc {
|
|
font-size: 12px;
|
|
line-height: 18px;
|
|
color: var(--dsw-alias-label-tertiary);
|
|
}
|
|
|
|
/* Custom switch: a real checkbox (native semantics + focus, visually
|
|
hidden) driving a styled 36x20 track with a sliding thumb. */
|
|
.switch {
|
|
position: relative;
|
|
display: inline-flex;
|
|
flex: none;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.switchInput {
|
|
position: absolute;
|
|
width: 1px;
|
|
height: 1px;
|
|
margin: 0;
|
|
opacity: 0;
|
|
}
|
|
|
|
.switchTrack {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
width: 36px;
|
|
height: 20px;
|
|
padding: 2px;
|
|
box-sizing: border-box;
|
|
border-radius: 10px;
|
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
background: var(--dsw-alias-bg-layer-2);
|
|
transition: background 0.15s ease, border-color 0.15s ease;
|
|
}
|
|
|
|
.switchThumb {
|
|
display: block;
|
|
width: 14px;
|
|
height: 14px;
|
|
border-radius: 50%;
|
|
background: var(--dsw-alias-label-tertiary);
|
|
transition: transform 0.15s ease, background 0.15s ease;
|
|
}
|
|
|
|
.switch:hover .switchTrack {
|
|
border-color: var(--dsw-alias-label-dimmed);
|
|
}
|
|
|
|
.switchInput:checked + .switchTrack {
|
|
border-color: var(--dsw-alias-button-primary-fill);
|
|
background: var(--dsw-alias-button-primary-fill);
|
|
}
|
|
|
|
.switchInput:checked + .switchTrack .switchThumb {
|
|
transform: translateX(16px);
|
|
background: var(--dsw-alias-bg-layer-3);
|
|
}
|
|
|
|
.switchInput:focus-visible + .switchTrack {
|
|
outline: 2px solid var(--dsw-alias-state-business-primary);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
.control {
|
|
flex: none;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.percentInput {
|
|
width: 76px;
|
|
}
|
|
|
|
/* Text/number rows in the settings popup: the free-form input (e.g. the
|
|
terminal font family) gets a generous width; numeric rows (font size)
|
|
keep the compact width of the general number inputs. */
|
|
.typedInput {
|
|
width: 200px;
|
|
}
|
|
|
|
.typedInputNumber {
|
|
width: 76px;
|
|
}
|
|
|
|
/* Select rows: the closed anchor is a compact button (selected option's
|
|
icon when the options carry icons, plus title and a chevron); the dropdown
|
|
itself is the primitives Menu (portal). */
|
|
.selectAnchor {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
max-width: 220px;
|
|
padding: 4px 8px;
|
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
border-radius: 8px;
|
|
background: var(--dsw-alias-bg-layer-3);
|
|
color: var(--dsw-alias-label-primary);
|
|
font-size: 13px;
|
|
line-height: 20px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.selectAnchor:hover {
|
|
border-color: var(--dsw-alias-label-dimmed);
|
|
}
|
|
|
|
.selectAnchorIcon {
|
|
display: inline-flex;
|
|
flex: none;
|
|
}
|
|
|
|
.selectAnchorText {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* One big-icon option card inside the select dropdown. */
|
|
.selectOption {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
min-width: 200px;
|
|
}
|
|
|
|
.selectOptionIcon {
|
|
display: inline-flex;
|
|
flex: none;
|
|
color: var(--dsw-alias-label-secondary);
|
|
}
|
|
|
|
.selectOptionText {
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-width: 0;
|
|
}
|
|
|
|
.suffix {
|
|
font-size: 14px;
|
|
line-height: 22px;
|
|
color: var(--dsw-alias-label-secondary);
|
|
}
|
|
|
|
/* The multi-line custom-CSS input (scheme `custom`): monospace, generous
|
|
height, resizable — same surface tokens as the typed inputs. */
|
|
.cssTextArea {
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
min-height: 120px;
|
|
padding: 8px 10px;
|
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
border-radius: 8px;
|
|
background: var(--dsw-alias-bg-layer-3);
|
|
color: var(--dsw-alias-label-primary);
|
|
font-family: var(--ds-font-family-code, monospace);
|
|
font-size: 12px;
|
|
line-height: 1.6;
|
|
resize: vertical;
|
|
}
|
|
|
|
.cssTextArea:focus-visible {
|
|
outline: 2px solid var(--dsw-alias-state-business-primary);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
/* Secondary settings popup: a wider dialog than the primitive default (the
|
|
dialog portals to document.body; the doubled class keeps the width rule
|
|
above the primitive's .dialog regardless of stylesheet order). */
|
|
.popupDialog.popupDialog {
|
|
width: min(460px, 100%);
|
|
}
|
|
|
|
/* Popup rows: each setting gets the same outer chrome as a plugin-list
|
|
PluginCard (l2 hairline, 12px radius, layer-3 fill). The row's title,
|
|
description and control geometry stay unchanged inside that frame.
|
|
The body scrolls INTERNALLY once a feature declares many rows (e.g.
|
|
Terminal's six) — the dialog shell and its Done footer stay put; a thin
|
|
token-driven scrollbar makes the scrollable region discoverable.
|
|
box-sizing:border-box keeps width:100% + the scrollbar gutter from
|
|
overflowing horizontally (a content-box width would add the 4px pad and
|
|
show a horizontal scrollbar); overflow-x:hidden is the belt-and-braces
|
|
guarantee that rows always FILL the width and never slide sideways. */
|
|
.popupRows {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
max-height: min(52vh, 440px);
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
padding-right: 4px;
|
|
scrollbar-width: thin;
|
|
scrollbar-color: var(--dsw-alias-scrollbar-bg-l2, transparent) transparent;
|
|
}
|
|
|
|
.popupRows::-webkit-scrollbar {
|
|
width: 6px;
|
|
}
|
|
|
|
.popupRows::-webkit-scrollbar-thumb {
|
|
border-radius: 3px;
|
|
background: var(--dsw-alias-scrollbar-bg-l2, var(--dsw-alias-border-l2));
|
|
}
|
|
|
|
.popupRows::-webkit-scrollbar-thumb:hover {
|
|
background: var(--dsw-alias-scrollbar-hover-l2, var(--dsw-alias-label-dimmed));
|
|
}
|
|
|
|
.popupRow {
|
|
flex: none;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 16px;
|
|
min-width: 0;
|
|
padding: 12px 14px;
|
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
border-radius: 12px;
|
|
background: var(--dsw-alias-bg-layer-3);
|
|
transition: border-color 0.16s, background 0.16s;
|
|
}
|
|
|
|
.popupRow:hover {
|
|
border-color: var(--dsw-alias-label-dimmed);
|
|
}
|
|
|
|
/* The popup's Done button: the settings primary-button recipe
|
|
(PluginCard .save: label-primary fill, inverted surface text). */
|
|
.done {
|
|
appearance: none;
|
|
border: 1px solid transparent;
|
|
border-radius: 8px;
|
|
padding: 5px 14px;
|
|
font: inherit;
|
|
font-size: 13px;
|
|
line-height: 1.5;
|
|
cursor: pointer;
|
|
background: var(--dsw-alias-label-primary);
|
|
color: var(--dsw-alias-bg-layer-3);
|
|
}
|
|
|
|
.done:focus-visible {
|
|
outline: 2px solid var(--dsw-alias-brand-primary);
|
|
outline-offset: 1px;
|
|
}
|
|
|
|
.error {
|
|
padding: 10px 0 2px;
|
|
font-size: 12px;
|
|
line-height: 17px;
|
|
color: var(--dsw-alias-state-error-primary);
|
|
}
|
|
|
|
/* ── "Add plugin" modal ──────────────────────────────────────────────── */
|
|
|
|
/* A wide dialog like the settings popup (doubled class keeps the width
|
|
rule above the primitive's .dialog regardless of stylesheet order). */
|
|
.pluginModal.pluginModal {
|
|
width: min(560px, 100%);
|
|
}
|
|
|
|
.pluginList {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
width: 100%;
|
|
}
|
|
|
|
/* The GitHub topic entry: a full-width secondary BUTTON (not a link — it
|
|
opens a real new browser tab via window.open, bypassing the sidebar
|
|
link takeover). */
|
|
.pluginTopicBtn {
|
|
width: 100%;
|
|
appearance: none;
|
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
border-radius: 8px;
|
|
padding: 6px 12px;
|
|
font: inherit;
|
|
font-size: 12px;
|
|
line-height: 18px;
|
|
color: var(--dsw-alias-label-secondary);
|
|
background: var(--dsw-alias-bg-layer-1);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.pluginTopicBtn:hover {
|
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
border-color: var(--dsw-alias-interactive-bg-hover-accent);
|
|
color: var(--dsw-alias-label-primary);
|
|
}
|
|
|
|
.pluginTopicBtn:focus-visible {
|
|
outline: 2px solid var(--dsw-alias-border-l4);
|
|
outline-offset: 1px;
|
|
}
|
|
|
|
.pluginEmpty {
|
|
padding: 20px 2px;
|
|
font-size: 12px;
|
|
line-height: 18px;
|
|
color: var(--dsw-alias-label-tertiary);
|
|
}
|
|
|
|
/* One catalog entry: name + install button on the head row, description,
|
|
then the install command in a mono block (the exact script that gets
|
|
pre-typed into the terminal — review it here before installing). */
|
|
.pluginEntry {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
padding: 12px;
|
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
border-radius: 12px;
|
|
background: var(--dsw-alias-bg-layer-1);
|
|
}
|
|
|
|
.pluginEntryHead {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
}
|
|
|
|
.pluginEntryActions {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
flex: none;
|
|
}
|
|
|
|
/* The per-entry "jump" button: opens the plugin repo in a REAL new browser
|
|
tab (window.open — a button, so the sidebar link takeover cannot reroute
|
|
it). Secondary style, quieter than the Install button. */
|
|
.pluginJumpBtn {
|
|
flex: none;
|
|
appearance: none;
|
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
border-radius: 8px;
|
|
padding: 3px 12px;
|
|
font: inherit;
|
|
font-size: 12px;
|
|
line-height: 1.5;
|
|
cursor: pointer;
|
|
background: transparent;
|
|
color: var(--dsw-alias-label-secondary);
|
|
}
|
|
|
|
.pluginJumpBtn:hover {
|
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
border-color: var(--dsw-alias-interactive-bg-hover-accent);
|
|
color: var(--dsw-alias-label-primary);
|
|
}
|
|
|
|
.pluginJumpBtn:focus-visible {
|
|
outline: 2px solid var(--dsw-alias-border-l4);
|
|
outline-offset: 1px;
|
|
}
|
|
|
|
/* The plugin name: a BUTTON (not an anchor — an anchor would be caught by
|
|
the sidebar link takeover at document capture, ignoring target=_blank)
|
|
styled like a link; opens the repo in a real new browser tab. */
|
|
.pluginName {
|
|
appearance: none;
|
|
min-width: 0;
|
|
padding: 0;
|
|
border: 0;
|
|
background: transparent;
|
|
font: inherit;
|
|
font-size: 13px;
|
|
line-height: 20px;
|
|
font-weight: 600;
|
|
color: var(--dsw-alias-label-primary);
|
|
text-align: left;
|
|
text-decoration: none;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.pluginName:hover {
|
|
text-decoration: underline;
|
|
color: var(--dsw-alias-button-primary-fill);
|
|
}
|
|
|
|
.pluginDesc {
|
|
font-size: 12px;
|
|
line-height: 18px;
|
|
color: var(--dsw-alias-label-secondary);
|
|
}
|
|
|
|
.pluginInstall {
|
|
display: block;
|
|
padding: 6px 10px;
|
|
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
font-size: 11px;
|
|
line-height: 16px;
|
|
color: var(--dsw-alias-label-secondary);
|
|
background: var(--dsw-alias-bg-layer-2);
|
|
border: 1px solid var(--dsw-alias-border-l1);
|
|
border-radius: 8px;
|
|
overflow-x: auto;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* The per-entry copy button: copies the install script to the
|
|
clipboard (the settings primary-button recipe, smaller than .done). */
|
|
.pluginCopyBtn {
|
|
flex: none;
|
|
appearance: none;
|
|
border: 1px solid transparent;
|
|
border-radius: 8px;
|
|
padding: 3px 12px;
|
|
font: inherit;
|
|
font-size: 12px;
|
|
line-height: 1.5;
|
|
cursor: pointer;
|
|
background: var(--dsw-alias-label-primary);
|
|
color: var(--dsw-alias-bg-layer-3);
|
|
}
|
|
|
|
.pluginCopyBtn:hover {
|
|
background: var(--dsw-alias-button-primary-hover);
|
|
}
|
|
|
|
.pluginCopyBtn:focus-visible {
|
|
outline: 2px solid var(--dsw-alias-brand-primary);
|
|
outline-offset: 1px;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.card,
|
|
.cardSettings,
|
|
.cardSwitchTrack,
|
|
.cardSwitchThumb,
|
|
.rowGear,
|
|
.popupRow,
|
|
.switchTrack,
|
|
.switchThumb {
|
|
transition: none;
|
|
}
|
|
}
|
|
|
|
/* The managing plugin's identity badge: name + version pill, token-driven
|
|
(labels via --dsw-alias-label-*, surface via --dsw-alias-bg-layer-2). */
|
|
.versionBadge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
align-self: flex-start;
|
|
padding: 4px 12px 4px 14px;
|
|
border-radius: 999px;
|
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
background: var(--dsw-alias-bg-layer-2);
|
|
font-size: 12px;
|
|
line-height: 18px;
|
|
}
|
|
|
|
.versionBadgeName {
|
|
color: var(--dsw-alias-label-primary);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.versionBadgeTag {
|
|
padding: 1px 8px;
|
|
border-radius: 999px;
|
|
background: var(--dsw-alias-accent-soft, var(--dsw-alias-border-l2));
|
|
color: var(--dsw-alias-label-secondary);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
/* Catalog search input: a compact text field above the list (token-driven).
|
|
The entries list below it scrolls INDEPENDENTLY — with many plugins the
|
|
modal stays navigable instead of growing unbounded. */
|
|
.pluginSearch {
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
appearance: none;
|
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
border-radius: 8px;
|
|
padding: 6px 10px;
|
|
font: inherit;
|
|
font-size: 12px;
|
|
line-height: 18px;
|
|
color: var(--dsw-alias-label-primary);
|
|
background: var(--dsw-alias-bg-layer-1);
|
|
}
|
|
|
|
.pluginSearch::placeholder {
|
|
color: var(--dsw-alias-label-tertiary);
|
|
}
|
|
|
|
.pluginSearch:focus-visible {
|
|
outline: 2px solid var(--dsw-alias-border-l4);
|
|
outline-offset: 1px;
|
|
}
|
|
|
|
/* The entries area scrolls within the modal once the catalog grows past
|
|
the viewport (max-height + own scrollbar keeps the modal shell fixed). */
|
|
.pluginEntries {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
max-height: 46vh;
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
padding-right: 2px;
|
|
}
|
|
|
|
/* One catalog group: optional category heading above its stacked entries. */
|
|
.pluginGroup {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.pluginGroupHeading {
|
|
font-size: 12px;
|
|
line-height: 18px;
|
|
font-weight: 600;
|
|
color: var(--dsw-alias-label-secondary);
|
|
padding: 2px 2px 0;
|
|
}
|
|
|
|
/* Open-with settings panel: one custom editor row (name + URL template +
|
|
VSCode-family checkbox + remove) and the row-validity hint. */
|
|
.openWithEditorRow {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1.5fr auto auto;
|
|
gap: 8px;
|
|
align-items: center;
|
|
min-width: 0;
|
|
}
|
|
|
|
.openWithEditorInput,
|
|
.openWithEditorTemplate {
|
|
width: 100%;
|
|
min-width: 0;
|
|
box-sizing: border-box;
|
|
padding: 5px 8px;
|
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
border-radius: 8px;
|
|
background: var(--dsw-alias-bg-layer-3);
|
|
color: var(--dsw-alias-label-primary);
|
|
font: inherit;
|
|
font-size: 13px;
|
|
line-height: 20px;
|
|
}
|
|
|
|
.openWithEditorInput:focus-visible,
|
|
.openWithEditorTemplate:focus-visible {
|
|
outline: 2px solid var(--dsw-alias-state-business-primary);
|
|
outline-offset: 1px;
|
|
}
|
|
|
|
.openWithFamily {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
font-size: 12px;
|
|
color: var(--dsw-alias-label-secondary);
|
|
white-space: nowrap;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.openWithRemove {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 4px;
|
|
border: none;
|
|
border-radius: 8px;
|
|
background: none;
|
|
color: var(--dsw-alias-label-tertiary);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.openWithRemove:hover {
|
|
color: var(--dsw-alias-state-error-primary);
|
|
background: var(--dsw-alias-bg-layer-2);
|
|
}
|
|
|
|
.openWithHint {
|
|
font-size: 12px;
|
|
line-height: 17px;
|
|
color: var(--dsw-alias-state-error-primary);
|
|
padding: 0 2px;
|
|
}
|