@salemcobalt/ui 0.2.0-rc.28 → 0.2.0-rc.29

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/README.md CHANGED
@@ -51,7 +51,7 @@ Styles ship as one stylesheet inside `@layer sc`, so an app's unlayered CSS alwa
51
51
  | `Menu` | `Root` (`open`, `onOpenChange`, `modal`), `Trigger` (secondary Button with a chevron, or `render={<IconButton label="More" />}`), `Content` (`side`, `align`), `Item` (`icon`, decorative; `description`, a muted second line that is the item's accessible description, not part of its name; `trailing`, `tone="danger"`, `disabled`), `LinkItem` (`href`, and the same `icon` and `description`), `CheckboxItem` (`checked`, `onCheckedChange`), `RadioGroup` (`value`, `onValueChange`, `label`) + `RadioItem`, `Group` (`label`), `Separator`. Arrow keys, Home/End, type-ahead, Esc and focus return come from Base UI. |
52
52
  | `Table` selection | `useRowSelection(ids)` (ids in visible order; ids that disappear leave the selection): `selected`, `selectedIds`, `count`, `anchor`, `headerState` none · some · all, `isSelected`, `toggle`, `toggleRange`, `selectAll`, `clear`; a pure reducer underneath (`selection.*` events). `Table.Root selection` + `Table.SelectionHeader` (`label`, mixed when some) + `Table.SelectionCell label="Select …"` in a `Table.Row selectionId`. Shift+click (checkbox or row) and Shift+Space extend from the anchor; Cmd/Ctrl+click on a row toggles without reaching its `onClick`; Cmd/Ctrl+A in the table selects all, Esc clears. |
53
53
  | `SelectionBar` | Floating bulk-action bar on Toolbar, centred at the bottom of the viewport (content width up to 48rem, gutters and the safe area respected, full width between the gutters under 640px, fade and slide-up, none under reduced motion): `count` (hidden at 0), `onClear`, children are `Toolbar.Button`s, `aria-label` ("Bulk actions"), `countLabel`, `clearLabel`. Mount it at the end of the scrolling content: while open it leaves a spacer as tall as itself so the last rows scroll clear. A polite status region announces the count; focus returns to where it came from when the bar leaves. |
54
- | `Toast` | `Toast.Provider` (`timeout` 6000, `limit` 3) near the root, `useToast().add({ title, description, tone, action: { label, onClick }, timeout, priority })` below it; Base UI Toast: polite region, paused on hover and focus, F6 to reach it, bottom-end at `--sc-z-toast`. |
54
+ | `Toast` | `Toast.Provider` (`timeout` 6000, `limit` 3) near the root (one viewport: a Provider inside another adds nothing), `useToast().add({ title, description, tone, action: { label, onClick, keepOpen }, timeout, priority, id, dismissLabel, onClose })` below it (`timeout: 0` stays; the same `id` updates in place; `dismissLabel` makes the × a text button such as Later); Base UI Toast: polite region, paused on hover and focus, F6 to reach it, bottom-end at `--sc-z-toast`, above an open `SelectionBar` or any bar with `anchor-name: --sc-bottom-bar`. |
55
55
  | `PriorityNav` | Priority+ primary navigation (docs/adr/0005): required `aria-label`; `items` as `{ href, label, id?, current?, badge?, badgeLabel? }` in priority order; links that fit stay inline and the rest go into a "More" disclosure (APG disclosure navigation: a `button` with `aria-expanded`, not a menu; Esc, click outside and focus leaving close it). The `current` link is never collapsed. `moreLabel`, `moreCountLabel(n)` (announced only: "More, 3 links"), `renderLink={(props) => <Link {...props} />}` for a router. Server-renders every link on one clipped line (scrolls sideways without JS), measures widths once (again after web fonts load or when keys/string labels change) and re-renders only when the inline count changes. Takes the free space in a flex row (`flex: 1 1 0%`); in a grid use a `minmax(0, 1fr)` track. The open panel sits in the top layer (Popover API), anchored to the toggle with CSS anchor positioning (a JS fallback elsewhere) and capped to the viewport. `data-overflowing` + `onOverflowChange(boolean)` let a header make room, with `overflowHysteresis` (px) so it cannot oscillate. Under 400px wide (container query) More shows only an ellipsis. `badge` (a count, or `'dot'`; `0` shows nothing) draws an accent pill or dot after the label, decorative, with `badgeLabel` ("2 suggestions to review") appended to the link's name; while the item is collapsed, More shows the collapsed items' summed count (a dot when they only have dots) and adds their `badgeLabel`s to its name ("More, 3 links, 2 suggestions to review"). Icon-only, the badge sits on More's corner. No motion. |
56
56
  | `Popover` | Non-modal panel on Base UI Popover: `Root` (`open`, `defaultOpen`, `onOpenChange`, `modal` default false), `Trigger` (secondary Button, or `render={<IconButton label dot />}`), `Content` (`side`, `align`, `sideOffset`, `arrow`, `anchor` to position against another element than the Trigger, e.g. a row's action button with a controlled `Root`; portaled to `document.body`; Menu's surface, `--sc-z-dropdown`, dropping to the backdrop layer while a Dialog opened from inside it is open, so the Dialog covers it), `Title` (names the dialog), `Description`, `Close` (the × with no children, or wraps a Button). Esc and focus return come from Base UI. |
57
57
  | `BroomIcon` | Animated 24px-grid icon for "a sweep is running": `sweeping`, `size` (Icon sizes, default `lg`, or `display` 72px with a 1.75px stroke), `title`. Inline SVG; strokes stay 1.25px at any size; holds still with the dust shown under `prefers-reduced-motion`. Inside an IconButton it takes the next icon size up. |
package/dist/index.d.ts CHANGED
@@ -1870,15 +1870,28 @@ interface ToastOptions {
1870
1870
  description?: ReactNode | undefined;
1871
1871
  /** A coloured edge. Default `neutral`. */
1872
1872
  tone?: ToastTone | undefined;
1873
- /** One action, usually Undo. It closes the toast after `onClick`. */
1873
+ /**
1874
+ * One action, usually Undo. It closes the toast after `onClick`, unless `keepOpen` (the action asks first, and the
1875
+ * person may cancel).
1876
+ */
1874
1877
  action?: {
1875
1878
  label: string;
1876
1879
  onClick: () => void;
1880
+ keepOpen?: boolean | undefined;
1877
1881
  } | undefined;
1878
1882
  /** Milliseconds before it closes; paused while hovered or focused; `0` keeps it. Default: the Provider's. */
1879
1883
  timeout?: number | undefined;
1880
1884
  /** `high` announces assertively (errors). Default `low`, polite. */
1881
1885
  priority?: 'low' | 'high' | undefined;
1886
+ /**
1887
+ * Names the toast. Adding one whose id is showing updates it in place (new text, the timer starts again) instead
1888
+ * of stacking a second.
1889
+ */
1890
+ id?: string | undefined;
1891
+ /** The dismiss control as a text button ("Later") instead of the ×. */
1892
+ dismissLabel?: string | undefined;
1893
+ /** Called once when it closes, however: dismissed, swiped away, its action, its timeout or `close()`. */
1894
+ onClose?: (() => void) | undefined;
1882
1895
  }
1883
1896
  interface ToastProviderProps {
1884
1897
  children?: ReactNode | undefined;
@@ -1887,8 +1900,12 @@ interface ToastProviderProps {
1887
1900
  /** How many show at once; older ones close. Default 3. */
1888
1901
  limit?: number | undefined;
1889
1902
  }
1890
- /** Holds the toasts for `useToast()` below it and renders them, bottom-end, in the top layer. */
1891
- declare function Provider({ children, timeout, limit }: ToastProviderProps): react.JSX.Element;
1903
+ /**
1904
+ * Holds the toasts for `useToast()` below it and renders them, bottom-end, above the page. One viewport per page: a
1905
+ * Provider inside another adds nothing (its `timeout` and `limit` are ignored), so a component may wrap itself in one
1906
+ * and still share the app's.
1907
+ */
1908
+ declare function Provider({ children, timeout, limit }: ToastProviderProps): string | number | bigint | boolean | react.JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | null | undefined;
1892
1909
  interface ToastApi {
1893
1910
  /** Shows a toast and returns its id. */
1894
1911
  add: (options: ToastOptions) => string;
package/dist/index.js CHANGED
@@ -2052,13 +2052,15 @@ function SelectionBar({
2052
2052
 
2053
2053
  // src/components/Toast/Toast.tsx
2054
2054
  import { Toast as BaseToast } from "@base-ui/react/toast";
2055
- import { useMemo as useMemo2 } from "react";
2055
+ import { createContext as createContext3, useContext as useContext3, useMemo as useMemo2 } from "react";
2056
2056
  import { jsx as jsx23, jsxs as jsxs20 } from "react/jsx-runtime";
2057
+ var InsideProvider = createContext3(false);
2057
2058
  function Provider2({ children, timeout = 6e3, limit = 3 }) {
2058
- return /* @__PURE__ */ jsxs20(BaseToast.Provider, { timeout, limit, children: [
2059
+ if (useContext3(InsideProvider)) return children;
2060
+ return /* @__PURE__ */ jsx23(InsideProvider.Provider, { value: true, children: /* @__PURE__ */ jsxs20(BaseToast.Provider, { timeout, limit, children: [
2059
2061
  children,
2060
2062
  /* @__PURE__ */ jsx23(BaseToast.Portal, { children: /* @__PURE__ */ jsx23(Viewport, {}) })
2061
- ] });
2063
+ ] }) });
2062
2064
  }
2063
2065
  function Viewport() {
2064
2066
  const { toasts, close } = BaseToast.useToastManager();
@@ -2074,27 +2076,40 @@ function Viewport() {
2074
2076
  render: /* @__PURE__ */ jsx23(Button, { variant: "secondary", size: "sm" })
2075
2077
  }
2076
2078
  ) : null,
2077
- /* @__PURE__ */ jsx23(IconButton, { label: "Dismiss", size: "sm", className: "sc-toast__close", onClick: () => close(toast.id), children: /* @__PURE__ */ jsx23(XIcon, {}) })
2079
+ toast.data?.dismissLabel ? /* @__PURE__ */ jsx23(Button, { variant: "ghost", size: "sm", className: "sc-toast__close", onClick: () => close(toast.id), children: toast.data.dismissLabel }) : /* @__PURE__ */ jsx23(IconButton, { label: "Dismiss", size: "sm", className: "sc-toast__close", onClick: () => close(toast.id), children: /* @__PURE__ */ jsx23(XIcon, {}) })
2078
2080
  ] }, toast.id)) });
2079
2081
  }
2080
2082
  function useToast() {
2081
2083
  const manager = BaseToast.useToastManager();
2082
2084
  return useMemo2(
2083
2085
  () => ({
2084
- add: ({ title, description, tone = "neutral", action, timeout, priority }) => {
2085
- let id = "";
2086
+ add: ({
2087
+ title,
2088
+ description,
2089
+ tone = "neutral",
2090
+ action,
2091
+ timeout,
2092
+ priority,
2093
+ id: given,
2094
+ dismissLabel,
2095
+ onClose
2096
+ }) => {
2097
+ let id = given ?? "";
2086
2098
  id = manager.add({
2087
2099
  title,
2088
2100
  description,
2089
2101
  type: tone,
2102
+ data: { dismissLabel },
2103
+ ...given ? { id: given } : {},
2090
2104
  ...timeout !== void 0 ? { timeout } : {},
2091
2105
  ...priority ? { priority } : {},
2106
+ ...onClose ? { onClose } : {},
2092
2107
  ...action ? {
2093
2108
  actionProps: {
2094
2109
  children: action.label,
2095
2110
  onClick: () => {
2096
2111
  action.onClick();
2097
- manager.close(id);
2112
+ if (!action.keepOpen) manager.close(id);
2098
2113
  }
2099
2114
  }
2100
2115
  } : {}
@@ -3398,9 +3413,9 @@ function BroomIcon({ sweeping = false, size = "lg", title, className, ...rest })
3398
3413
  // src/components/FullScreenFlow/FullScreenFlow.tsx
3399
3414
  import {
3400
3415
  Children as Children2,
3401
- createContext as createContext3,
3416
+ createContext as createContext4,
3402
3417
  useCallback as useCallback3,
3403
- useContext as useContext3,
3418
+ useContext as useContext4,
3404
3419
  useEffect as useEffect6,
3405
3420
  useId as useId6,
3406
3421
  useMemo as useMemo3,
@@ -3408,9 +3423,9 @@ import {
3408
3423
  useState as useState8
3409
3424
  } from "react";
3410
3425
  import { jsx as jsx34, jsxs as jsxs29 } from "react/jsx-runtime";
3411
- var FlowContext = createContext3({ announce: () => {
3426
+ var FlowContext = createContext4({ announce: () => {
3412
3427
  } });
3413
- var FrameContext = createContext3(void 0);
3428
+ var FrameContext = createContext4(void 0);
3414
3429
  var ENTER_IGNORES = 'textarea, button, a[href], summary, select, [contenteditable], [role="combobox"], [data-flow-keys="local"]';
3415
3430
  var ARROW_IGNORES = 'input, textarea, select, button, a[href], summary, [contenteditable], [role="radiogroup"], [role="slider"], [role="listbox"], [role="tablist"], [role="menu"], [role="grid"], [role="combobox"], [data-flow-keys="local"]';
3416
3431
  function Root7({
@@ -3680,8 +3695,8 @@ function Step({
3680
3695
  }) {
3681
3696
  const id = useId6();
3682
3697
  const titleRef = useRef6(null);
3683
- const phase = useContext3(FrameContext);
3684
- const { announce } = useContext3(FlowContext);
3698
+ const phase = useContext4(FrameContext);
3699
+ const { announce } = useContext4(FlowContext);
3685
3700
  const message = announcement ?? (typeof title === "string" ? title : void 0);
3686
3701
  const latest = useRef6({ message, announce });
3687
3702
  useEffect6(() => {