diff --git a/packages/common/src/overlays/useAlert.ts b/packages/common/src/overlays/useAlert.ts index 6ee64e1de..0cdbd582b 100644 --- a/packages/common/src/overlays/useAlert.ts +++ b/packages/common/src/overlays/useAlert.ts @@ -1,7 +1,7 @@ import { useOverlay } from './useOverlay'; /** - * @deprecated Use the visible and onRequestClose props as outlined in the docs here https://cds.coinbase.com/components/modal#get-started + * @deprecated Use the `visible` and `onRequestClose` props as outlined in the docs here https://cds.coinbase.com/components/overlay/Modal */ export const useAlert = () => { return useOverlay('alert_'); diff --git a/packages/common/src/overlays/useModal.ts b/packages/common/src/overlays/useModal.ts index cf8ae9f0d..66dbee0be 100644 --- a/packages/common/src/overlays/useModal.ts +++ b/packages/common/src/overlays/useModal.ts @@ -3,7 +3,7 @@ import { useMemo } from 'react'; import { useOverlay } from './useOverlay'; /** - * @deprecated Use the visible and onRequestClose props as outlined in the docs here https://cds.coinbase.com/components/modal#get-started + * @deprecated Use the `visible` and `onRequestClose` props as outlined in the docs here https://cds.coinbase.com/components/overlay/Modal */ export const useModal = () => { const { open, close } = useOverlay('modal_'); diff --git a/packages/mobile/src/buttons/IconCounterButton.tsx b/packages/mobile/src/buttons/IconCounterButton.tsx index 608b0192c..730f245f7 100644 --- a/packages/mobile/src/buttons/IconCounterButton.tsx +++ b/packages/mobile/src/buttons/IconCounterButton.tsx @@ -14,7 +14,7 @@ import { Text } from '../typography/Text'; export type IconCounterButtonBaseProps = { /** Name of the icon or a ReactNode */ icon: Exclude | IconName; - /** @deprecated Use `size` instead. */ + /** @deprecated Use `size` instead. This prop will be removed in a future version. */ iconSize?: IconSize; /** Size for given icon. */ size?: IconSize; diff --git a/packages/mobile/src/cards/AnnouncementCard.tsx b/packages/mobile/src/cards/AnnouncementCard.tsx index 77e2344dc..eb6f129ac 100644 --- a/packages/mobile/src/cards/AnnouncementCard.tsx +++ b/packages/mobile/src/cards/AnnouncementCard.tsx @@ -4,7 +4,7 @@ import { Card, type CardBaseProps } from './Card'; import { CardBody, type CardBodyBaseProps, type CardBodyProps } from './CardBody'; export type AnnouncementCardBaseProps = CardBaseProps & CardBodyBaseProps; -/** @deprecated will be removed in v7.0.0 use NudgeCard or UpsellCard instead */ +/** @deprecated This component will be removed in a future version. Use NudgeCard or UpsellCard instead. */ export type AnnouncementCardProps = AnnouncementCardBaseProps; /** @deprecated This component will be removed in a future version. Use NudgeCard or UpsellCard instead. */ diff --git a/packages/mobile/src/cards/FeatureEntryCard.tsx b/packages/mobile/src/cards/FeatureEntryCard.tsx index a9738461c..5e952a0f7 100644 --- a/packages/mobile/src/cards/FeatureEntryCard.tsx +++ b/packages/mobile/src/cards/FeatureEntryCard.tsx @@ -5,10 +5,10 @@ import { CardBody, type CardBodyBaseProps } from './CardBody'; export type FeatureEntryCardBaseProps = CardBaseProps & CardBodyBaseProps; -/** @deprecated will be removed in v7.0.0 use NudgeCard or UpsellCard instead */ +/** @deprecated This component will be removed in a future version. Use NudgeCard or UpsellCard instead. */ export type FeatureEntryCardProps = FeatureEntryCardBaseProps; -/** @deprecated will be removed in v7.0.0 use NudgeCard or UpsellCard instead */ +/** @deprecated This component will be removed in a future version. Use NudgeCard or UpsellCard instead. */ export const FeatureEntryCard = memo(function FeatureEntryCard({ onPress, testID = 'feature-entry-card', diff --git a/packages/mobile/src/cells/Cell.tsx b/packages/mobile/src/cells/Cell.tsx index 24f370c8e..ccb4305e4 100644 --- a/packages/mobile/src/cells/Cell.tsx +++ b/packages/mobile/src/cells/Cell.tsx @@ -44,7 +44,7 @@ export type CellBaseProps = SharedProps & */ end?: React.ReactNode; /** - * @deprecated Use `end` instead. `detail` will be removed in a release. + * @deprecated Use `end` instead. This prop will be removed in a future version. */ detail?: React.ReactNode; /** Middle content between main content and detail. */ @@ -53,7 +53,7 @@ export type CellBaseProps = SharedProps & media?: React.ReactElement; borderRadius?: ThemeVars.BorderRadius; /** - * @deprecated Use `styles.end` instead. `detailWidth` will be removed in a release. + * @deprecated Use `styles.end` instead. This prop will be removed in a future version. */ detailWidth?: number | string; /** Is the cell disabled? Will apply opacity and disable interaction. */ diff --git a/packages/mobile/src/cells/CellMedia.tsx b/packages/mobile/src/cells/CellMedia.tsx index 911c656c7..bca8624f8 100644 --- a/packages/mobile/src/cells/CellMedia.tsx +++ b/packages/mobile/src/cells/CellMedia.tsx @@ -27,7 +27,7 @@ export type CellMediaPictogramProps = { type CellMediaOtherProps = { type: Exclude; /** - * @deprecated This prop will be removed in v6.0.0 + * @deprecated This prop will be removed in a future version. * If required, use `accessibilityLabel` and `accessibilityHint` instead to set accessible labels. * Refer to https://cds.coinbase.com/components/cell-media/ for updated accessibility guidance. */ diff --git a/packages/mobile/src/cells/ListCell.tsx b/packages/mobile/src/cells/ListCell.tsx index 65089b5ae..8bb32ca61 100644 --- a/packages/mobile/src/cells/ListCell.tsx +++ b/packages/mobile/src/cells/ListCell.tsx @@ -33,11 +33,11 @@ export type ListCellBaseProps = CellDetailProps & */ end?: React.ReactNode; /** - * @deprecated Use `end` instead. `action` will be removed in a release. + * @deprecated Use `end` instead. This prop will be removed in a future version. */ action?: React.ReactNode; /** - * @deprecated Use `spacingVariant="compact"`. `compact` will be removed in a release. + * @deprecated Use `spacingVariant="compact"`. This prop will be removed in a future version. */ compact?: boolean; /** diff --git a/packages/mobile/src/gradients/LinearGradient.tsx b/packages/mobile/src/gradients/LinearGradient.tsx index 0d349a407..74dadb438 100644 --- a/packages/mobile/src/gradients/LinearGradient.tsx +++ b/packages/mobile/src/gradients/LinearGradient.tsx @@ -46,7 +46,7 @@ type LinearGradientProps = { */ colors: NonNullable[]; /** - * @deprecated This prop will be removed in a future version. Please use the elevated prop instead. + * @deprecated Use the `elevated` prop instead. This prop will be removed in a future version. * Sets layout position between SVG and children. Set it to false when gradient should overlay children content. * @default true */ diff --git a/packages/mobile/src/media/RemoteImage.tsx b/packages/mobile/src/media/RemoteImage.tsx index 029dc1398..396be8ada 100644 --- a/packages/mobile/src/media/RemoteImage.tsx +++ b/packages/mobile/src/media/RemoteImage.tsx @@ -43,7 +43,7 @@ type BaseRemoteImageProps = Omit(undefined); -// export type ThemeProviderProps = SystemProviderProps & -// ThemeManagerProps & -// FramerMotionProviderProps; +/** + * Diff two themes and return a new partial theme with only the differences. + */ +export const diffThemes = (theme: Theme, parentTheme?: Theme) => { + if (!parentTheme) return theme; + const themeDiff = { + id: theme.id, + activeColorScheme: theme.activeColorScheme, + } as Record; + (Object.keys(theme) as (keyof Theme)[]).forEach((key) => { + if (key === 'id' || key === 'activeColorScheme') return; + themeDiff[key] = {}; + Object.keys(theme[key] ?? {}).forEach((value) => { + if ((theme[key] as any)?.[value] !== (parentTheme[key] as any)?.[value]) { + themeDiff[key][value] = (theme[key] as any)[value]; + } + }); + }); + return themeDiff as Partial; +}; export type ThemeProviderProps = { theme: ThemeConfig; @@ -17,7 +34,7 @@ export type ThemeProviderProps = { children?: React.ReactNode; }; -export const ThemeProvider = ({ theme, activeColorScheme, children }: ThemeProviderProps) => { +export const ThemeProvider = memo(({ theme, activeColorScheme, children }: ThemeProviderProps) => { const themeApi = useMemo(() => { const activeSpectrumKey = activeColorScheme === 'dark' ? 'darkSpectrum' : 'lightSpectrum'; const activeColorKey = activeColorScheme === 'dark' ? 'darkColor' : 'lightColor'; @@ -26,22 +43,22 @@ export const ThemeProvider = ({ theme, activeColorScheme, children }: ThemeProvi if (!theme[activeColorKey]) throw Error( - `ThemeProvider activeColorScheme is ${activeColorScheme} but no ${activeColorScheme} colors are defined for the theme. See the docs at https://cds.coinbase.com/getting-started/theming/#creating-a-theme`, + `ThemeProvider activeColorScheme is ${activeColorScheme} but no ${activeColorScheme} colors are defined for the theme. See the docs https://cds.coinbase.com/getting-started/theming`, ); if (!theme[activeSpectrumKey]) throw Error( - `ThemeProvider activeColorScheme is ${activeColorScheme} but no ${activeSpectrumKey} values are defined for the theme. See the docs at https://cds.coinbase.com/getting-started/theming/#creating-a-theme`, + `ThemeProvider activeColorScheme is ${activeColorScheme} but no ${activeSpectrumKey} values are defined for the theme. See the docs https://cds.coinbase.com/getting-started/theming`, ); if (theme[inverseSpectrumKey] && !theme[inverseColorKey]) throw Error( - `ThemeProvider theme has ${inverseSpectrumKey} values defined but no ${inverseColorKey} colors are defined for the theme. See the docs at https://cds.coinbase.com/getting-started/theming/#creating-a-theme`, + `ThemeProvider theme has ${inverseSpectrumKey} values defined but no ${inverseColorKey} colors are defined for the theme. See the docs https://cds.coinbase.com/getting-started/theming`, ); if (theme[inverseColorKey] && !theme[inverseSpectrumKey]) throw Error( - `ThemeProvider theme has ${inverseColorKey} colors defined but no ${inverseSpectrumKey} values are defined for the theme. See the docs at https://cds.coinbase.com/getting-started/theming/#creating-a-theme`, + `ThemeProvider theme has ${inverseColorKey} colors defined but no ${inverseSpectrumKey} values are defined for the theme. See the docs https://cds.coinbase.com/getting-started/theming`, ); return { @@ -53,14 +70,14 @@ export const ThemeProvider = ({ theme, activeColorScheme, children }: ThemeProvi }, [theme, activeColorScheme]); return {children}; -}; +}); export type InvertedThemeProviderProps = { children?: React.ReactNode; }; /** Falls back to the currently active colorScheme if the inverse colors are not defined in the theme. */ -export const InvertedThemeProvider = ({ children }: InvertedThemeProviderProps) => { +export const InvertedThemeProvider = memo(({ children }: InvertedThemeProviderProps) => { const context = useContext(ThemeContext); if (!context) throw Error('InvertedThemeProvider must be used within a ThemeProvider'); const inverseColorScheme = context.activeColorScheme === 'dark' ? 'light' : 'dark'; @@ -72,4 +89,4 @@ export const InvertedThemeProvider = ({ children }: InvertedThemeProviderProps) {children} ); -}; +}); diff --git a/packages/web/src/cards/AnnouncementCard.tsx b/packages/web/src/cards/AnnouncementCard.tsx index 052c7c973..7ab31faff 100644 --- a/packages/web/src/cards/AnnouncementCard.tsx +++ b/packages/web/src/cards/AnnouncementCard.tsx @@ -5,7 +5,7 @@ import { CardBody, type CardBodyBaseProps } from './CardBody'; export type AnnouncementCardBaseProps = CardBaseProps & CardBodyBaseProps; export type AnnouncementCardProps = AnnouncementCardBaseProps; -/** @deprecated will be removed in v7.0.0 use NudgeCard or UpsellCard instead */ +/** @deprecated This component will be removed in a future version. Use NudgeCard or UpsellCard instead. */ export const AnnouncementCard = memo(function AnnouncementCard({ width, title, diff --git a/packages/web/src/cards/FeatureEntryCard.tsx b/packages/web/src/cards/FeatureEntryCard.tsx index 6f289f460..7bdf03906 100644 --- a/packages/web/src/cards/FeatureEntryCard.tsx +++ b/packages/web/src/cards/FeatureEntryCard.tsx @@ -5,7 +5,7 @@ import { CardBody, type CardBodyBaseProps } from './CardBody'; export type FeatureEntryCardBaseProps = CardBaseProps & CardBodyBaseProps; -/** @deprecated will be removed in v7.0.0 use NudgeCard or UpsellCard instead */ +/** @deprecated This component will be removed in a future version. Use NudgeCard or UpsellCard instead. */ export type FeatureEntryCardProps = FeatureEntryCardBaseProps; /** @deprecated This component will be removed in a future version. Use NudgeCard or UpsellCard instead. */ diff --git a/packages/web/src/cells/CellMedia.tsx b/packages/web/src/cells/CellMedia.tsx index cdbad2d70..f96c4d22d 100644 --- a/packages/web/src/cells/CellMedia.tsx +++ b/packages/web/src/cells/CellMedia.tsx @@ -26,7 +26,7 @@ export type CellMediaPictogramProps = { type CellMediaOtherProps = { type: Exclude; /** - * @deprecated This prop will be removed in v6.0.0 + * @deprecated This prop will be removed in a future version. * If required, use `accessibilityLabel` and `accessibilityHint` instead to set accessible labels. * Refer to https://cds.coinbase.com/components/cell-media/ for updated accessibility guidance. */ diff --git a/packages/web/src/overlays/Portal.tsx b/packages/web/src/overlays/Portal.tsx index 98c51773d..8c610021c 100644 --- a/packages/web/src/overlays/Portal.tsx +++ b/packages/web/src/overlays/Portal.tsx @@ -29,7 +29,7 @@ export const Portal = memo(function Portal({ } return createPortal( - + {children} , document.getElementById(containerId) as HTMLElement, diff --git a/packages/web/src/overlays/useModal.ts b/packages/web/src/overlays/useModal.ts index 1cd2dda6d..ffcd2af0f 100644 --- a/packages/web/src/overlays/useModal.ts +++ b/packages/web/src/overlays/useModal.ts @@ -1,6 +1,6 @@ import { useModal } from '@coinbase/cds-common/overlays/useModal'; /** - * @deprecated Use the visible and onRequestClose props as outlined in the docs here https://cds.coinbase.com/components/modal#get-started + * @deprecated Use the `visible` and `onRequestClose` props as outlined in the docs here https://cds.coinbase.com/components/overlay/Modal */ export { useModal }; diff --git a/packages/web/src/system/ThemeProvider.tsx b/packages/web/src/system/ThemeProvider.tsx index 6d2582e0b..5bbe31edd 100644 --- a/packages/web/src/system/ThemeProvider.tsx +++ b/packages/web/src/system/ThemeProvider.tsx @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-empty-object-type */ -import React, { createContext, useContext, useMemo } from 'react'; +import React, { createContext, memo, useContext, useMemo } from 'react'; import type { ColorScheme } from '@coinbase/cds-common/core/theme'; import { createThemeCssVars } from '../core/createThemeCssVars'; @@ -23,15 +23,16 @@ type ThemeManagerProps = { className?: string; style?: React.CSSProperties; children?: React.ReactNode; - theme: Theme; + theme: Partial; }; -export const useThemeProviderStyles = (theme: Theme) => { +export const useThemeProviderStyles = (theme: Partial) => { const style = useMemo(() => createThemeCssVars(theme), [theme]); return style; }; -const ThemeManager = ({ display, className, style, children, theme }: ThemeManagerProps) => { +/** Injects theme CSS variables into the DOM by calling `createThemeCssVars` via `useThemeProviderStyles`. */ +const ThemeManager = memo(({ display, className, style, children, theme }: ThemeManagerProps) => { const themeStyles = useThemeProviderStyles(theme); const styles = useMemo( () => ({ ...themeStyles, display, ...style }), @@ -42,6 +43,27 @@ const ThemeManager = ({ display, className, style, children, theme }: ThemeManag {children} ); +}); + +/** + * Diff two themes and return a new partial theme with only the differences. + */ +export const diffThemes = (theme: Theme, parentTheme?: Theme) => { + if (!parentTheme) return theme; + const themeDiff = { + id: theme.id, + activeColorScheme: theme.activeColorScheme, + } as Record; + (Object.keys(theme) as (keyof Theme)[]).forEach((key) => { + if (key === 'id' || key === 'activeColorScheme') return; + themeDiff[key] = {}; + Object.keys(theme[key] ?? {}).forEach((value) => { + if ((theme[key] as any)?.[value] !== (parentTheme[key] as any)?.[value]) { + themeDiff[key][value] = (theme[key] as any)[value]; + } + }); + }); + return themeDiff as Partial; }; export type ThemeProviderProps = Pick & @@ -49,62 +71,73 @@ export type ThemeProviderProps = Pick { - const themeApi = useMemo(() => { - const activeSpectrumKey = activeColorScheme === 'dark' ? 'darkSpectrum' : 'lightSpectrum'; - const activeColorKey = activeColorScheme === 'dark' ? 'darkColor' : 'lightColor'; - const inverseSpectrumKey = activeColorScheme === 'dark' ? 'lightSpectrum' : 'darkSpectrum'; - const inverseColorKey = activeColorScheme === 'dark' ? 'lightColor' : 'darkColor'; - - // TO DO: Link to color / theme docs in these error messages - if (!theme[activeColorKey]) - throw Error( - `ThemeProvider activeColorScheme is ${activeColorScheme} but no ${activeColorScheme} colors are defined for the theme`, - ); - - if (!theme[activeSpectrumKey]) - throw Error( - `ThemeProvider activeColorScheme is ${activeColorScheme} but no ${activeSpectrumKey} values are defined for the theme`, - ); - - if (theme[inverseSpectrumKey] && !theme[inverseColorKey]) - throw Error( - `ThemeProvider theme has ${inverseSpectrumKey} values defined but no ${inverseColorKey} colors are defined for the theme`, - ); - - if (theme[inverseColorKey] && !theme[inverseSpectrumKey]) - throw Error( - `ThemeProvider theme has ${inverseColorKey} colors defined but no ${inverseSpectrumKey} values are defined for the theme`, - ); - - return { - ...theme, - activeColorScheme: activeColorScheme, - spectrum: theme[activeSpectrumKey], - color: theme[activeColorKey], - }; - }, [theme, activeColorScheme]); - - return ( - - - - {children} - - - - ); -}; +export const ThemeProvider = memo( + ({ + theme, + activeColorScheme, + children, + className, + display, + style, + motionFeatures, + forceInjectThemeCss, + }: ThemeProviderProps) => { + const themeApi = useMemo(() => { + const activeSpectrumKey = activeColorScheme === 'dark' ? 'darkSpectrum' : 'lightSpectrum'; + const activeColorKey = activeColorScheme === 'dark' ? 'darkColor' : 'lightColor'; + const inverseSpectrumKey = activeColorScheme === 'dark' ? 'lightSpectrum' : 'darkSpectrum'; + const inverseColorKey = activeColorScheme === 'dark' ? 'lightColor' : 'darkColor'; + + if (!theme[activeColorKey]) + throw Error( + `ThemeProvider activeColorScheme is ${activeColorScheme} but no ${activeColorScheme} colors are defined for the theme. See the docs https://cds.coinbase.com/getting-started/theming`, + ); + + if (!theme[activeSpectrumKey]) + throw Error( + `ThemeProvider activeColorScheme is ${activeColorScheme} but no ${activeSpectrumKey} values are defined for the theme. See the docs https://cds.coinbase.com/getting-started/theming`, + ); + + if (theme[inverseSpectrumKey] && !theme[inverseColorKey]) + throw Error( + `ThemeProvider theme has ${inverseSpectrumKey} values defined but no ${inverseColorKey} colors are defined for the theme. See the docs https://cds.coinbase.com/getting-started/theming`, + ); + + if (theme[inverseColorKey] && !theme[inverseSpectrumKey]) + throw Error( + `ThemeProvider theme has ${inverseColorKey} colors defined but no ${inverseSpectrumKey} values are defined for the theme. See the docs https://cds.coinbase.com/getting-started/theming`, + ); + + return { + ...theme, + activeColorScheme: activeColorScheme, + spectrum: theme[activeSpectrumKey], + color: theme[activeColorKey], + }; + }, [theme, activeColorScheme]); + + const parentTheme = useContext(ThemeContext); + + const partialTheme = useMemo( + () => (forceInjectThemeCss ? themeApi : diffThemes(themeApi, parentTheme)), + [themeApi, parentTheme, forceInjectThemeCss], + ); + + return ( + + + + {children} + + + + ); + }, +); export type InvertedThemeProviderProps = Pick< ThemeManagerProps, @@ -114,27 +147,26 @@ export type InvertedThemeProviderProps = Pick< }; /** Falls back to the currently active colorScheme if the inverse colors are not defined in the theme. */ -export const InvertedThemeProvider = ({ - children, - display, - className, - style, -}: InvertedThemeProviderProps) => { - const context = useContext(ThemeContext); - if (!context) throw Error('InvertedThemeProvider must be used within a ThemeProvider'); - const inverseColorScheme = context.activeColorScheme === 'dark' ? 'light' : 'dark'; - const inverseColorKey = context.activeColorScheme === 'dark' ? 'lightColor' : 'darkColor'; - const newColorScheme = context[inverseColorKey] ? inverseColorScheme : context.activeColorScheme; - - return ( - - {children} - - ); -}; +export const InvertedThemeProvider = memo( + ({ children, display, className, style }: InvertedThemeProviderProps) => { + const context = useContext(ThemeContext); + if (!context) throw Error('InvertedThemeProvider must be used within a ThemeProvider'); + const inverseColorScheme = context.activeColorScheme === 'dark' ? 'light' : 'dark'; + const inverseColorKey = context.activeColorScheme === 'dark' ? 'lightColor' : 'darkColor'; + const newColorScheme = context[inverseColorKey] + ? inverseColorScheme + : context.activeColorScheme; + + return ( + + {children} + + ); + }, +); diff --git a/packages/web/src/visualizations/ProgressCircle.tsx b/packages/web/src/visualizations/ProgressCircle.tsx index 5df23462f..459b3e47e 100644 --- a/packages/web/src/visualizations/ProgressCircle.tsx +++ b/packages/web/src/visualizations/ProgressCircle.tsx @@ -29,7 +29,7 @@ export type ProgressCircleBaseProps = ProgressBaseProps & { */ hideContent?: boolean; /** - * @deprecated Use hideContent instead + * @deprecated Use `hideContent` instead. This prop will be removed in a future version. * Toggle used to hide the text rendered inside the circle. */ hideText?: boolean;