🐛 Wrap long shortcut key lists in the shortcuts panel (#12735)

* 🐛 Wrap long shortcut key lists in the shortcuts panel

The Next board and Previous board rows of the viewer have five key
combinations. Their keys ran past the panel edge and covered the label.

Since #11081 the label sits inside a span with no flex rules, and the
key list never wrapped. Now each combination is a group that never
breaks, and the list wraps between groups. The comma stays at the end
of its group and "or" leads the last one, so no line starts with a
separator. The label keeps its width and the keys take the rest.

Closes #12641

AI-assisted-by: claude-opus-5-5

* ♻️ Drop unused deprecated imports from the shortcuts styles

shortcuts.scss no longer uses anything from refactor/ or ds/mixins.
Remove both imports and use the typography module without a
namespace. No visual change.

AI-assisted-by: claude-opus-5-5
This commit is contained in:
Eva Marco 2026-10-30 14:12:49 +01:00 committed by GitHub
parent 759a2b301c
commit a69d5d15d2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 67 additions and 37 deletions

View File

@ -164,3 +164,25 @@ test.describe("Palette", () => {
).toBeVisible();
});
});
test.describe("Shortcuts panel", () => {
const shot = { maxDiffPixelRatio: 0, threshold: 0 };
test("Wraps long key lists between combinations", async ({ page }) => {
const workspace = new WorkspacePage(page);
await workspace.setupEmptyFile();
await workspace.goToWorkspace();
await page.keyboard.press("Shift+?");
const list = page.locator("[class*='shortcuts-list']");
await expect(list).toBeVisible();
await page.getByRole("textbox").first().fill("board");
const next = page.getByRole("listitem", { name: "Next board" });
const prev = page.getByRole("listitem", { name: "Previous board" });
await prev.scrollIntoViewIfNeeded();
await page.mouse.move(640, 300);
await expect(next).toHaveScreenshot(shot);
await expect(prev).toHaveScreenshot(shot);
});
});

View File

@ -539,39 +539,31 @@
(mf/defc shortcuts-keys*
[{:keys [content command is-customized has-conflict? light-shortcut]}]
(let [managed-list (if (coll? content)
content
(conj () content))
chars-list (map ds/split-sc managed-list)
last-element (last chars-list)
short-char-list (if (= 1 (count chars-list))
chars-list
(drop-last chars-list))
penultimate (last short-char-list)]
(let [managed-list (if (coll? content)
content
(conj () content))
chars-list (map ds/split-sc managed-list)
last-index (dec (count chars-list))
key-class (cond
has-conflict? (stl/css :conflict-key)
is-customized (stl/css :customized-key)
light-shortcut (stl/css :light-key))]
;; Each combination is a group that never breaks; the list wraps
;; between groups. The separator stays inside a group so no line
;; starts with a comma.
[:span {:class (stl/css :keys)}
(for [chars short-char-list]
[:* {:key (str/join chars)}
(for [[index chars] (d/enumerate chars-list)]
[:span {:key (dm/str index "-" (str/join chars))
:class (stl/css :key-group)}
(when (and (pos? index) (= index last-index))
[:span {:class (stl/css :space)} (tr "shortcuts.or")])
(for [char chars]
[:> converted-chars* {:key (dm/str char "-" (name command))
:char char
:command command
:class (cond
has-conflict? (stl/css :conflict-key)
is-customized (stl/css :customized-key)
light-shortcut (stl/css :light-key))}])
(when (not= chars penultimate) [:span {:class (stl/css :space)} ","])])
(when (not= last-element penultimate)
[:*
[:span {:class (stl/css :space)} (tr "shortcuts.or")]
(for [char last-element]
[:> converted-chars* {:key (dm/str char "-" (name command))
:char char
:command command
:class (cond
has-conflict? (stl/css :conflict-key)
is-customized (stl/css :customized-key)
light-shortcut (stl/css :light-key))}])])]))
:class key-class}])
(when (< index (dec last-index))
[:span {:class (stl/css :space)} ","])])]))
(mf/defc shortcut-row-editable*
[{:keys [elements custom-shortcuts command-translate conflicts section-key]}]
@ -863,7 +855,7 @@
:data-conflict (str has-conflict?)
:aria-label command-translate
:key command-translate}
[:span
[:span {:class (stl/css :command-label)}
[:span {:class (stl/css-case :command-name true
:not-customizable-label (not customizable?))
:id (dm/str command-translate "-label")}

View File

@ -4,12 +4,10 @@
//
// Copyright (c) KALEIDOS SUBSIDIARY SL
@use "refactor/common-refactor.scss" as deprecated;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
@use "ds/_utils.scss" as *;
@use "ds/mixins.scss" as *;
@use "ds/typography.scss" as t;
@use "ds/typography.scss" as *;
.section {
margin: 0;
@ -21,7 +19,7 @@
.section-title,
.subsection-title {
@include t.use-typography("headline-small");
@include use-typography("headline-small");
display: flex;
align-items: center;
@ -60,6 +58,7 @@
display: flex;
align-items: center;
justify-content: space-between;
column-gap: var(--sp-s);
inline-size: 100%;
min-block-size: $sz-32;
padding: px2rem(6);
@ -69,7 +68,7 @@
}
.command-name {
@include t.use-typography("body-small");
@include use-typography("body-small");
margin-inline-start: var(--sp-xxs);
color: var(--color-foreground-primary);
@ -78,6 +77,16 @@
text-align: start;
}
.command-label {
flex: 0 1 auto;
}
// The keys take the space the label leaves and wrap between groups.
.shortcuts-name .shortcut-actions {
flex: 1 1 0;
justify-content: flex-end;
}
.not-customizable-label {
padding-inline-start: px2rem(6);
}
@ -163,13 +172,20 @@
.keys {
display: flex;
justify-content: center;
flex-wrap: wrap;
justify-content: flex-end;
align-items: center;
gap: var(--sp-xxs);
}
.key-group {
display: flex;
align-items: center;
gap: var(--sp-xxs);
}
.key {
@include t.use-typography("body-small");
@include use-typography("body-small");
display: flex;
justify-content: center;
@ -203,7 +219,7 @@
}
.shortcut-placeholder {
@include t.use-typography("body-small");
@include use-typography("body-small");
color: var(--color-foreground-secondary);
}