diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f9d3f16f4..ce88b38731 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,6 +78,17 @@ Newest first. `Unreleased` is what is on `main` and not yet tagged. `COMPUTER_MEMORY_HIGH_PERCENT=0` and `COMPUTER_TAB_IDLE_MS=0` (Helm: `computers.memoryHighPercent` and `computers.tabIdleMs`) keep the old behaviour. +### The default look: an inset page, slate greys and quieter headings + +Each page now sits in a panel inset from the window, on a darker frame shared with the sidebar, +with white cards on a light grey page; admin and settings scroll inside it like the rest of the +app. The greys are Tailwind's slate rather than neutral, and page and section headings are lighter. +Rows show an app's logo or initial instead of the same plug; Home shows Bots as compact cards; a +Bot's page leads with its own one-liner; Connections tiles no longer cut their text off; and the +Composio directory shows its rows loading. Empty states carry a faint dot pattern whose file, +colour and strength are theme tokens (`--empty-pattern*` in `theme.css`), so a deployment can +replace it or turn it off. Skins are unchanged. + ### A computer closes browsers nobody is using when its memory runs high A computer runs one browser for each Bot that is browsing, and the only limits were a count and half diff --git a/app/src/assets/patterns/dot-matrix.svg b/app/src/assets/patterns/dot-matrix.svg new file mode 100644 index 0000000000..be9834f09a --- /dev/null +++ b/app/src/assets/patterns/dot-matrix.svg @@ -0,0 +1,136 @@ + + diff --git a/app/src/components/admin/admin-sidebar.tsx b/app/src/components/admin/admin-sidebar.tsx index 2b4a7d5d6c..62286f8498 100644 --- a/app/src/components/admin/admin-sidebar.tsx +++ b/app/src/components/admin/admin-sidebar.tsx @@ -1,15 +1,16 @@ import { IconArrowLeft, IconBuildingBank, + IconBulb, IconChecks, IconCode, IconDeviceDesktop, - IconFileText, IconKey, IconLayoutGrid, IconListDetails, IconPuzzle, IconShieldCheck, + IconSlash, IconUsers, } from "@tabler/icons-react"; import { Link, type LinkOptions } from "@tanstack/react-router"; @@ -79,12 +80,12 @@ const GROUPS: { }, { title: "Skills", - icon: IconFileText, + icon: IconSlash, linkOptions: { to: "/admin/skills" }, }, { title: "Automatic learning", - icon: IconFileText, + icon: IconBulb, linkOptions: { to: "/admin/learning" }, }, { @@ -130,7 +131,7 @@ export function AdminSidebar({ ...props }: React.ComponentProps) { return ( - + {/* * Pinned to the same height as the app sidebar's header. Left to itself this one is 60px * against the app's 45px — `p-2` around a `size="lg"` button rather than a fixed height — so diff --git a/app/src/components/agents/agent-card.tsx b/app/src/components/agents/agent-card.tsx index 67cc270a06..2aa5abeb83 100644 --- a/app/src/components/agents/agent-card.tsx +++ b/app/src/components/agents/agent-card.tsx @@ -9,23 +9,23 @@ export function AgentCard({ appearance = "compact", }: { agent: AgentProfile; - appearance?: "compact" | "tile"; + appearance?: "compact" | "row"; }) { - if (appearance === "tile") { + if (appearance === "row") { return ( -
+ /* + * The avatar beside the name rather than above it: a tall card left most of its height empty + * between the two, and a row reads the name and what the Bot is for in one glance. + */ +
- {/* Bottom-aligned, where the text sat on the earlier artwork card; the avatar holds the top. */} -
- +
+ {agent.name} - + {agent.roleDescription}
diff --git a/app/src/components/agents/handoff-panel.tsx b/app/src/components/agents/handoff-panel.tsx index 6a693c4910..90ad484fdd 100644 --- a/app/src/components/agents/handoff-panel.tsx +++ b/app/src/components/agents/handoff-panel.tsx @@ -1,4 +1,4 @@ -import { IconArrowsExchange, IconLock, IconRobot } from "@tabler/icons-react"; +import { IconArrowsExchange, IconBolt, IconLock } from "@tabler/icons-react"; import { useMutation, useQuery } from "@tanstack/react-query"; import { Fragment } from "react"; import { AbstractAvatar } from "@/components/agents/abstract-avatar"; @@ -102,7 +102,7 @@ export function HandoffPanel({ agentId }: { agentId: string }) { ) : null} {grantable && total === 0 ? ( - + When there are more, you can let this Bot ask them here. ) : null} diff --git a/app/src/components/app-sidebar/app-sidebar.tsx b/app/src/components/app-sidebar/app-sidebar.tsx index 27fe657b18..e7e8e99b59 100644 --- a/app/src/components/app-sidebar/app-sidebar.tsx +++ b/app/src/components/app-sidebar/app-sidebar.tsx @@ -220,7 +220,7 @@ export function AppSidebar({ ...props }: React.ComponentProps) { const navigation = navigationFor(currentUser?.role); return ( - + diff --git a/app/src/components/app-sidebar/channel-item-content.tsx b/app/src/components/app-sidebar/channel-item-content.tsx index ece35115c2..a8128c95bc 100644 --- a/app/src/components/app-sidebar/channel-item-content.tsx +++ b/app/src/components/app-sidebar/channel-item-content.tsx @@ -26,7 +26,7 @@ export function ChannelItemContent({ pinned?: boolean; revealing?: boolean; }) { - const primaryText = `text-[0.9rem] leading-5 tracking-[-1%] ${unread ? "font-semibold" : "font-medium"}`; + const primaryText = `text-sm leading-5 tracking-[-1%] ${unread ? "font-semibold" : "font-medium"}`; const secondaryText = "text-[12px] leading-4 text-muted-foreground"; return ( diff --git a/app/src/components/bot-profile/activity.tsx b/app/src/components/bot-profile/activity.tsx index 91c0fad637..41c3d17599 100644 --- a/app/src/components/bot-profile/activity.tsx +++ b/app/src/components/bot-profile/activity.tsx @@ -197,22 +197,31 @@ function ActivitySection({ /** How many running or scheduled items the Bot's page shows before "All activity". */ const GLANCE = 3; +/** Running work first, then what is lined up, as many as the Bot's page shows. */ +function upcomingOf(activity: BotActivity | undefined) { + return activity + ? [ + ...activity.inProgress.map((item) => ({ item, live: true })), + ...activity.scheduled.map((item) => ({ item, live: false })), + ].slice(0, GLANCE) + : []; +} + /** * The Bot page's view of its activity: what is under way and what is lined up, so either is seen * without opening anything, then a row to the full lists. Running work comes first, with its dot. * + * Only when there is something running or lined up. With nothing to show, a whole section for one + * row to the full lists is a heading over nothing; that row joins "For you" instead, through + * `BotActivityRow`. + * * The full lists used to sit here and ran on for screens; finished work is the long part and the * least urgent, so it lives only on the Activity page. */ export function BotActivityGlance({ agentId }: { agentId: string }) { const activity = useQuery(botActivityQueryOptions(agentId)); - if (activity.isPending) return null; - const upcoming = activity.data - ? [ - ...activity.data.inProgress.map((item) => ({ item, live: true })), - ...activity.data.scheduled.map((item) => ({ item, live: false })), - ].slice(0, GLANCE) - : []; + const upcoming = upcomingOf(activity.data); + if (activity.isPending || upcoming.length === 0) return null; return ( @@ -222,32 +231,69 @@ export function BotActivityGlance({ agentId }: { agentId: string }) { ))} - } - size="sm" - > - {upcoming.length ? null : ( - - - - )} - - All activity - - {activity.data - ? activitySummary(activity.data) - : "Couldn't load this Bot's activity."} - - - - - - + ); } +/** + * The row to a Bot's activity, inside "For you", for when nothing is running or lined up and + * `BotActivityGlance` draws no section of its own. Starts with a separator, because it follows the + * card's other rows. + */ +export function BotActivityRow({ agentId }: { agentId: string }) { + const activity = useQuery(botActivityQueryOptions(agentId)); + if (activity.isPending || upcomingOf(activity.data).length > 0) return null; + return ( + <> + + + + ); +} + +function AllActivityItem({ + activity, + agentId, + title = "All activity", + withIcon = false, +}: { + activity: BotActivity | undefined; + agentId: string; + title?: string; + withIcon?: boolean; +}) { + return ( + } + size="sm" + > + {withIcon ? ( + + + + ) : null} + + {title} + + {activity + ? activitySummary(activity) + : "Couldn't load this Bot's activity."} + + + + + + + ); +} + /** * The Bot page's one line about its activity: what is running and what is lined up, the two that * might want a look. Finished work is only counted when there is nothing else to say. diff --git a/app/src/components/bot-profile/profile.tsx b/app/src/components/bot-profile/profile.tsx index fef082c983..f452d292e2 100644 --- a/app/src/components/bot-profile/profile.tsx +++ b/app/src/components/bot-profile/profile.tsx @@ -41,7 +41,7 @@ import { botLifecycleQueryOptions, } from "@/lib/bot-lifecycle/queries"; import { queryClient } from "@/query-client"; -import { BotActivityGlance } from "./activity"; +import { BotActivityGlance, BotActivityRow } from "./activity"; import { BotPausedBanner } from "./pause-banner"; const NOTIFY_LABEL: Record = { @@ -77,14 +77,12 @@ export function BotProfile({ return ( <> + {/* The one-liner is the page's description, under the title; here, the face and its title. */}
-
- {agent.title &&

{agent.title}

} -

- {agent.roleDescription} -

-
+ {agent.title ? ( +

{agent.title}

+ ) : null}
@@ -218,6 +216,7 @@ export function BotProfile({ + )} diff --git a/app/src/components/bots/bot-roster.tsx b/app/src/components/bots/bot-roster.tsx index c801d12f9e..209741e0d2 100644 --- a/app/src/components/bots/bot-roster.tsx +++ b/app/src/components/bots/bot-roster.tsx @@ -1,7 +1,7 @@ import { + IconBolt, IconChevronRight, IconPlus, - IconRobot, IconShare, } from "@tabler/icons-react"; import { useQuery } from "@tanstack/react-query"; @@ -96,7 +96,7 @@ export function BotRoster() { ) } - icon={IconRobot} + icon={IconBolt} title={ yoursPinned ? "All pinned" diff --git a/app/src/components/component-preview.tsx b/app/src/components/component-preview.tsx index 57d991c949..fad1047c31 100644 --- a/app/src/components/component-preview.tsx +++ b/app/src/components/component-preview.tsx @@ -36,7 +36,7 @@ function PreviewNotice({ children }: { children: ReactNode }) { return (
{/* The artwork stays pale in both themes, so this text must stay dark. */} -

{children}

+

{children}

); } diff --git a/app/src/components/layout/page-parts.tsx b/app/src/components/layout/page-parts.tsx index 7fa01f8dba..33e4ff17b7 100644 --- a/app/src/components/layout/page-parts.tsx +++ b/app/src/components/layout/page-parts.tsx @@ -40,20 +40,13 @@ export function PageColumn({ return ( <> {/* - * The scroller, and it has to live here rather than in either shell, because the two shells - * this frame is used under disagree about who scrolls. - * - * Under `_authed` — admin, settings — the document scrolls, so a page taller than the window - * has always just worked. Under `_authed/_app` it does not: that shell is `h-svh - * overflow-hidden` on purpose ("one viewport, never scrolls: panes scroll inside it") and its - * `main` is `overflow-hidden` too, so a PageShell taller than the window was silently CLIPPED - * — 246px of Skills sat below the fold with no way to reach it, by keyboard or otherwise. + * The scroller. Every shell — app, admin, settings — is exactly the window's height and holds + * the page in an inset panel that never grows past it, so a page taller than the window + * scrolls here, inside the panel, and the panel's rounded corners stay on screen. * * `min-h-0` is the load-bearing half of `min-h-0 flex-1`: a flex item's default `min-height: - * auto` refuses to shrink below its content, so `flex-1` alone would grow this past the pane - * and clip exactly as before. Under `_authed` both are inert — the parent is not a flex - * container — and `overflow-y-auto` on an auto-height element shows no scrollbar, so those - * eighteen screens are unaffected. + * auto` refuses to shrink below its content, so `flex-1` alone would grow this past the panel + * and the overflow would be clipped instead of scrolled. * * Separate from the centred column below so the scrollbar rides the pane's edge instead of * appearing inside the measure with content either side of it. @@ -98,7 +91,10 @@ export function PageHeader({ return (
-

+

{title}

{action} @@ -136,7 +132,11 @@ export function PageSection({
{title ? (
-

{title}

+ {/* + * Close to body size and lighter than the page title, so the sections read as parts of + * the page rather than as a second row of titles competing with it. + */} +

{title}

{action}
) : null} @@ -243,7 +243,10 @@ export function PageEmpty({ title?: string; }) { return ( - + // `isolate` gives the pattern its own stacking context, so it sits behind the text, not on it. + + {/* The pattern is decoration: see `.empty-pattern` in styles.css and its tokens in theme.css. */} +
{Icon ? ( diff --git a/app/src/components/layout/page-shell.tsx b/app/src/components/layout/page-shell.tsx index 073ed9e42e..73c4c01727 100644 --- a/app/src/components/layout/page-shell.tsx +++ b/app/src/components/layout/page-shell.tsx @@ -83,10 +83,10 @@ export function PageShell({ if (width === "full") { return ( /* - * One viewport, never scrolls. `min-w-0` lets a table wider than the pane scroll inside its own - * container instead of pushing the page sideways. + * Exactly the panel's height, never scrolls. `min-w-0` lets a table wider than the pane scroll + * inside its own container instead of pushing the page sideways. */ -
+
{barRow}
{header} diff --git a/app/src/components/onboarding/laptop-illustration.tsx b/app/src/components/onboarding/laptop-illustration.tsx index cd648a6e94..5ce12fac53 100644 --- a/app/src/components/onboarding/laptop-illustration.tsx +++ b/app/src/components/onboarding/laptop-illustration.tsx @@ -18,12 +18,12 @@ export function LaptopIllustration() {
-
+
-
-
+
+
); diff --git a/app/src/components/plugins/broker-directory.tsx b/app/src/components/plugins/broker-directory.tsx index 7137bb66e3..4f9b97b8a7 100644 --- a/app/src/components/plugins/broker-directory.tsx +++ b/app/src/components/plugins/broker-directory.tsx @@ -7,6 +7,7 @@ import { PageSection, PageShell, } from "@/components/layout/page-shell"; +import { AppInitial } from "@/components/plugins/plugin-logo"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { @@ -18,6 +19,7 @@ import { ItemTitle, } from "@/components/ui/item"; import { Separator } from "@/components/ui/separator"; +import { Skeleton } from "@/components/ui/skeleton"; import { consentBreadth, coverageWarning, @@ -56,6 +58,9 @@ import { queryClient } from "@/query-client"; * here branches on which one it is — a `provider === "composio"` test inside a screen is the same * mistake wearing a conditional. */ +/** Placeholder rows while the directory loads: enough to fill the view, not to suggest a count. */ +const LOADING_ROWS = ["a", "b", "c", "d", "e", "f"]; + export function BrokerDirectory({ provider }: { provider: PluginsProvider }) { const { name, directoryLabel } = providerVocabulary(provider); const [search, setSearch] = React.useState(""); @@ -146,8 +151,31 @@ export function BrokerDirectory({ provider }: { provider: PluginsProvider }) { value={search} /> - {/* Pending, error, empty, rows — pending first, so no sentence asserts anything mid-fetch. */} - {apps.isPending ? null : apps.error ? ( + {/* + * Pending, error, empty, rows. Pending draws placeholder rows: the full directory takes a + * couple of seconds to arrive, and a bare search box over blank space read as a page with + * nothing on it. They assert nothing, so no sentence claims anything mid-fetch. + */} + {apps.isPending ? ( +
+ + {LOADING_ROWS.map((key, index) => ( + + {index > 0 ? : null} + + + + + + + + + + + ))} + +
+ ) : apps.error ? (

{/* * The server's own sentence, as the enable failure above already does. A keyless @@ -167,64 +195,72 @@ export function BrokerDirectory({ provider }: { provider: PluginsProvider }) { : `${name} returned no apps.`} ) : ( - - {listed.map((entry, index) => { - // One mutation serves every row, so which rows are mid-request is tracked beside it - // rather than read off it. See `adding` above. - const isAdding = adding.has(entry.slug); + <> + {/* How many there are, before the list: with no search, this is the whole directory. */} +

+ {query + ? `${listed.length.toLocaleString()} ${listed.length === 1 ? "match" : "matches"}` + : `${listed.length.toLocaleString()} apps. Search by name to narrow them.`} +

+ + {listed.map((entry, index) => { + // One mutation serves every row, so which rows are mid-request is tracked beside it + // rather than read off it. See `adding` above. + const isAdding = adding.has(entry.slug); - return ( - - {/* The test id names the screen, not the vendor: this module draws whichever + return ( + + {/* The test id names the screen, not the vendor: this module draws whichever broker is active, and the id is not user-visible. */} - - {/* The vendor's own mark where the broker supplies one, and a plug where it does - not, so a list of third parties still has a fixed left edge. */} - - {entry.logo ? ( - - ) : ( - - )} - - - {entry.name} - {entry.description} - {entry.enabled ? null : } - {entry.enabled ? null : } - - - {/* The size of the decision, stated before it is made: an app is not one + + {/* The vendor's own mark where the broker supplies one, and its initial where it + does not, so a list of third parties still has a fixed left edge. */} + + {entry.logo ? ( + + ) : ( + + )} + + + {entry.name} + {entry.description} + {entry.enabled ? null : } + {entry.enabled ? null : } + + + {/* The size of the decision, stated before it is made: an app is not one tool, and "167 actions" is what says so. */} - - {entry.actionCount} actions - - {/* - * An app this deployment already has says so rather than offering Add again. - * A second Add would ask the server to record a connector it already holds, - * and there is nothing on the row to suggest that would be harmless. - */} - {entry.enabled ? ( - Added + {entry.actionCount} actions - ) : ( - - )} - - - {index !== listed.length - 1 && } - - ); - })} - + {/* + * An app this deployment already has says so rather than offering Add again. + * A second Add would ask the server to record a connector it already holds, + * and there is nothing on the row to suggest that would be harmless. + */} + {entry.enabled ? ( + + Added + + ) : ( + + )} + + + {index !== listed.length - 1 && } + + ); + })} + + )} diff --git a/app/src/components/plugins/plugin-logo.tsx b/app/src/components/plugins/plugin-logo.tsx index c457a11b0c..d2c279a25f 100644 --- a/app/src/components/plugins/plugin-logo.tsx +++ b/app/src/components/plugins/plugin-logo.tsx @@ -1,9 +1,24 @@ import { IconPlug } from "@tabler/icons-react"; import { useState } from "react"; -export function PluginLogo({ logo }: { logo?: string | null }) { +/** + * An app's logo, or its initial where it has none. + * + * The initial rather than a generic plug because a list of apps that all show the same plug is a + * column of identical tiles, which tells nobody anything; "T" and "G" at least tell Todoist from + * GitHub at a glance. The plug is left only for a caller with no name to take an initial from. + */ +export function PluginLogo({ + logo, + name, +}: { + logo?: string | null; + name?: string; +}) { const [failedUrl, setFailedUrl] = useState(null); - if (!logo || failedUrl === logo) return ; + if (!logo || failedUrl === logo) { + return name ? : ; + } return ( ); } + +/** The first letter of an app's name, drawn where its logo would go. Hidden from screen readers: the row's title already says the name. */ +export function AppInitial({ name }: { name: string }) { + const initial = name.trim().charAt(0).toUpperCase(); + return ( + + {initial || "?"} + + ); +} diff --git a/app/src/components/settings/settings-sidebar.tsx b/app/src/components/settings/settings-sidebar.tsx index 53e5d9bec3..d0694cca64 100644 --- a/app/src/components/settings/settings-sidebar.tsx +++ b/app/src/components/settings/settings-sidebar.tsx @@ -84,7 +84,7 @@ export function SettingsSidebar({ ...props }: React.ComponentProps) { return ( - + {/* Matched to the app sidebar's header, as Admin's is. See admin-sidebar.tsx. */} diff --git a/app/src/components/ui/input-group.tsx b/app/src/components/ui/input-group.tsx index c5af85eeaa..429c2230a1 100644 --- a/app/src/components/ui/input-group.tsx +++ b/app/src/components/ui/input-group.tsx @@ -11,7 +11,7 @@ function InputGroup({ className, ...props }: React.ComponentProps<"div">) { data-slot="input-group" role="group" className={cn( - "group/input-group relative flex h-9 w-full min-w-0 items-center rounded-lg border border-input transition-colors outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto dark:bg-input/30 dark:has-disabled:bg-input/80 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5", + "group/input-group relative flex h-9 w-full min-w-0 items-center rounded-lg border border-input bg-card transition-colors outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto dark:bg-input/30 dark:has-disabled:bg-input/80 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5", className, )} {...props} diff --git a/app/src/components/ui/input.tsx b/app/src/components/ui/input.tsx index 11dff01045..2a5363d8f0 100644 --- a/app/src/components/ui/input.tsx +++ b/app/src/components/ui/input.tsx @@ -9,7 +9,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) { type={type} data-slot="input" className={cn( - "h-9 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40", + "h-9 w-full min-w-0 rounded-lg border border-input bg-card px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40", className, )} {...props} diff --git a/app/src/components/ui/item.tsx b/app/src/components/ui/item.tsx index 0f68d1c1bf..51b336baca 100644 --- a/app/src/components/ui/item.tsx +++ b/app/src/components/ui/item.tsx @@ -90,7 +90,7 @@ const itemMediaVariants = cva( variant: { default: "bg-transparent", // A 32px tile behind a 16px glyph, so a column of rows reads as a column of marks. - icon: "size-8 rounded-md bg-muted text-muted-foreground group-data-[size=xs]/item:size-6 [&_svg:not([class*='size-'])]:size-4", + icon: "size-8 rounded-md bg-background text-muted-foreground ring-1 ring-border group-data-[size=xs]/item:size-6 [&_svg:not([class*='size-'])]:size-4", image: "size-10 overflow-hidden rounded-sm group-data-[size=sm]/item:size-8 group-data-[size=xs]/item:size-6 [&_img]:size-full [&_img]:object-cover", }, diff --git a/app/src/components/ui/select.tsx b/app/src/components/ui/select.tsx index f854d1b542..e101c0fb6f 100644 --- a/app/src/components/ui/select.tsx +++ b/app/src/components/ui/select.tsx @@ -43,7 +43,7 @@ function SelectTrigger({ data-slot="select-trigger" data-size={size} className={cn( - "flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-9 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 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + "flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-card py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-9 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 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className, )} {...props} diff --git a/app/src/components/ui/sidebar.tsx b/app/src/components/ui/sidebar.tsx index b5148fffe0..89672ca877 100644 --- a/app/src/components/ui/sidebar.tsx +++ b/app/src/components/ui/sidebar.tsx @@ -325,7 +325,7 @@ function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
) {