Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions frontend/documentation/components/Link.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import React, { ComponentProps } from 'react'
import type { Meta, StoryObj } from 'storybook'

import Link from 'components/Link'
import Icon from 'components/icons/Icon'

type LinkProps = ComponentProps<typeof Link>

const meta: Meta<LinkProps> = {
argTypes: {
children: {
control: 'text',
description: 'Link content. Composes naturally with text and icons.',
},
external: {
control: 'boolean',
description:
'Forces or suppresses external behaviour. Defaults to auto-detect: `true` for `http(s)://` URLs, `false` otherwise.',
},
href: {
control: 'text',
description: 'Link target. Internal paths or external URLs.',
},
noUnderline: {
control: 'boolean',
description: 'Drops the hover underline.',
},
},
args: {
children: 'View docs',
href: 'https://docs.flagsmith.com',
},
component: Link,
parameters: {
docs: {
description: {
component:
'Anchor primitive. Children-only — icons compose via JSX, no `iconLeft`/`iconRight` props. External-vs-internal behaviour auto-detects from the URL.',
},
},
layout: 'centered',
},
title: 'Components/Navigation/Link',
}
export default meta

type Story = StoryObj<LinkProps>

export const Default: Story = {}

export const Internal: Story = {
args: {
children: 'Go to features',
href: '/features',
},
parameters: {
docs: {
description: {
story:
'Internal route — does NOT add `target="_blank"` (auto-detected from non-`http(s)://` href).',
},
},
},
}

export const Inline: Story = {
parameters: {
docs: {
description: {
story:
'Inline composition with surrounding text — typical "see the docs" callout.',
},
},
},
render: () => (
<p className='text-default'>
Permission groups can be configured at the project level. See{' '}
<Link href='https://docs.flagsmith.com/system-administration/rbac'>
the RBAC docs
</Link>{' '}
for details.
</p>
),
}

export const WithIcon: Story = {
parameters: {
docs: {
description: {
story:
'Icon + label via children composition — no `iconLeft`/`iconRight` props. Spacing handled by the link wrapper.',
},
},
},
render: () => (
<Link href='https://docs.flagsmith.com'>
<Icon name='open-external-link' width={14} /> Open in new tab
</Link>
),
}

export const NoUnderline: Story = {
args: {
noUnderline: true,
},
parameters: {
docs: {
description: {
story:
'Hides the hover underline — useful for links inside dense layouts where the underline is too noisy.',
},
},
},
}
10 changes: 3 additions & 7 deletions frontend/web/components/AdminAPIKeys.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import data from 'common/data/base/_data'
import Token from './Token'
import JSONReference from './JSONReference'
import Button from './base/forms/Button'
import Link from './Link'
import DateSelect from './DateSelect'
import Icon from './icons/Icon'
import Switch from './Switch'
Expand Down Expand Up @@ -431,14 +432,9 @@ export default class AdminAPIKeys extends PureComponent {
{`API keys are used to authenticate with the Admin API.`}
</p>
<div className='mb-4 fs-small lh-sm'>
<Button
theme='text'
href='https://docs.flagsmith.com/integrations/terraform#terraform-api-key'
target='_blank'
className='fw-normal'
>
<Link href='https://docs.flagsmith.com/integrations/terraform#terraform-api-key'>
{`Learn about API Keys.`}
</Button>
</Link>
</div>
<Button onClick={this.createAPIKey} disabled={this.state.isLoading}>
{`Create API Key`}
Expand Down
10 changes: 3 additions & 7 deletions frontend/web/components/EditPermissions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import AccountStore from 'common/stores/account-store'
import Format from 'common/utils/format'
import PanelSearch from './PanelSearch'
import Button from './base/forms/Button'
import Link from './Link'
import InfoMessage from './InfoMessage'
import Switch from './Switch'
import TabItem from './navigation/TabMenu/TabItem'
Expand Down Expand Up @@ -1068,14 +1069,9 @@ const EditPermissions: FC<EditPermissionsType> = (props) => {
<p className='fs-small lh-sm col-md-8 mb-4'>
Flagsmith lets you manage fine-grained permissions for your projects and
environments.{' '}
<Button
theme='text'
href='https://docs.flagsmith.com/system-administration/rbac'
target='_blank'
className='fw-normal'
>
<Link href='https://docs.flagsmith.com/system-administration/rbac'>
Learn about User Roles.
</Button>
</Link>
</p>
<Tabs
urlParam='type'
Expand Down
19 changes: 5 additions & 14 deletions frontend/web/components/IntegrationList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ import {
} from 'common/types/responses'
import map from 'lodash/map'
import Button from './base/forms/Button'
import Link from './Link'
import DropdownMenu from './base/DropdownMenu'
import Icon from './icons/Icon'
import { IonIcon } from '@ionic/react'
import { close as closeIcon } from 'ionicons/icons'
import Utils from 'common/utils/utils'
import { Link, useHistory } from 'react-router-dom'
import { Link as RouterLink, useHistory } from 'react-router-dom'
import each from 'lodash/each'
import { useGetProjectQuery } from 'common/services/useProject'

Expand Down Expand Up @@ -277,17 +278,7 @@ const Integration: FC<IntegrationProps> = (props) => {
<div className='flex-1 flex-column'>
<h4 className='mb-0'>{title}</h4>
<div className='subtitle'>
{description}{' '}
{docs && (
<Button
theme='text'
href={docs}
target='_blank'
className='fw-normal'
>
View docs
</Button>
)}
{description} {docs && <Link href={docs}>View docs</Link>}
</div>
</div>
<div className='d-flex align-items-center'>{renderActions()}</div>
Expand All @@ -300,13 +291,13 @@ const Integration: FC<IntegrationProps> = (props) => {
{`Integration ${
props.lastSaved.isCreate ? 'added to' : 'saved to'
} ${lastSavedProject?.name ?? 'your project'}.\u00A0`}
<Link
<RouterLink
to={`/project/${props.lastSaved.projectId}/integrations`}
data-test='view-project-integrations-link'
className='text-primary'
>
Manage in project integrations
</Link>
</RouterLink>
</span>
{props.onDismissLastSaved && (
<a
Expand Down
49 changes: 49 additions & 0 deletions frontend/web/components/Link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React, {
AnchorHTMLAttributes,
FC,
HTMLAttributeAnchorTarget,
ReactNode,
} from 'react'
import cn from 'classnames'

type LinkProps = {
href: string
children: ReactNode
/**
* When true, opens in a new tab and adds `rel="noreferrer"`. Defaults to
* `true` when `href` starts with `http(s)://`, `false` otherwise.
*/
external?: boolean
/** Drops the hover underline. Defaults to `false` (underline shown). */
noUnderline?: boolean
className?: string
target?: HTMLAttributeAnchorTarget
rel?: string
onClick?: AnchorHTMLAttributes<HTMLAnchorElement>['onClick']
}

const Link: FC<LinkProps> = ({
children,
className,
external,
href,
noUnderline = false,
onClick,
rel,
target,
}) => {
const isExternal = external ?? /^https?:\/\//.test(href)
return (
<a
className={cn('btn-link', { 'no-underline': noUnderline }, className)}
href={href}
onClick={onClick}
rel={rel ?? (isExternal ? 'noreferrer' : undefined)}
target={target ?? (isExternal ? '_blank' : undefined)}
>
{children}
</a>
)
}

export default Link
9 changes: 3 additions & 6 deletions frontend/web/components/modals/AuditLogWebhooks.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { FC } from 'react'
import JSONReference from 'components/JSONReference'
import Button from 'components/base/forms/Button'
import Link from 'components/Link'
import Icon from 'components/icons/Icon'
import Constants from 'common/constants'
import {
Expand Down Expand Up @@ -74,13 +75,9 @@ const AuditLogWebhooks: FC<AuditLogWebhooksType> = ({ organisationId }) => {
Audit webhooks let you know when audit logs occur. You can configure
1 or more audit webhooks per organisation.
<br />
<Button
theme='text'
href='https://docs.flagsmith.com/system-administration/webhooks'
className='fw-normal'
>
<Link href='https://docs.flagsmith.com/system-administration/webhooks'>
Learn about Audit Webhooks.
</Button>
</Link>
</p>
</div>

Expand Down
10 changes: 3 additions & 7 deletions frontend/web/components/modals/InviteUsers.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useState, useEffect, useRef, FC } from 'react'
import Button from 'components/base/forms/Button'
import Link from 'components/Link'
import ConfigProvider from 'common/providers/ConfigProvider'
import Constants from 'common/constants'
import Icon from 'components/icons/Icon'
Expand Down Expand Up @@ -297,14 +298,9 @@ const InviteUsers: FC = () => {
Users without administrator privileges will need to be invited
to individual projects.
<div>
<Button
theme='text'
target='_blank'
href='https://docs.flagsmith.com/system-administration/rbac'
className='fw-normal'
>
<Link href='https://docs.flagsmith.com/system-administration/rbac'>
Learn about User Roles.
</Button>
</Link>
</div>
</div>
{error && <ErrorMessage error={error} />}
Expand Down
9 changes: 2 additions & 7 deletions frontend/web/components/pages/AccountSettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -316,14 +316,9 @@ const AccountSettingsPage: FC = () => {
<p>
You can use this token to securely integrate with the private
endpoints of our{' '}
<Button
theme='text'
href='https://docs.flagsmith.com/clients/rest#private-api-endpoints'
target='_blank'
className='fw-normal'
>
<Link href='https://docs.flagsmith.com/clients/rest#private-api-endpoints'>
RESTful API
</Button>
</Link>
.
</p>
<p>
Expand Down
25 changes: 8 additions & 17 deletions frontend/web/components/pages/EnvironmentSettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import PageTitle from 'components/PageTitle'
import { getStore } from 'common/store'
import { getRoles } from 'common/services/useRole'
import AccountStore from 'common/stores/account-store'
import { Link, useHistory, useRouteMatch } from 'react-router-dom'
import { Link as RouterLink, useHistory, useRouteMatch } from 'react-router-dom'
import { enableFeatureVersioning } from 'common/services/useEnableFeatureVersioning'
import AddMetadataToEntity from 'components/metadata/AddMetadataToEntity'
import { getSupportedContentType } from 'common/services/useSupportedContentType'
Expand All @@ -41,6 +41,7 @@ import {
useUpdateWebhookMutation,
} from 'common/services/useWebhooks'
import Button from 'components/base/forms/Button'
import Link from 'components/Link'
import Input from 'components/base/forms/Input'
import { useGetEnvironmentQuery } from 'common/services/useEnvironment'
import { useRouteContext } from 'components/providers/RouteContext'
Expand Down Expand Up @@ -658,9 +659,9 @@ const EnvironmentSettingsPage: React.FC = () => {
enabling this will prevent the API from returning
features that are disabled. You can also manage this
in{' '}
<Link to={`/project/${projectId}/settings`}>
<RouterLink to={`/project/${projectId}/settings`}>
Project settings
</Link>
</RouterLink>
.
</p>
</div>
Expand Down Expand Up @@ -729,14 +730,9 @@ const EnvironmentSettingsPage: React.FC = () => {
<br />
For full information on the excluded fields see
documentation{' '}
<Button
theme='text'
href='https://docs.flagsmith.com/system-administration/security#hide-sensitive-data'
target='_blank'
className='fw-normal'
>
<Link href='https://docs.flagsmith.com/system-administration/security#hide-sensitive-data'>
here.
</Button>
</Link>
<div className='text-danger'>
Enabling this feature will change the response
from the API and could break your existing
Expand Down Expand Up @@ -795,14 +791,9 @@ const EnvironmentSettingsPage: React.FC = () => {
Feature webhooks let you know when features have
changed. You can configure 1 or more Feature Webhooks
per Environment.{' '}
<Button
theme='text'
href='https://docs.flagsmith.com/system-administration/webhooks#environment-web-hooks'
target='_blank'
className='fw-normal'
>
<Link href='https://docs.flagsmith.com/system-administration/webhooks#environment-web-hooks'>
Learn about Feature Webhooks.
</Button>
</Link>
</p>
</div>
<Button onClick={handleCreateWebhook}>
Expand Down
Loading
Loading