@pcoi/components 0.1.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.
- package/dist/components.css +1 -0
- package/dist/index.d.ts +667 -0
- package/dist/index.js +2 -0
- package/dist/index.mjs +1048 -0
- package/package.json +36 -0
- package/src/Badge/Badge.css +40 -0
- package/src/Badge/Badge.tsx +36 -0
- package/src/Badge/index.ts +2 -0
- package/src/Button/Button.css +93 -0
- package/src/Button/Button.figma.tsx +29 -0
- package/src/Button/Button.tsx +47 -0
- package/src/Button/index.ts +1 -0
- package/src/Callout/Callout.css +43 -0
- package/src/Callout/Callout.tsx +39 -0
- package/src/Callout/index.ts +1 -0
- package/src/Card/Card.css +88 -0
- package/src/Card/Card.tsx +60 -0
- package/src/Card/index.ts +1 -0
- package/src/ChatInterface/ChatInterface.css +49 -0
- package/src/ChatInterface/ChatInterface.tsx +120 -0
- package/src/ChatInterface/index.ts +6 -0
- package/src/ChatMessage/ChatMessage.css +55 -0
- package/src/ChatMessage/ChatMessage.tsx +71 -0
- package/src/ChatMessage/index.ts +2 -0
- package/src/ChatMessageList/ChatMessageList.css +24 -0
- package/src/ChatMessageList/ChatMessageList.tsx +51 -0
- package/src/ChatMessageList/index.ts +2 -0
- package/src/Checkbox/Checkbox.css +97 -0
- package/src/Checkbox/Checkbox.tsx +70 -0
- package/src/Checkbox/index.ts +2 -0
- package/src/CitationMark/CitationMark.css +40 -0
- package/src/CitationMark/CitationMark.tsx +38 -0
- package/src/CitationMark/index.ts +2 -0
- package/src/CitedExcerpt/CitedExcerpt.css +75 -0
- package/src/CitedExcerpt/CitedExcerpt.tsx +51 -0
- package/src/CitedExcerpt/index.ts +2 -0
- package/src/ComparisonTable/ComparisonTable.css +66 -0
- package/src/ComparisonTable/ComparisonTable.tsx +48 -0
- package/src/ComparisonTable/index.ts +1 -0
- package/src/ContactForm/ContactForm.css +38 -0
- package/src/ContactForm/ContactForm.tsx +57 -0
- package/src/ContactForm/index.ts +1 -0
- package/src/DataTable/DataTable.css +56 -0
- package/src/DataTable/DataTable.tsx +104 -0
- package/src/DataTable/index.ts +2 -0
- package/src/DocumentOverlay/DocumentOverlay.css +57 -0
- package/src/DocumentOverlay/DocumentOverlay.tsx +86 -0
- package/src/DocumentOverlay/index.ts +2 -0
- package/src/Footer/Footer.css +72 -0
- package/src/Footer/Footer.tsx +56 -0
- package/src/Footer/index.ts +1 -0
- package/src/FormField/FormField.css +78 -0
- package/src/FormField/FormField.tsx +103 -0
- package/src/FormField/index.ts +2 -0
- package/src/HowStep/HowStep.css +48 -0
- package/src/HowStep/HowStep.tsx +38 -0
- package/src/HowStep/index.ts +1 -0
- package/src/LogoMark/LogoMark.css +16 -0
- package/src/LogoMark/LogoMark.tsx +25 -0
- package/src/LogoMark/index.ts +2 -0
- package/src/Modal/Modal.css +101 -0
- package/src/Modal/Modal.tsx +141 -0
- package/src/Modal/index.ts +2 -0
- package/src/Nav/Nav.css +161 -0
- package/src/Nav/Nav.tsx +101 -0
- package/src/Nav/index.ts +1 -0
- package/src/Panel/Panel.css +35 -0
- package/src/Panel/Panel.tsx +61 -0
- package/src/Panel/index.ts +2 -0
- package/src/PromptBar/PromptBar.css +68 -0
- package/src/PromptBar/PromptBar.tsx +93 -0
- package/src/PromptBar/index.ts +2 -0
- package/src/RadioGroup/RadioGroup.css +117 -0
- package/src/RadioGroup/RadioGroup.tsx +112 -0
- package/src/RadioGroup/index.ts +2 -0
- package/src/SectionHeader/SectionHeader.css +38 -0
- package/src/SectionHeader/SectionHeader.tsx +55 -0
- package/src/SectionHeader/index.ts +1 -0
- package/src/Select/Select.css +90 -0
- package/src/Select/Select.tsx +100 -0
- package/src/Select/index.ts +2 -0
- package/src/SignalsPanel/SignalsPanel.css +51 -0
- package/src/SignalsPanel/SignalsPanel.tsx +33 -0
- package/src/SignalsPanel/index.ts +1 -0
- package/src/SuggestionCard/SuggestionCard.css +51 -0
- package/src/SuggestionCard/SuggestionCard.tsx +34 -0
- package/src/SuggestionCard/index.ts +2 -0
- package/src/SuggestionCards/SuggestionCards.css +15 -0
- package/src/SuggestionCards/SuggestionCards.tsx +40 -0
- package/src/SuggestionCards/index.ts +2 -0
- package/src/Toast/Toast.css +85 -0
- package/src/Toast/Toast.tsx +77 -0
- package/src/Toast/index.ts +2 -0
- package/src/Toggle/Toggle.css +110 -0
- package/src/Toggle/Toggle.tsx +73 -0
- package/src/Toggle/index.ts +2 -0
- package/src/TypingIndicator/TypingIndicator.css +70 -0
- package/src/TypingIndicator/TypingIndicator.tsx +37 -0
- package/src/TypingIndicator/index.ts +2 -0
- package/src/index.ts +37 -0
- package/src/styles/utilities.css +14 -0
- package/src/styles.css +32 -0
- package/src/types.ts +65 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/* Toast — @pcoi/components */
|
|
2
|
+
|
|
3
|
+
.pcoi-toast {
|
|
4
|
+
position: fixed;
|
|
5
|
+
bottom: var(--pcoi-spacing-24);
|
|
6
|
+
right: var(--pcoi-spacing-24);
|
|
7
|
+
z-index: var(--pcoi-layout-zIndex-toast, 600);
|
|
8
|
+
animation: pcoi-toast-slide-in 0.3s ease;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.pcoi-toast__content {
|
|
12
|
+
display: flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
gap: var(--pcoi-spacing-12);
|
|
15
|
+
font-family: var(--pcoi-semantic-type-body-font);
|
|
16
|
+
background: var(--pcoi-semantic-surface-elevated);
|
|
17
|
+
border: 1px solid var(--pcoi-semantic-border-default);
|
|
18
|
+
border-radius: var(--pcoi-radius-md);
|
|
19
|
+
box-shadow: var(--pcoi-effect-shadow-elevated);
|
|
20
|
+
padding: var(--pcoi-spacing-14) var(--pcoi-spacing-20);
|
|
21
|
+
min-width: var(--pcoi-layout-container-toast-min);
|
|
22
|
+
max-width: var(--pcoi-layout-container-toast-max);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* ── Left accent border per variant ── */
|
|
26
|
+
.pcoi-toast--success .pcoi-toast__content {
|
|
27
|
+
border-left: var(--pcoi-layout-component-accent-border-w) solid var(--pcoi-semantic-border-success);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.pcoi-toast--error .pcoi-toast__content {
|
|
31
|
+
border-left: var(--pcoi-layout-component-accent-border-w) solid var(--pcoi-semantic-border-error);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.pcoi-toast--warning .pcoi-toast__content {
|
|
35
|
+
border-left: var(--pcoi-layout-component-accent-border-w) solid var(--pcoi-semantic-border-warning);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.pcoi-toast--info .pcoi-toast__content {
|
|
39
|
+
border-left: var(--pcoi-layout-component-accent-border-w) solid var(--pcoi-semantic-border-info);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.pcoi-toast__message {
|
|
43
|
+
flex: 1;
|
|
44
|
+
font-size: var(--pcoi-semantic-type-body-size);
|
|
45
|
+
color: var(--pcoi-semantic-text-primary);
|
|
46
|
+
line-height: var(--pcoi-semantic-type-body-compact-line-height);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.pcoi-toast__close {
|
|
50
|
+
background: none;
|
|
51
|
+
border: none;
|
|
52
|
+
color: var(--pcoi-semantic-text-secondary);
|
|
53
|
+
font-size: var(--pcoi-semantic-type-close-sm-size);
|
|
54
|
+
line-height: var(--pcoi-semantic-type-none-line-height);
|
|
55
|
+
cursor: pointer;
|
|
56
|
+
padding: var(--pcoi-spacing-4);
|
|
57
|
+
border-radius: var(--pcoi-radius-sm);
|
|
58
|
+
flex-shrink: 0;
|
|
59
|
+
transition: color var(--pcoi-effect-transition-fast, 0.2s ease);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.pcoi-toast__close:hover {
|
|
63
|
+
color: var(--pcoi-semantic-text-primary);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.pcoi-toast__close:active {
|
|
67
|
+
transform: var(--pcoi-effect-transform-press-scale-icon);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.pcoi-toast__close:focus-visible {
|
|
71
|
+
outline: none;
|
|
72
|
+
box-shadow: var(--pcoi-effect-shadow-focus-ring);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* ── Animation ── */
|
|
76
|
+
@keyframes pcoi-toast-slide-in {
|
|
77
|
+
from {
|
|
78
|
+
transform: translateX(100%);
|
|
79
|
+
opacity: 0;
|
|
80
|
+
}
|
|
81
|
+
to {
|
|
82
|
+
transform: translateX(0);
|
|
83
|
+
opacity: 1;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React, { useEffect, useCallback } from "react";
|
|
2
|
+
import { createPortal } from "react-dom";
|
|
3
|
+
import { CloseIcon } from "../../../icons/src/react/CloseIcon";
|
|
4
|
+
|
|
5
|
+
export type ToastVariant = "success" | "error" | "warning" | "info";
|
|
6
|
+
|
|
7
|
+
export interface ToastProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
8
|
+
/** Toast message */
|
|
9
|
+
message: string;
|
|
10
|
+
/** Visual variant determining color */
|
|
11
|
+
variant?: ToastVariant;
|
|
12
|
+
/** Whether the toast is visible */
|
|
13
|
+
open: boolean;
|
|
14
|
+
/** Called when the toast should dismiss */
|
|
15
|
+
onClose?: () => void;
|
|
16
|
+
/** Auto-dismiss duration in ms (default 5000, 0 = no auto-dismiss) */
|
|
17
|
+
duration?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* PCOI Toast — Non-blocking notification
|
|
22
|
+
* Tokens: surface/elevated, border/success|error|warning|info,
|
|
23
|
+
* text/primary, text/success|error|warning|info,
|
|
24
|
+
* shadow/elevated, zIndex/toast, radius-md
|
|
25
|
+
*/
|
|
26
|
+
export const Toast: React.FC<ToastProps> = ({
|
|
27
|
+
message,
|
|
28
|
+
variant = "info",
|
|
29
|
+
open,
|
|
30
|
+
onClose,
|
|
31
|
+
duration = 5000,
|
|
32
|
+
className = "",
|
|
33
|
+
...rest
|
|
34
|
+
}) => {
|
|
35
|
+
const handleClose = useCallback(() => {
|
|
36
|
+
onClose?.();
|
|
37
|
+
}, [onClose]);
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (!open || duration === 0) return;
|
|
41
|
+
|
|
42
|
+
const timer = setTimeout(handleClose, duration);
|
|
43
|
+
return () => clearTimeout(timer);
|
|
44
|
+
}, [open, duration, handleClose]);
|
|
45
|
+
|
|
46
|
+
if (!open) return null;
|
|
47
|
+
|
|
48
|
+
const wrapperClasses = [
|
|
49
|
+
"pcoi-toast",
|
|
50
|
+
`pcoi-toast--${variant}`,
|
|
51
|
+
className,
|
|
52
|
+
]
|
|
53
|
+
.filter(Boolean)
|
|
54
|
+
.join(" ");
|
|
55
|
+
|
|
56
|
+
return createPortal(
|
|
57
|
+
<div className={wrapperClasses} role="alert" {...rest}>
|
|
58
|
+
<div className="pcoi-toast__content">
|
|
59
|
+
<span className="pcoi-toast__message">{message}</span>
|
|
60
|
+
{onClose && (
|
|
61
|
+
<button
|
|
62
|
+
type="button"
|
|
63
|
+
className="pcoi-toast__close"
|
|
64
|
+
onClick={handleClose}
|
|
65
|
+
aria-label="Dismiss notification"
|
|
66
|
+
>
|
|
67
|
+
<CloseIcon size={16} />
|
|
68
|
+
</button>
|
|
69
|
+
)}
|
|
70
|
+
</div>
|
|
71
|
+
</div>,
|
|
72
|
+
document.body
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
Toast.displayName = "Toast";
|
|
77
|
+
export default Toast;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/* Toggle — @pcoi/components */
|
|
2
|
+
|
|
3
|
+
.pcoi-toggle {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
gap: var(--pcoi-spacing-6);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.pcoi-toggle__control {
|
|
10
|
+
display: inline-flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
gap: var(--pcoi-spacing-12);
|
|
13
|
+
cursor: pointer;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.pcoi-toggle__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-toggle__track {
|
|
29
|
+
width: var(--pcoi-layout-component-toggle-track-w);
|
|
30
|
+
height: var(--pcoi-layout-component-toggle-track-h);
|
|
31
|
+
flex-shrink: 0;
|
|
32
|
+
border-radius: var(--pcoi-radius-full);
|
|
33
|
+
background: var(--pcoi-semantic-action-toggle-bg);
|
|
34
|
+
position: relative;
|
|
35
|
+
transition: background var(--pcoi-effect-transition-fast, 0.2s ease);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.pcoi-toggle__thumb {
|
|
39
|
+
position: absolute;
|
|
40
|
+
top: var(--pcoi-layout-component-toggle-thumb-inset);
|
|
41
|
+
left: var(--pcoi-layout-component-toggle-thumb-inset);
|
|
42
|
+
width: var(--pcoi-layout-component-toggle-thumb);
|
|
43
|
+
height: var(--pcoi-layout-component-toggle-thumb);
|
|
44
|
+
border-radius: var(--pcoi-radius-full);
|
|
45
|
+
background: var(--pcoi-semantic-action-toggle-thumb);
|
|
46
|
+
transition: transform var(--pcoi-effect-transition-fast, 0.2s ease),
|
|
47
|
+
background var(--pcoi-effect-transition-fast, 0.2s ease);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.pcoi-toggle__thumb::after {
|
|
51
|
+
content: "";
|
|
52
|
+
position: absolute;
|
|
53
|
+
top: 50%;
|
|
54
|
+
left: 50%;
|
|
55
|
+
width: var(--pcoi-layout-component-toggle-check-w);
|
|
56
|
+
height: var(--pcoi-layout-component-toggle-check-h);
|
|
57
|
+
border: solid var(--pcoi-semantic-action-success);
|
|
58
|
+
border-width: 0 var(--pcoi-layout-component-toggle-check-stroke) var(--pcoi-layout-component-toggle-check-stroke) 0;
|
|
59
|
+
transform: translate(-50%, -60%) rotate(45deg);
|
|
60
|
+
opacity: 0;
|
|
61
|
+
transition: opacity var(--pcoi-effect-transition-fast, 0.2s ease);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.pcoi-toggle__control:hover .pcoi-toggle__track {
|
|
65
|
+
box-shadow: 0 0 0 1px var(--pcoi-semantic-border-input-hover);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.pcoi-toggle__control:active .pcoi-toggle__track {
|
|
69
|
+
transform: var(--pcoi-effect-transform-press-scale-track);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.pcoi-toggle__input:checked + .pcoi-toggle__track {
|
|
73
|
+
background: var(--pcoi-semantic-action-toggle-bg-on);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.pcoi-toggle__input:checked + .pcoi-toggle__track .pcoi-toggle__thumb {
|
|
77
|
+
transform: translateX(var(--pcoi-layout-component-toggle-thumb-travel)) var(--pcoi-effect-transform-toggle-thumb-on);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.pcoi-toggle__input:checked + .pcoi-toggle__track .pcoi-toggle__thumb::after {
|
|
81
|
+
opacity: 1;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.pcoi-toggle__input:focus-visible + .pcoi-toggle__track {
|
|
85
|
+
outline: none;
|
|
86
|
+
box-shadow: var(--pcoi-effect-shadow-focus-ring);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.pcoi-toggle__label {
|
|
90
|
+
font-size: var(--pcoi-semantic-type-body-size);
|
|
91
|
+
color: var(--pcoi-semantic-text-primary);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* ── Error state ── */
|
|
95
|
+
.pcoi-toggle--error .pcoi-toggle__track {
|
|
96
|
+
box-shadow: 0 0 0 1px var(--pcoi-semantic-border-error);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.pcoi-toggle__error {
|
|
100
|
+
font-size: var(--pcoi-semantic-type-label-size);
|
|
101
|
+
color: var(--pcoi-semantic-text-error);
|
|
102
|
+
margin: 0;
|
|
103
|
+
padding-left: calc(var(--pcoi-layout-component-toggle-track-w) + var(--pcoi-spacing-12));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* ── Disabled state ── */
|
|
107
|
+
.pcoi-toggle--disabled {
|
|
108
|
+
opacity: var(--pcoi-effect-opacity-disabled);
|
|
109
|
+
pointer-events: none;
|
|
110
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
export interface ToggleProps
|
|
4
|
+
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "type" | "size"> {
|
|
5
|
+
/** Visible label text */
|
|
6
|
+
label: string;
|
|
7
|
+
/** Unique field name, also used to generate id */
|
|
8
|
+
name: string;
|
|
9
|
+
/** Error message — when truthy, field enters error state */
|
|
10
|
+
error?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* PCOI Toggle — Switch-style boolean control
|
|
15
|
+
* Tokens: action/toggle-bg, action/toggle-bg-on, action/toggle-thumb,
|
|
16
|
+
* focus/border, focus/glow, text/primary, text/error, radius-full
|
|
17
|
+
*/
|
|
18
|
+
export const Toggle = React.forwardRef<HTMLInputElement, ToggleProps>(
|
|
19
|
+
(
|
|
20
|
+
{
|
|
21
|
+
label,
|
|
22
|
+
name,
|
|
23
|
+
error,
|
|
24
|
+
disabled = false,
|
|
25
|
+
className = "",
|
|
26
|
+
...props
|
|
27
|
+
},
|
|
28
|
+
ref
|
|
29
|
+
) => {
|
|
30
|
+
const fieldId = `pcoi-field-${name}`;
|
|
31
|
+
const errorId = error ? `${fieldId}-error` : undefined;
|
|
32
|
+
|
|
33
|
+
const wrapperClasses = [
|
|
34
|
+
"pcoi-toggle",
|
|
35
|
+
error ? "pcoi-toggle--error" : "",
|
|
36
|
+
disabled ? "pcoi-toggle--disabled" : "",
|
|
37
|
+
className,
|
|
38
|
+
]
|
|
39
|
+
.filter(Boolean)
|
|
40
|
+
.join(" ");
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div className={wrapperClasses}>
|
|
44
|
+
<label htmlFor={fieldId} className="pcoi-toggle__control">
|
|
45
|
+
<input
|
|
46
|
+
ref={ref}
|
|
47
|
+
type="checkbox"
|
|
48
|
+
id={fieldId}
|
|
49
|
+
name={name}
|
|
50
|
+
disabled={disabled}
|
|
51
|
+
aria-invalid={!!error}
|
|
52
|
+
aria-describedby={errorId}
|
|
53
|
+
className="pcoi-toggle__input"
|
|
54
|
+
role="switch"
|
|
55
|
+
{...props}
|
|
56
|
+
/>
|
|
57
|
+
<span className="pcoi-toggle__track" aria-hidden="true">
|
|
58
|
+
<span className="pcoi-toggle__thumb" />
|
|
59
|
+
</span>
|
|
60
|
+
<span className="pcoi-toggle__label">{label}</span>
|
|
61
|
+
</label>
|
|
62
|
+
{error && (
|
|
63
|
+
<span id={errorId} className="pcoi-toggle__error" role="alert">
|
|
64
|
+
{error}
|
|
65
|
+
</span>
|
|
66
|
+
)}
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
Toggle.displayName = "Toggle";
|
|
73
|
+
export default Toggle;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/* TypingIndicator — @pcoi/components */
|
|
2
|
+
|
|
3
|
+
.pcoi-typing-indicator {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
gap: var(--pcoi-spacing-8);
|
|
7
|
+
padding: var(--pcoi-spacing-16);
|
|
8
|
+
background: var(--pcoi-semantic-surface-accent-dim);
|
|
9
|
+
border: 1px solid var(--pcoi-semantic-border-card);
|
|
10
|
+
border-radius: var(--pcoi-radius-md);
|
|
11
|
+
animation: pcoi-typing-fade-in 0.2s ease;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.pcoi-typing-indicator__header {
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.pcoi-typing-indicator__dots {
|
|
20
|
+
display: flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
gap: 6px;
|
|
23
|
+
padding: var(--pcoi-spacing-4) 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.pcoi-typing-indicator__dot {
|
|
27
|
+
width: 8px;
|
|
28
|
+
height: 8px;
|
|
29
|
+
border-radius: var(--pcoi-radius-full);
|
|
30
|
+
background: var(--pcoi-semantic-text-accent);
|
|
31
|
+
opacity: 0.4;
|
|
32
|
+
animation: pcoi-typing-bounce 1.4s ease-in-out infinite;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.pcoi-typing-indicator__dot:nth-child(2) {
|
|
36
|
+
animation-delay: 0.2s;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.pcoi-typing-indicator__dot:nth-child(3) {
|
|
40
|
+
animation-delay: 0.4s;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@keyframes pcoi-typing-bounce {
|
|
44
|
+
0%, 60%, 100% {
|
|
45
|
+
transform: translateY(0);
|
|
46
|
+
opacity: 0.4;
|
|
47
|
+
}
|
|
48
|
+
30% {
|
|
49
|
+
transform: translateY(-6px);
|
|
50
|
+
opacity: 1;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@keyframes pcoi-typing-fade-in {
|
|
55
|
+
from {
|
|
56
|
+
opacity: 0;
|
|
57
|
+
transform: translateY(4px);
|
|
58
|
+
}
|
|
59
|
+
to {
|
|
60
|
+
opacity: 1;
|
|
61
|
+
transform: translateY(0);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* ── Responsive: offset to match assistant messages on desktop ── */
|
|
66
|
+
@media (min-width: 1025px) {
|
|
67
|
+
.pcoi-typing-indicator {
|
|
68
|
+
margin-right: var(--pcoi-spacing-48);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Badge } from "../Badge/Badge";
|
|
3
|
+
|
|
4
|
+
export interface TypingIndicatorProps
|
|
5
|
+
extends React.HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
/** Label shown in the badge (default: "PCOI") */
|
|
7
|
+
label?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* PCOI TypingIndicator — Three bouncing dots indicating the assistant is composing a response
|
|
12
|
+
* Tokens: bg/card, border/card, radius-md, text/accent
|
|
13
|
+
*/
|
|
14
|
+
export const TypingIndicator = React.forwardRef<
|
|
15
|
+
HTMLDivElement,
|
|
16
|
+
TypingIndicatorProps
|
|
17
|
+
>(({ label = "PCOI", className = "", ...rest }, ref) => {
|
|
18
|
+
const classes = ["pcoi-typing-indicator", className]
|
|
19
|
+
.filter(Boolean)
|
|
20
|
+
.join(" ");
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div ref={ref} className={classes} {...rest}>
|
|
24
|
+
<div className="pcoi-typing-indicator__header">
|
|
25
|
+
<Badge>{label}</Badge>
|
|
26
|
+
</div>
|
|
27
|
+
<div className="pcoi-typing-indicator__dots" aria-label="Typing">
|
|
28
|
+
<span className="pcoi-typing-indicator__dot" />
|
|
29
|
+
<span className="pcoi-typing-indicator__dot" />
|
|
30
|
+
<span className="pcoi-typing-indicator__dot" />
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
TypingIndicator.displayName = "TypingIndicator";
|
|
37
|
+
export default TypingIndicator;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// @pcoi/components — Barrel Export
|
|
2
|
+
// All components from the PCOI Design System
|
|
3
|
+
import "./styles.css";
|
|
4
|
+
|
|
5
|
+
export { type HeadingLevel, type LinkItem, type OptionItem, type ChatMessageRole, type Citation, type Suggestion, type TrackingProps } from "./types";
|
|
6
|
+
export { LogoMark, type LogoMarkProps } from "./LogoMark";
|
|
7
|
+
export { Button, type ButtonProps, type ButtonVariant, type ButtonSize } from "./Button";
|
|
8
|
+
export { FormField, type FormFieldProps } from "./FormField";
|
|
9
|
+
export { Card, type CardProps, type CardVariant } from "./Card";
|
|
10
|
+
export { Nav, type NavProps, type NavLink } from "./Nav";
|
|
11
|
+
export { SectionHeader, type SectionHeaderProps } from "./SectionHeader";
|
|
12
|
+
export { HowStep, type HowStepProps } from "./HowStep";
|
|
13
|
+
export { ComparisonTable, type ComparisonTableProps, type ComparisonRow } from "./ComparisonTable";
|
|
14
|
+
export { ContactForm, type ContactFormProps } from "./ContactForm";
|
|
15
|
+
export { Callout, type CalloutProps } from "./Callout";
|
|
16
|
+
export { SignalsPanel, type SignalsPanelProps } from "./SignalsPanel";
|
|
17
|
+
export { Footer, type FooterProps, type FooterLink } from "./Footer";
|
|
18
|
+
export { Select, type SelectProps } from "./Select";
|
|
19
|
+
export { Checkbox, type CheckboxProps } from "./Checkbox";
|
|
20
|
+
export { Toggle, type ToggleProps } from "./Toggle";
|
|
21
|
+
export { RadioGroup, type RadioGroupProps } from "./RadioGroup";
|
|
22
|
+
export { Panel, type PanelProps } from "./Panel";
|
|
23
|
+
export { DataTable, type DataTableProps, type DataTableColumn } from "./DataTable";
|
|
24
|
+
export { Badge, type BadgeProps, type BadgeVariant } from "./Badge";
|
|
25
|
+
export { Modal, type ModalProps } from "./Modal";
|
|
26
|
+
export { Toast, type ToastProps, type ToastVariant } from "./Toast";
|
|
27
|
+
|
|
28
|
+
// Chat Interface components
|
|
29
|
+
export { SuggestionCard, type SuggestionCardProps } from "./SuggestionCard";
|
|
30
|
+
export { CitedExcerpt, type CitedExcerptProps } from "./CitedExcerpt";
|
|
31
|
+
export { PromptBar, type PromptBarProps } from "./PromptBar";
|
|
32
|
+
export { ChatMessage, type ChatMessageProps } from "./ChatMessage";
|
|
33
|
+
export { SuggestionCards, type SuggestionCardsProps } from "./SuggestionCards";
|
|
34
|
+
export { ChatMessageList, type ChatMessageListProps } from "./ChatMessageList";
|
|
35
|
+
export { DocumentOverlay, type DocumentOverlayProps } from "./DocumentOverlay";
|
|
36
|
+
export { TypingIndicator, type TypingIndicatorProps } from "./TypingIndicator";
|
|
37
|
+
export { ChatInterface, type ChatInterfaceProps, type ChatInterfaceMessage } from "./ChatInterface";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* @pcoi/components — Global Utilities */
|
|
2
|
+
|
|
3
|
+
/* Screen-reader only: visually hidden but accessible to assistive technology */
|
|
4
|
+
.pcoi-sr-only {
|
|
5
|
+
position: absolute;
|
|
6
|
+
width: 1px;
|
|
7
|
+
height: 1px;
|
|
8
|
+
padding: 0;
|
|
9
|
+
margin: -1px;
|
|
10
|
+
overflow: hidden;
|
|
11
|
+
clip: rect(0, 0, 0, 0);
|
|
12
|
+
white-space: nowrap;
|
|
13
|
+
border-width: 0;
|
|
14
|
+
}
|
package/src/styles.css
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/* @pcoi/components — All component styles */
|
|
2
|
+
@import "./styles/utilities.css";
|
|
3
|
+
@import "./Badge/Badge.css";
|
|
4
|
+
@import "./LogoMark/LogoMark.css";
|
|
5
|
+
@import "./Button/Button.css";
|
|
6
|
+
@import "./FormField/FormField.css";
|
|
7
|
+
@import "./Card/Card.css";
|
|
8
|
+
@import "./Nav/Nav.css";
|
|
9
|
+
@import "./SectionHeader/SectionHeader.css";
|
|
10
|
+
@import "./HowStep/HowStep.css";
|
|
11
|
+
@import "./ComparisonTable/ComparisonTable.css";
|
|
12
|
+
@import "./ContactForm/ContactForm.css";
|
|
13
|
+
@import "./Callout/Callout.css";
|
|
14
|
+
@import "./SignalsPanel/SignalsPanel.css";
|
|
15
|
+
@import "./Footer/Footer.css";
|
|
16
|
+
@import "./Select/Select.css";
|
|
17
|
+
@import "./Checkbox/Checkbox.css";
|
|
18
|
+
@import "./Toggle/Toggle.css";
|
|
19
|
+
@import "./RadioGroup/RadioGroup.css";
|
|
20
|
+
@import "./Panel/Panel.css";
|
|
21
|
+
@import "./DataTable/DataTable.css";
|
|
22
|
+
@import "./Modal/Modal.css";
|
|
23
|
+
@import "./Toast/Toast.css";
|
|
24
|
+
@import "./SuggestionCard/SuggestionCard.css";
|
|
25
|
+
@import "./SuggestionCards/SuggestionCards.css";
|
|
26
|
+
@import "./CitedExcerpt/CitedExcerpt.css";
|
|
27
|
+
@import "./PromptBar/PromptBar.css";
|
|
28
|
+
@import "./ChatMessage/ChatMessage.css";
|
|
29
|
+
@import "./ChatMessageList/ChatMessageList.css";
|
|
30
|
+
@import "./TypingIndicator/TypingIndicator.css";
|
|
31
|
+
@import "./DocumentOverlay/DocumentOverlay.css";
|
|
32
|
+
@import "./ChatInterface/ChatInterface.css";
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// @pcoi/components — Shared Types
|
|
2
|
+
|
|
3
|
+
/** Semantic heading level for document outline (h1–h6) */
|
|
4
|
+
export type HeadingLevel = "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
|
|
5
|
+
|
|
6
|
+
/** Navigation link used by Nav, Footer, and any link-list component */
|
|
7
|
+
export interface LinkItem {
|
|
8
|
+
label: string;
|
|
9
|
+
href: string;
|
|
10
|
+
/** Optional tracking identifier rendered as data-track-id on the anchor */
|
|
11
|
+
trackingId?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Option item used by Select, RadioGroup */
|
|
15
|
+
export interface OptionItem {
|
|
16
|
+
label: string;
|
|
17
|
+
value: string;
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Chat message sender role */
|
|
22
|
+
export type ChatMessageRole = "user" | "assistant";
|
|
23
|
+
|
|
24
|
+
/** Citation reference from an assistant response */
|
|
25
|
+
export interface Citation {
|
|
26
|
+
/** Unique citation index (1-based) */
|
|
27
|
+
index: number;
|
|
28
|
+
/** Short excerpt from the source document */
|
|
29
|
+
excerpt: string;
|
|
30
|
+
/** Source document title */
|
|
31
|
+
sourceTitle: string;
|
|
32
|
+
/** Source document identifier (e.g. URL or document ID) */
|
|
33
|
+
sourceId: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Suggestion prompt for the chat empty state */
|
|
37
|
+
export interface Suggestion {
|
|
38
|
+
/** Unique identifier */
|
|
39
|
+
id: string;
|
|
40
|
+
/** Display text */
|
|
41
|
+
label: string;
|
|
42
|
+
/** Optional icon rendered before the label */
|
|
43
|
+
icon?: React.ReactNode;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Standard data-track-* attributes for analytics integration.
|
|
48
|
+
* Supported on all components via HTML attribute pass-through.
|
|
49
|
+
* Exported for documentation and IDE discoverability.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* <Button data-track-id="hero-cta" data-track-action="click" data-track-category="engagement">
|
|
53
|
+
* Get Started
|
|
54
|
+
* </Button>
|
|
55
|
+
*/
|
|
56
|
+
export interface TrackingProps {
|
|
57
|
+
/** Unique identifier for the tracked element */
|
|
58
|
+
"data-track-id"?: string;
|
|
59
|
+
/** Action type (e.g. "click", "submit", "view", "toggle") */
|
|
60
|
+
"data-track-action"?: string;
|
|
61
|
+
/** Category grouping (e.g. "navigation", "form", "engagement") */
|
|
62
|
+
"data-track-category"?: string;
|
|
63
|
+
/** Human-readable label for analytics dashboards */
|
|
64
|
+
"data-track-label"?: string;
|
|
65
|
+
}
|