revert: color palette for calendar (#118)
This commit is contained in:
@@ -1,20 +0,0 @@
|
||||
import type { ThemeColor } from '@shared/util/themeColors';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import CourseCellColorPicker from '@views/components/common/CourseCellColorPicker/CourseCellColorPicker';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
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: () => {
|
||||
// TODO
|
||||
const [selectedColor, setSelectedColor] = useState<ThemeColor | null>(null);
|
||||
return <CourseCellColorPicker setSelectedColor={setSelectedColor} />;
|
||||
},
|
||||
};
|
||||
@@ -1,48 +0,0 @@
|
||||
import type { ThemeColor } from '@shared/util/themeColors';
|
||||
import { Button } from '@views/components/common/Button/Button';
|
||||
import React from 'react';
|
||||
|
||||
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
|
||||
*/
|
||||
export default function ColorPatch({
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -1,392 +0,0 @@
|
||||
import type { ThemeColor } from '@shared/util/themeColors';
|
||||
import { getThemeColorHexByName } from '@shared/util/themeColors';
|
||||
import { Button } from '@views/components/common/Button/Button';
|
||||
import React from 'react';
|
||||
|
||||
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[];
|
||||
}
|
||||
|
||||
// TODO: Replace with UnoCSS theme
|
||||
// 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.
|
||||
*/
|
||||
export default function CourseCellColorPicker({
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
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
|
||||
*/
|
||||
export default function DivWrapper({ children }: ItemWrapperProps): JSX.Element {
|
||||
return <div className='h-[26px] w-[26px] flex items-center justify-center p-[2px]'>{children}</div>;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import { getThemeColorHexByName } from '@shared/util/themeColors';
|
||||
import React from 'react';
|
||||
|
||||
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
|
||||
*/
|
||||
export default function HexColorEditor({ 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>
|
||||
);
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import type { ThemeColor } from '@shared/util/themeColors';
|
||||
import React from 'react';
|
||||
|
||||
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
|
||||
*/
|
||||
export default function HuePicker({ 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user