@wix/headless-bookings 0.0.45 → 0.0.47
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/__mocks__/services.d.ts +21 -0
- package/cjs/dist/__mocks__/services.js +54 -0
- package/cjs/dist/api/index.d.ts +1 -0
- package/cjs/dist/api/index.js +1 -0
- package/cjs/dist/api/query-categories/index.d.ts +34 -0
- package/cjs/dist/api/query-categories/index.js +28 -0
- package/cjs/dist/api/query-services/index.js +9 -2
- package/cjs/dist/react/core/service/Service.js +6 -0
- package/cjs/dist/react/core/service-list/ServiceListFilter.d.ts +55 -0
- package/cjs/dist/react/core/service-list/ServiceListFilter.js +59 -0
- package/cjs/dist/react/service/Service.d.ts +32 -3
- package/cjs/dist/react/service/Service.js +25 -2
- package/cjs/dist/react/service-list/ServiceList.d.ts +73 -3
- package/cjs/dist/react/service-list/ServiceList.js +63 -7
- package/cjs/dist/services/service/service.js +23 -3
- package/cjs/dist/services/service-list/service-list.d.ts +14 -1
- package/cjs/dist/services/service-list/service-list.js +94 -12
- package/dist/__mocks__/services.d.ts +21 -0
- package/dist/__mocks__/services.js +54 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -0
- package/dist/api/query-categories/index.d.ts +34 -0
- package/dist/api/query-categories/index.js +28 -0
- package/dist/api/query-services/index.js +9 -2
- package/dist/react/core/service/Service.js +6 -0
- package/dist/react/core/service-list/ServiceListFilter.d.ts +55 -0
- package/dist/react/core/service-list/ServiceListFilter.js +59 -0
- package/dist/react/service/Service.d.ts +32 -3
- package/dist/react/service/Service.js +25 -2
- package/dist/react/service-list/ServiceList.d.ts +73 -3
- package/dist/react/service-list/ServiceList.js +63 -7
- package/dist/services/service/service.js +23 -3
- package/dist/services/service-list/service-list.d.ts +14 -1
- package/dist/services/service-list/service-list.js +94 -12
- package/package.json +2 -2
|
@@ -5,6 +5,7 @@ import type { Service } from '@wix/auto_sdk_bookings_services';
|
|
|
5
5
|
import type { ServiceServiceConfig } from '../services/service/service.js';
|
|
6
6
|
import type { ServiceListServiceConfig } from '../services/service-list/service-list.js';
|
|
7
7
|
import type { PagingMetadata } from '../api/query-services/index.js';
|
|
8
|
+
import type { Category } from '../api/query-categories/index.js';
|
|
8
9
|
/**
|
|
9
10
|
* Booking app ID constant
|
|
10
11
|
*/
|
|
@@ -73,3 +74,23 @@ export declare const mockServiceConfig: ServiceServiceConfig;
|
|
|
73
74
|
* Helper to create a large list of services for pagination tests
|
|
74
75
|
*/
|
|
75
76
|
export declare function createLargeServiceList(count: number): Service[];
|
|
77
|
+
/**
|
|
78
|
+
* Mock categories for filter tests
|
|
79
|
+
*/
|
|
80
|
+
export declare const mockCategories: Category[];
|
|
81
|
+
/**
|
|
82
|
+
* Empty categories array
|
|
83
|
+
*/
|
|
84
|
+
export declare const mockEmptyCategories: Category[];
|
|
85
|
+
/**
|
|
86
|
+
* Service list config with categories for filter testing
|
|
87
|
+
*/
|
|
88
|
+
export declare const mockServiceListConfigWithCategories: ServiceListServiceConfig;
|
|
89
|
+
/**
|
|
90
|
+
* Service list config with categories and hasNext for pagination + filter testing
|
|
91
|
+
*/
|
|
92
|
+
export declare const mockServiceListConfigWithCategoriesAndMore: ServiceListServiceConfig;
|
|
93
|
+
/**
|
|
94
|
+
* Empty service list config with categories
|
|
95
|
+
*/
|
|
96
|
+
export declare const mockEmptyServiceListConfigWithCategories: ServiceListServiceConfig;
|
|
@@ -306,3 +306,57 @@ export function createLargeServiceList(count) {
|
|
|
306
306
|
},
|
|
307
307
|
}));
|
|
308
308
|
}
|
|
309
|
+
/**
|
|
310
|
+
* Mock categories for filter tests
|
|
311
|
+
*/
|
|
312
|
+
export const mockCategories = [
|
|
313
|
+
{
|
|
314
|
+
_id: 'category-1',
|
|
315
|
+
name: 'Wellness',
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
_id: 'category-2',
|
|
319
|
+
name: 'Fitness',
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
_id: 'category-3',
|
|
323
|
+
name: 'Beauty',
|
|
324
|
+
},
|
|
325
|
+
];
|
|
326
|
+
/**
|
|
327
|
+
* Empty categories array
|
|
328
|
+
*/
|
|
329
|
+
export const mockEmptyCategories = [];
|
|
330
|
+
/**
|
|
331
|
+
* Service list config with categories for filter testing
|
|
332
|
+
*/
|
|
333
|
+
export const mockServiceListConfigWithCategories = {
|
|
334
|
+
services: mockServices,
|
|
335
|
+
categories: mockCategories,
|
|
336
|
+
options: {
|
|
337
|
+
appId: BOOKING_APP_ID,
|
|
338
|
+
pagingMetadata: mockPagingMetadata,
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
/**
|
|
342
|
+
* Service list config with categories and hasNext for pagination + filter testing
|
|
343
|
+
*/
|
|
344
|
+
export const mockServiceListConfigWithCategoriesAndMore = {
|
|
345
|
+
services: mockServices,
|
|
346
|
+
categories: mockCategories,
|
|
347
|
+
options: {
|
|
348
|
+
appId: BOOKING_APP_ID,
|
|
349
|
+
pagingMetadata: mockPagingMetadataWithMore,
|
|
350
|
+
},
|
|
351
|
+
};
|
|
352
|
+
/**
|
|
353
|
+
* Empty service list config with categories
|
|
354
|
+
*/
|
|
355
|
+
export const mockEmptyServiceListConfigWithCategories = {
|
|
356
|
+
services: [],
|
|
357
|
+
categories: mockCategories,
|
|
358
|
+
options: {
|
|
359
|
+
appId: BOOKING_APP_ID,
|
|
360
|
+
pagingMetadata: mockPagingMetadataEmpty,
|
|
361
|
+
},
|
|
362
|
+
};
|
package/cjs/dist/api/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export { fetchAvailability } from './fetch-availability/index.js';
|
|
|
9
9
|
export { createBooking, type CreateBookingOptions, type Booking, type CreateBookingRequest, type CreateBookingResponse, type ParticipantNotification, type ContactDetails, } from './create-booking/index.js';
|
|
10
10
|
export { createCheckout, ChannelType, type CreateCheckoutRequest, type CreateCheckoutResponse, } from './create-checkout/index.js';
|
|
11
11
|
export { queryLocations, type QueryLocationsResult, type LocationData, } from './query-locations/index.js';
|
|
12
|
+
export { queryCategories, type QueryCategoriesResult, type Category, } from './query-categories/index.js';
|
|
12
13
|
/**
|
|
13
14
|
* PagingMetadata includes:
|
|
14
15
|
* - limit: Number of items per page
|
package/cjs/dist/api/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export { fetchAvailability } from './fetch-availability/index.js';
|
|
|
9
9
|
export { createBooking, } from './create-booking/index.js';
|
|
10
10
|
export { createCheckout, ChannelType, } from './create-checkout/index.js';
|
|
11
11
|
export { queryLocations, } from './query-locations/index.js';
|
|
12
|
+
export { queryCategories, } from './query-categories/index.js';
|
|
12
13
|
/**
|
|
13
14
|
* PagingMetadata includes:
|
|
14
15
|
* - limit: Number of items per page
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query Categories API
|
|
3
|
+
* Fetches booking service categories from Wix Bookings
|
|
4
|
+
*
|
|
5
|
+
* @module API/QueryCategories
|
|
6
|
+
*/
|
|
7
|
+
import { categoriesV2 } from '@wix/bookings';
|
|
8
|
+
/**
|
|
9
|
+
* Category type from the Wix Bookings Categories V2 API
|
|
10
|
+
*/
|
|
11
|
+
export type Category = categoriesV2.Category;
|
|
12
|
+
/**
|
|
13
|
+
* Result from querying categories
|
|
14
|
+
*/
|
|
15
|
+
export interface QueryCategoriesResult {
|
|
16
|
+
/** Array of booking service categories */
|
|
17
|
+
categories: Category[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Query all booking service categories.
|
|
21
|
+
* Returns an array of categories sorted by creation date (ascending).
|
|
22
|
+
*
|
|
23
|
+
* @returns Promise resolving to categories array
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* const { categories } = await queryCategories();
|
|
28
|
+
*
|
|
29
|
+
* categories.forEach(category => {
|
|
30
|
+
* console.log(category.name, category._id);
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare function queryCategories(): Promise<QueryCategoriesResult>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query Categories API
|
|
3
|
+
* Fetches booking service categories from Wix Bookings
|
|
4
|
+
*
|
|
5
|
+
* @module API/QueryCategories
|
|
6
|
+
*/
|
|
7
|
+
import { categoriesV2 } from '@wix/bookings';
|
|
8
|
+
/**
|
|
9
|
+
* Query all booking service categories.
|
|
10
|
+
* Returns an array of categories sorted by creation date (ascending).
|
|
11
|
+
*
|
|
12
|
+
* @returns Promise resolving to categories array
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* const { categories } = await queryCategories();
|
|
17
|
+
*
|
|
18
|
+
* categories.forEach(category => {
|
|
19
|
+
* console.log(category.name, category._id);
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export async function queryCategories() {
|
|
24
|
+
const result = await categoriesV2.queryCategories().find();
|
|
25
|
+
return {
|
|
26
|
+
categories: result.items || [],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -74,8 +74,15 @@ const buildServicesQueryRequest = ({ appId, filters, paging, sort, }) => {
|
|
|
74
74
|
if (filters?.serviceIds && filters.serviceIds.length > 0) {
|
|
75
75
|
filter['_id'] = { $in: filters.serviceIds };
|
|
76
76
|
}
|
|
77
|
-
// Add
|
|
78
|
-
|
|
77
|
+
// Add categoryIds filter if provided
|
|
78
|
+
if (filters?.categoryIds && filters.categoryIds.length > 0) {
|
|
79
|
+
filter['category.id'] = { $in: filters.categoryIds };
|
|
80
|
+
}
|
|
81
|
+
// Add locationIds filter if provided
|
|
82
|
+
if (filters?.locationIds && filters.locationIds.length > 0) {
|
|
83
|
+
filter['locations.business.id'] = { $hasSome: filters.locationIds };
|
|
84
|
+
}
|
|
85
|
+
// staffMemberIds to be added later
|
|
79
86
|
const query = {
|
|
80
87
|
filter: Object.keys(filter).length > 0 ? filter : undefined,
|
|
81
88
|
paging: paging || {
|
|
@@ -15,8 +15,14 @@ import { ServiceServiceDefinition, ServiceService, } from '../../../services/ser
|
|
|
15
15
|
*/
|
|
16
16
|
function RootContent({ children, }) {
|
|
17
17
|
const serviceService = useService(ServiceServiceDefinition);
|
|
18
|
+
const service = serviceService.serviceSignal.get();
|
|
18
19
|
const selected = serviceService.selectedSignal.get();
|
|
19
20
|
const bookable = serviceService.bookableSignal.get();
|
|
21
|
+
// In "from booking signal" mode, render null when no service is selected
|
|
22
|
+
// The signal will trigger a re-render when a service is selected
|
|
23
|
+
if (!service || !service._id) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
20
26
|
if (typeof children === 'function') {
|
|
21
27
|
return children({ selected, bookable });
|
|
22
28
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core ServiceListFilter Components
|
|
3
|
+
* Provides render props for filter data from ServiceListService and BookingService
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import type { FilterOption } from '@wix/headless-components/react';
|
|
7
|
+
import { type ServiceListFilters } from '../../../services/service-list/service-list.js';
|
|
8
|
+
/**
|
|
9
|
+
* Render props for the Root component
|
|
10
|
+
*/
|
|
11
|
+
export interface RootRenderProps {
|
|
12
|
+
/** Filter options configuration derived from categories */
|
|
13
|
+
filterOptions: FilterOption[];
|
|
14
|
+
/** Current filter state */
|
|
15
|
+
currentFilter: ServiceListFilters | null;
|
|
16
|
+
/** Function to update filter (includes location from BookingService) */
|
|
17
|
+
updateFilter: (filter: ServiceListFilters) => void;
|
|
18
|
+
/** Function to clear all filters */
|
|
19
|
+
clearFilters: () => void;
|
|
20
|
+
/** Whether any filters are active */
|
|
21
|
+
isFiltered: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Props for the Root component
|
|
25
|
+
*/
|
|
26
|
+
export interface RootProps {
|
|
27
|
+
/** Render function that receives filter data */
|
|
28
|
+
children: (props: RootRenderProps) => React.ReactNode;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Core render props component that provides filter data from services.
|
|
32
|
+
* Reads categories from ServiceListService and location from BookingService.
|
|
33
|
+
*
|
|
34
|
+
* @component
|
|
35
|
+
* @example
|
|
36
|
+
* ```tsx
|
|
37
|
+
* <ServiceListFilter.Root>
|
|
38
|
+
* {({ filterOptions, currentFilter, updateFilter, clearFilters, isFiltered }) => (
|
|
39
|
+
* <Filter.Root
|
|
40
|
+
* value={currentFilter}
|
|
41
|
+
* onChange={updateFilter}
|
|
42
|
+
* filterOptions={filterOptions}
|
|
43
|
+
* >
|
|
44
|
+
* <Filter.FilterOptions>
|
|
45
|
+
* <Filter.FilterOptionRepeater>
|
|
46
|
+
* <Filter.FilterOption.Label />
|
|
47
|
+
* <Filter.FilterOption.SingleFilter />
|
|
48
|
+
* </Filter.FilterOptionRepeater>
|
|
49
|
+
* </Filter.FilterOptions>
|
|
50
|
+
* </Filter.Root>
|
|
51
|
+
* )}
|
|
52
|
+
* </ServiceListFilter.Root>
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export declare function Root(props: RootProps): React.ReactNode;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core ServiceListFilter Components
|
|
3
|
+
* Provides render props for filter data from ServiceListService and BookingService
|
|
4
|
+
*/
|
|
5
|
+
import { useService } from '@wix/services-manager-react';
|
|
6
|
+
import { ServiceListServiceDefinition, } from '../../../services/service-list/service-list.js';
|
|
7
|
+
import { BookingServiceDefinition } from '../../../services/booking/booking.js';
|
|
8
|
+
/**
|
|
9
|
+
* Core render props component that provides filter data from services.
|
|
10
|
+
* Reads categories from ServiceListService and location from BookingService.
|
|
11
|
+
*
|
|
12
|
+
* @component
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* <ServiceListFilter.Root>
|
|
16
|
+
* {({ filterOptions, currentFilter, updateFilter, clearFilters, isFiltered }) => (
|
|
17
|
+
* <Filter.Root
|
|
18
|
+
* value={currentFilter}
|
|
19
|
+
* onChange={updateFilter}
|
|
20
|
+
* filterOptions={filterOptions}
|
|
21
|
+
* >
|
|
22
|
+
* <Filter.FilterOptions>
|
|
23
|
+
* <Filter.FilterOptionRepeater>
|
|
24
|
+
* <Filter.FilterOption.Label />
|
|
25
|
+
* <Filter.FilterOption.SingleFilter />
|
|
26
|
+
* </Filter.FilterOptionRepeater>
|
|
27
|
+
* </Filter.FilterOptions>
|
|
28
|
+
* </Filter.Root>
|
|
29
|
+
* )}
|
|
30
|
+
* </ServiceListFilter.Root>
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function Root(props) {
|
|
34
|
+
const serviceListService = useService(ServiceListServiceDefinition);
|
|
35
|
+
const bookingService = useService(BookingServiceDefinition);
|
|
36
|
+
// Get reactive data from signals
|
|
37
|
+
const filterOptions = serviceListService.filterOptionsSignal.get();
|
|
38
|
+
const currentFilter = serviceListService.filterSignal.get();
|
|
39
|
+
const isFiltered = serviceListService.isFilteredSignal.get();
|
|
40
|
+
const selectedLocation = bookingService.location.get();
|
|
41
|
+
const updateFilter = (newFilter) => {
|
|
42
|
+
// Include locationId from BookingService in the filter
|
|
43
|
+
const locationId = selectedLocation?.business?._id;
|
|
44
|
+
serviceListService.actions.setFilter({
|
|
45
|
+
...newFilter,
|
|
46
|
+
...(locationId ? { locationIds: [locationId] } : {}),
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
const clearFilters = () => {
|
|
50
|
+
serviceListService.actions.resetFilter();
|
|
51
|
+
};
|
|
52
|
+
return props.children({
|
|
53
|
+
filterOptions,
|
|
54
|
+
currentFilter,
|
|
55
|
+
updateFilter,
|
|
56
|
+
clearFilters,
|
|
57
|
+
isFiltered,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
@@ -43,14 +43,32 @@ type ServiceRootWithSlug = ServiceRootBaseProps & {
|
|
|
43
43
|
serviceId?: never;
|
|
44
44
|
serviceSlug: string;
|
|
45
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* Props for Service.Root that reads from BookingService's selected service.
|
|
48
|
+
* When used without service config, the component reads from the booking signal.
|
|
49
|
+
* Requires BookingService to be available in the context.
|
|
50
|
+
*/
|
|
51
|
+
type ServiceRootFromBooking = ServiceRootBaseProps & {
|
|
52
|
+
service?: never;
|
|
53
|
+
serviceId?: never;
|
|
54
|
+
serviceSlug?: never;
|
|
55
|
+
};
|
|
46
56
|
/**
|
|
47
57
|
* Props for Service.Root component.
|
|
48
|
-
*
|
|
58
|
+
* Accepts one of:
|
|
59
|
+
* - service: Pre-loaded service object (SSR pattern)
|
|
60
|
+
* - serviceId: Service ID for dynamic loading
|
|
61
|
+
* - serviceSlug: Service slug for dynamic loading
|
|
62
|
+
* - No service config: Reads from BookingService's selected service
|
|
49
63
|
*/
|
|
50
|
-
export type ServiceRootProps = ServiceRootWithService | ServiceRootWithId | ServiceRootWithSlug;
|
|
64
|
+
export type ServiceRootProps = ServiceRootWithService | ServiceRootWithId | ServiceRootWithSlug | ServiceRootFromBooking;
|
|
51
65
|
/**
|
|
52
66
|
* Root component that provides service context to the entire app.
|
|
53
|
-
*
|
|
67
|
+
* Can be used with:
|
|
68
|
+
* - service: Pre-loaded service object (SSR pattern)
|
|
69
|
+
* - serviceId: Service ID for dynamic loading
|
|
70
|
+
* - serviceSlug: Service slug for dynamic loading
|
|
71
|
+
* - No config: Reads from BookingService's selected service (requires BookingService)
|
|
54
72
|
*
|
|
55
73
|
* @order 1
|
|
56
74
|
* @component
|
|
@@ -87,6 +105,17 @@ export type ServiceRootProps = ServiceRootWithService | ServiceRootWithId | Serv
|
|
|
87
105
|
* </Service.Root>
|
|
88
106
|
* );
|
|
89
107
|
* }
|
|
108
|
+
*
|
|
109
|
+
* // Reading from BookingService's selected service
|
|
110
|
+
* // (requires Booking.Root parent context)
|
|
111
|
+
* function SelectedServiceDisplay() {
|
|
112
|
+
* return (
|
|
113
|
+
* <Service.Root>
|
|
114
|
+
* <Service.Name />
|
|
115
|
+
* <Service.Price />
|
|
116
|
+
* </Service.Root>
|
|
117
|
+
* );
|
|
118
|
+
* }
|
|
90
119
|
* ```
|
|
91
120
|
*/
|
|
92
121
|
export declare const Root: React.ForwardRefExoticComponent<ServiceRootProps & React.RefAttributes<HTMLElement>>;
|
|
@@ -49,7 +49,11 @@ var TestIds;
|
|
|
49
49
|
})(TestIds || (TestIds = {}));
|
|
50
50
|
/**
|
|
51
51
|
* Root component that provides service context to the entire app.
|
|
52
|
-
*
|
|
52
|
+
* Can be used with:
|
|
53
|
+
* - service: Pre-loaded service object (SSR pattern)
|
|
54
|
+
* - serviceId: Service ID for dynamic loading
|
|
55
|
+
* - serviceSlug: Service slug for dynamic loading
|
|
56
|
+
* - No config: Reads from BookingService's selected service (requires BookingService)
|
|
53
57
|
*
|
|
54
58
|
* @order 1
|
|
55
59
|
* @component
|
|
@@ -86,11 +90,30 @@ var TestIds;
|
|
|
86
90
|
* </Service.Root>
|
|
87
91
|
* );
|
|
88
92
|
* }
|
|
93
|
+
*
|
|
94
|
+
* // Reading from BookingService's selected service
|
|
95
|
+
* // (requires Booking.Root parent context)
|
|
96
|
+
* function SelectedServiceDisplay() {
|
|
97
|
+
* return (
|
|
98
|
+
* <Service.Root>
|
|
99
|
+
* <Service.Name />
|
|
100
|
+
* <Service.Price />
|
|
101
|
+
* </Service.Root>
|
|
102
|
+
* );
|
|
103
|
+
* }
|
|
89
104
|
* ```
|
|
90
105
|
*/
|
|
91
106
|
export const Root = React.forwardRef((props, ref) => {
|
|
92
107
|
const { children, service, serviceId, serviceSlug, appId, asChild, className, ...attrs } = props;
|
|
93
|
-
|
|
108
|
+
// Build config - when no service config is provided, pass empty object
|
|
109
|
+
// to trigger "from booking signal" mode in ServiceService
|
|
110
|
+
const serviceServiceConfig = {
|
|
111
|
+
service,
|
|
112
|
+
serviceId,
|
|
113
|
+
serviceSlug,
|
|
114
|
+
appId,
|
|
115
|
+
};
|
|
116
|
+
return (_jsx(CoreService.Root, { serviceServiceConfig: serviceServiceConfig, children: ({ selected, bookable }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.serviceRoot, "data-selected": selected, "data-bookable": bookable, customElement: children, customElementProps: { selected, bookable }, ...attrs, children: children })) }));
|
|
94
117
|
});
|
|
95
118
|
Root.displayName = 'Service.Root';
|
|
96
119
|
/**
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Provides components for displaying services list with pagination
|
|
4
4
|
*/
|
|
5
5
|
import React from 'react';
|
|
6
|
-
import { ListVariant, GenericListLoadMoreRenderProps } from '@wix/headless-components/react';
|
|
6
|
+
import { ListVariant, GenericListLoadMoreRenderProps, Filter as FilterPrimitive } from '@wix/headless-components/react';
|
|
7
7
|
import { AsChildChildren } from '@wix/headless-utils/react';
|
|
8
8
|
import type { ServiceListServiceConfig } from '../../services/service-list/service-list.js';
|
|
9
9
|
import type { Service as ServiceType } from '@wix/auto_sdk_bookings_services';
|
|
@@ -47,19 +47,28 @@ export declare const Root: React.ForwardRefExoticComponent<ServiceListRootProps
|
|
|
47
47
|
*/
|
|
48
48
|
export interface ServicesProps {
|
|
49
49
|
children: React.ReactNode;
|
|
50
|
+
/** Content to display when no services are available */
|
|
50
51
|
emptyState?: React.ReactNode;
|
|
52
|
+
/** Content to display when loading and no services are available */
|
|
53
|
+
loadingState?: React.ReactNode;
|
|
54
|
+
/** Content to display when an error occurs and no services are available */
|
|
55
|
+
errorState?: React.ReactNode;
|
|
51
56
|
infiniteScroll?: boolean;
|
|
52
57
|
pageSize?: number;
|
|
53
58
|
className?: string;
|
|
54
59
|
}
|
|
55
60
|
/**
|
|
56
|
-
* Container for the services list with empty state support.
|
|
61
|
+
* Container for the services list with empty, loading, and error state support.
|
|
57
62
|
* Wraps GenericList.Items. Follows List Container Level pattern.
|
|
58
63
|
*
|
|
59
64
|
* @component
|
|
60
65
|
* @example
|
|
61
66
|
* ```tsx
|
|
62
|
-
* <ServiceList.Services
|
|
67
|
+
* <ServiceList.Services
|
|
68
|
+
* emptyState={<div>No services found</div>}
|
|
69
|
+
* loadingState={<div>Loading services...</div>}
|
|
70
|
+
* errorState={<div>Failed to load services</div>}
|
|
71
|
+
* >
|
|
63
72
|
* <ServiceList.ServiceRepeater>
|
|
64
73
|
* <Service.Name />
|
|
65
74
|
* <Service.Price />
|
|
@@ -185,3 +194,64 @@ export declare const LoadMore: React.ForwardRefExoticComponent<LoadMoreProps & R
|
|
|
185
194
|
export declare const Actions: {
|
|
186
195
|
LoadMore: React.ForwardRefExoticComponent<LoadMoreProps & React.RefAttributes<HTMLButtonElement>>;
|
|
187
196
|
};
|
|
197
|
+
/**
|
|
198
|
+
* Data available through asChild pattern
|
|
199
|
+
*/
|
|
200
|
+
export interface FilterAsChildProps {
|
|
201
|
+
/** Filter options configuration derived from categories */
|
|
202
|
+
filterOptions: FilterPrimitive.FilterOption[];
|
|
203
|
+
/** Current filter state */
|
|
204
|
+
currentFilter: FilterPrimitive.Filter;
|
|
205
|
+
/** Function to update filter */
|
|
206
|
+
updateFilter: (filter: FilterPrimitive.Filter) => void;
|
|
207
|
+
/** Function to clear all active filters */
|
|
208
|
+
clearFilters: () => void;
|
|
209
|
+
/** Whether any filters are currently active */
|
|
210
|
+
isFiltered: boolean;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Props for ServiceList Filter component
|
|
214
|
+
*/
|
|
215
|
+
export interface FilterProps {
|
|
216
|
+
/** Child components that will have access to filter functionality */
|
|
217
|
+
children: React.ReactNode | ((props: FilterAsChildProps) => React.ReactNode);
|
|
218
|
+
/** When true, the component will not render its own div wrapper */
|
|
219
|
+
asChild?: boolean;
|
|
220
|
+
/** CSS classes to apply to the filter container */
|
|
221
|
+
className?: string;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Filter component that provides filtering functionality for service lists.
|
|
225
|
+
* Integrates with ServiceListService for categories and BookingService for location.
|
|
226
|
+
*
|
|
227
|
+
* @component
|
|
228
|
+
* @example
|
|
229
|
+
* ```tsx
|
|
230
|
+
* import { Filter } from '@wix/headless-components/react';
|
|
231
|
+
*
|
|
232
|
+
* <ServiceList.Filter className="filter-sidebar">
|
|
233
|
+
* <Filter.FilterOptions>
|
|
234
|
+
* <Filter.FilterOptionRepeater>
|
|
235
|
+
* <Filter.FilterOption.Label />
|
|
236
|
+
* <Filter.FilterOption.SingleFilter />
|
|
237
|
+
* </Filter.FilterOptionRepeater>
|
|
238
|
+
* </Filter.FilterOptions>
|
|
239
|
+
* <Filter.Filtered>
|
|
240
|
+
* <Filter.Action.Clear label="Clear Filters" />
|
|
241
|
+
* </Filter.Filtered>
|
|
242
|
+
* </ServiceList.Filter>
|
|
243
|
+
*
|
|
244
|
+
* // With asChild pattern
|
|
245
|
+
* <ServiceList.Filter asChild>
|
|
246
|
+
* <aside className="filter-sidebar">
|
|
247
|
+
* <Filter.FilterOptions>
|
|
248
|
+
* <Filter.FilterOptionRepeater>
|
|
249
|
+
* <Filter.FilterOption.Label />
|
|
250
|
+
* <Filter.FilterOption.SingleFilter />
|
|
251
|
+
* </Filter.FilterOptionRepeater>
|
|
252
|
+
* </Filter.FilterOptions>
|
|
253
|
+
* </aside>
|
|
254
|
+
* </ServiceList.Filter>
|
|
255
|
+
* ```
|
|
256
|
+
*/
|
|
257
|
+
export declare const Filter: React.ForwardRefExoticComponent<FilterProps & React.RefAttributes<HTMLElement>>;
|
|
@@ -4,8 +4,10 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
4
4
|
* Provides components for displaying services list with pagination
|
|
5
5
|
*/
|
|
6
6
|
import React from 'react';
|
|
7
|
-
import { GenericList, } from '@wix/headless-components/react';
|
|
7
|
+
import { GenericList, Filter as FilterPrimitive, } from '@wix/headless-components/react';
|
|
8
|
+
import { AsChildSlot } from '@wix/headless-utils/react';
|
|
8
9
|
import * as CoreServiceList from '../core/service-list/ServiceList.js';
|
|
10
|
+
import * as CoreServiceListFilter from '../core/service-list/ServiceListFilter.js';
|
|
9
11
|
import * as Service from '../service/Service.js';
|
|
10
12
|
/**
|
|
11
13
|
* Booking app ID constant
|
|
@@ -17,6 +19,7 @@ var TestIds;
|
|
|
17
19
|
TestIds["serviceListServices"] = "service-list-services";
|
|
18
20
|
TestIds["serviceListService"] = "service-list-service";
|
|
19
21
|
TestIds["serviceListLoadMore"] = "service-list-load-more";
|
|
22
|
+
TestIds["serviceListFilter"] = "service-list-filter";
|
|
20
23
|
})(TestIds || (TestIds = {}));
|
|
21
24
|
/**
|
|
22
25
|
* Root component that provides the ServiceList service context for rendering services lists.
|
|
@@ -70,22 +73,26 @@ const DEFAULT_PAGE_SIZE = 100;
|
|
|
70
73
|
*/
|
|
71
74
|
const RootContent = React.forwardRef((props, ref) => {
|
|
72
75
|
const { children, className, variant, pageSize = DEFAULT_PAGE_SIZE } = props;
|
|
73
|
-
return (_jsx(CoreServiceList.Services, { children: ({ services: servicesList, isLoading, hasMore, loadMore }) => {
|
|
76
|
+
return (_jsx(CoreServiceList.Services, { children: ({ services: servicesList, isLoading, hasMore, loadMore, error }) => {
|
|
74
77
|
const items = servicesList.map((service) => ({
|
|
75
78
|
...service,
|
|
76
79
|
id: service._id,
|
|
77
80
|
}));
|
|
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 }));
|
|
81
|
+
return (_jsx(GenericList.Root, { items: items, loadMore: () => loadMore(pageSize), hasMore: hasMore, isLoading: isLoading, error: error, className: className, ref: ref, "data-testid": TestIds.serviceListRoot, variant: variant, children: children }));
|
|
79
82
|
} }));
|
|
80
83
|
});
|
|
81
84
|
/**
|
|
82
|
-
* Container for the services list with empty state support.
|
|
85
|
+
* Container for the services list with empty, loading, and error state support.
|
|
83
86
|
* Wraps GenericList.Items. Follows List Container Level pattern.
|
|
84
87
|
*
|
|
85
88
|
* @component
|
|
86
89
|
* @example
|
|
87
90
|
* ```tsx
|
|
88
|
-
* <ServiceList.Services
|
|
91
|
+
* <ServiceList.Services
|
|
92
|
+
* emptyState={<div>No services found</div>}
|
|
93
|
+
* loadingState={<div>Loading services...</div>}
|
|
94
|
+
* errorState={<div>Failed to load services</div>}
|
|
95
|
+
* >
|
|
89
96
|
* <ServiceList.ServiceRepeater>
|
|
90
97
|
* <Service.Name />
|
|
91
98
|
* <Service.Price />
|
|
@@ -94,8 +101,8 @@ const RootContent = React.forwardRef((props, ref) => {
|
|
|
94
101
|
* ```
|
|
95
102
|
*/
|
|
96
103
|
export const Services = React.forwardRef((props, ref) => {
|
|
97
|
-
const { children, ...otherProps } = props;
|
|
98
|
-
return (_jsx(GenericList.Items, { ref: ref, "data-testid": TestIds.serviceListServices, ...otherProps, children: children }));
|
|
104
|
+
const { children, emptyState, loadingState, errorState, ...otherProps } = props;
|
|
105
|
+
return (_jsx(GenericList.Items, { ref: ref, "data-testid": TestIds.serviceListServices, emptyState: emptyState, loadingState: loadingState, errorState: errorState, ...otherProps, children: children }));
|
|
99
106
|
});
|
|
100
107
|
Services.displayName = 'ServiceList.Services';
|
|
101
108
|
/**
|
|
@@ -170,3 +177,52 @@ LoadMore.displayName = 'ServiceList.Actions.LoadMore';
|
|
|
170
177
|
export const Actions = {
|
|
171
178
|
LoadMore,
|
|
172
179
|
};
|
|
180
|
+
/**
|
|
181
|
+
* Filter component that provides filtering functionality for service lists.
|
|
182
|
+
* Integrates with ServiceListService for categories and BookingService for location.
|
|
183
|
+
*
|
|
184
|
+
* @component
|
|
185
|
+
* @example
|
|
186
|
+
* ```tsx
|
|
187
|
+
* import { Filter } from '@wix/headless-components/react';
|
|
188
|
+
*
|
|
189
|
+
* <ServiceList.Filter className="filter-sidebar">
|
|
190
|
+
* <Filter.FilterOptions>
|
|
191
|
+
* <Filter.FilterOptionRepeater>
|
|
192
|
+
* <Filter.FilterOption.Label />
|
|
193
|
+
* <Filter.FilterOption.SingleFilter />
|
|
194
|
+
* </Filter.FilterOptionRepeater>
|
|
195
|
+
* </Filter.FilterOptions>
|
|
196
|
+
* <Filter.Filtered>
|
|
197
|
+
* <Filter.Action.Clear label="Clear Filters" />
|
|
198
|
+
* </Filter.Filtered>
|
|
199
|
+
* </ServiceList.Filter>
|
|
200
|
+
*
|
|
201
|
+
* // With asChild pattern
|
|
202
|
+
* <ServiceList.Filter asChild>
|
|
203
|
+
* <aside className="filter-sidebar">
|
|
204
|
+
* <Filter.FilterOptions>
|
|
205
|
+
* <Filter.FilterOptionRepeater>
|
|
206
|
+
* <Filter.FilterOption.Label />
|
|
207
|
+
* <Filter.FilterOption.SingleFilter />
|
|
208
|
+
* </Filter.FilterOptionRepeater>
|
|
209
|
+
* </Filter.FilterOptions>
|
|
210
|
+
* </aside>
|
|
211
|
+
* </ServiceList.Filter>
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
214
|
+
export const Filter = React.forwardRef((props, ref) => {
|
|
215
|
+
const { children, asChild, className, ...otherProps } = props;
|
|
216
|
+
return (_jsx(CoreServiceListFilter.Root, { children: ({ filterOptions, currentFilter, updateFilter, clearFilters, isFiltered, }) => {
|
|
217
|
+
// Prepare asChildProps for function children
|
|
218
|
+
const asChildProps = {
|
|
219
|
+
filterOptions,
|
|
220
|
+
currentFilter: currentFilter,
|
|
221
|
+
updateFilter: (value) => updateFilter(value),
|
|
222
|
+
clearFilters,
|
|
223
|
+
isFiltered,
|
|
224
|
+
};
|
|
225
|
+
return (_jsx(FilterPrimitive.Root, { value: currentFilter, onChange: (value) => updateFilter(value), filterOptions: filterOptions, children: _jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.serviceListFilter, "data-is-filtered": isFiltered, customElement: children, customElementProps: asChildProps, ...otherProps, children: children }) }));
|
|
226
|
+
} }));
|
|
227
|
+
});
|
|
228
|
+
Filter.displayName = 'ServiceList.Filter';
|