Skip to content

Commit d55366a

Browse files
authored
Merge branch 'Weaverse:main' into main
2 parents bae88f3 + cc0f3b4 commit d55366a

File tree

7 files changed

+117
-62
lines changed

7 files changed

+117
-62
lines changed

app/components/image.tsx

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { Image as HydrogenImage } from "@shopify/hydrogen";
2+
import type { Image as ImageType } from "@shopify/hydrogen/storefront-api-types";
3+
import { useState } from "react";
4+
import { cn } from "~/utils/cn";
5+
6+
type Crop = "center" | "top" | "bottom" | "left" | "right";
7+
8+
export interface ImageProps extends React.ComponentPropsWithRef<"img"> {
9+
aspectRatio?: string;
10+
crop?: "center" | "top" | "bottom" | "left" | "right";
11+
data?: Partial<
12+
ImageType & {
13+
recurseIntoArrays: true;
14+
}
15+
>;
16+
loader?: (params: {
17+
src?: ImageType["url"];
18+
width?: number;
19+
height?: number;
20+
crop?: Crop;
21+
}) => string;
22+
srcSetOptions?: {
23+
intervals: number;
24+
startingWidth: number;
25+
incrementSize: number;
26+
placeholderWidth: number;
27+
};
28+
}
29+
30+
export function Image(props: ImageProps) {
31+
let { className, ...rest } = props;
32+
let [loaded, setLoaded] = useState(false);
33+
34+
return (
35+
<div
36+
className={cn(
37+
"w-full h-full overflow-hidden",
38+
!loaded && "animate-pulse [animation-duration:4s]",
39+
className,
40+
)}
41+
>
42+
<HydrogenImage
43+
className={cn(
44+
"[transition:filter_500ms_cubic-bezier(.4,0,.2,1)]",
45+
"h-full max-h-full w-full object-cover object-center",
46+
loaded ? "blur-0" : "blur-xl",
47+
)}
48+
onLoad={() => setLoaded(true)}
49+
{...rest}
50+
/>
51+
</div>
52+
);
53+
}

app/components/layout/desktop-menu.tsx

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { CaretDown } from "@phosphor-icons/react";
22
import * as Menubar from "@radix-ui/react-menubar";
3-
import { Image } from "@shopify/hydrogen";
43
import { useThemeSettings } from "@weaverse/hydrogen";
54
import clsx from "clsx";
65
import { useState } from "react";
6+
import { Image } from "~/components/image";
77
import Link from "~/components/link";
88
import { useShopMenu } from "~/hooks/use-shop-menu";
9-
import { cn } from "~/utils/cn";
109
import type { SingleMenuItem } from "~/types/menu";
10+
import { cn } from "~/utils/cn";
1111

1212
export function DesktopMenu() {
1313
let { headerMenu } = useShopMenu();
@@ -108,13 +108,14 @@ function MegaMenu({ items }: { items: SingleMenuItem[] }) {
108108
resource?.image && children.length === 0 ? (
109109
<SlideIn
110110
key={id}
111-
className="grow max-w-72 aspect-square relative group/item overflow-hidden"
111+
className="grow max-w-72 w-72 aspect-square relative group/item overflow-hidden"
112112
style={{ "--idx": idx } as React.CSSProperties}
113113
>
114114
<Image
115115
sizes="auto"
116116
data={resource.image}
117-
className="w-full h-full object-cover group-hover/item:scale-[1.03] transition-transform duration-300"
117+
className="group-hover/item:scale-[1.03] transition-transform duration-300"
118+
width={300}
118119
/>
119120
<Link
120121
to={to}
@@ -154,7 +155,7 @@ function MegaMenu({ items }: { items: SingleMenuItem[] }) {
154155
))}
155156
</div>
156157
</SlideIn>
157-
)
158+
),
158159
)}
159160
</div>
160161
);
@@ -170,7 +171,7 @@ function SlideIn(props: {
170171
<div
171172
className={cn(
172173
"opacity-0 animate-slide-left [animation-delay:calc(var(--idx)*0.1s+0.1s)]",
173-
className
174+
className,
174175
)}
175176
style={
176177
{

app/components/product/badges.tsx

+22-15
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useMoney as parseMoney } from "@shopify/hydrogen";
12
import type { MoneyV2 } from "@shopify/hydrogen/storefront-api-types";
23
import { useThemeSettings } from "@weaverse/hydrogen";
34
import { clsx } from "clsx";
@@ -33,8 +34,8 @@ export function NewBadge({
3334
publishedAt,
3435
className,
3536
}: { publishedAt: string; className?: string }) {
36-
let { newBadgeText, newBadgeColor } = useThemeSettings();
37-
if (isNewArrival(publishedAt)) {
37+
let { newBadgeText, newBadgeColor, newBadgeDaysOld } = useThemeSettings();
38+
if (isNewArrival(publishedAt, newBadgeDaysOld)) {
3839
return (
3940
<Badge
4041
text={newBadgeText}
@@ -73,16 +74,16 @@ export function SaleBadge({
7374
compareAtPrice,
7475
className,
7576
}: { price: MoneyV2; compareAtPrice: MoneyV2; className?: string }) {
76-
let { saleBadgeContent, saleBadgeText, saleBadgeColor } = useThemeSettings();
77-
let discount = calculateSalePercentage(price, compareAtPrice);
78-
if (discount > 0) {
77+
let { saleBadgeText = "Sale", saleBadgeColor } = useThemeSettings();
78+
let { amount, percentage } = calculateDiscount(price, compareAtPrice);
79+
let text = saleBadgeText
80+
.replace("[amount]", amount.toString())
81+
.replace("[percentage]", percentage.toString());
82+
83+
if (percentage > 0) {
7984
return (
8085
<Badge
81-
text={
82-
saleBadgeContent === "percentage"
83-
? `-${discount}% Off`
84-
: saleBadgeText
85-
}
86+
text={text}
8687
backgroundColor={saleBadgeColor}
8788
className={className}
8889
/>
@@ -98,15 +99,21 @@ function isNewArrival(date: string, daysOld = 30) {
9899
);
99100
}
100101

101-
function calculateSalePercentage(price: MoneyV2, compareAtPrice: MoneyV2) {
102+
function calculateDiscount(price: MoneyV2, compareAtPrice: MoneyV2) {
102103
if (price?.amount && compareAtPrice?.amount) {
103104
let priceNumber = Number(price.amount);
104105
let compareAtPriceNumber = Number(compareAtPrice.amount);
105106
if (compareAtPriceNumber > priceNumber) {
106-
return Math.round(
107-
((compareAtPriceNumber - priceNumber) / compareAtPriceNumber) * 100,
108-
);
107+
return {
108+
amount: parseMoney({
109+
amount: String(compareAtPriceNumber - priceNumber),
110+
currencyCode: price.currencyCode,
111+
}).withoutTrailingZeros,
112+
percentage: Math.round(
113+
((compareAtPriceNumber - priceNumber) / compareAtPriceNumber) * 100,
114+
),
115+
};
109116
}
110117
}
111-
return 0;
118+
return { amount: "0", percentage: 0 };
112119
}

app/components/product/product-card.tsx

+7-8
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
import { Image, flattenConnection } from "@shopify/hydrogen";
1+
import { flattenConnection } from "@shopify/hydrogen";
22
import type {
33
MoneyV2,
44
ProductVariant,
55
} from "@shopify/hydrogen/storefront-api-types";
66
import { useThemeSettings } from "@weaverse/hydrogen";
77
import clsx from "clsx";
88
import type { ProductCardFragment } from "storefront-api.generated";
9+
import { Image } from "~/components/image";
910
import { Link } from "~/components/link";
1011
import { NavLink } from "~/components/nav-link";
1112
import { VariantPrices } from "~/components/variant-prices";
12-
import { BestSellerBadge, NewBadge, SaleBadge, SoldOutBadge } from "./badges";
1313
import { getImageAspectRatio } from "~/utils/image";
14+
import { BestSellerBadge, NewBadge, SaleBadge, SoldOutBadge } from "./badges";
1415

1516
export function ProductCard({
1617
product,
@@ -56,6 +57,7 @@ export function ProductCard({
5657

5758
return (
5859
<div
60+
className="overflow-hidden"
5961
style={
6062
{
6163
borderRadius: pcardBorderRadius,
@@ -69,32 +71,29 @@ export function ProductCard({
6971
<Link
7072
to={`/products/${product.handle}`}
7173
prefetch="intent"
72-
className="block group relative aspect-[--pcard-image-ratio] overflow-hidden"
74+
className="block group relative aspect-[--pcard-image-ratio] overflow-hidden bg-gray-100"
7375
>
7476
<Image
7577
className={clsx([
76-
"w-full h-full object-cover object-center",
7778
"absolute inset-0",
78-
"opacity-0 animate-fade-in",
7979
pcardShowImageOnHover &&
8080
secondImage &&
8181
"transition-opacity duration-300 group-hover:opacity-0",
8282
])}
8383
sizes="(min-width: 64em) 25vw, (min-width: 48em) 30vw, 45vw"
8484
data={image}
85-
width={1000}
85+
width={700}
8686
alt={image.altText || `Picture of ${product.title}`}
8787
loading="lazy"
8888
/>
8989
{pcardShowImageOnHover && secondImage && (
9090
<Image
9191
className={clsx([
92-
"w-full h-full object-cover object-center",
9392
"absolute inset-0",
9493
"transition-opacity duration-300 opacity-0 group-hover:opacity-100",
9594
])}
9695
sizes="auto"
97-
width={1000}
96+
width={700}
9897
data={secondImage}
9998
alt={
10099
secondImage.altText || `Second picture of ${product.title}`

app/sections/collection-filters/index.tsx

+2-8
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { useLoaderData } from "@remix-run/react";
2-
import { Image } from "@shopify/hydrogen";
32
import type { HydrogenComponentSchema } from "@weaverse/hydrogen";
43
import clsx from "clsx";
54
import { forwardRef, useEffect, useState } from "react";
65
import type { CollectionDetailsQuery } from "storefront-api.generated";
76
import { BreadCrumb } from "~/components/breadcrumb";
7+
import { Image } from "~/components/image";
88
import { Section, type SectionProps, layoutInputs } from "~/components/section";
99
import { Filters } from "./filters";
1010
import { ProductsPagination } from "./products-pagination";
@@ -105,13 +105,7 @@ let CollectionFilters = forwardRef<HTMLElement, CollectionFiltersProps>(
105105
} as React.CSSProperties
106106
}
107107
>
108-
<Image
109-
data={banner}
110-
sizes="auto"
111-
className="w-full h-full object-cover object-center"
112-
width={2000}
113-
height={2000}
114-
/>
108+
<Image data={banner} sizes="auto" width={2000} />
115109
</div>
116110
)}
117111
</div>

app/sections/columns-with-images/column.tsx

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { Image } from "@shopify/hydrogen";
21
import {
32
type HydrogenComponentProps,
43
type HydrogenComponentSchema,
@@ -9,6 +8,7 @@ import type { VariantProps } from "class-variance-authority";
98
import { cva } from "class-variance-authority";
109
import type { CSSProperties } from "react";
1110
import { forwardRef } from "react";
11+
import { Image } from "~/components/image";
1212
import Link, { linkContentInputs, type LinkProps } from "~/components/link";
1313

1414
let variants = cva("", {
@@ -56,14 +56,12 @@ let ColumnWithImageItem = forwardRef<HTMLDivElement, ColumnWithImageItemProps>(
5656
{...rest}
5757
data-motion="slide-in"
5858
className={variants({ size, hideOnMobile })}
59-
style={
60-
{ "--image-border-radius": `${imageBorderRadius}px` } as CSSProperties
61-
}
59+
style={{ "--radius": `${imageBorderRadius}px` } as CSSProperties}
6260
>
6361
<Image
6462
data={typeof imageSrc === "object" ? imageSrc : { url: imageSrc }}
6563
sizes="auto"
66-
className="aspect-square object-cover object-center w-full rounded-[var(--image-border-radius)]"
64+
className="aspect-square h-auto rounded-[--radius]"
6765
/>
6866
<div className="text-center w-full space-y-3.5 mt-6">
6967
{heading && <h6>{heading}</h6>}

app/weaverse/schema.server.ts

+23-20
Original file line numberDiff line numberDiff line change
@@ -608,11 +608,24 @@ export let themeSchema: HydrogenThemeSchema = {
608608
},
609609
{
610610
type: "text",
611-
label: "New / Limited text",
611+
label: "New text",
612612
name: "newBadgeText",
613613
defaultValue: "New",
614614
placeholder: "New",
615615
},
616+
{
617+
type: "range",
618+
label: "Days old",
619+
name: "newBadgeDaysOld",
620+
configs: {
621+
min: 0,
622+
max: 365,
623+
step: 1,
624+
},
625+
defaultValue: 30,
626+
helpText:
627+
"The <strong>New</strong> badge will be shown if the product is published within the last days.",
628+
},
616629
{
617630
type: "text",
618631
label: "Sold out text",
@@ -621,26 +634,16 @@ export let themeSchema: HydrogenThemeSchema = {
621634
placeholder: "Sold out",
622635
},
623636
{
624-
type: "select",
625-
label: "Sale badge content",
626-
name: "saleBadgeContent",
627-
configs: {
628-
options: [
629-
{ value: "percentage", label: "Percentage" },
630-
{ value: "text", label: "Text" },
631-
],
632-
},
633-
defaultValue: "percentage",
634-
helpText:
635-
"Display as <strong>-20%</strong> or <strong>Sale</strong> text.",
636-
},
637-
{
638-
type: "text",
637+
type: "textarea",
639638
label: "Sale badge text",
640639
name: "saleBadgeText",
641-
defaultValue: "Sale",
642-
placeholder: "Sale",
643-
condition: "saleBadgeContent.eq.text",
640+
defaultValue: "-[percentage]% Off",
641+
placeholder: "-[percentage]% Off, Saved [amount], or Sale",
642+
helpText: [
643+
"<p class='mb-1'>- Use <strong>[percentage]</strong> to display the discount percentage.</p>",
644+
"<p class='mb-1'>- Use <strong>[amount]</strong> to display the discount amount.</p>",
645+
"<p>E.g. <strong>-[percentage]% Off</strong>, <strong>Saved [amount]</strong>, or <strong>Sale</strong>.</p>",
646+
].join(""),
644647
},
645648
],
646649
},
@@ -841,7 +844,7 @@ export let themeSchema: HydrogenThemeSchema = {
841844
},
842845
{
843846
type: "switch",
844-
label: "Show New / Limited badges",
847+
label: "Show New badges",
845848
name: "pcardShowNewBadges",
846849
defaultValue: true,
847850
},

0 commit comments

Comments
 (0)