app list modification

This commit is contained in:
JzoNg
2024-02-23 00:05:55 +08:00
parent 171dd5c737
commit 117b84116e
21 changed files with 430 additions and 95 deletions

View File

@ -0,0 +1,98 @@
'use client'
import React, { useState } from 'react'
import cn from 'classnames'
import { useTranslation } from 'react-i18next'
import s from './style.module.css'
import Modal from '@/app/components/base/modal'
import Button from '@/app/components/base/button'
import Toast from '@/app/components/base/toast'
import AppIcon from '@/app/components/base/app-icon'
import EmojiPicker from '@/app/components/base/emoji-picker'
import { useProviderContext } from '@/context/provider-context'
import AppsFull from '@/app/components/billing/apps-full-in-dialog'
export type DuplicateAppModalProps = {
appName: string
icon: string
icon_background: string
show: boolean
onConfirm: (info: {
name: string
icon: string
icon_background: string
}) => Promise<void>
onHide: () => void
}
const DuplicateAppModal = ({
appName,
icon,
icon_background,
show = false,
onConfirm,
onHide,
}: DuplicateAppModalProps) => {
const { t } = useTranslation()
const [name, setName] = React.useState(appName)
const [showEmojiPicker, setShowEmojiPicker] = useState(false)
const [emoji, setEmoji] = useState({ icon, icon_background })
const { plan, enableBilling } = useProviderContext()
const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps)
const submit = () => {
if (!name.trim()) {
Toast.notify({ type: 'error', message: t('explore.appCustomize.nameRequired') })
return
}
onConfirm({
name,
...emoji,
})
onHide()
}
return (
<>
<Modal
isShow={show}
onClose={() => { }}
className={cn(s.modal, '!max-w-[480px]', 'px-8')}
>
<span className={s.close} onClick={onHide} />
<div className={s.title}>{t('app.duplicateTitle')}</div>
<div className={s.content}>
<div className={s.subTitle}>{t('explore.appCustomize.subTitle')}</div>
<div className='flex items-center justify-between space-x-3'>
<AppIcon size='large' onClick={() => { setShowEmojiPicker(true) }} className='cursor-pointer' icon={emoji.icon} background={emoji.icon_background} />
<input
value={name}
onChange={e => setName(e.target.value)}
className='h-10 px-3 text-sm font-normal bg-gray-100 rounded-lg grow'
/>
</div>
{/* TODO loc */}
{isAppsFull && <AppsFull loc='app-duplicate-create' />}
</div>
<div className='flex flex-row-reverse'>
<Button disabled={isAppsFull} className='w-24 ml-2' type='primary' onClick={submit}>{t('app.duplicate')}</Button>
<Button className='w-24' onClick={onHide}>{t('common.operation.cancel')}</Button>
</div>
</Modal>
{showEmojiPicker && <EmojiPicker
onSelect={(icon, icon_background) => {
setEmoji({ icon, icon_background })
setShowEmojiPicker(false)
}}
onClose={() => {
setEmoji({ icon, icon_background })
setShowEmojiPicker(false)
}}
/>}
</>
)
}
export default DuplicateAppModal

View File

@ -0,0 +1,36 @@
.modal {
position: relative;
}
.modal .close {
position: absolute;
right: 16px;
top: 25px;
width: 32px;
height: 32px;
border-radius: 8px;
background: center no-repeat url(~@/app/components/datasets/create/assets/close.svg);
background-size: 16px;
cursor: pointer;
}
.modal .title {
@apply mb-9;
font-weight: 600;
font-size: 20px;
line-height: 30px;
color: #101828;
}
.modal .content {
@apply mb-9;
font-weight: 400;
font-size: 14px;
line-height: 20px;
color: #101828;
}
.subTitle {
margin-bottom: 8px;
font-weight: 500;
}

View File

@ -0,0 +1,10 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="route" clip-path="url(#clip0_664_8452)">
<path id="Icon" d="M5.75 2.5H5.9672C7.49082 2.5 8.25263 2.5 8.54182 2.77364C8.79179 3.01018 8.90257 3.35864 8.83508 3.69611C8.75701 4.08651 8.13505 4.52643 6.89114 5.40627L4.85886 6.84373C3.61495 7.72357 2.99299 8.16349 2.91492 8.5539C2.84743 8.89136 2.95821 9.23982 3.20818 9.47636C3.49737 9.75 4.25918 9.75 5.7828 9.75H6.25M4 2.5C4 3.32843 3.32843 4 2.5 4C1.67157 4 1 3.32843 1 2.5C1 1.67157 1.67157 1 2.5 1C3.32843 1 4 1.67157 4 2.5ZM11 9.5C11 10.3284 10.3284 11 9.5 11C8.67157 11 8 10.3284 8 9.5C8 8.67157 8.67157 8 9.5 8C10.3284 8 11 8.67157 11 9.5Z" stroke="#F79009" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_664_8452">
<rect width="12" height="12" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 900 B

View File

@ -0,0 +1,66 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "12",
"height": "12",
"viewBox": "0 0 12 12",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "route",
"clip-path": "url(#clip0_664_8452)"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Icon",
"d": "M5.75 2.5H5.9672C7.49082 2.5 8.25263 2.5 8.54182 2.77364C8.79179 3.01018 8.90257 3.35864 8.83508 3.69611C8.75701 4.08651 8.13505 4.52643 6.89114 5.40627L4.85886 6.84373C3.61495 7.72357 2.99299 8.16349 2.91492 8.5539C2.84743 8.89136 2.95821 9.23982 3.20818 9.47636C3.49737 9.75 4.25918 9.75 5.7828 9.75H6.25M4 2.5C4 3.32843 3.32843 4 2.5 4C1.67157 4 1 3.32843 1 2.5C1 1.67157 1.67157 1 2.5 1C3.32843 1 4 1.67157 4 2.5ZM11 9.5C11 10.3284 10.3284 11 9.5 11C8.67157 11 8 10.3284 8 9.5C8 8.67157 8.67157 8 9.5 8C10.3284 8 11 8.67157 11 9.5Z",
"stroke": "currentColor",
"stroke-width": "1.25",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
},
{
"type": "element",
"name": "defs",
"attributes": {},
"children": [
{
"type": "element",
"name": "clipPath",
"attributes": {
"id": "clip0_664_8452"
},
"children": [
{
"type": "element",
"name": "rect",
"attributes": {
"width": "12",
"height": "12",
"fill": "white"
},
"children": []
}
]
}
]
}
]
},
"name": "Route"
}

View File

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Route.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'Route'
export default Icon

View File

@ -1 +1,2 @@
export { default as Globe01 } from './Globe01'
export { default as Route } from './Route'

View File

@ -0,0 +1,38 @@
import type { FC } from 'react'
import cn from 'classnames'
type Option = {
value: string
text: string
}
type TabSliderProps = {
className?: string
value: string
onChange: (v: string) => void
options: Option[]
}
const TabSliderNew: FC<TabSliderProps> = ({
className,
value,
onChange,
options,
}) => {
return (
<div className={cn(className, 'relative flex')}>
{options.map(option => (
<div
key={option.value}
onClick={() => onChange(option.value)}
className={cn(
'mr-1 px-3 py-[5px] h-[28px] rounded-lg border-[0.5px] border-transparent text-gray-700 text-[13px] font-medium leading-[18px] cursor-pointer hover:bg-gray-200',
value === option.value && 'bg-white border-gray-200 shadow-xs text-primary-600 hover:bg-white',
)}
>
{option.text}
</div>
))}
</div>
)
}
export default TabSliderNew