A warmer, friendlier admin: first pass
- The INBUXA palette on warm surfaces, softer cards, buttons and inputs, and self-hosted Inter and Space Grotesk. - Each section's icon sits on a colored tile, colored by what the section is about. - The sidebar gets a guide line for sub-pages and a labeled Management / Settings / Account switch, and folds to a rail of tiles (remembered). - Every page has a header with its section's tile. - Fields and pages with no label of their own are spelled out in words (defaultCertificateId becomes Default certificate ID). - The dashboard greets you, and its stat cards wear colored tiles. - Unavailable live numbers are a calm note, not an error. - The cat appears in empty lists and while loading. - Chart colors work again: they were hex values wrapped in hsl().
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Coffey Labs
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/** Words that stay in capitals, or in their own casing, when a name is spelled out. */
|
||||
const SPECIAL: Record<string, string> = {
|
||||
id: 'ID',
|
||||
ids: 'IDs',
|
||||
url: 'URL',
|
||||
urls: 'URLs',
|
||||
uri: 'URI',
|
||||
uris: 'URIs',
|
||||
tls: 'TLS',
|
||||
dns: 'DNS',
|
||||
mx: 'MX',
|
||||
ip: 'IP',
|
||||
ips: 'IPs',
|
||||
api: 'API',
|
||||
http: 'HTTP',
|
||||
https: 'HTTPS',
|
||||
smtp: 'SMTP',
|
||||
imap: 'IMAP',
|
||||
pop3: 'POP3',
|
||||
jmap: 'JMAP',
|
||||
dkim: 'DKIM',
|
||||
spf: 'SPF',
|
||||
dmarc: 'DMARC',
|
||||
arc: 'ARC',
|
||||
acme: 'ACME',
|
||||
ldap: 'LDAP',
|
||||
sql: 'SQL',
|
||||
ttl: 'TTL',
|
||||
oauth: 'OAuth',
|
||||
oidc: 'OIDC',
|
||||
sni: 'SNI',
|
||||
mta: 'MTA',
|
||||
dav: 'DAV',
|
||||
cal: 'Cal',
|
||||
ai: 'AI',
|
||||
llm: 'LLM',
|
||||
otp: 'OTP',
|
||||
totp: 'TOTP',
|
||||
s3: 'S3',
|
||||
};
|
||||
|
||||
/**
|
||||
* `defaultCertificateId` → "Default certificate ID", `x:SystemSettings` →
|
||||
* "System settings". For names the server gives no label of its own: a
|
||||
* person reads words, not identifiers.
|
||||
*/
|
||||
export function humanize(name: string): string {
|
||||
const bare = name.replace(/^x:/, '').split('/').pop() ?? name;
|
||||
const words = bare
|
||||
.replace(/[_-]+/g, ' ')
|
||||
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
||||
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.filter(Boolean);
|
||||
return words
|
||||
.map((w, i) => {
|
||||
const special = SPECIAL[w.toLowerCase()];
|
||||
if (special) return special;
|
||||
const lower = w.toLowerCase();
|
||||
return i === 0 ? lower[0].toUpperCase() + lower.slice(1) : lower;
|
||||
})
|
||||
.join(' ');
|
||||
}
|
||||
Reference in New Issue
Block a user