diff --git a/src/components/atoms/Timeline/ScaleList.tsx b/src/components/atoms/Timeline/ScaleList.tsx index 495c72a12b..6213aa988e 100644 --- a/src/components/atoms/Timeline/ScaleList.tsx +++ b/src/components/atoms/Timeline/ScaleList.tsx @@ -1,24 +1,26 @@ import { memo } from "react"; import Text from "@reearth/components/atoms/Text"; -import { styled } from "@reearth/theme"; +import { PublishTheme, styled } from "@reearth/theme"; import { EPOCH_SEC, STRONG_SCALE_WIDTH, NORMAL_SCALE_WIDTH, PADDING_HORIZONTAL } from "./constants"; import { formatDateForTimeline } from "./utils"; type Props = { + publishedTheme?: PublishTheme; gapHorizontal: number; } & ScaleListInnerProps; -const ScaleList: React.FC = ({ gapHorizontal, ...props }) => { +const ScaleList: React.FC = ({ publishedTheme, gapHorizontal, ...props }) => { return ( - + ); }; type ScaleListInnerProps = { + publishedTheme?: PublishTheme; start: Date; scaleCount: number; hoursCount: number; @@ -27,6 +29,7 @@ type ScaleListInnerProps = { }; const ScaleListInner: React.FC = memo(function ScaleListPresenter({ + publishedTheme, start, scaleCount, hoursCount, @@ -45,7 +48,9 @@ const ScaleListInner: React.FC = memo(function ScaleListPre return ( - {label} + + {label} + ); @@ -56,6 +61,10 @@ const ScaleListInner: React.FC = memo(function ScaleListPre ); }); +export type StyledColorProps = { + publishedTheme: PublishTheme | undefined; +}; + const ScaleContainer = styled.div` display: flex; width: 0; @@ -67,7 +76,7 @@ const ScaleContainer = styled.div` ::after { content: ""; display: block; - padding-right: ${PADDING_HORIZONTAL}px; + padding-right: 1px; height: 1px; } `; @@ -79,11 +88,11 @@ const LabeledScale = styled.div` height: 100%; `; -const ScaleLabel = styled(Text)` +const ScaleLabel = styled(Text)` position: absolute; top: 0; left: 0; - color: ${({ theme }) => theme.colors.publish.dark.text.main}; + color: ${({ theme, publishedTheme }) => publishedTheme?.mainText || theme.main.text}; white-space: nowrap; `; diff --git a/src/components/atoms/Timeline/hooks.ts b/src/components/atoms/Timeline/hooks.ts index ac29eda4b0..a0119f4271 100644 --- a/src/components/atoms/Timeline/hooks.ts +++ b/src/components/atoms/Timeline/hooks.ts @@ -166,7 +166,10 @@ const useTimelinePlayer = ({ const lastIdx = textDate.lastIndexOf(" "); const date = textDate.slice(0, lastIdx); const time = textDate.slice(lastIdx); - return `${date}\n${time}`; + return { + date, + time, + }; }, [currentTime]); useEffect(() => { diff --git a/src/components/atoms/Timeline/index.tsx b/src/components/atoms/Timeline/index.tsx index 67b0be5a18..5f55a73cc6 100644 --- a/src/components/atoms/Timeline/index.tsx +++ b/src/components/atoms/Timeline/index.tsx @@ -5,11 +5,11 @@ import Text from "@reearth/components/atoms/Text"; // eslint-disable-next-line no-restricted-imports import type { SceneProperty } from "@reearth/components/molecules/Visualizer"; import { useT } from "@reearth/i18n"; -import { PublishTheme, styled, usePublishTheme } from "@reearth/theme"; +import { styled, usePublishTheme } from "@reearth/theme"; import { BORDER_WIDTH, PADDING_HORIZONTAL, KNOB_SIZE } from "./constants"; import { useTimeline } from "./hooks"; -import ScaleList from "./ScaleList"; +import ScaleList, { StyledColorProps } from "./ScaleList"; import { Range, TimeEventHandler } from "./types"; export type Props = { @@ -24,6 +24,8 @@ export type Props = { */ range?: { [K in keyof Range]?: Range[K] }; speed?: number; + isOpened?: boolean; + sceneProperty?: SceneProperty; onClick?: TimeEventHandler; onDrag?: TimeEventHandler; onPlay?: (isPlaying: boolean) => void; @@ -31,23 +33,21 @@ export type Props = { onOpen?: () => void; onClose?: () => void; onSpeedChange?: (speed: number) => void; - isOpened?: boolean; - sceneProperty?: SceneProperty; }; const Timeline: React.FC = memo(function TimelinePresenter({ currentTime, range, speed, + isOpened, + sceneProperty, onClick, onDrag, onPlay, onPlayReversed, - isOpened, onOpen, onClose, onSpeedChange: onSpeedChangeProps, - sceneProperty, }) { const { startDate, @@ -79,10 +79,12 @@ const Timeline: React.FC = memo(function TimelinePresenter({ const t = useT(); return isOpened ? ( - - - - + +
+ + + +
  • = memo(function TimelinePresenter({
  • - {speed}x + + {speed}X + = memo(function TimelinePresenter({
  • - - {formattedCurrentTime} - + + + {formattedCurrentTime.date} + + + {formattedCurrentTime.time} + + {/** * TODO: Support keyboard operation for accessibility * see: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/slider_role @@ -130,6 +139,7 @@ const Timeline: React.FC = memo(function TimelinePresenter({ gapHorizontal={gapHorizontal} scaleInterval={scaleInterval} strongScaleHours={strongScaleHours} + publishedTheme={publishedTheme} /> = memo(function TimelinePresenter({
    ) : ( - + ); }); -type StyledColorProps = { - publishedTheme: PublishTheme | undefined; -}; - -const Container = styled.div` - background: ${({ theme }) => theme.main.deepBg}; +const Container = styled.div` + background: ${({ theme, publishedTheme }) => publishedTheme?.background || theme.main.deepBg}; width: 100%; + height: 40px; display: flex; box-sizing: border-box; -webkit-user-select: none; @@ -163,16 +170,15 @@ const Container = styled.div` user-select: none; `; -const OpenButton = styled.button` - background: ${({ theme }) => theme.main.deepBg}; - color: ${({ theme }) => theme.main.text}; +const OpenButton = styled.button` + background: ${({ theme, publishedTheme }) => publishedTheme?.background || theme.main.deepBg}; + color: ${({ theme, publishedTheme }) => publishedTheme?.mainText || theme.main.text}; padding: 8px 12px; `; const CloseButton = styled.button` background: ${({ theme, publishedTheme }) => publishedTheme?.select || theme.main.select}; - padding: 4px; - color: ${({ theme }) => theme.main.text}; + color: ${({ theme, publishedTheme }) => publishedTheme?.mainText || theme.main.text}; display: flex; align-items: center; justify-content: center; @@ -191,10 +197,13 @@ const ToolBox = styled.ul` const PlayButton = styled.button<{ isRight?: boolean; isPlaying?: boolean } & StyledColorProps>` border-radius: 50%; - width: 24px; - height: 24px; - border: ${({ theme }) => `1px solid ${theme.main.weak}`}; - color: ${({ theme }) => theme.main.text}; + border-width: 1px; + border-style: solid; + border-color: ${({ theme, isPlaying, publishedTheme }) => + isPlaying ? publishedTheme?.select : publishedTheme?.mainText || theme.main.text}; + width: 22px; + height: 22px; + color: ${({ theme, publishedTheme }) => publishedTheme?.mainText || theme.main.text}; display: flex; align-items: center; justify-content: center; @@ -207,11 +216,11 @@ const InputRangeLabel = styled.label` display: flex; align-items: center; justify-content: center; - margin: ${({ theme }) => `0 ${theme.metrics.s}px 0 ${theme.metrics.m}px`}; + margin: ${({ theme }) => `0 ${theme.metrics["2xs"]}px 0 ${theme.metrics["2xs"]}px`}; `; -const InputRangeLabelText = styled(Text)` - color: ${({ theme }) => theme.main.text}; +const InputRangeLabelText = styled(Text)` + color: ${({ theme, publishedTheme }) => publishedTheme?.mainText || theme.main.text}; /* space for preventing layout shift by increasing speed label. */ width: 37px; text-align: right; @@ -233,40 +242,47 @@ const InputRange = styled.input` } `; -const CurrentTime = styled(Text)` +const CurrentTimeWrapper = styled.div` border: ${({ theme }) => `1px solid ${theme.main.weak}`}; - border-radius: 5px; - color: ${({ theme }) => theme.main.text}; + border-radius: 4px; padding: ${({ theme }) => `0 ${theme.metrics.s}px`}; - margin: ${({ theme }) => `${theme.metrics.s}px 0`}; + margin: ${({ theme }) => `${theme.metrics.xs}px 0`}; flex-shrink: 0; +`; + +const CurrentTime = styled(Text)` + color: ${({ theme, publishedTheme }) => publishedTheme?.mainText || theme.main.text}; + line-height: 16px; white-space: pre-line; `; const ScaleBox = styled.div` - border: ${({ theme }) => `${BORDER_WIDTH}px solid ${theme.colors.publish.dark.icon.weak}`}; + border: ${({ theme }) => `${BORDER_WIDTH}px solid ${theme.main.weak}`}; border-radius: 5px; box-sizing: border-box; position: relative; - overflow-x: auto; + overflow-x: overlay; overflow-y: hidden; width: 100%; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; - ::-webkit-scrollbar { - height: 5px; + transition: -webkit-scrollbar 1s; + :hover::-webkit-scrollbar { + height: 2px; } ::-webkit-scrollbar-track { background-color: transparent; + background-color: red; + display: none; } ::-webkit-scrollbar-thumb { border-radius: 5px; background-color: ${({ theme }) => theme.colors.publish.dark.icon.main}; } margin: ${({ theme }) => - `${theme.metrics.s}px ${theme.metrics.m}px ${theme.metrics.s}px ${theme.metrics.xs}px`}; + `${theme.metrics.xs}px ${theme.metrics.s}px ${theme.metrics.xs}px ${theme.metrics.xs}px`}; `; const IconWrapper = styled.div` diff --git a/src/components/molecules/Visualizer/Widget/Timeline/hooks.ts b/src/components/molecules/Visualizer/Widget/Timeline/hooks.ts index ab91a2cc3f..f13ff3a8c2 100644 --- a/src/components/molecules/Visualizer/Widget/Timeline/hooks.ts +++ b/src/components/molecules/Visualizer/Widget/Timeline/hooks.ts @@ -1,6 +1,7 @@ import { useState, useCallback, useEffect, useRef } from "react"; import { TimeEventHandler } from "@reearth/components/atoms/Timeline/types"; +import { Widget } from "@reearth/components/molecules/Visualizer/Plugin"; import { useContext } from "../../Plugin"; @@ -14,13 +15,19 @@ const makeRange = (startTime?: number, stopTime?: number) => { const DEFAULT_SPEED = 1; -export const useTimeline = () => { +export const useTimeline = ({ + widget, + onExtend, +}: { + widget: Widget; + onExtend?: (id: string, extended: boolean | undefined) => void; +}) => { const ctx = useContext(); const clock = ctx?.reearth.clock; const [range, setRange] = useState(() => makeRange(clock?.startTime.getTime(), clock?.stopTime.getTime()), ); - const [isOpened, setIsOpened] = useState(false); + const [isOpened, setIsOpened] = useState(true); const [currentTime, setCurrentTime] = useState(() => getOrNewDate(clock?.currentTime).getTime()); const isClockInitialized = useRef(false); const clockCurrentTime = clock?.currentTime.getTime(); @@ -31,11 +38,14 @@ export const useTimeline = () => { const [speed, setSpeed] = useState(clockSpeed); const handleOnOpen = useCallback(() => { + onExtend?.(widget.id, true); setIsOpened(true); - }, []); + }, [widget.id, onExtend]); + const handleOnClose = useCallback(() => { + onExtend?.(widget.id, false); setIsOpened(false); - }, []); + }, [widget.id, onExtend]); const handleTimeEvent: TimeEventHandler = useCallback( currentTime => { diff --git a/src/components/molecules/Visualizer/Widget/Timeline/index.tsx b/src/components/molecules/Visualizer/Widget/Timeline/index.tsx index 0a8782d55e..05af8bac50 100644 --- a/src/components/molecules/Visualizer/Widget/Timeline/index.tsx +++ b/src/components/molecules/Visualizer/Widget/Timeline/index.tsx @@ -6,11 +6,11 @@ import { useTimeline } from "./hooks"; export type Props = WidgetProps; -const Timeline = ({ widget, sceneProperty }: Props): JSX.Element | null => { - const { isOpened, currentTime, range, speed, events } = useTimeline(); +const Timeline = ({ widget, sceneProperty, onExtend }: Props): JSX.Element | null => { + const { isOpened, currentTime, range, speed, events } = useTimeline({ widget, onExtend }); return ( - + { const Widget = styled.div<{ extended?: boolean; + opened?: boolean; }>` max-width: 100vw; - width: ${({ extended }) => (extended ? "100%" : "720px")}; + width: ${({ extended, opened }) => (extended && opened ? "100%" : opened ? "720px" : "auto")}; @media (max-width: 560px) { - width: ${({ extended }) => (extended ? "100%" : "90vw")}; + width: ${({ extended, opened }) => (extended && opened ? "100%" : opened ? "90vw" : "auto")}; } `;