UNPKG

@dialpad/dialtone

Version:

Dialpad's Dialtone design system monorepo

371 lines (370 loc) 11.2 kB
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