Skip to content
This repository was archived by the owner on Sep 9, 2024. It is now read-only.

Commit 4a908d5

Browse files
authored
feat: custom themes (#885)
1 parent 21673c6 commit 4a908d5

File tree

160 files changed

+2906
-1686
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

160 files changed

+2906
-1686
lines changed

BREAKING_CHANGES.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
View Filters config structure changed
2-
View Groups config structure changed
2+
View Groups config structure changed
3+
`theme` props removed (use new `useTheme` hook instead)

packages/core/dev-test/backends/gitlab/index.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ CMS.registerShortcode('youtube', {
153153
return [src];
154154
},
155155
control: ({ src, onChange, theme }) => {
156+
const theme = useTheme();
157+
156158
return h('span', {}, [
157159
h('input', {
158160
key: 'control-input',
@@ -162,8 +164,8 @@ CMS.registerShortcode('youtube', {
162164
},
163165
style: {
164166
width: '100%',
165-
backgroundColor: theme === 'dark' ? 'rgb(30, 41, 59)' : 'white',
166-
color: theme === 'dark' ? 'white' : 'black',
167+
backgroundColor: theme.common.gray,
168+
color: theme.text.primary,
167169
padding: '4px 8px',
168170
},
169171
}),

packages/core/dev-test/index.js

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ const PostPreview = ({ entry, widgetFor }) => {
1212
};
1313

1414
const PostPreviewCard = ({ entry, theme, hasLocalBackup, collection }) => {
15+
const theme = useTheme();
1516
const date = new Date(entry.data.date);
1617

1718
const month = date.getMonth() + 1;
1819
const day = date.getDate();
1920

20-
const imageField = useMemo(() => collection.fields.find((f) => f.name === 'image'), []);
21+
const imageField = useMemo(() => collection.fields.find(f => f.name === 'image'), []);
2122
const image = useMediaAsset(entry.data.image, collection, imageField, entry);
2223

2324
return h(
@@ -49,7 +50,7 @@ const PostPreviewCard = ({ entry, theme, hasLocalBackup, collection }) => {
4950
justifyContent: 'space-between',
5051
alignItems: 'start',
5152
gap: '4px',
52-
color: theme === 'dark' ? 'white' : 'inherit',
53+
color: theme.text.primary,
5354
},
5455
},
5556
h(
@@ -268,6 +269,14 @@ CMS.registerAdditionalLink({
268269
},
269270
});
270271

272+
CMS.registerTheme({
273+
name: 'Custom Red Orange',
274+
extends: 'dark',
275+
primary: {
276+
main: '#ff4500',
277+
}
278+
});
279+
271280
CMS.registerShortcode('youtube', {
272281
label: 'YouTube',
273282
openTag: '[',
@@ -284,6 +293,8 @@ CMS.registerShortcode('youtube', {
284293
return [src];
285294
},
286295
control: ({ src, onChange, theme }) => {
296+
const theme = useTheme();
297+
287298
return h('span', {}, [
288299
h('input', {
289300
key: 'control-input',
@@ -293,8 +304,8 @@ CMS.registerShortcode('youtube', {
293304
},
294305
style: {
295306
width: '100%',
296-
backgroundColor: theme === 'dark' ? 'rgb(30, 41, 59)' : 'white',
297-
color: theme === 'dark' ? 'white' : 'black',
307+
backgroundColor: theme.common.gray,
308+
color: theme.text.primary,
298309
padding: '4px 8px',
299310
},
300311
}),

packages/core/src/actions/globalUI.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable import/prefer-default-export */
22
import { THEME_CHANGE } from '../constants';
33

4-
export function changeTheme(theme: 'dark' | 'light') {
4+
export function changeTheme(theme: string) {
55
return { type: THEME_CHANGE, payload: theme } as const;
66
}
77

packages/core/src/bootstrap.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@ import type { ConnectedProps } from 'react-redux';
2323
import type { BaseField, Config, UnknownField } from './interface';
2424
import type { RootState } from './store';
2525

26-
import './styles/datetime/calendar.css';
27-
import './styles/datetime/clock.css';
28-
import './styles/datetime/datetime.css';
2926
import './styles/main.css';
3027

3128
const ROOT_ID = 'nc-root';

packages/core/src/components/App.tsx

Lines changed: 23 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { createTheme, ThemeProvider } from '@mui/material/styles';
21
import React, { useCallback, useEffect, useMemo, useState } from 'react';
32
import { translate } from 'react-polyglot';
43
import { connect } from 'react-redux';
@@ -20,18 +19,21 @@ import { currentBackend } from '@staticcms/core/backend';
2019
import { changeTheme } from '../actions/globalUI';
2120
import { invokeEvent } from '../lib/registry';
2221
import { getDefaultPath } from '../lib/util/collection.util';
22+
import { isNotNullish } from '../lib/util/null.util';
23+
import { isEmpty } from '../lib/util/string.util';
2324
import { generateClassNames } from '../lib/util/theming.util';
24-
import { selectTheme } from '../reducers/selectors/globalUI';
25-
import { useAppDispatch, useAppSelector } from '../store/hooks';
25+
import { useAppDispatch } from '../store/hooks';
26+
import NotFoundPage from './NotFoundPage';
2627
import CollectionRoute from './collections/CollectionRoute';
2728
import { Alert } from './common/alert/Alert';
2829
import { Confirm } from './common/confirm/Confirm';
2930
import Loader from './common/progress/Loader';
3031
import EditorRoute from './entry-editor/EditorRoute';
3132
import MediaPage from './media-library/MediaPage';
32-
import NotFoundPage from './NotFoundPage';
3333
import Page from './page/Page';
3434
import Snackbars from './snackbar/Snackbars';
35+
import ThemeManager from './theme/ThemeManager';
36+
import useTheme from './theme/hooks/useTheme';
3537

3638
import type { Credentials, TranslatedProps } from '@staticcms/core/interface';
3739
import type { RootState } from '@staticcms/core/store';
@@ -78,25 +80,7 @@ const App = ({
7880
const navigate = useNavigate();
7981
const dispatch = useAppDispatch();
8082

81-
const mode = useAppSelector(selectTheme);
82-
83-
const theme = React.useMemo(
84-
() =>
85-
createTheme({
86-
palette: {
87-
mode,
88-
primary: {
89-
main: 'rgb(37 99 235)',
90-
},
91-
...(mode === 'dark' && {
92-
background: {
93-
paper: 'rgb(15 23 42)',
94-
},
95-
}),
96-
},
97-
}),
98-
[mode],
99-
);
83+
const theme = useTheme();
10084

10185
const configError = useCallback(
10286
(error?: string) => {
@@ -175,21 +159,6 @@ const App = ({
175159
dispatch(discardDraft());
176160
}, [dispatch, pathname, searchParams]);
177161

178-
useEffect(() => {
179-
// On page load or when changing themes, best to add inline in `head` to avoid FOUC
180-
if (
181-
localStorage.getItem('color-theme') === 'dark' ||
182-
(!('color-theme' in localStorage) &&
183-
window.matchMedia('(prefers-color-scheme: dark)').matches)
184-
) {
185-
document.documentElement.classList.add('dark');
186-
dispatch(changeTheme('dark'));
187-
} else {
188-
document.documentElement.classList.remove('dark');
189-
dispatch(changeTheme('light'));
190-
}
191-
}, [dispatch]);
192-
193162
const [prevUser, setPrevUser] = useState(user);
194163
useEffect(() => {
195164
if (!prevUser && user) {
@@ -255,6 +224,20 @@ const App = ({
255224
});
256225
}, []);
257226

227+
useEffect(() => {
228+
const defaultTheme = config.config?.theme?.default_theme;
229+
if (isEmpty(defaultTheme)) {
230+
return;
231+
}
232+
233+
const themeName = localStorage.getItem('color-theme');
234+
if (isNotNullish(themeName)) {
235+
return;
236+
}
237+
238+
dispatch(changeTheme(defaultTheme));
239+
}, [config.config?.theme?.default_theme, dispatch]);
240+
258241
if (!config.config) {
259242
return configError(t('app.app.configNotFound'));
260243
}
@@ -268,7 +251,7 @@ const App = ({
268251
}
269252

270253
return (
271-
<ThemeProvider theme={theme}>
254+
<ThemeManager theme={theme} element={document.documentElement}>
272255
<ScrollSync key="scroll-sync" enabled={scrollSyncEnabled}>
273256
<>
274257
<div key="back-to-top-anchor" id="back-to-top-anchor" />
@@ -282,7 +265,7 @@ const App = ({
282265
</div>
283266
</>
284267
</ScrollSync>
285-
</ThemeProvider>
268+
</ThemeManager>
286269
);
287270
};
288271

packages/core/src/components/ErrorBoundary.css

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
.CMS_ErrorBoundary_root {
2+
background: var(--background-dark);
3+
24
@apply flex
35
flex-col
4-
bg-slate-50
5-
dark:bg-slate-900
66
min-h-screen
77
gap-2;
88
}
@@ -21,8 +21,9 @@
2121
}
2222

2323
.CMS_ErrorBoundary_report-link {
24-
@apply text-blue-500
25-
hover:underline;
24+
color: var(--primary-main);
25+
26+
@apply hover:underline;
2627
}
2728

2829
.CMS_ErrorBoundary_content {

packages/core/src/components/MainView.css

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
.CMS_MainView_root {
2-
@apply flex
3-
bg-slate-50
4-
dark:bg-slate-900;
2+
background: var(--background-dark);
3+
4+
@apply flex;
55
}
66

77
.CMS_MainView_body {

packages/core/src/components/collections/Collection.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,12 @@
6060
}
6161

6262
.CMS_Collection_header {
63+
color: var(--text-primary);
64+
6365
@apply text-xl
6466
font-semibold
6567
flex
6668
items-center
67-
text-gray-800
68-
dark:text-gray-300
6969
gap-2
7070
md:w-auto;
7171
}

packages/core/src/components/collections/CollectionSearch.css

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,48 +13,47 @@
1313
}
1414

1515
.CMS_CollectionSearch_icon {
16+
color: var(--text-secondary);
17+
1618
@apply w-5
17-
h-5
18-
text-gray-500
19-
dark:text-gray-400;
19+
h-5;
2020
}
2121

2222
.CMS_CollectionSearch_input {
23+
color: var(--text-primary);
24+
background: color-mix(in srgb, var(--background-light) 50%, transparent);
25+
border-color: color-mix(in srgb, var(--background-divider) 50%, transparent);
26+
2327
@apply block
2428
w-full
2529
p-1.5
2630
pl-10
2731
text-sm
28-
text-gray-800
2932
border
30-
border-gray-300
3133
rounded-lg
32-
bg-gray-50
3334
focus-visible:outline-none
34-
focus:ring-4
35-
focus:ring-gray-200
36-
dark:bg-gray-700
37-
dark:border-gray-600
38-
dark:placeholder-gray-400
39-
dark:text-white
40-
dark:focus:ring-slate-700;
35+
focus:ring-4;
36+
37+
&:placeholder {
38+
color: var(--text-disabled);
39+
}
40+
41+
&:focus {
42+
--tw-ring-color: color-mix(in srgb, var(--primary-light) 50%, transparent);
43+
}
4144
}
4245

4346
.CMS_CollectionSearch_search-in {
47+
background: var(--background-light);
48+
4449
@apply absolute
4550
overflow-auto
4651
rounded-md
47-
bg-white
4852
text-base
4953
shadow-md
50-
ring-1
51-
ring-black
52-
ring-opacity-5
5354
focus:outline-none
5455
sm:text-sm
55-
z-[1300]
56-
dark:bg-slate-700
57-
dark:shadow-lg;
56+
z-[1300];
5857
}
5958

6059
.CMS_CollectionSearch_search-in-content {
@@ -64,17 +63,22 @@
6463
}
6564

6665
.CMS_CollectionSearch_search-in-label {
66+
color: var(--text-secondary);
67+
6768
@apply text-base
68-
text-slate-500
69-
dark:text-slate-400
7069
py-2
7170
px-3;
7271
}
7372

7473
.CMS_CollectionSearch_search-in-option {
74+
color: var(--text-primary);
75+
76+
&:hover {
77+
color: var(--text-secondary);
78+
background: var(--primary-main);
79+
}
80+
7581
@apply cursor-pointer
76-
hover:bg-blue-500
77-
hover:text-gray-100
7882
py-2
7983
px-3;
8084
}

0 commit comments

Comments
 (0)