Feature task 3634 tdei theme - #54
Conversation
Introduce TDEI branding: add tdei-logo.png, import Lato/Open Sans/Montserrat fonts, update theme primary color to #32006e, and add extensive utility CSS (color variables, button/link styles, layout helpers, accessibility focus styles) in main.scss. Replace many Bootstrap button/variant uses across components/pages with new tdei-* classes (tdei-primary-button, tdei-secondary-button, maroon-bg, tdei-primary-link) and adjust dialog button classes/confirm styles to match the new design system. These changes unify styling and apply the updated TDEI visual identity across the app.
Add new asset (osw-type.svg) and update global styles and pages to improve layout and mobile UX. Main changes: - components/AppNavbar.vue: Rework navbar into a responsive, sticky header with a mobile side menu and updated user dropdown. Adds state and lifecycle hooks to manage mobile menu visibility, accessibility attributes, and dedicated mobile interactions (close button, backdrop, logout flow). Extensive SCSS added to style the navbar, side menu, and responsive behaviors. - pages/index.vue: Make the primary CTA conditional on authentication (redirect to dashboard when signed in), import auth in the script, and update hero styling to use CSS variables and refined colors/hover states. - assets/scss/main.scss: Update font imports (remove Lato, ensure Open Sans and Montserrat are imported). - assets/img/osw-type.svg: Add new SVG asset. These changes are intended to improve navigation on small screens, unify header styling, and make the landing CTA reflect user authentication state.
Revamp dashboard workspace list and toolbar styling: remove duplicate Montserrat font import; rename toolbar button classes to tdei-toolbar-primary-button and add scoped styles for toolbar buttons and grouped button radii. Overhaul WorkspaceItem into a card-style layout (new markup, styles, responsive adjustments), show workspace ID, format type/status labels, and simplify class logic. Update dashboard page to add a page header subtitle, adjust Project Group label styling, wrap workspace list in a template, and add getLastProjectGroupName + selectedProjectGroupName computed to derive the displayed project group name from sessionStorage or project groups.
Add flex and pathways SVG icons and update WorkspaceItem.vue to select the correct icon based on workspace.type. Imports osw, flex, and pathways assets, introduces a computed typeIconSrc that maps types to their icons (falls back to osw), and replaces the hardcoded img src with a bound :src. Enables showing the appropriate icon for different workspace types.
Refactor the homepage template and SCSS to introduce a new hero layout and visual update. Replaced .jumbo-content with .hero-shell and .hero-copy, moved CTAs into a .hero-actions container (primary Nuxt link + external "Learn more"), and kept the auth-based dashboard/signin routing. Reworked background to use layered gradients over the existing bg.webp, added a decorative overlay, improved typography, spacing, and app-logo styling, and refined responsive behavior across breakpoints (lg, md, sm). These changes modernize the landing hero, improve readability and responsiveness, and polish CTA styles and spacing.
SCSS: Remove unnecessary !important from primary button rules, add proper :focus styles (box-shadow) for accessibility, and introduce .tdei-outline-cyan-button (replacing .tdei-button2) with hover/focus/disabled states. Unify radio focus styling and update shadow color to match brand. Minor CSS cleanup and spacing adjustments. Components: remove d-block from the Apply button in FilterDropdown. Pages: simplify selectedProjectGroupName logic in dashboard.vue (drop getLastProjectGroupName fallback) and reduce hero-primary-action min-height in index.vue.
Add an accessible alt attribute for workspace type images and adjust SCSS variables and styles. In WorkspaceItem.vue the img alt is now dynamic (uses formatTypeLabel) to improve accessibility. In assets/scss/main.scss: remove duplicate --primary-color-dark, add --tdei-cyan, change .jsonContent border to use --secondary-color, remove custom radio focus styles, and adjust .column-style flex/margins. Also restore/move text-transform: capitalize for .workspaceStatus to the stronger selector to keep visual consistency.
Add focus management and keyboard support for the mobile side menu: add refs for the toggle button and side menu, focus the close button when opened and restore focus to the toggle when closed, trap Tab/Shift+Tab inside the menu, handle Escape to close, and add aria-modal="true". Attach/detach keydown listener on mount/unmount. Minor style updates: introduce --primary-color-dark and small SCSS cleanup. Remove an unused getLastProjectGroupName() helper from dashboard.vue. Add rel="noopener noreferrer" to an external link for security.
SCSS: Update .tdei-rounded-button to .btn.tdei-rounded-button, remove unnecessary !important rules, nest hover/active state and use --primary-color-dark for hover/border. Also fix stray brace and tighten selector scope. AppNavbar.vue: Use focusable.at(0) and .at(-1) for first/last element and add a null check to avoid runtime errors when focusable is empty. pages/dashboard.vue: Render the "in <project group>" phrase only when a project group name exists and change the computed fallback from a literal 'project group' to null to avoid showing a placeholder when none is selected.
Overhaul the sign-in experience and adjust several layout components. Add a new background image asset and replace the signin page to render the new SigninForm component with a centered card, background, and responsive styles. Revamp SigninForm: new header, show/hide password toggle, improved loading/error states, and extensive SCSS for desktop/mobile. Tidy up AppNavbar templates and mobile menu markup (focus handling/focus trap fixes and markup formatting). Minor tweaks: AppSpinner markup, default layout nuxt-loading-indicator props, and refactor dashboard into a split layout with scrollable workspace list and sticky details panel.
Bind aria-modal to return 'true' or 'false' based on mobileMenuOpen/isMobileView to fix accessibility attribute type. Strengthen SigninForm error handling by adding a fetch-error type guard, parsing 400 responses for validation errors, and providing specific messages for 401 and 404 cases; also normalize other errors to strings. Add missing 'btn' class to the Close button in JoinDialog for consistent styling.
Add role="dialog" to the side menu element in AppNavbar.vue to improve accessibility and complement existing ARIA attributes. Remove an explicit color rule from .team-members-btn in teams/Item.vue to stop overriding the global body color and keep link styling consistent.
Remove external @import URL lines from assets/scss/main.scss and add the @nuxtjs/google-fonts module and configuration in nuxt.config.ts (register Open Sans and Montserrat, set display: 'swap', download: true). Also add @nuxtjs/google-fonts to devDependencies in package.json. Centralizes font loading, enables local font download/optimization, and avoids inline external CSS imports.
Add a long CSS transition to neutralize Chrome's autofill background, preventing the yellow/blue autofill styling from showing. Replace the AppNavbar side menu <aside> with a <div> to match the component structure. Scope SigninForm styles (add scoped) and remove the preset input background so autofill styling is handled consistently. Add an explicit Workspace type import and annotate selectWorkspace(workspace) with the Workspace type.
|
Warning Review limit reached
More reviews will be available in 45 minutes and 56 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis PR delivers a comprehensive design system refresh, converting the application from Bootstrap variants to custom TDEI-branded styling, updating the primary theme color from purple ( ChangesDesign System & UI Styling Refresh
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes The PR contains moderate complexity with changes spread across the design foundation (SCSS variables, utilities), multiple component refactors (navbar, forms, dashboard), and widespread button/modal styling updates applied consistently across 20+ files. While the button styling changes are repetitive and follow a single pattern, the navbar redesign introduces new responsive logic, authentication flows have stricter type guards, and layout restructures (dashboard, landing page, sign-in) require careful validation of responsive behavior and component integration. Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@assets/scss/main.scss`:
- Around line 9-10: The two CSS `@import` directives for
"maplibre-gl/dist/maplibre-gl.css" and "vue3-toastify/dist/index.css" are placed
after the input:-webkit-autofill rule, causing the
no-invalid-position-at-import-rule lint error; fix this by moving both `@import`
lines to the very top of assets/scss/main.scss so they appear before any style
rules (specifically before the input:-webkit-autofill selector and its block),
preserving their exact strings and order.
In `@components/AppNavbar.vue`:
- Around line 318-324: The CSS currently removes the keyboard focus indicator
for the mobile nav toggler (box-shadow: none) and only defines hover styles for
drawer controls, which hides focus for keyboard users; restore a visible focus
indicator by replacing the "box-shadow: none" rule on the toggler with a focused
style (use :focus and :focus-visible) that applies a visible outline or
box-shadow (e.g. using a --focus-ring-color and matching border-radius) and add
equivalent :focus/:focus-visible rules for the drawer controls so they show the
same focus ring as hover (but distinct for keyboard focus), ensuring you target
the same selectors used for the toggler and drawer controls in AppNavbar.vue so
keyboard users can track focus.
- Around line 65-74: The dropdown toggle currently nests a native <button>
inside the b-dropdown `#button-content` which breaks toggle semantics; replace the
inner interactive element (class "userProfile" button) with a non-interactive
wrapper (e.g., div or span) and move any click/keyboard handling to the
dropdown/toggle itself so the dropdown controls activation; ensure the wrapper
keeps the same classes/visual markup (class "userProfile") and accessible
labeling (use aria attributes on the toggle if needed) to preserve appearance
and accessibility.
In `@components/dashboard/WorkspaceItem.vue`:
- Around line 15-20: The role chip currently only handles 'lead' and 'validator'
and leaves 'contributor' or undefined blank; update the template in
WorkspaceItem.vue to add a v-else fallback that renders a span with class
"workspaceStatus" showing ROLE_LABELS.member (treat undefined the same as
'contributor'). Ensure you reference the existing workspace.role checks and
ROLE_LABELS constant so the new fallback collapses contributor/undefined into
the Member label.
In `@components/SigninForm.vue`:
- Around line 23-27: In SigninForm.vue, add autocomplete attributes to the
credential inputs so browsers/password managers work correctly: set
autocomplete="username" on the input using v-model="username" (id="username")
and set autocomplete="current-password" on the input using v-model="password"
(the password field around lines 43-47); ensure attributes are added to the same
element nodes that currently have class "form-control signin-input" and aria
attributes.
- Around line 107-113: The 400 error branch currently calls await
e.response.json() which can throw if the response body isn't valid JSON; wrap
the JSON parse in a safe guard (e.g., inspect
e.response.headers.get('content-type') for application/json and/or use a
try/catch around await e.response.json()) and fall back to a generic message
when parsing fails so error.value still gets set; update the logic around the
existing body variable and the error.value assignment in the SigninForm.vue
catch block to use the parsed body only when parse succeeds and otherwise set a
sensible fallback error.
In `@pages/signin.vue`:
- Around line 12-14: The redirect currently calls navigateTo('/dashboard')
without awaiting its promise; update the branch that checks tdeiAuth.complete to
await (or return) the navigateTo call so Nuxt handles the navigation
immediately—e.g., inside the script setup where tdeiAuth.complete is checked,
replace navigateTo('/dashboard') with await navigateTo('/dashboard') (or return
await navigateTo('/dashboard')) so the promise is not ignored.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 784db07f-7b18-4614-9663-5037757aebfd
⛔ Files ignored due to path filters (5)
assets/img/bg_login.pngis excluded by!**/*.pngassets/img/flex-type.svgis excluded by!**/*.svgassets/img/osw-type.svgis excluded by!**/*.svgassets/img/pathways-type.svgis excluded by!**/*.svgassets/img/tdei-logo.pngis excluded by!**/*.png
📒 Files selected for processing (35)
assets/scss/main.scssassets/scss/theme.scsscomponents/AppLogo.vuecomponents/AppNavbar.vuecomponents/AppSpinner.vuecomponents/DatasetTypeRadio.vuecomponents/SigninForm.vuecomponents/dashboard/Toolbar.vuecomponents/dashboard/WorkspaceItem.vuecomponents/review/FilterDropdown.vuecomponents/review/Toolbar.vuecomponents/settings/panel/Apps.vuecomponents/settings/panel/Delete.vuecomponents/settings/panel/General.vuecomponents/settings/panel/Imagery.vuecomponents/settings/teams/JoinDialog.vuecomponents/teams/Item.vuecomponents/teams/JoinDialog.vuecomponents/teams/MembersDialog.vuecomponents/teams/SettingsDialog.vuelayouts/default.vuenuxt.config.tspackage.jsonpages/dashboard.vuepages/help.vuepages/index.vuepages/signin.vuepages/workspace/[id]/export/index.vuepages/workspace/[id]/export/tdei.vuepages/workspace/[id]/settings/teams/index.vuepages/workspace/[id]/teams/[teamId].vuepages/workspace/create/blank.vuepages/workspace/create/file.vuepages/workspace/create/index.vuepages/workspace/create/tdei.vue
There was a problem hiding this comment.
@shweta2101 Initial observation: if we change the logo here, we'll need to update it in the AVIV ScoutRoute apps as well. Those both display the old Workspaces logo.
Looks nice!
There was a problem hiding this comment.
Thanks for the updates. Visually, the changes look pretty good, and close enough to the TDEI portal. I really appreciate the fix for the mobile navbar.
My main concern is the lack of a coherent design system for these changes. Most of the styles were implemented as brute-force classes rather than extending the existing Bootstrap system, and there are visual patterns that the TDEI portal has that aren't encapsulated for use in other places within the application. For example, the list-card style found on the dashboard is a distinctive visual pattern throughout TDEI, but the styles/components are not set up to reproduce that look and functionality elsewhere.
Introduce a central SCSS theme and migrate components to Bootstrap variants and unified classes. - Add assets/scss/theme.scss with SASS theme variables (brand colors, utility colors, text/nav colors). - Update assets/scss/main.scss to consume the new theme variables, remove many legacy utility classes, and switch several CSS rules to variables/SASS values. - Replace custom tdei button/link classes across components with Bootstrap button variants (btn-primary, btn-outline-secondary, btn-danger, btn-light, etc.). - Refactor AppNavbar to use BootstrapVue collapse/toggle components, improve mobile side-drawer behavior and keyboard focus handling, and normalize class names to kebab-case. - Normalize CSS class names and markup in WorkspaceItem and other components, add theme imports and scoped styles where appropriate, and make small UI/accessibility improvements (icons, alt text, font sizes). - Adjust nuxt.config to limit downloaded font weights and include additional BootstrapVue components used by the navbar. These changes consolidate styling around a single theme, reduce bespoke utility CSS, and align components with Bootstrap conventions for more consistent UI and easier maintenance.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
components/AppNavbar.vue (1)
120-126: ⚡ Quick winAlign the JS mobile breakpoint with Bootstrap's
md.
isMobileViewflips atinnerWidth <= 768, but the drawer CSS usesmedia-breakpoint-down(md)(max-width: 767.98px) andd-md-nonehides at≥ 768px. At exactly 768px the desktop layout renders whileisMobileViewis stilltrue, so thev-if="mobileMenuOpen && isMobileView"backdrop (Line 93) and an open drawer state can leak onto the desktop view. Use< 768(or read the shared Bootstrapmdvalue) to keep JS and CSS consistent.♻️ Proposed change
- isMobileView.value = window.innerWidth <= 768 + isMobileView.value = window.innerWidth < 768🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@components/AppNavbar.vue` around lines 120 - 126, The mobile breakpoint logic in syncMobileView() currently sets isMobileView when window.innerWidth <= 768 which mismatches Bootstrap's md breakpoint (max-width: 767.98px) and causes desktop layout bleed at exactly 768px; update syncMobileView() to use window.innerWidth < 768 (or otherwise read the shared Bootstrap md value) and ensure when switching to desktop you still set mobileMenuOpen.value = false so isMobileView and the CSS (d-md-none / media-breakpoint-down(md)) remain consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@components/AppNavbar.vue`:
- Around line 18-35: The mobile drawer currently only sets :inert on the
<b-collapse> when closed; implement a proper focus containment by adding an
overlay and toggling inert/aria-hidden/tabindex on the main app content whenever
mobileMenuOpen is true, and add a simple focus trap inside the drawer so
keyboard focus cycles within the <b-collapse> while open; use mobileMenuOpen and
isMobileView to drive this, handle focus management in open/close handlers
(e.g., capture the trigger element when opening, move focus to the first
focusable element inside <b-collapse>, and restore focus to the saved trigger
when side-menu-close button or mobileMenuOpen becomes false). Ensure the
side-menu-close button (and any programmatic close) restores focus and removes
inert/aria-hidden/tabindex on the background.
---
Nitpick comments:
In `@components/AppNavbar.vue`:
- Around line 120-126: The mobile breakpoint logic in syncMobileView() currently
sets isMobileView when window.innerWidth <= 768 which mismatches Bootstrap's md
breakpoint (max-width: 767.98px) and causes desktop layout bleed at exactly
768px; update syncMobileView() to use window.innerWidth < 768 (or otherwise read
the shared Bootstrap md value) and ensure when switching to desktop you still
set mobileMenuOpen.value = false so isMobileView and the CSS (d-md-none /
media-breakpoint-down(md)) remain consistent.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 6f651ac4-e83d-4ff5-ac9a-73fe67f20f56
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (23)
assets/scss/main.scssassets/scss/theme.scsscomponents/AppLogo.vuecomponents/AppNavbar.vuecomponents/DatasetTypeRadio.vuecomponents/SigninForm.vuecomponents/dashboard/Toolbar.vuecomponents/dashboard/WorkspaceItem.vuecomponents/review/FilterDropdown.vuecomponents/review/Toolbar.vuecomponents/settings/panel/Delete.vuecomponents/settings/teams/JoinDialog.vuecomponents/teams/Item.vuecomponents/teams/JoinDialog.vuecomponents/teams/MembersDialog.vuecomponents/teams/SettingsDialog.vuenuxt.config.tspages/dashboard.vuepages/help.vuepages/index.vuepages/signin.vuepages/workspace/[id]/export/index.vuepages/workspace/[id]/settings/teams/index.vue
✅ Files skipped from review due to trivial changes (8)
- components/DatasetTypeRadio.vue
- components/settings/panel/Delete.vue
- components/AppLogo.vue
- components/teams/Item.vue
- components/teams/SettingsDialog.vue
- components/teams/MembersDialog.vue
- pages/workspace/[id]/export/index.vue
- components/review/Toolbar.vue
🚧 Files skipped from review as they are similar to previous changes (4)
- components/dashboard/WorkspaceItem.vue
- components/SigninForm.vue
- pages/index.vue
- pages/signin.vue
Treat 768px as non-mobile by changing the mobile detection from <= 768 to < 768. This aligns the JS breakpoint with expected CSS/UX breakpoints so devices at exactly 768px are handled as desktop and will close the mobile menu.
|
Hi @cyrossignol , Thanks for the feedback. In the initial pass, my focus was on quickly matching the visual layout of the TDEI portal, which led to using custom override classes. I've refactored it: cleaned up the custom classes, set up a central theme.scss extending Bootstrap's variables directly, and scoped the list-card styles inside WorkspaceItem.vue so it is modular and reusable. I also agree that a more comprehensive design system approach would be the ideal long term direction for the application. For this task, my primary goal was to bring the existing React UI experience into the Vue application while keeping the implementation aligned with the current architecture and timeline. As part of addressing the feedback, I've tried to move the implementation closer to reusable patterns without significantly expanding the scope beyond the UI migration itself. Should be much cleaner now. Let me know if that looks better! |
DevBoard Task
https://dev.azure.com/TDEI-UW/TDEI/_workitems/edit/3634/
This PR applies TDEI brand theming across the application — updating colours, typography, layout, and accessibility throughout.
Visual changes
#32006e), new responsive mobile side-menu with slide-in animation, user profile dropdown, and sticky positioningtdei-primary-button,tdei-secondary-button,tdei-primary-link,tdei-rounded-button)Screenshots:
Theme & Styling Updates
#9b0092to TDEI brand colour#32006e$brand-accent,$tdei-secondary,$tdei-blue,$tdei-green,$tdei-cyan, background shades, and$text-navymain.scsswith webkit autofill transitions, new CSS variable declarations in:root, and dropdown menu active state styling@nuxtjs/google-fonts) with Open Sans and Montserrat font familiesNavigation & Navbar
AppNavbar.vuefrom Bootstrap collapsed layout to custom responsive design with sticky positioningb-collapsewith click-to-dismiss backdropisMobileViewfor viewport ≤768px) with automatic drawer closure on resizeAuthentication & Sign-in Page
SigninForm.vuewith TDEI-branded auth card layout and updated stylingshowPasswordstateresponse.statusandresponse.json()functionpages/signin.vue) to simplified markup with centered flex layout and login background imageDashboard Layout & Components
WorkspaceItem.vueworkspace picker card with type-specific icons, formatted type labels, conditional status/role pills, and responsive stylingselectedProjectGroupNamecomputed value for project group displayselectWorkspacefunction parameter asWorkspacedashboard/Toolbar.vuebutton styling: "Edit" uses primary variant, "Review/Export/Settings" use outline-secondaryHomepage Updates
hero-shell,hero-copy, andhero-actionsclasses/dashboardwhen authenticated, otherwise to/signin, with dynamic button labelpages/index.vueto importtdeiClientand expose auth state.jumbostyles with new hero-specific classes and responsive breakpoint adjustments across lg/md/sm viewportsComponent Button & Link Standardization
btn btn-outline-primarytobtn btn-outline-secondary(DatasetTypeRadio, help page)btn-darkstyling with appropriate primary/outline-secondary variants in review, settings, and teams componentsAccessibility & UX Improvements
aria-labelin WorkspaceItem to include workspace IDcancelClasswithp-0)Additional Changes
AppLogo.vueto use new TDEI logo (tdei-logo.pnginstead oficon.svg)BCollapseandBNavbarcomponents@nuxtjs/google-fontsto devDependenciesnuxt-loading-indicatorstyling in default layout with gradient color via CSS variables