feat(history): Blocks icon for projects tab; align padding; task title overflow

- Add animate Blocks icon and use for History projects tab
- History page horizontal padding px-20 to px-16
- Remove redundant break-words on task card title (overflow-wrap:anywhere)

Made-with: Cursor
This commit is contained in:
Douglas 2026-04-23 12:39:10 +01:00
parent d38baee698
commit debd59ba4e
4 changed files with 179 additions and 5 deletions

View file

@ -507,7 +507,7 @@ export function TaskCard({
</div>
<div className="min-w-0 flex flex-1 flex-col items-start justify-center">
<div
className={`min-w-0 w-full [overflow-wrap:anywhere] break-words whitespace-pre-line ${
className={`min-w-0 w-full [overflow-wrap:anywhere] whitespace-pre-line ${
task.status === TaskStatus.FAILED
? 'text-ds-text-caution-default-default'
: task.status === TaskStatus.BLOCKED

View file

@ -12,13 +12,13 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
import { Blocks } from '@/components/ui/animate-ui/icons/blocks';
import { Bot } from '@/components/ui/animate-ui/icons/bot';
import { Compass } from '@/components/ui/animate-ui/icons/compass';
import { Hammer } from '@/components/ui/animate-ui/icons/hammer';
import { AnimateIcon } from '@/components/ui/animate-ui/icons/icon';
import { Radio } from '@/components/ui/animate-ui/icons/radio';
import { Settings } from '@/components/ui/animate-ui/icons/settings';
import { Sparkle } from '@/components/ui/animate-ui/icons/sparkle';
import { cn } from '@/lib/utils';
import { motion } from 'framer-motion';
import type { ReactNode } from 'react';
@ -47,7 +47,7 @@ type TabConfig = {
};
const HISTORY_TABS: TabConfig[] = [
{ id: 'projects', icon: <Sparkle />, iconAnimateOnHover: 'wiggle' },
{ id: 'projects', icon: <Blocks />, iconAnimateOnHover: 'default' },
{ id: 'agents', icon: <Bot />, iconAnimateOnHover: 'default' },
{ id: 'channels', icon: <Radio />, iconAnimateOnHover: 'default' },
{ id: 'connectors', icon: <Hammer />, iconAnimateOnHover: 'default' },

View file

@ -0,0 +1,174 @@
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
'use client';
import { motion, type Variants } from 'motion/react';
import {
getVariants,
IconWrapper,
useAnimateIconContext,
type IconProps,
} from '@/components/ui/animate-ui/icons/icon';
type BlocksProps = IconProps<keyof typeof animations>;
const animations = {
default: {
path1: {
initial: {
x: 0,
y: 0,
d: 'M10 22V7c0-.6-.4-1-1-1H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-5c0-.6-.4-1-1-1H2',
strokeLinejoin: 'round',
transition: {
duration: 0.4,
ease: 'easeInOut',
},
},
animate: {
x: 2,
y: -2,
d: 'M10 22V6H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-6H2',
strokeLinejoin: 'miter',
transition: {
duration: 0.4,
ease: 'easeInOut',
d: { duration: 0, delay: 0.3 },
strokeLinejoin: { duration: 0, delay: 0.3 },
},
},
},
path2: {
initial: {
x: 0,
y: 0,
d: 'M15 2 H21 A1 1 0 0 1 22 3 V9 A1 1 0 0 1 21 10 H15 A1 1 0 0 1 14 9 V3 A1 1 0 0 1 15 2 Z',
transition: {
duration: 0.4,
ease: 'easeInOut',
},
},
animate: {
x: -2,
y: 2,
d: 'M15 2 H20 A2 2 0 0 1 22 4 V9 A1 1 0 0 1 21 10 H15 A1 1 0 0 1 14 9 V3 A1 1 0 0 1 15 2 Z',
transition: {
duration: 0.4,
ease: 'easeInOut',
},
},
},
} satisfies Record<string, Variants>,
'default-loop': {
path1: {
initial: {
x: 0,
y: 0,
d: 'M10 22V7c0-.6-.4-1-1-1H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-5c0-.6-.4-1-1-1H2',
strokeLinejoin: 'round',
transition: {
duration: 0.4,
ease: 'easeInOut',
},
},
animate: {
x: [0, 2, 0],
y: [0, -2, 0],
d: [
'M10 22V7c0-.6-.4-1-1-1H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-5c0-.6-.4-1-1-1H2',
'M10 22V6H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-6H2',
'M10 22V7c0-.6-.4-1-1-1H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-5c0-.6-.4-1-1-1H2',
],
strokeLinejoin: ['round', 'miter', 'round'],
transition: {
duration: 0.8,
ease: 'easeInOut',
d: { duration: 0, delay: 0.3 },
strokeLinejoin: { duration: 0, delay: 0.3 },
},
},
},
path2: {
initial: {
x: 0,
y: 0,
d: 'M15 2 H21 A1 1 0 0 1 22 3 V9 A1 1 0 0 1 21 10 H15 A1 1 0 0 1 14 9 V3 A1 1 0 0 1 15 2 Z',
transition: {
duration: 0.4,
ease: 'easeInOut',
},
},
animate: {
x: [0, -2, 0],
y: [0, 2, 0],
d: [
'M15 2 H21 A1 1 0 0 1 22 3 V9 A1 1 0 0 1 21 10 H15 A1 1 0 0 1 14 9 V3 A1 1 0 0 1 15 2 Z',
'M15 2 H20 A2 2 0 0 1 22 4 V9 A1 1 0 0 1 21 10 H15 A1 1 0 0 1 14 9 V3 A1 1 0 0 1 15 2 Z',
'M15 2 H21 A1 1 0 0 1 22 3 V9 A1 1 0 0 1 21 10 H15 A1 1 0 0 1 14 9 V3 A1 1 0 0 1 15 2 Z',
],
transition: {
duration: 0.8,
ease: 'easeInOut',
},
},
},
} satisfies Record<string, Variants>,
} as const;
function IconComponent({ size, ...props }: BlocksProps) {
const { controls } = useAnimateIconContext();
const variants = getVariants(animations);
return (
<motion.svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<motion.path
d="M10 22V7c0-.6-.4-1-1-1H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-5c0-.6-.4-1-1-1H2"
variants={variants.path1}
initial="initial"
animate={controls}
/>
<motion.path
d="M15 2 H21 A1 1 0 0 1 22 3 V9 A1 1 0 0 1 21 10 H15 A1 1 0 0 1 14 9 V3 A1 1 0 0 1 15 2 Z"
variants={variants.path2}
initial="initial"
animate={controls}
/>
</motion.svg>
);
}
function Blocks(props: BlocksProps) {
return <IconWrapper icon={IconComponent} {...props} />;
}
export {
animations,
Blocks,
Blocks as BlocksIcon,
type BlocksProps as BlocksIconProps,
type BlocksProps,
};

View file

@ -123,7 +123,7 @@ export default function Home() {
cancelText={t('layout.cancel')}
/>
{/* welcome text */}
<div className="from-ds-bg-neutral-default-default to-ds-bg-neutral-default-default px-20 pt-16 flex w-full flex-row bg-gradient-to-b">
<div className="from-ds-bg-neutral-default-default to-ds-bg-neutral-default-default px-16 pt-16 flex w-full flex-row bg-gradient-to-b">
<p className="m-0 gap-2 inline-flex flex-wrap items-baseline">
<WordCarousel
words={[t(timeGreetingKey)]}
@ -141,7 +141,7 @@ export default function Home() {
{/* Navbar */}
{/* -top-px avoids a visible hairline: at top-0 subpixel rounding can leave a gap; */}
<div
className={`border-ds-border-neutral-subtle-disabled bg-ds-bg-neutral-default-default pl-20 pr-4 pt-10 sticky -top-px z-20 flex flex-col items-center justify-between border-x-0 border-t-0 border-b-1 border-solid`}
className={`border-ds-border-neutral-subtle-disabled bg-ds-bg-neutral-default-default px-16 pt-10 sticky -top-px z-20 flex flex-col items-center justify-between border-x-0 border-t-0 border-b-1 border-solid`}
>
<div className="mx-auto flex w-full flex-row items-center justify-between">
<div className="gap-2 flex items-center">