mirror of
https://github.com/frappe/erpnext.git
synced 2026-09-18 02:55:20 +00:00
* feat: new banking module (#54720)
* feat: initial SPA setup for banking
* wip: bring over new banking module
* feat: added Espresso design tokens
* feat: button styles
* fix: add all ink colors
* wip: espresso design system changes
* feat: button and badge espresso components
* fix: button styling for reconcile
* feat: Espresso progress bar
* feat: Espresso toggle switch
* feat: Espresso tabs design
* fix: vertical tab support
* fix: button sizing across modals
* feat: Espresso style table layout
* feat: Espresso tooltip
* feat: Espresso elevations and checkbox
* feat: Dialog with Espresso styles
* feat: Espresso textarea
* fix: input styles
* fix: colors on bank picker
* fix: breadcrumb styling
* fix: bank picker styling
* feat: create doctypes and fields for bank reconciliation
* feat: APIs for banking
* fix: use date format parser
* fix: font styling to match Espresso
* wip: settings modal
* feat: settings dialog component
* fix: icons and invalid requests
* feat: preferences tab
* fix: adjust icon stroke width to 1.5
* feat: rule configuration in settings
* fix: remove sheet component
* feat: alert and error banner component
* feat: dropdown in Espresso
* feat: popover and select in Espresso
* fix: cleanup more styles
* fix: match size of link fields
* feat: command styling
* fix: remove unused style tokens
* fix: styles for global date picker dropdown
* fix: styles for match and reconcile
* feat: table Espresso component
* feat: remove all other design tokens
* fix: remove unused tokens
* fix: form elements
* fix: remove unused styles and fix filters in bank transaction list
* feat: fetch bank rec doctypes for filtering
* fix: record payment modal
* feat: support for dark mode switching
* fix: move bank logos to public folder
* feat: add support for RTL
* feat: support for RTL
* chore: send layout direction in dev boot
* fix: make checkbox work in RTL
* feat: dark mode support
* fix: dark mode style
* feat: bank logos in dark mode
* feat: dark mode bank logos
* chore: use dark mode bank logos everywhere
* chore: move rule evaluation to controller
* chore: add tests for bank transaction rules
* fix: move deps to fix actions errors
* fix: move tw-animate-css to deps
* fix: remove shadcn
* fix: do not open modal if no transactions selected
* fix: add translation strings
* feat: add banner on existing bank reconciliation tool
* feat: bank statement import
* fix: translations and layout directions
* fix: validation for transaction matching rule
* fix: styles
* fix: show conflicting transactions in alert
* fix: show help text for new banking module forms
* feat: show total debits and credits
* fix: dark mode colors in automatic config
* feat: add keyboard shortcuts help
* feat: added keyboard shortcut for settings
* fix: decrease size of progress bar
* chore: bump packages
* feat: add tests for statement import
* fix: settings dialog
* fix: show banner on small screens
* fix: show banner when no bank account set
(cherry picked from commit 6de5367f12)
# Conflicts:
# erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
* chore: resolve conflicts
* fix: add type hints to whitelisted methods
---------
Co-authored-by: Nikhil Kothari <nik.kothari22@live.com>
This commit is contained in:
273
banking/src/components/ui/settings-dialog.tsx
Normal file
273
banking/src/components/ui/settings-dialog.tsx
Normal file
@@ -0,0 +1,273 @@
|
||||
import * as React from "react"
|
||||
import { Tabs as TabsPrimitive, Dialog as DialogPrimitive } from "radix-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { DialogContent } from "./dialog"
|
||||
|
||||
/**
|
||||
* Sample Usage:
|
||||
*
|
||||
* <Dialog open={open} onOpenChange={setOpen}>
|
||||
* <DialogTrigger>
|
||||
* ...your content...
|
||||
* </DialogTrigger>
|
||||
*
|
||||
* <SettingsDialog onClose={() => setOpen(false)} defaultValue="preferences">
|
||||
* <SettingsTabs>
|
||||
* <SettingsTabGroup header="Configuration">
|
||||
* <SettingsTabItem icon={<SlidersVerticalIcon />} label="Preferences" value="preferences" />
|
||||
* <SettingsTabItem icon={<ZapIcon />} label="Matching Rules" value="rules" />
|
||||
* </SettingsTabGroup>
|
||||
* <SettingsTabGroup header="Setup">
|
||||
* <SettingsTabItem icon={<LandmarkIcon />} label="Bank Accounts" value="bank-accounts" />
|
||||
* <SettingsTabItem icon={<ListIcon />} label="Masters" value="masters" />
|
||||
* </SettingsTabGroup>
|
||||
* </SettingsTabs>
|
||||
*
|
||||
* <SettingsPanels>
|
||||
* <SettingsPanel value="preferences"><Preferences /></SettingsPanel>
|
||||
* <SettingsPanel value="rules"><MatchingRules /></SettingsPanel>
|
||||
* <SettingsPanel value="bank-accounts"><BankAccounts /></SettingsPanel>
|
||||
* <SettingsPanel value="masters"><Masters /></SettingsPanel>
|
||||
* </SettingsPanels>
|
||||
* </SettingsDialog>
|
||||
* </Dialog>
|
||||
*/
|
||||
|
||||
type SettingsDialogContextValue = {
|
||||
onClose?: VoidFunction
|
||||
}
|
||||
|
||||
const SettingsDialogContext = React.createContext<SettingsDialogContextValue>({})
|
||||
|
||||
/**
|
||||
* Exposes `onClose` to descendant panels so they can dismiss the dialog after
|
||||
* a successful save without prop-drilling.
|
||||
*/
|
||||
export const useSettingsDialog = () => React.useContext(SettingsDialogContext)
|
||||
|
||||
type SettingsDialogProps = Omit<
|
||||
React.ComponentProps<typeof TabsPrimitive.Root>,
|
||||
"orientation"
|
||||
> & {
|
||||
onClose?: VoidFunction
|
||||
contentClassName?: string
|
||||
}
|
||||
|
||||
function SettingsDialog({
|
||||
children,
|
||||
className,
|
||||
contentClassName,
|
||||
onClose,
|
||||
...props
|
||||
}: SettingsDialogProps) {
|
||||
const contextValue = React.useMemo(() => ({ onClose }), [onClose])
|
||||
|
||||
return (
|
||||
<DialogContent className={cn("min-w-5xl max-lg:min-w-[98vw] p-0 overflow-y-hidden", contentClassName)} showCloseButton={false}>
|
||||
<SettingsDialogContext.Provider value={contextValue}>
|
||||
<TabsPrimitive.Root
|
||||
data-slot="settings-dialog"
|
||||
orientation="vertical"
|
||||
className={cn(
|
||||
"flex h-[calc(100vh-8rem)] bg-surface-menu-bar",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</TabsPrimitive.Root>
|
||||
</SettingsDialogContext.Provider>
|
||||
</DialogContent>
|
||||
)
|
||||
}
|
||||
|
||||
function SettingsTabs({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TabsPrimitive.List>) {
|
||||
return (
|
||||
<TabsPrimitive.List
|
||||
data-slot="settings-tabs"
|
||||
className={cn(
|
||||
"flex flex-col w-56 bg-surface-menu-bar rounded-s-lg shrink-0 overflow-y-auto m-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type SettingsTabGroupProps = React.ComponentProps<"div"> & {
|
||||
header?: React.ReactNode
|
||||
}
|
||||
|
||||
function SettingsTabGroup({
|
||||
children,
|
||||
header,
|
||||
className,
|
||||
...props
|
||||
}: SettingsTabGroupProps) {
|
||||
return (
|
||||
<div data-slot="settings-tab-group" className={className} {...props}>
|
||||
{header && (
|
||||
<div className="h-7.5 px-2 py-[7px] my-[3px] flex cursor-default gap-1.5 text-xs font-medium text-ink-gray-5 transition-all duration-300 ease-in-out sticky top-0 z-10 bg-surface-menu-bar">
|
||||
<span>{header}</span>
|
||||
</div>
|
||||
)}
|
||||
<nav className="space-y-[3px] px-1">{children}</nav>
|
||||
<div className="mb-0.5 mt-[5px]"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type SettingsTabItemProps = React.ComponentProps<typeof TabsPrimitive.Trigger> & {
|
||||
icon?: React.ReactNode
|
||||
label: React.ReactNode
|
||||
}
|
||||
|
||||
function SettingsTabItem({
|
||||
icon,
|
||||
label,
|
||||
className,
|
||||
...props
|
||||
}: SettingsTabItemProps) {
|
||||
return (
|
||||
<TabsPrimitive.Trigger
|
||||
data-slot="settings-tab-item"
|
||||
className={cn(
|
||||
"flex h-7.5 cursor-pointer items-center rounded text-ink-gray-6 duration-300 ease-in-out focus:outline-none focus:transition-none focus-visible:rounded focus-visible:ring-2 focus-visible:ring-outline-gray-3 w-full",
|
||||
"hover:bg-surface-gray-3",
|
||||
"data-[state=active]:bg-surface-selected data-[state=active]:shadow-sm data-[state=active]:hover:bg-surface-selected",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="flex w-full items-center justify-between duration-300 ease-in-out px-2 py-[7px]">
|
||||
<div className="flex items-center truncate">
|
||||
{icon && (
|
||||
<div className="[&_svg:not([class*='size-'])]:size-4 text-ink-gray-6 [&_svg:not([class*='text-'])]:text-ink-gray-6">
|
||||
{icon}
|
||||
</div>
|
||||
)}
|
||||
<span
|
||||
className={cn(
|
||||
"flex-1 shrink-0 truncate text-sm duration-300 ease-in-out w-auto opacity-100 text-ink-gray-6",
|
||||
icon && "ms-2"
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</TabsPrimitive.Trigger>
|
||||
)
|
||||
}
|
||||
|
||||
function SettingsPanels({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="settings-panels"
|
||||
className={cn(
|
||||
"flex flex-col flex-1 overflow-y-auto bg-surface-modal",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function SettingsPanel({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
|
||||
return (
|
||||
<TabsPrimitive.Content
|
||||
data-slot="settings-panel"
|
||||
className={cn("flex flex-col h-full w-full text-ink-gray-8 py-8 px-6 gap-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
*
|
||||
* <SettingsPanelHeader actions={<><Button>Add</Button></>}>
|
||||
*
|
||||
* <SettingsPanelTitle>Settings</SettingsPanelTitle>
|
||||
* <SettingsPanelDescription>Settings description</SettingsPanelDescription>
|
||||
*
|
||||
* </SettingsPanelHeader>
|
||||
*/
|
||||
function SettingsPanelHeader({
|
||||
className,
|
||||
children,
|
||||
actions,
|
||||
...props
|
||||
}: React.ComponentProps<"div"> & { actions?: React.ReactNode }) {
|
||||
return (
|
||||
<div
|
||||
data-slot="dialog-header"
|
||||
className={cn("flex justify-between items-start px-2 text-ink-gray-7", className)}
|
||||
{...props}
|
||||
>
|
||||
<div className="flex flex-col gap-1 w-full">
|
||||
{children}
|
||||
</div>
|
||||
<div className="flex item-center space-x-2 w-fit justify-end">
|
||||
{actions}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function SettingsPanelTitle({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
||||
return (
|
||||
<DialogPrimitive.Title
|
||||
data-slot="dialog-title"
|
||||
className={cn("flex gap-2 text-xl font-semibold leading-none h-5", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function SettingsPanelDescription({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
||||
return (
|
||||
<DialogPrimitive.Description
|
||||
data-slot="dialog-description"
|
||||
className={cn("text-p-base text-ink-gray-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function SettingsPanelContent({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div className={cn("flex-1 flex flex-col overflow-y-auto px-2", className)} {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
SettingsDialog,
|
||||
SettingsTabs,
|
||||
SettingsTabGroup,
|
||||
SettingsTabItem,
|
||||
SettingsPanels,
|
||||
SettingsPanel,
|
||||
SettingsPanelHeader,
|
||||
SettingsPanelTitle,
|
||||
SettingsPanelDescription,
|
||||
SettingsPanelContent
|
||||
}
|
||||
Reference in New Issue
Block a user