schon/storefront/app/components/ui/search.vue
Alexandr SaVBaD Waltz 556354a44d feat(storefront): overhaul theming system and unify SCSS variables
Revamped the theming system with new SCSS variables for consistent styling across light and dark themes. Replaced static color values with dynamic variables for maintainability and improved theme adaptability. Updated components and layouts to use the new variables.

- Moved theme plugin logic for optimized handling of theme cookies and attributes.
- Enhanced `useThemes` composable for simplified client-side updates and SSR support.
- Replaced redundant SCSS color definitions with centralized variables.
- Improved page structure by introducing `ui-title` for reusable section headers.
- Unified transitions and border-radius for consistent design language.

Breaking Changes:
Theming system restructured—migrate to `$main`, `$primary`, and related variables for SCSS colors. Remove usage of `--color-*` variables in templates and styles.
2026-03-01 20:16:05 +03:00

297 lines
No EOL
6.3 KiB
Vue

<template>
<div class="search">
<div class="container">
<div class="search__inner">
<div
@click="toggleSearch(true)"
class="search__wrapper"
:class="[{ active: isSearchActive }]"
>
<form class="search__form" @submit.prevent="submitSearch">
<input
type="text"
v-model="query"
:placeholder="t('fields.search')"
inputmode="search"
/>
<div class="search__tools">
<button
type="button"
@click="clearSearch"
v-if="query"
>
<icon name="gridicons:cross" size="16" />
</button>
<div class="search__tools-line" v-if="query"></div>
<button type="submit">
<icon name="tabler:search" size="16" />
</button>
</div>
</form>
<div class="search__results" :class="[{ active: (searchResults && isSearchActive) || loading }]">
<skeletons-header-search v-if="loading" />
<div
class="search__results-inner"
v-for="(blocks, category) in filteredSearchResults"
:key="category"
>
<div class="search__results-title">
<p>{{ getBlockTitle(category) }}:</p>
</div>
<div
class="search__item"
v-for="item in blocks"
:key="item.uuid"
@click.stop="goTo(category, item)"
>
<div class="search__item-left">
<icon name="ic:twotone-search" size="18" />
<p>{{ item.name }}</p>
</div>
<icon name="line-md:external-link" size="18" />
</div>
</div>
<div class="search__results-empty" v-if="!hasResults && query && !loading">
<p>{{ t('header.search.empty') }}</p>
</div>
</div>
</div>
<transition name="opacity" mode="out-in">
<div
class="search__bg"
@click="toggleSearch(false)"
v-if="isSearchActive"
/>
</transition>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useSearchUI } from '@composables/search';
const {t} = useI18n();
const router = useRouter();
const appStore = useAppStore();
const {
query,
isSearchActive,
loading,
searchResults,
filteredSearchResults,
hasResults,
getBlockTitle,
clearSearch,
toggleSearch
} = useSearchUI();
function submitSearch() {
if (query.value) {
router.push({
path: '/search',
query: { q: query.value }
})
toggleSearch(false);
}
}
watch(
() => isSearchActive.value,
(state) => {
appStore.setOverflowHidden(state);
},
{ immediate: true }
);
function goTo(category: string, item: any) {
let path = "/";
switch (category) {
case "products": {
path = `/product/${item.slug}`;
break;
}
case "categories": {
path = `/catalog/${item.slug}`;
break;
}
case "brands": {
path = `/brand/${item.slug}`;
break;
}
case "posts": {
path = "/";
break;
}
}
toggleSearch(false);
router.push(path);
}
</script>
<style lang="scss" scoped>
.search {
width: 100%;
background-color: $main;
&__inner {
padding-block: 10px;
width: 100%;
position: relative;
z-index: 1;
height: 100%;
}
&__bg {
background-color: rgba(0, 0, 0, 0.2);
height: 100vh;
left: 0;
position: fixed;
top: 0;
width: 100vw;
z-index: 1;
}
&__wrapper {
width: 100%;
background-color: $border;
border-radius: $less_border_radius;
position: relative;
z-index: 2;
&.active {
background-color: $main;
box-shadow: 0 0 0 1px #0000000a,0 4px 4px #0000000a,0 20px 40px #00000014;
& .search__wrapper {
border-radius: $less_border_radius $less_border_radius 0 0;
}
& .search__form input {
border-radius: $less_border_radius $less_border_radius 0 0;
}
}
@include hover {
background-color: $main;
box-shadow: 0 0 0 1px #0000000a,0 4px 4px #0000000a,0 20px 40px #00000014;
}
}
&__form {
width: 100%;
height: 40px;
position: relative;
& input {
background-color: transparent;
width: 100%;
height: 100%;
padding-inline: 20px 150px;
border: 1px solid $link_secondary;
border-radius: $less_border_radius;
color: $secondary;
font-size: 16px;
font-weight: 500;
}
}
&__tools {
position: absolute;
right: 20px;
top: 50%;
transform: translateY(-50%);
display: flex;
align-items: center;
gap: 10px;
& button {
cursor: pointer;
display: grid;
place-items: center;
border-radius: $less_border_radius;
padding: 5px 12px;
border: 1px solid $primary;
background-color: transparent;
font-size: 12px;
color: $primary;
@include hover {
background-color: $primary;
color: $main;
}
}
&-line {
background-color: $primary;
height: 15px;
width: 1px;
}
}
&__results {
position: absolute;
z-index: 1;
top: 100%;
width: 100%;
max-height: 0;
overflow: auto;
background-color: $main;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.0392156863), 0 4px 4px rgba(0, 0, 0, 0.0392156863), 0 20px 40px rgba(0, 0, 0, 0.0784313725);
&.active {
max-height: 40vh;
}
&-title {
background-color: rgba($primary, 0.2);
padding: 7px 20px;
font-weight: 600;
}
&-empty {
padding: 10px 20px;
font-size: 14px;
}
}
&__item {
cursor: pointer;
padding: 7px 20px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 30px;
font-size: 14px;
@include hover {
background-color: $main_hover;
}
&-left {
display: flex;
align-items: center;
gap: 15px;
}
& p {
word-break: break-all;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
overflow: hidden;
}
& span {
color: $text;
}
}
}
</style>