feat: latest articles widget
This commit is contained in:
parent
026aaed5a1
commit
8c2833684b
@ -12,7 +12,12 @@ interface IconButtonProps {
|
||||
|
||||
const IconButton = ({ onClick, icon, children, className }: IconButtonProps) => {
|
||||
return (
|
||||
<button className={classNames("icon", className)} type="button" onClick={onClick}>
|
||||
<button
|
||||
className={classNames(
|
||||
"flex items-center justify-center aspect-square w-10 h-10 !p-0 !m-0 bg-gray-dark text-white",
|
||||
className,
|
||||
)}
|
||||
onClick={onClick}>
|
||||
<Icon {...icon} />
|
||||
{children}
|
||||
</button>
|
||||
|
68
packages/app/src/Components/RightWidgets/articles.tsx
Normal file
68
packages/app/src/Components/RightWidgets/articles.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
import { NostrLink } from "@snort/system";
|
||||
import { useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import { useArticles } from "@/Feed/ArticlesFeed";
|
||||
import { findTag } from "@/Utils";
|
||||
|
||||
import IconButton from "../Button/IconButton";
|
||||
import { ProxyImg } from "../ProxyImg";
|
||||
import ProfilePreview from "../User/ProfilePreview";
|
||||
import { BaseWidget } from "./base";
|
||||
|
||||
export default function LatestArticlesWidget() {
|
||||
const [idx, setIdx] = useState(0);
|
||||
const articles = useArticles();
|
||||
const selected = articles.at(idx);
|
||||
|
||||
function next(i: number) {
|
||||
setIdx(x => {
|
||||
x += i;
|
||||
if (x < 0) {
|
||||
x = articles.length - 1;
|
||||
} else if (x > articles.length) {
|
||||
x = 0;
|
||||
}
|
||||
return x;
|
||||
});
|
||||
}
|
||||
|
||||
if (!selected) return;
|
||||
const link = NostrLink.fromEvent(selected);
|
||||
const image = findTag(selected, "image");
|
||||
const title = findTag(selected, "title");
|
||||
return (
|
||||
<BaseWidget title={<FormattedMessage defaultMessage="Latest Articles" />}>
|
||||
<div className="flex flex-col gap-4">
|
||||
<Link
|
||||
to={`/${link.encode()}`}
|
||||
className="relative rounded-xl overflow-hidden w-full aspect-video"
|
||||
state={selected}>
|
||||
{image ? (
|
||||
<ProxyImg src={image} className="absolute w-full h-full object-fit" />
|
||||
) : (
|
||||
<div className="absolute w-full h-full object-fit bg-gray-dark"></div>
|
||||
)}
|
||||
<div className="absolute bottom-2 left-4 right-4 px-2 py-1 rounded-xl text-lg font-bold text-white bg-black/50">
|
||||
{title}
|
||||
</div>
|
||||
</Link>
|
||||
<div>
|
||||
<ProfilePreview
|
||||
pubkey={selected.pubkey}
|
||||
profileImageProps={{
|
||||
subHeader: <></>,
|
||||
}}
|
||||
actions={
|
||||
<div className="flex gap-2">
|
||||
<IconButton icon={{ name: "arrowFront", className: "rotate-180", size: 14 }} onClick={() => next(-1)} />
|
||||
<IconButton icon={{ name: "arrowFront", size: 14 }} onClick={() => next(1)} />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</BaseWidget>
|
||||
);
|
||||
}
|
@ -11,16 +11,16 @@ export interface BaseWidgetProps {
|
||||
}
|
||||
export function BaseWidget({ children, title, icon, iconClassName, contextMenu }: BaseWidgetProps) {
|
||||
return (
|
||||
<div className="br p bg-gray-ultradark">
|
||||
<div className="b br p bg-gray-ultradark">
|
||||
{title && (
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex gap-2 items-center text-xl text-white font-semibold mb-1">
|
||||
<div className="flex gap-2 items-center text-xl text-white font-semibold mb-2">
|
||||
{icon && (
|
||||
<div className="p-2 bg-gray-dark rounded-full">
|
||||
<Icon name={icon} className={iconClassName} />
|
||||
</div>
|
||||
)}
|
||||
<div>{title}</div>
|
||||
<div className="text-font-color">{title}</div>
|
||||
</div>
|
||||
{contextMenu}
|
||||
</div>
|
||||
|
@ -3,7 +3,7 @@ export enum RightColumnWidget {
|
||||
TrendingNotes,
|
||||
TrendingPeople,
|
||||
TrendingHashtags,
|
||||
TrendingArticls,
|
||||
LatestArticls,
|
||||
LiveStreams,
|
||||
InviteFriends,
|
||||
}
|
||||
|
@ -1,15 +1,10 @@
|
||||
import { useContext } from "react";
|
||||
|
||||
import Note, { NoteProps } from "@/Components/Event/EventComponent";
|
||||
import { useArticles } from "@/Feed/ArticlesFeed";
|
||||
import { DeckContext } from "@/Pages/Deck/DeckLayout";
|
||||
|
||||
import Note from "../../Components/Event/EventComponent";
|
||||
|
||||
const options = {
|
||||
longFormPreview: true,
|
||||
};
|
||||
|
||||
export default function Articles() {
|
||||
export default function Articles({ noteProps }: { noteProps?: Omit<NoteProps, "data"> }) {
|
||||
const data = useArticles();
|
||||
const deck = useContext(DeckContext);
|
||||
|
||||
@ -19,9 +14,14 @@ export default function Articles() {
|
||||
<Note
|
||||
data={a}
|
||||
key={a.id}
|
||||
options={options}
|
||||
{...noteProps}
|
||||
options={{
|
||||
longFormPreview: true,
|
||||
...noteProps?.options,
|
||||
}}
|
||||
onClick={ev => {
|
||||
deck?.setArticle(ev);
|
||||
noteProps?.onClick?.(ev);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
@ -2,6 +2,7 @@ import classNames from "classnames";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import { RightColumnWidget } from "@/Components/RightWidgets";
|
||||
import LatestArticlesWidget from "@/Components/RightWidgets/articles";
|
||||
import { BaseWidget } from "@/Components/RightWidgets/base";
|
||||
import InviteFriendsWidget from "@/Components/RightWidgets/invite-friends";
|
||||
import MiniStreamWidget from "@/Components/RightWidgets/mini-stream";
|
||||
@ -23,6 +24,7 @@ export default function RightColumn() {
|
||||
RightColumnWidget.InviteFriends,
|
||||
//RightColumnWidget.LiveStreams,
|
||||
RightColumnWidget.TrendingNotes,
|
||||
RightColumnWidget.LatestArticls,
|
||||
RightColumnWidget.TrendingPeople,
|
||||
RightColumnWidget.TrendingHashtags,
|
||||
]
|
||||
@ -65,6 +67,8 @@ export default function RightColumn() {
|
||||
return <InviteFriendsWidget />;
|
||||
case RightColumnWidget.LiveStreams:
|
||||
return <MiniStreamWidget />;
|
||||
case RightColumnWidget.LatestArticls:
|
||||
return <LatestArticlesWidget />;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -175,6 +175,10 @@ a.ext {
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.text-outline {
|
||||
-webkit-text-stroke: 1px black;
|
||||
}
|
||||
|
||||
.br {
|
||||
border-radius: 16px;
|
||||
}
|
||||
@ -941,6 +945,7 @@ svg.repeat {
|
||||
.pb-safe-area-plus-footer {
|
||||
padding-bottom: calc(env(safe-area-inset-bottom) + 56px);
|
||||
}
|
||||
|
||||
.sm-hide-scrollbar {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
|
@ -183,6 +183,9 @@
|
||||
"2ukA4d": {
|
||||
"defaultMessage": "{n} hours"
|
||||
},
|
||||
"2z7Kky": {
|
||||
"defaultMessage": "Latest Articles"
|
||||
},
|
||||
"3/onCd": {
|
||||
"defaultMessage": "Replies"
|
||||
},
|
||||
|
@ -60,6 +60,7 @@
|
||||
"2oCF7O": "Followed by friends of friends",
|
||||
"2raFAu": "Application-specific data",
|
||||
"2ukA4d": "{n} hours",
|
||||
"2z7Kky": "Latest Articles",
|
||||
"3/onCd": "Replies",
|
||||
"39AHJm": "Sign Up",
|
||||
"3GWu6/": "User Statuses",
|
||||
|
@ -28,6 +28,7 @@ module.exports = {
|
||||
},
|
||||
textColor: {
|
||||
secondary: "var(--font-secondary-color)",
|
||||
"font-color": "var(--font-color)",
|
||||
},
|
||||
spacing: {
|
||||
px: "1px",
|
||||
|
Loading…
x
Reference in New Issue
Block a user