This commit is contained in:
Red Adaya 2024-10-01 21:03:24 +08:00
parent 87255cf688
commit af175f68e2
4 changed files with 118 additions and 36 deletions

View File

@ -9,22 +9,28 @@
.list-item {
padding: 10px;
cursor: pointer;
user-select: none;
padding: 0;
}
.list-item-button {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.list-item-button.clickable {
&:hover {
background-color: #f0f0f0;
}
.list-item-content {
display: flex;
align-items: center;
}
.list-item-icon {
margin-right: 10px;
margin-right: 10px; /* Space between icon and text */
}
.list-item-text {
text-decoration: none;
}
.nested-list {
@ -39,3 +45,18 @@
.nested-list.closed {
display: none;
}
.list-item-button {
padding: 10px;
color: var(--main-text-color);
&:hover {
background-color: var(--accent-color);
color: var(--button-text-color);
border-radius: 4px;
}
}
.list-item-button.clickable:hover {
background-color: #f0f0f0;
}

View File

@ -1,3 +1,6 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import { Meta, StoryObj } from "@storybook/react";
import { List } from "./list";
import "./list.less";
@ -8,36 +11,77 @@ const meta: Meta<typeof List> = {
argTypes: {
items: { control: "object" },
renderItem: { control: false },
onClick: { control: false },
},
};
export default meta;
type Story = StoryObj<typeof meta>;
// Container style for limiting the width to 360px
const Container = (props: any) => (
<div
style={{ width: "360px", margin: "0 auto", border: "1px solid #ccc", padding: "10px", boxSizing: "border-box" }}
>
{props.children}
</div>
);
const basicItems = [
{ text: "Inbox", icon: <i className="fa-sharp fa-solid fa-inbox"></i>, link: "/inbox" },
{ text: "Sent Mail", icon: <i className="fa-sharp fa-solid fa-paper-plane"></i>, link: "/sent" },
{ text: "Drafts", icon: <i className="fa-sharp fa-solid fa-drafting-compass"></i>, link: "/drafts" },
{
text: "Inbox",
icon: <i className="fa-sharp fa-solid fa-inbox"></i>,
onClick: () => console.log("Inbox clicked"),
},
{
text: "Sent Mail",
icon: <i className="fa-sharp fa-solid fa-paper-plane"></i>,
onClick: () => console.log("Sent Mail clicked"),
},
{
text: "Drafts",
icon: <i className="fa-sharp fa-solid fa-drafting-compass"></i>,
onClick: () => console.log("Drafts clicked"),
},
];
const nestedItems = [
{
text: "Inbox",
icon: <i className="fa-sharp fa-solid fa-inbox"></i>,
onClick: () => console.log("Inbox clicked"),
children: [
{ text: "Starred", icon: <i className="fa-sharp fa-solid fa-star"></i> },
{ text: "Important", icon: <i className="fa-sharp fa-solid fa-star"></i> },
{
text: "Starred",
icon: <i className="fa-sharp fa-solid fa-star"></i>,
onClick: () => console.log("Starred clicked"),
},
{
text: "Important",
icon: <i className="fa-sharp fa-solid fa-star"></i>,
onClick: () => console.log("Important clicked"),
},
],
},
{ text: "Sent Mail", icon: <i className="fa-sharp fa-solid fa-paper-plane"></i>, link: "/sent" },
{ text: "Drafts", icon: <i className="fa-sharp fa-solid fa-drafting-compass"></i>, link: "/drafts" },
{
text: "Sent Mail",
icon: <i className="fa-sharp fa-solid fa-paper-plane"></i>,
onClick: () => console.log("Sent Mail clicked"),
},
{
text: "Drafts",
icon: <i className="fa-sharp fa-solid fa-drafting-compass"></i>,
onClick: () => console.log("Drafts clicked"),
},
];
const customRenderItem = (item: any, isOpen: boolean, handleClick: () => void) => (
<div className="custom-list-item" onClick={handleClick}>
<span className="custom-list-item-icon">{item.icon}</span>
<span className="custom-list-item-text">{item.link ? <a href={item.link}>{item.text}</a> : item.text}</span>
<div className="custom-list-item">
<span className="custom-list-item-icon" onClick={handleClick}>
{item.icon}
</span>
<span className="custom-list-item-text" onClick={handleClick}>
{item.text}
</span>
{item.children && <i className={`fa-sharp fa-solid ${isOpen ? "fa-angle-up" : "fa-angle-down"}`}></i>}
</div>
);
@ -46,12 +90,22 @@ export const Default: Story = {
args: {
items: basicItems,
},
render: (args) => (
<Container>
<List {...args} />
</Container>
),
};
export const NestedList: Story = {
args: {
items: nestedItems,
},
render: (args) => (
<Container>
<List {...args} />
</Container>
),
};
export const CustomRender: Story = {
@ -59,18 +113,31 @@ export const CustomRender: Story = {
items: nestedItems,
renderItem: customRenderItem,
},
render: (args) => (
<Container>
<List {...args} />
</Container>
),
};
export const WithClickHandlers: Story = {
args: {
items: basicItems,
onClick: (item) => alert(`Item clicked: ${item.text}`),
},
render: (args) => (
<Container>
<List {...args} />
</Container>
),
};
export const NestedWithClickHandlers: Story = {
args: {
items: nestedItems,
onClick: (item) => alert(`Item clicked: ${item.text}`),
},
render: (args) => (
<Container>
<List {...args} />
</Container>
),
};

View File

@ -1,30 +1,30 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import clsx from "clsx";
import React, { useState } from "react";
import "./list.less"; // Assuming you have your LESS styles
import "./list.less";
interface ListItem {
text: string;
icon: React.ReactNode;
link?: string;
children?: ListItem[];
onClick?: () => void;
}
interface ListProps {
items: ListItem[];
className?: string;
renderItem?: (item: ListItem, isOpen: boolean, handleClick: () => void) => React.ReactNode;
onClick?: (item: ListItem) => void;
}
const List = ({ items, renderItem, onClick }: ListProps) => {
const List = ({ items, className, renderItem }: ListProps) => {
const [open, setOpen] = useState<{ [key: string]: boolean }>({});
const handleClick = (item: ListItem) => {
setOpen((prevState) => ({ ...prevState, [item.text]: !prevState[item.text] }));
if (onClick) {
onClick(item); // Notify the consumer that the item was clicked
if (item.onClick) {
item.onClick();
}
};
@ -33,15 +33,15 @@ const List = ({ items, renderItem, onClick }: ListProps) => {
const hasChildren = item.children && item.children.length > 0;
return (
<li key={index} className="list-item">
<li key={index} className={clsx("list-item", className)}>
{renderItem ? (
renderItem(item, isOpen, () => handleClick(item))
) : (
<div className="list-item-button" onClick={() => handleClick(item)}>
<span className="list-item-icon">{item.icon}</span>
<span className="list-item-text">
{item.link ? <a href={item.link}>{item.text}</a> : item.text}
</span>
<div className="list-item-content">
<div className="list-item-icon">{item.icon}</div>
<div className="list-item-text">{item.text}</div>
</div>
{hasChildren && (
<i className={`fa-sharp fa-solid ${isOpen ? "fa-angle-up" : "fa-angle-down"}`}></i>
)}

View File

@ -147,7 +147,6 @@ export const DefaultRendererLeftPositioned: Story = {
},
args: {
items: items,
isVisible: false,
},
};
@ -209,7 +208,6 @@ export const DefaultRendererRightPositioned: Story = {
},
args: {
items: items,
isVisible: false,
},
};
@ -271,7 +269,6 @@ export const DefaultRendererBottomRightPositioned: Story = {
},
args: {
items: items,
isVisible: false,
},
};
@ -333,7 +330,6 @@ export const DefaultRendererBottomLeftPositioned: Story = {
},
args: {
items: items,
isVisible: false,
},
};
@ -406,7 +402,6 @@ export const CustomRenderer: Story = {
},
args: {
items: items,
isVisible: false,
},
};
@ -472,6 +467,5 @@ export const ContextMenu: Story = {
},
args: {
items: items,
isVisible: false,
},
};