From 97cf06bc7f1a6b581b48cbdbc869c80b75c733df Mon Sep 17 00:00:00 2001 From: Madeorsk Date: Sun, 7 Jul 2024 13:15:28 +0200 Subject: [PATCH] Basic main menus and submenus, floating elements improvements. --- demo/DemoApp.tsx | 49 ++++++++++++++ src/Components/Floating/Float.tsx | 16 +++-- src/Components/Menus/MainMenu.tsx | 16 +++++ src/Components/Menus/MainMenuItem.tsx | 35 ++++++++++ src/Components/Menus/Submenu.tsx | 14 ++++ src/Components/Menus/SubmenuFloat.tsx | 24 +++++++ src/Components/Menus/SubmenuItem.tsx | 33 +++++++++ src/Components/Select/Suggestible.tsx | 2 +- src/styles/_colors.less | 8 +++ src/styles/_components.less | 1 + src/styles/components/_menus.less | 2 + src/styles/components/menus/_main-menu.less | 74 +++++++++++++++++++++ src/styles/components/menus/_submenu.less | 37 +++++++++++ 13 files changed, 304 insertions(+), 7 deletions(-) create mode 100644 src/Components/Menus/MainMenu.tsx create mode 100644 src/Components/Menus/MainMenuItem.tsx create mode 100644 src/Components/Menus/Submenu.tsx create mode 100644 src/Components/Menus/SubmenuFloat.tsx create mode 100644 src/Components/Menus/SubmenuItem.tsx create mode 100644 src/styles/components/_menus.less create mode 100644 src/styles/components/menus/_main-menu.less create mode 100644 src/styles/components/menus/_submenu.less diff --git a/demo/DemoApp.tsx b/demo/DemoApp.tsx index f3d01b7..0e06b57 100644 --- a/demo/DemoApp.tsx +++ b/demo/DemoApp.tsx @@ -14,6 +14,11 @@ import {Select} from "../src/Components/Select/Select"; import {SpinningLoader} from "../src/Components/Loaders/SpinningLoader"; import {ListLoader} from "../src/Components/Loaders/ListLoader"; import {GenericLoader} from "../src/Components/Loaders/GenericLoader"; +import {MainMenu} from "../src/Components/Menus/MainMenu"; +import {SubmenuFloat} from "../src/Components/Menus/SubmenuFloat"; +import {Submenu} from "../src/Components/Menus/Submenu"; +import {SubmenuItem, SubmenuItemSubmenu} from "../src/Components/Menus/SubmenuItem"; +import {MainMenuItem, MainMenuItemSubmenu} from "../src/Components/Menus/MainMenuItem"; export function DemoApp() { @@ -23,6 +28,31 @@ export function DemoApp() return (
+ + Home + Test + + Test 1 + Test 2 + + Test A + Test B + + First last choice + Another last choice + + }>Submenu in submenu + + }>Submenu + + }> + Submenu + + +

KernelUI

TODO

@@ -263,6 +293,25 @@ export function DemoApp() Sample content. + +

Menus

+ + + Test 1 + Test 2 + + Test A + Test B + + }> + Submenu + + + } floatingOptions={{ placement: "right-start" }}> + +
); } diff --git a/src/Components/Floating/Float.tsx b/src/Components/Floating/Float.tsx index c7e49bc..9723db9 100644 --- a/src/Components/Floating/Float.tsx +++ b/src/Components/Floating/Float.tsx @@ -1,8 +1,9 @@ import React, {useCallback, useMemo, useState} from "react"; import {Card} from "../Card"; import { + flip, shift, - useClick, + useClick, useDismiss, useFloating, useFocus, useHover, @@ -41,6 +42,7 @@ export interface FloatProperties content?: React.ReactNode|Managed; className?: string; mode?: FloatingMode; + dismissible?: boolean; role?: FloatRole; floatingOptions?: UseFloatingOptions; } @@ -48,9 +50,10 @@ export interface FloatProperties /** * A component to show something floating next to an element. */ -export const Float = React.forwardRef(({children, content, className, mode, role, floatingOptions}: FloatProperties, ref): React.ReactElement => { +export const Float = React.forwardRef(({children, content, className, mode, dismissible, role, floatingOptions}: FloatProperties, ref): React.ReactElement => { // By default, use "always" mode. if (!mode) mode = "always"; + if (dismissible === undefined && (mode != "always" && mode != "managed")) dismissible = true; // Followed show status. const [shown, setShown] = useState(false); @@ -68,11 +71,11 @@ export const Float = React.forwardRef(({children, content, className, mode, role } // Floating initialization. - const { refs, floatingStyles, context } = useFloating( + const { refs, floatingStyles, context, placement } = useFloating( useMemo(() => (Object.assign({ open: shown, onOpenChange: setShown, - middleware: [shift()], + middleware: [shift(), flip()], } as UseFloatingOptions, floatingOptions)), [floatingOptions, shown, setShown]) ); @@ -80,11 +83,12 @@ export const Float = React.forwardRef(({children, content, className, mode, role const hover = useHover(context, useMemo(() => ({ enabled: mode == "hover" }), [mode])); const focus = useFocus(context, useMemo(() => ({ enabled: mode == "focus", visibleOnly: false }), [mode])); const click = useClick(context, useMemo(() => ({ enabled: mode == "click" }), [mode])); + const dismiss = useDismiss(context, useMemo(() => ({ enabled: !!dismissible }), [dismissible])); const roleProps = useRole(context, { role: role, enabled: !!role, }); - const {getReferenceProps, getFloatingProps} = useInteractions([roleProps, hover, focus, click]); + const {getReferenceProps, getFloatingProps} = useInteractions([roleProps, dismiss, hover, focus, click]); // Transition configuration. const {isMounted, styles: transitionStyles} = useTransitionStyles(context, { @@ -132,7 +136,7 @@ export const Float = React.forwardRef(({children, content, className, mode, role { // Showing floating element if the state says to do so. isMounted && -
+
{floatingContent} diff --git a/src/Components/Menus/MainMenu.tsx b/src/Components/Menus/MainMenu.tsx new file mode 100644 index 0000000..0933189 --- /dev/null +++ b/src/Components/Menus/MainMenu.tsx @@ -0,0 +1,16 @@ +import React from "react"; +import {classes} from "../../Utils"; + +/** + * Main component of a main menu. + */ +export function MainMenu({children, className, ...props}: React.PropsWithChildren>) +{ + return ( + + ); +} diff --git a/src/Components/Menus/MainMenuItem.tsx b/src/Components/Menus/MainMenuItem.tsx new file mode 100644 index 0000000..ef4c202 --- /dev/null +++ b/src/Components/Menus/MainMenuItem.tsx @@ -0,0 +1,35 @@ +import React from "react"; +import {classes, Modify} from "../../Utils"; +import {SubmenuFloat} from "./SubmenuFloat"; + +export type MainMenuItemProperties = React.PropsWithChildren, { +}>>; + +/** + * Main component of a main menu item. + */ +export const MainMenuItem = React.forwardRef(function SubmenuItem({children, ...props}: MainMenuItemProperties, ref) +{ + return ( +
  • + {children} +
  • + ); +}); + +/** + * A main menu item that open a submenu. + */ +export function MainMenuItemSubmenu({submenu, className, children, ...props}: Modify) +{ + return ( + + {children} + + ); +} diff --git a/src/Components/Menus/Submenu.tsx b/src/Components/Menus/Submenu.tsx new file mode 100644 index 0000000..c2c2b19 --- /dev/null +++ b/src/Components/Menus/Submenu.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import {classes} from "../../Utils"; + +/** + * Main component of a submenu. + */ +export function Submenu({className, children, ...props}: React.PropsWithChildren>) +{ + return ( +
    + {children} +
    + ); +} diff --git a/src/Components/Menus/SubmenuFloat.tsx b/src/Components/Menus/SubmenuFloat.tsx new file mode 100644 index 0000000..d00b58b --- /dev/null +++ b/src/Components/Menus/SubmenuFloat.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import {Float, FloatProperties} from "../Floating/Float"; +import {classes, Modify} from "../../Utils"; + +/** + * Add a submenu which opens on click on the child. + */ +export function SubmenuFloat({submenu, className, mode, children, content, ...props}: Modify) +{ + return ( + + {children} + + ); +} diff --git a/src/Components/Menus/SubmenuItem.tsx b/src/Components/Menus/SubmenuItem.tsx new file mode 100644 index 0000000..1a5dccb --- /dev/null +++ b/src/Components/Menus/SubmenuItem.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import {classes, Modify} from "../../Utils"; +import {SubmenuFloat} from "./SubmenuFloat"; + +export type SubmenuItemProperties = React.PropsWithChildren, { +}>>; + +/** + * Main component of a submenu item. + */ +export const SubmenuItem = React.forwardRef(function SubmenuItem({className, children, ...props}: SubmenuItemProperties, ref) +{ + return ( + {children} + ); +}); + +/** + * A submenu item that open a submenu. + */ +export function SubmenuItemSubmenu({submenu, className, children, ...props}: Modify) +{ + return ( + + {children} + + ); +} diff --git a/src/Components/Select/Suggestible.tsx b/src/Components/Select/Suggestible.tsx index bd293ae..492abc0 100644 --- a/src/Components/Select/Suggestible.tsx +++ b/src/Components/Select/Suggestible.tsx @@ -16,7 +16,7 @@ export function Suggestible({className, suggestions, mode, content, role, childr mode = mode ?? "focus"; return ( - + {children} ); diff --git a/src/styles/_colors.less b/src/styles/_colors.less index 1f9b7dd..c4653e5 100644 --- a/src/styles/_colors.less +++ b/src/styles/_colors.less @@ -35,6 +35,7 @@ @blue-darker: #0657C5; --blue-darker: @blue-darker; @blue-background: #9DC8FF; --blue-background: @blue-background; @blue-background-darker: #7EA9E1; --blue-background-darker: @blue-background-darker; + @blue-gradient: linear-gradient(33deg, @blue 0%, @blue-lighter 100%), @blue; --blue-gradient: @blue-gradient; @orange-lighter: #E77220; --orange-lighter: @orange-lighter; @orange: #D06112; --orange: @orange; @@ -72,9 +73,16 @@ @primary: @blue; --primary: @primary; @primary-darker: @blue-darker; --primary-darker: @primary-darker; @primary-background: @blue-background; --primary-background: @primary-background; + @primary-background-darker: @blue-background-darker; --primary-background-darker: @primary-background-darker; + @primary-gradient: @blue-gradient; --primary-gradient: @primary-gradient; @secondary-lighter: @orange-lighter; --secondary-lighter: @secondary-lighter; @secondary: @orange; --secondary: @secondary; @secondary-darker: @orange-darker; --secondary-darker: @secondary-darker; @secondary-background: @orange-background; --secondary-background: @secondary-background; + @secondary-background-darker: @orange-background-darker; --secondary-background-darker: @secondary-background-darker; + + + + @menu-hover: rgba(255, 255, 255, 0.15); --menu-hover: @menu-hover; } diff --git a/src/styles/_components.less b/src/styles/_components.less index 3659dd5..d21d60e 100644 --- a/src/styles/_components.less +++ b/src/styles/_components.less @@ -8,6 +8,7 @@ @import "components/_link"; @import "components/_list"; @import "components/_loaders"; +@import "components/_menus"; @import "components/_select"; @import "components/_steps"; @import "components/_table"; diff --git a/src/styles/components/_menus.less b/src/styles/components/_menus.less new file mode 100644 index 0000000..3bc0861 --- /dev/null +++ b/src/styles/components/_menus.less @@ -0,0 +1,2 @@ +@import "menus/_main-menu"; +@import "menus/_submenu"; diff --git a/src/styles/components/menus/_main-menu.less b/src/styles/components/menus/_main-menu.less new file mode 100644 index 0000000..ffe1217 --- /dev/null +++ b/src/styles/components/menus/_main-menu.less @@ -0,0 +1,74 @@ +nav.main.menu +{ + background: var(--primary-gradient); + color: var(--background); + + .floating + { + justify-content: flex-start; + } + .floating[data-placement="left-start"] + { + justify-content: flex-end; + } + + > ul + { + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; + + margin: 0; + padding: 0; + + list-style: none; + + > li + { + margin: 0; + padding: 0; + + > a + { + transition: background 0.2s ease; + display: block; + + padding: 0.75em 1em; + + background: transparent; + color: var(--background); + + text-align: center; + text-decoration: none; + cursor: pointer; + + &:hover + { + background: var(--menu-hover); + } + } + } + } + + + .submenu.card + { + box-shadow: 0 0 0.15em 0 var(--foreground-shadow); + border: none; + background: var(--primary-gradient); + + .submenu + { + > .item + { + color: var(--background); + + &:hover + { + background: var(--menu-hover); + } + } + } + } +} diff --git a/src/styles/components/menus/_submenu.less b/src/styles/components/menus/_submenu.less new file mode 100644 index 0000000..166aee1 --- /dev/null +++ b/src/styles/components/menus/_submenu.less @@ -0,0 +1,37 @@ +.submenu.card +{ + margin: -1px!important; + padding: 0; + + .floating + { + justify-content: flex-start; + } + .floating[data-placement="left-start"] + { + justify-content: flex-end; + } + + .submenu + { + display: flex; + flex-direction: column; + + > .item + { + transition: background 0.2s ease; + + padding: 0.75em; + + color: var(--foreground); + + text-decoration: none; + cursor: pointer; + + &:hover + { + background: var(--background-darker); + } + } + } +}