Added util menus
This commit is contained in:
parent
f7d5ffef72
commit
1a48b6fbec
12 changed files with 301 additions and 32 deletions
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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>);
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
@ -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>);
|
||||||
}
|
}
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
@ -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>);
|
||||||
}
|
}
|
||||||
17
src/components/taskbar/indicators/Network.module.css
Normal file
17
src/components/taskbar/indicators/Network.module.css
Normal 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;
|
||||||
|
}
|
||||||
|
|
@ -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>);
|
||||||
}
|
}
|
||||||
17
src/components/taskbar/indicators/Volume.module.css
Normal file
17
src/components/taskbar/indicators/Volume.module.css
Normal 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;
|
||||||
|
}
|
||||||
22
src/components/taskbar/menus/UtilMenu.jsx
Normal file
22
src/components/taskbar/menus/UtilMenu.jsx
Normal 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>);
|
||||||
|
}
|
||||||
30
src/components/taskbar/menus/UtilMenu.module.css
Normal file
30
src/components/taskbar/menus/UtilMenu.module.css
Normal 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);
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue