Skip to content

Commit

Permalink
feat(trending): implement trending feature and components (#820)
Browse files Browse the repository at this point in the history
* init

Signed-off-by: Innei <i@innei.in>

* update

Signed-off-by: Innei <i@innei.in>

* fix: update styles

Signed-off-by: Innei <i@innei.in>

* fix: hover

Signed-off-by: Innei <i@innei.in>

* update scroll

Signed-off-by: Innei <i@innei.in>

* update i18n

Signed-off-by: Innei <i@innei.in>

* chore: auto-fix linting and formatting issues

---------

Signed-off-by: Innei <i@innei.in>
Co-authored-by: Innei <Innei@users.noreply.github.com>
  • Loading branch information
Innei and Innei authored Oct 9, 2024
1 parent b7a054f commit 6c4c543
Show file tree
Hide file tree
Showing 14 changed files with 531 additions and 24 deletions.
1 change: 1 addition & 0 deletions apps/renderer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"@tanstack/react-query-persist-client": "5.56.2",
"@use-gesture/react": "10.3.1",
"@yornaath/batshit": "0.10.1",
"camelcase-keys": "9.1.3",
"class-variance-authority": "0.7.0",
"click-to-react-component": "1.1.0",
"clsx": "2.1.1",
Expand Down
11 changes: 11 additions & 0 deletions apps/renderer/src/api/trending.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import camelcaseKeys from "camelcase-keys"

import { apiFetch } from "~/lib/api-fetch"
import type { Models } from "~/models"

const v1ApiPrefix = "/v1"
export const getTrendingAggregates = () => {
return apiFetch<Models.TrendingAggregates>(`${v1ApiPrefix}/trendings`).then((data) =>
camelcaseKeys(data as any, { deep: true }),
)
}
17 changes: 17 additions & 0 deletions apps/renderer/src/components/icons/crown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { SVGProps } from "react"
import React from "react"

export function IconoirBrightCrown(props: SVGProps<SVGSVGElement>) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" {...props}>
<path
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M22 12h1M12 2V1m0 22v-1m8-2l-1-1m1-15l-1 1M4 20l1-1M4 4l1 1m-4 7h1m14.8 3.5l1.2-7l-4.2 2.1L12 8.5l-1.8 2.1L6 8.5l1.2 7z"
/>
</svg>
)
}
19 changes: 19 additions & 0 deletions apps/renderer/src/components/icons/users.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { SVGProps } from "react"
import React from "react"

export function PhUsersBold(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 256 256"
{...props}
>
<path
fill="currentColor"
d="M125.18 156.94a64 64 0 1 0-82.36 0a100.23 100.23 0 0 0-39.49 32a12 12 0 0 0 19.35 14.2a76 76 0 0 1 122.64 0a12 12 0 0 0 19.36-14.2a100.33 100.33 0 0 0-39.5-32M44 108a40 40 0 1 1 40 40a40 40 0 0 1-40-40m206.1 97.67a12 12 0 0 1-16.78-2.57A76.31 76.31 0 0 0 172 172a12 12 0 0 1 0-24a40 40 0 1 0-10.3-78.67a12 12 0 1 1-6.16-23.19a64 64 0 0 1 57.64 110.8a100.23 100.23 0 0 1 39.49 32a12 12 0 0 1-2.57 16.73"
/>
</svg>
)
}
57 changes: 53 additions & 4 deletions apps/renderer/src/components/ui/modal/stacked/custom-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { PropsWithChildren } from "react"
import { useState } from "react"
import { m, useAnimationControls } from "framer-motion"
import type { FC, PropsWithChildren } from "react"
import { useEffect, useState } from "react"

import { m } from "~/components/common/Motion"
import { stopPropagation } from "~/lib/dom"
import { nextFrame, stopPropagation } from "~/lib/dom"
import { cn } from "~/lib/utils"

import { ModalClose } from "./components"
Expand Down Expand Up @@ -61,3 +61,52 @@ SlideUpModal.class = (className: string) => {
<SlideUpModal {...props} className={cn(props.className, className)} />
)
}

const modalVariant = {
enter: {
x: 0,
opacity: 1,
},
initial: {
x: 700,
opacity: 0.9,
},
exit: {
x: 750,
opacity: 0,
},
}
export const DrawerModalLayout: FC<PropsWithChildren> = ({ children }) => {
const { dismiss } = useCurrentModal()
const controller = useAnimationControls()
useEffect(() => {
nextFrame(() => controller.start("enter"))
}, [controller])

return (
<div className={"h-full"} onPointerDown={dismiss} onClick={stopPropagation}>
<m.div
onPointerDown={stopPropagation}
tabIndex={-1}
initial="initial"
animate={controller}
variants={modalVariant}
transition={{
type: "spring",
mass: 0.4,
tension: 100,
friction: 1,
}}
exit="exit"
layout="size"
className={cn(
"flex flex-col items-center overflow-hidden rounded-xl border bg-theme-background p-8 pb-0",
"shadow-drawer-to-left w-[60ch] max-w-full",
"fixed inset-y-2 right-2",
)}
>
{children}
</m.div>
</div>
)
}
27 changes: 27 additions & 0 deletions apps/renderer/src/hooks/biz/useFollow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { t } from "i18next"
import { useCallback } from "react"

import { useModalStack } from "~/components/ui/modal/stacked"
import { FeedForm } from "~/modules/discover/feed-form"
import { ListForm } from "~/modules/discover/list-form"

export const useFollow = () => {
const { present } = useModalStack()

return useCallback(
(options?: { isList: boolean; id?: string; url?: string }) => {
present({
title: options?.isList
? t("sidebar.feed_actions.edit_list")
: t("sidebar.feed_actions.edit_feed"),
content: ({ dismiss }) =>
options?.isList ? (
<ListForm asWidget id={options?.id} onSuccess={dismiss} />
) : (
<FeedForm asWidget id={options?.id} url={options?.url} onSuccess={dismiss} />
),
})
},
[present],
)
}
40 changes: 40 additions & 0 deletions apps/renderer/src/models/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,41 @@
import type { User } from "@auth/core/types"

import type { FeedModel, ListModelPoplutedFeeds } from "./types"

export * from "./types"

/* eslint-disable @typescript-eslint/no-namespace */
export namespace Models {
export interface TrendingList {
id: string
title: string
description: string
image: string
view: number
fee: number
timelineUpdatedAt: string
ownerUserId: string
subscriberCount: number
}

export interface TrendingAggregates {
trendingFeeds: FeedModel[]
trendingLists: ListModelPoplutedFeeds[]
trendingEntries: TrendingEntry[]
trendingUsers: User[]
}

export interface TrendingEntry {
id: string
feedId: string
title: string
url: string
content: string
description: string
guid: string
author: string
insertedAt: string
publishedAt: string
readCount: number
}
}
4 changes: 2 additions & 2 deletions apps/renderer/src/modules/discover/recommendations-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ export const RecommendationCard: FC<RecommendationCardProps> = memo(({ data, rou
{Object.keys(data.routes).map((route) => (
<li
key={route}
className="hover:text-theme-foreground-hover"
className="group hover:text-theme-foreground-hover"
onClick={(e) => {
;(e.target as HTMLElement).querySelector("button")?.click()
}}
tabIndex={-1}
>
<button
type="button"
className="-translate-x-1.5 rounded p-0.5 px-1.5 duration-200 hover:translate-x-0 hover:bg-muted"
className="-translate-x-1.5 rounded p-0.5 px-1.5 duration-200 group-hover:translate-x-0 group-hover:bg-muted"
onClick={() => {
present({
id: `recommendation-content-${route}`,
Expand Down
Loading

0 comments on commit 6c4c543

Please sign in to comment.