Skip to content

Commit

Permalink
feat: add component SubMenu
Browse files Browse the repository at this point in the history
  • Loading branch information
xhofe committed Aug 25, 2022
1 parent c017f41 commit 4092fc1
Show file tree
Hide file tree
Showing 9 changed files with 210 additions and 58 deletions.
23 changes: 20 additions & 3 deletions packages/solid-contextmenu/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Component, createSignal, For } from "solid-js";
import { Menu, useContextMenu, Item, Separator, animation } from ".";
import { Menu, useContextMenu, Item, Separator, animation, Submenu } from ".";

const App: Component = () => {
const [_animation, setAnimation] = createSignal(animation.scale);
const [_theme, setTheme] = createSignal<"light" | "dark">("light");

const { show } = useContextMenu({ id: "1", props: "lala" });
return (
Expand Down Expand Up @@ -38,11 +39,27 @@ const App: Component = () => {
}}
</For>
</select>
<Menu id="1" animation={_animation()} theme="light">
<select
onChange={(e) => {
setTheme(e.currentTarget.value as any);
}}
>
<For each={["light", "dark"]}>
{(item) => {
return <option value={item}>{item}</option>;
}}
</For>
</select>
<Menu id="1" animation={_animation()} theme={_theme()}>
<Item>⚡ Beautiful</Item>
<Item>😊 Easy use</Item>
<Item>💕 Built with heart</Item>
<Separator />
<Item>💕 Built with heart</Item>
<Submenu label="▶️ submenu">
<Item>👋 Hello</Item>
<Item>😀 Hello</Item>
<Item>🤝 你好</Item>
</Submenu>
<Item disabled>❌ Disabled</Item>
</Menu>
</div>
Expand Down
18 changes: 16 additions & 2 deletions packages/solid-contextmenu/src/components/Item.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
import clsx from "clsx";
import { createMemo, mergeProps, Show, splitProps } from "solid-js";
import { ItemProps, STYLE } from "..";
import { createMemo, JSX, mergeProps, Show, splitProps } from "solid-js";
import { BooleanPredicate, HandlerParams, STYLE } from "..";
import { useMenu } from "../context";
import { getPredicateValue } from "../utils";

type LocalItemProps = {
children?: JSX.Element;
data?: any;
disabled?: BooleanPredicate;
hidden?: BooleanPredicate;
onClick?: (args: HandlerParams) => void;
};

export type ItemProps = Omit<
JSX.HTMLAttributes<HTMLDivElement>,
"hidden" | "disabled" | "onClick"
> &
LocalItemProps;

export const Item = (props: ItemProps) => {
const [local, others] = splitProps(props, [
"hidden",
Expand Down
37 changes: 35 additions & 2 deletions packages/solid-contextmenu/src/components/Menu.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
createMemo,
createSignal,
JSX,
mergeProps,
onCleanup,
Show,
Expand All @@ -10,11 +11,43 @@ import { Portal } from "solid-js/web";
import { Transition } from "solid-transition-group";
import { bus } from "../bus";
import { MenuContext } from "../context";
import { STYLE, MenuProps, Pos } from "..";
import { calPos } from "../utils";
import { STYLE, Pos, MenuId, Size } from "..";
import clsx from "clsx";
import "../scss/main.scss";

const calVal = (clickP: number, elS: number, boxS: number) => {
if (clickP + elS <= boxS) {
return clickP;
}
if (clickP < elS) {
return boxS - elS;
// return 0;
}
return clickP - elS;
};
const calPos = (clickPos: Pos, elSize: Size): Pos => {
const pageSize: Size = {
width: window.innerWidth,
height: window.innerHeight,
};
return {
x: calVal(clickPos.x, elSize.width, pageSize.width),
y: calVal(clickPos.y, elSize.height, pageSize.height),
};
};

export type LocalMenuProps = {
id: MenuId;
children?: JSX.Element;
theme?: "light" | "dark";
animation?: string | false;
onShown?: () => void;
onHidden?: () => void;
};

export type MenuProps = Omit<JSX.HTMLAttributes<HTMLDivElement>, "id"> &
LocalMenuProps;

export const Menu = (props: MenuProps) => {
const [local, others] = splitProps(props, [
"id",
Expand Down
113 changes: 113 additions & 0 deletions packages/solid-contextmenu/src/components/Submenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import clsx from "clsx";
import {
createMemo,
createSignal,
JSX,
mergeProps,
Show,
splitProps,
} from "solid-js";
import { useMenu } from "../context";
import { STYLE, BooleanPredicate, Pos, Size } from "..";
import { getPredicateValue } from "../utils";

export interface LocalSubMenuProps {
label: JSX.Element;
children: JSX.Element;
arrow?: JSX.Element;
disabled?: BooleanPredicate;
hidden?: BooleanPredicate;
}
export type SubMenuProps = Omit<JSX.HTMLAttributes<HTMLDivElement>, "hidden"> &
LocalSubMenuProps;
export const Submenu = (props: SubMenuProps) => {
const [local, others] = splitProps(props, [
"arrow",
"children",
"disabled",
"hidden",
"label",
"class",
]);
const merged = mergeProps(
{
arrow: "▶️",
disabled: false,
hidden: false,
},
local
);
const { props: showProps } = useMenu();
const handlerParams = createMemo(() => ({
props: showProps(),
}));
const isDisabled = createMemo(() =>
getPredicateValue(merged.disabled, handlerParams())
);
const isHidden = createMemo(() =>
getPredicateValue(merged.hidden, handlerParams())
);
let parent: HTMLDivElement;
let sub: HTMLDivElement;
const [pos, setPos] = createSignal<Pos>({ x: 0, y: 0 });
const updatePos = () => {
const parentRect = parent.getBoundingClientRect();
const subRect = sub.getBoundingClientRect();
const windowSize = {
width: window.innerWidth,
height: window.innerHeight,
};
let x = parentRect.right;
if (x + subRect.width > windowSize.width) {
if (parentRect.left - subRect.width > 0) {
x = parentRect.left - subRect.width;
} else {
x = windowSize.width - subRect.width;
}
}
let y = parentRect.top;
if (y + subRect.height > windowSize.height) {
if (parentRect.bottom - subRect.height > 0) {
y = parentRect.bottom - subRect.height;
} else {
y = windowSize.height - subRect.height;
}
}
setPos({ x, y });
};
const [shown, setShown] = createSignal(false);
const show = () => {
setShown(true);
updatePos();
};
return (
<Show when={!isHidden()}>
<div
{...others}
class={clsx(STYLE.item, local.class, {
[STYLE.itemDisabled]: isDisabled(),
})}
onMouseEnter={() => show()}
onMouseLeave={() => setShown(false)}
onClick={() => (shown() ? setShown(false) : show())}
>
<div class={STYLE.itemContent} ref={parent!}>
{merged.label}
<span class={STYLE.submenuArrow}>{merged.arrow}</span>
</div>
<Show when={shown()}>
<div
ref={sub!}
class={STYLE.submenu}
style={{
left: pos().x + "px",
top: pos().y + "px",
}}
>
{merged.children}
</div>
</Show>
</div>
</Show>
);
};
1 change: 1 addition & 0 deletions packages/solid-contextmenu/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./Menu";
export * from "./Item";
export * from "./Separator";
export * from "./Submenu";
4 changes: 2 additions & 2 deletions packages/solid-contextmenu/src/scss/_menu.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
}

& &__submenu {
position: absolute;
position: fixed;
/* negate padding */
top: -6px;
// top: -6px;
pointer-events: none;
transition: opacity 0.275s;
}
Expand Down
27 changes: 2 additions & 25 deletions packages/solid-contextmenu/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Accessor, JSX } from "solid-js";
import { Accessor } from "solid-js";
import { LocalMenuProps } from ".";

export type MenuId = string | number;

Expand All @@ -13,17 +14,6 @@ export interface Size {

export type TriggerEvent = MouseEvent;

export type MenuProps = Omit<JSX.HTMLAttributes<HTMLDivElement>, "id"> &
LocalMenuProps;
type LocalMenuProps = {
id: MenuId;
children?: JSX.Element | JSX.Element[];
theme?: "light" | "dark";
animation?: string | false;
onShown?: () => void;
onHidden?: () => void;
};

export type ShowContextMenuParams = {
id: MenuId;
event: TriggerEvent;
Expand Down Expand Up @@ -52,16 +42,3 @@ export type HandlerParams<Props = any, Data = any> = ItemParams<Props, Data> & {
event: MouseEvent;
};
export type BooleanPredicate = boolean | ((args: ItemParams) => boolean);
type LocalItemProps = {
children?: JSX.Element | JSX.Element[];
data?: any;
disabled?: BooleanPredicate;
hidden?: BooleanPredicate;
onClick?: (args: HandlerParams) => void;
};

export type ItemProps = Omit<
JSX.HTMLAttributes<HTMLDivElement>,
"hidden" | "disabled" | "onClick"
> &
LocalItemProps;
22 changes: 1 addition & 21 deletions packages/solid-contextmenu/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,4 @@
import { BooleanPredicate, Pos, Size, ItemParams } from ".";

const calVal = (clickV: number, elV: number, innerV: number) => {
if (clickV + elV <= innerV) {
return clickV;
}
if (clickV < elV) {
return 0;
}
return clickV - elV;
};
export const calPos = (clickPos: Pos, elSize: Size): Pos => {
const pageSize: Size = {
width: window.innerWidth,
height: window.innerHeight,
};
return {
x: calVal(clickPos.x, elSize.width, pageSize.width),
y: calVal(clickPos.y, elSize.height, pageSize.height),
};
};
import { BooleanPredicate, ItemParams } from ".";

export function isFn(v: any): v is Function {
return typeof v === "function";
Expand Down
23 changes: 20 additions & 3 deletions sites/site/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Component, createSignal, For } from "solid-js";
import { Menu, useContextMenu, Item, Separator, animation } from ".";
import { Menu, useContextMenu, Item, Separator, animation, Submenu } from ".";

const App: Component = () => {
const [_animation, setAnimation] = createSignal(animation.scale);
const [_theme, setTheme] = createSignal<"light" | "dark">("light");

const { show } = useContextMenu({ id: "1", props: "lala" });
return (
Expand Down Expand Up @@ -38,11 +39,27 @@ const App: Component = () => {
}}
</For>
</select>
<Menu id="1" animation={_animation()} theme="light">
<select
onChange={(e) => {
setTheme(e.currentTarget.value as any);
}}
>
<For each={["light", "dark"]}>
{(item) => {
return <option value={item}>{item}</option>;
}}
</For>
</select>
<Menu id="1" animation={_animation()} theme={_theme()}>
<Item>⚡ Beautiful</Item>
<Item>😊 Easy use</Item>
<Item>💕 Built with heart</Item>
<Separator />
<Item>💕 Built with heart</Item>
<Submenu label="▶️ submenu">
<Item>👋 Hello</Item>
<Item>😀 Hello</Item>
<Item>🤝 你好</Item>
</Submenu>
<Item disabled>❌ Disabled</Item>
</Menu>
</div>
Expand Down

1 comment on commit 4092fc1

@vercel
Copy link

@vercel vercel bot commented on 4092fc1 Aug 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

solid-contextmenu – ./

solid-contextmenu-git-main-xhofe.vercel.app
solid-contextmenu.vercel.app
solid-contextmenu-xhofe.vercel.app

Please sign in to comment.