mirror of
https://github.com/onyx-dot-app/onyx.git
synced 2026-10-19 01:50:51 -05:00
feat(connectors): the create-connector header icons render to spec (#15055)
This commit is contained in:
1 parent
7e54ce4fe3
commit
4c6217c70d
15 files changed
+103
-56
No files matched your search
@@ -27,7 +27,6 @@ interface ContentXlPresetConfig {
|
||||
/** More-icon-1 width/height (CSS value). */
|
||||
moreIcon1Size: string;
|
||||
/** More-icon-2 width/height (CSS value). */
|
||||
moreIcon2Size: string;
|
||||
/** Button `size` prop for the edit button. */
|
||||
editButtonSize: ContainerSizeVariants;
|
||||
/** Tailwind padding class for the edit button container. */
|
||||
@@ -67,7 +66,7 @@ interface ContentXlProps {
|
||||
/** Optional secondary icon rendered in the icon row. */
|
||||
moreIcon1?: IconFunctionComponent;
|
||||
|
||||
/** Optional tertiary icon rendered in the icon row. */
|
||||
/** Optional tertiary icon rendered in the icon row, boxed and sized like `icon`. */
|
||||
moreIcon2?: IconFunctionComponent;
|
||||
|
||||
/** Ref forwarded to the root `<div>`. */
|
||||
@@ -84,7 +83,6 @@ const CONTENT_XL_PRESETS: Record<ContentXlSizePreset, ContentXlPresetConfig> = {
|
||||
lineHeight: "2.25rem",
|
||||
iconSize: "2rem",
|
||||
moreIcon1Size: "1rem",
|
||||
moreIcon2Size: "2rem",
|
||||
editButtonSize: "md",
|
||||
editButtonPadding: "p-1",
|
||||
iconRowMarginBottom: "0rem",
|
||||
@@ -94,7 +92,6 @@ const CONTENT_XL_PRESETS: Record<ContentXlSizePreset, ContentXlPresetConfig> = {
|
||||
lineHeight: "1.75rem",
|
||||
iconSize: "1.5rem",
|
||||
moreIcon1Size: "0.75rem",
|
||||
moreIcon2Size: "1.5rem",
|
||||
editButtonSize: "sm",
|
||||
editButtonPadding: "p-0.5",
|
||||
iconRowMarginBottom: "0.25rem",
|
||||
@@ -168,14 +165,16 @@ function ContentXl({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* The tertiary icon is a peer of the primary one: same box, same
|
||||
line-height floor, same size. */}
|
||||
{MoreIcon2 && (
|
||||
<div className="opal-content-xl-more-icon-container shrink-0">
|
||||
<div
|
||||
className="opal-content-xl-icon-container shrink-0"
|
||||
style={{ minHeight: config.lineHeight }}
|
||||
>
|
||||
<MoreIcon2
|
||||
className="opal-content-xl-icon"
|
||||
style={{
|
||||
width: config.moreIcon2Size,
|
||||
height: config.moreIcon2Size,
|
||||
}}
|
||||
style={{ width: config.iconSize, height: config.iconSize }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -62,8 +62,8 @@ Invalid combinations (e.g. `sizePreset="headline" + variant="body"`) are exclude
|
||||
| `description` | `string` | — | Optional description below the title |
|
||||
| `editable` | `boolean` | `false` | Enable inline editing of the title |
|
||||
| `onTitleChange` | `(newTitle: string) => void` | — | Called when user commits an edit |
|
||||
| `moreIcon1` | `IconFunctionComponent` | — | Secondary icon in icon row (ContentXl only) |
|
||||
| `moreIcon2` | `IconFunctionComponent` | — | Tertiary icon in icon row (ContentXl only) |
|
||||
| `moreIcon1` | `IconFunctionComponent` | — | Secondary icon in icon row (ContentXl only); colored with the secondary foreground (`text-03`) |
|
||||
| `moreIcon2` | `IconFunctionComponent` | — | Tertiary icon in icon row (ContentXl only); boxed and sized exactly like `icon` |
|
||||
| `color` | `ColorTypes` | `"default"` | Icon and title color pair. `"muted-success"` / `"muted-warning"` color only the icon and leave the text at `text-03` |
|
||||
| `strikethrough` | `boolean` | `false` | Strike the title through, for a title whose option is switched off |
|
||||
|
||||
|
||||
@@ -154,6 +154,12 @@
|
||||
color: var(--content-foreground-icon);
|
||||
}
|
||||
|
||||
/* The secondary icon between two marks reads as a connector, so it takes
|
||||
the secondary foreground (text-03 in every mode), not the icon color. */
|
||||
.opal-content-xl-more-icon-container > .opal-content-xl-icon {
|
||||
color: var(--content-foreground-secondary);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
Title row — title (or input) + edit button
|
||||
--------------------------------------------------------------------------- */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { Logo } from "@/lib/app/components";
|
||||
import { FoldableLogo } from "@/lib/app/components";
|
||||
import AgentAvatar from "@/refresh-components/avatars/AgentAvatar";
|
||||
import Text from "@/refresh-components/texts/Text";
|
||||
import { MinimalAgent } from "@/lib/agents/types";
|
||||
@@ -65,7 +65,7 @@ export default function WelcomeMessage({
|
||||
gap={2}
|
||||
width="fit"
|
||||
>
|
||||
<Logo folded size={32} />
|
||||
<FoldableLogo folded size={32} />
|
||||
<Text as="p" dir="auto" headingH2>
|
||||
{greeting}
|
||||
</Text>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Text } from "@opal/components";
|
||||
import { Spacer } from "@opal/components";
|
||||
import { RequestNewVerificationEmail } from "../waiting-on-verification/RequestNewVerificationEmail";
|
||||
import { User } from "@/lib/types";
|
||||
import { Logo } from "@/lib/app/components";
|
||||
import { FoldableLogo } from "@/lib/app/components";
|
||||
import { NEXT_PUBLIC_CLOUD_ENABLED } from "@/lib/constants";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
@@ -62,7 +62,11 @@ export default function Verify({ user }: VerifyProps) {
|
||||
return (
|
||||
<main>
|
||||
<div className="min-h-screen flex flex-col items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
|
||||
<Logo folded size={64} className="mx-auto w-fit animate-pulse" />
|
||||
<FoldableLogo
|
||||
folded
|
||||
size={64}
|
||||
className="mx-auto w-fit animate-pulse"
|
||||
/>
|
||||
{!error ? (
|
||||
<>
|
||||
<Spacer rem={0.5} />
|
||||
|
||||
@@ -4,7 +4,7 @@ import { AuthTypeMetadata } from "@/lib/auth/types";
|
||||
import { redirect } from "next/navigation";
|
||||
import { User } from "@/lib/types";
|
||||
import { RequestNewVerificationEmail } from "./RequestNewVerificationEmail";
|
||||
import { Logo } from "@/lib/app/components";
|
||||
import { FoldableLogo } from "@/lib/app/components";
|
||||
import { Text } from "@opal/components";
|
||||
import { markdown, richNodes } from "@opal/utils";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
@@ -36,7 +36,7 @@ export default async function Page() {
|
||||
return (
|
||||
<main>
|
||||
<div className="min-h-screen flex flex-col items-center justify-center py-12 px-4 sm:px-6 lg:px-8 gap-4">
|
||||
<Logo folded size={64} className="mx-auto w-fit" />
|
||||
<FoldableLogo folded size={64} className="mx-auto w-fit" />
|
||||
<div className="flex flex-col gap-2">
|
||||
<Text as="span">
|
||||
{markdown(
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { BuildMessage } from "@/app/craft/types/streamingTypes";
|
||||
import type { StreamItem } from "@/app/craft/types/displayTypes";
|
||||
|
||||
jest.mock("@/lib/app/components", () => ({
|
||||
Logo: () => <div data-testid="onyx-logo" />,
|
||||
FoldableLogo: () => <div data-testid="onyx-logo" />,
|
||||
}));
|
||||
|
||||
jest.mock("@/components/chat/MinimalMarkdown", () => ({
|
||||
|
||||
@@ -7,7 +7,7 @@ import { CopyButton } from "@opal/components";
|
||||
import { Hoverable } from "@opal/core";
|
||||
import { SvgAlertCircle } from "@opal/icons";
|
||||
import { AnimatePresence, motion } from "motion/react";
|
||||
import { Logo } from "@/lib/app/components";
|
||||
import { FoldableLogo } from "@/lib/app/components";
|
||||
import SetupCard from "@/app/craft/components/setup-requests/SetupCard";
|
||||
import { ExternalAppUserResponse } from "@/app/craft/v1/apps/registry";
|
||||
import { errorHandlingFetcher } from "@/lib/fetcher";
|
||||
@@ -308,7 +308,7 @@ export default function BuildMessageList({
|
||||
<Hoverable.Root key={message.id} group="craftAgentMessage" width="full">
|
||||
<div className="flex items-start gap-3 py-4">
|
||||
<div className="shrink-0 h-9 flex items-center">
|
||||
<Logo onyxBranded folded size={24} />
|
||||
<FoldableLogo onyxBranded folded size={24} />
|
||||
</div>
|
||||
<div className="flex-1 flex flex-col gap-2 min-w-0">
|
||||
{visibleSavedRender ? (
|
||||
@@ -402,7 +402,7 @@ export default function BuildMessageList({
|
||||
{showStreamingArea && (
|
||||
<div className="flex items-start gap-3 py-4">
|
||||
<div className="shrink-0 mt-2">
|
||||
<Logo onyxBranded folded size={24} />
|
||||
<FoldableLogo onyxBranded folded size={24} />
|
||||
</div>
|
||||
<div className="flex-1 flex flex-col gap-2 min-w-0">
|
||||
{streamRender?.pinnedTodo && (
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useTranslations } from "next-intl";
|
||||
import { BuildFile } from "@/app/craft/contexts/UploadFilesContext";
|
||||
import { useVideoBackgroundToggleClick } from "@/app/craft/components/video-background/useVideoBackgroundToggleClick";
|
||||
import Text from "@/refresh-components/texts/Text";
|
||||
import { Logo } from "@/lib/app/components";
|
||||
import { FoldableLogo } from "@/lib/app/components";
|
||||
import CraftInputBar, {
|
||||
CraftInputBarHandle,
|
||||
} from "@/app/craft/components/CraftInputBar";
|
||||
@@ -73,7 +73,11 @@ export default function BuildWelcome({
|
||||
className="flex flex-row items-baseline gap-2 select-none"
|
||||
onClick={handleWordmarkClick}
|
||||
>
|
||||
<Logo onyxBranded size={28} className="translate-y-[6px]" />
|
||||
<FoldableLogo
|
||||
onyxBranded
|
||||
size={28}
|
||||
className="translate-y-[6px]"
|
||||
/>
|
||||
<Text
|
||||
text05
|
||||
style={{
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useTranslations } from "next-intl";
|
||||
import { Text } from "@opal/components";
|
||||
import { SvgArrowUp, SvgPlus } from "@opal/icons";
|
||||
import { cn } from "@opal/utils";
|
||||
import { Logo } from "@/lib/app/components";
|
||||
import { FoldableLogo } from "@/lib/app/components";
|
||||
|
||||
const PROMPT_PILL_KEYS = [
|
||||
"engineering",
|
||||
@@ -50,7 +50,11 @@ export default function WelcomePageMock({
|
||||
<div className="w-full max-w-3xl">
|
||||
<div className="flex flex-row items-center justify-between gap-4 pb-6">
|
||||
<div className="flex flex-row items-baseline gap-2 select-none">
|
||||
<Logo onyxBranded size={28} className="translate-y-[6px]" />
|
||||
<FoldableLogo
|
||||
onyxBranded
|
||||
size={28}
|
||||
className="translate-y-[6px]"
|
||||
/>
|
||||
<span
|
||||
className="text-text-05"
|
||||
style={{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { Logo } from "@/lib/app/components";
|
||||
import { FoldableLogo } from "@/lib/app/components";
|
||||
import { Button } from "@opal/components";
|
||||
import { SvgEditBig, SvgExternalLink } from "@opal/icons";
|
||||
import { useTranslations } from "next-intl";
|
||||
@@ -24,7 +24,7 @@ export default function SidePanelHeader({
|
||||
|
||||
return (
|
||||
<header className="flex items-center justify-between px-4 py-3 border-b border-border-01 bg-background">
|
||||
<Logo />
|
||||
<FoldableLogo />
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
prominence="tertiary"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { Logo } from "@/lib/app/components";
|
||||
import { FoldableLogo } from "@/lib/app/components";
|
||||
import { useSettings } from "@/lib/settings/hooks";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
@@ -10,7 +10,7 @@ export default function OnyxInitializingLoader() {
|
||||
|
||||
return (
|
||||
<div className="mx-auto my-auto animate-pulse">
|
||||
<Logo folded size={96} className="mx-auto mb-3" />
|
||||
<FoldableLogo folded size={96} className="mx-auto mb-3" />
|
||||
<p className="text-lg text-text font-semibold">
|
||||
{t("initializing.text", { appName })}
|
||||
</p>
|
||||
|
||||
@@ -10,38 +10,41 @@ import { cn } from "@opal/utils";
|
||||
import Text from "@/refresh-components/texts/Text";
|
||||
import Truncated from "@/refresh-components/texts/Truncated";
|
||||
import { SvgOnyxLogo, SvgOnyxLogoTyped } from "@opal/logos";
|
||||
import { IconProps } from "@opal/types";
|
||||
|
||||
export interface LogoProps {
|
||||
folded?: boolean;
|
||||
size?: number;
|
||||
className?: string;
|
||||
// Always render the real Onyx logo, ignoring enterprise white-label settings
|
||||
// (custom logo / application name). Used by Onyx-branded surfaces like Craft.
|
||||
export interface LogoProps extends IconProps {
|
||||
// Always render the real Onyx mark, ignoring the enterprise custom logo.
|
||||
// Used by Onyx-branded surfaces like Craft.
|
||||
onyxBranded?: boolean;
|
||||
}
|
||||
|
||||
export function Logo({ folded, size, className, onyxBranded }: LogoProps) {
|
||||
/**
|
||||
* The app mark alone: the uploaded custom logo when one is set, the Onyx
|
||||
* mark otherwise. Shaped like an Opal icon, so it fits any `icon` slot and
|
||||
* honours the size or style the slot passes.
|
||||
*/
|
||||
export function Logo({ size, className, style, onyxBranded }: LogoProps) {
|
||||
const t = useTranslations("common");
|
||||
const resolvedSize = size ?? DEFAULT_LOGO_SIZE_PX;
|
||||
const { enterprise, logoUrl } = useSettings();
|
||||
const logoDisplayStyle = enterprise?.logo_display_style;
|
||||
const applicationName = enterprise?.application_name;
|
||||
const { logoUrl } = useSettings();
|
||||
|
||||
if (onyxBranded) {
|
||||
return folded ? (
|
||||
<SvgOnyxLogo size={resolvedSize} className={cn("shrink-0", className)} />
|
||||
) : (
|
||||
<SvgOnyxLogoTyped size={resolvedSize} className={className} />
|
||||
if (onyxBranded || !logoUrl) {
|
||||
return (
|
||||
<SvgOnyxLogo
|
||||
size={resolvedSize}
|
||||
className={cn("shrink-0", className)}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const logo = logoUrl ? (
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"aspect-square rounded-full overflow-hidden relative shrink-0",
|
||||
className
|
||||
)}
|
||||
style={{ height: resolvedSize }}
|
||||
style={{ height: resolvedSize, ...style }}
|
||||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
@@ -50,9 +53,38 @@ export function Logo({ folded, size, className, onyxBranded }: LogoProps) {
|
||||
className="object-cover object-center w-full h-full"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<SvgOnyxLogo size={resolvedSize} className={cn("shrink-0", className)} />
|
||||
);
|
||||
}
|
||||
|
||||
export interface FoldableLogoProps extends LogoProps {
|
||||
folded?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* The sidebar's brand block: the mark, and when unfolded the application
|
||||
* name and the "powered by" line beside it, per the enterprise display style.
|
||||
*/
|
||||
export function FoldableLogo({
|
||||
folded,
|
||||
size,
|
||||
className,
|
||||
onyxBranded,
|
||||
}: FoldableLogoProps) {
|
||||
const t = useTranslations("common");
|
||||
const resolvedSize = size ?? DEFAULT_LOGO_SIZE_PX;
|
||||
const { enterprise } = useSettings();
|
||||
const logoDisplayStyle = enterprise?.logo_display_style;
|
||||
const applicationName = enterprise?.application_name;
|
||||
|
||||
if (onyxBranded) {
|
||||
return folded ? (
|
||||
<Logo onyxBranded size={resolvedSize} className={className} />
|
||||
) : (
|
||||
<SvgOnyxLogoTyped size={resolvedSize} className={className} />
|
||||
);
|
||||
}
|
||||
|
||||
const logo = <Logo size={resolvedSize} className={className} />;
|
||||
|
||||
const renderNameAndPoweredBy = (opts: {
|
||||
includeLogo: boolean;
|
||||
@@ -98,7 +130,7 @@ export function Logo({ folded, size, className, onyxBranded }: LogoProps) {
|
||||
return applicationName ? (
|
||||
renderNameAndPoweredBy({ includeLogo: true, includeName: true })
|
||||
) : folded ? (
|
||||
<SvgOnyxLogo size={resolvedSize} className={cn("shrink-0", className)} />
|
||||
<Logo size={resolvedSize} className={className} />
|
||||
) : (
|
||||
<SvgOnyxLogoTyped size={resolvedSize} className={className} />
|
||||
);
|
||||
|
||||
@@ -5,11 +5,11 @@ import { toast } from "@opal/layouts";
|
||||
import { ChatSession } from "@/app/app/interfaces";
|
||||
import { DEFAULT_AGENT_ID } from "@/lib/constants";
|
||||
import { LOCAL_STORAGE_KEYS } from "@/lib/sidebar/constants";
|
||||
import { Logo } from "@/lib/app/components";
|
||||
import { FoldableLogo } from "@/lib/app/components";
|
||||
import type { IconFunctionComponent } from "@opal/types";
|
||||
|
||||
export function renderSidebarLogo(folded: boolean): IconFunctionComponent {
|
||||
return (props) => React.createElement(Logo, { ...props, folded });
|
||||
return (props) => React.createElement(FoldableLogo, { ...props, folded });
|
||||
}
|
||||
|
||||
/** Parameters shared by move-operation helpers. */
|
||||
|
||||
@@ -6,8 +6,8 @@ import { Permission } from "@/lib/types";
|
||||
import useSWR, { mutate } from "swr";
|
||||
import { buildSimilarCredentialInfoURL } from "@/lib/connectors/utils";
|
||||
import { getSourceDisplayName, getSourceMetadata } from "@/lib/sources";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { renderSidebarLogo } from "@/lib/sidebar/utils";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Logo } from "@/lib/app/components";
|
||||
import { deleteCredential, linkCredential } from "@/lib/credential";
|
||||
import { submitFiles } from "@/lib/connectors/svc";
|
||||
import { submitGoogleSite } from "@/lib/connectors/svc";
|
||||
@@ -159,8 +159,6 @@ export default function AddConnector({
|
||||
|
||||
const router = useRouter();
|
||||
const settings = useSettings();
|
||||
// The app icon honours white-labelling, like the sidebar's.
|
||||
const AppIcon = useMemo(() => renderSidebarLogo(true), []);
|
||||
const defaultPruneFreqHours = settings.default_pruning_freq
|
||||
? settings.default_pruning_freq / 3600
|
||||
: 600; // 25 days fallback until settings load
|
||||
@@ -536,7 +534,7 @@ export default function AddConnector({
|
||||
<SettingsLayouts.Header
|
||||
icon={sourceMetadata.icon}
|
||||
moreIcon1={SvgArrowExchange}
|
||||
moreIcon2={AppIcon}
|
||||
moreIcon2={Logo}
|
||||
title={displayName}
|
||||
description={t("header.description", {
|
||||
source: displayName,
|
||||
|
||||
Reference in new issue
Block a user