Skip to content

Commit

Permalink
feat: visually editable site theme
Browse files Browse the repository at this point in the history
  • Loading branch information
agurtovoy authored and apledger committed Nov 11, 2024
1 parent e27ebe4 commit 1fef0b7
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 26 deletions.
29 changes: 16 additions & 13 deletions core/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ import { client } from '~/client';
import { graphql } from '~/client/graphql';
import { revalidate } from '~/client/revalidate-target';
import { MakeswiftProvider } from '~/lib/makeswift/provider';
import { CssTheme } from '~/makeswift/components/css-theme';

import { Notifications } from '../notifications';
import { Providers } from '../providers';
import { colors } from '../theme';

import '~/lib/makeswift/components';

Expand Down Expand Up @@ -113,20 +115,21 @@ export default function RootLayout({ children, params: { locale } }: Props) {
className={[inter.variable, dm_serif_text.variable, roboto_mono.variable].join(' ')}
lang={locale}
>
<head>
<DraftModeScript appOrigin={process.env.MAKESWIFT_APP_ORIGIN} />
</head>
<body className="flex h-screen min-w-[375px] flex-col font-body">
<Notifications />
<NextIntlClientProvider locale={locale} messages={messages}>
<NuqsAdapter>
<MakeswiftProvider previewMode={draftMode().isEnabled}>
<MakeswiftProvider previewMode={draftMode().isEnabled}>
<head>
<CssTheme colors={colors} />
<DraftModeScript appOrigin={process.env.MAKESWIFT_APP_ORIGIN} />
</head>
<body className="flex h-screen min-w-[375px] flex-col font-body">
<Notifications />
<NextIntlClientProvider locale={locale} messages={messages}>
<NuqsAdapter>
<Providers>{children}</Providers>
</MakeswiftProvider>
</NuqsAdapter>
</NextIntlClientProvider>
<VercelComponents />
</body>
</NuqsAdapter>
</NextIntlClientProvider>
<VercelComponents />
</body>
</MakeswiftProvider>
</html>
);
}
Expand Down
13 changes: 0 additions & 13 deletions core/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,6 @@
@tailwind utilities;

:root {
--primary: 96 100% 68%;
--accent: 96 100% 88%;
--background: 0 0% 100%;
--foreground: 0 0% 7%;
--success: 116 78% 65%;
--error: 0 100% 60%;
--warning: 40 100% 60%;
--info: 220 70% 45%;
--contrast-100: 0 0% 93%;
--contrast-200: 0 0% 82%;
--contrast-300: 0 0% 70%;
--contrast-400: 0 0% 54%;
--contrast-500: 0 0% 34%;
--font-variation-settings-body: 'slnt' 0;
--font-variation-settings-heading: 'slnt' 0;
--font-size-xs: 0.75rem;
Expand Down
17 changes: 17 additions & 0 deletions core/app/theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export const colors = {
primary: '96 100% 68%',
accent: '96 100% 88%',
background: '0 0% 100%',
foreground: '0 0% 7%',
success: '116 78% 65%',
error: '0 100% 60%',
warning: '40 100% 60%',
info: '220 70% 45%',
contrast: {
100: '0 0% 93%',
200: '0 0% 82%',
300: '0 0% 70%',
400: '0 0% 54%',
500: '0 0% 34%',
},
};
1 change: 1 addition & 0 deletions core/lib/makeswift/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import '~/makeswift/components/slideshow/slideshow.makeswift';
import '~/makeswift/components/featured-image/featured-image.makeswift';
import '~/makeswift/components/sticky-sidebar-layout/sticky-sidebar-layout.makeswift';
import '~/makeswift/components/section-layout/section-layout.makeswift';
import '~/makeswift/components/css-theme/register';

import { MakeswiftComponentType } from '@makeswift/runtime';

Expand Down
80 changes: 80 additions & 0 deletions core/makeswift/components/css-theme/client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
'use client';

import { createContext, type PropsWithChildren, useContext } from 'react';

type Colors = {
primary?: string;
accent?: string;
success?: string;
error?: string;
warning?: string;
info?: string;
background?: string;
foreground?: string;
contrast?: {
100?: string;
200?: string;
300?: string;
400?: string;
500?: string;
};
};

type Props = {
colors: Colors;
};

const colorToHslValue = (color: string) =>
color.startsWith('rgb') ? `from ${color} h s l` : color;

const colorToCssVar = (name: string, color: string) => `--${name}: ${colorToHslValue(color)};`;

function colorsToCssVars(colors: Colors) {
const { contrast, ...rest } = colors;

const mainColors = Object.entries(rest).map(([key, color]) =>
color != null ? colorToCssVar(key, color) : null,
);

const contrastColors = Object.entries(contrast ?? {}).map(([value, color]) =>
color != null ? colorToCssVar(`contrast-${value}`, color) : null,
);

return [...mainColors, ...contrastColors].filter(Boolean).join('\n');
}

export const CssTheme = ({ colors }: Props) => {
return (
<style data-makeswift="theme">{`:root {
${colorsToCssVars(colors)}
}
`}</style>
);
};

const PropsContext = createContext<Props>({ colors: {} });

export const PropsContextProvider = ({ value, children }: PropsWithChildren<{ value: Props }>) => (
<PropsContext.Provider value={value}>{children}</PropsContext.Provider>
);

type ColorMap<K extends string> = {
[key in K]?: string | ColorMap<K>;
};

function mergeColors<K extends string>(left: ColorMap<K>, right: ColorMap<K>): ColorMap<K> {
const result = { ...left };
for (const key in right) {
const rightValue = right[key];
if (rightValue != null) {
result[key] =
typeof rightValue === 'object' ? mergeColors(left[key] ?? {}, rightValue) : rightValue;
}
}
return result;
}

export const MakeswiftCssTheme = ({ colors }: Props) => {
const { colors: passedColors } = useContext(PropsContext);
return <CssTheme colors={mergeColors(passedColors, colors)} />;
};
26 changes: 26 additions & 0 deletions core/makeswift/components/css-theme/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { MakeswiftComponent } from '@makeswift/runtime/next';
import { type ComponentPropsWithoutRef } from 'react';

import { getComponentSnapshot } from '~/lib/makeswift/client';

import { CssTheme as CssThemeClient, PropsContextProvider } from './client';
import { COMPONENT_TYPE } from './register';

type Props = ComponentPropsWithoutRef<typeof CssThemeClient> & {
snapshotId?: string;
label?: string;
};

export const CssTheme = async ({
snapshotId = 'site-theme',
label = 'Site Theme',
...props
}: Props) => {
const snapshot = await getComponentSnapshot(snapshotId);

return (
<PropsContextProvider value={props}>
<MakeswiftComponent label={label} snapshot={snapshot} type={COMPONENT_TYPE} />
</PropsContextProvider>
);
};
39 changes: 39 additions & 0 deletions core/makeswift/components/css-theme/register.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Color, Shape } from '@makeswift/runtime/controls';

import { runtime } from '~/lib/makeswift/runtime';
import { colors } from 'app/theme';

import { MakeswiftCssTheme } from './client';

export const COMPONENT_TYPE = 'catalyst-makeswift-theme-provider';

const toHsl = (hslValues: string) => `hsl(${hslValues})`;

runtime.registerComponent(MakeswiftCssTheme, {
type: COMPONENT_TYPE,
label: 'MakeswiftCssTheme (private)',
hidden: true,
props: {
colors: Shape({
type: {
primary: Color({ label: 'Primary', defaultValue: toHsl(colors.primary) }),
accent: Color({ label: 'Accent', defaultValue: toHsl(colors.accent) }),
success: Color({ label: 'Success', defaultValue: toHsl(colors.success) }),
error: Color({ label: 'Error', defaultValue: toHsl(colors.error) }),
warning: Color({ label: 'Warning', defaultValue: toHsl(colors.warning) }),
info: Color({ label: 'Info', defaultValue: toHsl(colors.info) }),
background: Color({ label: 'Background', defaultValue: toHsl(colors.background) }),
foreground: Color({ label: 'Foreground', defaultValue: toHsl(colors.foreground) }),
contrast: Shape({
type: {
100: Color({ label: 'Contrast 100', defaultValue: toHsl(colors.contrast[100]) }),
200: Color({ label: 'Contrast 200', defaultValue: toHsl(colors.contrast[200]) }),
300: Color({ label: 'Contrast 300', defaultValue: toHsl(colors.contrast[300]) }),
400: Color({ label: 'Contrast 400', defaultValue: toHsl(colors.contrast[400]) }),
500: Color({ label: 'Contrast 500', defaultValue: toHsl(colors.contrast[500]) }),
},
}),
},
}),
},
});

0 comments on commit 1fef0b7

Please sign in to comment.