@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/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 };