diff --git a/public/chart-bars-background.svg b/public/chart-bars-background.svg
new file mode 100644
index 0000000..a8df022
--- /dev/null
+++ b/public/chart-bars-background.svg
@@ -0,0 +1,19 @@
+
diff --git a/public/chart-bars.svg b/public/chart-bars.svg
new file mode 100644
index 0000000..9a2e431
--- /dev/null
+++ b/public/chart-bars.svg
@@ -0,0 +1,20 @@
+
diff --git a/src/components/Panel.stories.tsx b/src/components/Panel.stories.tsx
index ecdb993..1ef7d85 100644
--- a/src/components/Panel.stories.tsx
+++ b/src/components/Panel.stories.tsx
@@ -1,10 +1,10 @@
import type { Story } from '@ladle/react';
-import { Panel } from '@/components/Panel';
+import { Panel, PanelProps } from '@/components/Panel';
import { StoryWrapper } from '.ladle/components';
-export const PanelStory: Story<{ slotHeader: React.ReactNode, children?: React.ReactNode }> = (args) => {
+export const PanelStory: Story = (args) => {
return (
@@ -13,6 +13,8 @@ export const PanelStory: Story<{ slotHeader: React.ReactNode, children?: React.R
};
PanelStory.args = {
- slotHeader: 'Header',
+ slotHeaderContent: 'Header',
children: 'Content',
+ slotRight: '1️⃣',
+ hasSeparator: true,
};
diff --git a/src/components/Panel.tsx b/src/components/Panel.tsx
index cebd1d7..1ea335f 100644
--- a/src/components/Panel.tsx
+++ b/src/components/Panel.tsx
@@ -6,7 +6,7 @@ import { Icon, IconName } from '@/components/Icon';
import { layoutMixins } from '@/styles/layoutMixins';
import { breakpoints } from '@/styles';
-type PanelProps = {
+type ElementProps = {
slotHeaderContent?: React.ReactNode;
slotHeader?: React.ReactNode;
slotRight?: React.ReactNode;
@@ -16,11 +16,13 @@ type PanelProps = {
onClick?: () => void;
};
-type PanelStyleProps = {
+type StyleProps = {
className?: string;
hasSeparator?: boolean;
};
+export type PanelProps = ElementProps & StyleProps;
+
export const Panel = ({
slotHeaderContent,
slotHeader,
@@ -31,7 +33,7 @@ export const Panel = ({
onClick,
hasSeparator,
className,
-}: PanelProps & PanelStyleProps) => (
+}: PanelProps) => (
{href ? (
diff --git a/src/constants/dialogs.ts b/src/constants/dialogs.ts
index e5cf8fd..bad393c 100644
--- a/src/constants/dialogs.ts
+++ b/src/constants/dialogs.ts
@@ -2,6 +2,7 @@ export enum DialogTypes {
ClosePosition = 'ClosePosition',
Deposit = 'Deposit',
DisconnectWallet = 'DisconnectWallet',
+ DisplaySettings = 'DisplaySettings',
ExchangeOffline = 'ExchangeOffline',
ExternalLink = 'ExternalLink',
FillDetails = 'FillDetails',
diff --git a/src/layout/DialogManager.tsx b/src/layout/DialogManager.tsx
index d7145ae..04406cc 100644
--- a/src/layout/DialogManager.tsx
+++ b/src/layout/DialogManager.tsx
@@ -9,6 +9,7 @@ import { getActiveDialog } from '@/state/dialogsSelectors';
import { ClosePositionDialog } from '@/views/dialogs/ClosePositionDialog';
import { DepositDialog } from '@/views/dialogs/DepositDialog';
import { DisconnectDialog } from '@/views/dialogs/DisconnectDialog';
+import { DisplaySettingsDialog } from '@/views/dialogs/DisplaySettingsDialog';
import { ExchangeOfflineDialog } from '@/views/dialogs/ExchangeOfflineDialog';
import { HelpDialog } from '@/views/dialogs/HelpDialog';
import { ExternalLinkDialog } from '@/views/dialogs/ExternalLinkDialog';
@@ -51,6 +52,7 @@ export const DialogManager = () => {
return {
[DialogTypes.ClosePosition]: ,
[DialogTypes.Deposit]: ,
+ [DialogTypes.DisplaySettings]: ,
[DialogTypes.DisconnectWallet]: ,
[DialogTypes.ExchangeOffline]: ,
[DialogTypes.FillDetails]: ,
diff --git a/src/views/dialogs/DisplaySettingsDialog.tsx b/src/views/dialogs/DisplaySettingsDialog.tsx
new file mode 100644
index 0000000..1cdd030
--- /dev/null
+++ b/src/views/dialogs/DisplaySettingsDialog.tsx
@@ -0,0 +1,199 @@
+import { useDispatch, useSelector } from 'react-redux';
+import styled, { AnyStyledComponent, css } from 'styled-components';
+
+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 { layoutMixins } from '@/styles/layoutMixins';
+import { Themes } from '@/styles/themes';
+
+import { STRING_KEYS } from '@/constants/localization';
+
+import { Dialog } from '@/components/Dialog';
+import { Icon, IconName } from '@/components/Icon';
+import { HorizontalSeparatorFiller } from '@/components/Separator';
+
+type ElementProps = {
+ setIsOpen: (open: boolean) => void;
+};
+
+export const DisplaySettingsDialog = ({ setIsOpen }: ElementProps) => {
+ const dispatch = useDispatch();
+ const stringGetter = useStringGetter();
+
+ const currentTheme: AppTheme = useSelector(getAppTheme);
+
+ const sectionHeader = (heading: string) => {
+ return (
+
+ {heading}
+
+
+ );
+ };
+
+ const themePanels = () => {
+ return (
+
+ {[
+ {
+ theme: AppTheme.Classic,
+ label: STRING_KEYS.CLASSIC_DARK,
+ },
+ {
+ theme: AppTheme.Dark,
+ label: STRING_KEYS.DARK,
+ },
+ {
+ theme: AppTheme.Light,
+ label: STRING_KEYS.LIGHT,
+ },
+ ].map(({ theme, label }) => (
+ {
+ dispatch(setAppTheme(theme));
+ }}
+ >
+
+ {stringGetter({ key: label })}
+
+
+
+
+
+
+ ))}
+
+ );
+ };
+
+ return (
+
+ );
+};
+
+const Styled: Record = {};
+
+const gridStyle = css`
+ display: grid;
+ gap: 1.5rem;
+`;
+
+Styled.Section = styled.div`
+ ${gridStyle}
+ padding: 1rem 0;
+`;
+
+Styled.Header = styled.header`
+ ${layoutMixins.inlineRow}
+`;
+
+Styled.AppThemeRoot = styled(Root)`
+ ${gridStyle}
+ grid-template-columns: 1fr 1fr;
+`;
+
+Styled.Item = styled(Item)`
+ --border-color: var(--color-border);
+ --item-padding: 0.75rem;
+
+ &[data-state='checked'] {
+ --border-color: var(--color-accent);
+ }
+
+ border: solid var(--border-width) var(--border-color);
+ border-radius: 0.875rem;
+ padding: var(--item-padding);
+`;
+
+Styled.AppThemeItem = styled(Styled.Item)<{ backgroundcolor: string; gridcolor: string }>`
+ ${({ backgroundcolor, gridcolor }) => css`
+ --themePanel-backgroundColor: ${backgroundcolor};
+ --themePanel-gridColor: ${gridcolor};
+ `}
+
+ display: flex;
+ flex-direction: column;
+ position: relative;
+ width: 100%;
+ background-color: var(--themePanel-backgroundColor);
+
+ &::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+
+ border-radius: 0.875rem;
+
+ background: radial-gradient(
+ 55% 35% at 50% 65%,
+ transparent,
+ var(--themePanel-backgroundColor) 100%
+ );
+ background-color: var(--themePanel-gridColor);
+ mask-image: url('/chart-bars-background.svg');
+ mask-size: cover;
+ }
+`;
+
+Styled.AppThemeHeader = styled.h3<{ textcolor: string }>`
+ ${({ textcolor }) => css`
+ color: ${textcolor};
+ `}
+ z-index: 1;
+`;
+
+Styled.Image = styled.img`
+ width: 100%;
+ height: auto;
+ z-index: 1;
+`;
+
+const indicatorStyle = css`
+ --indicator-size: 1.25rem;
+ --icon-size: 0.5rem;
+
+ height: var(--indicator-size);
+ width: var(--indicator-size);
+
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+`;
+
+Styled.CheckIndicator = styled(Indicator)`
+ ${indicatorStyle}
+
+ position: absolute;
+ bottom: var(--item-padding);
+ right: var(--item-padding);
+
+ background-color: var(--color-accent);
+ color: var(--color-text-2);
+`;
+
+Styled.CheckIcon = styled(Icon)`
+ width: var(--icon-size);
+ height: var(--icon-size);
+`;
diff --git a/src/views/menus/AccountMenu.tsx b/src/views/menus/AccountMenu.tsx
index 2796a88..b3df36d 100644
--- a/src/views/menus/AccountMenu.tsx
+++ b/src/views/menus/AccountMenu.tsx
@@ -32,9 +32,11 @@ import { Icon, IconName } from '@/components/Icon';
import { IconButton } from '@/components/IconButton';
import { WithTooltip } from '@/components/WithTooltip';
+import { AppTheme } from '@/state/configs';
import { openDialog } from '@/state/dialogs';
import { getOnboardingState, getSubaccount } from '@/state/accountSelectors';
+import { getAppTheme } from '@/state/configsSelectors';
import { isTruthy } from '@/lib/isTruthy';
import { truncateAddress } from '@/lib/wallet';
@@ -50,6 +52,7 @@ export const AccountMenu = () => {
const { freeCollateral } = useSelector(getSubaccount, shallowEqual) || {};
const { nativeTokenBalance } = useAccountBalance();
const { usdcLabel, chainTokenLabel } = useTokenConfigs();
+ const theme = useSelector(getAppTheme);
const { evmAddress, walletType, dydxAddress, hdKey } = useAccounts();
@@ -177,6 +180,17 @@ export const AccountMenu = () => {
label: stringGetter({ key: STRING_KEYS.PREFERENCES }),
onSelect: () => dispatch(openDialog({ type: DialogTypes.Preferences })),
},
+ {
+ value: 'DisplaySettings',
+ icon:
+ theme === AppTheme.Light ? (
+
+ ) : (
+
+ ),
+ label: stringGetter({ key: STRING_KEYS.DISPLAY_SETTINGS }),
+ onSelect: () => dispatch(openDialog({ type: DialogTypes.DisplaySettings })),
+ },
...(onboardingState === OnboardingState.AccountConnected && hdKey
? [
(!isMainnet || testFlags.showMobileSignInOption) && {