@rackbops/ui-react 0.2.42 → 0.2.44

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/Tabs.js CHANGED
@@ -47,10 +47,10 @@ export const Tabs = forwardRef(function Tabs({ items, defaultId, activeId: activ
47
47
  const selected = t.id === activeId;
48
48
  return (_jsx("button", { ref: (el) => {
49
49
  tabRefs.current[idx] = el;
50
- }, type: "button", role: "tab", id: `${base}-tab-${t.id}`, "aria-selected": selected, "aria-controls": `${base}-panel-${t.id}`, tabIndex: selected ? 0 : -1, className: cx("rb-tab", selected && "rb-tab--active"), onClick: () => select(t.id), onKeyDown: onKeyDown, children: t.label }, t.id));
51
- }) }), items.map((t) => {
50
+ }, type: "button", role: "tab", id: `${base}-tab-${idx}`, "aria-selected": selected, "aria-controls": `${base}-panel-${idx}`, tabIndex: selected ? 0 : -1, className: cx("rb-tab", selected && "rb-tab--active"), onClick: () => select(t.id), onKeyDown: onKeyDown, children: t.label }, t.id));
51
+ }) }), items.map((t, idx) => {
52
52
  const selected = t.id === activeId;
53
- return (_jsx("div", { className: "rb-tabpanel", role: "tabpanel", id: `${base}-panel-${t.id}`, "aria-labelledby": `${base}-tab-${t.id}`, hidden: !selected, tabIndex: 0, children: t.content }, t.id));
53
+ return (_jsx("div", { className: "rb-tabpanel", role: "tabpanel", id: `${base}-panel-${idx}`, "aria-labelledby": `${base}-tab-${idx}`, hidden: !selected, tabIndex: 0, children: t.content }, t.id));
54
54
  })] }));
55
55
  });
56
56
  Tabs.displayName = "Tabs";
@@ -1,4 +1,4 @@
1
- import type { ReactNode } from "react";
1
+ import { type ReactNode } from "react";
2
2
  export interface TabstripTab {
3
3
  /** Unique among this strip's tabs; what `onSelect` reports and `selected` matches. */
4
4
  id: string;
package/dist/Tabstrip.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRef } from "react";
2
3
  import { cx } from "./cx.js";
3
4
  /**
4
5
  * A controlled tab strip on the `rb-tabstrip` contract -- the design system's existing
@@ -18,8 +19,15 @@ import { cx } from "./cx.js";
18
19
  * relationship that does not exist would mislead a screen-reader user.
19
20
  */
20
21
  export function Tabstrip({ tabs, selected, onSelect, label }) {
22
+ const tabRefs = useRef([]);
21
23
  function onKeyDown(event) {
22
- const index = tabs.findIndex((t) => t.id === selected);
24
+ // Index from the tab that received the key when there is one: a controlled parent that
25
+ // commits `selected` late (a router transition, a debounce) would otherwise leave `selected`
26
+ // stale between presses, and focus (which has already moved) and the next "from" index
27
+ // would disagree. Fall back to `selected` when the event came from the strip itself.
28
+ const focused = event.target.closest?.('[role="tab"]');
29
+ const focusedIndex = focused ? tabRefs.current.indexOf(focused) : -1;
30
+ const index = focusedIndex !== -1 ? focusedIndex : tabs.findIndex((t) => t.id === selected);
23
31
  if (index === -1) {
24
32
  return;
25
33
  }
@@ -44,6 +52,8 @@ export function Tabstrip({ tabs, selected, onSelect, label }) {
44
52
  if (target) {
45
53
  event.preventDefault();
46
54
  onSelect(target.id);
55
+ // Roving tabindex: focus follows the selection, so Enter/Space acts on the new tab.
56
+ tabRefs.current[next]?.focus();
47
57
  }
48
58
  }
49
59
  // When `selected` matches no tab (a caller mid-reload passing a stale id), every tab would
@@ -54,7 +64,9 @@ export function Tabstrip({ tabs, selected, onSelect, label }) {
54
64
  return (_jsx("div", { role: "tablist", "aria-label": label, className: "rb-tabstrip", onKeyDown: onKeyDown, children: tabs.map((tab, index) => {
55
65
  const isSelected = tab.id === selected;
56
66
  const isTabStop = hasSelected ? isSelected : index === 0;
57
- return (_jsxs("button", { type: "button", role: "tab", "aria-selected": isSelected,
67
+ return (_jsxs("button", { ref: (el) => {
68
+ tabRefs.current[index] = el;
69
+ }, type: "button", role: "tab", "aria-selected": isSelected,
58
70
  // Roving tabIndex: exactly one tab is reachable by Tab, so the whole strip is one
59
71
  // stop and the arrow keys above do the moving within it.
60
72
  tabIndex: isTabStop ? 0 : -1, className: cx("rb-tabstrip__tab", isSelected && "rb-tabstrip__tab--active"), onClick: () => onSelect(tab.id), children: [tab.label, tab.badge !== undefined && _jsx("span", { className: "rb-tabstrip__badge", children: tab.badge })] }, tab.id));
@@ -1,4 +1,8 @@
1
1
  import { type HTMLAttributes, type ProgressHTMLAttributes, type ReactNode, type RefAttributes } from "react";
2
+ /** A forwardRef DOM wrapper that adds a base rb-* class and any fixed static attributes. */
3
+ export declare function styled<E extends HTMLElement, P extends {
4
+ className?: string;
5
+ }>(tag: string, cls: string, attrs?: Record<string, unknown>): import("react").ForwardRefExoticComponent<import("react").PropsWithoutRef<P> & RefAttributes<E>>;
2
6
  export type SemanticVariant = "info" | "success" | "warning" | "danger";
3
7
  export interface BadgeProps extends HTMLAttributes<HTMLSpanElement>, RefAttributes<HTMLSpanElement> {
4
8
  variant?: SemanticVariant;
package/dist/feedback.js CHANGED
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { createElement, forwardRef, } from "react";
3
3
  import { cx } from "./cx.js";
4
4
  /** A forwardRef DOM wrapper that adds a base rb-* class and any fixed static attributes. */
5
- function styled(tag, cls, attrs = {}) {
5
+ export function styled(tag, cls, attrs = {}) {
6
6
  return forwardRef(function Styled({ className, ...rest }, ref) {
7
7
  return createElement(tag, { ref, ...attrs, className: cx(cls, className), ...rest });
8
8
  });
package/dist/form.js CHANGED
@@ -1,12 +1,7 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { createElement, forwardRef, } from "react";
2
+ import { forwardRef, } from "react";
3
3
  import { cx } from "./cx.js";
4
- /** A forwardRef DOM wrapper that only adds a base rb-* class -- Input, Textarea, Select, Label, Field. */
5
- function styled(tag, cls) {
6
- return forwardRef(function Styled({ className, ...rest }, ref) {
7
- return createElement(tag, { ref, className: cx(cls, className), ...rest });
8
- });
9
- }
4
+ import { styled } from "./feedback.js";
10
5
  export const Input = styled("input", "rb-input");
11
6
  Input.displayName = "Input";
12
7
  export const Textarea = styled("textarea", "rb-textarea");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rackbops/ui-react",
3
- "version": "0.2.42",
3
+ "version": "0.2.44",
4
4
  "description": "React components styled by @rackbops/styles rackbops themes.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -12,6 +12,7 @@
12
12
  "access": "public"
13
13
  },
14
14
  "type": "module",
15
+ "sideEffects": false,
15
16
  "files": [
16
17
  "dist",
17
18
  "LICENSE",
@@ -26,17 +27,19 @@
26
27
  }
27
28
  },
28
29
  "peerDependencies": {
30
+ "@rackbops/styles": "^0.2.44",
29
31
  "react": ">=18"
30
32
  },
31
33
  "devDependencies": {
34
+ "@rackbops/styles": "^0.2.44",
32
35
  "@types/jsdom": "^30.0.0",
33
- "@types/node": "^26.0.0",
36
+ "@types/node": "^26.6.3",
34
37
  "@types/react": "^19.0.0",
35
38
  "@types/react-dom": "^19.0.0",
36
- "jsdom": "^30.0.1",
39
+ "jsdom": "^30.1.1",
37
40
  "react": "^19.0.0",
38
41
  "react-dom": "^19.0.0",
39
- "tsx": "^4.19.0",
42
+ "tsx": "^4.23.15",
40
43
  "typescript": "^7.0.0"
41
44
  },
42
45
  "scripts": {