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',
})