Skip to content

fix(ui): unify tab styles and stop tab labels from shifting - #5399

Open
ux-git wants to merge 9 commits into
sws2apps:mainfrom
ux-git:fix/unified-tab-styles
Open

fix(ui): unify tab styles and stop tab labels from shifting#5399
ux-git wants to merge 9 commits into
sws2apps:mainfrom
ux-git:fix/unified-tab-styles

Conversation

@ux-git

@ux-git ux-git commented Sep 4, 2026

Copy link
Copy Markdown
Member

Description

Tabs were styled by each page instead of by the tab component, so the same control looked different depending on where it was used, and the selected tab changed size and pushed its neighbours around.

Five pages each carried their own copy of the tab overrides, and the copies had drifted apart: the press ripple was rounded in three of them and square in the other two, while the component underneath stayed fully rounded. The tab styling now lives in one place and the components expose the choice as a prop:

  • appearancechip (fully rounded, for the date and period selectors) or plain (4px, for tabs that switch a page section). Hover, ripple and the selected background all follow it, so a variant can no longer disagree with itself.
  • layoutauto or stretch. Three pages repeated the same breakpoint check to pick the MUI variant; the component now resolves it.
  • badge — the counter next to a label. It used to be a node each page assembled, which nested a <div> inside a <p> and, because of a transform left behind when the badge stopped fading out at zero, pushed the label 12px to the right whenever the count was 0.

Selecting a tab swapped body-regular for h4. The two have different metrics, so the tab was remeasured and the row shifted. Both typographies are now stacked in the same grid cell, which reserves the width of the bolder one without a fixed width, so any translation and any label length still behaves.

indicatorMode, minHeight, variant and the unused tabsCountOnScreen were removed from the props: the first repeated appearance, and no page ever set the others.

Verified in the app at 1440px and 375px on the weekly schedules, publisher records, service year, meeting attendance, applications, speakers catalog, midweek meeting and congregation settings pages. Tab widths are identical before and after selection, the label stays centred, and no page scrolls horizontally.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • My changes generate no new warnings

Devin Review

@vercel

vercel Bot commented Sep 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
staging-organized-app Ready Ready Preview Sep 4, 2026 9:50pm UTC
test-organized-app Ready Ready Preview Sep 4, 2026 9:50pm UTC

@coderabbitai

coderabbitai Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Team

Run ID: 94fc8624-581b-404e-8353-8f253a0830f4

📥 Commits

Reviewing files that changed from the base of the PR and between 309f6d5 and 0149147.

📒 Files selected for processing (2)
  • src/components/tabs/index.styles.ts
  • src/components/tabs/index.tsx

Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review.


Important

Approval pending

CodeRabbit has no unresolved comments, but it has not reviewed the latest commit.

Use the checkbox below to review the latest commit. CodeRabbit will approve the changes if it finds no blocking issues.

  • 🔍 Trigger review

Walkthrough

The tab system now uses shared appearance and layout props, centralized styles, and a reusable TabLabel component. Consumers pass separate label and badge data. Responsive variant logic and local tab style overrides were removed.

Changes

Unified tab system

Layer / File(s) Summary
Tab contracts, shared styles, and labels
src/components/tabs/*, src/components/tab_switcher/index.tsx
Tab types now support appearance, layout, and numeric badges. Shared styles and TabLabel centralize tab appearance, typography, and badge rendering.
ScrollableTabs behavior
src/components/scrollable_tabs/index.tsx
ScrollableTabs derives layout and indicator behavior from the new props, uses a fixed height, applies shared styles, and renders TabLabel.
Consumer migration and related styling
src/features/meetings/my_assignments/index.tsx, src/features/persons/*, src/features/reports/*, src/pages/meetings/schedules/index.tsx, src/components/tab_label_with_badge/index.tsx
Consumers now pass appearance or layout and separate label and badge values. Legacy badge transitions and local tab style overrides were removed.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 3bd33

Tabs now consistently apply their selected appearance while preserving label widths during selection. No current merge-blocking risk is identified.

Sequence Diagram(s)

sequenceDiagram
  participant TabConsumer
  participant ScrollableTabs
  participant tabsSharedStyles
  participant TabLabel
  participant MUI_Tab
  TabConsumer->>ScrollableTabs: Pass layout, appearance, labels, and badges
  ScrollableTabs->>tabsSharedStyles: Request styles for appearance
  ScrollableTabs->>TabLabel: Pass label, badge, and selected
  TabLabel->>MUI_Tab: Render typography and badge content
  ScrollableTabs->>MUI_Tab: Apply layout, indicator, and shared styles
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: unified tab styles and prevented selected labels from shifting.
Description check ✅ Passed The description directly explains the shared tab styling, new props, label-width fix, removed props, and verification performed.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 18 files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

… resizing

Tabs picked up their look in five different places: every page that
wanted flat tabs copied the same block of overrides onto the shared
component, and the copies disagreed — some cleared the ripple radius,
others rounded it to 8px, while the tabs underneath stayed fully
rounded. Selecting a tab also swapped its typography class, and the
heavier text measured wider, so the neighbouring tabs shifted.

The two looks are now one prop on the shared component. `chip` keeps
the fully rounded pill the date and period selectors use, `plain` gives
the page section tabs a 4px background on hover and while pressed, and
both come from a single style module. The five copies of the override
block are gone.

The label renders both typographic states stacked in one grid cell, so
a tab always reserves the width of its selected state and nothing moves
when the weight changes. It measures whatever the label happens to be,
so translations of any length keep working without a fixed width.
The badge next to a tab label was passed in as a custom node built by each
call site, which nested block elements inside a typography and shifted the
label 12px to the right whenever the count was zero. The badge is now an
option of the shared tab label, so every tab renders it the same way.
Three pages repeated the same breakpoint check to pick the MUI tab variant.
They now state what they want with a layout prop and the component resolves
the breakpoint, next to the unused tabs count prop that has been dropped.
The indicator was requested by every plain tab row and by none of the chip
ones, so it now follows the appearance instead of being asked for twice, and
the tab height that no page ever set became a constant. The segmented
switcher reads its font from the global classes rather than repeating them.
@ux-git
ux-git force-pushed the fix/unified-tab-styles branch from dfc050b to 309f6d5 Compare September 4, 2026 01:04
coderabbitai[bot]

This comment was marked as resolved.

The component accepted an appearance but always drew the plain one, so a
caller asking for chips got plain tabs. It passes the prop through now,
and defaults to plain, which is what every caller renders today.
The shared styles carried a default of their own, so a component that
forgot to pass the appearance still got one, which is how the plain tabs
came to ignore the prop in the first place. The helper asks for it now.
Both components keep the appearance they render today: plain for the tabs,
chip for the scrollable ones, which is what most of their callers rely on.
coderabbitai[bot]
coderabbitai Bot previously approved these changes Sep 4, 2026
@mergify

mergify Bot commented Sep 4, 2026

Copy link
Copy Markdown

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

devin-ai-integration[bot]

This comment was marked as resolved.

Only text labels are duplicated to reserve their bold width; a component label would run its effects twice and repeat its ids.
@sonarqubecloud

sonarqubecloud Bot commented Sep 4, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant