### What problem does this PR solve? Feat: Modify the dataset list page style #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.19.0
| @@ -72,7 +72,7 @@ function CheckboxFormMultiple({ | |||
| <Form {...form}> | |||
| <form | |||
| onSubmit={form.handleSubmit(onSubmit)} | |||
| className="space-y-8" | |||
| className="space-y-8 px-5 py-2.5" | |||
| onReset={() => form.reset()} | |||
| > | |||
| {filters.map((x) => ( | |||
| @@ -81,9 +81,11 @@ function CheckboxFormMultiple({ | |||
| control={form.control} | |||
| name={x.field} | |||
| render={() => ( | |||
| <FormItem> | |||
| <div className="mb-4"> | |||
| <FormLabel className="text-base">{x.label}</FormLabel> | |||
| <FormItem className="space-y-4"> | |||
| <div> | |||
| <FormLabel className="text-base text-text-sub-title-invert"> | |||
| {x.label} | |||
| </FormLabel> | |||
| </div> | |||
| {x.list.map((item) => ( | |||
| <FormField | |||
| @@ -92,10 +94,10 @@ function CheckboxFormMultiple({ | |||
| name={x.field} | |||
| render={({ field }) => { | |||
| return ( | |||
| <div className="flex items-center justify-between"> | |||
| <div className="flex items-center justify-between text-text-title text-xs"> | |||
| <FormItem | |||
| key={item.id} | |||
| className="flex flex-row space-x-3 space-y-0 items-center" | |||
| className="flex flex-row space-x-3 space-y-0 items-center " | |||
| > | |||
| <FormControl> | |||
| <Checkbox | |||
| @@ -111,9 +113,7 @@ function CheckboxFormMultiple({ | |||
| }} | |||
| /> | |||
| </FormControl> | |||
| <FormLabel className="text-lg"> | |||
| {item.label} | |||
| </FormLabel> | |||
| <FormLabel>{item.label}</FormLabel> | |||
| </FormItem> | |||
| <span className=" text-sm">{item.count}</span> | |||
| </div> | |||
| @@ -126,7 +126,7 @@ function CheckboxFormMultiple({ | |||
| )} | |||
| /> | |||
| ))} | |||
| <div className="flex justify-between"> | |||
| <div className="flex justify-end gap-5"> | |||
| <Button | |||
| type="button" | |||
| variant={'outline'} | |||
| @@ -155,7 +155,7 @@ export function FilterPopover({ | |||
| return ( | |||
| <Popover open={open} onOpenChange={setOpen}> | |||
| <PopoverTrigger asChild>{children}</PopoverTrigger> | |||
| <PopoverContent> | |||
| <PopoverContent className="p-0"> | |||
| <CheckboxFormMultiple | |||
| onChange={onChange} | |||
| value={value} | |||
| @@ -6,12 +6,13 @@ import React, { | |||
| ReactNode, | |||
| useMemo, | |||
| } from 'react'; | |||
| import { IconFont } from '../icon-font'; | |||
| import { Button, ButtonProps } from '../ui/button'; | |||
| import { SearchInput } from '../ui/input'; | |||
| import { CheckboxFormMultipleProps, FilterPopover } from './filter-popover'; | |||
| interface IProps { | |||
| title?: string; | |||
| title?: ReactNode; | |||
| searchString?: string; | |||
| onSearchChange?: ChangeEventHandler<HTMLInputElement>; | |||
| showFilter?: boolean; | |||
| @@ -23,8 +24,21 @@ const FilterButton = React.forwardRef< | |||
| ButtonProps & { count?: number } | |||
| >(({ count = 0, ...props }, ref) => { | |||
| return ( | |||
| <Button variant="outline" size={'sm'} {...props} ref={ref}> | |||
| Filter <span>{count}</span> <ChevronDown /> | |||
| <Button variant="secondary" {...props} ref={ref}> | |||
| <span | |||
| className={cn({ | |||
| 'text-text-title': count > 0, | |||
| 'text-text-sub-title-invert': count === 0, | |||
| })} | |||
| > | |||
| Filter | |||
| </span> | |||
| {count > 0 && ( | |||
| <span className="rounded-full bg-text-badge px-1 text-xs "> | |||
| {count} | |||
| </span> | |||
| )} | |||
| <ChevronDown /> | |||
| </Button> | |||
| ); | |||
| }); | |||
| @@ -40,8 +54,10 @@ export default function ListFilterBar({ | |||
| onChange, | |||
| filters, | |||
| className, | |||
| icon, | |||
| }: PropsWithChildren<IProps & Omit<CheckboxFormMultipleProps, 'setOpen'>> & { | |||
| className?: string; | |||
| icon?: ReactNode; | |||
| }) { | |||
| const filterCount = useMemo(() => { | |||
| return typeof value === 'object' && value !== null | |||
| @@ -52,9 +68,16 @@ export default function ListFilterBar({ | |||
| }, [value]); | |||
| return ( | |||
| <div className={cn('flex justify-between mb-6 items-center', className)}> | |||
| <span className="text-3xl font-bold ">{leftPanel || title}</span> | |||
| <div className="flex gap-4 items-center"> | |||
| <div className={cn('flex justify-between mb-5 items-center', className)}> | |||
| <div className="text-2xl font-semibold flex items-center gap-2.5"> | |||
| {typeof icon === 'string' ? ( | |||
| <IconFont name={icon} className="size-6"></IconFont> | |||
| ) : ( | |||
| icon | |||
| )} | |||
| {leftPanel || title} | |||
| </div> | |||
| <div className="flex gap-5 items-center"> | |||
| {showFilter && ( | |||
| <FilterPopover value={value} onChange={onChange} filters={filters}> | |||
| <FilterButton count={filterCount}></FilterButton> | |||
| @@ -64,6 +87,7 @@ export default function ListFilterBar({ | |||
| <SearchInput | |||
| value={searchString} | |||
| onChange={onSearchChange} | |||
| className="w-32" | |||
| ></SearchInput> | |||
| {children} | |||
| </div> | |||
| @@ -13,7 +13,7 @@ const buttonVariants = cva( | |||
| destructive: | |||
| 'bg-destructive text-destructive-foreground hover:bg-destructive/90', | |||
| outline: | |||
| 'border border-colors-outline-sentiment-primary bg-background hover:bg-accent hover:text-accent-foreground', | |||
| 'border border-text-sub-title-invert bg-transparent hover:bg-accent hover:text-accent-foreground', | |||
| secondary: | |||
| 'bg-secondary text-secondary-foreground hover:bg-secondary/80', | |||
| ghost: 'hover:bg-accent hover:text-accent-foreground', | |||
| @@ -23,8 +23,8 @@ const buttonVariants = cva( | |||
| icon: 'bg-colors-background-inverse-standard text-foreground hover:bg-colors-background-inverse-standard/80', | |||
| }, | |||
| size: { | |||
| default: 'h-10 px-4 py-2', | |||
| sm: 'h-9 rounded-md px-3', | |||
| default: 'h-8 px-2.5 py-1.5 ', | |||
| sm: 'h-6 rounded-sm px-2', | |||
| lg: 'h-11 rounded-md px-8', | |||
| icon: 'h-10 w-10', | |||
| auto: 'h-full px-1', | |||
| @@ -12,7 +12,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>( | |||
| <input | |||
| type={type} | |||
| className={cn( | |||
| 'flex h-10 w-full rounded-md border border-input bg-colors-background-inverse-weak px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', | |||
| 'flex h-8 w-full rounded-md border border-input bg-colors-background-inverse-weak px-2 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', | |||
| className, | |||
| )} | |||
| ref={ref} | |||
| @@ -28,7 +28,12 @@ export interface ExpandedInputProps extends Omit<InputProps, 'prefix'> { | |||
| suffix?: React.ReactNode; | |||
| } | |||
| const ExpandedInput = ({ suffix, prefix, ...props }: ExpandedInputProps) => { | |||
| const ExpandedInput = ({ | |||
| suffix, | |||
| prefix, | |||
| className, | |||
| ...props | |||
| }: ExpandedInputProps) => { | |||
| return ( | |||
| <div className="relative"> | |||
| <span | |||
| @@ -39,7 +44,7 @@ const ExpandedInput = ({ suffix, prefix, ...props }: ExpandedInputProps) => { | |||
| {prefix} | |||
| </span> | |||
| <Input | |||
| className={cn({ 'pr-10': suffix, 'pl-10': prefix })} | |||
| className={cn({ 'pr-8': !!suffix, 'pl-8': !!prefix }, className)} | |||
| {...props} | |||
| ></Input> | |||
| <span | |||
| @@ -54,7 +59,12 @@ const ExpandedInput = ({ suffix, prefix, ...props }: ExpandedInputProps) => { | |||
| }; | |||
| const SearchInput = (props: InputProps) => { | |||
| return <ExpandedInput suffix={<Search />} {...props}></ExpandedInput>; | |||
| return ( | |||
| <ExpandedInput | |||
| prefix={<Search className="size-3.5" />} | |||
| {...props} | |||
| ></ExpandedInput> | |||
| ); | |||
| }; | |||
| export { ExpandedInput, Input, SearchInput }; | |||
| @@ -48,6 +48,7 @@ const PaginationLink = ({ | |||
| <a | |||
| aria-current={isActive ? 'page' : undefined} | |||
| className={cn( | |||
| 'size-8', | |||
| buttonVariants({ | |||
| variant: isActive ? 'outline' : 'ghost', | |||
| size, | |||
| @@ -70,7 +71,6 @@ const PaginationPrevious = ({ | |||
| {...props} | |||
| > | |||
| <ChevronLeft className="h-4 w-4" /> | |||
| <span>Previous</span> | |||
| </PaginationLink> | |||
| ); | |||
| PaginationPrevious.displayName = 'PaginationPrevious'; | |||
| @@ -85,7 +85,6 @@ const PaginationNext = ({ | |||
| className={cn('gap-1 pr-2.5', className)} | |||
| {...props} | |||
| > | |||
| <span>Next</span> | |||
| <ChevronRight className="h-4 w-4" /> | |||
| </PaginationLink> | |||
| ); | |||
| @@ -98,7 +98,7 @@ export function RAGFlowPagination({ | |||
| }, [pageSize]); | |||
| return ( | |||
| <section className="flex items-center justify-end"> | |||
| <section className="flex items-center justify-end text-text-sub-title-invert "> | |||
| <span className="mr-4">Total {total}</span> | |||
| <Pagination className="w-auto mx-0 mr-4"> | |||
| <PaginationContent> | |||
| @@ -108,9 +108,14 @@ export function RAGFlowPagination({ | |||
| {pages.map((x) => ( | |||
| <PaginationItem | |||
| key={x} | |||
| className={cn({ ['bg-accent rounded-md']: currentPage === x })} | |||
| className={cn({ | |||
| ['bg-background-header-bar rounded-md text-text-title']: | |||
| currentPage === x, | |||
| })} | |||
| > | |||
| <PaginationLink onClick={handlePageChange(x)}>{x}</PaginationLink> | |||
| <PaginationLink onClick={handlePageChange(x)} className="size-8"> | |||
| {x} | |||
| </PaginationLink> | |||
| </PaginationItem> | |||
| ))} | |||
| <PaginationItem> | |||
| @@ -126,6 +131,7 @@ export function RAGFlowPagination({ | |||
| options={sizeChangerOptions} | |||
| value={currentPageSize} | |||
| onChange={handlePageSizeChange} | |||
| triggerClassName="bg-background-header-bar" | |||
| ></RAGFlowSelect> | |||
| )} | |||
| </section> | |||
| @@ -26,7 +26,7 @@ const SelectTrigger = React.forwardRef< | |||
| <SelectPrimitive.Trigger | |||
| ref={ref} | |||
| className={cn( | |||
| 'flex h-10 w-full items-center justify-between rounded-md border border-input bg-colors-background-inverse-weak px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1', | |||
| 'flex h-8 w-full items-center justify-between rounded-md border border-input bg-colors-background-inverse-weak px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1', | |||
| className, | |||
| )} | |||
| {...props} | |||
| @@ -192,6 +192,7 @@ export type RAGFlowSelectProps = Partial<ControllerRenderProps> & { | |||
| allowClear?: boolean; | |||
| placeholder?: React.ReactNode; | |||
| contentProps?: React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>; | |||
| triggerClassName?: string; | |||
| } & SelectPrimitive.SelectProps; | |||
| /** | |||
| @@ -223,6 +224,7 @@ export const RAGFlowSelect = forwardRef< | |||
| placeholder, | |||
| contentProps = {}, | |||
| defaultValue, | |||
| triggerClassName, | |||
| }, | |||
| ref, | |||
| ) { | |||
| @@ -259,11 +261,11 @@ export const RAGFlowSelect = forwardRef< | |||
| <Select onValueChange={handleChange} value={value} key={key}> | |||
| <FormControlWidget> | |||
| <SelectTrigger | |||
| className="bg-colors-background-inverse-weak" | |||
| value={value} | |||
| onReset={handleReset} | |||
| allowClear={allowClear} | |||
| ref={ref} | |||
| className={triggerClassName} | |||
| > | |||
| <SelectValue placeholder={placeholder} /> | |||
| </SelectTrigger> | |||
| @@ -117,7 +117,7 @@ export function Header() { | |||
| }, [navigate]); | |||
| return ( | |||
| <section className="py-6 px-10 flex justify-between items-center "> | |||
| <section className="p-5 pr-14 flex justify-between items-center "> | |||
| <div className="flex items-center gap-4"> | |||
| <img | |||
| src={'/logo.svg'} | |||
| @@ -15,7 +15,6 @@ import * as React from 'react'; | |||
| import { ChunkMethodDialog } from '@/components/chunk-method-dialog'; | |||
| import { RenameDialog } from '@/components/rename-dialog'; | |||
| import { TableSkeleton } from '@/components/table-skeleton'; | |||
| import { RAGFlowPagination } from '@/components/ui/ragflow-pagination'; | |||
| import { | |||
| Table, | |||
| @@ -48,7 +47,6 @@ export function DatasetTable({ | |||
| setPagination, | |||
| rowSelection, | |||
| setRowSelection, | |||
| loading, | |||
| }: DatasetTableProps) { | |||
| const [sorting, setSorting] = React.useState<SortingState>([]); | |||
| const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>( | |||
| @@ -142,9 +140,7 @@ export function DatasetTable({ | |||
| ))} | |||
| </TableHeader> | |||
| <TableBody className="relative"> | |||
| {loading ? ( | |||
| <TableSkeleton columnsLength={columns.length}></TableSkeleton> | |||
| ) : table.getRowModel().rows?.length ? ( | |||
| {table.getRowModel().rows?.length ? ( | |||
| table.getRowModel().rows.map((row) => ( | |||
| <TableRow | |||
| key={row.id} | |||
| @@ -71,7 +71,7 @@ export default function Dataset() { | |||
| > | |||
| <DropdownMenu> | |||
| <DropdownMenuTrigger asChild> | |||
| <Button variant={'tertiary'} size={'sm'}> | |||
| <Button size={'sm'}> | |||
| <Upload /> | |||
| {t('knowledgeDetails.addFile')} | |||
| </Button> | |||
| @@ -85,7 +85,7 @@ export function DatasetCreatingDialog({ hideModal, onOk }: IModalProps<any>) { | |||
| </DialogHeader> | |||
| <InputForm onOk={onOk}></InputForm> | |||
| <DialogFooter> | |||
| <Button type="submit" variant={'tertiary'} size={'sm'} form={FormId}> | |||
| <Button type="submit" form={FormId}> | |||
| {t('common.save')} | |||
| </Button> | |||
| </DialogFooter> | |||
| @@ -53,18 +53,19 @@ export default function Datasets() { | |||
| ); | |||
| return ( | |||
| <section className="py-8 text-foreground"> | |||
| <section className="py-4 text-foreground"> | |||
| <ListFilterBar | |||
| title="Datasets" | |||
| title={t('header.knowledgeBase')} | |||
| searchString={searchString} | |||
| onSearchChange={handleInputChange} | |||
| value={filterValue} | |||
| filters={owners} | |||
| onChange={handleFilterSubmit} | |||
| className="px-8" | |||
| icon={'data'} | |||
| > | |||
| <Button size={'sm'} onClick={showModal}> | |||
| <Plus className="mr-2 size-2.5" /> | |||
| <Button onClick={showModal}> | |||
| <Plus className=" size-2.5" /> | |||
| {t('knowledgeList.createKnowledgeBase')} | |||
| </Button> | |||
| </ListFilterBar> | |||
| @@ -46,6 +46,7 @@ module.exports = { | |||
| 'text-badge': 'var(--text-badge)', | |||
| 'text-title': 'var(--text-title)', | |||
| 'text-sub-title': 'var(--text-sub-title)', | |||
| 'text-sub-title-invert': 'var(--text-sub-title-invert)', | |||
| 'text-title-invert': 'var(--text-title-invert)', | |||
| 'background-header-bar': 'var(--background-header-bar)', | |||
| 'background-card': 'var(--background-card)', | |||
| @@ -79,6 +79,7 @@ | |||
| --text-title: rgba(22, 22, 24, 1); | |||
| --text-sub-title: rgba(151, 154, 171, 1); | |||
| --text-sub-title-invert: rgba(91, 93, 106, 1); | |||
| --background-header-bar: rgba(11, 11, 12, 1); | |||
| --text-title-invert: rgba(255, 255, 255, 1); | |||
| @@ -182,6 +183,7 @@ | |||
| --text-title: rgba(255, 255, 255, 1); | |||
| --text-sub-title: rgba(91, 93, 106, 1); | |||
| --text-sub-title-invert: rgba(151, 154, 171, 1); | |||
| --background-header-bar: rgba(11, 11, 12, 1); | |||
| --text-title-invert: rgba(22, 22, 24, 1); | |||