Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(web): story page settings #639

Merged
merged 21 commits into from
Aug 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions web/src/beta/components/Icon/Icons/storyPage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions web/src/beta/components/Icon/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import WorkspaceAdd from "./Icons/workspaceAdd.svg";
import Workspaces from "./Icons/workspaces.svg";

// Storytelling tab
import StoryPage from "./Icons/storyPage.svg";
import Square from "./Icons/square.svg";
import Swiper from "./Icons/swiper.svg";
import Book from "./Icons/book.svg";
Expand Down Expand Up @@ -128,6 +129,7 @@ export default {
ellipse: Ellipse,
playRight: PlayRight,
playLeft: PlayLeft,
storyPage: StoryPage,
square: Square,
swiper: Swiper,
book: Book,
Expand Down
4 changes: 2 additions & 2 deletions web/src/beta/components/Icon/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import svgToMiniDataURI from "mini-svg-data-uri";
import React, { AriaAttributes, AriaRole, CSSProperties, memo, useMemo } from "react";
import React, { AriaAttributes, AriaRole, CSSProperties, MouseEvent, memo, useMemo } from "react";
import SVG from "react-inlinesvg";

import { ariaProps } from "@reearth/beta/utils/aria";
Expand All @@ -20,7 +20,7 @@ export type Props = {
role?: AriaRole;
notransition?: boolean;
transitionDuration?: string;
onClick?: () => void;
onClick?: (e?: MouseEvent<Element>) => void;
} & AriaAttributes;

const Icon: React.FC<Props> = ({
Expand Down
7 changes: 2 additions & 5 deletions web/src/beta/components/fields/ColorField/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@ import Property from "..";
import useHooks from "./hooks";
import { Props, RGBA } from "./types";

// Constants
const channels = ["r", "g", "b", "a"];
const hexPlaceholder = "#RRGGBBAA";

// Component
const ColorField: React.FC<Props> = ({ name, description, value, onChange }) => {
const t = useT();
const theme = useTheme();
Expand Down Expand Up @@ -60,14 +58,14 @@ const ColorField: React.FC<Props> = ({ name, description, value, onChange }) =>
<PickerWrapper>
<HeaderWrapper>
<PickerTitle size="footnote" weight="regular" color={theme.content.main}>
Color Picker
{t("Color Picker")}
</PickerTitle>
{handleClose && <CloseIcon icon="cancel" size={12} onClick={handleClose} />}
</HeaderWrapper>
<SelectorPickerWrapper>
<ColorPicker className="colorPicker" color={rgba} onChange={handleChange} />
<RgbaInputWrapper>
<Text size="footnote"> RGBA</Text>
<Text size="footnote">RGBA</Text>
<ValuesWrapper>
{channels.map(channel => (
<Input
Expand Down Expand Up @@ -104,7 +102,6 @@ const ColorField: React.FC<Props> = ({ name, description, value, onChange }) =>
);
};

// Styled Components
const Wrapper = styled.div`
text-align: center;
width: 100%;
Expand Down
206 changes: 206 additions & 0 deletions web/src/beta/features/Editor/StoryPanel/ActionPanel/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import { Dispatch, Fragment, MouseEvent, SetStateAction, useMemo } from "react";

import FieldComponents from "@reearth/beta/components/fields/PropertyFields";
import Icon, { Icons } from "@reearth/beta/components/Icon";
import * as Popover from "@reearth/beta/components/Popover";
import PopoverMenuContent from "@reearth/beta/components/PopoverMenuContent";
import Text from "@reearth/beta/components/Text";
import { stopClickPropagation } from "@reearth/beta/utils/events";
import { Item } from "@reearth/services/api/propertyApi/utils";
import { useT } from "@reearth/services/i18n";
import { styled } from "@reearth/services/theme";

export type ActionItem = {
icon: string;
name?: string;
hide?: boolean;
onClick?: (e: MouseEvent<HTMLDivElement>) => void;
};

export type ActionPosition = "left-top" | "left-bottom" | "right-top" | "right-bottom";

type Props = {
isSelected?: boolean;
showSettings?: boolean;
showPadding?: boolean;
propertyId?: string;
panelSettings?: Item;
actionItems: ActionItem[];
dndEnabled?: boolean;
position?: ActionPosition;
setShowPadding: Dispatch<SetStateAction<boolean>>;
onSettingsToggle?: () => void;
onRemove?: () => void;
};

const ActionPanel: React.FC<Props> = ({
isSelected,
showSettings,
showPadding,
propertyId,
panelSettings,
actionItems,
dndEnabled,
position,
setShowPadding,
onSettingsToggle,
onRemove,
}) => {
const t = useT();

const popoverContent = useMemo(() => {
const menuItems: { name: string; icon: Icons; onClick: () => void }[] = [
{
name: t("Padding settings"),
icon: "padding",
onClick: () => setShowPadding(true),
},
];
if (onRemove) {
menuItems.push({
name: t("Remove"),
icon: "trash",
onClick: onRemove,
});
}
return menuItems;
}, [t, setShowPadding, onRemove]);

return (
<Wrapper isSelected={isSelected} position={position} onClick={stopClickPropagation}>
{dndEnabled && <DndHandle icon="dndHandle" size={16} />}
<Popover.Provider
open={showSettings}
onOpenChange={() => onSettingsToggle?.()}
placement="bottom-start">
<BlockOptions isSelected={isSelected}>
{actionItems.map(
(a, idx) =>
!a.hide && (
<Fragment key={idx}>
<Popover.Trigger asChild>
<OptionWrapper
showPointer={!isSelected || !!a.onClick}
onClick={a.onClick ?? stopClickPropagation}>
<OptionIcon icon={a.icon} size={16} border={idx !== 0} />
{a.name && (
<OptionText size="footnote" customColor>
{a.name}
</OptionText>
)}
</OptionWrapper>
</Popover.Trigger>
</Fragment>
),
)}
</BlockOptions>
<Popover.Content>
{showPadding ? (
<SettingsDropdown>
<SettingsHeading>
<Text size="footnote" customColor>
{panelSettings?.title}
</Text>
<CancelIcon icon="cancel" size={14} onClick={() => setShowPadding(false)} />
</SettingsHeading>
{propertyId && panelSettings && (
<SettingsContent>
<FieldComponents propertyId={propertyId} item={panelSettings} />
</SettingsContent>
)}
</SettingsDropdown>
) : (
<PopoverMenuContent size="sm" items={popoverContent} />
)}
</Popover.Content>
</Popover.Provider>
</Wrapper>
);
};

export default ActionPanel;

const Wrapper = styled.div<{ isSelected?: boolean; position?: ActionPosition }>`
color: ${({ theme }) => theme.select.main};
display: flex;
align-items: center;
gap: 4px;
height: 24px;
position: absolute;
${({ position }) =>
position === "left-top"
? `
left: -1px;
top: -25px;
`
: position === "left-bottom"
? `
left: -1px;
top: 0;
`
: position === "right-bottom"
? `
top: 0;
right: -1px;
`
: `
right: -1px;
top: -25px;
`}
transition: all 0.2s;
`;

const BlockOptions = styled.div<{ isSelected?: boolean }>`
background: ${({ isSelected, theme }) => (isSelected ? theme.select.main : "transparent")};
color: ${({ isSelected, theme }) => (isSelected ? theme.content.main : theme.select.main)};
display: flex;
align-items: center;
height: 24px;
transition: all 0.2s;
`;

const OptionWrapper = styled.div<{ showPointer?: boolean }>`
display: flex;
align-items: center;
cursor: ${({ showPointer }) => (showPointer ? "pointer" : "default")};
`;

const OptionText = styled(Text)`
padding-right: 4px;
`;

const OptionIcon = styled(Icon)<{ border?: boolean }>`
padding: 4px;
${({ border }) => border && "border-left: 1px solid #f1f1f1;"}
`;

const SettingsDropdown = styled.div`
z-index: 999;
background: ${({ theme }) => theme.bg[1]};
border-radius: 2px;
border: 1px solid ${({ theme }) => theme.bg[3]};
`;

const SettingsHeading = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid ${({ theme }) => theme.outline.weak};
height: 28px;
padding: 0 8px;
`;

const SettingsContent = styled.div`
height: 100px;
width: 200px;
padding: 8px;
box-sizing: border-box;
`;

const CancelIcon = styled(Icon)`
cursor: pointer;
`;

const DndHandle = styled(Icon)`
cursor: move;
`;
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { useMemo } from "react";
import { ValueTypes } from "@reearth/beta/utils/value";
import { styled } from "@reearth/services/theme";

import { getFieldValue } from "../../../utils";
import { CommonProps as BlockProps } from "../../types";
import BlockWrapper from "../common/Wrapper";
import { getFieldValue } from "../utils";

const ImageBlock: React.FC<BlockProps> = ({ block, isSelected, ...props }) => {
const src = useMemo(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { useMemo } from "react";
import Text from "@reearth/beta/components/Text";
import { ValueTypes } from "@reearth/beta/utils/value";

import { getFieldValue } from "../../../utils";
import { CommonProps as BlockProps } from "../../types";
import BlockWrapper from "../common/Wrapper";
import { getFieldValue } from "../utils";

export type Props = BlockProps;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useMemo } from "react";

import Text from "@reearth/beta/components/Text";
import { ValueTypes } from "@reearth/beta/utils/value";
import { useT } from "@reearth/services/i18n";
import { styled } from "@reearth/services/theme";

import { getFieldValue } from "../../../utils";
import { CommonProps as BlockProps } from "../../types";
import BlockWrapper from "../common/Wrapper";

export type Props = BlockProps;

const TitleBlock: React.FC<Props> = ({ block, isSelected, ...props }) => {
const t = useT();
const text = useMemo(
() => getFieldValue(block?.property?.items ?? [], "title") as ValueTypes["string"],
[block?.property?.items],
);

const color = useMemo(
() => getFieldValue(block?.property?.items ?? [], "color") as ValueTypes["string"],
[block?.property?.items],
);

return (
<BlockWrapper
title={block?.title}
icon={block?.extensionId}
isSelected={isSelected}
propertyId={block?.property?.id}
propertyItems={block?.property?.items}
dndEnabled={false}
{...props}>
<Title size="h2" hasText={!!text} color={color} customColor>
{text ?? t("Untitled")}
</Title>
</BlockWrapper>
);
};

export default TitleBlock;

const Title = styled(Text)<{ hasText?: boolean; color?: string }>`
color: ${({ color, hasText, theme }) => (hasText ? color ?? "black" : theme.content.weak)};
`;
Loading
Loading