Shadcn conversion, lots of styling

This commit is contained in:
2025-02-19 10:47:23 -05:00
parent 110f4ec332
commit e034e83198
13 changed files with 165 additions and 175 deletions

View File

@@ -1,60 +0,0 @@
import { Button } from "@/components/ui/button"
import { X } from "lucide-react"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
AlertDialogPortal,
AlertDialogOverlay,
} from "@/components/ui/alert-dialog"
import { useRsi } from "../hooks/useRsi"
type ModalCloseButtonProps = {
onClose: () => void
}
export const ModalCloseButton = ({ onClose }: ModalCloseButtonProps) => {
const { translations } = useRsi()
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
variant="ghost"
size="icon"
className="absolute right-4 top-4 z-[60] h-10 w-10 rounded-full bg-background/80 p-0 text-muted-foreground backdrop-blur-sm hover:bg-background/50"
>
<X className="h-5 w-5" />
<span className="sr-only">Close modal</span>
</Button>
</AlertDialogTrigger>
<AlertDialogPortal>
<AlertDialogOverlay className="z-[1400]" />
<AlertDialogContent className="z-[1500]">
<AlertDialogHeader>
<AlertDialogTitle>
{translations.alerts.confirmClose.headerTitle}
</AlertDialogTitle>
<AlertDialogDescription>
{translations.alerts.confirmClose.bodyText}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>
{translations.alerts.confirmClose.cancelButtonTitle}
</AlertDialogCancel>
<AlertDialogAction onClick={onClose}>
{translations.alerts.confirmClose.exitButtonTitle}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogPortal>
</AlertDialog>
)
}

View File

@@ -1,7 +1,26 @@
import type React from "react"
import { Modal, ModalContent, ModalOverlay } from "@chakra-ui/react"
import { ModalCloseButton } from "./ModalCloseButton"
import {
Dialog,
DialogContent,
DialogOverlay,
DialogPortal,
DialogClose,
} from "@/components/ui/dialog"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
AlertDialogPortal,
AlertDialogOverlay,
} from "@/components/ui/alert-dialog"
import { useRsi } from "../hooks/useRsi"
import { useState } from "react"
type Props = {
children: React.ReactNode
@@ -10,24 +29,60 @@ type Props = {
}
export const ModalWrapper = ({ children, isOpen, onClose }: Props) => {
const { rtl } = useRsi()
const { rtl, translations } = useRsi()
const [showCloseAlert, setShowCloseAlert] = useState(false)
return (
<Modal
isOpen={isOpen}
onClose={onClose}
id="rsi"
variant="rsi"
closeOnEsc={false}
closeOnOverlayClick={false}
scrollBehavior="inside"
<>
<Dialog open={isOpen} onOpenChange={() => setShowCloseAlert(true)} modal>
<DialogPortal>
<DialogOverlay className="bg-background/80 backdrop-blur-sm" />
<DialogContent
onEscapeKeyDown={(e) => {
e.preventDefault()
setShowCloseAlert(true)
}}
onPointerDownOutside={(e) => e.preventDefault()}
className="fixed left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] w-[calc(100%-2rem)] h-[calc(100%-2rem)] max-w-[100vw] max-h-[100vh] flex flex-col overflow-hidden rounded-lg border bg-background p-0 shadow-lg sm:w-[calc(100%-3rem)] sm:h-[calc(100%-3rem)] md:w-[calc(100%-4rem)] md:h-[calc(100%-4rem)]"
>
<ModalOverlay />
<ModalContent>
<div dir={rtl ? "rtl" : "ltr"} className="relative">
<ModalCloseButton onClose={onClose} />
<AlertDialog>
<AlertDialogTrigger asChild>
<DialogClose className="absolute right-4 top-4" onClick={(e) => {
e.preventDefault()
setShowCloseAlert(true)
}} />
</AlertDialogTrigger>
</AlertDialog>
<div dir={rtl ? "rtl" : "ltr"} className="flex-1 overflow-auto">
{children}
</div>
</ModalContent>
</Modal>
</DialogContent>
</DialogPortal>
</Dialog>
<AlertDialog open={showCloseAlert} onOpenChange={setShowCloseAlert}>
<AlertDialogPortal>
<AlertDialogOverlay className="z-[1400]" />
<AlertDialogContent className="z-[1500]">
<AlertDialogHeader>
<AlertDialogTitle>
{translations.alerts.confirmClose.headerTitle}
</AlertDialogTitle>
<AlertDialogDescription>
{translations.alerts.confirmClose.bodyText}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={() => setShowCloseAlert(false)}>
{translations.alerts.confirmClose.cancelButtonTitle}
</AlertDialogCancel>
<AlertDialogAction onClick={onClose}>
{translations.alerts.confirmClose.exitButtonTitle}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogPortal>
</AlertDialog>
</>
)
}

View File

@@ -35,7 +35,7 @@ export const ColumnGrid = <T extends string>({
)
return (
<div className="flex h-[calc(100vh-9.5rem)] flex-col">
<div className="flex h-[calc(100vh-10rem)] flex-col">
<div className="flex-1 overflow-hidden">
<div className="px-8 py-6">
<div className="mb-8">
@@ -94,7 +94,7 @@ export const ColumnGrid = <T extends string>({
</ScrollArea>
</div>
</div>
<div className="border-t bg-muted px-8 py-4">
<div className="border-t bg-muted px-8 py-4 -mb-1">
<div className="flex items-center justify-between">
{onBack && (
<Button variant="outline" onClick={onBack}>

View File

@@ -21,16 +21,14 @@ import {
AccordionTrigger,
} from "@/components/ui/accordion"
import { Check } from "lucide-react"
import { cn } from "@/lib/utils"
type TemplateColumnProps<T extends string> = {
column: Column<T>
onChange: (value: T, columnIndex: number) => void
onSubChange: (value: string, columnIndex: number, entry: string) => void
}
const getAccordionTitle = <T extends string>(fields: Fields<T>, column: Column<T>, translations: any) => {
const fieldLabel = fields.find((field) => "value" in column && field.key === column.value)!.label
const fieldLabel = fields.find((field: Field<T>) => "value" in column && field.key === column.value)!.label
return `${translations.matchColumnsStep.matchDropdownTitle} ${fieldLabel} (${
"matchedOptions" in column && column.matchedOptions.filter((option) => !option.value).length
} ${translations.matchColumnsStep.unmatched})`

View File

@@ -4,7 +4,6 @@ import { X, RotateCcw } from "lucide-react"
import type { Column } from "../MatchColumnsStep"
import { ColumnType } from "../MatchColumnsStep"
import type { RawData } from "../../../types"
import { cn } from "@/lib/utils"
type UserTableColumnProps<T extends string> = {
column: Column<T>

View File

@@ -25,23 +25,20 @@ export const SelectHeaderStep = ({ data, onContinue, onBack }: SelectHeaderProps
}, [onContinue, data, selectedRows])
return (
<div className="flex h-full flex-col">
<div className="flex-1 overflow-hidden">
<div className="flex flex-col">
<div className="px-8 py-6">
<div className="mb-8">
<h2 className="text-3xl font-semibold text-foreground">
<h2 className="text-2xl font-semibold text-foreground">
{translations.selectHeaderStep.title}
</h2>
</div>
<div className="flex-1 px-8 mb-12 overflow-auto">
<SelectHeaderTable
data={data}
selectedRows={selectedRows}
setSelectedRows={setSelectedRows}
/>
</div>
</div>
<div className="border-t bg-muted px-8 py-4 mt-5">
<div className="flex items-center justify-between">
<div className="flex items-center justify-between border-t bg-muted px-8 py-4 mt-2">
{onBack && (
<Button variant="outline" onClick={onBack}>
{translations.selectHeaderStep.backButtonTitle}
@@ -56,6 +53,5 @@ export const SelectHeaderStep = ({ data, onContinue, onBack }: SelectHeaderProps
</Button>
</div>
</div>
</div>
)
}

View File

@@ -26,7 +26,7 @@ export const SelectSheetStep = ({ sheetNames, onContinue, onBack }: SelectSheetP
)
return (
<div className="flex h-[calc(100vh-9.5rem)] flex-col">
<div className="flex h-[calc(100vh-10rem)] flex-col">
<div className="flex-1 overflow-hidden">
<div className="px-8 py-6">
<div className="mb-8">
@@ -53,7 +53,7 @@ export const SelectSheetStep = ({ sheetNames, onContinue, onBack }: SelectSheetP
</RadioGroup>
</div>
</div>
<div className="flex items-center justify-between border-t p-6 bg-muted">
<div className="flex items-center justify-between border-t px-8 py-4 bg-muted -mb-1">
{onBack && (
<Button
variant="ghost"

View File

@@ -3,23 +3,14 @@ import { useRsi } from "../hooks/useRsi"
import { useRef, useState } from "react"
import { steps, stepTypeToStepIndex, stepIndexToStepType } from "../utils/steps"
import { CgCheck } from "react-icons/cg"
import { Separator } from "@/components/ui/separator"
// @ts-ignore
import { useSteps, Step, Steps as Stepper } from "chakra-ui-steps"
const CheckIcon = ({ color }: { color: string }) => <CgCheck size="24" className={color} />
export const Steps = () => {
const { initialStepState, translations, isNavigationEnabled } = useRsi()
const initialStep = stepTypeToStepIndex(initialStepState?.type)
const { nextStep, activeStep, setStep } = useSteps({
initialStep,
})
const [activeStep, setActiveStep] = useState(initialStep)
const [state, setState] = useState<StepState>(initialStepState || { type: StepType.upload })
const history = useRef<StepState[]>([])
const onClickStep = (stepIndex: number) => {
@@ -29,7 +20,7 @@ export const Steps = () => {
const nextHistory = history.current.slice(0, historyIdx + 1)
history.current = nextHistory
setState(nextHistory[nextHistory.length - 1])
setStep(stepIndex)
setActiveStep(stepIndex)
}
const onBack = () => {
@@ -39,7 +30,7 @@ export const Steps = () => {
const onNext = (v: StepState) => {
history.current.push(state)
setState(v)
v.type !== StepType.selectSheet && nextStep()
v.type !== StepType.selectSheet && setActiveStep(activeStep + 1)
}
return (
@@ -79,7 +70,6 @@ export const Steps = () => {
{translations[key].title}
</span>
</button>
</div>
)
})}

View File

@@ -1,6 +1,5 @@
import { useCallback, useState } from "react"
import { Progress, useToast } from "@chakra-ui/react"
import type XLSX from "xlsx-ugnis"
import type XLSX from "xlsx"
import { UploadStep } from "./UploadStep/UploadStep"
import { SelectHeaderStep } from "./SelectHeaderStep/SelectHeaderStep"
import { SelectSheetStep } from "./SelectSheetStep/SelectSheetStep"
@@ -11,6 +10,8 @@ import { MatchColumnsStep } from "./MatchColumnsStep/MatchColumnsStep"
import { exceedsMaxRecords } from "../utils/exceedsMaxRecords"
import { useRsi } from "../hooks/useRsi"
import type { RawData } from "../types"
import { Progress } from "@/components/ui/progress"
import { useToast } from "@/hooks/use-toast"
export enum StepType {
upload = "upload",
@@ -59,16 +60,13 @@ export const UploadFlow = ({ state, onNext, onBack }: Props) => {
tableHook,
} = useRsi()
const [uploadedFile, setUploadedFile] = useState<File | null>(null)
const toast = useToast()
const { toast } = useToast()
const errorToast = useCallback(
(description: string) => {
toast({
status: "error",
variant: "left-accent",
position: "bottom-left",
title: `${translations.alerts.toast.error}`,
variant: "destructive",
title: translations.alerts.toast.error,
description,
isClosable: true,
})
},
[toast, translations],
@@ -165,6 +163,6 @@ export const UploadFlow = ({ state, onNext, onBack }: Props) => {
case StepType.validateData:
return <ValidationStep initialData={state.data} file={uploadedFile!} onBack={onBack} />
default:
return <Progress isIndeterminate />
return <Progress value={33} className="w-full" />
}
}

View File

@@ -1,11 +1,9 @@
import type XLSX from "xlsx-ugnis"
import { Box, Heading, ModalBody, Text, useStyleConfig } from "@chakra-ui/react"
import { DropZone } from "./components/DropZone"
import { useRsi } from "../../hooks/useRsi"
import { ExampleTable } from "./components/ExampleTable"
import { useCallback, useState } from "react"
import { useRsi } from "../../hooks/useRsi"
import { DropZone } from "./components/DropZone"
import { ExampleTable } from "./components/ExampleTable"
import { FadingOverlay } from "./components/FadingOverlay"
import type { themeOverrides } from "../../theme"
type UploadProps = {
onContinue: (data: XLSX.WorkBook, file: File) => Promise<void>
@@ -13,8 +11,8 @@ type UploadProps = {
export const UploadStep = ({ onContinue }: UploadProps) => {
const [isLoading, setIsLoading] = useState(false)
const styles = useStyleConfig("UploadStep") as (typeof themeOverrides)["components"]["UploadStep"]["baseStyle"]
const { translations, fields } = useRsi()
const handleOnContinue = useCallback(
async (data: XLSX.WorkBook, file: File) => {
setIsLoading(true)
@@ -23,16 +21,19 @@ export const UploadStep = ({ onContinue }: UploadProps) => {
},
[onContinue],
)
return (
<ModalBody>
<Heading sx={styles.heading}>{translations.uploadStep.title}</Heading>
<Text sx={styles.title}>{translations.uploadStep.manifestTitle}</Text>
<Text sx={styles.subtitle}>{translations.uploadStep.manifestDescription}</Text>
<Box sx={styles.tableWrapper}>
<div className="p-6">
<h2 className="text-2xl font-semibold mb-4">{translations.uploadStep.title}</h2>
<p className="text-lg mb-2">{translations.uploadStep.manifestTitle}</p>
<p className="text-muted-foreground mb-6">{translations.uploadStep.manifestDescription}</p>
<div className="relative mb-0 border-t rounded-lg h-[80px]">
<div className="absolute inset-0">
<ExampleTable fields={fields} />
</div>
<FadingOverlay />
</Box>
</div>
<DropZone onContinue={handleOnContinue} isLoading={isLoading} />
</ModalBody>
</div>
)
}

View File

@@ -12,5 +12,14 @@ export const ExampleTable = <T extends string>({ fields }: Props<T>) => {
const data = useMemo(() => generateExampleRow(fields), [fields])
const columns = useMemo(() => generateColumns(fields), [fields])
return <Table rows={data} columns={columns} className={"rdg-example"} />
return (
<div className="h-full w-full">
<Table
rows={data}
columns={columns}
className="rdg-example h-full"
style={{ height: '100%' }}
/>
</div>
)
}

View File

@@ -1,13 +1,5 @@
import { Box } from "@chakra-ui/react"
export const FadingOverlay = () => (
<Box
position="absolute"
left={0}
right={0}
bottom={0}
height="48px"
pointerEvents="none"
bgGradient="linear(to bottom, backgroundAlpha, background)"
<div
className="absolute inset-x-0 bottom-0 h-12 pointer-events-none bg-gradient-to-t from-background to-transparent"
/>
)

View File

@@ -1,7 +1,12 @@
import type { Column } from "react-data-grid"
import { Box, Tooltip } from "@chakra-ui/react"
import type { Fields } from "../../../types"
import { CgInfo } from "react-icons/cg"
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip"
export const generateColumns = <T extends string>(fields: Fields<T>) =>
fields.map(
@@ -10,23 +15,30 @@ export const generateColumns = <T extends string>(fields: Fields<T>) =>
name: column.label,
minWidth: 150,
headerRenderer: () => (
<Box display="flex" gap={1} alignItems="center" position="relative">
<Box flex={1} overflow="hidden" textOverflow="ellipsis">
<div className="flex items-center gap-1 relative">
<div className="flex-1 overflow-hidden text-ellipsis">
{column.label}
</Box>
</div>
{column.description && (
<Tooltip placement="top" hasArrow label={column.description}>
<Box flex={"0 0 auto"}>
<CgInfo size="16px" />
</Box>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div className="flex-none">
<CgInfo className="h-4 w-4" />
</div>
</TooltipTrigger>
<TooltipContent>
{column.description}
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</Box>
</div>
),
formatter: ({ row }) => (
<Box minWidth="100%" minHeight="100%" overflow="hidden" textOverflow="ellipsis">
<div className="min-w-full min-h-full overflow-hidden text-ellipsis">
{row[column.key]}
</Box>
</div>
),
}),
)