@burdenoff/microfe-vibecontrols 2026.1013.2 → 2026.1013.4

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.
@@ -1,189 +1,151 @@
1
1
  import { useTr as e } from "../../shared/hooks/useTr.js";
2
2
  import { useCallback as t, useEffect as n, useId as r, useLayoutEffect as i, useMemo as a, useRef as o, useState as s } from "react";
3
3
  import { Check as c, ChevronDown as l, Search as u, X as d } from "lucide-react";
4
- import { AnimatePresence as f, motion as p } from "framer-motion";
5
- import { jsx as m, jsxs as h } from "react/jsx-runtime";
6
- import { createPortal as g } from "react-dom";
4
+ import { jsx as f, jsxs as p } from "react/jsx-runtime";
5
+ import { Popover as m, PopoverContent as h, PopoverTrigger as g } from "@burdenoff/fe-libs/ui";
7
6
  //#region src/components/shared/SearchableSelect.tsx
8
7
  function _({ label: _, placeholder: v, options: y, value: b, onChange: x, searchPlaceholder: S, disabled: C = !1, className: w = "", clearable: T = !1, emptyValue: E = "", dropdownAlign: D = "left" }) {
9
- let O = e(), k = v ?? O("vibecontrols.shared.searchableSelect.placeholder", "Select..."), A = S ?? O("vibecontrols.shared.searchableSelect.searchPlaceholder", "Search..."), j = `searchable-select-${r()}`, [M, N] = s(!1), [P, F] = s(""), [I, L] = s(-1), [R, z] = s({
10
- visibility: "hidden",
11
- position: "fixed"
12
- }), B = o(null), V = o(null), H = o(null), U = o(null), W = y.find((e) => e.value === b), G = a(() => {
8
+ let O = e(), k = v ?? O("vibecontrols.shared.searchableSelect.placeholder", "Select..."), A = S ?? O("vibecontrols.shared.searchableSelect.searchPlaceholder", "Search..."), j = `searchable-select-${r()}`, [M, N] = s(!1), [P, F] = s(""), [I, L] = s(-1), R = o(null), z = o(null), B = o(0), [V, H] = s(!1);
9
+ i(() => {
10
+ H(!!z.current?.closest("[role=\"dialog\"], [role=\"alertdialog\"]"));
11
+ }, []);
12
+ let U = y.find((e) => e.value === b), W = a(() => {
13
13
  if (!P) return y;
14
14
  let e = P.toLowerCase();
15
15
  return y.filter((t) => t.label.toLowerCase().includes(e) || t.description?.toLowerCase().includes(e) || t.value.toLowerCase().includes(e));
16
16
  }, [y, P]);
17
17
  n(() => {
18
- L(G.length > 0 ? 0 : -1);
19
- }, [G.length, P]), n(() => {
20
- function e(e) {
21
- let t = e.target, n = B.current?.contains(t) ?? !1, r = V.current?.contains(t) ?? !1;
22
- !n && !r && (N(!1), F(""));
23
- }
24
- return document.addEventListener("mousedown", e), () => document.removeEventListener("mousedown", e);
25
- }, []), n(() => {
26
- M && requestAnimationFrame(() => {
27
- H.current?.focus();
28
- });
29
- }, [M]), n(() => {
30
- if (I >= 0 && U.current) {
31
- let e = U.current.querySelectorAll("[data-option]")[I];
18
+ L(W.length > 0 ? 0 : -1);
19
+ }, [W.length, P]), n(() => {
20
+ if (I >= 0 && R.current) {
21
+ let e = R.current.querySelectorAll("[data-option]")[I];
32
22
  e && e.scrollIntoView({ block: "nearest" });
33
23
  }
34
- }, [I]), i(() => {
35
- if (!M || !B.current) return;
36
- let e = B.current.getBoundingClientRect(), t = window.innerWidth, n = window.innerHeight, r = n - e.bottom < 200 && e.top > 200, i = {
37
- position: "fixed",
38
- minWidth: 220
39
- };
40
- if (r ? i.bottom = n - e.top + 4 : i.top = e.bottom + 4, D === "right") {
41
- let n = t - e.right;
42
- i.right = Math.max(4, n), i.maxWidth = Math.max(80, t - Math.max(4, n) - 4);
43
- } else {
44
- let n = Math.max(4, e.left + e.width + 220 > t ? t - 220 - 4 : e.left);
45
- i.left = n, i.width = Math.min(e.width, t - n - 4);
46
- }
47
- z({
48
- ...i,
49
- visibility: "visible"
50
- });
51
- }, [M, D]), n(() => {
52
- M || z({
53
- visibility: "hidden",
54
- position: "fixed"
55
- });
56
- }, [M]);
57
- let K = t((e) => {
24
+ }, [I]);
25
+ let G = t((e) => {
26
+ N(e), e || F("");
27
+ }, []), K = t((e) => {
58
28
  x(e), N(!1), F("");
59
29
  }, [x]), q = t((e) => {
60
- if (!M) {
61
- (e.key === "Enter" || e.key === " " || e.key === "ArrowDown") && (e.preventDefault(), N(!0));
62
- return;
63
- }
64
30
  switch (e.key) {
65
31
  case "ArrowDown":
66
- e.preventDefault(), L((e) => e < G.length - 1 ? e + 1 : 0);
32
+ e.preventDefault(), L((e) => e < W.length - 1 ? e + 1 : 0);
67
33
  break;
68
34
  case "ArrowUp":
69
- e.preventDefault(), L((e) => e > 0 ? e - 1 : G.length - 1);
35
+ e.preventDefault(), L((e) => e > 0 ? e - 1 : W.length - 1);
70
36
  break;
71
- case "Enter":
72
- if (e.preventDefault(), I >= 0 && I < G.length) {
73
- let e = G[I];
74
- e.disabled || K(e.value);
75
- }
76
- break;
77
- case "Escape":
78
- e.preventDefault(), N(!1), F("");
37
+ case "Enter": {
38
+ e.preventDefault();
39
+ let t = W[I];
40
+ t && !t.disabled && K(t.value);
79
41
  break;
42
+ }
80
43
  case "Tab":
81
- N(!1), F("");
44
+ e.preventDefault(), B.current = e.shiftKey ? -1 : 1, N(!1), F("");
82
45
  break;
83
46
  }
84
47
  }, [
85
- M,
86
- G,
48
+ W,
87
49
  I,
88
50
  K
89
51
  ]), J = t((e) => {
52
+ !M && e.key === "ArrowDown" && (e.preventDefault(), N(!0));
53
+ }, [M]), Y = t((e) => {
54
+ let t = z.current;
55
+ if (!t) return !1;
56
+ let n = t.closest("[role=\"dialog\"], [role=\"alertdialog\"]") ?? document.body, r = Array.from(n.querySelectorAll("a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [contenteditable]:not([contenteditable=\"false\"]), [tabindex]:not([tabindex=\"-1\"])")).filter((e) => e.tabIndex >= 0 && e.getClientRects().length > 0), i = r.indexOf(t);
57
+ if (i === -1) return !1;
58
+ let a = r[i + e];
59
+ return a ||= e === 1 ? r[0] : r[r.length - 1], a ? (a.focus(), !0) : !1;
60
+ }, []), X = t((e) => {
61
+ let t = B.current;
62
+ B.current = 0, t !== 0 && Y(t) && e.preventDefault();
63
+ }, [Y]), Z = t((e) => {
90
64
  e.stopPropagation(), x(E);
91
65
  }, [x, E]);
92
- return /* @__PURE__ */ h("div", {
93
- className: `relative ${w}`,
94
- ref: B,
95
- onKeyDown: q,
96
- children: [/* @__PURE__ */ h("button", {
97
- type: "button",
98
- "aria-haspopup": "listbox",
99
- "aria-expanded": M,
100
- "aria-controls": M ? j : void 0,
101
- onClick: () => {
102
- C || N(!M);
103
- },
104
- disabled: C,
105
- className: `inline-flex items-center gap-2 w-full px-3 py-2 text-sm border rounded-md transition-colors text-left ${C ? "bg-bg-sunken border-border-default text-text-muted cursor-not-allowed" : M ? "bg-bg-surface border-border-strong ring-1 ring-border-strong text-text-primary" : "bg-bg-sunken border-border-default text-text-primary hover:bg-bg-elevated"}`,
106
- children: [
107
- _ && /* @__PURE__ */ h("span", {
108
- className: "text-text-secondary flex-shrink-0",
109
- children: [_, ":"]
110
- }),
111
- /* @__PURE__ */ m("span", {
112
- className: `flex-1 truncate ${W ? "font-medium" : "text-text-placeholder"}`,
113
- children: W?.label || k
114
- }),
115
- /* @__PURE__ */ h("div", {
116
- className: "flex items-center gap-1 flex-shrink-0",
117
- children: [T && b !== E && /* @__PURE__ */ m("span", {
118
- onKeyDown: (e) => {
119
- (e.key === "Enter" || e.key === " ") && (e.preventDefault(), J());
120
- },
121
- role: "button",
122
- tabIndex: -1,
123
- onClick: J,
124
- className: "p-0.5 rounded hover:bg-bg-sunken transition-colors",
125
- children: /* @__PURE__ */ m(d, { className: "size-3 text-text-muted" })
126
- }), /* @__PURE__ */ m(l, { className: `size-4 text-text-secondary transition-transform ${M ? "rotate-180" : ""}` })]
66
+ return /* @__PURE__ */ p(m, {
67
+ open: M,
68
+ onOpenChange: G,
69
+ modal: V,
70
+ children: [/* @__PURE__ */ f("div", {
71
+ className: `relative ${w}`,
72
+ children: /* @__PURE__ */ f(g, {
73
+ asChild: !0,
74
+ children: /* @__PURE__ */ p("button", {
75
+ ref: z,
76
+ type: "button",
77
+ "aria-haspopup": "listbox",
78
+ "aria-expanded": M,
79
+ "aria-controls": M ? j : void 0,
80
+ onKeyDown: J,
81
+ disabled: C,
82
+ className: `inline-flex items-center gap-2 w-full px-3 py-2 text-sm border rounded-md transition-colors text-left ${C ? "bg-bg-sunken border-border-default text-text-muted cursor-not-allowed" : M ? "bg-bg-surface border-border-strong ring-1 ring-border-strong text-text-primary" : "bg-bg-sunken border-border-default text-text-primary hover:bg-bg-elevated"}`,
83
+ children: [
84
+ _ && /* @__PURE__ */ p("span", {
85
+ className: "text-text-secondary flex-shrink-0",
86
+ children: [_, ":"]
87
+ }),
88
+ /* @__PURE__ */ f("span", {
89
+ className: `flex-1 truncate ${U ? "font-medium" : "text-text-placeholder"}`,
90
+ children: U?.label || k
91
+ }),
92
+ /* @__PURE__ */ p("span", {
93
+ className: "flex items-center gap-1 flex-shrink-0",
94
+ children: [T && b !== E && /* @__PURE__ */ f("span", {
95
+ onKeyDown: (e) => {
96
+ (e.key === "Enter" || e.key === " ") && (e.preventDefault(), Z(e));
97
+ },
98
+ role: "button",
99
+ tabIndex: -1,
100
+ "aria-label": O("vibecontrols.shared.searchableSelect.clear", "Clear selection"),
101
+ onClick: Z,
102
+ className: "p-0.5 rounded hover:bg-bg-sunken transition-colors",
103
+ children: /* @__PURE__ */ f(d, { className: "size-3 text-text-muted" })
104
+ }), /* @__PURE__ */ f(l, { className: `size-4 text-text-secondary transition-transform ${M ? "rotate-180" : ""}` })]
105
+ })
106
+ ]
127
107
  })
128
- ]
129
- }), g(/* @__PURE__ */ m(f, { children: M && /* @__PURE__ */ h(p.div, {
130
- ref: V,
131
- id: j,
132
- role: "listbox",
133
- "aria-label": _ || k,
134
- initial: {
135
- opacity: 0,
136
- y: -4,
137
- scale: .97
138
- },
139
- animate: {
140
- opacity: 1,
141
- y: 0,
142
- scale: 1
143
- },
144
- exit: {
145
- opacity: 0,
146
- y: -4,
147
- scale: .97
148
- },
149
- transition: {
150
- duration: .15,
151
- ease: [
152
- .4,
153
- 0,
154
- .2,
155
- 1
156
- ]
157
- },
158
- className: "z-[200] bg-bg-surface border border-border-default rounded-md shadow-lg",
159
- style: R,
160
- children: [/* @__PURE__ */ m("div", {
108
+ })
109
+ }), /* @__PURE__ */ p(h, {
110
+ align: D === "right" ? "end" : "start",
111
+ sideOffset: 4,
112
+ collisionPadding: 8,
113
+ onKeyDown: q,
114
+ onCloseAutoFocus: X,
115
+ className: "z-[200] w-(--radix-popover-trigger-width) min-w-[220px] max-w-[calc(100vw-1rem)] p-0 rounded-md bg-bg-surface border-border-default shadow-lg",
116
+ children: [/* @__PURE__ */ f("div", {
161
117
  className: "p-2 border-b border-border-default",
162
- children: /* @__PURE__ */ h("div", {
118
+ children: /* @__PURE__ */ p("div", {
163
119
  className: "flex items-center gap-2 px-2 py-1.5 bg-bg-sunken border border-border-default rounded-md",
164
120
  children: [
165
- /* @__PURE__ */ m(u, { className: "size-3.5 text-text-muted flex-shrink-0" }),
166
- /* @__PURE__ */ m("input", {
167
- "aria-label": "control",
168
- ref: H,
121
+ /* @__PURE__ */ f(u, {
122
+ className: "size-3.5 text-text-muted flex-shrink-0",
123
+ "aria-hidden": "true"
124
+ }),
125
+ /* @__PURE__ */ f("input", {
126
+ "aria-label": A,
169
127
  type: "text",
170
128
  value: P,
171
129
  onChange: (e) => F(e.target.value),
172
130
  placeholder: A,
173
131
  className: "flex-1 text-sm bg-transparent text-text-primary placeholder:text-text-placeholder outline-none"
174
132
  }),
175
- P && /* @__PURE__ */ m("button", {
133
+ P && /* @__PURE__ */ f("button", {
176
134
  type: "button",
177
135
  onClick: () => F(""),
136
+ "aria-label": O("vibecontrols.shared.searchableSelect.clearSearch", "Clear search"),
178
137
  className: "p-0.5 rounded hover:bg-bg-surface transition-colors",
179
- children: /* @__PURE__ */ m(d, { className: "size-3 text-text-muted" })
138
+ children: /* @__PURE__ */ f(d, { className: "size-3 text-text-muted" })
180
139
  })
181
140
  ]
182
141
  })
183
- }), /* @__PURE__ */ m("div", {
184
- ref: U,
142
+ }), /* @__PURE__ */ f("div", {
143
+ ref: R,
144
+ id: j,
145
+ role: "listbox",
146
+ "aria-label": _ || k,
185
147
  className: "max-h-56 overflow-y-auto py-1",
186
- children: G.length > 0 ? G.map((e, t) => /* @__PURE__ */ h("button", {
148
+ children: W.length > 0 ? W.map((e, t) => /* @__PURE__ */ p("button", {
187
149
  "data-option": !0,
188
150
  type: "button",
189
151
  role: "option",
@@ -193,24 +155,24 @@ function _({ label: _, placeholder: v, options: y, value: b, onChange: x, search
193
155
  e.disabled || K(e.value);
194
156
  },
195
157
  disabled: e.disabled,
196
- className: `flex items-center justify-between w-full px-3 py-2 text-sm transition-colors ${e.disabled ? "text-text-muted cursor-not-allowed" : t === I ? "bg-bg-sunken text-text-primary" : "text-text-primary hover:bg-bg-sunken"}`,
158
+ className: `flex items-center justify-between w-full px-3 py-2 text-sm text-left transition-colors ${e.disabled ? "text-text-muted cursor-not-allowed" : t === I ? "bg-bg-sunken text-text-primary" : "text-text-primary hover:bg-bg-sunken"}`,
197
159
  onMouseEnter: () => L(t),
198
- children: [/* @__PURE__ */ h("div", {
160
+ children: [/* @__PURE__ */ p("span", {
199
161
  className: "flex flex-col items-start min-w-0",
200
- children: [/* @__PURE__ */ m("span", {
162
+ children: [/* @__PURE__ */ f("span", {
201
163
  className: "truncate w-full",
202
164
  children: e.label
203
- }), e.description && /* @__PURE__ */ m("span", {
165
+ }), e.description && /* @__PURE__ */ f("span", {
204
166
  className: "text-xs text-text-muted truncate w-full",
205
167
  children: e.description
206
168
  })]
207
- }), b === e.value && /* @__PURE__ */ m(c, { className: "size-4 text-action-primary-bg flex-shrink-0 ml-2" })]
208
- }, e.value)) : /* @__PURE__ */ m("div", {
169
+ }), b === e.value && /* @__PURE__ */ f(c, { className: "size-4 text-action-primary-bg flex-shrink-0 ml-2" })]
170
+ }, e.value)) : /* @__PURE__ */ f("div", {
209
171
  className: "px-3 py-4 text-sm text-text-muted text-center",
210
172
  children: O("vibecontrols.shared.searchableSelect.noOptionsFound", "No options found")
211
173
  })
212
174
  })]
213
- }) }), document.body)]
175
+ })]
214
176
  });
215
177
  }
216
178
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-vibecontrols",
3
- "version": "2026.1013.2",
3
+ "version": "2026.1013.4",
4
4
  "description": "VibeControls microfrontend for Burdenoff products",
5
5
  "type": "module",
6
6
  "files": [