@pcoi/components 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/dist/components.css +1 -0
  2. package/dist/index.d.ts +667 -0
  3. package/dist/index.js +2 -0
  4. package/dist/index.mjs +1048 -0
  5. package/package.json +36 -0
  6. package/src/Badge/Badge.css +40 -0
  7. package/src/Badge/Badge.tsx +36 -0
  8. package/src/Badge/index.ts +2 -0
  9. package/src/Button/Button.css +93 -0
  10. package/src/Button/Button.figma.tsx +29 -0
  11. package/src/Button/Button.tsx +47 -0
  12. package/src/Button/index.ts +1 -0
  13. package/src/Callout/Callout.css +43 -0
  14. package/src/Callout/Callout.tsx +39 -0
  15. package/src/Callout/index.ts +1 -0
  16. package/src/Card/Card.css +88 -0
  17. package/src/Card/Card.tsx +60 -0
  18. package/src/Card/index.ts +1 -0
  19. package/src/ChatInterface/ChatInterface.css +49 -0
  20. package/src/ChatInterface/ChatInterface.tsx +120 -0
  21. package/src/ChatInterface/index.ts +6 -0
  22. package/src/ChatMessage/ChatMessage.css +55 -0
  23. package/src/ChatMessage/ChatMessage.tsx +71 -0
  24. package/src/ChatMessage/index.ts +2 -0
  25. package/src/ChatMessageList/ChatMessageList.css +24 -0
  26. package/src/ChatMessageList/ChatMessageList.tsx +51 -0
  27. package/src/ChatMessageList/index.ts +2 -0
  28. package/src/Checkbox/Checkbox.css +97 -0
  29. package/src/Checkbox/Checkbox.tsx +70 -0
  30. package/src/Checkbox/index.ts +2 -0
  31. package/src/CitationMark/CitationMark.css +40 -0
  32. package/src/CitationMark/CitationMark.tsx +38 -0
  33. package/src/CitationMark/index.ts +2 -0
  34. package/src/CitedExcerpt/CitedExcerpt.css +75 -0
  35. package/src/CitedExcerpt/CitedExcerpt.tsx +51 -0
  36. package/src/CitedExcerpt/index.ts +2 -0
  37. package/src/ComparisonTable/ComparisonTable.css +66 -0
  38. package/src/ComparisonTable/ComparisonTable.tsx +48 -0
  39. package/src/ComparisonTable/index.ts +1 -0
  40. package/src/ContactForm/ContactForm.css +38 -0
  41. package/src/ContactForm/ContactForm.tsx +57 -0
  42. package/src/ContactForm/index.ts +1 -0
  43. package/src/DataTable/DataTable.css +56 -0
  44. package/src/DataTable/DataTable.tsx +104 -0
  45. package/src/DataTable/index.ts +2 -0
  46. package/src/DocumentOverlay/DocumentOverlay.css +57 -0
  47. package/src/DocumentOverlay/DocumentOverlay.tsx +86 -0
  48. package/src/DocumentOverlay/index.ts +2 -0
  49. package/src/Footer/Footer.css +72 -0
  50. package/src/Footer/Footer.tsx +56 -0
  51. package/src/Footer/index.ts +1 -0
  52. package/src/FormField/FormField.css +78 -0
  53. package/src/FormField/FormField.tsx +103 -0
  54. package/src/FormField/index.ts +2 -0
  55. package/src/HowStep/HowStep.css +48 -0
  56. package/src/HowStep/HowStep.tsx +38 -0
  57. package/src/HowStep/index.ts +1 -0
  58. package/src/LogoMark/LogoMark.css +16 -0
  59. package/src/LogoMark/LogoMark.tsx +25 -0
  60. package/src/LogoMark/index.ts +2 -0
  61. package/src/Modal/Modal.css +101 -0
  62. package/src/Modal/Modal.tsx +141 -0
  63. package/src/Modal/index.ts +2 -0
  64. package/src/Nav/Nav.css +161 -0
  65. package/src/Nav/Nav.tsx +101 -0
  66. package/src/Nav/index.ts +1 -0
  67. package/src/Panel/Panel.css +35 -0
  68. package/src/Panel/Panel.tsx +61 -0
  69. package/src/Panel/index.ts +2 -0
  70. package/src/PromptBar/PromptBar.css +68 -0
  71. package/src/PromptBar/PromptBar.tsx +93 -0
  72. package/src/PromptBar/index.ts +2 -0
  73. package/src/RadioGroup/RadioGroup.css +117 -0
  74. package/src/RadioGroup/RadioGroup.tsx +112 -0
  75. package/src/RadioGroup/index.ts +2 -0
  76. package/src/SectionHeader/SectionHeader.css +38 -0
  77. package/src/SectionHeader/SectionHeader.tsx +55 -0
  78. package/src/SectionHeader/index.ts +1 -0
  79. package/src/Select/Select.css +90 -0
  80. package/src/Select/Select.tsx +100 -0
  81. package/src/Select/index.ts +2 -0
  82. package/src/SignalsPanel/SignalsPanel.css +51 -0
  83. package/src/SignalsPanel/SignalsPanel.tsx +33 -0
  84. package/src/SignalsPanel/index.ts +1 -0
  85. package/src/SuggestionCard/SuggestionCard.css +51 -0
  86. package/src/SuggestionCard/SuggestionCard.tsx +34 -0
  87. package/src/SuggestionCard/index.ts +2 -0
  88. package/src/SuggestionCards/SuggestionCards.css +15 -0
  89. package/src/SuggestionCards/SuggestionCards.tsx +40 -0
  90. package/src/SuggestionCards/index.ts +2 -0
  91. package/src/Toast/Toast.css +85 -0
  92. package/src/Toast/Toast.tsx +77 -0
  93. package/src/Toast/index.ts +2 -0
  94. package/src/Toggle/Toggle.css +110 -0
  95. package/src/Toggle/Toggle.tsx +73 -0
  96. package/src/Toggle/index.ts +2 -0
  97. package/src/TypingIndicator/TypingIndicator.css +70 -0
  98. package/src/TypingIndicator/TypingIndicator.tsx +37 -0
  99. package/src/TypingIndicator/index.ts +2 -0
  100. package/src/index.ts +37 -0
  101. package/src/styles/utilities.css +14 -0
  102. package/src/styles.css +32 -0
  103. package/src/types.ts +65 -0
@@ -0,0 +1,120 @@
1
+ import React from "react";
2
+ import { ChatMessageList } from "../ChatMessageList/ChatMessageList";
3
+ import { ChatMessage } from "../ChatMessage/ChatMessage";
4
+ import { SuggestionCards } from "../SuggestionCards/SuggestionCards";
5
+ import { PromptBar } from "../PromptBar/PromptBar";
6
+ import { TypingIndicator } from "../TypingIndicator/TypingIndicator";
7
+ import type { ChatMessageRole, Citation, Suggestion } from "../types";
8
+
9
+ export interface ChatInterfaceMessage {
10
+ id: string;
11
+ role: ChatMessageRole;
12
+ content: React.ReactNode;
13
+ citations?: Citation[];
14
+ timestamp?: string;
15
+ }
16
+
17
+ export interface ChatInterfaceProps
18
+ extends React.HTMLAttributes<HTMLDivElement> {
19
+ /** Conversation messages */
20
+ messages: ChatInterfaceMessage[];
21
+ /** Suggestion prompts for the empty state */
22
+ suggestions?: Suggestion[];
23
+ /** Current prompt textarea value */
24
+ promptValue: string;
25
+ /** Called when the prompt textarea changes */
26
+ onPromptChange: (value: string) => void;
27
+ /** Called when the user submits a prompt */
28
+ onPromptSubmit: (value: string) => void;
29
+ /** Called when a suggestion card is clicked */
30
+ onSuggestionSelect?: (suggestion: Suggestion) => void;
31
+ /** Called when a citation is clicked */
32
+ onCitationClick?: (citation: Citation) => void;
33
+ /** Textarea placeholder */
34
+ placeholder?: string;
35
+ /** Disable input while loading */
36
+ loading?: boolean;
37
+ }
38
+
39
+ /**
40
+ * PCOI ChatInterface — Top-level chat composition
41
+ * Empty state: suggestions + prompt. Active state: messages + prompt.
42
+ * Tokens: bg/default, container/narrow, spacing-16/24
43
+ */
44
+ export const ChatInterface = React.forwardRef<
45
+ HTMLDivElement,
46
+ ChatInterfaceProps
47
+ >(
48
+ (
49
+ {
50
+ messages,
51
+ suggestions,
52
+ promptValue,
53
+ onPromptChange,
54
+ onPromptSubmit,
55
+ onSuggestionSelect,
56
+ onCitationClick,
57
+ placeholder,
58
+ loading = false,
59
+ className = "",
60
+ ...rest
61
+ },
62
+ ref
63
+ ) => {
64
+ const isEmpty = messages.length === 0;
65
+
66
+ const classes = [
67
+ "pcoi-chat",
68
+ isEmpty ? "pcoi-chat--empty" : "",
69
+ className,
70
+ ]
71
+ .filter(Boolean)
72
+ .join(" ");
73
+
74
+ return (
75
+ <div ref={ref} className={classes} {...rest}>
76
+ {isEmpty ? (
77
+ <div className="pcoi-chat__empty-state">
78
+ {suggestions && suggestions.length > 0 && onSuggestionSelect && (
79
+ <div className="pcoi-chat__suggestions">
80
+ <SuggestionCards
81
+ suggestions={suggestions}
82
+ onSelect={onSuggestionSelect}
83
+ />
84
+ </div>
85
+ )}
86
+ </div>
87
+ ) : (
88
+ <ChatMessageList className="pcoi-chat__messages">
89
+ {messages.map((msg) => (
90
+ <ChatMessage
91
+ key={msg.id}
92
+ role={msg.role}
93
+ citations={msg.citations}
94
+ timestamp={msg.timestamp}
95
+ onCitationClick={onCitationClick}
96
+ >
97
+ {msg.content}
98
+ </ChatMessage>
99
+ ))}
100
+ {loading && <TypingIndicator />}
101
+ </ChatMessageList>
102
+ )}
103
+
104
+ <div className="pcoi-chat__prompt">
105
+ <PromptBar
106
+ value={promptValue}
107
+ onChange={onPromptChange}
108
+ onSubmit={onPromptSubmit}
109
+ placeholder={placeholder}
110
+ loading={loading}
111
+ disabled={loading}
112
+ />
113
+ </div>
114
+ </div>
115
+ );
116
+ }
117
+ );
118
+
119
+ ChatInterface.displayName = "ChatInterface";
120
+ export default ChatInterface;
@@ -0,0 +1,6 @@
1
+ export {
2
+ ChatInterface,
3
+ type ChatInterfaceProps,
4
+ type ChatInterfaceMessage,
5
+ } from "./ChatInterface";
6
+ export { default } from "./ChatInterface";
@@ -0,0 +1,55 @@
1
+ /* ChatMessage — @pcoi/components */
2
+
3
+ .pcoi-chat-message {
4
+ display: flex;
5
+ flex-direction: column;
6
+ gap: var(--pcoi-spacing-8);
7
+ padding: var(--pcoi-spacing-16);
8
+ border-radius: var(--pcoi-radius-md);
9
+ border: 1px solid var(--pcoi-semantic-border-card);
10
+ }
11
+
12
+ /* ── Role variants ── */
13
+ .pcoi-chat-message--user {
14
+ background: var(--pcoi-color-surface);
15
+ }
16
+
17
+ .pcoi-chat-message--assistant {
18
+ background: var(--pcoi-semantic-surface-accent-dim);
19
+ }
20
+
21
+ .pcoi-chat-message__header {
22
+ display: flex;
23
+ align-items: center;
24
+ }
25
+
26
+ .pcoi-chat-message__body {
27
+ font-family: var(--pcoi-semantic-type-body-font);
28
+ font-size: var(--pcoi-semantic-type-body-size);
29
+ line-height: var(--pcoi-semantic-type-body-line-height);
30
+ color: var(--pcoi-semantic-text-primary);
31
+ }
32
+
33
+ .pcoi-chat-message__citations {
34
+ display: flex;
35
+ flex-direction: column;
36
+ gap: var(--pcoi-spacing-8);
37
+ margin-top: var(--pcoi-spacing-4);
38
+ }
39
+
40
+ .pcoi-chat-message__timestamp {
41
+ font-family: var(--pcoi-semantic-type-body-font);
42
+ font-size: var(--pcoi-semantic-type-body-sm-size);
43
+ color: var(--pcoi-semantic-text-muted);
44
+ }
45
+
46
+ /* ── Responsive: offset margins on desktop ── */
47
+ @media (min-width: 1025px) {
48
+ .pcoi-chat-message--user {
49
+ margin-left: var(--pcoi-spacing-48);
50
+ }
51
+
52
+ .pcoi-chat-message--assistant {
53
+ margin-right: var(--pcoi-spacing-48);
54
+ }
55
+ }
@@ -0,0 +1,71 @@
1
+ import React from "react";
2
+ import { Badge } from "../Badge/Badge";
3
+ import { CitedExcerpt } from "../CitedExcerpt/CitedExcerpt";
4
+ import type { ChatMessageRole, Citation } from "../types";
5
+
6
+ export interface ChatMessageProps
7
+ extends React.HTMLAttributes<HTMLDivElement> {
8
+ /** Message sender role */
9
+ role: ChatMessageRole;
10
+ /** Message body content */
11
+ children: React.ReactNode;
12
+ /** Citations referenced in the message */
13
+ citations?: Citation[];
14
+ /** Displayed below the message body */
15
+ timestamp?: string;
16
+ /** Called when a citation marker or source link is clicked */
17
+ onCitationClick?: (citation: Citation) => void;
18
+ }
19
+
20
+ /**
21
+ * PCOI ChatMessage — User or assistant message bubble
22
+ * Tokens: surface/elevated (user), bg/card (assistant), border/card,
23
+ * radius-md, text/primary, text/muted, type/body-size
24
+ */
25
+ export const ChatMessage = React.forwardRef<HTMLDivElement, ChatMessageProps>(
26
+ (
27
+ { role, children, citations, timestamp, onCitationClick, className = "", ...rest },
28
+ ref
29
+ ) => {
30
+ const classes = [
31
+ "pcoi-chat-message",
32
+ `pcoi-chat-message--${role}`,
33
+ className,
34
+ ]
35
+ .filter(Boolean)
36
+ .join(" ");
37
+
38
+ return (
39
+ <div ref={ref} className={classes} {...rest}>
40
+ <div className="pcoi-chat-message__header">
41
+ <Badge variant={role === "assistant" ? "default" : "info"}>
42
+ {role === "assistant" ? "PCOI" : "You"}
43
+ </Badge>
44
+ </div>
45
+
46
+ <div className="pcoi-chat-message__body">{children}</div>
47
+
48
+ {citations && citations.length > 0 && (
49
+ <div className="pcoi-chat-message__citations">
50
+ {citations.map((c) => (
51
+ <CitedExcerpt
52
+ key={c.index}
53
+ index={c.index}
54
+ excerpt={c.excerpt}
55
+ sourceTitle={c.sourceTitle}
56
+ onSourceClick={() => onCitationClick?.(c)}
57
+ />
58
+ ))}
59
+ </div>
60
+ )}
61
+
62
+ {timestamp && (
63
+ <time className="pcoi-chat-message__timestamp">{timestamp}</time>
64
+ )}
65
+ </div>
66
+ );
67
+ }
68
+ );
69
+
70
+ ChatMessage.displayName = "ChatMessage";
71
+ export default ChatMessage;
@@ -0,0 +1,2 @@
1
+ export { ChatMessage, type ChatMessageProps } from "./ChatMessage";
2
+ export { default } from "./ChatMessage";
@@ -0,0 +1,24 @@
1
+ /* ChatMessageList — @pcoi/components */
2
+
3
+ .pcoi-chat-message-list {
4
+ flex: 1;
5
+ min-height: 0;
6
+ overflow: hidden;
7
+ }
8
+
9
+ .pcoi-chat-message-list__inner {
10
+ height: 100%;
11
+ overflow-y: auto;
12
+ display: flex;
13
+ flex-direction: column;
14
+ gap: var(--pcoi-spacing-16);
15
+ padding: var(--pcoi-spacing-16);
16
+ }
17
+
18
+ /* ── Responsive: wider spacing on tablet+ ── */
19
+ @media (min-width: 768px) {
20
+ .pcoi-chat-message-list__inner {
21
+ gap: var(--pcoi-spacing-20);
22
+ padding: var(--pcoi-spacing-24);
23
+ }
24
+ }
@@ -0,0 +1,51 @@
1
+ import React, { useRef, useEffect } from "react";
2
+
3
+ export interface ChatMessageListProps
4
+ extends React.HTMLAttributes<HTMLDivElement> {
5
+ /** ChatMessage elements */
6
+ children: React.ReactNode;
7
+ }
8
+
9
+ /**
10
+ * PCOI ChatMessageList — Scrollable container that auto-scrolls to newest message
11
+ * Tokens: spacing-16/20 (gap), spacing-16/24 (padding)
12
+ */
13
+ export const ChatMessageList = React.forwardRef<
14
+ HTMLDivElement,
15
+ ChatMessageListProps
16
+ >(({ children, className = "", ...rest }, ref) => {
17
+ const innerRef = useRef<HTMLDivElement>(null);
18
+
19
+ /* Scroll to bottom whenever the DOM inside the list changes */
20
+ useEffect(() => {
21
+ const el = innerRef.current;
22
+ if (!el) return;
23
+
24
+ const scrollToEnd = () => {
25
+ requestAnimationFrame(() => {
26
+ el.scrollTop = el.scrollHeight;
27
+ });
28
+ };
29
+
30
+ const observer = new MutationObserver(scrollToEnd);
31
+ observer.observe(el, { childList: true, subtree: true });
32
+
33
+ scrollToEnd();
34
+ return () => observer.disconnect();
35
+ }, []);
36
+
37
+ const classes = ["pcoi-chat-message-list", className]
38
+ .filter(Boolean)
39
+ .join(" ");
40
+
41
+ return (
42
+ <div ref={ref} className={classes} {...rest}>
43
+ <div ref={innerRef} className="pcoi-chat-message-list__inner">
44
+ {children}
45
+ </div>
46
+ </div>
47
+ );
48
+ });
49
+
50
+ ChatMessageList.displayName = "ChatMessageList";
51
+ export default ChatMessageList;
@@ -0,0 +1,2 @@
1
+ export { ChatMessageList, type ChatMessageListProps } from "./ChatMessageList";
2
+ export { default } from "./ChatMessageList";
@@ -0,0 +1,97 @@
1
+ /* Checkbox — @pcoi/components */
2
+
3
+ .pcoi-checkbox {
4
+ display: flex;
5
+ flex-direction: column;
6
+ gap: var(--pcoi-spacing-6);
7
+ }
8
+
9
+ .pcoi-checkbox__control {
10
+ display: inline-flex;
11
+ align-items: center;
12
+ gap: var(--pcoi-spacing-8);
13
+ cursor: pointer;
14
+ }
15
+
16
+ .pcoi-checkbox__input {
17
+ position: absolute;
18
+ width: 1px;
19
+ height: 1px;
20
+ padding: 0;
21
+ margin: -1px;
22
+ overflow: hidden;
23
+ clip: rect(0, 0, 0, 0);
24
+ white-space: nowrap;
25
+ border-width: 0;
26
+ }
27
+
28
+ .pcoi-checkbox__box {
29
+ width: var(--pcoi-layout-component-control-box);
30
+ height: var(--pcoi-layout-component-control-box);
31
+ flex-shrink: 0;
32
+ border: 1px solid var(--pcoi-semantic-border-default);
33
+ border-radius: var(--pcoi-radius-sm);
34
+ background: var(--pcoi-semantic-bg-default);
35
+ transition: background var(--pcoi-effect-transition-fast, 0.2s ease),
36
+ border-color var(--pcoi-effect-transition-fast, 0.2s ease);
37
+ position: relative;
38
+ }
39
+
40
+ .pcoi-checkbox__box::after {
41
+ content: "";
42
+ position: absolute;
43
+ top: 2px;
44
+ left: 5px;
45
+ width: 5px;
46
+ height: 9px;
47
+ border: solid var(--pcoi-semantic-action-primary-text);
48
+ border-width: 0 2px 2px 0;
49
+ transform: rotate(45deg);
50
+ opacity: 0;
51
+ transition: opacity var(--pcoi-effect-transition-fast, 0.2s ease);
52
+ }
53
+
54
+ .pcoi-checkbox__input:checked + .pcoi-checkbox__box {
55
+ background: var(--pcoi-semantic-action-primary-bg);
56
+ border-color: var(--pcoi-semantic-action-primary-bg);
57
+ }
58
+
59
+ .pcoi-checkbox__input:checked + .pcoi-checkbox__box::after {
60
+ opacity: 1;
61
+ }
62
+
63
+ .pcoi-checkbox__control:hover .pcoi-checkbox__box {
64
+ border-color: var(--pcoi-semantic-border-input-hover);
65
+ }
66
+
67
+ .pcoi-checkbox__control:active .pcoi-checkbox__box {
68
+ transform: var(--pcoi-effect-transform-press-scale-control);
69
+ }
70
+
71
+ .pcoi-checkbox__input:focus-visible + .pcoi-checkbox__box {
72
+ border-color: var(--pcoi-semantic-focus-border);
73
+ box-shadow: var(--pcoi-effect-shadow-focus-ring);
74
+ }
75
+
76
+ .pcoi-checkbox__label {
77
+ font-size: var(--pcoi-semantic-type-body-size);
78
+ color: var(--pcoi-semantic-text-primary);
79
+ }
80
+
81
+ /* ── Error state ── */
82
+ .pcoi-checkbox--error .pcoi-checkbox__box {
83
+ border-color: var(--pcoi-semantic-border-error);
84
+ }
85
+
86
+ .pcoi-checkbox__error {
87
+ font-size: var(--pcoi-semantic-type-label-size);
88
+ color: var(--pcoi-semantic-text-error);
89
+ margin: 0;
90
+ padding-left: calc(var(--pcoi-layout-component-control-box) + var(--pcoi-spacing-8));
91
+ }
92
+
93
+ /* ── Disabled state ── */
94
+ .pcoi-checkbox--disabled {
95
+ opacity: var(--pcoi-effect-opacity-disabled);
96
+ pointer-events: none;
97
+ }
@@ -0,0 +1,70 @@
1
+ import React from "react";
2
+
3
+ export interface CheckboxProps
4
+ extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "type" | "size"> {
5
+ /** Visible label text */
6
+ label: string;
7
+ /** Unique field name, also used to generate id */
8
+ name: string;
9
+ /** Error message — when truthy, field enters error state */
10
+ error?: string;
11
+ }
12
+
13
+ /**
14
+ * PCOI Checkbox — Custom styled checkbox with label
15
+ * Tokens: bg/default, border/default, border/error, focus/border, focus/glow,
16
+ * action/primary-bg, action/primary-text, text/primary, text/error, radius-sm
17
+ */
18
+ export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
19
+ (
20
+ {
21
+ label,
22
+ name,
23
+ error,
24
+ disabled = false,
25
+ className = "",
26
+ ...props
27
+ },
28
+ ref
29
+ ) => {
30
+ const fieldId = `pcoi-field-${name}`;
31
+ const errorId = error ? `${fieldId}-error` : undefined;
32
+
33
+ const wrapperClasses = [
34
+ "pcoi-checkbox",
35
+ error ? "pcoi-checkbox--error" : "",
36
+ disabled ? "pcoi-checkbox--disabled" : "",
37
+ className,
38
+ ]
39
+ .filter(Boolean)
40
+ .join(" ");
41
+
42
+ return (
43
+ <div className={wrapperClasses}>
44
+ <label htmlFor={fieldId} className="pcoi-checkbox__control">
45
+ <input
46
+ ref={ref}
47
+ type="checkbox"
48
+ id={fieldId}
49
+ name={name}
50
+ disabled={disabled}
51
+ aria-invalid={!!error}
52
+ aria-describedby={errorId}
53
+ className="pcoi-checkbox__input"
54
+ {...props}
55
+ />
56
+ <span className="pcoi-checkbox__box" aria-hidden="true" />
57
+ <span className="pcoi-checkbox__label">{label}</span>
58
+ </label>
59
+ {error && (
60
+ <span id={errorId} className="pcoi-checkbox__error" role="alert">
61
+ {error}
62
+ </span>
63
+ )}
64
+ </div>
65
+ );
66
+ }
67
+ );
68
+
69
+ Checkbox.displayName = "Checkbox";
70
+ export default Checkbox;
@@ -0,0 +1,2 @@
1
+ export { Checkbox, type CheckboxProps } from "./Checkbox";
2
+ export { default } from "./Checkbox";
@@ -0,0 +1,40 @@
1
+ /* CitationMark — @pcoi/components */
2
+
3
+ .pcoi-citation-mark {
4
+ display: inline-flex;
5
+ align-items: center;
6
+ justify-content: center;
7
+ min-width: 32px;
8
+ min-height: 32px;
9
+ padding: var(--pcoi-spacing-4) var(--pcoi-spacing-10);
10
+ font-family: var(--pcoi-semantic-type-mono-font);
11
+ font-size: var(--pcoi-semantic-type-body-compact-size);
12
+ font-weight: var(--pcoi-semantic-type-label-weight);
13
+ line-height: var(--pcoi-semantic-type-none-line-height);
14
+ color: var(--pcoi-semantic-text-accent);
15
+ background: var(--pcoi-semantic-surface-accent-dim);
16
+ border: 1px solid var(--pcoi-semantic-border-accent-dim);
17
+ border-radius: var(--pcoi-radius-full);
18
+ cursor: pointer;
19
+ vertical-align: middle;
20
+ transition:
21
+ background var(--pcoi-effect-transition-fast),
22
+ color var(--pcoi-effect-transition-fast),
23
+ border-color var(--pcoi-effect-transition-fast),
24
+ transform var(--pcoi-effect-transition-fast);
25
+ }
26
+
27
+ .pcoi-citation-mark:hover {
28
+ color: var(--pcoi-semantic-text-accent-hover);
29
+ background: var(--pcoi-color-accent-dim);
30
+ border-color: var(--pcoi-semantic-border-accent-subtle);
31
+ }
32
+
33
+ .pcoi-citation-mark:active {
34
+ transform: var(--pcoi-effect-transform-press-scale-control);
35
+ }
36
+
37
+ .pcoi-citation-mark:focus-visible {
38
+ outline: none;
39
+ box-shadow: var(--pcoi-effect-shadow-focus-ring);
40
+ }
@@ -0,0 +1,38 @@
1
+ import React from "react";
2
+
3
+ export interface CitationMarkProps
4
+ extends React.ButtonHTMLAttributes<HTMLButtonElement> {
5
+ /** 1-based citation index displayed in the button */
6
+ index: number;
7
+ /** Tooltip text (defaults to "Citation {index}") */
8
+ sourceLabel?: string;
9
+ }
10
+
11
+ /**
12
+ * PCOI CitationMark — Inline pill-shaped citation button [1]
13
+ * Tokens: text/accent, surface/accent-dim, border/accent-dim,
14
+ * radius-full, font-family/mono, transition-fast,
15
+ * shadow/focus-ring, transform/press-scale-control
16
+ */
17
+ export const CitationMark = React.forwardRef<
18
+ HTMLButtonElement,
19
+ CitationMarkProps
20
+ >(({ index, sourceLabel, className = "", ...rest }, ref) => {
21
+ const classes = ["pcoi-citation-mark", className].filter(Boolean).join(" ");
22
+
23
+ return (
24
+ <button
25
+ ref={ref}
26
+ type="button"
27
+ className={classes}
28
+ title={sourceLabel ?? `Citation ${index}`}
29
+ aria-label={sourceLabel ?? `Citation ${index}`}
30
+ {...rest}
31
+ >
32
+ {index}
33
+ </button>
34
+ );
35
+ });
36
+
37
+ CitationMark.displayName = "CitationMark";
38
+ export default CitationMark;
@@ -0,0 +1,2 @@
1
+ export { CitationMark, type CitationMarkProps } from "./CitationMark";
2
+ export { default } from "./CitationMark";
@@ -0,0 +1,75 @@
1
+ /* CitedExcerpt — @pcoi/components */
2
+
3
+ .pcoi-cited-excerpt {
4
+ display: flex;
5
+ flex-direction: column;
6
+ gap: var(--pcoi-spacing-6);
7
+ padding: var(--pcoi-spacing-12);
8
+ background: var(--pcoi-semantic-surface-accent-dim);
9
+ border-left: var(--pcoi-layout-component-accent-border-w) solid
10
+ var(--pcoi-semantic-border-accent-dim);
11
+ border-radius: var(--pcoi-radius-sm);
12
+ }
13
+
14
+ .pcoi-cited-excerpt__source {
15
+ display: inline-flex;
16
+ align-items: center;
17
+ align-self: flex-start;
18
+ padding: 0;
19
+ font-family: var(--pcoi-semantic-type-body-font);
20
+ font-size: var(--pcoi-semantic-type-body-sm-size);
21
+ color: var(--pcoi-semantic-text-accent);
22
+ background: none;
23
+ border: none;
24
+ cursor: pointer;
25
+ transition:
26
+ color var(--pcoi-effect-transition-fast);
27
+ }
28
+
29
+ .pcoi-cited-excerpt__source:hover {
30
+ color: var(--pcoi-semantic-text-accent-hover);
31
+ text-decoration: underline;
32
+ }
33
+
34
+ .pcoi-cited-excerpt__source:focus-visible {
35
+ outline: none;
36
+ box-shadow: var(--pcoi-effect-shadow-focus-ring);
37
+ border-radius: var(--pcoi-radius-sm);
38
+ }
39
+
40
+ .pcoi-cited-excerpt__body {
41
+ display: flex;
42
+ align-items: baseline;
43
+ gap: var(--pcoi-spacing-6);
44
+ }
45
+
46
+ .pcoi-cited-excerpt__index {
47
+ flex-shrink: 0;
48
+ padding: 0;
49
+ background: none;
50
+ border: none;
51
+ cursor: pointer;
52
+ font-family: var(--pcoi-semantic-type-mono-font);
53
+ font-size: var(--pcoi-semantic-type-body-compact-size);
54
+ font-weight: var(--pcoi-semantic-type-label-weight);
55
+ color: var(--pcoi-semantic-text-accent);
56
+ transition: color var(--pcoi-effect-transition-fast);
57
+ }
58
+
59
+ .pcoi-cited-excerpt__index:hover {
60
+ color: var(--pcoi-semantic-text-accent-hover);
61
+ }
62
+
63
+ .pcoi-cited-excerpt__index:focus-visible {
64
+ outline: none;
65
+ box-shadow: var(--pcoi-effect-shadow-focus-ring);
66
+ border-radius: var(--pcoi-radius-sm);
67
+ }
68
+
69
+ .pcoi-cited-excerpt__text {
70
+ margin: 0;
71
+ font-family: var(--pcoi-semantic-type-body-font);
72
+ font-size: var(--pcoi-semantic-type-body-compact-size);
73
+ color: var(--pcoi-semantic-text-secondary);
74
+ line-height: var(--pcoi-semantic-type-body-line-height);
75
+ }