mirror of
https://github.com/cheeaun/phanpy.git
synced 2025-01-23 00:56:23 +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);
|
||||
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);
|
||||
|
|
|
@ -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
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 { 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 = [];
|
||||
|
|
|
@ -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
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