Skip to content
Snippets Groups Projects
Unverified Commit 20b856fa authored by Alexander Polyankin's avatar Alexander Polyankin Committed by GitHub
Browse files

Use new ColorSelector for single color chart settings (#22974)

parent f7d8cb31
No related branches found
No related tags found
No related merge requests found
Showing
with 86 additions and 179 deletions
...@@ -5,8 +5,8 @@ module.exports = { ...@@ -5,8 +5,8 @@ module.exports = {
builder: "webpack5", builder: "webpack5",
}, },
stories: [ stories: [
"../(frontend|enterprise)/**/*.stories.mdx", "../frontend/**/*.stories.mdx",
"../(frontend|enterprise)/**/*.stories.@(js|jsx|ts|tsx)", "../frontend/**/*.stories.@(js|jsx|ts|tsx)",
], ],
addons: ["@storybook/addon-essentials", "@storybook/addon-links"], addons: ["@storybook/addon-essentials", "@storybook/addon-links"],
webpackFinal: storybookConfig => ({ webpackFinal: storybookConfig => ({
......
import React, { memo, useCallback, useMemo, useRef } from "react"; import React, { memo, useCallback, useMemo } from "react";
import { t } from "ttag"; import { t } from "ttag";
import { set, omit } from "lodash"; import { omit, set } from "lodash";
import { useCurrentRef } from "metabase/hooks/use-current-ref";
import ColorPicker from "metabase/core/components/ColorPicker"; import ColorPicker from "metabase/core/components/ColorPicker";
import { getBrandColorOptions } from "./utils"; import { getBrandColorOptions } from "./utils";
import { ColorOption } from "./types"; import { ColorOption } from "./types";
...@@ -24,12 +25,8 @@ const BrandColorSettings = ({ ...@@ -24,12 +25,8 @@ const BrandColorSettings = ({
originalColors, originalColors,
onChange, onChange,
}: BrandColorSettingsProps): JSX.Element => { }: BrandColorSettingsProps): JSX.Element => {
const colorsRef = useRef(colors); const colorsRef = useCurrentRef(colors);
colorsRef.current = colors; const options = useMemo(getBrandColorOptions, []);
const options = useMemo(() => {
return getBrandColorOptions();
}, []);
const handleChange = useCallback( const handleChange = useCallback(
(colorName: string, color?: string) => { (colorName: string, color?: string) => {
...@@ -39,7 +36,7 @@ const BrandColorSettings = ({ ...@@ -39,7 +36,7 @@ const BrandColorSettings = ({
onChange?.(omit({ ...colorsRef.current }, colorName)); onChange?.(omit({ ...colorsRef.current }, colorName));
} }
}, },
[onChange], [colorsRef, onChange],
); );
return ( return (
...@@ -112,10 +109,8 @@ const BrandColorRow = memo(function BrandColorRow({ ...@@ -112,10 +109,8 @@ const BrandColorRow = memo(function BrandColorRow({
<TableBodyRow> <TableBodyRow>
<TableBodyCell> <TableBodyCell>
<ColorPicker <ColorPicker
color={color ?? originalColor} value={color ?? originalColor}
isBordered isAuto={color == null || color === originalColor}
isSelected
isDefault={color == null || color === originalColor}
onChange={handleChange} onChange={handleChange}
/> />
</TableBodyCell> </TableBodyCell>
......
import React, { memo, useCallback, useMemo, useRef } from "react"; import React, { memo, useCallback, useMemo } from "react";
import { t } from "ttag"; import { t } from "ttag";
import { flatten, omit, set } from "lodash"; import { flatten, omit, set } from "lodash";
import { useCurrentRef } from "metabase/hooks/use-current-ref";
import ColorPicker from "metabase/core/components/ColorPicker"; import ColorPicker from "metabase/core/components/ColorPicker";
import { getChartColorGroups } from "./utils"; import { getChartColorGroups } from "./utils";
import { import {
...@@ -23,12 +24,8 @@ const ChartColorSettings = ({ ...@@ -23,12 +24,8 @@ const ChartColorSettings = ({
originalColors, originalColors,
onChange, onChange,
}: ChartColorSettingsProps): JSX.Element => { }: ChartColorSettingsProps): JSX.Element => {
const colorsRef = useRef(colors); const colorsRef = useCurrentRef(colors);
colorsRef.current = colors; const colorGroups = useMemo(getChartColorGroups, []);
const colorGroups = useMemo(() => {
return getChartColorGroups();
}, []);
const handleChange = useCallback( const handleChange = useCallback(
(colorName: string, color?: string) => { (colorName: string, color?: string) => {
...@@ -38,12 +35,12 @@ const ChartColorSettings = ({ ...@@ -38,12 +35,12 @@ const ChartColorSettings = ({
onChange?.(omit({ ...colorsRef.current }, colorName)); onChange?.(omit({ ...colorsRef.current }, colorName));
} }
}, },
[onChange], [colorsRef, onChange],
); );
const handleReset = useCallback(() => { const handleReset = useCallback(() => {
onChange?.(omit({ ...colorsRef.current }, flatten(colorGroups))); onChange?.(omit({ ...colorsRef.current }, flatten(colorGroups)));
}, [colorGroups, onChange]); }, [colorsRef, colorGroups, onChange]);
return ( return (
<ChartColorTable <ChartColorTable
...@@ -119,10 +116,8 @@ const ChartColorCell = memo(function ChartColorCell({ ...@@ -119,10 +116,8 @@ const ChartColorCell = memo(function ChartColorCell({
return ( return (
<TableBodyCell> <TableBodyCell>
<ColorPicker <ColorPicker
color={color ?? originalColor} value={color ?? originalColor}
isBordered isAuto={color == null || color === originalColor}
isSelected
isDefault={color == null || color === originalColor}
onChange={handleChange} onChange={handleChange}
/> />
</TableBodyCell> </TableBodyCell>
......
import React from "react";
import { ComponentStory } from "@storybook/react";
import { color } from "metabase/lib/colors";
import ColorSettings from "./ColorSettings";
export default {
title: "Whitelabel/ColorSettings",
component: ColorSettings,
};
const Template: ComponentStory<typeof ColorSettings> = args => {
return <ColorSettings {...args} />;
};
export const Default = Template.bind({});
Default.args = {
initialColors: {
brand: color("brand"),
},
originalColors: {
brand: color("brand"),
accent1: color("accent1"),
accent7: color("accent7"),
},
};
...@@ -13,7 +13,6 @@ import SettingRadio from "./widgets/SettingRadio"; ...@@ -13,7 +13,6 @@ import SettingRadio from "./widgets/SettingRadio";
import SettingToggle from "./widgets/SettingToggle"; import SettingToggle from "./widgets/SettingToggle";
import SettingSelect from "./widgets/SettingSelect"; import SettingSelect from "./widgets/SettingSelect";
import SettingText from "./widgets/SettingText"; import SettingText from "./widgets/SettingText";
import SettingColor from "./widgets/SettingColor";
import { settingToFormFieldId } from "./../../settings/utils"; import { settingToFormFieldId } from "./../../settings/utils";
const SETTING_WIDGET_MAP = { const SETTING_WIDGET_MAP = {
...@@ -24,7 +23,6 @@ const SETTING_WIDGET_MAP = { ...@@ -24,7 +23,6 @@ const SETTING_WIDGET_MAP = {
radio: SettingRadio, radio: SettingRadio,
boolean: SettingToggle, boolean: SettingToggle,
text: SettingText, text: SettingText,
color: SettingColor,
}; };
const updatePlaceholderForEnvironmentVars = props => { const updatePlaceholderForEnvironmentVars = props => {
......
/* eslint-disable react/prop-types */
import React from "react";
import ColorPicker from "metabase/components/ColorPicker";
const SettingColor = ({ setting, onChange }) => (
<ColorPicker value={setting.value || setting.default} onChange={onChange} />
);
export default SettingColor;
...@@ -9,13 +9,13 @@ export default { ...@@ -9,13 +9,13 @@ export default {
}; };
const Template: ComponentStory<typeof ColorInput> = args => { const Template: ComponentStory<typeof ColorInput> = args => {
const [{ color }, updateArgs] = useArgs(); const [{ value }, updateArgs] = useArgs();
const handleChange = (color?: string) => { const handleChange = (value?: string) => {
updateArgs({ color }); updateArgs({ value });
}; };
return <ColorInput {...args} color={color} onChange={handleChange} />; return <ColorInput {...args} value={value} onChange={handleChange} />;
}; };
export const Default = Template.bind({}); export const Default = Template.bind({});
...@@ -17,16 +17,16 @@ export type ColorInputAttributes = Omit< ...@@ -17,16 +17,16 @@ export type ColorInputAttributes = Omit<
>; >;
export interface ColorInputProps extends ColorInputAttributes { export interface ColorInputProps extends ColorInputAttributes {
color?: string; value?: string;
fullWidth?: boolean; fullWidth?: boolean;
onChange?: (value?: string) => void; onChange?: (value?: string) => void;
} }
const ColorInput = forwardRef(function ColorInput( const ColorInput = forwardRef(function ColorInput(
{ color, onFocus, onBlur, onChange, ...props }: ColorInputProps, { value, onFocus, onBlur, onChange, ...props }: ColorInputProps,
ref: Ref<HTMLDivElement>, ref: Ref<HTMLDivElement>,
) { ) {
const colorText = useMemo(() => getColorHex(color) ?? "", [color]); const colorText = useMemo(() => getColorHex(value) ?? "", [value]);
const [inputText, setInputText] = useState(colorText); const [inputText, setInputText] = useState(colorText);
const [isFocused, setIsFocused] = useState(false); const [isFocused, setIsFocused] = useState(false);
...@@ -69,9 +69,9 @@ const ColorInput = forwardRef(function ColorInput( ...@@ -69,9 +69,9 @@ const ColorInput = forwardRef(function ColorInput(
); );
}); });
const getColorHex = (color?: string) => { const getColorHex = (value?: string) => {
try { try {
return color ? Color(color).hex() : undefined; return value ? Color(value).hex() : undefined;
} catch (e) { } catch (e) {
return undefined; return undefined;
} }
......
...@@ -10,17 +10,17 @@ export default { ...@@ -10,17 +10,17 @@ export default {
}; };
const Template: ComponentStory<typeof ColorPicker> = args => { const Template: ComponentStory<typeof ColorPicker> = args => {
const [{ color }, updateArgs] = useArgs(); const [{ value }, updateArgs] = useArgs();
const handleChange = (color?: string) => { const handleChange = (value?: string) => {
updateArgs({ color }); updateArgs({ value });
}; };
return <ColorPicker {...args} color={color} onChange={handleChange} />; return <ColorPicker {...args} value={value} onChange={handleChange} />;
}; };
export const Default = Template.bind({}); export const Default = Template.bind({});
Default.args = { Default.args = {
color: color("brand"), value: color("brand"),
placeholder: color("brand"), placeholder: color("brand"),
}; };
...@@ -9,24 +9,14 @@ export type ColorPickerAttributes = Omit< ...@@ -9,24 +9,14 @@ export type ColorPickerAttributes = Omit<
>; >;
export interface ColorPickerProps extends ColorPickerAttributes { export interface ColorPickerProps extends ColorPickerAttributes {
color: string; value: string;
placeholder?: string; placeholder?: string;
isBordered?: boolean; isAuto?: boolean;
isSelected?: boolean;
isDefault?: boolean;
onChange?: (color?: string) => void; onChange?: (color?: string) => void;
} }
const ColorPicker = forwardRef(function ColorPicker( const ColorPicker = forwardRef(function ColorPicker(
{ { value, placeholder, isAuto, onChange, ...props }: ColorPickerProps,
color,
placeholder,
isBordered,
isSelected,
isDefault,
onChange,
...props
}: ColorPickerProps,
ref: Ref<HTMLDivElement>, ref: Ref<HTMLDivElement>,
) { ) {
return ( return (
...@@ -36,16 +26,14 @@ const ColorPicker = forwardRef(function ColorPicker( ...@@ -36,16 +26,14 @@ const ColorPicker = forwardRef(function ColorPicker(
<ColorPickerTrigger <ColorPickerTrigger
{...props} {...props}
ref={ref} ref={ref}
color={color} value={value}
placeholder={placeholder} placeholder={placeholder}
isBordered={isBordered} isAuto={isAuto}
isSelected={isSelected}
isDefault={isDefault}
onClick={onClick} onClick={onClick}
onChange={onChange} onChange={onChange}
/> />
)} )}
popoverContent={<ColorPickerContent color={color} onChange={onChange} />} popoverContent={<ColorPickerContent value={value} onChange={onChange} />}
/> />
); );
}); });
......
...@@ -5,10 +5,10 @@ import userEvent from "@testing-library/user-event"; ...@@ -5,10 +5,10 @@ import userEvent from "@testing-library/user-event";
import ColorPicker from "./ColorPicker"; import ColorPicker from "./ColorPicker";
const TestColorPicker = () => { const TestColorPicker = () => {
const [color, setColor] = useState("white"); const [value, setValue] = useState("white");
const handleChange = (color?: string) => setColor(color ?? "white"); const handleChange = (value?: string) => setValue(value ?? "white");
return <ColorPicker color={color} onChange={handleChange} />; return <ColorPicker value={value} onChange={handleChange} />;
}; };
describe("ColorPicker", () => { describe("ColorPicker", () => {
......
...@@ -10,12 +10,12 @@ export type ColorPickerContentAttributes = Omit< ...@@ -10,12 +10,12 @@ export type ColorPickerContentAttributes = Omit<
>; >;
export interface ColorPickerContentProps extends ColorPickerContentAttributes { export interface ColorPickerContentProps extends ColorPickerContentAttributes {
color?: string; value?: string;
onChange?: (color?: string) => void; onChange?: (value?: string) => void;
} }
const ColorPickerContent = forwardRef(function ColorPickerContent( const ColorPickerContent = forwardRef(function ColorPickerContent(
{ color, onChange, ...props }: ColorPickerContentProps, { value, onChange, ...props }: ColorPickerContentProps,
ref: Ref<HTMLDivElement>, ref: Ref<HTMLDivElement>,
) { ) {
const handleChange = useCallback( const handleChange = useCallback(
...@@ -25,8 +25,8 @@ const ColorPickerContent = forwardRef(function ColorPickerContent( ...@@ -25,8 +25,8 @@ const ColorPickerContent = forwardRef(function ColorPickerContent(
return ( return (
<ContentContainer {...props} ref={ref}> <ContentContainer {...props} ref={ref}>
<ColorPickerControls color={color} onChange={handleChange} /> <ColorPickerControls color={value} onChange={handleChange} />
<ColorInput color={color} fullWidth onChange={onChange} /> <ColorInput value={value} fullWidth onChange={onChange} />
</ContentContainer> </ContentContainer>
); );
}); });
......
...@@ -5,21 +5,17 @@ import { TriggerContainer } from "./ColorPicker.styled"; ...@@ -5,21 +5,17 @@ import { TriggerContainer } from "./ColorPicker.styled";
export interface ColorPickerTriggerProps export interface ColorPickerTriggerProps
extends Omit<HTMLAttributes<HTMLDivElement>, "onChange"> { extends Omit<HTMLAttributes<HTMLDivElement>, "onChange"> {
color: string; value: string;
placeholder?: string; placeholder?: string;
isBordered?: boolean; isAuto?: boolean;
isSelected?: boolean; onChange?: (value?: string) => void;
isDefault?: boolean;
onChange?: (color?: string) => void;
} }
const ColorPickerTrigger = forwardRef(function ColorPickerTrigger( const ColorPickerTrigger = forwardRef(function ColorPickerTrigger(
{ {
color, value,
placeholder, placeholder,
isBordered, isAuto,
isSelected,
isDefault,
onClick, onClick,
onChange, onChange,
...props ...props
...@@ -28,15 +24,9 @@ const ColorPickerTrigger = forwardRef(function ColorPickerTrigger( ...@@ -28,15 +24,9 @@ const ColorPickerTrigger = forwardRef(function ColorPickerTrigger(
) { ) {
return ( return (
<TriggerContainer {...props} ref={ref}> <TriggerContainer {...props} ref={ref}>
<ColorPill <ColorPill color={value} isAuto={isAuto} onClick={onClick} />
color={color}
isBordered={isBordered}
isSelected={isSelected}
isDefault={isDefault}
onClick={onClick}
/>
<ColorInput <ColorInput
color={color} value={value}
placeholder={placeholder} placeholder={placeholder}
fullWidth fullWidth
onChange={onChange} onChange={onChange}
......
...@@ -16,3 +16,9 @@ export const Default = Template.bind({}); ...@@ -16,3 +16,9 @@ export const Default = Template.bind({});
Default.args = { Default.args = {
color: color("brand"), color: color("brand"),
}; };
export const Auto = Template.bind({});
Auto.args = {
color: color("brand"),
isAuto: true,
};
...@@ -2,30 +2,23 @@ import styled from "@emotion/styled"; ...@@ -2,30 +2,23 @@ import styled from "@emotion/styled";
import { color } from "metabase/lib/colors"; import { color } from "metabase/lib/colors";
export interface ColorPillRootProps { export interface ColorPillRootProps {
isBordered?: boolean; isAuto: boolean;
isSelected?: boolean; isSelected: boolean;
isDefault?: boolean;
} }
export const ColorPillRoot = styled.div<ColorPillRootProps>` export const ColorPillRoot = styled.div<ColorPillRootProps>`
display: inline-block; display: inline-block;
width: 2rem; padding: 0.1875rem;
height: 2rem; border-width: 0.0625rem;
padding: ${props => props.isBordered && "0.1875rem"};
border-width: ${props => (props.isBordered ? "0.0625rem" : "0")};
border-color: ${props => border-color: ${props =>
props.isSelected ? color("text-light") : "transparent"}; props.isSelected ? color("text-light") : "transparent"};
border-style: ${props => (props.isDefault ? "dashed" : "solid")}; border-style: ${props => (props.isAuto ? "dashed" : "solid")};
border-radius: 50%; border-radius: 50%;
cursor: pointer; cursor: pointer;
`; `;
export interface ColorPillContentProps { export const ColorPillContent = styled.div`
isBordered?: boolean; width: 1.5rem;
} height: 1.5rem;
export const ColorPillContent = styled.div<ColorPillContentProps>`
width: ${props => (props.isBordered ? "1.5rem" : "2rem")};
height: ${props => (props.isBordered ? "1.5rem" : "2rem")};
border-radius: 50%; border-radius: 50%;
`; `;
...@@ -3,17 +3,15 @@ import { ColorPillContent, ColorPillRoot } from "./ColorPill.styled"; ...@@ -3,17 +3,15 @@ import { ColorPillContent, ColorPillRoot } from "./ColorPill.styled";
export interface ColorPillProps extends HTMLAttributes<HTMLDivElement> { export interface ColorPillProps extends HTMLAttributes<HTMLDivElement> {
color: string; color: string;
isBordered?: boolean; isAuto?: boolean;
isSelected?: boolean; isSelected?: boolean;
isDefault?: boolean;
} }
const ColorPill = forwardRef(function ColorPill( const ColorPill = forwardRef(function ColorPill(
{ {
color, color,
isBordered, isAuto = false,
isSelected, isSelected = true,
isDefault,
"aria-label": ariaLabel = color, "aria-label": ariaLabel = color,
...props ...props
}: ColorPillProps, }: ColorPillProps,
...@@ -23,15 +21,11 @@ const ColorPill = forwardRef(function ColorPill( ...@@ -23,15 +21,11 @@ const ColorPill = forwardRef(function ColorPill(
<ColorPillRoot <ColorPillRoot
{...props} {...props}
ref={ref} ref={ref}
isBordered={isBordered} isAuto={isAuto}
isSelected={isSelected} isSelected={isSelected}
isDefault={isDefault}
aria-label={ariaLabel} aria-label={ariaLabel}
> >
<ColorPillContent <ColorPillContent style={{ backgroundColor: color }} />
isBordered={isBordered}
style={{ backgroundColor: color }}
/>
</ColorPillRoot> </ColorPillRoot>
); );
}); });
......
...@@ -10,17 +10,17 @@ export default { ...@@ -10,17 +10,17 @@ export default {
}; };
const Template: ComponentStory<typeof ColorSelector> = args => { const Template: ComponentStory<typeof ColorSelector> = args => {
const [{ color }, updateArgs] = useArgs(); const [{ value }, updateArgs] = useArgs();
const handleChange = (color: string) => { const handleChange = (value: string) => {
updateArgs({ color }); updateArgs({ value });
}; };
return <ColorSelector {...args} color={color} onChange={handleChange} />; return <ColorSelector {...args} value={value} onChange={handleChange} />;
}; };
export const Default = Template.bind({}); export const Default = Template.bind({});
Default.args = { Default.args = {
color: color("brand"), value: color("brand"),
colors: [color("brand"), color("summarize"), color("filter")], colors: [color("brand"), color("summarize"), color("filter")],
}; };
...@@ -9,39 +9,23 @@ export type ColorSelectorAttributes = Omit< ...@@ -9,39 +9,23 @@ export type ColorSelectorAttributes = Omit<
>; >;
export interface ColorSelectorProps extends ColorSelectorAttributes { export interface ColorSelectorProps extends ColorSelectorAttributes {
color: string; value: string;
colors: string[]; colors: string[];
isBordered?: boolean;
isSelected?: boolean;
onChange?: (color: string) => void; onChange?: (color: string) => void;
} }
const ColorSelector = forwardRef(function ColorSelector( const ColorSelector = forwardRef(function ColorSelector(
{ { value, colors, onChange, ...props }: ColorSelectorProps,
color,
colors,
isBordered,
isSelected,
onChange,
...props
}: ColorSelectorProps,
ref: Ref<HTMLDivElement>, ref: Ref<HTMLDivElement>,
) { ) {
return ( return (
<TippyPopoverWithTrigger <TippyPopoverWithTrigger
renderTrigger={({ onClick }) => ( renderTrigger={({ onClick }) => (
<ColorPill <ColorPill {...props} ref={ref} color={value} onClick={onClick} />
{...props}
ref={ref}
color={color}
isBordered={isBordered}
isSelected={isSelected}
onClick={onClick}
/>
)} )}
popoverContent={ popoverContent={
<ColorSelectorContent <ColorSelectorContent
color={color} value={value}
colors={colors} colors={colors}
onChange={onChange} onChange={onChange}
/> />
......
...@@ -9,7 +9,7 @@ describe("ColorSelector", () => { ...@@ -9,7 +9,7 @@ describe("ColorSelector", () => {
render( render(
<ColorSelector <ColorSelector
color="white" value="white"
colors={["blue", "green"]} colors={["blue", "green"]}
onChange={onChange} onChange={onChange}
/>, />,
......
...@@ -4,13 +4,13 @@ import { ColorSelectorRoot } from "./ColorSelector.styled"; ...@@ -4,13 +4,13 @@ import { ColorSelectorRoot } from "./ColorSelector.styled";
export interface ColorSelectorContentProps export interface ColorSelectorContentProps
extends Omit<HTMLAttributes<HTMLDivElement>, "onChange"> { extends Omit<HTMLAttributes<HTMLDivElement>, "onChange"> {
color?: string; value?: string;
colors: string[]; colors: string[];
onChange?: (color: string) => void; onChange?: (value: string) => void;
} }
const ColorSelectorContent = forwardRef(function ColorSelector( const ColorSelectorContent = forwardRef(function ColorSelector(
{ color, colors, onChange, ...props }: ColorSelectorContentProps, { value, colors, onChange, ...props }: ColorSelectorContentProps,
ref: Ref<HTMLDivElement>, ref: Ref<HTMLDivElement>,
) { ) {
return ( return (
...@@ -19,8 +19,7 @@ const ColorSelectorContent = forwardRef(function ColorSelector( ...@@ -19,8 +19,7 @@ const ColorSelectorContent = forwardRef(function ColorSelector(
<ColorPill <ColorPill
key={index} key={index}
color={option} color={option}
isBordered isSelected={value === option}
isSelected={color === option}
onClick={() => onChange?.(option)} onClick={() => onChange?.(option)}
/> />
))} ))}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment