@salemcobalt/ui 0.2.0-rc.34 → 0.2.0-rc.36

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.d.ts CHANGED
@@ -1310,12 +1310,19 @@ declare const Toolbar: {
1310
1310
  };
1311
1311
 
1312
1312
  type BannerTone = 'info' | 'success' | 'warning' | 'danger' | 'neutral';
1313
+ type BannerPlacement = 'inline' | 'page';
1313
1314
  interface BannerProps extends ComponentPropsWithRef<'div'> {
1314
1315
  /** Colour and default icon. `warning` and `danger` are announced as alerts; the rest as status. */
1315
1316
  tone?: BannerTone | undefined;
1317
+ /**
1318
+ * `inline` (default): a rounded strip inside a view. `page`: a full-bleed notice across the top of the page, above
1319
+ * the header: square, compact, with a hairline under it. A page notice is there on every load, so pass
1320
+ * `role="status"` with a `warning` tone unless it must interrupt.
1321
+ */
1322
+ placement?: BannerPlacement | undefined;
1316
1323
  /** Replaces the tone's icon; `false` removes it. */
1317
1324
  icon?: ReactNode | false | undefined;
1318
- /** One Button, usually `variant="ghost" size="sm"`, at the end of the strip. */
1325
+ /** One Button, usually `variant="ghost" size="sm"`, at the end of the strip; or an Inline of two (Details, Hide). */
1319
1326
  action?: ReactNode | undefined;
1320
1327
  /** Shows a close button that hides the banner. */
1321
1328
  dismissible?: boolean | undefined;
@@ -1327,7 +1334,7 @@ interface BannerProps extends ComponentPropsWithRef<'div'> {
1327
1334
  dismissLabel?: string | undefined;
1328
1335
  }
1329
1336
  /** A single-line status strip across the top of a view: paused, offline, "3 new since you looked". */
1330
- declare function Banner({ tone, icon, action, dismissible, onDismiss, open, dismissLabel, role, className, children, ...rest }: BannerProps): react.JSX.Element | null;
1337
+ declare function Banner({ tone, placement, icon, action, dismissible, onDismiss, open, dismissLabel, role, className, children, ...rest }: BannerProps): react.JSX.Element | null;
1331
1338
 
1332
1339
  interface SpinnerProps extends ComponentPropsWithRef<'span'> {
1333
1340
  /** `xs` 12, `sm` 14, `md` 16 (default), `lg` 20, `xl` 24, or `em` to follow the font size. */
package/dist/index.js CHANGED
@@ -743,6 +743,51 @@ import { useRender as useRender2 } from "@base-ui/react/use-render";
743
743
 
744
744
  // src/components/Tooltip/Tooltip.tsx
745
745
  import { Tooltip as BaseTooltip } from "@base-ui/react/tooltip";
746
+
747
+ // src/utils/useSafeAreaPadding.ts
748
+ import { useSyncExternalStore } from "react";
749
+ var EDGE = 5;
750
+ var NONE = { top: EDGE, right: EDGE, bottom: EDGE, left: EDGE };
751
+ var current = NONE;
752
+ var listeners = /* @__PURE__ */ new Set();
753
+ function measure() {
754
+ const probe = document.createElement("div");
755
+ probe.setAttribute("data-sc-safe-area-probe", "");
756
+ probe.style.cssText = "position:fixed;visibility:hidden;pointer-events:none;padding:env(safe-area-inset-top,0px) env(safe-area-inset-right,0px) env(safe-area-inset-bottom,0px) env(safe-area-inset-left,0px)";
757
+ document.body.appendChild(probe);
758
+ const style = getComputedStyle(probe);
759
+ const px = (value) => EDGE + (parseFloat(value) || 0);
760
+ const next = {
761
+ top: px(style.paddingTop),
762
+ right: px(style.paddingRight),
763
+ bottom: px(style.paddingBottom),
764
+ left: px(style.paddingLeft)
765
+ };
766
+ probe.remove();
767
+ if (next.top === current.top && next.right === current.right && next.bottom === current.bottom && next.left === current.left) {
768
+ return;
769
+ }
770
+ current = next;
771
+ listeners.forEach((listener) => listener());
772
+ }
773
+ function subscribe(onChange) {
774
+ if (listeners.size === 0) window.addEventListener("resize", measure);
775
+ listeners.add(onChange);
776
+ measure();
777
+ return () => {
778
+ listeners.delete(onChange);
779
+ if (listeners.size === 0) window.removeEventListener("resize", measure);
780
+ };
781
+ }
782
+ function useSafeAreaPadding() {
783
+ return useSyncExternalStore(
784
+ subscribe,
785
+ () => current,
786
+ () => NONE
787
+ );
788
+ }
789
+
790
+ // src/components/Tooltip/Tooltip.tsx
746
791
  import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
747
792
  function TooltipBase({
748
793
  content,
@@ -757,6 +802,7 @@ function TooltipBase({
757
802
  disabled,
758
803
  className
759
804
  }) {
805
+ const collisionPadding = useSafeAreaPadding();
760
806
  return /* @__PURE__ */ jsxs6(
761
807
  BaseTooltip.Root,
762
808
  {
@@ -766,10 +812,20 @@ function TooltipBase({
766
812
  disabled,
767
813
  children: [
768
814
  /* @__PURE__ */ jsx7(BaseTooltip.Trigger, { render: children, delay, closeDelay }),
769
- /* @__PURE__ */ jsx7(BaseTooltip.Portal, { children: /* @__PURE__ */ jsx7(BaseTooltip.Positioner, { side, align, sideOffset: 6, className: "sc-tooltip__positioner", children: /* @__PURE__ */ jsxs6(BaseTooltip.Popup, { role: "tooltip", className: cx("sc-tooltip", className), children: [
770
- /* @__PURE__ */ jsx7(BaseTooltip.Arrow, { className: "sc-tooltip__arrow" }),
771
- content
772
- ] }) }) })
815
+ /* @__PURE__ */ jsx7(BaseTooltip.Portal, { children: /* @__PURE__ */ jsx7(
816
+ BaseTooltip.Positioner,
817
+ {
818
+ side,
819
+ align,
820
+ sideOffset: 6,
821
+ collisionPadding,
822
+ className: "sc-tooltip__positioner",
823
+ children: /* @__PURE__ */ jsxs6(BaseTooltip.Popup, { role: "tooltip", className: cx("sc-tooltip", className), children: [
824
+ /* @__PURE__ */ jsx7(BaseTooltip.Arrow, { className: "sc-tooltip__arrow" }),
825
+ content
826
+ ] })
827
+ }
828
+ ) })
773
829
  ]
774
830
  }
775
831
  );
@@ -866,7 +922,18 @@ function Content2({
866
922
  children,
867
923
  ...rest
868
924
  }) {
869
- return /* @__PURE__ */ jsx9(BaseMenu.Portal, { children: /* @__PURE__ */ jsx9(BaseMenu.Positioner, { side, align, sideOffset, className: "sc-menu__positioner", children: /* @__PURE__ */ jsx9(BaseMenu.Popup, { className: cx("sc-menu", className), ...rest, children }) }) });
925
+ const collisionPadding = useSafeAreaPadding();
926
+ return /* @__PURE__ */ jsx9(BaseMenu.Portal, { children: /* @__PURE__ */ jsx9(
927
+ BaseMenu.Positioner,
928
+ {
929
+ side,
930
+ align,
931
+ sideOffset,
932
+ collisionPadding,
933
+ className: "sc-menu__positioner",
934
+ children: /* @__PURE__ */ jsx9(BaseMenu.Popup, { className: cx("sc-menu", className), ...rest, children })
935
+ }
936
+ ) });
870
937
  }
871
938
  function useItemBody({
872
939
  icon,
@@ -1195,12 +1262,13 @@ function Select({
1195
1262
  const items = options.flatMap((entry) => isGroup(entry) ? entry.options : [entry]);
1196
1263
  const [internal, setInternal] = useState(defaultValue ?? null);
1197
1264
  const controlled = value !== void 0;
1198
- const current = controlled ? value : internal;
1265
+ const current2 = controlled ? value : internal;
1199
1266
  const setValue = (next) => {
1200
1267
  if (!controlled) setInternal(next);
1201
1268
  onValueChange?.(next);
1202
1269
  };
1203
- const canClear = clearable && current != null && !disabled && !readOnly;
1270
+ const canClear = clearable && current2 != null && !disabled && !readOnly;
1271
+ const collisionPadding = useSafeAreaPadding();
1204
1272
  const onTriggerKeyDown = (event) => {
1205
1273
  if (canClear && (event.key === "Backspace" || event.key === "Delete")) {
1206
1274
  event.preventDefault();
@@ -1224,7 +1292,7 @@ function Select({
1224
1292
  BaseSelect.Root,
1225
1293
  {
1226
1294
  items,
1227
- value: current,
1295
+ value: current2,
1228
1296
  onValueChange: (next) => setValue(next),
1229
1297
  disabled,
1230
1298
  required,
@@ -1262,6 +1330,7 @@ function Select({
1262
1330
  {
1263
1331
  className: "sc-select__positioner",
1264
1332
  sideOffset: 4,
1333
+ collisionPadding,
1265
1334
  alignItemWithTrigger: false,
1266
1335
  children: /* @__PURE__ */ jsx13(BaseSelect.Popup, { className: "sc-select__popup", children: /* @__PURE__ */ jsx13(BaseSelect.List, { children: options.map(
1267
1336
  (entry, i) => isGroup(entry) ? /* @__PURE__ */ jsxs11(BaseSelect.Group, { className: "sc-select__group", children: [
@@ -1436,6 +1505,7 @@ var toneIcons = {
1436
1505
  };
1437
1506
  function Banner({
1438
1507
  tone = "info",
1508
+ placement = "inline",
1439
1509
  icon,
1440
1510
  action,
1441
1511
  dismissible = false,
@@ -1457,6 +1527,7 @@ function Banner({
1457
1527
  role: role ?? (tone === "warning" || tone === "danger" ? "alert" : "status"),
1458
1528
  className: cx("sc-banner", className),
1459
1529
  "data-tone": tone,
1530
+ "data-placement": placement === "page" ? "page" : void 0,
1460
1531
  ...rest,
1461
1532
  children: [
1462
1533
  glyph ? /* @__PURE__ */ jsx18("span", { className: "sc-banner__icon", "aria-hidden": "true", children: glyph }) : null,
@@ -1533,12 +1604,12 @@ function Progress({
1533
1604
  className,
1534
1605
  ...rest
1535
1606
  }) {
1536
- const current = value ?? null;
1537
- const hasValueText = valueText != null || showValue && current !== null;
1607
+ const current2 = value ?? null;
1608
+ const hasValueText = valueText != null || showValue && current2 !== null;
1538
1609
  return /* @__PURE__ */ jsxs17(
1539
1610
  BaseProgress.Root,
1540
1611
  {
1541
- value: current,
1612
+ value: current2,
1542
1613
  max,
1543
1614
  min,
1544
1615
  getAriaValueText: (formatted) => typeof valueText === "string" ? valueText : formatted ?? "",
@@ -2478,9 +2549,9 @@ function fileTypeLabel(file) {
2478
2549
  }
2479
2550
 
2480
2551
  // src/utils/useMediaQuery.ts
2481
- import { useCallback as useCallback2, useSyncExternalStore } from "react";
2552
+ import { useCallback as useCallback2, useSyncExternalStore as useSyncExternalStore2 } from "react";
2482
2553
  function useMediaQuery(query) {
2483
- const subscribe = useCallback2(
2554
+ const subscribe2 = useCallback2(
2484
2555
  (onChange) => {
2485
2556
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return () => {
2486
2557
  };
@@ -2490,8 +2561,8 @@ function useMediaQuery(query) {
2490
2561
  },
2491
2562
  [query]
2492
2563
  );
2493
- return useSyncExternalStore(
2494
- subscribe,
2564
+ return useSyncExternalStore2(
2565
+ subscribe2,
2495
2566
  () => typeof window.matchMedia === "function" && window.matchMedia(query).matches,
2496
2567
  () => false
2497
2568
  );
@@ -2646,7 +2717,7 @@ function FileInput({
2646
2717
  const messagesId = `${id}messages`;
2647
2718
  const controlled = files !== void 0;
2648
2719
  const [uncontrolledFiles, setUncontrolledFiles] = useState6(defaultFiles ?? []);
2649
- const current = controlled ? files : uncontrolledFiles;
2720
+ const current2 = controlled ? files : uncontrolledFiles;
2650
2721
  const [rejections, setRejections] = useState6([]);
2651
2722
  const [inputId, setInputId] = useState6();
2652
2723
  const [previewFile, setPreviewFile] = useState6(null);
@@ -2658,21 +2729,21 @@ function FileInput({
2658
2729
  const moduleRef = useRef4(null);
2659
2730
  const patchRef = useRef4(() => {
2660
2731
  });
2661
- const settledRef = useRef4(current);
2732
+ const settledRef = useRef4(current2);
2662
2733
  const targetRef = useRef4(null);
2663
2734
  const acceptList = parseAccept(accept);
2664
2735
  const acceptAttr = acceptList.join(",");
2665
2736
  const maxBytes = parseSize(maxFileSize);
2666
2737
  const messages = [...rejections, ...error != null ? [error] : []];
2667
2738
  const isInvalid = invalid || messages.length > 0;
2668
- const idleText = idleLabel ?? (multiple ? "Drop files here or" : current.length > 0 ? "Drop another file to replace it, or" : "Drop a file here or");
2739
+ const idleText = idleLabel ?? (multiple ? "Drop files here or" : current2.length > 0 ? "Drop another file to replace it, or" : "Drop a file here or");
2669
2740
  const live = {
2670
2741
  acceptList,
2671
2742
  acceptLabel: acceptLabel ?? describeAccept(acceptList),
2672
2743
  maxBytes,
2673
2744
  maxFiles: multiple ? maxFiles : 1,
2674
2745
  multiple,
2675
- current,
2746
+ current: current2,
2676
2747
  controlled,
2677
2748
  onFilesChange,
2678
2749
  labelId,
@@ -2914,15 +2985,15 @@ function FileInput({
2914
2985
  const items = pond.getFiles();
2915
2986
  const idle = items.filter((item) => item.status === mod.FileStatus.IDLE);
2916
2987
  const inPond = idle.map((item) => item.file);
2917
- if (sameFiles(inPond, current)) {
2918
- settledRef.current = current;
2988
+ if (sameFiles(inPond, current2)) {
2989
+ settledRef.current = current2;
2919
2990
  return;
2920
2991
  }
2921
- targetRef.current = current;
2922
- for (const item of idle) if (!current.includes(item.file)) pond.removeFile(item.id);
2923
- const toAdd = current.filter((file) => !inPond.includes(file));
2992
+ targetRef.current = current2;
2993
+ for (const item of idle) if (!current2.includes(item.file)) pond.removeFile(item.id);
2994
+ const toAdd = current2.filter((file) => !inPond.includes(file));
2924
2995
  if (toAdd.length > 0) void pond.addFiles(toAdd);
2925
- }, [current, pondVersion]);
2996
+ }, [current2, pondVersion]);
2926
2997
  const ready = pondVersion > 0;
2927
2998
  return /* @__PURE__ */ jsxs25(
2928
2999
  "div",
@@ -3001,15 +3072,15 @@ function rowWidth(widths, gap) {
3001
3072
  for (const w of widths) total += w;
3002
3073
  return widths.length > 1 ? total + gap * (widths.length - 1) : total;
3003
3074
  }
3004
- function fitCount({ widths, current, available, more, gap }) {
3075
+ function fitCount({ widths, current: current2, available, more, gap }) {
3005
3076
  const n = widths.length;
3006
- const hasCurrent = current >= 0 && current < n;
3077
+ const hasCurrent = current2 >= 0 && current2 < n;
3007
3078
  const others = hasCurrent ? n - 1 : n;
3008
3079
  if (rowWidth(widths, gap) <= available + EPSILON) return others;
3009
- let used = more + (hasCurrent ? widths[current] + gap : 0);
3080
+ let used = more + (hasCurrent ? widths[current2] + gap : 0);
3010
3081
  let count = 0;
3011
3082
  for (let i = 0; i < n; i++) {
3012
- if (i === current) continue;
3083
+ if (i === current2) continue;
3013
3084
  const next = used + widths[i] + gap;
3014
3085
  if (next > available + EPSILON) break;
3015
3086
  used = next;
@@ -3017,11 +3088,11 @@ function fitCount({ widths, current, available, more, gap }) {
3017
3088
  }
3018
3089
  return count;
3019
3090
  }
3020
- function inlineFlags(length, current, count) {
3091
+ function inlineFlags(length, current2, count) {
3021
3092
  const flags = [];
3022
3093
  let seen = 0;
3023
3094
  for (let i = 0; i < length; i++) {
3024
- if (i === current) flags.push(true);
3095
+ if (i === current2) flags.push(true);
3025
3096
  else flags.push(seen++ < count);
3026
3097
  }
3027
3098
  return flags;
@@ -3080,7 +3151,7 @@ function PriorityNav({
3080
3151
  ...rest
3081
3152
  }) {
3082
3153
  const signature = signatureOf(items);
3083
- const current = items.findIndex((item) => item.current);
3154
+ const current2 = items.findIndex((item) => item.current);
3084
3155
  const [fit, setFit] = useState7({ signature: null, current: -1, count: 0, overflowing: false });
3085
3156
  const [open, setOpen] = useState7(false);
3086
3157
  const panelId = useId5();
@@ -3089,7 +3160,7 @@ function PriorityNav({
3089
3160
  const toggleRef = useRef5(null);
3090
3161
  const panelRef = useRef5(null);
3091
3162
  const measured = useRef5(null);
3092
- const currentRef = useRef5(current);
3163
+ const currentRef = useRef5(current2);
3093
3164
  const fitRef = useRef5(fit);
3094
3165
  const hysteresisRef = useRef5(overflowHysteresis);
3095
3166
  const onOverflowRef = useRef5(onOverflowChange);
@@ -3099,7 +3170,7 @@ function PriorityNav({
3099
3170
  });
3100
3171
  const measuring = fit.signature !== signature;
3101
3172
  useLayoutEffect2(() => {
3102
- currentRef.current = current;
3173
+ currentRef.current = current2;
3103
3174
  fitRef.current = fit;
3104
3175
  hysteresisRef.current = overflowHysteresis;
3105
3176
  onOverflowRef.current = onOverflowChange;
@@ -3119,11 +3190,11 @@ function PriorityNav({
3119
3190
  gap: parseFloat(getComputedStyle(list).columnGap) || 0,
3120
3191
  available: list.clientWidth
3121
3192
  };
3122
- } else if (fit.current === current) return;
3193
+ } else if (fit.current === current2) return;
3123
3194
  const m = measured.current;
3124
3195
  if (!m) return;
3125
- setFit({ signature, current, ...solve(m, current, fit.overflowing) });
3126
- }, [measuring, signature, current, fit]);
3196
+ setFit({ signature, current: current2, ...solve(m, current2, fit.overflowing) });
3197
+ }, [measuring, signature, current2, fit]);
3127
3198
  useEffect5(() => {
3128
3199
  const list = listRef.current;
3129
3200
  const more = moreRef.current;
@@ -3165,7 +3236,7 @@ function PriorityNav({
3165
3236
  live = false;
3166
3237
  };
3167
3238
  }, []);
3168
- const flags = measuring ? null : inlineFlags(items.length, current, fit.count);
3239
+ const flags = measuring ? null : inlineFlags(items.length, current2, fit.count);
3169
3240
  const overflow = flags ? items.filter((_, i) => !flags[i]) : [];
3170
3241
  const expanded = open && overflow.length > 0;
3171
3242
  const overflowBadges = overflow.map(badgeOf).filter((b) => b !== null);
@@ -3340,6 +3411,7 @@ function Content3({
3340
3411
  children,
3341
3412
  ...rest
3342
3413
  }) {
3414
+ const collisionPadding = useSafeAreaPadding();
3343
3415
  return /* @__PURE__ */ jsx32(BasePopover.Portal, { children: /* @__PURE__ */ jsx32(
3344
3416
  BasePopover.Positioner,
3345
3417
  {
@@ -3348,6 +3420,7 @@ function Content3({
3348
3420
  sideOffset: sideOffset ?? (arrow ? 8 : 4),
3349
3421
  anchor,
3350
3422
  collisionAvoidance: COLLISION_AVOIDANCE,
3423
+ collisionPadding,
3351
3424
  className: "sc-popover__positioner",
3352
3425
  children: /* @__PURE__ */ jsxs27(BasePopover.Popup, { className: cx("sc-popover", className), ...rest, children: [
3353
3426
  arrow ? /* @__PURE__ */ jsx32(BasePopover.Arrow, { className: "sc-popover__arrow" }) : null,
@@ -3540,15 +3613,15 @@ function Progress2({
3540
3613
  label ? /* @__PURE__ */ jsx34("div", { className: "sc-flow__progress-label", children: label }) : null,
3541
3614
  /* @__PURE__ */ jsx34("ol", { className: "sc-flow__segments", "aria-label": listLabel, children: chapters.map((chapter, i) => {
3542
3615
  const from = starts[i];
3543
- const current = step > from && step <= from + chapter.steps;
3544
- const done = Math.min(chapter.steps, Math.max(0, step - 1 - from)) + (current ? 0.5 : 0);
3616
+ const current2 = step > from && step <= from + chapter.steps;
3617
+ const done = Math.min(chapter.steps, Math.max(0, step - 1 - from)) + (current2 ? 0.5 : 0);
3545
3618
  const percent = chapter.steps > 0 ? Math.min(100, done / chapter.steps * 100) : 0;
3546
3619
  return /* @__PURE__ */ jsxs29(
3547
3620
  "li",
3548
3621
  {
3549
3622
  className: "sc-flow__segment",
3550
3623
  style: { flexGrow: chapter.steps },
3551
- "aria-current": current ? "step" : void 0,
3624
+ "aria-current": current2 ? "step" : void 0,
3552
3625
  children: [
3553
3626
  /* @__PURE__ */ jsx34("span", { className: "sc-flow__segment-fill", style: { inlineSize: `${percent}%` } }),
3554
3627
  /* @__PURE__ */ jsx34("span", { className: "sc-visually-hidden", children: chapter.label })
@@ -3582,7 +3655,7 @@ function RailGroup({ label, className, children, ...rest }) {
3582
3655
  ] });
3583
3656
  }
3584
3657
  function RailItem({
3585
- current = false,
3658
+ current: current2 = false,
3586
3659
  done = false,
3587
3660
  doneLabel = "done",
3588
3661
  adornment,
@@ -3596,7 +3669,7 @@ function RailItem({
3596
3669
  {
3597
3670
  type,
3598
3671
  className: cx("sc-flow__rail-item", className),
3599
- "aria-current": current ? "step" : void 0,
3672
+ "aria-current": current2 ? "step" : void 0,
3600
3673
  "data-done": done || void 0,
3601
3674
  ...rest,
3602
3675
  children: [