@pcoi/components 0.1.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +52 -0
  3. package/dist/index.d.ts +826 -568
  4. package/dist/index.js +3517 -2
  5. package/dist/styles.css +2 -0
  6. package/package.json +36 -32
  7. package/dist/components.css +0 -1
  8. package/dist/index.mjs +0 -969
  9. package/src/Badge/Badge.css +0 -40
  10. package/src/Badge/Badge.tsx +0 -36
  11. package/src/Badge/index.ts +0 -2
  12. package/src/Button/Button.css +0 -93
  13. package/src/Button/Button.figma.tsx +0 -27
  14. package/src/Button/Button.test.tsx +0 -32
  15. package/src/Button/Button.tsx +0 -47
  16. package/src/Button/index.ts +0 -1
  17. package/src/Callout/Callout.css +0 -43
  18. package/src/Callout/Callout.figma.tsx +0 -25
  19. package/src/Callout/Callout.tsx +0 -39
  20. package/src/Callout/index.ts +0 -1
  21. package/src/Card/Card.css +0 -88
  22. package/src/Card/Card.figma.tsx +0 -28
  23. package/src/Card/Card.tsx +0 -60
  24. package/src/Card/index.ts +0 -1
  25. package/src/ChatInterface/ChatInterface.css +0 -49
  26. package/src/ChatInterface/ChatInterface.integration.test.tsx +0 -123
  27. package/src/ChatInterface/ChatInterface.tsx +0 -120
  28. package/src/ChatInterface/index.ts +0 -6
  29. package/src/ChatMessage/ChatMessage.css +0 -55
  30. package/src/ChatMessage/ChatMessage.tsx +0 -71
  31. package/src/ChatMessage/index.ts +0 -2
  32. package/src/ChatMessageList/ChatMessageList.css +0 -24
  33. package/src/ChatMessageList/ChatMessageList.test.tsx +0 -74
  34. package/src/ChatMessageList/ChatMessageList.tsx +0 -51
  35. package/src/ChatMessageList/index.ts +0 -2
  36. package/src/Checkbox/Checkbox.css +0 -97
  37. package/src/Checkbox/Checkbox.tsx +0 -70
  38. package/src/Checkbox/index.ts +0 -2
  39. package/src/CitationMark/CitationMark.css +0 -40
  40. package/src/CitationMark/CitationMark.tsx +0 -38
  41. package/src/CitationMark/index.ts +0 -2
  42. package/src/CitedExcerpt/CitedExcerpt.css +0 -75
  43. package/src/CitedExcerpt/CitedExcerpt.tsx +0 -51
  44. package/src/CitedExcerpt/index.ts +0 -2
  45. package/src/ComparisonTable/ComparisonTable.css +0 -66
  46. package/src/ComparisonTable/ComparisonTable.tsx +0 -48
  47. package/src/ComparisonTable/index.ts +0 -1
  48. package/src/ContactForm/ContactForm.css +0 -38
  49. package/src/ContactForm/ContactForm.tsx +0 -57
  50. package/src/ContactForm/index.ts +0 -1
  51. package/src/DataTable/DataTable.css +0 -56
  52. package/src/DataTable/DataTable.tsx +0 -104
  53. package/src/DataTable/index.ts +0 -2
  54. package/src/DocumentOverlay/DocumentOverlay.css +0 -57
  55. package/src/DocumentOverlay/DocumentOverlay.test.tsx +0 -95
  56. package/src/DocumentOverlay/DocumentOverlay.tsx +0 -86
  57. package/src/DocumentOverlay/index.ts +0 -2
  58. package/src/Footer/Footer.css +0 -72
  59. package/src/Footer/Footer.tsx +0 -56
  60. package/src/Footer/index.ts +0 -1
  61. package/src/FormField/FormField.css +0 -78
  62. package/src/FormField/FormField.figma.tsx +0 -28
  63. package/src/FormField/FormField.tsx +0 -103
  64. package/src/FormField/index.ts +0 -2
  65. package/src/HowStep/HowStep.css +0 -48
  66. package/src/HowStep/HowStep.figma.tsx +0 -23
  67. package/src/HowStep/HowStep.tsx +0 -38
  68. package/src/HowStep/index.ts +0 -1
  69. package/src/LogoMark/LogoMark.css +0 -16
  70. package/src/LogoMark/LogoMark.tsx +0 -24
  71. package/src/LogoMark/index.ts +0 -2
  72. package/src/Modal/Modal.css +0 -101
  73. package/src/Modal/Modal.figma.tsx +0 -28
  74. package/src/Modal/Modal.test.tsx +0 -46
  75. package/src/Modal/Modal.tsx +0 -139
  76. package/src/Modal/index.ts +0 -2
  77. package/src/Nav/Nav.css +0 -161
  78. package/src/Nav/Nav.tsx +0 -101
  79. package/src/Nav/index.ts +0 -1
  80. package/src/Panel/Panel.css +0 -35
  81. package/src/Panel/Panel.tsx +0 -61
  82. package/src/Panel/index.ts +0 -2
  83. package/src/PromptBar/PromptBar.css +0 -68
  84. package/src/PromptBar/PromptBar.figma.tsx +0 -25
  85. package/src/PromptBar/PromptBar.test.tsx +0 -83
  86. package/src/PromptBar/PromptBar.tsx +0 -93
  87. package/src/PromptBar/index.ts +0 -2
  88. package/src/RadioGroup/RadioGroup.css +0 -117
  89. package/src/RadioGroup/RadioGroup.tsx +0 -112
  90. package/src/RadioGroup/index.ts +0 -2
  91. package/src/SectionHeader/SectionHeader.css +0 -38
  92. package/src/SectionHeader/SectionHeader.figma.tsx +0 -23
  93. package/src/SectionHeader/SectionHeader.tsx +0 -55
  94. package/src/SectionHeader/index.ts +0 -1
  95. package/src/Select/Select.css +0 -90
  96. package/src/Select/Select.figma.tsx +0 -33
  97. package/src/Select/Select.tsx +0 -100
  98. package/src/Select/index.ts +0 -2
  99. package/src/SignalsPanel/SignalsPanel.css +0 -51
  100. package/src/SignalsPanel/SignalsPanel.tsx +0 -33
  101. package/src/SignalsPanel/index.ts +0 -1
  102. package/src/SuggestionCard/SuggestionCard.css +0 -51
  103. package/src/SuggestionCard/SuggestionCard.tsx +0 -34
  104. package/src/SuggestionCard/index.ts +0 -2
  105. package/src/SuggestionCards/SuggestionCards.css +0 -15
  106. package/src/SuggestionCards/SuggestionCards.test.tsx +0 -27
  107. package/src/SuggestionCards/SuggestionCards.tsx +0 -40
  108. package/src/SuggestionCards/index.ts +0 -2
  109. package/src/Toast/Toast.css +0 -85
  110. package/src/Toast/Toast.tsx +0 -77
  111. package/src/Toast/index.ts +0 -2
  112. package/src/Toggle/Toggle.css +0 -110
  113. package/src/Toggle/Toggle.figma.tsx +0 -24
  114. package/src/Toggle/Toggle.tsx +0 -73
  115. package/src/Toggle/index.ts +0 -2
  116. package/src/TypingIndicator/TypingIndicator.css +0 -70
  117. package/src/TypingIndicator/TypingIndicator.tsx +0 -37
  118. package/src/TypingIndicator/index.ts +0 -2
  119. package/src/index.ts +0 -37
  120. package/src/styles/utilities.css +0 -14
  121. package/src/styles.css +0 -32
  122. package/src/types.ts +0 -65
@@ -1,123 +0,0 @@
1
- import React, { useState } from "react";
2
- import { fireEvent, render, screen, waitFor } from "@testing-library/react";
3
- import { describe, expect, it, vi } from "vitest";
4
- import type { Citation } from "../types";
5
- import { DocumentOverlay } from "../DocumentOverlay/DocumentOverlay";
6
- import { ChatInterface, type ChatInterfaceMessage } from "./ChatInterface";
7
-
8
- const citation: Citation = {
9
- index: 1,
10
- excerpt: "Structured capture improves onboarding outcomes.",
11
- sourceTitle: "Knowledge Report",
12
- sourceId: "doc-1",
13
- };
14
-
15
- const messages: ChatInterfaceMessage[] = [
16
- {
17
- id: "m-1",
18
- role: "assistant",
19
- content: "Structured capture improves onboarding outcomes.",
20
- citations: [citation],
21
- timestamp: "2:35 PM",
22
- },
23
- ];
24
-
25
- function ChatCitationHarness() {
26
- const [overlayOpen, setOverlayOpen] = useState(false);
27
- const [activeCitation, setActiveCitation] = useState<Citation | null>(null);
28
-
29
- const handleCitationClick = (clickedCitation: Citation) => {
30
- setActiveCitation(clickedCitation);
31
- setOverlayOpen(true);
32
- };
33
-
34
- const doc = activeCitation
35
- ? {
36
- title: "Knowledge Retention Report",
37
- sourceLabel: "Internal Research",
38
- highlightId: "cite-1",
39
- content: (
40
- <>
41
- <p id="cite-1">Structured capture improves onboarding outcomes.</p>
42
- <p>Additional context.</p>
43
- </>
44
- ),
45
- }
46
- : null;
47
-
48
- return (
49
- <>
50
- <ChatInterface
51
- messages={messages}
52
- promptValue=""
53
- onPromptChange={() => {}}
54
- onPromptSubmit={() => {}}
55
- onCitationClick={handleCitationClick}
56
- />
57
- {doc && (
58
- <DocumentOverlay
59
- open={overlayOpen}
60
- onClose={() => {
61
- setOverlayOpen(false);
62
- setActiveCitation(null);
63
- }}
64
- title={doc.title}
65
- sourceLabel={doc.sourceLabel}
66
- highlightId={doc.highlightId}
67
- highlightIndex={activeCitation?.index}
68
- >
69
- {doc.content}
70
- </DocumentOverlay>
71
- )}
72
- </>
73
- );
74
- }
75
-
76
- describe("ChatInterface integration", () => {
77
- it("opens document overlay with highlighted citation content", async () => {
78
- if (!(globalThis.CSS && typeof globalThis.CSS.escape === "function")) {
79
- const existing = globalThis.CSS ?? ({} as CSS);
80
- globalThis.CSS = {
81
- ...existing,
82
- escape: (value: string) => value,
83
- } as CSS;
84
- }
85
-
86
- const raf = vi
87
- .spyOn(window, "requestAnimationFrame")
88
- .mockImplementation((cb: FrameRequestCallback) => {
89
- cb(0);
90
- return 1;
91
- });
92
-
93
- const scrollIntoView = vi.fn();
94
- Object.defineProperty(window.HTMLElement.prototype, "scrollIntoView", {
95
- configurable: true,
96
- value: scrollIntoView,
97
- });
98
-
99
- render(<ChatCitationHarness />);
100
-
101
- fireEvent.click(screen.getByRole("button", { name: "Knowledge Report" }));
102
-
103
- expect(screen.getByRole("dialog")).toBeInTheDocument();
104
- expect(screen.getByText("Knowledge Retention Report")).toBeInTheDocument();
105
- expect(screen.getByText("Internal Research")).toBeInTheDocument();
106
-
107
- const highlighted = document.querySelector("#cite-1");
108
- if (!highlighted) {
109
- throw new Error("Expected highlighted citation element to exist");
110
- }
111
-
112
- await waitFor(() => {
113
- expect(highlighted).toHaveClass("pcoi-doc-overlay__highlight");
114
- expect(highlighted).toHaveAttribute("data-highlight-index", "[1]");
115
- expect(scrollIntoView).toHaveBeenCalled();
116
- });
117
-
118
- fireEvent.click(screen.getByRole("button", { name: "Close modal" }));
119
- expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
120
-
121
- raf.mockRestore();
122
- });
123
- });
@@ -1,120 +0,0 @@
1
- import React from "react";
2
- import { ChatMessageList } from "../ChatMessageList/ChatMessageList";
3
- import { ChatMessage } from "../ChatMessage/ChatMessage";
4
- import { SuggestionCards } from "../SuggestionCards/SuggestionCards";
5
- import { PromptBar } from "../PromptBar/PromptBar";
6
- import { TypingIndicator } from "../TypingIndicator/TypingIndicator";
7
- import type { ChatMessageRole, Citation, Suggestion } from "../types";
8
-
9
- export interface ChatInterfaceMessage {
10
- id: string;
11
- role: ChatMessageRole;
12
- content: React.ReactNode;
13
- citations?: Citation[];
14
- timestamp?: string;
15
- }
16
-
17
- export interface ChatInterfaceProps
18
- extends React.HTMLAttributes<HTMLDivElement> {
19
- /** Conversation messages */
20
- messages: ChatInterfaceMessage[];
21
- /** Suggestion prompts for the empty state */
22
- suggestions?: Suggestion[];
23
- /** Current prompt textarea value */
24
- promptValue: string;
25
- /** Called when the prompt textarea changes */
26
- onPromptChange: (value: string) => void;
27
- /** Called when the user submits a prompt */
28
- onPromptSubmit: (value: string) => void;
29
- /** Called when a suggestion card is clicked */
30
- onSuggestionSelect?: (suggestion: Suggestion) => void;
31
- /** Called when a citation is clicked */
32
- onCitationClick?: (citation: Citation) => void;
33
- /** Textarea placeholder */
34
- placeholder?: string;
35
- /** Disable input while loading */
36
- loading?: boolean;
37
- }
38
-
39
- /**
40
- * PCOI ChatInterface — Top-level chat composition
41
- * Empty state: suggestions + prompt. Active state: messages + prompt.
42
- * Tokens: bg/default, container/narrow, spacing-16/24
43
- */
44
- export const ChatInterface = React.forwardRef<
45
- HTMLDivElement,
46
- ChatInterfaceProps
47
- >(
48
- (
49
- {
50
- messages,
51
- suggestions,
52
- promptValue,
53
- onPromptChange,
54
- onPromptSubmit,
55
- onSuggestionSelect,
56
- onCitationClick,
57
- placeholder,
58
- loading = false,
59
- className = "",
60
- ...rest
61
- },
62
- ref
63
- ) => {
64
- const isEmpty = messages.length === 0;
65
-
66
- const classes = [
67
- "pcoi-chat",
68
- isEmpty ? "pcoi-chat--empty" : "",
69
- className,
70
- ]
71
- .filter(Boolean)
72
- .join(" ");
73
-
74
- return (
75
- <div ref={ref} className={classes} {...rest}>
76
- {isEmpty ? (
77
- <div className="pcoi-chat__empty-state">
78
- {suggestions && suggestions.length > 0 && onSuggestionSelect && (
79
- <div className="pcoi-chat__suggestions">
80
- <SuggestionCards
81
- suggestions={suggestions}
82
- onSelect={onSuggestionSelect}
83
- />
84
- </div>
85
- )}
86
- </div>
87
- ) : (
88
- <ChatMessageList className="pcoi-chat__messages">
89
- {messages.map((msg) => (
90
- <ChatMessage
91
- key={msg.id}
92
- role={msg.role}
93
- citations={msg.citations}
94
- timestamp={msg.timestamp}
95
- onCitationClick={onCitationClick}
96
- >
97
- {msg.content}
98
- </ChatMessage>
99
- ))}
100
- {loading && <TypingIndicator />}
101
- </ChatMessageList>
102
- )}
103
-
104
- <div className="pcoi-chat__prompt">
105
- <PromptBar
106
- value={promptValue}
107
- onChange={onPromptChange}
108
- onSubmit={onPromptSubmit}
109
- placeholder={placeholder}
110
- loading={loading}
111
- disabled={loading}
112
- />
113
- </div>
114
- </div>
115
- );
116
- }
117
- );
118
-
119
- ChatInterface.displayName = "ChatInterface";
120
- export default ChatInterface;
@@ -1,6 +0,0 @@
1
- export {
2
- ChatInterface,
3
- type ChatInterfaceProps,
4
- type ChatInterfaceMessage,
5
- } from "./ChatInterface";
6
- export { default } from "./ChatInterface";
@@ -1,55 +0,0 @@
1
- /* ChatMessage — @pcoi/components */
2
-
3
- .pcoi-chat-message {
4
- display: flex;
5
- flex-direction: column;
6
- gap: var(--pcoi-semantic-spacing-inline-sm);
7
- padding: var(--pcoi-semantic-spacing-stack-md);
8
- border-radius: var(--pcoi-semantic-radius-card);
9
- border: 1px solid var(--pcoi-semantic-border-card);
10
- }
11
-
12
- /* ── Role variants ── */
13
- .pcoi-chat-message--user {
14
- background: var(--pcoi-semantic-bg-surface);
15
- }
16
-
17
- .pcoi-chat-message--assistant {
18
- background: var(--pcoi-semantic-surface-accent-dim);
19
- }
20
-
21
- .pcoi-chat-message__header {
22
- display: flex;
23
- align-items: center;
24
- }
25
-
26
- .pcoi-chat-message__body {
27
- font-family: var(--pcoi-semantic-type-body-font);
28
- font-size: var(--pcoi-semantic-type-body-size);
29
- line-height: var(--pcoi-semantic-type-body-line-height);
30
- color: var(--pcoi-semantic-text-primary);
31
- }
32
-
33
- .pcoi-chat-message__citations {
34
- display: flex;
35
- flex-direction: column;
36
- gap: var(--pcoi-semantic-spacing-inline-sm);
37
- margin-top: var(--pcoi-semantic-spacing-inline-2xs);
38
- }
39
-
40
- .pcoi-chat-message__timestamp {
41
- font-family: var(--pcoi-semantic-type-body-font);
42
- font-size: var(--pcoi-semantic-type-body-sm-size);
43
- color: var(--pcoi-semantic-text-muted);
44
- }
45
-
46
- /* ── Responsive: offset margins on desktop ── */
47
- @media (min-width: 1025px) {
48
- .pcoi-chat-message--user {
49
- margin-left: var(--pcoi-semantic-spacing-inline-xl);
50
- }
51
-
52
- .pcoi-chat-message--assistant {
53
- margin-right: var(--pcoi-semantic-spacing-inline-xl);
54
- }
55
- }
@@ -1,71 +0,0 @@
1
- import React from "react";
2
- import { Badge } from "../Badge/Badge";
3
- import { CitedExcerpt } from "../CitedExcerpt/CitedExcerpt";
4
- import type { ChatMessageRole, Citation } from "../types";
5
-
6
- export interface ChatMessageProps
7
- extends React.HTMLAttributes<HTMLDivElement> {
8
- /** Message sender role */
9
- role: ChatMessageRole;
10
- /** Message body content */
11
- children: React.ReactNode;
12
- /** Citations referenced in the message */
13
- citations?: Citation[];
14
- /** Displayed below the message body */
15
- timestamp?: string;
16
- /** Called when a citation marker or source link is clicked */
17
- onCitationClick?: (citation: Citation) => void;
18
- }
19
-
20
- /**
21
- * PCOI ChatMessage — User or assistant message bubble
22
- * Tokens: surface/elevated (user), bg/card (assistant), border/card,
23
- * radius-md, text/primary, text/muted, type/body-size
24
- */
25
- export const ChatMessage = React.forwardRef<HTMLDivElement, ChatMessageProps>(
26
- (
27
- { role, children, citations, timestamp, onCitationClick, className = "", ...rest },
28
- ref
29
- ) => {
30
- const classes = [
31
- "pcoi-chat-message",
32
- `pcoi-chat-message--${role}`,
33
- className,
34
- ]
35
- .filter(Boolean)
36
- .join(" ");
37
-
38
- return (
39
- <div ref={ref} className={classes} {...rest}>
40
- <div className="pcoi-chat-message__header">
41
- <Badge variant={role === "assistant" ? "default" : "info"}>
42
- {role === "assistant" ? "PCOI" : "You"}
43
- </Badge>
44
- </div>
45
-
46
- <div className="pcoi-chat-message__body">{children}</div>
47
-
48
- {citations && citations.length > 0 && (
49
- <div className="pcoi-chat-message__citations">
50
- {citations.map((c) => (
51
- <CitedExcerpt
52
- key={c.index}
53
- index={c.index}
54
- excerpt={c.excerpt}
55
- sourceTitle={c.sourceTitle}
56
- onSourceClick={() => onCitationClick?.(c)}
57
- />
58
- ))}
59
- </div>
60
- )}
61
-
62
- {timestamp && (
63
- <time className="pcoi-chat-message__timestamp">{timestamp}</time>
64
- )}
65
- </div>
66
- );
67
- }
68
- );
69
-
70
- ChatMessage.displayName = "ChatMessage";
71
- export default ChatMessage;
@@ -1,2 +0,0 @@
1
- export { ChatMessage, type ChatMessageProps } from "./ChatMessage";
2
- export { default } from "./ChatMessage";
@@ -1,24 +0,0 @@
1
- /* ChatMessageList — @pcoi/components */
2
-
3
- .pcoi-chat-message-list {
4
- flex: 1;
5
- min-height: 0;
6
- overflow: hidden;
7
- }
8
-
9
- .pcoi-chat-message-list__inner {
10
- height: 100%;
11
- overflow-y: auto;
12
- display: flex;
13
- flex-direction: column;
14
- gap: var(--pcoi-semantic-spacing-stack-md);
15
- padding: var(--pcoi-semantic-spacing-panel-padding-sm);
16
- }
17
-
18
- /* ── Responsive: wider spacing on tablet+ ── */
19
- @media (min-width: 768px) {
20
- .pcoi-chat-message-list__inner {
21
- gap: var(--pcoi-semantic-spacing-card-gap);
22
- padding: var(--pcoi-semantic-spacing-panel-padding);
23
- }
24
- }
@@ -1,74 +0,0 @@
1
- import { fireEvent, render, screen, waitFor } from "@testing-library/react";
2
- import { describe, expect, it, vi } from "vitest";
3
- import { ChatMessage } from "../ChatMessage";
4
- import type { Citation } from "../types";
5
- import { ChatMessageList } from "./ChatMessageList";
6
-
7
- describe("ChatMessageList", () => {
8
- it("renders children and relays citation clicks", () => {
9
- const citation: Citation = {
10
- index: 1,
11
- excerpt: "Referenced excerpt.",
12
- sourceTitle: "Knowledge Report",
13
- sourceId: "doc-1",
14
- };
15
-
16
- const onCitationClick = vi.fn();
17
-
18
- render(
19
- <ChatMessageList>
20
- <ChatMessage
21
- role="assistant"
22
- timestamp="2:35 PM"
23
- citations={[citation]}
24
- onCitationClick={onCitationClick}
25
- >
26
- Assistant response
27
- </ChatMessage>
28
- </ChatMessageList>
29
- );
30
-
31
- expect(screen.getByText("Assistant response")).toBeInTheDocument();
32
-
33
- fireEvent.click(screen.getByRole("button", { name: "Knowledge Report" }));
34
- fireEvent.click(screen.getByRole("button", { name: "[1]" }));
35
-
36
- expect(onCitationClick).toHaveBeenCalledTimes(2);
37
- expect(onCitationClick).toHaveBeenCalledWith(citation);
38
- });
39
-
40
- it("auto-scrolls to bottom when new messages are added", async () => {
41
- vi.spyOn(window, "requestAnimationFrame").mockImplementation(
42
- (cb: FrameRequestCallback) => {
43
- cb(0);
44
- return 1;
45
- }
46
- );
47
-
48
- const { container, rerender } = render(
49
- <ChatMessageList>
50
- <div>Message 1</div>
51
- </ChatMessageList>
52
- );
53
-
54
- const inner = container.querySelector(
55
- ".pcoi-chat-message-list__inner"
56
- ) as HTMLDivElement;
57
-
58
- Object.defineProperty(inner, "scrollHeight", {
59
- configurable: true,
60
- get: () => 480,
61
- });
62
-
63
- rerender(
64
- <ChatMessageList>
65
- <div>Message 1</div>
66
- <div>Message 2</div>
67
- </ChatMessageList>
68
- );
69
-
70
- await waitFor(() => {
71
- expect(inner.scrollTop).toBe(480);
72
- });
73
- });
74
- });
@@ -1,51 +0,0 @@
1
- import React, { useRef, useEffect } from "react";
2
-
3
- export interface ChatMessageListProps
4
- extends React.HTMLAttributes<HTMLDivElement> {
5
- /** ChatMessage elements */
6
- children: React.ReactNode;
7
- }
8
-
9
- /**
10
- * PCOI ChatMessageList — Scrollable container that auto-scrolls to newest message
11
- * Tokens: spacing-16/20 (gap), spacing-16/24 (padding)
12
- */
13
- export const ChatMessageList = React.forwardRef<
14
- HTMLDivElement,
15
- ChatMessageListProps
16
- >(({ children, className = "", ...rest }, ref) => {
17
- const innerRef = useRef<HTMLDivElement>(null);
18
-
19
- /* Scroll to bottom whenever the DOM inside the list changes */
20
- useEffect(() => {
21
- const el = innerRef.current;
22
- if (!el) return;
23
-
24
- const scrollToEnd = () => {
25
- requestAnimationFrame(() => {
26
- el.scrollTop = el.scrollHeight;
27
- });
28
- };
29
-
30
- const observer = new MutationObserver(scrollToEnd);
31
- observer.observe(el, { childList: true, subtree: true });
32
-
33
- scrollToEnd();
34
- return () => observer.disconnect();
35
- }, []);
36
-
37
- const classes = ["pcoi-chat-message-list", className]
38
- .filter(Boolean)
39
- .join(" ");
40
-
41
- return (
42
- <div ref={ref} className={classes} {...rest}>
43
- <div ref={innerRef} className="pcoi-chat-message-list__inner">
44
- {children}
45
- </div>
46
- </div>
47
- );
48
- });
49
-
50
- ChatMessageList.displayName = "ChatMessageList";
51
- export default ChatMessageList;
@@ -1,2 +0,0 @@
1
- export { ChatMessageList, type ChatMessageListProps } from "./ChatMessageList";
2
- export { default } from "./ChatMessageList";
@@ -1,97 +0,0 @@
1
- /* Checkbox — @pcoi/components */
2
-
3
- .pcoi-checkbox {
4
- display: flex;
5
- flex-direction: column;
6
- gap: var(--pcoi-semantic-spacing-form-gap-compact);
7
- }
8
-
9
- .pcoi-checkbox__control {
10
- display: inline-flex;
11
- align-items: center;
12
- gap: var(--pcoi-semantic-spacing-inline-sm);
13
- cursor: pointer;
14
- }
15
-
16
- .pcoi-checkbox__input {
17
- position: absolute;
18
- width: 1px;
19
- height: 1px;
20
- padding: 0;
21
- margin: -1px;
22
- overflow: hidden;
23
- clip: rect(0, 0, 0, 0);
24
- white-space: nowrap;
25
- border-width: 0;
26
- }
27
-
28
- .pcoi-checkbox__box {
29
- width: var(--pcoi-semantic-sizing-control-box-size);
30
- height: var(--pcoi-semantic-sizing-control-box-size);
31
- flex-shrink: 0;
32
- border: 1px solid var(--pcoi-semantic-border-default);
33
- border-radius: var(--pcoi-semantic-radius-input);
34
- background: var(--pcoi-semantic-bg-default);
35
- transition: background var(--pcoi-effect-transition-fast, 0.2s ease),
36
- border-color var(--pcoi-effect-transition-fast, 0.2s ease);
37
- position: relative;
38
- }
39
-
40
- .pcoi-checkbox__box::after {
41
- content: "";
42
- position: absolute;
43
- top: 2px;
44
- left: 5px;
45
- width: 5px;
46
- height: 9px;
47
- border: solid var(--pcoi-semantic-action-primary-text);
48
- border-width: 0 2px 2px 0;
49
- transform: rotate(45deg);
50
- opacity: 0;
51
- transition: opacity var(--pcoi-effect-transition-fast, 0.2s ease);
52
- }
53
-
54
- .pcoi-checkbox__input:checked + .pcoi-checkbox__box {
55
- background: var(--pcoi-semantic-action-primary-bg);
56
- border-color: var(--pcoi-semantic-action-primary-bg);
57
- }
58
-
59
- .pcoi-checkbox__input:checked + .pcoi-checkbox__box::after {
60
- opacity: 1;
61
- }
62
-
63
- .pcoi-checkbox__control:hover .pcoi-checkbox__box {
64
- border-color: var(--pcoi-semantic-border-input-hover);
65
- }
66
-
67
- .pcoi-checkbox__control:active .pcoi-checkbox__box {
68
- transform: var(--pcoi-effect-transform-press-scale-control);
69
- }
70
-
71
- .pcoi-checkbox__input:focus-visible + .pcoi-checkbox__box {
72
- border-color: var(--pcoi-semantic-focus-border);
73
- box-shadow: var(--pcoi-effect-shadow-focus-ring);
74
- }
75
-
76
- .pcoi-checkbox__label {
77
- font-size: var(--pcoi-semantic-type-body-size);
78
- color: var(--pcoi-semantic-text-primary);
79
- }
80
-
81
- /* ── Error state ── */
82
- .pcoi-checkbox--error .pcoi-checkbox__box {
83
- border-color: var(--pcoi-semantic-border-error);
84
- }
85
-
86
- .pcoi-checkbox__error {
87
- font-size: var(--pcoi-semantic-type-label-size);
88
- color: var(--pcoi-semantic-text-error);
89
- margin: 0;
90
- padding-left: calc(var(--pcoi-semantic-sizing-control-box-size) + var(--pcoi-semantic-spacing-inline-sm));
91
- }
92
-
93
- /* ── Disabled state ── */
94
- .pcoi-checkbox--disabled {
95
- opacity: var(--pcoi-effect-opacity-disabled);
96
- pointer-events: none;
97
- }