Skip to content

Commit fc1f459

Browse files
committed
theme's new model; new package: @courselit/page-primitives; a few widgets use primitives partially
1 parent 75f942d commit fc1f459

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+15524
-10622
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ coverage
1414
# Text editors configurations
1515
.vscode
1616
.rgignore
17+
.cursor
1718

1819
# Env file
1920
.env*.local

apps/web/app/(with-contexts)/dashboard/page/[id]/page.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
TypefacesContext,
1010
} from "@components/contexts";
1111
import { Profile } from "@courselit/common-models";
12+
import { defaultTheme } from "@courselit/page-primitives";
1213
import { useSearchParams } from "next/navigation";
1314
import { useContext } from "react";
1415

@@ -46,11 +47,7 @@ export default function Page({ params }: { params: { id: string } }) {
4647
checked: profile ? true : false,
4748
},
4849
networkAction: false,
49-
theme: {
50-
name: "dummy",
51-
active: false,
52-
styles: {},
53-
},
50+
theme: defaultTheme,
5451
typefaces: [
5552
{
5653
section: "default",

apps/web/app/(with-contexts)/layout.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ export default async function Layout({
4444
lemonsqueezySubscriptionYearlyVariantId,
4545
},
4646
theme {
47-
name,
48-
active,
49-
styles,
50-
url
47+
colors,
48+
typography,
49+
interactives,
50+
structure
5151
},
5252
typefaces {
5353
section,

apps/web/app/layout.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import "remirror/styles/all.css";
22
import "@courselit/common-widgets/styles.css";
33
import "@courselit/components-library/styles.css";
4+
import "@courselit/page-primitives/styles.css";
45
import "../styles/globals.css";
56
import type { Metadata } from "next";
67
import { headers } from "next/headers";
@@ -39,10 +40,10 @@ export async function generateMetadata(): Promise<Metadata> {
3940
lemonsqueezySubscriptionYearlyVariantId,
4041
},
4142
theme {
42-
name,
43-
active,
44-
styles,
45-
url
43+
colors,
44+
typography,
45+
interactives,
46+
structure
4647
},
4748
typefaces {
4849
section,

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

Lines changed: 228 additions & 92 deletions
Large diffs are not rendered by default.
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
import React, { useState } from "react";
2+
import { Theme } from "@courselit/common-models";
3+
import { EDIT_PAGE_BUTTON_THEME } from "../../../../ui-config/strings";
4+
import { Cross as Close, Back, ExpandMoreRight } from "@courselit/icons";
5+
import { IconButton, ColorSelector } from "@courselit/components-library";
6+
import { capitalize } from "@courselit/utils";
7+
import { Columns3, PaletteIcon, SquareMousePointer, Type } from "lucide-react";
8+
import TypographySelector from "./typography-selector";
9+
10+
interface ThemeEditorProps {
11+
draftTheme: Theme;
12+
onClose: () => void;
13+
onSave: (theme: Theme) => void;
14+
}
15+
16+
type Section = {
17+
id: string;
18+
label: string;
19+
icon: React.ReactNode;
20+
};
21+
22+
type NavigationItem = {
23+
id: string;
24+
label: string;
25+
component?: React.ReactNode;
26+
parent?: string;
27+
};
28+
29+
const sections: Section[] = [
30+
{
31+
id: "colors",
32+
label: "Colors",
33+
icon: <PaletteIcon className="h-4 w-4" />,
34+
},
35+
{
36+
id: "typography",
37+
label: "Typography",
38+
icon: <Type className="h-4 w-4" />,
39+
},
40+
{
41+
id: "interactives",
42+
label: "Interactives",
43+
icon: <SquareMousePointer className="h-4 w-4" />,
44+
},
45+
{
46+
id: "structure",
47+
label: "Structure",
48+
icon: <Columns3 className="h-4 w-4" />,
49+
},
50+
];
51+
52+
const typographyDisplayNames: Record<string, string> = {
53+
preheader: "Preheader",
54+
header1: "Header 1",
55+
header2: "Header 2",
56+
header3: "Header 3",
57+
header4: "Header 4",
58+
subheader1: "Subheader 1",
59+
subheader2: "Subheader 2",
60+
text1: "Text 1",
61+
text2: "Text 2",
62+
link: "Link",
63+
button: "Button Text",
64+
input: "Input Text",
65+
caption: "Caption",
66+
};
67+
68+
function ThemeEditor({ draftTheme, onClose, onSave }: ThemeEditorProps) {
69+
const [theme, setTheme] = useState<Theme>(draftTheme);
70+
const [navigationStack, setNavigationStack] = useState<NavigationItem[]>(
71+
[],
72+
);
73+
74+
// Auto-save changes
75+
React.useEffect(() => {
76+
onSave(theme);
77+
}, [theme, onSave]);
78+
79+
const navigateTo = (item: NavigationItem) => {
80+
setNavigationStack((prev) => [...prev, item]);
81+
};
82+
83+
const navigateBack = () => {
84+
setNavigationStack((prev) => prev.slice(0, -1));
85+
};
86+
87+
const getCurrentView = () => {
88+
const currentItem = navigationStack[navigationStack.length - 1];
89+
90+
if (!currentItem) {
91+
// Root view - Main sections
92+
return (
93+
<div className="p-2 space-y-1">
94+
{sections.map((section) => (
95+
<button
96+
key={section.id}
97+
onClick={() =>
98+
navigateTo({
99+
id: section.id,
100+
label: section.label,
101+
})
102+
}
103+
className="w-full flex items-center justify-between px-3 py-2 text-sm rounded-md hover:bg-muted transition-colors group"
104+
>
105+
<div className="flex items-center gap-3">
106+
<div className="text-muted-foreground group-hover:text-foreground transition-colors">
107+
{section.icon}
108+
</div>
109+
<span className="group-hover:text-foreground transition-colors">
110+
{section.label}
111+
</span>
112+
</div>
113+
<ExpandMoreRight className="h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
114+
</button>
115+
))}
116+
</div>
117+
);
118+
}
119+
120+
// Check if current item is a typography item
121+
if (currentItem.id in theme.typography) {
122+
return (
123+
<TypographySelector
124+
title={
125+
typographyDisplayNames[currentItem.id] ||
126+
capitalize(currentItem.id)
127+
}
128+
value={theme.typography[currentItem.id]}
129+
onChange={(value) =>
130+
setTheme({
131+
...theme,
132+
typography: {
133+
...theme.typography,
134+
[currentItem.id]: value,
135+
},
136+
})
137+
}
138+
/>
139+
);
140+
}
141+
142+
switch (currentItem.id) {
143+
case "colors":
144+
return (
145+
<div className="space-y-2 p-2">
146+
{Object.keys(theme.colors).map((color) => (
147+
<ColorSelector
148+
key={color}
149+
title={capitalize(color)}
150+
value={theme.colors[color]}
151+
onChange={(value) =>
152+
setTheme({
153+
...theme,
154+
colors: {
155+
...theme.colors,
156+
[color]: value,
157+
},
158+
})
159+
}
160+
allowReset={false}
161+
/>
162+
))}
163+
</div>
164+
);
165+
case "typography":
166+
return (
167+
<div className="space-y-1 p-2">
168+
{Object.keys(theme.typography).map((typography) => (
169+
<button
170+
key={typography}
171+
onClick={() =>
172+
navigateTo({
173+
id: typography,
174+
label:
175+
typographyDisplayNames[
176+
typography
177+
] || capitalize(typography),
178+
})
179+
}
180+
className="w-full flex items-center justify-between px-3 py-2 text-sm rounded-md hover:bg-muted transition-colors group"
181+
>
182+
<span className="group-hover:text-foreground transition-colors">
183+
{typographyDisplayNames[typography] ||
184+
capitalize(typography)}
185+
</span>
186+
<ExpandMoreRight className="h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
187+
</button>
188+
))}
189+
</div>
190+
);
191+
default:
192+
return (
193+
<p className="text-xs text-muted-foreground p-2">
194+
Coming soon
195+
</p>
196+
);
197+
}
198+
};
199+
200+
return (
201+
<div className="flex flex-col">
202+
<div className="flex items-center px-2 py-3 justify-between">
203+
<div className="flex items-center gap-1">
204+
{navigationStack.length > 0 && (
205+
<button
206+
onClick={navigateBack}
207+
className="flex items-center text-lg font-medium"
208+
>
209+
<Back className="h-4 w-4" />
210+
</button>
211+
)}
212+
<span className="text-lg font-medium">
213+
{navigationStack.length === 0
214+
? EDIT_PAGE_BUTTON_THEME
215+
: navigationStack[navigationStack.length - 1].label}
216+
</span>
217+
</div>
218+
<IconButton onClick={onClose} variant="soft">
219+
<Close fontSize="small" />
220+
</IconButton>
221+
</div>
222+
{getCurrentView()}
223+
</div>
224+
);
225+
}
226+
227+
export default ThemeEditor;

0 commit comments

Comments
 (0)