menu stories

This commit is contained in:
Red Adaya 2024-10-04 21:38:02 +08:00
parent 4b8520b9fa
commit 6a41e3c0a8
6 changed files with 50 additions and 60 deletions

View File

@ -56,7 +56,7 @@ const items = [
];
const meta = {
title: "Elements/Menu",
title: "Elements/ContextMenu",
component: ContextMenu,
args: {
items: [],

View File

@ -1,35 +1,35 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
.list {
.menu {
list-style: none;
padding: 0;
}
.list-item {
.menu-item {
padding: 10px;
cursor: pointer;
user-select: none;
padding: 0;
}
.list-item-button {
.menu-item-button {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.list-item-content {
.menu-item-content {
display: flex;
align-items: center;
}
.list-item-icon {
.menu-item-icon {
margin-right: 10px; /* Space between icon and text */
}
.list-item-text {
.menu-item-text {
text-decoration: none;
}
@ -46,7 +46,7 @@
display: none;
}
.list-item-button {
.menu-item-button {
padding: 10px;
color: var(--main-text-color);
@ -56,6 +56,6 @@
}
}
.list-item-button.clickable:hover {
.menu-item-button.clickable:hover {
background-color: #f0f0f0;
}

View File

@ -3,13 +3,13 @@
import { Meta, StoryObj } from "@storybook/react";
import { Avatar } from "./avatar";
import { List } from "./list";
import { Menu } from "./menu";
import "./list.less";
import "./menu.less";
const meta: Meta<typeof List> = {
title: "Elements/List",
component: List,
const meta: Meta<typeof Menu> = {
title: "Elements/Menu",
component: Menu,
argTypes: {
items: { control: "object" },
renderItem: { control: false },
@ -145,7 +145,7 @@ export const Default: Story = {
},
render: (args) => (
<Container>
<List {...args} />
<Menu {...args} />
</Container>
),
};
@ -156,7 +156,7 @@ export const NestedList: Story = {
},
render: (args) => (
<Container>
<List {...args} />
<Menu {...args} />
</Container>
),
};
@ -168,7 +168,7 @@ export const CustomRender: Story = {
},
render: (args) => (
<Container>
<List {...args} />
<Menu {...args} />
</Container>
),
};
@ -179,7 +179,7 @@ export const WithClickHandlers: Story = {
},
render: (args) => (
<Container>
<List {...args} />
<Menu {...args} />
</Container>
),
};
@ -190,7 +190,7 @@ export const NestedWithClickHandlers: Story = {
},
render: (args) => (
<Container>
<List {...args} />
<Menu {...args} />
</Container>
),
};
@ -224,7 +224,7 @@ export const WithAvatars: Story = {
},
render: (args) => (
<Container>
<List {...args} />
<Menu {...args} />
</Container>
),
};

View File

@ -3,48 +3,48 @@
import clsx from "clsx";
import React, { memo, useState } from "react";
import "./list.less";
import "./menu.less";
interface ListItem {
interface MenuItem {
text: string;
icon?: React.ReactNode;
children?: ListItem[];
children?: MenuItem[];
onClick?: () => void;
}
interface ListProps {
items: ListItem[];
interface MenuProps {
items: MenuItem[];
className?: string;
renderItem?: (item: ListItem, isOpen: boolean, handleClick: () => void) => React.ReactNode;
renderItem?: (item: MenuItem, isOpen: boolean, handleClick: () => void) => React.ReactNode;
}
const List = memo(({ items, className, renderItem }: ListProps) => {
const Menu = memo(({ items, className, renderItem }: MenuProps) => {
const [open, setOpen] = useState<{ [key: string]: boolean }>({});
// Helper function to generate a unique key for each item based on its path in the hierarchy
const getItemKey = (item: ListItem, path: string) => `${path}-${item.text}`;
const getItemKey = (item: MenuItem, path: string) => `${path}-${item.text}`;
const handleClick = (item: ListItem, itemKey: string) => {
const handleClick = (item: MenuItem, itemKey: string) => {
setOpen((prevState) => ({ ...prevState, [itemKey]: !prevState[itemKey] }));
if (item.onClick) {
item.onClick();
}
};
const renderListItem = (item: ListItem, index: number, path: string) => {
const itemKey = getItemKey(item, path); // Generate unique key based on the path
const renderListItem = (item: MenuItem, index: number, path: string) => {
const itemKey = getItemKey(item, path);
const isOpen = open[itemKey] === true;
const hasChildren = item.children && item.children.length > 0;
return (
<li key={itemKey} className={clsx("list-item", className)}>
<li key={itemKey} className={clsx("menu-item", className)}>
{renderItem ? (
renderItem(item, isOpen, () => handleClick(item, itemKey))
) : (
<div className="list-item-button" onClick={() => handleClick(item, itemKey)}>
<div className="list-item-content">
<div className="list-item-icon">{item.icon}</div>
<div className="list-item-text">{item.text}</div>
<div className="menu-item-button" onClick={() => handleClick(item, itemKey)}>
<div className="menu-item-content">
<div className="menu-item-icon">{item.icon}</div>
<div className="menu-item-text">{item.text}</div>
</div>
{hasChildren && (
<i className={`fa-sharp fa-solid ${isOpen ? "fa-angle-up" : "fa-angle-down"}`}></i>
@ -62,9 +62,9 @@ const List = memo(({ items, className, renderItem }: ListProps) => {
);
};
return <ul className="list">{items.map((item, index) => renderListItem(item, index, "root"))}</ul>;
return <ul className="menu">{items.map((item, index) => renderListItem(item, index, "root"))}</ul>;
});
List.displayName = "List";
Menu.displayName = "Menu";
export { List };
export { Menu };

View File

@ -1,16 +1,16 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import { Avatar } from "@/app/element/avatar"
import { List } from "@/app/element/list"
import { List } from "@/app/element/menu"
import { ChatItem } from "./chatitem"
import type { Channel, Message, User } from "./data"
import type { MessageListItem, UserListItem } from "./data"
import "./Layout.css";
interface ChatViewProps {
channels: Channel[];
users: User[];
messages: Message[];
channels: ListItem[];
users: UserListItem[];
messages: MessageListItem[];
}
const ChatView = ({ channels, users, messages }: ChatViewProps) => {
@ -33,7 +33,7 @@ const ChatView = ({ channels, users, messages }: ChatViewProps) => {
))}
<List items={channels}></List>
<div className="chat-section">
<List items={messages} renderItem={<ChatItem />}></List>
<List items={messages} renderItem={(props) => <ChatItem {...props} />}></List>
</div>
</div>
);

View File

@ -2,14 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import { Avatar } from "@/app/element/avatar";
export type Channel = {
text: string;
icon: JSX.Element;
onClick: () => void;
children?: Channel[];
};
export const channels: Channel[] = [
export const channels: ListItem[] = [
{
text: "Channel 1",
icon: <i className="fa-sharp fa-solid fa-wave"></i>,
@ -246,13 +239,11 @@ export const channels: Channel[] = [
},
];
export type User = {
name: string;
export type UserListItem = ListItem & {
status: "online" | "busy" | "away" | "offline";
onClick: () => void;
};
export const users = [
export const users: UserListItem[] = [
{
text: "John Doe",
status: "online",
@ -279,12 +270,11 @@ export const users = [
},
];
export type Message = {
text: string;
export type MessageListItem = ListItem & {
timestamp: string;
};
export const messages: Message[] = [
export const messages: MessageListItem[] = [
{ text: "Message 1 content", timestamp: "2024-09-24 17:02:12" },
{ text: "Message 2 content", timestamp: "2024-07-11 04:17:12" },
{ text: "Message 3 content", timestamp: "2024-07-30 15:32:12" },