From 873c74181f97440c4bb70250e04ac31a32cd5dd7 Mon Sep 17 00:00:00 2001 From: nina992 Date: Wed, 9 Aug 2023 17:56:20 +0300 Subject: [PATCH 01/12] feat(web): add color field component --- .../components/Fields/ColorField/hooks.ts | 170 +++++++++++ .../Fields/ColorField/index.stories.tsx | 16 + .../components/Fields/ColorField/index.tsx | 280 ++++++++++++++++++ .../components/Fields/ColorField/styles.css | 37 +++ .../TextInput/index.tsx | 0 .../{properties => Fields}/Toggle/index.tsx | 0 .../{properties => Fields}/index.tsx | 0 .../tabs/publish/Nav/PublishModal/index.tsx | 2 +- .../Editor/tabs/widgets/Nav/index.tsx | 2 +- .../tabs/widgets/SidePanel/Settings/index.tsx | 2 +- 10 files changed, 506 insertions(+), 3 deletions(-) create mode 100644 web/src/beta/components/Fields/ColorField/hooks.ts create mode 100644 web/src/beta/components/Fields/ColorField/index.stories.tsx create mode 100644 web/src/beta/components/Fields/ColorField/index.tsx create mode 100644 web/src/beta/components/Fields/ColorField/styles.css rename web/src/beta/components/{properties => Fields}/TextInput/index.tsx (100%) rename web/src/beta/components/{properties => Fields}/Toggle/index.tsx (100%) rename web/src/beta/components/{properties => Fields}/index.tsx (100%) diff --git a/web/src/beta/components/Fields/ColorField/hooks.ts b/web/src/beta/components/Fields/ColorField/hooks.ts new file mode 100644 index 0000000000..02ba3fbe80 --- /dev/null +++ b/web/src/beta/components/Fields/ColorField/hooks.ts @@ -0,0 +1,170 @@ +import { useState, useEffect, useCallback, useRef } from "react"; +import tinycolor, { ColorInput } from "tinycolor2"; + +export type RGBA = { + r: number; + g: number; + b: number; + a: number; +}; + +export type Params = { + value?: string; + onChange?: (value: string | undefined) => void | undefined; +}; + +export default ({ value, onChange }: Params) => { + const [colorState, setColor] = useState(); + const [rgba, setRgba] = useState(tinycolor(value).toRgb()); + const [open, setOpen] = useState(false); + const wrapperRef = useRef(null); + const pickerRef = useRef(null); + + const getHexString = (value?: ColorInput) => { + if (!value) return undefined; + const color = tinycolor(value); + return color.getAlpha() === 1 ? color.toHexString() : color.toHex8String(); + }; + + const handleChange = useCallback((newColor: RGBA) => { + const color = getHexString(newColor); + if (!color) return; + setColor(color); + setRgba(newColor); + }, []); + + const handleRgbaInput = useCallback( + (e: React.ChangeEvent) => { + e.preventDefault(); + + if (e.target.name === "a") { + setRgba({ + ...rgba, + [e.target.name]: Number(e.target.value) / 100, + }); + } else { + setRgba({ + ...rgba, + [e.target.name]: e.target.value ? Number(e.target.value) : undefined, + }); + } + }, + [rgba], + ); + + const handleHexInput = useCallback((e: React.ChangeEvent) => { + e.preventDefault(); + setColor(e.target.value); + }, []); + + const handleClose = useCallback(() => { + if (value) { + setColor(value); + setRgba(tinycolor(value).toRgb()); + } else { + setColor(undefined); + setRgba(tinycolor(colorState == null ? undefined : colorState).toRgb()); + } + setOpen(false); + }, [value, colorState]); + + const handleSave = useCallback(() => { + if (!onChange) return; + if (colorState != value) { + onChange(colorState); + } + setOpen(false); + }, [colorState, onChange, value]); + + const handleHexSave = useCallback(() => { + const hexPattern = /^#?([a-fA-F0-9]{3,4}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/; + if (colorState && hexPattern.test(colorState)) { + handleSave(); + } else { + value && setColor(value); + } + }, [colorState, handleSave, value]); + + const handleClick = useCallback(() => setOpen(!open), [open]); + + const handleKeyPress = useCallback( + (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + handleHexSave(); + } + }, + [handleHexSave], + ); + + useEffect(() => { + if (value) { + setColor(value); + setRgba(tinycolor(value).toRgb()); + } else { + setColor(undefined); + } + }, [value]); + + useEffect(() => { + if (!value) return; + if (rgba && tinycolor(rgba).toHex8String() !== value) { + setColor(tinycolor(rgba).toHex8String()); + } + }, [rgba]); // eslint-disable-line react-hooks/exhaustive-deps + + useEffect(() => { + const handleClickOutside = (e: MouseEvent | TouchEvent) => { + if (open && wrapperRef.current && !wrapperRef.current.contains(e.target as Node)) { + if (colorState != value && !open) { + handleSave(); + } + handleClose(); + setOpen(false); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + document.addEventListener("touchstart", handleClickOutside); + + return () => { + document.removeEventListener("mousedown", handleClickOutside); + document.removeEventListener("touchstart", handleClickOutside); + }; + }, [handleClose]); // eslint-disable-line react-hooks/exhaustive-deps + + const getChannelLabel = (channel: string) => { + switch (channel) { + case "r": + return "Red"; + case "g": + return "Green"; + case "b": + return "Blue"; + case "a": + return "Alpha"; + default: + return ""; + } + }; + + function getChannelValue(rgba: RGBA, channel: keyof RGBA): number { + return rgba[channel]; + } + return { + wrapperRef, + pickerRef, + colorState, + open, + rgba, + getChannelLabel, + getChannelValue, + handleClose, + handleSave, + handleHexSave, + handleChange, + handleRgbaInput, + handleHexInput, + handleClick, + handleKeyPress, + }; +}; diff --git a/web/src/beta/components/Fields/ColorField/index.stories.tsx b/web/src/beta/components/Fields/ColorField/index.stories.tsx new file mode 100644 index 0000000000..8ff395bf12 --- /dev/null +++ b/web/src/beta/components/Fields/ColorField/index.stories.tsx @@ -0,0 +1,16 @@ +import { action } from "@storybook/addon-actions"; +import { Meta, StoryObj } from "@storybook/react"; + +import ColorField from "."; + +const meta: Meta = { + component: ColorField, +}; + +export default meta; + +type Story = StoryObj; + +export const ColorFieldInput: Story = { + render: () => , +}; diff --git a/web/src/beta/components/Fields/ColorField/index.tsx b/web/src/beta/components/Fields/ColorField/index.tsx new file mode 100644 index 0000000000..49e67239f3 --- /dev/null +++ b/web/src/beta/components/Fields/ColorField/index.tsx @@ -0,0 +1,280 @@ +import React from "react"; +import { RgbaColorPicker } from "react-colorful"; +import { usePopper } from "react-popper"; + +import Button from "@reearth/beta/components/Button"; +import Icon from "@reearth/beta/components/Icon"; +import Text from "@reearth/beta/components/Text"; +import { useT } from "@reearth/services/i18n"; +import { styled, css, useTheme } from "@reearth/services/theme"; + +import Property from ".."; + +import useHooks, { RGBA } from "./hooks"; +import "./styles.css"; + +// Constants +const CHANNELS = ["r", "g", "b", "a"]; +const HEX_PLACEHOLDER = "#RRGGBBAA"; + +// Component Props +export type Props = { + name?: string; + description?: string; + value?: string; + onChange?: (value: string | undefined) => void | undefined; +}; + +// Component +const ColorField: React.FC = ({ name, description, value, onChange }) => { + const t = useT(); + const theme = useTheme(); + const { + wrapperRef, + pickerRef, + colorState, + open, + rgba, + getChannelValue, + handleClose, + handleSave, + handleHexSave, + handleChange, + handleRgbaInput, + handleHexInput, + handleClick, + handleKeyPress, + } = useHooks({ value, onChange }); + + const { styles, attributes } = usePopper(wrapperRef.current, pickerRef.current, { + placement: "bottom-start", + modifiers: [ + { + name: "offset", + options: { + offset: [0, 8], + }, + }, + { + name: "eventListeners", + enabled: !open, + options: { + scroll: false, + resize: false, + }, + }, + ], + }); + + return ( + + + + + + + + + + + + + Color Picker + + {handleClose && } + + + + + RGBA + + {CHANNELS.map(channel => ( + + ))} + + + + + + + + + + + ); +}; + +// Styled Components +const Wrapper = styled.div` + text-align: center; + width: 100%; + cursor: pointer; +`; + +const InputWrapper = styled.div` + display: flex; + gap: 4px; + background: ${({ theme }) => theme.bg[1]}; +`; + +const Layers = styled.div` + position: relative; + min-width: 28px; + min-height: 28px; + border-radius: 4px; + border: 1px solid ${({ theme }) => theme.outline.weak}; + box-shadow: 0px 2px 2px 0px rgba(0, 0, 0, 0.25) inset; +`; + +const layerStyle = css` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +`; + +const check = (color: string) => ` +linear-gradient( + 45deg, + ${color} 25%, + transparent 25%, + transparent 75%, + ${color} 25%, + ${color} +) +`; + +const CheckedPattern = styled.div` + background-color: ${({ theme }) => theme.outline.main}; + background-image: ${({ theme }) => check(theme.bg[3])}, ${({ theme }) => check(theme.bg[3])}; + background-position: 0 0, 6px 6px; + background-size: 12px 12px; + ${layerStyle}; +`; + +const Swatch = styled.div<{ c?: string }>` + background: ${({ c }) => c || "transparent"}; + ${layerStyle}; +`; + +const PickerWrapper = styled.div<{ open: boolean }>` + ${({ open }) => + !open && + css` + visibility: hidden; + pointer-events: none; + `} + width: 286px; + height: 362px; + border: 1px solid ${({ theme }) => theme.outline.weak}; + border-radius: 4px; + background: ${({ theme }) => theme.bg[1]}; + box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5); +`; + +const HeaderWrapper = styled.div` + display: flex; + padding: 4px 8px; + justify-content: space-between; + align-items: center; + gap: 10px; + height: 28px; + border-bottom: 1px solid ${({ theme }) => theme.outline.weak}; +`; + +const PickerTitle = styled(Text)` + text-align: center; + margin-right: auto; +`; + +const CloseIcon = styled(Icon)` + margin-left: auto; + cursor: pointer; +`; + +const SelectorPickerWrapper = styled.div` + display: flex; + padding: 8px; + flex-direction: column; + align-items: flex-start; +`; + +const ValuesWrapper = styled.div` + display: flex; + flex-direction: row; + align-items: flex-start; + gap: 4px; + padding-top: 8px; + padding-bottom: 8px; +`; + +const Input = styled.input<{ type?: string }>` + display: flex; + padding: 4px 8px; + align-items: center; + margin: 0 auto; + border-radius: 4px; + gap: 4px; + border: 1px solid ${({ theme }) => theme.outline.weak}; + color: ${({ theme }) => theme.content.main}; + background: ${({ theme }) => theme.bg[1]}; + box-shadow: 0px 2px 2px 0px rgba(0, 0, 0, 0.25) inset; + box-sizing: border-box; + outline: none; + &:focus { + border-color: ${({ theme }) => theme.outline.main}; + } + width: 100%; + height: 30px; +`; + +const RgbaInputWrapper = styled.div` + display: flex; + height: 56px; + flex-direction: column; + align-items: flex-start; + justify-content: flex-start; + gap: 8px; +`; +const FormButtonGroup = styled.div` + display: flex; + flex-direction: row; + height: 28px; + justify-content: center; + border-top: 1px solid ${({ theme }) => theme.bg[3]}; + padding: 8px; + gap: 8px; +`; +const ButtonWrapper = styled(Button)` + height: 27px; + min-width: 135px; + padding: 0px; + margin: 0px; +`; + +export default ColorField; diff --git a/web/src/beta/components/Fields/ColorField/styles.css b/web/src/beta/components/Fields/ColorField/styles.css new file mode 100644 index 0000000000..51dd677b97 --- /dev/null +++ b/web/src/beta/components/Fields/ColorField/styles.css @@ -0,0 +1,37 @@ +.colorPicker { + padding: 8px 8px 6px 0px; + gap:12px; + } + +.colorPicker .react-colorful__saturation-pointer { + + width: 15px; + height: 15px; + border-width: 2px; +} + +.colorPicker .react-colorful__hue-pointer, +.colorPicker .react-colorful__alpha-pointer { + width: 1px; + height: 10px; + border: 1px solid white; + border-radius: 2px; + +} + +.colorPicker .react-colorful__saturation { + margin-bottom: 10px; + border-radius: 3px; + + width: 270px; + border-bottom: none; + } + +.colorPicker .react-colorful__hue, +.colorPicker .react-colorful__alpha { + height: 10px; + width: 270px; + margin: 0 5px 10px 3px; + border-radius: 3px; + +} \ No newline at end of file diff --git a/web/src/beta/components/properties/TextInput/index.tsx b/web/src/beta/components/Fields/TextInput/index.tsx similarity index 100% rename from web/src/beta/components/properties/TextInput/index.tsx rename to web/src/beta/components/Fields/TextInput/index.tsx diff --git a/web/src/beta/components/properties/Toggle/index.tsx b/web/src/beta/components/Fields/Toggle/index.tsx similarity index 100% rename from web/src/beta/components/properties/Toggle/index.tsx rename to web/src/beta/components/Fields/Toggle/index.tsx diff --git a/web/src/beta/components/properties/index.tsx b/web/src/beta/components/Fields/index.tsx similarity index 100% rename from web/src/beta/components/properties/index.tsx rename to web/src/beta/components/Fields/index.tsx diff --git a/web/src/beta/features/Editor/tabs/publish/Nav/PublishModal/index.tsx b/web/src/beta/features/Editor/tabs/publish/Nav/PublishModal/index.tsx index b792755278..96d7f4619d 100644 --- a/web/src/beta/features/Editor/tabs/publish/Nav/PublishModal/index.tsx +++ b/web/src/beta/features/Editor/tabs/publish/Nav/PublishModal/index.tsx @@ -1,9 +1,9 @@ import { useMemo } from "react"; import Button from "@reearth/beta/components/Button"; +import ToggleButton from "@reearth/beta/components/Fields/Toggle"; import Icon from "@reearth/beta/components/Icon"; import Modal from "@reearth/beta/components/Modal"; -import ToggleButton from "@reearth/beta/components/properties/Toggle"; import Text from "@reearth/beta/components/Text"; import { useT } from "@reearth/services/i18n"; import { styled, metricsSizes, useTheme } from "@reearth/services/theme"; diff --git a/web/src/beta/features/Editor/tabs/widgets/Nav/index.tsx b/web/src/beta/features/Editor/tabs/widgets/Nav/index.tsx index 07571c2a64..08ad521897 100644 --- a/web/src/beta/features/Editor/tabs/widgets/Nav/index.tsx +++ b/web/src/beta/features/Editor/tabs/widgets/Nav/index.tsx @@ -1,4 +1,4 @@ -import Toggle from "@reearth/beta/components/properties/Toggle"; +import Toggle from "@reearth/beta/components/Fields/Toggle"; import SecondaryNav from "@reearth/beta/features/Editor/SecondaryNav"; import { useT } from "@reearth/services/i18n"; import { styled } from "@reearth/services/theme"; diff --git a/web/src/beta/features/Editor/tabs/widgets/SidePanel/Settings/index.tsx b/web/src/beta/features/Editor/tabs/widgets/SidePanel/Settings/index.tsx index 9e25554018..c1328e8e09 100644 --- a/web/src/beta/features/Editor/tabs/widgets/SidePanel/Settings/index.tsx +++ b/web/src/beta/features/Editor/tabs/widgets/SidePanel/Settings/index.tsx @@ -1,4 +1,4 @@ -import TextInput from "@reearth/beta/components/properties/TextInput"; +import TextInput from "@reearth/beta/components/Fields/TextInput"; import SidePanelSectionField from "@reearth/beta/components/SidePanelSectionField"; import { type Item } from "@reearth/services/api/propertyApi/utils"; import { styled } from "@reearth/services/theme"; From 72ece619c6a6007d1f5a466a49e029ce0fd42094 Mon Sep 17 00:00:00 2001 From: nina992 Date: Fri, 18 Aug 2023 21:32:28 +0300 Subject: [PATCH 02/12] ux-changes --- .../components/Fields/ColorField/hooks.ts | 34 ++++++++----------- .../components/Fields/ColorField/index.tsx | 8 ++--- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/web/src/beta/components/Fields/ColorField/hooks.ts b/web/src/beta/components/Fields/ColorField/hooks.ts index 02ba3fbe80..f47983b0f5 100644 --- a/web/src/beta/components/Fields/ColorField/hooks.ts +++ b/web/src/beta/components/Fields/ColorField/hooks.ts @@ -36,31 +36,27 @@ export default ({ value, onChange }: Params) => { const handleRgbaInput = useCallback( (e: React.ChangeEvent) => { e.preventDefault(); - - if (e.target.name === "a") { - setRgba({ - ...rgba, - [e.target.name]: Number(e.target.value) / 100, - }); - } else { - setRgba({ - ...rgba, - [e.target.name]: e.target.value ? Number(e.target.value) : undefined, - }); - } + setRgba({ + ...rgba, + [e.target.name]: e.target.value ? Number(e.target.value) : undefined, + }); }, [rgba], ); - const handleHexInput = useCallback((e: React.ChangeEvent) => { - e.preventDefault(); - setColor(e.target.value); - }, []); + const handleHexInput = useCallback( + (e: React.ChangeEvent) => { + e.preventDefault(); + setColor(e.target.value); + setRgba(tinycolor(e.target.value ?? colorState).toRgb()); + }, + [colorState], + ); const handleClose = useCallback(() => { - if (value) { - setColor(value); - setRgba(tinycolor(value).toRgb()); + if (value || colorState) { + setColor(value ?? colorState); + setRgba(tinycolor(value ?? colorState).toRgb()); } else { setColor(undefined); setRgba(tinycolor(colorState == null ? undefined : colorState).toRgb()); diff --git a/web/src/beta/components/Fields/ColorField/index.tsx b/web/src/beta/components/Fields/ColorField/index.tsx index 49e67239f3..418fea13db 100644 --- a/web/src/beta/components/Fields/ColorField/index.tsx +++ b/web/src/beta/components/Fields/ColorField/index.tsx @@ -14,8 +14,8 @@ import useHooks, { RGBA } from "./hooks"; import "./styles.css"; // Constants -const CHANNELS = ["r", "g", "b", "a"]; -const HEX_PLACEHOLDER = "#RRGGBBAA"; +const channels = ["r", "g", "b", "a"]; +const hexPlaceholder = "#RRGGBBAA"; // Component Props export type Props = { @@ -76,7 +76,7 @@ const ColorField: React.FC = ({ name, description, value, onChange }) => = ({ name, description, value, onChange }) => RGBA - {CHANNELS.map(channel => ( + {channels.map(channel => ( Date: Fri, 18 Aug 2023 21:37:25 +0300 Subject: [PATCH 03/12] translation --- web/src/services/i18n/translations/en.yml | 1 + web/src/services/i18n/translations/ja.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/web/src/services/i18n/translations/en.yml b/web/src/services/i18n/translations/en.yml index 9b31f18ae7..1b6e84f100 100644 --- a/web/src/services/i18n/translations/en.yml +++ b/web/src/services/i18n/translations/en.yml @@ -41,6 +41,7 @@ Wide: Wide Create New Workspace: Create New Workspace Create: Create Cancel: Cancel +Apply: Apply Workspace Name: Workspace Name Account Settings: Account Settings Workspaces: Workspaces diff --git a/web/src/services/i18n/translations/ja.yml b/web/src/services/i18n/translations/ja.yml index c696c14344..8e7db6aebe 100644 --- a/web/src/services/i18n/translations/ja.yml +++ b/web/src/services/i18n/translations/ja.yml @@ -37,6 +37,7 @@ Wide: 広い Create New Workspace: 新規ワークスペース作成 Create: 作成 Cancel: キャンセル +Apply: Apply Workspace Name: ワークスペース名 Account Settings: アカウント設定 Workspaces: ワークスペース From 34c01e2e214d6588f354f5ed9366f080f26cd695 Mon Sep 17 00:00:00 2001 From: nina992 Date: Fri, 18 Aug 2023 21:57:06 +0300 Subject: [PATCH 04/12] add-to-widget --- web/src/beta/components/Fields/ColorField/hooks.ts | 4 ++-- .../beta/components/Fields/ColorField/index.tsx | 2 +- .../tabs/widgets/SidePanel/Settings/index.tsx | 14 +++++++++++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/web/src/beta/components/Fields/ColorField/hooks.ts b/web/src/beta/components/Fields/ColorField/hooks.ts index f47983b0f5..d1d22ab4be 100644 --- a/web/src/beta/components/Fields/ColorField/hooks.ts +++ b/web/src/beta/components/Fields/ColorField/hooks.ts @@ -10,7 +10,7 @@ export type RGBA = { export type Params = { value?: string; - onChange?: (value: string | undefined) => void | undefined; + onChange?: (value: string) => void; }; export default ({ value, onChange }: Params) => { @@ -66,7 +66,7 @@ export default ({ value, onChange }: Params) => { const handleSave = useCallback(() => { if (!onChange) return; - if (colorState != value) { + if (colorState != value && colorState) { onChange(colorState); } setOpen(false); diff --git a/web/src/beta/components/Fields/ColorField/index.tsx b/web/src/beta/components/Fields/ColorField/index.tsx index 418fea13db..095017a8a2 100644 --- a/web/src/beta/components/Fields/ColorField/index.tsx +++ b/web/src/beta/components/Fields/ColorField/index.tsx @@ -22,7 +22,7 @@ export type Props = { name?: string; description?: string; value?: string; - onChange?: (value: string | undefined) => void | undefined; + onChange?: (value: string) => void; }; // Component diff --git a/web/src/beta/features/Editor/tabs/widgets/SidePanel/Settings/index.tsx b/web/src/beta/features/Editor/tabs/widgets/SidePanel/Settings/index.tsx index c1328e8e09..9b72d927e1 100644 --- a/web/src/beta/features/Editor/tabs/widgets/SidePanel/Settings/index.tsx +++ b/web/src/beta/features/Editor/tabs/widgets/SidePanel/Settings/index.tsx @@ -1,3 +1,4 @@ +import ColorField from "@reearth/beta/components/Fields/ColorField"; import TextInput from "@reearth/beta/components/Fields/TextInput"; import SidePanelSectionField from "@reearth/beta/components/SidePanelSectionField"; import { type Item } from "@reearth/services/api/propertyApi/utils"; @@ -23,7 +24,18 @@ const Settings: React.FC = ({ widgetPropertyId, propertyItems }) => { const value = !isList ? i.fields.find(f => f.id === sf.id)?.value : sf.defaultValue; return sf.type === "string" ? ( sf.ui === "color" ? ( -

Color field

+ ) : sf.ui === "selection" || sf.choices ? (

Selection or choices field

) : sf.ui === "buttons" ? ( From 436622326b4ee0ca03555cf9b9ae47f14fde6a11 Mon Sep 17 00:00:00 2001 From: nina992 Date: Mon, 21 Aug 2023 09:53:59 +0300 Subject: [PATCH 05/12] fields --- .../Editor/tabs/widgets/SidePanel/ContainerSettings/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/beta/features/Editor/tabs/widgets/SidePanel/ContainerSettings/index.tsx b/web/src/beta/features/Editor/tabs/widgets/SidePanel/ContainerSettings/index.tsx index 662078420e..03055ccbd9 100644 --- a/web/src/beta/features/Editor/tabs/widgets/SidePanel/ContainerSettings/index.tsx +++ b/web/src/beta/features/Editor/tabs/widgets/SidePanel/ContainerSettings/index.tsx @@ -1,4 +1,4 @@ -import TextInput from "@reearth/beta/components/properties/TextInput"; +import TextInput from "@reearth/beta/components/Fields/TextInput"; import SidePanelSectionField from "@reearth/beta/components/SidePanelSectionField"; import { WidgetAreaPadding, WidgetAreaState } from "@reearth/services/state"; From 7b6ed3284ccc7db255c079851b19d203bb04794d Mon Sep 17 00:00:00 2001 From: nina992 Date: Mon, 21 Aug 2023 16:45:07 +0300 Subject: [PATCH 06/12] fix-ux --- .../components/Fields/ColorField/hooks.ts | 78 +++++++++++-------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/web/src/beta/components/Fields/ColorField/hooks.ts b/web/src/beta/components/Fields/ColorField/hooks.ts index d1d22ab4be..3404e78c58 100644 --- a/web/src/beta/components/Fields/ColorField/hooks.ts +++ b/web/src/beta/components/Fields/ColorField/hooks.ts @@ -16,41 +16,66 @@ export type Params = { export default ({ value, onChange }: Params) => { const [colorState, setColor] = useState(); const [rgba, setRgba] = useState(tinycolor(value).toRgb()); + const [tempColor, setTempColor] = useState(colorState); const [open, setOpen] = useState(false); const wrapperRef = useRef(null); const pickerRef = useRef(null); + //Helper functions + const getHexString = (value?: ColorInput) => { if (!value) return undefined; const color = tinycolor(value); return color.getAlpha() === 1 ? color.toHexString() : color.toHex8String(); }; + const getChannelLabel = (channel: string) => { + switch (channel) { + case "r": + return "Red"; + case "g": + return "Green"; + case "b": + return "Blue"; + case "a": + return "Alpha"; + default: + return ""; + } + }; + + function getChannelValue(rgba: RGBA, channel: keyof RGBA): number { + return rgba[channel]; + } + + //Actions + const handleChange = useCallback((newColor: RGBA) => { const color = getHexString(newColor); if (!color) return; - setColor(color); + setTempColor(color); setRgba(newColor); }, []); - const handleRgbaInput = useCallback( + const handleHexInput = useCallback( (e: React.ChangeEvent) => { e.preventDefault(); - setRgba({ - ...rgba, - [e.target.name]: e.target.value ? Number(e.target.value) : undefined, - }); + setColor(e.target.value); + setRgba(tinycolor(e.target.value ?? colorState).toRgb()); }, - [rgba], + [colorState], ); - const handleHexInput = useCallback( + const handleRgbaInput = useCallback( (e: React.ChangeEvent) => { e.preventDefault(); - setColor(e.target.value); - setRgba(tinycolor(e.target.value ?? colorState).toRgb()); + + handleChange({ + ...rgba, + [e.target.name]: e.target.value ? Number(e.target.value) : undefined, + }); }, - [colorState], + [handleChange, rgba], ); const handleClose = useCallback(() => { @@ -66,11 +91,15 @@ export default ({ value, onChange }: Params) => { const handleSave = useCallback(() => { if (!onChange) return; - if (colorState != value && colorState) { + if (tempColor && tempColor != value && tempColor != colorState) { + setColor(tempColor); + setRgba(tinycolor(tempColor).toRgb()); + onChange(tempColor); + } else if (colorState != value && colorState) { onChange(colorState); } setOpen(false); - }, [colorState, onChange, value]); + }, [colorState, onChange, tempColor, value]); const handleHexSave = useCallback(() => { const hexPattern = /^#?([a-fA-F0-9]{3,4}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/; @@ -81,6 +110,8 @@ export default ({ value, onChange }: Params) => { } }, [colorState, handleSave, value]); + //events + const handleClick = useCallback(() => setOpen(!open), [open]); const handleKeyPress = useCallback( @@ -92,6 +123,8 @@ export default ({ value, onChange }: Params) => { [handleHexSave], ); + //UseEffects + useEffect(() => { if (value) { setColor(value); @@ -128,28 +161,11 @@ export default ({ value, onChange }: Params) => { }; }, [handleClose]); // eslint-disable-line react-hooks/exhaustive-deps - const getChannelLabel = (channel: string) => { - switch (channel) { - case "r": - return "Red"; - case "g": - return "Green"; - case "b": - return "Blue"; - case "a": - return "Alpha"; - default: - return ""; - } - }; - - function getChannelValue(rgba: RGBA, channel: keyof RGBA): number { - return rgba[channel]; - } return { wrapperRef, pickerRef, colorState, + tempColor, open, rgba, getChannelLabel, From 7d3761c9f1a5c240e3e80c7a02fef911a36dca06 Mon Sep 17 00:00:00 2001 From: nina992 Date: Mon, 21 Aug 2023 16:46:46 +0300 Subject: [PATCH 07/12] fix-ux2 --- web/src/beta/components/Fields/ColorField/hooks.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/web/src/beta/components/Fields/ColorField/hooks.ts b/web/src/beta/components/Fields/ColorField/hooks.ts index 3404e78c58..0e3287a938 100644 --- a/web/src/beta/components/Fields/ColorField/hooks.ts +++ b/web/src/beta/components/Fields/ColorField/hooks.ts @@ -165,7 +165,6 @@ export default ({ value, onChange }: Params) => { wrapperRef, pickerRef, colorState, - tempColor, open, rgba, getChannelLabel, From 1e3d61c9ddccd2fb7f0e29ccb9a18fe4b1177389 Mon Sep 17 00:00:00 2001 From: nina992 Date: Tue, 22 Aug 2023 09:38:49 +0300 Subject: [PATCH 08/12] fix-fields --- .../features/Editor/tabs/publish/Nav/PublishModal/index.tsx | 2 +- web/src/beta/features/Editor/tabs/widgets/Nav/index.tsx | 2 +- .../Editor/tabs/widgets/SidePanel/ContainerSettings/index.tsx | 2 +- .../features/Editor/tabs/widgets/SidePanel/Settings/index.tsx | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/web/src/beta/features/Editor/tabs/publish/Nav/PublishModal/index.tsx b/web/src/beta/features/Editor/tabs/publish/Nav/PublishModal/index.tsx index 96d7f4619d..b4616842d1 100644 --- a/web/src/beta/features/Editor/tabs/publish/Nav/PublishModal/index.tsx +++ b/web/src/beta/features/Editor/tabs/publish/Nav/PublishModal/index.tsx @@ -1,7 +1,7 @@ import { useMemo } from "react"; import Button from "@reearth/beta/components/Button"; -import ToggleButton from "@reearth/beta/components/Fields/Toggle"; +import ToggleButton from "@reearth/beta/components/fields/Toggle"; import Icon from "@reearth/beta/components/Icon"; import Modal from "@reearth/beta/components/Modal"; import Text from "@reearth/beta/components/Text"; diff --git a/web/src/beta/features/Editor/tabs/widgets/Nav/index.tsx b/web/src/beta/features/Editor/tabs/widgets/Nav/index.tsx index fdc59a061f..5b0a8161f6 100644 --- a/web/src/beta/features/Editor/tabs/widgets/Nav/index.tsx +++ b/web/src/beta/features/Editor/tabs/widgets/Nav/index.tsx @@ -1,6 +1,6 @@ import { useEffect } from "react"; -import Toggle from "@reearth/beta/components/Fields/Toggle"; +import Toggle from "@reearth/beta/components/fields/Toggle"; import SecondaryNav from "@reearth/beta/features/Editor/SecondaryNav"; import { useT } from "@reearth/services/i18n"; import { selectedWidgetAreaVar } from "@reearth/services/state"; diff --git a/web/src/beta/features/Editor/tabs/widgets/SidePanel/ContainerSettings/index.tsx b/web/src/beta/features/Editor/tabs/widgets/SidePanel/ContainerSettings/index.tsx index 03055ccbd9..7b33503715 100644 --- a/web/src/beta/features/Editor/tabs/widgets/SidePanel/ContainerSettings/index.tsx +++ b/web/src/beta/features/Editor/tabs/widgets/SidePanel/ContainerSettings/index.tsx @@ -1,4 +1,4 @@ -import TextInput from "@reearth/beta/components/Fields/TextInput"; +import TextInput from "@reearth/beta/components/fields/TextInput"; import SidePanelSectionField from "@reearth/beta/components/SidePanelSectionField"; import { WidgetAreaPadding, WidgetAreaState } from "@reearth/services/state"; diff --git a/web/src/beta/features/Editor/tabs/widgets/SidePanel/Settings/index.tsx b/web/src/beta/features/Editor/tabs/widgets/SidePanel/Settings/index.tsx index 9b72d927e1..1e74e8f586 100644 --- a/web/src/beta/features/Editor/tabs/widgets/SidePanel/Settings/index.tsx +++ b/web/src/beta/features/Editor/tabs/widgets/SidePanel/Settings/index.tsx @@ -1,5 +1,5 @@ -import ColorField from "@reearth/beta/components/Fields/ColorField"; -import TextInput from "@reearth/beta/components/Fields/TextInput"; +import ColorField from "@reearth/beta/components/fields/ColorField"; +import TextInput from "@reearth/beta/components/fields/TextInput"; import SidePanelSectionField from "@reearth/beta/components/SidePanelSectionField"; import { type Item } from "@reearth/services/api/propertyApi/utils"; import { styled } from "@reearth/services/theme"; From 8f19fa776aff266a9d472bfea6db207b4e308984 Mon Sep 17 00:00:00 2001 From: nina992 Date: Tue, 22 Aug 2023 17:50:27 +0300 Subject: [PATCH 09/12] use-popover --- .../components/Fields/ColorField/index.tsx | 142 ++++++++---------- 1 file changed, 60 insertions(+), 82 deletions(-) diff --git a/web/src/beta/components/Fields/ColorField/index.tsx b/web/src/beta/components/Fields/ColorField/index.tsx index 095017a8a2..2f58924e36 100644 --- a/web/src/beta/components/Fields/ColorField/index.tsx +++ b/web/src/beta/components/Fields/ColorField/index.tsx @@ -1,9 +1,9 @@ import React from "react"; import { RgbaColorPicker } from "react-colorful"; -import { usePopper } from "react-popper"; import Button from "@reearth/beta/components/Button"; import Icon from "@reearth/beta/components/Icon"; +import * as Popover from "@reearth/beta/components/Popover"; import Text from "@reearth/beta/components/Text"; import { useT } from "@reearth/services/i18n"; import { styled, css, useTheme } from "@reearth/services/theme"; @@ -46,83 +46,67 @@ const ColorField: React.FC = ({ name, description, value, onChange }) => handleKeyPress, } = useHooks({ value, onChange }); - const { styles, attributes } = usePopper(wrapperRef.current, pickerRef.current, { - placement: "bottom-start", - modifiers: [ - { - name: "offset", - options: { - offset: [0, 8], - }, - }, - { - name: "eventListeners", - enabled: !open, - options: { - scroll: false, - resize: false, - }, - }, - ], - }); - return ( - - - - - - - - - - - Color Picker - - {handleClose && } - - - - - RGBA - - {channels.map(channel => ( - - ))} - - - - - - - - + + + + + + + + + + + + + + Color Picker + + {handleClose && } + + + + + RGBA + + {channels.map(channel => ( + + ))} + + + + + + + + + ); @@ -182,13 +166,7 @@ const Swatch = styled.div<{ c?: string }>` ${layerStyle}; `; -const PickerWrapper = styled.div<{ open: boolean }>` - ${({ open }) => - !open && - css` - visibility: hidden; - pointer-events: none; - `} +const PickerWrapper = styled(Popover.Content)` width: 286px; height: 362px; border: 1px solid ${({ theme }) => theme.outline.weak}; From 846ef703a090fe0d5e0c204096ec9b0c80fd5edd Mon Sep 17 00:00:00 2001 From: nina992 Date: Tue, 22 Aug 2023 18:28:32 +0300 Subject: [PATCH 10/12] renaming --- .../beta/components/{Fields => fieldsfiles}/ColorField/hooks.ts | 0 .../{Fields => fieldsfiles}/ColorField/index.stories.tsx | 0 .../beta/components/{Fields => fieldsfiles}/ColorField/index.tsx | 0 .../beta/components/{Fields => fieldsfiles}/ColorField/styles.css | 0 .../beta/components/{Fields => fieldsfiles}/TextInput/index.tsx | 0 web/src/beta/components/{Fields => fieldsfiles}/Toggle/index.tsx | 0 web/src/beta/components/{Fields => fieldsfiles}/index.tsx | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename web/src/beta/components/{Fields => fieldsfiles}/ColorField/hooks.ts (100%) rename web/src/beta/components/{Fields => fieldsfiles}/ColorField/index.stories.tsx (100%) rename web/src/beta/components/{Fields => fieldsfiles}/ColorField/index.tsx (100%) rename web/src/beta/components/{Fields => fieldsfiles}/ColorField/styles.css (100%) rename web/src/beta/components/{Fields => fieldsfiles}/TextInput/index.tsx (100%) rename web/src/beta/components/{Fields => fieldsfiles}/Toggle/index.tsx (100%) rename web/src/beta/components/{Fields => fieldsfiles}/index.tsx (100%) diff --git a/web/src/beta/components/Fields/ColorField/hooks.ts b/web/src/beta/components/fieldsfiles/ColorField/hooks.ts similarity index 100% rename from web/src/beta/components/Fields/ColorField/hooks.ts rename to web/src/beta/components/fieldsfiles/ColorField/hooks.ts diff --git a/web/src/beta/components/Fields/ColorField/index.stories.tsx b/web/src/beta/components/fieldsfiles/ColorField/index.stories.tsx similarity index 100% rename from web/src/beta/components/Fields/ColorField/index.stories.tsx rename to web/src/beta/components/fieldsfiles/ColorField/index.stories.tsx diff --git a/web/src/beta/components/Fields/ColorField/index.tsx b/web/src/beta/components/fieldsfiles/ColorField/index.tsx similarity index 100% rename from web/src/beta/components/Fields/ColorField/index.tsx rename to web/src/beta/components/fieldsfiles/ColorField/index.tsx diff --git a/web/src/beta/components/Fields/ColorField/styles.css b/web/src/beta/components/fieldsfiles/ColorField/styles.css similarity index 100% rename from web/src/beta/components/Fields/ColorField/styles.css rename to web/src/beta/components/fieldsfiles/ColorField/styles.css diff --git a/web/src/beta/components/Fields/TextInput/index.tsx b/web/src/beta/components/fieldsfiles/TextInput/index.tsx similarity index 100% rename from web/src/beta/components/Fields/TextInput/index.tsx rename to web/src/beta/components/fieldsfiles/TextInput/index.tsx diff --git a/web/src/beta/components/Fields/Toggle/index.tsx b/web/src/beta/components/fieldsfiles/Toggle/index.tsx similarity index 100% rename from web/src/beta/components/Fields/Toggle/index.tsx rename to web/src/beta/components/fieldsfiles/Toggle/index.tsx diff --git a/web/src/beta/components/Fields/index.tsx b/web/src/beta/components/fieldsfiles/index.tsx similarity index 100% rename from web/src/beta/components/Fields/index.tsx rename to web/src/beta/components/fieldsfiles/index.tsx From 55c72937f1d1931bfe35ea204d4f9603a27d4f08 Mon Sep 17 00:00:00 2001 From: nina992 Date: Tue, 22 Aug 2023 18:31:05 +0300 Subject: [PATCH 11/12] renaming-fields --- .../beta/components/{fieldsfiles => fields}/ColorField/hooks.ts | 0 .../{fieldsfiles => fields}/ColorField/index.stories.tsx | 0 .../beta/components/{fieldsfiles => fields}/ColorField/index.tsx | 0 .../beta/components/{fieldsfiles => fields}/ColorField/styles.css | 0 .../beta/components/{fieldsfiles => fields}/TextInput/index.tsx | 0 web/src/beta/components/{fieldsfiles => fields}/Toggle/index.tsx | 0 web/src/beta/components/{fieldsfiles => fields}/index.tsx | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename web/src/beta/components/{fieldsfiles => fields}/ColorField/hooks.ts (100%) rename web/src/beta/components/{fieldsfiles => fields}/ColorField/index.stories.tsx (100%) rename web/src/beta/components/{fieldsfiles => fields}/ColorField/index.tsx (100%) rename web/src/beta/components/{fieldsfiles => fields}/ColorField/styles.css (100%) rename web/src/beta/components/{fieldsfiles => fields}/TextInput/index.tsx (100%) rename web/src/beta/components/{fieldsfiles => fields}/Toggle/index.tsx (100%) rename web/src/beta/components/{fieldsfiles => fields}/index.tsx (100%) diff --git a/web/src/beta/components/fieldsfiles/ColorField/hooks.ts b/web/src/beta/components/fields/ColorField/hooks.ts similarity index 100% rename from web/src/beta/components/fieldsfiles/ColorField/hooks.ts rename to web/src/beta/components/fields/ColorField/hooks.ts diff --git a/web/src/beta/components/fieldsfiles/ColorField/index.stories.tsx b/web/src/beta/components/fields/ColorField/index.stories.tsx similarity index 100% rename from web/src/beta/components/fieldsfiles/ColorField/index.stories.tsx rename to web/src/beta/components/fields/ColorField/index.stories.tsx diff --git a/web/src/beta/components/fieldsfiles/ColorField/index.tsx b/web/src/beta/components/fields/ColorField/index.tsx similarity index 100% rename from web/src/beta/components/fieldsfiles/ColorField/index.tsx rename to web/src/beta/components/fields/ColorField/index.tsx diff --git a/web/src/beta/components/fieldsfiles/ColorField/styles.css b/web/src/beta/components/fields/ColorField/styles.css similarity index 100% rename from web/src/beta/components/fieldsfiles/ColorField/styles.css rename to web/src/beta/components/fields/ColorField/styles.css diff --git a/web/src/beta/components/fieldsfiles/TextInput/index.tsx b/web/src/beta/components/fields/TextInput/index.tsx similarity index 100% rename from web/src/beta/components/fieldsfiles/TextInput/index.tsx rename to web/src/beta/components/fields/TextInput/index.tsx diff --git a/web/src/beta/components/fieldsfiles/Toggle/index.tsx b/web/src/beta/components/fields/Toggle/index.tsx similarity index 100% rename from web/src/beta/components/fieldsfiles/Toggle/index.tsx rename to web/src/beta/components/fields/Toggle/index.tsx diff --git a/web/src/beta/components/fieldsfiles/index.tsx b/web/src/beta/components/fields/index.tsx similarity index 100% rename from web/src/beta/components/fieldsfiles/index.tsx rename to web/src/beta/components/fields/index.tsx From 3b5589b9469a679c4afd4c76acd3e082804ba662 Mon Sep 17 00:00:00 2001 From: nina992 Date: Wed, 23 Aug 2023 10:22:29 +0300 Subject: [PATCH 12/12] pr-fix --- .../components/fields/ColorField/hooks.ts | 44 ++------------- .../components/fields/ColorField/index.tsx | 56 ++++++++++++++----- .../components/fields/ColorField/styles.css | 37 ------------ .../components/fields/ColorField/types.ts | 19 +++++++ .../components/fields/ColorField/utils.ts | 28 ++++++++++ 5 files changed, 95 insertions(+), 89 deletions(-) delete mode 100644 web/src/beta/components/fields/ColorField/styles.css create mode 100644 web/src/beta/components/fields/ColorField/types.ts create mode 100644 web/src/beta/components/fields/ColorField/utils.ts diff --git a/web/src/beta/components/fields/ColorField/hooks.ts b/web/src/beta/components/fields/ColorField/hooks.ts index 0e3287a938..31ea98e0ef 100644 --- a/web/src/beta/components/fields/ColorField/hooks.ts +++ b/web/src/beta/components/fields/ColorField/hooks.ts @@ -1,17 +1,8 @@ import { useState, useEffect, useCallback, useRef } from "react"; -import tinycolor, { ColorInput } from "tinycolor2"; +import tinycolor from "tinycolor2"; -export type RGBA = { - r: number; - g: number; - b: number; - a: number; -}; - -export type Params = { - value?: string; - onChange?: (value: string) => void; -}; +import { Params, RGBA } from "./types"; +import { getChannelLabel, getChannelValue, getHexString } from "./utils"; export default ({ value, onChange }: Params) => { const [colorState, setColor] = useState(); @@ -21,33 +12,6 @@ export default ({ value, onChange }: Params) => { const wrapperRef = useRef(null); const pickerRef = useRef(null); - //Helper functions - - const getHexString = (value?: ColorInput) => { - if (!value) return undefined; - const color = tinycolor(value); - return color.getAlpha() === 1 ? color.toHexString() : color.toHex8String(); - }; - - const getChannelLabel = (channel: string) => { - switch (channel) { - case "r": - return "Red"; - case "g": - return "Green"; - case "b": - return "Blue"; - case "a": - return "Alpha"; - default: - return ""; - } - }; - - function getChannelValue(rgba: RGBA, channel: keyof RGBA): number { - return rgba[channel]; - } - //Actions const handleChange = useCallback((newColor: RGBA) => { @@ -86,6 +50,7 @@ export default ({ value, onChange }: Params) => { setColor(undefined); setRgba(tinycolor(colorState == null ? undefined : colorState).toRgb()); } + setTempColor(undefined); setOpen(false); }, [value, colorState]); @@ -95,6 +60,7 @@ export default ({ value, onChange }: Params) => { setColor(tempColor); setRgba(tinycolor(tempColor).toRgb()); onChange(tempColor); + setTempColor(undefined); } else if (colorState != value && colorState) { onChange(colorState); } diff --git a/web/src/beta/components/fields/ColorField/index.tsx b/web/src/beta/components/fields/ColorField/index.tsx index 2f58924e36..e1724cc861 100644 --- a/web/src/beta/components/fields/ColorField/index.tsx +++ b/web/src/beta/components/fields/ColorField/index.tsx @@ -10,21 +10,13 @@ import { styled, css, useTheme } from "@reearth/services/theme"; import Property from ".."; -import useHooks, { RGBA } from "./hooks"; -import "./styles.css"; +import useHooks from "./hooks"; +import { Props, RGBA } from "./types"; // Constants const channels = ["r", "g", "b", "a"]; const hexPlaceholder = "#RRGGBBAA"; -// Component Props -export type Props = { - name?: string; - description?: string; - value?: string; - onChange?: (value: string) => void; -}; - // Component const ColorField: React.FC = ({ name, description, value, onChange }) => { const t = useT(); @@ -51,7 +43,7 @@ const ColorField: React.FC = ({ name, description, value, onChange }) => - + @@ -65,7 +57,7 @@ const ColorField: React.FC = ({ name, description, value, onChange }) => /> - + Color Picker @@ -73,7 +65,7 @@ const ColorField: React.FC = ({ name, description, value, onChange }) => {handleClose && } - + RGBA @@ -255,4 +247,42 @@ const ButtonWrapper = styled(Button)` margin: 0px; `; +const ColorPickerStyles = css` + padding: 8px 8px 6px 0px; + gap: 12px; + + .react-colorful__saturation-pointer { + width: 15px; + height: 15px; + border-width: 2px; + } + + .react-colorful__hue-pointer, + .react-colorful__alpha-pointer { + width: 1px; + height: 10px; + border: 1px solid white; + border-radius: 2px; + } + + .react-colorful__saturation { + margin-bottom: 10px; + border-radius: 3px; + width: 270px; + border-bottom: none; + } + + .react-colorful__hue, + .react-colorful__alpha { + height: 10px; + width: 270px; + margin: 0 5px 10px 3px; + border-radius: 3px; + } +`; + +const ColorPicker = styled(RgbaColorPicker)` + ${ColorPickerStyles} +`; + export default ColorField; diff --git a/web/src/beta/components/fields/ColorField/styles.css b/web/src/beta/components/fields/ColorField/styles.css deleted file mode 100644 index 51dd677b97..0000000000 --- a/web/src/beta/components/fields/ColorField/styles.css +++ /dev/null @@ -1,37 +0,0 @@ -.colorPicker { - padding: 8px 8px 6px 0px; - gap:12px; - } - -.colorPicker .react-colorful__saturation-pointer { - - width: 15px; - height: 15px; - border-width: 2px; -} - -.colorPicker .react-colorful__hue-pointer, -.colorPicker .react-colorful__alpha-pointer { - width: 1px; - height: 10px; - border: 1px solid white; - border-radius: 2px; - -} - -.colorPicker .react-colorful__saturation { - margin-bottom: 10px; - border-radius: 3px; - - width: 270px; - border-bottom: none; - } - -.colorPicker .react-colorful__hue, -.colorPicker .react-colorful__alpha { - height: 10px; - width: 270px; - margin: 0 5px 10px 3px; - border-radius: 3px; - -} \ No newline at end of file diff --git a/web/src/beta/components/fields/ColorField/types.ts b/web/src/beta/components/fields/ColorField/types.ts new file mode 100644 index 0000000000..2be9ab1ca6 --- /dev/null +++ b/web/src/beta/components/fields/ColorField/types.ts @@ -0,0 +1,19 @@ +// Component Props +export type Props = { + name?: string; + description?: string; + value?: string; + onChange?: (value: string) => void; +}; + +export type RGBA = { + r: number; + g: number; + b: number; + a: number; +}; + +export type Params = { + value?: string; + onChange?: (value: string) => void; +}; diff --git a/web/src/beta/components/fields/ColorField/utils.ts b/web/src/beta/components/fields/ColorField/utils.ts new file mode 100644 index 0000000000..87322e43fa --- /dev/null +++ b/web/src/beta/components/fields/ColorField/utils.ts @@ -0,0 +1,28 @@ +import tinycolor, { ColorInput } from "tinycolor2"; + +import { RGBA } from "./types"; + +export const getHexString = (value?: ColorInput) => { + if (!value) return undefined; + const color = tinycolor(value); + return color.getAlpha() === 1 ? color.toHexString() : color.toHex8String(); +}; + +export const getChannelLabel = (channel: string) => { + switch (channel) { + case "r": + return "Red"; + case "g": + return "Green"; + case "b": + return "Blue"; + case "a": + return "Alpha"; + default: + return ""; + } +}; + +export function getChannelValue(rgba: RGBA, channel: keyof RGBA): number { + return rgba[channel]; +}