@meeovi/layer-shared 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 +0 -0
- package/app/components/Gallery/Gallery.vue +187 -0
- package/app/components/Gallery/__tests__/Gallery.spec.ts +14 -0
- package/app/components/Heading/Heading.vue +14 -0
- package/app/components/Heading/__tests__/Heading.spec.ts +14 -0
- package/app/components/Heading/types.ts +5 -0
- package/app/components/media/audioGallery.vue +70 -0
- package/app/components/media/dragDropUpload.vue +67 -0
- package/app/components/media/fullscreenMediaModal.vue +66 -0
- package/app/components/media/imageCard.vue +65 -0
- package/app/components/media/imageGallery.vue +40 -0
- package/app/components/media/mediaCard.vue +89 -0
- package/app/components/media/mediaCarousel.vue +65 -0
- package/app/components/media/mediaFolderSidebar.vue +72 -0
- package/app/components/media/mediaPlayer.vue +40 -0
- package/app/components/media/mediaSearchBar.vue +16 -0
- package/app/components/media/videoGallery.vue +77 -0
- package/app/components/ui/AccordionItem/AccordionItem.vue +24 -0
- package/app/components/ui/AccordionItem/__tests__/AccordionItem.spec.ts +14 -0
- package/app/components/ui/AccordionItem/types.ts +5 -0
- package/app/components/ui/Alert/Alert.vue +34 -0
- package/app/components/ui/Alert/types.ts +5 -0
- package/app/components/ui/Breadcrumbs/Breadcrumbs.vue +76 -0
- package/app/components/ui/Breadcrumbs/__tests__/Breadcrumbs.spec.ts +14 -0
- package/app/components/ui/Breadcrumbs/types.ts +8 -0
- package/app/components/ui/CartProductCard/CartProductCard.vue +66 -0
- package/app/components/ui/CartProductCard/types.ts +18 -0
- package/app/components/ui/CategoryCard/CategoryCard.vue +41 -0
- package/app/components/ui/CategoryCard/types.ts +9 -0
- package/app/components/ui/Display/Display.vue +55 -0
- package/app/components/ui/Display/types.ts +12 -0
- package/app/components/ui/Divider/Divider.vue +3 -0
- package/app/components/ui/Divider/__tests__/Divider.spec.tsx +10 -0
- package/app/components/ui/Footer.vue +92 -0
- package/app/components/ui/Form/FormHelperText.vue +5 -0
- package/app/components/ui/Form/FormLabel.vue +5 -0
- package/app/components/ui/Form/FormPasswordInput.vue +15 -0
- package/app/components/ui/Form/__tests__/FormHelperText.spec.ts +10 -0
- package/app/components/ui/Form/__tests__/FormLabel.spec.ts +10 -0
- package/app/components/ui/Hero/Hero.vue +44 -0
- package/app/components/ui/Hero/types.ts +10 -0
- package/app/components/ui/Modal/Modal.vue +19 -0
- package/app/components/ui/Modal/types.ts +8 -0
- package/app/components/ui/Motionable.vue +45 -0
- package/app/components/ui/NavbarBottom.vue +63 -0
- package/app/components/ui/NavbarTop.vue +25 -0
- package/app/components/ui/Overlay/Overlay.vue +14 -0
- package/app/components/ui/Overlay/__tests__/Overlay.spec.ts +14 -0
- package/app/components/ui/Overlay/types.ts +3 -0
- package/app/components/ui/PageBuilder.vue +39 -0
- package/app/components/ui/PageContainer.vue +5 -0
- package/app/components/ui/Pagination/Pagination.vue +151 -0
- package/app/components/ui/Pagination/__tests__/Pagination.spec.ts +17 -0
- package/app/components/ui/Pagination/types.ts +6 -0
- package/app/components/ui/ProductCard/ProductCard.vue +55 -0
- package/app/components/ui/ProductCard/__tests__/ProductCard.spec.ts +16 -0
- package/app/components/ui/ProductCard/types.ts +12 -0
- package/app/components/ui/ProductCardHorizontal/ProductCardHorizontal.vue +34 -0
- package/app/components/ui/ProductCardHorizontal/__tests__/ProductCardHorizontal.spec.ts +36 -0
- package/app/components/ui/ProductCardHorizontal/types.ts +8 -0
- package/app/components/ui/PurchaseCard/PurchaseCard.vue +109 -0
- package/app/components/ui/PurchaseCard/__tests__/PurchaseCard.spec.ts +15 -0
- package/app/components/ui/PurchaseCard/types.ts +5 -0
- package/app/components/ui/QuantitySelector/QuantitySelector.vue +69 -0
- package/app/components/ui/QuantitySelector/__tests__/QuantitySelector.spec.ts +15 -0
- package/app/components/ui/QuantitySelector/types.ts +5 -0
- package/app/components/ui/RadialProgress.vue +44 -0
- package/app/components/ui/Review/Review.vue +57 -0
- package/app/components/ui/Review/__tests__/Review.spec.ts +15 -0
- package/app/components/ui/Review/types.ts +5 -0
- package/app/components/ui/ScrollTop.vue +82 -0
- package/app/components/ui/Search.vue +54 -0
- package/app/components/ui/Tag/Tag.vue +38 -0
- package/app/components/ui/Tag/__tests__/Tag.spec.ts +10 -0
- package/app/components/ui/Tag/types.ts +16 -0
- package/app/components/ui/VsfLogo.vue +7 -0
- package/app/components/ui/forms/BooleanInput.vue +34 -0
- package/app/components/ui/forms/DateTime.vue +44 -0
- package/app/components/ui/forms/DirectusFormElement.vue +60 -0
- package/app/components/ui/forms/DynamicTableElement.vue +57 -0
- package/app/components/ui/forms/FileInput.vue +85 -0
- package/app/components/ui/forms/FormField.vue +34 -0
- package/app/components/ui/forms/RelationSelect.vue +63 -0
- package/app/components/ui/forms/RepeaterInput.vue +121 -0
- package/app/components/ui/forms/SelectInput.vue +65 -0
- package/app/components/ui/forms/TextArea.vue +59 -0
- package/app/components/ui/forms/TextInput.vue +42 -0
- package/app/components/ui/forms/TiptapEditor.vue +136 -0
- package/app/components/ui/forms/[collection].vue +22 -0
- package/app/components/ui/studio/builder.vue +57 -0
- package/app/components/ui/studio/document.vue +69 -0
- package/app/components/ui/studio/email.vue +57 -0
- package/app/composables/globals/uploadFiles.js +41 -0
- package/app/composables/globals/useAdminTable.ts +12 -0
- package/app/composables/globals/useCustomFetch.ts +13 -0
- package/app/composables/globals/useDirectusField.ts +144 -0
- package/app/composables/globals/useDirectusForm.ts +70 -0
- package/app/composables/globals/useDirectusSchema.js +9 -0
- package/app/composables/globals/useFileManager.ts +76 -0
- package/app/composables/globals/useLivePreview.ts +17 -0
- package/app/composables/globals/useLoading.ts +23 -0
- package/app/composables/globals/useNavigation.js +19 -0
- package/app/composables/globals/useNotifications.ts +153 -0
- package/app/composables/globals/usePages.js +36 -0
- package/app/composables/globals/useRichText.ts +33 -0
- package/app/composables/globals/useServerRootMixin.ts +19 -0
- package/app/composables/globals/useVisualEditing.ts +38 -0
- package/app/composables/media/useFile.ts +6 -0
- package/app/composables/media/useMediaCenter.ts +353 -0
- package/app/composables/media/useVideojs.ts +45 -0
- package/app/composables/registry.ts +13 -0
- package/app/composables/types.ts +12 -0
- package/app/composables/useContent.ts +13 -0
- package/app/composables/useDirectusRequest.ts +32 -0
- package/app/stores/index.ts +0 -0
- package/app/types/api/global-search.ts +8 -0
- package/app/types/blocks/block-button-group.ts +7 -0
- package/app/types/blocks/block-button.ts +14 -0
- package/app/types/blocks/block-column.ts +20 -0
- package/app/types/blocks/block-cta.ts +10 -0
- package/app/types/blocks/block-divider.ts +4 -0
- package/app/types/blocks/block-faq.ts +12 -0
- package/app/types/blocks/block-form.ts +8 -0
- package/app/types/blocks/block-gallery.ts +14 -0
- package/app/types/blocks/block-hero.ts +12 -0
- package/app/types/blocks/block-html.ts +4 -0
- package/app/types/blocks/block-logocloud.ts +14 -0
- package/app/types/blocks/block-quote.ts +11 -0
- package/app/types/blocks/block-richtext.ts +7 -0
- package/app/types/blocks/block-steps.ts +22 -0
- package/app/types/blocks/block-team.ts +6 -0
- package/app/types/blocks/block-testimonial.ts +14 -0
- package/app/types/blocks/block-video.ts +10 -0
- package/app/types/blocks/block.ts +49 -0
- package/app/types/blocks/index.ts +18 -0
- package/app/types/componentMap.ts +15 -0
- package/app/types/content/category.ts +11 -0
- package/app/types/content/form.ts +20 -0
- package/app/types/content/index.ts +6 -0
- package/app/types/content/page.ts +76 -0
- package/app/types/content/post.ts +39 -0
- package/app/types/content/team.ts +16 -0
- package/app/types/content/testimonial.ts +19 -0
- package/app/types/directus.d.ts +47 -0
- package/app/types/env.d.ts +8 -0
- package/app/types/help/index.ts +53 -0
- package/app/types/index.d.ts +9 -0
- package/app/types/index.ts +7 -0
- package/app/types/meta/analytics.ts +18 -0
- package/app/types/meta/config.ts +21 -0
- package/app/types/meta/globals.ts +30 -0
- package/app/types/meta/index.ts +6 -0
- package/app/types/meta/navigation.ts +32 -0
- package/app/types/meta/redirect.ts +13 -0
- package/app/types/meta/seo.ts +19 -0
- package/app/types/os/contact.ts +23 -0
- package/app/types/os/conversation.ts +25 -0
- package/app/types/os/index.ts +16 -0
- package/app/types/os/organization.ts +54 -0
- package/app/types/os/os-activity.ts +28 -0
- package/app/types/os/os-deal.ts +45 -0
- package/app/types/os/os-expense.ts +22 -0
- package/app/types/os/os-invoice.ts +48 -0
- package/app/types/os/os-item.ts +18 -0
- package/app/types/os/os-payment.ts +29 -0
- package/app/types/os/os-project.ts +47 -0
- package/app/types/os/os-proposal.ts +84 -0
- package/app/types/os/os-settings.ts +19 -0
- package/app/types/os/os-subscription.ts +12 -0
- package/app/types/os/os-task.ts +34 -0
- package/app/types/os/os-tax-rate.ts +13 -0
- package/app/types/pageComponentMap.ts +8 -0
- package/app/types/schema.d.ts +39 -0
- package/app/types/schema.ts +151 -0
- package/app/types/system/file.ts +46 -0
- package/app/types/system/folder.ts +8 -0
- package/app/types/system/index.ts +4 -0
- package/app/types/system/role.ts +21 -0
- package/app/types/system/user.ts +56 -0
- package/app/utils/Timer.js +44 -0
- package/app/utils/billing-address.ts +24 -0
- package/app/utils/color.ts +14 -0
- package/app/utils/currency.ts +29 -0
- package/app/utils/embed.ts +57 -0
- package/app/utils/errors.ts +9 -0
- package/app/utils/fieldRegistry.js +89 -0
- package/app/utils/fonts.ts +24 -0
- package/app/utils/formkit.ts +75 -0
- package/app/utils/icons.ts +62 -0
- package/app/utils/index.js +0 -0
- package/app/utils/links.ts +28 -0
- package/app/utils/lodash.ts +33 -0
- package/app/utils/markdown.ts +9 -0
- package/app/utils/math.ts +25 -0
- package/app/utils/navigation.ts +11 -0
- package/app/utils/objects.ts +11 -0
- package/app/utils/paths.ts +21 -0
- package/app/utils/relations.ts +33 -0
- package/app/utils/strings.ts +113 -0
- package/app/utils/time.ts +148 -0
- package/app/utils/url.ts +22 -0
- package/app/utils/user-name.ts +21 -0
- package/app/utils/video/README.md +51 -0
- package/app/utils/video/playlist.js +0 -0
- package/dist/api/global-search.d.ts +8 -0
- package/dist/api/global-search.js +1 -0
- package/dist/blocks/block-button-group.d.ts +6 -0
- package/dist/blocks/block-button-group.js +1 -0
- package/dist/blocks/block-button.d.ts +13 -0
- package/dist/blocks/block-button.js +1 -0
- package/dist/blocks/block-column.d.ts +18 -0
- package/dist/blocks/block-column.js +1 -0
- package/dist/blocks/block-cta.d.ts +11 -0
- package/dist/blocks/block-cta.js +1 -0
- package/dist/blocks/block-divider.d.ts +4 -0
- package/dist/blocks/block-divider.js +1 -0
- package/dist/blocks/block-faq.d.ts +11 -0
- package/dist/blocks/block-faq.js +1 -0
- package/dist/blocks/block-form.d.ts +7 -0
- package/dist/blocks/block-form.js +1 -0
- package/dist/blocks/block-gallery.d.ts +13 -0
- package/dist/blocks/block-gallery.js +1 -0
- package/dist/blocks/block-hero.d.ts +11 -0
- package/dist/blocks/block-hero.js +1 -0
- package/dist/blocks/block-html.d.ts +4 -0
- package/dist/blocks/block-html.js +1 -0
- package/dist/blocks/block-logocloud.d.ts +13 -0
- package/dist/blocks/block-logocloud.js +1 -0
- package/dist/blocks/block-quote.d.ts +10 -0
- package/dist/blocks/block-quote.js +1 -0
- package/dist/blocks/block-richtext.d.ts +7 -0
- package/dist/blocks/block-richtext.js +1 -0
- package/dist/blocks/block-steps.d.ts +21 -0
- package/dist/blocks/block-steps.js +1 -0
- package/dist/blocks/block-team.d.ts +6 -0
- package/dist/blocks/block-team.js +1 -0
- package/dist/blocks/block-testimonial.d.ts +13 -0
- package/dist/blocks/block-testimonial.js +1 -0
- package/dist/blocks/block-video.d.ts +9 -0
- package/dist/blocks/block-video.js +1 -0
- package/dist/blocks/block.d.ts +17 -0
- package/dist/blocks/block.js +1 -0
- package/dist/blocks/index.d.ts +18 -0
- package/dist/blocks/index.js +1 -0
- package/dist/componentMap.d.ts +6 -0
- package/dist/componentMap.js +8 -0
- package/dist/content/category.d.ts +10 -0
- package/dist/content/category.js +1 -0
- package/dist/content/form.d.ts +21 -0
- package/dist/content/form.js +1 -0
- package/dist/content/index.d.ts +6 -0
- package/dist/content/index.js +1 -0
- package/dist/content/page.d.ts +38 -0
- package/dist/content/page.js +1 -0
- package/dist/content/post.d.ts +38 -0
- package/dist/content/post.js +1 -0
- package/dist/content/team.d.ts +17 -0
- package/dist/content/team.js +1 -0
- package/dist/content/testimonial.d.ts +18 -0
- package/dist/content/testimonial.js +1 -0
- package/dist/help/index.d.ts +51 -0
- package/dist/help/index.js +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +1 -0
- package/dist/meta/analytics.d.ts +21 -0
- package/dist/meta/analytics.js +1 -0
- package/dist/meta/config.d.ts +22 -0
- package/dist/meta/config.js +1 -0
- package/dist/meta/globals.d.ts +33 -0
- package/dist/meta/globals.js +1 -0
- package/dist/meta/index.d.ts +6 -0
- package/dist/meta/index.js +1 -0
- package/dist/meta/navigation.d.ts +31 -0
- package/dist/meta/navigation.js +1 -0
- package/dist/meta/redirect.d.ts +12 -0
- package/dist/meta/redirect.js +1 -0
- package/dist/meta/seo.d.ts +19 -0
- package/dist/meta/seo.js +1 -0
- package/dist/os/contact.d.ts +22 -0
- package/dist/os/contact.js +1 -0
- package/dist/os/conversation.d.ts +23 -0
- package/dist/os/conversation.js +1 -0
- package/dist/os/index.d.ts +16 -0
- package/dist/os/index.js +1 -0
- package/dist/os/organization.d.ts +51 -0
- package/dist/os/organization.js +1 -0
- package/dist/os/os-activity.d.ts +26 -0
- package/dist/os/os-activity.js +1 -0
- package/dist/os/os-deal.d.ts +42 -0
- package/dist/os/os-deal.js +1 -0
- package/dist/os/os-expense.d.ts +21 -0
- package/dist/os/os-expense.js +1 -0
- package/dist/os/os-invoice.d.ts +46 -0
- package/dist/os/os-invoice.js +1 -0
- package/dist/os/os-item.d.ts +17 -0
- package/dist/os/os-item.js +1 -0
- package/dist/os/os-payment.d.ts +27 -0
- package/dist/os/os-payment.js +1 -0
- package/dist/os/os-project.d.ts +45 -0
- package/dist/os/os-project.js +1 -0
- package/dist/os/os-proposal.d.ts +61 -0
- package/dist/os/os-proposal.js +1 -0
- package/dist/os/os-settings.d.ts +17 -0
- package/dist/os/os-settings.js +1 -0
- package/dist/os/os-subscription.d.ts +12 -0
- package/dist/os/os-subscription.js +1 -0
- package/dist/os/os-task.d.ts +32 -0
- package/dist/os/os-task.js +1 -0
- package/dist/os/os-tax-rate.d.ts +12 -0
- package/dist/os/os-tax-rate.js +1 -0
- package/dist/pageComponentMap.d.ts +2 -0
- package/dist/pageComponentMap.js +7 -0
- package/dist/schema.d.ts +78 -0
- package/dist/schema.js +1 -0
- package/dist/system/file.d.ts +47 -0
- package/dist/system/file.js +1 -0
- package/dist/system/folder.d.ts +8 -0
- package/dist/system/folder.js +1 -0
- package/dist/system/index.d.ts +4 -0
- package/dist/system/index.js +1 -0
- package/dist/system/role.d.ts +20 -0
- package/dist/system/role.js +1 -0
- package/dist/system/user.d.ts +57 -0
- package/dist/system/user.js +1 -0
- package/nuxt.config.ts +5 -0
- package/package.json +42 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="border border-neutral-200 rounded-md hover:shadow-lg flex-auto flex-shrink-0" data-testid="product-card">
|
|
3
|
+
<div class="relative">
|
|
4
|
+
<SfLink :tag="NuxtLink" :to="`${paths.product}${slug}`">
|
|
5
|
+
<NuxtImg
|
|
6
|
+
:src="imageUrl"
|
|
7
|
+
:alt="imageAlt"
|
|
8
|
+
class="object-cover rounded-md aspect-square w-full h-full"
|
|
9
|
+
data-testid="image-slot"
|
|
10
|
+
width="190"
|
|
11
|
+
height="190"
|
|
12
|
+
:loading="lazy && !priority ? 'lazy' : undefined"
|
|
13
|
+
:fetchpriority="priority ? 'high' : undefined"
|
|
14
|
+
:preload="priority"
|
|
15
|
+
format="webp"
|
|
16
|
+
/>
|
|
17
|
+
</SfLink>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="p-2 border-t border-neutral-200 typography-text-sm">
|
|
20
|
+
<SfLink :tag="NuxtLink" :to="`${paths.product}${slug}`" class="no-underline" variant="secondary">
|
|
21
|
+
{{ name }}
|
|
22
|
+
</SfLink>
|
|
23
|
+
<div class="flex items-center pt-1">
|
|
24
|
+
<SfRating size="xs" :value="rating ?? 0" :max="5" />
|
|
25
|
+
<SfLink to="#" variant="secondary" :tag="NuxtLink" class="ml-1 no-underline">
|
|
26
|
+
<SfCounter size="xs">{{ ratingCount }}</SfCounter>
|
|
27
|
+
</SfLink>
|
|
28
|
+
</div>
|
|
29
|
+
<p class="block py-2 font-normal typography-text-xs text-neutral-700 text-justify">
|
|
30
|
+
{{ description }}
|
|
31
|
+
</p>
|
|
32
|
+
<span class="block pb-2 font-bold typography-text-sm" data-testid="product-card-vertical-price">
|
|
33
|
+
${{ price }}
|
|
34
|
+
</span>
|
|
35
|
+
<SfButton size="sm">
|
|
36
|
+
<template #prefix>
|
|
37
|
+
<SfIconShoppingCart size="sm" />
|
|
38
|
+
</template>
|
|
39
|
+
{{ $t('addToCartShort') }}
|
|
40
|
+
</SfButton>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</template>
|
|
44
|
+
|
|
45
|
+
<script setup lang="ts">
|
|
46
|
+
import { SfLink, SfRating, SfCounter, SfButton, SfIconShoppingCart } from '@storefront-ui/vue';
|
|
47
|
+
import type { ProductCardProps } from '~/components/ui/ProductCard/types';
|
|
48
|
+
|
|
49
|
+
withDefaults(defineProps<ProductCardProps>(), {
|
|
50
|
+
lazy: true,
|
|
51
|
+
imageAlt: '',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const NuxtLink = resolveComponent('NuxtLink');
|
|
55
|
+
</script>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import ProductCard from '~/components/ui/ProductCard/ProductCard.vue';
|
|
3
|
+
|
|
4
|
+
describe('<ProductCard />', () => {
|
|
5
|
+
it('should render component', () => {
|
|
6
|
+
const { getByTestId } = mount(ProductCard, {
|
|
7
|
+
props: {
|
|
8
|
+
name: 'test',
|
|
9
|
+
price: 100,
|
|
10
|
+
imageUrl: '/images/product.webp',
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
expect(getByTestId('product-card'));
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex min-w-[320px] max-w-[470px] pt-4" data-testid="product-card-horizontal">
|
|
3
|
+
<div class="relative overflow-hidden rounded-md w-[100px]">
|
|
4
|
+
<NuxtImg
|
|
5
|
+
class="w-full h-auto border rounded-md border-neutral-200"
|
|
6
|
+
:src="product.gallery[0].url"
|
|
7
|
+
:alt="product.gallery[0].alt ?? ''"
|
|
8
|
+
width="100"
|
|
9
|
+
height="100"
|
|
10
|
+
/>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="flex flex-col pl-4 min-w-[180px] flex-1 typography-text-base">
|
|
13
|
+
{{ product.name }}
|
|
14
|
+
<div class="my-2 sm:mb-0">
|
|
15
|
+
<ul class="font-normal leading-5 typography-text-xs sm:typography-text-sm text-neutral-700">
|
|
16
|
+
<li>
|
|
17
|
+
<span class="mr-1">{{ product.attributes[0].name }}:</span>
|
|
18
|
+
<span class="font-medium">{{ product.attributes[0].value }}</span>
|
|
19
|
+
</li>
|
|
20
|
+
<li>
|
|
21
|
+
<span class="mr-1">{{ product.attributes[1].name }}:</span>
|
|
22
|
+
<span class="font-medium">{{ product.attributes[1].value }}</span>
|
|
23
|
+
</li>
|
|
24
|
+
</ul>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script setup lang="ts">
|
|
31
|
+
import type { ProductHorizontalProps } from '~/components/ui/ProductCardHorizontal/types';
|
|
32
|
+
|
|
33
|
+
defineProps<ProductHorizontalProps>();
|
|
34
|
+
</script>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import ProductCardHorizontal from '~/components/ui/ProductCardHorizontal/ProductCardHorizontal.vue';
|
|
3
|
+
|
|
4
|
+
describe('<ProductCardHorizontal />', () => {
|
|
5
|
+
it('should render component', () => {
|
|
6
|
+
const { getByTestId } = mount(ProductCardHorizontal, {
|
|
7
|
+
props: {
|
|
8
|
+
product: {
|
|
9
|
+
name: 'Smartwatch Fitness Tracker',
|
|
10
|
+
gallery: [
|
|
11
|
+
{
|
|
12
|
+
alt: 'Smartwatch Fitness Tracker',
|
|
13
|
+
url: '/images/watch.png',
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
attributes: [
|
|
17
|
+
{
|
|
18
|
+
label: 'Size',
|
|
19
|
+
name: 'Size',
|
|
20
|
+
value: '1.9″',
|
|
21
|
+
valueLabel: 'value',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'Color',
|
|
25
|
+
label: 'color',
|
|
26
|
+
value: 'Black',
|
|
27
|
+
valueLabel: 'value',
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
expect(getByTestId('product-card-horizontal'));
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section
|
|
3
|
+
class="p-4 xl:p-6 md:border md:border-neutral-100 md:shadow-lg md:rounded-md md:sticky md:top-20"
|
|
4
|
+
data-testid="purchase-card"
|
|
5
|
+
>
|
|
6
|
+
<UiTag variant="secondary" strong class="mb-4">
|
|
7
|
+
<SfIconSell size="sm" class="mr-1" />
|
|
8
|
+
<span class="mr-1">{{ $t(`sale`) }}</span>
|
|
9
|
+
</UiTag>
|
|
10
|
+
<h1 class="mb-1 font-bold typography-headline-4" data-testid="product-name">{{ product.name }}</h1>
|
|
11
|
+
<div class="my-1">
|
|
12
|
+
<span class="mr-2 text-secondary-700 font-bold font-headings text-2xl" data-testid="price">
|
|
13
|
+
${{ product.price?.value.amount }}
|
|
14
|
+
</span>
|
|
15
|
+
<span class="text-base font-normal text-neutral-500 line-through">
|
|
16
|
+
${{ product.price?.regularPrice.amount }}
|
|
17
|
+
</span>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="inline-flex items-center mt-4 mb-2">
|
|
20
|
+
<SfRating size="xs" :value="product.rating?.average" :max="5" />
|
|
21
|
+
<SfCounter class="ml-1" size="xs">{{ product.rating?.count }}</SfCounter>
|
|
22
|
+
<SfLink href="#" variant="secondary" class="ml-2 text-xs text-neutral-500">
|
|
23
|
+
{{ $t('reviewsCount', { count: product.rating?.count }) }}
|
|
24
|
+
</SfLink>
|
|
25
|
+
</div>
|
|
26
|
+
<p class="mb-4 font-normal typography-text-sm" data-testid="product-description">
|
|
27
|
+
{{ product.description }}
|
|
28
|
+
</p>
|
|
29
|
+
<div class="py-4 mb-4 border-gray-200 border-y">
|
|
30
|
+
<UiTag class="w-full mb-4">
|
|
31
|
+
<SfIconShoppingCartCheckout />
|
|
32
|
+
{{ $t('numberInCart', { count: 1 }) }}
|
|
33
|
+
</UiTag>
|
|
34
|
+
<div class="flex flex-col md:flex-row flex-wrap gap-4">
|
|
35
|
+
<UiQuantitySelector :value="quantitySelectorValue" class="min-w-[145px] flex-grow flex-shrink-0 basis-0" />
|
|
36
|
+
<SfButton size="lg" class="flex-grow-[2] flex-shrink basis-auto whitespace-nowrap">
|
|
37
|
+
<template #prefix>
|
|
38
|
+
<SfIconShoppingCart size="sm" />
|
|
39
|
+
</template>
|
|
40
|
+
{{ $t('addToCart') }}
|
|
41
|
+
</SfButton>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="flex justify-center mt-4 gap-x-4">
|
|
44
|
+
<SfButton size="sm" variant="tertiary">
|
|
45
|
+
<template #prefix>
|
|
46
|
+
<SfIconCompareArrows size="sm" />
|
|
47
|
+
</template>
|
|
48
|
+
{{ $t('compare') }}
|
|
49
|
+
</SfButton>
|
|
50
|
+
<SfButton size="sm" variant="tertiary">
|
|
51
|
+
<SfIconFavorite size="sm" />
|
|
52
|
+
{{ $t('addToList') }}
|
|
53
|
+
</SfButton>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="flex first:mt-4">
|
|
57
|
+
<SfIconPackage size="sm" class="flex-shrink-0 mr-1 text-neutral-500" />
|
|
58
|
+
<p class="text-sm">
|
|
59
|
+
<i18n-t keypath="additionalInfo.shipping">
|
|
60
|
+
<template #addAddress>
|
|
61
|
+
<SfLink href="#" variant="secondary">{{ $t('additionalInfo.addAddress') }}</SfLink>
|
|
62
|
+
</template>
|
|
63
|
+
</i18n-t>
|
|
64
|
+
</p>
|
|
65
|
+
</div>
|
|
66
|
+
<div class="flex mt-4">
|
|
67
|
+
<SfIconWarehouse size="sm" class="flex-shrink-0 mr-1 text-neutral-500" />
|
|
68
|
+
<p class="text-sm">
|
|
69
|
+
<i18n-t keypath="additionalInfo.pickup">
|
|
70
|
+
<template #checkAvailability>
|
|
71
|
+
<SfLink href="#" variant="secondary">{{ $t('additionalInfo.checkAvailability') }}</SfLink>
|
|
72
|
+
</template>
|
|
73
|
+
</i18n-t>
|
|
74
|
+
</p>
|
|
75
|
+
</div>
|
|
76
|
+
<div class="flex mt-4">
|
|
77
|
+
<p class="text-sm">
|
|
78
|
+
<SfIconSafetyCheck size="sm" class="flex-shrink-0 mr-1 text-neutral-500" />
|
|
79
|
+
<i18n-t keypath="additionalInfo.returns">
|
|
80
|
+
<template #details>
|
|
81
|
+
<SfLink href="#" variant="secondary">{{ $t('additionalInfo.details') }}</SfLink>
|
|
82
|
+
</template>
|
|
83
|
+
</i18n-t>
|
|
84
|
+
</p>
|
|
85
|
+
</div>
|
|
86
|
+
</section>
|
|
87
|
+
</template>
|
|
88
|
+
|
|
89
|
+
<script setup lang="ts">
|
|
90
|
+
import {
|
|
91
|
+
SfButton,
|
|
92
|
+
SfCounter,
|
|
93
|
+
SfLink,
|
|
94
|
+
SfRating,
|
|
95
|
+
SfIconSafetyCheck,
|
|
96
|
+
SfIconCompareArrows,
|
|
97
|
+
SfIconWarehouse,
|
|
98
|
+
SfIconPackage,
|
|
99
|
+
SfIconFavorite,
|
|
100
|
+
SfIconSell,
|
|
101
|
+
SfIconShoppingCartCheckout,
|
|
102
|
+
SfIconShoppingCart,
|
|
103
|
+
} from '@storefront-ui/vue';
|
|
104
|
+
import type { PurchaseCardProps } from '~/components/ui/PurchaseCard/types';
|
|
105
|
+
|
|
106
|
+
defineProps<PurchaseCardProps>();
|
|
107
|
+
|
|
108
|
+
const quantitySelectorValue = ref(1);
|
|
109
|
+
</script>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import PurchaseCard from '~/components/ui/PurchaseCard/PurchaseCard.vue';
|
|
3
|
+
import type { SfProduct } from '@vue-storefront/unified-data-model';
|
|
4
|
+
|
|
5
|
+
describe('<PurchaseCard />', () => {
|
|
6
|
+
it('should render component', () => {
|
|
7
|
+
const wrapper = mount(PurchaseCard, {
|
|
8
|
+
props: {
|
|
9
|
+
product: {} as SfProduct,
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
expect(wrapper.getByTestId('purchase-card'));
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="inline-flex flex-col items-center" data-testid="quantity-selector">
|
|
3
|
+
<div class="flex border border-neutral-300 rounded-md h-full w-full">
|
|
4
|
+
<SfButton
|
|
5
|
+
variant="tertiary"
|
|
6
|
+
:disabled="count <= minValue"
|
|
7
|
+
square
|
|
8
|
+
class="rounded-r-none"
|
|
9
|
+
:aria-controls="inputId"
|
|
10
|
+
:aria-label="$t('quantitySelectorDecrease')"
|
|
11
|
+
data-testid="quantity-selector-decrease-button"
|
|
12
|
+
@click="dec()"
|
|
13
|
+
>
|
|
14
|
+
<SfIconRemove />
|
|
15
|
+
</SfButton>
|
|
16
|
+
<input
|
|
17
|
+
:id="inputId"
|
|
18
|
+
v-model="count"
|
|
19
|
+
type="number"
|
|
20
|
+
role="spinbutton"
|
|
21
|
+
:class="inputClasses"
|
|
22
|
+
:min="minValue"
|
|
23
|
+
:max="maxValue"
|
|
24
|
+
data-testid="quantity-selector-input"
|
|
25
|
+
:aria-label="$t('quantitySelector')"
|
|
26
|
+
@input="handleOnChange"
|
|
27
|
+
/>
|
|
28
|
+
<SfButton
|
|
29
|
+
variant="tertiary"
|
|
30
|
+
:disabled="count >= maxValue"
|
|
31
|
+
square
|
|
32
|
+
class="rounded-l-none"
|
|
33
|
+
:aria-controls="inputId"
|
|
34
|
+
:aria-label="$t('quantitySelectorIncrease')"
|
|
35
|
+
data-testid="quantity-selector-increase-button"
|
|
36
|
+
@click="inc()"
|
|
37
|
+
>
|
|
38
|
+
<SfIconAdd />
|
|
39
|
+
</SfButton>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</template>
|
|
43
|
+
|
|
44
|
+
<script setup lang="ts">
|
|
45
|
+
import { clamp } from '@storefront-ui/shared';
|
|
46
|
+
import { SfButton, SfIconAdd, SfIconRemove, useId } from '@storefront-ui/vue';
|
|
47
|
+
import { useCounter } from '@vueuse/core';
|
|
48
|
+
import type { QuantitySelectorProps } from '~/components/ui/QuantitySelector/types';
|
|
49
|
+
|
|
50
|
+
const { value, minValue, maxValue } = withDefaults(defineProps<QuantitySelectorProps>(), {
|
|
51
|
+
value: 1,
|
|
52
|
+
minValue: 1,
|
|
53
|
+
maxValue: 10,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const inputId = useId();
|
|
57
|
+
const { count, inc, dec, set } = useCounter(value);
|
|
58
|
+
|
|
59
|
+
const inputClasses = computed(
|
|
60
|
+
() =>
|
|
61
|
+
'appearance-none flex-1 mx-2 w-8 text-center bg-transparent font-medium [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-inner-spin-button]:display-none [&::-webkit-inner-spin-button]:m-0 [&::-webkit-outer-spin-button]:display-none [&::-webkit-outer-spin-button]:m-0 [-moz-appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none disabled:placeholder-disabled-900 focus-visible:outline focus-visible:outline-offset focus-visible:rounded-sm',
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const handleOnChange = (event: Event) => {
|
|
65
|
+
const currentValue = (event.target as HTMLInputElement)?.value;
|
|
66
|
+
const nextValue = Number.parseFloat(currentValue);
|
|
67
|
+
set(clamp(nextValue, minValue, maxValue));
|
|
68
|
+
};
|
|
69
|
+
</script>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import QuantitySelector from '~/components/ui/QuantitySelector/QuantitySelector.vue';
|
|
3
|
+
|
|
4
|
+
const value = 1;
|
|
5
|
+
|
|
6
|
+
describe('<QuantitySelector />', () => {
|
|
7
|
+
it('should render component', () => {
|
|
8
|
+
const wrapper = mount(QuantitySelector, {
|
|
9
|
+
props: {
|
|
10
|
+
value,
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
expect(wrapper.getByTestId('quantity-selector'));
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const props = defineProps({
|
|
3
|
+
radius: {
|
|
4
|
+
type: Number,
|
|
5
|
+
default: 50,
|
|
6
|
+
},
|
|
7
|
+
progress: {
|
|
8
|
+
type: Number,
|
|
9
|
+
default: 0,
|
|
10
|
+
},
|
|
11
|
+
stroke: {
|
|
12
|
+
type: Number,
|
|
13
|
+
default: 10,
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const normalizedRadius = props.radius - props.stroke * 2;
|
|
18
|
+
const circumference = normalizedRadius * 2 * Math.PI;
|
|
19
|
+
|
|
20
|
+
const strokeDashoffset = computed(() => {
|
|
21
|
+
return circumference - ((props.progress * 100) / 100) * circumference;
|
|
22
|
+
});
|
|
23
|
+
</script>
|
|
24
|
+
<template>
|
|
25
|
+
<svg :height="radius * 2" :width="radius * 2">
|
|
26
|
+
<circle
|
|
27
|
+
stroke="currentColor"
|
|
28
|
+
:stroke-dasharray="circumference + ' ' + circumference"
|
|
29
|
+
:style="{ strokeDashoffset: strokeDashoffset }"
|
|
30
|
+
:stroke-width="stroke"
|
|
31
|
+
fill="transparent"
|
|
32
|
+
:r="normalizedRadius"
|
|
33
|
+
:cx="radius"
|
|
34
|
+
:cy="radius"
|
|
35
|
+
/>
|
|
36
|
+
</svg>
|
|
37
|
+
</template>
|
|
38
|
+
<style>
|
|
39
|
+
circle {
|
|
40
|
+
transition: stroke-dashoffset 0.25s;
|
|
41
|
+
transform: rotate(-90deg);
|
|
42
|
+
transform-origin: 50% 50%;
|
|
43
|
+
}
|
|
44
|
+
</style>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<article class="w-full p-4 border rounded-md" data-testid="review">
|
|
3
|
+
<p class="pb-2 font-medium">{{ review.title }}</p>
|
|
4
|
+
<header class="flex flex-col pb-2 md:flex-row md:justify-between">
|
|
5
|
+
<span class="flex items-center pr-2 text-xs text-neutral-500">
|
|
6
|
+
<SfRating :value="review.rating ?? undefined" :max="5" size="xs" class="mr-2" />
|
|
7
|
+
{{ $d(new Date(review.createdAt)) }}
|
|
8
|
+
</span>
|
|
9
|
+
<p class="flex items-center text-xs truncate text-primary-700">
|
|
10
|
+
<span class="mr-2 text-xs text-neutral-500">{{ review.reviewer }}</span>
|
|
11
|
+
<SfIconCheck size="xs" class="mr-1" /> {{ $t('review.verifiedPurchase') }}
|
|
12
|
+
</p>
|
|
13
|
+
</header>
|
|
14
|
+
<p class="pb-2 text-sm text-neutral-900">{{ truncatedContent }}</p>
|
|
15
|
+
<button
|
|
16
|
+
v-if="isButtonVisible"
|
|
17
|
+
type="button"
|
|
18
|
+
class="inline-block mb-2 text-sm font-normal border-b-2 border-black cursor-pointer w-fit hover:text-primary-700 hover:border-primary-800"
|
|
19
|
+
@click="isCollapsed = !isCollapsed"
|
|
20
|
+
>
|
|
21
|
+
{{ $t(isCollapsed ? 'readMore' : 'readLess') }}
|
|
22
|
+
</button>
|
|
23
|
+
<footer class="flex items-center justify-between">
|
|
24
|
+
<div class="text-sm text-neutral-500">
|
|
25
|
+
<button type="button" class="mr-6 hover:text-primary-800">
|
|
26
|
+
<SfIconThumbUp size="sm" class="mr-2.5" />
|
|
27
|
+
<SfCounter size="sm" class="text-inherit">6</SfCounter>
|
|
28
|
+
</button>
|
|
29
|
+
<button type="button" class="hover:text-primary-800">
|
|
30
|
+
<SfIconThumbDown size="sm" class="mr-2.5" />
|
|
31
|
+
<SfCounter size="sm" class="text-inherit">2</SfCounter>
|
|
32
|
+
</button>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<button class="px-3 py-1.5 text-neutral-500 font-medium text-sm hover:text-primary-800" type="button">
|
|
36
|
+
{{ $t('review.reportAbuse') }}
|
|
37
|
+
</button>
|
|
38
|
+
</footer>
|
|
39
|
+
</article>
|
|
40
|
+
</template>
|
|
41
|
+
|
|
42
|
+
<script setup lang="ts">
|
|
43
|
+
import { SfRating, SfIconCheck, SfIconThumbUp, SfIconThumbDown, SfCounter } from '@storefront-ui/vue';
|
|
44
|
+
import type { ReviewProps } from '~/components/ui/Review/types';
|
|
45
|
+
|
|
46
|
+
const props = defineProps<ReviewProps>();
|
|
47
|
+
|
|
48
|
+
const { review } = toRefs(props);
|
|
49
|
+
const charLimit = 400;
|
|
50
|
+
const isCollapsed = ref(true);
|
|
51
|
+
const isButtonVisible = computed(() => review.value.text?.length || 0 > charLimit);
|
|
52
|
+
const truncatedContent = computed(() =>
|
|
53
|
+
isButtonVisible.value && isCollapsed.value
|
|
54
|
+
? `${review.value.text?.slice(0, Math.max(0, charLimit))}...`
|
|
55
|
+
: review.value.text,
|
|
56
|
+
);
|
|
57
|
+
</script>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import Review from '~/components/ui/Review/Review.vue';
|
|
3
|
+
import { mockProductReviews } from '~/composables/useProductReviews/__tests__/productReviews.mock';
|
|
4
|
+
|
|
5
|
+
describe('<Review />', () => {
|
|
6
|
+
it('should render component', () => {
|
|
7
|
+
const { getByTestId } = mount(Review, {
|
|
8
|
+
props: {
|
|
9
|
+
review: mockProductReviews[0],
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
expect(getByTestId('review'));
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, computed } from 'vue';
|
|
3
|
+
import { useWindowScroll } from '@vueuse/core';
|
|
4
|
+
|
|
5
|
+
const el = ref<HTMLElement | null>(null);
|
|
6
|
+
const { y } = useWindowScroll();
|
|
7
|
+
|
|
8
|
+
const radius = 25;
|
|
9
|
+
const stroke = 5;
|
|
10
|
+
const circumference = 2 * Math.PI * radius;
|
|
11
|
+
const dashArray = `${circumference} ${circumference}`;
|
|
12
|
+
const progress = computed(() => {
|
|
13
|
+
if (el.value) {
|
|
14
|
+
const sc = el.value.scrollTop;
|
|
15
|
+
const total = el.value.scrollHeight - (el.value.clientHeight || 0);
|
|
16
|
+
return total <= 0 ? 0 : Math.min(sc / total, 1);
|
|
17
|
+
}
|
|
18
|
+
const sc = y.value;
|
|
19
|
+
const doc = document.documentElement;
|
|
20
|
+
const total = (doc.scrollHeight || 0) - (window.innerHeight || 0);
|
|
21
|
+
return total <= 0 ? 0 : Math.min(sc / total, 1);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const dashOffset = computed(() => circumference * (1 - (progress.value ?? 0)));
|
|
25
|
+
|
|
26
|
+
function scrollToTop() {
|
|
27
|
+
if (el.value) el.value.scrollTo({ top: 0, behavior: 'smooth' });
|
|
28
|
+
else window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
29
|
+
}
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<div class="flex items-center" ref="el">
|
|
34
|
+
<div class="relative">
|
|
35
|
+
<svg :width="(radius + stroke) * 2" :height="(radius + stroke) * 2" viewBox="0 0 60 60" class="text-primary/75">
|
|
36
|
+
<g transform="translate(30,30)">
|
|
37
|
+
<circle
|
|
38
|
+
r="25"
|
|
39
|
+
fill="transparent"
|
|
40
|
+
stroke="currentColor"
|
|
41
|
+
:stroke-width="stroke"
|
|
42
|
+
class="text-gray-200"
|
|
43
|
+
/>
|
|
44
|
+
<circle
|
|
45
|
+
r="25"
|
|
46
|
+
fill="transparent"
|
|
47
|
+
stroke="currentColor"
|
|
48
|
+
:stroke-width="stroke"
|
|
49
|
+
stroke-linecap="round"
|
|
50
|
+
:stroke-dasharray="dashArray"
|
|
51
|
+
:stroke-dashoffset="dashOffset"
|
|
52
|
+
transform="rotate(-90)"
|
|
53
|
+
class="text-primary/75"
|
|
54
|
+
/>
|
|
55
|
+
</g>
|
|
56
|
+
</svg>
|
|
57
|
+
|
|
58
|
+
<button
|
|
59
|
+
v-if="progress >= 0.95"
|
|
60
|
+
class="absolute inset-0 flex items-center justify-center"
|
|
61
|
+
@click="scrollToTop"
|
|
62
|
+
aria-label="Scroll to top"
|
|
63
|
+
>
|
|
64
|
+
<svg class="w-4 h-4 text-primary/75 hover:text-gray-500" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
65
|
+
<path d="M12 4v16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
66
|
+
<path d="M18 10l-6-6-6 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
67
|
+
</svg>
|
|
68
|
+
</button>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<span
|
|
72
|
+
v-if="progress >= 0.95"
|
|
73
|
+
class="p-1.5 text-gray-700 bg-gray-300 bg-opacity-50 rounded-xl dark:bg-gray-700 dark:text-gray-200 font-display"
|
|
74
|
+
>
|
|
75
|
+
fin.
|
|
76
|
+
</span>
|
|
77
|
+
</div>
|
|
78
|
+
</template>
|
|
79
|
+
|
|
80
|
+
<style scoped>
|
|
81
|
+
.text-primary\/75 { color: theme('colors.primary', '#0ea5a0'); }
|
|
82
|
+
</style>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<form ref="referenceRef" role="search" class="relative" @submit.prevent="handleSubmit">
|
|
3
|
+
<SfInput ref="inputReference" v-model="inputModel" aria-label="Search" placeholder="Search" @focus="open">
|
|
4
|
+
<template #prefix>
|
|
5
|
+
<SfIconSearch />
|
|
6
|
+
</template>
|
|
7
|
+
<template #suffix>
|
|
8
|
+
<button
|
|
9
|
+
v-if="inputModel"
|
|
10
|
+
type="button"
|
|
11
|
+
aria-label="Reset search"
|
|
12
|
+
class="flex rounded-md focus-visible:outline focus-visible:outline-offset"
|
|
13
|
+
@click="handleReset"
|
|
14
|
+
>
|
|
15
|
+
<SfIconCancel />
|
|
16
|
+
</button>
|
|
17
|
+
</template>
|
|
18
|
+
</SfInput>
|
|
19
|
+
</form>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<script setup lang="ts">
|
|
23
|
+
import { SfIconCancel, SfIconSearch, SfInput, useDisclosure } from '@storefront-ui/vue';
|
|
24
|
+
import { unrefElement } from '@vueuse/core';
|
|
25
|
+
|
|
26
|
+
const props = defineProps<{
|
|
27
|
+
close?: () => boolean;
|
|
28
|
+
}>();
|
|
29
|
+
|
|
30
|
+
const router = useRouter();
|
|
31
|
+
const { open } = useDisclosure();
|
|
32
|
+
|
|
33
|
+
const inputModel = ref('');
|
|
34
|
+
const inputReference = ref<HTMLSpanElement>();
|
|
35
|
+
const handleInputFocus = () => {
|
|
36
|
+
const inputElement = unrefElement(inputReference)?.querySelector('input');
|
|
37
|
+
inputElement?.focus();
|
|
38
|
+
};
|
|
39
|
+
const handleReset = () => {
|
|
40
|
+
inputModel.value = '';
|
|
41
|
+
handleInputFocus();
|
|
42
|
+
};
|
|
43
|
+
const handleSubmit = () => {
|
|
44
|
+
props.close?.();
|
|
45
|
+
router.push({ path: paths.search, query: { search: inputModel.value } });
|
|
46
|
+
handleReset();
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
watch(inputModel, () => {
|
|
50
|
+
if (inputModel.value === '') {
|
|
51
|
+
handleReset();
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
</script>
|