Skip to content
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

feat(embed): Add support for dark mode #6912

Merged
merged 10 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion bskyembed/snippet/embed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ window.addEventListener('message', event => {
return
}

// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const id = (event.data as {id: string}).id
if (!id) {
return
Expand All @@ -33,6 +34,7 @@ window.addEventListener('message', event => {
return
}

// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const height = (event.data as {height: number}).height
if (height) {
embed.style.height = `${height}px`
Expand All @@ -47,7 +49,7 @@ window.addEventListener('message', event => {
* @returns
*/
function scan(node = document) {
const embeds = node.querySelectorAll('[data-bluesky-uri]')
const embeds = node.querySelectorAll<HTMLIFrameElement>('[data-bluesky-uri]')

for (let i = 0; i < embeds.length; i++) {
const id = String(Math.random()).slice(2)
Expand Down
17 changes: 17 additions & 0 deletions bskyembed/src/color-mode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export function applyTheme(theme: 'light' | 'dark') {
document.documentElement.classList.remove('light', 'dark')
document.documentElement.classList.add(theme)
}

export function initColorMode() {
applyTheme(
window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light',
)
window
.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', mql => {
applyTheme(mql.matches ? 'dark' : 'light')
})
}
2 changes: 1 addition & 1 deletion bskyembed/src/components/container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function Container({
return (
<div
ref={ref}
className="w-full bg-white hover:bg-neutral-50 relative transition-colors max-w-[600px] min-w-[300px] flex border rounded-xl"
className="w-full bg-white text-black hover:bg-neutral-50 dark:bg-dimmedBg dark:hover:bg-dimmedBgLighten relative transition-colors max-w-[600px] min-w-[300px] flex border dark:border-slate-600 dark:text-slate-200 rounded-xl"
onClick={() => {
if (ref.current && href) {
// forwardRef requires preact/compat - let's keep it simple
Expand Down
34 changes: 20 additions & 14 deletions bskyembed/src/components/embed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,17 @@ export function Embed({
return (
<Link
href={`/profile/${record.author.did}/post/${getRkey(record)}`}
className="transition-colors hover:bg-neutral-100 border rounded-lg p-2 gap-1.5 w-full flex flex-col">
className="transition-colors hover:bg-neutral-100 dark:hover:bg-slate-700 border dark:border-slate-600 rounded-lg p-2 gap-1.5 w-full flex flex-col">
<div className="flex gap-1.5 items-center">
<div className="w-4 h-4 overflow-hidden rounded-full bg-neutral-300 shrink-0">
<div className="w-4 h-4 overflow-hidden rounded-full bg-neutral-300 dark:bg-slate-700 shrink-0">
<img
src={record.author.avatar}
style={isAuthorLabeled ? {filter: 'blur(1.5px)'} : undefined}
/>
</div>
<p className="line-clamp-1 text-sm">
<span className="font-bold">{record.author.displayName}</span>
<span className="text-textLight ml-1">
<span className="text-textLight dark:text-textDimmed ml-1">
@{record.author.handle}
</span>
</p>
Expand Down Expand Up @@ -210,7 +210,7 @@ function Info({children}: {children: ComponentChildren}) {
return (
<div className="w-full rounded-lg border py-2 px-2.5 flex-row flex gap-2 bg-neutral-50">
<img src={infoIcon} className="w-4 h-4 shrink-0 mt-0.5" />
<p className="text-sm text-textLight">{children}</p>
<p className="text-sm text-textLight dark:text-textDimmed">{children}</p>
</div>
)
}
Expand Down Expand Up @@ -309,7 +309,7 @@ function ExternalEmbed({
return (
<Link
href={content.external.uri}
className="w-full rounded-lg overflow-hidden border flex flex-col items-stretch"
className="w-full rounded-lg overflow-hidden border dark:border-slate-600 flex flex-col items-stretch"
disableTracking>
{content.external.thumb && (
<img
Expand All @@ -318,11 +318,11 @@ function ExternalEmbed({
/>
)}
<div className="py-3 px-4">
<p className="text-sm text-textLight line-clamp-1">
<p className="text-sm text-textLight dark:text-textDimmed line-clamp-1">
{toNiceDomain(content.external.uri)}
</p>
<p className="font-semibold line-clamp-3">{content.external.title}</p>
<p className="text-sm text-textLight line-clamp-2 mt-0.5">
<p className="text-sm text-textLight dark:text-textDimmed line-clamp-2 mt-0.5">
{content.external.description}
</p>
</div>
Expand All @@ -346,23 +346,29 @@ function GenericWithImageEmbed({
return (
<Link
href={href}
className="w-full rounded-lg border py-2 px-3 flex flex-col gap-2">
className="w-full rounded-lg border dark:border-slate-600 py-2 px-3 flex flex-col gap-2">
<div className="flex gap-2.5 items-center">
{image ? (
<img
src={image}
alt={title}
className="w-8 h-8 rounded-md bg-neutral-300 shrink-0"
className="w-8 h-8 rounded-md bg-neutral-300 dark:bg-slate-700 shrink-0"
/>
) : (
<div className="w-8 h-8 rounded-md bg-brand shrink-0" />
)}
<div className="flex-1">
<p className="font-bold text-sm">{title}</p>
<p className="text-textLight text-sm">{subtitle}</p>
<p className="text-textLight dark:text-textDimmed text-sm">
{subtitle}
</p>
</div>
</div>
{description && <p className="text-textLight text-sm">{description}</p>}
{description && (
<p className="text-textLight dark:text-textDimmed text-sm">
{description}
</p>
)}
</Link>
)
}
Expand Down Expand Up @@ -407,7 +413,7 @@ function StarterPackEmbed({
return (
<Link
href={starterPackHref}
className="w-full rounded-lg overflow-hidden border flex flex-col items-stretch">
className="w-full rounded-lg overflow-hidden border dark:border-slate-600 flex flex-col items-stretch">
<img src={imageUri} className="aspect-[1.91/1] object-cover" />
<div className="py-3 px-4">
<div className="flex space-x-2 items-center">
Expand All @@ -416,7 +422,7 @@ function StarterPackEmbed({
<p className="font-semibold leading-[21px]">
{content.record.name}
</p>
<p className="text-sm text-textLight line-clamp-2 leading-[18px]">
<p className="text-sm text-textLight dark:text-textDimmed line-clamp-2 leading-[18px]">
Starter pack by{' '}
{content.creator.displayName || `@${content.creator.handle}`}
</p>
Expand All @@ -426,7 +432,7 @@ function StarterPackEmbed({
<p className="text-sm mt-1">{content.record.description}</p>
)}
{!!content.joinedAllTimeCount && content.joinedAllTimeCount > 50 && (
<p className="text-sm font-semibold text-textLight mt-1">
<p className="text-sm font-semibold text-textLight dark:text-textDimmed mt-1">
{content.joinedAllTimeCount} users have joined!
</p>
)}
Expand Down
18 changes: 10 additions & 8 deletions bskyembed/src/components/post.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function Post({thread}: Props) {
<div className="flex-1 flex-col flex gap-2" lang={record?.langs?.[0]}>
<div className="flex gap-2.5 items-center cursor-pointer">
<Link href={`/profile/${post.author.did}`} className="rounded-full">
<div className="w-10 h-10 overflow-hidden rounded-full bg-neutral-300 shrink-0">
<div className="w-10 h-10 overflow-hidden rounded-full bg-neutral-300 dark:bg-slate-700 shrink-0">
<img
src={post.author.avatar}
style={isAuthorLabeled ? {filter: 'blur(2.5px)'} : undefined}
Expand All @@ -53,7 +53,7 @@ export function Post({thread}: Props) {
</Link>
<Link
href={`/profile/${post.author.did}`}
className="text-[15px] text-textLight hover:underline line-clamp-1">
className="text-[15px] text-textLight dark:text-textDimmed hover:underline line-clamp-1">
<p>@{post.author.handle}</p>
</Link>
</div>
Expand All @@ -69,33 +69,35 @@ export function Post({thread}: Props) {
<Link href={href}>
<time
datetime={new Date(post.indexedAt).toISOString()}
className="text-textLight mt-1 text-sm hover:underline">
className="text-textLight dark:text-textDimmed mt-1 text-sm hover:underline">
{niceDate(post.indexedAt)}
</time>
</Link>
<div className="border-t w-full pt-2.5 flex items-center gap-5 text-sm cursor-pointer">
<div className="border-t dark:border-slate-600 w-full pt-2.5 flex items-center gap-5 text-sm cursor-pointer">
{!!post.likeCount && (
<div className="flex items-center gap-2 cursor-pointer">
<img src={likeIcon} className="w-5 h-5" />
<p className="font-bold text-neutral-500 mb-px">
<p className="font-bold text-neutral-500 dark:text-neutral-300 mb-px">
{prettyNumber(post.likeCount)}
</p>
</div>
)}
{!!post.repostCount && (
<div className="flex items-center gap-2 cursor-pointer">
<img src={repostIcon} className="w-5 h-5" />
<p className="font-bold text-neutral-500 mb-px">
<p className="font-bold text-neutral-500 dark:text-neutral-300 mb-px">
{prettyNumber(post.repostCount)}
</p>
</div>
)}
<div className="flex items-center gap-2 cursor-pointer">
<img src={replyIcon} className="w-5 h-5" />
<p className="font-bold text-neutral-500 mb-px">Reply</p>
<p className="font-bold text-neutral-500 dark:text-neutral-300 mb-px">
Reply
</p>
</div>
<div className="flex-1" />
<p className="cursor-pointer text-brand font-bold hover:underline hidden min-[450px]:inline">
<p className="cursor-pointer text-brand dark:text-brandLighten font-bold hover:underline hidden min-[450px]:inline">
{post.replyCount
? `Read ${prettyNumber(post.replyCount)} ${
post.replyCount > 1 ? 'replies' : 'reply'
Expand Down
4 changes: 4 additions & 0 deletions bskyembed/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@
.break-word {
word-break: break-word;
}

:root {
color-scheme: light dark;
}
31 changes: 18 additions & 13 deletions bskyembed/src/screens/landing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {useEffect, useMemo, useRef, useState} from 'preact/hooks'

import arrowBottom from '../../assets/arrowBottom_stroke2_corner0_rounded.svg'
import logo from '../../assets/logo.svg'
import {initColorMode} from '../color-mode'
import {Container} from '../components/container'
import {Link} from '../components/link'
import {Post} from '../components/post'
Expand All @@ -21,6 +22,8 @@ export const EMBED_SCRIPT = `${EMBED_SERVICE}/static/embed.js`
const root = document.getElementById('app')
if (!root) throw new Error('No root element')

initColorMode()

const agent = new BskyAgent({
service: 'https://public.api.bsky.app',
})
Expand Down Expand Up @@ -108,7 +111,7 @@ function LandingPage() {
}, [uri])

return (
<main className="w-full min-h-screen flex flex-col items-center gap-8 py-14 px-4 md:pt-32">
<main className="w-full min-h-screen flex flex-col items-center gap-8 py-14 px-4 md:pt-32 dark:bg-dimmedBgDarken dark:text-slate-200">
<Link
href="https://bsky.social/about"
className="transition-transform hover:scale-110">
Expand All @@ -121,20 +124,22 @@ function LandingPage() {
type="text"
value={uri}
onInput={e => setUri(e.currentTarget.value)}
className="border rounded-lg py-3 w-full max-w-[600px] px-4"
className="border rounded-lg py-3 w-full max-w-[600px] px-4 dark:bg-dimmedBg dark:border-slate-500"
placeholder={DEFAULT_POST}
/>

<img src={arrowBottom} className="w-6" />
<img src={arrowBottom} className="w-6 dark:invert" />

{loading ? (
<Skeleton />
<div className="w-full max-w-[600px]">
<Skeleton />
</div>
) : (
<div className="w-full max-w-[600px] gap-8 flex flex-col">
{!error && thread && uri && <Snippet thread={thread} />}
{!error && thread && <Post thread={thread} key={thread.post.uri} />}
{error && (
<div className="w-full border border-red-500 bg-red-50 px-4 py-3 rounded-lg">
<div className="w-full border border-red-500 bg-red-500/10 px-4 py-3 rounded-lg">
<p className="text-red-500 text-center">{error}</p>
</div>
)}
Expand All @@ -149,15 +154,15 @@ function Skeleton() {
<Container>
<div className="flex-1 flex-col flex gap-2 pb-8">
<div className="flex gap-2.5 items-center">
<div className="w-10 h-10 overflow-hidden rounded-full bg-neutral-100 shrink-0 animate-pulse" />
<div className="w-10 h-10 overflow-hidden rounded-full bg-neutral-100 dark:bg-slate-700 shrink-0 animate-pulse" />
<div className="flex-1">
<div className="bg-neutral-100 animate-pulse w-64 h-4 rounded" />
<div className="bg-neutral-100 animate-pulse w-32 h-3 mt-1 rounded" />
<div className="bg-neutral-100 dark:bg-slate-700 animate-pulse w-64 h-4 rounded" />
<div className="bg-neutral-100 dark:bg-slate-700 animate-pulse w-32 h-3 mt-1 rounded" />
</div>
</div>
<div className="w-full h-4 mt-2 bg-neutral-100 rounded animate-pulse" />
<div className="w-5/6 h-4 bg-neutral-100 rounded animate-pulse" />
<div className="w-3/4 h-4 bg-neutral-100 rounded animate-pulse" />
<div className="w-full h-4 mt-2 bg-neutral-100 dark:bg-slate-700 rounded animate-pulse" />
<div className="w-5/6 h-4 bg-neutral-100 dark:bg-slate-700 rounded animate-pulse" />
<div className="w-3/4 h-4 bg-neutral-100 dark:bg-slate-700 rounded animate-pulse" />
</div>
</Container>
)
Expand Down Expand Up @@ -220,15 +225,15 @@ function Snippet({thread}: {thread: AppBskyFeedDefs.ThreadViewPost}) {
ref={ref}
type="text"
value={snippet}
className="border rounded-lg py-3 w-full px-4"
className="border rounded-lg py-3 w-full px-4 dark:bg-dimmedBg dark:border-slate-500"
readOnly
autoFocus
onFocus={() => {
ref.current?.select()
}}
/>
<button
className="rounded-lg bg-brand text-white color-white py-3 px-4 whitespace-nowrap min-w-28"
className="rounded-lg bg-brand text-white py-3 px-4 whitespace-nowrap min-w-28"
onClick={() => {
ref.current?.focus()
ref.current?.select()
Expand Down
9 changes: 6 additions & 3 deletions bskyembed/src/screens/post.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {AppBskyFeedDefs, AtpAgent} from '@atproto/api'
import {h, render} from 'preact'

import logo from '../../assets/logo.svg'
import {initColorMode} from '../color-mode'
import {Container} from '../components/container'
import {Link} from '../components/link'
import {Post} from '../components/post'
Expand All @@ -21,6 +22,8 @@ if (!uri) {
throw new Error('No uri in path')
}

initColorMode()

agent
.getPostThread({
uri,
Expand Down Expand Up @@ -55,13 +58,13 @@ function PwiOptOut({thread}: {thread: AppBskyFeedDefs.ThreadViewPost}) {
<img src={logo} className="h-6" />
</Link>
<div className="w-full py-12 gap-4 flex flex-col items-center">
<p className="max-w-80 text-center w-full text-textLight">
<p className="max-w-80 text-center w-full text-textLight dark:text-textDimmed">
The author of this post has requested their posts not be displayed on
external sites.
</p>
<Link
href={href}
className="max-w-80 rounded-lg bg-brand text-white color-white text-center py-1 px-4 w-full mx-auto">
className="max-w-80 rounded-lg bg-brand text-white text-center py-1 px-4 w-full mx-auto">
View on Bluesky
</Link>
</div>
Expand All @@ -77,7 +80,7 @@ function ErrorMessage() {
className="transition-transform hover:scale-110 absolute top-4 right-4">
<img src={logo} className="h-6" />
</Link>
<p className="my-16 text-center w-full text-textLight">
<p className="my-16 text-center w-full text-textLight dark:text-textDimmed">
Post not found, it may have been deleted.
</p>
</Container>
Expand Down
8 changes: 8 additions & 0 deletions bskyembed/tailwind.config.cjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
darkMode: ['variant', [
'&:is(.dark *):not(:is(.dark .light *))',
]],
theme: {
extend: {
colors: {
brand: 'rgb(10,122,255)',
brandLighten: 'rgb(32,139,254)',
textLight: 'rgb(66,87,108)',
textDimmed: 'rgb(174,187,201)',
dimmedBgLighten: 'rgb(30,41,54)',
dimmedBg: 'rgb(22,30,39)',
dimmedBgDarken: 'rgb(18,25,32)',
},
},
},
Expand Down
Loading
Loading