Files
inbuxa-admin/src/lib/humanize.ts
T
jcoffey-dev e4ad5e2c1e 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().
2026-09-19 00:38:53 -07:00

71 lines
1.5 KiB
TypeScript

/*
* 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(' ');
}