uhc-portal-components-react 0.2.24 → 0.2.26

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,19 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.2.26 - 2026-10-22
6
+
7
+ - Keep race and ethnicity outside the universal human profile: trusted
8
+ jurisdictional profiles may enable either, both or neither.
9
+ - Present enabled human demographic concepts as optional additions and omit
10
+ empty optional concepts from read-only views.
11
+
12
+ ## 0.2.25 - 2026-10-22
13
+
14
+ - Add one shared read/edit demographics slot whose trusted profile renders
15
+ human race and ethnicity or animal species and breed while preserving
16
+ distinct field names, terminology options and persistence adapters.
17
+
5
18
  ## 0.2.24 - 2026-10-22
6
19
 
7
20
  - Let each deployment omit brand artwork when no official mark is configured;
@@ -0,0 +1,42 @@
1
+ export type SubjectDemographicsOption = Readonly<{
2
+ value: string;
3
+ label: string;
4
+ }>;
5
+ export type SubjectDemographicsProfile = Readonly<{
6
+ subjectKind: 'human';
7
+ raceOptions?: readonly SubjectDemographicsOption[];
8
+ ethnicityOptions?: readonly SubjectDemographicsOption[];
9
+ }> | Readonly<{
10
+ subjectKind: 'animal';
11
+ speciesOptions: readonly SubjectDemographicsOption[];
12
+ breedOptions: readonly SubjectDemographicsOption[];
13
+ }>;
14
+ export type SubjectDemographicsCopy = Readonly<{
15
+ race?: string;
16
+ ethnicity?: string;
17
+ addRace?: string;
18
+ addEthnicity?: string;
19
+ species?: string;
20
+ breed?: string;
21
+ }>;
22
+ /**
23
+ * Shared presentation slot for human or animal demographics.
24
+ *
25
+ * Profiles preserve different field names and option catalogues so visual
26
+ * reuse can never turn race into species or ethnicity into breed. Hosts map
27
+ * the submitted fields to their own canonical persistence adapters.
28
+ */
29
+ export declare function SubjectDemographicsFields({ mode, profile, copy, values, onValuesChange, }: Readonly<{
30
+ mode: 'edit' | 'read';
31
+ profile: SubjectDemographicsProfile;
32
+ copy: SubjectDemographicsCopy;
33
+ values?: Readonly<{
34
+ primary?: string;
35
+ secondary?: string;
36
+ }>;
37
+ onValuesChange?: (values: Readonly<{
38
+ primary?: string;
39
+ secondary?: string;
40
+ }>) => void;
41
+ }>): import("react").JSX.Element;
42
+ //# sourceMappingURL=SubjectDemographicsFields.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SubjectDemographicsFields.d.ts","sourceRoot":"","sources":["../src/SubjectDemographicsFields.tsx"],"names":[],"mappings":"AAEA,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC/C,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd,CAAC,CAAA;AAEF,MAAM,MAAM,0BAA0B,GAClC,QAAQ,CAAC;IACP,WAAW,EAAE,OAAO,CAAA;IACpB,WAAW,CAAC,EAAE,SAAS,yBAAyB,EAAE,CAAA;IAClD,gBAAgB,CAAC,EAAE,SAAS,yBAAyB,EAAE,CAAA;CACxD,CAAC,GACF,QAAQ,CAAC;IACP,WAAW,EAAE,QAAQ,CAAA;IACrB,cAAc,EAAE,SAAS,yBAAyB,EAAE,CAAA;IACpD,YAAY,EAAE,SAAS,yBAAyB,EAAE,CAAA;CACnD,CAAC,CAAA;AAEN,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAC,CAAA;AAEF;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,EACxC,IAAI,EACJ,OAAO,EACP,IAAI,EACJ,MAAM,EACN,cAAc,GACf,EAAE,QAAQ,CAAC;IACV,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;IACrB,OAAO,EAAE,0BAA0B,CAAA;IACnC,IAAI,EAAE,uBAAuB,CAAA;IAC7B,MAAM,CAAC,EAAE,QAAQ,CAAC;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC3D,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,KAAK,IAAI,CAAA;CACtF,CAAC,+BAgFD"}
@@ -0,0 +1,71 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ /**
4
+ * Shared presentation slot for human or animal demographics.
5
+ *
6
+ * Profiles preserve different field names and option catalogues so visual
7
+ * reuse can never turn race into species or ethnicity into breed. Hosts map
8
+ * the submitted fields to their own canonical persistence adapters.
9
+ */
10
+ export function SubjectDemographicsFields({ mode, profile, copy, values, onValuesChange, }) {
11
+ const [visibleHumanFields, setVisibleHumanFields] = useState(() => new Set([
12
+ ...(values?.primary ? ['race'] : []),
13
+ ...(values?.secondary ? ['ethnicity'] : []),
14
+ ]));
15
+ const fields = profile.subjectKind === 'human'
16
+ ? [
17
+ ...(profile.raceOptions === undefined ? [] : [{
18
+ concept: 'race',
19
+ valueKey: 'primary',
20
+ name: 'human.race',
21
+ label: requiredCopy(copy.race, 'race'),
22
+ addLabel: mode === 'edit' ? requiredCopy(copy.addRace, 'add_race') : '',
23
+ options: profile.raceOptions,
24
+ value: values?.primary,
25
+ required: false,
26
+ }]),
27
+ ...(profile.ethnicityOptions === undefined ? [] : [{
28
+ concept: 'ethnicity',
29
+ valueKey: 'secondary',
30
+ name: 'human.ethnicity',
31
+ label: requiredCopy(copy.ethnicity, 'ethnicity'),
32
+ addLabel: mode === 'edit' ? requiredCopy(copy.addEthnicity, 'add_ethnicity') : '',
33
+ options: profile.ethnicityOptions,
34
+ value: values?.secondary,
35
+ required: false,
36
+ }]),
37
+ ]
38
+ : [
39
+ { concept: 'species', valueKey: 'primary', name: 'animal.species', label: requiredCopy(copy.species, 'species'), addLabel: '', options: profile.speciesOptions, value: values?.primary, required: true },
40
+ { concept: 'breed', valueKey: 'secondary', name: 'animal.breed', label: requiredCopy(copy.breed, 'breed'), addLabel: '', options: profile.breedOptions, value: values?.secondary, required: true },
41
+ ];
42
+ return _jsx("div", { className: "uhcp-subject-demographics-fields", "data-subject-kind": profile.subjectKind, children: mode === 'edit' && profile.subjectKind === 'human'
43
+ ? fields.map((field) => visibleHumanFields.has(field.concept)
44
+ ? _jsxs("label", { children: [_jsx("span", { children: field.label }), _jsxs("select", { name: field.name, "aria-label": field.label, ...(onValuesChange
45
+ ? {
46
+ value: field.value ?? '',
47
+ onChange: (event) => onValuesChange({
48
+ primary: field.valueKey === 'primary' ? event.currentTarget.value : values?.primary,
49
+ secondary: field.valueKey === 'secondary' ? event.currentTarget.value : values?.secondary,
50
+ }),
51
+ }
52
+ : { defaultValue: field.value ?? '' }), children: [_jsx("option", { value: "", disabled: true, children: field.label }), field.options.map((option) => _jsx("option", { value: option.value, children: option.label }, option.value))] })] }, field.name)
53
+ : _jsx("button", { className: "uhcp-subject-demographics-add", type: "button", onClick: () => setVisibleHumanFields((current) => new Set([...current, field.concept])), children: field.addLabel }, field.name))
54
+ : mode === 'edit'
55
+ ? fields.map((field) => _jsxs("label", { children: [_jsx("span", { children: field.label }), _jsxs("select", { name: field.name, "aria-label": field.label, required: field.required, ...(onValuesChange
56
+ ? {
57
+ value: field.value ?? '',
58
+ onChange: (event) => onValuesChange({
59
+ primary: field.valueKey === 'primary' ? event.currentTarget.value : values?.primary,
60
+ secondary: field.valueKey === 'secondary' ? event.currentTarget.value : values?.secondary,
61
+ }),
62
+ }
63
+ : { defaultValue: field.value ?? '' }), children: [_jsx("option", { value: "", disabled: true, children: field.label }), field.options.map((option) => _jsx("option", { value: option.value, children: option.label }, option.value))] })] }, field.name))
64
+ : _jsx("dl", { children: fields.filter((field) => profile.subjectKind === 'animal' || field.value).map((field) => _jsxs("div", { children: [_jsx("dt", { children: field.label }), _jsx("dd", { children: field.value || '—' })] }, field.name)) }) });
65
+ }
66
+ function requiredCopy(value, field) {
67
+ const normalized = String(value || '').trim();
68
+ if (!normalized)
69
+ throw new Error(`subject_demographics_${field}_copy_required`);
70
+ return normalized;
71
+ }
package/dist/index.d.ts CHANGED
@@ -50,6 +50,7 @@ export * from './notification-bff-client.js';
50
50
  export * from './CommunicationsWorkspace.js';
51
51
  export * from './SelectedSubjectWorkspace.js';
52
52
  export * from './SubjectWorkspace.js';
53
+ export * from './SubjectDemographicsFields.js';
53
54
  export * from './CardPreview.js';
54
55
  export * from './CollapsiblePortalSection.js';
55
56
  export * from './ElapsedOperationStatus.js';
@@ -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,2BAA2B,CAAA;AACzC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,8BAA8B,CAAA;AAC5C,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,mCAAmC,CAAA;AACjD,cAAc,+BAA+B,CAAA;AAC7C,cAAc,qCAAqC,CAAA;AACnD,cAAc,mCAAmC,CAAA;AACjD,cAAc,gCAAgC,CAAA;AAC9C,cAAc,0BAA0B,CAAA;AACxC,cAAc,iCAAiC,CAAA;AAC/C,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,iCAAiC,CAAA;AAC/C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,yBAAyB,CAAA;AACvC,cAAc,uCAAuC,CAAA;AACrD,cAAc,uBAAuB,CAAA;AACrC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,2BAA2B,CAAA;AACzC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,uBAAuB,CAAA;AACrC,cAAc,kBAAkB,CAAA;AAChC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,2BAA2B,CAAA;AACzC,cAAc,qBAAqB,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,2BAA2B,CAAA;AACzC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,8BAA8B,CAAA;AAC5C,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,mCAAmC,CAAA;AACjD,cAAc,+BAA+B,CAAA;AAC7C,cAAc,qCAAqC,CAAA;AACnD,cAAc,mCAAmC,CAAA;AACjD,cAAc,gCAAgC,CAAA;AAC9C,cAAc,0BAA0B,CAAA;AACxC,cAAc,iCAAiC,CAAA;AAC/C,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,iCAAiC,CAAA;AAC/C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,yBAAyB,CAAA;AACvC,cAAc,uCAAuC,CAAA;AACrD,cAAc,uBAAuB,CAAA;AACrC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,2BAA2B,CAAA;AACzC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,uBAAuB,CAAA;AACrC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,kBAAkB,CAAA;AAChC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,2BAA2B,CAAA;AACzC,cAAc,qBAAqB,CAAA"}
package/dist/index.js CHANGED
@@ -50,6 +50,7 @@ export * from './notification-bff-client.js';
50
50
  export * from './CommunicationsWorkspace.js';
51
51
  export * from './SelectedSubjectWorkspace.js';
52
52
  export * from './SubjectWorkspace.js';
53
+ export * from './SubjectDemographicsFields.js';
53
54
  export * from './CardPreview.js';
54
55
  export * from './CollapsiblePortalSection.js';
55
56
  export * from './ElapsedOperationStatus.js';
package/dist/styles.css CHANGED
@@ -974,3 +974,37 @@ html[data-theme='dark'] .uhcp-brand-artwork-dark { display: block !important; }
974
974
  .uhcp-selected-subject-tabs { display: flex; flex-wrap: wrap; gap: 0.75rem; }
975
975
  .uhcp-selected-subject-tabs button { min-height: 2.75rem; border: 1px solid var(--uhcp-border, #cbd5e1); border-radius: 999px; padding: 0.625rem 1rem; background: var(--uhcp-surface, #fff); color: inherit; font: inherit; font-weight: 700; }
976
976
  .uhcp-selected-subject-tabs button[aria-selected='true'] { border-color: var(--uhcp-accent, #0f766e); background: color-mix(in srgb, var(--uhcp-accent, #0f766e) 12%, transparent); }
977
+
978
+ .uhcp-subject-demographics-fields,
979
+ .uhcp-subject-demographics-fields dl {
980
+ display: grid;
981
+ grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
982
+ gap: 1rem;
983
+ }
984
+
985
+ .uhcp-subject-demographics-fields label,
986
+ .uhcp-subject-demographics-fields dl > div {
987
+ display: grid;
988
+ gap: 0.4rem;
989
+ }
990
+
991
+ .uhcp-subject-demographics-fields label > span,
992
+ .uhcp-subject-demographics-fields dt {
993
+ font-weight: 700;
994
+ }
995
+
996
+ .uhcp-subject-demographics-fields select {
997
+ min-height: 2.75rem;
998
+ width: 100%;
999
+ border: 1px solid var(--uhcp-border, #cbd5e1);
1000
+ border-radius: 0.75rem;
1001
+ padding: 0.625rem 0.75rem;
1002
+ background: var(--uhcp-surface, #fff);
1003
+ color: inherit;
1004
+ font: inherit;
1005
+ }
1006
+
1007
+ .uhcp-subject-demographics-fields dl,
1008
+ .uhcp-subject-demographics-fields dd {
1009
+ margin: 0;
1010
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uhc-portal-components-react",
3
- "version": "0.2.24",
3
+ "version": "0.2.26",
4
4
  "description": "Brand-neutral React portal screens, account preferences and protected-profile UI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",