From fb1e938852f0f027c593c8d113f1c6fa8a054638 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 23 Feb 2026 16:14:40 -0800 Subject: [PATCH 1/8] feat: implement useEmbeddedView hook and associated styles for embedded components --- .../components/IterableEmbeddedView.tsx | 21 ++++- src/embedded/hooks/index.ts | 1 + .../useEmbeddedView/embeddedViewDefaults.ts | 85 +++++++++++++++++++ .../hooks/useEmbeddedView/getStyles.ts | 81 ++++++++++++++++++ src/embedded/hooks/useEmbeddedView/index.ts | 2 + .../hooks/useEmbeddedView/useEmbeddedView.ts | 42 +++++++++ 6 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 src/embedded/hooks/index.ts create mode 100644 src/embedded/hooks/useEmbeddedView/embeddedViewDefaults.ts create mode 100644 src/embedded/hooks/useEmbeddedView/getStyles.ts create mode 100644 src/embedded/hooks/useEmbeddedView/index.ts create mode 100644 src/embedded/hooks/useEmbeddedView/useEmbeddedView.ts diff --git a/src/embedded/components/IterableEmbeddedView.tsx b/src/embedded/components/IterableEmbeddedView.tsx index c123c94b4..91ec5e355 100644 --- a/src/embedded/components/IterableEmbeddedView.tsx +++ b/src/embedded/components/IterableEmbeddedView.tsx @@ -1,4 +1,5 @@ import { useMemo } from 'react'; +import { View, Text } from 'react-native'; import { IterableEmbeddedViewType } from '../enums/IterableEmbeddedViewType'; @@ -6,6 +7,7 @@ import { IterableEmbeddedBanner } from './IterableEmbeddedBanner'; import { IterableEmbeddedCard } from './IterableEmbeddedCard'; import { IterableEmbeddedNotification } from './IterableEmbeddedNotification'; import type { IterableEmbeddedComponentProps } from '../types/IterableEmbeddedComponentProps'; +import { useEmbeddedView } from '../hooks/useEmbeddedView/useEmbeddedView'; /** * The props for the IterableEmbeddedView component. @@ -43,5 +45,22 @@ export const IterableEmbeddedView = ({ } }, [viewType]); - return Cmp ? : null; + const { parsedStyles } = + useEmbeddedView(IterableEmbeddedViewType.Notification, props); + + return Cmp ? ( + + parsedStyles.backgroundColor: {String(parsedStyles.backgroundColor)} + parsedStyles.borderColor: {String(parsedStyles.borderColor)} + parsedStyles.borderWidth: {parsedStyles.borderWidth} + parsedStyles.borderCornerRadius: {parsedStyles.borderCornerRadius} + parsedStyles.primaryBtnBackgroundColor: {String(parsedStyles.primaryBtnBackgroundColor)} + parsedStyles.primaryBtnTextColor: {String(parsedStyles.primaryBtnTextColor)} + parsedStyles.secondaryBtnBackgroundColor: {String(parsedStyles.secondaryBtnBackgroundColor)} + parsedStyles.secondaryBtnTextColor: {String(parsedStyles.secondaryBtnTextColor)} + parsedStyles.titleTextColor: {String(parsedStyles.titleTextColor)} + parsedStyles.bodyTextColor: {String(parsedStyles.bodyTextColor)} + + + ) : null; }; diff --git a/src/embedded/hooks/index.ts b/src/embedded/hooks/index.ts new file mode 100644 index 000000000..cbca753d9 --- /dev/null +++ b/src/embedded/hooks/index.ts @@ -0,0 +1 @@ +export * from './useEmbeddedView'; diff --git a/src/embedded/hooks/useEmbeddedView/embeddedViewDefaults.ts b/src/embedded/hooks/useEmbeddedView/embeddedViewDefaults.ts new file mode 100644 index 000000000..f20879388 --- /dev/null +++ b/src/embedded/hooks/useEmbeddedView/embeddedViewDefaults.ts @@ -0,0 +1,85 @@ +export const embeddedBackgroundColors = { + notification: '#ffffff', + card: '#ffffff', + banner: '#ffffff', +}; + +export const embeddedBorderColors = { + notification: '#E0DEDF', + card: '#E0DEDF', + banner: '#E0DEDF', +}; + +export const embeddedPrimaryBtnBackgroundColors = { + notification: '#6A266D', + card: 'transparent', + banner: '#6A266D', +}; + +export const embeddedPrimaryBtnTextColors = { + notification: '#ffffff', + card: '#79347F', + banner: '#ffffff', +}; + +export const embeddedSecondaryBtnBackgroundColors = { + notification: 'transparent', + card: 'transparent', + banner: 'transparent', +}; + +export const embeddedSecondaryBtnTextColors = { + notification: '#79347F', + card: '#79347F', + banner: '#79347F', +}; + +export const embeddedTitleTextColors = { + notification: '#3D3A3B', + card: '#3D3A3B', + banner: '#3D3A3B', +}; + +export const embeddedBodyTextColors = { + notification: '#787174', + card: '#787174', + banner: '#787174', +}; + +export const embeddedBorderRadius = { + notification: 8, + card: 6, + banner: 8, +}; + +export const embeddedBorderWidth = { + notification: 1, + card: 1, + banner: 1, +}; + +export const embeddedMediaImageBorderColors = { + notification: '#E0DEDF', + card: '#E0DEDF', + banner: '#E0DEDF', +}; + +export const embeddedMediaImageBackgroundColors = { + notification: '#F5F4F4', + card: '#F5F4F4', + banner: '#F5F4F4', +}; + +export const embeddedStyles = { + backgroundColor: embeddedBackgroundColors, + bodyText: embeddedBodyTextColors, + borderColor: embeddedBorderColors, + borderCornerRadius: embeddedBorderRadius, + borderWidth: embeddedBorderWidth, + mediaImageBorder: embeddedMediaImageBorderColors, + primaryBtnBackgroundColor: embeddedPrimaryBtnBackgroundColors, + primaryBtnTextColor: embeddedPrimaryBtnTextColors, + secondaryBtnBackground: embeddedSecondaryBtnBackgroundColors, + secondaryBtnTextColor: embeddedSecondaryBtnTextColors, + titleText: embeddedTitleTextColors, +}; diff --git a/src/embedded/hooks/useEmbeddedView/getStyles.ts b/src/embedded/hooks/useEmbeddedView/getStyles.ts new file mode 100644 index 000000000..16aa2f616 --- /dev/null +++ b/src/embedded/hooks/useEmbeddedView/getStyles.ts @@ -0,0 +1,81 @@ +import type { IterableEmbeddedViewConfig } from '../../types/IterableEmbeddedViewConfig'; +import { embeddedStyles } from './embeddedViewDefaults'; +import { IterableEmbeddedViewType } from '../../enums'; + +/** + * Get the default style for the embedded view type. + * + * @param viewType - The type of view to render. + * @param colors - The colors to use for the default style. + * @returns The default style. + */ +const getDefaultStyle = ( + viewType: IterableEmbeddedViewType, + colors: { + banner: T; + card: T; + notification: T; + } +): T => { + switch (viewType) { + case IterableEmbeddedViewType.Notification: + return colors.notification; + case IterableEmbeddedViewType.Card: + return colors.card; + default: + return colors.banner; + } +}; + +/** + * Get the style for the embedded view type. + * + * If a style is provided in the config, it will take precedence over the default style. + * + * @param viewType - The type of view to render. + * @param c - The config to use for the styles. + * @returns The styles. + * + * @example + * const styles = getStyles(IterableEmbeddedViewType.Notification, { + * backgroundColor: '#000000', + * borderColor: '#000000', + * borderWidth: 1, + * borderCornerRadius: 10, + * primaryBtnBackgroundColor: '#000000', + * primaryBtnTextColor: '#000000', + * }); + */ +export const getStyles = ( + viewType: IterableEmbeddedViewType, + c?: IterableEmbeddedViewConfig | null +) => { + return { + backgroundColor: + c?.backgroundColor ?? + getDefaultStyle(viewType, embeddedStyles.backgroundColor), + borderColor: + c?.borderColor ?? getDefaultStyle(viewType, embeddedStyles.borderColor), + borderWidth: + c?.borderWidth ?? getDefaultStyle(viewType, embeddedStyles.borderWidth), + borderCornerRadius: + c?.borderCornerRadius ?? + getDefaultStyle(viewType, embeddedStyles.borderCornerRadius), + primaryBtnBackgroundColor: + c?.primaryBtnBackgroundColor ?? + getDefaultStyle(viewType, embeddedStyles.primaryBtnBackgroundColor), + primaryBtnTextColor: + c?.primaryBtnTextColor ?? + getDefaultStyle(viewType, embeddedStyles.primaryBtnTextColor), + secondaryBtnBackgroundColor: + c?.secondaryBtnBackgroundColor ?? + getDefaultStyle(viewType, embeddedStyles.secondaryBtnBackground), + secondaryBtnTextColor: + c?.secondaryBtnTextColor ?? + getDefaultStyle(viewType, embeddedStyles.secondaryBtnTextColor), + titleTextColor: + c?.titleTextColor ?? getDefaultStyle(viewType, embeddedStyles.titleText), + bodyTextColor: + c?.bodyTextColor ?? getDefaultStyle(viewType, embeddedStyles.bodyText), + }; +}; diff --git a/src/embedded/hooks/useEmbeddedView/index.ts b/src/embedded/hooks/useEmbeddedView/index.ts new file mode 100644 index 000000000..bf1a77d44 --- /dev/null +++ b/src/embedded/hooks/useEmbeddedView/index.ts @@ -0,0 +1,2 @@ +export * from './useEmbeddedView'; +export { useEmbeddedView as default } from './useEmbeddedView'; diff --git a/src/embedded/hooks/useEmbeddedView/useEmbeddedView.ts b/src/embedded/hooks/useEmbeddedView/useEmbeddedView.ts new file mode 100644 index 000000000..9c3f9201d --- /dev/null +++ b/src/embedded/hooks/useEmbeddedView/useEmbeddedView.ts @@ -0,0 +1,42 @@ +import { useMemo } from 'react'; +import { IterableEmbeddedViewType } from '../../enums'; +import type { IterableEmbeddedComponentProps } from '../../types/IterableEmbeddedComponentProps'; +import { getStyles } from './getStyles'; + +/** + * This hook is used to manage the lifecycle of an embedded view. + * + * @param viewType - The type of view to render. + * @param props - The props for the embedded view. + * @returns The embedded view. + * + * @example + * const { parsedStyles } = useEmbeddedView(IterableEmbeddedViewType.Notification, { + * message, + * config, + * onButtonClick, + * onMessageClick, + * }); + * + * return ( + * + * {parsedStyles.backgroundColor} + * + * ); + */ +export const useEmbeddedView = ( + /** The type of view to render. */ + viewType: IterableEmbeddedViewType, + /** The props for the embedded view. */ + { + config, + }: IterableEmbeddedComponentProps +) => { + const parsedStyles = useMemo(() => { + return getStyles(viewType, config); + }, [viewType, config]); + + return { + parsedStyles, + }; +}; From c474b9368543973187c47cf3c75f319813dafd10 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 23 Feb 2026 16:17:38 -0800 Subject: [PATCH 2/8] test: add unit tests for getStyles function in useEmbeddedView hook --- .../hooks/useEmbeddedView/getStyles.test.ts | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 src/embedded/hooks/useEmbeddedView/getStyles.test.ts diff --git a/src/embedded/hooks/useEmbeddedView/getStyles.test.ts b/src/embedded/hooks/useEmbeddedView/getStyles.test.ts new file mode 100644 index 000000000..33d725cbe --- /dev/null +++ b/src/embedded/hooks/useEmbeddedView/getStyles.test.ts @@ -0,0 +1,156 @@ +import { getStyles } from './getStyles'; +import { IterableEmbeddedViewType } from '../../enums'; +import { + embeddedBackgroundColors, + embeddedBorderColors, + embeddedBorderRadius, + embeddedBorderWidth, + embeddedPrimaryBtnBackgroundColors, + embeddedPrimaryBtnTextColors, + embeddedSecondaryBtnBackgroundColors, + embeddedSecondaryBtnTextColors, + embeddedTitleTextColors, + embeddedBodyTextColors, +} from './embeddedViewDefaults'; + +describe('getStyles', () => { + describe('default styles by view type (no config)', () => { + it('returns Notification defaults when viewType is Notification', () => { + const result = getStyles(IterableEmbeddedViewType.Notification); + + expect(result.backgroundColor).toBe(embeddedBackgroundColors.notification); + expect(result.borderColor).toBe(embeddedBorderColors.notification); + expect(result.borderWidth).toBe(embeddedBorderWidth.notification); + expect(result.borderCornerRadius).toBe(embeddedBorderRadius.notification); + expect(result.primaryBtnBackgroundColor).toBe(embeddedPrimaryBtnBackgroundColors.notification); + expect(result.primaryBtnTextColor).toBe(embeddedPrimaryBtnTextColors.notification); + expect(result.secondaryBtnBackgroundColor).toBe(embeddedSecondaryBtnBackgroundColors.notification); + expect(result.secondaryBtnTextColor).toBe(embeddedSecondaryBtnTextColors.notification); + expect(result.titleTextColor).toBe(embeddedTitleTextColors.notification); + expect(result.bodyTextColor).toBe(embeddedBodyTextColors.notification); + }); + + it('returns Card defaults when viewType is Card', () => { + const result = getStyles(IterableEmbeddedViewType.Card); + + expect(result.backgroundColor).toBe(embeddedBackgroundColors.card); + expect(result.borderColor).toBe(embeddedBorderColors.card); + expect(result.borderWidth).toBe(embeddedBorderWidth.card); + expect(result.borderCornerRadius).toBe(embeddedBorderRadius.card); + expect(result.primaryBtnBackgroundColor).toBe(embeddedPrimaryBtnBackgroundColors.card); + expect(result.primaryBtnTextColor).toBe(embeddedPrimaryBtnTextColors.card); + expect(result.secondaryBtnBackgroundColor).toBe(embeddedSecondaryBtnBackgroundColors.card); + expect(result.secondaryBtnTextColor).toBe(embeddedSecondaryBtnTextColors.card); + expect(result.titleTextColor).toBe(embeddedTitleTextColors.card); + expect(result.bodyTextColor).toBe(embeddedBodyTextColors.card); + }); + + it('returns Banner defaults when viewType is Banner', () => { + const result = getStyles(IterableEmbeddedViewType.Banner); + + expect(result.backgroundColor).toBe(embeddedBackgroundColors.banner); + expect(result.borderColor).toBe(embeddedBorderColors.banner); + expect(result.borderWidth).toBe(embeddedBorderWidth.banner); + expect(result.borderCornerRadius).toBe(embeddedBorderRadius.banner); + expect(result.primaryBtnBackgroundColor).toBe(embeddedPrimaryBtnBackgroundColors.banner); + expect(result.primaryBtnTextColor).toBe(embeddedPrimaryBtnTextColors.banner); + expect(result.secondaryBtnBackgroundColor).toBe(embeddedSecondaryBtnBackgroundColors.banner); + expect(result.secondaryBtnTextColor).toBe(embeddedSecondaryBtnTextColors.banner); + expect(result.titleTextColor).toBe(embeddedTitleTextColors.banner); + expect(result.bodyTextColor).toBe(embeddedBodyTextColors.banner); + }); + + it('returns Banner defaults for unknown viewType (default branch)', () => { + const unknownViewType = 999 as IterableEmbeddedViewType; + const result = getStyles(unknownViewType); + + expect(result.backgroundColor).toBe(embeddedBackgroundColors.banner); + expect(result.primaryBtnBackgroundColor).toBe(embeddedPrimaryBtnBackgroundColors.banner); + }); + }); + + describe('with null or undefined config', () => { + it('returns defaults when config is null', () => { + const result = getStyles(IterableEmbeddedViewType.Notification, null); + + expect(result.backgroundColor).toBe(embeddedBackgroundColors.notification); + expect(result.primaryBtnTextColor).toBe(embeddedPrimaryBtnTextColors.notification); + }); + + it('returns defaults when config is undefined', () => { + const result = getStyles(IterableEmbeddedViewType.Card, undefined); + + expect(result.backgroundColor).toBe(embeddedBackgroundColors.card); + }); + }); + + describe('config overrides defaults', () => { + it('uses config values when provided, overrides all style keys', () => { + const config = { + backgroundColor: '#000000', + borderColor: '#111111', + borderWidth: 2, + borderCornerRadius: 10, + primaryBtnBackgroundColor: '#222222', + primaryBtnTextColor: '#333333', + secondaryBtnBackgroundColor: '#444444', + secondaryBtnTextColor: '#555555', + titleTextColor: '#666666', + bodyTextColor: '#777777', + }; + + const result = getStyles(IterableEmbeddedViewType.Notification, config); + + expect(result.backgroundColor).toBe('#000000'); + expect(result.borderColor).toBe('#111111'); + expect(result.borderWidth).toBe(2); + expect(result.borderCornerRadius).toBe(10); + expect(result.primaryBtnBackgroundColor).toBe('#222222'); + expect(result.primaryBtnTextColor).toBe('#333333'); + expect(result.secondaryBtnBackgroundColor).toBe('#444444'); + expect(result.secondaryBtnTextColor).toBe('#555555'); + expect(result.titleTextColor).toBe('#666666'); + expect(result.bodyTextColor).toBe('#777777'); + }); + + it('overrides only provided config keys, rest use view-type defaults', () => { + const config = { + backgroundColor: '#abc', + borderCornerRadius: 12, + }; + + const result = getStyles(IterableEmbeddedViewType.Card, config); + + expect(result.backgroundColor).toBe('#abc'); + expect(result.borderCornerRadius).toBe(12); + expect(result.borderColor).toBe(embeddedBorderColors.card); + expect(result.borderWidth).toBe(embeddedBorderWidth.card); + expect(result.primaryBtnBackgroundColor).toBe(embeddedPrimaryBtnBackgroundColors.card); + expect(result.primaryBtnTextColor).toBe(embeddedPrimaryBtnTextColors.card); + expect(result.secondaryBtnBackgroundColor).toBe(embeddedSecondaryBtnBackgroundColors.card); + expect(result.secondaryBtnTextColor).toBe(embeddedSecondaryBtnTextColors.card); + expect(result.titleTextColor).toBe(embeddedTitleTextColors.card); + expect(result.bodyTextColor).toBe(embeddedBodyTextColors.card); + }); + }); + + describe('return shape', () => { + it('returns an object with all expected style keys', () => { + const result = getStyles(IterableEmbeddedViewType.Banner); + + expect(result).toMatchObject({ + backgroundColor: expect.any(String), + borderColor: expect.any(String), + borderWidth: expect.any(Number), + borderCornerRadius: expect.any(Number), + primaryBtnBackgroundColor: expect.any(String), + primaryBtnTextColor: expect.any(String), + secondaryBtnBackgroundColor: expect.any(String), + secondaryBtnTextColor: expect.any(String), + titleTextColor: expect.any(String), + bodyTextColor: expect.any(String), + }); + expect(Object.keys(result)).toHaveLength(10); + }); + }); +}); From e0c0a24c7729d4de81ca9d006a44fd2c07e74a9e Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 23 Feb 2026 16:30:09 -0800 Subject: [PATCH 3/8] feat: add configuration modal to Embedded component for dynamic view settings --- .../components/Embedded/Embedded.styles.ts | 40 ++++++++++ example/src/components/Embedded/Embedded.tsx | 76 +++++++++++++++++++ .../components/IterableEmbeddedView.tsx | 39 +++++++--- 3 files changed, 145 insertions(+), 10 deletions(-) diff --git a/example/src/components/Embedded/Embedded.styles.ts b/example/src/components/Embedded/Embedded.styles.ts index a1e4f6257..c9a23adaa 100644 --- a/example/src/components/Embedded/Embedded.styles.ts +++ b/example/src/components/Embedded/Embedded.styles.ts @@ -15,6 +15,11 @@ import { const styles = StyleSheet.create({ button, buttonText, + configError: { + color: utilityColors.warning100, + fontSize: 12, + marginBottom: 8, + }, container, embeddedSection: { display: 'flex', @@ -26,6 +31,41 @@ const styles = StyleSheet.create({ inputContainer: { marginVertical: 10, }, + jsonEditor: { + ...input, + fontFamily: undefined, + fontSize: 12, + height: 220, + marginBottom: 12, + padding: 10, + }, + modalButton: { + flex: 1, + }, + modalButtons: { + flexDirection: 'row', + gap: 12, + justifyContent: 'flex-end', + }, + modalContent: { + backgroundColor: colors.backgroundPrimary, + borderRadius: 12, + maxHeight: '80%', + padding: 16, + }, + // eslint-disable-next-line react-native/no-color-literals + modalOverlay: { + backgroundColor: 'rgba(0,0,0,0.5)', + flex: 1, + justifyContent: 'center', + padding: 20, + }, + modalTitle: { + fontSize: 18, + fontWeight: '600', + marginBottom: 12, + textAlign: 'center', + }, subtitle: { ...subtitle, textAlign: 'center' }, text: { textAlign: 'center' }, textInput: input, diff --git a/example/src/components/Embedded/Embedded.tsx b/example/src/components/Embedded/Embedded.tsx index 4ac18a6f4..4998319b6 100644 --- a/example/src/components/Embedded/Embedded.tsx +++ b/example/src/components/Embedded/Embedded.tsx @@ -1,4 +1,5 @@ import { + Modal, ScrollView, Text, TextInput, @@ -9,6 +10,7 @@ import { useCallback, useState } from 'react'; import { Iterable, type IterableEmbeddedMessage, + type IterableEmbeddedViewConfig, IterableEmbeddedView, IterableEmbeddedViewType, } from '@iterable/react-native-sdk'; @@ -16,6 +18,9 @@ import { SafeAreaView } from 'react-native-safe-area-context'; import styles from './Embedded.styles'; +const DEFAULT_CONFIG_JSON = `{ +}`; + export const Embedded = () => { const [placementIdsInput, setPlacementIdsInput] = useState(''); const [embeddedMessages, setEmbeddedMessages] = useState< @@ -23,6 +28,11 @@ export const Embedded = () => { >([]); const [selectedViewType, setSelectedViewType] = useState(IterableEmbeddedViewType.Banner); + const [viewConfig, setViewConfig] = + useState(null); + const [configEditorVisible, setConfigEditorVisible] = useState(false); + const [configJson, setConfigJson] = useState(DEFAULT_CONFIG_JSON); + const [configError, setConfigError] = useState(null); // Parse placement IDs from input const parsedPlacementIds = placementIdsInput @@ -55,6 +65,30 @@ export const Embedded = () => { }); }, [idsToFetch]); + const openConfigEditor = useCallback(() => { + setConfigError(null); + setConfigJson( + viewConfig ? JSON.stringify(viewConfig, null, 2) : DEFAULT_CONFIG_JSON + ); + setConfigEditorVisible(true); + }, [viewConfig]); + + const applyConfig = useCallback(() => { + setConfigError(null); + try { + const parsed = JSON.parse(configJson) as IterableEmbeddedViewConfig; + setViewConfig(parsed); + setConfigEditorVisible(false); + } catch (e) { + setConfigError(e instanceof Error ? e.message : 'Invalid JSON'); + } + }, [configJson]); + + const closeConfigEditor = useCallback(() => { + setConfigEditorVisible(false); + setConfigError(null); + }, []); + return ( Embedded @@ -142,6 +176,9 @@ export const Embedded = () => { End session + + Set view config + Placement IDs (comma-separated): { + + + + View config (JSON) + {configError ? ( + {configError} + ) : null} + + + + Cancel + + + Apply + + + + + @@ -167,6 +242,7 @@ export const Embedded = () => { key={message.metadata.messageId} viewType={selectedViewType} message={message} + config={viewConfig} /> ))} diff --git a/src/embedded/components/IterableEmbeddedView.tsx b/src/embedded/components/IterableEmbeddedView.tsx index 91ec5e355..7234e3904 100644 --- a/src/embedded/components/IterableEmbeddedView.tsx +++ b/src/embedded/components/IterableEmbeddedView.tsx @@ -45,21 +45,40 @@ export const IterableEmbeddedView = ({ } }, [viewType]); - const { parsedStyles } = - useEmbeddedView(IterableEmbeddedViewType.Notification, props); + const { parsedStyles } = useEmbeddedView(viewType, props); return Cmp ? ( - parsedStyles.backgroundColor: {String(parsedStyles.backgroundColor)} + + parsedStyles.backgroundColor: {String(parsedStyles.backgroundColor)} + parsedStyles.borderColor: {String(parsedStyles.borderColor)} parsedStyles.borderWidth: {parsedStyles.borderWidth} - parsedStyles.borderCornerRadius: {parsedStyles.borderCornerRadius} - parsedStyles.primaryBtnBackgroundColor: {String(parsedStyles.primaryBtnBackgroundColor)} - parsedStyles.primaryBtnTextColor: {String(parsedStyles.primaryBtnTextColor)} - parsedStyles.secondaryBtnBackgroundColor: {String(parsedStyles.secondaryBtnBackgroundColor)} - parsedStyles.secondaryBtnTextColor: {String(parsedStyles.secondaryBtnTextColor)} - parsedStyles.titleTextColor: {String(parsedStyles.titleTextColor)} - parsedStyles.bodyTextColor: {String(parsedStyles.bodyTextColor)} + + parsedStyles.borderCornerRadius: {parsedStyles.borderCornerRadius} + + + parsedStyles.primaryBtnBackgroundColor:{' '} + {String(parsedStyles.primaryBtnBackgroundColor)} + + + parsedStyles.primaryBtnTextColor:{' '} + {String(parsedStyles.primaryBtnTextColor)} + + + parsedStyles.secondaryBtnBackgroundColor:{' '} + {String(parsedStyles.secondaryBtnBackgroundColor)} + + + parsedStyles.secondaryBtnTextColor:{' '} + {String(parsedStyles.secondaryBtnTextColor)} + + + parsedStyles.titleTextColor: {String(parsedStyles.titleTextColor)} + + + parsedStyles.bodyTextColor: {String(parsedStyles.bodyTextColor)} + ) : null; From 05114b1e33f05cb4c1026b9d25b51e691dee7ee3 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 23 Feb 2026 16:40:06 -0800 Subject: [PATCH 4/8] refactor: extract modal styles into a separate file and update Embedded component styles --- .../components/Embedded/Embedded.styles.ts | 37 ++++------------ example/src/components/Embedded/Embedded.tsx | 1 - example/src/constants/styles/index.ts | 1 + example/src/constants/styles/modal.ts | 43 +++++++++++++++++++ 4 files changed, 53 insertions(+), 29 deletions(-) create mode 100644 example/src/constants/styles/modal.ts diff --git a/example/src/components/Embedded/Embedded.styles.ts b/example/src/components/Embedded/Embedded.styles.ts index c9a23adaa..cbadf0fe2 100644 --- a/example/src/components/Embedded/Embedded.styles.ts +++ b/example/src/components/Embedded/Embedded.styles.ts @@ -7,9 +7,13 @@ import { container, hr, input, + modalButton, + modalButtons, + modalContent, + modalOverlay, subtitle, title, - utilityColors, + utilityColors } from '../../constants'; const styles = StyleSheet.create({ @@ -39,33 +43,10 @@ const styles = StyleSheet.create({ marginBottom: 12, padding: 10, }, - modalButton: { - flex: 1, - }, - modalButtons: { - flexDirection: 'row', - gap: 12, - justifyContent: 'flex-end', - }, - modalContent: { - backgroundColor: colors.backgroundPrimary, - borderRadius: 12, - maxHeight: '80%', - padding: 16, - }, - // eslint-disable-next-line react-native/no-color-literals - modalOverlay: { - backgroundColor: 'rgba(0,0,0,0.5)', - flex: 1, - justifyContent: 'center', - padding: 20, - }, - modalTitle: { - fontSize: 18, - fontWeight: '600', - marginBottom: 12, - textAlign: 'center', - }, + modalButton, + modalButtons: modalButtons, + modalContent, + modalOverlay, subtitle: { ...subtitle, textAlign: 'center' }, text: { textAlign: 'center' }, textInput: input, diff --git a/example/src/components/Embedded/Embedded.tsx b/example/src/components/Embedded/Embedded.tsx index 4998319b6..a9cff8fbe 100644 --- a/example/src/components/Embedded/Embedded.tsx +++ b/example/src/components/Embedded/Embedded.tsx @@ -204,7 +204,6 @@ export const Embedded = () => { > - View config (JSON) {configError ? ( {configError} ) : null} diff --git a/example/src/constants/styles/index.ts b/example/src/constants/styles/index.ts index b8c3bac5e..225ee4903 100644 --- a/example/src/constants/styles/index.ts +++ b/example/src/constants/styles/index.ts @@ -2,5 +2,6 @@ export * from './colors'; export * from './containers'; export * from './formElements'; export * from './miscElements'; +export * from './modal'; export * from './shadows'; export * from './typography'; diff --git a/example/src/constants/styles/modal.ts b/example/src/constants/styles/modal.ts new file mode 100644 index 000000000..07f513a96 --- /dev/null +++ b/example/src/constants/styles/modal.ts @@ -0,0 +1,43 @@ +import type { TextStyle, ViewStyle } from "react-native"; +import { colors } from "./colors"; + +export const modalTitle: TextStyle = { + fontSize: 18, + fontWeight: '600', + marginBottom: 12, + textAlign: 'center', +}; + +export const modalOverlay: ViewStyle = { + backgroundColor: 'rgba(0,0,0,0.5)', + flex: 1, + justifyContent: 'center', + padding: 20, +}; + +export const modalContent: ViewStyle = { + backgroundColor: colors.backgroundPrimary, + borderRadius: 12, + maxHeight: '80%', + padding: 16, +}; + +export const modalButtons: ViewStyle = { + flexDirection: 'row', + gap: 12, + justifyContent: 'flex-end', +}; + +export const modalButton: ViewStyle = { + flex: 1, +}; + +export const modalButtonText: TextStyle = { + color: colors.brandCyan, + fontSize: 14, + fontWeight: '600', +}; + +export const modalButtonTextSelected: TextStyle = { + color: colors.backgroundPrimary, +}; From d09719080417f0fb85aa30b839e549776f8b6fab Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 23 Feb 2026 16:46:18 -0800 Subject: [PATCH 5/8] refactor: simplify jsonEditor styles in Embedded component by removing unused properties --- example/src/components/Embedded/Embedded.styles.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/example/src/components/Embedded/Embedded.styles.ts b/example/src/components/Embedded/Embedded.styles.ts index cbadf0fe2..fd9571252 100644 --- a/example/src/components/Embedded/Embedded.styles.ts +++ b/example/src/components/Embedded/Embedded.styles.ts @@ -37,11 +37,8 @@ const styles = StyleSheet.create({ }, jsonEditor: { ...input, - fontFamily: undefined, fontSize: 12, height: 220, - marginBottom: 12, - padding: 10, }, modalButton, modalButtons: modalButtons, From 7de5d61c4de9b0041848253f9628bdb289cd6c78 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 23 Feb 2026 16:49:43 -0800 Subject: [PATCH 6/8] refactor: remove configError state, replacing error handling with an alert --- example/src/components/Embedded/Embedded.styles.ts | 5 ----- example/src/components/Embedded/Embedded.tsx | 13 +++---------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/example/src/components/Embedded/Embedded.styles.ts b/example/src/components/Embedded/Embedded.styles.ts index fd9571252..9f416c3bb 100644 --- a/example/src/components/Embedded/Embedded.styles.ts +++ b/example/src/components/Embedded/Embedded.styles.ts @@ -19,11 +19,6 @@ import { const styles = StyleSheet.create({ button, buttonText, - configError: { - color: utilityColors.warning100, - fontSize: 12, - marginBottom: 8, - }, container, embeddedSection: { display: 'flex', diff --git a/example/src/components/Embedded/Embedded.tsx b/example/src/components/Embedded/Embedded.tsx index a9cff8fbe..599718377 100644 --- a/example/src/components/Embedded/Embedded.tsx +++ b/example/src/components/Embedded/Embedded.tsx @@ -1,4 +1,5 @@ import { + Alert, Modal, ScrollView, Text, @@ -32,7 +33,6 @@ export const Embedded = () => { useState(null); const [configEditorVisible, setConfigEditorVisible] = useState(false); const [configJson, setConfigJson] = useState(DEFAULT_CONFIG_JSON); - const [configError, setConfigError] = useState(null); // Parse placement IDs from input const parsedPlacementIds = placementIdsInput @@ -66,27 +66,23 @@ export const Embedded = () => { }, [idsToFetch]); const openConfigEditor = useCallback(() => { - setConfigError(null); setConfigJson( viewConfig ? JSON.stringify(viewConfig, null, 2) : DEFAULT_CONFIG_JSON ); - setConfigEditorVisible(true); }, [viewConfig]); const applyConfig = useCallback(() => { - setConfigError(null); try { const parsed = JSON.parse(configJson) as IterableEmbeddedViewConfig; setViewConfig(parsed); setConfigEditorVisible(false); - } catch (e) { - setConfigError(e instanceof Error ? e.message : 'Invalid JSON'); + } catch { + Alert.alert('Error', 'Invalid JSON'); } }, [configJson]); const closeConfigEditor = useCallback(() => { setConfigEditorVisible(false); - setConfigError(null); }, []); return ( @@ -204,9 +200,6 @@ export const Embedded = () => { > - {configError ? ( - {configError} - ) : null} Date: Mon, 23 Feb 2026 16:55:57 -0800 Subject: [PATCH 7/8] refactor: streamline modal button styles and ensure config editor visibility in Embedded component --- example/src/components/Embedded/Embedded.styles.ts | 2 +- example/src/components/Embedded/Embedded.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/example/src/components/Embedded/Embedded.styles.ts b/example/src/components/Embedded/Embedded.styles.ts index 9f416c3bb..a1fb26b7e 100644 --- a/example/src/components/Embedded/Embedded.styles.ts +++ b/example/src/components/Embedded/Embedded.styles.ts @@ -36,7 +36,7 @@ const styles = StyleSheet.create({ height: 220, }, modalButton, - modalButtons: modalButtons, + modalButtons, modalContent, modalOverlay, subtitle: { ...subtitle, textAlign: 'center' }, diff --git a/example/src/components/Embedded/Embedded.tsx b/example/src/components/Embedded/Embedded.tsx index 599718377..189293d37 100644 --- a/example/src/components/Embedded/Embedded.tsx +++ b/example/src/components/Embedded/Embedded.tsx @@ -69,6 +69,7 @@ export const Embedded = () => { setConfigJson( viewConfig ? JSON.stringify(viewConfig, null, 2) : DEFAULT_CONFIG_JSON ); + setConfigEditorVisible(true); }, [viewConfig]); const applyConfig = useCallback(() => { From 2385cc0cc136bdbd398de36c9e681acf3f66dd81 Mon Sep 17 00:00:00 2001 From: Loren Posen Date: Mon, 23 Feb 2026 17:00:13 -0800 Subject: [PATCH 8/8] fix: correct syntax in JSDoc example for useEmbeddedView hook --- src/embedded/hooks/useEmbeddedView/useEmbeddedView.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/embedded/hooks/useEmbeddedView/useEmbeddedView.ts b/src/embedded/hooks/useEmbeddedView/useEmbeddedView.ts index 9c3f9201d..eb4997782 100644 --- a/src/embedded/hooks/useEmbeddedView/useEmbeddedView.ts +++ b/src/embedded/hooks/useEmbeddedView/useEmbeddedView.ts @@ -11,16 +11,16 @@ import { getStyles } from './getStyles'; * @returns The embedded view. * * @example - * const { parsedStyles } = useEmbeddedView(IterableEmbeddedViewType.Notification, { + * const \{ parsedStyles \} = useEmbeddedView(IterableEmbeddedViewType.Notification, \{ * message, * config, * onButtonClick, * onMessageClick, - * }); + * \}); * * return ( * - * {parsedStyles.backgroundColor} + * \{parsedStyles.backgroundColor\} * * ); */