diff --git a/webui/src/components/form/form.module.css b/webui/src/components/form/form.module.css
index ae8998b0..31eff1a8 100644
--- a/webui/src/components/form/form.module.css
+++ b/webui/src/components/form/form.module.css
@@ -449,48 +449,6 @@
}
}
-.badge {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- min-width: 2ch;
- padding: 1px 7px;
- border: 1px solid transparent;
- border-radius: 999px;
- font-size: 0.74rem;
- font-weight: 700;
- line-height: 1.2;
- white-space: nowrap;
- vertical-align: middle;
- color: rgba(255, 255, 255, 0.45);
- background: rgba(255, 255, 255, 0.05);
- border-color: rgba(255, 255, 255, 0.08);
-
- &[data-tone='info'] {
- color: rgb(var(--accent-light-rgb));
- background: rgba(var(--accent-rgb), 0.12);
- border-color: rgba(var(--accent-light-rgb), 0.12);
- }
-
- &[data-tone='success'] {
- color: #4ade80;
- background: rgba(74, 222, 128, 0.12);
- border-color: rgba(74, 222, 128, 0.12);
- }
-
- &[data-tone='warning'] {
- color: #fbbf24;
- background: rgba(251, 191, 36, 0.12);
- border-color: rgba(251, 191, 36, 0.12);
- }
-
- &[data-tone='danger'] {
- color: #f87171;
- background: rgba(248, 113, 113, 0.12);
- border-color: rgba(248, 113, 113, 0.12);
- }
-}
-
.button {
display: inline-flex;
align-items: center;
@@ -576,7 +534,7 @@
color: rgba(0, 0, 0, 0.55);
}
- &[data-variant='primary'] .badge {
+ &[data-variant='primary'] [data-slot='badge'] {
color: #fff;
background: rgba(0, 0, 0, 0.18);
border-color: rgba(0, 0, 0, 0.22);
diff --git a/webui/src/components/form/form.test.tsx b/webui/src/components/form/form.test.tsx
index 21e6abb3..77b7677b 100644
--- a/webui/src/components/form/form.test.tsx
+++ b/webui/src/components/form/form.test.tsx
@@ -3,7 +3,6 @@ import { useState } from 'react';
import { describe, expect, it } from 'vitest';
import {
- Badge,
Button,
Checkbox,
FormActions,
@@ -115,8 +114,6 @@ function FormDemo() {
/>
- 12
-
@@ -170,7 +167,6 @@ describe('form primitives', () => {
fireEvent.click(highPriority);
expect(highPriority).toHaveAttribute('aria-pressed', 'true');
- expect(screen.getByText('12')).toHaveAttribute('data-tone', 'warning');
expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Save' })).toHaveAttribute('data-variant', 'primary');
});
diff --git a/webui/src/components/form/form.tsx b/webui/src/components/form/form.tsx
index 06cf217d..9e894d43 100644
--- a/webui/src/components/form/form.tsx
+++ b/webui/src/components/form/form.tsx
@@ -283,22 +283,6 @@ export const OptionButton = forwardRef(fun
);
});
-type BaseBadgeProps = ComponentPropsWithoutRef<'span'>;
-
-export type BadgeTone = 'neutral' | 'info' | 'success' | 'warning' | 'danger';
-
-export type BadgeProps = Omit & {
- className?: string;
- tone?: BadgeTone;
-};
-
-export const Badge = forwardRef(function Badge(
- { className, tone = 'neutral', ...props },
- ref,
-) {
- return ;
-});
-
type BaseButtonProps = ComponentPropsWithoutRef;
export type ButtonProps = Omit & {
diff --git a/webui/src/components/form/index.ts b/webui/src/components/form/index.ts
index 0f6ffb0d..f9a268a6 100644
--- a/webui/src/components/form/index.ts
+++ b/webui/src/components/form/index.ts
@@ -1,6 +1,5 @@
export {
Button,
- Badge,
FormActions,
FormError,
FormField,
diff --git a/webui/src/components/primitives/primitives.module.css b/webui/src/components/primitives/primitives.module.css
index 23914559..f585d5e8 100644
--- a/webui/src/components/primitives/primitives.module.css
+++ b/webui/src/components/primitives/primitives.module.css
@@ -43,3 +43,45 @@
background: rgba(255, 120, 120, 0.08);
color: rgba(255, 214, 214, 0.94);
}
+
+.badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 2ch;
+ padding: 1px 7px;
+ border: 1px solid transparent;
+ border-radius: 999px;
+ font-size: 0.74rem;
+ font-weight: 700;
+ line-height: 1.2;
+ white-space: nowrap;
+ vertical-align: middle;
+ color: rgba(255, 255, 255, 0.45);
+ background: rgba(255, 255, 255, 0.05);
+ border-color: rgba(255, 255, 255, 0.08);
+}
+
+.badge[data-tone='info'] {
+ color: rgb(var(--accent-light-rgb));
+ background: rgba(var(--accent-rgb), 0.12);
+ border-color: rgba(var(--accent-light-rgb), 0.12);
+}
+
+.badge[data-tone='success'] {
+ color: #4ade80;
+ background: rgba(74, 222, 128, 0.12);
+ border-color: rgba(74, 222, 128, 0.12);
+}
+
+.badge[data-tone='warning'] {
+ color: #fbbf24;
+ background: rgba(251, 191, 36, 0.12);
+ border-color: rgba(251, 191, 36, 0.12);
+}
+
+.badge[data-tone='danger'] {
+ color: #f87171;
+ background: rgba(248, 113, 113, 0.12);
+ border-color: rgba(248, 113, 113, 0.12);
+}
diff --git a/webui/src/components/primitives/primitives.test.tsx b/webui/src/components/primitives/primitives.test.tsx
index add08aeb..9f5d6e2c 100644
--- a/webui/src/components/primitives/primitives.test.tsx
+++ b/webui/src/components/primitives/primitives.test.tsx
@@ -1,7 +1,7 @@
import { render, screen } from '@testing-library/react';
import { describe, expect, it } from 'vitest';
-import { Notice, Show } from './primitives';
+import { Badge, Notice, Show } from './primitives';
describe('Show', () => {
it('renders children when the condition is true', () => {
@@ -50,3 +50,18 @@ describe('Notice', () => {
expect(screen.getByRole('note')).toHaveAttribute('data-tone', 'warning');
});
});
+
+describe('Badge', () => {
+ it('renders with neutral styling by default', () => {
+ render(12);
+
+ expect(screen.getByText('12')).toHaveAttribute('data-slot', 'badge');
+ expect(screen.getByText('12')).toHaveAttribute('data-tone', 'neutral');
+ });
+
+ it('supports tone overrides', () => {
+ render(12);
+
+ expect(screen.getByText('12')).toHaveAttribute('data-tone', 'warning');
+ });
+});
diff --git a/webui/src/components/primitives/primitives.tsx b/webui/src/components/primitives/primitives.tsx
index 1fec52b9..8972632e 100644
--- a/webui/src/components/primitives/primitives.tsx
+++ b/webui/src/components/primitives/primitives.tsx
@@ -23,6 +23,30 @@ export function Show({ fallback = null, children, when }: ShowProps) {
return <>{children}>;
}
+type BaseBadgeProps = ComponentPropsWithoutRef<'span'>;
+
+export type BadgeTone = 'neutral' | 'info' | 'success' | 'warning' | 'danger';
+
+export type BadgeProps = Omit & {
+ className?: string;
+ tone?: BadgeTone;
+};
+
+export const Badge = forwardRef(function Badge(
+ { className, tone = 'neutral', ...props },
+ ref,
+) {
+ return (
+
+ );
+});
+
export type NoticeTone = 'neutral' | 'info' | 'success' | 'warning' | 'danger';
export type NoticeProps = Omit, 'className'> & {
diff --git a/webui/src/routes/import/-ui/auto-import-tab.tsx b/webui/src/routes/import/-ui/auto-import-tab.tsx
index c1576442..761a08df 100644
--- a/webui/src/routes/import/-ui/auto-import-tab.tsx
+++ b/webui/src/routes/import/-ui/auto-import-tab.tsx
@@ -3,7 +3,6 @@ import clsx from 'clsx';
import { useEffect, useState } from 'react';
import {
- Badge,
Button,
OptionButton,
OptionButtonGroup,
@@ -11,6 +10,7 @@ import {
Select,
Switch,
} from '@/components/form/form';
+import { Badge } from '@/components/primitives';
import type {
ImportAutoFilter,
diff --git a/webui/src/routes/import/-ui/singles-import-tab.tsx b/webui/src/routes/import/-ui/singles-import-tab.tsx
index a636bdee..7aac6728 100644
--- a/webui/src/routes/import/-ui/singles-import-tab.tsx
+++ b/webui/src/routes/import/-ui/singles-import-tab.tsx
@@ -1,8 +1,8 @@
import clsx from 'clsx';
import { useEffect } from 'react';
-import { Badge, Button, Checkbox, TextInput } from '@/components/form/form';
-import { Notice } from '@/components/primitives';
+import { Button, Checkbox, TextInput } from '@/components/form/form';
+import { Badge, Notice } from '@/components/primitives';
import type { SingleSearchState } from '../-import.store';
import type { ImportTrackResult } from '../-import.types';