refactor(ui): decouple settings modal trigger from modal logic
extract SettingsTrigger component to separate modal open control from SettingsModal. update HomeContent to manage modal state and pass open/onOpenChange props. remove redundant trigger button from SettingsModal for improved composability. removes embedded trigger logic from SettingsModal, enabling external state management and better integration with various UI layouts. also cleans up playwright config by centralizing testIgnore for unit tests.
This commit is contained in:
parent
8b45a4f6cd
commit
60c1540744
3 changed files with 45 additions and 31 deletions
|
|
@ -16,6 +16,7 @@ if (process.env.CI === 'true') {
|
|||
*/
|
||||
export default defineConfig({
|
||||
testDir: './tests',
|
||||
testIgnore: '**/unit/**',
|
||||
tsconfig: './tsconfig.json',
|
||||
timeout: 30 * 1000,
|
||||
outputDir: './tests/results',
|
||||
|
|
@ -60,7 +61,6 @@ export default defineConfig({
|
|||
|
||||
{
|
||||
name: 'firefox',
|
||||
testIgnore: '**/unit/**',
|
||||
use: {
|
||||
...devices['Desktop Firefox'],
|
||||
extraHTTPHeaders: { 'x-openreader-test-namespace': 'firefox' },
|
||||
|
|
@ -69,7 +69,6 @@ export default defineConfig({
|
|||
|
||||
{
|
||||
name: 'webkit',
|
||||
testIgnore: '**/unit/**',
|
||||
use: {
|
||||
...devices['Desktop Safari'],
|
||||
extraHTTPHeaders: { 'x-openreader-test-namespace': 'webkit' },
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { DocumentList } from '@/components/doclist/DocumentList';
|
||||
import { SettingsModal } from '@/components/SettingsModal';
|
||||
import { SettingsModal, SettingsTrigger } from '@/components/SettingsModal';
|
||||
import { UserMenu } from '@/components/auth/UserMenu';
|
||||
|
||||
const Brand = () => (
|
||||
|
|
@ -14,20 +15,24 @@ const Brand = () => (
|
|||
</div>
|
||||
);
|
||||
|
||||
const AppActions = () => (
|
||||
<div className="flex flex-col gap-0.5 w-full">
|
||||
<SettingsModal
|
||||
triggerLabel="Settings"
|
||||
className="w-full justify-start gap-2 px-2 py-1 text-[12px] border-transparent hover:border-accent"
|
||||
/>
|
||||
<UserMenu variant="sidebar" />
|
||||
</div>
|
||||
);
|
||||
|
||||
export function HomeContent() {
|
||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||
|
||||
const appActions = (
|
||||
<div className="flex flex-col gap-0.5 w-full">
|
||||
<SettingsTrigger
|
||||
triggerLabel="Settings"
|
||||
className="w-full justify-start gap-2 px-2 py-1 text-[12px] border-transparent hover:border-accent"
|
||||
onOpen={() => setSettingsOpen(true)}
|
||||
/>
|
||||
<UserMenu variant="sidebar" />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="w-full h-full">
|
||||
<DocumentList brand={<Brand />} appActions={<AppActions />} />
|
||||
<DocumentList brand={<Brand />} appActions={appActions} />
|
||||
<SettingsModal open={settingsOpen} onOpenChange={setSettingsOpen} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,19 +130,42 @@ const SIDEBAR_SECTIONS: SidebarSection[] = [
|
|||
|
||||
type AdminSubTab = 'providers' | 'features';
|
||||
|
||||
export function SettingsModal({
|
||||
export function SettingsTrigger({
|
||||
className = '',
|
||||
triggerLabel,
|
||||
onOpen,
|
||||
}: {
|
||||
className?: string;
|
||||
triggerLabel?: string;
|
||||
onOpen: () => void;
|
||||
}) {
|
||||
return (
|
||||
<Button
|
||||
onClick={onOpen}
|
||||
className={`inline-flex items-center py-1 px-2 rounded-md border border-offbase bg-base text-foreground text-xs hover:bg-offbase hover:text-accent transition-transform transition-colors duration-200 ease-out hover:scale-[1.01] ${className}`}
|
||||
aria-label="Settings"
|
||||
tabIndex={0}
|
||||
>
|
||||
<SettingsIcon className="w-4 h-4 transition-transform duration-200 ease-out hover:scale-[1.01] hover:rotate-45" />
|
||||
{triggerLabel && <span className="ml-2">{triggerLabel}</span>}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
export function SettingsModal({
|
||||
open,
|
||||
onOpenChange,
|
||||
}: {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}) {
|
||||
const runtimeConfig = useRuntimeConfig();
|
||||
const enableDestructiveDelete = runtimeConfig.enableDestructiveDeleteActions;
|
||||
const showAllProviderModels = runtimeConfig.showAllProviderModels;
|
||||
const enableTTSProvidersTab = runtimeConfig.enableTtsProvidersTab;
|
||||
const restrictUserApiKeys = runtimeConfig.restrictUserApiKeys;
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const isOpen = open;
|
||||
const setIsOpen = onOpenChange;
|
||||
const [isChangelogOpen, setIsChangelogOpen] = useState(false);
|
||||
const [activeSection, setActiveSection] = useState<SectionId>(enableTTSProvidersTab ? 'api' : 'theme');
|
||||
|
||||
|
|
@ -215,7 +238,7 @@ export function SettingsModal({
|
|||
if (changelogOpenSignal <= 0) return;
|
||||
setIsOpen(true);
|
||||
setIsChangelogOpen(true);
|
||||
}, [changelogOpenSignal]);
|
||||
}, [changelogOpenSignal, setIsOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
setLocalApiKey(apiKey);
|
||||
|
|
@ -409,7 +432,7 @@ export function SettingsModal({
|
|||
} else {
|
||||
setCustomModelInput('');
|
||||
}
|
||||
}, [apiKey, baseUrl, providerRef, providerType, ttsModel, ttsInstructions, ttsModels]);
|
||||
}, [apiKey, baseUrl, providerRef, providerType, ttsModel, ttsInstructions, ttsModels, setIsOpen]);
|
||||
|
||||
const [systemIsDark, setSystemIsDark] = useState(
|
||||
typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
|
|
@ -489,19 +512,6 @@ export function SettingsModal({
|
|||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setIsOpen(true);
|
||||
setIsChangelogOpen(false);
|
||||
}}
|
||||
className={`inline-flex items-center py-1 px-2 rounded-md border border-offbase bg-base text-foreground text-xs hover:bg-offbase hover:text-accent transition-transform transition-colors duration-200 ease-out hover:scale-[1.01] ${className}`}
|
||||
aria-label="Settings"
|
||||
tabIndex={0}
|
||||
>
|
||||
<SettingsIcon className="w-4 h-4 transition-transform duration-200 ease-out hover:scale-[1.01] hover:rotate-45" />
|
||||
{triggerLabel && <span className="ml-2">{triggerLabel}</span>}
|
||||
</Button>
|
||||
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog
|
||||
as="div"
|
||||
|
|
|
|||
Loading…
Reference in a new issue