csr tangled client in solid-js
15

Configure Feed

Select the types of activity you want to include in your feed.

improve pulls view mobile layout

dawn (May 31, 2026, 4:55 AM +0300) f0aeead2 c594185c

+165 -20
+40 -1
src/components/code-view.tsx
··· 658 658 roundLabel: string; 659 659 storageKey: string; 660 660 history: JSX.Element; 661 + mobileDrawerOpen?: boolean; 662 + onMobileDrawerClose?: () => void; 661 663 }> = (props) => { 662 664 const files = createMemo(() => parseDiffFiles(props.patch)); 663 665 const stats = createMemo(() => totalDiffStats(files())); ··· 666 668 const [filesVisible, setFilesVisible] = createSignal(true); 667 669 const [historyVisible, setHistoryVisible] = createSignal(true); 668 670 671 + const [isDesktop, setIsDesktop] = createSignal(window.matchMedia('(min-width: 768px)').matches); 672 + onMount(() => { 673 + const media = window.matchMedia('(min-width: 768px)'); 674 + const listener = (e: MediaQueryListEvent) => setIsDesktop(e.matches); 675 + media.addEventListener('change', listener); 676 + onCleanup(() => media.removeEventListener('change', listener)); 677 + }); 678 + 679 + createEffect(() => { 680 + if (!isDesktop() && props.mobileDrawerOpen) { 681 + document.body.style.overflow = 'hidden'; 682 + } else { 683 + document.body.style.overflow = ''; 684 + } 685 + }); 686 + 687 + onCleanup(() => { 688 + document.body.style.overflow = ''; 689 + }); 690 + 669 691 const loadReviewed = (): Set<string> => { 670 692 try { 671 693 const data = localStorage.getItem(props.storageKey); ··· 757 779 758 780 return ( 759 781 <section class="untangled-pr-diff-breakout"> 782 + {/* backdrop overlay - only visible on mobile when open */} 783 + <div 784 + id="bottomSheetBackdrop" 785 + class={clsx( 786 + "fixed inset-0 bg-black/50 md:hidden opacity-0 pointer-events-none transition-opacity duration-300 z-40", 787 + props.mobileDrawerOpen && "opacity-100 pointer-events-auto" 788 + )} 789 + onClick={() => props.onMobileDrawerClose?.()} 790 + /> 760 791 <div 761 792 class={clsx( 762 793 'untangled-pr-diff-toolbar', ··· 852 883 </div> 853 884 854 885 <Show when={historyVisible()}> 855 - <aside class="untangled-pr-history">{props.history}</aside> 886 + <aside 887 + class={clsx( 888 + "untangled-pr-history", 889 + "fixed bottom-0 left-0 right-0 z-50 w-full md:static md:z-auto md:max-h-screen md:sticky md:top-12 overflow-hidden", 890 + props.mobileDrawerOpen && "open" 891 + )} 892 + > 893 + {props.history} 894 + </aside> 856 895 </Show> 857 896 </div> 858 897 </section>
+82 -4
src/index.css
··· 605 605 position: static; 606 606 } 607 607 608 - .untangled-pr-file-tree, 608 + .untangled-pr-file-tree { 609 + position: static; 610 + max-height: none; 611 + display: none; 612 + } 613 + } 614 + 615 + @media (min-width: 768px) and (max-width: 1023px) { 609 616 .untangled-pr-history { 610 617 position: static; 611 618 max-height: none; 612 619 } 620 + } 613 621 614 - .untangled-pr-file-tree { 615 - display: none; 622 + @media (max-width: 767px) { 623 + .untangled-pr-history { 624 + position: fixed; 625 + top: auto; 626 + bottom: 0; 627 + left: 0; 628 + right: 0; 629 + z-index: 50; 630 + width: 100%; 631 + max-height: none; 632 + overflow: visible; 633 + padding: 0; 634 + background: transparent; 635 + border: none; 636 + box-shadow: none; 637 + transform: translateY(calc(100% - 3rem)); 638 + transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1); 639 + } 640 + 641 + .untangled-pr-history.open { 642 + transform: none; 643 + } 644 + 645 + .untangled-pr-history-body { 646 + transform: translateY(4rem); 647 + transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1); 616 648 } 617 649 650 + .untangled-pr-history.open .untangled-pr-history-body { 651 + transform: none; 652 + } 653 + 654 + .untangled-pr-history details, 655 + .untangled-pr-history details summary { 656 + border-top-left-radius: 0.625rem !important; 657 + border-top-right-radius: 0.625rem !important; 658 + } 659 + 660 + /* Style the history card skeleton to be a collapsed header at the bottom on mobile */ 661 + .untangled-pr-history-card { 662 + border: none !important; 663 + background: transparent !important; 664 + } 665 + 666 + .untangled-pr-history-card .untangled-pr-history-submission, 667 + .untangled-pr-history-card .px-4, 668 + .untangled-pr-history-card .p-4, 669 + .untangled-pr-history-card .untangled-pr-history-comment { 670 + display: none !important; 671 + } 672 + 673 + .untangled-pr-history-card > header { 674 + background: rgb(229 231 235); 675 + height: 3rem; 676 + border-top-left-radius: 0.625rem !important; 677 + border-top-right-radius: 0.625rem !important; 678 + padding: 0 1rem; 679 + border: 1px solid rgb(209 213 219); 680 + border-bottom: none; 681 + } 682 + } 683 + 684 + @media (max-width: 767px) and (prefers-color-scheme: dark) { 685 + .untangled-pr-history-card > header { 686 + background: rgb(31 41 55); 687 + border-color: rgb(55 65 81); 688 + } 689 + } 690 + 691 + summary::-webkit-details-marker { 692 + display: none; 693 + } 694 + summary { 695 + list-style: none; 618 696 } 619 697 620 698 @media (max-width: 767px) { 621 699 .untangled-pr-summary-card { 622 - padding: 1rem; 700 + padding: 0.75rem; 623 701 } 624 702 625 703 .untangled-thread-meta {
+43 -15
src/pages/repo/pulls.tsx
··· 3 3 ArrowLeftRight, 4 4 CircleChevronUp, 5 5 CirclePlus, 6 + ChevronDown, 7 + ChevronUp, 6 8 Copy, 7 9 Diff, 8 10 Eye, ··· 1028 1030 const [workingAction, setWorkingAction] = createSignal<'comment' | 'state' | 'merge' | null>(null); 1029 1031 const working = createMemo(() => workingAction() !== null); 1030 1032 const [error, setError] = createSignal<string | null>(null); 1033 + const [mobileDrawerOpen, setMobileDrawerOpen] = createSignal(false); 1031 1034 1032 1035 const pullQuery = createQuery(() => { 1033 1036 const repo = repoQuery.data; ··· 1391 1394 patch={patchQuery.data!} 1392 1395 roundLabel={roundLabel()} 1393 1396 storageKey={`reviewed:${repoQuery.data!.owner.handle}/${repoQuery.data!.slug}/pulls/${detail().pull.number || parseAtUri(detail().pull.uri).rkey}/round/${roundIndex()}`} 1397 + mobileDrawerOpen={mobileDrawerOpen()} 1398 + onMobileDrawerClose={() => setMobileDrawerOpen(false)} 1394 1399 history={ 1395 - <div class="flex flex-col"> 1396 - {/* History Header */} 1397 - <div 1400 + <details 1401 + open={true} 1402 + class="rounded-t shadow-lg md:shadow-none group/panel w-full" 1403 + > 1404 + <summary 1398 1405 class={clsx( 1399 - 'flex items-baseline justify-between border border-b-0 rounded-t px-4 py-3 shadow-sm transition-colors duration-200', 1400 - roundIndex() === 0 1401 - ? 'bg-blue-50 dark:bg-blue-950 border-blue-200 dark:border-blue-900' 1402 - : 'bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700' 1406 + "h-12 flex gap-4 items-center justify-between", 1407 + "rounded-t cursor-pointer list-none px-4", 1408 + "text-white md:text-black md:dark:text-white", 1409 + detail().pull.state === 'open' 1410 + ? 'bg-green-600 dark:bg-green-700' 1411 + : detail().pull.state === 'merged' 1412 + ? 'bg-purple-600 dark:bg-purple-700' 1413 + : 'bg-red-600 dark:bg-red-700', 1414 + "md:bg-white md:dark:bg-gray-800", 1415 + "shadow-sm border-t md:border-x border-gray-200 dark:border-gray-700", 1416 + "md:pointer-events-none" 1403 1417 )} 1418 + onClick={(e) => { 1419 + e.preventDefault(); 1420 + setMobileDrawerOpen(!mobileDrawerOpen()); 1421 + }} 1404 1422 > 1405 - <h3 class="text-base font-semibold text-gray-900 dark:text-white">history</h3> 1406 - <span class="text-sm text-gray-500 dark:text-gray-400"> 1407 - {detail().pull.value.rounds.length} round{detail().pull.value.rounds.length === 1 ? '' : 's'} 1408 - <span class="mx-1 select-none">·</span> 1409 - {detail().comments.length} comment{detail().comments.length === 1 ? '' : 's'} 1423 + <h3 class="text-base font-semibold text-white md:text-gray-900 md:dark:text-white">history</h3> 1424 + <span class="pointer-events-auto cursor-text flex items-center gap-2 text-sm text-white md:text-gray-500 md:dark:text-gray-400"> 1425 + <span> 1426 + {detail().pull.value.rounds.length} round{detail().pull.value.rounds.length === 1 ? '' : 's'} 1427 + </span> 1428 + <span class="select-none before:content-['\00B7']"></span> 1429 + <span> 1430 + {detail().comments.length} comment{detail().comments.length === 1 ? '' : 's'} 1431 + </span> 1432 + <span class="md:hidden inline ml-2"> 1433 + <Show when={mobileDrawerOpen()} fallback={<ChevronUp class="size-4" />}> 1434 + <ChevronDown class="size-4" /> 1435 + </Show> 1436 + </span> 1410 1437 </span> 1411 - </div> 1412 - 1413 - <For each={detail().pull.value.rounds}> 1438 + </summary> 1439 + <div class="max-h-[85vh] md:max-h-[calc(100vh-3rem-3rem)] w-full flex flex-col gap-4 overflow-y-auto bg-slate-100 dark:bg-gray-900 md:bg-transparent p-0 untangled-pr-history-body"> 1440 + <For each={detail().pull.value.rounds}> 1414 1441 {(round, index) => { 1415 1442 const isSelected = () => index() === roundIndex(); 1416 1443 const isLatest = () => index() === detail().pull.value.rounds.length - 1; ··· 1631 1658 }} 1632 1659 </For> 1633 1660 </div> 1661 + </details> 1634 1662 } 1635 1663 /> 1636 1664 </Match>