style: 🎨
This commit is contained in:
parent
d3d5842804
commit
51913a5163
6 changed files with 310 additions and 310 deletions
|
|
@ -1,78 +1,78 @@
|
||||||
import { useToast } from '@/hooks'
|
import { useToast } from '@/hooks'
|
||||||
import { useBookmarks } from '@/providers/BookmarksProvider'
|
import { useBookmarks } from '@/providers/BookmarksProvider'
|
||||||
import { useNostr } from '@/providers/NostrProvider'
|
import { useNostr } from '@/providers/NostrProvider'
|
||||||
import { BookmarkIcon, Loader } from 'lucide-react'
|
import { BookmarkIcon, Loader } from 'lucide-react'
|
||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Event } from 'nostr-tools'
|
import { Event } from 'nostr-tools'
|
||||||
|
|
||||||
export default function BookmarkButton({ event }: { event: Event }) {
|
export default function BookmarkButton({ event }: { event: Event }) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
const { pubkey: accountPubkey, bookmarkListEvent, checkLogin } = useNostr()
|
const { pubkey: accountPubkey, bookmarkListEvent, checkLogin } = useNostr()
|
||||||
const { addBookmark, removeBookmark } = useBookmarks()
|
const { addBookmark, removeBookmark } = useBookmarks()
|
||||||
const [updating, setUpdating] = useState(false)
|
const [updating, setUpdating] = useState(false)
|
||||||
const isBookmarked = useMemo(
|
const isBookmarked = useMemo(
|
||||||
() => bookmarkListEvent?.tags.some((tag) => tag[0] === 'e' && tag[1] === event.id),
|
() => bookmarkListEvent?.tags.some((tag) => tag[0] === 'e' && tag[1] === event.id),
|
||||||
[bookmarkListEvent, event]
|
[bookmarkListEvent, event]
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!accountPubkey) return null
|
if (!accountPubkey) return null
|
||||||
|
|
||||||
const handleBookmark = async (e: React.MouseEvent) => {
|
const handleBookmark = async (e: React.MouseEvent) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
checkLogin(async () => {
|
checkLogin(async () => {
|
||||||
if (isBookmarked) return
|
if (isBookmarked) return
|
||||||
|
|
||||||
setUpdating(true)
|
setUpdating(true)
|
||||||
try {
|
try {
|
||||||
await addBookmark(event)
|
await addBookmark(event)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast({
|
toast({
|
||||||
title: t('Bookmark failed'),
|
title: t('Bookmark failed'),
|
||||||
description: (error as Error).message,
|
description: (error as Error).message,
|
||||||
variant: 'destructive'
|
variant: 'destructive'
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
setUpdating(false)
|
setUpdating(false)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleRemoveBookmark = async (e: React.MouseEvent) => {
|
const handleRemoveBookmark = async (e: React.MouseEvent) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
checkLogin(async () => {
|
checkLogin(async () => {
|
||||||
if (!isBookmarked) return
|
if (!isBookmarked) return
|
||||||
|
|
||||||
setUpdating(true)
|
setUpdating(true)
|
||||||
try {
|
try {
|
||||||
await removeBookmark(event)
|
await removeBookmark(event)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast({
|
toast({
|
||||||
title: t('Remove bookmark failed'),
|
title: t('Remove bookmark failed'),
|
||||||
description: (error as Error).message,
|
description: (error as Error).message,
|
||||||
variant: 'destructive'
|
variant: 'destructive'
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
setUpdating(false)
|
setUpdating(false)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={`flex items-center gap-1 ${
|
className={`flex items-center gap-1 ${
|
||||||
isBookmarked ? 'text-rose-400' : 'text-muted-foreground'
|
isBookmarked ? 'text-rose-400' : 'text-muted-foreground'
|
||||||
} enabled:hover:text-rose-400 px-3 h-full`}
|
} enabled:hover:text-rose-400 px-3 h-full`}
|
||||||
onClick={isBookmarked ? handleRemoveBookmark : handleBookmark}
|
onClick={isBookmarked ? handleRemoveBookmark : handleBookmark}
|
||||||
disabled={updating}
|
disabled={updating}
|
||||||
title={isBookmarked ? t('Remove bookmark') : t('Bookmark')}
|
title={isBookmarked ? t('Remove bookmark') : t('Bookmark')}
|
||||||
>
|
>
|
||||||
{updating ? (
|
{updating ? (
|
||||||
<Loader className="animate-spin" />
|
<Loader className="animate-spin" />
|
||||||
) : (
|
) : (
|
||||||
<BookmarkIcon className={isBookmarked ? 'fill-rose-400' : ''} />
|
<BookmarkIcon className={isBookmarked ? 'fill-rose-400' : ''} />
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,97 +1,97 @@
|
||||||
import { useFetchEvent } from '@/hooks'
|
import { useFetchEvent } from '@/hooks'
|
||||||
import { generateEventIdFromETag } from '@/lib/tag'
|
import { generateEventIdFromETag } from '@/lib/tag'
|
||||||
import { useNostr } from '@/providers/NostrProvider'
|
import { useNostr } from '@/providers/NostrProvider'
|
||||||
import { kinds } from 'nostr-tools'
|
import { kinds } from 'nostr-tools'
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import NoteCard, { NoteCardLoadingSkeleton } from '../NoteCard'
|
import NoteCard, { NoteCardLoadingSkeleton } from '../NoteCard'
|
||||||
|
|
||||||
const SHOW_COUNT = 10
|
const SHOW_COUNT = 10
|
||||||
|
|
||||||
export default function BookmarkList() {
|
export default function BookmarkList() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { bookmarkListEvent } = useNostr()
|
const { bookmarkListEvent } = useNostr()
|
||||||
const eventIds = useMemo(() => {
|
const eventIds = useMemo(() => {
|
||||||
if (!bookmarkListEvent) return []
|
if (!bookmarkListEvent) return []
|
||||||
|
|
||||||
return (
|
return (
|
||||||
bookmarkListEvent.tags
|
bookmarkListEvent.tags
|
||||||
.map((tag) => (tag[0] === 'e' ? generateEventIdFromETag(tag) : undefined))
|
.map((tag) => (tag[0] === 'e' ? generateEventIdFromETag(tag) : undefined))
|
||||||
.filter(Boolean) as `nevent1${string}`[]
|
.filter(Boolean) as `nevent1${string}`[]
|
||||||
).reverse()
|
).reverse()
|
||||||
}, [bookmarkListEvent])
|
}, [bookmarkListEvent])
|
||||||
const [showCount, setShowCount] = useState(SHOW_COUNT)
|
const [showCount, setShowCount] = useState(SHOW_COUNT)
|
||||||
const bottomRef = useRef<HTMLDivElement | null>(null)
|
const bottomRef = useRef<HTMLDivElement | null>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const options = {
|
const options = {
|
||||||
root: null,
|
root: null,
|
||||||
rootMargin: '10px',
|
rootMargin: '10px',
|
||||||
threshold: 0.1
|
threshold: 0.1
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadMore = () => {
|
const loadMore = () => {
|
||||||
if (showCount < eventIds.length) {
|
if (showCount < eventIds.length) {
|
||||||
setShowCount((prev) => prev + SHOW_COUNT)
|
setShowCount((prev) => prev + SHOW_COUNT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const observerInstance = new IntersectionObserver((entries) => {
|
const observerInstance = new IntersectionObserver((entries) => {
|
||||||
if (entries[0].isIntersecting) {
|
if (entries[0].isIntersecting) {
|
||||||
loadMore()
|
loadMore()
|
||||||
}
|
}
|
||||||
}, options)
|
}, options)
|
||||||
|
|
||||||
const currentBottomRef = bottomRef.current
|
const currentBottomRef = bottomRef.current
|
||||||
|
|
||||||
if (currentBottomRef) {
|
if (currentBottomRef) {
|
||||||
observerInstance.observe(currentBottomRef)
|
observerInstance.observe(currentBottomRef)
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (observerInstance && currentBottomRef) {
|
if (observerInstance && currentBottomRef) {
|
||||||
observerInstance.unobserve(currentBottomRef)
|
observerInstance.unobserve(currentBottomRef)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [showCount, eventIds])
|
}, [showCount, eventIds])
|
||||||
|
|
||||||
if (eventIds.length === 0) {
|
if (eventIds.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="mt-2 text-sm text-center text-muted-foreground">
|
<div className="mt-2 text-sm text-center text-muted-foreground">
|
||||||
{t('no bookmarks found')}
|
{t('no bookmarks found')}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{eventIds.slice(0, showCount).map((eventId) => (
|
{eventIds.slice(0, showCount).map((eventId) => (
|
||||||
<BookmarkedNote key={eventId} eventId={eventId} />
|
<BookmarkedNote key={eventId} eventId={eventId} />
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{showCount < eventIds.length ? (
|
{showCount < eventIds.length ? (
|
||||||
<div ref={bottomRef}>
|
<div ref={bottomRef}>
|
||||||
<NoteCardLoadingSkeleton isPictures={false} />
|
<NoteCardLoadingSkeleton isPictures={false} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center text-sm text-muted-foreground mt-2">
|
<div className="text-center text-sm text-muted-foreground mt-2">
|
||||||
{t('no more bookmarks')}
|
{t('no more bookmarks')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function BookmarkedNote({ eventId }: { eventId: string }) {
|
function BookmarkedNote({ eventId }: { eventId: string }) {
|
||||||
const { event, isFetching } = useFetchEvent(eventId)
|
const { event, isFetching } = useFetchEvent(eventId)
|
||||||
|
|
||||||
if (isFetching) {
|
if (isFetching) {
|
||||||
return <NoteCardLoadingSkeleton isPictures={false} />
|
return <NoteCardLoadingSkeleton isPictures={false} />
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!event || event.kind !== kinds.ShortTextNote) {
|
if (!event || event.kind !== kinds.ShortTextNote) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return <NoteCard event={event} className="w-full" />
|
return <NoteCard event={event} className="w-full" />
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,68 +1,68 @@
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { SimpleUserAvatar } from '@/components/UserAvatar'
|
import { SimpleUserAvatar } from '@/components/UserAvatar'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||||
import { Event } from 'nostr-tools'
|
import { Event } from 'nostr-tools'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export default function NewNotesButton({
|
export default function NewNotesButton({
|
||||||
newEvents = [],
|
newEvents = [],
|
||||||
onClick
|
onClick
|
||||||
}: {
|
}: {
|
||||||
newEvents?: Event[]
|
newEvents?: Event[]
|
||||||
onClick?: () => void
|
onClick?: () => void
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { isSmallScreen } = useScreenSize()
|
const { isSmallScreen } = useScreenSize()
|
||||||
const pubkeys = useMemo(() => {
|
const pubkeys = useMemo(() => {
|
||||||
const arr: string[] = []
|
const arr: string[] = []
|
||||||
for (const event of newEvents) {
|
for (const event of newEvents) {
|
||||||
if (!arr.includes(event.pubkey)) {
|
if (!arr.includes(event.pubkey)) {
|
||||||
arr.push(event.pubkey)
|
arr.push(event.pubkey)
|
||||||
}
|
}
|
||||||
if (arr.length >= 3) break
|
if (arr.length >= 3) break
|
||||||
}
|
}
|
||||||
return arr
|
return arr
|
||||||
}, [newEvents])
|
}, [newEvents])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{newEvents.length > 0 && (
|
{newEvents.length > 0 && (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full flex justify-center z-40 pointer-events-none',
|
'w-full flex justify-center z-40 pointer-events-none',
|
||||||
isSmallScreen ? 'fixed' : 'absolute bottom-4'
|
isSmallScreen ? 'fixed' : 'absolute bottom-4'
|
||||||
)}
|
)}
|
||||||
style={isSmallScreen ? { bottom: 'calc(4rem + env(safe-area-inset-bottom))' } : undefined}
|
style={isSmallScreen ? { bottom: 'calc(4rem + env(safe-area-inset-bottom))' } : undefined}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className="group rounded-full h-fit pl-2 pr-3 hover:bg-primary-hover pointer-events-auto"
|
className="group rounded-full h-fit pl-2 pr-3 hover:bg-primary-hover pointer-events-auto"
|
||||||
>
|
>
|
||||||
{pubkeys.length > 0 && (
|
{pubkeys.length > 0 && (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
{pubkeys.map((pubkey, index) => (
|
{pubkeys.map((pubkey, index) => (
|
||||||
<div
|
<div
|
||||||
key={pubkey}
|
key={pubkey}
|
||||||
className="relative -mr-2.5 last:mr-0"
|
className="relative -mr-2.5 last:mr-0"
|
||||||
style={{ zIndex: 3 - index }}
|
style={{ zIndex: 3 - index }}
|
||||||
>
|
>
|
||||||
<SimpleUserAvatar
|
<SimpleUserAvatar
|
||||||
userId={pubkey}
|
userId={pubkey}
|
||||||
size="small"
|
size="small"
|
||||||
className="border-primary border-2 group-hover:border-primary-hover"
|
className="border-primary border-2 group-hover:border-primary-hover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="text-md font-medium">
|
<div className="text-md font-medium">
|
||||||
{t('Show n new notes', { n: newEvents.length > 99 ? '99+' : newEvents.length })}
|
{t('Show n new notes', { n: newEvents.length > 99 ? '99+' : newEvents.length })}
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ export default function Likes({ event }: { event: Event }) {
|
||||||
like(key, emoji)
|
like(key, emoji)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{liking === key ? <Loader className="animate-spin size-5" /> : <Emoji emoji={emoji} />}
|
{liking === key ? <Loader className="animate-spin size-4" /> : <Emoji emoji={emoji} />}
|
||||||
<div className="text-sm">{pubkeys.size}</div>
|
<div className="text-sm">{pubkeys.size}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -1,65 +1,65 @@
|
||||||
import { createBookmarkDraftEvent } from '@/lib/draft-event'
|
import { createBookmarkDraftEvent } from '@/lib/draft-event'
|
||||||
import client from '@/services/client.service'
|
import client from '@/services/client.service'
|
||||||
import { createContext, useContext } from 'react'
|
import { createContext, useContext } from 'react'
|
||||||
import { useNostr } from './NostrProvider'
|
import { useNostr } from './NostrProvider'
|
||||||
import { Event } from 'nostr-tools'
|
import { Event } from 'nostr-tools'
|
||||||
|
|
||||||
type TBookmarksContext = {
|
type TBookmarksContext = {
|
||||||
addBookmark: (event: Event) => Promise<void>
|
addBookmark: (event: Event) => Promise<void>
|
||||||
removeBookmark: (event: Event) => Promise<void>
|
removeBookmark: (event: Event) => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
const BookmarksContext = createContext<TBookmarksContext | undefined>(undefined)
|
const BookmarksContext = createContext<TBookmarksContext | undefined>(undefined)
|
||||||
|
|
||||||
export const useBookmarks = () => {
|
export const useBookmarks = () => {
|
||||||
const context = useContext(BookmarksContext)
|
const context = useContext(BookmarksContext)
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error('useBookmarks must be used within a BookmarksProvider')
|
throw new Error('useBookmarks must be used within a BookmarksProvider')
|
||||||
}
|
}
|
||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BookmarksProvider({ children }: { children: React.ReactNode }) {
|
export function BookmarksProvider({ children }: { children: React.ReactNode }) {
|
||||||
const { pubkey: accountPubkey, publish, updateBookmarkListEvent } = useNostr()
|
const { pubkey: accountPubkey, publish, updateBookmarkListEvent } = useNostr()
|
||||||
|
|
||||||
const addBookmark = async (event: Event) => {
|
const addBookmark = async (event: Event) => {
|
||||||
if (!accountPubkey) return
|
if (!accountPubkey) return
|
||||||
|
|
||||||
const bookmarkListEvent = await client.fetchBookmarkListEvent(accountPubkey)
|
const bookmarkListEvent = await client.fetchBookmarkListEvent(accountPubkey)
|
||||||
const currentTags = bookmarkListEvent?.tags || []
|
const currentTags = bookmarkListEvent?.tags || []
|
||||||
|
|
||||||
if (currentTags.some((tag) => tag[0] === 'e' && tag[1] === event.id)) return
|
if (currentTags.some((tag) => tag[0] === 'e' && tag[1] === event.id)) return
|
||||||
|
|
||||||
const newBookmarkDraftEvent = createBookmarkDraftEvent(
|
const newBookmarkDraftEvent = createBookmarkDraftEvent(
|
||||||
[...currentTags, ['e', event.id, client.getEventHint(event.id), '', event.pubkey]],
|
[...currentTags, ['e', event.id, client.getEventHint(event.id), '', event.pubkey]],
|
||||||
bookmarkListEvent?.content
|
bookmarkListEvent?.content
|
||||||
)
|
)
|
||||||
const newBookmarkEvent = await publish(newBookmarkDraftEvent)
|
const newBookmarkEvent = await publish(newBookmarkDraftEvent)
|
||||||
await updateBookmarkListEvent(newBookmarkEvent)
|
await updateBookmarkListEvent(newBookmarkEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeBookmark = async (event: Event) => {
|
const removeBookmark = async (event: Event) => {
|
||||||
if (!accountPubkey) return
|
if (!accountPubkey) return
|
||||||
|
|
||||||
const bookmarkListEvent = await client.fetchBookmarkListEvent(accountPubkey)
|
const bookmarkListEvent = await client.fetchBookmarkListEvent(accountPubkey)
|
||||||
if (!bookmarkListEvent) return
|
if (!bookmarkListEvent) return
|
||||||
|
|
||||||
const newTags = bookmarkListEvent.tags.filter((tag) => !(tag[0] === 'e' && tag[1] === event.id))
|
const newTags = bookmarkListEvent.tags.filter((tag) => !(tag[0] === 'e' && tag[1] === event.id))
|
||||||
if (newTags.length === bookmarkListEvent.tags.length) return
|
if (newTags.length === bookmarkListEvent.tags.length) return
|
||||||
|
|
||||||
const newBookmarkDraftEvent = createBookmarkDraftEvent(newTags, bookmarkListEvent.content)
|
const newBookmarkDraftEvent = createBookmarkDraftEvent(newTags, bookmarkListEvent.content)
|
||||||
const newBookmarkEvent = await publish(newBookmarkDraftEvent)
|
const newBookmarkEvent = await publish(newBookmarkDraftEvent)
|
||||||
await updateBookmarkListEvent(newBookmarkEvent)
|
await updateBookmarkListEvent(newBookmarkEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BookmarksContext.Provider
|
<BookmarksContext.Provider
|
||||||
value={{
|
value={{
|
||||||
addBookmark,
|
addBookmark,
|
||||||
removeBookmark
|
removeBookmark
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</BookmarksContext.Provider>
|
</BookmarksContext.Provider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export default {
|
||||||
primary: {
|
primary: {
|
||||||
DEFAULT: 'hsl(var(--primary))',
|
DEFAULT: 'hsl(var(--primary))',
|
||||||
foreground: 'hsl(var(--primary-foreground))',
|
foreground: 'hsl(var(--primary-foreground))',
|
||||||
hover: 'hsl(var(--primary-hover))',
|
hover: 'hsl(var(--primary-hover))'
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
DEFAULT: 'hsl(var(--secondary))',
|
DEFAULT: 'hsl(var(--secondary))',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue