mirror of
https://github.com/documenso/documenso.git
synced 2025-11-19 11:12:06 +10:00
feat: add signature configurations (#1710)
Add ability to enable or disable allowed signature types: - Drawn - Typed - Uploaded **Tabbed style signature dialog**  **Document settings**  **Team preferences**  ## Changes Made - Add multiselect to select allowed signatures in document and templates settings tab - Add multiselect to select allowed signatures in teams preferences - Removed "Enable typed signatures" from document/template edit page - Refactored signature pad to use tabs instead of an all in one signature pad ## Testing Performed Added E2E tests to check settings are applied correctly for documents and templates
This commit is contained in:
147
packages/ui/primitives/signature-pad/signature-tabs.tsx
Normal file
147
packages/ui/primitives/signature-pad/signature-tabs.tsx
Normal file
@ -0,0 +1,147 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
|
||||
interface TabsProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
defaultValue?: string;
|
||||
value?: string;
|
||||
onValueChange?: (value: string) => void;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
interface TabsContextValue {
|
||||
value: string;
|
||||
onValueChange: (value: string) => void;
|
||||
}
|
||||
|
||||
const TabsContext = React.createContext<TabsContextValue | undefined>(undefined);
|
||||
|
||||
function useTabs() {
|
||||
const context = React.useContext(TabsContext);
|
||||
if (!context) {
|
||||
throw new Error('useTabs must be used within a Tabs provider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
export function Tabs({
|
||||
defaultValue,
|
||||
value,
|
||||
onValueChange,
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: TabsProps) {
|
||||
const [tabValue, setTabValue] = React.useState(defaultValue || '');
|
||||
|
||||
const handleValueChange = React.useCallback(
|
||||
(newValue: string) => {
|
||||
setTabValue(newValue);
|
||||
onValueChange?.(newValue);
|
||||
},
|
||||
[onValueChange],
|
||||
);
|
||||
|
||||
const contextValue = React.useMemo(
|
||||
() => ({
|
||||
value: value !== undefined ? value : tabValue,
|
||||
onValueChange: handleValueChange,
|
||||
}),
|
||||
[value, tabValue, handleValueChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<TabsContext.Provider value={contextValue}>
|
||||
<div className={cn('w-full', className)} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
</TabsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
interface TabsListProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function TabsList({ children, className, ...props }: TabsListProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn('border-border flex flex-wrap border-b', className)}
|
||||
role="tabslist"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface TabsTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
value: string;
|
||||
icon?: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function TabsTrigger({ value, icon, children, className, ...props }: TabsTriggerProps) {
|
||||
const { value: selectedValue, onValueChange } = useTabs();
|
||||
const isSelected = selectedValue === value;
|
||||
|
||||
return (
|
||||
<button
|
||||
role="tab"
|
||||
type="button"
|
||||
aria-selected={isSelected}
|
||||
data-state={isSelected ? 'active' : 'inactive'}
|
||||
onClick={() => onValueChange(value)}
|
||||
className={cn(
|
||||
'relative flex items-center px-4 py-3 text-sm font-medium transition-all',
|
||||
'focus-visible:ring-ring focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
|
||||
isSelected ? 'text-foreground' : 'text-muted-foreground hover:text-foreground',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{icon && <span className="flex items-center">{icon}</span>}
|
||||
{children}
|
||||
{isSelected && (
|
||||
<motion.div
|
||||
layoutId="activeTabIndicator"
|
||||
className="bg-foreground/40 absolute bottom-0 left-0 h-0.5 w-full rounded-full"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{
|
||||
type: 'spring',
|
||||
stiffness: 500,
|
||||
damping: 50,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
interface TabsContentProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
value: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function TabsContent({ value, children, className, ...props }: TabsContentProps) {
|
||||
const { value: selectedValue } = useTabs();
|
||||
const isSelected = selectedValue === value;
|
||||
|
||||
if (!isSelected) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
role="tabpanel"
|
||||
data-state={isSelected ? 'active' : 'inactive'}
|
||||
className={cn('mt-4', className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user