From e40d1cba95012fdabfea96cc4c854194ff92f7dd Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Thu, 24 Sep 2026 22:18:52 +0200 Subject: [PATCH] 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 --- packages/excalidraw/components/Actions.scss | 14 ++++++ packages/excalidraw/components/Actions.tsx | 43 ++++++++----------- .../components/ColorPicker/ColorPicker.scss | 5 ++- packages/excalidraw/components/Range.scss | 16 ++++++- packages/excalidraw/components/Range.tsx | 34 +++------------ packages/excalidraw/css/styles.scss | 6 ++- packages/excalidraw/css/theme.scss | 4 +- packages/excalidraw/css/variables.module.scss | 4 +- 8 files changed, 64 insertions(+), 62 deletions(-) diff --git a/packages/excalidraw/components/Actions.scss b/packages/excalidraw/components/Actions.scss index f97f3c7b6f..26930a533f 100644 --- a/packages/excalidraw/components/Actions.scss +++ b/packages/excalidraw/components/Actions.scss @@ -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; diff --git a/packages/excalidraw/components/Actions.tsx b/packages/excalidraw/components/Actions.tsx index b8a314d038..6e09452237 100644 --- a/packages/excalidraw/components/Actions.tsx +++ b/packages/excalidraw/components/Actions.tsx @@ -94,31 +94,24 @@ const AlignFieldset = ({ return (
{t("labels.align")} -
- {isRTL ? ( - <> - {renderAction("alignRight")} - {renderAction("alignHorizontallyCentered")} - {renderAction("alignLeft")} - - ) : ( - <> - {renderAction("alignLeft")} - {renderAction("alignHorizontallyCentered")} - {renderAction("alignRight")} - - )} - {showDistribute && renderAction("distributeHorizontally")} - {/* breaks the row ˇˇ */} -
-
+
+
+ {isRTL ? ( + <> + {renderAction("alignRight")} + {renderAction("alignHorizontallyCentered")} + {renderAction("alignLeft")} + + ) : ( + <> + {renderAction("alignLeft")} + {renderAction("alignHorizontallyCentered")} + {renderAction("alignRight")} + + )} + {showDistribute && renderAction("distributeHorizontally")} +
+
{renderAction("alignTop")} {renderAction("alignVerticallyCentered")} {renderAction("alignBottom")} diff --git a/packages/excalidraw/components/ColorPicker/ColorPicker.scss b/packages/excalidraw/components/ColorPicker/ColorPicker.scss index 30af2fe56e..6c5d93bc77 100644 --- a/packages/excalidraw/components/ColorPicker/ColorPicker.scss +++ b/packages/excalidraw/components/ColorPicker/ColorPicker.scss @@ -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); } } diff --git a/packages/excalidraw/components/Range.scss b/packages/excalidraw/components/Range.scss index 7849763345..d62cd631e9 100644 --- a/packages/excalidraw/components/Range.scss +++ b/packages/excalidraw/components/Range.scss @@ -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); diff --git a/packages/excalidraw/components/Range.tsx b/packages/excalidraw/components/Range.tsx index 7a4bf397e7..5749c4fe03 100644 --- a/packages/excalidraw/components/Range.tsx +++ b/packages/excalidraw/components/Range.tsx @@ -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(null); - const valueRef = React.useRef(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 (