@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.
- package/dist/components.css +1 -1
- package/dist/index.d.ts +50 -13
- package/dist/index.js +2 -2
- package/dist/index.mjs +499 -553
- package/package.json +14 -7
- package/src/Badge/Badge.css +2 -2
- package/src/Button/Button.css +4 -4
- package/src/Button/Button.figma.tsx +3 -5
- package/src/Button/Button.test.tsx +32 -0
- package/src/Callout/Callout.css +10 -5
- package/src/Callout/Callout.figma.tsx +25 -0
- package/src/Callout/Callout.tsx +14 -10
- package/src/Card/Card.css +8 -8
- package/src/Card/Card.figma.tsx +28 -0
- package/src/ChatInterface/ChatInterface.css +6 -5
- package/src/ChatInterface/ChatInterface.integration.test.tsx +123 -0
- package/src/ChatInterface/ChatInterface.tsx +6 -1
- package/src/ChatMessage/ChatMessage.css +8 -8
- package/src/ChatMessageList/ChatMessageList.css +4 -4
- package/src/ChatMessageList/ChatMessageList.test.tsx +70 -0
- package/src/ChatMessageList/ChatMessageList.tsx +7 -2
- package/src/Checkbox/Checkbox.css +6 -6
- package/src/CitationMark/CitationMark.css +3 -3
- package/src/CitedExcerpt/CitedExcerpt.css +7 -7
- package/src/CitedExcerpt/CitedExcerpt.tsx +2 -0
- package/src/ComparisonTable/ComparisonTable.css +6 -6
- package/src/ComparisonTable/ComparisonTable.tsx +6 -0
- package/src/ContactForm/ContactForm.css +5 -5
- package/src/ContactForm/ContactForm.tsx +2 -1
- package/src/DataTable/DataTable.css +4 -4
- package/src/DocumentOverlay/DocumentOverlay.css +5 -5
- package/src/DocumentOverlay/DocumentOverlay.test.tsx +95 -0
- package/src/DocumentOverlay/DocumentOverlay.tsx +1 -0
- package/src/Footer/Footer.css +9 -9
- package/src/Footer/Footer.tsx +5 -2
- package/src/FormField/FormField.css +4 -4
- package/src/FormField/FormField.figma.tsx +28 -0
- package/src/HowStep/HowStep.css +4 -4
- package/src/HowStep/HowStep.figma.tsx +23 -0
- package/src/LogoMark/LogoMark.tsx +3 -4
- package/src/Modal/Modal.css +11 -11
- package/src/Modal/Modal.figma.tsx +28 -0
- package/src/Modal/Modal.test.tsx +46 -0
- package/src/Modal/Modal.tsx +88 -85
- package/src/Nav/Nav.css +16 -16
- package/src/Nav/Nav.tsx +6 -2
- package/src/Panel/Panel.css +3 -3
- package/src/PromptBar/PromptBar.css +10 -10
- package/src/PromptBar/PromptBar.figma.tsx +25 -0
- package/src/PromptBar/PromptBar.test.tsx +83 -0
- package/src/PromptBar/PromptBar.tsx +2 -2
- package/src/RadioGroup/RadioGroup.css +11 -11
- package/src/SectionHeader/SectionHeader.css +4 -4
- package/src/SectionHeader/SectionHeader.figma.tsx +23 -0
- package/src/Select/Select.css +5 -5
- package/src/Select/Select.figma.tsx +33 -0
- package/src/Select/Select.tsx +1 -1
- package/src/SignalsPanel/SignalsPanel.css +9 -9
- package/src/SignalsPanel/SignalsPanel.tsx +2 -0
- package/src/SuggestionCard/SuggestionCard.css +5 -5
- package/src/SuggestionCards/SuggestionCards.css +1 -1
- package/src/SuggestionCards/SuggestionCards.test.tsx +27 -0
- package/src/SuggestionCards/SuggestionCards.tsx +1 -1
- package/src/Toast/Toast.css +14 -14
- package/src/Toast/Toast.tsx +50 -45
- package/src/Toggle/Toggle.css +15 -15
- package/src/Toggle/Toggle.figma.tsx +24 -0
- package/src/TypingIndicator/TypingIndicator.css +6 -6
- package/src/TypingIndicator/TypingIndicator.tsx +2 -2
- package/src/index.ts +2 -0
- package/src/styles.css +1 -0
- package/src/types.ts +1 -0
package/src/Modal/Modal.css
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
.pcoi-modal {
|
|
4
4
|
position: fixed;
|
|
5
5
|
inset: 0;
|
|
6
|
-
z-index: var(--pcoi-layout-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
52
|
-
border-radius: var(--pcoi-radius-
|
|
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-
|
|
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-
|
|
83
|
-
padding: var(--pcoi-spacing-
|
|
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-
|
|
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
|
+
});
|
package/src/Modal/Modal.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useRef, useCallback } from "react";
|
|
2
2
|
import { createPortal } from "react-dom";
|
|
3
|
-
import { CloseIcon } from "
|
|
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
|
|
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
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
43
|
-
|
|
46
|
+
const Heading = headingLevel;
|
|
47
|
+
const titleId = title ? "pcoi-modal-title" : undefined;
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
const handleKeyDown = useCallback(
|
|
50
|
+
(e: KeyboardEvent) => {
|
|
51
|
+
if (e.key === "Escape") {
|
|
52
|
+
onClose();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
51
55
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
59
|
-
|
|
62
|
+
const first = focusable[0];
|
|
63
|
+
const last = focusable[focusable.length - 1];
|
|
60
64
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
71
|
-
);
|
|
73
|
+
},
|
|
74
|
+
[onClose]
|
|
75
|
+
);
|
|
72
76
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
return () => {
|
|
93
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
94
|
+
document.body.style.overflow = "";
|
|
95
|
+
previousFocusRef.current?.focus();
|
|
96
|
+
};
|
|
97
|
+
}, [open, handleKeyDown]);
|
|
94
98
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
99
|
+
const handleBackdropClick = (e: React.MouseEvent) => {
|
|
100
|
+
if (e.target === e.currentTarget) {
|
|
101
|
+
onClose();
|
|
102
|
+
}
|
|
103
|
+
};
|
|
100
104
|
|
|
101
|
-
|
|
105
|
+
if (!open) return null;
|
|
102
106
|
|
|
103
|
-
|
|
107
|
+
const wrapperClasses = ["pcoi-modal", className].filter(Boolean).join(" ");
|
|
104
108
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
</div
|
|
135
|
-
|
|
136
|
-
|
|
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-
|
|
9
|
-
height: var(--pcoi-
|
|
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-
|
|
22
|
+
max-width: var(--pcoi-semantic-sizing-container-max);
|
|
23
23
|
margin: 0 auto;
|
|
24
|
-
padding: 0 var(--pcoi-spacing-
|
|
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-
|
|
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-
|
|
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-
|
|
66
|
-
width: var(--pcoi-
|
|
67
|
-
height: var(--pcoi-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
))}
|
package/src/Panel/Panel.css
CHANGED
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
7
|
-
padding: var(--pcoi-spacing-
|
|
8
|
-
background: var(--pcoi-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
51
|
+
padding: var(--pcoi-semantic-spacing-control-padding-xs);
|
|
52
52
|
min-width: 0;
|
|
53
53
|
min-height: 0;
|
|
54
|
-
width:
|
|
55
|
-
height:
|
|
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-
|
|
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
|
+
});
|