feat: SSR tab bar#1949
Draft
KevinWu098 wants to merge 46 commits into
Draft
Conversation
… removing ssr:false boundary
- useActiveTab: removed useIsMobile, just reads route segment directly
- ScheduleManagementTab: replaced useIsMobile branching with CSS responsive sx
(flexDirection, sizing, display via custom breakpoints default/sm)
- ScheduleManagement: replaced dual tab render (!isMobile/isMobile) with single
render using CSS order for mobile-bottom/desktop-top placement
- Client: replaced isMobile ternary (MobileHome/DesktopHome) with CSS responsive
display, rendering both layouts with mutual exclusion via breakpoints
- ClientShell: removed dynamic({ ssr: false }) wrapper, Client now SSRs normally
Co-Authored-By: Kevin Wu <kevinwu098@gmail.com>
ColumnStore calls getLocalStorageColumnToggles() at module evaluation time. With ssr:false removed, this now runs during SSR prerender where window is undefined. Added typeof window guard (same pattern as SettingsStore and SectionThemeStore). Co-Authored-By: Kevin Wu <kevinwu098@gmail.com>
Added a getStorage() helper that returns window.localStorage when available and undefined during SSR. All localStorage access functions now use getStorage()?.method() with ?? null fallbacks for getters. This eliminates the need for per-call-site typeof window guards and makes the entire localStorage API safe to call during SSR/prerender. Co-Authored-By: Kevin Wu <kevinwu098@gmail.com>
Leaflet accesses window at module scope, so it cannot be imported
during SSR prerender. Replace React.lazy with next/dynamic({ ssr: false })
and move the loading fallback into the dynamic loading option, removing
the Suspense wrapper.
Co-Authored-By: Kevin Wu <kevinwu098@gmail.com>
…dration mismatch
Remove useIsMobile() from 16 components that now SSR after removing the
ssr:false boundary. Replace JS conditional rendering with CSS responsive
sx patterns (display: { default: X, sm: Y }) so server and client render
identical DOM.
Components fixed:
- CalendarToolbar: dual finals button with CSS display toggle
- CalendarRoot: always use desktop date format (calendar is desktop-only)
- TbaCalendarCard: dual text spans with CSS display toggle
- NotificationSnackbar: responsive marginBottom
- SectionTable: responsive color strip width
- SectionTableBodyRowColorStrip: responsive cursor/pointerEvents
- GpaCell/GradesPopover: remove isMobile prop, use responsive dimensions
- RestrictionsCell: dual Popover/Tooltip with CSS display toggle
- EnrollmentCell: always render Tooltip with disableTouchListener
- CourseInfoBar: responsive startIcon visibility
- CourseInfoButton: responsive text visibility
- EnrollmentColumnHeader: responsive Help icon visibility
- EnrollmentHistoryPopover: responsive dimensions
- PastSyllabiPopover: responsive dimensions
- ScheduleManagement: use window.matchMedia in useEffect for redirect
Co-Authored-By: Kevin Wu <kevinwu098@gmail.com>
…React #418) - Remove Math.random() from createSkeletonEvents() — always use variation[0] as the SSR-safe default so server and client render identical skeleton events - Move localStorage blueprint read into a useEffect in CalendarRoot so it only runs after hydration (SSR gets null → deterministic default, client hydrates with same default, then effect updates to stored blueprint) - Remove unused getLocalStorageSkeletonBlueprint import from skeletonHelpers Co-Authored-By: Kevin Wu <kevinwu098@gmail.com>
Remove the blanket dynamic({ ssr: false }) on Client so ScheduleManagement
tabs render on the server. Keep tab content and calendar client-only via
targeted dynamic imports, use CSS order for mobile-bottom/desktop-top tab
placement, and replace useIsMobile with responsive sx in tab components.
Move desktop /calendar redirect to proxy.ts using user-agent detection so the route never renders before redirect. Restore scroll position comments in ScheduleManagement and drop the client matchMedia redirect.
…l comments" This reverts commit 8562c3a.
useActiveTab dropped useIsMobile for SSR hydration but that let /calendar highlight on desktop. Seed isMobile from request user-agent in layout and restore calendar -> search mapping without useMediaQuery.
…p layout Gutter and panel sizes render in SSR markup via Group/Panel/Separator, eliminating the margin hack and react-split componentDidMount CLS.
Member
Author
|
/deploy |
Remove asymmetric paddingRight that shifted the grip bar off-center. Make the Separator a flex container that stretches to full pane height, and center the grip glyph with grid place-items.
Derive Panel defaultSize strings from DEFAULT_LAYOUT so percentages are explicit. Use minSize="400px". Drop redundant getBoundingClientRect width sync in favor of onResize only. Style the Separator directly and remove the inner Box wrapper.
Render ScheduleManagementTabs once and use flex order to place it above content on desktop and below on mobile, matching the sm (800px) breakpoint.
Resolve pnpm-lock.yaml by regenerating from merged package.json after integrating main's deploy perf, next-pwa removal, and get-data script.
CSS order with nested sm breakpoint keys did not apply correctly with the theme's custom breakpoint scale. Use column-reverse below sm (800px) and column at sm+ so tabs render at the bottom on mobile and top on desktop.
Wire explicit onDoubleClick to groupRef.setLayout(DEFAULT_LAYOUT) since the library built-in only resizes a single panel. Restore paddingRight: 1px and boxSizing: border-box from the old react-split gutter styling.
Replace MobileHome/DesktopHome branching with one Group: calendar panel and separator hide below sm via GlobalStyles, schedule panel always hosts the single ScheduleManagement instance. Sync panel percentages on viewport changes (0/100 mobile, 42.5/57.5 desktop). Defer desktop split calendar with noSsr media query to avoid loading it on mobile. /calendar on desktop: show search content and tab via responsive CSS and tab index logic without router redirect. Remove UserAgentProvider and revert useIsMobile to plain useMediaQuery. Use responsive sx on tab labels.
…agement" This reverts commit b09f885.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
SSR the schedule tab bar in a small slice (~11 files). Real MUI tabs ship in the initial HTML at the correct position: bottom on mobile, top of the right pane on desktop.
ClientShellonly existed to wrapdynamic({ ssr: false }). Layout now importsClientdirectly, same asHeader.How it works
Mobile vs desktop on SSR —
layout.tsxparsesuavia NextuserAgent()and passes it throughUserAgentProvider.useIsMobile()uses that asuseMediaQuerydefaultMatches, so the server picks the right layout before hydration.Tab bar —
ScheduleManagementrenders tabs above content on desktop and below on mobile (useIsMobile()conditionals, not CSS breakpoints). Tab labels use a composed icon + textBox.Client-only boundaries —
ScheduleManagementContentandScheduleCalendarstaydynamic({ ssr: false }). Everything else in the tab shell SSRs.Desktop split — Swapped
react-splitforreact-resizable-panels. Gutter and panel sizes are real markup (Group/Panel/Separator), so nocomponentDidMountlayout jump. Separator grip is markup styled fromtheme.palette.primary.Files changed
layout.tsx—UserAgentProvider, importClientdirectlyclient.tsx— mobile/desktop home, resizable panels, calendarssr: falseScheduleManagement.tsx— tab placement, contentssr: falseScheduleManagementTab.tsx—useIsMobile()tab stylesUserAgentProvider.tsx,useIsMobile.tsx— parsed UA for SSR hintglobals.css— remove unused.gutterrulesclient-shell.tsxTest plan
Follow-ups