feat: calendar-course-cell-color-picker (#157)
* feat: calendar-course-cell-color-picker done?? * fix: ensure hex code is lowercase * fix: make hex codes lower case * chore: convert px to rem in ColorPatch.tsx * fix: add functionality to the invert colors button * fix: some more lowercase stuff * fix: remove hardcoded color patch hex codes, remove hardcoded pixel values * chore: remove React.FC * chore: modify docs * fix: remove duplicate style * fix: used name over size specified classes * fix: grid over flex, elie feedback * refactor: use color strings instead of indices * refactor: remove console.log statements
This commit is contained in:
@@ -12,7 +12,6 @@ export const colors = {
|
||||
offwhite: '#D6D2C4',
|
||||
concrete: '#95A5A6',
|
||||
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: {
|
||||
red: '#AF2E2D',
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { ThemeColor } from '@shared/util/themeColors';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import CourseCellColorPicker from '@views/components/calendar/CalendarCourseCellColorPicker/CourseCellColorPicker';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/Calendar/CourseCellColorPicker',
|
||||
component: CourseCellColorPicker,
|
||||
} satisfies Meta<typeof CourseCellColorPicker>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof CourseCellColorPicker>;
|
||||
|
||||
function CourseCellColorPickerWithState() {
|
||||
const [, setSelectedColor] = useState<ThemeColor | null>(null);
|
||||
const [isInvertColorsToggled, setIsInvertColorsToggled] = useState<boolean>(false);
|
||||
return (
|
||||
<CourseCellColorPicker
|
||||
setSelectedColor={setSelectedColor}
|
||||
isInvertColorsToggled={isInvertColorsToggled}
|
||||
setIsInvertColorsToggled={setIsInvertColorsToggled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const Default: Story = {
|
||||
render: CourseCellColorPickerWithState,
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
import { getThemeColorHexByName } from '@shared/util/themeColors';
|
||||
import React from 'react';
|
||||
|
||||
import CheckIcon from '~icons/material-symbols/check';
|
||||
|
||||
/**
|
||||
* Props for the ColorPatch component
|
||||
*/
|
||||
interface ColorPatchProps {
|
||||
color: string;
|
||||
isSelected: boolean;
|
||||
handleSetSelectedColor: (color: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a color patch square used in the CalendarCourseCellColorPicker component.
|
||||
*
|
||||
* @param {Object} props - The component props.
|
||||
* @param {string} props.color - The color value (as a hex string with a hash prefix) to display in the patch.
|
||||
* @param {boolean} props.isSelected - Indicates whether the patch is selected.
|
||||
* @param {Function} props.handleSetSelectedColor - Function from parent component to control selection state of a patch.
|
||||
* color is a hex string with a hash prefix.
|
||||
* @returns {JSX.Element} The rendered color patch button.
|
||||
*/
|
||||
export default function ColorPatch({ color, isSelected, handleSetSelectedColor }: ColorPatchProps): JSX.Element {
|
||||
const handleClick = () => {
|
||||
handleSetSelectedColor(isSelected ? getThemeColorHexByName('ut-gray') : color);
|
||||
};
|
||||
return (
|
||||
<button
|
||||
className='h-5.5 w-5.5 p-0 transition-all duration-200 hover:scale-110 btn'
|
||||
style={{ backgroundColor: color }}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{isSelected && <CheckIcon className='h-5 w-5 color-white' />}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
import { getThemeColorHexByName } from '@shared/util/themeColors';
|
||||
import Divider from '@views/components/common/Divider/Divider';
|
||||
import React from 'react';
|
||||
import { theme } from 'unocss/preset-mini';
|
||||
|
||||
import InvertColorsIcon from '~icons/material-symbols/invert-colors';
|
||||
import InvertColorsOffIcon from '~icons/material-symbols/invert-colors-off';
|
||||
|
||||
import ColorPatch from './ColorPatch';
|
||||
import HexColorEditor from './HexColorEditor';
|
||||
|
||||
const baseColors = [
|
||||
'slate',
|
||||
'gray',
|
||||
'stone',
|
||||
'red',
|
||||
'orange',
|
||||
'amber',
|
||||
'yellow',
|
||||
'lime',
|
||||
'green',
|
||||
'emerald',
|
||||
'teal',
|
||||
'cyan',
|
||||
'sky',
|
||||
'blue',
|
||||
'indigo',
|
||||
'violet',
|
||||
'purple',
|
||||
'fuchsia',
|
||||
'pink',
|
||||
'rose',
|
||||
];
|
||||
|
||||
const BaseColorNum = 500;
|
||||
const StartingShadeIndex = 200;
|
||||
const ShadeIncrement = 100;
|
||||
|
||||
const colorPatchColors = new Map<string, string[]>(
|
||||
baseColors.map((baseColor: string) => [
|
||||
theme.colors[baseColor][BaseColorNum],
|
||||
Array.from({ length: 6 }, (_, index) => theme.colors[baseColor][StartingShadeIndex + ShadeIncrement * index]),
|
||||
])
|
||||
);
|
||||
|
||||
const hexCodeToBaseColor = new Map<string, string>(
|
||||
Array.from(colorPatchColors.entries()).flatMap(([baseColor, shades]) => shades.map(shade => [shade, baseColor]))
|
||||
);
|
||||
|
||||
/**
|
||||
* Props for the CourseCellColorPicker component.
|
||||
*/
|
||||
export interface CourseCellColorPickerProps {
|
||||
setSelectedColor: React.Dispatch<React.SetStateAction<string | null>>;
|
||||
isInvertColorsToggled: boolean;
|
||||
setIsInvertColorsToggled: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
* @param {boolean} props.isInvertColorsToggled - boolean state passed down from the parent component that indicates whether the color picker is in invert colors mode
|
||||
* @param {React.Dispatch<React.SetStateAction<boolean>>} props.setIsInvertColorsToggled - set state function passed down from the parent component to set invert colors mode
|
||||
* that will be called when a color is selected. The user can set any valid hex color they want.
|
||||
*
|
||||
* @example
|
||||
* ```
|
||||
* const [selectedColor, setSelectedColor] = useState<string | null>(null);
|
||||
* const [isInvertColorsToggled, setIsInvertColorsToggled] = useState<boolean>(false);
|
||||
* return (
|
||||
* <CourseCellColorPicker
|
||||
* setSelectedColor={setSelectedColor}
|
||||
* isInvertColorsToggled={isInvertColorsToggled}
|
||||
* setIsInvertColorsToggled={setIsInvertColorsToggled}
|
||||
* />
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* @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,
|
||||
isInvertColorsToggled,
|
||||
setIsInvertColorsToggled,
|
||||
}: CourseCellColorPickerProps): JSX.Element {
|
||||
// hexCode mirrors contents of HexColorEditor which has no hash prefix
|
||||
const [hexCode, setHexCode] = React.useState<string>(
|
||||
getThemeColorHexByName('ut-gray').slice(1).toLocaleLowerCase()
|
||||
);
|
||||
const hexCodeWithHash = `#${hexCode}`;
|
||||
const selectedBaseColor = hexCodeToBaseColor.get(hexCodeWithHash);
|
||||
|
||||
const handleSelectColorPatch = (baseColor: string) => {
|
||||
setHexCode(baseColor.slice(1).toLocaleLowerCase());
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
setFinalColor(hexCodeWithHash);
|
||||
}, [hexCodeWithHash, setFinalColor]);
|
||||
|
||||
return (
|
||||
<div className='inline-flex flex-col border border-1 border-ut-offwhite rounded-1 p-1.25'>
|
||||
<div className='grid grid-cols-6 gap-1'>
|
||||
{Array.from(colorPatchColors.keys()).map(baseColor => (
|
||||
<ColorPatch
|
||||
color={baseColor}
|
||||
isSelected={baseColor === selectedBaseColor}
|
||||
handleSetSelectedColor={handleSelectColorPatch}
|
||||
/>
|
||||
))}
|
||||
<div className='col-span-3 flex items-center justify-center overflow-hidden'>
|
||||
<HexColorEditor hexCode={hexCode} setHexCode={setHexCode} />
|
||||
</div>
|
||||
<button
|
||||
className='h-5.5 w-5.5 bg-ut-black p-0 transition-all duration-200 hover:scale-110 btn'
|
||||
onClick={() => setIsInvertColorsToggled(prev => !prev)}
|
||||
>
|
||||
{isInvertColorsToggled ? (
|
||||
<InvertColorsIcon className='h-3.5 w-3.5 color-white' />
|
||||
) : (
|
||||
<InvertColorsOffIcon className='h-3.5 w-3.5 color-white' />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
{hexCodeToBaseColor.has(hexCodeWithHash) && (
|
||||
<>
|
||||
<Divider orientation='horizontal' size='100%' className='my-1' />
|
||||
<div className='grid grid-cols-6 gap-1'>
|
||||
{colorPatchColors
|
||||
.get(selectedBaseColor)
|
||||
?.map(shadeColor => (
|
||||
<ColorPatch
|
||||
color={shadeColor}
|
||||
isSelected={shadeColor === hexCodeWithHash}
|
||||
handleSetSelectedColor={handleSelectColorPatch}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
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
|
||||
style={{ backgroundColor: previewColor }}
|
||||
className='h-5.5 w-5.25 flex items-center justify-center rounded-l-1'
|
||||
>
|
||||
<TagIcon className='h-4 w-4 text-color-white' />
|
||||
</div>
|
||||
<div className='h-5.5 w-[53px] flex flex-1 items-center justify-center border-b border-r border-t rounded-br rounded-tr p-1.25'>
|
||||
<input
|
||||
type='text'
|
||||
maxLength={6}
|
||||
className='w-full border-none bg-transparent font-size-2.75 font-normal outline-none focus:outline-none'
|
||||
value={hexCode}
|
||||
onChange={e => setHexCode(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user