2
0
Эх сурвалжийг харах

feat: tweak color swatch, and button bgs (#9330)

* feat: tweak color swatch, and button bgs

* snapshots
David Luzar 4 сар өмнө
parent
commit
57a9e301d4

+ 2 - 0
packages/common/src/colors.ts

@@ -2,6 +2,8 @@ import oc from "open-color";
 
 import type { Merge } from "./utility-types";
 
+export const COLOR_OUTLINE_CONTRAST_THRESHOLD = 240;
+
 // FIXME can't put to utils.ts rn because of circular dependency
 const pick = <R extends Record<string, any>, K extends readonly (keyof R)[]>(
   source: R,

+ 22 - 12
packages/excalidraw/components/ColorPicker/ColorPicker.scss

@@ -15,7 +15,7 @@
 
   .color-picker-container {
     display: grid;
-    grid-template-columns: 1fr 8px 1.625rem;
+    grid-template-columns: 1fr 20px 1.625rem;
     padding: 0.25rem 0px;
     align-items: center;
 
@@ -27,14 +27,19 @@
   .color-picker__top-picks {
     display: flex;
     justify-content: space-between;
+    align-items: center;
   }
 
   .color-picker__button {
-    --radius: 6px;
+    --radius: 4px;
     --size: 1.375rem;
 
+    &.has-outline {
+      box-shadow: inset 0 0 0 1px #d9d9d9;
+    }
+
     padding: 0;
-    margin: 1px;
+    margin: 0;
     width: var(--size);
     height: var(--size);
     border: 0;
@@ -46,15 +51,19 @@
     font-family: inherit;
     box-sizing: border-box;
 
-    &:hover:not(.active) {
+    &:hover:not(.active):not(.color-picker__button--large) {
+      transform: scale(1.075);
+    }
+
+    &:hover:not(.active).color-picker__button--large {
       &::after {
         content: "";
         position: absolute;
-        top: 0;
-        left: 0;
-        right: 0;
-        bottom: 0;
-        box-shadow: 0 0 0 1px var(--swatch-color);
+        top: -1px;
+        left: -1px;
+        right: -1px;
+        bottom: -1px;
+        box-shadow: 0 0 0 1px var(--color-gray-30);
         border-radius: var(--radius);
         filter: var(--theme-filter);
       }
@@ -70,7 +79,7 @@
         bottom: var(--offset);
         box-shadow: 0 0 0 1px var(--color-primary-darkest);
         z-index: 1; // due hover state so this has preference
-        border-radius: calc(var(--radius) + 1px);
+        border-radius: var(--radius);
         filter: var(--theme-filter);
       }
     }
@@ -125,10 +134,11 @@
 
   .color-picker__button__hotkey-label {
     position: absolute;
-    right: 4px;
-    bottom: 4px;
+    right: 5px;
+    bottom: 3px;
     filter: none;
     font-size: 11px;
+    font-weight: 500;
   }
 
   .color-picker {

+ 7 - 2
packages/excalidraw/components/ColorPicker/ColorPicker.tsx

@@ -2,7 +2,11 @@ import * as Popover from "@radix-ui/react-popover";
 import clsx from "clsx";
 import { useRef } from "react";
 
-import { COLOR_PALETTE, isTransparent } from "@excalidraw/common";
+import {
+  COLOR_OUTLINE_CONTRAST_THRESHOLD,
+  COLOR_PALETTE,
+  isTransparent,
+} from "@excalidraw/common";
 
 import type { ColorTuple, ColorPaletteCustom } from "@excalidraw/common";
 
@@ -19,7 +23,7 @@ import { ColorInput } from "./ColorInput";
 import { Picker } from "./Picker";
 import PickerHeading from "./PickerHeading";
 import { TopPicks } from "./TopPicks";
-import { activeColorPickerSectionAtom } from "./colorPickerUtils";
+import { activeColorPickerSectionAtom, isColorDark } from "./colorPickerUtils";
 
 import "./ColorPicker.scss";
 
@@ -190,6 +194,7 @@ const ColorPickerTrigger = ({
       type="button"
       className={clsx("color-picker__button active-color properties-trigger", {
         "is-transparent": color === "transparent" || !color,
+        "has-outline": !isColorDark(color, COLOR_OUTLINE_CONTRAST_THRESHOLD),
       })}
       aria-label={label}
       style={color ? { "--swatch-color": color } : undefined}

+ 2 - 2
packages/excalidraw/components/ColorPicker/CustomColorList.tsx

@@ -40,7 +40,7 @@ export const CustomColorList = ({
             tabIndex={-1}
             type="button"
             className={clsx(
-              "color-picker__button color-picker__button--large",
+              "color-picker__button color-picker__button--large has-outline",
               {
                 active: color === c,
                 "is-transparent": c === "transparent" || !c,
@@ -56,7 +56,7 @@ export const CustomColorList = ({
             key={i}
           >
             <div className="color-picker__button-outline" />
-            <HotkeyLabel color={c} keyLabel={i + 1} isCustomColor />
+            <HotkeyLabel color={c} keyLabel={i + 1} />
           </button>
         );
       })}

+ 2 - 4
packages/excalidraw/components/ColorPicker/HotkeyLabel.tsx

@@ -1,24 +1,22 @@
 import React from "react";
 
-import { getContrastYIQ } from "./colorPickerUtils";
+import { isColorDark } from "./colorPickerUtils";
 
 interface HotkeyLabelProps {
   color: string;
   keyLabel: string | number;
-  isCustomColor?: boolean;
   isShade?: boolean;
 }
 const HotkeyLabel = ({
   color,
   keyLabel,
-  isCustomColor = false,
   isShade = false,
 }: HotkeyLabelProps) => {
   return (
     <div
       className="color-picker__button__hotkey-label"
       style={{
-        color: getContrastYIQ(color, isCustomColor),
+        color: isColorDark(color) ? "#fff" : "#000",
       }}
     >
       {isShade && "⇧"}

+ 1 - 1
packages/excalidraw/components/ColorPicker/PickerColorList.tsx

@@ -65,7 +65,7 @@ const PickerColorList = ({
             tabIndex={-1}
             type="button"
             className={clsx(
-              "color-picker__button color-picker__button--large",
+              "color-picker__button color-picker__button--large has-outline",
               {
                 active: colorObj?.colorName === key,
                 "is-transparent": color === "transparent" || !color,

+ 1 - 1
packages/excalidraw/components/ColorPicker/ShadeList.tsx

@@ -55,7 +55,7 @@ export const ShadeList = ({ hex, onChange, palette }: ShadeListProps) => {
               key={i}
               type="button"
               className={clsx(
-                "color-picker__button color-picker__button--large",
+                "color-picker__button color-picker__button--large has-outline",
                 { active: i === shade },
               )}
               aria-label="Shade"

+ 7 - 0
packages/excalidraw/components/ColorPicker/TopPicks.tsx

@@ -1,11 +1,14 @@
 import clsx from "clsx";
 
 import {
+  COLOR_OUTLINE_CONTRAST_THRESHOLD,
   DEFAULT_CANVAS_BACKGROUND_PICKS,
   DEFAULT_ELEMENT_BACKGROUND_PICKS,
   DEFAULT_ELEMENT_STROKE_PICKS,
 } from "@excalidraw/common";
 
+import { isColorDark } from "./colorPickerUtils";
+
 import type { ColorPickerType } from "./colorPickerUtils";
 
 interface TopPicksProps {
@@ -51,6 +54,10 @@ export const TopPicks = ({
           className={clsx("color-picker__button", {
             active: color === activeColor,
             "is-transparent": color === "transparent" || !color,
+            "has-outline": !isColorDark(
+              color,
+              COLOR_OUTLINE_CONTRAST_THRESHOLD,
+            ),
           })}
           style={{ "--swatch-color": color }}
           key={color}

+ 39 - 19
packages/excalidraw/components/ColorPicker/colorPickerUtils.ts

@@ -93,19 +93,42 @@ export type ActiveColorPickerSectionAtomType =
 export const activeColorPickerSectionAtom =
   atom<ActiveColorPickerSectionAtomType>(null);
 
-const calculateContrast = (r: number, g: number, b: number) => {
+const calculateContrast = (r: number, g: number, b: number): number => {
   const yiq = (r * 299 + g * 587 + b * 114) / 1000;
-  return yiq >= 160 ? "black" : "white";
+  return yiq;
 };
 
-// inspiration from https://stackoverflow.com/a/11868398
-export const getContrastYIQ = (bgHex: string, isCustomColor: boolean) => {
-  if (isCustomColor) {
-    const style = new Option().style;
-    style.color = bgHex;
+// YIQ algo, inspiration from https://stackoverflow.com/a/11868398
+export const isColorDark = (color: string, threshold = 160): boolean => {
+  // no color ("") -> assume it default to black
+  if (!color) {
+    return true;
+  }
+
+  if (color === "transparent") {
+    return false;
+  }
 
-    if (style.color) {
-      const rgb = style.color
+  // a string color (white etc) or any other format -> convert to rgb by way
+  // of creating a DOM node and retrieving the computeStyle
+  if (!color.startsWith("#")) {
+    const node = document.createElement("div");
+    node.style.color = color;
+
+    if (node.style.color) {
+      // making invisible so document doesn't reflow (hopefully).
+      // display=none works too, but supposedly not in all browsers
+      node.style.position = "absolute";
+      node.style.visibility = "hidden";
+      node.style.width = "0";
+      node.style.height = "0";
+
+      // needs to be in DOM else browser won't compute the style
+      document.body.appendChild(node);
+      const computedColor = getComputedStyle(node).color;
+      document.body.removeChild(node);
+      // computed style is in rgb() format
+      const rgb = computedColor
         .replace(/^(rgb|rgba)\(/, "")
         .replace(/\)$/, "")
         .replace(/\s/g, "")
@@ -114,20 +137,17 @@ export const getContrastYIQ = (bgHex: string, isCustomColor: boolean) => {
       const g = parseInt(rgb[1]);
       const b = parseInt(rgb[2]);
 
-      return calculateContrast(r, g, b);
+      return calculateContrast(r, g, b) < threshold;
     }
+    // invalid color -> assume it default to black
+    return true;
   }
 
-  // TODO: ? is this wanted?
-  if (bgHex === "transparent") {
-    return "black";
-  }
-
-  const r = parseInt(bgHex.substring(1, 3), 16);
-  const g = parseInt(bgHex.substring(3, 5), 16);
-  const b = parseInt(bgHex.substring(5, 7), 16);
+  const r = parseInt(color.slice(1, 3), 16);
+  const g = parseInt(color.slice(3, 5), 16);
+  const b = parseInt(color.slice(5, 7), 16);
 
-  return calculateContrast(r, g, b);
+  return calculateContrast(r, g, b) < threshold;
 };
 
 export type ColorPickerType =

+ 3 - 9
packages/excalidraw/css/styles.scss

@@ -173,7 +173,7 @@ body.excalidraw-cursor-resize * {
     .buttonList {
       flex-wrap: wrap;
       display: flex;
-      column-gap: 0.375rem;
+      column-gap: 0.5rem;
       row-gap: 0.5rem;
 
       label {
@@ -386,16 +386,10 @@ body.excalidraw-cursor-resize * {
 
   .App-menu__left {
     overflow-y: auto;
-    padding: 0.75rem 0.75rem 0.25rem 0.75rem;
-    width: 11.875rem;
+    padding: 0.75rem;
+    width: 12.5rem;
     box-sizing: border-box;
     position: absolute;
-
-    .buttonList label,
-    .buttonList button,
-    .buttonList .zIndexButton {
-      --button-bg: transparent;
-    }
   }
 
   .dropdown-select {

+ 2 - 2
packages/excalidraw/css/theme.scss

@@ -148,7 +148,7 @@
   --border-radius-lg: 0.5rem;
 
   --color-surface-high: #f1f0ff;
-  --color-surface-mid: #f2f2f7;
+  --color-surface-mid: #f6f6f9;
   --color-surface-low: #ececf4;
   --color-surface-lowest: #ffffff;
   --color-on-surface: #1b1b1f;
@@ -252,7 +252,7 @@
 
     --color-logo-text: #e2dfff;
 
-    --color-surface-high: hsl(245, 10%, 21%);
+    --color-surface-high: #2e2d39;
     --color-surface-low: hsl(240, 8%, 15%);
     --color-surface-mid: hsl(240 6% 10%);
     --color-surface-lowest: hsl(0, 0%, 7%);

+ 6 - 6
packages/excalidraw/tests/__snapshots__/excalidraw.test.tsx.snap

@@ -572,7 +572,7 @@ exports[`<Excalidraw/> > Test UIOptions prop > Test canvasActions > should rende
               class="color-picker__top-picks"
             >
               <button
-                class="color-picker__button active"
+                class="color-picker__button active has-outline"
                 data-testid="color-top-pick-#ffffff"
                 style="--swatch-color: #ffffff;"
                 title="#ffffff"
@@ -583,7 +583,7 @@ exports[`<Excalidraw/> > Test UIOptions prop > Test canvasActions > should rende
                 />
               </button>
               <button
-                class="color-picker__button"
+                class="color-picker__button has-outline"
                 data-testid="color-top-pick-#f8f9fa"
                 style="--swatch-color: #f8f9fa;"
                 title="#f8f9fa"
@@ -594,7 +594,7 @@ exports[`<Excalidraw/> > Test UIOptions prop > Test canvasActions > should rende
                 />
               </button>
               <button
-                class="color-picker__button"
+                class="color-picker__button has-outline"
                 data-testid="color-top-pick-#f5faff"
                 style="--swatch-color: #f5faff;"
                 title="#f5faff"
@@ -605,7 +605,7 @@ exports[`<Excalidraw/> > Test UIOptions prop > Test canvasActions > should rende
                 />
               </button>
               <button
-                class="color-picker__button"
+                class="color-picker__button has-outline"
                 data-testid="color-top-pick-#fffce8"
                 style="--swatch-color: #fffce8;"
                 title="#fffce8"
@@ -616,7 +616,7 @@ exports[`<Excalidraw/> > Test UIOptions prop > Test canvasActions > should rende
                 />
               </button>
               <button
-                class="color-picker__button"
+                class="color-picker__button has-outline"
                 data-testid="color-top-pick-#fdf8f6"
                 style="--swatch-color: #fdf8f6;"
                 title="#fdf8f6"
@@ -635,7 +635,7 @@ exports[`<Excalidraw/> > Test UIOptions prop > Test canvasActions > should rende
               aria-expanded="false"
               aria-haspopup="dialog"
               aria-label="Canvas background"
-              class="color-picker__button active-color properties-trigger"
+              class="color-picker__button active-color properties-trigger has-outline"
               data-state="closed"
               style="--swatch-color: #ffffff;"
               title="Show background color picker"