uhc-portal-components-react 0.1.36 → 0.1.38

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/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.1.38
6
+
7
+ - Add a brand-neutral subject communication center that merges messages,
8
+ voice messages, internet calls and appointment notices behind accessible
9
+ All, Messages, Calls and Emergency filters.
10
+
11
+ - Add a portal-owned clinical speech-input provider. Schema-backed IPS text
12
+ fields now reflect the shared model state, append editable transcripts and
13
+ reuse the existing explicit terminology-candidate selection flow. A shared
14
+ external channel propagates runtime phase changes to editors that are
15
+ already open without importing or duplicating the model in those editors.
16
+
5
17
  - Add a shared progressive animal, owner-or-caregiver and physical-evidence
6
18
  onboarding form for veterinarian-assisted identity attestation.
7
19
  - Add a capability-aware `SubjectIdentityCard` for provider-confirmed animal
package/README.md CHANGED
@@ -86,6 +86,16 @@ The package deliberately exposes modules instead of a cloned application:
86
86
  terminology candidate preserves source text and adds only the confirmed
87
87
  `system|code` and display. Success requires a fresh provider readback Bundle;
88
88
  local form state is never presented as a persisted write.
89
+ - `ClinicalSpeechInputProvider`: lets the owning portal publish one
90
+ speech-recognition capability and model phase to every nested clinical
91
+ editor. Compatible IPS text fields show an accessible microphone only when
92
+ configured; recognized text remains editable and enters the same debounced
93
+ terminology-selection flow. The shared component never downloads a model,
94
+ stores audio or confirms a clinical code. Use
95
+ `createClinicalSpeechInputChannel` with
96
+ `ClinicalSpeechInputChannelProvider` when the portal model must notify
97
+ already-mounted IPS sections as it moves through download, initialization,
98
+ readiness or failure.
89
99
  - `InvitationAcceptancePanel`: controlled invitation-code UI for a related
90
100
  individual member; the host refreshes the authorized-subject list after its
91
101
  BFF accepts the invitation.
@@ -1 +1 @@
1
- {"version":3,"file":"ClinicalArtifactEditor.d.ts","sourceRoot":"","sources":["../src/ClinicalArtifactEditor.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ClinicalArtifactEditor.d.ts","sourceRoot":"","sources":["../src/ClinicalArtifactEditor.tsx"],"names":[],"mappings":"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"}
@@ -4,6 +4,7 @@ import { useEffect, useId, useMemo, useRef, useState } from 'react';
4
4
  import { clinicalArtifactEditorClaimsFromFhirR4, clinicalArtifactFhirR4ToFlat, composeFhirDateTimeFromLocalParts, splitFhirDateTimeForInput, } from 'fhir-data-utils-ts';
5
5
  import { buildMultichannelOperationBundle, MultichannelOperationMode, readMultichannelOperationFromBundle, selectMultichannelOperationClaimValues, } from 'sos-data-utils-ts/multichannel-operation-schemas';
6
6
  import { ClinicalEditorLayer } from './ClinicalEditorLayer.js';
7
+ import { ClinicalSpeechInputButton } from './ClinicalSpeechInput.js';
7
8
  /**
8
9
  * Shared human/animal clinical editor. The host owns authorization, transport,
9
10
  * persistence and terminology service access. A successful form submission is
@@ -140,7 +141,16 @@ export function ClinicalArtifactEditor({ request, schema, operationSchema, autho
140
141
  };
141
142
  if (isDateTimeSchemaNode(schemaNode))
142
143
  return _jsx(ClinicalDateTimeClaimField, { label: label, required: requiredClaims.has(claim), value: value, timeZone: timeZone, labels: copy.dateTime ?? DEFAULT_DATE_TIME_LABELS, onChange: (nextValue) => setClaims((current) => ({ ...current, [claim]: nextValue })) }, claim);
143
- return _jsxs("label", { className: isMultiline(parameter) ? 'uhcp-clinical-editor-wide' : undefined, htmlFor: inputId, children: [_jsxs("span", { children: [label, requiredClaims.has(claim) ? ' *' : ''] }), statusOptions(request.resourceType, parameter) ? _jsxs("select", { id: inputId, "aria-label": label, value: value, required: requiredClaims.has(claim), onChange: update, children: [!requiredClaims.has(claim) ? _jsx("option", { value: "" }) : null, statusOptions(request.resourceType, parameter).map((option) => _jsx("option", { value: option, children: humanize(option) }, option))] }) : isMultiline(parameter) ? _jsx("textarea", { id: inputId, "aria-label": label, value: value, required: requiredClaims.has(claim), rows: 3, onChange: update })
144
+ const fieldClassName = `uhcp-clinical-editor-field${isMultiline(parameter) ? ' uhcp-clinical-editor-wide' : ''}`;
145
+ const appendTranscript = (transcript) => {
146
+ setClaims((current) => ({
147
+ ...current,
148
+ [claim]: [current[claim]?.trim(), transcript].filter(Boolean).join(' '),
149
+ }));
150
+ if (terminologyEnabled)
151
+ setActiveTerminologyClaim(claim);
152
+ };
153
+ return _jsxs("div", { className: fieldClassName, children: [_jsxs("span", { className: "uhcp-clinical-editor-field-heading", children: [_jsxs("label", { htmlFor: inputId, children: [label, requiredClaims.has(claim) ? ' *' : ''] }), isSpeechCompatibleField(parameter, schemaNode) ? _jsx(ClinicalSpeechInputButton, { resourceType: request.resourceType, claim: claim, locale: locale, fieldLabel: label, onTranscript: appendTranscript }) : null] }), statusOptions(request.resourceType, parameter) ? _jsxs("select", { id: inputId, "aria-label": label, value: value, required: requiredClaims.has(claim), onChange: update, children: [!requiredClaims.has(claim) ? _jsx("option", { value: "" }) : null, statusOptions(request.resourceType, parameter).map((option) => _jsx("option", { value: option, children: humanize(option) }, option))] }) : isMultiline(parameter) ? _jsx("textarea", { id: inputId, "aria-label": label, value: value, required: requiredClaims.has(claim), rows: 3, onChange: update })
144
154
  : _jsx("input", { id: inputId, "aria-label": label, value: value, required: requiredClaims.has(claim), type: fieldInputType(parameter, operationSchema?.properties.claims?.properties?.[claim]), onChange: update }), terminologyEnabled && activeTerminologyClaim === claim ? _jsxs("div", { className: "uhcp-terminology-results", children: [terminologyBusy ? _jsx("small", { role: "status", children: copy.terminologyLoading }) : null, terminologyError ? _jsx("small", { role: "alert", children: copy.terminologyUnavailable }) : null, candidates.length ? _jsx("ul", { children: candidates.map((candidate) => _jsx("li", { children: _jsxs("button", { type: "button", onClick: () => {
145
155
  setClaims((current) => selectedCodingClaims(request.resourceType, claim, candidate, acceptedClaims, current));
146
156
  setCandidates([]);
@@ -185,6 +195,13 @@ function composeTemporalClaim(parts, timeZone) {
185
195
  function isDateTimeSchemaNode(schemaNode) {
186
196
  return schemaNode?.format === 'date-time' || schemaNode?.format === 'fhir-date-time';
187
197
  }
198
+ function isSpeechCompatibleField(parameter, schemaNode) {
199
+ if (statusOptions('', parameter))
200
+ return false;
201
+ if (schemaNode?.type === 'number' || schemaNode?.type === 'integer' || schemaNode?.type === 'boolean')
202
+ return false;
203
+ return fieldInputType(parameter, schemaNode) === 'text';
204
+ }
188
205
  function isSaveOutcome(value) {
189
206
  return typeof value === 'object' && value !== null && 'state' in value;
190
207
  }
@@ -0,0 +1,58 @@
1
+ import { type ReactNode } from 'react';
2
+ export declare const ClinicalSpeechInputPhases: Readonly<{
3
+ readonly Idle: "idle";
4
+ readonly Checking: "checking";
5
+ readonly Downloading: "downloading";
6
+ readonly Initializing: "initializing";
7
+ readonly Ready: "ready";
8
+ readonly Error: "error";
9
+ readonly Unavailable: "unavailable";
10
+ }>;
11
+ export type ClinicalSpeechInputPhase = typeof ClinicalSpeechInputPhases[keyof typeof ClinicalSpeechInputPhases];
12
+ export type ClinicalSpeechInputRequest = Readonly<{
13
+ resourceType: string;
14
+ claim: string;
15
+ locale: string;
16
+ }>;
17
+ export type ClinicalSpeechInputCapability = Readonly<{
18
+ phase: ClinicalSpeechInputPhase;
19
+ transcribe: (input: ClinicalSpeechInputRequest) => Promise<string>;
20
+ labels: Readonly<{
21
+ start: string;
22
+ unavailable: string;
23
+ busy: string;
24
+ failed?: string;
25
+ }>;
26
+ }>;
27
+ export type ClinicalSpeechInputRuntime = Pick<ClinicalSpeechInputCapability, 'phase' | 'transcribe'>;
28
+ export type ClinicalSpeechInputChannel = Readonly<{
29
+ getSnapshot: () => ClinicalSpeechInputRuntime;
30
+ publish: (runtime: ClinicalSpeechInputRuntime) => void;
31
+ subscribe: (listener: () => void) => () => void;
32
+ }>;
33
+ /**
34
+ * Creates one small external store through which a portal-owned model runtime
35
+ * can announce download, initialization and readiness changes to every nested
36
+ * clinical editor without importing the model into the shared UI package.
37
+ */
38
+ export declare function createClinicalSpeechInputChannel(initialRuntime?: ClinicalSpeechInputRuntime): ClinicalSpeechInputChannel;
39
+ /**
40
+ * Supplies one portal-owned speech-recognition capability to nested clinical
41
+ * editors. The portal owns model download, microphone permission and fallback;
42
+ * shared editors receive only an editable transcript.
43
+ */
44
+ export declare function ClinicalSpeechInputProvider({ value, children }: Readonly<{
45
+ value: ClinicalSpeechInputCapability;
46
+ children: ReactNode;
47
+ }>): import("react").JSX.Element;
48
+ export declare function ClinicalSpeechInputChannelProvider({ channel, labels, children, }: Readonly<{
49
+ channel: ClinicalSpeechInputChannel;
50
+ labels: ClinicalSpeechInputCapability['labels'];
51
+ children: ReactNode;
52
+ }>): import("react").JSX.Element;
53
+ export declare function useClinicalSpeechInput(): ClinicalSpeechInputCapability | undefined;
54
+ export declare function ClinicalSpeechInputButton({ resourceType, claim, locale, fieldLabel, onTranscript, }: Readonly<ClinicalSpeechInputRequest & {
55
+ fieldLabel: string;
56
+ onTranscript: (transcript: string) => void;
57
+ }>): import("react").JSX.Element | null;
58
+ //# sourceMappingURL=ClinicalSpeechInput.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClinicalSpeechInput.d.ts","sourceRoot":"","sources":["../src/ClinicalSpeechInput.tsx"],"names":[],"mappings":"AAEA,OAAO,EAA6D,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAEjG,eAAO,MAAM,yBAAyB;;;;;;;;EAQ3B,CAAA;AAEX,MAAM,MAAM,wBAAwB,GAAG,OAAO,yBAAyB,CAAC,MAAM,OAAO,yBAAyB,CAAC,CAAA;AAE/G,MAAM,MAAM,0BAA0B,GAAG,QAAQ,CAAC;IAChD,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf,CAAC,CAAA;AAEF,MAAM,MAAM,6BAA6B,GAAG,QAAQ,CAAC;IACnD,KAAK,EAAE,wBAAwB,CAAA;IAC/B,UAAU,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAClE,MAAM,EAAE,QAAQ,CAAC;QACf,KAAK,EAAE,MAAM,CAAA;QACb,WAAW,EAAE,MAAM,CAAA;QACnB,IAAI,EAAE,MAAM,CAAA;QACZ,MAAM,CAAC,EAAE,MAAM,CAAA;KAChB,CAAC,CAAA;CACH,CAAC,CAAA;AAEF,MAAM,MAAM,0BAA0B,GAAG,IAAI,CAAC,6BAA6B,EAAE,OAAO,GAAG,YAAY,CAAC,CAAA;AAEpG,MAAM,MAAM,0BAA0B,GAAG,QAAQ,CAAC;IAChD,WAAW,EAAE,MAAM,0BAA0B,CAAA;IAC7C,OAAO,EAAE,CAAC,OAAO,EAAE,0BAA0B,KAAK,IAAI,CAAA;IACtD,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAA;CAChD,CAAC,CAAA;AAOF;;;;GAIG;AACH,wBAAgB,gCAAgC,CAC9C,cAAc,GAAE,0BAA+C,GAC9D,0BAA0B,CAc5B;AAID;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,QAAQ,CAAC;IACxE,KAAK,EAAE,6BAA6B,CAAA;IACpC,QAAQ,EAAE,SAAS,CAAA;CACpB,CAAC,+BAED;AAED,wBAAgB,kCAAkC,CAAC,EACjD,OAAO,EACP,MAAM,EACN,QAAQ,GACT,EAAE,QAAQ,CAAC;IACV,OAAO,EAAE,0BAA0B,CAAA;IACnC,MAAM,EAAE,6BAA6B,CAAC,QAAQ,CAAC,CAAA;IAC/C,QAAQ,EAAE,SAAS,CAAA;CACpB,CAAC,+BAGD;AAED,wBAAgB,sBAAsB,IAAI,6BAA6B,GAAG,SAAS,CAElF;AAED,wBAAgB,yBAAyB,CAAC,EACxC,YAAY,EACZ,KAAK,EACL,MAAM,EACN,UAAU,EACV,YAAY,GACb,EAAE,QAAQ,CAAC,0BAA0B,GAAG;IACvC,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAA;CAC3C,CAAC,sCA2CD"}
@@ -0,0 +1,83 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { createContext, useContext, useState, useSyncExternalStore } from 'react';
4
+ export const ClinicalSpeechInputPhases = Object.freeze({
5
+ Idle: 'idle',
6
+ Checking: 'checking',
7
+ Downloading: 'downloading',
8
+ Initializing: 'initializing',
9
+ Ready: 'ready',
10
+ Error: 'error',
11
+ Unavailable: 'unavailable',
12
+ });
13
+ const unavailableRuntime = Object.freeze({
14
+ phase: ClinicalSpeechInputPhases.Unavailable,
15
+ transcribe: async () => { throw new Error('clinical_speech_input_unavailable'); },
16
+ });
17
+ /**
18
+ * Creates one small external store through which a portal-owned model runtime
19
+ * can announce download, initialization and readiness changes to every nested
20
+ * clinical editor without importing the model into the shared UI package.
21
+ */
22
+ export function createClinicalSpeechInputChannel(initialRuntime = unavailableRuntime) {
23
+ let snapshot = Object.freeze(initialRuntime);
24
+ const listeners = new Set();
25
+ return Object.freeze({
26
+ getSnapshot: () => snapshot,
27
+ publish(runtime) {
28
+ snapshot = Object.freeze(runtime);
29
+ listeners.forEach((listener) => listener());
30
+ },
31
+ subscribe(listener) {
32
+ listeners.add(listener);
33
+ return () => listeners.delete(listener);
34
+ },
35
+ });
36
+ }
37
+ const ClinicalSpeechInputContext = createContext(undefined);
38
+ /**
39
+ * Supplies one portal-owned speech-recognition capability to nested clinical
40
+ * editors. The portal owns model download, microphone permission and fallback;
41
+ * shared editors receive only an editable transcript.
42
+ */
43
+ export function ClinicalSpeechInputProvider({ value, children }) {
44
+ return _jsx(ClinicalSpeechInputContext.Provider, { value: value, children: children });
45
+ }
46
+ export function ClinicalSpeechInputChannelProvider({ channel, labels, children, }) {
47
+ const runtime = useSyncExternalStore(channel.subscribe, channel.getSnapshot, channel.getSnapshot);
48
+ return _jsx(ClinicalSpeechInputProvider, { value: { ...runtime, labels }, children: children });
49
+ }
50
+ export function useClinicalSpeechInput() {
51
+ return useContext(ClinicalSpeechInputContext);
52
+ }
53
+ export function ClinicalSpeechInputButton({ resourceType, claim, locale, fieldLabel, onTranscript, }) {
54
+ const capability = useClinicalSpeechInput();
55
+ const [busy, setBusy] = useState(false);
56
+ const [failed, setFailed] = useState(false);
57
+ if (!capability)
58
+ return null;
59
+ const ready = capability.phase === ClinicalSpeechInputPhases.Ready;
60
+ const actionLabel = busy
61
+ ? capability.labels.busy
62
+ : ready
63
+ ? capability.labels.start
64
+ : capability.labels.unavailable;
65
+ const transcribe = async () => {
66
+ if (!ready || busy)
67
+ return;
68
+ setBusy(true);
69
+ setFailed(false);
70
+ try {
71
+ const transcript = (await capability.transcribe({ resourceType, claim, locale })).trim();
72
+ if (transcript)
73
+ onTranscript(transcript);
74
+ }
75
+ catch {
76
+ setFailed(true);
77
+ }
78
+ finally {
79
+ setBusy(false);
80
+ }
81
+ };
82
+ return _jsxs("span", { className: "uhcp-clinical-speech-control", children: [_jsx("button", { type: "button", className: "uhcp-clinical-speech-button", "aria-label": `${actionLabel}: ${fieldLabel}`, title: actionLabel, disabled: !ready || busy, "aria-pressed": busy, onClick: () => { void transcribe(); }, children: _jsx("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", focusable: "false", children: _jsx("path", { d: "M12 14a3 3 0 0 0 3-3V5a3 3 0 1 0-6 0v6a3 3 0 0 0 3 3Zm-5-3a1 1 0 1 0-2 0 7 7 0 0 0 6 6.92V21H8a1 1 0 1 0 0 2h8a1 1 0 1 0 0-2h-3v-3.08A7 7 0 0 0 19 11a1 1 0 1 0-2 0 5 5 0 0 1-10 0Z" }) }) }), failed ? _jsx("small", { role: "alert", children: capability.labels.failed ?? capability.labels.unavailable }) : null] });
83
+ }
@@ -0,0 +1,26 @@
1
+ import { type ReactNode } from 'react';
2
+ export type SubjectCommunicationKind = 'message' | 'audio' | 'audio-call' | 'video-call' | 'appointment';
3
+ export type SubjectCommunicationCategory = 'emergency' | 'care' | 'other';
4
+ export type SubjectCommunicationFilter = 'all' | 'messages' | 'calls' | 'emergency';
5
+ export type SubjectCommunicationItem = Readonly<{
6
+ id: string;
7
+ kind: SubjectCommunicationKind;
8
+ category: SubjectCommunicationCategory;
9
+ title: string;
10
+ summary?: string;
11
+ timestamp?: string;
12
+ status?: string;
13
+ action?: ReactNode;
14
+ }>;
15
+ export type SubjectCommunicationCenterCopy = Readonly<{
16
+ heading(subjectLabel: string): string;
17
+ filters: Readonly<Record<SubjectCommunicationFilter, string>>;
18
+ empty: string;
19
+ }>;
20
+ /** Brand-neutral, subject-scoped message and internet-call timeline for personal portals. */
21
+ export declare function SubjectCommunicationCenter({ subjectLabel, items, copy }: Readonly<{
22
+ subjectLabel: string;
23
+ items: readonly SubjectCommunicationItem[];
24
+ copy?: SubjectCommunicationCenterCopy;
25
+ }>): import("react").JSX.Element;
26
+ //# sourceMappingURL=SubjectCommunicationCenter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SubjectCommunicationCenter.d.ts","sourceRoot":"","sources":["../src/SubjectCommunicationCenter.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAqB,MAAM,OAAO,CAAA;AAEzD,MAAM,MAAM,wBAAwB,GAAG,SAAS,GAAG,OAAO,GAAG,YAAY,GAAG,YAAY,GAAG,aAAa,CAAA;AACxG,MAAM,MAAM,4BAA4B,GAAG,WAAW,GAAG,MAAM,GAAG,OAAO,CAAA;AACzE,MAAM,MAAM,0BAA0B,GAAG,KAAK,GAAG,UAAU,GAAG,OAAO,GAAG,WAAW,CAAA;AAEnF,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC;IAC9C,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,wBAAwB,CAAA;IAC9B,QAAQ,EAAE,4BAA4B,CAAA;IACtC,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,SAAS,CAAA;CACnB,CAAC,CAAA;AAEF,MAAM,MAAM,8BAA8B,GAAG,QAAQ,CAAC;IACpD,OAAO,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAA;IACrC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,0BAA0B,EAAE,MAAM,CAAC,CAAC,CAAA;IAC7D,KAAK,EAAE,MAAM,CAAA;CACd,CAAC,CAAA;AAUF,6FAA6F;AAC7F,wBAAgB,0BAA0B,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,IAAmB,EAAE,EAAE,QAAQ,CAAC;IAChG,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,EAAE,SAAS,wBAAwB,EAAE,CAAA;IAC1C,IAAI,CAAC,EAAE,8BAA8B,CAAA;CACtC,CAAC,+BAsBD"}
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useState } from 'react';
3
+ const DEFAULT_COPY = {
4
+ heading: subjectLabel => `Communications for ${subjectLabel}`,
5
+ filters: { all: 'All', messages: 'Messages', calls: 'Calls', emergency: 'Emergency' },
6
+ empty: 'There are no communications in this view.',
7
+ };
8
+ const FILTERS = ['all', 'messages', 'calls', 'emergency'];
9
+ /** Brand-neutral, subject-scoped message and internet-call timeline for personal portals. */
10
+ export function SubjectCommunicationCenter({ subjectLabel, items, copy = DEFAULT_COPY }) {
11
+ const [filter, setFilter] = useState('all');
12
+ const visibleItems = useMemo(() => items.filter(item => matchesFilter(item, filter)), [filter, items]);
13
+ return _jsxs("section", { className: "subject-communication-center", "aria-labelledby": "subject-communication-center-title", children: [_jsx("h2", { id: "subject-communication-center-title", children: copy.heading(subjectLabel) }), _jsx("div", { className: "subject-communication-filters", "aria-label": copy.heading(subjectLabel), children: FILTERS.map(value => _jsx("button", { type: "button", "aria-pressed": filter === value, onClick: () => setFilter(value), children: copy.filters[value] }, value)) }), visibleItems.length === 0 ? _jsx("p", { className: "subject-communication-empty", children: copy.empty }) : _jsx("ol", { className: "subject-communication-list", children: visibleItems.map(item => _jsxs("li", { "data-kind": item.kind, "data-category": item.category, children: [_jsxs("div", { children: [_jsx("strong", { children: item.title }), item.summary ? _jsx("p", { children: item.summary }) : null] }), _jsxs("div", { className: "subject-communication-meta", children: [item.timestamp ? _jsx("time", { dateTime: item.timestamp, children: item.timestamp }) : null, item.status ? _jsx("span", { children: item.status }) : null, item.action] })] }, item.id)) })] });
14
+ }
15
+ function matchesFilter(item, filter) {
16
+ if (filter === 'all')
17
+ return true;
18
+ if (filter === 'emergency')
19
+ return item.category === 'emergency';
20
+ if (filter === 'calls')
21
+ return item.kind === 'audio-call' || item.kind === 'video-call';
22
+ return item.kind === 'message' || item.kind === 'audio' || item.kind === 'appointment';
23
+ }
package/dist/index.d.ts CHANGED
@@ -12,6 +12,7 @@ export * from './ClinicalDocumentEncounterExplorer.js';
12
12
  export * from './AllClinicalSections.js';
13
13
  export * from './ClinicalTimeline.js';
14
14
  export * from './ClinicalArtifactEditor.js';
15
+ export * from './ClinicalSpeechInput.js';
15
16
  export * from './ClinicalActionButton.js';
16
17
  export * from './VitalSignArtifactEditor.js';
17
18
  export * from './InvitationAcceptancePanel.js';
@@ -29,4 +30,5 @@ export * from './IdentityEvidenceDocumentPanel.js';
29
30
  export * from './IdentityEvidenceDeclarationForm.js';
30
31
  export * from './AnimalIdentityOnboarding.js';
31
32
  export * from './EmergencyTaskHub.js';
33
+ export * from './SubjectCommunicationCenter.js';
32
34
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,yBAAyB,CAAA;AACvC,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,4BAA4B,CAAA;AAC1C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,uBAAuB,CAAA;AACrC,cAAc,wCAAwC,CAAA;AACtD,cAAc,0BAA0B,CAAA;AACxC,cAAc,uBAAuB,CAAA;AACrC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,2BAA2B,CAAA;AACzC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,wBAAwB,CAAA;AACtC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,iCAAiC,CAAA;AAC/C,cAAc,0CAA0C,CAAA;AACxD,cAAc,kCAAkC,CAAA;AAChD,cAAc,0BAA0B,CAAA;AACxC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,oCAAoC,CAAA;AAClD,cAAc,sCAAsC,CAAA;AACpD,cAAc,+BAA+B,CAAA;AAC7C,cAAc,uBAAuB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,yBAAyB,CAAA;AACvC,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,4BAA4B,CAAA;AAC1C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,uBAAuB,CAAA;AACrC,cAAc,wCAAwC,CAAA;AACtD,cAAc,0BAA0B,CAAA;AACxC,cAAc,uBAAuB,CAAA;AACrC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,0BAA0B,CAAA;AACxC,cAAc,2BAA2B,CAAA;AACzC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,wBAAwB,CAAA;AACtC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,iCAAiC,CAAA;AAC/C,cAAc,0CAA0C,CAAA;AACxD,cAAc,kCAAkC,CAAA;AAChD,cAAc,0BAA0B,CAAA;AACxC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,oCAAoC,CAAA;AAClD,cAAc,sCAAsC,CAAA;AACpD,cAAc,+BAA+B,CAAA;AAC7C,cAAc,uBAAuB,CAAA;AACrC,cAAc,iCAAiC,CAAA"}
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ export * from './ClinicalDocumentEncounterExplorer.js';
12
12
  export * from './AllClinicalSections.js';
13
13
  export * from './ClinicalTimeline.js';
14
14
  export * from './ClinicalArtifactEditor.js';
15
+ export * from './ClinicalSpeechInput.js';
15
16
  export * from './ClinicalActionButton.js';
16
17
  export * from './VitalSignArtifactEditor.js';
17
18
  export * from './InvitationAcceptancePanel.js';
@@ -29,3 +30,4 @@ export * from './IdentityEvidenceDocumentPanel.js';
29
30
  export * from './IdentityEvidenceDeclarationForm.js';
30
31
  export * from './AnimalIdentityOnboarding.js';
31
32
  export * from './EmergencyTaskHub.js';
33
+ export * from './SubjectCommunicationCenter.js';
package/dist/styles.css CHANGED
@@ -177,8 +177,14 @@ html[data-theme='dark'] .uhcp-brand-artwork-dark { display: block !important; }
177
177
  .uhcp-clinical-editor h2,.uhcp-clinical-editor p { margin:.2rem 0; }
178
178
  .uhcp-clinical-editor>header>button { background:transparent; border:1px solid var(--uhcp-border); border-radius:50%; color:inherit; cursor:pointer; font-size:1.4rem; height:2.5rem; width:2.5rem; }
179
179
  .uhcp-clinical-editor form { display:grid; gap:1rem; grid-template-columns:repeat(2,minmax(0,1fr)); margin-top:1.25rem; min-width:0; overflow:auto; }
180
- .uhcp-clinical-editor label { color:var(--uhcp-text); display:grid; font-weight:700; gap:.35rem; }
181
- .uhcp-clinical-editor label,.uhcp-clinical-editor fieldset { min-width:0; }
180
+ .uhcp-clinical-editor label { color:var(--uhcp-text); font-weight:700; }
181
+ .uhcp-clinical-editor label,.uhcp-clinical-editor fieldset,.uhcp-clinical-editor-field { min-width:0; }
182
+ .uhcp-clinical-editor-field { display:grid; gap:.35rem; }
183
+ .uhcp-clinical-editor-field-heading { align-items:center; display:flex; gap:.5rem; justify-content:space-between; }
184
+ .uhcp-clinical-speech-control { align-items:center; display:flex; gap:.4rem; }
185
+ .uhcp-clinical-speech-button { align-items:center; background:transparent; border:1px solid var(--uhcp-border); border-radius:50%; color:inherit; cursor:pointer; display:inline-flex; height:2.25rem; justify-content:center; padding:.45rem; width:2.25rem; }
186
+ .uhcp-clinical-speech-button:disabled { cursor:not-allowed; opacity:.45; }
187
+ .uhcp-clinical-speech-button svg { fill:currentColor; height:1.15rem; width:1.15rem; }
182
188
  .uhcp-clinical-editor input,.uhcp-clinical-editor select,.uhcp-clinical-editor textarea { background:var(--uhcp-panel); border:1px solid var(--uhcp-border); border-radius:.7rem; color:inherit; font:inherit; min-width:0; padding:.75rem; width:100%; }
183
189
  .uhcp-clinical-editor-date-time { grid-column:1/-1; }
184
190
  .uhcp-clinical-editor-wide,.uhcp-clinical-editor-error,.uhcp-clinical-editor footer { grid-column:1/-1; }
@@ -673,3 +679,13 @@ html[data-theme='dark'] .uhcp-brand-artwork-dark { display: block !important; }
673
679
  .uhcp-subject-identity-card dt { color: var(--uhcp-muted, #5f6875); font-size: .8rem; }
674
680
  .uhcp-subject-identity-card dd { font-weight: 700; margin: .15rem 0 0; overflow-wrap: anywhere; }
675
681
  .uhcp-subject-identity-relationship { align-items: center; background: var(--uhcp-surface-soft, #f6f8fb); border-radius: .8rem; display: grid; gap: .25rem; padding: .8rem; }
682
+ /* Subject-scoped communications remain product-neutral; portals provide brand tokens. */
683
+ .subject-communication-center { display: grid; gap: 1rem; }
684
+ .subject-communication-filters { display: flex; flex-wrap: wrap; gap: .5rem; }
685
+ .subject-communication-filters button { border: 1px solid currentColor; border-radius: 999px; padding: .55rem .9rem; background: transparent; font: inherit; cursor: pointer; }
686
+ .subject-communication-filters button[aria-pressed="true"] { background: currentColor; color: Canvas; }
687
+ .subject-communication-list { display: grid; gap: .75rem; margin: 0; padding: 0; list-style: none; }
688
+ .subject-communication-list > li { display: flex; justify-content: space-between; gap: 1rem; align-items: start; border: 1px solid color-mix(in srgb, currentColor 18%, transparent); border-radius: 1rem; padding: 1rem; }
689
+ .subject-communication-list p { margin: .35rem 0 0; }
690
+ .subject-communication-meta { display: grid; justify-items: end; gap: .35rem; font-size: .875em; }
691
+ .subject-communication-empty { margin: 0; opacity: .72; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uhc-portal-components-react",
3
- "version": "0.1.36",
3
+ "version": "0.1.38",
4
4
  "description": "Brand-neutral React portal shell, account preferences and protected-profile UI for UHC and VetChain portals.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",