@wix/headless-bookings 0.0.13 → 0.0.15
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/cjs/dist/react/core/service-list/ServiceList.d.ts +4 -0
- package/cjs/dist/react/core/service-list/ServiceList.js +4 -0
- package/cjs/dist/react/core/time-slot-list/TimeSlot.d.ts +368 -0
- package/cjs/dist/react/core/time-slot-list/TimeSlot.js +357 -0
- package/cjs/dist/react/core/time-slot-list/TimeSlotList.d.ts +84 -0
- package/cjs/dist/react/core/time-slot-list/TimeSlotList.js +68 -0
- package/cjs/dist/react/index.d.ts +1 -0
- package/cjs/dist/react/index.js +1 -0
- package/cjs/dist/react/service/Service.js +1 -11
- package/cjs/dist/react/service-list/ServiceList.d.ts +2 -0
- package/cjs/dist/react/service-list/ServiceList.js +23 -35
- package/cjs/dist/react/time-slot-list/TimeSlot.d.ts +438 -0
- package/cjs/dist/react/time-slot-list/TimeSlot.js +427 -0
- package/cjs/dist/react/time-slot-list/TimeSlotList.d.ts +209 -0
- package/cjs/dist/react/time-slot-list/TimeSlotList.js +199 -0
- package/cjs/dist/services/time-slot-list/index.d.ts +1 -1
- package/dist/react/core/service-list/ServiceList.d.ts +4 -0
- package/dist/react/core/service-list/ServiceList.js +4 -0
- package/dist/react/core/time-slot-list/TimeSlot.d.ts +368 -0
- package/dist/react/core/time-slot-list/TimeSlot.js +357 -0
- package/dist/react/core/time-slot-list/TimeSlotList.d.ts +84 -0
- package/dist/react/core/time-slot-list/TimeSlotList.js +68 -0
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.js +1 -0
- package/dist/react/service/Service.js +1 -11
- package/dist/react/service-list/ServiceList.d.ts +2 -0
- package/dist/react/service-list/ServiceList.js +23 -35
- package/dist/react/time-slot-list/TimeSlot.d.ts +438 -0
- package/dist/react/time-slot-list/TimeSlot.js +427 -0
- package/dist/react/time-slot-list/TimeSlotList.d.ts +209 -0
- package/dist/react/time-slot-list/TimeSlotList.js +199 -0
- package/dist/services/time-slot-list/index.d.ts +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Core TimeSlot Components
|
|
4
|
+
* Provides low-level access to TimeSlot data via render props
|
|
5
|
+
*
|
|
6
|
+
* Note: These components must be used within TimeSlotList.TimeSlotRepeater
|
|
7
|
+
* which provides the TimeSlotProvider context.
|
|
8
|
+
*/
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import { useTimeSlotListService } from './TimeSlotList.js';
|
|
11
|
+
const TimeSlotContext = React.createContext(null);
|
|
12
|
+
/**
|
|
13
|
+
* Hook to access TimeSlot context
|
|
14
|
+
* @throws Error if used outside of TimeSlotProvider
|
|
15
|
+
*/
|
|
16
|
+
export function useTimeSlotContext() {
|
|
17
|
+
const context = React.useContext(TimeSlotContext);
|
|
18
|
+
if (!context) {
|
|
19
|
+
throw new Error('TimeSlot components must be used within TimeSlotList.TimeSlotRepeater');
|
|
20
|
+
}
|
|
21
|
+
return context;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Provider component used by TimeSlotRepeater to provide context.
|
|
25
|
+
* Not used directly by consumers - only by TimeSlotList.TimeSlotRepeater.
|
|
26
|
+
*/
|
|
27
|
+
export function TimeSlotProvider(props) {
|
|
28
|
+
const { children, slot } = props;
|
|
29
|
+
const service = useTimeSlotListService();
|
|
30
|
+
const selection = service.selection.get();
|
|
31
|
+
const isSelected = selection?.slot?.id === slot.id;
|
|
32
|
+
const selectSlot = () => {
|
|
33
|
+
service.actions.selectSlot(slot);
|
|
34
|
+
};
|
|
35
|
+
const contextValue = {
|
|
36
|
+
slot,
|
|
37
|
+
isSelected,
|
|
38
|
+
selectSlot,
|
|
39
|
+
};
|
|
40
|
+
return (_jsx(TimeSlotContext.Provider, { value: contextValue, children: children }));
|
|
41
|
+
}
|
|
42
|
+
const LocationContext = React.createContext(null);
|
|
43
|
+
/**
|
|
44
|
+
* Hook to access Location context
|
|
45
|
+
* @throws Error if used outside of LocationProvider
|
|
46
|
+
*/
|
|
47
|
+
export function useLocationContext() {
|
|
48
|
+
const context = React.useContext(LocationContext);
|
|
49
|
+
if (!context) {
|
|
50
|
+
throw new Error('Location components must be used within LocationProvider');
|
|
51
|
+
}
|
|
52
|
+
return context;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Provider component used by LocationRepeater to provide context.
|
|
56
|
+
* Not used directly by consumers - only by TimeSlot.LocationRepeater.
|
|
57
|
+
*/
|
|
58
|
+
export function LocationProvider(props) {
|
|
59
|
+
const { children, location } = props;
|
|
60
|
+
const service = useTimeSlotListService();
|
|
61
|
+
const selectLocation = () => {
|
|
62
|
+
service.actions.selectLocation(location.id);
|
|
63
|
+
};
|
|
64
|
+
const contextValue = {
|
|
65
|
+
location,
|
|
66
|
+
selectLocation,
|
|
67
|
+
};
|
|
68
|
+
return (_jsx(LocationContext.Provider, { value: contextValue, children: children }));
|
|
69
|
+
}
|
|
70
|
+
const StaffMemberContext = React.createContext(null);
|
|
71
|
+
/**
|
|
72
|
+
* Hook to access StaffMember context
|
|
73
|
+
* @throws Error if used outside of StaffMemberProvider
|
|
74
|
+
*/
|
|
75
|
+
export function useStaffMemberContext() {
|
|
76
|
+
const context = React.useContext(StaffMemberContext);
|
|
77
|
+
if (!context) {
|
|
78
|
+
throw new Error('StaffMember components must be used within StaffMemberProvider');
|
|
79
|
+
}
|
|
80
|
+
return context;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Provider component used by StaffMemberRepeater to provide context.
|
|
84
|
+
* Not used directly by consumers - only by TimeSlot.StaffMemberRepeater.
|
|
85
|
+
*/
|
|
86
|
+
export function StaffMemberProvider(props) {
|
|
87
|
+
const { children, staffMember } = props;
|
|
88
|
+
const service = useTimeSlotListService();
|
|
89
|
+
const selectStaffMember = () => {
|
|
90
|
+
service.actions.selectStaffMember(staffMember.id);
|
|
91
|
+
};
|
|
92
|
+
const contextValue = {
|
|
93
|
+
staffMember,
|
|
94
|
+
selectStaffMember,
|
|
95
|
+
};
|
|
96
|
+
return (_jsx(StaffMemberContext.Provider, { value: contextValue, children: children }));
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Core component that provides access to time slot info via render props.
|
|
100
|
+
* Includes start/end dates, duration, bookable status, and selection state.
|
|
101
|
+
*
|
|
102
|
+
* @component
|
|
103
|
+
* @example
|
|
104
|
+
* ```tsx
|
|
105
|
+
* <TimeSlot.Info>
|
|
106
|
+
* {({ startDate, endDate, durationInMinutes, bookable, isSelected }) => (
|
|
107
|
+
* <div>
|
|
108
|
+
* <span>{startDate}</span>
|
|
109
|
+
* <span>{durationInMinutes} min</span>
|
|
110
|
+
* </div>
|
|
111
|
+
* )}
|
|
112
|
+
* </TimeSlot.Info>
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
export function Info(props) {
|
|
116
|
+
const { slot, isSelected } = useTimeSlotContext();
|
|
117
|
+
const startDate = slot.localStartDate ?? undefined;
|
|
118
|
+
const endDate = slot.localEndDate ?? undefined;
|
|
119
|
+
const bookable = slot.bookable ?? false;
|
|
120
|
+
// Calculate duration from start and end dates
|
|
121
|
+
let durationInMinutes = null;
|
|
122
|
+
if (startDate && endDate) {
|
|
123
|
+
const startTime = new Date(startDate).getTime();
|
|
124
|
+
const endTime = new Date(endDate).getTime();
|
|
125
|
+
durationInMinutes = Math.round((endTime - startTime) / (1000 * 60));
|
|
126
|
+
}
|
|
127
|
+
return props.children({
|
|
128
|
+
startDate,
|
|
129
|
+
endDate,
|
|
130
|
+
durationInMinutes,
|
|
131
|
+
bookable,
|
|
132
|
+
isSelected,
|
|
133
|
+
slot,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Core component that provides slot actions via render props.
|
|
138
|
+
*
|
|
139
|
+
* @component
|
|
140
|
+
* @example
|
|
141
|
+
* ```tsx
|
|
142
|
+
* <TimeSlot.Actions>
|
|
143
|
+
* {({ selectSlot, isSelected, bookable, clearPreferences, shouldShowClearPreferences }) => (
|
|
144
|
+
* <div>
|
|
145
|
+
* <button onClick={selectSlot} disabled={!bookable}>
|
|
146
|
+
* {isSelected ? 'Selected' : 'Select'}
|
|
147
|
+
* </button>
|
|
148
|
+
* {shouldShowClearPreferences && (
|
|
149
|
+
* <button onClick={clearPreferences}>Change</button>
|
|
150
|
+
* )}
|
|
151
|
+
* </div>
|
|
152
|
+
* )}
|
|
153
|
+
* </TimeSlot.Actions>
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
export function Actions(props) {
|
|
157
|
+
const { selectSlot, slot, isSelected } = useTimeSlotContext();
|
|
158
|
+
const service = useTimeSlotListService();
|
|
159
|
+
const selection = service.selection.get();
|
|
160
|
+
const bookable = slot.bookable ?? false;
|
|
161
|
+
// Check if at least one preference is selected
|
|
162
|
+
const hasPreferenceSelected = !!selection?.locationId || !!selection?.staffMemberId;
|
|
163
|
+
// Check if there are multiple options
|
|
164
|
+
const locationCount = slot.staffByLocation.length;
|
|
165
|
+
const staffCount = new Set(slot.staffByLocation.flatMap(({ staffMembers }) => staffMembers.map((s) => s.id))).size;
|
|
166
|
+
const hasMultipleOptions = locationCount > 1 || staffCount > 1;
|
|
167
|
+
const shouldShowClearPreferences = hasPreferenceSelected && hasMultipleOptions;
|
|
168
|
+
const clearPreferences = shouldShowClearPreferences
|
|
169
|
+
? service.actions.clearPreferences
|
|
170
|
+
: undefined;
|
|
171
|
+
return props.children({
|
|
172
|
+
selectSlot,
|
|
173
|
+
clearPreferences,
|
|
174
|
+
slot,
|
|
175
|
+
bookable,
|
|
176
|
+
isSelected,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Core component that provides access to time slot locations via render props.
|
|
181
|
+
* Extracts locations from slot.staffByLocation and enriches with name from originalSlots.
|
|
182
|
+
* Each location includes isSelected and isSelectable based on current selection state.
|
|
183
|
+
*
|
|
184
|
+
* @component
|
|
185
|
+
* @example
|
|
186
|
+
* ```tsx
|
|
187
|
+
* <CoreTimeSlot.Locations>
|
|
188
|
+
* {({ locations, locationsCount, selectLocation }) => (
|
|
189
|
+
* <GenericList.Root items={locations}>
|
|
190
|
+
* {children}
|
|
191
|
+
* </GenericList.Root>
|
|
192
|
+
* )}
|
|
193
|
+
* </CoreTimeSlot.Locations>
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
export function Locations(props) {
|
|
197
|
+
const { slot } = useTimeSlotContext();
|
|
198
|
+
const service = useTimeSlotListService();
|
|
199
|
+
const selection = service.selection.get();
|
|
200
|
+
const selectedLocationId = selection?.locationId ?? null;
|
|
201
|
+
const selectedStaffMemberId = selection?.staffMemberId ?? null;
|
|
202
|
+
// Build locations array from staffByLocation (names already included)
|
|
203
|
+
const locations = slot.staffByLocation.map(({ location, staffMembers }) => {
|
|
204
|
+
// Check if this location is selectable based on selected staff
|
|
205
|
+
// If no staff selected, all locations are selectable
|
|
206
|
+
// If staff selected, only locations where that staff works are selectable
|
|
207
|
+
const isSelectable = selectedStaffMemberId
|
|
208
|
+
? staffMembers.some((s) => s.id === selectedStaffMemberId)
|
|
209
|
+
: true;
|
|
210
|
+
return {
|
|
211
|
+
id: location.id,
|
|
212
|
+
name: location.name,
|
|
213
|
+
isSelected: selectedLocationId === location.id,
|
|
214
|
+
isSelectable,
|
|
215
|
+
};
|
|
216
|
+
});
|
|
217
|
+
return props.children({
|
|
218
|
+
locations,
|
|
219
|
+
selectLocation: service.actions.selectLocation,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Core component that provides access to location info via render props.
|
|
224
|
+
* Must be used within LocationProvider.
|
|
225
|
+
*
|
|
226
|
+
* @component
|
|
227
|
+
* @example
|
|
228
|
+
* ```tsx
|
|
229
|
+
* <LocationInfo>
|
|
230
|
+
* {({ name, isSelected, isSelectable }) => (
|
|
231
|
+
* <span>{name}</span>
|
|
232
|
+
* )}
|
|
233
|
+
* </LocationInfo>
|
|
234
|
+
* ```
|
|
235
|
+
*/
|
|
236
|
+
export function LocationInfo(props) {
|
|
237
|
+
const { location } = useLocationContext();
|
|
238
|
+
return props.children({
|
|
239
|
+
name: location.name,
|
|
240
|
+
isSelected: location.isSelected,
|
|
241
|
+
isSelectable: location.isSelectable,
|
|
242
|
+
location,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Core component that provides location actions via render props.
|
|
247
|
+
* Must be used within LocationProvider.
|
|
248
|
+
*
|
|
249
|
+
* @component
|
|
250
|
+
* @example
|
|
251
|
+
* ```tsx
|
|
252
|
+
* <LocationActions>
|
|
253
|
+
* {({ selectLocation, isSelected, isSelectable }) => (
|
|
254
|
+
* <button onClick={selectLocation} disabled={!isSelectable}>
|
|
255
|
+
* {isSelected ? 'Selected' : 'Select'}
|
|
256
|
+
* </button>
|
|
257
|
+
* )}
|
|
258
|
+
* </LocationActions>
|
|
259
|
+
* ```
|
|
260
|
+
*/
|
|
261
|
+
export function LocationActions(props) {
|
|
262
|
+
const { location, selectLocation } = useLocationContext();
|
|
263
|
+
return props.children({
|
|
264
|
+
selectLocation,
|
|
265
|
+
isSelected: location.isSelected,
|
|
266
|
+
isSelectable: location.isSelectable,
|
|
267
|
+
location,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Core component that provides access to time slot staff members via render props.
|
|
272
|
+
* Extracts unique staff members from slot.staffByLocation (names already included).
|
|
273
|
+
* Each staff member includes isSelected and isSelectable based on current selection state.
|
|
274
|
+
*
|
|
275
|
+
* @component
|
|
276
|
+
* @example
|
|
277
|
+
* ```tsx
|
|
278
|
+
* <CoreTimeSlot.StaffMembers>
|
|
279
|
+
* {({ staffMembers, selectStaffMember }) => (
|
|
280
|
+
* <GenericList.Root items={staffMembers}>
|
|
281
|
+
* {children}
|
|
282
|
+
* </GenericList.Root>
|
|
283
|
+
* )}
|
|
284
|
+
* </CoreTimeSlot.StaffMembers>
|
|
285
|
+
* ```
|
|
286
|
+
*/
|
|
287
|
+
export function StaffMembers(props) {
|
|
288
|
+
const { slot } = useTimeSlotContext();
|
|
289
|
+
const service = useTimeSlotListService();
|
|
290
|
+
const selection = service.selection.get();
|
|
291
|
+
const selectedStaffMemberId = selection?.staffMemberId ?? null;
|
|
292
|
+
const selectedLocationId = selection?.locationId ?? null;
|
|
293
|
+
// Build unique staff members array from staffByLocation
|
|
294
|
+
// Flatten all staff members with their location IDs, then dedupe by ID
|
|
295
|
+
const allStaff = slot.staffByLocation.flatMap(({ location, staffMembers }) => staffMembers.map((s) => ({ ...s, locationId: location.id })));
|
|
296
|
+
// Group by staff ID to get all locations per staff member
|
|
297
|
+
const grouped = Object.groupBy(allStaff, (s) => s.id);
|
|
298
|
+
const staffMembers = Object.entries(grouped).map(([id, entries]) => ({
|
|
299
|
+
id,
|
|
300
|
+
name: entries[0].name,
|
|
301
|
+
isSelected: selectedStaffMemberId === id,
|
|
302
|
+
isSelectable: !selectedLocationId ||
|
|
303
|
+
entries.some((e) => e.locationId === selectedLocationId),
|
|
304
|
+
}));
|
|
305
|
+
return props.children({
|
|
306
|
+
staffMembers,
|
|
307
|
+
selectStaffMember: service.actions.selectStaffMember,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Core component that provides access to staff member info via render props.
|
|
312
|
+
* Must be used within StaffMemberProvider.
|
|
313
|
+
*
|
|
314
|
+
* @component
|
|
315
|
+
* @example
|
|
316
|
+
* ```tsx
|
|
317
|
+
* <StaffMemberInfo>
|
|
318
|
+
* {({ name, isSelected, isSelectable }) => (
|
|
319
|
+
* <span>{name}</span>
|
|
320
|
+
* )}
|
|
321
|
+
* </StaffMemberInfo>
|
|
322
|
+
* ```
|
|
323
|
+
*/
|
|
324
|
+
export function StaffMemberInfo(props) {
|
|
325
|
+
const { staffMember } = useStaffMemberContext();
|
|
326
|
+
return props.children({
|
|
327
|
+
name: staffMember.name,
|
|
328
|
+
isSelected: staffMember.isSelected,
|
|
329
|
+
isSelectable: staffMember.isSelectable,
|
|
330
|
+
staffMember,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Core component that provides staff member actions via render props.
|
|
335
|
+
* Must be used within StaffMemberProvider.
|
|
336
|
+
*
|
|
337
|
+
* @component
|
|
338
|
+
* @example
|
|
339
|
+
* ```tsx
|
|
340
|
+
* <StaffMemberActions>
|
|
341
|
+
* {({ selectStaffMember, isSelected, isSelectable }) => (
|
|
342
|
+
* <button onClick={selectStaffMember} disabled={!isSelectable}>
|
|
343
|
+
* {isSelected ? 'Selected' : 'Select'}
|
|
344
|
+
* </button>
|
|
345
|
+
* )}
|
|
346
|
+
* </StaffMemberActions>
|
|
347
|
+
* ```
|
|
348
|
+
*/
|
|
349
|
+
export function StaffMemberActions(props) {
|
|
350
|
+
const { staffMember, selectStaffMember } = useStaffMemberContext();
|
|
351
|
+
return props.children({
|
|
352
|
+
selectStaffMember,
|
|
353
|
+
isSelected: staffMember.isSelected,
|
|
354
|
+
isSelectable: staffMember.isSelectable,
|
|
355
|
+
staffMember,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core TimeSlotList Components
|
|
3
|
+
* Provides low-level access to TimeSlotListService via render props
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { type TimeSlotListServiceAPI, type TimeSlotListServiceConfig, type TimeSlot } from '../../../services/time-slot-list/index.js';
|
|
7
|
+
/**
|
|
8
|
+
* Hook to access TimeSlotListService from within TimeSlotList.Root
|
|
9
|
+
*/
|
|
10
|
+
export declare const useTimeSlotListService: () => TimeSlotListServiceAPI;
|
|
11
|
+
/**
|
|
12
|
+
* Props for Root component
|
|
13
|
+
*/
|
|
14
|
+
export interface RootProps {
|
|
15
|
+
children: React.ReactNode;
|
|
16
|
+
config?: TimeSlotListServiceConfig;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Root component that provides TimeSlotListService context to its children.
|
|
20
|
+
*
|
|
21
|
+
* @component
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* <Root config={{ services, timeSlots, timezone }}>
|
|
25
|
+
* {children}
|
|
26
|
+
* </Root>
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function Root(props: RootProps): React.ReactNode;
|
|
30
|
+
/**
|
|
31
|
+
* Props for Timezone render prop component
|
|
32
|
+
*/
|
|
33
|
+
export interface TimezoneProps {
|
|
34
|
+
children: (data: {
|
|
35
|
+
timezone: string | undefined;
|
|
36
|
+
}) => React.ReactNode;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Core component that provides access to the current timezone via render props.
|
|
40
|
+
*
|
|
41
|
+
* @component
|
|
42
|
+
* @example
|
|
43
|
+
* ```tsx
|
|
44
|
+
* <Timezone>
|
|
45
|
+
* {({ timezone }) => (
|
|
46
|
+
* <span>{timezone}</span>
|
|
47
|
+
* )}
|
|
48
|
+
* </Timezone>
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export declare function Timezone(props: TimezoneProps): React.ReactNode;
|
|
52
|
+
/**
|
|
53
|
+
* Render props for ListData component
|
|
54
|
+
*/
|
|
55
|
+
export interface ListDataRenderProps {
|
|
56
|
+
timeSlots: TimeSlot[];
|
|
57
|
+
hasMore: boolean;
|
|
58
|
+
isLoading: boolean;
|
|
59
|
+
error: string | null;
|
|
60
|
+
loadMore: () => Promise<void>;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Props for ListData render prop component
|
|
64
|
+
*/
|
|
65
|
+
export interface ListDataProps {
|
|
66
|
+
children: (data: ListDataRenderProps) => React.ReactNode;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Core component that provides access to time slots list data via render props.
|
|
70
|
+
* Use this to build list containers with GenericList.
|
|
71
|
+
*
|
|
72
|
+
* @component
|
|
73
|
+
* @example
|
|
74
|
+
* ```tsx
|
|
75
|
+
* <ListData>
|
|
76
|
+
* {({ timeSlots, hasMore, isLoading, loadMore }) => (
|
|
77
|
+
* <GenericList.Root items={timeSlots} hasMore={hasMore} isLoading={isLoading} loadMore={loadMore}>
|
|
78
|
+
* {children}
|
|
79
|
+
* </GenericList.Root>
|
|
80
|
+
* )}
|
|
81
|
+
* </ListData>
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export declare function ListData(props: ListDataProps): React.ReactNode;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useService, WixServices } from '@wix/services-manager-react';
|
|
3
|
+
import { createServicesMap } from '@wix/services-manager';
|
|
4
|
+
import { TimeSlotListServiceDefinition, TimeSlotListService, } from '../../../services/time-slot-list/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* Hook to access TimeSlotListService from within TimeSlotList.Root
|
|
7
|
+
*/
|
|
8
|
+
export const useTimeSlotListService = () => {
|
|
9
|
+
return useService(TimeSlotListServiceDefinition);
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Root component that provides TimeSlotListService context to its children.
|
|
13
|
+
*
|
|
14
|
+
* @component
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* <Root config={{ services, timeSlots, timezone }}>
|
|
18
|
+
* {children}
|
|
19
|
+
* </Root>
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function Root(props) {
|
|
23
|
+
return (_jsx(WixServices, { servicesMap: createServicesMap().addService(TimeSlotListServiceDefinition, TimeSlotListService, props.config || {}), children: props.children }));
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Core component that provides access to the current timezone via render props.
|
|
27
|
+
*
|
|
28
|
+
* @component
|
|
29
|
+
* @example
|
|
30
|
+
* ```tsx
|
|
31
|
+
* <Timezone>
|
|
32
|
+
* {({ timezone }) => (
|
|
33
|
+
* <span>{timezone}</span>
|
|
34
|
+
* )}
|
|
35
|
+
* </Timezone>
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export function Timezone(props) {
|
|
39
|
+
const service = useTimeSlotListService();
|
|
40
|
+
const timezone = service.timezone.get();
|
|
41
|
+
return props.children({ timezone });
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Core component that provides access to time slots list data via render props.
|
|
45
|
+
* Use this to build list containers with GenericList.
|
|
46
|
+
*
|
|
47
|
+
* @component
|
|
48
|
+
* @example
|
|
49
|
+
* ```tsx
|
|
50
|
+
* <ListData>
|
|
51
|
+
* {({ timeSlots, hasMore, isLoading, loadMore }) => (
|
|
52
|
+
* <GenericList.Root items={timeSlots} hasMore={hasMore} isLoading={isLoading} loadMore={loadMore}>
|
|
53
|
+
* {children}
|
|
54
|
+
* </GenericList.Root>
|
|
55
|
+
* )}
|
|
56
|
+
* </ListData>
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export function ListData(props) {
|
|
60
|
+
const service = useTimeSlotListService();
|
|
61
|
+
return props.children({
|
|
62
|
+
timeSlots: service.timeSlots.get(),
|
|
63
|
+
hasMore: service.hasMore.get(),
|
|
64
|
+
isLoading: service.isLoading.get(),
|
|
65
|
+
error: service.error.get(),
|
|
66
|
+
loadMore: service.actions.loadMore,
|
|
67
|
+
});
|
|
68
|
+
}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * as Booking from './booking/Booking.js';
|
|
2
2
|
export * as Service from './service/Service.js';
|
|
3
|
+
export * as TimeSlotList from './time-slot-list/TimeSlotList.js';
|
|
3
4
|
export * as ServiceList from './service-list/ServiceList.js';
|
|
4
5
|
export * as Location from './location/Location.js';
|
package/dist/react/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * as Booking from './booking/Booking.js';
|
|
2
2
|
export * as Service from './service/Service.js';
|
|
3
|
+
export * as TimeSlotList from './time-slot-list/TimeSlotList.js';
|
|
3
4
|
export * as ServiceList from './service-list/ServiceList.js';
|
|
4
5
|
export * as Location from './location/Location.js';
|
|
@@ -535,17 +535,7 @@ export const StaffMemberRepeater = React.forwardRef((props, ref) => {
|
|
|
535
535
|
const staffMemberItemWrapper = ({ item: member, children: itemChildren, }) => {
|
|
536
536
|
return (_jsxs(React.Fragment, { children: [_jsx("span", { "data-testid": TestIds.serviceStaffMemberRepeater, children: member.name }), itemChildren] }, member.id));
|
|
537
537
|
};
|
|
538
|
-
|
|
539
|
-
if (asChild && typeof children === 'function') {
|
|
540
|
-
return (_jsx(GenericList.Repeater, { ref: ref, asChild: true, itemWrapper: staffMemberItemWrapper, children: (genericProps) => {
|
|
541
|
-
const renderProps = {
|
|
542
|
-
items: genericProps.items,
|
|
543
|
-
itemWrapper: staffMemberItemWrapper,
|
|
544
|
-
};
|
|
545
|
-
return children(renderProps);
|
|
546
|
-
} }));
|
|
547
|
-
}
|
|
548
|
-
return (_jsx(GenericList.Repeater, { ref: ref, itemWrapper: staffMemberItemWrapper, children: children }));
|
|
538
|
+
return (_jsx(GenericList.Repeater, { ref: ref, itemWrapper: staffMemberItemWrapper, asChild: asChild, children: children }));
|
|
549
539
|
});
|
|
550
540
|
StaffMemberRepeater.displayName = 'Service.StaffMembers.StaffMemberRepeater';
|
|
551
541
|
/**
|
|
@@ -15,6 +15,8 @@ export interface ServiceListRootProps {
|
|
|
15
15
|
serviceListConfig?: ServiceListServiceConfig;
|
|
16
16
|
className?: string;
|
|
17
17
|
variant?: ListVariant;
|
|
18
|
+
/** Number of items to load per page. Defaults to config limit or 100 */
|
|
19
|
+
pageSize?: number;
|
|
18
20
|
}
|
|
19
21
|
/**
|
|
20
22
|
* Root component that provides the ServiceList service context for rendering services lists.
|
|
@@ -5,8 +5,6 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
5
5
|
*/
|
|
6
6
|
import React from 'react';
|
|
7
7
|
import { GenericList, } from '@wix/headless-components/react';
|
|
8
|
-
import { useService } from '@wix/services-manager-react';
|
|
9
|
-
import { ServiceListServiceDefinition } from '../../services/service-list/service-list.js';
|
|
10
8
|
import * as CoreServiceList from '../core/service-list/ServiceList.js';
|
|
11
9
|
import * as Service from '../service/Service.js';
|
|
12
10
|
/**
|
|
@@ -44,7 +42,7 @@ var TestIds;
|
|
|
44
42
|
* ```
|
|
45
43
|
*/
|
|
46
44
|
export const Root = React.forwardRef((props, ref) => {
|
|
47
|
-
const { children, serviceListConfig, className, variant } = props;
|
|
45
|
+
const { children, serviceListConfig, className, variant, pageSize } = props;
|
|
48
46
|
const serviceConfig = serviceListConfig || {
|
|
49
47
|
services: [],
|
|
50
48
|
options: {
|
|
@@ -56,22 +54,29 @@ export const Root = React.forwardRef((props, ref) => {
|
|
|
56
54
|
},
|
|
57
55
|
},
|
|
58
56
|
};
|
|
59
|
-
|
|
57
|
+
// Calculate effective page size: prop > config limit > default
|
|
58
|
+
const effectivePageSize = pageSize ??
|
|
59
|
+
serviceListConfig?.options?.pagingMetadata?.limit ??
|
|
60
|
+
DEFAULT_PAGE_SIZE;
|
|
61
|
+
return (_jsx(CoreServiceList.Root, { serviceListConfig: serviceConfig, children: _jsx(RootContent, { className: className, ref: ref, variant: variant, pageSize: effectivePageSize, children: children }) }));
|
|
60
62
|
});
|
|
61
63
|
Root.displayName = 'ServiceList.Root';
|
|
64
|
+
/**
|
|
65
|
+
* Default page size for load more
|
|
66
|
+
*/
|
|
67
|
+
const DEFAULT_PAGE_SIZE = 100;
|
|
62
68
|
/**
|
|
63
69
|
* Internal component to handle the Root content with service access
|
|
64
70
|
*/
|
|
65
71
|
const RootContent = React.forwardRef((props, ref) => {
|
|
66
|
-
const { children, className, variant } = props;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return (_jsx(GenericList.Root, { items: items, loadMore: () => serviceListService.actions.loadMore(10), hasMore: serviceListService.hasMoreServicesSignal.get(), isLoading: serviceListService.isLoadingSignal.get(), className: className, ref: ref, "data-testid": TestIds.serviceListRoot, variant: variant, children: children }));
|
|
72
|
+
const { children, className, variant, pageSize = DEFAULT_PAGE_SIZE } = props;
|
|
73
|
+
return (_jsx(CoreServiceList.Services, { children: ({ services: servicesList, isLoading, hasMore, loadMore }) => {
|
|
74
|
+
const items = servicesList.map((service) => ({
|
|
75
|
+
...service,
|
|
76
|
+
id: service._id,
|
|
77
|
+
}));
|
|
78
|
+
return (_jsx(GenericList.Root, { items: items, loadMore: () => loadMore(pageSize), hasMore: hasMore, isLoading: isLoading, className: className, ref: ref, "data-testid": TestIds.serviceListRoot, variant: variant, children: children }));
|
|
79
|
+
} }));
|
|
75
80
|
});
|
|
76
81
|
/**
|
|
77
82
|
* Container for the services list with empty state support.
|
|
@@ -126,37 +131,20 @@ Services.displayName = 'ServiceList.Services';
|
|
|
126
131
|
*/
|
|
127
132
|
export const ServiceRepeater = React.forwardRef((props, ref) => {
|
|
128
133
|
const { children, asChild } = props;
|
|
129
|
-
const serviceListService = useService(ServiceListServiceDefinition);
|
|
130
|
-
const appId = serviceListService.queryOptionsSignal.get().appId;
|
|
131
134
|
// Check if children is a function that matches ServiceRootChildren pattern
|
|
132
135
|
// (function with single parameter that receives { selected, bookable })
|
|
133
136
|
const isServiceRootFunction = typeof children === 'function' && children.length === 1 && !asChild;
|
|
134
137
|
// Custom itemWrapper that matches ItemWrapperProps signature
|
|
135
138
|
const serviceItemWrapper = (wrapperProps) => {
|
|
136
139
|
const { item: service, children: itemChildren } = wrapperProps;
|
|
137
|
-
return (_jsx(Service.Root, { service: service,
|
|
140
|
+
return (_jsx(Service.Root, { service: service, "data-testid": TestIds.serviceListService, "data-service-id": service._id, "data-item-id": service._id, children: isServiceRootFunction
|
|
138
141
|
? children
|
|
139
142
|
: itemChildren }, service._id));
|
|
140
143
|
};
|
|
141
|
-
//
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const renderProps = {
|
|
146
|
-
items: genericProps.items,
|
|
147
|
-
variant: genericProps.variant,
|
|
148
|
-
itemWrapper: (props) => serviceItemWrapper({
|
|
149
|
-
item: props.item,
|
|
150
|
-
index: props.index ?? 0,
|
|
151
|
-
children: props.children,
|
|
152
|
-
}),
|
|
153
|
-
};
|
|
154
|
-
return children(renderProps, ref);
|
|
155
|
-
}
|
|
156
|
-
: isServiceRootFunction
|
|
157
|
-
? undefined
|
|
158
|
-
: children;
|
|
159
|
-
return (_jsx(GenericList.Repeater, { ref: ref, asChild: asChild, itemWrapper: serviceItemWrapper, children: transformedChildren }));
|
|
144
|
+
// When isServiceRootFunction is true, children is a function for Service.Root (not repeater render props)
|
|
145
|
+
// so we pass undefined and let itemWrapper handle the children
|
|
146
|
+
const repeaterChildren = isServiceRootFunction ? undefined : children;
|
|
147
|
+
return (_jsx(GenericList.Repeater, { ref: ref, asChild: asChild, itemWrapper: serviceItemWrapper, children: repeaterChildren }));
|
|
160
148
|
});
|
|
161
149
|
ServiceRepeater.displayName = 'ServiceList.ServiceRepeater';
|
|
162
150
|
/**
|