sellmate-design-system-react 9.0.0-beta.26 → 9.0.0-beta.27

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.
@@ -288,12 +288,17 @@ export interface SChipFilterFieldBase {
288
288
  ```ts
289
289
  /** 프리셋으로 기간을 고르는 필터 — datepicker-range·datepicker-statistics */
290
290
  export interface SChipFilterPresetsField extends SChipFilterFieldBase {
291
- /** 프리셋 라디오 목록. datepicker-range에서 지정하지 않으면 프리셋 없이 기간 피커 하나만 놓이고,
292
- * datepicker-statistics에서 지정하지 않으면 일별·월별·분기별·반기별·연도별·사용자 지정 기본 목록을 쓴다 */
291
+ /** 프리셋 라디오 목록. 지정하지 않으면 타입별 기본 목록을 쓴다
292
+ * datepicker-range: 오늘·지난 7일·지난 30일·사용자 지정,
293
+ * datepicker-statistics: 일별·월별·분기별·반기별·연도별·사용자 지정.
294
+ * 프리셋 라디오 없이 캘린더(datepicker-range) 하나만 두려면 presets: [] 를 명시한다 */
293
295
  presets?: SChipFilterDatePreset[];
294
296
  /** 선택 가능 범위 */
295
297
  selectable?: [string, string];
296
- /** "사용자 지정" 프리셋으로 기간을 고를 때의 최대 선택 일수 */
298
+ /** "사용자 지정" 프리셋으로 기간을 고를 때의 최대 선택 일수.
299
+ * 지정하지 않아도 presets에 custom 프리셋이 있으면 기본 90일이 적용된다 — 무제한 기간
300
+ * 집계·조회로 백엔드에 과도한 요청이 가는 것을 막기 위한 안전장치. 다른 상한이 필요하면
301
+ * 이 값을 직접 지정한다 */
297
302
  maxRange?: number;
298
303
  }
299
304
  ```
@@ -350,10 +355,10 @@ export interface SChipFilterDatePreset {
350
355
 
351
356
  - [SDatePicker](../SDatePicker)
352
357
  - [SDateRangePicker](../SDateRangePicker)
353
- - [SGhostButton](../SGhostButton)
354
358
  - [SIcon](../SIcon)
355
359
  - [SRadio](../SRadio)
356
360
  - [SRadioButton](../SRadioButton)
361
+ - [SSearchInput](../SSearchInput)
357
362
  - [STag](../STag)
358
363
  - [STextLink](../STextLink)
359
364
  - [STooltip](../STooltip)
@@ -364,10 +369,10 @@ export interface SChipFilterDatePreset {
364
369
  graph TD;
365
370
  SChipFilter --> SDatePicker
366
371
  SChipFilter --> SDateRangePicker
367
- SChipFilter --> SGhostButton
368
372
  SChipFilter --> SIcon
369
373
  SChipFilter --> SRadio
370
374
  SChipFilter --> SRadioButton
375
+ SChipFilter --> SSearchInput
371
376
  SChipFilter --> STag
372
377
  SChipFilter --> STextLink
373
378
  SChipFilter --> STooltip
@@ -75,12 +75,17 @@ export interface SChipFilterOptionsField extends SChipFilterFieldBase {
75
75
  }
76
76
  /** 프리셋으로 기간을 고르는 필터 — datepicker-range·datepicker-statistics */
77
77
  export interface SChipFilterPresetsField extends SChipFilterFieldBase {
78
- /** 프리셋 라디오 목록. datepicker-range에서 지정하지 않으면 프리셋 없이 기간 피커 하나만 놓이고,
79
- * datepicker-statistics에서 지정하지 않으면 일별·월별·분기별·반기별·연도별·사용자 지정 기본 목록을 쓴다 */
78
+ /** 프리셋 라디오 목록. 지정하지 않으면 타입별 기본 목록을 쓴다
79
+ * datepicker-range: 오늘·지난 7일·지난 30일·사용자 지정,
80
+ * datepicker-statistics: 일별·월별·분기별·반기별·연도별·사용자 지정.
81
+ * 프리셋 라디오 없이 캘린더(datepicker-range) 하나만 두려면 presets: [] 를 명시한다 */
80
82
  presets?: SChipFilterDatePreset[];
81
83
  /** 선택 가능 범위 */
82
84
  selectable?: [string, string];
83
- /** "사용자 지정" 프리셋으로 기간을 고를 때의 최대 선택 일수 */
85
+ /** "사용자 지정" 프리셋으로 기간을 고를 때의 최대 선택 일수.
86
+ * 지정하지 않아도 presets에 custom 프리셋이 있으면 기본 90일이 적용된다 — 무제한 기간
87
+ * 집계·조회로 백엔드에 과도한 요청이 가는 것을 막기 위한 안전장치. 다른 상한이 필요하면
88
+ * 이 값을 직접 지정한다 */
84
89
  maxRange?: number;
85
90
  }
86
91
  /** 후보 하나를 고른다 */
@@ -48,7 +48,6 @@ export type SGhostButtonIntent = 'default' | 'danger' | 'action' | 'subAction' |
48
48
  - [SBarcodeInput](../SBarcodeInput)
49
49
  - [SCalendar](../SCalendar)
50
50
  - [SChip](../SChip)
51
- - [SChipFilter](../SChipFilter)
52
51
  - [SDatePicker](../SDatePicker)
53
52
  - [SDateRangePicker](../SDateRangePicker)
54
53
  - [SFilePicker](../SFilePicker)
@@ -81,7 +80,6 @@ graph TD;
81
80
  SBarcodeInput --> SGhostButton
82
81
  SCalendar --> SGhostButton
83
82
  SChip --> SGhostButton
84
- SChipFilter --> SGhostButton
85
83
  SDatePicker --> SGhostButton
86
84
  SDateRangePicker --> SGhostButton
87
85
  SFilePicker --> SGhostButton
@@ -41,6 +41,7 @@ export type SSearchInputSize = SFieldSize;
41
41
 
42
42
  ### Used by
43
43
 
44
+ - [SChipFilter](../SChipFilter)
44
45
  - [SSelect](../SSelect)
45
46
 
46
47
  ### Depends on
@@ -56,6 +57,7 @@ graph TD;
56
57
  SSearchInput --> SField
57
58
  SSearchInput --> SGhostButton
58
59
  SSearchInput --> SIcon
60
+ SChipFilter --> SSearchInput
59
61
  SSelect --> SSearchInput
60
62
  style SSearchInput fill:#f9f,stroke:#333,stroke-width:4px
61
63
  ```
@@ -22,7 +22,7 @@
22
22
  | `error?` | `boolean` | `false` | 에러 상태 |
23
23
  | `rules?` | `Rule[]` | — | 유효성 규칙 — 닫힐 때 자동 검증 |
24
24
  | `labelTooltipProps?` | `Partial<STooltipProps>` | — | 레이블 툴팁 상세 옵션 |
25
- | `dropdownHeight?` | `string` | `'260px'` | 드롭다운 최대 높이 |
25
+ | `dropdownHeight?` | `string` | `'640px'` | 드롭다운 최대 높이. 기본 640px — 창이 이보다 작으면 창 안에 남는 만큼까지만 채워진다 |
26
26
  | `dropdownWidth?` | `string` | — | 드롭다운 너비 (없으면 트리거 너비). 창보다 넓게 줘도 창 폭까지만 벌어진다. |
27
27
  | `maxDropdownWidth?` | `string` | `'640px'` | 드롭다운 최대 너비. 창이 이보다 좁으면 창 폭이 상한이 된다. |
28
28
  | `label?` | `string` | — | |
@@ -49,7 +49,7 @@ export interface SSelectProps {
49
49
  rules?: Rule[];
50
50
  /** 레이블 툴팁 상세 옵션 */
51
51
  labelTooltipProps?: Partial<STooltipProps>;
52
- /** 드롭다운 최대 높이 */
52
+ /** 드롭다운 최대 높이. 기본 640px — 창이 이보다 작으면 창 안에 남는 만큼까지만 채워진다 */
53
53
  dropdownHeight?: string;
54
54
  /** 드롭다운 너비 (없으면 트리거 너비). 창보다 넓게 줘도 창 폭까지만 벌어진다. */
55
55
  dropdownWidth?: string;
package/dist/index.cjs CHANGED
@@ -10444,6 +10444,14 @@ var SStepper = /* @__PURE__ */ react.forwardRef(function SStepper2({
10444
10444
  }
10445
10445
  );
10446
10446
  });
10447
+
10448
+ // src/lib/floating-height.ts
10449
+ var POPOVER_AVAILABLE_HEIGHT = "var(--radix-popover-content-available-height)";
10450
+ function clampToAvailableHeight(value, availableHeight) {
10451
+ if (value == null || value === "") return availableHeight;
10452
+ const css = typeof value === "number" ? `${value}px` : value;
10453
+ return `min(${css}, ${availableHeight})`;
10454
+ }
10447
10455
  function runRules(value, rules) {
10448
10456
  if (!rules || rules.length === 0) return "";
10449
10457
  for (const rule of rules) {
@@ -10841,7 +10849,7 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
10841
10849
  error = false,
10842
10850
  rules,
10843
10851
  labelTooltipProps,
10844
- dropdownHeight = "260px",
10852
+ dropdownHeight = "640px",
10845
10853
  dropdownWidth,
10846
10854
  maxDropdownWidth = "640px",
10847
10855
  label,
@@ -11202,6 +11210,7 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
11202
11210
  placement: "bottom",
11203
11211
  align: "start",
11204
11212
  offset: 4,
11213
+ viewportPadding: 24,
11205
11214
  fit: !dropdownWidth,
11206
11215
  onOpenAutoFocus: (event) => {
11207
11216
  if (!showSearch) {
@@ -11234,7 +11243,7 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
11234
11243
  SCROLLBAR_TRACK_BG_CLASS,
11235
11244
  SCROLLBAR_TRACK_BORDER_CLASS
11236
11245
  ),
11237
- style: { maxHeight: dropdownHeight },
11246
+ style: { maxHeight: clampToAvailableHeight(dropdownHeight, POPOVER_AVAILABLE_HEIGHT) },
11238
11247
  onMouseLeave: () => setFocusedIndex(-1),
11239
11248
  children: [
11240
11249
  showSearch && /* @__PURE__ */ jsxRuntime.jsx(
@@ -17387,6 +17396,8 @@ var SEditor = /* @__PURE__ */ react.forwardRef(function SEditor2({
17387
17396
  });
17388
17397
  var CHIPFILTER_LISTBOX_WIDTH = 240;
17389
17398
  var CHIPFILTER_DATE_PRESET_LISTBOX_WIDTH = 246;
17399
+ var EDITOR_LISTBOX_MAX_HEIGHT = "min(640px, var(--radix-popover-content-available-height))";
17400
+ var EDITOR_POPOVER_VIEWPORT_PADDING = 24;
17390
17401
  var CHIPFILTER = {
17391
17402
  barBg: "var(--cmp-chipFilter-bar-bg)",
17392
17403
  barBorder: "var(--cmp-chipFilter-bar-border)",
@@ -17426,6 +17437,7 @@ var CHIPFILTER = {
17426
17437
  itemTextDefault: "var(--cmp-chipFilter-item-text-default)",
17427
17438
  itemTextSelected: "var(--cmp-chipFilter-item-text-selected)"
17428
17439
  };
17440
+ var DEFAULT_CUSTOM_MAX_RANGE = 90;
17429
17441
  var DEFAULT_PERIOD_PRESETS = [
17430
17442
  { value: "day", label: "\uC77C\uBCC4" },
17431
17443
  { value: "month", label: "\uC6D4\uBCC4" },
@@ -17434,6 +17446,20 @@ var DEFAULT_PERIOD_PRESETS = [
17434
17446
  { value: "year", label: "\uC5F0\uB3C4\uBCC4" },
17435
17447
  { value: "custom", label: "\uC0AC\uC6A9\uC790 \uC9C0\uC815", custom: true }
17436
17448
  ];
17449
+ var DEFAULT_DATE_RANGE_PRESETS = [
17450
+ { value: "today", label: "\uC624\uB298", resolve: () => todayIso() },
17451
+ {
17452
+ value: "last7",
17453
+ label: "\uC9C0\uB09C 7\uC77C",
17454
+ resolve: () => [addDaysIso(todayIso(), -7), todayIso()]
17455
+ },
17456
+ {
17457
+ value: "last30",
17458
+ label: "\uC9C0\uB09C 30\uC77C",
17459
+ resolve: () => [addDaysIso(todayIso(), -30), todayIso()]
17460
+ },
17461
+ { value: "custom", label: "\uC0AC\uC6A9\uC790 \uC9C0\uC815", custom: true }
17462
+ ];
17437
17463
  var isKeywordValue = (value) => typeof value === "object" && value != null && !Array.isArray(value) && "keywords" in value;
17438
17464
  var isPeriodValue = (value) => typeof value === "object" && value != null && !Array.isArray(value) && "unit" in value;
17439
17465
  var keywordEntries = (value) => {
@@ -17458,23 +17484,39 @@ var todayIso = () => {
17458
17484
  const pad = (n) => String(n).padStart(2, "0");
17459
17485
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
17460
17486
  };
17487
+ var addDaysIso = (iso, n) => {
17488
+ const d = /* @__PURE__ */ new Date(`${iso}T00:00:00`);
17489
+ d.setDate(d.getDate() + n);
17490
+ const pad = (v) => String(v).padStart(2, "0");
17491
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
17492
+ };
17461
17493
  var isInlineDateField = (field) => field.type === "datepicker-range" && field.radioButton === true && (field.presets?.length ?? 0) > 0;
17462
17494
  var getFieldPresets = (field) => {
17463
17495
  if (field.type === "datepicker-statistics") return field.presets ?? DEFAULT_PERIOD_PRESETS;
17464
- return field.presets ?? [];
17496
+ return field.presets ?? DEFAULT_DATE_RANGE_PRESETS;
17465
17497
  };
17466
- var getDefaultPeriodInputValue = (unit) => {
17498
+ var getEffectiveMaxRange = (field) => {
17499
+ if (field.maxRange !== void 0) return field.maxRange;
17500
+ const hasCustomPreset = getFieldPresets(field).some((preset) => preset.custom);
17501
+ return hasCustomPreset ? DEFAULT_CUSTOM_MAX_RANGE : void 0;
17502
+ };
17503
+ var getPresetLabel = (preset, maxRange) => {
17504
+ if (!preset.custom || !maxRange || preset.label.includes("\uCD5C\uB300")) return preset.label;
17505
+ return `${preset.label} (\uCD5C\uB300 ${maxRange}\uC77C\uC529)`;
17506
+ };
17507
+ var getDefaultPeriodInputValue = (unit, maxRange) => {
17467
17508
  const today = todayIso();
17468
17509
  if (unit === "day") return today;
17469
17510
  if (unit === "month") return today.slice(0, 7);
17470
17511
  if (unit === "quarter") return 1;
17471
17512
  if (unit === "half") return "first";
17472
17513
  if (unit === "year") return today.slice(0, 4);
17473
- return [today, today];
17514
+ const days = maxRange && maxRange > 0 ? maxRange : DEFAULT_CUSTOM_MAX_RANGE;
17515
+ return [addDaysIso(today, -days), today];
17474
17516
  };
17475
- var createPeriodValue = (unit) => ({
17517
+ var createPeriodValue = (unit, maxRange) => ({
17476
17518
  unit,
17477
- value: getDefaultPeriodInputValue(unit)
17519
+ value: getDefaultPeriodInputValue(unit, maxRange)
17478
17520
  });
17479
17521
  var resolveFieldDefault = (field) => {
17480
17522
  if (field.defaultValue !== void 0 && isFixedField(field)) return field.defaultValue;
@@ -18023,6 +18065,7 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
18023
18065
  side: "bottom",
18024
18066
  align: "start",
18025
18067
  sideOffset: 4,
18068
+ collisionPadding: EDITOR_POPOVER_VIEWPORT_PADDING,
18026
18069
  onOpenAutoFocus: (event) => event.preventDefault(),
18027
18070
  className: cn("z-50", FLOATING_SLIDE_ANIM),
18028
18071
  style: editorPopoverStyle,
@@ -18188,6 +18231,7 @@ function EditorPanel({
18188
18231
  onClose
18189
18232
  }) {
18190
18233
  const wrapsInPaddedTop = field.type === "datepicker-day" || field.type === "datepicker-range" || field.type === "datepicker-statistics";
18234
+ const isScrollableListField = field.type === "select" || field.type === "select-multi" || field.type === "keyword";
18191
18235
  const isMulti = field.type === "select-multi";
18192
18236
  const selectableValues = isMulti ? (field.options ?? []).filter((option) => !option.disabled).map((option) => option.value) : [];
18193
18237
  const selectedValues = isMulti ? toArrayValue(value) : [];
@@ -18197,7 +18241,7 @@ function EditorPanel({
18197
18241
  allSelected ? selectedValues.filter((v) => !selectableValues.includes(v)) : [...selectedValues, ...selectableValues.filter((v) => !selectedValues.includes(v))]
18198
18242
  );
18199
18243
  };
18200
- return /* @__PURE__ */ jsxRuntime.jsxs(ListBox, { children: [
18244
+ return /* @__PURE__ */ jsxRuntime.jsxs(ListBox, { maxHeight: isScrollableListField ? EDITOR_LISTBOX_MAX_HEIGHT : void 0, children: [
18201
18245
  wrapsInPaddedTop ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-[var(--cmp-chipFilter-listbox-top-gap)] px-[var(--cmp-chipFilter-listbox-top-paddingX)] py-[var(--cmp-chipFilter-listbox-top-paddingY)]", children: /* @__PURE__ */ jsxRuntime.jsx(
18202
18246
  FilterEditor,
18203
18247
  {
@@ -18220,7 +18264,7 @@ function EditorPanel({
18220
18264
  /* @__PURE__ */ jsxRuntime.jsxs(
18221
18265
  "div",
18222
18266
  {
18223
- className: "flex items-center justify-end gap-[var(--cmp-chipFilter-listbox-bottom-gap)] border-t border-solid px-[var(--cmp-chipFilter-listbox-bottom-paddingX)] py-[var(--cmp-chipFilter-listbox-bottom-paddingY)]",
18267
+ className: "flex shrink-0 items-center justify-end gap-[var(--cmp-chipFilter-listbox-bottom-gap)] border-t border-solid px-[var(--cmp-chipFilter-listbox-bottom-paddingX)] py-[var(--cmp-chipFilter-listbox-bottom-paddingY)]",
18224
18268
  style: { borderColor: CHIPFILTER.listboxBorder },
18225
18269
  children: [
18226
18270
  isMulti && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -18335,6 +18379,7 @@ function DatePresetEditor({
18335
18379
  onValueChange
18336
18380
  }) {
18337
18381
  const presets = getFieldPresets(field);
18382
+ const maxRange = getEffectiveMaxRange(field);
18338
18383
  const [selectedId, setSelectedId] = react.useState(() => matchPresetId(presets, value));
18339
18384
  const syncedInitialValueRef = react.useRef(!isEmptyValue(value));
18340
18385
  react.useEffect(() => {
@@ -18345,7 +18390,13 @@ function DatePresetEditor({
18345
18390
  const selectedPreset = presets.find((preset) => preset.value === selectedId);
18346
18391
  const selectPreset = (preset) => {
18347
18392
  setSelectedId(preset.value);
18348
- if (!preset.custom) onValueChange(preset.resolve ? preset.resolve() : preset.value);
18393
+ if (!preset.custom) {
18394
+ onValueChange(preset.resolve ? preset.resolve() : preset.value);
18395
+ return;
18396
+ }
18397
+ const today = todayIso();
18398
+ const days = maxRange && maxRange > 0 ? maxRange : DEFAULT_CUSTOM_MAX_RANGE;
18399
+ onValueChange([addDaysIso(today, -days), today]);
18349
18400
  };
18350
18401
  if (presets.length === 0) {
18351
18402
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -18354,7 +18405,7 @@ function DatePresetEditor({
18354
18405
  value: Array.isArray(value) && value.length === 2 ? value : null,
18355
18406
  onValueChange,
18356
18407
  selectable: field.selectable,
18357
- maxRange: field.maxRange,
18408
+ maxRange,
18358
18409
  disabled,
18359
18410
  width: "100%"
18360
18411
  }
@@ -18366,7 +18417,7 @@ function DatePresetEditor({
18366
18417
  {
18367
18418
  val: preset.value,
18368
18419
  value: selectedId,
18369
- label: preset.label,
18420
+ label: getPresetLabel(preset, maxRange),
18370
18421
  disabled,
18371
18422
  onValueChange: () => selectPreset(preset)
18372
18423
  },
@@ -18378,7 +18429,7 @@ function DatePresetEditor({
18378
18429
  value: Array.isArray(value) && value.length === 2 ? value : null,
18379
18430
  onValueChange,
18380
18431
  selectable: field.selectable,
18381
- maxRange: field.maxRange,
18432
+ maxRange,
18382
18433
  disabled
18383
18434
  }
18384
18435
  )
@@ -18391,9 +18442,10 @@ function PeriodPresetEditor({
18391
18442
  onValueChange
18392
18443
  }) {
18393
18444
  const presets = getFieldPresets(field);
18445
+ const maxRange = getEffectiveMaxRange(field);
18394
18446
  const firstUnit = presets.find((preset) => !preset.custom)?.value ?? "day";
18395
18447
  const [selectedId, setSelectedId] = react.useState(() => matchPresetId(presets, value) ?? firstUnit);
18396
- const periodValue = isPeriodValue(value) ? value : createPeriodValue(selectedId ?? firstUnit);
18448
+ const periodValue = isPeriodValue(value) ? value : createPeriodValue(selectedId ?? firstUnit, maxRange);
18397
18449
  const selectedUnit = selectedId ?? periodValue.unit;
18398
18450
  react.useEffect(() => {
18399
18451
  if (isEmptyValue(value)) return;
@@ -18402,7 +18454,7 @@ function PeriodPresetEditor({
18402
18454
  const selectPreset = (preset) => {
18403
18455
  const nextUnit = preset.value;
18404
18456
  setSelectedId(nextUnit);
18405
- onValueChange(createPeriodValue(nextUnit));
18457
+ onValueChange(createPeriodValue(nextUnit, maxRange));
18406
18458
  };
18407
18459
  const updateInputValue = (nextValue) => {
18408
18460
  onValueChange({ unit: selectedUnit, value: nextValue });
@@ -18485,7 +18537,7 @@ function PeriodPresetEditor({
18485
18537
  value: Array.isArray(periodValue.value) && periodValue.value.length === 2 ? periodValue.value : null,
18486
18538
  onValueChange: updateInputValue,
18487
18539
  selectable: field.selectable,
18488
- maxRange: field.maxRange,
18540
+ maxRange,
18489
18541
  disabled,
18490
18542
  width: "100%"
18491
18543
  }
@@ -18497,7 +18549,7 @@ function PeriodPresetEditor({
18497
18549
  {
18498
18550
  val: preset.value,
18499
18551
  value: selectedId,
18500
- label: preset.label,
18552
+ label: getPresetLabel(preset, maxRange),
18501
18553
  disabled,
18502
18554
  onValueChange: () => selectPreset(preset)
18503
18555
  },
@@ -18709,7 +18761,7 @@ function KeywordEditor({
18709
18761
  }
18710
18762
  };
18711
18763
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
18712
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-[var(--cmp-chipFilter-listbox-top-gap)] px-[var(--cmp-chipFilter-listbox-top-paddingX)] py-[var(--cmp-chipFilter-listbox-top-paddingY)]", children: [
18764
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 flex-col gap-[var(--cmp-chipFilter-listbox-top-gap)] px-[var(--cmp-chipFilter-listbox-top-paddingX)] py-[var(--cmp-chipFilter-listbox-top-paddingY)]", children: [
18713
18765
  field.matchModes && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
18714
18766
  /* @__PURE__ */ jsxRuntime.jsx(
18715
18767
  "span",
@@ -18751,7 +18803,7 @@ function KeywordEditor({
18751
18803
  entries3.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
18752
18804
  "div",
18753
18805
  {
18754
- className: "max-h-[220px] overflow-auto border-t border-solid py-[var(--cmp-chipFilter-listbox-list-paddingY)]",
18806
+ className: "min-h-0 flex-1 overflow-auto border-t border-solid py-[var(--cmp-chipFilter-listbox-list-paddingY)]",
18755
18807
  style: { borderColor: CHIPFILTER.listboxBorder },
18756
18808
  children: entries3.map((entry) => /* @__PURE__ */ jsxRuntime.jsx(
18757
18809
  KeywordEntryRow,
@@ -18809,6 +18861,7 @@ function OptionList({
18809
18861
  const selected = multiple ? toArrayValue(value) : [];
18810
18862
  const singleValue = !multiple && (typeof value === "string" || typeof value === "number") ? value : null;
18811
18863
  const [search, setSearch] = react.useState("");
18864
+ const searchInputRef = react.useRef(null);
18812
18865
  const options = field.options ?? [];
18813
18866
  const showSearch = options.length >= OPTION_SEARCH_THRESHOLD;
18814
18867
  const filteredOptions = react.useMemo(() => {
@@ -18816,74 +18869,70 @@ function OptionList({
18816
18869
  const query = search.trim().toLowerCase();
18817
18870
  return options.filter((option) => option.label.toLowerCase().includes(query));
18818
18871
  }, [options, search, showSearch]);
18872
+ react.useEffect(() => {
18873
+ if (!showSearch || disabled) return;
18874
+ searchInputRef.current?.focus();
18875
+ }, [showSearch, disabled]);
18819
18876
  if (options.length === 0) return /* @__PURE__ */ jsxRuntime.jsx(EmptyItem, { children: "\uD45C\uC2DC\uD560 \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." });
18820
- return /* @__PURE__ */ jsxRuntime.jsxs(
18821
- "div",
18822
- {
18823
- className: cn(
18824
- "max-h-[240px] overflow-auto",
18825
- !showSearch && "pt-[var(--cmp-chipFilter-listbox-list-paddingY)]",
18826
- "pb-[var(--cmp-chipFilter-listbox-list-paddingY)]"
18827
- ),
18828
- children: [
18829
- showSearch && /* @__PURE__ */ jsxRuntime.jsx(
18830
- "div",
18877
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 flex-col", children: [
18878
+ showSearch && /* @__PURE__ */ jsxRuntime.jsx(
18879
+ "div",
18880
+ {
18881
+ className: "shrink-0 border-b border-solid px-[8px] pb-[4px] pt-[var(--cmp-chipFilter-listbox-list-paddingY)]",
18882
+ style: { borderColor: CHIPFILTER.listboxBorder },
18883
+ children: /* @__PURE__ */ jsxRuntime.jsx(
18884
+ SSearchInput,
18831
18885
  {
18832
- className: "sticky top-0 z-[1] border-b border-solid px-[8px] pb-[4px] pt-[var(--cmp-chipFilter-listbox-list-paddingY)]",
18833
- style: { background: CHIPFILTER.listboxBg, borderColor: CHIPFILTER.listboxBorder },
18834
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-[6px] rounded-[4px] border border-solid border-[color:var(--sys-color-field-border-default)] bg-[var(--sys-color-field-bg-default)] px-[8px] py-[4px] focus-within:border-[color:var(--sys-color-field-border-focus)]", children: [
18835
- /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "search", size: 16, color: "var(--sys-color-field-icon-default)" }),
18836
- /* @__PURE__ */ jsxRuntime.jsx(
18837
- "input",
18838
- {
18839
- value: search,
18840
- disabled,
18841
- placeholder: "\uAC80\uC0C9",
18842
- onChange: (event) => setSearch(event.target.value),
18843
- className: "min-w-0 flex-1 border-none bg-transparent text-[12px] leading-[20px] text-[color:var(--sys-color-field-text-default)] outline-none placeholder:text-[color:var(--sys-color-field-text-placeholder)] disabled:cursor-not-allowed"
18844
- }
18845
- ),
18846
- search !== "" && !disabled && /* @__PURE__ */ jsxRuntime.jsx(
18847
- SGhostButton,
18848
- {
18849
- icon: "close",
18850
- size: "xxs",
18851
- ariaLabel: "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30",
18852
- className: "flex-shrink-0",
18853
- onClick: () => setSearch("")
18854
- }
18855
- )
18856
- ] })
18886
+ ref: searchInputRef,
18887
+ size: "sm",
18888
+ value: search,
18889
+ onValueChange: setSearch,
18890
+ disabled,
18891
+ placeholder: "\uAC80\uC0C9",
18892
+ clearable: true,
18893
+ width: "100%"
18857
18894
  }
18895
+ )
18896
+ }
18897
+ ),
18898
+ /* @__PURE__ */ jsxRuntime.jsxs(
18899
+ "div",
18900
+ {
18901
+ className: cn(
18902
+ "min-h-0 flex-1 overflow-auto",
18903
+ !showSearch && "pt-[var(--cmp-chipFilter-listbox-list-paddingY)]",
18904
+ "pb-[var(--cmp-chipFilter-listbox-list-paddingY)]"
18858
18905
  ),
18859
- showSearch && filteredOptions.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(EmptyItem, { children: "\uAC80\uC0C9 \uACB0\uACFC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }),
18860
- filteredOptions.map((option) => {
18861
- const checked = multiple ? selected.includes(option.value) : singleValue === option.value;
18862
- return /* @__PURE__ */ jsxRuntime.jsxs(
18863
- MenuItem,
18864
- {
18865
- selected: checked,
18866
- disabled: disabled || option.disabled,
18867
- multi: multiple,
18868
- onClick: () => {
18869
- if (multiple) {
18870
- const next = checked ? selected.filter((v) => v !== option.value) : [...selected, option.value];
18871
- onValueChange(next);
18872
- } else {
18873
- onValueChange(option.value);
18874
- }
18906
+ children: [
18907
+ showSearch && filteredOptions.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(EmptyItem, { children: "\uAC80\uC0C9 \uACB0\uACFC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }),
18908
+ filteredOptions.map((option) => {
18909
+ const checked = multiple ? selected.includes(option.value) : singleValue === option.value;
18910
+ return /* @__PURE__ */ jsxRuntime.jsxs(
18911
+ MenuItem,
18912
+ {
18913
+ selected: checked,
18914
+ disabled: disabled || option.disabled,
18915
+ multi: multiple,
18916
+ onClick: () => {
18917
+ if (multiple) {
18918
+ const next = checked ? selected.filter((v) => v !== option.value) : [...selected, option.value];
18919
+ onValueChange(next);
18920
+ } else {
18921
+ onValueChange(option.value);
18922
+ }
18923
+ },
18924
+ children: [
18925
+ multiple && /* @__PURE__ */ jsxRuntime.jsx(OptionCheckbox, { checked }),
18926
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", children: option.label })
18927
+ ]
18875
18928
  },
18876
- children: [
18877
- multiple && /* @__PURE__ */ jsxRuntime.jsx(OptionCheckbox, { checked }),
18878
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", children: option.label })
18879
- ]
18880
- },
18881
- String(option.value)
18882
- );
18883
- })
18884
- ]
18885
- }
18886
- );
18929
+ String(option.value)
18930
+ );
18931
+ })
18932
+ ]
18933
+ }
18934
+ )
18935
+ ] });
18887
18936
  }
18888
18937
  function OptionCheckbox({ checked }) {
18889
18938
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -18897,15 +18946,22 @@ function OptionCheckbox({ checked }) {
18897
18946
  }
18898
18947
  );
18899
18948
  }
18900
- function ListBox({ children }) {
18949
+ function ListBox({
18950
+ children,
18951
+ maxHeight
18952
+ }) {
18901
18953
  return /* @__PURE__ */ jsxRuntime.jsx(
18902
18954
  "div",
18903
18955
  {
18904
- className: "overflow-hidden border border-solid shadow-[var(--shadow-overlay)]",
18956
+ className: cn(
18957
+ "overflow-hidden border border-solid shadow-[var(--shadow-overlay)]",
18958
+ maxHeight && "flex flex-col"
18959
+ ),
18905
18960
  style: {
18906
18961
  borderRadius: CHIPFILTER.listboxRadius,
18907
18962
  background: CHIPFILTER.listboxBg,
18908
- borderColor: CHIPFILTER.listboxBorder
18963
+ borderColor: CHIPFILTER.listboxBorder,
18964
+ maxHeight
18909
18965
  },
18910
18966
  children
18911
18967
  }