From d81e6b3a2c1f0a692ada1760fb6894bec3b2413d Mon Sep 17 00:00:00 2001 From: moo-onthelawn <70078372+moo-onthelawn@users.noreply.github.com> Date: Thu, 1 Feb 2024 19:01:53 -0500 Subject: [PATCH] TRCL-3520 Add entry point + modal for themeing (#255) * wip * clean up * clean up UI --- public/chart-bars-background.svg | 19 ++ public/chart-bars.svg | 20 ++ src/components/Panel.stories.tsx | 8 +- src/components/Panel.tsx | 8 +- src/constants/dialogs.ts | 1 + src/layout/DialogManager.tsx | 2 + src/views/dialogs/DisplaySettingsDialog.tsx | 199 ++++++++++++++++++++ src/views/menus/AccountMenu.tsx | 14 ++ 8 files changed, 265 insertions(+), 6 deletions(-) create mode 100644 public/chart-bars-background.svg create mode 100644 public/chart-bars.svg create mode 100644 src/views/dialogs/DisplaySettingsDialog.tsx 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 ( + + + {sectionHeader(stringGetter({ key: STRING_KEYS.THEME }))} + {themePanels()} + + + ); +}; + +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) && {