chore: implement message UI
This commit is contained in:
parent
5bb773c0e3
commit
32e3b43bc4
10 changed files with 221 additions and 22 deletions
|
@ -87,14 +87,17 @@
|
||||||
"*.{js,jsx,ts,tsx}": "eslint --fix"
|
"*.{js,jsx,ts,tsx}": "eslint --fix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@perfsee/jsonr": "^1.8.2",
|
||||||
"@radix-ui/react-avatar": "^1.0.3",
|
"@radix-ui/react-avatar": "^1.0.3",
|
||||||
"@radix-ui/react-hover-card": "^1.0.6",
|
"@radix-ui/react-hover-card": "^1.0.6",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
"@radix-ui/react-slot": "^1.0.2",
|
"@radix-ui/react-slot": "^1.0.2",
|
||||||
"class-variance-authority": "^0.6.1",
|
"class-variance-authority": "^0.6.1",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
|
"date-fns": "^2.30.0",
|
||||||
"lucide-react": "^0.263.0",
|
"lucide-react": "^0.263.0",
|
||||||
"peerjs": "^1.4.7",
|
"peerjs": "^1.4.7",
|
||||||
|
"react-nice-avatar": "^1.4.1",
|
||||||
"react-use": "^17.4.0",
|
"react-use": "^17.4.0",
|
||||||
"tailwind-merge": "^1.13.2",
|
"tailwind-merge": "^1.13.2",
|
||||||
"type-fest": "^3.13.0"
|
"type-fest": "^3.13.0"
|
||||||
|
|
|
@ -5,6 +5,9 @@ settings:
|
||||||
excludeLinksFromLockfile: false
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@perfsee/jsonr':
|
||||||
|
specifier: ^1.8.2
|
||||||
|
version: 1.8.2
|
||||||
'@radix-ui/react-avatar':
|
'@radix-ui/react-avatar':
|
||||||
specifier: ^1.0.3
|
specifier: ^1.0.3
|
||||||
version: 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
|
version: 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
@ -23,12 +26,18 @@ dependencies:
|
||||||
clsx:
|
clsx:
|
||||||
specifier: ^1.2.1
|
specifier: ^1.2.1
|
||||||
version: 1.2.1
|
version: 1.2.1
|
||||||
|
date-fns:
|
||||||
|
specifier: ^2.30.0
|
||||||
|
version: 2.30.0
|
||||||
lucide-react:
|
lucide-react:
|
||||||
specifier: ^0.263.0
|
specifier: ^0.263.0
|
||||||
version: 0.263.0(react@18.2.0)
|
version: 0.263.0(react@18.2.0)
|
||||||
peerjs:
|
peerjs:
|
||||||
specifier: ^1.4.7
|
specifier: ^1.4.7
|
||||||
version: 1.4.7
|
version: 1.4.7
|
||||||
|
react-nice-avatar:
|
||||||
|
specifier: ^1.4.1
|
||||||
|
version: 1.4.1(react@18.2.0)
|
||||||
react-use:
|
react-use:
|
||||||
specifier: ^17.4.0
|
specifier: ^17.4.0
|
||||||
version: 17.4.0(react-dom@18.2.0)(react@18.2.0)
|
version: 17.4.0(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
@ -1034,6 +1043,12 @@ packages:
|
||||||
fastq: 1.15.0
|
fastq: 1.15.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@perfsee/jsonr@1.8.2:
|
||||||
|
resolution: {integrity: sha512-16VkW9j0aH1MjKit7iD+X5uDcYMrEhAN8kE/wrvAJJ1b8KkCbivPU3bpDqszfjOGt8Hae21ukoz2+qPYSnqoqw==}
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.6.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@pkgjs/parseargs@0.11.0:
|
/@pkgjs/parseargs@0.11.0:
|
||||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
@ -2440,6 +2455,10 @@ packages:
|
||||||
fsevents: 2.3.2
|
fsevents: 2.3.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/chroma-js@2.4.2:
|
||||||
|
resolution: {integrity: sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/chrome-launcher@0.15.1:
|
/chrome-launcher@0.15.1:
|
||||||
resolution: {integrity: sha512-UugC8u59/w2AyX5sHLZUHoxBAiSiunUhZa3zZwMH6zPVis0C3dDKiRWyUGIo14tTbZHGVviWxv3PQWZ7taZ4fg==}
|
resolution: {integrity: sha512-UugC8u59/w2AyX5sHLZUHoxBAiSiunUhZa3zZwMH6zPVis0C3dDKiRWyUGIo14tTbZHGVviWxv3PQWZ7taZ4fg==}
|
||||||
engines: {node: '>=12.13.0'}
|
engines: {node: '>=12.13.0'}
|
||||||
|
@ -2843,6 +2862,13 @@ packages:
|
||||||
engines: {node: '>= 12'}
|
engines: {node: '>= 12'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/date-fns@2.30.0:
|
||||||
|
resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==}
|
||||||
|
engines: {node: '>=0.11'}
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.21.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/debounce@1.2.1:
|
/debounce@1.2.1:
|
||||||
resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==}
|
resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -5489,7 +5515,6 @@ packages:
|
||||||
/object-assign@4.1.1:
|
/object-assign@4.1.1:
|
||||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/object-hash@3.0.0:
|
/object-hash@3.0.0:
|
||||||
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
|
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
|
||||||
|
@ -6066,7 +6091,6 @@ packages:
|
||||||
loose-envify: 1.4.0
|
loose-envify: 1.4.0
|
||||||
object-assign: 4.1.1
|
object-assign: 4.1.1
|
||||||
react-is: 16.13.1
|
react-is: 16.13.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/proto-list@1.2.4:
|
/proto-list@1.2.4:
|
||||||
resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
|
resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
|
||||||
|
@ -6154,7 +6178,17 @@ packages:
|
||||||
|
|
||||||
/react-is@16.13.1:
|
/react-is@16.13.1:
|
||||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||||
dev: true
|
|
||||||
|
/react-nice-avatar@1.4.1(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-IKC51UTBbPm7rVsFNCiSEs/oP7W2/scod7/s8xCxqF/yMGS+Xs5HDAVxpyTEwv2iCYVoBq8H/E8SnbqwnWvupA==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.0.0'
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.21.0
|
||||||
|
chroma-js: 2.4.2
|
||||||
|
prop-types: 15.8.1
|
||||||
|
react: 18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react-refresh@0.14.0:
|
/react-refresh@0.14.0:
|
||||||
resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==}
|
resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==}
|
||||||
|
|
|
@ -6,7 +6,7 @@ export interface AppContainerProps {
|
||||||
|
|
||||||
const AppContainer: FC<AppContainerProps> = ({ children }) => {
|
const AppContainer: FC<AppContainerProps> = ({ children }) => {
|
||||||
return (
|
return (
|
||||||
<div className="fixed bottom-10 right-10 top-10 z-top box-border grid w-1/4 grid-flow-col grid-rows-[auto_1fr_auto] overflow-hidden rounded-xl bg-white shadow-2xl transition-transform">
|
<div className="fixed bottom-10 right-10 top-10 z-top box-border grid w-1/4 grid-flow-col grid-rows-[auto_1fr_auto] overflow-hidden rounded-xl bg-slate-50 shadow-2xl transition-transform">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { useState, type FC, type ChangeEvent } from 'react'
|
import { useState, type FC, type ChangeEvent } from 'react'
|
||||||
import { Textarea } from '@/components/ui/Textarea'
|
import { Textarea } from '@/components/ui/Textarea'
|
||||||
import { Button } from '@/components/ui/Button'
|
import { Button } from '@/components/ui/Button'
|
||||||
import { Smile, Command, CornerDownLeft } from 'lucide-react'
|
import { SmileIcon, CommandIcon, CornerDownLeftIcon } from 'lucide-react'
|
||||||
import { useBreakpoint } from '@/hooks/useBreakpoint'
|
import { useBreakpoint } from '@/hooks/useBreakpoint'
|
||||||
|
|
||||||
const Footer: FC = () => {
|
const Footer: FC = () => {
|
||||||
|
@ -13,22 +13,22 @@ const Footer: FC = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-2 gap-y-2 p-4">
|
<div className="grid grid-cols-2 gap-y-2 p-4">
|
||||||
<Textarea
|
<Textarea
|
||||||
className="col-span-2"
|
className="col-span-2 rounded-lg bg-gray-50"
|
||||||
rows={is2XL ? 3 : 2}
|
rows={is2XL ? 3 : 2}
|
||||||
value={message}
|
value={message}
|
||||||
placeholder="Type your message here."
|
placeholder="Type your message here."
|
||||||
onInput={handleInput}
|
onInput={handleInput}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button variant="ghost" size="sm" className="place-self-start">
|
<Button variant="ghost" size="icon" className="place-self-start">
|
||||||
<Smile size={20} />
|
<SmileIcon size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="sm" className="place-self-end">
|
<Button size="sm" className="place-self-end">
|
||||||
<span className="mr-2">Send</span>
|
<span className="mr-2">Send</span>
|
||||||
<Command className="text-slate-400" size={12}></Command>
|
<CommandIcon className="text-slate-400" size={12}></CommandIcon>
|
||||||
<CornerDownLeft className="text-slate-400" size={12}></CornerDownLeft>
|
<CornerDownLeftIcon className="text-slate-400" size={12}></CornerDownLeftIcon>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
import { useState, type FC } from 'react'
|
import { type FC } from 'react'
|
||||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/HoverCard'
|
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/HoverCard'
|
||||||
import { Button } from '@/components/ui/Button'
|
import { Button } from '@/components/ui/Button'
|
||||||
import getWebSiteInfo from '@/utils/getWebsiteInfo'
|
import getWebSiteInfo from '@/utils/getWebsiteInfo'
|
||||||
|
|
||||||
const Header: FC = ({ ...props }) => {
|
const Header: FC = () => {
|
||||||
const [websiteInfo] = useState(getWebSiteInfo())
|
const websiteInfo = getWebSiteInfo()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-12 items-center px-4 shadow-sm 2xl:h-14">
|
<div className="shadow-xs flex h-12 items-center bg-white px-4 2xl:h-14">
|
||||||
<img className="h-8 w-8 overflow-hidden rounded-full" src={websiteInfo.icon} />
|
<img className="h-8 w-8 overflow-hidden rounded-full" src={websiteInfo.icon} />
|
||||||
<HoverCard>
|
<HoverCard>
|
||||||
<HoverCardTrigger asChild>
|
<HoverCardTrigger asChild>
|
||||||
<Button className="overflow-hidden text-xl" variant="link">
|
<Button className="overflow-hidden" variant="link">
|
||||||
<h1 className="truncate">{websiteInfo.hostname}</h1>
|
<span className="truncate text-lg font-medium text-slate-600">{websiteInfo.hostname}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</HoverCardTrigger>
|
</HoverCardTrigger>
|
||||||
<HoverCardContent className="w-80">
|
<HoverCardContent className="w-80">
|
||||||
|
|
45
src/components/Main/LikeButton.tsx
Normal file
45
src/components/Main/LikeButton.tsx
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import { FrownIcon, type LucideIcon, ThumbsUpIcon } from 'lucide-react'
|
||||||
|
import { type MouseEvent, type FC } from 'react'
|
||||||
|
import { Button } from '@/components/ui/Button'
|
||||||
|
import { cn } from '@/utils'
|
||||||
|
|
||||||
|
export interface LikeButtonProps {
|
||||||
|
type: 'like' | 'hate'
|
||||||
|
count: number
|
||||||
|
checked: boolean
|
||||||
|
onClick?: (e: MouseEvent<HTMLButtonElement>) => void
|
||||||
|
onChange?: (checked: boolean, count: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const iconMapping: Record<LikeButtonProps['type'], LucideIcon> = {
|
||||||
|
like: ThumbsUpIcon,
|
||||||
|
hate: FrownIcon
|
||||||
|
}
|
||||||
|
|
||||||
|
const LikeButton: FC<LikeButtonProps> = ({ type, checked, count, onClick, onChange }) => {
|
||||||
|
const Icon = iconMapping[type]
|
||||||
|
|
||||||
|
const handleOnClick = (e: MouseEvent<HTMLButtonElement>) => {
|
||||||
|
onClick?.(e)
|
||||||
|
onChange?.(!checked, checked ? count - 1 : count + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
onClick={handleOnClick}
|
||||||
|
variant="secondary"
|
||||||
|
className={cn(
|
||||||
|
'flex items-center gap-x-1 rounded-full transition-all ',
|
||||||
|
checked ? 'text-orange-500' : 'text-slate-500'
|
||||||
|
)}
|
||||||
|
size="xs"
|
||||||
|
>
|
||||||
|
<Icon size={14} />
|
||||||
|
{!!count && <span className="text-xs">{count}</span>}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
LikeButton.displayName = 'LikeButton'
|
||||||
|
|
||||||
|
export default LikeButton
|
|
@ -1,7 +1,71 @@
|
||||||
import { type FC } from 'react'
|
import { type FC, useState } from 'react'
|
||||||
|
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/Avatar'
|
||||||
|
import { format } from 'date-fns'
|
||||||
|
|
||||||
const Message: FC = () => {
|
import LikeButton from './LikeButton'
|
||||||
return <div>Message</div>
|
export interface MessageProps {
|
||||||
|
data: {
|
||||||
|
id: string
|
||||||
|
body: string
|
||||||
|
username: string
|
||||||
|
avatar: string
|
||||||
|
date: number
|
||||||
|
likeChecked: boolean
|
||||||
|
hateChecked: boolean
|
||||||
|
likeCount: number
|
||||||
|
hateCount: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Message: FC<MessageProps> = ({ data }) => {
|
||||||
|
const [formatData, setFormatData] = useState({
|
||||||
|
...data,
|
||||||
|
date: format(data.date, 'yyyy/MM/dd HH:mm:ss')
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleLikeChange = (type: 'like' | 'hate', checked: boolean, count: number) => {
|
||||||
|
setFormatData((prev) => {
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
[`${type}Checked`]: checked,
|
||||||
|
[`${type}Count`]: count
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex w-full gap-x-2">
|
||||||
|
<Avatar>
|
||||||
|
<AvatarImage src={formatData.avatar} />
|
||||||
|
<AvatarFallback>{formatData.username}</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-baseline gap-x-2 leading-none">
|
||||||
|
<div className="text-sm font-medium text-slate-600">{formatData.username}</div>
|
||||||
|
<div className="text-xs text-slate-400">{formatData.date}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="pb-2">
|
||||||
|
<pre className="text-sm">{formatData.body}</pre>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-end gap-x-2 leading-none">
|
||||||
|
<LikeButton
|
||||||
|
type="like"
|
||||||
|
checked={formatData.likeChecked}
|
||||||
|
onChange={(...args) => handleLikeChange('like', ...args)}
|
||||||
|
count={formatData.likeCount}
|
||||||
|
></LikeButton>
|
||||||
|
<LikeButton
|
||||||
|
type="hate"
|
||||||
|
checked={formatData.hateChecked}
|
||||||
|
onChange={(...args) => handleLikeChange('hate', ...args)}
|
||||||
|
count={formatData.hateCount}
|
||||||
|
></LikeButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Message.displayName = 'Message'
|
Message.displayName = 'Message'
|
||||||
|
|
|
@ -1,7 +1,49 @@
|
||||||
import { type FC } from 'react'
|
import { type FC } from 'react'
|
||||||
|
import Message from './Message'
|
||||||
|
|
||||||
const Main: FC = () => {
|
const Main: FC = () => {
|
||||||
return <div>Main</div>
|
const messages = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
body: 'Who are you?',
|
||||||
|
username: 'molvqingtai',
|
||||||
|
avatar: 'https://github.com/shadcn.png',
|
||||||
|
date: Date.now(),
|
||||||
|
likeChecked: false,
|
||||||
|
hateChecked: false,
|
||||||
|
likeCount: 0,
|
||||||
|
hateCount: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
body: `I'm Chinese`,
|
||||||
|
username: 'Love XJP',
|
||||||
|
avatar: 'https://github.com/shadcn.png',
|
||||||
|
date: Date.now(),
|
||||||
|
likeChecked: false,
|
||||||
|
hateChecked: false,
|
||||||
|
likeCount: 0,
|
||||||
|
hateCount: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
body: 'Do you like XJP?',
|
||||||
|
username: 'molvqingtai',
|
||||||
|
avatar: 'https://github.com/shadcn.png',
|
||||||
|
date: Date.now(),
|
||||||
|
likeChecked: false,
|
||||||
|
hateChecked: false,
|
||||||
|
likeCount: 98,
|
||||||
|
hateCount: 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-y-4 p-4">
|
||||||
|
{messages.map((message) => (
|
||||||
|
<Message key={message.id} data={message} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Main.displayName = 'Main'
|
Main.displayName = 'Main'
|
||||||
|
|
|
@ -19,6 +19,7 @@ const buttonVariants = cva(
|
||||||
size: {
|
size: {
|
||||||
default: 'h-9 px-4 py-2',
|
default: 'h-9 px-4 py-2',
|
||||||
sm: 'h-8 rounded-md px-3 text-xs',
|
sm: 'h-8 rounded-md px-3 text-xs',
|
||||||
|
xs: 'h-6 rounded-md px-2 text-xs',
|
||||||
lg: 'h-10 rounded-md px-8',
|
lg: 'h-10 rounded-md px-8',
|
||||||
icon: 'h-9 w-9'
|
icon: 'h-9 w-9'
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,14 @@
|
||||||
const getWebSiteInfo = () => {
|
export interface WebSiteInfo {
|
||||||
|
host: string
|
||||||
|
hostname: string
|
||||||
|
href: string
|
||||||
|
origin: string
|
||||||
|
title: string
|
||||||
|
icon: string
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const getWebSiteInfo = (): WebSiteInfo => {
|
||||||
return {
|
return {
|
||||||
host: document.location.host,
|
host: document.location.host,
|
||||||
hostname: document.location.hostname,
|
hostname: document.location.hostname,
|
||||||
|
|
Loading…
Reference in a new issue