Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
branches: [ "main" ]

jobs:
publish:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
Expand Down
Binary file removed public/example.png
Binary file not shown.
Binary file added public/holder-min.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed public/holder.jpg
Binary file not shown.
Binary file removed public/nature.jpg
Binary file not shown.
31 changes: 10 additions & 21 deletions src/components/Annotator/Annotator.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,23 @@
import React from "react";
import styled from "styled-components";

export type AnnotatorProps = {
id?: string;
primary?: boolean;
};

const Annotator: React.FC<AnnotatorProps> = ({ id, primary, ...props }) => {
const baseClasses = primary
? "text-white bg-red-600"
: "text-black bg-gray-300";
const headerClasses = "bg-gray-800 text-white py-2 text-center";
const containerClasses = "flex h-screen";
const toolbarClasses = "bg-gray-400 w-1/20 p-4";
const canvasContainerClasses = "flex-grow";
const stackMenuClasses = "bg-gray-400 w-1/6 p-4";
const StyledDiv = styled.div``;

const Annotator: React.FC<AnnotatorProps> = ({ id, ...props }) => {
return (
<div
id={id}
role={"annotator"}
className={`w-full ${baseClasses}`}
{...props}
>
<div className={headerClasses}>Header</div>
<div className={containerClasses}>
<div className={toolbarClasses}>Toolbar</div>
<div className={canvasContainerClasses}>CanvasContainer</div>
<div className={stackMenuClasses}>Stack Menu</div>
<StyledDiv id={id} role="annotator" {...props}>
<div>Header</div>
<div>
<div>Toolbar</div>
<div>CanvasContainer</div>
<div>Stack Menu</div>
</div>
</div>
</StyledDiv>
);
};

Expand Down
2 changes: 1 addition & 1 deletion src/components/Board/__docs__/ExampleMain.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const ExampleMain: FC<BoardProps> = ({
editor.canvas.defaultCursor = draggingEnabled ? "pointer" : "default";

fabric.Image.fromURL(
"holder.jpg",
"holder-min.jpg",
(img) => {
const { canvas } = editor;
const scaleRatio = Math.min(
Expand Down
154 changes: 118 additions & 36 deletions src/components/Menu/Menu.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,128 @@
import React, { MouseEventHandler } from "react";
import React, { useState } from "react";
import styled from "styled-components";
import { FiChevronDown, FiChevronUp } from "react-icons/fi"; // Assuming you have react-icons installed
import { AvailableIcons } from "../../utils";
import tokens from "../../tokens";
import { CgUnavailable } from "react-icons/cg";

export type MenuProps = {
text?: string;
primary?: boolean;
disabled?: boolean;
size?: "small" | "medium" | "large";
onClick?: MouseEventHandler<HTMLButtonElement>;
visible?: boolean;
items: {
icon?: keyof typeof AvailableIcons;
title: string;
content: React.ReactNode;
disabled?: boolean;
}[];
};

const Menu: React.FC<MenuProps> = ({
size,
primary,
disabled,
text,
onClick,
...props
}) => {
// Determine button color and background color based on primary prop
const buttonColor = primary ? "text-white" : "text-black";
const bgColor = primary ? "bg-red-600" : "bg-gray-300";

// Determine padding based on size prop
let paddingClass = "";
if (size === "small") {
paddingClass = "py-1 px-6";
} else if (size === "medium") {
paddingClass = "py-2 px-8";
} else {
paddingClass = "py-3 px-8";
}
// Styled components
const MenuContainer = styled.div<Omit<MenuProps, "items">>`
width: 100%;
font-family: "Roboto";
font-style: normal;
visibility: ${(props) => (props.visible ? "visible" : "hidden")};
background-color: ${(props) =>
props.primary
? tokens.primary.backgroundColor
: tokens.secondary.semiBackgroundColor};
color: ${(props) =>
props.primary ? tokens.primary.color : tokens.secondary.color};
`;

const MenuItem = styled.div<Omit<MenuProps, "items">>`
border: 1px solid
${(props) =>
props.primary ? tokens.primary.lightColor : tokens.secondary.lightColor};
border-radius: 5px;
margin: 5px;
background-color: ${(props) =>
props.primary ? undefined : tokens.secondary.activeColor};
`;

const MenuHeader = styled.div<
Omit<MenuProps, "items"> & { disabled?: boolean }
>`
background-color: ${(props) =>
props.primary
? tokens.primary.semiBackgroundColor
: tokens.secondary.backgroundColor};

padding: 10px;
border-radius: 5px;
cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")};
display: flex;
justify-content: space-between;
align-items: center;
`;

const MenuTitle = styled.span<Omit<MenuProps, "items">>`
display: flex;
gap: 10px;
font-weight: 600;
`;

const MenuIcon = styled.div`
display: "flex";
align-items: "center";
justify-content: "center";
`;

const MenuContent = styled.div<Omit<MenuProps, "items"> & { isOpen: boolean }>`
padding: 10px;
display: ${({ isOpen }) => (isOpen ? "block" : "none")};
color: ${(props) =>
props.primary ? tokens.primary.color : tokens.secondary.color};
`;

const Menu: React.FC<MenuProps> = ({ primary, visible = true, items }) => {
const [openIndexes, setOpenIndexes] = useState<number[]>([]);

const toggleMenu = (index: number) => {
const currentIndex = openIndexes.indexOf(index);
const newOpenIndexes = [...openIndexes];
if (currentIndex === -1) {
newOpenIndexes.push(index);
} else {
newOpenIndexes.splice(currentIndex, 1);
}
setOpenIndexes(newOpenIndexes);
};

return (
<button
role="menu"
type="button"
onClick={onClick}
disabled={disabled}
className={`border-0 font-semibold rounded-lg inline-block cursor-pointer ${buttonColor} ${bgColor} ${paddingClass}`}
{...props}
>
{text}
</button>
<MenuContainer primary={primary} visible={visible} role="menu">
{items.map(({ icon, title, content, disabled }, index) => {
const DynamicIcon = AvailableIcons[icon ?? "rectangle"];
return (
<MenuItem primary={primary} key={index}>
<MenuHeader
primary={primary}
disabled={!!disabled}
onClick={() => !disabled && toggleMenu(index)}
>
<MenuTitle primary={primary}>
{icon ? (
<MenuIcon>
<DynamicIcon size={tokens.icon.medium} />
</MenuIcon>
) : undefined}
{title}
</MenuTitle>
{openIndexes.includes(index) ? (
<FiChevronUp />
) : disabled ? (
<CgUnavailable />
) : (
<FiChevronDown />
)}
</MenuHeader>
<MenuContent primary={primary} isOpen={openIndexes.includes(index)}>
{content}
</MenuContent>
</MenuItem>
);
})}
</MenuContainer>
);
};

Expand Down
23 changes: 9 additions & 14 deletions src/components/Menu/__docs__/Example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,23 @@ import React, { FC } from "react";
import Menu, { MenuProps } from "../Menu";

const Example: FC<MenuProps> = ({
disabled = false,
onClick = () => {},
primary = true,
size = "small",
text = "Menu",
visible = true,
items = [
{ icon: "tags", title: "Tag", content: <>Hello World</> },
{ icon: "zoomReset", title: "Zooming", content: <>Hello World</> },
],
}) => {
return (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
height: "100%",
width: "500px",
height: "500px",
overflow: "auto",
}}
>
<Menu
size={size}
text={text}
disabled={disabled}
onClick={onClick}
primary={primary}
/>
<Menu primary={primary} visible={visible} items={items} />
</div>
);
};
Expand Down
4 changes: 1 addition & 3 deletions src/components/Menu/__docs__/Menu.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@ import {Menu} from "react-image-annotator";
const Example = () => {
return (
<Menu
size={"small"}
text={"Menu"}
onClick={()=> console.log("Clicked")}
primary
items={[{ icon: "tags", title: "Tag", content: <>Hello World</> }]}
/>
);
};
Expand Down
81 changes: 68 additions & 13 deletions src/components/Menu/__docs__/Menu.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Meta, StoryObj } from "@storybook/react";
import Example from "./Example";
import React from "react";

const meta: Meta<typeof Example> = {
title: "Menu",
Expand All @@ -11,19 +12,73 @@ type Story = StoryObj<typeof Example>;

export const Primary: Story = {
args: {
text: "Menu",
primary: true,
disabled: false,
size: "small",
onClick: () => console.log("Menu"),
},
};
export const Secondary: Story = {
args: {
text: "Menu",
primary: false,
disabled: false,
size: "small",
onClick: () => console.log("Menu"),
visible: true,
items: [
{
icon: "circle",
title: "Circle",
content: (
<div>
<p>It is a circle</p>
<ul>
<li>Circle 1</li>
<li>Circle 2</li>
<li>Circle 3</li>
</ul>
<button>Click me (circle)!</button>
</div>
),
disabled: false,
},
{
icon: "tags",
title: "Tags",
content: (
<div>
<p>It is a tag</p>
<ul>
<li>Tag 1</li>
<li>Tag 2</li>
<li>Tag 3</li>
</ul>
<button>Click me (tag)!</button>
</div>
),
disabled: false,
},
{
icon: "annotation",
title: "Annotation",
content: (
<div>
<p>It is a annotation</p>
<ul>
<li>Annotation 1</li>
<li>Annotation 2</li>
<li>Annotation 3</li>
</ul>
<button>Click me (annotation)!</button>
</div>
),
disabled: true,
},
{
icon: "coffee",
title: "Coffee",
content: (
<div>
<p>It is a coffee</p>
<ul>
<li>Coffee 1</li>
<li>Coffee 2</li>
<li>Coffee 3</li>
</ul>
<button>Click me (coffee)!</button>
</div>
),
disabled: false,
},
],
},
};
2 changes: 1 addition & 1 deletion src/components/Menu/__test__/Menu.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Menu from "../Menu";

describe("Menu component", () => {
it("Menu should render correctly", () => {
render(<Menu />);
render(<Menu items={[]} />);
const menu = screen.getByRole("menu");
expect(menu).toBeInTheDocument();
});
Expand Down
Loading