Added search component
This commit is contained in:
parent
b64aa7eaa7
commit
e80a9ec7e7
|
@ -71,6 +71,7 @@
|
||||||
|
|
||||||
color: #9fa1a7;
|
color: #9fa1a7;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
|
||||||
@include media-breakpoint-down(md) {
|
@include media-breakpoint-down(md) {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
.topicHeader {
|
.topicHeader {
|
||||||
@include font-size(1.7rem);
|
@include font-size(1.7rem);
|
||||||
|
|
||||||
padding-top: 2.8rem;
|
padding: 2.8rem $container-padding-x 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
|
@ -24,7 +24,8 @@
|
||||||
color: #fff;
|
color: #fff;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
margin: 0 1.2rem;
|
margin: 0 1.2rem 1em;
|
||||||
padding: 0.8rem 1.6rem;
|
padding: 0.8rem 1.6rem;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ export const FullTopic = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<div class={styles.topicFull}>
|
<div class={styles.topicFull}>
|
||||||
<Show when={!!props.topic?.slug}>
|
<Show when={!!props.topic?.slug}>
|
||||||
<div class={clsx(styles.topicHeader, 'col-md-8 offset-md-2')}>
|
<div class={clsx(styles.topicHeader, 'col-md-8 col-lg-6 offset-md-2 offset-lg-3')}>
|
||||||
<h1>#{props.topic.title}</h1>
|
<h1>#{props.topic.title}</h1>
|
||||||
<p>{props.topic.body}</p>
|
<p>{props.topic.body}</p>
|
||||||
<div class={clsx(styles.topicActions)}>
|
<div class={clsx(styles.topicActions)}>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { createEffect, createMemo, createSignal, For, Show } from 'solid-js'
|
import { createEffect, createMemo, createSignal, For, Show } from 'solid-js'
|
||||||
import type { Topic } from '../../graphql/types.gen'
|
import type { Topic } from '../../graphql/types.gen'
|
||||||
import { Icon } from '../_shared/Icon'
|
|
||||||
import { t } from '../../utils/intl'
|
import { t } from '../../utils/intl'
|
||||||
import { setTopicsSort, useTopicsStore } from '../../stores/zine/topics'
|
import { setTopicsSort, useTopicsStore } from '../../stores/zine/topics'
|
||||||
import { useRouter } from '../../stores/router'
|
import { useRouter } from '../../stores/router'
|
||||||
|
@ -10,6 +9,7 @@ import { useSession } from '../../context/session'
|
||||||
import { locale } from '../../stores/ui'
|
import { locale } from '../../stores/ui'
|
||||||
import { translit } from '../../utils/ru2en'
|
import { translit } from '../../utils/ru2en'
|
||||||
import styles from '../../styles/AllTopics.module.scss'
|
import styles from '../../styles/AllTopics.module.scss'
|
||||||
|
import { SearchField } from '../_shared/SearchField'
|
||||||
|
|
||||||
type AllTopicsPageSearchParams = {
|
type AllTopicsPageSearchParams = {
|
||||||
by: 'shouts' | 'authors' | 'title' | ''
|
by: 'shouts' | 'authors' | 'title' | ''
|
||||||
|
@ -24,6 +24,42 @@ const PAGE_SIZE = 20
|
||||||
export const AllTopicsView = (props: AllTopicsViewProps) => {
|
export const AllTopicsView = (props: AllTopicsViewProps) => {
|
||||||
const { searchParams, changeSearchParam } = useRouter<AllTopicsPageSearchParams>()
|
const { searchParams, changeSearchParam } = useRouter<AllTopicsPageSearchParams>()
|
||||||
const [limit, setLimit] = createSignal(PAGE_SIZE)
|
const [limit, setLimit] = createSignal(PAGE_SIZE)
|
||||||
|
const ALPHABET = [
|
||||||
|
'#',
|
||||||
|
'А',
|
||||||
|
'Б',
|
||||||
|
'В',
|
||||||
|
'Г',
|
||||||
|
'Д',
|
||||||
|
'Е',
|
||||||
|
'Ё',
|
||||||
|
'Ж',
|
||||||
|
'З',
|
||||||
|
'И',
|
||||||
|
'Й',
|
||||||
|
'К',
|
||||||
|
'Л',
|
||||||
|
'М',
|
||||||
|
'Н',
|
||||||
|
'О',
|
||||||
|
'П',
|
||||||
|
'Р',
|
||||||
|
'С',
|
||||||
|
'Т',
|
||||||
|
'У',
|
||||||
|
'Ф',
|
||||||
|
'Х',
|
||||||
|
'Ц',
|
||||||
|
'Ч',
|
||||||
|
'Ш',
|
||||||
|
'Щ',
|
||||||
|
'Ъ',
|
||||||
|
'Ы',
|
||||||
|
'Ь',
|
||||||
|
'Э',
|
||||||
|
'Ю',
|
||||||
|
'Я'
|
||||||
|
]
|
||||||
|
|
||||||
const { sortedTopics } = useTopicsStore({
|
const { sortedTopics } = useTopicsStore({
|
||||||
topics: props.topics,
|
topics: props.topics,
|
||||||
|
@ -56,12 +92,11 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
|
||||||
const subscribed = (s) => Boolean(session()?.news?.topics && session()?.news?.topics?.includes(s || ''))
|
const subscribed = (s) => Boolean(session()?.news?.topics && session()?.news?.topics?.includes(s || ''))
|
||||||
|
|
||||||
const showMore = () => setLimit((oldLimit) => oldLimit + PAGE_SIZE)
|
const showMore = () => setLimit((oldLimit) => oldLimit + PAGE_SIZE)
|
||||||
let searchEl: HTMLInputElement
|
|
||||||
const [searchResults, setSearchResults] = createSignal<Topic[]>([])
|
const [searchResults, setSearchResults] = createSignal<Topic[]>([])
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
const searchTopics = () => {
|
const searchTopics = (value) => {
|
||||||
/* very stupid search algorithm with no deps */
|
/* very stupid search algorithm with no deps */
|
||||||
let q = searchEl.value.toLowerCase()
|
let q = value.toLowerCase()
|
||||||
if (q.length > 0) {
|
if (q.length > 0) {
|
||||||
console.debug(q)
|
console.debug(q)
|
||||||
setSearchResults([])
|
setSearchResults([])
|
||||||
|
@ -106,16 +141,8 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
|
||||||
<li classList={{ selected: searchParams().by === 'title' }}>
|
<li classList={{ selected: searchParams().by === 'title' }}>
|
||||||
<a href="/topics?by=title">{t('By alphabet')}</a>
|
<a href="/topics?by=title">{t('By alphabet')}</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="search-switcher">
|
<li class="view-switcher__search">
|
||||||
<Icon name="search" />
|
<SearchField onChange={searchTopics} />
|
||||||
<input
|
|
||||||
class="search-input"
|
|
||||||
ref={searchEl}
|
|
||||||
onChange={searchTopics}
|
|
||||||
onInput={searchTopics}
|
|
||||||
onFocus={() => (searchEl.innerHTML = '')}
|
|
||||||
placeholder={t('Search')}
|
|
||||||
/>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -123,15 +150,32 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
<div class={clsx(styles.allTopicsPage, 'container')}>
|
<div class={clsx(styles.allTopicsPage, 'container')}>
|
||||||
|
<div class="shift-content">
|
||||||
<AllTopicsHead />
|
<AllTopicsHead />
|
||||||
|
|
||||||
<div class="shift-content">
|
|
||||||
<Show when={sortedTopics().length > 0 || searchResults().length > 0}>
|
<Show when={sortedTopics().length > 0 || searchResults().length > 0}>
|
||||||
<Show when={searchParams().by === 'title'}>
|
<Show when={searchParams().by === 'title'}>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-10 col-xl-9">
|
||||||
|
<ul class={clsx('nodash', styles.alphabet)}>
|
||||||
|
<For each={ALPHABET}>
|
||||||
|
{(letter, index) => (
|
||||||
|
<li>
|
||||||
|
<Show when={sortedKeys().includes(letter)}>
|
||||||
|
<a href={`#letter-${index()}`}>{letter}</a>
|
||||||
|
</Show>
|
||||||
|
<Show when={!sortedKeys().includes(letter)}>{letter}</Show>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<For each={sortedKeys()}>
|
<For each={sortedKeys()}>
|
||||||
{(letter) => (
|
{(letter, index) => (
|
||||||
<div class={clsx(styles.group, 'group')}>
|
<div class={clsx(styles.group, 'group')}>
|
||||||
<h2>{letter}</h2>
|
<h2 id={`letter-${index()}`}>{letter}</h2>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-10">
|
<div class="col-lg-10">
|
||||||
|
|
22
src/components/_shared/SearchField.module.scss
Normal file
22
src/components/_shared/SearchField.module.scss
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
.searchField {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
input {
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
flex: 1 60%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 2rem;
|
||||||
|
}
|
||||||
|
}
|
26
src/components/_shared/SearchField.tsx
Normal file
26
src/components/_shared/SearchField.tsx
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import styles from './SearchField.module.scss'
|
||||||
|
import { Icon } from './Icon'
|
||||||
|
import { t } from '../../utils/intl'
|
||||||
|
|
||||||
|
type SearchFieldProps = {
|
||||||
|
onChange: (value: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SearchField = (props: SearchFieldProps) => {
|
||||||
|
const handleInputChange = (event) => props.onChange(event.target.value)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class={styles.searchField}>
|
||||||
|
<label for="search-field">
|
||||||
|
<Icon name="search" class={styles.icon} />
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="search-field"
|
||||||
|
type="text"
|
||||||
|
class="search-input"
|
||||||
|
onInput={handleInputChange}
|
||||||
|
placeholder={t('Search')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -52,3 +52,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.alphabet {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 1.5em -3% 0 0;
|
||||||
|
|
||||||
|
@include font-size(1.5rem);
|
||||||
|
|
||||||
|
li {
|
||||||
|
min-width: 1.5em;
|
||||||
|
margin-right: 3%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
.groupControls {
|
.groupControls {
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
margin-bottom: 4rem;
|
margin-bottom: 4rem;
|
||||||
margin-top: 7rem;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.floorImportant {
|
.floorImportant {
|
||||||
|
|
|
@ -523,6 +523,7 @@ figcaption {
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-switcher__search {
|
.view-switcher__search {
|
||||||
|
flex: 1 100%;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user