Experimental hidden search page

And refactored out some reusable components
This commit is contained in:
Lim Chee Aun 2023-02-10 22:10:13 +08:00
parent 541d318fdc
commit e0e236bd26
7 changed files with 208 additions and 52 deletions

View file

@ -106,8 +106,10 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
border-bottom: var(--hairline-width) solid var(--divider-color);
min-height: 3em;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-columns: 1fr 2fr 1fr;
align-items: center;
text-overflow: ellipsis;
white-space: nowrap;
}
.deck > header .header-grid > .header-side:last-of-type {
text-align: right;
@ -121,7 +123,6 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
padding: 0;
font-size: 1.2em;
text-align: center;
white-space: nowrap;
}
.deck > header .header-grid h1:first-child {
text-align: left;
@ -1252,7 +1253,7 @@ meter.donut:is(.danger, .explode):after {
.updates-button {
margin-top: 24px;
}
.timeline-deck .timeline:not(.flat) > li {
.timeline:not(.flat) > li {
border: 1px solid var(--divider-color);
margin: 16px 0;
background-color: var(--bg-color);
@ -1262,15 +1263,13 @@ meter.donut:is(.danger, .explode):after {
transition: transform 0.4s var(--timing-function);
--back-transition: transform 0.4s ease-out;
}
.timeline-deck .timeline:not(.flat) > li:has(.status-link.is-active) {
.timeline:not(.flat) > li:has(.status-link.is-active) {
transition: var(--back-transition);
transform: translate3d(-2.5vw, 0, 0);
}
.timeline-deck
.timeline:not(.flat)
.timeline:not(.flat)
> li:not(:has(.boost-carousel)):has(+ li .status-link.is-active),
.timeline-deck
.timeline:not(.flat)
.timeline:not(.flat)
> li:not(:has(.boost-carousel)):has(.status-link.is-active)
+ li {
transition: var(--back-transition);

View file

@ -39,6 +39,7 @@ import Lists from './pages/lists';
import Login from './pages/login';
import Notifications from './pages/notifications';
import Public from './pages/public';
import Search from './pages/search';
import Settings from './pages/settings';
import Status from './pages/status';
import Welcome from './pages/welcome';
@ -215,7 +216,7 @@ function App() {
<Route index element={<Public />} />
<Route path="l" element={<Public local />} />
</Route>
{/* <Route path="/:instance?/p/l?" element={<Public />} /> */}
<Route path="/:instance?/search" element={<Search />} />
{/* <Route path="/:anything" element={<NotFound />} /> */}
</Routes>
<Routes>

55
src/components/menu.jsx Normal file
View file

@ -0,0 +1,55 @@
import { FocusableItem, Menu, MenuDivider, MenuItem } from '@szhsin/react-menu';
import states from '../utils/states';
import Icon from './icon';
import Link from './link';
function NavMenu(props) {
return (
<Menu
{...props}
menuButton={
<button type="button" class="button plain">
<Icon icon="menu" size="l" />
</button>
}
>
<MenuLink to="/">
<Icon icon="home" size="l" /> <span>Home</span>
</MenuLink>
<MenuLink to="/b">
<Icon icon="bookmark" size="l" /> <span>Bookmarks</span>
</MenuLink>
<MenuLink to="/f">
<Icon icon="heart" size="l" /> <span>Favourites</span>
</MenuLink>
<MenuDivider />
<MenuItem
onClick={() => {
states.showSettings = true;
}}
>
<Icon icon="gear" size="l" alt="Settings" /> <span>Settings</span>
</MenuItem>
</Menu>
);
}
function MenuLink(props) {
return (
<FocusableItem>
{({ ref, closeMenu }) => (
<Link
{...props}
ref={ref}
onClick={({ detail }) =>
closeMenu(detail === 0 ? 'Enter' : undefined)
}
/>
)}
</FocusableItem>
);
}
export default NavMenu;

View file

@ -1,9 +1,7 @@
import { FocusableItem, Menu, MenuDivider, MenuItem } from '@szhsin/react-menu';
import { useEffect, useRef, useState } from 'preact/hooks';
import { useHotkeys } from 'react-hotkeys-hook';
import { useDebouncedCallback } from 'use-debounce';
import states from '../utils/states';
import useInterval from '../utils/useInterval';
import usePageVisibility from '../utils/usePageVisibility';
import useScroll from '../utils/useScroll';
@ -11,6 +9,7 @@ import useScroll from '../utils/useScroll';
import Icon from './icon';
import Link from './link';
import Loader from './loader';
import Menu from './menu';
import Status from './status';
function Timeline({
@ -257,31 +256,10 @@ function Timeline({
<div class="header-grid">
<div class="header-side">
<Menu
menuButton={
<button type="button" class="button plain">
<Icon icon="menu" size="l" />
</button>
}
>
<MenuLink to="/">
<Icon icon="home" size="l" /> <span>Home</span>
</MenuLink>
<MenuLink to="/b">
<Icon icon="bookmark" size="l" /> <span>Bookmarks</span>
</MenuLink>
<MenuLink to="/f">
<Icon icon="heart" size="l" /> <span>Favourites</span>
</MenuLink>
<MenuDivider />
<MenuItem
onClick={() => {
states.showSettings = true;
}}
>
<Icon icon="gear" size="l" alt="Settings" />{' '}
<span>Settings</span>
</MenuItem>
</Menu>
portal={{
target: scrollableRef.current,
}}
/>
{headerStart !== null && headerStart !== undefined ? (
headerStart
) : (
@ -409,22 +387,6 @@ function Timeline({
);
}
function MenuLink(props) {
return (
<FocusableItem>
{({ ref, closeMenu }) => (
<Link
{...props}
ref={ref}
onClick={({ detail }) =>
closeMenu(detail === 0 ? 'Enter' : undefined)
}
/>
)}
</FocusableItem>
);
}
function groupBoosts(values) {
let newValues = [];
let boostStash = [];

View file

@ -8,6 +8,7 @@ import Avatar from '../components/avatar';
import Icon from '../components/icon';
import Link from '../components/link';
import Loader from '../components/loader';
import Menu from '../components/menu';
import NameText from '../components/name-text';
import RelativeTime from '../components/relative-time';
import Status from '../components/status';
@ -145,6 +146,11 @@ function Notifications() {
>
<div class="header-grid">
<div class="header-side">
<Menu
portal={{
target: scrollableRef.current,
}}
/>
<Link to="/" class="button plain">
<Icon icon="home" size="l" />
</Link>

29
src/pages/search.css Normal file
View file

@ -0,0 +1,29 @@
#search-page header input {
width: 100%;
padding: 8px 16px;
border: 0;
border-radius: 999px;
background-color: var(--bg-faded-color);
}
#search-page header input:focus {
background-color: var(--bg-color);
outline: 2px solid var(--link-color);
}
#search-page ul.accounts-list {
display: flex;
flex-wrap: wrap;
}
#search-page ul.accounts-list li {
flex-basis: 320px;
display: flex;
padding: 8px 16px;
gap: 8px;
align-items: center;
}
@media (min-width: 40em) {
#search-page header input {
background-color: var(--bg-color);
}
}

104
src/pages/search.jsx Normal file
View file

@ -0,0 +1,104 @@
import './search.css';
import { useEffect, useRef, useState } from 'preact/hooks';
import { useSearchParams } from 'react-router-dom';
import Avatar from '../components/avatar';
import Link from '../components/link';
import Menu from '../components/menu';
import NameText from '../components/name-text';
import Status from '../components/status';
import { api } from '../utils/api';
function Search() {
const { masto, instance, authenticated } = api();
const [searchParams, setSearchParams] = useSearchParams();
const searchFieldRef = useRef();
const q = searchParams.get('q');
const [statusResults, setStatusResults] = useState([]);
const [accountResults, setAccountResults] = useState([]);
useEffect(() => {
if (q) {
searchFieldRef.current.value = q;
(async () => {
const results = await masto.v2.search({
q,
limit: 20,
resolve: authenticated,
});
console.log(results);
setStatusResults(results.statuses);
setAccountResults(results.accounts);
})();
}
}, [q]);
console.log({ accountResults });
return (
<div id="search-page" class="deck-container">
<div class="timeline-deck deck">
<header>
<div class="header-grid">
<div class="header-side">
<Menu />
</div>
<form
onSubmit={(e) => {
e.preventDefault();
const { q } = e.target;
if (q.value) {
setSearchParams({ q: q.value });
}
}}
>
<input
ref={searchFieldRef}
name="q"
type="search"
autofocus
placeholder="Search or paste URL"
/>
</form>
<div class="header-side" />
</div>
</header>
<main>
<h2 class="timeline-header">Accounts</h2>
{accountResults.length > 0 && (
<ul class="timeline flat accounts-list">
{accountResults.map((account) => (
<li>
<Avatar url={account.avatar} size="xl" />
<NameText account={account} instance={instance} showAcct />
</li>
))}
</ul>
)}
<h2 class="timeline-header">Posts</h2>
{statusResults.length > 0 && (
<ul class="timeline">
{statusResults.map((status) => (
<li>
<Link
class="status-link"
to={
instance
? `/${instance}/s/${status.id}`
: `/s/${status.id}`
}
>
<Status status={status} />
</Link>
</li>
))}
</ul>
)}
</main>
</div>
</div>
);
}
export default Search;