@pcoi/components 0.1.0 → 0.1.2

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 (72) hide show
  1. package/dist/components.css +1 -1
  2. package/dist/index.d.ts +50 -13
  3. package/dist/index.js +2 -2
  4. package/dist/index.mjs +499 -553
  5. package/package.json +14 -7
  6. package/src/Badge/Badge.css +2 -2
  7. package/src/Button/Button.css +4 -4
  8. package/src/Button/Button.figma.tsx +3 -5
  9. package/src/Button/Button.test.tsx +32 -0
  10. package/src/Callout/Callout.css +10 -5
  11. package/src/Callout/Callout.figma.tsx +25 -0
  12. package/src/Callout/Callout.tsx +14 -10
  13. package/src/Card/Card.css +8 -8
  14. package/src/Card/Card.figma.tsx +28 -0
  15. package/src/ChatInterface/ChatInterface.css +6 -5
  16. package/src/ChatInterface/ChatInterface.integration.test.tsx +123 -0
  17. package/src/ChatInterface/ChatInterface.tsx +6 -1
  18. package/src/ChatMessage/ChatMessage.css +8 -8
  19. package/src/ChatMessageList/ChatMessageList.css +4 -4
  20. package/src/ChatMessageList/ChatMessageList.test.tsx +70 -0
  21. package/src/ChatMessageList/ChatMessageList.tsx +7 -2
  22. package/src/Checkbox/Checkbox.css +6 -6
  23. package/src/CitationMark/CitationMark.css +3 -3
  24. package/src/CitedExcerpt/CitedExcerpt.css +7 -7
  25. package/src/CitedExcerpt/CitedExcerpt.tsx +2 -0
  26. package/src/ComparisonTable/ComparisonTable.css +6 -6
  27. package/src/ComparisonTable/ComparisonTable.tsx +6 -0
  28. package/src/ContactForm/ContactForm.css +5 -5
  29. package/src/ContactForm/ContactForm.tsx +2 -1
  30. package/src/DataTable/DataTable.css +4 -4
  31. package/src/DocumentOverlay/DocumentOverlay.css +5 -5
  32. package/src/DocumentOverlay/DocumentOverlay.test.tsx +95 -0
  33. package/src/DocumentOverlay/DocumentOverlay.tsx +1 -0
  34. package/src/Footer/Footer.css +9 -9
  35. package/src/Footer/Footer.tsx +5 -2
  36. package/src/FormField/FormField.css +4 -4
  37. package/src/FormField/FormField.figma.tsx +28 -0
  38. package/src/HowStep/HowStep.css +4 -4
  39. package/src/HowStep/HowStep.figma.tsx +23 -0
  40. package/src/LogoMark/LogoMark.tsx +3 -4
  41. package/src/Modal/Modal.css +11 -11
  42. package/src/Modal/Modal.figma.tsx +28 -0
  43. package/src/Modal/Modal.test.tsx +46 -0
  44. package/src/Modal/Modal.tsx +88 -85
  45. package/src/Nav/Nav.css +16 -16
  46. package/src/Nav/Nav.tsx +6 -2
  47. package/src/Panel/Panel.css +3 -3
  48. package/src/PromptBar/PromptBar.css +10 -10
  49. package/src/PromptBar/PromptBar.figma.tsx +25 -0
  50. package/src/PromptBar/PromptBar.test.tsx +83 -0
  51. package/src/PromptBar/PromptBar.tsx +2 -2
  52. package/src/RadioGroup/RadioGroup.css +11 -11
  53. package/src/SectionHeader/SectionHeader.css +4 -4
  54. package/src/SectionHeader/SectionHeader.figma.tsx +23 -0
  55. package/src/Select/Select.css +5 -5
  56. package/src/Select/Select.figma.tsx +33 -0
  57. package/src/Select/Select.tsx +1 -1
  58. package/src/SignalsPanel/SignalsPanel.css +9 -9
  59. package/src/SignalsPanel/SignalsPanel.tsx +2 -0
  60. package/src/SuggestionCard/SuggestionCard.css +5 -5
  61. package/src/SuggestionCards/SuggestionCards.css +1 -1
  62. package/src/SuggestionCards/SuggestionCards.test.tsx +27 -0
  63. package/src/SuggestionCards/SuggestionCards.tsx +1 -1
  64. package/src/Toast/Toast.css +14 -14
  65. package/src/Toast/Toast.tsx +50 -45
  66. package/src/Toggle/Toggle.css +15 -15
  67. package/src/Toggle/Toggle.figma.tsx +24 -0
  68. package/src/TypingIndicator/TypingIndicator.css +6 -6
  69. package/src/TypingIndicator/TypingIndicator.tsx +2 -2
  70. package/src/index.ts +2 -0
  71. package/src/styles.css +1 -0
  72. package/src/types.ts +1 -0
@@ -3,12 +3,12 @@
3
3
  .pcoi-modal {
4
4
  position: fixed;
5
5
  inset: 0;
6
- z-index: var(--pcoi-layout-zIndex-modal, 500);
6
+ z-index: var(--pcoi-semantic-layout-z-modal, 500);
7
7
  display: flex;
8
8
  align-items: center;
9
9
  justify-content: center;
10
10
  background: var(--pcoi-semantic-bg-overlay);
11
- padding: var(--pcoi-spacing-24);
11
+ padding: var(--pcoi-semantic-spacing-panel-padding);
12
12
  animation: pcoi-modal-fade-in 0.2s ease;
13
13
  }
14
14
 
@@ -16,10 +16,10 @@
16
16
  font-family: var(--pcoi-semantic-type-body-font);
17
17
  background: var(--pcoi-semantic-surface-elevated);
18
18
  border: 1px solid var(--pcoi-semantic-border-default);
19
- border-radius: var(--pcoi-radius-lg);
19
+ border-radius: var(--pcoi-semantic-radius-panel);
20
20
  box-shadow: var(--pcoi-effect-shadow-elevated);
21
21
  width: 100%;
22
- max-width: var(--pcoi-layout-container-modal);
22
+ max-width: var(--pcoi-semantic-sizing-modal-width);
23
23
  max-height: 85vh;
24
24
  display: flex;
25
25
  flex-direction: column;
@@ -30,7 +30,7 @@
30
30
  display: flex;
31
31
  align-items: center;
32
32
  justify-content: space-between;
33
- padding: var(--pcoi-spacing-20) var(--pcoi-spacing-24);
33
+ padding: var(--pcoi-semantic-spacing-card-gap) var(--pcoi-semantic-spacing-panel-padding);
34
34
  border-bottom: 1px solid var(--pcoi-semantic-border-default);
35
35
  }
36
36
 
@@ -48,8 +48,8 @@
48
48
  font-size: var(--pcoi-semantic-type-close-lg-size);
49
49
  line-height: var(--pcoi-semantic-type-none-line-height);
50
50
  cursor: pointer;
51
- padding: var(--pcoi-spacing-4);
52
- border-radius: var(--pcoi-radius-sm);
51
+ padding: var(--pcoi-semantic-spacing-control-padding-2xs);
52
+ border-radius: var(--pcoi-semantic-radius-btn);
53
53
  transition: color var(--pcoi-effect-transition-fast, 0.2s ease);
54
54
  }
55
55
 
@@ -67,7 +67,7 @@
67
67
  }
68
68
 
69
69
  .pcoi-modal__body {
70
- padding: var(--pcoi-spacing-24);
70
+ padding: var(--pcoi-semantic-spacing-panel-padding);
71
71
  overflow-y: auto;
72
72
  flex: 1;
73
73
  font-size: var(--pcoi-semantic-type-body-size);
@@ -79,14 +79,14 @@
79
79
  display: flex;
80
80
  align-items: center;
81
81
  justify-content: flex-end;
82
- gap: var(--pcoi-spacing-12);
83
- padding: var(--pcoi-spacing-16) var(--pcoi-spacing-24);
82
+ gap: var(--pcoi-semantic-spacing-panel-gap);
83
+ padding: var(--pcoi-semantic-spacing-panel-padding-sm) var(--pcoi-semantic-spacing-panel-padding);
84
84
  border-top: 1px solid var(--pcoi-semantic-border-default);
85
85
  }
86
86
 
87
87
  /* ── Size variants ── */
88
88
  .pcoi-modal__dialog--wide {
89
- max-width: var(--pcoi-layout-container-document, 900px);
89
+ max-width: var(--pcoi-semantic-sizing-document-width, 900px);
90
90
  }
91
91
 
92
92
  /* ── Animations ── */
@@ -0,0 +1,28 @@
1
+ import figma from "@figma/code-connect";
2
+ import { Modal } from "./Modal";
3
+
4
+ /**
5
+ * Code Connect: Modal
6
+ * Maps Figma modal variants to React Modal props
7
+ */
8
+ figma.connect(Modal, "https://www.figma.com/file/PCOIxDesignSystem/PCOI-Design-System?node-id=104-1", {
9
+ props: {
10
+ size: figma.enum("Size", {
11
+ Default: "default",
12
+ Wide: "wide",
13
+ }),
14
+ title: figma.string("Title"),
15
+ withFooter: figma.boolean("Footer"),
16
+ },
17
+ example: ({ size, title, withFooter }) => (
18
+ <Modal
19
+ open
20
+ onClose={() => undefined}
21
+ size={size}
22
+ title={title}
23
+ footer={withFooter ? <button type="button">Confirm</button> : undefined}
24
+ >
25
+ <p>Modal body content</p>
26
+ </Modal>
27
+ ),
28
+ });
@@ -0,0 +1,46 @@
1
+ import { fireEvent, render, screen } from "@testing-library/react";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { Modal } from "./Modal";
4
+
5
+ describe("Modal", () => {
6
+ it("does not render when closed", () => {
7
+ render(
8
+ <Modal open={false} onClose={vi.fn()} title="Dialog title">
9
+ Body
10
+ </Modal>
11
+ );
12
+
13
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
14
+ });
15
+
16
+ it("renders dialog content when open", () => {
17
+ render(
18
+ <Modal open onClose={vi.fn()} title="Dialog title">
19
+ Body content
20
+ </Modal>
21
+ );
22
+
23
+ expect(screen.getByRole("dialog")).toBeInTheDocument();
24
+ expect(screen.getByText("Dialog title")).toBeInTheDocument();
25
+ expect(screen.getByText("Body content")).toBeInTheDocument();
26
+ });
27
+
28
+ it("calls onClose for close button, Escape key, and backdrop click", () => {
29
+ const onClose = vi.fn();
30
+
31
+ render(
32
+ <Modal open onClose={onClose} title="Dialog title">
33
+ Body
34
+ </Modal>
35
+ );
36
+
37
+ fireEvent.click(screen.getByRole("button", { name: "Close modal" }));
38
+ fireEvent.keyDown(document, { key: "Escape" });
39
+
40
+ const backdrop = document.querySelector(".pcoi-modal");
41
+ if (!backdrop) throw new Error("Expected modal backdrop to exist");
42
+ fireEvent.click(backdrop);
43
+
44
+ expect(onClose).toHaveBeenCalledTimes(3);
45
+ });
46
+ });
@@ -1,6 +1,6 @@
1
1
  import React, { useEffect, useRef, useCallback } from "react";
2
2
  import { createPortal } from "react-dom";
3
- import { CloseIcon } from "../../../icons/src/react/CloseIcon";
3
+ import { CloseIcon } from "@pcoi/icons";
4
4
  import type { HeadingLevel } from "../types";
5
5
 
6
6
  export interface ModalProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
@@ -15,7 +15,7 @@ export interface ModalProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "
15
15
  /** Dialog width: "default" (520px) or "wide" (900px) */
16
16
  size?: "default" | "wide";
17
17
  /** Modal body content */
18
- children: React.ReactNode;
18
+ children?: React.ReactNode;
19
19
  /** Footer content (e.g. action buttons) */
20
20
  footer?: React.ReactNode;
21
21
  }
@@ -25,94 +25,97 @@ export interface ModalProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "
25
25
  * Tokens: bg/overlay, surface/elevated, border/default, shadow/elevated,
26
26
  * zIndex/modal, radius-lg, text/primary, text/secondary
27
27
  */
28
- export const Modal: React.FC<ModalProps> = ({
29
- open,
30
- onClose,
31
- title,
32
- headingLevel = "h2",
33
- size = "default",
34
- children,
35
- footer,
36
- className = "",
37
- ...rest
38
- }) => {
39
- const dialogRef = useRef<HTMLDivElement>(null);
40
- const previousFocusRef = useRef<HTMLElement | null>(null);
28
+ export const Modal = React.forwardRef<HTMLDivElement, ModalProps>(
29
+ (
30
+ {
31
+ open,
32
+ onClose,
33
+ title,
34
+ headingLevel = "h2",
35
+ size = "default",
36
+ children,
37
+ footer,
38
+ className = "",
39
+ ...rest
40
+ },
41
+ _ref
42
+ ) => {
43
+ const dialogRef = useRef<HTMLDivElement>(null);
44
+ const previousFocusRef = useRef<HTMLElement | null>(null);
41
45
 
42
- const Heading = headingLevel;
43
- const titleId = title ? "pcoi-modal-title" : undefined;
46
+ const Heading = headingLevel;
47
+ const titleId = title ? "pcoi-modal-title" : undefined;
44
48
 
45
- const handleKeyDown = useCallback(
46
- (e: KeyboardEvent) => {
47
- if (e.key === "Escape") {
48
- onClose();
49
- return;
50
- }
49
+ const handleKeyDown = useCallback(
50
+ (e: KeyboardEvent) => {
51
+ if (e.key === "Escape") {
52
+ onClose();
53
+ return;
54
+ }
51
55
 
52
- if (e.key === "Tab" && dialogRef.current) {
53
- const focusable = dialogRef.current.querySelectorAll<HTMLElement>(
54
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
55
- );
56
- if (focusable.length === 0) return;
56
+ if (e.key === "Tab" && dialogRef.current) {
57
+ const focusable = dialogRef.current.querySelectorAll<HTMLElement>(
58
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
59
+ );
60
+ if (focusable.length === 0) return;
57
61
 
58
- const first = focusable[0];
59
- const last = focusable[focusable.length - 1];
62
+ const first = focusable[0];
63
+ const last = focusable[focusable.length - 1];
60
64
 
61
- if (e.shiftKey && document.activeElement === first) {
62
- e.preventDefault();
63
- last.focus();
64
- } else if (!e.shiftKey && document.activeElement === last) {
65
- e.preventDefault();
66
- first.focus();
65
+ if (e.shiftKey && document.activeElement === first) {
66
+ e.preventDefault();
67
+ last.focus();
68
+ } else if (!e.shiftKey && document.activeElement === last) {
69
+ e.preventDefault();
70
+ first.focus();
71
+ }
67
72
  }
68
- }
69
- },
70
- [onClose]
71
- );
73
+ },
74
+ [onClose]
75
+ );
72
76
 
73
- useEffect(() => {
74
- if (open) {
75
- previousFocusRef.current = document.activeElement as HTMLElement;
76
- document.addEventListener("keydown", handleKeyDown);
77
- document.body.style.overflow = "hidden";
77
+ useEffect(() => {
78
+ if (open) {
79
+ previousFocusRef.current = document.activeElement as HTMLElement;
80
+ document.addEventListener("keydown", handleKeyDown);
81
+ document.body.style.overflow = "hidden";
78
82
 
79
- // Focus the dialog after render
80
- requestAnimationFrame(() => {
81
- const focusable = dialogRef.current?.querySelector<HTMLElement>(
82
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
83
- );
84
- focusable?.focus();
85
- });
86
- }
83
+ // Focus the dialog after render
84
+ requestAnimationFrame(() => {
85
+ const focusable = dialogRef.current?.querySelector<HTMLElement>(
86
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
87
+ );
88
+ focusable?.focus();
89
+ });
90
+ }
87
91
 
88
- return () => {
89
- document.removeEventListener("keydown", handleKeyDown);
90
- document.body.style.overflow = "";
91
- previousFocusRef.current?.focus();
92
- };
93
- }, [open, handleKeyDown]);
92
+ return () => {
93
+ document.removeEventListener("keydown", handleKeyDown);
94
+ document.body.style.overflow = "";
95
+ previousFocusRef.current?.focus();
96
+ };
97
+ }, [open, handleKeyDown]);
94
98
 
95
- const handleBackdropClick = (e: React.MouseEvent) => {
96
- if (e.target === e.currentTarget) {
97
- onClose();
98
- }
99
- };
99
+ const handleBackdropClick = (e: React.MouseEvent) => {
100
+ if (e.target === e.currentTarget) {
101
+ onClose();
102
+ }
103
+ };
100
104
 
101
- if (!open) return null;
105
+ if (!open) return null;
102
106
 
103
- const wrapperClasses = ["pcoi-modal", className].filter(Boolean).join(" ");
107
+ const wrapperClasses = ["pcoi-modal", className].filter(Boolean).join(" ");
104
108
 
105
- return createPortal(
106
- <div className={wrapperClasses} onClick={handleBackdropClick}>
107
- <div
108
- ref={dialogRef}
109
- className={`pcoi-modal__dialog${size === "wide" ? " pcoi-modal__dialog--wide" : ""}`}
110
- role="dialog"
111
- aria-modal="true"
112
- aria-labelledby={titleId}
113
- {...rest}
114
- >
115
- {(title || true) && (
109
+ return createPortal(
110
+ <div className={wrapperClasses} onClick={handleBackdropClick}>
111
+ <div
112
+ ref={dialogRef}
113
+ className={`pcoi-modal__dialog${size === "wide" ? " pcoi-modal__dialog--wide" : ""}`}
114
+ role="dialog"
115
+ aria-modal="true"
116
+ aria-labelledby={titleId}
117
+ {...rest}
118
+ >
116
119
  <div className="pcoi-modal__header">
117
120
  {title && (
118
121
  <Heading id={titleId} className="pcoi-modal__title">
@@ -128,14 +131,14 @@ export const Modal: React.FC<ModalProps> = ({
128
131
  <CloseIcon size={20} />
129
132
  </button>
130
133
  </div>
131
- )}
132
- <div className="pcoi-modal__body">{children}</div>
133
- {footer && <div className="pcoi-modal__footer">{footer}</div>}
134
- </div>
135
- </div>,
136
- document.body
137
- );
138
- };
134
+ <div className="pcoi-modal__body">{children}</div>
135
+ {footer && <div className="pcoi-modal__footer">{footer}</div>}
136
+ </div>
137
+ </div>,
138
+ document.body
139
+ );
140
+ }
141
+ );
139
142
 
140
143
  Modal.displayName = "Modal";
141
144
  export default Modal;
package/src/Nav/Nav.css CHANGED
@@ -5,8 +5,8 @@
5
5
  top: 0;
6
6
  left: 0;
7
7
  right: 0;
8
- z-index: var(--pcoi-layout-zIndex-nav, 1000);
9
- height: var(--pcoi-layout-nav-height, 72px);
8
+ z-index: var(--pcoi-semantic-layout-z-nav, 1000);
9
+ height: var(--pcoi-semantic-spacing-nav-height, 72px);
10
10
  transition: background var(--pcoi-effect-transition-medium, 0.3s ease),
11
11
  box-shadow var(--pcoi-effect-transition-medium, 0.3s ease);
12
12
  }
@@ -19,9 +19,9 @@
19
19
  }
20
20
 
21
21
  .pcoi-nav__inner {
22
- max-width: var(--pcoi-layout-container-max);
22
+ max-width: var(--pcoi-semantic-sizing-container-max);
23
23
  margin: 0 auto;
24
- padding: 0 var(--pcoi-spacing-24);
24
+ padding: 0 var(--pcoi-semantic-spacing-btn-x);
25
25
  height: 100%;
26
26
  display: flex;
27
27
  align-items: center;
@@ -32,7 +32,7 @@
32
32
  .pcoi-nav__links {
33
33
  display: flex;
34
34
  align-items: center;
35
- gap: var(--pcoi-spacing-32);
35
+ gap: var(--pcoi-semantic-spacing-stack-lg);
36
36
  }
37
37
 
38
38
  .pcoi-nav__links a {
@@ -50,7 +50,7 @@
50
50
  .pcoi-nav__links a:focus-visible {
51
51
  outline: none;
52
52
  box-shadow: var(--pcoi-effect-shadow-focus-ring);
53
- border-radius: var(--pcoi-radius-sm);
53
+ border-radius: var(--pcoi-semantic-radius-btn);
54
54
  }
55
55
 
56
56
  .pcoi-nav__links a:active {
@@ -62,9 +62,9 @@
62
62
  display: none;
63
63
  flex-direction: column;
64
64
  justify-content: center;
65
- gap: var(--pcoi-spacing-5);
66
- width: var(--pcoi-layout-component-hamburger);
67
- height: var(--pcoi-layout-component-hamburger);
65
+ gap: var(--pcoi-semantic-spacing-hamburger-gap);
66
+ width: var(--pcoi-semantic-sizing-hamburger-size);
67
+ height: var(--pcoi-semantic-sizing-hamburger-size);
68
68
  background: none;
69
69
  border: none;
70
70
  cursor: pointer;
@@ -78,7 +78,7 @@
78
78
  .pcoi-nav__hamburger:focus-visible {
79
79
  outline: none;
80
80
  box-shadow: var(--pcoi-effect-shadow-focus-ring);
81
- border-radius: var(--pcoi-radius-sm);
81
+ border-radius: var(--pcoi-semantic-radius-btn);
82
82
  }
83
83
 
84
84
  .pcoi-nav__hamburger:active {
@@ -96,7 +96,7 @@
96
96
  }
97
97
 
98
98
  .pcoi-nav__hamburger--active span:nth-child(1) {
99
- transform: translateY(var(--pcoi-layout-component-hamburger-bar-offset)) rotate(45deg);
99
+ transform: translateY(var(--pcoi-semantic-spacing-hamburger-bar-offset)) rotate(45deg);
100
100
  }
101
101
 
102
102
  .pcoi-nav__hamburger--active span:nth-child(2) {
@@ -104,14 +104,14 @@
104
104
  }
105
105
 
106
106
  .pcoi-nav__hamburger--active span:nth-child(3) {
107
- transform: translateY(calc(-1 * var(--pcoi-layout-component-hamburger-bar-offset))) rotate(-45deg);
107
+ transform: translateY(calc(-1 * var(--pcoi-semantic-spacing-hamburger-bar-offset))) rotate(-45deg);
108
108
  }
109
109
 
110
110
  /* ── Mobile Menu ── */
111
111
  .pcoi-nav__mobile-menu {
112
112
  display: none;
113
113
  flex-direction: column;
114
- padding: var(--pcoi-spacing-24) var(--pcoi-spacing-24) var(--pcoi-spacing-32);
114
+ padding: var(--pcoi-semantic-spacing-btn-x) var(--pcoi-semantic-spacing-btn-x) var(--pcoi-semantic-spacing-stack-lg);
115
115
  background: var(--pcoi-semantic-bg-default);
116
116
  box-shadow: var(--pcoi-effect-shadow-mobile-menu);
117
117
  }
@@ -121,7 +121,7 @@
121
121
  font-weight: var(--pcoi-semantic-type-label-weight);
122
122
  color: var(--pcoi-semantic-text-secondary);
123
123
  text-decoration: none;
124
- padding: var(--pcoi-spacing-12) 0;
124
+ padding: var(--pcoi-semantic-spacing-input-y) 0;
125
125
  transition: color var(--pcoi-effect-transition-fast, 0.2s ease);
126
126
  }
127
127
 
@@ -132,7 +132,7 @@
132
132
  .pcoi-nav__mobile-menu a:focus-visible {
133
133
  outline: none;
134
134
  box-shadow: var(--pcoi-effect-shadow-focus-ring);
135
- border-radius: var(--pcoi-radius-sm);
135
+ border-radius: var(--pcoi-semantic-radius-btn);
136
136
  }
137
137
 
138
138
  .pcoi-nav__mobile-menu a:active {
@@ -140,7 +140,7 @@
140
140
  }
141
141
 
142
142
  .pcoi-nav__mobile-menu .pcoi-btn {
143
- margin-top: var(--pcoi-spacing-16);
143
+ margin-top: var(--pcoi-semantic-spacing-stack-md);
144
144
  width: 100%;
145
145
  justify-content: center;
146
146
  }
package/src/Nav/Nav.tsx CHANGED
@@ -7,9 +7,13 @@ import type { LinkItem } from "../types";
7
7
  export type NavLink = LinkItem;
8
8
 
9
9
  export interface NavProps extends React.HTMLAttributes<HTMLElement> {
10
+ /** Navigation links rendered in the navbar */
10
11
  links?: LinkItem[];
12
+ /** Label text for the call-to-action button */
11
13
  ctaLabel?: string;
14
+ /** URL the CTA button links to */
12
15
  ctaHref?: string;
16
+ /** Called when the CTA button is clicked */
13
17
  onCtaClick?: () => void;
14
18
  }
15
19
 
@@ -82,9 +86,9 @@ export const Nav = React.forwardRef<HTMLElement, NavProps>(
82
86
  <span /><span /><span />
83
87
  </button>
84
88
  </div>
85
- <div className="pcoi-nav__mobile-menu" id="pcoi-mobile-menu">
89
+ <div className="pcoi-nav__mobile-menu" id="pcoi-mobile-menu" role="menu" aria-hidden={!menuOpen}>
86
90
  {links.map((link) => (
87
- <a key={link.href} href={link.href} data-track-id={link.trackingId} onClick={() => setMenuOpen(false)}>
91
+ <a key={link.href} href={link.href} data-track-id={link.trackingId} role="menuitem" tabIndex={menuOpen ? 0 : -1} onClick={() => setMenuOpen(false)}>
88
92
  {link.label}
89
93
  </a>
90
94
  ))}
@@ -3,7 +3,7 @@
3
3
  .pcoi-panel {
4
4
  background: var(--pcoi-semantic-bg-card);
5
5
  border: 1px solid var(--pcoi-semantic-border-default);
6
- border-radius: var(--pcoi-radius-md);
6
+ border-radius: var(--pcoi-semantic-radius-card);
7
7
  overflow: hidden;
8
8
  }
9
9
 
@@ -11,7 +11,7 @@
11
11
  display: flex;
12
12
  align-items: center;
13
13
  justify-content: space-between;
14
- padding: var(--pcoi-spacing-16) var(--pcoi-spacing-24);
14
+ padding: var(--pcoi-semantic-spacing-panel-padding-sm) var(--pcoi-semantic-spacing-panel-padding);
15
15
  border-bottom: 1px solid var(--pcoi-semantic-border-default);
16
16
  }
17
17
 
@@ -27,7 +27,7 @@
27
27
  }
28
28
 
29
29
  .pcoi-panel__body {
30
- padding: var(--pcoi-spacing-24);
30
+ padding: var(--pcoi-semantic-spacing-panel-padding);
31
31
  }
32
32
 
33
33
  .pcoi-panel--flush .pcoi-panel__body {
@@ -3,11 +3,11 @@
3
3
  .pcoi-prompt-bar {
4
4
  display: flex;
5
5
  align-items: flex-end;
6
- gap: var(--pcoi-spacing-12);
7
- padding: var(--pcoi-spacing-12) var(--pcoi-spacing-16);
8
- background: var(--pcoi-color-bg-card);
6
+ gap: var(--pcoi-semantic-spacing-panel-gap);
7
+ padding: var(--pcoi-semantic-spacing-input-y) var(--pcoi-semantic-spacing-input-x);
8
+ background: var(--pcoi-semantic-bg-card);
9
9
  border: 1px solid var(--pcoi-semantic-border-default);
10
- border-radius: var(--pcoi-radius-md);
10
+ border-radius: var(--pcoi-semantic-radius-card);
11
11
  transition:
12
12
  border-color var(--pcoi-effect-transition-fast),
13
13
  box-shadow var(--pcoi-effect-transition-fast);
@@ -18,7 +18,7 @@
18
18
  }
19
19
 
20
20
  .pcoi-prompt-bar:focus-within {
21
- border-color: var(--pcoi-color-focus-border);
21
+ border-color: var(--pcoi-semantic-focus-border);
22
22
  box-shadow: var(--pcoi-effect-shadow-focus-ring);
23
23
  }
24
24
 
@@ -26,7 +26,7 @@
26
26
  flex: 1;
27
27
  min-height: 24px;
28
28
  max-height: 120px;
29
- padding: var(--pcoi-spacing-4) 0;
29
+ padding: var(--pcoi-semantic-spacing-control-padding-2xs) 0;
30
30
  font-family: var(--pcoi-semantic-type-body-font);
31
31
  font-size: var(--pcoi-semantic-type-body-size);
32
32
  line-height: var(--pcoi-semantic-type-body-line-height);
@@ -48,11 +48,11 @@
48
48
  }
49
49
 
50
50
  .pcoi-prompt-bar__send.pcoi-btn {
51
- padding: var(--pcoi-spacing-6);
51
+ padding: var(--pcoi-semantic-spacing-control-padding-xs);
52
52
  min-width: 0;
53
53
  min-height: 0;
54
- width: 32px;
55
- height: 32px;
54
+ width: var(--pcoi-semantic-sizing-btn-height-nav);
55
+ height: var(--pcoi-semantic-sizing-btn-height-nav);
56
56
  }
57
57
 
58
58
  .pcoi-prompt-bar__send-icon {
@@ -63,6 +63,6 @@
63
63
  /* ── Responsive: compact send on small mobile ── */
64
64
  @media (max-width: 480px) {
65
65
  .pcoi-prompt-bar {
66
- padding: var(--pcoi-spacing-8) var(--pcoi-spacing-12);
66
+ padding: var(--pcoi-semantic-spacing-control-padding-sm) var(--pcoi-semantic-spacing-input-y);
67
67
  }
68
68
  }
@@ -0,0 +1,25 @@
1
+ import figma from "@figma/code-connect";
2
+ import { PromptBar } from "./PromptBar";
3
+
4
+ /**
5
+ * Code Connect: PromptBar
6
+ * Maps Figma prompt bar states to React PromptBar props
7
+ */
8
+ figma.connect(PromptBar, "https://www.figma.com/file/PCOIxDesignSystem/PCOI-Design-System?node-id=105-1", {
9
+ props: {
10
+ value: figma.string("Value"),
11
+ placeholder: figma.string("Placeholder"),
12
+ disabled: figma.boolean("Disabled"),
13
+ loading: figma.boolean("Loading"),
14
+ },
15
+ example: ({ value, placeholder, disabled, loading }) => (
16
+ <PromptBar
17
+ value={value}
18
+ placeholder={placeholder}
19
+ disabled={disabled}
20
+ loading={loading}
21
+ onChange={() => undefined}
22
+ onSubmit={() => undefined}
23
+ />
24
+ ),
25
+ });