diff --git a/web/src/beta/lib/reearth-ui/components/Tabs/index.stories.tsx b/web/src/beta/lib/reearth-ui/components/Tabs/index.stories.tsx new file mode 100644 index 0000000000..574108bc76 --- /dev/null +++ b/web/src/beta/lib/reearth-ui/components/Tabs/index.stories.tsx @@ -0,0 +1,112 @@ +import { Meta, StoryObj } from "@storybook/react"; +import { FC, useCallback, useState } from "react"; + +import { TabItems, Tabs as TabsMenu, TabsProps } from "."; + +const meta: Meta = { + component: TabsMenu, +}; + +export default meta; + +type Story = StoryObj; + +const Tabs: FC = ({ position, tabs, tabStyle }) => { + const [activeTab, setActiveTab] = useState("tab1"); + const handleTabChange = useCallback((newTab: string) => { + setActiveTab(newTab); + }, []); + + return ( + + ); +}; + +const tabsItem: TabItems[] = [ + { + id: "tab1", + name: "Tab One", + children: ( +
+

Here is tab one content

+

Here is tab one content

+
+ ), + }, + { + id: "tab2", + name: "Tab Two", + icon: "editor", + children:
This is tab two content
, + }, + { + id: "tab3", + name: "Tab Three", + children:
Content for Tab 3
, + }, +]; + +const tabsIcons: TabItems[] = [ + { + id: "tab1", + icon: "data", + children: ( +
+

Here is tab one content

+
+ ), + }, + { + id: "tab2", + icon: "editor", + children:
This is tab two content
, + }, + { + id: "tab3", + icon: "layers", + children:
Content for Tab 3
, + }, +]; + +export const Default: Story = { + render: arg => ( +
+ +
+ ), + args: { + position: "top", + tabs: tabsItem, + }, +}; + +export const LeftSideTabs: Story = { + render: arg => ( +
+ +
+ ), + args: { + position: "left", + tabs: tabsItem, + tabStyle: "separated", + }, +}; + +export const IconTabs: Story = { + render: arg => ( +
+ +
+ ), + args: { + position: "left", + tabs: tabsIcons, + }, +}; diff --git a/web/src/beta/lib/reearth-ui/components/Tabs/index.tsx b/web/src/beta/lib/reearth-ui/components/Tabs/index.tsx new file mode 100644 index 0000000000..bf604a8e39 --- /dev/null +++ b/web/src/beta/lib/reearth-ui/components/Tabs/index.tsx @@ -0,0 +1,102 @@ +import { FC, ReactNode, useMemo } from "react"; + +import { Icon, IconName, Typography } from "@reearth/beta/lib/reearth-ui"; +import { styled, useTheme } from "@reearth/services/theme"; + +export type TabItems = { + id: string; + name?: string; + icon?: IconName; + children?: ReactNode; +}; + +export type TabsProps = { + tabs: TabItems[]; + position?: "top" | "left"; + tabStyle?: "normal" | "separated"; + activeTab?: string; + onChange?: (tab: string) => void; +}; + +export const Tabs: FC = ({ + tabs, + position = "top", + tabStyle = "normal", + activeTab, + onChange, +}) => { + const theme = useTheme(); + const selectedTabItem = useMemo(() => { + return tabs.find(({ id }) => id === activeTab); + }, [activeTab, tabs]); + + return ( + + + {tabs.map(({ id, icon, name }) => ( + onChange?.(id)} + selected={id === activeTab} + position={position} + tabStyle={tabStyle}> + {icon && ( + + )} + {name && ( + + {name} + + )} + + ))} + + {selectedTabItem ? selectedTabItem.children : null} + + ); +}; + +const Wrapper = styled("div")<{ position?: "top" | "left" }>(({ position, theme }) => ({ + display: "flex", + flexFlow: position === "top" ? "column nowrap" : "row nowrap", + background: theme.bg[1], + height: "100%", +})); + +const TabsMenu = styled("div")<{ position?: "top" | "left"; tabStyle?: "normal" | "separated" }>( + ({ position, tabStyle, theme }) => ({ + display: "flex", + flexFlow: position === "top" ? "row nowrap" : "column nowrap", + background: theme.bg[0], + padding: tabStyle === "normal" ? " " : theme.spacing.large, + gap: theme.spacing.micro, + }), +); + +const Tab = styled("div")<{ + position?: "top" | "left"; + selected: boolean; + tabStyle?: "normal" | "separated"; +}>(({ position, selected, tabStyle, theme }) => ({ + display: "flex", + alignItems: "center", + cursor: "pointer", + gap: theme.spacing.smallest, + background: selected ? theme.bg[1] : "inherit", + padding: `${theme.spacing.smallest}px ${theme.spacing.small}px`, + borderRadius: tabStyle === "separated" ? theme.radius.small : 0, + borderTopRightRadius: position === "top" && tabStyle === "normal" ? theme.radius.small : "", + borderTopLeftRadius: tabStyle === "normal" ? theme.radius.small : "", + borderBottomLeftRadius: position === "left" && tabStyle === "normal" ? theme.radius.small : "", +})); + +const Content = styled("div")(({ theme }) => ({ + padding: theme.spacing.normal, + height: "auto", +}));