mirror of
https://github.com/cheeaun/phanpy.git
synced 2025-02-02 14:16:39 +01:00
Experimental hidden search page
And refactored out some reusable components
This commit is contained in:
parent
541d318fdc
commit
e0e236bd26
7 changed files with 208 additions and 52 deletions
15
src/app.css
15
src/app.css
|
@ -106,8 +106,10 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
||||||
border-bottom: var(--hairline-width) solid var(--divider-color);
|
border-bottom: var(--hairline-width) solid var(--divider-color);
|
||||||
min-height: 3em;
|
min-height: 3em;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
grid-template-columns: 1fr 2fr 1fr;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.deck > header .header-grid > .header-side:last-of-type {
|
.deck > header .header-grid > .header-side:last-of-type {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
@ -121,7 +123,6 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
.deck > header .header-grid h1:first-child {
|
.deck > header .header-grid h1:first-child {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
@ -1252,7 +1253,7 @@ meter.donut:is(.danger, .explode):after {
|
||||||
.updates-button {
|
.updates-button {
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
}
|
}
|
||||||
.timeline-deck .timeline:not(.flat) > li {
|
.timeline:not(.flat) > li {
|
||||||
border: 1px solid var(--divider-color);
|
border: 1px solid var(--divider-color);
|
||||||
margin: 16px 0;
|
margin: 16px 0;
|
||||||
background-color: var(--bg-color);
|
background-color: var(--bg-color);
|
||||||
|
@ -1262,15 +1263,13 @@ meter.donut:is(.danger, .explode):after {
|
||||||
transition: transform 0.4s var(--timing-function);
|
transition: transform 0.4s var(--timing-function);
|
||||||
--back-transition: transform 0.4s ease-out;
|
--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);
|
transition: var(--back-transition);
|
||||||
transform: translate3d(-2.5vw, 0, 0);
|
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),
|
> 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:not(:has(.boost-carousel)):has(.status-link.is-active)
|
||||||
+ li {
|
+ li {
|
||||||
transition: var(--back-transition);
|
transition: var(--back-transition);
|
||||||
|
|
|
@ -39,6 +39,7 @@ import Lists from './pages/lists';
|
||||||
import Login from './pages/login';
|
import Login from './pages/login';
|
||||||
import Notifications from './pages/notifications';
|
import Notifications from './pages/notifications';
|
||||||
import Public from './pages/public';
|
import Public from './pages/public';
|
||||||
|
import Search from './pages/search';
|
||||||
import Settings from './pages/settings';
|
import Settings from './pages/settings';
|
||||||
import Status from './pages/status';
|
import Status from './pages/status';
|
||||||
import Welcome from './pages/welcome';
|
import Welcome from './pages/welcome';
|
||||||
|
@ -215,7 +216,7 @@ function App() {
|
||||||
<Route index element={<Public />} />
|
<Route index element={<Public />} />
|
||||||
<Route path="l" element={<Public local />} />
|
<Route path="l" element={<Public local />} />
|
||||||
</Route>
|
</Route>
|
||||||
{/* <Route path="/:instance?/p/l?" element={<Public />} /> */}
|
<Route path="/:instance?/search" element={<Search />} />
|
||||||
{/* <Route path="/:anything" element={<NotFound />} /> */}
|
{/* <Route path="/:anything" element={<NotFound />} /> */}
|
||||||
</Routes>
|
</Routes>
|
||||||
<Routes>
|
<Routes>
|
||||||
|
|
55
src/components/menu.jsx
Normal file
55
src/components/menu.jsx
Normal 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;
|
|
@ -1,9 +1,7 @@
|
||||||
import { FocusableItem, Menu, MenuDivider, MenuItem } from '@szhsin/react-menu';
|
|
||||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useDebouncedCallback } from 'use-debounce';
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
|
|
||||||
import states from '../utils/states';
|
|
||||||
import useInterval from '../utils/useInterval';
|
import useInterval from '../utils/useInterval';
|
||||||
import usePageVisibility from '../utils/usePageVisibility';
|
import usePageVisibility from '../utils/usePageVisibility';
|
||||||
import useScroll from '../utils/useScroll';
|
import useScroll from '../utils/useScroll';
|
||||||
|
@ -11,6 +9,7 @@ import useScroll from '../utils/useScroll';
|
||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
import Link from './link';
|
import Link from './link';
|
||||||
import Loader from './loader';
|
import Loader from './loader';
|
||||||
|
import Menu from './menu';
|
||||||
import Status from './status';
|
import Status from './status';
|
||||||
|
|
||||||
function Timeline({
|
function Timeline({
|
||||||
|
@ -257,31 +256,10 @@ function Timeline({
|
||||||
<div class="header-grid">
|
<div class="header-grid">
|
||||||
<div class="header-side">
|
<div class="header-side">
|
||||||
<Menu
|
<Menu
|
||||||
menuButton={
|
portal={{
|
||||||
<button type="button" class="button plain">
|
target: scrollableRef.current,
|
||||||
<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>
|
|
||||||
{headerStart !== null && headerStart !== undefined ? (
|
{headerStart !== null && headerStart !== undefined ? (
|
||||||
headerStart
|
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) {
|
function groupBoosts(values) {
|
||||||
let newValues = [];
|
let newValues = [];
|
||||||
let boostStash = [];
|
let boostStash = [];
|
||||||
|
|
|
@ -8,6 +8,7 @@ import Avatar from '../components/avatar';
|
||||||
import Icon from '../components/icon';
|
import Icon from '../components/icon';
|
||||||
import Link from '../components/link';
|
import Link from '../components/link';
|
||||||
import Loader from '../components/loader';
|
import Loader from '../components/loader';
|
||||||
|
import Menu from '../components/menu';
|
||||||
import NameText from '../components/name-text';
|
import NameText from '../components/name-text';
|
||||||
import RelativeTime from '../components/relative-time';
|
import RelativeTime from '../components/relative-time';
|
||||||
import Status from '../components/status';
|
import Status from '../components/status';
|
||||||
|
@ -145,6 +146,11 @@ function Notifications() {
|
||||||
>
|
>
|
||||||
<div class="header-grid">
|
<div class="header-grid">
|
||||||
<div class="header-side">
|
<div class="header-side">
|
||||||
|
<Menu
|
||||||
|
portal={{
|
||||||
|
target: scrollableRef.current,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Link to="/" class="button plain">
|
<Link to="/" class="button plain">
|
||||||
<Icon icon="home" size="l" />
|
<Icon icon="home" size="l" />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
29
src/pages/search.css
Normal file
29
src/pages/search.css
Normal 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
104
src/pages/search.jsx
Normal 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;
|
Loading…
Reference in a new issue