-
Notifications
You must be signed in to change notification settings - Fork 31
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
Add alpha channel support #15
Add alpha channel support #15
Conversation
Add alpha slider component Add alpha props and alpha slider to components
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.
Hi!
Thank you for your work! I will try to review it asap.
However, I'm not sure about the possible undefined
in a
(in the ColorRGB
and ColorHSV
interfaces), when user does not use the alpha channel. Maybe it would be better to leave it equal to 100?
I'd be concerned that existing users using regular non-alpha colors might be accidentally impacted by the picker suddenly returning alpha values; for example if |
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'd be concerned that existing users using regular non-alpha colors might be accidentally impacted by the picker suddenly returning alpha values; for example if
ColorRGB.a
defaults to 255 then gets converted into a hex string before returning to the end user it would be an eight-digit hex string ending inff
instead of the six-digit hex string it is now. This way the 4-channel results are only introduced if thealpha
prop is present and/or thea
property is present. The alternative would be to introduce a breaking change where alpha values and eight-digit hex strings are always returned even if thealpha
prop isn't present, which seems very risky to me.
Hmm. I think you're right.
Don't you think it would be more correct to convert the alpha channel to values from 0 to 1, instead of from 0 to 255 (ColorRGB
) and from 0 to 100 (ColorHSV
). Since in css
when using rgba
, browser expects to get 1 as 100% for the alpha channel. Then the users will not need to convert it.
src/components/Alpha.component.tsx
Outdated
}; | ||
|
||
return ( | ||
<Interactive className="rcp-alpha" onChange={updateColor}> |
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 think you need to change color of the alpha channel to the selected color.
<Interactive
style={{
background: `linear-gradient(to right, rgba(${color.rgb.r}, ${color.rgb.g}, ${color.rgb.b}, 0), rgba(${color.rgb.r}, ${color.rgb.g}, ${color.rgb.b}, 1)) top left / auto auto,
conic-gradient(#666 0.25turn, #999 0.25turn 0.5turn, #555 0.5turn 0.75turn, #999 0.75turn) top left / 12px 12px
repeat`,
}}
className="rcp-alpha"
onChange={updateColor}
>
src/components/Alpha.component.tsx
Outdated
<div | ||
className="rcp-alpha-cursor" | ||
style={{ left: position, backgroundColor: `hsl(0, 0%, ${color.hsv.a ?? 100}%)` }} | ||
/> |
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.
For the cursor, respectively, also.
<div
className="rcp-alpha-cursor"
style={{
left: position,
background: `linear-gradient(to right, rgba(${color.rgb.r}, ${color.rgb.g}, ${color.rgb.b}, ${color.rgb.a}), rgba(${color.rgb.r}, ${color.rgb.g}, ${color.rgb.b}, ${color.rgb.a})) top left / auto auto,
conic-gradient(#666 0.25turn, #999 0.25turn 0.5turn, #555 0.5turn 0.75turn, #999 0.75turn) top left / 12px 12px
repeat`,
}}
/>
src/css/styles.css
Outdated
background: linear-gradient(to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1)) top left / auto auto, | ||
conic-gradient(#666 0.25turn, #999 0.25turn 0.5turn, #555 0.5turn 0.75turn, #999 0.75turn) top left / 12px 12px | ||
repeat; |
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.
Because we are now changing the color dynamically. background
is no longer needed.
src/utils/convert.util.ts
Outdated
let newValue = "#"; | ||
|
||
for (let i = 1; i < value.length; i++) { | ||
newValue += value[i] + value[i]; | ||
} | ||
|
||
return newValue; |
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.
What do you think about this change? It does not create variables internally and is more concise in writing.
value = value
.split("")
.map((v, i) => (i ? v + v : "#"))
.join("");
return value;
src/utils/convert.util.ts
Outdated
if (!Number.isNaN(a)) { | ||
return { r, g, b, a }; | ||
} |
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 the condition is not necessary, because our interface resolves a
as undefined
.
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 one I think we actually do need to keep, or at least something similar; a
in this case is the result of calling parseInt(hex.slice(6, 8), 16)
. parseInt()
returns NaN
if it cannot parse the value. So when calling this function with a six-digit hex string like #FF007F
, the parseInt()
will return NaN
and you'll end up with { r: 255, g: 0, b: 127, a: NaN }
which is probably not what we want.
src/utils/convert.util.ts
Outdated
|
||
if (!Number.isNaN(a)) { | ||
return { r, g, b, a }; | ||
} | ||
|
||
return { r, g, b }; |
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.
Accordingly, now so.
return { r, g, b, a };
src/utils/convert.util.ts
Outdated
if (a !== undefined) { | ||
a /= 255; | ||
a *= 100; | ||
} |
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.
if (a) {
a /= 255;
a *= 100;
}
src/utils/convert.util.ts
Outdated
if (a !== undefined) { | ||
a = Math.round((a / 100) * 255); | ||
} |
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.
if (a) {
a = Math.round((a / 100) * 255);
}
src/utils/convert.util.ts
Outdated
export function rgb2hex({ r, g, b }: Color["rgb"]): Color["hex"] { | ||
const hex = [r, g, b].map((v) => v.toString(16).padStart(2, "0")).join(""); | ||
export function rgb2hex({ r, g, b, a }: Color["rgb"]): Color["hex"] { | ||
const hex = [r, g, b, a].map((v) => (v !== undefined ? v.toString(16).padStart(2, "0") : "")).join(""); |
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.
const hex = [r, g, b, a].map((v) => (v ? v.toString(16).padStart(2, "0") : "")).join("");
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 change would cause any value equal to zero to emit an empty string, since v ? (...) : (...)
implicitly casts to a boolean and zero is a falsy value.
Great feedback! I'll implement those changes and update the PR. Making the range 0-1 for alpha makes sense; I'll need to add a bit of extra work in parsing the RGB/HSV text fields since they currently only accept integer values. Something to look out for after the PR is updated. |
This pull request is being automatically deployed with Vercel (learn more). 🔍 Inspect: https://vercel.com/wondermarin/react-color-palette/DacboYeFXGUw79B1cWJdXztWMbAH |
Updated to resolve the feedback, except for things I was unsure about. I tried to reduce some duplicated code as well. |
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.
Except for this, everything else is fine! Thank you for the work, as soon as you update the PR, I will be happy to merge it.
src/utils/convert.util.ts
Outdated
|
||
return { r, g, b }; | ||
return { r, g, b, a: Number.isNaN(a) ? undefined : a / 255 }; |
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.
Since now the alpha channel format is from 0 to 1, you can do this.
const a = parseInt(hex.slice(6, 8), 16) || undefined;
if (a) a /= 255;
return { r, g, b, a };
dark = false, | ||
}: ColorPickerProps): JSX.Element => ( | ||
<div className={`rcp ${dark ? "rcp-dark" : "rcp-light"}`} style={{ width }}> | ||
<Saturation width={width} height={height} color={color} onChange={onChange} /> | ||
<div className="rcp-body"> | ||
<Hue width={width - 40} color={color} onChange={onChange} /> | ||
<Fields color={color} hideHEX={hideHEX} hideRGB={hideRGB} hideHSV={hideHSV} onChange={onChange} /> | ||
{alpha ? <Alpha width={width - 40} color={color} onChange={onChange} /> : 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.
Is it just me or the &&
a cleaner way to do optional rendering so that null
is not needed.
What I mean is this
{alpha && <Alpha width={width - 40} color={color} onChange={onChange} />}
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.
Is it just me or the
&&
a cleaner way to do optional rendering so thatnull
is not needed.What I mean is this
{alpha && <Alpha width={width - 40} color={color} onChange={onChange} />}
Oh. I didn't notice. Thank you ❤️
Updated! Should address those bits of feedback. |
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.
Great job!
Switched from react-color to this library, big part of that was because of this feature, thank you @Hawkbat! 💪 and thanks for the clean alternative @Wondermarin 👍 |
Checklist
master
branch.yarn test (npm run test)
passes with this change.Description of Change
I took a crack at adding alpha support. The only public interface changes are the addition of an optional boolean
alpha
prop to display the alpha slider and toggle parsing of alpha values from the text inputs, as well as an extension of the color constructors to accept 4/8-digit hex strings, an additionala
property on rgb/hsv colors, and four-value arrays.There should be no breaking changes unless a user was already passing in 4/8-digit hex strings, four-value arrays, or objects with
a
alpha channels, in which case those will now be round-tripped appropriately instead of truncated. Since that behavior would technically be a violation of the previous interface I think it's up to you how you want to handle that.The utils are updated to handle the alpha channel if present/not
undefined
. There's a new alpha slider component below the hue slider that's only visible if thealpha
prop is passed in. And the text inputs will correctly parse and display alpha values if they are provided. If no alpha value is set, the components return the colors without an alpha value.Absolutely willing to go back and forth on this if it's not up to snuff for the project! I'd been looking for a TypeScript-based color picker for a while and couldn't find one quite as clean as yours.