color picker updates

This commit is contained in:
Nate Kelley 2025-04-11 22:30:47 -06:00
parent e2921f251e
commit 52f7d99c44
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
6 changed files with 92 additions and 112 deletions

11
web/package-lock.json generated
View File

@ -73,6 +73,7 @@
"prettier-plugin-tailwindcss": "^0.6.11",
"react": "^18",
"react-color": "^2.19.3",
"react-colorful": "^5.6.1",
"react-data-grid": "7.0.0-beta.47",
"react-day-picker": "8.10.1",
"react-dom": "^18",
@ -18523,6 +18524,16 @@
"react": "*"
}
},
"node_modules/react-colorful": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz",
"integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==",
"license": "MIT",
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/react-confetti": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/react-confetti/-/react-confetti-6.4.0.tgz",

View File

@ -82,6 +82,7 @@
"prettier-plugin-tailwindcss": "^0.6.11",
"react": "^18",
"react-color": "^2.19.3",
"react-colorful": "^5.6.1",
"react-data-grid": "7.0.0-beta.47",
"react-day-picker": "8.10.1",
"react-dom": "^18",

View File

@ -7,8 +7,7 @@ const meta: Meta<typeof ColorPicker> = {
tags: ['autodocs'],
args: {
value: '#000000',
size: 'default',
variant: 'default'
size: 'default'
},
argTypes: {
value: {
@ -26,11 +25,7 @@ const meta: Meta<typeof ColorPicker> = {
options: ['small', 'default', 'tall'],
description: 'The size of the color picker button'
},
variant: {
control: 'select',
options: ['default', 'outline', 'secondary', 'ghost', 'link'],
description: 'The variant style of the color picker button'
},
className: {
control: 'text',
description: 'Additional CSS classes to apply'
@ -60,31 +55,3 @@ export const Tall: Story = {
size: 'tall'
}
};
export const OutlineVariant: Story = {
args: {
value: '#800080',
variant: 'outline'
}
};
export const SecondaryVariant: Story = {
args: {
value: '#FFA500',
variant: 'secondary'
}
};
export const GhostVariant: Story = {
args: {
value: '#008080',
variant: 'ghost'
}
};
export const LinkVariant: Story = {
args: {
value: '#4B0082',
variant: 'link'
}
};

View File

@ -1,100 +1,104 @@
import React from 'react';
import { PopoverRoot, PopoverContent, PopoverTrigger } from '@/components/ui/popover/PopoverBase';
import { cva } from 'class-variance-authority';
'use client';
import { forwardRef, useCallback, useMemo, useState } from 'react';
import { HexColorPicker } from 'react-colorful';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/buttons';
import { ChromePicker, ColorResult } from 'react-color';
import { useMemoizedFn } from '@/hooks';
import { Button, ButtonProps } from '@/components/ui/buttons';
import { Popover, PopoverRoot, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { Input } from '@/components/ui/inputs';
import { useDebounceFn } from '@/hooks';
import { cva } from 'class-variance-authority';
const colorPickerVariants = cva(
'rounded border bg-background transition-colors hover:bg-item-hover hover:text-text-default',
{
variants: {
variant: {
default: 'border-input',
outline: 'border-input',
secondary: 'border-secondary bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline'
},
size: {
default: 'h-6 min-h-6 max-h-6 px-1',
tall: 'h-7 min-h-7 max-h-7 px-0.5',
small: 'h-5 min-h-5 max-h-5 px-0.5 text-xs'
}
},
defaultVariants: {
variant: 'default',
size: 'default'
}
}
);
export interface ColorPickerProps {
value?: string | null;
onChange?: (color: string) => void;
onChangeComplete?: (color: string) => void;
size?: 'small' | 'default' | 'tall';
variant?: 'default' | 'outline' | 'secondary' | 'ghost' | 'link';
interface ColorPickerProps {
value: string | null | undefined;
onChange?: (value: string) => void;
onChangeComplete?: (value: string) => void;
onBlur?: () => void;
disabled?: boolean;
size?: 'default' | 'small' | 'tall';
name?: string;
className?: string;
}
const ColorPicker = React.forwardRef<HTMLButtonElement, ColorPickerProps>(
const colorPickerWrapperVariants = cva('border p-1 rounded cursor-pointer shadow', {
variants: {
size: {
default: 'w-6 min-w-6 max-w-6 h-6 min-h-6 max-h-6',
small: 'w-5 min-w-5 max-w-5 h-5 min-h-5 max-h-5',
tall: 'w-7 min-w-7 max-w-7 h-7 min-h-7 max-h-7'
},
disabled: {
true: 'cursor-not-allowed opacity-60',
false: 'cursor-pointer'
}
}
});
const ColorPicker = forwardRef<HTMLInputElement, ColorPickerProps>(
(
{
className,
value = '#000000',
onChange,
disabled,
onChangeComplete,
size = 'default',
variant = 'default'
value: valueProp = '#000000',
onChange,
name,
className = '',
...props
},
ref
forwardedRef
) => {
const handleChange = useMemoizedFn(
(color: ColorResult, event: React.ChangeEvent<HTMLInputElement>) => {
onChange?.(color.hex);
}
const [open, setOpen] = useState(false);
const [value, setValue] = useState(valueProp);
const parsedValue = useMemo(() => {
return value || '#000000';
}, [value]);
const { run: debouncedOnChangeComplete } = useDebounceFn(
(value: string) => {
onChangeComplete?.(value);
},
{ wait: 150 }
);
const handleChangeComplete = useMemoizedFn(
(color: ColorResult, event: React.ChangeEvent<HTMLInputElement>) => {
onChangeComplete?.(color.hex);
}
const handleInputChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e?.currentTarget?.value);
onChange?.(e?.currentTarget?.value);
debouncedOnChangeComplete?.(e?.currentTarget?.value);
},
[onChange, debouncedOnChangeComplete]
);
const handleHexColorPickerChange = useCallback(
(color: string) => {
setValue(color);
onChange?.(color);
debouncedOnChangeComplete?.(color);
},
[onChange, debouncedOnChangeComplete]
);
return (
<PopoverRoot>
<PopoverTrigger asChild>
<Button ref={ref} className={cn(colorPickerVariants({ size, variant }), className)}>
<div
className="border-border h-4 w-4 rounded-sm border"
style={{ backgroundColor: value || '#000000' }}
/>
</Button>
<PopoverRoot onOpenChange={setOpen} open={open}>
<PopoverTrigger asChild disabled={disabled}>
<div className={colorPickerWrapperVariants({ size, disabled })}>
<div className="h-full w-full rounded-sm" style={{ backgroundColor: parsedValue }} />
</div>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<ChromePicker
color={value || '#000000'}
onChange={handleChange}
onChangeComplete={handleChangeComplete}
disableAlpha
styles={{
default: {
picker: {
background: 'var(--background)',
border: '1px solid var(--border)',
boxShadow: 'var(--shadow)'
}
}
}}
<PopoverContent className="w-full" align="end" side="bottom">
<HexColorPicker color={parsedValue} onChange={handleHexColorPickerChange} />
<Input
className="mt-2.5"
maxLength={7}
onChange={handleInputChange}
value={parsedValue}
/>
</PopoverContent>
</PopoverRoot>
);
}
);
ColorPicker.displayName = 'ColorPicker';
export { ColorPicker };

View File

@ -185,7 +185,6 @@ const GoalLineItemContent: React.FC<{
<LabelAndInput label="Goal line color">
<div className="flex w-full items-center justify-end">
<ColorPicker
size="small"
value={goalLineColor}
onChangeComplete={(color) => {
const hexColor = color;

View File

@ -11,8 +11,6 @@ export const ReasoningMessage_Text: React.FC<ReasoningMessageProps> = React.memo
(x) => (x?.reasoning_messages[reasoningMessageId] as BusterChatMessageReasoning_text)?.message
)!;
console.log('message', message);
if (!message) return null;
return (