diff --git a/eslint.config.mjs b/eslint.config.mjs
index ab1841c..2a0310d 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -50,12 +50,12 @@ const UI_ARCHITECTURE_IMPORT_PATHS = [
{
name: "@/components/formPrimitives",
message:
- "Use '@/components/ui' primitives in migrated app surfaces; formPrimitives is a compatibility shim.",
+ "Use '@/components/ui' primitives; formPrimitives has been removed.",
},
{
name: "@/components/ui/buttonPrimitives",
message:
- "Use '@/components/ui' button exports in migrated app surfaces; buttonPrimitives is a compatibility shim.",
+ "Use '@/components/ui' button exports; buttonPrimitives has been removed.",
},
{
name: "@headlessui/react",
diff --git a/src/app/(public)/layout.tsx b/src/app/(public)/layout.tsx
index 7cc7024..4b08f30 100644
--- a/src/app/(public)/layout.tsx
+++ b/src/app/(public)/layout.tsx
@@ -1,7 +1,7 @@
import type { ReactNode } from 'react';
import Link from 'next/link';
import { getResolvedRuntimeConfigForRsc } from '@/lib/server/runtime-config-rsc';
-import { buttonClass } from '@/components/ui/buttonPrimitives';
+import { buttonClass } from '@/components/ui';
import './public.css';
export default async function PublicLayout({ children }: { children: ReactNode }) {
diff --git a/src/app/(public)/page.tsx b/src/app/(public)/page.tsx
index f995997..49ead03 100644
--- a/src/app/(public)/page.tsx
+++ b/src/app/(public)/page.tsx
@@ -2,7 +2,7 @@ import type { CSSProperties } from 'react';
import type { Metadata } from 'next';
import Link from 'next/link';
import { getResolvedRuntimeConfigForRsc } from '@/lib/server/runtime-config-rsc';
-import { buttonClass } from '@/components/ui/buttonPrimitives';
+import { buttonClass } from '@/components/ui';
export const metadata: Metadata = {
title: 'Open Source Read-Along Workspace',
diff --git a/src/app/(public)/privacy/page.tsx b/src/app/(public)/privacy/page.tsx
index 644ade8..30818b5 100644
--- a/src/app/(public)/privacy/page.tsx
+++ b/src/app/(public)/privacy/page.tsx
@@ -1,7 +1,7 @@
import type { Metadata } from 'next';
import Link from 'next/link';
import { headers } from 'next/headers';
-import { buttonClass } from '@/components/ui/buttonPrimitives';
+import { buttonClass } from '@/components/ui';
export const metadata: Metadata = {
title: 'Privacy & Data Usage',
diff --git a/src/components/AudiobookExportModal.tsx b/src/components/AudiobookExportModal.tsx
index 945f006..ead5815 100644
--- a/src/components/AudiobookExportModal.tsx
+++ b/src/components/AudiobookExportModal.tsx
@@ -1,7 +1,7 @@
'use client';
import { Fragment, useState, useRef, useCallback, useEffect, useMemo } from 'react';
-import { Transition, Button, Listbox, ListboxButton, ListboxOptions, ListboxOption, Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/react';
+import { Transition, Listbox, ListboxButton, ListboxOptions, ListboxOption, Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/react';
import { useTimeEstimation } from '@/hooks/useTimeEstimation';
import { ProgressPopup } from '@/components/ProgressPopup';
import { ProgressCard } from '@/components/ProgressCard';
@@ -13,6 +13,7 @@ import { VoicesControlBase } from '@/components/player/VoicesControlBase';
import { ReaderSidebarShell } from '@/components/reader/ReaderSidebarShell';
import { resolveTtsProviderModelPolicy } from '@/lib/shared/tts-provider-policy';
import type { TTSAudiobookChapter, TTSAudiobookFormat } from '@/types/tts';
+import { Button, IconButton, MenuItemClass, cn, listboxButtonClass, listboxOptionClass, listboxPanelClass, menuPanelClass, rangeInputClass } from '@/components/ui';
import {
getAudiobookStatus,
deleteAudiobookChapter,
@@ -560,7 +561,7 @@ export function AudiobookExportModal({
disabled={chapters.length > 0 || settingsLocked}
>
-
+
{format.toUpperCase()}
@@ -572,12 +573,10 @@ export function AudiobookExportModal({
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
-
+
- `relative cursor-pointer select-none py-2 pl-3 pr-4 ${active ? 'bg-surface-sunken text-accent' : 'text-foreground'}`
- }
+ className={({ active, selected }) => cn(listboxOptionClass(active, selected, 'none'), 'py-2')}
>
{({ selected }) => (
@@ -587,9 +586,7 @@ export function AudiobookExportModal({
- `relative cursor-pointer select-none py-2 pl-3 pr-4 ${active ? 'bg-surface-sunken text-accent' : 'text-foreground'}`
- }
+ className={({ active, selected }) => cn(listboxOptionClass(active, selected, 'none'), 'py-2')}
>
{({ selected }) => (
@@ -629,7 +626,7 @@ export function AudiobookExportModal({
step="0.1"
value={nativeSpeed}
onChange={(e) => setNativeSpeed(parseFloat(e.target.value))}
- className="w-full h-1.5 bg-surface-sunken rounded-full appearance-none cursor-pointer [&::-webkit-slider-runnable-track]:bg-surface-sunken [&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-runnable-track]:h-1.5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-accent [&::-webkit-slider-thumb]:-mt-[5px] [&::-webkit-slider-thumb]:shadow-elev-1 [&::-moz-range-track]:bg-surface-sunken [&::-moz-range-track]:rounded-full [&::-moz-range-track]:h-1.5 [&::-moz-range-thumb]:appearance-none [&::-moz-range-thumb]:h-4 [&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-accent [&::-moz-range-thumb]:border-0"
+ className={rangeInputClass}
/>
0.5x
@@ -653,7 +650,7 @@ export function AudiobookExportModal({
step="0.1"
value={postSpeed}
onChange={(e) => setPostSpeed(parseFloat(e.target.value))}
- className="w-full h-1.5 bg-surface-sunken rounded-full appearance-none cursor-pointer [&::-webkit-slider-runnable-track]:bg-surface-sunken [&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-runnable-track]:h-1.5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-accent [&::-webkit-slider-thumb]:-mt-[5px] [&::-webkit-slider-thumb]:shadow-elev-1 [&::-moz-range-track]:bg-surface-sunken [&::-moz-range-track]:rounded-full [&::-moz-range-track]:h-1.5 [&::-moz-range-thumb]:appearance-none [&::-moz-range-thumb]:h-4 [&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-accent [&::-moz-range-thumb]:border-0"
+ className={rangeInputClass}
/>
0.5x
@@ -670,10 +667,9 @@ export function AudiobookExportModal({
@@ -682,10 +678,9 @@ export function AudiobookExportModal({
@@ -694,10 +689,8 @@ export function AudiobookExportModal({
)}
{/* Progress Info */}
@@ -757,14 +750,16 @@ export function AudiobookExportModal({
{chapter.status === 'completed' ? (
) : onRegenerateChapter ? (
-
handleRegenerateChapter(chapter)}
disabled={regeneratingChapter !== null || chapter.status === 'generating' || isGenerating}
- className="inline-flex items-center justify-center rounded-full bg-surface-sunken text-accent hover:bg-accent-wash p-1.5 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent transform transition-transform duration-base ease-standard disabled:opacity-50 disabled:cursor-not-allowed"
+ tone="ghost"
+ size="sm"
+ className="rounded-full bg-surface-sunken text-accent"
title={chapter.status === 'generating' ? 'Generating...' : 'Regenerate this chapter'}
>
-
+
) : (
)}
@@ -782,7 +777,8 @@ export function AudiobookExportModal({
{((onRegenerateChapter && !isGenerating) || chapter.status === 'completed') && (