Implemented actions for header menu

This commit is contained in:
Prozilla 2024-06-02 19:31:53 +02:00
parent a9a7c78128
commit 54249b4ba6
No known key found for this signature in database
GPG key ID: 5858DFE71CAF31EE
6 changed files with 149 additions and 87 deletions

View file

@ -143,4 +143,83 @@
border-radius: 1rem;
background-color: var(--foreground-color-c);
margin: 0.5rem auto;
}
.Header-menu {
display: flex;
width: inherit;
height: inherit;
}
.Header-menu .Dropdown {
display: block;
width: auto;
height: 100%;
padding: 0 0.5rem;
background: none;
border: none;
outline: none;
font-size: 0.85rem;
cursor: pointer;
}
.Header-menu .Dropdown:hover, .Header-menu .Dropdown:focus-visible {
background-color: rgba(255, 255, 255, 5%);
}
.Header-menu .Dropdown > .Label {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
.Header-menu .Dropdown-arrow {
display: none;
}
.Header-menu .Dropdown-content {
display: flex;
flex-direction: column;
position: absolute;
top: 100%;
left: 0;
padding: 0.35rem;
background-color: var(--background-color-b);
border-bottom-left-radius: 0.5rem;
border-bottom-right-radius: 0.5rem;
}
.Header-menu .Dropdown:not(.Active) .Dropdown-content {
opacity: 0;
pointer-events: none;
}
.Header-menu .Button {
display: flex;
gap: 0.75rem;
justify-content: space-between;
width: 100%;
padding: 0.25rem 0.5rem;
background: none;
border: none;
border-radius: 0.5rem;
outline: none;
font-size: 0.85rem;
text-align: start;
white-space: nowrap;
cursor: pointer;
}
.Header-menu .Button:hover, .Header-menu .Button:focus-visible {
background-color: hsla(var(--background-color-a-hsl), 75%);
}
.Header-menu .Dropdown > .Label > p, .Header-menu .Button > .Label > p {
margin: 0;
}
.Header-menu .Shortcut {
color: var(--foreground-color-b);
margin: 0;
}

View file

@ -6,6 +6,7 @@ import { useScreenBounds } from "../../hooks/_utils/screen";
export const STYLES = {
CONTEXT_MENU: styles["Context-menu"],
SHORTCUTS_LISTENER: styles["Shortcuts-listener"],
HEADER_MENU: styles["Header-menu"]
};
export interface ActionProps {
@ -63,12 +64,13 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
if (isListener) {
return iterateOverChildren((child.props as ActionProps).children);
}
return cloneElement(child, {
...child.props,
actionId,
children: iterateOverChildren((child.props as ActionProps).children),
onTrigger: (event, ...args) => {
console.log(event);
onAnyTrigger?.(event, triggerParams, ...args);
onTrigger?.(event, triggerParams, ...args);
}

View file

@ -3,28 +3,39 @@ import styles from "../Actions.module.css";
import { faCaretRight, IconDefinition } from "@fortawesome/free-solid-svg-icons";
import { ReactElement, useState } from "react";
import { ActionProps } from "../Actions";
import OutsideClickListener from "../../../hooks/_utils/outsideClick";
export function DropdownAction({ label, icon, children }: ActionProps): ReactElement {
interface DropdownActionProps extends ActionProps {
showOnHover?: boolean;
}
export function DropdownAction({ label, icon, children, showOnHover = true }: DropdownActionProps): ReactElement {
const [showContent, setShowContent] = useState(false);
const classNames = [styles.Dropdown];
if (showContent)
classNames.push(styles.Active);
return <div
key={label}
className={classNames.join(" ")}
tabIndex={0}
onMouseEnter={() => { setShowContent(true); }}
onMouseLeave={() => { setShowContent(false); }}
>
<span className={styles.Label}>
{icon && <div className={styles.Icon}><FontAwesomeIcon icon={icon as IconDefinition}/></div>}
<p>{label}</p>
</span>
<div className={styles["Dropdown-arrow"]}><FontAwesomeIcon icon={faCaretRight}/></div>
<div className={styles["Dropdown-content"]}>
{children}
return <OutsideClickListener onOutsideClick={() => {
if (!showOnHover)
setShowContent(false);
}}>
<div
key={label}
className={classNames.join(" ")}
tabIndex={0}
onMouseEnter={showOnHover ? () => { setShowContent(true); } : null}
onMouseLeave={showOnHover ? () => { setShowContent(false); } : null}
onClick={!showOnHover ? () => { setShowContent(!showContent); } : null}
>
<span className={styles.Label}>
{icon && <div className={styles.Icon}><FontAwesomeIcon icon={icon as IconDefinition}/></div>}
<p>{label}</p>
</span>
<div className={styles["Dropdown-arrow"]}><FontAwesomeIcon icon={faCaretRight}/></div>
<div className={styles["Dropdown-content"]}>
{children}
</div>
</div>
</div>;
</OutsideClickListener>;
}

View file

@ -2,5 +2,6 @@
display: flex;
width: 100%;
height: 1.5rem;
min-height: 1.5rem;
background-color: var(--background-color-a);
}

View file

@ -1,20 +1,11 @@
import { useShortcuts } from "../../../../hooks/_utils/keyboard";
import { DropdownButton } from "../../../_utils/dropdown-button/DropdownButton";
import styles from "./HeaderMenu.module.css";
import { ActionsProps, Actions, STYLES } from "../../../actions/Actions";
interface HeaderMenuProps {
options: Record<string, Record<string, Function>> | Record<string, Function>;
shortcuts: Record<string, Record<string, string[]>> | Record<string, string[]>;
}
export function HeaderMenu({ options, shortcuts }: HeaderMenuProps) {
useShortcuts({ options, shortcuts });
return (
<div className={styles.Container}>
{Object.entries(options).map(([key, value]) =>
<DropdownButton key={key} label={key} options={value} shortcuts={shortcuts[key]}/>
)}
</div>
);
export function HeaderMenu({ children, ...props }: ActionsProps) {
return <div className={styles.Container}>
<Actions className={STYLES.HEADER_MENU} {...props}>
{children}
</Actions>
</div>;
}

View file

@ -15,6 +15,8 @@ import { FileSelector } from "../../modals/file-selector/FileSelector";
import { SELECTOR_MODE } from "../../../config/apps/fileExplorer.config";
import { VirtualFile } from "../../../features/virtual-drive/file/virtualFile";
import { WindowProps } from "../../windows/WindowView";
import { DropdownAction } from "../../actions/actions/DropdownAction";
import { ClickAction } from "../../actions/actions/ClickAction";
const OVERRIDES = {
a: MarkdownLink,
@ -125,60 +127,36 @@ export function TextEditor({ file, setTitle, setIconUrl, close, mode, app, modal
return (
<div className={styles.Container} style={{ fontSize: zoom }}>
<HeaderMenu
options={{
"File": {
"New": newText,
"Open": () => {
openWindowedModal({
size: DEFAULT_FILE_SELECTOR_SIZE,
Modal: (props) => <FileSelector
type={SELECTOR_MODE.SINGLE}
onFinish={(file: VirtualFile) => {
setCurrentFile(file);
setUnsavedChanges(false);
}}
{...props}
/>
});
},
"Save": saveText,
// "Save As": saveTextAs,
"Quit": () => {
close();
},
},
"View": {
[currentMode === "view" ? "Edit mode" : "Preview mode"]: () => {
setCurrentMode(currentMode === "view" ? "edit" : "view");
},
"Zoom In": () => {
setZoom(zoom + ZOOM_FACTOR);
},
"Zoom Out": () => {
setZoom(zoom - ZOOM_FACTOR);
},
"Reset Zoom": () => {
setZoom(DEFAULT_ZOOM);
}
}
}}
shortcuts={{
"File": {
"New": ["Control", "e"],
"Open": ["Control", "o"],
"Save": ["Control", "s"],
"Quit": ["Control", "q"],
},
"View": {
"Zoom In": ["Control", "+"],
"Zoom Out": ["Control", "-"],
"Reset Zoom": ["Control", "0"],
"Edit mode": ["Control v"],
"Preview mode": ["Control v"],
}
}}
/>
<HeaderMenu onAnyTrigger={(event) => {
console.log(event);
}}>
<DropdownAction label="File" showOnHover={false}>
<ClickAction label="New" onTrigger={() => { newText(); }} shortcut={["Control", "e"]}/>
<ClickAction label="Open" onTrigger={() => {
openWindowedModal({
size: DEFAULT_FILE_SELECTOR_SIZE,
Modal: (props) => <FileSelector
type={SELECTOR_MODE.SINGLE}
onFinish={(file: VirtualFile) => {
setCurrentFile(file);
setUnsavedChanges(false);
}}
{...props}
/>
});
}} shortcut={["Control", "o"]}/>
<ClickAction label="Save" onTrigger={() => { saveText(); }} shortcut={["Control", "s"]}/>
<ClickAction label="Quit" onTrigger={() => { close(); }} shortcut={["Control", "q"]}/>
</DropdownAction>
<DropdownAction label="View" showOnHover={false}>
<ClickAction label={currentMode === "view" ? "Edit mode" : "Preview mode"} onTrigger={() => {
setCurrentMode(currentMode === "view" ? "edit" : "view");
}} shortcut={["Control", "v"]}/>
<ClickAction label="Zoom in" onTrigger={() => { setZoom(zoom + ZOOM_FACTOR); }} shortcut={["Control", "+"]}/>
<ClickAction label="Zoom out" onTrigger={() => { setZoom(zoom - ZOOM_FACTOR); }} shortcut={["Control", "-"]}/>
<ClickAction label="Reset Zoom" onTrigger={() => { setZoom(DEFAULT_ZOOM); }} shortcut={["Control", "0"]}/>
</DropdownAction>
</HeaderMenu>
{currentMode === "view"
? CODE_FORMATS.includes(currentFile?.extension)
? <SyntaxHighlighter