-
Notifications
You must be signed in to change notification settings - Fork 63
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
base: main
Are you sure you want to change the base?
Conversation
…stration-Plus into feature/color-picker
There was a problem hiding this 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:
- Async/Other courses should also have the color picker button
- The color picker button should be closer to the top right corner of the course block and, along with the color palette, should be slightly bigger. See the video below showing this PR vs the Figma component (Figma shows palette and color picker button as bigger):
https://github.com/user-attachments/assets/b2f13887-afdf-4c40-ae67-57c0542df7cb - Color picker button should have a shadow like other buttons.
- Ignore the Invert color button. That can be hidden for now. (I kind of want to make it a gear button that takes folks to the settings page to customize how colors work)
- There is a bug where courses glitch out of the grid when opening the color palette, and is fixed upon refresh. See video:
https://github.com/user-attachments/assets/e7ae7ea2-bc63-4812-9838-2f35869d3c1e
There was a problem hiding this 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/components/calendar/CalendarCourseCellColorPicker/HexColorEditor.tsx
Outdated
Show resolved
Hide resolved
…pdate related props
… based on preview color
This is now fixed with #430 |
/** | ||
* 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')}`; | ||
} | ||
|
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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.
/** | ||
* 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 |
There was a problem hiding this comment.
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} |
There was a problem hiding this comment.
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-
/** | ||
* 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. | ||
*/ |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
/** | ||
* 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; | ||
} |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
Also please resolve merge conflicts. |
'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', |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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': |
There was a problem hiding this comment.
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
/** | ||
* 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; | ||
} |
There was a problem hiding this comment.
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
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; | ||
} | ||
}; |
There was a problem hiding this comment.
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?
There was a problem hiding this 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.
Resolves #352
This change is