From 1a3107dba736f8eafb42856901640db553127a61 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Wed, 11 Jun 2025 20:51:27 +0300 Subject: [PATCH] create `transparentifyColor` function --- src/alf/util/__tests__/colors.test.ts | 48 +++++++++++++++++++ src/alf/util/colorGeneration.ts | 28 +++++++++++ .../post-thread/PostThreadComposePrompt.tsx | 6 +-- 3 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 src/alf/util/__tests__/colors.test.ts diff --git a/src/alf/util/__tests__/colors.test.ts b/src/alf/util/__tests__/colors.test.ts new file mode 100644 index 0000000000..350b6ff4a4 --- /dev/null +++ b/src/alf/util/__tests__/colors.test.ts @@ -0,0 +1,48 @@ +import {jest} from '@jest/globals' + +import {logger} from '#/logger' +import {transparentifyColor} from '../colorGeneration' + +jest.mock('#/logger', () => ({ + logger: {warn: jest.fn()}, +})) + +describe('transparentifyColor', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + it('converts hsl() to hsla()', () => { + const result = transparentifyColor('hsl(120 100% 50%)', 0.5) + expect(result).toBe('hsla(120 100% 50%, 0.5)') + }) + + it('converts hsl() to hsla() - fully transparent', () => { + const result = transparentifyColor('hsl(120 100% 50%)', 0) + expect(result).toBe('hsla(120 100% 50%, 0)') + }) + + it('converts rgb() to rgba()', () => { + const result = transparentifyColor('rgb(255 0 0)', 0.75) + expect(result).toBe('rgba(255 0 0, 0.75)') + }) + + it('expands 3-digit hex and appends alpha channel', () => { + const result = transparentifyColor('#abc', 0.4) + expect(result).toBe('#aabbcc66') + }) + + it('appends alpha to 6-digit hex', () => { + const result = transparentifyColor('#aabbcc', 0.4) + expect(result).toBe('#aabbcc66') + }) + + it('returns the original string and warns for unsupported formats', () => { + const unsupported = 'blue' + const result = transparentifyColor(unsupported, 0.5) + expect(result).toBe(unsupported) + expect(logger.warn).toHaveBeenCalledWith( + `Could not make '${unsupported}' transparent`, + ) + }) +}) diff --git a/src/alf/util/colorGeneration.ts b/src/alf/util/colorGeneration.ts index 8d769b51b1..574ab0a496 100644 --- a/src/alf/util/colorGeneration.ts +++ b/src/alf/util/colorGeneration.ts @@ -1,3 +1,5 @@ +import {logger} from '#/logger' + export const BLUE_HUE = 211 export const RED_HUE = 346 export const GREEN_HUE = 152 @@ -19,3 +21,29 @@ export function generateScale(start: number, end: number) { export const defaultScale = generateScale(6, 100) // dim shifted 6% lighter export const dimScale = generateScale(12, 100) + +export function transparentifyColor(color: string, alpha: number) { + if (color.startsWith('hsl(')) { + return 'hsla(' + color.slice('hsl('.length, -1) + `, ${alpha})` + } else if (color.startsWith('rgb(')) { + return 'rgba(' + color.slice('rgb('.length, -1) + `, ${alpha})` + } else if (color.startsWith('#')) { + if (color.length === 7) { + const alphaHex = Math.round(alpha * 255).toString(16) + // Per MDN: If there is only one number, it is duplicated: e means ee + // https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color + return color.slice(0, 7) + alphaHex.padStart(2, alphaHex) + } else if (color.length === 4) { + // convert to 6-digit hex before adding alpha + const [r, g, b] = color.slice(1).split('') + const alphaHex = Math.round(alpha * 255).toString(16) + return `#${r.repeat(2)}${g.repeat(2)}${b.repeat(2)}${alphaHex.padStart( + 2, + alphaHex, + )}` + } + } else { + logger.warn(`Could not make '${color}' transparent`) + } + return color +} diff --git a/src/view/com/post-thread/PostThreadComposePrompt.tsx b/src/view/com/post-thread/PostThreadComposePrompt.tsx index 6b421e9de9..3de69337e1 100644 --- a/src/view/com/post-thread/PostThreadComposePrompt.tsx +++ b/src/view/com/post-thread/PostThreadComposePrompt.tsx @@ -10,6 +10,7 @@ import {useProfileQuery} from '#/state/queries/profile' import {useSession} from '#/state/session' import {UserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, ios, native, useBreakpoints, useTheme} from '#/alf' +import {transparentifyColor} from '#/alf/util/colorGeneration' import {useInteractionState} from '#/components/hooks/useInteractionState' import {Text} from '#/components/Typography' @@ -47,12 +48,11 @@ export function PostThreadComposePrompt({ ]}> {!gtMobile && (