feat: use eslint-plugin-react

This commit is contained in:
DIYgod 2023-06-08 17:02:09 +01:00
parent 083a7ac106
commit b1405a614b
No known key found for this signature in database
77 changed files with 471 additions and 304 deletions

View File

@ -1,5 +1,5 @@
module.exports = {
extends: ["next/core-web-vitals", "plugin:import/recommended"],
extends: ["next/core-web-vitals", "plugin:import/recommended", "plugin:react/recommended"],
root: true,
settings: {
"import/resolver": {
@ -8,11 +8,12 @@ module.exports = {
},
plugins: ["unused-imports"],
rules: {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-imports": "error",
"import/named": "off",
"import/no-anonymous-default-export": "off",
"import/no-named-as-default": "off",
"react/react-in-jsx-scope": "off",
"react-hooks/exhaustive-deps": "off",
"import/no-named-as-default-member": "off",
},
}

View File

@ -19,7 +19,8 @@
"docker:db": "docker-compose -f docker-compose.db.yml up -d",
"spell-check": "cspell lint . --no-progress",
"analyze": "NODE_ENV=production ANALYZE=true BUNDLE_ANALYZE=browser next build",
"postinstall": "prisma generate"
"postinstall": "prisma generate",
"lint": "next lint"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
@ -187,6 +188,7 @@
"eslint": "8.42.0",
"eslint-config-next": "13.4.4",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-react": "7.32.2",
"eslint-plugin-unused-imports": "2.0.0",
"husky": "8.0.3",
"lint-staged": "13.2.2",

View File

@ -478,6 +478,9 @@ devDependencies:
eslint-plugin-import:
specifier: 2.27.5
version: 2.27.5(@typescript-eslint/parser@5.59.8)(eslint-import-resolver-typescript@3.5.5)(eslint@8.42.0)
eslint-plugin-react:
specifier: 7.32.2
version: 7.32.2(eslint@8.42.0)
eslint-plugin-unused-imports:
specifier: 2.0.0
version: 2.0.0(eslint@8.42.0)
@ -6439,7 +6442,7 @@ packages:
dev: true
/concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
/configstore@5.0.1:
resolution: {integrity: sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==}

View File

@ -288,6 +288,7 @@ async function Home() {
href={getSiteLink({ subdomain: "xlog" })}
role="button"
className="button is-black rounded-lg"
rel="noreferrer"
>
{t(`features.${feature.title}.extra.button`)}
</a>

View File

@ -6,7 +6,6 @@ import { nanoid } from "nanoid"
import { useParams, useRouter, useSearchParams } from "next/navigation"
import {
ChangeEvent,
FC,
memo,
useCallback,
useEffect,
@ -729,16 +728,7 @@ export default function SubdomainEditor() {
)
}
const EditorExtraProperties: FC<{
updateValue: <K extends keyof Values>(key: K, value: Values[K]) => void
isPost: boolean
subdomain: string
defaultSlug: string
userTags: string[]
openAdvancedOptions: () => void
}> = memo(
const EditorExtraProperties = memo(
({
isPost,
updateValue,
@ -746,6 +736,15 @@ const EditorExtraProperties: FC<{
defaultSlug,
userTags,
openAdvancedOptions,
}: {
updateValue: <K extends keyof Values>(key: K, value: Values[K]) => void
isPost: boolean
subdomain: string
defaultSlug: string
userTags: string[]
openAdvancedOptions: () => void
}) => {
const values = useEditorState(
(state) =>
@ -918,13 +917,17 @@ const EditorExtraProperties: FC<{
EditorExtraProperties.displayName = "EditorExtraProperties"
const EditorAdvancedOptions: FC<{
updateValue: <K extends keyof Values>(key: K, value: Values[K]) => void
const EditorAdvancedOptions = memo(
({
updateValue,
isAdvancedOptionsOpen,
setIsAdvancedOptionsOpen,
}: {
updateValue: <K extends keyof Values>(key: K, value: Values[K]) => void
isAdvancedOptionsOpen: boolean
setIsAdvancedOptionsOpen: (state: boolean) => void
}> = memo(
({ updateValue, isAdvancedOptionsOpen, setIsAdvancedOptionsOpen }) => {
isAdvancedOptionsOpen: boolean
setIsAdvancedOptionsOpen: (state: boolean) => void
}) => {
const values = useEditorState(
(state) => pick(state, ["disableAISummary"]),
shallow,

View File

@ -18,11 +18,15 @@ type UpdateItem = (id: string, newItem: Partial<SiteNavigationItem>) => void
type RemoveItem = (id: string) => void
const SortableNavigationItem: React.FC<{
const SortableNavigationItem = ({
item,
updateItem,
removeItem,
}: {
item: SiteNavigationItem
updateItem: UpdateItem
removeItem: RemoveItem
}> = ({ item, updateItem, removeItem }) => {
}) => {
const { t } = useTranslation("dashboard")
return (
<div className="flex space-x-5 border-b p-5 bg-zinc-50 last:border-0">
@ -172,6 +176,7 @@ export default function SiteSettingsNavigationPage() {
/>
)
})}
{/* eslint-disable-next-line react/no-unknown-property */}
<style jsx global>{`
.sortable-ghost {
opacity: 0.4;

View File

@ -27,12 +27,17 @@ import {
type RemoveItem = (operator: string) => void
const SortableNavigationItem: React.FC<{
const SortableNavigationItem = ({
item,
removeItem,
isLoading,
disabled,
}: {
item: string
removeItem: RemoveItem
isLoading: boolean
disabled?: boolean
}> = ({ item, removeItem, isLoading, disabled }) => {
}) => {
const { t } = useTranslation("dashboard")
return (
<div className="flex space-x-5 border-b p-5 bg-zinc-50 last:border-0 items-center">
@ -241,6 +246,7 @@ export default function SettingsOperatorPage() {
/>
)
})}
{/* eslint-disable-next-line react/no-unknown-property */}
<style jsx global>{`
.sortable-ghost {
opacity: 0.4;

View File

@ -27,11 +27,15 @@ type UpdateItem = (id: string, newItem: Partial<Item>) => void
type RemoveItem = (id: string) => void
const SortableNavigationItem: React.FC<{
const SortableNavigationItem = ({
item,
updateItem,
removeItem,
}: {
item: Item
updateItem: UpdateItem
removeItem: RemoveItem
}> = ({ item, updateItem, removeItem }) => {
}) => {
const { t } = useTranslation("dashboard")
return (
<div className="flex space-x-5 border-b p-5 bg-zinc-50 last:border-0">
@ -213,6 +217,7 @@ export default function SiteSettingsNavigationPage() {
/>
)
})}
{/* eslint-disable-next-line react/no-unknown-property */}
<style jsx global>{`
.sortable-ghost {
opacity: 0.4;

View File

@ -34,13 +34,19 @@ export const Badge = ({
)
}
export const AchievementItem: React.FC<{
export const AchievementItem = ({
group,
layoutId,
size,
characterId,
isOwner,
}: {
group: AchievementSection["groups"][number]
layoutId: string
size?: number
characterId?: number
isOwner: boolean
}> = ({ group, layoutId, size, characterId, isOwner }) => {
}) => {
const date = useDate()
const { t } = useTranslation("common")

View File

@ -12,14 +12,21 @@ import { cn } from "~/lib/utils"
import type { AchievementSection } from "~/models/site.model"
import { useMintAchievement } from "~/queries/site"
export const AchievementModal: React.FC<{
export const AchievementModal = ({
opened,
setOpened,
group,
layoutId,
isOwner,
characterId,
}: {
opened: boolean
setOpened: (value: boolean) => void
group: AchievementSection["groups"][number]
layoutId: string
isOwner: boolean
characterId?: number
}> = ({ opened, setOpened, group, layoutId, isOwner, characterId }) => {
}) => {
const date = useDate()
const { t } = useTranslation("common")

View File

@ -10,10 +10,13 @@ import { ExpandedCharacter, ExpandedNote } from "~/lib/types"
import { cn } from "~/lib/utils"
import { useGetGreenfieldId } from "~/queries/site"
export const BlockchainInfo: React.FC<{
export const BlockchainInfo = ({
site,
page,
}: {
site?: ExpandedCharacter
page?: ExpandedNote
}> = ({ site, page }) => {
}) => {
const { t } = useTranslation("common")
const ipfs = (page ? page.metadata?.uri : site?.metadata?.uri) || ""

View File

@ -8,15 +8,7 @@ import { useTranslation } from "~/lib/i18n/client"
import { cn } from "~/lib/utils"
import { useGetCharacterCard } from "~/queries/site"
export const CharacterCard: React.FC<{
siteId?: string
address?: string
open?: boolean
setButtonLoading?: (loading: boolean) => void
hideFollowButton?: boolean
simple?: boolean
style?: "flat" | "normal"
}> = ({
export const CharacterCard = ({
siteId,
address,
open,
@ -24,6 +16,14 @@ export const CharacterCard: React.FC<{
hideFollowButton,
simple,
style,
}: {
siteId?: string
address?: string
open?: boolean
setButtonLoading?: (loading: boolean) => void
hideFollowButton?: boolean
simple?: boolean
style?: "flat" | "normal"
}) => {
const date = useDate()
const { t } = useTranslation("common")

View File

@ -19,10 +19,13 @@ import { CharacterCard } from "~/components/common/CharacterCard"
import { Portal } from "./Portal"
export const CharacterFloatCard: React.FC<{
export const CharacterFloatCard = ({
siteId,
children,
}: {
siteId?: string
children: JSX.Element
}> = ({ siteId, children }) => {
}) => {
const [open, setOpen] = useState(false)
const { x, y, refs, strategy, context } = useFloating({

View File

@ -9,7 +9,14 @@ import { Button } from "../ui/Button"
import CharacterListItem from "./CharacterListItem"
import { PortalProvider } from "./Portal"
export const CharacterList: React.FC<{
export const CharacterList = ({
open,
setOpen,
hasMore,
loadMore,
list,
title,
}: {
open: boolean
setOpen: (open: boolean) => void
hasMore: boolean
@ -18,7 +25,7 @@ export const CharacterList: React.FC<{
list: any[]
} | null)[]
title: string
}> = ({ open, setOpen, hasMore, loadMore, list, title }) => {
}) => {
const { t } = useTranslation("common")
const flattenList = list?.reduce(
(acc, cur) => acc.concat(cur?.list || []),

View File

@ -11,10 +11,13 @@ import { Avatar } from "../ui/Avatar"
import { UniLink } from "../ui/UniLink"
import { FollowingButton } from "./FollowingButton"
const CharacterListItem: React.FC<{
const CharacterListItem = ({
character,
sub,
}: {
character: ExpandedCharacter
sub: any
}> = ({ character, sub }) => {
}) => {
return (
<div className="py-3 flex items-center justify-between space-x-2 text-sm px-5">
<div className="flex flex-1 overflow-hidden space-x-2">

View File

@ -9,10 +9,13 @@ import { ExpandedNote } from "~/lib/types"
import { cn } from "~/lib/utils"
import { useGetComments } from "~/queries/page"
export const Comment: React.FC<{
export const Comment = ({
page,
className,
}: {
page?: ExpandedNote
className?: string
}> = ({ page, className }) => {
}) => {
const comments = useGetComments({
characterId: page?.characterId,
noteId: page?.noteId,

View File

@ -17,7 +17,14 @@ import { CodeMirrorEditor } from "../ui/CodeMirror"
import { Input } from "../ui/Input"
import { EmojiPicker } from "./EmojiPicker"
export const CommentInput: React.FC<{
export const CommentInput = ({
characterId,
noteId,
originalCharacterId,
originalNoteId,
onSubmitted,
comment,
}: {
characterId?: number
noteId?: number
originalCharacterId?: number
@ -26,13 +33,6 @@ export const CommentInput: React.FC<{
comment?: NoteEntity & {
character?: CharacterEntity | null
}
}> = ({
characterId,
noteId,
originalCharacterId,
originalNoteId,
onSubmitted,
comment,
}) => {
const account = useAccountState((s) => s.computed.account)
const commentPage = useCommentPage()

View File

@ -20,7 +20,13 @@ import { getSiteLink } from "~/lib/helpers"
import { useTranslation } from "~/lib/i18n/client"
import { cn } from "~/lib/utils"
export const CommentItem: React.FC<{
export const CommentItem = ({
comment,
originalCharacterId,
originalNoteId,
depth,
className,
}: {
comment: NoteEntity & {
character?: CharacterEntity | null
}
@ -28,7 +34,7 @@ export const CommentItem: React.FC<{
originalNoteId?: number
depth: number
className?: string
}> = ({ comment, originalCharacterId, originalNoteId, depth, className }) => {
}) => {
const [replyOpen, setReplyOpen] = useState(false)
const [editOpen, setEditOpen] = useState(false)

View File

@ -47,15 +47,7 @@ type HeaderLinkType = {
}
)
export const ConnectButton: React.FC<{
left?: boolean
variant?: Variant
variantColor?: VariantColor
size?: "base" | "sm"
hideNotification?: boolean
mobileSimplification?: boolean
hideName?: boolean
}> = ({
export const ConnectButton = ({
left,
variant,
variantColor,
@ -63,6 +55,14 @@ export const ConnectButton: React.FC<{
hideNotification,
mobileSimplification,
hideName,
}: {
left?: boolean
variant?: Variant
variantColor?: VariantColor
size?: "base" | "sm"
hideNotification?: boolean
mobileSimplification?: boolean
hideName?: boolean
}) => {
let avatarSize
let sizeDecrease

View File

@ -3,9 +3,11 @@ import { useTranslation } from "react-i18next"
import { useIsDark } from "~/hooks/useDarkMode"
export const EmojiPicker: React.FC<{
export const EmojiPicker = ({
onEmojiSelect,
}: {
onEmojiSelect: (e: any) => void
}> = ({ onEmojiSelect }) => {
}) => {
const ref = useRef<any>()
const isDark = useIsDark()

View File

@ -10,12 +10,17 @@ import { useTranslation } from "~/lib/i18n/client"
import { getSubscriptionsFromList } from "~/models/site.model"
import { useSubscribeToSites } from "~/queries/site"
export const FollowAllButton: React.FC<{
export const FollowAllButton = ({
size,
characterIds = [],
siteIds = [],
className,
}: {
size?: "sm" | "xl"
characterIds?: number[]
siteIds?: string[]
className?: string
}> = ({ size, characterIds = [], siteIds = [], className }) => {
}) => {
const subscribeToSites = useSubscribeToSites()
const { t } = useTranslation("index")
const account = useAccountState((s) => s.computed.account)

View File

@ -14,13 +14,19 @@ import {
useUnsubscribeFromSite,
} from "~/queries/site"
export const FollowingButton: React.FC<{
export const FollowingButton = ({
site,
variant,
className,
size,
loadingStatusChange,
}: {
site?: ExpandedCharacter
variant?: Variant
className?: string
size?: "sm" | "xl"
loadingStatusChange?: (status: boolean) => void
}> = ({ site, variant, className, size, loadingStatusChange }) => {
}) => {
const subscribeToSite = useSubscribeToSite()
const unsubscribeFromSite = useUnsubscribeFromSite()
const { t, i18n } = useTranslation("common")

View File

@ -8,10 +8,13 @@ import {
useGetSiteToSubscriptions,
} from "~/queries/site"
export const FollowingCount: React.FC<{
export const FollowingCount = ({
characterId,
disableList,
}: {
characterId?: number
disableList?: boolean
}> = ({ characterId, disableList }) => {
}) => {
let [isFollowListOpen, setIsFollowListOpen] = useState(false)
let [isToFollowListOpen, setIsToFollowListOpen] = useState(false)
const { t } = useTranslation("common")

View File

@ -7,13 +7,19 @@ import { Image } from "~/components/ui/Image"
import LogoLottieJSON from "../../../public/assets/logo.json"
export const Logo: React.FC<{
export const Logo = ({
type,
width,
height,
loop,
autoplay,
}: {
type: "svg" | "png" | "lottie"
width?: number
height?: number
loop?: boolean
autoplay?: boolean
}> = ({ type, width, height, loop, autoplay }) => {
}) => {
const ref = useRef<LottieRefCurrentProps>(null)
switch (type) {

View File

@ -10,19 +10,7 @@ import { ExpandedCharacter, ExpandedNote } from "~/lib/types"
import { cn, scrollTo } from "~/lib/utils"
import { renderPageContent } from "~/markdown"
export const PageContent: React.FC<{
content?: string
className?: string
toc?: boolean
inputRef?: MutableRefObject<HTMLDivElement | null>
onScroll?: (scrollTop: number) => void
onMouseEnter?: () => void
parsedContent?: ReturnType<typeof renderPageContent>
isComment?: boolean
page?: ExpandedNote
site?: ExpandedCharacter
withActions?: boolean
}> = ({
export const PageContent = ({
className,
content,
toc,
@ -34,6 +22,18 @@ export const PageContent: React.FC<{
page,
site,
withActions,
}: {
content?: string
className?: string
toc?: boolean
inputRef?: MutableRefObject<HTMLDivElement | null>
onScroll?: (scrollTop: number) => void
onMouseEnter?: () => void
parsedContent?: ReturnType<typeof renderPageContent>
isComment?: boolean
page?: ExpandedNote
site?: ExpandedCharacter
withActions?: boolean
}) => {
useCodeCopy()

View File

@ -6,12 +6,17 @@ import { useTranslation } from "~/lib/i18n/client"
import { ExpandedCharacter } from "~/lib/types"
import { cn } from "~/lib/utils"
export const PatronButton: React.FC<{
export const PatronButton = ({
site,
className,
size,
loadingStatusChange,
}: {
site?: ExpandedCharacter
className?: string
size?: "sm" | "xl"
loadingStatusChange?: (status: boolean) => void
}> = ({ site, className, size, loadingStatusChange }) => {
}) => {
const { t } = useTranslation("common")
const [open, setOpen] = useState(false)

View File

@ -18,12 +18,17 @@ import { Tabs } from "../ui/Tabs"
import { UniLink } from "../ui/UniLink"
import { CharacterFloatCard } from "./CharacterFloatCard"
export const PatronModal: React.FC<{
export const PatronModal = ({
site,
page,
open,
setOpen,
}: {
site?: ExpandedCharacter
page?: ExpandedNote
open: boolean
setOpen: (open: boolean) => void
}> = ({ site, page, open, setOpen }) => {
}) => {
const { t } = useTranslation("common")
const tipCharacter = useTipCharacter()
const tips = useGetTips(

View File

@ -7,10 +7,13 @@ import { useCheckComment, useGetComments } from "~/queries/page"
import { Button } from "../ui/Button"
export const ReactionComment: React.FC<{
export const ReactionComment = ({
characterId,
noteId,
}: {
characterId?: number
noteId?: number
}> = ({ characterId, noteId }) => {
}) => {
const { t } = useTranslation("common")
const comments = useGetComments({

View File

@ -20,12 +20,17 @@ import {
import { AvatarStack } from "../ui/AvatarStack"
import { Button } from "../ui/Button"
export const ReactionLike: React.FC<{
export const ReactionLike = ({
size,
characterId,
noteId,
vertical,
}: {
size?: "sm" | "base"
characterId?: number
noteId?: number
vertical?: boolean
}> = ({ size, characterId, noteId, vertical }) => {
}) => {
const toggleLikePage = useToggleLikePage()
const { t, i18n } = useTranslation("common")

View File

@ -18,12 +18,17 @@ import { useCheckMint, useGetMints, useMintPage } from "~/queries/page"
import { AvatarStack } from "../ui/AvatarStack"
import { Button } from "../ui/Button"
export const ReactionMint: React.FC<{
export const ReactionMint = ({
size,
noteId,
characterId,
vertical,
}: {
size?: "sm" | "base"
noteId?: number
characterId?: number
vertical?: boolean
}> = ({ size, noteId, characterId, vertical }) => {
}) => {
const mintPage = useMintPage()
const { t, i18n } = useTranslation("common")

View File

@ -15,13 +15,19 @@ import { useGetTips } from "~/queries/site"
import { AvatarStack } from "../ui/AvatarStack"
import { Button } from "../ui/Button"
export const ReactionTip: React.FC<{
export const ReactionTip = ({
characterId,
noteId,
site,
page,
vertical,
}: {
characterId?: number
noteId?: number
site?: ExpandedCharacter
page?: ExpandedNote
vertical?: boolean
}> = ({ characterId, noteId, site, page, vertical }) => {
}) => {
const { t } = useTranslation("common")
const account = useAccountState((s) => s.computed.account)

View File

@ -6,10 +6,13 @@ import { useForm } from "react-hook-form"
import { useTranslation } from "~/lib/i18n/client"
import { cn } from "~/lib/utils"
export const SearchInput: React.FC<{
export const SearchInput = ({
noBorder,
onSubmit,
}: {
noBorder?: boolean
onSubmit?: (value?: string) => void
}> = ({ noBorder, onSubmit }) => {
}) => {
const router = useRouter()
const searchParams = useSearchParams()
const { t } = useTranslation("common")

View File

@ -1,9 +1,12 @@
import { toGateway } from "~/lib/ipfs-parser"
const Style: React.FC<{
const Style = ({
content,
children,
}: {
content?: string
children?: React.ReactNode[]
}> = ({ content, children }) => {
}) => {
let css = content
if (!css && typeof children?.[0] === "string") {
css = children[0].trim()

View File

@ -4,9 +4,7 @@ import { useEffect, useState } from "react"
import { useDate } from "~/hooks/useDate"
export const Time: React.FC<{
isoString?: string
}> = ({ isoString }) => {
export const Time = ({ isoString }: { isoString?: string }) => {
const date = useDate()
const [isMounted, setIsMounted] = useState(false)

View File

@ -16,9 +16,7 @@ const icons: {
},
}
export const Titles: React.FC<{
characterId?: number
}> = ({ characterId }) => {
export const Titles = ({ characterId }: { characterId?: number }) => {
if (!characterId) {
return null
}

View File

@ -2,12 +2,17 @@ import { useIsMobileLayout } from "~/hooks/useMobileLayout"
import { useTranslation } from "~/lib/i18n/client"
import { cn } from "~/lib/utils"
export const DashboardMain: React.FC<{
export const DashboardMain = ({
children,
fullWidth,
title,
className,
}: {
children: React.ReactNode
fullWidth?: boolean
title?: string
className?: string
}> = ({ children, fullWidth, title, className }) => {
}) => {
const { t } = useTranslation("dashboard")
const isMobileLayout = useIsMobileLayout()

View File

@ -2,9 +2,11 @@ import { useEffect, useState } from "react"
import { getStorage, setStorage } from "~/lib/storage"
export const DashboardSidebar: React.FC<{
export const DashboardSidebar = ({
children,
}: {
children: (isOpen: boolean) => React.ReactNode
}> = ({ children }) => {
}) => {
const [isOpen, setIsOpen] = useState(true)
useEffect(() => {

View File

@ -5,11 +5,15 @@ import { Bars3Icon } from "@heroicons/react/24/outline"
import { Logo } from "../common/Logo"
export const DashboardTopbar: React.FC<{
export const DashboardTopbar = ({
children,
userWidget,
drawerWidget,
}: {
children: (open: boolean) => React.ReactNode
userWidget: React.ReactNode
drawerWidget: (close: any) => React.ReactNode
}> = ({ children, userWidget, drawerWidget }) => {
}) => {
const [isOpen, setIsOpen] = useState(false)
return (

View File

@ -3,12 +3,17 @@ import { useTranslation } from "react-i18next"
import { Button } from "../ui/Button"
import { Modal } from "../ui/Modal"
export const DeleteConfirmationModal: React.FC<{
export const DeleteConfirmationModal = ({
open,
setOpen,
onConfirm,
isPost,
}: {
open: boolean
setOpen: (open: boolean) => void
onConfirm: () => void
isPost?: boolean
}> = ({ open, setOpen, onConfirm, isPost }) => {
}) => {
const { t } = useTranslation("dashboard")
return (

View File

@ -6,9 +6,7 @@ import { useTranslation } from "~/lib/i18n/client"
import { PageContent } from "../common/PageContent"
export const ImportPreview: React.FC<{
note: NoteMetadata
}> = ({ note }) => {
export const ImportPreview = ({ note }: { note: NoteMetadata }) => {
const date = useDate()
const [showcaseMore, setShowcaseMore] = useState(false)
const { t } = useTranslation("common")

View File

@ -8,18 +8,7 @@ import { cn } from "~/lib/utils"
import { DeleteConfirmationModal } from "./DeleteConfirmationModal"
export const OptionsButton: React.FC<{
visibility: PageVisibilityEnum | undefined
previewPage: () => void
renderPage: Dispatch<SetStateAction<boolean>>
savePage: (published: boolean) => any
propertiesWidget: React.ReactNode
isRendering: boolean
published: boolean
isPost: boolean
isModified: boolean
discardChanges: () => void
}> = ({
export const OptionsButton = ({
visibility,
previewPage,
savePage,
@ -30,6 +19,17 @@ export const OptionsButton: React.FC<{
isPost,
isModified,
discardChanges,
}: {
visibility: PageVisibilityEnum | undefined
previewPage: () => void
renderPage: Dispatch<SetStateAction<boolean>>
savePage: (published: boolean) => any
propertiesWidget: React.ReactNode
isRendering: boolean
published: boolean
isPost: boolean
isModified: boolean
discardChanges: () => void
}) => {
const { t } = useTranslation("dashboard")

View File

@ -30,9 +30,7 @@ import { DashboardMain } from "./DashboardMain"
import { PagesManagerBatchSelectActionTab } from "./PagesManagerBatchSelectActionTab"
import { PagesManagerMenu } from "./PagesManagerMenu"
export const PagesManager: React.FC<{
isPost: boolean
}> = ({ isPost }) => {
export const PagesManager = ({ isPost }: { isPost: boolean }) => {
const params = useParams()
const subdomain = params?.subdomain as string
const site = useGetSite(subdomain)

View File

@ -18,7 +18,13 @@ function getPageId(page: ExpandedNote) {
return page.noteId || page.draftKey || 0
}
export const PagesManagerBatchSelectActionTab: React.FC<{
export const PagesManagerBatchSelectActionTab = ({
isPost,
isNotxLogContent,
pages,
batchSelected,
setBatchSelected,
}: {
isPost: boolean
isNotxLogContent: boolean
pages?: InfiniteData<{
@ -26,7 +32,7 @@ export const PagesManagerBatchSelectActionTab: React.FC<{
}>
batchSelected: (string | number)[]
setBatchSelected: (selected: (string | number)[]) => void
}> = ({ isPost, isNotxLogContent, pages, batchSelected, setBatchSelected }) => {
}) => {
const { t } = useTranslation("dashboard")
const params = useParams()

View File

@ -1,5 +1,5 @@
import { useParams, useRouter } from "next/navigation"
import { FC, useEffect, useState } from "react"
import { useEffect, useState } from "react"
import toast from "react-hot-toast"
import { Menu } from "@headlessui/react"
@ -30,11 +30,15 @@ interface Item {
icon: JSX.Element
onClick: () => void
}
export const PagesManagerMenu: FC<{
export const PagesManagerMenu = ({
isPost,
page,
onClick: onClose,
}: {
isPost: boolean
page: ExpandedNote
onClick: () => void
}> = ({ isPost, page, onClick: onClose }) => {
}) => {
const { t } = useTranslation("dashboard")
const isCrossbell = !page.metadata?.content?.sources?.includes("xlog")

View File

@ -6,16 +6,7 @@ import { useTranslation } from "~/lib/i18n/client"
import { Button, ButtonGroup } from "../ui/Button"
import { DeleteConfirmationModal } from "./DeleteConfirmationModal"
export const PublishButton: React.FC<{
save: (published: boolean) => void
published: boolean
isSaving: boolean
isDisabled: boolean
twitterShareUrl?: string
isPost: boolean
isModified: boolean
discardChanges: () => void
}> = ({
export const PublishButton = ({
save,
discardChanges,
published,
@ -24,6 +15,15 @@ export const PublishButton: React.FC<{
isModified,
twitterShareUrl,
isPost,
}: {
save: (published: boolean) => void
published: boolean
isSaving: boolean
isDisabled: boolean
twitterShareUrl?: string
isPost: boolean
isModified: boolean
discardChanges: () => void
}) => {
const [showDropdown, setShowDropdown] = useState(false)
const dropdownRef = useRef<HTMLDivElement | null>(null)

View File

@ -8,10 +8,13 @@ import { useTranslation } from "~/lib/i18n/client"
import { type TabItem, Tabs } from "../ui/Tabs"
import { DashboardMain } from "./DashboardMain"
export const SettingsLayout: React.FC<{
export const SettingsLayout = ({
title,
children,
}: {
title: string
children: React.ReactNode
}> = ({ title, children }) => {
}) => {
const { t } = useTranslation("dashboard")
const xSettingsModal = useXSettingsModal()

View File

@ -225,10 +225,13 @@ const Post = ({
const MemoedPost = memo(Post)
export const HomeFeed: React.FC<{
export const HomeFeed = ({
noteIds,
type,
}: {
noteIds?: string[]
type?: FeedType
}> = ({ noteIds, type }) => {
}) => {
const { t } = useTranslation("common")
const searchParams = useSearchParams()

View File

@ -1,8 +1,6 @@
import React from "react"
export const MintIcon: React.FC<React.ComponentPropsWithoutRef<"svg">> = (
props,
) => {
export const MintIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
return (
<svg
width="1em"

View File

@ -9,7 +9,7 @@ import { FABBase } from "../ui/FAB"
const isShouldShow = () =>
document.documentElement.scrollTop > document.documentElement.clientHeight
export const BackToTopFAB: React.FC<{}> = () => {
export const BackToTopFAB = () => {
const [shouldShow, setShouldShow] = useState(isShouldShow())
useEffect(() => {
const handler = throttle(() => {

View File

@ -7,11 +7,15 @@ import { useUserRole } from "~/hooks/useUserRole"
import { SITE_URL } from "~/lib/env"
import { useTranslation } from "~/lib/i18n/client"
export const EditButton: React.FC<{
export const EditButton = ({
handle,
noteId,
isPost,
}: {
handle?: string
noteId?: number
isPost?: boolean
}> = ({ handle, noteId, isPost }) => {
}) => {
const { t } = useTranslation("common")
const [showEdit, setShowEdit] = useState(false)
const userRole = useUserRole(handle)

View File

@ -5,10 +5,13 @@ import React, { useCallback, useEffect, useState } from "react"
import { useIsMobileLayout } from "~/hooks/useMobileLayout"
import { useTranslation } from "~/lib/i18n/client"
export const OIAButton: React.FC<{
export const OIAButton = ({
link,
isInRN,
}: {
link: `/${string}`
isInRN: boolean
}> = ({ link, isInRN }) => {
}) => {
const { t } = useTranslation("site")
const [isVisible, setIsVisible] = useState(true)

View File

@ -135,11 +135,15 @@ export const PlatformsSyncMap: {
},
}
export const Platform: React.FC<{
export const Platform = ({
platform,
username,
className,
}: {
platform: string
username: string
className?: string
}> = ({ platform, username, className }) => {
}) => {
platform = platform.toLowerCase()
let link = PlatformsSyncMap[platform]?.url

View File

@ -10,10 +10,13 @@ import { ExpandedCharacter, ExpandedNote } from "~/lib/types"
import { usePostFooterInView } from "./PostFooter"
export const PostActions: React.FC<{
export const PostActions = ({
page,
site,
}: {
page?: ExpandedNote
site?: ExpandedCharacter
}> = ({ page, site }) => {
}) => {
const containerRef = useRef<HTMLDivElement | null>(null)
const isPostFooterInView = usePostFooterInView()

View File

@ -20,10 +20,13 @@ export const usePostFooterInView = () => {
})
return data
}
export const PostFooter: React.FC<{
export const PostFooter = ({
page,
site,
}: {
page?: ExpandedNote
site?: ExpandedCharacter
}> = ({ page, site }) => {
}) => {
const actionElRef = useRef<HTMLDivElement>(null)
const [isInView] = useInViewport(actionElRef)

View File

@ -128,9 +128,7 @@ function Items(props: ItemsProps) {
)
}
export const PostToc: React.FC<{
data: TocResult
}> = ({ data }) => {
export const PostToc = ({ data }: { data: TocResult }) => {
const containerRef = useRef<HTMLDivElement | null>(null)
const idList = getIds(data?.map)

View File

@ -14,7 +14,7 @@ import { useGetSite } from "~/queries/site"
import { EmptyState } from "../ui/EmptyState"
import { UniLink } from "../ui/UniLink"
export const SiteArchives: React.FC = () => {
export const SiteArchives = () => {
let currentLength = 0
const date = useDate()
const { t } = useTranslation("site")

View File

@ -32,7 +32,7 @@ type HeaderLinkType = {
onClick?: () => void
}
const HeaderLink: React.FC<{ link: HeaderLinkType }> = ({ link }) => {
const HeaderLink = ({ link }: { link: HeaderLinkType }) => {
const pathname = usePathname()
const { t } = useTranslation("site")
@ -51,9 +51,7 @@ const HeaderLink: React.FC<{ link: HeaderLinkType }> = ({ link }) => {
)
}
export const SiteHeader: React.FC<{
handle: string
}> = ({ handle }) => {
export const SiteHeader = ({ handle }: { handle: string }) => {
const site = useGetSite(handle)
const { t } = useTranslation("site")
const leftLinks: HeaderLinkType[] =

View File

@ -14,7 +14,7 @@ import { useGetSite } from "~/queries/site"
import { EmptyState } from "../ui/EmptyState"
export const SiteSearch: React.FC = () => {
export const SiteSearch = () => {
const router = useRouter()
const { t } = useTranslation("site")
const date = useDate()

View File

@ -4,14 +4,7 @@ import { memo } from "react"
import { toGateway } from "~/lib/ipfs-parser"
const APlayer: React.FC<
{
name?: string
artist?: string
cover?: string
lrc?: string
} & React.AudioHTMLAttributes<HTMLAudioElement>
> = memo(function APlayer({
const APlayer = memo(function APlayer({
src,
name,
artist,
@ -20,7 +13,12 @@ const APlayer: React.FC<
muted,
autoPlay,
loop,
}) {
}: {
name?: string
artist?: string
cover?: string
lrc?: string
} & React.AudioHTMLAttributes<HTMLAudioElement>) {
if (!src) return null
src = toGateway(src)

View File

@ -4,15 +4,22 @@ import { Image } from "~/components/ui/Image"
import { toGateway } from "~/lib/ipfs-parser"
import { cn } from "~/lib/utils"
export const Avatar: React.FC<
{
images: (string | null | undefined)[]
name?: string | null
size?: number
rounded?: boolean
imageRef?: React.MutableRefObject<HTMLImageElement>
} & React.HTMLAttributes<HTMLSpanElement>
> = ({ images, size, name, className, rounded, imageRef, ...props }) => {
export const Avatar = ({
images,
size,
name,
className,
rounded,
imageRef,
...props
}: {
images: (string | null | undefined)[]
name?: string | null
size?: number
rounded?: boolean
imageRef?: React.MutableRefObject<HTMLImageElement>
className?: string
} & React.HTMLAttributes<HTMLSpanElement>) => {
size = size || 60
const fontSize = size * 0.5

View File

@ -2,10 +2,13 @@ import React from "react"
import { cn } from "~/lib/utils"
export const Badge: React.FC<{
export const Badge = ({
children,
className,
}: {
children: React.ReactNode
className?: string
}> = ({ children, className }) => {
}) => {
return (
<span
className={cn(

View File

@ -18,11 +18,15 @@ export type RadioItem = {
default?: boolean
}
export const BoxRadio: React.FC<{
export const BoxRadio = ({
value,
setValue,
items,
}: {
value: string
setValue: Dispatch<SetStateAction<string>>
items: RadioItem[]
}> = ({ value, setValue, items }) => {
}) => {
const { t } = useTranslation("common")
const randomId = useMemo(() => nanoid(), [])
const [isCustom, setIsCustom] = useState(false)

View File

@ -1,12 +1,13 @@
/* eslint-disable react/prop-types */
"use client"
import React, { memo } from "react"
import { cn } from "~/lib/utils"
export const ButtonGroup: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
/* eslint-disable react/prop-types */
export const ButtonGroup = ({ children }: { children: React.ReactNode }) => {
return <div className="button-group">{children}</div>
}
@ -60,7 +61,7 @@ export const Button = memo(
rounded,
isAutoWidth,
...props
},
}: React.ButtonHTMLAttributes<HTMLButtonElement> & ButtonProps,
ref,
) {
return (

View File

@ -1,6 +1,5 @@
import {
CSSProperties,
FC,
Suspense,
createElement,
forwardRef,
@ -47,7 +46,7 @@ interface XLogCodeMirrorEditorProps {
onUpdate?: (update: ViewUpdate) => void
onCreateEditor?: (view: EditorView, state: EditorState) => void
onMouseEnter?: () => void
LoadingComponent?: FC
LoadingComponent?: () => JSX.Element
}
export const CodeMirrorEditor = forwardRef<

View File

@ -1,15 +1,17 @@
import { DPlayerProps, Player as RcDPlayer } from "rc-dplayer"
import { FC, memo } from "react"
import { memo } from "react"
import { ReactElement } from "rehype-react/lib"
import { toGateway } from "~/lib/ipfs-parser"
const DPlayer: FC<
{
src?: string
children?: ReactElement[]
} & DPlayerProps
> = memo(function DPlayer({ src, children, ...props }) {
const DPlayer = memo(function DPlayer({
src,
children,
...props
}: {
src?: string
children?: ReactElement[]
} & DPlayerProps) {
const sources = children?.filter(
(child) =>
child && typeof child.type === "string" && child.type === "source",

View File

@ -1,10 +1,11 @@
import { FC, memo, useCallback, useState } from "react"
/* eslint-disable react/prop-types */
import { memo, useCallback, useState } from "react"
import { usePopper } from "react-popper"
import { EditorView } from "@codemirror/view"
import { Popover } from "@headlessui/react"
import { ICommand } from "~/editor"
import type { ICommand } from "~/editor"
import { useTranslation } from "~/lib/i18n/client"
import { Tooltip } from "./Tooltip"
@ -21,14 +22,14 @@ enum ToolbarMode {
type IToolbarItemProps = ICommand<any> & { view?: EditorView }
const ToolbarItemWithPopover: FC<IToolbarItemProps> = ({
const ToolbarItemWithPopover = ({
name,
icon,
label,
execute,
ui,
view,
}) => {
}: IToolbarItemProps) => {
const { t } = useTranslation("dashboard")
let [popoverButtonRef, setPopoverButtonRef] =
useState<HTMLButtonElement | null>(null)
@ -79,56 +80,54 @@ const ToolbarItemWithPopover: FC<IToolbarItemProps> = ({
)
}
export const EditorToolbar: FC<EditorToolbarProps> = memo(
({ view, toolbars }) => {
const { t } = useTranslation("dashboard")
const renderToolbar = useCallback(
(mode: ToolbarMode) =>
// eslint-disable-next-line react/display-name
({ name, icon, label, execute, ui }: ICommand) => {
return ui ? (
<ToolbarItemWithPopover
export const EditorToolbar = memo(({ view, toolbars }: EditorToolbarProps) => {
const { t } = useTranslation("dashboard")
const renderToolbar = useCallback(
(mode: ToolbarMode) =>
// eslint-disable-next-line react/display-name
({ name, icon, label, execute, ui }: ICommand) => {
return ui ? (
<ToolbarItemWithPopover
key={name}
name={name}
icon={icon}
label={label}
execute={execute}
ui={ui}
view={view}
/>
) : (
<Tooltip key={name} label={t(label)} placement="bottom">
<button
key={name}
name={name}
icon={icon}
label={label}
execute={execute}
ui={ui}
view={view}
/>
) : (
<Tooltip key={name} label={t(label)} placement="bottom">
<button
key={name}
title={name}
type="button"
className={
"w-9 h-9 transition-colors text-lg border border-transparent rounded flex items-center justify-center text-zinc-500 group-hover:text-zinc-600 hover:text-zinc-500 hover:border-zinc-300 hover:bg-zinc-100"
}
onClick={() => {
view &&
execute({
view,
options: { container: view.dom },
})
}}
>
<span className={icon}></span>
</button>
</Tooltip>
)
},
[view, t],
)
title={name}
type="button"
className={
"w-9 h-9 transition-colors text-lg border border-transparent rounded flex items-center justify-center text-zinc-500 group-hover:text-zinc-600 hover:text-zinc-500 hover:border-zinc-300 hover:bg-zinc-100"
}
onClick={() => {
view &&
execute({
view,
options: { container: view.dom },
})
}}
>
<span className={icon}></span>
</button>
</Tooltip>
)
},
[view, t],
)
return (
<div className="flex group">
<div className="flex-1 flex space-x-1">
{toolbars?.map(renderToolbar(ToolbarMode.Normal))}
</div>
return (
<div className="flex group">
<div className="flex-1 flex space-x-1">
{toolbars?.map(renderToolbar(ToolbarMode.Normal))}
</div>
)
},
)
</div>
)
})
EditorToolbar.displayName = "EditorToolbar"

View File

@ -1,4 +1,4 @@
export const EmptyState: React.FC<{ resource?: string }> = ({ resource }) => {
export const EmptyState = ({ resource }: { resource?: string }) => {
return (
<div className="text-center py-10 mb-10 text-2xl text-zinc-300 font-medium flex justify-center flex-col items-center">
<span className="icon-[mingcute--ghost-line] text-5xl mb-2"></span>

View File

@ -1,6 +1,6 @@
"use client"
import React, { FC, PropsWithChildren, useEffect, useState } from "react"
import React, { PropsWithChildren, useEffect, useState } from "react"
import { useGetState } from "~/hooks/useGetState"
import { cn } from "~/lib/utils"
@ -52,8 +52,8 @@ export const useFAB = (fabConfig: FABConfig) => {
}, [])
}
export const FABBase: FC<
PropsWithChildren<
export const FABBase = (
props: PropsWithChildren<
{
id: string
show?: boolean
@ -62,8 +62,8 @@ export const FABBase: FC<
React.ButtonHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement
>
>
> = (props) => {
>,
) => {
const { children, id, show = true, ...extra } = props
const { className, onTransitionEnd, ...rest } = extra
@ -106,9 +106,9 @@ export const FABBase: FC<
)
}
export const FABContainer: FC<{
export const FABContainer = (props: {
children: JSX.Element | JSX.Element[]
}> = (props) => {
}) => {
const [fabConfig, setFabConfig] = useState<FABConfig[]>([])
useEffect(() => {
fab.register(setFabConfig)

View File

@ -1,7 +1,4 @@
export const FieldLabel: React.FC<{ id?: string; label: string }> = ({
id,
label,
}) => {
export const FieldLabel = ({ id, label }: { id?: string; label: string }) => {
return (
<label className="form-label" htmlFor={id}>
{label}

View File

@ -19,7 +19,7 @@ type TImageProps = {
} & React.HTMLAttributes<HTMLImageElement> &
ImageProps
export const Image: React.FC<TImageProps> = ({
export const Image = ({
fill,
className,
alt,
@ -29,7 +29,7 @@ export const Image: React.FC<TImageProps> = ({
imageRef,
zoom,
...props
}) => {
}: TImageProps) => {
src = toIPFS(src)
const [paddingTop, setPaddingTop] = React.useState("0")
const [autoWidth, setAutoWidth] = React.useState(0)
@ -152,7 +152,7 @@ export const Image: React.FC<TImageProps> = ({
)
}
export const ZoomedImage: React.FC<TImageProps> = (props) => {
export const ZoomedImage = (props: TImageProps) => {
const info = useGetImageInfo(props.src)
const autoProps = info?.data
? {

View File

@ -18,11 +18,8 @@ const getSiteId = ({ id, children }: { id: string; children?: any }) => {
}
}
const Mention: React.FC<{
id: string
children?: any
}> = memo(
function Mention({ id, children }) {
const Mention = memo(
function Mention({ id, children }: { id: string; children?: any }) {
let siteId = getSiteId({ id, children })
const site = useGetSite(siteId)

View File

@ -1,17 +1,15 @@
"use client"
import { nanoid } from "nanoid"
import { FC, memo, useEffect, useState } from "react"
import { memo, useEffect, useState } from "react"
import { useIsDark } from "~/hooks/useDarkMode"
import { useIsUnmounted } from "~/hooks/useLifecycle"
import { ZoomedImage } from "./Image"
const Mermaid: FC<{
children: [string]
}> = memo(
function Mermaid(props) {
const Mermaid = memo(
function Mermaid(props: { children: [string] }) {
const [loading, setLoading] = useState(true)
const [error, setError] = useState("")
const [svg, setSvg] = useState("")

View File

@ -15,9 +15,12 @@ export type TabItem = {
tooltip?: string
}
export const Tabs: React.FC<{ items: TabItem[]; className?: string }> = ({
export const Tabs = ({
items,
className,
}: {
items: TabItem[]
className?: string
}) => {
const { t } = useTranslation("dashboard")

View File

@ -3,8 +3,8 @@ import { Tweet as ReactTweet } from "react-tweet"
import type { TweetComponents } from "react-tweet"
const components: TweetComponents = {
AvatarImg: (props) => <Image {...props} />,
MediaImg: (props) => <Image {...props} fill unoptimized />,
AvatarImg: (props) => <Image {...props} alt="avatar" />,
MediaImg: (props) => <Image {...props} fill unoptimized alt="tweet-media" />,
}
export default function Tweet({ id }: { id: string }) {

View File

@ -8,14 +8,14 @@ export type UniLinkProps = {
target?: string
}
export const UniLink: React.FC<UniLinkProps> = ({
export const UniLink = ({
href,
onClick,
children,
className,
target,
...props
}) => {
}: UniLinkProps) => {
if (onClick) {
return (
<button className={className} onClick={onClick} {...props}>

View File

@ -5,10 +5,13 @@ import React, { useState } from "react"
import { Image } from "~/components/ui/Image"
import { IPFS_GATEWAY } from "~/lib/env"
export const UniMedia: React.FC<{
export const UniMedia = ({
src,
mime_type,
}: {
src: string
mime_type?: string
}> = ({ src, mime_type }) => {
}) => {
const [errorHandled, setErrorHandled] = useState(false)
const [type, setType] = useState(mime_type)

View File

@ -38,10 +38,10 @@ export type ICommand<P = any> = {
// While the `ui` field exists, the ui component will be rendered in a popover first,
// then you can call `transferPayload` to transfer the payload to the `execute` function.
// You can find a sample usage in `./Emoji.tsx`.
ui?: React.FC<{
ui?: (props: {
transferPayload: (payload: P) => void
view: EditorView
}>
}) => JSX.Element
}
export type IPrependExecute = {