@wix/headless-restaurants-olo 0.0.39 → 0.0.40

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.
@@ -2,7 +2,12 @@ import { defineService, implementService } from '@wix/services-definitions';
2
2
  import { SignalsServiceDefinition, } from '@wix/services-definitions/core-services/signals';
3
3
  import * as operationsSDK from '@wix/auto_sdk_restaurants_operations';
4
4
  import { DispatchType, FulfillmentTypeEnum, } from '../types/fulfillments-types.js';
5
- import { convertDateToTimezone, createTimeSlotId, getLocale, getTimezone, resolveFulfillmentInfo, } from '../utils/fulfillments-utils.js';
5
+ import {
6
+ // convertDateToTimezone,
7
+ createTimeSlotId,
8
+ // getLocale,
9
+ // getTimezone,
10
+ resolveFulfillmentInfo, } from '../utils/fulfillments-utils.js';
6
11
  import { createDateFromYMD } from '../utils/date-utils.js';
7
12
  import { FulfillmentsServiceDefinition } from './fulfillments-service.js';
8
13
  // ========================================
@@ -66,12 +71,14 @@ export const FulfillmentDetailsService = implementService.withConfig()(Fulfillme
66
71
  durationRangeOptions: selectedFulfillmentInfo?.durationRange,
67
72
  ...selectedFulfillmentInfo,
68
73
  };
69
- const timezone = getTimezone();
70
- const locale = getLocale();
74
+ // const timezone = getTimezone();
75
+ // const locale = getLocale();
71
76
  slots.push({
72
77
  id: createTimeSlotId(startTime, endTime),
73
- startTime: convertDateToTimezone(startTime, timezone, locale),
74
- endTime: convertDateToTimezone(endTime, timezone, locale),
78
+ // startTime: convertDateToTimezone(startTime, timezone, locale),
79
+ // endTime: convertDateToTimezone(endTime, timezone, locale),
80
+ startTime,
81
+ endTime,
75
82
  dispatchType: slotDispatchType,
76
83
  startsNow: orderSchedulingType === operationsSDK.OrderSchedulingType.ASAP,
77
84
  fulfillmentDetails,
@@ -240,7 +247,8 @@ export const FulfillmentDetailsService = implementService.withConfig()(Fulfillme
240
247
  const slots = processTimeSlots(timeslotsPerFulfillmentType, currentDispatchType).reverse();
241
248
  availableTimeSlotsForDate.set(slots);
242
249
  // Auto-select first available slot if none selected
243
- if (slots.length > 0 && !timeslot.get()) {
250
+ // if (slots.length > 0 && !timeslot.get()) {
251
+ if (slots.length > 0) {
244
252
  const firstAsapSlot = slots.find((s) => s.startsNow);
245
253
  setTimeslot(firstAsapSlot ?? slots[0]);
246
254
  }
@@ -82,10 +82,18 @@ export const FulfillmentsService = implementService.withConfig()(FulfillmentsSer
82
82
  const timeSlot = firstAvailableTimeSlots.get()?.find((t) => t.dispatchType === type) ??
83
83
  null;
84
84
  if (timeSlot) {
85
- const timezone = getTimezone();
86
- const locale = getLocale();
87
- timeSlot.startTime = convertDateToTimezone(timeSlot.startTime, timezone, locale);
88
- timeSlot.endTime = convertDateToTimezone(timeSlot.endTime, timezone, locale);
85
+ // const timezone = getTimezone();
86
+ // const locale = getLocale();
87
+ // timeSlot.startTime = convertDateToTimezone(
88
+ // timeSlot.startTime,
89
+ // timezone,
90
+ // locale,
91
+ // );
92
+ // timeSlot.endTime = convertDateToTimezone(
93
+ // timeSlot.endTime,
94
+ // timezone,
95
+ // locale,
96
+ // );
89
97
  setSelectedTimeSlot(timeSlot);
90
98
  }
91
99
  };
@@ -354,4 +354,91 @@ interface DeliveryAddressProps extends Omit<React.ComponentPropsWithoutRef<'div'
354
354
  * ```
355
355
  */
356
356
  export declare const DeliveryAddress: React.ForwardRefExoticComponent<DeliveryAddressProps & React.RefAttributes<HTMLDivElement>>;
357
+ export interface AddressPrediction {
358
+ /** The human-readable name of the prediction */
359
+ description?: string;
360
+ /** The id of the prediction that can be used in place API */
361
+ searchId?: string;
362
+ /** Contains the main text of a prediction, usually the name of the place */
363
+ mainText?: string;
364
+ /** Contains the secondary text of a prediction, usually the location of the place */
365
+ secondaryText?: string;
366
+ }
367
+ interface AddressPickerProps extends Omit<React.ComponentPropsWithoutRef<'div'>, 'children'> {
368
+ /** Whether to render as a child component */
369
+ asChild?: boolean;
370
+ /** Placeholder text for the address input */
371
+ placeholder?: string;
372
+ /** Optional class name for the input element */
373
+ inputClassName?: string;
374
+ /** Optional class name for the predictions container */
375
+ predictionsClassName?: string;
376
+ /** Optional class name for individual prediction items */
377
+ predictionClassName?: string;
378
+ /** Optional class name for the prediction main text */
379
+ predictionMainTextClassName?: string;
380
+ /** Optional class name for the prediction secondary text */
381
+ predictionSecondaryTextClassName?: string;
382
+ /** Optional class name for the prediction description */
383
+ predictionDescriptionClassName?: string;
384
+ /** Minimum input length before showing predictions (default: 2) */
385
+ minInputLength?: number;
386
+ /** Children render prop that receives the address picker props */
387
+ children?: AsChildChildren<{
388
+ inputValue: string;
389
+ onInputChange: (value: string) => void;
390
+ predictions: AddressPrediction[];
391
+ isLoading: boolean;
392
+ onPredictionSelect: (prediction: AddressPrediction) => Promise<void>;
393
+ isDelivery: boolean;
394
+ dispatchType: DispatchType | null;
395
+ }> | React.ReactNode;
396
+ }
397
+ export declare const AddressPicker: React.ForwardRefExoticComponent<AddressPickerProps & React.RefAttributes<HTMLDivElement>>;
398
+ interface SaveButtonProps extends Omit<React.ComponentPropsWithoutRef<'button'>, 'children'> {
399
+ /** Whether to render as a child component */
400
+ asChild?: boolean;
401
+ /** Text to display while saving */
402
+ savingText?: string;
403
+ /** Text to display when save is available */
404
+ saveText?: string;
405
+ /** Children render prop that receives the save state */
406
+ children?: AsChildChildren<{
407
+ onSave: () => void;
408
+ isSaving: boolean;
409
+ canSave: boolean;
410
+ hasChanges: boolean;
411
+ selectedTimeSlot: {
412
+ id: string;
413
+ startTime: Date;
414
+ endTime: Date;
415
+ dispatchType: DispatchType;
416
+ } | null;
417
+ dispatchType: DispatchType | null;
418
+ }> | React.ReactNode;
419
+ }
420
+ /**
421
+ * Headless component for saving fulfillment details
422
+ * Saves the selected time slot to the FulfillmentsService
423
+ *
424
+ * @example
425
+ * ```tsx
426
+ * // Default usage - renders a save button
427
+ * <FulfillmentDetails.SaveButton saveText="Confirm" savingText="Saving..." />
428
+ *
429
+ * // Using asChild pattern for custom rendering
430
+ * <FulfillmentDetails.SaveButton asChild>
431
+ * {({ onSave, isSaving, canSave, hasChanges }) => (
432
+ * <button
433
+ * onClick={onSave}
434
+ * disabled={!canSave || isSaving}
435
+ * className={hasChanges ? 'bg-primary' : 'bg-secondary'}
436
+ * >
437
+ * {isSaving ? 'Saving...' : hasChanges ? 'Save Changes' : 'Confirm'}
438
+ * </button>
439
+ * )}
440
+ * </FulfillmentDetails.SaveButton>
441
+ * ```
442
+ */
443
+ export declare const SaveButton: React.ForwardRefExoticComponent<SaveButtonProps & React.RefAttributes<HTMLButtonElement>>;
357
444
  export {};
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import React from 'react';
2
+ import React, { useMemo, useRef, useEffect } from 'react';
3
3
  import { CoreFulfillmentDetails } from './core/index.js';
4
4
  import { FulfillmentTypeEnum, } from '../types/fulfillments-types.js';
5
5
  import { AsChildSlot } from '@wix/headless-utils/react';
@@ -27,6 +27,11 @@ var TestIds;
27
27
  TestIds["fulfillmentType"] = "fulfillment-type";
28
28
  TestIds["deliveryAddress"] = "fulfillment-delivery-address";
29
29
  TestIds["deliveryAddressInput"] = "fulfillment-delivery-address-input";
30
+ TestIds["addressPicker"] = "fulfillment-address-picker";
31
+ TestIds["addressPickerInput"] = "fulfillment-address-picker-input";
32
+ TestIds["addressPickerPredictions"] = "fulfillment-address-picker-predictions";
33
+ TestIds["addressPickerPrediction"] = "fulfillment-address-picker-prediction";
34
+ TestIds["saveButton"] = "fulfillment-save-button";
30
35
  })(TestIds || (TestIds = {}));
31
36
  /**
32
37
  * Root headless component for Fulfillment Details
@@ -357,3 +362,203 @@ export const DeliveryAddress = React.forwardRef(({ children, asChild, className,
357
362
  } }));
358
363
  });
359
364
  DeliveryAddress.displayName = 'FulfillmentDetails.DeliveryAddress';
365
+ const AddressPickerInput = React.memo(({ inputValue, onInputChange, predictions, isLoading, placeholder, inputClassName, predictionsClassName, predictionClassName, predictionMainTextClassName, predictionSecondaryTextClassName, predictionDescriptionClassName, onPredictionSelect, }) => {
366
+ // Track focused prediction index for keyboard navigation
367
+ const [focusedIndex, setFocusedIndex] = React.useState(-1);
368
+ const listRef = useRef(null);
369
+ const itemRefs = useRef([]);
370
+ // Reset focused index when predictions change
371
+ useEffect(() => {
372
+ setFocusedIndex(-1);
373
+ itemRefs.current = [];
374
+ }, [predictions.length]);
375
+ // Use ref to store latest onInputChange to keep handleChange stable
376
+ const onInputChangeRef = useRef(onInputChange);
377
+ useEffect(() => {
378
+ onInputChangeRef.current = onInputChange;
379
+ }, [onInputChange]);
380
+ // Use ref to store latest onPredictionSelect
381
+ const onPredictionSelectRef = useRef(onPredictionSelect);
382
+ useEffect(() => {
383
+ onPredictionSelectRef.current = onPredictionSelect;
384
+ }, [onPredictionSelect]);
385
+ // Stable onChange handler that always calls the latest onInputChange
386
+ const handleChange = useMemo(() => (e) => {
387
+ onInputChangeRef.current(e.target.value);
388
+ // Reset focus when user types
389
+ setFocusedIndex(-1);
390
+ }, []);
391
+ // Handle keyboard navigation
392
+ const handleKeyDown = useMemo(() => (e) => {
393
+ if (predictions.length === 0)
394
+ return;
395
+ switch (e.key) {
396
+ case 'ArrowDown':
397
+ e.preventDefault();
398
+ setFocusedIndex((prev) => {
399
+ const nextIndex = prev < predictions.length - 1 ? prev + 1 : 0;
400
+ // Scroll into view
401
+ setTimeout(() => {
402
+ itemRefs.current[nextIndex]?.scrollIntoView({
403
+ block: 'nearest',
404
+ behavior: 'smooth',
405
+ });
406
+ }, 0);
407
+ return nextIndex;
408
+ });
409
+ break;
410
+ case 'ArrowUp':
411
+ e.preventDefault();
412
+ setFocusedIndex((prev) => {
413
+ const nextIndex = prev > 0 ? prev - 1 : predictions.length - 1;
414
+ // Scroll into view
415
+ setTimeout(() => {
416
+ itemRefs.current[nextIndex]?.scrollIntoView({
417
+ block: 'nearest',
418
+ behavior: 'smooth',
419
+ });
420
+ }, 0);
421
+ return nextIndex;
422
+ });
423
+ break;
424
+ case 'Enter':
425
+ if (focusedIndex >= 0 && focusedIndex < predictions.length) {
426
+ const selectedPrediction = predictions[focusedIndex];
427
+ if (selectedPrediction) {
428
+ e.preventDefault();
429
+ onPredictionSelectRef.current(selectedPrediction);
430
+ setFocusedIndex(-1);
431
+ }
432
+ }
433
+ break;
434
+ case 'Escape':
435
+ setFocusedIndex(-1);
436
+ break;
437
+ }
438
+ }, [predictions, focusedIndex]);
439
+ const inputElement = useMemo(() => (_jsx("input", { type: "text", className: inputClassName, value: inputValue, onChange: handleChange, onKeyDown: handleKeyDown, placeholder: placeholder, "data-testid": TestIds.addressPickerInput, "aria-busy": isLoading, "aria-expanded": predictions.length > 0, "aria-controls": predictions.length > 0 ? 'address-picker-predictions' : undefined, role: "combobox" })), [
440
+ inputValue,
441
+ inputClassName,
442
+ placeholder,
443
+ handleChange,
444
+ handleKeyDown,
445
+ isLoading,
446
+ predictions.length,
447
+ ]);
448
+ const predictionsList = predictions.length > 0 ? (_jsx("ul", { ref: listRef, id: "address-picker-predictions", className: predictionsClassName, "data-testid": TestIds.addressPickerPredictions, role: "listbox", children: predictions.map((prediction, index) => {
449
+ const isFocused = focusedIndex === index;
450
+ return (_jsxs("li", { ref: (el) => {
451
+ itemRefs.current[index] = el;
452
+ }, className: predictionClassName, onClick: () => {
453
+ onPredictionSelect(prediction);
454
+ setFocusedIndex(-1);
455
+ }, onMouseEnter: () => setFocusedIndex(index), onMouseLeave: () => {
456
+ // Only clear focus if it was set by mouse (not keyboard)
457
+ // Keep keyboard focus until user types or selects
458
+ }, "data-testid": TestIds.addressPickerPrediction, role: "option", "aria-selected": isFocused, "data-focused": isFocused ? 'true' : undefined, children: [prediction.mainText && (_jsx("div", { className: predictionMainTextClassName, children: prediction.mainText })), prediction.secondaryText && (_jsx("div", { className: predictionSecondaryTextClassName, children: prediction.secondaryText })), !prediction.mainText && !prediction.secondaryText && (_jsx("div", { className: predictionDescriptionClassName, children: prediction.description }))] }, prediction.searchId || index));
459
+ }) })) : null;
460
+ return (_jsxs(_Fragment, { children: [inputElement, predictionsList] }));
461
+ },
462
+ // Custom comparison function to prevent re-renders when only predictions array reference changes
463
+ // but the actual predictions content is the same
464
+ (prevProps, nextProps) => {
465
+ // Check if predictions array has actually changed (by comparing lengths and content)
466
+ const predictionsChanged = prevProps.predictions.length !== nextProps.predictions.length ||
467
+ prevProps.predictions.some((pred, index) => pred.searchId !== nextProps.predictions[index]?.searchId ||
468
+ pred.description !== nextProps.predictions[index]?.description);
469
+ return (prevProps.inputValue === nextProps.inputValue &&
470
+ prevProps.isLoading === nextProps.isLoading &&
471
+ prevProps.placeholder === nextProps.placeholder &&
472
+ prevProps.inputClassName === nextProps.inputClassName &&
473
+ prevProps.predictionsClassName === nextProps.predictionsClassName &&
474
+ prevProps.predictionClassName === nextProps.predictionClassName &&
475
+ prevProps.predictionMainTextClassName ===
476
+ nextProps.predictionMainTextClassName &&
477
+ prevProps.predictionSecondaryTextClassName ===
478
+ nextProps.predictionSecondaryTextClassName &&
479
+ prevProps.predictionDescriptionClassName ===
480
+ nextProps.predictionDescriptionClassName &&
481
+ prevProps.onInputChange === nextProps.onInputChange &&
482
+ prevProps.onPredictionSelect === nextProps.onPredictionSelect &&
483
+ !predictionsChanged);
484
+ });
485
+ export const AddressPicker = React.forwardRef(({ children, asChild, className, placeholder = 'Enter delivery address', inputClassName, predictionsClassName, predictionClassName, predictionMainTextClassName, predictionSecondaryTextClassName, predictionDescriptionClassName, minInputLength = 2, ...rest }, ref) => {
486
+ return (_jsx(CoreFulfillmentDetails.AddressPicker, { children: ({ inputValue, onInputChange, predictions, isLoading, onPredictionSelect, isDelivery, dispatchType, }) => {
487
+ // Don't render anything if not delivery
488
+ if (!isDelivery) {
489
+ return null;
490
+ }
491
+ // Memoize defaultContent to prevent recreation when only predictions change
492
+ // This ensures the input element maintains focus
493
+ // Note: predictions is passed to AddressPickerInput which handles it internally,
494
+ // so we don't need to recreate defaultContent when only predictions change
495
+ const defaultContent = useMemo(() => (_jsx("div", { ref: ref, className: className, "data-testid": TestIds.addressPicker, ...rest, children: _jsx(AddressPickerInput, { inputValue: inputValue, onInputChange: onInputChange, predictions: predictions, isLoading: isLoading, placeholder: placeholder, inputClassName: inputClassName, predictionsClassName: predictionsClassName, predictionClassName: predictionClassName, predictionMainTextClassName: predictionMainTextClassName, predictionSecondaryTextClassName: predictionSecondaryTextClassName, predictionDescriptionClassName: predictionDescriptionClassName, onPredictionSelect: onPredictionSelect }) })),
496
+ // Include predictions in deps so AddressPickerInput receives updates
497
+ // AddressPickerInput is memoized and will only re-render when necessary
498
+ [
499
+ inputValue,
500
+ onInputChange,
501
+ predictions,
502
+ isLoading,
503
+ onPredictionSelect,
504
+ placeholder,
505
+ inputClassName,
506
+ predictionsClassName,
507
+ predictionClassName,
508
+ predictionMainTextClassName,
509
+ predictionSecondaryTextClassName,
510
+ predictionDescriptionClassName,
511
+ className,
512
+ // Note: rest and ref are intentionally excluded as they should be stable
513
+ ]);
514
+ return (_jsx(AsChildSlot, { asChild: asChild, customElement: children, customElementProps: {
515
+ inputValue,
516
+ onInputChange,
517
+ predictions,
518
+ isLoading,
519
+ onPredictionSelect,
520
+ isDelivery,
521
+ dispatchType,
522
+ }, content: defaultContent, children: defaultContent }));
523
+ } }));
524
+ });
525
+ AddressPicker.displayName = 'FulfillmentDetails.AddressPicker';
526
+ /**
527
+ * Headless component for saving fulfillment details
528
+ * Saves the selected time slot to the FulfillmentsService
529
+ *
530
+ * @example
531
+ * ```tsx
532
+ * // Default usage - renders a save button
533
+ * <FulfillmentDetails.SaveButton saveText="Confirm" savingText="Saving..." />
534
+ *
535
+ * // Using asChild pattern for custom rendering
536
+ * <FulfillmentDetails.SaveButton asChild>
537
+ * {({ onSave, isSaving, canSave, hasChanges }) => (
538
+ * <button
539
+ * onClick={onSave}
540
+ * disabled={!canSave || isSaving}
541
+ * className={hasChanges ? 'bg-primary' : 'bg-secondary'}
542
+ * >
543
+ * {isSaving ? 'Saving...' : hasChanges ? 'Save Changes' : 'Confirm'}
544
+ * </button>
545
+ * )}
546
+ * </FulfillmentDetails.SaveButton>
547
+ * ```
548
+ */
549
+ export const SaveButton = React.forwardRef(({ children, asChild, className, savingText = 'Saving...', saveText = 'Save', disabled, ...rest }, ref) => {
550
+ return (_jsx(CoreFulfillmentDetails.SaveButton, { children: ({ onSave, isSaving, canSave, hasChanges, selectedTimeSlot, dispatchType, }) => {
551
+ const isDisabled = disabled || !canSave || isSaving;
552
+ const buttonText = isSaving ? savingText : saveText;
553
+ const defaultContent = (_jsx("button", { ref: ref, type: "button", className: className, onClick: onSave, disabled: isDisabled, "data-testid": TestIds.saveButton, "data-saving": isSaving, "data-can-save": canSave, "data-has-changes": hasChanges, ...rest, children: buttonText }));
554
+ return (_jsx(AsChildSlot, { asChild: asChild, customElement: children, customElementProps: {
555
+ onSave,
556
+ isSaving,
557
+ canSave,
558
+ hasChanges,
559
+ selectedTimeSlot,
560
+ dispatchType,
561
+ }, content: defaultContent, children: defaultContent }));
562
+ } }));
563
+ });
564
+ SaveButton.displayName = 'FulfillmentDetails.SaveButton';
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { type FulfillmentDetailsServiceConfig } from '../../services/fulfillment-details-service.js';
3
- import { DispatchType, FulfillmentTypeEnum } from '../../types/fulfillments-types.js';
3
+ import { DispatchType, FulfillmentTypeEnum, TimeSlot } from '../../types/fulfillments-types.js';
4
4
  export interface RootProps {
5
5
  children: React.ReactNode;
6
6
  fulfillmentDetailsServiceConfig?: FulfillmentDetailsServiceConfig;
@@ -142,3 +142,98 @@ export interface DeliveryAddressProps {
142
142
  * ```
143
143
  */
144
144
  export declare const DeliveryAddress: React.FC<DeliveryAddressProps>;
145
+ export interface AddressPrediction {
146
+ /** The human-readable name of the prediction */
147
+ description?: string;
148
+ /** The id of the prediction that can be used in place API */
149
+ searchId?: string;
150
+ /** Contains the main text of a prediction, usually the name of the place */
151
+ mainText?: string;
152
+ /** Contains the secondary text of a prediction, usually the location of the place */
153
+ secondaryText?: string;
154
+ }
155
+ export interface AddressPickerProps {
156
+ children: (props: {
157
+ /** Current input value */
158
+ inputValue: string;
159
+ /** Update the input value */
160
+ onInputChange: (value: string) => void;
161
+ /** List of address predictions */
162
+ predictions: AddressPrediction[];
163
+ /** Whether predictions are being loaded */
164
+ isLoading: boolean;
165
+ /** Select a prediction and fetch place details */
166
+ onPredictionSelect: (prediction: AddressPrediction) => Promise<void>;
167
+ /** Whether delivery is selected */
168
+ isDelivery: boolean;
169
+ /** Current dispatch type */
170
+ dispatchType: DispatchType | null;
171
+ }) => React.ReactNode;
172
+ }
173
+ /**
174
+ * Component that provides address autocomplete functionality
175
+ * Uses Atlas Autocomplete to predict addresses and Atlas Places to get place details
176
+ * Only provides functionality when dispatch type is DELIVERY
177
+ *
178
+ * @example
179
+ * ```tsx
180
+ * <CoreFulfillmentDetails.AddressPicker>
181
+ * {({ inputValue, onInputChange, predictions, onPredictionSelect, isDelivery }) => (
182
+ * isDelivery && (
183
+ * <div>
184
+ * <input
185
+ * value={inputValue}
186
+ * onChange={(e) => onInputChange(e.target.value)}
187
+ * placeholder="Enter address"
188
+ * />
189
+ * {predictions.length > 0 && (
190
+ * <ul>
191
+ * {predictions.map((prediction, index) => (
192
+ * <li
193
+ * key={prediction.searchId || index}
194
+ * onClick={() => onPredictionSelect(prediction)}
195
+ * >
196
+ * {prediction.description}
197
+ * </li>
198
+ * ))}
199
+ * </ul>
200
+ * )}
201
+ * </div>
202
+ * )
203
+ * )}
204
+ * </CoreFulfillmentDetails.AddressPicker>
205
+ * ```
206
+ */
207
+ export declare const AddressPicker: React.FC<AddressPickerProps>;
208
+ export interface SaveButtonProps {
209
+ children: (props: {
210
+ /** Save the current fulfillment details to the fulfillments service */
211
+ onSave: () => void;
212
+ /** Whether save operation is in progress */
213
+ isSaving: boolean;
214
+ /** Whether there are valid details to save */
215
+ canSave: boolean;
216
+ /** Whether the details have been modified */
217
+ hasChanges: boolean;
218
+ /** The currently selected time slot */
219
+ selectedTimeSlot: TimeSlot | null;
220
+ /** The currently selected dispatch type */
221
+ dispatchType: DispatchType | null;
222
+ }) => React.ReactNode;
223
+ }
224
+ /**
225
+ * Component that provides save functionality for fulfillment details
226
+ * Saves the selected time slot from FulfillmentDetailsService to FulfillmentsService
227
+ *
228
+ * @example
229
+ * ```tsx
230
+ * <CoreFulfillmentDetails.SaveButton>
231
+ * {({ onSave, isSaving, canSave }) => (
232
+ * <button onClick={onSave} disabled={!canSave || isSaving}>
233
+ * {isSaving ? 'Saving...' : 'Save'}
234
+ * </button>
235
+ * )}
236
+ * </CoreFulfillmentDetails.SaveButton>
237
+ * ```
238
+ */
239
+ export declare const SaveButton: React.FC<SaveButtonProps>;