Skip to content

Commit f59d37a

Browse files
authored
feat: better layout (#17)
1 parent 9154791 commit f59d37a

File tree

14 files changed

+612
-291
lines changed

14 files changed

+612
-291
lines changed

src/components/FeedSelector.tsx

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { Timeline } from './Timeline';
88
import { useTranslation } from 'react-i18next';
99

1010
export const FeedSelector = ({ columnNumber = 1 }: { columnNumber: number }) => {
11-
const { setSettings, experiments, columns } = useSettings();
11+
const { setSettings, columns } = useSettings();
1212
const { t } = useTranslation('app');
1313
const { isAuthenticated } = useAuth();
1414
const { data: preferences } = usePreferences();
@@ -47,7 +47,7 @@ export const FeedSelector = ({ columnNumber = 1 }: { columnNumber: number }) =>
4747
}
4848

4949
return (
50-
<div className="flex flex-col gap-2 rounded-lg">
50+
<div className="flex flex-col gap-2 rounded-lg w-[550px]">
5151
<Ariakit.TabProvider
5252
defaultSelectedId={selectedFeed}
5353
setSelectedId={(selectedId) => {
@@ -64,32 +64,30 @@ export const FeedSelector = ({ columnNumber = 1 }: { columnNumber: number }) =>
6464
>
6565
{/* // if there are less than 2 feeds, don't show the selector */}
6666
{feeds.length >= 2 && (
67-
<Ariakit.TabList
68-
className="flex flex-row gap-4 max-w-full overflow-x-scroll bg-neutral-900 p-2 m-2 mb-0 rounded-md"
69-
aria-label="feeds"
70-
>
71-
{data?.map((feed) => (
72-
<Ariakit.Tab
73-
id={feed.uri}
74-
className={cn(
75-
'flex h-10 items-center justify-center whitespace-nowrap bg-neutral-800 px-4',
76-
selectedFeed === feed.uri && 'bg-neutral-700',
77-
)}
78-
>
79-
{feed.displayName}
80-
</Ariakit.Tab>
81-
))}
82-
</Ariakit.TabList>
67+
<div>
68+
<Ariakit.TabList
69+
className="flex flex-row gap-4 max-w-full overflow-x-scroll bg-neutral-900 p-2 m-2 mb-0 rounded-md"
70+
aria-label="feeds"
71+
>
72+
{data?.map((feed) => (
73+
<Ariakit.Tab
74+
id={feed.uri}
75+
className={cn(
76+
'flex h-10 items-center justify-center whitespace-nowrap bg-neutral-800 px-4',
77+
selectedFeed === feed.uri && 'bg-neutral-700',
78+
)}
79+
>
80+
{feed.displayName}
81+
</Ariakit.Tab>
82+
))}
83+
</Ariakit.TabList>
84+
</div>
8385
)}
84-
<div className="p-2">
85-
{data?.map((feed) => (
86-
<Ariakit.TabPanel tabId={feed.uri}>
87-
<div className={cn(experiments.columns !== 1 && 'h-dvh overflow-scroll')}>
88-
<Timeline columnNumber={columnNumber} />
89-
</div>
90-
</Ariakit.TabPanel>
91-
))}
92-
</div>
86+
{data?.map((feed) => (
87+
<Ariakit.TabPanel tabId={feed.uri} className="flex-1 overflow-y-scroll min-h-0">
88+
<Timeline columnNumber={columnNumber} />
89+
</Ariakit.TabPanel>
90+
))}
9391
</Ariakit.TabProvider>
9492
</div>
9593
);

src/components/LoginForm.tsx

Lines changed: 42 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -27,48 +27,51 @@ export function LoginForm() {
2727
};
2828

2929
return (
30-
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4 w-full max-w-md">
31-
<div>
32-
<Label htmlFor="handle">{t('app:blueskyHandle')}</Label>
33-
<Controller
34-
name="handle"
35-
control={control}
36-
rules={{ required: 'Handle is required' }}
37-
render={({ field }) => <HandleInput value={field.value || ''} onChange={field.onChange} className="mt-1" />}
38-
/>
39-
{errors.handle && <p className="mt-1 text-sm text-red-500">{errors.handle.message}</p>}
40-
</div>
30+
<form onSubmit={handleSubmit(onSubmit)} className="p-2">
31+
<div className="flex flex-col gap-2 w-[550px]">
32+
<div>
33+
<Label htmlFor="handle">{t('app:blueskyHandle')}</Label>
34+
<Controller
35+
name="handle"
36+
control={control}
37+
rules={{ required: 'Handle is required' }}
38+
render={({ field }) => <HandleInput value={field.value || ''} onChange={field.onChange} className="mt-1" />}
39+
/>
40+
{errors.handle && <p className="mt-1 text-sm text-red-500">{errors.handle.message}</p>}
41+
</div>
4142

42-
<div>
43-
<Label htmlFor="password">{t('password')}</Label>
44-
<Input
45-
id="password"
46-
type="password"
47-
{...register('password', { required: 'Password is required' })}
48-
error={!!errors.password}
49-
/>
50-
{errors.password && <p className="mt-1 text-sm text-red-500">{errors.password.message}</p>}
51-
</div>
43+
<div>
44+
<Label htmlFor="password">{t('password')}</Label>
45+
<Input
46+
id="password"
47+
type="password"
48+
{...register('password', { required: 'Password is required' })}
49+
error={!!errors.password}
50+
/>
51+
{errors.password && <p className="mt-1 text-sm text-red-500">{errors.password.message}</p>}
52+
</div>
5253

53-
{error &&
54-
(error?.message === 'A sign in code has been sent to your email address' || error?.message === 'Token is invalid' ? (
55-
<div>
56-
<Label htmlFor="authFactorToken">{t('authFactorToken')}</Label>
57-
<Input
58-
id="authFactorToken"
59-
type="text"
60-
{...register('authFactorToken', { required: 'Two-factor token is required' })}
61-
error={!!errors.authFactorToken}
62-
/>
63-
{errors.authFactorToken && <p className="mt-1 text-sm text-red-500">{errors.authFactorToken?.message}</p>}
64-
</div>
65-
) : (
66-
<p className="text-red-500 text-sm">{error.message}</p>
67-
))}
54+
{error &&
55+
(error?.message === 'A sign in code has been sent to your email address' ||
56+
error?.message === 'Token is invalid' ? (
57+
<div>
58+
<Label htmlFor="authFactorToken">{t('authFactorToken')}</Label>
59+
<Input
60+
id="authFactorToken"
61+
type="text"
62+
{...register('authFactorToken', { required: 'Two-factor token is required' })}
63+
error={!!errors.authFactorToken}
64+
/>
65+
{errors.authFactorToken && <p className="mt-1 text-sm text-red-500">{errors.authFactorToken?.message}</p>}
66+
</div>
67+
) : (
68+
<p className="text-red-500 text-sm">{error.message}</p>
69+
))}
6870

69-
<Button type="submit" disabled={isLoading} className="w-full">
70-
{isLoading ? t('login.pending') : t('login.default')}
71-
</Button>
71+
<Button type="submit" disabled={isLoading} className="w-full">
72+
{isLoading ? t('login.pending') : t('login.default')}
73+
</Button>
74+
</div>
7275
</form>
7376
);
7477
}

src/components/Navbar.tsx

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,36 @@ import { useTranslation } from 'react-i18next';
22
import { useAuth } from '../lib/bluesky/hooks/useAuth';
33
import { Link } from './ui/Link';
44
import { useBlueskyStore } from '../lib/bluesky/store';
5+
import { BellIcon, HomeIcon, MailIcon, SettingsIcon, UserIcon } from 'lucide-react';
6+
7+
const HomeLink = () => {
8+
const { t } = useTranslation('app');
9+
return (
10+
<Link to="/">
11+
<HomeIcon className="size-10 lg:hidden" />
12+
<h1 className="text-2xl font-bold hidden lg:block">{t('appName')}</h1>
13+
</Link>
14+
);
15+
};
516

617
const MessagesLink = () => {
718
const { t } = useTranslation('messages');
8-
return <Link to="/messages">{t('messages')}</Link>;
19+
return (
20+
<Link to="/messages">
21+
<MailIcon className="size-10 lg:hidden" />
22+
<span className="hidden lg:block">{t('messages')}</span>
23+
</Link>
24+
);
925
};
1026

1127
const NotificationsLink = () => {
1228
const { t } = useTranslation('notifications');
13-
return <Link to="/notifications">{t('notifications')}</Link>;
29+
return (
30+
<Link to="/notifications">
31+
<BellIcon className="size-10 lg:hidden" />
32+
<span className="hidden lg:block">{t('notifications')}</span>
33+
</Link>
34+
);
1435
};
1536

1637
const ProfileLink = () => {
@@ -26,20 +47,20 @@ const ProfileLink = () => {
2647
handle: session?.handle,
2748
}}
2849
>
29-
{t('profile')}
50+
<UserIcon className="size-10 lg:hidden" />
51+
<span className="hidden lg:block">{t('profile')}</span>
3052
</Link>
3153
);
3254
};
3355

3456
const SettingsLink = () => {
3557
const { t } = useTranslation('app');
36-
return <Link to="/settings">{t('settings')}</Link>;
37-
};
38-
39-
const LogoutButton = () => {
40-
const { logout } = useAuth();
41-
const { t } = useTranslation('auth');
42-
return <button onClick={logout}>{t('logout')}</button>;
58+
return (
59+
<Link to="/settings">
60+
<SettingsIcon className="size-10 lg:hidden" />
61+
<span className="hidden lg:block">{t('settings')}</span>
62+
</Link>
63+
);
4364
};
4465

4566
const LoginButton = () => {
@@ -49,21 +70,16 @@ const LoginButton = () => {
4970

5071
export const Navbar = () => {
5172
const { isAuthenticated } = useAuth();
52-
const { t } = useTranslation('app');
5373

5474
return (
55-
<div className="flex flex-col justify-between items-center">
56-
<div className="flex justify-between items-center w-full">
57-
<Link to="/">
58-
<h1 className="text-2xl font-bold">{t('appName')}</h1>
59-
</Link>
60-
<div className="flex flex-row gap-2">
61-
{isAuthenticated && <MessagesLink />}
62-
{isAuthenticated && <NotificationsLink />}
63-
{isAuthenticated && <ProfileLink />}
64-
<SettingsLink />
65-
{isAuthenticated ? <LogoutButton /> : <LoginButton />}
66-
</div>
75+
<div className="fixed bottom-0 left-0 right-0 bg-neutral-900 lg:bg-inherit p-4 z-50 md:top-0 md:right-auto lg:relative lg:h-fit">
76+
<div className="flex flex-row gap-2 justify-between md:flex-col">
77+
<HomeLink />
78+
{isAuthenticated && <MessagesLink />}
79+
{isAuthenticated && <NotificationsLink />}
80+
{isAuthenticated && <ProfileLink />}
81+
<SettingsLink />
82+
{!isAuthenticated && <LoginButton />}
6783
</div>
6884
</div>
6985
);

src/components/PostCard.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,11 @@ export function PostCard({ post, context, className, onClick }: PostCardProps) {
6666
}
6767

6868
return (
69-
<div className={cn('bg-white dark:bg-neutral-900 p-4 rounded-lg shadow', className)} onClick={onClick} id={post.uri}>
69+
<div
70+
className={cn('bg-white dark:bg-neutral-900 p-4 rounded-lg shadow w-[550px]', className)}
71+
onClick={onClick}
72+
id={post.uri}
73+
>
7074
{/* {!!post.record.reply && <PostCard post={reply} />} */}
7175
<div className="flex items-center space-x-3 mb-2">
7276
{post.author.avatar && (

src/components/PostEmbed.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export const PostEmbed = ({ embed }: { embed?: BskyPostEmbed | null }) => {
5959
</a>
6060
<Image
6161
type="post"
62-
src={embed.external.thumb}
62+
src={embed.external.uri ?? embed.external.thumb}
6363
alt={embed.external.title}
6464
className="rounded-lg w-full aspect-square object-cover"
6565
/>
@@ -113,7 +113,7 @@ export const PostEmbed = ({ embed }: { embed?: BskyPostEmbed | null }) => {
113113
if (embed.record.$type === 'app.bsky.embed.record#viewRecord' && embed.record.text) {
114114
return (
115115
<p className="text-gray-800 dark:text-gray-200 mb-3">
116-
{<FacetedText text={embed.record.text} facets={embed.record.facets} key={embed.record.uri} />}
116+
<FacetedText text={embed.record.text} facets={embed.record.facets} key={embed.record.uri} />
117117
</p>
118118
);
119119
}
@@ -142,7 +142,22 @@ export const PostEmbed = ({ embed }: { embed?: BskyPostEmbed | null }) => {
142142
</div>
143143
);
144144
}
145+
case 'app.bsky.embed.recordWithMedia#view':
146+
return (
147+
<>
148+
<div className={cn((embed.record.record.embeds ?? [])?.length >= 2 && 'grid grid-cols-2', 'gap-2 mb-3')}>
149+
<Image
150+
type="post"
151+
key={embed.media.external.uri}
152+
src={embed.media.external.uri ?? embed.media.external.thumb}
153+
alt={embed.media.external.description}
154+
className="rounded-lg w-full object-cover"
155+
/>
156+
</div>
157+
</>
158+
);
145159
default:
160+
// @ts-expect-error - this should never happen
146161
return <NotImplementedBox type={embed.$type} data={embed.record} />;
147162
}
148163
};

src/components/Timeline.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export function Timeline({ columnNumber = 1 }: { columnNumber: number }) {
112112
}
113113

114114
return (
115-
<div className="space-y-4">
115+
<div className="flex flex-col gap-4">
116116
{posts.map(({ post, feedContext }) => (
117117
<PostCard
118118
key={post.uri}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { useTranslation } from 'react-i18next';
22
import { Debug } from './Debug';
3-
export const NotImplementedBox = ({ type, data }: { type: string; data: unknown }) => {
3+
export const NotImplementedBox = ({ type, data }: { type: string; data?: unknown }) => {
44
const { t } = useTranslation('debug');
55
return (
6-
<div className="p-4 bg-gray-100 dark:bg-gray-800 rounded-lg mb-3">
6+
<div className="p-4 bg-gray-100 dark:bg-neutral-600 text-center">
77
{t('notImplemented', { value: type })}
8-
<Debug value={data} />
8+
{Boolean(data) && <Debug value={data} />}
99
</div>
1010
);
1111
};

0 commit comments

Comments
 (0)