Adopt shared WebUI structural primitives
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
|||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Link } from "react-router";
|
import { Link } from "react-router";
|
||||||
import { FormGrid, ToolbarGroup,
|
import { FormGrid, ToolbarGroup,
|
||||||
|
ActionToolbar,
|
||||||
AdminIconButton,
|
AdminIconButton,
|
||||||
Button,
|
Button,
|
||||||
DateTimeField,
|
DateTimeField,
|
||||||
@@ -20,10 +21,14 @@ import { FormGrid, ToolbarGroup,
|
|||||||
DismissibleAlert,
|
DismissibleAlert,
|
||||||
DocumentationHelpLink,
|
DocumentationHelpLink,
|
||||||
FormField,
|
FormField,
|
||||||
|
FilterBar,
|
||||||
SegmentedControl,
|
SegmentedControl,
|
||||||
SelectionList,
|
SelectionList,
|
||||||
SelectionListItem,
|
SelectionListItem,
|
||||||
|
StatePanel,
|
||||||
StatusBadge,
|
StatusBadge,
|
||||||
|
WorkspaceFrame,
|
||||||
|
WorkspaceLayout,
|
||||||
hasScope,
|
hasScope,
|
||||||
type ApiSettings,
|
type ApiSettings,
|
||||||
type AuthInfo
|
type AuthInfo
|
||||||
@@ -120,10 +125,22 @@ export default function TasksPage({ settings, auth }: { settings: ApiSettings; a
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="tasks-page" data-help-context-id="tasks.page.inbox">
|
<WorkspaceFrame as="main" height="viewport" surface="plain" className="tasks-page" label="Task inbox" data-help-context-id="tasks.page.inbox">
|
||||||
<div className="tasks-shell">
|
<WorkspaceLayout
|
||||||
<aside className="tasks-sidebar">
|
variant="split"
|
||||||
<div className="tasks-sidebar-bar">
|
primarySize="default"
|
||||||
|
primaryScrollable={false}
|
||||||
|
contentScrollable={false}
|
||||||
|
surface="contained"
|
||||||
|
primaryClassName="tasks-sidebar"
|
||||||
|
contentClassName="tasks-workspace"
|
||||||
|
primaryLabel="i18n:govoplan-tasks.work"
|
||||||
|
contentLabel="i18n:govoplan-tasks.work_details"
|
||||||
|
interfaceId="tasks.inbox.workspace"
|
||||||
|
helpContextId="tasks.page.inbox"
|
||||||
|
helpModuleId="tasks"
|
||||||
|
primary={<>
|
||||||
|
<ActionToolbar surface="panel-header" className="tasks-sidebar-bar">
|
||||||
<span className="tasks-title"><ListChecks size={18} /><strong>i18n:govoplan-tasks.work</strong></span>
|
<span className="tasks-title"><ListChecks size={18} /><strong>i18n:govoplan-tasks.work</strong></span>
|
||||||
<ToolbarGroup align="end" className="tasks-toolbar-actions">
|
<ToolbarGroup align="end" className="tasks-toolbar-actions">
|
||||||
<AdminIconButton
|
<AdminIconButton
|
||||||
@@ -142,8 +159,8 @@ export default function TasksPage({ settings, auth }: { settings: ApiSettings; a
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</ToolbarGroup>
|
</ToolbarGroup>
|
||||||
</div>
|
</ActionToolbar>
|
||||||
<form className="tasks-search" onSubmit={submitSearch} role="search">
|
<FilterBar as="form" surface="control" wrap="never" className="tasks-search" onSubmit={submitSearch} role="search">
|
||||||
<Search size={15} aria-hidden="true" />
|
<Search size={15} aria-hidden="true" />
|
||||||
<input
|
<input
|
||||||
value={searchDraft}
|
value={searchDraft}
|
||||||
@@ -151,7 +168,7 @@ export default function TasksPage({ settings, auth }: { settings: ApiSettings; a
|
|||||||
placeholder="i18n:govoplan-tasks.search_placeholder"
|
placeholder="i18n:govoplan-tasks.search_placeholder"
|
||||||
aria-label="i18n:govoplan-tasks.search"
|
aria-label="i18n:govoplan-tasks.search"
|
||||||
/>
|
/>
|
||||||
</form>
|
</FilterBar>
|
||||||
<SegmentedControl
|
<SegmentedControl
|
||||||
className="tasks-status-filter"
|
className="tasks-status-filter"
|
||||||
options={[
|
options={[
|
||||||
@@ -168,7 +185,7 @@ export default function TasksPage({ settings, auth }: { settings: ApiSettings; a
|
|||||||
{loading ? <p className="tasks-note">i18n:govoplan-tasks.loading</p> : null}
|
{loading ? <p className="tasks-note">i18n:govoplan-tasks.loading</p> : null}
|
||||||
{!loading && items.length === 0 ? <p className="tasks-note">i18n:govoplan-tasks.empty</p> : null}
|
{!loading && items.length === 0 ? <p className="tasks-note">i18n:govoplan-tasks.empty</p> : null}
|
||||||
{items.length ? (
|
{items.length ? (
|
||||||
<SelectionList label="i18n:govoplan-tasks.work_items">
|
<SelectionList variant="navigation" label="i18n:govoplan-tasks.work_items">
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
<SelectionListItem
|
<SelectionListItem
|
||||||
key={`${item.provider_id}:${item.id}`}
|
key={`${item.provider_id}:${item.id}`}
|
||||||
@@ -183,25 +200,24 @@ export default function TasksPage({ settings, auth }: { settings: ApiSettings; a
|
|||||||
</SelectionList>
|
</SelectionList>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</>}
|
||||||
|
>
|
||||||
|
|
||||||
<section className="tasks-workspace" data-help-context-id="tasks.page.detail">
|
<ActionToolbar surface="panel-header" className="tasks-topbar" data-help-context-id="tasks.page.detail">
|
||||||
<div className="tasks-topbar">
|
|
||||||
<span className="tasks-detail-title"><ListChecks size={18} /><strong>{selected?.title ?? "i18n:govoplan-tasks.work_details"}</strong></span>
|
<span className="tasks-detail-title"><ListChecks size={18} /><strong>{selected?.title ?? "i18n:govoplan-tasks.work_details"}</strong></span>
|
||||||
<ToolbarGroup align="end" className="tasks-toolbar-actions">
|
<ToolbarGroup align="end" className="tasks-toolbar-actions">
|
||||||
<DocumentationHelpLink reference={DOCUMENTATION} />
|
<DocumentationHelpLink reference={DOCUMENTATION} />
|
||||||
{selected?.provider_id === "tasks.explicit" && canWrite ? <TaskActions item={selected} busy={busy} onAction={(action) => action === "defer" ? setDeferOpen(true) : void runAction(action)} /> : null}
|
{selected?.provider_id === "tasks.explicit" && canWrite ? <TaskActions item={selected} busy={busy} onAction={(action) => action === "defer" ? setDeferOpen(true) : void runAction(action)} /> : null}
|
||||||
</ToolbarGroup>
|
</ToolbarGroup>
|
||||||
</div>
|
</ActionToolbar>
|
||||||
|
|
||||||
{error ? <DismissibleAlert tone="danger" compact resetKey={error}>{error}</DismissibleAlert> : null}
|
{error ? <DismissibleAlert tone="danger" compact resetKey={error}>{error}</DismissibleAlert> : null}
|
||||||
{diagnostics.map((message) => <DismissibleAlert key={message} tone="warning" compact resetKey={message}>{message}</DismissibleAlert>)}
|
{diagnostics.map((message) => <DismissibleAlert key={message} tone="warning" compact resetKey={message}>{message}</DismissibleAlert>)}
|
||||||
|
|
||||||
{selected ? <TaskDetails item={selected} /> : (
|
{selected ? <TaskDetails item={selected} /> : (
|
||||||
<div className="tasks-empty-detail"><ListChecks size={24} /><h1>i18n:govoplan-tasks.work</h1><p>i18n:govoplan-tasks.select_help</p></div>
|
<StatePanel size="fill" icon={<ListChecks size={24} />} title="i18n:govoplan-tasks.work" description="i18n:govoplan-tasks.select_help" />
|
||||||
)}
|
)}
|
||||||
</section>
|
</WorkspaceLayout>
|
||||||
</div>
|
|
||||||
|
|
||||||
<CreateTaskDialog
|
<CreateTaskDialog
|
||||||
open={createOpen}
|
open={createOpen}
|
||||||
@@ -230,7 +246,7 @@ export default function TasksPage({ settings, auth }: { settings: ApiSettings; a
|
|||||||
<DateTimeField value={deferredUntil} onChange={setDeferredUntil} min={localDateTime(new Date())} />
|
<DateTimeField value={deferredUntil} onChange={setDeferredUntil} min={localDateTime(new Date())} />
|
||||||
</FormField>
|
</FormField>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</main>
|
</WorkspaceFrame>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,3 @@
|
|||||||
.tasks-page {
|
|
||||||
box-sizing: border-box;
|
|
||||||
height: calc(100vh - 115px);
|
|
||||||
min-height: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
color: var(--text);
|
|
||||||
background: var(--bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tasks-page *,
|
|
||||||
.tasks-page *::before,
|
|
||||||
.tasks-page *::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tasks-shell {
|
|
||||||
height: 100%;
|
|
||||||
min-height: 0;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: minmax(285px, 350px) minmax(0, 1fr);
|
|
||||||
border: var(--border-line);
|
|
||||||
background: var(--panel);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tasks-sidebar,
|
.tasks-sidebar,
|
||||||
.tasks-workspace {
|
.tasks-workspace {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
@@ -32,13 +7,6 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tasks-sidebar {
|
|
||||||
border-right: var(--border-line);
|
|
||||||
background: var(--panel-soft);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tasks-sidebar-bar,
|
|
||||||
.tasks-topbar,
|
|
||||||
.tasks-title,
|
.tasks-title,
|
||||||
.tasks-detail-title,
|
.tasks-detail-title,
|
||||||
.tasks-toolbar-actions,
|
.tasks-toolbar-actions,
|
||||||
@@ -51,15 +19,6 @@
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tasks-sidebar-bar,
|
|
||||||
.tasks-topbar {
|
|
||||||
min-height: 54px;
|
|
||||||
justify-content: space-between;
|
|
||||||
border-bottom: var(--border-line);
|
|
||||||
background: var(--panel-header);
|
|
||||||
padding: 9px 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tasks-detail-title {
|
.tasks-detail-title {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
@@ -82,19 +41,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tasks-search {
|
.tasks-search {
|
||||||
height: 36px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 7px;
|
|
||||||
border: var(--border-line);
|
|
||||||
border-radius: 5px;
|
|
||||||
background: var(--surface);
|
|
||||||
margin: 8px 8px 0;
|
margin: 8px 8px 0;
|
||||||
padding: 0 9px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tasks-search:focus-within {
|
|
||||||
border-color: var(--accent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tasks-search input {
|
.tasks-search input {
|
||||||
@@ -232,22 +179,6 @@
|
|||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tasks-empty-detail {
|
|
||||||
min-height: 100%;
|
|
||||||
display: grid;
|
|
||||||
place-content: center;
|
|
||||||
justify-items: center;
|
|
||||||
color: var(--muted);
|
|
||||||
text-align: center;
|
|
||||||
padding: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tasks-empty-detail h1 {
|
|
||||||
margin: 10px 0 0;
|
|
||||||
color: var(--text-strong);
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tasks-create-form {
|
.tasks-create-form {
|
||||||
width: min(620px, 75vw);
|
width: min(620px, 75vw);
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -275,10 +206,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 820px) {
|
@media (max-width: 820px) {
|
||||||
.tasks-shell {
|
|
||||||
grid-template-columns: minmax(230px, 42%) minmax(0, 1fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tasks-topbar {
|
.tasks-topbar {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
@@ -289,16 +216,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 620px) {
|
@media (max-width: 620px) {
|
||||||
.tasks-shell {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
grid-template-rows: minmax(250px, 44%) minmax(0, 1fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tasks-sidebar {
|
|
||||||
border-right: 0;
|
|
||||||
border-bottom: var(--border-line);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tasks-create-form {
|
.tasks-create-form {
|
||||||
width: min(100%, 88vw);
|
width: min(100%, 88vw);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user