Skip to content

Commit e5813a0

Browse files
committed
working selection of fonts; typography with preview; interactives with previews (wip)
1 parent 6486327 commit e5813a0

11 files changed

Lines changed: 1456 additions & 383 deletions

File tree

apps/web/app/layout.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { getBackendAddress } from "@ui-lib/utils";
99
import { FetchBuilder } from "@courselit/utils";
1010
import { SiteInfo } from "@courselit/common-models";
1111
import { SITE_SETTINGS_DEFAULT_TITLE } from "@ui-config/strings";
12+
import * as fonts from "@/lib/fonts";
1213

1314
export async function generateMetadata(): Promise<Metadata> {
1415
const headersList = headers();
@@ -82,7 +83,10 @@ export default function RootLayout({
8283
children: React.ReactNode;
8384
}) {
8485
return (
85-
<html lang="en">
86+
<html
87+
lang="en"
88+
className={`${fonts.openSans.variable} ${fonts.montserrat.variable} ${fonts.lato.variable} ${fonts.poppins.variable} ${fonts.sourceSans3.variable} ${fonts.raleway.variable} ${fonts.notoSans.variable} ${fonts.merriweather.variable} ${fonts.inter.variable} ${fonts.alegreya.variable} ${fonts.roboto.variable} ${fonts.mulish.variable} ${fonts.nunito.variable} ${fonts.rubik.variable} ${fonts.playfairDisplay.variable} ${fonts.oswald.variable} ${fonts.ptSans.variable} ${fonts.workSans.variable} ${fonts.robotoSlab.variable} ${fonts.sourceSerif4.variable} ${fonts.bebasNeue.variable} ${fonts.quicksand.variable} font-sans`}
89+
>
8690
<body>{children}</body>
8791
</html>
8892
);

apps/web/components/admin/page-editor/index.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ import {
5656
SelectTrigger,
5757
SelectValue,
5858
} from "@/components/ui/select";
59-
import { ThemeContext } from "@components/contexts";
6059

6160
const EditWidget = dynamic(() => import("./edit-widget"));
6261
const AddWidget = dynamic(() => import("./add-widget"));

apps/web/components/admin/page-editor/theme-editor/index.tsx

Lines changed: 210 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,16 @@ import {
99
SquareMousePointer,
1010
Type,
1111
ChevronLeft,
12+
ChevronDown,
1213
} from "lucide-react";
1314
import TypographySelector from "./typography-selector";
1415
import { toast } from "@/hooks/use-toast";
1516
import { AddressContext } from "@components/contexts";
1617
import { TOAST_TITLE_ERROR } from "@ui-config/strings";
18+
import {
19+
InteractiveSelector,
20+
interactiveDisplayNames,
21+
} from "./interactive-selector";
1722

1823
interface ThemeEditorProps {
1924
draftTheme: Theme;
@@ -58,26 +63,62 @@ const sections: Section[] = [
5863
];
5964

6065
const typographyDisplayNames: Record<string, string> = {
61-
preheader: "Preheader",
66+
// Headers
6267
header1: "Header 1",
6368
header2: "Header 2",
6469
header3: "Header 3",
6570
header4: "Header 4",
71+
preheader: "Preheader",
72+
73+
// Subheaders
6674
subheader1: "Subheader 1",
6775
subheader2: "Subheader 2",
76+
77+
// Body Text
6878
text1: "Text 1",
6979
text2: "Text 2",
80+
caption: "Caption",
81+
82+
// Interactive Elements
7083
link: "Link",
7184
button: "Button Text",
7285
input: "Input Text",
73-
caption: "Caption",
86+
} as const;
87+
88+
// Add a type for the categories
89+
type TypographyCategory = {
90+
name: string;
91+
items: string[];
7492
};
7593

94+
// Define the categories
95+
const typographyCategories: TypographyCategory[] = [
96+
{
97+
name: "Headers",
98+
items: ["header1", "header2", "header3", "header4", "preheader"],
99+
},
100+
{
101+
name: "Subheaders",
102+
items: ["subheader1", "subheader2"],
103+
},
104+
{
105+
name: "Body Text",
106+
items: ["text1", "text2", "caption"],
107+
},
108+
{
109+
name: "Interactive Elements",
110+
items: ["link", "button", "input"],
111+
},
112+
];
113+
76114
function ThemeEditor({ draftTheme, onClose, onSave }: ThemeEditorProps) {
77115
const [theme, setTheme] = useState<Theme>(draftTheme);
78116
const [navigationStack, setNavigationStack] = useState<NavigationItem[]>(
79117
[],
80118
);
119+
const [collapsedCategories, setCollapsedCategories] = useState<
120+
Record<string, boolean>
121+
>({});
81122
const address = useContext(AddressContext);
82123

83124
React.useEffect(() => {
@@ -92,6 +133,13 @@ function ThemeEditor({ draftTheme, onClose, onSave }: ThemeEditorProps) {
92133
setNavigationStack((prev) => prev.slice(0, -1));
93134
};
94135

136+
const toggleCategory = (categoryName: string) => {
137+
setCollapsedCategories((prev) => ({
138+
...prev,
139+
[categoryName]: !prev[categoryName],
140+
}));
141+
};
142+
95143
const updateThemeCategory = async (
96144
category: "colors" | "typography" | "interactives" | "structure",
97145
categoryData: Record<string, string>,
@@ -129,6 +177,7 @@ function ThemeEditor({ draftTheme, onClose, onSave }: ThemeEditorProps) {
129177

130178
const getCurrentView = () => {
131179
const currentItem = navigationStack[navigationStack.length - 1];
180+
const parentItem = navigationStack[navigationStack.length - 2];
132181

133182
if (!currentItem) {
134183
// Root view - Main sections
@@ -161,7 +210,10 @@ function ThemeEditor({ draftTheme, onClose, onSave }: ThemeEditorProps) {
161210
}
162211

163212
// Check if current item is a typography item
164-
if (currentItem.id in theme.typography) {
213+
if (
214+
parentItem?.id === "typography" &&
215+
currentItem.id in theme.typography
216+
) {
165217
return (
166218
<TypographySelector
167219
title={
@@ -190,63 +242,173 @@ function ThemeEditor({ draftTheme, onClose, onSave }: ThemeEditorProps) {
190242
);
191243
}
192244

245+
// Check if current item is an interactive item
246+
if (
247+
parentItem?.id === "interactives" &&
248+
currentItem.id in theme.interactives
249+
) {
250+
return (
251+
<InteractiveSelector
252+
title={
253+
interactiveDisplayNames[currentItem.id] ||
254+
capitalize(currentItem.id)
255+
}
256+
type={
257+
currentItem.id as "button" | "link" | "card" | "input"
258+
}
259+
value={theme.interactives[currentItem.id]}
260+
onChange={async (value) => {
261+
const updatedInteractives = {
262+
...theme.interactives,
263+
[currentItem.id]: value,
264+
};
265+
setTheme({
266+
...theme,
267+
interactives: updatedInteractives,
268+
});
269+
await updateThemeCategory(
270+
"interactives",
271+
updatedInteractives as unknown as Record<
272+
string,
273+
string
274+
>,
275+
);
276+
}}
277+
/>
278+
);
279+
}
280+
193281
switch (currentItem.id) {
194282
case "colors":
195283
return (
196284
<div className="space-y-2 p-2">
197-
{Object.keys(theme.colors).map((color) => (
198-
<ColorSelector
199-
key={color}
200-
title={capitalize(color)}
201-
value={theme.colors[color]}
202-
onChange={async (value) => {
203-
const updatedColors = {
204-
...theme.colors,
205-
[color]: value,
206-
};
207-
setTheme({
208-
...theme,
209-
colors: updatedColors,
210-
});
211-
await updateThemeCategory(
212-
"colors",
213-
updatedColors as unknown as Record<
214-
string,
215-
string
216-
>,
217-
);
218-
}}
219-
allowReset={false}
220-
/>
221-
))}
285+
{Object.keys(theme.colors)
286+
.filter(
287+
(color) =>
288+
![
289+
"success",
290+
"warning",
291+
"error",
292+
"info",
293+
].includes(color),
294+
)
295+
.map((color) => (
296+
<ColorSelector
297+
key={color}
298+
title={capitalize(color)}
299+
value={theme.colors[color]}
300+
onChange={async (value) => {
301+
const updatedColors = {
302+
...theme.colors,
303+
[color]: value,
304+
};
305+
setTheme({
306+
...theme,
307+
colors: updatedColors,
308+
});
309+
await updateThemeCategory(
310+
"colors",
311+
updatedColors as unknown as Record<
312+
string,
313+
string
314+
>,
315+
);
316+
}}
317+
allowReset={false}
318+
/>
319+
))}
222320
</div>
223321
);
224322
case "typography":
225323
return (
226324
<div className="space-y-1 p-2">
227-
{Object.keys(theme.typography).map((typography) => (
228-
<button
229-
key={typography}
230-
onClick={() =>
231-
navigateTo({
232-
id: typography,
233-
label:
234-
typographyDisplayNames[
235-
typography
236-
] || capitalize(typography),
237-
})
238-
}
239-
className="w-full flex items-center justify-between px-3 py-2 text-sm rounded-md hover:bg-muted transition-colors group"
240-
>
241-
<span className="group-hover:text-foreground transition-colors">
242-
{typographyDisplayNames[typography] ||
243-
capitalize(typography)}
244-
</span>
245-
<ExpandMoreRight className="h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
246-
</button>
325+
{typographyCategories.map((category) => (
326+
<div key={category.name} className="mb-2">
327+
<button
328+
type="button"
329+
className="flex items-center w-full px-2 py-1.5 text-sm hover:bg-muted rounded transition-colors"
330+
onClick={() =>
331+
toggleCategory(category.name)
332+
}
333+
>
334+
<ChevronDown
335+
className={`h-4 w-4 mr-2 transition-transform ${collapsedCategories[category.name] ? "rotate-[-90deg]" : "rotate-0"}`}
336+
/>
337+
{category.name}
338+
</button>
339+
{!collapsedCategories[category.name] && (
340+
<div>
341+
{category.items.map(
342+
(typography, idx) => (
343+
<button
344+
key={typography}
345+
onClick={() =>
346+
navigateTo({
347+
id: typography,
348+
label:
349+
typographyDisplayNames[
350+
typography
351+
] ||
352+
capitalize(
353+
typography,
354+
),
355+
})
356+
}
357+
className={
358+
`w-full flex items-center justify-between px-3 py-2 text-xs rounded-md hover:bg-muted transition-colors group` +
359+
(idx ===
360+
category.items.length -
361+
1
362+
? " mb-4"
363+
: "")
364+
}
365+
>
366+
<span className="group-hover:text-foreground transition-colors">
367+
{typographyDisplayNames[
368+
typography
369+
] ||
370+
capitalize(
371+
typography,
372+
)}
373+
</span>
374+
<ExpandMoreRight className="h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
375+
</button>
376+
),
377+
)}
378+
</div>
379+
)}
380+
</div>
247381
))}
248382
</div>
249383
);
384+
case "interactives":
385+
return (
386+
<div className="space-y-1 p-2">
387+
{Object.keys(interactiveDisplayNames).map(
388+
(interactive) => (
389+
<button
390+
key={interactive}
391+
onClick={() =>
392+
navigateTo({
393+
id: interactive,
394+
label:
395+
interactiveDisplayNames[
396+
interactive
397+
] || capitalize(interactive),
398+
})
399+
}
400+
className="w-full flex items-center justify-between px-3 py-2 text-xs rounded-md hover:bg-muted transition-colors group"
401+
>
402+
<span className="group-hover:text-foreground transition-colors">
403+
{interactiveDisplayNames[interactive] ||
404+
capitalize(interactive)}
405+
</span>
406+
<ExpandMoreRight className="h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
407+
</button>
408+
),
409+
)}
410+
</div>
411+
);
250412
default:
251413
return (
252414
<p className="text-xs text-muted-foreground p-2">

0 commit comments

Comments
 (0)