Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/contracts/_ui.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"Layout Principles": "* Put reading-first content in the left/main column: introductions, descriptions, markdown, homework content, and comments.\n* Put structured facts in the right/side column: identifiers, metadata, dates, counts, actions, mini calendars, and quick links.\n* On mobile, stack in that order.\n* Reuse existing grids, cards, borders, muted text, and buttons.\n* Popups with discussion use details on the left and discussion on the right on desktop; stack on mobile.",
"Layout Principles": "* Put reading-first content in the left/main column: introductions, descriptions, markdown, homework content, and comments.\n* Put structured facts in the right/side column: identifiers, metadata, dates, counts, actions, mini calendars, and quick links.\n* On mobile, stack in that order.\n* Reuse existing grids, cards, borders, muted text, and buttons.\n* Popups are one scrollable column at a comfortable reading width, not a two-column page. Keep the title in a sticky header. Lead with one primary fact (due datetime) and one secondary line (relative urgency), then list remaining overlay facts in a table, then reading, then discussion. Do not add a status side column, chip row, or nested collapsible inside the overlay. Side rails belong on pages, not in overlays.",
"List Table": "* Used for discovery lists such as courses, sections, and teachers.\n* Primary names follow the active locale; an available alternate localized name is secondary text.\n* Structured fields support comparison.\n* Rows link to detail pages.\n* Across viewport widths, one prominent search remains directly available, advanced filters open in an accessible sheet, and active filters remain removable above results.\n* Compact layouts use cards that avoid page-level horizontal scrolling; wide desktop layouts keep every comparison-table column inside the results container without horizontal scrolling.\n* Pagination uses URL-driven Previous, page, and Next links while preserving filters.",
"Data Table Cells": "* Desktop data-table rows keep a consistent compact height; optional secondary lines reserve space when absent.\n* Overflowing text is truncated, with its full value available from a tooltip on pointer hover and keyboard focus.\n* Course and section codes use plain monospace text, never badge styling.\n* Header labels and cell content share the same column edge; textual and categorical values align left, while numeric measures and counts align right.\n* Blank placeholders are hidden from assistive technology and do not replace meaningful labels such as unknown or TBD.\n* Numeric, status, control, and action cells do not receive truncation tooltips when their content fits.",
"Detail Hero": "* Used at the top of course, section, and teacher detail pages.\n* Contains breadcrumb, h1, and an optional subtitle.\n* h1 uses the primary display name of the current object, not an internal ID.\n* At mobile widths, stable public identifiers and concise metadata precede a responsive title so long localized names do not crowd out context or actions.",
Expand Down
4 changes: 2 additions & 2 deletions docs/contracts/homework.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"filter: incomplete/completed/all",
"mobile compact toolbar with direct completion filters and overflow display-mode control",
"cards/list view mode persisted in browser storage",
"detail popup order: description, due summary, vertical metadata excluding platform createdAt, action controls, discussion; desktop places discussion to the right of the details",
"detail popup is one scrollable column: due datetime, relative urgency, remaining facts in a table excluding platform createdAt, then description, then full-width discussion; completion stays in the footer; do not use a two-column overlay",
"create form exposes the collapsible advisory homework title and description style guide"
]
}
Expand Down Expand Up @@ -247,7 +247,7 @@
"user completion status",
"edit action",
"cards/list view mode persisted in browser storage",
"detail popup order: description, due summary, vertical metadata excluding platform createdAt, edit/completion controls, inline discussion; desktop places discussion to the right of the details",
"detail popup is one scrollable column: due datetime is primary, relative urgency is secondary, remaining facts (status, tags, publication, submission opens) are a table that does not repeat due date or platform createdAt, then description, then full-width discussion; edit/completion stay in the footer; do not use a two-column overlay",
"section cards use a responsive multi-column layout",
"create and edit forms expose the same collapsible advisory homework title and description style guide"
]
Expand Down
1 change: 1 addition & 0 deletions docs/contracts/todo.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@
"todo.content (description)",
"todo.priority",
"todo.dueAt",
"detail popup stacks title, then due datetime, relative urgency, a table of remaining facts (priority, completion), then content; completion/edit/delete stay in the footer",
"completed status action button",
"filter: incomplete/completed/all",
"mobile compact toolbar with direct completion filters and overflow display-mode control"
Expand Down
1 change: 1 addition & 0 deletions messages/en-us.json
Original file line number Diff line number Diff line change
Expand Up @@ -2263,6 +2263,7 @@
"contentPlaceholder": "Supports Markdown formatting",
"contentEmpty": "No description added.",
"priorityLabel": "Priority",
"statusLabel": "Status",
"dueAtLabel": "Due date",
"dueAtPlaceholder": "Optional date and time",
"dueLabel": "Due",
Expand Down
1 change: 1 addition & 0 deletions messages/zh-cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -2229,6 +2229,7 @@
"contentPlaceholder": "支持 Markdown 格式",
"contentEmpty": "暂无内容",
"priorityLabel": "优先级",
"statusLabel": "状态",
"dueAtLabel": "截止时间",
"dueAtPlaceholder": "选择截止时间(可选)",
"dueLabel": "截止",
Expand Down
57 changes: 51 additions & 6 deletions src/features/dashboard/components/TodoDetailDialog.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,22 @@ import type {
} from "@/features/dashboard/lib/dashboard-controller-helpers";
import MarkdownPreview from "$lib/components/MarkdownPreview.svelte";
import * as AlertDialog from "$lib/components/ui/alert-dialog/index.js";
import { Badge } from "$lib/components/ui/badge/index.js";
import { Button, buttonVariants } from "$lib/components/ui/button/index.js";
import * as Dialog from "$lib/components/ui/dialog/index.js";
import { ScrollArea } from "$lib/components/ui/scroll-area/index.js";
import { Separator } from "$lib/components/ui/separator/index.js";
import { Spinner } from "$lib/components/ui/spinner/index.js";
import * as Table from "$lib/components/ui/table/index.js";
import { cn } from "$lib/utils.js";

export let deleteTodo: (todo: DashboardTodoItem) => void | Promise<void>;
export let fmtDate: (value: string | Date | null | undefined) => string;
export let isDueOverdue: (value: string | Date | null | undefined) => boolean;
export let onClose: () => void;
export let openTodoEditor: (todo: DashboardTodoItem) => void;
export let relativeDueLabel: (
value: string | Date | null | undefined,
) => string;
export let todo: DashboardTodoItem | null;
export let todoActionLabel: (todo: DashboardTodoItem) => string;
export let todoSavingById: Record<string, boolean>;
Expand Down Expand Up @@ -62,22 +66,63 @@ async function confirmDelete(event: MouseEvent) {
<Dialog.Content
class="flex h-[calc(100dvh-2rem)] max-h-[calc(100dvh-2rem)] min-h-0 max-w-lg flex-col gap-0 overflow-clip p-0 sm:h-[min(64vh,36rem)] sm:max-h-[min(64vh,36rem)] sm:max-w-lg"
>
<Dialog.Header class="shrink-0 px-5 pb-2 pt-4">
<Dialog.Header class="shrink-0 px-5 pb-2 pt-4 pr-12">
<Dialog.Title class="break-words">{todo.title}</Dialog.Title>
<Dialog.Description>
<Dialog.Description class="sr-only">
{todosCopy.priority[todo.priority]} · {fmtDate(todo.dueAt)}
</Dialog.Description>
</Dialog.Header>
<ScrollArea class="h-0 min-h-0 flex-1">
<div class="grid min-w-0 gap-4 px-5 py-4">
<div class="min-w-0" data-testid="todo-detail-summary">
<p class="text-muted-foreground text-sm">{todosCopy.dueAtLabel}</p>
<p class="mt-1 text-xl font-semibold tracking-tight">
{fmtDate(todo.dueAt)}
</p>
{#if todo.dueAt}
<p
class={cn(
"mt-1 text-sm",
isDueOverdue(todo.dueAt)
? "text-destructive font-medium"
: "text-muted-foreground",
)}
>
{relativeDueLabel(todo.dueAt)}
</p>
{/if}
<Table.Root class="mt-4">
<Table.Body>
<Table.Row>
<Table.Head
class="text-muted-foreground h-auto w-[38%] px-0 py-2"
scope="row"
>
{todosCopy.priorityLabel}
</Table.Head>
<Table.Cell class="h-auto px-0 py-2">
{todosCopy.priority[todo.priority]}
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Head
class="text-muted-foreground h-auto px-0 py-2"
scope="row"
>
{todosCopy.statusLabel}
</Table.Head>
<Table.Cell class="h-auto px-0 py-2">
{todoStatus(todo)}
</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>
</div>
{#if todo.content}
<MarkdownPreview class="min-w-0 break-words text-sm" content={todo.content} />
{:else}
<p class="text-muted-foreground text-sm">{todosCopy.contentEmpty}</p>
{/if}
<div class="flex flex-wrap gap-2">
<Badge>{todoStatus(todo)}</Badge>
</div>
</div>
</ScrollArea>
<Dialog.Footer class="mx-0 mb-0 shrink-0 p-4">
Expand Down
26 changes: 18 additions & 8 deletions src/features/dashboard/components/TodosTab.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,25 @@ export let isCreatingTodo: boolean;
export let isUpdatingTodo: boolean;
let datetimeLocalValue: TodoDateFormatter;
let fmtDate: TodoDateFormatter;
let isDueOverdue: (value: Date | string | null | undefined) => boolean;
let relativeDueLabel: TodoDateFormatter;
let todoActionLabel: TodoAction;
let todoStatus: TodoAction;

$: ({ datetimeLocalValue, fmtDate, todoActionLabel, todoStatus } =
createTodoTabDisplayActions({
dashboardCopy,
locale,
referenceDate,
sectionCopy,
todosCopy,
}));
$: ({
datetimeLocalValue,
fmtDate,
isDueOverdue,
relativeDueLabel,
todoActionLabel,
todoStatus,
} = createTodoTabDisplayActions({
dashboardCopy,
locale,
referenceDate,
sectionCopy,
todosCopy,
}));
$: displayTodoFilter = resolveDashboardTaskFilter(
todoFilter,
todoItems.some((todo) => !todo.completed),
Expand Down Expand Up @@ -120,7 +128,9 @@ $: displayTodoFilter = resolveDashboardTaskFilter(
{fmtDate}
{isCreatingTodo}
{isUpdatingTodo}
{isDueOverdue}
{openTodoEditor}
{relativeDueLabel}
bind:selectedTodo
bind:showCreateTodo
{todoActionLabel}
Expand Down
6 changes: 6 additions & 0 deletions src/features/dashboard/components/TodosTabDialogs.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,12 @@ export let editTodoError: string;
export let editingTodo: DashboardTodoItem | null;
export let fmtDate: (value: string | Date | null | undefined) => string;
export let isCreatingTodo: boolean;
export let isDueOverdue: (value: string | Date | null | undefined) => boolean;
export let isUpdatingTodo: boolean;
export let openTodoEditor: (todo: DashboardTodoItem) => void;
export let relativeDueLabel: (
value: string | Date | null | undefined,
) => string;
export let selectedTodo: DashboardTodoItem | null;
export let showCreateTodo: boolean;
export let todoActionLabel: (todo: DashboardTodoItem) => string;
Expand Down Expand Up @@ -53,10 +57,12 @@ export let updateTodoAction: SubmitFunction;
<TodoDetailDialog
deleteTodo={(todo) => deleteTodo(todo)}
{fmtDate}
{isDueOverdue}
onClose={() => {
selectedTodo = null;
}}
{openTodoEditor}
{relativeDueLabel}
todo={selectedTodo}
{todoActionLabel}
{todoSavingById}
Expand Down
1 change: 1 addition & 0 deletions src/features/dashboard/lib/dashboard-controller-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ export type DashboardTodosCopy = DashboardRecord & {
saveFailed: string;
uncompleteSuccess: string;
saving: string;
statusLabel: string;
subtitle: string;
titleLabel: string;
titlePlaceholder: string;
Expand Down
15 changes: 14 additions & 1 deletion src/features/dashboard/lib/todos-tab-display.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import type {
DashboardTodoItem,
DashboardTodosCopy,
} from "./dashboard-controller-types";
import { formatDashboardDateTime } from "./date-formatters";
import {
formatDashboardDateTime,
formatDashboardDueRelativeTime,
isDashboardDueOverdue,
} from "./date-formatters";
import {
todoActionLabel as buildTodoActionLabel,
todoStatus as buildTodoStatus,
Expand Down Expand Up @@ -44,5 +48,14 @@ export function createTodoTabDisplayActions({
completed: dashboardCopy.completedStatus,
pending: dashboardCopy.pendingStatus,
}),
relativeDueLabel: (value: Date | string | null | undefined) =>
formatDashboardDueRelativeTime(
value,
sectionCopy.dateTBD,
referenceDate,
locale,
),
isDueOverdue: (value: Date | string | null | undefined) =>
isDashboardDueOverdue(value, referenceDate),
};
}
41 changes: 13 additions & 28 deletions src/features/homeworks/components/HomeworkDeadlineSummary.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type {
HomeworkDeadlineState,
HomeworkDetailModel,
} from "@/features/homeworks/lib/homework-presentation";
import { Badge } from "$lib/components/ui/badge/index.js";
import { cn } from "$lib/utils.js";
import type {
HomeworkDetailCopy,
Expand All @@ -25,34 +24,20 @@ $: relativeLabel = relativeEtaLabel(homework.submissionDueAt, referenceDate);
$: isOverdue = deadlineState === "overdue";
</script>

<dl
aria-label={copy.submissionDue}
class="grid min-w-0 gap-4 rounded-xl bg-muted/40 p-4 sm:grid-cols-[minmax(0,1fr)_auto] sm:items-start sm:p-5"
data-testid="homework-deadline-summary"
>
<div class="min-w-0">
<dt class="text-muted-foreground text-sm">{copy.submissionDue}</dt>
<dd class="mt-1 truncate text-xl font-semibold tracking-tight sm:text-2xl">
{dueLabel}
</dd>
<dd
<div class="min-w-0" data-testid="homework-deadline-summary">
<p class="text-muted-foreground text-sm">{copy.submissionDue}</p>
<p class="mt-1 text-xl font-semibold tracking-tight sm:text-2xl">
{dueLabel}
</p>
{#if homework.submissionDueAt && relativeLabel}
<p
aria-label={copy.relativeTime}
class={cn(
"mt-2 flex items-center gap-2 text-sm",
"mt-1 text-sm",
isOverdue ? "text-destructive font-medium" : "text-muted-foreground",
)}
>
<span>{copy.relativeTime}</span>
<span aria-hidden="true">·</span>
<span>{relativeLabel}</span>
</dd>
</div>

<div class="flex min-w-0 items-center justify-between gap-3 sm:flex-col sm:items-end">
<dt class="text-muted-foreground text-sm">{copy.statusLabel}</dt>
<dd>
<Badge variant={homework.completed ? "secondary" : "outline"}>
{homework.completed ? copy.completedLabel : copy.pendingLabel}
</Badge>
</dd>
</div>
</dl>
{relativeLabel}
</p>
{/if}
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ onMount(() => {
referenceDate={liveReferenceDate}
{relativeEtaLabel}
/>
<HomeworkDetailDescription {copy} {homework} />
<HomeworkDetailSecondaryDetails {copy} {fmtDate} {homework} />
<HomeworkDetailDescription {copy} {homework} />
{/if}

<HomeworkDetailDiscussion
Expand Down
Loading
Loading