@paroicms/react-ui 0.5.3 → 0.5.5

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.
Files changed (58) hide show
  1. package/dist/Breadcrumb.d.ts +1 -0
  2. package/dist/Breadcrumb.js +4 -1
  3. package/dist/DataTable.d.ts +12 -3
  4. package/dist/DataTable.js +2 -10
  5. package/dist/InputSwitch.d.ts +10 -0
  6. package/dist/InputSwitch.js +10 -0
  7. package/dist/MenuItem.js +2 -2
  8. package/dist/PopupMenu.d.ts +4 -2
  9. package/dist/PopupMenu.js +11 -4
  10. package/dist/SideMenu.js +13 -2
  11. package/dist/SortableList.js +1 -2
  12. package/dist/Spinner.d.ts +5 -1
  13. package/dist/Spinner.js +3 -2
  14. package/dist/SplitButton.js +1 -3
  15. package/dist/Tabs.js +1 -1
  16. package/dist/ToggleButton.d.ts +2 -1
  17. package/dist/ToggleButton.js +2 -2
  18. package/dist/ToggleGroup.js +1 -1
  19. package/dist/index.d.ts +1 -2
  20. package/dist/index.js +1 -2
  21. package/dist/paroi-ui-lib-types.d.ts +5 -1
  22. package/dist/popup-positioning.js +3 -2
  23. package/package.json +1 -1
  24. package/styles/Accordion.css +7 -0
  25. package/styles/Alert.css +7 -1
  26. package/styles/Breadcrumb.css +20 -11
  27. package/styles/Button.css +46 -10
  28. package/styles/Checkbox.css +3 -1
  29. package/styles/Chip.css +7 -1
  30. package/styles/DataTable.css +27 -18
  31. package/styles/DateInput.css +3 -0
  32. package/styles/Dialog.css +8 -2
  33. package/styles/Inplace.css +15 -2
  34. package/styles/InputNumber.css +4 -3
  35. package/styles/{Switch.css → InputSwitch.css} +10 -10
  36. package/styles/InputText.css +19 -13
  37. package/styles/MenuItem.css +47 -45
  38. package/styles/MultiSelect.css +11 -5
  39. package/styles/Panel.css +8 -1
  40. package/styles/PasswordInput.css +18 -5
  41. package/styles/PopupMenu.css +19 -0
  42. package/styles/RadioButton.css +3 -1
  43. package/styles/Select.css +18 -5
  44. package/styles/Spinner.css +8 -4
  45. package/styles/SplitButton.css +128 -36
  46. package/styles/Tabs.css +11 -3
  47. package/styles/Textarea.css +4 -3
  48. package/styles/ToggleButton.css +126 -12
  49. package/styles/ToggleGroup.css +6 -4
  50. package/styles/Tree.css +7 -1
  51. package/styles/theme/common.css +4 -4
  52. package/styles/theme/index.css +1 -1
  53. package/styles/theme/tokens.css +14 -1
  54. package/styles/theme/{margins.css → utilities.css} +63 -36
  55. package/dist/Column.d.ts +0 -14
  56. package/dist/Column.js +0 -7
  57. package/dist/Switch.d.ts +0 -9
  58. package/dist/Switch.js +0 -12
@@ -5,6 +5,7 @@ export interface BreadcrumbItem {
5
5
  icon?: ReactNode;
6
6
  url?: string;
7
7
  onClick?: () => void;
8
+ className?: string;
8
9
  }
9
10
  export interface BreadcrumbProps {
10
11
  items: BreadcrumbItem[];
@@ -5,6 +5,9 @@ import "../styles/Breadcrumb.css";
5
5
  export function Breadcrumb({ items, separator = _jsx(ChevronRight, { size: 12 }), className, }) {
6
6
  return (_jsx("nav", { className: clsx("PaBreadcrumb", className), "aria-label": "Breadcrumb", children: items.map((item, index) => {
7
7
  const isLast = index === items.length - 1;
8
- return (_jsxs("div", { className: "PaBreadcrumb-item", children: [isLast ? (_jsxs("span", { className: "PaBreadcrumb-current", children: [item.icon, item.label] })) : item.url ? (_jsxs("a", { href: item.url, className: "PaBreadcrumb-link", children: [item.icon, item.label] })) : item.onClick ? (_jsxs("button", { type: "button", className: "PaBreadcrumb-link", onClick: item.onClick, children: [item.icon, item.label] })) : (_jsxs("span", { className: "PaBreadcrumb-link", children: [item.icon, item.label] })), !isLast && _jsx("span", { className: "PaBreadcrumb-separator", children: separator })] }, `${item.label}:${index}`));
8
+ const linkClassName = clsx("PaBreadcrumb-link", {
9
+ iconBtn: item.icon && !item.label,
10
+ });
11
+ return (_jsxs("div", { className: clsx("PaBreadcrumb-item", item.className), children: [item.url ? (_jsxs("a", { href: item.url, className: linkClassName, children: [item.icon, item.label] })) : item.onClick ? (_jsxs("button", { type: "button", className: linkClassName, onClick: item.onClick, children: [item.icon, item.label] })) : (_jsxs("span", { className: linkClassName, children: [item.icon, item.label] })), !isLast && _jsx("span", { className: "PaBreadcrumb-separator", children: separator })] }, `${item.label}:${index}`));
9
12
  }) }));
10
13
  }
@@ -1,12 +1,21 @@
1
1
  import "../styles/DataTable.css";
2
- import { type ReactNode } from "react";
2
+ import type { CSSProperties, ReactNode } from "react";
3
+ export interface DataTableColumn<T = unknown> {
4
+ field?: keyof T & string;
5
+ header?: string;
6
+ body?: (rowData: T) => ReactNode;
7
+ style?: CSSProperties;
8
+ sortable?: boolean;
9
+ bodyClassName?: string;
10
+ }
3
11
  export interface DataTableProps<T> {
4
12
  value: T[];
5
13
  dataKey: keyof T & string;
14
+ columns: DataTableColumn<T>[];
6
15
  size?: "small" | "normal";
7
16
  loading?: boolean;
8
- children: ReactNode;
9
17
  className?: string;
18
+ tableClassName?: string;
10
19
  emptyMessage?: string;
11
20
  paginator?: boolean;
12
21
  rows?: number;
@@ -27,4 +36,4 @@ export interface DataTableProps<T> {
27
36
  rowClassName?: (row: T) => string | undefined;
28
37
  totalLabel?: string;
29
38
  }
30
- export declare function DataTable<T extends object>({ value, dataKey, size, loading, children, className, emptyMessage, paginator, rows, totalRecords, first, onPage, rowsPerPageOptions, sortField, sortOrder, onSort, onRowClick, rowClassName, totalLabel, }: DataTableProps<T>): import("react/jsx-runtime").JSX.Element;
39
+ export declare function DataTable<T extends object>({ value, dataKey, columns, size, loading, className, tableClassName, emptyMessage, paginator, rows, totalRecords, first, onPage, rowsPerPageOptions, sortField, sortOrder, onSort, onRowClick, rowClassName, totalLabel, }: DataTableProps<T>): import("react/jsx-runtime").JSX.Element;
package/dist/DataTable.js CHANGED
@@ -2,17 +2,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import "../styles/DataTable.css";
3
3
  import { clsx } from "clsx";
4
4
  import { ArrowDown, ArrowUp, ChevronLeft, ChevronRight, ChevronsUpDown } from "lucide-react";
5
- import { Children, isValidElement } from "react";
6
5
  import { Select } from "./Select.js";
7
6
  import { Spinner } from "./Spinner.js";
8
- export function DataTable({ value, dataKey, size, loading, children, className, emptyMessage = "No data available", paginator, rows = 10, totalRecords, first = 0, onPage, rowsPerPageOptions, sortField, sortOrder, onSort, onRowClick, rowClassName, totalLabel, }) {
9
- // Extract column definitions from children
10
- const columns = [];
11
- Children.forEach(children, (child) => {
12
- if (isValidElement(child) && child.props) {
13
- columns.push(child.props);
14
- }
15
- });
7
+ export function DataTable({ value, dataKey, columns, size, loading, className, tableClassName, emptyMessage = "No data available", paginator, rows = 10, totalRecords, first = 0, onPage, rowsPerPageOptions, sortField, sortOrder, onSort, onRowClick, rowClassName, totalLabel, }) {
16
8
  const total = totalRecords ?? value.length;
17
9
  const pageCount = Math.ceil(total / rows);
18
10
  const currentPage = Math.floor(first / rows) + 1;
@@ -22,5 +14,5 @@ export function DataTable({ value, dataKey, size, loading, children, className,
22
14
  const newOrder = sortField === field && sortOrder === 1 ? -1 : 1;
23
15
  onSort({ field, order: newOrder });
24
16
  };
25
- return (_jsxs("div", { className: clsx("PaDataTable-wrapper", loading && "loading"), children: [loading && (_jsx("div", { className: "PaDataTable-overlay", children: _jsx(Spinner, {}) })), _jsx("div", { className: "PaDataTable-scrollable", children: _jsxs("table", { className: clsx("PaDataTable", size, className), children: [_jsx("thead", { className: "PaDataTable-header", children: _jsx("tr", { children: columns.map((col, i) => (_jsx("th", { className: clsx("PaDataTable-headerCell", col.sortable && "sortable"), style: col.style, onClick: col.sortable ? () => handleSort(col.field) : undefined, tabIndex: 0, children: _jsxs("span", { className: "PaDataTable-headerContent", children: [col.header, col.sortable && (_jsx("span", { className: "PaDataTable-sortIcon", children: col.field === sortField ? (sortOrder === 1 ? (_jsx(ArrowDown, { size: 14 })) : (_jsx(ArrowUp, { size: 14 }))) : (_jsx(ChevronsUpDown, { size: 14 })) }))] }) }, col.field ?? i))) }) }), _jsx("tbody", { children: value.length === 0 ? (_jsx("tr", { className: "PaDataTable-emptyRow", children: _jsx("td", { colSpan: columns.length, className: "PaDataTable-emptyCell", children: emptyMessage }) })) : (value.map((row) => (_jsx("tr", { className: clsx("PaDataTable-row", onRowClick && "clickable", rowClassName?.(row)), onClick: onRowClick ? (e) => onRowClick(row, e) : undefined, children: columns.map((col, i) => (_jsx("td", { className: clsx("PaDataTable-cell", col.bodyClassName), style: col.style, children: col.body ? col.body(row) : col.field ? String(row[col.field] ?? "") : null }, col.field ?? i))) }, String(row[dataKey]))))) })] }) }), paginator && onPage && pageCount > 0 && (_jsxs("div", { className: "PaDataTable-footer", children: [_jsxs("span", { className: "PaDataTable-totalCount", children: [total, " ", totalLabel ?? "items"] }), _jsxs("div", { className: "PaDataTable-pagination", children: [_jsx("button", { type: "button", className: "PaDataTable-paginationBtn", disabled: currentPage <= 1, onClick: () => onPage({ first: first - rows, rows }), children: _jsx(ChevronLeft, { size: 16 }) }), _jsxs("span", { className: "PaDataTable-paginationText", children: ["Page ", currentPage, " of ", pageCount] }), _jsx("button", { type: "button", className: "PaDataTable-paginationBtn", disabled: currentPage >= pageCount, onClick: () => onPage({ first: first + rows, rows }), children: _jsx(ChevronRight, { size: 16 }) }), rowsPerPageOptions && rowsPerPageOptions.length > 0 && (_jsx(Select, { className: "PaDataTable-rowsPerPage", value: String(rows), options: rowsPerPageOptions.map((n) => ({ label: String(n), value: String(n) })), onChange: (val) => onPage({ first: 0, rows: Number(val) }) }))] })] }))] }));
17
+ return (_jsxs("div", { className: clsx("PaDataTable", { loading }, className), children: [loading && (_jsx("div", { className: "PaDataTable-overlay", children: _jsx(Spinner, {}) })), _jsx("div", { className: "PaDataTable-scrollable", children: _jsxs("table", { className: clsx("PaDataTable-table", size, tableClassName), children: [_jsx("thead", { className: "PaDataTable-header", children: _jsx("tr", { children: columns.map((col, i) => (_jsx("th", { className: clsx("PaDataTable-headerCell", col.sortable && "sortable"), style: col.style, onClick: col.sortable ? () => handleSort(col.field) : undefined, tabIndex: 0, children: _jsxs("span", { className: "PaDataTable-headerContent", children: [col.header, col.sortable && (_jsx("span", { className: "PaDataTable-sortIcon", children: col.field === sortField ? (sortOrder === 1 ? (_jsx(ArrowDown, { size: 14 })) : (_jsx(ArrowUp, { size: 14 }))) : (_jsx(ChevronsUpDown, { size: 14 })) }))] }) }, col.field ?? i))) }) }), _jsx("tbody", { children: value.length === 0 ? (_jsx("tr", { className: "PaDataTable-emptyRow", children: _jsx("td", { colSpan: columns.length, className: "PaDataTable-emptyCell", children: emptyMessage }) })) : (value.map((row) => (_jsx("tr", { className: clsx("PaDataTable-row", onRowClick && "clickable", rowClassName?.(row)), onClick: onRowClick ? (e) => onRowClick(row, e) : undefined, children: columns.map((col, i) => (_jsx("td", { className: clsx("PaDataTable-cell", col.bodyClassName), style: col.style, children: col.body ? col.body(row) : col.field ? String(row[col.field] ?? "") : null }, col.field ?? i))) }, String(row[dataKey]))))) })] }) }), paginator && onPage && pageCount > 1 && (_jsxs("div", { className: "PaDataTable-footer", children: [_jsxs("span", { className: "PaDataTable-totalCount", children: [total, " ", totalLabel ?? "items"] }), _jsxs("div", { className: "PaDataTable-pagination", children: [_jsx("button", { type: "button", className: "PaDataTable-paginationBtn", disabled: currentPage <= 1, onClick: () => onPage({ first: first - rows, rows }), children: _jsx(ChevronLeft, { size: 16 }) }), _jsxs("span", { className: "PaDataTable-paginationText", children: ["Page ", currentPage, " of ", pageCount] }), _jsx("button", { type: "button", className: "PaDataTable-paginationBtn", disabled: currentPage >= pageCount, onClick: () => onPage({ first: first + rows, rows }), children: _jsx(ChevronRight, { size: 16 }) }), rowsPerPageOptions && rowsPerPageOptions.length > 0 && (_jsx(Select, { className: "PaDataTable-rowsPerPage", value: String(rows), options: rowsPerPageOptions.map((n) => ({ label: String(n), value: String(n) })), onChange: (val) => onPage({ first: 0, rows: Number(val) }) }))] })] }))] }));
26
18
  }
@@ -0,0 +1,10 @@
1
+ import "../styles/InputSwitch.css";
2
+ import type { ChangeEvent, InputHTMLAttributes } from "react";
3
+ export interface InputSwitchProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "onChange"> {
4
+ checked: boolean;
5
+ onChange?: (checked: boolean, ev: ChangeEvent<HTMLInputElement>) => void;
6
+ label?: string;
7
+ inputLabel?: string;
8
+ className?: string;
9
+ }
10
+ export declare function InputSwitch({ checked, onChange, label, inputLabel, className, ...rest }: InputSwitchProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import "../styles/InputSwitch.css";
3
+ import { clsx } from "clsx";
4
+ export function InputSwitch({ checked, onChange, label, inputLabel, className, ...rest }) {
5
+ const handleChange = (e) => {
6
+ onChange?.(e.target.checked, e);
7
+ };
8
+ const fieldContent = inputLabel ? (_jsxs("label", { className: clsx("PaInputSwitch", className), children: [_jsx("input", { className: "PaInputSwitch-input", type: "checkbox", checked: checked, onChange: handleChange, ...rest }), _jsx("span", { className: "PaInputSwitch-track", children: _jsx("span", { className: "PaInputSwitch-thumb" }) }), _jsx("span", { className: "PaInputSwitch-label", children: inputLabel })] })) : (_jsxs("span", { className: clsx("PaInputSwitch", className), children: [_jsx("input", { className: "PaInputSwitch-input", type: "checkbox", checked: checked, onChange: handleChange, ...rest }), _jsx("span", { className: "PaInputSwitch-track", children: _jsx("span", { className: "PaInputSwitch-thumb" }) })] }));
9
+ return (_jsx(_Fragment, { children: label ? (_jsxs("label", { className: clsx("PaField", className), children: [_jsx("span", { className: "PaField-label", children: label }), fieldContent] })) : (fieldContent) }));
10
+ }
package/dist/MenuItem.js CHANGED
@@ -9,10 +9,10 @@ export function MenuItem({ item, expanded, onToggle }) {
9
9
  url = undefined;
10
10
  command = undefined;
11
11
  }
12
- return (_jsxs("div", { className: clsx("PaMenuItem", active && "active", disabled && "disabled", className), style: style, children: [expanded !== undefined && onToggle ? (_jsx("button", { className: "PaMenuItem-left toggle", onClick: () => onToggle(!expanded), type: "button", children: expanded ? _jsx(ChevronDown, { size: 14 }) : _jsx(ChevronRight, { size: 14 }) })) : icon ? (_jsx("div", { className: "PaMenuItem-left icon", children: icon })) : undefined, url ? (_jsx("a", { className: "PaMenuItem-label", href: url, onClick: url && command
12
+ return (_jsxs("div", { className: clsx("PaMenuItem", { active }, className), style: style, children: [expanded !== undefined && onToggle ? (_jsx("button", { className: "PaMenuItem-left toggle", onClick: () => onToggle(!expanded), type: "button", children: expanded ? _jsx(ChevronDown, { size: 14 }) : _jsx(ChevronRight, { size: 14 }) })) : icon ? (_jsx("div", { className: "PaMenuItem-left icon", children: icon })) : undefined, url ? (_jsx("a", { className: clsx("PaMenuItem-label", { disabled }), href: url, onClick: url && command
13
13
  ? (ev) => {
14
14
  ev.preventDefault();
15
15
  command?.();
16
16
  }
17
- : undefined, children: label })) : command ? (_jsx("button", { className: "PaMenuItem-label", onClick: command, type: "button", children: label })) : (_jsx("span", { className: "PaMenuItem-label", children: label })), popupMenu && _jsx(PopupMenu, { className: "PaMenuItem-popup", items: popupMenu })] }, id));
17
+ : undefined, children: label })) : command ? (_jsx("button", { className: "PaMenuItem-label", onClick: command, type: "button", disabled: disabled, children: label })) : (_jsx("span", { className: clsx("PaMenuItem-label", { disabled }), children: label })), popupMenu && _jsx(PopupMenu, { className: "PaMenuItem-popup", items: popupMenu })] }, id));
18
18
  }
@@ -1,5 +1,5 @@
1
1
  import "../styles/PopupMenu.css";
2
- import { type MouseEvent, type ReactNode, type Ref } from "react";
2
+ import { type MouseEvent, type ReactElement, type ReactNode, type Ref } from "react";
3
3
  import type { MenuNode } from "./paroi-ui-lib-types.js";
4
4
  export interface PopupMenuProps {
5
5
  items: MenuNode[];
@@ -9,6 +9,8 @@ export interface PopupMenuProps {
9
9
  position?: "auto" | "topLeft" | "topRight" | "bottomLeft" | "bottomRight";
10
10
  /** Optional custom trigger element. If provided, it will be used instead of the default button. */
11
11
  children?: ReactNode;
12
+ /** Optional footer render prop. Receives a hide function to close the menu. */
13
+ footer?: (hide: () => void) => ReactElement;
12
14
  ref?: Ref<PopupMenuRef>;
13
15
  }
14
16
  export interface PopupMenuRef {
@@ -16,4 +18,4 @@ export interface PopupMenuRef {
16
18
  show: (event: MouseEvent) => void;
17
19
  hide: () => void;
18
20
  }
19
- export declare function PopupMenu({ items, className, id, position, children, ref, }: PopupMenuProps): import("react/jsx-runtime").JSX.Element;
21
+ export declare function PopupMenu({ items, className, id, position, children, footer, ref, }: PopupMenuProps): import("react/jsx-runtime").JSX.Element;
package/dist/PopupMenu.js CHANGED
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import "../styles/PopupMenu.css";
3
3
  import clsx from "clsx";
4
4
  import { ChevronRight, ChevronsDown } from "lucide-react";
@@ -6,12 +6,15 @@ import { cloneElement, isValidElement, useEffect, useImperativeHandle, useRef, }
6
6
  import { Button } from "./Button.js";
7
7
  import { MenuItem } from "./MenuItem.js";
8
8
  import { computePopupPosition, setupPopoverPositioning } from "./popup-positioning.js";
9
+ function isSeparator(item) {
10
+ return "separator" in item && item.separator === true;
11
+ }
9
12
  const isPopoverSupported = typeof HTMLElement !== "undefined" && "showPopover" in HTMLElement.prototype;
10
13
  if (!isPopoverSupported) {
11
14
  console?.error("PopupMenu: Popover API is not supported in this browser.");
12
15
  }
13
16
  let seq = 0;
14
- export function PopupMenu({ items, className, id, position = "auto", children, ref, }) {
17
+ export function PopupMenu({ items, className, id, position = "auto", children, footer, ref, }) {
15
18
  const menuRef = useRef(null);
16
19
  const triggerRef = useRef(null);
17
20
  const menuIdRef = useRef(undefined);
@@ -62,7 +65,9 @@ export function PopupMenu({ items, className, id, position = "auto", children, r
62
65
  }
63
66
  return (_jsx(Button, { ref: triggerRef, className: clsx(className, "borderless"), outlined: true, severity: "secondary", "aria-label": "unfold", "aria-controls": menuIdRef.current, "aria-haspopup": true, ...triggerProps, children: _jsx(ChevronsDown, { size: 16 }) }));
64
67
  };
65
- return (_jsxs(_Fragment, { children: [renderTrigger(), _jsx("div", { className: "PaPopupMenu", ref: menuRef, id: menuIdRef.current, popover: "auto", children: _jsx("div", { className: "PaPopupMenu-content", children: items.map((item) => (_jsx(PopupMenuItemWrapper, { item: item, onHide: hide }, item.key))) }) })] }));
68
+ return (_jsxs(_Fragment, { children: [renderTrigger(), _jsxs("div", { className: "PaPopupMenu", ref: menuRef, id: menuIdRef.current, popover: "auto", children: [_jsx("div", { className: "PaPopupMenu-content", children: items.map((item, index) => isSeparator(item) ? (
69
+ // biome-ignore lint/suspicious/noArrayIndexKey: separator items may not have unique keys
70
+ _jsx("div", { className: "PaPopupMenu-separator" }, `separator-${index}`)) : (_jsx(PopupMenuItemWrapper, { item: item, onHide: hide }, item.key))) }), footer && _jsx("div", { className: "PaPopupMenu-footer", children: footer(hide) })] })] }));
66
71
  }
67
72
  function PopupMenuItemWrapper({ item, onHide }) {
68
73
  const subMenuRef = useRef(null);
@@ -102,5 +107,7 @@ function PopupMenuItemWrapper({ item, onHide }) {
102
107
  return;
103
108
  return setupPopoverPositioning(subMenuEl, () => computePopupPosition(parentEl, subMenuEl, "right", "cardinal"));
104
109
  }, [item.subMenu]);
105
- return (_jsxs("div", { className: "PaPopupMenu-item", popoverTargetAction: "toggle", popoverTarget: subMenuId.current, children: [_jsx(MenuItem, { item: itemWithSubmenuHandling }), item.subMenu && (_jsx("div", { className: "PaPopupMenu-submenu", popover: "auto", id: subMenuId.current, ref: subMenuRef, children: item.subMenu.map((subItem) => (_jsx(PopupMenuItemWrapper, { item: subItem, onHide: onHide }, subItem.key))) }))] }));
110
+ return (_jsxs("div", { className: "PaPopupMenu-item", popoverTargetAction: "toggle", popoverTarget: subMenuId.current, children: [_jsx(MenuItem, { item: itemWithSubmenuHandling }), item.subMenu && (_jsx("div", { className: "PaPopupMenu-submenu", popover: "auto", id: subMenuId.current, ref: subMenuRef, children: item.subMenu.map((subItem, index) => isSeparator(subItem) ? (
111
+ // biome-ignore lint/suspicious/noArrayIndexKey: separator items may not have unique keys
112
+ _jsx("div", { className: "PaPopupMenu-separator" }, `separator-${index}`)) : (_jsx(PopupMenuItemWrapper, { item: subItem, onHide: onHide }, subItem.key))) }))] }));
106
113
  }
package/dist/SideMenu.js CHANGED
@@ -2,12 +2,23 @@ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import "../styles/SideMenu.css";
3
3
  import { Accordion } from "./Accordion.js";
4
4
  import { MenuItem } from "./MenuItem.js";
5
+ function isSeparator(item) {
6
+ return "separator" in item && item.separator === true;
7
+ }
5
8
  export function SideMenu({ menu }) {
6
- return (_jsx(_Fragment, { children: menu.map((child) => (_jsx(AccordionOrMenuItem, { item: child }, child.key))) }));
9
+ return (_jsx(_Fragment, { children: menu.map((child, index) => {
10
+ return isSeparator(child) ? (
11
+ // biome-ignore lint/suspicious/noArrayIndexKey: separator items may not have unique keys
12
+ _jsx("div", { className: "PaSideMenu-separator" }, `separator-${index}`)) : (_jsx(AccordionOrMenuItem, { item: child }, child.key));
13
+ }) }));
7
14
  }
8
15
  function AccordionOrMenuItem({ item }) {
9
16
  if (item.subMenu) {
10
- return (_jsx(Accordion, { header: item, expanded: item.expanded, children: item.subMenu.map((child) => (_jsx(AccordionOrMenuItem, { item: child }, child.key))) }));
17
+ return (_jsx(Accordion, { header: item, expanded: item.expanded, children: item.subMenu.map((child, index) => {
18
+ return isSeparator(child) ? (
19
+ // biome-ignore lint/suspicious/noArrayIndexKey: separator items may not have unique keys
20
+ _jsx("div", { className: "PaSideMenu-separator" }, `separator-${index}`)) : (_jsx(AccordionOrMenuItem, { item: child }, child.key));
21
+ }) }));
11
22
  }
12
23
  return _jsx(MenuItem, { item: item });
13
24
  }
@@ -17,9 +17,8 @@ export function SortableList({ items, onReorder, keyField, renderItem, direction
17
17
  }, [items, keyField]);
18
18
  const handleSetList = (newList) => {
19
19
  // Ignore empty list updates from ReactSortable during initialization
20
- if (newList.length === 0 && sortableItems.length > 0) {
20
+ if (newList.length === 0 && sortableItems.length > 0)
21
21
  return;
22
- }
23
22
  setSortableItems(newList);
24
23
  onReorder(newList.map((item) => item.data));
25
24
  };
package/dist/Spinner.d.ts CHANGED
@@ -1,2 +1,6 @@
1
1
  import "../styles/Spinner.css";
2
- export declare function Spinner(): import("react/jsx-runtime").JSX.Element;
2
+ export interface SpinnerProps {
3
+ /** @default `"medium"` */
4
+ size?: "small" | "medium" | "large";
5
+ }
6
+ export declare function Spinner({ size }: SpinnerProps): import("react/jsx-runtime").JSX.Element;
package/dist/Spinner.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import "../styles/Spinner.css";
3
- export function Spinner() {
4
- return _jsx("div", { className: "PaSpinner" });
3
+ import clsx from "clsx";
4
+ export function Spinner({ size = "medium" }) {
5
+ return _jsx("div", { className: clsx("PaSpinner", size) });
5
6
  }
@@ -10,8 +10,6 @@ export function SplitButton(props) {
10
10
  const containerRef = useRef(null);
11
11
  const menuRef = useRef(null);
12
12
  const menuId = useRef(`pa-splitbtn-menu-${Math.random().toString(36).substring(2, 9)}`);
13
- const severity = props.severity ?? "primary";
14
- const disabled = props.disabled;
15
13
  const hasEnabledItems = items.some((item) => !item.disabled);
16
14
  useEffect(() => {
17
15
  const menu = menuRef.current;
@@ -36,5 +34,5 @@ export function SplitButton(props) {
36
34
  menuRef.current?.hidePopover();
37
35
  }
38
36
  };
39
- return (_jsxs("div", { ref: containerRef, className: clsx("PaSplitBtn", severity, disabled && "disabled", className), children: [_jsx(Button, { ...buttonProps, className: "PaSplitBtn-main" }), _jsx("button", { type: "button", className: "PaSplitBtn-toggle", popoverTarget: menuId.current, popoverTargetAction: "toggle", "aria-haspopup": "menu", disabled: !hasEnabledItems, children: _jsx(ChevronDown, { className: "PaSplitBtn-toggleIcon" }) }), _jsx("div", { ref: menuRef, id: menuId.current, className: "PaSplitBtn-menu", role: "menu", popover: "auto", children: items.map((item) => (_jsxs("button", { type: "button", className: clsx("PaSplitBtn-menuItem", item.danger && "danger", item.disabled && "disabled"), onClick: () => void handleItemClick(item), role: "menuitem", disabled: item.disabled, children: [item.icon && _jsx("span", { className: "PaSplitBtn-menuIcon", children: item.icon }), item.label] }, item.label))) })] }));
37
+ return (_jsxs("div", { ref: containerRef, className: clsx("PaSplitBtn", className, { outlined: props.outlined }), children: [_jsx(Button, { ...buttonProps, className: "PaSplitBtn-main" }), _jsx("button", { type: "button", className: clsx("PaSplitBtn-toggle", props.severity ?? "primary"), popoverTarget: menuId.current, popoverTargetAction: "toggle", "aria-haspopup": "menu", disabled: !hasEnabledItems, children: _jsx(ChevronDown, { className: "PaSplitBtn-toggleIcon" }) }), _jsx("div", { ref: menuRef, id: menuId.current, className: "PaSplitBtn-menu", role: "menu", popover: "auto", children: items.map((item) => (_jsxs("button", { type: "button", className: clsx("PaSplitBtn-menuItem", item.danger && "danger"), onClick: () => void handleItemClick(item), role: "menuitem", disabled: item.disabled, children: [item.icon && _jsx("span", { className: "PaSplitBtn-menuIcon", children: item.icon }), item.label] }, item.label))) })] }));
40
38
  }
package/dist/Tabs.js CHANGED
@@ -10,7 +10,7 @@ export function Tabs({ children, value, onChange, variant = "default", className
10
10
  tabs.push(child.props);
11
11
  }
12
12
  });
13
- return (_jsx("div", { className: clsx("PaTabs", variant, className), role: "tablist", children: tabs.map((tab) => (_jsxs("button", { type: "button", className: clsx("PaTab", value === tab.value && "active", tab.disabled && "disabled", tab.className), onClick: () => !tab.disabled && onChange(tab.value), disabled: tab.disabled, role: "tab", "aria-selected": value === tab.value, children: [tab.icon && _jsx("span", { className: "PaTab-icon", children: tab.icon }), tab.label] }, tab.value))) }));
13
+ return (_jsx("div", { className: clsx("PaTabs", variant, className), role: "tablist", children: tabs.map((tab) => (_jsxs("button", { type: "button", className: clsx("PaTab", value === tab.value && "active", tab.className), onClick: () => !tab.disabled && onChange(tab.value), disabled: tab.disabled, role: "tab", "aria-selected": value === tab.value, children: [tab.icon && _jsx("span", { className: "PaTab-icon", children: tab.icon }), tab.label] }, tab.value))) }));
14
14
  }
15
15
  /**
16
16
  * Render-less component for tab definition.
@@ -6,6 +6,7 @@ export interface ToggleButtonProps {
6
6
  children: ReactNode;
7
7
  className?: string;
8
8
  disabled?: boolean;
9
+ severity?: "primary" | "secondary" | "success" | "info" | "warning" | "danger" | "ghost";
9
10
  "aria-label"?: string;
10
11
  }
11
- export declare function ToggleButton({ checked, onChange, children, className, disabled, ...rest }: ToggleButtonProps): import("react/jsx-runtime").JSX.Element;
12
+ export declare function ToggleButton({ checked, onChange, children, className, disabled, severity, ...rest }: ToggleButtonProps): import("react/jsx-runtime").JSX.Element;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import "../styles/ToggleButton.css";
3
3
  import { clsx } from "clsx";
4
- export function ToggleButton({ checked, onChange, children, className, disabled, ...rest }) {
5
- return (_jsx("button", { type: "button", className: clsx("PaToggleBtn", checked && "active", disabled && "disabled", className), onClick: () => !disabled && onChange(!checked), disabled: disabled, "aria-pressed": checked, ...rest, children: children }));
4
+ export function ToggleButton({ checked, onChange, children, className, disabled, severity = "primary", ...rest }) {
5
+ return (_jsx("button", { type: "button", className: clsx("PaToggleBtn", severity, checked && "active", className), onClick: () => !disabled && onChange(!checked), disabled: disabled, "aria-pressed": checked, ...rest, children: children }));
6
6
  }
@@ -2,5 +2,5 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import "../styles/ToggleGroup.css";
3
3
  import { clsx } from "clsx";
4
4
  export function ToggleGroup({ value, onChange, options, className, disabled }) {
5
- return (_jsx("fieldset", { className: clsx("PaToggleGroup", disabled && "disabled", className), disabled: disabled, children: options.map((option) => (_jsx("button", { type: "button", className: clsx("PaToggleGroup-item", value === option.value && "active", option.disabled && "disabled"), onClick: () => !option.disabled && onChange(option.value), disabled: option.disabled, "aria-pressed": value === option.value, children: option.label }, option.value))) }));
5
+ return (_jsx("fieldset", { className: clsx("PaToggleGroup", className), disabled: disabled, children: options.map((option) => (_jsx("button", { type: "button", className: clsx("PaToggleGroup-item", value === option.value && "active"), onClick: () => !option.disabled && onChange(option.value), disabled: option.disabled, "aria-pressed": value === option.value, children: option.label }, option.value))) }));
6
6
  }
package/dist/index.d.ts CHANGED
@@ -2,12 +2,12 @@ import "../styles/theme/index.css";
2
2
  export * from "./Checkbox.js";
3
3
  export * from "./DateInput.js";
4
4
  export * from "./InputNumber.js";
5
+ export * from "./InputSwitch.js";
5
6
  export * from "./InputText.js";
6
7
  export * from "./MultiSelect.js";
7
8
  export * from "./PasswordInput.js";
8
9
  export * from "./RadioButton.js";
9
10
  export * from "./Select.js";
10
- export * from "./Switch.js";
11
11
  export * from "./Textarea.js";
12
12
  export * from "./ToggleGroup.js";
13
13
  export * from "./Button.js";
@@ -17,7 +17,6 @@ export * from "./Alert.js";
17
17
  export * from "./Badge.js";
18
18
  export * from "./Card.js";
19
19
  export * from "./Chip.js";
20
- export * from "./Column.js";
21
20
  export * from "./DataTable.js";
22
21
  export * from "./Breadcrumb.js";
23
22
  export * from "./MenuItem.js";
package/dist/index.js CHANGED
@@ -3,12 +3,12 @@ import "../styles/theme/index.css";
3
3
  export * from "./Checkbox.js";
4
4
  export * from "./DateInput.js";
5
5
  export * from "./InputNumber.js";
6
+ export * from "./InputSwitch.js";
6
7
  export * from "./InputText.js";
7
8
  export * from "./MultiSelect.js";
8
9
  export * from "./PasswordInput.js";
9
10
  export * from "./RadioButton.js";
10
11
  export * from "./Select.js";
11
- export * from "./Switch.js";
12
12
  export * from "./Textarea.js";
13
13
  export * from "./ToggleGroup.js";
14
14
  // Buttons
@@ -20,7 +20,6 @@ export * from "./Alert.js";
20
20
  export * from "./Badge.js";
21
21
  export * from "./Card.js";
22
22
  export * from "./Chip.js";
23
- export * from "./Column.js";
24
23
  export * from "./DataTable.js";
25
24
  // Navigation
26
25
  export * from "./Breadcrumb.js";
@@ -35,7 +35,8 @@ export interface MenuOption {
35
35
  popupMenu?: MenuNode[];
36
36
  active?: boolean;
37
37
  }
38
- export interface MenuNode extends MenuOption {
38
+ export type MenuNode = OptionMenuNode | SeparatorMenuNode;
39
+ export interface OptionMenuNode extends MenuOption {
39
40
  /**
40
41
  * An array of child menu items.
41
42
  */
@@ -45,3 +46,6 @@ export interface MenuNode extends MenuOption {
45
46
  */
46
47
  expanded?: boolean;
47
48
  }
49
+ export interface SeparatorMenuNode {
50
+ separator: true;
51
+ }
@@ -45,11 +45,12 @@ function getConstrainingBounds(triggerEl) {
45
45
  bottom: rect.bottom,
46
46
  };
47
47
  }
48
+ const viewport = window.visualViewport;
48
49
  return {
49
50
  top: 0,
50
51
  left: 0,
51
- right: window.innerWidth,
52
- bottom: window.innerHeight,
52
+ right: viewport?.width ?? window.innerWidth,
53
+ bottom: viewport?.height ?? window.innerHeight,
53
54
  };
54
55
  }
55
56
  function computeCornerPosition(triggerRect, popupWidth, popupHeight, bounds, preferredPosition) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paroicms/react-ui",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "description": "React UI toolkit for ParoiCMS.",
5
5
  "keywords": [
6
6
  "paroicms",
@@ -24,6 +24,13 @@
24
24
  background: var(--color-bg-subtle);
25
25
  transition: background-color var(--transition);
26
26
 
27
+ &:disabled,
28
+ &.disabled {
29
+ color: var(--color-text-muted);
30
+ pointer-events: none;
31
+ cursor: not-allowed;
32
+ }
33
+
27
34
  /* &:hover {
28
35
  background: var(--color-bg-section);
29
36
  } */
package/styles/Alert.css CHANGED
@@ -55,10 +55,16 @@
55
55
  color 0.15s,
56
56
  background-color 0.15s;
57
57
 
58
- &:hover {
58
+ &:hover:not(:disabled) {
59
59
  color: var(--color-text);
60
60
  background: rgb(0 0 0 / 10%);
61
61
  }
62
+
63
+ &:disabled {
64
+ color: var(--color-text-light);
65
+ pointer-events: none;
66
+ cursor: not-allowed;
67
+ }
62
68
  }
63
69
 
64
70
  .PaAlert-title {
@@ -21,15 +21,34 @@
21
21
  font-size: var(--text-sm);
22
22
  color: var(--color-text-muted);
23
23
  white-space: nowrap;
24
+
25
+ &.iconBtn {
26
+ display: flex;
27
+ align-items: center;
28
+ justify-content: center;
29
+ width: 32px;
30
+ height: 32px;
31
+ }
32
+ }
33
+
34
+ ._mobile .PaNavbar-item {
35
+ padding: 0 var(--space-2);
24
36
  }
25
37
 
26
38
  .PaBreadcrumb-link:is(a, button) {
27
39
  cursor: pointer;
28
40
  transition: color var(--transition);
29
41
 
30
- &:hover {
42
+ &:hover:not(:disabled):not(.disabled) {
31
43
  color: var(--color-primary);
32
44
  }
45
+
46
+ &:disabled,
47
+ &.disabled {
48
+ color: var(--color-text-muted);
49
+ pointer-events: none;
50
+ cursor: not-allowed;
51
+ }
33
52
  }
34
53
 
35
54
  .PaBreadcrumb-separator {
@@ -45,13 +64,3 @@
45
64
  height: 16px;
46
65
  }
47
66
  }
48
-
49
- .PaBreadcrumb-current {
50
- max-width: 200px;
51
- overflow: hidden;
52
- text-overflow: ellipsis;
53
- font-size: var(--text-sm);
54
- font-weight: var(--font-medium);
55
- color: var(--color-text);
56
- white-space: nowrap;
57
- }
package/styles/Button.css CHANGED
@@ -1,6 +1,7 @@
1
1
  /* ========================================
2
2
  Buttons
3
3
  ======================================== */
4
+ ._paForm button,
4
5
  .PaBtn {
5
6
  display: inline-flex;
6
7
  gap: var(--space-2);
@@ -24,11 +25,12 @@
24
25
  }
25
26
 
26
27
  /* Primary button */
28
+ &,
27
29
  &.primary {
28
30
  color: var(--color-text-inverse);
29
31
  background: var(--color-primary);
30
32
 
31
- &:hover {
33
+ &:hover:not(:disabled):not(.disabled) {
32
34
  background: var(--color-primary-hover);
33
35
  }
34
36
 
@@ -40,10 +42,10 @@
40
42
  /* Secondary button (outline) */
41
43
  &.secondary {
42
44
  color: var(--color-text-muted);
43
- background: transparent;
45
+ background: var(--color-bg-muted);
44
46
  border: 1px solid var(--color-border);
45
47
 
46
- &:hover {
48
+ &:hover:not(:disabled):not(.disabled) {
47
49
  color: var(--color-text);
48
50
  background: var(--color-bg-subtle);
49
51
  border-color: var(--color-text-muted);
@@ -60,7 +62,7 @@
60
62
  color: var(--color-text-muted);
61
63
  background: transparent;
62
64
 
63
- &:hover {
65
+ &:hover:not(:disabled):not(.disabled) {
64
66
  color: var(--color-text);
65
67
  background: var(--color-bg-subtle);
66
68
  }
@@ -71,7 +73,7 @@
71
73
  color: var(--color-text-inverse);
72
74
  background: var(--color-danger);
73
75
 
74
- &:hover {
76
+ &:hover:not(:disabled):not(.disabled) {
75
77
  background: var(--color-danger-dark);
76
78
  }
77
79
  }
@@ -82,7 +84,7 @@
82
84
  background: transparent;
83
85
  border: 2px solid var(--color-yellow);
84
86
 
85
- &:hover {
87
+ &:hover:not(:disabled):not(.disabled) {
86
88
  background: var(--color-yellow-light);
87
89
  }
88
90
  }
@@ -107,10 +109,44 @@
107
109
  /* Disabled state */
108
110
  &:disabled,
109
111
  &.disabled {
112
+ color: var(--color-text-disabled);
110
113
  pointer-events: none;
111
114
  cursor: not-allowed;
112
- opacity: 0.6;
113
- filter: grayscale(40%);
115
+ background: var(--color-bg-disabled);
116
+ border-color: var(--color-bg-disabled);
117
+
118
+ &.outlined {
119
+ background: transparent;
120
+ border-color: var(--color-border-disabled);
121
+ }
122
+
123
+ &.primary {
124
+ color: var(--color-primary-disabled);
125
+ background: var(--color-primary-bg-disabled);
126
+ border-color: var(--color-primary-bg-disabled);
127
+
128
+ &.outlined {
129
+ background: transparent;
130
+ border-color: var(--color-primary-disabled);
131
+ }
132
+ }
133
+
134
+ &.danger {
135
+ color: var(--color-danger-disabled);
136
+ background: var(--color-danger-bg-disabled);
137
+ border-color: var(--color-danger-bg-disabled);
138
+
139
+ &.outlined {
140
+ background: transparent;
141
+ border-color: var(--color-danger-disabled);
142
+ }
143
+ }
144
+
145
+ &.warning {
146
+ color: var(--color-warning-disabled);
147
+ background: transparent;
148
+ border-color: var(--color-warning-disabled);
149
+ }
114
150
  }
115
151
 
116
152
  /* Outlined variant */
@@ -119,7 +155,7 @@
119
155
  border: 1px solid currentColor;
120
156
  }
121
157
 
122
- &.danger.outlined {
158
+ &.danger.outlined:not(:disabled):not(.disabled) {
123
159
  color: var(--color-danger);
124
160
 
125
161
  &:hover {
@@ -127,7 +163,7 @@
127
163
  }
128
164
  }
129
165
 
130
- &.primary.outlined {
166
+ &.primary.outlined:not(:disabled):not(.disabled) {
131
167
  color: var(--color-primary);
132
168
 
133
169
  &:hover {
@@ -9,6 +9,7 @@
9
9
  user-select: none;
10
10
  }
11
11
 
12
+ ._paForm input[type="checkbox"],
12
13
  .PaCheckboxInput {
13
14
  display: flex;
14
15
  flex-shrink: 0;
@@ -51,7 +52,8 @@
51
52
 
52
53
  &:disabled {
53
54
  cursor: not-allowed;
54
- opacity: 0.5;
55
+ background: var(--color-bg-muted);
56
+ border-color: var(--color-border-light);
55
57
  }
56
58
  }
57
59