significant refactor

This commit is contained in:
2025-08-30 01:42:23 -05:00
parent 7136f646a3
commit 052f53444e
106 changed files with 1994 additions and 1701 deletions

View File

@@ -1,8 +1,8 @@
import { Box, Container, useComputedColorScheme } from "@mantine/core";
import { PropsWithChildren, useEffect } from "react";
import { Drawer as VaulDrawer } from 'vaul';
import { useMantineColorScheme } from '@mantine/core';
import styles from './styles.module.css';
import { Drawer as VaulDrawer } from "vaul";
import { useMantineColorScheme } from "@mantine/core";
import styles from "./styles.module.css";
interface DrawerProps extends PropsWithChildren {
title?: string;
@@ -10,44 +10,51 @@ interface DrawerProps extends PropsWithChildren {
onChange: (next: boolean) => void;
}
const Drawer: React.FC<DrawerProps> = ({ title, children, opened, onChange }) => {
const colorScheme = useComputedColorScheme('light');
const Drawer: React.FC<DrawerProps> = ({
title,
children,
opened,
onChange,
}) => {
const colorScheme = useComputedColorScheme("light");
useEffect(() => {
const appElement = document.querySelector('.app') as HTMLElement;
const appElement = document.querySelector(".app") as HTMLElement;
if (!appElement) return;
let themeColorMeta = document.querySelector('meta[name="theme-color"]') as HTMLMetaElement;
let themeColorMeta = document.querySelector(
'meta[name="theme-color"]'
) as HTMLMetaElement;
if (!themeColorMeta) {
themeColorMeta = document.createElement('meta');
themeColorMeta.name = 'theme-color';
themeColorMeta = document.createElement("meta");
themeColorMeta.name = "theme-color";
document.head.appendChild(themeColorMeta);
}
const colors = {
light: {
normal: 'rgb(255,255,255)',
overlay: 'rgb(153,153,153)'
normal: "rgb(255,255,255)",
overlay: "rgb(153,153,153)",
},
dark: {
normal: 'rgb(36,36,36)',
overlay: 'rgb(22,22,22)'
}
normal: "rgb(36,36,36)",
overlay: "rgb(22,22,22)",
},
};
const currentColors = colors[colorScheme] || colors.light;
if (opened) {
appElement.classList.add('drawer-scaling');
appElement.classList.add("drawer-scaling");
themeColorMeta.content = currentColors.overlay;
} else {
appElement.classList.remove('drawer-scaling');
appElement.classList.remove("drawer-scaling");
themeColorMeta.content = currentColors.normal;
}
return () => {
appElement.classList.remove('drawer-scaling');
appElement.classList.remove("drawer-scaling");
themeColorMeta.content = currentColors.normal;
};
}, [opened, colorScheme]);
@@ -57,9 +64,17 @@ const Drawer: React.FC<DrawerProps> = ({ title, children, opened, onChange }) =>
<VaulDrawer.Portal>
<VaulDrawer.Overlay className={styles.drawerOverlay} />
<VaulDrawer.Content className={styles.drawerContent}>
<Container flex={1} p='md'>
<Box mb='sm' bg='var(--mantine-color-gray-4)' w='3rem' h='0.375rem' ml='auto' mr='auto' style={{ borderRadius: '9999px' }} />
<Container mah='fit-content' mx='auto' maw='28rem' px={0}>
<Container flex={1} p="md">
<Box
mb="sm"
bg="var(--mantine-color-gray-4)"
w="3rem"
h="0.375rem"
ml="auto"
mr="auto"
style={{ borderRadius: "9999px" }}
/>
<Container mah="fit-content" mx="auto" maw="28rem" px={0}>
<VaulDrawer.Title>{title}</VaulDrawer.Title>
{children}
</Container>
@@ -67,7 +82,7 @@ const Drawer: React.FC<DrawerProps> = ({ title, children, opened, onChange }) =>
</VaulDrawer.Content>
</VaulDrawer.Portal>
</VaulDrawer.Root>
)
}
);
};
export default Drawer;

View File

@@ -8,9 +8,13 @@ interface ModalProps extends PropsWithChildren {
}
const Modal: React.FC<ModalProps> = ({ title, children, opened, onClose }) => (
<MantineModal opened={opened} onClose={onClose} title={<Title order={3}>{title}</Title>}>
<MantineModal
opened={opened}
onClose={onClose}
title={<Title order={3}>{title}</Title>}
>
{children}
</MantineModal>
)
);
export default Modal;

View File

@@ -17,14 +17,22 @@ const Sheet: React.FC<SheetProps> = ({ title, children, opened, onChange }) => {
const SheetComponent = isMobile ? Drawer : Modal;
return (
<SheetComponent title={title} opened={opened} onChange={onChange} onClose={handleClose}>
<ScrollArea style={{ flex: 1 }} scrollbarSize={8} scrollbars='y' type='scroll'>
<Box mah='70vh'>
{children}
</Box>
<SheetComponent
title={title}
opened={opened}
onChange={onChange}
onClose={handleClose}
>
<ScrollArea
style={{ flex: 1 }}
scrollbarSize={8}
scrollbars="y"
type="scroll"
>
<Box mah="70vh">{children}</Box>
</ScrollArea>
</SheetComponent>
);
};
export default Sheet;
export default Sheet;

View File

@@ -32,12 +32,12 @@ const SlidePanelField = ({
const context = useContext(SlidePanelContext);
if (!context) {
throw new Error('SlidePanelField must be used within a SlidePanel');
throw new Error("SlidePanelField must be used within a SlidePanel");
}
const handleClick = () => {
if (!onChange) return;
context.openPanel({
title,
Component,
@@ -64,26 +64,51 @@ const SlidePanelField = ({
<Box>
<UnstyledButton
onClick={handleClick}
p='sm'
p="sm"
style={{
width: '100%',
border: error ? '1px solid var(--mantine-color-error)' : '1px solid var(--mantine-color-dimmed)',
borderRadius: 'var(--mantine-radius-md)',
backgroundColor: 'var(--mantine-color-body)',
textAlign: 'left',
width: "100%",
border: error
? "1px solid var(--mantine-color-error)"
: "1px solid var(--mantine-color-dimmed)",
borderRadius: "var(--mantine-radius-md)",
backgroundColor: "var(--mantine-color-body)",
textAlign: "left",
}}
>
<Flex justify="space-between" align="center">
<Stack>
<Text size="sm" fw={500}>{label}{withAsterisk && <Text span size="sm" c='var(--mantine-color-error)' fw={500} ml={4}>*</Text>}</Text>
<Text size="sm" c='dimmed'>{displayValue()}</Text>
<Text size="sm" fw={500}>
{label}
{withAsterisk && (
<Text
span
size="sm"
c="var(--mantine-color-error)"
fw={500}
ml={4}
>
*
</Text>
)}
</Text>
<Text size="sm" c="dimmed">
{displayValue()}
</Text>
</Stack>
<CaretRightIcon size={24} weight='thin' style={{ marginRight: '12px' }} />
<CaretRightIcon
size={24}
weight="thin"
style={{ marginRight: "12px" }}
/>
</Flex>
</UnstyledButton>
{error && <Text size="xs" c='var(--mantine-color-error)' fw={500} ml={4} mt={4}>{error}</Text>}
{error && (
<Text size="xs" c="var(--mantine-color-error)" fw={500} ml={4} mt={4}>
{error}
</Text>
)}
</Box>
);
};
export { SlidePanelField };
export { SlidePanelField };

View File

@@ -1,6 +1,13 @@
import { Box, Text, Group, ActionIcon, ScrollArea, Divider } from "@mantine/core";
import {
Box,
Text,
Group,
ActionIcon,
ScrollArea,
Divider,
} from "@mantine/core";
import { ArrowLeftIcon, CheckIcon } from "@phosphor-icons/react";
import { useState, ReactNode} from "react";
import { useState, ReactNode } from "react";
import { SlidePanelContext, type PanelConfig } from "./slide-panel-context";
import Button from "@/components/button";
@@ -15,20 +22,15 @@ interface SlidePanelProps {
loading?: boolean;
}
/**
* SlidePanel is a form component meant to be used inside a drawer/modal
* It is used to create a form with multiple views/panels that slides in from the side
* Use with SlidePanelField for an extra panel
*/
const SlidePanel = ({
children,
const SlidePanel = ({
children,
onSubmit,
onCancel,
submitText = "Submit",
cancelText = "Cancel",
maxHeight = "70vh",
formProps = {},
loading = false
loading = false,
}: SlidePanelProps) => {
const [isOpen, setIsOpen] = useState(false);
const [panelConfig, setPanelConfig] = useState<PanelConfig | null>(null);
@@ -58,54 +60,62 @@ const SlidePanel = ({
return (
<SlidePanelContext.Provider value={{ openPanel, closePanel }}>
<Box
style={{
position: 'relative',
<Box
style={{
position: "relative",
height: maxHeight,
overflow: 'hidden',
display: 'flex',
flexDirection: 'column',
overflow: "hidden",
display: "flex",
flexDirection: "column",
}}
>
<Box
style={{
position: 'absolute',
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
transform: isOpen ? 'translateX(-100%)' : 'translateX(0)',
transition: 'transform 0.3s ease-in-out',
display: 'flex',
flexDirection: 'column'
transform: isOpen ? "translateX(-100%)" : "translateX(0)",
transition: "transform 0.3s ease-in-out",
display: "flex",
flexDirection: "column",
}}
>
<form
{...formProps}
onSubmit={handleFormSubmit}
style={{
display: 'flex',
flexDirection: 'column',
height: '100%',
display: "flex",
flexDirection: "column",
height: "100%",
...formProps.style,
}}
>
<ScrollArea style={{ flex: 1 }} scrollbarSize={8} scrollbars='y' type='always'>
<Box p="md">
{children}
</Box>
<ScrollArea
style={{ flex: 1 }}
scrollbarSize={8}
scrollbars="y"
type="always"
>
<Box p="md">{children}</Box>
</ScrollArea>
<Box p="sm">
<Group gap="md">
<Button type="submit" fullWidth loading={loading} disabled={loading}>
<Button
type="submit"
fullWidth
loading={loading}
disabled={loading}
>
{submitText}
</Button>
{onCancel && (
<Button
variant="subtle"
color="red"
fullWidth
<Button
variant="subtle"
color="red"
fullWidth
onClick={onCancel}
type="button"
disabled={loading}
@@ -120,32 +130,41 @@ const SlidePanel = ({
<Box
style={{
position: 'absolute',
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
transform: isOpen ? 'translateX(0)' : 'translateX(100%)',
transition: 'transform 0.3s ease-in-out',
backgroundColor: 'var(--mantine-color-body)',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
transform: isOpen ? "translateX(0)" : "translateX(100%)",
transition: "transform 0.3s ease-in-out",
backgroundColor: "var(--mantine-color-body)",
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
{panelConfig && (
<>
<Group justify="space-between" p="md" align="center" w='100%'>
<Group justify="space-between" p="md" align="center" w="100%">
<ActionIcon variant="transparent" onClick={closePanel}>
<ArrowLeftIcon size={24} />
</ActionIcon>
<Text fw={500}>{panelConfig.title}</Text>
<ActionIcon variant="transparent" color="green" onClick={handleConfirm}>
<ActionIcon
variant="transparent"
color="green"
onClick={handleConfirm}
>
<CheckIcon size={24} />
</ActionIcon>
</Group>
<Divider h='1px' w='100%' bg='var(--mantine-color-dimmed)' my='xs'/>
<Divider
h="1px"
w="100%"
bg="var(--mantine-color-dimmed)"
my="xs"
/>
<Box>
<panelConfig.Component
@@ -162,4 +181,4 @@ const SlidePanel = ({
);
};
export { SlidePanel };
export { SlidePanel };