Skip to content

menu #846

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
Sep 27, 2024
Merged

menu #846

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f4d7697
button stories
adred Sep 20, 2024
dc0eb91
fix issues
adred Sep 22, 2024
aefefa8
save work
adred Sep 22, 2024
640ffd3
save work
adred Sep 23, 2024
2d3703a
fix hiding/showing issue
adred Sep 23, 2024
e991b61
cleanup
adred Sep 23, 2024
5d2a748
take into acount page and boundary scroll x/y
adred Sep 23, 2024
c2d5f20
hightlight ancestors as well
adred Sep 23, 2024
7ec1a75
cleanup
adred Sep 23, 2024
b99b30e
fixing shadowing issue
adred Sep 24, 2024
785a241
hide dropdown when an item is clicked
adred Sep 24, 2024
3c60757
hide if click outside the menus
adred Sep 24, 2024
72a9864
allow custom rendering of menu/submenu and items
adred Sep 24, 2024
81d1653
scope clickOutside trigger within the block
adred Sep 25, 2024
3c39674
allow showing of mdropdown when only initialPosition is provided
adred Sep 25, 2024
36f3774
cleanup
adred Sep 25, 2024
cc7198a
various improvements
adred Sep 25, 2024
0054a92
use button component as anchor element
adred Sep 25, 2024
129c1f2
renamed to menu
adred Sep 25, 2024
5a95c3c
minor fix
adred Sep 25, 2024
846a9b0
fix merge conflicts
adred Sep 25, 2024
1182a4f
Merge branch 'main' of github.com:wavetermdev/waveterm into red/dropdown
adred Sep 26, 2024
b2c2c19
more stories and improvements
adred Sep 26, 2024
c68184f
add down arrow to buttons
adred Sep 26, 2024
8021cc6
make menu items reusable
adred Sep 26, 2024
dd31f7b
minor fix
adred Sep 26, 2024
87cf71f
menu item with sub items should not be clickable
adred Sep 26, 2024
2588986
initial work on keyboard navigation
adred Sep 26, 2024
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
1 change: 1 addition & 0 deletions .storybook/global.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
body {
height: 100vh;
padding: 0;
overflow: auto;
}

#storybook-root {
Expand Down
5 changes: 5 additions & 0 deletions .storybook/preview-head.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<link rel="stylesheet" href="/fontawesome/css/fontawesome.min.css" />
<link rel="stylesheet" href="/fontawesome/css/brands.min.css" />
<link rel="stylesheet" href="/fontawesome/css/solid.min.css" />
<link rel="stylesheet" href="/fontawesome/css/sharp-solid.min.css" />
<link rel="stylesheet" href="/fontawesome/css/sharp-regular.min.css" />
3 changes: 3 additions & 0 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import type { Preview } from "@storybook/react";
import React from "react";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import "../frontend/app/theme.less";
import "../frontend/app/app.less";
import "../frontend/app/reset.less";
import "./global.css";

const preview: Preview = {
Expand Down
5 changes: 2 additions & 3 deletions frontend/app/element/button.less
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// override default button appearance
border: 1px solid transparent;
outline: 1px solid transparent;
border: none;
border: 1px solid transparent;

cursor: pointer;
display: flex;
Expand Down Expand Up @@ -150,8 +150,7 @@
pointer-events: none;
}

&:focus,
&.focus {
&:focus-visible {
outline: 1px solid var(--success-color);
outline-offset: 2px;
}
Expand Down
121 changes: 121 additions & 0 deletions frontend/app/element/button.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

import type { Meta, StoryObj } from "@storybook/react";
import { fn } from "@storybook/test";
import { Button } from "./button";

const meta = {
title: "Elements/Button",
component: Button,
args: {
children: "Click Me",
disabled: false,
className: "",
onClick: fn(),
},
argTypes: {
onClick: {
action: "clicked",
description: "Click event handler",
},
children: {
description: "Content inside the button",
},
disabled: {
description: "Disables the button if true",
},
className: {
description: "Additional class names to style the button",
},
},
} satisfies Meta<typeof Button>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {};

export const Disabled: Story = {
args: {
disabled: true,
children: "Disabled Button",
},
};

export const GreySolid: Story = {
args: {
className: "solid grey",
children: "Grey Solid Button",
},
};

export const RedSolid: Story = {
args: {
className: "solid red",
children: "Red Solid Button",
},
};

export const YellowSolid: Story = {
args: {
className: "solid yellow",
children: "Yellow Solid Button",
},
};

export const GreenOutlined: Story = {
args: {
className: "outlined green",
children: "Green Outline Button",
},
};

export const GreyOutlined: Story = {
args: {
className: "outlined grey",
children: "Grey Outline Button",
},
};

export const RedOutlined: Story = {
args: {
className: "outlined red",
children: "Red Outline Button",
},
};

export const YellowOutlined: Story = {
args: {
className: "outlined yellow",
children: "Yellow Outline Button",
},
};

export const GreenGhostText: Story = {
args: {
className: "ghost green",
children: "Yellow Ghost Text Button",
},
};

export const GreyGhostText: Story = {
args: {
className: "ghost grey",
children: "Grey Ghost Text Button",
},
};

export const RedGhost: Story = {
args: {
className: "ghost red",
children: "Red Ghost Text Button",
},
};

export const YellowGhostText: Story = {
args: {
className: "ghost yellow",
children: "Yellow Ghost Text Button",
},
};
62 changes: 30 additions & 32 deletions frontend/app/element/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,41 +9,39 @@ import "./button.less";
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
className?: string;
children?: ReactNode;
target?: string;
source?: string;
}

const Button = memo(
forwardRef<HTMLButtonElement, ButtonProps>(
({ children, disabled, source, className = "", ...props }: ButtonProps, ref) => {
const btnRef = useRef<HTMLButtonElement>(null);
useImperativeHandle(ref, () => btnRef.current as HTMLButtonElement);

const childrenArray = Children.toArray(children);

// Check if the className contains any of the categories: solid, outlined, or ghost
const containsButtonCategory = /(solid|outline|ghost)/.test(className);
// If no category is present, default to 'solid'
const categoryClassName = containsButtonCategory ? className : `solid ${className}`;

// Check if the className contains any of the color options: green, grey, red, or yellow
const containsColor = /(green|grey|red|yellow)/.test(categoryClassName);
// If no color is present, default to 'green'
const finalClassName = containsColor ? categoryClassName : `green ${categoryClassName}`;

return (
<button
ref={btnRef}
tabIndex={disabled ? -1 : 0}
className={clsx("button", finalClassName)}
disabled={disabled}
{...props}
>
{childrenArray}
</button>
);
}
)
forwardRef<HTMLButtonElement, ButtonProps>(({ children, disabled, className = "", ...props }: ButtonProps, ref) => {
const btnRef = useRef<HTMLButtonElement>(null);
useImperativeHandle(ref, () => btnRef.current as HTMLButtonElement);

const childrenArray = Children.toArray(children);

// Check if the className contains any of the categories: solid, outlined, or ghost
const containsButtonCategory = /(solid|outline|ghost)/.test(className);
// If no category is present, default to 'solid'
const categoryClassName = containsButtonCategory ? className : `solid ${className}`;

// Check if the className contains any of the color options: green, grey, red, or yellow
const containsColor = /(green|grey|red|yellow)/.test(categoryClassName);
// If no color is present, default to 'green'
const finalClassName = containsColor ? categoryClassName : `green ${categoryClassName}`;

return (
<button
ref={btnRef}
tabIndex={disabled ? -1 : 0}
className={clsx("button", finalClassName)}
disabled={disabled}
{...props}
>
{childrenArray}
</button>
);
})
);

Button.displayName = "Button";

export { Button };
55 changes: 55 additions & 0 deletions frontend/app/element/menu.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
.menu {
position: absolute;
z-index: 1000; // TODO: put this in theme.less
display: flex;
width: 125px; /* Ensure the menu has a fixed width */
padding: 2px;
flex-direction: column;
justify-content: flex-end;
align-items: flex-start;
gap: 1px;
border-radius: 4px;
border: 1px solid rgba(255, 255, 255, 0.15);
background: #212121;
box-shadow: 0px 8px 24px 0px rgba(0, 0, 0, 0.3);

.menu-item {
padding: 4px 6px;
cursor: pointer;
color: #fff;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: normal;
letter-spacing: -0.12px;
width: 100%;
border-radius: 2px;
display: flex;
align-items: center;
justify-content: space-between;

/* Make sure the label and the icon don't overlap */
.label {
flex: 1; /* Allow the label to take up available space */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-right: 8px; /* Add some space between label and icon */
}

i {
color: var(--main-text-color);
flex-shrink: 0; /* Prevent icon from shrinking */
}

&:hover,
&.active {
background-color: var(--accent-color);
color: var(--button-text-color);

i {
color: var(--button-text-color);
}
}
}
}
Loading