smartsell-ui-kit 0.0.5 → 0.1.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/index.cjs CHANGED
@@ -22,8 +22,9 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  Button: () => Button,
24
24
  Card: () => Card,
25
+ Checkbox: () => Checkbox,
25
26
  ComposableModal: () => ComposableModal,
26
- Field: () => Field,
27
+ DatePicker: () => DatePicker,
27
28
  Input: () => Input,
28
29
  Modal: () => Modal,
29
30
  Select: () => Select,
@@ -35,42 +36,147 @@ module.exports = __toCommonJS(index_exports);
35
36
 
36
37
  // src/components/Button.tsx
37
38
  var import_react2 = require("react");
38
- var import_smartsell_sales_sdk2 = require("smartsell-sales-sdk");
39
+
40
+ // src/components/controlSize.ts
41
+ var CONTROL_METRICS_BY_SIZE = {
42
+ sm: {
43
+ height: 30,
44
+ fontSize: 13,
45
+ lineHeight: "16px",
46
+ paddingX: 12,
47
+ paddingY: 6,
48
+ buttonPaddingX: 14,
49
+ selectPaddingRight: 40,
50
+ selectIndicatorOffset: 12
51
+ },
52
+ md: {
53
+ height: 38,
54
+ fontSize: 14,
55
+ lineHeight: "18px",
56
+ paddingX: 16,
57
+ paddingY: 9,
58
+ buttonPaddingX: 18,
59
+ selectPaddingRight: 44,
60
+ selectIndicatorOffset: 16
61
+ },
62
+ lg: {
63
+ height: 46,
64
+ fontSize: 15,
65
+ lineHeight: "18px",
66
+ paddingX: 18,
67
+ paddingY: 13,
68
+ buttonPaddingX: 20,
69
+ selectPaddingRight: 48,
70
+ selectIndicatorOffset: 18
71
+ }
72
+ };
73
+ var getControlMetrics = (size) => CONTROL_METRICS_BY_SIZE[size];
74
+ var getTextInputPadding = (size, options = {}) => {
75
+ var _a, _b;
76
+ const metrics = getControlMetrics(size);
77
+ const leadingInset = (_a = options.leadingInset) != null ? _a : 0;
78
+ const trailingInset = (_b = options.trailingInset) != null ? _b : 0;
79
+ return `${metrics.paddingY}px ${metrics.paddingX + trailingInset}px ${metrics.paddingY}px ${metrics.paddingX + leadingInset}px`;
80
+ };
81
+ var getSelectControlPadding = (size) => {
82
+ const metrics = getControlMetrics(size);
83
+ return `${metrics.paddingY}px ${metrics.selectPaddingRight}px ${metrics.paddingY}px ${metrics.paddingX}px`;
84
+ };
85
+ var getSelectIndicatorOffset = (size) => getControlMetrics(size).selectIndicatorOffset;
86
+ var getButtonSizeStyles = (size) => {
87
+ const metrics = getControlMetrics(size);
88
+ return {
89
+ boxSizing: "border-box",
90
+ height: metrics.height,
91
+ padding: `0 ${metrics.buttonPaddingX}px`,
92
+ fontSize: metrics.fontSize,
93
+ lineHeight: metrics.lineHeight
94
+ };
95
+ };
96
+
97
+ // src/color.ts
98
+ function withAlpha(hex, alpha) {
99
+ const normalized = hex.replace("#", "").trim();
100
+ const value = normalized.length === 3 ? normalized.split("").map((char) => char + char).join("") : normalized;
101
+ const red = Number.parseInt(value.slice(0, 2), 16);
102
+ const green = Number.parseInt(value.slice(2, 4), 16);
103
+ const blue = Number.parseInt(value.slice(4, 6), 16);
104
+ return `rgba(${red}, ${green}, ${blue}, ${alpha})`;
105
+ }
39
106
 
40
107
  // src/theme.ts
41
108
  var import_react = require("react");
42
- var import_smartsell_sales_sdk = require("smartsell-sales-sdk");
43
- function useUiKitTheme(sdk) {
44
- const { theme } = (0, import_smartsell_sales_sdk.useSalesTheme)(sdk);
109
+ var import_smartsell_theme = require("smartsell-theme");
110
+ var DEFAULT_THEME_COLORS = {
111
+ primary: "#111827",
112
+ secondary: "#6b7280",
113
+ background: "#f8fafc",
114
+ surface: "#ffffff",
115
+ text: "#111827",
116
+ textLight: "#6b7280",
117
+ positive: "#16a34a",
118
+ negative: "#dc2626",
119
+ warning: "#d97706",
120
+ info: "#2563eb",
121
+ onPrimary: "#ffffff"
122
+ };
123
+ function normalizeUiKitTheme(theme) {
124
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v;
125
+ const colors = theme.colors;
126
+ const primary = (_a = colors.primary) != null ? _a : DEFAULT_THEME_COLORS.primary;
127
+ const background = (_b = colors.background) != null ? _b : DEFAULT_THEME_COLORS.background;
128
+ const surface = (_c = colors.surface) != null ? _c : background;
129
+ const text = (_f = (_e = (_d = colors.text) != null ? _d : colors.onSurface) != null ? _e : colors.onBackground) != null ? _f : DEFAULT_THEME_COLORS.text;
130
+ const textLight = (_h = (_g = colors.textLight) != null ? _g : colors.onSurfaceLight) != null ? _h : text;
131
+ const normalizedColors = {
132
+ ...theme.colors,
133
+ primary,
134
+ primaryLight: (_i = colors.primaryLight) != null ? _i : primary,
135
+ primaryDark: (_j = colors.primaryDark) != null ? _j : primary,
136
+ secondary: (_k = colors.secondary) != null ? _k : DEFAULT_THEME_COLORS.secondary,
137
+ background,
138
+ backgroundLight: (_l = colors.backgroundLight) != null ? _l : background,
139
+ surface,
140
+ text,
141
+ textLight,
142
+ positive: (_m = colors.positive) != null ? _m : DEFAULT_THEME_COLORS.positive,
143
+ negative: (_n = colors.negative) != null ? _n : DEFAULT_THEME_COLORS.negative,
144
+ warning: (_o = colors.warning) != null ? _o : DEFAULT_THEME_COLORS.warning,
145
+ info: (_p = colors.info) != null ? _p : DEFAULT_THEME_COLORS.info,
146
+ onPrimary: (_q = colors.onPrimary) != null ? _q : DEFAULT_THEME_COLORS.onPrimary,
147
+ onSecondary: (_r = colors.onSecondary) != null ? _r : text,
148
+ onBackground: (_s = colors.onBackground) != null ? _s : text,
149
+ onSurface: (_t = colors.onSurface) != null ? _t : text,
150
+ onSurfaceLight: (_u = colors.onSurfaceLight) != null ? _u : textLight,
151
+ onWarning: (_v = colors.onWarning) != null ? _v : text
152
+ };
153
+ return {
154
+ ...theme,
155
+ colors: normalizedColors,
156
+ color: normalizedColors
157
+ };
158
+ }
159
+ function useUiKitTheme() {
160
+ const activeTheme = (0, import_smartsell_theme.useThemeValue)();
45
161
  return (0, import_react.useMemo)(() => {
162
+ const theme = normalizeUiKitTheme(activeTheme);
46
163
  return {
47
164
  theme,
48
- borderSoft: (0, import_smartsell_sales_sdk.withAlpha)(theme.colors.onSurface, 0.1),
49
- borderStrong: (0, import_smartsell_sales_sdk.withAlpha)(theme.colors.onSurface, 0.16),
50
- surfaceMuted: (0, import_smartsell_sales_sdk.withAlpha)(theme.colors.onSurface, 0.04),
51
- surfaceRaised: (0, import_smartsell_sales_sdk.withAlpha)(theme.colors.surface, 0.94),
52
- overlay: (0, import_smartsell_sales_sdk.withAlpha)("#030712", 0.62),
53
- focusRing: (0, import_smartsell_sales_sdk.withAlpha)(theme.colors.primary, 0.22),
54
- mutedText: (0, import_smartsell_sales_sdk.withAlpha)(theme.colors.onSurface, 0.72)
165
+ borderSoft: withAlpha(theme.colors.onSurface, 0.1),
166
+ borderStrong: withAlpha(theme.colors.onSurface, 0.16),
167
+ surfaceMuted: withAlpha(theme.colors.onSurface, 0.04),
168
+ surfaceRaised: withAlpha(theme.colors.surface, 0.94),
169
+ overlay: withAlpha("#030712", 0.62),
170
+ focusRing: withAlpha(theme.colors.primary, 0.22),
171
+ mutedText: withAlpha(theme.colors.onSurface, 0.72)
55
172
  };
56
- }, [theme]);
173
+ }, [activeTheme]);
57
174
  }
58
175
 
59
176
  // src/components/Button.tsx
60
177
  var import_jsx_runtime = require("react/jsx-runtime");
61
- var getSizeStyles = (size) => {
62
- if (size === "sm") {
63
- return { minHeight: 38, padding: "0 14px", fontSize: 13 };
64
- }
65
- if (size === "lg") {
66
- return { minHeight: 50, padding: "0 20px", fontSize: 15 };
67
- }
68
- return { minHeight: 44, padding: "0 18px", fontSize: 14 };
69
- };
70
178
  function Button({
71
- sdk,
72
179
  variant = "solid",
73
- tone = "primary",
74
180
  size = "md",
75
181
  fullWidth = false,
76
182
  leadingIcon,
@@ -91,64 +197,42 @@ function Button({
91
197
  onTouchCancel,
92
198
  ...rest
93
199
  }) {
94
- const { theme } = useUiKitTheme(sdk);
200
+ const { theme } = useUiKitTheme();
95
201
  const [isHovered, setIsHovered] = (0, import_react2.useState)(false);
96
202
  const [isPressed, setIsPressed] = (0, import_react2.useState)(false);
97
- const palette = {
98
- primary: {
99
- background: theme.colors.primary,
100
- foreground: theme.colors.onPrimary,
101
- border: (0, import_smartsell_sales_sdk2.withAlpha)(theme.colors.primary, 0.3)
102
- },
103
- secondary: {
104
- background: theme.colors.secondary,
105
- foreground: theme.colors.onSecondary,
106
- border: (0, import_smartsell_sales_sdk2.withAlpha)(theme.colors.secondary, 0.3)
107
- },
108
- neutral: {
109
- background: theme.colors.surface,
110
- foreground: theme.colors.onSurface,
111
- border: (0, import_smartsell_sales_sdk2.withAlpha)(theme.colors.onSurface, 0.16)
112
- },
113
- danger: {
114
- background: theme.colors.negative,
115
- foreground: "#ffffff",
116
- border: (0, import_smartsell_sales_sdk2.withAlpha)(theme.colors.negative, 0.32)
117
- }
118
- }[tone];
119
203
  const variantStyles = {
120
204
  solid: {
121
- backgroundColor: palette.background,
122
- borderColor: palette.border,
123
- color: palette.foreground
205
+ backgroundColor: theme.colors.primary,
206
+ borderColor: withAlpha(theme.colors.primary, 0.5),
207
+ color: theme.colors.onPrimary
124
208
  },
125
209
  outline: {
126
210
  backgroundColor: "transparent",
127
- borderColor: palette.border,
128
- color: tone === "neutral" ? palette.foreground : palette.background
211
+ borderColor: withAlpha(theme.colors.text, 0.5),
212
+ color: theme.colors.onSurface
129
213
  },
130
214
  ghost: {
131
215
  backgroundColor: "transparent",
132
216
  borderColor: "transparent",
133
- color: tone === "neutral" ? palette.foreground : palette.background
217
+ color: theme.colors.onSurface
134
218
  }
135
219
  };
136
- const interactiveColor = tone === "neutral" ? palette.foreground : palette.background;
220
+ const interactiveColor = variant === "solid" ? theme.colors.primary : theme.colors.onSurface;
137
221
  const isInteractive = !disabled;
138
222
  const interactionStyles = {
139
223
  solid: {
140
224
  transform: isPressed ? "translateY(1px) scale(0.985)" : isHovered ? "translateY(-1px)" : "translateY(0)",
141
- boxShadow: isPressed ? `0 8px 16px ${(0, import_smartsell_sales_sdk2.withAlpha)(palette.background, 0.2)}` : isHovered ? `0 18px 36px ${(0, import_smartsell_sales_sdk2.withAlpha)(palette.background, 0.26)}` : `0 14px 28px ${(0, import_smartsell_sales_sdk2.withAlpha)(palette.background, 0.18)}`,
142
- filter: isPressed ? "brightness(0.95)" : isHovered ? "brightness(1.03)" : void 0
225
+ boxShadow: "none",
226
+ filter: void 0
143
227
  },
144
228
  outline: {
145
229
  transform: isPressed ? "translateY(1px) scale(0.985)" : isHovered ? "translateY(-1px)" : "translateY(0)",
146
- backgroundColor: isPressed ? (0, import_smartsell_sales_sdk2.withAlpha)(interactiveColor, 0.14) : isHovered ? (0, import_smartsell_sales_sdk2.withAlpha)(interactiveColor, 0.08) : "transparent",
147
- boxShadow: isPressed ? `0 6px 12px ${(0, import_smartsell_sales_sdk2.withAlpha)(theme.colors.onSurface, 0.08)}` : isHovered ? `0 12px 24px ${(0, import_smartsell_sales_sdk2.withAlpha)(theme.colors.onSurface, 0.08)}` : "none"
230
+ backgroundColor: isPressed ? withAlpha(interactiveColor, 0.14) : isHovered ? withAlpha(interactiveColor, 0.08) : "transparent",
231
+ boxShadow: "none"
148
232
  },
149
233
  ghost: {
150
234
  transform: isPressed ? "translateY(1px) scale(0.985)" : isHovered ? "translateY(-1px)" : "translateY(0)",
151
- backgroundColor: isPressed ? (0, import_smartsell_sales_sdk2.withAlpha)(interactiveColor, 0.12) : isHovered ? (0, import_smartsell_sales_sdk2.withAlpha)(interactiveColor, 0.06) : "transparent",
235
+ backgroundColor: isPressed ? withAlpha(interactiveColor, 0.12) : isHovered ? withAlpha(interactiveColor, 0.06) : "transparent",
152
236
  boxShadow: "none"
153
237
  }
154
238
  };
@@ -220,10 +304,9 @@ function Button({
220
304
  cursor: disabled ? "not-allowed" : "pointer",
221
305
  fontWeight: 700,
222
306
  letterSpacing: "0.01em",
223
- lineHeight: 1,
224
307
  opacity: disabled ? 0.58 : 1,
225
- transition: "transform 140ms ease, opacity 160ms ease, background-color 160ms ease, box-shadow 160ms ease, filter 160ms ease",
226
- ...getSizeStyles(size),
308
+ transition: "transform 140ms ease, opacity 160ms ease, background-color 160ms ease",
309
+ ...getButtonSizeStyles(size),
227
310
  ...variantStyles[variant],
228
311
  ...isInteractive ? interactionStyles[variant] : void 0,
229
312
  ...style
@@ -238,45 +321,26 @@ function Button({
238
321
  }
239
322
 
240
323
  // src/components/Card.tsx
241
- var import_smartsell_sales_sdk3 = require("smartsell-sales-sdk");
242
324
  var import_jsx_runtime2 = require("react/jsx-runtime");
243
- var getToneStyles = (tone, sdk) => {
244
- const { theme } = useUiKitTheme(sdk);
245
- const accent = {
246
- default: theme.colors.primary,
247
- primary: theme.colors.primary,
248
- secondary: theme.colors.secondary,
249
- warning: theme.colors.warning
250
- }[tone];
251
- return {
252
- accent,
253
- background: `linear-gradient(160deg, ${(0, import_smartsell_sales_sdk3.withAlpha)(theme.colors.surface, 0.98)}, ${(0, import_smartsell_sales_sdk3.withAlpha)(accent, 0.08)})`,
254
- border: (0, import_smartsell_sales_sdk3.withAlpha)(accent, 0.22)
255
- };
256
- };
257
325
  function Card({
258
- sdk,
259
326
  children,
260
327
  eyebrow,
261
328
  title,
262
329
  action,
263
- tone = "default",
264
330
  padded = true,
265
331
  style,
266
332
  ...rest
267
333
  }) {
268
- const { theme, mutedText } = useUiKitTheme(sdk);
269
- const toneStyles = getToneStyles(tone, sdk);
334
+ const { theme } = useUiKitTheme();
270
335
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
271
336
  "div",
272
337
  {
273
338
  ...rest,
274
339
  style: {
275
340
  borderRadius: 28,
276
- border: `1px solid ${toneStyles.border}`,
277
- background: toneStyles.background,
278
- color: theme.colors.onSurface,
279
- boxShadow: `0 24px 48px ${(0, import_smartsell_sales_sdk3.withAlpha)(theme.colors.onSurface, 0.06)}`,
341
+ border: `1px solid ${withAlpha(theme.colors.text, 0.5)}`,
342
+ background: `${withAlpha(theme.colors.background, 0.7)}`,
343
+ color: theme.colors.text,
280
344
  padding: padded ? 24 : void 0,
281
345
  ...style
282
346
  },
@@ -297,7 +361,7 @@ function Card({
297
361
  "span",
298
362
  {
299
363
  style: {
300
- color: toneStyles.accent,
364
+ color: theme.colors.textLight,
301
365
  fontSize: 12,
302
366
  fontWeight: 800,
303
367
  letterSpacing: "0.16em",
@@ -310,7 +374,7 @@ function Card({
310
374
  "div",
311
375
  {
312
376
  style: {
313
- color: theme.colors.onSurface,
377
+ color: theme.colors.text,
314
378
  fontSize: 24,
315
379
  fontWeight: 700,
316
380
  lineHeight: 1.2
@@ -319,7 +383,7 @@ function Card({
319
383
  }
320
384
  ) : null
321
385
  ] }),
322
- action ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { color: mutedText }, children: action }) : null
386
+ action ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { color: theme.colors.text }, children: action }) : null
323
387
  ]
324
388
  }
325
389
  ),
@@ -329,13 +393,257 @@ function Card({
329
393
  );
330
394
  }
331
395
 
396
+ // src/components/Checkbox.tsx
397
+ var import_react3 = require("react");
398
+ var import_jsx_runtime3 = require("react/jsx-runtime");
399
+ var CHECKBOX_SIZE_BY_CONTROL_SIZE = {
400
+ sm: 18,
401
+ md: 20,
402
+ lg: 22
403
+ };
404
+ var getCheckboxSize = (controlSize) => CHECKBOX_SIZE_BY_CONTROL_SIZE[controlSize];
405
+ function CheckIcon({ size, color }) {
406
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { width: size, height: size, viewBox: "0 0 20 20", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
407
+ "path",
408
+ {
409
+ d: "M5.75 10.25L8.5 13l5.75-6",
410
+ stroke: color,
411
+ strokeWidth: "1.8",
412
+ strokeLinecap: "round",
413
+ strokeLinejoin: "round"
414
+ }
415
+ ) });
416
+ }
417
+ function MixedIcon({ size, color }) {
418
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { width: size, height: size, viewBox: "0 0 20 20", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M5.5 10H14.5", stroke: color, strokeWidth: "1.8", strokeLinecap: "round" }) });
419
+ }
420
+ function CheckboxIndicator({
421
+ checked,
422
+ indeterminate = false,
423
+ disabled = false,
424
+ size = 20,
425
+ checkedColor,
426
+ iconColor,
427
+ uncheckedBorderColor,
428
+ uncheckedBackgroundColor = "transparent",
429
+ style
430
+ }) {
431
+ const isActive = checked || indeterminate;
432
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
433
+ "span",
434
+ {
435
+ "aria-hidden": "true",
436
+ style: {
437
+ width: size,
438
+ height: size,
439
+ borderRadius: 999,
440
+ boxSizing: "border-box",
441
+ flexShrink: 0,
442
+ display: "inline-flex",
443
+ alignItems: "center",
444
+ justifyContent: "center",
445
+ border: `1.5px solid ${isActive ? checkedColor : uncheckedBorderColor}`,
446
+ backgroundColor: isActive ? checkedColor : uncheckedBackgroundColor,
447
+ opacity: disabled ? 0.45 : 1,
448
+ transition: "background-color 140ms ease, border-color 140ms ease, opacity 140ms ease",
449
+ ...style
450
+ },
451
+ children: [
452
+ indeterminate ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MixedIcon, { size: Math.max(10, size - 6), color: iconColor }) : null,
453
+ !indeterminate && checked ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CheckIcon, { size: Math.max(10, size - 6), color: iconColor }) : null
454
+ ]
455
+ }
456
+ );
457
+ }
458
+ function Checkbox({
459
+ label,
460
+ hint,
461
+ error,
462
+ controlSize = "md",
463
+ id,
464
+ checked,
465
+ defaultChecked,
466
+ disabled,
467
+ indeterminate = false,
468
+ required,
469
+ className,
470
+ style,
471
+ onChange,
472
+ onFocus,
473
+ onBlur,
474
+ ...rest
475
+ }) {
476
+ const { theme, mutedText } = useUiKitTheme();
477
+ const controlMetrics = getControlMetrics(controlSize);
478
+ const indicatorSize = getCheckboxSize(controlSize);
479
+ const resolvedId = id != null ? id : `ss-checkbox-${(0, import_react3.useId)().replace(/:/g, "")}`;
480
+ const inputRef = (0, import_react3.useRef)(null);
481
+ const [isFocused, setIsFocused] = (0, import_react3.useState)(false);
482
+ const [internalChecked, setInternalChecked] = (0, import_react3.useState)(Boolean(defaultChecked));
483
+ const isControlled = checked !== void 0;
484
+ const resolvedChecked = isControlled ? Boolean(checked) : internalChecked;
485
+ (0, import_react3.useEffect)(() => {
486
+ if (!inputRef.current) {
487
+ return;
488
+ }
489
+ inputRef.current.indeterminate = indeterminate;
490
+ }, [indeterminate, resolvedChecked]);
491
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
492
+ "div",
493
+ {
494
+ className,
495
+ style: {
496
+ width: "100%",
497
+ display: "flex",
498
+ flexDirection: "column",
499
+ gap: 6,
500
+ ...style
501
+ },
502
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
503
+ "label",
504
+ {
505
+ htmlFor: resolvedId,
506
+ style: {
507
+ display: "inline-flex",
508
+ alignItems: label || hint ? "flex-start" : "center",
509
+ gap: 10,
510
+ width: label || hint || error ? "100%" : "auto",
511
+ cursor: disabled ? "not-allowed" : "pointer",
512
+ opacity: disabled ? 0.7 : 1,
513
+ userSelect: "none"
514
+ },
515
+ children: [
516
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
517
+ "span",
518
+ {
519
+ style: {
520
+ position: "relative",
521
+ display: "inline-flex",
522
+ alignItems: "center",
523
+ justifyContent: "center",
524
+ flexShrink: 0,
525
+ padding: 2,
526
+ marginTop: label || hint ? 1 : 0,
527
+ borderRadius: 999,
528
+ boxShadow: isFocused ? `0 0 0 3px ${withAlpha(theme.colors.primary, 0.16)}` : "none",
529
+ transition: "box-shadow 140ms ease"
530
+ },
531
+ children: [
532
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
533
+ "input",
534
+ {
535
+ ...rest,
536
+ ref: inputRef,
537
+ id: resolvedId,
538
+ type: "checkbox",
539
+ checked: resolvedChecked,
540
+ disabled,
541
+ required,
542
+ onChange: (event) => {
543
+ if (!isControlled) {
544
+ setInternalChecked(event.currentTarget.checked);
545
+ }
546
+ onChange == null ? void 0 : onChange(event);
547
+ },
548
+ onFocus: (event) => {
549
+ setIsFocused(true);
550
+ onFocus == null ? void 0 : onFocus(event);
551
+ },
552
+ onBlur: (event) => {
553
+ setIsFocused(false);
554
+ onBlur == null ? void 0 : onBlur(event);
555
+ },
556
+ style: {
557
+ position: "absolute",
558
+ inset: 0,
559
+ width: "100%",
560
+ height: "100%",
561
+ margin: 0,
562
+ opacity: 0,
563
+ cursor: disabled ? "not-allowed" : "pointer"
564
+ }
565
+ }
566
+ ),
567
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
568
+ CheckboxIndicator,
569
+ {
570
+ checked: resolvedChecked,
571
+ indeterminate,
572
+ disabled,
573
+ size: indicatorSize,
574
+ checkedColor: theme.colors.primary,
575
+ iconColor: theme.colors.onPrimary,
576
+ uncheckedBorderColor: withAlpha(theme.colors.onSurface, 0.24),
577
+ uncheckedBackgroundColor: theme.colors.surface
578
+ }
579
+ )
580
+ ]
581
+ }
582
+ ),
583
+ label || hint || error ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
584
+ "span",
585
+ {
586
+ style: {
587
+ display: "flex",
588
+ flexDirection: "column",
589
+ gap: 4,
590
+ minWidth: 0
591
+ },
592
+ children: [
593
+ label ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
594
+ "span",
595
+ {
596
+ style: {
597
+ color: theme.colors.onSurface,
598
+ fontSize: controlMetrics.fontSize,
599
+ lineHeight: controlMetrics.lineHeight,
600
+ fontWeight: 600
601
+ },
602
+ children: [
603
+ label,
604
+ required ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { color: theme.colors.negative, marginLeft: 4 }, children: "*" }) : null
605
+ ]
606
+ }
607
+ ) : null,
608
+ hint ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
609
+ "span",
610
+ {
611
+ style: {
612
+ color: mutedText,
613
+ fontSize: 13,
614
+ lineHeight: 1.45
615
+ },
616
+ children: hint
617
+ }
618
+ ) : null,
619
+ error ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
620
+ "span",
621
+ {
622
+ style: {
623
+ color: theme.colors.negative,
624
+ fontSize: 13,
625
+ lineHeight: 1.45,
626
+ fontWeight: 600
627
+ },
628
+ children: error
629
+ }
630
+ ) : null
631
+ ]
632
+ }
633
+ ) : null
634
+ ]
635
+ }
636
+ )
637
+ }
638
+ );
639
+ }
640
+
332
641
  // src/components/ComposableModal.tsx
333
- var import_react4 = require("react");
334
- var import_smartsell_sales_sdk4 = require("smartsell-sales-sdk");
642
+ var import_react5 = require("react");
335
643
 
336
644
  // src/components/Modal.tsx
337
- var import_react3 = require("react");
338
- var import_jsx_runtime3 = require("react/jsx-runtime");
645
+ var import_react4 = require("react");
646
+ var import_jsx_runtime4 = require("react/jsx-runtime");
339
647
  var defaultThemeColors = {
340
648
  background: "#f8fafc",
341
649
  surface: "#ffffff",
@@ -348,13 +656,16 @@ var defaultThemeColors = {
348
656
  negative: "#dc2626",
349
657
  warning: "#d97706"
350
658
  };
351
- var withAlpha4 = (hex, alpha) => {
352
- const normalized = hex.replace("#", "").trim();
353
- const value = normalized.length === 3 ? normalized.split("").map((char) => char + char).join("") : normalized;
354
- const red = Number.parseInt(value.slice(0, 2), 16);
355
- const green = Number.parseInt(value.slice(2, 4), 16);
356
- const blue = Number.parseInt(value.slice(4, 6), 16);
357
- return `rgba(${red}, ${green}, ${blue}, ${alpha})`;
659
+ var srOnlyStyle = {
660
+ position: "absolute",
661
+ width: 1,
662
+ height: 1,
663
+ padding: 0,
664
+ margin: -1,
665
+ overflow: "hidden",
666
+ clip: "rect(0, 0, 0, 0)",
667
+ whiteSpace: "nowrap",
668
+ border: 0
358
669
  };
359
670
  var isUiKitThemeTokens = (theme) => {
360
671
  return Boolean(theme && typeof theme === "object" && "theme" in theme);
@@ -368,12 +679,12 @@ var resolveTheme = (theme) => {
368
679
  };
369
680
  return {
370
681
  colors,
371
- borderSoft: isUiKitThemeTokens(theme) ? theme.borderSoft : withAlpha4(colors.onSurface, 0.1),
372
- borderStrong: isUiKitThemeTokens(theme) ? theme.borderStrong : withAlpha4(colors.onSurface, 0.16),
373
- surfaceMuted: isUiKitThemeTokens(theme) ? theme.surfaceMuted : withAlpha4(colors.onSurface, 0.04),
374
- surfaceRaised: isUiKitThemeTokens(theme) ? theme.surfaceRaised : withAlpha4(colors.surface, 0.94),
375
- overlay: isUiKitThemeTokens(theme) ? theme.overlay : withAlpha4("#030712", 0.62),
376
- mutedText: isUiKitThemeTokens(theme) ? theme.mutedText : withAlpha4(colors.onSurface, 0.72)
682
+ borderSoft: isUiKitThemeTokens(theme) ? theme.borderSoft : withAlpha(colors.onSurface, 0.1),
683
+ borderStrong: isUiKitThemeTokens(theme) ? theme.borderStrong : withAlpha(colors.onSurface, 0.16),
684
+ surfaceMuted: isUiKitThemeTokens(theme) ? theme.surfaceMuted : withAlpha(colors.onSurface, 0.04),
685
+ surfaceRaised: isUiKitThemeTokens(theme) ? theme.surfaceRaised : withAlpha(colors.surface, 0.94),
686
+ overlay: isUiKitThemeTokens(theme) ? theme.overlay : withAlpha("#030712", 0.62),
687
+ mutedText: isUiKitThemeTokens(theme) ? theme.mutedText : withAlpha(colors.onSurface, 0.72)
377
688
  };
378
689
  };
379
690
  var normalizeWidth = (maxWidth) => {
@@ -390,58 +701,122 @@ var isLikelyMobileViewport = () => {
390
701
  navigator.userAgent
391
702
  );
392
703
  };
704
+ var createTitleId = (title) => {
705
+ if (typeof title === "string" || typeof title === "number") {
706
+ const normalized = String(title).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
707
+ return `smartsell-modal-${normalized || "dialog"}`;
708
+ }
709
+ return "smartsell-modal-dialog";
710
+ };
393
711
  var isLegacyModalProps = (props) => {
394
712
  return "showModal" in props;
395
713
  };
396
- function LegacyActionButton({
397
- label,
398
- onClick,
399
- disabled,
400
- type = "contained",
401
- theme,
402
- style
714
+ var normalizeLegacyAction = (action, fallbackDisabled = false) => {
715
+ var _a, _b, _c;
716
+ return {
717
+ label: action.name,
718
+ onClick: action.onClick,
719
+ appearance: (_a = action.type) != null ? _a : "contained",
720
+ style: (_b = action.style) != null ? _b : { maxWidth: "150px" },
721
+ disabled: (_c = action.disable) != null ? _c : fallbackDisabled
722
+ };
723
+ };
724
+ var normalizeModalProps = (props) => {
725
+ var _a, _b;
726
+ if (!isLegacyModalProps(props)) {
727
+ return props;
728
+ }
729
+ const isBusy = Boolean(props.isLoading);
730
+ const disableAction = Boolean(props.disableActionBtn || props.isLoading);
731
+ return {
732
+ isOpen: props.showModal,
733
+ onClose: props.closeModal,
734
+ title: props.title,
735
+ children: props.children,
736
+ theme: props.theme,
737
+ maximized: props.maximized,
738
+ contentStyle: props.style,
739
+ bodyStyle: props.bodyStyle,
740
+ hideCloseButton: props.hideCloseIcon,
741
+ disableDismiss: isBusy,
742
+ closeOnBackdrop: false,
743
+ closeOnEscape: false,
744
+ isBusy,
745
+ closeAction: props.onlyCloseButton ? {
746
+ label: "Close",
747
+ onClick: props.closeModal,
748
+ appearance: "contained",
749
+ disabled: isBusy
750
+ } : void 0,
751
+ cancelAction: props.onlyCloseButton || !props.cancelButton ? void 0 : {
752
+ label: "Cancel",
753
+ onClick: props.cancelButton,
754
+ appearance: "text",
755
+ style: { maxWidth: "100px" }
756
+ },
757
+ confirmAction: props.onlyCloseButton || props.hideActionButton || !props.action ? void 0 : {
758
+ label: (_a = props.actionNameBtn) != null ? _a : "Save",
759
+ onClick: props.action,
760
+ appearance: "contained",
761
+ style: { maxWidth: "100px" },
762
+ disabled: disableAction
763
+ },
764
+ extraActions: props.onlyCloseButton ? void 0 : (_b = props.additionalActions) == null ? void 0 : _b.map((action) => normalizeLegacyAction(action, disableAction))
765
+ };
766
+ };
767
+ function ModalActionButton({
768
+ action,
769
+ theme
403
770
  }) {
404
771
  var _a, _b;
405
772
  const { colors } = theme;
773
+ const {
774
+ label,
775
+ onClick,
776
+ appearance = "contained",
777
+ disabled = false,
778
+ style
779
+ } = action;
406
780
  const buttonStyle = {
407
781
  width: "100%",
408
782
  display: "flex",
409
783
  alignItems: "center",
410
784
  justifyContent: "center",
411
785
  borderRadius: "0.4rem",
412
- padding: type === "link" ? 0 : "1.3rem",
786
+ padding: appearance === "link" ? 0 : "0.9rem 1.15rem",
413
787
  boxSizing: "border-box",
414
788
  cursor: disabled ? "not-allowed" : "pointer",
415
789
  fontWeight: 700,
416
- fontSize: "1.3rem",
790
+ fontSize: "1.05rem",
791
+ lineHeight: 1.2,
417
792
  margin: 0,
418
793
  textAlign: "center",
419
794
  transition: "0.5s",
420
795
  opacity: disabled ? 0.5 : 1,
421
796
  filter: disabled ? "saturate(0)" : void 0
422
797
  };
423
- if (type === "text") {
798
+ if (appearance === "text") {
424
799
  buttonStyle.background = "none";
425
800
  buttonStyle.border = "1px solid transparent";
426
801
  buttonStyle.color = colors.primary;
427
802
  buttonStyle.boxShadow = "none";
428
- } else if (type === "link") {
803
+ } else if (appearance === "link") {
429
804
  buttonStyle.background = "none";
430
805
  buttonStyle.border = "none";
431
806
  buttonStyle.color = colors.primary;
432
807
  buttonStyle.boxShadow = "none";
433
- } else if (type === "outlined") {
808
+ } else if (appearance === "outlined") {
434
809
  buttonStyle.background = "none";
435
810
  buttonStyle.border = `2px solid ${colors.primary}`;
436
811
  buttonStyle.color = colors.primary;
437
- buttonStyle.boxShadow = `0 14px 28px ${withAlpha4(colors.onSurface, 0.08)}`;
812
+ buttonStyle.boxShadow = `0 14px 28px ${withAlpha(colors.onSurface, 0.08)}`;
438
813
  } else {
439
814
  buttonStyle.background = colors.primary;
440
815
  buttonStyle.border = "1px solid transparent";
441
816
  buttonStyle.color = colors.onPrimary;
442
- buttonStyle.boxShadow = `0 14px 28px ${withAlpha4(colors.primary, 0.22)}`;
817
+ buttonStyle.boxShadow = `0 14px 28px ${withAlpha(colors.primary, 0.22)}`;
443
818
  }
444
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
819
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
445
820
  "div",
446
821
  {
447
822
  style: {
@@ -449,11 +824,11 @@ function LegacyActionButton({
449
824
  display: "flex",
450
825
  alignItems: "center",
451
826
  justifyContent: "center",
452
- margin: "0 5px",
827
+ margin: 0,
453
828
  marginBottom: (_b = style == null ? void 0 : style.marginBottom) != null ? _b : 0,
454
829
  ...style
455
830
  },
456
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
831
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
457
832
  "button",
458
833
  {
459
834
  type: "button",
@@ -466,30 +841,92 @@ function LegacyActionButton({
466
841
  }
467
842
  );
468
843
  }
469
- function StandardModal({
844
+ function ModalBusyIndicator({
845
+ theme
846
+ }) {
847
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
848
+ "div",
849
+ {
850
+ "aria-live": "polite",
851
+ role: "status",
852
+ style: {
853
+ position: "relative",
854
+ display: "inline-flex",
855
+ alignItems: "center",
856
+ justifyContent: "center",
857
+ width: 56,
858
+ height: 56,
859
+ borderRadius: 999,
860
+ backgroundColor: withAlpha(theme.colors.surface, 0.88),
861
+ boxShadow: `0 18px 36px ${withAlpha(theme.colors.onSurface, 0.08)}`
862
+ },
863
+ children: [
864
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: srOnlyStyle, children: "Loading" }),
865
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { "aria-hidden": "true", width: "32", height: "32", viewBox: "0 0 40 40", fill: "none", children: [
866
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
867
+ "circle",
868
+ {
869
+ cx: "20",
870
+ cy: "20",
871
+ r: "16",
872
+ stroke: withAlpha(theme.colors.onSurface, 0.12),
873
+ strokeWidth: "4"
874
+ }
875
+ ),
876
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
877
+ "path",
878
+ {
879
+ d: "M20 4C28.837 4 36 11.163 36 20",
880
+ stroke: theme.colors.primary,
881
+ strokeWidth: "4",
882
+ strokeLinecap: "round",
883
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
884
+ "animateTransform",
885
+ {
886
+ attributeName: "transform",
887
+ type: "rotate",
888
+ from: "0 20 20",
889
+ to: "360 20 20",
890
+ dur: "0.75s",
891
+ repeatCount: "indefinite"
892
+ }
893
+ )
894
+ }
895
+ )
896
+ ] })
897
+ ]
898
+ }
899
+ );
900
+ }
901
+ function UnifiedModal({
470
902
  isOpen,
471
903
  onClose,
472
904
  children,
473
- footer,
474
905
  title,
475
- description,
476
906
  headerContent,
907
+ footer,
908
+ confirmAction,
909
+ cancelAction,
910
+ closeAction,
911
+ extraActions,
477
912
  maxWidth,
478
913
  closeOnBackdrop = true,
479
914
  closeOnEscape = true,
480
915
  hideCloseButton = false,
481
916
  hideExpandButton = false,
917
+ disableDismiss = false,
482
918
  maximized,
483
919
  contentStyle,
920
+ bodyStyle,
921
+ isBusy = false,
484
922
  theme
485
923
  }) {
486
- const modalTheme = (0, import_react3.useMemo)(() => resolveTheme(theme), [theme]);
487
- const [isMaximized, setIsMaximized] = (0, import_react3.useState)(maximized != null ? maximized : false);
488
- const titleId = (0, import_react3.useMemo)(
489
- () => `smartsell-modal-${String(title != null ? title : "dialog").toLowerCase().replace(/[^a-z0-9]+/g, "-")}`,
490
- [title]
491
- );
492
- (0, import_react3.useEffect)(() => {
924
+ var _a, _b;
925
+ const modalTheme = (0, import_react4.useMemo)(() => resolveTheme(theme), [theme]);
926
+ const [isMaximized, setIsMaximized] = (0, import_react4.useState)(maximized != null ? maximized : false);
927
+ const titleId = (0, import_react4.useMemo)(() => createTitleId(title), [title]);
928
+ const shouldDisableDismiss = disableDismiss || isBusy;
929
+ (0, import_react4.useEffect)(() => {
493
930
  if (typeof maximized === "boolean") {
494
931
  setIsMaximized(maximized);
495
932
  return;
@@ -498,14 +935,14 @@ function StandardModal({
498
935
  setIsMaximized(true);
499
936
  }
500
937
  }, [maximized]);
501
- (0, import_react3.useEffect)(() => {
938
+ (0, import_react4.useEffect)(() => {
502
939
  if (!isOpen || typeof window === "undefined" || typeof document === "undefined") {
503
940
  return;
504
941
  }
505
942
  const previousOverflow = document.body.style.overflow;
506
943
  document.body.style.overflow = "hidden";
507
944
  const handleKeyDown = (event) => {
508
- if (closeOnEscape && event.key === "Escape") {
945
+ if (!shouldDisableDismiss && closeOnEscape && event.key === "Escape") {
509
946
  onClose();
510
947
  }
511
948
  };
@@ -514,47 +951,82 @@ function StandardModal({
514
951
  document.body.style.overflow = previousOverflow;
515
952
  window.removeEventListener("keydown", handleKeyDown);
516
953
  };
517
- }, [closeOnEscape, isOpen, onClose]);
954
+ }, [closeOnEscape, isOpen, onClose, shouldDisableDismiss]);
518
955
  if (!isOpen) {
519
956
  return null;
520
957
  }
521
- const header = headerContent != null ? headerContent : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
522
- title ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
523
- "h2",
524
- {
525
- id: titleId,
526
- style: {
527
- margin: 0,
528
- color: modalTheme.colors.onSurface,
529
- fontSize: 30,
530
- fontWeight: 800,
531
- lineHeight: 1.1
532
- },
533
- children: title
534
- }
535
- ) : null,
536
- description ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
537
- "p",
538
- {
539
- style: {
540
- margin: 0,
541
- color: modalTheme.mutedText,
542
- fontSize: 15,
543
- lineHeight: 1.6
958
+ const resolvedHeader = headerContent != null ? headerContent : title ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
959
+ "h2",
960
+ {
961
+ id: titleId,
962
+ style: {
963
+ margin: 0,
964
+ color: modalTheme.colors.onSurface,
965
+ fontSize: 30,
966
+ fontWeight: 800,
967
+ lineHeight: 1.1
968
+ },
969
+ children: title
970
+ }
971
+ ) : null;
972
+ let footerContent = footer;
973
+ if (footerContent === void 0) {
974
+ if (closeAction && !closeAction.hidden) {
975
+ footerContent = /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
976
+ ModalActionButton,
977
+ {
978
+ action: { ...closeAction, disabled: closeAction.disabled || isBusy },
979
+ theme: modalTheme
980
+ }
981
+ );
982
+ } else {
983
+ const resolvedActions = [
984
+ cancelAction ? {
985
+ ...cancelAction,
986
+ appearance: (_a = cancelAction.appearance) != null ? _a : "text",
987
+ disabled: cancelAction.disabled || isBusy
988
+ } : void 0,
989
+ ...(extraActions != null ? extraActions : []).map((action) => {
990
+ var _a2;
991
+ return {
992
+ ...action,
993
+ appearance: (_a2 = action.appearance) != null ? _a2 : "contained",
994
+ disabled: action.disabled || isBusy
995
+ };
996
+ }),
997
+ confirmAction ? {
998
+ ...confirmAction,
999
+ appearance: (_b = confirmAction.appearance) != null ? _b : "contained",
1000
+ disabled: confirmAction.disabled || isBusy
1001
+ } : void 0
1002
+ ].reduce((items, action) => {
1003
+ if (!action || action.hidden) {
1004
+ return items;
1005
+ }
1006
+ items.push(action);
1007
+ return items;
1008
+ }, []);
1009
+ footerContent = resolvedActions.length > 0 ? resolvedActions.map((action, index) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1010
+ ModalActionButton,
1011
+ {
1012
+ action,
1013
+ theme: modalTheme
544
1014
  },
545
- children: description
546
- }
547
- ) : null
548
- ] });
549
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1015
+ `modal-action-${index}`
1016
+ )) : void 0;
1017
+ }
1018
+ }
1019
+ const shouldRenderFooter = footerContent !== void 0 && footerContent !== null && footerContent !== false;
1020
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
550
1021
  "div",
551
1022
  {
552
1023
  "aria-label": headerContent ? typeof title === "string" ? title : "Dialog" : void 0,
553
1024
  "aria-labelledby": headerContent ? void 0 : title ? titleId : void 0,
1025
+ "aria-busy": isBusy,
554
1026
  "aria-modal": "true",
555
1027
  role: "dialog",
556
1028
  onMouseDown: (event) => {
557
- if (closeOnBackdrop && event.target === event.currentTarget) {
1029
+ if (!shouldDisableDismiss && closeOnBackdrop && event.target === event.currentTarget) {
558
1030
  onClose();
559
1031
  }
560
1032
  },
@@ -569,7 +1041,7 @@ function StandardModal({
569
1041
  backgroundColor: modalTheme.overlay,
570
1042
  backdropFilter: "blur(8px)"
571
1043
  },
572
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1044
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
573
1045
  "div",
574
1046
  {
575
1047
  style: {
@@ -580,30 +1052,41 @@ function StandardModal({
580
1052
  maxWidth: isMaximized ? "100vw" : normalizeWidth(maxWidth),
581
1053
  height: isMaximized ? "100vh" : void 0,
582
1054
  maxHeight: isMaximized ? "100vh" : "calc(100vh - 32px)",
1055
+ minWidth: isMaximized ? "100vw" : 300,
583
1056
  borderRadius: isMaximized ? 0 : 30,
584
1057
  border: `1px solid ${modalTheme.borderSoft}`,
585
1058
  background: `linear-gradient(180deg, ${modalTheme.surfaceRaised}, ${modalTheme.colors.surface})`,
586
- boxShadow: `0 34px 80px ${withAlpha4("#020617", 0.28)}`,
1059
+ boxShadow: `0 34px 80px ${withAlpha("#020617", 0.28)}`,
587
1060
  color: modalTheme.colors.onSurface,
588
1061
  overflow: "hidden",
589
1062
  ...contentStyle
590
1063
  },
591
1064
  children: [
592
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1065
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
593
1066
  "div",
594
1067
  {
595
1068
  style: {
596
1069
  display: "flex",
597
1070
  alignItems: "flex-start",
598
1071
  justifyContent: "space-between",
599
- gap: 16,
600
- padding: "24px 24px 16px",
601
- borderBottom: `1px solid ${withAlpha4(modalTheme.colors.onSurface, 0.06)}`
1072
+ gap: 12,
1073
+ padding: "18px 20px 12px",
1074
+ borderBottom: `1px solid ${withAlpha(modalTheme.colors.onSurface, 0.06)}`
602
1075
  },
603
1076
  children: [
604
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { flex: 1 }, children: header }),
605
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 10 }, children: [
606
- !hideExpandButton ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1077
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1078
+ "div",
1079
+ {
1080
+ style: {
1081
+ flex: 1,
1082
+ opacity: isBusy ? 0.38 : 1,
1083
+ transition: "opacity 180ms ease"
1084
+ },
1085
+ children: resolvedHeader
1086
+ }
1087
+ ),
1088
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
1089
+ !hideExpandButton ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
607
1090
  "button",
608
1091
  {
609
1092
  "aria-label": isMaximized ? "Minimize modal" : "Maximize modal",
@@ -611,8 +1094,8 @@ function StandardModal({
611
1094
  type: "button",
612
1095
  onClick: () => setIsMaximized((current) => !current),
613
1096
  style: {
614
- height: 42,
615
- width: 42,
1097
+ height: 40,
1098
+ width: 40,
616
1099
  borderRadius: 999,
617
1100
  border: `1px solid ${modalTheme.borderSoft}`,
618
1101
  backgroundColor: "transparent",
@@ -624,22 +1107,23 @@ function StandardModal({
624
1107
  children: isMaximized ? "\u25A2" : "\u25A1"
625
1108
  }
626
1109
  ) : null,
627
- !hideCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1110
+ !hideCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
628
1111
  "button",
629
1112
  {
630
1113
  "aria-label": "Close modal",
631
1114
  type: "button",
632
- onClick: onClose,
1115
+ onClick: shouldDisableDismiss ? void 0 : onClose,
633
1116
  style: {
634
- height: 42,
635
- width: 42,
1117
+ height: 40,
1118
+ width: 40,
636
1119
  borderRadius: 999,
637
1120
  border: `1px solid ${modalTheme.borderSoft}`,
638
1121
  backgroundColor: "transparent",
639
1122
  color: modalTheme.colors.onSurface,
640
- cursor: "pointer",
1123
+ cursor: shouldDisableDismiss ? "not-allowed" : "pointer",
641
1124
  fontSize: 20,
642
- fontWeight: 700
1125
+ fontWeight: 700,
1126
+ opacity: shouldDisableDismiss ? 0.5 : 1
643
1127
  },
644
1128
  children: "\xD7"
645
1129
  }
@@ -648,29 +1132,59 @@ function StandardModal({
648
1132
  ]
649
1133
  }
650
1134
  ),
651
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1135
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
652
1136
  "div",
653
1137
  {
654
1138
  style: {
655
1139
  flex: 1,
656
1140
  minHeight: 0,
1141
+ position: "relative",
657
1142
  overflow: "auto",
658
- padding: 24
1143
+ padding: 24,
1144
+ ...bodyStyle
659
1145
  },
660
- children
1146
+ children: [
1147
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1148
+ "div",
1149
+ {
1150
+ style: {
1151
+ opacity: isBusy ? 0.38 : 1,
1152
+ filter: isBusy ? "saturate(0.8)" : "none",
1153
+ pointerEvents: isBusy ? "none" : "auto",
1154
+ transition: "opacity 180ms ease, filter 180ms ease"
1155
+ },
1156
+ children
1157
+ }
1158
+ ),
1159
+ isBusy ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1160
+ "div",
1161
+ {
1162
+ style: {
1163
+ position: "absolute",
1164
+ inset: 0,
1165
+ display: "flex",
1166
+ alignItems: "center",
1167
+ justifyContent: "center",
1168
+ backgroundColor: withAlpha(modalTheme.colors.surface, 0.58),
1169
+ backdropFilter: "blur(2px)"
1170
+ },
1171
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ModalBusyIndicator, { theme: modalTheme })
1172
+ }
1173
+ ) : null
1174
+ ]
661
1175
  }
662
1176
  ),
663
- footer ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1177
+ shouldRenderFooter ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
664
1178
  "div",
665
1179
  {
666
1180
  style: {
667
1181
  display: "flex",
668
1182
  justifyContent: "flex-end",
669
- gap: 12,
670
- padding: "16px 24px 24px",
671
- borderTop: `1px solid ${withAlpha4(modalTheme.colors.onSurface, 0.06)}`
1183
+ gap: 10,
1184
+ padding: "12px 20px 16px",
1185
+ borderTop: `1px solid ${withAlpha(modalTheme.colors.onSurface, 0.06)}`
672
1186
  },
673
- children: footer
1187
+ children: footerContent
674
1188
  }
675
1189
  ) : null
676
1190
  ]
@@ -679,548 +1193,1913 @@ function StandardModal({
679
1193
  }
680
1194
  );
681
1195
  }
682
- function LegacyModal({
1196
+ function Modal(props) {
1197
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(UnifiedModal, { ...normalizeModalProps(props) });
1198
+ }
1199
+
1200
+ // src/components/ComposableModal.tsx
1201
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1202
+ var createModalSlotIds = (slotBaseId) => ({
1203
+ header: `${slotBaseId}:modal-header`,
1204
+ body: `${slotBaseId}:modal-body`,
1205
+ footer: `${slotBaseId}:modal-footer`
1206
+ });
1207
+ function ComposableModal({
1208
+ isOpen,
1209
+ onClose,
683
1210
  title,
684
- showModal,
685
- closeModal,
686
- children,
687
- action,
688
- actionNameBtn,
689
- hideActionButton = false,
690
- cancelButton,
691
- disableActionBtn = false,
692
- isLoading = false,
693
- onlyCloseButton = false,
694
- hideCloseIcon = false,
695
- style,
696
- bodyStyle,
697
- additionalActions,
1211
+ slotBaseId,
1212
+ renderSlot,
1213
+ payload,
1214
+ onConfirm,
1215
+ confirmLabel = "Confirm",
1216
+ cancelLabel = "Cancel",
1217
+ isBusy = false,
1218
+ closeOnBackdrop = true,
1219
+ closeOnEscape = true,
1220
+ hideExpandButton = false,
698
1221
  maximized,
699
- theme
1222
+ hideDefaultFooter = false,
1223
+ maxWidth,
1224
+ headerFallback,
1225
+ footerFallback,
1226
+ children
700
1227
  }) {
701
- const modalTheme = (0, import_react3.useMemo)(() => resolveTheme(theme), [theme]);
702
- const [isMaximized, setIsMaximized] = (0, import_react3.useState)(maximized != null ? maximized : false);
703
- const resolvedAction = action != null ? action : (() => null);
704
- (0, import_react3.useEffect)(() => {
705
- if (maximized === void 0 && isLikelyMobileViewport()) {
706
- setIsMaximized(true);
1228
+ const theme = useUiKitTheme();
1229
+ const slotIds = createModalSlotIds(slotBaseId);
1230
+ const context = (0, import_react5.useMemo)(
1231
+ () => ({
1232
+ title,
1233
+ payload,
1234
+ close: onClose,
1235
+ confirm: onConfirm,
1236
+ confirmLabel,
1237
+ cancelLabel,
1238
+ isOpen,
1239
+ isBusy
1240
+ }),
1241
+ [cancelLabel, confirmLabel, isBusy, isOpen, onClose, onConfirm, payload, title]
1242
+ );
1243
+ const defaultFooter = hideDefaultFooter ? void 0 : footerFallback != null ? footerFallback : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
1244
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Button, { variant: "outline", onClick: onClose, disabled: isBusy, children: cancelLabel }),
1245
+ onConfirm ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Button, { onClick: onConfirm, disabled: isBusy, children: confirmLabel }) : null
1246
+ ] });
1247
+ const defaultHeader = headerFallback != null ? headerFallback : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1248
+ "h2",
1249
+ {
1250
+ style: {
1251
+ margin: 0,
1252
+ fontSize: 30,
1253
+ fontWeight: 800,
1254
+ lineHeight: 1.1
1255
+ },
1256
+ children: title
707
1257
  }
708
- }, [maximized]);
709
- if (!showModal) {
710
- return null;
1258
+ );
1259
+ const renderSlotContent = (id, slotChildren) => {
1260
+ if (!renderSlot) {
1261
+ return slotChildren;
1262
+ }
1263
+ return renderSlot({
1264
+ id,
1265
+ context,
1266
+ children: slotChildren
1267
+ });
1268
+ };
1269
+ const headerContent = renderSlotContent(slotIds.header, defaultHeader);
1270
+ const bodyContent = renderSlotContent(slotIds.body, children);
1271
+ const footerContent = renderSlotContent(slotIds.footer, defaultFooter);
1272
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1273
+ Modal,
1274
+ {
1275
+ isOpen,
1276
+ onClose,
1277
+ title,
1278
+ isBusy,
1279
+ headerContent,
1280
+ footer: footerContent,
1281
+ theme,
1282
+ maxWidth,
1283
+ hideExpandButton,
1284
+ maximized,
1285
+ closeOnBackdrop,
1286
+ closeOnEscape,
1287
+ children: bodyContent
1288
+ }
1289
+ );
1290
+ }
1291
+
1292
+ // src/components/DatePicker.tsx
1293
+ var import_react7 = require("react");
1294
+
1295
+ // src/components/Input.tsx
1296
+ var import_react6 = require("react");
1297
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1298
+ var PASSWORD_TOGGLE_METRICS_BY_SIZE = {
1299
+ sm: {
1300
+ buttonSize: 18,
1301
+ leftOffset: 8,
1302
+ contentGap: 8,
1303
+ iconSize: 14
1304
+ },
1305
+ md: {
1306
+ buttonSize: 20,
1307
+ leftOffset: 10,
1308
+ contentGap: 10,
1309
+ iconSize: 16
1310
+ },
1311
+ lg: {
1312
+ buttonSize: 22,
1313
+ leftOffset: 12,
1314
+ contentGap: 12,
1315
+ iconSize: 18
1316
+ }
1317
+ };
1318
+ var getPasswordToggleMetrics = (size) => PASSWORD_TOGGLE_METRICS_BY_SIZE[size];
1319
+ function ControlFrame({
1320
+ label,
1321
+ hint,
1322
+ error,
1323
+ htmlFor,
1324
+ required = false,
1325
+ useLabelWrapper = true,
1326
+ children,
1327
+ style
1328
+ }) {
1329
+ const { theme, mutedText } = useUiKitTheme();
1330
+ if (!label && !hint && !error) {
1331
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children });
711
1332
  }
712
- const actionButtons = () => {
713
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
714
- additionalActions == null ? void 0 : additionalActions.map((item) => {
715
- var _a, _b, _c;
716
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
717
- LegacyActionButton,
1333
+ const labelStyle = {
1334
+ color: theme.colors.onSurface,
1335
+ fontSize: 14,
1336
+ fontWeight: 700,
1337
+ letterSpacing: "0.01em"
1338
+ };
1339
+ const wrapperStyle = {
1340
+ display: "flex",
1341
+ flexDirection: "column",
1342
+ gap: 8,
1343
+ width: "100%",
1344
+ ...style
1345
+ };
1346
+ if (!useLabelWrapper) {
1347
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: wrapperStyle, children: [
1348
+ label || hint ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: [
1349
+ label ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { htmlFor, style: labelStyle, children: [
1350
+ label,
1351
+ required ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { color: theme.colors.negative, marginLeft: 4 }, children: "*" }) : null
1352
+ ] }) : null,
1353
+ hint ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1354
+ "span",
718
1355
  {
719
- label: item.name,
720
- onClick: item.onClick,
721
- type: (_a = item.type) != null ? _a : "contained",
722
- style: (_b = item.style) != null ? _b : { maxWidth: "150px" },
723
- theme: modalTheme,
724
- disabled: (_c = item.disable) != null ? _c : disableActionBtn || isLoading
725
- },
726
- item.name
727
- );
728
- }),
729
- !hideActionButton ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
730
- LegacyActionButton,
1356
+ style: {
1357
+ color: mutedText,
1358
+ fontSize: 13,
1359
+ lineHeight: 1.45
1360
+ },
1361
+ children: hint
1362
+ }
1363
+ ) : null
1364
+ ] }) : null,
1365
+ children,
1366
+ error ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1367
+ "span",
731
1368
  {
732
- label: actionNameBtn != null ? actionNameBtn : "Save",
733
- onClick: resolvedAction,
734
- type: "contained",
735
- style: { maxWidth: "100px" },
736
- theme: modalTheme,
737
- disabled: disableActionBtn || isLoading
1369
+ style: {
1370
+ color: theme.colors.negative,
1371
+ fontSize: 13,
1372
+ fontWeight: 600,
1373
+ lineHeight: 1.45
1374
+ },
1375
+ children: error
738
1376
  }
739
1377
  ) : null
740
1378
  ] });
1379
+ }
1380
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { htmlFor, style: wrapperStyle, children: [
1381
+ label || hint ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: [
1382
+ label ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: labelStyle, children: [
1383
+ label,
1384
+ required ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { color: theme.colors.negative, marginLeft: 4 }, children: "*" }) : null
1385
+ ] }) : null,
1386
+ hint ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1387
+ "span",
1388
+ {
1389
+ style: {
1390
+ color: mutedText,
1391
+ fontSize: 13,
1392
+ lineHeight: 1.45
1393
+ },
1394
+ children: hint
1395
+ }
1396
+ ) : null
1397
+ ] }) : null,
1398
+ children,
1399
+ error ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1400
+ "span",
1401
+ {
1402
+ style: {
1403
+ color: theme.colors.negative,
1404
+ fontSize: 13,
1405
+ fontWeight: 600,
1406
+ lineHeight: 1.45
1407
+ },
1408
+ children: error
1409
+ }
1410
+ ) : null
1411
+ ] });
1412
+ }
1413
+ function Input({
1414
+ label,
1415
+ hint,
1416
+ error,
1417
+ controlSize = "md",
1418
+ showPasswordToggle = false,
1419
+ id,
1420
+ disabled,
1421
+ style,
1422
+ type = "text",
1423
+ ...rest
1424
+ }) {
1425
+ const { theme, focusRing } = useUiKitTheme();
1426
+ const controlMetrics = getControlMetrics(controlSize);
1427
+ const passwordToggleMetrics = getPasswordToggleMetrics(controlSize);
1428
+ const [isPasswordVisible, setIsPasswordVisible] = (0, import_react6.useState)(false);
1429
+ const isPasswordField = type === "password";
1430
+ const canTogglePasswordVisibility = isPasswordField && showPasswordToggle;
1431
+ const passwordToggleInset = Math.max(
1432
+ 0,
1433
+ passwordToggleMetrics.leftOffset + passwordToggleMetrics.buttonSize + passwordToggleMetrics.contentGap - controlMetrics.paddingX
1434
+ );
1435
+ const leadingInset = Number(canTogglePasswordVisibility) * passwordToggleInset;
1436
+ const resolvedType = canTogglePasswordVisibility ? isPasswordVisible ? "text" : "password" : type;
1437
+ const inputStyle = {
1438
+ width: "100%",
1439
+ boxSizing: "border-box",
1440
+ height: controlMetrics.height,
1441
+ borderRadius: 16,
1442
+ border: `1px solid ${error ? theme.colors.negative : withAlpha(theme.colors.textLight, 0.5)}`,
1443
+ backgroundColor: theme.colors.surface,
1444
+ color: theme.colors.text,
1445
+ cursor: disabled ? "not-allowed" : "text",
1446
+ opacity: disabled ? 0.7 : 1,
1447
+ padding: getTextInputPadding(controlSize, { leadingInset }),
1448
+ outline: "none",
1449
+ fontSize: controlMetrics.fontSize,
1450
+ lineHeight: controlMetrics.lineHeight,
1451
+ boxShadow: error ? `0 0 0 3px ${withAlpha(theme.colors.negative, 0.12)}` : `0 0 0 0 ${focusRing}`,
1452
+ transition: "border-color 160ms ease, box-shadow 160ms ease, background-color 160ms ease",
1453
+ ...style
741
1454
  };
742
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
743
- "div",
1455
+ const input = canTogglePasswordVisibility ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { position: "relative", width: "100%" }, children: [
1456
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1457
+ "button",
1458
+ {
1459
+ type: "button",
1460
+ "aria-label": isPasswordVisible ? "Hide password" : "Show password",
1461
+ "aria-pressed": isPasswordVisible,
1462
+ disabled,
1463
+ onMouseDown: (event) => {
1464
+ event.preventDefault();
1465
+ },
1466
+ onClick: () => {
1467
+ setIsPasswordVisible((currentValue) => !currentValue);
1468
+ },
1469
+ style: {
1470
+ position: "absolute",
1471
+ top: "50%",
1472
+ left: passwordToggleMetrics.leftOffset,
1473
+ transform: "translateY(-50%)",
1474
+ zIndex: 1,
1475
+ width: passwordToggleMetrics.buttonSize,
1476
+ height: passwordToggleMetrics.buttonSize,
1477
+ display: "inline-flex",
1478
+ alignItems: "center",
1479
+ justifyContent: "center",
1480
+ border: "none",
1481
+ padding: 0,
1482
+ margin: 0,
1483
+ borderRadius: 999,
1484
+ backgroundColor: "transparent",
1485
+ color: isPasswordVisible ? theme.colors.primary : theme.colors.textLight,
1486
+ cursor: disabled ? "not-allowed" : "pointer",
1487
+ opacity: disabled ? 0.5 : 1
1488
+ },
1489
+ children: isPasswordVisible ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1490
+ "svg",
1491
+ {
1492
+ width: passwordToggleMetrics.iconSize,
1493
+ height: passwordToggleMetrics.iconSize,
1494
+ viewBox: "0 0 20 20",
1495
+ fill: "none",
1496
+ "aria-hidden": "true",
1497
+ children: [
1498
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1499
+ "path",
1500
+ {
1501
+ d: "M2.5 3.5L17.5 16.5",
1502
+ stroke: "currentColor",
1503
+ strokeWidth: "1.8",
1504
+ strokeLinecap: "round"
1505
+ }
1506
+ ),
1507
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1508
+ "path",
1509
+ {
1510
+ d: "M8.825 8.786a2 2 0 0 0 2.389 2.389",
1511
+ stroke: "currentColor",
1512
+ strokeWidth: "1.8",
1513
+ strokeLinecap: "round",
1514
+ strokeLinejoin: "round"
1515
+ }
1516
+ ),
1517
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1518
+ "path",
1519
+ {
1520
+ d: "M4.42 6.774C3.422 7.73 2.777 8.815 2.5 9.375c1.182 2.39 3.934 5.625 7.5 5.625 1.155 0 2.224-.34 3.166-.847",
1521
+ stroke: "currentColor",
1522
+ strokeWidth: "1.8",
1523
+ strokeLinecap: "round",
1524
+ strokeLinejoin: "round"
1525
+ }
1526
+ ),
1527
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1528
+ "path",
1529
+ {
1530
+ d: "M7.043 4.455A7.7 7.7 0 0 1 10 3.75c3.566 0 6.318 3.235 7.5 5.625-.39.787-1.504 2.588-3.259 3.986",
1531
+ stroke: "currentColor",
1532
+ strokeWidth: "1.8",
1533
+ strokeLinecap: "round",
1534
+ strokeLinejoin: "round"
1535
+ }
1536
+ )
1537
+ ]
1538
+ }
1539
+ ) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1540
+ "svg",
1541
+ {
1542
+ width: passwordToggleMetrics.iconSize,
1543
+ height: passwordToggleMetrics.iconSize,
1544
+ viewBox: "0 0 20 20",
1545
+ fill: "none",
1546
+ "aria-hidden": "true",
1547
+ children: [
1548
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1549
+ "path",
1550
+ {
1551
+ d: "M2.5 10c1.182-2.39 3.934-5.625 7.5-5.625S16.318 7.61 17.5 10c-1.182 2.39-3.934 5.625-7.5 5.625S3.682 12.39 2.5 10Z",
1552
+ stroke: "currentColor",
1553
+ strokeWidth: "1.8",
1554
+ strokeLinecap: "round",
1555
+ strokeLinejoin: "round"
1556
+ }
1557
+ ),
1558
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "10", cy: "10", r: "2.25", stroke: "currentColor", strokeWidth: "1.8" })
1559
+ ]
1560
+ }
1561
+ )
1562
+ }
1563
+ ),
1564
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { ...rest, id, type: resolvedType, disabled, style: inputStyle })
1565
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { ...rest, id, type: resolvedType, disabled, style: inputStyle });
1566
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ControlFrame, { label, hint, error, htmlFor: id, required: rest.required, children: input });
1567
+ }
1568
+
1569
+ // src/components/DatePicker.tsx
1570
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1571
+ var DEFAULT_FORMAT_BY_MODE = {
1572
+ date: "DD/MM/YYYY",
1573
+ datetime: "DD/MM/YYYY HH:mm"
1574
+ };
1575
+ var HTML_DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
1576
+ var HTML_DATETIME_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::\d{2})?$/;
1577
+ var FORMAT_TOKENS = [
1578
+ {
1579
+ token: "YYYY",
1580
+ key: "year",
1581
+ pattern: "(\\d{4})",
1582
+ formatter: (date) => String(date.getFullYear())
1583
+ },
1584
+ {
1585
+ token: "MM",
1586
+ key: "month",
1587
+ pattern: "(\\d{1,2})",
1588
+ formatter: (date) => padNumber(date.getMonth() + 1)
1589
+ },
1590
+ {
1591
+ token: "DD",
1592
+ key: "day",
1593
+ pattern: "(\\d{1,2})",
1594
+ formatter: (date) => padNumber(date.getDate())
1595
+ },
1596
+ {
1597
+ token: "HH",
1598
+ key: "hour",
1599
+ pattern: "(\\d{1,2})",
1600
+ formatter: (date) => padNumber(date.getHours())
1601
+ },
1602
+ {
1603
+ token: "mm",
1604
+ key: "minute",
1605
+ pattern: "(\\d{1,2})",
1606
+ formatter: (date) => padNumber(date.getMinutes())
1607
+ }
1608
+ ];
1609
+ var ACCESSORY_METRICS_BY_SIZE = {
1610
+ sm: {
1611
+ buttonSize: 18,
1612
+ iconSize: 14,
1613
+ rightOffset: 8,
1614
+ clusterGap: 6,
1615
+ contentGap: 8
1616
+ },
1617
+ md: {
1618
+ buttonSize: 20,
1619
+ iconSize: 16,
1620
+ rightOffset: 10,
1621
+ clusterGap: 8,
1622
+ contentGap: 10
1623
+ },
1624
+ lg: {
1625
+ buttonSize: 22,
1626
+ iconSize: 18,
1627
+ rightOffset: 12,
1628
+ clusterGap: 8,
1629
+ contentGap: 12
1630
+ }
1631
+ };
1632
+ function padNumber(value, length = 2) {
1633
+ return String(value).padStart(length, "0");
1634
+ }
1635
+ function escapeRegExp(value) {
1636
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1637
+ }
1638
+ function isValidDateInstance(value) {
1639
+ return !Number.isNaN(value.getTime());
1640
+ }
1641
+ function createValidatedDate(year, month, day, hour = 0, minute = 0) {
1642
+ const nextDate = new Date(year, month - 1, day, hour, minute, 0, 0);
1643
+ if (!isValidDateInstance(nextDate)) {
1644
+ return null;
1645
+ }
1646
+ if (nextDate.getFullYear() !== year || nextDate.getMonth() !== month - 1 || nextDate.getDate() !== day || nextDate.getHours() !== hour || nextDate.getMinutes() !== minute) {
1647
+ return null;
1648
+ }
1649
+ return nextDate;
1650
+ }
1651
+ function normalizeDatePrecision(date, mode) {
1652
+ if (mode === "date") {
1653
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0);
1654
+ }
1655
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), 0, 0);
1656
+ }
1657
+ function toLocalInputValue(date, mode) {
1658
+ const normalizedDate = normalizeDatePrecision(date, mode);
1659
+ const datePart = `${normalizedDate.getFullYear()}-${padNumber(normalizedDate.getMonth() + 1)}-${padNumber(normalizedDate.getDate())}`;
1660
+ if (mode === "date") {
1661
+ return datePart;
1662
+ }
1663
+ return `${datePart}T${padNumber(normalizedDate.getHours())}:${padNumber(normalizedDate.getMinutes())}`;
1664
+ }
1665
+ function parseHtmlDateLikeValue(value) {
1666
+ const trimmedValue = value.trim();
1667
+ const dateMatch = HTML_DATE_PATTERN.exec(trimmedValue);
1668
+ if (dateMatch) {
1669
+ return createValidatedDate(
1670
+ Number(dateMatch[1]),
1671
+ Number(dateMatch[2]),
1672
+ Number(dateMatch[3])
1673
+ );
1674
+ }
1675
+ const datetimeMatch = HTML_DATETIME_PATTERN.exec(trimmedValue);
1676
+ if (datetimeMatch) {
1677
+ return createValidatedDate(
1678
+ Number(datetimeMatch[1]),
1679
+ Number(datetimeMatch[2]),
1680
+ Number(datetimeMatch[3]),
1681
+ Number(datetimeMatch[4]),
1682
+ Number(datetimeMatch[5])
1683
+ );
1684
+ }
1685
+ return null;
1686
+ }
1687
+ function parseFormattedDateValue(value, format, mode) {
1688
+ const orderedParts = [];
1689
+ let expression = "^";
1690
+ for (let index = 0; index < format.length; ) {
1691
+ const token = FORMAT_TOKENS.find(({ token: currentToken }) => format.startsWith(currentToken, index));
1692
+ if (token) {
1693
+ expression += token.pattern;
1694
+ orderedParts.push(token.key);
1695
+ index += token.token.length;
1696
+ continue;
1697
+ }
1698
+ expression += escapeRegExp(format[index]);
1699
+ index += 1;
1700
+ }
1701
+ expression += "$";
1702
+ const match = new RegExp(expression).exec(value.trim());
1703
+ if (!match) {
1704
+ return null;
1705
+ }
1706
+ const resolvedParts = {
1707
+ year: 0,
1708
+ month: 1,
1709
+ day: 1,
1710
+ hour: 0,
1711
+ minute: 0
1712
+ };
1713
+ orderedParts.forEach((key, index) => {
1714
+ resolvedParts[key] = Number(match[index + 1]);
1715
+ });
1716
+ if (!resolvedParts.year || !resolvedParts.month || !resolvedParts.day) {
1717
+ return null;
1718
+ }
1719
+ return createValidatedDate(
1720
+ resolvedParts.year,
1721
+ resolvedParts.month,
1722
+ resolvedParts.day,
1723
+ mode === "datetime" ? resolvedParts.hour : 0,
1724
+ mode === "datetime" ? resolvedParts.minute : 0
1725
+ );
1726
+ }
1727
+ function parseDateValue(value, mode, format) {
1728
+ if (value == null || value === "") {
1729
+ return null;
1730
+ }
1731
+ if (value instanceof Date) {
1732
+ return isValidDateInstance(value) ? normalizeDatePrecision(value, mode) : null;
1733
+ }
1734
+ if (typeof value !== "string") {
1735
+ return null;
1736
+ }
1737
+ const parsedFormattedDate = parseFormattedDateValue(value, format, mode);
1738
+ if (parsedFormattedDate) {
1739
+ return normalizeDatePrecision(parsedFormattedDate, mode);
1740
+ }
1741
+ const parsedHtmlDate = parseHtmlDateLikeValue(value);
1742
+ if (parsedHtmlDate) {
1743
+ return normalizeDatePrecision(parsedHtmlDate, mode);
1744
+ }
1745
+ const parsedNativeDate = new Date(value);
1746
+ return isValidDateInstance(parsedNativeDate) ? normalizeDatePrecision(parsedNativeDate, mode) : null;
1747
+ }
1748
+ function formatDateValue(date, format) {
1749
+ return FORMAT_TOKENS.reduce((currentFormat, token) => {
1750
+ return currentFormat.split(token.token).join(token.formatter(date));
1751
+ }, format);
1752
+ }
1753
+ function getLaterDate(leftDate, rightDate) {
1754
+ if (!leftDate) return rightDate;
1755
+ if (!rightDate) return leftDate;
1756
+ return leftDate.getTime() >= rightDate.getTime() ? leftDate : rightDate;
1757
+ }
1758
+ function getEarlierDate(leftDate, rightDate) {
1759
+ if (!leftDate) return rightDate;
1760
+ if (!rightDate) return leftDate;
1761
+ return leftDate.getTime() <= rightDate.getTime() ? leftDate : rightDate;
1762
+ }
1763
+ function CalendarIcon({ size }) {
1764
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("svg", { width: size, height: size, viewBox: "0 0 20 20", fill: "none", "aria-hidden": "true", children: [
1765
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("rect", { x: "3", y: "4.5", width: "14", height: "12.5", rx: "3", stroke: "currentColor", strokeWidth: "1.6" }),
1766
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M6.25 2.75V6", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" }),
1767
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M13.75 2.75V6", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" }),
1768
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M3 7.5H17", stroke: "currentColor", strokeWidth: "1.6" }),
1769
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "10", cy: "11.5", r: "1.2", fill: "currentColor" })
1770
+ ] });
1771
+ }
1772
+ function ClearIcon({ size }) {
1773
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("svg", { width: size, height: size, viewBox: "0 0 20 20", fill: "none", "aria-hidden": "true", children: [
1774
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M5 5L15 15", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round" }),
1775
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M15 5L5 15", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round" })
1776
+ ] });
1777
+ }
1778
+ function DatePicker({
1779
+ label,
1780
+ hint,
1781
+ error,
1782
+ controlSize = "md",
1783
+ value,
1784
+ defaultValue,
1785
+ onChange,
1786
+ mode = "date",
1787
+ format,
1788
+ allowPastDates = true,
1789
+ allowFutureDates = true,
1790
+ minDate,
1791
+ maxDate,
1792
+ minuteStep = 5,
1793
+ clearable = true,
1794
+ showPickerButton = true,
1795
+ isDateUnavailable,
1796
+ invalidDateMessage,
1797
+ unavailableDateMessage,
1798
+ pickerButtonLabel,
1799
+ clearButtonLabel,
1800
+ id,
1801
+ name,
1802
+ disabled,
1803
+ readOnly,
1804
+ required,
1805
+ style,
1806
+ className,
1807
+ placeholder,
1808
+ onBlur,
1809
+ onFocus,
1810
+ onKeyDown,
1811
+ ...rest
1812
+ }) {
1813
+ var _a;
1814
+ const { theme, focusRing } = useUiKitTheme();
1815
+ const controlMetrics = getControlMetrics(controlSize);
1816
+ const accessoryMetrics = ACCESSORY_METRICS_BY_SIZE[controlSize];
1817
+ const generatedIdRef = (0, import_react7.useRef)();
1818
+ const visibleInputRef = (0, import_react7.useRef)(null);
1819
+ const nativeInputRef = (0, import_react7.useRef)(null);
1820
+ const [internalError, setInternalError] = (0, import_react7.useState)();
1821
+ const resolvedFormat = (format == null ? void 0 : format.trim()) ? format : DEFAULT_FORMAT_BY_MODE[mode];
1822
+ const isControlled = value !== void 0;
1823
+ const nativePickerType = mode === "datetime" ? "datetime-local" : "date";
1824
+ if (!generatedIdRef.current) {
1825
+ generatedIdRef.current = `ss-date-picker-${Math.random().toString(36).slice(2, 10)}`;
1826
+ }
1827
+ const resolvedId = id != null ? id : generatedIdRef.current;
1828
+ const normalizedDefaultValue = (0, import_react7.useMemo)(() => {
1829
+ const parsedDefaultValue = parseDateValue(defaultValue, mode, resolvedFormat);
1830
+ return parsedDefaultValue ? toLocalInputValue(parsedDefaultValue, mode) : null;
1831
+ }, [defaultValue, mode, resolvedFormat]);
1832
+ const [uncontrolledValue, setUncontrolledValue] = (0, import_react7.useState)(normalizedDefaultValue);
1833
+ const committedDate = (0, import_react7.useMemo)(() => {
1834
+ const sourceValue = isControlled ? value : uncontrolledValue;
1835
+ return parseDateValue(sourceValue, mode, resolvedFormat);
1836
+ }, [isControlled, mode, resolvedFormat, uncontrolledValue, value]);
1837
+ const committedValue = committedDate ? toLocalInputValue(committedDate, mode) : null;
1838
+ const committedDisplayValue = committedDate ? formatDateValue(committedDate, resolvedFormat) : "";
1839
+ const [draftValue, setDraftValue] = (0, import_react7.useState)(committedDisplayValue);
1840
+ (0, import_react7.useEffect)(() => {
1841
+ setDraftValue(committedDisplayValue);
1842
+ setInternalError(void 0);
1843
+ }, [committedDisplayValue]);
1844
+ (0, import_react7.useEffect)(() => {
1845
+ if (!isControlled) {
1846
+ setUncontrolledValue((currentValue) => {
1847
+ if (currentValue == null) {
1848
+ return currentValue;
1849
+ }
1850
+ const parsedCurrentValue = parseDateValue(currentValue, mode, resolvedFormat);
1851
+ return parsedCurrentValue ? toLocalInputValue(parsedCurrentValue, mode) : null;
1852
+ });
1853
+ }
1854
+ }, [isControlled, mode, resolvedFormat]);
1855
+ const parsedMinDate = (0, import_react7.useMemo)(() => {
1856
+ const explicitMinDate = parseDateValue(minDate, mode, resolvedFormat);
1857
+ const guardedMinDate = allowPastDates ? null : normalizeDatePrecision(/* @__PURE__ */ new Date(), mode);
1858
+ return getLaterDate(explicitMinDate, guardedMinDate);
1859
+ }, [allowPastDates, minDate, mode, resolvedFormat]);
1860
+ const parsedMaxDate = (0, import_react7.useMemo)(() => {
1861
+ const explicitMaxDate = parseDateValue(maxDate, mode, resolvedFormat);
1862
+ const guardedMaxDate = allowFutureDates ? null : normalizeDatePrecision(/* @__PURE__ */ new Date(), mode);
1863
+ return getEarlierDate(explicitMaxDate, guardedMaxDate);
1864
+ }, [allowFutureDates, maxDate, mode, resolvedFormat]);
1865
+ const hasExternalError = error !== void 0 && error !== null && error !== "";
1866
+ const hasConfigurationConflict = Boolean(parsedMinDate && parsedMaxDate && parsedMinDate.getTime() > parsedMaxDate.getTime());
1867
+ const derivedError = hasConfigurationConflict ? "Current date rules do not allow any selection." : internalError;
1868
+ const resolvedError = hasExternalError ? error : derivedError;
1869
+ const hasAnyValue = draftValue.trim().length > 0 || Boolean(committedValue);
1870
+ const canOpenPicker = showPickerButton && !disabled && !readOnly && !hasConfigurationConflict;
1871
+ const canClear = clearable && !disabled && !readOnly && !required && hasAnyValue;
1872
+ const visibleButtonCount = Number(canOpenPicker) + Number(canClear);
1873
+ const trailingInset = visibleButtonCount > 0 ? Math.max(
1874
+ 0,
1875
+ accessoryMetrics.rightOffset + visibleButtonCount * accessoryMetrics.buttonSize + (visibleButtonCount - 1) * accessoryMetrics.clusterGap + accessoryMetrics.contentGap - controlMetrics.paddingX
1876
+ ) : 0;
1877
+ const getValidationMessage = (parsedDate) => {
1878
+ if (parsedMinDate && parsedDate.getTime() < parsedMinDate.getTime()) {
1879
+ return `Choose a value on or after ${formatDateValue(parsedMinDate, resolvedFormat)}.`;
1880
+ }
1881
+ if (parsedMaxDate && parsedDate.getTime() > parsedMaxDate.getTime()) {
1882
+ return `Choose a value on or before ${formatDateValue(parsedMaxDate, resolvedFormat)}.`;
1883
+ }
1884
+ if (mode === "datetime" && minuteStep > 1 && parsedDate.getMinutes() % minuteStep !== 0) {
1885
+ return `Minutes must use ${minuteStep}-minute intervals.`;
1886
+ }
1887
+ if (isDateUnavailable == null ? void 0 : isDateUnavailable(parsedDate)) {
1888
+ return unavailableDateMessage != null ? unavailableDateMessage : "This date is not available.";
1889
+ }
1890
+ return void 0;
1891
+ };
1892
+ const emitChange = (nextValue, context) => {
1893
+ onChange == null ? void 0 : onChange(nextValue, context);
1894
+ };
1895
+ const commitValue = (nextValue, reason) => {
1896
+ if (hasConfigurationConflict) {
1897
+ setInternalError("Current date rules do not allow any selection.");
1898
+ return false;
1899
+ }
1900
+ if (!nextValue) {
1901
+ if (!isControlled) {
1902
+ setUncontrolledValue(null);
1903
+ }
1904
+ setDraftValue("");
1905
+ setInternalError(void 0);
1906
+ emitChange(null, {
1907
+ reason,
1908
+ date: null,
1909
+ nativeValue: "",
1910
+ displayValue: ""
1911
+ });
1912
+ return true;
1913
+ }
1914
+ const parsedDate = parseHtmlDateLikeValue(nextValue);
1915
+ if (!parsedDate) {
1916
+ setInternalError(invalidDateMessage != null ? invalidDateMessage : `Enter a valid value using ${resolvedFormat}.`);
1917
+ return false;
1918
+ }
1919
+ const normalizedDate = normalizeDatePrecision(parsedDate, mode);
1920
+ const validationMessage = getValidationMessage(normalizedDate);
1921
+ if (validationMessage) {
1922
+ setInternalError(validationMessage);
1923
+ return false;
1924
+ }
1925
+ const normalizedValue = toLocalInputValue(normalizedDate, mode);
1926
+ const displayValue = formatDateValue(normalizedDate, resolvedFormat);
1927
+ if (!isControlled) {
1928
+ setUncontrolledValue(normalizedValue);
1929
+ }
1930
+ setDraftValue(displayValue);
1931
+ setInternalError(void 0);
1932
+ emitChange(normalizedValue, {
1933
+ reason,
1934
+ date: normalizedDate,
1935
+ nativeValue: normalizedValue,
1936
+ displayValue
1937
+ });
1938
+ return true;
1939
+ };
1940
+ const commitDraftValue = () => {
1941
+ var _a2, _b;
1942
+ const trimmedDraftValue = draftValue.trim();
1943
+ if (!trimmedDraftValue) {
1944
+ return commitValue(null, "clear");
1945
+ }
1946
+ const parsedDraftDate = (_b = (_a2 = parseFormattedDateValue(trimmedDraftValue, resolvedFormat, mode)) != null ? _a2 : parseHtmlDateLikeValue(trimmedDraftValue)) != null ? _b : parseDateValue(trimmedDraftValue, mode, resolvedFormat);
1947
+ if (!parsedDraftDate) {
1948
+ setInternalError(invalidDateMessage != null ? invalidDateMessage : `Enter a valid value using ${resolvedFormat}.`);
1949
+ return false;
1950
+ }
1951
+ return commitValue(toLocalInputValue(parsedDraftDate, mode), "input");
1952
+ };
1953
+ const openNativePicker = () => {
1954
+ var _a2, _b;
1955
+ if (!canOpenPicker || !nativeInputRef.current) {
1956
+ return;
1957
+ }
1958
+ const parsedDraftDate = draftValue.trim() ? (_a2 = parseFormattedDateValue(draftValue, resolvedFormat, mode)) != null ? _a2 : parseHtmlDateLikeValue(draftValue) : null;
1959
+ if (parsedDraftDate) {
1960
+ nativeInputRef.current.value = toLocalInputValue(parsedDraftDate, mode);
1961
+ }
1962
+ const pickerInput = nativeInputRef.current;
1963
+ try {
1964
+ if (pickerInput.showPicker) {
1965
+ pickerInput.showPicker();
1966
+ return;
1967
+ }
1968
+ pickerInput.focus();
1969
+ } catch {
1970
+ (_b = visibleInputRef.current) == null ? void 0 : _b.focus();
1971
+ }
1972
+ };
1973
+ const inputStyle = {
1974
+ width: "100%",
1975
+ boxSizing: "border-box",
1976
+ height: controlMetrics.height,
1977
+ borderRadius: 16,
1978
+ border: `1px solid ${resolvedError ? theme.colors.negative : withAlpha(theme.colors.textLight, 0.5)}`,
1979
+ backgroundColor: theme.colors.surface,
1980
+ color: theme.colors.text,
1981
+ cursor: disabled ? "not-allowed" : readOnly ? "default" : "text",
1982
+ opacity: disabled ? 0.7 : 1,
1983
+ padding: getTextInputPadding(controlSize, { trailingInset }),
1984
+ outline: "none",
1985
+ fontSize: controlMetrics.fontSize,
1986
+ lineHeight: controlMetrics.lineHeight,
1987
+ boxShadow: resolvedError ? `0 0 0 3px ${withAlpha(theme.colors.negative, 0.12)}` : `0 0 0 0 ${focusRing}`,
1988
+ transition: "border-color 160ms ease, box-shadow 160ms ease, background-color 160ms ease",
1989
+ ...style
1990
+ };
1991
+ const accessoryButtonStyle = {
1992
+ width: accessoryMetrics.buttonSize,
1993
+ height: accessoryMetrics.buttonSize,
1994
+ border: "none",
1995
+ padding: 0,
1996
+ margin: 0,
1997
+ display: "inline-flex",
1998
+ alignItems: "center",
1999
+ justifyContent: "center",
2000
+ borderRadius: 999,
2001
+ backgroundColor: "transparent",
2002
+ color: theme.colors.textLight,
2003
+ cursor: disabled || readOnly ? "not-allowed" : "pointer",
2004
+ opacity: disabled ? 0.5 : 1
2005
+ };
2006
+ const input = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { position: "relative", width: "100%" }, children: [
2007
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2008
+ "input",
2009
+ {
2010
+ ...rest,
2011
+ ref: visibleInputRef,
2012
+ id: resolvedId,
2013
+ className,
2014
+ type: "text",
2015
+ value: draftValue,
2016
+ disabled,
2017
+ readOnly,
2018
+ required,
2019
+ placeholder: placeholder != null ? placeholder : resolvedFormat,
2020
+ autoComplete: (_a = rest.autoComplete) != null ? _a : "off",
2021
+ spellCheck: false,
2022
+ "aria-invalid": Boolean(resolvedError),
2023
+ onChange: (event) => {
2024
+ setDraftValue(event.target.value);
2025
+ setInternalError(void 0);
2026
+ },
2027
+ onFocus: (event) => {
2028
+ setInternalError(void 0);
2029
+ onFocus == null ? void 0 : onFocus(event);
2030
+ },
2031
+ onBlur: (event) => {
2032
+ commitDraftValue();
2033
+ onBlur == null ? void 0 : onBlur(event);
2034
+ },
2035
+ onKeyDown: (event) => {
2036
+ onKeyDown == null ? void 0 : onKeyDown(event);
2037
+ if (event.defaultPrevented) {
2038
+ return;
2039
+ }
2040
+ if (event.key === "Enter") {
2041
+ event.preventDefault();
2042
+ commitDraftValue();
2043
+ return;
2044
+ }
2045
+ if (event.key === "Escape") {
2046
+ event.preventDefault();
2047
+ setDraftValue(committedDisplayValue);
2048
+ setInternalError(void 0);
2049
+ }
2050
+ },
2051
+ style: inputStyle
2052
+ }
2053
+ ),
2054
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2055
+ "input",
2056
+ {
2057
+ ref: nativeInputRef,
2058
+ tabIndex: -1,
2059
+ "aria-hidden": "true",
2060
+ name,
2061
+ type: nativePickerType,
2062
+ value: committedValue != null ? committedValue : "",
2063
+ min: parsedMinDate ? toLocalInputValue(parsedMinDate, mode) : void 0,
2064
+ max: parsedMaxDate ? toLocalInputValue(parsedMaxDate, mode) : void 0,
2065
+ step: mode === "datetime" ? Math.max(1, minuteStep) * 60 : void 0,
2066
+ disabled: disabled || readOnly || hasConfigurationConflict,
2067
+ onChange: (event) => {
2068
+ commitValue(event.target.value || null, "picker");
2069
+ },
2070
+ style: {
2071
+ position: "absolute",
2072
+ width: 1,
2073
+ height: 1,
2074
+ padding: 0,
2075
+ margin: -1,
2076
+ overflow: "hidden",
2077
+ clip: "rect(0, 0, 0, 0)",
2078
+ whiteSpace: "nowrap",
2079
+ border: 0,
2080
+ opacity: 0,
2081
+ pointerEvents: "none"
2082
+ }
2083
+ }
2084
+ ),
2085
+ visibleButtonCount > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2086
+ "div",
2087
+ {
2088
+ style: {
2089
+ position: "absolute",
2090
+ top: "50%",
2091
+ right: accessoryMetrics.rightOffset,
2092
+ transform: "translateY(-50%)",
2093
+ display: "inline-flex",
2094
+ alignItems: "center",
2095
+ gap: accessoryMetrics.clusterGap
2096
+ },
2097
+ children: [
2098
+ canClear ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2099
+ "button",
2100
+ {
2101
+ type: "button",
2102
+ "aria-label": clearButtonLabel != null ? clearButtonLabel : "Clear date",
2103
+ disabled: disabled || readOnly,
2104
+ onMouseDown: (event) => {
2105
+ event.preventDefault();
2106
+ },
2107
+ onClick: () => {
2108
+ var _a2;
2109
+ commitValue(null, "clear");
2110
+ (_a2 = visibleInputRef.current) == null ? void 0 : _a2.focus();
2111
+ },
2112
+ style: accessoryButtonStyle,
2113
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ClearIcon, { size: accessoryMetrics.iconSize })
2114
+ }
2115
+ ) : null,
2116
+ canOpenPicker ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2117
+ "button",
2118
+ {
2119
+ type: "button",
2120
+ "aria-label": pickerButtonLabel != null ? pickerButtonLabel : mode === "datetime" ? "Open date and time picker" : "Open date picker",
2121
+ disabled: disabled || readOnly || hasConfigurationConflict,
2122
+ onMouseDown: (event) => {
2123
+ event.preventDefault();
2124
+ },
2125
+ onClick: openNativePicker,
2126
+ style: accessoryButtonStyle,
2127
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CalendarIcon, { size: accessoryMetrics.iconSize })
2128
+ }
2129
+ ) : null
2130
+ ]
2131
+ }
2132
+ ) : null
2133
+ ] });
2134
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2135
+ ControlFrame,
744
2136
  {
745
- "aria-labelledby": "modal-title",
746
- "aria-modal": "true",
747
- role: "dialog",
2137
+ label,
2138
+ hint,
2139
+ error: resolvedError,
2140
+ htmlFor: resolvedId,
2141
+ required,
2142
+ children: input
2143
+ }
2144
+ );
2145
+ }
2146
+
2147
+ // src/components/Select.tsx
2148
+ var import_react8 = require("react");
2149
+ var import_jsx_runtime8 = require("react/jsx-runtime");
2150
+ var MENU_OFFSET = 8;
2151
+ var MENU_MAX_HEIGHT = 280;
2152
+ var getOptionPadding = (size) => {
2153
+ if (size === "sm") return "10px 12px";
2154
+ if (size === "lg") return "14px 16px";
2155
+ return "12px 14px";
2156
+ };
2157
+ function getTextContent(node) {
2158
+ if (node == null || typeof node === "boolean") {
2159
+ return "";
2160
+ }
2161
+ if (typeof node === "string" || typeof node === "number") {
2162
+ return String(node);
2163
+ }
2164
+ if (Array.isArray(node)) {
2165
+ return node.map(getTextContent).join("");
2166
+ }
2167
+ if ((0, import_react8.isValidElement)(node)) {
2168
+ return getTextContent(node.props.children);
2169
+ }
2170
+ return "";
2171
+ }
2172
+ function normalizeSelectValue(value) {
2173
+ if (Array.isArray(value)) {
2174
+ return value.length > 0 ? normalizeSelectValue(value[0]) : "";
2175
+ }
2176
+ return value == null ? "" : String(value);
2177
+ }
2178
+ function normalizeSelectValues(value) {
2179
+ if (Array.isArray(value)) {
2180
+ return value.map((item) => normalizeSelectValue(item));
2181
+ }
2182
+ if (value == null) {
2183
+ return [];
2184
+ }
2185
+ return [normalizeSelectValue(value)];
2186
+ }
2187
+ function buildOption(keyPrefix, index, props, groupLabel, groupDisabled = false) {
2188
+ var _a;
2189
+ const rawValue = props.value !== void 0 ? props.value : getTextContent(props.children).trim();
2190
+ const normalizedValue = normalizeSelectValue(rawValue);
2191
+ return {
2192
+ key: `${keyPrefix}-${index}-${normalizedValue}`,
2193
+ value: normalizedValue,
2194
+ label: (_a = props.children) != null ? _a : normalizedValue,
2195
+ disabled: groupDisabled || Boolean(props.disabled)
2196
+ };
2197
+ }
2198
+ function parseSelectChildren(children) {
2199
+ const groups = [];
2200
+ const standaloneOptions = [];
2201
+ let optionIndex = 0;
2202
+ let groupIndex = 0;
2203
+ const flushStandaloneOptions = () => {
2204
+ if (standaloneOptions.length === 0) {
2205
+ return;
2206
+ }
2207
+ groups.push({
2208
+ key: `standalone-${groups.length}`,
2209
+ options: [...standaloneOptions]
2210
+ });
2211
+ standaloneOptions.length = 0;
2212
+ };
2213
+ import_react8.Children.forEach(children, (child) => {
2214
+ if (!(0, import_react8.isValidElement)(child)) {
2215
+ return;
2216
+ }
2217
+ if (child.type === "option") {
2218
+ standaloneOptions.push(buildOption("option", optionIndex, child.props));
2219
+ optionIndex += 1;
2220
+ return;
2221
+ }
2222
+ if (child.type !== "optgroup") {
2223
+ return;
2224
+ }
2225
+ flushStandaloneOptions();
2226
+ const groupOptions = [];
2227
+ let groupOptionIndex = 0;
2228
+ import_react8.Children.forEach(child.props.children, (optionChild) => {
2229
+ if (!(0, import_react8.isValidElement)(optionChild) || optionChild.type !== "option") {
2230
+ return;
2231
+ }
2232
+ groupOptions.push(
2233
+ buildOption(
2234
+ `group-${groupIndex}-option`,
2235
+ groupOptionIndex,
2236
+ optionChild.props,
2237
+ child.props.label,
2238
+ Boolean(child.props.disabled)
2239
+ )
2240
+ );
2241
+ groupOptionIndex += 1;
2242
+ });
2243
+ if (groupOptions.length > 0) {
2244
+ groups.push({
2245
+ key: `group-${groupIndex}`,
2246
+ label: child.props.label,
2247
+ options: groupOptions
2248
+ });
2249
+ groupIndex += 1;
2250
+ }
2251
+ });
2252
+ flushStandaloneOptions();
2253
+ return groups;
2254
+ }
2255
+ function filterSelectGroups(groups, query) {
2256
+ const normalizedQuery = query.trim().toLowerCase();
2257
+ if (!normalizedQuery) {
2258
+ return groups;
2259
+ }
2260
+ return groups.map((group) => ({
2261
+ ...group,
2262
+ options: group.options.filter((option) => {
2263
+ const optionText = getTextContent(option.label).trim().toLowerCase();
2264
+ return optionText.includes(normalizedQuery);
2265
+ })
2266
+ })).filter((group) => group.options.length > 0);
2267
+ }
2268
+ function Select({
2269
+ label,
2270
+ hint,
2271
+ error,
2272
+ controlSize = "md",
2273
+ id,
2274
+ disabled,
2275
+ style,
2276
+ children,
2277
+ value,
2278
+ defaultValue,
2279
+ onChange,
2280
+ onFocus,
2281
+ onBlur,
2282
+ onKeyDown,
2283
+ onClick,
2284
+ autoFocus,
2285
+ className,
2286
+ isMulti,
2287
+ isFilterable,
2288
+ multiple,
2289
+ ...rest
2290
+ }) {
2291
+ var _a, _b, _c, _d, _e, _f, _g, _h;
2292
+ const { theme, mutedText, surfaceRaised } = useUiKitTheme();
2293
+ const generatedId = (0, import_react8.useId)().replace(/:/g, "");
2294
+ const resolvedId = id != null ? id : `ss-select-${generatedId}`;
2295
+ const isMultiSelect = Boolean(isMulti || multiple);
2296
+ const hasFilter = Boolean(isFilterable);
2297
+ const groups = parseSelectChildren(children);
2298
+ const flatOptions = groups.flatMap((group) => group.options);
2299
+ const enabledOptions = flatOptions.filter((option) => !option.disabled);
2300
+ const isControlled = value !== void 0;
2301
+ const fallbackValues = defaultValue !== void 0 ? normalizeSelectValues(defaultValue) : isMultiSelect ? [] : ((_a = enabledOptions[0]) == null ? void 0 : _a.value) ? [enabledOptions[0].value] : [];
2302
+ const [uncontrolledValues, setUncontrolledValues] = (0, import_react8.useState)(fallbackValues);
2303
+ const [isOpen, setIsOpen] = (0, import_react8.useState)(false);
2304
+ const [filterQuery, setFilterQuery] = (0, import_react8.useState)("");
2305
+ const [menuPlacement, setMenuPlacement] = (0, import_react8.useState)("bottom");
2306
+ const [isPlacementResolved, setIsPlacementResolved] = (0, import_react8.useState)(false);
2307
+ const [highlightedValue, setHighlightedValue] = (0, import_react8.useState)((_d = (_c = fallbackValues[0]) != null ? _c : (_b = enabledOptions[0]) == null ? void 0 : _b.value) != null ? _d : "");
2308
+ const shellRef = (0, import_react8.useRef)(null);
2309
+ const buttonRef = (0, import_react8.useRef)(null);
2310
+ const menuRef = (0, import_react8.useRef)(null);
2311
+ const searchInputRef = (0, import_react8.useRef)(null);
2312
+ const hiddenSelectRef = (0, import_react8.useRef)(null);
2313
+ const selectedValues = isControlled ? normalizeSelectValues(value) : uncontrolledValues;
2314
+ const selectedValue = (_e = selectedValues[0]) != null ? _e : "";
2315
+ const selectedOptions = flatOptions.filter((option) => selectedValues.includes(option.value));
2316
+ const selectedOption = (_f = selectedOptions[0]) != null ? _f : null;
2317
+ const displayLabel = isMultiSelect ? selectedOptions.length > 0 ? selectedOptions.map((option) => getTextContent(option.label).trim() || option.value).join(", ") : null : (_g = selectedOption == null ? void 0 : selectedOption.label) != null ? _g : selectedValue ? selectedValue : null;
2318
+ const displayContent = displayLabel != null ? displayLabel : "\xA0";
2319
+ const visibleGroups = hasFilter ? filterSelectGroups(groups, filterQuery) : groups;
2320
+ const visibleOptions = visibleGroups.flatMap((group) => group.options);
2321
+ const visibleEnabledOptions = visibleOptions.filter((option) => !option.disabled);
2322
+ const visibleEnabledValues = visibleEnabledOptions.map((option) => option.value);
2323
+ const selectedValuesKey = selectedValues.join("\0");
2324
+ const visibleEnabledValuesKey = visibleEnabledValues.join("\0");
2325
+ const indicatorOffset = getSelectIndicatorOffset(controlSize);
2326
+ const controlMetrics = getControlMetrics(controlSize);
2327
+ const listboxId = `${resolvedId}-listbox`;
2328
+ const scrollbarThumbColor = withAlpha(theme.colors.onSurface, 0.22);
2329
+ const scrollbarThumbHoverColor = withAlpha(theme.colors.onSurface, 0.32);
2330
+ const highlightedOption = (_h = visibleOptions.find((option) => option.value === highlightedValue)) != null ? _h : null;
2331
+ const optionHoverBackground = withAlpha(theme.colors.primary, 1);
2332
+ const optionSelectedBackground = withAlpha(theme.colors.primary, 1);
2333
+ const triggerShadow = error ? `0 0 0 3px ${withAlpha(theme.colors.negative, 0.12)}` : "none";
2334
+ const buttonPassthroughProps = Object.fromEntries(
2335
+ Object.entries(rest).filter(([key]) => key.startsWith("aria-") || key.startsWith("data-"))
2336
+ );
2337
+ (0, import_react8.useEffect)(() => {
2338
+ var _a2, _b2, _c2, _d2;
2339
+ if (isControlled) {
2340
+ return;
2341
+ }
2342
+ const invalidValues = uncontrolledValues.filter(
2343
+ (currentValue) => !flatOptions.some((option) => option.value === currentValue)
2344
+ );
2345
+ if (invalidValues.length === 0) {
2346
+ return;
2347
+ }
2348
+ const nextValues = defaultValue !== void 0 ? normalizeSelectValues(defaultValue).filter(
2349
+ (currentValue) => flatOptions.some((option) => option.value === currentValue)
2350
+ ) : isMultiSelect ? [] : ((_a2 = enabledOptions[0]) == null ? void 0 : _a2.value) ? [enabledOptions[0].value] : [];
2351
+ setUncontrolledValues(nextValues);
2352
+ setHighlightedValue((_d2 = (_c2 = nextValues[0]) != null ? _c2 : (_b2 = enabledOptions[0]) == null ? void 0 : _b2.value) != null ? _d2 : "");
2353
+ }, [children, defaultValue, enabledOptions, flatOptions, isControlled, isMultiSelect, uncontrolledValues]);
2354
+ (0, import_react8.useEffect)(() => {
2355
+ var _a2, _b2, _c2;
2356
+ if (!isOpen) {
2357
+ setMenuPlacement("bottom");
2358
+ setIsPlacementResolved(false);
2359
+ return;
2360
+ }
2361
+ const nextHighlightedValue = (_c2 = (_b2 = [...selectedValues].reverse().find((currentValue) => visibleEnabledValues.includes(currentValue))) != null ? _b2 : (_a2 = visibleEnabledOptions[0]) == null ? void 0 : _a2.value) != null ? _c2 : "";
2362
+ setHighlightedValue(nextHighlightedValue);
2363
+ }, [isOpen, selectedValuesKey, visibleEnabledValuesKey]);
2364
+ (0, import_react8.useEffect)(() => {
2365
+ var _a2;
2366
+ if (!isOpen || !hasFilter) {
2367
+ return;
2368
+ }
2369
+ (_a2 = searchInputRef.current) == null ? void 0 : _a2.focus();
2370
+ }, [hasFilter, isOpen]);
2371
+ (0, import_react8.useEffect)(() => {
2372
+ if (!isOpen) {
2373
+ return;
2374
+ }
2375
+ const frameId = window.requestAnimationFrame(() => {
2376
+ var _a2;
2377
+ const triggerRect = (_a2 = buttonRef.current) == null ? void 0 : _a2.getBoundingClientRect();
2378
+ const menuElement = menuRef.current;
2379
+ if (!triggerRect || !menuElement) {
2380
+ setMenuPlacement("bottom");
2381
+ setIsPlacementResolved(true);
2382
+ return;
2383
+ }
2384
+ const measuredMenuHeight = menuElement.getBoundingClientRect().height;
2385
+ const menuHeight = Math.min(measuredMenuHeight || menuElement.scrollHeight, MENU_MAX_HEIGHT);
2386
+ const availableBelow = window.innerHeight - triggerRect.bottom - MENU_OFFSET;
2387
+ const availableAbove = triggerRect.top - MENU_OFFSET;
2388
+ const nextPlacement = availableBelow < menuHeight && availableAbove >= menuHeight ? "top" : "bottom";
2389
+ setMenuPlacement(nextPlacement);
2390
+ setIsPlacementResolved(true);
2391
+ });
2392
+ return () => {
2393
+ window.cancelAnimationFrame(frameId);
2394
+ };
2395
+ }, [isOpen, visibleOptions.length]);
2396
+ (0, import_react8.useEffect)(() => {
2397
+ if (isOpen) {
2398
+ return;
2399
+ }
2400
+ setFilterQuery("");
2401
+ }, [isOpen]);
2402
+ (0, import_react8.useEffect)(() => {
2403
+ if (!isOpen) {
2404
+ return;
2405
+ }
2406
+ const handlePointerDown = (event) => {
2407
+ var _a2;
2408
+ if ((_a2 = shellRef.current) == null ? void 0 : _a2.contains(event.target)) {
2409
+ return;
2410
+ }
2411
+ setIsOpen(false);
2412
+ };
2413
+ document.addEventListener("pointerdown", handlePointerDown);
2414
+ return () => {
2415
+ document.removeEventListener("pointerdown", handlePointerDown);
2416
+ };
2417
+ }, [isOpen]);
2418
+ (0, import_react8.useEffect)(() => {
2419
+ var _a2;
2420
+ if (isControlled) {
2421
+ return;
2422
+ }
2423
+ const form = (_a2 = hiddenSelectRef.current) == null ? void 0 : _a2.form;
2424
+ if (!form) {
2425
+ return;
2426
+ }
2427
+ const handleReset = () => {
2428
+ queueMicrotask(() => {
2429
+ var _a3, _b2, _c2, _d2;
2430
+ const nextValues = defaultValue !== void 0 ? normalizeSelectValues(defaultValue) : isMultiSelect ? [] : ((_a3 = enabledOptions[0]) == null ? void 0 : _a3.value) ? [enabledOptions[0].value] : [];
2431
+ setUncontrolledValues(nextValues);
2432
+ setHighlightedValue((_d2 = (_c2 = nextValues[0]) != null ? _c2 : (_b2 = enabledOptions[0]) == null ? void 0 : _b2.value) != null ? _d2 : "");
2433
+ setIsOpen(false);
2434
+ });
2435
+ };
2436
+ form.addEventListener("reset", handleReset);
2437
+ return () => {
2438
+ form.removeEventListener("reset", handleReset);
2439
+ };
2440
+ }, [defaultValue, enabledOptions, isControlled, isMultiSelect]);
2441
+ const syncHiddenSelect = (nextValues) => {
2442
+ var _a2, _b2;
2443
+ const selectElement = hiddenSelectRef.current;
2444
+ if (!selectElement) {
2445
+ return;
2446
+ }
2447
+ if (isMultiSelect) {
2448
+ Array.from(selectElement.options).forEach((optionElement) => {
2449
+ optionElement.selected = nextValues.includes(optionElement.value);
2450
+ });
2451
+ } else {
2452
+ const descriptor = Object.getOwnPropertyDescriptor(HTMLSelectElement.prototype, "value");
2453
+ if (descriptor == null ? void 0 : descriptor.set) {
2454
+ descriptor.set.call(selectElement, (_a2 = nextValues[0]) != null ? _a2 : "");
2455
+ } else {
2456
+ selectElement.value = (_b2 = nextValues[0]) != null ? _b2 : "";
2457
+ }
2458
+ }
2459
+ selectElement.dispatchEvent(new Event("change", { bubbles: true }));
2460
+ };
2461
+ const updateMultiSelection = (nextValues) => {
2462
+ var _a2, _b2, _c2;
2463
+ if (!isMultiSelect || disabled) {
2464
+ return;
2465
+ }
2466
+ if (!isControlled) {
2467
+ setUncontrolledValues(nextValues);
2468
+ }
2469
+ setHighlightedValue((_c2 = (_b2 = nextValues[nextValues.length - 1]) != null ? _b2 : (_a2 = enabledOptions[0]) == null ? void 0 : _a2.value) != null ? _c2 : "");
2470
+ syncHiddenSelect(nextValues);
2471
+ };
2472
+ const toggleVisibleSelection = (checked) => {
2473
+ if (!isMultiSelect || disabled || visibleEnabledValues.length === 0) {
2474
+ return;
2475
+ }
2476
+ const nextValues = checked ? Array.from(/* @__PURE__ */ new Set([...selectedValues, ...visibleEnabledValues])) : selectedValues.filter((currentValue) => !visibleEnabledValues.includes(currentValue));
2477
+ updateMultiSelection(nextValues);
2478
+ };
2479
+ const commitSelection = (nextValue) => {
2480
+ var _a2;
2481
+ if (disabled) {
2482
+ if (!isMultiSelect) {
2483
+ setIsOpen(false);
2484
+ }
2485
+ return;
2486
+ }
2487
+ if (isMultiSelect) {
2488
+ const nextValues = selectedValues.includes(nextValue) ? selectedValues.filter((currentValue) => currentValue !== nextValue) : [...selectedValues, nextValue];
2489
+ updateMultiSelection(nextValues);
2490
+ return;
2491
+ }
2492
+ if (nextValue === selectedValue) {
2493
+ setIsOpen(false);
2494
+ return;
2495
+ }
2496
+ if (!isControlled) {
2497
+ setUncontrolledValues([nextValue]);
2498
+ }
2499
+ setHighlightedValue(nextValue);
2500
+ setIsOpen(false);
2501
+ syncHiddenSelect([nextValue]);
2502
+ (_a2 = buttonRef.current) == null ? void 0 : _a2.focus();
2503
+ };
2504
+ const handleHiddenSelectChange = (event) => {
2505
+ var _a2, _b2, _c2;
2506
+ const nextValues = isMultiSelect ? Array.from(event.currentTarget.selectedOptions, (option) => option.value) : [event.currentTarget.value];
2507
+ if (!isControlled) {
2508
+ setUncontrolledValues(nextValues);
2509
+ }
2510
+ setHighlightedValue((_c2 = (_b2 = nextValues[nextValues.length - 1]) != null ? _b2 : (_a2 = enabledOptions[0]) == null ? void 0 : _a2.value) != null ? _c2 : "");
2511
+ onChange == null ? void 0 : onChange(event);
2512
+ };
2513
+ const visibleSelectedCount = visibleEnabledValues.filter((currentValue) => selectedValues.includes(currentValue)).length;
2514
+ const hasSomeVisibleSelected = visibleSelectedCount > 0 && visibleSelectedCount < visibleEnabledValues.length;
2515
+ const hasAllVisibleSelected = visibleEnabledValues.length > 0 && visibleSelectedCount === visibleEnabledValues.length;
2516
+ const canToggleVisibleSelection = !disabled && visibleEnabledValues.length > 0;
2517
+ const bulkToggleControl = isMultiSelect ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2518
+ "button",
2519
+ {
2520
+ type: "button",
2521
+ role: "checkbox",
2522
+ "aria-label": "Toggle all visible options",
2523
+ "aria-checked": hasAllVisibleSelected ? true : hasSomeVisibleSelected ? "mixed" : false,
2524
+ disabled: !canToggleVisibleSelection,
2525
+ onMouseDown: (event) => {
2526
+ event.preventDefault();
2527
+ event.stopPropagation();
2528
+ },
2529
+ onClick: (event) => {
2530
+ event.stopPropagation();
2531
+ toggleVisibleSelection(!hasAllVisibleSelected);
2532
+ },
748
2533
  style: {
749
- background: `${modalTheme.colors.background}90`,
750
- backdropFilter: "blur(2px)",
751
- width: "100%",
752
- height: "100%",
753
- position: "fixed",
754
- zIndex: 999,
755
- top: 0,
756
- left: 0,
757
- display: "flex",
758
- justifyContent: "center",
2534
+ width: 20,
2535
+ height: 20,
2536
+ padding: 0,
2537
+ margin: 0,
2538
+ display: "inline-flex",
759
2539
  alignItems: "center",
760
- boxSizing: "border-box"
2540
+ justifyContent: "center",
2541
+ flexShrink: 0,
2542
+ border: "none",
2543
+ background: "transparent",
2544
+ cursor: canToggleVisibleSelection ? "pointer" : "not-allowed",
2545
+ opacity: canToggleVisibleSelection ? 1 : 0.45
761
2546
  },
762
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2547
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2548
+ CheckboxIndicator,
2549
+ {
2550
+ checked: hasAllVisibleSelected,
2551
+ indeterminate: hasSomeVisibleSelected,
2552
+ disabled: !canToggleVisibleSelection,
2553
+ checkedColor: theme.colors.secondary,
2554
+ iconColor: theme.colors.surface,
2555
+ uncheckedBorderColor: withAlpha(theme.colors.onSurface, 0.22),
2556
+ uncheckedBackgroundColor: theme.colors.surface
2557
+ }
2558
+ )
2559
+ }
2560
+ ) : null;
2561
+ const moveHighlight = (direction) => {
2562
+ if (visibleEnabledOptions.length === 0) {
2563
+ return;
2564
+ }
2565
+ const currentIndex = visibleEnabledOptions.findIndex((option) => option.value === highlightedValue);
2566
+ if (currentIndex === -1) {
2567
+ setHighlightedValue(
2568
+ direction === 1 ? visibleEnabledOptions[0].value : visibleEnabledOptions[visibleEnabledOptions.length - 1].value
2569
+ );
2570
+ return;
2571
+ }
2572
+ const nextIndex = Math.max(0, Math.min(visibleEnabledOptions.length - 1, currentIndex + direction));
2573
+ setHighlightedValue(visibleEnabledOptions[nextIndex].value);
2574
+ };
2575
+ const handleTriggerClick = (event) => {
2576
+ onClick == null ? void 0 : onClick(event);
2577
+ if (event.defaultPrevented || disabled || flatOptions.length === 0) {
2578
+ return;
2579
+ }
2580
+ setIsOpen((current) => !current);
2581
+ };
2582
+ const handleTriggerFocus = (event) => {
2583
+ onFocus == null ? void 0 : onFocus(event);
2584
+ };
2585
+ const handleTriggerBlur = (event) => {
2586
+ var _a2;
2587
+ const nextTarget = event.relatedTarget;
2588
+ if (!((_a2 = shellRef.current) == null ? void 0 : _a2.contains(nextTarget))) {
2589
+ setIsOpen(false);
2590
+ }
2591
+ onBlur == null ? void 0 : onBlur(event);
2592
+ };
2593
+ const handleTriggerKeyDown = (event) => {
2594
+ var _a2, _b2, _c2;
2595
+ onKeyDown == null ? void 0 : onKeyDown(event);
2596
+ if (event.defaultPrevented || disabled || visibleEnabledOptions.length === 0) {
2597
+ return;
2598
+ }
2599
+ if (event.key === "ArrowDown") {
2600
+ event.preventDefault();
2601
+ if (!isOpen) {
2602
+ setIsOpen(true);
2603
+ setHighlightedValue((_a2 = selectedOption == null ? void 0 : selectedOption.value) != null ? _a2 : visibleEnabledOptions[0].value);
2604
+ return;
2605
+ }
2606
+ moveHighlight(1);
2607
+ return;
2608
+ }
2609
+ if (event.key === "ArrowUp") {
2610
+ event.preventDefault();
2611
+ if (!isOpen) {
2612
+ setIsOpen(true);
2613
+ setHighlightedValue((_b2 = selectedOption == null ? void 0 : selectedOption.value) != null ? _b2 : visibleEnabledOptions[visibleEnabledOptions.length - 1].value);
2614
+ return;
2615
+ }
2616
+ moveHighlight(-1);
2617
+ return;
2618
+ }
2619
+ if (event.key === "Home" && isOpen) {
2620
+ event.preventDefault();
2621
+ setHighlightedValue(visibleEnabledOptions[0].value);
2622
+ return;
2623
+ }
2624
+ if (event.key === "End" && isOpen) {
2625
+ event.preventDefault();
2626
+ setHighlightedValue(visibleEnabledOptions[visibleEnabledOptions.length - 1].value);
2627
+ return;
2628
+ }
2629
+ if (event.key === "Enter" || event.key === " ") {
2630
+ event.preventDefault();
2631
+ if (!isOpen) {
2632
+ setIsOpen(true);
2633
+ setHighlightedValue((_c2 = selectedOption == null ? void 0 : selectedOption.value) != null ? _c2 : visibleEnabledOptions[0].value);
2634
+ return;
2635
+ }
2636
+ if (highlightedOption && !highlightedOption.disabled) {
2637
+ commitSelection(highlightedOption.value);
2638
+ }
2639
+ return;
2640
+ }
2641
+ if (event.key === "Escape" && isOpen) {
2642
+ event.preventDefault();
2643
+ setIsOpen(false);
2644
+ return;
2645
+ }
2646
+ if (event.key === "Tab") {
2647
+ setIsOpen(false);
2648
+ }
2649
+ };
2650
+ const handleFilterKeyDown = (event) => {
2651
+ var _a2;
2652
+ if (event.key === "ArrowDown") {
2653
+ event.preventDefault();
2654
+ moveHighlight(1);
2655
+ return;
2656
+ }
2657
+ if (event.key === "ArrowUp") {
2658
+ event.preventDefault();
2659
+ moveHighlight(-1);
2660
+ return;
2661
+ }
2662
+ if (event.key === "Enter") {
2663
+ event.preventDefault();
2664
+ if (highlightedOption && !highlightedOption.disabled) {
2665
+ commitSelection(highlightedOption.value);
2666
+ }
2667
+ return;
2668
+ }
2669
+ if (event.key === "Escape") {
2670
+ event.preventDefault();
2671
+ setIsOpen(false);
2672
+ (_a2 = buttonRef.current) == null ? void 0 : _a2.focus();
2673
+ return;
2674
+ }
2675
+ if (event.key === "Tab") {
2676
+ setIsOpen(false);
2677
+ }
2678
+ };
2679
+ const select = /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { ref: shellRef, style: { position: "relative", width: "100%" }, children: [
2680
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2681
+ "button",
2682
+ {
2683
+ ...buttonPassthroughProps,
2684
+ id: resolvedId,
2685
+ ref: buttonRef,
2686
+ type: "button",
2687
+ className,
2688
+ title: rest.title,
2689
+ disabled: disabled || flatOptions.length === 0,
2690
+ autoFocus,
2691
+ tabIndex: rest.tabIndex,
2692
+ role: "combobox",
2693
+ "aria-controls": listboxId,
2694
+ "aria-expanded": isOpen,
2695
+ "aria-haspopup": "listbox",
2696
+ "aria-invalid": error ? true : rest["aria-invalid"],
2697
+ "aria-required": rest.required,
2698
+ onClick: handleTriggerClick,
2699
+ onFocus: handleTriggerFocus,
2700
+ onBlur: handleTriggerBlur,
2701
+ onKeyDown: handleTriggerKeyDown,
2702
+ style: {
2703
+ width: "100%",
2704
+ boxSizing: "border-box",
2705
+ height: controlMetrics.height,
2706
+ borderRadius: 16,
2707
+ border: `1px solid ${error ? theme.colors.negative : withAlpha(theme.colors.textLight, 0.5)}`,
2708
+ backgroundColor: theme.colors.surface,
2709
+ color: displayLabel ? theme.colors.onSurface : mutedText,
2710
+ cursor: disabled ? "not-allowed" : "pointer",
2711
+ opacity: disabled ? 0.7 : 1,
2712
+ padding: getSelectControlPadding(controlSize),
2713
+ outline: "none",
2714
+ font: "inherit",
2715
+ fontSize: controlMetrics.fontSize,
2716
+ lineHeight: controlMetrics.lineHeight,
2717
+ transition: "border-color 160ms ease, box-shadow 160ms ease, background-color 160ms ease",
2718
+ display: "flex",
2719
+ alignItems: "center",
2720
+ justifyContent: "space-between",
2721
+ gap: 12,
2722
+ minWidth: 0,
2723
+ textAlign: "left",
2724
+ ...style
2725
+ },
2726
+ children: [
2727
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2728
+ "span",
2729
+ {
2730
+ style: {
2731
+ display: "block",
2732
+ flex: "1 1 auto",
2733
+ minWidth: 0,
2734
+ overflow: "hidden",
2735
+ textOverflow: "ellipsis",
2736
+ whiteSpace: "nowrap"
2737
+ },
2738
+ children: displayContent
2739
+ }
2740
+ ),
2741
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2742
+ "span",
2743
+ {
2744
+ "aria-hidden": "true",
2745
+ style: {
2746
+ position: "absolute",
2747
+ right: indicatorOffset,
2748
+ top: "50%",
2749
+ transform: `translateY(-50%) rotate(${isOpen ? 180 : 0}deg)`,
2750
+ transformOrigin: "center",
2751
+ color: disabled ? withAlpha(theme.colors.onSurface, 0.42) : mutedText,
2752
+ display: "flex",
2753
+ alignItems: "center",
2754
+ justifyContent: "center",
2755
+ pointerEvents: "none",
2756
+ transition: "transform 160ms ease, color 160ms ease"
2757
+ },
2758
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("svg", { width: "18", height: "18", viewBox: "0 0 20 20", fill: "none", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2759
+ "path",
2760
+ {
2761
+ d: "M5.75 7.75L10 12l4.25-4.25",
2762
+ stroke: "currentColor",
2763
+ strokeWidth: "1.8",
2764
+ strokeLinecap: "round",
2765
+ strokeLinejoin: "round"
2766
+ }
2767
+ ) })
2768
+ }
2769
+ )
2770
+ ]
2771
+ }
2772
+ ),
2773
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2774
+ "select",
2775
+ {
2776
+ ...rest,
2777
+ id: `${resolvedId}-native`,
2778
+ ref: hiddenSelectRef,
2779
+ name: rest.name,
2780
+ disabled,
2781
+ multiple: isMultiSelect,
2782
+ value: isMultiSelect ? selectedValues : selectedValue,
2783
+ onChange: handleHiddenSelectChange,
2784
+ tabIndex: -1,
2785
+ "aria-hidden": "true",
2786
+ style: {
2787
+ position: "absolute",
2788
+ width: 1,
2789
+ height: 1,
2790
+ padding: 0,
2791
+ margin: 0,
2792
+ border: 0,
2793
+ opacity: 0,
2794
+ pointerEvents: "none",
2795
+ clipPath: "inset(50%)"
2796
+ },
2797
+ children
2798
+ }
2799
+ ),
2800
+ isOpen ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
2801
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("style", { children: `
2802
+ #${listboxId} {
2803
+ scrollbar-width: thin;
2804
+ scrollbar-color: ${scrollbarThumbColor} transparent;
2805
+ }
2806
+
2807
+ #${listboxId}::-webkit-scrollbar {
2808
+ width: 8px;
2809
+ }
2810
+
2811
+ #${listboxId}::-webkit-scrollbar-track {
2812
+ background: transparent;
2813
+ }
2814
+
2815
+ #${listboxId}::-webkit-scrollbar-thumb {
2816
+ background: ${scrollbarThumbColor};
2817
+ border-radius: 999px;
2818
+ border: 2px solid transparent;
2819
+ background-clip: padding-box;
2820
+ }
2821
+
2822
+ #${listboxId}::-webkit-scrollbar-thumb:hover {
2823
+ background: ${scrollbarThumbHoverColor};
2824
+ background-clip: padding-box;
2825
+ }
2826
+ ` }),
2827
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
763
2828
  "div",
764
2829
  {
765
- id: isLoading ? "ldg1" : void 0,
2830
+ ref: menuRef,
766
2831
  style: {
767
- position: isMaximized ? "fixed" : "relative",
768
- left: isMaximized ? 0 : void 0,
769
- top: isMaximized ? 0 : void 0,
770
- background: modalTheme.colors.background,
2832
+ position: "absolute",
2833
+ top: menuPlacement === "bottom" ? `calc(100% + ${MENU_OFFSET}px)` : "auto",
2834
+ bottom: menuPlacement === "top" ? `calc(100% + ${MENU_OFFSET}px)` : "auto",
2835
+ left: 0,
2836
+ right: 0,
2837
+ zIndex: 30,
2838
+ maxHeight: MENU_MAX_HEIGHT,
2839
+ overflow: "hidden",
2840
+ boxSizing: "border-box",
2841
+ borderRadius: 20,
2842
+ border: `1px solid ${withAlpha(theme.colors.textLight, 0.5)}`,
2843
+ backgroundColor: surfaceRaised,
2844
+ // boxShadow: `0 10px 24px ${withAlpha(theme.colors.onSurface, 0.08)}`,
2845
+ padding: 8,
2846
+ backdropFilter: "blur(6px)",
2847
+ visibility: isPlacementResolved ? "visible" : "hidden",
771
2848
  display: "flex",
772
- alignItems: "center",
773
2849
  flexDirection: "column",
774
- borderRadius: "0.8rem",
775
- boxSizing: "border-box",
776
- maxHeight: isMaximized ? "100vh" : "80vh",
777
- maxWidth: isMaximized ? "100vw" : "80vw",
778
- minWidth: isMaximized ? "100vw" : 300,
779
- minHeight: isMaximized ? "100vh" : void 0,
780
- width: isMaximized ? "100vw" : void 0,
781
- boxShadow: "0 24px 64px rgba(15, 23, 42, 0.18)",
782
- ...isLoading ? { opacity: 0.5 } : null,
783
- ...style
2850
+ gap: 4
784
2851
  },
785
2852
  children: [
786
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2853
+ hasFilter ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
787
2854
  "div",
788
2855
  {
789
2856
  style: {
790
- background: modalTheme.colors.surface,
791
- color: modalTheme.colors.onSurface,
792
- position: "absolute",
793
- top: 0,
794
- zIndex: 500,
795
2857
  display: "flex",
796
- justifyContent: "space-between",
797
2858
  alignItems: "center",
798
- width: "100%",
799
- height: "4rem",
800
- padding: "1rem",
801
- boxSizing: "border-box",
802
- borderTopLeftRadius: "0.8rem",
803
- borderTopRightRadius: "0.8rem",
804
- borderBottom: `1px solid ${withAlpha4(modalTheme.colors.onBackground, 0.1)}`
2859
+ justifyContent: "flex-start",
2860
+ gap: 8,
2861
+ padding: "4px 4px 10px",
2862
+ borderBottom: `1px solid ${withAlpha(theme.colors.onSurface, 0.08)}`,
2863
+ flexShrink: 0
805
2864
  },
806
2865
  children: [
807
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { id: "modal-title", style: { fontWeight: "bold", fontSize: "1.3rem" }, children: title }),
808
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
809
- "div",
2866
+ bulkToggleControl,
2867
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2868
+ "input",
810
2869
  {
811
- id: "modal-header-icons",
812
- style: { display: "flex", alignItems: "center", gap: "0.5rem" },
813
- children: [
814
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
815
- "button",
816
- {
817
- "aria-label": isMaximized ? "Minimize modal" : "Maximize modal",
818
- title: isMaximized ? "Minimize" : "Maximize",
819
- type: "button",
820
- onClick: () => setIsMaximized((current) => !current),
821
- style: {
822
- border: "none",
823
- background: "transparent",
824
- color: modalTheme.colors.onSurface,
825
- cursor: "pointer",
826
- fontSize: 18,
827
- lineHeight: 1,
828
- padding: 0
829
- },
830
- children: isMaximized ? "\u25A2" : "\u25A1"
831
- }
832
- ),
833
- !hideCloseIcon ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
834
- "button",
835
- {
836
- "aria-label": "Close modal",
837
- title: "Close",
838
- type: "button",
839
- onClick: isLoading ? void 0 : closeModal,
840
- style: {
841
- border: "none",
842
- background: "transparent",
843
- color: modalTheme.colors.onSurface,
844
- cursor: isLoading ? "not-allowed" : "pointer",
845
- fontSize: 22,
846
- lineHeight: 1,
847
- padding: 0,
848
- opacity: isLoading ? 0.5 : 1
849
- },
850
- children: "\xD7"
851
- }
852
- ) : null
853
- ]
2870
+ ref: searchInputRef,
2871
+ type: "search",
2872
+ value: filterQuery,
2873
+ onChange: (event) => {
2874
+ setFilterQuery(event.currentTarget.value);
2875
+ },
2876
+ onKeyDown: handleFilterKeyDown,
2877
+ onMouseDown: (event) => {
2878
+ event.stopPropagation();
2879
+ },
2880
+ placeholder: "Search options",
2881
+ "aria-label": "Search options",
2882
+ style: {
2883
+ flex: "1 1 auto",
2884
+ width: "100%",
2885
+ border: `1px solid ${withAlpha(theme.colors.textLight, 0.5)}`,
2886
+ backgroundColor: theme.colors.surface,
2887
+ padding: "8px 12px",
2888
+ borderRadius: 12,
2889
+ font: "inherit",
2890
+ fontSize: 12,
2891
+ lineHeight: 1.4,
2892
+ color: theme.colors.textLight,
2893
+ outline: "none",
2894
+ boxSizing: "border-box"
2895
+ }
854
2896
  }
855
2897
  )
856
2898
  ]
857
2899
  }
858
- ),
859
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2900
+ ) : isMultiSelect ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
860
2901
  "div",
861
2902
  {
862
2903
  style: {
863
- scrollbarWidth: "thin",
864
- width: "100%",
865
- maxWidth: "100%",
866
- paddingTop: "4rem",
867
- paddingBottom: "4rem",
868
- overflow: "auto",
869
- ...bodyStyle
2904
+ display: "flex",
2905
+ alignItems: "center",
2906
+ justifyContent: "flex-start",
2907
+ padding: "4px 4px 10px",
2908
+ borderBottom: `1px solid ${withAlpha(theme.colors.onSurface, 0.08)}`,
2909
+ flexShrink: 0
870
2910
  },
871
- children
2911
+ children: bulkToggleControl
872
2912
  }
873
- ),
874
- onlyCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2913
+ ) : null,
2914
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
875
2915
  "div",
876
2916
  {
2917
+ id: listboxId,
2918
+ role: "listbox",
2919
+ "aria-labelledby": resolvedId,
2920
+ "aria-multiselectable": isMultiSelect || void 0,
877
2921
  style: {
878
- background: modalTheme.colors.surface,
879
- color: modalTheme.colors.onSurface,
880
- position: "absolute",
881
- bottom: 0,
882
- width: "100%",
883
- height: "4rem",
884
- padding: "0.5rem",
885
- borderTop: `1px solid ${withAlpha4(modalTheme.colors.onBackground, 0.1)}`,
886
- boxSizing: "border-box",
887
- borderBottomLeftRadius: "0.8rem",
888
- borderBottomRightRadius: "0.8rem"
2922
+ overflowY: "auto",
2923
+ overflowX: "hidden",
2924
+ minHeight: 0,
2925
+ flex: "1 1 auto"
889
2926
  },
890
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2927
+ children: visibleGroups.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
891
2928
  "div",
892
2929
  {
893
- className: "buttons-footer",
894
- style: { flex: 1, display: "flex", justifyContent: "end" },
895
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
896
- LegacyActionButton,
897
- {
898
- label: "Close",
899
- onClick: closeModal,
900
- theme: modalTheme,
901
- disabled: isLoading
902
- }
903
- )
2930
+ style: {
2931
+ padding: "14px 12px",
2932
+ color: mutedText,
2933
+ fontSize: 13,
2934
+ textAlign: "center"
2935
+ },
2936
+ children: "No options found."
904
2937
  }
905
- )
906
- }
907
- ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
908
- "div",
909
- {
910
- style: {
911
- background: modalTheme.colors.surface,
912
- color: modalTheme.colors.onSurface,
913
- position: "absolute",
914
- bottom: 0,
915
- width: "100%",
916
- height: "4rem",
917
- padding: "0.5rem",
918
- borderTop: `1px solid ${withAlpha4(modalTheme.colors.onBackground, 0.1)}`,
919
- boxSizing: "border-box",
920
- borderBottomLeftRadius: "0.8rem",
921
- borderBottomRightRadius: "0.8rem"
922
- },
923
- children: cancelButton ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2938
+ ) : visibleGroups.map((group) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
924
2939
  "div",
925
2940
  {
926
- className: "buttons-footer",
927
- style: { flex: 1, display: "flex", justifyContent: "end" },
2941
+ style: {
2942
+ display: "flex",
2943
+ flexDirection: "column",
2944
+ gap: 4,
2945
+ marginTop: group.label ? 4 : 0
2946
+ },
928
2947
  children: [
929
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
930
- LegacyActionButton,
2948
+ group.label ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2949
+ "span",
931
2950
  {
932
- label: "Cancel",
933
- onClick: cancelButton,
934
- type: "text",
935
- style: { maxWidth: "100px" },
936
- theme: modalTheme,
937
- disabled: false
2951
+ style: {
2952
+ padding: "6px 8px 2px",
2953
+ color: mutedText,
2954
+ fontSize: 11,
2955
+ fontWeight: 800,
2956
+ letterSpacing: "0.08em",
2957
+ textTransform: "uppercase"
2958
+ },
2959
+ children: group.label
938
2960
  }
939
- ),
940
- actionButtons()
2961
+ ) : null,
2962
+ group.options.map((option) => {
2963
+ const isSelected = selectedValues.includes(option.value);
2964
+ const isHighlighted = option.value === highlightedValue;
2965
+ const isActive = isSelected || isHighlighted;
2966
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2967
+ "div",
2968
+ {
2969
+ role: "option",
2970
+ "aria-selected": isSelected,
2971
+ "aria-disabled": option.disabled,
2972
+ onMouseEnter: () => {
2973
+ if (!option.disabled) {
2974
+ setHighlightedValue(option.value);
2975
+ }
2976
+ },
2977
+ onMouseDown: (event) => {
2978
+ event.preventDefault();
2979
+ },
2980
+ onClick: () => {
2981
+ if (!option.disabled) {
2982
+ commitSelection(option.value);
2983
+ }
2984
+ },
2985
+ style: {
2986
+ display: "flex",
2987
+ alignItems: "center",
2988
+ justifyContent: "space-between",
2989
+ gap: 12,
2990
+ width: "100%",
2991
+ minWidth: 0,
2992
+ boxSizing: "border-box",
2993
+ borderRadius: 14,
2994
+ padding: getOptionPadding(controlSize),
2995
+ cursor: option.disabled ? "not-allowed" : "pointer",
2996
+ color: option.disabled ? withAlpha(theme.colors.onSurface, 0.38) : isActive ? theme.colors.onPrimary : theme.colors.onSurface,
2997
+ backgroundColor: isSelected ? optionSelectedBackground : isHighlighted ? optionHoverBackground : "transparent",
2998
+ fontWeight: isSelected ? 700 : 500,
2999
+ transition: "background-color 140ms ease, color 140ms ease"
3000
+ },
3001
+ children: [
3002
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3003
+ "span",
3004
+ {
3005
+ style: {
3006
+ display: "flex",
3007
+ alignItems: "center",
3008
+ gap: 10,
3009
+ flex: "1 1 auto",
3010
+ minWidth: 0
3011
+ },
3012
+ children: [
3013
+ isMultiSelect ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3014
+ CheckboxIndicator,
3015
+ {
3016
+ checked: isSelected,
3017
+ disabled: option.disabled,
3018
+ size: 16,
3019
+ checkedColor: isActive ? theme.colors.onPrimary : theme.colors.primary,
3020
+ iconColor: isActive ? theme.colors.primary : theme.colors.onPrimary,
3021
+ uncheckedBorderColor: isActive ? theme.colors.onPrimary : withAlpha(theme.colors.onSurface, 0.26),
3022
+ uncheckedBackgroundColor: "transparent"
3023
+ }
3024
+ ) : null,
3025
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3026
+ "span",
3027
+ {
3028
+ style: {
3029
+ display: "block",
3030
+ flex: "1 1 auto",
3031
+ minWidth: 0,
3032
+ overflow: "hidden",
3033
+ textOverflow: "ellipsis",
3034
+ whiteSpace: "nowrap"
3035
+ },
3036
+ children: option.label
3037
+ }
3038
+ )
3039
+ ]
3040
+ }
3041
+ ),
3042
+ !isMultiSelect && isSelected ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3043
+ "span",
3044
+ {
3045
+ "aria-hidden": "true",
3046
+ style: {
3047
+ display: "flex",
3048
+ alignItems: "center",
3049
+ justifyContent: "center",
3050
+ color: theme.colors.primary,
3051
+ flexShrink: 0
3052
+ },
3053
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 20 20", fill: "none", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3054
+ "path",
3055
+ {
3056
+ d: "M5.75 10.25L8.5 13l5.75-6",
3057
+ stroke: "currentColor",
3058
+ strokeWidth: "1.8",
3059
+ strokeLinecap: "round",
3060
+ strokeLinejoin: "round"
3061
+ }
3062
+ ) })
3063
+ }
3064
+ ) : null
3065
+ ]
3066
+ },
3067
+ option.key
3068
+ );
3069
+ })
941
3070
  ]
942
- }
943
- ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
944
- "div",
945
- {
946
- className: "buttons-footer",
947
- style: { flex: 1, display: "flex", justifyContent: "end" },
948
- children: actionButtons()
949
- }
950
- )
3071
+ },
3072
+ group.key
3073
+ ))
951
3074
  }
952
3075
  )
953
3076
  ]
954
3077
  }
955
3078
  )
956
- }
957
- );
958
- }
959
- function Modal(props) {
960
- if (isLegacyModalProps(props)) {
961
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(LegacyModal, { ...props });
962
- }
963
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StandardModal, { ...props });
964
- }
965
-
966
- // src/components/ComposableModal.tsx
967
- var import_jsx_runtime4 = require("react/jsx-runtime");
968
- var createModalSlotIds = (slotBaseId) => ({
969
- header: `${slotBaseId}:modal-header`,
970
- body: `${slotBaseId}:modal-body`,
971
- footer: `${slotBaseId}:modal-footer`
972
- });
973
- function ComposableModal({
974
- sdk,
975
- isOpen,
976
- onClose,
977
- title,
978
- description,
979
- slotBaseId,
980
- payload,
981
- onConfirm,
982
- confirmLabel = "Confirm",
983
- cancelLabel = "Cancel",
984
- isBusy = false,
985
- closeOnBackdrop = true,
986
- closeOnEscape = true,
987
- hideExpandButton = false,
988
- maximized,
989
- hideDefaultFooter = false,
990
- maxWidth,
991
- headerFallback,
992
- footerFallback,
993
- children
994
- }) {
995
- const theme = useUiKitTheme(sdk);
996
- const slotIds = createModalSlotIds(slotBaseId);
997
- const context = (0, import_react4.useMemo)(
998
- () => ({
999
- title,
1000
- description,
1001
- payload,
1002
- close: onClose,
1003
- confirm: onConfirm,
1004
- confirmLabel,
1005
- cancelLabel,
1006
- isOpen,
1007
- isBusy
1008
- }),
1009
- [cancelLabel, confirmLabel, description, isBusy, isOpen, onClose, onConfirm, payload, title]
1010
- );
1011
- const defaultFooter = hideDefaultFooter ? void 0 : footerFallback != null ? footerFallback : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1012
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Button, { sdk, variant: "outline", tone: "neutral", onClick: onClose, disabled: isBusy, children: cancelLabel }),
1013
- onConfirm ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Button, { sdk, onClick: onConfirm, disabled: isBusy, children: confirmLabel }) : null
1014
- ] });
1015
- const defaultHeader = headerFallback != null ? headerFallback : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
1016
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1017
- "h2",
1018
- {
1019
- style: {
1020
- margin: 0,
1021
- fontSize: 30,
1022
- fontWeight: 800,
1023
- lineHeight: 1.1
1024
- },
1025
- children: title
1026
- }
1027
- ),
1028
- description ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1029
- "p",
1030
- {
1031
- style: {
1032
- margin: 0,
1033
- fontSize: 15,
1034
- lineHeight: 1.6,
1035
- opacity: 0.78
1036
- },
1037
- children: description
1038
- }
1039
- ) : null
3079
+ ] }) : null
1040
3080
  ] });
1041
- const headerContent = /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_smartsell_sales_sdk4.SlotRenderer, { id: slotIds.header, sdk, context, children: defaultHeader });
1042
- const bodyContent = /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_smartsell_sales_sdk4.SlotRenderer, { id: slotIds.body, sdk, context, children });
1043
- const footerContent = /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_smartsell_sales_sdk4.SlotRenderer, { id: slotIds.footer, sdk, context, children: defaultFooter });
1044
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1045
- Modal,
1046
- {
1047
- isOpen,
1048
- onClose,
1049
- title,
1050
- description,
1051
- headerContent,
1052
- footer: footerContent,
1053
- theme,
1054
- maxWidth,
1055
- hideExpandButton,
1056
- maximized,
1057
- closeOnBackdrop,
1058
- closeOnEscape,
1059
- children: bodyContent
1060
- }
1061
- );
1062
- }
1063
-
1064
- // src/components/Field.tsx
1065
- var import_jsx_runtime5 = require("react/jsx-runtime");
1066
- function Field({
1067
- sdk,
1068
- label,
1069
- hint,
1070
- error,
1071
- htmlFor,
1072
- required = false,
1073
- children,
1074
- style
1075
- }) {
1076
- const { theme, mutedText } = useUiKitTheme(sdk);
1077
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1078
- "label",
3081
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3082
+ ControlFrame,
1079
3083
  {
1080
- htmlFor,
1081
- style: {
1082
- display: "flex",
1083
- flexDirection: "column",
1084
- gap: 8,
1085
- width: "100%",
1086
- ...style
1087
- },
1088
- children: [
1089
- (label || hint) && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: [
1090
- label ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1091
- "span",
1092
- {
1093
- style: {
1094
- color: theme.colors.onSurface,
1095
- fontSize: 14,
1096
- fontWeight: 700,
1097
- letterSpacing: "0.01em"
1098
- },
1099
- children: [
1100
- label,
1101
- required ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: { color: theme.colors.negative, marginLeft: 4 }, children: "*" }) : null
1102
- ]
1103
- }
1104
- ) : null,
1105
- hint ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1106
- "span",
1107
- {
1108
- style: {
1109
- color: mutedText,
1110
- fontSize: 13,
1111
- lineHeight: 1.45
1112
- },
1113
- children: hint
1114
- }
1115
- ) : null
1116
- ] }),
1117
- children,
1118
- error ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1119
- "span",
1120
- {
1121
- style: {
1122
- color: theme.colors.negative,
1123
- fontSize: 13,
1124
- fontWeight: 600,
1125
- lineHeight: 1.45
1126
- },
1127
- children: error
1128
- }
1129
- ) : null
1130
- ]
3084
+ label,
3085
+ hint,
3086
+ error,
3087
+ htmlFor: resolvedId,
3088
+ required: rest.required,
3089
+ useLabelWrapper: false,
3090
+ children: select
1131
3091
  }
1132
3092
  );
1133
3093
  }
1134
3094
 
1135
- // src/components/Input.tsx
1136
- var import_smartsell_sales_sdk5 = require("smartsell-sales-sdk");
1137
- var import_jsx_runtime6 = require("react/jsx-runtime");
1138
- var getPadding = (size) => {
1139
- if (size === "sm") return "10px 12px";
1140
- if (size === "lg") return "16px 18px";
1141
- return "13px 16px";
1142
- };
1143
- function Input({
1144
- sdk,
1145
- label,
1146
- hint,
1147
- error,
1148
- controlSize = "md",
1149
- id,
1150
- disabled,
1151
- style,
1152
- ...rest
1153
- }) {
1154
- const { theme, borderSoft, focusRing, surfaceMuted } = useUiKitTheme(sdk);
1155
- const inputStyle = {
1156
- width: "100%",
1157
- borderRadius: 16,
1158
- border: `1px solid ${error ? theme.colors.negative : borderSoft}`,
1159
- padding: getPadding(controlSize),
1160
- outline: "none",
1161
- color: theme.colors.onSurface,
1162
- backgroundColor: disabled ? surfaceMuted : theme.colors.surface,
1163
- boxShadow: error ? `0 0 0 3px ${(0, import_smartsell_sales_sdk5.withAlpha)(theme.colors.negative, 0.12)}` : `0 0 0 0 ${focusRing}`,
1164
- transition: "border-color 160ms ease, box-shadow 160ms ease, background-color 160ms ease",
1165
- ...style
1166
- };
1167
- const input = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { ...rest, id, disabled, style: inputStyle });
1168
- if (!label && !hint && !error) {
1169
- return input;
1170
- }
1171
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Field, { sdk, label, hint, error, htmlFor: id, children: input });
1172
- }
1173
-
1174
- // src/components/Select.tsx
1175
- var import_smartsell_sales_sdk6 = require("smartsell-sales-sdk");
1176
- var import_jsx_runtime7 = require("react/jsx-runtime");
1177
- var getPadding2 = (size) => {
1178
- if (size === "sm") return "10px 12px";
1179
- if (size === "lg") return "16px 18px";
1180
- return "13px 16px";
1181
- };
1182
- function Select({
1183
- sdk,
1184
- label,
1185
- hint,
1186
- error,
1187
- controlSize = "md",
1188
- id,
1189
- disabled,
1190
- style,
1191
- children,
1192
- ...rest
1193
- }) {
1194
- const { theme, borderSoft, focusRing, surfaceMuted } = useUiKitTheme(sdk);
1195
- const selectStyle = {
1196
- width: "100%",
1197
- borderRadius: 16,
1198
- border: `1px solid ${error ? theme.colors.negative : borderSoft}`,
1199
- padding: getPadding2(controlSize),
1200
- outline: "none",
1201
- color: theme.colors.onSurface,
1202
- backgroundColor: disabled ? surfaceMuted : theme.colors.surface,
1203
- boxShadow: error ? `0 0 0 3px ${(0, import_smartsell_sales_sdk6.withAlpha)(theme.colors.negative, 0.12)}` : `0 0 0 0 ${focusRing}`,
1204
- transition: "border-color 160ms ease, box-shadow 160ms ease, background-color 160ms ease",
1205
- ...style
1206
- };
1207
- const select = /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("select", { ...rest, id, disabled, style: selectStyle, children });
1208
- if (!label && !hint && !error) {
1209
- return select;
1210
- }
1211
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Field, { sdk, label, hint, error, htmlFor: id, children: select });
1212
- }
1213
-
1214
3095
  // src/components/Textarea.tsx
1215
- var import_smartsell_sales_sdk7 = require("smartsell-sales-sdk");
1216
- var import_jsx_runtime8 = require("react/jsx-runtime");
1217
- var getPadding3 = (size) => {
3096
+ var import_jsx_runtime9 = require("react/jsx-runtime");
3097
+ var getPadding = (size) => {
1218
3098
  if (size === "sm") return "10px 12px";
1219
3099
  if (size === "lg") return "16px 18px";
1220
3100
  return "13px 16px";
1221
3101
  };
1222
3102
  function Textarea({
1223
- sdk,
1224
3103
  label,
1225
3104
  hint,
1226
3105
  error,
@@ -1231,32 +3110,33 @@ function Textarea({
1231
3110
  rows = 5,
1232
3111
  ...rest
1233
3112
  }) {
1234
- const { theme, borderSoft, focusRing, surfaceMuted } = useUiKitTheme(sdk);
3113
+ const { theme, focusRing } = useUiKitTheme();
1235
3114
  const textareaStyle = {
3115
+ border: `1px solid ${error ? theme.colors.negative : withAlpha(theme.colors.textLight, 0.5)}`,
3116
+ backgroundColor: theme.colors.surface,
3117
+ color: theme.colors.text,
3118
+ cursor: disabled ? "not-allowed" : "text",
3119
+ opacity: disabled ? 0.7 : 1,
1236
3120
  width: "100%",
3121
+ boxSizing: "border-box",
1237
3122
  borderRadius: 16,
1238
- border: `1px solid ${error ? theme.colors.negative : borderSoft}`,
1239
- padding: getPadding3(controlSize),
3123
+ padding: getPadding(controlSize),
1240
3124
  outline: "none",
1241
- color: theme.colors.onSurface,
1242
- backgroundColor: disabled ? surfaceMuted : theme.colors.surface,
1243
- boxShadow: error ? `0 0 0 3px ${(0, import_smartsell_sales_sdk7.withAlpha)(theme.colors.negative, 0.12)}` : `0 0 0 0 ${focusRing}`,
3125
+ boxShadow: error ? `0 0 0 3px ${withAlpha(theme.colors.negative, 0.12)}` : `0 0 0 0 ${focusRing}`,
1244
3126
  transition: "border-color 160ms ease, box-shadow 160ms ease, background-color 160ms ease",
1245
3127
  resize: "vertical",
1246
3128
  ...style
1247
3129
  };
1248
- const textarea = /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("textarea", { ...rest, id, disabled, rows, style: textareaStyle });
1249
- if (!label && !hint && !error) {
1250
- return textarea;
1251
- }
1252
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Field, { sdk, label, hint, error, htmlFor: id, children: textarea });
3130
+ const textarea = /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("textarea", { ...rest, id, disabled, rows, style: textareaStyle });
3131
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ControlFrame, { label, hint, error, htmlFor: id, required: rest.required, children: textarea });
1253
3132
  }
1254
3133
  // Annotate the CommonJS export names for ESM import in node:
1255
3134
  0 && (module.exports = {
1256
3135
  Button,
1257
3136
  Card,
3137
+ Checkbox,
1258
3138
  ComposableModal,
1259
- Field,
3139
+ DatePicker,
1260
3140
  Input,
1261
3141
  Modal,
1262
3142
  Select,