Skip to content

Commit ea89969

Browse files
committed
feat: enhance theme management with user preferences and dialog support
1 parent 5e82ec5 commit ea89969

File tree

9 files changed

+48
-90
lines changed

9 files changed

+48
-90
lines changed

src/Shared/Components/ConfirmationModal/ConfirmationModal.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import { AnimatePresence, motion } from 'framer-motion'
1919
import { noop, stopPropagation, useRegisterShortcut, UseRegisterShortcutProvider } from '@Common/index'
2020
import { ComponentSizeType } from '@Shared/constants'
2121
import { getUniqueId } from '@Shared/Helpers'
22-
import { ConfirmationModalBodyProps, ConfirmationModalProps } from './types'
22+
import { getComponentSpecificThemeClass } from '@Shared/index'
23+
import { ConfirmationModalBodyProps, ConfirmationModalProps, ConfirmationModalVariantType } from './types'
2324
import { getPrimaryButtonStyleFromVariant, getConfirmationLabel, getIconFromVariant } from './utils'
2425
import { Button, ButtonStyleType, ButtonVariantType } from '../Button'
2526
import { Backdrop } from '../Backdrop'
@@ -37,6 +38,7 @@ const ConfirmationModalBody = ({
3738
children,
3839
handleClose,
3940
shouldCloseOnEscape = true,
41+
overriddenTheme,
4042
}: ConfirmationModalBodyProps) => {
4143
const { registerShortcut, unregisterShortcut } = useRegisterShortcut()
4244

@@ -48,6 +50,7 @@ const ConfirmationModalBody = ({
4850
const { primaryButtonConfig, secondaryButtonConfig } = buttonConfig
4951

5052
const RenderIcon = Icon ?? getIconFromVariant(variant)
53+
const hideIcon = variant === ConfirmationModalVariantType.custom && !Icon
5154

5255
const disablePrimaryButton: boolean =
5356
('disabled' in primaryButtonConfig && primaryButtonConfig.disabled) ||
@@ -80,16 +83,18 @@ const ConfirmationModalBody = ({
8083
return (
8184
<Backdrop onEscape={shouldCloseOnEscape ? handleCloseWrapper : noop}>
8285
<motion.div
83-
className="confirmation-modal border__secondary flexbox-col br-8 bg__primary dc__m-auto mt-40 w-400"
86+
className={`${overriddenTheme ? getComponentSpecificThemeClass(overriddenTheme) : ''} confirmation-modal border__secondary flexbox-col br-8 bg__primary dc__m-auto mt-40 w-400`}
8487
exit={{ y: 100, opacity: 0, scale: 0.75, transition: { duration: 0.35 } }}
8588
initial={{ y: 100, opacity: 0, scale: 0.75 }}
8689
animate={{ y: 0, opacity: 1, scale: 1 }}
8790
onClick={stopPropagation}
8891
>
8992
<div className="flexbox-col dc__gap-16 p-20">
90-
{cloneElement(RenderIcon, {
91-
className: `${RenderIcon.props?.className ?? ''} icon-dim-48 dc__no-shrink`,
92-
})}
93+
{hideIcon
94+
? null
95+
: cloneElement(RenderIcon, {
96+
className: `${RenderIcon.props?.className ?? ''} icon-dim-48 dc__no-shrink`,
97+
})}
9398

9499
<div className="flexbox-col dc__gap-8">
95100
<span className="cn-9 fs-16 fw-6 lh-24 dc__word-break">{title}</span>

src/Shared/Components/ConfirmationModal/types.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
SetStateAction,
2424
SyntheticEvent,
2525
} from 'react'
26+
import { AppThemeType } from '@Shared/index'
2627
import { ButtonProps } from '../Button'
2728

2829
export enum ConfirmationModalVariantType {
@@ -64,7 +65,7 @@ type ButtonConfigAndVariantType<isConfig extends boolean> =
6465
}
6566
| {
6667
variant: ConfirmationModalVariantType.custom
67-
Icon: ReactElement
68+
Icon?: ReactElement
6869
buttonConfig: ButtonConfig<isConfig, true>
6970
}
7071

@@ -94,6 +95,10 @@ export type ConfirmationModalProps<isConfig extends boolean = false> = PropsWith
9495
* Configuration object for confirmation behavior.
9596
*/
9697
confirmationConfig?: ConfirmationConfigType
98+
/**
99+
* If provided, the modal will use the specified theme instead of using the theme set by theme provider.
100+
*/
101+
overriddenTheme?: AppThemeType
97102
}> &
98103
ButtonConfigAndVariantType<isConfig> &
99104
(isConfig extends false

src/Shared/Components/ThemeSwitcher/ThemeSwitcher.component.tsx

Lines changed: 11 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -15,56 +15,29 @@
1515
*/
1616

1717
import { useTheme } from '@Shared/Providers'
18-
import { THEME_PREFERENCE_MAP, ThemePreferenceType } from '@Shared/Providers/ThemeProvider/types'
19-
import { SegmentedControl, SegmentedControlProps, SegmentedControlVariant } from '@Common/SegmentedControl'
20-
import { ChangeEvent, useMemo } from 'react'
21-
import { stopPropagation } from '@Common/Helper'
22-
import { ComponentSizeType } from '@Shared/constants'
23-
import { THEME_PREFERENCE_TO_ICON_MAP } from './constants'
2418
import { ThemeSwitcherProps } from './types'
2519

2620
const ThemeSwitcher = ({ onChange }: ThemeSwitcherProps) => {
27-
const { themePreference, handleThemePreferenceChange } = useTheme()
28-
29-
const { tabs, tooltips } = useMemo<Required<Pick<SegmentedControlProps, 'tabs' | 'tooltips'>>>(() => {
30-
const availableThemePreferences = Object.values(THEME_PREFERENCE_MAP)
31-
32-
return {
33-
tabs: availableThemePreferences.map((value) => ({
34-
label: (
35-
<span className="dc__no-shrink icon-dim-16 flex dc__fill-available-space">
36-
{THEME_PREFERENCE_TO_ICON_MAP[value].icon}
37-
</span>
38-
),
39-
value,
40-
})),
41-
tooltips: availableThemePreferences.map((value) => THEME_PREFERENCE_TO_ICON_MAP[value].tippyContent),
42-
}
43-
}, [])
21+
const { handleThemeSwitcherDialogVisibilityChange } = useTheme()
4422

4523
if (!window._env_.FEATURE_EXPERIMENTAL_THEMING_ENABLE) {
4624
return null
4725
}
4826

49-
const handleThemeSwitch = (e: ChangeEvent<HTMLInputElement>) => {
50-
const updatedThemePreference = e.target.value as ThemePreferenceType
51-
handleThemePreferenceChange(updatedThemePreference)
27+
const handleShowThemeSwitcherDialog = () => {
28+
handleThemeSwitcherDialogVisibilityChange(true)
5229
onChange()
5330
}
5431

5532
return (
56-
<div className="flex dc__content-space dc__gap-8 px-12 py-6" onClick={stopPropagation}>
57-
<p className="m-0 fs-13 fw-4 lh-20 cn-9">Theme</p>
58-
<SegmentedControl
59-
initialTab={themePreference}
60-
name="theme-preference-selector"
61-
onChange={handleThemeSwitch}
62-
tabs={tabs}
63-
tooltips={tooltips}
64-
size={ComponentSizeType.medium}
65-
variant={SegmentedControlVariant.GRAY_ON_WHITE}
66-
/>
67-
</div>
33+
<button
34+
type="button"
35+
data-testid="open-theme-switcher-dialog"
36+
className="dc__transparent fs-13 fw-4 lh-20 cn-9 dc__hover-n50 w-100"
37+
onClick={handleShowThemeSwitcherDialog}
38+
>
39+
Theme
40+
</button>
6841
)
6942
}
7043

src/Shared/Components/ThemeSwitcher/constants.tsx

Lines changed: 0 additions & 42 deletions
This file was deleted.

src/Shared/Providers/ThemeProvider/ThemeProvider.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { DARK_COLOR_SCHEME_MATCH_QUERY } from './constants'
2727
const themeContext = createContext<ThemeContextType>(null)
2828

2929
export const ThemeProvider = ({ children }: ThemeProviderProps) => {
30+
const [showThemeSwitcherDialog, setShowThemeSwitcherDialog] = useState<boolean>(false)
3031
const [themeConfig, setThemeConfig] = useState<ThemeConfigType>(getThemeConfigFromLocalStorage)
3132

3233
const handleThemePreferenceChange: ThemeContextType['handleThemePreferenceChange'] = (updatedThemePreference) => {
@@ -68,12 +69,18 @@ export const ThemeProvider = ({ children }: ThemeProviderProps) => {
6869
}
6970
}, [themeConfig.themePreference])
7071

72+
const handleThemeSwitcherDialogVisibilityChange = (isVisible: boolean) => {
73+
setShowThemeSwitcherDialog(isVisible)
74+
}
75+
7176
const value = useMemo<ThemeContextType>(
7277
() => ({
7378
...themeConfig,
79+
showThemeSwitcherDialog,
80+
handleThemeSwitcherDialogVisibilityChange,
7481
handleThemePreferenceChange,
7582
}),
76-
[themeConfig],
83+
[themeConfig, showThemeSwitcherDialog],
7784
)
7885

7986
return <themeContext.Provider value={value}>{children}</themeContext.Provider>

src/Shared/Providers/ThemeProvider/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@
1616

1717
export * from './ThemeProvider'
1818
export { AppThemeType } from './types'
19-
export { getComponentSpecificThemeClass } from './utils'
19+
export { getComponentSpecificThemeClass, getAppThemeForAutoPreference } from './utils'

src/Shared/Providers/ThemeProvider/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,14 @@ export interface ThemeConfigType {
4040
*
4141
* @default THEME_PREFERENCE_MAP.auto
4242
*
43-
* Note: This shouldn't be consumed other than in ThemeSwitcher component
43+
* Note: This shouldn't be consumed other than in ThemeSwitcherDialog component
4444
*/
4545
themePreference: ThemePreferenceType
4646
}
4747

4848
export interface ThemeContextType extends ThemeConfigType {
49+
showThemeSwitcherDialog: boolean
50+
handleThemeSwitcherDialogVisibilityChange: (isVisible: boolean) => void
4951
handleThemePreferenceChange: (updatedThemePreference: ThemePreferenceType) => void
5052
}
5153

src/Shared/Services/common.service.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export const getUserPreferences = async (): Promise<UserPreferencesType> => {
5858

5959
return {
6060
pipelineRBACViewSelectedTab,
61+
themePreference: parsedResult.themePreference,
6162
}
6263
}
6364

@@ -70,6 +71,7 @@ export const updateUserPreferences = async (
7071
viewPermittedEnvOnly:
7172
updatedUserPreferences.pipelineRBACViewSelectedTab ===
7273
ViewIsPipelineRBACConfiguredRadioTabs.ACCESS_ONLY,
74+
themePreference: updatedUserPreferences.themePreference,
7375
}
7476

7577
const payload: UpdateUserPreferencesPayloadType = {

src/Shared/Services/types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
import { MainContext } from '@Shared/Providers'
18+
import { ThemePreferenceType } from '@Shared/Providers/ThemeProvider/types'
1819
import { getUrlWithSearchParams } from '../../Common'
1920
import { PolicyKindType, ResourceKindType, ResourceVersionType, ViewIsPipelineRBACConfiguredRadioTabs } from '../types'
2021
import { USER_PREFERENCES_ATTRIBUTE_KEY } from './constants'
@@ -61,6 +62,11 @@ export interface GetUserPreferencesQueryParamsType {
6162

6263
export interface GetUserPreferencesParsedDTO {
6364
viewPermittedEnvOnly?: boolean
65+
/**
66+
* Preferred theme for the user
67+
* If null, would forcibly show user theme switcher dialog for user to select
68+
*/
69+
themePreference: ThemePreferenceType | null
6470
}
6571

6672
export interface UpdateUserPreferencesParsedValueType extends GetUserPreferencesParsedDTO {}
@@ -69,6 +75,6 @@ export interface UpdateUserPreferencesPayloadType extends Pick<GetUserPreference
6975
value: string
7076
}
7177

72-
export interface UserPreferencesType {
78+
export interface UserPreferencesType extends Pick<GetUserPreferencesParsedDTO, 'themePreference'> {
7379
pipelineRBACViewSelectedTab: ViewIsPipelineRBACConfiguredRadioTabs
7480
}

0 commit comments

Comments
 (0)