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

feat: tagging of layers #144

Merged
merged 40 commits into from
Dec 17, 2021
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
8587739
tag comp
HideBa Nov 18, 2021
b32ed3d
searchable select
HideBa Nov 18, 2021
f16d249
add test, refactor
HideBa Nov 25, 2021
20e7ef5
auto complete component
HideBa Nov 25, 2021
f3901aa
wip: autocomplete comp
HideBa Nov 25, 2021
3588c3e
add test
HideBa Dec 1, 2021
2c130a6
add test
HideBa Dec 2, 2021
8c966d3
tag group
HideBa Dec 2, 2021
f2e451a
tag group
HideBa Dec 2, 2021
db5ad46
wip: tag pane
HideBa Dec 2, 2021
9eea2c4
dev tag pane
HideBa Dec 9, 2021
ebcf70d
add tag tab
HideBa Dec 9, 2021
d6e8fd6
add tag query
HideBa Dec 9, 2021
6f9b22d
imple mutation/query for tag
HideBa Dec 10, 2021
e69d307
Merge branch 'main' of github.com:reearth/reearth-web into tag-system
HideBa Dec 10, 2021
934e0a9
gen gql
HideBa Dec 11, 2021
960d7e9
wip: attach tag
HideBa Dec 14, 2021
28de3ce
wip: split tag pane to layer and scene
HideBa Dec 14, 2021
50e18b2
fix onselect
HideBa Dec 14, 2021
4fb468e
add validation
HideBa Dec 14, 2021
d4961b0
fix attach tag to layer
HideBa Dec 14, 2021
aface70
reduce refetch
HideBa Dec 15, 2021
028ad77
fix select style
HideBa Dec 15, 2021
e1359b5
fix query
HideBa Dec 15, 2021
2267d94
fix dep
HideBa Dec 15, 2021
e12204e
clean up components
HideBa Dec 16, 2021
430d9e8
refactor tag
HideBa Dec 16, 2021
66f7fb5
fix lint
HideBa Dec 16, 2021
bfc0a83
update gql query
HideBa Dec 16, 2021
de43f86
fix layr tag query
HideBa Dec 17, 2021
9384d78
add translation
HideBa Dec 17, 2021
fb47fef
Update src/components/atoms/Select/index.tsx
HideBa Dec 17, 2021
7d17766
Update translations/ja.yml
HideBa Dec 17, 2021
0f8c929
Update src/components/atoms/Icon/Icons/tag.svg
HideBa Dec 17, 2021
25a90f6
Update src/components/atoms/Icon/Icons/tag.svg
HideBa Dec 17, 2021
f3fb206
Update translations/en.yml
HideBa Dec 17, 2021
73dc867
Merge branch 'main' of https://github.com/reearth/reearth-web into ta…
rot1024 Dec 17, 2021
1c7dcf8
disable graphql/template-strings
rot1024 Dec 17, 2021
1316c9a
fix warning
rot1024 Dec 17, 2021
f37b314
refine
HideBa Dec 17, 2021
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
43 changes: 43 additions & 0 deletions src/components/atoms/AutoComplete/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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");
});
});
});
});
99 changes: 99 additions & 0 deletions src/components/atoms/AutoComplete/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
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;
items?: Item<Value>[];
fullWidth?: boolean;
creatable?: boolean;
onCreate?: (value: Value) => void;
onSelect?: (value: Value) => void;
};

function AutoComplete<Value extends string | number>({
className,
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 = (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} />
}
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
Original file line number Diff line number Diff line change
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
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 src/components/atoms/Icon/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,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 @@ -232,4 +233,5 @@ export default {
publicGitHubRepo: PublicGitHubRepo,
menuForDevice: MenuForDevice,
plugin: Plugin,
tag: Tag,
};
Loading