polish human language page styling

Improve visual hierarchy across header, filters, and nested content cards to make the Human Language management view cleaner and easier to scan.

Made-with: Cursor
This commit is contained in:
Yared Yemane 2026-04-08 00:47:54 -07:00
parent d1842579e9
commit 79da9014de

View File

@ -907,30 +907,43 @@ export function HumanLanguagePage() {
} }
return ( return (
<div className="space-y-6"> <div className="mx-auto w-full max-w-[1600px] space-y-6 pb-10">
<div className="rounded-2xl border border-grayScale-200 bg-gradient-to-r from-white to-brand-50/30 p-5 shadow-sm"> <div className="overflow-hidden rounded-3xl border border-brand-100/70 bg-gradient-to-r from-white via-brand-50/20 to-violet-50/40 p-6 shadow-sm sm:p-7">
<div className="flex items-start gap-3"> <div className="flex flex-wrap items-start justify-between gap-4">
<div className="rounded-xl bg-brand-100 p-2 text-brand-700"> <div className="flex items-start gap-3">
<Languages className="h-5 w-5" /> <div className="rounded-2xl bg-brand-100 p-2.5 text-brand-700 shadow-sm ring-1 ring-brand-200/70">
<Languages className="h-5 w-5" />
</div>
<div>
<h2 className="text-xl font-semibold tracking-tight text-grayScale-900">Human Language Content</h2>
<p className="mt-1.5 text-sm leading-relaxed text-grayScale-600">
Manage CEFR learning paths from A1 to C3 with quick lesson and practice oversight.
</p>
</div>
</div> </div>
<div> <div className="flex flex-wrap items-center gap-2">
<h2 className="text-lg font-semibold text-grayScale-900">Human Language Content</h2> <span className="rounded-full bg-white/90 px-3 py-1 text-xs font-semibold text-grayScale-700 ring-1 ring-grayScale-200">
<p className="mt-1 text-sm text-grayScale-500"> {selectedCourses.length} path{selectedCourses.length === 1 ? "" : "s"}
Dedicated management view for CEFR levels A1 to C3 with no sub-levels. </span>
</p> <span className="rounded-full bg-white/90 px-3 py-1 text-xs font-semibold text-grayScale-700 ring-1 ring-grayScale-200">
{subCategories.length} sub-categor{subCategories.length === 1 ? "y" : "ies"}
</span>
<span className="rounded-full bg-white/90 px-3 py-1 text-xs font-semibold text-grayScale-700 ring-1 ring-grayScale-200">
{visibleCefrLevels.length} level{visibleCefrLevels.length === 1 ? "" : "s"}
</span>
</div> </div>
</div> </div>
</div> </div>
<Card className="border-grayScale-200/80 shadow-sm"> <Card className="sticky top-3 z-20 border-grayScale-200/80 bg-white/95 shadow-sm backdrop-blur supports-[backdrop-filter]:bg-white/80">
<CardHeader> <CardHeader className="pb-3">
<CardTitle className="text-base">Filters</CardTitle> <CardTitle className="text-base font-semibold text-grayScale-900">Filters</CardTitle>
</CardHeader> </CardHeader>
<CardContent className="grid grid-cols-1 gap-4 md:grid-cols-3"> <CardContent className="grid grid-cols-1 gap-4 md:grid-cols-3">
<div className="space-y-1.5"> <div className="space-y-1.5">
<label className="text-xs font-medium uppercase tracking-wide text-grayScale-500">Subcategory</label> <label className="text-xs font-medium uppercase tracking-wide text-grayScale-500">Subcategory</label>
<select <select
className="h-10 w-full rounded-md border border-grayScale-200 bg-white px-3 text-sm" className="h-11 w-full rounded-xl border border-grayScale-200 bg-white px-3 text-sm shadow-sm transition focus:border-brand-400 focus:outline-none focus:ring-2 focus:ring-brand-100"
value={selectedSubCategoryId} value={selectedSubCategoryId}
onChange={(e) => onChange={(e) =>
setSelectedSubCategoryId(e.target.value === "ALL" ? "ALL" : Number(e.target.value)) setSelectedSubCategoryId(e.target.value === "ALL" ? "ALL" : Number(e.target.value))
@ -947,7 +960,7 @@ export function HumanLanguagePage() {
<div className="space-y-1.5"> <div className="space-y-1.5">
<label className="text-xs font-medium uppercase tracking-wide text-grayScale-500">Course</label> <label className="text-xs font-medium uppercase tracking-wide text-grayScale-500">Course</label>
<select <select
className="h-10 w-full rounded-md border border-grayScale-200 bg-white px-3 text-sm" className="h-11 w-full rounded-xl border border-grayScale-200 bg-white px-3 text-sm shadow-sm transition focus:border-brand-400 focus:outline-none focus:ring-2 focus:ring-brand-100"
value={selectedCourseId} value={selectedCourseId}
onChange={(e) => onChange={(e) =>
setSelectedCourseId(e.target.value === "ALL" ? "ALL" : Number(e.target.value)) setSelectedCourseId(e.target.value === "ALL" ? "ALL" : Number(e.target.value))
@ -964,7 +977,7 @@ export function HumanLanguagePage() {
<div className="space-y-1.5"> <div className="space-y-1.5">
<label className="text-xs font-medium uppercase tracking-wide text-grayScale-500">Fetch lessons by level</label> <label className="text-xs font-medium uppercase tracking-wide text-grayScale-500">Fetch lessons by level</label>
<select <select
className="h-10 w-full rounded-md border border-grayScale-200 bg-white px-3 text-sm" className="h-11 w-full rounded-xl border border-grayScale-200 bg-white px-3 text-sm shadow-sm transition focus:border-brand-400 focus:outline-none focus:ring-2 focus:ring-brand-100"
value={selectedLevel} value={selectedLevel}
onChange={(e) => setSelectedLevel(e.target.value as CefrLevel | "ALL")} onChange={(e) => setSelectedLevel(e.target.value as CefrLevel | "ALL")}
> >
@ -985,7 +998,7 @@ export function HumanLanguagePage() {
Loading human language lessons... Loading human language lessons...
</div> </div>
) : ( ) : (
<div className="space-y-3"> <div className="space-y-4">
{availableCourses.length === 0 ? ( {availableCourses.length === 0 ? (
<Card className="overflow-hidden border-grayScale-200/80"> <Card className="overflow-hidden border-grayScale-200/80">
<div className="flex items-center justify-between border-b border-grayScale-100 bg-white px-5 py-4"> <div className="flex items-center justify-between border-b border-grayScale-100 bg-white px-5 py-4">
@ -993,7 +1006,7 @@ export function HumanLanguagePage() {
<div className="relative w-full max-w-sm"> <div className="relative w-full max-w-sm">
<Search className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-grayScale-400" /> <Search className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-grayScale-400" />
<input <input
className="h-11 w-full rounded-xl border border-grayScale-200 bg-white pl-9 pr-3 text-sm" className="h-11 w-full rounded-xl border border-grayScale-200 bg-white pl-9 pr-3 text-sm shadow-sm transition focus:border-brand-400 focus:outline-none focus:ring-2 focus:ring-brand-100"
placeholder="Search sub-categories..." placeholder="Search sub-categories..."
value={quickSearch} value={quickSearch}
onChange={(e) => setQuickSearch(e.target.value)} onChange={(e) => setQuickSearch(e.target.value)}
@ -1011,13 +1024,13 @@ export function HumanLanguagePage() {
</p> </p>
<div className="mx-auto mt-5 grid max-w-3xl grid-cols-1 gap-2 md:grid-cols-3"> <div className="mx-auto mt-5 grid max-w-3xl grid-cols-1 gap-2 md:grid-cols-3">
<input <input
className="h-10 rounded-md border border-grayScale-200 bg-white px-3 text-sm" className="h-11 rounded-xl border border-grayScale-200 bg-white px-3 text-sm shadow-sm transition focus:border-brand-400 focus:outline-none focus:ring-2 focus:ring-brand-100"
placeholder="Subcategory (e.g., English)" placeholder="Subcategory (e.g., English)"
value={quickSubCategoryName} value={quickSubCategoryName}
onChange={(e) => setQuickSubCategoryName(e.target.value)} onChange={(e) => setQuickSubCategoryName(e.target.value)}
/> />
<input <input
className="h-10 rounded-md border border-grayScale-200 bg-white px-3 text-sm" className="h-11 rounded-xl border border-grayScale-200 bg-white px-3 text-sm shadow-sm transition focus:border-brand-400 focus:outline-none focus:ring-2 focus:ring-brand-100"
placeholder="Course (e.g., Speaking)" placeholder="Course (e.g., Speaking)"
value={quickCourseName} value={quickCourseName}
onChange={(e) => setQuickCourseName(e.target.value)} onChange={(e) => setQuickCourseName(e.target.value)}
@ -1046,8 +1059,8 @@ export function HumanLanguagePage() {
const pathLevelsFull = levelsDone.length >= CEFR_LEVELS.length const pathLevelsFull = levelsDone.length >= CEFR_LEVELS.length
return ( return (
<Card key={course.course_id} className="overflow-hidden border-grayScale-200/80 shadow-sm"> <Card key={course.course_id} className="overflow-hidden border-grayScale-200/80 shadow-sm transition-shadow hover:shadow-md">
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-grayScale-100 bg-white px-4 py-3"> <div className="flex flex-wrap items-center justify-between gap-3 border-b border-grayScale-100 bg-gradient-to-r from-white to-grayScale-50/40 px-4 py-3.5">
<button <button
type="button" type="button"
className="flex min-w-0 flex-1 items-center gap-2 text-left" className="flex min-w-0 flex-1 items-center gap-2 text-left"
@ -1059,6 +1072,9 @@ export function HumanLanguagePage() {
<ChevronDown className="h-5 w-5 shrink-0 text-grayScale-500" aria-hidden /> <ChevronDown className="h-5 w-5 shrink-0 text-grayScale-500" aria-hidden />
)} )}
<span className="text-base font-semibold text-brand-700">{course.course_name}</span> <span className="text-base font-semibold text-brand-700">{course.course_name}</span>
<span className="rounded-full bg-brand-50 px-2 py-0.5 text-[11px] font-semibold text-brand-700 ring-1 ring-brand-100">
{levelsDone.length}/{CEFR_LEVELS.length} levels
</span>
</button> </button>
<div className="flex flex-wrap items-center justify-end gap-2"> <div className="flex flex-wrap items-center justify-end gap-2">
<Button <Button
@ -1080,7 +1096,7 @@ export function HumanLanguagePage() {
</div> </div>
</div> </div>
{!pathCollapsed ? ( {!pathCollapsed ? (
<CardContent className="space-y-3 p-4"> <CardContent className="space-y-3 p-4 sm:p-5">
{courseLevels.length === 0 ? ( {courseLevels.length === 0 ? (
<p className="text-sm text-grayScale-500">No levels match the current level filter.</p> <p className="text-sm text-grayScale-500">No levels match the current level filter.</p>
) : ( ) : (
@ -1091,8 +1107,8 @@ export function HumanLanguagePage() {
const levelRemoveIds = modules.flatMap((m) => m.sub_modules.map((s) => s.id)) const levelRemoveIds = modules.flatMap((m) => m.sub_modules.map((s) => s.id))
const canRemoveLevel = levelRemoveIds.length > 0 const canRemoveLevel = levelRemoveIds.length > 0
return ( return (
<div key={levelKey} className="overflow-hidden rounded-lg border border-grayScale-200/90"> <div key={levelKey} className="overflow-hidden rounded-xl border border-grayScale-200/90 bg-white shadow-sm">
<div className="flex w-full flex-wrap items-center justify-between gap-2 border-b border-grayScale-100 bg-grayScale-50/60 px-4 py-3"> <div className="flex w-full flex-wrap items-center justify-between gap-2 border-b border-grayScale-100 bg-gradient-to-r from-grayScale-50/80 to-white px-4 py-3">
<button <button
type="button" type="button"
className="flex min-w-0 flex-1 items-center gap-2 text-left" className="flex min-w-0 flex-1 items-center gap-2 text-left"
@ -1126,11 +1142,12 @@ export function HumanLanguagePage() {
</Button> </Button>
</div> </div>
{!collapsedLevels.includes(levelKey) ? ( {!collapsedLevels.includes(levelKey) ? (
<div className="space-y-2 p-3"> <div className="space-y-2.5 p-3.5">
<div className="flex items-center justify-between gap-2"> <div className="flex items-center justify-between gap-2">
<Button <Button
size="sm" size="sm"
variant="outline" variant="outline"
className="h-8 border-grayScale-200 bg-white text-xs hover:border-brand-200 hover:bg-brand-50/40"
onClick={() => handleCreateModule(course.course_id, level, modules)} onClick={() => handleCreateModule(course.course_id, level, modules)}
disabled={creatingKey === `module-${course.course_id}-${level}`} disabled={creatingKey === `module-${course.course_id}-${level}`}
> >
@ -1146,13 +1163,14 @@ export function HumanLanguagePage() {
<p className="text-xs text-grayScale-500">No modules yet. Use Add Module to start.</p> <p className="text-xs text-grayScale-500">No modules yet. Use Add Module to start.</p>
) : ( ) : (
modules.map((module) => ( modules.map((module) => (
<div key={module.id} className="rounded-lg border border-grayScale-100 bg-grayScale-50/60 p-3"> <div key={module.id} className="rounded-xl border border-grayScale-100 bg-gradient-to-b from-grayScale-50/70 to-white p-3.5">
<div className="flex items-center justify-between gap-2"> <div className="flex items-center justify-between gap-2">
<p className="text-sm font-semibold text-grayScale-900">Module: {module.title}</p> <p className="text-sm font-semibold text-grayScale-900">Module: {module.title}</p>
<div className="flex gap-2"> <div className="flex gap-2">
<Button <Button
size="sm" size="sm"
variant="outline" variant="outline"
className="h-8 border-grayScale-200 bg-white text-xs hover:border-brand-200 hover:bg-brand-50/40"
onClick={() => onClick={() =>
handleCreateSubModule(course.course_id, level, module.title, module.sub_modules) handleCreateSubModule(course.course_id, level, module.title, module.sub_modules)
} }
@ -1169,7 +1187,7 @@ export function HumanLanguagePage() {
type="button" type="button"
size="sm" size="sm"
variant="outline" variant="outline"
className="h-8 gap-1 border-red-200/90 px-2.5 text-xs font-medium text-red-600 hover:bg-red-50" className="h-8 gap-1 border-red-200/90 bg-white px-2.5 text-xs font-medium text-red-600 hover:bg-red-50"
disabled={deletingKey === `module-${module.id}`} disabled={deletingKey === `module-${module.id}`}
onClick={() => onClick={() =>
requestRemove({ requestRemove({
@ -1210,9 +1228,9 @@ export function HumanLanguagePage() {
return ( return (
<div <div
key={subModule.id} key={subModule.id}
className="mt-2 overflow-hidden rounded-lg border border-grayScale-200/90 bg-white shadow-sm" className="mt-2 overflow-hidden rounded-xl border border-grayScale-200/90 bg-white shadow-sm"
> >
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-grayScale-100 bg-grayScale-50/90 px-3 py-2.5"> <div className="flex flex-wrap items-center justify-between gap-2 border-b border-grayScale-100 bg-gradient-to-r from-grayScale-50/90 to-white px-3 py-2.5">
<p className="text-sm font-semibold text-grayScale-800"> <p className="text-sm font-semibold text-grayScale-800">
Sub-module: {subModule.title} Sub-module: {subModule.title}
</p> </p>
@ -1221,7 +1239,7 @@ export function HumanLanguagePage() {
<Link <Link
to={`/content/human-language/${categoryId}/${course.course_id}/sub-module/${subModule.id}`} to={`/content/human-language/${categoryId}/${course.course_id}/sub-module/${subModule.id}`}
> >
<Button type="button" variant="outline" size="sm" className="h-8 text-xs"> <Button type="button" variant="outline" size="sm" className="h-8 border-grayScale-200 bg-white text-xs hover:border-brand-200 hover:bg-brand-50/40">
Open editor Open editor
</Button> </Button>
</Link> </Link>
@ -1229,7 +1247,7 @@ export function HumanLanguagePage() {
type="button" type="button"
size="sm" size="sm"
variant="outline" variant="outline"
className="h-8 gap-1 border-red-200/90 px-2.5 text-xs font-medium text-red-600 hover:bg-red-50" className="h-8 gap-1 border-red-200/90 bg-white px-2.5 text-xs font-medium text-red-600 hover:bg-red-50"
disabled={deletingKey === `submodule-${subModule.id}`} disabled={deletingKey === `submodule-${subModule.id}`}
onClick={() => onClick={() =>
requestRemove({ requestRemove({
@ -1290,7 +1308,7 @@ export function HumanLanguagePage() {
type="button" type="button"
size="sm" size="sm"
variant="outline" variant="outline"
className="h-7 px-2 text-[11px]" className="h-8 border-grayScale-200 bg-white px-2 text-[11px] hover:border-brand-200 hover:bg-brand-50/40"
onClick={() => openCreatePracticeDialog(subModule.id)} onClick={() => openCreatePracticeDialog(subModule.id)}
> >
<Plus className="h-3.5 w-3.5" /> <Plus className="h-3.5 w-3.5" />
@ -1300,7 +1318,7 @@ export function HumanLanguagePage() {
</div> </div>
</div> </div>
<div className="p-3"> <div className="p-3.5">
{panelTab === "lessons" ? ( {panelTab === "lessons" ? (
lessonRows.length === 0 ? ( lessonRows.length === 0 ? (
<div className="rounded-lg border border-dashed border-grayScale-200 bg-grayScale-50/40 px-4 py-8 text-center text-sm text-grayScale-500"> <div className="rounded-lg border border-dashed border-grayScale-200 bg-grayScale-50/40 px-4 py-8 text-center text-sm text-grayScale-500">