From 7383be324e0068057399588ee550f1e4decc6a48 Mon Sep 17 00:00:00 2001 From: Maks Pikov Date: Tue, 3 Mar 2026 22:16:40 +0000 Subject: [PATCH 1/8] feat(addon-controls): add maxPresetColors option to ColorControl The ColorControl preset limit was previously hardcoded to 27 colors. This prevented users with large design systems (more than 27 colors) from displaying all their preset colors. Add a new optional maxPresetColors prop to ColorConfig/ColorControl that allows users to configure or remove this limit: - Defaults to 27 for backward compatibility - Setting to 0 or Infinity removes the limit entirely - Any other positive number limits presets to that count Add two new stories demonstrating the feature: - WithMaxPresetColors (limit to 5) - WithUnlimitedPresetColors (maxPresetColors: 0, 40 colors) Fixes #20298 --- .../src/blocks/controls/Color.stories.tsx | 30 +++++++++++++++++++ .../addons/docs/src/blocks/controls/Color.tsx | 14 ++++++--- code/addons/docs/src/blocks/controls/types.ts | 8 +++++ 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/code/addons/docs/src/blocks/controls/Color.stories.tsx b/code/addons/docs/src/blocks/controls/Color.stories.tsx index 867e0d0c5cb7..83a8e3582eae 100644 --- a/code/addons/docs/src/blocks/controls/Color.stories.tsx +++ b/code/addons/docs/src/blocks/controls/Color.stories.tsx @@ -65,6 +65,36 @@ export const WithPresetColors: Story = { }, }; +export const WithMaxPresetColors: Story = { + name: 'With maxPresetColors (limit to 5)', + args: { + value: '#00ffff', + maxPresetColors: 5, + presetColors: [ + { color: '#ff4785', title: 'Coral' }, + { color: '#1EA7FD', title: 'Ocean' }, + { color: 'rgb(252, 82, 31)', title: 'Orange' }, + { color: 'rgba(255, 174, 0, 0.5)', title: 'Gold' }, + { color: 'hsl(101, 52%, 49%)', title: 'Green' }, + { color: 'hsla(179,65%,53%,0.5)', title: 'Seafoam' }, + { color: '#6F2CAC', title: 'Purple' }, + { color: '#2A0481', title: 'Ultraviolet' }, + ], + }, +}; + +export const WithUnlimitedPresetColors: Story = { + name: 'With unlimited presets (maxPresetColors: 0)', + args: { + value: '#00ffff', + maxPresetColors: 0, + presetColors: Array.from({ length: 40 }, (_, i) => { + const hue = Math.round((i / 40) * 360); + return { color: `hsl(${hue}, 70%, 50%)`, title: `Color ${i + 1}` }; + }), + }, +}; + export const StartOpen: Story = { args: { startOpen: true, diff --git a/code/addons/docs/src/blocks/controls/Color.tsx b/code/addons/docs/src/blocks/controls/Color.tsx index 0758f7b8611c..4e6e7b625fe9 100644 --- a/code/addons/docs/src/blocks/controls/Color.tsx +++ b/code/addons/docs/src/blocks/controls/Color.tsx @@ -307,7 +307,8 @@ const id = (value: string) => value.replace(/\s*/, '').toLowerCase(); const usePresets = ( presetColors: PresetColor[], currentColor: ParsedColor | undefined, - colorSpace: ColorSpace + colorSpace: ColorSpace, + maxPresetColors = 27 ) => { const [selectedColors, setSelectedColors] = useState(currentColor?.valid ? [currentColor] : []); @@ -330,8 +331,12 @@ const usePresets = ( } return parseValue(preset.color); }); - return initialPresets.concat(selectedColors).filter(Boolean).slice(-27); - }, [presetColors, selectedColors]); + const combined = initialPresets.concat(selectedColors).filter(Boolean); + if (!maxPresetColors || maxPresetColors === Infinity) { + return combined; + } + return combined.slice(-maxPresetColors); + }, [presetColors, selectedColors, maxPresetColors]); const addPreset: (color: ParsedColor) => void = useCallback( (color) => { @@ -365,6 +370,7 @@ export const ColorControl: FC = ({ onFocus, onBlur, presetColors, + maxPresetColors, startOpen = false, argType, }) => { @@ -373,7 +379,7 @@ export const ColorControl: FC = ({ initialValue, debouncedOnChange ); - const { presets, addPreset } = usePresets(presetColors ?? [], color, colorSpace); + const { presets, addPreset } = usePresets(presetColors ?? [], color, colorSpace, maxPresetColors); const Picker = ColorPicker[colorSpace]; const readOnly = !!argType?.table?.readonly; diff --git a/code/addons/docs/src/blocks/controls/types.ts b/code/addons/docs/src/blocks/controls/types.ts index c080d54bccc4..dec551b2af7d 100644 --- a/code/addons/docs/src/blocks/controls/types.ts +++ b/code/addons/docs/src/blocks/controls/types.ts @@ -17,6 +17,14 @@ export type ColorValue = string; export type PresetColor = ColorValue | { color: ColorValue; title?: string }; export interface ColorConfig { presetColors?: PresetColor[]; + /** + * Maximum number of preset colors shown in the color picker. When the number of presets exceeds + * this value, the oldest presets are removed first (most-recently-used order). Set to `Infinity` + * or `0` to disable the limit and show all presets. + * + * @default 27 + */ + maxPresetColors?: number; /** * Whether the color picker should be open by default when rendered. * From 42d56f83c84fd42c5a7c19da0d068af517ed93d2 Mon Sep 17 00:00:00 2001 From: Maks Pikov Date: Thu, 12 Mar 2026 09:34:03 +0000 Subject: [PATCH 2/8] fix(controls): guard maxPresetColors against invalid values and add startOpen to stories - Normalize maxPresetColors: fall back to default 27 for negative, non-integer, or NaN values - Add startOpen: true to WithMaxPresetColors and WithUnlimitedPresetColors stories for Chromatic captures --- code/addons/docs/src/blocks/controls/Color.stories.tsx | 2 ++ code/addons/docs/src/blocks/controls/Color.tsx | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/code/addons/docs/src/blocks/controls/Color.stories.tsx b/code/addons/docs/src/blocks/controls/Color.stories.tsx index 83a8e3582eae..44a176f309a7 100644 --- a/code/addons/docs/src/blocks/controls/Color.stories.tsx +++ b/code/addons/docs/src/blocks/controls/Color.stories.tsx @@ -69,6 +69,7 @@ export const WithMaxPresetColors: Story = { name: 'With maxPresetColors (limit to 5)', args: { value: '#00ffff', + startOpen: true, maxPresetColors: 5, presetColors: [ { color: '#ff4785', title: 'Coral' }, @@ -87,6 +88,7 @@ export const WithUnlimitedPresetColors: Story = { name: 'With unlimited presets (maxPresetColors: 0)', args: { value: '#00ffff', + startOpen: true, maxPresetColors: 0, presetColors: Array.from({ length: 40 }, (_, i) => { const hue = Math.round((i / 40) * 360); diff --git a/code/addons/docs/src/blocks/controls/Color.tsx b/code/addons/docs/src/blocks/controls/Color.tsx index 4e6e7b625fe9..5f6ccc65cb09 100644 --- a/code/addons/docs/src/blocks/controls/Color.tsx +++ b/code/addons/docs/src/blocks/controls/Color.tsx @@ -332,10 +332,12 @@ const usePresets = ( return parseValue(preset.color); }); const combined = initialPresets.concat(selectedColors).filter(Boolean); - if (!maxPresetColors || maxPresetColors === Infinity) { + if (maxPresetColors === 0 || maxPresetColors === Infinity) { return combined; } - return combined.slice(-maxPresetColors); + const limit = + Number.isInteger(maxPresetColors) && maxPresetColors > 0 ? maxPresetColors : 27; + return combined.slice(-limit); }, [presetColors, selectedColors, maxPresetColors]); const addPreset: (color: ParsedColor) => void = useCallback( From 9ae0cdac4a2ce9e4a5fb0fd2d19c089a639852a9 Mon Sep 17 00:00:00 2001 From: Maks Pikov Date: Fri, 13 Mar 2026 01:08:19 +0000 Subject: [PATCH 3/8] fix: restore yarn.lock from upstream and fix accidental svelte-vite peerDep downgrade --- code/frameworks/svelte-vite/package.json | 2 +- yarn.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/code/frameworks/svelte-vite/package.json b/code/frameworks/svelte-vite/package.json index 38cc0597515a..e723f2248c03 100644 --- a/code/frameworks/svelte-vite/package.json +++ b/code/frameworks/svelte-vite/package.json @@ -67,7 +67,7 @@ "vite": "^7.0.4" }, "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", + "@sveltejs/vite-plugin-svelte": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", "storybook": "workspace:^", "svelte": "^5.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" diff --git a/yarn.lock b/yarn.lock index 493339d5f83e..d9d43b21c426 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8647,7 +8647,7 @@ __metadata: typescript: "npm:^5.9.3" vite: "npm:^7.0.4" peerDependencies: - "@sveltejs/vite-plugin-svelte": ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 + "@sveltejs/vite-plugin-svelte": ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 storybook: "workspace:^" svelte: ^5.0.0 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 From 37b56c4cd2205139b48ea56570dcf8c08cb05c68 Mon Sep 17 00:00:00 2001 From: Maks Pikov Date: Fri, 13 Mar 2026 01:19:45 +0000 Subject: [PATCH 4/8] fix(controls): add story for invalid maxPresetColors value Demonstrates fallback to default (27) when negative value is passed. Co-Authored-By: Claude Opus 4.6 --- .../docs/src/blocks/controls/Color.stories.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/code/addons/docs/src/blocks/controls/Color.stories.tsx b/code/addons/docs/src/blocks/controls/Color.stories.tsx index 44a176f309a7..b801bf02517b 100644 --- a/code/addons/docs/src/blocks/controls/Color.stories.tsx +++ b/code/addons/docs/src/blocks/controls/Color.stories.tsx @@ -97,6 +97,22 @@ export const WithUnlimitedPresetColors: Story = { }, }; +export const WithInvalidMaxPresetColors: Story = { + name: 'With invalid maxPresetColors (negative, falls back to 27)', + args: { + value: '#00ffff', + startOpen: true, + maxPresetColors: -5, + presetColors: [ + { color: '#ff4785', title: 'Coral' }, + { color: '#1EA7FD', title: 'Ocean' }, + { color: 'rgb(252, 82, 31)', title: 'Orange' }, + { color: 'rgba(255, 174, 0, 0.5)', title: 'Gold' }, + { color: 'hsl(101, 52%, 49%)', title: 'Green' }, + ], + }, +}; + export const StartOpen: Story = { args: { startOpen: true, From f1cf7c86a802facbc451b3609cb3b2a9aba8eb53 Mon Sep 17 00:00:00 2001 From: Maks Pikov Date: Sun, 15 Mar 2026 01:09:48 +0000 Subject: [PATCH 5/8] style: fix prettier formatting in Color control files --- code/addons/docs/src/blocks/controls/Color.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/code/addons/docs/src/blocks/controls/Color.tsx b/code/addons/docs/src/blocks/controls/Color.tsx index 5f6ccc65cb09..d42764d26864 100644 --- a/code/addons/docs/src/blocks/controls/Color.tsx +++ b/code/addons/docs/src/blocks/controls/Color.tsx @@ -335,8 +335,7 @@ const usePresets = ( if (maxPresetColors === 0 || maxPresetColors === Infinity) { return combined; } - const limit = - Number.isInteger(maxPresetColors) && maxPresetColors > 0 ? maxPresetColors : 27; + const limit = Number.isInteger(maxPresetColors) && maxPresetColors > 0 ? maxPresetColors : 27; return combined.slice(-limit); }, [presetColors, selectedColors, maxPresetColors]); From c9fd5f47e4f487a6e419d5857c8af8a121580a70 Mon Sep 17 00:00:00 2001 From: Steve Dodier-Lazaro Date: Thu, 19 Mar 2026 10:42:30 +0100 Subject: [PATCH 6/8] Make invalid maxPresetColor story use more presets --- code/addons/docs/src/blocks/controls/Color.stories.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/code/addons/docs/src/blocks/controls/Color.stories.tsx b/code/addons/docs/src/blocks/controls/Color.stories.tsx index b801bf02517b..39ddc06a5aa4 100644 --- a/code/addons/docs/src/blocks/controls/Color.stories.tsx +++ b/code/addons/docs/src/blocks/controls/Color.stories.tsx @@ -110,7 +110,10 @@ export const WithInvalidMaxPresetColors: Story = { { color: 'rgba(255, 174, 0, 0.5)', title: 'Gold' }, { color: 'hsl(101, 52%, 49%)', title: 'Green' }, ], - }, + presetColors: Array.from({ length: 40 }, (_, i) => { + const hue = Math.round((i / 40) * 360); + return { color: `hsl(${hue}, 70%, 50%)`, title: `Color ${i + 1}` }; + }), }; export const StartOpen: Story = { From 59bc7c633f51eae024c07822290629b6cac6c2c4 Mon Sep 17 00:00:00 2001 From: Maks Pikov Date: Sun, 22 Mar 2026 01:04:37 +0000 Subject: [PATCH 7/8] fix: apply prettier formatting to Color control files --- .../src/blocks/controls/Color.stories.tsx | 105 +++++----- .../addons/docs/src/blocks/controls/Color.tsx | 193 ++++++++++-------- code/addons/docs/src/blocks/controls/types.ts | 30 +-- 3 files changed, 177 insertions(+), 151 deletions(-) diff --git a/code/addons/docs/src/blocks/controls/Color.stories.tsx b/code/addons/docs/src/blocks/controls/Color.stories.tsx index 39ddc06a5aa4..75f5b0ef03cd 100644 --- a/code/addons/docs/src/blocks/controls/Color.stories.tsx +++ b/code/addons/docs/src/blocks/controls/Color.stories.tsx @@ -1,21 +1,24 @@ -import type { Meta, StoryObj } from '@storybook/react-vite'; +import type { Meta, StoryObj } from "@storybook/react-vite"; -import { fn } from 'storybook/test'; +import { fn } from "storybook/test"; -import { ColorControl } from './Color'; +import { ColorControl } from "./Color"; const meta = { component: ColorControl, - parameters: { withRawArg: 'value', controls: { include: ['value', 'startOpen'] } }, - tags: ['autodocs'], + parameters: { + withRawArg: "value", + controls: { include: ["value", "startOpen"] }, + }, + tags: ["autodocs"], argTypes: { value: { control: { - type: 'color', + type: "color", }, }, }, - args: { name: 'color', onChange: fn() }, + args: { name: "color", onChange: fn() }, } satisfies Meta; export default meta; @@ -24,7 +27,7 @@ type Story = StoryObj; export const Basic: Story = { args: { - value: '#ff00ff', + value: "#ff00ff", }, }; @@ -36,58 +39,58 @@ export const Undefined: Story = { export const WithPresetColors: Story = { args: { - value: '#00ffff', + value: "#00ffff", presetColors: [ - { color: '#ff4785', title: 'Coral' }, - { color: '#1EA7FD', title: 'Ocean' }, - { color: 'rgb(252, 82, 31)', title: 'Orange' }, - { color: 'rgba(255, 174, 0, 0.5)', title: 'Gold' }, - { color: 'hsl(101, 52%, 49%)', title: 'Green' }, - { color: 'hsla(179,65%,53%,0.5)', title: 'Seafoam' }, - { color: '#6F2CAC', title: 'Purple' }, - { color: '#2A0481', title: 'Ultraviolet' }, - { color: 'black' }, - { color: '#333', title: 'Darkest' }, - { color: '#444', title: 'Darker' }, - { color: '#666', title: 'Dark' }, - { color: '#999', title: 'Mediumdark' }, - { color: '#ddd', title: 'Medium' }, - { color: '#EEE', title: 'Mediumlight' }, - { color: '#F3F3F3', title: 'Light' }, - { color: '#F8F8F8', title: 'Lighter' }, - { color: '#FFFFFF', title: 'Lightest' }, - '#fe4a49', - '#FED766', - 'rgba(0, 159, 183, 1)', - 'hsla(240,11%,91%,0.5)', - 'slategray', + { color: "#ff4785", title: "Coral" }, + { color: "#1EA7FD", title: "Ocean" }, + { color: "rgb(252, 82, 31)", title: "Orange" }, + { color: "rgba(255, 174, 0, 0.5)", title: "Gold" }, + { color: "hsl(101, 52%, 49%)", title: "Green" }, + { color: "hsla(179,65%,53%,0.5)", title: "Seafoam" }, + { color: "#6F2CAC", title: "Purple" }, + { color: "#2A0481", title: "Ultraviolet" }, + { color: "black" }, + { color: "#333", title: "Darkest" }, + { color: "#444", title: "Darker" }, + { color: "#666", title: "Dark" }, + { color: "#999", title: "Mediumdark" }, + { color: "#ddd", title: "Medium" }, + { color: "#EEE", title: "Mediumlight" }, + { color: "#F3F3F3", title: "Light" }, + { color: "#F8F8F8", title: "Lighter" }, + { color: "#FFFFFF", title: "Lightest" }, + "#fe4a49", + "#FED766", + "rgba(0, 159, 183, 1)", + "hsla(240,11%,91%,0.5)", + "slategray", ], }, }; export const WithMaxPresetColors: Story = { - name: 'With maxPresetColors (limit to 5)', + name: "With maxPresetColors (limit to 5)", args: { - value: '#00ffff', + value: "#00ffff", startOpen: true, maxPresetColors: 5, presetColors: [ - { color: '#ff4785', title: 'Coral' }, - { color: '#1EA7FD', title: 'Ocean' }, - { color: 'rgb(252, 82, 31)', title: 'Orange' }, - { color: 'rgba(255, 174, 0, 0.5)', title: 'Gold' }, - { color: 'hsl(101, 52%, 49%)', title: 'Green' }, - { color: 'hsla(179,65%,53%,0.5)', title: 'Seafoam' }, - { color: '#6F2CAC', title: 'Purple' }, - { color: '#2A0481', title: 'Ultraviolet' }, + { color: "#ff4785", title: "Coral" }, + { color: "#1EA7FD", title: "Ocean" }, + { color: "rgb(252, 82, 31)", title: "Orange" }, + { color: "rgba(255, 174, 0, 0.5)", title: "Gold" }, + { color: "hsl(101, 52%, 49%)", title: "Green" }, + { color: "hsla(179,65%,53%,0.5)", title: "Seafoam" }, + { color: "#6F2CAC", title: "Purple" }, + { color: "#2A0481", title: "Ultraviolet" }, ], }, }; export const WithUnlimitedPresetColors: Story = { - name: 'With unlimited presets (maxPresetColors: 0)', + name: "With unlimited presets (maxPresetColors: 0)", args: { - value: '#00ffff', + value: "#00ffff", startOpen: true, maxPresetColors: 0, presetColors: Array.from({ length: 40 }, (_, i) => { @@ -98,22 +101,16 @@ export const WithUnlimitedPresetColors: Story = { }; export const WithInvalidMaxPresetColors: Story = { - name: 'With invalid maxPresetColors (negative, falls back to 27)', + name: "With invalid maxPresetColors (negative, falls back to 27)", args: { - value: '#00ffff', + value: "#00ffff", startOpen: true, maxPresetColors: -5, - presetColors: [ - { color: '#ff4785', title: 'Coral' }, - { color: '#1EA7FD', title: 'Ocean' }, - { color: 'rgb(252, 82, 31)', title: 'Orange' }, - { color: 'rgba(255, 174, 0, 0.5)', title: 'Gold' }, - { color: 'hsl(101, 52%, 49%)', title: 'Green' }, - ], presetColors: Array.from({ length: 40 }, (_, i) => { const hue = Math.round((i / 40) * 360); return { color: `hsl(${hue}, 70%, 50%)`, title: `Color ${i + 1}` }; }), + }, }; export const StartOpen: Story = { @@ -124,7 +121,7 @@ export const StartOpen: Story = { export const Readonly: Story = { args: { - value: '#ff00ff', + value: "#ff00ff", argType: { table: { readonly: true } }, }, }; diff --git a/code/addons/docs/src/blocks/controls/Color.tsx b/code/addons/docs/src/blocks/controls/Color.tsx index b109004c2e63..82e054506d89 100644 --- a/code/addons/docs/src/blocks/controls/Color.tsx +++ b/code/addons/docs/src/blocks/controls/Color.tsx @@ -1,20 +1,29 @@ -import type { ChangeEvent, FC, FocusEvent } from 'react'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; - -import { Button, Form, PopoverProvider } from 'storybook/internal/components'; - -import { MarkupIcon } from '@storybook/icons'; - -import convert from 'color-convert'; -import { debounce } from 'es-toolkit/function'; -import { HexColorPicker, HslaStringColorPicker, RgbaStringColorPicker } from 'react-colorful'; -import { styled } from 'storybook/theming'; - -import { getControlId } from './helpers'; -import type { ColorConfig, ColorValue, ControlProps, PresetColor } from './types'; +import type { ChangeEvent, FC, FocusEvent } from "react"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; + +import { Button, Form, PopoverProvider } from "storybook/internal/components"; + +import { MarkupIcon } from "@storybook/icons"; + +import convert from "color-convert"; +import { debounce } from "es-toolkit/function"; +import { + HexColorPicker, + HslaStringColorPicker, + RgbaStringColorPicker, +} from "react-colorful"; +import { styled } from "storybook/theming"; + +import { getControlId } from "./helpers"; +import type { + ColorConfig, + ColorValue, + ControlProps, + PresetColor, +} from "./types"; const Wrapper = styled.div({ - position: 'relative', + position: "relative", maxWidth: 250, }); @@ -22,20 +31,20 @@ const TooltipContent = styled.div({ width: 200, margin: 5, - '.react-colorful__saturation': { - borderRadius: '4px 4px 0 0', + ".react-colorful__saturation": { + borderRadius: "4px 4px 0 0", }, - '.react-colorful__hue': { - boxShadow: 'inset 0 0 0 1px rgb(0 0 0 / 5%)', + ".react-colorful__hue": { + boxShadow: "inset 0 0 0 1px rgb(0 0 0 / 5%)", }, - '.react-colorful__last-control': { - borderRadius: '0 0 4px 4px', + ".react-colorful__last-control": { + borderRadius: "0 0 4px 4px", }, }); const Swatches = styled.div({ - display: 'grid', - gridTemplateColumns: 'repeat(9, 16px)', + display: "grid", + gridTemplateColumns: "repeat(9, 16px)", gap: 6, padding: 3, marginTop: 5, @@ -43,7 +52,7 @@ const Swatches = styled.div({ }); const swatchBackground = (isDark: boolean) => - `url('data:image/svg+xml;charset=utf-8,')`; + `url('data:image/svg+xml;charset=utf-8,')`; const SwatchColor = styled(Button)<{ selected?: boolean; value: string }>( ({ value, selected, theme }) => ({ @@ -52,59 +61,63 @@ const SwatchColor = styled(Button)<{ selected?: boolean; value: string }>( boxShadow: selected ? `${theme.appBorderColor} 0 0 0 1px inset, ${theme.textMutedColor}50 0 0 0 4px` : `${theme.appBorderColor} 0 0 0 1px inset`, - border: 'none', + border: "none", borderRadius: theme.appBorderRadius, - '&, &:hover': { - background: 'unset', - backgroundColor: 'unset', - backgroundImage: `linear-gradient(${value}, ${value}), ${swatchBackground(theme.base === 'dark')}`, + "&, &:hover": { + background: "unset", + backgroundColor: "unset", + backgroundImage: `linear-gradient(${value}, ${value}), ${swatchBackground(theme.base === "dark")}`, }, - }) + }), ); const Input = styled(Form.Input)(({ theme }) => ({ - width: '100%', + width: "100%", paddingLeft: 30, paddingRight: 30, - boxSizing: 'border-box', + boxSizing: "border-box", fontFamily: theme.typography.fonts.base, '[aria-readonly="true"] > &': { - background: theme.base === 'light' ? theme.color.lighter : 'transparent', + background: theme.base === "light" ? theme.color.lighter : "transparent", }, })); -const PopoverTrigger = styled(SwatchColor)<{ disabled: boolean }>(({ disabled }) => ({ - position: 'absolute', - top: 4, - left: 4, - zIndex: 1, - cursor: disabled ? 'not-allowed' : 'pointer', -})); +const PopoverTrigger = styled(SwatchColor)<{ disabled: boolean }>( + ({ disabled }) => ({ + position: "absolute", + top: 4, + left: 4, + zIndex: 1, + cursor: disabled ? "not-allowed" : "pointer", + }), +); const CycleColorSpaceButton = styled(Button)(({ theme }) => ({ - position: 'absolute', + position: "absolute", zIndex: 1, top: 6, right: 7, width: 20, height: 20, padding: 4, - boxSizing: 'border-box', - cursor: 'pointer', + boxSizing: "border-box", + cursor: "pointer", color: theme.input.color, })); enum ColorSpace { - RGB = 'rgb', - HSL = 'hsl', - HEX = 'hex', + RGB = "rgb", + HSL = "hsl", + HEX = "hex", } const COLOR_SPACES = Object.values(ColorSpace); const COLOR_REGEXP = /\(([0-9]+),\s*([0-9]+)%?,\s*([0-9]+)%?,?\s*([0-9.]+)?\)/; -const RGB_REGEXP = /^\s*rgba?\(([0-9]+),\s*([0-9]+),\s*([0-9]+),?\s*([0-9.]+)?\)\s*$/i; -const HSL_REGEXP = /^\s*hsla?\(([0-9]+),\s*([0-9]+)%,\s*([0-9]+)%,?\s*([0-9.]+)?\)\s*$/i; +const RGB_REGEXP = + /^\s*rgba?\(([0-9]+),\s*([0-9]+),\s*([0-9]+),?\s*([0-9.]+)?\)\s*$/i; +const HSL_REGEXP = + /^\s*hsla?\(([0-9]+),\s*([0-9]+)%,\s*([0-9]+)%,?\s*([0-9.]+)?\)\s*$/i; const HEX_REGEXP = /^\s*#?([0-9a-f]{3}|[0-9a-f]{6})\s*$/i; const SHORTHEX_REGEXP = /^\s*#?([0-9a-f]{3})\s*$/i; @@ -125,9 +138,9 @@ const ColorPicker = { }; const fallbackColor = { - [ColorSpace.HEX]: 'transparent', - [ColorSpace.RGB]: 'rgba(0, 0, 0, 0)', - [ColorSpace.HSL]: 'hsla(0, 0%, 0%, 0)', + [ColorSpace.HEX]: "transparent", + [ColorSpace.RGB]: "rgba(0, 0, 0, 0)", + [ColorSpace.HSL]: "hsla(0, 0%, 0%, 0)", }; const stringToArgs = (value: string) => { @@ -171,7 +184,7 @@ const parseHsl = (value: string): ParsedColor => { }; const parseHexOrKeyword = (value: string): ParsedColor => { - const plain = value.replace('#', ''); + const plain = value.replace("#", ""); // Try interpreting as keyword or hex const rgb = convert.keyword.rgb(plain as any) || convert.hex.rgb(plain); const hsl = convert.rgb.hsl(rgb); @@ -185,7 +198,7 @@ const parseHexOrKeyword = (value: string): ParsedColor => { } let valid = true; - if (mapped.startsWith('#')) { + if (mapped.startsWith("#")) { valid = HEX_REGEXP.test(mapped); } else { try { @@ -219,7 +232,11 @@ const parseValue = (value: string): ParsedColor | undefined => { return parseHexOrKeyword(value); }; -const getRealValue = (value: string, color: ParsedColor | undefined, colorSpace: ColorSpace) => { +const getRealValue = ( + value: string, + color: ParsedColor | undefined, + colorSpace: ColorSpace, +) => { if (!value || !color?.valid) { return fallbackColor[colorSpace]; } @@ -227,7 +244,7 @@ const getRealValue = (value: string, color: ParsedColor | undefined, colorSpace: if (colorSpace !== ColorSpace.HEX) { return color?.[colorSpace] || fallbackColor[colorSpace]; } - if (!color.hex.startsWith('#')) { + if (!color.hex.startsWith("#")) { try { return `#${convert.keyword.hex(color.hex as any)}`; } catch (e) { @@ -239,21 +256,23 @@ const getRealValue = (value: string, color: ParsedColor | undefined, colorSpace: if (!short) { return HEX_REGEXP.test(color.hex) ? color.hex : fallbackColor.hex; } - const [r, g, b] = short[1].split(''); + const [r, g, b] = short[1].split(""); return `#${r}${r}${g}${g}${b}${b}`; }; const useColorInput = ( initialValue: string | undefined, - onChange: (value: string | undefined) => string | void + onChange: (value: string | undefined) => string | void, ) => { - const [value, setValue] = useState(initialValue || ''); + const [value, setValue] = useState(initialValue || ""); const [color, setColor] = useState(() => parseValue(value)); - const [colorSpace, setColorSpace] = useState(color?.colorSpace || ColorSpace.HEX); + const [colorSpace, setColorSpace] = useState( + color?.colorSpace || ColorSpace.HEX, + ); // Reset state when initialValue changes (when resetting controls) useEffect(() => { - const nextValue = initialValue || ''; + const nextValue = initialValue || ""; const nextColor = parseValue(nextValue); setValue(nextValue); setColor(nextColor); @@ -262,17 +281,17 @@ const useColorInput = ( const realValue = useMemo( () => getRealValue(value, color, colorSpace).toLowerCase(), - [value, color, colorSpace] + [value, color, colorSpace], ); const updateValue = useCallback( (update: string) => { const parsed = parseValue(update); - const v = parsed?.value || update || ''; + const v = parsed?.value || update || ""; setValue(v); - if (v === '') { + if (v === "") { setColor(undefined); onChange(undefined); } @@ -285,7 +304,7 @@ const useColorInput = ( setColorSpace(parsed.colorSpace); onChange(parsed.value); }, - [onChange] + [onChange], ); const cycleColorSpace = useCallback(() => { @@ -294,7 +313,7 @@ const useColorInput = ( const nextSpace = COLOR_SPACES[nextIndex]; setColorSpace(nextSpace); - const updatedValue = color?.[nextSpace] || ''; + const updatedValue = color?.[nextSpace] || ""; setValue(updatedValue); onChange(updatedValue); }, [color, colorSpace, onChange]); @@ -302,15 +321,17 @@ const useColorInput = ( return { value, realValue, updateValue, color, colorSpace, cycleColorSpace }; }; -const id = (value: string) => value.replace(/\s*/, '').toLowerCase(); +const id = (value: string) => value.replace(/\s*/, "").toLowerCase(); const usePresets = ( presetColors: PresetColor[], currentColor: ParsedColor | undefined, colorSpace: ColorSpace, - maxPresetColors = 27 + maxPresetColors = 27, ) => { - const [selectedColors, setSelectedColors] = useState(currentColor?.valid ? [currentColor] : []); + const [selectedColors, setSelectedColors] = useState( + currentColor?.valid ? [currentColor] : [], + ); // Reset state when currentColor becomes undefined (when resetting controls) useEffect(() => { @@ -322,7 +343,7 @@ const usePresets = ( const presets = useMemo(() => { const initialPresets = (presetColors || []).map((preset) => { - if (typeof preset === 'string') { + if (typeof preset === "string") { return parseValue(preset); } @@ -335,7 +356,10 @@ const usePresets = ( if (maxPresetColors === 0 || maxPresetColors === Infinity) { return combined; } - const limit = Number.isInteger(maxPresetColors) && maxPresetColors > 0 ? maxPresetColors : 27; + const limit = + Number.isInteger(maxPresetColors) && maxPresetColors > 0 + ? maxPresetColors + : 27; return combined.slice(-limit); }, [presetColors, selectedColors, maxPresetColors]); @@ -350,14 +374,14 @@ const usePresets = ( (preset) => preset && preset[colorSpace] && - id(preset[colorSpace] || '') === id(color[colorSpace] || '') + id(preset[colorSpace] || "") === id(color[colorSpace] || ""), ) ) { return; } setSelectedColors((arr) => arr.concat(color)); }, - [colorSpace, presets] + [colorSpace, presets], ); return { presets, addPreset }; @@ -377,11 +401,14 @@ export const ColorControl: FC = ({ argType, }) => { const debouncedOnChange = useCallback(debounce(onChange, 200), [onChange]); - const { value, realValue, updateValue, color, colorSpace, cycleColorSpace } = useColorInput( - initialValue, - debouncedOnChange + const { value, realValue, updateValue, color, colorSpace, cycleColorSpace } = + useColorInput(initialValue, debouncedOnChange); + const { presets, addPreset } = usePresets( + presetColors ?? [], + color, + colorSpace, + maxPresetColors, ); - const { presets, addPreset } = usePresets(presetColors ?? [], color, colorSpace, maxPresetColors); const Picker = ColorPicker[colorSpace]; const readOnly = !!argType?.table?.readonly; @@ -395,7 +422,9 @@ export const ColorControl: FC = ({ ) => updateValue(e.target.value)} + onChange={(e: ChangeEvent) => + updateValue(e.target.value) + } onFocus={(e: FocusEvent) => e.target.select()} readOnly={readOnly} placeholder="Choose color..." @@ -408,7 +437,7 @@ export const ColorControl: FC = ({ popover={ {presets.length > 0 && ( @@ -420,17 +449,17 @@ export const ColorControl: FC = ({ padding="small" size="small" ariaLabel="Pick this color" - tooltip={preset?.keyword || preset?.value || ''} - value={preset?.value || ''} + tooltip={preset?.keyword || preset?.value || ""} + value={preset?.value || ""} selected={ !!( color && preset && preset[colorSpace] && - id(preset[colorSpace] || '') === id(color[colorSpace]) + id(preset[colorSpace] || "") === id(color[colorSpace]) ) } - onClick={() => preset && updateValue(preset.value || '')} + onClick={() => preset && updateValue(preset.value || "")} /> ))} diff --git a/code/addons/docs/src/blocks/controls/types.ts b/code/addons/docs/src/blocks/controls/types.ts index ff909b4bdc8e..b226910a1242 100644 --- a/code/addons/docs/src/blocks/controls/types.ts +++ b/code/addons/docs/src/blocks/controls/types.ts @@ -1,4 +1,4 @@ -import type { ArgType } from '../components/ArgsTable/types'; +import type { ArgType } from "../components/ArgsTable/types"; export interface ControlProps { name: string; @@ -56,12 +56,12 @@ export type OptionsArray = any[]; export type OptionsObject = Record; export type Options = OptionsArray | OptionsObject; export type OptionsControlType = - | 'radio' - | 'inline-radio' - | 'check' - | 'inline-check' - | 'select' - | 'multi-select'; + | "radio" + | "inline-radio" + | "check" + | "inline-check" + | "select" + | "multi-select"; export interface OptionsConfig { labels?: Record; @@ -78,15 +78,15 @@ export interface TextConfig { } export type ControlType = - | 'array' - | 'boolean' - | 'color' - | 'date' - | 'number' - | 'range' - | 'object' + | "array" + | "boolean" + | "color" + | "date" + | "number" + | "range" + | "object" | OptionsControlType - | 'text'; + | "text"; export type Control = | BooleanConfig From 11b7b484fc90b617253a85c67c389caa4f1f66d9 Mon Sep 17 00:00:00 2001 From: Steve Dodier-Lazaro Date: Mon, 23 Mar 2026 21:41:28 +0100 Subject: [PATCH 8/8] Reformat files with oxfmt --- .../src/blocks/controls/Color.stories.tsx | 96 ++++----- .../addons/docs/src/blocks/controls/Color.tsx | 193 ++++++++---------- code/addons/docs/src/blocks/controls/types.ts | 30 +-- 3 files changed, 145 insertions(+), 174 deletions(-) diff --git a/code/addons/docs/src/blocks/controls/Color.stories.tsx b/code/addons/docs/src/blocks/controls/Color.stories.tsx index 75f5b0ef03cd..fed25a0e7abc 100644 --- a/code/addons/docs/src/blocks/controls/Color.stories.tsx +++ b/code/addons/docs/src/blocks/controls/Color.stories.tsx @@ -1,24 +1,24 @@ -import type { Meta, StoryObj } from "@storybook/react-vite"; +import type { Meta, StoryObj } from '@storybook/react-vite'; -import { fn } from "storybook/test"; +import { fn } from 'storybook/test'; -import { ColorControl } from "./Color"; +import { ColorControl } from './Color'; const meta = { component: ColorControl, parameters: { - withRawArg: "value", - controls: { include: ["value", "startOpen"] }, + withRawArg: 'value', + controls: { include: ['value', 'startOpen'] }, }, - tags: ["autodocs"], + tags: ['autodocs'], argTypes: { value: { control: { - type: "color", + type: 'color', }, }, }, - args: { name: "color", onChange: fn() }, + args: { name: 'color', onChange: fn() }, } satisfies Meta; export default meta; @@ -27,7 +27,7 @@ type Story = StoryObj; export const Basic: Story = { args: { - value: "#ff00ff", + value: '#ff00ff', }, }; @@ -39,58 +39,58 @@ export const Undefined: Story = { export const WithPresetColors: Story = { args: { - value: "#00ffff", + value: '#00ffff', presetColors: [ - { color: "#ff4785", title: "Coral" }, - { color: "#1EA7FD", title: "Ocean" }, - { color: "rgb(252, 82, 31)", title: "Orange" }, - { color: "rgba(255, 174, 0, 0.5)", title: "Gold" }, - { color: "hsl(101, 52%, 49%)", title: "Green" }, - { color: "hsla(179,65%,53%,0.5)", title: "Seafoam" }, - { color: "#6F2CAC", title: "Purple" }, - { color: "#2A0481", title: "Ultraviolet" }, - { color: "black" }, - { color: "#333", title: "Darkest" }, - { color: "#444", title: "Darker" }, - { color: "#666", title: "Dark" }, - { color: "#999", title: "Mediumdark" }, - { color: "#ddd", title: "Medium" }, - { color: "#EEE", title: "Mediumlight" }, - { color: "#F3F3F3", title: "Light" }, - { color: "#F8F8F8", title: "Lighter" }, - { color: "#FFFFFF", title: "Lightest" }, - "#fe4a49", - "#FED766", - "rgba(0, 159, 183, 1)", - "hsla(240,11%,91%,0.5)", - "slategray", + { color: '#ff4785', title: 'Coral' }, + { color: '#1EA7FD', title: 'Ocean' }, + { color: 'rgb(252, 82, 31)', title: 'Orange' }, + { color: 'rgba(255, 174, 0, 0.5)', title: 'Gold' }, + { color: 'hsl(101, 52%, 49%)', title: 'Green' }, + { color: 'hsla(179,65%,53%,0.5)', title: 'Seafoam' }, + { color: '#6F2CAC', title: 'Purple' }, + { color: '#2A0481', title: 'Ultraviolet' }, + { color: 'black' }, + { color: '#333', title: 'Darkest' }, + { color: '#444', title: 'Darker' }, + { color: '#666', title: 'Dark' }, + { color: '#999', title: 'Mediumdark' }, + { color: '#ddd', title: 'Medium' }, + { color: '#EEE', title: 'Mediumlight' }, + { color: '#F3F3F3', title: 'Light' }, + { color: '#F8F8F8', title: 'Lighter' }, + { color: '#FFFFFF', title: 'Lightest' }, + '#fe4a49', + '#FED766', + 'rgba(0, 159, 183, 1)', + 'hsla(240,11%,91%,0.5)', + 'slategray', ], }, }; export const WithMaxPresetColors: Story = { - name: "With maxPresetColors (limit to 5)", + name: 'With maxPresetColors (limit to 5)', args: { - value: "#00ffff", + value: '#00ffff', startOpen: true, maxPresetColors: 5, presetColors: [ - { color: "#ff4785", title: "Coral" }, - { color: "#1EA7FD", title: "Ocean" }, - { color: "rgb(252, 82, 31)", title: "Orange" }, - { color: "rgba(255, 174, 0, 0.5)", title: "Gold" }, - { color: "hsl(101, 52%, 49%)", title: "Green" }, - { color: "hsla(179,65%,53%,0.5)", title: "Seafoam" }, - { color: "#6F2CAC", title: "Purple" }, - { color: "#2A0481", title: "Ultraviolet" }, + { color: '#ff4785', title: 'Coral' }, + { color: '#1EA7FD', title: 'Ocean' }, + { color: 'rgb(252, 82, 31)', title: 'Orange' }, + { color: 'rgba(255, 174, 0, 0.5)', title: 'Gold' }, + { color: 'hsl(101, 52%, 49%)', title: 'Green' }, + { color: 'hsla(179,65%,53%,0.5)', title: 'Seafoam' }, + { color: '#6F2CAC', title: 'Purple' }, + { color: '#2A0481', title: 'Ultraviolet' }, ], }, }; export const WithUnlimitedPresetColors: Story = { - name: "With unlimited presets (maxPresetColors: 0)", + name: 'With unlimited presets (maxPresetColors: 0)', args: { - value: "#00ffff", + value: '#00ffff', startOpen: true, maxPresetColors: 0, presetColors: Array.from({ length: 40 }, (_, i) => { @@ -101,9 +101,9 @@ export const WithUnlimitedPresetColors: Story = { }; export const WithInvalidMaxPresetColors: Story = { - name: "With invalid maxPresetColors (negative, falls back to 27)", + name: 'With invalid maxPresetColors (negative, falls back to 27)', args: { - value: "#00ffff", + value: '#00ffff', startOpen: true, maxPresetColors: -5, presetColors: Array.from({ length: 40 }, (_, i) => { @@ -121,7 +121,7 @@ export const StartOpen: Story = { export const Readonly: Story = { args: { - value: "#ff00ff", + value: '#ff00ff', argType: { table: { readonly: true } }, }, }; diff --git a/code/addons/docs/src/blocks/controls/Color.tsx b/code/addons/docs/src/blocks/controls/Color.tsx index 82e054506d89..b109004c2e63 100644 --- a/code/addons/docs/src/blocks/controls/Color.tsx +++ b/code/addons/docs/src/blocks/controls/Color.tsx @@ -1,29 +1,20 @@ -import type { ChangeEvent, FC, FocusEvent } from "react"; -import React, { useCallback, useEffect, useMemo, useState } from "react"; - -import { Button, Form, PopoverProvider } from "storybook/internal/components"; - -import { MarkupIcon } from "@storybook/icons"; - -import convert from "color-convert"; -import { debounce } from "es-toolkit/function"; -import { - HexColorPicker, - HslaStringColorPicker, - RgbaStringColorPicker, -} from "react-colorful"; -import { styled } from "storybook/theming"; - -import { getControlId } from "./helpers"; -import type { - ColorConfig, - ColorValue, - ControlProps, - PresetColor, -} from "./types"; +import type { ChangeEvent, FC, FocusEvent } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; + +import { Button, Form, PopoverProvider } from 'storybook/internal/components'; + +import { MarkupIcon } from '@storybook/icons'; + +import convert from 'color-convert'; +import { debounce } from 'es-toolkit/function'; +import { HexColorPicker, HslaStringColorPicker, RgbaStringColorPicker } from 'react-colorful'; +import { styled } from 'storybook/theming'; + +import { getControlId } from './helpers'; +import type { ColorConfig, ColorValue, ControlProps, PresetColor } from './types'; const Wrapper = styled.div({ - position: "relative", + position: 'relative', maxWidth: 250, }); @@ -31,20 +22,20 @@ const TooltipContent = styled.div({ width: 200, margin: 5, - ".react-colorful__saturation": { - borderRadius: "4px 4px 0 0", + '.react-colorful__saturation': { + borderRadius: '4px 4px 0 0', }, - ".react-colorful__hue": { - boxShadow: "inset 0 0 0 1px rgb(0 0 0 / 5%)", + '.react-colorful__hue': { + boxShadow: 'inset 0 0 0 1px rgb(0 0 0 / 5%)', }, - ".react-colorful__last-control": { - borderRadius: "0 0 4px 4px", + '.react-colorful__last-control': { + borderRadius: '0 0 4px 4px', }, }); const Swatches = styled.div({ - display: "grid", - gridTemplateColumns: "repeat(9, 16px)", + display: 'grid', + gridTemplateColumns: 'repeat(9, 16px)', gap: 6, padding: 3, marginTop: 5, @@ -52,7 +43,7 @@ const Swatches = styled.div({ }); const swatchBackground = (isDark: boolean) => - `url('data:image/svg+xml;charset=utf-8,')`; + `url('data:image/svg+xml;charset=utf-8,')`; const SwatchColor = styled(Button)<{ selected?: boolean; value: string }>( ({ value, selected, theme }) => ({ @@ -61,63 +52,59 @@ const SwatchColor = styled(Button)<{ selected?: boolean; value: string }>( boxShadow: selected ? `${theme.appBorderColor} 0 0 0 1px inset, ${theme.textMutedColor}50 0 0 0 4px` : `${theme.appBorderColor} 0 0 0 1px inset`, - border: "none", + border: 'none', borderRadius: theme.appBorderRadius, - "&, &:hover": { - background: "unset", - backgroundColor: "unset", - backgroundImage: `linear-gradient(${value}, ${value}), ${swatchBackground(theme.base === "dark")}`, + '&, &:hover': { + background: 'unset', + backgroundColor: 'unset', + backgroundImage: `linear-gradient(${value}, ${value}), ${swatchBackground(theme.base === 'dark')}`, }, - }), + }) ); const Input = styled(Form.Input)(({ theme }) => ({ - width: "100%", + width: '100%', paddingLeft: 30, paddingRight: 30, - boxSizing: "border-box", + boxSizing: 'border-box', fontFamily: theme.typography.fonts.base, '[aria-readonly="true"] > &': { - background: theme.base === "light" ? theme.color.lighter : "transparent", + background: theme.base === 'light' ? theme.color.lighter : 'transparent', }, })); -const PopoverTrigger = styled(SwatchColor)<{ disabled: boolean }>( - ({ disabled }) => ({ - position: "absolute", - top: 4, - left: 4, - zIndex: 1, - cursor: disabled ? "not-allowed" : "pointer", - }), -); +const PopoverTrigger = styled(SwatchColor)<{ disabled: boolean }>(({ disabled }) => ({ + position: 'absolute', + top: 4, + left: 4, + zIndex: 1, + cursor: disabled ? 'not-allowed' : 'pointer', +})); const CycleColorSpaceButton = styled(Button)(({ theme }) => ({ - position: "absolute", + position: 'absolute', zIndex: 1, top: 6, right: 7, width: 20, height: 20, padding: 4, - boxSizing: "border-box", - cursor: "pointer", + boxSizing: 'border-box', + cursor: 'pointer', color: theme.input.color, })); enum ColorSpace { - RGB = "rgb", - HSL = "hsl", - HEX = "hex", + RGB = 'rgb', + HSL = 'hsl', + HEX = 'hex', } const COLOR_SPACES = Object.values(ColorSpace); const COLOR_REGEXP = /\(([0-9]+),\s*([0-9]+)%?,\s*([0-9]+)%?,?\s*([0-9.]+)?\)/; -const RGB_REGEXP = - /^\s*rgba?\(([0-9]+),\s*([0-9]+),\s*([0-9]+),?\s*([0-9.]+)?\)\s*$/i; -const HSL_REGEXP = - /^\s*hsla?\(([0-9]+),\s*([0-9]+)%,\s*([0-9]+)%,?\s*([0-9.]+)?\)\s*$/i; +const RGB_REGEXP = /^\s*rgba?\(([0-9]+),\s*([0-9]+),\s*([0-9]+),?\s*([0-9.]+)?\)\s*$/i; +const HSL_REGEXP = /^\s*hsla?\(([0-9]+),\s*([0-9]+)%,\s*([0-9]+)%,?\s*([0-9.]+)?\)\s*$/i; const HEX_REGEXP = /^\s*#?([0-9a-f]{3}|[0-9a-f]{6})\s*$/i; const SHORTHEX_REGEXP = /^\s*#?([0-9a-f]{3})\s*$/i; @@ -138,9 +125,9 @@ const ColorPicker = { }; const fallbackColor = { - [ColorSpace.HEX]: "transparent", - [ColorSpace.RGB]: "rgba(0, 0, 0, 0)", - [ColorSpace.HSL]: "hsla(0, 0%, 0%, 0)", + [ColorSpace.HEX]: 'transparent', + [ColorSpace.RGB]: 'rgba(0, 0, 0, 0)', + [ColorSpace.HSL]: 'hsla(0, 0%, 0%, 0)', }; const stringToArgs = (value: string) => { @@ -184,7 +171,7 @@ const parseHsl = (value: string): ParsedColor => { }; const parseHexOrKeyword = (value: string): ParsedColor => { - const plain = value.replace("#", ""); + const plain = value.replace('#', ''); // Try interpreting as keyword or hex const rgb = convert.keyword.rgb(plain as any) || convert.hex.rgb(plain); const hsl = convert.rgb.hsl(rgb); @@ -198,7 +185,7 @@ const parseHexOrKeyword = (value: string): ParsedColor => { } let valid = true; - if (mapped.startsWith("#")) { + if (mapped.startsWith('#')) { valid = HEX_REGEXP.test(mapped); } else { try { @@ -232,11 +219,7 @@ const parseValue = (value: string): ParsedColor | undefined => { return parseHexOrKeyword(value); }; -const getRealValue = ( - value: string, - color: ParsedColor | undefined, - colorSpace: ColorSpace, -) => { +const getRealValue = (value: string, color: ParsedColor | undefined, colorSpace: ColorSpace) => { if (!value || !color?.valid) { return fallbackColor[colorSpace]; } @@ -244,7 +227,7 @@ const getRealValue = ( if (colorSpace !== ColorSpace.HEX) { return color?.[colorSpace] || fallbackColor[colorSpace]; } - if (!color.hex.startsWith("#")) { + if (!color.hex.startsWith('#')) { try { return `#${convert.keyword.hex(color.hex as any)}`; } catch (e) { @@ -256,23 +239,21 @@ const getRealValue = ( if (!short) { return HEX_REGEXP.test(color.hex) ? color.hex : fallbackColor.hex; } - const [r, g, b] = short[1].split(""); + const [r, g, b] = short[1].split(''); return `#${r}${r}${g}${g}${b}${b}`; }; const useColorInput = ( initialValue: string | undefined, - onChange: (value: string | undefined) => string | void, + onChange: (value: string | undefined) => string | void ) => { - const [value, setValue] = useState(initialValue || ""); + const [value, setValue] = useState(initialValue || ''); const [color, setColor] = useState(() => parseValue(value)); - const [colorSpace, setColorSpace] = useState( - color?.colorSpace || ColorSpace.HEX, - ); + const [colorSpace, setColorSpace] = useState(color?.colorSpace || ColorSpace.HEX); // Reset state when initialValue changes (when resetting controls) useEffect(() => { - const nextValue = initialValue || ""; + const nextValue = initialValue || ''; const nextColor = parseValue(nextValue); setValue(nextValue); setColor(nextColor); @@ -281,17 +262,17 @@ const useColorInput = ( const realValue = useMemo( () => getRealValue(value, color, colorSpace).toLowerCase(), - [value, color, colorSpace], + [value, color, colorSpace] ); const updateValue = useCallback( (update: string) => { const parsed = parseValue(update); - const v = parsed?.value || update || ""; + const v = parsed?.value || update || ''; setValue(v); - if (v === "") { + if (v === '') { setColor(undefined); onChange(undefined); } @@ -304,7 +285,7 @@ const useColorInput = ( setColorSpace(parsed.colorSpace); onChange(parsed.value); }, - [onChange], + [onChange] ); const cycleColorSpace = useCallback(() => { @@ -313,7 +294,7 @@ const useColorInput = ( const nextSpace = COLOR_SPACES[nextIndex]; setColorSpace(nextSpace); - const updatedValue = color?.[nextSpace] || ""; + const updatedValue = color?.[nextSpace] || ''; setValue(updatedValue); onChange(updatedValue); }, [color, colorSpace, onChange]); @@ -321,17 +302,15 @@ const useColorInput = ( return { value, realValue, updateValue, color, colorSpace, cycleColorSpace }; }; -const id = (value: string) => value.replace(/\s*/, "").toLowerCase(); +const id = (value: string) => value.replace(/\s*/, '').toLowerCase(); const usePresets = ( presetColors: PresetColor[], currentColor: ParsedColor | undefined, colorSpace: ColorSpace, - maxPresetColors = 27, + maxPresetColors = 27 ) => { - const [selectedColors, setSelectedColors] = useState( - currentColor?.valid ? [currentColor] : [], - ); + const [selectedColors, setSelectedColors] = useState(currentColor?.valid ? [currentColor] : []); // Reset state when currentColor becomes undefined (when resetting controls) useEffect(() => { @@ -343,7 +322,7 @@ const usePresets = ( const presets = useMemo(() => { const initialPresets = (presetColors || []).map((preset) => { - if (typeof preset === "string") { + if (typeof preset === 'string') { return parseValue(preset); } @@ -356,10 +335,7 @@ const usePresets = ( if (maxPresetColors === 0 || maxPresetColors === Infinity) { return combined; } - const limit = - Number.isInteger(maxPresetColors) && maxPresetColors > 0 - ? maxPresetColors - : 27; + const limit = Number.isInteger(maxPresetColors) && maxPresetColors > 0 ? maxPresetColors : 27; return combined.slice(-limit); }, [presetColors, selectedColors, maxPresetColors]); @@ -374,14 +350,14 @@ const usePresets = ( (preset) => preset && preset[colorSpace] && - id(preset[colorSpace] || "") === id(color[colorSpace] || ""), + id(preset[colorSpace] || '') === id(color[colorSpace] || '') ) ) { return; } setSelectedColors((arr) => arr.concat(color)); }, - [colorSpace, presets], + [colorSpace, presets] ); return { presets, addPreset }; @@ -401,14 +377,11 @@ export const ColorControl: FC = ({ argType, }) => { const debouncedOnChange = useCallback(debounce(onChange, 200), [onChange]); - const { value, realValue, updateValue, color, colorSpace, cycleColorSpace } = - useColorInput(initialValue, debouncedOnChange); - const { presets, addPreset } = usePresets( - presetColors ?? [], - color, - colorSpace, - maxPresetColors, + const { value, realValue, updateValue, color, colorSpace, cycleColorSpace } = useColorInput( + initialValue, + debouncedOnChange ); + const { presets, addPreset } = usePresets(presetColors ?? [], color, colorSpace, maxPresetColors); const Picker = ColorPicker[colorSpace]; const readOnly = !!argType?.table?.readonly; @@ -422,9 +395,7 @@ export const ColorControl: FC = ({ ) => - updateValue(e.target.value) - } + onChange={(e: ChangeEvent) => updateValue(e.target.value)} onFocus={(e: FocusEvent) => e.target.select()} readOnly={readOnly} placeholder="Choose color..." @@ -437,7 +408,7 @@ export const ColorControl: FC = ({ popover={ {presets.length > 0 && ( @@ -449,17 +420,17 @@ export const ColorControl: FC = ({ padding="small" size="small" ariaLabel="Pick this color" - tooltip={preset?.keyword || preset?.value || ""} - value={preset?.value || ""} + tooltip={preset?.keyword || preset?.value || ''} + value={preset?.value || ''} selected={ !!( color && preset && preset[colorSpace] && - id(preset[colorSpace] || "") === id(color[colorSpace]) + id(preset[colorSpace] || '') === id(color[colorSpace]) ) } - onClick={() => preset && updateValue(preset.value || "")} + onClick={() => preset && updateValue(preset.value || '')} /> ))} diff --git a/code/addons/docs/src/blocks/controls/types.ts b/code/addons/docs/src/blocks/controls/types.ts index b226910a1242..ff909b4bdc8e 100644 --- a/code/addons/docs/src/blocks/controls/types.ts +++ b/code/addons/docs/src/blocks/controls/types.ts @@ -1,4 +1,4 @@ -import type { ArgType } from "../components/ArgsTable/types"; +import type { ArgType } from '../components/ArgsTable/types'; export interface ControlProps { name: string; @@ -56,12 +56,12 @@ export type OptionsArray = any[]; export type OptionsObject = Record; export type Options = OptionsArray | OptionsObject; export type OptionsControlType = - | "radio" - | "inline-radio" - | "check" - | "inline-check" - | "select" - | "multi-select"; + | 'radio' + | 'inline-radio' + | 'check' + | 'inline-check' + | 'select' + | 'multi-select'; export interface OptionsConfig { labels?: Record; @@ -78,15 +78,15 @@ export interface TextConfig { } export type ControlType = - | "array" - | "boolean" - | "color" - | "date" - | "number" - | "range" - | "object" + | 'array' + | 'boolean' + | 'color' + | 'date' + | 'number' + | 'range' + | 'object' | OptionsControlType - | "text"; + | 'text'; export type Control = | BooleanConfig