UNPKG

@availity/favorites

Version:

Favorite Heart for favoriting items such as links/resources etc.

438 lines (426 loc) 19.6 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); var __objRest = (source, exclude) => { var target = {}; for (var prop in source) if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0) target[prop] = source[prop]; if (source != null && __getOwnPropSymbols) for (var prop of __getOwnPropSymbols(source)) { if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop)) target[prop] = source[prop]; } return target; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; // src/index.ts var index_exports = {}; __export(index_exports, { FavoriteHeart: () => FavoriteHeart, FavoritesProvider: () => FavoritesProvider, default: () => FavoritesProvider, useFavoritesQuery: () => useFavoritesQuery }); module.exports = __toCommonJS(index_exports); // src/Icons.tsx var import_jsx_runtime = require("react/jsx-runtime"); var IconBase = ({ className, style, color, title, "aria-hidden": ariaHidden = false, size, pathData }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)( "svg", { style: __spreadValues({ color }, style), className, "aria-hidden": ariaHidden, xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 512 512", height: size, width: size, children: [ !!title && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("title", { children: title }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "currentColor", d: pathData }) ] } ); var HeartFilled = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)( IconBase, __spreadProps(__spreadValues({}, props), { pathData: "M0 190.9V185.1C0 115.2 50.52 55.58 119.4 44.1C164.1 36.51 211.4 51.37 244 84.02L256 96L267.1 84.02C300.6 51.37 347 36.51 392.6 44.1C461.5 55.58 512 115.2 512 185.1V190.9C512 232.4 494.8 272.1 464.4 300.4L283.7 469.1C276.2 476.1 266.3 480 256 480C245.7 480 235.8 476.1 228.3 469.1L47.59 300.4C17.23 272.1 .0003 232.4 .0003 190.9L0 190.9z" }) ); var HeartOutlined = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)( IconBase, __spreadProps(__spreadValues({}, props), { pathData: "M244 84L255.1 96L267.1 84.02C300.6 51.37 347 36.51 392.6 44.1C461.5 55.58 512 115.2 512 185.1V190.9C512 232.4 494.8 272.1 464.4 300.4L283.7 469.1C276.2 476.1 266.3 480 256 480C245.7 480 235.8 476.1 228.3 469.1L47.59 300.4C17.23 272.1 0 232.4 0 190.9V185.1C0 115.2 50.52 55.58 119.4 44.1C164.1 36.51 211.4 51.37 244 84C243.1 84 244 84.01 244 84L244 84zM255.1 163.9L210.1 117.1C188.4 96.28 157.6 86.4 127.3 91.44C81.55 99.07 48 138.7 48 185.1V190.9C48 219.1 59.71 246.1 80.34 265.3L256 429.3L431.7 265.3C452.3 246.1 464 219.1 464 190.9V185.1C464 138.7 430.4 99.07 384.7 91.44C354.4 86.4 323.6 96.28 301.9 117.1L255.1 163.9z" }) ); var Spinner = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)( IconBase, __spreadProps(__spreadValues({}, props), { pathData: "M304 48C304 74.51 282.5 96 256 96C229.5 96 208 74.51 208 48C208 21.49 229.5 0 256 0C282.5 0 304 21.49 304 48zM304 464C304 490.5 282.5 512 256 512C229.5 512 208 490.5 208 464C208 437.5 229.5 416 256 416C282.5 416 304 437.5 304 464zM0 256C0 229.5 21.49 208 48 208C74.51 208 96 229.5 96 256C96 282.5 74.51 304 48 304C21.49 304 0 282.5 0 256zM512 256C512 282.5 490.5 304 464 304C437.5 304 416 282.5 416 256C416 229.5 437.5 208 464 208C490.5 208 512 229.5 512 256zM74.98 437C56.23 418.3 56.23 387.9 74.98 369.1C93.73 350.4 124.1 350.4 142.9 369.1C161.6 387.9 161.6 418.3 142.9 437C124.1 455.8 93.73 455.8 74.98 437V437zM142.9 142.9C124.1 161.6 93.73 161.6 74.98 142.9C56.24 124.1 56.24 93.73 74.98 74.98C93.73 56.23 124.1 56.23 142.9 74.98C161.6 93.73 161.6 124.1 142.9 142.9zM369.1 369.1C387.9 350.4 418.3 350.4 437 369.1C455.8 387.9 455.8 418.3 437 437C418.3 455.8 387.9 455.8 369.1 437C350.4 418.3 350.4 387.9 369.1 369.1V369.1z" }) ); // src/FavoriteHeart.module.scss var FavoriteHeart_module_default = { "screenReaderOnly": "_screenReaderOnly_d12e7_1", "root": "_root_d12e7_13", "input": "_input_d12e7_21", "disabled": "_disabled_d12e7_38", "icons": "_icons_d12e7_42", "icon": "_icon_d12e7_42", "spinner": "_spinner_d12e7_59", "spin": "_spin_d12e7_59" }; // src/context/FavoritesProvider.tsx var import_react = require("react"); var import_message_core2 = __toESM(require("@availity/message-core")); var import_react_query2 = require("@tanstack/react-query"); // src/context/constants.ts var MAX_FAVORITES = 60; var NAV_APP_ID = "Gateway-AvNavigation"; var AV_INTERNAL_GLOBALS = { FAVORITES_UPDATE: "av:favorites:update", FAVORITES_CHANGED: "av:favorites:changed", MAX_FAVORITES: "av:favorites:maxed", MY_TOP_APPS_UPDATED: "av:topApps:updated" }; // src/context/utils.ts var import_message_core = __toESM(require("@availity/message-core")); var import_api_axios = require("@availity/api-axios"); var import_react_query = require("@tanstack/react-query"); var isFavorite = (arg) => Boolean(typeof (arg == null ? void 0 : arg.id) === "string" && typeof (arg == null ? void 0 : arg.pos) === "number"); var validateFavorites = (unvalidatedFavorites) => { const validatedFavorites = Array.isArray(unvalidatedFavorites) ? unvalidatedFavorites == null ? void 0 : unvalidatedFavorites.filter(isFavorite) : []; return validatedFavorites; }; var submit = (_0, _1) => __async(void 0, [_0, _1], function* ({ favorites, targetFavoriteId }, applicationId) { const response = yield import_api_axios.avSettingsApi.setApplication(applicationId, { favorites }); return { favorites: response.data.favorites, targetFavoriteId }; }); var getFavorites = (applicationId) => __async(void 0, null, function* () { var _a, _b, _c; const result = yield import_api_axios.avSettingsApi.getApplication(applicationId); const unvalidatedFavorites = (_c = (_b = (_a = result == null ? void 0 : result.data) == null ? void 0 : _a.settings) == null ? void 0 : _b[0]) == null ? void 0 : _c.favorites; const validatedFavorites = validateFavorites(unvalidatedFavorites); return validatedFavorites; }); var useFavoritesQuery = (applicationId) => (0, import_react_query.useQuery)(["favorites"], () => getFavorites(applicationId)); var useSubmitFavorites = ({ onMutationStart }, applicationId) => { const queryClient = (0, import_react_query.useQueryClient)(); const _a = (0, import_react_query.useMutation)( ({ favorites, targetFavoriteId }) => submit({ favorites, targetFavoriteId }, applicationId), { onMutate(variables) { onMutationStart == null ? void 0 : onMutationStart(variables.targetFavoriteId); }, onSuccess(data) { queryClient.setQueryData(["favorites"], data.favorites); } } ), { mutateAsync: submitFavorites } = _a, rest = __objRest(_a, ["mutateAsync"]); return __spreadValues({ submitFavorites }, rest); }; var sendUpdateMessage = (favorites, applicationId) => { if (applicationId === NAV_APP_ID) { import_message_core.default.send({ favorites, event: AV_INTERNAL_GLOBALS.FAVORITES_UPDATE }); } }; var openMaxModal = (applicationId) => { if (applicationId === NAV_APP_ID) { return import_message_core.default.send(AV_INTERNAL_GLOBALS.MAX_FAVORITES); } return null; }; // src/context/FavoritesProvider.tsx var import_jsx_runtime2 = require("react/jsx-runtime"); var FavoritesContext = (0, import_react.createContext)(null); var FavoritesProvider = ({ children, onFavoritesChange, applicationId = NAV_APP_ID, maxFavorites = MAX_FAVORITES }) => { const [lastClickedFavoriteId, setLastClickedFavoriteId] = (0, import_react.useState)(""); const queryClient = (0, import_react_query2.useQueryClient)(); const { data: favorites, status: queryStatus } = useFavoritesQuery(applicationId); const { submitFavorites, status: mutationStatus } = useSubmitFavorites({ onMutationStart(targetFavoriteId) { setLastClickedFavoriteId(targetFavoriteId); } }, applicationId); (0, import_react.useEffect)(() => { if (applicationId === NAV_APP_ID) { const unsubscribeFavoritesChanged = import_message_core2.default.subscribe( AV_INTERNAL_GLOBALS.FAVORITES_CHANGED, (data) => { if (data == null ? void 0 : data.favorites) { queryClient.setQueryData(["favorites"], data == null ? void 0 : data.favorites); } }, { ignoreSameWindow: false } ); const unsubscribeFavoritesUpdate = import_message_core2.default.subscribe( AV_INTERNAL_GLOBALS.FAVORITES_UPDATE, (data) => { if (data == null ? void 0 : data.favorites) { queryClient.setQueryData(["favorites"], data == null ? void 0 : data.favorites); } }, { ignoreSameWindow: false } ); return () => { unsubscribeFavoritesChanged(); unsubscribeFavoritesUpdate(); }; } return () => null; }, [queryClient, applicationId]); const deleteFavorite = (id) => __async(void 0, null, function* () { if (favorites) { const response = yield submitFavorites({ favorites: favorites.filter((favorite) => favorite.id !== id), targetFavoriteId: id }); sendUpdateMessage(response.favorites, applicationId); onFavoritesChange == null ? void 0 : onFavoritesChange(response.favorites); } }); const addFavorite = (id) => __async(void 0, null, function* () { if (!favorites) return false; if (favorites.length >= maxFavorites) { openMaxModal(applicationId); return false; } const maxFavorite = favorites.reduce((accum, fave) => { if (!accum || fave.pos > accum.pos) { accum = fave; } return accum; }, null); const newFavPos = maxFavorite ? maxFavorite.pos + 1 : 0; const response = yield submitFavorites({ favorites: [...favorites, { id, pos: newFavPos }], targetFavoriteId: id }); sendUpdateMessage(response.favorites, applicationId); onFavoritesChange == null ? void 0 : onFavoritesChange(response.favorites); const isFavorited = response.favorites.find((f) => f.id === id); return !!isFavorited; }); return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( FavoritesContext.Provider, { value: { favorites, queryStatus, mutationStatus, lastClickedFavoriteId, deleteFavorite, addFavorite }, children } ); }; var noOp = () => { }; var useFavorites = (id) => { const context = (0, import_react.useContext)(FavoritesContext); if (context === null) { throw new Error("useFavorites must be used within a FavoritesProvider"); } const { favorites, queryStatus, mutationStatus, lastClickedFavoriteId, deleteFavorite, addFavorite } = context; const isLastClickedFavorite = lastClickedFavoriteId === id; const isFavorited = (0, import_react.useMemo)(() => { const fav = favorites == null ? void 0 : favorites.find((f) => f.id === id); return !!fav; }, [favorites, id]); const toggleFavorite = () => isFavorited ? deleteFavorite(id) : addFavorite(id); const isDisabled = queryStatus === "loading" || queryStatus === "idle" || mutationStatus === "loading"; let status = "initLoading"; if (queryStatus === "loading") status = "initLoading"; if (mutationStatus === "loading") status = "reloading"; if (queryStatus === "error" || mutationStatus === "error") status = "error"; if (queryStatus === "success" && (mutationStatus === "success" || mutationStatus === "idle")) status = "success"; return { isFavorited, status, isLastClickedFavorite, toggleFavorite: isDisabled ? noOp : toggleFavorite }; }; // src/components/FavoritesTooltip/FavoritesTooltip.tsx var import_react_tooltip = require("@radix-ui/react-tooltip"); // src/components/FavoritesTooltip/FavoritesTooltip.module.scss var FavoritesTooltip_module_default = { "arrow": "_arrow_1r76w_1", "content": "_content_1r76w_5" }; // src/components/FavoritesTooltip/FavoritesTooltip.tsx var import_jsx_runtime3 = require("react/jsx-runtime"); var Tooltip = ({ children, content, "data-testid": dataTestId }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_tooltip.Provider, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_tooltip.Root, { children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_tooltip.Trigger, { asChild: true, children }), /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_tooltip.Content, { side: "top", className: FavoritesTooltip_module_default.content, "data-testid": dataTestId, children: [ content, /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_tooltip.Arrow, { className: FavoritesTooltip_module_default.arrow, offset: 7 }) ] }) ] }) }); var FavoritesTooltip_default = Tooltip; // src/FavoriteHeart.tsx var import_jsx_runtime4 = require("react/jsx-runtime"); var RED = "#ed5624"; var GREY = "#4d4f53"; var DISABLED_OPACITY = 0.6; var INIT_LOADING_OPACITY = 0.5; var icons = { // Loading icon spinner: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Spinner, { "aria-hidden": true, className: `${FavoriteHeart_module_default.icon} ${FavoriteHeart_module_default.spinner}`, style: { color: GREY, opacity: DISABLED_OPACITY } }), // disabled heart icons unknownDisabledHeart: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(HeartFilled, { "aria-hidden": true, className: FavoriteHeart_module_default.icon, style: { color: GREY, opacity: INIT_LOADING_OPACITY } }), favoritedDisabledHeart: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(HeartFilled, { "aria-hidden": true, className: FavoriteHeart_module_default.icon, style: { color: RED, opacity: DISABLED_OPACITY } }), unfavoritedDisabledHeart: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(HeartOutlined, { "aria-hidden": true, className: FavoriteHeart_module_default.icon, style: { color: GREY, opacity: DISABLED_OPACITY } }), // regular heart icons favoritedHeart: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(HeartFilled, { "aria-hidden": true, className: FavoriteHeart_module_default.icon, style: { color: RED } }), unfavoritedHeart: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(HeartOutlined, { "aria-hidden": true, className: FavoriteHeart_module_default.icon, style: { color: GREY } }) }; var FavoriteHeart = ({ id, name, onChange, onMouseDown, disabled = false, size }) => { const { isFavorited, isLastClickedFavorite, status, toggleFavorite } = useFavorites(id); const handleChange = (event) => { onChange == null ? void 0 : onChange(isFavorited, event); toggleFavorite(); }; const handleKeyPress = (event) => { if (event.code === "Enter" || event.key === "Enter") { onChange == null ? void 0 : onChange(isFavorited, event); toggleFavorite(); } }; const iconKey = (() => { if (status === "initLoading") return "unknownDisabledHeart"; if (status === "reloading") { if (isLastClickedFavorite) return "spinner"; return isFavorited ? "favoritedDisabledHeart" : "unfavoritedDisabledHeart"; } if (disabled) { return isFavorited ? "favoritedDisabledHeart" : "unfavoritedDisabledHeart"; } if (isFavorited) return "favoritedHeart"; return "unfavoritedHeart"; })(); const cursor = disabled || !isLastClickedFavorite && (status === "initLoading" || status === "reloading") ? "not-allowed" : void 0; const tooltipContent = `${isFavorited ? "Remove from" : "Add to"} My Favorites`; return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: `${FavoriteHeart_module_default.root} ${disabled ? FavoriteHeart_module_default.disabled : ""}`, style: { height: size, width: size }, children: [ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: FavoriteHeart_module_default.icons, children: icons[iconKey] }), /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( "span", { "aria-live": isLastClickedFavorite && (status === "reloading" || status === "error") ? "polite" : "off", className: FavoriteHeart_module_default.screenReaderOnly, children: isLastClickedFavorite && status === "reloading" ? "Loading..." : isLastClickedFavorite && status === "error" ? "An error has occurred. Please try again." : "" } ), /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FavoritesTooltip_default, { content: tooltipContent, "data-testid": `av-favorite-heart-${id}-tooltip`, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( "input", { style: { cursor, height: size, width: size }, className: FavoriteHeart_module_default.input, onKeyPress: handleKeyPress, type: "checkbox", "aria-label": `Favorite ${name}`, id: `av-favorite-heart-${id}`, disabled, checked: isFavorited, onChange: handleChange, onMouseDown } ) }) ] }); }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { FavoriteHeart, FavoritesProvider, useFavoritesQuery });