Newer
Older
vmk-ui-kit / src / vue / components / GnOptionCard.js
/**
 * 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
		image: { type: String, default: "" },
		match: { type: [String, Number], default: "" },
		price: { type: String, default: "" },
		area: { type: String, default: "" },
		title: { type: String, default: "" },
		badges: { type: Array, default: () => [] },
		expanded: { type: Boolean, default: false },
		stats: { type: Array, default: () => [] },
		details: { type: Array, default: () => [] },
		actions: { type: Array, default: () => [] }
	},
	emits: ["action", "select"],
	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 () => h("article", {
			...attrs,
			class: cx(
				"option-card",
				`option-card-${props.layout}`,
				{ "option-card-expanded": props.expanded },
				attrs.class
			),
			onClick: () => emit("select")
		}, [
			mediaNode(),
			h("div", { class: "option-card-body" }, [
				headerNode(),
				props.title && h("h3", { class: "option-card-title" }, props.title),
				slots.default?.(),
				badgesNode(),
				actionsNode()
			]),
			statsNode(),
			detailsNode()
		]);
	}
});