Updated taskbar + added search menu
This commit is contained in:
parent
27a0c480ad
commit
1499e4f178
14 changed files with 530 additions and 79 deletions
25
src/components/task-bar/AppList.module.css
Normal file
25
src/components/task-bar/AppList.module.css
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
.App-list {
|
||||
--scrollbar-color: rgba(0, 0, 0, 25%);
|
||||
overflow-y: auto;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.App-button {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.App-button > div,
|
||||
.App-button > div > div,
|
||||
.App-button > div > div > svg {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.App-button > p {
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
69
src/components/task-bar/Battery.jsx
Normal file
69
src/components/task-bar/Battery.jsx
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import { faBatteryEmpty, faBatteryFull, faBatteryHalf, faBatteryQuarter, faBatteryThreeQuarters, faMinus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useEffect, useState } from "react";
|
||||
import styles from "./Battery.module.css";
|
||||
|
||||
export function Battery() {
|
||||
const [isCharging, setIsCharging] = useState(false);
|
||||
const [percentage, setPercentage] = useState(100);
|
||||
// const [chargingTime, setChargingTime] = useState(0);
|
||||
// const [dischargingTime, setDischargingTime] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
navigator.getBattery().then((battery) => {
|
||||
const updateIsCharging = () => {
|
||||
setIsCharging(battery.charging);
|
||||
};
|
||||
|
||||
const updatePercentage = () => {
|
||||
setPercentage(battery.level * 100);
|
||||
};
|
||||
|
||||
// const updateChargingTime = () => {
|
||||
// setChargingTime(battery.chargingTime);
|
||||
// };
|
||||
|
||||
// const updateDischargingTime = () => {
|
||||
// setDischargingTime(battery.dischargingTime);
|
||||
// };
|
||||
|
||||
updateIsCharging();
|
||||
updatePercentage();
|
||||
// updateChargingTime();
|
||||
// updateDischargingTime();
|
||||
|
||||
battery.addEventListener("chargingchange", updateIsCharging);
|
||||
battery.addEventListener("levelchange", updatePercentage);
|
||||
// battery.addEventListener("chargingtimechange", updateChargingTime);
|
||||
// battery.addEventListener("dischargingtimechange", updateDischargingTime);
|
||||
|
||||
return () => {
|
||||
battery.removeEventListener("chargingchange", updateIsCharging);
|
||||
battery.removeEventListener("levelchange", updatePercentage);
|
||||
// battery.removeEventListener("chargingtimechange", updateChargingTime);
|
||||
// battery.removeEventListener("dischargingtimechange", updateDischargingTime);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let icon = faBatteryFull;
|
||||
if (percentage < 25) {
|
||||
icon = faBatteryEmpty;
|
||||
} else if (percentage < 50) {
|
||||
icon = faBatteryQuarter;
|
||||
} else if (percentage < 75) {
|
||||
icon = faBatteryHalf;
|
||||
} else if (percentage < 100) {
|
||||
icon = faBatteryThreeQuarters;
|
||||
}
|
||||
|
||||
return (
|
||||
<button className={styles.Button} title="Battery">
|
||||
{!isCharging
|
||||
? <FontAwesomeIcon className={styles["Charging-indicator"]} icon={faMinus}/>
|
||||
: null
|
||||
}
|
||||
<FontAwesomeIcon icon={icon}/>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
13
src/components/task-bar/Battery.module.css
Normal file
13
src/components/task-bar/Battery.module.css
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
.Button {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.Charging-indicator {
|
||||
--outline-color: var(--background-color-c);
|
||||
--outline-width: 2px;
|
||||
|
||||
position: absolute;
|
||||
top: 0.55rem;
|
||||
right: 0.15rem;
|
||||
height: 0.7rem !important;
|
||||
}
|
||||
28
src/components/task-bar/Calendar.jsx
Normal file
28
src/components/task-bar/Calendar.jsx
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import styles from "./Calendar.module.css";
|
||||
|
||||
export function Calendar() {
|
||||
const [date, setDate] = useState(new Date());
|
||||
|
||||
useEffect(() => {
|
||||
setInterval(() => {
|
||||
setDate(new Date());
|
||||
}, 30000);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<button className={styles.Button} title="Date & Time" style={{ userSelect: "none" }}>
|
||||
{date.toLocaleString("en-US", {
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
hour12: false,
|
||||
})}
|
||||
<br/>
|
||||
{date.toLocaleDateString("en-GB", {
|
||||
day: "numeric",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
})}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
3
src/components/task-bar/Calendar.module.css
Normal file
3
src/components/task-bar/Calendar.module.css
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
.Button {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import styles from "./HomeMenu.module.css";
|
||||
import appStyles from "./AppList.module.css";
|
||||
import { faCircleInfo, faFileLines, faGear, faImage, faPowerOff } from "@fortawesome/free-solid-svg-icons";
|
||||
import { useWindowsManager } from "../../hooks/windows/WindowsManagerContext.js";
|
||||
import ApplicationsManager from "../../features/applications/applications.js";
|
||||
|
|
@ -8,7 +9,7 @@ import { closeTab } from "../../features/utils/browser.js";
|
|||
import { useKeyboardListener } from "../../hooks/utils/keyboard.js";
|
||||
import { useVirtualRoot } from "../../hooks/virtual-drive/VirtualRootContext.js";
|
||||
|
||||
export function HomeMenu({ active, setActive }) {
|
||||
export function HomeMenu({ active, setActive, search }) {
|
||||
const windowsManager = useWindowsManager();
|
||||
const virtualRoot = useVirtualRoot();
|
||||
|
||||
|
|
@ -23,6 +24,10 @@ export function HomeMenu({ active, setActive }) {
|
|||
onlyAltKey = true;
|
||||
} else {
|
||||
onlyAltKey = false;
|
||||
|
||||
if (active && event.key.length === 1) {
|
||||
search(event.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -39,7 +44,7 @@ export function HomeMenu({ active, setActive }) {
|
|||
useKeyboardListener({ onKeyDown, onKeyUp });
|
||||
|
||||
return (
|
||||
<div className={classNames.join(" ")} onKeyDown={onKeyDown}>
|
||||
<div className={classNames.join(" ")}>
|
||||
<div className={styles["Container-inner"]}>
|
||||
<div className={styles.Buttons}>
|
||||
<button title="Power" onClick={() => { closeTab(); }}>
|
||||
|
|
@ -72,11 +77,11 @@ export function HomeMenu({ active, setActive }) {
|
|||
</div>
|
||||
<div className={styles.Apps}>
|
||||
<h2>Apps</h2>
|
||||
<div className={styles["App-list"]}>
|
||||
<div className={appStyles["App-list"]}>
|
||||
{ApplicationsManager.APPLICATIONS.map(({ name, id }) =>
|
||||
<button
|
||||
key={id}
|
||||
className={styles["App-button"]}
|
||||
className={appStyles["App-button"]}
|
||||
onClick={() => {
|
||||
setActive(false);
|
||||
windowsManager.open(id);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,11 @@
|
|||
transition: opacity 200ms ease-in-out, transform 200ms ease-in-out;
|
||||
}
|
||||
|
||||
|
||||
.Container-outer:not(.Active) {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.Container-outer:not(.Active) .Container-inner {
|
||||
opacity: 0;
|
||||
transform: translateY(100px);
|
||||
|
|
@ -35,6 +40,8 @@
|
|||
flex-direction: column-reverse;
|
||||
align-items: center;
|
||||
padding: 0.25rem;
|
||||
margin-top: auto;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.Buttons > button {
|
||||
|
|
@ -47,10 +54,9 @@
|
|||
}
|
||||
|
||||
.Apps {
|
||||
--scrollbar-color: rgba(0, 0, 0, 25%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
width: 100% !important;
|
||||
max-height: 20rem;
|
||||
padding: 1rem;
|
||||
padding-right: 0.25rem;
|
||||
|
|
@ -63,29 +69,4 @@
|
|||
font-size: 1rem;
|
||||
margin: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.App-list {
|
||||
overflow: auto;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.App-list > button {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.App-list > button > div,
|
||||
.App-list > button > div > div,
|
||||
.App-list > button > div > div > svg {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.App-list > button > p {
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
10
src/components/task-bar/Network.jsx
Normal file
10
src/components/task-bar/Network.jsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { faWifi } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
export function Network() {
|
||||
return (
|
||||
<button title="Wifi">
|
||||
<FontAwesomeIcon icon={faWifi}/>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
57
src/components/task-bar/SearchMenu.jsx
Normal file
57
src/components/task-bar/SearchMenu.jsx
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import styles from "./SearchMenu.module.css";
|
||||
import appStyles from "./AppList.module.css";
|
||||
import ApplicationsManager from "../../features/applications/applications.js";
|
||||
import { useWindowsManager } from "../../hooks/windows/WindowsManagerContext.js";
|
||||
import { ReactSVG } from "react-svg";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export function SearchMenu({ active, setActive, searchQuery, setSearchQuery, inputRef }) {
|
||||
const windowsManager = useWindowsManager();
|
||||
|
||||
useEffect(() => {
|
||||
if (inputRef.current)
|
||||
inputRef.current.focus();
|
||||
}, [inputRef]);
|
||||
|
||||
const onChange = (event) => {
|
||||
const value = event.target.value;
|
||||
setSearchQuery(value);
|
||||
}
|
||||
|
||||
const classNames = [styles["Container-outer"]];
|
||||
if (active)
|
||||
classNames.push(styles.Active);
|
||||
|
||||
return (
|
||||
<div className={classNames.join(" ")}>
|
||||
<div className={styles["Container-inner"]}>
|
||||
<div className={appStyles["App-list"]}>
|
||||
{ApplicationsManager.APPLICATIONS.filter(({ name }) =>
|
||||
name.toLowerCase().includes(searchQuery.toLowerCase().trim())
|
||||
).map(({ name, id }) =>
|
||||
<button
|
||||
key={id}
|
||||
className={appStyles["App-button"]}
|
||||
onClick={() => {
|
||||
setActive(false);
|
||||
windowsManager.open(id);
|
||||
}}
|
||||
title={name}
|
||||
>
|
||||
<ReactSVG src={`${process.env.PUBLIC_URL}/media/applications/icons/${id}.svg`}/>
|
||||
<p>{name}</p>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<input
|
||||
ref={inputRef}
|
||||
className={styles.Input}
|
||||
value={searchQuery}
|
||||
onChange={onChange}
|
||||
spellCheck={false}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
50
src/components/task-bar/SearchMenu.module.css
Normal file
50
src/components/task-bar/SearchMenu.module.css
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
.Container-outer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 100%;
|
||||
height: auto !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.Container-inner {
|
||||
opacity: 1;
|
||||
background-color: rgba(25, 25, 25, 75%);
|
||||
backdrop-filter: blur(15px);
|
||||
transform: none;
|
||||
transition: opacity 200ms ease-in-out, transform 200ms ease-in-out;
|
||||
}
|
||||
|
||||
.Container-outer:not(.Active) {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.Container-outer:not(.Active) .Container-inner {
|
||||
opacity: 0;
|
||||
transform: translateY(100px);
|
||||
}
|
||||
|
||||
.Container-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 13rem;
|
||||
max-width: 29rem;
|
||||
max-height: 20rem;
|
||||
padding: 0.5rem;
|
||||
border-top-left-radius: 0.5rem;
|
||||
border-top-right-radius: 0.5rem;
|
||||
border-bottom-right-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
resize: horizontal;
|
||||
}
|
||||
|
||||
.Input {
|
||||
width: 100%;
|
||||
padding: 0.25rem 0.5rem;
|
||||
color: var(--foreground-color-a);
|
||||
background-color: rgba(0, 0, 0, 25%);
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
outline: none;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import styles from "./TaskBar.module.css";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faBatteryFull, faSearch, faVolumeHigh, faWifi } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faSearch } from "@fortawesome/free-solid-svg-icons";
|
||||
import ApplicationsManager from "../../features/applications/applications.js";
|
||||
import { useWindows } from "../../hooks/windows/WindowsContext.js";
|
||||
import { useWindowsManager } from "../../hooks/windows/WindowsManagerContext.js";
|
||||
|
|
@ -10,6 +10,12 @@ import { ReactSVG } from "react-svg";
|
|||
import Application from "../../features/applications/application.js";
|
||||
import { HomeMenu } from "./HomeMenu.jsx";
|
||||
import OutsideClickListener from "../../hooks/utils/outsideClick.js";
|
||||
import { Battery } from "./Battery.jsx";
|
||||
import { Network } from "./Network.jsx";
|
||||
import { Volume } from "./Volume.jsx";
|
||||
import { SearchMenu } from "./SearchMenu.jsx";
|
||||
import { Calendar } from "./Calendar.jsx";
|
||||
import { useScrollWithShadow } from "../../hooks/utils/scrollWithShadows.js";
|
||||
|
||||
/**
|
||||
* @param {Object} props
|
||||
|
|
@ -41,56 +47,99 @@ function AppButton({ app }) {
|
|||
}
|
||||
|
||||
export function Taskbar() {
|
||||
const [date, setDate] = useState(new Date());
|
||||
const [showHome, setShowHome] = useState(false);
|
||||
const [showSearch, setShowSearch] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const ref = useRef(null);
|
||||
const { boxShadow, onUpdate } = useScrollWithShadow({
|
||||
ref,
|
||||
shadow: {
|
||||
offset: 20,
|
||||
blurRadius: 10,
|
||||
spreadRadius: -10,
|
||||
color: {
|
||||
a: 25
|
||||
}
|
||||
}
|
||||
});
|
||||
const inputRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
setInterval(() => {
|
||||
setDate(new Date());
|
||||
}, 30000);
|
||||
}, []);
|
||||
const updateShowHome = (value) => {
|
||||
setShowHome(value);
|
||||
|
||||
if (value) {
|
||||
setShowSearch(false);
|
||||
}
|
||||
};
|
||||
|
||||
const updateShowSearch = (value) => {
|
||||
setShowSearch(value);
|
||||
|
||||
if (value) {
|
||||
if (searchQuery !== "") {
|
||||
setSearchQuery("");
|
||||
}
|
||||
|
||||
setShowHome(false);
|
||||
|
||||
if (inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
if (!showSearch) {
|
||||
setSearchQuery("");
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
};
|
||||
|
||||
const search = (query) => {
|
||||
updateShowSearch(true);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles["Task-bar"]}>
|
||||
<div className={styles["Program-icons"]}>
|
||||
<div className={styles["Menu-icons"]}>
|
||||
<div className={styles["Home-container"]}>
|
||||
<OutsideClickListener onOutsideClick={() => { setShowHome(false); }}>
|
||||
<button title="Home" className={styles["Home-button"]} onClick={() => { setShowHome(!showHome); }}>
|
||||
<OutsideClickListener onOutsideClick={() => { updateShowHome(false); }}>
|
||||
<button title="Home"
|
||||
className={`${styles["Menu-button"]} ${styles["Home-button"]}`}
|
||||
onClick={() => { updateShowHome(!showHome); }}
|
||||
>
|
||||
<ReactSVG src={process.env.PUBLIC_URL + "/media/logo.svg"}/>
|
||||
</button>
|
||||
<HomeMenu active={showHome} setActive={setShowHome}/>
|
||||
<HomeMenu active={showHome} setActive={updateShowHome} search={search}/>
|
||||
</OutsideClickListener>
|
||||
</div>
|
||||
<button title="Search">
|
||||
<FontAwesomeIcon icon={faSearch}/>
|
||||
</button>
|
||||
<div className={styles["Search-container"]}>
|
||||
<OutsideClickListener onOutsideClick={() => { updateShowSearch(false); }}>
|
||||
<button title="Search"
|
||||
className={`${styles["Menu-button"]} ${styles["Search-button"]}`}
|
||||
onClick={() => { updateShowSearch(!showSearch); }}
|
||||
>
|
||||
<FontAwesomeIcon icon={faSearch}/>
|
||||
</button>
|
||||
<SearchMenu
|
||||
active={showSearch}
|
||||
setActive={updateShowSearch}
|
||||
searchQuery={searchQuery}
|
||||
setSearchQuery={setSearchQuery}
|
||||
inputRef={inputRef}
|
||||
/>
|
||||
</OutsideClickListener>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles["App-icons"]} onScroll={onUpdate} onResize={onUpdate} ref={ref} style={{ boxShadow }}>
|
||||
{ApplicationsManager.APPLICATIONS.map((app) =>
|
||||
<AppButton app={app} key={app.id}/>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles["Util-icons"]}>
|
||||
<button title="Battery">
|
||||
<FontAwesomeIcon icon={faBatteryFull}/>
|
||||
</button>
|
||||
<button title="Wifi">
|
||||
<FontAwesomeIcon icon={faWifi}/>
|
||||
</button>
|
||||
<button title="Volume">
|
||||
<FontAwesomeIcon icon={faVolumeHigh}/>
|
||||
</button>
|
||||
<button title="Date & Time" style={{ userSelect: "none" }}>
|
||||
{date.toLocaleString("en-US", {
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
hour12: false,
|
||||
})}
|
||||
<br/>
|
||||
{date.toLocaleDateString("en-GB", {
|
||||
day: "numeric",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
})}
|
||||
</button>
|
||||
<Battery/>
|
||||
<Network/>
|
||||
<Volume/>
|
||||
<Calendar/>
|
||||
<button title="View Desktop" id="desktop-button"/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
.Task-bar {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
|
|
@ -26,13 +25,15 @@
|
|||
border: none;
|
||||
outline: none;
|
||||
transition: background-color 100ms ease-in-out;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.Task-bar button:hover {
|
||||
background-color: var(--task-bar-button-hover-color);
|
||||
}
|
||||
|
||||
.Home-container {
|
||||
.Home-container,
|
||||
.Search-container {
|
||||
position: relative;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
|
@ -42,27 +43,37 @@
|
|||
filter: none;
|
||||
}
|
||||
|
||||
.Program-icons {
|
||||
.Menu-icons,
|
||||
.App-icons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.Program-icons > *, .Home-button {
|
||||
.App-icons {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.App-icons::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.App-icons > *,
|
||||
.Menu-button {
|
||||
height: 100%;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.Program-icons > * > svg {
|
||||
.App-icons > * > svg,
|
||||
.Menu-button > svg {
|
||||
height: 1.25rem;
|
||||
}
|
||||
|
||||
.Program-icons > * > div,
|
||||
.Program-icons > * > div > div,
|
||||
.Program-icons > * > div > div > svg,
|
||||
.Home-button > div,
|
||||
.Home-button > div > div,
|
||||
.Home-button > div > div > svg {
|
||||
.Menu-icons div,
|
||||
.Menu-icons div > svg,
|
||||
.App-icons div,
|
||||
.App-icons div > svg {
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
|
@ -97,6 +108,9 @@
|
|||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
margin-left: auto;
|
||||
padding-left: 0.5rem;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.Util-icons > * {
|
||||
|
|
|
|||
10
src/components/task-bar/Volume.jsx
Normal file
10
src/components/task-bar/Volume.jsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { faVolumeHigh } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
export function Volume() {
|
||||
return (
|
||||
<button title="Volume">
|
||||
<FontAwesomeIcon icon={faVolumeHigh}/>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
137
src/hooks/utils/scrollWithShadows.js
Normal file
137
src/hooks/utils/scrollWithShadows.js
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
/**
|
||||
* https://medium.com/dfind-consulting/react-scroll-hook-with-shadows-9ba2d47ae32
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import React, { useCallback, useEffect, useRef } from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {React.ElementRef} options.ref
|
||||
* @param {Boolean=true} options.horizontal
|
||||
* @param {Boolean=true} options.dynamicOffset
|
||||
* @param {Number=1} options.dynamicOffsetFactor
|
||||
* @param {Object} options.shadow
|
||||
* @param {Number=8} options.shadow.offset
|
||||
* @param {Number=5} options.shadow.blurRadius
|
||||
* @param {Number=-5} options.shadow.spreadRadius
|
||||
* @param {Object} options.shadow.color
|
||||
* @param {Number=0} options.shadow.color.r
|
||||
* @param {Number=0} options.shadow.color.g
|
||||
* @param {Number=0} options.shadow.color.b
|
||||
* @param {Number=50} options.shadow.color.a
|
||||
*/
|
||||
export function useScrollWithShadow(options) {
|
||||
const [initiated, setInitiated] = useState(false);
|
||||
const [scrollStart, setScrollStart] = useState(0);
|
||||
const [scrollLength, setScrollLength] = useState(0);
|
||||
const [clientLength, setClientLength] = useState(0);
|
||||
|
||||
if (options == null)
|
||||
options = {};
|
||||
if (options.shadow == null)
|
||||
options.shadow = {};
|
||||
if (options.shadow.color == null)
|
||||
options.shadow.color = {};
|
||||
|
||||
const {
|
||||
ref,
|
||||
horizontal = true,
|
||||
dynamicOffset = true,
|
||||
dynamicOffsetFactor = 3,
|
||||
shadow: {
|
||||
offset = 8,
|
||||
blurRadius = 5,
|
||||
spreadRadius = -5,
|
||||
color: {
|
||||
r = 0,
|
||||
g = 0,
|
||||
b = 0,
|
||||
a = 50
|
||||
}
|
||||
}
|
||||
} = options;
|
||||
|
||||
const updateValues = useCallback((element) => {
|
||||
if (!element)
|
||||
return;
|
||||
|
||||
console.log(element);
|
||||
|
||||
setScrollStart(horizontal ? element.scrollLeft : element.scrollTop);
|
||||
setScrollLength(horizontal ? element.scrollWidth : element.scrollHeight);
|
||||
setClientLength(horizontal ? element.clientWidth : element.clientHeight);
|
||||
}, [horizontal]);
|
||||
|
||||
const onUpdate = (event) => {
|
||||
console.log(event);
|
||||
updateValues(event.target);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const onResize = () => {
|
||||
updateValues(ref.current);
|
||||
};
|
||||
|
||||
if (ref.current && !initiated) {
|
||||
console.log(ref.current);
|
||||
setInitiated(true);
|
||||
updateValues(ref.current);
|
||||
}
|
||||
|
||||
window.addEventListener("resize", onResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", onResize);
|
||||
}
|
||||
}, [ref, updateValues, initiated]);
|
||||
|
||||
const getBoxShadow = () => {
|
||||
const startDistance = scrollStart;
|
||||
const endDistance = scrollLength - scrollStart - clientLength;
|
||||
|
||||
const isStart = startDistance === 0;
|
||||
const isEnd = endDistance === 0;
|
||||
const isBetween = scrollStart > 0 && clientLength < scrollLength - scrollStart;
|
||||
|
||||
let startOffset = offset;
|
||||
let endOffset = offset;
|
||||
|
||||
if (dynamicOffset) {
|
||||
startOffset = startDistance * dynamicOffsetFactor - offset;
|
||||
endOffset = endDistance * dynamicOffsetFactor - offset;
|
||||
|
||||
if (startOffset > offset) {
|
||||
startOffset = offset;
|
||||
} else if (startOffset < 0) {
|
||||
startOffset = 0;
|
||||
}
|
||||
if (endOffset > offset) {
|
||||
endOffset = offset;
|
||||
} else if (endOffset < 0) {
|
||||
endOffset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
const shadowStartOffset = horizontal ? `${startOffset}px 0` : `0 ${startOffset}px`;
|
||||
const shadowEndOffset = horizontal ? `-${endOffset}px 0` : `0 -${endOffset}px`;
|
||||
|
||||
const start = `inset ${shadowStartOffset} ${blurRadius}px ${spreadRadius}px rgba(${r}, ${g}, ${b}, ${a}%)`;
|
||||
const end = `inset ${shadowEndOffset} ${blurRadius}px ${spreadRadius}px rgba(${r}, ${g}, ${b}, ${a}%)`;
|
||||
|
||||
let boxShadow = "none";
|
||||
|
||||
if (isStart) {
|
||||
boxShadow = end;
|
||||
} else if (isBetween) {
|
||||
boxShadow = `${start}, ${end}`;
|
||||
} else if (isEnd) {
|
||||
boxShadow = start;
|
||||
}
|
||||
|
||||
return boxShadow;
|
||||
};
|
||||
|
||||
return { boxShadow: getBoxShadow(), onUpdate };
|
||||
}
|
||||
Loading…
Reference in a new issue