From f5f02aa4b36ba85c0287324e1654bccaf19cfd5f Mon Sep 17 00:00:00 2001
From: Samuel Newman <mozzius@protonmail.com>
Date: Wed, 5 Mar 2025 17:50:20 +0000
Subject: Add dark mode option to in-app embed snippet dialog (#7377)

* add color theme selection

* change chevron direction

* change chevron direction again

* rm unused ref
---
 src/components/dialogs/Embed.tsx | 184 ++++++++++++++++++++++++++-------------
 1 file changed, 125 insertions(+), 59 deletions(-)

(limited to 'src/components/dialogs/Embed.tsx')

diff --git a/src/components/dialogs/Embed.tsx b/src/components/dialogs/Embed.tsx
index ca75b0139..23d050488 100644
--- a/src/components/dialogs/Embed.tsx
+++ b/src/components/dialogs/Embed.tsx
@@ -1,5 +1,5 @@
-import React, {memo, useRef, useState} from 'react'
-import {TextInput, View} from 'react-native'
+import {memo, useEffect, useMemo, useState} from 'react'
+import {View} from 'react-native'
 import {AppBskyActorDefs, AppBskyFeedPost, AtUri} from '@atproto/api'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
@@ -8,12 +8,19 @@ import {EMBED_SCRIPT} from '#/lib/constants'
 import {niceDate} from '#/lib/strings/time'
 import {toShareUrl} from '#/lib/strings/url-helpers'
 import {atoms as a, useTheme} from '#/alf'
+import {Button, ButtonIcon, ButtonText} from '#/components/Button'
 import * as Dialog from '#/components/Dialog'
 import * as TextField from '#/components/forms/TextField'
-import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
-import {CodeBrackets_Stroke2_Corner0_Rounded as CodeBrackets} from '#/components/icons/CodeBrackets'
+import * as ToggleButton from '#/components/forms/ToggleButton'
+import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check'
+import {
+  ChevronBottom_Stroke2_Corner0_Rounded as ChevronBottomIcon,
+  ChevronRight_Stroke2_Corner0_Rounded as ChevronRightIcon,
+} from '#/components/icons/Chevron'
+import {CodeBrackets_Stroke2_Corner0_Rounded as CodeBracketsIcon} from '#/components/icons/CodeBrackets'
 import {Text} from '#/components/Typography'
-import {Button, ButtonIcon, ButtonText} from '../Button'
+
+export type ColorModeValues = 'system' | 'light' | 'dark'
 
 type EmbedDialogProps = {
   control: Dialog.DialogControlProps
@@ -44,11 +51,12 @@ function EmbedDialogInner({
 }: Omit<EmbedDialogProps, 'control'>) {
   const t = useTheme()
   const {_, i18n} = useLingui()
-  const ref = useRef<TextInput>(null)
   const [copied, setCopied] = useState(false)
+  const [showCustomisation, setShowCustomisation] = useState(false)
+  const [colorMode, setColorMode] = useState<ColorModeValues>('system')
 
   // reset copied state after 2 seconds
-  React.useEffect(() => {
+  useEffect(() => {
     if (copied) {
       const timeout = setTimeout(() => {
         setCopied(false)
@@ -57,7 +65,7 @@ function EmbedDialogInner({
     }
   }, [copied])
 
-  const snippet = React.useMemo(() => {
+  const snippet = useMemo(() => {
     function toEmbedUrl(href: string) {
       return toShareUrl(href) + '?ref_src=embed'
     }
@@ -75,9 +83,11 @@ function EmbedDialogInner({
     // x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x
     return `<blockquote class="bluesky-embed" data-bluesky-uri="${escapeHtml(
       postUri,
-    )}" data-bluesky-cid="${escapeHtml(postCid)}"><p lang="${escapeHtml(
-      lang,
-    )}">${escapeHtml(record.text)}${
+    )}" data-bluesky-cid="${escapeHtml(
+      postCid,
+    )}" data-bluesky-embed-color-mode="${escapeHtml(
+      colorMode,
+    )}"><p lang="${escapeHtml(lang)}">${escapeHtml(record.text)}${
       record.embed
         ? `<br><br><a href="${escapeHtml(href)}">[image or embed]</a>`
         : ''
@@ -88,60 +98,116 @@ function EmbedDialogInner({
     )}</a>) <a href="${escapeHtml(href)}">${escapeHtml(
       niceDate(i18n, timestamp),
     )}</a></blockquote><script async src="${EMBED_SCRIPT}" charset="utf-8"></script>`
-  }, [i18n, postUri, postCid, record, timestamp, postAuthor])
+  }, [i18n, postUri, postCid, record, timestamp, postAuthor, colorMode])
 
   return (
-    <Dialog.Inner label="Embed post" style={[a.gap_md, {maxWidth: 500}]}>
-      <View style={[a.gap_sm, a.pb_lg]}>
-        <Text style={[a.text_2xl, a.font_bold]}>
-          <Trans>Embed post</Trans>
-        </Text>
-        <Text
-          style={[a.text_md, t.atoms.text_contrast_medium, a.leading_normal]}>
-          <Trans>
-            Embed this post in your website. Simply copy the following snippet
-            and paste it into the HTML code of your website.
-          </Trans>
-        </Text>
-      </View>
-
-      <View style={[a.flex_row, a.gap_sm]}>
-        <View style={[a.flex_1]}>
-          <TextField.Root>
-            <TextField.Icon icon={CodeBrackets} />
-            <TextField.Input
-              label={_(msg`Embed HTML code`)}
-              editable={false}
-              selection={{start: 0, end: snippet.length}}
-              value={snippet}
-              style={{}}
-            />
-          </TextField.Root>
+    <Dialog.Inner label="Embed post" style={[{maxWidth: 500}]}>
+      <View style={[a.gap_lg]}>
+        <View style={[a.gap_sm]}>
+          <Text style={[a.text_2xl, a.font_heavy]}>
+            <Trans>Embed post</Trans>
+          </Text>
+          <Text
+            style={[a.text_md, t.atoms.text_contrast_medium, a.leading_normal]}>
+            <Trans>
+              Embed this post in your website. Simply copy the following snippet
+              and paste it into the HTML code of your website.
+            </Trans>
+          </Text>
         </View>
-        <Button
-          label={_(msg`Copy code`)}
-          color="primary"
-          variant="solid"
-          size="large"
-          onPress={() => {
-            ref.current?.focus()
-            ref.current?.setSelection(0, snippet.length)
-            navigator.clipboard.writeText(snippet)
-            setCopied(true)
-          }}>
-          {copied ? (
-            <>
-              <ButtonIcon icon={Check} />
-              <ButtonText>
-                <Trans>Copied!</Trans>
-              </ButtonText>
-            </>
-          ) : (
+        <View
+          style={[
+            a.border,
+            t.atoms.border_contrast_low,
+            a.rounded_sm,
+            a.overflow_hidden,
+          ]}>
+          <Button
+            label={
+              showCustomisation
+                ? _(msg`Hide customization options`)
+                : _(msg`Show customization options`)
+            }
+            color="secondary"
+            variant="ghost"
+            size="small"
+            shape="default"
+            onPress={() => setShowCustomisation(c => !c)}
+            style={[
+              a.justify_start,
+              showCustomisation && t.atoms.bg_contrast_25,
+            ]}>
+            <ButtonIcon
+              icon={showCustomisation ? ChevronBottomIcon : ChevronRightIcon}
+            />
             <ButtonText>
-              <Trans>Copy code</Trans>
+              <Trans>Customization options</Trans>
             </ButtonText>
+          </Button>
+
+          {showCustomisation && (
+            <View style={[a.gap_sm, a.p_md]}>
+              <Text style={[t.atoms.text_contrast_medium, a.font_bold]}>
+                <Trans>Color theme</Trans>
+              </Text>
+              <ToggleButton.Group
+                label={_(msg`Color mode`)}
+                values={[colorMode]}
+                onChange={([value]) => setColorMode(value as ColorModeValues)}>
+                <ToggleButton.Button name="system" label={_(msg`System`)}>
+                  <ToggleButton.ButtonText>
+                    <Trans>System</Trans>
+                  </ToggleButton.ButtonText>
+                </ToggleButton.Button>
+                <ToggleButton.Button name="light" label={_(msg`Light`)}>
+                  <ToggleButton.ButtonText>
+                    <Trans>Light</Trans>
+                  </ToggleButton.ButtonText>
+                </ToggleButton.Button>
+                <ToggleButton.Button name="dark" label={_(msg`Dark`)}>
+                  <ToggleButton.ButtonText>
+                    <Trans>Dark</Trans>
+                  </ToggleButton.ButtonText>
+                </ToggleButton.Button>
+              </ToggleButton.Group>
+            </View>
           )}
-        </Button>
+        </View>
+        <View style={[a.flex_row, a.gap_sm]}>
+          <View style={[a.flex_1]}>
+            <TextField.Root>
+              <TextField.Icon icon={CodeBracketsIcon} />
+              <TextField.Input
+                label={_(msg`Embed HTML code`)}
+                editable={false}
+                selection={{start: 0, end: snippet.length}}
+                value={snippet}
+              />
+            </TextField.Root>
+          </View>
+          <Button
+            label={_(msg`Copy code`)}
+            color="primary"
+            variant="solid"
+            size="large"
+            onPress={() => {
+              navigator.clipboard.writeText(snippet)
+              setCopied(true)
+            }}>
+            {copied ? (
+              <>
+                <ButtonIcon icon={CheckIcon} />
+                <ButtonText>
+                  <Trans>Copied!</Trans>
+                </ButtonText>
+              </>
+            ) : (
+              <ButtonText>
+                <Trans>Copy code</Trans>
+              </ButtonText>
+            )}
+          </Button>
+        </View>
       </View>
       <Dialog.Close />
     </Dialog.Inner>
-- 
cgit 1.4.1

