mirror of
https://github.com/excalidraw/excalidraw.git
synced 2026-10-19 16:15:56 +00:00
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:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user