Added util menus

This commit is contained in:
Prozilla 2024-03-04 12:16:25 +01:00
parent f7d5ffef72
commit 1a48b6fbec
No known key found for this signature in database
GPG key ID: 5858DFE71CAF31EE
12 changed files with 301 additions and 32 deletions

View file

@ -32,6 +32,7 @@ export const Taskbar = memo(() => {
const settingsManager = useSettingsManager(); const settingsManager = useSettingsManager();
const [showHome, setShowHome] = useState(false); const [showHome, setShowHome] = useState(false);
const [showSearch, setShowSearch] = useState(false); const [showSearch, setShowSearch] = useState(false);
const [hideUtilMenus, setHideUtilMenus] = useState(false);
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const { boxShadow, onUpdate } = useScrollWithShadow({ ref, shadow: { const { boxShadow, onUpdate } = useScrollWithShadow({ ref, shadow: {
offset: 20, offset: 20,
@ -87,23 +88,25 @@ export const Taskbar = memo(() => {
}); });
}, [settingsManager]); }, [settingsManager]);
const updateShowHome = (value) => { const updateShowHome = (show) => {
setShowHome(value); setShowHome(show);
if (value) { if (show) {
setShowSearch(false); setShowSearch(false);
setHideUtilMenus(true);
} }
}; };
const updateShowSearch = (value) => { const updateShowSearch = (show) => {
setShowSearch(value); setShowSearch(show);
if (value) { if (show) {
if (searchQuery !== "") { if (searchQuery !== "") {
setSearchQuery(""); setSearchQuery("");
} }
setShowHome(false); setShowHome(false);
setHideUtilMenus(true);
if (inputRef.current) { if (inputRef.current) {
inputRef.current.focus(); inputRef.current.focus();
@ -118,6 +121,12 @@ export const Taskbar = memo(() => {
} }
}; };
const showUtilMenu = () => {
setShowHome(false);
setShowSearch(false);
setHideUtilMenus(false);
};
const search = (query) => { const search = (query) => {
updateShowSearch(true); updateShowSearch(true);
}; };
@ -179,10 +188,10 @@ export const Taskbar = memo(() => {
</div> </div>
</div> </div>
<div className={styles["Util-icons"]}> <div className={styles["Util-icons"]}>
<Battery/> <Battery showUtilMenu={showUtilMenu} hideUtilMenus={hideUtilMenus}/>
<Network/> <Network showUtilMenu={showUtilMenu} hideUtilMenus={hideUtilMenus}/>
<Volume/> <Volume showUtilMenu={showUtilMenu} hideUtilMenus={hideUtilMenus}/>
<Calendar/> <Calendar showUtilMenu={showUtilMenu} hideUtilMenus={hideUtilMenus}/>
<button title="Show Desktop" id="desktop-button" onClick={() => { windowsManager.minimizeAll(); }}/> <button title="Show Desktop" id="desktop-button" onClick={() => { windowsManager.minimizeAll(); }}/>
</div> </div>
</div> </div>

View file

@ -105,14 +105,22 @@
z-index: -1; z-index: -1;
} }
.Util-icons > * { .Util-icons > div {
height: 100%;
width: min-content;
z-index: -1;
}
.Util-icons > button,
.Util-icons > div > button {
height: 100%; height: 100%;
width: min-content; width: min-content;
margin: 0; margin: 0;
padding: 0.4rem; padding: 0.4rem;
} }
.Util-icons > * > svg { .Util-icons > button > svg,
.Util-icons > div > button > svg {
height: 1rem; height: 1rem;
width: 1rem; width: 1rem;
aspect-ratio: 1; aspect-ratio: 1;

View file

@ -2,10 +2,18 @@ import { faBatteryEmpty, faBatteryFull, faBatteryHalf, faBatteryQuarter, faBatte
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import styles from "./Battery.module.css"; import styles from "./Battery.module.css";
import { UtilMenu } from "../menus/UtilMenu.jsx";
import OutsideClickListener from "../../../hooks/_utils/outsideClick.js";
export function Battery() { /**
* @param {object} props
* @param {boolean} props.hideUtilMenus
* @param {Function} props.showUtilMenu
*/
export function Battery({ hideUtilMenus, showUtilMenu }) {
const [isCharging, setIsCharging] = useState(true); const [isCharging, setIsCharging] = useState(true);
const [percentage, setPercentage] = useState(100); const [percentage, setPercentage] = useState(100);
const [showMenu, setShowMenu] = useState(false);
// const [chargingTime, setChargingTime] = useState(0); // const [chargingTime, setChargingTime] = useState(0);
// const [dischargingTime, setDischargingTime] = useState(0); // const [dischargingTime, setDischargingTime] = useState(0);
@ -46,6 +54,19 @@ export function Battery() {
}); });
}, []); }, []);
useEffect(() => {
if (hideUtilMenus && showMenu) {
setShowMenu(false);
}
}, [hideUtilMenus, showMenu]);
const updateShowMenu = (show) => {
if (show)
showUtilMenu();
setShowMenu(show);
};
let icon = faBatteryFull; let icon = faBatteryFull;
if (percentage < 10) { if (percentage < 10) {
icon = faBatteryEmpty; icon = faBatteryEmpty;
@ -57,13 +78,23 @@ export function Battery() {
icon = faBatteryThreeQuarters; icon = faBatteryThreeQuarters;
} }
return ( return (<OutsideClickListener onOutsideClick={() => { updateShowMenu(false); }}>
<button className={styles.Button} title="Battery" tabIndex={0}> <button className={styles.Button} title="Battery" tabIndex={0} onClick={() => { updateShowMenu(!showMenu); }}>
{!isCharging {!isCharging
? <FontAwesomeIcon className={styles["Charging-indicator"]} icon={faMinus}/> ? <FontAwesomeIcon className={styles["Charging-indicator"]} icon={faMinus}/>
: null : null
} }
<FontAwesomeIcon icon={icon}/> <FontAwesomeIcon icon={icon}/>
</button> </button>
); <UtilMenu active={showMenu} setActive={setShowMenu} className={styles.Menu}>
<div>
{!isCharging
? <FontAwesomeIcon className={styles["Charging-indicator"]} icon={faMinus}/>
: null
}
<FontAwesomeIcon icon={icon}/>
</div>
<p>{Math.round(percentage)}%</p>
</UtilMenu>
</OutsideClickListener>);
} }

View file

@ -10,4 +10,23 @@
top: 0.55rem; top: 0.55rem;
right: 0.15rem; right: 0.15rem;
height: 0.7rem !important; height: 0.7rem !important;
}
.Menu > div {
display: flex;
gap: 0.5rem;
justify-content: center;
align-items: center;
padding: 0.5rem 1rem;
}
.Menu > div > div,
.Menu > div > div > svg {
width: auto;
height: 1.25rem;
}
.Menu > div > p {
margin: 0;
font-size: 1.25rem;
} }

View file

@ -1,18 +1,43 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import styles from "./Calendar.module.css"; import styles from "./Calendar.module.css";
import OutsideClickListener from "../../../hooks/_utils/outsideClick.js";
import { UtilMenu } from "../menus/UtilMenu.jsx";
export function Calendar() { /**
* @param {object} props
* @param {boolean} props.hideUtilMenus
* @param {Function} props.showUtilMenu
*/
export function Calendar({ hideUtilMenus, showUtilMenu }) {
const [date, setDate] = useState(new Date()); const [date, setDate] = useState(new Date());
const [showMenu, setShowMenu] = useState(false);
useEffect(() => { useEffect(() => {
setInterval(() => { const interval = setInterval(() => {
setDate(new Date()); setDate(new Date());
}, 30000); }, showMenu ? 500 : 30000);
}, []);
return ( return () => {
<button className={styles.Button} title="Date & Time" style={{ userSelect: "none" }} tabIndex={0}> clearInterval(interval);
{date.toLocaleString("en-US", { };
}, [showMenu]);
useEffect(() => {
if (hideUtilMenus && showMenu) {
setShowMenu(false);
}
}, [hideUtilMenus, showMenu]);
const updateShowMenu = (show) => {
if (show)
showUtilMenu();
setShowMenu(show);
};
return (<OutsideClickListener onOutsideClick={() => { updateShowMenu(false); }}>
<button className={styles.Button} title="Date & Time" tabIndex={0} onClick={() => { updateShowMenu(!showMenu); }}>
{date.toLocaleString("en-GB", {
hour: "numeric", hour: "numeric",
minute: "numeric", minute: "numeric",
hour12: false, hour12: false,
@ -24,5 +49,19 @@ export function Calendar() {
year: "numeric", year: "numeric",
})} })}
</button> </button>
); <UtilMenu active={showMenu} setActive={setShowMenu} className={styles.Menu}>
<p className={styles.Time}>{date.toLocaleString("en-GB", {
hour: "numeric",
minute: "numeric",
second: "numeric",
hour12: false,
})}</p>
<p className={styles.Date}>{date.toLocaleString("en-GB", {
weekday: "long",
day: "numeric",
month: "long",
year: "numeric",
})}</p>
</UtilMenu>
</OutsideClickListener>);
} }

View file

@ -1,3 +1,24 @@
.Button { .Button {
white-space: nowrap; white-space: nowrap;
user-select: none;
}
.Menu > div {
display: flex;
gap: 0.5rem;
flex-direction: column;
align-items: flex-start;
padding: 0.5rem 1rem;
}
.Time, .Date {
margin: 0;
}
.Time {
font-size: 1.5rem;
}
.Date {
color: var(--foreground-color-b);
} }

View file

@ -1,10 +1,38 @@
import { faWifi } from "@fortawesome/free-solid-svg-icons"; import { faWifi } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useEffect, useState } from "react";
import OutsideClickListener from "../../../hooks/_utils/outsideClick.js";
import { UtilMenu } from "../menus/UtilMenu.jsx";
import styles from "./Network.module.css";
export function Network() { /**
return ( * @param {object} props
<button title="Network" tabIndex={0}> * @param {boolean} props.hideUtilMenus
* @param {Function} props.showUtilMenu
*/
export function Network({ hideUtilMenus, showUtilMenu }) {
const [showMenu, setShowMenu] = useState(false);
useEffect(() => {
if (hideUtilMenus && showMenu) {
setShowMenu(false);
}
}, [hideUtilMenus, showMenu]);
const updateShowMenu = (show) => {
if (show)
showUtilMenu();
setShowMenu(show);
};
return (<OutsideClickListener onOutsideClick={() => { updateShowMenu(false); }}>
<button title="Network" tabIndex={0} onClick={() => { updateShowMenu(!showMenu); }}>
<FontAwesomeIcon icon={faWifi}/> <FontAwesomeIcon icon={faWifi}/>
</button> </button>
); <UtilMenu active={showMenu} setActive={setShowMenu} className={styles.Menu}>
<FontAwesomeIcon icon={faWifi}/>
<p>Connected</p>
</UtilMenu>
</OutsideClickListener>);
} }

View file

@ -0,0 +1,17 @@
.Menu > div {
display: flex;
gap: 0.5rem;
justify-content: center;
align-items: center;
padding: 0.5rem 1rem;
}
.Menu > div > svg {
width: auto;
height: 1rem;
}
.Menu > div > p {
margin: 0;
font-size: 1rem;
}

View file

@ -1,10 +1,38 @@
import { faVolumeHigh } from "@fortawesome/free-solid-svg-icons"; import { faVolumeHigh } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useEffect, useState } from "react";
import OutsideClickListener from "../../../hooks/_utils/outsideClick.js";
import { UtilMenu } from "../menus/UtilMenu.jsx";
import styles from "./Volume.module.css";
export function Volume() { /**
return ( * @param {object} props
<button title="Volume" tabIndex={0}> * @param {boolean} props.hideUtilMenus
* @param {Function} props.showUtilMenu
*/
export function Volume({ hideUtilMenus, showUtilMenu }) {
const [showMenu, setShowMenu] = useState(false);
useEffect(() => {
if (hideUtilMenus && showMenu) {
setShowMenu(false);
}
}, [hideUtilMenus, showMenu]);
const updateShowMenu = (show) => {
if (show)
showUtilMenu();
setShowMenu(show);
};
return (<OutsideClickListener onOutsideClick={() => { updateShowMenu(false); }}>
<button title="Volume" tabIndex={0} onClick={() => { updateShowMenu(!showMenu); }}>
<FontAwesomeIcon icon={faVolumeHigh}/> <FontAwesomeIcon icon={faVolumeHigh}/>
</button> </button>
); <UtilMenu active={showMenu} setActive={setShowMenu} className={styles.Menu}>
<FontAwesomeIcon icon={faVolumeHigh}/>
<p>100%</p>
</UtilMenu>
</OutsideClickListener>);
} }

View file

@ -0,0 +1,17 @@
.Menu > div {
display: flex;
gap: 0.5rem;
justify-content: center;
align-items: center;
padding: 0.5rem 1rem;
}
.Menu > div > svg {
width: auto;
height: 1rem;
}
.Menu > div > p {
margin: 0;
font-size: 1rem;
}

View file

@ -0,0 +1,22 @@
import styles from "./UtilMenu.module.css";
/**
* @param {object} props
* @param {boolean} props.active
* @param {Function} props.setActive
* @param {*} props.className
* @param {*} props.children
*/
export function UtilMenu({ active, setActive, className, children }) {
const classNames = [styles["Container-outer"]];
if (active)
classNames.push(styles.Active);
if (className != null)
classNames.push(className);
return (<div className={classNames.join(" ")}>
<div className={styles["Container-inner"]}>
{children}
</div>
</div>);
}

View file

@ -0,0 +1,30 @@
.Container-outer {
position: absolute;
right: 0;
bottom: 100%;
height: auto !important;
overflow: hidden;
}
.Container-inner {
opacity: 1;
display: flex;
background-color: rgba(25, 25, 25, 75%);
border-top-left-radius: 0.5rem;
border-top-right-radius: 0.5rem;
border-bottom-left-radius: 0.5rem;
backdrop-filter: var(--taskbar-filter);
transform: none;
transition: opacity 200ms ease-in-out, transform 200ms ease-in-out;
overflow: hidden;
resize: horizontal;
}
.Container-outer:not(.Active) {
pointer-events: none;
}
.Container-outer:not(.Active) .Container-inner {
opacity: 0;
transform: translateY(100px);
}