@apliteni/apliteni-ui 0.130.0 → 0.132.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/react/dist/index.d.ts +28 -1
- package/react/dist/index.js +628 -565
- package/src/components/account-nav.js +4 -7
- package/src/components/avatar.js +53 -0
- package/src/components/dropdown.js +529 -30
- package/src/components/shell.js +3 -2
- package/src/components/topbar.js +4 -3
- package/src/index.css +1 -0
- package/src/index.d.ts +40 -0
- package/src/index.js +1 -0
- package/src/inline.js +2 -0
- package/src/logic/avatar.js +28 -0
- package/src/logic/dropdown.js +22 -0
- package/src/styles/avatar.css +42 -0
- package/src/styles/dropdown.css +92 -22
- package/src/styles/layout.css +3 -6
- package/src/styles/topbar.css +6 -3
- package/src/tokens/tokens.css +23 -0
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// the published name docs/components.md documents.
|
|
6
6
|
// why: docs/components.md#the-page-shell
|
|
7
7
|
import { esc } from './index.js';
|
|
8
|
+
import { avatarInitials } from '../logic/avatar.js';
|
|
8
9
|
|
|
9
10
|
// nav.js item objects. Labels are raw text — every nav primitive escapes, so a
|
|
10
11
|
// pre-escaped `&` would come out as `&`. `key` means credentials;
|
|
@@ -38,10 +39,6 @@ export const accountMenuNav = () => ACCOUNT_NAV.map(toMenuTuple);
|
|
|
38
39
|
// beside ACCOUNT_NAV for the same reason ACCOUNT_NAV lives here: both files
|
|
39
40
|
// need it and shell.js already imports topbar.js.
|
|
40
41
|
//
|
|
41
|
-
// The
|
|
42
|
-
//
|
|
43
|
-
export const initials = (name
|
|
44
|
-
const from = String(name ?? '').trim() || String(email ?? '').split('@')[0] || '';
|
|
45
|
-
const parts = from.split(/[\s._-]+/).filter(Boolean).map((w) => w[0]);
|
|
46
|
-
return (parts.slice(0, 2).join('') || '?').toUpperCase();
|
|
47
|
-
};
|
|
42
|
+
// The displayed name alone supplies the letters. The part draws its user glyph
|
|
43
|
+
// when no name is shown.
|
|
44
|
+
export const initials = (name) => avatarInitials(name);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { esc, icon } from './index.js';
|
|
2
|
+
import { safeUrl } from '../html.js';
|
|
3
|
+
import { avatarHue, avatarInitials } from '../logic/avatar.js';
|
|
4
|
+
export { avatarHue, avatarInitials, AVATAR_HUES } from '../logic/avatar.js';
|
|
5
|
+
|
|
6
|
+
// Avatar — a person's round mark. The letters come from `name`, the hue from a hash of
|
|
7
|
+
// `key`, and a photo covers the letter. A photo that fails to load removes itself, so the
|
|
8
|
+
// letter shows and not the browser's broken-image glyph.
|
|
9
|
+
// With no name the mark is the `user` glyph; `empty` is a dashed ring for "no one yet".
|
|
10
|
+
// It is decoration beside the name it stands for, so it is hidden from assistive
|
|
11
|
+
// technology, and it has no cursor, hover or focus of its own.
|
|
12
|
+
// why: docs/components.md#avatar
|
|
13
|
+
const cx = (...a) => a.filter(Boolean).join(' ');
|
|
14
|
+
const wiredDocs = new WeakSet();
|
|
15
|
+
|
|
16
|
+
function wireAvatarPhotos(doc) {
|
|
17
|
+
if (!doc || wiredDocs.has(doc)) return;
|
|
18
|
+
wiredDocs.add(doc);
|
|
19
|
+
doc.querySelectorAll('.ui-avatar > img[data-avatar-photo]').forEach((img) => {
|
|
20
|
+
if (!img.complete) return;
|
|
21
|
+
if (img.naturalWidth > 0) img.classList.add('is-loaded');
|
|
22
|
+
else img.remove();
|
|
23
|
+
});
|
|
24
|
+
doc.addEventListener('load', (event) => {
|
|
25
|
+
if (event.target?.matches?.('.ui-avatar > img[data-avatar-photo]')) event.target.classList.add('is-loaded');
|
|
26
|
+
}, true);
|
|
27
|
+
doc.addEventListener('error', (event) => {
|
|
28
|
+
if (event.target?.matches?.('.ui-avatar > img[data-avatar-photo]')) event.target.remove();
|
|
29
|
+
}, true);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (typeof document !== 'undefined') wireAvatarPhotos(document);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {object} [o]
|
|
36
|
+
* @param {string|number} [o.key] the app's stable id for the person; picks the hue
|
|
37
|
+
* @param {string} [o.name] the person's name; empty draws the `user` glyph
|
|
38
|
+
* @param {string} [o.initials] the letters to draw, when the caller has already chosen them
|
|
39
|
+
* @param {string} [o.src] a photo URL; the letters show if it fails to load
|
|
40
|
+
* @param {24|32} [o.size] the mark's size in px
|
|
41
|
+
* @param {boolean} [o.empty] a dashed ring in place of a person
|
|
42
|
+
* @returns {string} html
|
|
43
|
+
*/
|
|
44
|
+
export function avatar({ key, name, initials, src, size = 24, empty = false } = {}) {
|
|
45
|
+
if (typeof document !== 'undefined') wireAvatarPhotos(document);
|
|
46
|
+
const base = ['ui-avatar', size === 32 && 'ui-avatar--32'];
|
|
47
|
+
if (empty) return `<span class="${esc(cx(...base, 'ui-avatar--empty'))}" aria-hidden="true"></span>`;
|
|
48
|
+
const letters = initials || avatarInitials(name);
|
|
49
|
+
if (!letters) return `<span class="${esc(cx(...base, 'ui-avatar--anon'))}" aria-hidden="true">${icon('user')}</span>`;
|
|
50
|
+
const hue = avatarHue(key ?? name ?? letters);
|
|
51
|
+
const img = src ? `<img data-avatar-photo src="${esc(safeUrl(src))}" alt="" loading="lazy">` : '';
|
|
52
|
+
return `<span class="${esc(cx(...base, `ui-avatar--h${hue}`))}" data-initials="${letters.length}" aria-hidden="true">${esc(letters)}${img}</span>`;
|
|
53
|
+
}
|