@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.
Files changed (35) hide show
  1. package/cjs/dist/__mocks__/services.d.ts +21 -0
  2. package/cjs/dist/__mocks__/services.js +54 -0
  3. package/cjs/dist/api/index.d.ts +1 -0
  4. package/cjs/dist/api/index.js +1 -0
  5. package/cjs/dist/api/query-categories/index.d.ts +34 -0
  6. package/cjs/dist/api/query-categories/index.js +28 -0
  7. package/cjs/dist/api/query-services/index.js +9 -2
  8. package/cjs/dist/react/core/service/Service.js +6 -0
  9. package/cjs/dist/react/core/service-list/ServiceListFilter.d.ts +55 -0
  10. package/cjs/dist/react/core/service-list/ServiceListFilter.js +59 -0
  11. package/cjs/dist/react/service/Service.d.ts +32 -3
  12. package/cjs/dist/react/service/Service.js +25 -2
  13. package/cjs/dist/react/service-list/ServiceList.d.ts +73 -3
  14. package/cjs/dist/react/service-list/ServiceList.js +63 -7
  15. package/cjs/dist/services/service/service.js +23 -3
  16. package/cjs/dist/services/service-list/service-list.d.ts +14 -1
  17. package/cjs/dist/services/service-list/service-list.js +94 -12
  18. package/dist/__mocks__/services.d.ts +21 -0
  19. package/dist/__mocks__/services.js +54 -0
  20. package/dist/api/index.d.ts +1 -0
  21. package/dist/api/index.js +1 -0
  22. package/dist/api/query-categories/index.d.ts +34 -0
  23. package/dist/api/query-categories/index.js +28 -0
  24. package/dist/api/query-services/index.js +9 -2
  25. package/dist/react/core/service/Service.js +6 -0
  26. package/dist/react/core/service-list/ServiceListFilter.d.ts +55 -0
  27. package/dist/react/core/service-list/ServiceListFilter.js +59 -0
  28. package/dist/react/service/Service.d.ts +32 -3
  29. package/dist/react/service/Service.js +25 -2
  30. package/dist/react/service-list/ServiceList.d.ts +73 -3
  31. package/dist/react/service-list/ServiceList.js +63 -7
  32. package/dist/services/service/service.js +23 -3
  33. package/dist/services/service-list/service-list.d.ts +14 -1
  34. package/dist/services/service-list/service-list.js +94 -12
  35. 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
+ };
@@ -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
@@ -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 other filters as they are implemented in future phases
78
- // categoryIds, locationIds, staffMemberIds to be added later
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
- * Requires exactly one of: service, serviceId, or serviceSlug.
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
- * Requires exactly one of: service, serviceId, or serviceSlug.
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
- * Requires exactly one of: service, serviceId, or serviceSlug.
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
- return (_jsx(CoreService.Root, { serviceServiceConfig: { service, serviceId, serviceSlug, appId }, 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 })) }));
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 emptyState={<div>No services found</div>}>
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 emptyState={<div>No services found</div>}>
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';