Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ui): course color picker #382

Open
wants to merge 31 commits into
base: main
Choose a base branch
from

Conversation

EthanL06
Copy link
Contributor

@EthanL06 EthanL06 commented Oct 23, 2024

Resolves #352


This change is Reviewable

@DereC4 DereC4 added the feature label Oct 23, 2024
@IsaDavRod IsaDavRod self-requested a review October 27, 2024 21:43
@EthanL06 EthanL06 marked this pull request as ready for review October 28, 2024 06:54
Copy link
Member

@IsaDavRod IsaDavRod left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here are some things I've noticed:

Copy link
Member

@Razboy20 Razboy20 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewing on mobile so I'll have to come back and take a look at this later, but overall looking good.

I think it would be best if we used headless ui paradigms for rendering the floating elements rather than dealing with them manually, and that also enables easier animation/transitions.

Also, preferably (although I know it already exists in the codebase) remove types from JSDoc annotations, as you're repeating information given elsewhere-that is, through typescript.

I recommend trying to follow https://tsdoc.org/

src/views/hooks/useSchedules.ts Outdated Show resolved Hide resolved
src/shared/util/colors.ts Outdated Show resolved Hide resolved
src/shared/util/colors.ts Outdated Show resolved Hide resolved
src/views/components/calendar/CalendarCourseCell.tsx Outdated Show resolved Hide resolved
src/views/components/calendar/CalendarCourseCell.tsx Outdated Show resolved Hide resolved
@doprz
Copy link
Collaborator

doprz commented Nov 17, 2024

Reviewing on mobile so I'll have to come back and take a look at this later, but overall looking good.

I think it would be best if we used headless ui paradigms for rendering the floating elements rather than dealing with them manually, and that also enables easier animation/transitions.

Also, preferably (although I know it already exists in the codebase) remove types from JSDoc annotations, as you're repeating information given elsewhere-that is, through typescript.

I recommend trying to follow https://tsdoc.org/

This is now fixed with #430

Comment on lines +143 to +164
/**
* Returns a darker shade of the given hex color by reducing the RGB values by the specified offset.
*
* @param color - The hexadecimal color value to darken.
* @param offset - The amount to reduce each RGB component by (default is 20).
* @returns The darker shade of the given hex color.
* @throws If the provided color is not a valid hex color.
*/
export function getDarkerShade(color: HexColor, offset: number = 20): HexColor {
const rgb = hexToRGB(color);
if (!rgb) {
throw new Error('color: Invalid hex.');
}

const [r, g, b] = rgb.map(c => Math.max(0, c - offset));
if (r === undefined || g === undefined || b === undefined) {
throw new Error('RGB values are undefined');
}

return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally when you want to get a darker shade of something, HSL values are much nicer to work with. I would use HSL values for this.

@@ -145,15 +192,15 @@ export function getUnusedColor(
...c,
colorway: getColorwayFromColor(c.colors.primaryColor),
}));
const usedColorways = new Set(scheduleCourses.map(c => c.colorway));
const usedColorways = new Set(scheduleCourses.map(c => c.colorway.colorway));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

c.colorway.colorway could be a bit confusing, let's try renaming it a bit for code clarity.

Comment on lines 17 to 24
/**
* 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
* @param props - the props for the component
* @param 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
* @param props.setHexCode - set state fn to control the hex color code from parent
* @returns the hex color editor component
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please pull changes from main to include changes from #430 and refactor the jsdoc for HexColorEditor using the tsdoc spec.

@@ -69,7 +70,9 @@ export default function CalendarGrid({
.map(() => (
<div className='h-4 flex items-end justify-center border-r border-gray-300' />
))}
{courseCells ? <AccountForCourseConflicts courseCells={courseCells} setCourse={setCourse} /> : null}
<ColorPickerProvider>
{courseCells ? <AccountForCourseConflicts courseCells={courseCells} setCourse={setCourse} /> : null}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

null isn't needed here. Refactor to conditionally render HexColorEditor if courseCells is true. Utilize the logical and && operator instead of the ternary operator.

https://react.dev/learn/conditional-rendering#logical-and-operator-

src/views/components/calendar/CalendarGrid.tsx Outdated Show resolved Hide resolved
Comment on lines +21 to +38
/**
* Provides the color picker context to its children.
*
* @param props - The properties for the ColorPickerProvider component.
* @param props.children - The child components that will have access to the color picker context.
* @returns The provider component that supplies the color picker context to its children.
*/
export const ColorPickerProvider = ({ children }: ColorPickerProviderProps) => {
const colorPicker = useColorPicker();

return <ColorPickerContext.Provider value={colorPicker}>{children}</ColorPickerContext.Provider>;
};

/**
* Custom hook to use the ColorPicker context.
* Throws an error if used outside of a ColorPickerProvider.
* @returns {ColorPickerInterface} The color picker context value.
*/
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactor jsdoc to use tsdoc spec. Refer to previous comment.

import { useCallback, useEffect, useRef } from 'react';

type Timer = ReturnType<typeof setTimeout>;
type SomeFunction<T extends unknown[]> = (...args: T) => void;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit. Not the best naming for this.

Comment on lines +6 to +38
/**
* Custom hook to debounce a function call.
*
* @param func - The original, non-debounced function.
* @param delay - The delay (in ms) for the function to return.
* @returns The debounced function, which will run only if the debounced function has not been called in the last (delay) ms.
*/
export function useDebounce<T extends unknown[]>(func: SomeFunction<T>, delay: number = 1000): SomeFunction<T> {
const timer = useRef<Timer>();

useEffect(
() => () => {
if (timer.current) {
clearTimeout(timer.current);
}
},
[]
);

const debouncedFunction = useCallback(
(...args: T) => {
if (timer.current) {
clearTimeout(timer.current);
}
timer.current = setTimeout(() => {
func(...args);
}, delay);
},
[func, delay]
);

return debouncedFunction;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good. We can also use lodash for this https://www.npmjs.com/package/lodash

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we really want to bring in lodash? last commit 3 years ago, last publish on NPM is 4 years ago

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with either. Just worth mentioning.

@doprz
Copy link
Collaborator

doprz commented Nov 18, 2024

Also please resolve merge conflicts.

Comment on lines +166 to +167
'absolute text-black transition-all ease-in-out',
'group-focus-within:pointer-events-auto group-hover:pointer-events-auto group-focus-within:opacity-100 group-hover:opacity-100 gap-y-0.75',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are these two different strings? just line lengths?

onClick={e => {
e.stopPropagation();
}}
className={clsx(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the color picker (and related) should have screenshot:opacity-0! so that it doesn't show up in screenshot if it's opened

} satisfies React.CSSProperties
}
className={clsx(
'btn',
{
'text-white! bg-opacity-100 hover:enabled:shadow-md active:enabled:shadow-sm shadow-black/20':
'!text-white bg-opacity-100 hover:enabled:shadow-md active:enabled:shadow-sm shadow-black/20':
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this line doesn't need to be changed. in the majority of the codebase, we use the ! (the !important CSS modifier) after the name of a specific class. officially, tailwind puts it at the front, but UnoCSS (which we use) supports both

Comment on lines +6 to +38
/**
* Custom hook to debounce a function call.
*
* @param func - The original, non-debounced function.
* @param delay - The delay (in ms) for the function to return.
* @returns The debounced function, which will run only if the debounced function has not been called in the last (delay) ms.
*/
export function useDebounce<T extends unknown[]>(func: SomeFunction<T>, delay: number = 1000): SomeFunction<T> {
const timer = useRef<Timer>();

useEffect(
() => () => {
if (timer.current) {
clearTimeout(timer.current);
}
},
[]
);

const debouncedFunction = useCallback(
(...args: T) => {
if (timer.current) {
clearTimeout(timer.current);
}
timer.current = setTimeout(() => {
func(...args);
}, delay);
},
[func, delay]
);

return debouncedFunction;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we really want to bring in lodash? last commit 3 years ago, last publish on NPM is 4 years ago

Comment on lines +142 to +156
const determineSecondaryColor = (color: HexColor): HexColor => {
try {
const { colorway: primaryColorWay, index: primaryIndex } = getColorwayFromColor(color);
const { secondaryColor } = getCourseColors(primaryColorWay, primaryIndex, 400);

if (!secondaryColor) {
throw new Error('Secondary color not found');
}

return secondaryColor;
} catch (e) {
const secondaryColor = getDarkerShade(color, 80);
return secondaryColor;
}
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this function defined inside of another function? should this function be in its own file, moved into a utils file related to colors, or be part of this file?

Copy link
Member

@IsaDavRod IsaDavRod left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as a note taken from Discord convo: This PR does not let me add a course to schedules where at least one course block is a custom color.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement color picker button. Color picker button shows when hovering over course block
6 participants