fix: align button icons and polish vendor sidebar
All checks were successful
CI / build (push) Successful in 2m8s
CI / docker (push) Successful in 1m41s

Fix vertical alignment of Lucide icons inside Button children across
all pages by adding verticalAlign offsets (-3px for 16px icons, -2px
for 14px icons). The design system Button wraps children in an inline
span, so SVG icons defaulted to baseline alignment.

Hide the redundant top-right "Create Tenant" button on VendorTenantsPage
when no tenants exist — the EmptyState already provides that action.

Add icons to all vendor sidebar sub-items for consistency (previously
only Email Connector had one).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-25 21:30:37 +02:00
parent adb4ef1af8
commit dee1f39554
9 changed files with 44 additions and 36 deletions

View File

@@ -4,7 +4,7 @@ import {
Sidebar, Sidebar,
TopBar, TopBar,
} from '@cameleer/design-system'; } from '@cameleer/design-system';
import { LayoutDashboard, ShieldCheck, Users, Settings, Shield, Building, ScrollText, Mail } from 'lucide-react'; import { LayoutDashboard, ShieldCheck, Users, Settings, Shield, Building, ScrollText, Mail, BarChart3, Server, ExternalLink } from 'lucide-react';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useAuth } from '../auth/useAuth'; import { useAuth } from '../auth/useAuth';
import { useScopes } from '../auth/useScopes'; import { useScopes } from '../auth/useScopes';
@@ -78,6 +78,7 @@ export function Layout() {
color: isActive(location, '/vendor/tenants') && !location.pathname.startsWith('/vendor/tenants/') ? 'var(--amber)' : 'var(--text-muted)' }} color: isActive(location, '/vendor/tenants') && !location.pathname.startsWith('/vendor/tenants/') ? 'var(--amber)' : 'var(--text-muted)' }}
onClick={() => navigate('/vendor/tenants')} onClick={() => navigate('/vendor/tenants')}
> >
<Users size={12} style={{ marginRight: 6, verticalAlign: -1 }} />
Tenants Tenants
</div> </div>
{vendorTenants?.filter(t => t.status !== 'DELETED').map(t => ( {vendorTenants?.filter(t => t.status !== 'DELETED').map(t => (
@@ -99,6 +100,7 @@ export function Layout() {
color: isActive(location, '/vendor/audit') ? 'var(--amber)' : 'var(--text-muted)' }} color: isActive(location, '/vendor/audit') ? 'var(--amber)' : 'var(--text-muted)' }}
onClick={() => navigate('/vendor/audit')} onClick={() => navigate('/vendor/audit')}
> >
<ScrollText size={12} style={{ marginRight: 6, verticalAlign: -1 }} />
Audit Log Audit Log
</div> </div>
<div <div
@@ -107,6 +109,7 @@ export function Layout() {
color: isActive(location, '/vendor/certificates') ? 'var(--amber)' : 'var(--text-muted)' }} color: isActive(location, '/vendor/certificates') ? 'var(--amber)' : 'var(--text-muted)' }}
onClick={() => navigate('/vendor/certificates')} onClick={() => navigate('/vendor/certificates')}
> >
<ShieldCheck size={12} style={{ marginRight: 6, verticalAlign: -1 }} />
Certificates Certificates
</div> </div>
<div <div
@@ -115,6 +118,7 @@ export function Layout() {
color: isActive(location, '/vendor/metrics') ? 'var(--amber)' : 'var(--text-muted)' }} color: isActive(location, '/vendor/metrics') ? 'var(--amber)' : 'var(--text-muted)' }}
onClick={() => navigate('/vendor/metrics')} onClick={() => navigate('/vendor/metrics')}
> >
<BarChart3 size={12} style={{ marginRight: 6, verticalAlign: -1 }} />
Metrics Metrics
</div> </div>
<div <div
@@ -123,6 +127,7 @@ export function Layout() {
color: isActive(location, '/vendor/infrastructure') ? 'var(--amber)' : 'var(--text-muted)' }} color: isActive(location, '/vendor/infrastructure') ? 'var(--amber)' : 'var(--text-muted)' }}
onClick={() => navigate('/vendor/infrastructure')} onClick={() => navigate('/vendor/infrastructure')}
> >
<Server size={12} style={{ marginRight: 6, verticalAlign: -1 }} />
Infrastructure Infrastructure
</div> </div>
<div <div
@@ -138,6 +143,7 @@ export function Layout() {
style={{ padding: '6px 12px 6px 36px', fontSize: 13, cursor: 'pointer', color: 'var(--text-muted)' }} style={{ padding: '6px 12px 6px 36px', fontSize: 13, cursor: 'pointer', color: 'var(--text-muted)' }}
onClick={() => window.open(`${window.location.protocol}//${window.location.hostname}:3002`, '_blank', 'noopener')} onClick={() => window.open(`${window.location.protocol}//${window.location.hostname}:3002`, '_blank', 'noopener')}
> >
<ExternalLink size={12} style={{ marginRight: 6, verticalAlign: -1 }} />
Logto Console Logto Console
</div> </div>
</Sidebar.Section> </Sidebar.Section>

View File

@@ -185,7 +185,7 @@ export function SsoPage() {
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<h1 style={{ margin: 0, fontSize: '1.25rem', fontWeight: 600 }}>Enterprise SSO</h1> <h1 style={{ margin: 0, fontSize: '1.25rem', fontWeight: 600 }}>Enterprise SSO</h1>
<Button variant="primary" onClick={() => setShowCreate((v) => !v)}> <Button variant="primary" onClick={() => setShowCreate((v) => !v)}>
<Plus size={16} style={{ marginRight: 6 }} /> <Plus size={16} style={{ marginRight: 6, verticalAlign: -3 }} />
Add SSO Connection Add SSO Connection
</Button> </Button>
</div> </div>
@@ -284,7 +284,7 @@ export function SsoPage() {
description="Add an enterprise SSO connection to let your team sign in with their corporate identity provider." description="Add an enterprise SSO connection to let your team sign in with their corporate identity provider."
action={ action={
<Button variant="primary" onClick={() => setShowCreate(true)}> <Button variant="primary" onClick={() => setShowCreate(true)}>
<Plus size={16} style={{ marginRight: 6 }} /> <Plus size={16} style={{ marginRight: 6, verticalAlign: -3 }} />
Add SSO Connection Add SSO Connection
</Button> </Button>
} }
@@ -413,7 +413,7 @@ function CaCertificatesSection() {
<FileInput ref={certRef} accept=".pem,.crt,.cer" icon={<ShieldCheck size={16} />} /> <FileInput ref={certRef} accept=".pem,.crt,.cer" icon={<ShieldCheck size={16} />} />
</FormField> </FormField>
<Button variant="primary" onClick={handleUpload} loading={stageMutation.isPending}> <Button variant="primary" onClick={handleUpload} loading={stageMutation.isPending}>
<Upload size={14} style={{ marginRight: 6 }} /> <Upload size={14} style={{ marginRight: 6, verticalAlign: -2 }} />
Stage Certificate Stage Certificate
</Button> </Button>
</div> </div>
@@ -447,7 +447,7 @@ function CaCertificatesSection() {
</div> </div>
<div style={{ paddingTop: 8, display: 'flex', gap: 8 }}> <div style={{ paddingTop: 8, display: 'flex', gap: 8 }}>
<Button variant="primary" onClick={() => handleActivate(cert.id)} loading={activateMutation.isPending}> <Button variant="primary" onClick={() => handleActivate(cert.id)} loading={activateMutation.isPending}>
<ShieldCheck size={14} style={{ marginRight: 6 }} /> <ShieldCheck size={14} style={{ marginRight: 6, verticalAlign: -2 }} />
Activate Activate
</Button> </Button>
<Button variant="danger" onClick={() => setDeleteTarget(cert)}> <Button variant="danger" onClick={() => setDeleteTarget(cert)}>
@@ -486,7 +486,7 @@ function CaCertificatesSection() {
</div> </div>
<div style={{ paddingTop: 8 }}> <div style={{ paddingTop: 8 }}>
<Button variant="danger" onClick={() => setDeleteTarget(cert)}> <Button variant="danger" onClick={() => setDeleteTarget(cert)}>
<Trash2 size={14} style={{ marginRight: 6 }} /> <Trash2 size={14} style={{ marginRight: 6, verticalAlign: -2 }} />
Remove Remove
</Button> </Button>
</div> </div>

View File

@@ -161,7 +161,7 @@ export function TeamPage() {
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<h1 style={{ margin: 0, fontSize: '1.25rem', fontWeight: 600 }}>Team</h1> <h1 style={{ margin: 0, fontSize: '1.25rem', fontWeight: 600 }}>Team</h1>
<Button variant="primary" onClick={() => setShowInvite((v) => !v)}> <Button variant="primary" onClick={() => setShowInvite((v) => !v)}>
<Plus size={16} style={{ marginRight: 6 }} /> <Plus size={16} style={{ marginRight: 6, verticalAlign: -3 }} />
Invite Member Invite Member
</Button> </Button>
</div> </div>
@@ -222,7 +222,7 @@ export function TeamPage() {
description="Invite colleagues to collaborate on this tenant." description="Invite colleagues to collaborate on this tenant."
action={ action={
<Button variant="primary" onClick={() => setShowInvite(true)}> <Button variant="primary" onClick={() => setShowInvite(true)}>
<Plus size={16} style={{ marginRight: 6 }} /> <Plus size={16} style={{ marginRight: 6, verticalAlign: -3 }} />
Invite Member Invite Member
</Button> </Button>
} }

View File

@@ -112,7 +112,7 @@ export function TenantDashboardPage() {
}} }}
loading={restartServer.isPending} loading={restartServer.isPending}
> >
<RefreshCw size={14} style={{ marginRight: 6 }} /> <RefreshCw size={14} style={{ marginRight: 6, verticalAlign: -2 }} />
Restart Restart
</Button> </Button>
<Button <Button
@@ -127,7 +127,7 @@ export function TenantDashboardPage() {
}} }}
loading={upgradeServer.isPending} loading={upgradeServer.isPending}
> >
<ArrowUpCircle size={14} style={{ marginRight: 6 }} /> <ArrowUpCircle size={14} style={{ marginRight: 6, verticalAlign: -2 }} />
Upgrade Upgrade
</Button> </Button>
</div> </div>
@@ -150,16 +150,16 @@ export function TenantDashboardPage() {
variant="secondary" variant="secondary"
onClick={() => window.open(`/t/${data.slug}/`, '_blank')} onClick={() => window.open(`/t/${data.slug}/`, '_blank')}
> >
<ExternalLink size={14} style={{ marginRight: 6 }} /> <ExternalLink size={14} style={{ marginRight: 6, verticalAlign: -2 }} />
Open Server Dashboard Open Server Dashboard
</Button> </Button>
)} )}
<Button variant="secondary" onClick={() => navigate('../license')}> <Button variant="secondary" onClick={() => navigate('../license')}>
<Key size={14} style={{ marginRight: 6 }} /> <Key size={14} style={{ marginRight: 6, verticalAlign: -2 }} />
View License View License
</Button> </Button>
<Button variant="secondary" onClick={() => navigate('../oidc')}> <Button variant="secondary" onClick={() => navigate('../oidc')}>
<Settings size={14} style={{ marginRight: 6 }} /> <Settings size={14} style={{ marginRight: 6, verticalAlign: -2 }} />
Configure OIDC Configure OIDC
</Button> </Button>
</div> </div>

View File

@@ -132,12 +132,12 @@ export function TenantLicensePage() {
<div style={{ display: 'flex', gap: 8 }}> <div style={{ display: 'flex', gap: 8 }}>
<Button variant="secondary" onClick={() => setShowToken((v) => !v)}> <Button variant="secondary" onClick={() => setShowToken((v) => !v)}>
{showToken {showToken
? <><EyeOff size={14} style={{ marginRight: 6 }} />Hide</> ? <><EyeOff size={14} style={{ marginRight: 6, verticalAlign: -2 }} />Hide</>
: <><Eye size={14} style={{ marginRight: 6 }} />Show</> : <><Eye size={14} style={{ marginRight: 6, verticalAlign: -2 }} />Show</>
} }
</Button> </Button>
<Button variant="secondary" onClick={handleCopy}> <Button variant="secondary" onClick={handleCopy}>
<Copy size={14} style={{ marginRight: 6 }} /> <Copy size={14} style={{ marginRight: 6, verticalAlign: -2 }} />
Copy Copy
</Button> </Button>
</div> </div>

View File

@@ -225,7 +225,7 @@ export function CertificatesPage() {
onClick={handleActivate} onClick={handleActivate}
loading={activateMutation.isPending} loading={activateMutation.isPending}
> >
<ShieldCheck size={14} style={{ marginRight: 6 }} /> <ShieldCheck size={14} style={{ marginRight: 6, verticalAlign: -2 }} />
Activate Activate
</Button> </Button>
<Button <Button
@@ -233,7 +233,7 @@ export function CertificatesPage() {
onClick={handleDiscard} onClick={handleDiscard}
loading={discardMutation.isPending} loading={discardMutation.isPending}
> >
<Trash2 size={14} style={{ marginRight: 6 }} /> <Trash2 size={14} style={{ marginRight: 6, verticalAlign: -2 }} />
Discard Discard
</Button> </Button>
</> </>
@@ -252,7 +252,7 @@ export function CertificatesPage() {
loading={restoreMutation.isPending} loading={restoreMutation.isPending}
disabled={expired} disabled={expired}
> >
<RotateCcw size={14} style={{ marginRight: 6 }} /> <RotateCcw size={14} style={{ marginRight: 6, verticalAlign: -2 }} />
{expired ? 'Expired' : 'Restore'} {expired ? 'Expired' : 'Restore'}
</Button> </Button>
} }
@@ -284,7 +284,7 @@ export function CertificatesPage() {
onClick={handleUpload} onClick={handleUpload}
loading={stageMutation.isPending} loading={stageMutation.isPending}
> >
<Upload size={14} style={{ marginRight: 6 }} /> <Upload size={14} style={{ marginRight: 6, verticalAlign: -2 }} />
Stage Certificate Stage Certificate
</Button> </Button>
</div> </div>

View File

@@ -172,7 +172,7 @@ export function EmailConfigPage() {
</Button> </Button>
{!confirmDelete ? ( {!confirmDelete ? (
<Button variant="secondary" onClick={() => setConfirmDelete(true)}> <Button variant="secondary" onClick={() => setConfirmDelete(true)}>
<Trash2 size={14} style={{ marginRight: 6 }} /> <Trash2 size={14} style={{ marginRight: 6, verticalAlign: -2 }} />
Remove Remove
</Button> </Button>
) : ( ) : (
@@ -235,7 +235,7 @@ export function EmailConfigPage() {
</FormField> </FormField>
<div style={{ display: 'flex', gap: 8 }}> <div style={{ display: 'flex', gap: 8 }}>
<Button variant="primary" onClick={handleSave} loading={saveMutation.isPending}> <Button variant="primary" onClick={handleSave} loading={saveMutation.isPending}>
<Save size={14} style={{ marginRight: 6 }} /> <Save size={14} style={{ marginRight: 6, verticalAlign: -2 }} />
{isConfigured ? 'Update' : 'Save'} {isConfigured ? 'Update' : 'Save'}
</Button> </Button>
{editing && ( {editing && (
@@ -263,7 +263,7 @@ export function EmailConfigPage() {
onClick={handleToggleRegistration} onClick={handleToggleRegistration}
loading={toggleMutation.isPending} loading={toggleMutation.isPending}
> >
<Power size={14} style={{ marginRight: 6 }} /> <Power size={14} style={{ marginRight: 6, verticalAlign: -2 }} />
{connector.registrationEnabled ? 'Disable Registration' : 'Enable Registration'} {connector.registrationEnabled ? 'Disable Registration' : 'Enable Registration'}
</Button> </Button>
</div> </div>
@@ -284,7 +284,7 @@ export function EmailConfigPage() {
/> />
</FormField> </FormField>
<Button variant="primary" onClick={handleTest} loading={testMutation.isPending}> <Button variant="primary" onClick={handleTest} loading={testMutation.isPending}>
<Send size={14} style={{ marginRight: 6 }} /> <Send size={14} style={{ marginRight: 6, verticalAlign: -2 }} />
Send Test Email Send Test Email
</Button> </Button>
</div> </div>

View File

@@ -227,7 +227,7 @@ export function TenantDetailPage() {
onClick={handleRenewLicense} onClick={handleRenewLicense}
loading={renewLicense.isPending} loading={renewLicense.isPending}
> >
<RefreshCw size={14} style={{ marginRight: 6 }} /> <RefreshCw size={14} style={{ marginRight: 6, verticalAlign: -2 }} />
Renew License Renew License
</Button> </Button>
</div> </div>
@@ -276,7 +276,7 @@ export function TenantDetailPage() {
onClick={handleRestart} onClick={handleRestart}
loading={restartServer.isPending} loading={restartServer.isPending}
> >
<RefreshCw size={14} style={{ marginRight: 6 }} /> <RefreshCw size={14} style={{ marginRight: 6, verticalAlign: -2 }} />
Restart Server Restart Server
</Button> </Button>
<Button <Button
@@ -284,7 +284,7 @@ export function TenantDetailPage() {
onClick={handleUpgrade} onClick={handleUpgrade}
loading={upgradeServer.isPending} loading={upgradeServer.isPending}
> >
<ArrowUpCircle size={14} style={{ marginRight: 6 }} /> <ArrowUpCircle size={14} style={{ marginRight: 6, verticalAlign: -2 }} />
Upgrade Server Upgrade Server
</Button> </Button>
<Button <Button
@@ -298,7 +298,7 @@ export function TenantDetailPage() {
variant="danger" variant="danger"
onClick={() => setDeleteOpen(true)} onClick={() => setDeleteOpen(true)}
> >
<Trash2 size={14} style={{ marginRight: 6 }} /> <Trash2 size={14} style={{ marginRight: 6, verticalAlign: -2 }} />
Delete Tenant Delete Tenant
</Button> </Button>
</div> </div>

View File

@@ -93,13 +93,15 @@ export function VendorTenantsPage() {
<div style={{ padding: '24px', display: 'flex', flexDirection: 'column', gap: 20 }}> <div style={{ padding: '24px', display: 'flex', flexDirection: 'column', gap: 20 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<h1 style={{ margin: 0, fontSize: '1.25rem', fontWeight: 600 }}>Tenants</h1> <h1 style={{ margin: 0, fontSize: '1.25rem', fontWeight: 600 }}>Tenants</h1>
{!isLoading && tenants && tenants.length > 0 && (
<Button <Button
variant="primary" variant="primary"
onClick={() => navigate('/vendor/tenants/new')} onClick={() => navigate('/vendor/tenants/new')}
> >
<Plus size={16} style={{ marginRight: 6 }} /> <Plus size={16} style={{ marginRight: 6, verticalAlign: -3 }} />
Create Tenant Create Tenant
</Button> </Button>
)}
</div> </div>
{isLoading && ( {isLoading && (
@@ -115,7 +117,7 @@ export function VendorTenantsPage() {
description="Create your first tenant to get started." description="Create your first tenant to get started."
action={ action={
<Button variant="primary" onClick={() => navigate('/vendor/tenants/new')}> <Button variant="primary" onClick={() => navigate('/vendor/tenants/new')}>
<Plus size={16} style={{ marginRight: 6 }} /> <Plus size={16} style={{ marginRight: 6, verticalAlign: -3 }} />
Create Tenant Create Tenant
</Button> </Button>
} }