@fire-spark/rn-ui 0.0.1
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/dist/button.d.ts +16 -0
- package/dist/button.js +47 -0
- package/dist/cart.d.ts +62 -0
- package/dist/cart.js +100 -0
- package/dist/category-nav.d.ts +23 -0
- package/dist/category-nav.js +59 -0
- package/dist/category.d.ts +30 -0
- package/dist/category.js +78 -0
- package/dist/checkout.d.ts +67 -0
- package/dist/checkout.js +86 -0
- package/dist/cn-C_hacsED.js +6 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/menu.d.ts +21 -0
- package/dist/menu.js +42 -0
- package/dist/product-card.d.ts +31 -0
- package/dist/product-card.js +105 -0
- package/dist/product-detail.d.ts +88 -0
- package/dist/product-detail.js +185 -0
- package/dist/ui-image-A3QxzafV.js +17 -0
- package/dist/ui-image-bUZSq9v9.d.ts +11 -0
- package/dist/use-active-category.d.ts +29 -0
- package/dist/use-active-category.js +81 -0
- package/package.json +138 -0
package/dist/menu.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Text, View } from "react-native";
|
|
2
|
+
import { ComponentProps } from "react";
|
|
3
|
+
//#region src/menu.d.ts
|
|
4
|
+
type ViewProps = ComponentProps<typeof View> & {
|
|
5
|
+
className?: string;
|
|
6
|
+
};
|
|
7
|
+
type TextProps = ComponentProps<typeof Text> & {
|
|
8
|
+
className?: string;
|
|
9
|
+
};
|
|
10
|
+
type EmptyMediaProps = ViewProps & {
|
|
11
|
+
variant?: "icon" | "default";
|
|
12
|
+
};
|
|
13
|
+
declare const Menu: ({ className, ...props }: ViewProps) => import("react").JSX.Element;
|
|
14
|
+
declare const MenuEmpty: ({ className, ...props }: ViewProps) => import("react").JSX.Element;
|
|
15
|
+
declare const MenuEmptyHeader: ({ className, ...props }: ViewProps) => import("react").JSX.Element;
|
|
16
|
+
declare const MenuEmptyMedia: (props: EmptyMediaProps) => import("react").JSX.Element;
|
|
17
|
+
declare const MenuEmptyTitle: ({ className, ...props }: TextProps) => import("react").JSX.Element;
|
|
18
|
+
declare const MenuEmptyDescription: ({ className, ...props }: TextProps) => import("react").JSX.Element;
|
|
19
|
+
declare const MenuEmptyContent: ({ className, ...props }: ViewProps) => import("react").JSX.Element;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { Menu, MenuEmpty, MenuEmptyContent, MenuEmptyDescription, MenuEmptyHeader, MenuEmptyMedia, MenuEmptyTitle };
|
package/dist/menu.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { t as cn } from "./cn-C_hacsED.js";
|
|
2
|
+
import { Text, View } from "react-native";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
//#region src/menu.tsx
|
|
5
|
+
const menuBase = "Menu bg-background text-foreground min-h-0 flex-1 flex-col";
|
|
6
|
+
const emptyBase = `border-border bg-muted/30 min-h-0 w-full flex-1 flex-col items-center justify-center gap-4 rounded-xl border border-dashed px-6 py-12`;
|
|
7
|
+
const emptyTitleBase = "text-foreground text-sm font-semibold tracking-tight";
|
|
8
|
+
const emptyDescBase = "text-muted-foreground text-sm leading-relaxed";
|
|
9
|
+
const emptyContentBase = "flex-row flex-wrap items-center justify-center gap-2";
|
|
10
|
+
const Menu = ({ className, ...props }) => /* @__PURE__ */ jsx(View, {
|
|
11
|
+
className: cn(menuBase, className),
|
|
12
|
+
...props
|
|
13
|
+
});
|
|
14
|
+
const MenuEmpty = ({ className, ...props }) => /* @__PURE__ */ jsx(View, {
|
|
15
|
+
className: cn(emptyBase, className),
|
|
16
|
+
...props
|
|
17
|
+
});
|
|
18
|
+
const MenuEmptyHeader = ({ className, ...props }) => /* @__PURE__ */ jsx(View, {
|
|
19
|
+
className: cn("max-w-sm flex-col items-center gap-2", className),
|
|
20
|
+
...props
|
|
21
|
+
});
|
|
22
|
+
const MenuEmptyMedia = (props) => {
|
|
23
|
+
const { className, variant = "icon", ...rest } = props;
|
|
24
|
+
return /* @__PURE__ */ jsx(View, {
|
|
25
|
+
className: cn("mb-1 shrink-0 items-center justify-center", variant === "icon" ? "bg-muted size-12 rounded-full" : "text-muted-foreground", className),
|
|
26
|
+
...rest
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
const MenuEmptyTitle = ({ className, ...props }) => /* @__PURE__ */ jsx(Text, {
|
|
30
|
+
className: cn(emptyTitleBase, className),
|
|
31
|
+
...props
|
|
32
|
+
});
|
|
33
|
+
const MenuEmptyDescription = ({ className, ...props }) => /* @__PURE__ */ jsx(Text, {
|
|
34
|
+
className: cn(emptyDescBase, className),
|
|
35
|
+
...props
|
|
36
|
+
});
|
|
37
|
+
const MenuEmptyContent = ({ className, ...props }) => /* @__PURE__ */ jsx(View, {
|
|
38
|
+
className: cn(emptyContentBase, className),
|
|
39
|
+
...props
|
|
40
|
+
});
|
|
41
|
+
//#endregion
|
|
42
|
+
export { Menu, MenuEmpty, MenuEmptyContent, MenuEmptyDescription, MenuEmptyHeader, MenuEmptyMedia, MenuEmptyTitle };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { t as UiImageProps } from "./ui-image-bUZSq9v9.js";
|
|
2
|
+
import { Pressable, Text, View } from "react-native";
|
|
3
|
+
import { ComponentProps } from "react";
|
|
4
|
+
//#region src/product-card.d.ts
|
|
5
|
+
type ViewProps = ComponentProps<typeof View> & {
|
|
6
|
+
className?: string;
|
|
7
|
+
};
|
|
8
|
+
type TextProps = ComponentProps<typeof Text> & {
|
|
9
|
+
className?: string;
|
|
10
|
+
};
|
|
11
|
+
type PressableProps = ComponentProps<typeof Pressable> & {
|
|
12
|
+
className?: string;
|
|
13
|
+
};
|
|
14
|
+
declare const ProductCard: ({ className, ...props }: ViewProps) => import("react").JSX.Element;
|
|
15
|
+
declare const ProductCardContent: ({ className, ...props }: ViewProps) => import("react").JSX.Element;
|
|
16
|
+
declare const ProductCardMedia: ({ className, ...props }: ViewProps) => import("react").JSX.Element;
|
|
17
|
+
declare const ProductCardImageSlot: ({ className, ...props }: ViewProps) => import("react").JSX.Element;
|
|
18
|
+
declare const ProductCardImage: (props: UiImageProps) => import("react").JSX.Element;
|
|
19
|
+
declare const ProductCardHeader: ({ className, ...props }: ViewProps) => import("react").JSX.Element;
|
|
20
|
+
declare const ProductCardTitle: ({ className, ...props }: TextProps) => import("react").JSX.Element;
|
|
21
|
+
declare const ProductCardMeta: ({ className, ...props }: ViewProps) => import("react").JSX.Element;
|
|
22
|
+
declare const ProductCardDescription: ({ className, ...props }: TextProps) => import("react").JSX.Element;
|
|
23
|
+
declare const ProductCardFooter: ({ className, ...props }: ViewProps) => import("react").JSX.Element;
|
|
24
|
+
declare const ProductCardPrice: ({ className, ...props }: TextProps) => import("react").JSX.Element;
|
|
25
|
+
declare const ProductCardAnchorPrice: ({ className, ...props }: TextProps) => import("react").JSX.Element;
|
|
26
|
+
declare const ProductCardRating: ({ className, ...props }: TextProps) => import("react").JSX.Element;
|
|
27
|
+
declare const ProductCardBadge: ({ className, ...props }: TextProps) => import("react").JSX.Element;
|
|
28
|
+
declare const ProductCardDiscount: ({ className, ...props }: TextProps) => import("react").JSX.Element;
|
|
29
|
+
declare const ProductCardAddButton: (props: PressableProps) => import("react").JSX.Element;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { ProductCard, ProductCardAddButton, ProductCardAnchorPrice, ProductCardBadge, ProductCardContent, ProductCardDescription, ProductCardDiscount, ProductCardFooter, ProductCardHeader, ProductCardImage, ProductCardImageSlot, ProductCardMedia, ProductCardMeta, ProductCardPrice, ProductCardRating, ProductCardTitle };
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { t as cn } from "./cn-C_hacsED.js";
|
|
2
|
+
import { t as UiImage } from "./ui-image-A3QxzafV.js";
|
|
3
|
+
import { Pressable, Text, View } from "react-native";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
//#region src/product-card.tsx
|
|
6
|
+
const cardBase = `bg-card text-card-foreground border-border w-full flex-row overflow-hidden rounded-xl border`;
|
|
7
|
+
const contentBase = `ProductCardContent min-h-28 min-w-0 flex-1 flex-col justify-center gap-1 p-3`;
|
|
8
|
+
const titleBase = `text-card-foreground line-clamp-1 min-w-0 text-base min-h-[1.375em] font-semibold leading-snug`;
|
|
9
|
+
const footerBase = "mt-auto flex-row flex-wrap items-center gap-2 pt-1";
|
|
10
|
+
const priceBase = "text-card-foreground text-base font-semibold tabular-nums";
|
|
11
|
+
const anchorBase = "text-muted-foreground text-sm tabular-nums line-through";
|
|
12
|
+
const badgeBase = `bg-secondary text-secondary-foreground rounded-md px-2 py-0.5 text-xs font-medium`;
|
|
13
|
+
const discountBase = `w-fit rounded-full bg-red-600 px-2 py-0.5 text-xs font-semibold text-white`;
|
|
14
|
+
const addBtnBase = `bg-background border-border absolute bottom-2 right-2 z-10 size-8 items-center justify-center rounded-full`;
|
|
15
|
+
const ProductCard = ({ className, ...props }) => /* @__PURE__ */ jsx(View, {
|
|
16
|
+
className: cn(cardBase, className),
|
|
17
|
+
...props
|
|
18
|
+
});
|
|
19
|
+
const ProductCardContent = ({ className, ...props }) => /* @__PURE__ */ jsx(View, {
|
|
20
|
+
className: cn(contentBase, className),
|
|
21
|
+
...props
|
|
22
|
+
});
|
|
23
|
+
const ProductCardMedia = ({ className, ...props }) => /* @__PURE__ */ jsx(View, {
|
|
24
|
+
className: cn("bg-muted relative size-[164px] overflow-hidden", className),
|
|
25
|
+
...props
|
|
26
|
+
});
|
|
27
|
+
const ProductCardImageSlot = ({ className, ...props }) => /* @__PURE__ */ jsx(View, {
|
|
28
|
+
className: cn("absolute inset-0", className),
|
|
29
|
+
...props
|
|
30
|
+
});
|
|
31
|
+
const ProductCardImage = (props) => /* @__PURE__ */ jsx(ProductCardImageSlot, { children: /* @__PURE__ */ jsx(UiImage, { ...props }) });
|
|
32
|
+
const ProductCardHeader = ({ className, ...props }) => /* @__PURE__ */ jsx(View, {
|
|
33
|
+
className: cn("flex-col gap-1", className),
|
|
34
|
+
...props
|
|
35
|
+
});
|
|
36
|
+
const ProductCardTitle = ({ className, ...props }) => /* @__PURE__ */ jsx(Text, {
|
|
37
|
+
className: cn(titleBase, className),
|
|
38
|
+
numberOfLines: 1,
|
|
39
|
+
...props
|
|
40
|
+
});
|
|
41
|
+
const ProductCardMeta = ({ className, ...props }) => /* @__PURE__ */ jsx(View, {
|
|
42
|
+
className: cn("flex-row flex-wrap items-center gap-1", className),
|
|
43
|
+
...props
|
|
44
|
+
});
|
|
45
|
+
const ProductCardDescription = ({ className, ...props }) => /* @__PURE__ */ jsx(Text, {
|
|
46
|
+
className: cn("text-muted-foreground min-h-[2.75em] text-sm leading-snug", className),
|
|
47
|
+
numberOfLines: 2,
|
|
48
|
+
...props
|
|
49
|
+
});
|
|
50
|
+
const ProductCardFooter = ({ className, ...props }) => /* @__PURE__ */ jsx(View, {
|
|
51
|
+
className: cn(footerBase, className),
|
|
52
|
+
...props
|
|
53
|
+
});
|
|
54
|
+
const ProductCardPrice = ({ className, ...props }) => /* @__PURE__ */ jsx(Text, {
|
|
55
|
+
className: cn(priceBase, className),
|
|
56
|
+
...props
|
|
57
|
+
});
|
|
58
|
+
const ProductCardAnchorPrice = ({ className, ...props }) => /* @__PURE__ */ jsx(Text, {
|
|
59
|
+
className: cn(anchorBase, className),
|
|
60
|
+
...props
|
|
61
|
+
});
|
|
62
|
+
const ProductCardRating = ({ className, ...props }) => /* @__PURE__ */ jsx(Text, {
|
|
63
|
+
className: cn("text-sm font-medium", className),
|
|
64
|
+
...props
|
|
65
|
+
});
|
|
66
|
+
const ProductCardBadge = ({ className, ...props }) => /* @__PURE__ */ jsx(Text, {
|
|
67
|
+
className: cn(badgeBase, className),
|
|
68
|
+
...props
|
|
69
|
+
});
|
|
70
|
+
const ProductCardDiscount = ({ className, ...props }) => /* @__PURE__ */ jsx(Text, {
|
|
71
|
+
className: cn(discountBase, className),
|
|
72
|
+
...props
|
|
73
|
+
});
|
|
74
|
+
const plus1 = {
|
|
75
|
+
color: "#fff",
|
|
76
|
+
fontSize: 20
|
|
77
|
+
};
|
|
78
|
+
const plus2 = {
|
|
79
|
+
lineHeight: 22,
|
|
80
|
+
fontWeight: "400"
|
|
81
|
+
};
|
|
82
|
+
const plusStyle = {
|
|
83
|
+
...plus1,
|
|
84
|
+
...plus2
|
|
85
|
+
};
|
|
86
|
+
const addBtnStyle = { borderWidth: .5 };
|
|
87
|
+
const ProductCardAddButton = (props) => {
|
|
88
|
+
const { children, className, style, ...rest } = props;
|
|
89
|
+
const fallback = /* @__PURE__ */ jsx(Text, {
|
|
90
|
+
style: plusStyle,
|
|
91
|
+
children: "+"
|
|
92
|
+
});
|
|
93
|
+
const btnClass = cn(addBtnBase, className);
|
|
94
|
+
let btnStyle = addBtnStyle;
|
|
95
|
+
if (typeof style === "function") btnStyle = (state) => [addBtnStyle, style(state)];
|
|
96
|
+
else if (style != null) btnStyle = [addBtnStyle, style];
|
|
97
|
+
return /* @__PURE__ */ jsx(Pressable, {
|
|
98
|
+
className: btnClass,
|
|
99
|
+
style: btnStyle,
|
|
100
|
+
...rest,
|
|
101
|
+
children: children ?? fallback
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
//#endregion
|
|
105
|
+
export { ProductCard, ProductCardAddButton, ProductCardAnchorPrice, ProductCardBadge, ProductCardContent, ProductCardDescription, ProductCardDiscount, ProductCardFooter, ProductCardHeader, ProductCardImage, ProductCardImageSlot, ProductCardMedia, ProductCardMeta, ProductCardPrice, ProductCardRating, ProductCardTitle };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { t as UiImageProps } from "./ui-image-bUZSq9v9.js";
|
|
2
|
+
import { Pressable, ScrollView, Text, View } from "react-native";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
//#region src/product-detail.d.ts
|
|
5
|
+
type ProductDetailQuantities = Record<string, Record<string, number>>;
|
|
6
|
+
type ProductDetailState = {
|
|
7
|
+
quantities: ProductDetailQuantities;
|
|
8
|
+
viewStack: string[];
|
|
9
|
+
};
|
|
10
|
+
type ProductDetailActions = {
|
|
11
|
+
selectSingle: (groupId: string, modifierId: string) => void;
|
|
12
|
+
toggleModifier: (groupId: string, modifierId: string) => void;
|
|
13
|
+
setQuantity: (groupId: string, modifierId: string, quantity: number) => void;
|
|
14
|
+
clearGroup: (groupId: string) => void;
|
|
15
|
+
pushView: (viewId: string) => void;
|
|
16
|
+
popView: () => void;
|
|
17
|
+
reset: () => void;
|
|
18
|
+
};
|
|
19
|
+
type ProductDetailContextMeta = {
|
|
20
|
+
productId?: string;
|
|
21
|
+
};
|
|
22
|
+
type ProductDetailContextValue = {
|
|
23
|
+
state: ProductDetailState;
|
|
24
|
+
actions: ProductDetailActions;
|
|
25
|
+
meta: ProductDetailContextMeta;
|
|
26
|
+
};
|
|
27
|
+
type ProductDetailProviderProps = {
|
|
28
|
+
children: React.ReactNode;
|
|
29
|
+
productId?: string;
|
|
30
|
+
initialQuantities?: ProductDetailQuantities;
|
|
31
|
+
};
|
|
32
|
+
declare const useProductDetail: () => ProductDetailContextValue;
|
|
33
|
+
declare const ProductDetailProvider: (props: ProductDetailProviderProps) => React.JSX.Element;
|
|
34
|
+
type ViewProps = React.ComponentProps<typeof View> & {
|
|
35
|
+
className?: string;
|
|
36
|
+
};
|
|
37
|
+
type BodyScroll = {
|
|
38
|
+
scrollTo: (o: {
|
|
39
|
+
y: number;
|
|
40
|
+
animated?: boolean;
|
|
41
|
+
}) => void;
|
|
42
|
+
};
|
|
43
|
+
type ScrollProps = React.ComponentProps<typeof ScrollView> & {
|
|
44
|
+
className?: string;
|
|
45
|
+
ref?: React.Ref<BodyScroll>;
|
|
46
|
+
};
|
|
47
|
+
type TextProps = React.ComponentProps<typeof Text> & {
|
|
48
|
+
className?: string;
|
|
49
|
+
};
|
|
50
|
+
type PressableProps = React.ComponentProps<typeof Pressable> & {
|
|
51
|
+
className?: string;
|
|
52
|
+
};
|
|
53
|
+
declare const ProductDetail: ({ className, ...rest }: ViewProps) => React.JSX.Element;
|
|
54
|
+
declare const ProductDetailBody: (props: ScrollProps) => React.JSX.Element;
|
|
55
|
+
declare const ProductDetailLayout: ({ className, ...rest }: ViewProps) => React.JSX.Element;
|
|
56
|
+
declare const ProductDetailMedia: ({ className, ...rest }: ViewProps) => React.JSX.Element;
|
|
57
|
+
declare const ProductDetailImageSlot: ({ className, ...rest }: ViewProps) => React.JSX.Element;
|
|
58
|
+
declare const ProductDetailImage: (props: UiImageProps) => React.JSX.Element;
|
|
59
|
+
declare const ProductDetailPanel: ({ className, ...rest }: ViewProps) => React.JSX.Element;
|
|
60
|
+
declare const ProductDetailHeader: ({ className, ...rest }: ViewProps) => React.JSX.Element;
|
|
61
|
+
declare const ProductDetailTitle: ({ className, ...rest }: TextProps) => React.JSX.Element;
|
|
62
|
+
declare const ProductDetailMeta: ({ className, ...rest }: ViewProps) => React.JSX.Element;
|
|
63
|
+
declare const ProductDetailPrice: ({ className, ...rest }: TextProps) => React.JSX.Element;
|
|
64
|
+
declare const ProductDetailAnchorPrice: ({ className, ...rest }: TextProps) => React.JSX.Element;
|
|
65
|
+
declare const ProductDetailDiscount: ({ className, ...rest }: TextProps) => React.JSX.Element;
|
|
66
|
+
declare const ProductDetailDescription: ({ className, ...rest }: TextProps) => React.JSX.Element;
|
|
67
|
+
declare const ProductDetailBadge: ({ className, ...rest }: TextProps) => React.JSX.Element;
|
|
68
|
+
declare const ProductDetailGroups: ({ className, ...rest }: ViewProps) => React.JSX.Element;
|
|
69
|
+
declare const ProductDetailGroup: ({ className, ...rest }: ViewProps) => React.JSX.Element;
|
|
70
|
+
declare const ProductDetailGroupHeader: ({ className, ...rest }: ViewProps) => React.JSX.Element;
|
|
71
|
+
declare const ProductDetailGroupTitle: ({ className, ...rest }: TextProps) => React.JSX.Element;
|
|
72
|
+
declare const ProductDetailGroupHint: ({ className, ...rest }: TextProps) => React.JSX.Element;
|
|
73
|
+
declare const ProductDetailGroupDescription: ({ className, ...rest }: TextProps) => React.JSX.Element;
|
|
74
|
+
declare const ProductDetailGroupRequired: ({ className, ...rest }: TextProps) => React.JSX.Element;
|
|
75
|
+
declare const ProductDetailOptions: ({ className, ...rest }: ViewProps) => React.JSX.Element;
|
|
76
|
+
declare const ProductDetailOption: ({ className, ...rest }: ViewProps) => React.JSX.Element;
|
|
77
|
+
declare const ProductDetailOptionLabel: ({ className, ...rest }: ViewProps) => React.JSX.Element;
|
|
78
|
+
declare const ProductDetailOptionName: ({ className, ...rest }: TextProps) => React.JSX.Element;
|
|
79
|
+
declare const ProductDetailOptionPrice: ({ className, ...rest }: TextProps) => React.JSX.Element;
|
|
80
|
+
declare const ProductDetailOptionControl: ({ className, ...rest }: ViewProps) => React.JSX.Element;
|
|
81
|
+
declare const ProductDetailBack: ({ children, className, ...rest }: PressableProps) => React.JSX.Element;
|
|
82
|
+
declare const ProductDetailRelated: ({ className, ...rest }: ViewProps) => React.JSX.Element;
|
|
83
|
+
declare const ProductDetailRelatedTitle: ({ className, ...rest }: TextProps) => React.JSX.Element;
|
|
84
|
+
declare const ProductDetailRelatedGrid: ({ className, ...rest }: ViewProps) => React.JSX.Element;
|
|
85
|
+
declare const ProductDetailFooter: ({ className, ...rest }: ViewProps) => React.JSX.Element;
|
|
86
|
+
declare const ProductDetailSubmit: ({ children, className, ...rest }: PressableProps) => React.JSX.Element;
|
|
87
|
+
//#endregion
|
|
88
|
+
export { ProductDetail, ProductDetailActions, ProductDetailAnchorPrice, ProductDetailBack, ProductDetailBadge, ProductDetailBody, ProductDetailContextMeta, ProductDetailContextValue, ProductDetailDescription, ProductDetailDiscount, ProductDetailFooter, ProductDetailGroup, ProductDetailGroupDescription, ProductDetailGroupHeader, ProductDetailGroupHint, ProductDetailGroupRequired, ProductDetailGroupTitle, ProductDetailGroups, ProductDetailHeader, ProductDetailImage, ProductDetailImageSlot, ProductDetailLayout, ProductDetailMedia, ProductDetailMeta, ProductDetailOption, ProductDetailOptionControl, ProductDetailOptionLabel, ProductDetailOptionName, ProductDetailOptionPrice, ProductDetailOptions, ProductDetailPanel, ProductDetailPrice, ProductDetailProvider, ProductDetailProviderProps, ProductDetailQuantities, ProductDetailRelated, ProductDetailRelatedGrid, ProductDetailRelatedTitle, ProductDetailState, ProductDetailSubmit, ProductDetailTitle, useProductDetail };
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { t as cn } from "./cn-C_hacsED.js";
|
|
2
|
+
import { t as UiImage } from "./ui-image-A3QxzafV.js";
|
|
3
|
+
import { Pressable, ScrollView, Text, View } from "react-native";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
//#region src/product-detail.tsx
|
|
7
|
+
const ProductDetailContext = React.createContext(null);
|
|
8
|
+
const useProductDetail = () => {
|
|
9
|
+
const value = React.use(ProductDetailContext);
|
|
10
|
+
if (!value) throw new Error("useProductDetail needs ProductDetailProvider");
|
|
11
|
+
return value;
|
|
12
|
+
};
|
|
13
|
+
const ProductDetailProvider = (props) => {
|
|
14
|
+
const { children, productId } = props;
|
|
15
|
+
const { initialQuantities = {} } = props;
|
|
16
|
+
const [quantities, setQuantities] = React.useState(initialQuantities);
|
|
17
|
+
const [viewStack, setViewStack] = React.useState([]);
|
|
18
|
+
const selectSingle = (groupId, modifierId) => {
|
|
19
|
+
const next = { [modifierId]: 1 };
|
|
20
|
+
setQuantities((current) => ({
|
|
21
|
+
...current,
|
|
22
|
+
[groupId]: next
|
|
23
|
+
}));
|
|
24
|
+
};
|
|
25
|
+
const toggleModifier = (groupId, modifierId) => {
|
|
26
|
+
setQuantities((current) => {
|
|
27
|
+
const group = { ...current[groupId] ?? {} };
|
|
28
|
+
if ((group[modifierId] ?? 0) > 0) delete group[modifierId];
|
|
29
|
+
else group[modifierId] = 1;
|
|
30
|
+
return {
|
|
31
|
+
...current,
|
|
32
|
+
[groupId]: group
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
const setQuantity = (groupId, modifierId, qty) => {
|
|
37
|
+
setQuantities((current) => {
|
|
38
|
+
const group = { ...current[groupId] ?? {} };
|
|
39
|
+
const next = Math.max(0, Math.floor(qty));
|
|
40
|
+
if (next <= 0) delete group[modifierId];
|
|
41
|
+
else group[modifierId] = next;
|
|
42
|
+
return {
|
|
43
|
+
...current,
|
|
44
|
+
[groupId]: group
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
const clearGroup = (groupId) => {
|
|
49
|
+
setQuantities((current) => ({
|
|
50
|
+
...current,
|
|
51
|
+
[groupId]: {}
|
|
52
|
+
}));
|
|
53
|
+
};
|
|
54
|
+
const pushView = (viewId) => {
|
|
55
|
+
setViewStack((current) => [...current, viewId]);
|
|
56
|
+
};
|
|
57
|
+
const popView = () => {
|
|
58
|
+
setViewStack((current) => current.slice(0, -1));
|
|
59
|
+
};
|
|
60
|
+
const reset = () => {
|
|
61
|
+
setQuantities(initialQuantities);
|
|
62
|
+
setViewStack([]);
|
|
63
|
+
};
|
|
64
|
+
const state = {
|
|
65
|
+
quantities,
|
|
66
|
+
viewStack
|
|
67
|
+
};
|
|
68
|
+
const a1 = {
|
|
69
|
+
selectSingle,
|
|
70
|
+
toggleModifier,
|
|
71
|
+
setQuantity
|
|
72
|
+
};
|
|
73
|
+
const a2 = {
|
|
74
|
+
clearGroup,
|
|
75
|
+
pushView,
|
|
76
|
+
popView,
|
|
77
|
+
reset
|
|
78
|
+
};
|
|
79
|
+
const value = {
|
|
80
|
+
state,
|
|
81
|
+
actions: {
|
|
82
|
+
...a1,
|
|
83
|
+
...a2
|
|
84
|
+
},
|
|
85
|
+
meta: { productId }
|
|
86
|
+
};
|
|
87
|
+
return /* @__PURE__ */ jsx(ProductDetailContext, {
|
|
88
|
+
value,
|
|
89
|
+
children
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
const view = (base) => {
|
|
93
|
+
return ({ className, ...rest }) => /* @__PURE__ */ jsx(View, {
|
|
94
|
+
className: cn(base, className),
|
|
95
|
+
...rest
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
const text = (base) => {
|
|
99
|
+
return ({ className, ...rest }) => /* @__PURE__ */ jsx(Text, {
|
|
100
|
+
className: cn(base, className),
|
|
101
|
+
...rest
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
const button = (base, labelTone) => {
|
|
105
|
+
return ({ children, className, ...rest }) => {
|
|
106
|
+
const labelClass = `${labelTone} text-sm font-medium`;
|
|
107
|
+
let content = children;
|
|
108
|
+
if (typeof children === "string" || typeof children === "number") content = /* @__PURE__ */ jsx(Text, {
|
|
109
|
+
className: labelClass,
|
|
110
|
+
children
|
|
111
|
+
});
|
|
112
|
+
return /* @__PURE__ */ jsx(Pressable, {
|
|
113
|
+
className: cn(base, className),
|
|
114
|
+
...rest,
|
|
115
|
+
children: content
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
const rootBase = "bg-background text-foreground min-h-0 flex-1 flex-col";
|
|
120
|
+
const titleBase = `text-foreground text-2xl font-bold leading-tight tracking-tight`;
|
|
121
|
+
const priceBase = "text-foreground text-lg font-semibold tabular-nums";
|
|
122
|
+
const groupTitleBase = `text-foreground text-base font-semibold tracking-tight`;
|
|
123
|
+
const optionNameBase = "text-foreground text-sm font-medium";
|
|
124
|
+
const badgeBase = `bg-muted text-foreground w-fit rounded-full px-2.5 py-1 text-xs font-medium`;
|
|
125
|
+
const requiredBase = `bg-muted text-foreground shrink-0 rounded-full px-2.5 py-1 text-xs font-medium`;
|
|
126
|
+
const optionsBase = "border-border flex-col rounded-xl border";
|
|
127
|
+
const optionBase = `border-border flex-row items-center justify-between gap-3 border-b px-4 py-3`;
|
|
128
|
+
const relatedBase = "border-border flex-col gap-4 border-t px-6 py-6";
|
|
129
|
+
const relatedTitleBase = `text-foreground text-base font-semibold tracking-tight`;
|
|
130
|
+
const footerBase = `bg-background border-border shrink-0 border-t px-6 pt-4 pb-6`;
|
|
131
|
+
const submitBase = `bg-primary w-full items-center justify-center rounded-full px-4 py-3`;
|
|
132
|
+
const submitLabel = "text-primary-foreground";
|
|
133
|
+
const backBase = "text-muted-foreground -ml-1 w-fit px-1 py-0.5";
|
|
134
|
+
const mediaBase = "bg-muted relative aspect-[3/2] w-full overflow-hidden";
|
|
135
|
+
const anchorBase = "text-muted-foreground text-sm tabular-nums line-through";
|
|
136
|
+
const discountBase = `w-fit rounded-full bg-red-600 px-2 py-0.5 text-xs font-semibold text-white`;
|
|
137
|
+
const descBase = "text-muted-foreground text-sm leading-relaxed";
|
|
138
|
+
const groupHeaderBase = "flex-row items-start justify-between gap-3";
|
|
139
|
+
const groupDescBase = "text-muted-foreground text-xs";
|
|
140
|
+
const optPriceBase = "text-muted-foreground text-sm tabular-nums";
|
|
141
|
+
const optControlBase = "shrink-0 flex-row items-center gap-2";
|
|
142
|
+
const metaBase = "flex-row flex-wrap items-center gap-2";
|
|
143
|
+
const ProductDetail = view(rootBase);
|
|
144
|
+
const ProductDetailBody = (props) => {
|
|
145
|
+
const { className, ref, ...rest } = props;
|
|
146
|
+
return /* @__PURE__ */ jsx(ScrollView, {
|
|
147
|
+
ref,
|
|
148
|
+
className: cn("min-h-0 flex-1", className),
|
|
149
|
+
...rest
|
|
150
|
+
});
|
|
151
|
+
};
|
|
152
|
+
const ProductDetailLayout = view("min-w-0 flex-col gap-4");
|
|
153
|
+
const ProductDetailMedia = view(mediaBase);
|
|
154
|
+
const ProductDetailImageSlot = view("absolute inset-0");
|
|
155
|
+
const ProductDetailImage = (props) => /* @__PURE__ */ jsx(ProductDetailImageSlot, { children: /* @__PURE__ */ jsx(UiImage, { ...props }) });
|
|
156
|
+
const ProductDetailPanel = view("min-w-0 flex-col px-6");
|
|
157
|
+
const ProductDetailHeader = view("flex-col gap-3 pb-2 pt-6");
|
|
158
|
+
const ProductDetailTitle = text(titleBase);
|
|
159
|
+
const ProductDetailMeta = view(metaBase);
|
|
160
|
+
const ProductDetailPrice = text(priceBase);
|
|
161
|
+
const ProductDetailAnchorPrice = text(anchorBase);
|
|
162
|
+
const ProductDetailDiscount = text(discountBase);
|
|
163
|
+
const ProductDetailDescription = text(descBase);
|
|
164
|
+
const ProductDetailBadge = text(badgeBase);
|
|
165
|
+
const ProductDetailGroups = view("flex-col gap-8 py-6");
|
|
166
|
+
const ProductDetailGroup = view("flex-col gap-3");
|
|
167
|
+
const ProductDetailGroupHeader = view(groupHeaderBase);
|
|
168
|
+
const ProductDetailGroupTitle = text(groupTitleBase);
|
|
169
|
+
const ProductDetailGroupHint = text("text-muted-foreground text-sm");
|
|
170
|
+
const ProductDetailGroupDescription = text(groupDescBase);
|
|
171
|
+
const ProductDetailGroupRequired = text(requiredBase);
|
|
172
|
+
const ProductDetailOptions = view(optionsBase);
|
|
173
|
+
const ProductDetailOption = view(optionBase);
|
|
174
|
+
const ProductDetailOptionLabel = view("min-w-0 flex-1");
|
|
175
|
+
const ProductDetailOptionName = text(optionNameBase);
|
|
176
|
+
const ProductDetailOptionPrice = text(optPriceBase);
|
|
177
|
+
const ProductDetailOptionControl = view(optControlBase);
|
|
178
|
+
const ProductDetailBack = button(backBase, "text-muted-foreground");
|
|
179
|
+
const ProductDetailRelated = view(relatedBase);
|
|
180
|
+
const ProductDetailRelatedTitle = text(relatedTitleBase);
|
|
181
|
+
const ProductDetailRelatedGrid = view("flex-row flex-wrap gap-3");
|
|
182
|
+
const ProductDetailFooter = view(footerBase);
|
|
183
|
+
const ProductDetailSubmit = button(submitBase, submitLabel);
|
|
184
|
+
//#endregion
|
|
185
|
+
export { ProductDetail, ProductDetailAnchorPrice, ProductDetailBack, ProductDetailBadge, ProductDetailBody, ProductDetailDescription, ProductDetailDiscount, ProductDetailFooter, ProductDetailGroup, ProductDetailGroupDescription, ProductDetailGroupHeader, ProductDetailGroupHint, ProductDetailGroupRequired, ProductDetailGroupTitle, ProductDetailGroups, ProductDetailHeader, ProductDetailImage, ProductDetailImageSlot, ProductDetailLayout, ProductDetailMedia, ProductDetailMeta, ProductDetailOption, ProductDetailOptionControl, ProductDetailOptionLabel, ProductDetailOptionName, ProductDetailOptionPrice, ProductDetailOptions, ProductDetailPanel, ProductDetailPrice, ProductDetailProvider, ProductDetailRelated, ProductDetailRelatedGrid, ProductDetailRelatedTitle, ProductDetailSubmit, ProductDetailTitle, useProductDetail };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { t as cn } from "./cn-C_hacsED.js";
|
|
2
|
+
import { Image } from "react-native";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
//#region src/lib/ui-image.tsx
|
|
5
|
+
/** Default RN Image, or any base via `as` (e.g. expo-image). */
|
|
6
|
+
const UiImage = (props) => {
|
|
7
|
+
const { as, className, ...rest } = props;
|
|
8
|
+
const Comp = as ?? Image;
|
|
9
|
+
const cover = as == null ? { resizeMode: "cover" } : {};
|
|
10
|
+
return /* @__PURE__ */ jsx(Comp, {
|
|
11
|
+
className: cn("size-full", className),
|
|
12
|
+
...cover,
|
|
13
|
+
...rest
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
export { UiImage as t };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Image } from "react-native";
|
|
2
|
+
import { ComponentProps, ElementType } from "react";
|
|
3
|
+
//#region src/lib/ui-image.d.ts
|
|
4
|
+
type RnImageProps = ComponentProps<typeof Image> & {
|
|
5
|
+
className?: string;
|
|
6
|
+
};
|
|
7
|
+
type UiImageProps = RnImageProps & {
|
|
8
|
+
as?: ElementType;
|
|
9
|
+
};
|
|
10
|
+
//#endregion
|
|
11
|
+
export { UiImageProps as t };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { LayoutChangeEvent } from "react-native";
|
|
2
|
+
import { RefObject } from "react";
|
|
3
|
+
//#region src/use-active-category.d.ts
|
|
4
|
+
type Scrollable = {
|
|
5
|
+
scrollTo: (opts: {
|
|
6
|
+
y: number;
|
|
7
|
+
animated?: boolean;
|
|
8
|
+
}) => void;
|
|
9
|
+
};
|
|
10
|
+
type UseActiveCategoryOptions = {
|
|
11
|
+
categoryIds: readonly string[];
|
|
12
|
+
offset?: number;
|
|
13
|
+
scrollRef?: RefObject<Scrollable | null>;
|
|
14
|
+
};
|
|
15
|
+
declare const useActiveCategory: (opts: UseActiveCategoryOptions) => {
|
|
16
|
+
activeCategoryId: string;
|
|
17
|
+
setActiveCategoryId: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
18
|
+
selectCategory: (categoryId: string) => void;
|
|
19
|
+
onScroll: (event: {
|
|
20
|
+
nativeEvent: {
|
|
21
|
+
contentOffset: {
|
|
22
|
+
y: number;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
}) => void;
|
|
26
|
+
registerCategory: (categoryId: string) => (event: LayoutChangeEvent) => void;
|
|
27
|
+
};
|
|
28
|
+
//#endregion
|
|
29
|
+
export { UseActiveCategoryOptions, useActiveCategory };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
//#region src/use-active-category.ts
|
|
3
|
+
/** Wait until scroll events stop before trusting the spy again. */
|
|
4
|
+
const SCROLL_SETTLE_MS = 140;
|
|
5
|
+
/** Fallback if scroll never starts; scroll handler re-arms while moving. */
|
|
6
|
+
const SCROLL_LOCK_FALLBACK_MS = 1e3;
|
|
7
|
+
const useActiveCategory = (opts) => {
|
|
8
|
+
const { categoryIds, offset = 0, scrollRef } = opts;
|
|
9
|
+
const [activeCategoryId, setActiveCategoryId] = useState(() => {
|
|
10
|
+
return categoryIds[0] ?? "";
|
|
11
|
+
});
|
|
12
|
+
const offsetsRef = useRef(/* @__PURE__ */ new Map());
|
|
13
|
+
const lockedIdRef = useRef(null);
|
|
14
|
+
const settleTimerRef = useRef(null);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (categoryIds.length === 0) {
|
|
17
|
+
setActiveCategoryId("");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
setActiveCategoryId((current) => {
|
|
21
|
+
if (categoryIds.includes(current)) return current;
|
|
22
|
+
return categoryIds[0] ?? "";
|
|
23
|
+
});
|
|
24
|
+
}, [categoryIds, categoryIds.join("\0")]);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
return () => {
|
|
27
|
+
if (settleTimerRef.current) clearTimeout(settleTimerRef.current);
|
|
28
|
+
};
|
|
29
|
+
}, []);
|
|
30
|
+
const armSettleUnlock = () => {
|
|
31
|
+
if (settleTimerRef.current) clearTimeout(settleTimerRef.current);
|
|
32
|
+
settleTimerRef.current = setTimeout(() => {
|
|
33
|
+
lockedIdRef.current = null;
|
|
34
|
+
}, SCROLL_SETTLE_MS);
|
|
35
|
+
};
|
|
36
|
+
const registerCategory = (categoryId) => {
|
|
37
|
+
return (event) => {
|
|
38
|
+
const y = event.nativeEvent.layout.y;
|
|
39
|
+
offsetsRef.current.set(categoryId, y);
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
const onScroll = (event) => {
|
|
43
|
+
const scrollY = event.nativeEvent.contentOffset.y;
|
|
44
|
+
if (lockedIdRef.current) {
|
|
45
|
+
armSettleUnlock();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const threshold = scrollY + offset + 1;
|
|
49
|
+
let current = categoryIds[0] ?? "";
|
|
50
|
+
for (const id of categoryIds) {
|
|
51
|
+
const top = offsetsRef.current.get(id);
|
|
52
|
+
if (top !== void 0 && top <= threshold) current = id;
|
|
53
|
+
}
|
|
54
|
+
setActiveCategoryId((previous) => {
|
|
55
|
+
return previous === current ? previous : current;
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
const selectCategory = (categoryId) => {
|
|
59
|
+
lockedIdRef.current = categoryId;
|
|
60
|
+
setActiveCategoryId(categoryId);
|
|
61
|
+
const top = offsetsRef.current.get(categoryId) ?? 0;
|
|
62
|
+
const y = Math.max(0, top - offset);
|
|
63
|
+
scrollRef?.current?.scrollTo({
|
|
64
|
+
y,
|
|
65
|
+
animated: true
|
|
66
|
+
});
|
|
67
|
+
if (settleTimerRef.current) clearTimeout(settleTimerRef.current);
|
|
68
|
+
settleTimerRef.current = setTimeout(() => {
|
|
69
|
+
lockedIdRef.current = null;
|
|
70
|
+
}, SCROLL_LOCK_FALLBACK_MS);
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
activeCategoryId,
|
|
74
|
+
setActiveCategoryId,
|
|
75
|
+
selectCategory,
|
|
76
|
+
onScroll,
|
|
77
|
+
registerCategory
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
//#endregion
|
|
81
|
+
export { useActiveCategory };
|