perf: optimize theme styles
This commit is contained in:
parent
bcdd435e45
commit
2d051fedd7
22 changed files with 105 additions and 72 deletions
|
@ -74,7 +74,6 @@
|
||||||
"idb-keyval": "^6.2.1",
|
"idb-keyval": "^6.2.1",
|
||||||
"lucide-react": "^0.453.0",
|
"lucide-react": "^0.453.0",
|
||||||
"nanoid": "^5.0.7",
|
"nanoid": "^5.0.7",
|
||||||
"next-themes": "^0.3.0",
|
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-hook-form": "^7.53.0",
|
"react-hook-form": "^7.53.0",
|
||||||
|
|
|
@ -98,9 +98,6 @@ importers:
|
||||||
nanoid:
|
nanoid:
|
||||||
specifier: ^5.0.7
|
specifier: ^5.0.7
|
||||||
version: 5.0.7
|
version: 5.0.7
|
||||||
next-themes:
|
|
||||||
specifier: ^0.3.0
|
|
||||||
version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
|
||||||
react:
|
react:
|
||||||
specifier: ^18.3.1
|
specifier: ^18.3.1
|
||||||
version: 18.3.1
|
version: 18.3.1
|
||||||
|
@ -4949,12 +4946,6 @@ packages:
|
||||||
resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==}
|
resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==}
|
||||||
engines: {node: '>= 0.4.0'}
|
engines: {node: '>= 0.4.0'}
|
||||||
|
|
||||||
next-themes@0.3.0:
|
|
||||||
resolution: {integrity: sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==}
|
|
||||||
peerDependencies:
|
|
||||||
react: ^16.8 || ^17 || ^18
|
|
||||||
react-dom: ^16.8 || ^17 || ^18
|
|
||||||
|
|
||||||
nice-try@1.0.5:
|
nice-try@1.0.5:
|
||||||
resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==}
|
resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==}
|
||||||
|
|
||||||
|
@ -12482,11 +12473,6 @@ snapshots:
|
||||||
|
|
||||||
netmask@2.0.2: {}
|
netmask@2.0.2: {}
|
||||||
|
|
||||||
next-themes@0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
|
||||||
dependencies:
|
|
||||||
react: 18.3.1
|
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
|
||||||
|
|
||||||
nice-try@1.0.5: {}
|
nice-try@1.0.5: {}
|
||||||
|
|
||||||
no-case@3.0.4:
|
no-case@3.0.4:
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { AnimatePresence, motion } from 'framer-motion'
|
||||||
import DanmakuContainer from './components/DanmakuContainer'
|
import DanmakuContainer from './components/DanmakuContainer'
|
||||||
import DanmakuDomain from '@/domain/Danmaku'
|
import DanmakuDomain from '@/domain/Danmaku'
|
||||||
import AppStatusDomain from '@/domain/AppStatus'
|
import AppStatusDomain from '@/domain/AppStatus'
|
||||||
|
import { cn } from '@/utils'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fix requestAnimationFrame error in jest
|
* Fix requestAnimationFrame error in jest
|
||||||
|
@ -63,24 +64,29 @@ export default function App() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
appStatusLoadIsFinished && (
|
appStatusLoadIsFinished && (
|
||||||
<>
|
<div id="app" className={cn('contents', userInfo?.themeMode)}>
|
||||||
<AppMain className={userInfo?.themeMode}>
|
<AppMain>
|
||||||
<Header />
|
<Header />
|
||||||
<Main />
|
<Main />
|
||||||
<Footer />
|
<Footer />
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{notUserInfo && (
|
{notUserInfo && (
|
||||||
<motion.div initial={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.3 }}>
|
<motion.div
|
||||||
|
className="contents"
|
||||||
|
initial={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
>
|
||||||
<Setup></Setup>
|
<Setup></Setup>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
<Toaster richColors offset="70px" visibleToasts={1} position="top-center"></Toaster>
|
<Toaster richColors offset="70px" visibleToasts={1} position="top-center"></Toaster>
|
||||||
</AppMain>
|
</AppMain>
|
||||||
<AppButton className={userInfo?.themeMode}></AppButton>
|
<AppButton></AppButton>
|
||||||
|
|
||||||
<DanmakuContainer ref={danmakuContainerRef} className={userInfo?.themeMode} />
|
<DanmakuContainer ref={danmakuContainerRef} />
|
||||||
</>
|
</div>
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ export interface EmojiButtonProps {
|
||||||
onSelect?: (value: string) => void
|
onSelect?: (value: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const emojiGroups = chunk([...EMOJI_LIST], 8)
|
const emojiGroups = chunk([...EMOJI_LIST], 6)
|
||||||
|
|
||||||
// BUG: https://github.com/radix-ui/primitives/pull/2433
|
// BUG: https://github.com/radix-ui/primitives/pull/2433
|
||||||
// BUG https://github.com/radix-ui/primitives/issues/1666
|
// BUG https://github.com/radix-ui/primitives/issues/1666
|
||||||
|
@ -34,16 +34,19 @@ const EmojiButton: FC<EmojiButtonProps> = ({ onSelect }) => {
|
||||||
<SmileIcon size={20} />
|
<SmileIcon size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="z-infinity w-72 rounded-xl px-0" onCloseAutoFocus={handleCloseAutoFocus}>
|
<PopoverContent
|
||||||
<ScrollArea className="size-72 px-3">
|
className="z-infinity w-64 overflow-hidden rounded-xl p-0 dark:bg-slate-900"
|
||||||
|
onCloseAutoFocus={handleCloseAutoFocus}
|
||||||
|
>
|
||||||
|
<ScrollArea className="size-64 p-1">
|
||||||
{emojiGroups.map((group, index) => {
|
{emojiGroups.map((group, index) => {
|
||||||
return (
|
return (
|
||||||
<div key={index} className="grid grid-cols-8">
|
<div key={index} className="grid grid-cols-6">
|
||||||
{group.map((emoji, index) => (
|
{group.map((emoji, index) => (
|
||||||
<Button
|
<Button
|
||||||
key={index}
|
key={index}
|
||||||
size="sm"
|
size="icon"
|
||||||
className="text-base"
|
className="text-xl"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => handleSelect(emoji)}
|
onClick={() => handleSelect(emoji)}
|
||||||
>
|
>
|
||||||
|
|
|
@ -33,8 +33,8 @@ const LikeButton: FC<LikeButtonProps> & { Icon: FC<LikeButtonIconProps> } = ({
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className={cn(
|
className={cn(
|
||||||
'grid items-center overflow-hidden rounded-full leading-none transition-all select-none dark:bg-slate-200',
|
'grid items-center overflow-hidden rounded-full leading-none transition-all select-none dark:bg-slate-600',
|
||||||
checked ? 'text-orange-500' : 'text-slate-500',
|
checked ? 'text-orange-500' : 'text-slate-500 dark:text-slate-100',
|
||||||
count ? 'grid-cols-[auto_1fr] gap-x-1' : 'grid-cols-[auto_0fr] gap-x-0'
|
count ? 'grid-cols-[auto_1fr] gap-x-1' : 'grid-cols-[auto_0fr] gap-x-0'
|
||||||
)}
|
)}
|
||||||
size="xs"
|
size="xs"
|
||||||
|
|
|
@ -12,7 +12,7 @@ const MessageList: FC<MessageListProps> = ({ children }) => {
|
||||||
const [scrollParentRef, setScrollParentRef] = useState<HTMLDivElement | null>(null)
|
const [scrollParentRef, setScrollParentRef] = useState<HTMLDivElement | null>(null)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollArea ref={setScrollParentRef}>
|
<ScrollArea ref={setScrollParentRef} className="dark:bg-slate-900">
|
||||||
<Virtuoso
|
<Virtuoso
|
||||||
defaultItemHeight={108}
|
defaultItemHeight={108}
|
||||||
followOutput={(isAtBottom: boolean) => (isAtBottom ? 'smooth' : 'auto')}
|
followOutput={(isAtBottom: boolean) => (isAtBottom ? 'smooth' : 'auto')}
|
||||||
|
|
|
@ -13,10 +13,7 @@ export interface PromptItemProps {
|
||||||
const PromptItem: FC<PromptItemProps> = ({ data, className }) => {
|
const PromptItem: FC<PromptItemProps> = ({ data, className }) => {
|
||||||
return (
|
return (
|
||||||
<div className={cn('flex justify-center py-1 px-4 ', className)}>
|
<div className={cn('flex justify-center py-1 px-4 ', className)}>
|
||||||
<Badge
|
<Badge variant="secondary" className="gap-x-2 rounded-full px-2 font-medium text-slate-400 dark:bg-slate-800">
|
||||||
variant="secondary"
|
|
||||||
className="gap-x-2 rounded-full px-2 font-medium text-slate-400 dark:bg-slate-600 dark:text-slate-50"
|
|
||||||
>
|
|
||||||
<Avatar className="size-4">
|
<Avatar className="size-4">
|
||||||
<AvatarImage src={data.userAvatar} className="size-full" alt="avatar" />
|
<AvatarImage src={data.userAvatar} className="size-full" alt="avatar" />
|
||||||
<AvatarFallback>{data.username.at(0)}</AvatarFallback>
|
<AvatarFallback>{data.username.at(0)}</AvatarFallback>
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react'
|
||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
import { Remesh } from 'remesh'
|
import { Remesh } from 'remesh'
|
||||||
import { RemeshRoot, RemeshScope } from 'remesh-react'
|
import { RemeshRoot, RemeshScope } from 'remesh-react'
|
||||||
import { RemeshLogger } from 'remesh-logger'
|
// import { RemeshLogger } from 'remesh-logger'
|
||||||
import { defineContentScript } from 'wxt/sandbox'
|
import { defineContentScript } from 'wxt/sandbox'
|
||||||
import { createShadowRootUi } from 'wxt/client'
|
import { createShadowRootUi } from 'wxt/client'
|
||||||
|
|
||||||
|
@ -15,8 +15,8 @@ import { ToastImpl } from '@/domain/impls/Toast'
|
||||||
import { PeerRoomImpl } from '@/domain/impls/PeerRoom2'
|
import { PeerRoomImpl } from '@/domain/impls/PeerRoom2'
|
||||||
import '@/assets/styles/tailwind.css'
|
import '@/assets/styles/tailwind.css'
|
||||||
import '@/assets/styles/sonner.css'
|
import '@/assets/styles/sonner.css'
|
||||||
import { createElement } from '@/utils'
|
|
||||||
import NotificationDomain from '@/domain/Notification'
|
import NotificationDomain from '@/domain/Notification'
|
||||||
|
import { createElement } from '@/utils'
|
||||||
|
|
||||||
export default defineContentScript({
|
export default defineContentScript({
|
||||||
cssInjectionMode: 'ui',
|
cssInjectionMode: 'ui',
|
||||||
|
@ -24,6 +24,13 @@ export default defineContentScript({
|
||||||
matches: ['https://*/*'],
|
matches: ['https://*/*'],
|
||||||
excludeMatches: ['*://localhost/*', '*://127.0.0.1/*', '*://*.csdn.net/*', '*://*.csdn.com/*'],
|
excludeMatches: ['*://localhost/*', '*://127.0.0.1/*', '*://*.csdn.net/*', '*://*.csdn.com/*'],
|
||||||
async main(ctx) {
|
async main(ctx) {
|
||||||
|
window.CSS.registerProperty({
|
||||||
|
name: '--shimmer-angle',
|
||||||
|
syntax: '<angle>',
|
||||||
|
inherits: false,
|
||||||
|
initialValue: '0deg'
|
||||||
|
})
|
||||||
|
|
||||||
const store = Remesh.store({
|
const store = Remesh.store({
|
||||||
externs: [
|
externs: [
|
||||||
LocalStorageImpl,
|
LocalStorageImpl,
|
||||||
|
@ -45,9 +52,8 @@ export default defineContentScript({
|
||||||
mode: 'open',
|
mode: 'open',
|
||||||
isolateEvents: ['keyup', 'keydown', 'keypress'],
|
isolateEvents: ['keyup', 'keydown', 'keypress'],
|
||||||
onMount: (container) => {
|
onMount: (container) => {
|
||||||
const app = createElement('<div id="app"></div>')
|
const app = createElement('<div id="root"></div>')
|
||||||
container.append(app)
|
container.append(app)
|
||||||
|
|
||||||
const root = createRoot(app)
|
const root = createRoot(app)
|
||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { type FC, useState, type MouseEvent, useLayoutEffect, useEffect } from 'react'
|
import { type FC, useState, type MouseEvent, useEffect } from 'react'
|
||||||
import { SettingsIcon, MoonIcon, SunIcon, HandIcon } from 'lucide-react'
|
import { SettingsIcon, MoonIcon, SunIcon, HandIcon } from 'lucide-react'
|
||||||
import { motion, AnimatePresence } from 'framer-motion'
|
import { motion, AnimatePresence } from 'framer-motion'
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ import AppStatusDomain from '@/domain/AppStatus'
|
||||||
import { getDay } from 'date-fns'
|
import { getDay } from 'date-fns'
|
||||||
import { messenger } from '@/messenger'
|
import { messenger } from '@/messenger'
|
||||||
import useDarg from '@/hooks/useDarg'
|
import useDarg from '@/hooks/useDarg'
|
||||||
|
import useWindowResize from '@/hooks/useWindowResize'
|
||||||
|
|
||||||
export interface AppButtonProps {
|
export interface AppButtonProps {
|
||||||
className?: string
|
className?: string
|
||||||
|
@ -47,21 +48,17 @@ const AppButton: FC<AppButtonProps> = ({ className }) => {
|
||||||
} = useDarg({
|
} = useDarg({
|
||||||
initX: appPosition.x,
|
initX: appPosition.x,
|
||||||
initY: appPosition.y,
|
initY: appPosition.y,
|
||||||
minX: 44,
|
minX: 50,
|
||||||
maxX: window.innerWidth - 44,
|
maxX: window.innerWidth - 50,
|
||||||
maxY: window.innerHeight - 22,
|
maxY: window.innerHeight - 22,
|
||||||
minY: window.innerHeight / 2
|
minY: window.innerHeight / 2
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useWindowResize(({ width, height }) => {
|
||||||
const handler = () => {
|
send(appStatusDomain.command.UpdatePositionCommand({ x: width - 50, y: height - 22 }))
|
||||||
send(appStatusDomain.command.UpdatePositionCommand({ x: window.innerWidth - 44, y: window.innerHeight - 22 }))
|
})
|
||||||
}
|
|
||||||
window.addEventListener('resize', handler)
|
|
||||||
return () => window.removeEventListener('resize', handler)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useEffect(() => {
|
||||||
send(appStatusDomain.command.UpdatePositionCommand({ x, y }))
|
send(appStatusDomain.command.UpdatePositionCommand({ x, y }))
|
||||||
}, [x, y])
|
}, [x, y])
|
||||||
|
|
||||||
|
@ -116,7 +113,7 @@ const AppButton: FC<AppButtonProps> = ({ className }) => {
|
||||||
className={cn(
|
className={cn(
|
||||||
'absolute grid grid-rows-[repeat(2,minmax(0,2.5rem))] w-full justify-center items-center transition-all duration-500',
|
'absolute grid grid-rows-[repeat(2,minmax(0,2.5rem))] w-full justify-center items-center transition-all duration-500',
|
||||||
isDarkMode ? 'top-0' : '-top-10',
|
isDarkMode ? 'top-0' : '-top-10',
|
||||||
isDarkMode ? 'bg-slate-800 text-white' : 'bg-white text-orange-400'
|
isDarkMode ? 'bg-slate-950 text-white' : 'bg-white text-orange-400'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<MoonIcon size={20} />
|
<MoonIcon size={20} />
|
||||||
|
@ -136,7 +133,7 @@ const AppButton: FC<AppButtonProps> = ({ className }) => {
|
||||||
<Button
|
<Button
|
||||||
onClick={handleToggleApp}
|
onClick={handleToggleApp}
|
||||||
onContextMenu={handleToggleMenu}
|
onContextMenu={handleToggleMenu}
|
||||||
className="relative z-20 size-11 rounded-full p-0 text-xs shadow-lg shadow-slate-500/50"
|
className="relative z-20 size-11 rounded-full p-0 text-xs shadow-lg shadow-slate-500/50 after:absolute after:-inset-0.5 after:z-10 after:animate-[shimmer_2s_linear_infinite] after:rounded-full after:bg-[conic-gradient(from_var(--shimmer-angle),theme(colors.slate.500)_0%,theme(colors.white)_10%,theme(colors.slate.500)_20%)]"
|
||||||
>
|
>
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{hasUnreadQuery && (
|
{hasUnreadQuery && (
|
||||||
|
@ -145,7 +142,7 @@ const AppButton: FC<AppButtonProps> = ({ className }) => {
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
transition={{ duration: 0.1 }}
|
transition={{ duration: 0.1 }}
|
||||||
className="absolute -right-1 -top-1 flex size-5 items-center justify-center"
|
className="absolute -right-1 -top-1 z-30 flex size-5 items-center justify-center"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={cn('absolute inline-flex size-full animate-ping rounded-full opacity-75', 'bg-orange-400')}
|
className={cn('absolute inline-flex size-full animate-ping rounded-full opacity-75', 'bg-orange-400')}
|
||||||
|
@ -154,7 +151,8 @@ const AppButton: FC<AppButtonProps> = ({ className }) => {
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
<DayLogo className="max-h-full max-w-full"></DayLogo>
|
|
||||||
|
<DayLogo className="relative z-20 max-h-full max-w-full overflow-hidden"></DayLogo>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { motion, AnimatePresence } from 'framer-motion'
|
||||||
import AppStatusDomain from '@/domain/AppStatus'
|
import AppStatusDomain from '@/domain/AppStatus'
|
||||||
import { useRemeshDomain, useRemeshQuery } from 'remesh-react'
|
import { useRemeshDomain, useRemeshQuery } from 'remesh-react'
|
||||||
import { cn } from '@/utils'
|
import { cn } from '@/utils'
|
||||||
import { useWindowSize } from 'react-use'
|
import useWindowResize from '@/hooks/useWindowResize'
|
||||||
|
|
||||||
export interface AppMainProps {
|
export interface AppMainProps {
|
||||||
children?: ReactNode
|
children?: ReactNode
|
||||||
|
@ -16,9 +16,9 @@ const AppMain: FC<AppMainProps> = ({ children, className }) => {
|
||||||
const appOpenStatus = useRemeshQuery(appStatusDomain.query.OpenQuery())
|
const appOpenStatus = useRemeshQuery(appStatusDomain.query.OpenQuery())
|
||||||
const { x, y } = useRemeshQuery(appStatusDomain.query.PositionQuery())
|
const { x, y } = useRemeshQuery(appStatusDomain.query.PositionQuery())
|
||||||
|
|
||||||
const { width } = useWindowSize()
|
const { width } = useWindowResize()
|
||||||
|
|
||||||
const isOnRightSide = x >= width / 2 + 44
|
const isOnRightSide = x >= width / 2 + 50
|
||||||
|
|
||||||
const { size, setRef } = useResizable({
|
const { size, setRef } = useResizable({
|
||||||
initSize: Math.max(375, width / 6),
|
initSize: Math.max(375, width / 6),
|
||||||
|
@ -54,7 +54,7 @@ const AppMain: FC<AppMainProps> = ({ children, className }) => {
|
||||||
<div
|
<div
|
||||||
ref={setRef}
|
ref={setRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
'absolute inset-y-3 z-infinity w-1 dark:bg-slate-500 cursor-ew-resize rounded-xl bg-slate-100 opacity-0 shadow transition-opacity duration-200 ease-in hover:opacity-100',
|
'absolute inset-y-3 z-infinity w-1 dark:bg-slate-600 cursor-ew-resize rounded-xl bg-slate-100 opacity-0 shadow transition-opacity duration-200 ease-in hover:opacity-100',
|
||||||
isOnRightSide ? '-left-0.5' : '-right-0.5'
|
isOnRightSide ? '-left-0.5' : '-right-0.5'
|
||||||
)}
|
)}
|
||||||
></div>
|
></div>
|
||||||
|
|
|
@ -15,7 +15,7 @@ import useTriggerAway from '@/hooks/useTriggerAway'
|
||||||
import { ScrollArea } from '@/components/ui/ScrollArea'
|
import { ScrollArea } from '@/components/ui/ScrollArea'
|
||||||
import { Virtuoso, VirtuosoHandle } from 'react-virtuoso'
|
import { Virtuoso, VirtuosoHandle } from 'react-virtuoso'
|
||||||
import UserInfoDomain from '@/domain/UserInfo'
|
import UserInfoDomain from '@/domain/UserInfo'
|
||||||
import { cn, getTextSimilarity } from '@/utils'
|
import { cn, getRootNode, getTextSimilarity } from '@/utils'
|
||||||
import { Avatar, AvatarFallback } from '@/components/ui/Avatar'
|
import { Avatar, AvatarFallback } from '@/components/ui/Avatar'
|
||||||
import { AvatarImage } from '@radix-ui/react-avatar'
|
import { AvatarImage } from '@radix-ui/react-avatar'
|
||||||
import ToastDomain from '@/domain/Toast'
|
import ToastDomain from '@/domain/Toast'
|
||||||
|
@ -240,7 +240,7 @@ const Footer: FC = () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const root = document.querySelector(__NAME__)?.shadowRoot
|
const root = getRootNode()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative z-10 grid gap-y-2 rounded-b-xl px-4 pb-4 pt-2 before:pointer-events-none before:absolute before:inset-x-4 before:-top-2 before:h-2 before:bg-gradient-to-t before:from-slate-50 before:from-30% before:to-transparent dark:bg-slate-900 before:dark:from-slate-900">
|
<div className="relative z-10 grid gap-y-2 rounded-b-xl px-4 pb-4 pt-2 before:pointer-events-none before:absolute before:inset-x-4 before:-top-2 before:h-2 before:bg-gradient-to-t before:from-slate-50 before:from-30% before:to-transparent dark:bg-slate-900 before:dark:from-slate-900">
|
||||||
|
@ -248,8 +248,8 @@ const Footer: FC = () => {
|
||||||
<Portal
|
<Portal
|
||||||
container={root}
|
container={root}
|
||||||
ref={shareAutoCompleteListRef}
|
ref={shareAutoCompleteListRef}
|
||||||
className="fixed z-infinity w-36 -translate-y-full rounded-lg border bg-popover text-popover-foreground shadow-md duration-300 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95"
|
className="fixed z-infinity w-36 -translate-y-full overflow-hidden rounded-lg border bg-popover text-popover-foreground shadow-md duration-300 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95"
|
||||||
style={{ left: `min(${x}px, 100vw - 212px)`, top: `${y}px` }}
|
style={{ left: `min(${x}px, 100vw - 160px)`, top: `${y}px` }}
|
||||||
>
|
>
|
||||||
<ScrollArea className="max-h-[204px] min-h-9 p-1" ref={setScrollParentRef}>
|
<ScrollArea className="max-h-[204px] min-h-9 p-1" ref={setScrollParentRef}>
|
||||||
<Virtuoso
|
<Virtuoso
|
||||||
|
|
|
@ -19,7 +19,7 @@ const Header: FC = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="z-10 grid h-12 grid-flow-col grid-cols-[theme('spacing.20')_auto_theme('spacing.20')] items-center justify-between rounded-t-xl bg-white px-4 backdrop-blur-lg dark:bg-slate-950">
|
<div className="z-10 grid h-12 grid-flow-col grid-cols-[theme('spacing.20')_auto_theme('spacing.20')] items-center justify-between rounded-t-xl bg-white px-4 backdrop-blur-lg dark:bg-slate-950">
|
||||||
<Avatar className="size-8 dark:text-slate-50">
|
<Avatar className="size-8">
|
||||||
<AvatarImage src={siteInfo.icon} alt="favicon" />
|
<AvatarImage src={siteInfo.icon} alt="favicon" />
|
||||||
<AvatarFallback>
|
<AvatarFallback>
|
||||||
<Globe2Icon size="100%" className="text-gray-400" />
|
<Globe2Icon size="100%" className="text-gray-400" />
|
||||||
|
|
|
@ -16,7 +16,7 @@ function App() {
|
||||||
<VersionLink></VersionLink>
|
<VersionLink></VersionLink>
|
||||||
<Main>
|
<Main>
|
||||||
<ProfileForm></ProfileForm>
|
<ProfileForm></ProfileForm>
|
||||||
<Toaster richColors position="top-center" />
|
<Toaster richColors position="top-center" duration={1000000} />
|
||||||
</Main>
|
</Main>
|
||||||
<BadgeList></BadgeList>
|
<BadgeList></BadgeList>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
@ -82,3 +82,9 @@
|
||||||
direction: ltr !important;
|
direction: ltr !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* @property --shimmer-angle {
|
||||||
|
syntax: '<angle>';
|
||||||
|
inherits: false;
|
||||||
|
initial-value: 0deg;
|
||||||
|
} */
|
||||||
|
|
|
@ -113,7 +113,7 @@ const Markdown: FC<MarkdownProps> = ({ children = '', className }) => {
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
remarkPlugins={[remarkGfm, remarkBreaks]}
|
remarkPlugins={[remarkGfm, remarkBreaks]}
|
||||||
className={cn(className, 'prose prose-sm prose-slate break-words dark:text-slate-400')}
|
className={cn(className, 'prose prose-sm prose-slate break-words dark:text-slate-50')}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import * as PopoverPrimitive from '@radix-ui/react-popover'
|
import * as PopoverPrimitive from '@radix-ui/react-popover'
|
||||||
import { cn } from '@/utils/index'
|
import { cn, getRootNode } from '@/utils'
|
||||||
|
|
||||||
const Popover = PopoverPrimitive.Root
|
const Popover = PopoverPrimitive.Root
|
||||||
|
|
||||||
|
@ -10,8 +10,7 @@ const PopoverContent = React.forwardRef<
|
||||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||||
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => {
|
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => {
|
||||||
const root = document.querySelector(__NAME__)?.shadowRoot
|
const root = getRootNode()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PopoverPrimitive.Portal container={root}>
|
<PopoverPrimitive.Portal container={root}>
|
||||||
<PopoverPrimitive.Content
|
<PopoverPrimitive.Content
|
||||||
|
|
|
@ -7,10 +7,7 @@ const ScrollArea = React.forwardRef<
|
||||||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> & { scrollLock?: boolean }
|
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> & { scrollLock?: boolean }
|
||||||
>(({ className, children, scrollLock = true, ...props }, ref) => (
|
>(({ className, children, scrollLock = true, ...props }, ref) => (
|
||||||
<ScrollAreaPrimitive.Root
|
<ScrollAreaPrimitive.Root className={cn('relative grid grid-rows-[1fr] overflow-hidden', className)} {...props}>
|
||||||
className={cn('relative grid grid-rows-[1fr] overflow-hidden dark:bg-slate-900', className)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<ScrollAreaPrimitive.Viewport
|
<ScrollAreaPrimitive.Viewport
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn('size-full rounded-[inherit]', scrollLock ? 'overscroll-none' : 'overscroll-auto')}
|
className={cn('size-full rounded-[inherit]', scrollLock ? 'overscroll-none' : 'overscroll-auto')}
|
||||||
|
|
|
@ -15,7 +15,7 @@ export interface AppStatus {
|
||||||
export const defaultStatusState = {
|
export const defaultStatusState = {
|
||||||
open: false,
|
open: false,
|
||||||
unread: 0,
|
unread: 0,
|
||||||
position: { x: window.innerWidth - 44, y: window.innerHeight - 22 }
|
position: { x: window.innerWidth - 50, y: window.innerHeight - 22 }
|
||||||
}
|
}
|
||||||
|
|
||||||
const AppStatusDomain = Remesh.domain({
|
const AppStatusDomain = Remesh.domain({
|
||||||
|
|
22
src/hooks/useWindowResize.ts
Normal file
22
src/hooks/useWindowResize.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
const useWindowResize = (callback?: ({ width, height }: { width: number; height: number }) => void) => {
|
||||||
|
const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight })
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handler = () => {
|
||||||
|
const width = window.innerWidth
|
||||||
|
const height = window.innerHeight
|
||||||
|
setSize({ width, height })
|
||||||
|
callback?.({ width, height })
|
||||||
|
}
|
||||||
|
window.addEventListener('resize', handler)
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', handler)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useWindowResize
|
5
src/utils/getRootNode.ts
Normal file
5
src/utils/getRootNode.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export const getRootNode = () => {
|
||||||
|
return document.querySelector(__NAME__)?.shadowRoot?.querySelector('#app') || document.body
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getRootNode
|
|
@ -13,3 +13,4 @@ export { default as generateRandomAvatar } from './generateRandomAvatar'
|
||||||
export { default as generateRandomName } from './generateRandomName'
|
export { default as generateRandomName } from './generateRandomName'
|
||||||
export { default as getCursorPosition } from './getCursorPosition'
|
export { default as getCursorPosition } from './getCursorPosition'
|
||||||
export { default as getTextSimilarity } from './getTextSimilarity'
|
export { default as getTextSimilarity } from './getTextSimilarity'
|
||||||
|
export { default as getRootNode } from './getRootNode'
|
||||||
|
|
|
@ -81,6 +81,14 @@ export default {
|
||||||
transform: 'rotate(215deg) translateX(-500px)',
|
transform: 'rotate(215deg) translateX(-500px)',
|
||||||
opacity: '0'
|
opacity: '0'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
shimmer: {
|
||||||
|
'0%': {
|
||||||
|
'--shimmer-angle': '0deg'
|
||||||
|
},
|
||||||
|
'100%': {
|
||||||
|
'--shimmer-angle': '360deg'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
animation: {
|
animation: {
|
||||||
|
|
Loading…
Reference in a new issue