Skip to content
Merged
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
14 changes: 5 additions & 9 deletions apps/ui/src/components/AnalysisResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,7 @@ import { TranscriptionPianorollBlock } from './TranscriptionPianorollBlock';
import { Mt3TranscriptionPanel } from './Mt3TranscriptionPanel';
import { StemListeningNotesPanel } from './StemListeningNotesPanel';
import { hasStemListeningNotesContent } from '../services/sessionMusician';
import {
StatusBadge,
TokenBadgeList,
} from './MeasurementPrimitives';
import { Button, DeviceRack, MetricBar, MetricTile, Pill, SectionHeader } from './ui';
import { Button, DeviceRack, MetricBar, MetricTile, Pill, SectionHeader, TokenBadgeList } from './ui';
import { PhaseSourceBadge } from './PhaseSourceBadge';
import { StickyNav, type StickyNavSection } from './StickyNav';
import { CitationBlock, CitationHeadline } from './CitationBlock';
Expand Down Expand Up @@ -1048,7 +1044,7 @@ export function AnalysisResults({
icon={<Clock className="w-3.5 h-3.5 text-accent/60" />}
label="METER"
value={phase1.timeSignature}
footer={<StatusBadge label={meterStatusLabel(phase1)} tone="muted" compact />}
footer={<Pill tone="neutral" size="xs">{meterStatusLabel(phase1)}</Pill>}
/>

{/* CHARACTER — genre primary, characteristic pills secondary */}
Expand All @@ -1066,7 +1062,7 @@ export function AnalysisResults({
items={[
{ label: phase1.genreDetail.genreFamily, tone: 'accent' },
...(phase1.genreDetail.secondaryGenre
? [{ label: phase1.genreDetail.secondaryGenre, tone: 'muted' as const }]
? [{ label: phase1.genreDetail.secondaryGenre, tone: 'neutral' as const }]
: []),
]}
/>
Expand Down Expand Up @@ -1418,7 +1414,7 @@ export function AnalysisResults({
Instruments
</p>
<TokenBadgeList
items={styleProfile.instruments.map((item) => ({ label: item, tone: 'muted' as const }))}
items={styleProfile.instruments.map((item) => ({ label: item, tone: 'neutral' as const }))}
/>
</div>
)}
Expand All @@ -1428,7 +1424,7 @@ export function AnalysisResults({
Production Techniques
</p>
<TokenBadgeList
items={styleProfile.productionTechniques.map((item) => ({ label: item, tone: 'violet' as const }))}
items={styleProfile.productionTechniques.map((item) => ({ label: item, tone: 'neutral' as const }))}
/>
</div>
)}
Expand Down
19 changes: 7 additions & 12 deletions apps/ui/src/components/MixDoctorPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import React from 'react';
import type { MixDoctorReport, MixDynamicsIssue, MixIssue } from '../services/mixDoctor';
import {
DeltaBadge,
StatusBadge,
} from './MeasurementPrimitives';
import { DataTable, MetricTile, Panel } from './ui';
import { DataTable, DeltaBadge, MetricTile, Panel, Pill } from './ui';
import { getTextRoleClassName } from '../utils/displayText';

interface MixDoctorPanelProps {
Expand Down Expand Up @@ -59,17 +55,16 @@ export function MixDoctorPanel({ report }: MixDoctorPanelProps) {
accent="accent"
label="Target Genre"
value={report.genreName}
footer={<StatusBadge label={report.genreId} tone="muted" compact />}
footer={<Pill tone="neutral" size="xs">{report.genreId}</Pill>}
/>
<MetricTile
size="md"
accent="accent"
label="Health Score"
value={
<StatusBadge
label={`${report.overallScore}/100`}
tone={toneForScore(report.overallScore)}
/>
<Pill tone={toneForScore(report.overallScore)}>
{`${report.overallScore}/100`}
</Pill>
}
/>
<MetricTile
Expand Down Expand Up @@ -111,7 +106,7 @@ export function MixDoctorPanel({ report }: MixDoctorPanelProps) {
},
].map((item) => (
<Panel key={item.label} variant="surface" padding="md">
<StatusBadge label={item.label} tone={item.tone} compact />
<Pill tone={item.tone} size="xs">{item.label}</Pill>
<p className="mt-2 text-sm leading-5 text-text-primary">{item.message}</p>
</Panel>
))}
Expand Down Expand Up @@ -176,7 +171,7 @@ export function MixDoctorPanel({ report }: MixDoctorPanelProps) {
label: 'Issue',
render: (row) => (
<div className="flex justify-start">
<StatusBadge label={row.issue} tone={toneForMixIssue(row.issue)} compact />
<Pill tone={toneForMixIssue(row.issue)} size="xs">{row.issue}</Pill>
</div>
),
},
Expand Down
65 changes: 65 additions & 0 deletions apps/ui/src/components/ui/DeltaBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React from 'react';

import { Pill } from './Pill';
import type { Tone } from './variants';

export interface DeltaBadgeProps {
value: number | null | undefined;
unit?: string;
decimals?: number;
okThreshold: number;
warnThreshold: number;
invert?: boolean;
showSign?: boolean;
className?: string;
}

function formatSignedNumber(value: number, decimals: number, showSign: boolean): string {
const abs = Math.abs(value).toFixed(decimals);
if (!showSign) return value.toFixed(decimals);
return `${value >= 0 ? '+' : '-'}${abs}`;
}

/**
* Threshold-driven delta chip. Renders a {@link Pill} whose tone reflects how
* far `value` sits from zero relative to the ok/warn thresholds (or the inverse
* when `invert`). Non-finite values render an `n/a` neutral pill. Migrated from
* the retired MeasurementPrimitives layer onto the canonical token palette.
*/
export function DeltaBadge({
value,
unit,
decimals = 1,
okThreshold,
warnThreshold,
invert = false,
showSign = true,
className,
}: DeltaBadgeProps) {
if (typeof value !== 'number' || !Number.isFinite(value)) {
return (
<Pill tone="neutral" size="xs" className={className}>
n/a
</Pill>
);
}

const magnitude = Math.abs(value);
const tone: Tone =
magnitude <= okThreshold
? invert
? 'error'
: 'success'
: magnitude <= warnThreshold
? 'warning'
: invert
? 'success'
: 'error';
const label = `${formatSignedNumber(value, decimals, showSign)}${unit ? ` ${unit}` : ''}`;

return (
<Pill tone={tone} size="xs" className={className}>
{label}
</Pill>
);
}
28 changes: 28 additions & 0 deletions apps/ui/src/components/ui/TokenBadgeList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';

import { cn } from './cn';
import { Pill } from './Pill';
import type { Tone } from './variants';

export interface TokenBadgeListProps {
items: Array<{ label: string; tone?: Tone }>;
className?: string;
}

/**
* A wrapped row of {@link Pill} chips for tag/token lists (genres, instruments,
* production techniques, …). Migrated from the retired MeasurementPrimitives
* layer; tones are restricted to the token palette (off-palette tones collapse
* to `neutral` at the call site).
*/
export function TokenBadgeList({ items, className }: TokenBadgeListProps) {
return (
<div className={cn('flex flex-wrap gap-1.5', className)}>
{items.map((item, index) => (
<Pill key={`${item.label}-${index}`} tone={item.tone ?? 'neutral'} size="sm">
{item.label}
</Pill>
))}
</div>
);
}
2 changes: 2 additions & 0 deletions apps/ui/src/components/ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export {
type DataTableColumn,
type DataTableProps,
} from './DataTable';
export { DeltaBadge, type DeltaBadgeProps } from './DeltaBadge';
export {
DeviceRack,
deviceRackVariants,
Expand Down Expand Up @@ -41,4 +42,5 @@ export {
type SignalStageStatus,
} from './SignalChain';
export { TimeReadout, type TimeReadoutProps } from './TimeReadout';
export { TokenBadgeList, type TokenBadgeListProps } from './TokenBadgeList';
export { Tooltip, TooltipProvider, type TooltipProps } from './Tooltip';
6 changes: 3 additions & 3 deletions apps/ui/tests/services/mixDoctorPanel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,9 @@ describe('MixDoctorPanel', () => {
);

expect(html).toContain('86/100');
expect(html).toContain('border-success/30 bg-success/10');
expect(html).toContain('border-warning/30 bg-warning/10');
expect(html).toContain('border-error/30 bg-error/10');
expect(html).toContain('bg-success/20');
expect(html).toContain('bg-warning/20');
expect(html).toContain('bg-error/20');
expect(html).toContain('-1.8');
});

Expand Down
Loading