feat(editor): compact styles panel (#12163)

* fix(editor): align buttons inflating styles panel intrinsic width

The flex-wrap row break (zero-height flex-basis: 100% spacer) only breaks
the line at layout time; for intrinsic sizing both rows were measured as
a single line. Render the two rows as separate flex rows instead.

Opus 5.5 (1M context), low

* fix(editor): position range slider value label and track via CSS

The label position and filled track were computed in JS from the input's
offsetWidth, and only when the value changed, so they went stale when the
container resized. Drive both from a --range-progress CSS variable, and
drop the UA default inline margin that offset the track from the label.

Opus 5.5 (1M context), low

* feat(editor): compact styles panel

Styles panel buttons are 32px regardless of screen size, rows are closer
together, top-pick swatches are packed tighter, and the panel sizes to its
content (up to the previous fixed width).

Opus 5.5 (1M context), low

* reduce large icon size on 1920px+ screens

* fix color picker border color on mobile
This commit is contained in:
David Luzar
2026-09-24 22:18:52 +02:00
committed by GitHub
parent 1118751f3e
commit e40d1cba95
8 changed files with 64 additions and 62 deletions
@@ -92,6 +92,20 @@
}
}
// two separate rows (rather than a flex-wrap row break) so the panel's
// intrinsic width is that of a single row
.align-buttons {
display: flex;
flex-direction: column;
gap: 0.5rem;
&__row {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
}
.compact-shape-actions {
display: flex;
flex-direction: column;
+18 -25
View File
@@ -94,31 +94,24 @@ const AlignFieldset = ({
return (
<fieldset>
<legend>{t("labels.align")}</legend>
<div className="buttonList">
{isRTL ? (
<>
{renderAction("alignRight")}
{renderAction("alignHorizontallyCentered")}
{renderAction("alignLeft")}
</>
) : (
<>
{renderAction("alignLeft")}
{renderAction("alignHorizontallyCentered")}
{renderAction("alignRight")}
</>
)}
{showDistribute && renderAction("distributeHorizontally")}
{/* breaks the row ˇˇ */}
<div style={{ flexBasis: "100%", height: 0 }} />
<div
style={{
display: "flex",
flexWrap: "wrap",
gap: ".5rem",
marginTop: "-0.5rem",
}}
>
<div className="buttonList align-buttons">
<div className="align-buttons__row">
{isRTL ? (
<>
{renderAction("alignRight")}
{renderAction("alignHorizontallyCentered")}
{renderAction("alignLeft")}
</>
) : (
<>
{renderAction("alignLeft")}
{renderAction("alignHorizontallyCentered")}
{renderAction("alignRight")}
</>
)}
{showDistribute && renderAction("distributeHorizontally")}
</div>
<div className="align-buttons__row">
{renderAction("alignTop")}
{renderAction("alignVerticallyCentered")}
{renderAction("alignBottom")}
@@ -27,7 +27,7 @@
.color-picker-container {
display: grid;
grid-template-columns: 1fr 20px 1.625rem;
grid-template-columns: 1fr 15px 1.625rem;
padding: 0.25rem 0px;
align-items: center;
@@ -46,6 +46,7 @@
display: flex;
justify-content: space-between;
align-items: center;
gap: 5px;
}
.color-picker__button {
@@ -169,7 +170,7 @@
}
&.mobile-border {
border: 1px solid var(--mobile-color-border);
border: 1px solid var(--default-border-color);
}
}
+15 -1
View File
@@ -4,6 +4,9 @@
--slider-thumb-size: 16px;
.range-wrapper {
// 0..1, set from the value
--range-progress: 0;
position: relative;
padding-top: 10px;
padding-bottom: 25px;
@@ -11,9 +14,15 @@
.range-input {
width: 100%;
// UA default margin would offset the track from the value bubble
margin-inline: 0;
height: 4px;
-webkit-appearance: none;
background: var(--color-slider-track);
background: linear-gradient(
to right,
var(--color-slider-track) calc(var(--range-progress) * 100%),
var(--button-bg) calc(var(--range-progress) * 100%)
);
border-radius: 2px;
outline: none;
}
@@ -41,6 +50,11 @@
.value-bubble {
position: absolute;
bottom: 0;
// centered on the thumb, which travels (width - thumb size)
left: calc(
var(--slider-thumb-size) / 2 + (100% - var(--slider-thumb-size)) *
var(--range-progress)
);
transform: translateX(-50%);
font-size: 12px;
color: var(--text-primary-color);
+7 -27
View File
@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import React from "react";
import "./Range.scss";
@@ -25,39 +25,21 @@ export const Range = ({
hasCommonValue = true,
testId,
}: RangeProps) => {
const rangeRef = React.useRef<HTMLInputElement>(null);
const valueRef = React.useRef<HTMLDivElement>(null);
useEffect(() => {
if (rangeRef.current && valueRef.current) {
const rangeElement = rangeRef.current;
const valueElement = valueRef.current;
const inputWidth = rangeElement.offsetWidth;
const thumbWidth =
parseFloat(
getComputedStyle(rangeElement).getPropertyValue(
"--slider-thumb-size",
),
) || 16;
const progress = ((value - min) / (max - min || 1)) * 100;
const position =
(progress / 100) * (inputWidth - thumbWidth) + thumbWidth / 2;
valueElement.style.left = `${position}px`;
rangeElement.style.background = `linear-gradient(to right, var(--color-slider-track) 0%, var(--color-slider-track) ${progress}%, var(--button-bg) ${progress}%, var(--button-bg) 100%)`;
}
}, [max, min, value]);
const progress = (value - min) / (max - min || 1);
return (
<label className="control-label">
{label}
<div className="range-wrapper">
<div
className="range-wrapper"
style={{ ["--range-progress" as string]: progress }}
>
<input
style={{
["--color-slider-track" as string]: hasCommonValue
? undefined
: "var(--button-bg)",
}}
ref={rangeRef}
type="range"
min={min}
max={max}
@@ -69,9 +51,7 @@ export const Range = ({
className="range-input"
data-testid={testId}
/>
<div className="value-bubble" ref={valueRef}>
{value !== min ? value : null}
</div>
<div className="value-bubble">{value !== min ? value : null}</div>
<div className="zero-label">{minLabel}</div>
</div>
</label>
+4 -2
View File
@@ -158,7 +158,9 @@ body.excalidraw-cursor-resize * {
.selected-shape-actions {
display: flex;
flex-direction: column;
row-gap: 0.75rem;
row-gap: 0.5rem;
--default-button-size: 2rem;
h3,
legend,
@@ -494,7 +496,7 @@ body.excalidraw-cursor-resize * {
.App-menu__left {
overflow-y: auto;
padding: 0.75rem;
width: 12.5rem;
max-width: 12.5rem;
box-sizing: border-box;
position: absolute;
}
+1 -3
View File
@@ -10,7 +10,6 @@
--button-gray-2: #{$color-gray-4};
--button-gray-3: #{$color-gray-5};
--mobile-action-button-bg: rgba(255, 255, 255, 0.35);
--mobile-color-border: var(--default-border-color);
--button-special-active-bg-color: #{$color-green-0};
--dialog-border-color: var(--color-gray-20);
--dropdown-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="292.4" height="292.4" viewBox="0 0 292 292"><path d="M287 197L159 69c-4-3-8-5-13-5s-9 2-13 5L5 197c-3 4-5 8-5 13s2 9 5 13c4 4 8 5 13 5h256c5 0 9-1 13-5s5-8 5-13-1-9-5-13z"/></svg>');
@@ -170,7 +169,7 @@
}
@media screen and (min-device-width: 1921px) {
--lg-button-size: 2.5rem;
--lg-button-size: 2.375rem;
--lg-icon-size: 1.25rem;
--default-button-size: 2.25rem;
--default-icon-size: 1.25rem;
@@ -191,7 +190,6 @@
--button-gray-2: #272727;
--button-gray-3: #222;
--mobile-action-button-bg: var(--island-bg-color);
--mobile-color-border: rgba(255, 255, 255, 0.85);
--button-special-active-bg-color: #204624;
--dialog-border-color: var(--color-gray-80);
--dropdown-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="292.4" height="292.4" viewBox="0 0 292 292"><path fill="%23ced4da" d="M287 197L159 69c-4-3-8-5-13-5s-9 2-13 5L5 197c-3 4-5 8-5 13s2 9 5 13c4 4 8 5 13 5h256c5 0 9-1 13-5s5-8 5-13-1-9-5-13z"/></svg>');
@@ -52,8 +52,8 @@ $color-blue-8: #1971c2;
}
.ToolIcon__keybinding {
bottom: 4px;
right: 4px;
bottom: 2px;
right: 3px;
}
.ToolIcon__icon {