@paroicms/react-ui 0.4.3 → 0.5.0

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 (130) hide show
  1. package/dist/Accordion.d.ts +10 -0
  2. package/dist/{PuAccordion.jsx → Accordion.js} +5 -8
  3. package/dist/Alert.d.ts +10 -0
  4. package/dist/Alert.js +7 -0
  5. package/dist/Badge.d.ts +8 -0
  6. package/dist/Badge.js +6 -0
  7. package/dist/Breadcrumb.d.ts +14 -0
  8. package/dist/Breadcrumb.js +10 -0
  9. package/dist/Button.d.ts +36 -0
  10. package/dist/Button.js +74 -0
  11. package/dist/Card.d.ts +10 -0
  12. package/dist/Card.js +7 -0
  13. package/dist/Checkbox.d.ts +9 -0
  14. package/dist/Checkbox.js +12 -0
  15. package/dist/Chip.d.ts +8 -0
  16. package/dist/Chip.js +7 -0
  17. package/dist/Column.d.ts +14 -0
  18. package/dist/Column.js +7 -0
  19. package/dist/DataTable.d.ts +30 -0
  20. package/dist/DataTable.js +26 -0
  21. package/dist/DateInput.d.ts +9 -0
  22. package/dist/DateInput.js +7 -0
  23. package/dist/Dialog.d.ts +13 -0
  24. package/dist/Dialog.js +55 -0
  25. package/dist/Inplace.d.ts +12 -0
  26. package/dist/Inplace.js +16 -0
  27. package/dist/InputNumber.d.ts +10 -0
  28. package/dist/InputNumber.js +19 -0
  29. package/dist/InputText.d.ts +14 -0
  30. package/dist/InputText.js +11 -0
  31. package/dist/MenuItem.d.ts +8 -0
  32. package/dist/MenuItem.js +18 -0
  33. package/dist/MultiSelect.d.ts +17 -0
  34. package/dist/MultiSelect.js +50 -0
  35. package/dist/Panel.d.ts +11 -0
  36. package/dist/Panel.js +9 -0
  37. package/dist/PasswordInput.d.ts +8 -0
  38. package/dist/PasswordInput.js +10 -0
  39. package/dist/PopupMenu.d.ts +20 -0
  40. package/dist/{PuPopupMenu.jsx → PopupMenu.js} +45 -36
  41. package/dist/RadioButton.d.ts +9 -0
  42. package/dist/RadioButton.js +12 -0
  43. package/dist/Select.d.ts +18 -0
  44. package/dist/Select.js +11 -0
  45. package/dist/SideMenu.d.ts +5 -0
  46. package/dist/SideMenu.js +13 -0
  47. package/dist/SortableList.d.ts +19 -0
  48. package/dist/SortableList.js +27 -0
  49. package/dist/Spinner.d.ts +2 -0
  50. package/dist/Spinner.js +5 -0
  51. package/dist/SplitButton.d.ts +24 -0
  52. package/dist/SplitButton.js +42 -0
  53. package/dist/Switch.d.ts +9 -0
  54. package/dist/Switch.js +12 -0
  55. package/dist/Tabs.d.ts +22 -0
  56. package/dist/Tabs.js +21 -0
  57. package/dist/Textarea.d.ts +8 -0
  58. package/dist/Textarea.js +7 -0
  59. package/dist/ToggleButton.d.ts +11 -0
  60. package/dist/ToggleButton.js +6 -0
  61. package/dist/ToggleGroup.d.ts +15 -0
  62. package/dist/ToggleGroup.js +6 -0
  63. package/dist/Tooltip.d.ts +10 -0
  64. package/dist/Tooltip.js +50 -0
  65. package/dist/Tree.d.ts +22 -0
  66. package/dist/Tree.js +43 -0
  67. package/dist/alert-stack.d.ts +18 -0
  68. package/dist/alert-stack.js +72 -0
  69. package/dist/index.d.ts +36 -10
  70. package/dist/index.js +45 -10
  71. package/dist/paroi-ui-lib-types.d.ts +4 -4
  72. package/dist/react-ui-provider.d.ts +15 -0
  73. package/dist/react-ui-provider.js +22 -0
  74. package/package.json +18 -4
  75. package/styles/Accordion.css +46 -0
  76. package/styles/Alert.css +76 -0
  77. package/styles/Badge.css +59 -0
  78. package/styles/Breadcrumb.css +57 -0
  79. package/styles/Button.css +167 -0
  80. package/styles/Card.css +28 -0
  81. package/styles/Checkbox.css +61 -0
  82. package/styles/Chip.css +35 -0
  83. package/styles/DataTable.css +176 -0
  84. package/styles/DateInput.css +59 -0
  85. package/styles/Dialog.css +77 -0
  86. package/styles/Inplace.css +44 -0
  87. package/styles/InputNumber.css +60 -0
  88. package/styles/InputText.css +99 -0
  89. package/styles/MenuItem.css +169 -0
  90. package/styles/MultiSelect.css +158 -0
  91. package/styles/Panel.css +40 -0
  92. package/styles/PasswordInput.css +80 -0
  93. package/styles/PopupMenu.css +37 -0
  94. package/styles/RadioButton.css +60 -0
  95. package/styles/Select.css +72 -0
  96. package/styles/SideMenu.css +7 -0
  97. package/styles/SortableList.css +32 -0
  98. package/styles/Spinner.css +30 -0
  99. package/styles/SplitButton.css +143 -0
  100. package/styles/Switch.css +60 -0
  101. package/styles/Tabs.css +94 -0
  102. package/styles/Textarea.css +66 -0
  103. package/styles/ToggleButton.css +36 -0
  104. package/styles/ToggleGroup.css +55 -0
  105. package/styles/Tooltip.css +34 -0
  106. package/styles/Tree.css +161 -0
  107. package/styles/theme/base.css +40 -0
  108. package/styles/theme/common.css +410 -0
  109. package/styles/theme/index.css +15 -0
  110. package/styles/theme/margins.css +119 -0
  111. package/styles/theme/reset.css +119 -0
  112. package/styles/theme/tokens.css +226 -0
  113. package/dist/PuAccordion.d.ts +0 -9
  114. package/dist/PuButton.d.ts +0 -14
  115. package/dist/PuButton.jsx +0 -15
  116. package/dist/PuCheckbox.d.ts +0 -8
  117. package/dist/PuCheckbox.jsx +0 -13
  118. package/dist/PuInput.d.ts +0 -10
  119. package/dist/PuInput.jsx +0 -13
  120. package/dist/PuMenuItem.d.ts +0 -7
  121. package/dist/PuMenuItem.jsx +0 -33
  122. package/dist/PuPopupMenu.d.ts +0 -14
  123. package/dist/PuSelect.d.ts +0 -17
  124. package/dist/PuSelect.jsx +0 -24
  125. package/dist/PuSideMenu.d.ts +0 -4
  126. package/dist/PuSideMenu.jsx +0 -15
  127. package/dist/PuSpinner.d.ts +0 -1
  128. package/dist/PuSpinner.jsx +0 -3
  129. package/dist/svg-icons.d.ts +0 -5
  130. package/dist/svg-icons.jsx +0 -30
@@ -0,0 +1,50 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import "../styles/MultiSelect.css";
3
+ import { clsx } from "clsx";
4
+ import { ChevronDown, X } from "lucide-react";
5
+ import { useEffect, useRef, useState } from "react";
6
+ export function MultiSelect({ value, onChange, options, label, error, className, placeholder, disabled, }) {
7
+ const [open, setOpen] = useState(false);
8
+ const containerRef = useRef(null);
9
+ // Close dropdown when clicking outside
10
+ useEffect(() => {
11
+ if (!open)
12
+ return;
13
+ const handleClickOutside = (e) => {
14
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
15
+ setOpen(false);
16
+ }
17
+ };
18
+ document.addEventListener("mousedown", handleClickOutside);
19
+ return () => document.removeEventListener("mousedown", handleClickOutside);
20
+ }, [open]);
21
+ const toggleOption = (optionValue) => {
22
+ if (value.includes(optionValue)) {
23
+ onChange(value.filter((v) => v !== optionValue));
24
+ }
25
+ else {
26
+ onChange([...value, optionValue]);
27
+ }
28
+ };
29
+ const removeValue = (optionValue) => {
30
+ onChange(value.filter((v) => v !== optionValue));
31
+ };
32
+ const selectedOptions = options.filter((opt) => value.includes(opt.value));
33
+ const controlElement = (_jsxs(_Fragment, { children: [_jsxs("span", { className: clsx("PaMultiSelect-control", open && "open"), onClick: () => !disabled && setOpen(!open), onKeyDown: (e) => {
34
+ if (e.key === "Enter" || e.key === " ") {
35
+ e.preventDefault();
36
+ if (!disabled)
37
+ setOpen(!open);
38
+ }
39
+ }, tabIndex: disabled ? -1 : 0, role: "combobox", "aria-expanded": open, "aria-haspopup": "listbox", children: [_jsx("span", { className: "PaMultiSelect-values", children: selectedOptions.length > 0 ? (selectedOptions.map((opt) => (_jsxs("span", { className: "PaMultiSelect-chip", children: [opt.label, _jsx("button", { type: "button", className: "PaMultiSelect-chipRemove", onClick: (e) => {
40
+ e.stopPropagation();
41
+ removeValue(opt.value);
42
+ }, "aria-label": `Remove ${opt.label}`, children: _jsx(X, { size: 12 }) })] }, opt.value)))) : (_jsx("span", { className: "PaMultiSelect-placeholder", children: placeholder })) }), _jsx(ChevronDown, { className: "PaMultiSelect-icon", size: 16 })] }), open && (_jsx("span", { className: "PaMultiSelect-dropdown", role: "listbox", children: options.map((option) => (_jsxs("span", { className: clsx("PaMultiSelect-option", value.includes(option.value) && "selected", option.disabled && "disabled"), onClick: () => !option.disabled && toggleOption(option.value), onKeyDown: (e) => {
43
+ if (e.key === "Enter" || e.key === " ") {
44
+ e.preventDefault();
45
+ if (!option.disabled)
46
+ toggleOption(option.value);
47
+ }
48
+ }, tabIndex: option.disabled ? -1 : 0, role: "option", "aria-selected": value.includes(option.value), children: [_jsx("span", { className: "PaMultiSelect-checkbox", children: value.includes(option.value) && "✓" }), option.label] }, option.value))) }))] }));
49
+ return (_jsxs("span", { ref: containerRef, className: clsx("PaMultiSelect", error && "error", disabled && "disabled", className), children: [label ? (_jsxs("label", { className: "PaMultiSelect-wrapper", children: [_jsx("span", { className: "PaMultiSelect-label", children: label }), controlElement] })) : (controlElement), error && _jsx("span", { className: "PaMultiSelect-error", children: error })] }));
50
+ }
@@ -0,0 +1,11 @@
1
+ import type { ReactNode } from "react";
2
+ import "../styles/Panel.css";
3
+ export interface PanelProps {
4
+ header: ReactNode;
5
+ collapsed?: boolean;
6
+ onToggle?: () => void;
7
+ children: ReactNode;
8
+ className?: string;
9
+ collapsible?: boolean;
10
+ }
11
+ export declare function Panel({ header, collapsed, onToggle, children, className, collapsible, }: PanelProps): import("react/jsx-runtime").JSX.Element;
package/dist/Panel.js ADDED
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { clsx } from "clsx";
3
+ import { ChevronDown, ChevronRight } from "lucide-react";
4
+ import "../styles/Panel.css";
5
+ export function Panel({ header, collapsed = false, onToggle, children, className, collapsible = true, }) {
6
+ const isCollapsible = collapsible && onToggle;
7
+ const headerContent = (_jsxs(_Fragment, { children: [isCollapsible && (_jsx("span", { className: "PaPanel-chevron", children: collapsed ? _jsx(ChevronRight, { size: 16 }) : _jsx(ChevronDown, { size: 16 }) })), _jsx("span", { className: "PaPanel-title", children: header })] }));
8
+ return (_jsxs("div", { className: clsx("PaPanel", collapsed && "collapsed", className), children: [isCollapsible ? (_jsx("button", { type: "button", className: clsx("PaPanel-header", "collapsible"), onClick: onToggle, "aria-expanded": !collapsed, children: headerContent })) : (_jsx("div", { className: "PaPanel-header", children: headerContent })), !collapsed && _jsx("div", { className: "PaPanel-content", children: children })] }));
9
+ }
@@ -0,0 +1,8 @@
1
+ import "../styles/PasswordInput.css";
2
+ import { type InputHTMLAttributes, type Ref } from "react";
3
+ export interface PasswordInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "type"> {
4
+ label?: string;
5
+ error?: string;
6
+ ref?: Ref<HTMLInputElement>;
7
+ }
8
+ export declare function PasswordInput({ className, label, error, ref, ...props }: PasswordInputProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import "../styles/PasswordInput.css";
3
+ import { clsx } from "clsx";
4
+ import { Eye, EyeOff } from "lucide-react";
5
+ import { useState } from "react";
6
+ export function PasswordInput({ className, label, error, ref, ...props }) {
7
+ const [visible, setVisible] = useState(false);
8
+ const inputWithToggle = (_jsxs("span", { className: clsx("PaPasswordInput-inputWrapper", error && "error"), children: [_jsx("input", { ref: ref, type: visible ? "text" : "password", className: "PaPasswordInput-input", ...props }), _jsx("button", { type: "button", className: "PaPasswordInput-toggle", onClick: () => setVisible(!visible), tabIndex: -1, "aria-label": visible ? "Hide password" : "Show password", children: visible ? _jsx(EyeOff, { size: 16 }) : _jsx(Eye, { size: 16 }) })] }));
9
+ return (_jsxs("span", { className: clsx("PaPasswordInput", className), children: [label ? (_jsxs("label", { className: "PaPasswordInput-wrapper", children: [_jsx("span", { className: "PaPasswordInput-label", children: label }), inputWithToggle] })) : (inputWithToggle), error && _jsx("span", { className: "PaPasswordInput-error", children: error })] }));
10
+ }
@@ -0,0 +1,20 @@
1
+ import "../styles/PopupMenu.css";
2
+ import { type MouseEvent, type ReactNode, type Ref } from "react";
3
+ import type { MenuNode } from "./paroi-ui-lib-types.js";
4
+ export interface PopupMenuProps {
5
+ items: MenuNode[];
6
+ className?: string;
7
+ id?: string;
8
+ autoPosition?: boolean;
9
+ /** Position of the popup relative to the trigger: 'bottom' (default) or 'top' */
10
+ position?: "bottom" | "top";
11
+ /** Optional custom trigger element. If provided, it will be used instead of the default button. */
12
+ children?: ReactNode;
13
+ ref?: Ref<PopupMenuRef>;
14
+ }
15
+ export interface PopupMenuRef {
16
+ toggle: (event: MouseEvent) => void;
17
+ show: (event: MouseEvent) => void;
18
+ hide: () => void;
19
+ }
20
+ export declare function PopupMenu({ items, className, id, autoPosition, position, children, ref, }: PopupMenuProps): import("react/jsx-runtime").JSX.Element;
@@ -1,10 +1,13 @@
1
- import { forwardRef, useEffect, useImperativeHandle, useRef } from "react";
2
- import { PuButton } from "./PuButton.jsx";
3
- import { PuMenuItem } from "./PuMenuItem.jsx";
4
- import { PopupMenuBtnSvg, SubMenuIconLeft } from "./svg-icons.jsx";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import "../styles/PopupMenu.css";
3
+ import clsx from "clsx";
4
+ import { ChevronRight, ChevronsDown } from "lucide-react";
5
+ import { cloneElement, isValidElement, useEffect, useImperativeHandle, useRef, } from "react";
6
+ import { Button } from "./Button.js";
7
+ import { MenuItem } from "./MenuItem.js";
5
8
  const isPopoverSupported = typeof HTMLElement !== "undefined" && "showPopover" in HTMLElement.prototype;
6
9
  if (!isPopoverSupported) {
7
- console?.error("PuPopupMenu: Popover API is not supported in this browser.");
10
+ console?.error("PopupMenu: Popover API is not supported in this browser.");
8
11
  }
9
12
  let seq = 0;
10
13
  /**
@@ -23,12 +26,12 @@ function setupPopoverPositioning(element, getPosition) {
23
26
  element.addEventListener("toggle", handleToggle);
24
27
  return () => element.removeEventListener("toggle", handleToggle);
25
28
  }
26
- export const PuPopupMenu = forwardRef(function PuPopupMenu({ items, className = "", id, autoPosition = true }, ref) {
29
+ export function PopupMenu({ items, className, id, autoPosition = true, position = "bottom", children, ref, }) {
27
30
  const menuRef = useRef(null);
28
- const buttonRef = useRef(null);
31
+ const triggerRef = useRef(null);
29
32
  const menuIdRef = useRef(undefined);
30
33
  if (menuIdRef.current === undefined) {
31
- menuIdRef.current = id ?? `pu-popup-menu-${++seq}`;
34
+ menuIdRef.current = id ?? `pa-popup-menu-${++seq}`;
32
35
  }
33
36
  const toggle = (_ev) => {
34
37
  if (menuRef.current) {
@@ -52,43 +55,55 @@ export const PuPopupMenu = forwardRef(function PuPopupMenu({ items, className =
52
55
  }));
53
56
  // Position popover when it opens
54
57
  useEffect(() => {
55
- if (!autoPosition || !menuRef.current || !buttonRef.current)
58
+ if (!autoPosition || !menuRef.current || !triggerRef.current)
56
59
  return;
57
60
  const menuEl = menuRef.current;
58
61
  return setupPopoverPositioning(menuEl, () => {
59
- const rect = buttonRef.current?.getBoundingClientRect();
62
+ const rect = triggerRef.current?.getBoundingClientRect();
63
+ if (!rect)
64
+ return { top: 0, left: 0 };
65
+ if (position === "top") {
66
+ // Position above the trigger
67
+ const menuHeight = menuEl.offsetHeight || 150; // Fallback height
68
+ return {
69
+ top: rect.top + window.scrollY - menuHeight - 8,
70
+ left: rect.left + window.scrollX,
71
+ };
72
+ }
73
+ // Default: position below
60
74
  return {
61
- top: rect ? rect.bottom + window.scrollY : 0,
62
- left: rect ? rect.left + window.scrollX : 0,
75
+ top: rect.bottom + window.scrollY,
76
+ left: rect.left + window.scrollX,
63
77
  };
64
78
  });
65
- }, [autoPosition]);
66
- const buttonProps = {
79
+ }, [autoPosition, position]);
80
+ const triggerProps = {
67
81
  popoverTarget: menuIdRef.current,
68
82
  popoverTargetAction: "toggle",
69
83
  };
70
- return (<>
71
- <PuButton ref={buttonRef} className={className} outlined severity="secondary" aria-label="unfold" // should be localized
72
- aria-controls={menuIdRef.current} aria-haspopup {...buttonProps}>
73
- <PopupMenuBtnSvg />
74
- </PuButton>
75
-
76
- <div className="PuPopupMenu" ref={menuRef} id={menuIdRef.current} popover="auto">
77
- <div className="PuPopupMenu-content">
78
- {items.map((item) => (<PopupMenuItemWrapper key={item.key} item={item} onHide={hide}/>))}
79
- </div>
80
- </div>
81
- </>);
82
- });
84
+ // Render custom trigger if provided, otherwise use default button
85
+ const renderTrigger = () => {
86
+ if (children && isValidElement(children)) {
87
+ return cloneElement(children, {
88
+ ref: triggerRef,
89
+ "aria-controls": menuIdRef.current,
90
+ "aria-haspopup": true,
91
+ ...triggerProps,
92
+ });
93
+ }
94
+ 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 }) }));
95
+ };
96
+ 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))) }) })] }));
97
+ }
83
98
  function PopupMenuItemWrapper({ item, onHide }) {
84
99
  const subMenuRef = useRef(null);
85
- const subMenuId = useRef(`pu-submenu-${item.key}-${Math.random().toString(36).substring(2, 9)}`);
100
+ const subMenuId = useRef(`pa-submenu-${item.key}-${Math.random().toString(36).substring(2, 9)}`);
86
101
  // For items with submenu, override the command to toggle the submenu
87
102
  const itemWithSubmenuHandling = { ...item };
88
103
  if (item.subMenu && item.subMenu.length > 0) {
89
104
  // Create submenu trigger icon
90
105
  const originalIcon = item.icon;
91
- itemWithSubmenuHandling.icon = originalIcon || <SubMenuIconLeft />;
106
+ itemWithSubmenuHandling.icon = originalIcon || _jsx(ChevronRight, { size: 14 });
92
107
  // Create a command that opens the submenu
93
108
  const originalCommand = item.command;
94
109
  itemWithSubmenuHandling.command = () => {
@@ -125,11 +140,5 @@ function PopupMenuItemWrapper({ item, onHide }) {
125
140
  return { top: 0, left: 0 };
126
141
  });
127
142
  }, [item.subMenu]);
128
- return (<div className="PuPopupMenu-item" popoverTargetAction="toggle" popoverTarget={subMenuId.current}>
129
- <PuMenuItem item={itemWithSubmenuHandling}/>
130
-
131
- {item.subMenu && (<div className="PuPopupMenu-submenu" popover="auto" id={subMenuId.current} ref={subMenuRef}>
132
- {item.subMenu.map((subItem) => (<PopupMenuItemWrapper key={subItem.key} item={subItem} onHide={onHide}/>))}
133
- </div>)}
134
- </div>);
143
+ 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))) }))] }));
135
144
  }
@@ -0,0 +1,9 @@
1
+ import type { InputHTMLAttributes } from "react";
2
+ import "../styles/RadioButton.css";
3
+ export interface RadioButtonProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "onChange"> {
4
+ checked: boolean;
5
+ onChange: (checked: boolean) => void;
6
+ label?: string;
7
+ className?: string;
8
+ }
9
+ export declare function RadioButton({ checked, onChange, label, className, ...rest }: RadioButtonProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { clsx } from "clsx";
3
+ import "../styles/RadioButton.css";
4
+ export function RadioButton({ checked, onChange, label, className, ...rest }) {
5
+ const handleChange = (e) => {
6
+ onChange(e.target.checked);
7
+ };
8
+ if (label) {
9
+ return (_jsxs("label", { className: clsx("PaRadioButton", className), children: [_jsx("input", { className: "PaRadioButton-input", type: "radio", checked: checked, onChange: handleChange, ...rest }), _jsx("span", { className: "PaRadioButton-label", children: label })] }));
10
+ }
11
+ return (_jsx("input", { className: clsx("PaRadioButton-input", className), type: "radio", checked: checked, onChange: handleChange, ...rest }));
12
+ }
@@ -0,0 +1,18 @@
1
+ import "../styles/Select.css";
2
+ import type { SelectHTMLAttributes } from "react";
3
+ export interface SelectOption {
4
+ value: string;
5
+ label: string;
6
+ disabled?: boolean;
7
+ }
8
+ export interface SelectProps extends Omit<SelectHTMLAttributes<HTMLSelectElement>, "onChange"> {
9
+ value: string;
10
+ onChange: (value: string) => void;
11
+ options: SelectOption[];
12
+ label?: string;
13
+ error?: string;
14
+ className?: string;
15
+ fullWidth?: boolean;
16
+ placeholder?: string;
17
+ }
18
+ export declare function Select({ value, onChange, options, label, error, className, fullWidth, placeholder, ...rest }: SelectProps): import("react/jsx-runtime").JSX.Element;
package/dist/Select.js ADDED
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import "../styles/Select.css";
3
+ import { clsx } from "clsx";
4
+ import { ChevronDown } from "lucide-react";
5
+ export function Select({ value, onChange, options, label, error, className, fullWidth = false, placeholder, ...rest }) {
6
+ const handleChange = (e) => {
7
+ onChange(e.target.value);
8
+ };
9
+ const selectWithIcon = (_jsxs("span", { className: "PaSelect-fieldWrapper", children: [_jsxs("select", { className: "PaSelect-field", value: value, onChange: handleChange, ...rest, children: [placeholder && (_jsx("option", { value: "", disabled: true, children: placeholder })), options.map((option) => (_jsx("option", { value: option.value, disabled: option.disabled, children: option.label }, option.value)))] }), _jsx(ChevronDown, { className: "PaSelect-icon", size: 16 })] }));
10
+ return (_jsxs("span", { className: clsx("PaSelect", error && "error", fullWidth && "fullWidth", className), children: [label ? (_jsxs("label", { className: "PaSelect-wrapper", children: [_jsx("span", { className: "PaSelect-label", children: label }), selectWithIcon] })) : (selectWithIcon), error && _jsx("span", { className: "PaSelect-error", children: error })] }));
11
+ }
@@ -0,0 +1,5 @@
1
+ import "../styles/SideMenu.css";
2
+ import type { MenuNode } from "./paroi-ui-lib-types.js";
3
+ export declare function SideMenu({ menu }: {
4
+ menu: MenuNode[];
5
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import "../styles/SideMenu.css";
3
+ import { Accordion } from "./Accordion.js";
4
+ import { MenuItem } from "./MenuItem.js";
5
+ export function SideMenu({ menu }) {
6
+ return (_jsx(_Fragment, { children: menu.map((child) => (_jsx(AccordionOrMenuItem, { item: child }, child.key))) }));
7
+ }
8
+ function AccordionOrMenuItem({ item }) {
9
+ if (item.subMenu) {
10
+ return (_jsx(Accordion, { header: item, expanded: item.expanded, children: item.subMenu.map((child) => (_jsx(AccordionOrMenuItem, { item: child }, child.key))) }));
11
+ }
12
+ return _jsx(MenuItem, { item: item });
13
+ }
@@ -0,0 +1,19 @@
1
+ import "../styles/SortableList.css";
2
+ import { type ReactNode } from "react";
3
+ export interface SortableListProps<T> {
4
+ /** Items to display and reorder */
5
+ items: T[];
6
+ /** Callback when items are reordered */
7
+ onReorder: (items: T[]) => void;
8
+ /** Field name used as unique key for each item */
9
+ keyField: keyof T & string;
10
+ /** Render function for each item */
11
+ renderItem: (item: T, index: number) => ReactNode;
12
+ /** Layout direction */
13
+ direction?: "row" | "column";
14
+ /** Container class name */
15
+ className?: string;
16
+ /** Class name applied to each item wrapper */
17
+ itemClassName?: string;
18
+ }
19
+ export declare function SortableList<T extends object>({ items, onReorder, keyField, renderItem, direction, className, itemClassName, }: SortableListProps<T>): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import "../styles/SortableList.css";
3
+ import { clsx } from "clsx";
4
+ import { useEffect, useState } from "react";
5
+ import { ReactSortable } from "react-sortablejs";
6
+ export function SortableList({ items, onReorder, keyField, renderItem, direction = "column", className, itemClassName, }) {
7
+ const [sortableItems, setSortableItems] = useState(() => items.map((item) => ({
8
+ id: String(item[keyField]),
9
+ data: item,
10
+ })));
11
+ // Sync external items to internal state
12
+ useEffect(() => {
13
+ setSortableItems(items.map((item) => ({
14
+ id: String(item[keyField]),
15
+ data: item,
16
+ })));
17
+ }, [items, keyField]);
18
+ const handleSetList = (newList) => {
19
+ // Ignore empty list updates from ReactSortable during initialization
20
+ if (newList.length === 0 && sortableItems.length > 0) {
21
+ return;
22
+ }
23
+ setSortableItems(newList);
24
+ onReorder(newList.map((item) => item.data));
25
+ };
26
+ return (_jsx(ReactSortable, { tag: "div", className: clsx("PaSortableList", direction, className), list: sortableItems, setList: handleSetList, children: sortableItems.map((item, index) => (_jsx("div", { className: clsx("PaSortableList-item", itemClassName), children: renderItem(item.data, index) }, item.id))) }));
27
+ }
@@ -0,0 +1,2 @@
1
+ import "../styles/Spinner.css";
2
+ export declare function Spinner(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import "../styles/Spinner.css";
3
+ export function Spinner() {
4
+ return _jsx("div", { className: "PaSpinner" });
5
+ }
@@ -0,0 +1,24 @@
1
+ import "../styles/SplitButton.css";
2
+ import { type ReactNode } from "react";
3
+ import { type AnchorElProps, type AsyncButtonElProps, type ButtonElProps } from "./Button.js";
4
+ export type SplitButtonProps = SplitButtonElProps | SplitAsyncButtonElProps | SplitAnchorElProps;
5
+ export interface SplitButtonElProps extends SplitButtonBaseProps, Omit<ButtonElProps, "className"> {
6
+ }
7
+ export interface SplitAsyncButtonElProps extends SplitButtonBaseProps, Omit<AsyncButtonElProps, "className"> {
8
+ }
9
+ export interface SplitAnchorElProps extends SplitButtonBaseProps, Omit<AnchorElProps, "className"> {
10
+ }
11
+ interface SplitButtonBaseProps {
12
+ items: SplitButtonItem[];
13
+ className?: string;
14
+ }
15
+ export interface SplitButtonItem {
16
+ label: string;
17
+ icon?: ReactNode;
18
+ /** can be async */
19
+ command: () => unknown;
20
+ danger?: boolean;
21
+ disabled?: boolean;
22
+ }
23
+ export declare function SplitButton(props: SplitButtonProps): import("react/jsx-runtime").JSX.Element;
24
+ export {};
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import "../styles/SplitButton.css";
3
+ import { clsx } from "clsx";
4
+ import { ChevronDown } from "lucide-react";
5
+ import { useEffect, useRef, useState } from "react";
6
+ import { Button, } from "./Button.js";
7
+ export function SplitButton(props) {
8
+ const { items, className, ...buttonProps } = props;
9
+ const [open, setOpen] = useState(false);
10
+ const containerRef = useRef(null);
11
+ const severity = props.severity ?? "primary";
12
+ const disabled = props.disabled;
13
+ // Close dropdown when clicking outside
14
+ useEffect(() => {
15
+ if (!open)
16
+ return;
17
+ const handleClickOutside = (e) => {
18
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
19
+ setOpen(false);
20
+ }
21
+ };
22
+ document.addEventListener("mousedown", handleClickOutside);
23
+ return () => document.removeEventListener("mousedown", handleClickOutside);
24
+ }, [open]);
25
+ const handleItemClick = async (item) => {
26
+ try {
27
+ await item.command();
28
+ }
29
+ catch (error) {
30
+ if (props.as === "async" && props.onError) {
31
+ props.onError(error);
32
+ }
33
+ else {
34
+ console.error("Error executing SplitButton item command:", error);
35
+ }
36
+ }
37
+ finally {
38
+ setOpen(false);
39
+ }
40
+ };
41
+ return (_jsxs("div", { ref: containerRef, className: clsx("PaSplitBtn", severity, open && "open", disabled && "disabled", className), children: [_jsx(Button, { ...buttonProps, className: "PaSplitBtn-main" }), _jsx("button", { type: "button", className: "PaSplitBtn-toggle", onClick: () => setOpen(!open), "aria-haspopup": "menu", "aria-expanded": open, children: _jsx(ChevronDown, { className: "PaSplitBtn-toggleIcon" }) }), open && (_jsx("div", { className: "PaSplitBtn-menu", role: "menu", 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))) }))] }));
42
+ }
@@ -0,0 +1,9 @@
1
+ import type { InputHTMLAttributes } from "react";
2
+ import "../styles/Switch.css";
3
+ export interface SwitchProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "onChange"> {
4
+ checked: boolean;
5
+ onChange: (checked: boolean) => void;
6
+ label?: string;
7
+ className?: string;
8
+ }
9
+ export declare function Switch({ checked, onChange, label, className, ...rest }: SwitchProps): import("react/jsx-runtime").JSX.Element;
package/dist/Switch.js ADDED
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { clsx } from "clsx";
3
+ import "../styles/Switch.css";
4
+ export function Switch({ checked, onChange, label, className, ...rest }) {
5
+ const handleChange = (e) => {
6
+ onChange(e.target.checked);
7
+ };
8
+ if (label) {
9
+ return (_jsxs("label", { className: clsx("PaSwitch", className), children: [_jsx("input", { className: "PaSwitch-input", type: "checkbox", checked: checked, onChange: handleChange, ...rest }), _jsx("span", { className: "PaSwitch-track", children: _jsx("span", { className: "PaSwitch-thumb" }) }), _jsx("span", { className: "PaSwitch-label", children: label })] }));
10
+ }
11
+ return (_jsxs("span", { className: clsx("PaSwitch", className), children: [_jsx("input", { className: "PaSwitch-input", type: "checkbox", checked: checked, onChange: handleChange, ...rest }), _jsx("span", { className: "PaSwitch-track", children: _jsx("span", { className: "PaSwitch-thumb" }) })] }));
12
+ }
package/dist/Tabs.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { type ReactNode } from "react";
2
+ import "../styles/Tabs.css";
3
+ export interface TabProps {
4
+ value: string;
5
+ label: string;
6
+ icon?: ReactNode;
7
+ disabled?: boolean;
8
+ className?: string;
9
+ }
10
+ export interface TabsProps {
11
+ children: ReactNode;
12
+ value: string;
13
+ onChange: (value: string) => void;
14
+ variant?: "default" | "pill" | "stretch";
15
+ className?: string;
16
+ }
17
+ export declare function Tabs({ children, value, onChange, variant, className }: TabsProps): import("react/jsx-runtime").JSX.Element;
18
+ /**
19
+ * Render-less component for tab definition.
20
+ * Used for configuration only - does not render anything directly.
21
+ */
22
+ export declare function Tab(_props: TabProps): null;
package/dist/Tabs.js ADDED
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { clsx } from "clsx";
3
+ import { Children, isValidElement } from "react";
4
+ import "../styles/Tabs.css";
5
+ export function Tabs({ children, value, onChange, variant = "default", className }) {
6
+ // Extract tab definitions from children
7
+ const tabs = [];
8
+ Children.forEach(children, (child) => {
9
+ if (isValidElement(child) && child.props) {
10
+ tabs.push(child.props);
11
+ }
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))) }));
14
+ }
15
+ /**
16
+ * Render-less component for tab definition.
17
+ * Used for configuration only - does not render anything directly.
18
+ */
19
+ export function Tab(_props) {
20
+ return null;
21
+ }
@@ -0,0 +1,8 @@
1
+ import "../styles/Textarea.css";
2
+ import type { Ref, TextareaHTMLAttributes } from "react";
3
+ export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
4
+ label?: string;
5
+ error?: string;
6
+ ref?: Ref<HTMLTextAreaElement>;
7
+ }
8
+ export declare function Textarea({ className, label, error, ref, ...props }: TextareaProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import "../styles/Textarea.css";
3
+ import { clsx } from "clsx";
4
+ export function Textarea({ className, label, error, ref, ...props }) {
5
+ const textareaElement = (_jsx("textarea", { ref: ref, className: clsx("PaTextarea-field", error && "error"), ...props }));
6
+ return (_jsxs("span", { className: clsx("PaTextarea", className), children: [label ? (_jsxs("label", { className: "PaTextarea-wrapper", children: [_jsx("span", { className: "PaTextarea-label", children: label }), textareaElement] })) : (textareaElement), error && _jsx("span", { className: "PaTextarea-error", children: error })] }));
7
+ }
@@ -0,0 +1,11 @@
1
+ import "../styles/ToggleButton.css";
2
+ import type { ReactNode } from "react";
3
+ export interface ToggleButtonProps {
4
+ checked: boolean;
5
+ onChange: (checked: boolean) => void;
6
+ children: ReactNode;
7
+ className?: string;
8
+ disabled?: boolean;
9
+ "aria-label"?: string;
10
+ }
11
+ export declare function ToggleButton({ checked, onChange, children, className, disabled, ...rest }: ToggleButtonProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import "../styles/ToggleButton.css";
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 }));
6
+ }
@@ -0,0 +1,15 @@
1
+ import "../styles/ToggleGroup.css";
2
+ import type { ReactNode } from "react";
3
+ export interface ToggleGroupOption {
4
+ value: string;
5
+ label: ReactNode;
6
+ disabled?: boolean;
7
+ }
8
+ export interface ToggleGroupProps {
9
+ value: string;
10
+ onChange: (value: string) => void;
11
+ options: ToggleGroupOption[];
12
+ className?: string;
13
+ disabled?: boolean;
14
+ }
15
+ export declare function ToggleGroup({ value, onChange, options, className, disabled }: ToggleGroupProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import "../styles/ToggleGroup.css";
3
+ import { clsx } from "clsx";
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))) }));
6
+ }
@@ -0,0 +1,10 @@
1
+ import "../styles/Tooltip.css";
2
+ import { type ReactNode } from "react";
3
+ export interface TooltipProps {
4
+ content: string;
5
+ position?: "top" | "bottom" | "left" | "right";
6
+ children: ReactNode;
7
+ className?: string;
8
+ delay?: number;
9
+ }
10
+ export declare function Tooltip({ content, position, children, className, delay, }: TooltipProps): import("react/jsx-runtime").JSX.Element;