From c2328e461ea0a846f399a22bc525540da2796dcd Mon Sep 17 00:00:00 2001 From: Ethan Lanting Date: Wed, 29 Jan 2025 20:32:06 -0600 Subject: [PATCH] feat(ui): color picker final touches (#491) * fix: update CourseCellColorPicker.tsx background to white * feat: add color picker to CalendarCourseCell component * feat: add color picker functionality to update course colors * fix: type issues with storybook components * feat: add useColorPicker hook, isValidHexColor and updateCourseColors utilities * refactor: color picker logic and UI components * refactor: update useFlattenedCourseSchedule hook to include courseID property * refactor: update storybook calendar components with updated props * refactor: update color picker ui logic to account for position of cell * fix: revert back to error handling for invalid rgb * refactor: update jsdocs * refactor: integrate ColorPickerContext into Calendar components and update props * refactor: integrate ColorPickerContext into Calendar components and update related props * refactor: change JSDocs comments and remove unused color inversion state * refactor: update story components * feat: add functionality for selecting secondary course colors * refactor: enhance HexColorEditor to dynamically adjust tag icon color based on preview color * refactor: simplify JSDoc comment in useColorPicker hook * fix: revert Button component * refactor: update CalendarCourseCell component positioning and styling * fix: correct types in color.ts * feat: add getDarkerShade function to compute darker shades of hex colors * feat: add shadow to color picker button * fix: update button size in ColorPatch component * feat: implement debounced input for hex color editor and add useDebounce hook * chore: utilize the logical and && operator instead of the ternary operator * fix: imports and palette icon * refactor: remove unused import * fix: bug when course add fails with custom colors * chore: run lint * chore: run check-types * feat: add HSL color type and conversion functions * refactor: rename colorway to theme * fix: hide color picker on screenshot * fix: undo important syntax * refactor: rename SomeFunction to DebouncedCallback * refactor: remove inner function * refactor: update return type to DebouncedCallback * fix: adjust sizes for hash and palette button * feat: create tests for hexToHSL and isValidHexColor * refactor: update parameter type to use HexColor * fix: increase size of palette button * fix: update dependency array for hex code debounce * fix: change colorPickerRef element ref * feat: add Roboto Mono font * fix: update input class to use monospace font * feat: add getLighterShade function * chore: run prettier and lint * feat: synchronize local hex code with hexCode prop changes --------- Co-authored-by: doprz <52579214+doprz@users.noreply.github.com> --- public/fonts/roboto-mono.woff2 | Bin 0 -> 10660 bytes src/shared/util/colors.ts | 27 ++++++++++++++ src/shared/util/tests/colors.test.ts | 33 +++++++++++++++++- .../HexColorEditor.tsx | 8 ++++- src/views/hooks/useSchedules.ts | 9 +++-- src/views/styles/fonts.module.scss | 8 +++++ unocss.config.ts | 1 + 7 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 public/fonts/roboto-mono.woff2 diff --git a/public/fonts/roboto-mono.woff2 b/public/fonts/roboto-mono.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..07cf89e033c8833c96b2ccfa725605d3f3e62a1c GIT binary patch literal 10660 zcmV;VDO=WePew8T0RR9104byZ3;+NC07-}d04Y)c0suh(00000000000000000000 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

>o2LxtTf)$55ArZ$nIgtrJ7&?aVyHU)6{ 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 Math.round(c).toString(16).padStart(2, '0')).join('')}`; } +/** + * Returns a lighter shade of the given hex color by increasing the lightness in HSL color space. + * + * @param color - The hexadecimal color value to lighten. + * @param offset - The percentage to increase the lightness by (default is 20). + * @returns The lighter shade of the given hex color. + * @throws If the provided color is not a valid hex color. + */ +export function getLighterShade(color: HexColor, offset: number = 20): HexColor { + const rgb = hexToRGB(color); + if (!rgb) { + throw new Error('color: Invalid hex.'); + } + + // Convert to HSL + const [h, s, l] = hexToHSL(color); + + // Increase lightness by offset percentage, ensuring it doesn't go above 100 + const newL = Math.min(100, l + offset); + + // Convert back to RGB + const newRGB = hslToRGB([h, s, newL]); + + // Convert to hex + return `#${newRGB.map(c => Math.round(c).toString(16).padStart(2, '0')).join('')}`; +} + /** * Get next unused color in a tailwind colorway for a given schedule * diff --git a/src/shared/util/tests/colors.test.ts b/src/shared/util/tests/colors.test.ts index 69f54976..6bd47f6a 100644 --- a/src/shared/util/tests/colors.test.ts +++ b/src/shared/util/tests/colors.test.ts @@ -1,4 +1,4 @@ -import { hexToHSL, isValidHexColor } from '@shared/util/colors'; +import { getLighterShade, hexToHSL, isValidHexColor } from '@shared/util/colors'; import { describe, expect, it } from 'vitest'; describe('hexToHSL', () => { @@ -79,3 +79,34 @@ describe('isValidHexColor', () => { expect(isValidHexColor('')).toBe(false); }); }); + +describe('getLighterShade', () => { + it('should lighten a color by default offset (20%)', () => { + const result = getLighterShade('#BF5700'); + expect(result).toBe('#ff8624'); + }); + + it('should lighten black correctly', () => { + const result = getLighterShade('#000000'); + expect(result).toBe('#333333'); + }); + + it('should not exceed 100% lightness', () => { + const result = getLighterShade('#FFFFFF', 20); + expect(result).toBe('#ffffff'); + }); + + it('should handle custom offset values', () => { + const result = getLighterShade('#BF5700', 40); + expect(result).toBe('#ffbe8a'); + }); + + it('should maintain hue while increasing lightness', () => { + const result = getLighterShade('#00FF00', 20); // Pure green + expect(result.toLowerCase()).toBe('#66ff66'); + }); + + it('should throw error for invalid hex color', () => { + expect(() => getLighterShade('#GGGGGG')).toThrow('color: Invalid hex.'); + }); +}); diff --git a/src/views/components/calendar/CalendarCourseCellColorPicker/HexColorEditor.tsx b/src/views/components/calendar/CalendarCourseCellColorPicker/HexColorEditor.tsx index 64a06393..e941746a 100644 --- a/src/views/components/calendar/CalendarCourseCellColorPicker/HexColorEditor.tsx +++ b/src/views/components/calendar/CalendarCourseCellColorPicker/HexColorEditor.tsx @@ -29,6 +29,12 @@ export default function HexColorEditor({ hexCode, setHexCode }: HexColorEditorPr const [localHexCode, setLocalHexCode] = React.useState(hexCode); const debouncedSetHexCode = useDebounce((value: string) => setHexCode(value), 500); + React.useEffect(() => { + if (hexCode !== localHexCode) { + setLocalHexCode(hexCode); + } + }, [hexCode]); + React.useEffect(() => { debouncedSetHexCode(localHexCode); @@ -48,7 +54,7 @@ export default function HexColorEditor({ hexCode, setHexCode }: HexColorEditorPr setLocalHexCode(e.target.value)} /> diff --git a/src/views/hooks/useSchedules.ts b/src/views/hooks/useSchedules.ts index 2d9937f0..08293dcd 100644 --- a/src/views/hooks/useSchedules.ts +++ b/src/views/hooks/useSchedules.ts @@ -1,7 +1,7 @@ import { UserScheduleStore } from '@shared/storage/UserScheduleStore'; import type { HexColor } from '@shared/types/Color'; import { UserSchedule } from '@shared/types/UserSchedule'; -import { getColorwayFromColor, getCourseColors, getDarkerShade } from '@shared/util/colors'; +import { getColorwayFromColor, getCourseColors, getDarkerShade, getLighterShade } from '@shared/util/colors'; import { useEffect, useState } from 'react'; let schedulesCache: UserSchedule[] = []; @@ -150,7 +150,12 @@ export async function updateCourseColors(courseID: number, primaryColor: HexColo secondaryColor = colorFromWay; } catch (e) { - secondaryColor = getDarkerShade(primaryColor, 80); + secondaryColor = getDarkerShade(primaryColor, 20); + + // if primaryColor is too dark, get lighter shade instead + if (secondaryColor === '#000000') { + secondaryColor = getLighterShade(primaryColor, 35); + } } updatedCourse.colors.primaryColor = primaryColor; diff --git a/src/views/styles/fonts.module.scss b/src/views/styles/fonts.module.scss index 0c0f140f..c1724300 100644 --- a/src/views/styles/fonts.module.scss +++ b/src/views/styles/fonts.module.scss @@ -15,7 +15,15 @@ font-style: normal; } +@font-face { + font-family: 'Roboto Mono Local'; + src: url('@public/fonts/roboto-mono.woff2') format('woff2'); + font-display: swap; + font-style: normal; +} + @import url('https://fonts.googleapis.com/css2?family=Roboto+Flex:wght@100..1000&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono&display=swap'); $medium_size: 16px; diff --git a/unocss.config.ts b/unocss.config.ts index 5893b0c0..073cf74e 100644 --- a/unocss.config.ts +++ b/unocss.config.ts @@ -55,6 +55,7 @@ export default defineConfig({ provider: 'none', fonts: { sans: ['Roboto Flex', 'Roboto Flex Local'], + mono: ['Roboto Mono', 'Roboto Mono Local'], }, }), ],