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}
|
||||
|
||||
+5
-5
@@ -129,7 +129,7 @@ export function Pricing() {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<PublicLayout showMainContainer={false}>
|
||||
<div className='mx-auto w-full max-w-[1800px] px-4 pt-20 pb-10 sm:px-6 xl:px-8'>
|
||||
<div className='mx-auto w-full max-w-[1800px] px-3 pt-16 pb-8 sm:px-6 sm:pt-20 sm:pb-10 xl:px-8'>
|
||||
<LoadingSkeleton viewMode={viewMode} />
|
||||
</div>
|
||||
</PublicLayout>
|
||||
@@ -152,15 +152,15 @@ export function Pricing() {
|
||||
WebkitMaskImage: 'linear-gradient(to bottom, black 40%, transparent 100%)',
|
||||
}}
|
||||
/>
|
||||
<PageTransition className='relative mx-auto w-full max-w-[1800px] px-4 pt-20 pb-10 sm:px-6 xl:px-8'>
|
||||
<header className='mx-auto mb-8 max-w-3xl pt-8 text-center sm:mb-10 sm:pt-10'>
|
||||
<PageTransition className='relative mx-auto w-full max-w-[1800px] px-3 pt-16 pb-8 sm:px-6 sm:pt-20 sm:pb-10 xl:px-8'>
|
||||
<header className='mx-auto mb-5 max-w-3xl pt-5 text-center sm:mb-10 sm:pt-10'>
|
||||
<p className='text-muted-foreground mb-3 text-xs font-medium tracking-widest uppercase'>
|
||||
{t('Models Directory')}
|
||||
</p>
|
||||
<h1 className='text-[clamp(2rem,5.5vw,3.5rem)] leading-[1.15] font-bold tracking-tight'>
|
||||
{t('Model Square')}
|
||||
</h1>
|
||||
<p className='text-muted-foreground/80 mt-4 text-sm sm:text-base'>
|
||||
<p className='text-muted-foreground/80 mt-3 text-sm sm:mt-4 sm:text-base'>
|
||||
{t('This site currently has {{count}} models enabled', {
|
||||
count: models?.length || 0,
|
||||
})}
|
||||
@@ -175,7 +175,7 @@ export function Pricing() {
|
||||
onChange={setSearchInput}
|
||||
onClear={clearSearch}
|
||||
placeholder={t('Search model name, provider, endpoint, or tag...')}
|
||||
className='mx-auto mt-6 max-w-2xl'
|
||||
className='mx-auto mt-4 max-w-2xl sm:mt-6'
|
||||
/>
|
||||
</header>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user