Files
documenso/packages/ui/primitives/color-picker.tsx
T
Lucas Smith 0b86ece1d5 feat: add custom branding for signing pages (#2785)
Platform-plan organisations and teams can now customise non-embed
signing pages with six brand colour tokens, a border-radius, and
a free-text custom CSS block (up to 256 KB).

- Stored on OrganisationGlobalSettings / TeamGlobalSettings;
  teams inherit from the org via brandingEnabled === null.
- CSS is sanitised on save (PostCSS) so we can inline it at SSR
  with no per-render parsing.
- Rendered via a nonce'd <style> scoped under .documenso-branded,
  using native CSS nesting so user selectors don't need scoping.
- Gated on the existing embedSigningWhiteLabel claim (or
  self-hosted) — reuses the embed white-label decision.
2026-05-11 13:03:02 +10:00

90 lines
2.3 KiB
TypeScript

import type { HTMLAttributes } from 'react';
import { useEffect, useState } from 'react';
import { HexColorInput, HexColorPicker, setNonce } from 'react-colorful';
import { cn } from '../lib/utils';
import { Popover, PopoverContent, PopoverTrigger } from './popover';
export type ColorPickerProps = {
disabled?: boolean;
value: string;
defaultValue?: string;
onChange: (color: string) => void;
nonce?: string;
} & HTMLAttributes<HTMLDivElement>;
export const ColorPicker = ({
className,
disabled = false,
value,
defaultValue = '#000000',
onChange,
nonce,
...props
}: ColorPickerProps) => {
const [color, setColor] = useState(value || defaultValue);
const [inputColor, setInputColor] = useState(value || defaultValue);
const onColorChange = (newColor: string) => {
setColor(newColor);
setInputColor(newColor);
onChange(newColor);
};
const onInputChange = (newColor: string) => {
setInputColor(newColor);
};
const onInputBlur = () => {
setColor(inputColor);
onChange(inputColor);
};
useEffect(() => {
if (nonce) {
setNonce(nonce);
}
}, [nonce]);
return (
<Popover>
<PopoverTrigger>
<button
type="button"
disabled={disabled}
className="h-12 w-12 rounded-md border bg-background p-1 disabled:pointer-events-none disabled:opacity-50"
>
<div className="h-full w-full rounded-sm" style={{ backgroundColor: color }} />
</button>
</PopoverTrigger>
<PopoverContent className="w-auto">
<HexColorPicker
className={cn(className, 'w-full aria-disabled:pointer-events-none aria-disabled:opacity-50')}
color={color}
onChange={onColorChange}
aria-disabled={disabled}
nonce={nonce}
{...props}
/>
<HexColorInput
className="mt-4 h-10 rounded-md border bg-transparent px-3 py-2 text-sm disabled:pointer-events-none disabled:opacity-50"
color={inputColor}
onChange={onInputChange}
onBlur={onInputBlur}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
onInputBlur();
}
}}
disabled={disabled}
nonce={nonce}
/>
</PopoverContent>
</Popover>
);
};