feat(terminal): terminal wrapper and context
This commit is contained in:
@@ -1,20 +1,62 @@
|
|||||||
import { type FunctionComponent, type PropsWithChildren } from "react";
|
import { useRef, useState, useEffect, type ReactNode } from "react";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
import { TerminalContextProvider } from "~/context/TerminalContext";
|
||||||
|
|
||||||
type TerminalProps = PropsWithChildren<{
|
export const Terminal = (props: {
|
||||||
|
children?: ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
}>;
|
}) => {
|
||||||
|
const terminalRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
export const Terminal: FunctionComponent<TerminalProps> = ({
|
const [size, setSize] = useState<{ cols: number; rows: number }>();
|
||||||
children,
|
|
||||||
className,
|
useEffect(() => {
|
||||||
}) => (
|
const precision = 300;
|
||||||
<div
|
|
||||||
className={clsx(
|
const calculateSize = () => {
|
||||||
"rounded-lg border-2 border-[#595959] bg-[#1e1e2e] bg-opacity-95 px-1 text-[#cdd6f4] shadow-window transition-colors duration-[500ms] ease-out hover:border-[#cdd6f4] hover:duration-[200ms]",
|
if (!terminalRef.current) return;
|
||||||
className,
|
|
||||||
)}
|
const node = document.createElement("span");
|
||||||
>
|
node.style.color = "transparent";
|
||||||
{children}
|
node.style.position = "absolute";
|
||||||
</div>
|
node.textContent = "A".repeat(precision);
|
||||||
);
|
|
||||||
|
terminalRef.current.appendChild(node);
|
||||||
|
|
||||||
|
setSize({
|
||||||
|
cols: Math.floor(
|
||||||
|
(terminalRef.current.offsetWidth - 4) /
|
||||||
|
(node.offsetWidth / precision),
|
||||||
|
),
|
||||||
|
rows: Math.floor(
|
||||||
|
(terminalRef.current.offsetHeight - 4) / node.offsetHeight,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
node.remove();
|
||||||
|
};
|
||||||
|
|
||||||
|
calculateSize();
|
||||||
|
|
||||||
|
window.addEventListener("resize", calculateSize);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("resize", calculateSize);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TerminalContextProvider value={size}>
|
||||||
|
<div
|
||||||
|
ref={terminalRef}
|
||||||
|
className={clsx(
|
||||||
|
"overflow-hidden rounded-lg border-2 border-borderInactive bg-background bg-opacity-80 text-lg text-color7 text-foreground shadow-window transition-colors duration-[500ms] ease-out hover:border-borderActive hover:duration-[200ms]",
|
||||||
|
props.className,
|
||||||
|
)}
|
||||||
|
style={{ backdropFilter: "blur(2px)" }}
|
||||||
|
>
|
||||||
|
{size && props.children}
|
||||||
|
</div>
|
||||||
|
</TerminalContextProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
15
src/context/TerminalContext.tsx
Normal file
15
src/context/TerminalContext.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { createContext, useContext } from "react";
|
||||||
|
|
||||||
|
const TerminalContext = createContext<{ cols: number; height: number } | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const TerminalContextProvider = TerminalContext.Provider;
|
||||||
|
|
||||||
|
export const useTerminal = () => {
|
||||||
|
const context = useContext(TerminalContext);
|
||||||
|
if (!context)
|
||||||
|
throw new Error("useTerminal must be used inside a Terminal component");
|
||||||
|
|
||||||
|
return context;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user