Newer
Older
vmk-ui-kit / src / vue / components / GnOptionCard.js
@Eugene Sukhodolskiy Eugene Sukhodolskiy 19 hours ago 4 KB so many changes
/**
 * GnOptionCard — search-result / listing card.
 *
 * Figma: Option Card, Campaign search card, Contact search card.
 */

import { defineComponent, h } from "vue";
import { cx, iconNode } from "../utils.js";
import GnChip from "./GnChip.js";

export default defineComponent({
	name: "GnOptionCard",
	inheritAttrs: false,
	props: {
		layout: { type: String, default: "vertical" }, // vertical | horizontal
		size: { type: String, default: "md" }, // md | lg
		image: { type: String, default: "" },
		icon: { type: String, default: "" },
		match: { type: [String, Number], default: "" },
		price: { type: String, default: "" },
		area: { type: String, default: "" },
		title: { type: String, default: "" },
		description: { type: String, default: "" },
		badges: { type: Array, default: () => [] },
		expanded: { type: Boolean, default: false },
		stats: { type: Array, default: () => [] },
		details: { type: Array, default: () => [] },
		actions: { type: Array, default: () => [] },
		selectable: { type: Boolean, default: false },
		selected: { type: Boolean, default: false },
		indicator: { type: String, default: "radio" } // radio | check
	},
	emits: ["action", "select", "update:selected"],
	setup(props, { attrs, slots, emit }) {
		const mediaNode = () => h("div", { class: "option-card-media" }, [
			props.image
				? h("img", { src: props.image, alt: "" })
				: h("span", { class: "option-card-media-placeholder" }, iconNode("ph-image")),
			props.match && h("span", { class: "option-card-match" }, [
				iconNode("ph-thumbs-up"),
				`${props.match}%`
			]),
			slots.mediaCount?.() || (props.image && h("span", { class: "option-card-media-count" }, "1/5")),
			slots.dots?.()
		]);

		const headerNode = () => h("div", { class: "option-card-header" }, [
			h("p", { class: "option-card-price" }, [
				props.price,
				props.area && h("span", { class: "option-card-area" }, props.area)
			]),
			slots.actionsTop?.()
		]);

		const badgesNode = () => props.badges.length > 0 && h("div", { class: "option-card-badges" },
			props.badges.map(badge => h(GnChip, {
				variant: badge.variant || "default",
				removable: badge.removable,
				onRemove: () => emit("action", { type: "remove-badge", badge })
			}, () => badge.label))
			.concat(slots.badges?.() || [])
		);

		const actionsNode = () => (props.actions.length || slots.actions) && h("div", { class: "option-card-actions" }, [
			slots.actions?.(),
			props.actions.map(action => h("button", {
				class: cx("btn", `btn-${action.variant || "secondary"}`, action.size ? `btn-${action.size}` : "btn-sm"),
				type: "button",
				onClick: () => emit("action", { type: action.key || action.label, action })
			}, action.icon && iconNode(action.icon), action.label))
		]);

		const statsNode = () => props.expanded && props.stats.length > 0 && h("div", { class: "option-card-stats" },
			props.stats.map(stat => h("div", { class: "option-card-stat" }, [
				iconNode(stat.icon),
				h("span", null, [stat.label, " ", h("strong", null, stat.value)])
			]))
		);

		const detailsNode = () => props.expanded && props.details.length > 0 && h("div", { class: "option-card-details" }, [
			h("ul", { class: "option-card-detail-list" }, props.details.map(item => h("li", { class: "option-card-detail-item" }, [
				h("span", null, [item.icon && iconNode(item.icon), item.label]),
				h("strong", null, item.value)
			])))
		]);

		return () => {
			const hasIcon = Boolean(props.icon) || slots.icon;

			return h("article", {
				...attrs,
				class: cx(
					"option-card",
					`option-card-${props.layout}`,
					`option-card--${props.size}`,
					{
						"option-card-expanded": props.expanded,
						"option-card--selectable": props.selectable,
						"is-selected": props.selected
					},
					attrs.class
				),
				role: props.selectable ? "option" : undefined,
				"aria-selected": props.selectable ? String(props.selected) : undefined,
				onClick: () => {
					if (props.selectable) {
						emit("update:selected", !props.selected);
					}
					emit("select");
				}
			}, [
				props.selectable && h("span", { class: cx("option-card__indicator", `option-card__indicator--${props.indicator === "check" ? "check" : "radio"}`) }),
				mediaNode(),
				h("div", { class: "option-card-body" }, [
					hasIcon && h("span", { class: "option-card__icon" }, slots.icon?.() || iconNode(props.icon)),
					headerNode(),
					props.title && h("h3", { class: "option-card-title" }, props.title),
					props.description && h("p", { class: "option-card-description" }, props.description),
					slots.default?.(),
					badgesNode(),
					actionsNode()
				]),
				statsNode(),
				detailsNode()
			]);
		};
	}
});