Merge pull request #37010 from BerriAI/litellm_shadcn_next_0814

fix(ui): de-duplicate the reset budget option and polish shadcn surfaces
This commit is contained in:
yuneng-jiang 2026-08-15 17:18:25 -07:00 committed by GitHub
commit 992a8123ac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 295 additions and 154 deletions

View file

@ -2974,6 +2974,11 @@
"count": 1
}
},
"src/components/ui/button-group.tsx": {
"local/filename-pascal-case": {
"count": 1
}
},
"src/components/ui/button.tsx": {
"local/filename-pascal-case": {
"count": 1

View file

@ -190,7 +190,7 @@ const CacheDashboard: React.FC<CachePageProps> = ({ accessToken, token, userRole
Metrics&quot; on the Usage page or individual requests in the Logs page.
</p>
<div className="mt-4 grid grid-cols-1 gap-4 md:grid-cols-3">
<div className="mt-4 grid grid-cols-1 items-center gap-4 md:grid-cols-3">
<Combobox
multiple
items={uniqueApiKeys}
@ -207,7 +207,7 @@ const CacheDashboard: React.FC<CachePageProps> = ({ accessToken, token, userRole
))
}
</ComboboxValue>
<ComboboxChipsInput placeholder="Select Virtual Keys" className="border-0 bg-transparent" />
<ComboboxChipsInput placeholder="Select Virtual Keys" />
</ComboboxChips>
<ComboboxContent anchor={anchor1}>
<ComboboxEmpty>No virtual keys found</ComboboxEmpty>
@ -237,7 +237,7 @@ const CacheDashboard: React.FC<CachePageProps> = ({ accessToken, token, userRole
))
}
</ComboboxValue>
<ComboboxChipsInput placeholder="Select Models" className="border-0 bg-transparent" />
<ComboboxChipsInput placeholder="Select Models" />
</ComboboxChips>
<ComboboxContent anchor={anchor2}>
<ComboboxEmpty>No models found</ComboboxEmpty>

View file

@ -63,7 +63,7 @@ const CostOptimizationView: React.FC<CostOptimizationViewProps> = ({ accessToken
</div>
<Tabs defaultValue="usage" onValueChange={handleTabChange}>
<TabsList variant="line" className="h-auto w-full justify-start rounded-none border-b p-0">
<TabsList variant="line" className="h-auto w-full justify-start rounded-none p-0">
<TabsTrigger value="usage" className="flex-none rounded-none px-4 py-2">
Overall
</TabsTrigger>

View file

@ -131,7 +131,7 @@ export function GuardrailTestPanel({
</div>
{/* Input Section */}
<div className="flex-1 overflow-auto space-y-4">
<div className="flex-1 space-y-4 overflow-auto px-1">
<div className="space-y-3">
<div>
<div className="mb-2 flex items-center justify-between">

View file

@ -126,7 +126,7 @@ const GuardrailsPanel: React.FC<GuardrailsPanelProps> = ({ accessToken, userRole
return (
<div className="w-full mx-auto flex-auto overflow-y-auto m-8 p-2">
<Tabs defaultValue="guardrails">
<TabsList>
<TabsList variant="line">
{isAdmin && (
<>
<TabsTrigger value="garden" className="flex-none">

View file

@ -532,10 +532,7 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
{option.label}
</ComboboxChip>
))}
<ComboboxChipsInput
className="border-0 bg-transparent"
placeholder={mode.length === 0 ? "Select modes" : undefined}
/>
<ComboboxChipsInput placeholder={mode.length === 0 ? "Select modes" : undefined} />
</ComboboxChips>
<ComboboxContent anchor={anchor}>
<ComboboxEmpty>No matching modes</ComboboxEmpty>

View file

@ -59,7 +59,6 @@ export const CategoryFilter: React.FC<CategoryFilterProps> = ({ categories, sele
</ComboboxChip>
))}
<ComboboxChipsInput
className="border-0 bg-transparent"
placeholder={selectedCategories.length === 0 ? "Select categories to filter by" : undefined}
/>
</ComboboxChips>

View file

@ -175,7 +175,7 @@ export default function ModelsAndEndpointsPage() {
) : (
<Tabs value={activeKey} onValueChange={setActiveKey}>
<div className="flex min-w-0 flex-nowrap items-center gap-3 border-b">
<div className="-mb-1.5 min-w-0 flex-1 overflow-x-auto pb-1.5">
<div className="no-scrollbar scroll-fade-e -mb-1.5 min-w-0 flex-1 overflow-x-auto pb-1.5">
<TabsList variant="line" className="w-max justify-start">
{visibleSlugs.map((slug) => {
const key = slug || BASE_TAB_KEY;

View file

@ -891,7 +891,7 @@ const UsagePage: React.FC<UsagePageProps> = ({ accessToken, token, userRole, use
))
}
</ComboboxValue>
<ComboboxChipsInput placeholder="Select tags" className="border-0 bg-transparent" />
<ComboboxChipsInput placeholder="Select tags" />
</ComboboxChips>
<ComboboxContent anchor={anchor}>
<ComboboxEmpty>No tags found</ComboboxEmpty>

View file

@ -54,15 +54,12 @@ export function ChatComposer({
return (
<div className={cn("relative flex w-full flex-col gap-3", className)}>
{showSuggestions && suggestions.length > 0 && (
<div
className="flex w-full gap-2 overflow-x-auto pb-1 sm:grid sm:grid-cols-2 sm:overflow-visible"
data-testid="chat-suggested-actions"
>
<div className="flex w-full flex-col gap-1.5" data-testid="chat-suggested-actions">
{suggestions.map((suggestion) => (
<button
key={suggestion}
type="button"
className="min-w-[200px] shrink-0 rounded-xl border border-border/50 bg-card/30 px-4 py-3 text-left text-[12px] leading-relaxed text-muted-foreground transition-all duration-200 hover:-translate-y-0.5 hover:bg-card/60 hover:text-foreground sm:min-w-0 sm:whitespace-normal sm:p-4 sm:text-[13px]"
className="w-full truncate rounded-lg border border-border/50 bg-card/30 px-3 py-1.5 text-left text-[12px] leading-snug text-muted-foreground transition-colors hover:bg-card/60 hover:text-foreground"
onClick={() => onSuggestionSelect?.(suggestion)}
>
{suggestion}

View file

@ -49,20 +49,17 @@ export default function PlaygroundPage() {
return (
<div className="flex h-full min-h-0 w-full min-w-0 flex-col overflow-hidden">
<Tabs defaultValue="chat" className="flex min-h-0 min-w-0 flex-1 flex-col gap-0 overflow-hidden">
<TabsList
variant="line"
className="mb-0 h-auto w-full shrink-0 justify-start overflow-x-auto rounded-none border-b border-border bg-transparent p-0"
>
<TabsTrigger value="chat" className="flex-none rounded-none px-4 py-2">
<TabsList variant="line" className="w-full shrink-0 justify-start overflow-x-auto pb-1">
<TabsTrigger value="chat" className="flex-none">
Chat
</TabsTrigger>
<TabsTrigger value="compare" className="flex-none rounded-none px-4 py-2">
<TabsTrigger value="compare" className="flex-none">
Compare
</TabsTrigger>
<TabsTrigger value="compliance" className="flex-none rounded-none px-4 py-2">
<TabsTrigger value="compliance" className="flex-none">
Compliance
</TabsTrigger>
<TabsTrigger value="agent-builder" className="flex-none rounded-none px-4 py-2">
<TabsTrigger value="agent-builder" className="flex-none">
Agent Builder (Experimental)
</TabsTrigger>
</TabsList>

View file

@ -675,18 +675,15 @@ const EntityUsage: React.FC<EntityUsageProps> = ({
</AlertDescription>
</Alert>
)}
{entityType === "team" && (
<div className="mb-4">
<p className="mb-2 text-sm text-foreground">Filter by team</p>
<TeamMultiSelect value={selectedTags} onChange={setSelectedTags} />
</div>
)}
<UsageExportHeader
dateValue={dateValue}
entityType={entityType}
spendData={spendData}
showFilters={entityType !== "team" && entityList !== null && entityList.length > 0}
filterLabel={getFilterLabel(entityType)}
filterSlot={
entityType === "team" ? <TeamMultiSelect value={selectedTags} onChange={setSelectedTags} /> : undefined
}
filterLabel={entityType === "team" ? "Filter by team" : getFilterLabel(entityType)}
filterPlaceholder={getFilterPlaceholder(entityType)}
selectedFilters={selectedTags}
onFiltersChange={setSelectedTags}

View file

@ -154,7 +154,7 @@ const VectorStoreManagement: React.FC<VectorStoreProps> = ({ accessToken, userID
</p>
<Tabs defaultValue="create" onValueChange={onTabChange}>
<TabsList variant="line" className="mb-6 h-auto w-full justify-start rounded-none border-b p-0">
<TabsList variant="line" className="mb-6 h-auto w-full justify-start rounded-none p-0">
<TabsTrigger value="create" className="flex-none rounded-none px-4 py-2">
Create Vector Store
</TabsTrigger>

View file

@ -30,6 +30,54 @@
}
}
/* Verbatim end-edge subset of the shadcn scroll-fade utility (shadcn@4.17.0 dist/tailwind.css),
vendored so the CLI package is not a build dependency. Re-copy from upstream to update. */
@property --scroll-fade-e {
syntax: "<length-percentage>";
inherits: false;
initial-value: 0px;
}
@property --scroll-fade-mask {
syntax: "*";
inherits: false;
}
@theme inline {
@keyframes scroll-fade-reveal-e {
from {
--scroll-fade-e: var(--_scroll-fade-size-e, var(--scroll-fade-size, min(12%, calc(var(--spacing) * 10))));
}
to {
--scroll-fade-e: 0px;
}
}
}
@utility scroll-fade-e {
--_scroll-fade-size-e: var(--scroll-fade-e-size, var(--scroll-fade-size, min(12%, calc(var(--spacing) * 10))));
--scroll-fade-mask: linear-gradient(to right, #000 0, #000 calc(100% - var(--scroll-fade-e, 0px)), transparent 100%);
&:where([dir="rtl"], [dir="rtl"] *) {
--scroll-fade-mask: linear-gradient(to left, #000 0, #000 calc(100% - var(--scroll-fade-e, 0px)), transparent 100%);
}
-webkit-mask-image: var(--scroll-fade-mask);
mask-image: var(--scroll-fade-mask);
-webkit-mask-composite: source-in;
mask-composite: intersect;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
@supports (animation-timeline: scroll()) {
animation: scroll-fade-reveal-e 1ms ease-in-out;
animation-timeline: scroll(self inline);
animation-range: calc(100% - var(--scroll-fade-reveal, calc(var(--spacing) * 24))) 100%;
animation-fill-mode: both;
}
@supports not (animation-timeline: scroll()) {
--scroll-fade-e: var(--_scroll-fade-size-e);
}
}
:root {
--radius: 0.5rem;
--background: oklch(1 0 0);
@ -211,6 +259,30 @@
outline-color: color-mix(in oklab, var(--color-ring) 50%, transparent);
}
/* @tailwindcss/forms paints a 1px blue-600 ring via box-shadow on :focus, which `outline-none`
cannot cancel. Components that declare their own focus ring override this from the utilities
layer; the ones that do not inherit a blue rectangle. `:not([disabled])` is here only to
outweigh the plugin's own (0,1,1) selector, which Tailwind emits after this block. Delete
along with the plugin once no route renders a Tremor form control. */
:is(input, textarea, select):focus:not([disabled]) {
--tw-ring-color: initial;
--tw-ring-shadow: 0 0 #0000;
border-color: var(--color-border);
}
/* Same plugin, resting state: it also gives every bare input a border, a white fill, 0.5rem/0.75rem
padding and 1rem text. shadcn primitives style themselves and only break where a base class omits
those, which today is `combobox-chip-input` alone (audited against every input-rendering file in
components/ui). These values restore Tailwind's own preflight, and `[data-slot]` outweighs the
plugin's zero-specificity `input:where(:not([type]))`. Delete along with the plugin. */
[data-slot="combobox-chip-input"] {
border-width: 0;
background-color: transparent;
padding: 0;
font: inherit;
letter-spacing: inherit;
}
button:not(:disabled),
[role="button"]:not(:disabled) {
cursor: pointer;

View file

@ -32,6 +32,7 @@ interface UsageExportHeaderProps {
onFiltersChange?: (filters: string[]) => void;
filterOptions?: Array<{ label: string; value: string }>;
filterMode?: "multiple" | "single";
filterSlot?: React.ReactNode;
customTitle?: string;
compactLayout?: boolean;
teams?: Team[];
@ -48,6 +49,7 @@ const UsageExportHeader: React.FC<UsageExportHeaderProps> = ({
onFiltersChange,
filterOptions = [],
filterMode = "multiple",
filterSlot,
customTitle,
compactLayout = false,
teams = [],
@ -55,7 +57,7 @@ const UsageExportHeader: React.FC<UsageExportHeaderProps> = ({
const anchor = useComboboxAnchor();
const [isExportModalOpen, setIsExportModalOpen] = useState(false);
const hasFilters = showFilters && filterOptions.length > 0;
const hasFilters = filterSlot != null || (showFilters && filterOptions.length > 0);
const optionValues = filterOptions.map((option) => option.value);
const labelOf = (value: string) => filterOptions.find((option) => option.value === value)?.label ?? value;
@ -72,6 +74,46 @@ const UsageExportHeader: React.FC<UsageExportHeaderProps> = ({
</ComboboxContent>
);
const builtInFilter =
filterMode === "single" ? (
<Combobox
items={optionValues}
value={selectedFilters[0] ?? null}
onValueChange={(next: string | null) => onFiltersChange?.(next ? [next] : [])}
itemToStringLabel={labelOf}
>
<ComboboxInput
className="w-full"
placeholder={filterPlaceholder}
aria-label={filterPlaceholder}
showClear={selectedFilters.length > 0}
/>
{filterList}
</Combobox>
) : (
<Combobox
multiple
items={optionValues}
value={selectedFilters}
onValueChange={(next: string[]) => onFiltersChange?.(next)}
>
<ComboboxChips render={<div ref={anchor} />} className="w-full">
<ComboboxValue>
{(selected: string[]) =>
selected.map((value) => (
<ComboboxChip key={value} aria-label={labelOf(value)}>
{labelOf(value)}
</ComboboxChip>
))
}
</ComboboxValue>
<ComboboxChipsInput placeholder={filterPlaceholder} aria-label={filterPlaceholder} />
{selectedFilters.length > 0 && <ComboboxClear aria-label={`Clear ${filterLabel ?? "filters"}`} />}
</ComboboxChips>
{filterList}
</Combobox>
);
return (
<>
<div className="mb-4">
@ -84,48 +126,7 @@ const UsageExportHeader: React.FC<UsageExportHeaderProps> = ({
{hasFilters && (
<div>
{filterLabel && <label className="text-sm font-medium text-gray-700 block mb-2">{filterLabel}</label>}
{filterMode === "single" ? (
<Combobox
items={optionValues}
value={selectedFilters[0] ?? null}
onValueChange={(next: string | null) => onFiltersChange?.(next ? [next] : [])}
itemToStringLabel={labelOf}
>
<ComboboxInput
className="w-full"
placeholder={filterPlaceholder}
aria-label={filterPlaceholder}
showClear={selectedFilters.length > 0}
/>
{filterList}
</Combobox>
) : (
<Combobox
multiple
items={optionValues}
value={selectedFilters}
onValueChange={(next: string[]) => onFiltersChange?.(next)}
>
<ComboboxChips render={<div ref={anchor} />} className="w-full">
<ComboboxValue>
{(selected: string[]) =>
selected.map((value) => (
<ComboboxChip key={value} aria-label={labelOf(value)}>
{labelOf(value)}
</ComboboxChip>
))
}
</ComboboxValue>
<ComboboxChipsInput
className="border-0 bg-transparent"
placeholder={filterPlaceholder}
aria-label={filterPlaceholder}
/>
{selectedFilters.length > 0 && <ComboboxClear aria-label={`Clear ${filterLabel ?? "filters"}`} />}
</ComboboxChips>
{filterList}
</Combobox>
)}
{filterSlot ?? builtInFilter}
</div>
)}

View file

@ -256,11 +256,7 @@ export const ModelSelect = (props: ModelSelectProps) => {
</>
)}
</ComboboxValue>
<ComboboxChipsInput
placeholder="Select Models"
aria-label="Select Models"
className="h-5 min-w-24 flex-1 border-0 bg-transparent py-0 text-sm"
/>
<ComboboxChipsInput placeholder="Select Models" aria-label="Select Models" className="min-w-24" />
</ComboboxChips>
<ComboboxContent anchor={anchor}>
<ComboboxEmpty>No models found</ComboboxEmpty>

View file

@ -1,10 +1,25 @@
import { useDisableShowPrompts } from "@/app/(dashboard)/hooks/useDisableShowPrompts";
import { buttonVariants } from "@/components/ui/button";
import { ButtonGroup } from "@/components/ui/button-group";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { cn } from "@/lib/cva.config";
import { Github, Slack } from "lucide-react";
import React from "react";
const iconBtnClass =
"inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-md border-0 bg-transparent text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700 cursor-pointer";
const COMMUNITY_LINKS = [
{
href: "https://www.litellm.ai/support",
label: "Join Slack",
tooltip: "LiteLLM Slack community",
Icon: Slack,
},
{
href: "https://github.com/BerriAI/litellm",
label: "LiteLLM on GitHub",
tooltip: "LiteLLM on GitHub",
Icon: Github,
},
] as const;
export const CommunityEngagementButtons: React.FC = () => {
const disableShowPrompts = useDisableShowPrompts();
@ -14,44 +29,27 @@ export const CommunityEngagementButtons: React.FC = () => {
}
return (
<div
className="flex items-center gap-0.5 rounded-md border border-gray-200/80 bg-gray-50 px-0.5 py-0"
aria-label="Community links"
>
<TooltipProvider>
<Tooltip>
<TooltipTrigger
render={
<a
href="https://www.litellm.ai/support"
target="_blank"
rel="noopener noreferrer"
className={iconBtnClass}
aria-label="Join Slack"
/>
}
>
<Slack className="size-[18px]" />
</TooltipTrigger>
<TooltipContent>LiteLLM Slack community</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger
render={
<a
href="https://github.com/BerriAI/litellm"
target="_blank"
rel="noopener noreferrer"
className={iconBtnClass}
aria-label="LiteLLM on GitHub"
/>
}
>
<Github className="size-[18px]" />
</TooltipTrigger>
<TooltipContent>LiteLLM on GitHub</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<TooltipProvider>
<ButtonGroup aria-label="Community links">
{COMMUNITY_LINKS.map(({ href, label, tooltip, Icon }) => (
<Tooltip key={href}>
<TooltipTrigger
render={
<a
href={href}
target="_blank"
rel="noopener noreferrer"
aria-label={label}
className={cn(buttonVariants({ variant: "outline", size: "icon" }), "text-muted-foreground")}
/>
}
>
<Icon />
</TooltipTrigger>
<TooltipContent>{tooltip}</TooltipContent>
</Tooltip>
))}
</ButtonGroup>
</TooltipProvider>
);
};

View file

@ -384,11 +384,7 @@ const TeamSSOSettings: React.FC<TeamSSOSettingsProps> = ({ accessToken }) => {
))
}
</ComboboxValue>
<ComboboxChipsInput
className="border-0 bg-transparent"
placeholder="Select permissions"
aria-label="Team Member Permissions"
/>
<ComboboxChipsInput placeholder="Select permissions" aria-label="Team Member Permissions" />
</ComboboxChips>
<ComboboxContent anchor={anchor}>
<ComboboxList>

View file

@ -87,12 +87,7 @@ const TeamMultiSelect: React.FC<TeamMultiSelectProps> = ({
))
}
</ComboboxValue>
<ComboboxChipsInput
className="border-0 bg-transparent"
placeholder={placeholder}
aria-label={placeholder}
disabled={disabled}
/>
<ComboboxChipsInput placeholder={placeholder} aria-label={placeholder} disabled={disabled} />
{value.length > 0 && <ComboboxClear aria-label="Clear all teams" disabled={disabled} />}
</ComboboxChips>
<ComboboxContent anchor={anchor}>

View file

@ -251,9 +251,17 @@ vi.mock("../molecules/notifications_manager", () => ({
vi.mock("../agent_management/AgentSelector", () => ({ default: () => null }));
vi.mock("../common_components/budget_duration_dropdown", () => ({
NEVER_RESETS_BUDGET_DURATION: "none",
default: ({ showNeverResets, onChange }: { showNeverResets?: boolean; onChange?: (value: string) => void }) => (
default: ({
showNeverResets,
placeholder,
onChange,
}: {
showNeverResets?: boolean;
placeholder?: string;
onChange?: (value: string) => void;
}) => (
<select data-testid="budget-duration-dropdown" onChange={(event) => onChange?.(event.target.value)}>
<option value="">n/a</option>
<option value="">{placeholder ?? "n/a"}</option>
{showNeverResets ? <option value="none">Never resets</option> : null}
<option value="30d">monthly</option>
</select>
@ -871,6 +879,19 @@ describe("CreateKey", () => {
expect(formValues.budget_duration).toBeNull();
});
it("should label the omit option distinctly from 'Never resets'", async () => {
await openModal();
const optionLabels = Array.from(
screen.getByTestId("budget-duration-dropdown").querySelectorAll("option"),
(option) => option.textContent,
);
expect(optionLabels).toContain("Never resets");
expect(new Set(optionLabels).size).toBe(optionLabels.length);
expect(screen.getByRole("option", { name: "Never resets" })).toHaveValue("none");
});
it("should omit budget_duration entirely when the reset dropdown is untouched", async () => {
await openModal();

View file

@ -1069,7 +1069,7 @@ const CreateKey: React.FC<CreateKeyProps> = ({ team, teams, data, addKey, autoOp
>
<BudgetDurationDropdown
showNeverResets
placeholder="Never resets"
placeholder="Not set"
onChange={(value) => form.setFieldValue("budget_duration", value)}
/>
</Form.Item>

View file

@ -637,7 +637,7 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ accessToken, isEmbedded
<ComboboxChipsInput
placeholder="Select providers"
aria-label="Select providers"
className="h-5 min-w-24 flex-1 border-0 bg-transparent py-0 text-sm"
className="min-w-24"
/>
</ComboboxChips>
<ComboboxContent anchor={anchor}>

View file

@ -79,12 +79,7 @@ const SearchToolSelector: React.FC<SearchToolSelectorProps> = ({
))
}
</ComboboxValue>
<ComboboxChipsInput
className="border-0 bg-transparent"
placeholder={placeholder}
aria-label={placeholder}
disabled={disabled}
/>
<ComboboxChipsInput placeholder={placeholder} aria-label={placeholder} disabled={disabled} />
{value && value.length > 0 && <ComboboxClear aria-label="Clear all search tools" disabled={disabled} />}
</ComboboxChips>
<ComboboxContent anchor={anchor}>

View file

@ -303,7 +303,10 @@ function DataTableBodyRow<TData>({
function MessageRow({ colSpan, children }: { colSpan: number; children: React.ReactNode }) {
return (
<TableRow className="hover:bg-transparent">
<TableCell colSpan={colSpan} className="h-24 text-center align-middle text-sm text-muted-foreground">
<TableCell
colSpan={colSpan}
className="h-24 text-center align-middle text-sm whitespace-normal text-muted-foreground"
>
{children}
</TableCell>
</TableRow>

View file

@ -102,7 +102,7 @@ export function MultiSelect({
))}
<ComboboxChipsInput
placeholder={loading ? "Loading..." : placeholder}
className="h-5 min-w-24 flex-1 border-0 bg-transparent py-0 text-sm"
className="min-w-24"
aria-label={placeholder}
/>
</>

View file

@ -0,0 +1,76 @@
import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render";
import { type VariantProps } from "cva";
import { cn, cva } from "@/lib/cva.config";
import { Separator } from "@/components/ui/separator";
const buttonGroupVariants = cva({
base: "flex w-fit items-stretch *:focus-visible:relative *:focus-visible:z-10 has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
variants: {
orientation: {
horizontal:
"*:data-slot:rounded-r-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-md! [&>[data-slot]~[data-slot]]:rounded-l-none [&>[data-slot]~[data-slot]]:border-l-0",
vertical:
"flex-col *:data-slot:rounded-b-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-md! [&>[data-slot]~[data-slot]]:rounded-t-none [&>[data-slot]~[data-slot]]:border-t-0",
},
},
defaultVariants: {
orientation: "horizontal",
},
});
function ButtonGroup({
className,
orientation,
...props
}: React.ComponentProps<"div"> & VariantProps<typeof buttonGroupVariants>) {
return (
<div
role="group"
data-slot="button-group"
data-orientation={orientation}
className={cn(buttonGroupVariants({ orientation }), className)}
{...props}
/>
);
}
function ButtonGroupText({ className, render, ...props }: useRender.ComponentProps<"div">) {
return useRender({
defaultTagName: "div",
props: mergeProps<"div">(
{
className: cn(
"flex items-center gap-2 rounded-md border bg-muted px-2.5 text-sm font-medium shadow-xs [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
className,
),
},
props,
),
render,
state: {
slot: "button-group-text",
},
});
}
function ButtonGroupSeparator({
className,
orientation = "vertical",
...props
}: React.ComponentProps<typeof Separator>) {
return (
<Separator
data-slot="button-group-separator"
orientation={orientation}
className={cn(
"relative self-stretch bg-input data-horizontal:mx-px data-horizontal:w-auto data-vertical:my-px data-vertical:h-auto",
className,
)}
{...props}
/>
);
}
export { ButtonGroup, ButtonGroupSeparator, ButtonGroupText, buttonGroupVariants };

View file

@ -397,11 +397,7 @@ const UserAgentActivity: React.FC<UserAgentActivityProps> = ({ accessToken, user
))
}
</ComboboxValue>
<ComboboxChipsInput
className="border-0 bg-transparent"
placeholder="All User Agents"
aria-label="All User Agents"
/>
<ComboboxChipsInput placeholder="All User Agents" aria-label="All User Agents" />
{selectedTags.length > 0 && <ComboboxClear aria-label="Clear user agent filter" />}
</ComboboxChips>
<ComboboxContent anchor={anchor}>

View file

@ -1,6 +1,6 @@
import { JsonView, defaultStyles } from "react-json-view-lite";
import "react-json-view-lite/dist/index.css";
import { JSON_MAX_HEIGHT, COLOR_BG_LIGHT, SPACING_LARGE } from "./constants";
import { JSON_MAX_HEIGHT, SPACING_LARGE } from "./constants";
interface JsonViewerProps {
data: any;
@ -16,15 +16,15 @@ export function JsonViewer({ data }: JsonViewerProps) {
return (
<div
className="bg-background"
style={{
maxHeight: JSON_MAX_HEIGHT,
overflow: "auto",
background: COLOR_BG_LIGHT,
padding: SPACING_LARGE,
borderRadius: 4,
}}
>
<div className="**:[[role='tree']]:bg-white **:[[role='tree']]:text-slate-900">
<div className="**:[[role='tree']]:bg-background! **:[[role='tree']]:text-foreground">
<JsonView data={data} style={defaultStyles} clickToExpandNode={true} />
</div>
</div>