-
Notifications
You must be signed in to change notification settings - Fork 3.6k
[WEB-3540] dev: color picker component #6823
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
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
9782495
dev: color picker component added
anmolsinghbhatia ff9431d
chore: helper function added
anmolsinghbhatia 28f993e
chore: code refactor
anmolsinghbhatia ea9c114
chore: code refactor
anmolsinghbhatia 23d82c6
Merge branch 'preview' of github.com:makeplane/plane into dev-color-p…
anmolsinghbhatia 4752fb0
Merge branch 'preview' of github.com:makeplane/plane into dev-color-p…
anmolsinghbhatia 8ba6caf
Merge branch 'preview' of github.com:makeplane/plane into dev-color-p…
anmolsinghbhatia 88e7c67
chore: code refactor
anmolsinghbhatia d317f9e
chore: code refactor
anmolsinghbhatia File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| import * as React from "react"; | ||
|
|
||
| interface ColorPickerProps { | ||
| value: string; | ||
| onChange: (color: string) => void; | ||
| className?: string; | ||
| } | ||
|
|
||
| export const ColorPicker: React.FC<ColorPickerProps> = (props) => { | ||
| const { value, onChange, className = "" } = props; | ||
| // refs | ||
| const inputRef = React.useRef<HTMLInputElement>(null); | ||
|
|
||
| // handlers | ||
| const handleOnClick = () => { | ||
| inputRef.current?.click(); | ||
| }; | ||
|
|
||
| return ( | ||
| <div className="flex items-center justify-center relative"> | ||
| <button | ||
| className={`size-4 rounded-full cursor-pointer conical-gradient ${className}`} | ||
| onClick={handleOnClick} | ||
| aria-label="Open color picker" | ||
| /> | ||
| <input | ||
| ref={inputRef} | ||
| type="color" | ||
| value={value} | ||
| onChange={(e) => onChange(e.target.value)} | ||
| className="absolute inset-0 size-4 invisible" | ||
| aria-hidden="true" | ||
| /> | ||
| </div> | ||
| ); | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export * from "./color-picker"; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| import chroma from "chroma-js"; | ||
|
|
||
| interface HSLColor { | ||
| h: number; // hue (0-360) | ||
| s: number; // saturation (0-100) | ||
| l: number; // lightness (0-100) | ||
| } | ||
|
|
||
| interface ColorAdjustmentOptions { | ||
| targetContrast?: number; // Minimum contrast ratio (4.5 for WCAG AAA, 3 for WCAG AA) | ||
| preserveHue?: boolean; // Whether to maintain the original hue | ||
| maxTries?: number; // Maximum attempts to find accessible colors | ||
| } | ||
|
|
||
| // Helper function to ensure color contrast compliance | ||
| const ensureAccessibleColors = ( | ||
| foreground: string, | ||
| background: string, | ||
| options: ColorAdjustmentOptions = {} | ||
| ): { foreground: string; background: string } => { | ||
| const { | ||
| targetContrast = 4.5, // WCAG AAA by default | ||
| preserveHue = true, | ||
| maxTries = 10, | ||
| } = options; | ||
|
|
||
| try { | ||
| const fg = chroma(foreground); | ||
| const bg = chroma(background); | ||
| let contrast = chroma.contrast(fg, bg); | ||
|
|
||
| // If contrast is already good, return original colors | ||
| if (contrast >= targetContrast) { | ||
| return { foreground, background }; | ||
| } | ||
|
|
||
| // Adjust colors to meet contrast requirements | ||
| let adjustedFg = fg; | ||
| let adjustedBg = bg; | ||
| let tries = 0; | ||
|
|
||
| while (contrast < targetContrast && tries < maxTries) { | ||
| if (fg.luminance() > bg.luminance()) { | ||
| // Make foreground lighter and background darker | ||
| adjustedFg = preserveHue ? fg.luminance(Math.min(fg.luminance() + 0.1, 0.9)) : fg.brighten(0.5); | ||
| adjustedBg = preserveHue ? bg.luminance(Math.max(bg.luminance() - 0.1, 0.1)) : bg.darken(0.5); | ||
| } else { | ||
| // Make foreground darker and background lighter | ||
| adjustedFg = preserveHue ? fg.luminance(Math.max(fg.luminance() - 0.1, 0.1)) : fg.darken(0.5); | ||
| adjustedBg = preserveHue ? bg.luminance(Math.min(bg.luminance() + 0.1, 0.9)) : bg.brighten(0.5); | ||
| } | ||
|
|
||
| contrast = chroma.contrast(adjustedFg, adjustedBg); | ||
| tries++; | ||
| } | ||
|
|
||
| return { | ||
| foreground: adjustedFg.css(), | ||
| background: adjustedBg.css(), | ||
| }; | ||
| } catch (error) { | ||
| console.warn("Color adjustment failed:", error); | ||
| return { foreground, background }; | ||
| } | ||
| }; | ||
|
|
||
| // background color | ||
| export const createBackgroundColor = (hsl: HSLColor, resolvedTheme: "light" | "dark" = "light"): string => { | ||
| const baseColor = chroma.hsl(hsl.h, hsl.s / 100, hsl.l / 100); | ||
|
|
||
| // Set base opacity according to theme | ||
| const baseOpacity = resolvedTheme === "dark" ? 0.25 : 0.15; | ||
|
|
||
| // Create semi-transparent background | ||
| let backgroundColor = baseColor.alpha(baseOpacity); | ||
|
|
||
| if (hsl.l > 90) { | ||
| backgroundColor = baseColor.darken(1).alpha(resolvedTheme === "dark" ? 0.3 : 0.2); | ||
| } else if (hsl.l > 70) { | ||
| backgroundColor = baseColor.darken(0.5).alpha(resolvedTheme === "dark" ? 0.28 : 0.18); | ||
| } else if (hsl.l < 30) { | ||
| backgroundColor = baseColor.brighten(0.5).alpha(resolvedTheme === "dark" ? 0.22 : 0.12); | ||
| } | ||
|
|
||
| return backgroundColor.css(); | ||
| }; | ||
|
|
||
| // foreground color | ||
| export const getIconColor = (hsl: HSLColor): string => { | ||
| const baseColor = chroma.hsl(hsl.h, hsl.s / 100, hsl.l / 100); | ||
| const backgroundColor = createBackgroundColor(hsl); | ||
|
|
||
| // Adjust colors for accessibility | ||
| const { foreground } = ensureAccessibleColors(baseColor.css(), backgroundColor, { | ||
| targetContrast: 3, // WCAG AA for UI components | ||
| preserveHue: true, | ||
| }); | ||
|
|
||
| return foreground; | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.