Implemented actions for header menu
This commit is contained in:
parent
a9a7c78128
commit
54249b4ba6
6 changed files with 149 additions and 87 deletions
|
|
@ -143,4 +143,83 @@
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
@ -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 {
|
||||||
|
|
@ -63,12 +64,13 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
|
||||||
if (isListener) {
|
if (isListener) {
|
||||||
return iterateOverChildren((child.props as ActionProps).children);
|
return iterateOverChildren((child.props as ActionProps).children);
|
||||||
}
|
}
|
||||||
|
|
||||||
return cloneElement(child, {
|
return cloneElement(child, {
|
||||||
...child.props,
|
...child.props,
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
@ -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>;
|
|
||||||
shortcuts: Record<string, Record<string, string[]>> | Record<string, string[]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function HeaderMenu({ options, shortcuts }: HeaderMenuProps) {
|
export function HeaderMenu({ children, ...props }: ActionsProps) {
|
||||||
useShortcuts({ options, shortcuts });
|
return <div className={styles.Container}>
|
||||||
|
<Actions className={STYLES.HEADER_MENU} {...props}>
|
||||||
return (
|
{children}
|
||||||
<div className={styles.Container}>
|
</Actions>
|
||||||
{Object.entries(options).map(([key, value]) =>
|
</div>;
|
||||||
<DropdownButton key={key} label={key} options={value} shortcuts={shortcuts[key]}/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue