update navigation design

This commit is contained in:
Ren Amamiya
2023-05-02 21:23:00 +07:00
parent 90bcabf27b
commit e86cd16ba4
15 changed files with 134 additions and 152 deletions

View File

@@ -82,13 +82,13 @@ export default function ChannelCreateModal() {
<button
type="button"
onClick={() => openModal()}
className="group inline-flex items-center gap-2 rounded-md px-2.5 py-1.5 hover:bg-zinc-900"
className="group inline-flex h-8 items-center gap-2.5 rounded-md px-2.5 hover:bg-zinc-900"
>
<div className="inline-flex h-5 w-5 shrink items-center justify-center rounded bg-zinc-900 group-hover:bg-zinc-800">
<PlusIcon width={12} height={12} className="text-zinc-500" />
</div>
<div>
<h5 className="text-sm font-medium text-zinc-500 group-hover:text-zinc-400">Add a new channel</h5>
<h5 className="text-[13px] font-semibold text-zinc-500 group-hover:text-zinc-400">Add a new channel</h5>
</div>
</button>
<Transition appear show={isOpen} as={Fragment}>

View File

@@ -1,4 +1,3 @@
import { DEFAULT_AVATAR } from '@lume/stores/constants';
import { useChannelProfile } from '@lume/utils/hooks/useChannelProfile';
import { usePageContext } from '@lume/utils/hooks/usePageContext';
@@ -15,19 +14,15 @@ export default function ChannelsListItem({ data }: { data: any }) {
<a
href={`/app/channel?id=${data.event_id}&pubkey=${data.pubkey}`}
className={twMerge(
'inline-flex items-center gap-2 rounded-md px-2.5 py-1.5 hover:bg-zinc-900',
'group inline-flex h-8 items-center gap-2.5 rounded-md px-2.5 hover:bg-zinc-900',
pageID === data.event_id ? 'dark:bg-zinc-900 dark:text-zinc-100 hover:dark:bg-zinc-800' : ''
)}
>
<div className="relative h-5 w-5 shrink-0 rounded">
<img
src={channel?.picture || DEFAULT_AVATAR}
alt={data.event_id}
className="h-5 w-5 rounded bg-white object-cover"
/>
<div className="inline-flex h-5 w-5 items-center justify-center rounded bg-zinc-900 group-hover:bg-zinc-800">
<span className="text-xs text-zinc-200">#</span>
</div>
<div>
<h5 className="truncate text-sm font-medium text-zinc-400">{channel?.name}</h5>
<h5 className="truncate text-[13px] font-semibold text-zinc-400">{channel?.name}</h5>
</div>
</a>
);

View File

@@ -10,24 +10,21 @@ export default function ChannelsList() {
const { data, error }: any = useSWR('channels', fetcher);
return (
<div className="flex flex-col gap-px">
<>
{error && <div>failed to fetch</div>}
{!data ? (
<>
<div className="inline-flex items-center gap-2 rounded-md px-2.5 py-1.5">
<div className="relative h-5 w-5 shrink-0 animate-pulse rounded bg-zinc-800"></div>
<div className="h-3 w-full animate-pulse bg-zinc-800"></div>
</div>
<div className="inline-flex items-center gap-2 rounded-md px-2.5 py-1.5">
<div className="relative h-5 w-5 shrink-0 animate-pulse rounded bg-zinc-800"></div>
<div className="h-3 w-full animate-pulse bg-zinc-800"></div>
</div>
</>
) : (
data.map((item: { event_id: string }) => <ChannelsListItem key={item.event_id} data={item} />)
)}
</>
<div className="flex flex-col">
{!data || error ? (
<>
<div className="inline-flex h-8 items-center gap-2 rounded-md px-2.5">
<div className="relative h-5 w-5 shrink-0 animate-pulse rounded bg-zinc-800"></div>
<div className="h-3 w-full animate-pulse bg-zinc-800"></div>
</div>
<div className="inline-flex h-8 items-center gap-2 rounded-md px-2.5">
<div className="relative h-5 w-5 shrink-0 animate-pulse rounded bg-zinc-800"></div>
<div className="h-3 w-full animate-pulse bg-zinc-800"></div>
</div>
</>
) : (
data.map((item: { event_id: string }) => <ChannelsListItem key={item.event_id} data={item} />)
)}
<ChannelCreateModal />
</div>
);

View File

@@ -17,7 +17,7 @@ export default function ChatsListItem({ pubkey }: { pubkey: string }) {
<>
{isError && <div>error</div>}
{isLoading && !user ? (
<div className="inline-flex items-center gap-2 rounded-md px-2.5 py-1.5">
<div className="inline-flex h-8 items-center gap-2.5 rounded-md px-2.5">
<div className="relative h-5 w-5 shrink-0 animate-pulse rounded bg-zinc-800"></div>
<div>
<div className="h-2.5 w-full animate-pulse truncate rounded bg-zinc-800 text-sm font-medium"></div>
@@ -27,7 +27,7 @@ export default function ChatsListItem({ pubkey }: { pubkey: string }) {
<a
href={`/app/chat?pubkey=${pubkey}`}
className={twMerge(
'inline-flex items-center gap-2 rounded-md px-2.5 py-1.5 hover:bg-zinc-900',
'group inline-flex h-8 items-center gap-2.5 rounded-md px-2.5 hover:bg-zinc-900',
pagePubkey === pubkey ? 'dark:bg-zinc-900 dark:text-zinc-100 hover:dark:bg-zinc-800' : ''
)}
>
@@ -35,7 +35,7 @@ export default function ChatsListItem({ pubkey }: { pubkey: string }) {
<img src={user.picture || DEFAULT_AVATAR} alt={pubkey} className="h-5 w-5 rounded bg-white object-cover" />
</div>
<div>
<h5 className="truncate text-sm font-medium text-zinc-400">
<h5 className="truncate text-[13px] font-semibold text-zinc-400 group-hover:text-zinc-200">
{user.display_name || user.name || shortenKey(pubkey)}
</h5>
</div>

View File

@@ -13,24 +13,21 @@ export default function ChatsList() {
return (
<div className="flex flex-col gap-px">
<>
<ChatsListSelfItem />
{error && <div>failed to fetch</div>}
{!chats ? (
<>
<div className="inline-flex items-center gap-2 rounded-md px-2.5 py-1.5">
<div className="relative h-5 w-5 shrink-0 animate-pulse rounded bg-zinc-800"></div>
<div className="h-3 w-full animate-pulse bg-zinc-800"></div>
</div>
<div className="inline-flex items-center gap-2 rounded-md px-2.5 py-1.5">
<div className="relative h-5 w-5 shrink-0 animate-pulse rounded bg-zinc-800"></div>
<div className="h-3 w-full animate-pulse bg-zinc-800"></div>
</div>
</>
) : (
chats.map((item: { pubkey: string }) => <ChatsListItem key={item.pubkey} pubkey={item.pubkey} />)
)}
</>
<ChatsListSelfItem />
{!chats || error ? (
<>
<div className="inline-flex h-8 items-center gap-2 rounded-md px-2.5">
<div className="relative h-5 w-5 shrink-0 animate-pulse rounded bg-zinc-800"></div>
<div className="h-3 w-full animate-pulse bg-zinc-800"></div>
</div>
<div className="inline-flex h-8 items-center gap-2 rounded-md px-2.5">
<div className="relative h-5 w-5 shrink-0 animate-pulse rounded bg-zinc-800"></div>
<div className="h-3 w-full animate-pulse bg-zinc-800"></div>
</div>
</>
) : (
chats.map((item: { pubkey: string }) => <ChatsListItem key={item.pubkey} pubkey={item.pubkey} />)
)}
</div>
);
}

View File

@@ -18,7 +18,7 @@ export default function ChatsListSelfItem() {
<>
{isError && <div>error</div>}
{isLoading && !account ? (
<div className="inline-flex items-center gap-2 rounded-md px-2.5 py-1.5">
<div className="inline-flex h-8 items-center gap-2.5 rounded-md px-2.5">
<div className="relative h-5 w-5 shrink-0 animate-pulse rounded bg-zinc-800"></div>
<div>
<div className="h-2.5 w-full animate-pulse truncate rounded bg-zinc-800 text-sm font-medium"></div>
@@ -28,7 +28,7 @@ export default function ChatsListSelfItem() {
<a
href={`/app/chat?pubkey=${account.pubkey}`}
className={twMerge(
'inline-flex items-center gap-2 rounded-md px-2.5 py-1.5 hover:bg-zinc-900',
'inline-flex h-8 items-center gap-2.5 rounded-md px-2.5 hover:bg-zinc-900',
pagePubkey === account.pubkey ? 'dark:bg-zinc-900 dark:text-zinc-100 hover:dark:bg-zinc-800' : ''
)}
>
@@ -40,8 +40,9 @@ export default function ChatsListSelfItem() {
/>
</div>
<div>
<h5 className="truncate text-sm font-medium text-zinc-400">
{profile?.display_name || profile?.name || shortenKey(account.pubkey)} (you)
<h5 className="truncate text-[13px] font-semibold text-zinc-400">
{profile?.display_name || profile?.name || shortenKey(account.pubkey)}{' '}
<span className="text-zinc-500">(you)</span>
</h5>
</div>
</a>

View File

@@ -0,0 +1,15 @@
import { SVGProps } from 'react';
export default function MyspaceIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg width={24} height={24} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M8.75 7.75H15.25M8.75 11.75H12.25M4.25 3.25V20.75H19.75V3.25H4.25Z"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@@ -0,0 +1,12 @@
import { SVGProps } from 'react';
export default function ThreadsIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg width={24} height={24} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M2.75 3.75V3C2.33579 3 2 3.33579 2 3.75H2.75ZM16.25 3.75H17C17 3.33579 16.6642 3 16.25 3V3.75ZM21.25 12H22C22 11.5858 21.6642 11.25 21.25 11.25V12ZM6.75 15C6.33579 15 6 15.3358 6 15.75C6 16.1642 6.33579 16.5 6.75 16.5V15ZM6.75 7.75V7C6.33579 7 6 7.33579 6 7.75H6.75ZM12.25 7.75H13C13 7.33579 12.6642 7 12.25 7V7.75ZM12.25 12.25V13C12.6642 13 13 12.6642 13 12.25H12.25ZM6.75 12.25H6C6 12.6642 6.33579 13 6.75 13V12.25ZM2.75 4.5H16.25V3H2.75V4.5ZM22 17.75V12H20.5V17.75H22ZM15.5 3.75V12H17V3.75H15.5ZM15.5 12V17.75H17V12H15.5ZM21.25 11.25H16.25V12.75H21.25V11.25ZM2 3.75V17.75H3.5V3.75H2ZM5.25 21H18.5V19.5H5.25V21ZM2 17.75C2 19.5449 3.45507 21 5.25 21V19.5C4.2835 19.5 3.5 18.7165 3.5 17.75H2ZM18.75 21C20.5449 21 22 19.5449 22 17.75H20.5C20.5 18.7165 19.7165 19.5 18.75 19.5V21ZM18.75 19.5C17.7835 19.5 17 18.7165 17 17.75H15.5C15.5 19.5449 16.9551 21 18.75 21V19.5ZM6.75 16.5H12.25V15H6.75V16.5ZM6.75 8.5H12.25V7H6.75V8.5ZM11.5 7.75V12.25H13V7.75H11.5ZM12.25 11.5H6.75V13H12.25V11.5ZM7.5 12.25V7.75H6V12.25H7.5Z"
fill="currentColor"
/>
</svg>
);
}

View File

@@ -0,0 +1,15 @@
import { SVGProps } from 'react';
export default function WorldIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg width={24} height={24} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M19.7783 4.22184L4.22197 19.7782M21.25 12C21.25 17.1086 17.1086 21.25 12 21.25C6.89137 21.25 2.75 17.1086 2.75 12C2.75 6.89137 6.89137 2.75 12 2.75C17.1086 2.75 21.25 6.89137 21.25 12ZM18.5163 18.516C17.3167 19.7156 13.427 17.7707 9.82826 14.172C6.22955 10.5733 4.28467 6.68352 5.48424 5.48395C6.68381 4.28438 10.5736 6.22927 14.1723 9.82798C17.771 13.4267 19.7159 17.3165 18.5163 18.516Z"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@@ -1,6 +1,5 @@
import ActiveAccount from '@lume/shared/accounts/active';
import InactiveAccount from '@lume/shared/accounts/inactive';
import LumeIcon from '@lume/shared/icons/lume';
import PlusIcon from '@lume/shared/icons/plus';
import { APP_VERSION } from '@lume/stores/constants';
import { getAccounts } from '@lume/utils/storage';
@@ -13,14 +12,8 @@ export default function MultiAccounts() {
const { data, error }: any = useSWR('allAccounts', fetcher);
return (
<div className="flex h-full flex-col items-center justify-between px-2 pb-4 pt-3">
<div className="flex h-full flex-col items-center justify-between pb-4 pt-3">
<div className="flex flex-col gap-3">
<a
href="/app/newsfeed/following"
className="group relative flex h-11 w-11 shrink cursor-pointer items-center justify-center rounded-lg bg-zinc-900 hover:bg-zinc-800"
>
<LumeIcon className="h-6 w-auto text-zinc-400 group-hover:text-zinc-200" />
</a>
<>
{error && <div>failed to load</div>}
{!data ? (

View File

@@ -1,53 +1,59 @@
import ChannelsList from '@lume/app/channel/components/list';
import ChatsList from '@lume/app/chat/components/list';
import ActiveLink from '@lume/shared/activeLink';
import MyspaceIcon from '@lume/shared/icons/myspace';
import NavArrowDownIcon from '@lume/shared/icons/navArrowDown';
import ThreadsIcon from '@lume/shared/icons/threads';
import WorldIcon from '@lume/shared/icons/world';
import { Disclosure } from '@headlessui/react';
export default function Navigation() {
return (
<div className="relative flex h-full flex-col gap-1 overflow-hidden pt-3">
<div className="relative flex h-full flex-col gap-3 overflow-hidden pt-3">
{/* Newsfeed */}
<Disclosure defaultOpen={true}>
{({ open }) => (
<div className="flex flex-col px-2">
<Disclosure.Button className="flex cursor-pointer items-center gap-1 px-1 py-1">
<div
className={`inline-flex h-5 w-5 transform items-center justify-center transition-transform duration-150 ease-in-out ${
open ? '' : 'rotate-180'
}`}
>
<NavArrowDownIcon width={12} height={12} className="text-zinc-700" />
</div>
<h3 className="text-[11px] font-bold uppercase tracking-widest text-zinc-600">Newsfeed</h3>
</Disclosure.Button>
<Disclosure.Panel className="flex flex-col text-zinc-400">
<ActiveLink
href="/app/newsfeed/following"
className="flex h-8 items-center gap-2.5 rounded-md px-2.5 text-sm font-medium hover:text-zinc-200"
activeClassName="dark:bg-zinc-900 dark:text-zinc-100 hover:dark:bg-zinc-800"
>
<span>#</span>
<span>Following</span>
</ActiveLink>
<ActiveLink
href="/app/newsfeed/circle"
className="flex h-8 items-center gap-2.5 rounded-md px-2.5 text-sm font-medium hover:text-zinc-200"
activeClassName="dark:bg-zinc-900 dark:text-zinc-100 hover:dark:bg-zinc-800"
>
<span>#</span>
<span>Circle</span>
</ActiveLink>
</Disclosure.Panel>
</div>
)}
</Disclosure>
<div className="flex flex-col gap-0.5 px-1.5">
<div className="px-2.5">
<h3 className="text-[11px] font-bold uppercase tracking-widest text-zinc-600">Feeds</h3>
</div>
<div className="flex flex-col text-zinc-400">
<ActiveLink
href="/app/newsfeed/following"
className="flex h-8 items-center gap-2.5 rounded-md px-2.5 text-[13px] font-semibold hover:text-zinc-200"
activeClassName=""
>
<span className="inline-flex h-5 w-5 items-center justify-center rounded bg-zinc-900">
<WorldIcon width={12} height={12} className="text-zinc-200" />
</span>
<span>Daily</span>
</ActiveLink>
<ActiveLink
href="/app/threads"
className="flex h-8 items-center gap-2.5 rounded-md px-2.5 text-[13px] font-semibold hover:text-zinc-200"
activeClassName=""
>
<span className="inline-flex h-5 w-5 items-center justify-center rounded bg-zinc-900">
<ThreadsIcon width={12} height={12} className="text-zinc-200" />
</span>
<span>Threads</span>
</ActiveLink>
<ActiveLink
href="/app/myspace"
className="flex h-8 items-center gap-2.5 rounded-md px-2.5 text-[13px] font-semibold hover:text-zinc-200"
activeClassName=""
>
<span className="inline-flex h-5 w-5 items-center justify-center rounded bg-zinc-900">
<MyspaceIcon width={12} height={12} className="text-zinc-200" />
</span>
<span>MySpace</span>
</ActiveLink>
</div>
</div>
{/* Channels */}
<Disclosure defaultOpen={true}>
{({ open }) => (
<div className="flex flex-col px-2">
<Disclosure.Button className="flex cursor-pointer items-center gap-1 px-1 py-1">
<div className="flex flex-col gap-0.5 px-1.5">
<Disclosure.Button className="flex cursor-pointer items-center gap-1 px-2.5">
<div
className={`inline-flex h-5 w-5 transform items-center justify-center transition-transform duration-150 ease-in-out ${
open ? '' : 'rotate-180'
@@ -66,8 +72,8 @@ export default function Navigation() {
{/* Chats */}
<Disclosure defaultOpen={true}>
{({ open }) => (
<div className="flex flex-col px-2">
<Disclosure.Button className="flex cursor-pointer items-center gap-1 px-1 py-1">
<div className="flex flex-col gap-0.5 px-1.5">
<Disclosure.Button className="flex cursor-pointer items-center gap-1 px-2.5">
<div
className={`inline-flex h-5 w-5 transform items-center justify-center transition-transform duration-150 ease-in-out ${
open ? '' : 'rotate-180'

View File

@@ -1,14 +0,0 @@
export default function ImagePreview({ url, size }: { url: string; size: string }) {
return (
<div className={`relative h-full ${size === 'large' ? 'w-4/5' : 'w-1/2'} mt-2 rounded-lg border border-zinc-800`}>
<img
src={url}
alt={url}
className="h-auto w-full rounded-lg object-cover"
loading="lazy"
decoding="async"
style={{ contentVisibility: 'auto' }}
/>
</div>
);
}

View File

@@ -1,14 +0,0 @@
import { MediaOutlet, MediaPlayer } from '@vidstack/react';
export default function VideoPreview({ url, size }: { url: string; size: string }) {
return (
<div
onClick={(e) => e.stopPropagation()}
className={`relative mt-2 flex flex-col overflow-hidden rounded-lg ${size === 'large' ? 'w-4/5' : 'w-2/3'}`}
>
<MediaPlayer src={url} poster="" controls>
<MediaOutlet />
</MediaPlayer>
</div>
);
}

View File

@@ -1,19 +0,0 @@
import YouTube from 'react-youtube';
function getVideoId(url: string) {
const regex = /(youtu.*be.*)\/(watch\?v=|embed\/|v|shorts|)(.*?((?=[&#?])|$))/gm;
return regex.exec(url)[3];
}
export default function YoutubePreview({ url, size }: { url: string; size: string }) {
const id = getVideoId(url);
return (
<div
onClick={(e) => e.stopPropagation()}
className={`relative mt-2 flex flex-col overflow-hidden rounded-lg ${size === 'large' ? 'w-4/5' : 'w-2/3'}`}
>
<YouTube videoId={id} className="aspect-video xl:w-2/3" opts={{ width: '100%', height: '100%' }} />
</div>
);
}

View File

@@ -1,5 +1,3 @@
import { messageParser } from '@lume/utils/parser';
import { nip04 } from 'nostr-tools';
import { useCallback, useEffect, useState } from 'react';
@@ -10,7 +8,7 @@ export const useDecryptMessage = (
eventTags: string[],
encryptedContent: string
) => {
const [content, setContent] = useState('');
const [content, setContent] = useState(null);
const extractSenderKey = useCallback(() => {
const keyInTags = eventTags.find(([k, v]) => k === 'p' && v && v !== '')[1];
@@ -32,5 +30,5 @@ export const useDecryptMessage = (
decrypt().catch(console.error);
}, [decrypt]);
return content.length > 0 ? messageParser(content) : '';
return content ? content : '';
};