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): basic widget page #578

Merged
merged 36 commits into from
Jul 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
4cfb7fd
update icons
KaWaite Jul 12, 2023
d10cba1
update Editor
KaWaite Jul 12, 2023
f77f683
add VisualizerNav component
KaWaite Jul 12, 2023
eebc074
add WidgetNav component to widget page w editor toggle func
KaWaite Jul 12, 2023
4d37c8b
basic device layout support
KaWaite Jul 12, 2023
15890ad
fix secondary navbar styles across tabs
KaWaite Jul 12, 2023
a851d08
refactor, basic right panel
KaWaite Jul 13, 2023
090d718
delete unused types file
KaWaite Jul 13, 2023
5fdfb59
refactor widget navbar
KaWaite Jul 13, 2023
ef9efd5
refactor
KaWaite Jul 13, 2023
37a99f4
remove custom fetch policy
KaWaite Jul 13, 2023
afa076a
fix unique key warning
KaWaite Jul 13, 2023
2a2b4b5
update gql api
KaWaite Jul 13, 2023
2d5e749
ui updates
KaWaite Jul 13, 2023
c9725bd
new components
KaWaite Jul 13, 2023
48aeaa6
fix UI, etc
KaWaite Jul 13, 2023
60512a1
update theme use in toggle
KaWaite Jul 13, 2023
139f5e7
Merge remote-tracking branch 'origin/main' into feat-basic-widget-page
KaWaite Jul 14, 2023
044b1b1
update TabButton
KaWaite Jul 14, 2023
95aa83c
refactor/fix dropdown in nav
KaWaite Jul 18, 2023
0981c6f
Merge remote-tracking branch 'origin/main' into feat-basic-widget-page
KaWaite Jul 20, 2023
a0b228a
wip: show installed/installable widgets
KaWaite Jul 21, 2023
dc264aa
move and update Item component
KaWaite Jul 21, 2023
aaa871e
refactor
KaWaite Jul 21, 2023
cdb637b
refactor
KaWaite Jul 21, 2023
2b1c9b2
rename component
KaWaite Jul 21, 2023
448a5b9
refactor state
KaWaite Jul 21, 2023
20154f3
stuff
KaWaite Jul 21, 2023
06b96f9
revert listitem changes
KaWaite Jul 21, 2023
8fb023b
Merge branch 'main' into feat-basic-widget-page
KaWaite Jul 21, 2023
df4151c
Merge branch 'feat-basic-widget-page' of https://github.com/reearth/r…
KaWaite Jul 21, 2023
0738a39
fix story
KaWaite Jul 21, 2023
0c45602
fix another story
KaWaite Jul 21, 2023
246ffbe
fix type
KaWaite Jul 21, 2023
e90d5b5
update icon
KaWaite Jul 21, 2023
87e6ed9
updated translations
KaWaite Jul 21, 2023
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
25 changes: 7 additions & 18 deletions web/src/beta/components/Dropdown/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Meta, StoryObj } from "@storybook/react";
import { ReactNode } from "react";

import Dropdown from ".";
import Dropdown, { Menu } from ".";

const Wrapper: React.FC<{ children: ReactNode }> = ({ children }) => (
<div style={{ width: "100px", height: "60px" }}>{children}</div>
Expand All @@ -15,41 +15,30 @@ export default meta;

type Story = StoryObj<typeof Dropdown>;

const DropDownContent: React.FC = () => {
return (
<ul>
<li>Apple</li>
<li>Banana</li>
<li>Orange</li>
</ul>
);
const menu: Menu = {
width: 200,
items: [{ text: "HARHAR" }, { text: "HARHAR" }, { text: "HARHAR" }],
};

export const Default: Story = {
render: () => (
<Wrapper>
<Dropdown isOpen label="Sample">
<DropDownContent />
</Dropdown>
<Dropdown isOpen label="Sample" menu={menu} />
</Wrapper>
),
};

export const DirectionRight: Story = {
render: () => (
<Wrapper>
<Dropdown isOpen direction="right" label="Sample">
<DropDownContent />
</Dropdown>
<Dropdown isOpen direction="right" label="Sample" menu={menu} />
</Wrapper>
),
};
export const DirectionDown: Story = {
render: () => (
<Wrapper>
<Dropdown isOpen direction="down" label="Sample">
<DropDownContent />
</Dropdown>
<Dropdown isOpen direction="down" label="Sample" menu={menu} />
</Wrapper>
),
};
259 changes: 187 additions & 72 deletions web/src/beta/components/Dropdown/index.tsx
Original file line number Diff line number Diff line change
@@ -1,141 +1,256 @@
import { ReactNode, useState, useRef, forwardRef, useImperativeHandle } from "react";
import { useClickAway } from "react-use";
import { ReactNode, useState, Fragment } from "react";

import Icon from "@reearth/beta/components/Icon";
import Icon, { Icons } from "@reearth/beta/components/Icon";
import {
MenuListItemLabel,
MenuList,
MenuListItem,
} from "@reearth/beta/features/Navbar/Menus/MenuList";
import { styled } from "@reearth/services/theme";

import Text from "../Text";

type Direction = "right" | "down" | "none";

type Gap = "sm" | "md" | "lg";

export type Menu = {
width?: number;
items?: MenuItem[];
};

export type MenuItem = {
text?: string;
icon?: Icons;
iconPosition?: "left" | "right";
breakpoint?: boolean;
items?: MenuItem[];
linkTo?: string;
selected?: boolean;
onClick?: () => void;
};

export type Props = {
className?: string;
isOpen?: boolean;
label: ReactNode;
openOnClick?: boolean;
direction?: Direction;
gap?: Gap;
dropdownWidth?: number;
parentWidth?: number;
hasIcon?: boolean;
noHoverStyle?: boolean;
centered?: boolean;
children?: ReactNode;
menu?: Menu;
isChild?: boolean;
onClick?: () => void;
};

export type Ref = {
close: () => void;
};
const defaultWidth = 200;

const Dropdown: React.ForwardRefRenderFunction<Ref, Props> = (
{
className,
isOpen = false,
openOnClick,
noHoverStyle,
centered,
label,
direction = "down",
hasIcon,
children,
},
ref,
) => {
const Dropdown: React.FC<Props> = ({
className,
isOpen = false,
label,
openOnClick,
direction = "down",
gap,
dropdownWidth = defaultWidth,
parentWidth = defaultWidth,
centered,
noHoverStyle,
hasIcon,
menu,
isChild,
onClick,
}) => {
const [open, setOpen] = useState(isOpen);

const wrapperRef = useRef(null);
useClickAway(wrapperRef, () => {
setOpen(false);
});

useImperativeHandle(
ref,
() => ({
close: () => setOpen(false),
}),
[],
);

return (
<Wrapper
className={className}
ref={wrapperRef}
onMouseEnter={openOnClick ? undefined : () => setOpen(true)}
onMouseLeave={openOnClick ? undefined : () => setOpen(false)}>
<Parent noHover={noHoverStyle} centered={centered}>
<Label onClick={openOnClick ? () => setOpen(o => !o) : undefined}>
<Wrapper className={className}>
<Parent
noHover={noHoverStyle}
centered={centered}
onClick={openOnClick ? () => setOpen(o => !o) : undefined}
onMouseEnter={openOnClick ? undefined : () => setOpen(true)}
onMouseLeave={openOnClick ? undefined : () => setOpen(false)}>
<Label isChild={isChild}>
{label}
{hasIcon && (
<StyledIcon icon={direction === "right" ? "arrowRight" : "arrowDown"} size={24} />
<StyledIcon icon={direction === "right" ? "arrowRight" : "arrowDown"} size={16} />
)}
</Label>
</Parent>
{open && (
<Child onClick={openOnClick ? () => setOpen(o => !o) : undefined} direction={direction}>
{children}
</Child>
<StyledMenuList
direction={direction}
gap={gap}
dropdownWidth={dropdownWidth}
parentWidth={parentWidth}>
{menu?.items?.map((item, idx) => {
const handleClick = () => {
onClick?.();
setOpen(false);
item.onClick?.();
};

return (
<Fragment key={idx}>
{item.items ? (
<Dropdown
direction="right"
hasIcon
openOnClick
gap="sm"
isChild
dropdownWidth={menu.width}
parentWidth={dropdownWidth}
label={
<MenuListItem noHover>
<MenuListItemLabel text={item.text} />
</MenuListItem>
}
menu={{ items: item.items }}
onClick={handleClick}
/>
) : item.linkTo ? (
<MenuListItem>
<MenuListItemLabel linkTo={item.linkTo} text={item.text} icon={item.icon} />
{item.selected && <SelectedIcon isActive />}
</MenuListItem>
) : item.onClick ? (
<MenuListItem>
<MenuListItemLabel onClick={handleClick} text={item.text} icon={item.icon} />
{item.selected && <SelectedIcon isActive />}
</MenuListItem>
) : item.text ? (
<InfoOnlyItem size="h5">{item.text}</InfoOnlyItem>
) : item.breakpoint ? (
<Spacer />
) : null}
</Fragment>
);
})}
</StyledMenuList>
)}
</Wrapper>
);
};

const Wrapper = styled.div`
position: relative;
display: flex;
align-items: center;
height: 100%;
`;

const Parent = styled.div<{ noHover?: boolean; centered?: boolean }>`
position: relative;
height: inherit;
width: 100%;
display: flex;
align-items: center;
justify-content: ${({ centered }) => centered && `center;`}
background-color: ${({ theme }) => theme.navbar.bg.main};
&:hover {
${({ centered }) => centered && "justify-content: center;"}
height: inherit;
border-radius: 4px;
color: inherit;
transition: all 0.3s;
cursor: pointer;

:hover {
${({ noHover, theme }) =>
!noHover &&
`
background-color: ${theme.navbar.bg.hover};
color: ${theme.general.content.main};
background: ${theme.general.bg.main};
`}
}
`;
}
`;

const Label = styled.div`
const Label = styled.div<{ isChild?: boolean }>`
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
cursor: pointer;
width: 100%;
gap: 8px;
user-select: none;

${({ isChild }) =>
!isChild &&
`
margin-left: 8px;
margin-right: 8px;
`}
`;

const childTransform = (direction: Direction) => {
const childTransform = (direction: Direction, width: number, gap?: Gap) => {
let translateValue;
if (direction === "down") {
translateValue = gap ? (gap === "sm" ? "102%" : gap === "md" ? "104%" : "106%") : "100%";
} else {
translateValue = gap
? gap === "sm"
? `${width * 1.02}px`
: gap === "md"
? `${width * 1.04}px`
: `${width * 1.06}px`
: width;
}
switch (direction) {
case "down":
return "translateY(100%)";
case "right":
case "none":
return "translateX(100%)";
return `translateX(${translateValue})`;
case "down":
default:
return "translateY(100%)";
return `translateY(${translateValue})`;
}
};

const Child = styled.div<{ direction: Direction }>`
const StyledMenuList = styled(MenuList)<{
direction: Direction;
dropdownWidth: number;
parentWidth?: number;
gap?: Gap;
}>`
position: absolute;
background-color: ${({ theme }) => theme.navbar.bg.main};
min-width: 200px;
max-width: 230px;
background: ${({ theme }) => theme.navbar.bg.main};

width: ${({ dropdownWidth }) => (dropdownWidth ? `${dropdownWidth}px` : "200px")};
margin: 0 auto;
padding: 2px 0;
left: 0;
right: 0;
top: ${({ direction }) => (direction === "down" ? "auto" : "0")};
bottom: ${({ direction }) => (direction === "down" ? "0" : "auto")};
transform: ${({ direction }) => childTransform(direction)};
transform: ${({ direction, dropdownWidth, parentWidth, gap }) =>
childTransform(direction, parentWidth ?? dropdownWidth, gap)};
box-shadow: 6px 6px 8px rgba(0, 0, 0, 0.3);
z-index: ${props => props.theme.zIndexes.dropDown};
`;

const StyledIcon = styled(Icon)`
margin-left: 8px;
color: ${({ theme }) => theme.general.content.weak};
pointer-events: none;
`;

const SelectedIcon = styled.div<{ isActive: boolean }>`
width: 10px;
height: 10px;
border-radius: 50%;
margin-left: 4px;
order: 2;
background-color: ${({ theme }) => theme.general.select};
`;

// const ChildrenWrapper = styled.div`
// background: ${({ theme }) => theme.general.bg.strong};
// border-radius: 4px;
// `;

const Spacer = styled.div`
border-top: 0.5px solid ${({ theme }) => theme.general.border};
margin: 2px 0;
`;

const InfoOnlyItem = styled(Text)`
padding: 2px 16px;
cursor: default;
user-select: none;
`;

export default forwardRef(Dropdown);
export default Dropdown;
6 changes: 6 additions & 0 deletions web/src/beta/components/Icon/Icons/desktop.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions web/src/beta/components/Icon/Icons/folderPlus.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading