Skip to content

Commit 9ef0ea4

Browse files
committed
add new componets from coral
1 parent 3842ace commit 9ef0ea4

File tree

14 files changed

+438
-291
lines changed

14 files changed

+438
-291
lines changed

src/frontend_react/src/components/content/HomeInput.tsx

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { TaskService } from "../../services/TaskService";
1818
import { NewTaskService } from "../../services/NewTaskService";
1919
import ChatInput from "@/coral/modules/ChatInput";
2020
import InlineToaster, { useInlineToaster } from "../toast/InlineToaster";
21+
import PromptCard from "@/coral/components/PromptCard";
2122

2223
const HomeInput: React.FC<HomeInputProps> = ({
2324
onInputSubmit,
@@ -124,33 +125,13 @@ const HomeInput: React.FC<HomeInputProps> = ({
124125
</div>
125126
<div className="home-input-quick-tasks">
126127
{quickTasks.map((task) => (
127-
<Card
128+
<PromptCard
128129
key={task.id}
129-
className="home-input-quick-task-card"
130-
onMouseOver={(e) =>
131-
(e.currentTarget.style.backgroundColor =
132-
"var(--colorNeutralBackground4Hover)")
133-
}
134-
onMouseOut={(e) =>
135-
(e.currentTarget.style.backgroundColor =
136-
"var(--colorNeutralBackground3)")
137-
}
138-
onClick={
139-
!submitting ? () => handleQuickTaskClick(task) : undefined
140-
}
141-
>
142-
<div className="home-input-quick-task-content">
143-
<div className="home-input-quick-task-icon">
144-
{task.icon}
145-
</div>
146-
<div className="home-input-quick-task-text-content">
147-
<Body1Strong>{task.title}</Body1Strong>
148-
<Body1 className="home-input-quick-task-description">
149-
{task.description}
150-
</Body1>
151-
</div>
152-
</div>
153-
</Card>
130+
title={task.title}
131+
icon={task.icon}
132+
description={task.description}
133+
onClick={() => handleQuickTaskClick(task)}
134+
/>
154135
))}
155136
</div>
156137
</div>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import React from "react";
2+
3+
const CoralAccordion: React.FC<{ children: React.ReactNode }> = ({ children }) => {
4+
return <div>{children}</div>; // Just a semantic wrapper
5+
};
6+
7+
export default CoralAccordion;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { createContext, useContext } from "react";
2+
3+
export const CoralAccordionContext = createContext<{
4+
open: boolean;
5+
toggle: () => void;
6+
} | null>(null);
7+
8+
export const useCoralAccordion = () => {
9+
const ctx = useContext(CoralAccordionContext);
10+
if (!ctx) throw new Error("Must be used inside CoralAccordionItem");
11+
return ctx;
12+
};
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React from "react";
2+
import { Body1 } from "@fluentui/react-components";
3+
import { ChevronDown20Regular } from "@fluentui/react-icons";
4+
import { useCoralAccordion } from "./CoralAccordionContext";
5+
6+
type Props = {
7+
children: React.ReactNode;
8+
height?: string;
9+
chevron?: boolean;
10+
};
11+
12+
const CoralAccordionHeader: React.FC<Props> = ({
13+
children,
14+
height = "32px",
15+
chevron = false,
16+
}) => {
17+
const { open, toggle } = useCoralAccordion();
18+
19+
return (
20+
<div
21+
onClick={toggle}
22+
style={{
23+
color: "var(--colorNeutralForeground3)",
24+
padding: "0px 16px 0px 16px",
25+
backgroundColor: "transparent",
26+
cursor: "pointer",
27+
justifyContent: "space-between",
28+
display: "flex",
29+
alignItems: "center",
30+
height: '40px',
31+
}}
32+
>
33+
<Body1>{children}</Body1>
34+
{chevron && (
35+
<span
36+
style={{
37+
display: "flex",
38+
transition: "transform 0.25s ease",
39+
transform: open ? "rotate(-180deg)" : "rotate(0deg)",
40+
}}
41+
>
42+
<ChevronDown20Regular />
43+
</span>
44+
)}
45+
</div>
46+
);
47+
};
48+
49+
CoralAccordionHeader.displayName = "CoralAccordionHeader";
50+
export default CoralAccordionHeader;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import React, { useState, cloneElement, isValidElement } from "react";
2+
import { CoralAccordionContext } from "./CoralAccordionContext";
3+
4+
type CoralAccordionItemProps = {
5+
defaultOpen?: boolean;
6+
children: React.ReactNode;
7+
};
8+
9+
const CoralAccordionItem: React.FC<CoralAccordionItemProps> = ({
10+
defaultOpen = false,
11+
children,
12+
}) => {
13+
const [open, setOpen] = useState(defaultOpen);
14+
const toggle = () => setOpen((prev) => !prev);
15+
16+
return (
17+
<CoralAccordionContext.Provider value={{ open, toggle }}>
18+
{React.Children.map(children, (child) => {
19+
if (!isValidElement(child)) return child;
20+
21+
const type =
22+
(child.type as any)?.displayName || (child.type as any)?.name || "";
23+
24+
// Show CoralAccordionPanel only if open
25+
if (type === "CoralAccordionPanel") {
26+
return open ? child : null;
27+
}
28+
29+
// Render all other children (e.g., CoralAccordionHeader)
30+
return child;
31+
})}
32+
</CoralAccordionContext.Provider>
33+
);
34+
};
35+
36+
export default CoralAccordionItem;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import React from "react";
2+
3+
const CoralAccordionPanel: React.FC<{ children: React.ReactNode }> = ({ children }) => {
4+
return <div style={{ margin: "8px" }}>{children}</div>;
5+
};
6+
7+
export default CoralAccordionPanel;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import React from "react";
2+
3+
const PanelFooter: React.FC<{ children: React.ReactNode }> = ({ children }) => {
4+
return (
5+
<div
6+
style={{
7+
padding: "24px 16px",
8+
width:'100%'
9+
}}
10+
>
11+
{children}
12+
</div>
13+
);
14+
};
15+
16+
PanelFooter.displayName = "PanelFooter";
17+
18+
export default PanelFooter;

src/frontend_react/src/coral/components/Panels/PanelLeft.tsx

Lines changed: 20 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1-
import React, { useState, useEffect, ReactNode, ReactElement } from "react";
2-
import PanelToolbar from "./PanelLeftToolbar.js"; // Import to identify toolbar
1+
import React, {
2+
useState,
3+
useEffect,
4+
ReactNode,
5+
ReactElement,
6+
isValidElement,
7+
} from "react";
8+
import PanelToolbar from "./PanelLeftToolbar.js";
9+
import PanelFooter from "./PanelFooter"; // 👈 new
310
import {
411
Avatar,
512
Body1,
@@ -43,24 +50,27 @@ const PanelLeft: React.FC<PanelLeftProps> = ({
4350
const onMouseUp = () => {
4451
document.removeEventListener("mousemove", onMouseMove);
4552
document.removeEventListener("mouseup", onMouseUp);
46-
47-
// Re-enable text selection
4853
document.body.style.userSelect = "";
4954
};
5055

5156
document.addEventListener("mousemove", onMouseMove);
5257
document.addEventListener("mouseup", onMouseUp);
53-
54-
// Disable text selection
5558
document.body.style.userSelect = "none";
5659
};
5760

5861
const childrenArray = React.Children.toArray(children) as ReactElement[];
5962
const toolbar = childrenArray.find(
60-
(child) => React.isValidElement(child) && child.type === PanelToolbar
63+
(child) => isValidElement(child) && child.type === PanelToolbar
64+
);
65+
const footer = childrenArray.find(
66+
(child) => isValidElement(child) && child.type === PanelFooter
6167
);
6268
const content = childrenArray.filter(
63-
(child) => !(React.isValidElement(child) && child.type === PanelToolbar)
69+
(child) =>
70+
!(
71+
isValidElement(child) &&
72+
(child.type === PanelToolbar || child.type === PanelFooter)
73+
)
6474
);
6575

6676
return (
@@ -90,12 +100,13 @@ const PanelLeft: React.FC<PanelLeftProps> = ({
90100
overflowY: "auto",
91101
overflowX: "hidden",
92102
boxSizing: "border-box",
93-
// padding: "16px",
94103
}}
95104
>
96105
{content}
97106
</div>
98107

108+
{footer && <div>{footer}</div>}
109+
99110
{panelResize && (
100111
<div
101112
className="resizeHandle"
@@ -116,28 +127,6 @@ const PanelLeft: React.FC<PanelLeftProps> = ({
116127
}}
117128
/>
118129
)}
119-
120-
<div
121-
style={{
122-
display: "flex",
123-
padding: "24px 16px",
124-
gap: "12px",
125-
alignItems: "center",
126-
}}
127-
>
128-
<Avatar
129-
name="Pepper Hayuki"
130-
image={{
131-
src: Human,
132-
}}
133-
/>
134-
<div style={{ display: "flex", flexDirection: "column" }}>
135-
<Body1Strong>Pepper Hayuki</Body1Strong>
136-
<Caption1 style={{ color: "var(--colorNeutralForeground3)" }}>
137-
138-
</Caption1>
139-
</div>
140-
</div>
141130
</div>
142131
);
143132
};
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import React from "react";
2+
import {
3+
Avatar,
4+
AvatarProps,
5+
Body1Strong,
6+
Caption1,
7+
} from "@fluentui/react-components";
8+
9+
interface PanelUserCardProps extends AvatarProps {
10+
name: string; // required for both Avatar and label
11+
alias?: string; // optional sub-label
12+
}
13+
14+
const PanelUserCard: React.FC<PanelUserCardProps> = ({ name, alias, ...avatarProps }) => {
15+
return (
16+
<div
17+
style={{
18+
display: "flex",
19+
gap: "12px",
20+
alignItems: "center",
21+
}}
22+
>
23+
<Avatar
24+
name={name}
25+
{...avatarProps}
26+
/>
27+
<div style={{ display: "flex", flexDirection: "column" }}>
28+
<Body1Strong>{name}</Body1Strong>
29+
{alias && (
30+
<Caption1 style={{ color: "var(--colorNeutralForeground3)" }}>
31+
{alias}
32+
</Caption1>
33+
)}
34+
</div>
35+
</div>
36+
);
37+
};
38+
39+
export default PanelUserCard;
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// PromptCard.tsx
2+
import React from "react";
3+
import { Card } from "@fluentui/react-components";
4+
import { Body1, Body1Strong } from "@fluentui/react-components";
5+
6+
type PromptCardProps = {
7+
title: string;
8+
description: string;
9+
icon?: React.ReactNode;
10+
onClick?: () => void;
11+
};
12+
13+
const PromptCard: React.FC<PromptCardProps> = ({
14+
title,
15+
description,
16+
icon,
17+
onClick,
18+
}) => {
19+
return (
20+
<Card
21+
onClick={onClick}
22+
style={{
23+
flex: "1",
24+
display: "flex",
25+
flexDirection: "column",
26+
padding: "16px",
27+
backgroundColor: "var(--colorNeutralBackground3)",
28+
border: "1px solid var(--colorNeutralStroke2)",
29+
borderRadius: "8px",
30+
cursor: "pointer",
31+
boxShadow: "none",
32+
transition: "background-color 0.2s ease-in-out",
33+
}}
34+
onMouseOver={(e) =>
35+
(e.currentTarget.style.backgroundColor =
36+
"var(--colorNeutralBackground4Hover)")
37+
}
38+
onMouseOut={(e) =>
39+
(e.currentTarget.style.backgroundColor =
40+
"var(--colorNeutralBackground3)")
41+
}
42+
>
43+
<div style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
44+
{icon && (
45+
<div
46+
style={{
47+
fontSize: "20px",
48+
color: "var(--colorBrandForeground1)",
49+
marginTop: "2px",
50+
}}
51+
>
52+
{icon}
53+
</div>
54+
)}
55+
<div style={{ display: "flex", flexDirection: "column", gap: "4px" }}>
56+
<Body1Strong>{title}</Body1Strong>
57+
<Body1 style={{ color: "var(--colorNeutralForeground3)" }}>
58+
{description}
59+
</Body1>
60+
</div>
61+
</div>
62+
</Card>
63+
);
64+
};
65+
66+
export default PromptCard;

0 commit comments

Comments
 (0)