@pcoi/components 0.1.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +52 -0
  3. package/dist/index.d.ts +829 -609
  4. package/dist/index.js +3517 -2
  5. package/dist/styles.css +2 -0
  6. package/package.json +35 -31
  7. package/dist/components.css +0 -1
  8. package/dist/index.mjs +0 -994
  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 -48
  18. package/src/Callout/Callout.figma.tsx +0 -25
  19. package/src/Callout/Callout.tsx +0 -43
  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 -50
  26. package/src/ChatInterface/ChatInterface.integration.test.tsx +0 -123
  27. package/src/ChatInterface/ChatInterface.tsx +0 -125
  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 -70
  34. package/src/ChatMessageList/ChatMessageList.tsx +0 -56
  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 -53
  44. package/src/CitedExcerpt/index.ts +0 -2
  45. package/src/ComparisonTable/ComparisonTable.css +0 -66
  46. package/src/ComparisonTable/ComparisonTable.tsx +0 -54
  47. package/src/ComparisonTable/index.ts +0 -1
  48. package/src/ContactForm/ContactForm.css +0 -38
  49. package/src/ContactForm/ContactForm.tsx +0 -58
  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 -87
  57. package/src/DocumentOverlay/index.ts +0 -2
  58. package/src/Footer/Footer.css +0 -72
  59. package/src/Footer/Footer.tsx +0 -59
  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 -144
  76. package/src/Modal/index.ts +0 -2
  77. package/src/Nav/Nav.css +0 -161
  78. package/src/Nav/Nav.tsx +0 -105
  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 -35
  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 -82
  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 -39
  120. package/src/styles/utilities.css +0 -14
  121. package/src/styles.css +0 -33
  122. package/src/types.ts +0 -66
@@ -1,35 +0,0 @@
1
- /* Panel — @pcoi/components */
2
-
3
- .pcoi-panel {
4
- background: var(--pcoi-semantic-bg-card);
5
- border: 1px solid var(--pcoi-semantic-border-default);
6
- border-radius: var(--pcoi-semantic-radius-card);
7
- overflow: hidden;
8
- }
9
-
10
- .pcoi-panel__header {
11
- display: flex;
12
- align-items: center;
13
- justify-content: space-between;
14
- padding: var(--pcoi-semantic-spacing-panel-padding-sm) var(--pcoi-semantic-spacing-panel-padding);
15
- border-bottom: 1px solid var(--pcoi-semantic-border-default);
16
- }
17
-
18
- .pcoi-panel__title {
19
- font-size: var(--pcoi-semantic-type-body-size);
20
- font-weight: var(--pcoi-semantic-type-emphasis-weight);
21
- color: var(--pcoi-semantic-text-primary);
22
- margin: 0;
23
- }
24
-
25
- .pcoi-panel__action {
26
- flex-shrink: 0;
27
- }
28
-
29
- .pcoi-panel__body {
30
- padding: var(--pcoi-semantic-spacing-panel-padding);
31
- }
32
-
33
- .pcoi-panel--flush .pcoi-panel__body {
34
- padding: 0;
35
- }
@@ -1,61 +0,0 @@
1
- import React from "react";
2
- import type { HeadingLevel } from "../types";
3
-
4
- export interface PanelProps extends React.HTMLAttributes<HTMLDivElement> {
5
- /** Panel title rendered in a header bar */
6
- title?: string;
7
- /** Heading level for the title (default: "h3") */
8
- headingLevel?: HeadingLevel;
9
- /** Action slot in the header (e.g. a Button or filter) */
10
- action?: React.ReactNode;
11
- /** Panel body content */
12
- children: React.ReactNode;
13
- /** Remove default body padding (for tables/charts that need edge-to-edge content) */
14
- flush?: boolean;
15
- }
16
-
17
- /**
18
- * PCOI Panel — Dashboard section container with optional header
19
- * Tokens: bg/card, border/default, radius-md, spacing-16/24, text/primary
20
- */
21
- export const Panel = React.forwardRef<HTMLDivElement, PanelProps>(
22
- (
23
- {
24
- title,
25
- headingLevel = "h3",
26
- action,
27
- children,
28
- flush = false,
29
- className = "",
30
- ...rest
31
- },
32
- ref
33
- ) => {
34
- const Heading = headingLevel;
35
-
36
- const wrapperClasses = [
37
- "pcoi-panel",
38
- flush ? "pcoi-panel--flush" : "",
39
- className,
40
- ]
41
- .filter(Boolean)
42
- .join(" ");
43
-
44
- return (
45
- <div ref={ref} className={wrapperClasses} {...rest}>
46
- {(title || action) && (
47
- <div className="pcoi-panel__header">
48
- {title && (
49
- <Heading className="pcoi-panel__title">{title}</Heading>
50
- )}
51
- {action && <div className="pcoi-panel__action">{action}</div>}
52
- </div>
53
- )}
54
- <div className="pcoi-panel__body">{children}</div>
55
- </div>
56
- );
57
- }
58
- );
59
-
60
- Panel.displayName = "Panel";
61
- export default Panel;
@@ -1,2 +0,0 @@
1
- export { Panel, type PanelProps } from "./Panel";
2
- export { default } from "./Panel";
@@ -1,68 +0,0 @@
1
- /* PromptBar — @pcoi/components */
2
-
3
- .pcoi-prompt-bar {
4
- display: flex;
5
- align-items: flex-end;
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
- border: 1px solid var(--pcoi-semantic-border-default);
10
- border-radius: var(--pcoi-semantic-radius-card);
11
- transition:
12
- border-color var(--pcoi-effect-transition-fast),
13
- box-shadow var(--pcoi-effect-transition-fast);
14
- }
15
-
16
- .pcoi-prompt-bar:not(:focus-within):hover {
17
- border-color: var(--pcoi-semantic-border-input-hover);
18
- }
19
-
20
- .pcoi-prompt-bar:focus-within {
21
- border-color: var(--pcoi-semantic-focus-border);
22
- box-shadow: var(--pcoi-effect-shadow-focus-ring);
23
- }
24
-
25
- .pcoi-prompt-bar__input {
26
- flex: 1;
27
- min-height: 24px;
28
- max-height: 120px;
29
- padding: var(--pcoi-semantic-spacing-control-padding-2xs) 0;
30
- font-family: var(--pcoi-semantic-type-body-font);
31
- font-size: var(--pcoi-semantic-type-body-size);
32
- line-height: var(--pcoi-semantic-type-body-line-height);
33
- color: var(--pcoi-semantic-text-primary);
34
- background: transparent;
35
- border: none;
36
- outline: none;
37
- resize: none;
38
- }
39
-
40
- .pcoi-prompt-bar__input::placeholder {
41
- color: var(--pcoi-semantic-text-muted);
42
- }
43
-
44
- .pcoi-prompt-bar__actions {
45
- flex-shrink: 0;
46
- display: flex;
47
- align-items: center;
48
- }
49
-
50
- .pcoi-prompt-bar__send.pcoi-btn {
51
- padding: var(--pcoi-semantic-spacing-control-padding-xs);
52
- min-width: 0;
53
- min-height: 0;
54
- width: var(--pcoi-semantic-sizing-btn-height-nav);
55
- height: var(--pcoi-semantic-sizing-btn-height-nav);
56
- }
57
-
58
- .pcoi-prompt-bar__send-icon {
59
- display: flex;
60
- transform: rotate(-90deg);
61
- }
62
-
63
- /* ── Responsive: compact send on small mobile ── */
64
- @media (max-width: 480px) {
65
- .pcoi-prompt-bar {
66
- padding: var(--pcoi-semantic-spacing-control-padding-sm) var(--pcoi-semantic-spacing-input-y);
67
- }
68
- }
@@ -1,25 +0,0 @@
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
- });
@@ -1,83 +0,0 @@
1
- import { fireEvent, render, screen } from "@testing-library/react";
2
- import { describe, expect, it, vi } from "vitest";
3
- import { PromptBar } from "./PromptBar";
4
-
5
- describe("PromptBar", () => {
6
- it("calls onChange with textarea value", () => {
7
- const onChange = vi.fn();
8
- const onSubmit = vi.fn();
9
-
10
- render(
11
- <PromptBar
12
- value=""
13
- onChange={onChange}
14
- onSubmit={onSubmit}
15
- placeholder="Ask"
16
- />
17
- );
18
-
19
- fireEvent.change(screen.getByRole("textbox", { name: "Ask" }), {
20
- target: { value: "hello" },
21
- });
22
-
23
- expect(onChange).toHaveBeenCalledWith("hello");
24
- });
25
-
26
- it("submits trimmed value on Enter", () => {
27
- const onChange = vi.fn();
28
- const onSubmit = vi.fn();
29
-
30
- render(
31
- <PromptBar
32
- value=" hello world "
33
- onChange={onChange}
34
- onSubmit={onSubmit}
35
- placeholder="Ask"
36
- />
37
- );
38
-
39
- fireEvent.keyDown(screen.getByRole("textbox", { name: "Ask" }), {
40
- key: "Enter",
41
- code: "Enter",
42
- shiftKey: false,
43
- });
44
-
45
- expect(onSubmit).toHaveBeenCalledTimes(1);
46
- expect(onSubmit).toHaveBeenCalledWith("hello world");
47
- });
48
-
49
- it("does not submit on Shift+Enter", () => {
50
- const onChange = vi.fn();
51
- const onSubmit = vi.fn();
52
-
53
- render(
54
- <PromptBar
55
- value="hello"
56
- onChange={onChange}
57
- onSubmit={onSubmit}
58
- placeholder="Ask"
59
- />
60
- );
61
-
62
- fireEvent.keyDown(screen.getByRole("textbox", { name: "Ask" }), {
63
- key: "Enter",
64
- code: "Enter",
65
- shiftKey: true,
66
- });
67
-
68
- expect(onSubmit).not.toHaveBeenCalled();
69
- });
70
-
71
- it("disables send button when value is empty/whitespace", () => {
72
- render(
73
- <PromptBar
74
- value=" "
75
- onChange={vi.fn()}
76
- onSubmit={vi.fn()}
77
- placeholder="Ask"
78
- />
79
- );
80
-
81
- expect(screen.getByRole("button", { name: "Send message" })).toBeDisabled();
82
- });
83
- });
@@ -1,93 +0,0 @@
1
- import React, { useRef, useCallback } from "react";
2
- import { Button } from "../Button/Button";
3
- import { ArrowRightIcon } from "@pcoi/icons";
4
-
5
- export interface PromptBarProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange" | "onSubmit"> {
6
- /** Current textarea value */
7
- value: string;
8
- /** Called when the textarea value changes */
9
- onChange: (value: string) => void;
10
- /** Called when the user submits (Enter or send button) */
11
- onSubmit: (value: string) => void;
12
- /** Placeholder text */
13
- placeholder?: string;
14
- /** Disable input and send */
15
- disabled?: boolean;
16
- /** Show loading state on send button */
17
- loading?: boolean;
18
- }
19
-
20
- /**
21
- * PCOI PromptBar — Textarea + send button for chat input
22
- * Tokens: bg/card, border/default, focus-border, radius-md,
23
- * text/primary, text/muted, transition-fast
24
- */
25
- export const PromptBar = React.forwardRef<HTMLDivElement, PromptBarProps>(
26
- (
27
- {
28
- value,
29
- onChange,
30
- onSubmit,
31
- placeholder = "Ask a question…",
32
- disabled = false,
33
- loading = false,
34
- className = "",
35
- ...rest
36
- },
37
- ref
38
- ) => {
39
- const textareaRef = useRef<HTMLTextAreaElement>(null);
40
-
41
- const handleKeyDown = useCallback(
42
- (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
43
- if (e.key === "Enter" && !e.shiftKey) {
44
- e.preventDefault();
45
- if (value.trim() && !disabled && !loading) {
46
- onSubmit(value.trim());
47
- }
48
- }
49
- },
50
- [value, disabled, loading, onSubmit]
51
- );
52
-
53
- const handleSubmit = useCallback(() => {
54
- if (value.trim() && !disabled && !loading) {
55
- onSubmit(value.trim());
56
- }
57
- }, [value, disabled, loading, onSubmit]);
58
-
59
- const classes = ["pcoi-prompt-bar", className].filter(Boolean).join(" ");
60
-
61
- return (
62
- <div ref={ref} className={classes} {...rest}>
63
- <textarea
64
- ref={textareaRef}
65
- className="pcoi-prompt-bar__input"
66
- value={value}
67
- onChange={(e) => onChange(e.target.value)}
68
- onKeyDown={handleKeyDown}
69
- placeholder={placeholder}
70
- disabled={disabled}
71
- rows={1}
72
- aria-label={placeholder}
73
- />
74
- <div className="pcoi-prompt-bar__actions">
75
- <Button
76
- variant="primary"
77
- className="pcoi-prompt-bar__send"
78
- onClick={handleSubmit}
79
- disabled={disabled || loading || !value.trim()}
80
- aria-label="Send message"
81
- >
82
- <span className="pcoi-prompt-bar__send-icon">
83
- <ArrowRightIcon size={16} />
84
- </span>
85
- </Button>
86
- </div>
87
- </div>
88
- );
89
- }
90
- );
91
-
92
- PromptBar.displayName = "PromptBar";
93
- export default PromptBar;
@@ -1,2 +0,0 @@
1
- export { PromptBar, type PromptBarProps } from "./PromptBar";
2
- export { default } from "./PromptBar";
@@ -1,117 +0,0 @@
1
- /* RadioGroup — @pcoi/components */
2
-
3
- .pcoi-radio-group {
4
- border: none;
5
- padding: 0;
6
- margin: 0;
7
- display: flex;
8
- flex-direction: column;
9
- gap: var(--pcoi-semantic-spacing-text-gap-sm);
10
- }
11
-
12
- .pcoi-radio-group__legend {
13
- font-size: var(--pcoi-semantic-type-label-size);
14
- font-weight: var(--pcoi-semantic-type-label-weight);
15
- letter-spacing: var(--pcoi-semantic-type-label-letter-spacing);
16
- text-transform: uppercase;
17
- color: var(--pcoi-semantic-text-secondary);
18
- margin-bottom: var(--pcoi-semantic-spacing-inline-2xs);
19
- }
20
-
21
- .pcoi-radio-group__required {
22
- color: var(--pcoi-semantic-text-error);
23
- margin-left: var(--pcoi-semantic-spacing-inline-2xs);
24
- }
25
-
26
- .pcoi-radio-group__options {
27
- display: flex;
28
- flex-direction: column;
29
- gap: var(--pcoi-semantic-spacing-panel-gap);
30
- }
31
-
32
- .pcoi-radio-group__option {
33
- display: inline-flex;
34
- align-items: center;
35
- gap: var(--pcoi-semantic-spacing-inline-sm);
36
- cursor: pointer;
37
- }
38
-
39
- .pcoi-radio-group__input {
40
- position: absolute;
41
- width: 1px;
42
- height: 1px;
43
- padding: 0;
44
- margin: -1px;
45
- overflow: hidden;
46
- clip: rect(0, 0, 0, 0);
47
- white-space: nowrap;
48
- border-width: 0;
49
- }
50
-
51
- .pcoi-radio-group__circle {
52
- width: var(--pcoi-semantic-sizing-control-box-size);
53
- height: var(--pcoi-semantic-sizing-control-box-size);
54
- flex-shrink: 0;
55
- border: 1px solid var(--pcoi-semantic-border-default);
56
- border-radius: var(--pcoi-semantic-radius-badge);
57
- background: var(--pcoi-semantic-bg-default);
58
- position: relative;
59
- transition: border-color var(--pcoi-effect-transition-fast, 0.2s ease);
60
- }
61
-
62
- .pcoi-radio-group__circle::after {
63
- content: "";
64
- position: absolute;
65
- top: 50%;
66
- left: 50%;
67
- transform: translate(-50%, -50%);
68
- width: var(--pcoi-semantic-sizing-radio-dot-size);
69
- height: var(--pcoi-semantic-sizing-radio-dot-size);
70
- border-radius: var(--pcoi-semantic-radius-badge);
71
- background: var(--pcoi-semantic-action-primary-bg);
72
- opacity: 0;
73
- transition: opacity var(--pcoi-effect-transition-fast, 0.2s ease);
74
- }
75
-
76
- .pcoi-radio-group__input:checked + .pcoi-radio-group__circle {
77
- border-color: var(--pcoi-semantic-action-primary-bg);
78
- }
79
-
80
- .pcoi-radio-group__input:checked + .pcoi-radio-group__circle::after {
81
- opacity: 1;
82
- }
83
-
84
- .pcoi-radio-group__option:hover .pcoi-radio-group__circle {
85
- border-color: var(--pcoi-semantic-border-input-hover);
86
- }
87
-
88
- .pcoi-radio-group__option:active .pcoi-radio-group__circle {
89
- transform: var(--pcoi-effect-transform-press-scale-control);
90
- }
91
-
92
- .pcoi-radio-group__input:focus-visible + .pcoi-radio-group__circle {
93
- border-color: var(--pcoi-semantic-focus-border);
94
- box-shadow: var(--pcoi-effect-shadow-focus-ring);
95
- }
96
-
97
- .pcoi-radio-group__label {
98
- font-size: var(--pcoi-semantic-type-body-size);
99
- color: var(--pcoi-semantic-text-primary);
100
- }
101
-
102
- /* ── Error state ── */
103
- .pcoi-radio-group--error .pcoi-radio-group__circle {
104
- border-color: var(--pcoi-semantic-border-error);
105
- }
106
-
107
- .pcoi-radio-group__error {
108
- font-size: var(--pcoi-semantic-type-label-size);
109
- color: var(--pcoi-semantic-text-error);
110
- margin: 0;
111
- }
112
-
113
- /* ── Disabled state ── */
114
- .pcoi-radio-group--disabled {
115
- opacity: var(--pcoi-effect-opacity-disabled);
116
- pointer-events: none;
117
- }
@@ -1,112 +0,0 @@
1
- import React from "react";
2
- import type { OptionItem } from "../types";
3
-
4
- export interface RadioGroupProps extends Omit<React.FieldsetHTMLAttributes<HTMLFieldSetElement>, "onChange"> {
5
- /** Group label rendered as a fieldset legend */
6
- label: string;
7
- /** Shared name for all radio inputs */
8
- name: string;
9
- /** List of radio options */
10
- options: OptionItem[];
11
- /** Pre-selected value */
12
- defaultValue?: string;
13
- /** Error message — when truthy, group enters error state */
14
- error?: string;
15
- /** Marks the group as required */
16
- required?: boolean;
17
- /** Disables all radio inputs */
18
- disabled?: boolean;
19
- /** Called with the selected value when it changes */
20
- onChange?: (value: string) => void;
21
- }
22
-
23
- /**
24
- * PCOI RadioGroup — Accessible radio button group using fieldset/legend
25
- * Tokens: bg/default, border/default, border/error, focus/border, focus/glow,
26
- * action/primary-bg, text/primary, text/secondary, text/error, radius-full
27
- */
28
- export const RadioGroup = React.forwardRef<HTMLFieldSetElement, RadioGroupProps>(
29
- (
30
- {
31
- label,
32
- name,
33
- options,
34
- defaultValue,
35
- error,
36
- required = false,
37
- disabled = false,
38
- onChange,
39
- className = "",
40
- ...rest
41
- },
42
- ref
43
- ) => {
44
- const errorId = error ? `pcoi-field-${name}-error` : undefined;
45
-
46
- const wrapperClasses = [
47
- "pcoi-radio-group",
48
- error ? "pcoi-radio-group--error" : "",
49
- disabled ? "pcoi-radio-group--disabled" : "",
50
- className,
51
- ]
52
- .filter(Boolean)
53
- .join(" ");
54
-
55
- const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
56
- onChange?.(e.target.value);
57
- };
58
-
59
- return (
60
- <fieldset
61
- ref={ref}
62
- className={wrapperClasses}
63
- aria-invalid={!!error}
64
- aria-describedby={errorId}
65
- {...rest}
66
- >
67
- <legend className="pcoi-radio-group__legend">
68
- {label}
69
- {required && (
70
- <span className="pcoi-radio-group__required" aria-hidden="true">
71
- *
72
- </span>
73
- )}
74
- </legend>
75
- <div className="pcoi-radio-group__options">
76
- {options.map((opt) => {
77
- const optionId = `pcoi-field-${name}-${opt.value}`;
78
- return (
79
- <label
80
- key={opt.value}
81
- htmlFor={optionId}
82
- className="pcoi-radio-group__option"
83
- >
84
- <input
85
- type="radio"
86
- id={optionId}
87
- name={name}
88
- value={opt.value}
89
- defaultChecked={defaultValue === opt.value}
90
- required={required}
91
- disabled={disabled || opt.disabled}
92
- onChange={handleChange}
93
- className="pcoi-radio-group__input"
94
- />
95
- <span className="pcoi-radio-group__circle" aria-hidden="true" />
96
- <span className="pcoi-radio-group__label">{opt.label}</span>
97
- </label>
98
- );
99
- })}
100
- </div>
101
- {error && (
102
- <span id={errorId} className="pcoi-radio-group__error" role="alert">
103
- {error}
104
- </span>
105
- )}
106
- </fieldset>
107
- );
108
- }
109
- );
110
-
111
- RadioGroup.displayName = "RadioGroup";
112
- export default RadioGroup;
@@ -1,2 +0,0 @@
1
- export { RadioGroup, type RadioGroupProps } from "./RadioGroup";
2
- export { default } from "./RadioGroup";
@@ -1,38 +0,0 @@
1
- /* SectionHeader — @pcoi/components */
2
-
3
- .pcoi-section-header {
4
- text-align: center;
5
- max-width: var(--pcoi-semantic-sizing-container-narrow);
6
- margin: 0 auto var(--pcoi-semantic-spacing-section-header-margin);
7
- }
8
-
9
- .pcoi-section-header__label {
10
- display: inline-block;
11
- font-family: var(--pcoi-semantic-type-mono-font);
12
- font-size: var(--pcoi-semantic-type-section-label-size);
13
- font-weight: var(--pcoi-semantic-type-label-weight);
14
- letter-spacing: var(--pcoi-semantic-type-section-label-letter-spacing);
15
- text-transform: uppercase;
16
- color: var(--pcoi-semantic-text-accent);
17
- margin-bottom: var(--pcoi-semantic-spacing-stack-md);
18
- }
19
-
20
- .pcoi-section-header__title {
21
- font-size: var(--pcoi-semantic-type-section-title-size);
22
- font-weight: var(--pcoi-semantic-type-heading-weight);
23
- line-height: var(--pcoi-semantic-type-heading-line-height);
24
- letter-spacing: var(--pcoi-semantic-type-heading-letter-spacing);
25
- color: var(--pcoi-semantic-text-primary);
26
- margin: 0 0 var(--pcoi-semantic-spacing-stack-md) 0;
27
- }
28
-
29
- .pcoi-section-header__title em {
30
- font-style: normal;
31
- color: var(--pcoi-semantic-text-accent);
32
- }
33
-
34
- .pcoi-section-header__desc {
35
- font-size: var(--pcoi-semantic-type-heading-sm-size);
36
- color: var(--pcoi-semantic-text-secondary);
37
- line-height: var(--pcoi-semantic-type-relaxed-line-height);
38
- }
@@ -1,23 +0,0 @@
1
- import figma from "@figma/code-connect";
2
- import { SectionHeader } from "./SectionHeader";
3
-
4
- /**
5
- * Code Connect: SectionHeader
6
- * Maps Figma section header properties to React SectionHeader props
7
- */
8
- figma.connect(SectionHeader, "https://www.figma.com/file/PCOIxDesignSystem/PCOI-Design-System?node-id=106-1", {
9
- props: {
10
- label: figma.string("Label"),
11
- title: figma.string("Title"),
12
- titleEmphasis: figma.string("Title Emphasis"),
13
- description: figma.string("Description"),
14
- },
15
- example: ({ label, title, titleEmphasis, description }) => (
16
- <SectionHeader
17
- label={label}
18
- title={title}
19
- titleEmphasis={titleEmphasis || undefined}
20
- description={description || undefined}
21
- />
22
- ),
23
- });