@cystackapp/ui 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/assets/background-pattern-circles-md.svg.js +5 -0
  2. package/dist/components/badge/Badge.d.ts +29 -0
  3. package/dist/components/badge/Badge.js +67 -0
  4. package/dist/components/badge/BadgeTestStory.d.ts +5 -0
  5. package/dist/components/badge/badge-utils.d.ts +27 -0
  6. package/dist/components/badge/badge-utils.js +74 -0
  7. package/dist/components/badge/variants/BadgeMore.d.ts +6 -0
  8. package/dist/components/badge/variants/BadgeMore.js +10 -0
  9. package/dist/components/badge/variants/BadgeMoreTestStory.d.ts +5 -0
  10. package/dist/components/badge/variants/BadgeTag.d.ts +10 -0
  11. package/dist/components/badge/variants/BadgeTag.js +50 -0
  12. package/dist/components/combobox/Combobox.d.ts +96 -0
  13. package/dist/components/combobox/Combobox.js +206 -0
  14. package/dist/components/combobox/use-validated-combobox.d.ts +17 -0
  15. package/dist/components/combobox/use-validated-combobox.js +23 -0
  16. package/dist/components/error-state/ErrorState.d.ts +9 -0
  17. package/dist/components/error-state/ErrorState.js +33 -0
  18. package/dist/components/page-title/PageTitle.d.ts +7 -0
  19. package/dist/components/page-title/PageTitle.js +6 -0
  20. package/dist/components/popover/Popover.d.ts +8 -0
  21. package/dist/components/popover/Popover.js +42 -0
  22. package/dist/components/popover/use-popover-coord.d.ts +24 -0
  23. package/dist/components/popover/use-popover-coord.js +130 -0
  24. package/dist/components/switch/Switch.d.ts +7 -0
  25. package/dist/components/switch/Switch.js +39 -0
  26. package/dist/components/tooltip/Tooltip.d.ts +11 -0
  27. package/dist/components/tooltip/Tooltip.js +58 -0
  28. package/dist/components/tooltip/tooltip-utils.d.ts +4 -0
  29. package/dist/components/tooltip/tooltip-utils.js +120 -0
  30. package/dist/hooks/element-shift/use-animation-frame.d.ts +4 -0
  31. package/dist/hooks/element-shift/use-animation-frame.js +14 -0
  32. package/dist/hooks/element-shift/use-element-shift.d.ts +17 -0
  33. package/dist/hooks/element-shift/use-element-shift.js +22 -0
  34. package/dist/hooks/element-shift/use-mutation-observer.d.ts +4 -0
  35. package/dist/hooks/element-shift/use-mutation-observer.js +15 -0
  36. package/dist/hooks/element-shift/use-resize-observer.d.ts +5 -0
  37. package/dist/hooks/element-shift/use-resize-observer.js +13 -0
  38. package/dist/hooks/element-shift/use-scroll-listener.d.ts +5 -0
  39. package/dist/hooks/element-shift/use-scroll-listener.js +26 -0
  40. package/dist/hooks/element-shift/use-transition-end-listener.d.ts +5 -0
  41. package/dist/hooks/element-shift/use-transition-end-listener.js +28 -0
  42. package/dist/hooks/element-shift/use-window-resize-listener.d.ts +4 -0
  43. package/dist/hooks/element-shift/use-window-resize-listener.js +10 -0
  44. package/dist/i18n/resources.d.ts +2 -0
  45. package/dist/index.d.ts +23 -0
  46. package/dist/index.js +42 -0
  47. package/dist/node_modules/clsx/dist/clsx.js +16 -0
  48. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +2924 -0
  49. package/dist/utils/cn.d.ts +2 -0
  50. package/dist/utils/cn.js +8 -0
  51. package/package.json +66 -0
  52. package/theme.css +358 -0
@@ -0,0 +1,5 @@
1
+ import * as e from "react";
2
+ const r = (t) => /* @__PURE__ */ e.createElement("svg", { height: 480, width: 480, viewBox: "0 0 480 480", xmlns: "http://www.w3.org/2000/svg", ...t }, /* @__PURE__ */ e.createElement("defs", null, /* @__PURE__ */ e.createElement("radialGradient", { id: "radialGradient-mask", cx: "50%", cy: "50%", r: "50%", fx: "50%", fy: "50%" }, /* @__PURE__ */ e.createElement("stop", { offset: "0%", stopColor: "white" }), /* @__PURE__ */ e.createElement("stop", { offset: "100%", stopColor: "black" })), /* @__PURE__ */ e.createElement("mask", { id: "background-pattern-decorative-mask" }, /* @__PURE__ */ e.createElement("rect", { x: 0, y: 0, height: "100%", width: "100%", fill: "url(#radialGradient-mask)" }))), /* @__PURE__ */ e.createElement("g", { mask: "url(#background-pattern-decorative-mask)" }, /* @__PURE__ */ e.createElement("circle", { cx: 240, cy: 240, r: 240, stroke: "#E9EAEB", fill: "transparent" }), /* @__PURE__ */ e.createElement("circle", { cx: 240, cy: 240, r: 208, stroke: "#E9EAEB", fill: "transparent" }), /* @__PURE__ */ e.createElement("circle", { cx: 240, cy: 240, r: 176, stroke: "#E9EAEB", fill: "transparent" }), /* @__PURE__ */ e.createElement("circle", { cx: 240, cy: 240, r: 144, stroke: "#E9EAEB", fill: "transparent" }), /* @__PURE__ */ e.createElement("circle", { cx: 240, cy: 240, r: 112, stroke: "#E9EAEB", fill: "transparent" }), /* @__PURE__ */ e.createElement("circle", { cx: 240, cy: 240, r: 80, stroke: "#E9EAEB", fill: "transparent" }), /* @__PURE__ */ e.createElement("circle", { cx: 240, cy: 240, r: 48, stroke: "#E9EAEB", fill: "transparent" })));
3
+ export {
4
+ r as default
5
+ };
@@ -0,0 +1,29 @@
1
+ import { ReactNode } from 'react';
2
+ import { BadgeColor, BadgeSize, BadgeType } from './badge-utils';
3
+ export interface BadgeProps {
4
+ children: ReactNode;
5
+ color?: BadgeColor;
6
+ size?: BadgeSize;
7
+ type?: BadgeType;
8
+ dot?: boolean;
9
+ iconLeading?: ReactNode;
10
+ iconTrailing?: ReactNode;
11
+ /** When provided, renders a close/dismiss button. */
12
+ onClose?: () => void;
13
+ className?: string;
14
+ }
15
+ /**
16
+ * Visual indicator for categorizing items. Supports three style types:
17
+ * pill (rounded-full), badge (rounded-md), and modern (shadow + white bg).
18
+ *
19
+ * `dot` and `iconLeading` share the same leading slot — if both are provided,
20
+ * `iconLeading` takes precedence and the dot is not rendered.
21
+ *
22
+ * @example
23
+ * ```tsx
24
+ * <Badge color="success" size="sm">Active</Badge>
25
+ * <Badge type="modern" dot color="error">Failed</Badge>
26
+ * <Badge iconLeading={<AlertCircle />} onClose={handleDismiss}>Warning</Badge>
27
+ * ```
28
+ */
29
+ export declare const Badge: ({ children, color, size, type, dot, iconLeading, iconTrailing, onClose, className, }: BadgeProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,67 @@
1
+ import { jsxs as b, jsx as e } from "react/jsx-runtime";
2
+ import { useRef as D, useState as i, useCallback as y, useEffect as N } from "react";
3
+ import { XClose as k } from "@untitled-ui/icons-react";
4
+ import { cn as u } from "../../utils/cn.js";
5
+ import { Tooltip as B } from "../tooltip/Tooltip.js";
6
+ import { useResizeObserver as R } from "../../hooks/element-shift/use-resize-observer.js";
7
+ import { BADGE_ICON_SIZE_CLASSES as T, BADGE_DOT_CLASSES as h, BADGE_MODERN_TEXT_CLASSES as w, BADGE_COLOR_CLASSES as G, BADGE_SIZE_CLASSES as O, BADGE_TYPE_CLASSES as v } from "./badge-utils.js";
8
+ const X = ({
9
+ children: t,
10
+ color: o = "gray",
11
+ size: a = "md",
12
+ type: l = "pill",
13
+ dot: f = !1,
14
+ iconLeading: n,
15
+ iconTrailing: c,
16
+ onClose: m,
17
+ className: p
18
+ }) => {
19
+ const s = D(null), [S, E] = i(!1), [_, A] = i(!1), d = y(() => {
20
+ const r = s.current;
21
+ r && E(r.scrollWidth > r.clientWidth);
22
+ }, []);
23
+ R(s, d), N(() => {
24
+ A(!0);
25
+ }, []);
26
+ const C = f && !n;
27
+ return /* @__PURE__ */ b(
28
+ "span",
29
+ {
30
+ className: u(
31
+ "inline-flex items-center w-fit max-w-32 font-medium transition-all",
32
+ _ ? "opacity-100 scale-100" : "opacity-0 scale-90",
33
+ v[l],
34
+ O[a],
35
+ l === "modern" ? `bg-white border-gray-v2-300 ${w[o]}` : G[o],
36
+ p
37
+ ),
38
+ children: [
39
+ C ? /* @__PURE__ */ e(
40
+ "span",
41
+ {
42
+ className: u(
43
+ "size-1.5 rounded-full shrink-0",
44
+ h[o]
45
+ )
46
+ }
47
+ ) : null,
48
+ n || null,
49
+ S && typeof t == "string" ? /* @__PURE__ */ e(B, { content: t, children: /* @__PURE__ */ e("span", { ref: s, className: "block truncate min-w-0", children: t }) }) : /* @__PURE__ */ e("span", { ref: s, className: "block truncate min-w-0", children: t }),
50
+ c || null,
51
+ m ? /* @__PURE__ */ e(
52
+ "button",
53
+ {
54
+ type: "button",
55
+ "aria-label": typeof t == "string" ? `Remove ${t}` : "Remove",
56
+ onClick: m,
57
+ className: "shrink-0 cursor-pointer rounded-full p-0 hover:opacity-70 transition-opacity",
58
+ children: /* @__PURE__ */ e(k, { className: T[a] })
59
+ }
60
+ ) : null
61
+ ]
62
+ }
63
+ );
64
+ };
65
+ export {
66
+ X as Badge
67
+ };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Test helper that renders a dismissible Badge and displays
3
+ * whether the onClose callback was fired.
4
+ */
5
+ export declare const BadgeWithCloseDisplay: () => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Available badge colors — matches Untitled UI's standard palette.
3
+ *
4
+ * These must correspond to color tokens defined in `packages/ui/theme.css`
5
+ * `@theme` block (e.g. `--color-gray-v2-50`, `--color-brand-v2-200`). If a color is
6
+ * added or removed from the theme, update this array and all class maps below.
7
+ */
8
+ export declare const BADGE_COLORS: readonly ["gray", "brand", "error", "warning", "success", "gray-blue", "blue-light", "blue", "indigo", "purple", "pink", "orange"];
9
+ /** Available badge sizes. */
10
+ export declare const BADGE_SIZES: readonly ["sm", "md", "lg"];
11
+ /** Available badge shape/style types. */
12
+ export declare const BADGE_TYPES: readonly ["pill", "badge", "modern"];
13
+ export type BadgeColor = (typeof BADGE_COLORS)[number];
14
+ export type BadgeSize = (typeof BADGE_SIZES)[number];
15
+ export type BadgeType = (typeof BADGE_TYPES)[number];
16
+ /** Padding, font-size, and gap per size. */
17
+ export declare const BADGE_SIZE_CLASSES: Record<BadgeSize, string>;
18
+ /** Icon dimensions per size. */
19
+ export declare const BADGE_ICON_SIZE_CLASSES: Record<BadgeSize, string>;
20
+ /** Border-radius and structural classes per type. */
21
+ export declare const BADGE_TYPE_CLASSES: Record<BadgeType, string>;
22
+ /** Background, border, and text classes for `pill` and `badge` types. */
23
+ export declare const BADGE_COLOR_CLASSES: Record<BadgeColor, string>;
24
+ /** Text-only classes for `modern` type (white bg, gray border). */
25
+ export declare const BADGE_MODERN_TEXT_CLASSES: Record<BadgeColor, string>;
26
+ /** Dot indicator background per color. */
27
+ export declare const BADGE_DOT_CLASSES: Record<BadgeColor, string>;
@@ -0,0 +1,74 @@
1
+ const r = [
2
+ "gray",
3
+ "brand",
4
+ "error",
5
+ "warning",
6
+ "success",
7
+ "gray-blue",
8
+ "blue-light",
9
+ "blue",
10
+ "indigo",
11
+ "purple",
12
+ "pink",
13
+ "orange"
14
+ ], e = {
15
+ sm: "px-2 py-0.5 text-xs gap-1",
16
+ md: "px-2.5 py-0.5 text-sm gap-1.5",
17
+ lg: "px-3 py-1 text-sm gap-1.5"
18
+ }, g = {
19
+ sm: "size-3",
20
+ md: "size-4",
21
+ lg: "size-4"
22
+ }, b = {
23
+ pill: "rounded-full border",
24
+ badge: "rounded-md border",
25
+ modern: "rounded-md border shadow-xs"
26
+ }, t = {
27
+ gray: "bg-gray-v2-50 border-gray-v2-200 text-gray-v2-700",
28
+ brand: "bg-brand-v2-50 border-brand-v2-200 text-brand-v2-700",
29
+ error: "bg-error-v2-50 border-error-v2-200 text-error-v2-700",
30
+ warning: "bg-warning-v2-50 border-warning-v2-200 text-warning-v2-700",
31
+ success: "bg-success-v2-50 border-success-v2-200 text-success-v2-700",
32
+ "gray-blue": "bg-gray-blue-v2-50 border-gray-blue-v2-200 text-gray-blue-v2-700",
33
+ "blue-light": "bg-blue-light-v2-50 border-blue-light-v2-200 text-blue-light-v2-700",
34
+ blue: "bg-blue-v2-50 border-blue-v2-200 text-blue-v2-700",
35
+ indigo: "bg-indigo-v2-50 border-indigo-v2-200 text-indigo-v2-700",
36
+ purple: "bg-purple-v2-50 border-purple-v2-200 text-purple-v2-700",
37
+ pink: "bg-pink-v2-50 border-pink-v2-200 text-pink-v2-700",
38
+ orange: "bg-orange-v2-50 border-orange-v2-200 text-orange-v2-700"
39
+ }, n = {
40
+ gray: "text-gray-v2-700",
41
+ brand: "text-brand-v2-700",
42
+ error: "text-error-v2-700",
43
+ warning: "text-warning-v2-700",
44
+ success: "text-success-v2-700",
45
+ "gray-blue": "text-gray-blue-v2-700",
46
+ "blue-light": "text-blue-light-v2-700",
47
+ blue: "text-blue-v2-700",
48
+ indigo: "text-indigo-v2-700",
49
+ purple: "text-purple-v2-700",
50
+ pink: "text-pink-v2-700",
51
+ orange: "text-orange-v2-700"
52
+ }, v = {
53
+ gray: "bg-gray-v2-500",
54
+ brand: "bg-brand-v2-500",
55
+ error: "bg-error-v2-500",
56
+ warning: "bg-warning-v2-500",
57
+ success: "bg-success-v2-500",
58
+ "gray-blue": "bg-gray-blue-v2-500",
59
+ "blue-light": "bg-blue-light-v2-500",
60
+ blue: "bg-blue-v2-500",
61
+ indigo: "bg-indigo-v2-500",
62
+ purple: "bg-purple-v2-500",
63
+ pink: "bg-pink-v2-500",
64
+ orange: "bg-orange-v2-500"
65
+ };
66
+ export {
67
+ r as BADGE_COLORS,
68
+ t as BADGE_COLOR_CLASSES,
69
+ v as BADGE_DOT_CLASSES,
70
+ g as BADGE_ICON_SIZE_CLASSES,
71
+ n as BADGE_MODERN_TEXT_CLASSES,
72
+ e as BADGE_SIZE_CLASSES,
73
+ b as BADGE_TYPE_CLASSES
74
+ };
@@ -0,0 +1,6 @@
1
+ import { ReactNode } from 'react';
2
+ interface Props {
3
+ badges: ReactNode[];
4
+ }
5
+ export declare const BadgeMore: ({ badges }: Props) => import("react/jsx-runtime").JSX.Element | null;
6
+ export {};
@@ -0,0 +1,10 @@
1
+ import { jsx as o, jsxs as e } from "react/jsx-runtime";
2
+ import { Popover as t } from "../../popover/Popover.js";
3
+ import { Badge as n } from "../Badge.js";
4
+ const c = ({ badges: r }) => r.length === 0 ? null : /* @__PURE__ */ o(t, { content: r, contentClassName: "max-w-[40vw]", children: /* @__PURE__ */ e(n, { color: "gray", size: "sm", children: [
5
+ "+",
6
+ r.length
7
+ ] }) });
8
+ export {
9
+ c as BadgeMore
10
+ };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Test helper that renders a BadgeMore with a fixed set of badges
3
+ * for use in hover interaction tests.
4
+ */
5
+ export declare const BadgeMoreHoverDisplay: () => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import { BadgeColor, BadgeSize } from '../badge-utils';
2
+ /** Decide the color of the badge by getting a hash from text content. */
3
+ export declare function getBadgeAutoColor(name: string): BadgeColor;
4
+ export declare const TAG_ICON_COLOR_CLASSES: Record<BadgeColor, string>;
5
+ interface Props {
6
+ name: string;
7
+ size?: BadgeSize;
8
+ }
9
+ export declare const BadgeTag: ({ name, size }: Props) => import("react/jsx-runtime").JSX.Element;
10
+ export {};
@@ -0,0 +1,50 @@
1
+ import { jsx as o } from "react/jsx-runtime";
2
+ import { Tag01 as g } from "@untitled-ui/icons-react";
3
+ import { cn as i } from "../../../utils/cn.js";
4
+ import { Badge as c } from "../Badge.js";
5
+ import { BADGE_COLORS as n, BADGE_ICON_SIZE_CLASSES as l } from "../badge-utils.js";
6
+ function u(t) {
7
+ const r = Array.from(t).reduce(
8
+ (e, a) => e + a.charCodeAt(0),
9
+ 0
10
+ );
11
+ return n[r % n.length];
12
+ }
13
+ const s = {
14
+ gray: "text-gray-v2-500",
15
+ brand: "text-brand-v2-500",
16
+ error: "text-error-v2-500",
17
+ warning: "text-warning-v2-500",
18
+ success: "text-success-v2-500",
19
+ "gray-blue": "text-gray-blue-v2-500",
20
+ "blue-light": "text-blue-light-v2-500",
21
+ blue: "text-blue-v2-500",
22
+ indigo: "text-indigo-v2-500",
23
+ purple: "text-purple-v2-500",
24
+ pink: "text-pink-v2-500",
25
+ orange: "text-orange-v2-500"
26
+ }, b = ({ name: t, size: r = "sm" }) => {
27
+ const e = u(t);
28
+ return /* @__PURE__ */ o(
29
+ c,
30
+ {
31
+ color: e,
32
+ size: r,
33
+ iconLeading: /* @__PURE__ */ o(
34
+ g,
35
+ {
36
+ className: i(
37
+ s[e],
38
+ l[r]
39
+ )
40
+ }
41
+ ),
42
+ children: t
43
+ }
44
+ );
45
+ };
46
+ export {
47
+ b as BadgeTag,
48
+ s as TAG_ICON_COLOR_CLASSES,
49
+ u as getBadgeAutoColor
50
+ };
@@ -0,0 +1,96 @@
1
+ import { ReactNode } from 'react';
2
+ import { BadgeProps } from '../badge/Badge';
3
+ export interface ComboboxSuggestionItem {
4
+ value: string;
5
+ label?: string;
6
+ icon?: ReactNode;
7
+ }
8
+ interface Props {
9
+ id?: string;
10
+ /**
11
+ * Comma-joined string encoding both confirmed items and the current pending
12
+ * input. The last segment (after the final comma) is always the pending input;
13
+ * all preceding segments are confirmed items rendered as badges.
14
+ *
15
+ * Examples:
16
+ * "" — empty field
17
+ * "bob" — "bob" is pending (not yet confirmed)
18
+ * "bob," — "bob" is confirmed, pending is empty
19
+ * "bob,alice" — "bob" confirmed, "alice" pending
20
+ * "bob,alice," — both confirmed, pending is empty
21
+ *
22
+ * The form owns this value. Validation (shape, required, etc.) is the form's
23
+ * responsibility — this component does not validate.
24
+ */
25
+ value: string;
26
+ /**
27
+ * Called on every keystroke and every confirmation.
28
+ *
29
+ * Validation pattern — the form parses the value on each change:
30
+ *
31
+ * ```
32
+ * const segments = value.split(",");
33
+ * const pending = segments.at(-1); // not yet confirmed
34
+ * const confirmed = segments.slice(0, -1).filter(Boolean); // confirmed badges
35
+ * ```
36
+ *
37
+ * To validate item shape on confirmation: check whether the number of
38
+ * confirmed items increased. If the newest confirmed item is invalid, do NOT
39
+ * update state — the value reverts to the previous string, the invalid input
40
+ * stays in the pending field, and the form sets error={true} to show the ring.
41
+ *
42
+ * To validate the whole field (e.g. required, min items): run against
43
+ * `confirmed` on onBlur or onSubmit.
44
+ *
45
+ * The `pending` segment should generally be excluded from submit-time
46
+ * validation — if you want it included, append "," to the value before
47
+ * reading confirmed items.
48
+ *
49
+ */
50
+ onChange: (value: string) => void;
51
+ /** Called when focus leaves the combobox. The pending input is NOT
52
+ * auto-confirmed — it remains in the value string as-is. The form decides
53
+ * whether to validate or consume the pending segment on blur. */
54
+ onBlur?: () => void;
55
+ getSuggestions?: (input: string) => ComboboxSuggestionItem[];
56
+ /** Characters that trigger confirmation of the pending input in addition to
57
+ * Enter. The character is swallowed (not inserted into the field).
58
+ * Default: [",", ";"] */
59
+ delimiter?: string[];
60
+ placeholder?: string;
61
+ error?: boolean;
62
+ disabled?: boolean;
63
+ getBadgeProps?: (value: string) => Partial<Omit<BadgeProps, "children" | "onClose" | "size">>;
64
+ /** Maps a confirmed value to its display label. Use when the stored value
65
+ * (e.g. an ID) differs from what should appear in the badge. */
66
+ getLabel?: (value: string) => string;
67
+ }
68
+ /**
69
+ * Multi-value input that renders confirmed items as badges. The `value` string
70
+ * encodes both confirmed items and the current pending input — see the `value`
71
+ * and `onChange` prop docs for the format.
72
+ *
73
+ * @example
74
+ * const EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
75
+ *
76
+ * const [value, setValue] = useState("");
77
+ * const [error, setError] = useState(false);
78
+ *
79
+ * const handleChange = (next: string) => {
80
+ * const confirmed = next.split(",").slice(0, -1).filter(Boolean);
81
+ * const prev = value.split(",").slice(0, -1).filter(Boolean);
82
+ * if (confirmed.length > prev.length) {
83
+ * const newest = confirmed[confirmed.length - 1];
84
+ * if (!EMAIL.test(newest)) {
85
+ * setError(true);
86
+ * return; // reject — value reverts, invalid input stays pending
87
+ * }
88
+ * }
89
+ * setError(false);
90
+ * setValue(next);
91
+ * };
92
+ *
93
+ * <Combobox value={value} onChange={handleChange} error={error} />
94
+ */
95
+ export declare const Combobox: ({ id, value, onChange, onBlur, getSuggestions, delimiter, placeholder, error, disabled, getBadgeProps, getLabel, }: Props) => import("react/jsx-runtime").JSX.Element;
96
+ export {};
@@ -0,0 +1,206 @@
1
+ import { jsxs as b, Fragment as _, jsx as w } from "react/jsx-runtime";
2
+ import { useId as B, useRef as x, useState as O, useMemo as D, useCallback as G, useEffect as k } from "react";
3
+ import { createPortal as J } from "react-dom";
4
+ import { cn as T } from "../../utils/cn.js";
5
+ import { Badge as Q } from "../badge/Badge.js";
6
+ import { usePopoverCoord as X } from "../popover/use-popover-coord.js";
7
+ const Y = [",", ";"];
8
+ function I(p) {
9
+ return `combobox-option-${p}`;
10
+ }
11
+ const le = ({
12
+ id: p,
13
+ value: E,
14
+ onChange: f,
15
+ onBlur: m,
16
+ getSuggestions: h,
17
+ delimiter: P = Y,
18
+ placeholder: S,
19
+ error: z = !1,
20
+ disabled: g = !1,
21
+ getBadgeProps: M,
22
+ getLabel: C
23
+ }) => {
24
+ const R = B(), y = x(null), j = x(null), u = x(null), [a, c] = O(!1), [l, n] = O(-1), { style: U } = X(y, u, {
25
+ syncWidth: !0
26
+ }), d = D(() => E.split(","), [E]), i = d[d.length - 1], o = D(
27
+ () => d.slice(0, -1).filter(Boolean),
28
+ [d]
29
+ ), r = D(() => h ? h(i).filter(
30
+ (e) => !o.includes(e.value)
31
+ ) : [], [h, i, o]), $ = l >= 0 && r[l] ? I(r[l].value) : void 0, v = G(() => {
32
+ c(!1), n(-1);
33
+ }, []), A = () => {
34
+ const e = i.trim();
35
+ e !== "" && f([...o, e, ""].join(","));
36
+ }, q = (e) => {
37
+ const t = e.target.value;
38
+ f([...o, t].join(",")), c(!0);
39
+ }, H = () => {
40
+ r.length > 0 && c(!0);
41
+ }, V = () => {
42
+ v(), m == null || m();
43
+ }, L = (e) => {
44
+ f([...o, e.value, ""].join(",")), n(-1);
45
+ }, N = (e) => {
46
+ const t = o.filter((s) => s !== e);
47
+ f([...t, i].join(","));
48
+ }, W = (e) => {
49
+ if (e.key === "ArrowDown") {
50
+ if (e.preventDefault(), !a && r.length > 0) {
51
+ c(!0), e.altKey || n(0);
52
+ return;
53
+ }
54
+ e.altKey || n((t) => Math.min(t + 1, r.length - 1));
55
+ return;
56
+ }
57
+ if (e.key === "ArrowUp") {
58
+ if (e.preventDefault(), e.altKey) {
59
+ v();
60
+ return;
61
+ }
62
+ n((t) => Math.max(t - 1, -1));
63
+ return;
64
+ }
65
+ if (e.key === "Home" && a) {
66
+ e.preventDefault(), n(0);
67
+ return;
68
+ }
69
+ if (e.key === "End" && a) {
70
+ e.preventDefault(), n(r.length - 1);
71
+ return;
72
+ }
73
+ if (e.key === "ArrowLeft" || e.key === "ArrowRight" || e.key === "Delete") {
74
+ n(-1);
75
+ return;
76
+ }
77
+ if (P.includes(e.key)) {
78
+ e.preventDefault(), A();
79
+ return;
80
+ }
81
+ if (e.key === "Enter") {
82
+ if (e.preventDefault(), a && l >= 0) {
83
+ L(r[l]);
84
+ return;
85
+ }
86
+ A();
87
+ return;
88
+ }
89
+ if (e.key === "Escape") {
90
+ a && (e.stopPropagation(), v());
91
+ return;
92
+ }
93
+ if (e.key === "Tab") {
94
+ v();
95
+ return;
96
+ }
97
+ e.key === "Backspace" && i === "" && l < 0 && o.length > 0 && N(o[o.length - 1]);
98
+ };
99
+ return k(() => {
100
+ if (!a) return;
101
+ const e = (t) => {
102
+ var K, F;
103
+ const { target: s } = t;
104
+ s instanceof Node && ((K = y.current) != null && K.contains(s) || (F = u.current) != null && F.contains(s) || (c(!1), n(-1)));
105
+ };
106
+ return document.addEventListener("mousedown", e), () => document.removeEventListener("mousedown", e);
107
+ }, [a]), k(() => {
108
+ n(-1);
109
+ }, [r]), k(() => {
110
+ if (l < 0 || !u.current) return;
111
+ const e = r[l];
112
+ if (!e) return;
113
+ const t = u.current.querySelector(
114
+ `[id="${I(e.value)}"]`
115
+ );
116
+ t == null || t.scrollIntoView({ block: "nearest" });
117
+ }, [l, r]), /* @__PURE__ */ b(_, { children: [
118
+ /* @__PURE__ */ b(
119
+ "div",
120
+ {
121
+ ref: y,
122
+ onMouseDown: (e) => {
123
+ var t;
124
+ e.target === e.currentTarget && (e.preventDefault(), (t = j.current) == null || t.focus());
125
+ },
126
+ className: T(
127
+ "flex flex-wrap items-center gap-1.5 px-3 py-1.5 min-h-10 bg-white rounded-lg border shadow-xs transition-all outline outline-transparent",
128
+ g ? "bg-gray-v2-50 border-gray-v2-300" : z ? "border-error-v2-300 focus-within:border-error-v2 focus-within:outline-error-v2" : "border-gray-v2-300 focus-within:border-brand-v2 focus-within:outline-brand-v2"
129
+ ),
130
+ children: [
131
+ o.map((e) => /* @__PURE__ */ w(
132
+ Q,
133
+ {
134
+ ...M ? M(e) : void 0,
135
+ size: "sm",
136
+ onClose: g ? void 0 : () => N(e),
137
+ className: "max-w-48",
138
+ children: C ? C(e) : e
139
+ },
140
+ e
141
+ )),
142
+ /* @__PURE__ */ w(
143
+ "input",
144
+ {
145
+ id: p,
146
+ role: "combobox",
147
+ "aria-expanded": a,
148
+ "aria-controls": R,
149
+ "aria-autocomplete": "list",
150
+ "aria-activedescendant": $,
151
+ ref: j,
152
+ autoComplete: "off",
153
+ value: i,
154
+ onChange: q,
155
+ onKeyDown: W,
156
+ onFocus: H,
157
+ onBlur: V,
158
+ placeholder: o.length === 0 ? S : void 0,
159
+ disabled: g,
160
+ className: "flex-1 min-w-20 bg-transparent outline-none text-sm text-gray-v2-900 placeholder:text-gray-v2-500 disabled:cursor-not-allowed"
161
+ }
162
+ )
163
+ ]
164
+ }
165
+ ),
166
+ a && r.length > 0 ? J(
167
+ /* @__PURE__ */ w(
168
+ "div",
169
+ {
170
+ ref: u,
171
+ role: "listbox",
172
+ id: R,
173
+ style: U,
174
+ className: "z-20 rounded-lg border border-gray-v2-200 bg-white p-1 shadow-lg transition-all max-h-[40vh] overflow-auto",
175
+ children: r.map((e, t) => /* @__PURE__ */ b(
176
+ "button",
177
+ {
178
+ type: "button",
179
+ role: "option",
180
+ "aria-selected": "false",
181
+ tabIndex: -1,
182
+ id: I(e.value),
183
+ className: T(
184
+ "flex w-full items-center gap-2 rounded-md p-2 pr-2.5 text-left text-sm font-semibold text-gray-v2-700",
185
+ t === l ? "bg-gray-v2-100" : "hover:bg-gray-v2-50"
186
+ ),
187
+ onMouseDown: (s) => s.preventDefault(),
188
+ onClick: () => L(e),
189
+ onMouseEnter: () => n(t),
190
+ onMouseLeave: () => n(-1),
191
+ children: [
192
+ e.icon ? e.icon : null,
193
+ e.label ? e.label : e.value
194
+ ]
195
+ },
196
+ e.value
197
+ ))
198
+ }
199
+ ),
200
+ document.body
201
+ ) : null
202
+ ] });
203
+ };
204
+ export {
205
+ le as Combobox
206
+ };
@@ -0,0 +1,17 @@
1
+ interface Options {
2
+ pattern: RegExp;
3
+ initialValue?: string;
4
+ }
5
+ interface Result {
6
+ value: string;
7
+ error: boolean;
8
+ items: string[];
9
+ onChange: (next: string) => void;
10
+ reset: () => void;
11
+ }
12
+ /**
13
+ * Validates each confirmed item against a pattern and rejects invalid ones.
14
+ * Invalid confirmation leaves the input text in place and sets error=true.
15
+ */
16
+ export declare function useValidatedCombobox({ pattern, initialValue, }: Options): Result;
17
+ export {};
@@ -0,0 +1,23 @@
1
+ import { useState as c, useCallback as g } from "react";
2
+ function b({
3
+ pattern: l,
4
+ initialValue: t = ""
5
+ }) {
6
+ const [s, n] = c(t), [f, o] = c(!1), i = (e) => {
7
+ const r = e.split(",").slice(0, -1).filter(Boolean), m = s.split(",").slice(0, -1).filter(Boolean);
8
+ if (r.length > m.length) {
9
+ const p = r[r.length - 1];
10
+ if (!l.test(p)) {
11
+ o(!0);
12
+ return;
13
+ }
14
+ }
15
+ o(!1), n(e);
16
+ }, a = g(() => {
17
+ n(t), o(!1);
18
+ }, [t]), u = s.split(",").filter((e) => l.test(e));
19
+ return { value: s, error: f, items: u, onChange: i, reset: a };
20
+ }
21
+ export {
22
+ b as useValidatedCombobox
23
+ };
@@ -0,0 +1,9 @@
1
+ import { ReactNode } from 'react';
2
+ interface Props {
3
+ title: string;
4
+ supportingText?: string;
5
+ buttons?: ReactNode;
6
+ className?: string;
7
+ }
8
+ export declare const ErrorState: ({ title, supportingText, buttons, className, }: Props) => import("react/jsx-runtime").JSX.Element;
9
+ export {};