|
19 | 19 |
|
20 | 20 | let showCategoriesDropdown = $state(false); |
21 | 21 | let showTagsDropdown = $state(false); |
| 22 | + let showNutritionFilters = $state(false); |
| 23 | +
|
| 24 | + let activeNutritionCount = $derived( |
| 25 | + [data.minCalories, data.maxCalories, data.minProtein, data.maxProtein].filter(Boolean).length |
| 26 | + ); |
22 | 27 |
|
23 | 28 | const sortOptions = [ |
24 | 29 | { label: 'Name (A-Z)', value: '' }, |
|
68 | 73 | <div class="text-sm text-gray-500 dark:text-gray-400">{data.recipes.total} recipes</div> |
69 | 74 | </div> |
70 | 75 |
|
71 | | - <div class="flex flex-col md:flex-row md:flex-wrap gap-3"> |
72 | | - <div class="relative"> |
73 | | - <button |
74 | | - bind:this={categoriesBtnEl} |
75 | | - onclick={() => (showCategoriesDropdown = !showCategoriesDropdown)} |
76 | | - class="p-2 border rounded-sm hover:bg-gray-100 dark:hover:bg-gray-800 flex items-center gap-2 justify-between w-full md:w-auto" |
77 | | - > |
78 | | - <div class="whitespace-nowrap overflow-hidden text-left w-full md:w-60 2xl:w-72"> |
79 | | - {data.selectedCategories.join(', ')} |
80 | | - </div> |
81 | | - <Icon icon="ph:caret-down" color="#000" width="1.2rem" /> |
82 | | - </button> |
83 | | - {#if categoriesBtnEl} |
84 | | - <div |
85 | | - class="absolute left-0 right-0 w-full top-full bg-white dark:bg-gray-900 shadow-lg flex flex-col rounded-sm border z-10 max-h-96 overflow-auto" |
86 | | - class:hidden={!showCategoriesDropdown} |
87 | | - use:clickOutside={[categoriesBtnEl]} |
88 | | - onclickoutside={() => { |
89 | | - showCategoriesDropdown = false; |
90 | | - }} |
| 76 | + <div class="flex flex-col gap-3"> |
| 77 | + <div class="flex flex-col md:flex-row md:flex-wrap gap-3"> |
| 78 | + <div class="relative"> |
| 79 | + <button |
| 80 | + bind:this={categoriesBtnEl} |
| 81 | + onclick={() => (showCategoriesDropdown = !showCategoriesDropdown)} |
| 82 | + class="p-2 border rounded-sm hover:bg-gray-100 dark:hover:bg-gray-800 flex items-center gap-2 justify-between w-full md:w-auto" |
91 | 83 | > |
92 | | - {#each data.categories as category} |
93 | | - <a |
94 | | - href={`?${new URLSearchParams({ |
95 | | - ...Object.fromEntries($storePage.url.searchParams), |
96 | | - page: '1', |
97 | | - categories: ($storePage.url.searchParams.get('categories') || '') |
98 | | - .split(',') |
99 | | - .includes(category.slug) |
100 | | - ? ($storePage.url.searchParams.get('categories') || '') |
101 | | - .split(',') |
102 | | - .filter((c) => c !== category.slug) |
103 | | - .join(',') |
104 | | - : [ |
105 | | - ...($storePage.url.searchParams.get('categories') || '').split(','), |
106 | | - category.slug |
107 | | - ] |
108 | | - .filter((c) => c) |
109 | | - .join(',') |
110 | | - }) |
111 | | - .toString() |
112 | | - .trim()}`} |
113 | | - class={`flex justify-between items-center w-full p-3 hover:bg-gray-100 dark:hover:bg-gray-800 first:rounded-t last:rounded-b ${data.selectedCategoriesSlugs.includes(category.slug) ? ' bg-gray-100 dark:bg-gray-800' : ''}`} |
114 | | - > |
115 | | - <span>{category.name}</span> |
116 | | - <input |
117 | | - type="checkbox" |
118 | | - class="w-4 h-4 hover:cursor-pointer pointer-events-none" |
119 | | - checked={data.selectedCategoriesSlugs.includes(category.slug)} |
120 | | - /> |
121 | | - </a> |
122 | | - {/each} |
123 | | - </div> |
124 | | - {/if} |
125 | | - </div> |
| 84 | + <div class="whitespace-nowrap overflow-hidden text-left w-full md:w-60 2xl:w-72"> |
| 85 | + {data.selectedCategories.join(', ')} |
| 86 | + </div> |
| 87 | + <Icon icon="ph:caret-down" color="#000" width="1.2rem" /> |
| 88 | + </button> |
| 89 | + {#if categoriesBtnEl} |
| 90 | + <div |
| 91 | + class="absolute left-0 right-0 w-full top-full bg-white dark:bg-gray-900 shadow-lg flex flex-col rounded-sm border z-10 max-h-96 overflow-auto" |
| 92 | + class:hidden={!showCategoriesDropdown} |
| 93 | + use:clickOutside={[categoriesBtnEl]} |
| 94 | + onclickoutside={() => { |
| 95 | + showCategoriesDropdown = false; |
| 96 | + }} |
| 97 | + > |
| 98 | + {#each data.categories as category} |
| 99 | + <a |
| 100 | + href={`?${new URLSearchParams({ |
| 101 | + ...Object.fromEntries($storePage.url.searchParams), |
| 102 | + page: '1', |
| 103 | + categories: ($storePage.url.searchParams.get('categories') || '') |
| 104 | + .split(',') |
| 105 | + .includes(category.slug) |
| 106 | + ? ($storePage.url.searchParams.get('categories') || '') |
| 107 | + .split(',') |
| 108 | + .filter((c) => c !== category.slug) |
| 109 | + .join(',') |
| 110 | + : [ |
| 111 | + ...($storePage.url.searchParams.get('categories') || '').split(','), |
| 112 | + category.slug |
| 113 | + ] |
| 114 | + .filter((c) => c) |
| 115 | + .join(',') |
| 116 | + }) |
| 117 | + .toString() |
| 118 | + .trim()}`} |
| 119 | + class={`flex justify-between items-center w-full p-3 hover:bg-gray-100 dark:hover:bg-gray-800 first:rounded-t last:rounded-b ${data.selectedCategoriesSlugs.includes(category.slug) ? ' bg-gray-100 dark:bg-gray-800' : ''}`} |
| 120 | + > |
| 121 | + <span>{category.name}</span> |
| 122 | + <input |
| 123 | + type="checkbox" |
| 124 | + class="w-4 h-4 hover:cursor-pointer pointer-events-none" |
| 125 | + checked={data.selectedCategoriesSlugs.includes(category.slug)} |
| 126 | + /> |
| 127 | + </a> |
| 128 | + {/each} |
| 129 | + </div> |
| 130 | + {/if} |
| 131 | + </div> |
| 132 | + |
| 133 | + <div class="relative"> |
| 134 | + <button |
| 135 | + bind:this={tagsBtnEl} |
| 136 | + onclick={() => (showTagsDropdown = !showTagsDropdown)} |
| 137 | + class="p-2 border rounded-sm hover:bg-gray-100 dark:hover:bg-gray-800 flex items-center gap-2 justify-between w-full md:w-auto" |
| 138 | + > |
| 139 | + <div class="whitespace-nowrap overflow-hidden text-left w-full md:w-60 2xl:w-72"> |
| 140 | + {data.selectedTags.join(', ')} |
| 141 | + </div> |
| 142 | + <Icon icon="ph:caret-down" color="#000" width="1.2rem" /> |
| 143 | + </button> |
| 144 | + {#if tagsBtnEl} |
| 145 | + <div |
| 146 | + class="absolute left-0 right-0 w-full top-full bg-white dark:bg-gray-900 shadow-lg flex flex-col rounded-sm border z-10 max-h-96 overflow-auto" |
| 147 | + class:hidden={!showTagsDropdown} |
| 148 | + use:clickOutside={[tagsBtnEl]} |
| 149 | + onclickoutside={() => { |
| 150 | + showTagsDropdown = false; |
| 151 | + }} |
| 152 | + > |
| 153 | + {#each data.tags as tag} |
| 154 | + <a |
| 155 | + href={`?${new URLSearchParams({ |
| 156 | + ...Object.fromEntries($storePage.url.searchParams), |
| 157 | + page: '1', |
| 158 | + tags: ($storePage.url.searchParams.get('tags') || '') |
| 159 | + .split(',') |
| 160 | + .includes(tag.slug) |
| 161 | + ? ($storePage.url.searchParams.get('tags') || '') |
| 162 | + .split(',') |
| 163 | + .filter((c) => c !== tag.slug) |
| 164 | + .join(',') |
| 165 | + : [...($storePage.url.searchParams.get('tags') || '').split(','), tag.slug] |
| 166 | + .filter((c) => c) |
| 167 | + .join(',') |
| 168 | + }) |
| 169 | + .toString() |
| 170 | + .trim()}`} |
| 171 | + class={`flex justify-between items-center w-full p-3 hover:bg-gray-100 dark:hover:bg-gray-800 first:rounded-t last:rounded-b ${data.selectedTagsSlugs.includes(tag.slug) ? ' bg-gray-100 dark:bg-gray-800' : ''}`} |
| 172 | + > |
| 173 | + <span>{tag.name}</span> |
| 174 | + <input |
| 175 | + type="checkbox" |
| 176 | + class="w-4 h-4 hover:cursor-pointer pointer-events-none" |
| 177 | + checked={data.selectedTagsSlugs.includes(tag.slug)} |
| 178 | + /> |
| 179 | + </a> |
| 180 | + {/each} |
| 181 | + </div> |
| 182 | + {/if} |
| 183 | + </div> |
| 184 | + |
| 185 | + <select |
| 186 | + value={data.selectedSort} |
| 187 | + onchange={onSortChange} |
| 188 | + class="p-2 border rounded-sm hover:bg-gray-100 dark:hover:bg-gray-800 dark:bg-gray-900 bg-white" |
| 189 | + > |
| 190 | + {#each sortOptions as opt} |
| 191 | + <option value={opt.value}>{opt.label}</option> |
| 192 | + {/each} |
| 193 | + </select> |
126 | 194 |
|
127 | | - <div class="relative"> |
128 | 195 | <button |
129 | | - bind:this={tagsBtnEl} |
130 | | - onclick={() => (showTagsDropdown = !showTagsDropdown)} |
131 | | - class="p-2 border rounded-sm hover:bg-gray-100 dark:hover:bg-gray-800 flex items-center gap-2 justify-between w-full md:w-auto" |
| 196 | + onclick={() => (showNutritionFilters = !showNutritionFilters)} |
| 197 | + class="p-2 border rounded-sm hover:bg-gray-100 dark:hover:bg-gray-800 flex items-center gap-2 whitespace-nowrap" |
132 | 198 | > |
133 | | - <div class="whitespace-nowrap overflow-hidden text-left w-full md:w-60 2xl:w-72"> |
134 | | - {data.selectedTags.join(', ')} |
135 | | - </div> |
136 | | - <Icon icon="ph:caret-down" color="#000" width="1.2rem" /> |
| 199 | + <Icon icon="ph:funnel" width="1.2rem" /> |
| 200 | + Filters{#if activeNutritionCount > 0} <span |
| 201 | + class="bg-blue-500 text-white text-xs rounded-full px-1.5 py-0.5" |
| 202 | + >{activeNutritionCount}</span |
| 203 | + >{/if} |
137 | 204 | </button> |
138 | | - {#if tagsBtnEl} |
139 | | - <div |
140 | | - class="absolute left-0 right-0 w-full top-full bg-white dark:bg-gray-900 shadow-lg flex flex-col rounded-sm border z-10 max-h-96 overflow-auto" |
141 | | - class:hidden={!showTagsDropdown} |
142 | | - use:clickOutside={[tagsBtnEl]} |
143 | | - onclickoutside={() => { |
144 | | - showTagsDropdown = false; |
145 | | - }} |
146 | | - > |
147 | | - {#each data.tags as tag} |
148 | | - <a |
149 | | - href={`?${new URLSearchParams({ |
150 | | - ...Object.fromEntries($storePage.url.searchParams), |
151 | | - page: '1', |
152 | | - tags: ($storePage.url.searchParams.get('tags') || '') |
153 | | - .split(',') |
154 | | - .includes(tag.slug) |
155 | | - ? ($storePage.url.searchParams.get('tags') || '') |
156 | | - .split(',') |
157 | | - .filter((c) => c !== tag.slug) |
158 | | - .join(',') |
159 | | - : [...($storePage.url.searchParams.get('tags') || '').split(','), tag.slug] |
160 | | - .filter((c) => c) |
161 | | - .join(',') |
162 | | - }) |
163 | | - .toString() |
164 | | - .trim()}`} |
165 | | - class={`flex justify-between items-center w-full p-3 hover:bg-gray-100 dark:hover:bg-gray-800 first:rounded-t last:rounded-b ${data.selectedTagsSlugs.includes(tag.slug) ? ' bg-gray-100 dark:bg-gray-800' : ''}`} |
166 | | - > |
167 | | - <span>{tag.name}</span> |
168 | | - <input |
169 | | - type="checkbox" |
170 | | - class="w-4 h-4 hover:cursor-pointer pointer-events-none" |
171 | | - checked={data.selectedTagsSlugs.includes(tag.slug)} |
172 | | - /> |
173 | | - </a> |
174 | | - {/each} |
175 | | - </div> |
176 | | - {/if} |
177 | 205 | </div> |
178 | 206 |
|
179 | | - <select |
180 | | - value={data.selectedSort} |
181 | | - onchange={onSortChange} |
182 | | - class="p-2 border rounded-sm hover:bg-gray-100 dark:hover:bg-gray-800 dark:bg-gray-900 bg-white" |
183 | | - > |
184 | | - {#each sortOptions as opt} |
185 | | - <option value={opt.value}>{opt.label}</option> |
186 | | - {/each} |
187 | | - </select> |
188 | | - |
189 | | - <div class="flex items-center gap-2"> |
190 | | - <input |
191 | | - type="number" |
192 | | - placeholder="Min cal" |
193 | | - value={data.minCalories} |
194 | | - onchange={(e) => onNutritionChange('minCalories', e)} |
195 | | - class="p-2 border rounded-sm w-24 dark:bg-gray-900 bg-white" |
196 | | - min="0" |
197 | | - /> |
198 | | - <input |
199 | | - type="number" |
200 | | - placeholder="Max cal" |
201 | | - value={data.maxCalories} |
202 | | - onchange={(e) => onNutritionChange('maxCalories', e)} |
203 | | - class="p-2 border rounded-sm w-24 dark:bg-gray-900 bg-white" |
204 | | - min="0" |
205 | | - /> |
206 | | - </div> |
| 207 | + {#if showNutritionFilters} |
| 208 | + <div class="flex flex-wrap gap-3"> |
| 209 | + <div class="flex items-center gap-2"> |
| 210 | + <input |
| 211 | + type="number" |
| 212 | + placeholder="Min cal" |
| 213 | + value={data.minCalories} |
| 214 | + onchange={(e) => onNutritionChange('minCalories', e)} |
| 215 | + class="p-2 border rounded-sm w-24 dark:bg-gray-900 bg-white" |
| 216 | + min="0" |
| 217 | + /> |
| 218 | + <input |
| 219 | + type="number" |
| 220 | + placeholder="Max cal" |
| 221 | + value={data.maxCalories} |
| 222 | + onchange={(e) => onNutritionChange('maxCalories', e)} |
| 223 | + class="p-2 border rounded-sm w-24 dark:bg-gray-900 bg-white" |
| 224 | + min="0" |
| 225 | + /> |
| 226 | + </div> |
207 | 227 |
|
208 | | - <div class="flex items-center gap-2"> |
209 | | - <input |
210 | | - type="number" |
211 | | - placeholder="Min prot" |
212 | | - value={data.minProtein} |
213 | | - onchange={(e) => onNutritionChange('minProtein', e)} |
214 | | - class="p-2 border rounded-sm w-24 dark:bg-gray-900 bg-white" |
215 | | - min="0" |
216 | | - /> |
217 | | - <input |
218 | | - type="number" |
219 | | - placeholder="Max prot" |
220 | | - value={data.maxProtein} |
221 | | - onchange={(e) => onNutritionChange('maxProtein', e)} |
222 | | - class="p-2 border rounded-sm w-24 dark:bg-gray-900 bg-white" |
223 | | - min="0" |
224 | | - /> |
225 | | - </div> |
| 228 | + <div class="flex items-center gap-2"> |
| 229 | + <input |
| 230 | + type="number" |
| 231 | + placeholder="Min prot" |
| 232 | + value={data.minProtein} |
| 233 | + onchange={(e) => onNutritionChange('minProtein', e)} |
| 234 | + class="p-2 border rounded-sm w-24 dark:bg-gray-900 bg-white" |
| 235 | + min="0" |
| 236 | + /> |
| 237 | + <input |
| 238 | + type="number" |
| 239 | + placeholder="Max prot" |
| 240 | + value={data.maxProtein} |
| 241 | + onchange={(e) => onNutritionChange('maxProtein', e)} |
| 242 | + class="p-2 border rounded-sm w-24 dark:bg-gray-900 bg-white" |
| 243 | + min="0" |
| 244 | + /> |
| 245 | + </div> |
| 246 | + </div> |
| 247 | + {/if} |
226 | 248 | </div> |
227 | 249 | </div> |
228 | 250 |
|
|
0 commit comments