121 lines
3.5 KiB
TypeScript
121 lines
3.5 KiB
TypeScript
import { Icon } from '@iconify/react/dist/iconify.js';
|
|
import { isNil } from 'lodash-es';
|
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
import { useCopyToClipboard } from 'react-use';
|
|
import NoColor from '../assets/NoColor.svg';
|
|
import { useColorFunction } from '../ColorFunctionContext';
|
|
import styles from './ColorStand.module.css';
|
|
import { HSegmentedControl } from './HSegmentedControl';
|
|
import { NotificationType, useNotification } from './Notifications';
|
|
|
|
type ColorValueProps = {
|
|
value: string | null;
|
|
};
|
|
|
|
function ColorValue({ value }: ColorValueProps) {
|
|
const [cpState, copyToClipboard] = useCopyToClipboard();
|
|
const { showToast } = useNotification();
|
|
const handleCopy = useCallback(() => {
|
|
if (!isNil(value)) {
|
|
copyToClipboard(value);
|
|
}
|
|
}, [value]);
|
|
|
|
useEffect(() => {
|
|
if (!isNil(cpState.error)) {
|
|
showToast(NotificationType.ERROR, 'Failed to copy to clipboard', 'tabler:alert-circle', 3000);
|
|
} else if (!isNil(cpState.value)) {
|
|
showToast(
|
|
NotificationType.SUCCESS,
|
|
`${cpState.value} has been copied to clipboard.`,
|
|
'tabler:circle-check',
|
|
3000,
|
|
);
|
|
}
|
|
}, [cpState]);
|
|
|
|
return (
|
|
<div className={styles.color_value}>
|
|
{isNil(value) ? (
|
|
<div className={styles.na_value}>
|
|
<Icon icon="tabler:alert-hexagon" />
|
|
<span>Not Available</span>
|
|
</div>
|
|
) : (
|
|
<div className={styles.value} onClick={handleCopy}>
|
|
{value}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
type ColorStandProps = {
|
|
title?: string;
|
|
color?: string;
|
|
};
|
|
|
|
export function ColorStand({ title, color }: ColorStandProps) {
|
|
const { colorFn } = useColorFunction();
|
|
const [viewColor, setViewColor] = useState<'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch'>('hex');
|
|
const displayColorValue = useMemo(() => {
|
|
if (isNil(color)) {
|
|
return null;
|
|
}
|
|
try {
|
|
switch (viewColor) {
|
|
case 'hex':
|
|
return color;
|
|
case 'rgb': {
|
|
const rgb = colorFn?.represent_rgb(color);
|
|
return `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
|
|
}
|
|
case 'hsl': {
|
|
const hsl = colorFn?.represent_hsl(color);
|
|
return `hsl(${hsl[0]}, ${hsl[1]}%, ${hsl[2]}%)`;
|
|
}
|
|
case 'lab': {
|
|
const lab = colorFn?.represent_lab(color);
|
|
return `lab(${lab[0]}, ${lab[1]}, ${lab[2]})`;
|
|
}
|
|
case 'oklch': {
|
|
const oklch = colorFn?.represent_oklch(color);
|
|
return `oklch(${oklch[0]}%, ${oklch[1]}, ${oklch[2]})`;
|
|
}
|
|
default:
|
|
return null;
|
|
}
|
|
} catch (e) {
|
|
console.error('[ColorStand Convert]', e);
|
|
return null;
|
|
}
|
|
}, [viewColor, color]);
|
|
|
|
return (
|
|
<div className={styles.color_stand}>
|
|
<div className={styles.preview_box}>
|
|
<div className={styles.head_line}>
|
|
<h5>{title}</h5>
|
|
</div>
|
|
<div className={styles.color_box} style={{ backgroundColor: color }}>
|
|
<img src={NoColor} alt="No Color" />
|
|
</div>
|
|
</div>
|
|
<div className={styles.color_describe}>
|
|
<HSegmentedControl
|
|
options={[
|
|
{ label: 'HEX', value: 'hex' },
|
|
{ label: 'RGB', value: 'rgb' },
|
|
{ label: 'HSL', value: 'hsl' },
|
|
{ label: 'LAB', value: 'lab' },
|
|
{ label: 'OKLCH', value: 'oklch' },
|
|
]}
|
|
value={viewColor}
|
|
onChange={(value) => setViewColor(value as typeof viewColor)}
|
|
/>
|
|
<ColorValue value={displayColorValue} />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|