♻️ refactor(web): consolidate design-system primitives and responsive data views
This commit is contained in:
@@ -19,7 +19,7 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
import { Search } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Button } from '@/components/design-system/button'
|
||||
|
||||
export interface EmptyStateProps {
|
||||
searchQuery?: string
|
||||
@@ -49,7 +49,7 @@ export function EmptyState(props: EmptyStateProps) {
|
||||
</p>
|
||||
|
||||
{(props.hasActiveFilters || hasSearch) && (
|
||||
<Button variant='outline' size='sm' onClick={props.onClearFilters}>
|
||||
<Button variant='outline' onClick={props.onClearFilters}>
|
||||
{t('Clear all filters')}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -21,7 +21,7 @@ import { ChevronLeft, ChevronRight } from 'lucide-react'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Button } from '@/components/design-system/button'
|
||||
import { getPerfMetricsSummary } from '@/features/performance-metrics/api'
|
||||
|
||||
import { DEFAULT_PRICING_PAGE_SIZE, DEFAULT_TOKEN_UNIT } from '../constants'
|
||||
@@ -101,7 +101,6 @@ export function ModelCardGrid(props: ModelCardGridProps) {
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
size='sm'
|
||||
onClick={() => setPage((current) => Math.max(1, current - 1))}
|
||||
disabled={currentPage <= 1}
|
||||
className='gap-1.5'
|
||||
@@ -112,7 +111,6 @@ export function ModelCardGrid(props: ModelCardGridProps) {
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
size='sm'
|
||||
onClick={() =>
|
||||
setPage((current) => Math.min(totalPages, current + 1))
|
||||
}
|
||||
|
||||
@@ -260,12 +260,9 @@ export const ModelCard = memo(function ModelCard(props: ModelCardProps) {
|
||||
{isTokenBased ? t('Token-based') : t('Per Request')}
|
||||
</span>
|
||||
{isDynamicPricing && (
|
||||
<StatusBadge
|
||||
label={t('Dynamic Pricing')}
|
||||
variant='warning'
|
||||
copyable={false}
|
||||
size='sm'
|
||||
/>
|
||||
<StatusBadge variant='warning' size='sm'>
|
||||
{t('Dynamic Pricing')}
|
||||
</StatusBadge>
|
||||
)}
|
||||
</div>
|
||||
<ModelPerfBadge perf={props.perf} className='row-span-2 self-start' />
|
||||
|
||||
@@ -36,8 +36,8 @@ import {
|
||||
StaticDataTable,
|
||||
staticDataTableClassNames as tableStyles,
|
||||
} from '@/components/data-table'
|
||||
import { Tabs, TabsList, TabsTrigger } from '@/components/design-system/tabs'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { useStatus } from '@/hooks/use-status'
|
||||
|
||||
import {
|
||||
@@ -502,7 +502,7 @@ function CodeSamplesSection(props: {
|
||||
<div className='flex flex-wrap items-center gap-2'>
|
||||
{endpoints.length > 1 && (
|
||||
<Tabs value={endpointType} onValueChange={setEndpointType}>
|
||||
<TabsList className='bg-muted/40 h-8 p-0.5'>
|
||||
<TabsList className='bg-muted/40 p-0.5'>
|
||||
{endpoints.map((ep) => (
|
||||
<TabsTrigger
|
||||
key={ep.type}
|
||||
@@ -521,7 +521,7 @@ function CodeSamplesSection(props: {
|
||||
onValueChange={(v) => setLang(v as Lang)}
|
||||
className='ml-auto'
|
||||
>
|
||||
<TabsList className='bg-muted/40 h-8 p-0.5'>
|
||||
<TabsList className='bg-muted/40 p-0.5'>
|
||||
{(Object.keys(LANG_LABELS) as Lang[]).map((l) => (
|
||||
<TabsTrigger key={l} value={l} className='h-7 px-2.5 text-xs'>
|
||||
{LANG_LABELS[l]}
|
||||
|
||||
@@ -35,10 +35,16 @@ import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { CopyButton } from '@/components/copy-button'
|
||||
import { StaticDataTable } from '@/components/data-table'
|
||||
import { Button } from '@/components/design-system/button'
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from '@/components/design-system/tabs'
|
||||
import { sideDrawerContentClassName } from '@/components/drawer-layout'
|
||||
import { GroupBadge } from '@/components/group-badge'
|
||||
import { PublicLayout } from '@/components/layout'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
@@ -47,7 +53,6 @@ import {
|
||||
SheetTitle,
|
||||
} from '@/components/ui/sheet'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { getPerfMetrics } from '@/features/performance-metrics/api'
|
||||
import {
|
||||
formatLatency,
|
||||
@@ -545,7 +550,7 @@ function ModelHeader(props: { model: PricingModel }) {
|
||||
</h1>
|
||||
<CopyButton
|
||||
value={model.model_name || ''}
|
||||
className='size-6'
|
||||
size='icon-xs'
|
||||
iconClassName='size-3'
|
||||
tooltip={t('Copy model name')}
|
||||
successTooltip={t('Copied!')}
|
||||
@@ -1165,7 +1170,7 @@ export function ModelDetailsContent(props: ModelDetailsContentProps) {
|
||||
<ModelHeader model={props.model} />
|
||||
|
||||
<Tabs defaultValue='overview' className='gap-4'>
|
||||
<TabsList className='bg-muted/60 grid w-full grid-cols-3 gap-1 rounded-lg p-1 group-data-horizontal/tabs:h-auto'>
|
||||
<TabsList className='bg-muted/60 grid w-full grid-cols-3 gap-1 rounded-lg p-1 group-data-horizontal/tabs:h-auto sm:group-data-horizontal/tabs:h-auto'>
|
||||
{TAB_VALUES.map((value) => {
|
||||
const Icon = TAB_META[value].icon
|
||||
return (
|
||||
@@ -1323,7 +1328,7 @@ export function ModelDetails() {
|
||||
<p className='text-muted-foreground mb-4 text-sm'>
|
||||
{t("The model you're looking for doesn't exist.")}
|
||||
</p>
|
||||
<Button onClick={handleBack} variant='outline' size='sm'>
|
||||
<Button onClick={handleBack} variant='outline'>
|
||||
{t('Back to Models')}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -1336,9 +1341,8 @@ export function ModelDetails() {
|
||||
<div className='mx-auto max-w-5xl px-4 sm:px-6'>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
onClick={handleBack}
|
||||
className='text-muted-foreground hover:text-foreground mb-4 h-auto gap-1 px-0 py-1 text-xs'
|
||||
className='text-muted-foreground hover:text-foreground mb-4 h-auto gap-1 px-0 py-1 text-xs sm:h-auto'
|
||||
>
|
||||
<ArrowLeft className='size-3.5' />
|
||||
{t('Back')}
|
||||
|
||||
+12
-25
@@ -100,11 +100,9 @@ export function usePricingColumns(
|
||||
cell: ({ row }) => {
|
||||
const isTokenBased = row.original.quota_type === QUOTA_TYPE_VALUES.TOKEN
|
||||
return (
|
||||
<StatusBadge
|
||||
label={isTokenBased ? t('Token') : t('Request')}
|
||||
variant={isTokenBased ? 'info' : 'neutral'}
|
||||
copyable={false}
|
||||
/>
|
||||
<StatusBadge variant={isTokenBased ? 'info' : 'neutral'}>
|
||||
{isTokenBased ? t('Token') : t('Request')}
|
||||
</StatusBadge>
|
||||
)
|
||||
},
|
||||
size: 80,
|
||||
@@ -334,12 +332,9 @@ export function usePricingColumns(
|
||||
return (
|
||||
<BadgeCell className='gap-1.5'>
|
||||
{vendorIcon}
|
||||
<StatusBadge
|
||||
label={model.vendor_name}
|
||||
autoColor={model.vendor_name}
|
||||
size='sm'
|
||||
copyable={false}
|
||||
/>
|
||||
<StatusBadge variant='neutral' size='sm'>
|
||||
{model.vendor_name}
|
||||
</StatusBadge>
|
||||
</BadgeCell>
|
||||
)
|
||||
},
|
||||
@@ -356,13 +351,9 @@ export function usePricingColumns(
|
||||
return (
|
||||
<BadgeListCell
|
||||
items={tags.map((tag) => (
|
||||
<StatusBadge
|
||||
key={tag}
|
||||
label={tag}
|
||||
autoColor={tag}
|
||||
size='sm'
|
||||
copyable={false}
|
||||
/>
|
||||
<StatusBadge key={tag} variant='neutral' size='sm'>
|
||||
{tag}
|
||||
</StatusBadge>
|
||||
))}
|
||||
/>
|
||||
)
|
||||
@@ -380,13 +371,9 @@ export function usePricingColumns(
|
||||
return (
|
||||
<BadgeListCell
|
||||
items={endpoints.map((ep) => (
|
||||
<StatusBadge
|
||||
key={ep}
|
||||
label={ep}
|
||||
autoColor={ep}
|
||||
size='sm'
|
||||
copyable={false}
|
||||
/>
|
||||
<StatusBadge key={ep} variant='neutral' size='sm'>
|
||||
{ep}
|
||||
</StatusBadge>
|
||||
))}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -20,8 +20,8 @@ import { ChevronDown, RotateCcw } from 'lucide-react'
|
||||
import type { ReactNode } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { Button } from '@/components/design-system/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
@@ -260,7 +260,6 @@ export function PricingSidebar(props: PricingSidebarProps) {
|
||||
size='sm'
|
||||
onClick={props.onClearFilters}
|
||||
disabled={!props.hasActiveFilters}
|
||||
className='h-7 gap-1.5 px-2 text-xs'
|
||||
>
|
||||
<RotateCcw className='size-3.5' />
|
||||
{t('Reset')}
|
||||
|
||||
@@ -20,13 +20,13 @@ import { ArrowUpDown, Check, Filter, Grid2X2, Table2 } from 'lucide-react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { Button } from '@/components/design-system/button'
|
||||
import {
|
||||
sideDrawerContentClassName,
|
||||
sideDrawerFormClassName,
|
||||
sideDrawerHeaderClassName,
|
||||
} from '@/components/drawer-layout'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -172,7 +172,6 @@ export function PricingToolbar(props: PricingToolbarProps) {
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
size='sm'
|
||||
onClick={() => setMobileFiltersOpen(true)}
|
||||
className='gap-1.5 xl:hidden'
|
||||
>
|
||||
@@ -222,14 +221,7 @@ export function PricingToolbar(props: PricingToolbarProps) {
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
render={
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
size='sm'
|
||||
className='h-8 gap-1.5 px-3 text-xs'
|
||||
/>
|
||||
}
|
||||
render={<Button type='button' variant='outline' />}
|
||||
>
|
||||
<ArrowUpDown className='size-3.5' />
|
||||
<span>{sortLabels[props.sortBy as SortOption] || t('Sort')}</span>
|
||||
|
||||
@@ -20,7 +20,7 @@ import { Search, X } from 'lucide-react'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Button } from '@/components/design-system/button'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export interface SearchBarProps {
|
||||
@@ -70,9 +70,9 @@ export function SearchBar(props: SearchBarProps) {
|
||||
{props.value ? (
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='icon'
|
||||
size='icon-sm'
|
||||
onClick={props.onClear}
|
||||
className='text-muted-foreground/60 hover:text-foreground size-7'
|
||||
className='text-muted-foreground/60 hover:text-foreground'
|
||||
aria-label={t('Clear search')}
|
||||
>
|
||||
<X className='size-4' />
|
||||
|
||||
Reference in New Issue
Block a user