Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
5a0766e
fix(button): drop iconLeft/iconRight props, icons compose via children
talissoncosta May 6, 2026
0f51d33
fix(button): only wrap children when icon+label spacing is needed
talissoncosta May 6, 2026
39293c8
fix(modal-confirm): remove erroneous trash icon from danger confirm b…
talissoncosta May 6, 2026
8cbbb7c
chore(button): drop unused theme='project' map entry
talissoncosta May 6, 2026
b40e679
fix(buttons): use semantic icon tokens for btn-icon path fills
talissoncosta May 6, 2026
9898e7a
fix(buttons): give btn-with-icon a self-contained resting fill
talissoncosta May 6, 2026
b24cb41
docs(button): tidy Storybook stories
talissoncosta May 7, 2026
c423c1d
fix(buttons): switch .btn to inline-flex with hover and height fixes
talissoncosta May 7, 2026
e482265
fix(buttons): opt .btn-project out of new inline-flex layout
talissoncosta May 7, 2026
3e775a2
refactor(invite-users): swap broken icon spacing for project Icon at …
talissoncosta May 7, 2026
971afb1
fix(view-docs): drop alignment hacks now that .btn is flex+gap
talissoncosta May 7, 2026
8f92fc8
refactor(icons): swap hardcoded hex fills for semantic tokens
talissoncosta May 7, 2026
1a0db6a
fix(buttons): make .btn-link inline-flex so theme='text' flows with text
talissoncosta May 7, 2026
3494875
fix(buttons): wire .btn-success to the \$btn-success-* ladder, add mi…
talissoncosta May 7, 2026
0c4afa6
fix(buttons): keep white text on disabled .btn-success / .btn-danger
talissoncosta May 7, 2026
77636be
docs(button): drop CSS implementation detail from WithIcons story
talissoncosta May 7, 2026
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
24 changes: 14 additions & 10 deletions frontend/documentation/components/Button.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
sizeClassNames,
} from 'components/base/forms/Button'
import type { ButtonType } from 'components/base/forms/Button'
import { Icon } from 'components/icons'

const themeOptions = Object.keys(themeClassNames) as Array<
keyof typeof themeClassNames
Expand All @@ -19,7 +20,7 @@ const meta: Meta<ButtonType> = {
argTypes: {
children: {
control: 'text',
description: 'Button label content.',
description: 'Button label content. Compose icons via `<Icon>` children.',
},
disabled: {
control: 'boolean',
Expand Down Expand Up @@ -60,7 +61,7 @@ export const Variants: Story = {
docs: {
description: {
story:
'All available button themes. Use `primary` for main actions, `secondary` for alternatives, `outline` for low-emphasis actions, `danger` for destructive actions, and `success` for positive confirmations. `icon` is for icon-only buttons (copy, action triggers in tables); `project` is the avatar-style button used in the project picker.',
'All available button themes. Use `primary` for main actions, `secondary` for alternatives, `outline` for low-emphasis actions, `danger` for destructive actions, and `success` for positive confirmations.',
},
},
},
Expand All @@ -73,10 +74,9 @@ export const Variants: Story = {
<Button theme='success'>Success</Button>
<Button theme='tertiary'>Tertiary</Button>
<Button theme='text'>Text</Button>
<Button theme='icon' iconLeft='copy'>
{''}
<Button theme='icon'>
<Icon name='copy' />
</Button>
<Button theme='project'>Project</Button>
</div>
),
}
Expand All @@ -85,7 +85,7 @@ export const Sizes: Story = {
parameters: {
docs: {
description: {
story: 'Button sizes from large to extra small.',
story: 'Button sizes from large to extra-extra small.',
},
},
},
Expand All @@ -95,6 +95,7 @@ export const Sizes: Story = {
<Button size='default'>Default</Button>
<Button size='small'>Small</Button>
<Button size='xSmall'>Extra Small</Button>
<Button size='xxSmall'>XX Small</Button>
</div>
),
}
Expand Down Expand Up @@ -130,20 +131,23 @@ export const WithIcons: Story = {
docs: {
description: {
story:
'Buttons support `iconLeft` and `iconRight` props. Pass any `IconName` from the icon system.',
'Icons compose via children — pass `<Icon>` JSX directly alongside the label. Button handles icon+label spacing internally.',
},
},
},
render: () => (
<div className='d-flex align-items-center flex-wrap gap-2'>
<Button theme='primary' iconLeft='plus'>
<Button theme='primary'>
<Icon name='plus' />
Add Item
</Button>
<Button theme='danger' iconLeft='trash-2'>
<Button theme='danger'>
<Icon name='trash-2' />
Delete
</Button>
<Button theme='outline' iconRight='chevron-right'>
<Button theme='outline'>
Next
<Icon name='chevron-right' />
</Button>
</div>
),
Expand Down
8 changes: 3 additions & 5 deletions frontend/web/components/EditIdentity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { FC, useEffect, useRef, useState } from 'react'
import { Identity } from 'common/types/responses'
import { useUpdateIdentityMutation } from 'common/services/useIdentity'
import Button from './base/forms/Button'
import { Icon } from './icons'
import ErrorMessage from './ErrorMessage'
import GhostInput from './base/forms/GhostInput'

Expand Down Expand Up @@ -62,15 +63,12 @@ const EditIdentity: FC<EditIdentityType> = ({ data, environmentId }) => {
/>
<Button
disabled={!data}
iconSize={16}
theme='text'
style={{ lineHeight: 'inherit' }}
className='text-primary d-flex align-items-center'
iconRightColour='primary'
iconRight={'edit'}
className='text-primary'
onClick={handleFocus}
>
Edit
<Icon name='edit' width={16} />
</Button>
<ErrorMessage>{error}</ErrorMessage>
</>
Expand Down
3 changes: 2 additions & 1 deletion frontend/web/components/GoogleButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { FC } from 'react'
import { TokenResponse, useGoogleLogin } from '@react-oauth/google'
import Button from './base/forms/Button'
import { Icon } from './icons'

type GoogleButtonProps = {
className?: string
Expand All @@ -22,10 +23,10 @@ const GoogleButton: FC<GoogleButtonProps> = ({ className, onSuccess }) => {
<Button
className={className}
theme='secondary'
iconLeft='google'
key='google'
onClick={() => login()}
>
<Icon name='google' />
Google
</Button>
)
Expand Down
5 changes: 2 additions & 3 deletions frontend/web/components/JSONReference.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Highlight from './Highlight'
import Button from './base/forms/Button'
import Switch from './Switch'
import flagsmith from '@flagsmith/flagsmith'
import Icon from './icons/Icon'
import { Icon } from './icons'
import Utils from 'common/utils/utils'

type JSONReferenceType = {
Expand Down Expand Up @@ -146,9 +146,8 @@ const JSONReference: FC<JSONReferenceType> = ({
Utils.copyToClipboard(condensed ? idsOnly : value)
}}
size='xSmall'
iconLeft='copy'
iconLeftColour='white'
>
<Icon name='copy' />
Copy JSON
</Button>
</Row>
Expand Down
4 changes: 2 additions & 2 deletions frontend/web/components/ViewDocs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ type ViewDocsType = ButtonType & {}
const ViewDocs: FC<ViewDocsType> = (props) => {
return (
<Button {...props} target='_blank' className='fw-bold' theme='text'>
<Icon style={{ marginTop: -5 }} fill={'#6837fc'} name={'file-text'} />
<span style={{ lineHeight: '24px' }}> View docs</span>
<Icon name='file-text' />
View docs
</Button>
)
}
Expand Down
75 changes: 12 additions & 63 deletions frontend/web/components/base/forms/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,14 @@
import React from 'react'
import cn from 'classnames'
import { ButtonHTMLAttributes, HTMLAttributeAnchorTarget } from 'react'
import Icon, { IconName } from 'components/icons/Icon'

const iconColours = {
primary: '#6837fc',
white: '#ffffff',
} as const

export type IconColour = keyof typeof iconColours

export const themeClassNames = {
danger: 'btn btn-danger',
danger: 'btn-danger',
icon: 'btn-icon',
outline: 'btn--outline',
primary: 'btn-primary',
project: 'btn-project',
secondary: 'btn btn-secondary',
success: 'btn btn-success',
secondary: 'btn-secondary',
success: 'btn-success',
tertiary: 'btn-tertiary',
text: 'btn-link',
}
Expand All @@ -31,15 +22,10 @@ export const sizeClassNames = {
}

export type ButtonType = ButtonHTMLAttributes<HTMLButtonElement> & {
iconRight?: IconName
iconRightColour?: IconColour
iconLeftColour?: IconColour
iconLeft?: IconName
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like FlagEnvironmentsPage has been skipped during the migration (still having a iconLeft='arrow-left there)

href?: string
target?: HTMLAttributeAnchorTarget
theme?: keyof typeof themeClassNames
size?: keyof typeof sizeClassNames
iconSize?: number
}

export const Button = React.forwardRef<
Expand All @@ -51,11 +37,6 @@ export const Button = React.forwardRef<
children,
className,
href,
iconLeft,
iconLeftColour,
iconRight,
iconRightColour,
iconSize = 24,
onMouseUp,
size = 'default',
target,
Expand All @@ -65,64 +46,32 @@ export const Button = React.forwardRef<
},
ref,
) => {
const classes = cn(
'btn',
className,
themeClassNames[theme],
sizeClassNames[size],
)
return href ? (
<a
onClick={rest.onClick as React.MouseEventHandler}
className={cn(className, themeClassNames[theme], sizeClassNames[size])}
className={classes}
target={target}
href={href}
rel='noreferrer'
ref={ref as React.RefObject<HTMLAnchorElement>}
>
<div className='d-flex h-100 align-items-center justify-content-center gap-2'>
{!!iconLeft && (
<Icon
fill={iconLeftColour ? iconColours[iconLeftColour] : undefined}
name={iconLeft}
width={iconSize}
/>
)}
{children}
</div>
{!!iconRight && (
<Icon
fill={iconRightColour ? iconColours[iconRightColour] : undefined}
className='ml-2'
name={iconRight}
width={iconSize}
/>
)}
{children}
</a>
) : (
<button
{...rest}
type={type}
onMouseUp={onMouseUp}
className={cn(
{ btn: true },
className,
themeClassNames[theme],
sizeClassNames[size],
)}
className={classes}
ref={ref as React.RefObject<HTMLButtonElement>}
>
{!!iconLeft && (
<Icon
fill={iconLeftColour ? iconColours[iconLeftColour] : undefined}
className='mr-2'
name={iconLeft}
width={iconSize}
/>
)}
{children}
{!!iconRight && (
<Icon
fill={iconRightColour ? iconColours[iconRightColour] : undefined}
className='ml-2'
name={iconRight}
width={iconSize}
/>
)}
</button>
)
},
Expand Down
2 changes: 2 additions & 0 deletions frontend/web/components/icons/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as Icon } from './Icon'
export type { IconName } from './Icon'
13 changes: 3 additions & 10 deletions frontend/web/components/modals/InviteUsers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import Button from 'components/base/forms/Button'
import ConfigProvider from 'common/providers/ConfigProvider'
import Constants from 'common/constants'
import Icon from 'components/icons/Icon'
import { add } from 'ionicons/icons'
import { IonIcon } from '@ionic/react'
import { getPlanBasedOption } from 'components/PlanBasedAccess'
import InputGroup from 'components/base/forms/InputGroup'
Expand Down Expand Up @@ -259,7 +258,7 @@ const InviteUsers: FC = () => {
onClick={() => deleteInvite(invite.temporaryId)}
className='btn btn-with-icon mb-2'
>
<Icon name='trash-2' width={20} fill='#656D7B' />
<Icon name='trash-2' width={20} />
</Button>
</div>
) : (
Expand All @@ -282,14 +281,8 @@ const InviteUsers: FC = () => {
])
}
>
<Row>
<span className='pl-2 icon'>
<IonIcon icon={add} style={{ fontSize: '13px' }} />
</span>
<span>
{isSaving ? 'Sending' : 'Invite additional member'}
</span>
</Row>
<Icon name='plus' width={16} />
{isSaving ? 'Sending' : 'Invite additional member'}
</Button>
</div>

Expand Down
1 change: 0 additions & 1 deletion frontend/web/components/modals/base/ModalConfirm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ const Confirm: FC<Confirm> = ({
theme='danger'
id='confirm-btn-yes'
disabled={disabled || disabledYes}
iconRight='fas fa-trash'
onClick={yes}
>
{yesText}
Expand Down
3 changes: 2 additions & 1 deletion frontend/web/components/pages/HomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Button from 'components/base/forms/Button'
import PasswordRequirements from 'components/PasswordRequirements'
import { informationCircleOutline } from 'ionicons/icons'
import { IonIcon } from '@ionic/react'
import { Icon } from 'components/icons'
import classNames from 'classnames'
import InfoMessage from 'components/InfoMessage'
import OnboardingPage from './OnboardingPage'
Expand Down Expand Up @@ -206,9 +207,9 @@ const HomePage: React.FC = () => {
<Button
theme='secondary'
className='w-100'
iconLeft='github'
href={JSON.parse(Utils.getFlagsmithValue('oauth_github')).url}
>
<Icon name='github' />
GitHub
</Button>
</div>,
Expand Down
2 changes: 1 addition & 1 deletion frontend/web/components/pages/IdentityPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ const IdentityPage: FC = () => {
/>
</span>
{showAliases && (
<h6 className='d-flex mb-0 align-items-end gap-1'>
<h6 className='d-flex mb-0 align-items-baseline gap-1'>
<Tooltip
title={
<span className='user-select-none'>Alias: </span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { StageActionBody, StageActionRequest } from 'common/types/requests'
import { useMemo } from 'react'
import SinglePipelineStageAction from './SinglePipelineStageAction'
import Button from 'components/base/forms/Button'
import { Icon } from 'components/icons'
import { StageActionType } from 'common/types/responses'

interface PipelineStageActionsProps {
Expand Down Expand Up @@ -60,7 +61,8 @@ const PipelineStageActions = ({
onRemoveAction={index > 0 ? onRemoveAction : undefined}
/>
))}
<Button iconLeft='plus' className='w-100' onClick={onAddAction}>
<Button className='w-100' onClick={onAddAction}>
<Icon name='plus' />
Add Flag Action
</Button>
</>
Expand Down
Loading
Loading