-
Notifications
You must be signed in to change notification settings - Fork 140
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: visually editable site theme #1614
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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%; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved out of CSS to a Typescript file so we can reuse these values in theme component registration to provide props with default values. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm wondering if there's a better way than deleting things for when we need to separate Makeswift related integration code from main. Ideally we don't have to delete anything from the base repo, just add Makeswift related code. Maybe that's wishful thinking. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is a good goal and we can accomplish it by overriding these values with Makeswift CSS variables further down. That being said, sometimes conflicts are good as we would want to know when this list changes to that we can do the equivalent change on |
||
--font-variation-settings-body: 'slnt' 0; | ||
--font-variation-settings-heading: 'slnt' 0; | ||
--font-size-xs: 0.75rem; | ||
|
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%', | ||
}, | ||
}; |
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)} />; | ||
}; |
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> | ||
); | ||
}; |
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]) }), | ||
}, | ||
}), | ||
}, | ||
}), | ||
}, | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved up
<MakeswiftProvider>
so that it wraps the whole document, including<head>
where<CssTheme>
is injecting the styles.