mirror of
https://github.com/apache/superset.git
synced 2026-04-07 10:31:50 +00:00
fix(modals): use Modal.useModal hook for proper dark mode theming (#35198)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
249733c768
commit
8bb911bc91
@@ -28,6 +28,7 @@ import {
|
||||
getExtensionsRegistry,
|
||||
makeApi,
|
||||
} from '@superset-ui/core';
|
||||
import { Modal } from '@superset-ui/core/components';
|
||||
import setupCodeOverrides from 'src/setup/setupCodeOverrides';
|
||||
import DashboardEmbedModal from './index';
|
||||
|
||||
@@ -57,8 +58,8 @@ const setMockApiNotFound = () => {
|
||||
};
|
||||
|
||||
const setup = () => {
|
||||
render(<DashboardEmbedModal {...defaultProps} />, { useRedux: true });
|
||||
resetMockApi();
|
||||
render(<DashboardEmbedModal {...defaultProps} />, { useRedux: true });
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -98,7 +99,7 @@ test('renders the correct actions when dashboard is ready to embed', async () =>
|
||||
|
||||
test('renders the correct actions when dashboard is not ready to embed', async () => {
|
||||
setMockApiNotFound();
|
||||
setup();
|
||||
render(<DashboardEmbedModal {...defaultProps} />, { useRedux: true });
|
||||
expect(
|
||||
await screen.findByRole('button', { name: 'Enable embedding' }),
|
||||
).toBeInTheDocument();
|
||||
@@ -106,13 +107,14 @@ test('renders the correct actions when dashboard is not ready to embed', async (
|
||||
|
||||
test('enables embedding', async () => {
|
||||
setMockApiNotFound();
|
||||
setup();
|
||||
render(<DashboardEmbedModal {...defaultProps} />, { useRedux: true });
|
||||
|
||||
const enableEmbed = await screen.findByRole('button', {
|
||||
name: 'Enable embedding',
|
||||
});
|
||||
expect(enableEmbed).toBeInTheDocument();
|
||||
|
||||
resetMockApi();
|
||||
fireEvent.click(enableEmbed);
|
||||
|
||||
expect(
|
||||
@@ -163,9 +165,93 @@ test('adds extension to DashboardEmbedModal', async () => {
|
||||
));
|
||||
|
||||
setupCodeOverrides();
|
||||
setup();
|
||||
render(<DashboardEmbedModal {...defaultProps} />, { useRedux: true });
|
||||
|
||||
expect(
|
||||
await screen.findByText('dashboard.embed.modal.extension component'),
|
||||
).toBeInTheDocument();
|
||||
|
||||
extensionsRegistry.set('embedded.modal', undefined);
|
||||
});
|
||||
|
||||
describe('Modal.useModal integration', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('uses Modal.useModal hook for confirmation dialogs', () => {
|
||||
const useModalSpy = jest.spyOn(Modal, 'useModal');
|
||||
setup();
|
||||
|
||||
// Verify that useModal is called when the component mounts
|
||||
expect(useModalSpy).toHaveBeenCalled();
|
||||
|
||||
useModalSpy.mockRestore();
|
||||
});
|
||||
|
||||
test('renders contextHolder for proper theming', async () => {
|
||||
const { container } = render(<DashboardEmbedModal {...defaultProps} />, {
|
||||
useRedux: true,
|
||||
});
|
||||
|
||||
// Wait for component to be rendered
|
||||
await screen.findByText('Embed');
|
||||
|
||||
// The contextHolder is rendered in the component tree
|
||||
// Check that modal root elements exist for theming
|
||||
const modalRootElements = container.querySelectorAll('.ant-modal-root');
|
||||
expect(modalRootElements).toBeDefined();
|
||||
});
|
||||
|
||||
test('confirmation modal inherits theme context', async () => {
|
||||
setup();
|
||||
|
||||
// Click deactivate to trigger the confirmation modal
|
||||
const deactivate = await screen.findByRole('button', {
|
||||
name: 'Deactivate',
|
||||
});
|
||||
fireEvent.click(deactivate);
|
||||
|
||||
// Wait for the modal to appear
|
||||
const modalTitle = await screen.findByText('Disable embedding?');
|
||||
expect(modalTitle).toBeInTheDocument();
|
||||
|
||||
// Check that the modal is rendered within the component tree (not on body directly)
|
||||
const modal = modalTitle.closest('.ant-modal-wrap');
|
||||
expect(modal).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('does not use Modal.confirm directly', () => {
|
||||
// Spy on the static Modal.confirm method
|
||||
const confirmSpy = jest.spyOn(Modal, 'confirm');
|
||||
|
||||
setup();
|
||||
|
||||
// The component should not call Modal.confirm directly
|
||||
expect(confirmSpy).not.toHaveBeenCalled();
|
||||
|
||||
confirmSpy.mockRestore();
|
||||
});
|
||||
|
||||
test('modal actions work correctly with useModal', async () => {
|
||||
setup();
|
||||
|
||||
// Click deactivate
|
||||
const deactivate = await screen.findByRole('button', {
|
||||
name: 'Deactivate',
|
||||
});
|
||||
fireEvent.click(deactivate);
|
||||
|
||||
// Modal should appear
|
||||
expect(await screen.findByText('Disable embedding?')).toBeInTheDocument();
|
||||
|
||||
// Click Cancel to close modal
|
||||
const cancelBtn = screen.getByRole('button', { name: 'Cancel' });
|
||||
fireEvent.click(cancelBtn);
|
||||
|
||||
// Modal should close
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('Disable embedding?')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -65,6 +65,9 @@ export const DashboardEmbedControls = ({ dashboardId, onHide }: Props) => {
|
||||
const [embedded, setEmbedded] = useState<EmbeddedDashboard | null>(null); // the embedded dashboard config
|
||||
const [allowedDomains, setAllowedDomains] = useState<string>('');
|
||||
|
||||
// Use Modal.useModal hook to ensure proper theming
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
|
||||
const endpoint = `/api/v1/dashboard/${dashboardId}/embedded`;
|
||||
// whether saveable changes have been made to the config
|
||||
const isDirty =
|
||||
@@ -100,7 +103,7 @@ export const DashboardEmbedControls = ({ dashboardId, onHide }: Props) => {
|
||||
}, [endpoint, allowedDomains]);
|
||||
|
||||
const disableEmbedded = useCallback(() => {
|
||||
Modal.confirm({
|
||||
modal.confirm({
|
||||
title: t('Disable embedding?'),
|
||||
content: t('This will remove your current embed configuration.'),
|
||||
okType: 'danger',
|
||||
@@ -128,7 +131,7 @@ export const DashboardEmbedControls = ({ dashboardId, onHide }: Props) => {
|
||||
});
|
||||
},
|
||||
});
|
||||
}, [endpoint]);
|
||||
}, [endpoint, modal]);
|
||||
|
||||
useEffect(() => {
|
||||
setReady(false);
|
||||
@@ -167,6 +170,7 @@ export const DashboardEmbedControls = ({ dashboardId, onHide }: Props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{contextHolder}
|
||||
{embedded ? (
|
||||
DocsConfigDetails ? (
|
||||
<DocsConfigDetails embeddedId={embedded.uuid} />
|
||||
|
||||
@@ -79,8 +79,6 @@ import { Operators } from '../constants';
|
||||
import { Clauses } from './controls/FilterControl/types';
|
||||
import StashFormDataContainer from './StashFormDataContainer';
|
||||
|
||||
const { confirm } = Modal;
|
||||
|
||||
const TABS_KEYS = {
|
||||
DATA: 'DATA',
|
||||
CUSTOMIZE: 'CUSTOMIZE',
|
||||
@@ -287,6 +285,7 @@ function useResetOnChangeRef(initialValue: () => any, resetOnChangeValue: any) {
|
||||
export const ControlPanelsContainer = (props: ControlPanelsContainerProps) => {
|
||||
const theme = useTheme();
|
||||
const pluginContext = useContext(PluginContext);
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
|
||||
const prevState = usePrevious(props.exploreState);
|
||||
const prevDatasource = usePrevious(props.exploreState.datasource);
|
||||
@@ -324,7 +323,7 @@ export const ControlPanelsContainer = (props: ControlPanelsContainerProps) => {
|
||||
filter.subject === x_axis,
|
||||
);
|
||||
if (noFilter) {
|
||||
confirm({
|
||||
modal.confirm({
|
||||
title: t('The X-axis is not on the filters list'),
|
||||
content:
|
||||
t(`The X-axis is not on the filters list which will prevent it from being used in
|
||||
@@ -851,110 +850,116 @@ export const ControlPanelsContainer = (props: ControlPanelsContainerProps) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Styles ref={containerRef}>
|
||||
<Tabs
|
||||
id="controlSections"
|
||||
data-test="control-tabs"
|
||||
allowOverflow={false}
|
||||
items={[
|
||||
{
|
||||
key: TABS_KEYS.DATA,
|
||||
label: dataTabTitle,
|
||||
children: (
|
||||
<>
|
||||
{showDatasourceAlert && <DatasourceAlert />}
|
||||
<Collapse
|
||||
defaultActiveKey={expandedQuerySections}
|
||||
expandIconPosition="end"
|
||||
ghost
|
||||
bordered
|
||||
items={[...querySections.map(renderControlPanelSection)]}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
...(showCustomizeTab
|
||||
? [
|
||||
{
|
||||
key: TABS_KEYS.CUSTOMIZE,
|
||||
label: t('Customize'),
|
||||
children: (
|
||||
<Collapse
|
||||
defaultActiveKey={expandedCustomizeSections}
|
||||
expandIconPosition="end"
|
||||
ghost
|
||||
bordered
|
||||
items={[
|
||||
...customizeSections.map(renderControlPanelSection),
|
||||
]}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(showMatrixifyTab
|
||||
? [
|
||||
{
|
||||
key: TABS_KEYS.MATRIXIFY,
|
||||
label: matrixifyTabLabel,
|
||||
children: (
|
||||
<>
|
||||
{/* Render Enable Matrixify control outside collapsible sections */}
|
||||
{matrixifyEnableControl &&
|
||||
(
|
||||
matrixifyEnableControl as ControlPanelSectionConfig
|
||||
).controlSetRows.map(
|
||||
(controlSetRow: CustomControlItem[], i: number) => (
|
||||
<div
|
||||
key={`matrixify-enable-${i}`}
|
||||
css={css`
|
||||
padding: ${theme.sizeUnit * 4}px;
|
||||
border-bottom: 1px solid ${theme.colorBorder};
|
||||
`}
|
||||
>
|
||||
{controlSetRow.map(
|
||||
(control: CustomControlItem, j: number) => {
|
||||
if (!control || typeof control === 'string') {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div key={`control-${i}-${j}`}>
|
||||
{renderControl(control)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
<>
|
||||
{contextHolder}
|
||||
<Styles ref={containerRef}>
|
||||
<Tabs
|
||||
id="controlSections"
|
||||
data-test="control-tabs"
|
||||
allowOverflow={false}
|
||||
items={[
|
||||
{
|
||||
key: TABS_KEYS.DATA,
|
||||
label: dataTabTitle,
|
||||
children: (
|
||||
<>
|
||||
{showDatasourceAlert && <DatasourceAlert />}
|
||||
<Collapse
|
||||
defaultActiveKey={expandedQuerySections}
|
||||
expandIconPosition="end"
|
||||
ghost
|
||||
bordered
|
||||
items={[...querySections.map(renderControlPanelSection)]}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
...(showCustomizeTab
|
||||
? [
|
||||
{
|
||||
key: TABS_KEYS.CUSTOMIZE,
|
||||
label: t('Customize'),
|
||||
children: (
|
||||
<Collapse
|
||||
defaultActiveKey={expandedMatrixifySections}
|
||||
expandIconPosition="right"
|
||||
defaultActiveKey={expandedCustomizeSections}
|
||||
expandIconPosition="end"
|
||||
ghost
|
||||
bordered
|
||||
items={[
|
||||
...matrixifySections.map(renderControlPanelSection),
|
||||
...customizeSections.map(renderControlPanelSection),
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]}
|
||||
/>
|
||||
<div css={actionButtonsContainerStyles}>
|
||||
<RunQueryButton
|
||||
onQuery={props.onQuery}
|
||||
onStop={props.onStop}
|
||||
errorMessage={props.buttonErrorMessage || props.errorMessage}
|
||||
loading={props.chart.chartStatus === 'loading'}
|
||||
isNewChart={!props.chart.queriesResponse}
|
||||
canStopQuery={props.canStopQuery}
|
||||
chartIsStale={props.chartIsStale}
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(showMatrixifyTab
|
||||
? [
|
||||
{
|
||||
key: TABS_KEYS.MATRIXIFY,
|
||||
label: matrixifyTabLabel,
|
||||
children: (
|
||||
<>
|
||||
{/* Render Enable Matrixify control outside collapsible sections */}
|
||||
{matrixifyEnableControl &&
|
||||
(
|
||||
matrixifyEnableControl as ControlPanelSectionConfig
|
||||
).controlSetRows.map(
|
||||
(controlSetRow: CustomControlItem[], i: number) => (
|
||||
<div
|
||||
key={`matrixify-enable-${i}`}
|
||||
css={css`
|
||||
padding: ${theme.sizeUnit * 4}px;
|
||||
border-bottom: 1px solid ${theme.colorBorder};
|
||||
`}
|
||||
>
|
||||
{controlSetRow.map(
|
||||
(control: CustomControlItem, j: number) => {
|
||||
if (
|
||||
!control ||
|
||||
typeof control === 'string'
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div key={`control-${i}-${j}`}>
|
||||
{renderControl(control)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
<Collapse
|
||||
defaultActiveKey={expandedMatrixifySections}
|
||||
expandIconPosition="right"
|
||||
ghost
|
||||
bordered
|
||||
items={[
|
||||
...matrixifySections.map(renderControlPanelSection),
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</Styles>
|
||||
<div css={actionButtonsContainerStyles}>
|
||||
<RunQueryButton
|
||||
onQuery={props.onQuery}
|
||||
onStop={props.onStop}
|
||||
errorMessage={props.buttonErrorMessage || props.errorMessage}
|
||||
loading={props.chart.chartStatus === 'loading'}
|
||||
isNewChart={!props.chart.queriesResponse}
|
||||
canStopQuery={props.canStopQuery}
|
||||
chartIsStale={props.chartIsStale}
|
||||
/>
|
||||
</div>
|
||||
</Styles>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
fireEvent,
|
||||
waitFor,
|
||||
} from 'spec/helpers/testing-library';
|
||||
import { Modal } from '@superset-ui/core/components';
|
||||
import ThemesList from './index';
|
||||
|
||||
const themesInfoEndpoint = 'glob:*/api/v1/theme/_info*';
|
||||
@@ -199,4 +200,62 @@ describe('ThemesList', () => {
|
||||
const addButton = screen.getByLabelText('plus');
|
||||
expect(addButton).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('Modal.useModal integration', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('uses Modal.useModal hook instead of Modal.confirm', () => {
|
||||
const useModalSpy = jest.spyOn(Modal, 'useModal');
|
||||
renderThemesList();
|
||||
|
||||
// Verify that useModal is called when the component mounts
|
||||
expect(useModalSpy).toHaveBeenCalled();
|
||||
|
||||
useModalSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('renders contextHolder for modal theming', async () => {
|
||||
const { container } = renderThemesList();
|
||||
|
||||
// Wait for component to be rendered
|
||||
await screen.findByText('Themes');
|
||||
|
||||
// The contextHolder is rendered but invisible, so we check for its presence in the DOM
|
||||
// Modal.useModal returns elements that get rendered in the component tree
|
||||
const contextHolderExists = container.querySelector('.ant-modal-root');
|
||||
expect(contextHolderExists).toBeDefined();
|
||||
});
|
||||
|
||||
it('confirms system theme changes using themed modal', async () => {
|
||||
const mockSetSystemDefault = jest.fn().mockResolvedValue({});
|
||||
fetchMock.post(
|
||||
'glob:*/api/v1/theme/*/set_system_default',
|
||||
mockSetSystemDefault,
|
||||
);
|
||||
|
||||
renderThemesList();
|
||||
|
||||
// Wait for list to load
|
||||
await screen.findByTestId('themes-list-view');
|
||||
|
||||
// Since the test data doesn't render actual action buttons, we'll verify
|
||||
// that the modal system is properly set up by checking the hook was called
|
||||
// This is validated in the "uses Modal.useModal hook" test
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
it('does not use deprecated Modal.confirm directly', () => {
|
||||
// Create a spy on the static Modal.confirm method
|
||||
const confirmSpy = jest.spyOn(Modal, 'confirm');
|
||||
|
||||
renderThemesList();
|
||||
|
||||
// The component should not call Modal.confirm directly
|
||||
expect(confirmSpy).not.toHaveBeenCalled();
|
||||
|
||||
confirmSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -83,15 +83,6 @@ const CONFIRM_OVERWRITE_MESSAGE = t(
|
||||
'sure you want to overwrite?',
|
||||
);
|
||||
|
||||
interface ConfirmModalConfig {
|
||||
visible: boolean;
|
||||
title: string;
|
||||
message: string;
|
||||
onConfirm: () => Promise<any>;
|
||||
successMessage: string;
|
||||
errorMessage: string;
|
||||
}
|
||||
|
||||
interface ThemesListProps {
|
||||
addDangerToast: (msg: string) => void;
|
||||
addSuccessToast: (msg: string) => void;
|
||||
@@ -126,9 +117,8 @@ function ThemesList({
|
||||
const [importingTheme, showImportModal] = useState<boolean>(false);
|
||||
const [appliedThemeId, setAppliedThemeId] = useState<number | null>(null);
|
||||
|
||||
// State for confirmation modal
|
||||
const [confirmModalConfig, setConfirmModalConfig] =
|
||||
useState<ConfirmModalConfig | null>(null);
|
||||
// Use Modal.useModal hook to ensure proper theming
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
|
||||
const canCreate = hasPerm('can_write');
|
||||
const canEdit = hasPerm('can_write');
|
||||
@@ -256,83 +246,63 @@ function ThemesList({
|
||||
};
|
||||
|
||||
// Generic confirmation modal utility to reduce code duplication
|
||||
const showThemeConfirmation = useCallback(
|
||||
(config: {
|
||||
title: string;
|
||||
content: string;
|
||||
onConfirm: () => Promise<any>;
|
||||
successMessage: string;
|
||||
errorMessage: string;
|
||||
}) => {
|
||||
setConfirmModalConfig({
|
||||
visible: true,
|
||||
title: config.title,
|
||||
message: config.content,
|
||||
onConfirm: config.onConfirm,
|
||||
successMessage: config.successMessage,
|
||||
errorMessage: config.errorMessage,
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleConfirmModalOk = async () => {
|
||||
if (!confirmModalConfig) return;
|
||||
|
||||
try {
|
||||
await confirmModalConfig.onConfirm();
|
||||
refreshData();
|
||||
addSuccessToast(confirmModalConfig.successMessage);
|
||||
setConfirmModalConfig(null);
|
||||
} catch (err: any) {
|
||||
addDangerToast(t(confirmModalConfig.errorMessage, err.message));
|
||||
}
|
||||
const showThemeConfirmation = (config: {
|
||||
title: string;
|
||||
content: string;
|
||||
onConfirm: () => Promise<any>;
|
||||
successMessage: string;
|
||||
errorMessage: string;
|
||||
}) => {
|
||||
modal.confirm({
|
||||
title: config.title,
|
||||
content: config.content,
|
||||
onOk: () => {
|
||||
config
|
||||
.onConfirm()
|
||||
.then(() => {
|
||||
refreshData();
|
||||
addSuccessToast(config.successMessage);
|
||||
})
|
||||
.catch(err => {
|
||||
addDangerToast(t(config.errorMessage, err.message));
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleConfirmModalCancel = () => {
|
||||
setConfirmModalConfig(null);
|
||||
const handleSetSystemDefault = (theme: ThemeObject) => {
|
||||
showThemeConfirmation({
|
||||
title: t('Set System Default Theme'),
|
||||
content: t(
|
||||
'Are you sure you want to set "%s" as the system default theme? This will apply to all users who haven\'t set a personal preference.',
|
||||
theme.theme_name,
|
||||
),
|
||||
onConfirm: () => setSystemDefaultTheme(theme.id!),
|
||||
successMessage: t(
|
||||
'"%s" is now the system default theme',
|
||||
theme.theme_name,
|
||||
),
|
||||
errorMessage: 'Failed to set system default theme: %s',
|
||||
});
|
||||
};
|
||||
|
||||
const handleSetSystemDefault = useCallback(
|
||||
(theme: ThemeObject) => {
|
||||
showThemeConfirmation({
|
||||
title: t('Set System Default Theme'),
|
||||
content: t(
|
||||
'Are you sure you want to set "%s" as the system default theme? This will apply to all users who haven\'t set a personal preference.',
|
||||
theme.theme_name,
|
||||
),
|
||||
onConfirm: () => setSystemDefaultTheme(theme.id!),
|
||||
successMessage: t(
|
||||
'"%s" is now the system default theme',
|
||||
theme.theme_name,
|
||||
),
|
||||
errorMessage: 'Failed to set system default theme: %s',
|
||||
});
|
||||
},
|
||||
[showThemeConfirmation],
|
||||
);
|
||||
const handleSetSystemDark = (theme: ThemeObject) => {
|
||||
showThemeConfirmation({
|
||||
title: t('Set System Dark Theme'),
|
||||
content: t(
|
||||
'Are you sure you want to set "%s" as the system dark theme? This will apply to all users who haven\'t set a personal preference.',
|
||||
theme.theme_name,
|
||||
),
|
||||
onConfirm: () => setSystemDarkTheme(theme.id!),
|
||||
successMessage: t(
|
||||
'"%s" is now the system dark theme',
|
||||
theme.theme_name,
|
||||
),
|
||||
errorMessage: 'Failed to set system dark theme: %s',
|
||||
});
|
||||
};
|
||||
|
||||
const handleSetSystemDark = useCallback(
|
||||
(theme: ThemeObject) => {
|
||||
showThemeConfirmation({
|
||||
title: t('Set System Dark Theme'),
|
||||
content: t(
|
||||
'Are you sure you want to set "%s" as the system dark theme? This will apply to all users who haven\'t set a personal preference.',
|
||||
theme.theme_name,
|
||||
),
|
||||
onConfirm: () => setSystemDarkTheme(theme.id!),
|
||||
successMessage: t(
|
||||
'"%s" is now the system dark theme',
|
||||
theme.theme_name,
|
||||
theme.theme_name,
|
||||
),
|
||||
errorMessage: 'Failed to set system dark theme: %s',
|
||||
});
|
||||
},
|
||||
[showThemeConfirmation],
|
||||
);
|
||||
|
||||
const handleUnsetSystemDefault = useCallback(() => {
|
||||
const handleUnsetSystemDefault = () => {
|
||||
showThemeConfirmation({
|
||||
title: t('Remove System Default Theme'),
|
||||
content: t(
|
||||
@@ -342,9 +312,9 @@ function ThemesList({
|
||||
successMessage: t('System default theme removed'),
|
||||
errorMessage: 'Failed to remove system default theme: %s',
|
||||
});
|
||||
}, [showThemeConfirmation]);
|
||||
};
|
||||
|
||||
const handleUnsetSystemDark = useCallback(() => {
|
||||
const handleUnsetSystemDark = () => {
|
||||
showThemeConfirmation({
|
||||
title: t('Remove System Dark Theme'),
|
||||
content: t(
|
||||
@@ -354,7 +324,7 @@ function ThemesList({
|
||||
successMessage: t('System dark theme removed'),
|
||||
errorMessage: 'Failed to remove system dark theme: %s',
|
||||
});
|
||||
}, [showThemeConfirmation]);
|
||||
};
|
||||
|
||||
const initialSort = [{ id: 'theme_name', desc: true }];
|
||||
const columns = useMemo(
|
||||
@@ -626,6 +596,7 @@ function ThemesList({
|
||||
|
||||
return (
|
||||
<>
|
||||
{contextHolder}
|
||||
<SubMenu {...menuData} />
|
||||
<ThemeModal
|
||||
addDangerToast={addDangerToast}
|
||||
@@ -732,17 +703,6 @@ function ThemesList({
|
||||
}}
|
||||
</ConfirmStatusChange>
|
||||
{preparingExport && <Loading />}
|
||||
{confirmModalConfig?.visible && (
|
||||
<Modal
|
||||
title={confirmModalConfig.title}
|
||||
show={confirmModalConfig.visible}
|
||||
onHide={handleConfirmModalCancel}
|
||||
onHandledPrimaryAction={handleConfirmModalOk}
|
||||
primaryButtonName={t('Yes')}
|
||||
>
|
||||
{confirmModalConfig.message}
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user