@olundot/ui 0.20.0-next.10 → 0.20.0-next.12

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/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # @olundot/ui
2
2
 
3
+ ## 0.20.0-next.12
4
+
5
+ ### Minor Changes
6
+
7
+ - 2105422: 구조형 `Dialog`의 `size`에 `"xl"`(960px)을 더했다. 두 단 구성처럼 `"lg"`(720px)로 모자란 내용에 쓴다.
8
+
9
+ - 1024px 이상에서 가운데 상자다. 폭은 다른 크기처럼 화면 폭에서 32px을 뺀 값을 넘지 않는다. 높이 상한과 본문 스크롤도 같다.
10
+ - 1024px 미만에서는 다른 크기처럼 아래쪽 시트다.
11
+ - `"sm"`, `"md"`, `"lg"`의 폭과 기본값 `"md"`는 그대로다. Sheet와 AlertDialog 폭도 바뀌지 않는다.
12
+
13
+ ## 0.20.0-next.11
14
+
15
+ ### Minor Changes
16
+
17
+ - 708df3d: 떠 있는 `BulkActionBar`(`placement="floating"`, 기본값)를 알약 띠에서 카드로 바꾼다. 화면 폭과 관계없이 모양이 하나다.
18
+
19
+ - 1행: 개수(왼쪽, `role="status"` 그대로)와 선택 해제 ✕(오른쪽, ghost `IconButton` md). 2행: `summary`(있을 때만, 카드 안쪽 폭 전체, `keep-all`로 줄 바꿈, 자르지 않음). 3행: 작업 버튼(오른쪽 맞춤, 넘치면 다음 줄로 감싸며 오른쪽 맞춤 유지).
20
+ - 작업 버튼이 `Button` `appearance="outline"` `size="sm"`(30px)이 된다. 일반은 outline neutral, `variant: "destructive"`는 outline danger, 비활성은 Button의 disabled 꼴이다. 아이콘은 라벨 앞 그대로다. 터치 영역은 위아래로 4px씩만 넓혀(38px) 줄바꿈된 줄과 겹치지 않는다.
21
+ - 간격: 안쪽 여백 위 8 · 좌우 16 · 아래 12, 1행 32, 행 사이 8, 작업 줄 위 12, 버튼 사이 8. 모서리 `--radius-xl`. 폭 `min(360px, 자리 − 32px)`. 구분선이 없다.
22
+ - DOM 순서는 개수 → 작업 → ✕ 그대로다. ✕는 1행 오른쪽에 그려지지만 Tab으로는 맨 뒤다.
23
+ - 카드가 떠 있는 동안 문서 루트에 `--olun-bulk-bar-offset`(카드 높이 + 아래 간격 `16px + env(safe-area-inset-bottom)`)을 둔다. 언마운트, count 0으로 숨김, 높이 0(`display: none`)이면 지운다. 카드가 여럿이면 가장 큰 값이다.
24
+ - `ToastViewport`가 이 값보다 8px 위에 뜬다(값이 없으면 예전처럼 아래 24px).
25
+ - `DataLayout`의 `bulkBar` 자리가 아래 16px에 `env(safe-area-inset-bottom)`을 더한다. 떠 있는 카드와 같은 간격이다.
26
+
27
+ 호환성 영향:
28
+
29
+ - floating에서 `trailing`을 받지 않는다. 타입에서 막히고(`placement`가 `"inline"`일 때만 허용), 타입을 우회해 넘기면 그리지 않고 개발 모드에서 경고를 한 번 낸다. 편집·완료 같은 컨트롤은 도구 줄 오른쪽 끝에 둔다. `placement="inline"`의 `trailing`은 그대로다.
30
+ - `BulkActionBarProps`가 `placement` 유니언 타입이 되어 `interface X extends BulkActionBarProps`가 타입 오류를 낸다. `type X = BulkActionBarProps & { … }`로 바꾼다.
31
+ - floating에서 `data-slot="bulk-action-bar-divider"`와 `data-slot="bulk-action-bar-clear"`가 없어진다. ✕는 `IconButton`의 `data-slot="icon-button"`이고 `aria-label="선택 해제"`로 찾는다. 작업 버튼의 `data-slot="bulk-action-bar-action"`과 `data-variant`는 그대로다.
32
+ - 카드 높이가 예전 알약(약 42px)보다 크다. 목록 아래 여백을 카드 높이에 맞추거나 `--olun-bulk-bar-offset`을 쓴다.
33
+ - `placement="inline"`(사용 중단 예정)은 바뀌지 않는다.
34
+
3
35
  ## 0.20.0-next.10
4
36
 
5
37
  ### Minor Changes
package/dist/index.d.ts CHANGED
@@ -711,8 +711,8 @@ type OverlayScrim = "blur" | "dim";
711
711
  * Pure CSS, so SSR and hydration render the same tree at every width (no matchMedia swap).
712
712
  */
713
713
  type DialogMobilePresentation = "center" | "sheet";
714
- /** Structured Dialog width: 420 · 560 · 720. */
715
- type DialogSize = "sm" | "md" | "lg";
714
+ /** Structured Dialog width: 420 · 560 · 720 · 960. */
715
+ type DialogSize = "sm" | "md" | "lg" | "xl";
716
716
  /**
717
717
  * Structured Dialog (spec §3.2): a center window for forms, pickers and long content. X + footer,
718
718
  * outside click does not close, Escape does (not while submitting), the body scrolls inside the max
@@ -725,7 +725,10 @@ interface DialogProps {
725
725
  title: string;
726
726
  /** Line under the title, also the accessible description. */
727
727
  description?: string;
728
- /** Box width: 420 · 560 · 720. Default `"md"`. Other widths are not supported. */
728
+ /**
729
+ * Box width: 420 · 560 · 720 · 960. Default `"md"`. `"xl"` is for content 720 cannot hold, such as
730
+ * two columns. Other widths are not supported.
731
+ */
729
732
  size?: DialogSize;
730
733
  /** Body. Scrolls inside the box's max height. */
731
734
  children: React.ReactNode;
@@ -1238,21 +1241,17 @@ interface BulkActionBarAction {
1238
1241
  /** Disabled reason tooltip. */
1239
1242
  title?: string;
1240
1243
  }
1241
- interface BulkActionBarProps extends React.HTMLAttributes<HTMLDivElement> {
1244
+ interface BulkActionBarBaseProps extends React.HTMLAttributes<HTMLDivElement> {
1242
1245
  count: number;
1243
1246
  actions: BulkActionBarAction[];
1244
1247
  onClear: () => void;
1245
1248
  emptyHint?: React.ReactNode;
1246
- /**
1247
- * `floating` (default) — pill stuck to the bottom of the list.
1248
- * `inline` — full-width band stuck to the top, placed where the status line sits
1249
- * under the toolbar. Render it BEFORE the list so it comes first in tab order.
1250
- * Deprecated: `inline` is removed in the next minor.
1251
- */
1252
- placement?: "floating" | "inline";
1253
1249
  /** Count unit — `12명 선택됨`. Defaults to `개`. */
1254
1250
  unit?: string;
1255
- /** Next to the count — e.g. an "이 조건의 3,214명 모두 선택" link or a cap notice. */
1251
+ /**
1252
+ * Under the count in the floating card, next to it inline — e.g. an "이 조건의 3,214명
1253
+ * 모두 선택" link or a cap notice. The card wraps it at full width, never clips it.
1254
+ */
1256
1255
  summary?: React.ReactNode;
1257
1256
  /**
1258
1257
  * Inline only: a shorter `summary` (e.g. "모두 선택") swapped in when the band is too
@@ -1266,28 +1265,44 @@ interface BulkActionBarProps extends React.HTMLAttributes<HTMLDivElement> {
1266
1265
  * Takes precedence over `emptyHint`; with neither, the bar hides at count 0.
1267
1266
  */
1268
1267
  idleContent?: React.ReactNode;
1268
+ }
1269
+ type BulkActionBarProps = BulkActionBarBaseProps & ({
1270
+ /** `floating` (default) — card stuck to the bottom of the list. */
1271
+ placement?: "floating";
1272
+ /** Not taken by the floating card: edit-mode controls go at the right end of the toolbar. */
1273
+ trailing?: never;
1274
+ } | {
1269
1275
  /**
1270
- * After the clear (✕) button — e.g. an edit-mode "완료" button. On the idle line it
1271
- * sits at the right end, outside the `role="status"` region, so it is not re-read
1272
- * whenever `idleContent` changes. Same spot in both placements.
1276
+ * `inline` — full-width band stuck to the top, placed where the status line sits
1277
+ * under the toolbar. Render it BEFORE the list so it comes first in tab order.
1278
+ * Deprecated: `inline` is removed in the next minor.
1279
+ */
1280
+ placement: "inline";
1281
+ /**
1282
+ * Inline only: after the clear (✕) button — e.g. an edit-mode "완료" button. On the
1283
+ * idle line it sits at the right end, outside the `role="status"` region, so it is
1284
+ * not re-read whenever `idleContent` changes.
1273
1285
  */
1274
1286
  trailing?: React.ReactNode;
1275
- }
1287
+ });
1276
1288
  /**
1277
1289
  * OLUN BulkActionBar — multi-select table/list actions.
1278
1290
  *
1279
- * placement="floating" (default) is the bottom pill; placement="inline" is a
1280
- * top-sticky band that replaces the status line under the toolbar, so it comes
1281
- * before the rows in tab order. `unit` sets the count unit, `summary` holds a
1282
- * suggestion next to the count, `idleContent` shows while nothing is selected,
1283
- * `trailing` follows the clear button (right end of the idle line).
1284
- * Inline, the band and the idle line are one 40px line: a long `summary` is
1285
- * clipped or swapped for `compactSummary`, never wrapped.
1291
+ * placement="floating" (default) is a card at the bottom of the list: the count
1292
+ * with the clear (✕) button on its right, `summary` under it at full width, then
1293
+ * the actions as outline sm Buttons aligned right, wrapping onto more lines when
1294
+ * they do not fit. `variant: "destructive"` is outline danger. The card is
1295
+ * min(360px, its slot − 32px) wide; while it is mounted the document root carries
1296
+ * `--olun-bulk-bar-offset` (its height + bottom gap) for toasts to clear it.
1297
+ * DOM order stays count → actions → ✕. It takes no `trailing`.
1298
+ *
1299
+ * placement="inline" (deprecated) is a top-sticky band that replaces the status
1300
+ * line under the toolbar, so it comes before the rows in tab order. The band and
1301
+ * the idle line are one 40px line: a long `summary` is clipped or swapped for
1302
+ * `compactSummary`, never wrapped. Its actions are borderless text/icon buttons
1303
+ * between hairline dividers; `trailing` follows the clear button.
1286
1304
  *
1287
- * Low-chrome by design: actions are borderless text/icon buttons separated by
1288
- * hairline dividers — not filled Button components. Destructive actions read as
1289
- * error-toned text (var(--error)), not a red filled button. Two dividers are
1290
- * always rendered: count↔actions and actions↔clear.
1305
+ * `unit` sets the count unit, `idleContent` shows while nothing is selected.
1291
1306
  *
1292
1307
  * @example
1293
1308
  * <BulkActionBar count={sel.size} onClear={clear}
package/dist/index.js CHANGED
@@ -2777,7 +2777,7 @@ var DataLayout = React17.forwardRef(
2777
2777
  "div",
2778
2778
  {
2779
2779
  "data-slot": "data-layout-bulkbar",
2780
- className: "pointer-events-none sticky bottom-[var(--space-4)] z-20 mt-[var(--space-2)] flex justify-center",
2780
+ className: "pointer-events-none sticky bottom-[calc(var(--space-4)+env(safe-area-inset-bottom))] z-20 mt-[var(--space-2)] flex justify-center",
2781
2781
  children: /* @__PURE__ */ jsx16("div", { className: "flex w-full flex-col items-center [&>*:has([data-slot=bulk-action-bar])]:self-stretch [&>*:not(:has([data-slot=bulk-action-bar]))]:pointer-events-auto [&>*]:max-w-full [&_[data-slot=bulk-action-bar]]:pointer-events-auto", children: bulkBar })
2782
2782
  }
2783
2783
  ) : null
@@ -4293,7 +4293,8 @@ var overlaySize = {
4293
4293
  dialog: {
4294
4294
  sm: "max-w-[420px]",
4295
4295
  md: "max-w-[560px]",
4296
- lg: "max-w-[720px]"
4296
+ lg: "max-w-[720px]",
4297
+ xl: "max-w-[960px]"
4297
4298
  },
4298
4299
  sheet: {
4299
4300
  md: "lg:w-[480px] lg:max-w-[480px]",
@@ -7639,8 +7640,8 @@ function useVirtualWindow({
7639
7640
  padBottom: 0
7640
7641
  });
7641
7642
  const desktopSize = React35.useRef(0);
7642
- const cardHeights = React35.useRef(null);
7643
- cardHeights.current ??= new CardHeights();
7643
+ const cardHeights2 = React35.useRef(null);
7644
+ cardHeights2.current ??= new CardHeights();
7644
7645
  const update = React35.useRef(() => void 0);
7645
7646
  const reported = React35.useRef("");
7646
7647
  const latest = React35.useRef({
@@ -7666,9 +7667,9 @@ function useVirtualWindow({
7666
7667
  extent = uniformExtent(size);
7667
7668
  } else {
7668
7669
  if (list) {
7669
- cardHeights.current.measure(list);
7670
+ cardHeights2.current.measure(list);
7670
7671
  }
7671
- const { offsets, estimate } = cardHeights.current.offsets(rowIds);
7672
+ const { offsets, estimate } = cardHeights2.current.offsets(rowIds);
7672
7673
  extent = measuredExtent(offsets, estimate);
7673
7674
  }
7674
7675
  latest.current = { count, layout, extent };
@@ -8348,6 +8349,35 @@ function ActionButton({ action }) {
8348
8349
  }
8349
8350
  );
8350
8351
  }
8352
+ function CardActionButton({ action }) {
8353
+ const Icon = action.icon;
8354
+ return /* @__PURE__ */ jsxs35(
8355
+ Button,
8356
+ {
8357
+ appearance: "outline",
8358
+ tone: action.variant === "destructive" ? "danger" : "neutral",
8359
+ size: "sm",
8360
+ className: "before:-inset-y-[5px]",
8361
+ "data-slot": "bulk-action-bar-action",
8362
+ "data-variant": action.variant ?? "default",
8363
+ disabled: action.disabled,
8364
+ title: action.title,
8365
+ onClick: action.onClick,
8366
+ children: [
8367
+ Icon ? /* @__PURE__ */ jsx39(
8368
+ Icon,
8369
+ {
8370
+ "aria-hidden": "true",
8371
+ "data-slot": "bulk-action-bar-action-icon",
8372
+ size: 16,
8373
+ strokeWidth: 1.75
8374
+ }
8375
+ ) : null,
8376
+ action.label
8377
+ ]
8378
+ }
8379
+ );
8380
+ }
8351
8381
  function Divider() {
8352
8382
  return /* @__PURE__ */ jsx39(
8353
8383
  "span",
@@ -8390,24 +8420,24 @@ function ClearButton({ onClear }) {
8390
8420
  }
8391
8421
  );
8392
8422
  }
8423
+ var CLEAR_CLEARANCE = 32;
8393
8424
  var INLINE_HEIGHT = 40;
8394
8425
  var HIT_AREA = 44;
8395
8426
  var HIT_INSET = 8;
8427
+ var FLOATING_BOTTOM = "calc(var(--space-4) + env(safe-area-inset-bottom))";
8396
8428
  var floatingStyle = {
8397
8429
  position: "sticky",
8398
- bottom: "calc(var(--space-4) + env(safe-area-inset-bottom))",
8430
+ bottom: FLOATING_BOTTOM,
8399
8431
  zIndex: 1100,
8400
8432
  margin: "0 auto",
8401
8433
  display: "flex",
8402
- minHeight: 32,
8403
- width: "fit-content",
8404
- maxWidth: "calc(100% - var(--space-8))",
8405
- alignItems: "center",
8406
- gap: "var(--space-2)",
8407
- borderRadius: 9999,
8434
+ boxSizing: "border-box",
8435
+ width: "min(360px, calc(100% - var(--space-8)))",
8436
+ flexDirection: "column",
8437
+ borderRadius: "var(--radius-xl)",
8408
8438
  border: "1px solid var(--border)",
8409
8439
  background: "var(--bg-raised)",
8410
- padding: "var(--space-1) var(--space-2)",
8440
+ padding: "var(--space-2) var(--space-4) var(--space-3)",
8411
8441
  boxShadow: "var(--shadow-8)"
8412
8442
  };
8413
8443
  var inlineStyle = {
@@ -8449,7 +8479,49 @@ function warnInlineOnce() {
8449
8479
  warnedInline = true;
8450
8480
  console.warn('BulkActionBar: placement="inline" is deprecated and removed in the next minor. Use "floating".');
8451
8481
  }
8482
+ var warnedTrailing = false;
8483
+ function warnTrailingOnce() {
8484
+ if (warnedTrailing || typeof process === "undefined" || process.env.NODE_ENV === "production") {
8485
+ return;
8486
+ }
8487
+ warnedTrailing = true;
8488
+ console.warn("BulkActionBar: the floating card ignores `trailing`. Put edit-mode controls at the right end of the toolbar.");
8489
+ }
8490
+ var OFFSET_PROPERTY = "--olun-bulk-bar-offset";
8491
+ var cardHeights = /* @__PURE__ */ new Map();
8492
+ function writeOffset() {
8493
+ const root = document.documentElement;
8494
+ const height = Math.max(0, ...cardHeights.values());
8495
+ if (height > 0) root.style.setProperty(OFFSET_PROPERTY, `calc(${height}px + ${FLOATING_BOTTOM})`);
8496
+ else root.style.removeProperty(OFFSET_PROPERTY);
8497
+ }
8498
+ function reportHeight(key, card) {
8499
+ const height = card.offsetHeight;
8500
+ if (height > 0) cardHeights.set(key, height);
8501
+ else cardHeights.delete(key);
8502
+ writeOffset();
8503
+ }
8452
8504
  var useIsomorphicLayoutEffect2 = typeof window === "undefined" ? React36.useEffect : React36.useLayoutEffect;
8505
+ function useBottomOffset(card) {
8506
+ const [key] = React36.useState(() => ({}));
8507
+ React36.useEffect(() => {
8508
+ if (!card) return;
8509
+ const report = () => reportHeight(key, card);
8510
+ report();
8511
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(report);
8512
+ observer?.observe(card);
8513
+ if (!observer) window.addEventListener("resize", report);
8514
+ return () => {
8515
+ observer?.disconnect();
8516
+ if (!observer) window.removeEventListener("resize", report);
8517
+ cardHeights.delete(key);
8518
+ writeOffset();
8519
+ };
8520
+ }, [key, card]);
8521
+ useIsomorphicLayoutEffect2(() => {
8522
+ if (card && typeof ResizeObserver === "undefined") reportHeight(key, card);
8523
+ });
8524
+ }
8453
8525
  function useCompactSummary(slot, enabled) {
8454
8526
  const [fullWidth, setFullWidth] = React36.useState(null);
8455
8527
  const measure = React36.useCallback(() => {
@@ -8487,7 +8559,9 @@ function BulkActionBar({
8487
8559
  }) {
8488
8560
  const inline = placement === "inline";
8489
8561
  if (inline) warnInlineOnce();
8490
- const trailingSlot = trailing !== void 0 && trailing !== null ? /* @__PURE__ */ jsx39(
8562
+ const hasTrailing = trailing !== void 0 && trailing !== null;
8563
+ if (!inline && hasTrailing) warnTrailingOnce();
8564
+ const trailingSlot = inline && hasTrailing ? /* @__PURE__ */ jsx39(
8491
8565
  "div",
8492
8566
  {
8493
8567
  "data-slot": "bulk-action-bar-trailing",
@@ -8505,6 +8579,8 @@ function BulkActionBar({
8505
8579
  summaryElement,
8506
8580
  inline && compactSummary !== void 0 && compactSummary !== null
8507
8581
  );
8582
+ const [card, setCard] = React36.useState(null);
8583
+ useBottomOffset(card);
8508
8584
  if (count === 0 && idleContent !== void 0 && trailingSlot) {
8509
8585
  return /* @__PURE__ */ jsxs35(
8510
8586
  "div",
@@ -8559,16 +8635,31 @@ function BulkActionBar({
8559
8635
  "span",
8560
8636
  {
8561
8637
  "data-slot": "bulk-action-bar-count",
8562
- style: {
8563
- paddingLeft: inline ? void 0 : "var(--space-2)",
8638
+ style: inline ? {
8564
8639
  paddingRight: "var(--space-1)",
8565
8640
  fontFamily: "var(--font-sans)",
8566
8641
  fontSize: 13,
8567
- fontWeight: inline ? 600 : 500,
8642
+ fontWeight: 600,
8568
8643
  fontVariantNumeric: "tabular-nums",
8569
8644
  color: "var(--text-primary)",
8570
8645
  whiteSpace: "nowrap"
8571
- },
8646
+ } : (
8647
+ // First row, 32px like the ✕ beside it; the right padding keeps text clear of it,
8648
+ // and a long unbroken unit or hint breaks instead of running under it.
8649
+ {
8650
+ display: "flex",
8651
+ minHeight: 32,
8652
+ alignItems: "center",
8653
+ paddingRight: CLEAR_CLEARANCE,
8654
+ fontFamily: "var(--font-sans)",
8655
+ fontSize: 13,
8656
+ fontWeight: 500,
8657
+ fontVariantNumeric: "tabular-nums",
8658
+ color: "var(--text-primary)",
8659
+ wordBreak: "keep-all",
8660
+ overflowWrap: "anywhere"
8661
+ }
8662
+ ),
8572
8663
  children: count > 0 ? `${count.toLocaleString("ko-KR")}${unit} \uC120\uD0DD\uB428` : emptyHint
8573
8664
  }
8574
8665
  );
@@ -8578,7 +8669,7 @@ function BulkActionBar({
8578
8669
  ref: setSummaryElement,
8579
8670
  "data-slot": "bulk-action-bar-summary",
8580
8671
  style: {
8581
- display: inline ? "flex" : "inline-flex",
8672
+ display: "flex",
8582
8673
  minWidth: 0,
8583
8674
  alignItems: "center",
8584
8675
  columnGap: "var(--space-3)",
@@ -8601,27 +8692,30 @@ function BulkActionBar({
8601
8692
  whiteSpace: "nowrap",
8602
8693
  contain: "inline-size"
8603
8694
  }
8604
- ) : { flexWrap: "wrap", wordBreak: "keep-all" }
8695
+ ) : (
8696
+ // Its own row at the card's full inner width; wraps, never clips.
8697
+ { flexWrap: "wrap", lineHeight: 1.5, wordBreak: "keep-all", overflowWrap: "anywhere" }
8698
+ )
8605
8699
  },
8606
8700
  children: compact ? compactSummary : summary
8607
8701
  }
8608
8702
  ) : null;
8609
- const actionGroup = /* @__PURE__ */ jsx39(
8610
- "div",
8611
- {
8612
- "data-slot": "bulk-action-bar-actions",
8613
- style: {
8614
- display: "flex",
8615
- minWidth: 0,
8616
- flexWrap: inline ? "nowrap" : "wrap",
8617
- alignItems: "center",
8618
- justifyContent: "flex-end",
8619
- gap: 2
8620
- },
8621
- children: actions.map((action) => /* @__PURE__ */ jsx39(ActionButton, { action }, action.key))
8622
- }
8623
- );
8624
8703
  if (inline) {
8704
+ const actionGroup = /* @__PURE__ */ jsx39(
8705
+ "div",
8706
+ {
8707
+ "data-slot": "bulk-action-bar-actions",
8708
+ style: {
8709
+ display: "flex",
8710
+ minWidth: 0,
8711
+ flexWrap: "nowrap",
8712
+ alignItems: "center",
8713
+ justifyContent: "flex-end",
8714
+ gap: 2
8715
+ },
8716
+ children: actions.map((action) => /* @__PURE__ */ jsx39(ActionButton, { action }, action.key))
8717
+ }
8718
+ );
8625
8719
  return /* @__PURE__ */ jsxs35(
8626
8720
  "div",
8627
8721
  {
@@ -8686,6 +8780,7 @@ function BulkActionBar({
8686
8780
  "animate-[olun-bulk-action-bar-in_var(--dur-fast)_var(--ease-out)_both] motion-reduce:animate-none",
8687
8781
  className
8688
8782
  ),
8783
+ ref: setCard,
8689
8784
  style: { ...floatingStyle, ...style },
8690
8785
  ...props,
8691
8786
  children: [
@@ -8697,8 +8792,8 @@ function BulkActionBar({
8697
8792
  style: {
8698
8793
  display: "flex",
8699
8794
  minWidth: 0,
8700
- alignItems: "center",
8701
- gap: "var(--space-2)"
8795
+ flexDirection: "column",
8796
+ rowGap: "var(--space-2)"
8702
8797
  },
8703
8798
  children: [
8704
8799
  countLabel,
@@ -8706,11 +8801,29 @@ function BulkActionBar({
8706
8801
  ]
8707
8802
  }
8708
8803
  ),
8709
- count > 0 && actions.length > 0 ? /* @__PURE__ */ jsx39(Divider, {}) : null,
8710
- actionGroup,
8711
- /* @__PURE__ */ jsx39(Divider, {}),
8712
- /* @__PURE__ */ jsx39(ClearButton, { onClear }),
8713
- trailingSlot
8804
+ actions.length > 0 ? /* @__PURE__ */ jsx39(
8805
+ "div",
8806
+ {
8807
+ "data-slot": "bulk-action-bar-actions",
8808
+ style: {
8809
+ display: "flex",
8810
+ flexWrap: "wrap",
8811
+ justifyContent: "flex-end",
8812
+ gap: "var(--space-2)",
8813
+ marginTop: "var(--space-3)"
8814
+ },
8815
+ children: actions.map((action) => /* @__PURE__ */ jsx39(CardActionButton, { action }, action.key))
8816
+ }
8817
+ ) : null,
8818
+ /* @__PURE__ */ jsx39(
8819
+ IconButton,
8820
+ {
8821
+ icon: X4,
8822
+ "aria-label": "\uC120\uD0DD \uD574\uC81C",
8823
+ onClick: onClear,
8824
+ style: { position: "absolute", top: "var(--space-2)", right: "var(--space-2)" }
8825
+ }
8826
+ )
8714
8827
  ]
8715
8828
  }
8716
8829
  );
@@ -10321,7 +10434,8 @@ var toastVariants = cva21(
10321
10434
  );
10322
10435
  var toastViewportVariants = cva21(
10323
10436
  [
10324
- "fixed bottom-[var(--space-6)] right-[var(--space-6)] z-[var(--z-toast,50)]",
10437
+ // A floating BulkActionBar publishes --olun-bulk-bar-offset while it is up; sit 8px above it.
10438
+ "fixed bottom-[max(var(--space-6),calc(var(--olun-bulk-bar-offset,0px)+var(--space-2)))] right-[var(--space-6)] z-[var(--z-toast,50)]",
10325
10439
  "flex w-[min(420px,calc(100vw-var(--space-12)))] list-none flex-col gap-[var(--space-2)]",
10326
10440
  "pointer-events-none outline-none"
10327
10441
  ].join(" ")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olundot/ui",
3
- "version": "0.20.0-next.10",
3
+ "version": "0.20.0-next.12",
4
4
  "description": "OLUN. shared React UI components built on OLUN design tokens",
5
5
  "license": "MIT",
6
6
  "keywords": [