Skip to content

Commit 5ecb285

Browse files
authored
menu (#846)
1 parent 11126a4 commit 5ecb285

File tree

10 files changed

+1129
-35
lines changed

10 files changed

+1129
-35
lines changed

.storybook/global.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
body {
22
height: 100vh;
33
padding: 0;
4+
overflow: auto;
45
}
56

67
#storybook-root {

.storybook/preview-head.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<link rel="stylesheet" href="/fontawesome/css/fontawesome.min.css" />
2+
<link rel="stylesheet" href="/fontawesome/css/brands.min.css" />
3+
<link rel="stylesheet" href="/fontawesome/css/solid.min.css" />
4+
<link rel="stylesheet" href="/fontawesome/css/sharp-solid.min.css" />
5+
<link rel="stylesheet" href="/fontawesome/css/sharp-regular.min.css" />

.storybook/preview.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import type { Preview } from "@storybook/react";
33
import React from "react";
44
import { DndProvider } from "react-dnd";
55
import { HTML5Backend } from "react-dnd-html5-backend";
6+
import "../frontend/app/theme.less";
7+
import "../frontend/app/app.less";
8+
import "../frontend/app/reset.less";
69
import "./global.css";
710

811
const preview: Preview = {

frontend/app/element/button.less

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// override default button appearance
66
border: 1px solid transparent;
77
outline: 1px solid transparent;
8-
border: none;
8+
border: 1px solid transparent;
99

1010
cursor: pointer;
1111
display: flex;
@@ -150,8 +150,7 @@
150150
pointer-events: none;
151151
}
152152

153-
&:focus,
154-
&.focus {
153+
&:focus-visible {
155154
outline: 1px solid var(--success-color);
156155
outline-offset: 2px;
157156
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Copyright 2024, Command Line Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import type { Meta, StoryObj } from "@storybook/react";
5+
import { fn } from "@storybook/test";
6+
import { Button } from "./button";
7+
8+
const meta = {
9+
title: "Elements/Button",
10+
component: Button,
11+
args: {
12+
children: "Click Me",
13+
disabled: false,
14+
className: "",
15+
onClick: fn(),
16+
},
17+
argTypes: {
18+
onClick: {
19+
action: "clicked",
20+
description: "Click event handler",
21+
},
22+
children: {
23+
description: "Content inside the button",
24+
},
25+
disabled: {
26+
description: "Disables the button if true",
27+
},
28+
className: {
29+
description: "Additional class names to style the button",
30+
},
31+
},
32+
} satisfies Meta<typeof Button>;
33+
34+
export default meta;
35+
type Story = StoryObj<typeof meta>;
36+
37+
export const Default: Story = {};
38+
39+
export const Disabled: Story = {
40+
args: {
41+
disabled: true,
42+
children: "Disabled Button",
43+
},
44+
};
45+
46+
export const GreySolid: Story = {
47+
args: {
48+
className: "solid grey",
49+
children: "Grey Solid Button",
50+
},
51+
};
52+
53+
export const RedSolid: Story = {
54+
args: {
55+
className: "solid red",
56+
children: "Red Solid Button",
57+
},
58+
};
59+
60+
export const YellowSolid: Story = {
61+
args: {
62+
className: "solid yellow",
63+
children: "Yellow Solid Button",
64+
},
65+
};
66+
67+
export const GreenOutlined: Story = {
68+
args: {
69+
className: "outlined green",
70+
children: "Green Outline Button",
71+
},
72+
};
73+
74+
export const GreyOutlined: Story = {
75+
args: {
76+
className: "outlined grey",
77+
children: "Grey Outline Button",
78+
},
79+
};
80+
81+
export const RedOutlined: Story = {
82+
args: {
83+
className: "outlined red",
84+
children: "Red Outline Button",
85+
},
86+
};
87+
88+
export const YellowOutlined: Story = {
89+
args: {
90+
className: "outlined yellow",
91+
children: "Yellow Outline Button",
92+
},
93+
};
94+
95+
export const GreenGhostText: Story = {
96+
args: {
97+
className: "ghost green",
98+
children: "Yellow Ghost Text Button",
99+
},
100+
};
101+
102+
export const GreyGhostText: Story = {
103+
args: {
104+
className: "ghost grey",
105+
children: "Grey Ghost Text Button",
106+
},
107+
};
108+
109+
export const RedGhost: Story = {
110+
args: {
111+
className: "ghost red",
112+
children: "Red Ghost Text Button",
113+
},
114+
};
115+
116+
export const YellowGhostText: Story = {
117+
args: {
118+
className: "ghost yellow",
119+
children: "Yellow Ghost Text Button",
120+
},
121+
};

frontend/app/element/button.tsx

Lines changed: 30 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,41 +9,39 @@ import "./button.less";
99
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
1010
className?: string;
1111
children?: ReactNode;
12-
target?: string;
13-
source?: string;
1412
}
1513

1614
const Button = memo(
17-
forwardRef<HTMLButtonElement, ButtonProps>(
18-
({ children, disabled, source, className = "", ...props }: ButtonProps, ref) => {
19-
const btnRef = useRef<HTMLButtonElement>(null);
20-
useImperativeHandle(ref, () => btnRef.current as HTMLButtonElement);
21-
22-
const childrenArray = Children.toArray(children);
23-
24-
// Check if the className contains any of the categories: solid, outlined, or ghost
25-
const containsButtonCategory = /(solid|outline|ghost)/.test(className);
26-
// If no category is present, default to 'solid'
27-
const categoryClassName = containsButtonCategory ? className : `solid ${className}`;
28-
29-
// Check if the className contains any of the color options: green, grey, red, or yellow
30-
const containsColor = /(green|grey|red|yellow)/.test(categoryClassName);
31-
// If no color is present, default to 'green'
32-
const finalClassName = containsColor ? categoryClassName : `green ${categoryClassName}`;
33-
34-
return (
35-
<button
36-
ref={btnRef}
37-
tabIndex={disabled ? -1 : 0}
38-
className={clsx("button", finalClassName)}
39-
disabled={disabled}
40-
{...props}
41-
>
42-
{childrenArray}
43-
</button>
44-
);
45-
}
46-
)
15+
forwardRef<HTMLButtonElement, ButtonProps>(({ children, disabled, className = "", ...props }: ButtonProps, ref) => {
16+
const btnRef = useRef<HTMLButtonElement>(null);
17+
useImperativeHandle(ref, () => btnRef.current as HTMLButtonElement);
18+
19+
const childrenArray = Children.toArray(children);
20+
21+
// Check if the className contains any of the categories: solid, outlined, or ghost
22+
const containsButtonCategory = /(solid|outline|ghost)/.test(className);
23+
// If no category is present, default to 'solid'
24+
const categoryClassName = containsButtonCategory ? className : `solid ${className}`;
25+
26+
// Check if the className contains any of the color options: green, grey, red, or yellow
27+
const containsColor = /(green|grey|red|yellow)/.test(categoryClassName);
28+
// If no color is present, default to 'green'
29+
const finalClassName = containsColor ? categoryClassName : `green ${categoryClassName}`;
30+
31+
return (
32+
<button
33+
ref={btnRef}
34+
tabIndex={disabled ? -1 : 0}
35+
className={clsx("button", finalClassName)}
36+
disabled={disabled}
37+
{...props}
38+
>
39+
{childrenArray}
40+
</button>
41+
);
42+
})
4743
);
4844

45+
Button.displayName = "Button";
46+
4947
export { Button };

frontend/app/element/menu.less

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
.menu {
2+
position: absolute;
3+
z-index: 1000; // TODO: put this in theme.less
4+
display: flex;
5+
width: 125px; /* Ensure the menu has a fixed width */
6+
padding: 2px;
7+
flex-direction: column;
8+
justify-content: flex-end;
9+
align-items: flex-start;
10+
gap: 1px;
11+
border-radius: 4px;
12+
border: 1px solid rgba(255, 255, 255, 0.15);
13+
background: #212121;
14+
box-shadow: 0px 8px 24px 0px rgba(0, 0, 0, 0.3);
15+
16+
.menu-item {
17+
padding: 4px 6px;
18+
cursor: pointer;
19+
color: #fff;
20+
font-size: 12px;
21+
font-style: normal;
22+
font-weight: 400;
23+
line-height: normal;
24+
letter-spacing: -0.12px;
25+
width: 100%;
26+
border-radius: 2px;
27+
display: flex;
28+
align-items: center;
29+
justify-content: space-between;
30+
31+
/* Make sure the label and the icon don't overlap */
32+
.label {
33+
flex: 1; /* Allow the label to take up available space */
34+
white-space: nowrap;
35+
overflow: hidden;
36+
text-overflow: ellipsis;
37+
margin-right: 8px; /* Add some space between label and icon */
38+
}
39+
40+
i {
41+
color: var(--main-text-color);
42+
flex-shrink: 0; /* Prevent icon from shrinking */
43+
}
44+
45+
&:hover,
46+
&.active {
47+
background-color: var(--accent-color);
48+
color: var(--button-text-color);
49+
50+
i {
51+
color: var(--button-text-color);
52+
}
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)