fix(tabs): consolidate isDirty marker and decouple URL sync from Tabs#7387
Draft
talissoncosta wants to merge 4 commits intorefactor/tabs-cleanupfrom
Draft
fix(tabs): consolidate isDirty marker and decouple URL sync from Tabs#7387talissoncosta wants to merge 4 commits intorefactor/tabs-cleanupfrom
talissoncosta wants to merge 4 commits intorefactor/tabs-cleanupfrom
Conversation
Closes #6935. The unsaved-changes badge was reimplemented at every call site as a JSX tabLabel containing a `<div className='unread'>*</div>` next to the text. Two problems with that: - Each site had its own slightly different wrapper (Row, div, varying margin/padding classes), and one of them caused #6935: the badge overflowed below the active-tab underline and stayed visible on inactive tabs. - The badge belongs to the tab component, not the consumer. This commit: - Adds `isDirty?: boolean` to `TabItem`. TabButton renders the badge inside an inline-flex `.btn-tab__label` wrapper so label and badge stay on the same line and align vertically (port of the wrapper from #7038 by @Zaimwa9 — credit for the fix idea). - Migrates the seven badged call sites in CreateGroup, CreateSegment (×2), CreateFeature (×3), and create-experiment to use `isDirty`. This drops the local `<Row>`/`<div>`/`unread` JSX in favour of plain string `tabLabel`s, removing the redundant `tabLabelString` aliases alongside. - Adds a `WithDirtyMarker` story to `Tabs.stories.tsx` for Chromatic coverage. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Decouples Tabs from react-router. Tabs becomes a controlled component
(`value` + `onChange`); URL persistence is opt-in per consumer via
`useTabUrlSync(paramName, labels)`.
- Add `common/hooks/useTabUrlSync.ts`. Reads/writes a URL search param,
slugifies labels, falls back to `window.history.replaceState` when
rendered outside Router context (modal portals).
- Migrate 16 function-component consumers from `urlParam` /
`uncontrolled` to controlled Tabs:
- URL-bound (10): AdminDashboardPage, ImportPage, UsersAndPermissionsPage,
ChangeRequestsPage, FeatureHistoryDetailPage, EnvironmentSettingsPage,
OrganisationSettingsPage, ProjectSettingsPage, CreateSegment, CreateFeature.
- useState (6): AccountSettingsPage, CreateGroup, CreateSAML,
EnvironmentDocumentCodeHelp, DiffSegmentOverrides.
- Convert ComparePage class component to a function component using the
hook. (`create-experiment/index.js` is left on the legacy `urlParam`
path — gated by the `experimental_flags` Flagsmith feature flag,
follow-up when that's promoted or the modal converts.)
- Mark `urlParam`, `history`, `uncontrolled`, `tabLabelString`, and
`isRoles` as `@deprecated` on the Tabs/TabItem types. Implementation
stays for the legacy class consumer plus the two `OrganisationUsersTable`
Tabs that render inline inside `openModal` (no component context for
hooks until extracted).
- Add `WithUrlSync`, `Controlled`, and `HideNavOnSingleTab` stories so
the new pattern is the documented one.
- Incidentally rewrite a pre-existing nested ternary in
UsersAndPermissionsPage as an IIFE to satisfy the pre-commit hook
(the file's now in scope for lint-staged).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The two `openModal(...)` calls in `OrganisationUsersTable` rendered Tabs inline as JSX, with no surrounding component context — meaning they couldn't use hooks. Extract each tree into a proper function component so the outer Tabs can become controlled (`useState`) instead of relying on the deprecated `uncontrolled` prop. - Add `EditUserPermissionsModal` (Permissions/Groups tabs). - Add `InspectUserPermissionsModal` (Permissions tab only). - `OrganisationUsersTable.tsx` now passes the FCs to `openModal`. Note: the inner `PermissionsTabs` and `InspectPermissions` still pass `uncontrolled` through to their own internal Tabs; that's a separate migration on those wrapper components. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ermissions controlled - Drop `uncontrolled` from `Tabs.tsx`. Pure controlled API now: `value` + `onChange`. The internal `useState` fallback is removed; `urlParam` remains the only legacy escape hatch (still used by `create-experiment/index.js`). - `PermissionsTabs` and `InspectPermissions` no longer accept `uncontrolled`; `value` and `onChange` are required. Updates all three consumers (`CreateGroup`, `EditUserPermissionsModal`, `InspectUserPermissionsModal`) to wire up local `useState`. - Migrate Tabs stories to the controlled pattern with `useState`. New consumers should not see `uncontrolled` in any documented example. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
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.
Thanks for submitting a PR! Please check the boxes below:
Changes
Closes #6935. Contributes to #6606 (Design System Audit). Stacked on top of #7386.
This PR consolidates the unsaved-changes badge into Tabs, then takes the rest of the API down to its essentials by moving URL/router concerns out into a hook. `Tabs` is now a pure presentational component (`value` + `onChange`), and consumers compose state in via `useState` or the new `useTabUrlSync` hook.
Closes #6935 — `isDirty` on `TabItem`
The unsaved-changes badge was reimplemented at every call site (`
Decouple URL/router from Tabs
Left for follow-up
Supersedes #7038
#7038 was the surgical fix for #6935 by @Zaimwa9. Most of its diff is now redundant after this stack (the `` wrappers are gone, badge wrapper lives in TabButton). Plan to close #7038 with a comment pointing here once this lands.
How did you test this code?
Manually verified: