@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,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,86 +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
- open,
69
- onClose,
70
- title,
71
- size: "wide",
72
- footer,
73
- className: classes,
74
- ...rest,
75
- },
76
- React.createElement(
77
- "div",
78
- { ref: contentRef, className: "pcoi-doc-overlay__content" },
79
- children
80
- )
81
- );
82
- }
83
- );
84
-
85
- DocumentOverlay.displayName = "DocumentOverlay";
86
- export default DocumentOverlay;
@@ -1,2 +0,0 @@
1
- export { DocumentOverlay, type DocumentOverlayProps } from "./DocumentOverlay";
2
- export { default } from "./DocumentOverlay";
@@ -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
- }
@@ -1,56 +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
- links?: LinkItem[];
10
- tagline?: string;
11
- copyright?: string;
12
- }
13
-
14
- /**
15
- * PCOI Footer
16
- * Tokens: border/default, spacing-60/40/24, text/secondary, text/muted
17
- */
18
- export const Footer = React.forwardRef<HTMLElement, FooterProps>(
19
- (
20
- {
21
- links = [
22
- { label: "Problem", href: "#problem" },
23
- { label: "How It Works", href: "#how-it-works" },
24
- { label: "Difference", href: "#difference" },
25
- { label: "Principles", href: "#principles" },
26
- { label: "Who It's For", href: "#who" },
27
- { label: "Contact", href: "#contact" },
28
- ],
29
- tagline = "Portable, Company-Owned\nOperational Intelligence",
30
- copyright = `© ${new Date().getFullYear()} PCOI. Operational intelligence you own.`,
31
- className = "",
32
- ...rest
33
- },
34
- ref
35
- ) => (
36
- <footer ref={ref} className={`pcoi-footer ${className}`} {...rest}>
37
- <div className="pcoi-footer__inner">
38
- <div className="pcoi-footer__brand">
39
- <LogoMark className="pcoi-footer__logo" />
40
- <p className="pcoi-footer__tagline">{tagline}</p>
41
- </div>
42
- <div className="pcoi-footer__links">
43
- {links.map((link) => (
44
- <a key={link.href} href={link.href} data-track-id={link.trackingId}>{link.label}</a>
45
- ))}
46
- </div>
47
- </div>
48
- <div className="pcoi-footer__bottom">
49
- <p>{copyright}</p>
50
- </div>
51
- </footer>
52
- )
53
- );
54
-
55
- Footer.displayName = "Footer";
56
- export default Footer;
@@ -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;
@@ -1,2 +0,0 @@
1
- export { FormField, type FormFieldProps } from "./FormField";
2
- export { default } from "./FormField";
@@ -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
- });