feat(11-05): visual consistency pass — typography, focus-visible, hover/active states, skeleton cleanup

- Normalize page titles to text-2xl font-semibold (was font-bold in TopicsView, DocumentView, SharedView)
- Normalize section titles to text-lg font-semibold (was text-xl in SettingsPreferencesTab, SettingsAiTab, SettingsCloudTab, BackupCodesDisplay, CloudCredentialModal)
- Normalize panel headings to text-sm font-semibold (SettingsAccountTab, DocumentView)
- Normalize AdminOverviewView page title from text-xl to text-2xl font-semibold
- Replace decorative sidebar skeleton inline styles with static Tailwind width classes (w-12/w-16/w-20)
- Add focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1 to all interactive buttons across the app
- Add active:bg-* states to primary and secondary buttons for coherent press feedback
- Update nav-link scoped CSS in AppSidebar and AdminSidebar to include active:bg-gray-200 and focus-visible ring
- Add 7 new tests: VISUAL-01 skeleton class invariant, VISUAL-04 focus-visible invariant, VISUAL-03 typography invariant (36 test files, 270 tests pass)
This commit is contained in:
curo1305
2026-06-16 21:48:56 +02:00
parent deea237033
commit 2af5b7c313
21 changed files with 460 additions and 80 deletions
+10 -10
View File
@@ -1,7 +1,7 @@
<template>
<div class="p-8 max-w-4xl mx-auto">
<!-- Back -->
<button @click="$router.back()" class="text-sm text-indigo-600 hover:underline mb-6 flex items-center gap-1">
<button @click="$router.back()" class="text-sm text-indigo-600 hover:underline mb-6 flex items-center gap-1 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1 rounded">
Back
</button>
@@ -12,7 +12,7 @@
<!-- Header -->
<div class="flex items-start justify-between gap-4 mb-6">
<div>
<h2 class="text-2xl font-bold text-gray-900 break-all">{{ doc.original_name }}</h2>
<h2 class="text-2xl font-semibold text-gray-900 break-all">{{ doc.original_name }}</h2>
<p class="text-sm text-gray-400 mt-1">
Uploaded {{ formatDate(doc.created_at) }} · {{ formatSize(doc.size_bytes) }} · {{ doc.mime_type }}
</p>
@@ -22,13 +22,13 @@
<button
v-if="isPdf"
@click="openPdf"
class="text-sm px-3 py-1.5 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors"
class="text-sm px-3 py-1.5 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 active:bg-indigo-800 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1"
>
{{ pdfOpenMode === 'in_app' ? 'Preview' : 'Open' }}
</button>
<button
@click="confirmDelete"
class="text-sm text-red-500 hover:text-red-700"
class="text-sm text-red-500 hover:text-red-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-500 focus-visible:ring-offset-1 rounded"
>Delete</button>
</div>
</div>
@@ -36,19 +36,19 @@
<!-- Topics -->
<div class="bg-white border border-gray-200 rounded-xl p-5 mb-5">
<div class="flex items-center justify-between mb-3">
<h3 class="font-semibold text-gray-800">Topics</h3>
<h3 class="text-sm font-semibold text-gray-800">Topics</h3>
<div class="flex gap-2">
<button
@click="reclassify"
:disabled="classifying"
class="text-xs px-3 py-1.5 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors disabled:opacity-50"
class="text-xs px-3 py-1.5 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 active:bg-indigo-800 transition-colors disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1"
>
{{ classifying ? 'Classifying…' : 'Re-classify' }}
</button>
<button
@click="suggestTopics"
:disabled="suggesting"
class="text-xs px-3 py-1.5 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors disabled:opacity-50"
class="text-xs px-3 py-1.5 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 active:bg-gray-100 transition-colors disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1"
>
{{ suggesting ? 'Suggesting…' : 'Suggest Topics' }}
</button>
@@ -97,7 +97,7 @@
<!-- Extracted text -->
<div class="bg-white border border-gray-200 rounded-xl p-5">
<h3 class="font-semibold text-gray-800 mb-3">Extracted Text</h3>
<h3 class="text-sm font-semibold text-gray-800 mb-3">Extracted Text</h3>
<pre class="text-xs text-gray-600 whitespace-pre-wrap font-mono bg-gray-50 rounded-lg p-4 max-h-96 overflow-y-auto">{{ doc.extracted_text || '(no text extracted)' }}</pre>
</div>
</template>
@@ -132,11 +132,11 @@
<div class="flex gap-2 justify-end">
<button
@click="cancelCloudDeleteWarning"
class="border border-gray-300 text-gray-700 text-sm px-4 py-2 rounded-lg hover:bg-gray-50 transition-colors"
class="border border-gray-300 text-gray-700 text-sm px-4 py-2 rounded-lg hover:bg-gray-50 active:bg-gray-100 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1"
>Cancel</button>
<button
@click="confirmRemoveOnly"
class="bg-red-600 hover:bg-red-700 text-white text-sm px-4 py-2 rounded-lg transition-colors"
class="bg-red-600 hover:bg-red-700 active:bg-red-800 text-white text-sm px-4 py-2 rounded-lg transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-500 focus-visible:ring-offset-1"
>Remove from app</button>
</div>
</div>
+3 -3
View File
@@ -18,7 +18,7 @@
<button
@click="oauthSuccessProvider = null"
aria-label="Dismiss notification"
class="text-gray-400 hover:text-gray-600 shrink-0"
class="text-gray-400 hover:text-gray-600 shrink-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1 rounded"
>
<AppIcon name="x" class="w-4 h-4" />
</button>
@@ -30,7 +30,7 @@
v-for="tab in tabs"
:key="tab.id"
@click="activeTab = tab.id"
class="px-4 py-2 text-sm font-semibold border-b-2 transition-colors"
class="px-4 py-2 text-sm font-semibold border-b-2 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1 rounded-t"
:class="activeTab === tab.id
? 'text-indigo-600 border-indigo-600'
: 'text-gray-500 hover:text-gray-700 border-transparent'"
@@ -64,7 +64,7 @@
<button
@click="oauthError = null"
aria-label="Dismiss error"
class="text-red-400 hover:text-red-600 shrink-0"
class="text-red-400 hover:text-red-600 shrink-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-500 focus-visible:ring-offset-1 rounded"
>
<AppIcon name="x" class="w-4 h-4" />
</button>
+1 -1
View File
@@ -2,7 +2,7 @@
<div class="p-8 max-w-4xl mx-auto">
<BreadcrumbBar :segments="[{ label: 'Shared with me' }]" :show-root="false" class="mb-4" />
<h2 class="text-2xl font-bold text-gray-900 mb-1">Shared with me</h2>
<h2 class="text-2xl font-semibold text-gray-900 mb-1">Shared with me</h2>
<p class="text-gray-500 text-sm mb-6">Documents other users have shared with you.</p>
<div v-if="loading" class="text-sm text-gray-400">Loading</div>
+3 -3
View File
@@ -3,7 +3,7 @@
<!-- Header -->
<div class="flex items-center justify-between mb-6">
<div>
<h2 class="text-2xl font-bold text-gray-900">
<h2 class="text-2xl font-semibold text-gray-900">
{{ activeTopic ? activeTopic : 'All Topics' }}
</h2>
<p class="text-gray-500 text-sm mt-0.5">
@@ -13,7 +13,7 @@
<button
v-if="activeTopic"
@click="$router.push('/topics')"
class="text-sm text-indigo-600 hover:underline"
class="text-sm text-indigo-600 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1 rounded"
>
All Topics
</button>
@@ -31,7 +31,7 @@
v-for="topic in topicsStore.topics"
:key="topic.id"
:to="`/topics/${encodeURIComponent(topic.name)}`"
class="bg-white border border-gray-200 rounded-xl p-4 hover:border-indigo-300 hover:shadow-sm transition-all"
class="bg-white border border-gray-200 rounded-xl p-4 hover:border-indigo-300 hover:shadow-sm transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1"
>
<div class="flex items-center gap-2 mb-2">
<span class="w-3 h-3 rounded-full" :style="{ backgroundColor: topic.color }"></span>
@@ -0,0 +1,171 @@
/**
* VISUAL-01 / VISUAL-03: Typography normalization invariants
*
* Tests that page-level h2 headings use text-2xl font-semibold (not font-bold),
* and section-level h3 headings inside cards use text-sm or text-lg font-semibold (not text-xl).
*/
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import { setActivePinia, createPinia } from 'pinia'
import { createRouter, createMemoryHistory } from 'vue-router'
vi.mock('../../api/client.js', () => ({
getSharedWithMe: vi.fn().mockResolvedValue([]),
listFolders: vi.fn().mockResolvedValue({ items: [] }),
getDocument: vi.fn().mockResolvedValue(null),
getMyPreferences: vi.fn().mockResolvedValue({ pdf_open_mode: 'new_tab' }),
listTopics: vi.fn().mockResolvedValue({ topics: [] }),
getMyQuota: vi.fn().mockResolvedValue({ used_bytes: 0, limit_bytes: 104857600 }),
}))
function makeRouter(extraRoutes = []) {
return createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/', component: { template: '<div/>' } },
{ path: '/topics', component: { template: '<div/>' } },
{ path: '/shared', component: { template: '<div/>' } },
{ path: '/settings', component: { template: '<div/>' } },
...extraRoutes,
],
})
}
describe('VISUAL-03: SharedView page title uses font-semibold (not font-bold)', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.resetModules()
})
it('h2 has text-2xl font-semibold and not font-bold', async () => {
vi.doMock('../../api/client.js', () => ({
getSharedWithMe: vi.fn().mockResolvedValue([]),
listFolders: vi.fn().mockResolvedValue({ items: [] }),
getDocument: vi.fn().mockResolvedValue(null),
getMyPreferences: vi.fn().mockResolvedValue({ pdf_open_mode: 'new_tab' }),
listTopics: vi.fn().mockResolvedValue({ topics: [] }),
getMyQuota: vi.fn().mockResolvedValue({ used_bytes: 0, limit_bytes: 104857600 }),
}))
setActivePinia(createPinia())
const router = makeRouter()
await router.push('/shared')
await router.isReady()
const SharedView = (await import('../SharedView.vue')).default
const wrapper = mount(SharedView, {
global: {
plugins: [router],
stubs: { BreadcrumbBar: true, EmptyState: true, AppIcon: true },
},
})
await flushPromises()
const h2 = wrapper.find('h2')
expect(h2.exists()).toBe(true)
const cls = h2.attributes('class') || ''
expect(cls).toContain('font-semibold')
expect(cls).not.toContain('font-bold')
expect(cls).toContain('text-2xl')
wrapper.unmount()
})
})
describe('VISUAL-03: TopicsView page title uses font-semibold (not font-bold)', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.resetModules()
})
it('h2 has text-2xl font-semibold and not font-bold', async () => {
vi.doMock('../../api/client.js', () => ({
getSharedWithMe: vi.fn().mockResolvedValue([]),
listFolders: vi.fn().mockResolvedValue({ items: [] }),
getDocument: vi.fn().mockResolvedValue(null),
getMyPreferences: vi.fn().mockResolvedValue({ pdf_open_mode: 'new_tab' }),
listTopics: vi.fn().mockResolvedValue({ topics: [] }),
getMyQuota: vi.fn().mockResolvedValue({ used_bytes: 0, limit_bytes: 104857600 }),
}))
setActivePinia(createPinia())
const router = makeRouter([
{ path: '/topics/:name?', component: { template: '<div/>' } },
])
await router.push('/topics')
await router.isReady()
const TopicsView = (await import('../TopicsView.vue')).default
const wrapper = mount(TopicsView, {
global: {
plugins: [router],
stubs: { TopicManager: true, DocumentCard: true, BreadcrumbBar: true },
},
})
await flushPromises()
const h2 = wrapper.find('h2')
expect(h2.exists()).toBe(true)
const cls = h2.attributes('class') || ''
expect(cls).toContain('font-semibold')
expect(cls).not.toContain('font-bold')
expect(cls).toContain('text-2xl')
wrapper.unmount()
})
})
describe('VISUAL-03: SettingsAccountTab section headings use text-sm font-semibold', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.resetModules()
})
it('h3 inside bg-white panel does not use text-xl or font-bold', async () => {
vi.doMock('../../api/client.js', () => ({
getSharedWithMe: vi.fn().mockResolvedValue([]),
listFolders: vi.fn().mockResolvedValue({ items: [] }),
getDocument: vi.fn().mockResolvedValue(null),
getMyPreferences: vi.fn().mockResolvedValue({ pdf_open_mode: 'new_tab' }),
listTopics: vi.fn().mockResolvedValue({ topics: [] }),
getMyQuota: vi.fn().mockResolvedValue({ used_bytes: 0, limit_bytes: 104857600 }),
}))
setActivePinia(createPinia())
const { useAuthStore } = await import('../../stores/auth.js')
const authStore = useAuthStore()
authStore.user = { email: 'test@example.com', role: 'user', totp_enabled: false, handle: 'test' }
const router = makeRouter()
await router.push('/settings')
await router.isReady()
const SettingsAccountTab = (await import('../../components/settings/SettingsAccountTab.vue')).default
const wrapper = mount(SettingsAccountTab, {
global: {
plugins: [router],
stubs: {
TotpEnrollment: true,
ConfirmBlock: true,
PasswordStrengthBar: true,
AppIcon: true,
AppSpinner: true,
},
},
})
await flushPromises()
const headings = wrapper.findAll('h3')
expect(headings.length).toBeGreaterThan(0)
for (const h of headings) {
const cls = h.attributes('class') || ''
expect(cls).not.toContain('text-xl')
expect(cls).not.toContain('font-bold')
expect(cls).toContain('font-semibold')
}
wrapper.unmount()
})
})
+5 -5
View File
@@ -28,7 +28,7 @@
:class="['transition-colors', prov.is_active ? 'border-l-4 border-l-green-500' : '']"
>
<button
class="w-full flex items-center justify-between px-6 py-3 text-left hover:bg-gray-50 transition-colors"
class="w-full flex items-center justify-between px-6 py-3 text-left hover:bg-gray-50 active:bg-gray-100 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-indigo-500"
@click="toggleProvider(prov.provider_id)"
>
<div class="flex items-center gap-3">
@@ -91,7 +91,7 @@
<button
@click="saveSystemProvider(prov.provider_id, { activate: true })"
:disabled="savingProvider === prov.provider_id || prov.is_active"
class="text-xs bg-green-600 text-white font-semibold px-3 py-1.5 rounded-lg hover:bg-green-700 disabled:opacity-50 transition-colors"
class="text-xs bg-green-600 text-white font-semibold px-3 py-1.5 rounded-lg hover:bg-green-700 active:bg-green-800 disabled:opacity-50 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-green-500 focus-visible:ring-offset-1"
>
Set Active
</button>
@@ -99,7 +99,7 @@
<button
@click="saveSystemProvider(prov.provider_id)"
:disabled="savingProvider === prov.provider_id"
class="text-xs bg-indigo-600 text-white font-semibold px-3 py-1.5 rounded-lg hover:bg-indigo-700 disabled:opacity-50 flex items-center gap-1 transition-colors"
class="text-xs bg-indigo-600 text-white font-semibold px-3 py-1.5 rounded-lg hover:bg-indigo-700 active:bg-indigo-800 disabled:opacity-50 flex items-center gap-1 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1"
>
<span v-if="savingProvider === prov.provider_id" class="animate-spin rounded-full border-2 border-current border-t-transparent w-3 h-3"></span>
{{ savingProvider === prov.provider_id ? 'Saving' : 'Save' }}
@@ -108,7 +108,7 @@
<button
@click="runTestConnection(prov.provider_id)"
:disabled="testingProvider === prov.provider_id || savingProvider === prov.provider_id"
class="text-xs border border-gray-300 text-gray-700 font-semibold px-3 py-1.5 rounded-lg hover:bg-gray-100 disabled:opacity-50 flex items-center gap-1.5 transition-colors"
class="text-xs border border-gray-300 text-gray-700 font-semibold px-3 py-1.5 rounded-lg hover:bg-gray-100 active:bg-gray-200 disabled:opacity-50 flex items-center gap-1.5 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1"
>
<span
v-if="testingProvider === prov.provider_id"
@@ -178,7 +178,7 @@
<button
@click="saveConfig(user.id)"
:disabled="savingId === user.id"
class="text-sm text-indigo-600 hover:text-indigo-700 font-semibold disabled:opacity-50 flex items-center gap-1"
class="text-sm text-indigo-600 hover:text-indigo-700 font-semibold disabled:opacity-50 flex items-center gap-1 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1 rounded"
>
<span v-if="savingId === user.id" class="animate-spin rounded-full border-2 border-current border-t-transparent w-3 h-3"></span>
{{ savingId === user.id ? 'Saving' : 'Save' }}
+6 -6
View File
@@ -44,14 +44,14 @@
</div>
<button
@click="applyFilters"
class="bg-indigo-600 text-white text-sm px-4 py-2 rounded-lg hover:bg-indigo-700 transition-colors"
class="bg-indigo-600 text-white text-sm px-4 py-2 rounded-lg hover:bg-indigo-700 active:bg-indigo-800 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1"
>
Apply filters
</button>
<button
v-if="activeFilterCount > 0"
@click="clearFilters"
class="border border-gray-300 text-gray-500 text-sm px-4 py-2 rounded-lg hover:bg-gray-50 transition-colors"
class="border border-gray-300 text-gray-500 text-sm px-4 py-2 rounded-lg hover:bg-gray-50 active:bg-gray-100 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1"
>
Clear filters
</button>
@@ -59,7 +59,7 @@
<button
@click="exportCsv"
:disabled="exportingCsv"
class="border border-gray-300 text-gray-700 text-sm px-4 py-2 rounded-lg hover:bg-gray-50 transition-colors disabled:opacity-50"
class="border border-gray-300 text-gray-700 text-sm px-4 py-2 rounded-lg hover:bg-gray-50 active:bg-gray-100 transition-colors disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1"
>
<span v-if="exportingCsv" class="flex items-center gap-1">
<span class="animate-spin rounded-full border-2 border-current border-t-transparent w-3 h-3"></span>
@@ -141,7 +141,7 @@
<button
@click="prevPage"
:disabled="page <= 1"
class="text-sm text-gray-600 hover:text-gray-900 disabled:opacity-40 disabled:cursor-not-allowed px-3 py-1.5 border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors"
class="text-sm text-gray-600 hover:text-gray-900 disabled:opacity-40 disabled:cursor-not-allowed px-3 py-1.5 border border-gray-200 rounded-lg hover:bg-gray-50 active:bg-gray-100 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1"
>
Previous
</button>
@@ -149,7 +149,7 @@
<button
@click="nextPage"
:disabled="page * perPage >= total"
class="text-sm text-gray-600 hover:text-gray-900 disabled:opacity-40 disabled:cursor-not-allowed px-3 py-1.5 border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors"
class="text-sm text-gray-600 hover:text-gray-900 disabled:opacity-40 disabled:cursor-not-allowed px-3 py-1.5 border border-gray-200 rounded-lg hover:bg-gray-50 active:bg-gray-100 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1"
>
Next
</button>
@@ -183,7 +183,7 @@
<button
@click="downloadDailyExport"
:disabled="!selectedExportDate || downloadingExport"
class="bg-indigo-600 hover:bg-indigo-700 text-white text-sm px-4 py-2 rounded-lg disabled:opacity-50 transition-colors flex items-center gap-1"
class="bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800 text-white text-sm px-4 py-2 rounded-lg disabled:opacity-50 transition-colors flex items-center gap-1 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1"
>
<span v-if="downloadingExport" class="animate-spin rounded-full border-2 border-current border-t-transparent w-3 h-3"></span>
Download
@@ -2,7 +2,7 @@
<div>
<BreadcrumbBar :segments="[]" :show-root="false" class="mb-4" />
<h2 class="text-xl font-semibold text-gray-900 mb-6">Overview</h2>
<h2 class="text-2xl font-semibold text-gray-900 mb-6">Overview</h2>
<div v-if="loading" class="text-gray-500">Loading overview</div>
+3 -3
View File
@@ -55,7 +55,7 @@
<button
@click="submitCreate"
:disabled="creating"
class="bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-semibold px-4 py-2 rounded-lg disabled:opacity-50 transition-colors"
class="bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800 text-white text-sm font-semibold px-4 py-2 rounded-lg disabled:opacity-50 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1"
>
<span v-if="creating" class="flex items-center gap-1.5">
<span class="animate-spin rounded-full border-2 border-current border-t-transparent w-4 h-4"></span>
@@ -65,7 +65,7 @@
</button>
<button
@click="cancelCreate"
class="text-sm px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
class="text-sm px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 active:bg-gray-100 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1"
>
Cancel
</button>
@@ -78,7 +78,7 @@
<button
v-if="!showCreateForm"
@click="openCreateForm"
class="bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-semibold px-4 py-2 rounded-lg transition-colors"
class="bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800 text-white text-sm font-semibold px-4 py-2 rounded-lg transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1"
>
Create user
</button>