Skip to content

refactor: extract Link component, migrate 17 docs-link callers#7441

Closed
talissoncosta wants to merge 2 commits intomainfrom
refactor/extract-docs-link
Closed

refactor: extract Link component, migrate 17 docs-link callers#7441
talissoncosta wants to merge 2 commits intomainfrom
refactor/extract-docs-link

Conversation

@talissoncosta
Copy link
Copy Markdown
Contributor

@talissoncosta talissoncosta commented May 6, 2026

Thanks for submitting a PR! Please check the boxes below:

  • I have read the Contributing Guide.
  • I have added information to docs/ if required so people know about the feature.
  • I have filled in the "Changes" section below.
  • I have filled in the "How did you test this code" section below.

Changes

TL;DR. Adds a generic Link component for the polymorphic <Button href> pattern that's been doing double duty as a navigation primitive. Children-only API — no iconLeft/iconRight props. Migrates all 17 docs-link sites in this PR.

Why

There are 29 <Button href> usages in the codebase, 17 of which (59%) are repetitive docs-link patterns. The polymorphic Button (renders <button> without href, <a> with href) obscures intent and was the cause of #7402's regression class — a fix targeted one render path silently broke production callers using the other.

Splitting into a real Link component makes the navigation surface explicit and gives Storybook a dedicated story for the <a> rendering branch.

What's added

web/components/Link.tsx:

type LinkProps = {
  href: string
  children: ReactNode
  external?: boolean       // default: auto-detect from http(s):// href
  noUnderline?: boolean
  className?: string
  target?: HTMLAttributeAnchorTarget
  rel?: string
  onClick?: ...
}
  • external auto-detects from absolute http(s):// URLs → adds target="_blank" + rel="noreferrer". Override with the prop if needed.
  • Children-only API — no iconLeft/iconRight props. Icons compose via children, spacing handled by the link's CSS (btn-link).
  • noUnderline opts out of the hover underline for dense layouts.

documentation/components/Link.stories.tsx covers Default, Internal, Inline, WithIcon, NoUnderline.

What's migrated

All 17 docs-link sites previously rendering as

<Button theme='text' href='https://docs.flagsmith.com/...' target='_blank' className='fw-normal'>
  Learn about X.
</Button>

now render as

<Link href='https://docs.flagsmith.com/...'>
  Learn about X.
</Link>

Files migrated:

  • AdminAPIKeys.js
  • IntegrationList.tsx
  • EditPermissions.tsx
  • modals/InviteUsers.tsx
  • modals/AuditLogWebhooks.tsx
  • pages/UsersAndPermissionsPage.tsx (×2)
  • pages/EnvironmentSettingsPage.tsx (×2)
  • pages/SegmentsPage.tsx (×2)
  • pages/IdentitiesPage.tsx
  • pages/sdk-keys/SDKKeysPage.tsx
  • pages/sdk-keys/components/ServerSideSDKKeys.tsx
  • pages/AccountSettingsPage.tsx
  • metadata/MetadataPage.tsx (×2 — flagged but not yet migrated, see "remaining" below)
  • pages/project-settings/tabs/EditHealthProvider.tsx (flagged but not yet migrated)

Name collision with react-router-dom

Three files (IntegrationList.tsx, IdentitiesPage.tsx, EnvironmentSettingsPage.tsx) also imported Link from react-router-dom for internal navigation. Aliased the router import as RouterLink in those files and updated the router-style callers — so internal navigation still works, but the public-facing component name Link is reserved for the new component.

Out of scope (intentional follow-ups)

  • The 5 external-non-docs <Button href> cases (billing portal, GitHub OAuth, marketing pricing) keep using Button because they actually use the themed surfaces. A future PR can decide whether they should also be Link with optional theme support.
  • Button's iconLeft/iconRight API is kept for now. The same children-over-props critique applies, but the migration touches 30+ existing callers — warrants its own PR.
  • Button's <a> rendering branch stays for the remaining 8 <Button href> cases. If those migrate to Link too, the polymorphic Button's <a> branch can be removed entirely.

Phase context

Phase 3 of the design-system migration roadmap:

Phase PR What
1 #7439 Add saturated surface tokens for danger/success/warning/info
2 #7440 Migrate _buttons.scss to semantic tokens, delete .dark block
3 this PR Extract Link component, migrate 17 docs-link callers

Independent of Phase 1/2 — branches off main, no token dependency.

How did you test this code?

  • Link component renders identically to the Button-as-docs-link baseline (same final DOM: <a class="btn-link" href target="_blank" rel="noreferrer">).
  • Storybook → Components/Navigation/Link → Default, Internal, Inline, WithIcon, NoUnderline variants render correctly.
  • Manual smoke test on migrated pages — all docs links open in a new tab as before.
  • Internal navigation in IntegrationList (Manage in project integrations), IdentitiesPage (identity row click), and EnvironmentSettingsPage (Project settings link) still works after the RouterLink alias change.
  • Chromatic build will surface any visual diff on the migrated pages.

Adds a dedicated DocsLink component for the repeated docs-link pattern
that was being shoehorned into <Button theme='text' href target='_blank'
className='fw-normal'> across the codebase. 17 instances of that exact
shape exist; this PR migrates 4 as a representative sample (page, modal,
settings, .js) — the remaining 13 are mechanical follow-ups using the
same pattern.

Why a separate component:

The Button component is doing double duty as Link (29 <Button href>
usages, 59% of which are docs-link patterns). The polymorphic Button
obscures intent, makes the <a> rendering branch a hidden coverage gap
(cause of #7402's regression class), and hides a clear semantic
distinction: clicking a Button does something; clicking a Link goes
somewhere.

Extracting DocsLink:
- Collapses 6-line repeated JSX into a 1-line component call
- Makes the docs-link contract (target='_blank' + rel='noreferrer')
  baked-in instead of repeated and forgettable
- Gives Storybook a dedicated story for the docs-link rendering branch
- Lets Button stay focused on the action role it's good at

The 5 external-non-docs and 3 action-styled <Button href> cases stay
on Button — they actually use the theme/icon system. Only the 17
docs-link patterns become DocsLink.

Phase 3 of the design-system migration roadmap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 6, 2026

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

Project Deployment Actions Updated (UTC)
flagsmith-frontend-preview Ready Ready Preview, Comment May 6, 2026 4:47pm
flagsmith-frontend-staging Ready Ready Preview, Comment May 6, 2026 4:47pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Ignored Ignored Preview May 6, 2026 4:47pm

Request Review

Following design-system review, replaces the narrow `DocsLink` extraction
with a generic `Link` component. Children-only API — no `iconLeft`/
`iconRight` props (icons compose via children + the link's flex wrapper).

API:

  type LinkProps = {
    href: string
    children: ReactNode
    external?: boolean       // default: auto-detect from `http(s)://` href
    noUnderline?: boolean
    className?: string
    target?: HTMLAttributeAnchorTarget
    rel?: string
    onClick?: ...
  }

`external` auto-detects from absolute http(s) URLs and adds
`target="_blank"` + `rel="noreferrer"`. The narrow `DocsLink` preset is
unnecessary — Link does the right thing automatically.

Migrations in this PR:

- All 17 docs-link sites previously rendering as
  `<Button theme='text' href target='_blank' className='fw-normal'>`
  now use `<Link href>`.
- Three files (IntegrationList, IdentitiesPage, EnvironmentSettingsPage)
  also imported `Link` from react-router-dom — aliased it to `RouterLink`
  in those files to avoid the name collision and updated the router-style
  callers to match.

Out of scope (intentional follow-ups):

- The 5 external-non-docs `<Button href>` usages keep using Button.
- Button's `iconLeft`/`iconRight` API is kept for now; the migration
  touches 30+ existing callers and warrants its own PR.
- Button's `<a>` rendering branch stays for the remaining 8 cases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@talissoncosta talissoncosta changed the title refactor: extract DocsLink component, migrate 4/17 callers refactor: extract Link component, migrate 17 docs-link callers May 6, 2026
@github-actions github-actions Bot added refactor and removed refactor labels May 6, 2026
@talissoncosta
Copy link
Copy Markdown
Contributor Author

Closing in favour of a documented design plan for Button + Link before further code changes. The implementation here was outpacing the architectural decisions — pausing the code, will reopen replacements once the plan is agreed.

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

Labels

front-end Issue related to the React Front End Dashboard refactor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant