feat: unify site theme and session controls
This commit is contained in:
parent
3f4f7d9c92
commit
2831f6028b
@ -13,7 +13,7 @@
|
||||
--app-shell-nav-offset: 5.5rem;
|
||||
|
||||
/* Light theme defaults */
|
||||
--color-background: #f7f7f8;
|
||||
--color-background: #f5f5f7;
|
||||
--color-background-muted: #ececef;
|
||||
--color-surface: #ffffff;
|
||||
--color-surface-elevated: rgba(255, 255, 255, 0.96);
|
||||
@ -27,14 +27,14 @@
|
||||
--color-text-muted: #4b5563;
|
||||
--color-text-subtle: #6b7280;
|
||||
--color-text-inverse: #f8fbff;
|
||||
--color-primary: #3366ff;
|
||||
--color-primary-hover: #4d7aff;
|
||||
--color-primary-muted: #f0f4ff;
|
||||
--color-primary-border: #d6e0ff;
|
||||
--color-primary: #4c8bf5;
|
||||
--color-primary-hover: #5d97f6;
|
||||
--color-primary-muted: #edf3ff;
|
||||
--color-primary-border: #d7e5ff;
|
||||
--color-primary-foreground: #ffffff;
|
||||
--color-accent: #254edb;
|
||||
--color-accent-muted: #e3e9ff;
|
||||
--color-accent-foreground: #162a6b;
|
||||
--color-accent: #335fd4;
|
||||
--color-accent-muted: #e7edff;
|
||||
--color-accent-foreground: #1b3477;
|
||||
--color-success: #16a34a;
|
||||
--color-success-muted: #dcfce7;
|
||||
--color-success-foreground: #166534;
|
||||
@ -58,15 +58,17 @@
|
||||
--gradient-app-from: #fafafa;
|
||||
--gradient-app-via: #f4f5f7;
|
||||
--gradient-app-to: #f7f7f8;
|
||||
--gradient-primary-from: #3366ff;
|
||||
--gradient-primary-to: #254edb;
|
||||
--gradient-primary-from: #4c8bf5;
|
||||
--gradient-primary-to: #335fd4;
|
||||
|
||||
--shadow-sm:
|
||||
0 1px 2px rgba(17, 24, 39, 0.06), 0 1px 3px rgba(17, 24, 39, 0.04);
|
||||
--shadow-md: 0 10px 24px rgba(17, 24, 39, 0.08);
|
||||
0 1px 2px rgba(17, 24, 39, 0.05), 0 3px 10px rgba(17, 24, 39, 0.04);
|
||||
--shadow-md: 0 10px 28px rgba(17, 24, 39, 0.07);
|
||||
--shadow-soft:
|
||||
0 1px 2px rgba(17, 24, 39, 0.04), 0 8px 20px rgba(17, 24, 39, 0.05);
|
||||
|
||||
--radius-lg: 0.875rem;
|
||||
--radius-xl: 1.125rem;
|
||||
--radius-lg: 0.75rem;
|
||||
--radius-xl: 1rem;
|
||||
--radius-pill: 999px;
|
||||
|
||||
--type-body-size: 1rem;
|
||||
@ -91,11 +93,13 @@ body {
|
||||
font-size: var(--type-body-size);
|
||||
line-height: var(--type-body-line-height);
|
||||
background-color: var(--color-background);
|
||||
background-image: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.58),
|
||||
rgba(255, 255, 255, 0)
|
||||
);
|
||||
background-image:
|
||||
radial-gradient(
|
||||
circle at top left,
|
||||
rgba(76, 139, 245, 0.08),
|
||||
transparent 26%
|
||||
),
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.62), rgba(255, 255, 255, 0));
|
||||
color: var(--color-text);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
@ -157,6 +161,73 @@ button {
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.tactile-button {
|
||||
display: inline-flex;
|
||||
min-height: 40px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
border-radius: 12px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.625rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
transition:
|
||||
background-color 160ms ease-in-out,
|
||||
border-color 160ms ease-in-out,
|
||||
color 160ms ease-in-out,
|
||||
transform 160ms ease-in-out,
|
||||
box-shadow 160ms ease-in-out;
|
||||
}
|
||||
|
||||
.tactile-button:hover {
|
||||
filter: brightness(1.02);
|
||||
}
|
||||
|
||||
.tactile-button:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.tactile-button:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px var(--color-focus);
|
||||
}
|
||||
|
||||
.tactile-button-soft {
|
||||
border-color: var(--color-surface-border);
|
||||
background: #f1f1f3;
|
||||
color: var(--color-text);
|
||||
box-shadow: var(--shadow-soft);
|
||||
}
|
||||
|
||||
.tactile-button-soft:hover {
|
||||
background: #e5e5ea;
|
||||
}
|
||||
|
||||
.tactile-button-primary {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-primary-foreground);
|
||||
box-shadow: var(--shadow-soft);
|
||||
}
|
||||
|
||||
.tactile-button-primary:hover {
|
||||
background: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
.tactile-button-subtle {
|
||||
border-color: var(--color-surface-border);
|
||||
background: rgba(255, 255, 255, 0.84);
|
||||
color: var(--color-text-subtle);
|
||||
}
|
||||
|
||||
.tactile-control {
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--color-surface-border);
|
||||
background: rgba(255, 255, 255, 0.88);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.editorial-display {
|
||||
font-family: var(--font-editorial-display);
|
||||
letter-spacing: -0.04em;
|
||||
|
||||
@ -33,11 +33,11 @@ import { useMoltbotStore } from "../lib/moltbotStore";
|
||||
import { cn } from "../lib/utils";
|
||||
|
||||
const HOME_SECTION_CLASS =
|
||||
"rounded-[2rem] border border-slate-900/10 bg-white/90 shadow-[0_18px_40px_rgba(15,23,42,0.05)]";
|
||||
"rounded-[1rem] border border-slate-900/8 bg-white/88 shadow-[var(--shadow-soft)]";
|
||||
const HOME_SECTION_LABEL_CLASS =
|
||||
"text-[0.68rem] font-semibold uppercase tracking-[0.26em] text-text-subtle";
|
||||
const HOME_LIST_CARD_CLASS =
|
||||
"rounded-[1.5rem] border border-slate-900/10 bg-[#fcfbf8] transition duration-200";
|
||||
"rounded-[0.9rem] border border-slate-900/8 bg-white/82 transition duration-200";
|
||||
const EMPTY_ASSISTANT_DEFAULTS: IntegrationDefaults = {
|
||||
openclawUrl: "",
|
||||
openclawOrigin: "",
|
||||
@ -117,7 +117,7 @@ export default function HomePage() {
|
||||
<div className="mobile-home-shell relative flex min-h-screen flex-col overflow-x-hidden bg-background text-text transition-colors duration-150">
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute inset-0 bg-[linear-gradient(180deg,rgba(255,255,255,0.56),rgba(255,255,255,0))]"
|
||||
className="pointer-events-none absolute inset-0 bg-[linear-gradient(180deg,rgba(255,255,255,0.5),rgba(255,255,255,0))]"
|
||||
/>
|
||||
<UnifiedNavigation />
|
||||
|
||||
@ -128,8 +128,8 @@ export default function HomePage() {
|
||||
)}
|
||||
>
|
||||
<div className="relative flex-1 overflow-y-auto">
|
||||
<div className="relative mx-auto max-w-6xl px-4 pb-16 sm:px-6 sm:pb-20">
|
||||
<main className="relative space-y-6 pt-6 sm:space-y-8 sm:pt-10">
|
||||
<div className="relative w-full px-2 pb-10 sm:px-3 sm:pb-12 lg:px-4">
|
||||
<main className="relative space-y-3 pt-3 sm:space-y-4 sm:pt-4">
|
||||
<HeroSection />
|
||||
<StatsSection />
|
||||
<ShortcutsSection />
|
||||
@ -165,11 +165,12 @@ export function HeroSection() {
|
||||
},
|
||||
);
|
||||
const entry =
|
||||
homepageVideoSWR.data?.resolved ?? DEFAULT_HOMEPAGE_VIDEO_SETTINGS.defaultEntry;
|
||||
homepageVideoSWR.data?.resolved ??
|
||||
DEFAULT_HOMEPAGE_VIDEO_SETTINGS.defaultEntry;
|
||||
const presentation = resolveHomepageVideoPresentation(entry);
|
||||
|
||||
const heroCopy = isChinese
|
||||
? {
|
||||
? {
|
||||
eyebrow: "AI Native Workspace",
|
||||
subtitle: "从想法到上线,AI 自动完成构建、部署与优化。",
|
||||
demoLabel: "产品演示",
|
||||
@ -182,33 +183,37 @@ export function HeroSection() {
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="relative overflow-hidden rounded-[1.75rem] border border-slate-900/10 bg-[linear-gradient(180deg,#ffffff,#faf7f2)] p-3 shadow-[0_18px_40px_rgba(15,23,42,0.05)] sm:p-4 lg:p-5">
|
||||
<section className="relative overflow-hidden rounded-[1rem] border border-slate-900/8 bg-[linear-gradient(180deg,rgba(255,255,255,0.96),rgba(247,248,250,0.98))] p-2.5 shadow-[var(--shadow-soft)] sm:p-3 lg:p-3.5">
|
||||
<div aria-hidden className="pointer-events-none absolute inset-0">
|
||||
<div className="absolute left-[8%] top-[8%] h-[16rem] w-[16rem] rounded-full bg-[radial-gradient(circle,rgba(37,78,219,0.1),transparent_64%)] blur-3xl" />
|
||||
<div className="absolute left-[30%] top-[12%] h-[14rem] w-[14rem] rounded-full bg-[radial-gradient(circle,rgba(245,211,170,0.42),transparent_66%)] blur-3xl" />
|
||||
<div className="absolute right-[10%] top-[10%] h-[18rem] w-[18rem] rounded-full bg-[radial-gradient(circle,rgba(255,255,255,0.92),transparent_72%)]" />
|
||||
<div className="absolute inset-x-0 top-0 h-[18rem] bg-[linear-gradient(180deg,rgba(255,255,255,0.78),rgba(255,255,255,0)_72%)]" />
|
||||
<div className="absolute left-[6%] top-[4%] h-[14rem] w-[14rem] rounded-full bg-[radial-gradient(circle,rgba(76,139,245,0.12),transparent_64%)] blur-3xl" />
|
||||
<div className="absolute left-[28%] top-[8%] h-[12rem] w-[12rem] rounded-full bg-[radial-gradient(circle,rgba(255,255,255,0.86),transparent_66%)] blur-2xl" />
|
||||
<div className="absolute inset-x-0 top-0 h-[16rem] bg-[linear-gradient(180deg,rgba(255,255,255,0.72),rgba(255,255,255,0)_72%)]" />
|
||||
</div>
|
||||
|
||||
<div className="relative grid gap-4 lg:grid-cols-[0.98fr_1.02fr] lg:gap-5">
|
||||
<div className="flex flex-col gap-3 pt-1">
|
||||
<div className="overflow-hidden rounded-[1.35rem] border border-slate-900/10 bg-[linear-gradient(180deg,rgba(255,255,255,0.96),rgba(243,246,251,0.96))] shadow-[0_18px_44px_rgba(15,23,42,0.07)]">
|
||||
<div className="relative grid gap-3 lg:grid-cols-[0.98fr_1.02fr] lg:gap-3">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="overflow-hidden rounded-[0.95rem] border border-slate-900/8 bg-[linear-gradient(180deg,rgba(255,255,255,0.96),rgba(245,247,250,0.98))] shadow-[var(--shadow-soft)]">
|
||||
<div className="border-b border-slate-900/10 px-4 py-3 sm:px-4.5">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<p className={HOME_SECTION_LABEL_CLASS}>{heroCopy.demoLabel}</p>
|
||||
<p className={HOME_SECTION_LABEL_CLASS}>
|
||||
{heroCopy.demoLabel}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3 p-3 sm:p-3.5">
|
||||
<DemoVideoSurface presentation={presentation} isChinese={isChinese} />
|
||||
<DemoVideoSurface
|
||||
presentation={presentation}
|
||||
isChinese={isChinese}
|
||||
/>
|
||||
<div className="flex flex-wrap gap-2 text-xs text-slate-500">
|
||||
<a
|
||||
href={entry.videoUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="inline-flex items-center rounded-full border border-slate-900/10 bg-white px-2.5 py-1 font-semibold text-slate-600 transition hover:border-slate-300 hover:text-slate-900"
|
||||
className="tactile-button tactile-button-subtle px-3 text-slate-700"
|
||||
>
|
||||
{isChinese ? "打开原始链接" : "Open source link"}
|
||||
</a>
|
||||
@ -226,8 +231,8 @@ export function HeroSection() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="lg:pl-1">
|
||||
<div className="overflow-hidden rounded-[1.35rem] border border-slate-900/10 bg-[linear-gradient(180deg,rgba(255,255,255,0.94),rgba(246,248,251,0.98))] shadow-[0_18px_44px_rgba(15,23,42,0.07)]">
|
||||
<div>
|
||||
<div className="overflow-hidden rounded-[0.95rem] border border-slate-900/8 bg-[linear-gradient(180deg,rgba(255,255,255,0.96),rgba(245,247,250,0.98))] shadow-[var(--shadow-soft)]">
|
||||
<div className="border-b border-slate-900/10 px-4 py-3 sm:px-4.5">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
@ -235,7 +240,7 @@ export function HeroSection() {
|
||||
{isChinese ? "X 助手" : "X Assistant"}
|
||||
</p>
|
||||
</div>
|
||||
<span className="hidden rounded-full border border-slate-900/10 bg-white/90 px-2.5 py-0.5 text-xs font-semibold text-slate-600 sm:inline-flex">
|
||||
<span className="hidden rounded-[12px] border border-slate-900/8 bg-white/84 px-3 py-1 text-xs font-semibold text-slate-600 sm:inline-flex">
|
||||
{isChinese ? "对话即入口" : "Prompt-first"}
|
||||
</span>
|
||||
</div>
|
||||
@ -323,7 +328,7 @@ export function StatsSection() {
|
||||
];
|
||||
|
||||
return (
|
||||
<section className={cn(HOME_SECTION_CLASS, "space-y-5 p-5 lg:p-7")}>
|
||||
<section className={cn(HOME_SECTION_CLASS, "space-y-4 p-4 lg:p-5")}>
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
|
||||
<div>
|
||||
<p className={HOME_SECTION_LABEL_CLASS}>
|
||||
@ -335,7 +340,7 @@ export function StatsSection() {
|
||||
: "Keep key numbers in the same calm visual rhythm instead of a separate heavy dashboard block."}
|
||||
</p>
|
||||
</div>
|
||||
<span className="inline-flex w-fit items-center rounded-full border border-slate-900/10 bg-white px-3 py-1 text-xs font-semibold text-slate-600">
|
||||
<span className="inline-flex w-fit items-center rounded-[12px] border border-slate-900/8 bg-white/82 px-3 py-1.5 text-xs font-semibold text-slate-600">
|
||||
{language === "zh" ? "每小时更新" : "Updated hourly"}
|
||||
</span>
|
||||
</div>
|
||||
@ -344,7 +349,7 @@ export function StatsSection() {
|
||||
{displayStats.map((stat, index: number) => (
|
||||
<div
|
||||
key={index}
|
||||
className="rounded-[1.5rem] border border-slate-900/10 bg-[#fcfbf8] px-4 py-5"
|
||||
className="rounded-[0.9rem] border border-slate-900/8 bg-white/80 px-4 py-4"
|
||||
>
|
||||
<div className="editorial-display text-[2.2rem] leading-none text-slate-950 sm:text-[2.7rem]">
|
||||
{stat.value}
|
||||
@ -395,7 +400,7 @@ export function ShortcutsSection() {
|
||||
}));
|
||||
|
||||
return (
|
||||
<section className={cn(HOME_SECTION_CLASS, "space-y-4 p-5 lg:p-7")}>
|
||||
<section className={cn(HOME_SECTION_CLASS, "space-y-4 p-4 lg:p-5")}>
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
|
||||
<div>
|
||||
<p className={HOME_SECTION_LABEL_CLASS}>{t.shortcuts.title}</p>
|
||||
@ -407,19 +412,19 @@ export function ShortcutsSection() {
|
||||
<div className="flex flex-wrap gap-2 text-xs font-semibold">
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-full border border-slate-900/10 bg-slate-950 px-3 py-2 text-white transition hover:bg-primary"
|
||||
className="tactile-button tactile-button-primary px-4"
|
||||
>
|
||||
{t.shortcuts.buttons.start}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-full border border-slate-900/10 bg-white px-3 py-2 text-slate-700 transition hover:bg-slate-50"
|
||||
className="tactile-button tactile-button-soft px-4 text-slate-700"
|
||||
>
|
||||
{t.shortcuts.buttons.docs}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-full border border-slate-900/10 bg-white px-3 py-2 text-slate-700 transition hover:bg-slate-50"
|
||||
className="tactile-button tactile-button-soft px-4 text-slate-700"
|
||||
>
|
||||
{t.shortcuts.buttons.guides}
|
||||
</button>
|
||||
@ -435,10 +440,10 @@ export function ShortcutsSection() {
|
||||
href={item.href}
|
||||
className={cn(
|
||||
HOME_LIST_CARD_CLASS,
|
||||
"group flex items-start gap-3 p-4 hover:-translate-y-[1px] hover:bg-white",
|
||||
"group flex items-start gap-3 p-4 hover:-translate-y-[1px] hover:bg-white/96",
|
||||
)}
|
||||
>
|
||||
<div className="mt-1 flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-slate-900/[0.04] text-primary">
|
||||
<div className="mt-1 flex h-10 w-10 shrink-0 items-center justify-center rounded-[12px] bg-slate-900/[0.04] text-primary">
|
||||
<Icon className="h-5 w-5" aria-hidden />
|
||||
</div>
|
||||
<div className="min-w-0 space-y-1">
|
||||
|
||||
@ -1,18 +1,37 @@
|
||||
'use client'
|
||||
import { useLanguage } from '../i18n/LanguageProvider'
|
||||
"use client";
|
||||
|
||||
import { useLanguage } from "../i18n/LanguageProvider";
|
||||
|
||||
export default function LanguageToggle() {
|
||||
const { language, setLanguage } = useLanguage()
|
||||
const { language, setLanguage } = useLanguage();
|
||||
|
||||
return (
|
||||
<select
|
||||
value={language}
|
||||
onChange={(e) => setLanguage(e.target.value as 'en' | 'zh')}
|
||||
className="bg-gray-100 text-gray-900 border border-gray-300 px-2 py-1 rounded text-sm"
|
||||
>
|
||||
<option value="en">English</option>
|
||||
<option value="zh">中文</option>
|
||||
</select>
|
||||
)
|
||||
<div className="tactile-control relative">
|
||||
<select
|
||||
value={language}
|
||||
onChange={(event) => setLanguage(event.target.value as "en" | "zh")}
|
||||
aria-label="Language switcher"
|
||||
className="min-h-10 appearance-none bg-transparent py-2 pl-4 pr-10 text-sm font-medium text-text outline-none"
|
||||
>
|
||||
<option value="en">English</option>
|
||||
<option value="zh">中文</option>
|
||||
</select>
|
||||
<span className="pointer-events-none absolute inset-y-0 right-3 flex items-center text-text-subtle">
|
||||
<svg
|
||||
className="h-4 w-4"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.75"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M5 7.5 10 12.5 15 7.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// This component provides a dropdown to toggle between English and Chinese languages.
|
||||
@ -1,64 +1,68 @@
|
||||
'use client'
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useLanguage } from '../i18n/LanguageProvider'
|
||||
import { translations } from '../i18n/translations'
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useLanguage } from "../i18n/LanguageProvider";
|
||||
import { translations } from "../i18n/translations";
|
||||
|
||||
export type ReleaseChannel = 'stable' | 'beta' | 'develop'
|
||||
export type ReleaseChannel = "stable" | "beta" | "develop";
|
||||
|
||||
type ReleaseChannelSelectorProps = {
|
||||
selected: ReleaseChannel[]
|
||||
onToggle: (channel: ReleaseChannel) => void
|
||||
variant?: 'default' | 'compact' | 'icon'
|
||||
}
|
||||
selected: ReleaseChannel[];
|
||||
onToggle: (channel: ReleaseChannel) => void;
|
||||
variant?: "default" | "compact" | "icon";
|
||||
};
|
||||
|
||||
const CHANNEL_ORDER: ReleaseChannel[] = ['stable', 'beta', 'develop']
|
||||
const CHANNEL_ORDER: ReleaseChannel[] = ["stable", "beta", "develop"];
|
||||
|
||||
export default function ReleaseChannelSelector({
|
||||
selected,
|
||||
onToggle,
|
||||
variant = 'default',
|
||||
variant = "default",
|
||||
}: ReleaseChannelSelectorProps) {
|
||||
const { language } = useLanguage()
|
||||
const labels = translations[language].nav.releaseChannels
|
||||
const [open, setOpen] = useState(false)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const isCompact = variant === 'compact'
|
||||
const isIcon = variant === 'icon'
|
||||
const hasPreviewSelection = selected.some((channel) => channel !== 'stable')
|
||||
const { language } = useLanguage();
|
||||
const labels = translations[language].nav.releaseChannels;
|
||||
const [open, setOpen] = useState(false);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const isCompact = variant === "compact";
|
||||
const isIcon = variant === "icon";
|
||||
const hasPreviewSelection = selected.some((channel) => channel !== "stable");
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (!containerRef.current) return
|
||||
if (containerRef.current.contains(event.target as Node)) return
|
||||
setOpen(false)
|
||||
}
|
||||
if (!containerRef.current) return;
|
||||
if (containerRef.current.contains(event.target as Node)) return;
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
if (open) {
|
||||
document.addEventListener('mousedown', handleClickOutside)
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside)
|
||||
}
|
||||
}, [open])
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, [open]);
|
||||
|
||||
const selectedNames = CHANNEL_ORDER.filter((channel) => selected.includes(channel)).map(
|
||||
(channel) => labels[channel].name,
|
||||
)
|
||||
const summary = selectedNames.length > 0 ? selectedNames.join(' + ') : labels.stable.name
|
||||
const selectedNames = CHANNEL_ORDER.filter((channel) =>
|
||||
selected.includes(channel),
|
||||
).map((channel) => labels[channel].name);
|
||||
const summary =
|
||||
selectedNames.length > 0 ? selectedNames.join(" + ") : labels.stable.name;
|
||||
|
||||
return (
|
||||
<div className={`relative ${isCompact || isIcon ? 'group' : ''}`} ref={containerRef}>
|
||||
<div
|
||||
className={`relative ${isCompact || isIcon ? "group" : ""}`}
|
||||
ref={containerRef}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen((prev) => !prev)}
|
||||
className={`relative flex items-center gap-2 rounded-md border border-gray-200 bg-white/80 text-sm text-gray-700 shadow-sm transition hover:border-purple-300 hover:text-purple-600 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 ${
|
||||
className={`tactile-button relative border border-surface-border bg-surface text-text shadow-[var(--shadow-soft)] ${
|
||||
isIcon
|
||||
? 'h-9 w-9 justify-center p-0'
|
||||
? "h-10 w-10 justify-center p-0"
|
||||
: isCompact
|
||||
? 'px-3 py-1 text-xs font-medium'
|
||||
: 'w-full justify-between px-3 py-1.5 md:w-auto md:justify-start'
|
||||
? "px-3 text-xs font-medium"
|
||||
: "w-full justify-between md:w-auto md:justify-start"
|
||||
}`}
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded={open}
|
||||
@ -66,7 +70,7 @@ export default function ReleaseChannelSelector({
|
||||
>
|
||||
{isIcon ? (
|
||||
<svg
|
||||
className="h-5 w-5 text-purple-600"
|
||||
className="h-5 w-5 text-primary"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
@ -86,64 +90,81 @@ export default function ReleaseChannelSelector({
|
||||
</svg>
|
||||
) : (
|
||||
<>
|
||||
<span className={`font-medium ${isCompact ? '' : 'text-gray-700'}`}>{labels.label}</span>
|
||||
<span className={`font-medium ${isCompact ? "" : "text-text"}`}>
|
||||
{labels.label}
|
||||
</span>
|
||||
{!isCompact && (
|
||||
<span className="text-xs text-gray-500">
|
||||
<span className="text-xs text-text-subtle">
|
||||
{labels.summaryPrefix}: {summary}
|
||||
</span>
|
||||
)}
|
||||
<svg
|
||||
className={`h-4 w-4 text-gray-400 transition-transform ${open ? 'rotate-180' : ''}`}
|
||||
className={`h-4 w-4 text-text-subtle transition-transform ${open ? "rotate-180" : ""}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M19 9l-7 7-7-7"
|
||||
/>
|
||||
</svg>
|
||||
</>
|
||||
)}
|
||||
{isIcon && hasPreviewSelection && (
|
||||
<span className="absolute -top-1 -right-1 h-2 w-2 rounded-full bg-purple-500" />
|
||||
<span className="absolute -right-0.5 -top-0.5 h-2.5 w-2.5 rounded-full bg-primary" />
|
||||
)}
|
||||
</button>
|
||||
{(isCompact || isIcon) && (
|
||||
<div className="pointer-events-none absolute bottom-full left-1/2 z-40 mb-2 hidden w-56 -translate-x-1/2 rounded-md bg-gray-900 px-3 py-2 text-xs text-white opacity-0 transition group-hover:block group-hover:opacity-100 group-focus-within:block group-focus-within:opacity-100">
|
||||
<div className="pointer-events-none absolute bottom-full left-1/2 z-40 mb-2 hidden w-56 -translate-x-1/2 rounded-xl bg-slate-900 px-3 py-2 text-xs text-white opacity-0 shadow-[var(--shadow-md)] transition group-hover:block group-hover:opacity-100 group-focus-within:block group-focus-within:opacity-100">
|
||||
<div className="font-semibold">{labels.label}</div>
|
||||
<div className="mt-1 text-gray-200">
|
||||
<div className="mt-1 text-slate-200">
|
||||
{labels.summaryPrefix}: {summary}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{open && (
|
||||
<div className="absolute right-0 z-50 mt-2 w-64 rounded-md border border-gray-200 bg-white shadow-lg">
|
||||
<ul className="py-2 text-sm text-gray-700" role="listbox" aria-label={labels.label}>
|
||||
<div className="absolute right-0 z-50 mt-2 w-64 overflow-hidden rounded-[14px] border border-surface-border bg-surface shadow-[var(--shadow-md)]">
|
||||
<ul
|
||||
className="py-2 text-sm text-text"
|
||||
role="listbox"
|
||||
aria-label={labels.label}
|
||||
>
|
||||
{CHANNEL_ORDER.map((channel) => {
|
||||
const channelLabels = labels[channel]
|
||||
const checked = selected.includes(channel)
|
||||
const isStable = channel === 'stable'
|
||||
const channelLabels = labels[channel];
|
||||
const checked = selected.includes(channel);
|
||||
const isStable = channel === "stable";
|
||||
return (
|
||||
<li key={channel}>
|
||||
<label className="flex cursor-pointer items-start gap-3 px-3 py-2 hover:bg-gray-50">
|
||||
<label className="flex cursor-pointer items-start gap-3 px-3 py-2.5 transition hover:bg-surface-muted">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="mt-1 h-4 w-4 rounded border-gray-300 text-purple-600 focus:ring-purple-500"
|
||||
className="mt-1 h-4 w-4 rounded border-surface-border text-primary focus:ring-primary"
|
||||
checked={checked}
|
||||
onChange={() => (!isStable ? onToggle(channel) : undefined)}
|
||||
onChange={() =>
|
||||
!isStable ? onToggle(channel) : undefined
|
||||
}
|
||||
disabled={isStable}
|
||||
/>
|
||||
<div>
|
||||
<div className="font-medium text-gray-900">{channelLabels.name}</div>
|
||||
<p className="text-xs text-gray-500">{channelLabels.description}</p>
|
||||
<div className="font-medium text-text">
|
||||
{channelLabels.name}
|
||||
</div>
|
||||
<p className="text-xs text-text-subtle">
|
||||
{channelLabels.description}
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
</li>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -223,9 +223,9 @@ export default function UnifiedNavigation() {
|
||||
width: "calc(100% + var(--assistant-reserve-offset, 0px))",
|
||||
marginRight: "calc(var(--assistant-reserve-offset, 0px) * -1)",
|
||||
}}
|
||||
className="sticky top-0 z-50 w-full border-b border-surface-border bg-background/95 text-text backdrop-blur transition-colors duration-150"
|
||||
className="sticky top-0 z-50 w-full border-b border-surface-border/80 bg-background/92 text-text backdrop-blur-xl transition-colors duration-150"
|
||||
>
|
||||
<div className="flex items-center justify-between border-b border-surface-border/70 bg-background px-5 pb-3 pt-[max(0.875rem,env(safe-area-inset-top))] lg:hidden">
|
||||
<div className="flex items-center justify-between border-b border-surface-border/70 bg-background px-4 pb-3 pt-[max(0.875rem,env(safe-area-inset-top))] lg:hidden">
|
||||
<Link
|
||||
href="/"
|
||||
className="flex items-center gap-2"
|
||||
@ -245,7 +245,7 @@ export default function UnifiedNavigation() {
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => setMenuOpen(!menuOpen)}
|
||||
className="rounded-[1.15rem] bg-surface-muted p-3 text-text transition-colors hover:bg-surface-hover"
|
||||
className="tactile-button tactile-button-soft h-10 w-10 rounded-[12px] p-0"
|
||||
aria-label="Toggle menu"
|
||||
>
|
||||
{menuOpen ? (
|
||||
@ -270,14 +270,14 @@ export default function UnifiedNavigation() {
|
||||
onClick={() => {
|
||||
toggleOpen();
|
||||
}}
|
||||
className={`flex items-center gap-1.5 px-2 py-1.5 rounded-lg transition-colors whitespace-nowrap ${
|
||||
className={`tactile-button min-h-9 gap-1.5 rounded-[12px] px-3 py-2 text-[13px] shadow-none whitespace-nowrap ${
|
||||
active
|
||||
? "bg-primary/10 text-primary"
|
||||
: "text-text-muted hover:text-text hover:bg-surface-muted"
|
||||
? "border border-primary/10 bg-primary/12 text-primary"
|
||||
: "text-text-muted hover:bg-surface-muted hover:text-text"
|
||||
}`}
|
||||
>
|
||||
{item.icon && <item.icon className="w-4 h-4" />}
|
||||
<span className="text-[13px] tracking-tight">
|
||||
<span className="tracking-tight">
|
||||
{getLabel(item.label, language)}
|
||||
</span>
|
||||
</button>
|
||||
@ -287,14 +287,14 @@ export default function UnifiedNavigation() {
|
||||
<Link
|
||||
key={item.key}
|
||||
href={item.href}
|
||||
className={`flex items-center gap-1.5 px-2 py-1.5 rounded-lg transition-colors whitespace-nowrap ${
|
||||
className={`tactile-button min-h-9 gap-1.5 rounded-[12px] px-3 py-2 text-[13px] shadow-none whitespace-nowrap ${
|
||||
active
|
||||
? "bg-primary/10 text-primary"
|
||||
: "text-text-muted hover:text-text hover:bg-surface-muted"
|
||||
? "border border-primary/10 bg-primary/12 text-primary"
|
||||
: "text-text-muted hover:bg-surface-muted hover:text-text"
|
||||
}`}
|
||||
>
|
||||
{item.icon && <item.icon className="w-4 h-4" />}
|
||||
<span className="text-[13px] tracking-tight">
|
||||
<span className="tracking-tight">
|
||||
{getLabel(item.label, language)}
|
||||
</span>
|
||||
</Link>
|
||||
@ -339,7 +339,7 @@ export default function UnifiedNavigation() {
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-10 w-10 items-center justify-center rounded-full bg-gradient-to-br from-primary to-accent text-sm font-semibold text-white shadow-shadow-sm transition hover:from-primary-hover hover:to-accent focus:outline-none focus:ring-2 focus:ring-primary/60 focus:ring-offset-2 focus:ring-offset-background outline-none ring-offset-background"
|
||||
className="tactile-button tactile-button-primary h-10 w-10 rounded-[12px] p-0 text-sm outline-none"
|
||||
aria-label="User account menu"
|
||||
>
|
||||
{accountInitial}
|
||||
@ -410,7 +410,7 @@ export default function UnifiedNavigation() {
|
||||
/>
|
||||
<Link
|
||||
href="/register"
|
||||
className="rounded-md border border-surface-border px-3 py-1 text-primary transition hover:border-primary/40 hover:bg-surface-muted"
|
||||
className="tactile-button tactile-button-soft min-h-10 px-4 text-primary"
|
||||
>
|
||||
{nav.account.register}
|
||||
</Link>
|
||||
@ -460,7 +460,7 @@ export default function UnifiedNavigation() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setLanguage(language === "zh" ? "en" : "zh")}
|
||||
className="inline-flex h-10 min-w-10 items-center justify-center rounded-full border border-surface-border bg-surface-muted/75 px-3 text-xs font-semibold uppercase tracking-[0.18em] text-text shadow-sm transition hover:bg-surface-hover"
|
||||
className="tactile-button tactile-button-soft h-10 min-w-10 px-3 text-xs uppercase tracking-[0.18em]"
|
||||
aria-label={
|
||||
isChinese ? "切换到英文" : "Switch language to Chinese"
|
||||
}
|
||||
@ -469,7 +469,7 @@ export default function UnifiedNavigation() {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setMenuOpen(false)}
|
||||
className="rounded-full border border-surface-border bg-surface-muted/75 p-2.5 text-text shadow-sm transition-colors hover:bg-surface-hover"
|
||||
className="tactile-button tactile-button-soft h-10 w-10 p-0"
|
||||
aria-label={isChinese ? "关闭菜单" : "Close menu"}
|
||||
>
|
||||
<X className="h-5 w-5" />
|
||||
@ -519,7 +519,7 @@ export default function UnifiedNavigation() {
|
||||
|
||||
{mobileQuickLinks.length > 0 ? (
|
||||
<div className="pointer-events-none absolute right-0 top-[60%] flex -translate-y-1/2 justify-end min-[390px]:top-[59%] min-[430px]:top-[58%]">
|
||||
<div className="pointer-events-auto w-[min(10.75rem,45vw)] rounded-[1.75rem] bg-surface-muted/82 p-4 shadow-[0_18px_40px_rgba(15,23,42,0.08)]">
|
||||
<div className="pointer-events-auto w-[min(10.75rem,45vw)] rounded-[14px] border border-surface-border/70 bg-surface-muted/84 p-4 shadow-[var(--shadow-md)]">
|
||||
<div className="space-y-2.5">
|
||||
{mobileQuickLinks.map((item) =>
|
||||
item.key === "chat" ? (
|
||||
@ -574,7 +574,7 @@ export default function UnifiedNavigation() {
|
||||
<Link
|
||||
href={primaryAccountAction.href}
|
||||
onClick={() => setMenuOpen(false)}
|
||||
className="inline-flex min-h-[3.1rem] min-w-[7rem] items-center justify-center rounded-full bg-surface-muted px-6 text-[1.05rem] font-semibold text-text shadow-sm transition hover:bg-surface-hover"
|
||||
className="tactile-button tactile-button-soft min-h-[3.1rem] min-w-[7rem] px-6 text-[1.05rem]"
|
||||
>
|
||||
{typeof primaryAccountAction.label === "function"
|
||||
? primaryAccountAction.label(language)
|
||||
|
||||
@ -89,8 +89,7 @@ type PersistedPairingRequiredLookup = {
|
||||
expired: boolean;
|
||||
};
|
||||
|
||||
const PAIRING_REQUIRED_SESSION_STORAGE_KEY =
|
||||
"openclaw:pairing-required-state";
|
||||
const PAIRING_REQUIRED_SESSION_STORAGE_KEY = "openclaw:pairing-required-state";
|
||||
const PAIRING_REQUIRED_STATE_TTL_MS = 1000 * 60 * 60 * 12;
|
||||
const PAIRING_REQUIRED_GUEST_TTL_MS = 1000 * 60 * 60;
|
||||
|
||||
@ -1159,7 +1158,7 @@ export function OpenClawAssistantPane({
|
||||
}
|
||||
|
||||
const containerClassName = cn(
|
||||
"flex h-full min-h-0 flex-col overflow-hidden rounded-[var(--radius-xl)] border border-[color:var(--color-surface-border)] bg-[var(--color-surface-elevated)] shadow-[var(--shadow-md)]",
|
||||
"flex h-full min-h-0 flex-col overflow-hidden rounded-[14px] border border-[color:var(--color-surface-border)] bg-[var(--color-surface-elevated)] shadow-[var(--shadow-md)]",
|
||||
compact ? "rounded-none border-0 shadow-none" : "",
|
||||
);
|
||||
|
||||
@ -1184,7 +1183,7 @@ export function OpenClawAssistantPane({
|
||||
)}
|
||||
>
|
||||
{!minimalPage ? (
|
||||
<div className="inline-flex items-center gap-2 rounded-full border border-[color:var(--color-surface-border)] bg-[var(--color-surface-muted)] px-2.5 py-1 text-xs font-medium text-[var(--color-text-subtle)]">
|
||||
<div className="inline-flex min-h-10 items-center gap-2 rounded-[12px] border border-[color:var(--color-surface-border)] bg-[var(--color-surface-muted)] px-3 py-2 text-xs font-medium text-[var(--color-text-subtle)] shadow-[var(--shadow-soft)]">
|
||||
<span
|
||||
className={cn(
|
||||
"h-2.5 w-2.5 rounded-full",
|
||||
@ -1227,7 +1226,7 @@ export function OpenClawAssistantPane({
|
||||
setSelectedSessionKey("");
|
||||
void connectGateway("", event.target.value, { force: true });
|
||||
}}
|
||||
className="w-full rounded-full border border-[color:var(--color-surface-border)] bg-[var(--color-surface)] px-3 py-1.5 text-sm text-[var(--color-text)] outline-none transition focus:border-[color:var(--color-primary)]"
|
||||
className="tactile-control min-h-10 w-full px-3 py-2 text-sm text-[var(--color-text)] outline-none transition focus:border-[color:var(--color-primary)]"
|
||||
>
|
||||
<option value="">{copy.mainAgent}</option>
|
||||
{agents.map((agent) => (
|
||||
@ -1243,7 +1242,7 @@ export function OpenClawAssistantPane({
|
||||
)}
|
||||
|
||||
{minimalPage ? (
|
||||
<div className="ml-auto inline-flex items-center gap-2 rounded-full border border-[color:var(--color-surface-border)] bg-[var(--color-primary-muted)] px-3 py-1.5 text-xs font-semibold text-[var(--color-heading)]">
|
||||
<div className="ml-auto inline-flex min-h-10 items-center gap-2 rounded-[12px] border border-[color:var(--color-surface-border)] bg-[var(--color-primary-muted)] px-3 py-2 text-xs font-semibold text-[var(--color-heading)] shadow-[var(--shadow-soft)]">
|
||||
<span
|
||||
className={cn(
|
||||
"h-2.5 w-2.5 rounded-full",
|
||||
@ -1262,10 +1261,10 @@ export function OpenClawAssistantPane({
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
void connectGateway(undefined, undefined, { force: true });
|
||||
}}
|
||||
className="inline-flex items-center gap-2 rounded-full border border-[color:var(--color-surface-border)] px-3 py-1.5 text-xs font-semibold text-[var(--color-text)] transition hover:border-[color:var(--color-primary-border)] hover:bg-[var(--color-surface-muted)]"
|
||||
onClick={() => {
|
||||
void connectGateway(undefined, undefined, { force: true });
|
||||
}}
|
||||
className="tactile-button tactile-button-soft px-3 text-xs text-[var(--color-text)]"
|
||||
title={copy.reconnect}
|
||||
>
|
||||
{connectionState === "connecting" ? (
|
||||
@ -1279,7 +1278,7 @@ export function OpenClawAssistantPane({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.push("/panel/api")}
|
||||
className="inline-flex items-center gap-2 rounded-full border border-[color:var(--color-primary-border)] bg-[var(--color-primary-muted)] px-3 py-1.5 text-xs font-semibold text-[var(--color-primary)] transition hover:opacity-90"
|
||||
className="tactile-button tactile-button-primary border border-[color:var(--color-primary-border)] px-3 text-xs text-[var(--color-primary-foreground)]"
|
||||
title={copy.integrations}
|
||||
>
|
||||
<Settings2 className="h-3.5 w-3.5" />
|
||||
@ -1305,10 +1304,12 @@ export function OpenClawAssistantPane({
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSelectedSessionKey(session.key);
|
||||
void connectGateway(session.key, undefined, { force: true });
|
||||
void connectGateway(session.key, undefined, {
|
||||
force: true,
|
||||
});
|
||||
}}
|
||||
className={cn(
|
||||
"inline-flex items-center gap-2 rounded-full border px-2.5 py-1 text-xs transition",
|
||||
"inline-flex min-h-9 items-center gap-2 rounded-[12px] border px-3 py-1.5 text-xs transition",
|
||||
session.key === selectedSessionKey
|
||||
? "border-[color:var(--color-primary)] bg-[var(--color-primary-muted)] text-[var(--color-primary)]"
|
||||
: "border-[color:var(--color-surface-border)] bg-[var(--color-surface)] text-[var(--color-text-subtle)] hover:border-[color:var(--color-primary-border)]",
|
||||
@ -1363,7 +1364,7 @@ export function OpenClawAssistantPane({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.push("/panel/api")}
|
||||
className="inline-flex items-center gap-2 rounded-full bg-[var(--color-primary)] px-3.5 py-2 text-sm font-semibold text-[var(--color-primary-foreground)]"
|
||||
className="tactile-button tactile-button-primary px-4 text-sm"
|
||||
>
|
||||
{copy.openIntegrations}
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
@ -1392,7 +1393,7 @@ export function OpenClawAssistantPane({
|
||||
setComposerValue(action);
|
||||
textareaRef.current?.focus();
|
||||
}}
|
||||
className="rounded-full border border-[color:var(--color-surface-border)] bg-[var(--color-surface)] px-3 py-1.5 text-xs font-medium text-[var(--color-text-subtle)] transition hover:border-[color:var(--color-primary-border)] hover:text-[var(--color-primary)]"
|
||||
className="tactile-button tactile-button-subtle min-h-9 px-3 text-xs font-medium text-[var(--color-text-subtle)] hover:text-[var(--color-primary)]"
|
||||
>
|
||||
{action}
|
||||
</button>
|
||||
@ -1473,7 +1474,7 @@ export function OpenClawAssistantPane({
|
||||
type="button"
|
||||
onClick={() => setAssistantMode(option.value)}
|
||||
className={cn(
|
||||
"rounded-full px-2.5 py-1 text-xs font-semibold transition",
|
||||
"min-h-9 rounded-[12px] px-3 py-1.5 text-xs font-semibold transition",
|
||||
assistantMode === option.value
|
||||
? "bg-[var(--color-primary)] text-[var(--color-primary-foreground)]"
|
||||
: "border border-[color:var(--color-surface-border)] text-[var(--color-text-subtle)] hover:border-[color:var(--color-primary-border)]",
|
||||
@ -1483,7 +1484,7 @@ export function OpenClawAssistantPane({
|
||||
</button>
|
||||
))}
|
||||
|
||||
<div className="ml-auto flex items-center gap-2 rounded-full border border-[color:var(--color-surface-border)] bg-[var(--color-surface)] px-2.5 py-1 text-xs text-[var(--color-text-subtle)]">
|
||||
<div className="ml-auto flex min-h-9 items-center gap-2 rounded-[12px] border border-[color:var(--color-surface-border)] bg-[var(--color-surface)] px-3 py-1.5 text-xs text-[var(--color-text-subtle)]">
|
||||
<BrainCircuit className="h-3.5 w-3.5" />
|
||||
<select
|
||||
value={thinking}
|
||||
@ -1506,7 +1507,7 @@ export function OpenClawAssistantPane({
|
||||
{attachments.map((attachment) => (
|
||||
<div
|
||||
key={attachment.id}
|
||||
className="inline-flex items-center gap-2 rounded-full border border-[color:var(--color-surface-border)] bg-[var(--color-surface-muted)] px-2.5 py-1 text-xs text-[var(--color-text)]"
|
||||
className="inline-flex min-h-8 items-center gap-2 rounded-[12px] border border-[color:var(--color-surface-border)] bg-[var(--color-surface-muted)] px-3 py-1 text-xs text-[var(--color-text)]"
|
||||
>
|
||||
{attachment.type === "image" ? (
|
||||
<Camera className="h-3.5 w-3.5" />
|
||||
@ -1540,14 +1541,14 @@ export function OpenClawAssistantPane({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.push("/login")}
|
||||
className="inline-flex items-center gap-2 rounded-full bg-[var(--color-primary)] px-3 py-1.5 text-xs font-semibold text-[var(--color-primary-foreground)] transition hover:opacity-90"
|
||||
className="tactile-button tactile-button-primary px-3 text-xs"
|
||||
>
|
||||
{copy.login}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.push("/register")}
|
||||
className="inline-flex items-center gap-2 rounded-full border border-[color:var(--color-surface-border)] bg-[var(--color-surface)] px-3 py-1.5 text-xs font-semibold text-[var(--color-text)] transition hover:border-[color:var(--color-primary-border)] hover:bg-[var(--color-surface-muted)]"
|
||||
className="tactile-button tactile-button-soft px-3 text-xs text-[var(--color-text)]"
|
||||
>
|
||||
{copy.register}
|
||||
</button>
|
||||
@ -1586,7 +1587,7 @@ export function OpenClawAssistantPane({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className="inline-flex items-center gap-2 rounded-full border border-[color:var(--color-surface-border)] px-2.5 py-1 text-xs font-semibold text-[var(--color-text)] transition hover:border-[color:var(--color-primary-border)] hover:bg-[var(--color-surface-muted)]"
|
||||
className="tactile-button tactile-button-soft min-h-9 px-3 text-xs text-[var(--color-text)]"
|
||||
>
|
||||
<Paperclip className="h-3.5 w-3.5" />
|
||||
{copy.attachment}
|
||||
@ -1598,7 +1599,7 @@ export function OpenClawAssistantPane({
|
||||
void capturePage();
|
||||
}}
|
||||
disabled={isCapturing}
|
||||
className="inline-flex items-center gap-2 rounded-full border border-[color:var(--color-surface-border)] px-2.5 py-1 text-xs font-semibold text-[var(--color-text)] transition hover:border-[color:var(--color-primary-border)] hover:bg-[var(--color-surface-muted)] disabled:cursor-not-allowed disabled:opacity-60"
|
||||
className="tactile-button tactile-button-soft min-h-9 px-3 text-xs text-[var(--color-text)] disabled:cursor-not-allowed disabled:opacity-60"
|
||||
>
|
||||
{isCapturing ? (
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
@ -1629,7 +1630,7 @@ export function OpenClawAssistantPane({
|
||||
isSending ||
|
||||
(!composerValue.trim() && attachments.length === 0)
|
||||
}
|
||||
className="inline-flex items-center gap-2 rounded-full bg-[var(--color-primary)] px-3.5 py-1.5 text-sm font-semibold text-[var(--color-primary-foreground)] transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
className="tactile-button tactile-button-primary px-4 text-sm disabled:cursor-not-allowed disabled:opacity-60"
|
||||
>
|
||||
{isSending ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user