Standardize Button/ActionIcon variants and enforce via ESLint (#1610)

This commit is contained in:
Elizabet Oliveira 2026-01-16 14:42:16 +00:00 committed by GitHub
parent 1d96140909
commit 5b3ce9fc7c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
49 changed files with 303 additions and 234 deletions

View file

@ -0,0 +1,5 @@
---
"@hyperdx/app": patch
---
refactor: Standardize Button/ActionIcon variants and add ESLint enforcement

View file

@ -43,6 +43,7 @@ The project uses **Yarn 4.5.1** workspaces. Docker Compose manages ClickHouse, M
3. **Existing patterns**: Follow established patterns in the codebase - explore similar files before implementing
4. **Component size**: Keep files under 300 lines; break down large components
5. **Testing**: Tests live in `__tests__/` directories; use Jest for unit/integration tests, `cd packages/app && yarn ci:unit` for unit tests, and `cd packages/app && yarn ci:int` for integration tests
6. **UI Components**: Use custom Button/ActionIcon variants (`primary`, `secondary`, `danger`) - see `agent_docs/code_style.md` for required patterns
## Important Context

View file

@ -25,19 +25,63 @@
## Mantine UI Components
The project uses Mantine UI with **custom variants** defined in `packages/app/src/theme/mantineTheme.ts`:
The project uses Mantine UI with **custom variants** defined in `packages/app/src/theme/mantineTheme.ts`.
### Custom Button Variants
- `variant="primary"` - Light green button for primary actions
- `variant="secondary"` - Default styled button for secondary actions
- `variant="danger"` - Light red button for destructive actions
### Button & ActionIcon Variants (REQUIRED)
### Custom ActionIcon Variants
- `variant="primary"` - Light green action icon
- `variant="secondary"` - Default styled action icon
- `variant="danger"` - Light red action icon for destructive actions
**ONLY use these variants for Button and ActionIcon components:**
These are valid variants - do not replace them with standard Mantine variants like `variant="light" color="red"`.
| Variant | Use Case | Example |
|---------|----------|---------|
| `variant="primary"` | Primary actions (Submit, Save, Create, Run) | `<Button variant="primary">Save</Button>` |
| `variant="secondary"` | Secondary actions (Cancel, Clear, auxiliary actions) | `<Button variant="secondary">Cancel</Button>` |
| `variant="danger"` | Destructive actions (Delete, Remove, Rotate API Key) | `<Button variant="danger">Delete</Button>` |
### DO NOT USE (Forbidden Patterns)
The following patterns are **NOT ALLOWED** for Button and ActionIcon:
```tsx
// ❌ WRONG - Don't use these
<Button variant="light" color="green">Save</Button>
<Button variant="light" color="gray">Cancel</Button>
<Button variant="light" color="red">Delete</Button>
<Button variant="outline" color="green">Save</Button>
<Button variant="outline" color="gray">Cancel</Button>
<Button variant="outline" color="red">Delete</Button>
<Button variant="filled" color="gray">Cancel</Button>
<Button variant="default">Cancel</Button>
<ActionIcon variant="light" color="red">...</ActionIcon>
<ActionIcon variant="filled" color="gray">...</ActionIcon>
// ✅ CORRECT - Use custom variants
<Button variant="primary">Save</Button>
<Button variant="secondary">Cancel</Button>
<Button variant="danger">Delete</Button>
<ActionIcon variant="primary">...</ActionIcon>
<ActionIcon variant="secondary">...</ActionIcon>
<ActionIcon variant="danger">...</ActionIcon>
```
**Note**: `variant="filled"` is still valid for **form inputs** (Select, TextInput, etc.), just not for Button/ActionIcon.
### Icon-Only Buttons → ActionIcon
**If a Button only contains an icon (no text), use ActionIcon instead:**
```tsx
// ❌ WRONG - Button with only an icon
<Button variant="secondary" px="xs">
<IconRefresh size={18} />
</Button>
// ✅ CORRECT - Use ActionIcon for icon-only buttons
<ActionIcon variant="secondary" size="input-sm">
<IconRefresh size={18} />
</ActionIcon>
```
This pattern cannot be enforced by ESLint and requires manual code review.
## Refactoring

View file

@ -86,6 +86,51 @@ export default [
selector: 'Literal[value=/\\bbi-\\b/i]',
message: 'Please update to use @tabler/icons-react instead',
},
// Enforce custom Button/ActionIcon variants (see agent_docs/code_style.md)
// NOTE: Icon-only Buttons should use ActionIcon instead - this requires manual review
// as ESLint cannot detect children content patterns
{
selector:
'JSXElement[openingElement.name.name="Button"] JSXAttribute[name.name="variant"][value.value="light"]',
message:
'Use variant="primary", "secondary", or "danger" for Button. See agent_docs/code_style.md',
},
{
selector:
'JSXElement[openingElement.name.name="Button"] JSXAttribute[name.name="variant"][value.value="filled"]',
message:
'Use variant="primary", "secondary", or "danger" for Button. See agent_docs/code_style.md',
},
{
selector:
'JSXElement[openingElement.name.name="Button"] JSXAttribute[name.name="variant"][value.value="outline"]',
message:
'Use variant="primary", "secondary", or "danger" for Button. See agent_docs/code_style.md',
},
{
selector:
'JSXElement[openingElement.name.name="Button"] JSXAttribute[name.name="variant"][value.value="default"]',
message:
'Use variant="primary", "secondary", or "danger" for Button. See agent_docs/code_style.md',
},
{
selector:
'JSXElement[openingElement.name.name="ActionIcon"] JSXAttribute[name.name="variant"][value.value="light"]',
message:
'Use variant="primary", "secondary", or "danger" for ActionIcon. See agent_docs/code_style.md',
},
{
selector:
'JSXElement[openingElement.name.name="ActionIcon"] JSXAttribute[name.name="variant"][value.value="filled"]',
message:
'Use variant="primary", "secondary", or "danger" for ActionIcon. See agent_docs/code_style.md',
},
{
selector:
'JSXElement[openingElement.name.name="ActionIcon"] JSXAttribute[name.name="variant"][value.value="outline"]',
message:
'Use variant="primary", "secondary", or "danger" for ActionIcon. See agent_docs/code_style.md',
},
],
'react-hooks/exhaustive-deps': 'error',
'no-console': ['error', { allow: ['warn', 'error'] }],

View file

@ -159,7 +159,7 @@ function AckAlert({ alert }: { alert: AlertsPageItem }) {
<Menu.Target>
<Button
size="compact-sm"
variant="light"
variant="primary"
color={
isNoLongerMuted
? 'var(--color-bg-warning)'
@ -212,7 +212,7 @@ function AckAlert({ alert }: { alert: AlertsPageItem }) {
<ErrorBoundary message="Failed to load alert acknowledgment menu">
<Menu disabled={silenceAlert.isPending}>
<Menu.Target>
<Button size="compact-sm" variant="default">
<Button size="compact-sm" variant="secondary">
Ack
</Button>
</Menu.Target>

View file

@ -48,7 +48,7 @@ export const AppNavCloudBanner = () => {
<span className="fs-8">Ready to deploy on ClickHouse Cloud?</span>
<div className="mt-2 mb-2">
<Link href="https://clickhouse.com/docs/use-cases/observability/clickstack/getting-started#deploy-with-clickhouse-cloud">
<Button variant="light" size="xs" className="hover-color-white">
<Button variant="primary" size="xs" className="hover-color-white">
Get Started for Free
</Button>
</Link>

View file

@ -200,7 +200,7 @@ export default function AuthPage({ action }: { action: 'register' | 'login' }) {
<Button
mt={4}
type="submit"
variant="light"
variant="primary"
size="md"
disabled={isSubmitting || verificationSent}
loading={isSubmitting}

View file

@ -254,7 +254,7 @@ function BenchmarkPage() {
</Stack>
</Grid.Col>
</Grid>
<Button variant="outline" type="submit" loading={isQueryIdsLoading}>
<Button variant="primary" type="submit" loading={isQueryIdsLoading}>
Run Benchmark
</Button>
{isQueryIdsLoading && (

View file

@ -15,6 +15,7 @@ import {
Filter,
} from '@hyperdx/common-utils/dist/types';
import {
ActionIcon,
Box,
Button,
Grid,
@ -589,18 +590,17 @@ function ClickhousePage() {
/>
</form>
<Tooltip withArrow label="Refresh dashboard" fz="xs" color="gray">
<Button
<ActionIcon
onClick={refresh}
loading={manualRefreshCooloff}
disabled={manualRefreshCooloff}
color="gray"
variant="outline"
variant="secondary"
title="Refresh dashboard"
aria-label="Refresh dashboard"
px="xs"
size="lg"
>
<IconRefresh size={18} />
</Button>
</ActionIcon>
</Tooltip>
</Group>
</Group>

View file

@ -23,7 +23,7 @@ export default function Clipboard({
}}
>
<Button
variant="default"
variant="secondary"
p={0}
className={cx('text-decoration-none', className)}
size="xs"

View file

@ -189,7 +189,7 @@ function AIAssistant({
{chartAssistant.isPending ? (
<Loader size="xs" type="dots" />
) : (
<Button type="submit" size="xs" variant="light">
<Button type="submit" size="xs" variant="primary">
Generate
</Button>
)}

View file

@ -31,6 +31,7 @@ import {
SQLInterval,
} from '@hyperdx/common-utils/dist/types';
import {
ActionIcon,
Box,
Button,
Flex,
@ -244,49 +245,45 @@ const Tile = forwardRef(
mr={4}
>
<Tooltip label={alertTooltip} withArrow>
<Button
<ActionIcon
data-testid={`tile-alerts-button-${chart.id}`}
variant="subtle"
color="gray"
size="xxs"
size="sm"
onClick={onEditClick}
>
<IconBell size={16} />
</Button>
</ActionIcon>
</Tooltip>
</Indicator>
)}
<Button
<ActionIcon
data-testid={`tile-duplicate-button-${chart.id}`}
variant="subtle"
color="gray"
size="xxs"
size="sm"
onClick={onDuplicateClick}
title="Duplicate"
>
<IconCopy size={14} />
</Button>
<Button
</ActionIcon>
<ActionIcon
data-testid={`tile-edit-button-${chart.id}`}
variant="subtle"
color="gray"
size="xxs"
size="sm"
onClick={onEditClick}
title="Edit"
>
<IconPencil size={14} />
</Button>
<Button
</ActionIcon>
<ActionIcon
data-testid={`tile-delete-button-${chart.id}`}
variant="subtle"
color="gray"
size="xxs"
size="sm"
onClick={onDeleteClick}
title="Delete"
>
<IconTrash size={14} />
</Button>
</ActionIcon>
</Flex>
);
}, [
@ -535,7 +532,7 @@ function DashboardName({
}
placeholder="Dashboard Name"
/>
<Button ms="sm" variant="outline" type="submit" color="green">
<Button ms="sm" variant="primary" type="submit">
Save Name
</Button>
</form>
@ -934,12 +931,7 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) {
<Text size="sm">
This is a temporary dashboard and can not be saved.
</Text>
<Button
variant="outline"
color="green"
fw={400}
onClick={onCreateDashboard}
>
<Button variant="primary" fw={400} onClick={onCreateDashboard}>
Create New Saved Dashboard
</Button>
</Flex>
@ -966,7 +958,7 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) {
onChange={handleUpdateTags}
>
<Button
variant="default"
variant="secondary"
px="xs"
size="xs"
style={{ flexShrink: 0 }}
@ -980,9 +972,9 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) {
{!isLocalDashboard /* local dashboards cant be "deleted" */ && (
<Menu width={250}>
<Menu.Target>
<Button variant="default" px="xs" size="xs">
<ActionIcon variant="secondary" size="input-xs">
<IconDotsVertical size={14} />
</Button>
</ActionIcon>
</Menu.Target>
<Menu.Dropdown>
@ -1107,42 +1099,44 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) {
<Button
onClick={() => setIsLive(prev => !prev)}
size="sm"
variant={isLive ? 'filled' : 'default'}
variant={isLive ? 'primary' : 'secondary'}
title={isLive ? 'Disable auto-refresh' : 'Enable auto-refresh'}
>
Live
</Button>
</Tooltip>
<Tooltip withArrow label="Refresh dashboard" fz="xs" color="gray">
<Button
<ActionIcon
onClick={refresh}
loading={manualRefreshCooloff}
disabled={manualRefreshCooloff}
variant="default"
variant="secondary"
title="Refresh dashboard"
px="xs"
size="input-sm"
>
<IconRefresh size={18} />
</Button>
</ActionIcon>
</Tooltip>
{!IS_LOCAL_MODE && (
<Tooltip withArrow label="Edit Filters" fz="xs" color="gray">
<Button
variant="default"
px="xs"
<ActionIcon
variant="secondary"
onClick={() => setShowFiltersModal(true)}
data-testid="edit-filters-button"
size="input-sm"
>
<IconFilterEdit strokeWidth={1} />
</Button>
<IconFilterEdit size={18} />
</ActionIcon>
</Tooltip>
)}
<Button
data-testid="search-submit-button"
variant="outline"
variant="primary"
type="submit"
leftSection={<IconPlayerPlay size={16} />}
style={{ flexShrink: 0 }}
>
<IconPlayerPlay size={16} />
Run
</Button>
</Flex>
<DashboardFilters
@ -1204,9 +1198,8 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) {
</Box>
<Button
data-testid="add-new-tile-button"
variant="outline"
variant={dashboard?.tiles.length === 0 ? 'primary' : 'secondary'}
mt="sm"
color={dashboard?.tiles.length === 0 ? 'green' : 'gray'}
fw={400}
onClick={onAddTile}
w="100%"

View file

@ -280,8 +280,12 @@ function ResumeLiveTailButton({
handleResumeLiveTail: () => void;
}) {
return (
<Button size="compact-xs" variant="outline" onClick={handleResumeLiveTail}>
<IconBolt size={14} className="text-success me-2" />
<Button
size="compact-xs"
variant="primary"
onClick={handleResumeLiveTail}
leftSection={<IconBolt size={14} />}
>
Resume Live Tail
</Button>
);
@ -295,11 +299,12 @@ function SearchSubmitButton({
return (
<Button
data-testid="search-submit-button"
variant="outline"
variant={isFormStateDirty ? 'primary' : 'secondary'}
type="submit"
color={isFormStateDirty ? 'var(--color-text-success)' : 'gray'}
leftSection={<IconPlayerPlay size={16} />}
style={{ flexShrink: 0 }}
>
<IconPlayerPlay size={16} />
Run
</Button>
);
}
@ -498,8 +503,7 @@ function SaveSearchModalComponent({
{tags.map(tag => (
<Button
key={tag}
variant="light"
color="gray"
variant="secondary"
size="xs"
rightSection={
<ActionIcon
@ -521,8 +525,7 @@ function SaveSearchModalComponent({
<Tags allowCreate values={tags} onChange={setTags}>
<Button
data-testid="add-tag-button"
variant="outline"
color="gray"
variant="secondary"
size="xs"
>
<IconPlus size={14} className="me-1" />
@ -533,8 +536,7 @@ function SaveSearchModalComponent({
</Box>
<Button
data-testid="save-search-submit-button"
variant="outline"
color="green"
variant="primary"
type="submit"
disabled={!formState.isValid}
>
@ -1624,7 +1626,7 @@ function DBSearchPage() {
{!savedSearchId ? (
<Button
data-testid="save-search-button"
variant="default"
variant="secondary"
size="xs"
onClick={onSaveSearch}
style={{ flexShrink: 0 }}
@ -1634,7 +1636,7 @@ function DBSearchPage() {
) : (
<Button
data-testid="update-search-button"
variant="default"
variant="secondary"
size="xs"
onClick={() => {
setSaveSearchModalState('update');
@ -1647,7 +1649,7 @@ function DBSearchPage() {
{!IS_LOCAL_MODE && (
<Button
data-testid="alerts-button"
variant="default"
variant="secondary"
size="xs"
onClick={openAlertModal}
style={{ flexShrink: 0 }}
@ -1664,7 +1666,7 @@ function DBSearchPage() {
>
<Button
data-testid="tags-button"
variant="default"
variant="secondary"
px="xs"
size="xs"
style={{ flexShrink: 0 }}

View file

@ -239,10 +239,10 @@ const AlertForm = ({
)}
</div>
<Group gap="xs">
<Button variant="light" color="gray" onClick={onClose}>
<Button variant="secondary" onClick={onClose}>
Cancel
</Button>
<Button variant="light" type="submit" loading={loading}>
<Button variant="primary" type="submit" loading={loading}>
{defaultValues
? 'Save Alert'
: hasSavedSearch

View file

@ -495,9 +495,8 @@ export default function DOMPlayer({
<ActionIcon
onClick={copy}
title="Copy URL"
variant="light"
variant="secondary"
size="sm"
color="gray"
>
{copied ? <IconCheck size={14} /> : <IconCopy size={14} />}
</ActionIcon>

View file

@ -204,10 +204,14 @@ const DashboardFilterEditForm = ({
</CustomInputWrapper>
<Group justify="space-between" my="xs">
<Button variant="default" onClick={onCancel}>
<Button variant="secondary" onClick={onCancel}>
Cancel
</Button>
<Button type="submit" data-testid="save-filter-button">
<Button
type="submit"
variant="primary"
data-testid="save-filter-button"
>
Save filter
</Button>
</Group>
@ -240,7 +244,7 @@ const EmptyState = ({ onCreateFilter, onClose }: EmptyStateProps) => {
filters will stay with this dashboard.
</Text>
<Button
variant="filled"
variant="primary"
onClick={onCreateFilter}
data-testid="add-filter-button"
>
@ -328,14 +332,14 @@ const DashboardFiltersList = ({
<Group justify="space-between" my="sm">
<Button
variant="default"
variant="secondary"
onClick={onClose}
data-testid="close-filters-button"
>
Close
</Button>
<Button
variant="filled"
variant="primary"
onClick={onAddNew}
data-testid="add-filter-button"
>

View file

@ -183,7 +183,7 @@ export default function InstallInstructionModal({
<span className="ms-2 text-muted">(Logs + Traces)</span>
</div>
<div className="mt-4">
<Button variant="default" onClick={() => onHide()}>
<Button variant="secondary" onClick={() => onHide()}>
Cancel
</Button>
</div>

View file

@ -43,7 +43,7 @@ export default function JoinTeam() {
)}
<div className="text-center mt-4">
<Button
variant="light"
variant="primary"
className="px-6"
type="submit"
data-test-id="submit"

View file

@ -9,6 +9,7 @@ import { useForm, useWatch } from 'react-hook-form';
import { convertDateRangeToGranularityString } from '@hyperdx/common-utils/dist/core/utils';
import { SourceKind, TSource } from '@hyperdx/common-utils/dist/types';
import {
ActionIcon,
Alert,
Badge,
Box,
@ -1276,18 +1277,17 @@ function KubernetesDashboardPage() {
/>
</form>
<Tooltip withArrow label="Refresh dashboard" fz="xs" color="gray">
<Button
<ActionIcon
onClick={refresh}
loading={manualRefreshCooloff}
disabled={manualRefreshCooloff}
color="gray"
variant="outline"
variant="secondary"
title="Refresh dashboard"
aria-label="Refresh dashboard"
px="xs"
size="lg"
>
<IconRefresh size={18} />
</Button>
</ActionIcon>
</Tooltip>
</Group>
</Group>

View file

@ -79,14 +79,14 @@ export default function LandingHeader({
activeKey !== '/register' &&
installation?.isTeamExisting === false && (
<Link href="/register">
<Button variant="outline" color="green" size="sm">
<Button variant="primary" size="sm">
Setup Account
</Button>
</Link>
)}
{isLoggedIn && (
<Link href="/search">
<Button variant="outline" color="green" size="sm">
<Button variant="primary" size="sm">
Go to Search
</Button>
</Link>
@ -124,14 +124,14 @@ export default function LandingHeader({
activeKey !== '/register' &&
installation?.isTeamExisting === false && (
<Link href="/register">
<Button variant="outline" color="green" size="sm" fullWidth>
<Button variant="primary" size="sm" fullWidth>
Setup Account
</Button>
</Link>
)}
{isLoggedIn && (
<Link href="/search">
<Button variant="outline" color="green" size="sm" fullWidth>
<Button variant="primary" size="sm" fullWidth>
Go to Search
</Button>
</Link>

View file

@ -544,7 +544,7 @@ export const SourceMapsFtux = () => {
code.
</Text>
<Link href="https://www.npmjs.com/package/@hyperdx/cli" target="_blank">
<Button size="compact-xs" variant="light" mt="xs">
<Button size="compact-xs" variant="primary" mt="xs">
See docs
</Button>
</Link>

View file

@ -21,6 +21,7 @@ import {
TSource,
} from '@hyperdx/common-utils/dist/types';
import {
ActionIcon,
Box,
Button,
Grid,
@ -1586,31 +1587,36 @@ function ServicesDashboardPage() {
/>
{!IS_LOCAL_MODE && (
<Tooltip withArrow label="Edit Filters" fz="xs" color="gray">
<Button
variant="default"
px="xs"
<ActionIcon
variant="secondary"
onClick={() => setShowFiltersModal(true)}
size="lg"
>
<IconFilterEdit strokeWidth={1} />
</Button>
<IconFilterEdit size={18} />
</ActionIcon>
</Tooltip>
)}
<Tooltip withArrow label="Refresh dashboard" fz="xs" color="gray">
<Button
<ActionIcon
onClick={refresh}
loading={manualRefreshCooloff}
disabled={manualRefreshCooloff}
color="gray"
variant="outline"
variant="secondary"
title="Refresh dashboard"
aria-label="Refresh dashboard"
px="xs"
size="lg"
>
<IconRefresh size={18} />
</Button>
</ActionIcon>
</Tooltip>
<Button variant="outline" type="submit" px="sm">
<IconPlayerPlay size={16} />
<Button
variant="primary"
type="submit"
px="sm"
leftSection={<IconPlayerPlay size={16} />}
style={{ flexShrink: 0 }}
>
Run
</Button>
</Group>
</Group>

View file

@ -7,8 +7,7 @@ import {
SearchConditionLanguage,
TSource,
} from '@hyperdx/common-utils/dist/types';
import { Button } from '@mantine/core';
import { Drawer } from '@mantine/core';
import { ActionIcon, Button, Drawer } from '@mantine/core';
import { notifications } from '@mantine/notifications';
import { IconLink, IconX } from '@tabler/icons-react';
@ -122,7 +121,7 @@ export default function SessionSidePanel({
}}
>
<Button
variant="default"
variant="secondary"
size="sm"
leftSection={<IconLink size={14} />}
style={{ fontSize: '12px' }}
@ -130,14 +129,9 @@ export default function SessionSidePanel({
Share Session
</Button>
</CopyToClipboard>
<Button
variant="default"
size="sm"
onClick={onClose}
style={{ padding: '4px 8px' }}
>
<ActionIcon variant="secondary" size="md" onClick={onClose}>
<IconX size={14} />
</Button>
</ActionIcon>
</div>
</div>
</div>

View file

@ -634,8 +634,7 @@ export default function SessionSubpanel({
<Group align="center" justify="center" gap="xs">
<Tooltip label="Go 15 seconds back" color="gray">
<ActionIcon
variant="filled"
color="gray"
variant="secondary"
size="md"
radius="xl"
onClick={skipBackward}
@ -649,8 +648,7 @@ export default function SessionSubpanel({
color="gray"
>
<ActionIcon
variant="filled"
color="gray"
variant="secondary"
size="lg"
radius="xl"
onClick={togglePlayerState}
@ -664,8 +662,7 @@ export default function SessionSubpanel({
</Tooltip>
<Tooltip label="Skip 15 seconds" color="gray">
<ActionIcon
variant="filled"
color="gray"
variant="secondary"
size="md"
radius="xl"
onClick={skipForward}
@ -678,8 +675,7 @@ export default function SessionSubpanel({
<Group align="center" justify="flex-end" gap="xs">
<Button
size="compact-sm"
color="gray"
variant="light"
variant="secondary"
fw="normal"
rightSection={
skipInactive ? (
@ -695,8 +691,7 @@ export default function SessionSubpanel({
</Button>
<Button
size="compact-sm"
color="gray"
variant="light"
variant="secondary"
fw="normal"
rightSection={
<span className="fw-bold pe-1">{playerSpeed}x</span>

View file

@ -471,8 +471,14 @@ export default function SessionsPage() {
onSearch(range);
}}
/>
<Button variant="outline" type="submit" px="sm">
<IconPlayerPlay size={16} />
<Button
variant="primary"
type="submit"
px="sm"
leftSection={<IconPlayerPlay size={16} />}
style={{ flexShrink: 0 }}
>
Run
</Button>
</Group>
</Flex>

View file

@ -111,7 +111,7 @@ function ConnectionsSection() {
{!isCreatingConnection &&
(IS_LOCAL_MODE ? (connections?.length ?? 0) < 1 : true) && (
<Button
variant="outline"
variant="primary"
onClick={() => setIsCreatingConnection(true)}
>
Add Connection
@ -228,8 +228,7 @@ function TeamNameSection() {
<Button
type="submit"
size="xs"
variant="light"
color="green"
variant="primary"
loading={setTeamName.isPending}
>
Save
@ -237,7 +236,7 @@ function TeamNameSection() {
<Button
type="button"
size="xs"
variant="default"
variant="secondary"
disabled={setTeamName.isPending}
onClick={() => setIsEditingTeamName(false)}
>
@ -251,7 +250,7 @@ function TeamNameSection() {
{hasAdminAccess && (
<Button
size="xs"
variant="default"
variant="secondary"
leftSection={<IconPencil size={16} />}
onClick={() => {
setIsEditingTeamName(true);
@ -412,8 +411,7 @@ function ClickhouseSettingForm({
<Button
type="submit"
size="xs"
variant="light"
color="green"
variant="primary"
loading={updateClickhouseSettings.isPending}
>
Save
@ -421,7 +419,7 @@ function ClickhouseSettingForm({
<Button
type="button"
size="xs"
variant="default"
variant="secondary"
disabled={updateClickhouseSettings.isPending}
onClick={() => {
setIsEditing(false);
@ -441,7 +439,7 @@ function ClickhouseSettingForm({
{hasAdminAccess && (
<Button
size="xs"
variant="default"
variant="secondary"
leftSection={<IconPencil size={16} />}
onClick={() => setIsEditing(true)}
>
@ -593,8 +591,7 @@ function ApiKeysSection() {
)}
{hasAdminAccess && (
<Button
variant="light"
color="red"
variant="danger"
onClick={() => setRotateApiKeyConfirmationModalShow(true)}
>
Rotate API Key
@ -620,7 +617,7 @@ function ApiKeysSection() {
</Text>
<Group justify="end">
<Button
variant="default"
variant="secondary"
className="mt-2 px-4 ms-2 float-end"
size="sm"
onClick={() => setRotateApiKeyConfirmationModalShow(false)}
@ -628,8 +625,7 @@ function ApiKeysSection() {
Cancel
</Button>
<Button
variant="outline"
color="red"
variant="danger"
className="mt-2 px-4 float-end"
size="sm"
onClick={onConfirmUpdateTeamApiKey}

View file

@ -187,8 +187,7 @@ export const UserPreferencesModal = ({
description={
<Group gap={4}>
<Button
variant="light"
color="gray"
variant="secondary"
size="compact-xs"
onClick={() =>
setUserPreference({
@ -199,8 +198,7 @@ export const UserPreferencesModal = ({
Try this
</Button>
<Button
variant="light"
color="gray"
variant="secondary"
size="compact-xs"
onClick={() =>
setUserPreference({

View file

@ -50,8 +50,7 @@ export const ColorSwatchInput = ({
<Popover.Target>
<Button
size="compact-xs"
variant="light"
color="gray"
variant="secondary"
onClick={() => setOpened(o => !o)}
>
{value ? (

View file

@ -250,7 +250,7 @@ export function ConnectionForm({
{!showUpdatePassword && !isNew && (
<Button
data-testid="update-password-button"
variant="outline"
variant="secondary"
onClick={() => {
setShowUpdatePassword(true);
}}
@ -270,7 +270,7 @@ export function ConnectionForm({
{!isNew && (
<Button
data-testid="cancel-password-button"
variant="outline"
variant="secondary"
onClick={() => {
setShowUpdatePassword(false);
resetField('password');
@ -286,7 +286,7 @@ export function ConnectionForm({
<Group gap="xs" justify="flex-start">
<Button
data-testid="connection-save-button"
variant="outline"
variant="primary"
type="submit"
loading={
isNew ? createConnection.isPending : updateConnection.isPending
@ -337,7 +337,7 @@ export function ConnectionForm({
/>
)}
{onClose && showCancelButton && (
<Button variant="outline" onClick={onClose}>
<Button variant="secondary" onClick={onClose}>
Cancel
</Button>
)}

View file

@ -30,6 +30,7 @@ import {
} from '@hyperdx/common-utils/dist/types';
import {
Accordion,
ActionIcon,
Box,
Button,
Center,
@ -1205,7 +1206,7 @@ export default function EditTimeChartForm({
<Button
data-testid="chart-save-button"
loading={isSaving}
variant="outline"
variant="primary"
onClick={handleSubmit(handleSave)}
>
Save
@ -1259,19 +1260,21 @@ export default function EditTimeChartForm({
{activeTab !== 'markdown' && (
<Button
data-testid="chart-run-query-button"
variant="outline"
variant="primary"
type="submit"
onClick={onSubmit}
leftSection={<IconPlayerPlay size={16} />}
style={{ flexShrink: 0 }}
>
<IconPlayerPlay size={16} />
Run
</Button>
)}
{!IS_LOCAL_MODE && !dashboardId && (
<Menu width={250}>
<Menu.Target>
<Button variant="outline" color="gray" px="xs" size="sm">
<IconDotsVertical size={14} />
</Button>
<ActionIcon variant="secondary" size="input-sm">
<IconDotsVertical size={16} />
</ActionIcon>
</Menu.Target>
<Menu.Dropdown>
<Menu.Item

View file

@ -436,12 +436,7 @@ export function DBRowJsonViewer({
leftSection={<IconSearch size={16} />}
/>
{filter && (
<Button
variant="filled"
color="gray"
size="xs"
onClick={() => setFilter('')}
>
<Button variant="secondary" size="xs" onClick={() => setFilter('')}>
Clear
</Button>
)}

View file

@ -1407,8 +1407,7 @@ const DBSearchPageFiltersComponent = ({
})()}
<Button
color="gray"
variant="light"
variant="secondary"
size="compact-xs"
loading={isFacetsFetching}
rightSection={

View file

@ -164,8 +164,7 @@ export default function DBTracePanel({
/>
<Button
ms="sm"
variant="outline"
color="green"
variant="primary"
onClick={traceIdHandleSubmit(({ traceIdExpression }) => {
if (parentSourceData != null) {
updateTableSource({
@ -182,8 +181,7 @@ export default function DBTracePanel({
</Button>
<Button
ms="sm"
variant="outline"
color="gray"
variant="secondary"
onClick={() => setShowTraceIdInput(false)}
size="xs"
>

View file

@ -16,7 +16,7 @@ export const DrawerHeader = React.memo<{
<CloseButton
onClick={onClose}
aria-label="Close modal"
variant="light"
variant="subtle"
size="md"
/>
</Group>

View file

@ -621,7 +621,7 @@ export const ExceptionSubpanel = ({
{stacktraceHiddenRowsCount ? (
<Button
variant="default"
variant="secondary"
size="xs"
m="xs"
onClick={handleStacktraceToggleMoreRows}
@ -650,7 +650,7 @@ export const ExceptionSubpanel = ({
/>
{breadcrumbHiddenRowsCount ? (
<Button
variant="default"
variant="secondary"
size="xs"
m="xs"
onClick={handleBreadcrumbToggleMoreRows}

View file

@ -222,7 +222,7 @@ export function NetworkPropertySubpanel({
});
}}
>
<Button size="xs" variant="light">
<Button size="xs" variant="primary">
<IconTerminal size={14} className="me-2" />
Copy Request as Curl
</Button>

View file

@ -1,6 +1,7 @@
import * as React from 'react';
import { useForm, useWatch } from 'react-hook-form';
import {
ActionIcon,
Button,
Checkbox as MCheckbox,
Drawer,
@ -186,7 +187,7 @@ export const NumberFormatForm: React.FC<{
<Button type="submit" onClick={handleSubmit(onApply)}>
Apply
</Button>
<Button onClick={onClose} variant="default">
<Button onClick={onClose} variant="secondary">
Cancel
</Button>
</Stack>
@ -225,22 +226,19 @@ export const NumberFormatInput: React.FC<{
<Button
onClick={open}
size="compact-sm"
color="dark"
variant="default"
variant="secondary"
leftSection={value?.output && FORMAT_ICONS[value.output]}
>
{value?.output ? FORMAT_NAMES[value.output] : 'Set number format'}
</Button>
{value?.output && (
<Button
size="compact-sm"
color="dark"
variant="default"
px="xs"
<ActionIcon
size="sm"
variant="secondary"
onClick={() => handleApply(undefined)}
>
<IconX size={14} />
</Button>
</ActionIcon>
)}
</Button.Group>
</>

View file

@ -435,7 +435,7 @@ function OnboardingModalComponent({
<Divider label="OR" my="md" />
<Button
data-testid="demo-server-button"
variant="outline"
variant="secondary"
w="100%"
onClick={handleDemoServerClick}
>

View file

@ -191,7 +191,7 @@ function DBSearchHeatmapForm({
</div>
<ActionIcon
w="40px"
variant="outline"
variant="primary"
type="submit"
h="auto"
title="Run"

View file

@ -1,4 +1,4 @@
import { Button, Menu, Text } from '@mantine/core';
import { ActionIcon, Menu } from '@mantine/core';
import { IconDotsVertical, IconForms, IconTrash } from '@tabler/icons-react';
export default function SearchPageActionBar({
@ -11,15 +11,13 @@ export default function SearchPageActionBar({
return (
<Menu width={250}>
<Menu.Target>
<Button
variant="outline"
color="gray"
px="xs"
size="xs"
<ActionIcon
variant="secondary"
style={{ flexShrink: 0 }}
size="input-xs"
>
<IconDotsVertical size={14} />
</Button>
</ActionIcon>
</Menu.Target>
<Menu.Dropdown>

View file

@ -374,9 +374,8 @@ function HighlightedAttributeExpressionsFormRow({
))}
</Grid>
<Button
variant="default"
variant="secondary"
size="sm"
color="gray"
className="align-self-start"
mt={highlightedAttributes.length ? 'sm' : 'md'}
onClick={() => {
@ -439,7 +438,7 @@ function MaterializedViewsFormSection({ control, setValue }: TableModelProps) {
))}
<Button
variant="default"
variant="secondary"
onClick={() => {
appendMaterializedView({
databaseName: databaseName,
@ -752,7 +751,7 @@ function AggregatedColumnsFormSection({
/>
))}
</Grid>
<Button size="sm" variant="default" onClick={addAggregate} mt="lg">
<Button size="sm" variant="secondary" onClick={addAggregate} mt="lg">
<Group>
<IconCirclePlus size={16} />
Add Column

View file

@ -125,8 +125,7 @@ export function SourcesList({
</Text>
<Button
size="xs"
variant="light"
color="red"
variant="danger"
leftSection={<IconRefresh size={14} />}
onClick={handleRetry}
>

View file

@ -122,7 +122,7 @@ export const SpanEventsSubpanel = ({
{hiddenRowsCount ? (
<Button
variant="default"
variant="secondary"
size="xs"
my="sm"
onClick={handleToggleMoreRows}

View file

@ -97,9 +97,8 @@ export const Tags = React.memo(
<Popover.Target>
{children || (
<ActionIcon
variant="filled"
variant="secondary"
size="sm"
color="gray"
style={{ cursor: 'pointer' }}
>
<IconTags size={14} />
@ -156,8 +155,7 @@ export const Tags = React.memo(
<Checkbox label={tag.toUpperCase()} value={tag} size="xs" />
{tags.length >= 2 && (
<Button
variant="filled"
color="gray"
variant="secondary"
size="compact-xs"
fw="normal"
onClick={() => {
@ -176,7 +174,7 @@ export const Tags = React.memo(
<div className="ms-2 fs-8 ">{values.length || 'None'} selected</div>
{values.length >= 1 && (
<Button
variant="default"
variant="secondary"
size="compact-xs"
fw="normal"
onClick={handleClearAll}

View file

@ -215,7 +215,7 @@ export default function TeamMembersSection() {
<Group align="center" justify="space-between">
<div className="fs-7">Team Members</div>
<Button
variant="light"
variant="primary"
leftSection={<IconUserPlus size={16} />}
onClick={() => setTeamInviteModalShow(true)}
>
@ -277,8 +277,7 @@ export default function TeamMembersSection() {
<Group justify="flex-end" gap="8">
<Button
size="compact-sm"
variant="light"
color="red"
variant="danger"
onClick={() =>
setDeleteTeamMemberConfirmationModalData({
mode: 'team',
@ -308,7 +307,7 @@ export default function TeamMembersSection() {
Pending Invite
</Badge>
<CopyToClipboard text={invitation.url}>
<Button size="compact-xs" variant="default" ml="xs">
<Button size="compact-xs" variant="secondary" ml="xs">
📋 Copy URL
</Button>
</CopyToClipboard>
@ -318,8 +317,7 @@ export default function TeamMembersSection() {
<Group justify="flex-end" gap="8">
<Button
size="compact-sm"
variant="light"
color="red"
variant="danger"
onClick={() =>
setDeleteTeamMemberConfirmationModalData({
mode: 'teamInvite',
@ -374,7 +372,7 @@ export default function TeamMembersSection() {
</Text>
<Group justify="flex-end" gap="xs">
<Button
variant="default"
variant="secondary"
onClick={() =>
setDeleteTeamMemberConfirmationModalData({
mode: null,
@ -386,8 +384,7 @@ export default function TeamMembersSection() {
Cancel
</Button>
<Button
variant="outline"
color="red"
variant="danger"
onClick={() =>
deleteTeamMemberConfirmationModalData.id &&
onConfirmDeleteTeamMember(
@ -434,7 +431,11 @@ function InviteTeamMemberForm({
<div className="fs-8">
The invite link will automatically expire after 30 days.
</div>
<Button variant="light" type="submit" disabled={!email || isSubmitting}>
<Button
variant="primary"
type="submit"
disabled={!email || isSubmitting}
>
Send Invite
</Button>
</Stack>

View file

@ -400,15 +400,14 @@ export function WebhookForm({
<Group justify="space-between">
<Group>
<Button
variant="outline"
variant="primary"
type="submit"
loading={saveWebhook.isPending || updateWebhook.isPending}
>
{isEditing ? 'Update Webhook' : 'Add Webhook'}
</Button>
<Button
variant="outline"
color="blue"
variant="secondary"
onClick={form.handleSubmit(handleTestWebhook)}
loading={testWebhook.isPending}
type="button"
@ -416,7 +415,7 @@ export function WebhookForm({
Test Webhook
</Button>
</Group>
<Button variant="outline" color="gray" onClick={onClose} type="reset">
<Button variant="secondary" onClick={onClose} type="reset">
Cancel
</Button>
</Group>

View file

@ -62,9 +62,8 @@ function DeleteWebhookButton({
return (
<Button
color="red"
size="compact-xs"
variant="outline"
variant="danger"
onClick={handleDelete}
loading={deleteWebhook.isPending}
>
@ -187,7 +186,7 @@ export default function WebhooksSection() {
</Stack>
{!isAddWebhookModalOpen ? (
<Button variant="outline" color="gray.4" onClick={openWebhookModal}>
<Button variant="secondary" onClick={openWebhookModal}>
Add Webhook
</Button>
) : (

View file

@ -292,8 +292,7 @@ const TimePickerComponent = ({
<Button
data-testid="time-picker-1h-back"
size="compact-xs"
color="gray"
variant="light"
variant="secondary"
onClick={handleMove.bind(null, { hours: -1 })}
disabled={isLiveMode || isRelative}
>
@ -302,8 +301,7 @@ const TimePickerComponent = ({
<Button
data-testid="time-picker-1h-forward"
size="compact-xs"
color="gray"
variant="light"
variant="secondary"
onClick={handleMove.bind(null, { hours: 1 })}
disabled={isLiveMode || isRelative}
>
@ -455,7 +453,7 @@ const TimePickerComponent = ({
<Button
data-testid="time-picker-apply"
size="compact-sm"
variant="light"
variant="primary"
disabled={!form.isValid() || isRelative}
onClick={handleApply}
>

View file

@ -59,18 +59,16 @@ export const useConfirmModal = () => {
<Button
data-testid="confirm-cancel-button"
size="xs"
variant="outline"
variant="secondary"
onClick={handleClose}
color="Gray"
>
Cancel
</Button>
<Button
data-testid="confirm-confirm-button"
size="xs"
variant="outline"
variant="danger"
onClick={confirm?.onConfirm}
color="red"
>
{confirm?.confirmLabel || 'Confirm'}
</Button>