diff --git a/packages/dev/.env.example b/packages/dev/.env.example new file mode 100644 index 0000000000..f5e3035f39 --- /dev/null +++ b/packages/dev/.env.example @@ -0,0 +1 @@ +PAYLOAD_SECRET=ls3nd09adff9cmlq71j diff --git a/packages/next/src/layouts/Root/index.tsx b/packages/next/src/layouts/Root/index.tsx index 1738db036c..6a3d7dada5 100644 --- a/packages/next/src/layouts/Root/index.tsx +++ b/packages/next/src/layouts/Root/index.tsx @@ -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', diff --git a/packages/next/src/pages/Dashboard/index.tsx b/packages/next/src/pages/Dashboard/index.tsx index a933ead8b1..d22727b98e 100644 --- a/packages/next/src/pages/Dashboard/index.tsx +++ b/packages/next/src/pages/Dashboard/index.tsx @@ -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 }) => - async function () { - const config = await configPromise +export const Dashboard = async ({ + config: configPromise, +}: { + config: Promise +}) => { + const config = await configPromise - const CustomDashboardComponent = config.admin.components?.views?.Dashboard + const CustomDashboardComponent = config.admin.components?.views?.Dashboard - return ( - - ) - } + return ( + + ) +} diff --git a/packages/payload/src/collections/operations/create.ts b/packages/payload/src/collections/operations/create.ts index e74a230f8f..e7dbd9f4aa 100644 --- a/packages/payload/src/collections/operations/create.ts +++ b/packages/payload/src/collections/operations/create.ts @@ -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( // 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( // ///////////////////////////////////// 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( 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 diff --git a/packages/payload/src/fields/validations.ts b/packages/payload/src/fields/validations.ts index ea2cc8c24e..9692c1e6cd 100644 --- a/packages/payload/src/fields/validations.ts +++ b/packages/payload/src/fields/validations.ts @@ -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 = ( export const password: Validate = ( 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 = (value: string, { r export const textarea: Validate = ( 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 = (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 = 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) => { diff --git a/packages/ui/package.json b/packages/ui/package.json index 3ad5d78fc4..f233815349 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -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/", diff --git a/packages/ui/src/providers/Locale/index.tsx b/packages/ui/src/providers/Locale/index.tsx index ebcf84eced..b7b1d27d3f 100644 --- a/packages/ui/src/providers/Locale/index.tsx +++ b/packages/ui/src/providers/Locale/index.tsx @@ -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' diff --git a/packages/ui/src/providers/Root/index.tsx b/packages/ui/src/providers/Root/index.tsx index 7fd8333c4d..582b0936f0 100644 --- a/packages/ui/src/providers/Root/index.tsx +++ b/packages/ui/src/providers/Root/index.tsx @@ -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 diff --git a/packages/ui/src/providers/Theme/index.tsx b/packages/ui/src/providers/Theme/index.tsx index e11d6d51d4..6da66a3445 100644 --- a/packages/ui/src/providers/Theme/index.tsx +++ b/packages/ui/src/providers/Theme/index.tsx @@ -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(getTheme) - const [autoMode, setAutoMode] = useState(() => { - const themeFromStorage = window.localStorage.getItem(localStorageKey) - return !themeFromStorage - }) + const [theme, setThemeState] = useState( + 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 {children} + 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 {children} } export const useTheme = (): ThemeContext => useContext(Context) diff --git a/packages/ui/src/providers/Theme/types.ts b/packages/ui/src/providers/Theme/types.ts new file mode 100644 index 0000000000..e1778a8746 --- /dev/null +++ b/packages/ui/src/providers/Theme/types.ts @@ -0,0 +1,6 @@ +export type Theme = 'dark' | 'light' + +export type ThemeContext = { + setTheme: (theme: Theme) => void + theme: Theme +} diff --git a/packages/ui/src/providers/Theme/utilities.ts b/packages/ui/src/providers/Theme/utilities.ts new file mode 100644 index 0000000000..94374f8a90 --- /dev/null +++ b/packages/ui/src/providers/Theme/utilities.ts @@ -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 +} diff --git a/packages/ui/src/scss/app.scss b/packages/ui/src/scss/app.scss index 5462ec4024..690f201c5c 100644 --- a/packages/ui/src/scss/app.scss +++ b/packages/ui/src/scss/app.scss @@ -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); diff --git a/packages/payload/src/utilities/canUseDOM.ts b/packages/ui/src/utilities/canUseDOM.ts similarity index 100% rename from packages/payload/src/utilities/canUseDOM.ts rename to packages/ui/src/utilities/canUseDOM.ts diff --git a/packages/ui/src/views/Dashboard/index.tsx b/packages/ui/src/views/Dashboard/index.tsx index 5a7ed27240..48c21b9e23 100644 --- a/packages/ui/src/views/Dashboard/index.tsx +++ b/packages/ui/src/views/Dashboard/index.tsx @@ -21,11 +21,11 @@ export const DefaultDashboard: React.FC<{ return (
- {/* {Array.isArray(beforeDashboard) && - beforeDashboard.map((Component, i) => )} */} - {/* */} - {/* {Array.isArray(afterDashboard) && - afterDashboard.map((Component, i) => )} */} + {Array.isArray(beforeDashboard) && + beforeDashboard.map((Component, i) => )} + + {Array.isArray(afterDashboard) && + afterDashboard.map((Component, i) => )}
) diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json index 130639454f..7819e40e8e 100644 --- a/packages/ui/tsconfig.json +++ b/packages/ui/tsconfig.json @@ -20,6 +20,7 @@ "src/**/*.spec.tsx" ], "include": [ + "src/scss/app.scss", "src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts",