🎨 fix(theme): align UI controls with global radius tokens

Remove hard-coded and capped border radius overrides so shared controls and feature actions consistently follow the active theme radius.

- Replace fixed radius utilities with semantic theme-aware radius tokens
- Remove redundant `rounded-full` and pixel-based overrides from header, toolbar, Playground, and utility actions
- Drop unused `StatusBadge` rounded prop usage
- Keep existing component behavior intact while improving global theme consistency
This commit is contained in:
t0ng7u 2026-05-08 01:50:03 +08:00
parent c19d5aa663
commit 948780e3fa
64 changed files with 193 additions and 129 deletions

View File

@ -11,7 +11,7 @@ export function IconThemeSystem({
xmlns='http://www.w3.org/2000/svg' xmlns='http://www.w3.org/2000/svg'
viewBox='0 0 79.86 51.14' viewBox='0 0 79.86 51.14'
className={cn( className={cn(
'overflow-hidden rounded-[6px]', 'overflow-hidden rounded-md',
'stroke-primary fill-primary group-data-unchecked:stroke-muted-foreground group-data-unchecked:fill-muted-foreground', 'stroke-primary fill-primary group-data-unchecked:stroke-muted-foreground group-data-unchecked:fill-muted-foreground',
className className
)} )}

View File

@ -159,7 +159,7 @@ export const BranchPrevious = ({
<Button <Button
aria-label={t('Previous branch')} aria-label={t('Previous branch')}
className={cn( className={cn(
'text-muted-foreground size-7 shrink-0 rounded-full transition-colors', 'text-muted-foreground size-7 shrink-0 transition-colors',
'hover:bg-accent hover:text-foreground', 'hover:bg-accent hover:text-foreground',
'disabled:pointer-events-none disabled:opacity-50', 'disabled:pointer-events-none disabled:opacity-50',
className className
@ -190,7 +190,7 @@ export const BranchNext = ({
<Button <Button
aria-label={t('Next branch')} aria-label={t('Next branch')}
className={cn( className={cn(
'text-muted-foreground size-7 shrink-0 rounded-full transition-colors', 'text-muted-foreground size-7 shrink-0 transition-colors',
'hover:bg-accent hover:text-foreground', 'hover:bg-accent hover:text-foreground',
'disabled:pointer-events-none disabled:opacity-50', 'disabled:pointer-events-none disabled:opacity-50',
className className

View File

@ -90,7 +90,7 @@ export const ConversationScrollButton = ({
!isAtBottom && ( !isAtBottom && (
<Button <Button
className={cn( className={cn(
'absolute bottom-4 left-[50%] translate-x-[-50%] rounded-full', 'absolute bottom-4 left-[50%] translate-x-[-50%]',
className className
)} )}
onClick={handleScrollToBottom} onClick={handleScrollToBottom}

View File

@ -68,11 +68,7 @@ export const InlineCitationCardTrigger = ({
delay={0} delay={0}
closeDelay={0} closeDelay={0}
render={ render={
<Badge <Badge className={cn('ml-1', className)} variant='secondary' {...props}>
className={cn('ml-1 rounded-full', className)}
variant='secondary'
{...props}
>
{sources[0] ? ( {sources[0] ? (
<> <>
{new URL(sources[0]).hostname}{' '} {new URL(sources[0]).hostname}{' '}

View File

@ -429,7 +429,7 @@ export type PromptInputProps = Omit<
) => void | Promise<void> ) => void | Promise<void>
/** /**
* Optional className applied to the inner InputGroup wrapper * Optional className applied to the inner InputGroup wrapper
* (useful for customizing rounded corners, e.g., rounded-[20px]). * (useful for layout or semantic radius utilities such as rounded-xl).
*/ */
groupClassName?: string groupClassName?: string
} }

View File

@ -40,7 +40,7 @@ export const Suggestion = ({
return ( return (
<Button <Button
className={cn('cursor-pointer rounded-full px-4', className)} className={cn('cursor-pointer px-4', className)}
onClick={handleClick} onClick={handleClick}
size={size} size={size}
type='button' type='button'

View File

@ -65,7 +65,7 @@ const getStatusBadge = (status: ExtendedToolState) => {
} }
return ( return (
<Badge className='gap-1.5 rounded-full text-xs' variant='secondary'> <Badge className='gap-1.5 text-xs' variant='secondary'>
{icons[status]} {icons[status]}
{labels[status]} {labels[status]}
</Badge> </Badge>

View File

@ -1,6 +1,7 @@
import type { ReactNode } from 'react' import type { ReactNode } from 'react'
import type { UseQueryResult } from '@tanstack/react-query' import type { UseQueryResult } from '@tanstack/react-query'
import { AutoSkeleton } from 'auto-skeleton-react' import { AutoSkeleton } from 'auto-skeleton-react'
import { useThemeRadiusPx } from '@/lib/theme-radius'
import { ErrorState } from '@/components/error-state' import { ErrorState } from '@/components/error-state'
interface ContentSkeletonProps { interface ContentSkeletonProps {
@ -13,6 +14,8 @@ interface ContentSkeletonProps {
} }
export function ContentSkeleton(props: ContentSkeletonProps) { export function ContentSkeleton(props: ContentSkeletonProps) {
const themeRadius = useThemeRadiusPx()
return ( return (
<div className={props.className}> <div className={props.className}>
<AutoSkeleton <AutoSkeleton
@ -21,7 +24,7 @@ export function ContentSkeleton(props: ContentSkeletonProps) {
animation: 'none', animation: 'none',
baseColor: 'var(--skeleton-base)', baseColor: 'var(--skeleton-base)',
highlightColor: 'var(--skeleton-highlight)', highlightColor: 'var(--skeleton-highlight)',
borderRadius: props.borderRadius ?? 6, borderRadius: props.borderRadius ?? themeRadius,
minTextHeight: props.minTextHeight ?? 14, minTextHeight: props.minTextHeight ?? 14,
maxDepth: props.maxDepth ?? 10, maxDepth: props.maxDepth ?? 10,
}} }}

View File

@ -64,7 +64,7 @@ export function ConfigDrawer() {
variant='ghost' variant='ghost'
aria-label={t('Open theme settings')} aria-label={t('Open theme settings')}
aria-describedby='config-drawer-description' aria-describedby='config-drawer-description'
className='rounded-full max-md:hidden' className='max-md:hidden'
/> />
} }
> >
@ -119,7 +119,7 @@ function SectionTitle(props: {
<Button <Button
size='icon' size='icon'
variant='secondary' variant='secondary'
className='size-4 rounded-full' className='size-4'
onClick={props.onReset} onClick={props.onReset}
aria-label='Reset' aria-label='Reset'
> >
@ -148,7 +148,7 @@ function RadioGroupItem(props: {
> >
<div <div
className={cn( className={cn(
'ring-border relative rounded-[6px] ring-[1px]', 'ring-border relative rounded-md ring-[1px]',
'group-data-checked:ring-primary group-data-checked:shadow-2xl', 'group-data-checked:ring-primary group-data-checked:shadow-2xl',
'group-focus-visible:ring-2' 'group-focus-visible:ring-2'
)} )}

View File

@ -162,7 +162,7 @@ export function DataTableBulkActions<TData>({
variant='outline' variant='outline'
size='icon' size='icon'
onClick={handleClearSelection} onClick={handleClearSelection}
className='size-6 rounded-full' className='size-6'
aria-label={t('Clear selection')} aria-label={t('Clear selection')}
title={t('Clear selection (Escape)')} title={t('Clear selection (Escape)')}
/> />

View File

@ -60,7 +60,7 @@ function ListSkeleton() {
<div key={i} className='px-3 py-2.5'> <div key={i} className='px-3 py-2.5'>
<div className='flex items-center justify-between'> <div className='flex items-center justify-between'>
<Skeleton className='h-4 w-32' /> <Skeleton className='h-4 w-32' />
<Skeleton className='h-5 w-16 rounded-full' /> <Skeleton className='h-5 w-16 rounded-md' />
</div> </div>
<div className='mt-1.5 grid grid-cols-2 gap-2'> <div className='mt-1.5 grid grid-cols-2 gap-2'>
<div className='flex-1'> <div className='flex-1'>

View File

@ -42,13 +42,7 @@ export function LanguageSwitcher() {
return ( return (
<DropdownMenu modal={false}> <DropdownMenu modal={false}>
<DropdownMenuTrigger <DropdownMenuTrigger
render={ render={<Button variant='ghost' size='icon' className='h-9 w-9' />}
<Button
variant='ghost'
size='icon'
className='h-9 w-9 rounded-full'
/>
}
> >
<Languages className='size-[1.2rem]' /> <Languages className='size-[1.2rem]' />
<span className='sr-only'>{t('Change language')}</span> <span className='sr-only'>{t('Change language')}</span>

View File

@ -7,7 +7,7 @@ const mockupVariants = cva(
{ {
variants: { variants: {
type: { type: {
mobile: 'rounded-[48px] max-w-[350px]', mobile: 'rounded-4xl max-w-[350px]',
responsive: 'rounded-md', responsive: 'rounded-md',
}, },
}, },

View File

@ -94,7 +94,7 @@ export function NavGroup({ title, items }: NavGroupProps) {
* Navigation badge component * Navigation badge component
*/ */
function NavBadge({ children }: { children: ReactNode }) { function NavBadge({ children }: { children: ReactNode }) {
return <Badge className='rounded-full px-1 py-0 text-xs'>{children}</Badge> return <Badge className='px-1 py-0 text-xs'>{children}</Badge>
} }
/** /**

View File

@ -195,8 +195,11 @@ export function PublicHeader(props: PublicHeaderProps) {
{showAuthButtons && !loading && isAuthenticated && ( {showAuthButtons && !loading && isAuthenticated && (
<ProfileDropdown /> <ProfileDropdown />
)} )}
<button <Button
className='hover:bg-muted/40 flex size-9 items-center justify-center rounded-lg transition-colors' type='button'
variant='ghost'
size='icon'
className='size-9'
onClick={() => setMobileOpen((v) => !v)} onClick={() => setMobileOpen((v) => !v)}
aria-label={t('Toggle navigation menu')} aria-label={t('Toggle navigation menu')}
> >
@ -220,7 +223,7 @@ export function PublicHeader(props: PublicHeaderProps) {
)} )}
/> />
</div> </div>
</button> </Button>
</div> </div>
</nav> </nav>
</div> </div>

View File

@ -25,7 +25,7 @@ export function LearnMore({
<Popover {...props}> <Popover {...props}>
<PopoverTrigger <PopoverTrigger
{...triggerProps} {...triggerProps}
className={cn('size-5 rounded-full', triggerProps?.className)} className={cn('size-5', triggerProps?.className)}
render={<Button variant='outline' size='icon' />} render={<Button variant='outline' size='icon' />}
> >
<span className='sr-only'>{t('Learn more')}</span> <span className='sr-only'>{t('Learn more')}</span>

View File

@ -71,7 +71,7 @@ const ModelTriggerButton = React.forwardRef<
size='sm' size='sm'
disabled={isDisabled} disabled={isDisabled}
className={cn( className={cn(
'flex h-8 items-center gap-2 rounded-full border px-3 font-medium', 'flex h-8 items-center gap-2 border px-3 font-medium',
'justify-center p-0 sm:w-auto sm:justify-start sm:px-3', 'justify-center p-0 sm:w-auto sm:justify-start sm:px-3',
'w-8', 'w-8',
'bg-background text-foreground', 'bg-background text-foreground',
@ -107,7 +107,7 @@ const GroupTriggerButton = React.forwardRef<
size='sm' size='sm'
disabled={isDisabled} disabled={isDisabled}
className={cn( className={cn(
'flex h-8 items-center gap-2 rounded-full border px-3 font-medium', 'flex h-8 items-center gap-2 border px-3 font-medium',
'justify-center p-0 sm:w-auto sm:justify-start sm:px-3', 'justify-center p-0 sm:w-auto sm:justify-start sm:px-3',
'w-8', 'w-8',
'bg-background text-foreground', 'bg-background text-foreground',

View File

@ -70,7 +70,7 @@ export function MultiSelect({
variant='ghost' variant='ghost'
size='icon-sm' size='icon-sm'
aria-label='Remove' aria-label='Remove'
className='ml-1 size-auto rounded-full p-0' className='ml-1 size-auto p-0'
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
handleUnselect(value) handleUnselect(value)

View File

@ -26,7 +26,7 @@ export function NotificationButton({
variant='ghost' variant='ghost'
size='icon' size='icon'
onClick={onClick} onClick={onClick}
className={cn('h-9 w-9 rounded-full', className)} className={cn('h-9 w-9', className)}
aria-label={t('Notifications')} aria-label={t('Notifications')}
> >
<Bell className='size-[1.2rem]' /> <Bell className='size-[1.2rem]' />
@ -35,7 +35,7 @@ export function NotificationButton({
{unreadCount > 0 && ( {unreadCount > 0 && (
<Badge <Badge
variant='destructive' variant='destructive'
className='absolute -top-1 -right-1 flex h-5 min-w-5 items-center justify-center rounded-full px-1 text-[10px] font-semibold tabular-nums' className='absolute -top-1 -right-1 flex h-5 min-w-5 items-center justify-center px-1 text-[10px] font-semibold tabular-nums'
> >
{unreadCount > 99 ? '99+' : unreadCount} {unreadCount > 99 ? '99+' : unreadCount}
</Badge> </Badge>

View File

@ -38,12 +38,7 @@ export function ProfileDropdown() {
<> <>
<DropdownMenu modal={false}> <DropdownMenu modal={false}>
<DropdownMenuTrigger <DropdownMenuTrigger
render={ render={<Button variant='ghost' className='relative size-6 p-0' />}
<Button
variant='ghost'
className='relative size-6 rounded-full p-0'
/>
}
> >
<Avatar className='size-6'> <Avatar className='size-6'>
<AvatarFallback <AvatarFallback

View File

@ -73,8 +73,6 @@ export interface StatusBadgeProps extends Omit<
showDot?: boolean showDot?: boolean
variant?: StatusVariant | null variant?: StatusVariant | null
size?: 'sm' | 'md' | 'lg' | null size?: 'sm' | 'md' | 'lg' | null
/** @deprecated No longer applicable in flat design */
rounded?: 'full' | 'md' | 'sm' | 'lg'
copyable?: boolean copyable?: boolean
copyText?: string copyText?: string
autoColor?: string autoColor?: string
@ -88,7 +86,6 @@ export function StatusBadge({
size = 'sm', size = 'sm',
pulse = false, pulse = false,
showDot = true, showDot = true,
rounded: _rounded,
copyable = true, copyable = true,
copyText, copyText,
autoColor, autoColor,

View File

@ -21,7 +21,7 @@ export function ThemeQuickSwitcher() {
<div <div
role='radiogroup' role='radiogroup'
aria-labelledby='theme-switcher-label' aria-labelledby='theme-switcher-label'
className='border-muted/50 bg-muted/40 inline-flex w-auto items-center gap-1.5 rounded-full border px-1.5 py-1' className='border-muted/50 bg-muted/40 inline-flex w-auto items-center gap-1.5 rounded-lg border px-1.5 py-1'
> >
<Button <Button
variant='ghost' variant='ghost'
@ -31,14 +31,14 @@ export function ThemeQuickSwitcher() {
aria-checked={theme === 'system'} aria-checked={theme === 'system'}
onClick={() => setTheme('system')} onClick={() => setTheme('system')}
className={cn( className={cn(
'relative size-7 rounded-full', 'relative size-7',
theme === 'system' && 'text-accent-foreground' theme === 'system' && 'text-accent-foreground'
)} )}
> >
{theme === 'system' && ( {theme === 'system' && (
<motion.span <motion.span
layoutId='theme-switcher-active' layoutId='theme-switcher-active'
className='bg-accent ring-border absolute inset-0 rounded-full ring-1' className='bg-accent ring-border absolute inset-0 rounded-md ring-1'
transition={{ transition={{
type: 'spring', type: 'spring',
stiffness: 500, stiffness: 500,
@ -58,14 +58,14 @@ export function ThemeQuickSwitcher() {
aria-checked={theme === 'light'} aria-checked={theme === 'light'}
onClick={() => setTheme('light')} onClick={() => setTheme('light')}
className={cn( className={cn(
'relative size-7 rounded-full', 'relative size-7',
theme === 'light' && 'text-accent-foreground' theme === 'light' && 'text-accent-foreground'
)} )}
> >
{theme === 'light' && ( {theme === 'light' && (
<motion.span <motion.span
layoutId='theme-switcher-active' layoutId='theme-switcher-active'
className='bg-accent ring-border absolute inset-0 rounded-full ring-1' className='bg-accent ring-border absolute inset-0 rounded-md ring-1'
transition={{ transition={{
type: 'spring', type: 'spring',
stiffness: 500, stiffness: 500,
@ -85,14 +85,14 @@ export function ThemeQuickSwitcher() {
aria-checked={theme === 'dark'} aria-checked={theme === 'dark'}
onClick={() => setTheme('dark')} onClick={() => setTheme('dark')}
className={cn( className={cn(
'relative size-7 rounded-full', 'relative size-7',
theme === 'dark' && 'text-accent-foreground' theme === 'dark' && 'text-accent-foreground'
)} )}
> >
{theme === 'dark' && ( {theme === 'dark' && (
<motion.span <motion.span
layoutId='theme-switcher-active' layoutId='theme-switcher-active'
className='bg-accent ring-border absolute inset-0 rounded-full ring-1' className='bg-accent ring-border absolute inset-0 rounded-md ring-1'
transition={{ transition={{
type: 'spring', type: 'spring',
stiffness: 500, stiffness: 500,

View File

@ -26,13 +26,7 @@ export function ThemeSwitch() {
return ( return (
<DropdownMenu modal={false}> <DropdownMenu modal={false}>
<DropdownMenuTrigger <DropdownMenuTrigger
render={ render={<Button variant='ghost' size='icon' className='h-9 w-9' />}
<Button
variant='ghost'
size='icon'
className='h-9 w-9 rounded-full'
/>
}
> >
<Sun className='size-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90' /> <Sun className='size-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90' />
<Moon className='absolute size-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0' /> <Moon className='absolute size-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0' />

View File

@ -22,14 +22,13 @@ const buttonVariants = cva(
size: { size: {
default: default:
'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2', 'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3", xs: "h-6 gap-1 rounded-md px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5", sm: "h-7 gap-1 rounded-md px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2', lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
icon: 'size-8', icon: 'size-8',
'icon-xs': 'icon-xs':
"size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3", "size-6 rounded-md in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
'icon-sm': 'icon-sm': 'size-7 rounded-md in-data-[slot=button-group]:rounded-lg',
'size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg',
'icon-lg': 'size-9', 'icon-lg': 'size-9',
}, },
}, },

View File

@ -185,7 +185,7 @@ function CarouselPrevious({
variant={variant} variant={variant}
size={size} size={size}
className={cn( className={cn(
'absolute touch-manipulation rounded-full', 'absolute touch-manipulation',
orientation === 'horizontal' orientation === 'horizontal'
? 'top-1/2 -left-12 -translate-y-1/2' ? 'top-1/2 -left-12 -translate-y-1/2'
: '-top-12 left-1/2 -translate-x-1/2 rotate-90', : '-top-12 left-1/2 -translate-x-1/2 rotate-90',
@ -215,7 +215,7 @@ function CarouselNext({
variant={variant} variant={variant}
size={size} size={size}
className={cn( className={cn(
'absolute touch-manipulation rounded-full', 'absolute touch-manipulation',
orientation === 'horizontal' orientation === 'horizontal'
? 'top-1/2 -right-12 -translate-y-1/2' ? 'top-1/2 -right-12 -translate-y-1/2'
: '-bottom-12 left-1/2 -translate-x-1/2 rotate-90', : '-bottom-12 left-1/2 -translate-x-1/2 rotate-90',

View File

@ -219,7 +219,7 @@ function ChartTooltipContent({
!hideIndicator && ( !hideIndicator && (
<div <div
className={cn( className={cn(
'shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)', 'shrink-0 rounded-sm border-(--color-border) bg-(--color-bg)',
{ {
'h-2.5 w-2.5': indicator === 'dot', 'h-2.5 w-2.5': indicator === 'dot',
'w-1': indicator === 'line', 'w-1': indicator === 'line',
@ -310,7 +310,7 @@ function ChartLegendContent({
<itemConfig.icon /> <itemConfig.icon />
) : ( ) : (
<div <div
className='h-2 w-2 shrink-0 rounded-[2px]' className='h-2 w-2 shrink-0 rounded-sm'
style={{ style={{
backgroundColor: item.color, backgroundColor: item.color,
}} }}

View File

@ -10,7 +10,7 @@ function Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {
<CheckboxPrimitive.Root <CheckboxPrimitive.Root
data-slot='checkbox' data-slot='checkbox'
className={cn( className={cn(
'peer border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary relative flex size-4 shrink-0 items-center justify-center rounded-[4px] border transition-colors outline-none group-has-disabled/field:opacity-50 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:ring-3 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-3', 'peer border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary relative flex size-4 shrink-0 items-center justify-center rounded-sm border transition-colors outline-none group-has-disabled/field:opacity-50 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:ring-3 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-3',
className className
)} )}
{...props} {...props}

View File

@ -22,7 +22,7 @@ function InputGroup({ className, ...props }: React.ComponentProps<'div'>) {
} }
const inputGroupAddonVariants = cva( const inputGroupAddonVariants = cva(
"flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4", "flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-sm [&>svg:not([class*='size-'])]:size-4",
{ {
variants: { variants: {
align: { align: {
@ -69,10 +69,9 @@ const inputGroupButtonVariants = cva(
{ {
variants: { variants: {
size: { size: {
xs: "h-6 gap-1 rounded-[calc(var(--radius)-3px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5", xs: "h-6 gap-1 rounded-md px-1.5 [&>svg:not([class*='size-'])]:size-3.5",
sm: '', sm: '',
'icon-xs': 'icon-xs': 'size-6 rounded-md p-0 has-[>svg]:p-0',
'size-6 rounded-[calc(var(--radius)-3px)] p-0 has-[>svg]:p-0',
'icon-sm': 'size-8 p-0 has-[>svg]:p-0', 'icon-sm': 'size-8 p-0 has-[>svg]:p-0',
}, },
}, },

View File

@ -24,7 +24,7 @@ function NativeSelect({
<select <select
data-slot='native-select' data-slot='native-select'
data-size={size} data-size={size}
className='border-input selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 h-8 w-full min-w-0 appearance-none rounded-lg border bg-transparent py-1 pr-8 pl-2.5 text-sm transition-colors outline-none select-none focus-visible:ring-3 disabled:pointer-events-none disabled:cursor-not-allowed aria-invalid:ring-3 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] data-[size=sm]:py-0.5' className='border-input selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 h-8 w-full min-w-0 appearance-none rounded-lg border bg-transparent py-1 pr-8 pl-2.5 text-sm transition-colors outline-none select-none focus-visible:ring-3 disabled:pointer-events-none disabled:cursor-not-allowed aria-invalid:ring-3 data-[size=sm]:h-7 data-[size=sm]:rounded-md data-[size=sm]:py-0.5'
{...props} {...props}
/> />
<HugeiconsIcon <HugeiconsIcon

View File

@ -46,7 +46,7 @@ function SelectTrigger({
data-slot='select-trigger' data-slot='select-trigger'
data-size={size} data-size={size}
className={cn( className={cn(
"border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 flex w-fit items-center justify-between gap-1.5 rounded-lg border bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:ring-3 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-3 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 flex w-fit items-center justify-between gap-1.5 rounded-lg border bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:ring-3 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-3 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-md *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className className
)} )}
{...props} {...props}

View File

@ -39,7 +39,7 @@ function ToggleGroup({
data-orientation={orientation} data-orientation={orientation}
style={{ '--gap': spacing } as React.CSSProperties} style={{ '--gap': spacing } as React.CSSProperties}
className={cn( className={cn(
'group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] rounded-lg data-vertical:flex-col data-vertical:items-stretch data-[size=sm]:rounded-[min(var(--radius-md),10px)]', 'group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] rounded-lg data-vertical:flex-col data-vertical:items-stretch data-[size=sm]:rounded-md',
className className
)} )}
{...props} {...props}

View File

@ -15,7 +15,7 @@ const toggleVariants = cva(
size: { size: {
default: default:
'h-8 min-w-8 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2', 'h-8 min-w-8 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
sm: "h-7 min-w-7 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5", sm: "h-7 min-w-7 rounded-md px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
lg: 'h-9 min-w-9 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2', lg: 'h-9 min-w-9 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
}, },
}, },

View File

@ -75,7 +75,7 @@ export function OAuthCallbackScreen({
<AuthLayout> <AuthLayout>
<div className='w-full space-y-8'> <div className='w-full space-y-8'>
<div className='flex flex-col items-center space-y-4 text-center'> <div className='flex flex-col items-center space-y-4 text-center'>
<div className='bg-muted flex h-16 w-16 items-center justify-center rounded-full'> <div className='bg-muted flex h-16 w-16 items-center justify-center rounded-2xl'>
<Icon className='h-8 w-8' /> <Icon className='h-8 w-8' />
</div> </div>
<div className='space-y-2'> <div className='space-y-2'>

View File

@ -92,7 +92,7 @@ export function SecureVerificationDialog({
<div className='flex-1 overflow-y-auto px-6 py-5'> <div className='flex-1 overflow-y-auto px-6 py-5'>
{availableTabs.length === 0 ? ( {availableTabs.length === 0 ? (
<div className='grid place-items-center gap-4 text-center'> <div className='grid place-items-center gap-4 text-center'>
<div className='bg-muted flex h-16 w-16 items-center justify-center rounded-full'> <div className='bg-muted flex h-16 w-16 items-center justify-center rounded-2xl'>
<ShieldCheck className='text-muted-foreground h-8 w-8' /> <ShieldCheck className='text-muted-foreground h-8 w-8' />
</div> </div>
<p className='text-muted-foreground text-sm'> <p className='text-muted-foreground text-sm'>

View File

@ -637,7 +637,7 @@ export function useChannelsColumns(): ColumnDef<Channel>[] {
<Tooltip> <Tooltip>
<TooltipTrigger <TooltipTrigger
render={ render={
<span className='border-border bg-muted text-primary inline-flex h-6 w-6 items-center justify-center rounded-full border' /> <span className='border-border bg-muted text-primary inline-flex h-6 w-6 items-center justify-center rounded-md border' />
} }
> >
<MultiKeyModeIcon className='h-3.5 w-3.5' /> <MultiKeyModeIcon className='h-3.5 w-3.5' />

View File

@ -264,7 +264,7 @@ function RateLimitGroupSection(props: RateLimitGroupSectionProps) {
<div className='text-muted-foreground flex flex-wrap items-center gap-2 text-xs'> <div className='text-muted-foreground flex flex-wrap items-center gap-2 text-xs'>
{props.description && <span>{props.description}</span>} {props.description && <span>{props.description}</span>}
{props.meteredFeature && ( {props.meteredFeature && (
<span className='bg-muted/60 inline-flex max-w-full items-center gap-2 rounded-full px-2 py-0.5'> <span className='bg-muted/60 inline-flex max-w-full items-center gap-2 rounded-md px-2 py-0.5'>
<span className='text-[11px]'>metered_feature</span> <span className='text-[11px]'>metered_feature</span>
<span className='min-w-0 font-mono text-xs break-all'> <span className='min-w-0 font-mono text-xs break-all'>
{props.meteredFeature} {props.meteredFeature}

View File

@ -2,6 +2,7 @@ import { useEffect, useMemo, useRef, useState } from 'react'
import { VChart } from '@visactor/react-vchart' import { VChart } from '@visactor/react-vchart'
import { AreaChart, BarChart3, WalletCards } from 'lucide-react' import { AreaChart, BarChart3, WalletCards } from 'lucide-react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useThemeRadiusPx } from '@/lib/theme-radius'
import type { TimeGranularity } from '@/lib/time' import type { TimeGranularity } from '@/lib/time'
import { VCHART_OPTION } from '@/lib/vchart' import { VCHART_OPTION } from '@/lib/vchart'
import { useThemeCustomization } from '@/context/theme-customization-provider' import { useThemeCustomization } from '@/context/theme-customization-provider'
@ -41,6 +42,10 @@ export function ConsumptionDistributionChart(
const { t } = useTranslation() const { t } = useTranslation()
const { resolvedTheme } = useTheme() const { resolvedTheme } = useTheme()
const { customization } = useThemeCustomization() const { customization } = useThemeCustomization()
const chartRadius = useThemeRadiusPx(
'--radius-md',
`${customization.preset}:${customization.radius}`
)
const [chartType, setChartType] = useState<ConsumptionDistributionChartType>( const [chartType, setChartType] = useState<ConsumptionDistributionChartType>(
props.defaultChartType ?? 'bar' props.defaultChartType ?? 'bar'
) )
@ -79,9 +84,17 @@ export function ConsumptionDistributionChart(
props.loading ? [] : props.data, props.loading ? [] : props.data,
timeGranularity, timeGranularity,
t, t,
customization.preset customization.preset,
chartRadius
), ),
[props.data, props.loading, timeGranularity, t, customization.preset] [
props.data,
props.loading,
timeGranularity,
t,
customization.preset,
chartRadius,
]
) )
const spec = chartType === 'bar' ? chartData.spec_line : chartData.spec_area const spec = chartType === 'bar' ? chartData.spec_line : chartData.spec_area

View File

@ -2,6 +2,7 @@ import { useEffect, useMemo, useState, useRef } from 'react'
import { VChart } from '@visactor/react-vchart' import { VChart } from '@visactor/react-vchart'
import { PieChart as PieChartIcon } from 'lucide-react' import { PieChart as PieChartIcon } from 'lucide-react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useThemeRadiusPx } from '@/lib/theme-radius'
import type { TimeGranularity } from '@/lib/time' import type { TimeGranularity } from '@/lib/time'
import { VCHART_OPTION } from '@/lib/vchart' import { VCHART_OPTION } from '@/lib/vchart'
import { useThemeCustomization } from '@/context/theme-customization-provider' import { useThemeCustomization } from '@/context/theme-customization-provider'
@ -39,6 +40,10 @@ export function ModelCharts(props: ModelChartsProps) {
const { t } = useTranslation() const { t } = useTranslation()
const { resolvedTheme } = useTheme() const { resolvedTheme } = useTheme()
const { customization } = useThemeCustomization() const { customization } = useThemeCustomization()
const chartRadius = useThemeRadiusPx(
'--radius-md',
`${customization.preset}:${customization.radius}`
)
const [activeTab, setActiveTab] = useState<ModelAnalyticsChartTab>( const [activeTab, setActiveTab] = useState<ModelAnalyticsChartTab>(
props.defaultChartTab ?? 'trend' props.defaultChartTab ?? 'trend'
) )
@ -77,9 +82,17 @@ export function ModelCharts(props: ModelChartsProps) {
props.loading ? [] : props.data, props.loading ? [] : props.data,
timeGranularity, timeGranularity,
t, t,
customization.preset customization.preset,
chartRadius
), ),
[props.data, props.loading, timeGranularity, t, customization.preset] [
props.data,
props.loading,
timeGranularity,
t,
customization.preset,
chartRadius,
]
) )
const spec = chartData[CHART_SPEC_KEYS[activeTab]] const spec = chartData[CHART_SPEC_KEYS[activeTab]]

View File

@ -209,7 +209,7 @@ function StartStepItem(props: {
)} )}
<span <span
className={cn( className={cn(
'bg-background relative z-10 flex size-8 shrink-0 items-center justify-center rounded-full border shadow-xs', 'bg-background relative z-10 flex size-8 shrink-0 items-center justify-center rounded-lg border shadow-xs',
props.step.completed && 'border-success/30 bg-success/10' props.step.completed && 'border-success/30 bg-success/10'
)} )}
> >
@ -657,7 +657,7 @@ export function OverviewDashboard() {
? t('Setup guide complete') ? t('Setup guide complete')
: t('Setup guide')} : t('Setup guide')}
</h3> </h3>
<span className='text-muted-foreground bg-background/60 rounded-full border px-2 py-0.5 text-xs'> <span className='text-muted-foreground bg-background/60 rounded-md border px-2 py-0.5 text-xs'>
{t('Setup progress: {{completed}}/{{total}}', { {t('Setup progress: {{completed}}/{{total}}', {
completed: completedStepCount, completed: completedStepCount,
total: startSteps.length, total: startSteps.length,

View File

@ -129,6 +129,7 @@ export function UserCharts() {
t, t,
topUserLimit, topUserLimit,
customization.preset, customization.preset,
customization.radius,
] ]
) )

View File

@ -80,7 +80,8 @@ export function processChartData(
data: QuotaDataItem[], data: QuotaDataItem[],
timeGranularity: TimeGranularity = 'day', timeGranularity: TimeGranularity = 'day',
t?: TFunction, t?: TFunction,
themeKey?: string themeKey?: string,
chartCornerRadius?: number
): ProcessedChartData { ): ProcessedChartData {
const tt: TFunction = t ?? ((x) => x) const tt: TFunction = t ?? ((x) => x)
const otherLabel = tt('Other') const otherLabel = tt('Other')
@ -472,7 +473,8 @@ export function processChartData(
valueField: 'value', valueField: 'value',
categoryField: 'type', categoryField: 'type',
pie: { pie: {
style: { cornerRadius: 10 }, style:
chartCornerRadius == null ? {} : { cornerRadius: chartCornerRadius },
state: { state: {
hover: { outerRadius: 0.85, stroke: '#000', lineWidth: 1 }, hover: { outerRadius: 0.85, stroke: '#000', lineWidth: 1 },
selected: { outerRadius: 0.85, stroke: '#000', lineWidth: 1 }, selected: { outerRadius: 0.85, stroke: '#000', lineWidth: 1 },

View File

@ -15,7 +15,7 @@ export function GatewayCard({ logo, systemName }: GatewayCardProps) {
const features = getGatewayFeatures(t) const features = getGatewayFeatures(t)
return ( return (
<div className='glass-3 group border-border/50 dark:border-border/20 relative overflow-hidden rounded-[32px] border p-10 shadow-2xl transition-all duration-500 sm:p-12 dark:shadow-[0_25px_80px_-15px_rgba(0,0,0,0.4)]'> <div className='glass-3 group border-border/50 dark:border-border/20 relative overflow-hidden rounded-4xl border p-10 shadow-2xl transition-all duration-500 sm:p-12 dark:shadow-[0_25px_80px_-15px_rgba(0,0,0,0.4)]'>
{/* Top gradient border effect */} {/* Top gradient border effect */}
<Separator className='absolute top-0 left-[10%] h-[2px] w-[80%] bg-gradient-to-r from-transparent via-amber-500/80 to-transparent' /> <Separator className='absolute top-0 left-[10%] h-[2px] w-[80%] bg-gradient-to-r from-transparent via-amber-500/80 to-transparent' />

View File

@ -62,7 +62,7 @@ function ApiKeysMobileSkeleton() {
> >
<div className='flex items-center justify-between'> <div className='flex items-center justify-between'>
<Skeleton className='h-4 w-32' /> <Skeleton className='h-4 w-32' />
<Skeleton className='h-5 w-16 rounded-full' /> <Skeleton className='h-5 w-16 rounded-md' />
</div> </div>
<div className='flex items-center justify-between gap-3'> <div className='flex items-center justify-between gap-3'>
<Skeleton className='h-7 w-44' /> <Skeleton className='h-7 w-44' />

View File

@ -66,7 +66,7 @@ export function LegalDocument({
<div className='mx-auto max-w-2xl py-12'> <div className='mx-auto max-w-2xl py-12'>
<Card className='border-dashed'> <Card className='border-dashed'>
<CardHeader className='flex flex-row items-center gap-4'> <CardHeader className='flex flex-row items-center gap-4'>
<div className='bg-muted rounded-full p-2'> <div className='bg-muted rounded-lg p-2'>
<FileWarning className='text-muted-foreground h-5 w-5' /> <FileWarning className='text-muted-foreground h-5 w-5' />
</div> </div>
<div className='space-y-1'> <div className='space-y-1'>

View File

@ -122,7 +122,7 @@ export function DeploymentAccessGuard({
return ( return (
<div className='mx-auto mt-8 max-w-md'> <div className='mx-auto mt-8 max-w-md'>
<div className='text-center'> <div className='text-center'>
<div className='mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-amber-100 dark:bg-amber-900/20'> <div className='mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-2xl bg-amber-100 dark:bg-amber-900/20'>
<Server className='h-8 w-8 text-amber-600 dark:text-amber-400' /> <Server className='h-8 w-8 text-amber-600 dark:text-amber-400' />
</div> </div>
<h3 className='mb-6 text-xl font-semibold'> <h3 className='mb-6 text-xl font-semibold'>
@ -153,7 +153,7 @@ export function DeploymentAccessGuard({
return ( return (
<div className='mx-auto mt-8 max-w-md'> <div className='mx-auto mt-8 max-w-md'>
<div className='text-center'> <div className='text-center'>
<div className='mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-red-100 dark:bg-red-900/20'> <div className='mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-2xl bg-red-100 dark:bg-red-900/20'>
<WifiOff className='h-8 w-8 text-red-600 dark:text-red-400' /> <WifiOff className='h-8 w-8 text-red-600 dark:text-red-400' />
</div> </div>
<h3 className='mb-6 text-xl font-semibold'> <h3 className='mb-6 text-xl font-semibold'>

View File

@ -85,7 +85,6 @@ export function useDeploymentsColumns(opts: {
showDot={config.showDot} showDot={config.showDot}
size='sm' size='sm'
copyable={false} copyable={false}
rounded='full'
/> />
) )
}, },
@ -117,7 +116,6 @@ export function useDeploymentsColumns(opts: {
autoColor={String(provider)} autoColor={String(provider)}
size='sm' size='sm'
copyable={false} copyable={false}
rounded='full'
/> />
) )
}, },
@ -161,7 +159,6 @@ export function useDeploymentsColumns(opts: {
variant='info' variant='info'
size='sm' size='sm'
copyable={false} copyable={false}
rounded='full'
/> />
) : null} ) : null}
</div> </div>

View File

@ -188,7 +188,7 @@ export function PrefillGroupManagementDialog({
<Button <Button
variant='ghost' variant='ghost'
size='icon' size='icon'
className='text-muted-foreground hover:text-foreground absolute top-4 right-4 rounded-full border border-transparent sm:top-5 sm:right-6' className='text-muted-foreground hover:text-foreground absolute top-4 right-4 border border-transparent sm:top-5 sm:right-6'
/> />
} }
> >

View File

@ -95,10 +95,7 @@ export function PlaygroundInput({
return ( return (
<div className='grid shrink-0 gap-4 px-1 md:pb-4'> <div className='grid shrink-0 gap-4 px-1 md:pb-4'>
<PromptInput <PromptInput groupClassName='rounded-xl' onSubmit={handleSubmit}>
groupClassName='rounded-[20px] [--radius:20px]'
onSubmit={handleSubmit}
>
<PromptInputTextarea <PromptInputTextarea
autoComplete='off' autoComplete='off'
autoCorrect='off' autoCorrect='off'
@ -117,7 +114,7 @@ export function PlaygroundInput({
<DropdownMenuTrigger <DropdownMenuTrigger
render={ render={
<PromptInputButton <PromptInputButton
className='!rounded-full border font-medium' className='border font-medium'
disabled={disabled} disabled={disabled}
variant='outline' variant='outline'
/> />
@ -156,7 +153,7 @@ export function PlaygroundInput({
</DropdownMenu> </DropdownMenu>
<PromptInputButton <PromptInputButton
className='rounded-full border font-medium' className='border font-medium'
disabled={disabled} disabled={disabled}
onClick={() => toast.info(t('Search feature in development'))} onClick={() => toast.info(t('Search feature in development'))}
variant='outline' variant='outline'
@ -180,7 +177,7 @@ export function PlaygroundInput({
{isGenerating && onStop ? ( {isGenerating && onStop ? (
<PromptInputButton <PromptInputButton
className='text-foreground rounded-full font-medium' className='text-foreground font-medium'
onClick={onStop} onClick={onStop}
variant='secondary' variant='secondary'
> >
@ -190,7 +187,7 @@ export function PlaygroundInput({
</PromptInputButton> </PromptInputButton>
) : ( ) : (
<PromptInputButton <PromptInputButton
className='text-foreground rounded-full font-medium' className='text-foreground font-medium'
disabled={disabled || !text.trim()} disabled={disabled || !text.trim()}
type='submit' type='submit'
variant='secondary' variant='secondary'

View File

@ -12,7 +12,7 @@ export function getMessageContentStyles() {
'group-[.is-user]:text-foreground', 'group-[.is-user]:text-foreground',
'group-[.is-user]:bg-secondary', 'group-[.is-user]:bg-secondary',
'dark:group-[.is-user]:bg-muted', 'dark:group-[.is-user]:bg-muted',
'group-[.is-user]:rounded-[24px]', 'group-[.is-user]:rounded-3xl',
// Assistant bubble: flat serif style (one-sided style) // Assistant bubble: flat serif style (one-sided style)
'group-[.is-assistant]:text-foreground', 'group-[.is-assistant]:text-foreground',
'group-[.is-assistant]:bg-transparent', 'group-[.is-assistant]:bg-transparent',

View File

@ -179,7 +179,7 @@ export function DynamicPricingBreakdown({
return ( return (
<section className='min-w-0 py-4'> <section className='min-w-0 py-4'>
<div className='mb-3 flex items-center gap-2'> <div className='mb-3 flex items-center gap-2'>
<span className='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'> <span className='inline-flex size-6 items-center justify-center rounded-lg bg-amber-100 text-amber-700 shadow-sm dark:bg-amber-500/20 dark:text-amber-300'>
<TagIcon className='size-3.5' /> <TagIcon className='size-3.5' />
</span> </span>
<div> <div>
@ -212,7 +212,7 @@ export function DynamicPricingBreakdown({
return ( return (
<section className='min-w-0 py-3 sm:py-4'> <section className='min-w-0 py-3 sm:py-4'>
<div className='mb-3 flex items-start gap-2 sm:mb-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'> <span className='mt-0.5 inline-flex size-6 items-center justify-center rounded-lg bg-amber-100 text-amber-700 shadow-sm dark:bg-amber-500/20 dark:text-amber-300'>
<TagIcon className='size-3.5' /> <TagIcon className='size-3.5' />
</span> </span>
<div> <div>

View File

@ -67,7 +67,7 @@ function FilterBarSkeleton() {
{[80, 90, 75, 85, 70].map((width, i) => ( {[80, 90, 75, 85, 70].map((width, i) => (
<Skeleton <Skeleton
key={i} key={i}
className='h-8 rounded-full' className='h-8 rounded-lg'
style={{ width: `${width}px` }} style={{ width: `${width}px` }}
/> />
))} ))}

View File

@ -41,7 +41,7 @@ function RankBadge(props: { rank: number }) {
return ( return (
<span <span
className={cn( className={cn(
'inline-flex size-7 shrink-0 items-center justify-center rounded-full font-mono text-xs font-bold tabular-nums', 'inline-flex size-7 shrink-0 items-center justify-center rounded-md font-mono text-xs font-bold tabular-nums',
palette palette
)} )}
> >

View File

@ -1,9 +1,11 @@
import { useMemo } from 'react' import { useMemo } from 'react'
import { VChart } from '@visactor/react-vchart' import { VChart } from '@visactor/react-vchart'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useThemeRadiusPx } from '@/lib/theme-radius'
import { useChartTheme } from '@/lib/use-chart-theme' import { useChartTheme } from '@/lib/use-chart-theme'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { VCHART_OPTION } from '@/lib/vchart' import { VCHART_OPTION } from '@/lib/vchart'
import { useThemeCustomization } from '@/context/theme-customization-provider'
import type { LatencyTimePoint, UptimeDayPoint } from '../lib/mock-stats' import type { LatencyTimePoint, UptimeDayPoint } from '../lib/mock-stats'
function formatHourLabel(iso: string): string { function formatHourLabel(iso: string): string {
@ -246,6 +248,11 @@ export function ThroughputBarChart(props: {
}) { }) {
const { t } = useTranslation() const { t } = useTranslation()
const { resolvedTheme, themeReady } = useChartTheme() const { resolvedTheme, themeReady } = useChartTheme()
const { customization } = useThemeCustomization()
const barRadius = useThemeRadiusPx(
'--radius-sm',
`${customization.preset}:${customization.radius}`
)
const filtered = useMemo( const filtered = useMemo(
() => props.rows.filter((r) => r.throughput_tps > 0), () => props.rows.filter((r) => r.throughput_tps > 0),
@ -261,7 +268,10 @@ export function ThroughputBarChart(props: {
xField: 'throughput_tps', xField: 'throughput_tps',
yField: 'group', yField: 'group',
bar: { bar: {
style: { fill: '#6366f1', cornerRadius: 2 }, style: {
fill: '#6366f1',
...(barRadius == null ? {} : { cornerRadius: barRadius }),
},
}, },
label: { label: {
visible: true, visible: true,
@ -294,7 +304,7 @@ export function ThroughputBarChart(props: {
}, },
}, },
} }
}, [filtered, t]) }, [barRadius, filtered, t])
if (filtered.length === 0) { if (filtered.length === 0) {
return null return null

View File

@ -87,7 +87,7 @@ export function UptimeSparkline(props: UptimeSparklineProps) {
render={ render={
<div <div
className={cn( className={cn(
'rounded-[1px] transition-opacity hover:opacity-80', 'rounded-sm transition-opacity hover:opacity-80',
barWidth, barWidth,
containerHeight, containerHeight,
'flex items-end' 'flex items-end'
@ -97,7 +97,7 @@ export function UptimeSparkline(props: UptimeSparklineProps) {
> >
<div <div
className={cn( className={cn(
'w-full rounded-[1px]', 'w-full rounded-sm',
colourFor(day.uptime_pct), colourFor(day.uptime_pct),
heightFor(day.uptime_pct) heightFor(day.uptime_pct)
)} )}

View File

@ -95,7 +95,7 @@ function FilterChip(props: {
{(props.option.suffix || props.option.count != null) && ( {(props.option.suffix || props.option.count != null) && (
<span <span
className={cn( className={cn(
'rounded-full px-1.5 py-0.5 text-[10px]', 'rounded-md px-1.5 py-0.5 text-[10px]',
props.active props.active
? 'bg-background text-foreground' ? 'bg-background text-foreground'
: 'bg-muted text-muted-foreground' : 'bg-muted text-muted-foreground'

View File

@ -154,7 +154,7 @@ export function PricingToolbar(props: PricingToolbarProps) {
<Filter className='size-4' /> <Filter className='size-4' />
{t('Filter')} {t('Filter')}
{props.activeFilterCount > 0 && ( {props.activeFilterCount > 0 && (
<Badge className='ml-0.5 size-5 justify-center rounded-full p-0 text-[10px]'> <Badge className='ml-0.5 size-5 justify-center p-0 text-[10px]'>
{props.activeFilterCount} {props.activeFilterCount}
</Badge> </Badge>
)} )}

View File

@ -212,13 +212,13 @@ export function CheckinCalendarCard({
<div className='p-6'> <div className='p-6'>
<div className='flex items-start justify-between gap-4'> <div className='flex items-start justify-between gap-4'>
<div className='flex items-center gap-3'> <div className='flex items-center gap-3'>
<Skeleton className='h-10 w-10 rounded-full' /> <Skeleton className='h-10 w-10 rounded-xl' />
<div className='space-y-2'> <div className='space-y-2'>
<Skeleton className='h-5 w-32' /> <Skeleton className='h-5 w-32' />
<Skeleton className='h-3 w-56' /> <Skeleton className='h-3 w-56' />
</div> </div>
</div> </div>
<Skeleton className='h-9 w-28 rounded-full' /> <Skeleton className='h-9 w-28 rounded-md' />
</div> </div>
</div> </div>
</div> </div>
@ -280,7 +280,7 @@ export function CheckinCalendarCard({
{t('Daily Check-in')} {t('Daily Check-in')}
</h3> </h3>
{checkedToday && ( {checkedToday && (
<div className='inline-flex items-center gap-1 rounded-full bg-emerald-500/10 px-2 py-0.5 text-[11px] font-medium text-emerald-600 sm:gap-1.5 sm:px-2.5 sm:text-xs dark:text-emerald-400'> <div className='inline-flex items-center gap-1 rounded-md bg-emerald-500/10 px-2 py-0.5 text-[11px] font-medium text-emerald-600 sm:gap-1.5 sm:px-2.5 sm:text-xs dark:text-emerald-400'>
<Sparkles className='h-2.5 w-2.5 sm:h-3 sm:w-3' /> <Sparkles className='h-2.5 w-2.5 sm:h-3 sm:w-3' />
{t('Checked in')} {t('Checked in')}
</div> </div>
@ -304,7 +304,7 @@ export function CheckinCalendarCard({
onClick={() => doCheckin()} onClick={() => doCheckin()}
disabled={checkinLoading || checkedToday} disabled={checkinLoading || checkedToday}
size='sm' size='sm'
className='w-full shrink-0 rounded-full sm:w-auto' className='w-full shrink-0 sm:w-auto'
> >
{checkinLoading {checkinLoading
? t('Loading...') ? t('Loading...')

View File

@ -47,7 +47,7 @@ export function TelegramBindDialog({
</Alert> </Alert>
<div className='flex flex-col items-center justify-center gap-4 rounded-lg border p-6'> <div className='flex flex-col items-center justify-center gap-4 rounded-lg border p-6'>
<div className='flex h-12 w-12 items-center justify-center rounded-full bg-blue-100 dark:bg-blue-900'> <div className='flex h-12 w-12 items-center justify-center rounded-xl bg-blue-100 dark:bg-blue-900'>
<Send className='h-6 w-6 text-blue-600 dark:text-blue-400' /> <Send className='h-6 w-6 text-blue-600 dark:text-blue-400' />
</div> </div>

View File

@ -2,8 +2,10 @@ import { useMemo } from 'react'
import { VChart } from '@visactor/react-vchart' import { VChart } from '@visactor/react-vchart'
import { PieChart } from 'lucide-react' import { PieChart } from 'lucide-react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useThemeRadiusPx } from '@/lib/theme-radius'
import { useChartTheme } from '@/lib/use-chart-theme' import { useChartTheme } from '@/lib/use-chart-theme'
import { VCHART_OPTION } from '@/lib/vchart' import { VCHART_OPTION } from '@/lib/vchart'
import { useThemeCustomization } from '@/context/theme-customization-provider'
import { formatShare, formatTokens } from '../lib/format' import { formatShare, formatTokens } from '../lib/format'
import type { RankingPeriod, VendorRanking, VendorShareSeries } from '../types' import type { RankingPeriod, VendorRanking, VendorShareSeries } from '../types'
import { VendorLink } from './entity-links' import { VendorLink } from './entity-links'
@ -84,6 +86,11 @@ type MarketShareSectionProps = {
export function MarketShareSection(props: MarketShareSectionProps) { export function MarketShareSection(props: MarketShareSectionProps) {
const { t } = useTranslation() const { t } = useTranslation()
const { resolvedTheme, themeReady } = useChartTheme() const { resolvedTheme, themeReady } = useChartTheme()
const { customization } = useThemeCustomization()
const barRadius = useThemeRadiusPx(
'--radius-sm',
`${customization.preset}:${customization.radius}`
)
const colourMap = useMemo( const colourMap = useMemo(
() => buildVendorColourMap(props.history.vendors.map((v) => v.name)), () => buildVendorColourMap(props.history.vendors.map((v) => v.name)),
@ -112,7 +119,9 @@ export function MarketShareSection(props: MarketShareSectionProps) {
stack: true, stack: true,
paddingInner: 0.12, paddingInner: 0.12,
legends: { visible: false }, legends: { visible: false },
bar: { style: { cornerRadius: 1 } }, bar: {
style: barRadius == null ? {} : { cornerRadius: barRadius },
},
color: { specified: colourMap }, color: { specified: colourMap },
axes: [ axes: [
{ {
@ -175,7 +184,7 @@ export function MarketShareSection(props: MarketShareSectionProps) {
}, },
animationAppear: { duration: 500 }, animationAppear: { duration: 500 },
} }
}, [colourMap, orderedPoints]) }, [barRadius, colourMap, orderedPoints])
const visible = props.rows.slice(0, MAX_VENDORS_IN_LIST) const visible = props.rows.slice(0, MAX_VENDORS_IN_LIST)
const half = Math.ceil(visible.length / 2) const half = Math.ceil(visible.length / 2)

View File

@ -2,8 +2,10 @@ import { useMemo } from 'react'
import { VChart } from '@visactor/react-vchart' import { VChart } from '@visactor/react-vchart'
import { BarChart3, Trophy } from 'lucide-react' import { BarChart3, Trophy } from 'lucide-react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useThemeRadiusPx } from '@/lib/theme-radius'
import { useChartTheme } from '@/lib/use-chart-theme' import { useChartTheme } from '@/lib/use-chart-theme'
import { VCHART_OPTION } from '@/lib/vchart' import { VCHART_OPTION } from '@/lib/vchart'
import { useThemeCustomization } from '@/context/theme-customization-provider'
import { formatTokens } from '../lib/format' import { formatTokens } from '../lib/format'
import type { ModelHistorySeries, ModelRanking, RankingPeriod } from '../types' import type { ModelHistorySeries, ModelRanking, RankingPeriod } from '../types'
import { ModelLeaderboard } from './model-leaderboard' import { ModelLeaderboard } from './model-leaderboard'
@ -32,6 +34,11 @@ type ModelsSectionProps = {
export function ModelsSection(props: ModelsSectionProps) { export function ModelsSection(props: ModelsSectionProps) {
const { t } = useTranslation() const { t } = useTranslation()
const { resolvedTheme, themeReady } = useChartTheme() const { resolvedTheme, themeReady } = useChartTheme()
const { customization } = useThemeCustomization()
const barRadius = useThemeRadiusPx(
'--radius-sm',
`${customization.preset}:${customization.radius}`
)
// Order points so the largest model appears at the bottom of every stack. // Order points so the largest model appears at the bottom of every stack.
const orderedPoints = useMemo(() => { const orderedPoints = useMemo(() => {
@ -59,7 +66,9 @@ export function ModelsSection(props: ModelsSectionProps) {
yField: 'tokens', yField: 'tokens',
seriesField: 'model', seriesField: 'model',
stack: true, stack: true,
bar: { style: { cornerRadius: 1 } }, bar: {
style: barRadius == null ? {} : { cornerRadius: barRadius },
},
legends: { visible: false }, legends: { visible: false },
axes: [ axes: [
{ {
@ -132,7 +141,7 @@ export function ModelsSection(props: ModelsSectionProps) {
}, },
animationAppear: { duration: 500 }, animationAppear: { duration: 500 },
} }
}, [orderedPoints, t]) }, [barRadius, orderedPoints, t])
return ( return (
<section className='bg-card overflow-hidden rounded-lg border'> <section className='bg-card overflow-hidden rounded-lg border'>

View File

@ -32,7 +32,7 @@ export function CompleteStep({ status, values }: CompleteStepProps) {
return ( return (
<div className='flex flex-col items-center gap-6 text-center'> <div className='flex flex-col items-center gap-6 text-center'>
<div className='rounded-full bg-emerald-500/10 p-4 text-emerald-600 dark:bg-emerald-500/20 dark:text-emerald-300'> <div className='rounded-2xl bg-emerald-500/10 p-4 text-emerald-600 dark:bg-emerald-500/20 dark:text-emerald-300'>
<CheckCircle2 className='size-8' /> <CheckCircle2 className='size-8' />
</div> </div>
<div className='space-y-2'> <div className='space-y-2'>

View File

@ -320,7 +320,7 @@ export function SetupWizard() {
<div className='flex items-start gap-3'> <div className='flex items-start gap-3'>
<span <span
className={cn( className={cn(
'flex size-6 items-center justify-center rounded-full border text-xs font-semibold', 'flex size-6 items-center justify-center rounded-md border text-xs font-semibold',
isActive isActive
? 'border-primary bg-primary text-primary-foreground' ? 'border-primary bg-primary text-primary-foreground'
: isCompleted : isCompleted

View File

@ -102,7 +102,7 @@ export function AmountOptionsVisualEditor({
e.stopPropagation() e.stopPropagation()
handleRemove(amount) handleRemove(amount)
}} }}
className='hover:bg-muted-foreground/20 size-auto rounded-full p-0.5' className='hover:bg-muted-foreground/20 size-auto p-0.5'
aria-label={t('Remove ${{amount}}', { amount })} aria-label={t('Remove ${{amount}}', { amount })}
> >
<X className='h-3.5 w-3.5' /> <X className='h-3.5 w-3.5' />

33
web/default/src/lib/theme-radius.ts vendored Normal file
View File

@ -0,0 +1,33 @@
import { useEffect, useState } from 'react'
export function resolveThemeRadiusPx(
cssVariable = '--radius-md'
): number | undefined {
if (typeof document === 'undefined') return undefined
const probe = document.createElement('div')
probe.style.borderRadius = `var(${cssVariable})`
probe.style.pointerEvents = 'none'
probe.style.position = 'absolute'
probe.style.visibility = 'hidden'
document.documentElement.appendChild(probe)
const resolvedRadius = getComputedStyle(probe).borderTopLeftRadius
probe.remove()
const parsedRadius = Number.parseFloat(resolvedRadius)
return Number.isFinite(parsedRadius) ? parsedRadius : undefined
}
export function useThemeRadiusPx(
cssVariable = '--radius-md',
refreshKey?: string
): number | undefined {
const [radius, setRadius] = useState<number | undefined>()
useEffect(() => {
setRadius(resolveThemeRadiusPx(cssVariable))
}, [cssVariable, refreshKey])
return radius
}