@dialpad/dialtone
Version:
Dialpad's Dialtone design system monorepo
371 lines (370 loc) • 11.2 kB
JavaScript
import { getUniqueString, hasSlotContent, getRandomElement } from "../../common/utils.js";
import { AVATAR_SIZE_MODIFIERS, AVATAR_PRESENCE_STATES, AVATAR_GROUP_VALIDATOR, AVATAR_KIND_MODIFIERS, AVATAR_PRESENCE_SIZE_MODIFIERS, AVATAR_ICON_SIZES, AVATAR_RANDOM_COLORS } from "./avatar_constants.js";
import { ICON_SIZE_MODIFIERS } from "../icon/icon_constants.js";
import { extractInitialsFromName } from "./utils.js";
import { resolveComponent, openBlock, createBlock, resolveDynamicComponent, normalizeClass, normalizeStyle, withCtx, createElementVNode, createElementBlock, renderSlot, toDisplayString, createCommentVNode, mergeProps } from "vue";
import _export_sfc from "../../_virtual/_plugin-vue_export-helper.js";
import DtPresence from "../presence/presence.vue.js";
const _sfc_main = {
compatConfig: { MODE: 3 },
name: "DtAvatar",
components: { DtPresence },
inheritAttrs: false,
props: {
/**
* Id of the avatar content wrapper element
*/
id: {
type: String,
default() {
return getUniqueString();
}
},
/**
* Pass in a seed to get the random color generation based on that string. For example if you pass in a
* user ID as the string it will return the same randomly generated colors every time for that user.
*/
seed: {
type: String,
default: void 0
},
/**
* Set the avatar background to a specific color. If undefined will randomize the color which can be deterministic
* if the seed prop is set.
*/
color: {
type: String,
default: void 0
},
/**
* The size of the avatar
* @values xs, sm, md, lg, xl
*/
size: {
type: String,
default: "md",
validator: (size) => Object.keys(AVATAR_SIZE_MODIFIERS).includes(size)
},
/**
* Used to customize the avatar container
*/
avatarClass: {
type: [String, Array, Object],
default: ""
},
/**
* Set classes on the avatar canvas. Wrapper around the core avatar image.
*/
canvasClass: {
type: [String, Array, Object],
default: ""
},
/**
* Pass through classes. Used to customize the avatar icon
*/
iconClass: {
type: [String, Array, Object],
default: ""
},
/**
* Determines whether to show the presence indicator for
* Avatar - accepts PRESENCE_STATES values: 'busy', 'away', 'offline',
* or 'active'. By default, it's null and nothing is shown.
* @values null, busy, away, offline, active
*/
presence: {
type: String,
default: AVATAR_PRESENCE_STATES.NONE,
validator: (state) => {
return Object.values(AVATAR_PRESENCE_STATES).includes(state);
}
},
/**
* A set of props to be passed into the presence component.
*/
presenceProps: {
type: Object,
default: () => ({})
},
/**
* Determines whether to show a group avatar.
* Limit to 2 digits max, more than 99 will be rendered as “99+”.
* if the number is 1 or less it would just show the regular avatar as if group had not been set.
*/
group: {
type: Number,
default: void 0,
validator: (group) => AVATAR_GROUP_VALIDATOR(group)
},
/**
* The text that overlays the avatar
*/
overlayText: {
type: String,
default: ""
},
/**
* Used to customize the avatar overlay
*/
overlayClass: {
type: [String, Array, Object],
default: ""
},
/**
* Source of the image
*/
imageSrc: {
type: String,
default: ""
},
/**
* Alt attribute of the image, required if imageSrc is provided.
* Can be set to '' (empty string) if the image is described
* in text nearby
*/
imageAlt: {
type: String,
default: void 0
},
/**
* Icon size to be displayed on the avatar
* @values 100, 200, 300, 400, 500, 600, 700, 800
*/
iconSize: {
type: String,
default: "",
validator: (size) => !size || Object.keys(ICON_SIZE_MODIFIERS).includes(size)
},
/**
* Full name used to extract initials.
*/
fullName: {
type: String,
default: ""
},
/**
* Makes the avatar focusable and clickable,
* emits a click event when clicked.
*/
clickable: {
type: Boolean,
default: false
},
/**
* Descriptive label for the icon.
* To avoid a11y issues, set this prop if clickable and iconName are set.
*/
iconAriaLabel: {
type: String,
default: void 0
}
},
emits: [
/**
* Avatar click event
*
* @event click
* @type {PointerEvent | KeyboardEvent}
*/
"click"
],
data() {
return {
AVATAR_SIZE_MODIFIERS,
AVATAR_KIND_MODIFIERS,
AVATAR_PRESENCE_SIZE_MODIFIERS,
AVATAR_ICON_SIZES,
imageLoadedSuccessfully: null,
formattedInitials: "",
initializing: false,
hasSlotContent
};
},
computed: {
hasOverlayIcon() {
return hasSlotContent(this.$slots.overlayIcon);
},
iconDataQa() {
return "dt-avatar-icon";
},
avatarClasses() {
return [
"d-avatar",
this.$attrs.class,
AVATAR_SIZE_MODIFIERS[this.validatedSize],
this.avatarClass,
{
"d-avatar--group": this.showGroup,
[`d-avatar--color-${this.getColor()}`]: !this.isIconType(),
"d-avatar--clickable": this.clickable
}
];
},
overlayClasses() {
return [
"d-avatar__overlay",
this.overlayClass,
{ "d-avatar__overlay-icon": this.hasOverlayIcon }
];
},
showGroup() {
return AVATAR_GROUP_VALIDATOR(this.group);
},
formattedGroup() {
return this.group > 99 ? "99+" : this.group;
},
validatedSize() {
return this.group ? "xs" : this.size;
},
showImage() {
return this.imageLoadedSuccessfully !== false && this.imageSrc;
}
},
watch: {
fullName: {
immediate: true,
handler() {
this.formatInitials();
}
},
size: {
immediate: true,
handler() {
this.formatInitials();
}
},
group: {
immediate: true,
handler() {
this.formatInitials();
}
},
imageSrc(newSrc) {
this.imageLoadedSuccessfully = null;
if (!newSrc) return;
this.validateProps();
this.setImageListeners();
}
},
mounted() {
this.validateProps();
this.setImageListeners();
},
methods: {
isIconType() {
return hasSlotContent(this.$slots.icon);
},
async setImageListeners() {
await this.$nextTick();
const el = this.$refs.avatarImage;
if (!el) return;
el.addEventListener("load", () => this._loadedImageEventHandler(el), { once: true });
el.addEventListener("error", () => this._erroredImageEventHandler(el), { once: true });
},
formatInitials() {
const initials = extractInitialsFromName(this.fullName);
if (this.validatedSize === "xs") {
this.formattedInitials = "";
} else if (this.validatedSize === "sm") {
this.formattedInitials = initials[0];
} else {
this.formattedInitials = initials;
}
},
getColor() {
return this.color ?? getRandomElement(AVATAR_RANDOM_COLORS, this.seed);
},
_loadedImageEventHandler(el) {
this.imageLoadedSuccessfully = true;
el.classList.remove("d-d-none");
},
_erroredImageEventHandler(el) {
this.imageLoadedSuccessfully = false;
el.classList.add("d-d-none");
},
validateProps() {
if (this.imageSrc && this.imageAlt === void 0) {
console.error('image-alt required if image-src is provided. Can be set to "" (empty string) if the image is described in text nearby');
}
},
handleClick(e) {
if (!this.clickable) return;
this.$emit("click", e);
}
}
};
const _hoisted_1 = ["src", "alt"];
const _hoisted_2 = ["aria-label", "data-qa", "role"];
const _hoisted_3 = {
key: 1,
class: "d-avatar__overlay-text"
};
const _hoisted_4 = {
key: 1,
class: "d-avatar__count",
"data-qa": "dt-avatar-count"
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
const _component_dt_presence = resolveComponent("dt-presence");
return openBlock(), createBlock(resolveDynamicComponent($props.clickable ? "button" : "div"), {
id: $props.id,
class: normalizeClass($options.avatarClasses),
style: normalizeStyle(_ctx.$attrs.style),
"data-qa": "dt-avatar",
onClick: $options.handleClick
}, {
default: withCtx(() => [
createElementVNode("div", {
ref: "canvas",
class: normalizeClass([
$props.canvasClass,
"d-avatar__canvas",
{ "d-avatar--image-loaded": $data.imageLoadedSuccessfully }
])
}, [
$options.showImage ? (openBlock(), createElementBlock("img", {
key: 0,
ref: "avatarImage",
class: "d-avatar__image",
"data-qa": "dt-avatar-image",
src: $props.imageSrc,
alt: $props.imageAlt
}, null, 8, _hoisted_1)) : $options.isIconType() ? (openBlock(), createElementBlock("div", {
key: 1,
class: normalizeClass([$props.iconClass, $data.AVATAR_KIND_MODIFIERS.icon]),
"aria-label": $props.clickable ? $props.iconAriaLabel : "",
"data-qa": $options.iconDataQa,
role: $props.clickable ? "button" : ""
}, [
renderSlot(_ctx.$slots, "icon", {
iconSize: $props.iconSize || $data.AVATAR_ICON_SIZES[$props.size]
})
], 10, _hoisted_2)) : (openBlock(), createElementBlock("span", {
key: 2,
class: normalizeClass([$data.AVATAR_KIND_MODIFIERS.initials])
}, toDisplayString($data.formattedInitials), 3))
], 2),
$options.hasOverlayIcon || $props.overlayText ? (openBlock(), createElementBlock("div", {
key: 0,
class: normalizeClass($options.overlayClasses)
}, [
$options.hasOverlayIcon ? renderSlot(_ctx.$slots, "overlayIcon", { key: 0 }) : $props.overlayText ? (openBlock(), createElementBlock("p", _hoisted_3, toDisplayString($props.overlayText), 1)) : createCommentVNode("", true)
], 2)) : createCommentVNode("", true),
$options.showGroup ? (openBlock(), createElementBlock("span", _hoisted_4, toDisplayString($options.formattedGroup), 1)) : createCommentVNode("", true),
$props.presence && !$options.showGroup ? (openBlock(), createBlock(_component_dt_presence, mergeProps({
key: 2,
presence: $props.presence,
class: [
"d-avatar__presence",
$data.AVATAR_PRESENCE_SIZE_MODIFIERS[$props.size]
]
}, $props.presenceProps, { "data-qa": "dt-presence" }), null, 16, ["presence", "class"])) : createCommentVNode("", true)
]),
_: 3
}, 8, ["id", "class", "style", "onClick"]);
}
const DtAvatar = /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render]]);
export {
DtAvatar as default
};
//# sourceMappingURL=avatar.vue.js.map