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

@ -144,3 +144,82 @@
background-color: var(--foreground-color-c); background-color: var(--foreground-color-c);
margin: 0.5rem auto; 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 = { export const STYLES = {
CONTEXT_MENU: styles["Context-menu"], CONTEXT_MENU: styles["Context-menu"],
SHORTCUTS_LISTENER: styles["Shortcuts-listener"], SHORTCUTS_LISTENER: styles["Shortcuts-listener"],
HEADER_MENU: styles["Header-menu"]
}; };
export interface ActionProps { export interface ActionProps {
@ -69,6 +70,7 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
actionId, actionId,
children: iterateOverChildren((child.props as ActionProps).children), children: iterateOverChildren((child.props as ActionProps).children),
onTrigger: (event, ...args) => { onTrigger: (event, ...args) => {
console.log(event);
onAnyTrigger?.(event, triggerParams, ...args); onAnyTrigger?.(event, triggerParams, ...args);
onTrigger?.(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 { faCaretRight, IconDefinition } from "@fortawesome/free-solid-svg-icons";
import { ReactElement, useState } from "react"; import { ReactElement, useState } from "react";
import { ActionProps } from "../Actions"; 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 [showContent, setShowContent] = useState(false);
const classNames = [styles.Dropdown]; const classNames = [styles.Dropdown];
if (showContent) if (showContent)
classNames.push(styles.Active); classNames.push(styles.Active);
return <div return <OutsideClickListener onOutsideClick={() => {
key={label} if (!showOnHover)
className={classNames.join(" ")} setShowContent(false);
tabIndex={0} }}>
onMouseEnter={() => { setShowContent(true); }} <div
onMouseLeave={() => { setShowContent(false); }} key={label}
> className={classNames.join(" ")}
<span className={styles.Label}> tabIndex={0}
{icon && <div className={styles.Icon}><FontAwesomeIcon icon={icon as IconDefinition}/></div>} onMouseEnter={showOnHover ? () => { setShowContent(true); } : null}
<p>{label}</p> onMouseLeave={showOnHover ? () => { setShowContent(false); } : null}
</span> onClick={!showOnHover ? () => { setShowContent(!showContent); } : null}
<div className={styles["Dropdown-arrow"]}><FontAwesomeIcon icon={faCaretRight}/></div> >
<div className={styles["Dropdown-content"]}> <span className={styles.Label}>
{children} {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>
</div>; </OutsideClickListener>;
} }

View file

@ -2,5 +2,6 @@
display: flex; display: flex;
width: 100%; width: 100%;
height: 1.5rem; height: 1.5rem;
min-height: 1.5rem;
background-color: var(--background-color-a); 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 styles from "./HeaderMenu.module.css";
import { ActionsProps, Actions, STYLES } from "../../../actions/Actions";
interface HeaderMenuProps {
options: Record<string, Record<string, Function>> | Record<string, Function>; export function HeaderMenu({ children, ...props }: ActionsProps) {
shortcuts: Record<string, Record<string, string[]>> | Record<string, string[]>; return <div className={styles.Container}>
} <Actions className={STYLES.HEADER_MENU} {...props}>
{children}
export function HeaderMenu({ options, shortcuts }: HeaderMenuProps) { </Actions>
useShortcuts({ options, shortcuts }); </div>;
return (
<div className={styles.Container}>
{Object.entries(options).map(([key, value]) =>
<DropdownButton key={key} label={key} options={value} shortcuts={shortcuts[key]}/>
)}
</div>
);
} }

View file

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