diff --git a/assets/icons/community/leaflet.svg b/assets/icons/community/leaflet.svg index ae7d7bfe3c..e9a65c154d 100644 --- a/assets/icons/community/leaflet.svg +++ b/assets/icons/community/leaflet.svg @@ -1 +1 @@ - + diff --git a/assets/icons/community/standard-site.svg b/assets/icons/community/standard-site.svg index f52160abc0..64dd20c3bd 100644 --- a/assets/icons/community/standard-site.svg +++ b/assets/icons/community/standard-site.svg @@ -1 +1 @@ - + diff --git a/src/alf/index.tsx b/src/alf/index.tsx index 1c7b080d87..0cb5ceb3f7 100644 --- a/src/alf/index.tsx +++ b/src/alf/index.tsx @@ -1,5 +1,5 @@ import {createContext, useCallback, useContext, useMemo, useState} from 'react' -import {type Theme, type ThemeName} from '@bsky.app/alf' +import {type Theme, type ThemeName, utils as baseUtils} from '@bsky.app/alf' import { computeFontScaleMultiplier, @@ -9,14 +9,10 @@ import { setFontScale as persistFontScale, } from '#/alf/fonts' import {themes} from '#/alf/themes' +import {darken, lighten, rgbToHex} from '#/alf/util/colorGeneration' import {type Device} from '#/storage' -export { - type TextStyleProp, - type Theme, - utils, - type ViewStyleProp, -} from '@bsky.app/alf' +export {type TextStyleProp, type Theme, type ViewStyleProp} from '@bsky.app/alf' export {atoms} from '#/alf/atoms' export * from '#/alf/breakpoints' export * from '#/alf/fonts' @@ -25,6 +21,12 @@ export * from '#/alf/util/flatten' export * from '#/alf/util/platform' export * from '#/alf/util/themeSelector' export * from '#/alf/util/useGutters' +export const utils = { + ...baseUtils, + rgbToHex, + lighten, + darken, +} export type Alf = { themeName: ThemeName @@ -64,7 +66,11 @@ Context.displayName = 'AlfContext' export function ThemeProvider({ children, theme: themeName, -}: React.PropsWithChildren<{theme: ThemeName}>) { + themesOverride, +}: React.PropsWithChildren<{ + theme: ThemeName + themesOverride?: Partial +}>) { const [fontScale, setFontScale] = useState(() => getFontScale(), ) @@ -90,11 +96,15 @@ export function ThemeProvider({ [setFontFamily], ) - const value = useMemo( - () => ({ - themes, + const value = useMemo(() => { + const t = { + ...themes, + ...themesOverride, + } + return { + themes: t, themeName: themeName, - theme: themes[themeName], + theme: t[themeName], fonts: { scale: fontScale, scaleMultiplier: fontScaleMultiplier, @@ -103,16 +113,16 @@ export function ThemeProvider({ setFontFamily: setFontFamilyAndPersist, }, flags: {}, - }), - [ - themeName, - fontScale, - setFontScaleAndPersist, - fontFamily, - setFontFamilyAndPersist, - fontScaleMultiplier, - ], - ) + } + }, [ + themeName, + fontScale, + setFontScaleAndPersist, + fontFamily, + setFontFamilyAndPersist, + fontScaleMultiplier, + themesOverride, + ]) return {children} } diff --git a/src/alf/util/colorGeneration.ts b/src/alf/util/colorGeneration.ts index 467270676a..ee1224be6e 100644 --- a/src/alf/util/colorGeneration.ts +++ b/src/alf/util/colorGeneration.ts @@ -6,3 +6,114 @@ export const BLUE_HUE = 211 * @deprecated use `utils.alpha` from `@bsky.app/alf` instead */ export const transparentifyColor = utils.alpha + +/** + * Lighten a hex color by `amount` percentage points of HSL lightness (0-100). + */ +export function lighten(hex: string, amount: number): string { + return adjustLightness(hex, amount) +} + +/** + * Darken a hex color by `amount` percentage points of HSL lightness (0-100). + */ +export function darken(hex: string, amount: number): string { + return adjustLightness(hex, -amount) +} + +function adjustLightness(hex: string, delta: number): string { + const {r, g, b} = hexToRgb(hex) + const {h, s, l} = rgbToHsl(r, g, b) + const next = clamp(l + delta, 0, 100) + const out = hslToRgb(h, s, next) + return rgbToHex(out.r, out.g, out.b) +} + +function hexToRgb(hex: string): {r: number; g: number; b: number} { + const h = hex.replace('#', '') + const expanded = + h.length === 3 + ? h + .split('') + .map(c => c + c) + .join('') + : h + const num = parseInt(expanded, 16) + return { + r: (num >> 16) & 255, + g: (num >> 8) & 255, + b: num & 255, + } +} + +export function rgbToHex(r: number, g: number, b: number): string { + const c = (n: number) => clamp(Math.round(n), 0, 255) + return `#${((1 << 24) + (c(r) << 16) + (c(g) << 8) + c(b)) + .toString(16) + .slice(1)}` +} + +function rgbToHsl( + r: number, + g: number, + b: number, +): {h: number; s: number; l: number} { + r /= 255 + g /= 255 + b /= 255 + const max = Math.max(r, g, b) + const min = Math.min(r, g, b) + const l = (max + min) / 2 + let h = 0 + let s = 0 + if (max !== min) { + const d = max - min + s = l > 0.5 ? d / (2 - max - min) : d / (max + min) + switch (max) { + case r: + h = (g - b) / d + (g < b ? 6 : 0) + break + case g: + h = (b - r) / d + 2 + break + case b: + h = (r - g) / d + 4 + break + } + h /= 6 + } + return {h: h * 360, s: s * 100, l: l * 100} +} + +function hslToRgb( + h: number, + s: number, + l: number, +): {r: number; g: number; b: number} { + h /= 360 + s /= 100 + l /= 100 + if (s === 0) { + const v = l * 255 + return {r: v, g: v, b: v} + } + const q = l < 0.5 ? l * (1 + s) : l + s - l * s + const p = 2 * l - q + const f = (t: number) => { + if (t < 0) t += 1 + if (t > 1) t -= 1 + if (t < 1 / 6) return p + (q - p) * 6 * t + if (t < 1 / 2) return q + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6 + return p + } + return { + r: f(h + 1 / 3) * 255, + g: f(h) * 255, + b: f(h - 1 / 3) * 255, + } +} + +function clamp(n: number, min: number, max: number): number { + return Math.max(min, Math.min(max, n)) +} diff --git a/src/components/Button.tsx b/src/components/Button.tsx index aa705b1897..7a3806c2a2 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -263,6 +263,7 @@ export const Button = forwardRef( baseStyles.push(t.atoms.bg_contrast_50) } } else if (color === 'secondary_inverted') { + console.log(t) if (!disabled) { baseStyles.push({ backgroundColor: t.palette.contrast_900, diff --git a/src/components/Post/Embed/StandardSiteEmbed/index.tsx b/src/components/Post/Embed/StandardSiteEmbed/index.tsx index 623049fc5e..209dd43876 100644 --- a/src/components/Post/Embed/StandardSiteEmbed/index.tsx +++ b/src/components/Post/Embed/StandardSiteEmbed/index.tsx @@ -16,7 +16,14 @@ import {toNiceDomain} from '#/lib/strings/url-helpers' import {useExternalEmbedsPrefs} from '#/state/preferences' import {useProfileQuery} from '#/state/queries/profile' import {UserAvatar} from '#/view/com/util/UserAvatar' -import {atoms as a, useBreakpoints, useTheme} from '#/alf' +import { + atoms as a, + ThemeProvider, + useAlf, + useBreakpoints, + useTheme, + utils, +} from '#/alf' import {ButtonIcon, ButtonText} from '#/components/Button' import {Divider} from '#/components/Divider' import {Clock_Stroke2_Corner0_Rounded as Clock} from '#/components/icons/Clock' @@ -31,6 +38,7 @@ import {Text} from '#/components/Typography' import {IS_NATIVE} from '#/env' export type ThemeColors = { + custom: boolean accent: string accentForeground: string } @@ -105,14 +113,20 @@ export const StandardSiteEmbed = ({ new AtUri(ref.uri).collection !== 'site.standard.document', ) const themeColors = useMemo(() => { + let custom = false let accent = t.atoms.text.color let accentForeground = t.atoms.text_inverted.color const {accentRGB, accentForegroundRGB} = view.source?.theme || {} - if (accent && accentForeground) { - accent = colorRGBToHex(accentRGB) - accentForeground = colorRGBToHex(accentForegroundRGB) + if (accentRGB && accentForegroundRGB) { + custom = true + accent = utils.rgbToHex(accentRGB.r, accentRGB.g, accentRGB.b) + accentForeground = utils.rgbToHex( + accentForegroundRGB.r, + accentForegroundRGB.g, + accentForegroundRGB.b, + ) } - return {accent, accentForeground} + return {custom, accent, accentForeground} }, [view]) const maybeAuthorDid = useMemo(() => { @@ -471,6 +485,7 @@ export function SubscribeButton({ view, onPress, onLongPress, + themeColors, style, }: { view: AppBskyEmbedExternal.ViewExternal @@ -479,34 +494,96 @@ export function SubscribeButton({ themeColors: ThemeColors style?: StyleProp }) { + const alf = useAlf() const {t: l} = useLingui() const highlightedPublisher = useStandardSitePublisherConfig(view) const cta = highlightedPublisher ? l`Subscribe on ${highlightedPublisher.name}` : l`View publication` + const themesOverride = useMemo(() => { + if (!themeColors.custom) return alf.themes + const atoms = { + text_inverted: {color: themeColors.accentForeground}, // text + } + const palette = { + contrast_975: utils.darken(themeColors.accent, 5), // hover + contrast_900: themeColors.accent, // bg + contrast_600: utils.lighten(themeColors.accent, 5), // disabled bg + contrast_300: themeColors.accentForeground, // disabled text + } + return { + lightPalette: { + ...alf.themes.lightPalette, + ...palette, + }, + darkPalette: { + ...alf.themes.darkPalette, + ...palette, + }, + dimPalette: { + ...alf.themes.dimPalette, + ...palette, + }, + light: { + ...alf.themes.light, + atoms: { + ...alf.themes.light.atoms, + ...atoms, + }, + palette: { + ...alf.themes.light.palette, + ...palette, + }, + }, + dark: { + ...alf.themes.dark, + atoms: { + ...alf.themes.dark.atoms, + ...atoms, + }, + palette: { + ...alf.themes.dark.palette, + ...palette, + }, + }, + dim: { + ...alf.themes.dim, + atoms: { + ...alf.themes.dim.atoms, + ...atoms, + }, + palette: { + ...alf.themes.dim.palette, + ...palette, + }, + }, + } + }, [alf, themeColors]) return ( - - {highlightedPublisher ? ( - <> - - - | - + + + {highlightedPublisher ? ( + <> + + + {/*|*/} + + {cta} + + ) : ( {cta} - - ) : ( - {cta} - )} - + )} + + ) } @@ -707,11 +784,3 @@ export function PublicationFooter({ ) } - -function colorRGBToHex( - rgb: AppBskyEmbedExternal.ViewExternalSourceTheme['accentRGB'], -): string { - if (!rgb) return '#000000' - const {r, g, b} = rgb - return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}` -} diff --git a/src/components/icons/community/Leaflet.tsx b/src/components/icons/community/Leaflet.tsx index d988053371..15649f98bd 100644 --- a/src/components/icons/community/Leaflet.tsx +++ b/src/components/icons/community/Leaflet.tsx @@ -1,5 +1,5 @@ import {createSinglePathSVG} from '../TEMPLATE' export const Leaflet = createSinglePathSVG({ - path: 'M16.86 3.907c.25.027.47.18.582.405.764 1.527 1.559 3.26 1.559 6.251 0 4.493-3.882 8.14-8.14 8.14a5.92 5.92 0 0 1-4.604-2.178c-.741.682-1.133 1.635-1.133 2.735a.74.74 0 0 1-1.48 0c0-1.632.65-3.05 1.82-4.009a5.923 5.923 0 0 1 4.367-8.3c2.124-.41 3.375-.697 4.278-1.087.852-.367 1.409-.835 2.095-1.685a.74.74 0 0 1 .656-.272m-.273 2.053a6 6 0 0 1-1.89 1.263c-1.077.464-2.491.777-4.586 1.181l-.014.003A4.442 4.442 0 0 0 6.74 14.48 6.3 6.3 0 0 1 8 14.095c.805-.16 1.646-.498 2.38-.892.74-.397 1.32-.823 1.623-1.126a.74.74 0 0 1 1.047 1.046c-.438.438-1.153.945-1.97 1.384-.825.442-1.805.843-2.79 1.039h-.002a5 5 0 0 0-.744.207 4.43 4.43 0 0 0 3.314 1.473h.003l.324-.01c3.34-.18 6.336-3.117 6.336-6.651 0-2.067-.413-3.428-.933-4.604', + path: 'M16.091 3.001c.385-.01 1.07.04 1.51.646q.122.17.186.334c.317-.029.759-.066 1.17-.032.395.034 1.195.163 1.661.9.411.65.308 1.316.107 1.768a3.3 3.3 0 0 1-.416.667c.142.124.302.293.428.507.302.514.316 1.083.182 1.62-.236.944-1.025 1.356-1.504 1.54-.493.19-1.07.294-1.502.371l-.05.009-.1.017c.106.368.084.759-.039 1.13-.34 1.025-1.114 1.513-1.85 1.718-.632.176-1.337.176-1.839.175h-.18c.126.508-.018 1.009-.158 1.348-.52 1.25-1.696 1.717-2.647 1.915s-2.052.204-2.966.21h-.063c-.5.002-.945.005-1.334.027-.345.666-.63 1.407-.962 2.271v.003l-.27.701c-.37.943-1.413 1.4-2.33 1.02-.918-.378-1.364-1.45-.996-2.392.473-1.207.962-2.049 1.615-3.342.175-.348.322-.628.452-.889 0-.786.057-1.341.17-2.045.19-1.183.35-1.639.585-2.718.215-.746.532-1.747 1.03-2.299.285-.317.727-.78 1.392-.812.404-.02.656.315.869.457.618-.86 1.143-2.005 1.82-2.894.542-.713 1.125-1.336 2.067-1.188.485.077 1.176.452 1.176 1.188.18-.205.729-.995.96-1.188.44-.367 1.048-.721 1.826-.743m-.347.953c-.828.023-1.412.747-1.902 1.354-.355.44-.66.82-.975.828-.308.008-.285-.204-.256-.473.042-.383.095-.88-.78-1.02-.726-.114-1.492 1.266-2.157 2.464-.455.82-.864 1.555-1.18 1.667-.482.172-.705-.03-.896-.202-.23-.208-.413-.373-.947.22-.563.623-.947 2.328-1.319 4.186-.047.237.283.39.434.203.856-1.057 2.248-2.51 3.908-3.585 1.526-.988 2.998-1.97 4.664-2.464.245-.073.315.21.08.312-1.878.82-3.491 1.903-5.181 3.348-1.9 1.589-3.696 3.703-4.607 5.427a22 22 0 0 0-1.511 3.127.6.6 0 0 0 .32.771.573.573 0 0 0 .751-.329q.134-.345.262-.68c.39-1.02.756-1.976 1.271-2.845a.21.21 0 0 1 .154-.101c.56-.069 1.227-.073 1.917-.077 1.881-.01 3.936-.023 4.487-1.348.287-.692-.06-.792-.373-.881-.255-.074-.489-.141-.335-.52.268-.655 1.138-.655 2.061-.655 1.075 0 2.22 0 2.563-1.036.147-.443-.115-.618-.347-.772-.194-.129-.366-.244-.262-.492.167-.394.89-.525 1.658-.663.917-.165 1.895-.34 2.057-.99.154-.614-.133-.812-.429-1.017-.18-.126-.364-.255-.454-.481-.11-.279.13-.576.38-.885.296-.365.604-.746.362-1.13-.276-.435-1.096-.358-1.775-.295-.424.04-.793.073-.94-.025-.148-.1-.142-.268-.136-.436.009-.262.017-.521-.566-.505', }) diff --git a/src/components/icons/community/StandardSite.tsx b/src/components/icons/community/StandardSite.tsx index c283cc39f8..430b258eb5 100644 --- a/src/components/icons/community/StandardSite.tsx +++ b/src/components/icons/community/StandardSite.tsx @@ -1,5 +1,5 @@ import {createSinglePathSVG} from '../TEMPLATE' export const StandardSite = createSinglePathSVG({ - path: 'M11.862 3.042a.25.25 0 0 1 .276 0c.098.066.15.712.255 2.004l.036.45h2.484a3.56 3.56 0 0 1 3.559 3.559v.613h-1.228v-.613a2.33 2.33 0 0 0-2.331-2.332H12.53c.11 1.243.22 1.965.545 2.557a3.93 3.93 0 0 0 1.519 1.537c.727.41 1.66.5 3.526.682l1.025.1c1.165.114 1.748.17 1.813.268a.24.24 0 0 1-.002.273c-.066.097-.65.144-1.817.24l-.668.054v2.512a3.56 3.56 0 0 1-3.56 3.559h-2.486l-.033.42c-.104 1.31-.157 1.966-.255 2.033a.25.25 0 0 1-.276 0c-.098-.066-.15-.722-.255-2.033l-.033-.42H9.022a3.56 3.56 0 0 1-3.56-3.56v-.613H6.69v.613a2.33 2.33 0 0 0 2.332 2.332h2.452c-.112-1.29-.223-2.034-.557-2.639a3.93 3.93 0 0 0-1.548-1.544c-.74-.406-1.69-.484-3.59-.64l-.92-.074c-1.166-.096-1.75-.143-1.816-.24a.24.24 0 0 1-.002-.273c.065-.097.647-.154 1.813-.268l.609-.06V9.056a3.56 3.56 0 0 1 3.559-3.56h2.549l.036-.449c.105-1.292.157-1.938.255-2.004m5.382 9.495c-1.272.114-2.011.227-2.613.557a3.93 3.93 0 0 0-1.548 1.544c-.334.605-.445 1.349-.557 2.64h2.387a2.33 2.33 0 0 0 2.331-2.332zM9.022 6.723A2.33 2.33 0 0 0 6.69 9.055v2.364c1.342-.138 2.102-.257 2.715-.602a3.93 3.93 0 0 0 1.52-1.537c.323-.592.434-1.314.544-2.557z', + path: 'M11.846 2.047a.275.275 0 0 1 .308 0c.108.073.166.791.283 2.227l.04.499h2.76a3.955 3.955 0 0 1 3.954 3.954v.682h-1.364v-.682a2.59 2.59 0 0 0-2.59-2.59H12.59c.123 1.38.245 2.183.605 2.84a4.37 4.37 0 0 0 1.688 1.708c.808.455 1.845.556 3.918.759l1.14.11c1.294.127 1.942.19 2.014.298a.27.27 0 0 1-.003.304c-.073.107-.722.16-2.018.266l-.743.06v2.79a3.954 3.954 0 0 1-3.955 3.955h-2.762l-.037.467c-.116 1.457-.174 2.185-.283 2.259a.275.275 0 0 1-.308 0c-.108-.073-.166-.802-.282-2.259l-.038-.467H8.691a3.955 3.955 0 0 1-3.955-3.954v-.682H6.1v.682a2.59 2.59 0 0 0 2.59 2.59h2.725c-.124-1.433-.247-2.26-.618-2.932a4.37 4.37 0 0 0-1.72-1.715c-.823-.452-1.878-.538-3.988-.71l-1.023-.084c-1.296-.106-1.945-.16-2.018-.266a.27.27 0 0 1-.003-.304c.072-.108.72-.171 2.015-.297l.676-.066V8.727a3.955 3.955 0 0 1 3.955-3.954h2.832l.04-.5c.117-1.435.175-2.153.283-2.226m5.981 10.55c-1.414.126-2.235.252-2.903.619a4.37 4.37 0 0 0-1.72 1.715c-.372.672-.495 1.499-.62 2.933h2.652a2.59 2.59 0 0 0 2.591-2.591zm-9.136-6.46A2.59 2.59 0 0 0 6.1 8.726v2.627c1.492-.153 2.336-.285 3.017-.669a4.37 4.37 0 0 0 1.688-1.707c.36-.658.483-1.46.605-2.842z', })