@pcoi/components 0.1.2 → 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.
- package/LICENSE +21 -0
- package/README.md +52 -0
- package/dist/index.d.ts +829 -609
- package/dist/index.js +3517 -2
- package/dist/styles.css +2 -0
- package/package.json +35 -31
- package/dist/components.css +0 -1
- package/dist/index.mjs +0 -994
- package/src/Badge/Badge.css +0 -40
- package/src/Badge/Badge.tsx +0 -36
- package/src/Badge/index.ts +0 -2
- package/src/Button/Button.css +0 -93
- package/src/Button/Button.figma.tsx +0 -27
- package/src/Button/Button.test.tsx +0 -32
- package/src/Button/Button.tsx +0 -47
- package/src/Button/index.ts +0 -1
- package/src/Callout/Callout.css +0 -48
- package/src/Callout/Callout.figma.tsx +0 -25
- package/src/Callout/Callout.tsx +0 -43
- package/src/Callout/index.ts +0 -1
- package/src/Card/Card.css +0 -88
- package/src/Card/Card.figma.tsx +0 -28
- package/src/Card/Card.tsx +0 -60
- package/src/Card/index.ts +0 -1
- package/src/ChatInterface/ChatInterface.css +0 -50
- package/src/ChatInterface/ChatInterface.integration.test.tsx +0 -123
- package/src/ChatInterface/ChatInterface.tsx +0 -125
- package/src/ChatInterface/index.ts +0 -6
- package/src/ChatMessage/ChatMessage.css +0 -55
- package/src/ChatMessage/ChatMessage.tsx +0 -71
- package/src/ChatMessage/index.ts +0 -2
- package/src/ChatMessageList/ChatMessageList.css +0 -24
- package/src/ChatMessageList/ChatMessageList.test.tsx +0 -70
- package/src/ChatMessageList/ChatMessageList.tsx +0 -56
- package/src/ChatMessageList/index.ts +0 -2
- package/src/Checkbox/Checkbox.css +0 -97
- package/src/Checkbox/Checkbox.tsx +0 -70
- package/src/Checkbox/index.ts +0 -2
- package/src/CitationMark/CitationMark.css +0 -40
- package/src/CitationMark/CitationMark.tsx +0 -38
- package/src/CitationMark/index.ts +0 -2
- package/src/CitedExcerpt/CitedExcerpt.css +0 -75
- package/src/CitedExcerpt/CitedExcerpt.tsx +0 -53
- package/src/CitedExcerpt/index.ts +0 -2
- package/src/ComparisonTable/ComparisonTable.css +0 -66
- package/src/ComparisonTable/ComparisonTable.tsx +0 -54
- package/src/ComparisonTable/index.ts +0 -1
- package/src/ContactForm/ContactForm.css +0 -38
- package/src/ContactForm/ContactForm.tsx +0 -58
- package/src/ContactForm/index.ts +0 -1
- package/src/DataTable/DataTable.css +0 -56
- package/src/DataTable/DataTable.tsx +0 -104
- package/src/DataTable/index.ts +0 -2
- package/src/DocumentOverlay/DocumentOverlay.css +0 -57
- package/src/DocumentOverlay/DocumentOverlay.test.tsx +0 -95
- package/src/DocumentOverlay/DocumentOverlay.tsx +0 -87
- package/src/DocumentOverlay/index.ts +0 -2
- package/src/Footer/Footer.css +0 -72
- package/src/Footer/Footer.tsx +0 -59
- package/src/Footer/index.ts +0 -1
- package/src/FormField/FormField.css +0 -78
- package/src/FormField/FormField.figma.tsx +0 -28
- package/src/FormField/FormField.tsx +0 -103
- package/src/FormField/index.ts +0 -2
- package/src/HowStep/HowStep.css +0 -48
- package/src/HowStep/HowStep.figma.tsx +0 -23
- package/src/HowStep/HowStep.tsx +0 -38
- package/src/HowStep/index.ts +0 -1
- package/src/LogoMark/LogoMark.css +0 -16
- package/src/LogoMark/LogoMark.tsx +0 -24
- package/src/LogoMark/index.ts +0 -2
- package/src/Modal/Modal.css +0 -101
- package/src/Modal/Modal.figma.tsx +0 -28
- package/src/Modal/Modal.test.tsx +0 -46
- package/src/Modal/Modal.tsx +0 -144
- package/src/Modal/index.ts +0 -2
- package/src/Nav/Nav.css +0 -161
- package/src/Nav/Nav.tsx +0 -105
- package/src/Nav/index.ts +0 -1
- package/src/Panel/Panel.css +0 -35
- package/src/Panel/Panel.tsx +0 -61
- package/src/Panel/index.ts +0 -2
- package/src/PromptBar/PromptBar.css +0 -68
- package/src/PromptBar/PromptBar.figma.tsx +0 -25
- package/src/PromptBar/PromptBar.test.tsx +0 -83
- package/src/PromptBar/PromptBar.tsx +0 -93
- package/src/PromptBar/index.ts +0 -2
- package/src/RadioGroup/RadioGroup.css +0 -117
- package/src/RadioGroup/RadioGroup.tsx +0 -112
- package/src/RadioGroup/index.ts +0 -2
- package/src/SectionHeader/SectionHeader.css +0 -38
- package/src/SectionHeader/SectionHeader.figma.tsx +0 -23
- package/src/SectionHeader/SectionHeader.tsx +0 -55
- package/src/SectionHeader/index.ts +0 -1
- package/src/Select/Select.css +0 -90
- package/src/Select/Select.figma.tsx +0 -33
- package/src/Select/Select.tsx +0 -100
- package/src/Select/index.ts +0 -2
- package/src/SignalsPanel/SignalsPanel.css +0 -51
- package/src/SignalsPanel/SignalsPanel.tsx +0 -35
- package/src/SignalsPanel/index.ts +0 -1
- package/src/SuggestionCard/SuggestionCard.css +0 -51
- package/src/SuggestionCard/SuggestionCard.tsx +0 -34
- package/src/SuggestionCard/index.ts +0 -2
- package/src/SuggestionCards/SuggestionCards.css +0 -15
- package/src/SuggestionCards/SuggestionCards.test.tsx +0 -27
- package/src/SuggestionCards/SuggestionCards.tsx +0 -40
- package/src/SuggestionCards/index.ts +0 -2
- package/src/Toast/Toast.css +0 -85
- package/src/Toast/Toast.tsx +0 -82
- package/src/Toast/index.ts +0 -2
- package/src/Toggle/Toggle.css +0 -110
- package/src/Toggle/Toggle.figma.tsx +0 -24
- package/src/Toggle/Toggle.tsx +0 -73
- package/src/Toggle/index.ts +0 -2
- package/src/TypingIndicator/TypingIndicator.css +0 -70
- package/src/TypingIndicator/TypingIndicator.tsx +0 -37
- package/src/TypingIndicator/index.ts +0 -2
- package/src/index.ts +0 -39
- package/src/styles/utilities.css +0 -14
- package/src/styles.css +0 -33
- package/src/types.ts +0 -66
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/* DocumentOverlay — @pcoi/components */
|
|
2
|
-
|
|
3
|
-
/* ── Gold modal title ── */
|
|
4
|
-
.pcoi-doc-overlay .pcoi-modal__title {
|
|
5
|
-
color: var(--pcoi-semantic-text-accent);
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
.pcoi-doc-overlay__content {
|
|
9
|
-
font-family: var(--pcoi-semantic-type-body-font);
|
|
10
|
-
font-size: var(--pcoi-semantic-type-body-size);
|
|
11
|
-
line-height: var(--pcoi-semantic-type-body-line-height);
|
|
12
|
-
color: var(--pcoi-semantic-text-primary);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
.pcoi-doc-overlay__content p {
|
|
16
|
-
margin: 0 0 var(--pcoi-semantic-spacing-panel-gap);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
.pcoi-doc-overlay__content p:last-child {
|
|
20
|
-
margin-bottom: 0;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
.pcoi-doc-overlay__highlight {
|
|
24
|
-
display: flex;
|
|
25
|
-
align-items: baseline;
|
|
26
|
-
gap: var(--pcoi-semantic-spacing-form-gap-compact);
|
|
27
|
-
background: var(--pcoi-semantic-surface-highlight);
|
|
28
|
-
border-left: var(--pcoi-semantic-sizing-accent-border-width) solid
|
|
29
|
-
var(--pcoi-semantic-border-accent-subtle);
|
|
30
|
-
padding: var(--pcoi-semantic-spacing-inline-sm) var(--pcoi-semantic-spacing-input-y);
|
|
31
|
-
border-radius: var(--pcoi-semantic-radius-input);
|
|
32
|
-
animation: pcoi-doc-highlight-fade 0.4s ease;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.pcoi-doc-overlay__highlight[data-highlight-index]::before {
|
|
36
|
-
content: attr(data-highlight-index);
|
|
37
|
-
flex-shrink: 0;
|
|
38
|
-
font-family: var(--pcoi-semantic-type-mono-font);
|
|
39
|
-
font-size: var(--pcoi-semantic-type-body-compact-size);
|
|
40
|
-
font-weight: var(--pcoi-semantic-type-label-weight);
|
|
41
|
-
color: var(--pcoi-semantic-text-accent);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.pcoi-doc-overlay__source {
|
|
45
|
-
display: flex;
|
|
46
|
-
align-items: center;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/* ── Animation ── */
|
|
50
|
-
@keyframes pcoi-doc-highlight-fade {
|
|
51
|
-
from {
|
|
52
|
-
background: var(--pcoi-semantic-surface-accent-dim);
|
|
53
|
-
}
|
|
54
|
-
to {
|
|
55
|
-
background: var(--pcoi-semantic-surface-highlight);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { fireEvent, render, screen } from "@testing-library/react";
|
|
2
|
-
import { describe, expect, it, vi } from "vitest";
|
|
3
|
-
import { DocumentOverlay } from "./DocumentOverlay";
|
|
4
|
-
|
|
5
|
-
describe("DocumentOverlay", () => {
|
|
6
|
-
it("does not render when closed", () => {
|
|
7
|
-
render(
|
|
8
|
-
<DocumentOverlay open={false} onClose={vi.fn()} title="Source doc">
|
|
9
|
-
<p>Body content</p>
|
|
10
|
-
</DocumentOverlay>
|
|
11
|
-
);
|
|
12
|
-
|
|
13
|
-
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it("renders title, body, and source label when open", () => {
|
|
17
|
-
render(
|
|
18
|
-
<DocumentOverlay
|
|
19
|
-
open
|
|
20
|
-
onClose={vi.fn()}
|
|
21
|
-
title="Source doc"
|
|
22
|
-
sourceLabel="Internal Research"
|
|
23
|
-
>
|
|
24
|
-
<p>Body content</p>
|
|
25
|
-
</DocumentOverlay>
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
|
29
|
-
expect(screen.getByText("Source doc")).toBeInTheDocument();
|
|
30
|
-
expect(screen.getByText("Body content")).toBeInTheDocument();
|
|
31
|
-
expect(screen.getByText("Internal Research")).toBeInTheDocument();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it("calls onClose from close button, Escape key, and backdrop click", () => {
|
|
35
|
-
const onClose = vi.fn();
|
|
36
|
-
|
|
37
|
-
render(
|
|
38
|
-
<DocumentOverlay open onClose={onClose} title="Source doc">
|
|
39
|
-
<p>Body content</p>
|
|
40
|
-
</DocumentOverlay>
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
fireEvent.click(screen.getByRole("button", { name: "Close modal" }));
|
|
44
|
-
fireEvent.keyDown(document, { key: "Escape" });
|
|
45
|
-
|
|
46
|
-
const backdrop = document.querySelector(".pcoi-modal");
|
|
47
|
-
if (!backdrop) throw new Error("Expected modal backdrop to exist");
|
|
48
|
-
fireEvent.click(backdrop);
|
|
49
|
-
|
|
50
|
-
expect(onClose).toHaveBeenCalledTimes(3);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("highlights and indexes cited content when highlight props are provided", () => {
|
|
54
|
-
// JSDOM may not provide CSS.escape by default.
|
|
55
|
-
if (!(globalThis.CSS && typeof globalThis.CSS.escape === "function")) {
|
|
56
|
-
const existing = globalThis.CSS ?? ({} as CSS);
|
|
57
|
-
globalThis.CSS = {
|
|
58
|
-
...existing,
|
|
59
|
-
escape: (value: string) => value,
|
|
60
|
-
} as CSS;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const raf = vi
|
|
64
|
-
.spyOn(window, "requestAnimationFrame")
|
|
65
|
-
.mockImplementation((cb: FrameRequestCallback) => {
|
|
66
|
-
cb(0);
|
|
67
|
-
return 1;
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
const scrollIntoView = vi.fn();
|
|
71
|
-
Object.defineProperty(window.HTMLElement.prototype, "scrollIntoView", {
|
|
72
|
-
configurable: true,
|
|
73
|
-
value: scrollIntoView,
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
render(
|
|
77
|
-
<DocumentOverlay
|
|
78
|
-
open
|
|
79
|
-
onClose={vi.fn()}
|
|
80
|
-
title="Source doc"
|
|
81
|
-
highlightId="cite-1"
|
|
82
|
-
highlightIndex={2}
|
|
83
|
-
>
|
|
84
|
-
<p id="cite-1">Cited paragraph</p>
|
|
85
|
-
</DocumentOverlay>
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
const cited = screen.getByText("Cited paragraph");
|
|
89
|
-
expect(cited).toHaveClass("pcoi-doc-overlay__highlight");
|
|
90
|
-
expect(cited).toHaveAttribute("data-highlight-index", "[2]");
|
|
91
|
-
expect(scrollIntoView).toHaveBeenCalled();
|
|
92
|
-
|
|
93
|
-
raf.mockRestore();
|
|
94
|
-
});
|
|
95
|
-
});
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useRef } from "react";
|
|
2
|
-
import { Modal } from "../Modal/Modal";
|
|
3
|
-
import { Badge } from "../Badge/Badge";
|
|
4
|
-
|
|
5
|
-
export interface DocumentOverlayProps
|
|
6
|
-
extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
|
|
7
|
-
/** Whether the overlay is visible */
|
|
8
|
-
open: boolean;
|
|
9
|
-
/** Called when the overlay should close */
|
|
10
|
-
onClose: () => void;
|
|
11
|
-
/** Document title shown in the header */
|
|
12
|
-
title: string;
|
|
13
|
-
/** Optional source label shown as a badge */
|
|
14
|
-
sourceLabel?: string;
|
|
15
|
-
/** Document body content */
|
|
16
|
-
children: React.ReactNode;
|
|
17
|
-
/** Element ID to scroll to and highlight on open */
|
|
18
|
-
highlightId?: string;
|
|
19
|
-
/** Citation index to display beside the highlighted element */
|
|
20
|
-
highlightIndex?: number;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* PCOI DocumentOverlay — Wide modal for viewing source documents with highlighted citations
|
|
25
|
-
* Composes Modal (size="wide") + Badge
|
|
26
|
-
* Tokens: surface/highlight, border/accent-subtle, container/document
|
|
27
|
-
*/
|
|
28
|
-
export const DocumentOverlay = React.forwardRef<
|
|
29
|
-
HTMLDivElement,
|
|
30
|
-
DocumentOverlayProps
|
|
31
|
-
>(
|
|
32
|
-
(
|
|
33
|
-
{ open, onClose, title, sourceLabel, children, highlightId, highlightIndex, className = "", ...rest },
|
|
34
|
-
ref
|
|
35
|
-
) => {
|
|
36
|
-
const contentRef = useRef<HTMLDivElement>(null);
|
|
37
|
-
|
|
38
|
-
useEffect(() => {
|
|
39
|
-
if (open && highlightId && contentRef.current) {
|
|
40
|
-
requestAnimationFrame(() => {
|
|
41
|
-
const el = contentRef.current?.querySelector(`#${CSS.escape(highlightId)}`);
|
|
42
|
-
if (el) {
|
|
43
|
-
el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
44
|
-
el.classList.add("pcoi-doc-overlay__highlight");
|
|
45
|
-
if (highlightIndex !== undefined) {
|
|
46
|
-
el.setAttribute("data-highlight-index", `[${highlightIndex}]`);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
}, [open, highlightId, highlightIndex]);
|
|
52
|
-
|
|
53
|
-
const classes = ["pcoi-doc-overlay", className]
|
|
54
|
-
.filter(Boolean)
|
|
55
|
-
.join(" ");
|
|
56
|
-
|
|
57
|
-
const footer = sourceLabel
|
|
58
|
-
? React.createElement(
|
|
59
|
-
"div",
|
|
60
|
-
{ className: "pcoi-doc-overlay__source" },
|
|
61
|
-
React.createElement(Badge, null, sourceLabel)
|
|
62
|
-
)
|
|
63
|
-
: undefined;
|
|
64
|
-
|
|
65
|
-
return React.createElement(
|
|
66
|
-
Modal,
|
|
67
|
-
{
|
|
68
|
-
ref,
|
|
69
|
-
open,
|
|
70
|
-
onClose,
|
|
71
|
-
title,
|
|
72
|
-
size: "wide",
|
|
73
|
-
footer,
|
|
74
|
-
className: classes,
|
|
75
|
-
...rest,
|
|
76
|
-
},
|
|
77
|
-
React.createElement(
|
|
78
|
-
"div",
|
|
79
|
-
{ ref: contentRef, className: "pcoi-doc-overlay__content" },
|
|
80
|
-
children
|
|
81
|
-
)
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
DocumentOverlay.displayName = "DocumentOverlay";
|
|
87
|
-
export default DocumentOverlay;
|
package/src/Footer/Footer.css
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/* Footer — @pcoi/components */
|
|
2
|
-
|
|
3
|
-
.pcoi-footer {
|
|
4
|
-
border-top: 1px solid var(--pcoi-semantic-border-default);
|
|
5
|
-
padding: var(--pcoi-semantic-spacing-section-y-md) var(--pcoi-semantic-spacing-btn-x) var(--pcoi-semantic-spacing-component-gap);
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
.pcoi-footer__inner {
|
|
9
|
-
max-width: var(--pcoi-semantic-sizing-container-max);
|
|
10
|
-
margin: 0 auto;
|
|
11
|
-
display: flex;
|
|
12
|
-
justify-content: space-between;
|
|
13
|
-
align-items: flex-start;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/* ── Brand (LogoMark overrides) ── */
|
|
17
|
-
.pcoi-footer__logo {
|
|
18
|
-
margin-bottom: var(--pcoi-semantic-spacing-stack-sm);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.pcoi-footer__tagline {
|
|
22
|
-
font-size: var(--pcoi-semantic-type-body-sm-size);
|
|
23
|
-
color: var(--pcoi-semantic-text-secondary);
|
|
24
|
-
line-height: var(--pcoi-semantic-type-body-compact-line-height);
|
|
25
|
-
margin: 0;
|
|
26
|
-
white-space: pre-line;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/* ── Links ── */
|
|
30
|
-
.pcoi-footer__links {
|
|
31
|
-
display: flex;
|
|
32
|
-
flex-wrap: wrap;
|
|
33
|
-
gap: var(--pcoi-semantic-spacing-inline-lg);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
.pcoi-footer__links a {
|
|
37
|
-
font-size: var(--pcoi-semantic-type-nav-size);
|
|
38
|
-
color: var(--pcoi-semantic-text-secondary);
|
|
39
|
-
text-decoration: none;
|
|
40
|
-
transition: color var(--pcoi-effect-transition-fast, 0.2s ease);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.pcoi-footer__links a:hover {
|
|
44
|
-
color: var(--pcoi-semantic-text-primary);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/* ── Bottom ── */
|
|
48
|
-
.pcoi-footer__bottom {
|
|
49
|
-
max-width: var(--pcoi-semantic-sizing-container-max);
|
|
50
|
-
margin: var(--pcoi-semantic-spacing-component-gap) auto 0;
|
|
51
|
-
padding-top: var(--pcoi-semantic-spacing-panel-padding);
|
|
52
|
-
border-top: 1px solid var(--pcoi-semantic-border-default);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.pcoi-footer__bottom p {
|
|
56
|
-
font-size: var(--pcoi-semantic-type-label-size);
|
|
57
|
-
color: var(--pcoi-semantic-text-muted);
|
|
58
|
-
margin: 0;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/* ── Responsive ── */
|
|
62
|
-
@media (max-width: 768px) {
|
|
63
|
-
.pcoi-footer__inner {
|
|
64
|
-
flex-direction: column;
|
|
65
|
-
gap: var(--pcoi-semantic-spacing-stack-lg);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.pcoi-footer__links {
|
|
69
|
-
flex-direction: column;
|
|
70
|
-
gap: var(--pcoi-semantic-spacing-stack-compact);
|
|
71
|
-
}
|
|
72
|
-
}
|
package/src/Footer/Footer.tsx
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { LogoMark } from "../LogoMark";
|
|
3
|
-
import type { LinkItem } from "../types";
|
|
4
|
-
|
|
5
|
-
/** @deprecated Use `LinkItem` from `@pcoi/components` instead */
|
|
6
|
-
export type FooterLink = LinkItem;
|
|
7
|
-
|
|
8
|
-
export interface FooterProps extends React.HTMLAttributes<HTMLElement> {
|
|
9
|
-
/** Navigation links rendered in the footer */
|
|
10
|
-
links?: LinkItem[];
|
|
11
|
-
/** Brand tagline displayed alongside the logo */
|
|
12
|
-
tagline?: string;
|
|
13
|
-
/** Copyright notice shown at the bottom */
|
|
14
|
-
copyright?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* PCOI Footer
|
|
19
|
-
* Tokens: border/default, spacing-60/40/24, text/secondary, text/muted
|
|
20
|
-
*/
|
|
21
|
-
export const Footer = React.forwardRef<HTMLElement, FooterProps>(
|
|
22
|
-
(
|
|
23
|
-
{
|
|
24
|
-
links = [
|
|
25
|
-
{ label: "Problem", href: "#problem" },
|
|
26
|
-
{ label: "How It Works", href: "#how-it-works" },
|
|
27
|
-
{ label: "Difference", href: "#difference" },
|
|
28
|
-
{ label: "Principles", href: "#principles" },
|
|
29
|
-
{ label: "Who It's For", href: "#who" },
|
|
30
|
-
{ label: "Contact", href: "#contact" },
|
|
31
|
-
],
|
|
32
|
-
tagline = "Portable, Company-Owned\nOperational Intelligence",
|
|
33
|
-
copyright = `© ${new Date().getFullYear()} PCOI. Operational intelligence you own.`,
|
|
34
|
-
className = "",
|
|
35
|
-
...rest
|
|
36
|
-
},
|
|
37
|
-
ref
|
|
38
|
-
) => (
|
|
39
|
-
<footer ref={ref} className={`pcoi-footer ${className}`} {...rest}>
|
|
40
|
-
<div className="pcoi-footer__inner">
|
|
41
|
-
<div className="pcoi-footer__brand">
|
|
42
|
-
<LogoMark className="pcoi-footer__logo" />
|
|
43
|
-
<p className="pcoi-footer__tagline">{tagline}</p>
|
|
44
|
-
</div>
|
|
45
|
-
<nav className="pcoi-footer__links" aria-label="Footer">
|
|
46
|
-
{links.map((link) => (
|
|
47
|
-
<a key={link.href} href={link.href} data-track-id={link.trackingId}>{link.label}</a>
|
|
48
|
-
))}
|
|
49
|
-
</nav>
|
|
50
|
-
</div>
|
|
51
|
-
<div className="pcoi-footer__bottom">
|
|
52
|
-
<p>{copyright}</p>
|
|
53
|
-
</div>
|
|
54
|
-
</footer>
|
|
55
|
-
)
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
Footer.displayName = "Footer";
|
|
59
|
-
export default Footer;
|
package/src/Footer/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { Footer, type FooterProps, type FooterLink } from "./Footer";
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/* FormField — @pcoi/components */
|
|
2
|
-
|
|
3
|
-
.pcoi-field {
|
|
4
|
-
display: flex;
|
|
5
|
-
flex-direction: column;
|
|
6
|
-
gap: var(--pcoi-semantic-spacing-form-gap-compact);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
.pcoi-field__label {
|
|
10
|
-
font-size: var(--pcoi-semantic-type-label-size);
|
|
11
|
-
font-weight: var(--pcoi-semantic-type-label-weight);
|
|
12
|
-
letter-spacing: var(--pcoi-semantic-type-label-letter-spacing);
|
|
13
|
-
text-transform: uppercase;
|
|
14
|
-
color: var(--pcoi-semantic-text-secondary);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
.pcoi-field__required {
|
|
18
|
-
color: var(--pcoi-semantic-text-error);
|
|
19
|
-
margin-left: var(--pcoi-semantic-spacing-inline-2xs);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
.pcoi-field__input,
|
|
23
|
-
.pcoi-field__textarea {
|
|
24
|
-
font-family: var(--pcoi-semantic-type-body-font);
|
|
25
|
-
font-size: var(--pcoi-semantic-type-body-size);
|
|
26
|
-
color: var(--pcoi-semantic-text-primary);
|
|
27
|
-
background: var(--pcoi-semantic-bg-default);
|
|
28
|
-
border: 1px solid var(--pcoi-semantic-border-default);
|
|
29
|
-
border-radius: var(--pcoi-semantic-radius-input);
|
|
30
|
-
padding: var(--pcoi-semantic-spacing-input-y) var(--pcoi-semantic-spacing-input-x-compact);
|
|
31
|
-
transition: border-color var(--pcoi-effect-transition-fast, 0.2s ease),
|
|
32
|
-
box-shadow var(--pcoi-effect-transition-fast, 0.2s ease);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.pcoi-field__input::placeholder,
|
|
36
|
-
.pcoi-field__textarea::placeholder {
|
|
37
|
-
color: var(--pcoi-semantic-text-muted);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
.pcoi-field__input:not(:disabled):not(:focus):hover,
|
|
41
|
-
.pcoi-field__textarea:not(:disabled):not(:focus):hover {
|
|
42
|
-
border-color: var(--pcoi-semantic-border-input-hover);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
.pcoi-field__input:focus,
|
|
46
|
-
.pcoi-field__textarea:focus {
|
|
47
|
-
outline: none;
|
|
48
|
-
border-color: var(--pcoi-semantic-focus-border);
|
|
49
|
-
box-shadow: var(--pcoi-effect-shadow-focus-ring);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
.pcoi-field__textarea {
|
|
53
|
-
resize: vertical;
|
|
54
|
-
min-height: var(--pcoi-semantic-sizing-textarea-min-height);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/* ── Error state ── */
|
|
58
|
-
.pcoi-field--error .pcoi-field__input,
|
|
59
|
-
.pcoi-field--error .pcoi-field__textarea {
|
|
60
|
-
border-color: var(--pcoi-semantic-border-error);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
.pcoi-field--error .pcoi-field__input:focus,
|
|
64
|
-
.pcoi-field--error .pcoi-field__textarea:focus {
|
|
65
|
-
box-shadow: var(--pcoi-effect-shadow-focus-ring-error);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.pcoi-field__error {
|
|
69
|
-
font-size: var(--pcoi-semantic-type-label-size);
|
|
70
|
-
color: var(--pcoi-semantic-text-error);
|
|
71
|
-
margin: 0;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/* ── Disabled state ── */
|
|
75
|
-
.pcoi-field--disabled {
|
|
76
|
-
opacity: var(--pcoi-effect-opacity-disabled);
|
|
77
|
-
pointer-events: none;
|
|
78
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import figma from "@figma/code-connect";
|
|
2
|
-
import { FormField } from "./FormField";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Code Connect: FormField
|
|
6
|
-
* Maps Figma form field properties to React FormField props
|
|
7
|
-
*/
|
|
8
|
-
figma.connect(FormField, "https://www.figma.com/file/PCOIxDesignSystem/PCOI-Design-System?node-id=103-1", {
|
|
9
|
-
props: {
|
|
10
|
-
label: figma.string("Label"),
|
|
11
|
-
placeholder: figma.string("Placeholder"),
|
|
12
|
-
multiline: figma.boolean("Multiline"),
|
|
13
|
-
required: figma.boolean("Required"),
|
|
14
|
-
disabled: figma.boolean("Disabled"),
|
|
15
|
-
hasError: figma.boolean("Error"),
|
|
16
|
-
},
|
|
17
|
-
example: ({ label, placeholder, multiline, required, disabled, hasError }) => (
|
|
18
|
-
<FormField
|
|
19
|
-
name="field"
|
|
20
|
-
label={label}
|
|
21
|
-
placeholder={placeholder}
|
|
22
|
-
multiline={multiline}
|
|
23
|
-
required={required}
|
|
24
|
-
disabled={disabled}
|
|
25
|
-
error={hasError ? "This field is required." : undefined}
|
|
26
|
-
/>
|
|
27
|
-
),
|
|
28
|
-
});
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
|
|
3
|
-
export interface FormFieldProps
|
|
4
|
-
extends Omit<
|
|
5
|
-
React.InputHTMLAttributes<HTMLInputElement | HTMLTextAreaElement>,
|
|
6
|
-
"size"
|
|
7
|
-
> {
|
|
8
|
-
/** Visible label text */
|
|
9
|
-
label: string;
|
|
10
|
-
/** Unique field name, also used to generate id */
|
|
11
|
-
name: string;
|
|
12
|
-
/** Render as textarea instead of input */
|
|
13
|
-
multiline?: boolean;
|
|
14
|
-
/** Number of visible text rows when multiline (default 3) */
|
|
15
|
-
rows?: number;
|
|
16
|
-
/** Error message — when truthy, field enters error state */
|
|
17
|
-
error?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* PCOI FormField — Label + input/textarea atom
|
|
22
|
-
* Tokens: bg/default, border/default, border/error, focus/border, focus/glow,
|
|
23
|
-
* text/primary, text/secondary, text/muted, text/error,
|
|
24
|
-
* radius-sm, spacing-6/12/14, font-size/caption/body-default
|
|
25
|
-
*/
|
|
26
|
-
export const FormField = React.forwardRef<
|
|
27
|
-
HTMLInputElement | HTMLTextAreaElement,
|
|
28
|
-
FormFieldProps
|
|
29
|
-
>(
|
|
30
|
-
(
|
|
31
|
-
{
|
|
32
|
-
label,
|
|
33
|
-
name,
|
|
34
|
-
multiline = false,
|
|
35
|
-
rows = 3,
|
|
36
|
-
error,
|
|
37
|
-
required = false,
|
|
38
|
-
disabled = false,
|
|
39
|
-
className = "",
|
|
40
|
-
...props
|
|
41
|
-
},
|
|
42
|
-
ref
|
|
43
|
-
) => {
|
|
44
|
-
const fieldId = `pcoi-field-${name}`;
|
|
45
|
-
const errorId = error ? `${fieldId}-error` : undefined;
|
|
46
|
-
|
|
47
|
-
const wrapperClasses = [
|
|
48
|
-
"pcoi-field",
|
|
49
|
-
error ? "pcoi-field--error" : "",
|
|
50
|
-
disabled ? "pcoi-field--disabled" : "",
|
|
51
|
-
className,
|
|
52
|
-
]
|
|
53
|
-
.filter(Boolean)
|
|
54
|
-
.join(" ");
|
|
55
|
-
|
|
56
|
-
return (
|
|
57
|
-
<div className={wrapperClasses}>
|
|
58
|
-
<label htmlFor={fieldId} className="pcoi-field__label">
|
|
59
|
-
{label}
|
|
60
|
-
{required && (
|
|
61
|
-
<span className="pcoi-field__required" aria-hidden="true">
|
|
62
|
-
*
|
|
63
|
-
</span>
|
|
64
|
-
)}
|
|
65
|
-
</label>
|
|
66
|
-
{multiline ? (
|
|
67
|
-
<textarea
|
|
68
|
-
ref={ref as React.Ref<HTMLTextAreaElement>}
|
|
69
|
-
id={fieldId}
|
|
70
|
-
name={name}
|
|
71
|
-
rows={rows}
|
|
72
|
-
required={required}
|
|
73
|
-
disabled={disabled}
|
|
74
|
-
aria-invalid={!!error}
|
|
75
|
-
aria-describedby={errorId}
|
|
76
|
-
className="pcoi-field__textarea"
|
|
77
|
-
{...(props as React.TextareaHTMLAttributes<HTMLTextAreaElement>)}
|
|
78
|
-
/>
|
|
79
|
-
) : (
|
|
80
|
-
<input
|
|
81
|
-
ref={ref as React.Ref<HTMLInputElement>}
|
|
82
|
-
id={fieldId}
|
|
83
|
-
name={name}
|
|
84
|
-
required={required}
|
|
85
|
-
disabled={disabled}
|
|
86
|
-
aria-invalid={!!error}
|
|
87
|
-
aria-describedby={errorId}
|
|
88
|
-
className="pcoi-field__input"
|
|
89
|
-
{...(props as React.InputHTMLAttributes<HTMLInputElement>)}
|
|
90
|
-
/>
|
|
91
|
-
)}
|
|
92
|
-
{error && (
|
|
93
|
-
<span id={errorId} className="pcoi-field__error" role="alert">
|
|
94
|
-
{error}
|
|
95
|
-
</span>
|
|
96
|
-
)}
|
|
97
|
-
</div>
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
FormField.displayName = "FormField";
|
|
103
|
-
export default FormField;
|
package/src/FormField/index.ts
DELETED
package/src/HowStep/HowStep.css
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/* HowStep — @pcoi/components */
|
|
2
|
-
|
|
3
|
-
.pcoi-how-step {
|
|
4
|
-
display: flex;
|
|
5
|
-
align-items: flex-start;
|
|
6
|
-
gap: var(--pcoi-semantic-spacing-stack-lg);
|
|
7
|
-
padding: var(--pcoi-semantic-spacing-component-gap) 0;
|
|
8
|
-
border-bottom: 1px solid var(--pcoi-semantic-border-default);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
.pcoi-how-step--last {
|
|
12
|
-
border-bottom: none;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
.pcoi-how-step__number {
|
|
16
|
-
flex-shrink: 0;
|
|
17
|
-
width: var(--pcoi-semantic-sizing-step-number);
|
|
18
|
-
height: var(--pcoi-semantic-sizing-step-number);
|
|
19
|
-
border-radius: var(--pcoi-semantic-radius-avatar);
|
|
20
|
-
background: var(--pcoi-semantic-surface-accent-dim);
|
|
21
|
-
border: 1px solid var(--pcoi-semantic-border-accent-dim);
|
|
22
|
-
display: flex;
|
|
23
|
-
align-items: center;
|
|
24
|
-
justify-content: center;
|
|
25
|
-
font-family: var(--pcoi-semantic-type-mono-font);
|
|
26
|
-
font-size: var(--pcoi-semantic-type-body-sm-size);
|
|
27
|
-
font-weight: var(--pcoi-semantic-type-emphasis-weight);
|
|
28
|
-
color: var(--pcoi-semantic-text-accent);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
.pcoi-how-step__content {
|
|
32
|
-
flex: 1;
|
|
33
|
-
min-width: 0;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
.pcoi-how-step__title {
|
|
37
|
-
font-size: var(--pcoi-semantic-type-step-title-size);
|
|
38
|
-
font-weight: var(--pcoi-semantic-type-emphasis-weight);
|
|
39
|
-
color: var(--pcoi-semantic-text-primary);
|
|
40
|
-
margin: 0 0 var(--pcoi-semantic-spacing-stack-sm) 0;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.pcoi-how-step__desc {
|
|
44
|
-
font-size: var(--pcoi-semantic-type-body-size);
|
|
45
|
-
color: var(--pcoi-semantic-text-secondary);
|
|
46
|
-
line-height: var(--pcoi-semantic-type-step-line-height);
|
|
47
|
-
margin: 0;
|
|
48
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import figma from "@figma/code-connect";
|
|
2
|
-
import { HowStep } from "./HowStep";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Code Connect: HowStep
|
|
6
|
-
* Maps Figma step properties to React HowStep props
|
|
7
|
-
*/
|
|
8
|
-
figma.connect(HowStep, "https://www.figma.com/file/PCOIxDesignSystem/PCOI-Design-System?node-id=107-1", {
|
|
9
|
-
props: {
|
|
10
|
-
number: figma.string("Step Number"),
|
|
11
|
-
title: figma.string("Title"),
|
|
12
|
-
description: figma.string("Description"),
|
|
13
|
-
isLast: figma.boolean("Last Step"),
|
|
14
|
-
},
|
|
15
|
-
example: ({ number, title, description, isLast }) => (
|
|
16
|
-
<HowStep
|
|
17
|
-
number={number}
|
|
18
|
-
title={title}
|
|
19
|
-
description={description}
|
|
20
|
-
isLast={isLast}
|
|
21
|
-
/>
|
|
22
|
-
),
|
|
23
|
-
});
|