diff --git a/apps/desktop/src/renderer/desktop/issue-detail.tsx b/apps/desktop/src/renderer/desktop/issue-detail.tsx index e12acde4a2..15ff16352b 100644 --- a/apps/desktop/src/renderer/desktop/issue-detail.tsx +++ b/apps/desktop/src/renderer/desktop/issue-detail.tsx @@ -6,8 +6,10 @@ import { BREADCRUMBS_VARIANT, Breadcrumbs, ConfirmDialog, + DetailPage, EllipsisIcon, IconButton, + PAGE_FRAME, SKELETON_SHAPE, Skeleton, SkeletonRegion, @@ -241,16 +243,21 @@ export function IssueDetail({ ); const identifier = issueIdentifier(workspaceName, issue.number); return ( -
- - {/* The issue is drawn as a task's document is (`version-view.tsx`): its title, then its body, at the document's measure. */} -
-
+ <> + + } + > + {/* The issue is drawn as a task's document is (`version-view.tsx`): its title, then its body, at the document's measure. */} +
@@ -267,9 +274,9 @@ export function IssueDetail({ />
-
+ -
+ ); } @@ -282,31 +289,29 @@ export function IssueDetail({ */ export function IssueDetailSkeleton({ label }: { label: string }): React.JSX.Element { return ( -
-
- -
-
-
- -
-
-
- -
-
- {SKELETON_CHIPS.map((width) => ( - - ))} -
-
- {SKELETON_DESCRIPTION.map((width) => ( - - ))} +
+ +
+
+
+
- -
+
+ +
+
+ {SKELETON_CHIPS.map((width) => ( + + ))} +
+
+ {SKELETON_DESCRIPTION.map((width) => ( + + ))} +
+
+
); } diff --git a/apps/desktop/src/renderer/desktop/issues-page.tsx b/apps/desktop/src/renderer/desktop/issues-page.tsx index c0d4bfd068..64113b3585 100644 --- a/apps/desktop/src/renderer/desktop/issues-page.tsx +++ b/apps/desktop/src/renderer/desktop/issues-page.tsx @@ -12,6 +12,7 @@ import { COLLECTION_STATE, CollectionPage, type CollectionState, + DetailPage, EmptyState, ERROR_STATE_PLACEMENT, ErrorState, @@ -323,23 +324,26 @@ function DetailFrame({ }): React.JSX.Element { useAppCommand(APP_COMMAND.EXIT_ISSUES, onBack); return ( -
-
-
- - -
-
+ +
+ + +
+ + } + > {failure === undefined ? ( ) : ( -
- - {failure === ISSUE_CALL_FAILURE.NOT_FOUND ? GONE_LINE : UNREAD_ONE_LINE} - -
+ + {failure === ISSUE_CALL_FAILURE.NOT_FOUND ? GONE_LINE : UNREAD_ONE_LINE} + )} -
+ ); } @@ -348,7 +352,7 @@ function DetailFrame({ * holds it until then, and every change sent through the list, so the list * it goes back to already holds what was kept. */ -function DetailPage({ +function IssuePage({ tasks, issueId, issues, @@ -473,7 +477,7 @@ function IssuesPages({ tasks }: { tasks: TasksControl }): React.JSX.Element { /> ) : ( // Keyed by the issue, so another issue's page holds nothing of the last's. - .luke-detail-page-content > .luke-empty-state:only-child) { + display: flex; + flex-direction: column; + } + + .luke-detail-page-content:has(> .luke-empty-state:only-child) { + display: flex; + flex: 1 0 auto; + flex-direction: column; + } + /* Content across the room scrolls sideways in its own column. */ .luke-detail-page[data-width="full"] .luke-detail-page-main { overflow-x: auto; @@ -190,6 +205,19 @@ padding-inline: var(--luke-detail-page-side); } + /* A lone empty or failed state fills the one scroll, as it fills the + main column above. */ + .luke-detail-page-body:has( + > .luke-detail-page-main > .luke-detail-page-content > .luke-empty-state:only-child + ) { + display: flex; + flex-direction: column; + } + + .luke-detail-page-main:has(> .luke-detail-page-content > .luke-empty-state:only-child) { + flex: 1 0 auto; + } + /* The secondary column follows the main one's end gap, no more. */ .luke-detail-page[data-frame="toolbar"] .luke-detail-page-secondary diff --git a/packages/ui/src/components/detail-page/detail-page.tsx b/packages/ui/src/components/detail-page/detail-page.tsx index 4b4e44f635..4d31fd274f 100644 --- a/packages/ui/src/components/detail-page/detail-page.tsx +++ b/packages/ui/src/components/detail-page/detail-page.tsx @@ -12,7 +12,10 @@ import { PAGE_FRAME, PAGE_WIDTH, type PageHeading } from "../page-frame/page-fra * edge and the header's acts end on the secondary column's. Each scroll * reaches the page's edge on its own side, so the wheel works anywhere * over the column it moves. In a pane too narrow for two columns, the - * secondary column follows the main content in the one scroll. + * secondary column follows the main content in the one scroll. A main + * content that is only an `EmptyState` (or a centred `ErrorState`, where the + * thing could not be read) stands it a little above the room's middle, as a + * collection's page stands its empty and failed states. * * Under the window's toolbar (`PAGE_FRAME.TOOLBAR`) the page draws no * header: the toolbar stands still above the columns, and the page is named