feat: color palette for calendar (#118)
* feat: work on the palette * feat: palette basically done? * fix: lint warnings and errors * fix: minor fixes * fix: color patch colors and shades * fix: prettier issue * chore: use TS satisfies * chore: remove eslint-disable comment --------- Co-authored-by: doprz <52579214+doprz@users.noreply.github.com>
This commit is contained in:
@@ -12,6 +12,7 @@ export const colors = {
|
|||||||
offwhite: '#D6D2C4',
|
offwhite: '#D6D2C4',
|
||||||
concrete: '#95A5A6',
|
concrete: '#95A5A6',
|
||||||
red: '#B91C1C', // Not sure if this should be here, but it's used for remove course, and add course is ut-green
|
red: '#B91C1C', // Not sure if this should be here, but it's used for remove course, and add course is ut-green
|
||||||
|
white: '#FFFFFF',
|
||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
red: '#AF2E2D',
|
red: '#AF2E2D',
|
||||||
@@ -31,6 +32,128 @@ export const colors = {
|
|||||||
dminus: '#B91C1C',
|
dminus: '#B91C1C',
|
||||||
f: '#B91C1C',
|
f: '#B91C1C',
|
||||||
},
|
},
|
||||||
|
palette: {
|
||||||
|
slateBase: '#64748B',
|
||||||
|
slate200: '#e2e8f0',
|
||||||
|
slate300: '#cbd5e1',
|
||||||
|
slate400: '#94a3b8',
|
||||||
|
slate600: '#475569',
|
||||||
|
slate700: '#334155',
|
||||||
|
grayBase: '#6b7280',
|
||||||
|
gray200: '#e5e7eb',
|
||||||
|
gray300: '#d1d5db',
|
||||||
|
gray400: '#9ca3af',
|
||||||
|
gray600: '#4b5563',
|
||||||
|
gray700: '#374151',
|
||||||
|
stoneBase: '#78716c',
|
||||||
|
stone200: '#e7e5e4',
|
||||||
|
stone300: '#d6d3d1',
|
||||||
|
stone400: '#a8a29e',
|
||||||
|
stone600: '#57534e',
|
||||||
|
stone700: '#44403c',
|
||||||
|
redBase: '#ef4444',
|
||||||
|
red200: '#fecaca',
|
||||||
|
red300: '#fca5a5',
|
||||||
|
red400: '#f87171',
|
||||||
|
red600: '#dc2626',
|
||||||
|
red700: '#b91c1c',
|
||||||
|
orangeBase: '#f97316',
|
||||||
|
orange200: '#fed7aa',
|
||||||
|
orange300: '#fdba74',
|
||||||
|
orange400: '#fb923c',
|
||||||
|
orange600: '#ea580c',
|
||||||
|
orange700: '#c2410c',
|
||||||
|
amberBase: '#f59e0b',
|
||||||
|
amber200: '#fde68a',
|
||||||
|
amber300: '#fcd34d',
|
||||||
|
amber400: '#fbbf24',
|
||||||
|
amber600: '#d97706',
|
||||||
|
amber700: '#b45309',
|
||||||
|
yellowBase: '#eab308',
|
||||||
|
yellow200: '#fef08a',
|
||||||
|
yellow300: '#fde047',
|
||||||
|
yellow400: '#facc15',
|
||||||
|
yellow600: '#ca8a04',
|
||||||
|
yellow700: '#a16207',
|
||||||
|
limeBase: '#84cc16',
|
||||||
|
lime200: '#d9f99d',
|
||||||
|
lime300: '#bef264',
|
||||||
|
lime400: '#a3e635',
|
||||||
|
lime600: '#65a30d',
|
||||||
|
lime700: '#4d7c0f',
|
||||||
|
greenBase: '#22c55e',
|
||||||
|
green200: '#bbf7d0',
|
||||||
|
green300: '#86efac',
|
||||||
|
green400: '#4ade80',
|
||||||
|
green600: '#16a34a',
|
||||||
|
green700: '#15803d',
|
||||||
|
emeraldBase: '#10b981',
|
||||||
|
emerald200: '#a7f3d0',
|
||||||
|
emerald300: '#6ee7b7',
|
||||||
|
emerald400: '#34d399',
|
||||||
|
emerald600: '#059669',
|
||||||
|
emerald700: '#047857',
|
||||||
|
tealBase: '#14b8a6',
|
||||||
|
teal200: '#99f6e4',
|
||||||
|
teal300: '#5eead4',
|
||||||
|
teal400: '#2dd4bf',
|
||||||
|
teal600: '#0d9488',
|
||||||
|
teal700: '#0f766e',
|
||||||
|
cyanBase: '#06b6d4',
|
||||||
|
cyan200: '#a5f3fc',
|
||||||
|
cyan300: '#67e8f9',
|
||||||
|
cyan400: '#22d3ee',
|
||||||
|
cyan600: '#0891b2',
|
||||||
|
cyan700: '#0e7490',
|
||||||
|
skyBase: '#0ea5e9',
|
||||||
|
sky200: '#bae6fd',
|
||||||
|
sky300: '#7dd3fc',
|
||||||
|
sky400: '#38bdf8',
|
||||||
|
sky600: '#0284c7',
|
||||||
|
sky700: '#0369a1',
|
||||||
|
blueBase: '#3b82f6',
|
||||||
|
blue200: '#bfdbfe',
|
||||||
|
blue300: '#93c5fd',
|
||||||
|
blue400: '#60a5fa',
|
||||||
|
blue600: '#2563eb',
|
||||||
|
blue700: '#1d4ed8',
|
||||||
|
indigoBase: '#6366f1',
|
||||||
|
indigo200: '#c7d2fe',
|
||||||
|
indigo300: '#a5b4fc',
|
||||||
|
indigo400: '#818cf8',
|
||||||
|
indigo600: '#4f46e5',
|
||||||
|
indigo700: '#4338ca',
|
||||||
|
violetBase: '#8b5cf6',
|
||||||
|
violet200: '#ddd6fe',
|
||||||
|
violet300: '#c4b5fd',
|
||||||
|
violet400: '#a78bfa',
|
||||||
|
violet600: '#7c3aed',
|
||||||
|
violet700: '#6d28d9',
|
||||||
|
purpleBase: '#a855f7',
|
||||||
|
purple200: '#e9d5ff',
|
||||||
|
purple300: '#d8b4fe',
|
||||||
|
purple400: '#c084fc',
|
||||||
|
purple600: '#9333ea',
|
||||||
|
purple700: '#7e22ce',
|
||||||
|
fuschiaBase: '#d946ef',
|
||||||
|
fuschia200: '#f5d0fe',
|
||||||
|
fuschia300: '#f0abfc',
|
||||||
|
fuschia400: '#e879f9',
|
||||||
|
fuschia600: '#c026d3',
|
||||||
|
fuschia700: '#a21caf',
|
||||||
|
pinkBase: '#ec4899',
|
||||||
|
pink200: '#fbcfe8',
|
||||||
|
pink300: '#f9a8d4',
|
||||||
|
pink400: '#f472b6',
|
||||||
|
pink600: '#db2777',
|
||||||
|
pink700: '#be185d',
|
||||||
|
roseBase: '#f43f5e',
|
||||||
|
rose200: '#fecdd3',
|
||||||
|
rose300: '#fda4af',
|
||||||
|
rose400: '#fb7185',
|
||||||
|
rose600: '#e11d48',
|
||||||
|
rose700: '#be123c',
|
||||||
|
},
|
||||||
} as const satisfies Record<string, Record<string, string>>;
|
} as const satisfies Record<string, Record<string, string>>;
|
||||||
|
|
||||||
type NestedKeys<T> = {
|
type NestedKeys<T> = {
|
||||||
|
|||||||
19
src/stories/components/CourseCellColorPicker.stories.tsx
Normal file
19
src/stories/components/CourseCellColorPicker.stories.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import CourseCellColorPicker from '@views/components/common/CourseCellColorPicker/CourseCellColorPicker';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import type { ThemeColor } from 'src/shared/util/themeColors';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Components/Common/CourseCellColorPicker',
|
||||||
|
component: CourseCellColorPicker,
|
||||||
|
} satisfies Meta<typeof CourseCellColorPicker>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof CourseCellColorPicker>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
render: () => {
|
||||||
|
const [selectedColor, setSelectedColor] = useState<ThemeColor | null>(null);
|
||||||
|
return <CourseCellColorPicker setSelectedColor={setSelectedColor} />;
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { Button } from '@views/components/common/Button/Button';
|
||||||
|
import React from 'react';
|
||||||
|
import type { ThemeColor } from 'src/shared/util/themeColors';
|
||||||
|
|
||||||
|
import CheckIcon from '~icons/material-symbols/check';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the ColorPatch component
|
||||||
|
*/
|
||||||
|
interface ColorPatchProps {
|
||||||
|
color: ThemeColor;
|
||||||
|
index: number;
|
||||||
|
selectedColor: number;
|
||||||
|
handleSetSelectedColorPatch: (colorPatchIndex: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {ColorPatchProps} props - the props for the component
|
||||||
|
* @param {ThemeColor} props.color - the color to display
|
||||||
|
* @param {number} props.index - the index of this color patch in the parent color palette
|
||||||
|
* @param {number} props.selectedColor - the index of the selected color patch in the parent color palette
|
||||||
|
* @param {(colorPatchIndex: number) => void} props.handleSetSelectedColorPatch - fn called when a color patch is selected. This function
|
||||||
|
* is passed from the parent and updates the necessary parent state when this color patch is selected.
|
||||||
|
* @returns {JSX.Element} - the color patch component
|
||||||
|
*/
|
||||||
|
const ColorPatch: React.FC<ColorPatchProps> = ({
|
||||||
|
color,
|
||||||
|
index,
|
||||||
|
selectedColor,
|
||||||
|
handleSetSelectedColorPatch,
|
||||||
|
}: ColorPatchProps): JSX.Element => {
|
||||||
|
const isSelected = selectedColor === index;
|
||||||
|
const handleClick = () => {
|
||||||
|
handleSetSelectedColorPatch(isSelected ? -1 : index);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
style={{ backgroundColor: color }}
|
||||||
|
className='h-[22px] w-[22px] p-0'
|
||||||
|
variant='filled'
|
||||||
|
onClick={handleClick}
|
||||||
|
color={color}
|
||||||
|
>
|
||||||
|
{isSelected && <CheckIcon className='h-[20px] w-[20px]' />}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ColorPatch;
|
||||||
@@ -0,0 +1,393 @@
|
|||||||
|
import { Button } from '@views/components/common/Button/Button';
|
||||||
|
import React from 'react';
|
||||||
|
import type { ThemeColor } from 'src/shared/util/themeColors';
|
||||||
|
import { getThemeColorHexByName } from 'src/shared/util/themeColors';
|
||||||
|
|
||||||
|
import InvertColorsOffIcon from '~icons/material-symbols/invert-colors-off';
|
||||||
|
|
||||||
|
import Divider from '../Divider/Divider';
|
||||||
|
import ColorPatch from './ColorPatch';
|
||||||
|
import DivWrapper from './DivWrapper';
|
||||||
|
import HexColorEditor from './HexColorEditor';
|
||||||
|
import HuePicker from './HuePicker';
|
||||||
|
|
||||||
|
interface Color {
|
||||||
|
baseColor: ThemeColor;
|
||||||
|
shades: ThemeColor[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const colorPatches: Color[] = [
|
||||||
|
{
|
||||||
|
baseColor: 'palette-slateBase',
|
||||||
|
shades: [
|
||||||
|
'palette-slate200',
|
||||||
|
'palette-slate300',
|
||||||
|
'palette-slate400',
|
||||||
|
'palette-slateBase',
|
||||||
|
'palette-slate600',
|
||||||
|
'palette-slate700',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
baseColor: 'palette-grayBase',
|
||||||
|
shades: [
|
||||||
|
'palette-gray200',
|
||||||
|
'palette-gray300',
|
||||||
|
'palette-gray400',
|
||||||
|
'palette-grayBase',
|
||||||
|
'palette-gray600',
|
||||||
|
'palette-gray700',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
baseColor: 'palette-stoneBase',
|
||||||
|
shades: [
|
||||||
|
'palette-stone200',
|
||||||
|
'palette-stone300',
|
||||||
|
'palette-stone400',
|
||||||
|
'palette-stoneBase',
|
||||||
|
'palette-stone600',
|
||||||
|
'palette-stone700',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
baseColor: 'palette-redBase',
|
||||||
|
shades: [
|
||||||
|
'palette-red200',
|
||||||
|
'palette-red300',
|
||||||
|
'palette-red400',
|
||||||
|
'palette-redBase',
|
||||||
|
'palette-red600',
|
||||||
|
'palette-red700',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
baseColor: 'palette-orangeBase',
|
||||||
|
shades: [
|
||||||
|
'palette-orange200',
|
||||||
|
'palette-orange300',
|
||||||
|
'palette-orange400',
|
||||||
|
'palette-orangeBase',
|
||||||
|
'palette-orange600',
|
||||||
|
'palette-orange700',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
baseColor: 'palette-amberBase',
|
||||||
|
shades: [
|
||||||
|
'palette-amber200',
|
||||||
|
'palette-amber300',
|
||||||
|
'palette-amber400',
|
||||||
|
'palette-amberBase',
|
||||||
|
'palette-amber600',
|
||||||
|
'palette-amber700',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
baseColor: 'palette-yellowBase',
|
||||||
|
shades: [
|
||||||
|
'palette-yellow200',
|
||||||
|
'palette-yellow300',
|
||||||
|
'palette-yellow400',
|
||||||
|
'palette-yellowBase',
|
||||||
|
'palette-yellow600',
|
||||||
|
'palette-yellow700',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
baseColor: 'palette-limeBase',
|
||||||
|
shades: [
|
||||||
|
'palette-lime200',
|
||||||
|
'palette-lime300',
|
||||||
|
'palette-lime400',
|
||||||
|
'palette-limeBase',
|
||||||
|
'palette-lime600',
|
||||||
|
'palette-lime700',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
baseColor: 'palette-greenBase',
|
||||||
|
shades: [
|
||||||
|
'palette-green200',
|
||||||
|
'palette-green300',
|
||||||
|
'palette-green400',
|
||||||
|
'palette-greenBase',
|
||||||
|
'palette-green600',
|
||||||
|
'palette-green700',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
baseColor: 'palette-emeraldBase',
|
||||||
|
shades: [
|
||||||
|
'palette-emerald200',
|
||||||
|
'palette-emerald300',
|
||||||
|
'palette-emerald400',
|
||||||
|
'palette-emeraldBase',
|
||||||
|
'palette-emerald600',
|
||||||
|
'palette-emerald700',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
baseColor: 'palette-tealBase',
|
||||||
|
shades: [
|
||||||
|
'palette-teal200',
|
||||||
|
'palette-teal300',
|
||||||
|
'palette-teal400',
|
||||||
|
'palette-tealBase',
|
||||||
|
'palette-teal600',
|
||||||
|
'palette-teal700',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
baseColor: 'palette-cyanBase',
|
||||||
|
shades: [
|
||||||
|
'palette-cyan200',
|
||||||
|
'palette-cyan300',
|
||||||
|
'palette-cyan400',
|
||||||
|
'palette-cyanBase',
|
||||||
|
'palette-cyan600',
|
||||||
|
'palette-cyan700',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
baseColor: 'palette-skyBase',
|
||||||
|
shades: [
|
||||||
|
'palette-sky200',
|
||||||
|
'palette-sky300',
|
||||||
|
'palette-sky400',
|
||||||
|
'palette-skyBase',
|
||||||
|
'palette-sky600',
|
||||||
|
'palette-sky700',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
baseColor: 'palette-blueBase',
|
||||||
|
shades: [
|
||||||
|
'palette-blue200',
|
||||||
|
'palette-blue300',
|
||||||
|
'palette-blue400',
|
||||||
|
'palette-blueBase',
|
||||||
|
'palette-blue600',
|
||||||
|
'palette-blue700',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
baseColor: 'palette-indigoBase',
|
||||||
|
shades: [
|
||||||
|
'palette-indigo200',
|
||||||
|
'palette-indigo300',
|
||||||
|
'palette-indigo400',
|
||||||
|
'palette-indigoBase',
|
||||||
|
'palette-indigo600',
|
||||||
|
'palette-indigo700',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
baseColor: 'palette-violetBase',
|
||||||
|
shades: [
|
||||||
|
'palette-violet200',
|
||||||
|
'palette-violet300',
|
||||||
|
'palette-violet400',
|
||||||
|
'palette-violetBase',
|
||||||
|
'palette-violet600',
|
||||||
|
'palette-violet700',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
baseColor: 'palette-purpleBase',
|
||||||
|
shades: [
|
||||||
|
'palette-purple200',
|
||||||
|
'palette-purple300',
|
||||||
|
'palette-purple400',
|
||||||
|
'palette-purpleBase',
|
||||||
|
'palette-purple600',
|
||||||
|
'palette-purple700',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
baseColor: 'palette-fuschiaBase',
|
||||||
|
shades: [
|
||||||
|
'palette-fuschia200',
|
||||||
|
'palette-fuschia300',
|
||||||
|
'palette-fuschia400',
|
||||||
|
'palette-fuschiaBase',
|
||||||
|
'palette-fuschia600',
|
||||||
|
'palette-fuschia700',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
baseColor: 'palette-pinkBase',
|
||||||
|
shades: [
|
||||||
|
'palette-pink200',
|
||||||
|
'palette-pink300',
|
||||||
|
'palette-pink400',
|
||||||
|
'palette-pinkBase',
|
||||||
|
'palette-pink600',
|
||||||
|
'palette-pink700',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
baseColor: 'palette-roseBase',
|
||||||
|
shades: [
|
||||||
|
'palette-rose200',
|
||||||
|
'palette-rose300',
|
||||||
|
'palette-rose400',
|
||||||
|
'palette-roseBase',
|
||||||
|
'palette-rose600',
|
||||||
|
'palette-rose700',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const hexCodeToBaseColorPatchIndex = new Map(
|
||||||
|
colorPatches.map((color: Color, index: number) => [getThemeColorHexByName(color.baseColor), index])
|
||||||
|
);
|
||||||
|
|
||||||
|
const hexCodeToShadeColorPatchIndex = new Map(
|
||||||
|
colorPatches.flatMap((color: Color, index: number) =>
|
||||||
|
color.shades.map(shade => [getThemeColorHexByName(shade), index])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the CourseCellColorPicker component.
|
||||||
|
*/
|
||||||
|
export interface CourseCellColorPickerProps {
|
||||||
|
setSelectedColor: React.Dispatch<React.SetStateAction<string | null>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {CourseCellColorPickerProps} props - the props for the component
|
||||||
|
* @param {React.Dispatch<React.SetStateAction<string | null>>} props.setSelectedColor - set state function passed down from the parent component
|
||||||
|
* that will be called when a color is selected. The user can set any valid hex color they want.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* const CourseCell = () => {
|
||||||
|
* const [selectedColor, setSelectedColor] = useState<string | null>(null);
|
||||||
|
* ...
|
||||||
|
* return (
|
||||||
|
* <div style={{ backgroundColor: selectedColor }}>
|
||||||
|
...
|
||||||
|
* <CourseCellColorPicker setSelectedColor={setSelectedColor} />
|
||||||
|
* );
|
||||||
|
* };
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @returns {JSX.Element} - the color picker component that displays a color palette with a list of color patches.
|
||||||
|
* This component is available when a user hovers over a course cell in their calendar to
|
||||||
|
* color for the course cell. The user can set any valid hex color they want.
|
||||||
|
*/
|
||||||
|
const CourseCellColorPicker: React.FC<CourseCellColorPickerProps> = ({
|
||||||
|
setSelectedColor: setFinalColor,
|
||||||
|
}: CourseCellColorPickerProps): JSX.Element => {
|
||||||
|
const [selectedBaseColorPatch, setSelectedBaseColorPatch] = React.useState<number>(-1);
|
||||||
|
const [selectedShadeColorPatch, setSelectShadeColorPatch] = React.useState<number>(-1);
|
||||||
|
const [hexCode, setHexCode] = React.useState<string>('');
|
||||||
|
const numColumns = 6;
|
||||||
|
const numFullRows = 3;
|
||||||
|
|
||||||
|
const handleSelectBaseColorPatch = (baseColorPatchIndex: number) => {
|
||||||
|
const color = baseColorPatchIndex > -1 ? colorPatches[baseColorPatchIndex].baseColor : 'ut-gray';
|
||||||
|
const newHexCode = baseColorPatchIndex > -1 ? getThemeColorHexByName(color).slice(1) : '';
|
||||||
|
setHexCode(newHexCode);
|
||||||
|
setSelectedBaseColorPatch(baseColorPatchIndex);
|
||||||
|
setSelectShadeColorPatch(3);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectShadeColorPatch = (shadeColorPatchIndex: number) => {
|
||||||
|
const color = colorPatches[selectedBaseColorPatch].shades[shadeColorPatchIndex];
|
||||||
|
const newHexCode = getThemeColorHexByName(color).slice(1);
|
||||||
|
setHexCode(newHexCode);
|
||||||
|
setSelectShadeColorPatch(shadeColorPatchIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const hexCodeWithHash = `#${hexCode}`;
|
||||||
|
if (hexCodeToBaseColorPatchIndex.has(hexCodeWithHash)) {
|
||||||
|
setSelectedBaseColorPatch(hexCodeToBaseColorPatchIndex.get(hexCodeWithHash));
|
||||||
|
}
|
||||||
|
if (hexCodeToShadeColorPatchIndex.has(hexCodeWithHash)) {
|
||||||
|
setSelectedBaseColorPatch(hexCodeToShadeColorPatchIndex.get(hexCodeWithHash));
|
||||||
|
}
|
||||||
|
if (!hexCodeToBaseColorPatchIndex.has(hexCodeWithHash) && !hexCodeToShadeColorPatchIndex.has(hexCodeWithHash)) {
|
||||||
|
setSelectedBaseColorPatch(-1);
|
||||||
|
}
|
||||||
|
}, [hexCode]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
let finalColor: string | null = null;
|
||||||
|
if (selectedBaseColorPatch === -1 && hexCode.length === 6) {
|
||||||
|
finalColor = `#${hexCode}`;
|
||||||
|
} else if (selectedBaseColorPatch > -1 && selectedShadeColorPatch === -1) {
|
||||||
|
finalColor = getThemeColorHexByName(colorPatches[selectedBaseColorPatch].baseColor);
|
||||||
|
} else if (selectedBaseColorPatch > -1 && selectedShadeColorPatch > -1) {
|
||||||
|
finalColor = getThemeColorHexByName(colorPatches[selectedBaseColorPatch].shades[selectedShadeColorPatch]);
|
||||||
|
} else {
|
||||||
|
finalColor = null;
|
||||||
|
}
|
||||||
|
console.log('finalColor', finalColor);
|
||||||
|
setFinalColor(finalColor);
|
||||||
|
}, [hexCode, selectedBaseColorPatch, selectedShadeColorPatch, setFinalColor]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='inline-flex flex-col border border-1 border-ut-offwhite rounded-1 p-[5px]'>
|
||||||
|
{Array.from({ length: numFullRows }, (_, rowIndex) => (
|
||||||
|
<div className='flex gap-0 flex-content-between' key={rowIndex}>
|
||||||
|
{colorPatches.map((color: Color, index) => {
|
||||||
|
if (index >= rowIndex * numColumns && index < (rowIndex + 1) * numColumns) {
|
||||||
|
return (
|
||||||
|
<DivWrapper key={color.baseColor}>
|
||||||
|
<ColorPatch
|
||||||
|
color={color.baseColor}
|
||||||
|
index={index}
|
||||||
|
selectedColor={selectedBaseColorPatch}
|
||||||
|
handleSetSelectedColorPatch={handleSelectBaseColorPatch}
|
||||||
|
/>
|
||||||
|
</DivWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className='flex gap-0 flex-content-between'>
|
||||||
|
<DivWrapper>
|
||||||
|
<ColorPatch
|
||||||
|
color={colorPatches[colorPatches.length - 2].baseColor}
|
||||||
|
index={colorPatches.length - 2}
|
||||||
|
selectedColor={selectedBaseColorPatch}
|
||||||
|
handleSetSelectedColorPatch={handleSelectBaseColorPatch}
|
||||||
|
/>
|
||||||
|
</DivWrapper>
|
||||||
|
<DivWrapper>
|
||||||
|
<ColorPatch
|
||||||
|
color={colorPatches[colorPatches.length - 1].baseColor}
|
||||||
|
index={colorPatches.length - 1}
|
||||||
|
selectedColor={selectedBaseColorPatch}
|
||||||
|
handleSetSelectedColorPatch={handleSelectBaseColorPatch}
|
||||||
|
/>
|
||||||
|
</DivWrapper>
|
||||||
|
<div className='flex items-center justify-center overflow-hidden p-[2px]'>
|
||||||
|
<HexColorEditor hexCode={hexCode} setHexCode={setHexCode} />
|
||||||
|
</div>
|
||||||
|
<DivWrapper>
|
||||||
|
{/* TODO (achadaga): Not really sure what this button is actually supposed to do */}
|
||||||
|
<Button className='h-[22px] w-[22px] p-0' variant='filled' color='ut-black' onClick={() => {}}>
|
||||||
|
<InvertColorsOffIcon className='h-[14px] w-[14px]' />
|
||||||
|
</Button>
|
||||||
|
</DivWrapper>
|
||||||
|
</div>
|
||||||
|
<Divider orientation='horizontal' size='100%' className='my-1' />
|
||||||
|
{selectedBaseColorPatch !== -1 && (
|
||||||
|
<HuePicker
|
||||||
|
shades={colorPatches[selectedBaseColorPatch].shades}
|
||||||
|
selectedColor={selectedShadeColorPatch}
|
||||||
|
setSelectedColor={handleSelectShadeColorPatch}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CourseCellColorPicker;
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the DivWrapper component
|
||||||
|
*/
|
||||||
|
interface ItemWrapperProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility component to space all the color patches in the color picker component
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {ItemWrapperProps} props - the props for the component
|
||||||
|
* @param {React.ReactNode} props.children - the children to be wrapped in the div
|
||||||
|
* @returns {JSX.Element} - the div wrapper component
|
||||||
|
*/
|
||||||
|
const DivWrapper: React.FC<ItemWrapperProps> = ({ children }: ItemWrapperProps) => (
|
||||||
|
<div className='h-[26px] w-[26px] flex items-center justify-center p-[2px]'>{children}</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default DivWrapper;
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { getThemeColorHexByName } from 'src/shared/util/themeColors';
|
||||||
|
|
||||||
|
import TagIcon from '~icons/material-symbols/tag';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the HexColorEditor component
|
||||||
|
*/
|
||||||
|
export interface HexColorEditorProps {
|
||||||
|
hexCode: string;
|
||||||
|
setHexCode: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility component to allow the user to enter a valid hex color code
|
||||||
|
*
|
||||||
|
* @param {HexColorEditorProps} props - the props for the component
|
||||||
|
* @param {string} props.hexCode - the current hex color code displayed in this component. Note that this code does not
|
||||||
|
* include the leading '#' character since it is already included in the component. Passed down from the parent component.
|
||||||
|
* @param {React.Dispatch<React.SetStateAction<string>>} props.setHexCode - set state fn to control the hex color code from parent
|
||||||
|
* @returns {JSX.Element} - the hex color editor component
|
||||||
|
*/
|
||||||
|
const HexColorEditor: React.FC<HexColorEditorProps> = ({ hexCode, setHexCode }: HexColorEditorProps): JSX.Element => {
|
||||||
|
const baseColor = React.useMemo(() => getThemeColorHexByName('ut-gray'), []);
|
||||||
|
const previewColor = hexCode.length === 6 ? `#${hexCode}` : baseColor;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='h-[22px] w-[74px] flex items-center border-[0.5px] border-ut-gray/50 rounded-1'>
|
||||||
|
<div
|
||||||
|
style={{ backgroundColor: previewColor }}
|
||||||
|
className='h-[22px] w-[21px] flex items-center justify-center rounded-l-1 -m-[0.5px]'
|
||||||
|
>
|
||||||
|
<TagIcon className='h-[16px] w-[16px] text-ut-white' />
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-1 items-center justify-center p-[5px]'>
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
maxLength={6}
|
||||||
|
className='box-border w-full border-none bg-transparent font-size-[11px] font-400 font-normal outline-none focus:outline-none'
|
||||||
|
value={hexCode}
|
||||||
|
onChange={e => setHexCode(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HexColorEditor;
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import type { ThemeColor } from 'src/shared/util/themeColors';
|
||||||
|
|
||||||
|
import ColorPatch from './ColorPatch';
|
||||||
|
import DivWrapper from './DivWrapper';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the HuePicker component
|
||||||
|
*/
|
||||||
|
interface HuePickerProps {
|
||||||
|
shades: ThemeColor[];
|
||||||
|
selectedColor: number;
|
||||||
|
setSelectedColor: React.Dispatch<React.SetStateAction<number>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bottom row of the color picker component that displays all the shades of a base color
|
||||||
|
*
|
||||||
|
* @param {HuePickerProps} props - the props for the component
|
||||||
|
* @param {ThemeColor[]} props.shades - the list of shades of the base color
|
||||||
|
* @param {number} props.selectedColor - the index of the selected color patch in the parent color palette
|
||||||
|
* @param {React.Dispatch<React.SetStateAction<number>>} props.setSelectedColor - set state fn to control the selected color patch from parent
|
||||||
|
* @returns {JSX.Element} - the hue picker component
|
||||||
|
*/
|
||||||
|
const HuePicker: React.FC<HuePickerProps> = ({
|
||||||
|
shades,
|
||||||
|
selectedColor,
|
||||||
|
setSelectedColor,
|
||||||
|
}: HuePickerProps): JSX.Element => {
|
||||||
|
const numColumns = 6;
|
||||||
|
return (
|
||||||
|
<div className='flex gap-0 flex-content-between'>
|
||||||
|
{Array.from({ length: numColumns }, (_, index) => (
|
||||||
|
<DivWrapper key={shades[index]}>
|
||||||
|
<ColorPatch
|
||||||
|
color={shades[index]}
|
||||||
|
index={index}
|
||||||
|
selectedColor={selectedColor}
|
||||||
|
handleSetSelectedColorPatch={setSelectedColor}
|
||||||
|
/>
|
||||||
|
</DivWrapper>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HuePicker;
|
||||||
Reference in New Issue
Block a user