@alfadocs/ui-kit-debug 0.65.0 → 0.66.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.
- package/dist/_chunks/{ai-tools-rail-JnlTqfS4.js → ai-tools-rail-DAfaW8eA.js} +64 -55
- package/dist/_chunks/ai-tools-rail-DAfaW8eA.js.map +1 -0
- package/dist/_chunks/{bishop-score-x2fNqbuZ.js → bishop-score-DPN4Ched.js} +2 -2
- package/dist/_chunks/{bishop-score-x2fNqbuZ.js.map → bishop-score-DPN4Ched.js.map} +1 -1
- package/dist/_chunks/{bmi-calculator-C3roxbqD.js → bmi-calculator-DuUneHuZ.js} +2 -2
- package/dist/_chunks/{bmi-calculator-C3roxbqD.js.map → bmi-calculator-DuUneHuZ.js.map} +1 -1
- package/dist/_chunks/columns-2-BTOBZbep.js +15 -0
- package/dist/_chunks/columns-2-BTOBZbep.js.map +1 -0
- package/dist/_chunks/{cycle-calculator-DrVXbIB4.js → cycle-calculator-D6LTmtQt.js} +2 -2
- package/dist/_chunks/{cycle-calculator-DrVXbIB4.js.map → cycle-calculator-D6LTmtQt.js.map} +1 -1
- package/dist/_chunks/{due-date-calculator-D4K1BnE2.js → due-date-calculator-CIG194vq.js} +2 -2
- package/dist/_chunks/{due-date-calculator-D4K1BnE2.js.map → due-date-calculator-CIG194vq.js.map} +1 -1
- package/dist/_chunks/{editable-currency-cell-renderer-CFqQc56y.js → editable-currency-cell-renderer-f3d0Rhue.js} +330 -340
- package/dist/_chunks/editable-currency-cell-renderer-f3d0Rhue.js.map +1 -0
- package/dist/_chunks/{fetal-weight-B_pBjlO7.js → fetal-weight-Xf8-ZoDy.js} +2 -2
- package/dist/_chunks/{fetal-weight-B_pBjlO7.js.map → fetal-weight-Xf8-ZoDy.js.map} +1 -1
- package/dist/_chunks/{gestational-age-calculator-BETQAVkA.js → gestational-age-calculator-BGsZaWXW.js} +2 -2
- package/dist/_chunks/{gestational-age-calculator-BETQAVkA.js.map → gestational-age-calculator-BGsZaWXW.js.map} +1 -1
- package/dist/_chunks/{hcg-doubling-8zXCJgAQ.js → hcg-doubling-LH5i6oM7.js} +2 -2
- package/dist/_chunks/{hcg-doubling-8zXCJgAQ.js.map → hcg-doubling-LH5i6oM7.js.map} +1 -1
- package/dist/_chunks/{type-DZ5OMRA2.js → image-C6RM5hfF.js} +4 -16
- package/dist/_chunks/image-C6RM5hfF.js.map +1 -0
- package/dist/_chunks/{insert-result-DIAG3ltJ.js → insert-result-njqBthzT.js} +68 -57
- package/dist/_chunks/insert-result-njqBthzT.js.map +1 -0
- package/dist/_chunks/{marketplace-app-shell-BXfRMiRk.js → marketplace-app-shell-Cou6XnMJ.js} +2 -2
- package/dist/_chunks/{marketplace-app-shell-BXfRMiRk.js.map → marketplace-app-shell-Cou6XnMJ.js.map} +1 -1
- package/dist/_chunks/{patient-shell-CF_qon7C.js → patient-shell-Tgpv0HND.js} +2 -2
- package/dist/_chunks/{patient-shell-CF_qon7C.js.map → patient-shell-Tgpv0HND.js.map} +1 -1
- package/dist/_chunks/{pregnancy-dating-BuzLTwjy.js → pregnancy-dating-f9aJbiAk.js} +2 -2
- package/dist/_chunks/{pregnancy-dating-BuzLTwjy.js.map → pregnancy-dating-f9aJbiAk.js.map} +1 -1
- package/dist/_chunks/{pregnancy-weight-gain-aicn7qs7.js → pregnancy-weight-gain-BMRBeA8V.js} +2 -2
- package/dist/_chunks/{pregnancy-weight-gain-aicn7qs7.js.map → pregnancy-weight-gain-BMRBeA8V.js.map} +1 -1
- package/dist/_chunks/rich-text-editor-B5XQNxe8.js +1586 -0
- package/dist/_chunks/rich-text-editor-B5XQNxe8.js.map +1 -0
- package/dist/_chunks/{sidebar-DPEHzxLZ.js → sidebar-BFwbx74W.js} +12 -1
- package/dist/_chunks/sidebar-BFwbx74W.js.map +1 -0
- package/dist/_chunks/trash-2-rPAKaRgB.js +18 -0
- package/dist/_chunks/trash-2-rPAKaRgB.js.map +1 -0
- package/dist/_chunks/{unit-converter-CHN2cZ4b.js → unit-converter-C1V3ACM9.js} +2 -2
- package/dist/_chunks/{unit-converter-CHN2cZ4b.js.map → unit-converter-C1V3ACM9.js.map} +1 -1
- package/dist/_chunks/{workflow-map-BX3JY1Bu.js → workflow-map-Bt4Of5dT.js} +136 -148
- package/dist/_chunks/workflow-map-Bt4Of5dT.js.map +1 -0
- package/dist/agent-catalog.json +1 -1
- package/dist/components/ai-tools-rail/ai-tools-rail.d.ts.map +1 -1
- package/dist/components/ai-tools-rail/index.js +1 -1
- package/dist/components/bishop-score/index.js +1 -1
- package/dist/components/bmi-calculator/index.js +1 -1
- package/dist/components/cycle-calculator/index.js +1 -1
- package/dist/components/data-table/index.js +1 -1
- package/dist/components/due-date-calculator/index.js +1 -1
- package/dist/components/fetal-weight/index.js +1 -1
- package/dist/components/gestational-age-calculator/index.js +1 -1
- package/dist/components/hcg-doubling/index.js +1 -1
- package/dist/components/pregnancy-dating/index.js +1 -1
- package/dist/components/pregnancy-weight-gain/index.js +1 -1
- package/dist/components/rich-text-editor/editor-toolbar.d.ts +12 -2
- package/dist/components/rich-text-editor/editor-toolbar.d.ts.map +1 -1
- package/dist/components/rich-text-editor/index.js +1 -1
- package/dist/components/rich-text-editor/rich-text-editor.d.ts.map +1 -1
- package/dist/components/sidebar/index.js +1 -1
- package/dist/components/sidebar/sidebar.d.ts.map +1 -1
- package/dist/components/unit-converter/index.js +1 -1
- package/dist/components/workflow/index.js +1 -1
- package/dist/i18n/locales/ar.d.ts +16 -0
- package/dist/i18n/locales/ar.d.ts.map +1 -1
- package/dist/i18n/locales/ar.js +16 -0
- package/dist/i18n/locales/ar.js.map +1 -1
- package/dist/i18n/locales/de.d.ts +16 -0
- package/dist/i18n/locales/de.d.ts.map +1 -1
- package/dist/i18n/locales/de.js +16 -0
- package/dist/i18n/locales/de.js.map +1 -1
- package/dist/i18n/locales/el.d.ts +16 -0
- package/dist/i18n/locales/el.d.ts.map +1 -1
- package/dist/i18n/locales/el.js +16 -0
- package/dist/i18n/locales/el.js.map +1 -1
- package/dist/i18n/locales/en.d.ts +16 -0
- package/dist/i18n/locales/en.d.ts.map +1 -1
- package/dist/i18n/locales/en.js +16 -0
- package/dist/i18n/locales/en.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +16 -0
- package/dist/i18n/locales/es.d.ts.map +1 -1
- package/dist/i18n/locales/es.js +16 -0
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +16 -0
- package/dist/i18n/locales/fr.d.ts.map +1 -1
- package/dist/i18n/locales/fr.js +16 -0
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hi.d.ts +16 -0
- package/dist/i18n/locales/hi.d.ts.map +1 -1
- package/dist/i18n/locales/hi.js +16 -0
- package/dist/i18n/locales/hi.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +16 -0
- package/dist/i18n/locales/it.d.ts.map +1 -1
- package/dist/i18n/locales/it.js +16 -0
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/ja.d.ts +16 -0
- package/dist/i18n/locales/ja.d.ts.map +1 -1
- package/dist/i18n/locales/ja.js +16 -0
- package/dist/i18n/locales/ja.js.map +1 -1
- package/dist/i18n/locales/nl.d.ts +16 -0
- package/dist/i18n/locales/nl.d.ts.map +1 -1
- package/dist/i18n/locales/nl.js +16 -0
- package/dist/i18n/locales/nl.js.map +1 -1
- package/dist/i18n/locales/pl.d.ts +16 -0
- package/dist/i18n/locales/pl.d.ts.map +1 -1
- package/dist/i18n/locales/pl.js +16 -0
- package/dist/i18n/locales/pl.js.map +1 -1
- package/dist/i18n/locales/pt.d.ts +16 -0
- package/dist/i18n/locales/pt.d.ts.map +1 -1
- package/dist/i18n/locales/pt.js +16 -0
- package/dist/i18n/locales/pt.js.map +1 -1
- package/dist/i18n/locales/ro.d.ts +16 -0
- package/dist/i18n/locales/ro.d.ts.map +1 -1
- package/dist/i18n/locales/ro.js +16 -0
- package/dist/i18n/locales/ro.js.map +1 -1
- package/dist/i18n/locales/ru.d.ts +16 -0
- package/dist/i18n/locales/ru.d.ts.map +1 -1
- package/dist/i18n/locales/ru.js +16 -0
- package/dist/i18n/locales/ru.js.map +1 -1
- package/dist/i18n/locales/sq.d.ts +16 -0
- package/dist/i18n/locales/sq.d.ts.map +1 -1
- package/dist/i18n/locales/sq.js +16 -0
- package/dist/i18n/locales/sq.js.map +1 -1
- package/dist/i18n/locales/sv.d.ts +16 -0
- package/dist/i18n/locales/sv.d.ts.map +1 -1
- package/dist/i18n/locales/sv.js +16 -0
- package/dist/i18n/locales/sv.js.map +1 -1
- package/dist/i18n/locales/tr.d.ts +16 -0
- package/dist/i18n/locales/tr.d.ts.map +1 -1
- package/dist/i18n/locales/tr.js +16 -0
- package/dist/i18n/locales/tr.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +16 -0
- package/dist/i18n/locales/zh.d.ts.map +1 -1
- package/dist/i18n/locales/zh.js +16 -0
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/index.js +18 -18
- package/dist/locales/ar.json +16 -0
- package/dist/locales/de.json +16 -0
- package/dist/locales/el.json +16 -0
- package/dist/locales/en.json +16 -0
- package/dist/locales/es.json +16 -0
- package/dist/locales/fr.json +16 -0
- package/dist/locales/hi.json +16 -0
- package/dist/locales/it.json +16 -0
- package/dist/locales/ja.json +16 -0
- package/dist/locales/nl.json +16 -0
- package/dist/locales/pl.json +16 -0
- package/dist/locales/pt.json +16 -0
- package/dist/locales/ro.json +16 -0
- package/dist/locales/ru.json +16 -0
- package/dist/locales/sq.json +16 -0
- package/dist/locales/sv.json +16 -0
- package/dist/locales/tr.json +16 -0
- package/dist/locales/zh.json +16 -0
- package/dist/patterns/marketplace-app-shell/index.js +1 -1
- package/dist/patterns/patient-shell/index.js +1 -1
- package/dist/tokens.css +1 -1
- package/package.json +1 -1
- package/dist/_chunks/ai-tools-rail-JnlTqfS4.js.map +0 -1
- package/dist/_chunks/editable-currency-cell-renderer-CFqQc56y.js.map +0 -1
- package/dist/_chunks/insert-result-DIAG3ltJ.js.map +0 -1
- package/dist/_chunks/rich-text-editor-u36FqSWz.js +0 -1190
- package/dist/_chunks/rich-text-editor-u36FqSWz.js.map +0 -1
- package/dist/_chunks/sidebar-DPEHzxLZ.js.map +0 -1
- package/dist/_chunks/type-DZ5OMRA2.js.map +0 -1
- package/dist/_chunks/workflow-map-BX3JY1Bu.js.map +0 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { jsxs as z, jsx as p } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef as Q, useRef as v, useState as ee, useMemo as A, useCallback as B, useEffect as se, useImperativeHandle as ae } from "react";
|
|
3
|
-
import { c as
|
|
3
|
+
import { c as g } from "./index-D2ZczOXr.js";
|
|
4
4
|
import { useTranslation as ie } from "react-i18next";
|
|
5
5
|
import { I as ne } from "./icon-button-LqoiKcUN.js";
|
|
6
6
|
import { S as te } from "./scroll-area-BWqCkEGz.js";
|
|
7
7
|
import { a as oe } from "./tooltip-DvmfrNvB.js";
|
|
8
8
|
import { u as re } from "./use-controllable-state-BiY4xTzM.js";
|
|
9
|
-
import { u as
|
|
9
|
+
import { u as de } from "./use-edge-resize-ZnGG7gyO.js";
|
|
10
10
|
import { u as H } from "./use-isomorphic-layout-effect-BGfaCOP1.js";
|
|
11
|
-
import { u as
|
|
11
|
+
import { u as le } from "./registry-nPAVE19X.js";
|
|
12
12
|
const ce = {
|
|
13
13
|
id: "ai-tools-rail",
|
|
14
14
|
capabilities: ["open", "close", "select_single"],
|
|
@@ -64,13 +64,15 @@ const ce = {
|
|
|
64
64
|
description: "Stable tool id emitted on each strip IconButton. The active icon also carries data-active set to its id."
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
-
}, ue = "ds:[inline-size:3rem]", fe = "ds:[inline-size:var(--ai-tools-rail-panel-inline-size,22rem)]", F = "--ai-tools-rail-panel-inline-size", pe = 288, ve = 640,
|
|
67
|
+
}, ue = "ds:[inline-size:3rem]", fe = "ds:[inline-size:var(--ai-tools-rail-panel-inline-size,22rem)]", F = "--ai-tools-rail-panel-inline-size", pe = 288, ve = 640, be = 352, ge = g(
|
|
68
68
|
["ds:flex ds:shrink-0 ds:h-full", "ds:bg-[var(--background)]"].join(" "),
|
|
69
69
|
{
|
|
70
70
|
variants: {
|
|
71
71
|
side: {
|
|
72
72
|
// Strip docks inline-end; the panel mounts inline-start of it
|
|
73
|
-
// (flex-row, panel rendered first). Shadow faces the main area
|
|
73
|
+
// (flex-row, panel rendered first). Shadow faces the main area —
|
|
74
|
+
// no border against main (the rail stays line-free; the only seam
|
|
75
|
+
// is the strip↔panel divider on the open panel, see render).
|
|
74
76
|
end: "ds:flex-row ds:shadow-[var(--shadow-chrome-start)]",
|
|
75
77
|
// Strip docks inline-start; the panel mounts inline-end of it.
|
|
76
78
|
start: "ds:flex-row-reverse ds:shadow-[var(--shadow-chrome-end)]"
|
|
@@ -78,14 +80,14 @@ const ce = {
|
|
|
78
80
|
},
|
|
79
81
|
defaultVariants: { side: "end" }
|
|
80
82
|
}
|
|
81
|
-
),
|
|
83
|
+
), me = g(
|
|
82
84
|
[
|
|
83
85
|
"ds:flex ds:shrink-0 ds:flex-col ds:items-center",
|
|
84
86
|
"ds:pt-[var(--spacing-md)] ds:pb-[var(--spacing-md)]",
|
|
85
87
|
"ds:gap-[var(--spacing-xs)]",
|
|
86
88
|
ue
|
|
87
89
|
].join(" ")
|
|
88
|
-
), he =
|
|
90
|
+
), he = g(
|
|
89
91
|
[
|
|
90
92
|
"ds:relative ds:flex ds:min-h-0 ds:flex-col ds:h-full",
|
|
91
93
|
"ds:bg-[var(--background)]",
|
|
@@ -94,13 +96,12 @@ const ce = {
|
|
|
94
96
|
// consumer adds a transition on the var, suppress it mid-drag.
|
|
95
97
|
"ds:data-[resizing]:transition-none"
|
|
96
98
|
].join(" ")
|
|
97
|
-
), ye =
|
|
99
|
+
), ye = g(
|
|
98
100
|
[
|
|
99
101
|
"ds:absolute ds:[inset-block:0] ds:z-[1] ds:select-none ds:touch-none",
|
|
100
102
|
"ds:[inline-size:var(--resizable-handle-size)] ds:cursor-col-resize",
|
|
101
|
-
"ds:bg-[var(--border)]",
|
|
102
103
|
"ds:transition-colors ds:duration-[var(--animation-duration)] ds:motion-reduce:transition-none",
|
|
103
|
-
"ds:hover:bg-[var(--accent)] ds:data-[resizing]:bg-[var(--accent)]",
|
|
104
|
+
"ds:hover:bg-[var(--accent)] ds:focus-visible:bg-[var(--accent)] ds:data-[resizing]:bg-[var(--accent)]",
|
|
104
105
|
"ds:focus-visible:outline-[length:var(--focus-ring-width)] ds:focus-visible:outline-solid",
|
|
105
106
|
"ds:focus-visible:outline-[color:var(--ring)] ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)]",
|
|
106
107
|
"ds:forced-colors:focus-visible:outline-[CanvasText]",
|
|
@@ -119,7 +120,7 @@ const ce = {
|
|
|
119
120
|
},
|
|
120
121
|
defaultVariants: { edge: "inline-start" }
|
|
121
122
|
}
|
|
122
|
-
), xe =
|
|
123
|
+
), xe = g(
|
|
123
124
|
[
|
|
124
125
|
"ds:absolute ds:-top-[var(--spacing-xs)] ds:-end-[var(--spacing-xs)]",
|
|
125
126
|
"ds:inline-flex ds:items-center ds:justify-center",
|
|
@@ -143,42 +144,42 @@ const Te = Q(
|
|
|
143
144
|
activeToolId: M,
|
|
144
145
|
defaultActiveToolId: U = null,
|
|
145
146
|
onActiveToolChange: W,
|
|
146
|
-
side:
|
|
147
|
+
side: m = "end",
|
|
147
148
|
railLabel: Z,
|
|
148
|
-
resizablePanel:
|
|
149
|
+
resizablePanel: h = !1,
|
|
149
150
|
className: O
|
|
150
151
|
}, q) => {
|
|
151
152
|
var P;
|
|
152
|
-
const { t:
|
|
153
|
+
const { t: b } = ie(), [X, d] = re({
|
|
153
154
|
value: M,
|
|
154
155
|
defaultValue: U,
|
|
155
156
|
onChange: W
|
|
156
|
-
}), c = X ?? null, R = v([]),
|
|
157
|
+
}), c = X ?? null, R = v([]), y = v(null), [T, k] = ee(0), f = v(!1), w = v(!1), u = A(
|
|
157
158
|
() => s.findIndex((e) => e.id === c),
|
|
158
159
|
[s, c]
|
|
159
|
-
),
|
|
160
|
+
), l = u >= 0 ? s[u] : null, Y = A(
|
|
160
161
|
() => s.findIndex((e) => !e.disabled),
|
|
161
162
|
[s]
|
|
162
|
-
),
|
|
163
|
+
), x = u >= 0 ? u : T >= 0 && !((P = s[T]) != null && P.disabled) ? T : Y, $ = B(
|
|
163
164
|
(e, a) => {
|
|
164
165
|
const i = s.find((r) => r.id === e);
|
|
165
|
-
!i || i.disabled || (f.current = a,
|
|
166
|
+
!i || i.disabled || (f.current = a, d(c === e ? null : e));
|
|
166
167
|
},
|
|
167
|
-
[s, c,
|
|
168
|
+
[s, c, d]
|
|
168
169
|
), G = B(
|
|
169
170
|
(e) => {
|
|
170
171
|
var D, K, j;
|
|
171
172
|
const a = s.length;
|
|
172
173
|
if (a === 0) return;
|
|
173
174
|
let i = null;
|
|
174
|
-
if (e.key === "ArrowDown" ? i = (
|
|
175
|
+
if (e.key === "ArrowDown" ? i = (x + 1) % a : e.key === "ArrowUp" ? i = (x - 1 + a) % a : e.key === "Home" ? i = 0 : e.key === "End" && (i = a - 1), i === null) return;
|
|
175
176
|
const r = e.key === "ArrowUp" || e.key === "End";
|
|
176
177
|
let t = i, o = 0;
|
|
177
178
|
for (; o < a && ((D = s[t]) != null && D.disabled); )
|
|
178
179
|
t = r ? (t - 1 + a) % a : (t + 1) % a, o += 1;
|
|
179
180
|
(K = s[t]) != null && K.disabled || (e.preventDefault(), k(t), (j = R.current[t]) == null || j.focus());
|
|
180
181
|
},
|
|
181
|
-
[s,
|
|
182
|
+
[s, x]
|
|
182
183
|
), E = v(null);
|
|
183
184
|
se(() => {
|
|
184
185
|
const e = E.current;
|
|
@@ -187,52 +188,52 @@ const Te = Q(
|
|
|
187
188
|
if (i.key !== "Escape" || u < 0) return;
|
|
188
189
|
i.preventDefault();
|
|
189
190
|
const r = u;
|
|
190
|
-
|
|
191
|
+
d(null), k(r), requestAnimationFrame(() => {
|
|
191
192
|
var t;
|
|
192
193
|
(t = R.current[r]) == null || t.focus();
|
|
193
194
|
});
|
|
194
195
|
};
|
|
195
196
|
return e.addEventListener("keydown", a), () => e.removeEventListener("keydown", a);
|
|
196
|
-
}, [u,
|
|
197
|
+
}, [u, d]), H(() => {
|
|
197
198
|
var e;
|
|
198
|
-
|
|
199
|
-
}, [
|
|
200
|
-
const _ =
|
|
199
|
+
l && f.current && ((e = y.current) == null || e.focus()), f.current = !1;
|
|
200
|
+
}, [l]);
|
|
201
|
+
const _ = m === "end" ? "inline-start" : "inline-end", { separatorProps: N, isResizing: S, isResized: C } = de({
|
|
201
202
|
edge: _,
|
|
202
|
-
targetRef:
|
|
203
|
+
targetRef: y,
|
|
203
204
|
cssProperty: F,
|
|
204
205
|
minSize: pe,
|
|
205
206
|
maxSize: ve,
|
|
206
|
-
defaultSize:
|
|
207
|
-
disabled: !
|
|
207
|
+
defaultSize: be,
|
|
208
|
+
disabled: !h
|
|
208
209
|
}), L = N["aria-valuenow"];
|
|
209
210
|
H(() => {
|
|
210
211
|
var e;
|
|
211
|
-
!
|
|
212
|
+
!h || !C || !l || (e = y.current) == null || e.style.setProperty(
|
|
212
213
|
F,
|
|
213
214
|
`${L}px`
|
|
214
215
|
);
|
|
215
|
-
}, [
|
|
216
|
+
}, [l, h, C, L]);
|
|
216
217
|
const V = v(c);
|
|
217
218
|
V.current = c;
|
|
218
219
|
const I = A(
|
|
219
220
|
() => ({
|
|
220
221
|
getActiveToolId: () => V.current,
|
|
221
222
|
setActiveTool: (e) => {
|
|
222
|
-
f.current = !1,
|
|
223
|
+
f.current = !1, d(e);
|
|
223
224
|
},
|
|
224
225
|
open: (e) => {
|
|
225
226
|
const a = s.find((i) => i.id === e);
|
|
226
|
-
!a || a.disabled || (f.current = !1,
|
|
227
|
+
!a || a.disabled || (f.current = !1, d(e));
|
|
227
228
|
},
|
|
228
229
|
close: () => {
|
|
229
|
-
f.current = !1,
|
|
230
|
+
f.current = !1, d(null);
|
|
230
231
|
}
|
|
231
232
|
}),
|
|
232
|
-
[s,
|
|
233
|
+
[s, d]
|
|
233
234
|
);
|
|
234
|
-
ae(q, () => I, [I]),
|
|
235
|
-
const J =
|
|
235
|
+
ae(q, () => I, [I]), le(ce, I, n);
|
|
236
|
+
const J = b("aiToolsRail.badgeCountOverflow", {
|
|
236
237
|
defaultValue: "99+"
|
|
237
238
|
});
|
|
238
239
|
return /* @__PURE__ */ z(
|
|
@@ -241,31 +242,39 @@ const Te = Q(
|
|
|
241
242
|
ref: E,
|
|
242
243
|
"data-component": "ai-tools-rail",
|
|
243
244
|
"data-component-id": n,
|
|
244
|
-
"data-side":
|
|
245
|
-
className: [
|
|
245
|
+
"data-side": m,
|
|
246
|
+
className: [ge({ side: m }), O].filter(Boolean).join(" "),
|
|
246
247
|
children: [
|
|
247
|
-
|
|
248
|
+
l ? /* @__PURE__ */ z(
|
|
248
249
|
"aside",
|
|
249
250
|
{
|
|
250
|
-
ref:
|
|
251
|
+
ref: y,
|
|
251
252
|
tabIndex: -1,
|
|
252
|
-
"aria-label":
|
|
253
|
-
tool:
|
|
253
|
+
"aria-label": b("aiToolsRail.panelLabel", {
|
|
254
|
+
tool: l.label,
|
|
254
255
|
defaultValue: "{{tool}} panel"
|
|
255
256
|
}),
|
|
256
257
|
"data-ai-tools-rail-state": "expanded",
|
|
257
|
-
"data-active-tool":
|
|
258
|
+
"data-active-tool": l.id,
|
|
258
259
|
"data-resizing": S ? "" : void 0,
|
|
259
|
-
className: [
|
|
260
|
-
|
|
261
|
-
|
|
260
|
+
className: [
|
|
261
|
+
he(),
|
|
262
|
+
// 1px divider between the panel and the icon strip (the tab
|
|
263
|
+
// row). Lives on the panel's STRIP-facing edge, so it only
|
|
264
|
+
// renders while the panel is open (the aside is unmounted when
|
|
265
|
+
// collapsed) and never doubles as a rail↔main line. `border-e`
|
|
266
|
+
// / `border-s` are the real logical border-width utilities —
|
|
267
|
+
// bare `border-inline-*` emits no CSS in this build.
|
|
268
|
+
m === "end" ? "ds:border-e ds:border-[var(--border)]" : "ds:border-s ds:border-[var(--border)]",
|
|
269
|
+
"ds:focus-visible:outline-none"
|
|
270
|
+
].join(" "),
|
|
262
271
|
children: [
|
|
263
|
-
/* @__PURE__ */ p(te, { orientation: "vertical", className: "ds:flex-1 ds:min-h-0", children:
|
|
264
|
-
|
|
272
|
+
/* @__PURE__ */ p(te, { orientation: "vertical", className: "ds:flex-1 ds:min-h-0", children: l.panel }),
|
|
273
|
+
h ? /* @__PURE__ */ p(
|
|
265
274
|
"div",
|
|
266
275
|
{
|
|
267
276
|
...N,
|
|
268
|
-
"aria-label":
|
|
277
|
+
"aria-label": b("aiToolsRail.resizePanel", {
|
|
269
278
|
defaultValue: "Resize tool panel"
|
|
270
279
|
}),
|
|
271
280
|
"data-resizing": S ? "" : void 0,
|
|
@@ -280,11 +289,11 @@ const Te = Q(
|
|
|
280
289
|
{
|
|
281
290
|
role: "toolbar",
|
|
282
291
|
"aria-orientation": "vertical",
|
|
283
|
-
"aria-label": Z ??
|
|
284
|
-
className:
|
|
292
|
+
"aria-label": Z ?? b("aiToolsRail.railLabel", { defaultValue: "AI tools" }),
|
|
293
|
+
className: me(),
|
|
285
294
|
onKeyDown: G,
|
|
286
295
|
children: s.map((e, a) => {
|
|
287
|
-
const i = e.id === c, r = typeof e.badgeCount == "number" && e.badgeCount > 0, t = r ?
|
|
296
|
+
const i = e.id === c, r = typeof e.badgeCount == "number" && e.badgeCount > 0, t = r ? b("aiToolsRail.toolWithCount", {
|
|
288
297
|
tool: e.label,
|
|
289
298
|
count: e.badgeCount,
|
|
290
299
|
defaultValue: "{{tool}}, {{count}} updates"
|
|
@@ -306,7 +315,7 @@ const Te = Q(
|
|
|
306
315
|
"aria-expanded": i,
|
|
307
316
|
"data-active": i ? e.id : void 0,
|
|
308
317
|
"data-tool-id": e.id,
|
|
309
|
-
tabIndex: a ===
|
|
318
|
+
tabIndex: a === x ? 0 : -1,
|
|
310
319
|
onFocus: () => {
|
|
311
320
|
e.disabled || k(a);
|
|
312
321
|
},
|
|
@@ -337,4 +346,4 @@ export {
|
|
|
337
346
|
Te as A,
|
|
338
347
|
ce as a
|
|
339
348
|
};
|
|
340
|
-
//# sourceMappingURL=ai-tools-rail-
|
|
349
|
+
//# sourceMappingURL=ai-tools-rail-DAfaW8eA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-tools-rail-DAfaW8eA.js","sources":["../../src/components/ai-tools-rail/ai-tools-rail.agent.ts","../../src/components/ai-tools-rail/ai-tools-rail.tsx"],"sourcesContent":["/* -------------------------------------------------------------------- */\n/* Agent adapter — AiToolsRail. */\n/* */\n/* Rail-level surface: open / close a tool's panel and read which tool */\n/* is active. State is strictly the active tool id — never any tool's */\n/* panel CONTENT (which may carry PHI). Tool ids + badge counts are */\n/* supplied by the host via props, not derived from panel bodies. */\n/* -------------------------------------------------------------------- */\n\nimport type { AgentAdapter } from '../../agent/types';\nimport type { AiToolsRailHandle } from './ai-tools-rail';\n\nexport const aiToolsRailAgent: AgentAdapter<AiToolsRailHandle> = {\n id: 'ai-tools-rail',\n capabilities: ['open', 'close', 'select_single'],\n state: {\n activeToolId: {\n type: 'string | null',\n descriptionKey: 'ui.aiToolsRail.agent.state.activeToolId',\n description:\n 'Id of the tool whose panel is currently open, or null when the rail is collapsed to its strip. No panel content is ever exposed.',\n read: (handle) => handle.getActiveToolId(),\n },\n },\n actions: {\n open: {\n safety: 'read',\n argsType: '{ id: string }',\n descriptionKey: 'ui.aiToolsRail.agent.actions.open',\n description: \"Open the given tool's panel.\",\n invoke: (handle, args: { id: string }) => {\n handle.open(args.id);\n },\n },\n close: {\n safety: 'read',\n descriptionKey: 'ui.aiToolsRail.agent.actions.close',\n description: 'Collapse the rail to its strip (no active tool).',\n invoke: (handle) => {\n handle.close();\n },\n },\n select_tool: {\n safety: 'read',\n argsType: '{ id: string | null }',\n descriptionKey: 'ui.aiToolsRail.agent.actions.selectTool',\n description:\n 'Set the active tool by id (opens its panel), or pass null to collapse.',\n invoke: (handle, args: { id: string | null }) => {\n handle.setActiveTool(args.id);\n },\n },\n },\n domHooks: {\n root: {\n attr: 'data-component',\n value: 'ai-tools-rail',\n description: 'Marks the AiToolsRail wrapper.',\n },\n instanceId: {\n attr: 'data-component-id',\n sourceProp: 'id',\n description:\n 'Sourced from the id prop. Required to address a specific rail from the agent.',\n },\n item: {\n attr: 'data-tool-id',\n description:\n 'Stable tool id emitted on each strip IconButton. The active icon also carries data-active set to its id.',\n },\n },\n};\n","import {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n type KeyboardEvent as ReactKeyboardEvent,\n type ReactNode,\n} from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport { IconButton } from '../button/icon-button';\nimport { ScrollArea } from '../scroll-area/scroll-area';\nimport { TooltipProvider } from '../tooltip/tooltip';\nimport { useControllableState } from '../../hooks/use-controllable-state';\nimport { useEdgeResize } from '../../hooks/use-edge-resize';\nimport { useIsomorphicLayoutEffect } from '../../hooks/use-isomorphic-layout-effect';\nimport { useAgentRegistration } from '../../agent/registry';\nimport { aiToolsRailAgent } from './ai-tools-rail.agent';\n\n/* ------------------------------------------------------------------ */\n/* Tokenised widths */\n/* */\n/* Fixed + tokenised so the rail stays constraint-compliant — no */\n/* prop-driven inline styles. Bare `[inline-size:…]` is NOT a */\n/* recognised utility under the `ds:` prefix; it must carry the */\n/* prefix or Tailwind emits no rule. Consumers needing a different */\n/* size wrap the rail and override via CSS. */\n/* ------------------------------------------------------------------ */\nconst RAIL_WIDTH = 'ds:[inline-size:3rem]';\n// Panel width rides a consumer-overridable custom property (set\n// `--ai-tools-rail-panel-inline-size` on the rail or a wrapper) so\n// content-heavy tools (e.g. an inbox feed) can widen their panel\n// without inline styles. Defaults to the 22rem tray-family width.\nconst PANEL_WIDTH =\n 'ds:[inline-size:var(--ai-tools-rail-panel-inline-size,22rem)]';\n// The custom property PANEL_WIDTH reads. The resize hook writes the\n// committed px width here (on the panel element, so it beats consumer\n// class overrides on the root) and removes it on reset.\nconst PANEL_SIZE_PROPERTY = '--ai-tools-rail-panel-inline-size';\n// Resize clamp + nominal default, px (352px = the 22rem token default).\nconst PANEL_MIN_SIZE = 288;\nconst PANEL_MAX_SIZE = 640;\nconst PANEL_DEFAULT_SIZE = 352;\n\n/* ------------------------------------------------------------------ */\n/* Public types */\n/* ------------------------------------------------------------------ */\n\nexport interface AiToolsRailTool {\n /** Stable tool id — used in `data-active`, the imperative handle, and\n * to label the open panel region. */\n id: string;\n /** Accessible name for the strip icon + tooltip, and the open panel's\n * region label. Never rendered as visible body text. */\n label: string;\n /** lucide-react glyph (rendered `aria-hidden` inside the IconButton). */\n icon: ReactNode;\n /** Panel body for this tool. Rendered inline-start of the strip when\n * `side='end'` (mirrored for `'start'`). The consumer composes it. */\n panel: ReactNode;\n /** Optional unseen / notification count shown on the strip icon. */\n badgeCount?: number;\n /** When true the icon is non-interactive and skipped by roving focus. */\n disabled?: boolean;\n}\n\n/**\n * Curated imperative handle for AiToolsRail. Exposed as the forwardRef\n * target so a future agent / MCP UI bridge can drive the rail without\n * touching the DOM. See `ai-tools-rail.agent.ts`.\n *\n * State surfaced here is strictly identifiers + counts — never any\n * tool's panel content (which may carry PHI).\n */\nexport interface AiToolsRailHandle {\n /** Currently-active tool id, or null when collapsed to strip-only. */\n getActiveToolId: () => string | null;\n /** Activate a tool by id (opens its panel), or null to collapse. */\n setActiveTool: (id: string | null) => void;\n /** Activate a tool by id (opens its panel). */\n open: (id: string) => void;\n /** Collapse to strip-only (no active tool). */\n close: () => void;\n}\n\n/* ------------------------------------------------------------------ */\n/* CVA — `side` drives the dock edge + which shadow faces main content */\n/* ------------------------------------------------------------------ */\n\nconst railVariants = cva(\n ['ds:flex ds:shrink-0 ds:h-full', 'ds:bg-[var(--background)]'].join(' '),\n {\n variants: {\n side: {\n // Strip docks inline-end; the panel mounts inline-start of it\n // (flex-row, panel rendered first). Shadow faces the main area —\n // no border against main (the rail stays line-free; the only seam\n // is the strip↔panel divider on the open panel, see render).\n end: 'ds:flex-row ds:shadow-[var(--shadow-chrome-start)]',\n // Strip docks inline-start; the panel mounts inline-end of it.\n start: 'ds:flex-row-reverse ds:shadow-[var(--shadow-chrome-end)]',\n },\n },\n defaultVariants: { side: 'end' },\n },\n);\n\nconst stripVariants = cva(\n [\n 'ds:flex ds:shrink-0 ds:flex-col ds:items-center',\n 'ds:pt-[var(--spacing-md)] ds:pb-[var(--spacing-md)]',\n 'ds:gap-[var(--spacing-xs)]',\n RAIL_WIDTH,\n ].join(' '),\n);\n\nconst panelVariants = cva(\n [\n 'ds:relative ds:flex ds:min-h-0 ds:flex-col ds:h-full',\n 'ds:bg-[var(--background)]',\n PANEL_WIDTH,\n // Defensive: the panel doesn't animate its width today, but if a\n // consumer adds a transition on the var, suppress it mid-drag.\n 'ds:data-[resizing]:transition-none',\n ].join(' '),\n);\n\n// Resize handle on the panel's inner edge — mirrors Resizable's Handle\n// (::before hit area to --min-target-size, hover/focus/drag accent, token\n// focus ring) but absolutely positioned over the panel edge. Like the\n// Sidebar's handle it stays TRANSPARENT at rest: the rail root's\n// `shadow-chrome-*` (which faces the main content) carries the resting seam,\n// exactly as the start-docked Sidebar leans on its own shadow — so the rail\n// never shows the constant coloured bar it used to. The strip only tints\n// accent on hover / focus / drag.\nconst panelResizeHandleVariants = cva(\n [\n 'ds:absolute ds:[inset-block:0] ds:z-[1] ds:select-none ds:touch-none',\n 'ds:[inline-size:var(--resizable-handle-size)] ds:cursor-col-resize',\n 'ds:transition-colors ds:duration-[var(--animation-duration)] ds:motion-reduce:transition-none',\n 'ds:hover:bg-[var(--accent)] ds:focus-visible:bg-[var(--accent)] ds:data-[resizing]:bg-[var(--accent)]',\n 'ds:focus-visible:outline-[length:var(--focus-ring-width)] ds:focus-visible:outline-solid',\n 'ds:focus-visible:outline-[color:var(--ring)] ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)]',\n 'ds:forced-colors:focus-visible:outline-[CanvasText]',\n \"ds:before:absolute ds:before:content-['']\",\n 'ds:before:[block-size:100%]',\n 'ds:before:[inline-size:var(--min-target-size)]',\n 'ds:before:[inset-block:0]',\n 'ds:before:[inset-inline:calc(0px_-_(var(--min-target-size)_-_var(--resizable-handle-size))_/_2)]',\n ],\n {\n variants: {\n edge: {\n 'inline-start': 'ds:start-0',\n 'inline-end': 'ds:end-0',\n },\n },\n defaultVariants: { edge: 'inline-start' },\n },\n);\n\n// Hand-rolled overlay count chip — mirrors the AliaSidebar / MessageTray\n// badge markup. A <Badge> would re-introduce its own landmark/role; the\n// strip icon already carries the count in its accessible name, so this\n// span is purely decorative (`aria-hidden`).\nconst badgeVariants = cva(\n [\n 'ds:absolute ds:-top-[var(--spacing-xs)] ds:-end-[var(--spacing-xs)]',\n 'ds:inline-flex ds:items-center ds:justify-center',\n 'ds:min-w-[calc(var(--spacing-md)+var(--spacing-xs))]',\n 'ds:h-[calc(var(--spacing-md)+var(--spacing-xs))]',\n 'ds:ps-[calc(var(--spacing-xs)/1.5)] ds:pe-[calc(var(--spacing-xs)/1.5)]',\n 'ds:rounded-[var(--radius-full)]',\n 'ds:bg-[color:var(--destructive)] ds:text-[color:var(--destructive-foreground)]',\n 'ds:text-[length:var(--font-size-xs)] ds:font-semibold ds:leading-none',\n 'ds:pointer-events-none ds:select-none',\n 'ds:forced-colors:outline ds:forced-colors:outline-1 ds:forced-colors:outline-[CanvasText]',\n ].join(' '),\n);\n\n/* ------------------------------------------------------------------ */\n/* Props */\n/* ------------------------------------------------------------------ */\n\nexport interface AiToolsRailProps extends VariantProps<typeof railVariants> {\n /** Stable instance id, surfaced on the root as `data-component-id`. */\n id?: string;\n /** The tools shown as icons on the strip. */\n tools: AiToolsRailTool[];\n /** Controlled active tool id (null = collapsed / strip-only). */\n activeToolId?: string | null;\n /** Uncontrolled initial active tool id (default null). */\n defaultActiveToolId?: string | null;\n /** Fires whenever the active tool changes (incl. → null on collapse). */\n onActiveToolChange?: (id: string | null) => void;\n /** Which edge the rail docks to. Default `'end'` (inline-end). */\n side?: 'start' | 'end';\n /** Accessible label for the toolbar strip. */\n railLabel?: string;\n /**\n * Opt-in resize affordance for the open tool panel. Renders a\n * `role=\"separator\"` drag handle on the panel's inner edge (the edge\n * facing the main content). The committed width is written to\n * `--ai-tools-rail-panel-inline-size` on the panel element (288–640px);\n * Enter / double-click resets to the token-driven default. Default `false`.\n */\n resizablePanel?: boolean;\n className?: string;\n}\n\n/* ------------------------------------------------------------------ */\n/* Helpers */\n/* ------------------------------------------------------------------ */\n\nfunction formatBadgeCount(count: number, overflowLabel: string): string {\n return count > 99 ? overflowLabel : String(count);\n}\n\n/* ------------------------------------------------------------------ */\n/* Root */\n/* ------------------------------------------------------------------ */\n\nexport const AiToolsRail = forwardRef<AiToolsRailHandle, AiToolsRailProps>(\n (\n {\n id,\n tools,\n activeToolId,\n defaultActiveToolId = null,\n onActiveToolChange,\n side = 'end',\n railLabel,\n resizablePanel = false,\n className,\n },\n ref,\n ) => {\n const { t } = useTranslation();\n\n // Controlled / uncontrolled bridge — the same hook MessageTray uses.\n const [activeRaw, setActiveRaw] = useControllableState<string | null>({\n value: activeToolId,\n defaultValue: defaultActiveToolId,\n onChange: onActiveToolChange,\n });\n const activeId = activeRaw ?? null;\n\n // Roving-tabindex bookkeeping for the vertical toolbar strip.\n const buttonsRef = useRef<Array<HTMLButtonElement | null>>([]);\n const panelRef = useRef<HTMLElement | null>(null);\n const [focusIndex, setFocusIndex] = useState(0);\n // Distinguishes keyboard activation (auto-focus into the panel) from\n // pointer activation (focus stays on the icon — less jarring).\n const activatedByKeyboardRef = useRef(false);\n // Set on Enter/Space keydown, read by the click handler that follows,\n // cleared on keyup — so a subsequent mouse click never inherits it.\n const keyboardActivateRef = useRef(false);\n\n const activeIndex = useMemo(\n () => tools.findIndex((tl) => tl.id === activeId),\n [tools, activeId],\n );\n const activeTool = activeIndex >= 0 ? tools[activeIndex] : null;\n\n // First non-disabled index — the resting roving stop when none active.\n const firstEnabledIndex = useMemo(\n () => tools.findIndex((tl) => !tl.disabled),\n [tools],\n );\n\n // The roving tab stop: the active icon when a tool is open, else the\n // index the user last focused, else the first enabled icon.\n const rovingIndex =\n activeIndex >= 0\n ? activeIndex\n : focusIndex >= 0 && !tools[focusIndex]?.disabled\n ? focusIndex\n : firstEnabledIndex;\n\n const selectTool = useCallback(\n (toolId: string, fromKeyboard: boolean) => {\n const tool = tools.find((tl) => tl.id === toolId);\n if (!tool || tool.disabled) return;\n activatedByKeyboardRef.current = fromKeyboard;\n // Clicking / activating the active tool collapses to strip-only.\n setActiveRaw(activeId === toolId ? null : toolId);\n },\n [tools, activeId, setActiveRaw],\n );\n\n /* ---- Roving arrow-key navigation (vertical toolbar) ----------- */\n const handleStripKeyDown = useCallback(\n (event: ReactKeyboardEvent<HTMLDivElement>) => {\n const count = tools.length;\n if (count === 0) return;\n\n let next: number | null = null;\n if (event.key === 'ArrowDown') next = (rovingIndex + 1) % count;\n else if (event.key === 'ArrowUp')\n next = (rovingIndex - 1 + count) % count;\n else if (event.key === 'Home') next = 0;\n else if (event.key === 'End') next = count - 1;\n if (next === null) return;\n\n // Skip disabled icons in the travel direction.\n const goingBack = event.key === 'ArrowUp' || event.key === 'End';\n let candidate = next;\n let attempts = 0;\n while (attempts < count && tools[candidate]?.disabled) {\n candidate = goingBack\n ? (candidate - 1 + count) % count\n : (candidate + 1) % count;\n attempts += 1;\n }\n if (tools[candidate]?.disabled) return;\n\n event.preventDefault();\n setFocusIndex(candidate);\n buttonsRef.current[candidate]?.focus();\n },\n [tools, rovingIndex],\n );\n\n /* ---- Esc collapses + returns focus to the active tool's icon -- */\n // Attached imperatively via the root ref (not a JSX onKeyDown on the\n // static container) so the Escape shortcut works from anywhere inside the\n // open panel without an interaction handler on a non-interactive element.\n const rootRef = useRef<HTMLDivElement>(null);\n useEffect(() => {\n const node = rootRef.current;\n if (!node) return undefined;\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Escape') return;\n if (activeIndex < 0) return;\n event.preventDefault();\n const returnIndex = activeIndex;\n setActiveRaw(null);\n setFocusIndex(returnIndex);\n // Defer until after the panel unmounts so focus lands cleanly.\n requestAnimationFrame(() => {\n buttonsRef.current[returnIndex]?.focus();\n });\n };\n node.addEventListener('keydown', onKeyDown);\n return () => node.removeEventListener('keydown', onKeyDown);\n }, [activeIndex, setActiveRaw]);\n\n /* ---- Move focus into the panel when opened by keyboard -------- */\n useIsomorphicLayoutEffect(() => {\n if (activeTool && activatedByKeyboardRef.current) {\n panelRef.current?.focus();\n }\n activatedByKeyboardRef.current = false;\n }, [activeTool]);\n\n /* ---- Opt-in panel resize (inner edge) -------------------------- */\n // The handle sits on the edge facing the main content: the panel's\n // inline-start edge for an end-docked rail, inline-end for start.\n const panelEdge = side === 'end' ? 'inline-start' : 'inline-end';\n const { separatorProps, isResizing, isResized } = useEdgeResize({\n edge: panelEdge,\n targetRef: panelRef,\n cssProperty: PANEL_SIZE_PROPERTY,\n minSize: PANEL_MIN_SIZE,\n maxSize: PANEL_MAX_SIZE,\n defaultSize: PANEL_DEFAULT_SIZE,\n disabled: !resizablePanel,\n });\n\n // The aside unmounts on collapse, dropping the hook's inline override.\n // Re-assert the committed width when a panel (re)mounts so a reopened\n // panel keeps the user's chosen size.\n const committedPanelWidth = separatorProps['aria-valuenow'];\n useIsomorphicLayoutEffect(() => {\n if (!resizablePanel || !isResized || !activeTool) return;\n panelRef.current?.style.setProperty(\n PANEL_SIZE_PROPERTY,\n `${committedPanelWidth}px`,\n );\n }, [activeTool, resizablePanel, isResized, committedPanelWidth]);\n\n /* ---- Curated imperative handle for agent integration ---------- */\n const activeIdRef = useRef<string | null>(activeId);\n activeIdRef.current = activeId;\n\n const handle = useMemo<AiToolsRailHandle>(\n () => ({\n getActiveToolId: () => activeIdRef.current,\n setActiveTool: (toolId) => {\n activatedByKeyboardRef.current = false;\n setActiveRaw(toolId);\n },\n open: (toolId) => {\n const tool = tools.find((tl) => tl.id === toolId);\n if (!tool || tool.disabled) return;\n activatedByKeyboardRef.current = false;\n setActiveRaw(toolId);\n },\n close: () => {\n activatedByKeyboardRef.current = false;\n setActiveRaw(null);\n },\n }),\n [tools, setActiveRaw],\n );\n\n useImperativeHandle(ref, () => handle, [handle]);\n useAgentRegistration(aiToolsRailAgent, handle, id);\n\n const overflowLabel = t('aiToolsRail.badgeCountOverflow', {\n defaultValue: '99+',\n });\n\n return (\n <div\n ref={rootRef}\n data-component=\"ai-tools-rail\"\n data-component-id={id}\n data-side={side}\n className={[railVariants({ side }), className]\n .filter(Boolean)\n .join(' ')}\n >\n {/* The OPEN panel is the single complementary landmark, named by\n the active tool. Rendered only while a tool is active so two\n unnamed regions never coexist. */}\n {activeTool ? (\n <aside\n ref={panelRef}\n tabIndex={-1}\n aria-label={t('aiToolsRail.panelLabel', {\n tool: activeTool.label,\n defaultValue: '{{tool}} panel',\n })}\n data-ai-tools-rail-state=\"expanded\"\n data-active-tool={activeTool.id}\n data-resizing={isResizing ? '' : undefined}\n className={[\n panelVariants(),\n // 1px divider between the panel and the icon strip (the tab\n // row). Lives on the panel's STRIP-facing edge, so it only\n // renders while the panel is open (the aside is unmounted when\n // collapsed) and never doubles as a rail↔main line. `border-e`\n // / `border-s` are the real logical border-width utilities —\n // bare `border-inline-*` emits no CSS in this build.\n side === 'end'\n ? 'ds:border-e ds:border-[var(--border)]'\n : 'ds:border-s ds:border-[var(--border)]',\n 'ds:focus-visible:outline-none',\n ].join(' ')}\n >\n <ScrollArea orientation=\"vertical\" className=\"ds:flex-1 ds:min-h-0\">\n {activeTool.panel}\n </ScrollArea>\n {/* Inside the aside (so it belongs to the complementary\n landmark) and outside the toolbar strip — the strip's\n roving handlers never see its key events, and Escape\n still bubbles to the root to collapse, as everywhere\n else in the panel. No stopPropagation needed. */}\n {resizablePanel ? (\n <div\n {...separatorProps}\n aria-label={t('aiToolsRail.resizePanel', {\n defaultValue: 'Resize tool panel',\n })}\n data-resizing={isResizing ? '' : undefined}\n className={panelResizeHandleVariants({ edge: panelEdge })}\n />\n ) : null}\n </aside>\n ) : null}\n\n {/* The 3rem icon strip — a vertical toolbar with roving tabindex.\n Self-contained TooltipProvider so consumers needn't mount one;\n Radix providers compose, so a host-level provider still works. */}\n <TooltipProvider>\n <div\n role=\"toolbar\"\n aria-orientation=\"vertical\"\n aria-label={\n railLabel ??\n t('aiToolsRail.railLabel', { defaultValue: 'AI tools' })\n }\n className={stripVariants()}\n onKeyDown={handleStripKeyDown}\n >\n {tools.map((tool, index) => {\n const isActive = tool.id === activeId;\n const showBadge =\n typeof tool.badgeCount === 'number' && tool.badgeCount > 0;\n // Fold the count into the accessible name so SR users hear it\n // without relying on the decorative chip.\n const accessibleName = showBadge\n ? t('aiToolsRail.toolWithCount', {\n tool: tool.label,\n count: tool.badgeCount,\n defaultValue: '{{tool}}, {{count}} updates',\n })\n : tool.label;\n return (\n <div key={tool.id} className=\"ds:relative\">\n <IconButton\n ref={(el) => {\n buttonsRef.current[index] = el;\n }}\n icon={tool.icon}\n tooltip={tool.label}\n aria-label={accessibleName}\n intent=\"ghost\"\n size=\"sm\"\n disabled={tool.disabled}\n aria-pressed={isActive}\n aria-expanded={isActive}\n data-active={isActive ? tool.id : undefined}\n data-tool-id={tool.id}\n tabIndex={index === rovingIndex ? 0 : -1}\n onFocus={() => {\n if (!tool.disabled) setFocusIndex(index);\n }}\n onClick={() =>\n selectTool(tool.id, keyboardActivateRef.current)\n }\n onKeyDown={(event) => {\n // Enter/Space activate via the native button's click,\n // which fires AFTER this keydown. Flag the upcoming\n // click as keyboard-driven so focus moves into the\n // panel; reset on keyup so a later mouse click doesn't\n // inherit the flag.\n if (event.key === 'Enter' || event.key === ' ') {\n keyboardActivateRef.current = true;\n }\n }}\n onKeyUp={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n keyboardActivateRef.current = false;\n }\n }}\n />\n {showBadge ? (\n <span aria-hidden=\"true\" className={badgeVariants()}>\n {formatBadgeCount(\n tool.badgeCount as number,\n overflowLabel,\n )}\n </span>\n ) : null}\n </div>\n );\n })}\n </div>\n </TooltipProvider>\n </div>\n );\n },\n);\n\nAiToolsRail.displayName = 'AiToolsRail';\n"],"names":["aiToolsRailAgent","handle","args","RAIL_WIDTH","PANEL_WIDTH","PANEL_SIZE_PROPERTY","PANEL_MIN_SIZE","PANEL_MAX_SIZE","PANEL_DEFAULT_SIZE","railVariants","cva","stripVariants","panelVariants","panelResizeHandleVariants","badgeVariants","formatBadgeCount","count","overflowLabel","AiToolsRail","forwardRef","id","tools","activeToolId","defaultActiveToolId","onActiveToolChange","side","railLabel","resizablePanel","className","ref","t","useTranslation","activeRaw","setActiveRaw","useControllableState","activeId","buttonsRef","useRef","panelRef","focusIndex","setFocusIndex","useState","activatedByKeyboardRef","keyboardActivateRef","activeIndex","useMemo","tl","activeTool","firstEnabledIndex","rovingIndex","_a","selectTool","useCallback","toolId","fromKeyboard","tool","handleStripKeyDown","event","next","goingBack","candidate","attempts","_b","_c","rootRef","useEffect","node","onKeyDown","returnIndex","useIsomorphicLayoutEffect","panelEdge","separatorProps","isResizing","isResized","useEdgeResize","committedPanelWidth","activeIdRef","useImperativeHandle","useAgentRegistration","jsxs","jsx","ScrollArea","TooltipProvider","index","isActive","showBadge","accessibleName","IconButton","el"],"mappings":"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"}
|
|
@@ -6,7 +6,7 @@ import { F as I } from "./form-field-BOm9hK35.js";
|
|
|
6
6
|
import { S as P } from "./select-CEtRcon5.js";
|
|
7
7
|
import { C as m } from "./card-DPmk26CL.js";
|
|
8
8
|
import { B as T } from "./badge-zsf5i5bH.js";
|
|
9
|
-
import { I as A } from "./insert-result-
|
|
9
|
+
import { I as A } from "./insert-result-njqBthzT.js";
|
|
10
10
|
const b = [
|
|
11
11
|
"dilation",
|
|
12
12
|
"effacement",
|
|
@@ -182,4 +182,4 @@ export {
|
|
|
182
182
|
D as d,
|
|
183
183
|
M as e
|
|
184
184
|
};
|
|
185
|
-
//# sourceMappingURL=bishop-score-
|
|
185
|
+
//# sourceMappingURL=bishop-score-DPN4Ched.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bishop-score-x2fNqbuZ.js","sources":["../../src/components/bishop-score/bishop.ts","../../src/components/bishop-score/bishop-score.tsx"],"sourcesContent":["/* ------------------------------------------------------------------ */\n/* Bishop score — cervical favourability for induction of labour. */\n/* */\n/* Pure, framework-free, unit-testable. Five components scored 0–3 */\n/* (dilation, effacement, station) or 0–2 (consistency, position), */\n/* summed to a 0–13 total. Bands per ACOG: ≤6 unfavourable (cervical */\n/* ripening advised), 7 intermediate, ≥8 favourable. */\n/* */\n/* Bishop EH, Obstet Gynecol 1964; thresholds per ACOG Practice */\n/* Bulletin 107 (Induction of Labor). */\n/* ------------------------------------------------------------------ */\n\nexport type BishopComponent =\n | 'dilation'\n | 'effacement'\n | 'station'\n | 'consistency'\n | 'position';\n\n/** The component fields in the order they are scored / displayed. */\nexport const BISHOP_COMPONENTS: readonly BishopComponent[] = [\n 'dilation',\n 'effacement',\n 'station',\n 'consistency',\n 'position',\n] as const;\n\nexport interface BishopOption {\n /** Stable value key → i18n lookup `bishopScore.<component>.<value>`. */\n value: string;\n /** Points this option contributes. */\n score: number;\n}\n\n/**\n * Scored options per component. Dilation / effacement / station run 0–3;\n * consistency / position run 0–2 (the classic Bishop weighting).\n */\nexport const BISHOP_OPTIONS: Record<BishopComponent, BishopOption[]> = {\n dilation: [\n { value: 'closed', score: 0 },\n { value: '1-2', score: 1 },\n { value: '3-4', score: 2 },\n { value: '5+', score: 3 },\n ],\n effacement: [\n { value: '0-30', score: 0 },\n { value: '40-50', score: 1 },\n { value: '60-70', score: 2 },\n { value: '80+', score: 3 },\n ],\n station: [\n { value: '-3', score: 0 },\n { value: '-2', score: 1 },\n { value: '-1-0', score: 2 },\n { value: '+1+2', score: 3 },\n ],\n consistency: [\n { value: 'firm', score: 0 },\n { value: 'medium', score: 1 },\n { value: 'soft', score: 2 },\n ],\n position: [\n { value: 'posterior', score: 0 },\n { value: 'mid', score: 1 },\n { value: 'anterior', score: 2 },\n ],\n};\n\nexport type BishopBand = 'unfavourable' | 'intermediate' | 'favourable';\n\n/** Highest attainable total (3 + 3 + 3 + 2 + 2). */\nexport const BISHOP_MAX_SCORE = 13;\n\n/** Classify a total into a favourability band (ACOG thresholds). */\nexport function bishopBand(total: number): BishopBand {\n if (total <= 6) return 'unfavourable';\n if (total >= 8) return 'favourable';\n return 'intermediate';\n}\n\nexport type BishopSelection = Partial<Record<BishopComponent, string>>;\n\nexport interface BishopResult {\n total: number;\n band: BishopBand;\n}\n\n/** Look up the score for a component's selected option, or `null` if unknown. */\nfunction scoreFor(component: BishopComponent, value: string): number | null {\n return (\n BISHOP_OPTIONS[component].find((o) => o.value === value)?.score ?? null\n );\n}\n\n/**\n * Sum the Bishop score. Returns `null` until every one of the five components\n * has a valid selection (a partial exam doesn't yield a meaningful total).\n */\nexport function bishopScore(selection: BishopSelection): BishopResult | null {\n let total = 0;\n for (const component of BISHOP_COMPONENTS) {\n const value = selection[component];\n if (value === undefined) return null;\n const score = scoreFor(component, value);\n if (score === null) return null;\n total += score;\n }\n return { total, band: bishopBand(total) };\n}\n","/* ------------------------------------------------------------------ */\n/* BishopScore — cervical favourability for induction of labour. */\n/* */\n/* Five enumerated cervical-exam findings → a 0–13 total with a */\n/* favourability band. Maths lives in `./bishop` (pure, tested). */\n/* ------------------------------------------------------------------ */\n\nimport { forwardRef, useEffect, useMemo, useState } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport { FormField } from '../form-field';\nimport { Select } from '../select';\nimport { Card } from '../card';\nimport { Badge } from '../badge';\nimport {\n InsertButton,\n type InsertPayload,\n type InsertVariant,\n type InsertMode,\n} from '../_shared/insert-result';\nimport {\n type BishopComponent,\n type BishopSelection,\n type BishopResult,\n type BishopBand,\n BISHOP_COMPONENTS,\n BISHOP_OPTIONS,\n BISHOP_MAX_SCORE,\n bishopScore,\n} from './bishop';\n\nconst rootVariants = cva('ds:flex ds:flex-col ds:gap-[var(--spacing-lg)]', {\n variants: {\n width: { full: 'ds:w-full', auto: 'ds:inline-flex' },\n },\n defaultVariants: { width: 'full' },\n});\n\n/** Band → on-screen Badge variant + inserted-card chip token. */\nconst BAND_BADGE: Record<BishopBand, 'warning' | 'info' | 'success'> = {\n unfavourable: 'warning',\n intermediate: 'info',\n favourable: 'success',\n};\nconst BAND_TOKEN: Record<BishopBand, string> = {\n unfavourable: '--warning-readable',\n intermediate: '--info',\n favourable: '--success',\n};\n\nexport interface BishopScoreProps extends VariantProps<typeof rootVariants> {\n /** Fires whenever all five findings are set (and `null` when not). */\n onResultChange?: (result: BishopResult | null) => void;\n /** When provided, shows the result-action buttons that emit / copy the result. */\n onInsert?: (payload: InsertPayload) => void;\n /** Which verb the result button performs. Defaults to `'insert'`. */\n insertVariant?: InsertVariant;\n /** `copy` variant only — fired after a successful clipboard write. */\n onCopy?: (mode: InsertMode) => void;\n /** `copy` variant only — fired if the clipboard write can't proceed. */\n onError?: (error: unknown) => void;\n /** Brand wordmark printed in the inserted/copied result-card footer. */\n insertBrand?: string | false;\n /** Opaque instance id, emitted as `data-component-id`. */\n id?: string;\n /** Extra class names on the wrapper. */\n className?: string;\n}\n\nexport const BishopScore = forwardRef<HTMLDivElement, BishopScoreProps>(\n (\n {\n onResultChange,\n onInsert,\n insertVariant = 'insert',\n onCopy,\n onError,\n insertBrand,\n id,\n width,\n className,\n },\n ref,\n ) => {\n const { t } = useTranslation();\n\n const [selection, setSelection] = useState<BishopSelection>({});\n\n const result = useMemo(() => bishopScore(selection), [selection]);\n\n useEffect(() => {\n onResultChange?.(result);\n }, [result, onResultChange]);\n\n const optionsFor = (component: BishopComponent) =>\n BISHOP_OPTIONS[component].map((o) => ({\n value: o.value,\n label: t(`bishopScore.${component}.${o.value}`),\n }));\n\n const bandLabel = (band: BishopBand): string =>\n t(`bishopScore.band.${band}`);\n\n return (\n <div\n ref={ref}\n data-component=\"bishop-score\"\n data-component-id={id}\n className={rootVariants({ width, className })}\n >\n <div className=\"ds:grid ds:grid-cols-1 ds:gap-[var(--spacing-md)] ds:sm:grid-cols-2\">\n {BISHOP_COMPONENTS.map((component) => (\n <FormField\n key={component}\n label={t(`bishopScore.${component}.label`)}\n >\n <Select\n options={optionsFor(component)}\n value={selection[component] ?? ''}\n onValueChange={(next) =>\n setSelection((prev) => ({ ...prev, [component]: next }))\n }\n />\n </FormField>\n ))}\n </div>\n\n <p className=\"ds:sr-only\" role=\"status\" aria-live=\"polite\">\n {result\n ? `${t('bishopScore.totalLabel')}: ${t('bishopScore.scoreOf', {\n score: result.total,\n max: BISHOP_MAX_SCORE,\n })}. ${bandLabel(result.band)}.`\n : ''}\n </p>\n\n {result ? (\n <Card variant=\"elevated\">\n <Card.Body className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)]\">\n <dl className=\"ds:grid ds:grid-cols-1 ds:gap-[var(--spacing-md)] ds:sm:grid-cols-2\">\n <div className=\"ds:flex ds:flex-col ds:items-start ds:gap-[var(--spacing-xs)]\">\n <dt className=\"type-label ds:text-muted-foreground\">\n {t('bishopScore.totalLabel')}\n </dt>\n <dd className=\"type-metric ds:text-foreground\">\n {t('bishopScore.scoreOf', {\n score: result.total,\n max: BISHOP_MAX_SCORE,\n })}\n </dd>\n </div>\n <div className=\"ds:flex ds:flex-col ds:items-end ds:gap-[var(--spacing-xs)] ds:text-end\">\n <dt className=\"type-label ds:text-muted-foreground\">\n {t('bishopScore.bandLabel')}\n </dt>\n <dd>\n <Badge variant={BAND_BADGE[result.band]} size=\"lg\">\n {bandLabel(result.band)}\n </Badge>\n </dd>\n </div>\n </dl>\n <p className=\"type-body-sm ds:text-muted-foreground\">\n {t(`bishopScore.interpretation.${result.band}`)}\n </p>\n {insertVariant === 'copy' || onInsert ? (\n <InsertButton\n onInsert={onInsert}\n variant={insertVariant}\n onCopy={onCopy}\n onError={onError}\n card={{\n title: t('insert.title.bishop'),\n icon: 'activity',\n highlight: bandLabel(result.band),\n highlightToken: BAND_TOKEN[result.band],\n brand: insertBrand,\n fields: [\n {\n icon: 'percent',\n label: t('bishopScore.totalLabel'),\n value: t('bishopScore.scoreOf', {\n score: result.total,\n max: BISHOP_MAX_SCORE,\n }),\n },\n {\n icon: 'tag',\n label: t('bishopScore.bandLabel'),\n value: bandLabel(result.band),\n },\n ],\n }}\n />\n ) : null}\n </Card.Body>\n </Card>\n ) : (\n <p className=\"type-body ds:text-muted-foreground\">\n {t('bishopScore.empty')}\n </p>\n )}\n </div>\n );\n },\n);\n\nBishopScore.displayName = 'BishopScore';\n"],"names":["BISHOP_COMPONENTS","BISHOP_OPTIONS","BISHOP_MAX_SCORE","bishopBand","total","scoreFor","component","value","_a","o","bishopScore","selection","score","rootVariants","cva","BAND_BADGE","BAND_TOKEN","BishopScore","forwardRef","onResultChange","onInsert","insertVariant","onCopy","onError","insertBrand","id","width","className","ref","t","useTranslation","setSelection","useState","result","useMemo","useEffect","optionsFor","bandLabel","band","jsxs","jsx","FormField","Select","next","prev","Card","Badge","InsertButton"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bishop-score-DPN4Ched.js","sources":["../../src/components/bishop-score/bishop.ts","../../src/components/bishop-score/bishop-score.tsx"],"sourcesContent":["/* ------------------------------------------------------------------ */\n/* Bishop score — cervical favourability for induction of labour. */\n/* */\n/* Pure, framework-free, unit-testable. Five components scored 0–3 */\n/* (dilation, effacement, station) or 0–2 (consistency, position), */\n/* summed to a 0–13 total. Bands per ACOG: ≤6 unfavourable (cervical */\n/* ripening advised), 7 intermediate, ≥8 favourable. */\n/* */\n/* Bishop EH, Obstet Gynecol 1964; thresholds per ACOG Practice */\n/* Bulletin 107 (Induction of Labor). */\n/* ------------------------------------------------------------------ */\n\nexport type BishopComponent =\n | 'dilation'\n | 'effacement'\n | 'station'\n | 'consistency'\n | 'position';\n\n/** The component fields in the order they are scored / displayed. */\nexport const BISHOP_COMPONENTS: readonly BishopComponent[] = [\n 'dilation',\n 'effacement',\n 'station',\n 'consistency',\n 'position',\n] as const;\n\nexport interface BishopOption {\n /** Stable value key → i18n lookup `bishopScore.<component>.<value>`. */\n value: string;\n /** Points this option contributes. */\n score: number;\n}\n\n/**\n * Scored options per component. Dilation / effacement / station run 0–3;\n * consistency / position run 0–2 (the classic Bishop weighting).\n */\nexport const BISHOP_OPTIONS: Record<BishopComponent, BishopOption[]> = {\n dilation: [\n { value: 'closed', score: 0 },\n { value: '1-2', score: 1 },\n { value: '3-4', score: 2 },\n { value: '5+', score: 3 },\n ],\n effacement: [\n { value: '0-30', score: 0 },\n { value: '40-50', score: 1 },\n { value: '60-70', score: 2 },\n { value: '80+', score: 3 },\n ],\n station: [\n { value: '-3', score: 0 },\n { value: '-2', score: 1 },\n { value: '-1-0', score: 2 },\n { value: '+1+2', score: 3 },\n ],\n consistency: [\n { value: 'firm', score: 0 },\n { value: 'medium', score: 1 },\n { value: 'soft', score: 2 },\n ],\n position: [\n { value: 'posterior', score: 0 },\n { value: 'mid', score: 1 },\n { value: 'anterior', score: 2 },\n ],\n};\n\nexport type BishopBand = 'unfavourable' | 'intermediate' | 'favourable';\n\n/** Highest attainable total (3 + 3 + 3 + 2 + 2). */\nexport const BISHOP_MAX_SCORE = 13;\n\n/** Classify a total into a favourability band (ACOG thresholds). */\nexport function bishopBand(total: number): BishopBand {\n if (total <= 6) return 'unfavourable';\n if (total >= 8) return 'favourable';\n return 'intermediate';\n}\n\nexport type BishopSelection = Partial<Record<BishopComponent, string>>;\n\nexport interface BishopResult {\n total: number;\n band: BishopBand;\n}\n\n/** Look up the score for a component's selected option, or `null` if unknown. */\nfunction scoreFor(component: BishopComponent, value: string): number | null {\n return (\n BISHOP_OPTIONS[component].find((o) => o.value === value)?.score ?? null\n );\n}\n\n/**\n * Sum the Bishop score. Returns `null` until every one of the five components\n * has a valid selection (a partial exam doesn't yield a meaningful total).\n */\nexport function bishopScore(selection: BishopSelection): BishopResult | null {\n let total = 0;\n for (const component of BISHOP_COMPONENTS) {\n const value = selection[component];\n if (value === undefined) return null;\n const score = scoreFor(component, value);\n if (score === null) return null;\n total += score;\n }\n return { total, band: bishopBand(total) };\n}\n","/* ------------------------------------------------------------------ */\n/* BishopScore — cervical favourability for induction of labour. */\n/* */\n/* Five enumerated cervical-exam findings → a 0–13 total with a */\n/* favourability band. Maths lives in `./bishop` (pure, tested). */\n/* ------------------------------------------------------------------ */\n\nimport { forwardRef, useEffect, useMemo, useState } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport { FormField } from '../form-field';\nimport { Select } from '../select';\nimport { Card } from '../card';\nimport { Badge } from '../badge';\nimport {\n InsertButton,\n type InsertPayload,\n type InsertVariant,\n type InsertMode,\n} from '../_shared/insert-result';\nimport {\n type BishopComponent,\n type BishopSelection,\n type BishopResult,\n type BishopBand,\n BISHOP_COMPONENTS,\n BISHOP_OPTIONS,\n BISHOP_MAX_SCORE,\n bishopScore,\n} from './bishop';\n\nconst rootVariants = cva('ds:flex ds:flex-col ds:gap-[var(--spacing-lg)]', {\n variants: {\n width: { full: 'ds:w-full', auto: 'ds:inline-flex' },\n },\n defaultVariants: { width: 'full' },\n});\n\n/** Band → on-screen Badge variant + inserted-card chip token. */\nconst BAND_BADGE: Record<BishopBand, 'warning' | 'info' | 'success'> = {\n unfavourable: 'warning',\n intermediate: 'info',\n favourable: 'success',\n};\nconst BAND_TOKEN: Record<BishopBand, string> = {\n unfavourable: '--warning-readable',\n intermediate: '--info',\n favourable: '--success',\n};\n\nexport interface BishopScoreProps extends VariantProps<typeof rootVariants> {\n /** Fires whenever all five findings are set (and `null` when not). */\n onResultChange?: (result: BishopResult | null) => void;\n /** When provided, shows the result-action buttons that emit / copy the result. */\n onInsert?: (payload: InsertPayload) => void;\n /** Which verb the result button performs. Defaults to `'insert'`. */\n insertVariant?: InsertVariant;\n /** `copy` variant only — fired after a successful clipboard write. */\n onCopy?: (mode: InsertMode) => void;\n /** `copy` variant only — fired if the clipboard write can't proceed. */\n onError?: (error: unknown) => void;\n /** Brand wordmark printed in the inserted/copied result-card footer. */\n insertBrand?: string | false;\n /** Opaque instance id, emitted as `data-component-id`. */\n id?: string;\n /** Extra class names on the wrapper. */\n className?: string;\n}\n\nexport const BishopScore = forwardRef<HTMLDivElement, BishopScoreProps>(\n (\n {\n onResultChange,\n onInsert,\n insertVariant = 'insert',\n onCopy,\n onError,\n insertBrand,\n id,\n width,\n className,\n },\n ref,\n ) => {\n const { t } = useTranslation();\n\n const [selection, setSelection] = useState<BishopSelection>({});\n\n const result = useMemo(() => bishopScore(selection), [selection]);\n\n useEffect(() => {\n onResultChange?.(result);\n }, [result, onResultChange]);\n\n const optionsFor = (component: BishopComponent) =>\n BISHOP_OPTIONS[component].map((o) => ({\n value: o.value,\n label: t(`bishopScore.${component}.${o.value}`),\n }));\n\n const bandLabel = (band: BishopBand): string =>\n t(`bishopScore.band.${band}`);\n\n return (\n <div\n ref={ref}\n data-component=\"bishop-score\"\n data-component-id={id}\n className={rootVariants({ width, className })}\n >\n <div className=\"ds:grid ds:grid-cols-1 ds:gap-[var(--spacing-md)] ds:sm:grid-cols-2\">\n {BISHOP_COMPONENTS.map((component) => (\n <FormField\n key={component}\n label={t(`bishopScore.${component}.label`)}\n >\n <Select\n options={optionsFor(component)}\n value={selection[component] ?? ''}\n onValueChange={(next) =>\n setSelection((prev) => ({ ...prev, [component]: next }))\n }\n />\n </FormField>\n ))}\n </div>\n\n <p className=\"ds:sr-only\" role=\"status\" aria-live=\"polite\">\n {result\n ? `${t('bishopScore.totalLabel')}: ${t('bishopScore.scoreOf', {\n score: result.total,\n max: BISHOP_MAX_SCORE,\n })}. ${bandLabel(result.band)}.`\n : ''}\n </p>\n\n {result ? (\n <Card variant=\"elevated\">\n <Card.Body className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)]\">\n <dl className=\"ds:grid ds:grid-cols-1 ds:gap-[var(--spacing-md)] ds:sm:grid-cols-2\">\n <div className=\"ds:flex ds:flex-col ds:items-start ds:gap-[var(--spacing-xs)]\">\n <dt className=\"type-label ds:text-muted-foreground\">\n {t('bishopScore.totalLabel')}\n </dt>\n <dd className=\"type-metric ds:text-foreground\">\n {t('bishopScore.scoreOf', {\n score: result.total,\n max: BISHOP_MAX_SCORE,\n })}\n </dd>\n </div>\n <div className=\"ds:flex ds:flex-col ds:items-end ds:gap-[var(--spacing-xs)] ds:text-end\">\n <dt className=\"type-label ds:text-muted-foreground\">\n {t('bishopScore.bandLabel')}\n </dt>\n <dd>\n <Badge variant={BAND_BADGE[result.band]} size=\"lg\">\n {bandLabel(result.band)}\n </Badge>\n </dd>\n </div>\n </dl>\n <p className=\"type-body-sm ds:text-muted-foreground\">\n {t(`bishopScore.interpretation.${result.band}`)}\n </p>\n {insertVariant === 'copy' || onInsert ? (\n <InsertButton\n onInsert={onInsert}\n variant={insertVariant}\n onCopy={onCopy}\n onError={onError}\n card={{\n title: t('insert.title.bishop'),\n icon: 'activity',\n highlight: bandLabel(result.band),\n highlightToken: BAND_TOKEN[result.band],\n brand: insertBrand,\n fields: [\n {\n icon: 'percent',\n label: t('bishopScore.totalLabel'),\n value: t('bishopScore.scoreOf', {\n score: result.total,\n max: BISHOP_MAX_SCORE,\n }),\n },\n {\n icon: 'tag',\n label: t('bishopScore.bandLabel'),\n value: bandLabel(result.band),\n },\n ],\n }}\n />\n ) : null}\n </Card.Body>\n </Card>\n ) : (\n <p className=\"type-body ds:text-muted-foreground\">\n {t('bishopScore.empty')}\n </p>\n )}\n </div>\n );\n },\n);\n\nBishopScore.displayName = 'BishopScore';\n"],"names":["BISHOP_COMPONENTS","BISHOP_OPTIONS","BISHOP_MAX_SCORE","bishopBand","total","scoreFor","component","value","_a","o","bishopScore","selection","score","rootVariants","cva","BAND_BADGE","BAND_TOKEN","BishopScore","forwardRef","onResultChange","onInsert","insertVariant","onCopy","onError","insertBrand","id","width","className","ref","t","useTranslation","setSelection","useState","result","useMemo","useEffect","optionsFor","bandLabel","band","jsxs","jsx","FormField","Select","next","prev","Card","Badge","InsertButton"],"mappings":";;;;;;;;;AAoBO,MAAMA,IAAgD;AAAA,EAC3D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAaaC,IAA0D;AAAA,EACrE,UAAU;AAAA,IACR,EAAE,OAAO,UAAU,OAAO,EAAA;AAAA,IAC1B,EAAE,OAAO,OAAO,OAAO,EAAA;AAAA,IACvB,EAAE,OAAO,OAAO,OAAO,EAAA;AAAA,IACvB,EAAE,OAAO,MAAM,OAAO,EAAA;AAAA,EAAE;AAAA,EAE1B,YAAY;AAAA,IACV,EAAE,OAAO,QAAQ,OAAO,EAAA;AAAA,IACxB,EAAE,OAAO,SAAS,OAAO,EAAA;AAAA,IACzB,EAAE,OAAO,SAAS,OAAO,EAAA;AAAA,IACzB,EAAE,OAAO,OAAO,OAAO,EAAA;AAAA,EAAE;AAAA,EAE3B,SAAS;AAAA,IACP,EAAE,OAAO,MAAM,OAAO,EAAA;AAAA,IACtB,EAAE,OAAO,MAAM,OAAO,EAAA;AAAA,IACtB,EAAE,OAAO,QAAQ,OAAO,EAAA;AAAA,IACxB,EAAE,OAAO,QAAQ,OAAO,EAAA;AAAA,EAAE;AAAA,EAE5B,aAAa;AAAA,IACX,EAAE,OAAO,QAAQ,OAAO,EAAA;AAAA,IACxB,EAAE,OAAO,UAAU,OAAO,EAAA;AAAA,IAC1B,EAAE,OAAO,QAAQ,OAAO,EAAA;AAAA,EAAE;AAAA,EAE5B,UAAU;AAAA,IACR,EAAE,OAAO,aAAa,OAAO,EAAA;AAAA,IAC7B,EAAE,OAAO,OAAO,OAAO,EAAA;AAAA,IACvB,EAAE,OAAO,YAAY,OAAO,EAAA;AAAA,EAAE;AAElC,GAKaC,IAAmB;AAGzB,SAASC,EAAWC,GAA2B;AACpD,SAAIA,KAAS,IAAU,iBACnBA,KAAS,IAAU,eAChB;AACT;AAUA,SAASC,EAASC,GAA4BC,GAA8B;;AAC1E,WACEC,IAAAP,EAAeK,CAAS,EAAE,KAAK,CAACG,MAAMA,EAAE,UAAUF,CAAK,MAAvD,gBAAAC,EAA0D,UAAS;AAEvE;AAMO,SAASE,EAAYC,GAAiD;AAC3E,MAAIP,IAAQ;AACZ,aAAWE,KAAaN,GAAmB;AACzC,UAAMO,IAAQI,EAAUL,CAAS;AACjC,QAAIC,MAAU,OAAW,QAAO;AAChC,UAAMK,IAAQP,EAASC,GAAWC,CAAK;AACvC,QAAIK,MAAU,KAAM,QAAO;AAC3B,IAAAR,KAASQ;AAAA,EACX;AACA,SAAO,EAAE,OAAAR,GAAO,MAAMD,EAAWC,CAAK,EAAA;AACxC;AC/EA,MAAMS,IAAeC,EAAI,kDAAkD;AAAA,EACzE,UAAU;AAAA,IACR,OAAO,EAAE,MAAM,aAAa,MAAM,iBAAA;AAAA,EAAiB;AAAA,EAErD,iBAAiB,EAAE,OAAO,OAAA;AAC5B,CAAC,GAGKC,IAAiE;AAAA,EACrE,cAAc;AAAA,EACd,cAAc;AAAA,EACd,YAAY;AACd,GACMC,IAAyC;AAAA,EAC7C,cAAc;AAAA,EACd,cAAc;AAAA,EACd,YAAY;AACd,GAqBaC,IAAcC;AAAA,EACzB,CACE;AAAA,IACE,gBAAAC;AAAA,IACA,UAAAC;AAAA,IACA,eAAAC,IAAgB;AAAA,IAChB,QAAAC;AAAA,IACA,SAAAC;AAAA,IACA,aAAAC;AAAA,IACA,IAAAC;AAAA,IACA,OAAAC;AAAA,IACA,WAAAC;AAAA,EAAA,GAEFC,MACG;AACH,UAAM,EAAE,GAAAC,EAAA,IAAMC,EAAA,GAER,CAACnB,GAAWoB,CAAY,IAAIC,EAA0B,CAAA,CAAE,GAExDC,IAASC,EAAQ,MAAMxB,EAAYC,CAAS,GAAG,CAACA,CAAS,CAAC;AAEhE,IAAAwB,EAAU,MAAM;AACd,MAAAhB,KAAA,QAAAA,EAAiBc;AAAA,IACnB,GAAG,CAACA,GAAQd,CAAc,CAAC;AAE3B,UAAMiB,IAAa,CAAC9B,MAClBL,EAAeK,CAAS,EAAE,IAAI,CAACG,OAAO;AAAA,MACpC,OAAOA,EAAE;AAAA,MACT,OAAOoB,EAAE,eAAevB,CAAS,IAAIG,EAAE,KAAK,EAAE;AAAA,IAAA,EAC9C,GAEE4B,IAAY,CAACC,MACjBT,EAAE,oBAAoBS,CAAI,EAAE;AAE9B,WACE,gBAAAC;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAAX;AAAA,QACA,kBAAe;AAAA,QACf,qBAAmBH;AAAA,QACnB,WAAWZ,EAAa,EAAE,OAAAa,GAAO,WAAAC,GAAW;AAAA,QAE5C,UAAA;AAAA,UAAA,gBAAAa,EAAC,SAAI,WAAU,uEACZ,UAAAxC,EAAkB,IAAI,CAACM,MACtB,gBAAAkC;AAAA,YAACC;AAAA,YAAA;AAAA,cAEC,OAAOZ,EAAE,eAAevB,CAAS,QAAQ;AAAA,cAEzC,UAAA,gBAAAkC;AAAA,gBAACE;AAAA,gBAAA;AAAA,kBACC,SAASN,EAAW9B,CAAS;AAAA,kBAC7B,OAAOK,EAAUL,CAAS,KAAK;AAAA,kBAC/B,eAAe,CAACqC,MACdZ,EAAa,CAACa,OAAU,EAAE,GAAGA,GAAM,CAACtC,CAAS,GAAGqC,IAAO;AAAA,gBAAA;AAAA,cAAA;AAAA,YAE3D;AAAA,YATKrC;AAAA,UAAA,CAWR,GACH;AAAA,UAEA,gBAAAkC,EAAC,KAAA,EAAE,WAAU,cAAa,MAAK,UAAS,aAAU,UAC/C,UAAAP,IACG,GAAGJ,EAAE,wBAAwB,CAAC,KAAKA,EAAE,uBAAuB;AAAA,YAC1D,OAAOI,EAAO;AAAA,YACd,KAAK/B;AAAA,UAAA,CACN,CAAC,KAAKmC,EAAUJ,EAAO,IAAI,CAAC,MAC7B,IACN;AAAA,UAECA,IACC,gBAAAO,EAACK,GAAA,EAAK,SAAQ,YACZ,4BAACA,EAAK,MAAL,EAAU,WAAU,kDACnB,UAAA;AAAA,YAAA,gBAAAN,EAAC,MAAA,EAAG,WAAU,uEACZ,UAAA;AAAA,cAAA,gBAAAA,EAAC,OAAA,EAAI,WAAU,iEACb,UAAA;AAAA,gBAAA,gBAAAC,EAAC,MAAA,EAAG,WAAU,uCACX,UAAAX,EAAE,wBAAwB,GAC7B;AAAA,gBACA,gBAAAW,EAAC,MAAA,EAAG,WAAU,kCACX,YAAE,uBAAuB;AAAA,kBACxB,OAAOP,EAAO;AAAA,kBACd,KAAK/B;AAAA,gBAAA,CACN,EAAA,CACH;AAAA,cAAA,GACF;AAAA,cACA,gBAAAqC,EAAC,OAAA,EAAI,WAAU,2EACb,UAAA;AAAA,gBAAA,gBAAAC,EAAC,MAAA,EAAG,WAAU,uCACX,UAAAX,EAAE,uBAAuB,GAC5B;AAAA,gBACA,gBAAAW,EAAC,MAAA,EACC,UAAA,gBAAAA,EAACM,GAAA,EAAM,SAAS/B,EAAWkB,EAAO,IAAI,GAAG,MAAK,MAC3C,UAAAI,EAAUJ,EAAO,IAAI,GACxB,EAAA,CACF;AAAA,cAAA,EAAA,CACF;AAAA,YAAA,GACF;AAAA,YACA,gBAAAO,EAAC,OAAE,WAAU,yCACV,YAAE,8BAA8BP,EAAO,IAAI,EAAE,EAAA,CAChD;AAAA,YACCZ,MAAkB,UAAUD,IAC3B,gBAAAoB;AAAA,cAACO;AAAA,cAAA;AAAA,gBACC,UAAA3B;AAAA,gBACA,SAASC;AAAA,gBACT,QAAAC;AAAA,gBACA,SAAAC;AAAA,gBACA,MAAM;AAAA,kBACJ,OAAOM,EAAE,qBAAqB;AAAA,kBAC9B,MAAM;AAAA,kBACN,WAAWQ,EAAUJ,EAAO,IAAI;AAAA,kBAChC,gBAAgBjB,EAAWiB,EAAO,IAAI;AAAA,kBACtC,OAAOT;AAAA,kBACP,QAAQ;AAAA,oBACN;AAAA,sBACE,MAAM;AAAA,sBACN,OAAOK,EAAE,wBAAwB;AAAA,sBACjC,OAAOA,EAAE,uBAAuB;AAAA,wBAC9B,OAAOI,EAAO;AAAA,wBACd,KAAK/B;AAAA,sBAAA,CACN;AAAA,oBAAA;AAAA,oBAEH;AAAA,sBACE,MAAM;AAAA,sBACN,OAAO2B,EAAE,uBAAuB;AAAA,sBAChC,OAAOQ,EAAUJ,EAAO,IAAI;AAAA,oBAAA;AAAA,kBAC9B;AAAA,gBACF;AAAA,cACF;AAAA,YAAA,IAEA;AAAA,UAAA,EAAA,CACN,EAAA,CACF,IAEA,gBAAAO,EAAC,KAAA,EAAE,WAAU,sCACV,UAAAX,EAAE,mBAAmB,EAAA,CACxB;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAIR;AACF;AAEAZ,EAAY,cAAc;"}
|
|
@@ -8,7 +8,7 @@ import { F as D } from "./form-field-BOm9hK35.js";
|
|
|
8
8
|
import { N as p } from "./number-input-Dj5L3pXK.js";
|
|
9
9
|
import { C as M } from "./card-DPmk26CL.js";
|
|
10
10
|
import { B as na } from "./badge-zsf5i5bH.js";
|
|
11
|
-
import { B as ra, I as sa, b as oa } from "./insert-result-
|
|
11
|
+
import { B as ra, I as sa, b as oa } from "./insert-result-njqBthzT.js";
|
|
12
12
|
import { b as ca, e as ma, c as da, f as O, l as W, k as ua } from "./bmi-BxD-tFzU.js";
|
|
13
13
|
const ga = la("ds:flex ds:flex-col ds:gap-[var(--spacing-lg)]", {
|
|
14
14
|
variants: {
|
|
@@ -255,4 +255,4 @@ pa.displayName = "BmiCalculator";
|
|
|
255
255
|
export {
|
|
256
256
|
pa as B
|
|
257
257
|
};
|
|
258
|
-
//# sourceMappingURL=bmi-calculator-
|
|
258
|
+
//# sourceMappingURL=bmi-calculator-DuUneHuZ.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bmi-calculator-C3roxbqD.js","sources":["../../src/components/bmi-calculator/bmi-calculator.tsx"],"sourcesContent":["/* ------------------------------------------------------------------ */\n/* BmiCalculator — height + weight → WHO body-mass-index, shown on a */\n/* banded-linear gauge. */\n/* */\n/* - Maths + unit conversion live in `./bmi` (pure, separately tested). */\n/* - Metric ⇄ imperial toggle persists the entered figures across the */\n/* switch by converting them (a patient measured in lb/ft doesn't */\n/* lose their numbers when a clinician flips to kg/cm). */\n/* - The gauge is the shared `BandedGauge`: a horizontal WHO-banded bar */\n/* (15–40) with a marker at the BMI's x-position. The SAME geometry */\n/* bakes into the inserted result-card PNG (banded variant), so the */\n/* on-screen widget and the rasterised card never drift. The band */\n/* fills come from semantic tokens (`--info` / `--success` / */\n/* `--warning-readable` / `--destructive`) — the contrast-safe */\n/* `--warning-readable` token handles the overweight orange in every */\n/* theme, so no `getComputedStyle` arc-colour sampling is needed. */\n/* ------------------------------------------------------------------ */\n\nimport { forwardRef, useEffect, useId, useMemo, useState } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport { RadioGroup, Radio } from '../radio-group';\nimport { FormField } from '../form-field';\nimport { NumberInput } from '../number-input';\nimport { Card } from '../card';\nimport { Badge } from '../badge';\nimport {\n InsertButton,\n type InsertPayload,\n type InsertVariant,\n type InsertMode,\n} from '../_shared/insert-result';\nimport { BandedGauge, bmiBands } from '../_shared/banded-gauge';\nimport {\n type UnitSystem,\n type BmiCategory,\n computeBmi,\n bmiCategory,\n cmToFtIn,\n ftInToCm,\n kgToLb,\n lbToKg,\n} from './bmi';\n\n/* ------------------------------------------------------------------ */\n/* Result payload emitted to consumers */\n/* ------------------------------------------------------------------ */\n\nexport interface BmiResult {\n /** BMI in kg/m². */\n bmi: number;\n /** WHO category the BMI falls into. */\n category: BmiCategory;\n}\n\n/* ------------------------------------------------------------------ */\n/* CVA */\n/* ------------------------------------------------------------------ */\n\nconst rootVariants = cva('ds:flex ds:flex-col ds:gap-[var(--spacing-lg)]', {\n variants: {\n width: {\n full: 'ds:w-full',\n auto: 'ds:inline-flex',\n },\n },\n defaultVariants: { width: 'full' },\n});\n\n/* ------------------------------------------------------------------ */\n/* Category → semantic intent for the Stat headline */\n/* ------------------------------------------------------------------ */\n\nconst CATEGORY_BADGE: Record<\n BmiCategory,\n 'info' | 'success' | 'warning' | 'error'\n> = {\n underweight: 'info',\n normal: 'success',\n overweight: 'warning',\n obese: 'error',\n};\n\n/* ------------------------------------------------------------------ */\n/* Category → semantic band token. */\n/* */\n/* The four WHO bands carry these tokens both on-screen (the shared */\n/* `bmiBands()` definition fills each zone with the same name) and in */\n/* the card chip's `highlightToken`. `overweight` uses */\n/* `--warning-readable` — the kit's contrast-safe orange that already */\n/* resolves correctly per theme (orange-600 in light/non-accessible, */\n/* `--warning` elsewhere), so NO `getComputedStyle` arc sampling or */\n/* `document.documentElement` override is needed. */\n/* ------------------------------------------------------------------ */\n\nconst CATEGORY_TOKEN: Record<BmiCategory, string> = {\n underweight: '--info',\n normal: '--success',\n overweight: '--warning-readable',\n obese: '--destructive',\n};\n\nexport interface BmiCalculatorProps extends VariantProps<typeof rootVariants> {\n /** Initial unit system. Defaults to `'metric'`. */\n defaultUnitSystem?: UnitSystem;\n /** Fires whenever a valid BMI can be computed (and with `null` when it can't). */\n onResultChange?: (result: BmiResult | null) => void;\n /** When provided, shows an \"Insert\" button that emits the result for an editor. */\n onInsert?: (payload: InsertPayload) => void;\n /**\n * Which verb the result button performs. Defaults to `'insert'`.\n * Use `'copy'` in an app-shell surface (no editor to insert into) — the\n * button writes the result to the clipboard as a multi-format `ClipboardItem`.\n */\n insertVariant?: InsertVariant;\n /** `copy` variant only — fired after a successful clipboard write. */\n onCopy?: (mode: InsertMode) => void;\n /** `copy` variant only — fired if the clipboard write can't proceed. */\n onError?: (error: unknown) => void;\n /**\n * Brand wordmark printed in the inserted/copied result-card footer.\n * Omitted → no brand line (and no footer hairline); a string → that custom\n * brand; `false` → no brand line. Brand is opt-in.\n */\n insertBrand?: string | false;\n /** Opaque instance id, emitted as `data-component-id`. */\n id?: string;\n /** Extra class names on the wrapper. */\n className?: string;\n}\n\n/** Round to one decimal place — used when seeding a unit-switch. */\nconst round1 = (n: number): number => Math.round(n * 10) / 10;\n\nexport const BmiCalculator = forwardRef<HTMLDivElement, BmiCalculatorProps>(\n (\n {\n defaultUnitSystem = 'metric',\n onResultChange,\n onInsert,\n insertVariant = 'insert',\n onCopy,\n onError,\n insertBrand,\n id,\n width,\n className,\n },\n ref,\n ) => {\n const { t, i18n } = useTranslation();\n const heightGroupId = useId();\n\n const [unitSystem, setUnitSystem] = useState<UnitSystem>(defaultUnitSystem);\n\n // Raw per-system inputs. Metric is canonical for cm/kg; imperial keeps\n // ft + in + lb so typing isn't fought by rounding round-trips.\n const [heightCm, setHeightCm] = useState<number | null>(null);\n const [weightKg, setWeightKg] = useState<number | null>(null);\n const [heightFt, setHeightFt] = useState<number | null>(null);\n const [heightIn, setHeightIn] = useState<number | null>(null);\n const [weightLb, setWeightLb] = useState<number | null>(null);\n\n const handleUnitChange = (next: string): void => {\n const target = next as UnitSystem;\n if (target === unitSystem) return;\n if (target === 'imperial') {\n if (heightCm !== null) {\n const { ft, in: inches } = cmToFtIn(heightCm);\n setHeightFt(ft);\n setHeightIn(round1(inches));\n }\n if (weightKg !== null) setWeightLb(round1(kgToLb(weightKg)));\n } else {\n if (heightFt !== null || heightIn !== null) {\n setHeightCm(round1(ftInToCm(heightFt ?? 0, heightIn ?? 0)));\n }\n if (weightLb !== null) setWeightKg(round1(lbToKg(weightLb)));\n }\n setUnitSystem(target);\n };\n\n /* Canonical metric figures for the active system. */\n const canonicalHeightCm =\n unitSystem === 'metric'\n ? heightCm\n : heightFt !== null || heightIn !== null\n ? ftInToCm(heightFt ?? 0, heightIn ?? 0)\n : null;\n const canonicalWeightKg =\n unitSystem === 'metric'\n ? weightKg\n : weightLb !== null\n ? lbToKg(weightLb)\n : null;\n\n const bmi = computeBmi(canonicalWeightKg, canonicalHeightCm);\n const category = bmi !== null ? bmiCategory(bmi) : null;\n\n const bmiFormatter = useMemo(\n () =>\n new Intl.NumberFormat(i18n.language, {\n minimumFractionDigits: 1,\n maximumFractionDigits: 1,\n }),\n [i18n.language],\n );\n\n // Notify consumers without re-firing on unrelated re-renders.\n useEffect(() => {\n onResultChange?.(\n bmi !== null && category !== null ? { bmi, category } : null,\n );\n }, [bmi, category, onResultChange]);\n\n const isMetric = unitSystem === 'metric';\n\n // Pre-translated gauge labels, shared by the on-screen widget and the\n // inserted card so both surfaces read identically. The shared `BandedGauge`\n // is i18n-agnostic — every visible string arrives already through `t()`.\n const gauge = useMemo(() => {\n if (bmi === null || category === null) return null;\n const { bands, min, max, ticks } = bmiBands();\n const valueLabel = bmiFormatter.format(bmi);\n const categoryLabel = t(`bmiCalculator.category.${category}`);\n const rangeLabel = t(`bmiCalculator.range.${category}`);\n const ariaLabel = t('bmiCalculator.gaugeAria', {\n bmi: valueLabel,\n category: categoryLabel,\n range: rangeLabel,\n });\n return {\n value: bmi,\n bands,\n min,\n max,\n ticks,\n valueLabel,\n categoryLabel,\n rangeLabel,\n ariaLabel,\n };\n }, [bmi, category, bmiFormatter, t]);\n\n return (\n <div\n ref={ref}\n data-component=\"bmi-calculator\"\n data-component-id={id}\n className={rootVariants({ width, className })}\n >\n <RadioGroup\n label={t('bmiCalculator.unitSystem.label')}\n variant=\"horizontal\"\n value={unitSystem}\n onValueChange={handleUnitChange}\n >\n <Radio label={t('bmiCalculator.unitSystem.metric')} value=\"metric\" />\n <Radio\n label={t('bmiCalculator.unitSystem.imperial')}\n value=\"imperial\"\n />\n </RadioGroup>\n\n <div className=\"ds:grid ds:grid-cols-1 ds:gap-[var(--spacing-md)] ds:sm:grid-cols-2\">\n {isMetric ? (\n <FormField\n label={`${t('bmiCalculator.height')} (${t('bmiCalculator.units.cm')})`}\n >\n <NumberInput\n mode=\"decimal\"\n min={0}\n step={0.5}\n value={heightCm}\n onChange={setHeightCm}\n />\n </FormField>\n ) : (\n <div role=\"group\" aria-labelledby={heightGroupId}>\n <span\n id={heightGroupId}\n className=\"type-label ds:mb-[var(--spacing-xs)] ds:block ds:text-foreground\"\n >\n {t('bmiCalculator.height')}\n </span>\n <div className=\"ds:flex ds:items-center ds:gap-[var(--spacing-sm)]\">\n <NumberInput\n mode=\"integer\"\n min={0}\n value={heightFt}\n onChange={setHeightFt}\n aria-label={t('bmiCalculator.feet')}\n />\n <span className=\"type-label ds:text-muted-foreground\">\n {t('bmiCalculator.units.ft')}\n </span>\n <NumberInput\n mode=\"decimal\"\n min={0}\n max={11.9}\n step={0.5}\n value={heightIn}\n onChange={setHeightIn}\n aria-label={t('bmiCalculator.inches')}\n />\n <span className=\"type-label ds:text-muted-foreground\">\n {t('bmiCalculator.units.in')}\n </span>\n </div>\n </div>\n )}\n\n <FormField\n label={`${t('bmiCalculator.weight')} (${t(\n isMetric ? 'bmiCalculator.units.kg' : 'bmiCalculator.units.lb',\n )})`}\n >\n <NumberInput\n mode=\"decimal\"\n min={0}\n step={isMetric ? 0.1 : 0.5}\n value={isMetric ? weightKg : weightLb}\n onChange={isMetric ? setWeightKg : setWeightLb}\n />\n </FormField>\n </div>\n\n {/* Concise polite announcement when the result resolves — the gauge\n itself stays out of the live region to avoid re-reading on every\n keystroke. */}\n <p className=\"ds:sr-only\" role=\"status\" aria-live=\"polite\">\n {gauge ? gauge.ariaLabel : ''}\n </p>\n\n {bmi !== null && category !== null && gauge !== null ? (\n <Card variant=\"elevated\">\n <Card.Body className=\"ds:flex ds:flex-col ds:items-center ds:gap-[var(--spacing-md)]\">\n <div className=\"ds:flex ds:flex-col ds:items-center ds:gap-[var(--spacing-xs)] ds:text-center\">\n <span className=\"type-label ds:text-muted-foreground\">\n {t('bmiCalculator.category.label')}\n </span>\n <Badge variant={CATEGORY_BADGE[category]} size=\"lg\">\n {t(`bmiCalculator.category.${category}`)}\n </Badge>\n <p className=\"type-body ds:text-muted-foreground\">\n {gauge.rangeLabel}\n </p>\n </div>\n {/* `data-bmi-category` exposes the WHO band so the gauge's\n semantic colour is assertable without sampling pixels. The\n shared BandedGauge fills each zone via `var(<token>)`, so a\n theme switch repaints the bands with no JS re-sample. */}\n <div\n data-bmi-category={category}\n className=\"ds:w-full ds:max-w-[320px]\"\n >\n <BandedGauge model={gauge} className=\"ds:w-full\" />\n </div>\n {insertVariant === 'copy' || onInsert ? (\n <InsertButton\n onInsert={onInsert}\n variant={insertVariant}\n onCopy={onCopy}\n onError={onError}\n card={{\n title: t('insert.title.bmi'),\n icon: 'scale',\n highlight: t(`bmiCalculator.category.${category}`),\n // Chip shares the WHO-category semantic token so the\n // inserted PNG chip matches the on-screen band colour.\n highlightToken: CATEGORY_TOKEN[category],\n brand: insertBrand,\n // Banded gauge: the SAME geometry + WHO bands as the\n // on-screen widget, baked into the card PNG with\n // probe-resolved theme colours.\n gauge: {\n variant: 'banded',\n value: bmi,\n bands: gauge.bands,\n min: gauge.min,\n max: gauge.max,\n valueLabel: gauge.valueLabel,\n categoryLabel: gauge.categoryLabel,\n rangeLabel: gauge.rangeLabel,\n ariaLabel: gauge.ariaLabel,\n ticks: gauge.ticks,\n },\n fields: [\n {\n // Mirrors the on-screen scale glyph + header icon.\n icon: 'scale',\n label: t('insert.title.bmi'),\n value: gauge.valueLabel,\n },\n {\n // WHO band is a classification → tag glyph.\n icon: 'tag',\n label: t('bmiCalculator.category.label'),\n value: gauge.categoryLabel,\n },\n ],\n }}\n />\n ) : null}\n </Card.Body>\n </Card>\n ) : (\n <p className=\"type-body ds:text-muted-foreground\">\n {t('bmiCalculator.empty')}\n </p>\n )}\n </div>\n );\n },\n);\n\nBmiCalculator.displayName = 'BmiCalculator';\n"],"names":["rootVariants","cva","CATEGORY_BADGE","CATEGORY_TOKEN","round1","n","BmiCalculator","forwardRef","defaultUnitSystem","onResultChange","onInsert","insertVariant","onCopy","onError","insertBrand","id","width","className","ref","t","i18n","useTranslation","heightGroupId","useId","unitSystem","setUnitSystem","useState","heightCm","setHeightCm","weightKg","setWeightKg","heightFt","setHeightFt","heightIn","setHeightIn","weightLb","setWeightLb","handleUnitChange","next","target","ft","inches","cmToFtIn","kgToLb","ftInToCm","lbToKg","canonicalHeightCm","canonicalWeightKg","bmi","computeBmi","category","bmiCategory","bmiFormatter","useMemo","useEffect","isMetric","gauge","bands","min","max","ticks","bmiBands","valueLabel","categoryLabel","rangeLabel","ariaLabel","jsxs","RadioGroup","jsx","Radio","FormField","NumberInput","Card","Badge","BandedGauge","InsertButton"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bmi-calculator-DuUneHuZ.js","sources":["../../src/components/bmi-calculator/bmi-calculator.tsx"],"sourcesContent":["/* ------------------------------------------------------------------ */\n/* BmiCalculator — height + weight → WHO body-mass-index, shown on a */\n/* banded-linear gauge. */\n/* */\n/* - Maths + unit conversion live in `./bmi` (pure, separately tested). */\n/* - Metric ⇄ imperial toggle persists the entered figures across the */\n/* switch by converting them (a patient measured in lb/ft doesn't */\n/* lose their numbers when a clinician flips to kg/cm). */\n/* - The gauge is the shared `BandedGauge`: a horizontal WHO-banded bar */\n/* (15–40) with a marker at the BMI's x-position. The SAME geometry */\n/* bakes into the inserted result-card PNG (banded variant), so the */\n/* on-screen widget and the rasterised card never drift. The band */\n/* fills come from semantic tokens (`--info` / `--success` / */\n/* `--warning-readable` / `--destructive`) — the contrast-safe */\n/* `--warning-readable` token handles the overweight orange in every */\n/* theme, so no `getComputedStyle` arc-colour sampling is needed. */\n/* ------------------------------------------------------------------ */\n\nimport { forwardRef, useEffect, useId, useMemo, useState } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport { RadioGroup, Radio } from '../radio-group';\nimport { FormField } from '../form-field';\nimport { NumberInput } from '../number-input';\nimport { Card } from '../card';\nimport { Badge } from '../badge';\nimport {\n InsertButton,\n type InsertPayload,\n type InsertVariant,\n type InsertMode,\n} from '../_shared/insert-result';\nimport { BandedGauge, bmiBands } from '../_shared/banded-gauge';\nimport {\n type UnitSystem,\n type BmiCategory,\n computeBmi,\n bmiCategory,\n cmToFtIn,\n ftInToCm,\n kgToLb,\n lbToKg,\n} from './bmi';\n\n/* ------------------------------------------------------------------ */\n/* Result payload emitted to consumers */\n/* ------------------------------------------------------------------ */\n\nexport interface BmiResult {\n /** BMI in kg/m². */\n bmi: number;\n /** WHO category the BMI falls into. */\n category: BmiCategory;\n}\n\n/* ------------------------------------------------------------------ */\n/* CVA */\n/* ------------------------------------------------------------------ */\n\nconst rootVariants = cva('ds:flex ds:flex-col ds:gap-[var(--spacing-lg)]', {\n variants: {\n width: {\n full: 'ds:w-full',\n auto: 'ds:inline-flex',\n },\n },\n defaultVariants: { width: 'full' },\n});\n\n/* ------------------------------------------------------------------ */\n/* Category → semantic intent for the Stat headline */\n/* ------------------------------------------------------------------ */\n\nconst CATEGORY_BADGE: Record<\n BmiCategory,\n 'info' | 'success' | 'warning' | 'error'\n> = {\n underweight: 'info',\n normal: 'success',\n overweight: 'warning',\n obese: 'error',\n};\n\n/* ------------------------------------------------------------------ */\n/* Category → semantic band token. */\n/* */\n/* The four WHO bands carry these tokens both on-screen (the shared */\n/* `bmiBands()` definition fills each zone with the same name) and in */\n/* the card chip's `highlightToken`. `overweight` uses */\n/* `--warning-readable` — the kit's contrast-safe orange that already */\n/* resolves correctly per theme (orange-600 in light/non-accessible, */\n/* `--warning` elsewhere), so NO `getComputedStyle` arc sampling or */\n/* `document.documentElement` override is needed. */\n/* ------------------------------------------------------------------ */\n\nconst CATEGORY_TOKEN: Record<BmiCategory, string> = {\n underweight: '--info',\n normal: '--success',\n overweight: '--warning-readable',\n obese: '--destructive',\n};\n\nexport interface BmiCalculatorProps extends VariantProps<typeof rootVariants> {\n /** Initial unit system. Defaults to `'metric'`. */\n defaultUnitSystem?: UnitSystem;\n /** Fires whenever a valid BMI can be computed (and with `null` when it can't). */\n onResultChange?: (result: BmiResult | null) => void;\n /** When provided, shows an \"Insert\" button that emits the result for an editor. */\n onInsert?: (payload: InsertPayload) => void;\n /**\n * Which verb the result button performs. Defaults to `'insert'`.\n * Use `'copy'` in an app-shell surface (no editor to insert into) — the\n * button writes the result to the clipboard as a multi-format `ClipboardItem`.\n */\n insertVariant?: InsertVariant;\n /** `copy` variant only — fired after a successful clipboard write. */\n onCopy?: (mode: InsertMode) => void;\n /** `copy` variant only — fired if the clipboard write can't proceed. */\n onError?: (error: unknown) => void;\n /**\n * Brand wordmark printed in the inserted/copied result-card footer.\n * Omitted → no brand line (and no footer hairline); a string → that custom\n * brand; `false` → no brand line. Brand is opt-in.\n */\n insertBrand?: string | false;\n /** Opaque instance id, emitted as `data-component-id`. */\n id?: string;\n /** Extra class names on the wrapper. */\n className?: string;\n}\n\n/** Round to one decimal place — used when seeding a unit-switch. */\nconst round1 = (n: number): number => Math.round(n * 10) / 10;\n\nexport const BmiCalculator = forwardRef<HTMLDivElement, BmiCalculatorProps>(\n (\n {\n defaultUnitSystem = 'metric',\n onResultChange,\n onInsert,\n insertVariant = 'insert',\n onCopy,\n onError,\n insertBrand,\n id,\n width,\n className,\n },\n ref,\n ) => {\n const { t, i18n } = useTranslation();\n const heightGroupId = useId();\n\n const [unitSystem, setUnitSystem] = useState<UnitSystem>(defaultUnitSystem);\n\n // Raw per-system inputs. Metric is canonical for cm/kg; imperial keeps\n // ft + in + lb so typing isn't fought by rounding round-trips.\n const [heightCm, setHeightCm] = useState<number | null>(null);\n const [weightKg, setWeightKg] = useState<number | null>(null);\n const [heightFt, setHeightFt] = useState<number | null>(null);\n const [heightIn, setHeightIn] = useState<number | null>(null);\n const [weightLb, setWeightLb] = useState<number | null>(null);\n\n const handleUnitChange = (next: string): void => {\n const target = next as UnitSystem;\n if (target === unitSystem) return;\n if (target === 'imperial') {\n if (heightCm !== null) {\n const { ft, in: inches } = cmToFtIn(heightCm);\n setHeightFt(ft);\n setHeightIn(round1(inches));\n }\n if (weightKg !== null) setWeightLb(round1(kgToLb(weightKg)));\n } else {\n if (heightFt !== null || heightIn !== null) {\n setHeightCm(round1(ftInToCm(heightFt ?? 0, heightIn ?? 0)));\n }\n if (weightLb !== null) setWeightKg(round1(lbToKg(weightLb)));\n }\n setUnitSystem(target);\n };\n\n /* Canonical metric figures for the active system. */\n const canonicalHeightCm =\n unitSystem === 'metric'\n ? heightCm\n : heightFt !== null || heightIn !== null\n ? ftInToCm(heightFt ?? 0, heightIn ?? 0)\n : null;\n const canonicalWeightKg =\n unitSystem === 'metric'\n ? weightKg\n : weightLb !== null\n ? lbToKg(weightLb)\n : null;\n\n const bmi = computeBmi(canonicalWeightKg, canonicalHeightCm);\n const category = bmi !== null ? bmiCategory(bmi) : null;\n\n const bmiFormatter = useMemo(\n () =>\n new Intl.NumberFormat(i18n.language, {\n minimumFractionDigits: 1,\n maximumFractionDigits: 1,\n }),\n [i18n.language],\n );\n\n // Notify consumers without re-firing on unrelated re-renders.\n useEffect(() => {\n onResultChange?.(\n bmi !== null && category !== null ? { bmi, category } : null,\n );\n }, [bmi, category, onResultChange]);\n\n const isMetric = unitSystem === 'metric';\n\n // Pre-translated gauge labels, shared by the on-screen widget and the\n // inserted card so both surfaces read identically. The shared `BandedGauge`\n // is i18n-agnostic — every visible string arrives already through `t()`.\n const gauge = useMemo(() => {\n if (bmi === null || category === null) return null;\n const { bands, min, max, ticks } = bmiBands();\n const valueLabel = bmiFormatter.format(bmi);\n const categoryLabel = t(`bmiCalculator.category.${category}`);\n const rangeLabel = t(`bmiCalculator.range.${category}`);\n const ariaLabel = t('bmiCalculator.gaugeAria', {\n bmi: valueLabel,\n category: categoryLabel,\n range: rangeLabel,\n });\n return {\n value: bmi,\n bands,\n min,\n max,\n ticks,\n valueLabel,\n categoryLabel,\n rangeLabel,\n ariaLabel,\n };\n }, [bmi, category, bmiFormatter, t]);\n\n return (\n <div\n ref={ref}\n data-component=\"bmi-calculator\"\n data-component-id={id}\n className={rootVariants({ width, className })}\n >\n <RadioGroup\n label={t('bmiCalculator.unitSystem.label')}\n variant=\"horizontal\"\n value={unitSystem}\n onValueChange={handleUnitChange}\n >\n <Radio label={t('bmiCalculator.unitSystem.metric')} value=\"metric\" />\n <Radio\n label={t('bmiCalculator.unitSystem.imperial')}\n value=\"imperial\"\n />\n </RadioGroup>\n\n <div className=\"ds:grid ds:grid-cols-1 ds:gap-[var(--spacing-md)] ds:sm:grid-cols-2\">\n {isMetric ? (\n <FormField\n label={`${t('bmiCalculator.height')} (${t('bmiCalculator.units.cm')})`}\n >\n <NumberInput\n mode=\"decimal\"\n min={0}\n step={0.5}\n value={heightCm}\n onChange={setHeightCm}\n />\n </FormField>\n ) : (\n <div role=\"group\" aria-labelledby={heightGroupId}>\n <span\n id={heightGroupId}\n className=\"type-label ds:mb-[var(--spacing-xs)] ds:block ds:text-foreground\"\n >\n {t('bmiCalculator.height')}\n </span>\n <div className=\"ds:flex ds:items-center ds:gap-[var(--spacing-sm)]\">\n <NumberInput\n mode=\"integer\"\n min={0}\n value={heightFt}\n onChange={setHeightFt}\n aria-label={t('bmiCalculator.feet')}\n />\n <span className=\"type-label ds:text-muted-foreground\">\n {t('bmiCalculator.units.ft')}\n </span>\n <NumberInput\n mode=\"decimal\"\n min={0}\n max={11.9}\n step={0.5}\n value={heightIn}\n onChange={setHeightIn}\n aria-label={t('bmiCalculator.inches')}\n />\n <span className=\"type-label ds:text-muted-foreground\">\n {t('bmiCalculator.units.in')}\n </span>\n </div>\n </div>\n )}\n\n <FormField\n label={`${t('bmiCalculator.weight')} (${t(\n isMetric ? 'bmiCalculator.units.kg' : 'bmiCalculator.units.lb',\n )})`}\n >\n <NumberInput\n mode=\"decimal\"\n min={0}\n step={isMetric ? 0.1 : 0.5}\n value={isMetric ? weightKg : weightLb}\n onChange={isMetric ? setWeightKg : setWeightLb}\n />\n </FormField>\n </div>\n\n {/* Concise polite announcement when the result resolves — the gauge\n itself stays out of the live region to avoid re-reading on every\n keystroke. */}\n <p className=\"ds:sr-only\" role=\"status\" aria-live=\"polite\">\n {gauge ? gauge.ariaLabel : ''}\n </p>\n\n {bmi !== null && category !== null && gauge !== null ? (\n <Card variant=\"elevated\">\n <Card.Body className=\"ds:flex ds:flex-col ds:items-center ds:gap-[var(--spacing-md)]\">\n <div className=\"ds:flex ds:flex-col ds:items-center ds:gap-[var(--spacing-xs)] ds:text-center\">\n <span className=\"type-label ds:text-muted-foreground\">\n {t('bmiCalculator.category.label')}\n </span>\n <Badge variant={CATEGORY_BADGE[category]} size=\"lg\">\n {t(`bmiCalculator.category.${category}`)}\n </Badge>\n <p className=\"type-body ds:text-muted-foreground\">\n {gauge.rangeLabel}\n </p>\n </div>\n {/* `data-bmi-category` exposes the WHO band so the gauge's\n semantic colour is assertable without sampling pixels. The\n shared BandedGauge fills each zone via `var(<token>)`, so a\n theme switch repaints the bands with no JS re-sample. */}\n <div\n data-bmi-category={category}\n className=\"ds:w-full ds:max-w-[320px]\"\n >\n <BandedGauge model={gauge} className=\"ds:w-full\" />\n </div>\n {insertVariant === 'copy' || onInsert ? (\n <InsertButton\n onInsert={onInsert}\n variant={insertVariant}\n onCopy={onCopy}\n onError={onError}\n card={{\n title: t('insert.title.bmi'),\n icon: 'scale',\n highlight: t(`bmiCalculator.category.${category}`),\n // Chip shares the WHO-category semantic token so the\n // inserted PNG chip matches the on-screen band colour.\n highlightToken: CATEGORY_TOKEN[category],\n brand: insertBrand,\n // Banded gauge: the SAME geometry + WHO bands as the\n // on-screen widget, baked into the card PNG with\n // probe-resolved theme colours.\n gauge: {\n variant: 'banded',\n value: bmi,\n bands: gauge.bands,\n min: gauge.min,\n max: gauge.max,\n valueLabel: gauge.valueLabel,\n categoryLabel: gauge.categoryLabel,\n rangeLabel: gauge.rangeLabel,\n ariaLabel: gauge.ariaLabel,\n ticks: gauge.ticks,\n },\n fields: [\n {\n // Mirrors the on-screen scale glyph + header icon.\n icon: 'scale',\n label: t('insert.title.bmi'),\n value: gauge.valueLabel,\n },\n {\n // WHO band is a classification → tag glyph.\n icon: 'tag',\n label: t('bmiCalculator.category.label'),\n value: gauge.categoryLabel,\n },\n ],\n }}\n />\n ) : null}\n </Card.Body>\n </Card>\n ) : (\n <p className=\"type-body ds:text-muted-foreground\">\n {t('bmiCalculator.empty')}\n </p>\n )}\n </div>\n );\n },\n);\n\nBmiCalculator.displayName = 'BmiCalculator';\n"],"names":["rootVariants","cva","CATEGORY_BADGE","CATEGORY_TOKEN","round1","n","BmiCalculator","forwardRef","defaultUnitSystem","onResultChange","onInsert","insertVariant","onCopy","onError","insertBrand","id","width","className","ref","t","i18n","useTranslation","heightGroupId","useId","unitSystem","setUnitSystem","useState","heightCm","setHeightCm","weightKg","setWeightKg","heightFt","setHeightFt","heightIn","setHeightIn","weightLb","setWeightLb","handleUnitChange","next","target","ft","inches","cmToFtIn","kgToLb","ftInToCm","lbToKg","canonicalHeightCm","canonicalWeightKg","bmi","computeBmi","category","bmiCategory","bmiFormatter","useMemo","useEffect","isMetric","gauge","bands","min","max","ticks","bmiBands","valueLabel","categoryLabel","rangeLabel","ariaLabel","jsxs","RadioGroup","jsx","Radio","FormField","NumberInput","Card","Badge","BandedGauge","InsertButton"],"mappings":"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"}
|