willba-component-library 0.3.13 → 0.3.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.
Files changed (110) hide show
  1. package/README.md +12 -12
  2. package/lib/assets/IconsSvg.d.ts +2 -1
  3. package/lib/components/Button/Button.d.ts +2 -2
  4. package/lib/components/FilterBar/FilterBar.d.ts +3 -2
  5. package/lib/components/FilterBar/components/FilterControls/FilterControls.d.ts +3 -0
  6. package/lib/components/FilterBar/components/FilterPanels/Categories/Categories.d.ts +11 -0
  7. package/lib/components/FilterBar/components/FilterPanels/Dates/Dates.d.ts +16 -0
  8. package/lib/components/FilterBar/components/FilterPanels/FilterPanels.d.ts +3 -0
  9. package/lib/components/FilterBar/components/FilterPanels/Guests/GuestCount/GuestCount.d.ts +4 -0
  10. package/lib/components/FilterBar/components/FilterPanels/Guests/Guests.d.ts +12 -0
  11. package/lib/components/FilterBar/components/FilterPanels/Locations/Locations.d.ts +14 -0
  12. package/lib/components/FilterBar/components/FilterPanels/SectionHeader/SectionHeader.d.ts +8 -0
  13. package/lib/components/FilterBar/components/FilterTabs/FilterTabs.d.ts +3 -0
  14. package/lib/components/FilterBar/components/ImageCard/ImageCard.d.ts +11 -0
  15. package/lib/components/FilterBar/components/SelectButton/SelectButton.d.ts +13 -0
  16. package/lib/components/FilterBar/components/TabButton/TabButton.d.ts +10 -0
  17. package/lib/components/FilterBar/components/buttons/select-button/SelectButton.d.ts +2 -1
  18. package/lib/components/FilterBar/components/buttons/tab-button/TabButton.d.ts +2 -1
  19. package/lib/components/FilterBar/components/cards/image-card/ImageCard.d.ts +2 -1
  20. package/lib/components/FilterBar/components/categories/Categories.d.ts +2 -1
  21. package/lib/components/FilterBar/components/common/FilterSectionHeader.d.ts +2 -2
  22. package/lib/components/FilterBar/components/dates/Dates.d.ts +2 -2
  23. package/lib/components/FilterBar/components/divider/Divider.d.ts +2 -1
  24. package/lib/components/FilterBar/components/guests/Guests.d.ts +2 -1
  25. package/lib/components/FilterBar/components/index.d.ts +6 -5
  26. package/lib/components/FilterBar/components/locations/Locations.d.ts +2 -1
  27. package/lib/components/FilterBar/hooks/index.d.ts +4 -1
  28. package/lib/components/FilterBar/hooks/useFilterActions.d.ts +25 -0
  29. package/lib/components/FilterBar/hooks/useFilterState.d.ts +22 -0
  30. package/lib/components/FilterBar/hooks/useFilterUi.d.ts +7 -0
  31. package/lib/components/FilterBar/hooks/usePanelPosition.d.ts +12 -0
  32. package/lib/components/FilterBar/index.d.ts +1 -1
  33. package/lib/components/FilterBar/providers/FilterBarProvider.d.ts +31 -0
  34. package/lib/components/FilterBar/providers/index.d.ts +1 -0
  35. package/lib/components/FilterBar/utils/ageCategoriesRules.d.ts +7 -0
  36. package/lib/components/FilterBar/utils/index.d.ts +1 -1
  37. package/lib/components/FilterBar/utils/parseGuests.d.ts +5 -1
  38. package/lib/components/FilterCalendar/FilterCalendar.d.ts +3 -2
  39. package/lib/components/FilterCalendar/components/Footer.d.ts +2 -1
  40. package/lib/core/components/buttons/CloseButton/CloseButton.d.ts +7 -0
  41. package/lib/core/components/buttons/SubmitButton/SubmitButton.d.ts +14 -0
  42. package/lib/core/components/buttons/close-button/CloseButton.d.ts +2 -1
  43. package/lib/core/components/buttons/submit-button/SubmitButton.d.ts +2 -1
  44. package/lib/core/components/calendar/Calendar.d.ts +2 -1
  45. package/lib/core/components/index.d.ts +4 -2
  46. package/lib/index.d.ts +4 -5
  47. package/lib/index.esm.js +1593 -1480
  48. package/lib/index.esm.js.map +1 -1
  49. package/lib/index.js +2156 -2043
  50. package/lib/index.js.map +1 -1
  51. package/lib/index.umd.js +2159 -2045
  52. package/lib/index.umd.js.map +1 -1
  53. package/lib/themes/useTheme.d.ts +1 -1
  54. package/package.json +1 -1
  55. package/rollup.config.mjs +1 -1
  56. package/src/components/FilterBar/FilterBar.css +4 -70
  57. package/src/components/FilterBar/FilterBar.stories.tsx +1 -1
  58. package/src/components/FilterBar/FilterBar.tsx +28 -283
  59. package/src/components/FilterBar/components/FilterControls/FilterControls.css +18 -0
  60. package/src/components/FilterBar/components/FilterControls/FilterControls.tsx +135 -0
  61. package/src/components/FilterBar/components/{categories → FilterPanels/Categories}/Categories.css +1 -1
  62. package/src/components/FilterBar/components/{categories → FilterPanels/Categories}/Categories.tsx +3 -1
  63. package/src/components/FilterBar/components/{dates → FilterPanels/Dates}/Dates.tsx +11 -11
  64. package/src/components/FilterBar/components/FilterPanels/FilterPanels.css +24 -0
  65. package/src/components/FilterBar/components/FilterPanels/FilterPanels.tsx +115 -0
  66. package/src/components/FilterBar/components/{guests → FilterPanels/Guests}/GuestCount/GuestCount.tsx +1 -1
  67. package/src/components/FilterBar/components/{guests → FilterPanels/Guests}/Guests.css +5 -6
  68. package/src/components/FilterBar/components/{guests → FilterPanels/Guests}/Guests.tsx +7 -7
  69. package/src/components/FilterBar/components/{locations → FilterPanels/Locations}/Locations.css +1 -1
  70. package/src/components/FilterBar/components/{locations → FilterPanels/Locations}/Locations.tsx +7 -6
  71. package/src/components/FilterBar/components/{common/FilterSectionHeader.tsx → FilterPanels/SectionHeader/SectionHeader.tsx} +2 -2
  72. package/src/components/FilterBar/components/FilterTabs/FilterTabs.css +10 -0
  73. package/src/components/FilterBar/components/FilterTabs/FilterTabs.tsx +47 -0
  74. package/src/components/FilterBar/components/{buttons/select-button → SelectButton}/SelectButton.css +1 -0
  75. package/src/components/FilterBar/components/{buttons/select-button → SelectButton}/SelectButton.tsx +7 -6
  76. package/src/components/FilterBar/components/index.ts +6 -6
  77. package/src/components/FilterBar/hooks/index.ts +4 -1
  78. package/src/components/FilterBar/hooks/useFilterActions.tsx +126 -0
  79. package/src/components/FilterBar/hooks/useFilterState.tsx +86 -0
  80. package/src/components/FilterBar/hooks/useFilterUi.tsx +19 -0
  81. package/src/components/FilterBar/hooks/usePanelPosition.tsx +66 -0
  82. package/src/components/FilterBar/index.ts +1 -1
  83. package/src/components/FilterBar/providers/FilterBarProvider.tsx +169 -0
  84. package/src/components/FilterBar/providers/index.ts +1 -0
  85. package/src/components/FilterBar/utils/ageCategoriesRules.ts +27 -0
  86. package/src/components/FilterBar/utils/index.tsx +1 -1
  87. package/src/components/FilterBar/utils/parseGuests.tsx +20 -9
  88. package/src/components/FilterBar/utils/{parseLocations.tsx → parseLocations.ts} +3 -4
  89. package/src/components/FilterCalendar/FilterCalendar.tsx +2 -2
  90. package/src/core/components/index.ts +5 -2
  91. package/src/index.ts +1 -1
  92. package/src/themes/useTheme.tsx +1 -1
  93. package/src/components/FilterBar/components/buttons/index.ts +0 -5
  94. package/src/components/FilterBar/components/cards/index.ts +0 -1
  95. package/src/components/FilterBar/components/dates/index.ts +0 -1
  96. package/src/components/FilterBar/hooks/useFilterBar.tsx +0 -208
  97. package/src/components/FilterBar/utils/calculateDropdownPosition.tsx +0 -96
  98. /package/src/components/FilterBar/components/{divider → Divider}/Divider.css +0 -0
  99. /package/src/components/FilterBar/components/{divider → Divider}/Divider.tsx +0 -0
  100. /package/src/components/FilterBar/components/{dates → FilterPanels/Dates}/Dates.css +0 -0
  101. /package/src/components/FilterBar/components/{guests → FilterPanels/Guests}/GuestCount/GuestCount.css +0 -0
  102. /package/src/components/FilterBar/components/{common/FilterSectionHeader.css → FilterPanels/SectionHeader/SectionHeader.css} +0 -0
  103. /package/src/components/FilterBar/components/{cards/image-card → ImageCard}/ImageCard.css +0 -0
  104. /package/src/components/FilterBar/components/{cards/image-card → ImageCard}/ImageCard.tsx +0 -0
  105. /package/src/components/FilterBar/components/{buttons/tab-button → TabButton}/TabButton.css +0 -0
  106. /package/src/components/FilterBar/components/{buttons/tab-button → TabButton}/TabButton.tsx +0 -0
  107. /package/src/core/components/buttons/{close-button → CloseButton}/CloseButton.css +0 -0
  108. /package/src/core/components/buttons/{close-button → CloseButton}/CloseButton.tsx +0 -0
  109. /package/src/core/components/buttons/{submit-button → SubmitButton}/SubmitButton.css +0 -0
  110. /package/src/core/components/buttons/{submit-button → SubmitButton}/SubmitButton.tsx +0 -0
@@ -0,0 +1,24 @@
1
+ .will-filter-bar-panels {
2
+ background-color: var(--will-white);
3
+ min-height: 100px;
4
+ position: absolute;
5
+ top: 125px;
6
+ z-index: 111;
7
+ border-radius: 25px;
8
+ box-shadow: var(--will-box-shadow);
9
+ }
10
+
11
+ @media (max-width: 960px) {
12
+ .will-root {
13
+ width: 100%;
14
+ min-width: auto;
15
+ max-height: 100vh;
16
+ z-index: 3;
17
+ }
18
+
19
+ .will-filter-bar-panels {
20
+ margin-top: 10px;
21
+ top: 0;
22
+ position: relative;
23
+ }
24
+ }
@@ -0,0 +1,115 @@
1
+ import React, { CSSProperties, useEffect, useState } from 'react'
2
+
3
+ import { useCloseFilterSection } from '../../../../core/hooks'
4
+ import { FilterSections } from '../../FilterBarTypes'
5
+ import { useFilterBar } from '../../providers'
6
+
7
+ import { Dates } from './Dates/Dates'
8
+ import { Guests } from './Guests/Guests'
9
+ import { Locations } from './Locations/Locations'
10
+ import { Categories } from './Categories/Categories'
11
+
12
+ import './FilterPanels.css'
13
+ import { usePanelPosition } from '../../hooks'
14
+
15
+ export const FilterPanels = () => {
16
+ const {
17
+ categories,
18
+ calendarRange,
19
+ selectedFilter,
20
+ selectedPath,
21
+ ageCategoryCounts,
22
+ selectedLocations,
23
+ mode,
24
+ tabs,
25
+ disableCalendarDates,
26
+ ageCategories,
27
+ locations,
28
+ language,
29
+ isMobile,
30
+ panelRef,
31
+ buttonRefs,
32
+ targetFilterBarRef,
33
+ setSelectedLocations,
34
+ setCalendarRange,
35
+ handleSelectedFilter,
36
+ updateGuestsCount,
37
+ setCategories,
38
+ } = useFilterBar()
39
+
40
+ // Handle close filter section
41
+ const { filtersRef } = useCloseFilterSection({ handleSelectedFilter })
42
+
43
+ const { localStyles } = usePanelPosition({
44
+ selectedFilter,
45
+ panelRef,
46
+ targetFilterBarRef,
47
+ buttonRefs,
48
+ isMobile,
49
+ })
50
+
51
+ const renderContent = () => {
52
+ switch (selectedFilter) {
53
+ case FilterSections.CALENDAR:
54
+ return (
55
+ <Dates
56
+ autoFocus
57
+ ref={filtersRef}
58
+ onClose={() => handleSelectedFilter(false)}
59
+ calendarRange={calendarRange}
60
+ setCalendarRange={setCalendarRange}
61
+ disableCalendarDates={disableCalendarDates}
62
+ selectedPath={selectedPath}
63
+ language={language}
64
+ />
65
+ )
66
+ case FilterSections.GUESTS:
67
+ return (
68
+ <Guests
69
+ autoFocus
70
+ ref={filtersRef}
71
+ ageCategories={ageCategories}
72
+ ageCategoryCounts={ageCategoryCounts}
73
+ updateGuestsCount={updateGuestsCount}
74
+ onClose={() => handleSelectedFilter(false)}
75
+ />
76
+ )
77
+ case FilterSections.CATEGORIES:
78
+ return (
79
+ <Categories categories={categories} setCategories={setCategories} />
80
+ )
81
+ case FilterSections.LOCATIONS:
82
+ return (
83
+ <Locations
84
+ autoFocus
85
+ ref={filtersRef}
86
+ locations={locations?.data}
87
+ language={language}
88
+ selectedLocations={selectedLocations}
89
+ setSelectedLocations={setSelectedLocations}
90
+ multiSelect={locations?.multiSelect}
91
+ onClose={() => handleSelectedFilter(false)}
92
+ />
93
+ )
94
+
95
+ default:
96
+ return null
97
+ }
98
+ }
99
+
100
+ return (
101
+ selectedFilter && (
102
+ <div
103
+ ref={panelRef}
104
+ className={`will-filter-bar-panels ${mode || 'light'}`}
105
+ style={
106
+ (!tabs || tabs.length < 2) && !isMobile
107
+ ? { top: 66 }
108
+ : { ...localStyles }
109
+ }
110
+ >
111
+ {renderContent()}
112
+ </div>
113
+ )
114
+ )
115
+ }
@@ -1,6 +1,6 @@
1
1
  import React, { FC, useEffect } from 'react'
2
2
 
3
- import { GuestsCountPropsType } from '../../../FilterBarTypes'
3
+ import { GuestsCountPropsType } from '../../../../FilterBarTypes'
4
4
 
5
5
  import './GuestCount.css'
6
6
 
@@ -1,4 +1,4 @@
1
- .will-filter-bar-guests {
1
+ #will-filter-bar-guests {
2
2
  text-align: initial;
3
3
  }
4
4
 
@@ -10,9 +10,8 @@
10
10
  padding: 16px;
11
11
  }
12
12
 
13
- @media (max-width: 960px) {
14
- .will-guests-filter-container {
15
- margin-top: 15px;
16
- min-width: 100%;
17
- }
13
+ /**/
14
+ .will-guest-count {
15
+ display: inline-block;
16
+ min-width: 10px;
18
17
  }
@@ -1,16 +1,16 @@
1
1
  import React, { forwardRef } from 'react'
2
2
  import { useTranslation } from 'react-i18next'
3
3
 
4
- import { useAutoFocus } from '../../../../core/hooks'
4
+ import { useAutoFocus } from '../../../../../core/hooks'
5
+ import { CloseButton } from '../../../../../core/components'
5
6
  import { GuestCount } from './GuestCount/GuestCount'
6
- import { AgeCategoryCount, AgeCategoryType } from '../../FilterBarTypes'
7
- import { FilterSectionHeader } from '../common/FilterSectionHeader'
8
- import { CloseButton } from '../../../../core/components'
7
+ import { AgeCategoryCount, AgeCategoryType } from '../../../FilterBarTypes'
8
+ import { SectionHeader } from '../SectionHeader/SectionHeader'
9
9
 
10
10
  import './Guests.css'
11
11
 
12
12
  type Props = {
13
- ageCategories: AgeCategoryType[]
13
+ ageCategories?: AgeCategoryType[]
14
14
  updateGuestsCount: (arg1: string, arg2: number) => void
15
15
  ageCategoryCounts: AgeCategoryCount
16
16
  autoFocus?: boolean
@@ -32,8 +32,8 @@ export const Guests = forwardRef<HTMLDivElement, Props>(
32
32
  const containerRef = useAutoFocus<HTMLDivElement>(autoFocus)
33
33
 
34
34
  return (
35
- <div className="will-filter-bar-guests" ref={ref}>
36
- <FilterSectionHeader
35
+ <div id="will-filter-bar-guests" ref={ref}>
36
+ <SectionHeader
37
37
  title={t('guests.title')}
38
38
  action={onClose && <CloseButton handleClose={onClose} />}
39
39
  />
@@ -1,4 +1,4 @@
1
- .will-filter-bar-locations {
1
+ #will-filter-bar-locations {
2
2
  text-align: initial;
3
3
  }
4
4
 
@@ -1,11 +1,12 @@
1
1
  import React, { forwardRef, useEffect, useRef } from 'react'
2
2
  import { useTranslation } from 'react-i18next'
3
3
 
4
+ import { CloseButton } from '../../../../../core/components'
5
+ import { Location } from '../../../FilterBarTypes'
6
+ import { ImageCard } from '../../ImageCard/ImageCard'
7
+ import { SectionHeader } from '../SectionHeader/SectionHeader'
8
+
4
9
  import './Locations.css'
5
- import { ImageCard } from '../cards/image-card/ImageCard'
6
- import { Location } from '../../FilterBarTypes'
7
- import { FilterSectionHeader } from '../common/FilterSectionHeader'
8
- import { CloseButton } from '../../../../core/components'
9
10
 
10
11
  type Props = {
11
12
  locations?: Location[]
@@ -63,8 +64,8 @@ export const Locations = forwardRef<HTMLDivElement, Props>(
63
64
  }
64
65
 
65
66
  return (
66
- <div className="will-filter-bar-locations" ref={ref}>
67
- <FilterSectionHeader
67
+ <div id="will-filter-bar-locations" ref={ref}>
68
+ <SectionHeader
68
69
  title={t('locations.title')}
69
70
  action={onClose && <CloseButton handleClose={onClose} />}
70
71
  />
@@ -1,13 +1,13 @@
1
1
  import React, { ReactNode } from 'react'
2
2
 
3
- import './FilterSectionHeader.css'
3
+ import './SectionHeader.css'
4
4
 
5
5
  type Props = {
6
6
  title: string
7
7
  action?: ReactNode
8
8
  }
9
9
 
10
- export const FilterSectionHeader = ({ title, action }: Props) => {
10
+ export const SectionHeader = ({ title, action }: Props) => {
11
11
  return (
12
12
  <div className="will-filter-section-header">
13
13
  <h3 className="will-filter-section-title">{title}</h3>
@@ -0,0 +1,10 @@
1
+ .will-filter-bar-tabs {
2
+ display: flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ position: relative;
6
+ z-index: 222;
7
+ background: transparent;
8
+ padding: 10px;
9
+ gap: 10px;
10
+ }
@@ -0,0 +1,47 @@
1
+ import React from 'react'
2
+
3
+ import { useTranslation } from 'react-i18next'
4
+
5
+ import { useFilterBar } from '../../providers'
6
+
7
+ import { TabButton } from '../TabButton/TabButton'
8
+
9
+ import './FilterTabs.css'
10
+
11
+ export const FilterTabs = () => {
12
+ const { t } = useTranslation('filterBar')
13
+
14
+ const {
15
+ selectedPath,
16
+ mode,
17
+ tabs,
18
+ handleSelectedFilter,
19
+ setSelectedPath,
20
+ handleResetFilters,
21
+ //
22
+ targetFilterBarRef,
23
+ } = useFilterBar()
24
+
25
+ return (
26
+ tabs &&
27
+ tabs.length > 1 && (
28
+ <div className="will-filter-bar-tabs" ref={targetFilterBarRef}>
29
+ {tabs
30
+ .sort((a, b) => a.order - b.order)
31
+ .map((tab, idx) => (
32
+ <TabButton
33
+ key={`tab-${idx}`}
34
+ label={tab.label || t(`tabs.${tab.path.substring(1)}`)}
35
+ onClick={() => {
36
+ setSelectedPath(tab.path)
37
+ handleResetFilters()
38
+ handleSelectedFilter(false)
39
+ }}
40
+ active={selectedPath === tab.path}
41
+ mode={mode}
42
+ />
43
+ ))}
44
+ </div>
45
+ )
46
+ )
47
+ }
@@ -8,6 +8,7 @@
8
8
  cursor: pointer;
9
9
  font-size: 14px;
10
10
  text-align: initial;
11
+ user-select: none;
11
12
  }
12
13
 
13
14
  .will-filter-bar-select-button.disabled {
@@ -1,11 +1,11 @@
1
- import React, { forwardRef } from 'react'
1
+ import React, { forwardRef, ReactNode, MouseEvent } from 'react'
2
2
 
3
3
  import './SelectButton.css'
4
4
 
5
5
  type Props = {
6
6
  label: string
7
- onClick: () => void
8
- description: string
7
+ onClick: (e: MouseEvent<HTMLButtonElement>) => void
8
+ description: ReactNode
9
9
  active?: boolean
10
10
  disabled?: boolean
11
11
  ariaExpanded?: boolean
@@ -29,7 +29,7 @@ export const SelectButton = forwardRef<HTMLButtonElement, Props>(
29
29
  <button
30
30
  ref={ref}
31
31
  className={`will-filter-bar-select-button ${disabled ? 'disabled' : ''}`}
32
- onClick={disabled ? undefined : onClick}
32
+ onClick={(e) => (disabled ? undefined : onClick(e))}
33
33
  disabled={disabled}
34
34
  aria-expanded={ariaExpanded}
35
35
  aria-controls={ariaControls}
@@ -41,8 +41,9 @@ export const SelectButton = forwardRef<HTMLButtonElement, Props>(
41
41
  <p className={`select-button-label`}>{label}</p>
42
42
  <p
43
43
  className={`select-button-description ${active ? 'active' : ''}`}
44
- dangerouslySetInnerHTML={{ __html: description }}
45
- />
44
+ >
45
+ {description}
46
+ </p>
46
47
  </div>
47
48
  </span>
48
49
  </button>
@@ -1,6 +1,6 @@
1
- export { CloseButton, SelectButton, SubmitButton, TabButton } from './buttons'
2
-
3
- export { Guests } from './guests/Guests'
4
- export { Divider } from './divider/Divider'
5
- export { Categories } from './categories/Categories'
6
- export { Locations } from './locations/Locations'
1
+ export { SelectButton } from './SelectButton/SelectButton'
2
+ export { TabButton } from './TabButton/TabButton'
3
+ export { Divider } from './Divider/Divider'
4
+ export { FilterPanels } from './FilterPanels/FilterPanels'
5
+ export { FilterControls } from './FilterControls/FilterControls'
6
+ export { FilterTabs } from './FilterTabs/FilterTabs'
@@ -1,2 +1,5 @@
1
- export { useFilterBar } from './useFilterBar'
2
1
  export { useScrollInToView } from './useScrollInToView'
2
+ export { useFilterState } from './useFilterState'
3
+ export { useFilterActions } from './useFilterActions'
4
+ export { useFilterUi } from './useFilterUi'
5
+ export { usePanelPosition } from './usePanelPosition'
@@ -0,0 +1,126 @@
1
+ import { Dispatch, SetStateAction, useEffect } from 'react'
2
+ import { DateRange } from 'react-day-picker'
3
+ import { format } from 'date-fns'
4
+
5
+ import {
6
+ AgeCategoryCount,
7
+ FilterBarTypes,
8
+ Locations,
9
+ Pages,
10
+ } from '../FilterBarTypes'
11
+ import { ageCategoryRules } from '../utils'
12
+
13
+ type Props = {
14
+ tabs: FilterBarTypes['tabs']
15
+ ageCategories: FilterBarTypes['ageCategories']
16
+ redirectUrl: FilterBarTypes['redirectUrl']
17
+ ageCategoryCounts: AgeCategoryCount
18
+ selectedLocations: Locations['data']
19
+ calendarRange?: DateRange
20
+ selectedPath: string
21
+ setSelectedPath: (val: string) => void
22
+ setAgeCategoryCounts: Dispatch<SetStateAction<AgeCategoryCount>>
23
+ setSelectedFilter: (val: string | boolean) => void
24
+ setCalendarRange: (val: DateRange | undefined) => void
25
+ onSubmit: FilterBarTypes['onSubmit']
26
+ setInnerLoading: (val: boolean) => void
27
+ }
28
+
29
+ export const useFilterActions = ({
30
+ tabs,
31
+ calendarRange,
32
+ ageCategoryCounts,
33
+ ageCategories,
34
+ selectedLocations,
35
+ selectedPath,
36
+ redirectUrl,
37
+ setSelectedPath,
38
+ setAgeCategoryCounts,
39
+ setSelectedFilter,
40
+ setCalendarRange,
41
+ onSubmit,
42
+ setInnerLoading,
43
+ }: Props) => {
44
+ useEffect(() => {
45
+ if (typeof window === 'undefined') return
46
+
47
+ const defaultTab =
48
+ tabs?.length === 1 ? tabs[0] : tabs?.find((tab) => tab.default)
49
+
50
+ const paths = [Pages.EVENTS, Pages.ROOMS, Pages.SALES]
51
+
52
+ const currentPath =
53
+ paths.find((path) => window.location.pathname.includes(path)) ||
54
+ defaultTab?.path ||
55
+ Pages.EVENTS
56
+
57
+ setSelectedPath(currentPath)
58
+ }, [tabs])
59
+
60
+ const updateGuestsCount = (id: string, newCount: number) => {
61
+ setAgeCategoryCounts((prev) => ({
62
+ ...prev,
63
+ [id]: newCount,
64
+ }))
65
+ }
66
+
67
+ const handleSelectedFilter = (id: string | boolean) => {
68
+ setSelectedFilter(id)
69
+ }
70
+
71
+ const handleSubmit = () => {
72
+ if (typeof window === 'undefined') return
73
+
74
+ const newParams = {
75
+ startDate: calendarRange?.from
76
+ ? format(calendarRange.from, 'yyyy-MM-dd')
77
+ : '',
78
+ endDate: calendarRange?.to ? format(calendarRange.to, 'yyyy-MM-dd') : '',
79
+ ageCategoryCounts: ageCategoryRules({
80
+ ageCategoryCounts,
81
+ ageCategories,
82
+ }),
83
+ selectedLocations: selectedLocations
84
+ .map((l) => l.id.toString())
85
+ .join(','),
86
+ }
87
+
88
+ const querySearchParams = new URLSearchParams()
89
+
90
+ for (const [key, value] of Object.entries(newParams)) {
91
+ if (!value) continue
92
+
93
+ if (key === 'selectedLocations' && selectedLocations.length) {
94
+ selectedLocations.forEach((location) =>
95
+ querySearchParams.append('locationId', location.id.toString())
96
+ )
97
+ } else {
98
+ querySearchParams.append(key, value.toString())
99
+ }
100
+ }
101
+
102
+ setSelectedFilter(false)
103
+
104
+ if (onSubmit && window.location.href.includes(selectedPath)) {
105
+ onSubmit(newParams)
106
+ return
107
+ }
108
+
109
+ const paramString = querySearchParams.toString()
110
+ const path = `${redirectUrl}${selectedPath}`
111
+
112
+ setInnerLoading(true)
113
+ window.location.href = paramString ? `${path}?${paramString}` : path
114
+ }
115
+
116
+ const handleResetFilters = () => {
117
+ setAgeCategoryCounts({})
118
+ setCalendarRange(undefined)
119
+ }
120
+ return {
121
+ updateGuestsCount,
122
+ handleSelectedFilter,
123
+ handleSubmit,
124
+ handleResetFilters,
125
+ }
126
+ }
@@ -0,0 +1,86 @@
1
+ import { useEffect, useState } from 'react'
2
+ import { DateRange } from 'react-day-picker'
3
+
4
+ import { AgeCategoryCount, FilterBarTypes, Pages } from '../FilterBarTypes'
5
+
6
+ type Props = {
7
+ locations: FilterBarTypes['locations']
8
+ }
9
+
10
+ export const useFilterState = ({ locations }: Props) => {
11
+ const [selectedPath, setSelectedPath] = useState<string>(Pages.EVENTS)
12
+ const [selectedFilter, setSelectedFilter] = useState<string | boolean>(false)
13
+ const [calendarRange, setCalendarRange] = useState<DateRange | undefined>()
14
+ const [innerLoading, setInnerLoading] = useState<boolean>(false)
15
+ const [categories, setCategories] = useState<number>(0)
16
+ const [ageCategoryCounts, setAgeCategoryCounts] = useState<AgeCategoryCount>(
17
+ {}
18
+ )
19
+ const [selectedLocations, setSelectedLocations] = useState<
20
+ NonNullable<FilterBarTypes['locations']>['data']
21
+ >([])
22
+
23
+ useEffect(() => {
24
+ if (typeof window === 'undefined') return
25
+
26
+ const urlSearchParams = new URLSearchParams(window.location.search)
27
+
28
+ const startDateParam = urlSearchParams.get('startDate')
29
+ const endDateParam = urlSearchParams.get('endDate')
30
+ const locationIdParams = urlSearchParams.getAll('locationId')
31
+
32
+ let ageCategoryCountsParam: AgeCategoryCount = {}
33
+ const ageCategoryCountsQuery = urlSearchParams.get('ageCategoryCounts')
34
+
35
+ if (ageCategoryCountsQuery) {
36
+ try {
37
+ ageCategoryCountsParam = JSON.parse(ageCategoryCountsQuery)
38
+ } catch (error) {
39
+ console.warn('Invalid ageCategoryCounts query param, ignoring', error)
40
+ ageCategoryCountsParam = {}
41
+ }
42
+ }
43
+
44
+ const categoriesParam = urlSearchParams.get('categories')
45
+ let parsedCategories = 0
46
+
47
+ if (categoriesParam) {
48
+ const parsed = parseInt(categoriesParam, 10)
49
+ parsedCategories = Number.isNaN(parsed) ? 0 : parsed
50
+ }
51
+
52
+ if (startDateParam && endDateParam) {
53
+ setCalendarRange({
54
+ from: new Date(startDateParam),
55
+ to: new Date(endDateParam),
56
+ })
57
+ }
58
+
59
+ setAgeCategoryCounts(ageCategoryCountsParam)
60
+ setCategories(parsedCategories)
61
+
62
+ if (locations?.data?.length && locationIdParams.length) {
63
+ const matchedLocations = locations.data.filter((location) =>
64
+ locationIdParams.includes(location.id.toString())
65
+ )
66
+ setSelectedLocations(matchedLocations)
67
+ }
68
+ }, [locations])
69
+
70
+ return {
71
+ selectedPath,
72
+ selectedFilter,
73
+ calendarRange,
74
+ innerLoading,
75
+ categories,
76
+ ageCategoryCounts,
77
+ selectedLocations,
78
+ setSelectedLocations,
79
+ setCalendarRange,
80
+ setAgeCategoryCounts,
81
+ setCategories,
82
+ setSelectedPath,
83
+ setSelectedFilter,
84
+ setInnerLoading,
85
+ }
86
+ }
@@ -0,0 +1,19 @@
1
+ import { useRef } from 'react'
2
+
3
+ import { useScrollInToView } from './useScrollInToView'
4
+
5
+ export const useFilterUi = (selectedFilter: string | boolean) => {
6
+ const buttonRefs = useRef<Record<string, HTMLButtonElement | null>>({})
7
+ const panelRef = useRef<HTMLDivElement | null>(null)
8
+ const previouslyFocusedButtonRef = useRef<HTMLButtonElement | null>(null)
9
+
10
+ const { isMobile, targetFilterBarRef } = useScrollInToView({ selectedFilter })
11
+
12
+ return {
13
+ previouslyFocusedButtonRef,
14
+ isMobile,
15
+ targetFilterBarRef,
16
+ panelRef,
17
+ buttonRefs,
18
+ }
19
+ }
@@ -0,0 +1,66 @@
1
+ import {
2
+ CSSProperties,
3
+ MutableRefObject,
4
+ RefObject,
5
+ useEffect,
6
+ useState,
7
+ } from 'react'
8
+
9
+ type Props = {
10
+ selectedFilter: string | boolean
11
+ panelRef: RefObject<HTMLDivElement | null>
12
+ targetFilterBarRef: RefObject<HTMLDivElement | null>
13
+ buttonRefs: MutableRefObject<Record<string, HTMLButtonElement | null>>
14
+ isMobile: boolean
15
+ }
16
+
17
+ export const usePanelPosition = ({
18
+ selectedFilter,
19
+ panelRef,
20
+ targetFilterBarRef,
21
+ buttonRefs,
22
+ isMobile,
23
+ }: Props) => {
24
+ const [localStyles, setLocalStyles] = useState<CSSProperties>({})
25
+
26
+ useEffect(() => {
27
+ if (!selectedFilter || typeof selectedFilter !== 'string' || isMobile) {
28
+ setLocalStyles({})
29
+ return
30
+ }
31
+
32
+ let timeoutId: number | null = null
33
+
34
+ const calculate = () => {
35
+ const panel = panelRef.current
36
+ const container = targetFilterBarRef.current
37
+ const button = buttonRefs.current[selectedFilter]
38
+
39
+ if (!panel || !container || !button) return
40
+
41
+ const panelRect = panel.getBoundingClientRect()
42
+ if (panelRect.width === 0) {
43
+ timeoutId = window.setTimeout(calculate, 10)
44
+ return
45
+ }
46
+
47
+ const containerRect = container.getBoundingClientRect()
48
+ const buttonRect = button.getBoundingClientRect()
49
+ const buttonLeft = buttonRect.left - containerRect.left
50
+ const left = Math.min(
51
+ buttonLeft,
52
+ Math.max(containerRect.width - panelRect.width, 0)
53
+ )
54
+
55
+ setLocalStyles({ left })
56
+ }
57
+
58
+ calculate()
59
+
60
+ return () => {
61
+ if (timeoutId) clearTimeout(timeoutId)
62
+ }
63
+ }, [selectedFilter, panelRef, targetFilterBarRef, buttonRefs, isMobile])
64
+
65
+ return { localStyles }
66
+ }
@@ -1,3 +1,3 @@
1
- export { default } from './FilterBar'
1
+ export { FilterBar } from './FilterBar'
2
2
 
3
3
  export * from './FilterBarTypes'