@camstack/ui-library 1.2.271 → 1.2.273

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.cjs CHANGED
@@ -32023,7 +32023,7 @@ function useTabsContext() {
32023
32023
  if (!ctx) throw new Error("Tabs compound components must be used within <Tabs>");
32024
32024
  return ctx;
32025
32025
  }
32026
- function Tabs({ value: controlledValue, onValueChange, defaultValue = "", className, ...props }) {
32026
+ function Tabs({ value: controlledValue, onValueChange, defaultValue = "", appearance = "underline", className, ...props }) {
32027
32027
  const [uncontrolledValue, setUncontrolledValue] = (0, react$1.useState)(defaultValue);
32028
32028
  const value = controlledValue ?? uncontrolledValue;
32029
32029
  const setValue = (0, react$1.useCallback)((next) => {
@@ -32033,7 +32033,8 @@ function Tabs({ value: controlledValue, onValueChange, defaultValue = "", classN
32033
32033
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TabsContext.Provider, {
32034
32034
  value: {
32035
32035
  value,
32036
- setValue
32036
+ setValue,
32037
+ appearance
32037
32038
  },
32038
32039
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
32039
32040
  className,
@@ -32059,14 +32060,15 @@ function Tabs({ value: controlledValue, onValueChange, defaultValue = "", classN
32059
32060
  * pinned to `hidden` makes it impossible here.
32060
32061
  */
32061
32062
  function TabsList({ className, ...props }) {
32063
+ const { appearance } = useTabsContext();
32062
32064
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
32063
32065
  role: "tablist",
32064
- className: cn("flex flex-row overflow-x-auto overflow-y-hidden border-b border-border-subtle", className),
32066
+ className: cn("flex flex-row overflow-x-auto overflow-y-hidden", appearance === "pills" ? "gap-1 border-b border-border-subtle py-1" : "border-b border-border-subtle", className),
32065
32067
  ...props
32066
32068
  });
32067
32069
  }
32068
32070
  function TabsTrigger({ value, className, ...props }) {
32069
- const { value: activeValue, setValue } = useTabsContext();
32071
+ const { value: activeValue, setValue, appearance } = useTabsContext();
32070
32072
  const isActive = value === activeValue;
32071
32073
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
32072
32074
  type: "button",
@@ -32074,7 +32076,7 @@ function TabsTrigger({ value, className, ...props }) {
32074
32076
  "aria-selected": isActive,
32075
32077
  "aria-controls": `tabpanel-${value}`,
32076
32078
  tabIndex: isActive ? 0 : -1,
32077
- className: cn("h-7 shrink-0 whitespace-nowrap text-xs px-3 transition-colors", isActive ? "border-b-2 border-primary text-foreground font-medium" : "text-foreground-muted hover:text-foreground", className),
32079
+ className: cn("h-7 shrink-0 whitespace-nowrap text-xs px-3 transition-colors", appearance === "pills" ? isActive ? "rounded-md bg-primary/10 font-medium text-primary" : "rounded-md text-foreground-muted hover:bg-surface-hover hover:text-foreground" : isActive ? "border-b-2 border-primary text-foreground font-medium" : "text-foreground-muted hover:text-foreground", className),
32078
32080
  onClick: () => setValue(value),
32079
32081
  ...props
32080
32082
  });
@@ -58419,12 +58421,23 @@ function ItemFormDialog({ open, onClose, title, icon: Icon, error, width = "lg",
58419
58421
  * Tiny URL query sync for surfaces that cannot assume react-router
58420
58422
  * (federated addon pages) or that only need a handful of params.
58421
58423
  *
58422
- * Reads on mount + `popstate`; writes with `history.replaceState`. Empty /
58423
- * null values are deleted so the URL stays short.
58424
+ * Reads on mount + whenever the query string changes (our writes, other
58425
+ * hook instances, react-router `setSearchParams`, back/forward). Writes
58426
+ * with `history.replaceState`. Empty / null values are deleted so the URL
58427
+ * stays short.
58428
+ *
58429
+ * Multiple `useUrlQueryParam` mounts (and a parent reading the same key via
58430
+ * this hook while a child writes) must stay in sync — otherwise a Rules
58431
+ * area pill updates `?section=` while the Notifications header still shows
58432
+ * "New rule" from a stale snapshot.
58424
58433
  */
58434
+ var URL_QUERY_EVENT = "camstack:url-query";
58425
58435
  function readParam(key) {
58426
58436
  return new URLSearchParams(window.location.search).get(key);
58427
58437
  }
58438
+ function notifyUrlQuery() {
58439
+ window.dispatchEvent(new Event(URL_QUERY_EVENT));
58440
+ }
58428
58441
  function writeParam(key, value) {
58429
58442
  const params = new URLSearchParams(window.location.search);
58430
58443
  if (value === null || value === "") params.delete(key);
@@ -58433,16 +58446,37 @@ function writeParam(key, value) {
58433
58446
  const url = qs ? `${window.location.pathname}?${qs}` : window.location.pathname;
58434
58447
  window.history.replaceState(null, "", url);
58435
58448
  }
58449
+ var historyPatched = false;
58450
+ /** So react-router (and anyone else) replaceState/pushState refresh every hook. */
58451
+ function ensureHistoryPatched() {
58452
+ if (historyPatched || typeof window === "undefined") return;
58453
+ historyPatched = true;
58454
+ const originalReplace = window.history.replaceState.bind(window.history);
58455
+ const originalPush = window.history.pushState.bind(window.history);
58456
+ window.history.replaceState = (...args) => {
58457
+ originalReplace(...args);
58458
+ notifyUrlQuery();
58459
+ };
58460
+ window.history.pushState = (...args) => {
58461
+ originalPush(...args);
58462
+ notifyUrlQuery();
58463
+ };
58464
+ }
58436
58465
  /**
58437
58466
  * One query param as React state. `null` means absent.
58438
58467
  * Changing it replaces the URL without a navigation.
58439
58468
  */
58440
58469
  function useUrlQueryParam(key) {
58470
+ ensureHistoryPatched();
58441
58471
  const [value, setValue] = (0, react$1.useState)(() => readParam(key));
58442
58472
  (0, react$1.useEffect)(() => {
58443
- const onPop = () => setValue(readParam(key));
58444
- window.addEventListener("popstate", onPop);
58445
- return () => window.removeEventListener("popstate", onPop);
58473
+ const sync = () => setValue(readParam(key));
58474
+ window.addEventListener("popstate", sync);
58475
+ window.addEventListener(URL_QUERY_EVENT, sync);
58476
+ return () => {
58477
+ window.removeEventListener("popstate", sync);
58478
+ window.removeEventListener(URL_QUERY_EVENT, sync);
58479
+ };
58446
58480
  }, [key]);
58447
58481
  return [value, (0, react$1.useCallback)((next) => {
58448
58482
  writeParam(key, next);
package/dist/index.js CHANGED
@@ -31999,7 +31999,7 @@ function useTabsContext() {
31999
31999
  if (!ctx) throw new Error("Tabs compound components must be used within <Tabs>");
32000
32000
  return ctx;
32001
32001
  }
32002
- function Tabs({ value: controlledValue, onValueChange, defaultValue = "", className, ...props }) {
32002
+ function Tabs({ value: controlledValue, onValueChange, defaultValue = "", appearance = "underline", className, ...props }) {
32003
32003
  const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
32004
32004
  const value = controlledValue ?? uncontrolledValue;
32005
32005
  const setValue = useCallback((next) => {
@@ -32009,7 +32009,8 @@ function Tabs({ value: controlledValue, onValueChange, defaultValue = "", classN
32009
32009
  return /* @__PURE__ */ jsx(TabsContext.Provider, {
32010
32010
  value: {
32011
32011
  value,
32012
- setValue
32012
+ setValue,
32013
+ appearance
32013
32014
  },
32014
32015
  children: /* @__PURE__ */ jsx("div", {
32015
32016
  className,
@@ -32035,14 +32036,15 @@ function Tabs({ value: controlledValue, onValueChange, defaultValue = "", classN
32035
32036
  * pinned to `hidden` makes it impossible here.
32036
32037
  */
32037
32038
  function TabsList({ className, ...props }) {
32039
+ const { appearance } = useTabsContext();
32038
32040
  return /* @__PURE__ */ jsx("div", {
32039
32041
  role: "tablist",
32040
- className: cn("flex flex-row overflow-x-auto overflow-y-hidden border-b border-border-subtle", className),
32042
+ className: cn("flex flex-row overflow-x-auto overflow-y-hidden", appearance === "pills" ? "gap-1 border-b border-border-subtle py-1" : "border-b border-border-subtle", className),
32041
32043
  ...props
32042
32044
  });
32043
32045
  }
32044
32046
  function TabsTrigger({ value, className, ...props }) {
32045
- const { value: activeValue, setValue } = useTabsContext();
32047
+ const { value: activeValue, setValue, appearance } = useTabsContext();
32046
32048
  const isActive = value === activeValue;
32047
32049
  return /* @__PURE__ */ jsx("button", {
32048
32050
  type: "button",
@@ -32050,7 +32052,7 @@ function TabsTrigger({ value, className, ...props }) {
32050
32052
  "aria-selected": isActive,
32051
32053
  "aria-controls": `tabpanel-${value}`,
32052
32054
  tabIndex: isActive ? 0 : -1,
32053
- className: cn("h-7 shrink-0 whitespace-nowrap text-xs px-3 transition-colors", isActive ? "border-b-2 border-primary text-foreground font-medium" : "text-foreground-muted hover:text-foreground", className),
32055
+ className: cn("h-7 shrink-0 whitespace-nowrap text-xs px-3 transition-colors", appearance === "pills" ? isActive ? "rounded-md bg-primary/10 font-medium text-primary" : "rounded-md text-foreground-muted hover:bg-surface-hover hover:text-foreground" : isActive ? "border-b-2 border-primary text-foreground font-medium" : "text-foreground-muted hover:text-foreground", className),
32054
32056
  onClick: () => setValue(value),
32055
32057
  ...props
32056
32058
  });
@@ -58395,12 +58397,23 @@ function ItemFormDialog({ open, onClose, title, icon: Icon, error, width = "lg",
58395
58397
  * Tiny URL query sync for surfaces that cannot assume react-router
58396
58398
  * (federated addon pages) or that only need a handful of params.
58397
58399
  *
58398
- * Reads on mount + `popstate`; writes with `history.replaceState`. Empty /
58399
- * null values are deleted so the URL stays short.
58400
+ * Reads on mount + whenever the query string changes (our writes, other
58401
+ * hook instances, react-router `setSearchParams`, back/forward). Writes
58402
+ * with `history.replaceState`. Empty / null values are deleted so the URL
58403
+ * stays short.
58404
+ *
58405
+ * Multiple `useUrlQueryParam` mounts (and a parent reading the same key via
58406
+ * this hook while a child writes) must stay in sync — otherwise a Rules
58407
+ * area pill updates `?section=` while the Notifications header still shows
58408
+ * "New rule" from a stale snapshot.
58400
58409
  */
58410
+ var URL_QUERY_EVENT = "camstack:url-query";
58401
58411
  function readParam(key) {
58402
58412
  return new URLSearchParams(window.location.search).get(key);
58403
58413
  }
58414
+ function notifyUrlQuery() {
58415
+ window.dispatchEvent(new Event(URL_QUERY_EVENT));
58416
+ }
58404
58417
  function writeParam(key, value) {
58405
58418
  const params = new URLSearchParams(window.location.search);
58406
58419
  if (value === null || value === "") params.delete(key);
@@ -58409,16 +58422,37 @@ function writeParam(key, value) {
58409
58422
  const url = qs ? `${window.location.pathname}?${qs}` : window.location.pathname;
58410
58423
  window.history.replaceState(null, "", url);
58411
58424
  }
58425
+ var historyPatched = false;
58426
+ /** So react-router (and anyone else) replaceState/pushState refresh every hook. */
58427
+ function ensureHistoryPatched() {
58428
+ if (historyPatched || typeof window === "undefined") return;
58429
+ historyPatched = true;
58430
+ const originalReplace = window.history.replaceState.bind(window.history);
58431
+ const originalPush = window.history.pushState.bind(window.history);
58432
+ window.history.replaceState = (...args) => {
58433
+ originalReplace(...args);
58434
+ notifyUrlQuery();
58435
+ };
58436
+ window.history.pushState = (...args) => {
58437
+ originalPush(...args);
58438
+ notifyUrlQuery();
58439
+ };
58440
+ }
58412
58441
  /**
58413
58442
  * One query param as React state. `null` means absent.
58414
58443
  * Changing it replaces the URL without a navigation.
58415
58444
  */
58416
58445
  function useUrlQueryParam(key) {
58446
+ ensureHistoryPatched();
58417
58447
  const [value, setValue] = useState(() => readParam(key));
58418
58448
  useEffect(() => {
58419
- const onPop = () => setValue(readParam(key));
58420
- window.addEventListener("popstate", onPop);
58421
- return () => window.removeEventListener("popstate", onPop);
58449
+ const sync = () => setValue(readParam(key));
58450
+ window.addEventListener("popstate", sync);
58451
+ window.addEventListener(URL_QUERY_EVENT, sync);
58452
+ return () => {
58453
+ window.removeEventListener("popstate", sync);
58454
+ window.removeEventListener(URL_QUERY_EVENT, sync);
58455
+ };
58422
58456
  }, [key]);
58423
58457
  return [value, useCallback((next) => {
58424
58458
  writeParam(key, next);
@@ -1,10 +1,17 @@
1
1
  import { type JSX, type ButtonHTMLAttributes, type HTMLAttributes } from "react";
2
+ export type TabsAppearance = "underline" | "pills";
2
3
  interface TabsProps extends HTMLAttributes<HTMLDivElement> {
3
4
  value?: string;
4
5
  onValueChange?: (value: string) => void;
5
6
  defaultValue?: string;
7
+ /**
8
+ * `underline` — border-b active (default, dialog editors).
9
+ * `pills` — chip active fill; scrollbar sits under chips so it cannot
10
+ * cover a bottom underline (Rules area strip).
11
+ */
12
+ appearance?: TabsAppearance;
6
13
  }
7
- export declare function Tabs({ value: controlledValue, onValueChange, defaultValue, className, ...props }: TabsProps): JSX.Element | null;
14
+ export declare function Tabs({ value: controlledValue, onValueChange, defaultValue, appearance, className, ...props }: TabsProps): JSX.Element | null;
8
15
  /**
9
16
  * The strip SCROLLS sideways; it never squashes and never clips.
10
17
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/ui-library",
3
- "version": "1.2.271",
3
+ "version": "1.2.273",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",