From 23909d9873b53140d5581095fd1b29ff4c126585 Mon Sep 17 00:00:00 2001 From: Yaroslav Lozhkin Date: Thu, 4 Dec 2025 20:49:42 +0400 Subject: [PATCH 1/3] Add BetaMenu and palette step presets features Introduces a BetaMenu component with palette range step presets, allowing users to quickly switch between different level step configurations. Adds utilities for applying, previewing, and distributing contrast and chroma values across levels, including new store actions and UI controls. Refactors chroma cap copying, enables hex-to-hue paste in hue headers, and updates related schemas, types, and color calculation logic to support these features. --- .../components/BetaMenu/BetaMenu.module.css | 20 +++ .../core/src/components/BetaMenu/BetaMenu.tsx | 63 +++++++++ .../src/components/Grid/GridCellHueHeader.tsx | 18 ++- .../Grid/GridCellLevelHeader.module.css | 25 ++++ .../components/Grid/GridCellLevelHeader.tsx | 70 ++++++---- .../src/components/Grid/GridLeftTopCell.tsx | 43 ++++-- packages/core/src/components/Icon/MFlask.tsx | 14 ++ packages/core/src/createApp.tsx | 11 +- packages/core/src/levelStepsPresets.ts | 56 ++++++++ packages/core/src/schemas/brand.ts | 7 + packages/core/src/schemas/settings.ts | 9 ++ packages/core/src/stores/colors.ts | 71 +++++++++- packages/core/src/stores/config.ts | 13 ++ packages/core/src/stores/settings.ts | 127 +++++++++++++++++- packages/core/src/types.ts | 1 + .../core/src/utils/colors/calculateApcach.ts | 2 +- .../core/src/utils/colors/calculateColors.ts | 4 +- .../core/src/utils/colors/hexToHueAngle.ts | 30 +++++ packages/core/src/utils/colors/types.ts | 2 +- 19 files changed, 541 insertions(+), 45 deletions(-) create mode 100644 packages/core/src/components/BetaMenu/BetaMenu.module.css create mode 100644 packages/core/src/components/BetaMenu/BetaMenu.tsx create mode 100644 packages/core/src/components/Icon/MFlask.tsx create mode 100644 packages/core/src/levelStepsPresets.ts create mode 100644 packages/core/src/utils/colors/hexToHueAngle.ts diff --git a/packages/core/src/components/BetaMenu/BetaMenu.module.css b/packages/core/src/components/BetaMenu/BetaMenu.module.css new file mode 100644 index 0000000..466816e --- /dev/null +++ b/packages/core/src/components/BetaMenu/BetaMenu.module.css @@ -0,0 +1,20 @@ +.button { + /* No positioning needed - will be positioned by parent FloatingActions */ +} + +.iconContainer { + display: flex; + gap: calc(var(--spacing) * 0.5); + align-items: center; +} + +.flaskIcon { + width: 16px; + height: 16px; +} + +.caretIcon { + width: 12px; + height: 12px; + opacity: 0.7; +} diff --git a/packages/core/src/components/BetaMenu/BetaMenu.tsx b/packages/core/src/components/BetaMenu/BetaMenu.tsx new file mode 100644 index 0000000..367aa92 --- /dev/null +++ b/packages/core/src/components/BetaMenu/BetaMenu.tsx @@ -0,0 +1,63 @@ +import { useSubscribe } from "@spred/react"; + +import { Button } from "@core/components/Button/Button"; +import { MFlask } from "@core/components/Icon/MFlask"; +import { MTriangleDown } from "@core/components/Icon/MTriangleDown"; +import { Menu } from "@core/components/Menu/Menu"; +import { MenuItemButton } from "@core/components/Menu/MenuItemButton"; +import { MenuItemGroup } from "@core/components/Menu/MenuItemGroup"; +import { MenuItemSeparator } from "@core/components/Menu/MenuItemSeparator"; +import { levelStepsPresets } from "@core/levelStepsPresets"; +import { LEVEL_STEPS_PRESETS, type LevelStepsPreset } from "@core/schemas/brand"; +import { resetToInitialState } from "@core/stores/config"; +import { + applyLevelStepsPreset, + distributeContrastEvenly, + levelStepsPresetStore, +} from "@core/stores/settings"; + +import styles from "./BetaMenu.module.css"; + +export function BetaMenu() { + const currentPreset = useSubscribe(levelStepsPresetStore.$lastValidValue); + + return ( + ( + + )} + > + + {LEVEL_STEPS_PRESETS.map((preset) => ( + applyLevelStepsPreset(preset as LevelStepsPreset)} + > + {levelStepsPresets[preset].label} + + ))} + + + distributeContrastEvenly()}> + Distribute Contrast Evenly + + + + Reset to Initial State + + + ); +} diff --git a/packages/core/src/components/Grid/GridCellHueHeader.tsx b/packages/core/src/components/Grid/GridCellHueHeader.tsx index 3c76d34..a790795 100644 --- a/packages/core/src/components/Grid/GridCellHueHeader.tsx +++ b/packages/core/src/components/Grid/GridCellHueHeader.tsx @@ -1,4 +1,4 @@ -import { memo, useCallback, useRef } from "react"; +import { type ClipboardEvent, memo, useCallback, useRef } from "react"; import { useSubscribe } from "@spred/react"; @@ -19,6 +19,7 @@ import { } from "@core/stores/colors"; import { $bgColorModeLeft } from "@core/stores/settings"; import type { HueId } from "@core/types"; +import { hexToHueAngle } from "@core/utils/colors/hexToHueAngle"; import type { AnyProps } from "@core/utils/react/types"; import { DATA_ATTR_CELL_HUE_ID } from "./constants"; @@ -70,6 +71,20 @@ const NameInput = memo(function NameInput({ hueId }: HueComponentProps) { } }); + const handlePaste = useCallback( + (e: ClipboardEvent) => { + const pastedText = e.clipboardData.getData("text"); + const hueAngle = hexToHueAngle(pastedText); + + if (hueAngle !== null) { + e.preventDefault(); + updateHueAngle(hueId, String(Math.round(hueAngle))); + resetHueName(hueId); + } + }, + [hueId], + ); + return ( updateLevelchromaCap(levelId, e.target.value || null)} - /> +
+ updateLevelchromaCap(levelId, e.target.value || null)} + /> + +
); }); diff --git a/packages/core/src/components/Grid/GridLeftTopCell.tsx b/packages/core/src/components/Grid/GridLeftTopCell.tsx index f86619d..907d9e7 100644 --- a/packages/core/src/components/Grid/GridLeftTopCell.tsx +++ b/packages/core/src/components/Grid/GridLeftTopCell.tsx @@ -4,9 +4,12 @@ import { useSubscribe } from "@spred/react"; import clsx from "clsx"; import { Button } from "@core/components/Button/Button"; -import { Select } from "@core/components/Select/Select"; +import { Menu } from "@core/components/Menu/Menu"; +import { MenuItemButton } from "@core/components/Menu/MenuItemButton"; +import { MenuItemSeparator } from "@core/components/Menu/MenuItemSeparator"; import { Text } from "@core/components/Text/Text"; -import { parseChromaMode } from "@core/schemas/settings"; +import { ChromaMode } from "@core/schemas/brand"; +import { $isAnyChromaCapSet, resetAllChroma } from "@core/stores/colors"; import { chromaModeStore, colorSpaceStore, @@ -43,6 +46,7 @@ export const GridLeftTopCell = memo(function GridLeftTopCell() { const chromaModeValue = useSubscribe(chromaModeStore.$lastValidValue); const colorSpace = useSubscribe(colorSpaceStore.$lastValidValue); const isColorSpaceLocked = useSubscribe($isColorSpaceLocked); + const hasChromaCaps = useSubscribe($isAnyChromaCapSet); return ( @@ -79,13 +83,34 @@ export const GridLeftTopCell = memo(function GridLeftTopCell() { colors -