feat(ui): improve mobile responsive layouts
This commit is contained in:
@@ -206,8 +206,8 @@ export function DynamicPricingBreakdown({
|
||||
})
|
||||
|
||||
return (
|
||||
<section className='min-w-0 py-4'>
|
||||
<div className='mb-4 flex items-start gap-2'>
|
||||
<section className='min-w-0 py-3 sm:py-4'>
|
||||
<div className='mb-3 flex items-start gap-2 sm:mb-4'>
|
||||
<span className='mt-0.5 inline-flex size-6 items-center justify-center rounded-full bg-amber-100 text-amber-700 shadow-sm dark:bg-amber-500/20 dark:text-amber-300'>
|
||||
<TagIcon className='size-3.5' />
|
||||
</span>
|
||||
@@ -222,11 +222,71 @@ export function DynamicPricingBreakdown({
|
||||
</div>
|
||||
|
||||
{hasTiers && (
|
||||
<div className='mb-4'>
|
||||
<div className='mb-3 sm:mb-4'>
|
||||
<div className='text-foreground mb-2 text-sm font-semibold'>
|
||||
{t('Tiered price table')}
|
||||
</div>
|
||||
<div className='-mx-4 max-w-[calc(100%+2rem)] overflow-x-auto sm:mx-0 sm:max-w-full'>
|
||||
<div className='space-y-1.5 sm:hidden'>
|
||||
{tiers.map((tier, i) => {
|
||||
const condSummary = formatConditionSummary(tier.conditions, t)
|
||||
const isMatched =
|
||||
matchedTierLabel != null &&
|
||||
matchedTierLabel !== '' &&
|
||||
tier.label === matchedTierLabel
|
||||
return (
|
||||
<div
|
||||
key={`tier-mobile-${i}`}
|
||||
className={cn(
|
||||
'rounded-md border p-2',
|
||||
isMatched &&
|
||||
'border-emerald-500/40 bg-emerald-500/10'
|
||||
)}
|
||||
>
|
||||
<div className='mb-1.5 flex flex-wrap items-center gap-1.5'>
|
||||
<Badge
|
||||
variant='secondary'
|
||||
className='bg-blue-100 text-blue-700 dark:bg-blue-500/20 dark:text-blue-300'
|
||||
>
|
||||
{tier.label || t('Default')}
|
||||
</Badge>
|
||||
{isMatched && (
|
||||
<Badge
|
||||
variant='secondary'
|
||||
className='bg-emerald-100 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-300'
|
||||
>
|
||||
{t('Matched')}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{condSummary && (
|
||||
<div className='text-muted-foreground mb-1.5 text-xs'>
|
||||
{condSummary}
|
||||
</div>
|
||||
)}
|
||||
<div className='grid grid-cols-2 gap-x-3 gap-y-1.5'>
|
||||
{visiblePriceFields.map((v) => {
|
||||
const value = Number(
|
||||
tier[v.field as string as keyof ParsedTier] || 0
|
||||
)
|
||||
return (
|
||||
<div key={v.field} className='min-w-0'>
|
||||
<div className='text-muted-foreground truncate text-[10px] font-medium tracking-wider uppercase'>
|
||||
{t(v.shortLabel)}
|
||||
</div>
|
||||
<div className='truncate font-mono text-sm font-semibold'>
|
||||
{value > 0
|
||||
? `${symbol}${(value * rate).toFixed(4)}`
|
||||
: '-'}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div className='hidden overflow-x-auto sm:block'>
|
||||
<Table className='text-sm'>
|
||||
<TableHeader>
|
||||
<TableRow className='hover:bg-transparent'>
|
||||
|
||||
@@ -620,16 +620,16 @@ export function FilterBar(props: FilterBarProps) {
|
||||
<Sheet open={mobileOpen} onOpenChange={setMobileOpen}>
|
||||
<SheetContent
|
||||
side='right'
|
||||
className='flex w-full flex-col overflow-hidden p-0 sm:max-w-md'
|
||||
className='flex h-dvh w-full flex-col overflow-hidden p-0 sm:max-w-md'
|
||||
>
|
||||
<SheetHeader className='border-b px-6 py-4'>
|
||||
<SheetHeader className='border-b px-4 py-3 sm:px-6 sm:py-4'>
|
||||
<SheetTitle>{t('Filters')}</SheetTitle>
|
||||
<SheetDescription className='sr-only'>
|
||||
{t('Filter models by type, endpoint, vendor, group and tags')}
|
||||
</SheetDescription>
|
||||
</SheetHeader>
|
||||
|
||||
<div className='flex-1 space-y-6 overflow-y-auto px-6 py-5'>
|
||||
<div className='flex-1 space-y-4 overflow-y-auto px-4 py-4 sm:space-y-6 sm:px-6 sm:py-5'>
|
||||
<MobileFilterGroup
|
||||
title={t('Pricing Type')}
|
||||
value={props.quotaTypeFilter}
|
||||
@@ -671,7 +671,7 @@ export function FilterBar(props: FilterBarProps) {
|
||||
<h3 className='text-foreground mb-3 text-sm font-semibold'>
|
||||
{t('Display Options')}
|
||||
</h3>
|
||||
<div className='space-y-4'>
|
||||
<div className='space-y-3 sm:space-y-4'>
|
||||
<div className='space-y-2'>
|
||||
<p className='text-muted-foreground text-xs'>
|
||||
{t('Price display')}
|
||||
@@ -704,8 +704,8 @@ export function FilterBar(props: FilterBarProps) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SheetFooter className='border-t px-6 py-4'>
|
||||
<div className='flex w-full gap-3'>
|
||||
<SheetFooter className='border-t px-4 py-3 sm:px-6 sm:py-4'>
|
||||
<div className='grid w-full grid-cols-2 gap-2 sm:flex sm:gap-3'>
|
||||
{props.hasActiveFilters && (
|
||||
<Button
|
||||
variant='outline'
|
||||
|
||||
@@ -68,7 +68,7 @@ export const ModelCard = memo(function ModelCard(props: ModelCardProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'group flex flex-col rounded-xl border p-4 transition-colors sm:p-5',
|
||||
'group flex flex-col rounded-xl border p-3 transition-colors sm:p-5',
|
||||
'hover:bg-muted/20'
|
||||
)}
|
||||
>
|
||||
@@ -175,12 +175,12 @@ export const ModelCard = memo(function ModelCard(props: ModelCardProps) {
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<p className='text-muted-foreground mt-3 line-clamp-1 flex-1 text-[13px] leading-relaxed sm:mt-4 sm:line-clamp-2 sm:min-h-[2.5rem]'>
|
||||
<p className='text-muted-foreground mt-2 line-clamp-1 flex-1 text-[13px] leading-relaxed sm:mt-4 sm:line-clamp-2 sm:min-h-[2.5rem]'>
|
||||
{props.model.description || t('No description available.')}
|
||||
</p>
|
||||
|
||||
{/* Footer row 1: group + billing type */}
|
||||
<div className='mt-3 flex flex-wrap items-center gap-x-2 gap-y-1 sm:mt-4'>
|
||||
<div className='mt-2 flex flex-wrap items-center gap-x-2 gap-y-1 sm:mt-4'>
|
||||
{primaryGroup && (
|
||||
<span className='text-muted-foreground text-xs font-medium'>
|
||||
{primaryGroup} {t('Groups')}
|
||||
|
||||
@@ -783,13 +783,13 @@ export function ModelDetailsDrawer(props: ModelDetailsDrawerProps) {
|
||||
<Sheet open={open} onOpenChange={onOpenChange}>
|
||||
<SheetContent
|
||||
side='right'
|
||||
className='flex w-full overflow-hidden p-0 sm:max-w-2xl xl:max-w-3xl'
|
||||
className='flex h-dvh w-full overflow-hidden p-0 sm:max-w-2xl xl:max-w-3xl'
|
||||
>
|
||||
<SheetHeader className='sr-only'>
|
||||
<SheetTitle>{props.model.model_name}</SheetTitle>
|
||||
<SheetDescription>{t('Model details')}</SheetDescription>
|
||||
</SheetHeader>
|
||||
<div className='flex-1 overflow-y-auto px-5 pt-12 pb-6 sm:px-6'>
|
||||
<div className='flex-1 overflow-y-auto px-4 pt-11 pb-5 sm:px-6 sm:pt-12 sm:pb-6'>
|
||||
<ModelDetailsContent {...contentProps} />
|
||||
</div>
|
||||
</SheetContent>
|
||||
|
||||
@@ -259,15 +259,15 @@ export function PricingToolbar(props: PricingToolbarProps) {
|
||||
<Sheet open={mobileFiltersOpen} onOpenChange={setMobileFiltersOpen}>
|
||||
<SheetContent
|
||||
side='right'
|
||||
className='flex w-full flex-col overflow-hidden p-0 sm:max-w-md'
|
||||
className='flex h-dvh w-full flex-col overflow-hidden p-0 sm:max-w-md'
|
||||
>
|
||||
<SheetHeader className='border-b px-6 py-4'>
|
||||
<SheetHeader className='border-b px-4 py-3 sm:px-6 sm:py-4'>
|
||||
<SheetTitle>{t('Filter')}</SheetTitle>
|
||||
<SheetDescription>
|
||||
{t('Filter models by provider, group, type, endpoint, and tags.')}
|
||||
</SheetDescription>
|
||||
</SheetHeader>
|
||||
<div className='flex-1 overflow-y-auto p-4'>
|
||||
<div className='flex-1 overflow-y-auto p-3 sm:p-4'>
|
||||
<PricingSidebar
|
||||
quotaTypeFilter={props.quotaTypeFilter}
|
||||
endpointTypeFilter={props.endpointTypeFilter}
|
||||
|
||||
Reference in New Issue
Block a user