diff --git a/app/containers/message/Components/Attachments/CollapsibleQuote/index.tsx b/app/containers/message/Components/Attachments/CollapsibleQuote/index.tsx index 073e5d7660d..f84df0aefdc 100644 --- a/app/containers/message/Components/Attachments/CollapsibleQuote/index.tsx +++ b/app/containers/message/Components/Attachments/CollapsibleQuote/index.tsx @@ -1,4 +1,3 @@ -import { transparentize } from 'color2k'; import { dequal } from 'dequal'; import React, { useContext, useState } from 'react'; import { StyleSheet, Text, View } from 'react-native'; @@ -13,6 +12,7 @@ import Markdown from '../../../../markdown'; import MessageContext from '../../../Context'; import Touchable from '../../../Touchable'; import { BUTTON_HIT_SLOP } from '../../../utils'; +import withAlpha from '../../../../../lib/helpers/withAlpha'; const styles = StyleSheet.create({ button: { @@ -144,7 +144,7 @@ const CollapsibleQuote = React.memo( try { if (attachment.color) { - backgroundColor = transparentize(attachment.color, 0.8); + backgroundColor = withAlpha(attachment.color, 0.8); // works for rgba, rgb, hex (#rgb, #rrggbb) strokeExtraLight = attachment.color; strokeLight = attachment.color; strokeMedium = attachment.color; diff --git a/app/lib/helpers/withAlpha.test.ts b/app/lib/helpers/withAlpha.test.ts new file mode 100644 index 00000000000..fcc6daaebca --- /dev/null +++ b/app/lib/helpers/withAlpha.test.ts @@ -0,0 +1,31 @@ +import withAlpha from './withAlpha'; + +describe('withAlpha', () => { + test('rgba to be rgba with updated alpha', () => { + expect(withAlpha('rgba(255, 0, 0, 0.2)', 0.8)).toBe('rgba(255, 0, 0, 0.8)'); + }); + + test('rgb to be rgba with updated alpha', () => { + expect(withAlpha('rgb(255, 0, 0)', 0.8)).toBe('rgba(255, 0, 0, 0.8)'); + }); + + test('hex #rgb to be hex #rrggbbaa with updated alpha', () => { + expect(withAlpha('#f00', 0.8)).toBe('#ff0000cc'); + }); + + test('hex #rrggbb to be hex #rrggbbaa with updated alpha', () => { + expect(withAlpha('#ff0000', 0.8)).toBe('#ff0000cc'); + }); + + test('hex #rrggbbaa to be hex #rrggbbaa with updated alpha', () => { + expect(withAlpha('#ff000022', 0.8)).toBe('#ff0000cc'); + }); + + test("'red' to be 'red' without change in alpha", () => { + expect(withAlpha('red', 0.8)).toBe('red'); + }); + + test('hsl to be hsl without change in alpha', () => { + expect(withAlpha('hsl(0, 100%, 50%)', 0.8)).toBe('hsl(0, 100%, 50%)'); + }); +}); diff --git a/app/lib/helpers/withAlpha.ts b/app/lib/helpers/withAlpha.ts new file mode 100644 index 00000000000..4fb5f4ffcdf --- /dev/null +++ b/app/lib/helpers/withAlpha.ts @@ -0,0 +1,78 @@ +/** + * Applies an alpha (opacity) value to a color string. + * + * Supported formats: + * - `rgba(r,g,b,a)` → updates alpha + * - `rgb(r,g,b)` → converted to `rgba` + * - `#rgb`, `#rrggbb`, `#rrggbbaa` → normalized to `#rrggbbaa` + * + * Unsupported formats (returned unchanged): + * - Named colors (`red`) + * - `hsl()` / `hsla()` + * - Invalid color strings + * + * @param color Input color string + * @param transparency Transparency value between `0` and `1` (default: `0`) + */ +const withAlpha = (color: string, transparency: number = 0) => { + if (!color) { + return color; + } + + const alpha = 1 - transparency; + + // case rgba + if (color.startsWith('rgba')) { + const match = color.match(/rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([0-9.]+)\s*\)/); + + if (!match) { + return color; + } + + const [, r, g, b] = match; + return `rgba(${r}, ${g}, ${b}, ${alpha})`; + } + + // case rgb + if (color.startsWith('rgb')) { + const match = color.match(/rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/); + + if (!match) { + return color; + } + + const [, r, g, b] = match; + return `rgba(${r}, ${g}, ${b}, ${alpha})`; + } + + // case hex + if (color.startsWith('#')) { + let hex = color.slice(1); + + // #rgb → #rrggbb + if (hex.length === 3) { + hex = hex + .split('') + .map(c => c + c) + .join(''); + } + + if (hex.length === 8) { + hex = hex.substring(0, 6); + } + if (hex.length !== 6) { + return color; + } + + const alphaHex = Math.round(alpha * 255) + .toString(16) + .padStart(2, '0'); + + return `#${hex}${alphaHex}`; + } + + // named colors / invalid strings + return color; +}; + +export default withAlpha; diff --git a/package.json b/package.json index 45c7b5e50e1..2508135c886 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,6 @@ "@rocket.chat/sdk": "RocketChat/Rocket.Chat.js.SDK#mobile", "@rocket.chat/ui-kit": "0.31.19", "bytebuffer": "5.0.1", - "color2k": "1.2.4", "dayjs": "^1.11.18", "dequal": "2.0.3", "ejson": "2.2.3", diff --git a/yarn.lock b/yarn.lock index 5f334cbad37..44b5ad84f64 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7000,11 +7000,6 @@ color-string@^1.9.0: color-name "^1.0.0" simple-swizzle "^0.2.2" -color2k@1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/color2k/-/color2k-1.2.4.tgz#af34950ac58e23cf224a01cb8dd0c9911a79605e" - integrity sha512-DiwdBwc0BryPFFXoCrW8XQGXl2rEtMToODybxZjKnN5IJXt/tV/FsN02pCK/b7KcWvJEioz3c74lQSmayFvS4Q== - color@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a"