Skip to content

[WEB-3540] dev: color picker component#6823

Merged
sriramveeraghanta merged 9 commits intopreviewfrom
dev-color-picker-component
Mar 27, 2025
Merged

[WEB-3540] dev: color picker component#6823
sriramveeraghanta merged 9 commits intopreviewfrom
dev-color-picker-component

Conversation

@anmolsinghbhatia
Copy link
Collaborator

@anmolsinghbhatia anmolsinghbhatia commented Mar 26, 2025

Description

This PR introduces a color picker component.

Type of Change

  • Global component

References

[WEB-3540]

Summary by CodeRabbit

  • New Features

    • Introduced a new, interactive color selection tool that makes it easy to pick and customize colors.
    • Enhanced visual accessibility by dynamically adjusting contrast for backgrounds and icons, ensuring better readability and a more accessible user interface.
    • Added a new component for color manipulation and accessibility features, including functions for ensuring accessible color contrasts.
  • Dependencies

    • Added new dependencies for color manipulation and TypeScript type definitions.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 26, 2025

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This pull request introduces a new UI component, ColorPicker, which enables color selection via a hidden HTML color input and a styled button. The component and its associated props are exported through a new module entry point. Additionally, the public API is expanded by re-exporting the color-picker module in the main UI index file. A new dependency, chroma-js, is added to support color manipulation. A helper file for theme-related color adjustments is also introduced, featuring functions to ensure accessible color contrast and generate theme-based colors.

Changes

File(s) Change Summary
packages/ui/src/color-picker/color-picker.tsx, packages/ui/src/color-picker/index.ts, packages/ui/src/index.ts Added new ColorPicker React component with props (value, onChange, className) and re-exported it for public API access.
web/package.json Added new dependencies: "@types/chroma-js": "^3.1.1" and "chroma-js": "^3.1.2" in the dependencies section.
web/helpers/theme.tsx Introduced functions and interfaces for color manipulation and accessibility: ensureAccessibleColors, createBackgroundColor, and getIconColor, leveraging chroma-js for contrast adjustments.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant CP as ColorPicker Component
    participant CI as Color Input (hidden)
    participant OC as onChange Callback

    U->>CP: Clicks styled button
    CP->>CI: Trigger click event (handleOnClick)
    CI->>U: Opens native color picker
    U->>CI: Selects new color
    CI->>CP: Triggers onChange event
    CP->>OC: Calls onChange callback with new color
Loading
sequenceDiagram
    participant UI as UI Code
    participant GI as getIconColor
    participant EA as ensureAccessibleColors
    participant Chroma as chroma-js

    UI->>GI: Request icon color calculation
    GI->>EA: Validate and adjust colors for accessibility
    EA->>Chroma: Compute color contrasts & adjustments
    Chroma-->>EA: Returns computed values
    EA-->>GI: Adjusted foreground/background colors
    GI-->>UI: Returns computed icon color
Loading

Suggested labels

🎨UI / UX, ✍️editor

Suggested reviewers

  • sriramveeraghanta
  • rahulramesha
  • SatishGandham

Poem

In fields of code where colors bloom,
I hop with glee beneath the moon.
A picker born to chase the hue,
With themes that shine both bright and true.
Carrots and code, together we zoom!
🥕🐇


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@makeplane
Copy link

makeplane bot commented Mar 26, 2025

Pull Request Linked with Plane Work Items

Comment Automatically Generated by Plane

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
packages/ui/src/color-picker/color-picker.tsx (3)

19-35: Consider showing visual feedback of the selected color

While the component functionality is correct, users might benefit from seeing the currently selected color. Consider adding a background color to the button based on the value prop.

<button
-  className={`size-4 rounded-full cursor-pointer conical-gradient ${className}`}
+  className={`size-4 rounded-full cursor-pointer ${className}`}
+  style={{ backgroundColor: value }}
  onClick={handleOnClick}
  aria-label="Open color picker"
/>

24-24: Enhance accessibility with keyboard support

Consider adding keyboard support to allow users to open the color picker using keyboard navigation.

<button
  className={`size-4 rounded-full cursor-pointer conical-gradient ${className}`}
  onClick={handleOnClick}
+  onKeyDown={(e) => {
+    if (e.key === 'Enter' || e.key === ' ') {
+      e.preventDefault();
+      handleOnClick();
+    }
+  }}
  aria-label="Open color picker"
/>

3-7: Add JSDoc comments to document the component props

Adding JSDoc comments would improve documentation for this component and help other developers understand its usage.

+/**
+ * ColorPicker component for selecting colors.
+ */
interface ColorPickerProps {
+  /** The current color value in hex format (e.g., "#FF0000") */
  value: string;
+  /** Callback function that is called when the color changes */
  onChange: (color: string) => void;
+  /** Optional className for additional styling */
  className?: string;
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 41447e5 and 9782495.

📒 Files selected for processing (3)
  • packages/ui/src/color-picker/color-picker.tsx (1 hunks)
  • packages/ui/src/color-picker/index.ts (1 hunks)
  • packages/ui/src/index.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Analyze (python)
🔇 Additional comments (3)
packages/ui/src/color-picker/color-picker.tsx (1)

22-22: Verify custom CSS classes

The size-4 and conical-gradient CSS classes should be verified to ensure they exist in the project's CSS framework. If conical-gradient is a custom class, ensure it's properly defined and documented.

packages/ui/src/color-picker/index.ts (1)

1-1: LGTM: Export pattern is correct

The barrel file pattern is correctly implemented, allowing for clean imports from the directory.

packages/ui/src/index.ts (1)

34-34: LGTM: Export is correctly added

The new export follows the existing pattern and correctly exposes the color-picker module.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
web/helpers/theme.ts (3)

1-5: Consider exporting the HSLColor interface.

The HSLColor interface is well-defined with appropriate comments, but it's not exported. If other components need to create or manipulate HSL colors directly, this interface should be exported to promote code reuse and type safety.

-interface HSLColor {
+export interface HSLColor {
  h: number; // hue (0-360)
  s: number; // saturation (0-100)
  l: number; // lightness (0-100)
}

7-21: Consider adding input validation for HSL values.

The createBackgroundColor function handles different lightness ranges appropriately, but it doesn't validate that input values are within expected ranges (h: 0-360, s: 0-100, l: 0-100). This could lead to unexpected results with invalid inputs.

export const createBackgroundColor = (hsl: HSLColor): string => {
+  // Ensure values are within valid ranges
+  const h = Math.max(0, Math.min(360, hsl.h));
+  const s = Math.max(0, Math.min(100, hsl.s));
+  const l = Math.max(0, Math.min(100, hsl.l));
+
-  if (hsl.l > 90) {
+  if (l > 90) {
    // Very light colors - increase opacity and darken slightly
-    return `hsla(${hsl.h}, ${hsl.s}%, ${Math.max(85, hsl.l - 10)}%, 0.25)`;
+    return `hsla(${h}, ${s}%, ${Math.max(85, l - 10)}%, 0.25)`;
-  } else if (hsl.l > 70) {
+  } else if (l > 70) {
    // Light colors
-    return `hsla(${hsl.h}, ${hsl.s}%, ${hsl.l - 5}%, 0.22)`;
+    return `hsla(${h}, ${s}%, ${l - 5}%, 0.22)`;
-  } else if (hsl.l > 50) {
+  } else if (l > 50) {
    // Medium colors
-    return `hsla(${hsl.h}, ${hsl.s}%, ${hsl.l}%, 0.18)`;
+    return `hsla(${h}, ${s}%, ${l}%, 0.18)`;
  } else {
    // Dark colors - increase lightness and opacity slightly
-    return `hsla(${hsl.h}, ${hsl.s}%, ${Math.min(hsl.l + 5, 50)}%, 0.15)`;
+    return `hsla(${h}, ${s}%, ${Math.min(l + 5, 50)}%, 0.15)`;
  }
};

7-36: Consider adding unit tests for color manipulation functions.

These color manipulation functions play an important role in maintaining visual consistency. Adding unit tests would help ensure they behave as expected across different input ranges and edge cases.

Example test structure:

// theme.test.ts
import { createBackgroundColor, getIconColor } from './theme';

describe('createBackgroundColor', () => {
  test('handles very light colors correctly', () => {
    const result = createBackgroundColor({ h: 180, s: 50, l: 95 });
    expect(result).toBe('hsla(180, 50%, 85%, 0.25)');
  });
  
  // Additional tests for other lightness ranges
});

describe('getIconColor', () => {
  test('darkens very light colors for better contrast', () => {
    const result = getIconColor({ h: 180, s: 50, l: 90 });
    expect(result).toBe('hsl(180, 60%, 31.5%)');
  });
  
  // Additional tests for other lightness ranges
});
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9782495 and ff9431d.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (2)
  • web/helpers/theme.ts (1 hunks)
  • web/package.json (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Analyze (python)
🔇 Additional comments (2)
web/package.json (1)

43-43: New Dependency Addition: chroma-js
The addition of "chroma-js": "^3.1.2" is appropriate for the new ColorPicker component, as it will provide robust color manipulation functionalities. Please confirm that this version meets the compatibility requirements with the rest of the project's dependencies, and consider verifying if any peer dependency warnings are generated during installation.

web/helpers/theme.ts (1)

23-36:

Details

❓ Verification inconclusive

Review saturation calculation for dark colors.

In the dark colors case, you're calculating saturation as Math.min(hsl.l * 2, 85) which uses the lightness value to modify saturation. This might be intentional for a specific visual effect, but it's unusual to couple these two properties this way. Consider if this is the desired behavior.


🌐 Web query:

What is the relationship between saturation and lightness in HSL color model?

💡 Result:

In the HSL color model, saturation and lightness are interdependent properties that together determine a color's intensity and brightness. Here's a detailed breakdown of their relationship:

Core Interaction

  • Saturation (0-100%) controls color purity (0% = gray, 100% = fully saturated)
  • Lightness (0-100%) controls brightness (0% = black, 100% = white)
  • Purest colors occur at 50% lightness with 100% saturation[2][5]

Key Observations

  1. Lightness dominance:

    • At 0% or 100% lightness, colors become pure black/white regardless of saturation[2][5]
    • Only at 50% lightness can colors achieve full saturation (100%)[4]
  2. Saturation limitations:

    • Maximum saturation decreases as lightness moves away from 50%
    • Colors become inherently less intense when:
      • Lightness >50% (tints with added white)
      • Lightness <50% (shades with added black)[3][5]
  3. Practical manipulation:

    • Tints: Increase lightness (>50%) → reduce perceived saturation
    • Shades: Decrease lightness (<50%) → reduce perceived saturation
    • Tones: Mix with gray by lowering saturation at any lightness[2][7]

Visual Comparison

Lightness Level Pure Saturation Effect Visual Result
0% No effect (always black) Black
25% Maximum muted saturation Dark shaded color
50% Full possible saturation Most vibrant hue
75% Maximum pastel saturation Light tinted color
100% No effect (always white) White

This relationship allows precise control in design workflows - adjusting lightness creates natural-looking tints/shadows while preserving the base hue[8][16]. In CSS, colors like hsl(200, 100%, 50%) represent the pure cyan at mid-lightness, while hsl(200, 100%, 20%) would show a darker, equally saturated navy blue[10][13].

Citations:


Action needed: Verify the saturation adjustment for dark colors

  • In web/helpers/theme.ts, the branch for very dark colors (when hsl.l < 30) computes saturation as Math.min(hsl.l * 2, 85).
  • This approach couples saturation directly with lightness—a pattern that differs from standard HSL usage, where saturation and lightness are typically adjusted independently.
  • Given that in the HSL model maximum color intensity is achieved at around 50% lightness, using a calculation based strictly on lightness might lead to less vivid colors than intended if the original hsl.s was higher.
  • Please confirm whether this behavior is a deliberate visual effect. If it is intentional, consider adding a clarifying comment. Otherwise, evaluate if the original or a different adjustment for saturation might better preserve the intended color appearance.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
web/helpers/theme.tsx (2)

15-65: Function logic is sound but has room for optimization

The ensureAccessibleColors function has a solid approach for ensuring color contrast, but there are some optimization opportunities:

  1. The algorithm always uses the original fg and bg for determining whether to make colors lighter/darker, even after adjustments have been made.
  2. It adjusts both colors in each iteration before checking contrast, which may lead to unnecessary adjustments.

Consider refactoring to use the adjusted colors for comparison in each iteration:

-      if (fg.luminance() > bg.luminance()) {
+      if (adjustedFg.luminance() > adjustedBg.luminance()) {

Also, consider exporting this function since it provides useful functionality that might be needed elsewhere in the application.


67-86: Missing handling for middle lightness range

The createBackgroundColor function handles adjustments for very light colors (l > 90 and l > 70) and very dark colors (l < 30), but there's no explicit handling for the middle range (30-70).

Consider adding a comment explaining the default behavior for the middle range or adding explicit handling if needed:

  } else if (hsl.l < 30) {
    backgroundColor = baseColor.brighten(0.5).alpha(resolvedTheme === "dark" ? 0.22 : 0.12);
+  } else {
+    // Middle range (30-70) uses the base opacity without brightness adjustments
  }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff9431d and 28f993e.

📒 Files selected for processing (1)
  • web/helpers/theme.tsx (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Analyze (python)
🔇 Additional comments (2)
web/helpers/theme.tsx (2)

1-14: Well-structured interfaces with clear documentation

The interfaces are properly defined with helpful comments explaining the range and purpose of each property. This facilitates understanding of the data structures used throughout the file.


1-100:

Details

✅ Verification successful

Verify WCAG color contrast calculations

The implementation uses chroma.js for calculating color contrast, but it's important to ensure the calculations meet the latest WCAG standards for accessibility.


🌐 Web query:

Is chroma.js contrast calculation compliant with WCAG 2.1 standards?

💡 Result:

Chroma.js provides a WCAG 2.1-compliant contrast calculation method that aligns with the standard’s requirements for measuring luminance ratios between text and background colors. The library implements the formula (Y_max + 0.05) / (Y_min + 0.05)[9], which matches the WCAG 2.1 specification for contrast ratios[2][3][6]. This method is widely used for accessibility validation, though it has documented limitations in perceptual accuracy compared to newer models like APCA[1][5].

Key Compliance Points:

  • Threshold Adherence: Chroma.js accurately calculates the 4.5:1 minimum ratio for standard text and 3:1 for large text (≥18pt or ≥14pt bold), as required by WCAG 2.1 Level AA[6][10][16].
  • Formula Implementation: The calculation includes a 0.05 offset for both foreground and background luminance to simulate real-world viewing conditions (e.g., screen glare)[5][9].
  • Non-Text Contrast Support: While not explicitly mentioned in Chroma.js documentation, WCAG 2.1’s 3:1 ratio for UI components (buttons, icons) can be verified using the same method[2][20].

Limitations to Note:

  • Perceptual Inaccuracy: WCAG 2.1’s linear model may fail to detect insufficient contrast in dark-mode interfaces or low-saturation colors[1][5]. For example, white text on a dark blue background might pass WCAG 2.1 but remain hard to read[14].
  • Alternatives in Development: Chroma.js also offers APCA contrast calculations[9], which address these perceptual issues and align with the upcoming WCAG 3.0 draft[1][5].

For compliance validation, Chroma.js’s WCAG 2.1 method remains a valid tool but should be supplemented with manual checks or APCA-based evaluations for complex color schemes[5][15].

Citations:


WCAG 2.1 Contrast Compliance Confirmed

After reviewing the implementation, the use of Chroma.js for calculating contrast is compliant with WCAG 2.1 standards. The code correctly applies the formula (Y_max + 0.05) / (Y_min + 0.05), meeting the necessary thresholds for both text and UI components. While the library has known perceptual limitations compared to methods like APCA, these do not affect its current compliance.

  • In ensureAccessibleColors: The contrast adjustment loop effectively uses Chroma.js to meet the specified WCAG requirements.
  • Thresholds: The default target contrast of 4.5 (for WCAG AAA) and 3 (for UI components) are appropriate.

@sriramveeraghanta sriramveeraghanta merged commit 99dba80 into preview Mar 27, 2025
5 of 6 checks passed
@sriramveeraghanta sriramveeraghanta deleted the dev-color-picker-component branch March 27, 2025 12:18
lifeiscontent pushed a commit that referenced this pull request Aug 18, 2025
* dev: color picker component added

* chore: helper function added

* chore: code refactor

* chore: code refactor

* chore: code refactor

* chore: code refactor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants