@wix/headless-restaurants-olo 0.0.6 → 0.0.8

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.
@@ -3,6 +3,7 @@ import { Commerce } from '@wix/ecom/components';
3
3
  import { type LineItem } from '@wix/ecom/services';
4
4
  import { type AsChildChildren } from '@wix/headless-utils/react';
5
5
  import { ItemServiceConfig } from '../services/item-details-service.js';
6
+ import { EnhancedModifier, EnhancedModifierGroup, EnhancedVariant } from '@wix/headless-restaurants-menus/services';
6
7
  /**
7
8
  * Root component for menu item display and interaction.
8
9
  * Provides context for all menu item-related components like name, price, description, image, etc.
@@ -46,36 +47,6 @@ export interface ItemDetailsNameProps {
46
47
  className?: string;
47
48
  }
48
49
  export declare const Name: React.ForwardRefExoticComponent<ItemDetailsNameProps & React.RefAttributes<HTMLElement>>;
49
- /**
50
- * Displays the item image with customizable rendering.
51
- *
52
- * @component
53
- * @example
54
- * ```tsx
55
- * <ItemDetails.Image />
56
- * <ItemDetails.Image asChild>
57
- * <img className="rounded" />
58
- * </ItemDetails.Image>
59
- * ```
60
- */
61
- export interface ItemDetailsImageProps {
62
- asChild?: boolean;
63
- /**
64
- * Custom render function when using asChild.
65
- * Receives an object with:
66
- * - hasImage: boolean - whether the item has an image
67
- * - image: string - the actual image element (WixMediaImage)
68
- * - altText: string - the alt text for the image
69
- */
70
- children?: (props: {
71
- hasImage: boolean;
72
- image?: string;
73
- altText: string;
74
- }) => React.ReactNode;
75
- /** CSS classes to apply to the default element */
76
- className?: string;
77
- }
78
- export declare const Image: React.ForwardRefExoticComponent<ItemDetailsImageProps & React.RefAttributes<HTMLElement>>;
79
50
  /**
80
51
  * Displays the item price with customizable rendering.
81
52
  *
@@ -113,52 +84,80 @@ export interface ItemDetailsDescriptionProps {
113
84
  }
114
85
  export declare const Description: React.ForwardRefExoticComponent<ItemDetailsDescriptionProps & React.RefAttributes<HTMLElement>>;
115
86
  /**
116
- * Wrapper component for Item.LabelsRepeater.
117
- * Renders the labels for the item using the headless component.
87
+ * Wrapper component for CoreItemDetails.ModifierComponent.
88
+ * Renders a single modifier with checkbox functionality.
118
89
  *
119
90
  * @component
120
91
  * @example
121
92
  * ```tsx
122
- * <ItemDetails.Labels>
123
- * {(label) => <span>{label.name}</span>}
124
- * </ItemDetails.Labels>
93
+ * <ItemDetails.Modifier>
94
+ * {({ modifier, isSelected, onToggle }) => (
95
+ * <div style={{ display: "flex", alignItems: "center" }}>
96
+ * <CheckboxPrimitive.Root
97
+ * className="CheckboxRoot"
98
+ * checked={isSelected}
99
+ * onCheckedChange={onToggle}
100
+ * id={modifier._id}
101
+ * >
102
+ * <CheckboxPrimitive.Indicator className="CheckboxIndicator">
103
+ * <CheckIcon />
104
+ * </CheckboxPrimitive.Indicator>
105
+ * </CheckboxPrimitive.Root>
106
+ * <label className="Label" htmlFor={modifier._id}>
107
+ * {modifier.name}
108
+ * </label>
109
+ * </div>
110
+ * )}
111
+ * </ItemDetails.Modifier>
125
112
  * ```
126
113
  */
127
- export interface ItemDetailsLabelsProps {
114
+ export interface ItemDetailsModifiersSingleSelectProps {
128
115
  children?: AsChildChildren<{
129
- label: string;
116
+ selectedModifierIds: string[];
117
+ onToggle: (modifierId: string) => void;
118
+ modifierGroup: EnhancedModifierGroup;
119
+ modifiers: EnhancedModifier[];
130
120
  }>;
131
121
  className?: string;
132
122
  asChild?: boolean;
133
- iconClassName?: string;
134
- nameClassName?: string;
123
+ modifierNameClassName?: string;
124
+ modifierPriceClassName?: string;
135
125
  }
136
- export declare const Labels: React.ForwardRefExoticComponent<ItemDetailsLabelsProps & React.RefAttributes<HTMLElement>>;
137
- /**
138
- * Wrapper component for Item.ModifierGroupsRepeater.
139
- * Renders the modifier groups for the item using the headless component.
140
- *
141
- * @component
142
- * @example
143
- * ```tsx
144
- * <ItemDetails.ModifierGroups>
145
- * {(modifierGroup) => <span>{modifierGroup.name}</span>}
146
- * </ItemDetails.ModifierGroups>
147
- * ```
148
- */
149
- export interface ItemDetailsModifierGroupsProps {
126
+ export interface ModifierCheckboxProps {
127
+ selectedModifierIds: string[];
128
+ onToggle: (modifierId: string) => void;
129
+ className?: string;
130
+ asChild?: boolean;
131
+ modifierNameClassName?: string;
132
+ modifierPriceClassName?: string;
133
+ children?: AsChildChildren<{
134
+ selectedModifierIds: string[];
135
+ onToggle: (modifierId: string) => void;
136
+ }>;
137
+ }
138
+ export declare const ModifierCheckbox: React.ForwardRefExoticComponent<ModifierCheckboxProps & React.RefAttributes<HTMLElement>>;
139
+ export interface ModifierRadioProps {
140
+ modifierNameClassName?: string;
141
+ modifierPriceClassName?: string;
142
+ }
143
+ export declare const ModifierRadio: React.ForwardRefExoticComponent<ModifierRadioProps & React.RefAttributes<HTMLElement>>;
144
+ export declare const ModifiersSingleSelect: React.ForwardRefExoticComponent<ItemDetailsModifiersSingleSelectProps & React.RefAttributes<HTMLElement>>;
145
+ export interface ItemDetailsModifiersMultiSelectProps {
150
146
  children?: AsChildChildren<{
151
- modifierGroup: any;
147
+ selectedModifierIds: string[];
148
+ onToggle: (modifierId: string) => void;
149
+ modifierGroup: EnhancedModifierGroup;
150
+ modifiers: EnhancedModifier[];
152
151
  }>;
153
152
  className?: string;
154
153
  asChild?: boolean;
155
154
  modifierNameClassName?: string;
156
155
  modifierPriceClassName?: string;
157
156
  }
158
- export declare const ModifierGroups: React.ForwardRefExoticComponent<ItemDetailsModifierGroupsProps & React.RefAttributes<HTMLElement>>;
157
+ export declare const ModifiersMultiSelect: React.ForwardRefExoticComponent<ItemDetailsModifiersMultiSelectProps & React.RefAttributes<HTMLElement>>;
159
158
  /**
160
- * Wrapper component for Item.VariantsRepeater.
161
- * Renders the variants for the item using the headless component.
159
+ * Wrapper component for CoreItemDetails.VariantsComponent.
160
+ * Renders the variants for the item using Radix UI RadioGroup.
162
161
  *
163
162
  * @component
164
163
  * @example
@@ -170,12 +169,17 @@ export declare const ModifierGroups: React.ForwardRefExoticComponent<ItemDetails
170
169
  */
171
170
  export interface ItemDetailsVariantsProps {
172
171
  children?: AsChildChildren<{
173
- variant: any;
172
+ variant: EnhancedVariant;
173
+ variants: EnhancedVariant[];
174
+ hasVariants: boolean;
175
+ selectedVariantId?: string;
176
+ onVariantChange?: (variantId: string) => void;
174
177
  }>;
175
178
  className?: string;
176
179
  asChild?: boolean;
177
180
  variantNameClassName?: string;
178
181
  variantPriceClassName?: string;
182
+ emptyState?: React.ReactNode;
179
183
  }
180
184
  export declare const Variants: React.ForwardRefExoticComponent<ItemDetailsVariantsProps & React.RefAttributes<HTMLElement>>;
181
185
  export interface AddToCartActionProps {
@@ -3,7 +3,9 @@ import React from 'react';
3
3
  import { Commerce } from '@wix/ecom/components';
4
4
  import { AsChildSlot } from '@wix/headless-utils/react';
5
5
  import { Quantity as QuantityComponent } from '@wix/headless-components/react';
6
- import { Item, Label, Modifier, ModifierGroup, Variant, } from '@wix/restaurants/components';
6
+ import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
7
+ import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
8
+ import { Item, Modifier, ModifierGroup, useModifierContext, } from '@wix/headless-restaurants-menus/react';
7
9
  import * as CoreItemDetails from './core/ItemDetails.js';
8
10
  var TestIds;
9
11
  (function (TestIds) {
@@ -14,9 +16,10 @@ var TestIds;
14
16
  TestIds["itemAddToCart"] = "item-add-to-cart";
15
17
  TestIds["itemSpecialRequest"] = "item-special-request";
16
18
  TestIds["itemLabels"] = "item-labels";
17
- TestIds["itemModifierGroups"] = "item-modifier-groups";
18
19
  TestIds["itemVariants"] = "item-variants";
20
+ TestIds["itemModifier"] = "item-modifier";
19
21
  })(TestIds || (TestIds = {}));
22
+ const CheckIcon = () => (_jsx("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { d: "M11.4669 3.72684C11.7558 3.91574 11.8369 4.30308 11.648 4.59198L7.39799 11.092C7.29783 11.2452 7.13556 11.3467 6.95402 11.3699C6.77247 11.3931 6.58989 11.3355 6.45446 11.2124L3.70446 8.71241C3.44905 8.48022 3.43023 8.08494 3.66242 7.82953C3.89461 7.57412 4.28989 7.55529 4.5453 7.78749L6.75292 9.79441L10.6018 3.90792C10.7907 3.61902 11.178 3.53795 11.4669 3.72684Z", fill: "currentColor" }) }));
20
23
  export const Root = ({ children, itemDetailsServiceConfig }) => {
21
24
  return (_jsx(CoreItemDetails.Root, { itemDetailsServiceConfig: itemDetailsServiceConfig, children: ({ item }) => _jsx(Item.Root, { item: item, children: children }) }));
22
25
  };
@@ -24,10 +27,6 @@ export const Name = React.forwardRef(({ asChild, children, className, ...rest },
24
27
  return (_jsx(Item.Name, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.itemName, ...rest, children: children }));
25
28
  });
26
29
  Name.displayName = 'ItemDetails.Name';
27
- export const Image = React.forwardRef(({ asChild, children, className, ...rest }) => {
28
- return (_jsx(Item.Image, { asChild: asChild, className: className, "data-testid": TestIds.itemImage, ...rest, children: children }));
29
- });
30
- Image.displayName = 'ItemDetails.Image';
31
30
  export const Price = React.forwardRef(({ asChild, className, ...rest }, ref) => {
32
31
  return (_jsx(Item.Price, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.itemPrice, ...rest }));
33
32
  });
@@ -36,18 +35,59 @@ export const Description = React.forwardRef(({ asChild, className, ...rest }, re
36
35
  return (_jsx(Item.Description, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.itemDescription, ...rest }));
37
36
  });
38
37
  Description.displayName = 'ItemDetails.Description';
39
- export const Labels = React.forwardRef(({ children, className, asChild, iconClassName, nameClassName, ...rest }) => {
40
- return (_jsx(Item.LabelsRepeater, { ...rest, children: _jsxs(AsChildSlot, { asChild: asChild, testId: TestIds.itemLabels, className: className, customElement: children, children: [_jsx(Label.Icon, { className: iconClassName }), _jsx(Label.Name, { className: nameClassName })] }) }));
38
+ export const ModifierCheckbox = React.forwardRef(({ selectedModifierIds, onToggle, className, asChild, modifierNameClassName, modifierPriceClassName, children, ...rest }) => {
39
+ const { modifier } = useModifierContext();
40
+ const isSelected = selectedModifierIds.includes(modifier._id || '');
41
+ return (_jsx(AsChildSlot, { asChild: asChild, testId: TestIds.itemModifier, className: className, customElement: children, customElementProps: {
42
+ selectedModifierIds,
43
+ onToggle,
44
+ }, ...rest, children: _jsxs("div", { style: { display: 'flex', alignItems: 'center' }, children: [_jsx(CheckboxPrimitive.Root, { className: "CheckboxRoot", checked: isSelected, onCheckedChange: () => onToggle(modifier._id || ''), id: modifier._id || undefined, children: _jsx(CheckboxPrimitive.Indicator, { className: "CheckboxIndicator", children: _jsx(CheckIcon, {}) }) }), _jsxs("label", { className: "Label", htmlFor: modifier._id || undefined, children: [_jsx(Modifier.Name, { className: modifierNameClassName }), _jsx(Modifier.Price, { className: modifierPriceClassName })] })] }) }));
45
+ });
46
+ ModifierCheckbox.displayName = 'ItemDetails.ModifierCheckbox';
47
+ export const ModifierRadio = React.forwardRef(({ modifierNameClassName, modifierPriceClassName }) => {
48
+ const { modifier } = useModifierContext();
49
+ return (_jsx(RadioGroupPrimitive.Item, { className: "RadioGroupItem", value: modifier._id || '', id: modifier._id || undefined, children: _jsxs("div", { style: { display: 'flex', alignItems: 'center' }, children: [_jsx(RadioGroupPrimitive.Indicator, { className: "RadioGroupIndicator" }), _jsxs("label", { className: "Label", htmlFor: modifier._id || undefined, children: [_jsx(Modifier.Name, { className: modifierNameClassName }), _jsx(Modifier.Price, { className: modifierPriceClassName })] })] }) }));
50
+ });
51
+ ModifierRadio.displayName = 'ItemDetails.ModifierRadio';
52
+ export const ModifiersSingleSelect = React.forwardRef(({ children, className, asChild, modifierNameClassName, modifierPriceClassName, ...rest }) => {
53
+ return (_jsx(CoreItemDetails.ModifiersComponent, { singleSelect: true, children: ({ selectedModifierIds, onToggle, modifierGroup, modifiers }) => {
54
+ const selectedModifierId = selectedModifierIds.length > 0 ? selectedModifierIds[0] : '';
55
+ return (_jsx(AsChildSlot, { asChild: asChild, testId: TestIds.itemModifier, className: className, customElement: children, customElementProps: {
56
+ selectedModifierIds,
57
+ onToggle,
58
+ modifierGroup,
59
+ modifiers,
60
+ }, ...rest, children: _jsx(RadioGroupPrimitive.Root, { value: selectedModifierId, onValueChange: onToggle, children: _jsx(ModifierGroup.ModifiersRepeater, { children: _jsx(ModifierRadio, { modifierNameClassName: modifierNameClassName, modifierPriceClassName: modifierPriceClassName }) }) }) }));
61
+ } }));
41
62
  });
42
- Labels.displayName = 'ItemDetails.Labels';
43
- export const ModifierGroups = React.forwardRef(({ children, className, asChild, modifierNameClassName, modifierPriceClassName, ...rest }) => {
44
- return (_jsx(Item.ModifierGroupsRepeater, { ...rest, children: _jsx(AsChildSlot, { asChild: asChild, testId: TestIds.itemModifierGroups, className: className, customElement: children, children: _jsxs(ModifierGroup.ModifiersRepeater, { children: [_jsx(Modifier.Name, { className: modifierNameClassName }), _jsx(Modifier.Price, { className: modifierPriceClassName })] }) }) }));
63
+ ModifiersSingleSelect.displayName = 'ItemDetails.ModifiersSingleSelect';
64
+ export const ModifiersMultiSelect = React.forwardRef(({ children, className, asChild, modifierNameClassName, modifierPriceClassName, ...rest }) => {
65
+ return (_jsx(CoreItemDetails.ModifiersComponent, { singleSelect: false, children: ({ selectedModifierIds, onToggle, modifierGroup, modifiers }) => {
66
+ return (_jsx(AsChildSlot, { asChild: asChild, testId: TestIds.itemModifier, className: className, customElement: children, customElementProps: {
67
+ selectedModifierIds,
68
+ onToggle,
69
+ modifierGroup,
70
+ modifiers,
71
+ }, ...rest, children: _jsx(ModifierGroup.ModifiersRepeater, { children: _jsx(ModifierCheckbox, { selectedModifierIds: selectedModifierIds, onToggle: onToggle, modifierNameClassName: modifierNameClassName, modifierPriceClassName: modifierPriceClassName }) }) }));
72
+ } }));
45
73
  });
46
- export const Variants = React.forwardRef(({ children, className, asChild, variantNameClassName, variantPriceClassName, ...rest }) => {
47
- return (_jsx(Item.VariantsRepeater, { ...rest, children: _jsxs(AsChildSlot, { asChild: asChild, testId: TestIds.itemVariants, className: className, customElement: children, children: [_jsx(Variant.Name, { className: variantNameClassName }), _jsx(Variant.Price, { className: variantPriceClassName })] }) }));
74
+ ModifiersMultiSelect.displayName = 'ItemDetails.ModifiersMultiSelect';
75
+ export const Variants = React.forwardRef(({ children, className, asChild, variantNameClassName, variantPriceClassName, emptyState, }, ref) => {
76
+ return (_jsx(CoreItemDetails.VariantsComponent, { children: ({ variants, hasVariants, selectedVariantId, onVariantChange }) => {
77
+ if (!hasVariants) {
78
+ return emptyState || null;
79
+ }
80
+ return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, testId: TestIds.itemVariants, className: className, customElement: children, customElementProps: {
81
+ variants,
82
+ hasVariants,
83
+ selectedVariantId,
84
+ onVariantChange,
85
+ }, children: _jsx(RadioGroupPrimitive.Root, { value: selectedVariantId, onValueChange: onVariantChange, children: variants.map((variant) => (_jsx(RadioGroupPrimitive.Item, { value: variant._id ?? '', children: _jsxs("div", { children: [_jsx("div", { className: variantNameClassName, children: variant.name }), _jsx("div", { className: variantPriceClassName, children: variant.priceInfo?.formattedPrice ||
86
+ variant.priceInfo?.price ||
87
+ '' })] }) }, variant._id))) }) }));
88
+ } }));
48
89
  });
49
90
  Variants.displayName = 'ItemDetails.Variants';
50
- ModifierGroups.displayName = 'ItemDetails.ModifierGroups';
51
91
  /**
52
92
  * Add to Cart button for the menu item.
53
93
  * Triggers the action to add the selected item (and its modifiers/variants) to the cart.
@@ -1,4 +1,4 @@
1
- import { useItemContext, useMenuContext, useSectionContext, } from '@wix/restaurants/components';
1
+ import { useItemContext, useMenuContext, useSectionContext, } from '@wix/headless-restaurants-menus/react';
2
2
  import { useService } from '@wix/services-manager-react';
3
3
  import { OLOSettingsServiceDefinition } from '@wix/headless-restaurants-olo/services';
4
4
  // import { OLOSettingsServiceDefinition } from "../../services/OLOSettingsService";
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import { type LineItem } from '@wix/ecom/services';
3
3
  import { ItemServiceConfig } from '../../services/item-details-service.js';
4
+ import { EnhancedModifier, EnhancedModifierGroup, EnhancedVariant } from '@wix/headless-restaurants-menus/services';
4
5
  interface ItemDetailsRootProps {
5
6
  children: (props: {
6
7
  item: unknown;
@@ -8,20 +9,6 @@ interface ItemDetailsRootProps {
8
9
  itemDetailsServiceConfig?: ItemServiceConfig;
9
10
  }
10
11
  export declare const Root: React.FC<ItemDetailsRootProps>;
11
- interface ItemDetailsModifiersRepeaterProps {
12
- children: (props: {
13
- modifiers: [];
14
- hasModifiers: boolean;
15
- }) => React.ReactNode;
16
- }
17
- export declare const ModifiersRepeater: React.FC<ItemDetailsModifiersRepeaterProps>;
18
- interface ItemDetailsVariantsRepeaterProps {
19
- children: (props: {
20
- variants: [];
21
- hasVariants: boolean;
22
- }) => React.ReactNode;
23
- }
24
- export declare const VariantsRepeater: React.FC<ItemDetailsVariantsRepeaterProps>;
25
12
  interface ItemDetailsSpecialRequestProps {
26
13
  children: (props: {
27
14
  value: string;
@@ -47,4 +34,24 @@ interface ItemDetailsQuantityProps {
47
34
  }) => React.ReactNode;
48
35
  }
49
36
  export declare const QuantityComponent: React.FC<ItemDetailsQuantityProps>;
37
+ interface ItemDetailsVariantsProps {
38
+ children: (props: {
39
+ variants: EnhancedVariant[];
40
+ hasVariants: boolean;
41
+ selectedVariantId?: string;
42
+ onVariantChange?: (variantId: string) => void;
43
+ selectedVariant?: EnhancedVariant;
44
+ }) => React.ReactNode;
45
+ }
46
+ export declare const VariantsComponent: React.FC<ItemDetailsVariantsProps>;
47
+ interface ItemDetailsModifiersProps {
48
+ children: (props: {
49
+ selectedModifierIds: string[];
50
+ onToggle: (modifierId: string) => void;
51
+ modifierGroup: EnhancedModifierGroup;
52
+ modifiers: EnhancedModifier[];
53
+ }) => React.ReactNode;
54
+ singleSelect?: boolean;
55
+ }
56
+ export declare const ModifiersComponent: React.FC<ItemDetailsModifiersProps>;
50
57
  export {};
@@ -4,6 +4,7 @@ import { useService, WixServices } from '@wix/services-manager-react';
4
4
  import { createServicesMap } from '@wix/services-manager';
5
5
  import { ItemService, ItemServiceDefinition, loadItemServiceConfig, } from '../../services/item-details-service.js';
6
6
  import { OLOSettingsServiceDefinition } from '../../services/olo-settings-service.js';
7
+ import { useItemContext, useModifierGroupContext, } from '@wix/headless-restaurants-menus/react';
7
8
  export const Root = ({ children, itemDetailsServiceConfig, }) => {
8
9
  const service = useService(OLOSettingsServiceDefinition);
9
10
  const selectedItem = service.selectedItem?.get();
@@ -19,22 +20,6 @@ export const Root = ({ children, itemDetailsServiceConfig, }) => {
19
20
  }
20
21
  return (_jsx(WixServices, { servicesMap: createServicesMap().addService(ItemServiceDefinition, ItemService, config), children: children({ item: itemDetailsServiceConfig?.item ?? selectedItem }) }));
21
22
  };
22
- export const ModifiersRepeater = ({ children, }) => {
23
- const service = useService(ItemServiceDefinition);
24
- const item = service.item?.get();
25
- // TODO: Check if modifiers exist on item type - might be in a different property
26
- const modifiers = item?.modifiers || [];
27
- const hasModifiers = modifiers.length > 0;
28
- return children({ modifiers, hasModifiers });
29
- };
30
- export const VariantsRepeater = ({ children, }) => {
31
- const service = useService(ItemServiceDefinition);
32
- const item = service.item?.get();
33
- // TODO: Check if variants exist on item type - might be in a different property
34
- const variants = item?.variants || [];
35
- const hasVariants = variants.length > 0;
36
- return children({ variants, hasVariants });
37
- };
38
23
  export const SpecialRequest = ({ children, }) => {
39
24
  const [value, setValue] = useState('');
40
25
  const service = useService(ItemServiceDefinition);
@@ -77,3 +62,49 @@ export const QuantityComponent = ({ children, }) => {
77
62
  onValueChange,
78
63
  });
79
64
  };
65
+ export const VariantsComponent = ({ children, }) => {
66
+ const service = useService(ItemServiceDefinition);
67
+ const { item } = useItemContext();
68
+ const selectedVariant = service.selectedVariant?.get?.();
69
+ // Get variants from item context
70
+ const variants = item?.priceVariants || [];
71
+ const hasVariants = variants.length > 0;
72
+ const selectedVariantId = selectedVariant?._id ?? undefined;
73
+ const onVariantChange = (variantId) => {
74
+ const variant = variants.find((v) => v._id === variantId);
75
+ if (variant) {
76
+ service.updateSelectedVariant?.(variant);
77
+ }
78
+ };
79
+ return children({
80
+ variants,
81
+ hasVariants,
82
+ selectedVariantId,
83
+ onVariantChange,
84
+ selectedVariant,
85
+ });
86
+ };
87
+ export const ModifiersComponent = ({ children, singleSelect, }) => {
88
+ const service = useService(ItemServiceDefinition);
89
+ const { modifierGroup } = useModifierGroupContext();
90
+ const selectedModifiers = service.selectedModifiers?.get?.() ?? {};
91
+ // Get selected modifier IDs for this group
92
+ const groupId = modifierGroup._id;
93
+ const groupSelectedModifierIds = groupId
94
+ ? selectedModifiers[groupId] || []
95
+ : [];
96
+ const onToggle = (modifierId) => {
97
+ if (groupId) {
98
+ service.toggleModifier?.(groupId, modifierId, singleSelect);
99
+ }
100
+ };
101
+ return children({
102
+ selectedModifierIds: groupSelectedModifierIds,
103
+ onToggle,
104
+ modifierGroup,
105
+ modifiers: modifierGroup.modifiers.map((modifier, index) => ({
106
+ ...modifier,
107
+ _id: `${modifier._id}~${index}`,
108
+ })),
109
+ });
110
+ };
@@ -1,6 +1,8 @@
1
1
  import { type Signal } from '@wix/services-definitions/core-services/signals';
2
- import * as items from '@wix/auto_sdk_restaurants_items';
3
2
  import { type LineItem } from '@wix/ecom/services';
3
+ import { itemVariants } from '@wix/restaurants';
4
+ import type { EnhancedItem } from '@wix/headless-restaurants-menus/services';
5
+ type Variant = itemVariants.Variant;
4
6
  /**
5
7
  * API interface for the Item Detailsservice, providing reactive item data management.
6
8
  * This service handles loading and managing a single item's data, loading state, and errors.
@@ -9,10 +11,12 @@ import { type LineItem } from '@wix/ecom/services';
9
11
  */
10
12
  export interface ItemServiceAPI {
11
13
  /** Reactive signal containing the current item data */
12
- item?: Signal<items.Item | undefined>;
14
+ item?: Signal<EnhancedItem | undefined>;
13
15
  quantity: Signal<number>;
14
16
  specialRequest: Signal<string>;
15
17
  lineItem: Signal<LineItem>;
18
+ selectedVariant: Signal<Variant | undefined>;
19
+ selectedModifiers: Signal<Record<string, Array<string>>>;
16
20
  /** Reactive signal indicating if a item is currently being loaded */
17
21
  isLoading: Signal<boolean>;
18
22
  /** Reactive signal containing any error message, or null if no error */
@@ -21,6 +25,10 @@ export interface ItemServiceAPI {
21
25
  updateQuantity: (quantity: number) => void;
22
26
  /** Function to update the special request of the item */
23
27
  updateSpecialRequest: (specialRequest: string) => void;
28
+ /** Function to update the selected variant of the item */
29
+ updateSelectedVariant: (variant: Variant) => void;
30
+ /** Function to toggle a modifier instance in a specific group */
31
+ toggleModifier: (modifierGroupId: string, modifierId: string, singleSelect?: boolean) => void;
24
32
  }
25
33
  /**
26
34
  * Service definition for the Item service.
@@ -41,7 +49,7 @@ export declare const ItemServiceDefinition: string & {
41
49
  */
42
50
  export interface ItemServiceConfig {
43
51
  /** The initial item data to configure the service with */
44
- item?: items.Item;
52
+ item?: EnhancedItem;
45
53
  itemId?: string;
46
54
  operationId?: string;
47
55
  }
@@ -149,6 +157,7 @@ export interface NotFoundItemServiceConfigResult {
149
157
  * ```
150
158
  */
151
159
  export declare function loadItemServiceConfig({ item, operationId, }: {
152
- item: any;
160
+ item: EnhancedItem;
153
161
  operationId: string;
154
162
  }): ItemServiceConfig;
163
+ export {};
@@ -49,6 +49,17 @@ export const ItemService = implementService.withConfig()(ItemServiceDefinition,
49
49
  const quantity = signalsService.signal(1);
50
50
  const specialRequest = signalsService.signal('');
51
51
  const lineItem = signalsService.signal({});
52
+ const priceVariants = config.item?.priceVariants || [];
53
+ const initialVariant = priceVariants.length > 0 ? priceVariants[0] : undefined;
54
+ const selectedVariant = signalsService.signal(initialVariant);
55
+ const modifierGroups = config.item?.modifierGroups || [];
56
+ const initialSelectedModifiers = {};
57
+ modifierGroups.forEach((group) => {
58
+ if (group._id) {
59
+ initialSelectedModifiers[group._id] = [];
60
+ }
61
+ });
62
+ const selectedModifiers = signalsService.signal(initialSelectedModifiers);
52
63
  if (config.item) {
53
64
  console.log('config.item', config.item);
54
65
  lineItem.set({
@@ -89,15 +100,53 @@ export const ItemService = implementService.withConfig()(ItemServiceDefinition,
89
100
  },
90
101
  });
91
102
  };
103
+ const updateSelectedVariant = (variant) => {
104
+ selectedVariant.set(variant);
105
+ };
106
+ const toggleModifier = (modifierGroupId, modifierId, singleSelect = false) => {
107
+ const currentSelectedModifiers = selectedModifiers.get();
108
+ const groupModifierIds = currentSelectedModifiers[modifierGroupId] || [];
109
+ // Check if this modifier is already selected
110
+ const isModifierSelected = groupModifierIds.includes(modifierId);
111
+ if (singleSelect) {
112
+ // Single select behavior: select the modifier, replacing any existing selection
113
+ selectedModifiers.set({
114
+ ...currentSelectedModifiers,
115
+ [modifierGroupId]: [modifierId],
116
+ });
117
+ }
118
+ else {
119
+ // Multi-select behavior: toggle the modifier
120
+ if (isModifierSelected) {
121
+ // Remove the modifier if it exists
122
+ const updatedModifierIds = groupModifierIds.filter((id) => id !== modifierId);
123
+ selectedModifiers.set({
124
+ ...currentSelectedModifiers,
125
+ [modifierGroupId]: updatedModifierIds,
126
+ });
127
+ }
128
+ else {
129
+ // Add the modifier to the existing selection
130
+ selectedModifiers.set({
131
+ ...currentSelectedModifiers,
132
+ [modifierGroupId]: [...groupModifierIds, modifierId],
133
+ });
134
+ }
135
+ }
136
+ };
92
137
  return {
93
138
  item,
94
139
  quantity,
95
140
  updateQuantity,
96
141
  updateSpecialRequest,
142
+ updateSelectedVariant,
143
+ toggleModifier,
97
144
  isLoading,
98
145
  error,
99
146
  specialRequest,
100
147
  lineItem,
148
+ selectedVariant,
149
+ selectedModifiers,
101
150
  };
102
151
  });
103
152
  /**
@@ -3,6 +3,7 @@ import { Commerce } from '@wix/ecom/components';
3
3
  import { type LineItem } from '@wix/ecom/services';
4
4
  import { type AsChildChildren } from '@wix/headless-utils/react';
5
5
  import { ItemServiceConfig } from '../services/item-details-service.js';
6
+ import { EnhancedModifier, EnhancedModifierGroup, EnhancedVariant } from '@wix/headless-restaurants-menus/services';
6
7
  /**
7
8
  * Root component for menu item display and interaction.
8
9
  * Provides context for all menu item-related components like name, price, description, image, etc.
@@ -46,36 +47,6 @@ export interface ItemDetailsNameProps {
46
47
  className?: string;
47
48
  }
48
49
  export declare const Name: React.ForwardRefExoticComponent<ItemDetailsNameProps & React.RefAttributes<HTMLElement>>;
49
- /**
50
- * Displays the item image with customizable rendering.
51
- *
52
- * @component
53
- * @example
54
- * ```tsx
55
- * <ItemDetails.Image />
56
- * <ItemDetails.Image asChild>
57
- * <img className="rounded" />
58
- * </ItemDetails.Image>
59
- * ```
60
- */
61
- export interface ItemDetailsImageProps {
62
- asChild?: boolean;
63
- /**
64
- * Custom render function when using asChild.
65
- * Receives an object with:
66
- * - hasImage: boolean - whether the item has an image
67
- * - image: string - the actual image element (WixMediaImage)
68
- * - altText: string - the alt text for the image
69
- */
70
- children?: (props: {
71
- hasImage: boolean;
72
- image?: string;
73
- altText: string;
74
- }) => React.ReactNode;
75
- /** CSS classes to apply to the default element */
76
- className?: string;
77
- }
78
- export declare const Image: React.ForwardRefExoticComponent<ItemDetailsImageProps & React.RefAttributes<HTMLElement>>;
79
50
  /**
80
51
  * Displays the item price with customizable rendering.
81
52
  *
@@ -113,52 +84,80 @@ export interface ItemDetailsDescriptionProps {
113
84
  }
114
85
  export declare const Description: React.ForwardRefExoticComponent<ItemDetailsDescriptionProps & React.RefAttributes<HTMLElement>>;
115
86
  /**
116
- * Wrapper component for Item.LabelsRepeater.
117
- * Renders the labels for the item using the headless component.
87
+ * Wrapper component for CoreItemDetails.ModifierComponent.
88
+ * Renders a single modifier with checkbox functionality.
118
89
  *
119
90
  * @component
120
91
  * @example
121
92
  * ```tsx
122
- * <ItemDetails.Labels>
123
- * {(label) => <span>{label.name}</span>}
124
- * </ItemDetails.Labels>
93
+ * <ItemDetails.Modifier>
94
+ * {({ modifier, isSelected, onToggle }) => (
95
+ * <div style={{ display: "flex", alignItems: "center" }}>
96
+ * <CheckboxPrimitive.Root
97
+ * className="CheckboxRoot"
98
+ * checked={isSelected}
99
+ * onCheckedChange={onToggle}
100
+ * id={modifier._id}
101
+ * >
102
+ * <CheckboxPrimitive.Indicator className="CheckboxIndicator">
103
+ * <CheckIcon />
104
+ * </CheckboxPrimitive.Indicator>
105
+ * </CheckboxPrimitive.Root>
106
+ * <label className="Label" htmlFor={modifier._id}>
107
+ * {modifier.name}
108
+ * </label>
109
+ * </div>
110
+ * )}
111
+ * </ItemDetails.Modifier>
125
112
  * ```
126
113
  */
127
- export interface ItemDetailsLabelsProps {
114
+ export interface ItemDetailsModifiersSingleSelectProps {
128
115
  children?: AsChildChildren<{
129
- label: string;
116
+ selectedModifierIds: string[];
117
+ onToggle: (modifierId: string) => void;
118
+ modifierGroup: EnhancedModifierGroup;
119
+ modifiers: EnhancedModifier[];
130
120
  }>;
131
121
  className?: string;
132
122
  asChild?: boolean;
133
- iconClassName?: string;
134
- nameClassName?: string;
123
+ modifierNameClassName?: string;
124
+ modifierPriceClassName?: string;
135
125
  }
136
- export declare const Labels: React.ForwardRefExoticComponent<ItemDetailsLabelsProps & React.RefAttributes<HTMLElement>>;
137
- /**
138
- * Wrapper component for Item.ModifierGroupsRepeater.
139
- * Renders the modifier groups for the item using the headless component.
140
- *
141
- * @component
142
- * @example
143
- * ```tsx
144
- * <ItemDetails.ModifierGroups>
145
- * {(modifierGroup) => <span>{modifierGroup.name}</span>}
146
- * </ItemDetails.ModifierGroups>
147
- * ```
148
- */
149
- export interface ItemDetailsModifierGroupsProps {
126
+ export interface ModifierCheckboxProps {
127
+ selectedModifierIds: string[];
128
+ onToggle: (modifierId: string) => void;
129
+ className?: string;
130
+ asChild?: boolean;
131
+ modifierNameClassName?: string;
132
+ modifierPriceClassName?: string;
133
+ children?: AsChildChildren<{
134
+ selectedModifierIds: string[];
135
+ onToggle: (modifierId: string) => void;
136
+ }>;
137
+ }
138
+ export declare const ModifierCheckbox: React.ForwardRefExoticComponent<ModifierCheckboxProps & React.RefAttributes<HTMLElement>>;
139
+ export interface ModifierRadioProps {
140
+ modifierNameClassName?: string;
141
+ modifierPriceClassName?: string;
142
+ }
143
+ export declare const ModifierRadio: React.ForwardRefExoticComponent<ModifierRadioProps & React.RefAttributes<HTMLElement>>;
144
+ export declare const ModifiersSingleSelect: React.ForwardRefExoticComponent<ItemDetailsModifiersSingleSelectProps & React.RefAttributes<HTMLElement>>;
145
+ export interface ItemDetailsModifiersMultiSelectProps {
150
146
  children?: AsChildChildren<{
151
- modifierGroup: any;
147
+ selectedModifierIds: string[];
148
+ onToggle: (modifierId: string) => void;
149
+ modifierGroup: EnhancedModifierGroup;
150
+ modifiers: EnhancedModifier[];
152
151
  }>;
153
152
  className?: string;
154
153
  asChild?: boolean;
155
154
  modifierNameClassName?: string;
156
155
  modifierPriceClassName?: string;
157
156
  }
158
- export declare const ModifierGroups: React.ForwardRefExoticComponent<ItemDetailsModifierGroupsProps & React.RefAttributes<HTMLElement>>;
157
+ export declare const ModifiersMultiSelect: React.ForwardRefExoticComponent<ItemDetailsModifiersMultiSelectProps & React.RefAttributes<HTMLElement>>;
159
158
  /**
160
- * Wrapper component for Item.VariantsRepeater.
161
- * Renders the variants for the item using the headless component.
159
+ * Wrapper component for CoreItemDetails.VariantsComponent.
160
+ * Renders the variants for the item using Radix UI RadioGroup.
162
161
  *
163
162
  * @component
164
163
  * @example
@@ -170,12 +169,17 @@ export declare const ModifierGroups: React.ForwardRefExoticComponent<ItemDetails
170
169
  */
171
170
  export interface ItemDetailsVariantsProps {
172
171
  children?: AsChildChildren<{
173
- variant: any;
172
+ variant: EnhancedVariant;
173
+ variants: EnhancedVariant[];
174
+ hasVariants: boolean;
175
+ selectedVariantId?: string;
176
+ onVariantChange?: (variantId: string) => void;
174
177
  }>;
175
178
  className?: string;
176
179
  asChild?: boolean;
177
180
  variantNameClassName?: string;
178
181
  variantPriceClassName?: string;
182
+ emptyState?: React.ReactNode;
179
183
  }
180
184
  export declare const Variants: React.ForwardRefExoticComponent<ItemDetailsVariantsProps & React.RefAttributes<HTMLElement>>;
181
185
  export interface AddToCartActionProps {
@@ -3,7 +3,9 @@ import React from 'react';
3
3
  import { Commerce } from '@wix/ecom/components';
4
4
  import { AsChildSlot } from '@wix/headless-utils/react';
5
5
  import { Quantity as QuantityComponent } from '@wix/headless-components/react';
6
- import { Item, Label, Modifier, ModifierGroup, Variant, } from '@wix/restaurants/components';
6
+ import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
7
+ import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
8
+ import { Item, Modifier, ModifierGroup, useModifierContext, } from '@wix/headless-restaurants-menus/react';
7
9
  import * as CoreItemDetails from './core/ItemDetails.js';
8
10
  var TestIds;
9
11
  (function (TestIds) {
@@ -14,9 +16,10 @@ var TestIds;
14
16
  TestIds["itemAddToCart"] = "item-add-to-cart";
15
17
  TestIds["itemSpecialRequest"] = "item-special-request";
16
18
  TestIds["itemLabels"] = "item-labels";
17
- TestIds["itemModifierGroups"] = "item-modifier-groups";
18
19
  TestIds["itemVariants"] = "item-variants";
20
+ TestIds["itemModifier"] = "item-modifier";
19
21
  })(TestIds || (TestIds = {}));
22
+ const CheckIcon = () => (_jsx("svg", { width: "15", height: "15", viewBox: "0 0 15 15", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { d: "M11.4669 3.72684C11.7558 3.91574 11.8369 4.30308 11.648 4.59198L7.39799 11.092C7.29783 11.2452 7.13556 11.3467 6.95402 11.3699C6.77247 11.3931 6.58989 11.3355 6.45446 11.2124L3.70446 8.71241C3.44905 8.48022 3.43023 8.08494 3.66242 7.82953C3.89461 7.57412 4.28989 7.55529 4.5453 7.78749L6.75292 9.79441L10.6018 3.90792C10.7907 3.61902 11.178 3.53795 11.4669 3.72684Z", fill: "currentColor" }) }));
20
23
  export const Root = ({ children, itemDetailsServiceConfig }) => {
21
24
  return (_jsx(CoreItemDetails.Root, { itemDetailsServiceConfig: itemDetailsServiceConfig, children: ({ item }) => _jsx(Item.Root, { item: item, children: children }) }));
22
25
  };
@@ -24,10 +27,6 @@ export const Name = React.forwardRef(({ asChild, children, className, ...rest },
24
27
  return (_jsx(Item.Name, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.itemName, ...rest, children: children }));
25
28
  });
26
29
  Name.displayName = 'ItemDetails.Name';
27
- export const Image = React.forwardRef(({ asChild, children, className, ...rest }) => {
28
- return (_jsx(Item.Image, { asChild: asChild, className: className, "data-testid": TestIds.itemImage, ...rest, children: children }));
29
- });
30
- Image.displayName = 'ItemDetails.Image';
31
30
  export const Price = React.forwardRef(({ asChild, className, ...rest }, ref) => {
32
31
  return (_jsx(Item.Price, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.itemPrice, ...rest }));
33
32
  });
@@ -36,18 +35,59 @@ export const Description = React.forwardRef(({ asChild, className, ...rest }, re
36
35
  return (_jsx(Item.Description, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.itemDescription, ...rest }));
37
36
  });
38
37
  Description.displayName = 'ItemDetails.Description';
39
- export const Labels = React.forwardRef(({ children, className, asChild, iconClassName, nameClassName, ...rest }) => {
40
- return (_jsx(Item.LabelsRepeater, { ...rest, children: _jsxs(AsChildSlot, { asChild: asChild, testId: TestIds.itemLabels, className: className, customElement: children, children: [_jsx(Label.Icon, { className: iconClassName }), _jsx(Label.Name, { className: nameClassName })] }) }));
38
+ export const ModifierCheckbox = React.forwardRef(({ selectedModifierIds, onToggle, className, asChild, modifierNameClassName, modifierPriceClassName, children, ...rest }) => {
39
+ const { modifier } = useModifierContext();
40
+ const isSelected = selectedModifierIds.includes(modifier._id || '');
41
+ return (_jsx(AsChildSlot, { asChild: asChild, testId: TestIds.itemModifier, className: className, customElement: children, customElementProps: {
42
+ selectedModifierIds,
43
+ onToggle,
44
+ }, ...rest, children: _jsxs("div", { style: { display: 'flex', alignItems: 'center' }, children: [_jsx(CheckboxPrimitive.Root, { className: "CheckboxRoot", checked: isSelected, onCheckedChange: () => onToggle(modifier._id || ''), id: modifier._id || undefined, children: _jsx(CheckboxPrimitive.Indicator, { className: "CheckboxIndicator", children: _jsx(CheckIcon, {}) }) }), _jsxs("label", { className: "Label", htmlFor: modifier._id || undefined, children: [_jsx(Modifier.Name, { className: modifierNameClassName }), _jsx(Modifier.Price, { className: modifierPriceClassName })] })] }) }));
45
+ });
46
+ ModifierCheckbox.displayName = 'ItemDetails.ModifierCheckbox';
47
+ export const ModifierRadio = React.forwardRef(({ modifierNameClassName, modifierPriceClassName }) => {
48
+ const { modifier } = useModifierContext();
49
+ return (_jsx(RadioGroupPrimitive.Item, { className: "RadioGroupItem", value: modifier._id || '', id: modifier._id || undefined, children: _jsxs("div", { style: { display: 'flex', alignItems: 'center' }, children: [_jsx(RadioGroupPrimitive.Indicator, { className: "RadioGroupIndicator" }), _jsxs("label", { className: "Label", htmlFor: modifier._id || undefined, children: [_jsx(Modifier.Name, { className: modifierNameClassName }), _jsx(Modifier.Price, { className: modifierPriceClassName })] })] }) }));
50
+ });
51
+ ModifierRadio.displayName = 'ItemDetails.ModifierRadio';
52
+ export const ModifiersSingleSelect = React.forwardRef(({ children, className, asChild, modifierNameClassName, modifierPriceClassName, ...rest }) => {
53
+ return (_jsx(CoreItemDetails.ModifiersComponent, { singleSelect: true, children: ({ selectedModifierIds, onToggle, modifierGroup, modifiers }) => {
54
+ const selectedModifierId = selectedModifierIds.length > 0 ? selectedModifierIds[0] : '';
55
+ return (_jsx(AsChildSlot, { asChild: asChild, testId: TestIds.itemModifier, className: className, customElement: children, customElementProps: {
56
+ selectedModifierIds,
57
+ onToggle,
58
+ modifierGroup,
59
+ modifiers,
60
+ }, ...rest, children: _jsx(RadioGroupPrimitive.Root, { value: selectedModifierId, onValueChange: onToggle, children: _jsx(ModifierGroup.ModifiersRepeater, { children: _jsx(ModifierRadio, { modifierNameClassName: modifierNameClassName, modifierPriceClassName: modifierPriceClassName }) }) }) }));
61
+ } }));
41
62
  });
42
- Labels.displayName = 'ItemDetails.Labels';
43
- export const ModifierGroups = React.forwardRef(({ children, className, asChild, modifierNameClassName, modifierPriceClassName, ...rest }) => {
44
- return (_jsx(Item.ModifierGroupsRepeater, { ...rest, children: _jsx(AsChildSlot, { asChild: asChild, testId: TestIds.itemModifierGroups, className: className, customElement: children, children: _jsxs(ModifierGroup.ModifiersRepeater, { children: [_jsx(Modifier.Name, { className: modifierNameClassName }), _jsx(Modifier.Price, { className: modifierPriceClassName })] }) }) }));
63
+ ModifiersSingleSelect.displayName = 'ItemDetails.ModifiersSingleSelect';
64
+ export const ModifiersMultiSelect = React.forwardRef(({ children, className, asChild, modifierNameClassName, modifierPriceClassName, ...rest }) => {
65
+ return (_jsx(CoreItemDetails.ModifiersComponent, { singleSelect: false, children: ({ selectedModifierIds, onToggle, modifierGroup, modifiers }) => {
66
+ return (_jsx(AsChildSlot, { asChild: asChild, testId: TestIds.itemModifier, className: className, customElement: children, customElementProps: {
67
+ selectedModifierIds,
68
+ onToggle,
69
+ modifierGroup,
70
+ modifiers,
71
+ }, ...rest, children: _jsx(ModifierGroup.ModifiersRepeater, { children: _jsx(ModifierCheckbox, { selectedModifierIds: selectedModifierIds, onToggle: onToggle, modifierNameClassName: modifierNameClassName, modifierPriceClassName: modifierPriceClassName }) }) }));
72
+ } }));
45
73
  });
46
- export const Variants = React.forwardRef(({ children, className, asChild, variantNameClassName, variantPriceClassName, ...rest }) => {
47
- return (_jsx(Item.VariantsRepeater, { ...rest, children: _jsxs(AsChildSlot, { asChild: asChild, testId: TestIds.itemVariants, className: className, customElement: children, children: [_jsx(Variant.Name, { className: variantNameClassName }), _jsx(Variant.Price, { className: variantPriceClassName })] }) }));
74
+ ModifiersMultiSelect.displayName = 'ItemDetails.ModifiersMultiSelect';
75
+ export const Variants = React.forwardRef(({ children, className, asChild, variantNameClassName, variantPriceClassName, emptyState, }, ref) => {
76
+ return (_jsx(CoreItemDetails.VariantsComponent, { children: ({ variants, hasVariants, selectedVariantId, onVariantChange }) => {
77
+ if (!hasVariants) {
78
+ return emptyState || null;
79
+ }
80
+ return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, testId: TestIds.itemVariants, className: className, customElement: children, customElementProps: {
81
+ variants,
82
+ hasVariants,
83
+ selectedVariantId,
84
+ onVariantChange,
85
+ }, children: _jsx(RadioGroupPrimitive.Root, { value: selectedVariantId, onValueChange: onVariantChange, children: variants.map((variant) => (_jsx(RadioGroupPrimitive.Item, { value: variant._id ?? '', children: _jsxs("div", { children: [_jsx("div", { className: variantNameClassName, children: variant.name }), _jsx("div", { className: variantPriceClassName, children: variant.priceInfo?.formattedPrice ||
86
+ variant.priceInfo?.price ||
87
+ '' })] }) }, variant._id))) }) }));
88
+ } }));
48
89
  });
49
90
  Variants.displayName = 'ItemDetails.Variants';
50
- ModifierGroups.displayName = 'ItemDetails.ModifierGroups';
51
91
  /**
52
92
  * Add to Cart button for the menu item.
53
93
  * Triggers the action to add the selected item (and its modifiers/variants) to the cart.
@@ -1,4 +1,4 @@
1
- import { useItemContext, useMenuContext, useSectionContext, } from '@wix/restaurants/components';
1
+ import { useItemContext, useMenuContext, useSectionContext, } from '@wix/headless-restaurants-menus/react';
2
2
  import { useService } from '@wix/services-manager-react';
3
3
  import { OLOSettingsServiceDefinition } from '@wix/headless-restaurants-olo/services';
4
4
  // import { OLOSettingsServiceDefinition } from "../../services/OLOSettingsService";
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import { type LineItem } from '@wix/ecom/services';
3
3
  import { ItemServiceConfig } from '../../services/item-details-service.js';
4
+ import { EnhancedModifier, EnhancedModifierGroup, EnhancedVariant } from '@wix/headless-restaurants-menus/services';
4
5
  interface ItemDetailsRootProps {
5
6
  children: (props: {
6
7
  item: unknown;
@@ -8,20 +9,6 @@ interface ItemDetailsRootProps {
8
9
  itemDetailsServiceConfig?: ItemServiceConfig;
9
10
  }
10
11
  export declare const Root: React.FC<ItemDetailsRootProps>;
11
- interface ItemDetailsModifiersRepeaterProps {
12
- children: (props: {
13
- modifiers: [];
14
- hasModifiers: boolean;
15
- }) => React.ReactNode;
16
- }
17
- export declare const ModifiersRepeater: React.FC<ItemDetailsModifiersRepeaterProps>;
18
- interface ItemDetailsVariantsRepeaterProps {
19
- children: (props: {
20
- variants: [];
21
- hasVariants: boolean;
22
- }) => React.ReactNode;
23
- }
24
- export declare const VariantsRepeater: React.FC<ItemDetailsVariantsRepeaterProps>;
25
12
  interface ItemDetailsSpecialRequestProps {
26
13
  children: (props: {
27
14
  value: string;
@@ -47,4 +34,24 @@ interface ItemDetailsQuantityProps {
47
34
  }) => React.ReactNode;
48
35
  }
49
36
  export declare const QuantityComponent: React.FC<ItemDetailsQuantityProps>;
37
+ interface ItemDetailsVariantsProps {
38
+ children: (props: {
39
+ variants: EnhancedVariant[];
40
+ hasVariants: boolean;
41
+ selectedVariantId?: string;
42
+ onVariantChange?: (variantId: string) => void;
43
+ selectedVariant?: EnhancedVariant;
44
+ }) => React.ReactNode;
45
+ }
46
+ export declare const VariantsComponent: React.FC<ItemDetailsVariantsProps>;
47
+ interface ItemDetailsModifiersProps {
48
+ children: (props: {
49
+ selectedModifierIds: string[];
50
+ onToggle: (modifierId: string) => void;
51
+ modifierGroup: EnhancedModifierGroup;
52
+ modifiers: EnhancedModifier[];
53
+ }) => React.ReactNode;
54
+ singleSelect?: boolean;
55
+ }
56
+ export declare const ModifiersComponent: React.FC<ItemDetailsModifiersProps>;
50
57
  export {};
@@ -4,6 +4,7 @@ import { useService, WixServices } from '@wix/services-manager-react';
4
4
  import { createServicesMap } from '@wix/services-manager';
5
5
  import { ItemService, ItemServiceDefinition, loadItemServiceConfig, } from '../../services/item-details-service.js';
6
6
  import { OLOSettingsServiceDefinition } from '../../services/olo-settings-service.js';
7
+ import { useItemContext, useModifierGroupContext, } from '@wix/headless-restaurants-menus/react';
7
8
  export const Root = ({ children, itemDetailsServiceConfig, }) => {
8
9
  const service = useService(OLOSettingsServiceDefinition);
9
10
  const selectedItem = service.selectedItem?.get();
@@ -19,22 +20,6 @@ export const Root = ({ children, itemDetailsServiceConfig, }) => {
19
20
  }
20
21
  return (_jsx(WixServices, { servicesMap: createServicesMap().addService(ItemServiceDefinition, ItemService, config), children: children({ item: itemDetailsServiceConfig?.item ?? selectedItem }) }));
21
22
  };
22
- export const ModifiersRepeater = ({ children, }) => {
23
- const service = useService(ItemServiceDefinition);
24
- const item = service.item?.get();
25
- // TODO: Check if modifiers exist on item type - might be in a different property
26
- const modifiers = item?.modifiers || [];
27
- const hasModifiers = modifiers.length > 0;
28
- return children({ modifiers, hasModifiers });
29
- };
30
- export const VariantsRepeater = ({ children, }) => {
31
- const service = useService(ItemServiceDefinition);
32
- const item = service.item?.get();
33
- // TODO: Check if variants exist on item type - might be in a different property
34
- const variants = item?.variants || [];
35
- const hasVariants = variants.length > 0;
36
- return children({ variants, hasVariants });
37
- };
38
23
  export const SpecialRequest = ({ children, }) => {
39
24
  const [value, setValue] = useState('');
40
25
  const service = useService(ItemServiceDefinition);
@@ -77,3 +62,49 @@ export const QuantityComponent = ({ children, }) => {
77
62
  onValueChange,
78
63
  });
79
64
  };
65
+ export const VariantsComponent = ({ children, }) => {
66
+ const service = useService(ItemServiceDefinition);
67
+ const { item } = useItemContext();
68
+ const selectedVariant = service.selectedVariant?.get?.();
69
+ // Get variants from item context
70
+ const variants = item?.priceVariants || [];
71
+ const hasVariants = variants.length > 0;
72
+ const selectedVariantId = selectedVariant?._id ?? undefined;
73
+ const onVariantChange = (variantId) => {
74
+ const variant = variants.find((v) => v._id === variantId);
75
+ if (variant) {
76
+ service.updateSelectedVariant?.(variant);
77
+ }
78
+ };
79
+ return children({
80
+ variants,
81
+ hasVariants,
82
+ selectedVariantId,
83
+ onVariantChange,
84
+ selectedVariant,
85
+ });
86
+ };
87
+ export const ModifiersComponent = ({ children, singleSelect, }) => {
88
+ const service = useService(ItemServiceDefinition);
89
+ const { modifierGroup } = useModifierGroupContext();
90
+ const selectedModifiers = service.selectedModifiers?.get?.() ?? {};
91
+ // Get selected modifier IDs for this group
92
+ const groupId = modifierGroup._id;
93
+ const groupSelectedModifierIds = groupId
94
+ ? selectedModifiers[groupId] || []
95
+ : [];
96
+ const onToggle = (modifierId) => {
97
+ if (groupId) {
98
+ service.toggleModifier?.(groupId, modifierId, singleSelect);
99
+ }
100
+ };
101
+ return children({
102
+ selectedModifierIds: groupSelectedModifierIds,
103
+ onToggle,
104
+ modifierGroup,
105
+ modifiers: modifierGroup.modifiers.map((modifier, index) => ({
106
+ ...modifier,
107
+ _id: `${modifier._id}~${index}`,
108
+ })),
109
+ });
110
+ };
@@ -1,6 +1,8 @@
1
1
  import { type Signal } from '@wix/services-definitions/core-services/signals';
2
- import * as items from '@wix/auto_sdk_restaurants_items';
3
2
  import { type LineItem } from '@wix/ecom/services';
3
+ import { itemVariants } from '@wix/restaurants';
4
+ import type { EnhancedItem } from '@wix/headless-restaurants-menus/services';
5
+ type Variant = itemVariants.Variant;
4
6
  /**
5
7
  * API interface for the Item Detailsservice, providing reactive item data management.
6
8
  * This service handles loading and managing a single item's data, loading state, and errors.
@@ -9,10 +11,12 @@ import { type LineItem } from '@wix/ecom/services';
9
11
  */
10
12
  export interface ItemServiceAPI {
11
13
  /** Reactive signal containing the current item data */
12
- item?: Signal<items.Item | undefined>;
14
+ item?: Signal<EnhancedItem | undefined>;
13
15
  quantity: Signal<number>;
14
16
  specialRequest: Signal<string>;
15
17
  lineItem: Signal<LineItem>;
18
+ selectedVariant: Signal<Variant | undefined>;
19
+ selectedModifiers: Signal<Record<string, Array<string>>>;
16
20
  /** Reactive signal indicating if a item is currently being loaded */
17
21
  isLoading: Signal<boolean>;
18
22
  /** Reactive signal containing any error message, or null if no error */
@@ -21,6 +25,10 @@ export interface ItemServiceAPI {
21
25
  updateQuantity: (quantity: number) => void;
22
26
  /** Function to update the special request of the item */
23
27
  updateSpecialRequest: (specialRequest: string) => void;
28
+ /** Function to update the selected variant of the item */
29
+ updateSelectedVariant: (variant: Variant) => void;
30
+ /** Function to toggle a modifier instance in a specific group */
31
+ toggleModifier: (modifierGroupId: string, modifierId: string, singleSelect?: boolean) => void;
24
32
  }
25
33
  /**
26
34
  * Service definition for the Item service.
@@ -41,7 +49,7 @@ export declare const ItemServiceDefinition: string & {
41
49
  */
42
50
  export interface ItemServiceConfig {
43
51
  /** The initial item data to configure the service with */
44
- item?: items.Item;
52
+ item?: EnhancedItem;
45
53
  itemId?: string;
46
54
  operationId?: string;
47
55
  }
@@ -149,6 +157,7 @@ export interface NotFoundItemServiceConfigResult {
149
157
  * ```
150
158
  */
151
159
  export declare function loadItemServiceConfig({ item, operationId, }: {
152
- item: any;
160
+ item: EnhancedItem;
153
161
  operationId: string;
154
162
  }): ItemServiceConfig;
163
+ export {};
@@ -49,6 +49,17 @@ export const ItemService = implementService.withConfig()(ItemServiceDefinition,
49
49
  const quantity = signalsService.signal(1);
50
50
  const specialRequest = signalsService.signal('');
51
51
  const lineItem = signalsService.signal({});
52
+ const priceVariants = config.item?.priceVariants || [];
53
+ const initialVariant = priceVariants.length > 0 ? priceVariants[0] : undefined;
54
+ const selectedVariant = signalsService.signal(initialVariant);
55
+ const modifierGroups = config.item?.modifierGroups || [];
56
+ const initialSelectedModifiers = {};
57
+ modifierGroups.forEach((group) => {
58
+ if (group._id) {
59
+ initialSelectedModifiers[group._id] = [];
60
+ }
61
+ });
62
+ const selectedModifiers = signalsService.signal(initialSelectedModifiers);
52
63
  if (config.item) {
53
64
  console.log('config.item', config.item);
54
65
  lineItem.set({
@@ -89,15 +100,53 @@ export const ItemService = implementService.withConfig()(ItemServiceDefinition,
89
100
  },
90
101
  });
91
102
  };
103
+ const updateSelectedVariant = (variant) => {
104
+ selectedVariant.set(variant);
105
+ };
106
+ const toggleModifier = (modifierGroupId, modifierId, singleSelect = false) => {
107
+ const currentSelectedModifiers = selectedModifiers.get();
108
+ const groupModifierIds = currentSelectedModifiers[modifierGroupId] || [];
109
+ // Check if this modifier is already selected
110
+ const isModifierSelected = groupModifierIds.includes(modifierId);
111
+ if (singleSelect) {
112
+ // Single select behavior: select the modifier, replacing any existing selection
113
+ selectedModifiers.set({
114
+ ...currentSelectedModifiers,
115
+ [modifierGroupId]: [modifierId],
116
+ });
117
+ }
118
+ else {
119
+ // Multi-select behavior: toggle the modifier
120
+ if (isModifierSelected) {
121
+ // Remove the modifier if it exists
122
+ const updatedModifierIds = groupModifierIds.filter((id) => id !== modifierId);
123
+ selectedModifiers.set({
124
+ ...currentSelectedModifiers,
125
+ [modifierGroupId]: updatedModifierIds,
126
+ });
127
+ }
128
+ else {
129
+ // Add the modifier to the existing selection
130
+ selectedModifiers.set({
131
+ ...currentSelectedModifiers,
132
+ [modifierGroupId]: [...groupModifierIds, modifierId],
133
+ });
134
+ }
135
+ }
136
+ };
92
137
  return {
93
138
  item,
94
139
  quantity,
95
140
  updateQuantity,
96
141
  updateSpecialRequest,
142
+ updateSelectedVariant,
143
+ toggleModifier,
97
144
  isLoading,
98
145
  error,
99
146
  specialRequest,
100
147
  lineItem,
148
+ selectedVariant,
149
+ selectedModifiers,
101
150
  };
102
151
  });
103
152
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/headless-restaurants-olo",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "npm run build:esm && npm run build:cjs",
@@ -48,9 +48,12 @@
48
48
  "vitest": "^3.1.4"
49
49
  },
50
50
  "dependencies": {
51
+ "@radix-ui/react-checkbox": "^1.3.3",
52
+ "@radix-ui/react-radio-group": "^1.1.3",
51
53
  "@radix-ui/react-slot": "^1.1.0",
52
54
  "@wix/auto_sdk_restaurants_items": "^1.0.48",
53
55
  "@wix/headless-media": "0.0.15",
56
+ "@wix/headless-restaurants-menus": "^0.0.11",
54
57
  "@wix/headless-utils": "^0.0.4",
55
58
  "@wix/redirects": "^1.0.0",
56
59
  "@wix/restaurants": "^1.0.396",