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 type React from "react"
import { Modal, ModalContent, ModalOverlay } from "@chakra-ui/react" import {
import { ModalCloseButton } from "./ModalCloseButton" 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 { useRsi } from "../hooks/useRsi"
import { useState } from "react"
type Props = { type Props = {
children: React.ReactNode children: React.ReactNode
@@ -10,24 +29,60 @@ type Props = {
} }
export const ModalWrapper = ({ children, isOpen, onClose }: Props) => { export const ModalWrapper = ({ children, isOpen, onClose }: Props) => {
const { rtl } = useRsi() const { rtl, translations } = useRsi()
const [showCloseAlert, setShowCloseAlert] = useState(false)
return ( return (
<Modal <>
isOpen={isOpen} <Dialog open={isOpen} onOpenChange={() => setShowCloseAlert(true)} modal>
onClose={onClose} <DialogPortal>
id="rsi" <DialogOverlay className="bg-background/80 backdrop-blur-sm" />
variant="rsi" <DialogContent
closeOnEsc={false} onEscapeKeyDown={(e) => {
closeOnOverlayClick={false} e.preventDefault()
scrollBehavior="inside" setShowCloseAlert(true)
> }}
<ModalOverlay /> onPointerDownOutside={(e) => e.preventDefault()}
<ModalContent> 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)]"
<div dir={rtl ? "rtl" : "ltr"} className="relative"> >
<ModalCloseButton onClose={onClose} /> <AlertDialog>
{children} <AlertDialogTrigger asChild>
</div> <DialogClose className="absolute right-4 top-4" onClick={(e) => {
</ModalContent> e.preventDefault()
</Modal> setShowCloseAlert(true)
}} />
</AlertDialogTrigger>
</AlertDialog>
<div dir={rtl ? "rtl" : "ltr"} className="flex-1 overflow-auto">
{children}
</div>
</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 ( 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="flex-1 overflow-hidden">
<div className="px-8 py-6"> <div className="px-8 py-6">
<div className="mb-8"> <div className="mb-8">
@@ -94,7 +94,7 @@ export const ColumnGrid = <T extends string>({
</ScrollArea> </ScrollArea>
</div> </div>
</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"> <div className="flex items-center justify-between">
{onBack && ( {onBack && (
<Button variant="outline" onClick={onBack}> <Button variant="outline" onClick={onBack}>

View File

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

View File

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

View File

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

View File

@@ -26,7 +26,7 @@ export const SelectSheetStep = ({ sheetNames, onContinue, onBack }: SelectSheetP
) )
return ( 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="flex-1 overflow-hidden">
<div className="px-8 py-6"> <div className="px-8 py-6">
<div className="mb-8"> <div className="mb-8">
@@ -53,7 +53,7 @@ export const SelectSheetStep = ({ sheetNames, onContinue, onBack }: SelectSheetP
</RadioGroup> </RadioGroup>
</div> </div>
</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 && ( {onBack && (
<Button <Button
variant="ghost" variant="ghost"

View File

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

View File

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

View File

@@ -1,11 +1,9 @@
import type XLSX from "xlsx-ugnis" 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 { 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 { FadingOverlay } from "./components/FadingOverlay"
import type { themeOverrides } from "../../theme"
type UploadProps = { type UploadProps = {
onContinue: (data: XLSX.WorkBook, file: File) => Promise<void> onContinue: (data: XLSX.WorkBook, file: File) => Promise<void>
@@ -13,8 +11,8 @@ type UploadProps = {
export const UploadStep = ({ onContinue }: UploadProps) => { export const UploadStep = ({ onContinue }: UploadProps) => {
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const styles = useStyleConfig("UploadStep") as (typeof themeOverrides)["components"]["UploadStep"]["baseStyle"]
const { translations, fields } = useRsi() const { translations, fields } = useRsi()
const handleOnContinue = useCallback( const handleOnContinue = useCallback(
async (data: XLSX.WorkBook, file: File) => { async (data: XLSX.WorkBook, file: File) => {
setIsLoading(true) setIsLoading(true)
@@ -23,16 +21,19 @@ export const UploadStep = ({ onContinue }: UploadProps) => {
}, },
[onContinue], [onContinue],
) )
return ( return (
<ModalBody> <div className="p-6">
<Heading sx={styles.heading}>{translations.uploadStep.title}</Heading> <h2 className="text-2xl font-semibold mb-4">{translations.uploadStep.title}</h2>
<Text sx={styles.title}>{translations.uploadStep.manifestTitle}</Text> <p className="text-lg mb-2">{translations.uploadStep.manifestTitle}</p>
<Text sx={styles.subtitle}>{translations.uploadStep.manifestDescription}</Text> <p className="text-muted-foreground mb-6">{translations.uploadStep.manifestDescription}</p>
<Box sx={styles.tableWrapper}> <div className="relative mb-0 border-t rounded-lg h-[80px]">
<ExampleTable fields={fields} /> <div className="absolute inset-0">
<ExampleTable fields={fields} />
</div>
<FadingOverlay /> <FadingOverlay />
</Box> </div>
<DropZone onContinue={handleOnContinue} isLoading={isLoading} /> <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 data = useMemo(() => generateExampleRow(fields), [fields])
const columns = useMemo(() => generateColumns(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 = () => ( export const FadingOverlay = () => (
<Box <div
position="absolute" className="absolute inset-x-0 bottom-0 h-12 pointer-events-none bg-gradient-to-t from-background to-transparent"
left={0}
right={0}
bottom={0}
height="48px"
pointerEvents="none"
bgGradient="linear(to bottom, backgroundAlpha, background)"
/> />
) )

View File

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