From 6c6734b68acc3986b94c7726aef56742f27fd487 Mon Sep 17 00:00:00 2001 From: m-abe-dev <66056064+m-abe-dev@users.noreply.github.com> Date: Fri, 12 Jul 2024 17:27:27 +0900 Subject: [PATCH] refactor(web): new field components (#1017) Co-authored-by: mkumbobeaty Co-authored-by: airslice --- .../fields/CameraField/EditPanel/hooks.ts | 2 +- .../components/fields/CameraField/types.ts | 8 +- .../components/DatePicker/index.tsx | 4 +- .../components/ModalPanel/index.tsx | 1 + .../components/NumberInput/index.tsx | 14 +- .../reearth-ui/components/Selector/index.tsx | 11 +- .../components/TextInput/index.stories.tsx | 2 +- .../reearth-ui/components/TextInput/index.tsx | 13 +- .../components/TimePicker/index.tsx | 4 +- .../beta/ui/components/EntryItem/index.tsx | 1 + web/src/beta/ui/fields/AssetField.tsx | 136 ++++++++++++++ .../ui/fields/CamersField/CapturePanel.tsx | 74 ++++++++ .../ui/fields/CamersField/EditorPanel.tsx | 100 +++++++++++ web/src/beta/ui/fields/CamersField/hooks.ts | 105 +++++++++++ web/src/beta/ui/fields/CamersField/index.tsx | 168 +++++++++++++++++ web/src/beta/ui/fields/CodeField.tsx | 29 +++ web/src/beta/ui/fields/ColorField.tsx | 7 +- web/src/beta/ui/fields/CommonField.tsx | 36 ++-- web/src/beta/ui/fields/InputField.tsx | 4 +- web/src/beta/ui/fields/ListField/ListItem.tsx | 112 ++++++++++++ .../ui/fields/ListField/index.stories.tsx | 114 ++++++++++++ web/src/beta/ui/fields/ListField/index.tsx | 120 +++++++++++++ web/src/beta/ui/fields/QuartetInputField.tsx | 83 +++++++++ web/src/beta/ui/fields/SelectField.tsx | 17 ++ web/src/beta/ui/fields/SpacingField.tsx | 93 ++++++++++ web/src/beta/ui/fields/SwitchField.tsx | 7 +- web/src/beta/ui/fields/TextareaField.tsx | 17 ++ .../ui/fields/TimePeriodField/EditModal.tsx | 134 ++++++++++++++ .../beta/ui/fields/TimePeriodField/hooks.ts | 106 +++++++++++ .../beta/ui/fields/TimePeriodField/index.tsx | 170 ++++++++++++++++++ .../ui/fields/TimePointField/EditPanel.tsx | 108 +++++++++++ .../beta/ui/fields/TimePointField/hooks.ts | 98 ++++++++++ .../beta/ui/fields/TimePointField/index.tsx | 111 ++++++++++++ web/src/beta/ui/fields/TripletInputField.tsx | 84 +++++++++ web/src/beta/ui/fields/TwinInputField.tsx | 84 +++++++++ web/src/beta/ui/fields/index.stories.tsx | 133 ++++++++++++++ web/src/beta/ui/fields/index.ts | 20 ++- web/src/services/i18n/translations/en.yml | 6 +- web/src/services/i18n/translations/ja.yml | 3 + 39 files changed, 2297 insertions(+), 42 deletions(-) create mode 100644 web/src/beta/ui/fields/AssetField.tsx create mode 100644 web/src/beta/ui/fields/CamersField/CapturePanel.tsx create mode 100644 web/src/beta/ui/fields/CamersField/EditorPanel.tsx create mode 100644 web/src/beta/ui/fields/CamersField/hooks.ts create mode 100644 web/src/beta/ui/fields/CamersField/index.tsx create mode 100644 web/src/beta/ui/fields/CodeField.tsx create mode 100644 web/src/beta/ui/fields/ListField/ListItem.tsx create mode 100644 web/src/beta/ui/fields/ListField/index.stories.tsx create mode 100644 web/src/beta/ui/fields/ListField/index.tsx create mode 100644 web/src/beta/ui/fields/QuartetInputField.tsx create mode 100644 web/src/beta/ui/fields/SelectField.tsx create mode 100644 web/src/beta/ui/fields/SpacingField.tsx create mode 100644 web/src/beta/ui/fields/TextareaField.tsx create mode 100644 web/src/beta/ui/fields/TimePeriodField/EditModal.tsx create mode 100644 web/src/beta/ui/fields/TimePeriodField/hooks.ts create mode 100644 web/src/beta/ui/fields/TimePeriodField/index.tsx create mode 100644 web/src/beta/ui/fields/TimePointField/EditPanel.tsx create mode 100644 web/src/beta/ui/fields/TimePointField/hooks.ts create mode 100644 web/src/beta/ui/fields/TimePointField/index.tsx create mode 100644 web/src/beta/ui/fields/TripletInputField.tsx create mode 100644 web/src/beta/ui/fields/TwinInputField.tsx create mode 100644 web/src/beta/ui/fields/index.stories.tsx diff --git a/web/src/beta/components/fields/CameraField/EditPanel/hooks.ts b/web/src/beta/components/fields/CameraField/EditPanel/hooks.ts index 20409b43b4..b9540d0cf3 100644 --- a/web/src/beta/components/fields/CameraField/EditPanel/hooks.ts +++ b/web/src/beta/components/fields/CameraField/EditPanel/hooks.ts @@ -44,7 +44,7 @@ export default ({ { id: "lat", description: t("Latitude") }, { id: "lng", description: t("Longitude") }, ], - [t("Height")]: [{ id: "height", suffix: "km" }], + [t("Height")]: [{ id: "height", unit: "km", description: t("Height") }], [t("Rotation")]: [ { id: "heading", description: t("Heading") }, { id: "pitch", description: t("Pitch") }, diff --git a/web/src/beta/components/fields/CameraField/types.ts b/web/src/beta/components/fields/CameraField/types.ts index de9d38b9f2..25297cb761 100644 --- a/web/src/beta/components/fields/CameraField/types.ts +++ b/web/src/beta/components/fields/CameraField/types.ts @@ -2,4 +2,10 @@ import type { Camera } from "@reearth/beta/utils/value"; export type { Camera } from "@reearth/beta/utils/value"; -export type RowType = { id: keyof Camera; value?: number; description?: string; suffix?: string }[]; +export type RowType = { + id: keyof Camera; + value?: number; + description?: string; + suffix?: string; + unit?: string; +}[]; diff --git a/web/src/beta/lib/reearth-ui/components/DatePicker/index.tsx b/web/src/beta/lib/reearth-ui/components/DatePicker/index.tsx index c6f99a4a3f..4822fdb2da 100644 --- a/web/src/beta/lib/reearth-ui/components/DatePicker/index.tsx +++ b/web/src/beta/lib/reearth-ui/components/DatePicker/index.tsx @@ -22,9 +22,9 @@ export const DatePicker: FC = ({ value, disabled, onChange, onB (e: ChangeEvent) => { const newValue = e.currentTarget.value; setCurrentValue(newValue ?? ""); - onChange?.(currentValue); + onChange?.(newValue); }, - [currentValue, onChange], + [onChange], ); const handleBlur = useCallback(() => { diff --git a/web/src/beta/lib/reearth-ui/components/ModalPanel/index.tsx b/web/src/beta/lib/reearth-ui/components/ModalPanel/index.tsx index 8cf8ab2fcc..b02d0d1eb2 100644 --- a/web/src/beta/lib/reearth-ui/components/ModalPanel/index.tsx +++ b/web/src/beta/lib/reearth-ui/components/ModalPanel/index.tsx @@ -50,6 +50,7 @@ const Title = styled("div")(() => ({ const Content = styled("div")(() => ({ alignSelf: "stretch", + userSelect: "none", })); const ActionWrapper = styled("div")(({ theme }) => ({ diff --git a/web/src/beta/lib/reearth-ui/components/NumberInput/index.tsx b/web/src/beta/lib/reearth-ui/components/NumberInput/index.tsx index e1484ca0d8..1e056a2bb8 100644 --- a/web/src/beta/lib/reearth-ui/components/NumberInput/index.tsx +++ b/web/src/beta/lib/reearth-ui/components/NumberInput/index.tsx @@ -8,6 +8,7 @@ export type NumberInputProps = { disabled?: boolean; placeholder?: string; appearance?: "readonly"; + extendWidth?: boolean; unit?: string; min?: number; max?: number; @@ -21,6 +22,7 @@ export const NumberInput: FC = ({ disabled, placeholder, appearance, + extendWidth, unit, min, max, @@ -50,9 +52,8 @@ export const NumberInput: FC = ({ validatedValue = currentValue; } } - setCurrentValue(validatedValue); - onChange?.(parseFloat(validatedValue)); + onChange?.(currentValue === "" ? undefined : parseFloat(validatedValue)); } }, [max, min, onChange], @@ -76,7 +77,7 @@ export const NumberInput: FC = ({ }, []); return ( - + = ({ const Wrapper = styled("div")<{ size: "normal" | "small"; status: "default" | "active"; -}>(({ size, theme, status }) => { + extendWidth?: boolean; +}>(({ size, theme, status, extendWidth }) => { return { border: status === "active" ? `1px solid ${theme.select.main}` : `1px solid ${theme.outline.weak}`, borderRadius: theme.radius.small, background: theme.bg[1], display: "flex", + flex: 1, gap: theme.spacing.smallest, alignItems: "center", padding: @@ -108,6 +111,8 @@ const Wrapper = styled("div")<{ ? `0 ${theme.spacing.smallest}px` : `${theme.spacing.smallest}px ${theme.spacing.small}px`, boxShadow: theme.shadow.input, + width: !extendWidth ? "" : "100%", + boxSizing: "border-box", }; }); @@ -124,6 +129,7 @@ const StyledInput = styled("input")<{ fontSize: fonts.sizes.body, lineHeight: `${fonts.lineHeights.body}px`, textOverflow: "ellipsis", + pointerEvents: disabled ? "none" : "inherit", overflow: "hidden", "::placeholder": { color: theme.content.weak, diff --git a/web/src/beta/lib/reearth-ui/components/Selector/index.tsx b/web/src/beta/lib/reearth-ui/components/Selector/index.tsx index 59f2d55c0d..146f16429f 100644 --- a/web/src/beta/lib/reearth-ui/components/Selector/index.tsx +++ b/web/src/beta/lib/reearth-ui/components/Selector/index.tsx @@ -13,6 +13,7 @@ export type SelectorProps = { options: { value: string; label?: string }[]; disabled?: boolean; placeholder?: string; + maxHeight?: number; onChange?: (value: string | string[]) => void; }; @@ -22,6 +23,7 @@ export const Selector: FC = ({ options, placeholder = "Please select", disabled, + maxHeight, onChange, }) => { const theme = useTheme(); @@ -151,7 +153,7 @@ export const Selector: FC = ({ onOpenChange={setIsOpen} disabled={disabled} placement="bottom-start"> - + {optionValues.length === 0 ? ( @@ -203,7 +205,7 @@ const SelectInput = styled("div")<{ }px`, cursor: disabled ? "not-allowed" : "pointer", minWidth: width ? `${width}px` : "fit-content", - minHeight: "32px", + height: "32px", })); const SelectedItems = styled("div")(({ theme }) => ({ @@ -225,7 +227,8 @@ const SelectedItem = styled("div")(({ theme }) => ({ const DropDownWrapper = styled("div")<{ width?: number; -}>(({ width, theme }) => ({ + maxHeight?: number; +}>(({ width, maxHeight, theme }) => ({ boxSizing: "border-box", display: "flex", flexDirection: "column", @@ -236,6 +239,8 @@ const DropDownWrapper = styled("div")<{ borderRadius: `${theme.radius.small}px`, width: width ? `${width}px` : "", border: `1px solid ${theme.outline.weaker}`, + maxHeight: maxHeight ? `${maxHeight}px` : "", + overflowY: maxHeight ? "auto" : "hidden", })); const DropDownItem = styled("div")<{ diff --git a/web/src/beta/lib/reearth-ui/components/TextInput/index.stories.tsx b/web/src/beta/lib/reearth-ui/components/TextInput/index.stories.tsx index faf7490db8..cd5dedfdf7 100644 --- a/web/src/beta/lib/reearth-ui/components/TextInput/index.stories.tsx +++ b/web/src/beta/lib/reearth-ui/components/TextInput/index.stories.tsx @@ -54,7 +54,7 @@ export const UsecaseReadonly: Story = { }, }; // TODO: use IconButton instead of MockButton -const MockButton: FC = () => ; +const MockButton: FC = () => ; export const Actions: Story = { args: { diff --git a/web/src/beta/lib/reearth-ui/components/TextInput/index.tsx b/web/src/beta/lib/reearth-ui/components/TextInput/index.tsx index 1ba7409d9d..72538b2f3b 100644 --- a/web/src/beta/lib/reearth-ui/components/TextInput/index.tsx +++ b/web/src/beta/lib/reearth-ui/components/TextInput/index.tsx @@ -12,6 +12,7 @@ export type TextInputProps = { extendWidth?: boolean; autoFocus?: boolean; actions?: FC[]; + leftAction?: FC[]; onChange?: (text: string) => void; onBlur?: (text: string) => void; }; @@ -25,6 +26,7 @@ export const TextInput: FC = ({ appearance, extendWidth, actions, + leftAction, autoFocus, onChange, onBlur, @@ -60,6 +62,13 @@ export const TextInput: FC = ({ appearance={appearance} extendWidth={extendWidth} status={isFocused || autoFocus ? "active" : "default"}> + {leftAction && ( + + {leftAction.map((Action, i) => ( + + ))} + + )} ({ alignItems: "center", gap: `${theme.spacing.smallest}px`, flexShrink: 0, - padding: theme.spacing.micro, - color: theme.content.weak, })); diff --git a/web/src/beta/lib/reearth-ui/components/TimePicker/index.tsx b/web/src/beta/lib/reearth-ui/components/TimePicker/index.tsx index 53445a7a42..c08991bf33 100644 --- a/web/src/beta/lib/reearth-ui/components/TimePicker/index.tsx +++ b/web/src/beta/lib/reearth-ui/components/TimePicker/index.tsx @@ -22,9 +22,9 @@ export const TimePicker: FC = ({ value, disabled, onChange, onB (e: ChangeEvent) => { const newValue = e.currentTarget.value; setCurrentValue(newValue ?? ""); - onChange?.(currentValue); + onChange?.(newValue); }, - [currentValue, onChange], + [onChange], ); const handleBlur = useCallback(() => { diff --git a/web/src/beta/ui/components/EntryItem/index.tsx b/web/src/beta/ui/components/EntryItem/index.tsx index 121956edfd..cced78b9be 100644 --- a/web/src/beta/ui/components/EntryItem/index.tsx +++ b/web/src/beta/ui/components/EntryItem/index.tsx @@ -101,6 +101,7 @@ const Wrapper = styled("div")<{ borderRadius: theme.radius.small, backgroundColor: "transparent", minHeight: 28, + width: "100%", cursor: "pointer", ...(hovered && { backgroundColor: theme.bg[1], diff --git a/web/src/beta/ui/fields/AssetField.tsx b/web/src/beta/ui/fields/AssetField.tsx new file mode 100644 index 0000000000..e31e18c412 --- /dev/null +++ b/web/src/beta/ui/fields/AssetField.tsx @@ -0,0 +1,136 @@ +import { FC, useCallback, useEffect, useState } from "react"; + +import { FILE_FORMATS, IMAGE_FORMATS } from "@reearth/beta/features/Assets/constants"; +import { AcceptedFileFormat } from "@reearth/beta/features/Assets/types"; +import AssetModal from "@reearth/beta/features/Modals/AssetModal"; +import LayerStyleModal from "@reearth/beta/features/Modals/LayerStyleModal"; +import useFileUploaderHook from "@reearth/beta/hooks/useAssetUploader/hooks"; +import { TextInput, Button } from "@reearth/beta/lib/reearth-ui"; +import { checkIfFileType } from "@reearth/beta/utils/util"; +import { useT } from "@reearth/services/i18n"; +import { useNotification, useWorkspace } from "@reearth/services/state"; +import { styled } from "@reearth/services/theme"; + +import CommonField, { CommonFieldProps } from "./CommonField"; + +export type AssetFieldProps = CommonFieldProps & { + value?: string; + fileType?: "asset" | "URL" | "layerStyle"; + entityType?: "image" | "file" | "layerStyle"; + fileFormat?: AcceptedFileFormat; + sceneId?: string; + placeholder?: string; + onChange?: (value: string | undefined, name: string | undefined) => void; +}; + +const AssetField: FC = ({ + commonTitle, + description, + value, + entityType, + fileType, + fileFormat, + sceneId, + placeholder, + onChange, +}) => { + const t = useT(); + const [open, setOpen] = useState(false); + const [currentWorkspace] = useWorkspace(); + const [, setNotification] = useNotification(); + const [currentValue, setCurrentValue] = useState(value); + + const handleChange = useCallback( + (inputValue?: string, name?: string) => { + if (!inputValue) { + setCurrentValue(inputValue); + onChange?.(inputValue, name); + } else if ( + fileType === "asset" && + !(checkIfFileType(inputValue, FILE_FORMATS) || checkIfFileType(inputValue, IMAGE_FORMATS)) + ) { + setNotification({ + type: "error", + text: t("Wrong file format"), + }); + setCurrentValue(undefined); + } else { + setCurrentValue(inputValue); + onChange?.(inputValue, name); + } + }, + [fileType, onChange, setNotification, t], + ); + + const { handleFileUpload } = useFileUploaderHook({ + workspaceId: currentWorkspace?.id, + onAssetSelect: handleChange, + assetType: entityType, + fileFormat, + }); + + useEffect(() => { + setCurrentValue(value ?? ""); + }, [value]); + + const handleClick = useCallback(() => setOpen(!open), [open]); + const handleModalClose = useCallback(() => setOpen(false), []); + + return ( + + + + {fileType === "asset" && ( + +