chore(ui): migrates theme provider

This commit is contained in:
Jacob Fletcher
2023-12-06 14:37:08 -05:00
parent ab24796316
commit ce3c084ea1
15 changed files with 119 additions and 93 deletions

View File

@@ -0,0 +1 @@
PAYLOAD_SECRET=ls3nd09adff9cmlq71j

View File

@@ -3,6 +3,8 @@ import { RootProvider } from '@payloadcms/ui/providers'
import { SanitizedConfig } from 'payload/types'
import { createClientConfig } from '../../createClientConfig'
import '@payloadcms/ui/scss/app.scss'
export const metadata = {
title: 'Next.js',
description: 'Generated by Next.js',

View File

@@ -3,21 +3,24 @@ import React from 'react'
import { RenderCustomComponent } from '@payloadcms/ui/elements'
import { DefaultDashboard } from '@payloadcms/ui/views'
export const Dashboard = ({ config: configPromise }: { config: Promise<SanitizedConfig> }) =>
async function () {
const config = await configPromise
export const Dashboard = async ({
config: configPromise,
}: {
config: Promise<SanitizedConfig>
}) => {
const config = await configPromise
const CustomDashboardComponent = config.admin.components?.views?.Dashboard
const CustomDashboardComponent = config.admin.components?.views?.Dashboard
return (
<RenderCustomComponent
CustomComponent={
typeof CustomDashboardComponent === 'function' ? CustomDashboardComponent : undefined
}
DefaultComponent={DefaultDashboard}
componentProps={{
config,
}}
/>
)
}
return (
<RenderCustomComponent
CustomComponent={
typeof CustomDashboardComponent === 'function' ? CustomDashboardComponent : undefined
}
DefaultComponent={DefaultDashboard}
componentProps={{
config,
}}
/>
)
}

View File

@@ -22,9 +22,9 @@ import { afterChange } from '../../fields/hooks/afterChange'
import { afterRead } from '../../fields/hooks/afterRead'
import { beforeChange } from '../../fields/hooks/beforeChange'
import { beforeValidate } from '../../fields/hooks/beforeValidate'
import { generateFileData } from '../../uploads/generateFileData'
import { unlinkTempFiles } from '../../uploads/unlinkTempFiles'
import { uploadFiles } from '../../uploads/uploadFiles'
// import { generateFileData } from '../../uploads/generateFileData'
// import { unlinkTempFiles } from '../../uploads/unlinkTempFiles'
// import { uploadFiles } from '../../uploads/uploadFiles' TODO: this was temporarily commented out bc it throws Sharp compilation errors in Next.js
import { commitTransaction } from '../../utilities/commitTransaction'
import { initTransaction } from '../../utilities/initTransaction'
import { killTransaction } from '../../utilities/killTransaction'
@@ -123,17 +123,18 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
// Generate data for all files and sizes
// /////////////////////////////////////
const { data: newFileData, files: filesToUpload } = await generateFileData({
collection,
config,
data,
overwriteExistingFiles,
req,
throwOnMissingFile:
!shouldSaveDraft && collection.config.upload.filesRequiredOnCreate !== false,
})
// TODO: this was temporarily commented out bc it throws Sharp compilation errors in Next.js
// const { data: newFileData, files: filesToUpload } = await generateFileData({
// collection,
// config,
// data,
// overwriteExistingFiles,
// req,
// throwOnMissingFile:
// !shouldSaveDraft && collection.config.upload.filesRequiredOnCreate !== false,
// })
data = newFileData
// data = newFileData
// /////////////////////////////////////
// beforeValidate - Fields
@@ -208,7 +209,7 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
// /////////////////////////////////////
if (!collectionConfig.upload.disableLocalStorage) {
await uploadFiles(payload, filesToUpload, req.t)
// await uploadFiles(payload, filesToUpload, req.t) // TODO: this was temporarily commented out bc it throws Sharp compilation errors in Next.js
}
// /////////////////////////////////////
@@ -356,7 +357,7 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
result,
})
await unlinkTempFiles({ collectionConfig, config, req })
// await unlinkTempFiles({ collectionConfig, config, req }) // TODO: this was temporarily commented out bc it throws Sharp compilation errors in Next.js
// /////////////////////////////////////
// Return results

View File

@@ -20,7 +20,6 @@ import type {
Validate,
} from './config/types'
import canUseDOM from '../utilities/canUseDOM'
import { getIDType } from '../utilities/getIDType'
import { isNumber } from '../utilities/isNumber'
import { isValidID } from '../utilities/isValidID'
@@ -53,7 +52,7 @@ export const text: Validate<unknown, unknown, TextField> = (
export const password: Validate<unknown, unknown, TextField> = (
value: string,
{ config, maxLength: fieldMaxLength, minLength, payload, required, t },
{ config, maxLength: fieldMaxLength, minLength, required, t },
) => {
let maxLength: number
@@ -85,7 +84,7 @@ export const email: Validate<unknown, unknown, EmailField> = (value: string, { r
export const textarea: Validate<unknown, unknown, TextareaField> = (
value: string,
{ config, maxLength: fieldMaxLength, minLength, payload, required, t },
{ config, maxLength: fieldMaxLength, minLength, required, t },
) => {
let maxLength: number
@@ -248,7 +247,7 @@ const validateFilterOptions: Validate = async (
value,
{ id, data, filterOptions, payload, relationTo, req, siblingData, t, user },
) => {
if (!canUseDOM && typeof filterOptions !== 'undefined' && value) {
if (typeof filterOptions !== 'undefined' && value) {
const options: {
[collection: string]: (number | string)[]
} = {}
@@ -343,7 +342,7 @@ export const upload: Validate<unknown, unknown, UploadField> = (value: string, o
return options.t('validation:required')
}
if (!canUseDOM && typeof value !== 'undefined' && value !== null) {
if (typeof value !== 'undefined' && value !== null) {
const idField = options?.config?.collections
?.find((collection) => collection.slug === options.relationTo)
?.fields?.find((field) => fieldAffectsData(field) && field.name === 'id')
@@ -378,7 +377,7 @@ export const relationship: Validate<unknown, unknown, RelationshipField> = async
}
}
if (!canUseDOM && typeof value !== 'undefined' && value !== null) {
if (typeof value !== 'undefined' && value !== null) {
const values = Array.isArray(value) ? value : [value]
const invalidRelationships = values.filter((val) => {

View File

@@ -15,7 +15,8 @@
"import": "./src/exports/*.ts",
"require": "./src/exports/*.ts",
"types": "./src/exports/*.ts"
}
},
"./scss/app.scss": "./src/scss/app.scss"
},
"devDependencies": {
"@payloadcms/eslint-config": "workspace:*",
@@ -54,7 +55,8 @@
"import": "./dist/exports/*.ts",
"require": "./dist/exports/*.ts",
"types": "./dist/exports/*.d.ts"
}
},
"./scss/app.scss": "./dist/scss/app.scss"
},
"main": "./dist/index.js",
"registry": "https://registry.npmjs.org/",

View File

@@ -2,7 +2,7 @@ import React, { createContext, useContext, useEffect, useState } from 'react'
import type { Locale } from 'payload/config'
import { findLocaleFromCode } from 'payload/utilities' // TODO: import this from 'payload/utilities' when it's available
import { findLocaleFromCode } from 'payload/dist/utilities/findLocaleFromCode' // TODO: import this from 'payload/utilities' when it's available
import { useAuth } from '../Auth'
import { useConfig } from '../Config'
import { usePreferences } from '../Preferences'

View File

@@ -13,7 +13,6 @@ import { LocaleProvider } from '../Locale'
import StepNav, { StepNavProvider } from '../../elements/StepNav'
import { LoadingOverlayProvider } from '../../elements/LoadingOverlay'
import { NavProvider } from '../../elements/Nav/context'
import '../../scss/app.scss'
type Props = {
config: ClientConfig

View File

@@ -1,68 +1,57 @@
'use client'
import React, { createContext, useCallback, useContext, useState } from 'react'
export type Theme = 'dark' | 'light'
export type ThemeContext = {
autoMode: boolean
setTheme: (theme: Theme) => void
theme: Theme
}
import React, { createContext, useCallback, useContext, useEffect, useState } from 'react'
import canUseDOM from '../../utilities/canUseDOM'
import { Theme, ThemeContext } from './types'
import { getImplicitPreference, themeIsValid } from './utilities'
const initialContext: ThemeContext = {
autoMode: true,
setTheme: () => null,
theme: 'light',
}
const Context = createContext(initialContext)
const localStorageKey = 'payload-theme'
export const themeLocalStorageKey = 'payload-theme'
const getTheme = () => {
let theme: Theme
const themeFromStorage = window.localStorage.getItem(localStorageKey)
if (themeFromStorage === 'light' || themeFromStorage === 'dark') {
theme = themeFromStorage
} else {
theme =
window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light'
}
document.documentElement.setAttribute('data-theme', theme)
return theme
}
export const defaultTheme = 'light'
export const ThemeProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
const [theme, setThemeState] = useState<Theme>(getTheme)
const [autoMode, setAutoMode] = useState(() => {
const themeFromStorage = window.localStorage.getItem(localStorageKey)
return !themeFromStorage
})
const [theme, setThemeState] = useState<Theme | undefined>(
canUseDOM ? (document.documentElement.getAttribute('data-theme') as Theme) : undefined,
)
const setTheme = useCallback((themeToSet: 'auto' | Theme) => {
if (themeToSet === 'light' || themeToSet === 'dark') {
const setTheme = useCallback((themeToSet: Theme | null) => {
if (themeToSet === null) {
window.localStorage.removeItem(themeLocalStorageKey)
const implicitPreference = getImplicitPreference()
document.documentElement.setAttribute('data-theme', implicitPreference || '')
if (implicitPreference) setThemeState(implicitPreference)
} else {
setThemeState(themeToSet)
setAutoMode(false)
window.localStorage.setItem(localStorageKey, themeToSet)
window.localStorage.setItem(themeLocalStorageKey, themeToSet)
document.documentElement.setAttribute('data-theme', themeToSet)
} else if (themeToSet === 'auto') {
const existingThemeFromStorage = window.localStorage.getItem(localStorageKey)
if (existingThemeFromStorage) window.localStorage.removeItem(localStorageKey)
const themeFromOS =
window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light'
document.documentElement.setAttribute('data-theme', themeFromOS)
setAutoMode(true)
setThemeState(themeFromOS)
}
}, [])
return <Context.Provider value={{ autoMode, setTheme, theme }}>{children}</Context.Provider>
useEffect(() => {
let themeToSet: Theme = defaultTheme
const preference = window.localStorage.getItem(themeLocalStorageKey)
if (themeIsValid(preference)) {
themeToSet = preference
} else {
const implicitPreference = getImplicitPreference()
if (implicitPreference) {
themeToSet = implicitPreference
}
}
document.documentElement.setAttribute('data-theme', themeToSet)
setThemeState(themeToSet)
}, [])
return <Context.Provider value={{ setTheme, theme }}>{children}</Context.Provider>
}
export const useTheme = (): ThemeContext => useContext(Context)

View File

@@ -0,0 +1,6 @@
export type Theme = 'dark' | 'light'
export type ThemeContext = {
setTheme: (theme: Theme) => void
theme: Theme
}

View File

@@ -0,0 +1,17 @@
import { Theme } from './types'
export function themeIsValid(string: string | null): string is Theme {
return string ? ['light', 'dark'].includes(string) : false
}
export const getImplicitPreference = (): Theme | null => {
const mediaQuery = '(prefers-color-scheme: dark)'
const mql = window.matchMedia(mediaQuery)
const hasImplicitPreference = typeof mql.matches === 'boolean'
if (hasImplicitPreference) {
return mql.matches ? 'dark' : 'light'
}
return null
}

View File

@@ -68,6 +68,12 @@ html {
@extend %body;
background: var(--theme-bg);
-webkit-font-smoothing: antialiased;
opacity: 0;
&[data-theme='dark'],
&[data-theme='light'] {
opacity: initial;
}
&[data-theme='dark'] {
--theme-bg: var(--theme-elevation-0);

View File

@@ -21,11 +21,11 @@ export const DefaultDashboard: React.FC<{
return (
<div className={baseClass}>
<Gutter className={`${baseClass}__wrap`}>
{/* {Array.isArray(beforeDashboard) &&
beforeDashboard.map((Component, i) => <Component key={i} />)} */}
{/* <DefaultDashboardClient /> */}
{/* {Array.isArray(afterDashboard) &&
afterDashboard.map((Component, i) => <Component key={i} />)} */}
{Array.isArray(beforeDashboard) &&
beforeDashboard.map((Component, i) => <Component key={i} />)}
<DefaultDashboardClient />
{Array.isArray(afterDashboard) &&
afterDashboard.map((Component, i) => <Component key={i} />)}
</Gutter>
</div>
)

View File

@@ -20,6 +20,7 @@
"src/**/*.spec.tsx"
],
"include": [
"src/scss/app.scss",
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.d.ts",