From 1de20e1e35a5a0d879a20e1d242dd855dc1b7310 Mon Sep 17 00:00:00 2001 From: Amir Angel <36531255+17Amir17@users.noreply.github.com> Date: Wed, 14 Feb 2024 17:21:50 +0200 Subject: [PATCH 1/8] fix: add toolbar and merge theme --- example/src/Examples/Basic.tsx | 4 + src/RichText/Toolbar/EditLinkBar.tsx | 69 +++++----------- src/RichText/Toolbar/Toolbar.tsx | 58 +++---------- src/RichText/Toolbar/toolbarTheme.ts | 118 +++++++++++++++++++++++++++ src/RichText/theme.ts | 6 ++ src/RichText/useEditorBridge.tsx | 14 +++- src/bridges/core.ts | 5 +- src/types/Theme.ts | 35 ++++++++ src/types/index.ts | 1 + 9 files changed, 212 insertions(+), 98 deletions(-) create mode 100644 src/RichText/Toolbar/toolbarTheme.ts create mode 100644 src/RichText/theme.ts create mode 100644 src/types/Theme.ts diff --git a/example/src/Examples/Basic.tsx b/example/src/Examples/Basic.tsx index ff6405b..4249883 100644 --- a/example/src/Examples/Basic.tsx +++ b/example/src/Examples/Basic.tsx @@ -8,12 +8,16 @@ import { StyleSheet, } from 'react-native'; import { RichText, Toolbar, useEditorBridge } from '@10play/tentap-editor'; +import { darkToolbarTheme } from '../../../src/RichText/Toolbar/toolbarTheme'; export const Basic = ({}: NativeStackScreenProps) => { const editor = useEditorBridge({ autofocus: true, avoidIosKeyboard: true, initialContent, + theme: { + toolbar: darkToolbarTheme, + }, }); return ( diff --git a/src/RichText/Toolbar/EditLinkBar.tsx b/src/RichText/Toolbar/EditLinkBar.tsx index 21e5e68..d46d822 100644 --- a/src/RichText/Toolbar/EditLinkBar.tsx +++ b/src/RichText/Toolbar/EditLinkBar.tsx @@ -1,50 +1,10 @@ import React from 'react'; -import { - View, - TextInput, - TouchableOpacity, - StyleSheet, - Text, - Image, -} from 'react-native'; -import { toolbarStyles } from './Toolbar'; +import { View, TextInput, TouchableOpacity, Text, Image } from 'react-native'; import { Images } from '../../assets'; - -const linkBarStyles = StyleSheet.create({ - addLinkContainer: { - flex: 1, - flexDirection: 'row', - height: 44, - borderTopWidth: 1, - borderTopColor: '#e0e0e0', - padding: 4, - paddingHorizontal: 8, - alignItems: 'center', - justifyContent: 'center', - }, - linkInput: { - paddingLeft: 12, - paddingTop: 1, - paddingBottom: 1, - paddingRight: 12, - flex: 1, - }, - doneButton: { - backgroundColor: '#F5F5F5', - justifyContent: 'center', - height: 32, - padding: 8, - borderRadius: 4, - }, - doneButtonText: { - color: '#0085FF', - }, - linkToolbarButton: { - paddingHorizontal: 0, - }, -}); +import type { EditorTheme } from '../../types'; interface EditLinkBarProps { + theme: EditorTheme; onBlur: () => void; onEditLink: (newLink: string) => void; onLinkIconClick: () => void; @@ -52,6 +12,7 @@ interface EditLinkBarProps { } export const EditLinkBar = ({ + theme, initialLink, onEditLink, onLinkIconClick, @@ -59,15 +20,20 @@ export const EditLinkBar = ({ }: EditLinkBarProps) => { const [link, setLink] = React.useState(initialLink || ''); return ( - + - + @@ -77,17 +43,20 @@ export const EditLinkBar = ({ onBlur={onBlur} onChangeText={setLink} placeholder="Type your URL here..." + placeholderTextColor={ + theme.toolbar.linkBarTheme.linkInput.placeholderTextColor + } autoFocus - style={linkBarStyles.linkInput} + style={theme.toolbar.linkBarTheme.linkInput} autoCapitalize="none" /> { onEditLink(link); }} > - Insert + Insert ); diff --git a/src/RichText/Toolbar/Toolbar.tsx b/src/RichText/Toolbar/Toolbar.tsx index 4babc40..526ba1a 100644 --- a/src/RichText/Toolbar/Toolbar.tsx +++ b/src/RichText/Toolbar/Toolbar.tsx @@ -23,44 +23,7 @@ interface ToolbarProps { items?: ToolbarItem[]; } -export const toolbarStyles = StyleSheet.create({ - toolbar: { - flex: 1, - borderTopWidth: 0.5, - borderTopColor: '#DEE0E3', - minWidth: '100%', - height: 44, - }, - toolbarButton: { - paddingHorizontal: 8, - backgroundColor: 'white', - alignItems: 'center', - justifyContent: 'center', - }, - disabled: { - opacity: 0.3, - tintColor: '#CACACA', - }, - active: { - backgroundColor: '#E5E5E5', - }, - hidden: { - display: 'none', - }, - keyboardAvoidingView: { - position: 'absolute', - width: '100%', - bottom: 0, - }, - iconWrapper: { - borderRadius: 4, - }, - icon: { - height: 28, - width: 28, - tintColor: '#898989', - }, -}); +export const toolbarStyles = StyleSheet.create({}); export enum ToolbarContext { Main, @@ -96,26 +59,30 @@ export function Toolbar({ { return ( @@ -128,6 +95,7 @@ export function Toolbar({ case ToolbarContext.Link: return ( setToolbarContext(ToolbarContext.Main)} onLinkIconClick={() => { diff --git a/src/RichText/Toolbar/toolbarTheme.ts b/src/RichText/Toolbar/toolbarTheme.ts new file mode 100644 index 0000000..f6f89d6 --- /dev/null +++ b/src/RichText/Toolbar/toolbarTheme.ts @@ -0,0 +1,118 @@ +import type { ToolbarTheme } from '../../types'; + +export const defaultToolbarTheme: ToolbarTheme = { + toolbarBody: { + flex: 1, + borderTopWidth: 0.5, + borderTopColor: '#DEE0E3', + backgroundColor: 'white', + minWidth: '100%', + height: 44, + }, + toolbarButton: { + paddingHorizontal: 8, + backgroundColor: 'white', + alignItems: 'center', + justifyContent: 'center', + }, + iconDisabled: { + tintColor: '#CACACA', + }, + iconWrapperDisabled: { + opacity: 0.3, + }, + iconWrapperActive: { + backgroundColor: '#E5E5E5', + }, + hidden: { + display: 'none', + }, + keyboardAvoidingView: { + position: 'absolute', + width: '100%', + bottom: 0, + }, + iconWrapper: { + borderRadius: 4, + }, + icon: { + height: 28, + width: 28, + tintColor: '#898989', + }, + iconActive: {}, + linkBarTheme: { + addLinkContainer: { + flex: 1, + flexDirection: 'row', + height: 44, + borderTopWidth: 1, + borderTopColor: '#e0e0e0', + backgroundColor: 'white', + padding: 4, + paddingHorizontal: 8, + alignItems: 'center', + justifyContent: 'center', + }, + linkInput: { + paddingLeft: 12, + paddingTop: 1, + paddingBottom: 1, + paddingRight: 12, + flex: 1, + }, + doneButton: { + backgroundColor: '#F5F5F5', + justifyContent: 'center', + height: 32, + padding: 8, + borderRadius: 4, + }, + doneButtonText: { + color: '#0085FF', + }, + linkToolbarButton: { + paddingHorizontal: 0, + }, + }, +}; + +export const darkToolbarTheme: Partial = { + toolbarBody: { + borderTopColor: '#C6C6C6', + backgroundColor: '#474747', + }, + toolbarButton: { + backgroundColor: '#474747', + }, + iconDisabled: { + tintColor: '#CACACA', + }, + iconWrapperActive: { + backgroundColor: '#8E8E93', + }, + hidden: { + display: 'none', + }, + icon: { + tintColor: 'white', + }, + linkBarTheme: { + addLinkContainer: { + backgroundColor: '#474747', + borderTopColor: '#939394', + }, + linkInput: { + backgroundColor: '#474747', + color: 'white', + placeholderTextColor: '#B2B2B8', + }, + doneButton: { + backgroundColor: '#0085FF', + }, + doneButtonText: { + color: 'white', + }, + linkToolbarButton: {}, + }, +}; diff --git a/src/RichText/theme.ts b/src/RichText/theme.ts new file mode 100644 index 0000000..c77df15 --- /dev/null +++ b/src/RichText/theme.ts @@ -0,0 +1,6 @@ +import type { EditorTheme } from '../types'; +import { defaultToolbarTheme } from './Toolbar/toolbarTheme'; + +export const defaultTheme: EditorTheme = { + toolbar: defaultToolbarTheme, +}; diff --git a/src/RichText/useEditorBridge.tsx b/src/RichText/useEditorBridge.tsx index c680901..6bad07a 100644 --- a/src/RichText/useEditorBridge.tsx +++ b/src/RichText/useEditorBridge.tsx @@ -1,17 +1,22 @@ import { useMemo, useRef } from 'react'; import WebView from 'react-native-webview'; +import merge from 'lodash/merge'; import { type EditorActionMessage, EditorMessageType, } from '../types/Messaging'; import { type BridgeState } from '../types/EditorBridge'; import { EditorHelper } from './EditorHelper'; -import type { EditorBridge } from '../types'; +import type { EditorBridge, EditorTheme } from '../types'; import type BridgeExtension from '../bridges/base'; import { TenTapStartKit } from '../bridges/StarterKit'; import { uniqueBy } from '../utils'; +import { defaultTheme } from './theme'; type Subscription = (cb: (val: T) => void) => () => void; +type RecursivePartial = { + [P in keyof T]?: RecursivePartial; +}; export const useEditorBridge = (options?: { bridgeExtensions?: BridgeExtension[]; @@ -21,6 +26,7 @@ export const useEditorBridge = (options?: { customSource?: string; DEV?: boolean; DEV_SERVER_URL?: string; + theme?: RecursivePartial; }): EditorBridge => { const webviewRef = useRef(null); // Till we will implement default per bridgeExtension @@ -33,6 +39,11 @@ export const useEditorBridge = (options?: { return uniqueBy(extensions, 'name'); }, [options?.bridgeExtensions]); + const mergedTheme = useMemo( + () => merge(defaultTheme, options?.theme), + [options?.theme] + ); + const _updateEditorState = (editorState: BridgeState) => { editorStateRef.current = editorState; editorStateSubsRef.current.forEach((sub) => sub(editorState)); @@ -73,6 +84,7 @@ export const useEditorBridge = (options?: { DEV_SERVER_URL: options?.DEV_SERVER_URL, DEV: options?.DEV, webviewRef, + theme: mergedTheme, getEditorState, _updateEditorState, _subscribeToEditorStateUpdate, diff --git a/src/bridges/core.ts b/src/bridges/core.ts index 3a149e9..e74e359 100644 --- a/src/bridges/core.ts +++ b/src/bridges/core.ts @@ -1,6 +1,6 @@ import BridgeExtension from './base'; import { asyncMessages } from '../RichText/AsyncMessages'; -import type { BridgeState } from '../types'; +import type { BridgeState, EditorTheme } from '../types'; import { focusListener } from '../webEditorUtils/focusListener'; import Document from '@tiptap/extension-document'; import Paragraph from '@tiptap/extension-paragraph'; @@ -20,6 +20,7 @@ type CoreEditorInstance = { setSelection: (from: number, to: number) => void; updateScrollThresholdAndMargin: (offset: number) => void; focus: (pos: focusArgs) => void; + theme: EditorTheme; }; declare module '../types/EditorBridge' { @@ -86,7 +87,7 @@ export type CoreMessages = export const CoreBridge = new BridgeExtension< CoreEditorState, - CoreEditorInstance, + Omit, CoreMessages >({ forceName: 'coreBridge', diff --git a/src/types/Theme.ts b/src/types/Theme.ts new file mode 100644 index 0000000..637af7e --- /dev/null +++ b/src/types/Theme.ts @@ -0,0 +1,35 @@ +import type { + ImageStyle, + ViewStyle, + StyleProp, + TextStyle, + ColorValue, +} from 'react-native'; + +export interface EditorTheme { + toolbar: ToolbarTheme; +} + +export type ToolbarTheme = { + toolbarBody: StyleProp; + toolbarButton: StyleProp; + iconDisabled: StyleProp; + iconActive: StyleProp; + icon: StyleProp; + iconWrapper: StyleProp; + iconWrapperDisabled: StyleProp; + iconWrapperActive: StyleProp; + hidden: StyleProp; + keyboardAvoidingView: StyleProp; + linkBarTheme: LinkBarTheme; +}; + +export type LinkBarTheme = { + addLinkContainer: StyleProp; + linkInput: StyleProp & { + placeholderTextColor?: ColorValue; + }; + doneButton: StyleProp; + doneButtonText: StyleProp; + linkToolbarButton: StyleProp; +}; diff --git a/src/types/index.ts b/src/types/index.ts index 4f83d9d..8f63421 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,3 +1,4 @@ export * from './EditorBridge'; export * from './Messaging'; export * from './Actions'; +export * from './Theme'; From 444abfd16cef3eb0627ee1a64054b79e430912c2 Mon Sep 17 00:00:00 2001 From: Amir Angel <36531255+17Amir17@users.noreply.github.com> Date: Wed, 14 Feb 2024 17:43:59 +0200 Subject: [PATCH 2/8] fix: some styling --- src/RichText/Toolbar/EditLinkBar.tsx | 4 +--- src/RichText/Toolbar/toolbarTheme.ts | 10 ++++++++-- src/types/Theme.ts | 5 ++--- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/RichText/Toolbar/EditLinkBar.tsx b/src/RichText/Toolbar/EditLinkBar.tsx index d46d822..988af70 100644 --- a/src/RichText/Toolbar/EditLinkBar.tsx +++ b/src/RichText/Toolbar/EditLinkBar.tsx @@ -43,9 +43,7 @@ export const EditLinkBar = ({ onBlur={onBlur} onChangeText={setLink} placeholder="Type your URL here..." - placeholderTextColor={ - theme.toolbar.linkBarTheme.linkInput.placeholderTextColor - } + placeholderTextColor={theme.toolbar.linkBarTheme.placeholderTextColor} autoFocus style={theme.toolbar.linkBarTheme.linkInput} autoCapitalize="none" diff --git a/src/RichText/Toolbar/toolbarTheme.ts b/src/RichText/Toolbar/toolbarTheme.ts index f6f89d6..489dccd 100644 --- a/src/RichText/Toolbar/toolbarTheme.ts +++ b/src/RichText/Toolbar/toolbarTheme.ts @@ -4,7 +4,9 @@ export const defaultToolbarTheme: ToolbarTheme = { toolbarBody: { flex: 1, borderTopWidth: 0.5, + borderBottomWidth: 0.5, borderTopColor: '#DEE0E3', + borderBottomColor: '#DEE0E3', backgroundColor: 'white', minWidth: '100%', height: 44, @@ -46,8 +48,10 @@ export const defaultToolbarTheme: ToolbarTheme = { flex: 1, flexDirection: 'row', height: 44, - borderTopWidth: 1, + borderTopWidth: 0.5, + borderBottomWidth: 0.5, borderTopColor: '#e0e0e0', + borderBottomColor: '#e0e0e0', backgroundColor: 'white', padding: 4, paddingHorizontal: 8, @@ -80,6 +84,7 @@ export const defaultToolbarTheme: ToolbarTheme = { export const darkToolbarTheme: Partial = { toolbarBody: { borderTopColor: '#C6C6C6', + borderBottomColor: '#C6C6C6', backgroundColor: '#474747', }, toolbarButton: { @@ -101,12 +106,13 @@ export const darkToolbarTheme: Partial = { addLinkContainer: { backgroundColor: '#474747', borderTopColor: '#939394', + borderBottomColor: '#939394', }, linkInput: { backgroundColor: '#474747', color: 'white', - placeholderTextColor: '#B2B2B8', }, + placeholderTextColor: '#B2B2B8', doneButton: { backgroundColor: '#0085FF', }, diff --git a/src/types/Theme.ts b/src/types/Theme.ts index 637af7e..30b8a61 100644 --- a/src/types/Theme.ts +++ b/src/types/Theme.ts @@ -26,9 +26,8 @@ export type ToolbarTheme = { export type LinkBarTheme = { addLinkContainer: StyleProp; - linkInput: StyleProp & { - placeholderTextColor?: ColorValue; - }; + linkInput: StyleProp; + placeholderTextColor?: ColorValue; doneButton: StyleProp; doneButtonText: StyleProp; linkToolbarButton: StyleProp; From 45a558b684b0e67c33869a72a9864323c52a2ae2 Mon Sep 17 00:00:00 2001 From: Amir Angel <36531255+17Amir17@users.noreply.github.com> Date: Wed, 14 Feb 2024 19:12:56 +0200 Subject: [PATCH 3/8] fix: dark keyboard --- example/src/Examples/Basic.tsx | 12 +- example/src/Examples/WithKeyboard.tsx | 3 +- src/RichText/EditorHelper.ts | 32 +++ src/RichText/Keyboard/ColorKeyboard.tsx | 224 +++++---------------- src/RichText/Keyboard/keyboardTheme.ts | 247 ++++++++++++++++++++++++ src/RichText/index.ts | 1 + src/RichText/theme.ts | 16 +- src/RichText/useEditorBridge.tsx | 7 +- src/types/EditorBridge.ts | 3 +- src/types/Subscription.ts | 1 + src/types/Theme.ts | 21 ++ 11 files changed, 374 insertions(+), 193 deletions(-) create mode 100644 src/RichText/Keyboard/keyboardTheme.ts create mode 100644 src/types/Subscription.ts diff --git a/example/src/Examples/Basic.tsx b/example/src/Examples/Basic.tsx index 4249883..bd33692 100644 --- a/example/src/Examples/Basic.tsx +++ b/example/src/Examples/Basic.tsx @@ -7,17 +7,19 @@ import { Platform, StyleSheet, } from 'react-native'; -import { RichText, Toolbar, useEditorBridge } from '@10play/tentap-editor'; -import { darkToolbarTheme } from '../../../src/RichText/Toolbar/toolbarTheme'; +import { + RichText, + Toolbar, + darkEditorTheme, + useEditorBridge, +} from '@10play/tentap-editor'; export const Basic = ({}: NativeStackScreenProps) => { const editor = useEditorBridge({ autofocus: true, avoidIosKeyboard: true, initialContent, - theme: { - toolbar: darkToolbarTheme, - }, + theme: darkEditorTheme, }); return ( diff --git a/example/src/Examples/WithKeyboard.tsx b/example/src/Examples/WithKeyboard.tsx index 7924987..4e9d7c6 100644 --- a/example/src/Examples/WithKeyboard.tsx +++ b/example/src/Examples/WithKeyboard.tsx @@ -19,6 +19,7 @@ import { useBridgeState, TenTapStartKit, CoreBridge, + darkEditorTheme, } from '@10play/tentap-editor'; import { Images } from '../../../src/assets'; import { customFont } from './font'; @@ -28,7 +29,6 @@ export const WithKeyboard = ({}: NativeStackScreenProps) => { autofocus: true, avoidIosKeyboard: true, initialContent, - DEV: true, bridgeExtensions: [ ...TenTapStartKit, CoreBridge.configureCSS(` @@ -38,6 +38,7 @@ export const WithKeyboard = ({}: NativeStackScreenProps) => { } `), ], + theme: darkEditorTheme, }); const rootRef = useRef(null); diff --git a/src/RichText/EditorHelper.ts b/src/RichText/EditorHelper.ts index 74759b2..e7a4497 100644 --- a/src/RichText/EditorHelper.ts +++ b/src/RichText/EditorHelper.ts @@ -1,14 +1,46 @@ +import { useEffect, useState } from 'react'; import type { EditorBridge } from '../types'; +import type { Subscription } from '../types/Subscription'; class _EditorHelper { editorLastInstance: EditorBridge | undefined; + cbs: ((editor: EditorBridge | undefined) => void)[] = []; + constructor() { this.editorLastInstance = undefined; } setEditorLastInstance(editorLastInstance: EditorBridge) { this.editorLastInstance = editorLastInstance; + this.cbs.forEach((cb) => { + cb(editorLastInstance); + }); } + + subscribe: Subscription = (cb) => { + this.cbs.push(cb); + return () => { + this.cbs = this.cbs.filter((sub) => sub !== cb); + }; + }; } export const EditorHelper = new _EditorHelper(); + +export const useRemoteEditorBridge = () => { + const [editor, setEditor] = useState( + EditorHelper.editorLastInstance + ); + + useEffect(() => { + const unsubscribe = EditorHelper.subscribe((editor) => { + setEditor(editor); + }); + + return () => { + unsubscribe(); + }; + }, []); + + return editor; +}; diff --git a/src/RichText/Keyboard/ColorKeyboard.tsx b/src/RichText/Keyboard/ColorKeyboard.tsx index a12db9f..03c56f8 100644 --- a/src/RichText/Keyboard/ColorKeyboard.tsx +++ b/src/RichText/Keyboard/ColorKeyboard.tsx @@ -2,107 +2,24 @@ import React, { useMemo } from 'react'; import { Image, ScrollView, - StyleSheet, Text, TouchableOpacity, View, type ColorValue, } from 'react-native'; -import { EditorHelper } from '../EditorHelper'; +import { EditorHelper, useRemoteEditorBridge } from '../EditorHelper'; import { CustomKeyboardExtension } from './CustomKeyboardExtension'; import { Images } from '../../assets'; +import type { Color, EditorTheme } from '../../types'; const DEFAULT_COLOR = '#898989'; -const DEFAULT_HIGHLIGHT = '#89898926'; - -interface Color { - value?: ColorValue; - name: string; -} -const textColors: Color[] = [ - { - name: 'Default', - value: undefined, - }, - { - name: 'Red', - value: '#EF233C', - }, - { - name: 'Yellow', - value: '#FFEE32', - }, - { - name: 'Orange', - value: '#FB8500', - }, - { - name: 'Blue', - value: '#0085FF', - }, - { - name: 'Green', - value: '#00A896', - }, - { - name: 'Purple', - value: '#A463F2', - }, - { - name: 'Pink', - value: '#FF5D8F', - }, - { - name: 'Black', - value: '#000000', - }, -]; - -const highlightColors: Color[] = [ - { - name: 'Default', - value: undefined, - }, - { - name: 'Red', - value: '#EF233C26', - }, - { - name: 'Yellow', - value: '#FFEE3226', - }, - { - name: 'Orange', - value: '#FB850026', - }, - { - name: 'Blue', - value: '#0085FF26', - }, - { - name: 'Green', - value: '#00A89626', - }, - { - name: 'Purple', - value: '#A463F226', - }, - { - name: 'Pink', - value: '#FF5D8F26', - }, - { - name: 'Black', - value: '#00000026', - }, -]; +const DEFAULT_HIGHLIGHT = '#8989894D'; const ColorKeyboardComp = () => { - const activeColor = - EditorHelper.editorLastInstance?.getEditorState().activeColor; - const activeHighlight = - EditorHelper.editorLastInstance?.getEditorState().activeHighlight; - + const editor = useRemoteEditorBridge(); + const activeColor = editor?.getEditorState().activeColor; + const activeHighlight = editor?.getEditorState().activeHighlight; + const theme = editor?.theme; const setColor = (color?: ColorValue) => { if (!EditorHelper.editorLastInstance) return; if (color) EditorHelper.editorLastInstance.setColor(color.toString()); @@ -117,31 +34,36 @@ const ColorKeyboardComp = () => { EditorHelper.editorLastInstance.focus(); }; - const groupedTextColors = useMemo(() => groupInChunks(textColors, 3), []); + const groupedTextColors = useMemo( + () => groupInChunks(theme?.colorKeyboard.colorSelection || [], 3), + [theme?.colorKeyboard.colorSelection] + ); const groupedHighlightColors = useMemo( - () => groupInChunks(highlightColors, 3), - [] + () => groupInChunks(theme?.colorKeyboard.highlightSelection || [], 3), + [theme?.colorKeyboard.highlightSelection] ); return ( - - - Color + + + Color {groupedTextColors.map((colorRow) => { return ( ); })} - Highlight + Highlight {groupedHighlightColors.map((colorRow) => { return ( { ); })} - + ); }; interface ColorRowProps { colors: Color[]; + theme?: EditorTheme; onPress: (color?: ColorValue) => void; activeColor?: string; icon?: boolean; } -const ColorRow = ({ colors, onPress, activeColor, icon }: ColorRowProps) => { +const ColorRow = ({ + theme, + colors, + onPress, + activeColor, + icon, +}: ColorRowProps) => { return ( - + {colors.map((color) => ( { isActive={ color.value === activeColor || (!color.value && !activeColor) } + theme={theme} icon={icon} /> ))} @@ -183,22 +113,29 @@ interface ColorButtonProps { onPress: () => void; color: Color; isActive: boolean; + theme?: EditorTheme; icon?: boolean; } -const ColorButton = ({ onPress, color, isActive, icon }: ColorButtonProps) => ( +const ColorButton = ({ + theme, + onPress, + color, + isActive, + icon, +}: ColorButtonProps) => ( {icon && ( - + ( )} {!icon && ( - + )} - {color.name} + {color.name} ); -const keyboardStyles = StyleSheet.create({ - keyboardScrollView: { - flex: 1, - width: '100%', - height: '100%', - padding: 8, - }, - container: { - flex: 1, - gap: 8, - }, - colorRow: { - flexDirection: 'row', - flex: 1, - justifyContent: 'space-between', - gap: 10, - }, - colorButton: { - width: 114, - height: 46, - flexDirection: 'row', - alignItems: 'center', - borderRadius: 10, - borderStyle: 'solid', - borderWidth: 0.5, - borderColor: '#DEE0E3', - gap: 16, - padding: 12, - }, - activeButton: { - borderWidth: 1, - borderColor: '#C8C8C9', - }, - icon: { - height: 20, - width: 20, - borderRadius: 4, - alignItems: 'center', - justifyContent: 'center', - borderStyle: 'solid', - backgroundColor: 'white', - shadowColor: '#898989', - shadowOffset: { width: 0, height: 0 }, - shadowOpacity: 0.2, - shadowRadius: 2, - }, - image: { - height: 14, - }, - highlight: { - width: 20, - height: 20, - borderRadius: 4, - }, - colorText: { - color: '#898989', - }, - sectionTitle: { - color: '#CACACA', - alignSelf: 'flex-start', - marginLeft: 20, - marginTop: 15, - fontSize: 14, - }, - bottomSpacer: { - height: 30, - }, -}); - function groupInChunks(array: T[], chunkSize: number): T[][] { let result = []; for (let i = 0; i < array.length; i += chunkSize) { diff --git a/src/RichText/Keyboard/keyboardTheme.ts b/src/RichText/Keyboard/keyboardTheme.ts new file mode 100644 index 0000000..c8cec14 --- /dev/null +++ b/src/RichText/Keyboard/keyboardTheme.ts @@ -0,0 +1,247 @@ +import type { ColorKeyboardTheme } from '../../types'; + +export const defaultColorKeyboardTheme: ColorKeyboardTheme = { + scrollViewContainer: { + flex: 1, + width: '100%', + height: '100%', + padding: 8, + backgroundColor: '#F5F5F5', + }, + keyboardContainer: { + flex: 1, + gap: 8, + }, + colorRow: { + flexDirection: 'row', + flex: 1, + justifyContent: 'space-between', + gap: 10, + }, + colorButton: { + width: 114, + height: 46, + flexDirection: 'row', + alignItems: 'center', + borderRadius: 10, + borderStyle: 'solid', + borderWidth: 0.5, + borderColor: '#DEE0E3', + backgroundColor: 'white', + gap: 16, + padding: 12, + }, + activeButton: { + borderWidth: 1, + borderColor: '#C8C8C9', + }, + iconContainer: { + height: 20, + width: 20, + borderRadius: 4, + alignItems: 'center', + justifyContent: 'center', + borderStyle: 'solid', + backgroundColor: 'white', + shadowColor: '#898989', + shadowOffset: { width: 0, height: 0 }, + shadowOpacity: 0.2, + shadowRadius: 2, + }, + textIcon: { + height: 14, + }, + highlight: { + width: 20, + height: 20, + borderRadius: 4, + }, + colorText: { + color: '#898989', + }, + sectionTitle: { + color: '#CACACA', + alignSelf: 'flex-start', + marginLeft: 20, + marginTop: 15, + fontSize: 14, + }, + bottomSpacer: { + height: 30, + }, + colorSelection: [ + { + name: 'Default', + value: undefined, + }, + { + name: 'Red', + value: '#EF233C', + }, + { + name: 'Yellow', + value: '#FFEE32', + }, + { + name: 'Orange', + value: '#FB8500', + }, + { + name: 'Blue', + value: '#0085FF', + }, + { + name: 'Green', + value: '#00A896', + }, + { + name: 'Purple', + value: '#A463F2', + }, + { + name: 'Pink', + value: '#FF5D8F', + }, + { + name: 'Black', + value: '#000000', + }, + ], + highlightSelection: [ + { + name: 'Default', + value: undefined, + }, + { + name: 'Red', + value: '#EF233C4D', + }, + { + name: 'Yellow', + value: '#FFEE324D', + }, + { + name: 'Orange', + value: '#FB85004D', + }, + { + name: 'Blue', + value: '#0085FF4D', + }, + { + name: 'Green', + value: '#00A8964D', + }, + { + name: 'Purple', + value: '#A463F24D', + }, + { + name: 'Pink', + value: '#FF5D8F4D', + }, + { + name: 'Black', + value: '#0000004D', + }, + ], +}; + +export const darkColorKeyboardTheme: Partial = { + scrollViewContainer: { + backgroundColor: '#313132', + }, + colorButton: { + borderColor: '#4B4B4C', + backgroundColor: '#4B4B4C', + }, + activeButton: { + borderColor: 'white', + }, + iconContainer: { + backgroundColor: '#8C8C8D', + shadowColor: '#898989', + }, + colorText: { + color: 'white', + }, + sectionTitle: { + color: '#CACACA', + }, + colorSelection: [ + { + name: 'Default', + value: undefined, + }, + { + name: 'Red', + value: '#E5112B', + }, + { + name: 'Yellow', + value: '#FFEE32', + }, + { + name: 'Orange', + value: '#F18200', + }, + { + name: 'Blue', + value: '#006ED3', + }, + { + name: 'Green', + value: '#07CE61', + }, + { + name: 'Purple', + value: '#9D4EDD', + }, + { + name: 'Pink', + value: '#FF77A1', + }, + { + name: 'Black', + value: '#000000', + }, + ], + highlightSelection: [ + { + name: 'Default', + value: undefined, + }, + { + name: 'Red', + value: '#E5112B4D', + }, + { + name: 'Yellow', + value: '#FFEE324D', + }, + { + name: 'Orange', + value: '#F182004D', + }, + { + name: 'Blue', + value: '#006ED34D', + }, + { + name: 'Green', + value: '#07CE614D', + }, + { + name: 'Purple', + value: '#9D4EDD4D', + }, + { + name: 'Pink', + value: '#FF77A14D', + }, + { + name: 'Black', + value: '#0000004D', + }, + ], +}; diff --git a/src/RichText/index.ts b/src/RichText/index.ts index d3762d2..11fc452 100644 --- a/src/RichText/index.ts +++ b/src/RichText/index.ts @@ -3,3 +3,4 @@ export * from './useEditorBridge'; export * from './useBridgeState'; export * from './Toolbar'; export * from './Keyboard'; +export { darkEditorTheme, defaultEditorTheme } from './theme'; diff --git a/src/RichText/theme.ts b/src/RichText/theme.ts index c77df15..367106e 100644 --- a/src/RichText/theme.ts +++ b/src/RichText/theme.ts @@ -1,6 +1,16 @@ -import type { EditorTheme } from '../types'; -import { defaultToolbarTheme } from './Toolbar/toolbarTheme'; +import type { ColorKeyboardTheme, EditorTheme, ToolbarTheme } from '../types'; +import { + darkColorKeyboardTheme, + defaultColorKeyboardTheme, +} from './Keyboard/keyboardTheme'; +import { darkToolbarTheme, defaultToolbarTheme } from './Toolbar/toolbarTheme'; -export const defaultTheme: EditorTheme = { +export const defaultEditorTheme: EditorTheme = { toolbar: defaultToolbarTheme, + colorKeyboard: defaultColorKeyboardTheme, +}; + +export const darkEditorTheme: EditorTheme = { + toolbar: darkToolbarTheme as ToolbarTheme, + colorKeyboard: darkColorKeyboardTheme as ColorKeyboardTheme, }; diff --git a/src/RichText/useEditorBridge.tsx b/src/RichText/useEditorBridge.tsx index 6bad07a..e7b16a7 100644 --- a/src/RichText/useEditorBridge.tsx +++ b/src/RichText/useEditorBridge.tsx @@ -11,9 +11,9 @@ import type { EditorBridge, EditorTheme } from '../types'; import type BridgeExtension from '../bridges/base'; import { TenTapStartKit } from '../bridges/StarterKit'; import { uniqueBy } from '../utils'; -import { defaultTheme } from './theme'; +import { defaultEditorTheme } from './theme'; +import type { Subscription } from '../types/Subscription'; -type Subscription = (cb: (val: T) => void) => () => void; type RecursivePartial = { [P in keyof T]?: RecursivePartial; }; @@ -40,10 +40,9 @@ export const useEditorBridge = (options?: { }, [options?.bridgeExtensions]); const mergedTheme = useMemo( - () => merge(defaultTheme, options?.theme), + () => merge(defaultEditorTheme, options?.theme), [options?.theme] ); - const _updateEditorState = (editorState: BridgeState) => { editorStateRef.current = editorState; editorStateSubsRef.current.forEach((sub) => sub(editorState)); diff --git a/src/types/EditorBridge.ts b/src/types/EditorBridge.ts index 7a6c852..070479d 100644 --- a/src/types/EditorBridge.ts +++ b/src/types/EditorBridge.ts @@ -1,11 +1,10 @@ import type { RefObject } from 'react'; import type WebView from 'react-native-webview'; import type BridgeExtension from '../bridges/base'; +import type { Subscription } from './Subscription'; export interface BridgeState {} -type Subscription = (cb: (val: T) => void) => () => void; - export interface EditorBridge { avoidIosKeyboard?: boolean; customSource?: string; diff --git a/src/types/Subscription.ts b/src/types/Subscription.ts new file mode 100644 index 0000000..b695802 --- /dev/null +++ b/src/types/Subscription.ts @@ -0,0 +1 @@ +export type Subscription = (cb: (val: T) => void) => () => void; diff --git a/src/types/Theme.ts b/src/types/Theme.ts index 30b8a61..c53625f 100644 --- a/src/types/Theme.ts +++ b/src/types/Theme.ts @@ -8,6 +8,7 @@ import type { export interface EditorTheme { toolbar: ToolbarTheme; + colorKeyboard: ColorKeyboardTheme; } export type ToolbarTheme = { @@ -32,3 +33,23 @@ export type LinkBarTheme = { doneButtonText: StyleProp; linkToolbarButton: StyleProp; }; + +export interface Color { + value?: ColorValue; + name: string; +} +export type ColorKeyboardTheme = { + scrollViewContainer: StyleProp; + keyboardContainer: StyleProp; + colorRow: StyleProp; + colorButton: StyleProp; + activeButton: StyleProp; + iconContainer: StyleProp; + textIcon: StyleProp; + highlight: StyleProp; + colorText: StyleProp; + sectionTitle: StyleProp; + bottomSpacer: StyleProp; + colorSelection: Color[]; + highlightSelection: Color[]; +}; From 7beb4a460a0ca9c363cbda5de4c3e50e04180588 Mon Sep 17 00:00:00 2001 From: Amir Angel <36531255+17Amir17@users.noreply.github.com> Date: Thu, 15 Feb 2024 13:07:47 +0200 Subject: [PATCH 4/8] fix: dark example --- example/src/App.tsx | 5 + example/src/Examples/DarkEditor.tsx | 141 ++++++++++++++++++++++++ example/src/Examples/WithKeyboard.tsx | 3 +- src/RichText/EditorHelper.ts | 26 +++-- src/RichText/Keyboard/ColorKeyboard.tsx | 10 +- src/RichText/Keyboard/keyboardTheme.ts | 4 + src/RichText/RichText.tsx | 13 ++- src/RichText/Toolbar/toolbarTheme.ts | 4 +- src/RichText/useEditorBridge.tsx | 4 +- src/types/Theme.ts | 2 + 10 files changed, 187 insertions(+), 25 deletions(-) create mode 100644 example/src/Examples/DarkEditor.tsx diff --git a/example/src/App.tsx b/example/src/App.tsx index b23290e..c1c83b6 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -14,6 +14,7 @@ import { WithKeyboard } from './Examples/WithKeyboard'; import { CustomCss } from './Examples/CustomCss'; import { ConfigureExtensions } from './Examples/ConfigureExtentions'; import { NavigationHeader } from './Examples/NavigationHeader'; +import { DarkEditor } from './Examples/DarkEditor'; const examples = [ { @@ -36,6 +37,10 @@ const examples = [ name: 'CustomKeyboard', component: CustomKeyboardExample, }, + { + name: 'Dark Editor', + component: DarkEditor, + }, { name: 'EditorStickToKeyboardExample', component: EditorStickToKeyboardExample, diff --git a/example/src/Examples/DarkEditor.tsx b/example/src/Examples/DarkEditor.tsx new file mode 100644 index 0000000..e6b689d --- /dev/null +++ b/example/src/Examples/DarkEditor.tsx @@ -0,0 +1,141 @@ +import type { NativeStackScreenProps } from '@react-navigation/native-stack'; +import React, { useRef } from 'react'; +import { + SafeAreaView, + View, + KeyboardAvoidingView, + Platform, + StyleSheet, +} from 'react-native'; +import { + RichText, + Toolbar, + useEditorBridge, + ColorKeyboard, + CustomKeyboard, + DEFAULT_TOOLBAR_ITEMS, + useKeyboard, + type EditorBridge, + useBridgeState, + TenTapStartKit, + CoreBridge, + darkEditorTheme, +} from '@10play/tentap-editor'; +import { Images } from '../../../src/assets'; + +const BACKGROUND_COLOR = '#1C1C1E'; +const DEFAULT_TEXT_COLOR = 'white'; +const darkEditorCss = ` + * { + background-color: ${BACKGROUND_COLOR}; + color: ${DEFAULT_TEXT_COLOR}; + } +`; + +export const DarkEditor = ({}: NativeStackScreenProps) => { + const editor = useEditorBridge({ + autofocus: true, + avoidIosKeyboard: true, + initialContent, + bridgeExtensions: [ + ...TenTapStartKit, + CoreBridge.configureCSS(darkEditorCss), + ], + theme: darkEditorTheme, + }); + + const rootRef = useRef(null); + const [activeKeyboard, setActiveKeyboard] = React.useState(); + + return ( + + + + + + + + + + ); +}; + +interface ToolbarWithColorProps { + editor: EditorBridge; + activeKeyboard: string | undefined; + setActiveKeyboard: (id: string | undefined) => void; +} +const ToolbarWithColor = ({ + editor, + activeKeyboard, + setActiveKeyboard, +}: ToolbarWithColorProps) => { + // Get updates of editor state + const editorState = useBridgeState(editor); + + const { isKeyboardUp: isNativeKeyboardUp } = useKeyboard(); + const customKeyboardOpen = activeKeyboard !== undefined; + const isKeyboardUp = isNativeKeyboardUp || customKeyboardOpen; + + // Here we make sure not to hide the keyboard if our custom keyboard is visible + const hideToolbar = + !isKeyboardUp || (!editorState.isFocused && !customKeyboardOpen); + + return ( + ) => { keyboards={[ColorKeyboard]} activeKeyboardID={activeKeyboard} setActiveKeyboardID={setActiveKeyboard} - rootBackground={KEYBOARD_BACKGROUND_COLOR} // <--- Give the keyboard root view a color /> diff --git a/src/RichText/Keyboard/CustomKeyboardBase.tsx b/src/RichText/Keyboard/CustomKeyboardBase.tsx index dee6c32..0143a0a 100644 --- a/src/RichText/Keyboard/CustomKeyboardBase.tsx +++ b/src/RichText/Keyboard/CustomKeyboardBase.tsx @@ -42,7 +42,9 @@ export const CustomKeyboard = ({ ); } diff --git a/src/RichText/Keyboard/keyboardTheme.ts b/src/RichText/Keyboard/keyboardTheme.ts index 967c8de..eff3ce0 100644 --- a/src/RichText/Keyboard/keyboardTheme.ts +++ b/src/RichText/Keyboard/keyboardTheme.ts @@ -1,6 +1,7 @@ import type { ColorKeyboardTheme } from '../../types'; export const defaultColorKeyboardTheme: ColorKeyboardTheme = { + keyboardRootColor: '#F5F5F5', scrollViewContainer: { flex: 1, width: '100%', @@ -150,6 +151,7 @@ export const defaultColorKeyboardTheme: ColorKeyboardTheme = { }; export const darkColorKeyboardTheme: Partial = { + keyboardRootColor: '#313132', scrollViewContainer: { backgroundColor: '#313132', }, diff --git a/src/RichText/RichText.tsx b/src/RichText/RichText.tsx index c96130b..7ec9ac4 100644 --- a/src/RichText/RichText.tsx +++ b/src/RichText/RichText.tsx @@ -122,7 +122,9 @@ export const RichText = ({ editor, ...props }: RichTextProps) => { style={[ RichTextStyles.fullScreen, { display: loaded ? 'flex' : 'none' }, + editor.theme.richText.style, ]} + containerStyle={editor.theme.richText.containerStyle} source={source} injectedJavaScript={getInjectedJS()} injectedJavaScriptBeforeContentLoaded={`${ diff --git a/src/RichText/theme.ts b/src/RichText/theme.ts index 367106e..07d9e3f 100644 --- a/src/RichText/theme.ts +++ b/src/RichText/theme.ts @@ -8,9 +8,21 @@ import { darkToolbarTheme, defaultToolbarTheme } from './Toolbar/toolbarTheme'; export const defaultEditorTheme: EditorTheme = { toolbar: defaultToolbarTheme, colorKeyboard: defaultColorKeyboardTheme, + richText: { + style: { + backgroundColor: 'white', + }, + containerStyle: {}, + }, }; export const darkEditorTheme: EditorTheme = { toolbar: darkToolbarTheme as ToolbarTheme, colorKeyboard: darkColorKeyboardTheme as ColorKeyboardTheme, + richText: { + style: { + backgroundColor: '#1C1C1E', + }, + containerStyle: {}, + }, }; diff --git a/src/types/Theme.ts b/src/types/Theme.ts index 8303bdb..420dec6 100644 --- a/src/types/Theme.ts +++ b/src/types/Theme.ts @@ -9,6 +9,7 @@ import type { export interface EditorTheme { toolbar: ToolbarTheme; colorKeyboard: ColorKeyboardTheme; + richText: RichTextTheme; } export type ToolbarTheme = { @@ -54,4 +55,10 @@ export type ColorKeyboardTheme = { highlightSelection: Color[]; defaultTextColor: ColorValue; defaultHighlightColor: ColorValue; + keyboardRootColor: ColorValue; +}; + +export type RichTextTheme = { + style: StyleProp; + containerStyle: StyleProp; }; diff --git a/website/docs/api/useEditorBridge.md b/website/docs/api/useEditorBridge.md index 2fba38a..d08bafc 100644 --- a/website/docs/api/useEditorBridge.md +++ b/website/docs/api/useEditorBridge.md @@ -30,6 +30,12 @@ when true the editor will auto focus default: `false`
On iOS help to handle follow cursor when the editor is fullpage and the iOS keyboard hide the bottom part +#### theme + +`EditorTheme` +default: `defaultEditorTheme` (light theme)
+this prop can be used to customize the libs components see the [theme example](../examples/customTheme.md) + #### customSource `string` diff --git a/website/docs/examples/customTheme.md b/website/docs/examples/customTheme.md new file mode 100644 index 0000000..d51b907 --- /dev/null +++ b/website/docs/examples/customTheme.md @@ -0,0 +1,80 @@ +--- +sidebar_position: 7 +--- + +# Custom Theme - DarkMode + +In this example we will implement darkmode in the editor. This is similar to setting up [custom css](./customCss.md). +We support custom themes for all of our exported components: `RichText`, `Toolbar`, `ColorKeyboard`. +There are two ways we can customize the theme: + +1. The `theme` prop `useEditorBridge` +2. Customizing the RichText css with `extendCss` + +## Adding Dark Theme + +To customize the native theme you can use the `theme` prop on `useEditorBridge` + +If we simply want to add the existing dark mode theme you can just do + +```tsx +import { ..., darkEditorTheme } from '@10play/tentap-editor'; +useEditorBridge({ + theme: darkEditorTheme +}); +``` + +Now we just need to update the web-side css with `extendCss` + +```tsx +const darkEditorCss = ` + * { + background-color: #1C1C1E; + color: white; + } +`; +useEditorBridge({ + ... + bridgeExtensions: [ + ...TenTapStartKit, + CoreBridge.configureCSS(darkEditorCss), // <--- Add our dark mode css + ], + theme: darkEditorTheme, +}); +``` + +## Adding Custom Theme + +We can also provide a custom theme + +```tsx +useEditorBridge({ + theme: { + toolbar: { + toolbarBody: { + borderTopColor: '#C6C6C6B3', + borderBottomColor: '#C6C6C6B3', + backgroundColor: '#474747', + }, + // Check the ToolbarTheme type for all options + }, + colorKeyboard: { + keyboardRootColor: 'white' // IOS only the background color of rootView of the custom keyboard + colorSelection: [ + // Custom colors in color keyboard + { + name: 'Custom Color', + value: '#E5112B', + }, + ], + // Check KeyboardTheme type for all options + }, + richText: { + style: { + backgroundColor: 'black' + }, + // Check RichTextTheme type for all options + }, + } +}) +``` diff --git a/website/sidebars.ts b/website/sidebars.ts index 21282c4..873c03e 100644 --- a/website/sidebars.ts +++ b/website/sidebars.ts @@ -19,6 +19,7 @@ const sidebars: SidebarsConfig = { 'examples/colorKeyboard', 'examples/navHeader', 'examples/customKeyboard', + 'examples/customTheme', ], }, }; From 6fba2dd5a96cd956d801f61848088fa1cd1c0e06 Mon Sep 17 00:00:00 2001 From: Amir Angel <36531255+17Amir17@users.noreply.github.com> Date: Thu, 15 Feb 2024 15:43:16 +0200 Subject: [PATCH 7/8] fix: update basic --- website/docs/examples/basic.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/examples/basic.md b/website/docs/examples/basic.md index c70c56a..224e854 100644 --- a/website/docs/examples/basic.md +++ b/website/docs/examples/basic.md @@ -64,7 +64,7 @@ import { } from 'react-native'; import { RichText, Toolbar, useEditorBridge } from '@10play/tentap-editor'; -export const Basic = ({}: NativeStackScreenProps) => { +export const Basic = () => { const editor = useEditorBridge({ autofocus: true, avoidIosKeyboard: true, From c368a4c63f1f62ced737537859c8393be93d3e9e Mon Sep 17 00:00:00 2001 From: Amir Angel <36531255+17Amir17@users.noreply.github.com> Date: Sun, 18 Feb 2024 11:33:56 +0200 Subject: [PATCH 8/8] fix: update docs --- website/docs/api/CustomKeyboardUtils.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/website/docs/api/CustomKeyboardUtils.md b/website/docs/api/CustomKeyboardUtils.md index a46378f..b6fd342 100644 --- a/website/docs/api/CustomKeyboardUtils.md +++ b/website/docs/api/CustomKeyboardUtils.md @@ -36,6 +36,11 @@ The active custom keyboard id `EditorBridge` Needed so when you close custom keyboard we can focus the editor again +#### rootBackground - `IOS ONLY` + +The background of the RCTRootView used to render the custom keyboard +This is helpful when you are using a custom theme + ### CustomKeyboardExtension A js class that will register new app with the customkeyboard id - it's important to understand that it will be a septate app so state management and other will not work on the custom component as you expected,