feat: support app rename and make app card ui better (#766)
Co-authored-by: Gillian97 <jinling.sunshine@gmail.com>
This commit is contained in:
@@ -6,7 +6,6 @@ import { useContext } from 'use-context-selector'
|
||||
import { UserCircleIcon } from '@heroicons/react/24/solid'
|
||||
import cn from 'classnames'
|
||||
import type { DisplayScene, FeedbackFunc, Feedbacktype, IChatItem, SubmitAnnotationFunc, ThoughtItem } from '../type'
|
||||
import { randomString } from '../../../app-sidebar/basic'
|
||||
import OperationBtn from '../operation'
|
||||
import LoadingAnim from '../loading-anim'
|
||||
import { EditIcon, EditIconSolid, OpeningStatementIcon, RatingIcon } from '../icon-component'
|
||||
@@ -14,6 +13,7 @@ import s from '../style.module.css'
|
||||
import MoreInfo from '../more-info'
|
||||
import CopyBtn from '../copy-btn'
|
||||
import Thought from '../thought'
|
||||
import { randomString } from '@/utils'
|
||||
import type { Annotation, MessageRating } from '@/models/log'
|
||||
import AppContext from '@/context/app-context'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
|
||||
@@ -16,8 +16,8 @@ import dayjs from 'dayjs'
|
||||
import { createContext, useContext } from 'use-context-selector'
|
||||
import classNames from 'classnames'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { randomString } from '../../app-sidebar/basic'
|
||||
import s from './style.module.css'
|
||||
import { randomString } from '@/utils'
|
||||
import { EditIconSolid } from '@/app/components/app/chat/icon-component'
|
||||
import type { FeedbackFunc, Feedbacktype, IChatItem, SubmitAnnotationFunc } from '@/app/components/app/chat/type'
|
||||
import type { Annotation, ChatConversationFullDetailResponse, ChatConversationGeneralDetail, ChatConversationsResponse, ChatMessage, ChatMessagesRequest, CompletionConversationFullDetailResponse, CompletionConversationGeneralDetail, CompletionConversationsResponse } from '@/models/log'
|
||||
|
||||
@@ -4,30 +4,32 @@ import React, { useMemo, useState } from 'react'
|
||||
import {
|
||||
Cog8ToothIcon,
|
||||
DocumentTextIcon,
|
||||
PaintBrushIcon,
|
||||
RocketLaunchIcon,
|
||||
ShareIcon,
|
||||
} from '@heroicons/react/24/outline'
|
||||
import { SparklesIcon } from '@heroicons/react/24/solid'
|
||||
import { usePathname, useRouter } from 'next/navigation'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import SettingsModal from './settings'
|
||||
import ShareLink from './share-link'
|
||||
import EmbeddedModal from './embedded'
|
||||
import CustomizeModal from './customize'
|
||||
import style from './style.module.css'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import AppBasic, { randomString } from '@/app/components/app-sidebar/basic'
|
||||
import AppBasic from '@/app/components/app-sidebar/basic'
|
||||
import { asyncRunSafe, randomString } from '@/utils'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Tag from '@/app/components/base/tag'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import CopyFeedback from '@/app/components/base/copy-feedback'
|
||||
import SecretKeyButton from '@/app/components/develop/secret-key/secret-key-button'
|
||||
import type { AppDetailResponse } from '@/models/app'
|
||||
import './style.css'
|
||||
import { AppType } from '@/types/app'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
|
||||
export type IAppCardProps = {
|
||||
className?: string
|
||||
appInfo: AppDetailResponse
|
||||
cardType?: 'app' | 'api' | 'webapp'
|
||||
cardType?: 'api' | 'webapp'
|
||||
customBgColor?: string
|
||||
onChangeStatus: (val: boolean) => Promise<any>
|
||||
onSaveSiteConfig?: (params: any) => Promise<any>
|
||||
@@ -35,12 +37,12 @@ export type IAppCardProps = {
|
||||
}
|
||||
|
||||
const EmbedIcon: FC<{ className?: string }> = ({ className = '' }) => {
|
||||
return <div className={`codeBrowserIcon ${className}`}></div>
|
||||
return <div className={`${style.codeBrowserIcon} ${className}`}></div>
|
||||
}
|
||||
|
||||
function AppCard({
|
||||
appInfo,
|
||||
cardType = 'app',
|
||||
cardType = 'webapp',
|
||||
customBgColor,
|
||||
onChangeStatus,
|
||||
onSaveSiteConfig,
|
||||
@@ -51,16 +53,16 @@ function AppCard({
|
||||
const pathname = usePathname()
|
||||
const { currentWorkspace, isCurrentWorkspaceManager } = useAppContext()
|
||||
const [showSettingsModal, setShowSettingsModal] = useState(false)
|
||||
const [showShareModal, setShowShareModal] = useState(false)
|
||||
const [showEmbedded, setShowEmbedded] = useState(false)
|
||||
const [showCustomizeModal, setShowCustomizeModal] = useState(false)
|
||||
const [genLoading, setGenLoading] = useState(false)
|
||||
const { t } = useTranslation()
|
||||
|
||||
const OPERATIONS_MAP = useMemo(() => {
|
||||
const operationsMap = {
|
||||
webapp: [
|
||||
{ opName: t('appOverview.overview.appInfo.preview'), opIcon: RocketLaunchIcon },
|
||||
{ opName: t('appOverview.overview.appInfo.share.entry'), opIcon: ShareIcon },
|
||||
{ opName: t('appOverview.overview.appInfo.customize.entry'), opIcon: PaintBrushIcon },
|
||||
] as { opName: string; opIcon: any }[],
|
||||
api: [{ opName: t('appOverview.overview.apiInfo.doc'), opIcon: DocumentTextIcon }],
|
||||
app: [],
|
||||
@@ -74,8 +76,10 @@ function AppCard({
|
||||
return operationsMap
|
||||
}, [isCurrentWorkspaceManager, appInfo, t])
|
||||
|
||||
const isApp = cardType === 'app' || cardType === 'webapp'
|
||||
const basicName = isApp ? appInfo?.site?.title : t('appOverview.overview.apiInfo.title')
|
||||
const isApp = cardType === 'webapp'
|
||||
const basicName = isApp
|
||||
? appInfo?.site?.title
|
||||
: t('appOverview.overview.apiInfo.title')
|
||||
const runningStatus = isApp ? appInfo.enable_site : appInfo.enable_api
|
||||
const { app_base_url, access_token } = appInfo.site ?? {}
|
||||
const appUrl = `${app_base_url}/${appInfo.mode}/${access_token}`
|
||||
@@ -91,9 +95,9 @@ function AppCard({
|
||||
return () => {
|
||||
window.open(appUrl, '_blank')
|
||||
}
|
||||
case t('appOverview.overview.appInfo.share.entry'):
|
||||
case t('appOverview.overview.appInfo.customize.entry'):
|
||||
return () => {
|
||||
setShowShareModal(true)
|
||||
setShowCustomizeModal(true)
|
||||
}
|
||||
case t('appOverview.overview.appInfo.settings.entry'):
|
||||
return () => {
|
||||
@@ -113,15 +117,19 @@ function AppCard({
|
||||
}
|
||||
}
|
||||
|
||||
const onClickCustomize = () => {
|
||||
setShowCustomizeModal(true)
|
||||
const onGenCode = async () => {
|
||||
setGenLoading(true)
|
||||
await asyncRunSafe(onGenerateCode?.() as any)
|
||||
setGenLoading(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col w-full shadow-xs border-[0.5px] rounded-lg border-gray-200 ${className ?? ''}`}
|
||||
className={`min-w-max shadow-xs border-[0.5px] rounded-lg border-gray-200 ${
|
||||
className ?? ''
|
||||
}`}
|
||||
>
|
||||
<div className={`px-6 py-4 ${customBgColor ?? bgColor} rounded-lg`}>
|
||||
<div className={`px-6 py-5 ${customBgColor ?? bgColor} rounded-lg`}>
|
||||
<div className="mb-2.5 flex flex-row items-start justify-between">
|
||||
<AppBasic
|
||||
iconType={cardType}
|
||||
@@ -136,7 +144,9 @@ function AppCard({
|
||||
/>
|
||||
<div className="flex flex-row items-center h-9">
|
||||
<Tag className="mr-2" color={runningStatus ? 'green' : 'yellow'}>
|
||||
{runningStatus ? t('appOverview.overview.status.running') : t('appOverview.overview.status.disable')}
|
||||
{runningStatus
|
||||
? t('appOverview.overview.status.running')
|
||||
: t('appOverview.overview.status.disable')}
|
||||
</Tag>
|
||||
<Switch defaultValue={runningStatus} onChange={onChangeStatus} disabled={currentWorkspace?.role === 'normal'} />
|
||||
</div>
|
||||
@@ -144,39 +154,67 @@ function AppCard({
|
||||
<div className="flex flex-col justify-center py-2">
|
||||
<div className="py-1">
|
||||
<div className="pb-1 text-xs text-gray-500">
|
||||
{isApp ? t('appOverview.overview.appInfo.accessibleAddress') : t('appOverview.overview.apiInfo.accessibleAddress')}
|
||||
{isApp
|
||||
? t('appOverview.overview.appInfo.accessibleAddress')
|
||||
: t('appOverview.overview.apiInfo.accessibleAddress')}
|
||||
</div>
|
||||
<div className="text-sm text-gray-800">
|
||||
{isApp ? appUrl : apiUrl}
|
||||
<div className="w-full h-9 pl-2 pr-0.5 py-0.5 bg-black bg-opacity-[0.02] rounded-lg border border-black border-opacity-5 justify-start items-center inline-flex">
|
||||
<div className="h-4 px-2 justify-start items-start gap-2 flex flex-1">
|
||||
<div className="text-gray-700 text-xs font-medium">
|
||||
{isApp ? appUrl : apiUrl}
|
||||
</div>
|
||||
</div>
|
||||
<Divider type="vertical" className="!h-3.5 shrink-0 !mx-0.5" />
|
||||
<CopyFeedback
|
||||
content={isApp ? appUrl : apiUrl}
|
||||
selectorId={randomString(8)}
|
||||
className={'hover:bg-gray-200'}
|
||||
/>
|
||||
{/* button copy link/ button regenerate */}
|
||||
{isApp && isCurrentWorkspaceManager && (
|
||||
<Tooltip
|
||||
content={t('appOverview.overview.appInfo.regenerate') || ''}
|
||||
selector={`code-generate-${randomString(8)}`}
|
||||
>
|
||||
<div
|
||||
className="w-8 h-8 ml-0.5 cursor-pointer hover:bg-gray-200 rounded-lg"
|
||||
onClick={onGenCode}
|
||||
>
|
||||
<div
|
||||
className={`w-full h-full ${style.refreshIcon} ${
|
||||
genLoading ? style.generateLogo : ''
|
||||
}`}
|
||||
></div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`pt-2 flex flex-row items-center ${!isApp ? 'mb-[calc(2rem_+_1px)]' : ''
|
||||
}`}
|
||||
>
|
||||
{OPERATIONS_MAP[cardType].map((op) => {
|
||||
<div className={'pt-2 flex flex-row items-center'}>
|
||||
{!isApp && <SecretKeyButton className='flex-shrink-0 !h-8 bg-white mr-2' textCls='!text-gray-700 font-medium' iconCls='stroke-[1.2px]' appId={appInfo.id} />}
|
||||
{OPERATIONS_MAP[cardType].map((op: any) => {
|
||||
const disabled
|
||||
= op.opName === t('appOverview.overview.appInfo.settings.entry')
|
||||
? false
|
||||
: !runningStatus
|
||||
return (
|
||||
<Button
|
||||
className="mr-2 text-gray-800"
|
||||
className="mr-2 border-[0.5px] !h-8 hover:outline hover:outline-[0.5px] hover:outline-gray-300 text-gray-700 font-medium bg-white shadow-[0px_1px_2px_0px_rgba(16,24,40,0.05)]"
|
||||
key={op.opName}
|
||||
onClick={genClickFuncByName(op.opName)}
|
||||
disabled={
|
||||
[t('appOverview.overview.appInfo.preview'), t('appOverview.overview.appInfo.share.entry'), t('appOverview.overview.appInfo.embedded.entry')].includes(op.opName) && !runningStatus
|
||||
}
|
||||
disabled={disabled}
|
||||
>
|
||||
<Tooltip
|
||||
content={t('appOverview.overview.appInfo.preUseReminder') ?? ''}
|
||||
selector={`op-btn-${randomString(16)}`}
|
||||
className={
|
||||
([t('appOverview.overview.appInfo.preview'), t('appOverview.overview.appInfo.share.entry'), t('appOverview.overview.appInfo.embedded.entry')].includes(op.opName) && !runningStatus)
|
||||
? 'mt-[-8px]'
|
||||
: '!hidden'
|
||||
content={
|
||||
t('appOverview.overview.appInfo.preUseReminder') ?? ''
|
||||
}
|
||||
selector={`op-btn-${randomString(16)}`}
|
||||
className={disabled ? 'mt-[-8px]' : '!hidden'}
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<op.opIcon className="h-4 w-4 mr-1.5 stroke-[1.8px]" />
|
||||
<span className="text-xs">{op.opName}</span>
|
||||
<span className="text-[13px]">{op.opName}</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Button>
|
||||
@@ -186,31 +224,7 @@ function AppCard({
|
||||
</div>
|
||||
{isApp
|
||||
? (
|
||||
<div
|
||||
className={
|
||||
'flex items-center px-6 py-2 box-border text-xs text-gray-500 bg-opacity-50 bg-white border-t-[0.5px] border-primary-50'
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="flex items-center hover:text-primary-600 hover:cursor-pointer"
|
||||
onClick={onClickCustomize}
|
||||
>
|
||||
<SparklesIcon className="w-4 h-4 mr-1" />
|
||||
{t('appOverview.overview.appInfo.customize.entry')}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
: null}
|
||||
{isApp
|
||||
? (
|
||||
<div>
|
||||
<ShareLink
|
||||
isShow={showShareModal}
|
||||
onClose={() => setShowShareModal(false)}
|
||||
linkUrl={appUrl}
|
||||
onGenerateCode={onGenerateCode}
|
||||
regeneratable={isCurrentWorkspaceManager}
|
||||
/>
|
||||
<>
|
||||
<SettingsModal
|
||||
appInfo={appInfo}
|
||||
isShow={showSettingsModal}
|
||||
@@ -230,7 +244,7 @@ function AppCard({
|
||||
appId={appInfo.id}
|
||||
mode={appInfo.mode}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
: null}
|
||||
</div>
|
||||
|
||||
@@ -229,7 +229,7 @@ const Chart: React.FC<IChartProps> = ({
|
||||
<div className='mb-3'>
|
||||
<Basic name={title} type={timePeriod} hoverTip={explanation} />
|
||||
</div>
|
||||
<div className='mb-4'>
|
||||
<div className='mb-4 flex-1'>
|
||||
<Basic
|
||||
isExtraInLine={CHART_TYPE_CONFIG[chartType].showTokens}
|
||||
name={chartType !== 'costs' ? (sumData.toLocaleString() + unit) : `${sumData < 1000 ? sumData : (`${formatNumber(Math.round(sumData / 1000))}k`)}`}
|
||||
@@ -347,6 +347,7 @@ export const UserSatisfactionRate: FC<IBizChartProps> = ({ id, period }) => {
|
||||
chartType='endUsers'
|
||||
isAvg
|
||||
{...(noDataFlag && { yMax: 1000 })}
|
||||
className='h-full'
|
||||
/>
|
||||
}
|
||||
|
||||
|
||||
3
web/app/components/app/overview/assets/refresh-hover.svg
Normal file
3
web/app/components/app/overview/assets/refresh-hover.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21.6353 16.5954C21.4501 18.3353 20.4643 19.9658 18.833 20.9076C16.1226 22.4724 12.6569 21.5438 11.0921 18.8335L10.9255 18.5448M10.3641 15.4047C10.5493 13.6647 11.5352 12.0343 13.1665 11.0924C15.8768 9.5276 19.3425 10.4562 20.9073 13.1666L21.074 13.4552M10.3288 20.044L10.8168 18.2227L12.6382 18.7107M19.3616 13.2893L21.183 13.7774L21.671 11.956" stroke="#1D2939" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 544 B |
3
web/app/components/app/overview/assets/refresh.svg
Normal file
3
web/app/components/app/overview/assets/refresh.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21.6353 16.5954C21.4501 18.3353 20.4643 19.9658 18.833 20.9076C16.1226 22.4724 12.6569 21.5438 11.0921 18.8335L10.9255 18.5448M10.3641 15.4047C10.5493 13.6647 11.5352 12.0343 13.1665 11.0924C15.8768 9.5276 19.3425 10.4562 20.9073 13.1666L21.074 13.4552M10.3288 20.044L10.8168 18.2227L12.6382 18.7107M19.3616 13.2893L21.183 13.7774L21.671 11.956" stroke="#667085" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 544 B |
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { ChevronRightIcon } from '@heroicons/react/20/solid'
|
||||
import Link from 'next/link'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
@@ -18,7 +18,7 @@ export type ISettingsModalProps = {
|
||||
isShow: boolean
|
||||
defaultValue?: string
|
||||
onClose: () => void
|
||||
onSave: (params: ConfigParams) => Promise<any>
|
||||
onSave?: (params: ConfigParams) => Promise<any>
|
||||
}
|
||||
|
||||
export type ConfigParams = {
|
||||
@@ -51,6 +51,12 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
||||
const [showEmojiPicker, setShowEmojiPicker] = useState(false)
|
||||
const [emoji, setEmoji] = useState({ icon, icon_background })
|
||||
|
||||
useEffect(() => {
|
||||
setInputInfo({ title, desc: description, copyright, privacyPolicy: privacy_policy })
|
||||
setLanguage(default_language)
|
||||
setEmoji({ icon, icon_background })
|
||||
}, [appInfo])
|
||||
|
||||
const onHide = () => {
|
||||
onClose()
|
||||
setTimeout(() => {
|
||||
@@ -70,7 +76,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
||||
icon: emoji.icon,
|
||||
icon_background: emoji.icon_background,
|
||||
}
|
||||
await onSave(params)
|
||||
await onSave?.(params)
|
||||
setSaveLoading(false)
|
||||
onHide()
|
||||
}
|
||||
@@ -99,7 +105,9 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
||||
/>
|
||||
<input className={`flex-grow rounded-lg h-10 box-border px-3 ${s.projectName} bg-gray-100`}
|
||||
value={inputInfo.title}
|
||||
onChange={onChange('title')} />
|
||||
onChange={onChange('title')}
|
||||
placeholder={t('app.appNamePlaceholder') || ''}
|
||||
/>
|
||||
</div>
|
||||
<div className={`mt-6 font-medium ${s.settingTitle} text-gray-900 `}>{t(`${prefixSettings}.webDesc`)}</div>
|
||||
<p className={`mt-1 ${s.settingsTip} text-gray-500`}>{t(`${prefixSettings}.webDescTip`)}</p>
|
||||
@@ -157,7 +165,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
||||
setShowEmojiPicker(false)
|
||||
}}
|
||||
onClose={() => {
|
||||
setEmoji({ icon: '🤖', icon_background: '#FFEAD5' })
|
||||
setEmoji({ icon: appInfo.site.icon, icon_background: appInfo.site.icon_background })
|
||||
setShowEmojiPicker(false)
|
||||
}}
|
||||
/>}
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import {
|
||||
ArrowPathIcon,
|
||||
LinkIcon,
|
||||
} from '@heroicons/react/24/outline'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
||||
import './style.css'
|
||||
|
||||
type IShareLinkProps = {
|
||||
isShow: boolean
|
||||
onClose: () => void
|
||||
onGenerateCode: () => Promise<void>
|
||||
linkUrl: string
|
||||
regeneratable?: boolean
|
||||
}
|
||||
|
||||
const prefixShare = 'appOverview.overview.appInfo.share'
|
||||
|
||||
const ShareLinkModal: FC<IShareLinkProps> = ({
|
||||
linkUrl,
|
||||
isShow,
|
||||
onClose,
|
||||
onGenerateCode,
|
||||
regeneratable,
|
||||
}) => {
|
||||
const [genLoading, setGenLoading] = useState(false)
|
||||
const [isCopied, setIsCopied] = useState(false)
|
||||
const { t } = useTranslation()
|
||||
return <Modal
|
||||
title={t(`${prefixShare}.explanation`)}
|
||||
isShow={isShow}
|
||||
onClose={onClose}
|
||||
>
|
||||
{/* share url */}
|
||||
<p className='mt-5 text-xs font-medium text-gray-500'>{t(`${prefixShare}.shareUrl`)}</p>
|
||||
{/* input share url */}
|
||||
<input disabled type='text' value={linkUrl} className='mt-1 w-full bg-gray-50 p-2 text-primary-600 text-xs font-normal outline-gray-50 hover:outline-gray-50 cursor-pointer' />
|
||||
{/* button copy link/ button regenerate */}
|
||||
<div className='mt-4 flex gap-3'>
|
||||
<Button
|
||||
type="primary"
|
||||
className='w-32 !px-0'
|
||||
onClick={() => {
|
||||
copy(linkUrl) && setIsCopied(true)
|
||||
}}
|
||||
>
|
||||
<LinkIcon className='w-4 h-4 mr-2' />
|
||||
{ t(`${prefixShare}.${isCopied ? 'linkCopied' : 'copyLink'}`) }
|
||||
</Button>
|
||||
{regeneratable && <Button className='w-32 !px-0' onClick={async () => {
|
||||
setGenLoading(true)
|
||||
await onGenerateCode()
|
||||
setGenLoading(false)
|
||||
setIsCopied(false)
|
||||
}}>
|
||||
<ArrowPathIcon className={`w-4 h-4 mr-2 ${genLoading ? 'generateLogo' : ''}`} />
|
||||
{t(`${prefixShare}.regenerate`)}
|
||||
</Button>}
|
||||
</div>
|
||||
</Modal>
|
||||
}
|
||||
|
||||
export default ShareLinkModal
|
||||
@@ -16,3 +16,16 @@
|
||||
@apply w-4 h-4 bg-center bg-no-repeat;
|
||||
background-image: url(./assets/code-browser.svg);
|
||||
}
|
||||
|
||||
.refreshIcon {
|
||||
background-image: url(./assets/refresh.svg);
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.refreshIcon:hover {
|
||||
background-image: url(./assets/refresh-hover.svg);
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user