Skip to content

Commit

Permalink
feat(widget): use theme colors from URL (#4188)
Browse files Browse the repository at this point in the history
* feat(widget): use theme colors from URL

* fix: dark theme for skeleton

* chore: fix useColorMode

* chore: rename isCowSwapWidgetPalette

* refactor: use json instead of keys

* fix: update color palette
  • Loading branch information
shoom3301 authored Apr 10, 2024
1 parent e3e45e1 commit 1623b37
Show file tree
Hide file tree
Showing 21 changed files with 172 additions and 92 deletions.
23 changes: 23 additions & 0 deletions apps/cowswap-frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,15 @@
flex-flow: column wrap;
}

#skeleton.skeleton-dark {
--colorBorder: rgb(236 241 248 / 15%);
--colorShimmerColor1: rgba(236, 241, 248, 0) 0;
--colorShimmerColor2: rgba(236, 241, 248, 0.1) 20%;
--colorShimmerColor3: rgba(255, 255, 255, 0.2) 50%;
--colorShimmerColor4: rgba(236, 241, 248, 0.1) 80%;
--colorShimmerColor5: rgba(236, 241, 248, 0) 100%;
}

@media (prefers-color-scheme: dark) {
#skeleton {
--colorBorder: rgb(236 241 248 / 15%);
Expand Down Expand Up @@ -200,6 +209,20 @@
</div>
</div>

<script>
(function(){
const search = window.location.hash ? window.location.hash.split('?')[1] : ''
const searchParams = new URLSearchParams(search)
const theme = searchParams.get('theme')

if (theme === 'dark') {
const skeleton = document.getElementById('skeleton')

if (skeleton) skeleton.classList.add('skeleton-dark')
}
})()
</script>

<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
Expand Down
9 changes: 8 additions & 1 deletion apps/cowswap-frontend/src/legacy/theme/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {

import { useInjectedWidgetPalette } from 'modules/injectedWidget'

import { ThemeFromUrlUpdater } from 'common/updaters/ThemeFromUrlUpdater'

import { mapWidgetTheme } from './mapWidgetTheme'
import { Colors } from './styled'

Expand Down Expand Up @@ -189,7 +191,12 @@ export default function ThemeProvider({ children }: { children?: React.ReactNode
return defaultTheme
}, [darkMode, injectedWidgetTheme])

return <StyledComponentsThemeProvider theme={themeObject}>{children}</StyledComponentsThemeProvider>
return (
<>
<ThemeFromUrlUpdater />
<StyledComponentsThemeProvider theme={themeObject}>{children}</StyledComponentsThemeProvider>
</>
)
}

export const FixedGlobalStyle = FixedGlobalStyleBase
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import {
} from 'common/updaters/orders'
import { SpotPricesUpdater } from 'common/updaters/orders/SpotPricesUpdater'
import { SentryUpdater } from 'common/updaters/SentryUpdater'
import { ThemeFromUrlUpdater } from 'common/updaters/ThemeFromUrlUpdater'
import { UserUpdater } from 'common/updaters/UserUpdater'

export function Updaters() {
Expand Down Expand Up @@ -60,7 +59,6 @@ export function Updaters() {
<EthFlowSlippageUpdater />
<EthFlowDeadlineUpdater />
<SpotPricesUpdater />
<ThemeFromUrlUpdater />
<InjectedWidgetUpdater />
<CowEventsUpdater />
<TotalSurplusUpdater />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import type { CowSwapWidgetPalette } from '@cowprotocol/widget-lib'
import { useMemo } from 'react'

import { useInjectedWidgetParams } from './useInjectedWidgetParams'
import { CowSwapWidgetPaletteParams } from '@cowprotocol/widget-lib'

import { useLocation } from 'react-router-dom'

// The theme palette provided by a consumer
export function useInjectedWidgetPalette(): Partial<CowSwapWidgetPalette> | undefined {
const state = useInjectedWidgetParams()
export function useInjectedWidgetPalette(): Partial<CowSwapWidgetPaletteParams> | undefined {
const { search } = useLocation()

return isCowSwapWidgetPallet(state.theme) ? state.theme : undefined
}
return useMemo(() => {
const searchParams = new URLSearchParams(search)
const palette = searchParams.get('palette')

if (!palette) return undefined

function isCowSwapWidgetPallet(palette: any): palette is CowSwapWidgetPalette {
return palette && typeof palette === 'object'
try {
return JSON.parse(decodeURIComponent(palette))
} catch (e) {
console.error('Failed to parse palette from URL', e)
}
}, [search])
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useAtomValue } from 'jotai'

import type { CowSwapWidgetParams } from '@cowprotocol/widget-lib'
import { CowSwapWidgetAppParams } from '@cowprotocol/widget-lib'

import { injectedWidgetParamsAtom } from '../state/injectedWidgetParamsAtom'

export function useInjectedWidgetParams(): Partial<CowSwapWidgetParams> {
export function useInjectedWidgetParams(): Partial<CowSwapWidgetAppParams> {
const { params } = useAtomValue(injectedWidgetParamsAtom)

return params
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { atom } from 'jotai'

import type { CowSwapWidgetParams } from '@cowprotocol/widget-lib'
import { CowSwapWidgetAppParams } from '@cowprotocol/widget-lib'

export type WidgetParamsErrors = Partial<{ [key in keyof CowSwapWidgetParams]: string[] | undefined }>
export type WidgetParamsErrors = Partial<{ [key in keyof CowSwapWidgetAppParams]: string[] | undefined }>

export const injectedWidgetParamsAtom = atom<{ params: Partial<CowSwapWidgetParams>; errors: WidgetParamsErrors }>({
export const injectedWidgetParamsAtom = atom<{ params: Partial<CowSwapWidgetAppParams>; errors: WidgetParamsErrors }>({
params: {},
errors: {},
})
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useEffect, useRef } from 'react'
import { useFeatureFlags } from '@cowprotocol/common-hooks'
import { deepEqual } from '@cowprotocol/common-utils'
import {
CowSwapWidgetParams,
CowSwapWidgetAppParams,
listenToMessageFromWindow,
postMessageToWindow,
stopListeningWindowListener,
Expand Down Expand Up @@ -36,7 +36,7 @@ const cacheMessages = (event: MessageEvent) => {
messagesCache[method] = event.data
}

const paramsWithoutPartnerFee = (params: CowSwapWidgetParams) => {
const paramsWithoutPartnerFee = (params: CowSwapWidgetAppParams) => {
const { partnerFee: _, ...rest } = params

return rest
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { CowSwapWidgetParams } from '@cowprotocol/widget-lib'
import { CowSwapWidgetAppParams } from '@cowprotocol/widget-lib'

import { validatePartnerFee } from './validatePartnerFee'

import { WidgetParamsErrors } from '../state/injectedWidgetParamsAtom'

type Keys = keyof CowSwapWidgetParams
type Keys = keyof CowSwapWidgetAppParams

const VALIDATIONS: Partial<{ [key in Keys]: (param: CowSwapWidgetParams[key]) => string[] | undefined }> = {
const VALIDATIONS: Partial<{ [key in Keys]: (param: CowSwapWidgetAppParams[key]) => string[] | undefined }> = {
partnerFee: validatePartnerFee,
}

export function validateWidgetParams(params: CowSwapWidgetParams): WidgetParamsErrors {
export function validateWidgetParams(params: CowSwapWidgetAppParams): WidgetParamsErrors {
const keys = Object.keys(params) as Keys[]

return keys.reduce<WidgetParamsErrors>((acc, key) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import imageConnectWallet from '@cowprotocol/assets/cow-swap/wallet-plus.svg'
import { isInjectedWidget } from '@cowprotocol/common-utils'
import { ExternalLink } from '@cowprotocol/ui'
import { UI, CowSwapSafeAppLink, MY_ORDERS_ID } from '@cowprotocol/ui'
import type { CowSwapWidgetParams } from '@cowprotocol/widget-lib'
import type { CowSwapWidgetAppParams } from '@cowprotocol/widget-lib'

import { Trans } from '@lingui/macro'
import SVG from 'react-inlinesvg'
Expand Down Expand Up @@ -175,7 +175,7 @@ interface OrdersProps extends OrdersTabsProps, OrdersTableProps {
pendingActivities: string[]
children?: ReactNode
orderType: TabOrderTypes
injectedWidgetParams: Partial<CowSwapWidgetParams>
injectedWidgetParams: Partial<CowSwapWidgetAppParams>
}

export function OrdersTableContainer({
Expand Down
4 changes: 2 additions & 2 deletions apps/cowswap-frontend/src/modules/sounds/utils/sound.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { CHRISTMAS_THEME_ENABLED } from '@cowprotocol/common-const'
import { jotaiStore } from '@cowprotocol/core'
import { CowSwapWidgetParams } from '@cowprotocol/widget-lib'
import { CowSwapWidgetAppParams } from '@cowprotocol/widget-lib'

import { injectedWidgetParamsAtom } from 'modules/injectedWidget/state/injectedWidgetParamsAtom'

type SoundType = 'SEND' | 'SUCCESS' | 'ERROR'
type Sounds = Record<SoundType, string>
type WidgetSounds = keyof NonNullable<CowSwapWidgetParams['sounds']>
type WidgetSounds = keyof NonNullable<CowSwapWidgetAppParams['sounds']>

const COW_SOUNDS: Sounds = {
SEND: CHRISTMAS_THEME_ENABLED ? '/audio/send-winterTheme.mp3' : '/audio/send.mp3',
Expand Down
6 changes: 3 additions & 3 deletions apps/widget-configurator/src/app/configurator/consts.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CowEventListeners, CowEvents, ToastMessageType } from '@cowprotocol/events'
import { TradeType, TokenInfo } from '@cowprotocol/widget-lib'
import { TradeType, TokenInfo, CowSwapWidgetPaletteParams } from '@cowprotocol/widget-lib'

import { TokenListItem } from './types'

Expand Down Expand Up @@ -36,7 +36,7 @@ export const DEFAULT_TOKEN_LISTS: TokenListItem[] = [
]
// TODO: Move default palette to a new lib that only exposes the palette colors.
// This wayit can be consumed by both the configurator and the widget.
export const DEFAULT_LIGHT_PALETTE = {
export const DEFAULT_LIGHT_PALETTE: CowSwapWidgetPaletteParams = {
primary: '#052b65',
background: '#FFFFFF',
paper: '#FFFFFF',
Expand All @@ -48,7 +48,7 @@ export const DEFAULT_LIGHT_PALETTE = {
success: '#007B28',
}

export const DEFAULT_DARK_PALETTE = {
export const DEFAULT_DARK_PALETTE: CowSwapWidgetPaletteParams = {
primary: '#0d5ed9',
background: '#303030',
paper: '#0c264b',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import { ColorPalette } from '../types'

const LOCAL_STORAGE_KEY_NAME = 'COW_WIDGET_PALETTE_'

const getCachedPalette = (mode: PaletteMode) => {
const cache = localStorage.getItem(`${LOCAL_STORAGE_KEY_NAME}${mode}`)

return cache ? JSON.parse(cache) : null
}

export interface ColorPaletteManager {
defaultPalette: ColorPalette
colorPalette: ColorPalette
Expand All @@ -19,7 +25,7 @@ export function useColorPaletteManager(mode: PaletteMode): ColorPaletteManager {
return mode === 'dark' ? DEFAULT_DARK_PALETTE : DEFAULT_LIGHT_PALETTE
}, [mode])

const [colorPalette, updateColorPalette] = useState<ColorPalette>(defaultPalette)
const [colorPalette, updateColorPalette] = useState<ColorPalette>(getCachedPalette(mode) || defaultPalette)

const persistPalette = useCallback(
(colorPalette: ColorPalette) => {
Expand All @@ -44,11 +50,9 @@ export function useColorPaletteManager(mode: PaletteMode): ColorPaletteManager {

// Restore palette from localStorage when mode changes
useEffect(() => {
const savedPalette = localStorage.getItem(`${LOCAL_STORAGE_KEY_NAME}${mode}`)

const newPalette = savedPalette ? JSON.parse(savedPalette) : defaultPalette
const newPalette = getCachedPalette(mode)

updateColorPalette(newPalette)
updateColorPalette(newPalette || defaultPalette)
}, [mode, defaultPalette])

return { defaultPalette, colorPalette, setColorPalette, resetColorPalette }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,21 @@ export function useWidgetParams(configuratorState: ConfiguratorState): CowSwapWi
sell: { asset: sellToken, amount: sellTokenAmount ? sellTokenAmount.toString() : undefined },
buy: { asset: buyToken, amount: buyTokenAmount?.toString() },
enabledTradeTypes,
theme: {
baseTheme: theme,
primary: themeColors.primary,
background: themeColors.background,
paper: themeColors.paper,
text: themeColors.text,
danger: themeColors.danger,
warning: themeColors.warning,
alert: themeColors.alert,
info: themeColors.info,
success: themeColors.success,
},
theme:
JSON.stringify(customColors) === JSON.stringify(defaultColors)
? theme
: {
baseTheme: theme,
primary: themeColors.primary,
background: themeColors.background,
paper: themeColors.paper,
text: themeColors.text,
danger: themeColors.danger,
warning: themeColors.warning,
alert: themeColors.alert,
info: themeColors.info,
success: themeColors.success,
},

standaloneMode,
disableToastMessages,
Expand Down
15 changes: 2 additions & 13 deletions apps/widget-configurator/src/app/configurator/types.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,10 @@
import type { SupportedChainId } from '@cowprotocol/cow-sdk'
import type { TradeType } from '@cowprotocol/widget-lib'
import { CowSwapWidgetPaletteColors, TradeType } from '@cowprotocol/widget-lib'

import { PaletteMode } from '@mui/material'

export type ColorKeys =
| 'primary'
| 'background'
| 'paper'
| 'text'
| 'danger'
| 'warning'
| 'alert'
| 'info'
| 'success'

export type ColorPalette = {
[key in ColorKeys]: string
[key in CowSwapWidgetPaletteColors]: string
}

export interface TokenListItem {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CowSwapWidgetPalette, CowSwapWidgetParams } from '@cowprotocol/widget-lib'
import { CowSwapWidgetPalette, CowSwapWidgetPaletteColors, CowSwapWidgetParams } from '@cowprotocol/widget-lib'

import { ColorKeys, ColorPalette } from '../../configurator/types'
import { ColorPalette } from '../../configurator/types'
import { SANITIZE_PARAMS } from '../const'

export function sanitizeParameters(params: CowSwapWidgetParams, defaultPalette: ColorPalette) {
Expand All @@ -18,7 +18,7 @@ function sanitizePalette(params: CowSwapWidgetParams, defaultPalette: ColorPalet
const palette = params.theme

const paletteDiff = Object.keys(palette).reduce((acc, key: string) => {
const colorKey = key as ColorKeys
const colorKey = key as CowSwapWidgetPaletteColors

if (defaultPalette[colorKey] !== palette[colorKey]) {
acc[colorKey] = palette[colorKey]
Expand Down
17 changes: 13 additions & 4 deletions apps/widget-configurator/src/theme/hooks/useColorMode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,30 @@ import useMediaQuery from '@mui/material/useMediaQuery'

import { ColorModeParams } from '../ColorModeContext'

const THEME_STORAGE_KEY = 'widget-cfg-theme'

const getThemeFromCache = () => localStorage.getItem(THEME_STORAGE_KEY) as PaletteMode

export function useColorMode(): ColorModeParams {
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)')
const [mode, setMode] = useState<PaletteMode>(prefersDarkMode ? 'dark' : 'light')
const [mode, setMode] = useState<PaletteMode>(getThemeFromCache() || (prefersDarkMode ? 'dark' : 'light'))

const updateMode = (mode: PaletteMode) => {
setMode(mode)
localStorage.setItem(THEME_STORAGE_KEY, mode)
}

return useMemo(
() => ({
mode,
setMode: (mode: PaletteMode) => {
setMode(mode)
updateMode(mode)
},
toggleColorMode: () => {
setMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light'))
updateMode(mode === 'light' ? 'dark' : 'light')
},
setAutoMode: () => {
setMode(prefersDarkMode ? 'dark' : 'light')
updateMode(prefersDarkMode ? 'dark' : 'light')
},
}),
[mode, prefersDarkMode]
Expand Down
Loading

0 comments on commit 1623b37

Please sign in to comment.