Skip to content
This repository has been archived by the owner on Apr 25, 2023. It is now read-only.

Commit

Permalink
feat: tagging of layers (#144)
Browse files Browse the repository at this point in the history
Co-authored-by: rot1024 <aayhrot@gmail.com>
Co-authored-by: KaWaite <34051327+KaWaite@users.noreply.github.com>
  • Loading branch information
3 people committed Dec 17, 2021
1 parent dec1dd1 commit 4d0a401
Show file tree
Hide file tree
Showing 45 changed files with 2,727 additions and 409 deletions.
3 changes: 0 additions & 3 deletions .eslintrc.yml
Expand Up @@ -102,9 +102,6 @@ overrides:
plugins:
- graphql
rules:
graphql/template-strings:
- error
- env: apollo
graphql/capitalized-type-name:
- error
- env: apollo
Expand Down
43 changes: 43 additions & 0 deletions src/components/atoms/AutoComplete/index.stories.tsx
@@ -0,0 +1,43 @@
import { Meta, Story } from "@storybook/react";
import React from "react";

import AutoComplete, { Props } from ".";

export default {
title: "atoms/AutoComplete",
component: AutoComplete,
} as Meta;

const sampleItems: { value: string; label: string }[] = [
{
value: "hoge",
label: "hoge",
},
{
value: "fuga",
label: "fuga",
},
];

const addItem = (value: string) => {
sampleItems.push({ value, label: value });
};

const handleSelect = (value: string) => {
console.log("select ", value);
};

const handleCreate = (value: string) => {
console.log("create ", value);
addItem(value);
};

export const Default: Story<Props<string>> = () => {
return <AutoComplete items={sampleItems} onCreate={handleCreate} onSelect={handleSelect} />;
};

export const Creatable: Story<Props<string>> = () => {
return (
<AutoComplete items={sampleItems} onCreate={handleCreate} onSelect={handleSelect} creatable />
);
};
83 changes: 83 additions & 0 deletions src/components/atoms/AutoComplete/index.test.tsx
@@ -0,0 +1,83 @@
/* eslint-disable testing-library/no-wait-for-multiple-assertions */
/* eslint-disable testing-library/no-unnecessary-act */
import React from "react";

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

import AutoComplete from "./index";

const sampleItems: { value: string; label: string }[] = [
{
value: "hoge",
label: "hoge",
},
{
value: "fuga",
label: "fuga",
},
];

test("component should be renered", async () => {
await act(async () => {
render(<AutoComplete />);
});
});

test("component should render items", async () => {
await act(async () => {
render(<AutoComplete items={sampleItems} />);
});
expect(screen.getByText(/hoge/)).toBeInTheDocument();
expect(screen.getByText(/fuga/)).toBeInTheDocument();
});

test("component should be inputtable", async () => {
await act(async () => {
render(<AutoComplete items={sampleItems} />);
});

const input = screen.getByRole("textbox");
fireEvent.change(input, { target: { value: "hoge" } });
expect(screen.getByText("hoge")).toBeInTheDocument();
});

describe("Ccomponent should be searchable", () => {
test("component should leave selects hit", async () => {
await act(async () => {
render(<AutoComplete items={sampleItems} />);
const input = screen.getByRole("textbox");
fireEvent.change(input, { target: { value: "hoge" } });
expect(screen.getByText("hoge")).toBeInTheDocument();
});
});

test("component shouldn't leave selects which don't hit inputted text", async () => {
await act(async () => {
render(<AutoComplete items={sampleItems} />);
const input = screen.getByRole("textbox");
fireEvent.change(input, { target: { value: "hoge" } });
await waitFor(() => {
expect(screen.queryByText("fuga")).not.toBeInTheDocument();
});
});
});

test("component should trigger onSelect function with click event", async () => {
await act(async () => {
const handleSelect = jest.fn((value: string) => {
console.log(value);
});
render(<AutoComplete items={sampleItems} onSelect={handleSelect} />);
const input = screen.getByRole("textbox");
await act(async () => {
fireEvent.change(input, { target: { value: "hoge" } });
const option = screen.getByText(/hoge/);
fireEvent.click(option);
});
await waitFor(() => {
expect(handleSelect).toBeCalled();
expect(handleSelect.mock.calls[0][0]).toBe("hoge");
});
});
});
});
106 changes: 106 additions & 0 deletions src/components/atoms/AutoComplete/index.tsx
@@ -0,0 +1,106 @@
import React, { useCallback, useEffect, useRef, useState } from "react";

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

import Icon from "../Icon";
import SelectCore from "../Select/core";
import { Option, OptionIcon } from "../SelectOption";

export type Item<Value extends string | number = string> = {
value: Value;
label: string;
icon?: string;
};

export type Props<Value extends string | number> = {
className?: string;
placeholder?: string;
items?: Item<Value>[];
fullWidth?: boolean;
creatable?: boolean;
onCreate?: (value: Value) => void;
onSelect?: (value: Value) => void;
};

function AutoComplete<Value extends string | number>({
className,
placeholder,
items,
fullWidth = false,
creatable,
onCreate,
onSelect,
}: Props<Value>): JSX.Element | null {
const [filterText, setFilterText] = useState("");
const [itemState, setItems] = useState<Item<Value>[]>(items ?? []);
useEffect(() => {
setItems(items?.filter(i => i.label.includes(filterText)) ?? []);
}, [filterText, items]);
const ref = useRef<HTMLDivElement>(null);

const handleInputChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setFilterText?.(e.currentTarget.value);
},
[setFilterText],
);

const isValueType = useCallback((value: any): value is Value => {
return !!value;
}, []);

const handleSelect = useCallback(
(value: Value) => {
itemState.length ? onSelect?.(value) : creatable && onCreate?.(value);
setFilterText("");
},
[creatable, itemState.length, onCreate, onSelect],
);
const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
e.preventDefault();
if (!isValueType(filterText)) return;
handleSelect(filterText);
}
},
[filterText, handleSelect, isValueType],
);

return (
<SelectCore
className={className}
fullWidth={fullWidth}
selectComponent={
<StyledTextBox
value={filterText}
onChange={handleInputChange}
onKeyDown={handleKeyDown}
placeholder={placeholder}
/>
}
onChange={handleSelect}
ref={ref}
options={itemState?.map(i => {
return (
<Option key={i.value} value={i.value} label={i.label}>
<OptionIcon size="xs">{i.icon && <Icon icon={i.icon} />}</OptionIcon>
{i.label}
</Option>
);
})}></SelectCore>
);
}

const StyledTextBox = styled.input`
outline: none;
width: 100%;
border: none;
background-color: ${({ theme }) => theme.properties.bg};
padding-left: ${metricsSizes.xs}px;
padding-right: ${metricsSizes.xs}px;
caret-color: ${({ theme }) => theme.main.text};
color: ${({ theme }) => theme.main.text};
`;

export default AutoComplete;
4 changes: 3 additions & 1 deletion src/components/atoms/Flex/index.tsx
Expand Up @@ -4,6 +4,7 @@ export type Props = {
className?: string;
onClick?: () => void;
children?: React.ReactNode;
testId?: string;
} & FlexOptions;

export type FlexOptions = {
Expand All @@ -22,6 +23,7 @@ const Flex: React.FC<Props> = ({
className,
onClick,
children,
testId,
align,
justify,
wrap,
Expand All @@ -45,7 +47,7 @@ const Flex: React.FC<Props> = ({
gap: gap, // TODO: Safari doesn't support this property and please develop polyfill
};
return (
<div className={className} style={styles} onClick={onClick}>
<div className={className} style={styles} onClick={onClick} data-testid={testId}>
{children}
</div>
);
Expand Down
4 changes: 4 additions & 0 deletions src/components/atoms/Icon/Icons/tag.svg
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 src/components/atoms/Icon/icons.ts
Expand Up @@ -121,6 +121,7 @@ import NoProjects from "./Icons/noProjects.svg";
import MenuForDevice from "./Icons/menuForDevice.svg";
import Moon from "./Icons/moon.svg";
import Sun from "./Icons/sun.svg";
import Tag from "./Icons/tag.svg";

// Plug-ins
import Plugin from "./Icons/plugin.svg";
Expand Down Expand Up @@ -236,4 +237,5 @@ export default {
publicGitHubRepo: PublicGitHubRepo,
menuForDevice: MenuForDevice,
plugin: Plugin,
tag: Tag,
};

0 comments on commit 4d0a401

Please sign in to comment.