Updates
This commit is contained in:
parent
979728e6c8
commit
0b7c70058d
@ -69,7 +69,7 @@ export default function PopoverComponent({
|
|||||||
<Paper
|
<Paper
|
||||||
{...props}
|
{...props}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"max-w-[300px]",
|
"max-w-[300px] z-[250]",
|
||||||
"twui-popover-content",
|
"twui-popover-content",
|
||||||
props.className
|
props.className
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -60,6 +60,8 @@
|
|||||||
--radius-default-xs: 1px;
|
--radius-default-xs: 1px;
|
||||||
--radius-default-lg: 7px;
|
--radius-default-lg: 7px;
|
||||||
--radius-default-xl: 10px;
|
--radius-default-xl: 10px;
|
||||||
|
|
||||||
|
--container-container: 1200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@custom-variant dark (&:where(.dark, .dark *));
|
@custom-variant dark (&:where(.dark, .dark *));
|
||||||
@ -155,3 +157,16 @@ option {
|
|||||||
.normal-text {
|
.normal-text {
|
||||||
@apply text-foreground-light dark:text-foreground-dark;
|
@apply text-foreground-light dark:text-foreground-dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ol {
|
||||||
|
list-style: decimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style: disc;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul,
|
||||||
|
ol {
|
||||||
|
margin-left: 25px;
|
||||||
|
}
|
||||||
|
|||||||
@ -115,7 +115,7 @@ export default function TWUIDocsLink({
|
|||||||
<TWUIDocsLink
|
<TWUIDocsLink
|
||||||
key={index}
|
key={index}
|
||||||
docLink={link}
|
docLink={link}
|
||||||
className="text-sm opacity-70"
|
className="opacity-70"
|
||||||
autoExpandAll={autoExpandAll}
|
autoExpandAll={autoExpandAll}
|
||||||
child
|
child
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -114,7 +114,7 @@ export default function AceEditor({
|
|||||||
return function () {
|
return function () {
|
||||||
editor.destroy();
|
editor.destroy();
|
||||||
};
|
};
|
||||||
}, [refresh, darkMode, ready, externalRefresh, content]);
|
}, [refresh, darkMode, ready, externalRefresh]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const htmlClassName = document.documentElement.className;
|
const htmlClassName = document.documentElement.className;
|
||||||
@ -145,7 +145,7 @@ export default function AceEditor({
|
|||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<span className="text-sm m-0">
|
<span className="m-0">
|
||||||
Editor Error:{" "}
|
Editor Error:{" "}
|
||||||
<b className="text-red-600">{error.message}</b>
|
<b className="text-red-600">{error.message}</b>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@ -3,9 +3,9 @@ import { RawEditorOptions, TinyMCE, Editor } from "./tinymce";
|
|||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
import twuiSlugToNormalText from "../../utils/slug-to-normal-text";
|
import twuiSlugToNormalText from "../../utils/slug-to-normal-text";
|
||||||
import Border from "../../elements/Border";
|
import Border from "../../elements/Border";
|
||||||
|
import useTinyMCE from "./useTinyMCE";
|
||||||
|
|
||||||
export type TinyMCEEditorProps<KeyType extends string> = {
|
export type TinyMCEEditorProps<KeyType extends string> = {
|
||||||
tinyMCE?: TinyMCE | null;
|
|
||||||
options?: RawEditorOptions;
|
options?: RawEditorOptions;
|
||||||
editorRef?: React.MutableRefObject<Editor | null>;
|
editorRef?: React.MutableRefObject<Editor | null>;
|
||||||
setEditor?: React.Dispatch<React.SetStateAction<Editor>>;
|
setEditor?: React.Dispatch<React.SetStateAction<Editor>>;
|
||||||
@ -26,8 +26,6 @@ export type TinyMCEEditorProps<KeyType extends string> = {
|
|||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
let interval: any;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* # Tiny MCE Editor Component
|
* # Tiny MCE Editor Component
|
||||||
* @className_wrapper twui-rte-wrapper
|
* @className_wrapper twui-rte-wrapper
|
||||||
@ -35,8 +33,7 @@ let interval: any;
|
|||||||
export default function TinyMCEEditor<KeyType extends string>({
|
export default function TinyMCEEditor<KeyType extends string>({
|
||||||
options,
|
options,
|
||||||
editorRef,
|
editorRef,
|
||||||
setEditor,
|
setEditor: passedSetEditor,
|
||||||
tinyMCE,
|
|
||||||
wrapperProps,
|
wrapperProps,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
changeHandler,
|
changeHandler,
|
||||||
@ -47,29 +44,49 @@ export default function TinyMCEEditor<KeyType extends string>({
|
|||||||
useParentCSS,
|
useParentCSS,
|
||||||
placeholder,
|
placeholder,
|
||||||
}: TinyMCEEditorProps<KeyType>) {
|
}: TinyMCEEditorProps<KeyType>) {
|
||||||
|
const { tinyMCE } = useTinyMCE();
|
||||||
|
|
||||||
const editorComponentRef = React.useRef<HTMLDivElement>(null);
|
const editorComponentRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const EDITOR_VALUE_CHANGE_TIMEOUT = 500;
|
||||||
|
|
||||||
const FINAL_HEIGHT = options?.height || 500;
|
const FINAL_HEIGHT = options?.height || 500;
|
||||||
const [themeReady, setThemeReady] = React.useState(false);
|
const [themeReady, setThemeReady] = React.useState(false);
|
||||||
const [ready, setReady] = React.useState(false);
|
const [ready, setReady] = React.useState(false);
|
||||||
const [darkMode, setDarkMode] = React.useState(false);
|
const [darkMode, setDarkMode] = React.useState(false);
|
||||||
|
const [refresh, setRefresh] = React.useState(0);
|
||||||
|
const [editor, setEditor] = React.useState<Editor>();
|
||||||
|
|
||||||
const title = name ? twuiSlugToNormalText(name) : "Rich Text";
|
const title = name ? twuiSlugToNormalText(name) : "Rich Text";
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const htmlClassName = document.documentElement.className;
|
if (!tinyMCE) {
|
||||||
if (htmlClassName.match(/dark/i)) setDarkMode(true);
|
|
||||||
setTimeout(() => {
|
|
||||||
setThemeReady(true);
|
|
||||||
}, 200);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (!editorComponentRef.current || !themeReady) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tinyMCE?.init({
|
const htmlClassName = document.documentElement.className;
|
||||||
|
|
||||||
|
if (htmlClassName.match(/dark/i)) setDarkMode(true);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setThemeReady(true);
|
||||||
|
}, 200);
|
||||||
|
}, [tinyMCE]);
|
||||||
|
|
||||||
|
let valueTimeout: any;
|
||||||
|
|
||||||
|
const id = crypto.randomUUID();
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!editorComponentRef.current || !themeReady || !tinyMCE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseUrl =
|
||||||
|
process.env.NEXT_PUBLIC_TINYMCE_BASE_URL ||
|
||||||
|
"https://www.datasquirel.com/tinymce-public";
|
||||||
|
|
||||||
|
tinyMCE.init({
|
||||||
height: FINAL_HEIGHT,
|
height: FINAL_HEIGHT,
|
||||||
menubar: false,
|
menubar: false,
|
||||||
plugins:
|
plugins:
|
||||||
@ -79,20 +96,30 @@ export default function TinyMCEEditor<KeyType extends string>({
|
|||||||
content_style:
|
content_style:
|
||||||
"body { font-family:Helvetica,Arial,sans-serif; font-size:14px; background-color: transparent }",
|
"body { font-family:Helvetica,Arial,sans-serif; font-size:14px; background-color: transparent }",
|
||||||
init_instance_callback: (editor) => {
|
init_instance_callback: (editor) => {
|
||||||
setEditor?.(editor as any);
|
setEditor(editor as any);
|
||||||
if (editorRef) editorRef.current = editor as any;
|
if (editorRef) editorRef.current = editor as any;
|
||||||
if (defaultValue) editor.setContent(defaultValue);
|
if (defaultValue) editor.setContent(defaultValue);
|
||||||
setReady(true);
|
setReady(true);
|
||||||
|
|
||||||
editor.on("change", (e) => {
|
// editor.on("change", (e) => {
|
||||||
changeHandler?.(editor.getContent());
|
// changeHandler?.(editor.getContent());
|
||||||
|
// });
|
||||||
|
|
||||||
|
editor.on("input", (e) => {
|
||||||
|
if (changeHandler) {
|
||||||
|
window.clearTimeout(valueTimeout);
|
||||||
|
|
||||||
|
valueTimeout = setTimeout(() => {
|
||||||
|
changeHandler(editor.getContent());
|
||||||
|
}, EDITOR_VALUE_CHANGE_TIMEOUT);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (useParentCSS) {
|
if (useParentCSS) {
|
||||||
useParentStyles(editor);
|
useParentStyles(editor);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
base_url: "https://www.datasquirel.com/tinymce-public",
|
base_url: baseUrl,
|
||||||
body_class: "twui-tinymce",
|
body_class: "twui-tinymce",
|
||||||
placeholder,
|
placeholder,
|
||||||
relative_urls: true,
|
relative_urls: true,
|
||||||
@ -106,9 +133,14 @@ export default function TinyMCEEditor<KeyType extends string>({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return function () {
|
return function () {
|
||||||
tinyMCE?.remove();
|
if (!ready) return;
|
||||||
|
|
||||||
|
const instance = editorComponentRef.current
|
||||||
|
? tinyMCE?.get(editorComponentRef.current?.id)
|
||||||
|
: undefined;
|
||||||
|
instance?.remove();
|
||||||
};
|
};
|
||||||
}, [tinyMCE, themeReady]);
|
}, [tinyMCE, themeReady, refresh]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -129,7 +161,7 @@ export default function TinyMCEEditor<KeyType extends string>({
|
|||||||
"bg-background-light dark:bg-background-dark text-gray-500",
|
"bg-background-light dark:bg-background-dark text-gray-500",
|
||||||
"dark:text-white/80 rounded"
|
"dark:text-white/80 rounded"
|
||||||
)}
|
)}
|
||||||
htmlFor={name || "twui-tinymce"}
|
htmlFor={id}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</label>
|
</label>
|
||||||
@ -153,7 +185,7 @@ export default function TinyMCEEditor<KeyType extends string>({
|
|||||||
"bg-slate-200 dark:bg-slate-700 rounded-sm w-full",
|
"bg-slate-200 dark:bg-slate-700 rounded-sm w-full",
|
||||||
"twui-rte-wrapper"
|
"twui-rte-wrapper"
|
||||||
)}
|
)}
|
||||||
id={name || "twui-tinymce"}
|
id={id}
|
||||||
></div>
|
></div>
|
||||||
</Border>
|
</Border>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -4,32 +4,49 @@ import { TinyMCE } from "./tinymce";
|
|||||||
let interval: any;
|
let interval: any;
|
||||||
|
|
||||||
export default function useTinyMCE() {
|
export default function useTinyMCE() {
|
||||||
const [tinyMCE, setTinyMCE] = React.useState<TinyMCE | null>(null);
|
const [tinyMCE, setTinyMCE] = React.useState<TinyMCE>();
|
||||||
|
const [refresh, setRefresh] = React.useState(0);
|
||||||
|
const [scriptLoaded, setScriptLoaded] = React.useState(false);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
// @ts-ignore
|
if (refresh >= 5) return;
|
||||||
if (window.tinymce) {
|
|
||||||
console.log("Tinymce already exists");
|
const clientWindow = window as Window & { tinymce?: TinyMCE };
|
||||||
// @ts-ignore
|
|
||||||
setTinyMCE(window.tinymce);
|
if (clientWindow.tinymce) {
|
||||||
|
setScriptLoaded(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const script = document.createElement("script");
|
const script = document.createElement("script");
|
||||||
script.src =
|
|
||||||
"https://www.datasquirel.com/tinymce-public/tinymce.min.js";
|
const baseUrl =
|
||||||
|
process.env.NEXT_PUBLIC_TINYMCE_BASE_URL ||
|
||||||
|
"https://www.datasquirel.com/tinymce-public";
|
||||||
|
|
||||||
|
script.src = `${baseUrl}/tinymce.min.js`;
|
||||||
script.async = true;
|
script.async = true;
|
||||||
|
|
||||||
document.head.appendChild(script);
|
|
||||||
|
|
||||||
script.onload = () => {
|
script.onload = () => {
|
||||||
// @ts-ignore
|
setScriptLoaded(true);
|
||||||
if (window.tinymce) {
|
|
||||||
// @ts-ignore
|
|
||||||
setTinyMCE(window.tinymce);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}, []);
|
|
||||||
|
document.head.appendChild(script);
|
||||||
|
}, [refresh]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!scriptLoaded) return;
|
||||||
|
|
||||||
|
const clientWindow = window as Window & { tinymce?: TinyMCE };
|
||||||
|
|
||||||
|
let tinyMCE = clientWindow.tinymce;
|
||||||
|
|
||||||
|
if (tinyMCE) {
|
||||||
|
setTinyMCE(tinyMCE);
|
||||||
|
} else {
|
||||||
|
setRefresh((prev) => prev + 1);
|
||||||
|
}
|
||||||
|
}, [scriptLoaded]);
|
||||||
|
|
||||||
return { tinyMCE };
|
return { tinyMCE };
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,8 @@ export const TWUIDropdownContentPositions = [
|
|||||||
"left",
|
"left",
|
||||||
"bottom-left",
|
"bottom-left",
|
||||||
"top-left",
|
"top-left",
|
||||||
|
"top",
|
||||||
|
"bottom",
|
||||||
"right",
|
"right",
|
||||||
"bottom-right",
|
"bottom-right",
|
||||||
"top-right",
|
"top-right",
|
||||||
@ -160,6 +162,8 @@ export default function Dropdown({
|
|||||||
? "right-0 top-[100%]"
|
? "right-0 top-[100%]"
|
||||||
: position == "center"
|
: position == "center"
|
||||||
? "left-[50%] -translate-x-[50%] top-[100%]"
|
? "left-[50%] -translate-x-[50%] top-[100%]"
|
||||||
|
: position == "top"
|
||||||
|
? "left-[50%] -translate-x-[50%] bottom-[100%]"
|
||||||
: "top-[100%]",
|
: "top-[100%]",
|
||||||
above ? "-translate-y-[120%]" : "",
|
above ? "-translate-y-[120%]" : "",
|
||||||
open ? "flex" : "hidden",
|
open ? "flex" : "hidden",
|
||||||
|
|||||||
@ -2,19 +2,26 @@ import { ComponentProps, DetailedHTMLProps, HTMLAttributes } from "react";
|
|||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
import Center from "../layout/Center";
|
import Center from "../layout/Center";
|
||||||
import Loading from "./Loading";
|
import Loading from "./Loading";
|
||||||
|
import Row from "../layout/Row";
|
||||||
|
import Span from "../layout/Span";
|
||||||
|
|
||||||
type Props = DetailedHTMLProps<
|
type Props = DetailedHTMLProps<
|
||||||
HTMLAttributes<HTMLDivElement>,
|
HTMLAttributes<HTMLDivElement>,
|
||||||
HTMLDivElement
|
HTMLDivElement
|
||||||
> & {
|
> & {
|
||||||
loadingProps?: ComponentProps<typeof Loading>;
|
loadingProps?: ComponentProps<typeof Loading>;
|
||||||
|
label?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* # Loading Overlay Component
|
* # Loading Overlay Component
|
||||||
* @className_wrapper twui-loading-overlay
|
* @className_wrapper twui-loading-overlay
|
||||||
*/
|
*/
|
||||||
export default function LoadingOverlay({ loadingProps, ...props }: Props) {
|
export default function LoadingOverlay({
|
||||||
|
loadingProps,
|
||||||
|
label,
|
||||||
|
...props
|
||||||
|
}: Props) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
{...props}
|
{...props}
|
||||||
@ -26,7 +33,10 @@ export default function LoadingOverlay({ loadingProps, ...props }: Props) {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Center>
|
<Center>
|
||||||
|
<Row>
|
||||||
<Loading {...loadingProps} />
|
<Loading {...loadingProps} />
|
||||||
|
{label && <Span>{label}</Span>}
|
||||||
|
</Row>
|
||||||
</Center>
|
</Center>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -32,6 +32,7 @@ export type TWUI_MODAL_PROPS = DetailedHTMLProps<
|
|||||||
trigger?: (typeof TWUIPopoverTriggers)[number];
|
trigger?: (typeof TWUIPopoverTriggers)[number];
|
||||||
debounce?: number;
|
debounce?: number;
|
||||||
onClose?: () => any;
|
onClose?: () => any;
|
||||||
|
hoverOpen?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,6 +56,7 @@ export default function Modal(props: TWUI_MODAL_PROPS) {
|
|||||||
trigger = "hover",
|
trigger = "hover",
|
||||||
debounce = 500,
|
debounce = 500,
|
||||||
onClose,
|
onClose,
|
||||||
|
hoverOpen,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [ready, setReady] = React.useState(false);
|
const [ready, setReady] = React.useState(false);
|
||||||
@ -65,6 +67,9 @@ export default function Modal(props: TWUI_MODAL_PROPS) {
|
|||||||
const modalRoot = document.getElementById(IDName);
|
const modalRoot = document.getElementById(IDName);
|
||||||
|
|
||||||
if (modalRoot) {
|
if (modalRoot) {
|
||||||
|
if (isPopover) {
|
||||||
|
modalRoot.style.zIndex = "1000";
|
||||||
|
}
|
||||||
setReady(true);
|
setReady(true);
|
||||||
} else {
|
} else {
|
||||||
const newModalRootEl = document.createElement("div");
|
const newModalRootEl = document.createElement("div");
|
||||||
@ -144,12 +149,12 @@ export default function Modal(props: TWUI_MODAL_PROPS) {
|
|||||||
onClick={(e) => setOpen(!open)}
|
onClick={(e) => setOpen(!open)}
|
||||||
ref={finalTargetRef}
|
ref={finalTargetRef}
|
||||||
onMouseEnter={
|
onMouseEnter={
|
||||||
isPopover && trigger === "hover"
|
isPopover && (trigger === "hover" || hoverOpen)
|
||||||
? popoverEnterFn
|
? popoverEnterFn
|
||||||
: targetWrapperProps?.onMouseEnter
|
: targetWrapperProps?.onMouseEnter
|
||||||
}
|
}
|
||||||
onMouseLeave={
|
onMouseLeave={
|
||||||
isPopover && trigger === "hover"
|
isPopover && (trigger === "hover" || hoverOpen)
|
||||||
? popoverLeaveFn
|
? popoverLeaveFn
|
||||||
: targetWrapperProps?.onMouseLeave
|
: targetWrapperProps?.onMouseLeave
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,6 +21,7 @@ export type SearchProps<KeyType extends string> = DetailedHTMLProps<
|
|||||||
>;
|
>;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
componentRef?: React.RefObject<HTMLInputElement | null>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,6 +38,7 @@ export default function Search<KeyType extends string>({
|
|||||||
buttonProps,
|
buttonProps,
|
||||||
loading,
|
loading,
|
||||||
placeholder,
|
placeholder,
|
||||||
|
componentRef,
|
||||||
...props
|
...props
|
||||||
}: SearchProps<KeyType>) {
|
}: SearchProps<KeyType>) {
|
||||||
const [input, setInput] = React.useState(
|
const [input, setInput] = React.useState(
|
||||||
@ -52,7 +54,7 @@ export default function Search<KeyType extends string>({
|
|||||||
}, delay);
|
}, delay);
|
||||||
}, [input]);
|
}, [input]);
|
||||||
|
|
||||||
const inputRef = React.useRef<HTMLInputElement>(null);
|
const inputRef = componentRef || React.useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (props.autoFocus) {
|
if (props.autoFocus) {
|
||||||
@ -64,7 +66,7 @@ export default function Search<KeyType extends string>({
|
|||||||
<Row
|
<Row
|
||||||
{...props}
|
{...props}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"relative xl:flex-nowrap items-stretch gap-0",
|
"relative xl:flex-nowrap items-stretch gap-0 flex-nowrap",
|
||||||
"twui-search-wrapper",
|
"twui-search-wrapper",
|
||||||
props?.className
|
props?.className
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -31,7 +31,7 @@ export default function Table({ data }: Props) {
|
|||||||
<th
|
<th
|
||||||
key={header}
|
key={header}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"px-3 py-2 text-left text-sm opacity-50",
|
"px-3 py-2 text-left opacity-50",
|
||||||
"font-semibold"
|
"font-semibold"
|
||||||
)}
|
)}
|
||||||
title={header}
|
title={header}
|
||||||
@ -58,7 +58,7 @@ export default function Table({ data }: Props) {
|
|||||||
<td
|
<td
|
||||||
key={`${header}-${index}`}
|
key={`${header}-${index}`}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"px-3 py-2 whitespace-nowrap text-sm text-foreground-light",
|
"px-3 py-2 whitespace-nowrap text-foreground-light",
|
||||||
"dark:text-foreground-dark max-w-[200px] overflow-hidden",
|
"dark:text-foreground-dark max-w-[200px] overflow-hidden",
|
||||||
"overflow-ellipsis"
|
"overflow-ellipsis"
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import twuiSlugify from "../utils/slugify";
|
|||||||
export type TWUITabsObject = {
|
export type TWUITabsObject = {
|
||||||
title: string;
|
title: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
content: React.ReactNode;
|
content?: React.ReactNode;
|
||||||
defaultActive?: boolean;
|
defaultActive?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -35,6 +35,8 @@ export type TWUI_TOGGLE_PROPS = React.ComponentProps<typeof Stack> & {
|
|||||||
* @className twui-tab-buttons
|
* @className twui-tab-buttons
|
||||||
* @className twui-tab-button-active
|
* @className twui-tab-button-active
|
||||||
* @className twui-tab-buttons-wrapper
|
* @className twui-tab-buttons-wrapper
|
||||||
|
* @className twui-tab-buttons-container
|
||||||
|
* @className twui-tabs-border
|
||||||
*/
|
*/
|
||||||
export default function Tabs({
|
export default function Tabs({
|
||||||
tabsContentArray,
|
tabsContentArray,
|
||||||
@ -91,13 +93,14 @@ export default function Tabs({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Border
|
<Border
|
||||||
className="p-0 w-full overflow-hidden"
|
className="p-0 w-full overflow-hidden twui-tabs-border"
|
||||||
{...tabsBorderProps}
|
{...tabsBorderProps}
|
||||||
>
|
>
|
||||||
<Row
|
<Row
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"gap-0 items-stretch w-full flex-nowrap overflow-x-auto",
|
"gap-0 items-stretch w-full flex-nowrap overflow-x-auto",
|
||||||
centered && "justify-center"
|
centered && "justify-center",
|
||||||
|
"twui-tab-buttons-container"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{values.map((value, index) => {
|
{values.map((value, index) => {
|
||||||
|
|||||||
@ -18,6 +18,7 @@ type Props = {
|
|||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
mdRes?: string;
|
mdRes?: string;
|
||||||
setMdRes: React.Dispatch<React.SetStateAction<string>>;
|
setMdRes: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
placeholder?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function AIPromptBlock({
|
export default function AIPromptBlock({
|
||||||
@ -27,6 +28,7 @@ export default function AIPromptBlock({
|
|||||||
loading = false,
|
loading = false,
|
||||||
mdRes = "",
|
mdRes = "",
|
||||||
setMdRes,
|
setMdRes,
|
||||||
|
placeholder,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [prompt, setPrompt] = React.useState("");
|
const [prompt, setPrompt] = React.useState("");
|
||||||
const currentPromptRef = React.useRef("");
|
const currentPromptRef = React.useRef("");
|
||||||
@ -43,7 +45,7 @@ export default function AIPromptBlock({
|
|||||||
<MessageCircleMore
|
<MessageCircleMore
|
||||||
size={15}
|
size={15}
|
||||||
opacity={0.5}
|
opacity={0.5}
|
||||||
className="-mt-[1px]"
|
className="-mt-px"
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
</Card>
|
</Card>
|
||||||
@ -53,11 +55,15 @@ export default function AIPromptBlock({
|
|||||||
setStreamRes={setMdRes}
|
setStreamRes={setMdRes}
|
||||||
streamRes={mdRes}
|
streamRes={mdRes}
|
||||||
history={history}
|
history={history}
|
||||||
|
loading={loading}
|
||||||
/>
|
/>
|
||||||
<Stack className="w-full relative">
|
<Stack className="w-full relative">
|
||||||
{loading && <LoadingOverlay />}
|
{loading && <LoadingOverlay />}
|
||||||
<Textarea
|
<Textarea
|
||||||
placeholder={model ? `Prompt ${model}` : "Prompt AI"}
|
placeholder={
|
||||||
|
placeholder ||
|
||||||
|
(model ? `Prompt ${model}` : "Prompt AI")
|
||||||
|
}
|
||||||
wrapperProps={{ className: "outline-none" }}
|
wrapperProps={{ className: "outline-none" }}
|
||||||
wrapperWrapperProps={{ className: "w-full" }}
|
wrapperWrapperProps={{ className: "w-full" }}
|
||||||
value={prompt}
|
value={prompt}
|
||||||
@ -65,7 +71,7 @@ export default function AIPromptBlock({
|
|||||||
setPrompt(e.target.value);
|
setPrompt(e.target.value);
|
||||||
}}
|
}}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key == "Enter" && !e.ctrlKey) {
|
if (e.key == "Enter" && !e.ctrlKey && !e.shiftKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
currentPromptRef.current = prompt;
|
currentPromptRef.current = prompt;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@ -8,12 +8,14 @@ type Props = {
|
|||||||
streamRes: string;
|
streamRes: string;
|
||||||
setStreamRes: React.Dispatch<React.SetStateAction<string>>;
|
setStreamRes: React.Dispatch<React.SetStateAction<string>>;
|
||||||
history: ChatCompletionMessageParam[];
|
history: ChatCompletionMessageParam[];
|
||||||
|
loading?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function AIPromptPreview({
|
export default function AIPromptPreview({
|
||||||
setStreamRes,
|
setStreamRes,
|
||||||
streamRes,
|
streamRes,
|
||||||
history,
|
history,
|
||||||
|
loading,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const responseContentRef = React.useRef<HTMLDivElement>(null);
|
const responseContentRef = React.useRef<HTMLDivElement>(null);
|
||||||
const isContentInterrupted = React.useRef(false);
|
const isContentInterrupted = React.useRef(false);
|
||||||
@ -27,7 +29,7 @@ export default function AIPromptPreview({
|
|||||||
}
|
}
|
||||||
}, [streamRes]);
|
}, [streamRes]);
|
||||||
|
|
||||||
if (!streamRes?.match(/./)) return null;
|
if (loading || !streamRes?.match(/./)) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack className="w-full">
|
<Stack className="w-full">
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import fileInputToBase64 from "../utils/form/fileInputToBase64";
|
|||||||
import Row from "../layout/Row";
|
import Row from "../layout/Row";
|
||||||
import Input from "./Input";
|
import Input from "./Input";
|
||||||
import Loading from "../elements/Loading";
|
import Loading from "../elements/Loading";
|
||||||
|
import Tag from "../elements/Tag";
|
||||||
|
|
||||||
type FileInputUtils = {
|
type FileInputUtils = {
|
||||||
clearFileInput?: () => void;
|
clearFileInput?: () => void;
|
||||||
@ -59,6 +60,7 @@ type ImageUploadProps = DetailedHTMLProps<
|
|||||||
existingFile?: FileInputToBase64FunctionReturn;
|
existingFile?: FileInputToBase64FunctionReturn;
|
||||||
existingFileUrl?: string;
|
existingFileUrl?: string;
|
||||||
icon?: ReactNode;
|
icon?: ReactNode;
|
||||||
|
externalSetFileURL?: React.Dispatch<string | undefined>;
|
||||||
labelSpanProps?: ComponentProps<typeof Span>;
|
labelSpanProps?: ComponentProps<typeof Span>;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
multiple?: boolean;
|
multiple?: boolean;
|
||||||
@ -86,6 +88,7 @@ export default function FileUpload({
|
|||||||
multiple,
|
multiple,
|
||||||
onClear,
|
onClear,
|
||||||
changeHandler,
|
changeHandler,
|
||||||
|
externalSetFileURL,
|
||||||
...props
|
...props
|
||||||
}: ImageUploadProps) {
|
}: ImageUploadProps) {
|
||||||
const [file, setFile] = React.useState<
|
const [file, setFile] = React.useState<
|
||||||
@ -97,6 +100,7 @@ export default function FileUpload({
|
|||||||
|
|
||||||
const [fileDraggedOver, setFileDraggedOver] = React.useState(false);
|
const [fileDraggedOver, setFileDraggedOver] = React.useState(false);
|
||||||
const inputRef = React.useRef<HTMLInputElement>(null);
|
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||||
|
const tempFileURLRef = React.useRef<string>(null);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (existingFileUrl) {
|
if (existingFileUrl) {
|
||||||
@ -244,6 +248,7 @@ export default function FileUpload({
|
|||||||
className="w-full relative h-full items-center justify-center overflow-hidden"
|
className="w-full relative h-full items-center justify-center overflow-hidden"
|
||||||
{...previewImageWrapperProps}
|
{...previewImageWrapperProps}
|
||||||
>
|
>
|
||||||
|
<Stack className="w-full">
|
||||||
{disablePreview ? (
|
{disablePreview ? (
|
||||||
<Span className="opacity-50" size="small">
|
<Span className="opacity-50" size="small">
|
||||||
Image Uploaded!
|
Image Uploaded!
|
||||||
@ -264,6 +269,16 @@ export default function FileUpload({
|
|||||||
{...previewImageProps}
|
{...previewImageProps}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<Tag
|
||||||
|
variant="outlined"
|
||||||
|
color="gray"
|
||||||
|
className="w-full py-2 text-sm"
|
||||||
|
>
|
||||||
|
{fileUrl}
|
||||||
|
</Tag>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
@ -345,6 +360,59 @@ export default function FileUpload({
|
|||||||
>
|
>
|
||||||
{label || "Click to Upload File"}
|
{label || "Click to Upload File"}
|
||||||
</Span>
|
</Span>
|
||||||
|
{externalSetFileURL ? (
|
||||||
|
<Row
|
||||||
|
className="flex-nowrap gap-0 items-stretch"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
placeholder="Add Media URL"
|
||||||
|
className="text-sm"
|
||||||
|
wrapperProps={{ className: "h-full" }}
|
||||||
|
wrapperWrapperProps={{
|
||||||
|
className: "h-full",
|
||||||
|
}}
|
||||||
|
changeHandler={(value) => {
|
||||||
|
tempFileURLRef.current = value;
|
||||||
|
}}
|
||||||
|
onKeyUp={(e) => {
|
||||||
|
if (e.key == "Enter") {
|
||||||
|
if (tempFileURLRef.current) {
|
||||||
|
setFileUrl(
|
||||||
|
tempFileURLRef.current
|
||||||
|
);
|
||||||
|
externalSetFileURL(
|
||||||
|
tempFileURLRef.current
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
title="Add Media URL"
|
||||||
|
variant="outlined"
|
||||||
|
className="py-0 px-3"
|
||||||
|
color="gray"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (tempFileURLRef.current) {
|
||||||
|
setFileUrl(
|
||||||
|
tempFileURLRef.current
|
||||||
|
);
|
||||||
|
externalSetFileURL(
|
||||||
|
tempFileURLRef.current
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="text-2xl">+</span>
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
) : null}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Center>
|
</Center>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@ -207,14 +207,16 @@ export default function Input<KeyType extends string>(
|
|||||||
|
|
||||||
window.clearTimeout(timeout);
|
window.clearTimeout(timeout);
|
||||||
|
|
||||||
if (validationRegex && !validationFunction) {
|
if (validationRegex) {
|
||||||
timeout = setTimeout(() => {
|
timeout = setTimeout(() => {
|
||||||
setValidity({
|
setValidity({
|
||||||
isValid: validationRegex.test(val),
|
isValid: validationRegex.test(val),
|
||||||
msg: "Value mismatch",
|
msg: "Value mismatch",
|
||||||
});
|
});
|
||||||
}, finalDebounce);
|
}, finalDebounce);
|
||||||
} else if (validationFunction) {
|
}
|
||||||
|
|
||||||
|
if (validationFunction) {
|
||||||
window.clearTimeout(validationFnTimeout);
|
window.clearTimeout(validationFnTimeout);
|
||||||
|
|
||||||
validationFnTimeout = setTimeout(() => {
|
validationFnTimeout = setTimeout(() => {
|
||||||
|
|||||||
@ -232,9 +232,9 @@ export default function Select<
|
|||||||
}
|
}
|
||||||
hoverOpen
|
hoverOpen
|
||||||
>
|
>
|
||||||
<Card className="min-w-[250px] text-sm p-6">
|
<Card className="min-w-[250px] p-6">
|
||||||
{typeof info == "string" ? (
|
{typeof info == "string" ? (
|
||||||
<Span className="text-sm">{info}</Span>
|
<Span>{info}</Span>
|
||||||
) : (
|
) : (
|
||||||
info
|
info
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -258,7 +258,7 @@ export default function Button({
|
|||||||
props.disabled ? "opacity-40 cursor-not-allowed" : "",
|
props.disabled ? "opacity-40 cursor-not-allowed" : "",
|
||||||
"twui-button-general",
|
"twui-button-general",
|
||||||
size == "small"
|
size == "small"
|
||||||
? "px-3 py-1.5 text-sm twui-button-small"
|
? "px-3 py-1.5 twui-button-small text-sm"
|
||||||
: size == "smaller"
|
: size == "smaller"
|
||||||
? "px-2 py-1 text-xs twui-button-smaller"
|
? "px-2 py-1 text-xs twui-button-smaller"
|
||||||
: size == "large"
|
: size == "large"
|
||||||
|
|||||||
@ -12,7 +12,7 @@ export default function Container({
|
|||||||
<div
|
<div
|
||||||
{...props}
|
{...props}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"flex w-full max-w-[1200px] gap-4 justify-between",
|
"flex w-full max-w-container gap-4 justify-between",
|
||||||
"flex-wrap flex-col xl:flex-row items-start xl:items-center",
|
"flex-wrap flex-col xl:flex-row items-start xl:items-center",
|
||||||
"twui-container",
|
"twui-container",
|
||||||
props.className
|
props.className
|
||||||
|
|||||||
@ -12,7 +12,7 @@ export default function H5({
|
|||||||
<h5
|
<h5
|
||||||
{...props}
|
{...props}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"text-sm mb-4",
|
"mb-4",
|
||||||
"twui-headings twui-heading",
|
"twui-headings twui-heading",
|
||||||
"twui-h5",
|
"twui-h5",
|
||||||
props.className
|
props.className
|
||||||
|
|||||||
@ -18,7 +18,7 @@ export default function Spacer({ horizontal, ...props }: Props) {
|
|||||||
<div
|
<div
|
||||||
{...props}
|
{...props}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"grow",
|
"",
|
||||||
horizontal ? "w-10" : "w-full h-10",
|
horizontal ? "w-10" : "w-full h-10",
|
||||||
"twui-spacer",
|
"twui-spacer",
|
||||||
props.className
|
props.className
|
||||||
|
|||||||
@ -23,6 +23,7 @@ export function useMDXComponents(params?: Params) {
|
|||||||
pre: ({ children, ...props }) => {
|
pre: ({ children, ...props }) => {
|
||||||
if (React.isValidElement(children) && children.props) {
|
if (React.isValidElement(children) && children.props) {
|
||||||
return (
|
return (
|
||||||
|
// @ts-ignore
|
||||||
<CodeBlock {...props} backgroundColor={codeBgColor}>
|
<CodeBlock {...props} backgroundColor={codeBgColor}>
|
||||||
{/* @ts-ignore */}
|
{/* @ts-ignore */}
|
||||||
{children.props.children}
|
{children.props.children}
|
||||||
@ -30,6 +31,7 @@ export function useMDXComponents(params?: Params) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
// @ts-ignore
|
||||||
<CodeBlock {...props} backgroundColor={codeBgColor}>
|
<CodeBlock {...props} backgroundColor={codeBgColor}>
|
||||||
{children}
|
{children}
|
||||||
</CodeBlock>
|
</CodeBlock>
|
||||||
@ -62,6 +64,7 @@ export function useMDXComponents(params?: Params) {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
img: (props) => (
|
img: (props) => (
|
||||||
|
// @ts-ignore
|
||||||
<img
|
<img
|
||||||
{...props}
|
{...props}
|
||||||
className="w-full h-auto shadow-lg rounded-default overflow-hidden"
|
className="w-full h-auto shadow-lg rounded-default overflow-hidden"
|
||||||
|
|||||||
@ -22,6 +22,7 @@ export default function useMDXComponents({
|
|||||||
pre: ({ children, ...props }) => {
|
pre: ({ children, ...props }) => {
|
||||||
if (React.isValidElement(children) && children.props) {
|
if (React.isValidElement(children) && children.props) {
|
||||||
return (
|
return (
|
||||||
|
// @ts-ignore
|
||||||
<CodeBlock {...props} backgroundColor={codeBgColor}>
|
<CodeBlock {...props} backgroundColor={codeBgColor}>
|
||||||
{/* @ts-ignore */}
|
{/* @ts-ignore */}
|
||||||
{children.props.children}
|
{children.props.children}
|
||||||
@ -29,6 +30,7 @@ export default function useMDXComponents({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
// @ts-ignore
|
||||||
<CodeBlock {...props} backgroundColor={codeBgColor}>
|
<CodeBlock {...props} backgroundColor={codeBgColor}>
|
||||||
{children}
|
{children}
|
||||||
</CodeBlock>
|
</CodeBlock>
|
||||||
|
|||||||
@ -1,17 +1,20 @@
|
|||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
|
||||||
|
export const FetchAPIMethods = [
|
||||||
|
"POST",
|
||||||
|
"GET",
|
||||||
|
"DELETE",
|
||||||
|
"PUT",
|
||||||
|
"PATCH",
|
||||||
|
"post",
|
||||||
|
"get",
|
||||||
|
"delete",
|
||||||
|
"put",
|
||||||
|
"patch",
|
||||||
|
] as const;
|
||||||
|
|
||||||
type FetchApiOptions<T extends { [k: string]: any } = { [k: string]: any }> = {
|
type FetchApiOptions<T extends { [k: string]: any } = { [k: string]: any }> = {
|
||||||
method:
|
method: (typeof FetchAPIMethods)[number];
|
||||||
| "POST"
|
|
||||||
| "GET"
|
|
||||||
| "DELETE"
|
|
||||||
| "PUT"
|
|
||||||
| "PATCH"
|
|
||||||
| "post"
|
|
||||||
| "get"
|
|
||||||
| "delete"
|
|
||||||
| "put"
|
|
||||||
| "patch";
|
|
||||||
body?: T | string;
|
body?: T | string;
|
||||||
headers?: FetchHeader;
|
headers?: FetchHeader;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -35,7 +35,7 @@ export default async function fileInputToBase64({
|
|||||||
resolve(reader.result?.toString());
|
resolve(reader.result?.toString());
|
||||||
};
|
};
|
||||||
reader.onerror = function (/** @type {*} */ error: any) {
|
reader.onerror = function (/** @type {*} */ error: any) {
|
||||||
console.log("Error: ", error.message);
|
console.log("File Input to Base64 Error: ", error.message);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@ -4,14 +4,28 @@ import Header from "./Header";
|
|||||||
import Footer from "./Footer";
|
import Footer from "./Footer";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
import MobileMenu from "./(sections)/MobileMenu";
|
import MobileMenu from "./(sections)/MobileMenu";
|
||||||
|
import Head from "next/head";
|
||||||
|
|
||||||
type Props = PropsWithChildren & {};
|
type Props = PropsWithChildren & {
|
||||||
|
meta?: {
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export default function Layout({ children }: Props) {
|
export default function Layout({ children, meta }: Props) {
|
||||||
const [menuOpen, setMenuOpen] = React.useState(false);
|
const [menuOpen, setMenuOpen] = React.useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-row items-stretch w-full min-h-screen">
|
<div className="flex flex-row items-stretch w-full min-h-screen">
|
||||||
|
<Head>
|
||||||
|
<title>
|
||||||
|
{meta?.title || "10X Software/Devops Engineer | Tben.me"}
|
||||||
|
</title>
|
||||||
|
{meta?.description && (
|
||||||
|
<meta name="description" content={meta?.description} />
|
||||||
|
)}
|
||||||
|
</Head>
|
||||||
<Aside />
|
<Aside />
|
||||||
<div className={twMerge("flex flex-col items-start gap-0", "grow")}>
|
<div className={twMerge("flex flex-col items-start gap-0", "grow")}>
|
||||||
<Header {...{ menuOpen, setMenuOpen }} />
|
<Header {...{ menuOpen, setMenuOpen }} />
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import Main from "@/components/pages/about";
|
|||||||
|
|
||||||
export default function ContactPage() {
|
export default function ContactPage() {
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout meta={{ title: "About Me | Tben.me" }}>
|
||||||
<Main />
|
<Main />
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import Main from "@/components/pages/contact";
|
|||||||
|
|
||||||
export default function ContactPage() {
|
export default function ContactPage() {
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout meta={{ title: "Contact Me | Tben.me" }}>
|
||||||
<Main />
|
<Main />
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import Layout from "@/layouts/main";
|
import Layout from "@/layouts/main";
|
||||||
import H1 from "@/components/lib/layout/H1";
|
|
||||||
import Main from "@/components/pages/Home";
|
import Main from "@/components/pages/Home";
|
||||||
import AboutSection from "@/components/pages/Home/(sections)/AboutSection";
|
import AboutSection from "@/components/pages/Home/(sections)/AboutSection";
|
||||||
import Divider from "@/components/lib/layout/Divider";
|
import Divider from "@/components/lib/layout/Divider";
|
||||||
@ -8,7 +7,12 @@ import MyWorkSection from "@/components/pages/Home/(sections)/MyWorkSection";
|
|||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout
|
||||||
|
meta={{
|
||||||
|
description:
|
||||||
|
"Software Engineer, DevOps Engineer, Full Stack Developer, Software Architect, Philosopher, Solar Energy Enthusiast.",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Main />
|
<Main />
|
||||||
<Divider />
|
<Divider />
|
||||||
<AboutSection />
|
<AboutSection />
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import MySkillsSection from "@/components/pages/Home/(sections)/MySkillsSection"
|
|||||||
|
|
||||||
export default function SkillsPage() {
|
export default function SkillsPage() {
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout meta={{ title: "My Skills | Tben.me" }}>
|
||||||
<Main />
|
<Main />
|
||||||
<Divider />
|
<Divider />
|
||||||
<MySkillsSection noTitle expand />
|
<MySkillsSection noTitle expand />
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import MyWorkSection from "@/components/pages/Home/(sections)/MyWorkSection";
|
|||||||
|
|
||||||
export default function WorkPage() {
|
export default function WorkPage() {
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout meta={{ title: "My Work | Tben.me" }}>
|
||||||
<Main />
|
<Main />
|
||||||
<Divider />
|
<Divider />
|
||||||
<MyWorkSection noTitle expand />
|
<MyWorkSection noTitle expand />
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user