Skip to content

Commit

Permalink
chore(web): beta project settings pages (#645)
Browse files Browse the repository at this point in the history
  • Loading branch information
airslice committed Aug 30, 2023
1 parent 8834e09 commit d422944
Show file tree
Hide file tree
Showing 79 changed files with 4,263 additions and 228 deletions.
76 changes: 76 additions & 0 deletions web/src/beta/components/Accordion/AccordionItem.tsx
@@ -0,0 +1,76 @@
import React from "react";
import {
AccordionItem as AccordionItemComponent,
AccordionItemButton,
AccordionItemHeading,
AccordionItemPanel,
AccordionItemState,
} from "react-accessible-accordion";

import { styled, useTheme } from "@reearth/services/theme";

import Icon from "../Icon";

export type Props = {
className?: string;
id: string;
heading?: React.ReactNode;
content?: React.ReactNode;
bg?: string;
};

const AccordionItem: React.FC<Props> = ({ className, id, heading, content, bg }) => {
const theme = useTheme();
return (
<Wrapper key={id} className={className} bg={bg} data-testid="atoms-accordion-item">
<AccordionItemComponent>
<AccordionItemHeading>
<StyledAccordionItemButton data-testid="atoms-accordion-item-header">
<AccordionItemStateWrapper>
<AccordionItemState>
{({ expanded }) => (
<>
<StyledIcon
color={theme.content.main}
icon="arrowToggle"
size={16}
open={!!expanded}
/>
{heading}
</>
)}
</AccordionItemState>
</AccordionItemStateWrapper>
</StyledAccordionItemButton>
</AccordionItemHeading>
<AccordionItemPanel data-testid="atoms-accordion-item-content">
{content}
</AccordionItemPanel>
</AccordionItemComponent>
</Wrapper>
);
};

const Wrapper = styled.div<{ bg?: string }>`
margin: ${({ theme }) => theme.metrics["2xl"]}px 0;
background-color: ${({ bg }) => bg};
border-radius: ${({ theme }) => theme.metrics["l"]}px;
`;

const AccordionItemStateWrapper = styled.div`
display: flex;
align-items: center;
padding: ${({ theme }) => theme.metrics["xl"]}px;
`;

const StyledIcon = styled(Icon)<{ open: boolean }>`
transition: transform 0.15s ease;
transform: ${({ open }) => open && "translateY(10%) rotate(90deg)"};
margin-right: 24px;
`;

const StyledAccordionItemButton = styled(AccordionItemButton)`
outline: none;
cursor: pointer;
`;
export default AccordionItem;
36 changes: 36 additions & 0 deletions web/src/beta/components/Accordion/index.stories.tsx
@@ -0,0 +1,36 @@
import { Meta, StoryObj } from "@storybook/react";

import Accordion from ".";

const meta: Meta<typeof Accordion> = {
component: Accordion,
};

export default meta;

type Story = StoryObj<typeof Accordion>;

const SampleHeading = <div style={{ color: "white", background: "black" }}>heading</div>;

const SampleContent = (
<div style={{ color: "white", background: "black", padding: "20px" }}>hoge</div>
);

export const Default: Story = {
render: () => (
<Accordion
items={[
{
id: "hoge",
heading: SampleHeading,
content: SampleContent,
},
{
id: "hogefuga",
heading: SampleHeading,
content: SampleContent,
},
]}
/>
),
};
42 changes: 42 additions & 0 deletions web/src/beta/components/Accordion/index.test.tsx
@@ -0,0 +1,42 @@
import { expect, test } from "vitest";

import { fireEvent, render, screen } from "@reearth/test/utils";

import Accordion, { AccordionItemType } from "./index";

const sampleContents: AccordionItemType[] = [
{
id: "1",
heading: <div>This is heading1</div>,
content: <div>This is content1</div>,
},
{
id: "2",
heading: <div>This is heading2</div>,
content: <div>This is content2</div>,
},
];

test("should be rendered", () => {
render(<Accordion items={sampleContents} />);
});

test("should display items header", () => {
render(<Accordion items={sampleContents} />);
expect(screen.getByTestId("atoms-accordion")).toBeInTheDocument();
expect(screen.getByText(/heading1/)).toBeInTheDocument();
expect(screen.getByText(/heading2/)).toBeInTheDocument();
});

test("should display items content", () => {
render(<Accordion items={sampleContents} />);
expect(screen.getByText(/content1/)).toBeInTheDocument();
expect(screen.getByText(/content2/)).toBeInTheDocument();
});

test("should open when header button is clicked", () => {
render(<Accordion items={sampleContents} />);
expect(screen.getAllByTestId("atoms-accordion-item-content")[0]).not.toBeVisible();
fireEvent.click(screen.getAllByTestId("atoms-accordion-item-header")[0]);
expect(screen.getAllByTestId("atoms-accordion-item-content")[0]).toBeVisible();
});
47 changes: 47 additions & 0 deletions web/src/beta/components/Accordion/index.tsx
@@ -0,0 +1,47 @@
import { Accordion as AccordionComponent } from "react-accessible-accordion";

import AccordionItem from "./AccordionItem";

export type Props = {
className?: string;
items?: AccordionItemType[];
allowZeroExpanded?: boolean;
allowMultipleExpanded?: boolean;
itemBgColor?: string;
};

export type AccordionItemType = {
id: string;
heading?: React.ReactNode;
content?: React.ReactNode;
};

const Accordion: React.FC<Props> = ({
className,
items,
allowMultipleExpanded,
allowZeroExpanded = true,
itemBgColor,
}) => {
return (
<AccordionComponent
className={className}
allowZeroExpanded={allowZeroExpanded}
data-testid="atoms-accordion"
allowMultipleExpanded={allowMultipleExpanded}>
{items?.map(i => {
return (
<AccordionItem
key={i.id}
id={i.id}
heading={i.heading}
content={i.content}
bg={itemBgColor}
/>
);
})}
</AccordionComponent>
);
};

export default Accordion;
10 changes: 8 additions & 2 deletions web/src/beta/components/Button/index.tsx
Expand Up @@ -19,6 +19,8 @@ export interface Props {
margin?: string;

onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
onMouseEnter?: () => void;
onMouseLeave?: () => void;
}

const Button: React.FC<Props> = ({
Expand All @@ -32,6 +34,8 @@ const Button: React.FC<Props> = ({
iconPosition = icon ? "left" : undefined,
margin,
onClick,
onMouseEnter,
onMouseLeave,
}) => {
const hasText = useMemo(() => {
return !!text || !!children;
Expand All @@ -57,7 +61,9 @@ const Button: React.FC<Props> = ({
text={hasText}
disabled={disabled}
margin={margin}
onClick={onClick}>
onClick={onClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}>
{iconPosition === "left" && WrappedIcon}
{size === "medium" ? (
<Text size="body" customColor>
Expand Down Expand Up @@ -120,7 +126,7 @@ const StyledButton = styled.button<ButtonProps>`
size === "medium"
? `${metricsSizes["s"]}px ${metricsSizes["l"]}px`
: `${metricsSizes["xs"]}px ${metricsSizes["s"]}px`};
margin: ${({ margin }) => margin || `${metricsSizes["m"]}px`};
margin: ${({ margin }) => margin};
user-select: none;
cursor: ${({ disabled }) => (disabled ? "not-allowed" : "pointer")};
justify-content: center;
Expand Down
26 changes: 26 additions & 0 deletions web/src/beta/components/Collapse/index.stories.tsx
@@ -0,0 +1,26 @@
import { Meta, StoryObj } from "@storybook/react";

import Collapse from ".";

const meta: Meta<typeof Collapse> = {
component: Collapse,
};

export default meta;

type Story = StoryObj<typeof Collapse>;

export const Default: Story = {
args: {
title: "Title",
children: <p>Item</p>,
},
};

export const AlwaysOpen: Story = {
args: {
title: "Title",
alwaysOpen: true,
children: <p>Item</p>,
},
};
59 changes: 59 additions & 0 deletions web/src/beta/components/Collapse/index.tsx
@@ -0,0 +1,59 @@
import { useState, ReactNode, useCallback } from "react";

import { styled, useTheme } from "@reearth/services/theme";

import Icon from "../Icon";
import Text from "../Text";

const Collapse: React.FC<{
title?: string;
alwaysOpen?: boolean;
children?: ReactNode;
}> = ({ title, alwaysOpen, children }) => {
const theme = useTheme();
const [opened, setOpened] = useState(true);
const handleOpen = useCallback(() => {
if (!alwaysOpen) {
setOpened(!opened);
}
}, [alwaysOpen, opened]);

return (
<Field>
{title && (
<Header onClick={handleOpen} clickable={!alwaysOpen}>
<Text size="body" color={theme.content.main}>
{title}
</Text>
{!alwaysOpen && (
<ArrowIcon icon="arrowToggle" size={12} color={theme.content.main} opened={opened} />
)}
</Header>
)}
{opened && children && <Content>{children}</Content>}
</Field>
);
};

const Field = styled.div`
background: ${({ theme }) => theme.bg[1]};
`;

const Header = styled.div<{ clickable?: boolean }>`
display: flex;
justify-content: space-between;
align-items: center;
cursor: ${({ clickable }) => (clickable ? "pointer" : "cursor")};
padding: ${({ theme }) => theme.spacing.normal}px;
`;

const ArrowIcon = styled(Icon)<{ opened: boolean }>`
transform: rotate(${props => (props.opened ? 90 : 180)}deg);
transition: all 0.2s;
`;

const Content = styled.div`
padding: ${({ theme }) => `${theme.spacing.largest}px ${theme.spacing.super}px`};
`;

export default Collapse;
3 changes: 3 additions & 0 deletions web/src/beta/components/Icon/Icons/bin.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions web/src/beta/components/Icon/Icons/checkCircle.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions web/src/beta/components/Icon/Icons/install.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions web/src/beta/components/Icon/Icons/marketplace.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions web/src/beta/components/Icon/Icons/publicGitHubRepo.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit d422944

Please sign in to comment.