diff --git a/.ladle/components.tsx b/.ladle/components.tsx index 04ee536..c6e7beb 100644 --- a/.ladle/components.tsx +++ b/.ladle/components.tsx @@ -9,9 +9,9 @@ import { GlobalStyle } from '@/styles/globalStyle'; import { SelectMenu, SelectItem } from '@/components/SelectMenu'; -import { AppThemeProvider } from '@/hooks/useAppTheme'; +import { AppThemeAndColorModeProvider } from '@/hooks/useAppThemeAndColorMode'; -import { AppTheme, setAppTheme } from '@/state/configs'; +import { AppTheme, AppColorMode, setAppTheme, setAppColorMode } from '@/state/configs'; import { setLocaleLoaded } from '@/state/localization'; import '@/index.css'; @@ -19,9 +19,12 @@ import './ladle.css'; export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => { const [theme, setTheme] = useState(AppTheme.Classic); + const [colorMode, setColorMode] = useState(AppColorMode.GreenUp); useEffect(() => { store.dispatch(setAppTheme(theme)); + store.dispatch(setAppColorMode(colorMode)); + switch (theme) { case AppTheme.Dark: { document?.documentElement?.classList.remove('theme-light'); @@ -38,7 +41,7 @@ export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children break; } } - }, [theme]); + }, [theme, colorMode]); useEffect(() => { store.dispatch(setLocaleLoaded(true)); @@ -48,10 +51,7 @@ export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children

Active Theme:

- + {[ { value: AppTheme.Classic, @@ -66,20 +66,31 @@ export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children label: 'Light theme', }, ].map(({ value, label }) => ( - + + ))} + +

Active Color Mode:

+ + {[ + { + value: AppColorMode.GreenUp, + label: 'Green up', + }, + { + value: AppColorMode.RedUp, + label: 'Red up', + }, + ].map(({ value, label }) => ( + ))}

- + {children} - -
+ + ); }; diff --git a/src/App.tsx b/src/App.tsx index 23d1a39..d80de5f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -16,7 +16,7 @@ import { } from '@/hooks'; import { DydxProvider } from '@/hooks/useDydxClient'; import { AccountsProvider } from '@/hooks/useAccounts'; -import { AppThemeProvider } from '@/hooks/useAppTheme'; +import { AppThemeAndColorModeProvider } from '@/hooks/useAppThemeAndColorMode'; import { DialogAreaProvider, useDialogArea } from '@/hooks/useDialogArea'; import { LocaleProvider } from '@/hooks/useLocaleSeparators'; import { NotificationsProvider } from '@/hooks/useNotifications'; @@ -141,8 +141,7 @@ const providers = [ wrapProvider(LocalNotificationsProvider), wrapProvider(NotificationsProvider), wrapProvider(DialogAreaProvider), - wrapProvider(PotentialMarketsProvider), - wrapProvider(AppThemeProvider), + wrapProvider(AppThemeAndColorModeProvider), ]; const App = () => { diff --git a/src/components/Button.tsx b/src/components/Button.tsx index dff6cdb..2d03f59 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -102,13 +102,13 @@ const buttonActionVariants = { [ButtonAction.Create]: css` --button-textColor: var(--color-text-2); - --button-backgroundColor: var(--color-positive); + --button-backgroundColor: var(--color-success); --button-border: solid var(--border-width) var(--color-border-white); `, [ButtonAction.Destroy]: css` --button-textColor: var(--color-text-2); - --button-backgroundColor: var(--color-negative); + --button-backgroundColor: var(--color-error); --button-border: solid var(--border-width) var(--color-border-white); `, @@ -119,7 +119,7 @@ const buttonActionVariants = { `, [ButtonAction.Reset]: css` - --button-textColor: var(--color-negative); + --button-textColor: var(--color-error); --button-backgroundColor: var(--color-layer-3); --button-border: solid var(--border-width) var(--color-border-red); `, diff --git a/src/components/CopyButton.tsx b/src/components/CopyButton.tsx index bff3368..33da9d1 100644 --- a/src/components/CopyButton.tsx +++ b/src/components/CopyButton.tsx @@ -87,7 +87,7 @@ Styled.Icon = styled(Icon)<{ copied: boolean }>` ${({ copied }) => copied && css` - color: var(--color-positive); + color: var(--color-success); `} `; @@ -96,7 +96,7 @@ Styled.IconButton = styled(IconButton)<{ copied: boolean }>` copied && css` svg { - color: var(--color-positive); + color: var(--color-success); } `} `; diff --git a/src/components/DiffArrow.tsx b/src/components/DiffArrow.tsx index b140042..6007683 100644 --- a/src/components/DiffArrow.tsx +++ b/src/components/DiffArrow.tsx @@ -62,6 +62,6 @@ Styled.DiffArrowContainer = styled.span` `, down: css` transform: rotate(90deg); - ` + `, }[direction || 'right'])} `; diff --git a/src/components/DiffOutput.tsx b/src/components/DiffOutput.tsx index f6f52e4..665aacf 100644 --- a/src/components/DiffOutput.tsx +++ b/src/components/DiffOutput.tsx @@ -75,7 +75,7 @@ Styled.DiffValue = styled.div<{ hasInvalidNewValue?: boolean }>` ${({ hasInvalidNewValue }) => hasInvalidNewValue && css` - color: var(--color-negative); + color: var(--color-error); `} `; diff --git a/src/components/DropdownMenu.stories.tsx b/src/components/DropdownMenu.stories.tsx index bee3c62..939ae48 100644 --- a/src/components/DropdownMenu.stories.tsx +++ b/src/components/DropdownMenu.stories.tsx @@ -6,29 +6,34 @@ import { StoryWrapper } from '.ladle/components'; export const DropdownMenuStory: Story> = (args) => { const exampleItems = [ + { + value: '0', + label: 'Item 0', + onSelect: () => alert('Item 0 action'), + }, { value: '1', - label: 'Item 1', + label: 'Item 1 (accent)', onSelect: () => alert('Item 1 action'), + highlightColor: 'accent', }, { value: '2', - label: 'Item 2', + label: 'Item 2 (create)', onSelect: () => alert('Item 2 action'), + highlightColor: 'create', }, { value: '3', - label: 'Item 3', + label: 'Item 3 (destroy)', onSelect: () => alert('Item 3 action'), + highlightColor: 'destroy', }, ]; return ( - + Menu diff --git a/src/components/DropdownMenu.tsx b/src/components/DropdownMenu.tsx index 6b74602..908e3fa 100644 --- a/src/components/DropdownMenu.tsx +++ b/src/components/DropdownMenu.tsx @@ -13,7 +13,7 @@ export type DropdownMenuItem = { label: React.ReactNode; onSelect?: () => void; separator?: boolean; - highlightColor?: 'accent' | 'positive' | 'negative'; + highlightColor?: 'accent' | 'create' | 'destroy'; }; type StyleProps = { @@ -82,7 +82,7 @@ Styled.Separator = styled(Separator)` margin: 0.25rem 1rem; `; -Styled.Item = styled(Item)<{ $highlightColor: 'accent' | 'positive' | 'negative' }>` +Styled.Item = styled(Item)<{ $highlightColor: 'accent' | 'create' | 'destroy' }>` ${popoverMixins.item} --item-font-size: var(--dropdownMenu-item-font-size); ${({ $highlightColor }) => @@ -90,11 +90,11 @@ Styled.Item = styled(Item)<{ $highlightColor: 'accent' | 'positive' | 'negative' ['accent']: ` --item-highlighted-textColor: var(--color-accent); `, - ['positive']: ` - --item-highlighted-textColor: var(--color-positive); + ['create']: ` + --item-highlighted-textColor: var(--color-success); `, - ['negative']: ` - --item-highlighted-textColor: var(--color-negative); + ['destroy']: ` + --item-highlighted-textColor: var(--color-error); `, }[$highlightColor])} diff --git a/src/components/WithConfirmationPopover.tsx b/src/components/WithConfirmationPopover.tsx index f2aabcc..b65c5a7 100644 --- a/src/components/WithConfirmationPopover.tsx +++ b/src/components/WithConfirmationPopover.tsx @@ -117,7 +117,7 @@ Styled.ConfirmButton = styled(Styled.IconButton)` --button-backgroundColor: hsla(203, 25%, 19%, 1); svg { - color: var(--color-positive); + color: var(--color-success); } `; @@ -125,7 +125,7 @@ Styled.CancelButton = styled(Styled.IconButton)` --button-backgroundColor: hsla(296, 16%, 18%, 1); svg { - color: var(--color-negative); + color: var(--color-error); width: 0.8em; height: 0.8em; diff --git a/src/constants/localStorage.ts b/src/constants/localStorage.ts index 863f24e..5ac32cb 100644 --- a/src/constants/localStorage.ts +++ b/src/constants/localStorage.ts @@ -20,6 +20,7 @@ export enum LocalStorageKey { SelectedLocale = 'dydx.SelectedLocale', SelectedNetwork = 'dydx.SelectedNetwork', SelectedTheme = 'dydx.SelectedTheme', + SelectedColorMode = 'dydx.SelectedColorMode', SelectedTradeLayout = 'dydx.SelectedTradeLayout', TradingViewChartConfig = 'dydx.TradingViewChartConfig', HasSeenLaunchIncentives = 'dydx.HasSeenLaunchIncentives', diff --git a/src/constants/styles/colors.ts b/src/constants/styles/colors.ts index a7c9eb1..dc9429e 100644 --- a/src/constants/styles/colors.ts +++ b/src/constants/styles/colors.ts @@ -1,4 +1,11 @@ -export type ThemeColors = LayerColors & +import { AppColorMode } from '@/state/configs'; + +export type Theme = { + [AppColorMode.GreenUp]: ThemeColorBase; + [AppColorMode.RedUp]: ThemeColorBase; +}; + +export type ThemeColorBase = LayerColors & BorderColors & TextColors & GradientColors & @@ -47,8 +54,13 @@ type StatusColors = { success: string; warning: string; error: string; + successFaded: string; + warningFaded: string; + errorFaded: string; }; +/** ##InvertDirectionalColors + * When adding colors here, make sure to update linked function to invert colors for AppColorMode. */ type DirectionalColors = { positive: string; negative: string; diff --git a/src/hooks/Orderbook/useDrawOrderbook.ts b/src/hooks/Orderbook/useDrawOrderbook.ts index 887d3f3..7577796 100644 --- a/src/hooks/Orderbook/useDrawOrderbook.ts +++ b/src/hooks/Orderbook/useDrawOrderbook.ts @@ -12,8 +12,9 @@ import { ORDERBOOK_WIDTH, } from '@/constants/orderbook'; +import { useAppThemeAndColorModeContext } from '@/hooks/useAppThemeAndColorMode'; + import { getCurrentMarketConfig, getCurrentMarketOrderbookMap } from '@/state/perpetualsSelectors'; -import { getAppTheme } from '@/state/configsSelectors'; import { MustBigNumber } from '@/lib/numbers'; @@ -23,7 +24,6 @@ import { getXByColumn, getYForElements, } from '@/lib/orderbookHelpers'; -import { useAppThemeContext } from '../useAppTheme'; type ElementProps = { data: Array; @@ -53,7 +53,7 @@ export const useDrawOrderbook = ({ const { stepSizeDecimals = TOKEN_DECIMALS, tickSizeDecimals = SMALL_USD_DECIMALS } = useSelector(getCurrentMarketConfig, shallowEqual) || {}; const prevData = useRef(data); - const theme = useAppThemeContext(); + const theme = useAppThemeAndColorModeContext(); /** * Scale canvas using device pixel ratio to unblur drawn text diff --git a/src/hooks/tradingView/useTradingView.ts b/src/hooks/tradingView/useTradingView.ts index 756ecc2..68065d8 100644 --- a/src/hooks/tradingView/useTradingView.ts +++ b/src/hooks/tradingView/useTradingView.ts @@ -11,7 +11,7 @@ import { useDydxClient, useLocalStorage } from '@/hooks'; import { store } from '@/state/_store'; import { getSelectedNetwork } from '@/state/appSelectors'; -import { getAppTheme } from '@/state/configsSelectors'; +import { getAppTheme, getAppColorMode } from '@/state/configsSelectors'; import { getSelectedLocale } from '@/state/localizationSelectors'; import { getCurrentMarketId, getMarketIds } from '@/state/perpetualsSelectors'; @@ -30,6 +30,7 @@ export const useTradingView = ({ }) => { const marketId = useSelector(getCurrentMarketId); const appTheme = useSelector(getAppTheme); + const appColorMode = useSelector(getAppColorMode); const marketIds = useSelector(getMarketIds, shallowEqual); const selectedLocale = useSelector(getSelectedLocale); const selectedNetwork = useSelector(getSelectedNetwork); @@ -46,7 +47,7 @@ export const useTradingView = ({ useEffect(() => { if (hasMarkets && isClientConnected && marketId) { const widgetOptions = getWidgetOptions(); - const widgetOverrides = getWidgetOverrides(appTheme); + const widgetOverrides = getWidgetOverrides({ appTheme, appColorMode }); const options = { // debug: true, ...widgetOptions, @@ -75,7 +76,14 @@ export const useTradingView = ({ tvWidgetRef.current = null; setIsChartReady(false); }; - }, [getCandlesForDatafeed, isClientConnected, hasMarkets, selectedLocale, selectedNetwork, !!marketId]); + }, [ + getCandlesForDatafeed, + isClientConnected, + hasMarkets, + selectedLocale, + selectedNetwork, + !!marketId, + ]); return { savedResolution }; }; diff --git a/src/hooks/tradingView/useTradingViewTheme.ts b/src/hooks/tradingView/useTradingViewTheme.ts index 6436063..b0ed677 100644 --- a/src/hooks/tradingView/useTradingViewTheme.ts +++ b/src/hooks/tradingView/useTradingViewTheme.ts @@ -3,8 +3,8 @@ import { useSelector } from 'react-redux'; import type { IChartingLibraryWidget, ThemeName } from 'public/tradingview/charting_library'; -import { AppTheme } from '@/state/configs'; -import { getAppTheme } from '@/state/configsSelectors'; +import { AppColorMode, AppTheme } from '@/state/configs'; +import { getAppTheme, getAppColorMode } from '@/state/configsSelectors'; import { getWidgetOverrides } from '@/lib/tradingView/utils'; @@ -29,6 +29,7 @@ export const useTradingViewTheme = ({ isWidgetReady?: boolean; }) => { const appTheme: AppTheme = useSelector(getAppTheme); + const appColorMode: AppColorMode = useSelector(getAppColorMode); useEffect(() => { if (tvWidget && isWidgetReady) { @@ -55,10 +56,24 @@ export const useTradingViewTheme = ({ } } - const { overrides, studies_overrides } = getWidgetOverrides(appTheme); + const { overrides, studies_overrides } = getWidgetOverrides({ appTheme, appColorMode }); tvWidget?.applyOverrides(overrides); tvWidget?.applyStudiesOverrides(studies_overrides); + + // Necessary to update existing indicators + const volumeStudyId = tvWidget + ?.activeChart() + ?.getAllStudies() + ?.find((x) => x.name === 'Volume')?.id; + + if (volumeStudyId) { + const volume = tvWidget?.activeChart()?.getStudyById(volumeStudyId); + volume.applyOverrides({ + 'volume.color.0': studies_overrides['volume.volume.color.0'], + 'volume.color.1': studies_overrides['volume.volume.color.1'], + }); + } }); } - }, [appTheme]); + }, [appTheme, appColorMode]); }; diff --git a/src/hooks/useAppTheme.tsx b/src/hooks/useAppTheme.tsx deleted file mode 100644 index 790cc89..0000000 --- a/src/hooks/useAppTheme.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { useSelector } from 'react-redux'; -import { ThemeProvider } from 'styled-components'; - -import { AppTheme } from '@/state/configs'; -import { getAppTheme } from '@/state/configsSelectors'; - -import { Themes } from '@/styles/themes'; - -export const AppThemeProvider = ({ ...props }) => { - return -}; - -export const useAppThemeContext = () => { - const theme: AppTheme = useSelector(getAppTheme); - return Themes[theme]; -} diff --git a/src/hooks/useAppThemeAndColorMode.tsx b/src/hooks/useAppThemeAndColorMode.tsx new file mode 100644 index 0000000..08572fa --- /dev/null +++ b/src/hooks/useAppThemeAndColorMode.tsx @@ -0,0 +1,18 @@ +import { useSelector } from 'react-redux'; +import { ThemeProvider } from 'styled-components'; + +import { AppTheme, AppColorMode } from '@/state/configs'; +import { getAppTheme, getAppColorMode } from '@/state/configsSelectors'; + +import { Themes } from '@/styles/themes'; + +export const AppThemeAndColorModeProvider = ({ ...props }) => { + return ; +}; + +export const useAppThemeAndColorModeContext = () => { + const theme: AppTheme = useSelector(getAppTheme); + const colorMode: AppColorMode = useSelector(getAppColorMode); + + return Themes[theme][colorMode]; +}; diff --git a/src/icons/logo-short.tsx b/src/icons/logo-short.tsx index 81ffe2f..a681fdc 100644 --- a/src/icons/logo-short.tsx +++ b/src/icons/logo-short.tsx @@ -1,7 +1,7 @@ -import { useAppThemeContext } from '@/hooks/useAppTheme'; +import { useAppThemeAndColorModeContext } from '@/hooks/useAppThemeAndColorMode'; const LogoShortIcon: React.FC<{ id?: string }> = ({ id }: { id?: string }) => { - const theme = useAppThemeContext(); + const theme = useAppThemeAndColorModeContext(); const fill = theme.logoFill; return ( diff --git a/src/layout/Footer/FooterDesktop.tsx b/src/layout/Footer/FooterDesktop.tsx index d6f3d1c..7426cd2 100644 --- a/src/layout/Footer/FooterDesktop.tsx +++ b/src/layout/Footer/FooterDesktop.tsx @@ -127,7 +127,7 @@ Styled.StatusDot = styled.div<{ exchangeStatus: ExchangeStatus }>` background-color: ${({ exchangeStatus }) => ({ [ExchangeStatus.Degraded]: css`var(--color-warning)`, - [ExchangeStatus.Operational]: css`var(--color-positive)`, + [ExchangeStatus.Operational]: css`var(--color-success)`, }[exchangeStatus])}; `; diff --git a/src/lib/orders.ts b/src/lib/orders.ts index 1aeb926..55c0e46 100644 --- a/src/lib/orders.ts +++ b/src/lib/orders.ts @@ -43,19 +43,19 @@ export const getStatusIconInfo = ({ case AbacusOrderStatus.filled: { return { statusIcon: IconName.OrderFilled, - statusIconColor: `var(--color-positive)`, + statusIconColor: `var(--color-success)`, }; } case AbacusOrderStatus.cancelled: { return { statusIcon: IconName.OrderCanceled, - statusIconColor: `var(--color-negative)`, + statusIconColor: `var(--color-error)`, }; } case AbacusOrderStatus.canceling: { return { statusIcon: IconName.OrderPending, - statusIconColor: `var(--color-negative)`, + statusIconColor: `var(--color-error)`, }; } case AbacusOrderStatus.untriggered: { diff --git a/src/lib/tradingView/utils.ts b/src/lib/tradingView/utils.ts index 5a50209..27c26b6 100644 --- a/src/lib/tradingView/utils.ts +++ b/src/lib/tradingView/utils.ts @@ -1,6 +1,6 @@ import { Candle, TradingViewBar, TradingViewSymbol } from '@/constants/candles'; -import { AppTheme } from '@/state/configs'; +import { AppTheme, AppColorMode } from '@/state/configs'; import { Themes } from '@/styles/themes'; @@ -47,8 +47,14 @@ export const getHistorySlice = ({ return bars.filter(({ time }) => time >= fromMs); }; -export const getWidgetOverrides = (appTheme: AppTheme) => { - const theme = Themes[appTheme]; +export const getWidgetOverrides = ({ + appTheme, + appColorMode, +}: { + appTheme: AppTheme; + appColorMode: AppColorMode; +}) => { + const theme = Themes[appTheme][appColorMode]; return { overrides: { diff --git a/src/pages/Profile.tsx b/src/pages/Profile.tsx index ff362bd..270131b 100644 --- a/src/pages/Profile.tsx +++ b/src/pages/Profile.tsx @@ -286,10 +286,10 @@ Styled.ConnectedIcon = styled.div` height: 0.5rem; width: 0.5rem; margin-right: 0.25rem; - background: var(--color-positive); + background: var(--color-success); border-radius: 50%; - box-shadow: 0 0 0 0.2rem var(--color-gradient-positive); + box-shadow: 0 0 0 0.2rem var(--color-gradient-success); `; Styled.Address = styled.h1` @@ -318,7 +318,7 @@ Styled.ActionButton = styled(IconButton)<{ iconName?: IconName }>` ${({ iconName }) => iconName === IconName.Close ? css` - --button-textColor: var(--color-negative); + --button-textColor: var(--color-error); --button-icon-size: 0.75em; ` : iconName === IconName.Transfer && diff --git a/src/state/configs.ts b/src/state/configs.ts index 9da0355..db1b3e7 100644 --- a/src/state/configs.ts +++ b/src/state/configs.ts @@ -12,8 +12,14 @@ export enum AppTheme { Light = 'Light', } +export enum AppColorMode { + GreenUp = 'GreenUp', + RedUp = 'RedUp', +} + export interface ConfigsState { appTheme: AppTheme; + appColorMode: AppColorMode; feeTiers?: kollections.List; feeDiscounts?: FeeDiscount[]; network?: NetworkConfigs; @@ -41,6 +47,10 @@ const initialState: ConfigsState = { key: LocalStorageKey.SelectedTheme, defaultValue: AppTheme.Classic, }), + appColorMode: getLocalStorage({ + key: LocalStorageKey.SelectedColorMode, + defaultValue: AppColorMode.GreenUp, + }), feeDiscounts: undefined, feeTiers: undefined, network: undefined, @@ -61,6 +71,10 @@ export const configsSlice = createSlice({ changeTheme(payload); state.appTheme = payload; }, + setAppColorMode: (state: ConfigsState, { payload }: PayloadAction) => { + setLocalStorage({ key: LocalStorageKey.SelectedColorMode, value: payload }); + state.appColorMode = payload; + }, setConfigs: (state: ConfigsState, action: PayloadAction>) => ({ ...state, ...action.payload, @@ -72,4 +86,5 @@ export const configsSlice = createSlice({ }, }); -export const { setAppTheme, setConfigs, markLaunchIncentivesSeen } = configsSlice.actions; +export const { setAppTheme, setAppColorMode, setConfigs, markLaunchIncentivesSeen } = + configsSlice.actions; diff --git a/src/state/configsSelectors.ts b/src/state/configsSelectors.ts index d83bd84..51dc198 100644 --- a/src/state/configsSelectors.ts +++ b/src/state/configsSelectors.ts @@ -2,6 +2,8 @@ import type { RootState } from './_store'; export const getAppTheme = (state: RootState) => state.configs.appTheme; +export const getAppColorMode = (state: RootState) => state.configs.appColorMode; + export const getFeeTiers = (state: RootState) => state.configs.feeTiers?.toArray(); export const getFeeDiscounts = (state: RootState) => state.configs.feeDiscounts; diff --git a/src/styles/globalStyle.ts b/src/styles/globalStyle.ts index bf534cd..44b284d 100644 --- a/src/styles/globalStyle.ts +++ b/src/styles/globalStyle.ts @@ -29,6 +29,9 @@ export const GlobalStyle = createGlobalStyle` --color-success: ${({ theme }) => theme.success}; --color-warning: ${({ theme }) => theme.warning}; --color-error: ${({ theme }) => theme.error}; + --color-gradient-success: ${({ theme }) => theme.successFaded}; + --color-gradient-warning: ${({ theme }) => theme.warningFaded}; + --color-gradient-error: ${({ theme }) => theme.errorFaded}; --color-positive: ${({ theme }) => theme.positive}; --color-negative: ${({ theme }) => theme.negative}; diff --git a/src/styles/themes.ts b/src/styles/themes.ts index 4b910c5..6418027 100644 --- a/src/styles/themes.ts +++ b/src/styles/themes.ts @@ -1,9 +1,9 @@ -import { AppTheme } from '@/state/configs'; -import type { ThemeColors } from '@/constants/styles/colors'; +import { AppTheme, AppColorMode } from '@/state/configs'; +import type { Theme, ThemeColorBase } from '@/constants/styles/colors'; import { ColorToken, OpacityToken } from '@/constants/styles/base'; import { generateFadedColorVariant } from '@/lib/styles'; -const ClassicTheme: ThemeColors = { +const ClassicThemeBase: ThemeColorBase = { layer0: ColorToken.GrayBlue7, layer1: ColorToken.GrayBlue6, layer2: ColorToken.GrayBlue5, @@ -31,6 +31,9 @@ const ClassicTheme: ThemeColors = { success: ColorToken.Green1, warning: ColorToken.Yellow0, error: ColorToken.Red2, + successFaded: generateFadedColorVariant(ColorToken.Green1, OpacityToken.Opacity16), + warningFaded: generateFadedColorVariant(ColorToken.Yellow0, OpacityToken.Opacity16), + errorFaded: generateFadedColorVariant(ColorToken.Red2, OpacityToken.Opacity16), positive: ColorToken.Green1, negative: ColorToken.Red2, @@ -52,7 +55,7 @@ const ClassicTheme: ThemeColors = { tooltipBackground: generateFadedColorVariant(ColorToken.GrayBlue3, OpacityToken.Opacity66), }; -const DarkTheme: ThemeColors = { +const DarkThemeBase: ThemeColorBase = { layer0: ColorToken.Black, layer1: ColorToken.DarkGray11, layer2: ColorToken.DarkGray13, @@ -80,6 +83,9 @@ const DarkTheme: ThemeColors = { success: ColorToken.Green0, warning: ColorToken.Yellow0, error: ColorToken.Red0, + successFaded: generateFadedColorVariant(ColorToken.Green0, OpacityToken.Opacity16), + warningFaded: generateFadedColorVariant(ColorToken.Yellow0, OpacityToken.Opacity16), + errorFaded: generateFadedColorVariant(ColorToken.Red0, OpacityToken.Opacity16), positive: ColorToken.Green0, negative: ColorToken.Red0, @@ -101,7 +107,7 @@ const DarkTheme: ThemeColors = { tooltipBackground: generateFadedColorVariant(ColorToken.DarkGray6, OpacityToken.Opacity66), }; -const LightTheme: ThemeColors = { +const LightThemeBase: ThemeColorBase = { layer0: ColorToken.White, layer1: ColorToken.LightGray6, layer2: ColorToken.White, @@ -129,6 +135,9 @@ const LightTheme: ThemeColors = { success: ColorToken.Green2, warning: ColorToken.Yellow0, error: ColorToken.Red1, + successFaded: generateFadedColorVariant(ColorToken.Green2, OpacityToken.Opacity16), + warningFaded: generateFadedColorVariant(ColorToken.Yellow0, OpacityToken.Opacity16), + errorFaded: generateFadedColorVariant(ColorToken.Red1, OpacityToken.Opacity16), positive: ColorToken.Green2, negative: ColorToken.Red1, @@ -150,8 +159,22 @@ const LightTheme: ThemeColors = { tooltipBackground: generateFadedColorVariant(ColorToken.LightGray7, OpacityToken.Opacity66), }; +const generateTheme = (themeBase: ThemeColorBase): Theme => { + return { + [AppColorMode.GreenUp]: themeBase, + [AppColorMode.RedUp]: { + ...themeBase, + // #InvertDirectionalColors + positive: themeBase.negative, + negative: themeBase.positive, + positiveFaded: themeBase.negativeFaded, + negativeFaded: themeBase.positiveFaded, + }, + }; +}; + export const Themes = { - [AppTheme.Classic]: ClassicTheme, - [AppTheme.Dark]: DarkTheme, - [AppTheme.Light]: LightTheme, + [AppTheme.Classic]: generateTheme(ClassicThemeBase), + [AppTheme.Dark]: generateTheme(DarkThemeBase), + [AppTheme.Light]: generateTheme(LightThemeBase), }; diff --git a/src/views/AccountInfo/AccountInfoConnectedState.tsx b/src/views/AccountInfo/AccountInfoConnectedState.tsx index 1627c7f..9996def 100644 --- a/src/views/AccountInfo/AccountInfoConnectedState.tsx +++ b/src/views/AccountInfo/AccountInfoConnectedState.tsx @@ -231,7 +231,7 @@ Styled.CircleContainer = styled.div` `; Styled.Icon = styled(Icon)` - color: var(--color-negative); + color: var(--color-error); `; Styled.WithUsage = styled.div` diff --git a/src/views/PositionInfo.tsx b/src/views/PositionInfo.tsx index ac79452..2ea9477 100644 --- a/src/views/PositionInfo.tsx +++ b/src/views/PositionInfo.tsx @@ -557,11 +557,11 @@ Styled.PositionTile = styled(PositionTile)``; Styled.ClosePositionButton = styled(Button)` --button-border: solid var(--border-width) var(--color-border-red); - --button-textColor: var(--color-negative); + --button-textColor: var(--color-error); `; Styled.ClosePositionToggleButton = styled(ToggleButton)` --button-border: solid var(--border-width) var(--color-border-red); - --button-toggle-off-textColor: var(--color-negative); - --button-toggle-on-textColor: var(--color-negative); + --button-toggle-off-textColor: var(--color-error); + --button-toggle-on-textColor: var(--color-error); `; diff --git a/src/views/dialogs/DisplaySettingsDialog.tsx b/src/views/dialogs/DisplaySettingsDialog.tsx index 1cdd030..8f8e451 100644 --- a/src/views/dialogs/DisplaySettingsDialog.tsx +++ b/src/views/dialogs/DisplaySettingsDialog.tsx @@ -5,8 +5,8 @@ import { Root, Item, Indicator } from '@radix-ui/react-radio-group'; import { useStringGetter } from '@/hooks'; -import { AppTheme, setAppTheme } from '@/state/configs'; -import { getAppTheme } from '@/state/configsSelectors'; +import { AppTheme, AppColorMode, setAppTheme, setAppColorMode } from '@/state/configs'; +import { getAppTheme, getAppColorMode } from '@/state/configsSelectors'; import { layoutMixins } from '@/styles/layoutMixins'; import { Themes } from '@/styles/themes'; @@ -26,6 +26,7 @@ export const DisplaySettingsDialog = ({ setIsOpen }: ElementProps) => { const stringGetter = useStringGetter(); const currentTheme: AppTheme = useSelector(getAppTheme); + const currentColorMode: AppColorMode = useSelector(getAppColorMode); const sectionHeader = (heading: string) => { return ( @@ -56,13 +57,13 @@ export const DisplaySettingsDialog = ({ setIsOpen }: ElementProps) => { { dispatch(setAppTheme(theme)); }} > - + {stringGetter({ key: label })} @@ -75,6 +76,48 @@ export const DisplaySettingsDialog = ({ setIsOpen }: ElementProps) => { ); }; + const colorModeOptions = () => { + return ( + + {[ + { + colorMode: AppColorMode.GreenUp, + label: STRING_KEYS.GREEN_IS_UP, + }, + { + colorMode: AppColorMode.RedUp, + label: STRING_KEYS.RED_IS_UP, + }, + ].map(({ colorMode, label }) => ( + { + dispatch(setAppColorMode(colorMode)); + }} + > + + + + + + {stringGetter({ key: label })} + + + + ))} + + ); + }; + return ( { {sectionHeader(stringGetter({ key: STRING_KEYS.THEME }))} {themePanels()} + + {sectionHeader(stringGetter({ key: STRING_KEYS.DIRECTION_COLOR_PREFERENCE }))} + {colorModeOptions()} + ); }; @@ -110,6 +157,11 @@ Styled.AppThemeRoot = styled(Root)` grid-template-columns: 1fr 1fr; `; +Styled.ColorPreferenceRoot = styled(Root)` + ${gridStyle} + grid-template-columns: 1fr; +`; + Styled.Item = styled(Item)` --border-color: var(--color-border); --item-padding: 0.75rem; @@ -120,9 +172,19 @@ Styled.Item = styled(Item)` border: solid var(--border-width) var(--border-color); border-radius: 0.875rem; + padding: var(--item-padding); `; +Styled.ColorPreferenceItem = styled(Styled.Item)` + &[data-state='checked'] { + background-color: var(--color-layer-4); + } + + ${layoutMixins.row} + justify-content: space-between; +`; + Styled.AppThemeItem = styled(Styled.Item)<{ backgroundcolor: string; gridcolor: string }>` ${({ backgroundcolor, gridcolor }) => css` --themePanel-backgroundColor: ${backgroundcolor}; @@ -133,6 +195,7 @@ Styled.AppThemeItem = styled(Styled.Item)<{ backgroundcolor: string; gridcolor: flex-direction: column; position: relative; width: 100%; + background-color: var(--themePanel-backgroundColor); &::before { @@ -169,6 +232,43 @@ Styled.Image = styled.img` z-index: 1; `; +Styled.ColorPreferenceLabel = styled.div` + ${layoutMixins.inlineRow}; + gap: 1ch; +`; + +Styled.ArrowIconContainer = styled.div` + ${layoutMixins.column} + gap: 0.5ch; + + svg { + height: 0.75em; + width: 0.75em; + } +`; + +Styled.ArrowIcon = styled(Icon)<{ direction: 'up' | 'down'; color: 'green' | 'red' }>` + ${({ direction }) => + ({ + ['up']: css` + transform: rotate(-90deg); + `, + ['down']: css` + transform: rotate(90deg); + `, + }[direction])} + + ${({ color }) => + ({ + ['green']: css` + color: var(--color-success); + `, + ['red']: css` + color: var(--color-error); + `, + }[color])} +`; + const indicatorStyle = css` --indicator-size: 1.25rem; --icon-size: 0.5rem; @@ -182,9 +282,33 @@ const indicatorStyle = css` justify-content: center; `; +Styled.DotIndicator = styled.div<{ $selected: boolean }>` + ${indicatorStyle} + --background-color: var(--color-layer-2); + --border-color: var(--color-border); + + ${({ $selected }) => + $selected && + css` + --background-color: var(--color-accent); + --border-color: var(--color-accent); + + &::after { + content: ''; + display: block; + width: var(--icon-size); + height: var(--icon-size); + background-color: var(--color-layer-2); + border-radius: 50%; + } + `} + + background-color: var(--background-color); + border: solid var(--border-width) var(--border-color); +`; + Styled.CheckIndicator = styled(Indicator)` ${indicatorStyle} - position: absolute; bottom: var(--item-padding); right: var(--item-padding); diff --git a/src/views/dialogs/OnboardingDialog/GenerateKeys.tsx b/src/views/dialogs/OnboardingDialog/GenerateKeys.tsx index c65186b..ab824d5 100644 --- a/src/views/dialogs/OnboardingDialog/GenerateKeys.tsx +++ b/src/views/dialogs/OnboardingDialog/GenerateKeys.tsx @@ -323,7 +323,7 @@ Styled.ReceiptArea = styled.div` `; Styled.Green = styled.span` - color: var(--color-positive); + color: var(--color-success); `; Styled.GreenCheckCircle = styled(GreenCheckCircle)` diff --git a/src/views/forms/AccountManagementForms/WithdrawForm.tsx b/src/views/forms/AccountManagementForms/WithdrawForm.tsx index 8a9d413..33f26ef 100644 --- a/src/views/forms/AccountManagementForms/WithdrawForm.tsx +++ b/src/views/forms/AccountManagementForms/WithdrawForm.tsx @@ -453,6 +453,6 @@ Styled.FormInputButton = styled(Button)` Styled.CheckIcon = styled(Icon)` margin: 0 1ch; - color: var(--color-positive); + color: var(--color-success); font-size: 0.625rem; `; diff --git a/src/views/forms/TransferForm.tsx b/src/views/forms/TransferForm.tsx index 291e768..4775c85 100644 --- a/src/views/forms/TransferForm.tsx +++ b/src/views/forms/TransferForm.tsx @@ -441,7 +441,7 @@ Styled.DestinationInputLabel = styled.span` ${layoutMixins.inlineRow} svg { - color: var(--color-positive); + color: var(--color-success); } `; diff --git a/src/views/menus/AccountMenu.tsx b/src/views/menus/AccountMenu.tsx index b3df36d..ac7bc6f 100644 --- a/src/views/menus/AccountMenu.tsx +++ b/src/views/menus/AccountMenu.tsx @@ -203,7 +203,7 @@ export const AccountMenu = () => { value: 'MnemonicExport', icon: , label: {stringGetter({ key: STRING_KEYS.EXPORT_SECRET_PHRASE })}, - highlightColor: 'negative', + highlightColor: 'destroy', onSelect: () => dispatch(openDialog({ type: DialogTypes.MnemonicExport })), }, ].filter(isTruthy) @@ -212,7 +212,7 @@ export const AccountMenu = () => { value: 'Disconnect', icon: , label: stringGetter({ key: STRING_KEYS.DISCONNECT }), - highlightColor: 'negative', + highlightColor: 'destroy', onSelect: () => dispatch(openDialog({ type: DialogTypes.DisconnectWallet })), }, ].filter(isTruthy)} diff --git a/src/views/notifications/BlockRewardNotification/index.tsx b/src/views/notifications/BlockRewardNotification/index.tsx index 200522b..fce36ee 100644 --- a/src/views/notifications/BlockRewardNotification/index.tsx +++ b/src/views/notifications/BlockRewardNotification/index.tsx @@ -72,7 +72,7 @@ Styled.Notification = styled(Notification)` Styled.Output = styled(Output)` &:before { content: '+'; - color: var(--color-positive); + color: var(--color-success); margin-right: 0.5ch; } `; diff --git a/src/views/notifications/TransferStatusNotification/TransferStatusSteps.tsx b/src/views/notifications/TransferStatusNotification/TransferStatusSteps.tsx index 3cb050b..96c1adf 100644 --- a/src/views/notifications/TransferStatusNotification/TransferStatusSteps.tsx +++ b/src/views/notifications/TransferStatusNotification/TransferStatusSteps.tsx @@ -179,7 +179,7 @@ Styled.Icon = styled.div<{ state: 'complete' | 'default' }>` ${({ state }) => ({ ['complete']: css` - color: var(--color-positive); + color: var(--color-success); `, ['default']: css` color: var(--color-text-0); diff --git a/src/views/tables/OrdersTable/OrderActionsCell.tsx b/src/views/tables/OrdersTable/OrderActionsCell.tsx index e2fac52..fd4c867 100644 --- a/src/views/tables/OrdersTable/OrderActionsCell.tsx +++ b/src/views/tables/OrdersTable/OrderActionsCell.tsx @@ -82,6 +82,6 @@ Styled.ActionButton = styled(IconButton)` Styled.CancelButton = styled(Styled.ActionButton)` &:not(:disabled) { - --button-textColor: var(--color-negative); + --button-textColor: var(--color-error); } `;