@osimart/create-storefront 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -2
- package/THEME_DEV_API.md +80 -0
- package/app/app.vue +154 -0
- package/app/assets/css/osi-controls.css +824 -0
- package/app/components/SectionRenderer.vue +46 -0
- package/app/components/builder/BuilderOverlay.vue +582 -0
- package/app/components/checkout/ExtraFees.vue +329 -0
- package/app/components/checkout/PaymentHandoff.vue +388 -0
- package/app/components/common/Banner.vue +35 -0
- package/app/components/common/ConfirmDeleteModal.vue +117 -0
- package/app/components/common/DropdownSelect.vue +432 -0
- package/app/components/common/Loader.vue +32 -0
- package/app/components/common/OsiField.vue +218 -0
- package/app/components/common/PhoneNumInput.vue +434 -0
- package/app/composables/UseBodyScrollLock.js +122 -0
- package/app/composables/UseThemeGroup.js +575 -0
- package/app/composables/useAddToCart.js +87 -0
- package/app/composables/useAnalytics.js +15 -0
- package/app/composables/useAnnouncements.js +29 -0
- package/app/composables/useApiErrorToast.js +104 -0
- package/app/composables/useAuth.js +99 -0
- package/app/composables/useBanners.js +25 -0
- package/app/composables/useBuilderPreview.js +320 -0
- package/app/composables/useButtonStyle.js +107 -0
- package/app/composables/useCarousel.js +160 -0
- package/app/composables/useCart.js +109 -0
- package/app/composables/useCheckout.js +76 -0
- package/app/composables/useClassificationItems.js +269 -0
- package/app/composables/useClassificationPage.js +545 -0
- package/app/composables/useClassificationUrl.js +63 -0
- package/app/composables/useColorScheme.js +44 -0
- package/app/composables/useContactForm.js +53 -0
- package/app/composables/useContentTree.js +770 -0
- package/app/composables/useDragScroll.js +73 -0
- package/app/composables/useFooterSocials.js +107 -0
- package/app/composables/useInputStyle.js +45 -0
- package/app/composables/useLayoutShell.js +114 -0
- package/app/composables/useLazyVideo.js +36 -0
- package/app/composables/useMediaTypes.js +66 -0
- package/app/composables/useMediaUrl.js +71 -0
- package/app/composables/useMenu.js +100 -0
- package/app/composables/useNavStyle.js +219 -0
- package/app/composables/usePlaceholders.js +294 -0
- package/app/composables/usePolicies.js +26 -0
- package/app/composables/useProductCardMedia.js +135 -0
- package/app/composables/useProductCardStyle.js +132 -0
- package/app/composables/useProductDetail.js +982 -0
- package/app/composables/useProductGallery.js +166 -0
- package/app/composables/useProductItems.js +270 -0
- package/app/composables/useProductUrl.js +60 -0
- package/app/composables/useProductVariants.js +210 -0
- package/app/composables/useQuickShop.js +115 -0
- package/app/composables/useRelatedProducts.js +186 -0
- package/app/composables/useRenderContext.js +45 -0
- package/app/composables/useScopedCss.js +106 -0
- package/app/composables/useSearch.js +35 -0
- package/app/composables/useSectionData.js +462 -0
- package/app/composables/useSectionShell.js +113 -0
- package/app/composables/useSidebar.js +69 -0
- package/app/composables/useStoreData.js +57 -0
- package/app/composables/useStoreInit.js +52 -0
- package/app/composables/useStorefront.js +239 -0
- package/app/composables/useT.js +21 -0
- package/app/composables/useTextStyles.js +84 -0
- package/app/composables/useThemeControls.js +34 -0
- package/app/composables/useThemeSeo.js +91 -0
- package/app/composables/useThemeTokens.js +171 -0
- package/app/composables/useWishlist.js +66 -0
- package/app/logic/components/AnnouncementBar.js +118 -0
- package/app/logic/components/CardGrid.js +125 -0
- package/app/logic/components/CardList.js +98 -0
- package/app/logic/components/CartDrawer.js +273 -0
- package/app/logic/components/CartItem.js +31 -0
- package/app/logic/components/ClassificationBento.js +6 -0
- package/app/logic/components/ClassificationCard.js +6 -0
- package/app/logic/components/ClassificationCarousel.js +115 -0
- package/app/logic/components/ClassificationEditorial.js +41 -0
- package/app/logic/components/ClassificationFeatured.js +57 -0
- package/app/logic/components/ClassificationGrid.js +6 -0
- package/app/logic/components/ClassificationNav.js +61 -0
- package/app/logic/components/ClassificationPart.js +72 -0
- package/app/logic/components/FooterBlock.js +91 -0
- package/app/logic/components/FooterSocials.js +60 -0
- package/app/logic/components/HeaderDropdownList.js +29 -0
- package/app/logic/components/HeaderIcon.js +68 -0
- package/app/logic/components/HeaderLogo.js +39 -0
- package/app/logic/components/HeaderNav.js +268 -0
- package/app/logic/components/MobileMenu.js +280 -0
- package/app/logic/components/ProductBuyButtons.js +66 -0
- package/app/logic/components/ProductCard.js +160 -0
- package/app/logic/components/ProductFilters.js +37 -0
- package/app/logic/components/ProductGallery.js +19 -0
- package/app/logic/components/ProductInfoBlock.js +411 -0
- package/app/logic/components/ProductLightbox.js +12 -0
- package/app/logic/components/ProductQuantity.js +31 -0
- package/app/logic/components/ProductVariantPicker.js +125 -0
- package/app/logic/components/QuickBuy.js +194 -0
- package/app/logic/components/SearchOverlay.js +41 -0
- package/app/logic/components/SidebarMenuItem.js +39 -0
- package/app/logic/components/SiteFooter.js +476 -0
- package/app/logic/components/SiteHeader.js +266 -0
- package/app/logic/components/WhatsAppButton.js +75 -0
- package/app/logic/layouts/default.js +66 -0
- package/app/logic/sections/AnnouncementBarSection.js +6 -0
- package/app/logic/sections/BlockCarousel.js +103 -0
- package/app/logic/sections/ClassificationListSection.js +40 -0
- package/app/logic/sections/ContactFormSection.js +294 -0
- package/app/logic/sections/ContactInfoSection.js +419 -0
- package/app/logic/sections/ContentNode.js +189 -0
- package/app/logic/sections/CountdownSection.js +254 -0
- package/app/logic/sections/CustomHtmlSection.js +51 -0
- package/app/logic/sections/DividerSection.js +31 -0
- package/app/logic/sections/FaqSection.js +53 -0
- package/app/logic/sections/FeatureListSection.js +6 -0
- package/app/logic/sections/FeaturedProductSection.js +40 -0
- package/app/logic/sections/FooterSection.js +16 -0
- package/app/logic/sections/FooterUtilitiesSection.js +24 -0
- package/app/logic/sections/GallerySection.js +232 -0
- package/app/logic/sections/HeaderSection.js +24 -0
- package/app/logic/sections/HeroSection.js +24 -0
- package/app/logic/sections/LogoListSection.js +6 -0
- package/app/logic/sections/MainClassificationSection.js +473 -0
- package/app/logic/sections/MainProductSection.js +211 -0
- package/app/logic/sections/MainSearchSection.js +6 -0
- package/app/logic/sections/MapSection.js +92 -0
- package/app/logic/sections/MediaContentSection.js +38 -0
- package/app/logic/sections/MediaWithTextSection.js +238 -0
- package/app/logic/sections/NewsletterSection.js +126 -0
- package/app/logic/sections/ProductHighlightSection.js +201 -0
- package/app/logic/sections/ProductListSection.js +45 -0
- package/app/logic/sections/ProductRecommendationsSection.js +113 -0
- package/app/logic/sections/RichTextSection.js +6 -0
- package/app/logic/sections/ScrollingTextSection.js +69 -0
- package/app/logic/sections/SearchHeaderSection.js +22 -0
- package/app/logic/sections/SlideshowSection.js +118 -0
- package/app/logic/sections/SpacerSection.js +37 -0
- package/app/logic/sections/SplitShowcaseSection.js +6 -0
- package/app/logic/sections/TestimonialListSection.js +6 -0
- package/app/logic/sections/VideoSection.js +235 -0
- package/app/pages/[category]/[product].vue +196 -0
- package/app/pages/__builder-preview.vue +38 -0
- package/app/pages/categories/[slug].vue +208 -0
- package/app/pages/checkout.vue +1972 -0
- package/app/pages/forgot-password.vue +115 -0
- package/app/pages/index.vue +64 -0
- package/app/pages/login.vue +585 -0
- package/app/pages/order-detail.vue +442 -0
- package/app/pages/pages/[handle].vue +58 -0
- package/app/pages/privacy-policy.vue +46 -0
- package/app/pages/resetpassword.vue +138 -0
- package/app/pages/resetverificationcode.vue +163 -0
- package/app/pages/result.vue +203 -0
- package/app/pages/return-policy.vue +50 -0
- package/app/pages/search.vue +100 -0
- package/app/pages/shipping-policy.vue +46 -0
- package/app/pages/user-profile/components/AddAddressModal.vue +452 -0
- package/app/pages/user-profile/components/Whishlist.vue +137 -0
- package/app/pages/user-profile/components/left-bar.vue +233 -0
- package/app/pages/user-profile/components/orders.vue +162 -0
- package/app/pages/user-profile/components/profile-details.vue +566 -0
- package/app/pages/user-profile/components/settings.vue +194 -0
- package/app/pages/user-profile/index.vue +252 -0
- package/app/pages/verificationcode.vue +188 -0
- package/app/plugins/analytics.client.js +99 -0
- package/app/plugins/geo.ts +24 -0
- package/app/plugins/reveal.client.js +53 -0
- package/app/plugins/toast.js +10 -0
- package/app/plugins/vue-query.ts +5 -0
- package/app/services/ApiServices.js +74 -0
- package/app/services/AuthBus.js +162 -0
- package/app/services/Authentication/AuthenticationService.js +133 -0
- package/app/services/Authentication/TokenService.js +62 -0
- package/app/services/CartServices.js +91 -0
- package/app/services/CartToken.js +26 -0
- package/app/services/CategoryServices.js +54 -0
- package/app/services/CheckoutServices.js +113 -0
- package/app/services/HomeServices.js +133 -0
- package/app/services/NewsletterServices.js +29 -0
- package/app/services/PaymentServices.js +71 -0
- package/app/services/PoliciesServices.js +16 -0
- package/app/services/ProductServices.js +79 -0
- package/app/services/SearchServices.js +18 -0
- package/app/services/UserServices.js +105 -0
- package/app/services/UtilsServices.js +46 -0
- package/app/services/WishlistServices.js +51 -0
- package/app/services/storefront.service.js +57 -0
- package/app/stores/userStore.js +13 -0
- package/app/theme/categoryTree.js +68 -0
- package/app/theme/controls.js +88 -0
- package/app/theme/registry.js +56 -0
- package/app/utils/analytics.js +864 -0
- package/app/utils/device.js +50 -0
- package/app/utils/eventBus.js +24 -0
- package/app/utils/icon-presets.js +24 -0
- package/app/utils/productOffers.js +118 -0
- package/app/utils/requestCache.js +36 -0
- package/app/utils/scrollToError.js +59 -0
- package/app/utils/toPlainError.js +30 -0
- package/bin/osimart-theme.mjs +259 -0
- package/nuxt/osi-sections.ts +56 -0
- package/nuxt.config.ts +60 -0
- package/package.json +43 -3
- package/public/css/input.css +29 -0
- package/public/css/output.css +2716 -0
- package/public/favicon-32x32.png +0 -0
- package/public/robots.txt +2 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quick buy — pick a product's options and add it without leaving the list.
|
|
3
|
+
* Opened by a product card's Buy button (PRODUCT_LIST) when the product has
|
|
4
|
+
* options; same selection / stock / add logic as the product page
|
|
5
|
+
* (useProductVariants + useQuickShop).
|
|
6
|
+
*
|
|
7
|
+
* SHEET a compact panel over the bottom of the card image; on phones a
|
|
8
|
+
* bottom sheet
|
|
9
|
+
* POPUP a quick-add dialog: gallery on one side, title / price / options /
|
|
10
|
+
* quantity / add on the other; a sheet with the gallery on top on
|
|
11
|
+
* phones
|
|
12
|
+
*
|
|
13
|
+
* Closing animates first and only then tells the card (`close` on
|
|
14
|
+
* after-leave) — the card unmounts this component, so emitting straight away
|
|
15
|
+
* cut the animation off.
|
|
16
|
+
*
|
|
17
|
+
* Every click inside stops at the panel: the card around it is a link.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
export default function useLogic({ props, emit }) {
|
|
22
|
+
const { isMobile } = useLayoutShell();
|
|
23
|
+
const { cartType, store } = useStorefront();
|
|
24
|
+
const { toggleCartSidebar } = useSidebar();
|
|
25
|
+
const { mediaUrl } = useMediaUrl();
|
|
26
|
+
const router = useRouter();
|
|
27
|
+
|
|
28
|
+
const variants = useProductVariants(() => props.product);
|
|
29
|
+
const quickShop = useQuickShop({
|
|
30
|
+
product: () => props.product,
|
|
31
|
+
variants,
|
|
32
|
+
// We close ourselves, after the leave animation (see close()).
|
|
33
|
+
closeOnAdd: false,
|
|
34
|
+
onAdded: () => {
|
|
35
|
+
afterClose = () => (cartType.value === "PAGE" ? router.push("/cart") : toggleCartSidebar(true));
|
|
36
|
+
shown.value = false;
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/* --------------------------------------------------------------- open / close
|
|
41
|
+
*
|
|
42
|
+
* Closing = fade out FIRST, then reset. Resetting at the start (the old way)
|
|
43
|
+
* cleared the picked options and unlocked page scroll mid-animation: the
|
|
44
|
+
* price, labels and swatches jumped and the scrollbar shifted the page under
|
|
45
|
+
* the fading dialog. Now `shown` drives the animation; the selection reset,
|
|
46
|
+
* the scroll unlock and the card's `close` all wait for after-leave.
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
const shown = ref(false);
|
|
50
|
+
const locked = ref(false);
|
|
51
|
+
let afterClose = null;
|
|
52
|
+
|
|
53
|
+
const inline = computed(() => props.mode === "SHEET" && !isMobile.value);
|
|
54
|
+
const popup = computed(() => props.mode === "POPUP");
|
|
55
|
+
|
|
56
|
+
onMounted(() => {
|
|
57
|
+
quickShop.open(props.mode === "SHEET" && isMobile.value ? "MOBILE" : "DESKTOP");
|
|
58
|
+
locked.value = true;
|
|
59
|
+
shown.value = true;
|
|
60
|
+
});
|
|
61
|
+
function close() {
|
|
62
|
+
shown.value = false;
|
|
63
|
+
}
|
|
64
|
+
function onAfterLeave() {
|
|
65
|
+
quickShop.close(); // resets the selection, releases the phone-sheet lock
|
|
66
|
+
locked.value = false;
|
|
67
|
+
emit("close");
|
|
68
|
+
afterClose?.();
|
|
69
|
+
afterClose = null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// The popup covers the page on every screen; the sheet only on phones
|
|
73
|
+
// (useQuickShop locks for that one).
|
|
74
|
+
useBodyScrollLock(() => locked.value && popup.value);
|
|
75
|
+
|
|
76
|
+
function onKey(e) {
|
|
77
|
+
if (e.key === "Escape" && shown.value) close();
|
|
78
|
+
}
|
|
79
|
+
onMounted(() => window.addEventListener("keydown", onKey));
|
|
80
|
+
onBeforeUnmount(() => window.removeEventListener("keydown", onKey));
|
|
81
|
+
|
|
82
|
+
const closeBtn = ref(null);
|
|
83
|
+
function onAfterEnter() {
|
|
84
|
+
if (!inline.value) closeBtn.value?.focus({ preventScroll: true });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* ------------------------------------------------------------------- details */
|
|
88
|
+
|
|
89
|
+
const isColor = (type) => ["color", "colour"].includes(String(type.name).toLowerCase());
|
|
90
|
+
const selected = computed(() => variants.selectedVariant.value);
|
|
91
|
+
const soldOut = computed(() => variants.stockFor(selected.value));
|
|
92
|
+
// The picked option has no price: shown as unavailable, can't be added.
|
|
93
|
+
const noPrice = computed(() => !!selected.value && !(Number(selected.value.price) > 0));
|
|
94
|
+
const currency = computed(
|
|
95
|
+
() => store.value?.default_currency?.symbol || store.value?.default_currency?.code || "$"
|
|
96
|
+
);
|
|
97
|
+
const money = (n) => (Number.isFinite(Number(n)) && Number(n) > 0 ? `${currency.value}${Number(n)}` : "");
|
|
98
|
+
|
|
99
|
+
/** The selected variant's price once chosen; the product's range before. */
|
|
100
|
+
const price = computed(() => {
|
|
101
|
+
if (selected.value) {
|
|
102
|
+
const sale = variants.variantDiscountedPrice(selected.value);
|
|
103
|
+
const now = sale ?? Number(selected.value.price);
|
|
104
|
+
const compare = Number(selected.value.compare_at_price) || 0;
|
|
105
|
+
const was = compare > now ? compare : sale != null ? Number(selected.value.price) : 0;
|
|
106
|
+
return { now: money(now), was: was ? money(was) : "", sale: sale != null };
|
|
107
|
+
}
|
|
108
|
+
const p = props.product;
|
|
109
|
+
const fmt = (v) =>
|
|
110
|
+
String(v ?? "")
|
|
111
|
+
.split("-")
|
|
112
|
+
.map((x) => money(parseFloat(x)))
|
|
113
|
+
.filter(Boolean)
|
|
114
|
+
.join(" – ");
|
|
115
|
+
const base = fmt(p.price_range);
|
|
116
|
+
const sale = fmt(p.discounted_price_range);
|
|
117
|
+
return sale && sale !== base ? { now: sale, was: base, sale: true } : { now: sale || base, was: "", sale: false };
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
/** "Black" next to "Color", once picked. */
|
|
121
|
+
function pickedName(type) {
|
|
122
|
+
const picked = variants.selectedOptions.value?.[type.id];
|
|
123
|
+
const id = picked?.id ?? picked;
|
|
124
|
+
if (!id) return "";
|
|
125
|
+
const v = (variants.applyCombinationLogic(type.id) ?? []).find((x) => x.id === id);
|
|
126
|
+
return v?.name ?? "";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* ------------------------------------------------------------------- gallery */
|
|
130
|
+
|
|
131
|
+
const images = computed(() => {
|
|
132
|
+
const p = props.product;
|
|
133
|
+
const out = [p.main_image, ...(p.gallery ?? []).map((g) => g?.media)].filter(Boolean);
|
|
134
|
+
const seen = new Set();
|
|
135
|
+
return out
|
|
136
|
+
.map((m) => mediaUrl(m))
|
|
137
|
+
.filter((src) => src && !seen.has(src) && seen.add(src));
|
|
138
|
+
});
|
|
139
|
+
const track = ref(null);
|
|
140
|
+
const slide = ref(0);
|
|
141
|
+
function onScroll() {
|
|
142
|
+
const el = track.value;
|
|
143
|
+
if (el) slide.value = Math.round(Math.abs(el.scrollLeft) / Math.max(1, el.clientWidth));
|
|
144
|
+
}
|
|
145
|
+
function goTo(i) {
|
|
146
|
+
const el = track.value;
|
|
147
|
+
if (!el) return;
|
|
148
|
+
const dir = getComputedStyle(el).direction === "rtl" ? -1 : 1;
|
|
149
|
+
el.scrollTo({ left: dir * i * el.clientWidth, behavior: "smooth" });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* --------------------------------------------------------------- description */
|
|
153
|
+
|
|
154
|
+
const description = computed(() => String(props.product.description || "").trim());
|
|
155
|
+
const descEl = ref(null);
|
|
156
|
+
const descLong = ref(false);
|
|
157
|
+
const descOpen = ref(false);
|
|
158
|
+
/** "View more" only when the text is actually cut. */
|
|
159
|
+
function measureDesc() {
|
|
160
|
+
const el = descEl.value;
|
|
161
|
+
descLong.value = !!el && el.scrollHeight > el.clientHeight + 2;
|
|
162
|
+
}
|
|
163
|
+
watch([shown, description], () => nextTick(measureDesc), { flush: "post" });
|
|
164
|
+
|
|
165
|
+
/* ------------------------------------------------------------------ quantity */
|
|
166
|
+
|
|
167
|
+
const step = computed(() => Number(props.product.quantity_step) || 1);
|
|
168
|
+
const qty = ref(step.value);
|
|
169
|
+
const dec = () => (qty.value = Math.max(step.value, qty.value - step.value));
|
|
170
|
+
const inc = () => (qty.value += step.value);
|
|
171
|
+
|
|
172
|
+
async function add() {
|
|
173
|
+
if (!variants.isSelectionComplete.value) {
|
|
174
|
+
quickShop.flashError(`Select a ${variants.missingVariantName.value}`);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (!selected.value) {
|
|
178
|
+
quickShop.flashError("That combination isn't available.");
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
await quickShop.addVariant(selected.value, qty.value);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const ctaLabel = computed(() =>
|
|
185
|
+
quickShop.errorMessage.value ||
|
|
186
|
+
(quickShop.adding.value
|
|
187
|
+
? "Adding…"
|
|
188
|
+
: soldOut.value
|
|
189
|
+
? "Sold out"
|
|
190
|
+
: "Add to cart") // no price: same label, the button is disabled
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
return { isMobile, cartType, store, toggleCartSidebar, mediaUrl, router, variants, quickShop, shown, locked, afterClose, inline, popup, close, onAfterLeave, onKey, closeBtn, onAfterEnter, isColor, selected, soldOut, noPrice, currency, money, price, pickedName, images, track, slide, onScroll, goTo, description, descEl, descLong, descOpen, measureDesc, step, qty, dec, inc, add, ctaLabel };
|
|
194
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
|
|
2
|
+
export default function useLogic({ props, emit }) {
|
|
3
|
+
const router = useRouter();
|
|
4
|
+
const query = ref("");
|
|
5
|
+
const error = ref("");
|
|
6
|
+
const input = ref(null);
|
|
7
|
+
|
|
8
|
+
useBodyScrollLock(() => props.open);
|
|
9
|
+
|
|
10
|
+
watch(
|
|
11
|
+
() => props.open,
|
|
12
|
+
async (open) => {
|
|
13
|
+
if (!open) {
|
|
14
|
+
query.value = "";
|
|
15
|
+
error.value = "";
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
await nextTick();
|
|
19
|
+
input.value?.focus();
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
function submit() {
|
|
24
|
+
const value = query.value.trim();
|
|
25
|
+
if (!value) {
|
|
26
|
+
// Terazar shook the field with no message. A shake alone doesn't tell a
|
|
27
|
+
// screen-reader user anything, so the message carries the meaning and the
|
|
28
|
+
// shake is decoration on top.
|
|
29
|
+
error.value = "Enter something to search for.";
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
emit("close");
|
|
33
|
+
router.push(`/search?q=${encodeURIComponent(value)}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function clearError() {
|
|
37
|
+
error.value = "";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return { router, query, error, input, submit, clearError };
|
|
41
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One sidebar row, recursive.
|
|
3
|
+
*
|
|
4
|
+
* Nesting depth is whatever the merchant's menu has, so the component renders
|
|
5
|
+
* itself rather than assuming two levels. SIDEBAR_MAX_DEPTH stops the
|
|
6
|
+
* recursion — a menu that accidentally references itself would otherwise
|
|
7
|
+
* recurse until the tab dies, and a 6-deep menu in a 380px drawer is unusable
|
|
8
|
+
* long before that.
|
|
9
|
+
*
|
|
10
|
+
* Expansion state is owned by the drawer and reached through inject(), not
|
|
11
|
+
* passed down: a level-3 item has to be able to close its level-3 siblings,
|
|
12
|
+
* and threading that through every intermediate level as props is how the
|
|
13
|
+
* "close others" setting ends up working on some levels and not others.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
export default function useLogic({ props }) {
|
|
18
|
+
const nav = inject("osi-sidebar-nav");
|
|
19
|
+
|
|
20
|
+
const canExpand = computed(
|
|
21
|
+
() => props.item.children?.length && props.depth + 1 < nav.maxDepth.value
|
|
22
|
+
);
|
|
23
|
+
const isOpen = computed(() => nav.isOpen(props.item.id));
|
|
24
|
+
const isActive = computed(() => nav.isActive(props.item));
|
|
25
|
+
|
|
26
|
+
// A parent deeper than the top level that links somewhere is two controls:
|
|
27
|
+
// its name goes to its page, its arrow opens its submenu. (Top-level items
|
|
28
|
+
// toggle on the whole row; their page is the backend's "View all" row.)
|
|
29
|
+
const split = computed(
|
|
30
|
+
() => canExpand.value && props.depth > 0 && !!props.item.url && props.item.url !== "/" && props.item.url !== "#"
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const childIds = computed(() => (props.item.children ?? []).map((c) => c.id));
|
|
34
|
+
const rowStyle = computed(() => ({
|
|
35
|
+
paddingInlineStart: `calc(var(--osi-sb-pad-x) + ${props.depth} * var(--osi-sb-indent))`,
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
return { nav, canExpand, isOpen, isActive, split, childIds, rowStyle };
|
|
39
|
+
}
|